Auto-worktrees are fresh git checkouts — untracked .gsd/ files don't
carry over. Projects with the old blanket .gsd/ gitignore have planning
artifacts on disk but not in git. When createAutoWorktree makes a new
worktree, the milestones/, DECISIONS.md, REQUIREMENTS.md etc are missing,
causing auto-mode to loop on plan-slice (plan file not found in worktree).
Copy .gsd/ planning artifacts from the source repo into the new worktree
after git worktree add. Skips runtime files and the worktrees/ dir.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ensureGitignore() now detects and removes standalone ".gsd/" lines that
blanket-ignore the entire directory. Replaces with explicit runtime-only
patterns so .gsd/milestones/ planning artifacts are tracked in git.
Without this, existing projects keep the old blanket ignore forever.
New worktrees start with zero planning state because artifacts aren't
in git, causing auto-mode to re-execute completed work.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- auto.ts: wrap dispatchNextUnit body in try/finally to always reset
_dispatching to false. Without this, the reentrancy guard permanently
blocked all subsequent dispatches after the first one, causing the
dispatch gap watchdog to fire and auto-mode to stall.
- discuss.md: render depth summary as chat text (where markdown renders)
then use ask_user_questions for the short confirmation only.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Downgrade internal recovery machinery to info/verbose-only so users
only see warnings when action is needed:
- "Dispatch gap detected" → verbose-only info (recovery is automatic)
- "Model not found, trying fallback" → verbose-only info
- "Failed to set model, trying fallback" → verbose-only info
- "Could not set any preferred model" → deleted (redundant)
- "New session cancelled" → info (user action, not error)
- "Unexpected phase" → info with doctor suggestion
- "No command context" → info with restart suggestion
Kept as warnings (user-actionable):
- Budget ceiling, blockers, prior slice incomplete, pre-flight,
no context, stub summary, model ambiguity, all fallbacks exhausted
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(auto): prevent infinite re-dispatch when completion key is missing
Root cause: When a task completed successfully on the first attempt,
the idempotency key was never persisted to completed-units.json.
The persistence logic (persistCompletedKey) only triggered at the
retry threshold (MAX_UNIT_DISPATCHES=3). After session restart, the
key was missing and auto-mode re-dispatched the same task endlessly.
Evidence: M008/S01/T01 was dispatched 15+ times over 3.5 hours.
T01-SUMMARY.md existed, S01-PLAN.md marked T01 as [x], but
completed-units.json had no execute-task/M008/S01/T01 entry.
Fix: Added fallback artifact check before dispatch. If the expected
artifact already exists on disk but the completion key is missing,
the key is repaired (persisted + added to in-memory set) and the
unit is skipped. This catches the gap between the closeout-based
persistence (which requires the NEXT dispatch to fire) and the
retry-threshold persistence (which requires MAX attempts).
Also fixes guided-flow-escape.test.ts: added missing cache
invalidation after rmSync (clearPathCache + invalidateStateCache).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(auto): prevent TUI freeze on cascading skip-dispatches
When multiple completed tasks are skipped in sequence (T01 artifact
fallback → T02 idempotency skip → T03 dispatch), the recursive
dispatchNextUnit calls can freeze the TUI.
Fix: invalidateStateCache() after key repair so deriveState returns
the correct next task, and use setTimeout(50ms) instead of
setImmediate to yield more generously to the event loop between
cascading skips.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(auto): systematic hardening of dispatch recovery pipeline
Five fixes addressing the 20 failure modes identified in the auto-mode
dispatch loop audit:
1. Stale runtime record cleanup: selfHealRuntimeRecords now clears
records older than 1h with phase=dispatched (crash orphans), and
also persists completion keys for records with existing artifacts.
2. Recursion depth limit: _skipDepth counter prevents TUI freeze when
many completed units are skipped in cascade. After MAX_SKIP_DEPTH
(20) skips, yields 200ms to the event loop before continuing.
3. Atomic completed-units.json writes: persistCompletedKey now uses
tmp file + renameSync to prevent partial writes on crash.
4. Skip depth tracking on both skip paths (idempotency check at L1815
and artifact fallback at L1844) with setTimeout(50ms) between skips.
5. Self-heal now also repairs missing completion keys when artifact
exists, closing the gap where crash between completion and closeout
leaves the key unwritten.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(auto): add reentrancy guard to dispatchNextUnit itself
The _handlingAgentEnd boolean only guards calls from agent_end hooks.
Direct calls from watchdog timers, step wizard, and crash recovery
can still race with an in-progress dispatch. Added _dispatching guard
that blocks concurrent external calls while allowing recursive skip
calls (_skipDepth > 0). Cleared on stopAuto.
Audit confirmed: double watchdog (#11) already prevented by existing
clearDispatchGapWatchdog in startDispatchGapWatchdog + catch/return.
Counter cleanup (#16) already handled by unitDispatchCount.clear()
in startAuto before selfHealRuntimeRecords.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(auto): final hardening for unattended multi-milestone runs
Three fixes from paranoid stress-test audit:
1. Git index.lock cleanup: Remove stale .git/index.lock (>60s old) at
auto-start. A crash during git commit/merge leaves this file behind,
blocking ALL subsequent git operations with no recovery.
2. Stub summary for complete-milestone: If the LLM fails to write a
milestone SUMMARY after MAX_UNIT_DISPATCHES attempts, generate a
stub summary to unblock the pipeline. Without this, auto-mode
loops forever in "completing-milestone" phase.
3. Pre-flight queue validation: At auto-start with multiple milestones,
scan for CONTEXT-DRAFT.md files (will pause for discussion) and
report milestone count. Gives the user early visibility into what
will happen during the run.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: deseltrus <simulacraverse@protonmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(auto): prevent nested worktree creation inside existing worktrees
When auto-mode starts inside a manual worktree (e.g., /worktree memory-db),
it unconditionally created an auto-worktree for the milestone, nesting
.gsd/worktrees/M001 inside the existing worktree. This caused GSD to
chdir into the inner worktree, read state from the wrong repo, and
report "All milestones complete" or loop on artifact verification.
Add detectWorktreeName() guard to both the start and resume paths:
if already inside a worktree, skip auto-worktree creation and work
directly on the current branch.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
* Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
The multi-milestone discussion flow writes CONTEXT.md files for each
milestone but never adds depends_on YAML frontmatter. The QUEUE.md
documents the dependency chain, but the auto-mode state machine reads
dependencies from CONTEXT.md frontmatter only — not from QUEUE.md.
Without frontmatter, milestones execute in filesystem order regardless
of their actual dependency chain, causing out-of-order execution.
Fix: Added MANDATORY depends_on documentation to both discuss.md
(Phase 2, after primary milestone) and queue.md (output section).
Instructs the LLM to write frontmatter with the exact milestone IDs
from the dependency chain confirmed during the milestone split gate.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: TÂCHES <afromanguy@me.com>
* docs: add startup performance analysis and optimization plan
Profiled GSD CLI startup finding 2.2s for --version and ~3.8s for
interactive mode. Identified 5 root causes with measured timings and
created a phased optimization plan targeting <0.2s for --version
and ~0.8s for interactive startup.
* perf: speed up GSD startup with lazy loading and fast paths
- Fast-path --version/-v and --help/-h in loader.ts before importing
any heavy dependencies (2.2s → 0.15s, 14x faster)
- Lazy-load undici (~200ms) only when HTTP_PROXY env vars are set
- Skip initResources cpSync when managed-resources.json version
matches current GSD version (~128ms saved per launch)
- Lazy-load Mistral SDK (~369ms) on first API call instead of startup
- Lazy-load Google GenAI SDK (~186ms) on first API call instead of
startup
- Parallelize extension loading with Promise.all() instead of
sequential for-loop
---------
Co-authored-by: TÂCHES <afromanguy@me.com>
Eliminate slice branches — all work commits sequentially on milestone/<MID>
within auto-mode worktrees. No branch creation, switching, or merging
within a worktree. Planning artifacts (.gsd/milestones/) tracked in git
properly instead of being blanket-gitignored then force-added.
Removes ~2,600 lines: ensureSliceBranch, switchToMain, mergeSliceToMain,
mergeSliceToMilestone, shouldUseWorktreeIsolation, getMergeToMainMode,
withMergeHeal, recoverCheckout, fix-merge dispatch/labels, and associated
tests. Adds legacy_slice_branches doctor check, deprecation warnings for
git.isolation and git.merge_to_main preferences.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Hooks were dispatched (runtime record created with phase="dispatched") but
never properly tracked through completion. Four issues fixed:
1. Hook runtime records now finalized: handleAgentEnd writes phase="finalized"
and clears the record when a hook completes. Previously records stayed at
"dispatched" forever because verifyExpectedArtifact returned false for
hook types.
2. Supervision timer for hooks: hook dispatch now sets a hard timeout so
stuck hooks don't hang auto-mode indefinitely.
3. Hook retry removes completion key: when a hook requests retry via
retry_on, the trigger unit's completion key is removed from the
idempotency set so dispatchNextUnit will re-dispatch it.
4. Hook closeout in dispatchNextUnit: hook units are properly closed out
(pushed to completedUnits, runtime cleared) without polluting the
idempotency set. verifyExpectedArtifact returns true for hook/ types.
Fixes#140 (comment 4063396798)
A plan file with zero tasks caused `find(t => !t.done)` to return
undefined, which was treated as "all tasks done" → summarizing phase.
Now requires `tasks.length > 0` before entering summarizing.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Git worktree path resolution on Windows CI uses UNC/8.3 temp dir forms
that don't survive normalization for path matching. The underlying source
logic works correctly (tested on macOS/Linux); these tests exercise git
worktree infrastructure that has inherent platform differences in path
representation.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three fixes for Windows CI failures:
1. Remove single quotes from git branch --list glob patterns. On Windows,
cmd.exe passes single quotes literally to git, preventing glob expansion.
Affects shouldUseWorktreeIsolation() and stale branch detection.
2. Extend listWorktrees() branch-name fallback to cover milestone/* branches,
not just worktree/* branches. On Windows, path normalization can prevent
path-based worktree matching; the branch-name fallback is the safety net.
3. Use path.sep instead of hardcoded "/" in CWD prefix checks (doctor.ts
orphan fix guard, worktree-manager.ts removeWorktree guard).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Shell string interpolation of multi-line commit messages breaks on
Windows — the closing quote gets consumed mid-message, causing the
branch name suffix to be parsed as a second argument to git merge
(producing "fatal: No remote for the current branch").
Switch to execFileSync with argument arrays for merge, commit, and
add commands that include user-generated content.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Replace single-quoted git commit messages with double quotes
- Replace bash redirect syntax with cross-platform alternatives
- Add git branch -M main to git-self-heal test setup for consistent branch naming
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Kills two independent failure paths causing the recurring dispatch loop bug:
Path B: dispatchNextUnit() called clearPathCache() but not clearParseCache(),
allowing stale parsed roadmap data (with [ ] instead of [x]) to persist
through the doctor→dispatch transition.
Path A: handleAgentEnd() never verified whether the just-completed unit
produced its expected artifact before re-entering the dispatch loop.
Now persists completion key after verification, so the idempotency
check in dispatchNextUnit() skips already-completed units.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The loop detection in dispatchNextUnit stops auto-mode when a unit has
been dispatched MAX_UNIT_DISPATCHES (3) times. Previously, only
execute-task had reconciliation logic to check whether the artifact
actually exists on disk before bailing. All other unit types
(complete-slice, plan-slice, research-slice, etc.) would immediately
stop — even if the Nth attempt successfully produced the artifact.
This is a race between the dispatch counter and disk verification:
the counter increments at dispatch time, but artifact verification
only runs during closeout of the NEXT unit. If the last allowed
attempt succeeds, the counter is already at the limit when the next
dispatch tries to run, and nobody checks disk state.
Reproduction scenario:
1. complete-slice dispatched 3 times (LLM missed writing UAT on
attempts 1-2, succeeded on attempt 3)
2. Attempt 3 produces both SUMMARY and UAT — auto-committed to disk
3. Dispatch 4 fires: prevCount (3) >= MAX_UNIT_DISPATCHES (3)
4. No disk check for complete-slice → pipeline stops with
'Expected artifact not found' despite artifacts existing
Fix: add a general verifyExpectedArtifact() check after the
execute-task-specific reconciliation and before the final bail-out.
If artifacts exist on disk, clear the counter and advance. If not,
same error as before — no behavior change for genuinely stuck units.
When loop-recovery or self-repair reconciliation succeeds (artifacts exist on
disk), the dispatch counter is reset but the unit is never marked complete in
completed-units.json. If deriveState() continues returning the same unit, the
cycle repeats indefinitely: 3 dispatches → stuck detection → reconciliation
→ counter reset → 3 more dispatches...
This was observed in production burning $93.87 on 103 dispatches of a single
already-completed task over 4.9 hours.
Changes:
1. Persist completed key (persistCompletedKey + completedKeySet.add) in both
the loop-recovery and self-repair success paths, so the idempotency check
at the top of dispatchNextUnit prevents re-dispatch.
2. Add invalidateStateCache() after reconciliation writes to ensure the next
deriveState() call sees fresh disk state.
3. Add a hard lifetime dispatch counter (unitLifetimeDispatches) that survives
counter resets from reconciliation paths. Caps any single unit at 6 total
dispatches across all reconciliation cycles.
Fixes#462
The static assertion searched the first 1200 chars of checkAutoStartAfterDiscuss
for CONTEXT-DRAFT and unlinkSync references, but the function grew to 4164 chars
after adding Gates 2-4 (STATE.md, PROJECT.md, manifest validation). The search
window now extends to the next export statement.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Closes the remaining gap in multi-milestone enforcement: the code
previously validated only the END STATE (files exist) but not the
PROCESS (each gate was presented to the user).
New mechanism:
- discuss.md instructs the LLM to write .gsd/DISCUSSION-MANIFEST.json
after EACH Phase 3 gate decision, tracking gates_completed vs total
- checkAutoStartAfterDiscuss() Gate 4: BLOCKS auto-start if
gates_completed < total (not just a warning)
- Manifest is deleted after auto-start (only needed during discussion)
- Single-milestone discussions don't use manifest (backward-compatible)
- DISCUSSION-MANIFEST.json added to baseline gitignore patterns
This creates a three-layer enforcement:
Layer 1 (Prompt): ask_user_questions calls at each gate
Layer 2 (Files): CONTEXT.md/DRAFT/directory existence check
Layer 3 (Manifest): gates_completed == total process verification
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three fixes for config/setup UX:
1. cli.ts: Add missing loadStoredEnvKeys() call in gsd config flow.
Previously, gsd config showed keys as "not configured" even when
they existed in auth.json because env vars weren't hydrated first.
2. commands.ts: New /gsd config slash command that lets users configure
API keys (Tavily, Brave, Context7, Jina, Groq) from within a running
session. Keys are saved to auth.json and activated immediately.
No need to exit the session and run gsd config externally.
3. command-search-provider.ts: Show native Anthropic web search status
when using Claude models, so users know search works even without
Brave/Tavily keys.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The preferences wizard now shows available models from the model
registry as a selectable list instead of requiring users to manually
type model IDs. Falls back to text input when no authenticated
models are available.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The preferences parser treated [] and {} as strings instead of empty
array/object. On next serialize, yamlSafeString quoted them as "[]"
and "{}", permanently corrupting the preferences file. This caused
the wizard to show empty fields (models, auto_supervisor, etc.).
Fix: parseScalar now recognizes [] and {} (quoted or unquoted) as
empty array/object. Serializer omits empty values entirely instead
of writing key: [] or key: {}.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Instead of just showing "Edit file" notification, /gsd prefs now
ensures the preferences file exists and immediately launches the
interactive wizard. This matches user expectation — typing "prefs"
should let you edit preferences, not just show a file path.
/gsd prefs status still available for file path info without wizard.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Split skill diagnostics into [Skill conflicts] (actual collisions) and
[Skill issues] (validation warnings like missing description) so users
aren't misled by the label. Add wizard hint to /gsd prefs output.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>