The per-turn dedup cache introduced in the parent commit persists across
test cases since they all use the same question signature. Test 1 populates
the cache, causing tests 2 and 3 to get cached results instead of exercising
their intended code paths.
syncProjectRootToWorktree deleted empty gsd.db but left companion
-wal and -shm files on disk. On Node 24, node:sqlite attempts WAL
recovery from orphaned files, triggering a synchronous CPU spin loop
(227% CPU, 1.4GB RSS). Now deletes gsd.db-wal and gsd.db-shm when
the main DB is deleted or already missing.
Gate triggerTurn behind getInFlightToolCount() === 0 for both soft
timeout and context-pressure wrapup messages. Add clearQueue() to
stopAuto() and pauseAuto() to flush late async follow-ups.
Machine-local indexing state (LanceDB, sync cursors, job files) was
being tracked in Git, causing merge conflicts and stale cursor
propagation across branches. Gitignore alone doesn't affect
already-tracked files, so this removes them from the index while
keeping them on disk.
resolveDefaultSessionModel() previously only returned a result for
provider/model format strings, silently ignoring valid bare model IDs
like "gpt-5.4". This meant preferences could fail to override stale
settings.json defaults when users configured models without explicit
provider prefixes.
Now accepts sessionProvider param (ctx.model?.provider) to resolve bare
IDs. Also handles object configs without explicit provider field.
nextDecisionId() and nextRequirementId() compute the next ID via
SELECT MAX then pass it to a separate upsertDecision/upsertRequirement
call. When parallel tool calls hit these functions concurrently, both
read the same MAX value and produce the same ID — the second insert
silently overwrites the first.
Move the SELECT MAX + INSERT into a single transaction() call from
gsd-db.ts, which uses BEGIN/COMMIT/ROLLBACK and works on both
better-sqlite3 and node:sqlite providers. The transaction is
re-entrant safe (nested calls skip the BEGIN).
Same fix applied to saveRequirementToDb for consistency.
Closes#3326, closes#3339, closes#3459
Session bootstrap used ctx.model (from settings.json defaultProvider/defaultModel)
as the autoModeStartModel snapshot. When settings.json had a stale provider
(e.g. claude-code) but PREFERENCES.md was fully configured for openai-codex,
sessions would start with the wrong provider and fail with auth errors.
Add resolveDefaultSessionModel() to preferences-models.ts which extracts the
default model from GSD preferences (execution → planning → first configured).
In auto-start.ts, the preferred model now takes priority over ctx.model when
building startModelSnapshot, so PREFERENCES.md always wins over stale settings.
GSD-2 only searches ~/.agents/skills/ and .agents/skills/ for skills.
Claude Code's official skill directories (~/.claude/skills/ and
.claude/skills/) are not included in the search path, making GSD-2
blind to any skills managed there.
The skills.sh CLI (npx skills list -g) already recognises both
~/.agents/skills/ and ~/.claude/skills/ as valid global skill
directories. This commit aligns GSD-2's resolution logic with
that behaviour.
Affected functions:
- getSkillSearchDirs(): adds ~/.claude/skills/ and .claude/skills/
- captureAvailableSkills(): includes Claude Code dir in telemetry
- detectStaleSkills(): includes Claude Code dir in staleness checks
- detectNewSkills(): resolves SKILL.md from either directory
- isPackInstalled(): checks both dirs before recommending installs
- formatSkillDetail(): finds SKILL.md in either directory
The questionSignature() function only hashed sorted question IDs,
meaning calls with the same IDs but different text/options would
return stale cached answers. Now hashes the full canonicalized
payload (id, header, question, options, allowMultiple).
Adds 4 regression tests for signature correctness.
Add gsd_progress, gsd_roadmap, gsd_history, gsd_doctor, gsd_captures,
and gsd_knowledge tools that parse .gsd/ on disk — no session needed.
Inline lightweight readers in src/readers/ keep the package standalone
(zero new dependencies). 33 new tests, 64 total passing.
Address adversarial review findings:
1. [high] Override routing now requires an active auto-mode session
(in-process or remote via checkRemoteAutoSession) before writing
to a worktree path. Previously, any existing worktree directory
would receive the override even if no agent was running there —
a leftover worktree from a previous session would silently eat
the override.
2. [medium] Success messages now report the actual resolved override
location (worktree vs project root .gsd/OVERRIDES.md) so operators
know exactly where to look during recovery or manual rewrite.
Additional tests cover: inactive worktree fallback, double-gate
(autoRunning + valid .git), and getAutoWorktreePath null on missing .git.
Closes#3476
handleSteer used process.cwd() as the base path for appendOverride,
which writes to project/.gsd/OVERRIDES.md. When auto-mode runs in a
worktree, it reads from worktree/.gsd/ — so overrides written from a
second terminal were never seen by the agent.
Now checks for an active worktree via getAutoWorktreePath and writes
the override there when one exists, falling back to the project root
when no worktree is active.
Closes#3476
The codebase preferences block was accepted as a known key but never
validated or assigned in validatePreferences(), causing all user-configured
codebase defaults to be silently discarded. Adds validation for
exclude_patterns (string[]), max_files (positive int), and collapse_threshold
(positive int) with unknown-key warnings and 4 new tests.
Add configurable codebase map options via preferences.md (exclude_patterns,
max_files, collapse_threshold), expose --collapse-threshold as a CLI flag,
and auto-generate CODEBASE.md during project init for instant agent orientation.
Closes#3509
The ensureDbOpen catch block now logs via logWarning with error message
instead of structured diagnostic object. Update source-level assertion
to match the new pattern.
Update existing workflow-logger tests to use logError for audit
persistence assertions (warnings are now ephemeral). Add void
expression to empty catch blocks in detectMainBranch to satisfy
the no-empty-catch CI check.
Only persist error-severity entries to audit-log.jsonl (warnings stay
ephemeral in stderr + buffer). Sanitize persisted entries with message
truncation and context field allowlisting. Demote expected main/master
branch probe failures to silent control flow. Remove JSON.stringify of
diagnostic objects embedding cwd/paths in warning messages.
Addresses Codex adversarial review findings on workflow-logger migration.
workflow-events.ts: stop logging raw event line content to audit log —
log byte length only to avoid persisting potentially sensitive payload
fragments to .gsd/audit-log.jsonl.
parallel-orchestrator.ts: revert worker NDJSON parse failure to silent
drop — non-JSON lines (progress text, tool output) are expected in
worker stdout and logging each one creates I/O pressure and audit log
bloat in the parallel execution hot path.
Replace raw process.stderr.write(), console.error(), and empty catch
blocks across 50 GSD files with structured logWarning/logError calls
from the centralized workflow-logger system.
Add 13 new LogComponent types to cover all subsystems: recovery,
session, prompt, dashboard, timer, worktree, command, parallel, fs,
bootstrap, guided, registry, renderer.
Every migrated catch block now automatically:
- Shows in terminal (stderr) with component tag
- Gets buffered for auto-loop stuck-detection summary
- Persists to .gsd/audit-log.jsonl for post-mortem analysis
Update regression test to verify catch blocks use workflow-logger
instead of raw stderr/console, covering auto-mode files and all
explicitly migrated infrastructure files.
Closes#3506
Supersedes the approach in #3496
- Stop/backtrack guard now calls pauseAuto before marking captures executed,
and returns break on any exception to prevent silently dropping user halt intent
- Backtrack target parsing excludes current milestone ID and rejects ambiguous
multi-target strings instead of guessing first match
- Fixed gsd_skip_slice parameter names in rethink prompt (milestone_id → milestoneId)
Auto-mode has empty catch blocks across 11 files that silently
swallow errors. When these operations fail (DB writes, git commands,
file sync, worktree operations), the error is lost and downstream
systems see stale or inconsistent state — leading to stuck loops,
phantom milestones, and silent data loss.
Replace every empty catch with a process.stderr.write() call that
logs the operation context and error message. Format:
gsd [filename]: <operation> failed: <error.message>
For catches already annotated with /* non-fatal */ or /* best-effort */
comments, the logging is added alongside the annotation to preserve
the original intent while making failures observable.
Adds a regression test that scans all auto-mode source files and
asserts no empty catch blocks remain.
Files modified (11):
auto-worktree.ts, auto.ts, auto-recovery.ts, auto-prompts.ts,
auto-dashboard.ts, auto-start.ts, auto-timers.ts, auto-post-unit.ts,
auto-dispatch.ts, auto-unit-closeout.ts, auto/phases.ts
No behavioral changes — only diagnostic output added.
Addresses #3348, addresses #3345
Users with existing lsp.json overrides referencing the old
"kotlin-language-server" key would silently lose their Kotlin
LSP config after the rename to "kotlin-lsp". LEGACY_ALIASES
map remaps old keys during mergeServers() so overrides still
merge correctly.