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)
Fix parsing issues that prevented OpenRouter model preferences from
being correctly picked up during auto-mode dispatching:
- Array items with colons (e.g. qwen/qwen3-coder:free) were incorrectly
parsed as objects instead of strings. Now only items matching a valid
key-value pattern (key:value where key is [A-Za-z0-9_]+) are treated
as structured objects.
- Inline YAML comments (# after whitespace) were included in parsed
values, causing model ID lookups to fail silently.
- Frontmatter regex now handles Windows CRLF line endings.
- GSDPreferences.models type updated from GSDModelConfig (legacy
string-only) to GSDModelConfig | GSDModelConfigV2 to match actual
runtime usage with extended object format.
- Explicit comment-line skipping in the parser loop for clarity.
- Added comprehensive test suite covering OpenRouter-style org/model IDs,
colon variants, inline comments, CRLF, and mixed format configs.
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 Input component had no placeholder text support — when empty, it
showed only "> " with a blinking cursor and no hint of expected input.
The ExtensionInputComponent received a placeholder parameter but
discarded it (_placeholder with underscore = intentionally unused).
Fix: Input now has a public placeholder property. When value is empty,
renders the placeholder in dim text. ExtensionInputComponent passes
the placeholder through to Input.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>