Commit graph

2449 commits

Author SHA1 Message Date
deseltrus
b7e0173e50 fix: route non-builtin slash commands after TUI dispatch
The TUI slash dispatcher started treating any unrecognized /command as handled before session.prompt() could resolve extension commands, prompt templates, or /skill:* inputs. That blocked valid non-builtin slash commands and also let /export swallow unrelated /export* prefixes.

Move unknown-command detection to the interactive entry points, allow only known builtins or session-resolved slash commands through, gate /skill:* on the skill-command setting, and tighten /export matching to exact command tokens.
2026-04-03 06:44:09 +02:00
Jeremy McSpadden
db4fa32854 Merge pull request #3426 from justinwyer/fix/configurable-security-overrides
fix(security): add configurable overrides for command allowlist and SSRF blocklist
2026-04-02 12:29:57 -05:00
Justin Wyer
95875c41c5 refactor(test): consolidate regression and override tests into #666 test files
Move regression tests and override tests from standalone files into
the existing test files introduced by PR #666:

- resolve-config-value.test.ts: add REGRESSION #666 describe block
  and setAllowedCommandPrefixes override tests
- url-utils.test.ts: add REGRESSION #666 describe block and
  setFetchAllowedUrls override tests
- Delete: regression-666.test.ts, resolve-config-value-override.test.ts,
  url-utils-override.test.ts

Same 59 tests, fewer files, tests live next to the code they test.
2026-04-02 14:06:19 +02:00
Justin Wyer
d5f581fe6b test: add regression tests for #666 (fails on main, passes on fix)
Two regression tests that prove the bug introduced by PR #666:

1. Non-default credential tool (sops) is silently blocked by the
   hardcoded SAFE_COMMAND_PREFIXES with no way to override.
2. Private IP URL is silently blocked by isBlockedUrl() with no
   way to allowlist.

Both tests use dynamic import to check for the override functions,
so they run cleanly on both main (where they fail) and this branch
(where they pass). Verified in a git worktree of main.
2026-04-02 14:03:34 +02:00
Justin Wyer
5ae78d8f8b docs: document command allowlist and fetch_page URL blocking
- custom-models.md: add Command Allowlist section under Value Resolution
  explaining the restriction, default list, and how to override via
  allowedCommandPrefixes setting or GSD_ALLOWED_COMMAND_PREFIXES env var

- configuration.md: add URL Blocking (fetch_page) section documenting
  what's blocked by default, why, and how to allowlist specific hosts
  via fetchAllowedUrls setting or GSD_FETCH_ALLOWED_URLS env var

- configuration.md: add both env vars to the Environment Variables table
2026-04-02 13:55:07 +02:00
Justin Wyer
71caa18552 fix(security): add configurable overrides for command allowlist and SSRF blocklist
PR #666 introduced hardcoded SAFE_COMMAND_PREFIXES and SSRF URL
blocklists with no override mechanism. Users with non-standard
credential tools (sops, doppler, age, infisical) or needing to fetch
from internal URLs (self-hosted docs, VPN services) were silently
blocked with no recourse.

Add two global-only settings (ignored in project-level settings.json
to preserve the security property against malicious repos):

- allowedCommandPrefixes: replaces the built-in command allowlist
- fetchAllowedUrls: exempts hostnames from SSRF blocking

Both also support env var overrides (GSD_ALLOWED_COMMAND_PREFIXES,
GSD_FETCH_ALLOWED_URLS) for CI/container environments. Env vars
take precedence over settings.json.

Security model: global-only keys are stripped from project settings
at load time via stripGlobalOnlyKeys(), applied at all three
assignment points for this.projectSettings. The merge function
stays untouched — no future caller can accidentally skip stripping.

15 new tests covering override behavior, cache invalidation,
allowlist exemptions, and global-only enforcement.
2026-04-02 13:45:05 +02:00
Jeremy McSpadden
46d5fa56af Merge pull request #2312 from jeremymcs/fix/tui-review
fix(tui): comprehensive TUI review — layout, flow, rendering, and state fixes
2026-04-01 16:38:31 -05:00
Jeremy
a3a2f2e3b3 test(tui): update provider-manager tests for confirmation-based removal
Tests now match the new hasAuth guard and double-press r confirmation
flow introduced in the TUI review PR.
2026-04-01 16:24:14 -05:00
Jeremy McSpadden
d0555857c2 Merge pull request #2976 from jeremymcs/splash-header-updates-clean
feat(splash): add remote channel indicator to tools row
2026-04-01 16:14:23 -05:00
Jeremy McSpadden
b2abff3ce5 Merge pull request #3138 from jeremymcs/claude/add-stale-commit-check-GIbgw
feat(doctor): stale commit safety check with gsd snapshot and auto-cleanup
2026-04-01 14:22:21 -05:00
Jeremy McSpadden
03bb723dfb Merge pull request #3204 from jeremymcs/fix/stream-re-catch-all-json-parse
fix(error-classifier): catch-all V8 JSON.parse pattern replaces STREAM_RE whack-a-mole
2026-04-01 14:22:07 -05:00
Jeremy
f7cb3ec07b chore(merge): resolve conflict with upstream/main for PR #3204
Keep catch-all STREAM_RE from PR; upstream's 5-variant whack-a-mole is
superseded by the /in JSON at position \d+/ pattern. Also drop the now-
stale comment about checking stream before server/connection (no longer
needed since catch-all avoids those false-positive overlaps).
2026-04-01 14:05:28 -05:00
Jeremy
d929e9ceed chore(merge): resolve conflicts with upstream/main for PR #3138
- auto-worktree.ts: take upstream's MERGE_HEAD cleanup wording/order
- state.ts: take upstream's inline disk→DB reconciliation (#2631)
  over the simpler "always call deriveStateFromDb" approach
2026-04-01 14:04:16 -05:00
Tom Boucher
6e7721406e Merge pull request #2680 from jeremymcs/refactor/vscode-extension-design
feat(vscode): sidebar redesign, SCM provider, checkpoints, diagnostics [3/3]
2026-04-01 11:51:36 -04:00
Tom Boucher
0415f41eee Merge pull request #3116 from jeremymcs/refactor/planning-tier-heavy
refactor(complexity): reclassify planning phases from standard to heavy tier
2026-04-01 11:49:11 -04:00
Tom Boucher
adaa661a87 Merge pull request #3401 from gsd-build/claude/resolve-pr-3322-conflict-GQXIc
fix(worktree): resolve merge conflict for PR #3322 — adopt comprehensive pre-merge cleanup
2026-04-01 11:31:31 -04:00
Claude
1edf172463 test(worktree): add regression test for SQUASH_MSG/MERGE_MSG pre-merge cleanup (#2912)
Satisfies CI require-tests gate by adding a test that verifies the
comprehensive pre-merge cleanup (step 7b) removes stale SQUASH_MSG and
MERGE_MSG files — the enhancement over the prior MERGE_HEAD-only cleanup.

https://claude.ai/code/session_01SSHD9RNwVGNxAJZEgNZpgZ
2026-04-01 15:19:18 +00:00
Claude
e5b6a6a1b9 fix(worktree): resolve merge conflict for PR #3322 — adopt comprehensive pre-merge cleanup
Main already had a simpler step 7c (removing only MERGE_HEAD). The PR's
step 7b is more thorough: it also removes SQUASH_MSG and MERGE_MSG,
matching the existing post-merge cleanup pattern. Replace 7c with 7b.

https://claude.ai/code/session_01SSHD9RNwVGNxAJZEgNZpgZ
2026-04-01 15:11:12 +00:00
Tom Boucher
77220d1dde Merge pull request #2283 from jeremymcs/feat/codebase-map
feat(gsd): codebase map — structural orientation for fresh agent contexts
2026-04-01 10:49:01 -04:00
Jeremy McSpadden
04ebe3f0a0 feat(extensions): add Ollama extension for first-class local LLM support (#3371)
Self-contained extension at src/resources/extensions/ollama/ that
auto-detects a running Ollama instance, discovers locally pulled models,
and registers them as a first-class provider with zero configuration.

Features:
- Auto-discovery of local models via /api/tags on session_start
- Capability detection (vision, reasoning, context window) for 40+ model families
- /ollama slash command with status, list, pull, remove, ps subcommands
- ollama_manage LLM-callable tool for agent-driven model operations
- Onboarding flow with auto-detect (no API key required)
- Non-blocking async probe — doesn't delay TUI paint
- Respects OLLAMA_HOST env var for non-default endpoints

Core changes (minimal):
- Add "ollama" to KnownProvider in pi-ai types
- Add "ollama" key resolution in env-api-keys.ts
- Add "ollama" default model in model-resolver.ts
- Add "Ollama (Local)" to onboarding wizard with probe flow
2026-04-01 08:37:31 -06:00
Jeremy
2cc01c11ee fix(merge): clean stale MERGE_HEAD before squash merge (#2912)
A pre-existing MERGE_HEAD (from failed prior merge, libgit2 native path,
or external tooling) blocks git merge --squash. Remove stale merge state
files before starting the squash merge, not just after.
2026-03-31 17:48:45 -05:00
Jeremy
0e978d4565 fix(state): always run disk→DB reconciliation when DB is available (#2631)
When DB was available but empty, deriveState skipped deriveStateFromDb
entirely, bypassing the disk→DB sync logic. Milestones created outside
the DB write path were never discovered.
2026-03-31 17:34:05 -05:00
Jeremy
36b03890da fix(git-service): fix merge-base ancestry check and .gsd/ leakage in snapshot absorption
- Check HEAD~1 (newest snapshot) instead of resetTarget (pre-snapshot
  base) for remote ancestry. The old check false-positived when the
  remote was at the pre-snapshot base but snapshots were local-only.
- Re-run smartStage() after soft reset so RUNTIME_EXCLUSION_PATHS
  apply to the absorbed commit. Without this, .gsd/ state files from
  snapshot commits leaked into the real commit.
2026-03-31 17:25:29 -05:00
Jeremy
fa0651bfd6 feat(doctor): stale commit safety check with gsd snapshot and auto-cleanup
Adds a safety mechanism that detects uncommitted changes idle past a
configurable threshold (default: 30 min), auto-snapshots tracked files
using `git add -u`, and cleans up snapshot commits when real work lands.

- New `stale_uncommitted_changes` doctor issue with auto-snapshot fix
- Detection in health widget (60s), pre-dispatch gate, and /gsd doctor
- `nativeAddTracked()` stages only tracked files (no secrets/binaries)
- `absorbSnapshotCommits()` squashes `gsd snapshot:` commits into next
  real autoCommit via soft reset + re-commit
- Configurable via `stale_commit_threshold_minutes` preference (0=off)
2026-03-31 17:25:29 -05:00
Jeremy McSpadden
e0d130e682 feat(extensions): wire up topological sort and unified registry filtering (#3152)
- Add extension-manifest.ts and extension-sort.ts to pi-coding-agent
  with manifest reading and Kahn's BFS topological sort algorithm
- Add extensionPathsTransform hook to DefaultResourceLoader that runs
  between path merging and loadExtensions() — enables pre-load
  filtering and reordering without modifying pi internals
- Wire GSD's buildResourceLoader() to provide a transform that:
  1. Filters ALL extensions (including community) through the GSD registry
  2. Sorts in topological dependency order via sortExtensionPaths()
- Mark discoverAndLoadExtensions() as @deprecated (dead code path)
- Add 16 tests covering manifest reading, dependency sorting, cycles,
  missing deps, and non-array deps

Previously, dependencies.extensions in manifests was decorative (sort
existed but was never called), and gsd extensions disable only worked
for bundled extensions. Community extensions in ~/.gsd/agent/extensions/
bypassed the registry entirely.
2026-03-31 11:54:48 -06:00
Jeremy McSpadden
f0059a5498 fix(extensions): update provides.hooks in 7 extension manifests to match actual registrations (#3157)
Audit found that 7 bundled extensions had incomplete provides.hooks
arrays in their manifests. Updated each to match actual pi.on() calls:

- async-jobs: +session_before_switch, session_shutdown
- bg-shell: +8 hooks (session_compact, session_tree, etc.)
- browser-tools: +session_start
- context7: +session_shutdown
- google-search: +session_shutdown
- gsd: +12 hooks (bash_transform, tool_call, tool_result, etc.)
- search-the-web: +session_start

Closes #3156
2026-03-31 11:54:41 -06:00
Jeremy McSpadden
1e89090136 test(state-machine): add regression suite — 86 tests across 6 files (#3161) (#3162)
Comprehensive validation of the GSD state machine identified 7 HIGH, 14 MEDIUM,
and 16 LOW findings. This adds regression and integration tests covering:

Unit tests (49):
- Event replay idempotency (M4 lossy blocker replay, M5 duplicate evidence)
- Reconciliation edge cases (fork detection, entity keys, conflict detection)
- Completion hierarchy guards (vacuous truth, phantom parents, rollback fidelity)
- State derivation parity (ghost milestones, phase transitions, DB/FS consistency)
- Stuck detection coverage (all 3 rules + documented gap for 3-unit cycles)

Integration tests (37):
- Full happy-path lifecycle (pre-planning → complete)
- 12 completion guard edge cases with real handlers
- 7 reopen operations including H5 (no reopen-milestone exists)
- Phantom parent auto-creation (H6)
- State derivation consistency with live DB
- Event log integrity across operations
- M12: stale SUMMARY.md causes reconciler to override reopen

Closes #3161
2026-03-31 11:54:30 -06:00
Jeremy McSpadden
fbb67f15f8 feat(widget): add last commit display and dashboard layout improvements (#3226)
- Health widget: always-on last commit with relative time + message
- Dashboard: move worktree/branch info to right-aligned line under header
- Dashboard: move last commit to bottom-left with hints on right
- Dashboard: cap task titles at 45 chars, commit messages at 65 chars
- Dashboard: use … instead of ... for all truncation
2026-03-31 11:49:35 -06:00
Jeremy McSpadden
eaccf3e690 test(state): comprehensive state machine phase walkthrough (#3276) (#3277)
70 tests covering all 16 phases of the GSD state machine with both
happy-path and failure-mode verification. Exercises DB and filesystem
derivation paths, reconciliation logic, and edge cases.

Findings documented in #3276: 0-byte SUMMARY triggers false completion,
DB task rows missing causes wrong phase, stale path cache across
derivations, non-standard status strings silently accepted.
2026-03-31 11:49:28 -06:00
Jeremy McSpadden
706a2f8e9f refactor(state): centralize pipeline logging through workflow logger (#3282)
* refactor(state): centralize pipeline logging through workflow logger

Route 15 raw process.stderr.write calls through the structured
workflow logger (logWarning/logError). Adds "db" and "dispatch"
as new LogComponent values. Enables auto-loop drain/summarize,
audit-log persistence, and doctor integration for reconciliation
and DB events that previously bypassed structured logging.

Files changed:
- workflow-logger.ts: add "db" and "dispatch" components
- state.ts: 3 reconciliation calls → logWarning/logError
- gsd-db.ts: 4 DB operation calls → logError
- workflow-reconcile.ts: 3 event merge calls → logWarning/logError
- auto-dispatch.ts: 1 reactive dispatch call → logError
- auto-post-unit.ts: 3 triage/rogue calls → logWarning/logError

* test(workflow-logger): add tests for db and dispatch log components

Cover the new LogComponent values added in this refactor to satisfy
the CI require-tests gate.
2026-03-31 11:49:19 -06:00
Jeremy McSpadden
17471ea280 feat(model-routing): enable dynamic routing by default (#3120)
* feat(model-routing): enable dynamic routing by default

Change defaultRoutingConfig().enabled from false to true so that
dynamic model routing (tier-based downgrading for light/standard
tasks) is active out of the box. Users can still disable it via
dynamic_routing.enabled: false in PREFERENCES.md.

This is a behavioral change: sessions that previously used the
configured model for all tasks will now automatically downgrade
to cheaper models for light and standard complexity tasks.

* test(model-routing): verify dynamic routing enabled by default

Tests that defaultRoutingConfig returns enabled: true and all
routing features are active.
2026-03-31 11:47:38 -06:00
Tom Boucher
081c5dc52f fix: surface nativeCommit errors in reconcileMergeState instead of silently swallowing (#3052)
The catch block in reconcileMergeState silently swallowed all nativeCommit
exceptions, including real failures (permissions, corrupt git state, hook
rejections). This caused auto-mode to report success and return true (dirty,
re-derive) even when the merge commit actually failed, leading to an infinite
loop where auto-mode repeatedly attempted worktree finalization.

Now the catch block logs the error via ctx.ui.notify at "error" level and
returns false to signal that reconciliation failed, allowing upstream logic
to react appropriately. The nativeCommit return value is also checked —
a null return (nothing to commit) gets its own info notification distinct
from a successful commit SHA.

Closes #2542

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 14:50:28 -06:00
Tom Boucher
46d798a1bf fix(parallel): scope commits to milestone boundaries in parallel mode (#3047)
When GSD_MILESTONE_LOCK is set (parallel worker mode), smartStage() now
excludes .gsd/milestones/<M>/ directories for all milestones other than the
locked one. This prevents a parallel worker (e.g., M033) from staging and
committing fabricated artifacts for a milestone it does not own (e.g., M032).

Previously, smartStage() ran `git add -A` with only runtime path exclusions,
allowing cross-milestone pollution when workers share the same .gsd/ directory
(git.isolation: "none"). The GSD_MILESTONE_LOCK env var only filtered what
deriveState() sees but did not prevent file staging.

Closes #1991

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 14:50:21 -06:00
Tom Boucher
de9ba8aeb7 fix: add windowsHide to all web-mode subprocess spawns (#2628) (#3046)
On Windows, child_process.spawn() and execFile() open a visible console
window by default. The web server spawn, RPC bridge, browser opener, and
all 15 web service subprocess calls were missing windowsHide: true,
causing constant console window flashing when running gsd --web.

Closes #2628

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 14:50:13 -06:00
Tom Boucher
466c7dea18 fix: skip auto-mode pause on empty-content aborted messages (#2695) (#3045)
When the LLM sends an assistant message with empty content[] and
stopReason "aborted", this is a non-fatal agent stop — not a crash.
The abort handler now checks for empty content and missing errorMessage
before deciding to pause. Empty-content aborts are routed to
resolveAgentEnd instead, breaking the stuck re-dispatch loop.

Closes #2695

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 14:50:05 -06:00
Tom Boucher
0b36977804 fix: detect and remove nested .git dirs in worktree cleanup to prevent data loss (#3044)
Scaffolding tools (create-next-app, cargo init, etc.) create nested .git
directories inside worktrees. Git records these as gitlinks (mode 160000)
without .gitmodules, so worktree cleanup destroys the only copy of the
nested object database — causing permanent silent data loss.

Added findNestedGitDirs() helper that recursively scans worktree for nested
.git directories (skipping node_modules and other non-project dirs). The
removeWorktree() function now calls this before cleanup and removes any
nested .git dirs so files are tracked as regular content.

Closes #2616

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 14:49:54 -06:00
Tom Boucher
9384641b25 fix: prevent data loss when git isolation default changes (#2625) (#3043)
When the default isolation mode flipped from "worktree" to "none" between
versions, mergeAndExit() returned early for mode "none" without checking
whether the session was physically inside an active worktree. This silently
skipped the merge, orphaning committed work on the milestone branch.

The fix moves the worktree-presence check (isInAutoWorktree + originalBasePath)
before the mode-none early return. If we are inside a worktree, mergeAndExit
proceeds with the worktree merge path regardless of the configured mode.

Also fixes the misleading JSDoc on GitPreferences.isolation that claimed
"worktree" was the default when the runtime default is actually "none".

Closes #2625

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 14:49:03 -06:00
Tom Boucher
893c525578 fix(read-tool): clamp offset to file bounds instead of throwing (#3007) (#3042)
When an agent requests read(file, offset: 30) on a 13-line file, the
read tool threw "Offset 30 is beyond end of file" which propagated as
invalid JSON downstream during milestone completion. Now clamps the
offset to the last line and prepends a notice, allowing the agent to
continue with valid content.

Fixes both read.ts and hashline-read.ts variants.

Closes #3007

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 14:48:01 -06:00
Tom Boucher
70e3d9d6c2 fix(gsd): preserve queued milestones with worktrees in ghost detection (#3041)
isGhostMilestone() now checks for DB rows and worktree directories before
falling back to content-file detection. A milestone with a DB row or a
worktree is a legitimate milestone that hasn't been populated yet, not a
ghost from a killed session.

Fixes #2921

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 14:47:53 -06:00
Tom Boucher
3a1cedd7de fix(compaction): add chunked fallback when messages exceed model context window (#3038)
When a session grows beyond the context window of available models,
generateSummary() now detects the overflow and falls back to chunked
summarization: split messages into context-fitting chunks, summarize
the first chunk, then iteratively merge subsequent chunks using the
existing UPDATE_SUMMARIZATION_PROMPT path.

Closes #2932

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 14:47:41 -06:00
Tom Boucher
dfb4fbecef fix: preserve interactive terminal across tab switches and project changes (#3055)
Two root causes destroyed terminal state during normal navigation:

1. The pagehide handler fired a shutdown beacon unconditionally, but on
   mobile/Safari tab switches pagehide fires with event.persisted=true
   (bfcache entry). This killed the server and all PTY sessions when the
   user merely switched browser tabs. Fix: check event.persisted and skip
   the beacon when the page is being cached, not unloaded.

2. ShellTerminal used project-agnostic session IDs ("default"), so
   switching projects and switching back either collided with the old
   session or spawned a new one, losing terminal state. Fix: scope session
   IDs by project path (e.g. "default:/path/to/project") so the server's
   getOrCreateSession returns the existing live PTY on reconnect.

Closes #2701

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 14:46:09 -06:00
Tom Boucher
fb10141e9b fix: call cleanupQuickBranch on turn_end to squash-merge quick branch back (#3054)
cleanupQuickBranch() was exported from quick.ts but never called anywhere.
After a /gsd quick task completed, the user was left on the quick branch
with orphaned state in quick-return.json.

Register a turn_end hook in register-hooks.ts that calls cleanupQuickBranch()
after each agent turn. The function is already idempotent (no-op when no
quick-return state is pending), so it is safe to call on every turn.

Closes #2668

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 14:46:03 -06:00
Tom Boucher
dfb18c6e62 fix: align run-uat artifact path to ASSESSMENT, preventing false stuck retries (#3053)
The run-uat prompt instructs the agent to save results via gsd_summary_save
with artifact_type: "ASSESSMENT", which writes S##-ASSESSMENT.md. But
resolveExpectedArtifactPath and diagnoseExpectedArtifact expected S##-UAT.md,
causing artifact verification to fail and auto-mode to retry indefinitely.

Align all three contract points (prompt uatResultPath, artifact resolution,
and diagnostic message) to use ASSESSMENT as the canonical artifact type.

Closes #2873

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 14:45:57 -06:00
Tom Boucher
fb0fb5582e fix: replace invalid Discord invite links with canonical URL (#3056)
Closes #2699

The Discord badge in README.md pointed to https://discord.gg/gsd (expired
vanity URL) and the Pi ecosystem doc used an old invite code. Both now use
the canonical invite https://discord.com/invite/nKXTsAcmbT that was
established in commit 0a1dad9a.

Adds a regression test that validates all Discord invite links in
user-facing files match the canonical URL.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 14:45:32 -06:00
Tom Boucher
fad23944e7 fix: add Windows shell guard to remaining spawn sites (#3058)
Three spawn call sites were missing `shell: process.platform === "win32"`,
causing ENOENT/EINVAL errors on Windows where npm-installed tools are .cmd
batch scripts that require shell resolution:

- exec.ts: hardcoded `shell: false` -> platform-guarded
- lsp/index.ts: missing shell option on project-type command spawn
- lsp/lspmux.ts: missing shell option on lspmux binary spawn

Adds a structural regression test that scans all spawn sites invoking
user-facing binaries and asserts the Windows shell guard is present.

Closes #2854

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 14:44:20 -06:00
Tom Boucher
05b7cb95cb fix: route gsd auto to headless runner to prevent hang on piped stdin/stdout (#3057)
`gsd auto` was not handled as a subcommand — it fell through to the
interactive TUI, which hangs indefinitely when stdin/stdout are piped
(non-TTY). Add `auto` as a recognized subcommand that rewrites argv
and delegates to `runHeadless(parseHeadlessArgs(...))`, matching the
existing `gsd headless auto` behavior.

Also adds `gsd auto` to TTY error hints and help text.

Closes #2732

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 14:44:04 -06:00
Tom Boucher
df9e06cfa5 fix: respect .gitignore for .gsd/ in rethink prompt (#3059)
* fix: respect .gitignore for .gsd/ in rethink prompt (#2570)

The rethink.md prompt template hardcoded `git add .gsd/` which caused
the executing agent to force-add .gsd/ files (via `git add -f`) when
.gsd was listed in .gitignore. This silently overrode the user's
gitignore configuration, tracking planning artifacts they explicitly
excluded.

- Add `isGsdGitignored()` utility that uses `git check-ignore` to
  detect when .gsd is covered by .gitignore rules
- Replace hardcoded `git add .gsd/` in rethink.md with the
  `{{commitInstruction}}` template variable (consistent with all
  other prompt templates)
- Pass gitignore-aware commit instruction from rethink.ts: skip
  commit when .gsd is gitignored, include git add only when it is not

Closes #2570

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

* ci: re-trigger checks

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 14:43:56 -06:00
Tom Boucher
e71de432ab fix: migrate unit ownership from JSON to SQLite to eliminate read-modify-write race (#3061)
The JSON-based unit-claims storage had a lost-update race under concurrent
multi-agent use: two agents could both read the file as unclaimed, then both
write their claim, with the second silently overwriting the first.

Replace with a SQLite-backed store using INSERT OR IGNORE on a PRIMARY KEY
constraint for atomic first-writer-wins claim semantics. claimUnit() now
returns boolean (true = claimed, false = already claimed by another agent).

Closes #2728

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 14:43:44 -06:00
Tom Boucher
e78dca41d4 fix(roadmap): handle numbered, bracketed, and indented prose H3 headers in slice parser (#3063)
The prose slice header fallback parser failed to extract slices when
LLMs generated common formatting variants: numbered prefixes (### 1. S01),
parenthetical numbering (### (1) S01), bracketed IDs (### [S01]), or
indented headings (  ### S01). This caused auto-mode to permanently block
with "No slice eligible" when the plan-milestone prompt produced these
formats inside a ## Slices section.

Broadened the parseProseSliceHeaders regex to accept optional leading
whitespace, numeric prefixes, parenthetical numbering, and square brackets
around slice IDs.

Closes #2567

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 14:43:33 -06:00
Tom Boucher
8b680179e2 fix: add worktree-merge to resolveModelWithFallbacksForUnit switch and update KNOWN_UNIT_TYPES (#3066)
Closes #2900

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 14:43:22 -06:00