19 KiB
AGENTS.md
AI agents should read this first. Keep changes small, project-specific, and backed by code facts.
Project Snapshot
-
NestJS框架代码迁移与pnpm/Turborepo工具链切换完成:开发/构建入口为
apps/api/src,Docker运行其部署产物,旧Hono入口、路由和依赖已移除;特殊数据/发布验收仍有未完成项,尚未部署生产。进度和验收见docs/nestjs-migration.md。使用Nest Controller、Guard、Zod Pipe、Filter和构造器注入,不恢复Hono兼容路由层。 -
This repo is
NestJS + React SPA, not Next.js or SSR. -
Runtime stack: Node.js 24, NestJS 12 (Express/native ws), pnpm + Turborepo tooling, React 19, React Router 8, Vite, Tailwind CSS 4, PostgreSQL, Drizzle ORM, Better Auth, Redis, NATS, AI SDK.
-
Use the pinned
pnpmversion andpnpm-lock.yamlfor development and deployment. Do not introduce Bun/npm/yarn lockfiles. Turborepo orchestrates package builds and typechecks; turbo watch rebuilds dependencies and restarts persistent development tasks. -
pnpm workspaces:
apps/api,apps/web, and six compiled libraries underpackages/*. The dependency graph and acceptance gates are indocs/architecture-boundaries.md. Each owns its runtime dependencies; root owns lint/test/maintenance tooling. Libraries must not import either app; apps consume package exports. The old shared workspace is removed. ESLint (Web/packages/tools) and Oxlint (API) check code correctness; migration-only import restrictions and the package-boundary script are retired. Keep the dependency direction above as an architecture convention. -
Path aliases are
@app->apps/web/srcand@server->apps/api/src.@daoyou/*libraries resolve through workspace package exports (no source alias bypass); library-internal imports use relative source paths rather than their own dist exports. -
Public package APIs use business subpaths with explicit named exports in
src/public; small modules with intentional loading boundaries may remain direct exports. Do not add wildcard exports or a package-wide barrel. Keep lightweight models/helpers separate from complete validators and content registries.game-content/authoring/*is for content validation tests and maintenance tools, not application/runtime imports. See the public API policy indocs/architecture-boundaries.md.
Key Directories
apps/api/src/main.ts: Node/Nest entrypoint, HTTP/WS adapters and shutdown coordination; runtime module owns cron and messaging lifecycle.apps/api/src: Nest feature modules. Each feature owns itsapplication/implementations (sects usesorganization/); player owns state coordination, runtime owns jobs/message composition, andlibretains shared infrastructure.apps/web/src: React SPA routes, layouts, game shell, UI, hooks, providers.packages/constants/src,packages/game-domain/src,packages/combat-core/src,packages/game-content/src,packages/game-rules/src,packages/contracts/src: vocabulary, models, core, content, rules and protocols. Follow the dependency graph and acceptance checklist indocs/architecture-boundaries.md.apps/api/src/lib/drizzle/schema.ts: Drizzle schema forwanjiedaoyou_*business tables.drizzle/: Drizzle SQL migrations and snapshots.drizzle-auth/: independent Drizzle migrations and snapshots for the fixedbetter_authschema.docs/: design and architecture notes; verify against current code before treating old docs as current truth..agents/skills/: project-specific AI skills. Use the matching skill before editing that area.
Commands
pnpm install
pnpm run dev
pnpm run prd
pnpm run lint
pnpm run typecheck
pnpm run test
pnpm run build
pnpm run db:migrate
dev[:api|:web]selectsenv/local.env;prd[:api|:web]selectsenv/staging.env. Node/Nest explicitly loads the selected file; maintenance scripts usenode --env-file=... --import tsx.pnpm run builduses Turbo to build the independent API and Web packages; Nest CLI 12 buildsapps/apiwith its default tsc builder; libraries are compiled in dependency order with tsc and export JavaScript/declarations from dist; Vite buildsapps/web. The old V5 resolver Worker target was retired in Phase 10H. Preserve the remaining CI/CD entrypoints.- Vitest uses node environment and discovers pure logic tests under
packages/*/src; tests move with their owning domain during package extraction. - Docker runtime contains Node, the pnpm-deployed API production dependencies, package metadata and
dist; ALTCHA uses the server-sideALTCHA_HMAC_SECRETand does not require a frontend site key. - GitHub Actions only builds and pushes the API image on tag pushes, always publishing latest; quality checks are run locally as needed.
Skills To Use
daoyou-backend-api-security: Nest controllers/guards, auth, admin, cron/internal APIs, LLM/provider security, Redis/SMTP integration boundaries.daoyou-data-layer: Drizzle schema/migrations, repositories, transactions, Better Auth schema, durable models.daoyou-game-ui:GameViewportLayoutmain-flow scene UI structure and review rules.daoyou-item-preview: 新增道具或调整物品预览字段、文案、层级、交互与适配器时,遵循 .agents/skills/daoyou-item-preview/SKILL.md 的固定展示规范和分类基线,避免恢复已删除的冗余信息。daoyou-ink-portraits: 玩家、NPC、BOSS 与灵兽写意墨像立绘的固定笔墨基准、物种设计方法、彩墨与视觉验收;见.agents/skills/daoyou-ink-portraits/SKILL.md。daoyou-skill-totems: 技能图标的水墨图腾构形、机制辨识、固定验收参考与小尺寸防漂移;见 .agents/skills/daoyou-skill-totems/SKILL.md。daoyou-map-art: 世界总览与独立区域地图的国画水墨、彩墨、地域辨识、空间尺度及素材交付;见 .agents/skills/daoyou-map-art/SKILL.md。daoyou-game-core-domain: combat-v6 core/rules/projection, sects, equipment, manuals, beasts, and shared inventory/reward rules.daoyou-beast-design: 灵兽物种、生灵层次、命名、资质成长及出生技能池设计与审查;扩充或调整物种前读取.agents/skills/daoyou-beast-design/SKILL.md,实施同时遵守领域技能。- Only the skills present in
.agents/skills/are project skill entrypoints. For runtime work, inspectpackage.json,apps/api/src/main.ts, Vite/Docker configs anddocs/local-development.md; for condition/alchemy/market work, combine the domain, data and backend skills as applicable.
Architecture Rules
- New API routes belong to Nest feature modules imported by
apps/api/src/app.module.ts. ReuseAccessGuard/@Access,SessionService,JsonBody,FirstQuery, Zod pipes and existing exception filters. Use explicit constructor@Inject; the API build deliberately disables implicit design-type metadata. - Frontend route loaders are UX guards only; backend middleware is the security boundary.
/api/auth/*is Better Auth throughapps/api/src/lib/auth/handler.ts./internal/cron/*uses BearerCRON_SECRETwhen configured; production requires it, while non-production withoutCRON_SECRETcurrently allows the request.- Request/response contracts live in
packages/contracts/src; domain models live inpackages/game-domain/src. Use the owning package exports and do not recreate moved definitions. - At request/identity-to-domain boundaries, explicitly select business fields when constructing numeric deltas, strict runtime objects or event payloads. TypeScript structural typing does not remove extra runtime fields:
attribute_model_versionmust not enter point totals, andActiveCultivatorRef.statusmust not enter strict actor/event objects. Do not sum all values of a request object. - Resource protocol types and reducers use
@daoyou/contracts/resources; complete runtime validators are bound in each app'ssrc/lib/resources/schemas.ts. Keep the authoritative inventory and sect-delivery checks when changing resource parsing. - Domain-event transport metadata uses
@daoyou/contracts/events; the API binds its parser to the game-rules payload validators insrc/lib/mq/domainEventSchema.ts. Domain event data models live ingame-domain/events. - Dev-tool request constructors live in contracts;
apps/api/src/dev-tools/dev-tools-input.tsbinds the current cultivation/root limits and complete reward/mail validators. Keep the local-only access policy incontracts/dev-tools-accessand enforce it on the server. - Arena simulation and visibility projection use
game-rules/combat/arena;apps/api/src/combat/arena-view.tsadds the existing API/protocol fields and decorates cached round results. Durable replay models/parsing live ingame-domain/combat/replay-archive; message delivery envelopes stay in contracts. - LLM calls should use
apps/api/src/utils/aiClient.ts; BYOK validation truth ispackages/contracts/src/llm/config.ts. Server routing is oneLLM_PROVIDERtable (provider[/model][:weight]) parsed inpackages/contracts/src/llm/routing.ts; multiple routes are sticky by user id hash. Request BYOK still wins. - Treat all LLM output as untrusted. Resource, reward, cost, drop, and other state-changing numbers need deterministic service/schema/resource-layer guards.
- Server config:
apps/api/src/config/configuration.module.tsuses@nestjs/config, ignores dotenv discovery, and publishes the validated immutable environment fromlib/config/environment.ts. Use injectedAppConfigServicein Nest services and that snapshot in framework-independent libraries; do not add directprocess.envreads outside it. - Redis access must go through
apps/api/src/lib/redis; do not instantiate feature-local Redis clients. - SMTP mail goes through
apps/api/src/lib/admin/smtp.ts.
Frontend Rules
-
新增或迁移图标渲染统一使用
apps/web/src/components/ui/GameIcon.tsx:emoji 直接传值,SVG/WebP/PNG 使用icon:名称;素材统一放在apps/web/public/assets/icons/,名称与路径只在components/ui/icons/registry.ts注册,业务组件不得自行解析协议或直接引用图标文件。见docs/game-icons.md。 -
Numeric data uses Tailwind default
font-mono; prose inherits the body font. Keep quantity weight/spacing local (font-semibold tracking-tight), and do not override--font-monoor add numeric font tokens/classes. Seedocs/numeric-typography.md. -
React routes are assembled in
apps/web/src/router.tsxfromroute-definitions/**and loaded withlazyRoute. Preserve route nesting/order and implicit IDs when moving definitions. -
Game scenes use
handle={scene(...)}; the scene id must exist inapps/web/src/components/game-shell/gameNavigation.ts. -
/gameuses distinct genesis, narrative, viewport, activity, combat, map and dungeon layouts. V6 battles haveCombatV6Layout; inspectroute-definitions/game.tsxand its branches for the actual wrapper before changing a scene. -
Main-flow game UI must follow
daoyou-game-ui: identity layer, task layer, and navigation layer stay separate. -
InkPageShelland the duplicate path-basedgameShellRegistryare retired; use the actual route-definition layout nesting. -
Cross-route reusable UI belongs in
apps/web/src/components/feature/**,apps/web/src/components/ui/**, orapps/web/src/components/game-shell/**;apps/web/src/routes/game/**/componentsis page-private. -
Reuse
apps/web/src/lib/resourceshooks/store,fetchJsonCached,useTaskList, and provider contexts before adding new page-level state.
Data And Domain Rules
- Do not create parallel
src/dborapps/api/src/db; DB entrypoints areapps/api/src/lib/drizzle/db.tsandschema.ts. - The main Drizzle Kit flow manages only
wanjiedaoyou_*business tables.drizzle.auth.config.tsindependently manages the fixedbetter_authschema. - Nest
DatabaseModuleexposes the same Drizzle client throughDRIZZLE_DATABASE; never create a second pool.RuntimeServiceclosesDatabaseServiceonly after draining requests and messaging. - Pass
DbExecutor/DbTransactionthrough write paths; do not open a fresh executor inside a transaction. - Current equipment/items use
inventory_itemsandcultivator_equipment_slots; personal manuals and beasts belong to the cultivator, while sect combat progression belongs to membership. Seedaoyou-data-layerfor tables and ownership. - Runtime DB access uses
pg.Pool/ node-postgres; userunDbTaskswhen a group of reads may run inside a transaction. - Active V6 combat is Redis-authoritative; durable history uses
combat_replay_archives/combat_replay_participants, with NATS-backed terminal/replay delivery. - Character persistent state is
cultivators.condition. Bag consumable facts (includingspec) are stored ininventory_items.instance_data; residual old tables are not the V6 bag authority. - Character permanent attributes remain vitality, strength, spirit, endurance, speed, willpower. Current projection is
projectCharacterToCombatV6; display shares that V6 pipeline. - V6 core must stay independent of rules/projection/content and must not import battle-v5 or creation-v2. Do not restore old ability/tag/product projection machinery for new V6 behavior.
- Legacy tables/types can remain without being current authorities. Check runtime callers and
docs/combat-v6-legacy-table-retirement.mdbefore migration/deletion;/api/battle-records/*is removed; do not restore legacy history routes.
High-Risk Areas
- Client/server build separation,
apps/api/src/main.tsstartup/shutdown, runtime lifecycle and HTTP/WS configuration. - Auth, ALTCHA, Better Auth schema, admin allowlist, and session cookie passthrough.
- LLM provider headers, prompt schemas, resource/reward/cost parsing, and metrics.
- Drizzle migrations, legacy tables, JSONB model shape, and transaction boundaries.
GameViewportLayout, bottom dock/HUD/world-chat offset, and scene metadata.- combat-v6 core/content/projection,
condition, unified inventory, alchemy, market and resource updates. - Redis CAS/occupancy locks, NATS/outboxes, terminal settlement, cron jobs, rankings and health-check behavior.
Verification Checklist
-
Testing has only two layers: pure
packages/*/srcunit tests and Codex browser/Playwright simulations followingdocs/testing.md. Do not add one-off smoke, E2E, seed, benchmark, or fault-injection scripts. -
Local browser test accounts and their shared password are documented in
docs/testing.mdsection 3. Reuse them; query the local database read-only to select existing accounts and characters instead of asking the user for known credentials again. Complete email verification through Mailpit and never apply these credentials or test writes to staging/production. -
Unit tests are forbidden under
apps/web/srcandapps/api/src; do not add*.test.*or*.spec.*files there. -
New unit tests are allowed only for pure, deterministic, reusable engine/domain logic under
packages/*/src. Integration between pure rules and the engine belongs to the higher-level rules package; bottom-level packages must not depend on higher layers for tests. -
Do not write tests that exercise or mock databases, repositories, HTTP controllers, auth, Redis, LLM/SMTP providers, network APIs, or other third-party services.
-
Frontend and backend changes must be verified with lint, typecheck/build, code inspection, and focused manual/runtime checks instead of unit tests.
-
For request adapter or mutation changes, verify a valid request with the complete client payload through the real local endpoint; inspect committed values and subsequent resource reads. Include the relevant budget/revision boundary and invalid input. Passing invalid-input checks or testing a manually stripped domain object alone does not verify the adapter. Restore temporary local preparation data following
docs/testing.md. Migration review evidence and remaining gaps are recorded indocs/nestjs-monorepo-review.md. -
For eligible shared engine changes, pick focused tests first, then broader shared-engine checks if the blast radius is large.
-
Run
pnpm run lint,pnpm run test, orpnpm run buildwhen code/config changes justify it. -
For route/layout changes, run lint/build and inspect the affected navigation and layout behavior manually.
-
For LLM/provider changes, run lint/build and inspect provider validation and runtime behavior without adding provider integration tests.
-
For data/model changes, inspect generated migrations, transaction boundaries, and build output; do not add database/repository tests.
-
For docs/skill-only changes, inspect Markdown structure, skill validation, and
git diff; full app tests are usually unnecessary. -
Always report commands run and any checks skipped.
Working Style
- State assumptions before coding. If multiple interpretations exist, surface them.
- Prefer the minimum code that solves the request. Do not add speculative flexibility.
- Touch only files needed for the task. Do not clean unrelated code or revert user changes.
- Match existing patterns even if you would design them differently.
- Remove only unused imports/variables/functions created by your own change.
- For bugs in eligible pure shared engine logic, prefer a reproducing test first. For frontend, backend, database, or third-party behavior, use non-test verification.
This is NOT the Turborepo you know
Turborepo configuration, task behavior, and CLI commands can vary between installed versions and may differ from your training data. Resolve the turbo package from this file's directory or relevant workspace; in monorepos, it may not be visible from the repository root. For example, run node -p "require.resolve('turbo/package.json')" from a workspace that depends on turbo.
Read docs/README.md inside that installed package first, then read the relevant pages from its docs/ directory before changing Turborepo configuration or commands. Heed deprecation notices. These bundled docs match the installed package version and are available without network access.
This block is written and re-added by turbo before repository-scoped commands when an AI agent is detected. In the Turborepo source repository, its template is defined in crates/turborepo-cli/src/cli/agent_guidance.rs. Removing the managed block while updates are enabled means a later qualifying invocation will add it again. Set "agentGuidance": false in the root turbo.json or turbo.jsonc to opt out; this does not remove an existing block. Keep the block committed with your work to avoid an uncommitted change on the next agent invocation.