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>
hotfix.yml was deleted (folded into release.yml). Remove the now-broken
release-coverage-scope and policy-release-no-npm-self-upgrade assertions that
readFileSync'd hotfix.yml (release.yml equivalents retained), drop the dead
install-smoke.yml path trigger, and update VERSIONING.md / docs/branching.md
prose to describe hotfixes via the Release workflow with a patch version.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
v1.3.0's production tree carries a moderate hono advisory (GHSA-3hrh-pfw6-9m5x
et al.) disclosed after release; the npm-audit unit gate blocked the hotfix
finalize. npm audit fix bumps hono to 4.12.23 (lockfile only).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#676): consolidate hotfix into release.yml; delete standalone hotfix workflow
npm allows only one trusted publisher per package and it is release.yml, so the
standalone hotfix.yml (token-auth) could never publish via OIDC (ENEEDAUTH on the
v1.3.1 finalize). Fold the patch/hotfix path into release.yml — the sole OIDC
trusted publisher — and delete hotfix.yml.
- validate-version accepts X.Y.Z (Z>0) → hotfix/X.Y.Z + base_tag; rejects rc for
hotfixes; X.Y.0 still → release/X.Y.0.
- create branches hotfix/X.Y.Z from the base tag with optional auto-cherry-pick
(default on) of fix:/chore: from next; release path unchanged.
- finalize is branch-agnostic already and publishes @latest via the existing
OIDC trusted publisher (no NODE_AUTH_TOKEN).
- CONTRIBUTING branching table updated; hotfix.yml removed.
Fixes#676
Supersedes #677.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#676): add hotfix/patch path to release.yml (OIDC trusted publisher)
The companion to the hotfix.yml deletion: release.yml now handles patch
versions (X.Y.Z) via hotfix/X.Y.Z branches and publishes @latest through the
existing OIDC trusted publisher. CONTRIBUTING branching table updated.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Backport of #675 to the 1.3.1 hotfix. An already-applied installer migration
whose recorded checksum drifted (e.g. a shipped body was edited in #615) is now
detected and reconciled on the next install instead of hard-aborting the
upgrade ("applied migration checksum changed"). Adds a CI baseline lock that
fails on any shipped-migration body drift.
Fixes#670
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Editing the body of an already-released installer migration drifts its computed
checksum (it hashes plan.toString()). The integrity guard then hard-aborted
every prior install on upgrade with "applied migration checksum changed" — a
100% reproducible blocker (v1.3.0, all platforms).
Already-applied migrations are filtered out of `pending` and never re-run, so
a drifted checksum is functionally inert. ADR-0008 anticipates checksum-mismatch
state as something the install-state layer must handle gracefully (plan -> apply
-> recover/report), not abort on.
This supersedes the published-checksum allowlist merged in #674 (per-release
maintenance debt — every historical checksum hand-pinned, still throws for any
unregistered value) with a general, self-healing recovery:
- Replace the throwing guard with non-fatal `collectAppliedChecksumDrift`,
surfaced on `plan.checksumDrift`.
- Reconcile drifted stored checksums durably on the next state write
(`reconcileDriftedChecksums`), idempotently (no perpetual writes).
- Relocate the "shipped migration bodies are immutable" rule to a CI baseline
test that locks every shipped migration's checksum and fails on body drift —
where #615 should have been caught, instead of blocking users.
Removes #674's legacyChecksums field, per-migration checksum pins,
published-checksums.json fixture, and compat test.
Fixes#670
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
The open-gsd org blocks the Actions GITHUB_TOKEN from creating PRs, so
auto-backmerge and the release finalize merge-back PR steps can't open
their PRs (must be done manually). Point those two steps at a scoped
secret (pull-requests:write + contents:write), falling back to
GITHUB_TOKEN so behavior is unchanged until the secret is added.
Refs #660
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>
Override stale main-only docs (README/translations/CHANGELOG #540/#542/#546/#548)
with the cleaned-up release content from next — these were old data confusing
users and were intentionally removed on next. Strategy 'ours' keeps the release
tree verbatim and records main as a parent so the merge-back is conflict-free.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replaces the persistent/frozen release branch + hand-moved tag with:
release always cut from next's head, immutable tags minted once at
finalize, next on a -dev stream, and @next dist-tag as the RC surface.
Closes#660
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Consolidate all pre-rename release notes (the retired get-shit-done-cc /
get-shit-done-redux lineage, 1.0.0 -> 1.50.0-canary.1) into one condensed,
read-only archive so the legacy 1.x version numbers no longer collide with
the current @opengsd/gsd-core line.
- Add docs/RELEASE-NOTES-LEGACY.md: rename banner, master version-index
table, condensed per-version sections (1.42.3 -> 1.0.0), and a separate
pre-release & canary builds section. Stale install commands stripped.
- Trim CHANGELOG.md to the current @opengsd/gsd-core line only; replace the
Legacy Release History block with a pointer to the archive and drop the
orphaned numbered legacy reference-link definitions.
- Remove the 10 standalone docs/RELEASE-v*.md files.
- Repoint docs/CANARY.md and docs/FEATURES.md links to the new archive.
Closes#658
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
The package author was still the legacy personal credit "TÂCHES", which npm
renders on the package page (issue #653). Set it to the org name OpenGSD,
matching the @opengsd scope / open-gsd org. The installer banner's "by TÂCHES"
was already removed in the #523 rebrand; this clears the last in-repo reference.
Closes#653
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(#586): make ship PHASE_VERIFICATION_INCOMPLETE actionable, drop dead `pass` arm
The ship preflight gate blocked with PHASE_VERIFICATION_INCOMPLETE but named no
next step, and accepted a `pass` status the verifier never emits. Capture the
verification status and route per value (gaps_found / human_needed / missing),
mirroring execute-phase's status table; accept only `passed`.
Closes#586
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore(#586): backfill changeset PR number 650
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(#586): scope ship verification status to frontmatter only
Codex adversarial review of PR #650 flagged that the status gate grepped
`^status:` over the entire VERIFICATION.md, so a `status:` line in the report
body (a code block / copied artifact) concatenates into a non-matching value and
blocks a genuinely-passed phase with the wrong next action. Restrict extraction
to the leading YAML frontmatter block, first match only. Adds a behavioral
regression test that runs the gate's own bash pipeline against a passing report
whose body contains decoy `status:` lines.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore(#586): drop manual PR ref from changeset body
The changelog renderer auto-appends `(#<pr>)` from the fragment's pr: field
(scripts/changeset/serialize.cjs, github-release-notes.cjs). The manual trailing
`(#586)` produced a double, mismatched ref (issue #586 + auto PR #650); remove it
to match the sibling-fragment convention.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test(#586): make ship-586 bash-fence regex Windows-safe (CRLF)
The behavioral test extracted the gate's bash block with /```bash\n.../ — a
literal \n that fails to match Windows CRLF checkouts and trips the
windows-test-parity-guard (fenceRegexLiteralNewline). Use ```bash\r?\n and
normalize the captured block to LF before running it. Full unit suite: 0 fail.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test(#586): run ship-586 bash-pipeline tests on POSIX only
On Windows CI the behavioral tests failed: git-bash is present (so the old
hasBash guard ran them) but receives a Windows-style tmpdir path it cannot glob,
so extraction returned empty. The extraction logic is platform-independent and
the gate's bash only runs in a POSIX workflow context, so skip the pipeline
execution on win32. POSIX (macOS/Linux) still runs and asserts it.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The #452 ESLint migration replaced the homegrown source-grep regex linters
with the local/no-source-grep AST rule (eslint-rules/no-source-grep.cjs, wired
in eslint.config.mjs) but left the old scripts on disk, wired to nothing.
Removed:
- scripts/lint-no-source-grep.cjs — CLI linter; no module.exports, never
required, not referenced by package.json / CI / eslint. Superseded by the rule.
- scripts/lint-no-source-grep-extras.cjs — var-binding + wrapped-assert-ok
regex detectors; the ESLint rule covers both forms via AST
(VariableDeclarator/AssignmentExpression tracking + member-call checks).
- tests/bug-2982-lint-var-binding.test.cjs — exercised only the deleted extras
module; the #2982 var-binding scenario is already covered against the live
rule by tests/eslint-rules.test.cjs.
Also dropped the now-dangling 'scripts/lint-no-source-grep.cjs' entry from
DEFAULT_RELATIVE_FILES in scripts/lint-pr-check-project-dir.cjs (the list is
existsSync-filtered, so this is tidy-up, not a behavior change).
No functional change: source-grep enforcement remains intact via the ESLint
rule, and the full unit suite stays green. Three surviving comment-only mentions
of "lint-no-source-grep" refer to the rule concept (which lives on in ESLint),
not the deleted files.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Tom Boucher <trekkie@nomorestars.com>
When ci-test-scope falls back to the 'unit' sentinel (#408 intent) and
ci-prepare-test-scope writes it verbatim, run-tests --files-from received
a bare 'unit' token that was not a filename, causing exit 2 with
"requested test file(s) not found: unit".
selectExplicitFiles() now recognises any SUITES member and delegates to
the existing selectFiles() resolver before the path-existence check,
reusing the suite expansion logic rather than reimplementing it.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(#619): resolve gsd-tools via runtime shim in codebase-drift-gate
The post-execution drift check ran the bare PATH binary
`gsd-tools verify codebase-drift`. On a shim-only install (gsd-tools.cjs
present, `gsd-tools` not on PATH) that exits 127, `2>/dev/null` hides it,
and the `|| echo` fallback marks the gate skipped — so codebase-drift
detection silently never runs. Non-blocking by contract, so nothing
surfaced; it just quietly stopped working.
Resolve gsd-tools through the runtime shim launcher (gsd_run) instead.
The canonical launcher preamble is now defined once in the always-run
drift-check block (the file's first gsd_run block); the conditional
auto-remap block reuses gsd_run from the workflow's shared shell scope,
keeping the file compliant with the single-canonical-preamble parity
invariant (tests/runtime-launcher-parity.test.cjs). This is the same
single-preamble pattern established by discuss-phase (#614). Non-blocking
is preserved for the drift command's internal failures via the unchanged
`|| echo '{"skipped":...}'` fallback.
Scope decision (the issue's open question): workflow step-file bash blocks
share one shell scope, so the preamble is defined once before the first
gsd_run call — matching discuss-phase and enforced by the parity test.
Regression test (bug-619-...): contract assertions (gsd_run not bare
gsd-tools; single preamble in the drift block; fallback intact) plus a
behavioral proof that runs the shipped drift-check block against a
shim-only topology and asserts the shim actually executes where the old
bare-binary form would have skipped. Red→green verified.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* chore(#619): add changeset for codebase-drift-gate shim fix
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#630): pin wave-cleanup to orchestrator root via manifest, not list first-entry
Follow-up to #590. #590 fixed the dispatch-side orchestrator cwd anchor
(ORCHESTRATOR_WT via git rev-parse --show-toplevel) but the two
wave-cleanup guards still resolved PRIMARY_WT from `git worktree list
--porcelain`'s first entry — always the main checkout. An orchestrator
running from a non-primary (per-phase lane) worktree was therefore cd'd
off its own lane at cleanup, tripping the #3174 branch-drift assertion
(ORCH_BRANCH != EXPECTED_BRANCH) and refusing merge-back — the same
failure #590 set out to fix, surviving on the cleanup side.
Persist the dispatch-time orchestrator root (show-toplevel, captured from
the lane the orchestrator dispatches from) into WAVE_WORKTREE_MANIFEST as
`orchestrator_root`, and resolve PRIMARY_WT from it at both cleanup sites.
The git-worktree-list first entry survives only as a guarded fallback for
pre-#630 manifests. Byte-identical for a primary orchestrator (its root
IS the first entry); unblocks the non-primary-orchestrator topology.
Regression test (bug-630-...): behaviorally proves the pivot by running
the shipped manifest-reader one-liner against a real non-primary-worktree
git topology — it resolves to the lane while first-entry resolves to main
— plus contract assertions. Updates the #3425 worktree-cleanup contract
tests to the new manifest-based resolution.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* chore(#630): add changeset for wave-cleanup orchestrator-root fix
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* test(#630): canonicalize paths with realpathSync.native for Windows 8.3 parity
On Windows the CI runner's os.tmpdir() yields an 8.3 short name (RUNNER~1)
while `git worktree list` reports the long form (runneradmin); plain
realpathSync preserved each input's form, so the first-entry/main sanity
comparison mismatched. Canonicalize both sides (and the reader output)
via fs.realpathSync.native, which reconciles 8.3 and long forms. Test-only;
the shipped manifest reader is unaffected.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#628): read snake_case requirements_completed in summary-extract
cmdSummaryExtract read only the kebab frontmatter key
`requirements-completed`, but the tool's own JSON output key and the
milestone-audit `--pick` both use the snake form `requirements_completed`.
A SUMMARY written in the snake form the tool itself emits was silently
read back as [] — a false negative in milestone requirement traceability
with no diagnostic.
Make the reader tolerant of both key forms (kebab takes precedence), so a
round-tripped field is no longer dropped. extractFrontmatter does no
hyphen<->underscore normalization, so distinct keys had to be read
explicitly.
Adds regression tests: snake-only fixture now returns the IDs, and a
both-forms-present fixture asserts kebab precedence.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* chore(#628): add changeset for summary-extract snake-key fix
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
cjsFallbackHandler was a no-op pass-through returning only its last
argument, and state-command-router was its sole consumer — every handler
wrapped a thunk in a 5-arg projection tuple whose first four args
(registryCommand, registryArgs, legacyArgs, rawFormatter) were
constructed and immediately discarded (rawFormatter null at 100% of
sites). The SDK-dispatch path the metadata was nominally for is a retired
shim.
- Collapse all 20 state handlers to plain () => state.cmd*(...) thunks,
matching the post-#298 inline convention used by the verify/roadmap/
phases routers (complete-phase already used this form).
- Remove the local fallback() helper and Handler type from
state-command-router.cts.
- Drop cjsFallbackHandler from cjs-command-router-adapter.cts exports —
no remaining consumers.
Behavior-preserving: each thunk body is unchanged. No test referenced
cjsFallbackHandler. Adapter test (8/8), state-routing tests (36/36), and
the full unit suite (3435 pass / 0 fail) stay green.
Source-only diff; gsd-core/bin/lib/*.cjs are built-at-publish per ADR-457.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Tom Boucher <trekkie@nomorestars.com>
Four pointers left stale by merged changes:
- npm run lint:tests / lint-tests CI job -> ESLint local/no-source-grep
rule (npm run lint), per the #452 lint-harness migration
- buildWindowsShimTriple example removed (deleted with the gsd-sdk shim);
promote the live verify-reapply-patches REASON-enum example instead
- scripts/verify-reapply-patches.cjs -> gsd-core/bin/verify-reapply-patches.cjs
(#604 get-shit-done -> gsd-core rename)
- bin/lib/config-schema.cjs -> gsd-core/bin/lib/ prefix (#604)
Also fixes the empty "Test Requirements by Contribution Type" heading that
was nested above its body.
Docs-only; no runtime change.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Tom Boucher <trekkie@nomorestars.com>
* fix(#621): route plan-phase post-planning-gaps through gsd_run launcher
The post-planning-gaps step in gsd-core/workflows/plan-phase.md invoked
gsd-tools via a hardcoded `node "$HOME/.claude/gsd-core/bin/gsd-tools.cjs"`
path twice on one line — for the gap-analysis call and its nested
init.plan-phase phase_req_ids query — bypassing the gsd_run launcher that
every other call in the workflow uses. On non-default install/runtime layouts
(relocated/global installs, non-Claude runtimes) the hardcoded path does not
resolve, so the assistant reported the gap-analysis tool as "not found" and
fell back to a frontmatter-only coverage check even when a working install
existed. #3668 fixed this class earlier in the file but missed this block.
Route both invocations through gsd_run, matching the rest of the workflow.
No hardcoded $HOME gsd-tools path remains in plan-phase.md.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* chore(#621): add changeset for plan-phase gsd_run gap-analysis fix
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* test(#621): update bug-2851 §13e guard for the gsd_run gap-analysis form
The #621 fix migrates plan-phase.md's post-planning-gaps gap-analysis call
from the hardcoded node "$HOME/.claude/gsd-core/bin/gsd-tools.cjs" form to the
gsd_run launcher (the canonical resolvable form every other call in the file
uses). bug-2851's §13e subtest pinned that line to the absolute-$HOME form and
now asserts stale behavior.
Update the §13e assertion to require `gsd_run gap-analysis` (still rejecting a
regression to the hardcoded $HOME path), retitle it, and note the migration in
the file header. The generic bare-`gsd-tools` sweeper test is unchanged —
gsd_run is a resolvable launcher, not a bare gsd-tools call.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#622): make graph.html copy optional in /gsd-graphify build chain
The Step 3 shell chain in commands/gsd/graphify.md copied graphify-out/graph.html
with an unconditional `cp` linked by `&&`. When a graph exceeds graphify's HTML
viz node limit (default 5000), `graphify update .` deliberately omits graph.html,
so the `cp` failed with "cannot stat" and aborted the chain — skipping the
GRAPH_REPORT.md copy, the diff-snapshot write, and the status report, and
reporting BUILD FAILED even though the graph data was rebuilt successfully.
Guard the graph.html copy with `{ [ -f graphify-out/graph.html ] && cp ... || true; }`,
mirroring the already-correct tolerant copy in hooks/lib/gsd-graphify-rebuild.sh.
A skipped optional HTML artifact no longer aborts the chain.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* chore(#622): add changeset for graph.html optional-copy fix
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* test(#622): CRLF-tolerant fence regex + allowlist the new graphify test
Two CI guards flagged the new regression test:
- windows-test-parity (fenceRegexLiteralNewline): the block-extraction regex
matched ```bash with a literal \n, which breaks on Windows CRLF checkouts.
Use ```bash\r?\n per the guard's sanctioned fix.
- lint-test-file-count: the new file is a 5th test in the grapify bucket
(cap 2, grandfathered at 4). Add it to the graphify allowlist files array
and give the entry a real tracking issue (TBD -> 622).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Release notes were hand-edited after every release to turn GitHub's flat
--generate-notes output into the curated Install + Feature/Enhancement/Fix
format. Add scripts/release-notes/format-github-release-notes.cjs to do this
deterministically (classifying each PR by its conventional-commit title
prefix) and wire it into the pre-release, final, and hotfix release steps so
it runs right after `gh release create --generate-notes`.
Closes#625
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#623): make release Verify-publish tolerant of npm propagation lag
The rc and latest Verify-publish steps used a single `sleep 10` + one
`npm view`, which false-failed the whole release job when npm's registry
read lagged the publish write — observed on the v1.3.0-rc.1 RC run, where
publish/tag/GitHub-release all succeeded but verification reported NOT_FOUND.
Extract the check into scripts/verify-npm-publish.cjs: a testable module
with a bounded retry/poll loop, a frozen REASON enum, and a --json mode
(mirrors verify-reapply-patches.cjs). Both workflow steps now call it.
dist-tag reporting stays informational and never fails the step, matching
prior behavior. Adds tests/verify-npm-publish.test.cjs covering retry,
exhaustion, and dist-tag reporting via injected lookups (no network).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* chore(#623): allowlist verify-npm-publish.test.cjs in the verify cluster
The test-file-count linter groups test files by production-module prefix.
scripts/verify-npm-publish.cjs lives under scripts/ (not a scanned prod
dir), so its test collapses into the existing `verify` module via the
startsWith(prefix + '-') rule — same as scripts/verify-reapply-patches.cjs,
whose tests are already allowlisted under `verify`. Add the new test to that
cluster's allowlisted set to satisfy the identity ratchet.
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>
* fix(#614): resolve gsd-tools via runtime shim in discuss-phase mode routing
The discuss-phase mode-routing snippet and the codebase-drift gate called
the bare `gsd-tools` binary. On a shim-only install (gsd-tools.cjs present
but `gsd-tools` not on PATH) the call exits 127, `2>/dev/null` hides it,
and `|| echo` silently substitutes a default — so `workflow.discuss_mode:
assumptions` was ignored and routing always fell back to standard discuss
mode. Both sites now resolve the binary through the canonical
`_GSD_SHIM_NAME` probe and call `gsd_run`. Discuss-phase fails loudly on a
genuinely missing shim (interactive — wrong mode is worse than an error);
the non-blocking drift gate uses a soft `return 127` fallback so it still
skips gracefully when nothing is resolvable.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* chore(#614): set changeset pr to 618
* fix(#614): scope to discuss-phase mode routing; revert drift-gate change
The runtime-launcher-parity invariant requires exactly one canonical
(byte-equal) gsd_run preamble per workflow .md before the first gsd_run
call. codebase-drift-gate.md has two independent gsd_run bash blocks;
hardening its first block cleanly conflicts with that invariant and risks
the auto-remap block's separate execution scope. Descope the drift-gate
hardening to a follow-up and keep this PR focused on the titled bug: the
discuss-phase mode-routing snippet now resolves gsd-tools via the runtime
shim (gsd_run) instead of the bare PATH command, so shim-only installs no
longer silently fall back to standard discuss mode. Drift-gate file
reverted to its next state; its test assertion removed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#384): make getAgentsDir runtime-aware so non-Claude installs find agents
getAgentsDir() ignored the active runtime and always returned the
claude-family __dirname-relative agents path, so on OpenCode (and other
non-Claude runtimes) checkAgentsInstalled() looked in the wrong directory
and reported agents missing even when installed. Resolve the per-runtime
global config dir via getGlobalConfigDir(runtime) (GSD_AGENTS_DIR env >
runtime arg > GSD_RUNTIME env > 'claude'), and surface agent_runtime and
agents_dir through withProjectRoot() so init diagnostics show which
directory was checked. Updates the stale W010 health-check test that
relied on the old __dirname path.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* chore(#384): set changeset pr to 617
* fix(#384): keep claude on __dirname agents path; fix test lint + leak
Address CI failures on the first cut:
- getAgentsDir now only routes NON-claude runtimes through
getGlobalConfigDir(runtime); claude retains the original __dirname-
relative agents path, which correctly resolves to <repo>/agents for
repo runs and the runtime config agents dir for real installs. This
restores validate-health / W010 checks that were regressing because the
claude default had moved off the repo-relative path.
- Revert the now-unneeded GSD_AGENTS_DIR workaround in
agent-install-validation.test.cjs (back to its next state).
- bug-384 test: use helpers.cleanup() instead of raw fs.rmSync()
(local/no-raw-rmsync-in-tests) and drop an unused var.
- Reword a doc comment that contained a literal ~/.claude/agents path,
which tripped the cline-install no-leaked-paths scanner.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#245): surface worktree.cleanup-wave SUMMARY rescue copy failure
rescueSummaryArtifacts recorded each path in the rescued set before the
copyFileSync attempt; a thrown (and swallowed) copy left the path marked
rescued, so the dirty-block filter excluded it and the worktree was
merged + removed despite the SUMMARY never being written — silent data
loss. Now a path is recorded only after a successful copy (or verified
identical dest), and a write failure is surfaced as a blocked entry with
reason 'summary_rescue_failed', failing closed instead of removing the
worktree.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* chore(#245): set changeset pr to 616
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* test(#606): guard that every same-dir require() target of a shipped hook is shipped
#606: gsd-check-update-worker.js require()'d its sibling managed-hooks-registry.cjs,
but the file was missing from HOOKS_TO_COPY, so the installer never placed it next
to the worker and the background update worker crashed silently with
"Cannot find module". The fix shipped in #611 (added the file to HOOKS_TO_COPY);
this adds the regression guard that was the issue's third acceptance criterion.
The new test scans every JS/CJS hook in HOOKS_TO_COPY for same-directory relative
requires — require('./x') and require('./subdir/x') — and asserts each target is
itself shipped: './x' is in HOOKS_TO_COPY, or './subdir/...' lives under a dir in
HOOKS_SUBDIRS_TO_COPY. require('../...') targets that escape the hooks/ dir are out
of scope (their shipping is governed by package.json "files").
Exports HOOKS_SUBDIRS_TO_COPY from scripts/build-hooks.js so the test reads the real
subdir allowlist instead of hardcoding ['lib'].
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* chore(#606): add changeset crediting installer registry fix
Fixed-type fragment so the #606 installer fix (managed-hooks-registry.cjs
now shipped) is credited in the release notes. The behavioral change landed
in #611; this records it for the changelog and credits the reporter.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* feat(#607): rebuild get-shit-done-cc → gsd-core migration
Leftover get-shit-done-cc installs poisoned the shared update cache,
causing a permanent false "update available". Rebuild the migration so a
stale old install is both harmless and actively removed.
- Per-package update cache filename (gsd-update-check-<slug>.json) in the
shared ~/.cache/gsd dir, single-sourced via package-identity; writers
stamp package_name and readers reject foreign/absent lineage. Multi-
runtime visibility preserved (same shared dir + filename across runtimes).
- New get-shit-done/bin/lib/legacy-cleanup.cjs seam: detects code-file
references to the old package + the legacy fixed-name cache across home
runtime dirs; installer auto-cleans on every install; --dry-run previews
and mutates nothing. User hooks and dev-preferences are never touched.
- update.md cache-clear globs gsd-update-check*.json across ALL supported
runtimes (adds cursor/windsurf/augment/trae/qwen/hermes/codebuddy/cline).
- Fix worker MODULE_NOT_FOUND post-install (ship managed-hooks-registry.cjs
+ degrade gracefully) so the per-package cache is always written.
- Diataxis how-to: docs/cleanup-get-shit-done-cc.md.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* chore(#607): add changeset for PR #611
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#607): set USERPROFILE alongside HOME in dry-run install test for Windows
os.homedir() reads USERPROFILE on win32, so HOME-only isolation let the
spawned installer scan the real runner home on windows-latest. Set both.
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: wire docs/agents config into AGENTS.md Agent skills section
Add the `## Agent skills` discovery block pointing the engineering
skills at the existing docs/agents/{issue-tracker,triage-labels,domain}.md
files (issue tracker, triage label mapping, single-context domain docs).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* docs: rebrand to GSD Core and restructure docs with Diataxis
Reorganise the root README and docs/ around the Diataxis framework
(tutorials, how-to guides, reference, explanation), add new how-to
guides and schema references (STATE.md / CONTEXT.md / PLAN.md /
planning artifacts), and cross-link the whole set. Update the lone
legacy gsd-build reference to open-gsd; keep internal get-shit-done/
filesystem paths unchanged (directory rename tracked separately in
open-gsd/gsd-core#604). Regenerate the ja-JP, ko-KR, pt-BR and zh-CN
localised trees to mirror the new structure.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* docs: backfill changeset PR number (#605)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Replaces the content-only coverage of the orchestrator cwd-drift guard with a
behavioral test. It extracts the guard's bash from the shipped execute-phase.md
(no reimplementation, so it tracks the deployed contract) and executes it against
real temporary git worktrees, asserting the differential exit matrix:
- feature worktree on a non-agent branch -> exit 0
- inside an agent worktree (worktree-agent-*) -> exit 1
- a SUBDIRECTORY of an agent worktree -> exit 1 (show-toplevel root resolution)
- a non-agent worktree under .claude/worktrees/ -> exit 0 (branch-namespace discriminator)
- not inside a git repo -> exit 1
Tests-only; no product behavior change. Follow-up to #48 / #590.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
The windows-test-parity ratchet greps test source for fs.rmSync-without-
maxRetries (and six other Windows-portability anti-patterns), failing when an
integer offender COUNT exceeds a frozen baseline (rmSync: 95). A count ratchet
is a Goodhart metric: fixing one offender and adding another keeps the count
constant, so a new defect slips through green. Replace it — and every other
count ratchet in the repo — with a layered, masking-proof design.
Behavioral seam test
- tests/helpers-cleanup.test.cjs proves helpers.cleanup() carries the Windows
EBUSY retry budget. cleanup() delegates retries to Node's fs.rmSync via
maxRetries (it owns no loop), so the test asserts the option contract
(recursive/force/maxRetries>0/retryDelay>0) + real-FS removal + the cwd-guard,
rather than a loop that does not exist. The EBUSY risk is now tested ONCE at
the helper, not approximated textually at every call site.
Write-time ESLint rule (AST-accurate, replaces the grep)
- eslint-rules/no-raw-rmsync-in-tests.cjs (error in tests/**/*.test.cjs) bans
raw fs.rmSync, steering to cleanup(). Catches member, computed (fs['rmSync']),
destructured and aliased forms; escape hatch is inline
`// eslint-disable-next-line local/no-raw-rmsync-in-tests -- <reason>` only.
- Migrated 336 raw fs.rmSync teardown calls across ~116 test files to cleanup().
~18 genuinely load-bearing sites (mid-test SUT/fault-injection removals,
error-swallowing or name-colliding local teardown helpers) keep the raw call
with an inline eslint-disable + reason.
Shared anti-ratchet primitive
- scripts/lib/allowlist-ratchet.cjs:
- assertWithinAllowlist: fails on NOVEL ids (new offender introduced) AND on
STALE ids (a known offender was fixed but not pruned) — identity, not count,
and a ratchet DOWN toward zero.
- assertTightCeiling: a size/length budget whose ceiling must stay within a
grace band of the high-water mark, so budgets may only tighten, never creep.
Ratchets converted onto the primitive
- windows-test-parity-guard.test.cjs: rmSync rule deleted (now ESLint-enforced);
the remaining six patterns moved from integer baselines to named-set
allowlists with ratchet-down.
- scripts/lint-test-file-count.{cjs,allowlist.json}: per-module integer counts →
named filename sets (closes the swap-a-file-keep-the-count blind spot); a
module dropping under cap now FAILS to force pruning its allowlist entry.
- enh-2790 skill-count `<= 63` → named skill allowlist (ratchets toward ~58).
Size budgets hardened (tighten-only)
- agent-size / workflow-size / feat-3039 help-tiered: ceilings lowered to the
current high-water mark and an assertTightCeiling anti-creep check added per
tier. Fixed external-contract limits (description ≤100 chars, agent ≤100 KB)
are intentionally left as-is — they are not grandfathered creeping budgets.
No user-facing behavior change (tests + tooling only); no USER_FACING_PREFIXES
touched, so no changeset fragment is required.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#214): apply OpenCode write-truncation contract to all large-file writer agents
Issue #214 / PR #598 fixed gsd-phase-researcher's OpenCode write-tool
truncation by adding a single-Write-default + sentinel-based
Write->Read->Edit incremental fallback contract to its Step 6. The root
cause is upstream opencode#18108: OUTPUT_TOKEN_MAX=32000 is shared with
the thinking budget, so a single oversized `write` tool call's JSON is
truncated mid-payload (`JSON Parse error: Expected '}'`) and OpenCode
doom-loops.
The same failure affects every GSD subagent that writes a large file in
one Write call. Mirror the phase-researcher write contract (adapted per
output filename) into the other large-file writers:
- gsd-research-synthesizer (SUMMARY.md) — extends the existing bug-222
hard-rules block with the truncation fallback as rule 6, preserving
every original rule
- gsd-planner (PLAN.md)
- gsd-executor (SUMMARY.md)
- gsd-domain-researcher (AI-SPEC.md Section 1b)
- gsd-project-researcher (.planning/research/*.md)
- gsd-ui-researcher (UI-SPEC.md)
Each keeps the single-Write default (no behavior change for Claude Code
and other non-truncating runtimes) and falls back to incremental,
sentinel-based section-by-section writes only on a truncation/invalid-tool
failure; never silently falls back to returning content.
Locked with a parametrized prompt-contract regression test mirroring the
bug-214 / bug-222 pattern across all six agents.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* chore(#214): set changeset pr to 599
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* chore: wire docs/agents config into AGENTS.md Agent skills section
Add the `## Agent skills` discovery block pointing the engineering
skills at the existing docs/agents/{issue-tracker,triage-labels,domain}.md
files (issue tracker, triage label mapping, single-context domain docs).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#214): make gsd-phase-researcher survive OpenCode write-tool truncation
OpenCode caps model output at OUTPUT_TOKEN_MAX=32000 and the thinking
budget shares that pool (upstream opencode#18108). A single oversized
`write` tool call for RESEARCH.md is truncated mid-payload, yielding
`JSON Parse error: Expected '}'`, which OpenCode misclassifies and then
doom-loops retrying identically. Short content writes fine; long
content fails 100% (reproducible, OpenCode 1.15.10).
Add a Step 6 write contract to agents/gsd-phase-researcher.md: keep the
single-Write default (no behavior change for Claude Code and other
runtimes that don't truncate), but on a truncation/invalid-tool failure
build the file incrementally via a sentinel-based Write -> Read -> Edit
sequence so no single tool-call payload is large enough to truncate;
never silently fall back to returning content (which truncates
identically). This is the upstream-recommended mitigation (write in
smaller chunks; use edit for follow-on writes).
Locked with a prompt-contract regression test mirroring the bug-222
write-contract pattern.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* chore(#214): add changeset for OpenCode write-truncation fix
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* feat(#163): tighten gsd-roadmapper granularity defaults to reduce thin-phase fragmentation
Tighten the Granularity Calibration buckets in gsd-roadmapper (Coarse 3-5->2-4,
Standard 5-8->4-6, Fine 8-12->6-10) and append inline Key guidance naming the
thin-phase failure pattern (single requirement / internal-quality goal /
task-shaped success criteria) with instruction to fold into a neighbor rather
than create a standalone phase. Implements the maintainer-approved proposal
verbatim.
Update the canonical English docs that hardcoded the old phase-count numbers:
docs/CONFIGURATION.md and docs/FEATURES.md. Translated docs are
community-maintained and are not updated per-PR (CONTRIBUTING.md language
policy).
Prompt/doc text only; no code, format, or downstream-consumer changes. Agent
size-budget and skills-awareness tests pass; full suite green.
Closes#163
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* chore(#163): add Changed changeset for roadmapper granularity tightening
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* test(#163): lock tightened gsd-roadmapper granularity buckets
source-text-is-the-product test asserting the Granularity Calibration table
holds the tightened ranges (Coarse 2-4, Standard 4-6, Fine 6-10), that no row
maps to an old bucket, and that the Key paragraph carries the thin-phase
folding guidance. Would fail if the values regress.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Closes#48. Makes the canonical worktree_branch_check fragment verify-only/fail-closed (exit 42, no git reset self-recovery), adds an orchestrator fail-closed collection rule and a cwd-drift guard at execute_waves entry. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Extracts the fail-closed worktree branch-check guard into a single canonical fragment (get-shit-done/references/worktree-branch-check.md) and repoints all five sites at it; orchestrator embeds the runnable block at dispatch. All safety invariants preserved; adversarially reviewed; full matrix green. Closes#588.
* feat(#41): extract per-commit gate_status into ship PR body TDD Audit
/gsd:ship's generate_pr_body now reconstructs the TDD gate trail that a
squash-merge would otherwise discard. A new TDD Audit section walks the
merge-base..HEAD commit range (merges excluded), reads each commit's
gate_status: trailer via Git's native trailer machinery, pairs each
test: commit with its following feat:/fix: implementation commit, and
counts commits lacking a recognized trailer as missing. A single
aggregate `gate_status: skill=N, fallback=N, exempt=N, missing=N`
trailer is emitted as the final line of the PR body so a GitHub
squash-merge carries the audit footprint into the base branch.
Hardening (per adversarial review): impl pairing is restricted to
feat:/fix: (refactor/docs/chore are skipped, never mistaken for GREEN);
the gate_status cell is normalized to a known token and never rendered
raw; commits with multiple gate_status trailers are treated as missing;
every table cell escapes pipes and strips CR/LF; records guard against
delimiter-injection from adversarial commit messages.
Scoped additively: no changes to commands, agents, templates, or SDK.
Closes#41
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* docs(#41): add changeset for ship TDD Audit enhancement
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>