Files
msd-core/sdk/src/runtime-bridge-sync
Tom Boucher 7ebcf41939 feat(3567): state.* router delegates via executeForCjs + Phase 5.0 worker fix (Phase 5.1 of #3524)
Phase 5.1 of the CJS↔SDK hard-seam migration (parent #3524). Migrates
the bin/lib/state-command-router.cjs handlers map to delegate every
canonical state subcommand through the executeForCjs synchronous
primitive (shipped in Phase 5.0, PR #3558).

## Bundled fix for Phase 5.0 worker defect

Discovered during Phase 5.1 implementation that the Phase 5.0
worker drops projectDir and workstream from
RuntimeBridgeExecuteInput. The dispatch closure at
sdk/src/runtime-bridge-sync/worker.ts:41-42 hardcoded projectDir
to '', so registry handlers that read .planning/ from projectDir
(every state.* handler) saw an empty path and failed. Phase 5.0's
pinning tests passed because they exercised commands that don't
depend on projectDir (generate-slug takes its arg directly;
unknown_command doesn't dispatch). Maintainer authorized bundling
the fix into this PR.

Fix: moved QueryNativeDirectAdapter construction inside the
dispatchNative lambda so request.projectDir and request.workstream
close over the per-request values. Per-request adapter construction
adds <1ms overhead; correctness wins. Regression test at
sdk/src/runtime-bridge-sync/projectdir-regression.test.ts demonstrates
RED before fix → GREEN after.

Phase 5.0's index.test.ts native_failure fixture was passing
because of the bug — it relied on projectDir = '' producing a
specific error path. Updated to use a /nonexistent-... path that
triggers ENOENT under realpath, producing native_failure as intended.

## What landed for Phase 5.1

- bin/lib/state-command-router.cjs migrated. Every subcommand
  entry in the handlers map dispatches via executeForCjs when SDK
  is available, with transparent fallback to the existing CJS
  handlers in state.cjs if (a) SDK is not built / not present, or
  (b) GSD_WORKSTREAM is set (the sync-bridge worker cannot serve
  workstream-scoped commands per the SDK transport architecture).
- Special cases preserved:
  - load --raw: SDK data formatted into key=value lines matching
    cmdStateLoad's exact format.
  - complete-phase: CJS-only (no SDK counterpart yet).
  - add-roadmap-evolution: stays on the unsupported list (SDK-only).
- Golden parity tests added for 12 previously-uncovered state
  subcommands: advance-plan, record-metric, update-progress,
  add-decision, add-blocker, resolve-blocker, record-session,
  signal-waiting, signal-resume, planned-phase, milestone-switch,
  prune.

## Design decisions worth reviewer visibility

1. Lazy SDK loading with CJS fallback. The migration routes via
   executeForCjs only when the SDK is loadable; otherwise falls
   back to the existing CJS handlers. Conservative for rollback —
   if the SDK build is broken on a deploy, state commands keep
   working via the CJS path. Trade-off: drift surface is not
   structurally eliminated yet — the CJS handlers remain reachable.

2. Workstream → CJS fallback. The SDK transport forces subprocess
   for workstream commands, but subprocess is disabled in the sync
   bridge. When GSD_WORKSTREAM is set, the entire state command
   falls back to CJS rather than failing. Workstream users continue
   running the CJS handlers; the SDK path is exercised only in the
   default (no workstream) case.

3. Two documented parity divergences. state.record-metric: CJS
   auto-creates ## Performance Metrics section when absent; SDK
   returns {recorded: false, reason}. Test requires fixture with
   the section present. state.prune: CJS counts phases from disk;
   SDK reads from frontmatter fields. Test asserts structural shape
   rather than exact equality.

## Numbers

- Full CJS suite: 9323/9323 pass (baseline 9323; +0 net because
  the 12 new parity tests are SDK-side vitest, not CJS-side).
- SDK vitest sync-bridge: 10/10 pass.
- Regression test: 3/3 pass (proved RED before fix, GREEN after).
- tests/state.test.cjs (the safety net): 104/104 pass unchanged.

## Performance

gsd-tools state load via the SDK path: 49ms first call (Worker
startup), 43-44ms steady-state median. Slower than the
Phase 5.0-measured 0.1ms because state.load does fs reads on top
of the bridge overhead. Still well within the budget for CJS
dispatcher overhead.

Closes #3567.
2026-05-15 14:34:25 -04:00
..