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.
| Location | Role today |
|---|---|
plugin/cli/sdlc.ts | Hand-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.ts | Entity projection (T-0011, done) — discovers/reads entities; the substrate the walk builds on. |
plugin/cli/_common.ts | Owns 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/§2a | Specifies the registry, the module-walk, and explicit registration. |
Proposed
Section titled “Proposed”plugin/lib/registry.ts exposing:
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.- A discovery walk — at startup, walk
plugin/lib/model/entities/<type>/ops/andplugin/lib/services/for modules adhering to the structural API and register each. ExplicitdefineOp(...)registration stays first-class for non-entity / project-level ops (doctor,reconcile, one-offs) per D0007 §2a — both paths feed the same registry. OpError— a typed error taxonomy ({ code, message, … }) returned/thrown by handlers; the exit-code table moves here fromcli/_common.ts. Each adapter maps a code to its native failure.- The
ctxobject —{ 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). - 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.
Approach
Section titled “Approach”- Define the
OpDescriptortype anddefineOp(validates the descriptor shape; registers into the runtime index). Move theOpErrortaxonomy out ofcli/_common.tsintoregistry.ts. - Define
ctxand the handler signature(input, ctx) => Result<output, OpError>; threaddryRun/io/git/ghthrough it. - Implement the discovery walk over
entities/<type>/ops/+services/, usingentity.tsdiscovery where it helps; load conforming modules and register their descriptors. - Keep explicit
defineOp(...)registration working for non-entity ops; provide a small registration entry point services/project ops call. - Add a
project-checkthat asserts structural-API conformance (every discovered module exports a valid descriptor; no malformed/missing export silently drops a command). - 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.
Files to touch
Section titled “Files to touch”| Location | Kind | Change |
|---|---|---|
plugin/lib/registry.ts | new | defineOp, runtime index, discovery walk, query surface |
plugin/lib/<error module> (or registry.ts) | new | OpError taxonomy (moved from cli/_common.ts) |
plugin/lib/model/entity.ts | modify | expose the per-entity ops/ discovery the walk needs |
plugin/cli/_common.ts | modify | re-export / delegate exit codes to the registry’s OpError |
.claude/skills/project-check/ | new | structural-API conformance check |
Acceptance criteria
Section titled “Acceptance criteria”- AC-1: A module under
entities/<type>/ops/exporting a validdefineOpdescriptor 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
OpErrorsurfaces itscode; a unit test asserts the code → CLI exit-code mapping (the taxonomy that was incli/_common.ts). - AC-4: Handlers receive
(input, ctx)and read no ambient cwd/env; a test drives an op with an injectedctx(customprojectRoot,dryRun: true) and asserts no filesystem writes under dry-run. - AC-5: A
project-checkfails when a module under a discoveredops/dir exports a malformed/missing descriptor.
Out of scope
Section titled “Out of scope”- 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).
Dependencies
Section titled “Dependencies”- D-0007-deterministic-op-substrate (accepted; §2/§2a is the spec) and D-0004-entity-definition-architecture (accepted; operations-module layout) — the contracts.
- T-0011 (
entity.tsprojection, done) — the discovery foundation. - Feeds T-87GH-new-scripts-derive-schema-bound-values, T-0010, T-0014.
Discovery context
Section titled “Discovery context”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.