T-NV49-op-path-substrate
Status: closed/done · Impact: high · Complexity: large
Every port in the op-substrate sweep lands on machinery that does not exist yet: 3-segment command paths, group-aware help, hidden leaves, long-running services, and non-JSON output. Land that substrate first so the thirteen port tasks are mechanical. This is D-0007 §Migration step 1 (taxonomy + registry contract first) applied to the adversarially-reviewed surface in D-H7FS-op-substrate-surface / docs/plans/op-substrate-full-sweep.md.
| Location | Role today |
|---|---|
plugin/lib/registry.ts#OpDescriptor | {noun, verb} strings; keyOf = "noun verb"; KEBAB-validated per segment |
plugin/lib/registry.ts#opModuleFiles | walk opens only <root>/<name>/ops/*.ts; service peers and group subdirs are invisible |
plugin/cli/registry_adapter.ts | flat noun→verb commander build; JSON-only output via emitJson; zero verb-level hidden handling |
plugin/cli/sdlc.ts#wantsAdvanced | honors --advanced only before the first positional |
plugin/cli/sdlc.ts#composeNouns | never imports @lib/model/ops — only entities validate registers, via a task update import side effect |
.claude/skills/project-check/check_op_modules.ts | asserts noun/verb descriptor keys |
Proposed
Section titled “Proposed”The registry accepts path: string[] (2–3 kebab segments, hard cap 3)
with a {noun, verb} back-compat normalizer; keyOf = path.join(' ');
the CLI generator builds a recursive trie with per-leaf and per-group
hidden plus --advanced threading; a defineService descriptor
(lifecycle start/stop?/list?) registers via a parallel validator,
dispatches outside invokeOp’s output-parse pipeline, and is discovered
by a second walk over services/<svc>/<name>-service.ts; a bounded
ops/<group>/*.ts descent supports depth-3 ops; the output contract of
D-H7FS-op-substrate-surface §4 is implemented — one
--output text|json|jsonl parameter (--json alias), op-declared
defaults (render hook → text, else json; never TTY-sniffed), streaming
as a descriptor property, the cli.render(output, io) hook as the text
projection with exit shaping; a SERVICE_ERROR exit tier exists; the
model/ops barrel is imported at composeNouns so all four entities
verbs register; check_op_modules asserts path shape and projected-name
uniqueness.
Approach
Section titled “Approach”- Golden-snapshot
sdlc --help,sdlc --help --advanced, and a representative dispatch set before any change. - Descriptor:
path[]+ normalizer + derivednoun/verb/groupsaccessors;keyOfjoin;getOp/invokeOparray overloads beside the 2-string overloads. - Recursive trie generator, per-leaf/per-group hidden,
--advancedthreading inregistry_adapter.ts/sdlc.ts. - Output contract per D-H7FS §4:
--output text|json|jsonlparameter, op-declared defaults, streaming descriptor property,cli.renderhook + exit shaping (jsonlaccepted-and-specified; implemented with the first machine-streaming consumer). defineService+ parallel validator + lifecycle dispatcher + SERVICE_ERROR tier.- Walk:
ops/<group>/descent + service-peer traversal, with fixtures proving both before any real consumer exists. composeNounsbarrel import; updatecheck_op_modules.- Assert post-change help snapshots byte-identical for the existing 2-level surface; ship as its own revertible PR.
Files to touch
Section titled “Files to touch”| Location | Kind | Change |
|---|---|---|
plugin/lib/registry.ts | modify | path[], keyOf, overloads, defineService, SERVICE_ERROR, walk descent + service-peer traversal |
plugin/cli/registry_adapter.ts | modify | recursive trie, hidden rendering, render hook, lifecycle dispatch |
plugin/cli/sdlc.ts | modify | —advanced threading; composeNouns imports @lib/model/ops |
.claude/skills/project-check/check_op_modules.ts | modify | path-shape + projected-name-uniqueness assertions |
plugin/lib/tests/registry.test.ts | modify | path[], service, group-walk coverage |
plugin/lib/tests/fixtures/registry/ | modify | depth-3 and service-peer fixtures |
plugin/cli/tests/ | modify | help snapshots, trie, hidden, render-hook tests |
Acceptance criteria
Section titled “Acceptance criteria”- AC-1:
sdlc --helpandsdlc --help --advancedbyte-match the pre-change snapshots — the existing 2-level surface is unaffected. - AC-2: a fixture op at
services/<svc>/ops/<group>/leaf.tsdeclaringpath: ['svc','group','leaf']is discovered, renders undersdlc svc group --help, and dispatches. - AC-3: a fixture
services/<svc>/fixture-service.tsregisters viadefineService; its lifecycle leaves render and dispatch without routing throughinvokeOpoutput parsing. - AC-4: a hidden leaf under a visible noun is absent from
sdlc <noun> --help, present with--advanced, and still dispatches when addressed directly. - AC-5: an op with a
cli.renderhook defaults to its text contract on stdout and shapes its exit code;--output json(and the--jsonalias) emits validated JSON; an op without a hook defaults to JSON. - AC-6:
sdlc entities audit|validate|migrate|check-identifiersall resolve from a clean process;check_op_modulespasses, including projected-name uniqueness. - AC-7:
bunx tsc --noEmit+bun testgreen; the PR reverts cleanly in isolation.
Out of scope
Section titled “Out of scope”- Porting any real capability (the port tasks consume this substrate).
- MCP/HTTP adapter implementations — only the projection contract in D-H7FS-op-substrate-surface binds them.
- Making
model/opsa discovery-walk root (T-U72C owns that question; the barrel import here is independent).
Dependencies
Section titled “Dependencies”- none — this is the first execution task; every port depends on it.
Discovery context
Section titled “Discovery context”- docs/plans/op-substrate-full-sweep.md (Rev 2) §1b machinery contract; adversarial refutation
findings: JSON-only adapter vs line-oriented consumers, invisible service peers,
entitiesops unregistered. Executes D-0007-deterministic-op-substrate §Migration step 1.