Commit Graph

46 Commits

Author SHA1 Message Date
Tom Boucher
de3ba45d00 test(#1971): consolidate 48 gsd-tools CLI regression tests into subcommand suites
Fold 48 issue-named gsd-tools CLI regression files into the canonical test file
that owns each subcommand subject (state, roadmap, phase, milestone, audit, config,
router/dispatch, stats, verify, health, etc.), preserving every assertion and its
origin issue number as provenance (block-scoped describe wrappers, 299 subtests
conserved 1:1). No monolithic gsd-tools.test.cjs created — routes into 18 existing
per-subject suites.

Removes 48 tests/ files. Regenerates regression-name allowlist (271->231), ratchets
the file-count allowlist across 6 buckets (audit/milestone/phase/roadmap/state/verify),
and makes 10 relocated allow-test-rule exemptions issue-ref-compliant (ADR-456; prunes
10 stale ids). Repoints one CONTEXT.md symptom ref and ADR-3524's parity-test ref.
lint:ci green.

Part of epic #1969. Closes #1971.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 01:55:48 -04:00
Tom Boucher
f08b177215 feat(#1726): G1-G6 portability AST rules; fix all offenders; delete the ratchet (Phase 4) (#1731)
Phase 4 of epic #1702. Closes #1726.
2026-06-25 18:24:39 -04:00
Tom Boucher
51c5497460 fix(#1668): restore CRLF By-Phase row-upsert regression (resolved via #1655) (#1669)
#1668 (phase complete did not persist the By-Phase row on a CRLF STATE.md) no longer
reproduces on next: #1655's restructure of updatePerformanceMetricsSection (By-Phase table
upsert now runs BEFORE the velocity manipulation) resolved it as a side effect. Verified
clean on next (CRLF STATE.md -> phase complete -> row upserted, placeholder removed,
velocity derived). Restore the #1658 test's full row-upsert + placeholder assertions that
#1667 had relaxed (they now pass), locking the fix.
2026-06-24 16:50:50 -04:00
Tom Boucher
c583bcc02c fix(#1659): dedup By-Phase rows across padded/unpadded phase numbers (#1663)
* fix(#1659): dedup By-Phase rows across padded/unpadded phase numbers

phaseRowPattern matched the phase number literally (escapeRegex(String(phaseNum))),
so a seeded zero-padded row '| 05 |' was not matched by 'phase complete 5' (pattern '| 5 |'),
producing a duplicate row that double-counted the phase. Canonicalize a numeric phase to
its integer form (Number('05')===Number('5')===5) and match with a 0* prefix so 5/05/005
all collapse to the same row in either direction. Regression folded into state.test.cjs:
seeded '| 05 |' + 'phase complete 5' yields exactly one phase-5 row. Non-numeric phase IDs
retain the literal escapeRegex match.

* chore(#1659): backfill changeset pr ref to 1663

* fix(#1659): add verification fixture to padded-dedup test under #1522 gate
2026-06-24 14:41:45 -04:00
Tom Boucher
ff161f2281 fix(#1582): derive phase-complete velocity from By-Phase table (idempotent) (#1655)
* fix(#1582): derive phase-complete velocity from By-Phase table (idempotent)

updatePerformanceMetricsSection blind-added summaryCount onto the prior velocity
total on every phase complete, so re-running phase complete on an already-complete
phase incremented the total each time (the sibling of #4, which fixed the Completed
Phases counter the same way). The velocity total is now derived as the sum of the
By-Phase table's Plans column AFTER the row upsert — re-completing a phase upserts
the same row, so the sum is stable; a hand-edited inflated total self-heals downward
to the true sum on the next completion. When the By-Phase table is absent the total
is left unchanged (no crash). Strengthens the misnamed 'idempotent' test (its comment
explicitly declined to assert velocity idempotency — the latent gap) and adds a
self-heal regression; corrects the #320 behavior-lock velocity assertion which had
encoded the blind-add (3 = 1+2 double-count) — the derived value is 2.

* chore(#1582): backfill changeset pr ref to 1655

* fix(#1582): velocity sum tolerates indented By-Phase rows (codex review)

Adversarial review (codex, gpt-5.5/high) flagged that byPhaseTablePattern's
data-row capture allows leading whitespace ([ \t]*\|), but the derive sum was
anchored at ^\| and would skip indented hand-edited/legacy rows — capturing them
in the table but silently undercounting. Align the sum regex (^\s*\|) with the
table capture's tolerance. Adds an indented-row regression. Two other codex
findings are pre-existing and out of scope: padded/unpadded phase dedup
(phaseRowPattern, identical in old code — derive yields the same value as the old
blind-add) and CRLF tables (the shared byPhaseTablePattern header requires bare
\n, so the upsert was already broken on CRLF; the fix changes stale-vs-
double-count, does not worsen it).

* fix(#1582): add verification fixtures to velocity tests under #1522 gate

Post-rebase onto next+#1548, the #1582 velocity tests (self-heal, indented-row) use
phase complete, which now fail-closes under #1522's canonical verification gate without a
passed *-VERIFICATION.md. Add writePassedVerification(tmpDir,'02-next','02') to both.
2026-06-24 14:36:01 -04:00
Tom Boucher
f202d243cb fix(#1658): #1658 test — add verification fixture + assert velocity (CRLF downstream bug filed) (#1667)
Under #1522's verification gate (#1548), phase complete fail-closes without a passed
VERIFICATION.md, so the #1658 test's fixture needed one. The By-Phase *row* upsert on CRLF
has a separate downstream bug (phase complete updates velocity + status but doesn't persist
the table row on CRLF, while it does on LF; byPhaseTablePattern matches CRLF — verified
directly), tracked separately. Relax the assertion to phase-complete-succeeds + velocity-
updates, proving CRLF STATE.md is processed end-to-end.
2026-06-24 14:21:37 -04:00
Tom Boucher
80607bec93 fix(#1658): make byPhaseTablePattern CRLF-tolerant on STATE.md tables (#1662)
* fix(#1658): make byPhaseTablePattern CRLF-tolerant on STATE.md tables

byPhaseTablePattern required a bare \n after the header and separator rows, so a
STATE.md with CRLF (\r\n) line endings (Windows, or hand-edited) had its By-Phase
table treated as absent: phase complete never upserted the row (and the velocity-from-
table derivation went stale). Make the header/separator terminators and the closing
lookahead CRLF-tolerant ([ \t]*\r?\n, (?=\r?\n|$)). Backward-compatible with LF.
Regression folded into tests/state.test.cjs: phase complete on a CRLF STATE.md upserts
the row and removes the placeholder. CONTRIBUTING's QA matrix lists Mixed CRLF/LF as a
required parser case.

* chore(#1658): backfill changeset pr ref to 1662
2026-06-24 13:52:47 -04:00
Jeremy McSpadden
77c7b4fc9d fix(#1522): enforce canonical verification before phase transition (#1548)
* fix: require fresh phase verification before transition

* no-mistakes(review): Fix canonical verification closeout gates

* no-mistakes(review): Fix verify-work frontmatter promotion command

* no-mistakes(review): Fix stale verification gates

* no-mistakes(review): Fix canonical verification routing gates

* no-mistakes(review): Fix verification dependency and runtime routing gates

* no-mistakes(review): Block stale verification bypasses

* fix: handle large init manager outputs in verification workflows

* chore: update changeset pr number

* fix(verify-work): use fresh verification.status for stale gate

The stale check after UAT used phase_completion.verification_status from
session-start INIT while human_needed promotion already queried fresh
verification.status. Align the stale gate with the canonical query so
mid-session verification refresh is not ignored.

* fix(init): skip roadmap-checked phases when selecting next_phase

Roadmap-only phases without a disk directory were still promoted to
next_phase when their checkbox was already checked. Exclude
checkboxComplete phases so progress routing does not point at work the
roadmap already marks done.

* fix: gaps_found not overridden by stale, transition uses canonical verification

- verification.cts: check gaps_found before stale so gap-closure routing
  is not masked by a newer summary mtime
- phase.cts: remove redundant findStaleVerificationSummary — readVerificationStatus
  already handles stale detection
- transition.md: replace raw grep on file content with verification.status query
  to avoid false-positive blocks from body text matching

* ci: retrigger tests after rebase

* fix(transition): replace gsd_run advisory check with awk frontmatter extraction

The runtime launcher is not defined until the update_roadmap_and_state step
bash block (~line 165). The early verify_completion block used gsd_run to
query verification.status, which violated the runtime-launcher-parity test:
'preamble appears AFTER the first gsd_run reference'.

Replace the gsd_run call with an awk-based frontmatter extractor that reads
only the status: field between the two --- fences. This avoids both the
preamble-ordering constraint and the original false-positive grep bug where
body text like 'previous_status: gaps_found' would match a full-text regex.

The phase.complete gate at update_roadmap_and_state is the canonical
enforcement point; this early check is advisory only.

Also update workflow-size-baseline.json for the updated transition.md size.

Fixes: runtime-launcher-parity test (B)

Co-authored-by: Codesmith <codesmith-bot@users.noreply.github.com>

* fix: re-check verification under planning lock in phase complete

Move readVerificationStatus into withPlanningLock so stale verification
cannot slip through when a SUMMARY.md is written between the gate and
the roadmap/state mutation. Return the blocked status from the lock
callback and emit the error after release to avoid leaving .lock behind.

* fix(transition): gate on canonical verification.status including stale

Replace awk frontmatter read with verification.status query so transition
blocks when summaries are newer than VERIFICATION.md, matching phase.complete
and other workflows (autonomous, progress, verify-work).

* Fix workflow verification gates for yolo transition and stale routing

Require VERIFY_STATUS passed before yolo/interactive transition advance.
Route stale verification recovery to verify-work, matching canonical projection.

* fix(transition): use verification.status query for stale-aware advisory check

The awk-based check read raw frontmatter status: passed, which misses the
stale case where summaries are newer than the VERIFICATION.md file even
though the frontmatter still says passed. The stale status is computed from
file modification times, not stored in frontmatter.

Move the preamble to the verify_completion bash block (the first block with
a gsd_run call) so gsd_run query verification.status can be used for the
advisory check. This gives the full readVerificationStatus logic including
mtime-based staleness detection, matching the enforcement gate at phase.complete.

Capture full JSON (VERIFY_JSON) so next_action can be included in the
advisory output alongside the status.

Also update workflow-size-baseline.json for the updated transition.md size.

Co-authored-by: Codesmith <codesmith-bot@users.noreply.github.com>

* ci: trigger test matrix for 525b946

Co-authored-by: Codesmith <codesmith-bot@users.noreply.github.com>

* fix(transition): restore awk frontmatter extraction for pre-shim verification check

The gsd_run launcher shim is not defined until line ~163 of transition.md,
so the verification debt check at line ~80 cannot use gsd_run. Restore the
awk-based frontmatter extraction that correctly reads status without needing
the runtime, and restore the shim at its proper location before
phase.complete.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(#1522): clarify transition verification gate wording

* fix(#1522): update transition workflow size baseline

* fix(#1522): update workflow-size-baseline after rebase onto next

Co-authored-by: Codesmith <codesmith-bot@users.noreply.github.com>

* fix(#1522): guard findStaleVerificationSummary FS calls + thread opts.fs seam (review)

Address review blocker B1 on #1548: findStaleVerificationSummary ran fs.readdirSync
and two fs.statSync calls unguarded between readVerificationStatus's try/catch sections,
so a TOCTOU race (a SUMMARY listed by scanPhasePlans then removed before statSync) or any
FS error threw uncaught into callers NOT under the planning lock (init.manager /
init.progress / uat-predicate). Wrap the body in try/catch degrading to 'not stale', and
thread the injectable opts.fs seam (add statSync to FsLike, pass fsImpl from the caller)
for parity with readVerificationStatus's no-throw contract and testability. Also adds the
Verification Module glossary entry to CONTEXT.md (review B3).

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Codesmith <codesmith-bot@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Tom Boucher <trekkie@nomorestars.com>
2026-06-24 13:19:10 -04:00
Tom Boucher
ce8bcb1b95 refactor(#1398): migrate state.cts section-collects onto markdown-sectionizer seam, byte-identical STATE.md (epic #1372 T6) (#1399)
* refactor(#1398): migrate state.cts section-collect regexes onto markdown-sectionizer seam (epic #1372 T6)

Replace ≈18 hand-rolled `/(heading)([\s\S]*?)(?=stop)/` regex splices in
state.cts with direct `tokenizeHeadings` calls that compute the exact
[bodyStart, stopOffset) span, preserving byte-identical STATE.md output.

Non-migratable site left in place: `cmdStateRecordMetric`'s metricsPattern
captures table-header rows in group 1 — not a standard heading+body shape.
Write orchestration (readModifyWriteStateMd / syncStateFrontmatter /
shouldPreserveExistingProgress / #952 no-op guard) is UNTOUCHED.

Verification: t6-headtohead.cjs head-to-head harness runs 25 ops across 6
STATE.md fixture variants (inline, trailing-blanks, CRLF, no-frontmatter,
nested-acc, post-milestone) against origin/next and reports 0 diffs.
No-op guard confirmed: record-session on recorded:false leaves STATE.md
byte-identical. All 150 state tests and 62 milestone/forensics tests pass.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test(#1398): capture T6 state-section-splice characterization; drop throwaway harness

Remove scripts/t6-headtohead.cjs (committed throwaway HEAD-vs-origin/next
byte-compare harness). Capture its coverage as 23 behavioral characterization
tests appended to tests/state.test.cjs, exercising all migrated cmdState*
write-ops across 7 fixture variants (inline, trailing-blanks, CRLF,
no-frontmatter, nested-acc, no-current-pos, post-milestone). Includes the
#952 no-op guard (recorded:false + byte-unchanged assertion) and
CRLF/trailing-blanks edge-case coverage. Also removes the dead
spliceStateSection helper (defined but never called) that was surfacing as
an @typescript-eslint/no-unused-vars warning.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-17 17:56:17 -04:00
Tom Boucher
c20d741dc9 fix(#1316): preserve prose STATE phase names (#1351) 2026-06-16 15:11:23 -04:00
Tom Boucher
00c447701d fix(#1257): update pipe-table Status/Phase/Plan cells in planned-phase + begin-phase (#1260)
* fix(#1257): update pipe-table Status/Phase/Plan cells in planned-phase + begin-phase

cmdStatePlannedPhase ran its body-field replacements on the full file content,
so the case-insensitive ^Status: pattern matched the YAML frontmatter `status:`
line before the body `| Status | … |` cell — the cell never advanced to
'Ready to execute' and syncStateFrontmatter re-derived the stale 'planning'
status (the #1230 delta heuristic preserved it). cmdStateBeginPhase had
pipe-table else-branches only for Status/Last activity (#1256), so the Current
Position `| Phase |` / `| Plan |` cells were left stale while a spurious inline
`Phase: N — EXECUTING` line was prepended.

Both handlers now strip frontmatter before body-field replacement and update the
pipe-table cells in place via stateReplaceField, matching inline-format
behaviour. Systemic residual of #1255 / #1256.

Adds 4 regression tests (#1257 block in tests/state.test.cjs) covering both
findings — RED before the fix, GREEN after; full state-area suite (437) and
local unit suite stay green.

Closes #1257

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* chore(#1257): backfill changeset pr number (#1260)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 00:29:18 -04:00
Tom Boucher
2668fbbeb0 fix(#1255): advance frontmatter status for pipe-table STATE.md on begin/complete-phase (#1256)
* fix(#1255): advance frontmatter status for pipe-table STATE.md on begin/complete-phase

state begin-phase/complete-phase called stateReplaceField on the FULL file content, so its case-insensitive ^Status: plain-pattern matched the YAML frontmatter status: line first (no g flag) and never updated the body pipe-table Status cell; syncStateFrontmatter then re-derived the stale status from the unchanged body, freezing the frontmatter status. Fix: strip frontmatter before the body-field replacements (operate on body only), reassemble with frontmatter preserved, so the body Status cell updates and the frontmatter derives correctly — for inline AND pipe-table body formats. Also corrects the Current Position pipe-table else-branches (Status/Phase/Last-activity) to write bare, consistent cell values. Pipe-table Status is a supported body format (not rewritten to inline).

Closes #1255

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* chore(#1255): add changeset for pipe-table state status fix (#1256)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* test(#1255): fold pipe-table regression into state.test.cjs + Windows-portable frontmatter regex

Per the 2026-06 audit, new tests/bug-NNNN-*.test.cjs files are banned (lint-regression-test-names) — folded the 7 #1255 regressions into tests/state.test.cjs and removed the standalone file + its lint-test-file-count allowlist entry. Also fixed the frontmatter assertions' /^---\n/ anchors to /^---\r?\n/ (windows-test-parity-guard frontmatterAnchorLiteralNewline).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 22:42:28 -04:00
Tom Boucher
3827054954 fix(#1156): support table-format STATE.md and insert missing roadmap plan rows (#1172)
* fix(#1162): support table-format STATE.md in state field read/replace

Extend stateExtractField and stateReplaceField in state-document.cts to
detect and operate on pipe-table rows (| Field | value |).  The separator
row | --- | --- | is excluded from matching.  updateCurrentPositionFields
in state.cts now falls through to stateReplaceField for table-format
Current Position sections when the inline Status:/Last activity: patterns
do not match.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(#1163): insert missing plan checklist rows in roadmap update-plan-progress

cmdRoadmapUpdatePlanProgress now inserts `- [ ] NN-XX-PLAN.md` checkbox
rows under the phase Plans: line when no per-plan checkbox rows exist yet
(fresh template).  Rows are sorted ascending and any already-summarised
plans are immediately marked [x].  The planCountPattern is extended to
also match plain `Plans:` (in addition to bold `**Plans:**`) so plan
counts are updated in both template variants.  The existing-rows check
covers both top-level and indented checkbox forms to preserve idempotency.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(#1163): fill partial plan-row gaps and scope insertion to active milestone

- Finding 1 (HIGH): replace all-or-nothing rowsAlreadyPresent guard with per-file
  set-difference so missing rows are inserted even when SOME plan rows already exist
- Finding 2 (MEDIUM): extend planCountPattern to recognise **Plans**: (canonical
  template form — bold word + outer colon) alongside **Plans:** and plain Plans:;
  use two-pattern fallback for row insertion to anchor under Plans: checklist header
  rather than the **Plans**: summary line
- Finding 3 (MEDIUM): scope row insertion to the active (post-</details>) milestone
  region so duplicate phase headings in archived sections never receive new rows
- Finding 4 (LOW): rename misleading "pipe-like content" test to honestly describe
  what it tests (multi-row table isolation); add out-of-scope escaped-pipe comment
- Remove now-unused anyCheckboxMatched variable (lint clean)
- Add 5 adversarial regression tests (pre-fix failures confirmed)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(#1163): scope missing-plan detection to active milestone; preserve authored state fields in table format

- roadmap.cts: compute activeRegion (post-</details> slice) once and use it
  for BOTH missingPlans detection and row insertion, so archived <details>
  rows no longer suppress active-section inserts (Finding 1 code-review)
- roadmap.cts: change (Plans:) inner capture to non-capturing (?:Plans:)
  in insertRowsPatternA to prevent group-numbering shift (Finding 3)
- state.cts: mirror inline-branch preserve-authored guards onto table-format
  branches in updateCurrentPositionFields — Status table branch checks
  isInList/matchesPattern before replacing; Last Activity table branch
  checks isDateShape/inList, preserving executor-authored narrative prose
  (Finding 2 code-review)
- tests: add three failing-first regression tests confirming each finding

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* test(#1162,#1163): fold table-format + plan-row regressions into owning module tests

Move bug-1162 cases into tests/state.test.cjs and bug-1163 cases into
tests/roadmap.test.cjs under named regression describe blocks; delete the
standalone bug-NNNN files and prune their allowlist entries.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs(#1156): add changeset fragment for table-format state + roadmap insert fix

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 21:56:18 -04:00
Tom Boucher
aec3374bc2 feat(#1138): make runtime descriptors authoritative (#1157) 2026-06-13 01:49:25 -04:00
Tom Boucher
b431b1fab4 fix(#1140): implement state add-roadmap-evolution CJS handler (#1148)
* fix(#1140): implement state add-roadmap-evolution CJS handler

`query state.add-roadmap-evolution` was unreachable: the CJS state router
listed it in the `unsupported` map with a circular message ("...is SDK-only.
Use: gsd-tools query state.add-roadmap-evolution ...") and no CJS handler
existed after the SDK retirement (ADR-0174). Every `/gsd:phase insert` and
`/gsd:phase --edit` run hit a dead end recording Roadmap Evolution.

Re-implement `cmdStateAddRoadmapEvolution` in CJS (src/state.cts) and wire it
into the state router; remove the now-stale `unsupported` entry. The handler
appends a single-line bullet under `## Accumulated Context` → `### Roadmap
Evolution` (creating the subsection/section if missing, deduping identical
entries), scoping every lookup to the Accumulated Context body so a decoy
heading in an unrelated section is never targeted, and flattening multiline
notes to a single bullet. Section-boundary regexes mirror the sibling
add-decision/add-blocker handlers and preserve following sections on CRLF input.

Regression cases live in tests/state.test.cjs (per the no-new-bug-NNNN-files
policy) and cover the literal issue repro plus the CLI/parser QA matrix
(missing/empty/whitespace note, flag-shaped value, duplicate flags, hostile
shell metacharacters, Unicode, decoy section, CRLF, missing STATE.md).

Closes #1140

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* chore(#1140): backfill changeset PR number (1148)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 22:27:02 -04:00
Tom Boucher
9855ea3f39 fix(#1070): recognize "Complete ✓" terminal status in planned-phase transition (#1078)
* fix(#1070): recognize "Complete ✓" terminal status in planned-phase transition

LLM phase executors (e.g. OpenCode) may write `Status: Complete ✓` into
STATE.md when finishing a phase. `state planned-phase` then failed to advance
the Status field on both the frontmatter `**Status:**` line and the Current
Position `Status:` line, because `Complete ✓` matched neither
KNOWN_TEMPLATE_DEFAULTS['Status'] nor any KNOWN_STATUS_PATTERNS entry — so it
was preserved as an executor-authored value and the state machine stayed stuck
on the prior phase.

Add a narrow, fully-anchored pattern `/^Complete\s*[✓✔✅☑]?\s*$/i` to
KNOWN_STATUS_PATTERNS so a bare `Complete` / `Complete ✓` terminal marker yields
to the next phase's `Ready to execute`. Both Status writers consult this array,
so the single addition fixes both paths. Caveat-bearing statuses like
`Complete but needs manual QA` are not matched and remain preserved.

Regression cases added to tests/state.test.cjs (planned-phase block) exercising
both code paths plus the preservation guarantee.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs(#1070): add changeset for planned-phase Complete-status fix

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 20:30:58 -04:00
Tom Boucher
463cffd894 chore(#604): rename get-shit-done/ runtime directory to gsd-core/ (#615)
* 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>
2026-06-02 18:35:29 -04:00
Tom Boucher
a8ff46484b fix(#474): deterministic STATE dates via deepened clock seam (nowIso/today + GSD_NOW_MS adapter) (#477)
* fix(#474): route state date-stamping + installer lock loop through clock seam (nowIso/today + GSD_NOW_MS adapter)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(#474): use process.ppid (not pid 1) as held-lock owner in install-lock timeout test

pid 1 is POSIX init/launchd (always alive) but does not exist on Windows,
so isPidAlive(1) returns false, the lock is reclaimed as stale, and
acquireInstallMigrationLock no longer throws -- failing the timeout
assertion on windows-latest,22. process.ppid is a live, non-self process
on every platform, so the lock is seen as held and the timeout path
throws deterministically cross-platform.

Refs #474

---------

Co-authored-by: CI Rebase Check <ci@gsd-redux>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 18:21:25 -04:00
Tom Boucher
616b387f12 perf(#320): hoist By-Phase state table regex to module scope (#403)
Static regex literal `byPhaseTablePattern` was recompiled on every call to `updatePerformanceMetricsSection`; hoisted to module scope (compiled once; stateless /i used with .match → safe to share across calls). `phaseRowPattern` uses dynamic interpolation and stays in-function. `Fixes #320`.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 20:22:06 -04:00
Colin Johnson
ed149ff283 fix: accept json input for state patch (#356)
* fix: accept json input for state patch

* chore: add changeset
2026-05-26 18:55:20 -04:00
Tom Boucher
06cf7826e3 test: deepen fixture module v2 for reliability (#329)
* test: deepen fixture seam for init/state/workstream suites

* test: deepen fixture module v2 with declarative builders
2026-05-26 11:23:42 -04:00
Tom Boucher
ecd57e622c fix(3265): prefer YAML frontmatter for state-snapshot canonical fields (#3275)
* fix(3265): prefer YAML frontmatter for state-snapshot canonical fields

stateSnapshot in both sdk/src/query/state.ts and the CJS twin
(get-shit-done/bin/lib/state.cjs cmdStateSnapshot) passed the whole
STATE.md blob to stateExtractField, whose bold pattern (**Field:**)
has no line anchor.  A body table cell such as
"**Status:** to ✅ COMPLETE" therefore silenced the correct YAML
frontmatter value.

Fix: extractFrontmatter(content) first; stripFrontmatter(content) for
the body passed to stateExtractField; for each canonical scalar field
prefer the non-empty frontmatter value, falling back to body extraction
when the key is absent or the file has no frontmatter block at all.

Regression tests added in sdk/src/query/state.test.ts (vitest) and
tests/state.test.cjs (node:test) covering:
- frontmatter status beats **Status:** inside a table cell
- frontmatter current_plan beats bold body value
- no-frontmatter files continue to extract from body
- field absent from frontmatter falls through to body extractor

Fixes #3265

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: add changeset for #3275

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test: reproduce fmStr drops non-string YAML scalars (#3275 CR finding)

Add tests/bug-3275-fmstr-non-string-scalars.test.cjs with 5 cases covering
CJS state-snapshot with numeric frontmatter scalars (current_phase: 19,
total_phases: 7, total_plans_in_phase: 5), string regression, and
no-frontmatter body fallback regression.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(state): fmStr accepts numeric/boolean YAML scalars (CR finding)

Rename `fmStr` to `fmScalar` in both state.cjs and sdk/src/query/state.ts
and broaden the type guard so that non-null number/boolean frontmatter values
are coerced to String(v) instead of being discarded.

The previous `typeof v === 'string'` check was a latent bug: if the YAML
parser ever returns typed scalars (e.g. `current_phase: 19` as the number 19),
the frontmatter value would be silently dropped and the stale body value used
instead.  Both files are updated identically (k014 parity).

Also adds three SDK vitest regression cases (numeric current_phase,
total_phases, total_plans_in_phase) in sdk/src/query/state.test.ts.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 23:53:21 -04:00
Tom Boucher
471df09242 fix(state): harden complete-phase resolution and add explicit override 2026-05-04 23:10:26 -04:00
Tom Boucher
918f987a19 feat(#2982): extend no-source-grep lint to catch var-binding readFileSync.includes() (#2985)
* feat(#2982): extend no-source-grep lint to catch var-binding readFileSync.includes()

The base lint (scripts/lint-no-source-grep.cjs) only catches
readFileSync(...).<text-method>() chained directly. The much more
common var-binding form escapes it:

  const src = fs.readFileSync(p, 'utf8');
  // 50 lines later
  if (src.includes('foo')) {}        // ← still grep, lint missed it

Scan of the test suite found ~141 files using this pattern.

Implementation built TDD per #2982 with structured-IR assertions:

  scripts/lint-no-source-grep-extras.cjs
    - detectVarBindingViolations(src) — pure detector, two passes:
      pass 1 collects vars bound from readFileSync, pass 2 finds any
      <var>.<includes|startsWith|endsWith|match|search>( on those vars.
    - detectWrappedAssertOkMatch(src) — flags
      assert.ok(<expr>.match(...)) which escapes the assert.match rule.
    - VIOLATION enum exposes stable codes for tests to assert on.

  scripts/lint-no-source-grep.cjs
    - Wires the new detectors into the existing per-file check; one
      additional violation row per file with the first 3 sample tokens.

  tests/bug-2982-lint-var-binding.test.cjs
    - 13 tests, all assertions on typed VIOLATION enum / structured
      records. Covers all 5 text-match methods, multi-var, no-bind,
      string literal (must NOT trigger), wrapped assert.ok(.match),
      and assert.match (must NOT double-flag).

Migration backlog (#2974 expanded scope):

  - 42 files annotated `// allow-test-rule: source-text-is-the-product`
    (legitimate — they read .md/.json/.yml files whose deployed text
    IS the product)
  - 3 files annotated `// allow-test-rule: pending-migration-to-typed-ir [#2974]`
    (read .cjs/.js source — clear migration debt)
  - 95 files annotated `pending-migration-to-typed-ir [#2974]` with
    `Per-file review may reclassify as source-text-is-the-product
    during migration` (mixed — manual review under #2974)

After this lands the lint reports 0 violations on main; new
violations in PRs surface immediately.

Closes #2982
Refs #2974

* test(#2982): fix truncated test name per CR

The label ended with a bare '(' from a copy-paste mishap. Now reads
'does NOT flag .matchAll(...) — matchAll is not match, so
assert.ok(.matchAll(...)) is not flagged'.

* chore(#2982): add changeset fragment for PR #2985

* chore(#2982): add changeset fragment for PR #2985
2026-05-01 19:50:10 -04:00
Tom Boucher
dc9b712967 refactor(state): drop unused args + lift currentPhase in cmdStateCompletePhase (#2761)
* refactor(state): drop unused args param and lift currentPhase in cmdStateCompletePhase

Two cleanup items surfaced by CodeRabbit review of PR #2759:

1. cmdStateCompletePhase(cwd, args, raw) — args is never read inside the
   function. All sibling state subcommands use the leaner (cwd, raw) shape.
   Remove the unused parameter and update the dispatch call in gsd-tools.cjs.

2. output() at line 1754 called fs.readFileSync(statePath) after
   readModifyWriteStateMd had already released the lock, re-extracting
   Current Phase via an extra fs read. The closure already computed
   currentPhase at line 1704; lifting resolvedPhase into outer scope and
   capturing it in the callback eliminates the post-lock read and closes the
   small race window.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test(#2761): apply CodeRabbit nitpicks with regression tests

Two CodeRabbit nitpicks from PR #2761 review, each landed with a
regression test so a future refactor can't unwind them.

1. tests/dispatcher.test.cjs — pin the enumerated subcommand list:
   the 'state unknown subcommand errors' test now also asserts that
   the dispatcher's error string includes 'complete-phase'. Without
   this, a future reformat of the available-subcommands enumeration
   could silently drop entries and the existing
   'Unknown state subcommand' substring check would still pass.

2. get-shit-done/bin/lib/state.cjs — tighten the Phase fallback in
   cmdStateCompletePhase: when STATE.md is missing the canonical
   '**Current Phase:**' field and the only phase signal is the
   decorated body line under '## Current Position' (e.g.
   'Phase: 01 (Foo) — EXECUTING'), the previous fallback returned
   the entire decorated string, producing messy downstream output:
     Status: Phase 01 (Foo) — EXECUTING complete
     Phase: 01 (Foo) — EXECUTING — COMPLETE
   The fallback now strips everything past the leading
   numeric/decimal token via /^\\s*([\\w.-]+)/ so degraded inputs
   produce clean output identical to the canonical path.

3. tests/state.test.cjs — two new tests in a dedicated describe block:
   - decorated Phase line writes clean Phase identifier
   - canonical Current Phase wins over Current Position decoration
   Both run real `gsd state complete-phase` against synthetic
   STATE.md fixtures and assert on the rendered Status field.

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 09:03:36 -04:00
Tom Boucher
53b9fba324 fix: stale phase dirs corrupt phase counts; stopped_at overwritten by historical prose (#2459)
* fix(sdk): extractCurrentMilestone Backlog leak + state.begin-phase flag parsing

Closes #2422
Closes #2420

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(#2444,#2445): scope stopped_at extraction to Session section; filter stale phase dirs

- buildStateFrontmatter now extracts stopped_at only from the ## Session
  section when one exists, preventing historical prose elsewhere in the
  body (e.g. "Stopped at: Phase 5 complete" in old notes) from overwriting
  the current value in frontmatter (bug #2444)
- buildStateFrontmatter de-duplicates phase dirs by normalized phase number
  before computing plan/phase counts, so stale phase dirs from a prior
  milestone with the same phase numbers as the new milestone don't inflate
  totals (bug #2445)
- cmdInitNewMilestone now filters phase dirs through getMilestonePhaseFilter
  so phase_dir_count excludes stale prior-milestone dirs (bug #2445)
- Tests: 4 tests in state.test.cjs covering both bugs

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 10:08:43 -04:00
Tom Boucher
2703422be8 refactor(tests): standardize to node:assert/strict and t.after() per CONTRIBUTING.md (#1675)
* refactor(tests): standardize to node:assert/strict and t.after() per CONTRIBUTING.md

- Replace require('node:assert') with require('node:assert/strict') across
  all 73 test files to enforce strict equality (no type coercion)
- Replace try/finally cleanup blocks with t.after() hooks in core.test.cjs
  and hooks-opt-in.test.cjs per the test lifecycle standards
- Utility functions in codex-config and security-scan retain try/finally
  as that is appropriate for per-function resource guards, not lifecycle hooks

Closes #1674

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* perf(tests): add --test-concurrency=4 to test runner for parallel file execution

Node.js --test-concurrency controls how many test files run as parallel child
processes. Set to 4 by default, configurable via TEST_CONCURRENCY env var.
Fixes tests at a known level rather than inheriting os.availableParallelism()
which varies across CI environments.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(security): allowlist verify.test.cjs in prompt-injection scanner

tests/verify.test.cjs uses <human>...</human> as GSD phase task-type
XML (meaning "a human should verify this step"), which matches the
scanner's fake-message-boundary pattern for LLM APIs. This is a
false positive — add it to the allowlist alongside the other test files
that legitimately contain injection-adjacent patterns.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 14:29:03 -04:00
Tom Boucher
f26e1e1141 feat(state): add programmatic gates for STATE.md consistency (#1647)
* feat(state): add programmatic gates for STATE.md consistency

Adds four enforcement gates to prevent STATE.md drift:
- `state validate`: detects drift between STATE.md and filesystem
- `state sync`: reconstructs STATE.md from actual project state
- `state planned-phase`: records state after plan-phase completes
- Performance Metrics update in `phase complete`

Also fixes ghost `state update-position` command reference in
execute-phase.md (command didn't exist in CLI dispatcher).

Closes #1627

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(state): By Phase table regex ate next section when table body was empty

The lazy [\s\S]*? with a $ lookahead in byPhaseTablePattern would
match past blank lines and capture the next ## section header as table
body when no data rows existed. Replaced with a precise row-matching
pattern ((?:[ \t]*\|[^\n]*\n)*) that only captures pipe-delimited
lines. Added regression assertion to verify row placement.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-04 08:01:39 -04:00
Tom Boucher
b5dd886e15 fix: correct STATE.md progress counter fields during plan execution (#1597)
* fix: correct STATE.md progress counter fields during plan/phase completion (#1589)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* ci: re-run CI with Windows pointer lifecycle fix in main

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 11:56:49 -04:00
Tom Boucher
43d1787670 fix: preserve Status/LastActivity/Progress in Current Position during begin-phase
cmdStateBeginPhase replaced the entire ## Current Position section with only
Phase and Plan lines, destroying Status, Last activity, and Progress fields.
cmdStateAdvancePlan then failed to update these fields since they no longer
existed.

Now begin-phase updates individual lines within Current Position instead of
replacing the whole section. Also adds updateCurrentPositionFields helper so
advance-plan keeps the Current Position body in sync with bold frontmatter
fields.

Fixes #1365

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 19:25:14 -04:00
Tom Boucher
616c1fa753 refactor: replace try/finally with beforeEach/afterEach + add CONTRIBUTING.md
Test suite modernization:
- Converted all try/finally cleanup patterns to beforeEach/afterEach hooks
  across 11 test files (core, copilot-install, config, workstream,
  milestone-summary, forensics, state, antigravity, profile-pipeline,
  workspace)
- Consolidated 40 inline mkdtempSync calls to use centralized helpers
- Added createTempDir() helper for bare temp directories
- Added optional prefix parameter to createTempProject/createTempGitProject
- Fixed config test HOME sandboxing (was reading global defaults.json)

New CONTRIBUTING.md:
- Test standards: hooks over try/finally, centralized helpers, HOME sandboxing
- Node 22/24 compatibility requirements with Node 26 forward-compat
- Code style, PR guidelines, security practices
- File structure overview

All 1382 tests pass, 0 failures.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 15:45:39 -04:00
j2h4u
f850952332 fix(state): preserve frontmatter status when body Status field is missing
syncStateFrontmatter() rebuilds YAML frontmatter from the body on every
writeStateMd() call. If an agent removes or omits the Status: field from
the body, buildStateFrontmatter() defaults to 'unknown', overwriting a
previously valid status (e.g., 'executing').

Fix: read existing frontmatter before stripping, and preserve its status
value when the body-derived status would be 'unknown'. This makes
frontmatter self-healing — once a status is set, it persists even if the
body loses the field.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 00:33:13 +05:00
Tom Boucher
3e2c85e6fd refactor: consolidate STATE.md field helpers, fix command injection in isGitIgnored
Refactoring:
- Extract stateReplaceFieldWithFallback() to state.cjs as single source of truth
  for the try-primary-then-fallback pattern that was duplicated inline across
  phase.cjs, milestone.cjs, and state.cjs
- Replace all inline bold-only regex patterns in cmdPhaseComplete with shared
  stateReplaceField/stateExtractField helpers — now supports both **Bold:**
  and plain Field: STATE.md formats (fixes the same bug as #924)
- Replace inline bold-only regex patterns in cmdMilestoneComplete with shared helpers
- Replace inline bold-only regex for Completed Phases/Total Phases/Progress
  counters in cmdPhaseComplete with stateExtractField/stateReplaceField
- Replace inline bold-only Total Phases regex in cmdPhaseRemove with shared helpers

Security:
- Fix command injection surface in isGitIgnored (core.cjs): replace execSync with
  string concatenation with execFileSync using array arguments — prevents shell
  interpretation of special characters in file paths

Tests (7 new):
- 5 tests for stateReplaceFieldWithFallback: primary field, fallback, neither,
  preference, and plain format
- 1 regression test: phase complete with plain-format STATE.md fields
- 1 regression test: milestone complete with plain-format STATE.md fields

854 tests pass (was 847). No behavioral regressions.
2026-03-19 15:05:20 -04:00
j2h4u
85a65fd384 fix(state): parse compound Plan field in advance-plan command
`cmdStateAdvancePlan` expected separate `Current Plan` and
`Total Plans in Phase` fields, but the current STATE.md template
uses a single compound field: `Plan: X of Y in current phase`.

Now tries legacy separate fields first, then falls back to parsing
the compound format. Preserves the compound format when writing back
(replaces only the plan number). Also handles `Last activity`
(lowercase) field name from current template.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-19 21:28:26 +05:00
j2h4u
e1b3277869 fix(state): derive total_phases from ROADMAP when phases lack directories
Phases listed in ROADMAP.md but not yet planned (no directory on disk)
were excluded from total_phases, causing premature milestone completion.
Now uses Math.max(diskDirs, roadmapCount) via filter.phaseCount.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 16:07:53 -06:00
j2h4u
b863ed6de5 fix(state): scope phase counting to current milestone
Extract getMilestonePhaseFilter() from milestone.cjs closure into core.cjs
as a shared helper. Apply it in buildStateFrontmatter and cmdPhaseComplete
so multi-milestone projects count only current milestone's phases instead
of all directories on disk.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 16:07:49 -06:00
Lex Christopherson
9c27da0261 fix(windows): cross-platform path separators, JSON quoting, and dollar signs
- Add toPosixPath() helper to normalize output paths to forward slashes
- Use string concatenation for relative base paths instead of path.join()
- Apply toPosixPath() to all user-facing file paths in init.cjs output
- Use array-based execFileSync in test helpers to bypass shell quoting
  issues with JSON args and dollar signs on Windows cmd.exe

Fixes 7 test failures on Windows: frontmatter set/merge (3), init
path assertions (2), and state dollar-amount corruption (2).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 11:34:36 -06:00
Ethan Hurst
c9e73e9c94 fix(quick-1): remove HIGH severity test overfitting in 4 test files
- frontmatter.test.cjs: delete FRONTMATTER_SCHEMAS describe block (4 tests asserting on lookup table shape)
- core.test.cjs: replace 12 per-profile per-agent matrix tests with 1 structural validation test
- commands.test.cjs: URL test uses URL.searchParams.get() instead of raw string includes
- state.test.cjs: date assertion uses before/after window to handle midnight boundary safely
2026-02-26 05:49:31 +10:00
Ethan Hurst
d5ef3b4f1f test(09-02): add cmdStateResolveBlocker and cmdStateRecordSession tests
- 5 tests for cmdStateResolveBlocker: case-insensitive removal, None placeholder on empty, missing text error, missing STATE.md, no-match succeeds
- 5 tests for cmdStateRecordSession: update all fields, timestamp-only update, None default resume-file, missing STATE.md, no session fields returns false
2026-02-26 05:49:31 +10:00
Ethan Hurst
763ff97764 test(09-02): add cmdStateAdvancePlan, cmdStateRecordMetric, cmdStateUpdateProgress tests
- 4 tests for cmdStateAdvancePlan: advance, last-plan complete, missing STATE.md, unparseable fields
- 4 tests for cmdStateRecordMetric: append row, None-yet placeholder, missing fields error, missing STATE.md
- 4 tests for cmdStateUpdateProgress: percent calculation, zero plans, missing Progress field, missing STATE.md
2026-02-26 05:49:31 +10:00
Ethan Hurst
29a87cba7c test(09-01): add cmdStateLoad, cmdStateGet, cmdStatePatch, cmdStateUpdate CLI tests
- 3 cmdStateLoad tests: state+config+roadmap detection, missing STATE.md, --raw flag output
- 5 cmdStateGet tests: full content, bold field extraction, section extraction, missing field error, missing file error
- 5 cmdStatePatch/cmdStateUpdate tests: batch update, failed fields reporting, single field update, field not found, missing STATE.md
2026-02-26 05:49:31 +10:00
Ethan Hurst
1ca7df13a0 test(09-01): add stateExtractField and stateReplaceField unit tests
- 4 stateExtractField tests: simple extraction, colon-in-value, null on missing, case-insensitive
- 4 stateReplaceField tests: replace value, null on missing, preserves surrounding content, round-trip
2026-02-26 05:49:31 +10:00
Lex Christopherson
0ca1a59ab3 feat: add YAML frontmatter sync to STATE.md for machine readability 2026-02-25 07:20:14 -06:00
Colin Johnson
77d434709d Fix STATE.md decision corruption and dollar handling (#701) 2026-02-23 09:55:54 -06:00
Lex Christopherson
3a56a207a5 fix(gsd-tools): support --cwd override for state-snapshot
Add --cwd <path> / --cwd=<path> support so sandboxed subagents running
outside the project root can target a specific directory. Invalid paths
return a clear error. Tests ported to tests/state.test.cjs (the old
monolithic test file was split into domain files on main).

Closes #622

Co-Authored-By: Colin Johnson <colin@solvely.net>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 09:31:06 -06:00
Tyler Satre
fa2e156887 refactor: split gsd-tools.test.cjs into domain test files
Move 81 tests (18 describe blocks) from single monolithic test file
into 7 domain-specific test files under tests/ with shared helpers.
Test parity verified: 81/81 pass before and after split.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 10:30:54 -05:00