Commit graph

1559 commits

Author SHA1 Message Date
Tom Boucher
dff73009c8 fix: use camelCase parameter names in execute-task and complete-slice prompts (#2933) (#3236)
The prompts told the LLM to pass snake_case params (milestone_id, slice_id,
task_id) but the TypeBox schemas expect camelCase (milestoneId, sliceId,
taskId), causing "Missing named parameter" validation errors.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 14:31:38 -06:00
Tom Boucher
4a82bc01dc fix: check bootstrap completeness in init wizard gate, not just .gsd/ existence (#2942) (#3237)
A zombie .gsd/ state (symlink exists but missing PREFERENCES.md and
milestones/) caused the init wizard to be skipped entirely, resulting in
an uninitialized project session.

- guided-flow.ts: Replace bare `!existsSync(gsdRoot(basePath))` with a
  compound check for PREFERENCES.md or milestones/ bootstrap artifacts
- auto-start.ts: Check milestones/ path directly instead of .gsd/ which
  ensureGsdSymlink already created (was dead code)
- Add zombie-gsd-state.test.ts verifying both fixes

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 14:31:10 -06:00
Tom Boucher
0cedaf5fb9 fix: specify write tool for PROJECT.md in milestone/slice prompts (#3238)
* fix: specify write tool for PROJECT.md in complete-milestone/slice prompts (#2946)

The prompts for complete-milestone step 11 and complete-slice step 13
gave ambiguous instructions to "update PROJECT.md" without naming which
tool to use. This caused LLMs to call `edit` with only `newText`,
missing the required `path` and `oldText` parameters. Now both prompts
explicitly instruct the LLM to use the `write` tool with the correct
parameters, since PROJECT.md updates are full-document refreshes.

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

* fix: preserve 'refresh current state if needed' phrase in complete-slice step 13

The PR's rewrite of step 13 to specify the write tool accidentally
removed the phrase that an existing test asserts on. Re-add it while
keeping the explicit write-tool instruction.

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 14:31:04 -06:00
Tom Boucher
3d0eb32756 fix: widen completing-milestone gate to accept "None required" and similar phrasings (#2931) (#3239)
The verification_operational gate used exact equality against "none",
causing a permanent dispatch-stop loop when the planning agent wrote
variants like "None required", "N/A", or "Not applicable". Extract
an isVerificationNotApplicable() helper with a regex that covers
common not-applicable phrasings and use it in the gate condition.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 14:30:57 -06:00
Tom Boucher
aa6cde32d9 fix: prevent ask_user_questions from poisoning auto-mode dispatch (#2936) (#3240)
Add autonomous execution guard to plan-slice, execute-task, and
complete-slice prompts prohibiting ask_user_questions and
secure_env_collect in auto-mode. When the LLM called these interactive
tools mid-unit, the queued user response caused the subsequent
gsd_plan_slice/gsd_complete_task call to fail with "Skipped due to
queued user message", leading to artifact verification failure and
stuck-loop re-dispatch 2-4x.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 14:30:43 -06:00
Tom Boucher
493bf3cd6b fix: guard null s.currentUnit in runUnitPhase closeout after stopAuto race (#2939) (#3241)
When the user stops auto-mode mid-unit, stopAuto() resolves the unit
promise and then resets s.currentUnit to null. The resumed runUnitPhase()
coroutine then hits s.currentUnit.startedAt on the closeout line and
throws a TypeError, producing a spurious "Iteration error" warning.

Fix: wrap the closeoutUnit call in an `if (s.currentUnit)` guard
(matching the existing pattern at lines 136 and 344), and switch
remaining accesses to optional chaining.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 14:30:29 -06:00
Tom Boucher
3a0cc5e849 fix: replace web_search with search-the-web in prompts and agent frontmatter (#2920) (#3245)
Prompt files referenced `web_search` (an Anthropic API implementation
detail) instead of the registered GSD tool name `search-the-web`, causing
Invalid tool input errors when the model attempted to call the wrong tool.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 14:30:01 -06:00
Tom Boucher
fc813bc657 fix: preserve milestone title in upsertMilestonePlanning when DB row pre-exists (#2879) (#3247)
State reconciliation inserts milestone rows with empty titles via INSERT
OR IGNORE. When gsd_plan_milestone later calls upsertMilestonePlanning,
the UPDATE statement did not include the title column, so it stayed empty
permanently. Add title as a COALESCE-guarded column in the UPDATE and
pass it from the plan-milestone handler.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 14:29:50 -06:00
Tom Boucher
c602b244c6 fix: invalidate stale milestone validation on roadmap reassessment (#2957) (#3242)
When roadmap reassessment adds/modifies/removes slices after a
needs-remediation validation verdict, the prior milestone-validation
DB row and VALIDATION.md file are now cleared. This forces
deriveState() to return phase: 'validating-milestone' once the new
slices complete, instead of dead-ending at 'completing-milestone'
with a stale verdict.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 14:29:39 -06:00
Tom Boucher
6a8f33f49c fix(discuss): add roadmap fallback when DB is open but empty (#2892) (#3244)
When gsd.db is truncated to 0 bytes after a crash, getMilestoneSlices()
returns [] even though isDbAvailable() is true. This caused showDiscuss()
to falsely report "All slices are complete" despite incomplete slices
existing in the ROADMAP file. Add a cross-check: if the DB returns zero
slices but a roadmap exists, fall back to parseRoadmapSlices() to derive
slice state from the roadmap (the ground truth).

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 14:29:31 -06:00
Tom Boucher
c31b03d57c fix: integrate Codex & Gemini CLI into provider routes and rate-limit handling (#2922) (#3246)
Extend PROVIDER_ROUTES so doctor/routing recognizes google-gemini-cli
as an alternative for google and openai-codex as an alternative for
openai. Cap rate-limit backoff at 30s for CLI-style providers to avoid
leaving users stuck in long backoff windows.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 14:23:26 -06:00
Tom Boucher
06d56e5f03 fix(error-classifier): widen STREAM_RE to cover all 7 V8 JSON parse error variants (#2916) (#3243)
Stream-truncation JSON parse errors like "Expected ',' or '}' after
property value in JSON" were falling through to kind: "unknown", causing
permanent auto-mode pause instead of transient 15s backoff.

- Broaden STREAM_RE: replace narrow "Expected double-quoted property name"
  with "Expected.*in JSON" and add "Unterminated.*in JSON" to catch all 7
  V8 JSON parse error message variants
- Move stream check before server/connection checks to prevent false
  matches (e.g. "position 500" matching SERVER_RE, "Unterminated" matching
  CONNECTION_RE's "terminated" pattern)
- Add 4 test cases for the previously uncovered V8 error variants

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 14:17:25 -06:00
Tom Boucher
c1c185c3b9 fix: prevent git stash from destroying queued milestone CONTEXT files (#2505) (#3273)
The pre-merge stash in mergeMilestoneToMain used --include-untracked
which swept ALL untracked files into the stash, including queued
milestone CONTEXT files under .gsd/milestones/. If stash pop failed,
these files were permanently trapped in the stash entry.

Two-part fix:
1. Add pathspec exclusion `:(exclude).gsd/milestones` to stash push
   so queued milestone dirs are never swept into the stash
2. Shelter queued milestone dirs before squash merge to prevent
   conflicts with copies in the milestone branch (via copyPlanningArtifacts),
   then restore them on both success and error paths

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 14:17:16 -06:00
Tom Boucher
873a79f484 fix: skip staleness rebuild in npm tarball installs (#2877) (#3250)
The ensure-workspace-builds.cjs postinstall script falsely detected
workspace packages as stale in npm tarball installs. npm sets all
tarball entries to a canonical timestamp (Oct 26 1985), but extraction
ordering causes src/ files to appear 1-2 seconds newer than dist/
files. This triggered a rebuild attempt that either failed silently
(no tsc available) or — when tsc was globally installed — could
produce broken dist/ output, corrupting the known-good pre-built
files and causing the DefaultResourceLoader export error on startup.

The fix gates the src-vs-dist staleness check behind a .git directory
check: only development clones (with .git/) perform the timestamp
comparison. npm tarball installs (no .git/) only check for missing
dist/index.js, which is the safe and correct behavior.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: TÂCHES <afromanguy@me.com>
2026-03-30 14:17:02 -06:00
Tom Boucher
16d31f2253 fix(parallel): check worktree DB for milestone completion in merge (#2812) (#3256)
determineMergeOrder relied solely on orchestrator WorkerInfo.state
being "stopped" to find mergeable milestones. When the orchestrator
state drifts (worker respawned, status.json deleted, etc.), completed
milestones become invisible to the merge command.

Now scans .gsd/worktrees/<MID>/.gsd/gsd.db for milestones with
status='complete', using the same subprocess-sqlite3 pattern as the
parallel-monitor-overlay. The worktree DB is the ground truth.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 13:52:21 -06:00
Tom Boucher
348c399c9d fix: make claude-code provider stateful with full context and sidechain events (#2859) (#3254)
The claude-code provider in gsd-pi was effectively stateless: it sent
only the last user message, disabled session persistence, and filtered
out all sidechain/subagent events. This made multi-turn conversations
feel isolated and caused incomplete responses.

- Replace extractLastUserPrompt with buildPromptFromContext that
  serialises the full conversation history (system prompt + all turns)
- Change persistSession from false to true for session continuity
- Remove parent_tool_use_id filtering so delegated/sidechain outputs
  are included in the final response
- Extract buildSdkOptions for testability

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 13:52:14 -06:00
Tom Boucher
a7388f795b fix(worktree): preserve non-empty gsd.db during sync to prevent truncation (#2815) (#3255)
syncProjectRootToWorktree unconditionally deleted the worktree's gsd.db
to force a rebuild from synced artifacts (#853). On respawned workers,
gsd-migrate had already populated the DB (~1.7MB), so the deletion
caused openDatabase to create a new empty file, leading to "no such
table: slices" failures and a respawn loop.

Now only deletes 0-byte (empty/corrupt) DB files, preserving freshly
migrated databases.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 13:52:06 -06:00
Tom Boucher
abd9be24e9 fix: align @gsd/native module type with compiled output (#3253)
* fix: align @gsd/native module type with compiled output (#2861)

The package declared "type": "module" and used "import"-only export
conditions, but the addon loader used import.meta.url which is
incompatible when the parent package enforces ESM resolution on
Node.js v24. Switch to "type": "commonjs" with "default" export
conditions and remove the import.meta.url/__dirname shim (CJS
provides both natively).

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

* fix: restore dual CJS/ESM compat for native addon loader

The ESM-to-CJS conversion removed import.meta.url polyfills, but the CI
test loader (dist-redirect.mjs) transpiles this file to ESM via
ts.transpileModule — making __dirname and require unavailable at test time.

Add runtime typeof guards that use the CJS globals when available (compiled
output) and fall back to import.meta.url in ESM (test runner). Use
@ts-expect-error to suppress TS1470 for the import.meta branches that are
unreachable in the compiled CJS output.

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

* fix: use indirect eval for import.meta.url to avoid CJS parse-time error

import.meta is a parse-time syntax error in CJS — typeof guards don't
help because Node.js rejects the syntax before executing any code.
Wrapping in new Function("return import.meta.url") hides the syntax
from the CJS parser while still working when executed as ESM (test runner).

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

* fix: replace new Function(import.meta.url) with loader-injected CJS globals

import.meta is static syntax unavailable in new Function() and eval()
scopes, causing rtk-portability CI failures across all platforms.

Instead of trying to access import.meta.url indirectly, the test loader
(dist-redirect.mjs) now injects __dirname, __filename, and require as a
preamble when transpiling workspace packages to ESM. This lets native.ts
use __dirname/require directly in both CJS (production) and ESM (CI test)
contexts without any import.meta.url fallback.

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 13:51:57 -06:00
Tom Boucher
8917cc75be fix: parse hook/* completed-unit keys correctly in forensics + doctor (#2826) (#3252)
The `key.indexOf("/")` split broke compound hook types like
"hook/telegram-progress/M007/S01", yielding unitType="hook" instead of
"hook/telegram-progress". This bypassed the `startsWith("hook/")` guard
in verifyExpectedArtifact, producing false-positive missing-artifact
errors for every hook unit.

Extract a shared `splitCompletedKey()` helper that handles the two-segment
hook prefix and use it in both `detectMissingArtifacts` (forensics.ts) and
the orphaned-key check (doctor-runtime-checks.ts).

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 13:51:45 -06:00
Tom Boucher
01d7200e7b fix: copy mcp.json into auto-mode worktrees (#2791) (#3251)
Add mcp.json to ROOT_STATE_FILES and copyPlanningArtifacts so MCP
server configurations are available inside worktrees.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 13:51:37 -06:00
Tom Boucher
01aea09f74 fix: add gsd_requirement_save and upsert path for requirement updates (#3249)
* fix: add gsd_requirement_save tool and upsert path for gsd_requirement_update (#2919)

gsd_requirement_update returned not_found for all requirements because
requirements written to REQUIREMENTS.md were never inserted into the DB,
and no create path existed. This adds:

- saveRequirementToDb() + nextRequirementId() in db-writer.ts (symmetric
  to saveDecisionToDb/nextDecisionId)
- gsd_requirement_save tool in db-tools.ts with auto-assigned IDs
- Upsert behavior in updateRequirementInDb() — creates a skeleton row
  when the requirement ID is not in the DB instead of throwing

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

* fix: add null check before reverting requirement on disk write failure

The `existing` variable from `getRequirementById` can be null when the
requirement was newly created (not previously in DB). Guard the revert
call to avoid passing null to `upsertRequirement`.

Fixes TypeScript error: 'Requirement | null' is not assignable to 'Requirement'

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 13:51:26 -06:00
Tom Boucher
341a211be2 fix: use authoritative milestone status in web roadmap (#2807) (#3258)
* fix: use authoritative milestone status in web roadmap instead of slice heuristics (#2807)

The roadmap view was deriving milestone status from slice completion
flags, which disagrees with the actual GSD state model when milestones
have lifecycle states (complete/active/pending/parked) or validation
verdicts that differ from what slice progress implies.

Add status and validationVerdict fields to WorkspaceMilestoneTarget,
populate them from the state registry and VALIDATION files, and update
getMilestoneStatus() to prefer the authoritative status with a fallback
to the old heuristic for backward compatibility.

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

* fix: add .js import extension and slice type annotations in workspace-status

Fixes TS2835 (missing .js extension for NodeNext resolution) and TS7006
(implicit any on slice callback parameters) that caused CI build failure.

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

* fix: extract workspace types to .ts file to avoid jsx resolution error

Move WorkspaceTaskTarget, WorkspaceSliceTarget, WorkspaceMilestoneTarget,
and RiskLevel to workspace-types.ts so that workspace-status.ts (a plain
.ts file) can import them without requiring --jsx. The .tsx store file
re-exports the types for backward compatibility.

Fixes TS6142 in CI for PR #3258.

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 13:50:57 -06:00
Tom Boucher
50fbd0a837 fix(docs): use ~/.pi/agent/extensions/ for community extension install path (#3131) (#3259)
Community extensions must be placed in ~/.pi/agent/extensions/, not
~/.gsd/agent/extensions/ which is reserved for bundled extensions synced
from the gsd-pi package. Extensions placed in the wrong path are silently
ignored by the loader.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 13:50:25 -06:00
Tom Boucher
d25dbb15fd fix: add disk→DB slice reconciliation in deriveStateFromDb (#2533) (#3262)
Slices defined in ROADMAP.md but missing from the SQLite database caused
permanent "No slice eligible — check dependency ordering" blocks. The
dependency resolver only considered DB rows, so disk-only slices were
invisible. This adds a reconciliation step (mirroring the existing
milestone reconciliation) that parses each milestone's ROADMAP.md,
compares against getMilestoneSlices(), and inserts missing slices with
correct status based on SUMMARY file presence.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 13:50:13 -06:00
Tom Boucher
0964c97931 fix: run forensics duplicate detection before investigation (#2704) (#3260)
Move the dedup check from after the Investigation Protocol to before it,
so already-known bugs are caught before spending tokens on deep source
analysis. The DEDUP_PROMPT_SECTION now acts as a pre-investigation gate
with a decision to skip full investigation when a match is found.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 13:50:06 -06:00
Tom Boucher
3d896eee8a fix: skip TUI render loop on non-TTY stdout to prevent CPU burn (#3095) (#3263)
When gsd is spawned as an RPC bridge child process, stdout is a pipe
(process.stdout.isTTY === undefined). The TUI render loop would run at
~4,600 renders/sec writing ANSI escape codes to the pipe, consuming
500%+ CPU per process while idle.

Add isTTY guard to Terminal interface, ProcessTerminal.start(), TUI.start(),
and requestRender() so the entire render pipeline is skipped on non-TTY stdout.
RemoteTerminal (browser-backed) correctly reports isTTY=true.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 13:49:55 -06:00
Tom Boucher
da135e9334 fix: persist forensics report context across follow-up turns (#2941) (#3261)
The forensics prompt was sent as a one-shot message via sendMessage()
with triggerTurn: true, causing context loss on follow-up turns. Now
writes an active-forensics.json marker to .gsd/runtime/ so that
buildBeforeAgentStartResult() can re-inject the forensics prompt on
subsequent turns, mirroring how guided task execution context works.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 13:49:50 -06:00
Tom Boucher
155df22e9e fix: invalidate workspace state on turn_end so milestones list stays current (#2706) (#3266)
The milestones list only refreshed on agent_end events, causing stale
milestone state during multi-turn agent execution. Add turn_end as a
workspace cache invalidation trigger so the UI reflects milestone
changes after each turn boundary.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 13:48:40 -06:00
Tom Boucher
603839d7f8 fix: eliminate 3 recurring doctor audit false positives (#3105) (#3264)
Bug 1: orphaned worktree check now skips directories that only contain
doctor artifacts (.gsd/doctor-history.jsonl), preventing the circular
false positive where appendDoctorHistory recreates the dir it reports.

Bug 2: blocker_discovered_no_replan check now skips when all tasks are
done, treating the blocker as implicitly resolved and breaking the
deadlock with stale_replan_file.

Bug 3: parsePlan now scans the full body for task checkboxes after the
Tasks section, finding T02+ entries that appear after interleaved
detail headings (## Steps, ## Must-Haves).

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 13:48:26 -06:00
Tom Boucher
03a479858d fix(web): reconcile auto-mode state with on-disk lock in dashboard (#2705) (#3265)
The subprocess spawned by collectAuthoritativeAutoDashboardData always
starts with fresh module state (s.active === false), so the web UI
always showed "Start auto" even while auto mode was running. After
obtaining the subprocess result, reconcile active/paused state with
the on-disk session lock (.gsd/auto.lock) and paused-session metadata
(.gsd/runtime/paused-session.json).

Closes #2705

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 13:48:21 -06:00
Tom Boucher
795e160cc3 fix: treat ghost milestones as ineligible for parallel execution (#2501) (#3268)
Milestones with no registry entry (ghost directories with no planning
files) were falling through to eligible status due to the fallback
`entry?.status ?? "pending"` combined with empty deps. Now explicitly
classified as ineligible with "no planning data" reason before any
status/dep checks run.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 13:48:15 -06:00
Tom Boucher
3ec96fd992 fix: redirect auto-mode to headless when stdout is piped (#2732) (#3269)
When `gsd auto` is run with piped stdout (e.g. `gsd auto | cat` or
`gsd auto > file`), the TUI cannot render on a non-terminal output
stream, causing the process to hang indefinitely.

This fix:
- Detects piped stdout before entering interactive mode and redirects
  `gsd auto` to headless mode automatically
- Extends the interactive mode TTY gate to also check process.stdout.isTTY
  (previously only checked stdin), with a descriptive error message
- Adds `gsd headless` to the non-interactive alternatives hint

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 13:48:07 -06:00
Tom Boucher
81e303a483 fix: attempt VACUUM recovery when initSchema fails with corrupt freelist (#2519) (#3270)
When a file-backed database has a corrupted freelist, DDL operations
fail with "database disk image is malformed" even though integrity_check
passes. This adds VACUUM recovery to openDatabase() before re-throwing,
matching SQLite's documented recovery strategy for freelist corruption.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 13:47:13 -06:00
Tom Boucher
eab13c0ef5 fix: resolve db_unavailable loop in worktree/symlink layouts (#2517) (#3271)
Three fixes for the auto-mode regression where db_unavailable causes
infinite artifact-retry re-dispatch loops:

1. resolveProjectRootDbPath now handles /.gsd/projects/<hash>/worktrees/
   paths (symlink-resolved layout) in addition to /.gsd/worktrees/
2. ensureDbOpen emits structured diagnostics (resolvedPath, cwd, error)
   instead of silently returning false
3. Post-unit artifact retry skips when isDbAvailable() is false, treating
   DB infra failure as fatal instead of entering a retry loop

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 13:47:01 -06:00
Tom Boucher
7b72e1132c fix: correct OAuth fallback request shape for google_search (#2963) (#3272)
The searchWithOAuth() function sent a request body that the Cloud Code
Assist API rejected with 400 INVALID_ARGUMENT. Two issues:

1. URL was missing ?alt=sse query parameter (endpoint returns SSE format)
2. Request body was missing the required userAgent field

Also adds regression tests that capture the fetch call and assert the
request URL and body match the Cloud Code Assist wire contract.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 13:46:38 -06:00
Tom Boucher
37ec08d23c fix: prevent UAT stuck-loop and orphaned worktree after milestone completion (#3065)
Bug 1 -- UAT stuck-loop: syncProjectRootToWorktree used force:false for
all milestone files, which preserved stale ASSESSMENT files in the
worktree. When the project root had a passing verdict but the worktree
retained a FAIL copy (or lost it during DB rebuild), checkNeedsRunUat
found no passing verdict and re-dispatched run-uat indefinitely (x9).

Fix: after the additive-only safeCopyRecursive, walk ASSESSMENT files in
the project root and force-overwrite the worktree copy when the source
contains a verdict field. This is safe because ASSESSMENT verdicts are
only ever overwritten in a forward direction (FAIL -> PASS on retry).

Bug 2 -- Orphaned worktree: removeWorktree silently swallowed failures
from git worktree remove when untracked files (UAT-RESULT, ASSESSMENT)
blocked removal. The .git/worktrees/<name> internal directory held a
lock that also prevented the rmSync fallback from working.

Fix: after both native removal attempts fail, explicitly remove the git
internal .git/worktrees/<name> directory first, then retry rmSync on
the worktree filesystem directory. Log a warning with manual cleanup
instructions if the final attempt also fails.

Closes #2821

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 13:36:01 -06:00
Tom Boucher
47c1b9cd7f fix(mcp): handle server names with spaces in mcp_discover (#3037)
getServerConfig now trims whitespace and performs case-insensitive
matching so that names like "langgraph Code" resolve correctly.
getOrConnect uses config.name as the canonical cache key to prevent
duplicate connections from variant casing.

Closes #3029

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 13:29:14 -06:00
Tom Boucher
74c585074f fix(gsd): detect markdown body verdicts and guard plan-milestone against completed slices (#2960) (#3035)
Two fixes for the state corruption chain reported in #2960:

1. extractVerdict() now detects verdicts in markdown body patterns
   (e.g., **Verdict:** PASS) when YAML frontmatter is absent, preventing
   the state machine from looping on validating-milestone when LLMs write
   VALIDATION.md manually.

2. handlePlanMilestone() now refuses to re-plan a milestone that has
   completed slices, preventing INSERT OR IGNORE from shadowing completed
   work after worktree recreation or DB resync.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 13:28:51 -06:00
TÂCHES
0a2c9b64c6 feat: stream full text and thinking output in headless verbose mode (#2934)
Previously, headless --verbose mode accumulated text_delta events into a
buffer and displayed a single truncated 120-char [thinking] line before
tool calls. The model's actual text responses between tool calls were
effectively invisible.

Changes:
- Stream text_delta and thinking_delta events directly to stderr in
  verbose mode with [text] and [thinking] block markers
- No truncation — full model output is visible
- Fix non-verbose fallback: read from ame.delta (correct field) instead
  of ame.text (always undefined for text_delta events)
- Track inTextBlock/inThinkingBlock state to properly close streaming
  blocks before tool calls
- Expand summarizeToolArgs with support for async_bash, await_job,
  cancel_job, find, ls, lsp, hashline_edit, subagent, browser_navigate,
  and gsd_* tools
- Add streaming formatter functions: formatTextStart, formatTextEnd,
  formatThinkingStart, formatThinkingEnd
- Update tests for new tool arg summarization and path field handling
2026-03-27 21:57:11 -06:00
TÂCHES
8d0a81ff89 Merge pull request #2925 from gsd-build/fix/2923-double-startauto-race
fix(auto): guard startAuto() against concurrent invocation
2026-03-27 19:45:38 -06:00
Lex Christopherson
86f97885cc fix(auto): guard startAuto() against concurrent invocation (#2923)
checkAutoStartAfterDiscuss() fire-and-forgets startAuto() when a
milestone is ready. The headless runner then chains `/gsd auto`,
calling startAuto() a second time. Two concurrent auto-loops on the
same AutoSession singleton corrupt shared state (counters, dispatch
maps), causing planning/execution to never run after research.

Add an early `s.active` check at the top of startAuto() so the second
call no-ops. Add source-scanning test to enforce the guard exists.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 19:35:10 -06:00
NilsR0711
79da90edde fix(auto-dispatch): widen operational verification gate regex (fixes #2866) (#2898)
Three defects in the completing-milestone dispatch guard caused false
positive blocks on valid validation output:

1. Single-line constraint: [^\n]* stopped at newlines, missing verdicts
   on subsequent lines. Fixed with [\s\S]{0,500}? (bounded lazy match).

2. Missing keywords: 'satisfied' and 'partially' were absent from the
   alternation. LLMs commonly write 'PARTIALLY SATISFIED' or 'FULLY
   SATISFIED'. Added both.

3. Markdown bold delimiters: **Operational** blocked [\s:] after the
   word. The new [\s\S] class handles any character including *.

Also adds SATISFIED to the structuredMatch includes check, and  to
the prose regex (overlaps with #2862).

Includes 8 regression test cases covering multi-line formats, satisfied
keyword variants, markdown bold tables, and checkmark emoji.
2026-03-27 18:10:10 -06:00
Jordan Gaytan
558b1f2081 fix(parallel): three bugs preventing reliable parallel worker execution (#2801)
Bug 1 — Workers exit immediately (#2792):
spawnWorker() used `--print "/gsd auto"` which calls session.prompt()
that returns immediately when ctx.newSession() resets the session inside
the auto-loop. Changed to `headless --json auto` which uses an RPC
client that keeps the process alive until auto-mode completes.

Bug 2 — Dispatch guard blocks parallel workers (#2797):
getPriorSliceCompletionBlocker() checked ALL milestones in queue order,
blocking M012 when M011 had incomplete slices. When GSD_MILESTONE_LOCK
is set, the guard now only checks intra-milestone slice dependencies.
Added test covering cross-milestone bypass + intra-milestone preservation.

Bug 3 — Orphaned RPC children on stop (#2798):
stopParallel() gave only 750ms for SIGTERM before SIGKILL. The headless
parent needs ~1500ms to cascade shutdown to its RPC child via
client.stop(). Increased to 3000ms to prevent orphaned processes holding
auto.lock.

Updated tests:
- dispatch-guard.test.ts: new test for GSD_MILESTONE_LOCK bypass
- parallel-worker-monitoring.test.ts: updated spawn args assertion
2026-03-27 18:10:02 -06:00
mastertyko
6918fb76c6 fix(gsd): parse raw YAML under preference headings (#2794)
Accept raw YAML blocks beneath markdown preference headings while preserving legacy heading-list parsing.

Closes #2787
2026-03-27 18:09:48 -06:00
mastertyko
c88a9d2d4f fix(gsd): persist verification classes in milestone validation (#2820) 2026-03-27 18:09:39 -06:00
drkthng
6dd02bb3ce fix(gsd): guard reconcileWorktreeDb against same-file ATTACH corruption (#2825)
When worktrees use shared-WAL mode (R012), the worktree DB path resolves
to the same physical file as the project root DB via symlink. Calling
reconcileWorktreeDb() ATTACHes this WAL-mode file to itself, corrupting
the database with 'database disk image is malformed'.

Fix 1 — auto-worktree.ts mergeMilestoneToMain(): skip reconciliation
when isSamePath() confirms both DB paths resolve to the same file.

Fix 2 — gsd-db.ts reconcileWorktreeDb(): defence-in-depth realpathSync
guard inside the function itself, before the ATTACH statement.

Fix 3 — auto/infra-errors.ts: classify 'database disk image is
malformed' as SQLITE_CORRUPT infrastructure error so the auto-loop
stops immediately instead of burning 3 retries on a guaranteed failure.

Regression tests verify:
1. Same-file via symlink returns zero (no ATTACH)
2. Identical string paths return zero
3. Genuinely different DBs still reconcile normally
4. Malformed DB message classified as infra error
5. Transient SQLITE_BUSY is not falsely classified

Closes #2823
2026-03-27 18:08:13 -06:00
TÂCHES
09ee279995 Merge pull request #2918 from gsd-build/fix/2917-execution-complete-auto-mode
fix(headless): skip execution_complete for multi-turn commands (auto/next)
2026-03-27 17:51:26 -06:00
TÂCHES
44dd06fe66 Merge pull request #2868 from jeremymcs/fix/model-override-unittype-mapping
fix(model-routing): use honest unitTypes for discuss dispatches
2026-03-27 17:40:08 -06:00
TÂCHES
8a35b2308f Merge pull request #2887 from mastertyko/fix/subagent-project-agents-dir
fix(gsd): discover project subagents in .gsd
2026-03-27 17:39:36 -06:00
TÂCHES
f9dd134933 Merge pull request #2904 from mastertyko/fix/auto-resume-cold-db-bootstrap
fix(gsd): resume cold auto bootstrap from db
2026-03-27 17:39:07 -06:00