* 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>
Local-install managed .sh hooks under Claude Code on Windows were wrapped with the absolute Git Bash path; Claude runs the hook string inside Git Bash, so bash tried to exec bash (cannot execute binary file). Centralizes the win32+claude+.sh guard (shellHookOmitsBashRunner) and adds an exported, testable buildLocalShellHookCommand so the local path matches the global path. Closes the #166/#377 regression in the local-install branch. Adds a Windows-covered regression test.
Fixes#580
* fix(#581): add Edit to six writer agents' tools so Edit-only discipline is enforceable
Six writer agents (gsd-eval-planner, gsd-ai-researcher, gsd-domain-researcher,
gsd-phase-researcher, gsd-ui-researcher, gsd-debug-session-manager) shipped with
Write but no Edit in their tools: frontmatter. Their spawn prompts instruct
surgical in-place section edits on existing/shared files (notably the AI-SPEC.md
trio writing disjoint sections of the same file), but with no Edit tool they
fall back to whole-file Write — silently clobbering sibling sections
(last-writer-wins) while still reporting success.
Same bug class as #571, fixed for gsd-doc-writer in #575. This adds Edit
alongside the existing Write for all six (Edit placed adjacent to Write, mirroring
the gsd-doc-writer fix). Write is retained; no prompt-body changes; no other agents
touched.
Adds a regression test (tests/agent-frontmatter.test.cjs) asserting each of the
six section-writer agents carries both Write and Edit.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore(#581): add changeset fragment for writer-agent Edit fix
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore(#581): regenerate changeset via npm run changeset
Replace hand-authored fragment with one generated by the official
scripts/changeset/new.cjs script (correct <adjective>-<noun>-<noun>
filename convention).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Closes#260
Moves the step-0b absolute-path guard from prose instructions to a harness-enforced PreToolUse hook (gsd-worktree-path-guard.js). Hard-blocks Edit/Write/MultiEdit calls whose absolute path resolves outside the active worktree root.
Squashed from claude/fervent-booth-fb7b1f. Hardens resolveModelPolicy against prototype pollution and fixes resolveModelForTier to check model_policy before dynamic_routing. 199 tests green.
Steps 2 and 4 of resolveEffortInternal now include an else branch that
consults CANONICAL_CONFIG_DEFAULTS.effort when effortCfg is null, mirroring
the existing Step 3 manifest-fallback pattern for routing_tier_defaults.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(#488): add gsd-tools effort sync command to re-apply effort config to installed agents
Effort frontmatter is injected at install time, but there was no way to propagate
config changes (agent_overrides, routing_tier_defaults, default) without a full reinstall.
- Adds `cmdEffortSync` to commands.cjs: scans `<configDir>/agents/gsd-*.md`, resolves
the current effort per agent via `resolveEffortInternal` + `renderEffortForRuntime`,
and rewrites (or injects) the `effort:` frontmatter idempotently.
- Dry-run mode (default) reports pending changes without writing; `--apply` writes.
- Accepts `--config-dir` and `--runtime` overrides; gracefully no-ops on non-claude runtimes.
- Wires the `effort sync` subcommand into `gsd-tools.cjs` and adds it to the help list.
- Five regression tests cover dry-run, apply, no-op, inject-missing, and non-claude runtime.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(#488): add gsd-tools effort sync command to re-apply effort config to installed agents
Effort frontmatter is injected at install time, but there was no way to propagate
config changes (agent_overrides, routing_tier_defaults, default) without a full reinstall.
- Adds `cmdEffortSync` to commands.cjs: scans `<configDir>/agents/gsd-*.md`, resolves
the current effort per agent via `resolveInstallTimeEffort` + `renderEffortForRuntime`,
and rewrites (or injects) the `effort:` frontmatter idempotently.
- Uses install-time resolvers (readGsdEffectiveEffortConfig from bin/install.js) rather
than the runtime resolver (loadConfig), so home-level effort changes in ~/.gsd/defaults.json
are correctly picked up even when a project .planning/config.json exists.
- Skips symlinks in agents dir to avoid clobbering symlink targets.
- Dry-run mode (default) reports pending changes without writing; --apply writes.
- Accepts --config-dir and --runtime overrides; gracefully no-ops on non-claude runtimes.
- Rejects unexpected positional arguments in the CLI parser.
- Wires the effort sync subcommand into gsd-tools.cjs and adds it to the help list.
- Eight regression tests: dry-run, apply, noop, inject-missing, non-claude runtime,
home-config gap scenario, CLI positional-arg rejection, and CLI dispatch integration.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(#570): scope Codex leak scanner to manifest, replace bare ~/.claude refs
Two root causes:
- scanForLeakedPaths walked entire ~/.codex tree, flagging pre-existing
unrelated files; now reads gsd-file-manifest.json to scope scan to
GSD-owned artifacts only
- convertClaudeToCodexMarkdown replaced ~/\.claude/ (slash form) but not
bare ~/\.claude\b; gsd-debugger.toml and gsd-surface/SKILL.md examples
slipped through; bare word-boundary replacement now added
- writeManifest tracked agents/gsd-*.md but Codex installs .toml files;
manifest now also records .toml agent files so the scoped scanner covers them
Closes#570
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* chore: add changeset fragment for #570
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Remove three source-grep describe blocks from bug-1834 and bug-2136 test
files that anchored on byte-offset windows in install.js source. The same
regressions are already fully covered by the E2E behavioral tests (Section 1
in bug-1834, Part 4 in bug-2136) that invoke the actual installer and inspect
the installed files directly.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(#571): forbid Write in doc-writer fix mode; add workflow truncation guard
gsd-doc-writer in fix mode only had Write in its tools list, so when
correcting a specific failing claim it would re-emit the whole file with
only the lines it had in context — truncating untracked docs with no git
recovery path.
Fix 1 (root cause): add Edit to the agent tools frontmatter and rewrite
fix_mode instructions to mandate Edit for surgical corrections and
explicitly forbid Write on existing files. Also reinforced in
critical_rules.
Fix 2 (safety net): add a post-fix line-count guard in the fix_loop step
of docs-update.md. If the file shrank by >90% after a fix agent runs,
the orchestrator restores the file from the existing_content it captured
before dispatch and logs a WARNING. This makes the previously
unrecoverable case recoverable.
Regression test: tests/bug-571-doc-writer-fix-mode-edit-only.test.cjs
covers both the agent contract and the workflow guard.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* chore: add changeset for fix#571
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(#571): address codex adversarial review findings
- Quote {doc_path} in shell snippets to handle paths with spaces (#SECURITY)
- Clarify corrupted doc re-verification vs re-fix distinction (#CORRECTNESS)
- Strengthen regression tests with structural ordering assertions (#REGRESSION)
- Move docs-update.md from global ALLOWLIST to SIZE_ONLY_WORKFLOWS so
injection scanning still runs while only the 50K size finding is exempt (#SECURITY)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(#49): provider-neutral model policy presets
Adds model_policy config surface with known-provider presets (openai/anthropic/google/qwen) and generic provider escape hatch. model_policy.runtime_tiers resolves before legacy model_profile_overrides. reasoning_effort is stripped for unsupported runtimes.
Closes#49
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(#49): replace unregistered /gsd-settings-advanced token in docs
docs-parity-live-registry enforces every /token in docs/*.md maps to
a live command. /gsd-settings-advanced is a workflow filename, not a
registered command — use /gsd:settings instead.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(#49): update INVENTORY.md count and manifest for config-types.cjs
inventory-counts and inventory-manifest-sync tests require the headline
count and INVENTORY-MANIFEST.json to reflect every file in bin/lib/.
config-types.cjs (new module added by feat(#49)) was missing from both.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
ADR-0174 retired @opengsd/gsd-sdk; sdk/ no longer exists. Removes the
sdkSrcExists guard + unused existsSync/join imports + sdk/dist/** ignore
from eslint.config.mjs, and drops the stale GENERATED comment + exclusion
for configuration.cjs (hand-authored since SDK removal) from stryker.config.mjs.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* enhancement(#558): add liveness hints to all GSD spawn announcements
Append '(runs in a subagent — no output until it returns, ~1–5 min; expected,
not a freeze)' inline to every ◆ Spawning… banner and subagent dispatch
instruction across 26 workflows. Silent subagents look identical to frozen
sessions — this note sets the expectation so users wait instead of killing
healthy in-progress work.
Changes:
- references/ui-brand.md: document liveness convention under Spawning Indicators
- 10 banner workflows: append liveness note to ◆ Spawning… lines in-place
- 18 subagent-only workflows: add print instruction with liveness phrase
- tests/spawn-liveness-banner.test.cjs: new test; fails if any workflow with
subagent_type omits 'runs in a subagent'
- docs/USER-GUIDE.md: troubleshooting entry for frozen-looking spawns
- .changeset/558-spawn-liveness-banner.md: changeset fragment
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(#558): add missing pr field to changeset fragment
The changeset lint requires pr: <NNN> in frontmatter; the fragment was
written without it, causing parse.cjs to reject it.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(#558): address codex review — missed spawns and tighten test
- plan-phase.md: add liveness note to chunked outline planner and
per-plan chunked planner banners (two missed ◆ Spawning… lines)
- quick.md: add liveness note to research banner and add missing
display line before planner spawn in Step 5
- plan-review-convergence.md: add liveness note to initial planning
and review-agent spawn Display lines
- docs-update.md: add Print instructions with liveness note before
gsd-doc-verifier spawns in Phase 1 and Phase 2
- autonomous.md: add Print instruction with liveness note before
background plan-phase agent dispatch in step 3b
- tests/spawn-liveness-banner.test.cjs: replace single file-level
check with two assertions:
(1) every ◆ Spawning… banner line carries the phrase on that line
(2) every file with subagent_type contains the phrase somewhere
The tighter test would have caught all five missed spawns.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(#558): tighten spawn-liveness test regex to catch spawn-word-anywhere variants
Previous SPAWN_BANNER_RE only matched ◆ immediately followed by Spawning|spawning.
Replace with /◆[^\n]*\bspawning?\b/i which matches the spawn word anywhere on the
◆ line — catching "◆ Chunked mode: spawning outline planner..." and similar.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(#558): rename changeset to PR number 566 and correct pr field
Changeset was filed as 558-spawn-liveness-banner.md (issue#) but the
convention is the PR number. Renamed to 566-spawn-liveness-banner.md
and updated pr: 558 → pr: 566 so release notes link to the right PR.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* enhancement(#40): integrate branch pruning into /gsd-cleanup archival workflow
Adds a prune_local_branches step to cleanup.md (between archive_phases and
commit) that force-deletes local branches whose upstream is gone — keeping
local clones symmetric with delete_branch_on_merge on GitHub.
Key design choices vs. PR #562 (the local-model draft):
- dry-run step shows stale branches using cached tracking refs only; git
fetch --prune is deferred to the execution step so the dry-run is
non-side-effecting
- awk uses { if ($1 != "*") print $1 } form to explicitly exclude the
currently checked-out branch (the * prefix in git branch -vv output),
not a prose note that lets xargs receive literal * as an argument
- git fetch --prune runs exactly once, in prune_local_branches, eliminating
the TOCTOU window between a preview fetch and an execution fetch
- two new negative-contract tests: identify_completed_milestones must not
run git branch commands; show_dry_run must not run git fetch --prune
Closes#40
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* chore: regenerate changeset using repo script (correct format)
Replaces hand-written fragment (used `/** ... */` comment syntax)
with one generated by `npm run changeset -- --type Changed --pr 562`.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: address codex review blockers — protect main/next/trunk, align dry-run with execution
Codex adversarial review (pre-PR gate) flagged two blockers:
1. awk filter only excluded '*' (current branch) but not protected names.
main/next/trunk/develop could be force-deleted if their upstream was
gone. Fix: use !~ /^\*$|^main$|^next$|^trunk$|^develop$/ regex match.
2. Dry-run enumerated from cached tracking refs; execution re-ran
git fetch --prune, creating a TOCTOU window between what the user
confirmed and what got deleted. Fix: move git fetch --prune into
show_dry_run (prefetch for display accuracy); prune_local_branches
now enumerates from the already-fetched state with no second fetch.
Updated 14 structural tests to match new design (added protected-name
exclusion test; inverted show_dry_run fetch assertion).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
- Import `extractCurrentMilestone` from `./core.cjs` into `state.cjs`
- Replace `getMilestonePhaseFilter.phaseCount` usage in `buildStateFrontmatter`
with a direct ROADMAP parse using the same digit-anchored pattern as
`roadmap.analyze` — single source of truth for `total_phases` (#549)
- Apply the same replacement in `cmdStateSync` for consistency
- Add regression test: bug-549-total-phases-overcounts-with-phase-section-heading.test.cjs
- Add changeset fragment: .changeset/549-total-phases-decimal-overcounting.md
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(#557): Milestone marked complete while ROADMAP lists unstarted phases
- Fix 1 — Broaden extractCurrentMilestone() (core.cjs):
(a) Extend sectionPattern to also match <summary> tag content: when a
milestone version appears only inside a <summary> tag, locate the
enclosing <details> block and return its content directly instead of
falling through to stripShippedMilestones().
(b) Extend activeMarkerPattern to include 🔄: change
/\b(?:STARTED|ACTIVE|WIP)\b|in\s+progress|🚧/i to also match 🔄.
(c) Extend the Step 2 fallback regex from /🚧\s*\*\*v(\d+\.\d+)\s/ to
/(?:🚧|🔄)\s*\*\*v(\d+\.\d+)\s/ so the emoji-only fallback also
catches 🔄.
- Fix 2 — Add completion guard (milestone.cjs):
Before writing "milestone complete" to STATE.md, check whether any phase
in the current milestone has no directory on disk (disk_status:
no_directory). When STATE.md milestone version matches the version being
completed and unstarted phases are found, emit an error. Re-run with
--force to override.
- Fix 3 — Add W021 health check (verify.cjs):
Check 14 (W021): if STATE.md status contains "milestone complete" or
"archived", scan the current milestone section of ROADMAP.md; if any
phase has no directory on disk, emit W021 warning: "STATE says milestone
complete but ROADMAP lists N unstarted phase(s)".
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(#557): replace hand-written changeset with generated fragment
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(#557): address Codex review findings
- core.cjs: add (?!Phase\s+\S) to sectionPattern so phase headings that
mention the milestone version (e.g. ### Phase 1: v1.3 migration) cannot
bypass the <summary> fallback path
- milestone.cjs: replace loose numeric-prefix matching with phaseTokenMatches
+ normalizePhaseName so decimal (2.1) and letter-suffix (12A) phase IDs
are handled correctly in the completion guard
- verify.cjs: same correction in W021 check; also add phaseTokenMatches to
core.cjs import
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(#557): fix getMilestonePhaseFilter version-heading false match
getMilestonePhaseFilter's sectionPattern also lacked the (?!Phase\s+\S)
exclusion that extractCurrentMilestone received in the previous commit.
A phase title like "### Phase 4: v1.3 migration" could match as the
milestone section start, mis-scoping the phase set used for completion
stats and archive.
Also handle the case where the version lives only in a <summary> tag:
when there is no heading match but a <summary> match exists, skip
setting missingExplicitVersion so milestone.complete does not incorrectly
error with "no phases found".
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Fixes#38
Removes the `"approved" → continue` ack-and-advance shortcut from the `human_needed` verification path in execute-phase. The phase now stays pending until `/gsd:verify-work` completes the UAT and triggers its auto-transition — enforcing the invariant that ROADMAP advances only after a completed verification record.
Also fixes: UAT file format mismatch (`status: testing` + correct `## Current Test` key shape), filename alignment (`{phase_num}-UAT.md`), explicit ack handler covering legacy `approved` keyword, stale `HUMAN-UAT.md` references in agent/reference files.
The @opengsd/gsd-sdk package boundary was retired (ADR-0174, #191/#192) and
the sdk/ tree is no longer tracked. ADR-0174's Supersedes table explicitly
records that the generator-based Shared-Module hand-sync lint is deleted as
part of that collapse. This removes the now-orphaned machinery it left behind:
- scripts/lint-shared-module-handsync.cjs — paired bin/lib/*.cjs files with
sdk/src/**/*.ts sources that no longer exist; wired into no CI workflow or
npm script (dead).
- scripts/shared-module-handsync-allowlist.json — the lint's allowlist; every
entry pointed at a non-existent sdk/src source / generated artifact /
freshness check.
- tests/lint-shared-module-handsync.test.cjs — tested the deleted lint.
Docs corrected to match:
- CONTRIBUTING.md — removed the "CJS↔SDK seam" instruction (it linked the
already-deleted docs/agents/cjs-sdk-seam.md and told contributors to
maintain the allowlist under a retired generator pattern).
- docs/prd/3524-cjs-sdk-hard-seam.md + docs/prd/README.md — marked the PRD
Superseded by ADR-0174, matching the already-superseded ADR-3524.
Added tests/no-cjs-sdk-handsync-tooling.test.cjs as a regression guard so the
retired tooling stays removed and is not silently re-wired into package.json.
ADR-3524 is left in place (already Superseded by ADR-0174); runtime modules and
regression tests that cite it in comments keep resolving. No user-facing change.
Closes#556
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
The GENERATED_CJS_IGNORES array in eslint.config.mjs wrongly listed 12
hand-written bin/lib/*.cjs modules as "generated" and excluded them from
linting. Genuinely-generated artifacts are already covered by the
**/*.generated.cjs glob and the ADR-457 semver-compare.cjs entry, so the
array only created a coverage gap. Remove it so the 12 modules are linted
under the existing get-shit-done/bin/**/*.cjs ruleset.
Linting them surfaces two dormant dead-code findings, both removed here:
- phase-lifecycle.cjs: stale `eslint-disable-next-line no-cond-assign`
directive — the loop already uses the parenthesized-assignment form the
rule permits by default, so it suppressed nothing.
- state-document.cjs: vestigial `tempField`/`tempDefaults` locals (and
their comment) left over from a refactor to an inline `.some(...)` check.
Pure dead-code/lint-config cleanup; no user-facing behavior change.
Closes#552
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#551): lint hand-written bin/lib/*.cjs mislabeled as generated
GENERATED_CJS_IGNORES excluded 12 hand-written runtime modules from the
ADR-452 ESLint harness. None carry an @generated header and no generator
emits them — they are hand-written, not generated. Remove the mislabeled
list so they lint like the rest of get-shit-done/bin/**/*.cjs. The genuinely
tsc-generated semver-compare.cjs keeps its own separate ADR-457 ignore.
Also drop the stale "Type-aware via parserOptions.project=tsconfig.lint.json"
comment on the .cjs block: that block sets no parser/project and enables no
@typescript-eslint rules; type-aware linting lives in the src/**/*.cts block.
Lint stays green (0 errors); 2 pre-existing warnings surface (already warn
severity) per the file's warn-first convention, tracked as follow-up cleanup.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* test(#551): guard ESLint coverage of hand-written bin/lib/*.cjs
Asserts via ESLint's isPathIgnored API that every get-shit-done/bin/lib/*.cjs
without an @generated header or a src/<name>.cts|.ts source is linted (not
ignored), and that the genuinely tsc-generated semver-compare.cjs stays
ignored (ADR-457). Fails 13/14 against the pre-fix config; passes on the fix.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* docs(#457): rewrite ADR-457 to ground truth and accept build-at-publish
The prior draft asserted a codebase state that never existed (13 tsc-generated
files, src/ trees, a tests/cjs-ts-parity.test.cjs). Corrected to verified ground
truth (84 bin/lib .cjs, 1 value-baked package-identity.cjs, no tsc pipeline),
distinguished value-baking from transpilation so package-identity stops being
miscited as precedent, made check-in-the-artifact vs build-at-publish the central
decision, and flipped status to Accepted (build-at-publish).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* build(#537): pilot TS build-at-publish for bin/lib (semver-compare)
First hand-written module collapsed to a TypeScript source of truth per ADR-457.
src/semver-compare.cts compiles (tsc, strict, noEmitOnError) to a gitignored
get-shit-done/bin/lib/semver-compare.cjs. build:lib is wired into build, pretest,
pretest:coverage, and prepublishOnly so the artifact is built before test and
shipped on publish. Type-aware ESLint on src/**/*.cts immediately caught the
params were over-typed as `unknown` (no-base-to-string); narrowed to a honest
VersionInput domain type. Behavior preserved: semver-compare.test.cjs (14) and
bug-10 (4) pass against the generated output; runtime consumer changeset/cli.cjs
unaffected.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#537): make build-at-publish robust across all CI paths (codex review)
Adversarial review found the pilot's generated artifact would be missing on
clean CI checkouts. `pretest`/`pretest:coverage` only fire for `npm test`, but
CI runs `test:unit`/`test:integration`/`test:install` and `node run-tests.cjs`
directly — none of which built the artifact, so any suite requiring
semver-compare.cjs would hit module-not-found on a clean checkout, and
install-smoke's `npm pack` could ship without it.
- Add a `prepare` script (`npm run build:lib`). `npm ci` runs it automatically,
so every CI test job and install-smoke's pack emit the artifact before use.
This is the idiomatic npm mechanism for compiled-output-not-in-git and fixes
both the test and pack paths in one place.
- Add `src/` + `tsconfig.build.json` to ci-test-scope and the install-smoke /
mutation path filters, so a source-only edit to a migrated module still
triggers its tests and mutation coverage (prevents silent CI skips).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#537): map src/*.cts to built artifact in mutation changed-files detection
Follow-up to the codex re-review. The prior commit added src/**/*.cts to the
mutation workflow's path trigger but left its "compute changed core lib files"
step diffing only get-shit-done/bin/lib/**/*.cjs — which are now gitignored and
never appear in a diff. A source-only edit would trigger the workflow then
early-exit ("no core lib files changed"), silently skipping mutation testing.
Map each changed src/*.cts to its built get-shit-done/bin/lib/*.cjs path (the
on-disk artifact Stryker mutates after prepare/build:lib), merge with the
hand-written .cjs diff, and apply the test/excluded-module filters once.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#537): use 'src/' pathspec in mutation diff (git glob doesn't match top-level)
Codex review caught that `git diff -- 'src/**/*.cts'` returns empty for a
top-level file like src/semver-compare.cts — git's default pathspec glob does
not match `**` across zero directories (verified on git 2.50.1). The prior
commit's src-detection therefore never fired, so source-only changes still
skipped mutation. Switch to the dir-scoped pathspec 'src/' + a `.cts` grep
(robust for flat and nested layouts), and broaden the workflow path trigger to
'src/**' to match install-smoke. Verified end-to-end: a change to
src/semver-compare.cts now resolves to get-shit-done/bin/lib/semver-compare.cjs
in the --mutate list.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* chore(#537): add changeset fragment for build-at-publish pilot
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#537): replace prepare with prepack + build-if-missing; defer mutation wiring
CI surfaced three real issues the local run and codex review missed:
1. lockfile-sync failed on every platform. Root cause: `npm ci --dry-run` (the
repo's lockfile health check) RUNS the `prepare` script, but in dry-run the
devDependencies aren't installed, so `tsc` is not found (exit 127) and the
check reports a misleading "out of sync". `prepare` is the wrong hook for a
build needing a devDep. Replace it with `prepack` (runs only on pack/publish,
when node_modules exists) for the tarball path, and build the artifact inside
scripts/run-tests.cjs (build-if-missing) for the test path — the universal
chokepoint every CI test invocation funnels through, including the direct
`node run-tests.cjs --files-from` step that bypasses npm lifecycle hooks. The
guard is a no-op once built, so the run-tests harness test is unaffected.
2. The Stryker mutation gate ran only 1 test against semver-compare (~0% score,
71/71 mutants surviving) — a Stryker test-selection problem orthogonal to the
build migration, and raising the score needs property tests (ADR-456). Revert
the mutation.yml src wiring; mutation coverage for src-authored modules is a
separate follow-up tracked in #537. (The deletion of the gitignored top-level
.cjs does not match the workflow's `bin/lib/**/*.cjs` git pathspec, so the
gate skips cleanly.)
Verified: clean-room `npm ci --dry-run` exits 0; deleting the artifact then
running a suite rebuilds it; run-tests harness 22/22 green; `npm pack` includes
the built artifact via prepack.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#448): resolve UI safety gate helper against the GSD install dir
The §5.6 UI Design Contract Gate (and autonomous §3a.5) resolved
ui-safety-gate.cjs via `git rev-parse --show-toplevel`, i.e. the
consuming project's git root — which has no bin/lib. The node call
failed, its exit code was conflated with "no UI", and the gate
silently no-opped so frontend phases skipped the UI-SPEC prompt.
Resolve the helper against the GSD install dir via RUNTIME_DIR (the
same idiom §1 uses for gsd-tools), with git-toplevel and $HOME/.claude
fallbacks. When the helper genuinely can't be found, fail OPEN with a
stderr warning (assume UI present) rather than silently skipping.
Tests: bug-3706 structural guard now requires RUNTIME_DIR resolution
and forbids the consuming-project GSD_REPO_ROOT anchor; a new
behavioral test resolves and runs the helper from a temp consuming
project (no bin/lib) with RUNTIME_DIR set. autonomous-ui-steps updated
to match.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* docs(#448): add changeset fragment for PR #539
* fix(#448): add get-shit-done/bin/lib/ to UI gate probe and deploy helper there
The installer copies get-shit-done/ to the target but not root bin/lib/, so
the helper was never found for installed users. Placing ui-safety-gate.cjs in
get-shit-done/bin/lib/ ensures the installer deploys it, and probing that path
first makes the gate work correctly in installed runtimes.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* chore: update changeset to cover get-shit-done/bin/lib/ deployment
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* chore: update inventory for ui-safety-gate.cjs in get-shit-done/bin/lib/
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#447): scope post-planning gap analysis to phase_req_ids
§13e gap-analysis diffed the entire REQUIREMENTS.md against a phase's
plans even when the phase mapped no REQ-IDs, so a phase mapping nothing
reported every unrelated project requirement as "not covered".
Teach the gap-analysis CLI a --phase-req-ids option (null/TBD skips the
requirements comparison, an ID list scopes to it, absent = unchanged
back-compat) and have §13e pass the phase's mapped IDs — mirroring the
§13 Requirements Coverage Gate's null/TBD skip. CONTEXT.md decisions stay
in scope regardless.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#447): source phase_req_ids from init.plan-phase, not roadmap.get-phase
Adversarial-review follow-up. roadmap.get-phase returns the raw phase
markdown (not JSON), so `--pick phase_req_ids` yielded nothing and §13e
would have skipped the requirements comparison for EVERY phase — a
silent regression. phase_req_ids is exposed by init.plan-phase; switch
§13e to it. Adds an integration test asserting the query exposes the
IDs and that gap-analysis scopes to them (and skips when unmapped).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* docs(#447): add changeset fragment for PR #538
* fix(#447): surface mapped REQ-IDs absent from REQUIREMENTS.md as explicit missing rows
Previously, a phase_req_ids entry not found in REQUIREMENTS.md was silently
dropped from the gap report, allowing the analysis to falsely report full
coverage when the requirement document had drifted.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* chore: update changeset to cover missing-REQ-ID detection
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>