- Fix auto.ts indentation: properly indent inner if-else chain inside summarizing guard
- Clear unitDispatchCount on resume path (paused → active) to prevent stale counts
- Add parseSummary regression tests for #91: bare scalar "none" coerced to string[]
- Add parseSummary test for missing frontmatter fields yielding empty arrays
- Verify .slice().join() works on coerced arrays (the original crash pattern)
Test results: 273 passed, 0 failed (24 new assertions)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
CLI routing (#81, #107):
- Import and route --mode rpc to runRpcMode() instead of silently falling through to runPrintMode
- Add TTY guard before interactive mode — exit with helpful message when stdin is not a TTY
- Add --version and --help flags
Auto-mode infinite loop (#96):
- Move summarizing/complete-slice dispatch before reassessment check (D1) — ensures mergeSliceToMain always runs
- Add per-unit dispatch counter to detect alternating loops like A→B→A→B (D3)
Windows shell escaping (#106, #98):
- Platform-aware escapeShellArg() in mcporter extension — double quotes on Windows, single quotes on Unix
CRASH: parseSummary (#91):
- Add asStringArray() helper to safely coerce YAML bare scalars (e.g. "none") to string arrays
- Applied to all 7 frontmatter fields that expect string[]
Google Search model (#99):
- Replace hardcoded gemini-3-flash-preview with env var GEMINI_SEARCH_MODEL (default: gemini-2.5-flash)
Worktree branch collision (#84):
- Check git worktree list before checkout to detect branches already in use by another worktree
Migration UX (#90, #93):
- Improve error messages to distinguish migration from new project setup, suggest /gsd:new-project
Keyboard shortcuts (#100, #104):
- Document terminal protocol requirement in shortcut descriptions — Ctrl+Alt combos need Kitty/modifyOtherKeys
Closes#81, #84, #91, #96, #99, #106, #107
Addresses #90, #93, #95, #98, #100, #104
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add Tavily Search API as an alternative backend for search-the-web and
search_and_read tools. Tavily is selected automatically when TAVILY_API_KEY
is set (preferred over Brave when both keys present). Existing Brave
Search paths are completely unchanged.
Motivation: Brave Search API signup requires Stripe payment which may
not be available in all regions. Tavily offers a free tier and also
provides a Deep Research API for future expansion.
Changes:
- Auth: Tavily API key in wizard, auth.json storage, env hydration
- search-the-web: Tavily POST backend with response normalization
- search_and_read: Tavily advanced search with client-side token budgeting
- /search-provider: slash command for explicit provider switching
- 61 new tests covering all Tavily integration paths
- Zero changes to existing Brave code paths
On Windows, cmd.exe does not strip single quotes like Unix shells.
This caused MCP tools (mcp_servers, mcp_discover, mcp_call) to fail
with 'Unknown command list' errors because mcporter received
literal 'list' instead of just list.
The fix uses execFile with shell=true on Windows, which properly
passes arguments without the shell interpreting quotes.
Closes#98
Co-authored-by: OpenClaw AI <ai@openclaw.dev>
Line 5's 'When to read this' guidance updated to reflect the actual
mechanism — the file is injected programmatically by /gsd, not read
directly by the agent.
Line 659's context-pressure resume instruction updated from:
'read @GSD-WORKFLOW.md - what\''s next?'
to:
'run /gsd to pick up where you left off, or /gsd auto to resume in
auto-execution mode.'
The read @GSD-WORKFLOW.md instruction was broken — the file is not
accessible via the read tool; it only enters context through
dispatchWorkflow(). Users who followed the old instruction got nothing.
Relates to #38 (same file, different problem).
gemini-3-flash-preview is not available on Vertex AI and has lower
rate limits on the Gemini Developer API. gemini-2.5-flash is the
stable model available on both Vertex AI and Gemini API.
Handle both plain number and { total: number } shapes for msg.usage.cost
in snapshotUnitMetrics, and coerce formatCost input to prevent crashes
when cost is null/undefined/NaN from corrupted ledger data.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
During auto-mode, the built-in footer is hidden entirely via setFooter()
and all its info is moved into the progress widget:
- pwd + git branch shown inside the widget
- Token stats (↑/↓/R/W) from current unit session
- Cumulative cost from metrics ledger (survives across unit resets)
- Context window usage with color coding (warning >70%, error >90%)
- Model name right-aligned
- Footer restored to built-in on pause or stop
- No model duplication (removed from hints)
The built-in write, read, and edit tools capture process.cwd() once at
startup. When /worktree switch calls process.chdir() into a worktree,
these tools still resolve relative paths against the original launch
directory. This caused GSD auto-mode to write .gsd/ artifacts to the
main project instead of the worktree.
The bash tool was already patched with a spawnHook for dynamic CWD.
Apply the same pattern to write, read, and edit: each execute() call
creates a fresh tool instance with the current process.cwd(), so
relative paths always resolve against the active working directory.
Replace the narrow 'if currentUnit === complete-slice' merge check with a
general merge guard that detects any completed slice branch and merges it
to main before dispatching the next unit.
The old check only triggered merges after the complete-slice unit type.
When the LLM or the doctor post-hook completed slice bookkeeping during
task execution, complete-slice was skipped entirely, leaving the slice
branch unmerged. On milestone transition, the next slice branch (forked
from main) couldn't see the prior milestone's summary, causing deriveState
to oscillate between milestones in an infinite loop.
The new guard checks: are we on a gsd/MID/SID branch where the roadmap
entry is [x]? If so, merge to main and re-derive state before dispatching.
Patch SDK setModel() to accept { persist: false } so per-unit model
switching in auto-mode no longer overwrites the user's global default.
Add state rebuild + doctor on resume, guard logging for silent dispatch
failures, and active-state check before prompt injection.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Validate channel IDs via isValidChannelId() before URL interpolation
in setup wizard, preventing SSRF during test-send
- Add 15s fetch timeout to setup API calls (fetchJson, Discord test-send)
- Sanitize Discord error responses before surfacing to user
- Remove dead send.ts + channels.ts (unused parallel implementation)
- Add poll retry tolerance in manager.ts (1 transient error before fail)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
ensureSliceBranch() now auto-commits dirty files before git checkout,
preventing "would be overwritten" errors when doctor/STATE.md rebuild
leaves uncommitted changes between slice dispatches. (closes#63)
On startup, migrate any .jsonl session files from the flat
~/.gsd/sessions/ directory into the per-cwd subdirectory so /resume
can find sessions created before per-directory scoping was added.
(closes#64)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Apple's on-device speech recognition resets bestTranscription after
silence gaps, discarding previous text. The Swift recognizer now
detects these resets (word count drop / different starting word) and
accumulates finalized segments so speech continues appending instead
of overwriting. TS side simplified to pass through the already-
accumulated text from the Swift process.
Provides a `google_search` tool as an alternative to Brave-based web
search for users with Google Cloud / Gemini API credits. Sends queries
to Gemini 3 Flash with `googleSearch: {}` grounding enabled, returning
an AI-synthesized answer with source URLs from grounding metadata.
Features:
- In-session caching (keyed by normalized query)
- Defensive truncation via truncateHead
- Classified error handling (auth, rate-limit, general)
- Custom TUI rendering for call/result display
- Session start warning if GEMINI_API_KEY is missing
- macOS-only (SFSpeechRecognizer), no-op on other platforms
- /voice command and Ctrl+Alt+V shortcut to toggle
- Streams partial transcription results directly into editor input
- Custom footer with flashing red dot + 'transcribing' indicator on row 1
- Enter to stop and keep text, Esc to cancel
- Ships precompiled Swift binary (60KB)
Run doctor (fix mode) and rebuild STATE.md after each unit completes
in handleAgentEnd. Catches missed checkboxes and stub summaries the
LLM may have skipped, and keeps STATE.md in sync with disk state.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- /gsd next: same state machine as /gsd auto but pauses between units
with a wizard showing what completed and what's next
- /gsd (bare): now defaults to step mode instead of the old guided flow
- /gsd auto: unchanged — continuous execution without pausing
- Deleted /gsd-run slash command (redundant with /gsd auto)
- Step mode preserves through discuss → auto-start transition
- User can switch from step → auto mid-session via wizard option
- Progress widget shows NEXT/AUTO based on current mode
The idle watchdog checked lastProgressAt to detect stalled agents, but
nothing updated that timestamp during normal execution. Any task taking
>10min triggered false idle recovery, steering messages, and eventually
got skipped — even while actively writing code.
Add detectWorkingTreeActivity() check before recovery: if git reports
uncommitted changes, the agent is working. Bump lastProgressAt and
skip recovery. Genuinely idle agents (clean working tree) still get
recovered as before.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Merge improvements:
- Auto-detect current worktree: /worktree merge (bare) and /worktree merge main
work from inside a worktree without specifying the worktree name
- Full repo diffs: preview and LLM prompt show all changed files, not just .gsd/
- Accurate preview: direct diff (main vs branch) shows actual merge impact
- Per-file line stats: +N/-N shown for each file in merge preview
- CWD fix: chdir to main tree before dispatching merge to prevent broken CWD
after worktree cleanup
- Prompt includes explicit paths so the LLM knows where to read/write
Create/switch:
- /worktree create <name> works as alias for create-or-switch behavior
- Guard against creating a worktree when the branch is already in use
Remove:
- /worktree remove <name> validates the name exists before attempting removal
- /worktree remove <name> confirms before deleting
- /worktree remove all removes every worktree after confirmation prompt
Reload resilience:
- Detects if CWD is inside a worktree on extension init and restores
originalCwd tracking, surviving /reload without losing worktree state
Command descriptions:
- /worktree shows '(also /wt)' in description
- /wt shows 'Alias for /worktree'
Replace the inline union cast in renderResult with a proper
discriminated union (LocalResultDetails | RemoteResultDetails)
keyed on the `remote` field. Improves type safety and makes
the rendering logic self-documenting.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The catch-all in checkReactions() silently swallowed auth failures
(401/403), making them indistinguishable from "no reaction yet". Now:
- 404: expected (no reactions for this emoji), continue
- 401/403: re-thrown so the poll loop surfaces the auth failure
- Other errors: best-effort skip (rate limits, network)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Error messages from adapter auth/send failures may contain token
fragments. Added sanitizeError() that strips Slack token patterns
(xoxb-, xoxp-, xoxa-) and long opaque secrets (20+ alphanumeric
chars). Also truncates verbose Discord API error responses to 200
chars. Applied to all error paths in manager.ts and discord-adapter.ts.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Individual HTTP calls to Slack/Discord APIs could hang indefinitely
if the network stalls. The overall poll deadline only bounds the loop,
not each request. Now each fetch() gets AbortSignal.timeout(15_000).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Arbitrary-length free-text replies from remote channels were passed
directly into the LLM context. Now truncated to 500 chars with
trailing ellipsis.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Slack IDs must match ^[A-Z0-9]{9,12}$, Discord snowflakes must match
^\d{17,20}$. resolveRemoteConfig() and getRemoteConfigStatus() now
reject malformed IDs before they reach any URL interpolation.
Also fixes pre-existing false-positive in config tests (env overrides
couldn't affect module-level homedir() cache) — replaced with direct
isValidChannelId() unit tests.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The Anthropic API rejects images exceeding 2000px in multi-image requests.
With deviceScaleFactor=2, viewport screenshots were 2560x1600px, triggering
400 errors that halted execution. Add scale:"css" to all API-facing screenshot
calls and a constrainScreenshot() fallback that downscales oversized images
(e.g. fullPage on tall pages) via the browser's canvas — zero new dependencies.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
resolveRemoteConfig test used process.env.HOME which is undefined on
Windows (Node uses USERPROFILE). Use a temp directory with both HOME
and USERPROFILE set, and clean up in a finally block.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
getLatestPromptSummary() sorted JSON filenames alphabetically to find
the most recent prompt. Since filenames are UUIDs (random, not temporal),
this returned arbitrary results. Now reads updatedAt from each record
and picks the highest.
Also fixes test isolation on Windows (USERPROFILE) and adds a regression
test that fails with the old alphabetical sort.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Child processes (Git Bash/MSYS2) strip the ENABLE_VIRTUAL_TERMINAL_INPUT
flag from the shared stdin console handle, corrupting terminal input.
Re-enable the flag after every child process exits in bash.js, bg-shell,
and cache FFI handles in pi-tui for cheap repeated calls.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Node's --experimental-strip-types doesn't support parameter properties
(private readonly in constructor params). Convert to explicit field
declarations + constructor assignments.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The custom input handlers called tui.invalidate() which caused infinite
recursion. Rewrite promptMaskedInput and promptInput to use the Editor
component (same pattern as get-secrets-from-user.ts) with proper
tui.requestRender() and cache invalidation.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Pi's DefaultResourceLoader auto-discovers extensions/*/index.ts and
expects a default export factory. remote-questions is an internal
library (consumed via dynamic import), not an extension — having an
index.ts caused the loader to try loading it as one and fail.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When ask_user_questions is called in non-interactive mode (ctx.hasUI = false),
transparently route questions to a configured Slack or Discord channel and poll
for the user's response. Same tool interface, automatic routing.
- Add adapter pattern for Slack (Bot Token API) and Discord (HTTP API)
- Add /gsd remote command for interactive setup wizard
- Add SLACK_BOT_TOKEN / DISCORD_BOT_TOKEN to wizard and env hydration
- Add remote_questions config to GSD preferences with merge support
- Fix parseScalar to preserve large numeric IDs (Discord channel IDs)
- Show remote channel status on session_start
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- bg_shell killProcess: add Windows-specific taskkill /F /T /PID path
with proper error handling (spawnSync with timeout, not stdio: "ignore")
- bg_shell startProcess: use getShellConfig() instead of hardcoded "bash",
disable detached mode on Windows (process groups don't apply)
- GSD bash tool: wrap execute to inject 120s default timeout when the LLM
omits the timeout parameter, preventing indefinite hangs
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>