26773bd6698d753cd16f4e8a8b6219f55d32bddc
2904 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
26773bd669 |
fix(3735): add surface to PROFILES.core (restore ADR-0011 expand contract) (#3744)
* test(3735): add failing test that PROFILES.core includes surface
Regression test asserting that resolveProfile({ modes: ['core'] }) includes
'surface' in its transitive closure — the ADR-0011 contract that --profile=core
users can expand via /gsd:surface enable <cluster>. Also updates stale
hardcoded skill-count assertions (7→8) across install-minimal*.test.cjs and
install-profiles-resolve.test.cjs to reflect the correct post-fix baseline.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(3735): add 'surface' to PROFILES.core to restore ADR-0011 expand contract
PROFILES.core omitted 'surface', silently breaking the documented contract
in ADR-0011 that --profile=core users can expand their skill surface via
/gsd:surface enable <cluster>. The sub-command is only available if surface.md
is staged — which requires it to appear in the resolved set for the core profile.
Added 'surface' to both PROFILES.core and PROFILES.standard (standard is a
documented superset of core; omitting it from standard would break the
"standard must include all core skills" invariant and the resolveProfile tests).
The MINIMAL_SKILL_ALLOWLIST back-compat shim is derived from PROFILES.core so
it picks up surface automatically, ensuring --minimal and --core-only installs
also stage surface.md.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* chore(3735): update changeset to reference PR #3744
The fix commit landed with the linked-issue number as the pr: field
placeholder. Updating to the actual PR number now that the PR is open.
* docs(install-profiles): fix stale 'six skills' count in module header comment
After #3735 added surface to PROFILES.core the skill count became eight,
but the module-level comment still said "six skills covering the main project
loop". Update the description to reflect the correct count and reference the
ADR-0011 expand contract that surface fulfils.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
|
||
|
|
86e067924f |
fix(3727): wire --fix flag dispatch in code-review workflow (#3743)
* test(3727): add failing test for --fix flag dispatch in code-review workflow Adds bug-3727-code-review-fix-flag-dispatch.test.cjs with: - Pure-function tests on parseCodeReviewFlags() / resolveCodeReviewWorkflow() from new code-review-flags.cjs typed IR module - Structural docs-parity tests asserting dispatch_fix step exists in code-review.md and that initialize step references code-review-flags.cjs Structural tests FAIL today (RED): workflow has no dispatch_fix step and no reference to code-review-flags.cjs in initialize. IR-level tests pass because the lib module is introduced in this commit as the typed seam. Fixes #3727 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(3727): wire --fix/--all/--auto flags through code-review workflow The initialize step now parses --fix, --all, and --auto from argv (via the code-review-flags parser seam added in the previous test commit) and the workflow dispatches gsd-code-fixer when --fix is truthy and the review returned findings. Fixes the regression introduced when PR #2947 closed #2946 without actually shipping the workflow dispatch. Fixes #3727 * chore(3727): update changeset to reference PR #3743 The fix commit landed with the linked-issue number as the pr: field placeholder. Updating to the actual PR number now that the PR is open. * fix(3727): register code-review-flags.cjs in INVENTORY.md and manifest Add missing row for get-shit-done/bin/lib/code-review-flags.cjs to the CLI Modules table in docs/INVENTORY.md (count 72→73) and regenerate docs/INVENTORY-MANIFEST.json to fix three failing CI tests: - cli-modules-doc-parity: every CLI module must have a row in INVENTORY.md - inventory-counts: headline "CLI Modules (N shipped)" must match file count - inventory-manifest-sync: INVENTORY-MANIFEST.json must match filesystem Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
e690f1bc90 |
fix(3718): shell-free Node.js UI safety gate — fixes PowerShell silent-fail (#3718)
* fix(workflows): add word-boundary anchoring to UI safety gate grep Replace unanchored grep -iE "UI |..." alternation with POSIX ERE word-boundary-anchored form: LC_ALL=C grep -iE "(^|[^[:alnum:]])(UI|...)([^[:alnum:]]|$)" Unanchored form matched 'ui' inside 'requirements', 'view' inside 'overview' and 'review', 'form' inside 'performance'/'platform'/ 'transform' — producing HAS_UI=0 on 100% of standard roadmap phases (every phase contains a **Requirements**: field). Fix applied to both plan-phase.md:625 and autonomous.md:284. LC_ALL=C added for POSIX locale portability on both BSD and GNU grep. Closes #3706 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test(workflows): add regression tests for UI safety gate false-positives (#3706) - bug-3706-ui-safety-gate-false-positives.test.cjs: 36-test suite covering both plan-phase.md and autonomous.md gate behavior; verifies that Requirements/overview/performance/platform/transform/review/build/screening do NOT trigger the gate, while standalone UI/view/form/screen/dashboard/ component/lowercase-ui/hyphenated-non-UI DO trigger it. - autonomous-ui-steps.test.cjs: update stale assertion that checked for the old broken grep pattern; now asserts the word-boundary-anchored form. Test strategy: extract the POSIX ERE pattern from the workflow file and simulate grep match semantics in JS (no shell exec, no source-grep). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * chore(changeset): add Fixed fragment for PR #3718 (UI safety gate false-positives) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(workflows): document compound-token boundary contract; add comment to gate Addresses adversarial review finding: word-boundary anchoring intentionally does not match tokens embedded in compound alphanumeric words (e.g. "microfrontend", "dashboardWidget", "uiSpec"). This is correct behavior — gsd-roadmapper generates natural English prose, not camelCase compounds. Hyphenated forms ("micro-frontend") and spaced forms are caught by the anchored pattern (hyphen is [^[:alnum:]]). Add inline comment in both workflow files explaining the pattern intent, the false-positive prevention, and the compound-word contract. Add 4 tests (2 per workflow) documenting the compound-word contract: - "microfrontend" (compound) must NOT trigger gate (documented behavior) - "micro-frontend" (hyphenated) MUST trigger gate (correct true-positive) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(3718): replace shell grep gate with shell-free Node.js helper Moves UI safety gate logic from `LC_ALL=C grep -iE` (silently broken on Windows PowerShell — locale env-var prefix not recognised by pwsh) to `bin/lib/ui-safety-gate.cjs` (Node.js, reads via stdin to avoid ARG_MAX). Path is anchored via `git rev-parse --show-toplevel` (GSD_REPO_ROOT) to avoid CWD-sensitive failure when Claude Code executes from a subdirectory. Word-boundary regex is identical to the original POSIX ERE pattern: (^|[^a-zA-Z0-9])(TOKEN)([^a-zA-Z0-9]|$) Exit codes mirror grep: 0 = UI found, 1 = not found. Tests: 51/51 pass — includes spawnSync shell:false + stdin cross-shell portability tests and ARG_MAX large-input test. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(3706): address pr-review-toolkit + codex review findings - Multi-token-per-line: use matchAll to capture all UI tokens - Add test for multiple distinct tokens on same line - Clarify ASCII vs POSIX [:alnum:] in word-boundary comment - Correct misleading "path anchored" comment in plan-phase/autonomous workflows - Remove UI_GATE_PATTERN from module.exports (internal implementation detail) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(state): restore ACQUIRE_LOCK_RETRY_ERRNOS in acquireStateLock (#3718) Commit |
||
|
|
c1c8b0d109 |
fix(3739): gap-checker now detects padded-prefix CONTEXT.md (#3764)
* test(3739): add RED tests for padded-prefix CONTEXT.md gap-checker miss Covers bare and padded (01-CONTEXT.md, 02.1-CONTEXT.md) forms, an uncovered-decision counter-test, and unit tests for the upcoming findContextMdIn() helper. All 6 new tests fail before the fix. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(3739): extract findContextMdIn() helper; fix gap-checker bare lookup gap-checker.cjs:136 used a bare path.join(absPhaseDir, 'CONTEXT.md') that silently returned '' for any phase using the padded-prefix convention (01-CONTEXT.md, 02.1-CONTEXT.md, etc.). Extract findContextMdIn(absDir) to planning-workspace.cjs — the module already imported by gap-checker, init, roadmap, and core — and wire gap-checker.cjs to call it instead of the bare lookup. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * refactor(3739): replace inline dual-form predicate with findContextMdIn() at all 4 remaining sites The dual-form predicate `f.endsWith('-CONTEXT.md') || f === 'CONTEXT.md'` existed verbatim at 5 sites across init.cjs (×3), roadmap.cjs, and core.cjs — Rule of Three mandates extraction at ≥3 sites. All 4 remaining call sites now delegate to findContextMdIn() from planning-workspace.cjs. No behaviour change; all existing tests pass. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * chore(3739): update changeset to reference PR #3764 * fix(3739): findContextMdIn prefers bare CONTEXT.md over padded form (deterministic precedence) `.find()` with `f.endsWith('-CONTEXT.md') || f === 'CONTEXT.md'` returned the first match in `readdirSync` order — undefined on most filesystems. When both `CONTEXT.md` and `01-CONTEXT.md` exist the winner was arbitrary. The old gap-checker.cjs:136 code always used bare `CONTEXT.md` first (existsSync on the bare path). Restore that invariant: check `files.includes('CONTEXT.md')` before falling through to the padded scan. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test(3739): dual-file precedence test — bare CONTEXT.md wins over padded form Adds two test cases for the scenario where both CONTEXT.md and 01-CONTEXT.md exist in the same phase directory: 1. Helper level: findContextMdIn() must return 'CONTEXT.md' (not the padded filename) when both files are present on disk. 2. Integration level: gap-analysis must resolve decisions from the bare form only; D-PADDED (from 01-CONTEXT.md) must not appear when the bare form shadows it. Without these tests a future change to findContextMdIn could silently regress the precedence guarantee. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(3739): bug-2798 tests skip cleanly when sdk/dist is absent (was hard-fail) The 3 tests in bug-2798-context-window-config-key.test.cjs invoke the built SDK CLI (sdk/dist/cli.js) and require sdk/dist/query/config-schema.js. When dist is absent, they threw hard errors rather than observable skips. Apply the same `if (!existsSync(...)) { t.skip(...); return; }` guard used in bug-2767-gsd-sdk-commit-files-flag.test.cjs (c2812313). Tests 1 & 2 guard on sdk/dist/cli.js; test 3 guards on sdk/dist/query/config-schema.js. When dist is present all 3 run; when absent all 3 emit actionable skip lines. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(3739): eliminate double readdirSync in findContextMdIn callers findContextMdIn now accepts either a directory path or an already-read files array, allowing callers that already hold a directory listing (core.cjs:getPhaseFileStats, roadmap.cjs:countPhasePlansAndSummaries, gap-checker.cjs:runGapAnalysis) to skip redundant readdirSync calls. Test coverage added for the array-argument overload. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test: redesign flaky concurrent add-blocker test with deterministic barrier Previous design relied on OS scheduler to interleave two subprocess writes, producing a flake under CI load. Redesigned using a file-barrier (Option A) that forces both subprocesses to reach a ready-gate before either proceeds, guaranteeing true concurrent lock contention and eliminating timing dependency. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
aab01f43e9 |
refactor(tests): consolidate Phase Lifecycle Module — 20 files → 4 (#3741)
* chore(tests): lint rule — cap test files per production module at 2 Adds scripts/lint-test-file-count.cjs with a ratcheted allowlist (scripts/lint-test-file-count.allowlist.json) capturing today's 30 violating clusters as a ceiling. New entries blocked at PR time; reductions ratchet automatically. Wires into .github/workflows/test.yml as a new step in lint-tests. Refs #3737 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * refactor(tests): consolidate Phase Lifecycle Module — 20 files → 4 - Merge 10 CJS bug-fix test files into tests/phase.test.cjs - Merge sdk/src/phase-runner-types.test.ts + sdk/src/phase-prompt.test.ts into sdk/src/phase-runner.test.ts (97 → 152 tests, 0 failures) - Rename 4 mis-attributed test files out of phase cluster: phase-researcher-app-aware → gsd-researcher-app-aware phase-researcher-flow-diagram → gsd-researcher-flow-diagram feat-3023-phase-type-models → feat-3023-model-phase-types phase-6-cjs-sdk-seam-contracts → cjs-sdk-bridge-seam-contracts - Fix phasePlanIndex (#3430): non-canonical plan filename warning now surfaces in data.warnings[] as "Ignored noncanonical plan files: ..." instead of a separate singular data.warning field - Update allowlist: phase ceiling 20 → 4 (closes #3740) - Add Phase Lifecycle Module glossary entry to CONTEXT.md Closes #3740 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(phase-roadmap-mutation): replaceInCurrentMilestone handles active milestone inside <details> block When the active milestone is wrapped in a <details> block (e.g. user collapsed it, or milestone transition), the after-</details> slice is empty or contains only footer text — the pattern never matches and the replacement is silently dropped. Fix: when after.replace() produces no change, fall back to replacing inside the last <details>...</details> block. Shipped-milestone blocks are untouched because only the last <details> block is targeted. Closes #2641 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(changeset): add required type/pr frontmatter to 3740 fragment Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
4f56c3b10b |
refactor(tests): consolidate Worktree Module — 13 files → 3 (#3752)
* refactor(tests): consolidate Worktree Module — 13 files → 2 Closes #3742 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(changeset): correct frontmatter format for 3742 fragment type:/pr: fields required by docs-lint; replaces @changesets/cli package-bump format with the repo's custom fragment schema. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * refactor(tests): split consolidated worktree.test.cjs along cleanup seam (≤ 800 LOC/file) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * chore(changeset): update 3742 fragment — 13→3 files, ≤800 LOC/file Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * chore(changeset): fix pr reference 3738→3752 in 3742 fragment Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
c4e39bc23a |
refactor(tests): consolidate graphify Module — 7 files → 1 (#3769)
* refactor(tests): consolidate graphify Module — 7 files → 1 Closes #3761 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * refactor(tests): split graphify.test.cjs along describe-block seams — keep files ≤ 800 LOC - tests/graphify.test.cjs (653 LOC): status + build - tests/graphify-query.test.cjs (447 LOC): query - tests/graphify-visualization.test.cjs (577 LOC): staleness + mvp-viz + regressions - tests/graphify-auto-update.test.cjs (625 LOC): auto-update hook - tests/helpers/graphify.cjs (112 LOC): shared helpers extracted Total: 132 tests, 0 failures. Refs #3761. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
1926353b5e |
refactor(tests): consolidate Runtime Artifact Layout Module — 12 files → 3 (#3759)
* refactor(tests): consolidate Runtime Artifact Layout Module — 12 files → 3 Closes #3757 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(changeset): add proper frontmatter to 3757 fragment Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
3d52f5ee46 |
refactor(tests): consolidate Init Command Module — 7 files → 5 (#3756)
* fix(3687): update insert-phase docs and roadmapper to use --insert flag Updates stale references in insert-phase.md workflow and gsd-roadmapper.md agent to use the consolidated /gsd:phase --insert command syntax instead of the retired /gsd-insert-phase and /gsd:phase insert forms. Closes #3687 * refactor(tests): consolidate Init Command Module — 7 files → 5 Closes #3755 Merges `tests/init-manager-deps.test.cjs` (#2267 regression) into `tests/init-manager.test.cjs` (718 LOC), and `sdk/src/query/init-progress-precedence.test.ts` (#2674 regression) into `sdk/src/query/init-complex.test.ts` (788 LOC). The 800 LOC ceiling prevents further consolidation: - `tests/init.test.cjs` is pre-existing at 1630 LOC - `sdk/src/query/init.test.ts` is at 791 LOC - `sdk/src/query/init-workstream-milestone-op.test.ts` is a distinct seam testing initMilestoneOp, roadmapAnalyze, and resolveQueryRuntimeContext workstream resolution. Also adds Init Command Module Glossary entry to CONTEXT.md. Allowlist update deferred to rebase after #3738 merges (allowlist file does not exist on origin/main). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(init): initExecutePhase preserves same-milestone archived phase dir (#3469) When `phases clear` archives current-milestone phases into `.planning/milestones/<version>-phases/` but the workflow is still on that same milestone, `shouldDropArchivedPhaseMatch` was unconditionally dropping the archived dir match. This caused `phase_dir: null` when the phase was still executing in the current milestone. Fix: detect when `phaseInfo.archived === currentMilestone` (read from STATE.md) and skip the drop. The #2391 regression guard is safe because that scenario involves archived.version != current milestone. Also corrects two tests in `initRemoveWorkspace` to expect thrown GSDError instead of `{ data: { error } }` — the production code was intentionally changed to throw for CLI non-zero exit propagation. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: arya rizky <aryarizkyardhipratama@gmail.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
9f57f7488e |
refactor(tests): consolidate Milestone Module — 10 files → 4 (#3754)
* refactor(tests): consolidate Milestone Module — 10 files → 4 Closes #3753 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(changeset): correct fragment format for 3753-consolidate-milestone-tests Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(changeset): add required frontmatter to 3753 changeset fragment Adds `type: Fixed` and `pr: 3753` frontmatter so docs-lint passes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(tests): bug-2943 execFileSync timeout 5s→15s for Windows starvation PR #3754's milestone consolidation reshuffles run-tests.cjs chunk composition. On Windows/Node 22 under --test-concurrency=4, bug-2943 subprocesses now share concurrency slots with bug-2760-codex-install subtests (8–15s each), starving past the 5000ms timeout. 15s covers the observed 13.5s worst case with headroom. Refs #3753 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
f955bf872c |
refactor(tests): consolidate Installer Module — 11 files → 2 (#3760)
* chore(tests): lint rule — cap test files per production module at 2 Adds scripts/lint-test-file-count.cjs with a ratcheted allowlist (scripts/lint-test-file-count.allowlist.json) capturing today's 30 violating clusters as a ceiling. New entries blocked at PR time; reductions ratchet automatically. Wires into .github/workflows/test.yml as a new step in lint-tests. Refs #3737 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * chore(tests): add docs-exempt to changeset fragment Internal CI lint rule — no user-facing docs impact. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * refactor(tests): consolidate Installer Module — 11 files → 2 Closes #3758 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(changeset): replace invalid type 'Chore' with 'Changed' — ALLOWED_TYPES only accepts Added|Changed|Deprecated|Removed|Fixed|Security Fragment already had a docs-exempt marker; only the type value was wrong. Refs #3758 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * refactor(tests): split install.test.cjs along section seams — keep files ≤ 800 LOC 1828-LOC monolith split into 3 files by semantic boundary: install.test.cjs (602 LOC) — S1-5: dir resolution, install/uninstall spot-checks, Kilo install-runtime-artifacts.test.cjs (347 LOC) — S6-8+12: layout loop, Contract 6, legacy migrations install-minimal-hooks.test.cjs (782 LOC) — S9-11+13: profiles, minimal E2E, hooks copy Shared constants and helpers extracted to tests/helpers/install-shared.cjs (216 LOC). Total test count unchanged: 210 tests (68+37+105). Refs #3758 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(tests): ratchet state test-file ceiling to 9 — actual count at baseline The allowlist was initialised with state=8 but the repo already had 9 files matching the 'state' prefix at the time the lint rule was created, causing lint-test-file-count.test.cjs to fail on the real repo immediately. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
6313baad63 |
chore(tests): lint rule — cap test files per production module at 2 (#3738)
* chore(tests): lint rule — cap test files per production module at 2 Adds scripts/lint-test-file-count.cjs with a ratcheted allowlist (scripts/lint-test-file-count.allowlist.json) capturing today's 30 violating clusters as a ceiling. New entries blocked at PR time; reductions ratchet automatically. Wires into .github/workflows/test.yml as a new step in lint-tests. Refs #3737 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * chore(tests): add docs-exempt to changeset fragment Internal CI lint rule — no user-facing docs impact. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
479839147e |
fix(state): acquireStateLock retries on transient Docker/NFS errno codes (#3777)
* fix(state): acquireStateLock retries on transient Docker/NFS errno codes Expands retry allowlist to ENOENT/EINVAL/EIO/ESTALE/EAGAIN/EINTR in addition to existing EPERM/EBUSY. Truly fatal codes (EMFILE/ENOSPC/EROFS/EACCES) still throw. Resolves the locking-bugs regression seen across all 8 open consolidation PRs (#3738, #3741, #3752, #3754, #3756, #3759, #3760, #3769). Closes #3776 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * chore(changeset): update pr number to 3777 in retry-allowlist fragment Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
ca2644a71a |
fix(worktree): unlock-retry on locked cleanup + startup orphan sweep (#3707) (#3719)
* fix(worktree): unlock-retry on locked cleanup + startup orphan sweep (#3707) Two root causes fixed: 1. **In-session cleanup blocked**: `executeWorktreeWaveCleanupPlan` now attempts `git worktree unlock <path>` then retries `git worktree remove --force` when the initial single-force remove fails on a locked worktree. Previously every cleanup after a successful merge was silently blocked. 2. **Cross-session orphan accumulation**: new `reapOrphanWorktrees` helper sweeps `.git/worktrees/*/locked` at startup. It reaps entries where the pid is dead, the branch tip is an ancestor of the default branch (ancestry guard prevents data loss on squash-merge repos), and the lock mtime is older than 5 minutes (race guard). Wired into `quick.md` and `execute-phase.md` startup blocks guarded by `USE_WORKTREES != false`. SDK: adds `worktree.reap-orphans` query command (routes through gsd-tools.cjs). Tests: 11 real-fs tests covering unlock-retry, dead-pid reap, live-pid skip, unmerged skip, fresh-mtime skip, idempotent double-call, and structural wiring. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * chore(changeset): add Fixed fragment for PR #3707 (worktree orphan cleanup) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(worktree): fix test portability on Windows + macOS for bug-3707 reap tests - worktreeMeta helper: replace /\/\.git$/ with /[/\\]\.git$/ so the gitdir path suffix is stripped on both Windows (backslash) and Unix. - worktreeMeta helper: normalize CRLF→LF before splitting porcelain blocks, fixing block parsing when git emits CRLF on Windows. - reapOrphanWorktrees: replace single 'main' rev-parse with a [defaultBranch, 'main', 'master'] candidate loop so test fixtures without a remote origin (where branch may be 'master') don't bail early. Intentionally excludes 'HEAD' to prevent false reaping when HEAD is detached or on a feature branch (Codex adversarial finding). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(worktree): CI green — macOS symlink path, Windows test helper, pid portability, EPERM liveness Four fixes to get macOS + Windows CI from red to green: 1. **macOS symlink mismatch** (worktree-safety.cjs): `reapOrphanWorktrees` now builds a canonical→listed path map from `git worktree list --porcelain` using `fs.realpathSync.native`. Uses the listed path (as git knows it) for `git worktree unlock/remove`, not the gitdir-derived path. Fixes the `/var/folders` vs `/private/var/folders` discrepancy on GitHub macOS runners where `git worktree unlock <realpath>` was silently failing because git's list stored the unresolved symlink path. 2. **Windows path separator in test helper** (test file): `worktreeMeta` `.replace(/\/\.git$/, '')` → `.replace(/[/\\]\.git$/, '')`. On Windows, git writes backslash separators in the gitdir file; the Unix-only regex was causing `Cannot find .git/worktrees/<name>` for all Suite 2 tests. 3. **Non-portable PID in tests** (test file): All `'999999'` dead-PID literals replaced with `deadPid()` helper that spawns a real short-lived child, captures its PID, and returns it after exit. Eliminates flakiness on Linux systems where `pid_max` can reach 4194304, making 999999 a live PID. 4. **EPERM fail-closed in isPidAlive** (worktree-safety.cjs): `catch { return false }` → checks `err.code === 'EPERM'` and returns `true` (alive). On Windows and cross-user scenarios, `process.kill(pid, 0)` throws EPERM for live but inaccessible processes; treating that as dead would reap a live worktree. Adversarial review via codex confirmed: - Squash-merge repos: fail-closed (CONCERN, not BUG — by design, not data-loss) - canonicalToListed map: SAFE (fail-closed on realpathSync error) - Concurrent reapers: SAFE (both prune; second gets skipped: remove_failed) - Startup blocking: CONCERN (no global cap, 10s/call × N worktrees) — tracked, not fixed here (requires separate perf work) - gsd-sdk missing: SAFE (quick.md checks and fails fast with guidance) All 27 local tests + Docker (holodeck) green. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(worktree): address codex adversarial findings — fail-closed default branch + CRLF map Two fixes from codex adversarial review of PR 3718: 1. **Default branch resolution (data-loss risk)**: `reapOrphanWorktrees` now uses `refs/remotes/origin/<branch>` exclusively when a remote is configured. If `origin/HEAD` is absent but a remote exists, we bail out (fail-closed) rather than falling back to a local `main`/`master` that may not be the real integration branch. The `main`/`master` fallback is only used when there is provably no remote (local-only test fixtures). 2. **CRLF normalization in canonical-path mapper**: The `worktree list --porcelain` output was split on '\n\n' without normalizing CRLF first. On Windows, git emits CRLF, which caused block-splitting to fail and left the canonicalToListed map only partially populated, weakening the symlink/path-mismatch fix introduced earlier. 3. **Windows 8.3 short-path fix (test helper)**: Both `beforeEach` blocks now call `resolvedTmpDir()` which pre-resolves `os.tmpdir()` via `fs.realpathSync.native` so temp paths avoid RUNNER~1-style short names that git stores in long form, causing worktreeMeta path comparisons to fail on Windows CI. All 11 real-fs + 16 unit tests green locally. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(worktree): adversarial findings + macOS CI path-mismatch fix ## Root cause (macOS CI fail) `reapOrphanWorktrees` stored `worktreePath` (gitdir-derived, real path via git's symlink resolution, e.g. `/private/var/folders/…`) in results, while the test's `wtDir` used the unresolved symlink form (`/var/folders/…`). After reaping, `canonicalPath(wtDir)` can no longer call `realpathSync.native` (directory gone), so it falls back to `path.resolve` — which returns the symlink form — causing the `result.find()` comparison to miss. ## Fixes applied ### Source — worktree-safety.cjs 1. **Finding 1 (fail-closed PID check)**: Non-parseable lock content (e.g. `"Locked by claude-code agent-xxx"`) is now treated as ALIVE with reason `lock_owner_unknown`, not as dead. Previously it fell through as dead. 2. **Finding 1b (EPERM safe)**: `isPidAlive` call wrapped in try/catch; any thrown error (EPERM = process exists but cross-user on Windows) → ALIVE. 3. **Finding 2 (startup warning)**: `cmdWorktreeReapOrphans` now writes a one-line stderr warning when ≥1 entry is skipped or when reaper throws, while keeping exit-zero so workflows don't break. 4. **Finding 3 (default-branch discovery)**: Local-only fallback now tries `init.defaultBranch` config and HEAD symref before `main`/`master`, so repos configured with `trunk`, `dev`, etc. get correct orphan detection. 5. **macOS path fix**: Result entry for reaped worktrees now uses `gitKnownPath` (from `git worktree list`) instead of `worktreePath` (from gitdir file), ensuring the caller always sees the path git uses for the worktree. ### Test — bug-3707-locked-worktree-cleanup.test.cjs 6. **macOS CI fix**: Pre-compute `wtDirCanonical = canonicalPath(wtDir)` before calling `reapOrphanWorktrees` so the comparison works after removal. 7. **Gap 1**: New test — Claude Code lock format (`"Locked by claude-code …"`) must not be reaped; asserts `status=skipped, reason=lock_owner_unknown`. 8. **Gap 2**: New test — `isPidAlive` throwing EPERM → must not reap. 9. **Gap 3**: New test — repo with `init.defaultBranch=trunk`; merged worktree must be reaped (verifies trunk is discovered as the integration branch). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(test): raise waitForStoppedAt timeout 2 s → 5 s for Windows/Node22 CI load Subprocess write latency exceeds 2 s on loaded windows-latest/Node22 runners (test duration was 6181 ms); 5 s gives sufficient headroom without changing any production behaviour. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
ab24d80b68 |
fix(state): acquireStateLock throws on non-EEXIST openSync errors (#3773)
* fix(state): acquireStateLock throws on non-EEXIST openSync errors Closes #3772 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * chore(changeset): update pr reference to #3773 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(state): retry on EPERM/EBUSY in acquireStateLock and withPlanningLock Resolves state update TOCTOU failure on macos-22 and config-set concurrency failure on windows-24. Root cause: openSync(O_CREAT|O_EXCL) can return EPERM or EBUSY transiently on some CI OS+AV combinations when the lock file is briefly held open by the deleting process; the new throw-on-non-EEXIST guard from #3772 propagated these transient errors, killing child processes and causing lost updates in the concurrency tests. Fix: guard EPERM/EBUSY with an explicit continue before the throw-on-non-EEXIST line in both acquireStateLock and withPlanningLock; the C1 source-audit test still passes because the throw pattern is preserved for all other non-EEXIST codes. Refs #3772 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
e5461ad4e2 |
refactor(tests): consolidate Dispatch Pipeline Module — 7 files → 1 (#3733)
Consolidates 7 sibling test files for sdk/src/query/query-dispatch.ts and its stage handlers into a single query-dispatch.test.ts with 8 describe blocks (699 LOC, 53 tests). Deletes 3 clean shim sources with zero non-test importers. Leaves query-dispatch-formatting.ts and query-dispatch-error-mapper.ts in place (non-test importer: query-fallback-executor.ts — deferred to #3732). - query-dispatch-input-validation.ts deleted (clean shim) - query-dispatch-plan.ts deleted (clean shim) - query-dispatch-result-builder.ts deleted (clean shim) - 6 per-stage test files deleted (consolidated into query-dispatch.test.ts) - counter-tests added per Contract 6 for each stage field - CONTEXT.md Glossary updated with Dispatch Pipeline Module entry Closes #3731 Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
473c279c23 |
fix(state): acquireStateLock must not unlink live locks on retry exhaustion (#3714) (#3717)
PR #3711 fixed a Windows phantom-lock bug by making the last-retry path unconditionally unlink the existing lock and re-acquire. That closed one hole but opened another: a slow-but-live writer (CI under c8 coverage instrumentation easily exceeds the old 2.25 s budget) would have its lock nuked by a second writer, both would read the same starting STATE.md, and the second write would clobber the first append. Replace the bounded retry loop with a deadline-driven loop that only unlinks a lock we did not place when its mtime exceeds a 10 s staleness threshold (crashed holder), with a 30 s wait ceiling above that threshold so a genuinely stuck holder still gets recovered. Locking-bugs regression suite: 10/10 pass, including 7 out of 8 stress runs (1 transient OS-load failure; not reproducible on re-run). Fixes #3714 Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
9c4895f409 |
chore(ci): bump actions/upload-artifact v4.6.2 → v7.0.1 (#3715) (#3716)
v4.6.2 ships a node20 runtime; GitHub Actions is deprecating Node 20 (removed from runners 2026-09-16). v7.0.1 ships node24 and is the last Node-20 reference in the repo. Pinned by full commit SHA per repo convention. Verified v5/v6/v7 breaking changes do not apply: artifact names are unique per workflow run and neither step uses include-hidden-files. Closes #3715 Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
6a5fa59129 |
feat(3081): auto-trim review prompts for small-context model reviewers (#3708)
* feat(3081): auto-trim review prompts for small-context model reviewers Adds review.max_prompt_tokens and review.max_prompt_tokens_per_reviewer config keys. When configured, the /gsd-review workflow deterministically trims the assembled prompt before sending to each reviewer (drop CONTEXT → RESEARCH → REQUIREMENTS; head-shrink PROJECT.md; tail-truncate PLANs proportionally; reserve disclosure-note tokens upfront). Trim metadata is recorded in REVIEWS.md frontmatter. Reviewer is skipped with a warning if even the minimum review set exceeds the budget. Closes #3081 * fix(3081): register prompt-budget in SDK query registry and update inventory manifest review.md references `gsd-sdk query prompt-budget` at three call sites, but the command had no handler in the SDK registry — failing the registry-integration drift-guard test on all 6 CI matrix legs. Added a native TypeScript SDK handler (sdk/src/query/prompt-budget.ts) that ports the applyBudget logic from the CJS module, registered it in DOMAIN_STATIC_CATALOG, and regenerated docs/INVENTORY-MANIFEST.json to include the new cli_modules/prompt-budget.cjs entry. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(3081): bump ws to 8.20.1 and allowlist prompt-budget sibling pair Two additional CI failures after the registry fix: 1. ws moderate CVE (GHSA-58qx-3vcg-4xpx, uninitialized memory disclosure): The advisory covers ws >=8.0.0 <8.20.1. Both root and sdk/package.json pinned ^8.20.0 which resolved to 8.20.0. Bumped both to 8.20.1 to clear the npm audit drift-guard test (bug-3588-npm-audit-clean.test.cjs). 2. lint-shared-module-handsync detected the new prompt-budget.ts / prompt-budget.cjs sibling pair without an allowlist entry. Added a cooperatingSiblings entry to scripts/shared-module-handsync-allowlist.json with classification and justification matching the established pattern. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(3081): align prompt-budget skip semantics across CJS and SDK dispatch paths Replace brittle `[ $EXIT -eq 2 ]` guards with `[ $EXIT -ne 0 ]` in all three local-reviewer blocks (Ollama, LM Studio, llama.cpp) in workflows/review.md. Any non-zero exit from prompt-budget now triggers a skip with a descriptive warning — exit 2/11 prints "budget too small", any other non-zero prints "unexpected exit code". This ensures the SDK bridge dispatch path (exit 11 via GSDError(Blocked)) triggers the same skip as the CJS path (exit 2). The SDK handler (sdk/src/query/prompt-budget.ts) already writes both metadata and prompt files before throwing, so no change needed there. The Ollama block also gains the missing OLLAMA_SKIP guard so the reviewer invocation is actually skipped (previously the block only suppressed the OLLAMA_PROMPT_FILE update but still ran the curl invocation). SDK integration path (hardFailed via GSDError(Blocked) → exit 11) is covered by handler unit tests in tests/prompt-budget.test.cjs; no gsd-sdk-*.test.cjs exercising the full bridge dispatch for this command exists yet — that gap remains and is documented here. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix prompt-budget trim ordering and review guard follow-ups * perf: optimize prompt-budget and dedup reviewer trim workflow * fix(3708): drop source-grep theater tests to satisfy lint-no-source-grep All four test files added in commit 2df566ed were pure source-grep theater: they read .cjs / .ts / .md source files and asserted that specific string literals were present or absent. None exercised runtime behaviour. Deleted: - tests/gsd-tools-memory-optimizer.test.cjs — 7 includes() on gsd-tools.cjs - tests/prompt-budget-hotpath-optimizer.test.cjs — includes() on prompt-budget.cjs + .ts - tests/prompt-budget-io-optimizer.test.cjs — includes() on prompt-budget.ts + gsd-tools.cjs - tests/review-workflow-budget-dedup.test.cjs — includes() on review.md Behavioural coverage for the prompt-budget feature already exists in tests/prompt-budget.test.cjs and tests/prompt-budget-cli.test.cjs (also added by this PR). No replacement tests needed. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(3708): correct budget-pressure threshold and minSet accounting Two bugs in applyBudget caused premature trimming and false hard-fails: 1. UNNEEDED_TRIM: budgetUnderPressure compared baseTokens against effectiveBudget - NOTE_RESERVE_TOKENS, triggering trim pressure 80 tokens before the budget was actually exceeded. Fix: compare against effectiveBudget directly; NOTE_RESERVE_TOKENS are still reserved in contentBudget once real pressure is confirmed. 2. FALSE_HARDFAIL: minSet included NOTE_RESERVE_TOKENS unconditionally, treating the note as mandatory even when no trim would occur and no note would be injected. Fix: exclude NOTE_RESERVE_TOKENS from minSet; a prompt that fits untrimmed needs no note and must not hard-fail. Both fixes applied in CJS and TypeScript implementations. Two regression tests added (cycles 11 and 12) that reproduce each case behaviorally. --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
e15348ea43 |
docs(context): add RULESET.TESTS.boundary-coverage + LEARNING.prompt-budget.boundary-gap
Adds four predicates derived from PR #3708 post-mortem: - RULESET.TESTS.boundary-coverage — must test {limit-1, limit, limit+1} and near-reserve-distance inputs - RULESET.TESTS.boundary-coverage.fixtures — required fixtures for budget/limit/quota/threshold code - RULESET.TESTS.boundary-coverage.anti-pattern — names the trivially-large + trivially-small pairing as the failure mode - LEARNING.prompt-budget.boundary-gap — full post-mortem of the UNNEEDED_TRIM + FALSE_HARDFAIL regressions |
||
|
|
77b6bb62db | fix(state): acquireStateLock last-retry now re-acquires before proceeding (#3711) | ||
|
|
3c2c56b9e8 |
fix(ci): skip install + slow lanes on Windows in main test matrix (#3710)
* fix(ci): skip install + slow lanes on Windows in main test matrix Gates the `Run install tests` and `Run slow tests` steps in `.github/workflows/test.yml` to `matrix.os != 'windows-latest'`. The install lane performs `npm install -g <tarball>` 7× per invocation of release-tarball-smoke.install.test.cjs (1× in the shared before() hook + 1× per of the 6 test cases). On windows-latest each install costs 60–90 s (NTFS + Defender) so the lane alone consumes ~8–9 min on top of the ~7 min already spent on npm ci + build:sdk + unit + integration + security — overflowing the 15-min `timeout-minutes` cap and cancelling the job mid-install. The dedicated install-smoke.yml workflow already excludes Windows from its matrix (ubuntu + macOS only); the weekly windows-compat workflow provides Windows-specific regression coverage. Linux + macOS install and slow lanes remain on main push for parity. Refs #3709 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(deps): bump ws 8.20.0 → 8.20.1 to clear GHSA-58qx-3vcg-4xpx The bug-3588 `npm audit --omit=dev reports zero advisories` test (tests/bug-3588-npm-audit-clean.test.cjs) is failing on main after a new advisory dropped against ws@8.20.0: GHSA-58qx-3vcg-4xpx — Uninitialized memory disclosure ws: range >=8.0.0 <8.20.1 (CVSS 4.4, moderate, CWE-908) Fix: `npm audit fix --omit=dev` at both root and sdk/. Lockfile-only bump to ws@8.20.1; package.json untouched (ws is transitive). `npm audit --omit=dev` reports `found 0 vulnerabilities` in both workspaces after the bump. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
f1a61620bc |
fix(ci): bump coverage heap budget + Windows npm timeout in runNpm helper (#3704)
Set NODE_OPTIONS=--max-old-space-size=6144 on the Unit coverage step so the c8 report phase has 6 GB instead of Node's default ~4 GB heap; the Linux Node 24 runner has 7 GB available so this leaves 1 GB headroom. Raise the runNpm default timeout from 55 000 ms to 180 000 ms so cold-cache Windows npm install -g runs (which take 60-90 s on NTFS + Defender) complete before the child process is killed. Refs #3703 Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
463eb26544 |
Match gsd-sdk query commit in graphify auto-update hook (#3653) (#3658)
* Match `gsd-sdk query commit` in graphify auto-update hook (#3653) The PostToolUse Bash hook only substring-matched direct shell git ops in tool_input.command. `gsd-sdk query commit` invokes git via spawnSync, so the literal "git commit" never appears in the Bash tool's command string and the hook silently skipped every SDK-issued commit. Result: .planning/graphs/ drifted stale after every phase that closed via gsd-sdk query commit, with no error and no log. Gate 2 now also matches `gsd-sdk query commit`. Other SDK verbs (phase.complete, roadmap.update-plan-progress, state.begin-phase) do not invoke git themselves and remain non-matching to avoid spurious rebuilds per state mutation. Adds positive + negative matcher tests. * Fix changeset frontmatter for #3658 `type: Bug Fix` rejected by scripts/changeset/parse.cjs ALLOWED_TYPES (Keep a Changelog values: Added/Changed/Deprecated/Removed/Fixed/Security). Switch to `type: Fixed` and add `pr: 3658` required by MISSING_PR check. docs-lint now reports `ok_no_triggering_fragments` locally. * fix(#3658): bound graphify SDK commit matcher * fix(#3658): exempt release note docs lint |
||
|
|
ef951098a6 |
chore(3686): add release-tarball lifecycle smoke to install-smoke workflow (#3692)
* chore(3686): add release-tarball lifecycle smoke to install-smoke workflow Closes #3686. Adds a non-interactive lifecycle smoke that runs against the installed tarball (not the working tree). Catches the two recent release-time bug classes that the working-tree test suite cannot see: * #3684 — symbol mismatches between init.cjs imports and secrets.cjs exports that landed in v1.42.3 (closed/fixed-pending-release). * #3668 — bare `gsd-sdk` invocations in 75 of 78 workflow files with no `command -v gsd-sdk … elif node "$GSD_TOOLS"` fallback (open). Shape: * `scripts/release-tarball-smoke.cjs` — pure CJS module exporting a frozen `SMOKE` enum and a `runSmoke({ tarballPath, installPrefix, expectedVersion, fixtureDir, lifecycleCommands })` function. CLI `--json` mode prints `JSON.stringify(result)` and exits 0 iff `result.code === SMOKE.OK`. Install is `--prefix <tmpdir>` so it does not pollute global node_modules. * `tests/release-tarball-smoke.test.cjs` — 6 tests covering happy path, version mismatch, lifecycle command file resolution, missing-command detection, sdk binary callability, and structural workflow-body checks. Tests assert on the SMOKE enum directly; no `assert.match` on rendered prose, no try/finally in test bodies, no source-grep theater. Uses `before`/`after` to pack+install once across the test file. * `tests/release-tarball-smoke-workflow.test.cjs` — 7 structural assertions on the parsed install-smoke.yml IR (workflow_call trigger preserved, lifecycle step calls release-tarball-smoke.cjs with --json, jq check enforces result.code === "ok", path filter includes the new files, artifact-on-failure step present). * `.github/workflows/install-smoke.yml` — extended (not duplicated). New "Lifecycle smoke" step after the existing version check, on the same matrix. Artifact upload on failure for debugging. Path filter now triggers on changes to the new script + test. Per CONTRIBUTING.md §"Prohibited: Raw Text Matching on Test Outputs" this PR avoids the same anti-pattern that caused PR #3666 to be reverted (PR #3688) — the script returns frozen enum codes, tests assert on the enum, never on stdout strings. Workflow-body checks in Cycle 3 are INFORMATIONAL (count returned, not enforced) on this PR. After #3668's fix lands and the 75 missing fallbacks are added, the lane can be tightened to enforce zero. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(3686): harden release smoke workflow and query scanner * fix(3686): move tarball-smoke test to install suite to fix Windows ETIMEDOUT + coverage OOM Windows (Node 22/24/26, jobs 76565215694/76565215710/76565215812): release-tarball-smoke.test.cjs had no suite marker so run-tests.cjs classified it as 'unit', running it on Windows PR CI. The before() hook calls execFileSync(npm.cmd install -g ...) with a 55 s timeout; on Windows GHA runners this npm global install consistently hits ETIMEDOUT (~62 s observed), causing all 3 Windows lanes to fail. Coverage (job 76565215085): c8 ran test:coverage:unit (unit suite only) with V8 coverage tracking active across child processes. The tarball-smoke test's before() hook spawned npm install subprocesses while c8 held V8 coverage descriptors open, driving the Node heap to 4 GB+ and triggering an OOM abort during report generation (exit code 134, all 5682 tests had already passed). Fix: rename to tests/release-tarball-smoke.install.test.cjs so run-tests.cjs routes it to the 'install' suite. The install suite is already skipped on PR CI by design (test.yml lines 179-181: only runs on main push). The dedicated install-smoke.yml workflow continues to exercise this test on its own matrix. Also update the install-smoke.yml PR path filter and the structural wiring test assertion to match the new filename. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * refactor(test): route tarball-smoke install test through tests/helpers.cjs Replaces direct fs.mkdtempSync and execFileSync calls in tests/release-tarball-smoke.install.test.cjs with createTempDir() and a new runNpm() helper in tests/helpers.cjs. Cleanup is now automatic via the helper. Addresses CodeRabbit Major refactor at https://github.com/gsd-build/get-shit-done/pull/3692#discussion_r3260433892. --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
b01089c05a |
fix(#3689): use find instead of chained ls for continue-here scan (#3693)
* fix(#3689): use find instead of chained ls for continue-here scan The check_incomplete_work step in resume-project.md chained six bare-glob ls arguments to discover .planning/.continue-here*.md handoff files. Under zsh's default NOMATCH option (macOS default shell), the first non-matching glob aborts the entire command during word-expansion, silently dropping every pattern after it — including .planning/.continue-here*.md, which holds the canonical pause checkpoint for default-context handoffs. The 2>/dev/null || true guard suppresses ls's own stderr / exit code but has no effect on the shell's pre-exec glob-abort. Replace the chain with two find invocations: find .planning -maxdepth 3 -name '.continue-here*.md' -print 2>/dev/null find . -maxdepth 1 -name '.continue-here*.md' -print 2>/dev/null find does not use shell glob expansion and tolerates absent directories on both bash and zsh. Adds tests/bug-3689-resume-glob-nomatch.test.cjs covering: - zsh -o nomatch with .planning/.continue-here-AT-1234.md present and no spike/sketch/deliberation subdirs (the regression scenario) - bash default, same layout - zsh -o nomatch with no checkpoints anywhere (clean exit, no output) - text invariant: resume-project.md no longer carries the chained-ls pattern and does carry the find-based scan Closes #3689 * fix(#3689): guard find commands with || true for Windows safety Restore the trailing '|| true' guard that the original chained ls had, per the windows-robustness.test.cjs invariant (informational bash commands in critical workflows must not let an exit-1 from find on exotic platforms tank the resume-project.md flow). * fix(#3689): address CodeRabbit review feedback - Set changeset frontmatter pr: 3693 (was 3690 placeholder); the release- note link now points at the right PR. - Use createTempDir / cleanup from tests/helpers.cjs instead of local tmpdir/cleanup wrappers, per repo test standards. * fix(#3689): update bug-3446 discovery contract to new find-based scan bug-3446 enforced three text invariants on the chained-ls implementation that this PR replaces with find. Update the assertions to verify the same three discovery paths are still covered: - .planning/.continue-here*.md at depth 1 -> find .planning -maxdepth 3 - .planning/sketches/SKETCH-NNN/.continue-here*.md at depth 3 -> same find with -maxdepth >= 3 (assertion now reads the actual depth and enforces a lower bound so future changes can deepen but not shallow it) - repo-root .continue-here*.md legacy fallback -> find . -maxdepth 1 The discovery contract is preserved; only the implementation under inspection changes. * test(#3689): convert bug-3446 from source-grep to behavioral assertions CodeRabbit flagged the text-regex assertions on the workflow source as a violation of the no-source-grep testing standard. Replace them with a behavioral integration test that: 1. Extracts the actual check_incomplete_work bash block from resume-project.md (so the test stays in sync with whatever the workflow does, no string match required). 2. Plants three handoff files in a temp dir covering the three discovery surfaces bug #3446 originally filed: - .planning/.continue-here.md (depth 1 under .planning) - .planning/sketches/SKETCH-001/.continue-here.md (depth 3) - ./.continue-here.md (legacy repo root) 3. Runs the snippet under bash and asserts each planted file appears in stdout. Same contract; now validated through runtime behavior instead of regex-on-file. * fix(#3689): use \\r?\\n in bash-fence regex for Windows CRLF parity The Windows test-parity guard at tests/windows-test-parity-guard.test.cjs:106 flags any new fence-extraction regex that uses a literal \\n after the language tag — on Windows CRLF the byte after `bash` is \\r, the regex silently fails to match, and the extracted snippet is empty. Switch to the canonical /```(?:bash|sh)\\r?\\n([\\s\\S]*?)```/ pattern. * fix(#3689): harden extractCheckBlock against missing </step> and CRLF closing fence Per CodeRabbit review: assert that the closing </step> tag is present before slicing (otherwise slice(stepStart, -1) silently grabs the wrong block and the test fails misleadingly), and add \r?\n to the closing fence as well so Windows CRLF doesn't sneak a stray carriage return into the captured snippet. |
||
|
|
1645bb5ffd |
fix(ci): replace minimatch with path.matchesGlob in pr-template-policy (#3701)
Root cause:
|
||
|
|
e50ad8127f |
fix(3696): pr-template enforcer recognises CI/tooling carve-out (#3697)
* fix(3696): pr-template enforcer recognises CI/tooling carve-out The enforcer in scripts/pr-template-policy.cjs only validated against three typed templates and three hard-coded DEFAULT_TEMPLATE_MARKERS. It had zero awareness of the documented CI/tooling/dep/doc-only exception in .github/pull_request_template.md, meaning external contributors who followed the documented escape hatch still had their PRs auto-closed. Fix: 1. Path-scope auto-skip: if every changed file matches a tooling glob allowlist (.github/**, scripts/**, docs/**, *.md, .changeset/**, dependency manifests), skip enforcement and exit success with no comment posted. 2. Explicit exemption marker: if the PR body contains <!-- pr-template-exempt: <non-empty reason> -->, skip enforcement. 3. Workflow updated to fetch changed file paths via gh and pass them as CHANGED_FILES env to the policy script. 4. Pull request template updated to document both mechanisms and retire the old "delete this file" prose carve-out. 5. 14 new tests (TDD red→green); all 25 tests pass. Closes #3696 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(3696): allow hyphenated pr-template exemption reasons --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
ae4c31426a |
ci: drop Node 26 from test matrix (#3695)
Node 26 lanes have been causing recurring instability and CI churn.
Drop the Node 26 entry from the strategy matrix in .github/workflows/test.yml
until the Node 26 release stabilises. Also remove the companion
continue-on-error guard and its explanatory comment block, which
existed solely to prevent Node 26 failures from gating the workflow.
Files changed:
.github/workflows/test.yml — removed node-version: 26 from matrix,
removed continue-on-error: ${{ matrix.node-version == 26 }} and its
two-line comment block
Preserved: node-version: [22, 24] still run on all three OS lanes
(ubuntu-latest, macos-latest, windows-latest).
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
|
||
|
|
cb154569cf |
revert: fix(surface) #3666 — CONTRIBUTING.md test-pattern violations (#3688)
Reverts
|
||
|
|
c2dc9e2532 |
Fix W002 false positives on archived phase references in STATE.md body (#3655)
* W002 health check: cross-reference milestones archive for STATE.md phase refs
After /gsd-complete-milestone, phase dirs move into milestones/vX.Y-phases/
and their `#### Phase N:` headings in ROADMAP.md are collapsed inside
<details> blocks. The ROADMAP heading scan misses them, so W002 fired for
every archived phase number mentioned in STATE.md's historical narrative body
("Recent", "Decisions", "Deferred Items") — leaving every project that ever
ran /gsd-complete-milestone permanently degraded with proportional W002 noise.
Union archived milestone phase directories into the validPhases set used by
the W002 check, mirroring the same milestone-archive lookup that W006
already uses (sdk/src/query/validate.ts:723-736).
Closes #3652
* Address review: use shared regex constants and mirror W002 archive fix into CJS path
CodeRabbit (PR #3655 review 1): the ad-hoc /^(\d+[A-Z]?(?:\.\d+)*)/i used to
extract phase tokens from archived phase dirs would skip project-code-prefixed
names like `CK-64-...`. Switch the new SDK block to the shared
PHASE_TOKEN_FROM_DIR_RE / MILESTONE_ARCHIVE_DIR_RE constants (defined at
sdk/src/query/validate.ts:32-33) so prefixed archives are recognised. Added a
companion regression test using `CK-`-prefixed dirs.
Codex review: the shipped CJS health command path (get-shit-done/bin/lib/verify.cjs
cmdValidateHealth, routed by validate-command-router.cjs) only unions
collectDiskPhases (active archive only) plus ROADMAP heading scan — same bug as
the SDK had. Port the all-archive scan into the CJS path via listMilestoneArchiveDirs
+ PHASE_TOKEN_FROM_DIR_RE (already declared at verify.cjs:401-402). Added a CJS
regression test covering the multi-sub-milestone (v1.3a + v1.3b) scenario from
the issue report.
Adds .changeset/lucky-lynx-wave.md.
* Address Gemini findings: shared regex + helper reuse + cross-platform path
P1 #2 — refactor the new W002 archive-scan block to reuse the existing
listMilestoneArchiveDirs helper (sdk/src/query/validate.ts:40) instead of
re-implementing readdir + filter inline. Eliminates duplication and prevents
the two call sites from drifting apart.
P2 #4 — listMilestoneArchiveDirs sorted by `a.slice(a.lastIndexOf('/') + 1)`,
which returns the full path on Windows where path.join produces backslashes.
Switch to path.basename(a) so the numeric version sort works cross-platform.
Brings the SDK helper in line with the CJS sibling at
get-shit-done/bin/lib/verify.cjs:411 which already uses path.basename.
P1 #1 / P2 #5 — the pre-existing W006/W007 archive + active phase scans
(Check 8) used an ad-hoc `^(\d+[A-Z]?(?:\.\d+)*)` regex that silently skipped
project-code-prefixed phase dirs like `CK-64-foo`, so W006 fired for a
correctly-archived phase and W007 fired for a correctly-on-disk phase. Switch
both scans to the shared PHASE_TOKEN_FROM_DIR_RE constant declared at
sdk/src/query/validate.ts:32. The W006 archive loop also now reuses
listMilestoneArchiveDirs for consistency.
P2 #6 — strengthen the CK-prefix regression test to also assert no W006
fires for `#### Phase 64: Prior shipped` (placed inside <details> so the
heading scan picks it up while the on-disk scan does not), pinning the
shared-regex behaviour in the W006 path.
* CI: switch retired /gsd-<cmd> comment syntax to canonical /gsd:<cmd>
The bug-2543 slash-namespace invariant lint scans get-shit-done/bin/lib/**
for /gsd-<cmd> patterns and fails CI when one slips into a comment. Use the
canonical /gsd:complete-milestone form in the new verify.cjs comment (and
mirror the change in the SDK + tests + changeset entry so all docstrings
referencing the milestone-completion command share one spelling).
Also: extract a small forEachArchivedPhaseToken helper in validate.ts (Gemini
P3 finding from review pass 2) so Check 4 (W002) and Check 8 (W006) share the
archive-walking loop instead of inlining it twice.
* Address rev3 review: shared regex parity, numeric sort, drop dead try/catch
Gemini P1 — Check 4's flat phases/ scan still used the ad-hoc
/^(\d+[A-Z]?(?:\.\d+)*)/ regex while the archive scan used the shared
PHASE_TOKEN_FROM_DIR_RE. Project-code-prefixed dirs (e.g. CK-65-current) on
the flat layout would have slipped past validity, so the W002 check could
still mis-classify them. Use PHASE_TOKEN_FROM_DIR_RE here too.
Gemini P3 — `[...validPhases].sort()` ordered tokens alphabetically, producing
error messages like "phases 1, 10, 19, 2, 20" instead of "1, 2, 10, 19, 20".
Switch both SDK and CJS to numeric localeCompare so the displayed list is
human-readable. Mirrored in both paths.
Grok P3 — the CJS Check 4 archive block wrapped listMilestoneArchiveDirs in
an outer try/catch even though the helper already swallows ENOENT/EACCES into
[]. The outer catch was unreachable. Removed; only the per-archive readdir
needs a catch.
Grok P2 / Gemini P1 (CJS Check 8 archive scan) — the assertion that CJS
Check 8 needs the same archive union as Check 4 was repeatedly raised across
review passes. It is incorrect: Check 8 filters ROADMAP.md through
extractCurrentMilestone() before scanning headings, which strips shipped
milestones (collapsed in <details> or not) so archived phase numbers never
reach `roadmapPhases`. Added an inline note documenting this and a positive
regression assertion in the CJS test that W006 does NOT fire for the
archived phases in the multi-sub-milestone fixture. (Skipped a parallel
W007 assertion because the active-archive fallback in
getActiveMilestoneArchiveDir is pre-existing behavior unrelated to #3652.)
* Port forEachArchivedPhaseToken helper to verify.cjs for SDK parity
Gemini rev5 P2 — the CJS Check 4 inlined the archive-walking loop while
the SDK already factored it into forEachArchivedPhaseToken(). Add a
mirror helper in verify.cjs so both seams use the same primitive,
matching the cooperating-sibling pattern documented in
scripts/shared-module-handsync-allowlist.json.
* ci: retrigger to clear unrelated TOCTOU flake
The previous CI run failed at the pre-existing #1925 concurrency test
(state add-blocker concurrent calls) on macos-24 and ubuntu-22 but
passed on ubuntu-24 — and the same test passed on the prior CI run of
this branch (commit 8a246916). The state add-blocker code path is
completely independent of the W002 archive-union changes in this PR.
|
||
|
|
c5657fcbfd |
fix(surface): default missing optional fields in readSurface, normalize writeSurface input (#3666)
* surface: default missing optional fields in readSurface, normalize writeSurface input readSurface used to reject any .gsd-surface.json missing one of its four fields and return null with no diagnostic, so the active surface silently degraded to the 'full' profile. Optional array fields (disabledClusters, explicitAdds, explicitRemoves) now default to [] when missing or wrong-typed; hard failures (malformed JSON, non-object root, missing/non-string baseProfile) still return null but emit a console.warn naming the file + reason. writeSurface now normalizes its input to the full SurfaceState shape and throws on missing baseProfile, so partial writes can no longer land on disk and trip readSurface later. Tests extended to cover the new lenient and warn-on-hard-fail behavior plus the writer guard. Fixes #3662 * surface: reject whitespace-only baseProfile + migrate new tests to helpers.cjs Applies CodeRabbit findings on PR #3666: 1. readSurface and writeSurface now reject baseProfile values that are blank after trim() (e.g. " "), not only the empty string. Whitespace-only strings would split-by-comma to [''] downstream and silently produce an unresolvable profile mode. Both guards updated symmetrically; warn/error messages reworded to "missing, non-string, or blank". 2. tests/surface-state.test.cjs now uses createTempDir + cleanup from tests/helpers.cjs instead of local mkdtempSync + fs.rmSync, aligning with the repo coding guideline for root-level tests. The local tmpDir() helper delegates to createTempDir for backward-compat with the existing test bodies. Per-test cleanup calls swapped to cleanup(dir). 3. Added regression tests: - readSurface rejects whitespace-only baseProfile and warns - writeSurface rejects whitespace-only baseProfile, non-string baseProfile, and null surfaceState Refs #3662 * surface: warn on unknown baseProfile mode names in read and write Applies a Codex review finding on PR #3666: readSurface and writeSurface used to accept any non-blank string as baseProfile. A typo like {"baseProfile":"standrad"} would pass validation, then resolveProfile() in install-profiles.cjs would silently fall back to 'full' with no diagnostic — the same silent-degradation symptom that #3662 was filed to fix, just through a different code path. Both functions now split baseProfile by comma, validate each mode against the registered PROFILES set ('core', 'standard', 'full'), and emit a single [gsd] console.warn line that names the unknown modes and lists the valid ones. The state is still parsed/written — resolveProfile() decides the actual resolution fallback. Composed profiles where some modes are valid and some are not warn only about the unknown subset. Side note: the pre-existing 'round-trips composed base profile' test used 'core,audit' as a stand-in composed string. 'audit' is not a registered profile (the three known profiles are 'core', 'standard', 'full'), so the test was relying on the old lack of validation. Switched to 'core,standard' to preserve the round-trip intent without producing diagnostic noise. Refs #3662 * changeset: include blank/typo baseProfile in documented read failure cases CodeRabbit minor finding on PR #3666 — the changeset wording only mentioned "missing/non-string baseProfile" but the implementation also rejects blank (including whitespace-only) baseProfile values, and warns on typo'd / unknown profile mode names. Updated to match actual behavior. * changeset: pr field should be PR number, not issue number Codex review finding on PR #3666. The changeset's `pr: 3662` was the linked issue (#3662), but the convention across other .changeset/*.md files is that `pr:` carries the PR number. Verified by spot-checking other changesets (2937 → pr: 3515, 3298 → pr: 3306, 3541 → pr: 3547 — all PR numbers). Updated to `pr: 3666`. The body text still references the issue. * surface: reject comma-only baseProfile + warn on wrong-typed optional fields Three Gemini review findings on PR #3666 — all spirit-of-#3662 edge cases: 1. Comma-only baseProfile bypasses validation. readSurface used to accept baseProfile: ", ," because trim() returned "," (non-empty). Downstream resolveProfile() would split-and-filter to [] and silently fall back to 'full'. Added effectiveProfileModes() helper that splits, trims, filters empty — both readSurface and writeSurface now reject when the result is empty. Same silent-degradation symptom as the original bug. 2. Wrong-typed optional fields silently coerced. readSurface used to coerce {disabledClusters: 42} to {disabledClusters: []} with no diagnostic. Now warns via mistypedOptionalFields() before normalizeSurfaceState() does the coercion, in both reader and writer. 3. Missing test coverage for the EACCES branch in readSurface. Added a chmod-000 unreadable-file test, skipped on Windows and root accounts (mode bits are ignored on those platforms). 48/48 surface tests pass. Final codex pass returned LGTM on the prior state; these three additions strengthen the same lenient/loud contract. Refs #3662 |
||
|
|
7e6ba56985 |
fix(3678): executor must respect commit_docs:false; teach SDK skip envelope (#3679)
* fix(3678): executor must respect commit_docs:false; teach SDK skip envelope Closes #3678 When `commit_docs: false` in `.planning/config.json`, the SDK's `cmdCommit` correctly short-circuits and returns `{committed: false, hash: null, reason: 'skipped_commit_docs_false'}` without staging or committing anything. The agent prompt at `agents/gsd-executor.md:710-720` (final_commit block) tells the executor to call `gsd-sdk query commit "docs(...)" --files .planning/...` but says NOTHING about how to interpret a skipped return. With no explicit instruction, the LLM improvises raw `git add` / `git add -f` / `git commit` to "fulfill" the per-plan commit step it was told to make, which leaks gitignored `.planning/` artifacts into the user's git history (exactly what the reporter observed). Three coordinated fixes: 1. **agents/gsd-executor.md final_commit block** — adds explicit handling text for all three SDK return envelopes (`committed:true`, `skipped:true commit_docs`, `skipped:true gitignored`, `committed:false other reasons`). States plainly: "Do not fall back to raw `git add` / `git commit` / `git add -f` when the SDK returns `skipped: true`." 2. **get-shit-done/bin/lib/commands.cjs cmdCommit** — adds `skipped: true` to both skip-path envelopes so agents see "skipped" as a first-class success signal rather than inferring "no commit happened, I must improvise" from absent `hash` / `committed:false`. Backward-compatible: existing callers reading `committed` / `hash` / `reason` are unaffected. 3. **tests/bug-3678-executor-commit-docs-respect.test.cjs** — 7-test regression covering: - A1/A2: agent prompt mentions the skip envelope AND explicitly forbids raw-git fallback (`source-text-is-the-product` exception) - B1: SDK envelope carries `committed:false`, `skipped:true`, canonical `reason: 'skipped_commit_docs_false'` (frozen enum) - B2: git index empty after commit_docs:false skip (no `.planning/` staged) - B3: HEAD unchanged after commit_docs:false skip - C1/C2: structural ban on `git add -f` / `git add --force` in any agent or workflow body (prohibition-sentence exception preserves audit prose) Verification: - node --test tests/bug-3678-*: 7/7 pass - Targeted regression (10 commit/executor-adjacent files): 135/135 pass - Full docker suite (gsd-test-summary): 11751/11740 pass / 0 fail (the 11 added are this test plus a few collateral pickups) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore(changeset): add fragment for #3678 fix (Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>) * chore(changeset): set PR number 3679 (Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>) * fix(3678): preserve skip-aware carve-out in executor completion checklist The new `final_commit` prose at lines 717-741 teaches the executor to treat `skipped:true` as success and forbids raw-git fallback, but the downstream completion checklist still contained an unconditional "Final metadata commit made" checkbox. An LLM executor reading an unchecked mandatory box may attempt to satisfy it via raw `git add`, re-introducing the exact regression this PR is meant to prevent. Update the checklist line to carve out the intentional-skip case and add a regression test asserting the carve-out remains present. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
09ab16f9e5 |
fix(3683): normalize /gsd:<cmd> → /gsd-<cmd> in command, workflow, and reference bodies (#3685)
* fix(3683): normalize /gsd:<cmd> → /gsd-<cmd> in command, workflow, and reference bodies Extends #3677's agent-body normalizer to all body text staged through copyWithPathReplacement (commands, workflows, references). The initial isCommand guard was structurally redundant — normalizeAgentBodyForRuntime already self-gates on shouldNormalizeHyphenNamespaceInAgentBody(runtime), so dropping it covers all hyphen-name runtimes (Claude / Qwen / Hermes) without affecting colon-canonical runtimes (Gemini). Addresses the user-visible symptom in #3683: workflows like get-shit-done/workflows/discuss-phase.md (7 colon refs) leaked /gsd:<cmd> markers to the model context, which the model echoed at the end of /gsd-discuss-phase runs. Source-prose drift caught by the new cross-reference invariant test: - commands/gsd/plan-phase.md: removed a slash-form mention of the deleted /gsd-research-phase command (#3042) - commands/gsd/profile-user.md: replaced a slash-form artifact reference with a backticked bare name (the referenced item is a skill config, not a user-callable slash command) Tests: - tests/bug-3683-command-colon-namespace-leak.test.cjs — runtime-form regression for commands/gsd/*.md staging - tests/bug-3683-command-cross-reference-invariant.test.cjs — locks cross-reference coherence: every /gsd-X / /gsd:X reference in a command body must resolve to commands/gsd/X.md (so a future rename forces every cross-reference to update) - tests/bug-3683-workflow-colon-namespace-leak.test.cjs — runtime-form regression for workflows + references; negative test for gemini asserting the colon form is preserved Fixes #3683 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(changeset): remove undefined cycle reference --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
80d6306e99 |
fix(3677): normalize /gsd:<cmd> → /gsd-<cmd> in agent bodies for hyphen-name runtimes (#3680)
* fix(3677): normalize /gsd:<cmd> → /gsd-<cmd> in agent bodies for hyphen-name runtimes Closes #3677 The executor agent bodies installed to `~/.claude/agents/gsd-*.md` (and the Qwen / Hermes equivalents) still contained retired `/gsd:<cmd>` colon-form references in their prose. Every GSD skill / agent has registered under the canonical hyphen `name:` form since #2808, so the colon form is unroutable — Claude Code rejects it with `Unknown command: /gsd:execute-phase. Did you mean /gsd-execute-phase?`. Reporter measured ~28 agent files / ~96 leaked refs on a full Claude global install. This is the agent-body surface of the same class of bug as the two already-fixed sibling surfaces: - #3583 (SKILL.md skill bodies) — fixed via #3629 - #3584 (user-facing runtime "Next step: /gsd:…" emissions) — fixed via #3606 The agent-body surface in `bin/install.js`'s agent install loop was never covered: the Claude-default / Qwen / Hermes branches register hyphen `name:` but copy bodies verbatim (Qwen/Hermes do branding-only swaps; Claude-default falls through with no body conversion at all), so the colon refs leak. Fix: 1. Add a pure predicate `shouldNormalizeHyphenNamespaceInAgentBody(runtime)` backed by an explicit allow-list `HYPHEN_NAME_AGENT_RUNTIMES = {claude, qwen, hermes}`. Unknown / future runtimes default to false (better to leak than to mangle). 2. Add `normalizeAgentBodyForRuntime(content, runtime, cmdNames)` that conditionally applies the shared `transformContentToHyphen` from `scripts/fix-slash-commands.cjs` (same transform #3629 used for SKILL.md bodies). 3. Call `normalizeAgentBodyForRuntime(content, runtime, readGsdCommandNames())` in the agent install loop right before `fs.writeFileSync`, so it composes with all the existing runtime branches. For Gemini and self-converting runtimes the predicate short-circuits, so their convertClaudeAgentToXAgent output is not re-rewritten. 4. Export both functions from `bin/install.js` for the regression test. Regression test (`tests/bug-3677-agent-colon-namespace-leak.test.cjs`): 24 tests across 4 groups — A (exports exist), B (predicate matrix covering all 15 runtimes in the layout table + an unknown-runtime case), C (normalize helper applies/skips correctly for claude/qwen/hermes/ gemini/copilot), D (sanity check of the underlying transform). Verification: - node --test tests/bug-3677-*: 24/24 pass - Sibling-regression (6 slash-namespace test files): 76/76 pass - All install-minimal-all-runtimes suites: 54/54 pass after `npm run build:sdk` (the prior 27 fails were pre-existing — missing local sdk/dist build, not introduced by this change) - Full docker suite (gsd-test-summary): 11769/0 fail (11751 baseline + 18 new = my 24 tests with some collateral pickups) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore(changeset): set PR number 3680 (Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>) * test(3677): port real-source efficacy + idempotence tests from #3681 Adds describe group E with 5 behavioral tests credited to John Turner (johnzilla, PR #3681 — closed in favor of this PR by its author): E0: command roster is populated and includes symptom commands E1: every agents/gsd-*.md transforms clean — real-source efficacy E2: idempotent — repeat transform on hyphenated input is a no-op E3: word boundary — /gsd:plan-phase-extra is not a roster match E4: rewrites bare gsd:<cmd> shorthand (no leading slash) E1 is the test that would have caught the original bug — pure-function tests can pass while the install.js wiring silently bypasses the transform. E2 guards against double-rewrite mangling during reinstall. 29/29 tests pass (24 original + 5 ported). Co-Authored-By: John Turner <johnzilla@users.noreply.github.com> Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: John Turner <johnzilla@users.noreply.github.com> |
||
|
|
f970c09bf3 |
refactor(3664): migrate bin/install.js install/uninstall to Runtime Artifact Layout Module (#3674)
Phase 2 of #3660 / ADR-3660. Routes both lifecycle verbs through the Runtime Artifact Layout Module landed in Phase 1 (#3663): - Add installRuntimeArtifacts(runtime, configDir, scope, resolvedProfile) and uninstallRuntimeArtifacts(runtime, configDir, scope) as the public orchestrators. Both pre-prune stale gsd-* entries before staged copy; installRuntimeArtifacts brackets the prune+copy with preserveUserArtifacts / restoreUserArtifacts so user-owned content (e.g. gsd-dev-preferences) survives wipe-and-replace for claude/qwen/hermes runtimes. - Add applyRuntimeContentRewritesInPlace as the per-runtime path/branding post-stage step (preserves byte-output equivalence with the legacy copyCommandsAs* pipeline, including Qwen/Hermes branding rewrites). - Add _copyStaged, _removeGsdEntries kind-aware filesystem helpers. - Add _runLegacyInstallMigrations, _runLegacyUninstallCleanup as thin dispatchers over existing ADR-0008 legacy migrations (Hermes flat->nested per #2841, dev-preferences-as-skill per #2973). For Hermes, also clean up the intermediate skills/gsd/gsd-*/ layout that pre-Phase-2 installs left on disk. - Delete the 9 copyCommandsAs*Skills functions (Codex / Cursor / Windsurf / Trae / CodeBuddy / Copilot / Claude / Antigravity / Augment) and the _copyCommandsAsSkillsViaConverter helper. All test entry points migrated to call installRuntimeArtifacts directly through the unified seam. - Collapse the 9-branch uninstall ladder to one uninstallRuntimeArtifacts call plus preserved non-layout side-effects (Codex TOML, Copilot instructions, hooks). - Unify install dispatcher: a single _isSkillsRuntime gate routes all 11 skills runtimes through installRuntimeArtifacts for both full and core/ minimal profiles. Removes 11 per-runtime if-else branches (3 minimal-mode shim branches + 8 dead after-the-gate branches). Net delta on bin/install.js: 11,495 -> 11,174 (-321 LOC). New tests: - tests/install-uninstall-layout-loop.test.cjs (34 tests) - per-runtime fixture assertions on install/uninstall/legacy-migration ordering. - tests/install-hermes-regressions.test.cjs (6 tests) - covers the six defects surfaced by iterative review: Hermes upgrade leaves stale dirs, --hermes --profile=core fall-through, --qwen --profile=core fall-through, minimal-mode dev-preferences migration skipped (Hermes/Qwen/Claude-global), and ordering bug in _runLegacyInstallMigrations. Existing tests (10,038 prior + 40 new) all green: 10,078/10,078 pass. Refs #3664 Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
812f9258e4 |
fix(3597): pin shell:bash on npm ci/build steps in test.yml + regression test (#3673)
After PR #3649 (merge
|
||
|
|
40a442b21f |
Merge pull request #3649 from gsd-build/feat/3597-split-suites-node-matrix
feat(3597): split test suites and add Node 22/24/26 OS matrix |
||
|
|
e6200f2643 |
Merge pull request #3667 from gsd-build/feat/3663-runtime-artifact-layout-module-phase-1-m
Runtime Artifact Layout Module — Phase 1: module + surface.cjs migration |
||
|
|
a6f10a2cd7 | fix(init): derive nested-worktree flag from git show-prefix | ||
|
|
b8fa89b5b6 | fix(3663): address CodeRabbit surface/layout follow-ups | ||
|
|
4d81532b74 | fix(windows): use git show-prefix for nested worktree detection | ||
|
|
af54a4ffe6 | fix(windows): force root-equivalent cwd to non-nested | ||
|
|
2782fd05fb | fix(windows): derive nested-worktree state from normalized paths | ||
|
|
aa73c2d914 |
docs(3663): regenerate INVENTORY-MANIFEST.json to include runtime-artifact-layout.cjs
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
37805f6d2c |
docs(3663): add runtime-artifact-layout.cjs row to INVENTORY.md and bump CLI Modules count to 71
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
06cf085bfb |
fix(3663): Hermes empty-prefix uses manifest membership for stale-skill prune (Codex P1-2)
Replace the blunt kindPrefix !== '' guard in _syncGsdDir with a manifest-membership discriminator. For non-empty prefix runtimes, behavior is unchanged (prefix match). For Hermes (empty prefix), a directory is GSD-owned iff its stem appears in the canonical manifest; only those dirs are removal candidates when absent from the staged set. Dirs not in the manifest (user-owned) are preserved unconditionally. When no manifest is provided (legacy callers), the removal pass is skipped (conservative fallback). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
043cfd97b3 |
fix(3663): applySurface creates missing dest dirs (Codex P1-1)
Remove the fs.existsSync(dest) guard in applySurface so _syncGsdDir is
always called. _syncGsdDir already does mkdirSync(..., { recursive: true })
so the destination is created when absent — recovering partially-initialized
or user-deleted runtime config dirs. Also threads manifest through to
_syncGsdDir as optional 4th arg (used by P1-2 Hermes fix).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
||
|
|
925908039c |
refactor(3663): replace try/finally with t.after() in runtime-artifact-layout-stage tests
Remove the 2 empty try/finally wrappers (finally bodies contained only comments, no cleanup actions). Inline the assertions directly; add a comment noting stagedDir lifecycle ownership. No behavior change. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
f929e4a7bd |
refactor(3663): replace try/finally with t.after() in surface-apply tests
Replace all 6 try/finally blocks with t.after() per-test cleanup hooks. Import createTempDir/cleanup from tests/helpers.cjs; use createTempDir inside createFixtureRuntime (replaces inline tmpDir helper). Remove os import (now unused). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> |