Files
agentic-dev/docs/guides/scaffolding-a-feature.md
Danijel Martinek 48bf4da4cd refactor(navigation)!: delete navigation demo feature
Veect retrofit (ADR-027): fifth and final slice of the demo-content
removal. Deletes packages/navigation whole and prunes every composition
edge in one commit: core-api router mount + dep + router test, core-cms
header-global composition + dep + regenerated Payload types (globals
now empty), web-next bindAll (prod + dev-seed) + tests + Tailwind
source + transpilePackages + dep, cms/core-cms payload config test
assertions, home e2e nav assertion, tsconfig paths, fallow
ignoreDependencies entry, anchor-guard FEATURES list, generator e2e
strip lists + reference-feature comments (navigation -> auth, incl.
feature templates + scaffolding guide), lockfile prune, and
feature-list doc trims (CLAUDE.md, AGENTS.md, glossary, app/feature
AGENTS.md). Compliance YAML regeneration produced no churn (navigation
declared no PII).

Cycle break: navigation's UI hooks were the last edge closing the
committed core-trpc -> core-api -> navigation -> core-trpc package
cycle. With it gone, the lint turbo task graph builds for the first
time and every package's ESLint executes; the epic's lint waiver
expires here. Latent findings: 3 errors, all mechanical, fixed
in-slice - require() import in turbo/generators/config.ts
(no-require-imports), literal type assertion in auth's
authentication.service.ts (prefer-as-const), and next-env.d.ts
triple-slash in apps/cms (rule scoped off for that generated file,
mirroring web-next's existing override). 99 warn-severity findings
remain across 5 packages (pii-declaration-must-be-complete on test
fixtures, turbo/no-undeclared-env-vars on test env keys) - all
warn-by-design, non-gating.

core-trpc keeps a consumer (apps/web-next providers) and stays per
ADR-027. Its unused @trpc/react-query dependency, surfaced by the
post-deletion fallow audit, is removed rather than ignore-listed -
core-trpc's hooks use @trpc/tanstack-react-query. Remaining fallow
warn (auth validateSession "unused member") is a false positive: the
method implements IAuthenticationService and is exercised in
container.test.ts; auth stays untouched as the regression canary.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016j8z4VHjedXDTjEDNg7qHK
2026-07-12 19:34:54 +02:00

6.7 KiB

Scaffolding a feature

turbo gen feature produces a feature package under packages/<name>/ matching the shape of the reference auth feature.

Invoking the generator

Interactive (recommended for first runs — Plop will prompt for each value):

pnpm turbo gen feature

Non-interactive (positional bypass — order matches the prompts in turbo/generators/config.ts):

pnpm turbo gen feature --args <name> <Entity> <entities-plural>
Position Prompt Example Conventions
<name> Feature package name widgets kebab-case, becomes @repo/<name> and packages/<name>/
<Entity> Entity name Widget PascalCase singular, drives class/symbol/use-case names
<entities-plural> Entity plural slug widgets kebab-case, used for the future Payload collection slug

Example end-to-end:

pnpm turbo gen feature --args widgets Widget widgets
pnpm install                              # link the new workspace package
pnpm --filter @repo/widgets lint typecheck test

Conformance-ready by default

Since milestone v of the conformance system, pnpm turbo gen feature <name> emits two conformance artefacts:

  • src/feature.manifest.ts declaring the scaffolded getX use case
  • src/di/bind-production.ts with assertFeatureConformance(...) called at the tail

Run pnpm conformance after generating a feature — it should pass cleanly. If you add bus.publish("X") calls in a factory body, you'll also need to add "X" to the manifest's publishes[] array for that use case, or the no-undeclared-event-publish ESLint rule will warn.

See docs/guides/conformance-quickref.md for the manifest field reference.

What it generates

  • Package files: package.json, tsconfig.json, vitest.config.ts, eslint.config.js, turbo.json, AGENTS.md
  • One entity (src/entities/models/<entity>.ts) with a Zod schema + unit test
  • One use case (src/application/use-cases/get-<entity>.use-case.ts) with exported input/output schemas, factory function, and tests
  • One controller (src/interface-adapters/controllers/get-<entity>.controller.ts) with the canonical safeParse → presenter shape
  • Mock + real repository (src/infrastructure/repositories/<entity>.repository{,.mock}.ts). Both wrap calls in tracer.startSpan; the real repo also calls logger.captureException on errors. The real repo body is a stub that returns null until you wire a Payload collection.
  • DI: symbols.ts, module.ts, container.ts, plus bind-production.ts and bind-dev-seed.ts that compose withSpan(tracer, opts, withCapture(logger, tags, factory(deps))) at bind time (span + capture sandwich pattern)
  • tRPC integration: procedures.ts (feature-scoped error middleware) and router.ts exposing get<Entity> with full router tests including BAD_REQUEST / NOT_FOUND mapping
  • Contract suite (__contracts__/), dev seed (__seeds__/dev.ts), and empty stubs for __factories__/ and ui/

Scope (intentionally limited)

The generator does NOT yet emit:

  • Payload CMS collection / global templates (integrations/cms/**)
  • React Query option builders (ui/query.ts)
  • Faker-driven defineFactory<Entity> factories (only stubs)
  • Multi-entity / multi-use-case (one get<Entity> only)
  • Aggregator wiring across the monorepo

Add these by hand once the entity stabilises. The generator's stub files clearly mark each TODO.

Manual aggregator wiring (printed on success)

After running the generator, hand-edit these files to mount the new feature on the app's runtime composition graph:

  1. apps/web-next/src/server/bind-production.ts — import bindProduction<Name> and bindDevSeed<Name> and call them from the bindAll() dispatcher (production branch + dev-seed branch).
  2. packages/core-api/src/root.ts — import <name>Router from @repo/<name>/api and mount it on the app router.
  3. packages/core-api/package.json — add "@repo/<name>": "workspace:*" to dependencies.
  4. apps/web-next/package.json — add "@repo/<name>": "workspace:*" to dependencies.
  5. (Later) Payload CMS — add a collection at packages/<name>/src/integrations/cms/collections/<entities-plural>.ts and register it in packages/core-cms/....
  6. Verify: pnpm --filter @repo/<name> lint typecheck test

The same checklist is printed by the generator when it finishes.

Adding events and jobs to a feature

Once a feature exists, augment it with cross-feature events or background jobs using the dedicated generators. See docs/guides/events-and-jobs.md for full walkthroughs.

pnpm turbo gen event publish     # publisher contract
pnpm turbo gen event consume     # consumer handler + Payload event-task
pnpm turbo gen job               # background job + TaskConfig
pnpm turbo gen realtime channel  # realtime channel descriptor (ADR-016)
pnpm turbo gen realtime handler  # inbound realtime handler (ADR-016)
pnpm turbo gen reader            # cross-feature reader interface + implementation

The event/job generators insert at six fixed // <gen:*> anchor comments. Generated features include four of them automatically (the // <gen:job-tasks> location is in integrations/cms/index.ts, which is manually authored as part of the post-scaffold wiring); pre-existing features were retrofitted in ADR-015.

The realtime generators insert at three additional fixed // <gen:realtime-*> anchor comments (// <gen:realtime-channels> in src/index.ts, // <gen:realtime-handler-symbols> in src/di/symbols.ts, // <gen:realtime-handlers> in both bind-*.ts files). Generated features include all three automatically; pre-existing features were retrofitted in ADR-016.

  • CLAUDE.md — Key Conventions (factory-style use cases, .toDynamicValue(), schemas-in-use-case, three binding modes per feature, span + capture sandwich)
  • packages/auth/AGENTS.md — canonical reference shape the templates mirror
  • docs/architecture/vertical-feature-spec.md — design rationale for the layout
  • docs/decisions/adr-012-feature-conventions.md — file naming + factory pattern
  • docs/decisions/adr-013-input-output-unification.md — schemas-in-use-case + presenter
  • docs/decisions/adr-014-instrumentation-sentry.md — span + capture wiring
  • docs/decisions/adr-015-events-and-jobs.md — cross-feature events + background jobs
  • docs/decisions/adr-026-cross-feature-readers.md — cross-feature synchronous readers
  • docs/decisions/adr-016-realtime-layer.md — Socket.IO realtime channels + handlers