Commit Graph

2813 Commits

Author SHA1 Message Date
Tom Boucher
c974d9de74 chore: pin changeset pr field to #3526
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 19:02:53 -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
9fb549e499 test(3521): add regression test for quick.md cleanup-loop CWD safety
Simulates orchestrator-leaked CWD in the post-merge cleanup loop and
asserts that PROJECT_ROOT is resolved via `git -C "$WT" rev-parse
--git-common-dir` before any bare git command, that a missing root
causes a logged skip/continue, and that the existing pre-merge deletion
guard (#1756) and STATE.md/ROADMAP.md backup/restore remain in-place
after the CWD pin.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 19:02:06 -04:00
Tom Boucher
04c1296aed chore: add changeset fragment for #3516 filter fix 2026-05-14 19:00:41 -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
490b2706f2 test(3516): add failing regression for missing gsd-update filter arm
Adds bug-3516-reapply-patches-gsd-update-filter.test.cjs — 7 tests that
assert all four exclusion patterns (gsd:update, gsd-update, GSD update,
gsd-install) are present in the git-enhanced two-way merge filter inside
get-shit-done/workflows/reapply-patches.md.

Two tests fail before the fix: 'filter excludes renamed gsd-update commits'
and 'all four expected exclusion patterns are present in the filter'.
2026-05-14 19:00:32 -04:00
Tom Boucher
7212e61142 Merge pull request #3520 from gsd-build/fix/3517-phase-complete-leaves-state-md-body-fron
fix(sdk): derive STATE.md fields from ROADMAP and refresh all stale fields after phase.complete (#3517)
2026-05-14 18:35:38 -04:00
Tom Boucher
2bee9ffb88 test(3517): add allow-test-rule exemption — STATE.md is the runtime contract 2026-05-14 17:40:45 -04:00
Tom Boucher
16eee9606a chore: pin changeset pr field to #3520 2026-05-14 17:22:34 -04:00
Tom Boucher
200c012f84 fix(sdk): derive completed_phases from ROADMAP and refresh all STATE.md fields after phase.complete
Fixes two root causes behind bug #3517:

1. Idempotency: completed_phases was blindly incremented (parseInt + 1),
   causing phase.complete N run twice to double-count (4 → 5 → 6).
   Now derives from ROADMAP progress table Complete-row count, making
   the operation idempotent.

2. Field coverage: eight STATE.md fields were left stale after phase
   completion. Now updates in the same atomic lock section:
   - frontmatter: stopped_at, last_updated, total_plans, completed_plans
   - body: Current focus, Status line, By Phase table row

   completed_plans = count of *-SUMMARY.md files across all phase dirs
   total_plans = sum of M/N plan counts from ROADMAP progress table
   percent = recomputed from fresh derived counts

Closes #3517

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 16:56:18 -04:00
Tom Boucher
f45f2ec746 Merge pull request #3515 from gsd-build/feat/2937-enhancement-statusline-opt-in-context-po
feat(statusline): opt-in context_position config for narrow terminals
2026-05-14 16:41:38 -04:00
Tom Boucher
3c2a252e14 test(2937): add CLI write-path negative test for invalid context_position (CR nitpick) 2026-05-14 16:38:05 -04:00
Tom Boucher
b40bc6bf3d chore: pin changeset pr field to #3515
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 15:46:35 -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
3f054b4a96 Merge pull request #3505 from gsd-build/fix/3346-codex-aot-toml-event-key
fix(install): emit event-name leaf key for Codex AoT hooks migration (#3346)
2026-05-14 14:03:54 -04:00
Tom Boucher
0e1de9627a Merge pull request #3508 from gsd-build/feat/3086-allow-disabling-or-customizing-the-miles
feat(workflow): add git.create_tag config to disable milestone tagging
2026-05-14 14:03:18 -04:00
Tom Boucher
db7d82513e Merge pull request #3510 from gsd-build/fix/3509-test-suite-cli-break-when-repo-path-cont
fix(tests): use array args for space-containing paths — closes #3509
2026-05-14 14:02:56 -04:00
Tom Boucher
79259c537e Merge pull request #3512 from gsd-build/codex/issue-3427-codex-skill-path
fix(codex): remove duplicate skill copies and consolidate hooks.json
2026-05-14 14:02:35 -04:00
Tom Boucher
e6627dfd77 test(codex): support direct SessionStart hooks in #2698 helper 2026-05-14 13:52:55 -04:00
Tom Boucher
44e65428e0 test(codex): harden hooks.json sessionStart test helper 2026-05-14 13:46:34 -04:00
Tom Boucher
3195d26d3f test(codex): migrate #2698 hook assertion to hooks.json 2026-05-14 13:45:11 -04:00
Tom Boucher
9dc9e37445 test(codex): align hook assertions with hooks.json 2026-05-14 13:38: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
925cce684a fix(codex): remove managed hooks.json SessionStart on uninstall 2026-05-14 13:30:59 -04:00
Tom Boucher
51f549caaf test(codex): align install contracts with no gsd skill copies 2026-05-14 13:27:12 -04:00
Tom Boucher
1d6284a718 fix(codex): remove duplicate skill copies and consolidate hook payloads 2026-05-14 13:17:39 -04:00
Tom Boucher
434e6aa162 test(3346): assert handler body drops legacy event field (CR nitpick) 2026-05-14 12:59:38 -04:00
Tom Boucher
f1be1111cf fix(tests): round-trip via frontmatter get to satisfy lint-no-source-grep 2026-05-14 12:49:48 -04:00
Tom Boucher
79425989e7 chore: pin changeset pr field to #3510
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 12:44:25 -04:00
Tom Boucher
c21bbf116e fix(tests): use array form for space-containing paths in CLI invocations
On machines where os.tmpdir() returns a path with a space (e.g.
/Volumes/Mini Me/tmp), runGsdTools() string args were whitespace-split by
the helper tokeniser, truncating paths at the first space.  Switch all
calls that embed a dynamic path into the argument list to the array form
of runGsdTools() so execFileSync receives each path as a single argv slot.

Fixes #3509

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 12:43:48 -04:00
Tom Boucher
efd98cdb76 fix(workflow): align complete-milestone tag placeholders 2026-05-14 12:30:16 -04:00
Tom Boucher
e1f742788e chore: pin changeset pr field to #3508
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 12:09:55 -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
625ccf5217 docs(context): capture 2026-05-14 triage-session learnings
Adds eight Session learnings entries covering the seven defect PRs opened
in this session plus the multi-PR triage operational pattern:

- #3095/#3490 — executor failure classification + cross-runtime sentinels
- #3489/#3499 — STATE.md idempotency oracle for state.complete-phase
- #3488/#3501 — same-phase short-form depends_on lookup index
- #3491/#3502 — git rev-parse --is-inside-work-tree primitive for nested
  worktree detection
- #3493/#3500 — extractCurrentMilestone generic Phase Details continuation
- #3503/#3504 — homedir substring containment trailing-slash anchor
- #3346/#3505 — Codex AoT TOML leaf key event-name composition
- #3506/#3507 — label-scoped stale-bot sub-job pattern

Plus operational lessons on GitHub auto-close-keyword parsing in PR bodies,
gh-templates-first hook gating, sub-agent capacity ceilings on 24 GB
systems, and trust-but-verify discipline on agent return reports.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 10:52:40 -04:00
Tom Boucher
b3aced3ca2 Merge pull request #3507 from gsd-build/chore/3506-stale-retest-5day
chore(ci): add 5-day auto-close for awaiting-retest / needs-reproduction (#3506)
2026-05-14 10:48:51 -04:00
Tom Boucher
35a09e7612 chore(ci): add 5-day auto-close for awaiting-retest / needs-reproduction (#3506)
Adds a second job to .github/workflows/stale.yml scoped via only-issue-labels
to awaiting-retest and needs-reproduction. days-before-issue-stale=5,
days-before-issue-close=0 — when the inactivity threshold trips the stale
+ close messages fire in the same run. PR side of the action is disabled
(-1 sentinel) so this job touches issues only.

The existing broad 28+14 job adds awaiting-retest and needs-reproduction to
its exempt-issue-labels list so the two jobs do not fight over the same
issues.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 10:48:08 -04:00
Tom Boucher
619915de96 fix(install): emit event-name leaf key for Codex AoT hooks migration (#3346)
migrateCodexHooksMapFormat re-emitted the raw `[hooks.<X>]` path segment as
the leaf TOML key of the new `[[hooks.<EVENT>]]` block. When the legacy
table key was a `<file>:<event>:<line>:<col>` location identifier and the
real event lived in an `event = "..."` body field, the migration emitted a
header like `[[hooks."C:\\Users\\helen\\.codex\\config.toml:session_start:0:0"]]`
that Codex 0.124.0+ refuses to load — causing `npx get-shit-done-cc@latest`
to abort the Codex runtime install on Windows configs that pre-date AoT.

Mirror the flat-AoT branch in the map-format and stale-namespaced-AoT
branches: when the section body declares `event = "..."`, that name wins
as the leaf key and `event` is excluded from the re-emitted handler body.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 10:43:09 -04:00
Tom Boucher
9deb0fea45 Merge pull request #3504 from gsd-build/fix/3503-path-replacement-trailing-slash
fix(tests): require trailing slash in path-replacement homedir check (#3503)
2026-05-14 10:30:30 -04:00
Tom Boucher
b4d2817e67 fix(tests): require trailing slash in path-replacement homedir check (#3503)
The check at tests/path-replacement.test.cjs:163 used a naive
content.includes(normalizedHomedir) to detect resolved homedir leaks in
installed .md files. When os.homedir() is short (e.g. /root inside a
Docker container), the substring false-matches inside ordinary tokens
such as `</root_cause_analysis>` in agents/gsd-debug-session-manager.md,
producing spurious failures with no actual path leak.

Real path leaks are always followed by a path separator, so require
`normalizedHomedir + '/'` instead. Extracted the predicate into a
testable `containsResolvedHomedir` helper and added regression tests
covering the /root case, a genuine /home/alice leak, /root followed by
an actual separator, and the $HOME placeholder short-circuit.

Fixes #3503

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 10:21:11 -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
62c64a757c Merge pull request #3500 from gsd-build/fix/3493-extract-milestone-generic-phase-details
fix(sdk): extractCurrentMilestone preserves generic Phase Details heading (#3493)
2026-05-14 10:08:44 -04:00
Tom Boucher
bb02b16698 Merge pull request #3501 from gsd-build/fix/3488-decimal-phase-short-form-depends-on
fix(sdk): expand short-form depends_on for decimal-phase plans (#3488)
2026-05-14 10:08:40 -04:00
Tom Boucher
888c9ba58a Merge pull request #3502 from gsd-build/fix/3491-detect-nested-git-worktree
fix(workflow): detect nested git worktree in new-project bootstrap (#3491)
2026-05-14 10:08:37 -04:00
Tom Boucher
5a8495adb7 fix(init): preserve inside=true on root lookup failures 2026-05-14 09:56:36 -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
809c2bee39 fix(sdk): expand short-form depends_on for decimal-phase plans (#3488)
The DAG resolver in phase-plan-index only matched full-stem
('03-01-auth-hardening') and canonical-prefix ('03-01') forms when
resolving `depends_on` references, so plans in decimal phases
(e.g. `99.9-test`, `02.2-cross-repo`) declaring short-form
`depends_on: [01]` had their edges silently dropped. Dependents
collapsed into wave 1 and the SDK emitted a misleading
"declared wave: N but depends_on DAG places it in wave 1" warning
that pointed at the wave declaration rather than the broken reference.

Add a tertiary short-form index keyed on the trailing `-NN` of each
plan's canonical ID — derived per plan via `lastIndexOf('-')` so it
handles integer, letter-suffixed, and decimal phase IDs uniformly.
Emit a dedicated `Plan X: unresolved depends_on reference 'NN' — no
matching plan in phase` warning whenever a dep fails all three lookup
forms, so a dropped edge can no longer hide behind the wave-mismatch
warning.

Regression coverage added in `sdk/src/query/phase.test.ts` for the
decimal-phase short-form case, the integer-phase short-form case, and
the unresolved-reference warning.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 08:28:11 -04:00
Tom Boucher
15e9c86ead fix(sdk): extractCurrentMilestone preserves generic Phase Details heading (#3493)
When a roadmap places shared phase-detail bodies under a non-version-prefixed
`## Phase Details` heading AFTER a `### 📋 vX.Y+ (Planned)` sibling in document
order, the entire Phase Details section fell outside the slice returned by
`extractCurrentMilestone`. `gsd-sdk query phase.insert N` then reported
"Phase N not found in ROADMAP.md" even though `### Phase N:` was unambiguously
present.

PR #2455 (closing #2422) added a same-version `continue` branch that already
handles the version-prefixed variant (e.g. `## v2.0 Phase Details`). This fix
extends the same intent to the generic-label variant: after the initial
boundary scan, look for a literal `^#{1,3}\s+Phase\s+Details\b` heading past
`sectionEnd` and, if found, append the Phase Details block (up to the next
real milestone boundary — version-bearing or milestone-emoji-bearing heading —
or EOF) to the returned slice. The intervening planned-milestone content is
skipped so it does not leak into the active-milestone view.

Bounded to a single append so a malformed roadmap can't loop. Only matches the
literal `Phase Details` label (canonical per GSD ROADMAP template); anything
else continues to terminate the slice. Does not regress the v2.0/v2.1+
version-prefixed handling shipped by #2455 (existing `bug-2422` test still
passes).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 07:52:51 -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
radioflyer28
810778e137 fix(sdk): gate reasoning effort by runtime allowlist 2026-05-13 22:27:15 -04:00