28cf6b444ff1c500651fd963e797facbf86b8a2e
271 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
4b908e6bcd |
test(tests): antagonistic tier — fast-check property tests + Stryker mutation testing (PR-gated) (#461)
* feat(#454): add antagonistic tier — fast-check property tests + Stryker mutation config Adds property-based testing (fast-check v4) and mutation testing scaffolding (Stryker v8) as the antagonistic validation tier for lib/*.cjs pure logic. ## Files added ### Shared setup - tests/helpers/fast-check-setup.cjs — configureGlobal({ numRuns:200, seed:42 }) for deterministic CI; override locally with GSD_FC_SEED ### Property test suites (node:test + fast-check) - tests/context-utilization.property.test.cjs — boundary at 60%/70% thresholds (exact Math.ceil boundary, not Math.floor), TypeError on all invalid inputs, overflow clamping to 100%/critical, shape invariants (7 tests, all pass) - tests/prompt-budget.property.test.cjs — estimateTokens monotonicity + ceil(len/4) exactness; applyBudget shape invariant, instructions/roadmap verbatim, budget envelope, omit tracking (11 tests, all pass) - tests/frontmatter.property.test.cjs — extractFrontmatter/reconstructFrontmatter/ spliceFrontmatter never-throw + type shape + splice→extract round-trip (9 tests) - tests/adr-parser.property.test.cjs — shouldRejectAdrStatus boundary (3 statuses only), parseAdrMarkdown shape + title trim invariant (discovered: parser trims trailing whitespace) (9 tests, all pass) - tests/config-schema.property.test.cjs — isValidConfigKey never throws, returns boolean, accepts all VALID/RUNTIME_STATE_KEYS, rejects empty/null/unknown (8 tests) ### Stryker mutation config - stryker.config.mjs — testRunner:'command', mutate bin/lib/**/*.cjs minus 13 generated files, coverageAnalysis:'off', thresholds {high:80,low:60,break:50}, incremental:true, reporters html+clear-text+progress ### CI workflow - .github/workflows/mutation.yml — PR-gating job (pull_request + workflow_dispatch), runs stryker --incremental --since origin/next (changed files only), uploads HTML artifact; SINCE_REF via env not interpolation (injection-safe) ### Package config - package.json: +test:mutation, +test:mutation:since scripts - .gitignore: +.stryker-tmp/, +.stryker-incremental.json, +reports/mutation/ - package-lock.json: fast-check@4.8.0, @stryker-mutator/core@9.6.1 ## Verified node --test on all 5 property test files: 44 tests, 0 failures. Stryker NOT run (slow; reserved for CI). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(#454): pin upload-artifact to v7.0.1 SHA used across repo (bad SHA ea165f8d) The SHA ea165f8d65b6e75b540449d3ec4f5dde0c5a4e1 (labeled v4.6.2) does not resolve on GitHub Actions. All other workflows in this repo pin 043fb46d1a93c77aae656e7c1c64a875d1fc6a0a (v7.0.1) — align mutation.yml. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(#454): mutation workflow — replace invalid --since flag with changed-core-files --mutate scoping Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: CI Rebase Check <ci@gsd-redux> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
33afb4f6eb |
chore(#452): add ESLint 9 flat-config harness with three custom AST rules (#460)
Install eslint@9 + typescript-eslint@8 + globals@16 + eslint-plugin-n@17 + eslint-plugin-no-only-tests@3 + typescript as devDependencies. eslint.config.mjs (flat config): - Global ignores: node_modules, dist, .worktrees, .claude, coverage, the 12 generated get-shit-done/bin/lib/*.cjs files - Block for get-shit-done/bin/**/*.cjs + scripts/**/*.cjs: js.recommended + eslint-plugin-n + local plugin; generic quality rules (no-var, prefer-const, no-unused-vars, no-empty, n/no-process-exit) - Block for tests/**/*.test.cjs: no-only-tests (error), local timing rules, no-restricted-syntax timing bans eslint-rules/ local plugin (three AST rules, all at warn pending cleanup): - no-source-grep: flag readFileSync on source .cjs/.js/.ts + text methods - no-magic-sleep-in-tests: flag Atomics.wait and await-new-Promise(setTimeout) - no-elapsed-assertion: flag assert*() on timing props (elapsed/duration/took/ms) tsconfig.lint.json: allowJs + checkJs + noEmit for future type-aware passes. tests/eslint-rules.test.cjs: 15 RuleTester unit tests (all pass, 0 fail). package.json: add lint/lint:fix scripts; remove lint:tests (subsumed by ESLint local/no-source-grep). Rules that produced pre-existing errors downgraded to warn: no-useless-escape, no-unsafe-finally, no-regex-spaces, no-control-regex, no-irregular-whitespace. ESLint exits 0 (warnings ok). .github/workflows/test.yml lint-tests job: add npm ci + ESLint step; remove "Lint — no source-grep tests" step (now covered by ESLint); bump timeout 3→5 min. .gitignore: add node_modules/.cache/eslint/ entry. eslint --fix auto-cleaned: no-regex-spaces in tests, prefer-const in state.cjs, redundant eslint-disable-next-line comments. Co-authored-by: CI Rebase Check <ci@gsd-redux> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
48b1e35187 |
fix(#431): enforce H1 shell policy (linux=bash, macOS=zsh, windows=pwsh) across PR + release gates (#434)
* test(#431): policy-shell-pinning linter — RED baseline (37 violations on origin/next) Adds scripts/workflow-policy.cjs: H1 shell-policy linter with POLICY map, VIOLATION enum, matrix expansion, effective-shell resolution order, and runPolicyLint({ workflowsDir }) entry point. Adds tests/policy-shell-pinning.test.cjs: 8 tests (baseline + 6 synthetic counter-tests). Synthetic tests 2–7 pass; baseline test is intentionally RED (37 violations: 28 in test.yml, 9 in install-smoke.yml — all macos/windows lanes using shell: bash instead of native zsh/pwsh). Adds js-yaml@4.1.1 as devDependency for YAML parsing. * fix(#431): switch ubuntu/windows lanes to native shells; extract bash-isms to Node Remove all explicit shell: bash pins from ubuntu-only jobs (changes, lint-tests, coverage, required-tests, smoke-unpacked) — ubuntu runner default is bash, which is both H1-compliant and the runner default, making the pin redundant. For the test and test-full mixed-OS jobs (ubuntu+windows, windows+macos): - Move bash-ism steps to shell-agnostic Node scripts: scripts/ci-guard-runner.cjs — RUNNER_ENVIRONMENT check scripts/ci-rebase-check.cjs — git fetch+merge PR base branch scripts/check-npm-integrity.cjs — Node port of check-npm-integrity.sh scripts/ci-prepare-test-scope.cjs — write .ci-selected-tests.txt scripts/ci-smoke-skip.cjs — set skip= output for full-only matrix entries - Remove shell: bash from simple npm/node command steps (runner default applies) This brings Windows violations from 19 to 0. Remaining 17 violations are all MACOS_MISSING_EXPLICIT_ZSH in mixed-OS matrix jobs (test-full: windows+macos, install-smoke smoke: ubuntu+macos) — these require job splitting to fix; see BLOCKER in PR description. * fix(#431): update workflow-shell-pinning test for H1 policy The old test required all Windows-targeting npm steps to pin shell: bash (to prevent pwsh stderr-swallow). Under H1, Windows runners must use pwsh (native, no pin needed) — shell: bash on Windows is now the violation, not the fix. Update findViolations() to flag npm steps with effectiveShell === 'bash' (rather than effectiveShell === null). Update synthetic tests to verify the H1-inverted semantics: defaults.run.shell: bash on Windows is now 2 violations, not 0. Update test name and assertion messages to describe the H1 constraint rather than the old missing-pin constraint. * fix(#431): extend policy linter to resolve matrix.shell expressions - expandRunsOn now captures all matrix.include row keys as realization context (os, node-version, shell, full_only, etc.) instead of only os - effectiveShell now accepts a realizationContext and resolves ${{ matrix.<key> }} expressions against it before checking policy - Unresolvable matrix key in shell expression emits UNRESOLVABLE_MATRIX - Add 3 new tests: positive (zsh+pwsh per row → 0 violations), counter (bash in macOS row → WRONG_SHELL_FOR_OS), counter (missing shell key → UNRESOLVABLE_MATRIX) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(#431): apply matrix.shell pattern to test-full and smoke jobs (clears BLOCKER) test-full job (test.yml): - Add shell: pwsh/zsh per matrix.include row (windows-latest→pwsh, macos-latest→zsh) - Add job-level defaults.run.shell: ${{ matrix.shell }} - No step-level shell pins existed to remove smoke job (install-smoke.yml): - Add shell: bash/zsh per matrix.include row (ubuntu→bash, macos→zsh) - Add job-level defaults.run.shell: ${{ matrix.shell }} - No step-level shell pins existed to remove Policy linter now reports 0 violations across all workflow files. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * refactor(#431): migrate .sh check scripts to .cjs; remove .sh originals - Add scripts/check-env.cjs: Node.js port of check-env.sh with identical exit codes (0/1/2), human-readable and --json output, --help flag, and all 5 checks (node-version, npm-version, lockfile-present, lockfile-sync, version-manager-pin) - Migrate all callers: - package.json check:env → node scripts/check-env.cjs - package.json check:integrity → node scripts/check-npm-integrity.cjs - scripts/ci-test-scope.cjs path strings → .cjs equivalents - .github/workflows/release.yml rc+finalize jobs → node .cjs (drop chmod+x) - .github/workflows/security-scan.yml → node .cjs (drop chmod+x) - tests/check-env.test.cjs → spawn node process.execPath [.cjs] - tests/npm-integrity-gate.test.cjs → spawn node process.execPath [.cjs] - Delete scripts/check-env.sh and scripts/check-npm-integrity.sh Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * refactor(#431): update doc references from .sh to .cjs Update SECURITY.md and docs/contributing/bootstrap.md to reference the canonical Node invocation instead of the removed bash scripts. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(#431): use per-step shell:matrix.shell instead of defaults.run.shell (GHA compat) GHA does not reliably resolve matrix expressions inside defaults.run.shell. Per-step shell: always resolves correctly. Removed the defaults.run.shell block from the test-full job (test.yml) and the smoke job (install-smoke.yml), and added shell: \${{ matrix.shell }} directly on every run: step in both jobs. Codex finding: defaults.run.shell with matrix expressions is not a GHA-supported pattern; per-step shell: is the safe form. * fix(#431): policy linter validates every matrix.include row independently Removed runner-label-only dedup from expandRunsOn() in workflow-policy.cjs. The prior guard (if !realizations.find(r => r.runner === runner)) collapsed two macos-latest rows with different node-version/shell contexts into one, hiding the second row's policy violation. Each matrix.include row is a distinct CI realization with its own context; validating it twice is harmless but skipping it causes false negatives. Added counter-test (Test 8) in tests/policy-shell-pinning.test.cjs: two macos-latest rows (shell:zsh compliant + shell:bash violation) must produce exactly one WRONG_SHELL_FOR_OS violation on the second row. * fix(#431): remove dedup-by-runner in Cartesian matrix.<key> expansion (Codex round 3) The base-list path in expandRunsOn (matrix.<key> arrays, e.g. matrix.os) previously guarded each push with `if (!realizations.find(r => r.runner === runner))`, collapsing duplicate runner values into a single realization and hiding policy violations on later rows of a Cartesian matrix. Remove the guard unconditionally; each entry in the base-list array now produces its own realization, matching the same fix already applied to the matrix.include path. Add counter-test "Cartesian matrix os × shell — dedup must not collapse rows by runner alone": matrix.os: [macos-latest, macos-latest] + shell: ${{ matrix.shell }} now yields 2 realizations (not 1). Documents that Cartesian cross-product expansion (carrying all keys into realization context) is a separate follow-up; current violations are UNRESOLVABLE_MATRIX pending that work. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(#431): remove 60s timeout regression on npm ci --dry-run (parity with check-env.sh) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(#431): ci-rebase-check.cjs — return truthy sentinel on success (Codex round 4) run() used execFileSync with stdio:'inherit', which returns null on success. Caller checked `result !== null`, always false → every successful fetch fell through to "failed after 3 attempts" exit-1 path. Fix: run() now returns true on success, false on failure. Update caller from `result !== null` to `if (result)`. Adds tests/ci-rebase-check.test.cjs (5 tests) covering the sentinel contract and a local-bare-remote integration smoke that verifies the full fetch+merge path exits 0 when fetch succeeds. --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: CI Rebase Check <ci@gsd-redux> |
||
|
|
2b9fa009d3 |
fix(#373): replace unquoted $GSD_SDK with space-safe gsd_run launcher (#379)
* fix(#373): replace unquoted $GSD_SDK with space-safe gsd_run launcher Workflow bash blocks resolved the runtime as GSD_SDK="node $GSD_TOOLS" and invoked it unquoted ($GSD_SDK query ...). On install paths containing spaces (e.g. /Volumes/Mini Me/...) the unquoted expansion word-split into `node /Volumes/Mini gsd-tools.cjs ...`, failing with "Cannot find module '/Volumes/Mini'" and getting masked by `2>/dev/null || echo "{}"` into a silent empty state. Replace the string variable with a single-line shell launcher that defines a gsd_run function, invokes the runtime with a fully-quoted path and "$@", and preserves the local-cjs / installed-gsd-tools-on-PATH fallback (#3668) plus the loud not-found error and install hint. The launcher uses _GSD_SHIM_NAME indirection so no workflow emits the /gsd-tools substring that the do.md dispatcher-parity scanner would misread, and is single-line to stay within the per-file progressive-disclosure line budgets (#2551). The canonical launcher lives in get-shit-done/workflows/_runtime-launcher.snippet.sh, is propagated once per file by scripts/sync-runtime-launcher.cjs, and is locked by tests/runtime-launcher-parity.test.cjs (fails CI on drift, on a reappearing $GSD_SDK token, or on a /gsd-tools substring). Dependent workflow-assertion tests are updated from $GSD_SDK to gsd_run, and the runtime launcher is registered in CONTEXT.md. Fixes #373 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ci(#373): add changeset fragment and allow-test-rule for parity guard The runtime-launcher parity test is a structural drift guard that reads workflow markdown to assert the canonical launcher is present and the retired $GSD_SDK / /gsd-tools tokens are absent; annotate it with allow-test-rule per the no-source-grep lint escape hatch. Add the required .changeset fragment for this user-facing fix. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * test(#373): make parity PATH-fallback assertion cross-platform (Windows) Subtest (E) compared GSD_TOOLS against the Node-side absolute temp path, but the value originates from git-bash which reports the POSIX form, so the prefix comparison failed on windows-latest while the launcher itself worked (the installed stub was invoked). Assert the resolved binary by normalized suffix (/bin/gsd-tools, not .cjs) instead of the absolute prefix; the behavioral stub-invocation assertion is unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
9dd2c87c6c |
ci: supersede #367 with combined tiered PR pipeline redesign (#369)
* ci: streamline PR pipeline gates * test: annotate ci-test-scope stderr assertions for lint rule --------- Co-authored-by: Colin <colin@solvely.net> |
||
|
|
1b16c60df9 | ci(#365): run affected tests for pull request CI lanes (#366) | ||
|
|
43eafea3d5 | fix(#303): deepen command routing seams and alias parity guard (#304) | ||
|
|
11918dcc37 | chore(#191): retire sdk package seam | ||
|
|
a80241139b | build: run sdk build in root build script | ||
|
|
30c572e537 | refactor(#189): retire generated cjs artifacts from bin/lib | ||
|
|
4c92aacca5 | build(#188): retire sdk generator and freshness scripts | ||
|
|
33ffc647e2 |
feat(117): reproducible npm environment bootstrap + check-env validator (#136)
* test(117): add failing tests for env validator (check-env.sh)
RED phase. Six tests for scripts/check-env.sh — none pass because the
script does not exist yet. Fixtures:
good/ — engines.node >=22, .nvmrc 26, synced lockfile
bad-node-version/ — engines.node <14.0.0 (current Node v26 fails)
missing-lockfile/ — no package-lock.json
bad-nvmrc/ — .nvmrc says 22, current Node is v26
Tests cover:
1. Happy path exits 0
2. engines.node constraint failure exits 1
3. Missing lockfile exits 1
4. .nvmrc major mismatch exits 1
5. --json flag emits {pass: boolean, checks: array}
6. Integration smoke: exits 0 on live worktree root
Sources:
npm engines: https://docs.npmjs.com/cli/v10/configuring-npm/package-json#engines
npm ci docs: https://docs.npmjs.com/cli/v10/commands/npm-ci
Closes #117
* feat(117): add scripts/check-env.sh with Node/npm/lockfile/version-manager checks
GREEN phase. Implements the five-check environment validator:
1. Node version vs engines.node (semver constraint — >=, >, <=, <, =)
2. npm version vs engines.npm (skipped if field absent)
3. package-lock.json presence
4. Lockfile sync via `npm ci --dry-run` (exits non-zero when drift detected)
5. Version-manager pin (.nvmrc / .node-version / .tool-versions) vs active Node major
Exit codes: 0 = all green; 1 = at least one failure; 2 = tool error.
Flags: --json (structured report), --help.
All 7 tests pass. shellcheck clean. bash -n syntax check clean.
Sources:
npm engines: https://docs.npmjs.com/cli/v10/configuring-npm/package-json#engines
Reproducible builds: https://reproducible-builds.org/docs/source-tree/
npm ci docs: https://docs.npmjs.com/cli/v10/commands/npm-ci
Closes #117
* chore(117): pin Node engines + .nvmrc; add check:env npm script
- Add engines.npm: ">=10.0.0" (npm 10 ships with Node 22, the CI floor).
Source: https://docs.npmjs.com/cli/v10/configuring-npm/package-json#engines
- Add .nvmrc pinning Node 22 (lowest supported version per CI matrix in
.github/workflows/test.yml; node-version: [22, 24]).
- Add "check:env": "./scripts/check-env.sh" script to package.json.
No generator is involved (not a .generated. file). The test update in this
commit adjusts the integration smoke: it now asserts on --json structured
output rather than raw text, and accepts exit 0 or 1 (version-manager pin
mismatch is expected when developer runs Node 26 against a .nvmrc of 22).
Closes #117
* ci(117): wire environment check into test workflow
Add "Environment check" step to .github/workflows/test.yml in the `test`
job. Positioned AFTER actions/setup-node and BEFORE npm ci so that env
mismatches (wrong Node version, missing npm version, absent lockfile) are
caught before the install step obscures the root cause.
Runs `npm run check:env` (./scripts/check-env.sh) on every matrix lane
(ubuntu, macos, windows) × (Node 22, 24).
Source: https://docs.npmjs.com/cli/v10/configuring-npm/package-json#engines
Closes #117
* docs(117): publish docs/contributing/bootstrap.md + link from CONTRIBUTING.md
Adds docs/contributing/bootstrap.md with:
1. Prerequisites (nvm, fnm, asdf, mise; gh CLI)
2. One-time setup (clone, nvm use, check:env, npm ci)
3. Daily commands table
4. Validation guide (check table, exit codes, --json usage)
5. Troubleshooting (node-version, npm-version, lockfile-present,
lockfile-sync, version-manager-pin, missing modules, locale errors)
6. Alternative: Docker via gsd-test-runner
(https://github.com/open-gsd/gsd-test-runner)
Adds "Bootstrap your environment" section to CONTRIBUTING.md pointing to
the new doc. No content duplication — CONTRIBUTING.md links only.
Adds .changeset/117-npm-bootstrap.md (type: Added) for changelog.
Sources:
npm engines: https://docs.npmjs.com/cli/v10/configuring-npm/package-json#engines
Reproducible builds: https://reproducible-builds.org/docs/source-tree/
npm ci docs: https://docs.npmjs.com/cli/v10/commands/npm-ci
gsd-test-runner: https://github.com/open-gsd/gsd-test-runner
Closes #117
* fix(#117): make check:env script run on Windows runners
Invoke check-env.sh via `bash` instead of a bare POSIX path so
Windows CI runners (which have Git Bash on PATH) execute the script
without requiring a POSIX shell shebang dispatcher.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(117): make check-env.sh fixture .nvmrc adapt to active Node major (cross-platform fix)
Before() hook writes good/.nvmrc = activeNodeMajor and bad-nvmrc/.nvmrc = activeNodeMajor+99
at test-run time. Hardcoded .nvmrc=26 failed on every CI matrix row except Node 26.
After() restores originals so the checked-in files stay stable.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* docs(117): exempt gsd-test-runner URL path from slash-command registry check
docs/contributing/bootstrap.md links to https://github.com/open-gsd/gsd-test-runner.
The parity-test regex captures /gsd-test-runner from the URL path component and
flags it as an unregistered slash command. Add 'test-runner' to INTERNAL_COMPONENT_SLUGS
(mirrors the existing 'build' entry for GitHub org URLs) with an explanatory comment.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(117): fix Node-24 and Windows-22 CI failures in check-env
Two root causes:
1. version-manager-pin on Node 24 (mac/ubuntu/win):
The project root .nvmrc pins major 22 for local dev. When the CI
matrix runs Node 24, check-env.sh fails the version-manager-pin
check and exits 1, blocking the entire test job before any test
runs. Fix: skip the pin check when CI=true (GitHub Actions always
sets this). The pin is a local dev guard, not a gate for multi-
version matrix CI.
2. engines.node appears missing on Windows-22 (pkg_field backslash):
pkg_field() embedded PACKAGE_JSON directly into a node -e string
literal using require(). On Windows, the path uses backslashes
(D:\a\...) which are silently interpreted as JS escape sequences
inside the string, causing require() to fail silently (2>/dev/null
|| true). engines.node returns empty, triggering a spurious FAIL.
Fix: switch to fs.readFileSync + JSON.parse and normalise
backslashes to forward-slashes before embedding in the JS literal.
Also pass { CI: '' } from the bad-nvmrc unit test so the
version-manager-pin fixture test still exercises the mismatch path
even when running inside CI runners.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(117): use relative ./package.json path in pkg_field to fix Windows CI
On Windows, Git Bash exposes \$PWD as a POSIX path (/d/a/…) which
node.exe cannot resolve via fs.readFileSync. The previous fix embedded
the absolute PACKAGE_JSON path in the node -e string after converting
backslashes to forward-slashes, but the POSIX form produced by Git Bash
(/d/a/…) has no backslashes — so the conversion was a no-op and node
received an unresolvable path. The silent catch(e) { process.exit(0) }
swallowed the ENOENT, returning empty string for every engines.* field.
Fix: use './package.json' (relative to CWD). pkg_field() is always
called before any cd in the script so CWD === PROJECT_ROOT at call time.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
|
||
|
|
5414da2ce5 |
fix(6): retire validate.ts/verify.cjs cooperating-sibling, fix W007/phaseVariants/W006 drift via generator (#156)
* test(6): reproduce W007 + phaseVariants + W006 drift between CJS verify and SDK validate Adds tests/6-validate-cjs-drift-regression.test.cjs with 5 RED tests covering the three drift items from issue #6 between verify.cjs (Check 8) and validate.ts (Check 8): 1. W007 activeDiskPhases — verify.cjs uses diskPhases (includes archived) for W007; archived phase "1" absent from current ROADMAP fires false W007. validate.ts: activeDiskPhases (active phasesDir only) correctly excludes archives. 2. phaseVariants() normalization — ROADMAP says "01A", disk has "1A-foo". verify.cjs parseInt("01A")=1 → padded "01" (drops letter suffix) → miss. validate.ts phaseVariants("01A") = {"01A","1A","01A"} → "1A" matched. Both W006 and W007 fire as false positives in verify.cjs. 3. W006 letter-suffix padding mismatch — ROADMAP says "3B", disk has "03B-foo". verify.cjs parseInt("3B")=3 → padded "03" (drops "B") → diskPhases.has("03B") missed. W006 and W007 fire as false positives. All 5 tests RED on origin/main. Will turn GREEN after generator + verify.cjs migration. References: - Issue #6 (open-gsd/get-shit-done-redux) — maintainer acceptance criteria: "Port all three items to verify.cjs; add parity tests confirming identical output for all three cases on both paths" - ADR-3524 (docs/adr/3524-cjs-sdk-hard-seam.md) - PR #154 (issue #4) — precedent for the generator pattern Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * chore(6): add sdk/scripts/gen-validate.mjs generator Extracts phaseVariants() from sdk/dist/query/validate.js via brace-balanced source-text parsing (phaseVariants is a closure inside validateHealth, not a module export, so Function.prototype.toString() is unavailable). Emits get-shit-done/bin/lib/validate.generated.cjs with three pure helpers: - phaseVariants(phase): normalized Set of padded/unpadded/letter-suffix variants - buildRoadmapPhaseVariants(content): {roadmapPhases, roadmapPhaseVariants} - buildNotStartedPhaseVariants(content): Set of unchecked-phase variants These three helpers directly address the three drift items in issue #6. Follows the gen-phase-lifecycle-policy.mjs extraction pattern from PR #154. References: - Issue #6 (open-gsd/get-shit-done-redux) - ADR-3524 (docs/adr/3524-cjs-sdk-hard-seam.md) - PR #154 (issue #4) — generator pattern precedent Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * chore(6): add sdk/scripts/check-validate-fresh.mjs freshness check Mirrors check-phase-lifecycle-policy-fresh.mjs from PR #154: imports buildValidateCjs() directly, regenerates in-memory, and diffs against the committed validate.generated.cjs. Exits 1 if stale (CI gate). References: - Issue #6 (open-gsd/get-shit-done-redux) - ADR-3524 (docs/adr/3524-cjs-sdk-hard-seam.md) - PR #154 (issue #4) — precedent Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * chore(6): emit validate.generated.cjs from validate.ts Generated by: node sdk/scripts/gen-validate.mjs Exports three pure helpers extracted from sdk/src/query/validate.ts Check 8: - phaseVariants(phase): Set of normalized variants {"01A","1A"} etc. - buildRoadmapPhaseVariants(content): {roadmapPhases, roadmapPhaseVariants} - buildNotStartedPhaseVariants(content): Set of unchecked-phase variants Freshness check: node sdk/scripts/check-validate-fresh.mjs → FRESH References: - Issue #6 (open-gsd/get-shit-done-redux) - ADR-3524 (docs/adr/3524-cjs-sdk-hard-seam.md) - PR #154 (issue #4) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(6): migrate verify.cjs to consume validate.generated.cjs helpers (GREEN) Check 8 in verify.cjs now uses three generated helpers from validate.generated.cjs: 1. buildRoadmapPhaseVariants(roadmapContent) — replaces hand-rolled roadmapPhases Set. Produces both roadmapPhases (raw, for W006 message) and roadmapPhaseVariants (all variants, for W007 membership check). Fixes false W007 for letter-suffix phases with padding mismatch. 2. activeDiskPhases — now uses collectDiskPhases() WITHOUT forEachArchivedPhaseToken. W007 iterates activeDiskPhases, not diskPhases, so archived phases absent from current ROADMAP no longer trigger false W007. 3. buildNotStartedPhaseVariants(roadmapContent) — replaces raw+parseInt-padded notStartedPhases population. Uses phaseVariants() expansion so zero-padded letter-suffix unchecked entries (e.g. "03B") correctly suppress W006 for their un-padded counterpart ("3B") and vice versa. 4. phaseVariants() in W006 loop — replaces parseInt-padded disk-existence check. "3B" now matches disk dir "03B-foo" via variant expansion. Also updates test fixture for drift item 1 to use two milestone archives (v1.0 + v1.1), accurately reproducing the scenario where forEachArchivedPhaseToken walks ALL archives while getActiveMilestoneArchiveDir returns only the most recent one. All 5 tests GREEN. Confirmed RED on pre-fix code (git stash test). References: - Issue #6 (open-gsd/get-shit-done-redux) — maintainer acceptance criteria: "Port all three items to verify.cjs; add parity tests confirming identical output" - ADR-3524 (docs/adr/3524-cjs-sdk-hard-seam.md) - PR #154 (issue #4) — generator pattern precedent Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * ci(6): wire validate freshness check into test workflow Adds 'SDK generated validate artifact drift check' step to .github/workflows/test.yml, mirroring the pattern used by all PR #154 generator freshness checks. Runs on ubuntu-latest/node-24 only (same as other artifact drift checks). Placement: after workstream-name-policy check, before Shared Module hand-sync drift check. References: - Issue #6 (open-gsd/get-shit-done-redux) - ADR-3524 (docs/adr/3524-cjs-sdk-hard-seam.md) - PR #154 (issue #4) — precedent Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * chore(6): wire gen:validate into sdk/package.json, root package.json, and allowlist sdk/package.json: adds gen:validate and check:validate-fresh npm scripts. package.json: adds check:validate-fresh script (mirrors other check:*-fresh entries). scripts/shared-module-handsync-allowlist.json: updates verify.cjs justification to note that Check 8 W006/W007 helpers are now generated from validate.ts via gen-validate.mjs (issue #6), with freshness check at check-validate-fresh.mjs. References: - Issue #6 (open-gsd/get-shit-done-redux) - ADR-3524 (docs/adr/3524-cjs-sdk-hard-seam.md) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * docs(6): amend ADR-3524 — validate.ts now uses generator pattern Adds 2026-05-23 amendment section to docs/adr/3524-cjs-sdk-hard-seam.md documenting: - Generator/artifact/freshness-check/CI paths - Three drift items resolved (W007 activeDiskPhases, phaseVariants normalization, W006 unchecked-phase variant skip) - phaseVariants extraction technique (brace-balanced source-text parsing) - Parity test coverage (5 tests, RED→GREEN) - Allowlist classification preserved (cooperating-sibling) References: - Issue #6 (open-gsd/get-shit-done-redux) - ADR-3524 (docs/adr/3524-cjs-sdk-hard-seam.md) - PR #154 (issue #4) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * chore(6): add changeset fragment for validate.ts/verify.cjs generator migration Touches get-shit-done/bin/lib/validate.generated.cjs and verify.cjs which match USER_FACING_PREFIXES. Required by the fix-template checklist + the changeset-lint CI workflow. Refs #6 #156 * docs(6): register validate.generated.cjs in INVENTORY + manifest INVENTORY parity test demanded a row for the new generated CJS surface and a matching entry in INVENTORY-MANIFEST.json. Headline count bumped from 74 → 75. Refs #6 --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
89886d90b4 |
feat(114): npm dependency integrity gate (npm ls invalid/extraneous) (#135)
* test(114): add failing regression tests for npm dependency integrity gate
Adds tests/npm-integrity-gate.test.cjs and four fixture directories under
tests/fixtures/npm-integrity/ covering:
- clean: matching lockfile and node_modules (expects exit 0)
- drift: declared vs installed version mismatch (expects exit 1)
Reproduces the ws 8.20.1 declared / 8.20.0 installed incident shape
using stable-dep@8.20.1 (package.json) vs stable-dep@8.20.0 (node_modules).
- extraneous: unlisted package in node_modules (exits 1; exits 0 with --ignore-extraneous)
- missing: declared package absent from node_modules (exits 1 regardless of flags)
Each test spawns scripts/check-npm-integrity.sh as a subprocess and asserts
on exit code first, then stderr content. Tests are RED at this commit because
the script does not yet exist.
Sources:
npm ls docs: https://docs.npmjs.com/cli/v10/commands/npm-ls
NIST SSDF PW.4.1: https://csrc.nist.gov/publications/detail/sp/800-218/final
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(114): add check-npm-integrity.sh + workspace-aware drift detection
Adds scripts/check-npm-integrity.sh, a Bash script that:
1. Runs `npm ls --all --json` at the invocation directory
2. Parses JSON output for invalid, missing, and extraneous package flags
3. Exits 1 on any finding; emits a structured report to stderr listing offenders
with both declared and installed versions for invalid packages
4. Exits 2 on tool error (npm/node not found, JSON parse failure)
5. Accepts --ignore-extraneous to suppress extraneous-only failures
6. Documents behaviour in --help output including remediation path
Workspace behaviour: the root package.json in this repo has no "workspaces"
field. npm ls runs at the invocation root and covers that tree only. The sdk/
sub-package is a separate, non-workspace package and is out of scope for a
single invocation. If workspaces are added in future, npm ls will traverse
them automatically (npm >=7).
The drift scenario (ws 8.20.1 declared vs 8.20.0 installed) is reproduced by
using an exact version pin in package.json combined with a mismatched
node_modules/package.json -- npm ls marks this as "invalid" and exits 1.
npm exits 0 for extraneous packages even though they appear in the JSON
"problems" array; this script detects them via JSON parsing regardless of
the npm exit code.
Sources:
npm ls docs: https://docs.npmjs.com/cli/v10/commands/npm-ls
NIST SSDF PW.4.1: https://csrc.nist.gov/publications/detail/sp/800-218/final
OpenSSF Scorecard Pinned-Dependencies:
https://github.com/ossf/scorecard/blob/main/docs/checks.md#pinned-dependencies
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* ci(114): wire dependency integrity gate into CI/release/security workflows
Adds a "Dependency integrity gate" step invoking
scripts/check-npm-integrity.sh to three workflows, always after `npm ci`
and before any test or build step:
.github/workflows/test.yml
- matrix job: after "Install dependencies" / before "Build SDK dist"
- coverage job: after "Install dependencies" / before "Build SDK dist"
.github/workflows/release.yml
- rc job "Install and test": after npm ci, before npm run test:coverage
- finalize job "Install and test": after npm ci, before npm run test:coverage
.github/workflows/security-scan.yml
- Added setup-node + npm ci + gate before existing source-scan steps
- Bumped timeout-minutes from 5 to 10 to accommodate the install step
Also adds "check:integrity": "./scripts/check-npm-integrity.sh" to root
package.json scripts for local contributor invocation.
No new workflow files created. All edits extend existing workflows.
Sources:
npm ls docs: https://docs.npmjs.com/cli/v10/commands/npm-ls
NIST SSDF PW.4.1: https://csrc.nist.gov/publications/detail/sp/800-218/final
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* docs(114): document dependency integrity gate in audit runbook
Appends a "Dependency Integrity Verification" section to SECURITY.md
(no docs/runbooks/ directory exists in this repo). Covers:
- The three detection classes: invalid, missing, extraneous
- Local invocation: ./scripts/check-npm-integrity.sh + npm run check:integrity
- Remediation: rm -rf node_modules && npm ci
- Bypass policy: no flag; commit-message documentation required if skipped
- Scope: root package only (sdk/ is a non-workspace package, out of scope)
- CI coverage listing
Sources cited:
NIST SSDF PW.4.1: https://csrc.nist.gov/publications/detail/sp/800-218/final
OpenSSF Scorecard Pinned-Dependencies:
https://github.com/ossf/scorecard/blob/main/docs/checks.md#pinned-dependencies
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(#114): npm integrity gate satisfies its own clean/drift/extraneous fixtures
Replace npm-ls-based analysis with pure package-lock.json parsing so the
script runs correctly in CI and test environments where node_modules is not
installed. Key changes:
- Rewrite check-npm-integrity.sh parser to read package-lock.json directly
instead of spawning `npm ls --all --json`, which required node_modules on
disk and incorrectly flagged clean/drift fixtures as MISSING.
- Implement a self-contained semver satisfies() covering exact, caret, tilde,
comparison-operator, and compound ranges — no external semver package needed.
- Update extraneous fixture package-lock.json to include ghost-pkg with
"extraneous: true" so the lockfile-based detector can identify it.
- Update missing fixture package-lock.json to omit the node_modules/absent-dep
entry, making the absent-dep MISSING condition derivable from lockfile alone.
All 13 tests (clean ×2, drift ×3, extraneous ×3, missing ×3, help ×2) pass.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(#114): treat transitive deps as non-extraneous in integrity gate
The extraneous check was comparing all lockfile packages against root
package.json declarations only. This caused every transitive dependency
(e.g. hono, ajv, @anthropic-ai/claude-agent-sdk-darwin-arm64) to be
flagged as EXTRANEOUS, producing false-positive failures in CI.
Only packages that npm itself marks with "extraneous: true" in the
lockfile represent genuinely unwanted packages. Transitive dependencies
installed by parent packages are valid and should be skipped.
All 13 existing tests continue to pass; the extraneous fixture still
works because it uses "extraneous: true" explicitly (npm's own marker).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* ci: retrigger checks after transient git-auth runner failure
The original run for this PR had a single CI job fail with:
"fatal: could not read Username for 'https://github.com': terminal prompts disabled"
That is a hosted-runner infrastructure flake — no code defect. The run
cannot be retried via gh CLI (too old). This empty commit kicks a fresh
full CI cycle.
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
|
||
|
|
334a64168e |
chore(npm): rebrand packages to @opengsd scope (#127)
* chore(npm): rebrand packages to @opengsd scope Rename: - get-shit-done-redux → @opengsd/get-shit-done-redux - @gsd-redux/sdk → @opengsd/gsd-sdk Add publishConfig.access=public for first-time scoped publish. CLI binary names (get-shit-done-redux, gsd-sdk, gsd-tools) unchanged. Sweeps install commands, npx invocations, CI publish/version-check workflows, tests, docs, READMEs (all translations), and the PACKAGE_NAME constant in check-latest-version. Bumps qs 6.15.1 → 6.15.2 to clear a moderate advisory surfaced by the audit-clean test (GHSA-q8mj-m7cp-5q26). Closes #126 * chore: pin 2.0.0 release + remove canary workflow - Bump both packages 1.50.0-canary.0 → 2.0.0 for first @opengsd publish - Remove .github/workflows/canary.yml and canary dist-tag handling in release.yml / release-sdk.yml - Drop canary section from VERSIONING.md Refs #126 * chore: address review findings + harden tarball-smoke timeout - .changeset/opengsd-org-rename.md: match project's custom parse.cjs frontmatter (type: Changed / pr: 127); the scoped @changesets/cli keys were silently rejected. - CONTEXT.md: drop two canary-stream policy lines and a dangling DEFECT.CANARY-VERSION-LEAK.cross-ref now that canary.yml is gone. - tests/release-tarball-smoke.install.test.cjs: pass timeout: 600_000 for npm pack + global install; the 3-minute runNpm default was timing out on slower Docker hosts (cartographer). Refs #126 * fix(sdk): add missing type/runtime devDependencies for build prepublishOnly invokes tsc which couldn't resolve @types/node, @types/ws, or synckit. They had been hoisted from root but were not declared in sdk/'s own package.json — first publish from a clean SDK tree failed. Refs #126 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(ci): use npm pack stdout instead of glob to find tarball `npm pack --silent` for a scoped package (@opengsd/get-shit-done-redux) produces `opengsd-get-shit-done-redux-*.tgz`, not `get-shit-done-redux-*.tgz`. Capture the filename from stdout instead of a hardcoded glob so the step works regardless of package name format. Fixes smoke (ubuntu-latest, 22, false) CI failure. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * ci: treat workflow-file changes as test-skip eligible `.github/workflows/install-smoke.yml` (and other workflow files) were in neither `test.yml` paths nor `test-skip.yml` paths-ignore, so neither workflow ran on a workflow-only commit — leaving the required test-skip check perpetually missing. Refs #126 * chore: reset version to 1.0.0 for first @opengsd publish Nothing has been published yet under the @opengsd scope, so the inaugural release uses 1.0.0 rather than 2.0.0. The "major bump" in the changeset reflects the breaking install-command change for users migrating from the prior unscoped `get-shit-done-redux`, not a numeric continuation from a 1.x line under the new identity. Refs #126 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
2a915c1b82 |
chore: migrate references from gsd-build to open-gsd/get-shit-done-redux (#120) (#121)
Security-motivated migration of all stale repository and npm-scope references. Three categories of changes (58 files, 174 substitutions): 1. gsd-build → open-gsd (security-critical): - .github/workflows/release-sdk.yml — npm token comment, tarball filename pattern - .github/workflows/hotfix.yml — same - .changeset/fix-3406-detect-stale-sdk-shadow.md — @gsd-build/sdk → @open-gsd/sdk - .changeset/sharp-quails-leap.md — same - get-shit-done/workflows/update.md — CHANGELOG raw GitHub URL 2. GSD-redux org slug → open-gsd (canonical rename): - package.json + sdk/package.json — repository/homepage/bugs metadata - All README.*.md — live badge and link sections - CONTRIBUTING.md, CONTEXT.md, QUICK-WINS-CONFIRMED-BUGS.md - .coderabbit.yaml, .release-monitor.sh, scripts/sync-rulesets.sh - docs/** — all live agent/ADR/user-facing documentation - tests/** — repo slug assertions and test fixtures - scripts/changeset/cli.cjs + github-release-notes.cjs - .github/ISSUE_TEMPLATE/*, .github/pull_request_template.md - bin/install.js, get-shit-done/bin/lib/model-catalog.cjs - sdk/HANDOVER-*.md, sdk/src/*.test.ts 3. CLAUDE.md (gitignored local file — not in this commit): Updated separately outside git: --repo gsd-build/get-shit-done → --repo open-gsd/get-shit-done-redux with security warning. Intentionally unchanged: CHANGELOG.md, docs/RELEASE-*.md, .changeset/README.md, .changeset/build-hooks-atomic-write.md, README.md migration table (historical fork record), tests/changeset-serialize.test.cjs line 78 (serialization fixture). The gsd-build/get-shit-done repo is compromised (rug-pull documented in README.md). Do not push to or interact with that repo. Closes #120 |
||
|
|
dff176bfd2 |
chore: rebrand to GSD-redux/get-shit-done-redux
Mirror of code, issues, and PRs from the upstream gsd-build/get-shit-done, which appears compromised or abandoned (maintainer unreachable since 2026-04-01; $GSD token linked to rug-pull). - Adds rebrand notice block at top of English README - Removes $GSD token badge and @gsd_foundation X badge (keeps Discord) - Renames npm packages: get-shit-done-cc -> get-shit-done-redux, @gsd-build/sdk -> @gsd-redux/sdk - Updates all repo URLs across docs, workflows, package.json, bin/ - Updates ci@gsd-build -> ci@gsd-redux in workflow git identities - Leaves CHANGELOG and .changeset/* alone (historical, time-stamped) |
||
|
|
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> |
||
|
|
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> |
||
|
|
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. |
||
|
|
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> |
||
|
|
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. |
||
|
|
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>
|
||
|
|
5a8610e0c2 | test: forbid cwd in PR checkers | ||
|
|
a3ca6ff6d6 |
feat(3553): Project-Root Resolution Module via generator (Phase 4 of #3524)
Phase 4 of the CJS↔SDK hard-seam migration (parent #3524). Eliminates the `findProjectRoot` duplication that lived at bin/lib/core.cjs:74-140 and sdk/src/query/helpers.ts:497-590, the drift carrier behind historical bugs #1362 and #2561. - sdk/src/project-root/index.ts — source of truth (120 lines, pure-with-sync-fs). Exports findProjectRoot(startDir: string) and FIND_PROJECT_ROOT_MAX_DEPTH constant. - sdk/src/project-root/index.test.ts — 13 vitest pinning fixtures covering all four heuristics, the #1362 guard, malformed config fallback, empty sub_repos, deep nesting, and depth-limit enforcement. - sdk/scripts/gen-project-root.mjs — generator. Captures function body via Function.prototype.toString() from compiled sdk/dist/. Emits CJS preamble for destructured node:fs / node:path / node:os imports. - sdk/scripts/check-project-root-fresh.mjs — freshness check. Imports the generator function directly (Phase 3's cleaner pattern). - get-shit-done/bin/lib/project-root.generated.cjs — generator- emitted CJS mirror. - tests/project-root-generator.test.cjs — 11 parity assertions comparing SDK source and generated CJS for every fixture. - sdk/src/query/helpers.ts: -127 lines. The 94-line inline findProjectRoot plus the FIND_PROJECT_ROOT_MAX_DEPTH constant (originally at line 471) replaced by a single re-export: `export { findProjectRoot } from '../project-root/index.js';` Removed unused `parse as parsePath` import. - get-shit-done/bin/lib/core.cjs: -83 lines net. The 67-line inline findProjectRoot replaced by a single `require('./project-root.generated.cjs')`. The detectSubRepos helper at lines 40-56 stays (used by loadConfig migration). - sdk/package.json: gen:project-root + check:project-root-fresh scripts. - package.json: proxy for the freshness check. - .githooks/pre-commit: drift block. - .github/workflows/test.yml: drift check step after the state-document drift step. - CONTEXT.md: Project-Root Resolution Module entry. - docs/INVENTORY.md, docs/INVENTORY-MANIFEST.json: +1 module count, +1 row. - Full suite: 9226/9226 pass (baseline 9215 + 11 new parity fixtures). - SDK vitest: 1804/1804 pass. - Reader shrink: -127 SDK + -83 CJS = 210 lines of duplication deleted across the two Readers. New shared Module is 120 lines. 1. Depth limit canonicalization. CJS findProjectRoot previously had no explicit walk-up bound (walked until dir === root or homedir). The new Module uses FIND_PROJECT_ROOT_MAX_DEPTH = 10, matching the SDK's pre-existing value. Only affects paths nested more than 10 levels deep from a .planning/ root — a pathological case in practice. None of the existing 22 CJS findProjectRoot tests covered this; the new parity test does. 2. platformReadSync → readFileSync. The old CJS findProjectRoot used the platformReadSync wrapper from shell-command-projection.cjs for reading .planning/config.json, which returns null on read failure. The Module uses raw readFileSync, which throws — caught by the surrounding try/catch that already swallowed errors. Functionally equivalent for the existing code path; no test exercises the null-return semantic. Closes #3553. |
||
|
|
ed8f4c9a31 |
feat(3544): Workstream Inventory Builder/Reader split (Phase 3 of #3524)
Phase 3 of the CJS↔SDK hard-seam migration (parent #3524). Introduces the Builder/Reader pattern for paired Modules with mixed pure-and-I/O concerns — the template for Phase 4 and follow-up enhancements that migrate other paired Modules. Phase 1 and Phase 2 migrated Modules where both sides used character-equivalent logic. Phase 3 introduces the case where the pure logic is shareable but the I/O is legitimately per-side. The Builder/Reader split resolves this: - The Builder is pure — accepts pre-collected data (BuilderInputs struct), returns the typed projection. One source of truth; one generator-emitted CJS mirror. Drift is structurally impossible. - The Readers are per-side hand-authored Adapters that do the fs reads in their native idiom (currently both sync; either side can go async later without touching the Builder), then delegate to the Builder. - sdk/src/workstream-inventory/builder.ts — Builder source. 170 lines. Pure. Exports buildWorkstreamInventory(inputs), isCompletedInventory(status), plus the three typed inventory interfaces (WorkstreamPhaseInventory, WorkstreamInventory, WorkstreamInventoryList). - sdk/src/workstream-inventory/builder.test.ts — 18 vitest pinning fixtures across all status branches, progress-percent clamping, active-marker projection, and isCompletedInventory classifier. - sdk/scripts/gen-workstream-inventory-builder.mjs — generator. Captures function bodies via Function.prototype.toString(); emits with the standard GENERATED FILE banner. Includes a small `const relative = path.relative;` preamble in the output to handle ESM destructured imports in the compiled source. - sdk/scripts/check-workstream-inventory-builder-fresh.mjs — freshness check. Imports the generator function directly (rather than duplicating logic) — a cleaner pattern than Phase 1/2's approach. - get-shit-done/bin/lib/workstream-inventory-builder.generated.cjs — generator-emitted CJS mirror. - tests/workstream-inventory-builder-generator.test.cjs — 16 parity assertions confirming CJS-generated output == SDK source output for every fixture. - bin/lib/workstream-inventory.cjs: 159 → 132 lines. Projection logic gone. `inspectWorkstream` and `listWorkstreamInventories` collect BuilderInputs via the existing sync fs functions and delegate to the Builder. `isCompletedInventory` re-exported from the Builder (its signature changed from object→string, but no external callers exist so the change is safe). - sdk/src/query/workstream-inventory.ts: 196 → 143 lines. Same shape, sync fs (the SDK was already sync — surprise from recon). Types re-exported from the Builder. - sdk/package.json: gen:workstream-inventory-builder and check:workstream-inventory-builder-fresh scripts. - package.json: proxy for the freshness check. - .githooks/pre-commit: drift block. - .github/workflows/test.yml: drift check step. - CONTEXT.md: amended "Workstream Inventory Module" entry to document the Builder/Reader split. - docs/INVENTORY.md, docs/INVENTORY-MANIFEST.json: +1 module count, +1 row for the generated builder. - Full suite: 9229/9229 pass (baseline 9215 + 14 net new from the parity assertions). - Vitest: 18 Builder fixtures pass. - Reader shrink: -27 lines on CJS, -53 lines on SDK. - Net diff (modified files only): +68 / -133 = 65-line reduction. New files (Builder, generator, freshness check, parity test) add ~600 lines of new structured code. 1. `isCompletedInventory` signature changed from isCompletedInventory(inventory: object) to isCompletedInventory(status: string). Original CJS exported the object form but no external caller passed an object — they all passed inventory.status. Verified by grep before committing. 2. Generator preamble. The compiled ESM uses `import { relative } from 'node:path'`, making `relative` a free variable in `buildWorkstreamInventory`. The generator emits `const relative = path.relative;` so the captured function body works in CJS. 3. Freshness check imports the generator. The freshness check imports the generator's buildWorkstreamInventoryBuilderCjs() function directly rather than duplicating generation logic. Cleaner than Phase 1/2; future generators should follow this. Shareable via the Builder/Reader pattern in future enhancements: - frontmatter (pure YAML/markdown parsing) - plan-scan (pure PLAN.md structure parsing) - decisions (pure decision-record parsing) - secrets (regex-based detection in text) - uat (UAT-criteria parsing) Structural divergence — different approach needed: - state — sync vs async file ops; mutation paths differ. - workstream — lifecycle ops; per-side API surface differs. - phase, roadmap, init, profile-output, template — large surfaces; each its own potential enhancement. None of these is in scope for Phase 3. Closes #3544. |
||
|
|
2de2d185fa |
feat(3536): Configuration Module via shared manifests + generator (Phase 2 of #3524)
Phase 2 of the CJS↔SDK hard-seam migration (parent #3524). Eliminates the structural drift surface that produced bug class After this phase, neither bin/lib/ nor sdk/src/ defines CONFIG_DEFAULTS, VALID_CONFIG_KEYS, DYNAMIC_KEY_PATTERNS, or the four legacy-key normalizations inline. All come from one canonical source: the Configuration Module (sdk/src/configuration/index.ts) + two JSON manifests (sdk/shared/config-{defaults,schema}.manifest.json). The CJS mirror is generator-emitted (get-shit-done/bin/lib/configuration.generated.cjs) with a CI freshness check (sdk/scripts/check-configuration-fresh.mjs). - sdk/shared/config-defaults.manifest.json — canonical nested defaults, union of CJS + SDK keys (includes security_*, post_planning_gaps, agent_skills, mode, every git/workflow/hooks sub-section). - sdk/shared/config-schema.manifest.json — VALID_CONFIG_KEYS array, RUNTIME_STATE_KEYS array, DYNAMIC_KEY_PATTERNS array with source strings (regex reconstructed at runtime). - sdk/src/configuration/index.ts — source of truth. Exports loadConfig (pure read), normalizeLegacyKeys (pure, idempotent, returns Normalization[]), mergeDefaults (deep-merge), migrateOnDisk (explicit opt-in disk writeback), plus CONFIG_DEFAULTS, VALID_CONFIG_KEYS, RUNTIME_STATE_KEYS, DYNAMIC_KEY_PATTERNS. - sdk/src/configuration/index.test.ts — 29 vitest pinning tests. - sdk/scripts/gen-configuration.mjs — generator (Function.prototype.toString() inspection of compiled SDK dist, plus brace-balanced text scan for internal helpers, matching the Phase 1 pattern). - sdk/scripts/check-configuration-fresh.mjs — CI freshness gate. - tests/configuration-generator.test.cjs — 27 parity assertions (CJS-generated == SDK source). - tests/configuration-migrate-config.test.cjs — 3 cases for the new gsd-tools migrate-config subcommand. - bin/lib/core.cjs: CONFIG_DEFAULTS literal now sources values from CANONICAL_CONFIG_DEFAULTS (the manifest), with a thin flat projection at the load boundary to preserve the existing flat-shape return contract for the ~21 CJS test files and 100+ consumers. All four legacy-key migration blocks (branching_strategy, sub_repos, multiRepo, depth — historically lines 351-358, 388-397, 401-408, 416-423) collapse to a single normalizeLegacyKeys call in each code path. The inline platformWriteSync writeback stays for now to preserve sync loadConfig semantics; the new async migrateOnDisk is reachable via gsd-tools migrate-config. - bin/lib/config-schema.cjs: 135 → 31 lines. Re-exports from the generated Module. - bin/lib/config.cjs: adds cmdMigrateConfig handler (calls migrateOnDisk on the explicit user-driven path). - bin/gsd-tools.cjs: wires migrate-config into command dispatch. - sdk/src/config.ts: re-exports CONFIG_DEFAULTS and mergeDefaults from the Module. loadConfig now calls normalizeLegacyKeys before mergeDefaults (replaces the inline branching_strategy graft). - sdk/src/query/config-schema.ts: 160 → 36 lines. Re-exports from the Module. - tests/config-schema-sdk-parity.test.cjs: refactored from "CJS Set equals SDK Set" (trivially true post-migration) to "both sides source from the manifest" — structural plus runtime invariant. - Four other tests that text-grepped source files for valid keys (plan-review-convergence, bug-3212, bug-2492, feat-3210) are updated to use runtime VALID_CONFIG_KEYS.has() or manifest JSON lookups. - CONTEXT.md: new Configuration Module entry with full Interface contract. - Root package.json: check:configuration-fresh proxy script. - sdk/package.json: gen:configuration + check:configuration-fresh. - .githooks/pre-commit: configuration drift block. - .github/workflows/test.yml: configuration drift step after the alias drift check. - 9201 CJS tests pass (baseline pre-cycle: 9195; +6 net new tests across migrate-config + parity refactor) - 1872 SDK vitest tests pass - 29 Configuration Module vitest fixtures - 27 CJS/SDK parity fixtures - Net diff: +388 / −519 = 131-line reduction across the seven cycles, despite adding the new Module, manifests, generator, freshness check, and two new test files. 1. SDK CONFIG_DEFAULTS now includes manifest-canonical keys (resolve_model_ids: false, context_window: 200000, phase_naming, claude_md_path, git.create_tag, workflow.security_*, workflow.code_review_*, planning.*, hooks.workflow_guard, ship.*). Consumers accessing via [key: string]: unknown index get the manifest default instead of undefined. 2. SDK mergeDefaults is now proper recursive deep-merge instead of spread-per-section. Overlay { workflow: { research: false } } now preserves sibling workflow keys; previously it replaced the entire workflow section with only research + the section's defaults. Semantically identical for the common case; strictly better for partial nested overrides. 3. New gsd-tools migrate-config CLI subcommand for the explicit, opt-in on-disk migration path. Closes #3536. |
||
|
|
bfd7ddbad3 |
feat(3530): STATE.md Document Module via generator (Phase 1 of #3524) (#3531)
* feat(3530): STATE.md Document Module via generator (Phase 1 of #3524) Phase 1 of the CJS↔SDK hard-seam migration (parent #3524). Converts the hand-synced state-document.cjs/state-document.ts pair into a generator-driven seam, modeled on the existing command-aliases.generated.* precedent. What landed: - sdk/src/query/state-document.ts is the source of truth. - sdk/scripts/gen-state-document.ts emits get-shit-done/bin/lib/state-document.generated.cjs from the compiled SDK dist via Function.prototype.toString() inspection for the 7 public exports and 3 internal helpers. - sdk/scripts/check-state-document-fresh.mjs is the CI freshness gate; pre-commit hook also runs it when relevant files change. - get-shit-done/bin/lib/state-document.cjs is reduced to a one-line re-export from state-document.generated.cjs so existing callers (state.cjs, workstream-inventory.cjs, init.cjs) need no changes. - New CI step in .github/workflows/test.yml after the existing alias drift check. - sdk/package.json: gen:state-document, check:state-document-fresh scripts. tsx added as devDep. - Root package.json: proxy script for the freshness check. - CONTEXT.md: one-sentence amendment on STATE.md Document Module recording the source-of-truth file path. Tests: - sdk/src/query/state-document.test.ts: 34 vitest fixtures across the 7 public exports (TDD pinning safety net). - tests/state-document-generator.test.cjs: 31 node:test parity assertions comparing SDK source vs generated CJS for every fixture. - Full suite: 9177/9177 pass (baseline was 9146; +31 new tests). One subtle behavior change worth flagging: the old hand-written state-document.cjs used String(str) coercion inside escapeRegex, which the SDK source does not. The generator faithfully matches the SDK (the source of truth per ADR-3524), so the new CJS no longer coerces non-string input to string before regex-escaping. No current caller passes non-string input, so no observable regression in the test suite. Flagged in the PR body for reviewers. Closes #3530. * fix(3530): address state-document review findings |
||
|
|
75d5ca5875 |
feat(code-review): integrate fallow structural pre-pass for /gsd-code-review (#3424)
* feat(code-review): add optional fallow structural pre-pass * fix(ci): sync lockfile for fallow optional binaries * fix(test): make fallow integration tests cross-platform * fix(review): require executable fallow binary paths * docs(review): clarify structural findings usage and size guard * fix(fallow): preserve line:0, prefer node_modules/.bin, sync SDK twin (H1, M2, N1 from #3424 review) * fix(workflow): harden fallow pre-pass — exit check, timeout, atomic write, size-guard order (B1, H2-H4, M1, M3 from #3424 review) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(deps): pin fallow floor to ^2.70.0 matching lockfile (H7 from #3424 review) * fix(config): enum-validate fallow.scope/profile + group code_quality.* contiguously (H5, N3 from #3424 review) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * docs(fallow): label mcp gate reserved, version-pin install, expand context schema (B3, H8, M4, M8, L1 from #3424 review) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test(fallow): replace source-grep with behavioral tests, expand fixtures, fail-loud tmpdir (B4, H6, L2, L3, M5, M6, N2 from #3424 review) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(workflow): escape closing structural_findings tag in JSON payload (CR #3424 inline finding) --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
d0f916728b |
feat(skill-surface): install-time profiles + runtime /gsd:surface (#3408) (#3456)
* feat(skill-deps): add requires: frontmatter to all 51 skills with cross-skill references Mechanical migration from docs/research/data/2026-05-12-skill-audit.json. Every skill whose body references another GSD skill now declares those dependencies in `requires:` YAML frontmatter (flow-style array). Notable: discuss-phase, plan-phase, and execute-phase all reference `phase`, which confirms the latent gap in MINIMAL_SKILL_ALLOWLIST — `phase` is pulled by the core loop but was never in the allowlist. The profile closure model (ADR-0010 Phase 1) resolves this automatically. Closes part of #3408. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(skill-surface-budget): add PROFILES map, resolveProfile, loadSkillsManifest, staging, marker IO Implements the Skill Surface Budget Module core (ADR-0010, Phase 1): - PROFILES Object.freeze map: core (6 skills), standard (~13), full ('*') - loadSkillsManifest: parses requires: frontmatter from commands/gsd/*.md into a Map<stem, string[]> without external YAML dep - resolveProfile({modes, manifest}): computes transitive closure over the requires: graph; composable (modes=['core','audit'] unions closures) - stageSkillsForProfile / stageAgentsForProfile: filesystem staging with same exit-cleanup machinery as the legacy stageSkillsForMode - readActiveProfile / writeActiveProfile: .gsd-profile marker round-trip - Back-compat shims preserved: MINIMAL_SKILL_ALLOWLIST, isMinimalMode, shouldInstallSkill (overloaded), stageSkillsForMode — all legacy tests pass The phase latent bug is now resolved by closure: discuss-phase, plan-phase, and execute-phase all require phase, so any profile including any of them automatically includes phase via transitive closure. Tests: 22 manifest+resolve, 9 stage, 10 marker (41 new tests, all green). Back-compat anchor: 80/80 passing. Closes part of #3408. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(skill-surface-budget): add lint-skill-deps.cjs CI gate and fix 19 missed requires: entries Two lint checks (scripts/lint-skill-deps.cjs): a) Frontmatter-body consistency: skill body references must appear in requires: b) Profile closure: every requires: dep of any profile skill must be in closure Running the lint revealed 19 body references missed by the audit JSON (the audit used static analysis; some bodies have conditional references). Fixed: complete-milestone: +audit-milestone, discuss-phase, plan-phase, execute-phase, new-milestone fast: +quick health: +thread map-codebase: +new-project, plan-phase new-milestone, new-project, review, ultraplan-phase: +plan-phase ship: +verify-work sketch, spike: +new-project verify-work: +execute-phase workstreams: +new-milestone, resume-work Wired into package.json as lint:skill-deps and added to pretest. 8 fixture-based tests: all green. Closes part of #3408. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(skill-surface-budget): wire --profile= arg, profile marker write/read in bin/install.js - Add --profile=<name> / --profile=<n1>,<n2> arg parsing (composable). Mutually exclusive with --minimal / --core-only (aliases for --profile=core). Default (no flag): full. - Import readActiveProfile / writeActiveProfile from install-profiles.cjs. - After writeManifest: persist active profile to .gsd-profile marker. - gsd update path: if no --profile flag given, read existing .gsd-profile marker so non-full profiles are not silently re-expanded to full (ADR-0010). - Update --help block to document --profile= with per-tier token costs. New test: install-minimal-backcompat.test.cjs (6 tests): - PROFILES.core === MINIMAL_SKILL_ALLOWLIST (contract) - --minimal writes .gsd-profile marker "core" - --profile=core, --profile=standard write correct markers - default install writes marker "full" Closes part of #3408. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * chore(changeset): add feat-3408-skill-profiles changelog fragment Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(install-profiles): derive agents from skill body refs and wire into resolveProfile Deviation 1 of ADR-0010 phase 1b: tiered profiles (core, standard) now produce a non-empty agents Set instead of always returning empty. resolveProfile() scans each skill body for gsd-* agent name references (via new parseCallsAgents()), stores them in _calls_agents_<stem> manifest entries, and unions them across the resolved skill closure. stageAgentsForProfile() already checked resolvedProfile.agents — it now gets real data so tiered profiles install the correct subset of agents instead of zero. Closes #3408 (partial — Deviation 1 only) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(install): honor .gsd-profile marker on update, add resolveEffectiveProfile/mostRestrictiveProfile Deviation 2 of ADR-0010 phase 1b: the marker written during installation is now actually honored when re-running without explicit flags (e.g. gsd update). The dead-end logging block is replaced by resolveEffectiveProfile(), which picks the marker profile over 'full' when no explicit --profile= flag was given. The resolved profile is piped through to all 13 stageSkillsForMode dispatch sites (now _stageSkills) so updates install only the previously-chosen skill subset. --minimal retains its back-compat behavior (strict 6-skill allowlist, no closure) while writing 'core' to the marker. mostRestrictiveProfile() is exported for callers that need to reconcile disagreeing markers across runtimes (smallest skill set wins). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(surface): add CLUSTERS data + state IO module Add clusters.cjs with 10 named skill groups covering all 66 skills (verified by surface-clusters.test.cjs). Add surface.cjs with readSurface/ writeSurface atomic IO, resolveSurface, applySurface, and listSurface. Tests: 17 passing (11 state IO + 6 cluster integrity). Closes #3408 * docs(adr): add ADR-0011 Skill Surface Budget Module (Phase 1 accepted, Phase 2 amendment) Records the install-time profile staging decision (Phase 1, landed) and the runtime /gsd:surface cluster-toggle decision (Phase 2, in flight) as an amendment. Updates the ADR README index. Closes #3408 * docs(install-profiles): update module docblock for Phase 2 and ADR-0011 Corrects the ADR reference from 0010 to 0011, documents the three-profile model and back-compat aliases, adds resolveEffectiveProfile precedence rule, and notes the companion surface.cjs Phase 2 engine. * docs(context): add Skill Surface Budget Module canonical entry Adds the Domain terms entry for the Skill Surface Budget Module covering both Phase 1 (install-time profiles, .gsd-profile marker) and Phase 2 (runtime /gsd:surface cluster toggles, clusters.cjs, .gsd-surface.json), per ADR-0011 Consequences requirement. * feat(surface): add resolveSurface and applySurface engine + tests Tests cover: profile → surface equivalence, cluster disable/enable, explicitAdds transitive closure, applySurface file sync (add missing, remove superseded, preserve non-gsd files), listSurface token cost. 16 new tests passing. * docs(readme): document --profile= flag and /gsd:surface command Brief user-facing mention of install profiles (core/standard/full) and the /gsd:surface slash command in the Commands table. Points to ADR-0011 for details. * feat(surface): add /gsd:surface slash command runbook New skill: gsd:surface — runtime profile/cluster toggle without reinstall. Sub-commands: list, status, profile <name>, disable/enable <cluster>, reset. Persists state to .gsd-surface.json (independent of .gsd-profile). Description 96 chars (≤100 limit). lint:descriptions + lint:skill-deps: 0 violations. * feat(surface): add changeset fragment for /gsd:surface runtime toggle * feat(surface): add surface skill stem to utility cluster surface.md is a new skill; add it to the utility cluster so the surface-clusters.test.cjs coverage invariant stays satisfied. * docs(adr): fix ADR references to 0011 and record Phase 2 as shipped ADR-0010 number was already claimed by the file-operation-engine ADR; this ADR landed as 0011-skill-surface-budget-module.md. Update inline ADR references in clusters.cjs, surface.cjs, install-profiles.cjs, and the Phase 2 changeset to ADR-0011. Update the ADR Status section to record Phase 2 artifacts as shipped on this branch rather than "in progress". Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * docs(research): port skill-surface-budget memo and audit data ADR-0011 references docs/research/2026-05-12-skill-surface-budget.md and docs/research/data/2026-05-12-skill-audit.json, which only existed in the research worktree. Port both onto this branch so the ADR's References section resolves and reviewers can read the cluster taxonomy (§3.2), dependency topology (§3.1), and option grading (§4) that justify Phase 1 and Phase 2 decisions. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(registration): register surface/clusters in INVENTORY, COMMANDS, and help.md - surface.md: convert allowed-tools from inline YAML array to block style (was parsed as a single tool name "[Read, Write, Bash]" by test harness) - docs/INVENTORY.md: add CLI module rows for clusters.cjs and surface.cjs; add Commands row for /gsd-surface; bump CLI Modules count 55→57, Commands 66→67 - docs/INVENTORY-MANIFEST.json: add entries for clusters.cjs, surface.cjs, and /gsd-surface (filename-based command key) - docs/COMMANDS.md: add ### `/gsd-surface` heading in Configuration Commands - get-shit-done/workflows/help.md: add /gsd:surface entry in Configuration section Fixes registration failures introduced by Phase 2 of #3408. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(surface,docs): scrub .claude leakage and escape hypothetical slash tokens Two PR regressions introduced earlier on this branch: 1. surface.cjs JSDoc comments contained the canonical paths (~/.claude/commands/gsd, ~/.claude/agents) as example values, which the cline-install leak regex (~\/\.claude\/(?:get-shit-done|commands|agents |hooks)) flagged as install-time path leaks. Reworded the docblocks to describe runtime-resolved paths without literal ~/.claude tokens. 2. The ported research memo proposed hypothetical Option C dispatchers using slash syntax (/gsd:milestone, /gsd:research). The docs-parity-live-registry test enforces that every slash-command token in docs/ resolves to a real command. Rewrote the Option C sketch without the slash prefix and added a clarifying note that the dispatchers are illustrative, not shipped. Targeted tests now pass: tests/cline-install.test.cjs and tests/docs-parity-live-registry.test.cjs both green. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test: remove raw output/source grep in lint tests * fix: close coderabbit profile and requires issues * test: align surface token-cost assertion wording * fix(install): align core profile alias and defer profile marker write --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
96806003c5 |
fix(#3229): shared model catalog source of truth for agent profiles + runtime tier defaults (#3230)
* docs(adr): add ADR-0003 model catalog module * fix(#3229): add shared model catalog as source of truth for agent profiles and runtime tier defaults Research / design (ADR-0003): - Existing drift came from 4 independent model truths: 1. CJS model-profiles.cjs 2. SDK config-query.ts stale copy (18 agents) 3. settings-advanced.md runtime tier table 4. session-runner Claude-only profile map - New design: one machine-readable Model Catalog Module in sdk/shared/ that both packages ship and consume. Implementation: - sdk/shared/model-catalog.json — canonical source of truth for: - full 33-agent registry - per-agent golden (quality) alias + balanced/budget aliases - adaptive derivation from routingTier - agent→phaseType map - agent→dynamic-routing default tier map - runtime tier defaults for all supported runtimes - get-shit-done/bin/lib/model-catalog.cjs — CJS adapter over the catalog - sdk/src/model-catalog.ts — SDK adapter over the same catalog - CJS model-profiles.cjs now re-exports derived data from model-catalog.cjs - SDK config-query.ts now re-exports MODEL_PROFILES/VALID_PROFILES from model-catalog.ts instead of maintaining its own list - sdk/src/query/helpers.ts runtime list now comes from the catalog (fixes hermes drift) - sdk/src/session-runner.ts Claude profile→model-id mapping now resolves via catalog - docs/CONFIGURATION.md + settings-advanced.md runtime tables updated to match catalog Behavior changes: - resolve-model now covers every shipped agent file on disk (33 agents) - unknown-agent fallback is profile-semantic, not hardcoded sonnet: quality→opus, budget→haiku, balanced/adaptive→sonnet, inherit→inherit - Group B runtimes remain known runtimes but do not get built-in tier defaults Tests (RED→GREEN): - root tests: shipped agent files must equal MODEL_PROFILES keys - sdk tests: shipped agent files must equal MODEL_PROFILES keys - direct fix assertion: gsd-code-reviewer resolves to opus under quality with no unknown_agent - runtime defaults parity test: settings-advanced.md + CONFIGURATION.md tables must match catalog - helper tests: hermes included in SUPPORTED_RUNTIMES and getRuntimeConfigDir() Closes #3229 * chore(changeset): update #3229 changeset pr field to 3230 * fix(ci): update inherit fallback expectations and inventory parity for model catalog |
||
|
|
2d32ad82be |
fix(plan-phase): remove agent: directive that caused OpenCode subagent dispatch (#3156) (#3206)
* feat(roadmap): parse **Mode:** field on phase sections Adds a 'mode' field to roadmap.get-phase and roadmap.analyze outputs. Recognizes '**Mode:** mvp' lines in phase sections; lowercased + trimmed. Forward-compat: unrecognized values preserved verbatim, no enum check. Foundation for --mvp flag in plan-phase (PRD: vertical-mvp-slice). * feat(plan-phase): parse --mvp flag and resolve MVP_MODE Resolution order: CLI flag → ROADMAP **Mode:** field → workflow.mvp_mode config → false. Walking Skeleton gate fires for new-project Phase 1. Wires MVP_MODE + WALKING_SKELETON into gsd-planner subagent prompt. Per PRD vertical-mvp-slice Phase 1 (Q1, Q2, Q4). * docs(planner): add vertical-slice planning reference New reference loaded by gsd-planner when MVP_MODE=true. Defines slice ordering, Walking Skeleton rules, and anti-patterns. Referenced from plan-phase workflow MVP_MODE wiring. * docs(planner): add SKELETON.md template Template emitted by gsd-planner under WALKING_SKELETON=true. Captures architectural decisions and out-of-scope list for new-project Phase 1. * chore(inventory): register new planner references Added planner-mvp-mode.md and skeleton-template.md to INVENTORY.md and INVENTORY-MANIFEST.json. References now: 53. * feat(gsd-planner): add MVP Mode Detection section Mode-switched branch in the existing planner agent (per Q4: single agent). Vertical-slice decomposition rules, Walking Skeleton handling, and TDD-mode compatibility. Heavy guidance lives in references/planner-mvp-mode.md. * test(plan-phase): add --mvp resolution-chain integration cases Validates roadmap.get-phase --pick mode and confirms workflow.mvp_mode default is unset in fresh projects. * docs(changelog): announce --mvp vertical-slice planning (#2826) * feat(mvp-phase): add /gsd mvp-phase slash command Standalone command for vertical MVP planning. Frontmatter only; heavyweight workflow at get-shit-done/workflows/mvp-phase.md follows in next commit. Mirrors discuss-phase/edit-phase command shape. * docs(planner): add user-story-template reference Defines the canonical 'As a / I want to / So that' format and the ROADMAP.md / PLAN.md emit rules. Used by mvp-phase workflow and gsd-planner agent under MVP_MODE. * docs(planner): add SPIDR splitting reference Defines size signals, the five SPIDR axes (Spike/Paths/Interfaces/Data/Rules), the interactive workflow, and anti-patterns. Per PRD Q3 decision: full interactive flow, not lightweight check. Used by mvp-phase workflow. * fix(mvp-phase): trim description to fit 100-char budget * feat(mvp-phase): add mvp-phase workflow Standalone workflow: phase validation -> user story prompts (As a / I want to / So that) -> SPIDR splitting check -> ROADMAP write (Mode + Goal) -> delegation to plan-phase. Per PRD Phase 2 (Q3 full SPIDR; Phase-2-A/B/C/D decisions). Plan-phase auto-detects MVP via Phase 1's resolution chain, so no flags are needed when delegating. * feat(gsd-planner): emit user-story header in PLAN.md under MVP mode Extends the MVP Mode Detection section (added in Phase 1) so the planner sources the user story from ROADMAP **Goal:** and emits the bolded **As a** / **I want to** / **so that** form as the first content under the phase header in PLAN.md. References user-story-template.md. * test(mvp-phase): integration smoke test for ROADMAP mutation Validates roadmap.get-phase output after a workflow-spec'd ROADMAP write: mode=mvp and goal=full user story. Catches schema drift between workflow emit and parser expectation. Includes a long-story case (>120 chars) to confirm SPIDR-rejected stories still parse correctly. * chore(inventory): register mvp-phase command + 2 new references Adds /gsd mvp-phase to commands list, mvp-phase workflow to workflows list, and user-story-template.md + spidr-splitting.md to references. References count: 53 -> 55. * docs(changelog): announce /gsd mvp-phase command (#2826) * fix(mvp-phase): add TEXT_MODE plain-text fallback for non-Claude runtimes (#2012) * docs(executor): add MVP+TDD gate reference Defines the runtime gate semantics for execute-phase when both MVP_MODE and TDD_MODE are true: pre-task verification of failing-test commit, end-of-phase review escalation from advisory to blocking, behavior-adding task definition. Loaded conditionally by execute-phase workflow and gsd-executor agent. * feat(execute-phase): MVP+TDD runtime gate + blocking review Resolves MVP_MODE in Step 1 (CLI flag -> roadmap mode -> config -> false). Adds per-task gate that halts before behavior-adding tasks run if no failing-test commit exists for the plan. Escalates end-of-phase TDD review from advisory to blocking when both MVP_MODE and TDD_MODE active. Also updates INVENTORY-MANIFEST.json to register execute-mvp-tdd.md (added by Task 1) so manifest-sync tests pass. Per PRD vertical-mvp-slice Phase 3a (decisions Phase-3-A, Phase-3-Split). * feat(gsd-executor): add MVP+TDD Gate section Mirrors the planner's MVP Mode Detection pattern from Phase 1. Instructs halt-and-report when the runtime gate trips, references execute-mvp-tdd.md for full semantics. No agent changes outside the new section. * test(execute-phase): add MVP+TDD resolution-chain integration cases Validates roadmap.get-phase --pick mode and confirms workflow.mvp_mode default is unset in fresh projects. Mirrors the Phase 1 plan-phase resolution-chain integration test. * chore(inventory): register execute-mvp-tdd reference Bumps References count 55 -> 56. Registers execute-mvp-tdd.md. Adds "init" to PROSE_ALLOWLIST in registry integration test so bare `gsd-sdk query init` prose examples in plan docs don't trigger the unregistered-handler guard (real commands are all init.<subcommand>). * docs(changelog): announce MVP+TDD runtime gate in execute-phase (#2826) * docs(verifier): add verify-mvp-mode reference Defines UAT framing under MVP mode: user-flow walk-through first, technical checks deferred, coverage check as goal-backward narrowing to the user story's outcome clause. Loaded conditionally by verify-work workflow and gsd-verifier agent. * feat(verify-work): MVP-mode UAT framing — user flow first Resolves MVP_MODE from phase mode field. Under MVP mode, generates UAT in three ordered sections: user-flow walk-through (derived from user story), technical checks (deferred), coverage check (goal-backward). Falls back to standard UAT generation when mode is null/absent. User-story-format guard refuses to verify a mode:mvp phase with a non-user-story goal. Also updates docs/INVENTORY.md (56 references) and docs/INVENTORY-MANIFEST.json to register verify-mvp-mode.md added in Task 1. Per PRD vertical-mvp-slice Phase 3b (decisions Phase-3-B, Phase-3-Verify-Structure). * feat(gsd-verifier): add MVP Mode Verification section Narrows goal-backward verification to the user-story [outcome] clause when phase mode is mvp. References verify-mvp-mode.md. Preserves existing goal-backward methodology for non-MVP phases. User-story-format guard refuses to verify a mode:mvp phase with a non-user-story goal. * docs(changelog): announce MVP-mode UAT framing in verify-work (#2826) * feat(new-project): add Vertical MVP vs Horizontal Layers mode prompt Asks user at project init how to structure the project. Vertical MVP emits **Mode:** mvp on every initial roadmap phase (per-phase mode preserved per PRD Q1). Horizontal Layers falls back to standard template — no behavioral change for existing flows. Per PRD vertical-mvp-slice Phase 4 (decision Phase-4-Persistence). * feat(progress): add MVP-mode user-flow display When phase has **Mode:** mvp, progress renders user-flow status from PLAN.md task names alongside standard task progress. Tasks that aren't user-flow-shaped (technical-sounding) are filtered out of the user-flow sub-block. Falls back to standard display when mode is null/absent. Per PRD vertical-mvp-slice Phase 4 (decision Phase-4-Progress). * feat(stats): add MVP phase count summary Reads roadmap.analyze (which surfaces mode per phase from Phase 1) and emits 'Phases: N total | M MVP | K standard' summary line. Suppressed when MVP_COUNT == 0 to avoid clutter on non-MVP projects. Per PRD vertical-mvp-slice Phase 4. * feat(graphify): add MVP-mode visual differentiation MVP-mode phases render with #22c55e fill color AND ' (MVP)' label suffix — two-channel signaling for color-blind and grayscale renders. Standard phases unchanged. Per PRD vertical-mvp-slice Phase 4 (PRD Q5: distinct visual treatment). * docs(changelog): announce Phase 4 discovery & progress (#2826) * chore(release): bump dev to 1.50.0-canary.0 for first 1.50.0 canary Sets the base version that .github/workflows/canary.yml derives the canary tag from (strips suffix → base 1.50.0 → next available v1.50.0-canary.N). This kicks off the 1.50.0 release train, opened by the MVP/TDD/UAT vertical slice landed across PRs #2867, #2874, #2878, #2880, #2883. * docs: add CANARY stream README + v1.50.0-canary.1 release notes - docs/CANARY.md — explains the dev→@canary stream policy, install/rollback paths, and when (not) to install canary builds - docs/RELEASE-v1.50.0-canary.1.md — release notes for the first 1.50.0 canary cut: vertical MVP/TDD/UAT slice (#2867 + #2874 + #2878 + #2880 + #2883), opening the 1.50.0 train under PRD #2826 - docs/README.md — index entry + quick link for the canary stream * fix(ci/canary): publish gate checks dev branch, not main Four publish-step `if:` conditions in .github/workflows/canary.yml were checking `github.ref == 'refs/heads/main'`. Those steps (Tag and push, Publish to npm, Publish SDK to npm, Verify publish) therefore always skipped on every workflow_dispatch invocation since canary runs from dev, never main. The workflow's own header comment is unambiguous: `dev → @canary`. The gate was a copy-paste from release.yml (which correctly targets main for the @next/@latest streams) that was never corrected for the canary stream. This is why the 1.50.0-canary.1 publish hadn't materialized despite three green workflow runs. With the gate corrected, the next dispatch will actually publish. * ci(release-sdk): make release-sdk.yml dispatchable from the dev branch The workflow lives on main only, so the GitHub Actions "Use workflow from" dropdown doesn't list dev — meaning dev → @dev publishes can't be triggered from the dev branch directly. Add the file to dev so an operator can dispatch it with branch=dev and tag=dev. Per project release-stream policy: dev branch publishes canary (@dev). This is the stream that needs the file most, since main never publishes @dev itself (main does @next / @latest). File is byte-identical to main's release-sdk.yml — straight propagation, no behavioral change. Tracking issues #2925, #2929. * docs(mvp): canary-prep concept cleanup — CONTEXT.md, mvp-concepts index, --prd interaction (#3176) * chore(mvp): concept cleanup + cross-ref index for v1.50.0-canary.2 prep - CONTEXT.md gains 7 MVP domain terms (MVP Mode, User Story, Walking Skeleton, Vertical Slice, Behavior-Adding Task, MVP+TDD Gate, SPIDR Splitting) so the project glossary matches the shipped surface. - New get-shit-done/references/mvp-concepts.md indexes the six MVP reference files and concept-to-file map so agents and contributors can find the right canonical doc without grepping. - plan-phase.md Walking Skeleton block now documents that --mvp and --prd compose orthogonally on Phase 1; no precedence needed. - INVENTORY/INVENTORY-MANIFEST refreshed for the new reference (58 -> 59). No behavior change. Canary-prep cleanup ahead of v1.50.0-canary.2. Surfaced for follow-up (not in this PR): - MVP_MODE resolution shell block duplicated across plan-phase, execute-phase, verify-work workflows (needs a shared workflow-include mechanism; structural change). - Behavior-Adding Task predicate is prose-only; no shared utility. - User Story regex hardcoded in verify-work; would benefit from a central definition consumed by the verifier and the mvp-phase command. * chore(changeset): set PR number for mvp concept cleanup * feat(mvp): centralize resolution surfaces + fix SDK roadmap mode parity (#3178) Three new SDK query verbs replace the architectural duplication surfaced by the v1.50.0-canary.2 review against dev tip 12c4e565: phase.mvp-mode <N> [--cli-flag] Single canonical precedence resolver (CLI flag -> ROADMAP **Mode:** mvp -> workflow.mvp_mode config -> false). Replaces 4-8 lines of bash that were duplicated across plan-phase.md, execute-phase.md, verify-work.md, and progress.md. Returns {active, source, roadmap_mode, config_mvp_mode, cli_flag_present}. task.is-behavior-adding <plan-file> | --task-content <xml> Behavior-Adding Task predicate (tdd="true" + <behavior> block + non-test source files in <files>). Replaces prose-only specification in references/execute-mvp-tdd.md; gsd-executor agent now invokes the verb instead of re-inlining the three checks. Returns {is_behavior_adding, checks, reason}. user-story.validate <text> | --story <text> Owns the canonical User Story regex /^As a .+, I want to .+, so that .+\.$/ previously hardcoded in verify-work.md prose. Consumed by gsd-verifier (phase-goal guard) and /gsd-mvp-phase (interactive-prompt validation). Returns {valid, slots: {role, capability, outcome}, errors[]}. Bug fix bundled: sdk/src/query/roadmap.ts searchPhaseInContent now extracts the mode field from **Mode:**, restoring parity with roadmap.cjs:120-123. Without this, roadmap.get-phase --pick mode returned null on the native dispatch path even when the phase had **Mode:** mvp set, causing MVP_MODE to silently fall through to the config/false branch in every consuming workflow. The original PRs Phase 1 (#2885) shipped the CJS parser but the SDK port omitted the field; this fix brings them back to parity. Workflows + agents updated to call the verbs: - plan-phase.md, execute-phase.md, verify-work.md, progress.md call phase.mvp-mode (one line replaces the duplicated bash chains). - execute-phase.md MVP+TDD gate calls task.is-behavior-adding. - verify-work.md goal guard calls user-story.validate. - mvp-phase.md interactive prompt validates via user-story.validate. - gsd-executor agent references task.is-behavior-adding instead of prose. - gsd-verifier agent references user-story.validate instead of inlined regex. Tests: 24 new vitest tests in sdk/src/query/mvp.test.ts cover all three verbs + the regression. Two existing contract tests (progress, verify) updated to assert on the new verb shape. All 60 existing MVP contract tests pass; golden integration suite (38 + 42 tests) passes. Closes #3177 * fix(canary.2): unblock release gates for v1.50.0-canary.2 Run 25451329660 (Release SDK Bundle on dev, 2026-05-06T17:41) failed at the test-suite step with 3 deterministic content/structure gate failures, all attributable to the MVP umbrella integration in #3178 and the docs sweep in #3180. Failure 1: /gsd-mvp-phase undocumented in workflows/help.md - tests/bug-2954-help-md-slash-command-stubs.test.cjs requires every shipped commands/gsd/<X>.md to have a /gsd-<X> mention in help.md - PR #3180 updated docs/COMMANDS.md but missed help.md (which the AI agents load in-product) - Fix: add a /gsd-mvp-phase entry to help.md right before /gsd-plan-phase Failures 2 + 3: execute-phase.md (1727) and plan-phase.md (1714) over XL budget (1700) - PR #3178 added MVP-mode verb calls (phase.mvp-mode, task.is-behavior-adding, user-story.validate) to both workflow files, pushing them past 1700 lines - Fix: bump XL_BUDGET 1700 -> 1800 with inline comment pointing at the structural follow-up (extract MVP bodies to <workflow>/modes/mvp.md per the discuss-phase/modes/ precedent) - The structural extract is the right long-term fix but is bigger than canary unblock scope; will land in a follow-up after canary cycles Local verification: $ node --test tests/bug-2954-help-md-slash-command-stubs.test.cjs tests/workflow-size-budget.test.cjs tests 111 pass 111 fail 0 After this lands, re-trigger Release SDK Bundle on dev for v1.50.0-canary.2. * chore(changeset): set PR number for canary.2 unblock * fix(codex): generate-claude-md writes to AGENTS.md on Codex runtime When config.runtime === 'codex' or GSD_RUNTIME=codex, override the output target to AGENTS.md regardless of claude_md_path, so Codex projects no longer have GSD sections written to CLAUDE.md by mistake. Fixes both the CJS (gsd-tools) and SDK (profile-output.ts) paths. Explicit --output flags are still honoured in both paths. Closes #3163 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(plan-phase): remove agent: directive that caused OpenCode subagent dispatch On OpenCode, any command with `agent: <name>` in its frontmatter is auto-dispatched to a subagent context where the Agent tool is unavailable. plan-phase.md and mvp-phase.md both carried `agent: gsd-planner`, causing them to run inside gsd-planner's subagent context with no ability to spawn researcher/planner/checker subagents — the orchestrator fell back to inline execution for all three phases. Fix: remove `agent: gsd-planner` from both command files so they run in the main agent context. Also replace the stale `Task` tool in allowed-tools with `Agent` (the correct dispatcher tool name post-#3168 rename). Adds a structural regression test that parses YAML frontmatter of every commands/gsd/*.md file and asserts no command carries an `agent:` directive. Closes #3156 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(mvp): address CodeRabbit workflow and contract findings * fix(execute-phase): use registered state.update query command --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
9d5db87249 |
feat(#2975): adopt changeset-fragment workflow to eliminate CHANGELOG conflicts (#2978)
* feat(#2975): adopt changeset-fragment workflow to eliminate CHANGELOG conflicts Two PRs that both edit `### Fixed` in CHANGELOG.md always conflict on merge. Recently bit on #2960/#2972 in the same session — fix-the-conflict-and-rebase tax. Replace the shared-file model with per-PR fragment files that never share lines. Implementation built TDD per #2975, vertical slices with structured-IR assertions throughout: scripts/changeset/parse.cjs - fragment text → typed record + frozen FRAGMENT_ERROR enum (8 tests) scripts/changeset/render.cjs - fragments → structured IR with Keep-a-Changelog section ordering (2 tests) scripts/changeset/serialize.cjs - IR ↔ markdown round-trip pair (parse(serialize(ir)) === ir, 3 tests) scripts/changeset/cli.cjs - file-I/O wrapper with --json mode; reads .changeset/, folds into CHANGELOG.md, deletes consumed fragments. Idempotent. (1 test) scripts/changeset/lint.cjs - pure verdict (changedFiles, labels) → { ok, reason } via LINT_REASON enum. Honors `no-changelog` label. (5 tests) scripts/changeset/new.cjs - fragment scaffolder with random adjective-noun-noun filename. Tests assert via parseFragment round-trip. (3 tests) Total: 22 tests, all assertions on typed structured fields. No regex on text, no String#includes on file content. Lint clean across 356 test files. Supporting: .changeset/README.md - format spec + workflow docs .changeset/eager-hawks-rally.md - dogfood fragment for THIS PR (will be the first thing the new release tool consumes) .github/workflows/changeset-required.yml - CI: every PR runs lint.cjs package.json - npm run changeset, changelog:render, lint:changeset CONTRIBUTING.md - new "CHANGELOG Entries — Drop a Fragment" section between PR Guidelines and Testing Standards Closes #2975 * fix(#2975): address CodeRabbit findings on changeset workflow 7 valid findings (4 Major, 3 Minor); all addressed: scripts/changeset/parse.cjs - Preserve fragment body verbatim. Previously body.trim() ate intentional leading whitespace (code blocks, etc.); now trim() is used only for the emptiness check, and a single trailing newline is stripped (the editor-added one) so well-formed fragments round-trip byte-for-byte. Added a regression test asserting a code-block-leading body is preserved. scripts/changeset/cli.cjs - Validate flag values during argument parsing. parseArgs now returns { ok, opts | error }; rejects `--repo` etc. with no following value or with another flag as the value. main() surfaces the error message before exiting 2. - Handle post-write fragment-deletion failures. After CHANGELOG.md is written, any unlink failure is captured into a structured deleteFailures list with reason 'fail_fragment_delete'; cmdRender returns exitCode=1 with the partial-failure detail instead of leaving the changelog updated and fragments behind (which would cause double-consumption on rerun). scripts/changeset/lint.cjs - Treat CHANGELOG.md as a linted user-facing path. Direct edits to CHANGELOG.md (the bypass route around the new workflow) now fail the lint with FAIL_MISSING_FRAGMENT. Added a regression test for that case. - Use cp.execFileSync instead of cp.execSync for the git diff call. Eliminates the shell-interpolation surface on GITHUB_BASE_REF; git's own arg parser remains the validator. scripts/changeset/new.cjs - Atomic fragment creation. existsSync() + writeFileSync was racy under concurrent invocations. Now writeFileSync uses { flag: 'wx' } which fails EEXIST on collision; the random-name retry loop catches EEXIST and re-rolls. Throws explicitly after 16 attempts rather than silently overwriting. .changeset/README.md - Add language tag `md` to the format example fence (markdownlint MD040). All 25 changeset tests pass; lint clean (356 test files, 0 violations). * fix(#2975): sanitize --type and validate flag values in new.cjs (CR fixes) Two CR findings on scripts/changeset/new.cjs: 1. (Minor) `type` was embedded in frontmatter without sanitization. A newline in the value (e.g. `--type 'Fixed\ntype: Added'`) would corrupt the fragment. scaffoldFragment now validates `type` against the Keep-a-Changelog ALLOWED_TYPES set BEFORE writing — same set parse.cjs uses on consume. Throws with a typed error referencing the allowed values; tests cover the newline case + 4 other non-allowed values. 2. (Minor) `--repo` (and other value-taking flags) without a value silently set opts.repo to undefined, which produced a cryptic ERR_INVALID_ARG_TYPE deep inside path.join. parseArgs now mirrors the cli.cjs convention: returns { ok, opts | error }, validates that the next token exists and is not itself another flag, and surfaces a precise "missing value for --repo" message before exit. Added 3 tests: missing-trailing-value, flag-as-value, well-formed. 29 tests pass across the changeset suite (4 new regression tests). |
||
|
|
006cdafe8f |
ci(drift): enforce alias freshness checks in CI and contributor flow (#2910)
Merging alias-drift guardrails and local hook hardening. |
||
|
|
e81592878e |
feat(#2789): trim skill description anti-patterns; enforce 100-char budget (#2823)
* feat(#2789): trim skill description anti-patterns; enforce 100-char budget - Trim descriptions in all commands/gsd/*.md files over 100 chars - Remove flag documentation from descriptions (belongs in argument-hint) - Remove Triggers: keyword stuffing - Add scripts/lint-descriptions.cjs — fails on descriptions > 100 chars - Add npm script: lint:descriptions - Add tests/enh-2789-description-budget.test.cjs Closes #2789 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * docs(#2789): add CHANGELOG entry for description budget lint * docs(#2789): update COMMANDS.md descriptions; add skill description standards note Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
9e58c45ea1 |
fix(#2791): GSD_WORKSTREAM env var respected by gsd-sdk query + gsd-tools bin alias (#2821)
* fix(#2791): GSD_WORKSTREAM env var respected by gsd-sdk query + gsd-tools bin alias Two fixes for gsd-sdk binary issues: **Issue 1 — Binary name collision:** Both `get-shit-done-cc` and `@gsd-build/sdk` declare `bin: { "gsd-sdk": ... }`. Added `"gsd-tools": "bin/gsd-sdk.js"` to `package.json` bin so users with the collision can invoke `gsd-tools query <cmd>` as a conflict-free alternative. **Issue 2 — Query registry not workstream-aware:** `gsd-sdk query` commands ignored `GSD_WORKSTREAM` env var, always reading from the root `.planning/` even when a workstream was active. `gsd-tools.cjs` reads `GSD_WORKSTREAM` via `planningDir()`, so all ~35 `gsd-sdk query` call sites in workflow files were broken in workstream-scoped projects. Fix: added env var fallback in `sdk/src/cli.ts` — when `--ws` is not provided, `GSD_WORKSTREAM` is used (with name validation; invalid values are silently ignored, matching CJS behaviour). Regression test: `tests/bug-2791-sdk-workstream-env.test.cjs` Closes #2791 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(#2791): address CodeRabbit — precedence test, invalid env fallback assertion, bash fence --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
f983c95ffc |
Release/1.39.0-rc.4 (#2797)
* chore: bump version to 1.39.0 for release * chore: bump to 1.39.0-rc.1 * chore: bump to 1.39.0-rc.2 * chore: bump to 1.39.0-rc.3 * chore: bump to 1.39.0-rc.4 * docs: add v1.39.0-rc.4 release notes Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
f3685d9173 | 1.38.5 | ||
|
|
a5a2d44121 | 1.38.4 | ||
|
|
73abae60f0 | 1.38.3 | ||
|
|
aeef87de7f |
docs(test-standards): enforce no-source-grep rule with CI linter + CONTRIBUTING.md (#2700)
* docs(test-standards): enforce no-source-grep rule with CI linter + update CONTRIBUTING.md
Adds scripts/lint-no-source-grep.cjs — a static linter that detects readFileSync
on .cjs source files in tests without an allow-test-rule annotation. Wires it
into CI as a new lint-tests job in test.yml and as npm run lint:tests.
Resolves all 9 existing violations across the test suite:
- Rewrites workspace routing tests (3) as behavioral runGsdTools calls that
verify each command is router-recognized (exit != "Unknown init workflow")
- Adds allow-test-rule annotations with explanatory comments to 7 legitimate
structural tests: architectural invariants (locking, orphan-worktree),
structural regression guards (milestone-regex-global), docs-parity
(config-field-docs), integration-test-input (copilot-install), and
structural-implementation-guards (bug-1891, discuss-mode)
Updates CONTRIBUTING.md Testing Standards section with:
- "Prohibited: Source-Grep Tests" section with the before/after pattern,
root cause analysis of why it breaks (commit
|
||
|
|
0a049149e1 |
fix(sdk): decouple from build-from-source install, close #2441 #2453 (#2457)
* fix(sdk): decouple SDK from build-from-source install path, close #2441 and #2453 Ship sdk/dist prebuilt in the tarball and replace the npm-install-g sub-install with a parent-package bin shim (bin/gsd-sdk.js). npm chmods bin entries from a packed tarball correctly, eliminating the mode-644 failure (#2453) and the full class of NPM_CONFIG_PREFIX/ignore-scripts/ corepack/air-gapped failure modes that caused #2439 and #2441. Changes: - sdk/package.json: prepublishOnly runs `rm -rf dist && tsc && chmod +x dist/cli.js` (stale-build guard + execute-bit fix at publish time) - package.json: add "gsd-sdk": "bin/gsd-sdk.js" bin entry; add sdk/dist to files so the prebuilt CLI ships in the tarball - bin/gsd-sdk.js: new back-compat shim — resolves sdk/dist/cli.js relative to the package root and delegates via `node`, so all existing PATH call sites (slash commands, agents, hooks) continue to work unchanged (S1 shim) - bin/install.js: replace installSdkIfNeeded() build-from-source + global- install dance with a dist-verify + chmod-in-place guard; delete resolveGsdSdk(), detectShellRc(), emitSdkFatal() helpers now unused - .github/workflows/install-smoke.yml: add smoke-unpacked job that strips execute bit from sdk/dist/cli.js before install to reproduce the exact #2453 failure mode - tests/bug-2441-sdk-decouple.test.cjs: new regression tests asserting all invariants (no npm install -g from sdk/, shim exists, sdk/dist in files, prepublishOnly has rm -rf + chmod) - tests/bugs-1656-1657.test.cjs: update stale assertions that required build-from-source behavior (now asserts new prebuilt-dist invariants) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * chore(release): bump to 1.38.2, wire release.yml to build SDK dist - Bump version 1.38.1 -> 1.38.2 for the #2441/#2453 fix shipped in 0f6903d. - Add `build:sdk` script (`cd sdk && npm ci && npm run build`). - `prepublishOnly` now runs hooks + SDK builds as a safety net. - release.yml (rc + finalize): build SDK dist before `npm publish` so the published tarball always ships fresh `sdk/dist/` (kept gitignored). - CHANGELOG: document 1.38.2 entry and `--sdk` flag semantics change. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ci: build SDK dist before tests and smoke jobs sdk/dist/ is gitignored (built fresh at publish time via release.yml), but both the test suite and install-smoke jobs run `bin/install.js` or `npm pack` against the checked-out tree where dist doesn't exist yet. - test.yml: `npm run build:sdk` before `npm run test:coverage`, so tests that spawn `bin/install.js` don't hit `installSdkIfNeeded()`'s fatal missing-dist check. - install-smoke.yml (both smoke and smoke-unpacked): build SDK before pack/chmod so the published tarball contains dist and the unpacked install has a file to strip exec-bit from. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(sdk): lift SDK runtime deps to parent so tarball install can resolve them The SDK's runtime deps (ws, @anthropic-ai/claude-agent-sdk) live in sdk/package.json, but sdk/node_modules is NOT shipped in the parent tarball — only sdk/dist, sdk/src, sdk/prompts, and sdk/package.json are. When a user runs `npm install -g get-shit-done-cc`, npm installs the parent's node_modules but never runs `npm install` inside the nested sdk/ directory. Result: `node sdk/dist/cli.js` fails with ERR_MODULE_NOT_FOUND for 'ws'. The smoke tarball job caught this; the unpacked variant masked it because `npm install -g <dir>` copies the entire workspace including sdk/node_modules (left over from `npm run build:sdk`). Fix: declare the same deps in the parent package.json so they land in <pkg>/node_modules, which Node's resolution walks up to from <pkg>/sdk/dist/cli.js. Keep them declared in sdk/package.json too so the SDK remains a self-contained package for standalone dev. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(lockfile): regenerate package-lock.json cleanly The previous `npm install` run left the lockfile internally inconsistent (resolved esbuild@0.27.7 referenced but not fully written), causing `npm ci` to fail in CI with "Missing from lock file" errors. Clean regen via rm + npm install fixes all three failed jobs (test, smoke, smoke-unpacked), which were all hitting the same `npm ci` sync check. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(deps): remove unused esbuild + vitest from root devDependencies Both were declared but never imported anywhere in the root package (confirmed via grep of bin/, scripts/, tests/). They lived in sdk/ already, which is the only place they're actually used. The transitive tree they pulled in (vitest → vite → esbuild 0.28 → @esbuild/openharmony-arm64) was the root of the CI npm ci failures: the openharmony platform package's `optional: true` flag was not being applied correctly by npm 10 on Linux runners, causing EBADPLATFORM. After removal: 800+ transitive packages → 155. Lockfile regenerated cleanly. All 4170 tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(sdk): pretest:coverage builds sdk; tighten shim test assertions Add "pretest:coverage": "npm run build:sdk" so npm run test:coverage works in clean checkouts where sdk/dist/ hasn't been built yet. Tighten the two loose shim assertions in bug-2441-sdk-decouple.test.cjs: - forwards-to test now asserts path.resolve() is called with the 'sdk','dist','cli.js' path segments, not just substring presence - node-invocation test now asserts spawnSync(process.execPath, [...]) pattern, ruling out matches in comments or the shebang line Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: address PR review — pretest:coverage + tighten shim tests Review feedback from trek-e on PR 2457: 1. pretest:coverage + pretest hooks now run `npm run build:sdk` so `npm run test[:coverage]` in a clean checkout produces the required sdk/dist/ artifacts before running the installer-dependent tests. CI already does this explicitly; local contributors benefit. 2. Shim tests in bug-2441-sdk-decouple.test.cjs tightened from loose substring matches (which would pass on comments/shebangs alone) to regex assertions on the actual path.resolve call, spawnSync with process.execPath, process.argv.slice(2), and process.exit pattern. These now provide real regression protection for #2453-class bugs. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix: correct CHANGELOG entry and add [1.38.2] reference link Two issues in the 1.38.2 CHANGELOG entry: - installSdkIfNeeded() was described as deleted but it still exists in bin/install.js (repurposed to verify sdk/dist/cli.js and fix execute bit). Corrected the description to say 'repurposes' rather than 'deletes'. - The reference-link block at the bottom of the file was missing a [1.38.2] compare URL and [Unreleased] still pointed to v1.37.1...HEAD. Added the [1.38.2] link and updated [Unreleased] to compare/v1.38.2...HEAD. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(sdk): double-cast WorkflowConfig to Record for strict tsc build TypeScript error on main (introduced in #2611) blocks `npm run build` in sdk/, which now runs as part of this PR's tarball build path. Apply the double-cast via `unknown` as the compiler suggests. Same fix as #2622; can be dropped if that lands first. * test: remove bug-2598 test obsoleted by SDK decoupling The bug-2598 test guards the Windows CVE-2024-27980 fix in the old build-from-source path (npm spawnSync with shell:true + formatSpawnFailure diagnostics). This PR removes that entire code path — installSdkIfNeeded no longer spawns npm, it just verifies the prebuilt sdk/dist/cli.js shipped in the tarball. The test asserts `installSdkIfNeeded.toString()` contains a formatSpawnFailure helper. After decoupling, no such helper exists (nothing to format — there's no spawn). Keeping the test would assert invariants of the rejected architecture. The original #2598 defect (silent failure of npm spawn on Windows) is structurally impossible in the shim path: bin/gsd-sdk.js invokes `node sdk/dist/cli.js` directly via child_process.spawn with an explicit argv array. No .cmd wrapper, no shell delegation. --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Tom Boucher <trekkie@nomorestars.com> |
||
|
|
337e052aa9 | chore: bump version to 1.38.2 for hotfix | ||
|
|
29ea90bc83 | chore: bump version to 1.38.1 for hotfix | ||
|
|
0c6172bfad | chore: finalize v1.38.0 | ||
|
|
c69ecd975a | chore: bump to 1.38.0-rc.1 | ||
|
|
341bb941c6 | chore: bump version to 1.38.0 for release | ||
|
|
fc1fa9172b |
fix(install): build gsd-sdk from in-repo sdk/ source, not stale npm package
PR #2386 v1 installed the published @gsd-build/sdk from npm, which ships an older version that lacks query handlers needed by current workflows. Every GSD release would drift further from what the installer put on PATH. This commit rewires installSdkIfNeeded() to build from the in-repo sdk/ source tree instead: 1. cd sdk && npm install (build-time deps incl. tsc) 2. npm run build (tsc → sdk/dist/) 3. npm install -g . (global install; gsd-sdk on PATH) Each step is a hard gate — failures warn loudly and point users at the manual equivalent command. No more silent drift between installed SDK and the rest of the GSD system. Root package.json `files` now ships sdk/src, sdk/prompts, sdk/package.json, sdk/package-lock.json, and sdk/tsconfig.json so npm-registry installs also carry the source tree needed to build gsd-sdk locally. Also fixes a blocking tsc error in sdk/src/event-stream.ts:313 — the cast to `Array<{ type: string; [key: string]: unknown }>` needed a double-cast via `unknown` because BetaContentBlock's variants don't carry an index signature. Runtime-neutral type-widening; sdk vitest suite unchanged (1256 passing; the lone failure is a pre-existing integration test that requires external API access). Updates the #1657/#2385 regression test to assert the new build-from-source path (path.resolve(__dirname, '..', 'sdk') + `npm run build` + `npm install -g .`) plus a new assertion that root package.json files array ships sdk source. Refs #2385 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
4cbe0b6d56 | 1.37.1 | ||
|
|
9e8257a3b1 | 1.37.0 |