e7f7465778a39e9f81edfa4d55d652d3cc5dca1e
2831 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
e7f7465778 | Merge remote-tracking branch 'origin/main' into fix/pr3649-review | ||
|
|
157f791d45 | fix(windows): derive nested worktree state from show-cdup | ||
|
|
835dd6ab44 |
test(3596): adversarial security/prompt-injection abuse suite (#3654)
* test(3596): adversarial security/prompt-injection abuse suite Adds `tests/security-prompt-injection.test.cjs` and a fixtures directory at `tests/fixtures/adversarial/security/` covering the attack classes enumerated in #3596: - Command substitution / backticks / heredoc payloads in workstream names — sentinel-file probes prove no shell is spawned, slugifier neutralises the input. - Path traversal through `--ws` and slash-bearing workstream names — rejected with structured `--json-errors` payload, no stack trace, no filesystem mutation outside the project root. - Fake `<system>` / `[SYSTEM]` / `<<SYS>>` / `[INST]` boundary tags — sanitizeForPrompt neutralises every form; structural negative property locked across all six styles in one place. - Zero-width / bidi-override codepoints — stripped per the documented codepoint set; asserted via codePoint inspection, not regex literals. - Hostile read of CONTEXT.md / PLAN.md / ROADMAP.md fixtures — `gsd-read-injection-scanner.js` surfaces the advisory; excluded paths and non-Read tools stay silent; malformed JSON does not crash the hook. - Hostile write of `.planning/` files — `gsd-prompt-guard.js` emits a `PreToolUse` advisory; non-Write/Edit tools stay silent. - Fake `ghp_*` / `sk-*` env tokens — never echoed in CLI stdout or stderr under hostile inputs; covered under `// allow-test-rule: structural-regression-guard` because the only way to assert byte-level absence is `.includes(token)` against the captured streams. - `validatePath`, `validateShellArg`, `validatePhaseNumber`, `validateFieldName` — focused negative-input contract pins. Pinned behavior gaps (documented, NOT fixed in this PR): - `<instructions>` is intentionally whitelisted by both the scanner and the sanitiser (GSD's own prompt scaffolding). Two REGRESSION GUARD tests lock that contract. - The current `scanForInjection` does NOT flag malicious markdown links (javascript:/data:/embedded-credentials URLs). PINNED with negative-proof so any future scope extension fails the assertion and forces a deliberate update to the acceptance map. - `prompt-builder.ts` does not yet wrap plan/context markdown in an "untrusted data" envelope. That seam lives on the TS side and is covered by `sdk/src/prompt-builder.test.ts`; out of scope for a CJS test file. Mentioned in the file header. Verification: - `node --test tests/security-prompt-injection.test.cjs` → 73 tests pass. - `node scripts/lint-no-source-grep.cjs` → 0 violations across 546 test files (one `allow-test-rule: structural-regression-guard` annotation on this file for the token-absence assertions). - `node scripts/run-tests.cjs` → 9730 tests pass, 0 fail. Refs #3596 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(3596): allow adversarial fixtures in scan + harden graphify status parse * fix(3596): skip adversarial security fixtures in secret scan --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
4956e5e5c6 |
test(3598): generator correctness, parity, and atomicity (#3656)
Adds tests/feat-3598-generator-correctness.test.cjs covering the gaps the existing generator/parity test surface does not exercise: Suite 1 — Stale-but-timestamp-valid detection: for each of the 9 generators that export a build*Cjs() function, asserts the fresh in-memory output is byte-equal to the committed .generated.cjs. Catches manual edits and partial-write drift that timestamp-only freshness checks miss. Suite 2 — Determinism: calls each build*Cjs() twice and asserts the outputs are identical. Catches time/random/iteration-order regressions. Suite 3 — Runtime/SDK alias parity: asserts canonical command names and alias sets are identical between get-shit-done/bin/lib/command-aliases.generated.cjs (runtime) and sdk/src/query/command-aliases.generated.ts (SDK). Beyond timestamp freshness. Suite 4 — No duplicate aliases in the live registry: behavioral equivalent of the issue's example test. The generator has no fixture/--source seam (it reads in-memory COMMAND_DEFINITIONS_BY_FAMILY), so the structural invariant is asserted on the deployed surface; a collision fails with both colliding canonicals named. Suite 5 — build-hooks.js atomicity: runs the build twice, snapshots hooks/dist/ each time, asserts byte-identical output. Also asserts no orphaned .dist-staging-* sibling directories remain and that every .js file shipped to dist parses (positive proof of the vm.Script syntax guard). 26 tests pass on macOS / Node 24. Closes #3598 |
||
|
|
3eb1fec1c9 |
docs(3660): ADR + CONTEXT.md entry for Runtime Artifact Layout Module (#3661)
* docs(3660): add Runtime Artifact Layout Module ADR + CONTEXT.md entry Records the architectural decision to add a Runtime Artifact Layout Module that owns per-runtime artifact placement (commands / agents / skills) as a typed seam. Closes the bug class behind #3659 where the Runtime Surface Module forgets artifact kinds the install/uninstall pipelines track. CONTEXT.md gains the new module entry directly after the Skill Surface Budget Module since the two seams compose. Pure docs — no code changes. Phase 1 implementation lands in a separate PR. Closes #3660 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * docs(3660): fix ADR parity and clarify phase boundaries --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
3c51e760f8 | fix(windows): harden nested-root and archive-warning assertions | ||
|
|
5ee2a148ca | fix(windows): stabilize init/workflow tests across path and npm edge cases | ||
|
|
02d3cf3033 | fix(3597): add Windows-safe rmSync retry budgets in lint/security tests | ||
|
|
1a1ad2ec87 |
Merge origin/main into feat/3597-split-suites-node-matrix
Resolves conflict in .github/workflows/test.yml: keep the 6 new drift-check steps from main (plan-scan, secrets, schema-detect, decisions, workstream-name-policy, Shared Module hand-sync) before the split-lane test runs from this PR. PR's dedicated `coverage` job replaces main's per-matrix `Run tests with coverage` step. Other conflicting files (CONTRIBUTING.md, get-shit-done/bin/lib/init.cjs, package.json) auto-merged cleanly. Changeset files (.changeset/*) brought in from main as adds. |
||
|
|
32dea2034b |
fix(3597): bug-1974 windows EPERM — retry rmSync on tmpDir teardown
Test spawns a fire-and-forget subprocess; on Windows the child may still hold a handle on tmpDir when afterEach runs, producing EPERM from fs.rmSync. Match helpers.cleanup() retry budget (20 × 250ms = 5s) to absorb the deferred-handle window without pulling in the helper (this suite predates it). Closes the Windows test (windows-latest, 24/26) lane failure on #3649. |
||
|
|
ae63cbe557 |
feat(3575): Phase 6 — CJS↔SDK seam migration end-to-end complete (#3524) (#3577)
* feat(3575): Phase 6 enforcement hardening + retrospective (#3524 feature-complete) Phase 6 of the CJS↔SDK hard-seam migration (parent #3524). Final phase per the PRD. After this lands the migration is feature-complete: shared Modules from Phases 1-4 are in place, the runtime-bridge primitive from Phase 5.0 is wired with the state.* family proof in Phase 5.1 (PR #3574), and Phase 6 hardens the seam against future drift via lint, CODEOWNERS, and retrospective documentation. ## What landed - scripts/lint-shared-module-handsync.cjs (274 lines) — the drift-prevention gate. Scans bin/lib/*.cjs and looks for same-named sdk/src/<name>.ts or sdk/src/query/<name>.ts (excluding generated artifacts). Pairs not on the allowlist fail the lint with a clear message: either add to allowlist with justification, or migrate to a shared Module. Supports --root, --allowlist, --cjs-dir, --sdk-src, --warn-all flags for testability. - scripts/shared-module-handsync-allowlist.json (148 lines) — two categories: - cooperatingSiblings (14 pairs) — legitimate Readers/Adapters that consume shared Modules or run structurally-different runtime paths. - migrateMeBacklog (8 pairs) — known drift anti-patterns that ARE on main today (config, decisions, intel, model-catalog, plan-scan, schema-detect, secrets, workstream-name-policy). Lint warns but does not fail on these; documented in the retrospective as candidate Shared Module migrations. - tests/lint-shared-module-handsync.test.cjs (285 lines, 11 cases) — proves the lint catches new drift, honors the allowlist, and exits 0 on the current tree. - .github/workflows/test.yml — new "Shared Module hand-sync drift check" step after the freshness checks. - .github/CODEOWNERS — appended 11 architecture-owned path rules for source-of-truth files (Shared Module dirs, manifest JSONs, runtime bridge, lint script, allowlist). Existing blanket rule preserved. - docs/agents/cjs-sdk-seam.md (280 lines) — full retrospective + guide: - Migration overview table linking Phases 1-6 with PR numbers. - 15 historical drift bugs (#1535 ... #3523) each mapped to the Phase 6 enforcement layer that would have blocked them. - "Guide: Adding a new Shared Module" — step-by-step using Phase 1 (state-document) as the worked example. - "Guide: Adding a new canonical command" — step-by-step using Phase 5.1 (state.update) as the worked example. - "Open follow-ups" listing the 8 MIGRATE_ME pairs, per-family Phase 5.2+ candidates pending maintainer authorization, sync bridge workstream support, and Phase 5.1's parity divergences. - CONTRIBUTING.md — short cross-reference paragraph in the Architecture & Domain Standards section. ## Audit findings All 5 freshness checks from Phases 0-4 are already wired in CI: command-aliases, state-document, configuration, workstream-inventory-builder, project-root. Phase 6 adds the 6th (hand-sync drift check) for total enforcement coverage. ## Numbers - Full CJS suite: 9335/9335 pass (baseline 9323 + 11 new lint tests + 1 cooperating). - Lint passes on current tree: 14 cooperating siblings + 8 backlog pairs accounted for, 0 unauthorized drift pairs. - Lint exits 1 (fails CI) on an intentional new hand-synced pair added to a fixture — verified by the test suite. Closes #3575. Closes the structural drift surface of #3524. * chore(3577): add changeset fragment for Phase 6 * feat(3575): Phase 6 end-to-end completion — CJS↔SDK seam migration done Per maintainer correction: Phase 6 is THE final phase and must complete the migration end-to-end. This commit absorbs Phase 5.1's work (state.* router + worker fix), finishes the remaining per-family router migrations, completes all five resolvable Shared Module extractions, resolves the parity divergences, lands native workstream support in the sync bridge, and ships the lint + CODEOWNERS + retrospective from the original Phase 6 scope. After this commit the CJS↔SDK seam migration started in #3524 is feature-complete. No follow-up "Phase 5.x" or "Phase 7" should be needed — the only documented carve-outs are three pairs that intentionally cannot be migrated (config CLI handlers, intel async wrapper, model-catalog already on the shared-JSON pattern). Cherry-picked state.* from Phase 5.1 (PR #3574 absorbed). Migrated verify.*, init.*, phase.*, phases.*, validate.*, roadmap.* via the same executeForCjs delegation pattern. Migrated the inline gsd-tools.cjs cases for frontmatter.*, config-* CLI, and non-family commands (generate-slug, current-timestamp, find-phase, docs-init) with shared _dispatchNonFamily helper + _tryLoadSdkBridge loader. CJS-native carve-outs documented: config-path, migrate-config, detect-custom-files (no SDK counterpart yet); state.complete-phase (no SDK counterpart yet); validate.context (CJS-only inline logic with no clean SDK port); phases.archive (SDK-only). - plan-scan (Module-via-generator from sdk/src/query/plan-scan.ts) - secrets (Module-via-generator) - schema-detect (Module-via-generator) - decisions (Module-via-generator; SDK regex aligned to CJS alphanumeric IDs to preserve project compatibility) - workstream-name-policy (Module-via-generator; SDK extended with hasInvalidPathSegment and isValidActiveWorkstreamName that CJS callers depend on) Each ships with: SDK source-of-truth, generator at sdk/scripts/gen-<name>.mjs, freshness check at sdk/scripts/check-<name>-fresh.mjs, parity test at tests/<name>-generator.test.cjs, CJS shim at get-shit-done/bin/lib/<name>.cjs, scripts in sdk and root package.json, pre-commit drift block, CI workflow step, CODEOWNERS rule, INVENTORY.md row. - config (config.cjs vs sdk/src/config.ts) — CJS file is CLI-handler surface (cmdConfigGet/Set/etc.); SDK file is loadConfig wrapper (already migrated in Phase 2). Zero logical overlap. Classified as CJS-CLI-ONLY in the allowlist. - intel (intel.cjs vs sdk/src/query/intel.ts) — SDK is the async QueryHandler wrapper of the CJS module; intentional split per the SDK file's own docstring. Classified as cooperating-sibling. - model-catalog (model-catalog.cjs vs sdk/src/model-catalog.ts) — both already consume sdk/shared/model-catalog.json (ADR-0003). No constants duplicated. Classified as ADAPTER-OVER-MODULE. - state.record-metric: SDK aligned to CJS auto-create of ## Performance Metrics section when absent. Parity assertion now exact equality. - state.prune: SDK aligned to CJS disk-based phase counting via stateExtractField. Parity assertion now exact equality. SDK unit tests updated to match. GSDTransport.shouldUseNative no longer forces subprocess when request.workstream is set — the Phase 5.0 worker fix already threaded workstream through dispatchNative + registry.dispatch, making the subprocess force unnecessary. state-command-router.cjs's workstream fallback guard removed. cjs-sdk-seam.md and the regression test updated to document the resolution. Unchanged from the previous commit on this branch. The lint now reports 22 cooperating siblings, 0 backlog pairs. The retrospective section "Open follow-ups" is reduced to the three intentional carve-outs above; the four stale subsections (8 MIGRATE_ME pairs, per-family Phase 5.x candidates, workstream support, parity divergences) are gone because they're all resolved in this commit. - Full CJS suite: 9441/9441 pass (baseline pre-Phase-6 was 9323; +118 from the Phase 6 work — 11 lint tests + 12 state-router parity + 6 verify parity + 3 phase parity + 1 roadmap parity + 24 plan-scan parity + 20 secrets parity + 18 schema-detect parity + 15 decisions parity + 19 workstream-name-policy parity). - SDK vitest unit: 1863/1863 pass. - Hand-sync lint: 22 cooperating siblings, 0 backlog pairs. - All freshness checks: fresh. Closes #3575. Closes the migration the CJS↔SDK seam was designed to eliminate (#3524). * fix(3575): lint-shared-module-handsync emits typed JSON; tests assert on IR The lint-no-source-grep CI step rejected the original Phase 6 test file (tests/lint-shared-module-handsync.test.cjs) because it substring-matched on .stdout/.stderr from the lint script output — prohibited per CONTRIBUTING.md "Raw Text Matching on Test Outputs". Fix: add --json mode to the production lint script and assert on typed IR fields. ## Changes scripts/lint-shared-module-handsync.cjs: - New --json flag. When set: - Success: emits { ok: true, cooperatingCount, backlogCount, warnings } - Unauthorized pairs: emits { ok: false, reason: 'unauthorized_pairs', errors: [{ relCjs, tsPaths }], warnings, cooperatingCount } - Missing CJS/SDK dir: emits { ok: false, reason: 'cjs_dir_missing' | 'sdk_src_missing', path } - Default (human-readable) output unchanged. - Warnings section is suppressed in --json mode (still surfaced in the IR's `warnings` field for tests to inspect). tests/lint-shared-module-handsync.test.cjs: - runLintJson() helper replaces runLint(), invoking the script with --json and parsing the IR. - Every assertion now reads typed fields (payload.ok, payload.reason, payload.errors, payload.warnings, payload.cooperatingCount) instead of substring-matching stdout/stderr. - Test count unchanged at 9 cases across 3 describe blocks. - All pass. ## Verification - node scripts/lint-no-source-grep.cjs → exit 0, 529 test files checked, 0 violations (was: 1 violation in this test file). - node --test tests/lint-shared-module-handsync.test.cjs → 9/9 pass. - node scripts/lint-shared-module-handsync.cjs → unchanged human-readable output, 22 cooperating siblings, 0 backlog pairs. - node scripts/run-tests.cjs → 9449/9449 pass. Addresses CI failure on PR #3577 (Phase 6 of #3524). * fix(3575): address CodeRabbit review on PR #3577 Six findings resolved: 1. scripts/lint-shared-module-handsync.cjs — allowlist matching now pair-aware. Keys composite ${cjs}::${ts} instead of cjs-only, so an entry covering one (cjs, ts) pair no longer silently passes a sibling at a different ts path with the same module name. Header doc-comment also corrected: removed the stale claim about GSD_LINT_CHANGED_FILES filtering (no such code existed). 2. sdk/src/gsd-transport.ts — removed dead 'workstream_forced' member from the TransportDecision.reason union (no longer assigned after Phase 5.0 workstream-native refactor). 3. sdk/src/gsd-transport.ts — removed stale workstream interpolation from the subprocess-reason Error message; the field is no longer load-bearing for that decision path. 4. All eight generator scripts (sdk/scripts/gen-*.mjs and gen-state-document.ts) — replaced the manual entry-point check that used `new URL(process.argv[1], 'file://')`. On Windows that misparses `C:\…\gen-*.mjs` as scheme "c:" and breaks the check. Replaced with the cross-platform-safe direct comparison `fileURLToPath(import.meta.url) === process.argv[1]`. (Not using `import.meta.main` — that's only stable in Node 24+ and the project supports Node 22+.) 5. docs/agents/cjs-sdk-seam.md — added explicit `text` language specifier to the four file-path fenced blocks (lines 157, 165, 173, 181). Closing fences correctly remain bare. Verification - node scripts/lint-no-source-grep.cjs → 0 violations - node scripts/lint-shared-module-handsync.cjs → 22 cooperating siblings, 0 backlog (counts unchanged after pair-aware refactor) - node scripts/lint-shared-module-handsync.cjs --json → typed IR unchanged - All 9 generator freshness checks → fresh - node scripts/run-tests.cjs → 9449/9449 pass - sdk vitest src/gsd-transport.test.ts → 10/10 pass Tests for pair-aware matching: the existing 9 cases in tests/lint-shared-module-handsync.test.cjs already build fixture allowlist entries with both `cjs` and `ts` fields, so they implicitly exercise the new pair-aware lookup; all 9 pass. * fix(3575): address second CodeRabbit review on PR #3577 Five new findings resolved. 1. Shared SDK bridge loader (`get-shit-done/bin/lib/cjs-sdk-bridge.cjs`) Eliminates seven-fold duplication of `tryLoadSdk` / `_executeForCjs` that lived verbatim in every `*-command-router.cjs` plus a near-identical variant in `gsd-tools.cjs`. The new module exposes `tryLoadSdk()`, `getExecuteForCjs()`, and `getSdkModule()` (the last for routers that pull additional named exports, e.g. state's `formatStateLoadRawStdout`). All eight call sites refactored to consume it. As a side benefit `gsd-tools.cjs` no longer imports from the private `@gsd-build/sdk/dist/runtime-bridge-sync/index.js` subpath; everyone now uses the public package entry consistently. 2. `phase remove` accepts zero positional args (#3577 review) `phase remove --force` previously passed validation with no phase number and invoked `cmdPhaseRemove(cwd, undefined, ...)`. Tightened to `positional.length !== 1` and added the early `return` so the handler never receives an undefined phase id. 3. Decisions parser regex hardened (#3577 review) `D-[A-Za-z0-9_-]+` allowed malformed IDs like `D--foo` and `D-_bar`. Tightened to `D-[A-Za-z0-9][A-Za-z0-9_-]*` so the first character after `D-` must be alphanumeric; internal `_`/`-` still permitted. Decisions generated CJS mirror regenerated. 4. plan-scan-generator test no longer uses hardcoded `/tmp` paths `/tmp/__gsd_test_nonexistent_dir_xyz__` and `/tmp/__nonexistent_gsd_test__` could collide with prior runs on shared CI runners. Replaced with `uniqueMissingPath()` helper that synthesizes `os.tmpdir()/<prefix>-<pid>-<ms>-<random>` and force-removes the path before returning. 5. lint-shared-module-handsync test now validates pair-aware TS matching Added `rejects pair when TS path differs from allowlist entry` — a regression guard that creates an on-disk pair at `sdk/src/query/<name>.ts` but allowlists the (cjs, sdk/src/<name>.ts) shape. The lint must reject because the (cjs, ts) tuple does not match. Demonstrates the pair-aware matching added in the previous commit and locks it in. ## Wiring `cjs-sdk-bridge.cjs` added to `docs/INVENTORY.md` (count 68→69) and `docs/INVENTORY-MANIFEST.json` regenerated. ## Verification - node scripts/lint-no-source-grep.cjs → 0 violations (529 files) - node scripts/lint-shared-module-handsync.cjs → 22 cooperating, 0 backlog - node scripts/run-tests.cjs → 9452/9452 pass (was 9449 + 1 lint-test + 1 changed plan-scan path test) - node sdk/scripts/check-decisions-fresh.mjs → fresh - sdk vitest src/query/decisions.test.ts → 15/15 pass * docs(3575): correct PR/issue refs in cjs-sdk-seam.md CodeRabbit caught two stale references that conflated the issue number (#3575) with the PR number (#3577). Phase 6 ships as PR #3577 closing issue #3575. Migration overview table row and the Final Completion Summary updated accordingly. * fix(3575): cjs-sdk-bridge actually loads the SDK (was dead-code since Phase 5.0) ## The bug `cjs-sdk-bridge.cjs:tryLoadSdk()` resolved `require('@gsd-build/sdk')`, but that package name is not installed in the root `node_modules` (the SDK lives as `./sdk/` — a sibling workspace, not a dependency) and the SDK's public entry doesn't re-export `executeForCjs` or `formatStateLoadRawStdout` anyway. `tryLoadSdk()` always returned false, the `_loadFailed = true` cache made every subsequent call return false for the lifetime of the process, and every CJS router silently fell through to the CJS handler. The pattern shipped in Phase 5.0 (PR #3558, merged) via `require('@gsd-build/sdk/dist/runtime-bridge-sync/index.js')` and was inherited into the routers via `require('@gsd-build/sdk')` in Phase 5.1 (PR #3574, merged). Both subpaths/imports failed in the same way. CI passed for the whole CJS↔SDK migration because the CJS fallback handlers kept running — meaning the entire claimed "state.* delegation" never actually executed via the SDK in any shipped run. This is exactly the silent-drift class the Phase 6 lint and retrospective are supposed to prevent. Catching it here closes the loop. ## The fix Resolve the bundled SDK by **package-relative filesystem path**: <root>/sdk/dist/runtime-bridge-sync/index.js <root>/sdk/dist/query/state-project-load.js The `files` array in `package.json` keeps `sdk/dist` at the same relative location inside the published tarball, so the path works in both dev and post-install. The two-file split is necessary because `formatStateLoadRawStdout` lives in the state handler, not the runtime-bridge entry. ## Integration test `tests/cjs-sdk-bridge-integration.test.cjs` proves four things and locks the load-success invariant so this regression cannot recur: 1. tryLoadSdk() returns true on the current checkout 2. getExecuteForCjs() returns a function (not null) 3. getFormatStateLoadRawStdout() returns a function (not null) 4. executeForCjs() actually dispatches a canonical registry command (generate-slug) and returns an ok:true result — proving real SDK execution, not a silent CJS-fallback ## State-router formatter wiring The state command router was reaching into `getSdkModule()` to pluck `formatStateLoadRawStdout`. Replaced with the explicit `getFormatStateLoadRawStdout()` getter so the bridge module owns all SDK-export resolution. ## state.load --raw output mode While the bridge was broken, the state.load --raw test happened to pass via CJS fallback. The first SDK execution exposed a contract mismatch: passing `mode: 'raw'` to the bridge tells the SDK to pre-render result.data to a JSON string, but the router was also calling `formatStateLoadRawStdout(result.data)` to project to key=value lines — the formatter saw a string and no-op'd. Fix: when a CJS-side rawFormatter is supplied, the router requests `mode: 'json'` from the bridge (always get typed data) and runs the formatter itself. When no rawFormatter, the user's --raw flag flows through to the bridge as usual. ## Surfaced pre-existing parity gaps (NOT yet fixed) With the bridge now actually executing the SDK, 8 `tests/state.test.cjs` cases reveal pre-existing CJS↔SDK behavioral drift that Phase 5.1's "104/104 pass" report could not see because the SDK was never running: - `state load returns error when STATE.md missing` - `state get returns error when STATE.md missing` - `state update returns error when STATE.md missing` - `state update reports field not found` - `state patch / record-metric / update-progress / resolve-blocker / record-session — error when STATE.md missing` - `add-decision --summary-file` / `add-blocker --text-file` (file-input path rejected by SDK security check) Each is a real CJS↔SDK divergence that needs explicit alignment in the SDK handler. Listed here so the next commit can address them honestly rather than letting the broken bridge mask them again. * fix(3575): align SDK with CJS contract — bridge-exposed divergences The Phase 5.1 bridge fix (0fc60b0c) made executeForCjs() actually load and dispatch. With routers now hitting the SDK in normal layouts, six CJS↔SDK behavioral divergences became visible. This commit aligns the SDK to match the canonical CJS contract test-by-test. ROUTER CHANGES (mode: raw → mode: json) All 7 CJS routers were passing `mode: raw ? 'raw' : 'json'`. With the bridge active, `mode: 'raw'` makes the bridge pre-render result.data to a JSON string, which CJS output() then re-stringifies — producing a JSON string of a JSON string. Routers now always request typed JSON; CJS output() handles user- facing rendering. Affected: gsd-tools, init, phase, phases, roadmap, state, validate, verify routers. SDK STATE MUTATION HANDLERS (sdk/src/query/state-mutation.ts) state.update / record-metric / update-progress / resolve-blocker / record- session no longer auto-create STATE.md via readModifyWriteStateMd. CJS errors out when STATE.md is missing; SDK now does the same via an upfront existsSync check returning {updated: false, reason: 'STATE.md not found'}. Also fixes: • resolve-blocker semantic: SDK returned resolved:false when no blocker line matched. CJS returns resolved:true whenever the Blockers section exists. Aligned. • readTextArgOrFile path validation: rejected /var/folders paths on macOS because /var → /private/var is a symlink. Now resolves both base and target via realpathSync before the prefix check. STATE.MD STOPPED_AT SCOPING (sdk/src/query/state.ts) buildStateFrontmatter extracted `Stopped At` from the entire body; CJS scopes it to the ## Session section. Bug-2444 parity restored — the field no longer bleeds in from unrelated sections of STATE.md. PHASE_DIR_COUNT MILESTONE FILTER (sdk/src/query/init.ts) initNewMilestone counted every directory under phases/ regardless of which milestone it belonged to. CJS uses getMilestonePhaseFilter to count only current-milestone phase dirs. Bug-2445 parity restored. ARCHIVED PHASE GUARD (sdk/src/query/init.ts) shouldDropArchivedPhaseMatch had an extra `archivedTag === milestone.version` escape hatch that doesn't exist in CJS. CJS unconditionally drops the archived match when the phase appears in the current ROADMAP. Removed the escape hatch — fixes the bug #2391 regression where `init plan-phase 03` returned the archived v1.0 phase instead of the current ROADMAP phase. PADDING-TOLERANT ROADMAP PHASE LOOKUP (sdk/src/query/roadmap.ts) searchPhaseInContent used `escapeRegex(phaseNum)` as the phase-number fragment — `03` failed to match `Phase 3:` headings. CJS uses phaseMarkdownRegexSource which emits `0*<integer>` for padding tolerance. Restored same helper inline in roadmap.ts. Fixes bug #2391 / #3537 parity in zero-padded phase lookups. STATE COMMAND ROUTER STATE.MD-MISSING ERROR SURFACE (get-shit-done/bin/lib/state-command-router.cjs) state.get must surface "STATE.md not found" as an error (matching CJS exit behavior); other state mutations must surface {updated: false, reason: ...} as data. Added EXIT_ON_STATE_MD_MISSING discriminator with STATE_MD_MISSING_ MESSAGE constant. VERIFICATION • init.test.cjs — 93/93 pass (was 91/2 fail) • state.test.cjs — 104/104 pass (was 95/9 fail) • core.test.cjs — pass • roadmap.test.cjs — pass • cjs-sdk-bridge-integration.test.cjs — 4/4 pass (bridge load locked in) The 13 phase.test.cjs failures (next-decimal 999.x backlog skip, add-batch JSON validation, insert dry-run rejection, find-phase non-canonical warnings) are pre-existing SDK gaps from the broken-bridge era and will be addressed in a follow-up commit on this same PR. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(3575): align SDK phase handlers with CJS (wave 2 — phase.test.cjs) The bridge-fix (0fc60b0c) exposed 13 more CJS↔SDK behavioral divergences inside the phase command family. All are now aligned to the canonical CJS contract, with per-test verification. phase.ts: • Centralised isCanonicalPlanFile / looksLikePlanFile / describeNonCanonical Plans helpers mirroring phase.cjs:17–52. Exported for reuse from phase-lifecycle.ts (phasesList) so the warning shape never drifts between read sites. • searchPhaseInDir now emits result.warning (singular) with the canonical message when a plan-shaped file would be skipped by the canonical filter. Bug #2893 parity for find-phase. • phasePlanIndex moved its non-canonical warning to the singular result.warning field (was a generic entry in result.warnings) so consumers see the same field name and message format as find-phase / phases-list. Other diagnostics (unresolved deps, wave-declaration mismatches) still flow through the warnings array unchanged. • Added PhaseInfo.warning to the type. getPhaseFileStats now also returns allFiles so the caller can compute the diagnostic without re-reading the directory. phase-lifecycle.ts: • phasesList (phases list --type plans) emits per-dir prefixed warnings matching phase.cjs:120 (`${dir}: ${describeNonCanonicalPlans(...)}`). • phaseAdd now matches the CJS router contract for arg parsing: accepts --raw (ignored), --dry-run, --id <value>; rejects every other --flag with "phase add does not support <flag>"; rejects dangling --id with "--id requires a value"; joins all positional tokens with space so `phase add User Dashboard` produces description "User Dashboard". customId comes from --id, never from positional[1]. • phaseInsert now mirrors phaseAdd's arg parsing: rejects --dry-run with "does not support --dry-run", strips --raw, joins positional.slice(1) for the description. Also reports the bug-3098 placeholder error ("Phase N exists in roadmap summary but is missing a detail section") when the ROADMAP has only a checklist entry but no detail section. • phaseAddBatch dangling --descriptions or --descriptions followed by another flag now surface "--descriptions must be a JSON array" instead of silently falling through to positional parsing or throwing "--descriptions must be a valid JSON array". • renameIntegerPhases now skips backlog phases (dirInt >= 999) — bug-2434 parity. Without this, removing phase 3 in a project with 999.1-backlog-* on disk would rename the backlog dir to 998.1-backlog-*. • updateRoadmapAfterPhaseRemoval rewritten to mirror phase.cjs:880-922 exactly: 5 targeted regex passes (not a loop), driven by three decrement helpers (decrementRoadmapPhaseNumber, decrementRoadmapPhase Token, decrementRoadmapPaddedPhaseNumber) that guard against `num >= 999`. The padded-prefix replace uses negative lookbehind/ lookahead to skip YYYY-MM-DD substrings. Fixes: - bug-2435: integer phase remove no longer corrupts dates in ROADMAP (e.g. `(Shipped: 2025-04-15)` is left alone when removing phase 4). - bug-3355: integer phase remove no longer renumbers the same phase more than once (loop overlap removed). - Backlog phases stay frozen during renumbering. • phaseComplete next-phase scan skips backlog dirs (999.x). Without this, `phase complete 2` in a project with 999.1-backlog/ on disk would emit next_phase: '999.1' even though Phase 3 exists in ROADMAP.md. Bug #2129 parity. VERIFICATION (per-test, targeted runs — full suite not exercised due to prior 89GB OOM with concurrent runs): • phase.test.cjs — 108/108 pass (was 13 fail) • init.test.cjs — 93/93 pass (no regression) • state.test.cjs — 104/104 pass (no regression) • validate.test.cjs — pass (no regression) • verify.test.cjs — pass (no regression) • core.test.cjs — pass (no regression) • roadmap.test.cjs — pass (no regression) • cjs-sdk-bridge-integration.test.cjs — 4/4 pass (bridge intact) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(3575): align SDK roadmap-mutation helpers with CJS — bug-2005 Three CJS↔SDK divergences in the phase.complete write path were hiding behind the broken bridge: 1. replaceInCurrentMilestone (sdk/src/query/phase-roadmap-mutation.ts) The SDK port carried an extra fallback that doesn't exist in the CJS (core.cjs:1013-1022): if the "after last </details>" slice didn't match the pattern, the SDK silently retried inside the last <details> block. That fallback corrupts the current milestone when it is itself wrapped in <details open>...</details> and there's no content after the close tag — the supposed-to-be-skipped scope is the only place the match exists. Aligned to CJS: split at the last </details>, replace only in the after-slice, return. No fallback. Documented with a "do not re-add" warning since this fallback has been added back twice in prior porting passes. 2. phase complete checkbox update (sdk/src/query/phase-lifecycle.ts) The SDK was scoping the `- [ ] Phase N:` → `- [x] Phase N:` replacement through replaceInCurrentMilestone. The CJS (phase.cjs:1057) uses a direct roadmapContent.replace(...) call. When the current milestone is wrapped in <details>, the scoped variant never reaches the checkbox; direct replace finds it. Aligned with CJS. 3. phase complete plan-count update (sdk/src/query/phase-lifecycle.ts) Same pattern — the SDK was scoping the `**Plans:** X/Y` update through replaceInCurrentMilestone. CJS (phase.cjs:1080) uses direct replace. Aligned. VERIFICATION • bug-2005-phase-complete-details.test.cjs — 2/2 pass (was 1 fail) • phase.test.cjs — 108/108 pass (no regression) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(3575): align SDK with CJS — add-decision DWIM + frontmatter paths Two more CJS↔SDK divergences exposed by the bridge fix: state.add-decision / state.add-blocker DWIM (sdk/src/query/state-mutation.ts) CJS state.cjs:481-498 + 532-548 auto-create the canonical Decisions / Blockers section when it's absent from STATE.md. The SDK was returning `{added: false, reason: '<Section> section not found in STATE.md'}` even when STATE.md was writable. Bug #3286 (parity for both verbs): • If section header pattern matches → append entry (existing path). • If section is absent → scaffold `## Decisions` (or `### Blockers`) and append the entry, then set `created: true` on the result. Matches the begin-phase / advance-plan DWIM behavior. Callers can now treat `state add-decision` as idempotent — first call creates the scaffold, subsequent calls append to it. frontmatter get/set/merge/validate (helpers.ts + frontmatter.ts + frontmatter-mutation.ts) CJS frontmatter.cjs:323/340/354/369 resolves user paths with the simple `path.isAbsolute(p) ? p : path.join(cwd, p)`. The SDK port had promoted this to `resolvePathUnderProject` which adds a real-path prefix check against the project root. That check rejects absolute paths outside the project — including macOS tmpdir paths whose names contain spaces, the exact regression cited in bug #3509. Frontmatter verbs are deliberately path-flexible in CJS because they're called against external files (plan paths from other repos, scratch markdown, tmpdir fixtures). Introduced `resolveFrontmatterPath()` mirroring the CJS one-liner. The project-scoped `resolvePathUnderProject()` is unchanged — still used for template output, decision artifacts, etc. VERIFICATION • bug-3286-state-write-routing.test.cjs — 13/13 pass (was 6 fail) • bug-3509-path-spaces.test.cjs — 6/6 pass (was 3 fail) • phase.test.cjs / init.test.cjs / state.test.cjs / validate.test.cjs / verify.test.cjs / core.test.cjs / roadmap.test.cjs — all pass (no regression — 566 total tests). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(3575): route SDK state handlers through scanPhasePlans — bug-3257 The SDK port of buildStateFrontmatter / stateValidate / stateSync was using a naive top-level filter (`files.filter(/-PLAN\.md$/i)`) instead of the canonical scanPhasePlans helper. The naive filter undercounts every phase that uses the nested layout `phases/NN-name/plans/<NN>-PLAN-MM-slug.md`, which is the default the planner agent produces. CJS routes all three sites through scanPhasePlans (state.cjs:408, 824, 1427). scanPhasePlans is already a Shared Module — generated CJS at plan-scan.generated.cjs from sdk/src/query/plan-scan.ts. The fix is just to consume it. CHANGES • buildStateFrontmatter (sdk/src/query/state.ts): replaced the inline `-PLAN.md` / `-SUMMARY.md` regex filters with scanPhasePlans; use the helper's `completed` flag for diskCompletedPhases. • stateValidate (sdk/src/query/state-mutation.ts): same swap on the current-phase plan-count drift check. • stateSync (sdk/src/query/state-mutation.ts): same swap on the rollup loop. Also routes the Progress percent through computeProgressPercent(completedPlans, totalPlans, diskCompletedPhases, syncTotalPhases) so the min(plan_fraction, phase_fraction) cap from bug #3242 Bug B is applied — without this, sync emitted 60% when the real progress was capped at 50% by phase-fraction. VERIFICATION • bug-3257-nested-plans-undercount.test.cjs — 14/14 pass (was 12 fail) • phase.test.cjs / init.test.cjs / state.test.cjs / validate.test.cjs / verify.test.cjs / core.test.cjs / roadmap.test.cjs / bug-3286 / bug-2005 / bug-3509 — all pass (no regression — 580 total). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * test(3575): phase 6 CJS↔SDK seam behavioral contracts — TDD-found worker bug Adds tests/phase-6-cjs-sdk-seam-contracts.test.cjs — a behavioral contract suite for everything Phase 6 of #3524 introduced. Written under the issue #3592 test rewrite discipline: • No source-grep on .cjs files • No assert.match / .includes on free-form child-process stdout/stderr • Every assertion is on a parsed JSON object, a filesystem fact, an exit code, or a frozen enum value (SYNC_ERROR_KIND, BRIDGE_EXPORTS, TRANSPORT_MODE) • Helpers come from tests/helpers.cjs (runGsdTools, createTempProject, cleanup) — no inline fs.mkdtempSync • Fixture content built with array.join('\n'), never template literals • beforeEach/afterEach for shared setup; no try/finally inside tests COVERAGE 1. Bridge module surface — exports lock against BRIDGE_EXPORTS 2. Bridge load lifecycle — tryLoadSdk, getters return cached refs, pre-load returns null 3. executeForCjs RuntimeBridgeSyncResult shape — ok:true vs ok:false discriminated union; mode:"json" never double-stringifies 4. CLI family-router dispatch — one structured-JSON assertion per family (roadmap, phase, phases, state, init, validate, find-phase) 5. mode:"json" regression guard — stdout parses to object, not to JSON-encoded string (the Wave-1 double-stringify bug shape) 6. GSD_WORKSTREAM gate — SDK path and CJS fallback produce identical structured fields for the same fixture 7. Validation error taxonomy — empty arg → ok:false + errorKind: SYNC_ERROR_KIND.VALIDATION_ERROR 8. phase.add filesystem facts — directory exists, ROADMAP file grew (asserted via fs.statSync, never by reading content back) TDD-FOUND BUG (RED → GREEN) Suite §7 (validation_error taxonomy) failed in the RED phase: expected: 'validation_error' actual: 'native_failure' Root cause in sdk/src/runtime-bridge-sync/worker.ts: when an SDK handler throws a GSDError(Validation), the native direct adapter wraps it in a GSDToolsError via createNativeFailureError, preserving the original on `.cause`. classifyError only checked for TypeError causes — every GSDError cause fell through to `native_failure`, breaking the documented SyncErrorKind contract. Fix: classifyError now unwraps the cause once. When the cause is a GSDError with ErrorClassification.Validation or .Blocked, the result is errorKind: 'validation_error' (exit 10) — matching the direct branch a few lines below for unwrapped GSDError. VERIFICATION (per-test, before and after the worker fix) • Phase 6 contract suite — 21/21 pass (was 20/1 fail at RED) • phase.test.cjs — 108/108 pass • init.test.cjs — 93/93 pass • state.test.cjs — 104/104 pass • validate.test.cjs / verify.test.cjs / core.test.cjs / roadmap.test.cjs — all pass • cjs-sdk-bridge-integration.test.cjs — 4/4 pass (bridge intact) • npm run lint:tests — 0 violations (no source-grep) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(3575): SDK config-get/set parity + reason-code propagation — bugs #2943 #3086 #3212 Three CJS↔SDK divergences in config dispatch exposed when Phase 6 routes `config-get` / `config-set` through `executeForCjs`: 1. SDK config-get was missing the SCHEMA_DEFAULTS map. CJS config.cjs:505-510 hard-codes documented defaults for `context_window` (200000), `executor.stall_detect_interval_minutes` (5), `executor.stall_threshold_minutes` (10), `git.create_tag` (true). When a config.json omits the key, CJS returns the documented default with exit 0. SDK threw `Key not found` for all four — every skill that reads `context_window`, executor stall thresholds, or the tag toggle broke under SDK dispatch. Ported the table verbatim into sdk/src/query/config-query.ts and consult it at every "not found" exit point (matching the three CJS branches: missing file, traversal collapse, terminal undefined). 2. SDK config-set was missing the `git.create_tag` boolean-only guard. CJS rejects `config-set git.create_tag maybe` because the schema is boolean. SDK silently accepted it and wrote "maybe" to disk under Phase 6 dispatch. Added the matching guard + the missing `workflow.post_planning_gaps` boolean guard. 3. SDK errors lost their structured reason code at the bridge boundary. `--json-errors` callers expect `reason: 'config_key_not_found'` etc. from a frozen `ERROR_REASON` taxonomy; the bridge dispatcher in gsd-tools.cjs was calling `error(message)` without the second argument, so every SDK-routed error surfaced as `reason: 'unknown'`. Fix is end-to-end: • config handlers tag the GSDError with `.reason = 'config_*'`. • worker.ts:classifyError reads `.reason` off the cause (or off the direct error) and forwards it via `errorDetails.reason`. • `_dispatchNonFamily` in gsd-tools.cjs passes that reason as the second arg to `error()` when present. • Also added the `--raw` scalar pass-through here, so `output(data, raw, String(data))` is called for primitive results — without it, `config-get context_window --raw` emitted the JSON shape '200000\n' which happens to match but breaks any primitive whose JSON encoding differs from its String() form (booleans for example, where the CJS produces `true` while the SDK-routed path was producing `true` — same here, but the structural guarantee was wrong before). VERIFICATION (per-test) • bug-2943-config-get-context-window-default.test.cjs — 5/5 pass • bug-3086-git-create-tag-config-gate.test.cjs — 4/4 pass • bug-3212-execute-phase-stall-safe-resume.test.cjs — 7/7 pass • Phase 6 contract suite — 21/21 pass • phase/init/state/core/roadmap/validate/verify — all pass (570 total) • Full bug-* suite: 24 fail → 17 fail (7 fixed in this commit). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(3575): SDK milestone-archive layout discovery — bug #3164 Two CJS↔SDK divergences in phase discovery and validation surfaced when projects moved to the milestone-archive layout (`.planning/milestones/v<version>-phases/<phase>/`) instead of the flat `.planning/phases/<phase>/`. 1. SDK findPhase had no `searched_directories` field on the not-found payload. CJS surfaces this for diagnostics. Added: track every directory probed (the active `.planning/phases/` plus each archive root) and include the relative paths in the not-found payload. Bug #3164 — #find-phase tests. 2. SDK validateConsistency only scanned `.planning/phases/`. CJS `cmdValidateConsistency` (verify.cjs:467) walks every active phase root via `collectPhaseRoots(planBase)` — the flat dir plus the active milestone archive resolved from STATE.md. Without parity, every roadmap phase on a milestone-archive-layout project emitted W006 ("no directory on disk") even though the phases were present in the archive. Ported the helper trio (listMilestoneArchiveDirs, getActiveMilestoneArchiveDir, collectPhaseRoots) verbatim from verify.cjs:400-444 and rewrote validateConsistency's disk-phase scan + per-phase plan scan to iterate `phaseRoots`. Warning labels now include the archive prefix so users can tell which root surfaced the issue. Also accepts prefixed archive dir names (`CK-64-...`) as phase 64 via the `(?:[A-Z]{1,6}-)?` group at the head of PHASE_TOKEN_FROM_DIR_RE — same regex CJS uses. VERIFICATION (per-test) • bug-3164-milestone-archive-layout.test.cjs — 8/8 pass • Phase 6 contract suite — 21/21 pass • phase/init/state/validate/verify/core/roadmap — 570 pass • Full bug-* suite: 17 fail → 12 fail (5 fixed in this commit; cumulative 12 fixed since Wave 6 start). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(3575): padded phase IDs match unpadded ROADMAP prose — bug #3537 Three failures in bug-3537-padded-id-against-unpadded-roadmap: 1. roadmap.get-phase returned `phase_number` verbatim from the user input — `02.7` produced `"phase_number": "02.7"` while `2.7` produced `"phase_number": "2.7"` on the same fixture, so a parity compare of the two stdouts fails. Fixed by promoting the matched phase token in `searchPhaseInContent` to a capture group and returning that as the canonical `phase_number`. Same fix in the checklist-fallback branch so the malformed-roadmap diagnostic carries the as-written form too. 2. phase.complete built every ROADMAP-prose regex from `escapeRegex(phaseNum)` instead of the padding-tolerant `phaseMarkdownRegexSource(phaseNum)`. Calling `phase complete 02.7` against the un-padded heading `### Phase 2.7:` matched nothing — checkbox didn't flip, plan count stayed at `0/1`, table row stayed `Planned`. Promoted `phaseMarkdownRegexSource` to an exported helper in roadmap.ts and wired it into phaseComplete's roadmap mutation block. 3. roadmap.annotate-dependencies infinite-looped through the bridge. The SDK handler delegates to `spawnSync(gsd-tools.cjs roadmap annotate-dependencies …)`; the child re-entered the roadmap router; the router re-dispatched through executeForCjs; synckit spawned the same SDK worker; that worker spawned gsd-tools.cjs again; … Recursion hit the 15s timeout and the test reported `code=null`. Fixed with a `GSD_SDK_NESTED=1` env-var guard: the SDK handler sets it when spawning the child, and the CJS roadmap router refuses SDK dispatch when it sees the flag. VERIFICATION (per-test) • bug-3537-padded-id-against-unpadded-roadmap.test.cjs — 6/6 pass • Phase 6 contract suite — 21/21 pass • phase/init/state/validate/verify/core/roadmap — 570 pass • Full bug-* suite: 12 fail → 7 fail (5 fixed in this commit; cumulative 17 fixed across the wave-6/7/8 sequence). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(3575): final-7 SDK parity — bugs #2787 #2268 #2526 Closes out the bug-suite tail. Three independent fixes against three independent regressions surfaced when Phase 6 routed read-only and mutation paths through the SDK. 1. extractCurrentMilestone truncated at heading-like lines inside fenced code blocks — bug #2787. The `^#{1,N}\\s+...vX.Y` scan ran with the `/m` flag, which matches `^` at every newline, including newlines inside ``` and ~~~ fences. A snippet like ```bash # Ops runbook — v1.0 compat ``` placed between Phase 2 and Phase 3 of a v1.1 milestone shortened the milestone slice and made phases 3, 4 invisible to roadmap.analyze / roadmap.get-phase. Added `isInsideFencedCodeBlock(content, offset)` — a GFM-aware walker that toggles a `fenceChar` cursor on each fence boundary (backticks and tildes; closing fences require the matching character and no info string — so ```js inside ```text does NOT close). The nextMilestoneRegex loop now skips any match that falls inside an open fence. 2. init.manager only marked the FIRST undiscussed phase as `is_next_to_discuss` — bug #2268. Two and five-phase fixtures both proved the regression: parallel-discuss capacity was lost, recommended_actions emitted at most one discuss action even when callers were free to take several. Replaced the sliding- window loop with an unconditional `phase.is_next_to_discuss = (status === 'empty' || status === 'no_directory')`. 3. phase.complete didn't surface "REQ-IDs found in body but missing from Traceability table" warnings — bug #2526. CJS phase.cjs:1140-1167 scans REQUIREMENTS.md for `**REQ-ID**` references in the body, intersects against the IDs that actually appear in the Traceability section table, and warns about the diff. The SDK port only ran the per-roadmap-REQ checkbox update and never emitted the body-scan warning. Added the missing scan + warning push; also routed the writeFile through a `reqContentChanged` flag so we only write when at least one substitution actually fired (parity with the implicit "every checkbox already complete" no-write CJS branch). VERIFICATION • bug-2787-milestone-fenced-block-truncation.test.cjs — 4/4 pass • bug-2268-parallel-discuss.test.cjs — 4/4 pass • bug-2526-phase-complete-req-discovery.test.cjs — 3/3 pass • Phase 6 contract suite — 21/21 pass • Major suites (phase/init/state/validate/verify/core/roadmap) — 570 pass • **Full bug-* suite: 2397/2397 pass — ZERO failures.** • Combined run (major + bug-*): 2967/2967 pass — zero failures. Cumulative since the bridge-fix landing (PR #3577): 12 sub-test regressions surfaced + every one resolved. Phase 6 is now byte-for- byte CJS-parity across every command family verified by the test suite. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(3575): preserve codex runtime command shape after router migration * test(3575): pin agent-install-validation init tests to GSD_AGENTS_DIR PR #3577 routed init.execute-phase and init.plan-phase through executeForCjs to the SDK handlers. The SDK side's resolveAgentsDir (sdk/src/query/helpers.ts) honors GSD_AGENTS_DIR or falls back to <runtimeConfigDir>/agents; it does not walk up from cwd to find <repo>/agents/ like the CJS-era code did. The two init-suite tests that asserted agents_installed=true relied on that implicit walk and only passed on dev machines where ~/.claude/agents/ already had the 33 agents installed — Linux CI runners have neither. Match the pattern every passing sibling in this file already uses: pass { GSD_AGENTS_DIR: REPO_AGENTS_DIR } through runGsdTools so the SDK resolver points at the repo's agents/ dir explicitly. No production code change. Refs sdk/src/query/QUERY-HANDLERS.md ("subprocess vs in-process path resolution") and CONTEXT.md DEFECT.PORT-DRIFT.cjs-sdk. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(3577): Phase 6 config-* SDK port parity carve-outs Restored the legacy contract for four CLI tests broken by the Phase 6 router migration: 1. `config-ensure-section` was bound to the new SDK `configEnsureSection` handler which requires `args[0]=sectionName`. Every real CLI caller uses the no-arg form expecting full default config.json creation. Reverted the dispatch case to call `config.cmdConfigEnsureSection` directly (matches the precedent in 7d5dfa9d for `codex` runtime). 2. SDK `configNewProject` `commit_docs` and `parallelization` defaults set to `true`/`true` (was `false`/`1`) — aligned with `sdk/shared/config-defaults.manifest.json` and the CJS `buildNewProjectConfig` `hardcoded` block. 3. SDK `configNewProject` returns the project-rooted relative path `.planning/config.json` instead of the absolute `paths.config`, matching the CJS `ensureConfigFile` output shape. 4. SDK error vocabulary aligned with CJS: `Unknown config key: <key>` (no surrounding quotes), and config-get's malformed-JSON message leads with `Failed to read config.json:` so legacy substring assertions in `tests/config.test.cjs` keep matching. Local: 132/132 across `tests/{config,agent-skills,ai-evals}.test.cjs`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(3631): family routers forward --raw to SDK bridge as mode:'raw' #3577 routed every family subcommand through the SDK bridge with a hardcoded mode:'json'. With --raw set, the bridge returned the typed JSON IR and routers called `output(result.data)` — bypassing output()'s rawValue branch. Shell consumers expecting scalar tokens (`gsd-tools phase next-decimal --raw 1` → `1.1`) received the JSON- stringified IR instead. Each `*-command-router.cjs` SDK dispatch path now requests `mode: raw ? 'raw' : 'json'` from the bridge. The sync-bridge worker is wired to `formatNativeRaw = formatQueryRawOutput` so the bridge returns the per-command scalar projection. Routers route the formatted string through `output(null, true, str)` (rawValue branch) so it lands on stdout verbatim. formatQueryRawOutput extended for the two commands covered by the issue acceptance criteria — phase.next-decimal (→ data.next) and roadmap.get-phase (→ data.section). Other registered raw projections (state.load, commit, config-set, state.begin-phase) are unaffected; the default `safeStringify` branch still applies to unprojected commands. state-command-router already had a dispatchViaSdk helper that selected mode based on a rawFormatter. The trailing fallthrough `output(result.data)` when no rawFormatter was present is the same regression and was patched to use the rawValue branch under --raw. Regression test `tests/bug-3631-router-raw-flag.test.cjs` exercises end-to-end: - `phase next-decimal --raw 1` emits a scalar phase token (not JSON). - `roadmap get-phase --raw 2` emits the section text (not JSON). The fix targets `feat/3575-enforcement-hardening` (PR #3577, open) — not origin/main as the issue body asserted. The #3577 regression lives on that branch and the fix needs to land there before merge. Fixes #3631 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * test(3631): force CJS dispatch path in router unit tests via GSD_WORKSTREAM phases-command-router.test.cjs and roadmap-command-router.test.cjs mock the CJS-side `phase`/`milestone`/`roadmap` handlers and assert they are called with the parsed args. Since #3577 the router prefers SDK dispatch when sdk/dist is present — the mocks are then bypassed and the SDK side fails because the test cwd `/tmp/proj` has no `.planning/` fixture. The router already gates SDK dispatch on `process.env.GSD_WORKSTREAM` being unset (workstream-scoped requests fall through to CJS). Setting GSD_WORKSTREAM in before()/after() deterministically routes through the CJS handlers the tests were written against, without weakening the assertions. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(3632): report each ts sibling independently in lint-shared-module-handsync The cooperatingPairs lookup ran inside `.some()` over all ts candidates for a given cjs. When two ts siblings shared the same basename (e.g. `sdk/src/foo.ts` and `sdk/src/query/foo.ts`) and only one pair was allowlisted, `.some()` short-circuited and the unallowlisted sibling silently passed through CI. Classify each ts sibling independently against the allowlist so partially- allowlisted multi-sibling drift surfaces. Added regression test `reports unallowlisted ts sibling when another ts sibling for the same cjs IS allowlisted (#3632)`. Real-tree lint output unchanged on `feat/3575-enforcement-hardening`: 22 cooperating siblings, 0 unauthorized, 0 backlog pairs. Fixes #3632 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(3577): ADR/PRD compliance + SDK port completeness for Phase 6 Multiple ADR/PRD violations in the Phase 6 cutover surfaced during gsd-test-summary docker runs. Root causes traced to docs/adr/ 3524-cjs-sdk-hard-seam.md §3 (out-of-seam module list) and docs/prd/3524-cjs-sdk-hard-seam.md L160 (CJS-only verbs must not route through the SDK runtime bridge), plus port-drift bugs the ADR was specifically written to prevent (DEFECT.PORT-DRIFT.cjs-sdk). Out-of-seam Module bindings removed from SDK catalog/manifests: - verify.codebase-drift (drift is CJS-only; the SDK stub used execFileSync back to gsd-tools, recursing infinitely with the Phase 6 router rewrite — forked hundreds of node procs on the 64 GiB plex2 docker host before manual kill) - intel.* (8 verbs: diff, snapshot, validate, status, query, extract-exports, patch-meta, update — intel is CJS-only per ADR) Both already have direct-CJS dispatch in gsd-tools.cjs (case 'intel') and verify-command-router.cjs (`'codebase-drift':` now calls verify.cmdVerifyCodebaseDrift without going via sdkHandler). config-ensure-section cutover restored via catalog rebind: - 'config-ensure-section' in command-static-catalog-foundation.ts rebound from configEnsureSection (single-section semantics, requires args[0]=sectionName the CLI never passes) to configNewProject (whose no-args branch produces the full default config.json — matches the legacy ensureConfigFile contract). - gsd-tools.cjs `case 'config-ensure-section'` restored to its Phase 6 _dispatchNonFamily form (no CJS fallback — the SDK handler now does the right thing). configNewProject defaults from canonical manifest: - Replaced the hardcoded duplicate `defaults` block with a derivation from CONFIG_DEFAULTS (sdk/src/configuration/index.ts, sourced from sdk/shared/config-defaults.manifest.json). The duplicate had drifted — omitted workflow.{ai_integration_phase, tdd_mode, human_verify_mode, pattern_mapper, plan_bounce*, auto_prune_state, subagent_timeout, security_*, post_planning_gaps}, git.create_tag, claude_md_path, planning.*, graphify.*, mode, resolve_model_ids, context_window — every one of which had a test asserting the post-init value. SDK configSet value-validation port (CJS cmdConfigSet parity): - workflow.drift_action enum (warn|auto-remap) - workflow.drift_threshold positive-integer - workflow.human_verify_mode enum (mid-flight|end-of-phase) - statusline.context_position enum (front|end) - code_quality.fallow.scope enum (phase|repo) - code_quality.fallow.profile enum (minimal|standard|strict) - review.default_reviewers array shape + slug regex + lowercase-unique normalisation (matches bin/lib/review-reviewer-selection.cjs normalizeConfiguredDefaultReviewers, with the normalised value persisted to disk) Init/roadmap/phase/workspace/frontmatter handler fixes: - initExecutePhase + initPlanPhase parse --tdd boolean override - initMapCodebase reads workflow.subagent_timeout with 300000 default per manifest - roadmapAnalyze surfaces `mode` per phase (parity with roadmapGetPhase) - phaseComplete auto-prunes STATE.md when workflow.auto_prune_state is true (port of bin/lib/phase.cjs:1378-1390; #2087) - initRemoveWorkspace throws GSDError on no-name and workspace-not-found instead of returning {data:{error}} which the CLI output path treated as success - frontmatterGet parses --field <name> in addition to positional args[1] Local: 150/150 across the failing-cluster test files (review-default-reviewers-config, subagent-timeout, pattern-mapper, tdd-mode, drift-detection, roadmap-mode-field, workspace, phase-complete-auto-prune, frontmatter-cli). Docker gsd-test-summary re-run in progress for full validation. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(3577): clear 12 ubuntu-only regressions surfaced by gsd-test-summary Docker test pass 3 (holodeck) surfaced 12 real bugs after the earlier ADR/PRD-compliance commit (cf4dd0cb). Every one is a SDK-side bug — fix-forward, not "pre-existing": bug-3599 (2 subtests) — roadmap.get-phase project-code-prefix lookup: Ported phaseMarkdownRegexSourceExact from CJS (core.cjs:704-708) so `PROJ-42` queries try the exact escaped form FIRST before falling back to the padding-tolerant numeric. searchPhaseInContent now does two-pass lookup. Without this, `roadmap get-phase PROJ-42` returned not-found even when ROADMAP contains `### Phase PROJ-42:`, and bare `42` queries cross-matched the PROJ-42 heading. roadmap-mode-field (1) — roadmapAnalyze surfaces `mode` per phase: Extracts the same `**Mode:**` field that roadmapGetPhase already parses (CONTEXT.md "MVP Mode" glossary). Without this, downstream consumers reading roadmap.analyze output couldn't tell which phases were MVP-mode. bug-3601 (2 subtests) — phase.remove preserves peer-depth decimals: Ported the depth-aware end-of-section regex from CJS phase.cjs (named capture `(?<h>#{2,4})` + `\k<h>(?!#)` backreference). Now removing `### Phase 2:` stops at `### Phase 2.1:` (same depth, peer decimal) while continuing past `#### Phase 27.1:` (child depth). bug-3602 (1 subtest) — phase.remove renumbers slugged plan refs: Extended the padded-plan-reference pattern with optional kebab-case slug segments `(?:-[A-Za-z][A-Za-z0-9-]*)*` between NN-NN and the PLAN/SUMMARY suffix, matching CJS phase.cjs:#3602 fix. Without this, `07-01-cherry-pick-foundation-PLAN.md` references stayed at `07-01-` after Phase 7 was removed, while the file on disk was already `06-01-...`. config.test (1) — config-get git.base_branch returns "Key not found": configNewProject now filters out manifest keys legacy CJS init does NOT materialize: top-level `resolve_model_ids`, `context_window`, `mode`, `planning`, `graphify`; nested `git.base_branch`. These have their own resolution paths (origin/HEAD auto-detect for base_branch, feature opt-in for planning/graphify) and materializing the manifest defaults would suppress them. Manifest stays the schema source of truth per ADR §6; init shape stays minimal per legacy CJS contract. gsd-sdk-query-registry-integration (1) — agents/gsd-intel-updater.md references retargeted from `gsd-sdk query intel.*` to `gsd-tools intel <subcommand>`. intel is out-of-seam per ADR §3 / PRD L160 ("CJS-only Module handlers ... keep their in-process CJS implementations"). Removing the SDK catalog entries (cf4dd0cb) made the SDK route invalid; the agent now correctly invokes the CJS handler via gsd-tools, which routes through Shell Command Projection for cross-platform formatting. Local: 79/79 across the failing test files. Docker re-run in progress. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(3577): regenerate command-aliases + retarget workflow drift-gate CI ubuntu-24 surfaced two remaining ADR-compliance gaps after the previous push: 1. `sdk/src/query/command-aliases.generated.{ts,cjs}` still listed verify.codebase-drift + intel.{snapshot,patch-meta} from before the manifest-side removal. Ran `npx tsx sdk/scripts/gen-command-aliases.ts` to regenerate; both files now match the manifest source of truth. Closes the `command-seam-coverage.test.ts` "missing registry canonical verify.codebase-drift" failure (its assertion is correct — the SDK does NOT register codebase-drift, so the alias entry must not be present either). 2. `get-shit-done/workflows/execute-phase/steps/codebase-drift-gate.md` invoked `gsd-sdk query verify.codebase-drift` — drift is out-of-seam (CJS-only) per ADR §3 / PRD L160, so there is no SDK handler to route through. Retargeted to `gsd-tools verify codebase-drift` which dispatches direct to bin/lib/drift.cjs (the canonical implementation) via the CJS router. Closes the `gsd-sdk-query-registry-integration.test.cjs` failure. Local: docker gsd-test-summary 11383/0 on plex2. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(3575): raise Node heap for coverage in CI matrix --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: ci <ci@gsd-build> |
||
|
|
c638665d59 |
fix(3569): surface phase_status from init.plan-phase; gate /gsd:plan-phase on closed phases (re-submit of #3578) (#3581)
* fix(3569): surface phase_status from init.plan-phase + gate /gsd:plan-phase on closed phases
Adds a new `phase_status` field to the `init.plan-phase` SDK + CJS query
output and a §1.5 "Closed-Phase Gate" in workflows/plan-phase.md that
short-circuits on closed phases instead of silently replanning over
shipped code.
`gsd-sdk query init.plan-phase <N>` returned the same "ready to plan"
payload for a closed phase (REQUIREMENTS Met, VERIFICATION.md status:
passed, ROADMAP flipped) as for an open one. No field signaled closure,
so `/gsd:plan-phase --reviews` happily replanned over closed phases —
risking documentation drift on already-shipped code.
- Export `determinePhaseStatus` from `commands.cjs` (already present, was
module-private).
- Both `cmdInitPlanPhase` (CJS) and `initPlanPhase` (TS SDK) now compute
`phase_status` from plan/summary counts + VERIFICATION.md status using
the existing `determinePhaseStatus` helper — the project-wide phase
lifecycle vocabulary (Pending | Planned | In Progress | Executed |
Complete | Needs Review). No directory yet → Pending.
- Workflow `plan-phase.md` adds §1.5 "Closed-Phase Gate":
- `phase_status == "Complete"` with `--reviews` → hard-stop, no
override (replanning a closed phase via review feedback is never
legitimate; concerns belong in a follow-up phase or new issue).
- `phase_status == "Complete"` without `--force` → exit with a clear
notice pointing at VERIFICATION.md.
- `phase_status == "Complete"` with `--force` → continue with a
transcript banner so the deliberate replan is visible.
`Executed` and `Needs Review` are intentionally not gated — those mean
planning finished but verification did not pass, and replanning is the
correct next step.
- SDK: 4 new `phase_status` cases in init.test.ts covering Pending /
Planned / Executed / Complete transitions.
- Existing init.plan-phase golden parity test continues to pass (the
`researcher_model: '' vs sonnet` drift in that test predates this
change and is unrelated).
- Full Mac+Docker suite: 9323 / 9323 passed (Mac), 9318 / 9323 passed
(Docker, 5 skipped).
Fixes #3569
* chore(3569): add changeset fragment for PR #3581
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
2e4fe65816 |
fix(3579): ship graphify hook + lib/ helper through build-hooks + install (#3640)
* fix(3579): ship graphify hook + lib/ helper through build-hooks + install `scripts/build-hooks.js` `HOOKS_TO_COPY` did not include `gsd-graphify-update.sh` (added in #3347 / PR #3557), so it never landed in `hooks/dist/` and `bin/install.js` — which `readdirSync`s the dist — never copied it to `~/.claude/hooks/`. The hook's detached rebuild helper at `hooks/lib/gsd-graphify-rebuild.sh` was also silently dropped because both build-hooks.js (flat allowlist) and bin/install.js (readdir + isFile filter) only walked top-level files. The published tarball gap (Gap 3 in the issue body) is not reproduced on origin/main — `npm pack --dry-run --json` shows both source files are present today. Only Gaps 1 and 2 are in scope. Changes: - Add `gsd-graphify-update.sh` to `HOOKS_TO_COPY`. - Add `HOOKS_SUBDIRS_TO_COPY = ['lib']` and copy whitelisted hook subdirectories (`hooks/<dir>/*` → `hooks/dist/<dir>/*`) in build-hooks.js, with the same syntax-check + atomic-rename path the top-level loop uses. - `bin/install.js`: when copying `hooks/dist/`, recurse one level into any directory entry so subdir files (e.g. `lib/gsd-graphify-rebuild.sh`) land at the mirrored target path the hook's REBUILD_SCRIPT lookup expects. Top-level if/else structure for files is unchanged. Regression test `tests/bug-3579-graphify-hook-publish.test.cjs`: - Drift guard: every top-level `hooks/*.sh` must appear in `HOOKS_TO_COPY`. Generalizes beyond graphify so the next .sh hook added cannot regress. - After build: `hooks/dist/gsd-graphify-update.sh` AND `hooks/dist/lib/gsd-graphify-rebuild.sh` exist. - After install: both files land at the target, and no "Missing expected hook: gsd-graphify-update.sh" warning is emitted. Fixes #3579 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * test(3579): replace source-grep drift guard with filesystem-behavior assertion The Gap-1 drift guard read scripts/build-hooks.js as text and regex-parsed the HOOKS_TO_COPY literal, which tripped lint-no-source-grep and is brittle under refactors. Replace with a behavior-based assertion: run the build, then for every top-level hooks/*.sh assert hooks/dist/<name> exists. Strictly stronger — catches both the original allowlist gap and any future regression that silently drops a hook for any other reason. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
05a8395566 |
fix(3406): detect + warn on stale @gsd-build/sdk@0.1.0 global shadow (#3641)
* fix(3406): detect + warn on stale @gsd-build/sdk@0.1.0 global shadow
`@gsd-build/sdk@0.1.0` is the only published version of the standalone
SDK package (the SDK now ships embedded in get-shit-done-cc). When a
user has the stale 0.1.0 globally installed, its `gsd-sdk` bin shadows
the shim get-shit-done-cc wires up — and the 0.1.0 binary only knows
`run | auto | init` (no `query`), so every `gsd-sdk query <cmd>` call
from skills and hooks fails silently until the user runs
`npm uninstall -g @gsd-build/sdk`.
Per maintainer triage decision (option 2): detect at install time and
surface the remediation, instead of waiting for the user to discover
the failure through a broken workflow.
Changes:
- New helper `detectStaleStandaloneSdk(runNpmLs)` (pure function,
accepts an injected executor). Returns `{stale: true, version}` when
`@gsd-build/sdk` is in the top-level dependency tree; returns
`{stale: false}` for every other input including executor throws,
malformed JSON, missing keys, and null/undefined returns.
- New helper `formatStaleStandaloneSdkWarning(info)` — message names
the package, version, the exact `npm uninstall -g @gsd-build/sdk`
remediation command, and references the issue.
- Call site in `install()` for `isGlobal` runs. Spawns
`npm ls -g @gsd-build/sdk --json --depth=0`, recovers the JSON
attached to the non-zero-exit error (npm's "absent" signal),
forwards to detectStaleStandaloneSdk, prints the warning if stale.
Best-effort: any failure is swallowed so detection never blocks
install.
- `GSD_SKIP_STALE_SDK_CHECK=1` opt-out for CI/test environments that
need silence (also used by the install-side test below).
Regression test `tests/bug-3406-stale-sdk-shadow-detect.test.cjs`:
- 8 unit tests pinning every detectStaleStandaloneSdk path (exported,
absent, present, executor-throws, malformed-JSON, no-deps-field,
null/undefined, format).
- 1 install-side end-to-end test confirming that when the package is
absent, the install run does NOT mention `@gsd-build/sdk` or `#3406`
in stdout. Uses a per-test `npm_config_prefix` so the test never
depends on the host's npm dependency tree.
Fixes #3406
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(3406): correct changeset pr field — 3406 was the issue number, not the PR
CodeRabbit caught that .changeset/fix-3406-detect-stale-sdk-shadow.md
referenced `pr: 3406` (the issue number) instead of `pr: 3641` (the
PR number). Per CONTEXT.md PRED.k329 changeset frontmatter pr: must
reference the pull request number.
Local: docker gsd-test-summary 11214/0 on plex2.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(3406): two CR follow-ups on bin/install.js stale-shadow check
Round-2 CodeRabbit findings on PR #3641:
1. bin/install.js:7769 — GSD_SKIP_STALE_SDK_CHECK opt-out now matches
only explicit "1" / "true" / "yes". The previous any-truthy check
silently disabled the warning for `GSD_SKIP_STALE_SDK_CHECK=0` and
`GSD_SKIP_STALE_SDK_CHECK=false`.
2. bin/install.js:10568 — detectStaleStandaloneSdk now gates stale=true
on version === '0.1.0' (the known-bad shadow). Any newer published
version is intentional and must not flag a "stale shadow" warning
on every install. Added a regression test for non-0.1.0 versions
(1.50.0-canary.0 and 2.0.0) returning stale:false.
Local: 11/11 in the bug-3406 test file + docker gsd-test-summary 11215/0
on plex2.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
cfbdf5f832 |
fix(3643): resolve full Claude model id under resolve_model_ids: true (#3648)
* fix(3643): resolve full Claude model id under resolve_model_ids: true
The SDK resolveModel handler bailed out of resolveRuntimeTier for
runtime: "claude" (config-query.ts:149) — the implicit/default runtime —
and fell through to return { model: alias } without consulting the
catalog when resolve_model_ids: true. Consumers received tier aliases
("opus" / "sonnet" / "haiku") instead of the full IDs the CJS resolver
produces (core.cjs:1348-1350 maps via MODEL_ALIAS_MAP).
Added a runtime === 'claude' && resolveModelIds === true branch that
calls resolveRuntimeTierDefault('claude', tier) from the shared
model-catalog so SDK and CJS paths derive Claude IDs from one source
of truth. model_overrides, phase-type tier override, and
resolve_model_ids: "omit" precedence unchanged.
7 new tests in sdk/src/query/config-query.test.ts cover:
- claude + resolve_model_ids:true × {budget, balanced, quality}
× {gsd-executor, gsd-planner} → full Claude IDs
- claude + phase-type override → opus full id
- claude WITHOUT resolve_model_ids → still returns alias (regression)
- claude + resolve_model_ids:"omit" → still wins
- model_overrides[agentType] → still wins
Fixes #3643
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(3643): backfill changeset pr field with #3648
Per DEFECT.CHANGESET-PR-FIELD-DRIFT.fix-forward — the changeset was
authored with pr: 0 placeholder before the PR existed; now pinned to
the actual PR number.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(3643): resolve full Claude model id when runtime is implicit
CodeRabbit caught that the resolve_model_ids:true branch at
sdk/src/query/config-query.ts only matched explicit runtime === 'claude'.
The resolveRuntimeTier bail-out at line ~149 treats empty/missing
runtime as implicit Claude, so projects without an explicit runtime
field fell through to the alias return — leaving callers that asked
for resolved model IDs with the tier alias instead of the full id
(e.g. 'sonnet' instead of 'claude-sonnet-4-6').
isClaudeRuntime = runtime === '' || runtime === 'claude' covers both
the explicit and implicit cases.
Local: docker gsd-test-summary 11201/0 on plex2.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
e090e91646 |
fix(3588)(security): clear production npm-audit advisories (#3642)
* fix(3588)(security): clear production npm-audit advisories Before: 6 production advisories (1 high, 5 moderate) reported by `npm audit --omit=dev` — fast-uri (high), @anthropic-ai/sdk, express-rate-limit, hono, ip-address (moderate), all pulled in through @anthropic-ai/claude-agent-sdk and @modelcontextprotocol/sdk. After: `npm audit fix` bumped the lockfile-pinned transitive versions to patched releases. No package.json edits — only package-lock.json and sdk/package-lock.json. Production audit is clean on both: `npm audit --omit=dev` → 0 vulnerabilities. Regression test `tests/bug-3588-npm-audit-clean.test.cjs` runs `npm audit --omit=dev --json` against root and sdk/ and asserts the metadata vulnerability counts are zero across info/low/moderate/high/ critical. RED on origin/main (1 high + 5 moderate at root), GREEN after the lockfile bumps. Skips gracefully when node_modules/ is absent so fresh checkouts mid-`npm install` don't false-fail. Fixes #3588 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(3588): npm audit harness throws on unexpected JSON shape CodeRabbit caught that auditProductionVulns returned null both for "node_modules missing → skip" AND for "unexpected JSON shape" — and callers interpret null uniformly as skip, so a real audit harness failure (npm changed output format, audit aborted before metadata section, etc.) would silently no-op instead of failing the test. null is now reserved for the skip signal only. Any other unexpected shape throws with the cwd in the message so the test fails loudly. Local: docker gsd-test-summary 11204/0 on plex2. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
05316369ae |
fix(3583): normalize retired colon-form commands in generated Claude/Qwen/Hermes SKILL.md bodies (#3629)
* Add first-class grok runtime support (maps to ~/.agents); wire installer, runtime-homes.cjs and sync-skills; update Grok Build engine in local ~/.agents to latest; record session progress in discussion doc * Normalize gsd colon references to hyphen in generated Claude SKILL.md bodies using the shared transformer. Fixes #3583. * Refine #3583 implementation after review: cache command names, improve tests, clean up comments * Harden gsd colon-to-hyphen transformer with bidirectional word boundaries and body-only regression guard * Track quick-wins batch status and local session notes for #3583/#3579 handoff * Port installer robustness (hoist copyLibDir + selective Codex hooks) from 3579 to make Codex tests pass on this branch. Fixes ReferenceError and prevents extra hook pollution in Codex installs. * Restore #3583 transformer wiring and Codex .sh GSD_VERSION branch lost in 50ff8f17 port Commit 50ff8f17 ('Port installer robustness from #3579') accidentally reverted: - the top-level require of transformContentToHyphen/readGsdCommandNames - the body normalization inside convertClaudeCommandToClaudeSkill - the Codex hook loop's .sh branch with {{GSD_VERSION}} substitution These were the actual #3583 fix and the Codex half of the #2136 invariant. Failing tests fixed: bug-2808-skill-hyphen-name, claude-skills-migration #3583 case, bug-2136 Codex .sh substitution. * Exempt 'sync-skills' slug from docs-parity check (skill dir name in path references) gsd-sync-skills is an installed Claude skill name and a workflow file but not a registered slash command. The docs-parity regex catches /gsd-sync-skills from filesystem path references like ~/.agents/skills/gsd-sync-skills/ in docs/discussions/grok-build-support-2026-05.md. Adding to INTERNAL_COMPONENT_SLUGS matches the existing exemption pattern for 'statusline', 'workspaces', 'graphify-update', etc. * Restrict hooks/lib/ install to hook-enabled runtimes and managed allowlist Codex/Copilot/Cursor/Windsurf/Trae/Cline already skip the hooks block but were still copying hooks/lib/ helpers, contradicting the downstream Codex comment. Gate the call on the same runtime check and pass GSD_HOOK_LIB_FILES so install scope matches the uninstall/manifest scope. |
||
|
|
7f8b5701bf |
Enforce documentation updates via lint:docs + PR templates (#3651)
* Enforce documentation updates via lint:docs + PR templates (#3213) New scripts/lint-docs-required.cjs + Docs Required CI workflow fail any PR whose changeset fragment is typed Added / Changed / Deprecated / Removed without modifying at least one file under docs/. Mirrors scripts/changeset/lint.cjs: pure evaluateLint({ changedFiles, fragments, labels }) returning { ok, reason, triggering } over a frozen LINT_REASON enum; CLI wrapper reads the PR diff and parses each touched changeset fragment via the existing parseFragment helper. Escape hatches: - no-docs PR label (global) - per-fragment <!-- docs-exempt: <reason> --> marker, all triggering fragments must carry it for the PR to pass Fixed and Security fragments do not trigger the lint — bug fixes restore documented behavior, they do not introduce new behavior to document. PR templates (enhancement.md, feature.md) gain a Documentation checklist section pointing at the which-doc-to-update matrix. CONTRIBUTING.md adds a Documentation Updates section codifying that matrix, the English-canonical language policy for docs/ and the root README, and the two opt-out routes. Closes #3213 * Address Codex review: fail-closed on malformed fragments and strip docs-exempt marker from rendered release notes (#3213) Two P2 issues caught by `codex review --base main`: 1) Malformed fragments could silently bypass docs enforcement. parseFragment would return ok:false on a triggering Added fragment with bad frontmatter and readFragmentsFromDisk dropped it, so evaluateLint saw no triggering fragments and passed. The changeset-required lint only checks fragment _presence_ not _validity_, so the assumed fallback did not catch it. Fix: readFragmentsFromDisk now returns { fragments, malformed }; evaluateLint accepts a malformed param and emits a new FAIL_MALFORMED_FRAGMENT verdict that outranks every OK path (including the no-docs label) — a parse failure must be fixed before docs lint can decide anything else. 2) The per-fragment <!-- docs-exempt: reason --> marker lived in the fragment body, so the existing changelog (serializeChangelog) and GitHub release-notes (formatBullet) serializers published it verbatim. Worse, both serializers append `(#NNNN)` to the body's last line — with the marker as the trailing line, the PR suffix attached to the hidden comment instead of the visible bullet. Fix: parseFragment now extracts the marker into a typed `docsExempt` field and strips it from `body`, so all downstream renderers produce clean output without remembering to strip. The regex is anchored to its own line (^...$ with m flag) so inline mentions of the marker syntax in documentation (e.g. inside backticks) cannot accidentally exempt a fragment. Bounded character class [^\n>] keeps the regex linear-time. Test additions: - tests/lint-docs-required.test.cjs: FAIL_MALFORMED_FRAGMENT coverage, end-to-end "Added fragment with bad pr → malformed → fail-closed" regression test, updated readFragmentsFromDisk return-shape assertions, isExemptFragment now checks the typed docsExempt field rather than body content. - tests/changeset-parse.test.cjs: extractDocsExempt extraction cases (with/ without reason, case-insensitive, EMPTY_BODY when body is only a marker), inline-mention false-positive guard, real-marker-wins-when-also-inline test. - tests/changeset-new.test.cjs: fragment shape now includes docsExempt: null. CONTRIBUTING.md updated to clarify the "on its own line" requirement and the parse-time stripping behavior. The bootstrap fragment cleaned up so its body no longer contains a literal marker example that would have triggered the false-positive case. Full suite: 9696/9696 pass. * CRLF-safe docs-exempt marker stripping (Codex review pass 2, #3213) Second `codex review --commit` pass caught a CRLF regression in the docs-exempt extraction added in the previous commit. Repro: a Windows-authored fragment ---\r\ntype: Added\r\npr: 1\r\n---\r\nFeature.\r\n\r\n<!-- docs-exempt: x -->\r\n would parse to body `Feature.\r\n\r\n\r` because: - The previous trailing-newline slice trimmed only `\n`, leaving `\r`. - DOCS_EXEMPT_RE was anchored with `$` only — in multiline mode `$` matches before `\n` but does not consume `\r`, so the marker line's trailing `\r` was left behind after replace. - The cleanup regex stripped trailing `\n` but not `\r`. Net effect: serializeChangelog emitted - Feature.\r \r \r (#1) — the `(#1)` PR suffix landed on a blank line instead of attached to the visible bullet. Same bug surfaces in github-release-notes formatBullet. Fix: - DOCS_EXEMPT_RE: add `\r?` before `$` so the regex consumes the CR of a CRLF terminator. Switch reason character class from `[^\n>]` to `[^\r\n>]` so CRLF-authored reasons don't carry a trailing `\r`. - extractDocsExempt cleanup: `[ \t\r]+$/gm` strips trailing `\r` on each line; `(?:\r?\n){3,}` collapses CRLF triple-blank-lines; `[\r\n]+$` strips every trailing line terminator (LF or CR). - parseFragment trailing-newline slice: CRLF-aware — strips `\r\n` (2 chars) before falling through to single `\n`. Tests: two CRLF regression cases in tests/changeset-parse.test.cjs — Codex's exact repro (end-to-end through serializeChangelog) plus the no-marker CRLF passthrough case. Full suite: 9698/9698 pass. * CRLF regression test asserts on parseChangelog IR not rendered text (Codex review pass 3, #3213) Third `codex review` pass caught that the CRLF regression test added in the previous commit asserted on serializeChangelog's rendered Markdown via `out.split('\n')` + `assert.match`. That violates CONTRIBUTING.md's "Prohibited: Raw Text Matching on Test Outputs" rule and the documented serializer contract in `serialize.cjs`: > tests assert via round-trip (parse(serialize(ir))) > rather than by inspecting serialized text Replace the regex check with the established `parseChangelog(out)` round-trip and assert on the structured `{ body: 'Feature.', pr: 1 }` bullet. This is also a stronger regression check than the substring match: Codex's own probe in the review session confirmed the pre-fix buggy body shape (`Feature.\r\n\r\n\r`) breaks parseChangelog's bullet regex entirely (returns `bullets: []`), so the round-trip catches the exact failure mode end-to-end. Full suite: 9698/9698 pass. * Address CodeRabbit findings: anchor link + require non-empty docs-exempt reason (#3213) CodeRabbit's review on the PR caught two actionable issues, both quick wins. Anchor link in PR templates pointed to a heading that does not exist. The CONTRIBUTING.md heading "Documentation Updates — Update the Relevant Docs" contains an em-dash, which GitHub strips entirely when generating anchor slugs (it does NOT collapse to a hyphen). The actual anchor is #documentation-updates-update-the-relevant-docs (single hyphen between every word), not #documentation-updates--update-the-relevant-docs (double hyphen where the em-dash was). Both feature.md and enhancement.md fixed. The docs-exempt marker matched a bare `<!-- docs-exempt -->` with no reason, which defeats the entire purpose of the escape hatch — the marker exists to leave an audit trail explaining WHY a PR is exempt. Without a reason it is a silent bypass. Fix: DOCS_EXEMPT_RE now requires both the colon AND a non-whitespace first reason character. Bare `<!-- docs-exempt -->`, empty `<!-- docs-exempt: -->`, and whitespace-only `<!-- docs-exempt: -->` are all rejected as if the marker were not present (`docsExempt: null`). The lint then falls through to its normal docs-required / no-docs-label checks. `isExemptFragment` in the lint module tightened too — defense-in-depth: even if a caller constructs a fragment with `docsExempt: ''` directly, it does not count as exempt. The predicate now requires `typeof === 'string'` and non-empty after trim. Tests: - changeset-parse.test.cjs: three new explicit-rejection cases (bare marker, empty reason, whitespace-only reason). Existing DOCS_EXEMPT_RE shape test extended with negative assertions for the same three forms. - lint-docs-required.test.cjs: prior "empty reason still exempt" test inverted — empty/whitespace docsExempt now produces FAIL_DOCS_MISSING. isExemptFragment helper test extended with the same negative cases. - CONTRIBUTING.md: clarified that the reason is required and non-empty. Skipped CodeRabbit's third finding ("use `npm run lint:docs` in CI workflow instead of `node scripts/lint-docs-required.cjs`") — the existing changeset-required.yml uses the direct-node form for the equivalent changeset lint, so the new docs-required.yml is convention-consistent. Switching one without the other would create drift, and switching both is out of scope for #3213. Bootstrap fragment continues to extract cleanly under the stricter regex (verified — `docsExempt` field still contains the full bootstrap reason). Full suite: 9701/9701 pass. |
||
|
|
90e60b4145 | fix(3597): avoid cleanup EPERM when cwd is inside tmp test dir | ||
|
|
6e68e19b3d |
chore: untrack CLAUDE.md (already in .gitignore as personal/local-only)
CLAUDE.md was committed before the .gitignore entry on line 4 took effect (.gitignore only filters untracked files). Switching it to local-only as intended by the existing ignore rule; file stays on disk for working copies that already have it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
5292bc5477 |
refactor(3597): extract captureConsole + toPosixPath to helpers; dedup makeTmp wrappers; CLAUDE.md MemPalace
## Helper deduplication (16 files)
Two genuinely-duplicated test helpers extracted to tests/helpers.cjs:
- captureConsole(fn) → {stdout, stderr} with ANSI strip + exception
re-throw after console restore (preserves the #2775 CR contract).
Removed from 6 bug-N test files (bug-2775, bug-2829, bug-3033,
bug-3211, bug-3231, bug-3359) where the implementation was either
byte-identical or trivially-different. installer-migration-install-
integration's captureConsole has a different signature ({value,
output}) and stays as-is.
- toPosixPath(p) → p with path.sep → '/'. Returns input unchanged if
null/undefined for safe optional-chain composition. Replaces the
local normalizePath in bug-3491-nested-git-worktree (the
prune-orphaned-worktrees inline normalizeSlashes was already swapped
for canonicalPath in the cluster-J batch).
## makeTmp/mkScratch wrappers (8 files)
Reduced each local wrapper from a 3-line fs.mkdtempSync(path.join(
os.tmpdir(), ...)) block to a 1-line arrow delegating to
createTempDir (already in helpers.cjs). Bug-number prefix conventions
stay local for readability:
- bug-2256, bug-2794, feat-3023, feat-3024 → `gsd-<bug>-${prefix}-`
- bug-3288 → makeTmpDir = createTempDir (identity)
- bug-3571 → 'gsd-3571-' (no parameter)
- feat-3595 → mkScratch = `fs-fault-${name}-`
- project-root-generator → 'gsd-parity-'
bug-3288 also collapsed local rmTmpDir into `cleanup` from helpers
(removes a separate inline rmSync site).
## CLAUDE.md — MemPalace protocol
Adds explicit instruction to call mempalace_status at session start
and mempalace_search / mempalace_kg_query before answering questions
about people, past work, or prior decisions in this project.
## Why this is a real consolidation
Initial survey suggested the bug-N test files could be parameterized
into one install-end-to-end.test.cjs — that turned out to oversell the
savings (~200 LOC on 2238) and to bury per-bug fixture context in a
table. The genuinely duplicated surface was the helpers themselves:
captureConsole copy-pasted ~6×, makeTmp variant copy-pasted ~8×.
Extracting them retires ~170 LOC of pure copy-paste without changing
any test semantics.
Validated: holodeck (ubuntu docker) 11232/0 pass; ratchet guard
still 7/7 at baseline.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
ca3be82f71 |
fix(3597): clear residual Windows test failures + add ratchet lint guard
Two more diagnostic passes (clusters J: residuals in already-touched files,
K: 12 untouched files) plus a production-code path fix and a new
ratchet-style lint guard.
## Test-only fixes (14 files)
bug-1736, bug-2248, bug-2698 — replace inline 1s-budget rmSync with the
shared cleanup() helper (5s budget, 20×250ms retries). The earlier
inline maxRetries:10 / retryDelay:100 wasn't enough to absorb Windows
Defender's deferred-scan handle hold on cold runners.
bug-2256, skill-manifest — also override USERPROFILE alongside HOME in
beforeEach/runGsdTools calls. os.homedir() reads USERPROFILE on win32,
so HOME-only stubs leak the runner's real home into the SUT.
bug-2784, bug-3608, enh-2500, enh-2790, few-shot-calibration,
gsd-settings-advanced — CRLF tolerance: literal \n in regexes against
file content (frontmatter anchors, bash-fence regex, multi-line
numbered-list captures, awk-block extractors) becomes \r?\n; split('\n')
becomes split(/\r?\n/). Windows checkout with autocrlf=true puts \r
before every \n; .+ doesn't match \r in JS regex by default.
bug-2966 — three-part fix to extractStepRun (CRLF split), awk regex
(\r?\n), and conflict-marker parser (rawLine + \r$ strip).
bug-2969, config — normalize separators on test assertions where the
SUT correctly emits \ on win32 but the test compares against /.
prompt-injection-scan — normalize relPath via replace(/\\/g, '/') before
ALLOWLIST.has() lookup. ALLOWLIST keys are POSIX; path.relative returns
backslashes on win32 → falsely scans allowlisted security module → trips
the boundary-tag detector on its own legitimate detection code.
prune-orphaned-worktrees — use the existing canonicalPath +
listedWorktreePaths(repoDir).has(...) helpers instead of substring
matching the raw path. git stores long-form canonical paths
(runneradmin), but mkdtempSync returns 8.3 short-form (RUNNER~1) on
Windows runners; plain string compare misses every entry.
## Production-code fix (1 file)
get-shit-done/bin/lib/init.cjs — bug-3491 in_nested_subdir computation
canonicalizes both worktreeRoot and cwd via fs.realpathSync.native +
path.relative before declaring "nested." Windows runner cwd (8.3 short
name) vs git's --show-toplevel (long form, forward slashes) made the
raw string compare always say true even at the worktree root, breaking
the "init new-project at worktree root" subtest.
## New ratchet lint guard
tests/windows-test-parity-guard.test.cjs — scans tests/ for 7
anti-patterns that drove the Windows failure clusters. Each rule has a
baseline count snapshot from this PR; the test fails when a new
occurrence appears (count grows above baseline), ratcheting down as
existing offenders are fixed. Patterns covered:
G1 split('\n') after readFileSync (use /\r?\n/)
G2 ```bash\n fence regex (use ```bash\r?\n)
G3 ^---\n frontmatter anchor (use ^---\r?\n)
G4 hardcoded "/tmp/..." literal passed to fs.* (use os.tmpdir())
G5 bare 'npm' to execFileSync without {shell:true} on win32
G6 process.env.HOME stub with no USERPROFILE
G7 fs.rmSync({recursive,force}) without maxRetries
Future Windows-parity regressions get caught at PR time rather than
five iterations into a CI loop.
Validated: holodeck (ubuntu docker) 11232/0 pass (count +8 = the 7
new ratchet tests + parent describe).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
3a98ce83e5 |
fix(3597): windows parity batch — sdk/install/hook/worktree clusters (21 files)
Three parallel diagnostic agents (G: sdk/install path-sep, H: hook scripts, I: worktree/workspace) characterised the remaining windows-22 failures on |
||
|
|
23b52f1a14 |
fix(3597): windows test parity batch — CRLF parsers, ESM file URLs, posix-tmp, bash-only skips, retry bump
Six independent windows-only failure clusters identified from the windows-22 CI log on |
||
|
|
aac6d3635b |
fix(3597): make markdown-fence regex CRLF-tolerant in 3 windows-failing tests
bug-2801, bug-3072, bug-3321 each read .md workflow files from disk and
parse fenced code blocks with /\`\`\`bash\n(...)\`\`\`/ — on a Windows
checkout with autocrlf=true the file content is CRLF, the regex never
matches, code blocks come back empty, and the assertion fails with
"expected bash code blocks in workflow" (and similar).
Fix: accept optional \r before each literal \n in the fence delimiter
and split lines on /\r?\n/. Same pattern bug-2839 had with its local
parseFrontmatter copy (already migrated to the CRLF-aware shared helper
in
|
||
|
|
1be0e4e2ee |
fix(3597): make windows test cleanup retry on EBUSY + drop CRLF-broken local parseFrontmatter
Two windows-only failure clusters surfaced when the chunking fix in
|
||
|
|
f8eda5bf16 |
fix(3597)(3347): write graphify rebuild lock in parent hook to close ENOTEMPTY race
The hook double-forked the rebuild subprocess and returned before the subprocess wrote .planning/graphs/.rebuild.lock. Callers (notably the feat-3347 test cleanup) waited for the lock to disappear before rm -rf'ing the tmpdir, but an absent lock was ambiguous: it could mean "subprocess finished and trapped lock removal" OR "subprocess hasn't started yet." Under ubuntu CI load the second case won, cleanup raced ahead, and rmSync walked into .planning/graphs while the subprocess was still creating files — surfacing as ENOTEMPTY: directory not empty, rmdir '/tmp/gsd-3347-*/.planning/graphs' on the "dispatches on: git commit -m fix" test. Spawn the rebuild as a regular backgrounded job, capture $!, and write the lock file synchronously in the parent before exit. Lock-presence is now a reliable in-flight signal; the rebuild script's existing trap-on-EXIT rm still owns cleanup. Validated: holodeck (ubuntu docker) 11224 pass / 0 fail. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
7fa5eb7e63 |
fix(3597): resolve CR threads — drop substring-assertion guidance, use $testDir
Two CodeRabbit threads from PR #3649 review: - docs/TESTING-SUITES.md:75 — removed the "stable message substring" fallback from the error-assertion guidance. Project rule (per the no-source-grep lint and lint-no-source-grep.cjs) is structured/typed checks only — err.code, JSON fields, enums. Substring matching re-introduces the exact prose-coupling we banned. - scripts/run-tests.cjs:106 — the "no test files found" error now reports the resolved testDir variable instead of the hardcoded 'tests/' string, so when GSD_TEST_DIR points elsewhere the message names the actual directory the harness searched. Local: docker gsd-test-summary 11224/0 on holodeck. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
52f23ac0a0 |
fix(3597): chunk node --test spawn to survive Windows CreateProcess limit
Windows CreateProcess caps lpCommandLine at 32,767 chars. The original `execFileSync(node, ['--test', ...546 paths])` exceeded that on every Windows runner and exited within ~70ms with no test output. Linux/macOS allow ~2 MB ARG_MAX so the same call worked there. `scripts/run-tests.cjs` now splits selected files into chunks that keep each spawn's argv under 28,000 chars (operator-overridable via RUN_TESTS_MAX_CMDLINE_CHARS), runs them sequentially, and reports the first non-zero exit. Cross-platform regression test forces chunking with a low ceiling and asserts the `run-tests: chunk N/M …` stderr marker. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
e82876fe45 |
feat(3597): split test suites and add Node 22/24/26 OS matrix
scripts/run-tests.cjs gains `--suite <name>` filtering using a filename
suffix convention (`*.security.test.cjs`, `*.integration.test.cjs`, …).
Files with no marker are `unit` (the default fast lane); files with a
marker land in the matching suite. No `--suite` flag preserves the prior
behavior of running every test (backcompat for `npm test` and
`npm run test:coverage`).
New package scripts wire the suites to stable entrypoints:
test:unit, test:integration, test:install, test:security, test:slow,
test:coverage:unit, test:coverage:all. Unknown suite → exit 2 with the
list of valid suites; empty suite → exit 0 with a stderr notice so empty
lanes (e.g. `security` before adversarial tests land) don't gate CI.
CI matrix grows from `ubuntu × {22,24}` + a single macOS lane to
`{ubuntu, macos, windows} × {22, 24, 26}`. `fail-fast: false` so one
lane failure doesn't cancel siblings. Node 26 is `continue-on-error`
until actions/setup-node stabilises that image. PR CI runs unit +
integration + security on every cell; `install` and `slow` only on
`main` push. A dedicated `coverage` job runs `test:coverage:unit` on
ubuntu/Node 24 and uploads the report.
Grouping policy lives in docs/TESTING-SUITES.md with a pointer from
CONTRIBUTING.md. New harness test covers arg parsing, filter selection,
empty-suite behavior, and failure propagation.
Closes #3597.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
beb7e05254 |
docs(3635): add v1.42.3 release notes and link from docs index (#3636)
New docs/RELEASE-v1.42.3.md follows v1.42.1's section layout (hotfix-shaped: Fixed only, no Added/Changed). Headline is Codex CLI 0.130.0 install routability — after install, $gsd-* skills now resolve under Codex 0.130.0+ where the previous build left zero routable entrypoints. Twelve user-visible fix categories covered: - Codex CLI 0.130.0 install materializes routable skills (#3562, #3582) - Runtime-aware slash formatter (#3584) - Argv-based subprocess for check.ship-ready (#3587) - phase_status field + /gsd:plan-phase closed-phase gate (#3569) - Canonical [features].hooks for Codex configs (#3566) - SDK bridge loads via public package export (#3567) - W006/W007 skip archived and future phases (#3559, #3560) - Ultraplan gates on Claude Code markers (#3561) - Padded phase IDs match unpadded ROADMAP prose (#3537) - Prompt-user migrations resolve in non-TTY runs (#3541) - Executor agents forbidden from git stash (#3542) - Configuration manifests load from installed payload (#3571) docs/README.md updates: - Documentation Index gets a row for v1.42.3 above v1.42.1 - Quick Links "What's new" lists v1.42.3 first, then v1.42.1 Codex CLI 0.130.0 minimum-version note added to the Installing section so operators upgrading to 1.42.3 know the Codex dependency. Closes #3635 Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
418b361a99 |
test(3595): filesystem fault-injection for platformWriteSync atomic-write seam (#3634)
* test(3595): filesystem fault-injection for platformWriteSync atomic-write seam
Per CONTRIBUTING.md §"QA Matrix Requirements / Filesystem writes and
installers", adds adversarial coverage against the canonical write
seam every CJS config/state/generated-artifact writer routes through —
`platformWriteSync` in get-shit-done/bin/lib/shell-command-projection.cjs.
Fault matrix exercised via node:test mock.method() on real fs seams,
with t.after() restoring mocks so failures don't leak between tests:
- happy path baseline (atomicity + no orphan tmp file)
- renameSync EXDEV → fallback path writes directly, tmp cleaned up
- tmp writeFileSync ENOSPC → fallback writes directly
- both tmp and fallback fail → fallback error propagates (PINNED:
original cause is swallowed; open follow-up for .cause chaining)
- mkdirSync EACCES → escapes unhandled (PINNED current behavior)
- target path is an existing directory → typed errno code surfaces
AND the directory is preserved
- paths with spaces / Unicode / tabs / newlines (POSIX only for \n)
- 25 sequential writes leave 0 tmp orphans (cleanup invariant)
- platformEnsureDir is idempotent (no EEXIST throw)
- platformEnsureDir EACCES propagates
Symlink-safety invariants (security-critical):
- REPLACES a symlink with a regular file rather than following it —
a planted symlink in .planning/ pointing at ~/.ssh/authorized_keys
is NOT clobbered. Test pins this so a future refactor to
fs.writeFileSync (which follows symlinks) is a visible regression.
- Broken symlinks are replaced with the intended regular file.
Concurrent-write collision: a renameSync EBUSY on the in-flight write
must still produce a parseable, complete final file via the fallback —
never a half-written corruption.
13 new tests; total 192/192 pass when bundled with the pre-existing
state/config/worktree-safety suites (179 of theirs). Zero production
code changes — test-only PR.
Two known-open gaps deliberately NOT fixed in this PR (warrant separate
focused issues):
- platformWriteSync fallback path swallows the original tmp-write
error. Operator only sees the fallback's error when both fail.
- platformWriteSync mkdirSync(dirname, recursive:true) error
escapes without context — no message-wrapping or typed reason.
Closes #3595
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test(3595): use t.skip() for Win32 symlink gates + fix rename mock delegation
Two Codex review findings addressed:
1. Symlink tests previously used `if (process.platform === 'win32') return`
which CI reports as PASS even though the test ran zero assertions.
Replaced with `t.skip('symlinks on Win32 need admin'); return;` so
CI correctly reports SKIPPED on Windows lanes. Applied to both the
symlink-replace and broken-symlink tests.
2. The concurrent-collision test's rename mock referenced a
non-existent `fs.renameSync.wrapped` property in its fallback
branch — that path would silently no-op instead of delegating to
the real renameSync. Capture the real `fs.renameSync` BEFORE
installing the mock and call `originalRename.call(fs, src, dest)`
in the fallback branch, matching the ENOSPC test's pattern.
Codex review on PR #3634.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
b1317633db |
test(3594): adversarial parser fixtures + frontmatter/roadmap matrix + property-style suite (#3633)
* test(3594): adversarial parser fixtures + frontmatter/roadmap matrix + property-style suite
Lands the adversarial parser-input corpus that CONTRIBUTING.md
§"QA Matrix Requirements / Parser and project-file inputs" and
TEST-EXAMPLES.md §"Parser Adversarial Fixtures" describe.
New tests/fixtures/adversarial/ layout:
frontmatter/
duplicate-keys.md — same key twice (collapses last-wins)
crlf-mixed.md — CRLF endings throughout
unclosed-block.md — `---` open with no close
unicode-keys-and-values.md — non-ASCII + emoji + Greek
null-byte-value.md — U+0000 in a value
huge-bounded.md — 2000-item array, ~30KB
roadmap/
phase-heading-inside-fenced-code.md — #2787 fence shadowing
nested-fenced-code.md — outer + inner ``` blocks
unicode-phase-titles.md — JP / Greek / emoji titles
repeated-phase-ids.md — phase 1 declared twice
decimal-phase-mixed.md — 2 vs 2.1 vs 2.10 vs 21
markdown-headings-inside-html-comment.md — comment shadowing
Test files (all node:test, no try/finally in test bodies, no source-grep,
no raw-text matching on stdout/file content):
tests/feat-3594-parser-adversarial-frontmatter.test.cjs (12 tests)
Loads each fixture, pins parser invariants on extractFrontmatter()
return shape. Cross-corpus "does not throw on any fixture" sweep.
tests/feat-3594-parser-adversarial-roadmap.test.cjs (18 tests)
Loads each fixture into a temp project's .planning/ROADMAP.md and
drives `gsd-tools roadmap get-phase <N>` via the runCli harness
introduced by #3593. Asserts on the typed JSON payload.
tests/feat-3594-parser-property-style.test.cjs (2 tests)
Deterministic mulberry32 PRNG generates 500 malformed-ish
frontmatter inputs per test. Pins (a) extractFrontmatter is total
over the corpus (no null-deref TypeError, always returns a plain
object on success), (b) the suite completes well under 2 seconds
(quadratic-regression guard).
Known-open bugs surfaced and pinned (intentionally NOT fixed in this
PR — separate issues warranted):
- CJS roadmap parser matches `## Phase N:` headings inside fenced
code blocks (the SDK parser tracks fences per the #2787 comment in
sdk/src/query/roadmap.ts but the CJS path has not caught up).
- CJS roadmap parser matches `## Phase N:` headings inside HTML
comments.
Both are documented in-test with the "currently STILL matches it
(open: needs <fix>)" naming pattern so the day the production fix
lands, flipping the assertion from `found: true` to `found: false` is
the regression guard.
Test totals:
- 32 new feat-3594-* tests (12 frontmatter + 18 roadmap + 2 property)
- 108/108 pass when running together with the pre-existing
frontmatter.test.cjs + roadmap.test.cjs suites (76 of theirs).
Closes #3594
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test(3594): use Fisher-Yates shuffle for deterministic seeded inputs
Replaces `arr.sort(() => rng() - 0.5)` with a Fisher-Yates shuffle
driven by the supplied PRNG. The sort-based shuffle is non-transitive:
V8's TimSort behavior on non-transitive comparators is engine-defined,
so the same seed produced different orderings across Node versions —
undermining the test's stated reproducibility guarantee.
Fisher-Yates is O(n), transitive (no comparator at all), and consumes
exactly n-1 RNG values in a fixed order. The mulberry32 seed now
determines the input sequence end-to-end.
Codex review on PR #3633.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
5583b81f35 |
fix(3628)(security): whitelist bundled hook filenames in classifier (#3630)
#3610 added a `bundled-gsd-hook` classifier to classifyPromptUserAction
that auto-removes blocked hook files on first-time-baseline scan. The
match shape was a regex `/^hooks\/gsd-[^/]+\.(?:js|sh|cjs|mjs)$/` that
matched ANY file under hooks/ named gsd-<name>.{js,sh,cjs,mjs}, not only
the 13 hooks the npm package actually ships. As a result the classifier
silently auto-classified — and the resolver auto-removed — user-authored
custom hooks (hooks/gsd-personal-experiment.js) and retired bundled hooks
from prior versions (hooks/gsd-old-statusline.js).
Evidence the maintainer was already working around this:
|
||
|
|
037a49c9c2 |
test(3593): CLI negative-matrix harness + config family + universal sweep (#3627)
Adds the shared adversarial-input harness described in TEST-EXAMPLES.md
§"CLI Negative Matrix" and applies it across two layers:
1. tests/helpers/cli-negative.cjs — runCli() wraps spawnSync of
get-shit-done/bin/gsd-tools.cjs, prepends --json-errors by default,
and returns a typed IR { status, ok, reason, message,
hasStackTrace, ... } so adversarial-case tests assert on
reason codes — never on stderr prose.
2. tests/feat-3593-cli-negative-config.test.cjs — full 12-category
matrix for the config command family (the highest-risk read/write
surface): missing/empty/whitespace args, duplicate --cwd,
unknown subcommand, value-looks-like-a-flag, corrupt config.json,
50KB key, Unicode/emoji keys and values, and 9 distinct shell-
metacharacter payloads asserted as NOT-executed via per-test
sentinel-file probes.
3. tests/feat-3593-cli-negative-universal.test.cjs — narrower
cross-family sweep (phase, roadmap, state, config, workstream,
init, validate). Pins the three universal invariants every
family must satisfy: bare invocation does not crash with a V8
stack trace, unknown subcommand emits a typed reason, and shell
payloads as argv values are not executed.
4. tests/feat-3593-cli-negative-harness.test.cjs — meta-test that
pins the harness IR contract so a future regression in the
parser (stack-trace detection, JSON shape extraction, hostile
stderr handling) surfaces before it cascades through every
matrix file.
Bug fix surfaced by the new tests:
get-shit-done/bin/lib/config.cjs cmdConfigSet — invoking
`config-set <key>` with no value silently returned exit 0 and
emitted { updated: true } even though the value parameter was
undefined. JSON.stringify dropped the key during the write or
persisted a corrupt entry. Now rejected with typed ERROR_REASON.USAGE
before any write. Matching guard added to SDK configSet for parity.
Harness coverage delivered: 58 new tests (9 meta + 26 config + 23
universal sweep). Pre-existing config suites (101 tests) all pass.
lint-no-source-grep clean.
Refs #3593
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
90eb9e0f8b |
Merge pull request #3607 from gsd-build/feat/3592-test-rewrite-text-existence-checks-into-
test: rewrite alias coverage as behavioral contract |
||
|
|
724133967a |
Merge pull request #3606 from gsd-build/fix/3584-runtime-bin-lib-cjs-emit-gsd-cmd-in-user
fix(3584): runtime-aware slash formatter for user-facing emissions |
||
|
|
afbf0d80de |
Merge pull request #3609 from gsd-build/fix/3582-codex-0-130-0-gsd-1-42-2-installation-su
fix(3582): regression test for Codex install skill-materialization |
||
|
|
2cf4c8e8ec |
Merge pull request #3611 from gsd-build/fix/3587-bug-security-check-ship-ready-shell-inje
fix(3587)(security): argv-based subprocess for check.ship-ready |
||
|
|
38f5201935 |
Merge pull request #3613 from gsd-build/fix/3605-stale-slash-command-references-in-5-agen
fix(3605): scrub retired slash commands from agents/*.md |
||
|
|
d4dbca5b49 |
Merge pull request #3614 from gsd-build/fix/3608-bug-antigravity-gsd-update-does-not-dete
fix(3608): model Antigravity as a first-class runtime in update.md |
||
|
|
55e1360b2c |
Merge pull request #3616 from gsd-build/fix/3599-bug-roadmap-get-phase-no-longer-matches-
fix(3599): preserve project-code prefix when looking up roadmap phases |
||
|
|
6bd4091aa0 |
Merge pull request #3618 from gsd-build/fix/3610-error-installing-v1-42-2-in-codex
fix(3610): unblock fresh Codex install when leftover bundled hooks present |
||
|
|
9351d0a0e7 |
Merge pull request #3619 from gsd-build/fix/3601-bug-phase-remove-can-delete-following-de
fix(3601): preserve peer-depth decimal phases on integer phase removal |
||
|
|
9fab09cdce |
Merge pull request #3620 from gsd-build/fix/3602-bug-phase-remove-leaves-stale-slugged-pl
fix(3602): renumber slugged plan references on phase removal |
||
|
|
8c46c3f1af |
Merge pull request #3622 from gsd-build/fix/3600-bug-init-new-milestone-counts-prefixed-p
fix(3600): count project-code-prefixed phase dirs in milestone filter |
||
|
|
afb0692522 |
Merge pull request #3624 from gsd-build/fix/3591-bug-gsdtools-native-runtime-drops-workst
fix(3591): forward workstream to native registry dispatch |
||
|
|
8b31739a01 |
Merge pull request #3626 from gsd-build/fix/3589-bug-security-sdk-planningpaths-accepts-u
fix(3589)(security): validate workstream in relPlanningPath against traversal |
||
|
|
66a429adf4 |
docs(3621): forbid bundling test-fixture updates into docs: commits (#3625)
Adds a Pull Request Guidelines bullet making explicit what v1.42.3 hotfix taught us the hard way: when a production change makes an existing test assertion stale, the test correction must be its own test: (or fix:) commit, not bundled into a docs: commit that also explains the change. The release-sdk hotfix cherry-pick filter routes by commit-subject prefix (fix:, chore:, test: — see release-sdk.yml). A docs: commit that hides a test fix is invisible to the picker. The result is a half-shipped state on the hotfix branch: production code changed, test assertion stale, CI red. This is the upstream contributor-side mitigation. The picker-side fix landed in PR #3623 (broadens the prefix filter and the classifier's CI-gating-path detection); this PR documents the upstream discipline that keeps the picker from getting fooled in the first place. Refs #3621 Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
11a6d3fdd6 | test(3589): assert empty-string workstream fallback |