Commit graph

2222 commits

Author SHA1 Message Date
Jeremy McSpadden
ff2c2605f3 feat(gsd): enable safety mechanisms by default (snapshots, pre-merge checks) (#2678)
Flip two safety mechanisms from opt-in to opt-out so all users benefit
from rollback protection and merge regression checks without manual
configuration.

- git.snapshots: false → true (creates recovery refs before destructive ops)
- git.pre_merge_check: false → "auto" in solo mode (auto-detects test runner)

Both remain configurable; users can explicitly disable with snapshots: false
or pre_merge_check: false.

Closes #2677
2026-03-26 18:15:31 -06:00
Iouri Goussev
de600c1db0 refactor(gsd): extract duplicated status guards and validation helpers (#2767)
* fix: rebuild stale workspace packages after git pull

ensure-workspace-builds.cjs only triggered a build when dist/index.js
was missing entirely. After `git pull` updates package sources, the old
dist/ stayed in place causing TypeScript type errors (bash_transform,
authMode, malformedArguments missing from compiled .d.ts files).

Now compares newest .ts mtime under src/ against dist/index.js mtime
and rebuilds any package whose sources are newer than its dist.

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

* fix(rtk): trust explicit binaryPath without existsSync check; add options object to shared rewriteCommandWithRtk

resolveRtkBinaryPath was calling existsSync on options.binaryPath, making
it impossible to inject a non-existent test binary — tests expected the
options-object API to bypass filesystem checks.

Also brings src/resources/extensions/shared/rtk.ts rewriteCommandWithRtk
in line with the same options-object signature already in src/rtk.ts.

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

* refactor(gsd): extract duplicated status guards and validation helpers

isClosedStatus(), isNonEmptyString(), and validateStringArray() were
each copy-pasted across 5-10 tool handler files with no shared module.
Extract them into status-guards.ts and validation.ts, replace all 26
inline status checks and 8 duplicated validation functions with imports.

Standardizes "inside a closed" -> "in a closed" in two reopen error
messages as a side effect of the normalization pass.

Closes #2727

* refactor(gsd): migrate state.ts isStatusDone to isClosedStatus; fix blank lines and import order

- state.ts had a private isStatusDone() identical to isClosedStatus() —
  replace with import from status-guards.ts
- Remove double blank lines left behind in plan-{milestone,slice,task}.ts
  and replan-slice.ts after local function extraction
- Fix import ordering in reassess-roadmap.ts (node built-ins first,
  status-guards/validation before gsd-db block)

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-26 18:14:43 -06:00
github-actions[bot]
34bbee21bc release: v2.52.0 2026-03-27 00:07:48 +00:00
TÂCHES
4f1ff1fe28 fix: auto-mode stops after provider errors (#2762) (#2764)
* feat: Registered 6 MCP tools (gsd_execute, gsd_status, gsd_result, gsd_…

- "packages/mcp-server/src/server.ts"
- "packages/mcp-server/src/cli.ts"
- "packages/mcp-server/src/index.ts"
- "packages/rpc-client/dist/index.d.ts"

GSD-Task: S05/T02

* docs: Added 31 integration tests, build pipeline, and consumer README f…

- "packages/mcp-server/src/mcp-server.test.ts"
- "packages/mcp-server/README.md"
- "packages/mcp-server/dist/"

GSD-Task: S05/T03

* fix: prevent auto-mode hard stop on provider errors and suppress duplicate async_job_result follow-ups (#2762)

Two compounding bugs caused auto-mode to silently die after unit completion:

1. async_job_result follow-ups fired after unit completion because deliverResult
   ran synchronously in the job promise .then() chain, racing with await_job's
   .then() that sets job.awaited=true. Deferring delivery by one microtask via
   queueMicrotask ensures await_job marks the job first.

2. Provider error pause converted to hard stop because pauseAuto resolved the
   unit promise with {status:"cancelled"} but no ErrorContext, so runUnitPhase
   treated it identically to a session-creation timeout and called stopAuto.
   Now pauseAuto accepts and forwards ErrorContext, and runUnitPhase checks for
   category:"provider" to break without hard-stopping.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* test: update source-scan assertion for new pauseAuto signature

The structural test checked for `resolveAgentEndCancelled()` with empty
parens. Now that pauseAuto passes _errorContext, match the call prefix.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 17:55:20 -06:00
TÂCHES
1c2d7ab307 fix: add missing runtime stage name to Dockerfile (#2765)
* feat: Registered 6 MCP tools (gsd_execute, gsd_status, gsd_result, gsd_…

- "packages/mcp-server/src/server.ts"
- "packages/mcp-server/src/cli.ts"
- "packages/mcp-server/src/index.ts"
- "packages/rpc-client/dist/index.d.ts"

GSD-Task: S05/T02

* docs: Added 31 integration tests, build pipeline, and consumer README f…

- "packages/mcp-server/src/mcp-server.test.ts"
- "packages/mcp-server/README.md"
- "packages/mcp-server/dist/"

GSD-Task: S05/T03

* fix: add missing runtime stage name to Dockerfile

CI pipeline uses `docker build --target runtime` but the FROM line
lacked the `AS runtime` alias, causing the build to fail.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 17:52:45 -06:00
TÂCHES
e296d8b9d9 Merge pull request #2681 from splichy/fix/provider-scoped-stream-routing
fix: exempt interactive tools from idle watchdog stall detection
2026-03-26 17:20:30 -06:00
TÂCHES
55524d4ffb Merge pull request #2687 from mastertyko/fix/copy-preferences-to-worktree
fix(gsd): include preferences.md in worktree sync and initial seed
2026-03-26 17:19:20 -06:00
TÂCHES
ac692d7fe4 Merge pull request #2758 from jeremymcs/ci/require-tests-with-prs
ci: enforce test requirements and coverage thresholds on PRs
2026-03-26 17:18:42 -06:00
TÂCHES
618f15d7c7 Merge pull request #2756 from gsd-build/claude/audit-sqlite-gsd-VLoLV
Optimize GSD database: statement caching, indexes, and slice dependencies
2026-03-26 17:18:41 -06:00
Lex Christopherson
e6a01a265a Merge remote-tracking branch 'origin/main' into claude/audit-sqlite-gsd-VLoLV 2026-03-26 17:13:27 -06:00
TÂCHES
11bd1a6734 Merge pull request #2760 from mastertyko/fix/sqlite-nested-transaction
fix: re-entrant transactions + missing slice_dependencies table in initSchema
2026-03-26 17:12:50 -06:00
TÂCHES
473583d349 Merge pull request #2759 from igouss/fix/tool-handlers-bypass-db-port-2726
refactor(gsd): wire tool handlers through DB port layer, remove _getAdapter from all tools
2026-03-26 17:12:24 -06:00
mastertyko
5ab375b773 fix: make transaction() re-entrant and add slice_dependencies to initSchema
Two bugs fixed:

1. transaction() now tracks nesting depth. When deleteTask/deleteSlice
   (which wrap in transaction()) are called from within an outer
   transaction() in reassess-roadmap.ts or replan-slice.ts, the inner
   call skips BEGIN/COMMIT since SQLite doesn't support nested
   transactions. This fixes:
   - reassess-handler.test.ts: 3 failing tests
   - replan-handler.test.ts: 4 failing tests
   All errors were: 'cannot start a transaction within a transaction'

2. slice_dependencies table and v13/v14 indexes were only created in
   migrateSchema (for upgrades from older versions) but missing from
   initSchema (for fresh databases). New databases started at schema
   version 14 but never created the table, causing 'no such table:
   slice_dependencies' when deleteSlice was called.
2026-03-26 23:59:46 +01:00
mastertyko
523e910f21 fix: remove preferences.md from ROOT_STATE_FILES to prevent back-sync overwrite
preferences.md was in ROOT_STATE_FILES which caused syncWorktreeStateBack()
to overwrite the project root's authoritative copy with the worktree's
stale copy. The forward-sync (main → worktree) is already handled
separately in syncGsdStateToWorktree() as additive-only.

Fixes the failing CI test:
  worktree-preferences-sync.test.ts:107
  '#2684: syncWorktreeStateBack does NOT overwrite project root preferences.md'

Also updates preferences-worktree-sync.test.ts to assert preferences.md
is NOT in ROOT_STATE_FILES (it must be handled separately).
2026-03-26 23:57:17 +01:00
Iouri Goussev
02e813c0a3 fix: wire tool handlers through DB port layer, remove _getAdapter from all tools
Fixes #2726. Tool handlers were bypassing the DB port layer by calling
_getAdapter() directly for raw SQL. Replace all such callsites with
proper exported DB functions.

- Add setTaskSummaryMd(), setSliceSummaryMd() to gsd-db.ts
- Extend updateMilestoneStatus() to accept optional completedAt param
- Add deleteVerificationEvidence(), deleteAssessmentByScope() to gsd-db.ts
- complete-task.ts: use updateTaskStatus, setTaskSummaryMd, deleteVerificationEvidence
- complete-slice.ts: use updateSliceStatus, setSliceSummaryMd
- complete-milestone.ts: use updateMilestoneStatus for both complete and rollback
- validate-milestone.ts: use insertAssessment, deleteAssessmentByScope
- plan-slice.ts, plan-milestone.ts: remove dead _getAdapter import
2026-03-26 18:56:06 -04:00
Jeremy
13bad4f4e9 ci: enforce test requirements and coverage thresholds on PRs
Add two CI gates to enforce CONTRIBUTING.md test requirements:

1. File-matching check (lint job): fails PRs that change source files
   without including test file changes. Exempts docs/chore/ci branches.

2. Coverage gate (build job): wires existing `npm run test:coverage`
   into CI with c8 thresholds (40% statements/lines, 20% branches/functions).
   Previously defined in package.json but never ran in CI.

Lowers coverage thresholds from 50% to 40% for statements/lines to match
current codebase reality (~44%) — prevents the gate from blocking every PR
on day one while still catching coverage regressions.
2026-03-26 17:55:53 -05:00
TÂCHES
bc9b12bb90 Merge pull request #2692 from igouss/fix/unified-error-classifier
refactor(gsd): unify three overlapping error classifiers into single pipeline
2026-03-26 16:52:39 -06:00
Lex Christopherson
f28aca2ee1 Merge branch 'main' into fix/unified-error-classifier
Resolve conflicts: keep unified classifyError (PR intent), remove old
classifyProviderError. Port stream_exhausted pattern from main into
unified CONNECTION_RE and add corresponding test.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 16:47:02 -06:00
Lex Christopherson
7d0130fa0f Merge branch 'main' into fix/unified-error-classifier
Resolve conflicts in provider-error-pause.ts and provider-errors.test.ts.
Add stream_exhausted(_without_result) pattern to unified CONNECTION_RE
(ported from main's classifyProviderError addition).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 16:43:26 -06:00
deekshanayak-1108
24da88cfbe Fix WARN log readability by improving yellow contrast in light theme (#2689) 2026-03-26 16:41:59 -06:00
mastertyko
032088e409 fix(gsd): move state machine guards inside transaction in 5 tool handlers (#2752)
plan-task, plan-slice, plan-milestone, reassess-roadmap, and
replan-slice all ran state machine guards (getSlice, getMilestone,
getTask) outside the transaction() callback, then performed writes
in a separate transaction. This created a TOCTOU race: two agents
could both pass the guard simultaneously and both write successfully.

Fix: move all guard checks into the transaction() callback using
the guardError pattern already used by complete-task, complete-slice,
reopen-task, and reopen-slice. The SQLite write lock now covers both
the guard reads and the subsequent writes atomically.

Closes #2723
2026-03-26 16:39:19 -06:00
Jeremy McSpadden
f8814f5a15 refactor(pi-ai): replace model-ID pattern matching with capability metadata (#2548)
* refactor(pi-ai): replace model-ID pattern matching with capability metadata

Add ModelCapabilities to Model<TApi> and a CAPABILITY_PATCHES mechanism
so call sites read model.capabilities fields instead of parsing model IDs
or hardcoding provider names.

- types.ts: add ModelCapabilities interface (supportsXhigh, requiresToolCallId,
  supportsServiceTier, charsPerToken) and capabilities?: ModelCapabilities to
  Model<TApi>
- models.ts: add CAPABILITY_PATCHES table applied at registry init; patches
  declare GPT-5.x and Opus 4.6 capabilities once instead of repeating ID
  checks at every call site; supportsXhigh() now reads capabilities only
- service-tier.ts: extract SERVICE_TIER_MODEL_PREFIXES constant so the gating
  list has a single named home; add path comment pointing to issue #2546 for
  the full capability-driven follow-up

No behaviour change. New models and providers can declare capabilities in
their model definitions without touching function logic.

Closes #2546

* fix(pi-ai): apply capability patches to custom/discovered/extension models

Models constructed outside the static pi-ai registry (custom models
from models.json, extension-registered models, discovered models)
bypassed CAPABILITY_PATCHES — causing supportsXhigh() to silently
return false for GPT-5.x or Opus 4.6 variants registered through
those paths.

Export applyCapabilityPatches() from pi-ai and call it in ModelRegistry
after model assembly in all three construction paths: loadModels(),
applyProviderConfig(), and discoverModels().

Add regression tests covering patching, precedence, idempotency,
and synthetic models that mimic the custom/extension path.

Closes #2546
2026-03-26 16:38:29 -06:00
Claude
2023291dca perf(gsd-db): comprehensive SQLite audit fixes — indexes, caching, safety, reconciliation
13 improvements from a full audit of the SQLite DB system powering /gsd auto:

Performance:
- Add 5 missing indexes for hot-path dispatch queries (schema v13)
- Add PRAGMA synchronous=NORMAL, cache_size, mmap_size, temp_store tuning
- Implement prepared statement caching in DbAdapter (eliminates re-parse)
- Replace getActiveSliceFromDb N+1 query with single json_each() query
- Add lightweight query variants (getActiveMilestoneIdFromDb, getSliceTaskCounts, etc.)
- Batch enforceMemoryCap into single UPDATE (was N individual updates)

Safety:
- Wrap deleteSlice/deleteTask in transactions (prevents orphaned rows on crash)
- Harden reconcileWorktreeDb path sanitization (reject quotes, semicolons, nulls)
- Fix memory ID race condition (insert-then-derive from AUTOINCREMENT seq)

Completeness:
- Extend worktree reconciliation to merge all 7 tables (was only 3)
- Add slice_dependencies junction table for indexed dep queries (schema v14)
- Add DB vacuuming (incremental on close, full VACUUM export for milestones)
- Update dead-code sequence column comments to "ordering hint"

All 22 DB-related tests pass (gsd-db, memory-store, worktree-db).

https://claude.ai/code/session_019h5VhLuSYNnQEd6kz9otwk
2026-03-26 22:38:23 +00:00
TÂCHES
cceee1d196 Merge branch 'main' into fix/copy-preferences-to-worktree 2026-03-26 16:24:54 -06:00
mastertyko
53d2da15b5 fix: reconcile disk milestones into empty DB before deriveStateFromDb guard (#2686)
When the milestones DB table has 0 rows (e.g. failed initial migration
per #2529), deriveState fell through to the filesystem path because
deriveStateFromDb was only called when dbMilestones.length > 0. The
reconciliation code inside deriveStateFromDb was unreachable — the very
condition it was supposed to fix gated its execution.

The fix moves disk→DB sync into deriveState itself: when the DB is
available but empty, scan disk milestone directories and insert them
before the length check. This ensures the DB path activates correctly
even after a failed migration.

Closes #2631
2026-03-26 16:24:19 -06:00
drkthng
1f10ed9585 fix(gsd): seed preferences.md into auto-mode worktrees (#2693)
preferences.md was missing from both copyPlanningArtifacts() (initial
worktree seed) and the ongoing forward-sync in syncGsdStateToWorktree().
This meant post_unit_hooks, skill rules, and custom instructions from
preferences.md were silently unavailable in auto-mode worktrees.

Fix:
- Add preferences.md to copyPlanningArtifacts() file list
- Add dedicated preferences.md forward-sync in syncGsdStateToWorktree()
  with additive-only semantics (only copies when missing in worktree)
- NOT added to ROOT_STATE_FILES to prevent syncWorktreeStateBack() from
  overwriting the project root's authoritative preferences.md

Regression test verifies:
1. Forward-sync copies preferences.md when missing from worktree
2. Forward-sync does NOT overwrite existing worktree preferences.md
3. Back-sync does NOT overwrite project root preferences.md

Closes #2684
2026-03-26 16:24:01 -06:00
Eric Muller
202da287d0 fix(claude-import): discover marketplace plugins nested inside container directories (#2718)
Claude Code stores marketplace sources under ~/.claude/plugins/marketplaces/,
where each subdirectory (e.g. marketplaces/my-marketplace/) is a marketplace
repo containing .claude-plugin/marketplace.json. The parent directory itself
does not have a marketplace.json.

categorizePluginRoots was checking only the root path for marketplace.json,
so ~/.claude/plugins/marketplaces/ was always categorized as flat (no
marketplace.json at that level). The flat fallback then looked for
package.json, which Claude plugins don't have — they use
.claude-plugin/plugin.json.

Two fixes:

1. categorizePluginRoots now scans one level deeper: when a root isn't
   itself a marketplace, it enumerates immediate subdirectories to find
   child marketplace repos. Deduplicates via a seen set when the same
   marketplace is reachable through multiple roots.

2. discoverClaudePlugins now recognizes .claude-plugin/plugin.json in
   addition to package.json, so cached Claude marketplace plugins are
   discoverable in the flat-path fallback.

Closes #2717

Co-authored-by: Eric Muller <ericmuller@confluent.io>
2026-03-26 16:23:16 -06:00
Vojtěch Šplíchal
f40418be8d fix: exempt interactive tools from idle watchdog stall detection (#2676)
The idle watchdog treated ask_user_questions and secure_env_collect as
stalled tools, killing sessions before users could respond.

Root cause: tool tracking stored only toolCallId → timestamp with no
tool name, so the watchdog couldn't distinguish user-interactive tools
from hung tools.

Changes:
- auto-tool-tracking: store toolName alongside timestamp, add
  INTERACTIVE_TOOLS set and hasInteractiveToolInFlight()
- auto.ts: forward optional toolName through markToolStart wrapper
- register-hooks: pass event.toolName to markToolStart
- auto-timers: skip stall detection when interactive tool is in-flight,
  record lastProgressKind: 'interactive-tool-waiting'
- New test: 13 cases covering interactive exemption, completion cleanup,
  backwards compat, and existing behavior preservation
2026-03-26 23:22:40 +01:00
mastertyko
8d77c40638 fix: guard allSlicesDone against vacuous truth on empty slice array (#2679)
deriveStateFromDb line 565 used activeMilestoneSlices.every() without a
length > 0 guard. In JavaScript, [].every() === true (vacuous truth),
which would cause a premature phase transition to validating-milestone
if the array were empty at that point.

While the current code has an early-return at line 536 that catches
length === 0, the guard is still necessary for consistency with the
identical checks at lines 368 and 413 (which both have the guard),
and to protect against future control-flow changes that might bypass
the early return.

Closes #2667
2026-03-26 16:19:35 -06:00
Jeremy McSpadden
c2cd8bcc0a feat(vscode): status bar, file decorations, bash terminal, session tree, conversation history, code lens [1/2] (#2651)
* feat(vscode): status bar, auto-retry, session name, copy response, keyboard shortcuts, full stats

* feat(vscode): file decorations, bash terminal, session tree view

* feat(vscode): conversation history webview, slash completion, code lens

- conversation-history.ts: GsdConversationHistoryPanel webview panel using
  getMessages() RPC; renders user/assistant turns with a Refresh button
- slash-completion.ts: GsdSlashCompletionProvider triggers on '/' at line
  start in md/plaintext/ts/js; fetches getCommands() RPC and caches results
- code-lens.ts: GsdCodeLensProvider adds 'Ask GSD' lens above named
  functions/classes in ts/js/py/go/rust; respects gsd.codeLens setting
- extension.ts: registers all three providers and new commands
  (gsd.showHistory, gsd.askAboutSymbol)
- package.json: declares new commands and gsd.codeLens config toggle
2026-03-26 16:18:37 -06:00
mastertyko
12713a547c fix: block complete-milestone dispatch when VALIDATION is needs-remediation (#2682)
When VALIDATION.md has verdict needs-remediation and all slices appear
done in the DB, the state machine enters completing-milestone. The
complete-milestone dispatch rule had no verdict check, so it dispatched
the unit — the agent correctly refused (validation failed), no SUMMARY
was written, and the unit was re-dispatched up to MAX_LIFETIME_DISPATCHES
times before stuck detection fired.

The fix adds a verdict check in the completing-milestone dispatch rule
that returns action: stop with level: warning when the verdict is
needs-remediation. Using warning level ensures the session pauses
(resumable) rather than hard-stopping, matching the pattern from #2474.

Closes #2675
2026-03-26 16:17:58 -06:00
mastertyko
c6c194b7e9 fix(gsd): sync milestone DB status in parkMilestone and unparkMilestone (#2696)
* fix: sync milestone DB status in parkMilestone and unparkMilestone

parkMilestone only wrote the PARKED.md filesystem marker but never
updated the DB milestones.status field. Similarly, unparkMilestone
deleted the marker but left the DB at 'parked'. Because
deriveStateFromDb checks BOTH the filesystem marker AND m.status,
an unparked milestone was still skipped — the user saw 'All milestones
complete' despite the milestone being unparked on disk.

The fix adds updateMilestoneStatus() to gsd-db.ts and calls it from
both parkMilestone (→ 'parked') and unparkMilestone (→ 'active'),
guarded by isDbAvailable() with non-fatal try/catch.

Closes #2694

* review: log DB sync failures instead of silently swallowing

Replace empty catch blocks with process.stderr.write so park/unpark
DB sync failures are visible. Matches the pattern used in gsd-db.ts
for non-fatal DB errors.

Addresses review feedback from igouss on PR #2696.
2026-03-26 16:17:25 -06:00
Andrew
6cc6c36a69 fix(web): auth token gate — synthetic 401 on missing token, unauthenticated boot state, and recovery screen (#2740)
When `gsd --web` is opened without the #token= hash fragment (manual URL
entry, bookmark, new tab), `authenticatedFetch` previously fell through to
a naked `fetch()` that always returned 401, flooding the console with
cascading errors and leaving the UI in a broken state with no recovery path.

Three changes:

1. `web/lib/auth.ts` — `authFetch()` now returns a synthetic 401 Response
   when `getAuthToken()` returns null instead of delegating to bare fetch.
   This makes missing-token failures consistent and immediately catchable
   by all callers without a network round-trip.

2. `web/lib/gsd-workspace-store.tsx` — Added `"unauthenticated"` to
   `WorkspaceStatus`. `refreshBoot()` now detects a 401 response from
   /api/boot and patches `bootStatus` to `"unauthenticated"` instead of
   throwing a generic error. This is a distinct state — not an error worth
   retrying, but a configuration problem the user must resolve.

3. `web/components/gsd/app-shell.tsx` — Added an early-return guard that
   renders a minimal "Authentication Required" screen when
   `bootStatus === "unauthenticated"`. The screen explains the problem and
   tells users to copy the full terminal URL (including `#token=…`) or
   restart with `gsd --web`.

Fixes #2731
2026-03-26 16:17:12 -06:00
Andrew
07d804588e feat(web): Dark mode contrast — raise token floor and flatten opacity tier system (#2734)
* feat: Raised four dark-mode tokens, converted five hardcoded oklch valu…

- "web/app/globals.css"
- "web/components/gsd/code-editor.tsx"

GSD-Task: S01/T01

* feat: Applied border-border 2-tier sweep across 21 component files: /20…

- "web/components/gsd/command-surface.tsx"
- "web/components/gsd/remaining-command-panels.tsx"
- "web/components/gsd/chat-mode.tsx"
- "web/components/gsd/settings-panels.tsx"
- "web/components/gsd/diagnostics-panels.tsx"
- "web/components/gsd/onboarding/step-authenticate.tsx"
- "web/components/gsd/knowledge-captures-panel.tsx"
- "web/components/gsd/projects-view.tsx"

GSD-Task: S02/T01

* feat: Swept text-foreground/muted-foreground/sidebar-foreground opacity…

- "web/components/gsd/command-surface.tsx"
- "web/components/gsd/remaining-command-panels.tsx"
- "web/components/gsd/chat-mode.tsx"
- "web/components/gsd/settings-panels.tsx"
- "web/components/gsd/diagnostics-panels.tsx"
- "web/components/gsd/knowledge-captures-panel.tsx"
- "web/components/gsd/projects-view.tsx"
- "web/components/gsd/visualizer-view.tsx"

GSD-Task: S02/T02

* feat: Applied background opacity mapping tables across all component fi…

- "web/components/gsd/remaining-command-panels.tsx"
- "web/components/gsd/command-surface.tsx"
- "web/components/gsd/visualizer-view.tsx"
- "web/components/gsd/chat-mode.tsx"
- "web/components/gsd/settings-panels.tsx"
- "web/components/gsd/diagnostics-panels.tsx"
- "web/components/gsd/onboarding/step-authenticate.tsx"
- "web/components/gsd/knowledge-captures-panel.tsx"

GSD-Task: S02/T03
2026-03-26 16:17:03 -06:00
Jeremy McSpadden
74c1736372 fix(remote-questions): empty-key entry in auth.json shadows valid Discord bot token (#2737)
* fix(remote-questions): empty-key entry in auth.json shadows valid Discord bot token

removeProviderToken() called auth.set(provider, { key: '' }) instead of
auth.remove(provider). Since AuthStorage.set() appends for api_key type
(deduplicating by exact key match), this inserted an empty-key entry at
index 0. Every credential lookup (.get(), .find()) matched the empty
entry first, shadowing valid tokens at later indices.

Fixes:
- remote-command.ts: use auth.remove() instead of auth.set() with empty key
- config.ts: hydrateRemoteTokensFromAuth .find() now requires non-empty key
- wizard.ts: loadStoredEnvKeys uses getCredentialsForProvider + .find()
  instead of .get() which returns creds[0]
- onboarding.ts: check existing tokens via .some() over full credentials
  array instead of .get() which only returns first entry
- key-manager.ts: filter empty-key entries in getAllKeyStatuses, add/remove/
  rotate provider pickers, and doctor env-conflict check

Tests: 3186 pass, 0 fail across full GSD test suite

* fix(config): ignore empty shadowing tool keys
2026-03-26 16:16:42 -06:00
TÂCHES
c684221b0b test: Add audit persistence regression tests (#2722) (#2749)
* feat: Created gsd-orchestrator/ skill directory with ClawHub frontmatte…

- "gsd-orchestrator/SKILL.md"
- "gsd-orchestrator/references/commands.md"
- "gsd-orchestrator/references/answer-injection.md"
- "gsd-orchestrator/references/json-result.md"

GSD-Task: S03/T01

* test: Add audit persistence tests for workflow-logger (#2722)

The production fix for #2722 (wiring setLogBasePath + preserving
_auditBasePath across _resetLogs) was already merged but had no
test coverage. Add tests verifying both behaviors.

Closes #2722

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 16:16:28 -06:00
mastertyko
11b38b8bb7 fix: idle watchdog stalled-tool detection overridden by filesystem activity (#2697)
Bug 1: When a tool stalls longer than idle_timeout, the watchdog notifies
but falls through to detectWorkingTreeActivity(), which resets
lastProgressAt when files were modified earlier in the task. Recovery is
never called — the session burns tokens indefinitely.

Fix: Add stalledToolDetected flag + clearInFlightTools() call. The
filesystem-activity check is guarded by !stalledToolDetected so it
cannot override the stall verdict.

Bug 2: After async recoverTimedOutUnit(), pauseAuto/stopAuto may set
s.currentUnit = null during the await, but the next line accesses
s.currentUnit.startedAt without a null guard — crash.

Fix: Add null guard for s.currentUnit after the recovery call.

Closes #2527
2026-03-26 16:14:09 -06:00
mastertyko
f2113f1353 fix: surface exhausted Claude SDK streams as errors (#2719)
Treat Claude SDK generator exhaustion without a terminal result as a
stream interruption instead of a successful completion.

This prevents phantom-success auto-mode advances, keeps the failure
classifiable as transient provider recovery, and adds regression tests
for the fallback message plus provider classification.

Closes #2575
2026-03-26 16:11:23 -06:00
Iouri Goussev
0e07c647c5 fix(docker): overhaul fragile setup, adopt proven container patterns (#2716)
Split fake multi-stage Dockerfile into independent CI builder and
runtime images. Add proper entrypoint with UID/GID remapping via
PUID/PGID, sentinel-based first-boot bootstrap, pre-creation of
critical file targets, and signal-forwarding privilege drop via gosu.
Standardize on Node 24, split compose into minimal + full reference.

Closes #9
2026-03-26 16:10:49 -06:00
Iouri Goussev
a952391b33 chore: rename preferences.md to PREFERENCES.md for consistency (#2700) (#2738)
All other .gsd/ state files use uppercase naming (DECISIONS.md,
REQUIREMENTS.md, PROJECT.md, etc). This renames the canonical
preferences file to PREFERENCES.md while keeping a migration
fallback — the loader checks PREFERENCES.md first, then falls
back to lowercase preferences.md for existing installations.

Closes #2700

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-26 16:09:59 -06:00
mastertyko
6172246772 fix(gsd): write DB before disk in validate-milestone to match engine pattern (#2742)
* fix(gsd): write DB before disk in validate-milestone to match engine pattern

validate-milestone.ts wrote the VALIDATION.md file to disk before
inserting the assessment row into the DB. Every other handler in the
engine (complete-task, complete-slice) does DB-first, disk-second with
rollback compensation. The inverted order meant a crash between disk
write and DB insert would leave an orphaned file with no DB record —
a state that is harder to detect and recover from than the inverse
(DB row exists, file missing → projection rendering can regenerate).

Fix: reorder to DB-first, disk-second. On disk write failure, delete
the DB row via DELETE FROM assessments so state stays consistent.

Add two handler-level tests verifying:
1. Both DB row and disk file exist after success
2. DB row is rolled back (deleted) when disk write fails

Closes #2725

* fix(test): use file-as-directory to trigger disk failure cross-platform

chmod 0o444 does not prevent writes on Windows. Replace with
replacing the milestone directory with a regular file, so
saveFile's mkdirSync/write fails on all platforms.

Fixes windows-portability CI failure.
2026-03-26 16:09:32 -06:00
mastertyko
bae9e6a67d fix(gsd): extract and honor milestone argument in /gsd auto and /gsd next (#2729)
`/gsd auto M016` silently discarded the milestone ID and started
whichever milestone deriveState() picked as first incomplete. The
command handler parsed --verbose, --debug, and --yolo flags but never
extracted a milestone target.

Root cause: handleAutoCommand() had no milestone-ID extraction step.
The `rest` string from parseYoloFlag was only checked for flags, and
startAuto() was always called without milestone scoping.

Fix: add parseMilestoneTarget() to extract M-prefixed IDs (M001,
M001-a3b4c5) from the command string. When a milestone is specified:
1. Validate it exists via findMilestoneIds() — notify on missing
2. Set GSD_MILESTONE_LOCK env var (already honored by state.ts at
   three derivation points and by auto-post-unit.ts) via a
   withMilestoneLock() wrapper that cleans up the env var when
   auto-mode exits, preventing leakage into subsequent commands.

Both `/gsd auto <milestone>` and `/gsd next <milestone>` are
supported. Flags (--verbose, --debug) continue to work in any order.

Closes #2521
2026-03-26 16:08:49 -06:00
TÂCHES
41dda26b9a Merge pull request #2748 from gsd-build/fix/2743-web-search-duplicate-rendering
fix: Remove premature pendingTools.delete causing web_search duplicate rendering
2026-03-26 16:08:39 -06:00
Matt Haynes
c557aea8de fix(windows): prevent EINVAL by disabling detached process groups on Win32 (#2744)
On Windows, `spawn()` with `detached: true` sets the
CREATE_NEW_PROCESS_GROUP flag in CreateProcess. In certain terminal
contexts — notably VSCode's integrated terminal (ConPTY), Windows
Terminal, and some MSYS2/Git Bash configurations — this flag conflicts
with the parent process group hierarchy and causes a synchronous EINVAL
from libuv, making *every* bash/async_bash/bg_shell command fail
immediately with `spawn EINVAL`.

The bg-shell extension already guards against this with
`detached: process.platform !== "win32"` (process-manager.ts:109),
but three other spawn sites were missed:

- `packages/pi-coding-agent/src/core/tools/bash.ts` (bash tool)
- `packages/pi-coding-agent/src/core/bash-executor.ts` (RPC executor)
- `src/resources/extensions/async-jobs/async-bash-tool.ts` (async_bash)

This commit aligns all spawn sites with the bg-shell pattern.

Additionally fixes two related issues:

1. `killProcessTree()` in shell.ts used `detached: true` on its own
   `taskkill` spawn call — unnecessary and potentially problematic
   in the same terminal contexts. Removed.

2. `killTree()` in async-bash-tool.ts used Unix-only
   `process.kill(-pid)` with no Windows fallback. On Windows, negative
   PIDs (process group kill) are not supported, so orphaned child
   processes could survive timeout kills. Now uses `taskkill /F /T`
   on Windows, matching the bg-shell and shell.ts implementations.

Includes a regression test that statically verifies no spawn site
uses unconditional `detached: true`, plus a smoke test confirming
the platform-guarded pattern works on all platforms.

Reproduction: Run GSD v2.42-v2.51 inside VSCode on Windows 11 with
Git Bash as the shell. Any bash tool call fails with `spawn EINVAL`.
The error is 100% reproducible and affects all shell operations
(bash, async_bash, bg_shell start).

Co-authored-by: Matt Haynes <matt@auroraventures.io>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-26 16:08:03 -06:00
mastertyko
543710b5a9 fix(gsd): delete orphaned verification_evidence rows on complete-task rollback (#2746)
When complete-task's disk render fails, the rollback path resets the
task status to 'pending' but did not clean up verification_evidence
rows inserted in the same transaction. Since insertVerificationEvidence
uses plain INSERT (no ON CONFLICT dedup), each retry accumulated
additional evidence rows pointing to a pending task.

Fix: add DELETE FROM verification_evidence before the status rollback
UPDATE. The DELETE must come first due to the FK constraint (evidence
references tasks). This matches the cleanup order already used in
undoTask() and resetSlice() at gsd-db.ts:1699-1712.

Closes #2724
2026-03-26 16:07:12 -06:00
Iouri Goussev
a436f06e2d fix(gsd): wire setLogBasePath into engine init to resurrect audit log (#2745)
* fix: wire setLogBasePath into engine init to resurrect audit log

_auditBasePath was always null — setLogBasePath() existed but was never
called from any production code path. Every logWarning/logError call hit
the if (_auditBasePath) guard as false, so nothing was ever written to
.gsd/audit-log.jsonl.

Two independent fixes:
1. Remove _auditBasePath = null from _resetLogs() — the base path must
   survive unit resets, it's stable for process lifetime
2. Call setLogBasePath(base) after s.basePath = base in both the fresh-
   start path (bootstrapAutoSession) and the resume path (startAuto)

Adds two tests verifying disk persistence and that _resetLogs doesn't
kill the audit path.

Fixes #2722

* refactor: clean up audit log tests and avoid redundant mkdirSync

- Use makeTempDir/cleanup from test-utils.ts instead of inline mkdtempSync/rmSync
- Add afterEach in audit describe block to reset _auditBasePath via
  setLogBasePath("") — prevents state bleed into subsequent tests since
  _resetLogs() no longer clears it
- Drop four raw imports (mkdtempSync, rmSync, tmpdir — join was already used)
- Guard mkdirSync in _push() with _auditDirEnsured flag — was calling
  mkdirSync on every log entry; now called once per base path

* revert: remove _auditDirEnsured flag

mkdirSync({ recursive: true }) on an existing dir is a cheap stat, not
meaningful overhead on a low-frequency warn/error path. The flag added
mutable state for no real gain.
2026-03-26 16:06:48 -06:00
Lex Christopherson
ef310574da fix: Remove premature pendingTools.delete in webSearchResult handler (#2743)
The webSearchResult branch deleted entries from pendingTools after rendering,
which removed the duplicate-prevention guard. Subsequent streaming tokens
re-iterated content blocks, re-created the serverToolUse component, and
re-rendered the search result — producing 18+ duplicate blocks.

The message_end handler already calls pendingTools.clear(), so the explicit
deletes were unnecessary and harmful.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 16:03:07 -06:00
Iouri Goussev
878ed00f1b fix(gsd): remove redundant assertions that fail TS2367 typecheck
After assert.equal narrows result.kind to a literal type, comparing
it against a different literal is flagged as always-true by tsc.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-26 13:39:52 -04:00
Lex Christopherson
c5b38d69e3 feat: Wire --bare mode across headless → pi-coding-agent → resource-loa…
- "src/headless.ts"
- "packages/pi-coding-agent/src/cli/args.ts"
- "packages/pi-coding-agent/src/main.ts"
- "src/tests/headless-cli-surface.test.ts"

GSD-Task: S02/T02
2026-03-26 11:39:25 -06:00
Lex Christopherson
d355ab93fb test: Added --output-format text|json|stream-json flag, standardized ex…
- "src/headless-types.ts"
- "src/headless-events.ts"
- "src/headless.ts"
- "src/help-text.ts"
- "src/tests/headless-cli-surface.test.ts"

GSD-Task: S02/T01
2026-03-26 11:34:21 -06:00