Security-motivated migration of all stale repository and npm-scope references. Three categories of changes (58 files, 174 substitutions): 1. gsd-build → open-gsd (security-critical): - .github/workflows/release-sdk.yml — npm token comment, tarball filename pattern - .github/workflows/hotfix.yml — same - .changeset/fix-3406-detect-stale-sdk-shadow.md — @gsd-build/sdk → @open-gsd/sdk - .changeset/sharp-quails-leap.md — same - get-shit-done/workflows/update.md — CHANGELOG raw GitHub URL 2. GSD-redux org slug → open-gsd (canonical rename): - package.json + sdk/package.json — repository/homepage/bugs metadata - All README.*.md — live badge and link sections - CONTRIBUTING.md, CONTEXT.md, QUICK-WINS-CONFIRMED-BUGS.md - .coderabbit.yaml, .release-monitor.sh, scripts/sync-rulesets.sh - docs/** — all live agent/ADR/user-facing documentation - tests/** — repo slug assertions and test fixtures - scripts/changeset/cli.cjs + github-release-notes.cjs - .github/ISSUE_TEMPLATE/*, .github/pull_request_template.md - bin/install.js, get-shit-done/bin/lib/model-catalog.cjs - sdk/HANDOVER-*.md, sdk/src/*.test.ts 3. CLAUDE.md (gitignored local file — not in this commit): Updated separately outside git: --repo gsd-build/get-shit-done → --repo open-gsd/get-shit-done-redux with security warning. Intentionally unchanged: CHANGELOG.md, docs/RELEASE-*.md, .changeset/README.md, .changeset/build-hooks-atomic-write.md, README.md migration table (historical fork record), tests/changeset-serialize.test.cjs line 78 (serialization fixture). The gsd-build/get-shit-done repo is compromised (rug-pull documented in README.md). Do not push to or interact with that repo. Closes #120
5.1 KiB
CommandRoutingHub as single dispatch seam for CJS command families
- Status: Accepted
- Date: 2026-05-20
Context
Seven *-command-router.cjs files (phase, phases, roadmap, state, verify, validate, init) each duplicate the same three-part dispatch pattern: (1) check GSD_WORKSTREAM + tryLoadSdk() to decide whether to use the SDK or CJS handler, (2) invoke the selected path, (3) map errors to the error() callback. The duplicated mode-selection logic means a policy change (e.g., adding a new fallback condition) must be applied in eight places. Tests for these routers are mock-heavy — they stub tryLoadSdk, stub getExecuteForCjs, and assert on internal call shapes rather than observable dispatch outcomes. The SDK-vs-CJS fallback decision is smeared across every router, making it impossible to reason about or test the policy in isolation.
Decision
Introduce CommandRoutingHub (get-shit-done/bin/lib/command-routing-hub.cjs) as the single dispatch seam for all CJS command family routers. The hub contract:
createHub({ mode: 'sdk' | 'cjs', sdkLoader, cjsRegistry, manifest }) -> hub
hub.dispatch({ family, subcommand, args, cwd, raw }) -> Result
Result = { ok: true, data }
| { ok: false, errorKind, message, details? }
Load-bearing design properties:
- Pure result: the hub never prints to stdout/stderr, never calls
process.exit, and never throws. All internal throws are caught and converted to{ ok: false, errorKind: 'HandlerFailure' }. - Mode fixed at construction:
modeis set once whencreateHubis called; it is never re-evaluated per dispatch call. Each adapter (caller) computes mode based on its own env/sdk-load context before constructing the hub. - No transparent fallback: an SDK-mode hub that encounters an SDK crash or load failure returns
{ ok: false, errorKind: 'SdkDispatchFailed' }or'SdkLoadFailed'respectively. It does not silently retry via the CJS registry. - Closed
errorKindenum: the six error kinds (UnknownCommand,InvalidArgs,HandlerRefusal,HandlerFailure,SdkLoadFailed,SdkDispatchFailed) are exported as a frozenERROR_KINDSobject. Callers switch onERROR_KINDSvalues, not bare string literals. Adding a new error kind requires amending this ADR.
The router adapter's responsibilities shrink to: determine mode from env, build stubs/registry, construct hub, dispatch, translate the pure Result to output()/error() calls. Each adapter remains a thin CLI-facing translation layer.
phase-command-router.cjs is migrated as the proof-of-concept for this PR. Remaining routers migrate in follow-up issues.
Consequences
- Positive: policy (mode decision, no-throw contract, error taxonomy) is concentrated in one module rather than duplicated across eight. Testing the policy requires only the hub unit tests; adapter tests verify translation correctness (args → dispatch, Result → output/error).
- Positive: future routers can be onboarded by wiring
cjsRegistryentries rather than hand-replicating the SDK/CJS conditional block. - Constraint: adding a new
errorKindvalue requires updatingERROR_KINDSincommand-routing-hub.cjsAND amending this ADR. The closed enum is the drift-prevention property; the amendment requirement makes scope of impact explicit. - Constraint: each adapter must compute mode before hub construction (no lazy re-evaluation). This is intentional — mode ambiguity at dispatch time is a prior source of subtle test flakiness.
Known limitation: SDK-incomplete subcommands
The hub's mode is fixed at construction ('sdk' or 'cjs'). This works cleanly only when every subcommand in a family has an implementation in the active mode. Today some phase subcommands have divergent CJS and SDK implementations. phase.mvp-mode is present in the SDK catalog (command-static-catalog-domain.ts) but its CJS-native implementation (phase.cmdPhaseMvpMode) differs in ROADMAP scan behaviour and error reason codes from the SDK query layer. Routing mvp-mode through the SDK hub would silently change observable CLI behaviour (exit codes, JSON error shape).
The proof-of-concept adapter (phase-command-router.cjs) handles this with an early-return bypass: mvp-mode is intercepted before the dispatch call so it never reaches the hub. This preserves observable behavior but introduces a hub-level abstraction leak — the adapter now carries per-subcommand routing policy that the hub was meant to own.
Future direction (deferred): the hub should consult manifest to detect per-subcommand SDK coverage and route to CJS automatically for subcommands not present in the SDK manifest. That refinement stays inside the global-mode decision — the mode still applies to the family as a whole — and avoids the per-command policy ladder that was explicitly rejected during design. This work is tracked alongside SDK-CJS migration #3524 closure.
References
- Extends ADR-0001 (Dispatch Policy Module) — the hub implements the no-throw + structured-result contract ADR-0001 established for the SDK query layer, applying it to the CJS adapter layer.
- Issue: #3788