* fix(#2654): bump js-yaml past the merge-key DoS advisory
js-yaml was pinned ^4.2.0, inside the vulnerable 4.0.0 - 4.2.0 range of
GHSA-52cp-r559-cp3m (YAML merge-key chains force quadratic CPU, CVSS
7.5). Bump to ^4.2.1; the lockfile resolves 4.3.0.
It is a devDependency with no reachability from shipped runtime code
under gsd-core/bin/ or src/ — the consumers are scripts/workflow-policy.cjs
and five test files. The path worth closing is CI: workflow-policy parses
workflow frontmatter during the Tests workflow, and on a fork PR that
frontmatter is attacker-controlled.
Scoped to js-yaml only. The remaining brace-expansion advisory is not
fixed by this and is deliberately left alone: npm audit fix takes the
high count from 1 to 5, because the three copies nested under eslint
land on 1.1.16, which still compares inside the advisory's <=5.0.7
range. Closing it needs an eslint major or an overrides entry.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* chore(#2654): backfill changeset pr number to 2655
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* chore(#2496): clear five newly-disclosed production advisories
The `#3588: npm audit --omit=dev reports zero advisories` gate began
failing mid-release. The v1.8.0 finalize dry run was green at 17:27:27Z;
GHSA-frvp-7c67-39w9 and GHSA-xgm2-5f3f-mvvc published at 18:17:25Z and
18:18:13Z, with fast-uri and two further hono advisories in the same
window. Nothing in the tree changed — the advisory database did.
All five arrive transitively through the one declared dependency
@anthropic-ai/claude-agent-sdk -> @modelcontextprotocol/sdk.
Two-part fix, both following existing repo precedent:
1. `npm audit fix --omit=dev` (no --force) re-resolves fast-uri and hono
inside their already-declared ranges. package.json untouched — the
same approach as .changeset/witty-badgers-hum.md (body-parser) and
.changeset/archived/fix-3588-npm-audit-clean.md. Clears the only high.
2. overrides["@hono/node-server"] = ">=2.0.5" for the remaining chain,
which cannot resolve in-range (^1.19.9 cannot reach 2.0.5) because
@modelcontextprotocol/sdk@1.29.0 is already latest and still declares
the vulnerable range. Extends the block that already pins qs and
body-parser. Resolves to 2.0.11.
Bumping @anthropic-ai/claude-agent-sdk to ^0.3.x was tested and REJECTED:
0.3.216 moves @modelcontextprotocol/sdk to peerDependencies, which npm
auto-installs, so the chain survives and resolution pulls extra
advisories — 5 vulnerabilities including a high, versus 4 moderate.
Forced major sits under a dependency no tracked source imports (see
src/mcp-server.cts:22 — the JSON-RPC loop is hand-rolled precisely to
avoid the MCP SDK), so runtime risk is minimal. Revisit once upstream
ships a release depending on patched @hono/node-server.
npm audit --omit=dev: found 0 vulnerabilities.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* chore(#2496): add changeset fragment for the advisory clearance
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* test(#2444): failing-first regression for checkpoint:* plan-structure validation
Add acceptance-criteria tests covering the three canonical checkpoint task
types (human-verify, decision, human-action) plus an unknown-subtype
forward-compat case. Each canonical type must pass verify plan-structure
when it carries its type-specific required fields (per
gsd-core/references/checkpoints.md), and must be flagged when those
fields are missing. Non-checkpoint tasks keep the existing
<action>/<verify>/<done>/<files> requirements unchanged (AC3 regression
guards).
The existing 'errors when checkpoint task but autonomous is true' fixture
is updated to use the canonical checkpoint:human-verify triple
(<what-built>/<how-to-verify>/<resume-signal>) so it does not collide
with the new per-type validator; the assertion (autonomous is not false)
is unchanged.
* fix(#2444): branch plan-structure validation on task type=checkpoint:*
cmdVerifyPlanStructure unconditionally required <action>/<verify>/<done>/
<files> on every task, so every checkpoint:* task — which uses the
checkpoint convention's type-specific fields instead — was reported as a
structural error. Checkpoint-heavy phases produced walls of false findings.
The fix introduces two pure helpers in verify.cts:
- extractPlanTaskInfos(content): single ReDoS-safe pass over
<task ...>...</task> blocks that captures BOTH the opening-tag
attribute string (so the type= selector is not lost, as it is with
extractTaggedBlocks) and the body, returning a typed PlanTaskInfo.
- validatePlanTaskStructure(task): branches on the task's type.
checkpoint:human-verify requires <what-built>/<how-to-verify>/
<resume-signal> (the canonical triple).
checkpoint:decision requires <decision>/<options>/<resume-signal>.
checkpoint:human-action requires <action>/<instructions>/
<verification>/<resume-signal>.
Unknown checkpoint:* subtypes require only the universal
<resume-signal> (forward-compat). All other types keep the historical
<action>/<verify>/<done>/<files> requirements unchanged.
Canonical reference: gsd-core/references/checkpoints.md. Per-type field
sets validated against the documented templates in
agents/gsd-planner.md and gsd-core/templates/phase-prompt.md.
* fix(#2444): re-resolve body-parser to 2.3.0 in lockfile (GHSA-v422-hmwv-36x6)
GHSA-v422-hmwv-36x6 (body-parser DoS via invalid limit value, low severity,
published 2026-07-20T23:23:26Z) made tests/npm-integrity-gate.test.cjs
(#3588: root workspace production tree has no advisories) fail any subsequent
npm audit --omit=dev. The advisory affects body-parser >=2.0.0 <2.3.0 pulled
transitively via @anthropic-ai/claude-agent-sdk -> @modelcontextprotocol/sdk
-> express -> body-parser@2.2.2.
express@5.2.1 already declares body-parser as ^2.2.1, so 2.3.0 is a valid
re-resolution within express's own compatibility range — no override needed.
Regenerated the lockfile via 'npm audit fix --omit=dev' which re-resolves
transitive deps within their declared ranges; package.json is unchanged.
Verified: npm audit --omit=dev reports 0/0/0/0/0 advisories; body-parser
now reads as 2.3.0 in 'npm ls body-parser --omit=dev'.
* test(#2444): close review gap-closure tests + harden type-attr charset
Orthogonal review (code-review + security-review subagents) returned APPROVE
on Standards and Spec. Per the playbook's zero-tolerance policy, address
every Low finding:
Spec gap-closures:
- AC3 verbatim: add explicit <done> and <files> regression tests for
non-checkpoint tasks (pre-existing tests only covered <action> and
<verify>).
- AC2: add checkpoint:decision missing <decision>, checkpoint:human-action
missing <action>, checkpoint:human-action missing <verification> cases
(the implementation enforces all of these; only one missing-field case
per type was previously tested).
- Remove the duplicate 'returns error for nonexistent file' test that
leaked into the new describe block from the insertion edit.
Security hardening (Low-sev, defense-in-depth):
- Tighten the task type= attribute extractor in src/verify.cts from
[^"'>\s]+ to [\w:-]+ so a hostile type= attribute cannot carry
markup fragments (e.g. type=evil<fragment) into the verifier's typed
JSON output. All legitimate type values (auto, tracer, manual,
checkpoint:human-verify, checkpoint:decision, checkpoint:human-action,
checkpoint:tdd-review) match the tighter charset.
- Add adversarial regression test asserting type=evil<fragment surfaces
as 'evil' (capture stops at '<'), with no markup chars (< > ( ) &)
in the surfaced type field.
* docs(changeset): add Fixed fragments for #2444 PR
Two fragments:
- sturdy-jays-tumble.md: the verify plan-structure checkpoint fix
- witty-badgers-hum.md: the body-parser 2.3.0 re-resolution
PR number backfilled to 0 placeholder per CLAUDE.md 'PR Number Handling';
will backfill to the real PR number immediately after gh pr create returns.
* docs(changeset): backfill PR number to 2473
Per CLAUDE.md 'PR Number Handling': backfill the placeholder pr:0 with the
real PR number returned by gh pr create.
* fix(release): finalize job calls sync-next-version.cjs to bump next after final release (#2423)
The release pipeline's 'finalize' job shipped X.Y.0 to npm 'latest' and
merged to main, but never bumped 'next' to match. 'scripts/sync-next-version.cjs'
exists exactly for this — its docstring promises to run 'for every release
type (rc / hotfix / final)' — but it was wired only into the 'rc' job
(release.yml:479), not 'finalize'. As a result, after 1.7.0 shipped on
2026-07-15, 'next' stayed at 1.7.0-rc.6 and every npm script banner on
'next' (and feature branches cut from it) reported the stale rc version.
Regression of #1104 — closed incomplete (covered rc only, not final).
This patch:
- adds a 'Sync next branch to the published release' step to the
'finalize' job, mirroring the rc job's pattern at line 479 (uses
VERSION from inputs.version rather than PRE_VERSION from steps.prerelease,
since finalize does not run the prerelease step);
- gates it on !inputs.dry_run + continue-on-error:true (matches rc);
- adds tests/release-finalize-syncs-next-version.test.cjs — a structural
YAML assertion that fails against the pre-fix workflow and passes after.
Failing-first demonstrated during development; the test parses job blocks
by indentation rather than grep so it stays valid as the file grows.
Root-cause diagnosis: scripts/sync-next-version.cjs:14 docstring admits
'used by release.yml's rc job, which has no next-targeting PR of its own'.
release.yml:506-685 (finalize job) had no sync-next-version step before
this patch. Every prior rc.N release has a matching 'chore: sync next
package version to 1.7.0-rc.N' commit; there is no such commit for 1.7.0.
* chore(release): sync next package version to 1.7.0 (#2423)
Replays the canonical 'chore: sync next package version to <v>' commit
that the release pipeline's rc job auto-produces via scripts/sync-next-version.cjs,
for the 1.7.0 final release that shipped on 2026-07-15 (commit dd4c90f82
'chore: finalize v1.7.0' on main). Without this, 'next' (and every feature
branch cut from it) carried 1.7.0-rc.6 indefinitely and reported it in
every npm script banner (e.g. 'lint:ci').
Bumps 43 synchronized manifests via the npm 'version' lifecycle hook
(scripts/sync-manifest-versions.cjs --stage + scripts/gen-capability-registry.cjs
--write), matching the file set of commit 27f69cc48 ('chore: sync next
package version to 1.7.0-rc.6') and commit dd4c90f82 ('chore: finalize
v1.7.0').
This is the immediate Layer-1 repair for #2423. Layer-2 (prevent recurrence)
is the workflow patch in the previous commit; Layer-3 (regression test)
ships with it. Future X.Y.0 final releases will produce this commit
automatically once the workflow fix lands.
* test(release): tighten #2423 dry-run gate assertion to the sync step
Code review of fix/2423 found that test #3 ('gates sync-next-version on
!inputs.dry_run') asserted too loosely: it scanned the entire finalize
block for any '!inputs.dry_run' line, so it would still pass if the
gate were stripped from the sync-next-version step specifically — the
exact regression the test name promises to catch. The finalize job has
multiple steps with their own !inputs.dry_run gates (e.g. Verify
publish), so the loose version masked the very bug it claimed to detect.
Tighten by extracting the specific YAML step block containing
'scripts/sync-next-version.cjs' and asserting the gate appears within
THAT step's lines, not anywhere in the job. Verified the tightened test:
- PASSES against the post-fix workflow (sync step has its own gate)
- FAILS when the sync step's gate is stripped (even when other steps
in finalize retain their own !inputs.dry_run gates) — the exact
regression that previously slipped through
Adds extractStepBlockContaining(jobBlock, marker) helper alongside the
existing extractJobBlock(text, jobName). Reuses the same indentation-
based parsing, so it stays valid as the file grows.
* chore(changeset): backfill pr:2437 in .changeset/sturdy-ibex-jump.md
CLAUDE.md changeset convention: 'Use placeholder pr:0 during initial commit.
Backfill immediately after gh api POST /pulls returns the real number.'
PR #2437 created from branch fix/2423-release-finalize-sync-next-version.
* fix(#2423): add see #2423 to allow-test-rule exemption per ADR-456
CI lint-allow-test-rule-refs failed on PR #2437: ADR-456 requires new
allow-test-rule exemptions added after the ADR's acceptance to include a
tracking issue number in the comment, in the form
// allow-test-rule: <reason> (see #NNN)
The exemption added in commit 976c8b0a2 lacked this ref. Fixed.
Verified locally:
node scripts/lint-allow-test-rule-refs.cjs
→ ok lint-allow-test-rule-refs: 173 grandfathered exemption(s) tracked, no novel untracked offenders
Move next onto the -dev prerelease stream after the v1.5.0 release per
ADR-660 (next must not rest at the last-released version).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
js-yaml <= 4.1.1 has a quadratic-complexity DoS in merge-key handling via
repeated aliases (GHSA-h67p-54hq-rp68 / CVE-2026-53550, medium). Patched in
4.2.0. js-yaml is dev-only here (direct devDependency + deduped transitive via
eslint/@eslint/eslintrc), so shipped users are not exposed; the bump clears
Dependabot alert #9 and patches the floor. The existing ^4.1.1 range already
permitted 4.2.0 — this only refreshes the stale lockfile pin. npm audit: 0
vulnerabilities.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The direct production dependency ws was pinned to 8.20.1, in the vulnerable
range of GHSA-96hv-2xvq-fx4p (high — memory-exhaustion DoS). The freshly
disclosed advisory turned the #3588 `npm audit --omit=dev reports zero
advisories` CI gate red repo-wide (next + every open PR). Bump to the
patched ^8.21.0 (backward-compatible). npm audit --omit=dev now reports 0.
Closes#1274
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#381): make gsd_run launcher reachable in fresh-shell-per-block runtimes
On runtimes that execute each fenced bash block in a separate shell process
(e.g. Claude Code — documented behavior: each Bash command is a separate
process; inline shell functions and exported vars do not persist between
calls), the once-per-file gsd_run() function was undefined in every block
after the preamble block, and the call was swallowed by
`2>/dev/null || echo "{}"` into silent empty state.
Fix (budget-neutral session-level resolution):
- Ship gsd-core/bin/gsd_run, a POSIX sh wrapper that symlink-resolves its own
location and execs the co-located gsd-tools.cjs. Exposed on PATH via the npm
`bin` field (global installs) and shipped to local installs via the recursive
gsd-core/ copy.
- The per-file launcher preamble now appends `export PATH='<bindir>':"$PATH"`
to the file named by $CLAUDE_ENV_FILE (Claude Code's documented
env-persistence mechanism) so later fresh-shell blocks resolve gsd_run from
PATH. Guarded as a strict no-op when CLAUDE_ENV_FILE is unset; the inline
gsd_run() definition remains the fallback for all other runtimes. The
single-quoted dir neutralizes shell metacharacters at source time.
- Propagated via scripts/sync-runtime-launcher.cjs to all launcher-using files.
- XL workflow byte budget 93000 -> 93200 (the ~130B clause pushes plan-phase.md
to 93135; legitimate content growth, ratchet-up per #717).
Regression tests (I)/(J) in runtime-launcher-parity.test.cjs cover wrapper
delegation and end-to-end PATH persistence (sourcing the env file with a
space-bearing install path).
Known limitation: an install path containing a literal single-quote yields a
malformed env-file line and falls back to the status quo (no regression);
rare on sanitized home directories.
Closes#381
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* docs(#381): add changeset for gsd_run fresh-shell reachability fix
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* test(#381): scope test (J) bare-PATH execution to POSIX (Windows Git Bash exec bit)
Windows Git Bash (msys2) does not honor Node's chmod exec bit for
PATH-executing extension-less scripts, so the bare `gsd_run` command lookup
failed there even though the env-file PATH persistence was correct. The
env-file content assertions (the fix's actual cross-platform logic) still run
on every platform; only the final source-and-execute sub-step is gated to
non-win32. Global installs on Windows are covered by npm's generated bin shim.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Same moderate hono advisory (GHSA-3hrh-pfw6-9m5x et al.) that blocked the 1.3.1
hotfix is present on next (was 4.12.19); bump to keep the npm-audit gate green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
After the 1.3.0 release, next moves onto the -dev prerelease stream so the
trunk self-identifies as unreleased (floor = next patch). First manual
exercise of the ADR-660 release model.
Refs #660
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#663): resolve open CodeQL/Dependabot security alerts
- ReDoS: collapse ambiguous nested quantifiers in phase-heading regexes
(verify/validate/commands) and the plan-filename lookahead (phase) to
provably-equivalent non-backtracking forms
- prototype pollution: guard __proto__/constructor/prototype in setConfigValue
- remove dead no-op .replace(/-/g,'-') in phase.cts
- escape all regex metachars in bug-2839 test
- add contents:read permissions to security-scan + install-smoke workflows
- pin qs >= 6.15.2 via overrides (DoS GHSA)
- broaden prompt-injection allowlist to translated security-model docs
Closes#663
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* test(#663): regression tests for prototype-pollution guard and roadmap-phase ReDoS
Behavioral test that config-set rejects __proto__/constructor/prototype keys
without polluting Object.prototype, plus a ReDoS guard (timing-bound) and
behavior-preservation assertions for the collapsed phase-heading regexes.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* test(#663): make ReDoS regression assert structured result, not elapsed time
Replace elapsed-time assertions (which tripped local/no-elapsed-assertion
ESLint rule and were unsound for synchronous ReDoS) with structured-result
assertions on adversarial inputs: assert that malformed phase headings/
unchecked-item lines without a terminating colon/space yield an empty Set,
which is both the correct behavior and an exercise of the fixed linear regex
on the catastrophic-backtracking input shape.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* chore(#663): add Security changeset fragment for #665
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* test(#663): fold prototype-pollution regression into config.test.cjs
The standalone bug-663-config-prototype-pollution.test.cjs was a 9th
config-module test file, tripping lint-test-file-count (the allowlist is
ratcheted and must not grow). Consolidated into config.test.cjs instead.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* chore(#604): rename get-shit-done/ runtime directory to gsd-core/
Renames the installed runtime directory `get-shit-done/` to `gsd-core/` so the
on-disk name matches the package (`@opengsd/gsd-core`), repo, and binary
(`gsd-tools`). The npm package name and binary are unchanged; npx/npm consumers
are unaffected.
Mechanical (bulk, ~90% of the diff):
- `git mv get-shit-done gsd-core`
- Swept path/identifier references across the repo via
`perl -pe 's/get-shit-done(?!-\w)/gsd-core/g'`. The negative lookahead
preserves the five legitimate slug variants that are NOT the directory:
get-shit-done-{OLD,cc,classic,cli,redux} (old package/repo names).
- Build/manifest wiring: package.json (bin, files, coverage globs),
tsconfig.build.json (outDir), ~86 .gitignore build-output entries,
stryker.config.mjs, scan-ignore files, install.js path strings.
- Frozen (not rewritten): CHANGELOG.md history; translated docs
(README.<locale>.md and docs/{ja-JP,ko-KR,pt-BR,zh-CN}/).
New logic (review here):
- src/installer-migrations/003-rename-get-shit-done-to-gsd-core.cts: a proper
ADR-0008 installer migration. On upgrade it walks the legacy
`~/.claude/get-shit-done/` tree, classifies each file via the prior install
manifest, and emits remove-managed / backup-and-remove for managed files
while PRESERVING unknown user-added files. Symlink-safe (skips a symlinked
root and symlinked entries; bounds-checks every path under configDir). The
framework rolls back on install failure. Emptied dirs may remain (framework
has no recursive dir-removal primitive) — documented.
- scripts/lint-legacy-dir-name.cjs: CI regression guard forbidding the bare
`get-shit-done` directory token (split token to avoid self-match; case-
insensitive; `(?!-\w)` lookahead allows the slug variants; allowlists
CHANGELOG, translated docs, and `gsd-allow-legacy-name` marker lines).
Wired into the lint-tests CI job.
- Restored scripts/lint-package-identity-drift.cjs detection regexes (the
mechanical sweep had wrongly rewritten the old-name patterns it exists to
detect) and marked them as intentional legacy references.
- TDD tests for the migration and the guard; do.md slash-command guard regex
tightened so a `/gsd-core/bin` path segment is not mistaken for a command;
changeset + docs/installer-migrations.md row added.
Breaking: the installed runtime path moves `~/.claude/get-shit-done/` ->
`~/.claude/gsd-core/`. Migration 003 removes the stale legacy dir's managed
files (preserving user files) on upgrade. Users with custom hooks/configs
hardcoding the old path must update them.
Closes#604
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#604): unsweep pending changesets + allowlist injection-example docs
CI fixes for the rename PR:
- Do not sweep pending .changeset/*.md (ephemeral release-note fragments,
like CHANGELOG); reverted those body edits so 5 pre-existing malformed
fragments (missing type/pr) no longer enter the PR diff and trip docs-lint.
Allowlisted .changeset/ in the legacy-name guard accordingly.
- Allowlisted TEST-EXAMPLES.md and docs/explanation/security-model.md in
prompt-injection-scan.sh: they contain intentional injection examples /
security-model prose; the path-reference rewrites are kept.
CodeQL alerts on this PR are pre-existing (alert lines unchanged by this PR;
none in the new migration/guard) and are out of scope for the rename.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#604): resolve CodeQL alerts surfaced on this PR
The rename diff touched files carrying pre-existing CodeQL findings; per the
no-pre-existing-dismissal rule, fixing every surfaced alert rather than waving
them off. All behavior-preserving:
- scripts/ci-test-scope.cjs: build the config-path match from string
.includes() instead of a RegExp over an arg-derived value (js/regex-injection).
- src/profile-output.cts: escape backslashes before pipe-escaping desc/safeName
so the table-cell escape is complete (js/incomplete-sanitization).
- tests/{bug-2643,bug-2808,docs-parity-live-registry}: two-pass HTML-comment
strip so a bare/unclosed `<!--` cannot survive (js/incomplete-multi-character-sanitization).
- tests/inline-plan-threshold: drop the no-op `\s`->`\s` identity replace,
keep the meaningful POSIX-class conversion (js/identity-replacement).
Verified: build:lib green; the touched test files + ci-test-scope + profile-output
suites pass; lint:legacy-name clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#604): correctly resolve remaining CodeQL alerts (regex-injection + sanitization)
The prior commit's fixes for two alerts were ineffective:
- ci-test-scope.cjs js/regex-injection: the alert is the CLI-arg-derived `file`
reaching static regex `.test(file)` calls (not the config rule). Removed ALL
regex over file/t — startsWith/includes/=== string checks + an isWindowsHint
helper — so there is no regex sink for the tainted value.
- js/incomplete-multi-character-sanitization (3 test files): a single
`.replace(/<!--...-->/g,'')` can let `<!--` re-form. Replaced with a fixpoint
loop (replace until stable) plus a final bare-opener strip.
Verified: no regex over file/t remains; ci-test-scope + the 3 test suites pass;
lint:legacy-name clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#604): make ci-test-scope + comment-strippers regex-free to clear CodeQL
CodeQL flags the regex PATTERNS syntactically (regex-injection on the
--files arg split; incomplete-multi-character-sanitization on the <!--...-->
replace), so loop fixes do not satisfy it. Made these paths regex-free:
- ci-test-scope.cjs splitFiles: char-by-char separator tokenizer (no /[,\\s]+/).
- 3 test files: indexOf/slice HTML-comment stripper (no .replace(/<!--/)).
Behavior preserved; ci-test-scope + the 3 suites pass; guard clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#604): unblock security base64 scan on the large rename diff
The security job hit its 10m timeout: base64-scan.sh choked on the binary
test fixture tests/feat-3594-parser-property-style.test.cjs (embedded NUL/
non-UTF8 bytes -> thousands of bogus blobs + "ignored null byte" warnings),
and the ~800-file rename diff is slow to scan regardless.
- scripts/base64-scan.sh: skip binary-by-content files (grep -Iq .) — they
can't carry base64-obfuscated *text* and feeding NUL bytes through the
per-line scanner is pathologically slow. collect_files already filtered
binary *extensions*; this catches binary *content* in text extensions.
- .github/workflows/security-scan.yml: raise the security job timeout 10m->30m
to accommodate very large diffs (the scan itself is unchanged).
Verified locally: scan skips the fixture, 0 "ignored null byte" warnings,
0 findings, exit 0.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#604): sweep get-shit-done refs introduced by merging next
The branch was updated with next (#614/#384/#618 etc.), which reference the
get-shit-done/ dir (still named that on next). Swept the stale references in
the merged files to gsd-core so the rename stays consistent and lint:legacy-name
passes:
- commands/gsd/discuss-phase.md (runtime-launcher shim paths)
- src/core.cts (getAgentsDir layout comments)
- tests/bug-384-agents-runtime-aware.test.cjs (require path to runtime lib)
Verified: guard 0 violations; build green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#604): exclude gsd-core/ path segments from bug-3683 command cross-ref invariant
The #614 runtime-launcher shim added to discuss-phase.md references
`${_GSD_RUNTIME_ROOT}/gsd-core/bin/...`. bug-3683's REF_PATTERN excluded path-y
refs only via lookbehind, but `}` precedes `/gsd-core/` in the shim, so it
mis-read the directory path as a dangling `/gsd-core` command ref (same class as
the #604 bug-2954 fix). Added a trailing `(?![\w-]*\/)` so `/gsd-<x>/...` path
segments are not treated as slash-command references.
Verified locally on BOTH platforms before pushing:
- mac (node 26) full suite: 0 failures
- gsd-test-runner (linux, node22 image) full suite: 0 failures
- bug-3683 + bug-2954 pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#604): lazily resolve findProjectRoot in gsd-tools (harden flaky CI)
CI intermittently failed state.test's gsd-tools subprocess with
"findProjectRoot is not a function" (flip-flopping across legs; not reproducible
on mac full suite, gsd-test linux full suite, test:unit, or state.test x8).
findProjectRoot is a re-export from core.cjs (sourced from project-root.cjs);
binding it via destructure at module-load can be undefined under a load-ordering
edge. Resolve it lazily at call time via a small wrapper so the lookup happens
after core.cjs is fully initialized.
Verified green on BOTH platforms before pushing:
- mac (node 26) full suite: 0 failures
- gsd-test-runner (linux, node22) full suite: 0 failures
- state.test.cjs: 106/106; gsd-tools loads cleanly.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#604): allowlist verification-patterns.md placeholder examples in secret scan
The rename git-mv'd references/verification-patterns.md into gsd-core/, pulling
it into the secret-scan diff. It documents stub/placeholder RED-FLAG env-var
examples (illustrative Stripe test-key / database-URL / API-key placeholders) —
not real credentials. Added it to .secretscanignore with the strict annotation,
mirroring the existing gsd-core/workflows/plan-phase.md exception.
Verified locally: secret-scan-lint --strict OK; secret-scan --diff origin/next
exits 0 with 0 findings.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* enhancement(#537): migrate code-review-flags to TS source of truth
Collapse the hand-written get-shit-done/bin/lib/code-review-flags.cjs to a
TypeScript source of truth (src/code-review-flags.cts), compiled by tsc to a
gitignored .cjs build artifact at the same path, per ADR-457 (build-at-publish).
Second module after the semver-compare pilot (#541).
Behaviour is preserved byte-for-behaviour (characterization test added in
tests/code-review-flags.test.cjs locks the parser quirks). Adds compile-time
type checking: CodeReviewFlags interface + CodeReviewWorkflow literal union.
The require() path is unchanged, so code-review.md and the bug-3727 test keep
working. The emitted .cjs is gitignored and eslint-ignored, mirroring the pilot.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* enhancement(#537): migrate 9 leaf bin/lib modules to TS source of truth
ADR-457 build-at-publish, batch 1 (pure leaf modules, 0 sibling-deps):
001-legacy-orphan-files, context-utilization, redaction, artifacts,
command-arg-projection, clock, ui-safety-gate, review-reviewer-selection,
clusters. Each moves to src/*.cts (strict TS, typed), compiled by tsc to a
gitignored .cjs at the same require() path; behaviour preserved byte-for-
behaviour. Adds src/node-globals.d.ts (minimal ambient shim; "types":[]).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* chore(#537): add @types/node, drop hand-rolled node-globals shim
ADR-457 migration infra: replace the temporary src/node-globals.d.ts ambient
shim with @types/node@22 + "types":["node"] in tsconfig.build.json. Unblocks
migrating the ~49 remaining bin/lib modules that use node:fs/path/os/
child_process. Build + full suite (3030 pass) + lint all green; no .cts type
changes were needed (real Node types matched the shim).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* enhancement(#537): migrate 9 more bin/lib modules to TS (batch 2)
ADR-457 build-at-publish. Clean leaves: installer-migration-report,
prompt-budget. Type-error-prone leaves (were tsconfig.lint-excluded; now
strict-typed and removed from that exclude list): secrets, phase-lifecycle,
workstream-name-policy, decisions, validate, schema-detect. Plus
runtime-name-policy. Strict type fixes narrow unknown->concrete domain types
(no any/ts-ignore); behaviour preserved. Full suite green, lint 0 errors.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* enhancement(#537): migrate runtime-slash to TS (cross-import proof)
ADR-457. First cross-module TS->TS import: src/runtime-slash.cts imports
./runtime-name-policy.cjs and tsc resolves the sibling .cts types under strict
(no declaration files; NodeNext .cjs->.cts mapping), emitting a correct
require("./runtime-name-policy.cjs"). Confirms the recipe for coupled modules,
which must be migrated in dependency order (leaves-up). Suite green, lint clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* enhancement(#537): migrate 10 more bin/lib modules to TS (batch 3)
ADR-457 build-at-publish, Wave-1 leaves: event, workstream-inventory-builder,
plan-scan, fallow-runner, project-root, installer-migration-authoring,
update-context, 000-first-time-baseline, runtime-homes, model-catalog. Strict
typing fixed real issues (narrowing unknown, qualified fs/path calls, removed
unnecessary casts); plan-scan/project-root/workstream-inventory-builder dropped
from tsconfig.lint exclude. Behaviour preserved; suite green, lint 0 errors.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* enhancement(#537): migrate 5 large Wave-1 leaves to TS (batch 4)
ADR-457 build-at-publish: configuration, state-document, shell-command-
projection (42 dependents), security, command-aliases. shell-command-
projection keeps a namespace child_process import for mock-intercept
testability. loadConfig/migrateOnDisk emit synchronously (every caller uses
them sync; the one awaited migrateOnDisk caller tolerates a non-Promise) —
full suite (3030 pass) confirms behaviour preserved. configuration/
state-document/command-aliases dropped from tsconfig.lint exclude. Also fixes
the malformed batch-3 changeset frontmatter (type/pr) that failed lint:docs.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* enhancement(#537): migrate 6 Wave-2 modules to TS (batch 5)
ADR-457 build-at-publish: config-schema, model-profiles,
002-codex-legacy-hooks-json, logger, active-workstream-store, adr-parser.
First batch importing already-migrated siblings (configuration, model-catalog,
shell-command-projection, redaction, security) via ./sibling.cjs specifiers.
Strict type narrowing (typeof guards over String(unknown)); behaviour
preserved; suite 3030 pass, lint 0 errors.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* enhancement(#537): migrate 5 large Wave-2 modules to TS (batch 6)
ADR-457 build-at-publish: graphify, install-profiles, intel,
installer-migrations, worktree-safety. installer-migrations preserves its
dynamic require() loader for numbered migration modules (scoped lint
suppressions). Strict typing (typeof guards over String(unknown)); behaviour
preserved; suite 3030 pass, lint 0 errors. Wave 2 complete.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* enhancement(#537): migrate Wave-3 modules to TS (batch 7)
ADR-457 build-at-publish: planning-workspace, runtime-artifact-layout,
command-routing-hub, drift. Uses `import x = require()` for export= siblings;
drift's lazy require of runtime-slash hoisted to a top-level import (verified
non-circular). Behaviour preserved; suite 3030 pass, lint 0 errors.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* enhancement(#537): migrate small Wave-4 modules to TS (batch 8)
ADR-457 build-at-publish: cjs-command-router-adapter, phase-command-router,
surface, roadmap-upgrade. Typed the hub router handler results as the HubResult
discriminated union; surface drops 4 genuinely-unused imports. Behaviour
preserved; suite 3030 pass, lint 0 errors.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* enhancement(#537): migrate core hub (2.5k LOC, 68 dependents) to TS (batch 9)
ADR-457 build-at-publish: get-shit-done/bin/lib/core.cjs -> src/core.cts,
preserving all 63 exports via export=. All sibling deps already migrated
(shell-command-projection, model-profiles, model-catalog, worktree-safety,
planning-workspace, project-root, configuration, config-schema). Strict types,
no any/ts-ignore; config-schema lazy require hoisted (non-circular). Behaviour
preserved (independently verified: core's shard 3030 pass / 0 fail).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* test(#537): make ESLint-coverage + test-sprawl checks migration-aware
#551 test hardcoded 12 now-migrated modules as "hand-written, must be linted";
that invariant is obsoleted by the ADR-457 migration. Rewrite it to a
filesystem-driven invariant that holds at every stage: a bin/lib/*.cjs must be
eslint-ignored IFF it has a src/*.cts source (tsc-generated), else linted
(covers package-identity, which has no TS source). Also eslint-ignore
config-types.cjs (has a src counterpart) and drop the redundant
tests/clock.test.cjs (clock already covered by clock-seam + bug-474 tests),
which tripped the lint-test-file-count ratchet.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* enhancement(#537): migrate 9 Wave-5 router/inventory modules to TS (batch 10)
ADR-457 build-at-publish: phases/verify/init/agent/task/validate/roadmap/state
command routers + workstream-inventory. Router handler results typed against
core's exported shapes; behaviour preserved (caught+fixed a --verify boolean
flag regression mid-migration). Full suite green across all shards (only the 4
local gpg-env changeset-notes failures remain; CI passes them).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* enhancement(#537): migrate 7 Wave-5 modules to TS (batch 11)
ADR-457 build-at-publish: gap-checker, docs, check-command-router, frontmatter,
learnings, gsd2-import, profile-pipeline. Behaviour preserved; full suite green
across all shards (only the 4 local gpg-env failures remain). Also broadens
atomic-write-coverage.test.cjs to accept the tsc-compiled namespace-import form
while still asserting platformWriteSync is called (safety guard intact).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* enhancement(#537): migrate config + profile-output to TS (batch 12)
ADR-457 build-at-publish: config (729 LOC), profile-output (1142 LOC). All
exports preserved; cmdMigrateConfig de-asynced (migrateOnDisk is sync, awaited
caller tolerates it). Behaviour preserved; suite green across all shards
(only the 4 local gpg-env failures). Wave 5 complete.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* enhancement(#537): migrate 5 Wave-6 modules to TS (batch 13)
ADR-457 build-at-publish: template, uat, workstream, roadmap, audit. Behaviour
preserved (dead toPosixPath import dropped from audit; inline requires hoisted).
Suite green across all shards (only the 4 local gpg-env failures).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* enhancement(#537): migrate commands + state hubs to TS (batch 14)
ADR-457 build-at-publish: commands (1305 LOC), state (2074 LOC, 17 dependents).
All exports preserved; inner requires kept non-hoisted where load-order matters
(install.js, per-call security); acquireStateLock cast inlined to preserve the
err.code source token a structural test inspects. Behaviour preserved; suite
green across all shards (only the 4 local gpg-env failures). Wave 6 complete.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* enhancement(#537): migrate milestone to TS (batch 15a, hand-authored)
ADR-457 build-at-publish: milestone -> src/milestone.cts. Authored directly
(subagent capacity was unavailable). Also relaxes core.output()'s 3rd param to
optional, matching its real always-optional call contract (unblocks remaining
2-arg output callers). Behaviour preserved; suite green across all shards
(only the 4 local gpg-env failures).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* enhancement(#537): migrate phase, verify, init to TS (batch 15, final modules)
ADR-457 build-at-publish, Wave 7 (the last hubs): phase (1608 LOC), verify
(1615), init (2113). Adds src/package-identity.d.cts so verify can import the
permanently value-baked package-identity.cjs under strict TS.
Fixes two regressions the migration introduced in verify: restore
cmdValidateHealth's `return result` (callers/tests read result.warnings — it is
NOT side-effect-only), and make the bug-3384 source-pattern test tolerant of the
tsc-compiled bracket-notation form of the git_list_failed->W020 branch (behaviour
intact). Full suite green across all shards (only the 4 local gpg-env failures);
lint 0 errors. All 86 migratable bin/lib modules are now TypeScript sources.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* chore(#537): finalize ADR-457 migration — retire tsconfig.lint.json
All hand-written bin/lib/*.cjs are now src/*.cts sources, so the checkJs
stopgap tsconfig.lint.json (unused; not wired into eslint, scripts, or CI) is
deleted per ADR-457's final step. Also gitignore the tsc-generated
config-types.cjs (was still committed) for consistency with every other
emitted artifact. package-identity.cjs stays value-baked (declared via
src/package-identity.d.cts). Suite green; #551 ESLint-coverage test green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#537): add prepare script so unpacked/git installs build bin/lib artifacts
ADR-457 build-at-publish: bin/lib/*.cjs are now gitignored, built by tsc. The
prepack/prepublishOnly hooks cover `npm pack`/publish, but `npm install -g
<dir>` and git installs run the `prepare` lifecycle — which was missing — so the
unpacked install shipped without the compiled .cjs and failed at startup with
"Cannot find module './lib/core.cjs'" (caught by the smoke-unpacked CI job).
Add `prepare` mirroring prepublishOnly (build:lib + build:hooks). prepare does
NOT run for registry consumers (they get the pre-built tarball), only for
source/local/pack installs.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#537): make CI build/lockfile checks work with gitignored bin/lib artifacts
ADR-457 build-at-publish exposed two CI assumptions that bin/lib/*.cjs are
always present on disk:
- check:env's lockfile-sync ran `npm ci --dry-run`, which now triggers the
`prepare` build (tsc) — but it runs before deps are installed, so tsc is
absent and it misreported the lockfile as out of sync. Add --ignore-scripts
(a lockfile check must not build).
- the lint-tests job installs with --ignore-scripts (no prepare build), but
lint:skill-deps require()s the built install-profiles.cjs. Add an explicit
`npm run build:lib` step after install.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#537): narrow prepare to build:lib only (unbreak packed-smoke pack step)
prepare running build:hooks emitted "✓ Copying ..." stdout during `npm pack`,
which the install-smoke "Pack root tarball" step captures into $GITHUB_OUTPUT —
breaking it with "Invalid format". build:lib (tsc) is silent on success and is
all the unpacked/source install needs (the smoke-unpacked assertions exercise
gsd-tools, i.e. bin/lib, and tolerate hook setup with `|| true`). Matches
prepack. build:hooks still runs on prepublishOnly for real publishes.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#537): wire Stryker mutation gate to build-at-publish layout
The gate scored 0.00 because it mutated changed bin/lib/*.cjs that (a) were
generated artifacts and (b) included modules with no coverage in the command's
test set. Rework: mutation.yml now derives changed COVERED modules from
src/*.cts and maps them to their built bin/lib/*.cjs; Stryker mutates those
built artifacts with a no-rebuild command (mutating src/*.cts + per-mutant tsc
was ~3x over the 30-min CI budget).
NOTE: with the gate now correctly measuring the covered modules, their actual
mutation score is 42.94% (< break 50) — a pre-existing test-coverage gap
(adr-parser/prompt-budget/etc.), not introduced by this behaviour-preserving
migration. Reaching 50 needs more tests, a threshold/scope change, or a waiver —
a maintainer decision.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* test(#537): raise mutation coverage of covered modules above the 50 gate
Adds focused example-based unit tests that kill surviving mutants in the two
lowest-scoring covered modules:
- tests/prompt-budget.unit.test.cjs (112 tests): 17.9% -> 97.9%
- tests/adr-parser.unit.test.cjs (205 tests): 44.7% -> 89.4%
Both wired into stryker.config.mjs's command. Fresh full run over the 6 covered
modules now scores 82.25% (>= break 50); every covered module is >= 68%.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* enhancement(#537,#609): parallelize mutation gate via dynamic per-module matrix
The serial Stryker run timed out at 30 min once the migration's added tests
made every mutant re-run ~300 tests. Replace it with a dynamic matrix so the
gate completes well under budget — folded into this PR (was tracked as #609)
because it's a prerequisite for this PR's mutation gate to pass.
- scripts/mutation-matrix.cjs: single source of truth (covered-module -> test
files) computing changed covered modules from git diff -> {has_work, matrix}.
- mutation.yml: detect -> dynamic `matrix: fromJSON(...)` mutate job (one
parallel shard per changed module, scoped via MUTATION_TEST_CMD to only that
module's tests, 15-min/shard) -> summary job that KEEPS the legacy check name
"Stryker mutation score (changed files only)" so branch protection is
unchanged. Per-shard jobs report as "Stryker (<module>)".
- stryker.config.mjs: commandRunner.command reads MUTATION_TEST_CMD (falls back
to the full command locally).
Closes#609.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* test(#537,#609): give each mutation shard ≥50% on its own tests; drop blacksmith note
Per-module sharding revealed that active-workstream-store (46.5%) and
frontmatter (7.4%) only cleared 50% in the old serial run via timeout-noise from
the bloated 300-test command; on their own tests they were below the gate. Add
focused unit tests:
- tests/active-workstream-store.unit.test.cjs (115 tests): 46.5% -> 81.9%
- tests/frontmatter.unit.test.cjs (165 tests): 7.4% -> 63.4%
Both wired into scripts/mutation-matrix.cjs (per-module test map) and
stryker.config.mjs DEFAULT_TEST_CMD. All 6 covered modules now clear break:50
with only their own tests (config-schema/context-utilization/prompt-budget/
adr-parser already did). Also removes the leftover blacksmith TODO comment —
GitHub-hosted runners only; speed comes from parallel per-module shards.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* test(#537,#609): strengthen prompt-budget tests to clear the gate on its own tests
prompt-budget scored 39.58% when mutation-tested with ONLY its own tests (the
way the per-module CI shard runs it) — an earlier ~98% reading was inflated by
accidentally running the full multi-module command. Add 96 targeted tests to
tests/prompt-budget.unit.test.cjs (exact note-template text, plan-truncation
arithmetic/percentages, drop-block strings, noteInjected/hardFailed booleans):
scoped score 39.58% -> 68.75% (>= break 50). All 6 covered modules now clear
the gate on their own tests.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* chore: rename npm package + bin to @opengsd/gsd-core (functional)
- package.json: name @opengsd/get-shit-done-redux → @opengsd/gsd-core,
bin key get-shit-done-redux → gsd-core, repository/homepage/bugs URLs
- package-lock.json: regenerated (npm install --package-lock-only)
- tests/**, scripts/**, bin/**, .github/**, agents/**, commands/**,
get-shit-done/bin/**, get-shit-done/workflows/**:
applied the 4-rule replacement (scoped npm ref, GitHub repo path,
bin/clone invocations) per #505 single-source refactor
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* docs: sweep live references to @opengsd/gsd-core
Update all live documentation (README.md + translations, docs/**,
CONTRIBUTING.md, VERSIONING.md, SECURITY.md, CONTEXT.md,
docs/CANARY.md) to reflect the renamed package and repository.
Rules applied:
- @opengsd/get-shit-done-redux → @opengsd/gsd-core (scoped npm name)
- open-gsd/get-shit-done-redux → open-gsd/gsd-core (GitHub repo)
- GSD-redux/get-shit-done-redux → open-gsd/gsd-core (stale badge org)
- bare bin/clone refs → gsd-core
CHANGELOG.md, docs/adr/**, docs/RELEASE-*.md, docs/research/**,
and .changeset/** are preserved byte-identical.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix: add negative lookbehind to slash-command regex in bug-2954 test
The extractSlashReferences regex matched /gsd-core inside npm package
URLs (@opengsd/gsd-core), producing a false /gsd:core command reference.
Adding a negative lookbehind (?<![a-z]) excludes matches preceded by a
letter, so only standalone /gsd-<cmd> and /gsd:<cmd> tokens are found.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* chore(#518): add changeset for package rename
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* test(#518): update package-identity expectations to the renamed coordinates
The rebase regenerated the seam to @opengsd/gsd-core (bin gsd-core, repo
open-gsd/gsd-core). The #498 seam tests assert deriveIdentity against the REAL
package.json, so their expected literals must follow the rename. The drift-lint
unit test is left as-is — its SEAM is a self-consistent fixture and its
stale-literal detection cases would shift if altered; the live-repo scan in it
already passes.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* 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>
* 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>
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)
* fix(3687): update insert-phase docs and roadmapper to use --insert flag
Updates stale references in insert-phase.md workflow and
gsd-roadmapper.md agent to use the consolidated /gsd:phase --insert
command syntax instead of the retired /gsd-insert-phase and
/gsd:phase insert forms.
Closes#3687
* refactor(tests): consolidate Init Command Module — 7 files → 5
Closes#3755
Merges `tests/init-manager-deps.test.cjs` (#2267 regression) into
`tests/init-manager.test.cjs` (718 LOC), and
`sdk/src/query/init-progress-precedence.test.ts` (#2674 regression)
into `sdk/src/query/init-complex.test.ts` (788 LOC).
The 800 LOC ceiling prevents further consolidation:
- `tests/init.test.cjs` is pre-existing at 1630 LOC
- `sdk/src/query/init.test.ts` is at 791 LOC
- `sdk/src/query/init-workstream-milestone-op.test.ts` is a distinct
seam testing initMilestoneOp, roadmapAnalyze, and
resolveQueryRuntimeContext workstream resolution.
Also adds Init Command Module Glossary entry to CONTEXT.md.
Allowlist update deferred to rebase after #3738 merges (allowlist
file does not exist on origin/main).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(init): initExecutePhase preserves same-milestone archived phase dir (#3469)
When `phases clear` archives current-milestone phases into
`.planning/milestones/<version>-phases/` but the workflow is still on
that same milestone, `shouldDropArchivedPhaseMatch` was unconditionally
dropping the archived dir match. This caused `phase_dir: null` when the
phase was still executing in the current milestone.
Fix: detect when `phaseInfo.archived === currentMilestone` (read from
STATE.md) and skip the drop. The #2391 regression guard is safe because
that scenario involves archived.version != current milestone.
Also corrects two tests in `initRemoveWorkspace` to expect thrown
GSDError instead of `{ data: { error } }` — the production code was
intentionally changed to throw for CLI non-zero exit propagation.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: arya rizky <aryarizkyardhipratama@gmail.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* 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>
* fix(ci): skip install + slow lanes on Windows in main test matrix
Gates the `Run install tests` and `Run slow tests` steps in
`.github/workflows/test.yml` to `matrix.os != 'windows-latest'`.
The install lane performs `npm install -g <tarball>` 7× per invocation
of release-tarball-smoke.install.test.cjs (1× in the shared before()
hook + 1× per of the 6 test cases). On windows-latest each install
costs 60–90 s (NTFS + Defender) so the lane alone consumes ~8–9 min
on top of the ~7 min already spent on npm ci + build:sdk + unit +
integration + security — overflowing the 15-min `timeout-minutes` cap
and cancelling the job mid-install.
The dedicated install-smoke.yml workflow already excludes Windows from
its matrix (ubuntu + macOS only); the weekly windows-compat workflow
provides Windows-specific regression coverage. Linux + macOS install
and slow lanes remain on main push for parity.
Refs #3709
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(deps): bump ws 8.20.0 → 8.20.1 to clear GHSA-58qx-3vcg-4xpx
The bug-3588 `npm audit --omit=dev reports zero advisories` test
(tests/bug-3588-npm-audit-clean.test.cjs) is failing on main after a
new advisory dropped against ws@8.20.0:
GHSA-58qx-3vcg-4xpx — Uninitialized memory disclosure
ws: range >=8.0.0 <8.20.1 (CVSS 4.4, moderate, CWE-908)
Fix: `npm audit fix --omit=dev` at both root and sdk/. Lockfile-only
bump to ws@8.20.1; package.json untouched (ws is transitive).
`npm audit --omit=dev` reports `found 0 vulnerabilities` in both
workspaces after the bump.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* fix(3588)(security): clear production npm-audit advisories
Before: 6 production advisories (1 high, 5 moderate) reported by
`npm audit --omit=dev` — fast-uri (high), @anthropic-ai/sdk,
express-rate-limit, hono, ip-address (moderate), all pulled in through
@anthropic-ai/claude-agent-sdk and @modelcontextprotocol/sdk.
After: `npm audit fix` bumped the lockfile-pinned transitive versions
to patched releases. No package.json edits — only package-lock.json
and sdk/package-lock.json. Production audit is clean on both:
`npm audit --omit=dev` → 0 vulnerabilities.
Regression test `tests/bug-3588-npm-audit-clean.test.cjs` runs
`npm audit --omit=dev --json` against root and sdk/ and asserts the
metadata vulnerability counts are zero across info/low/moderate/high/
critical. RED on origin/main (1 high + 5 moderate at root), GREEN after
the lockfile bumps. Skips gracefully when node_modules/ is absent so
fresh checkouts mid-`npm install` don't false-fail.
Fixes#3588
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(3588): npm audit harness throws on unexpected JSON shape
CodeRabbit caught that auditProductionVulns returned null both for
"node_modules missing → skip" AND for "unexpected JSON shape" — and
callers interpret null uniformly as skip, so a real audit harness
failure (npm changed output format, audit aborted before metadata
section, etc.) would silently no-op instead of failing the test.
null is now reserved for the skip signal only. Any other unexpected
shape throws with the cwd in the message so the test fails loudly.
Local: docker gsd-test-summary 11204/0 on plex2.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* 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