Commit Graph

1107 Commits

Author SHA1 Message Date
Tom Boucher
77b16993f8 fix(3584): address coderabbit findings on PR #3606
CodeRabbit surfaced one outstanding inline finding plus an outside-diff
finding and a finer point on two already-remediated sites; all addressed:

1. (inline, Minor) runtime-slash.cjs:31 — a degenerate input like `/gsd:`,
   `gsd:`, or `gsd-` normalizes to empty and the previous fallback returned
   the original colon-form input, reintroducing the deprecated shape the
   module exists to suppress. Now returns `''` (empty string) so callers
   see "no command" instead of an unroutable string. Also catches
   whitespace-only inputs the same way. New unit tests pin the contract.

2. (inline, Major) drift.cjs library purity — the earlier remediation
   passed `projectDir` into `detectDrift` and re-resolved runtime inside the
   library. CodeRabbit (correctly) flagged this as breaking the module's
   pure-library contract. detectDrift now accepts `input.runtime` directly;
   verify.cmdVerifyCodebaseDrift resolves the runtime once and passes the
   literal name in. drift.cjs no longer reads env or config at all.

3. (duplicate inline, Minor) gsd2-import.cjs:475 — when
   `gsd2-import --path <dir>` targets a project that isn't the process
   cwd, the preview command was formatted for the wrong runtime.
   buildPreview now receives the resolved `projectDir` (the same one
   used to find the .gsd/ root) instead of the raw `cwd`.

4. (outside-diff, Minor) tests/copilot-install.test.cjs:1-5 — the
   `allow-test-rule: integration-test-input` rationale block specifically
   named verify.cjs as the fixture, but #3584 changed that test to use a
   synthetic input. Comment now describes the real shape of the file's
   readFileSync usage (commands/, agents/, install.js source inputs to
   the installer/converter functions under test) and notes the synthetic
   substitution for the bin/lib path.

Full suite: 9366/9366 pass (+1 new test). Lint clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 19:52:17 -04:00
Tom Boucher
bdf06c04b0 fix(3584): address codex-review findings on PR #3606
Codex review surfaced five issues in the initial slash-formatter PR — three MED
and two LOW. All five are addressed:

1. (MED) formatter corrupted argument tails under codex runtime. Splitting on
   the first whitespace and lowercasing only the command token preserves
   path-like arguments (`Map-Codebase --paths C:\\Users\\Me\\Project`) and
   case-sensitive flag values. (`runtime-slash.cjs`)

2. (MED) profile-output `cmdGenerateDevPreferences` emitted a hardcoded
   `command_name: '/gsd-dev-preferences'`. Replaced with
   `formatGsdSlash('dev-preferences', resolveRuntime(cwd))` so the structured
   result honors codex/skills runtime distinction. (`profile-output.cjs`)

3. (MED) `drift.detectDrift` → `buildMessage` called `resolveRuntime(null)`,
   ignoring a project's `.planning/config.json` `runtime` setting when
   `GSD_RUNTIME` env var was absent. Threaded `projectDir` through
   `detectDrift({projectDir})` → `buildMessage(..., projectDir)` →
   `resolveRuntime(projectDir)`. `verify.cmdVerifyCodebaseDrift` now passes
   `cwd` into the detect call. (`drift.cjs`, `verify.cjs`)

4. (LOW) `gsd2-import.buildPreview` had the same env-vs-config issue. Threaded
   `cwd` through `buildPreview(..., projectDir)` for parity. (`gsd2-import.cjs`)

5. (LOW) The codex emitter test only asserted absence of `/gsd:` — a regression
   to `/gsd-` (skills) form in codex output would have passed undetected.
   Added positive assertions that every gsd-referencing fix string under
   `GSD_RUNTIME=codex` contains `$gsd-` and contains neither `/gsd-` nor
   `/gsd:`. Also added formatter unit tests pinning the argument-tail
   preservation contract for both hyphen and codex runtimes.

Full suite: 9365/9365 pass (+2 new). Lint clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 19:42:48 -04:00
Tom Boucher
034b47c8d0 fix(3584): runtime-aware slash formatter for user-facing emissions
Introduce `runtime-slash.cjs` as the single source of truth for emitting
GSD slash-command references in user-facing runtime output and persisted
artifacts. `formatGsdSlash(commandName, runtime)` produces `/gsd-<cmd>`
for skills-based runtimes (Claude/Cursor/OpenCode/Kilo/etc.) and
`$gsd-<cmd>` for Codex. The deprecated `/gsd:<cmd>` colon form is never
emitted — pasting a recommended-action command into Claude Code now
routes correctly instead of failing with `Unknown command`.

Wired into the high-impact emitters identified in #3584:

- `init.cjs` `cmdInitManager` recommended_actions[].command (the
  original failure path in the bug report) plus the no-ROADMAP /
  no-STATE error hints.
- `phase.cjs` `cmdPhaseAdd`, `cmdPhaseAddBatch`, `cmdPhaseInsert` —
  ROADMAP.md `Plans:` references now persist the routable form
  instead of the legacy colon form.
- `verify.cjs` `cmdValidateHealth` — every fix-hint addIssue() call
  (E001/E002/E003/E004/E005, W002/W003/W008/W009/W011/W016/W018) and
  the persisted STATE.md regenerate / MILESTONES.md backfill notes.
- `milestone.cjs` `cmdMilestoneComplete` — Operator Next Steps tail
  rewrite.
- `validate-command-router.cjs` — `validate context` recommendation
  strings for WARNING/CRITICAL utilization bands.
- `workstream.cjs` — missing .planning hint.
- `profile-output.cjs` — `generate-claude-md` workflow enforcement
  block, project/skills fallbacks, profile placeholder, and the
  dev-preferences refresh hints.
- `drift.cjs`, `gsd2-import.cjs`, `commands.cjs scaffold context` —
  remaining one-off persisted references.

Runtime detection: `resolveRuntime(projectDir)` reads
`process.env.GSD_RUNTIME` first, then a side-effect-free direct read of
`.planning/config.json` (NOT `loadConfig`, which would normalize legacy
keys and re-write the file just to read the runtime name).

Tests:
- `tests/bug-3584-runtime-slash-formatter.test.cjs` — 22 unit tests
  covering the pure formatter and resolver (hyphen vs codex, prefix
  normalization, defensive returns, env/config/default chain).
- `tests/bug-3584-runtime-slash-emitters.test.cjs` — 6 integration
  tests exercising `init manager`, `phase add` (via the structured
  `roadmap get-phase` payload to avoid raw-text matching on the
  on-disk artifact), `validate health`, `validate context`, and the
  codex variant.
- Existing tests updated to assert the new contract: validate-context
  recommendations, claude-md workflow block, milestone complete
  Operator Next Steps. Copilot-install engine-conversion test now
  asserts against a synthetic input since bin/lib/*.cjs no longer
  contains literal `/gsd:` references for the install-time converter
  to rewrite.

INVENTORY.md and INVENTORY-MANIFEST.json updated for the new module
(64 CLI modules shipped, +1).

Fixes #3584

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 19:32:02 -04:00
Tom Boucher
05d4ba8147 Revert "Merge pull request #3578 from gsd-build/fix/3569-init-plan-phase-status"
This reverts commit a244dd7fc3, reversing
changes made to 8f95b2fe23.
2026-05-15 15:16:55 -04:00
Tom Boucher
0aa4bd92fb fix(3569): surface phase_status from init.plan-phase + gate /gsd:plan-phase on closed phases
Adds a new `phase_status` field to the `init.plan-phase` SDK + CJS query
output and a §1.5 "Closed-Phase Gate" in workflows/plan-phase.md that
short-circuits on closed phases instead of silently replanning over
shipped code.

## What was broken

`gsd-sdk query init.plan-phase <N>` returned the same "ready to plan"
payload for a closed phase (REQUIREMENTS Met, VERIFICATION.md status:
passed, ROADMAP flipped) as for an open one. No field signaled closure,
so `/gsd:plan-phase --reviews` happily replanned over closed phases —
risking documentation drift on already-shipped code.

## Fix

- Export `determinePhaseStatus` from `commands.cjs` (already present, was
  module-private).
- Both `cmdInitPlanPhase` (CJS) and `initPlanPhase` (TS SDK) now compute
  `phase_status` from plan/summary counts + VERIFICATION.md status using
  the existing `determinePhaseStatus` helper — the project-wide phase
  lifecycle vocabulary (Pending | Planned | In Progress | Executed |
  Complete | Needs Review). No directory yet → Pending.
- Workflow `plan-phase.md` adds §1.5 "Closed-Phase Gate":
  - `phase_status == "Complete"` with `--reviews` → hard-stop, no
    override (replanning a closed phase via review feedback is never
    legitimate; concerns belong in a follow-up phase or new issue).
  - `phase_status == "Complete"` without `--force` → exit with a clear
    notice pointing at VERIFICATION.md.
  - `phase_status == "Complete"` with `--force` → continue with a
    transcript banner so the deliberate replan is visible.

`Executed` and `Needs Review` are intentionally not gated — those mean
planning finished but verification did not pass, and replanning is the
correct next step.

## Tests

- SDK: 4 new `phase_status` cases in init.test.ts covering Pending /
  Planned / Executed / Complete transitions.
- Existing init.plan-phase golden parity test continues to pass (the
  `researcher_model: '' vs sonnet` drift in that test predates this
  change and is unrelated).
- Full Mac+Docker suite: 9323 / 9323 passed (Mac), 9318 / 9323 passed
  (Docker, 5 skipped).

Fixes #3569
2026-05-15 15:04:39 -04:00
Tom Boucher
3edbf7f357 Merge pull request #3563 from gsd-build/fix/3561-gsd-ultraplan-phase-runtime-gate-checks-
fix: use Claude Code runtime markers in ultraplan gate
2026-05-15 15:03:14 -04:00
Tom Boucher
e3e54d67d6 fix(3567): load SDK bridge via public package export 2026-05-15 14:54:58 -04:00
Tom Boucher
7ebcf41939 feat(3567): state.* router delegates via executeForCjs + Phase 5.0 worker fix (Phase 5.1 of #3524)
Phase 5.1 of the CJS↔SDK hard-seam migration (parent #3524). Migrates
the bin/lib/state-command-router.cjs handlers map to delegate every
canonical state subcommand through the executeForCjs synchronous
primitive (shipped in Phase 5.0, PR #3558).

## Bundled fix for Phase 5.0 worker defect

Discovered during Phase 5.1 implementation that the Phase 5.0
worker drops projectDir and workstream from
RuntimeBridgeExecuteInput. The dispatch closure at
sdk/src/runtime-bridge-sync/worker.ts:41-42 hardcoded projectDir
to '', so registry handlers that read .planning/ from projectDir
(every state.* handler) saw an empty path and failed. Phase 5.0's
pinning tests passed because they exercised commands that don't
depend on projectDir (generate-slug takes its arg directly;
unknown_command doesn't dispatch). Maintainer authorized bundling
the fix into this PR.

Fix: moved QueryNativeDirectAdapter construction inside the
dispatchNative lambda so request.projectDir and request.workstream
close over the per-request values. Per-request adapter construction
adds <1ms overhead; correctness wins. Regression test at
sdk/src/runtime-bridge-sync/projectdir-regression.test.ts demonstrates
RED before fix → GREEN after.

Phase 5.0's index.test.ts native_failure fixture was passing
because of the bug — it relied on projectDir = '' producing a
specific error path. Updated to use a /nonexistent-... path that
triggers ENOENT under realpath, producing native_failure as intended.

## What landed for Phase 5.1

- bin/lib/state-command-router.cjs migrated. Every subcommand
  entry in the handlers map dispatches via executeForCjs when SDK
  is available, with transparent fallback to the existing CJS
  handlers in state.cjs if (a) SDK is not built / not present, or
  (b) GSD_WORKSTREAM is set (the sync-bridge worker cannot serve
  workstream-scoped commands per the SDK transport architecture).
- Special cases preserved:
  - load --raw: SDK data formatted into key=value lines matching
    cmdStateLoad's exact format.
  - complete-phase: CJS-only (no SDK counterpart yet).
  - add-roadmap-evolution: stays on the unsupported list (SDK-only).
- Golden parity tests added for 12 previously-uncovered state
  subcommands: advance-plan, record-metric, update-progress,
  add-decision, add-blocker, resolve-blocker, record-session,
  signal-waiting, signal-resume, planned-phase, milestone-switch,
  prune.

## Design decisions worth reviewer visibility

1. Lazy SDK loading with CJS fallback. The migration routes via
   executeForCjs only when the SDK is loadable; otherwise falls
   back to the existing CJS handlers. Conservative for rollback —
   if the SDK build is broken on a deploy, state commands keep
   working via the CJS path. Trade-off: drift surface is not
   structurally eliminated yet — the CJS handlers remain reachable.

2. Workstream → CJS fallback. The SDK transport forces subprocess
   for workstream commands, but subprocess is disabled in the sync
   bridge. When GSD_WORKSTREAM is set, the entire state command
   falls back to CJS rather than failing. Workstream users continue
   running the CJS handlers; the SDK path is exercised only in the
   default (no workstream) case.

3. Two documented parity divergences. state.record-metric: CJS
   auto-creates ## Performance Metrics section when absent; SDK
   returns {recorded: false, reason}. Test requires fixture with
   the section present. state.prune: CJS counts phases from disk;
   SDK reads from frontmatter fields. Test asserts structural shape
   rather than exact equality.

## Numbers

- Full CJS suite: 9323/9323 pass (baseline 9323; +0 net because
  the 12 new parity tests are SDK-side vitest, not CJS-side).
- SDK vitest sync-bridge: 10/10 pass.
- Regression test: 3/3 pass (proved RED before fix, GREEN after).
- tests/state.test.cjs (the safety net): 104/104 pass unchanged.

## Performance

gsd-tools state load via the SDK path: 49ms first call (Worker
startup), 43-44ms steady-state median. Slower than the
Phase 5.0-measured 0.1ms because state.load does fs reads on top
of the bridge overhead. Still well within the budget for CJS
dispatcher overhead.

Closes #3567.
2026-05-15 14:34:25 -04:00
Tom Boucher
f097598689 fix(3561): fail closed when Claude version is unavailable 2026-05-15 12:59:18 -04:00
Tom Boucher
18397fb7f5 fix(3561): gate ultraplan runtime on Claude Code markers 2026-05-15 12:47:54 -04:00
Tom Boucher
d20d3e88b5 fix: align settings docs and inventory completion matching 2026-05-15 11:59:43 -04:00
Tom Boucher
dd4f75376a fix(3347): surface auto-build state via graphifyStatus; trim planner edits to free size budget
agents/gsd-planner.md was 49,316 chars after the initial PR; the
planner-decomposition <48K test was passing on main at 49,150 chars (just under
the 49152 limit). My addition pushed it over.

Restructure: instead of teaching the planner agent to read .last-build-status.json
directly, fold the auto-build state into graphifyStatus()'s existing `stale: true`
signal. The planner's existing rule ("if stale: true, treat as approximate") fires
correctly for failed and in-flight auto-builds — no new planner-side prompt content
needed. The full state is exposed under `last_build_auto_update` for callers that
want exit_code / duration_ms / commit-sha context.

- get-shit-done/bin/lib/graphify.cjs: graphifyStatus() reads
  .planning/graphs/.last-build-status.json; OR-folds status in {failed, running}
  into the existing stale signal; exposes last_build_auto_update field
- agents/gsd-planner.md: revert the auto-update awareness paragraph (49,524 → 49,150)
- agents/gsd-phase-researcher.md: revert the parallel paragraph for consistency
- get-shit-done/references/planner-graphify-auto-update.md: rewrite to document
  the graphifyStatus seam instead of planner-side prompt instructions
- tests/feat-3347-graphify-auto-update-config.test.cjs: 4 new graphifyStatus
  tests pinning the failed/running/ok/missing matrix
- tests/feat-3347-graphify-auto-update-hook.test.cjs: bump per-spawn timeout
  5s → 30s and wait-deadline 5s → 15s to absorb cold-start latency under
  parallel-test-file load (full suite runs many *.test.cjs concurrently)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 11:59:43 -04:00
Tom Boucher
088fc204ee fix(3347): clear CI gates raised by initial commit
- docs/CONFIGURATION.md: document graphify.auto_update key (config-schema-docs-parity)
- hooks/gsd-check-update-worker.js: add gsd-graphify-update.sh to MANAGED_HOOKS (managed-hooks)
- agents/gsd-planner.md + gsd-phase-researcher.md: slim auto-update awareness block to
  a one-line @-reference; extract full instructions to a new reference file
- get-shit-done/references/planner-graphify-auto-update.md: new reference with the
  status-file schema, the four annotation cases (running/failed/ok-current/ok-stale),
  and interaction with the existing stale-mtime annotation
- docs/INVENTORY.md: References (60 → 61 shipped) + row for new reference; regenerate
  docs/INVENTORY-MANIFEST.json via gen-inventory-manifest.cjs

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 11:59:43 -04:00
Tom Boucher
dacc23137a feat(3347): opt-in auto-update of knowledge graph after main HEAD advances
Closes #3347

Config:
- Add graphify.auto_update (default false) to manifests:
  sdk/shared/config-defaults.manifest.json, config-schema.manifest.json

Hook:
- hooks/gsd-graphify-update.sh — PostToolUse Bash matcher
  - Gates: tool_name=Bash, HEAD-advancing git op, CI=unset, in git repo,
    current branch == default branch (git.base_branch override or main/
    master/trunk fallback), graphify.enabled && graphify.auto_update both
    true, graphify on PATH, no live PID lock
  - Writes .planning/graphs/.last-build-status.json with status=running
    synchronously, then detaches hooks/lib/gsd-graphify-rebuild.sh
- hooks/lib/gsd-graphify-rebuild.sh — detached rebuild runner
  - PID-lock acquire + trap-on-exit cleanup
  - graphify update . then cp graphify-out/* → .planning/graphs/
  - Status file rewritten to status=ok|failed with exit_code, duration_ms,
    head_at_build
- Portable detach (subshell + disown, no setsid dependency)

Installer:
- bin/install.js: register hook as PostToolUse Bash matcher (5s timeout)
- Add to gsdHooks uninstall list and expectedShHooks warning list

Planner / researcher status surface (issue #3347 reviewer must-have AC):
- agents/gsd-planner.md and agents/gsd-phase-researcher.md
  load_graph_context steps now read .last-build-status.json and surface:
  running → "rebuild in flight"; failed → "auto-rebuild FAILED at {ts},
  context is from prior build"; ok with stale head_at_build → "HEAD has
  advanced since last build"

Settings:
- get-shit-done/workflows/settings.md adds "Graph auto-update" question
  with No-Recommended default; bullets and update_config block updated

Inventory:
- docs/INVENTORY.md hook count 12 → 13 with new row
- docs/INVENTORY-MANIFEST.json regenerated

Tests:
- tests/feat-3347-graphify-auto-update-config.test.cjs (8 tests):
  isValidConfigKey accepts graphify.auto_update, CANONICAL_CONFIG_DEFAULTS
  default false, config-set round-trip, sibling key preservation
- tests/feat-3347-graphify-auto-update-hook.test.cjs (18 tests):
  all bail paths (non-Bash, non-HEAD-advancing, enabled=false,
  auto_update=false, CI=true, non-default-branch, missing graphify bin,
  live-PID lock), dispatch path with mock graphify bin (sync running
  status + detached transition to ok/failed), stale-PID lock, all five
  HEAD-advancing command matchers, git.base_branch override

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 11:59:43 -04:00
Tom Boucher
a3ca6ff6d6 feat(3553): Project-Root Resolution Module via generator (Phase 4 of #3524)
Phase 4 of the CJS↔SDK hard-seam migration (parent #3524).
Eliminates the `findProjectRoot` duplication that lived at
bin/lib/core.cjs:74-140 and sdk/src/query/helpers.ts:497-590,
the drift carrier behind historical bugs #1362 and #2561.

- sdk/src/project-root/index.ts — source of truth (120 lines,
  pure-with-sync-fs). Exports findProjectRoot(startDir: string)
  and FIND_PROJECT_ROOT_MAX_DEPTH constant.
- sdk/src/project-root/index.test.ts — 13 vitest pinning fixtures
  covering all four heuristics, the #1362 guard, malformed
  config fallback, empty sub_repos, deep nesting, and depth-limit
  enforcement.
- sdk/scripts/gen-project-root.mjs — generator. Captures
  function body via Function.prototype.toString() from compiled
  sdk/dist/. Emits CJS preamble for destructured node:fs /
  node:path / node:os imports.
- sdk/scripts/check-project-root-fresh.mjs — freshness check.
  Imports the generator function directly (Phase 3's cleaner
  pattern).
- get-shit-done/bin/lib/project-root.generated.cjs — generator-
  emitted CJS mirror.
- tests/project-root-generator.test.cjs — 11 parity assertions
  comparing SDK source and generated CJS for every fixture.

- sdk/src/query/helpers.ts: -127 lines. The 94-line inline
  findProjectRoot plus the FIND_PROJECT_ROOT_MAX_DEPTH constant
  (originally at line 471) replaced by a single re-export:
  `export { findProjectRoot } from '../project-root/index.js';`
  Removed unused `parse as parsePath` import.
- get-shit-done/bin/lib/core.cjs: -83 lines net. The 67-line
  inline findProjectRoot replaced by a single
  `require('./project-root.generated.cjs')`. The detectSubRepos
  helper at lines 40-56 stays (used by loadConfig migration).

- sdk/package.json: gen:project-root + check:project-root-fresh
  scripts.
- package.json: proxy for the freshness check.
- .githooks/pre-commit: drift block.
- .github/workflows/test.yml: drift check step after the
  state-document drift step.
- CONTEXT.md: Project-Root Resolution Module entry.
- docs/INVENTORY.md, docs/INVENTORY-MANIFEST.json:
  +1 module count, +1 row.

- Full suite: 9226/9226 pass (baseline 9215 + 11 new parity
  fixtures).
- SDK vitest: 1804/1804 pass.
- Reader shrink: -127 SDK + -83 CJS = 210 lines of duplication
  deleted across the two Readers. New shared Module is 120 lines.

1. Depth limit canonicalization. CJS findProjectRoot previously
   had no explicit walk-up bound (walked until dir === root or
   homedir). The new Module uses FIND_PROJECT_ROOT_MAX_DEPTH = 10,
   matching the SDK's pre-existing value. Only affects paths
   nested more than 10 levels deep from a .planning/ root — a
   pathological case in practice. None of the existing 22 CJS
   findProjectRoot tests covered this; the new parity test does.
2. platformReadSync → readFileSync. The old CJS findProjectRoot
   used the platformReadSync wrapper from
   shell-command-projection.cjs for reading .planning/config.json,
   which returns null on read failure. The Module uses raw
   readFileSync, which throws — caught by the surrounding
   try/catch that already swallowed errors. Functionally
   equivalent for the existing code path; no test exercises the
   null-return semantic.

Closes #3553.
2026-05-15 10:07:10 -04:00
Tom Boucher
ed8f4c9a31 feat(3544): Workstream Inventory Builder/Reader split (Phase 3 of #3524)
Phase 3 of the CJS↔SDK hard-seam migration (parent #3524).
Introduces the Builder/Reader pattern for paired Modules with
mixed pure-and-I/O concerns — the template for Phase 4 and
follow-up enhancements that migrate other paired Modules.

Phase 1 and Phase 2 migrated Modules where both sides used
character-equivalent logic. Phase 3 introduces the case where
the pure logic is shareable but the I/O is legitimately per-side.
The Builder/Reader split resolves this:

- The Builder is pure — accepts pre-collected data
  (BuilderInputs struct), returns the typed projection. One
  source of truth; one generator-emitted CJS mirror. Drift
  is structurally impossible.
- The Readers are per-side hand-authored Adapters that do the
  fs reads in their native idiom (currently both sync; either
  side can go async later without touching the Builder), then
  delegate to the Builder.

- sdk/src/workstream-inventory/builder.ts — Builder source.
  170 lines. Pure. Exports buildWorkstreamInventory(inputs),
  isCompletedInventory(status), plus the three typed inventory
  interfaces (WorkstreamPhaseInventory, WorkstreamInventory,
  WorkstreamInventoryList).
- sdk/src/workstream-inventory/builder.test.ts — 18 vitest
  pinning fixtures across all status branches, progress-percent
  clamping, active-marker projection, and isCompletedInventory
  classifier.
- sdk/scripts/gen-workstream-inventory-builder.mjs — generator.
  Captures function bodies via Function.prototype.toString();
  emits with the standard GENERATED FILE banner. Includes a
  small `const relative = path.relative;` preamble in the
  output to handle ESM destructured imports in the compiled
  source.
- sdk/scripts/check-workstream-inventory-builder-fresh.mjs —
  freshness check. Imports the generator function directly
  (rather than duplicating logic) — a cleaner pattern than
  Phase 1/2's approach.
- get-shit-done/bin/lib/workstream-inventory-builder.generated.cjs —
  generator-emitted CJS mirror.
- tests/workstream-inventory-builder-generator.test.cjs — 16
  parity assertions confirming CJS-generated output ==
  SDK source output for every fixture.

- bin/lib/workstream-inventory.cjs: 159 → 132 lines.
  Projection logic gone. `inspectWorkstream` and
  `listWorkstreamInventories` collect BuilderInputs via the
  existing sync fs functions and delegate to the Builder.
  `isCompletedInventory` re-exported from the Builder (its
  signature changed from object→string, but no external
  callers exist so the change is safe).
- sdk/src/query/workstream-inventory.ts: 196 → 143 lines.
  Same shape, sync fs (the SDK was already sync — surprise from
  recon). Types re-exported from the Builder.

- sdk/package.json: gen:workstream-inventory-builder and
  check:workstream-inventory-builder-fresh scripts.
- package.json: proxy for the freshness check.
- .githooks/pre-commit: drift block.
- .github/workflows/test.yml: drift check step.
- CONTEXT.md: amended "Workstream Inventory Module" entry
  to document the Builder/Reader split.
- docs/INVENTORY.md, docs/INVENTORY-MANIFEST.json:
  +1 module count, +1 row for the generated builder.

- Full suite: 9229/9229 pass (baseline 9215 + 14 net new from
  the parity assertions).
- Vitest: 18 Builder fixtures pass.
- Reader shrink: -27 lines on CJS, -53 lines on SDK.
- Net diff (modified files only): +68 / -133 = 65-line
  reduction. New files (Builder, generator, freshness check,
  parity test) add ~600 lines of new structured code.

1. `isCompletedInventory` signature changed from
   isCompletedInventory(inventory: object) to
   isCompletedInventory(status: string). Original CJS exported
   the object form but no external caller passed an object —
   they all passed inventory.status. Verified by grep before
   committing.
2. Generator preamble. The compiled ESM uses
   `import { relative } from 'node:path'`, making `relative`
   a free variable in `buildWorkstreamInventory`. The generator
   emits `const relative = path.relative;` so the captured
   function body works in CJS.
3. Freshness check imports the generator. The freshness check
   imports the generator's buildWorkstreamInventoryBuilderCjs()
   function directly rather than duplicating generation logic.
   Cleaner than Phase 1/2; future generators should follow this.

Shareable via the Builder/Reader pattern in future enhancements:
- frontmatter (pure YAML/markdown parsing)
- plan-scan (pure PLAN.md structure parsing)
- decisions (pure decision-record parsing)
- secrets (regex-based detection in text)
- uat (UAT-criteria parsing)

Structural divergence — different approach needed:
- state — sync vs async file ops; mutation paths differ.
- workstream — lifecycle ops; per-side API surface differs.
- phase, roadmap, init, profile-output, template — large
  surfaces; each its own potential enhancement.

None of these is in scope for Phase 3.

Closes #3544.
2026-05-15 09:35:02 -04:00
Tom Boucher
fa862c77ee Merge pull request #3540 from gsd-build/feat/3536-configuration-module
feat(3536): Configuration Module via shared manifests + generator (Phase 2 of #3524)
2026-05-15 09:33:22 -04:00
Tom Boucher
173743fd9e fix: preserve canonical sub-repos normalization semantics 2026-05-15 09:25:49 -04:00
Tom Boucher
a31d45eedd Merge pull request #3547 from gsd-build/fix/3541-first-time-baseline-migration-prompt-use
fix(3541): resolve prompt-user migration actions in non-TTY runs; improve error grouping
2026-05-15 09:24:40 -04:00
Tom Boucher
12a9f4f038 fix: harden configuration dynamic patterns and writes 2026-05-15 09:18:15 -04:00
Tom Boucher
544037e132 fix: honor installer migration resolution env override 2026-05-15 09:16:41 -04:00
Tom Boucher
4e7d0c2bbf fix(3541): use /gsd:update colon syntax in comment (retired /gsd-update form)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 08:33:41 -04:00
Tom Boucher
c39a7e1f6f fix(3541): resolve prompt-user migration actions in non-TTY runs; improve error grouping
Closes #3541

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 08:16:28 -04:00
Tom Boucher
85d7a8b4b1 fix(3542): prohibit git stash in executor agents — shared stash storage violates worktree isolation
Closes #3542

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 08:16:26 -04:00
Tom Boucher
2de2d185fa feat(3536): Configuration Module via shared manifests + generator (Phase 2 of #3524)
Phase 2 of the CJS↔SDK hard-seam migration (parent #3524).
Eliminates the structural drift surface that produced bug class

After this phase, neither bin/lib/ nor sdk/src/ defines
CONFIG_DEFAULTS, VALID_CONFIG_KEYS, DYNAMIC_KEY_PATTERNS, or the
four legacy-key normalizations inline. All come from one canonical
source: the Configuration Module (sdk/src/configuration/index.ts)
+ two JSON manifests (sdk/shared/config-{defaults,schema}.manifest.json).
The CJS mirror is generator-emitted (get-shit-done/bin/lib/configuration.generated.cjs)
with a CI freshness check (sdk/scripts/check-configuration-fresh.mjs).

- sdk/shared/config-defaults.manifest.json — canonical nested defaults,
  union of CJS + SDK keys (includes security_*, post_planning_gaps,
  agent_skills, mode, every git/workflow/hooks sub-section).
- sdk/shared/config-schema.manifest.json — VALID_CONFIG_KEYS array,
  RUNTIME_STATE_KEYS array, DYNAMIC_KEY_PATTERNS array with source
  strings (regex reconstructed at runtime).
- sdk/src/configuration/index.ts — source of truth. Exports
  loadConfig (pure read), normalizeLegacyKeys (pure, idempotent,
  returns Normalization[]), mergeDefaults (deep-merge), migrateOnDisk
  (explicit opt-in disk writeback), plus CONFIG_DEFAULTS,
  VALID_CONFIG_KEYS, RUNTIME_STATE_KEYS, DYNAMIC_KEY_PATTERNS.
- sdk/src/configuration/index.test.ts — 29 vitest pinning tests.
- sdk/scripts/gen-configuration.mjs — generator (Function.prototype.toString()
  inspection of compiled SDK dist, plus brace-balanced text scan for
  internal helpers, matching the Phase 1 pattern).
- sdk/scripts/check-configuration-fresh.mjs — CI freshness gate.
- tests/configuration-generator.test.cjs — 27 parity assertions
  (CJS-generated == SDK source).
- tests/configuration-migrate-config.test.cjs — 3 cases for the new
  gsd-tools migrate-config subcommand.

- bin/lib/core.cjs: CONFIG_DEFAULTS literal now sources values from
  CANONICAL_CONFIG_DEFAULTS (the manifest), with a thin flat
  projection at the load boundary to preserve the existing
  flat-shape return contract for the ~21 CJS test files and 100+
  consumers. All four legacy-key migration blocks (branching_strategy,
  sub_repos, multiRepo, depth — historically lines 351-358, 388-397,
  401-408, 416-423) collapse to a single normalizeLegacyKeys call
  in each code path. The inline platformWriteSync writeback stays
  for now to preserve sync loadConfig semantics; the new async
  migrateOnDisk is reachable via gsd-tools migrate-config.
- bin/lib/config-schema.cjs: 135 → 31 lines. Re-exports from the
  generated Module.
- bin/lib/config.cjs: adds cmdMigrateConfig handler (calls
  migrateOnDisk on the explicit user-driven path).
- bin/gsd-tools.cjs: wires migrate-config into command dispatch.

- sdk/src/config.ts: re-exports CONFIG_DEFAULTS and mergeDefaults
  from the Module. loadConfig now calls normalizeLegacyKeys before
  mergeDefaults (replaces the inline branching_strategy graft).
- sdk/src/query/config-schema.ts: 160 → 36 lines. Re-exports from
  the Module.

- tests/config-schema-sdk-parity.test.cjs: refactored from
  "CJS Set equals SDK Set" (trivially true post-migration) to
  "both sides source from the manifest" — structural plus runtime
  invariant.
- Four other tests that text-grepped source files for valid keys
  (plan-review-convergence, bug-3212, bug-2492, feat-3210) are
  updated to use runtime VALID_CONFIG_KEYS.has() or manifest JSON
  lookups.

- CONTEXT.md: new Configuration Module entry with full Interface
  contract.
- Root package.json: check:configuration-fresh proxy script.
- sdk/package.json: gen:configuration + check:configuration-fresh.
- .githooks/pre-commit: configuration drift block.
- .github/workflows/test.yml: configuration drift step after the
  alias drift check.

- 9201 CJS tests pass (baseline pre-cycle: 9195; +6 net new tests
  across migrate-config + parity refactor)
- 1872 SDK vitest tests pass
- 29 Configuration Module vitest fixtures
- 27 CJS/SDK parity fixtures
- Net diff: +388 / −519 = 131-line reduction across the seven cycles,
  despite adding the new Module, manifests, generator, freshness
  check, and two new test files.

1. SDK CONFIG_DEFAULTS now includes manifest-canonical keys
   (resolve_model_ids: false, context_window: 200000, phase_naming,
   claude_md_path, git.create_tag, workflow.security_*,
   workflow.code_review_*, planning.*, hooks.workflow_guard, ship.*).
   Consumers accessing via [key: string]: unknown index get
   the manifest default instead of undefined.
2. SDK mergeDefaults is now proper recursive deep-merge instead of
   spread-per-section. Overlay { workflow: { research: false } }
   now preserves sibling workflow keys; previously it replaced
   the entire workflow section with only research + the section's
   defaults. Semantically identical for the common case;
   strictly better for partial nested overrides.
3. New gsd-tools migrate-config CLI subcommand for the explicit,
   opt-in on-disk migration path.

Closes #3536.
2026-05-15 00:02:56 -04:00
Tom Boucher
a7f0af2ce9 fix(3537): route every phase-number ROADMAP regex through phaseMarkdownRegexSource (#3538)
* fix(3537): route every phase-number ROADMAP regex through phaseMarkdownRegexSource

v1.42.1 added the padding-tolerant `phaseMarkdownRegexSource()` helper but
wired it into only 1 of 8 call sites that build phase-number regexes against
ROADMAP/STATE prose. The other 7 used raw `escapeRegex(phaseNum)` or partial
`0*${escapeRegex(...)}` (tolerated extra padding, not missing), so when
skills passed the resolved padded form (`02.7`) against un-padded ROADMAP
prose (`### Phase 2.7:`, `- [ ] **Phase 2.7:**`), the verbs silently no-op'd
while reporting success.

This consolidates every phase-number ROADMAP/STATE regex through the
canonical helper:

- Promote `phaseMarkdownRegexSource` from `roadmap.cjs` to `core.cjs` so
  `phase.cjs` and `core.cjs` itself can consume it (no circular dep —
  both already import `core.cjs`).
- Wire the helper into the 7 remaining sites:
  - `core.cjs:getRoadmapPhaseInternal` (replaces hand-rolled `isNumeric`
    branch that only padded integers, not decimals).
  - `roadmap.cjs:cmdRoadmapGetPhase` (searchPhaseInContent escapedPhase).
  - `roadmap.cjs:cmdRoadmapAnalyze` checkbox lookup.
  - `roadmap.cjs:cmdRoadmapAnnotateDependencies` phase header lookup.
  - `phase.cjs:cmdPhaseNextDecimal` ROADMAP prose scan.
  - `phase.cjs:cmdPhaseInsert` target anchor + decimal scan + header.
  - `phase.cjs:cmdPhaseComplete` (3 regexes: checkbox, plan-count,
    REQUIREMENTS extraction).

Adds `tests/bug-3537-padded-id-against-unpadded-roadmap.test.cjs` — a
parity-style regression matching CONTEXT.md DEFECT.GENERATIVE-FIX: for
each user-facing verb, asserts that the padded form (`02.7`) and the
un-padded form (`2.7`) produce identical ROADMAP.md against an identical
fixture. Includes one control case (`update-plan-progress`, already wired
in 1.42.1) to prove the parity assertion is non-vacuous.

Closes #3537

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore(3537): add changeset fragment (pr: placeholder, amended post-create)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore(3537): pin changeset pr: field to #3538

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 23:17:38 -04:00
Tom Boucher
bfd7ddbad3 feat(3530): STATE.md Document Module via generator (Phase 1 of #3524) (#3531)
* feat(3530): STATE.md Document Module via generator (Phase 1 of #3524)

Phase 1 of the CJS↔SDK hard-seam migration (parent #3524).
Converts the hand-synced state-document.cjs/state-document.ts pair
into a generator-driven seam, modeled on the existing
command-aliases.generated.* precedent.

What landed:
- sdk/src/query/state-document.ts is the source of truth.
- sdk/scripts/gen-state-document.ts emits
  get-shit-done/bin/lib/state-document.generated.cjs from the
  compiled SDK dist via Function.prototype.toString() inspection
  for the 7 public exports and 3 internal helpers.
- sdk/scripts/check-state-document-fresh.mjs is the CI freshness
  gate; pre-commit hook also runs it when relevant files change.
- get-shit-done/bin/lib/state-document.cjs is reduced to a one-line
  re-export from state-document.generated.cjs so existing callers
  (state.cjs, workstream-inventory.cjs, init.cjs) need no changes.
- New CI step in .github/workflows/test.yml after the existing alias
  drift check.
- sdk/package.json: gen:state-document, check:state-document-fresh
  scripts. tsx added as devDep.
- Root package.json: proxy script for the freshness check.
- CONTEXT.md: one-sentence amendment on STATE.md Document Module
  recording the source-of-truth file path.

Tests:
- sdk/src/query/state-document.test.ts: 34 vitest fixtures across
  the 7 public exports (TDD pinning safety net).
- tests/state-document-generator.test.cjs: 31 node:test parity
  assertions comparing SDK source vs generated CJS for every
  fixture.
- Full suite: 9177/9177 pass (baseline was 9146; +31 new tests).

One subtle behavior change worth flagging: the old hand-written
state-document.cjs used String(str) coercion inside escapeRegex,
which the SDK source does not. The generator faithfully matches
the SDK (the source of truth per ADR-3524), so the new CJS no
longer coerces non-string input to string before regex-escaping.
No current caller passes non-string input, so no observable
regression in the test suite. Flagged in the PR body for
reviewers.

Closes #3530.

* fix(3530): address state-document review findings
2026-05-14 22:17:20 -04:00
Tom Boucher
d4d4178603 Merge pull request #3483 from radioflyer28/feat/agent-launch-reasoning-transport-3474
feat: transport resolved reasoning effort to agent launches
2026-05-14 20:01:32 -04:00
Tom Boucher
e6790238aa Merge pull request #3527 from gsd-build/fix/3523-bug-cjs-loadconfig-warns-top-level-branc
fix(3523): stop false 'branching_strategy will be ignored' warning in CJS loadConfig
2026-05-14 19:58:08 -04:00
Tom Boucher
cda6cd3e06 Merge pull request #3526 from gsd-build/fix/3521-workflows-quick-md-two-bugs-in-post-merg
fix(quick): pin cleanup-loop CWD to project root before bare git commands (#3521)
2026-05-14 19:57:45 -04:00
Tom Boucher
75dffc8069 fix: address branching strategy review findings 2026-05-14 19:44:01 -04:00
Tom Boucher
3ab24c6c56 fix(3523): self-healing migration of legacy top-level branching_strategy
Three-part fix for the false "unknown config key(s)" warning fired for
top-level `branching_strategy` in .planning/config.json:

1. On-disk migration (option 3, mirroring multiRepo → planning.sub_repos):
   When loadConfig reads a config.json with top-level `branching_strategy`
   set and `git.branching_strategy` unset, it grafts the value into
   `git.branching_strategy` and deletes the top-level key, then persists.
   If `git.branching_strategy` is already set, the nested value wins
   (matches SDK mergeDefaults precedence, PR #3116).

2. KNOWN_TOP_LEVEL safety net: 'branching_strategy' added to the deprecated-
   keys bucket so the warning never fires even on the first read of a root
   config that feeds a workstream merge (where `parsed` may still carry it).

3. Double-emission guard: a module-level `_warnedUnknownConfigKeys` Set
   deduplicates the unknown-key warning across multiple loadConfig calls
   within a single CLI invocation (init phase-op N called it twice).

Closes #3523

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 19:03:39 -04:00
Tom Boucher
e1270c3547 fix(quick): pin cleanup-loop CWD to project root before bare git commands (#3521)
The post-merge worktree-cleanup loop in quick.md issued bare `git diff`,
`git merge`, and related commands relying on CWD = project root. An LLM
orchestrator that reformats bash across separate tool calls can leak CWD
into a worktree, causing the merge to silently no-op.

At the top of each iteration body, resolve PROJECT_ROOT via
`git -C "$WT" rev-parse --git-common-dir` and `cd "$PROJECT_ROOT"`.
If the root cannot be resolved or reached, log a skip message and
continue to the next manifest entry. All existing guards (pre-merge
deletion guard #1756, STATE.md/ROADMAP.md backup/restore, resurrection
guard #2501/#3195) remain intact after the CWD pin.

Closes #3521 (bug 1 — CWD safety only; bug 2 / resurrection guard was
already fixed in a6beac40 / PR #3201 and is pending reporter retest).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 19:02:16 -04:00
Tom Boucher
8cbb0cbc45 fix(reapply-patches): add gsd-update arm to git-enhanced two-way merge filter (#3516)
The grep -v alternation in the git-enhanced two-way merge step was missing
the 'gsd-update' arm after the slash-command rename from /gsd:update to
/gsd-update. Commits authored by the current update flow fell through the
filter and were misclassified as user customizations, causing spurious merge
prompts during /gsd-update --reapply.

Adds 'gsd-update' between 'gsd:update' and 'GSD update'. The legacy
'gsd:update' arm is preserved for back-compat; 'GSD update' and 'gsd-install'
exclusions are unchanged.
2026-05-14 19:00:38 -04:00
Tom Boucher
e0adba7e08 feat(statusline): add opt-in context_position config for narrow terminals (#2937)
Extract composeStatusline() helper from duplicated inline template logic in
runStatusline() and renderStatusline(). Both call sites now route through the
helper, which accepts a position param ('end' | 'front', default 'end').

- 'end' (default) preserves byte-identical output to v1.38.x and earlier
- 'front' renders ctx immediately after model name, before the first │
- Invalid values silently coerce to 'end' at runtime (belt-and-suspenders;
  config-set rejects invalid values upfront via enum validator)

Adds statusline.context_position to VALID_CONFIG_KEYS in both CJS and TS
schemas, enum validator in config.cjs, docs row in CONFIGURATION.md,
and a changeset. Closes #2937.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 15:45:41 -04:00
Tom Boucher
92260b3dc9 fix(config): add create_tag to buildNewProjectConfig for git.* consistency (CR nitpick) 2026-05-14 13:30:59 -04:00
Tom Boucher
efd98cdb76 fix(workflow): align complete-milestone tag placeholders 2026-05-14 12:30:16 -04:00
Tom Boucher
da21edfb59 feat(workflow): add git.create_tag config to disable milestone tagging
Adds boolean config key `git.create_tag` (default: true, fully backcompat)
so projects with their own release flow can disable GSD's automatic
`git tag -a v[X.Y]` on milestone completion. Also adds tag-collision
pre-check to prevent silent failure on re-run. Closes #3086

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 12:06:36 -04:00
Tom Boucher
12accdbd02 Merge pull request #3499 from gsd-build/fix/3489-state-complete-phase-idempotent
fix(sdk): make state.complete-phase idempotent (#3489)
2026-05-14 10:08:49 -04:00
Tom Boucher
fb6633ceda fix(workflow): detect nested git worktree in new-project bootstrap (#3491)
The `has_git` boolean returned by `init new-project` and `init ingest-docs`
was derived from a shallow `pathExists(cwd, '.git')` check, so a subdirectory
of an existing repo reported `has_git: false`. The workflow then ran
`git init`, creating a nested `.git` inside the outer worktree and silently
diverting subsequent `gsd-sdk commit` calls into the nested repo.

Replace the shallow check with `git rev-parse --is-inside-work-tree`
semantics in both CJS (`get-shit-done/bin/lib/init.cjs`) and TS
(`sdk/src/query/init.ts`, `sdk/src/query/init-complex.ts`) handlers via a new
shared `gitWorktreeInfoInternal` helper, and expose `git_worktree_root` +
`in_nested_subdir` so the workflows can refuse `git init` inside an existing
worktree and warn that planning files will track to the outer repo.

Regression test: `tests/bug-3491-nested-git-worktree.test.cjs`.

Fixes #3491

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 09:11:59 -04:00
Tom Boucher
beca07cf3a fix(sdk): make state.complete-phase idempotent (#3489)
Re-invoking `state complete-phase --phase <N>` on a phase that was
already marked complete in STATE.md silently rolled STATE.md back to
that phase's moment-of-completion — clobbering Status, Last Activity,
Last Activity Description, and the ## Current Position body. The bug
fired whenever a follow-up phase had been inserted (or the next phase
had begun) and a downstream workflow re-ran complete-phase on the
already-closed phase. Damage was silent: handler reported
{"updated":["Status","Last Activity","Current Position"]} and no error.

Root cause: cmdStateCompletePhase wrote unconditionally — it never
consulted STATE.md to detect that the requested phase had been
superseded. The handler is a legacy-bridge fallback (no native SDK
registration), so the SDK CLI fell through to gsd-tools.cjs.

Fix: add an idempotency guard at the top of cmdStateCompletePhase.
If STATE.md's canonical Current Phase field already names a phase
distinct from the one we are being asked to mark complete, return a
no-op payload ({updated:[], phase:"<N>", idempotent:true, note:"phase
already superseded; no-op"}) without writing to STATE.md.

The guard is conservative — it only fires when Current Phase is set
and differs from the resolved target. First-time completion (Current
Phase == target, or Current Phase absent) is unaffected, so the four
existing complete-phase test cases (#2761, #3063) continue to pass.

Regression test: tests/bug-3489-complete-phase-idempotent.test.cjs
- re-running complete-phase --phase 02.2 with Current Phase=02.2.1
  in STATE.md leaves the file byte-identical and reports idempotent:true
- normal first-time completion is NOT flagged idempotent

Scope: handler-level idempotency only. Does not address the related
stopped_at filename-sort ordering issue called out in the bug report
(filed under suggested fix #2) or porting to the native registry.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 07:47:18 -04:00
Tom Boucher
49e7c48007 feat(execute-phase): classify quota/rate-limit failures across runtimes (#3095) (#3490)
* feat(execute-phase): classify quota/rate-limit failures across runtimes (#3095)

Dispatched executor subagents that die from provider quota or rate-limit
errors currently look identical to a crashed agent to the orchestrator —
so step 7's recovery prompt offers "retry now" when the right action is
"wait for reset and resume". This adds a runtime-agnostic classifier and
wires execute-phase step 7 to it.

- `agent.classify-failure` SDK query returns
  `{class: 'quota-exceeded' | 'classify-handoff-bug' | 'unknown-failure',
    sentinel?, retryAfterSeconds?}`. Sentinels cover Claude Code
  (`usage limit`, `429`), Copilot CLI (`rate_limit`,
  `user_weekly_rate_limited`), Codex (`usage_limit_reached`,
  `too many requests`), and Gemini (`RESOURCE_EXHAUSTED`,
  `exceeded your`).
- `execute-phase.md` step 7 now branches on the class. Quota-exceeded
  presents a wait-for-reset prompt and points at the safe-resume gate
  landing in #3212 instead of re-dispatching a fresh executor.
- `docs/research/provider-rate-limit-signals.md` records the proactive
  (header / SDK event) signals each provider exposes and the upstream
  Claude Code / Copilot / Codex issues blocking hook-side detection —
  the forward path once host runtimes surface them.

Resume-from-partial-worktree and context-load metrics from the original
report are deliberately out of scope; they overlap #3212's
`state.verify-against-disk` work already in flight.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(execute): render quota retry hint and refresh alias artifacts

* fix(workflow): restore slash namespace and execute-phase size budget

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 07:46:56 -04:00
Tom Boucher
75d5ca5875 feat(code-review): integrate fallow structural pre-pass for /gsd-code-review (#3424)
* feat(code-review): add optional fallow structural pre-pass

* fix(ci): sync lockfile for fallow optional binaries

* fix(test): make fallow integration tests cross-platform

* fix(review): require executable fallow binary paths

* docs(review): clarify structural findings usage and size guard

* fix(fallow): preserve line:0, prefer node_modules/.bin, sync SDK twin (H1, M2, N1 from #3424 review)

* fix(workflow): harden fallow pre-pass — exit check, timeout, atomic write, size-guard order (B1, H2-H4, M1, M3 from #3424 review)

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

* fix(deps): pin fallow floor to ^2.70.0 matching lockfile (H7 from #3424 review)

* fix(config): enum-validate fallow.scope/profile + group code_quality.* contiguously (H5, N3 from #3424 review)

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

* docs(fallow): label mcp gate reserved, version-pin install, expand context schema (B3, H8, M4, M8, L1 from #3424 review)

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

* test(fallow): replace source-grep with behavioral tests, expand fixtures, fail-loud tmpdir (B4, H6, L2, L3, M5, M6, N2 from #3424 review)

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

* fix(workflow): escape closing structural_findings tag in JSON payload (CR #3424 inline finding)

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 21:19:47 -04:00
Tom Boucher
1e091d2bcb refactor(shell-projection): remove deprecated wrappers + finalize ADRs (Phase 4, #3468) (#3484)
* refactor(shell-projection): remove deprecated wrappers + finalize ADRs (Phase 4, #3468)

Final phase of the shell-command-projection expansion. Removes the legacy
core.cjs wrappers (`atomicWriteFileSync`, `safeReadFile`, `normalizeMd`)
now that every call site lives behind the seam, plus three Phase-3
stragglers (`graphify.cjs`, `template.cjs`, dead import in
`profile-pipeline.cjs`).

Documentation:
- ADR-0009: addendum noting Phase 1–4 scope expansion (subprocess +
  file I/O ownership), supersession of "does not execute" constraint,
  and resolution of open Q4.
- ADR-0010: status changed to Superseded by ADR-0009 with explanation.
- CONTEXT.md "Shell Command Projection Module" entry already current
  from Phase 1 — no edit needed.

Tests:
- `tests/atomic-write.test.cjs` deleted — wrapper it tested is gone;
  `atomic-write-coverage.test.cjs` (Phase 3) covers platformWriteSync.
- `tests/core.test.cjs::safeReadFile` + `::normalizeMd` describes
  deleted — wrappers are gone.
- `tests/concurrency-safety.test.cjs` normalizeMd suite (behavioral /
  perf / snapshot) repointed via 2-line shim at the seam's
  `normalizeContent` — full regression coverage preserved.

Test result: 9059/9041/18 — exact pre-Phase-4 baseline. All 18
failures are pre-existing path-with-spaces local-env issues.

Closes #3468

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

* refactor(shell-projection): migrate remaining raw fs.writeFileSync sites (Phase 4, #3468)

Sweeps the 7 raw fs.writeFileSync call sites that bypassed the seam through Phase 3,
folding them into platformWriteSync. Net -14 lines: deletes the local writeFileAtomicSync
helper in installer-migrations.cjs and collapses surface.cjs's manual tmp+rename into a
single seam call.

Sites migrated:
- drift.cjs (1) — frontmatter write
- learnings.cjs (1) — learning record JSON write
- install-profiles.cjs (1) — profile marker write (collapsed redundant mkdir)
- gsd2-import.cjs (1) — imported file write (collapsed redundant mkdir)
- surface.cjs (1) — surface state write (replaced manual tmp+rename block)
- installer-migrations.cjs (3) — journal init/finalize + rewrite-json action;
  deleted private writeFileAtomicSync helper and its three call sites

Two sites intentionally retained outside the seam:
- planning-workspace.cjs:241 — workspace lock (wx-flag atomic-create; previously excluded by Phase 3)
- installer-migrations.cjs:220 — install migration lock (fd write into wx-opened handle)
- writeInstallState (installer-migrations.cjs) — strict atomic contract for install state;
  the seam's fallback-to-direct-write on rename failure would silently violate the
  invariant that install state must never be left half-written. Inline tmp+rename with
  rethrow keeps the original guarantee.

Tests: 9059 / 9041 / 18 — exactly the pre-Phase-4 baseline; 18 failures are the
pre-existing path-with-spaces local-env issues, identical files as before.

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

* fix(installer-migrations): use strict atomic write for rollback install-state restore

The rollback path was restoring INSTALL_STATE via platformWriteSync, which falls
back to a direct write on rename failure and would silently violate the
half-written invariant that the install-state contract guarantees elsewhere.

Extracts the strict tmp+rename logic from writeInstallState into a shared
atomicWriteInstallState(configDir, content) helper and routes both
writeInstallState and rollbackAppliedMigrationResult through it. Preserves the
existing null-handling (rmSync when previousInstallStateBytes === null) and
existing failure-collection (failures.push on caught errors).

Byte-faithful restore: previousInstallStateBytes is written as-is (no JSON
parse round-trip), preserving the exact prior file contents on restore.

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

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 20:46:02 -04:00
radioflyer28
f70829d067 feat: transport resolved reasoning effort 2026-05-13 19:43:28 -04:00
Tom Boucher
439d9ceacd refactor(shell-projection): migrate all fs call sites to platform* seam (Phase 3, #3467) (#3481)
* refactor(shell-projection): migrate roadmap.cjs writes to platformWriteSync (#3467)

2 atomicWriteFileSync calls → platformWriteSync. The seam owns markdown
normalization, so the explicit utf-8 encoding arg is no longer needed.

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

* refactor(shell-projection): migrate config.cjs writes to platformWriteSync (#3467)

- 3 atomicWriteFileSync calls → platformWriteSync
- 1 raw fs.writeFileSync (depth→granularity migration) → platformWriteSync
- 2 fs.mkdirSync(planningBase, { recursive: true }) → platformEnsureDir

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

* refactor(shell-projection): migrate docs.cjs reads to platformReadSync (#3467)

6 try { fs.readFileSync } catch {} patterns → platformReadSync(path) with
explicit null guards. detectProjectType now reads package.json once and
shares it across has_cli_bin/is_monorepo/has_tests checks. JSON.parse is
still wrapped in a try (parsing is a separate failure mode from missing file).

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

* refactor(shell-projection): migrate audit.cjs reads to platformReadSync (#3467)

8 try { fs.readFileSync(safeFilePath, 'utf-8') } catch { continue } patterns
→ const content = platformReadSync(safeFilePath); if (content === null) continue;

The single safeSum case (where catch set status='unreadable' rather than
continue) maps to an if/else that preserves the same semantics.

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

* refactor(shell-projection): migrate planning-workspace.cjs to platform* seam (#3467)

- 2 try { fs.readFileSync } catch {} → platformReadSync (null on missing)
- 2 fs.writeFileSync (workstream pointer writes) → platformWriteSync
- 3 fs.mkdirSync(..., { recursive: true }) → platformEnsureDir

The .lock file write at withPlanningLock is intentionally NOT migrated.
That call uses { flag: 'wx' } for atomic exclusive-create, which is the
correct lock-acquisition primitive. platformWriteSync's atomic-rename
pattern would silently overwrite an existing lock file and break the
locking guarantee.

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

* refactor(shell-projection): migrate milestone.cjs writes to platform* seam (#3467)

- 5 atomicWriteFileSync calls → platformWriteSync (4 dropped normalizeMd
  wrapper; seam handles .md normalization automatically)
- 2 raw fs.writeFileSync (archive ROADMAP.md / REQUIREMENTS.md) → platformWriteSync
- 2 fs.mkdirSync(..., { recursive: true }) → platformEnsureDir
- Dropped normalizeMd import (only used as write pre-call here)

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

* refactor(shell-projection): migrate intel.cjs to platform* seam (#3467)

- 7 fs.readFileSync (existsSync+readFileSync patterns and try/catch) → platformReadSync
- 2 fs.writeFileSync → platformWriteSync
- 1 fs.mkdirSync(intelPath, { recursive: true }) → platformEnsureDir
- Consolidated dual-check (existsSync + readFileSync) into single platformReadSync
  call returning null on missing file

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

* refactor(shell-projection): migrate workstream.cjs to platform* seam (#3467)

- 5 fs.mkdirSync(..., { recursive: true }) → platformEnsureDir
- 1 fs.writeFileSync (STATE.md initial scaffold) → platformWriteSync

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

* refactor(shell-projection): migrate init.cjs reads/writes to platform* seam (#3467)

- 11 try/readFileSync and existsSync+readFileSync patterns → platformReadSync
- 1 fs.writeFileSync (skill-manifest.json) → platformWriteSync

Three bare fs.readFileSync calls remain (ROADMAP/STATE reads in code paths
where the file is required to exist) — these are not "Done when" violations
(no try/catch wrapping, no inline existsSync guard).

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

* refactor(shell-projection): migrate commands.cjs reads/writes to platform* seam (#3467)

- 6 try/readFileSync and existsSync+readFileSync patterns → platformReadSync
- 2 fs.writeFileSync → platformWriteSync
- 3 fs.mkdirSync(..., { recursive: true }) → platformEnsureDir
- Removed unused safeReadFile import (zero call sites in this file)

Three bare fs.readFileSync calls remain (sourcePath at line 752, fullPath at
443, roadmapPath in cmdAuditOpen) — preceded by existsSync guards or in code
paths where file presence is required; not "Done when" violations.

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

* refactor(shell-projection): migrate profile-output.cjs to platform* seam (#3467)

- 6 safeReadFile (from core.cjs) calls preserved by aliasing platformReadSync
  as safeReadFile in the import — same semantics, zero call-site changes
- 3 try/JSON.parse(readFileSync) patterns → platformReadSync + try/JSON.parse
- 1 existsSync+readFileSync pattern (claude.md update) → platformReadSync
- 5 fs.writeFileSync → platformWriteSync
- 4 fs.mkdirSync(..., { recursive: true }) → platformEnsureDir

Two bare fs.readFileSync calls remain (template reads where file must exist
or fail loudly) — not "Done when" violations.

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

* refactor(shell-projection): migrate state.cjs to platform* seam (#3467)

- 4 atomicWriteFileSync calls → platformWriteSync (3 dropped normalizeMd
  wrapper; seam handles .md normalization)
- 4 try/readFileSync and existsSync+readFileSync patterns → platformReadSync
- 1 fs.writeFileSync (WAITING.json) → platformWriteSync
- 1 fs.mkdirSync(..., { recursive: true }) → platformEnsureDir
- Dropped normalizeMd and atomicWriteFileSync imports (only used as write
  pre-calls here)

Bare fs.readFileSync calls remain in code paths where STATE.md is required
to exist (statePath reads in cmd handlers, dry-run prune) — not "Done when"
violations.

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

* refactor(shell-projection): migrate core.cjs to platform* seam (#3467)

- 7 try/readFileSync and existsSync+readFileSync patterns → platformReadSync
- 3 fs.writeFileSync (config writes + large-payload temp file) → platformWriteSync
- 1 fs.mkdirSync (GSD_TEMP_DIR) → platformEnsureDir

Three fs calls remain — they are the internal implementations of the
safeReadFile and atomicWriteFileSync wrappers that core.cjs exports for
backward compatibility. The wrappers are scheduled for removal in Phase 4
(#3468) and will not be migrated here.

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

* refactor(shell-projection): migrate phase.cjs writes to platform* seam (#3467)

- 6 atomicWriteFileSync calls → platformWriteSync
- 3 fs.writeFileSync(path.join(dirPath, '.gitkeep'), '') → platformWriteSync
- 3 fs.mkdirSync(..., { recursive: true }) → platformEnsureDir

Bare fs.readFileSync calls remain for roadmapPath/planPath reads where the
file is required to exist; these are not "Done when" violations.

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

* refactor(shell-projection): migrate verify.cjs to platform* seam (#3467)

- 8 safeReadFile (from core.cjs) calls preserved by aliasing platformReadSync
  as safeReadFile in the import — same semantics, zero call-site changes
- 1 existsSync+readFileSync inline ternary → safeReadFile (returns null)
- 5 fs.writeFileSync (config writes + milestones writes) → platformWriteSync

Bare fs.readFileSync calls remain for code paths where the file is required
to exist (roadmap/state/config full reads); these are not "Done when"
violations.

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

* refactor(shell-projection): migrate frontmatter.cjs + update atomic-write test (#3467)

- frontmatter.cjs: 2 atomicWriteFileSync calls → platformWriteSync. The
  legacy normalizeMd wrapper is dropped because the seam handles markdown
  normalization. safeReadFile preserved by aliasing platformReadSync.
- atomic-write-coverage.test.cjs: update the #1972 structural invariant
  to assert on platformWriteSync. platformWriteSync uses the same
  tmp-file + atomic-rename primitive that atomicWriteFileSync did — the
  no-partial-write guarantee is preserved across the migration.

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

* chore(changeset): add entry for shell-projection Phase 3 migration (#3467)

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

* chore(coderabbit): disable ESLint tool (repo uses custom lint scripts)

CodeRabbit's review surface emits a "skipped: no ESLint configuration"
warning because the repo doesn't ship ESLint config. The repo
intentionally does not use ESLint — it ships its own targeted lint
scripts (scripts/lint-no-source-grep.cjs, npm run lint:tests) that
enforce repo-specific test-quality invariants. Adding ESLint config
purely to satisfy CR would add an external dependency
(CONTRIBUTING.md: "No external dependencies in core") and overlap
with the existing custom lint surface.

Disable the ESLint tool in CR's tools config so the skip warning
stops appearing on every PR.

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

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 19:34:23 -04:00
Tom Boucher
639e4d603a refactor(shell-projection): migrate all subprocess call sites to exec*/probeTty seam (Phase 2, #3466) (#3476)
* refactor(shell-projection): migrate planning-workspace.cjs tty probe to probeTty seam (#3466)

Replaces direct execFileSync('tty') with probeTty() from the shell-projection
seam. Removes try/catch — probeTty() returns null on error/non-tty/win32.

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

* refactor(shell-projection): migrate commands.cjs to execGit seam (#3466)

- Replaces execSync('git diff --cached --name-only') with execGit array call
- Migrates 14 existing execGit(cwd, args) callers from core.cjs's local
  wrapper to the seam's execGit(args, { cwd }) signature
- Drops execGit from the core.cjs destructure to resolve naming collision

Drops try/catch around git diff — execGit returns exitCode without throwing,
so the no-staged-files / not-a-git-repo case is detected by exitCode !== 0.

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

* refactor(shell-projection): migrate check-latest-version.cjs to execNpm seam (#3466)

Routes the default-spawn path through execNpm — execNpm owns the win32
shell-flag policy. The injection point remains spawnSync-shaped for test
compatibility; an internal adapter translates { exitCode } → { status }.

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

* refactor(shell-projection): migrate init.cjs git calls to execGit seam (#3466)

Replaces 3 execSync calls with execGit array-args:
- detectChildRepos: git status --porcelain
- cmdInitNewWorkspace: git --version (worktree availability probe)
- cmdRemoveWorkspace: git status --porcelain

Drops 3 try/catch blocks — execGit returns exitCode without throwing, so
best-effort handling becomes a clean exitCode === 0 check.

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

* refactor(shell-projection): migrate core.cjs to execGit seam delegation (#3466)

- Removes direct require('child_process') from core.cjs
- Replaces execFileSync('git check-ignore') with seam's execGit
- Local execGit wrapper now a thin adapter delegating to seam — keeps the
  legacy (cwd, args) positional signature and derived timedOut field for
  the verify.cjs and worktree-safety.cjs consumers that are out of Phase 2
  scope (the wrapper proper would only be removed once those consumers
  migrate, tracked separately)

Extends the seam's _spawnResult to expose signal and error fields so callers
can compute timedOut without bypassing the seam. The Phase 1 test suite
asserts on required field presence only, so the extension is
backward-compatible.

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

* refactor(shell-projection): migrate graphify.cjs to execTool seam (#3466)

- execGraphify: spawnSync('graphify', ...) → execTool with env passthrough,
  preserving the ENOENT/TIMEOUT/EXIT_NONZERO typed reason mapping using the
  seam's signal/error fields
- checkGraphifyInstalled: spawnSync('graphify', ['--help']) → execTool
- checkGraphifyVersion strategy 1: graphify --version via execTool
- checkGraphifyVersion strategy 2: python3 importlib.metadata via execTool

Adds env option to execTool — graphify needs PYTHONUNBUFFERED=1 to drain
buffered stdout on long-running operations.

Changes seam internals to access spawnSync/execFileSync via the non-destructured
childProcess module reference. Destructured imports capture references at load
time and are un-mockable by mock.method(childProcess, 'spawnSync', ...) — which
breaks all the graphify subprocess tests. Non-destructured access restores
mockability without changing public behavior.

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

* feat(shell-projection): execGit defaults to non-interactive git env (#3466)

Bakes GIT_TERMINAL_PROMPT=0 and GCM_INTERACTIVE=never into execGit's default
env. Without these, a credential prompt or terminal-input probe blocks the
git subprocess indefinitely until our 10s timeout kills it — surfacing as
a generic timeout instead of the actual auth-prompt cause.

These were previously set ad-hoc in worktree-safety.cjs's local execGitDefault
wrapper. Moving them to the seam makes them the consistent default for every
git call across the codebase. Callers can override via opts.env.

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

* refactor(shell-projection): remove core.cjs local execGit wrapper; migrate verify + worktree-safety (#3466)

Completes the Phase 2 "remove local execGit wrapper" criterion. All callers
now use the shell-projection seam's execGit(args, opts) signature directly.

- core.cjs: delete the local execGit wrapper and the execGit export. The
  isGitIgnored seam check now calls execGit from the seam. Worktree-safety
  function calls drop their execGit DI passthrough — worktree-safety's
  internal execGitDefault now delegates to the seam and adds timedOut.
- verify.cjs: 6 callers migrate from execGit(cwd, args) to
  execGit(args, { cwd }). Imports execGit from the seam directly. The
  inspectWorktreeHealth DI passes the seam's execGit (worktree-safety now
  matches that shape).
- worktree-safety.cjs: local execGitDefault becomes a thin adapter over
  the seam — no more direct spawnSync. 11 internal callers migrate to the
  new shape. DI contract for tests changes from (cwd, args) → (args, opts).
- graphify.cjs: 2 remaining execGit callers migrate from core.cjs (now
  removed) to the seam directly.
- test mocks updated in 3 worktree-safety test files to match the new
  (args, opts) DI shape — most mocks were shape-agnostic and required no
  changes; only those that destructured cwd/args needed updates.

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

* chore(changeset): add entry for shell-projection Phase 2 migration (#3466)

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

* fix(pr3476): address CodeRabbit review findings

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 18:33:32 -04:00
Tom Boucher
ba625c0978 feat(shell-projection): add exec* dispatch and platform* file I/O seam (#3465) (#3470)
* feat(shell-projection): add exec* dispatch and platform* file I/O seam (Phase 1, #3465)

Extends shell-command-projection.cjs with two new sections:

Subprocess dispatch:
- execGit(args, opts) → { exitCode, stdout, stderr }
- execNpm(args, opts) → same; owns shell:true on Windows (npm.cmd)
- execTool(program, args, opts) → same; ENOENT → exitCode 127, no throw
- probeTty(opts) → string | null; returns null on Windows and non-tty

Platform file I/O:
- normalizeContent(filePath, content) → { content, encoding }; pure function;
  .md → full normalizeMd pass; other → CRLF→LF + trailing newline
- platformWriteSync(filePath, content, opts) → ensureDir + normalizeContent + atomic write
- platformReadSync(filePath, opts) → null on ENOENT; throws when required:true
- platformEnsureDir(dirPath) → mkdirSync recursive, idempotent

Absorbs normalizeMd fence-tracking logic from core.cjs (no circular dep).
Existing rendering exports untouched. core.cjs compat exports unchanged until Phase 4.

Updates CONTEXT.md with Shell Command Projection Module canonical definition.
31 new behavioral tests; full suite green.

Closes #3465

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

* docs(context): record shell-projection expansion session learnings

* chore(changeset): add entry for shell-projection I/O seam (#3465)

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

* docs(adr): add ADR-0010 skill-surface budget module and ADR-0011 review default reviewers (Phase 1, #3465)

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

* docs(context): record phase 1 rebase and PR session learnings (#3465)

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

* test(shell-projection): drop substring match on stderr — assert typed shape only

The lint-no-source-grep rule prohibits substring matching on stderr (or any
test-output text). The exitCode === 127 assertion already proves the ENOENT
path; the stderr content was implementation-detail of the OS.

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

* fix(pr3470): address CodeRabbit findings and minimal-core drift

* docs(adr0010): align profile interface with phase-1 scope

* docs(adr): index newly added 0010/0011 ADR drafts in README

enh-3271 invariant requires every file in docs/adr/ to be linked from
the README. Adds entries for the three ADR drafts committed earlier
in this PR.

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

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 17:12:53 -04:00
Tom Boucher
245d5f66a1 feat: add review.default_reviewers config for /gsd-review defaults (#3464)
* feat(review): add review.default_reviewers selection policy

* docs(review): explain default reviewer config and precedence

* chore(changeset): add feature entry for review.default_reviewers

* chore(changeset): set pr field for #3464

* test(review): cover unavailable default-reviewer failure path

* fix(review): sync sdk and inventory parity for default reviewers

* fix(review): use canonical /gsd:review namespace in source
2026-05-13 14:10:15 -04:00
Tom Boucher
c5d4cf35e6 fix: state mutations recurse when Current Position has dollar amounts (#3463)
* fix(state): prevent  backreference expansion in state mutations

* chore(changeset): set pr field for #3463

* test(state): use structured STATE parser in bug-3454 regression
2026-05-13 13:54:30 -04:00