Skip to content

T-FCVG-op-registry-and-module-walk

Status: closed/done · Impact: high · Complexity: large

D-0007-deterministic-op-substrate §2/§2a fixes a runtime op registry as the substrate’s source of truth: ops are declared once (defineOp) and the adapters (CLI/MCP/HTTP) are generated from the composed registry. Nothing implements it yet — commands are hand-wired in plugin/cli/sdlc.ts’s NOUNS array. This task builds the registry: the defineOp descriptor, the discovery walk over the structural API, the OpError taxonomy, and the (input, ctx) DI seam. It is the keystone — the pipeline (T-87GH-new-scripts-derive-schema-bound-values), the ops relocation (T-0010), and the generated CLI (T-0014) all build on it.

LocationRole today
plugin/cli/sdlc.tsHand-maintained NOUNS array (backlog/task/lease), each with explicit build/dispatch callbacks. No registry; adding a command means hand-wiring an entry.
plugin/lib/model/entity.tsEntity projection (T-0011, done) — discovers/reads entities; the substrate the walk builds on.
plugin/cli/_common.tsOwns the exit-code taxonomy (lease 2–7, backlog 8) — CLI-local; D0007 moves this into the registry as OpError.
[D-0007-deterministic-op-substrate](/planning/decisions/deterministic-op-substrate/) §2/§2aSpecifies the registry, the module-walk, and explicit registration.

plugin/lib/registry.ts exposing:

  1. defineOp(descriptor) — the structural API: { noun, verb, input (Zod), output (Zod), cli (flag-ergonomics hints), handler(input, ctx) }. Registers the descriptor into a process-level runtime index.
  2. A discovery walk — at startup, walk plugin/lib/model/entities/<type>/ops/ and plugin/lib/services/ for modules adhering to the structural API and register each. Explicit defineOp(...) registration stays first-class for non-entity / project-level ops (doctor, reconcile, one-offs) per D0007 §2a — both paths feed the same registry.
  3. OpError — a typed error taxonomy ({ code, message, … }) returned/thrown by handlers; the exit-code table moves here from cli/_common.ts. Each adapter maps a code to its native failure.
  4. The ctx object{ projectRoot, dryRun, io, git, gh }, passed as the second handler arg so no handler reads ambient cwd/env (makes ops testable and off-process-callable).
  5. A query surface — list/lookup ops by noun/verb so adapters (and a project-check) can enumerate the registry.

Adapters are out of scope here (T0014 builds the CLI generator); this task delivers the registry + walk + the contracts the adapters consume.

  1. Define the OpDescriptor type and defineOp (validates the descriptor shape; registers into the runtime index). Move the OpError taxonomy out of cli/_common.ts into registry.ts.
  2. Define ctx and the handler signature (input, ctx) => Result<output, OpError>; thread dryRun/io/git/gh through it.
  3. Implement the discovery walk over entities/<type>/ops/ + services/, using entity.ts discovery where it helps; load conforming modules and register their descriptors.
  4. Keep explicit defineOp(...) registration working for non-entity ops; provide a small registration entry point services/project ops call.
  5. Add a project-check that asserts structural-API conformance (every discovered module exports a valid descriptor; no malformed/missing export silently drops a command).
  6. Provide the registry query surface T0014’s CLI generator will consume; leave a thin shim so the existing NOUNS-wired commands keep working during the transition.
LocationKindChange
plugin/lib/registry.tsnewdefineOp, runtime index, discovery walk, query surface
plugin/lib/<error module> (or registry.ts)newOpError taxonomy (moved from cli/_common.ts)
plugin/lib/model/entity.tsmodifyexpose the per-entity ops/ discovery the walk needs
plugin/cli/_common.tsmodifyre-export / delegate exit codes to the registry’s OpError
.claude/skills/project-check/newstructural-API conformance check
  • AC-1: A module under entities/<type>/ops/ exporting a valid defineOp descriptor is registered by the discovery walk and is enumerable from the registry — with no edit to any hand-maintained list.
  • AC-2: An explicit defineOp(...) call (a non-entity/project-level op) registers into the same registry and is enumerable alongside discovered ops.
  • AC-3: A handler returning an OpError surfaces its code; a unit test asserts the code → CLI exit-code mapping (the taxonomy that was in cli/_common.ts).
  • AC-4: Handlers receive (input, ctx) and read no ambient cwd/env; a test drives an op with an injected ctx (custom projectRoot, dryRun: true) and asserts no filesystem writes under dry-run.
  • AC-5: A project-check fails when a module under a discovered ops/ dir exports a malformed/missing descriptor.
  • The adapters — the CLI generator that turns the registry into sdlc <type> <op> is T-0014; MCP/HTTP are later. This task delivers only the registry + walk + contracts.
  • The authoring pipeline (schema-driven frontmatter + Eta body) — T-87GH-new-scripts-derive-schema-bound-values.
  • Relocating existing scaffolder logic into ops/ modules — T-0010 (consumes this registry).

Surfaced 2026-06-02 by “do we have the registry implemented to register entity commands and expose them in the adapter layer?” — the answer was no (registry and adapters are design-only), and D0007 §2a was added to fix the composition model (runtime registry via module-walk + explicit registration). This task is that model’s implementation.


← Back to Tasks