Commit graph

1870 commits

Author SHA1 Message Date
Tom Boucher
d21db9f398 fix(preferences): deduplicate unrecognized format warning on repeated loads (#2375)
parsePreferencesMarkdown emitted a console.warn every time preferences
were loaded with an unrecognized format, spamming stderr on each call
to loadEffectiveGSDPreferences. Gate the warning behind a warn-once
flag so it prints at most once per process.

Fixes #2373

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 22:06:37 -06:00
Jeremy McSpadden
e0b3bad2a5 feat(system-context): inject global ~/.gsd/agent/KNOWLEDGE.md into system prompt (#2331)
* feat(system-context): inject global ~/.gsd/agent/KNOWLEDGE.md into system prompt

Reads ~/.gsd/agent/KNOWLEDGE.md (global) alongside the existing project
.gsd/KNOWLEDGE.md and merges both into the [KNOWLEDGE] block. Global
section appears first so project entries can override or refine global
rules. Emits a startup warning when the global file exceeds 4 KB to
keep system prompt size in check.

Extracted loading logic into loadKnowledgeBlock() for testability.
Five new unit tests cover: empty state, project-only, global-only,
merged order, and size threshold.

Closes #2316

* fix(test): relax derive-state-db perf threshold from 1ms to 10ms

The <1ms assertion was intermittently failing on loaded CI runners
(observed: 1.054ms). 10ms still validates the in-memory cache path
is fast while being robust across shared CI environments.

---------

Co-authored-by: TÂCHES <afromanguy@me.com>
2026-03-24 22:03:00 -06:00
Tom Boucher
cace21cb02 docs(contributing): add testing standards section (#2441)
Codifies node:test patterns, cleanup hooks (beforeEach/afterEach vs
t.after() vs try/finally), template literal fixture guidance, and
test-first requirement for bug fixes. These standards reflect the
patterns established during the 10-PR test modernization effort.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 22:01:53 -06:00
Tom Boucher
17e172b466 fix: gate auto-mode bootstrap on SQLite availability (#2419) (#2421) 2026-03-24 21:37:19 -06:00
Tom Boucher
6409070225 fix: block /gsd quick when auto-mode is active (#2420) 2026-03-24 21:36:56 -06:00
Tom Boucher
3e68acfa11 docs: sync documentation with codebase through v2.44.0 (#2415) 2026-03-24 21:36:25 -06:00
Tom Boucher
b24594d79f refactor: migrate D-G test files from createTestContext to node:test (#2418) 2026-03-24 21:34:52 -06:00
Tom Boucher
e4d21c40d0 refactor(test): replace try/finally with beforeEach/afterEach in packages tests (#2390) 2026-03-24 21:34:10 -06:00
Tom Boucher
77460942ac refactor(test): migrate gsd/tests s-z from custom harness to node:test (#2397) 2026-03-24 21:33:39 -06:00
Tom Boucher
1fe52a2e8e refactor(test): migrate gsd/tests o-r from custom harness to node:test (#2401) 2026-03-24 21:33:17 -06:00
Tom Boucher
4498dcea32 refactor(test): migrate gsd/tests i-n from custom harness to node:test (#2399) 2026-03-24 21:33:01 -06:00
Tom Boucher
b1782a8678 refactor(test): migrate gsd/tests a-c from custom harness to node:test (#2400) 2026-03-24 21:32:26 -06:00
Tom Boucher
c237c56016 refactor(test): replace try/finally with t.after() in gsd/tests (e-i) (#2396) 2026-03-24 21:31:42 -06:00
Tom Boucher
2223298f76 refactor(test): replace try/finally with t.after() in gsd/tests (a-d) (#2395) 2026-03-24 21:31:29 -06:00
Tom Boucher
30775f4dcc refactor(test): replace try/finally with t.after() in src/tests (o-z) (#2392) 2026-03-24 21:30:29 -06:00
Tom Boucher
99af6b0315 refactor(test): replace try/finally with t.after() in src/tests (a-n) (#2394) 2026-03-24 21:30:00 -06:00
Lex Christopherson
ea0b1e4444 fix(ci): add Rust target for all platforms, not just cross-compilation
macOS x64 builds on ARM64 runners also need the target added explicitly.
Use rustup target add for all matrix entries to avoid Blacksmith's
target rewriting in dtolnay/rust-toolchain.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 16:22:36 -06:00
Lex Christopherson
b80cebfd4a fix(ci): restore Rust target triple and separate cross-compilation setup
Blacksmith migration (#2414) incorrectly rewrote the Rust target triple
aarch64-unknown-linux-gnu to the runner label blacksmith-4vcpu-ubuntu-2404-arm.
Restore the correct Rust target and split cross-compilation target addition
into an explicit rustup command.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 16:18:31 -06:00
Lex Christopherson
f33e27734b fix(ci): separate cross-compilation target from toolchain install
dtolnay/rust-toolchain resolves Blacksmith runner hostnames as Rust
targets on ARM64 runners. Split target addition into explicit rustup
command for cross-compilation builds.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 16:17:13 -06:00
github-actions[bot]
9e31a6985c release: v2.44.0 2026-03-24 22:09:37 +00:00
blacksmith-sh[bot]
c523d49590 Migrate workflows to Blacksmith (#2414)
Co-authored-by: blacksmith-sh[bot] <157653362+blacksmith-sh[bot]@users.noreply.github.com>
2026-03-24 15:58:30 -06:00
TÂCHES
ebfc63c42b fix: post-migration cleanup — pragmas, rollbacks, tool gaps, stale code (#2410)
* fix(gsd-db): add PRAGMA busy_timeout and foreign_keys

Concurrent worktrees sharing a WAL-mode DB get immediate SQLITE_BUSY
errors without a retry window. Add busy_timeout = 5000ms for file-backed
DBs. Enable foreign_keys per-connection so FK constraints declared in
the schema are actually enforced — prevents orphaned rows in slices,
tasks, verification_evidence, etc.

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

* fix(prompts): replace direct file writes with DB tool calls

plan-milestone.md single-slice fast path instructed mkdir + direct file
writes, bypassing gsd_plan_slice. discuss.md instructed writing
ROADMAP.md directly instead of calling gsd_plan_milestone. Both create
state where the DB has no knowledge of planning artifacts.

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

* fix(recover): wrap delete + repopulate in single transaction

handleRecover deleted hierarchy rows inside a transaction, then called
migrateHierarchyToDb() outside it. A crash mid-repopulate left a
partially populated DB. Wrap both operations in one dbTransaction()
call for atomicity.

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

* fix(tools): add rollback on render failure

plan-milestone and plan-slice committed DB transactions then rendered
markdown — if rendering failed, DB had planning data with no file on
disk. db-writer functions (saveDecisionToDb, updateRequirementInDb,
saveArtifactToDb) had the same issue: DB upsert before disk write with
no rollback. Add rollback logic matching the complete-task.ts pattern.

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

* feat(gsd): add gsd_complete_milestone tool and rogue detection gaps

Task and slice completion had DB-backed tools but milestone completion
used direct file writes. Add gsd_complete_milestone following the same
pattern: validate all slices complete, update DB status in transaction,
render SUMMARY.md, rollback on failure.

Extend detectRogueFileWrites() to cover reassess-roadmap (ASSESSMENT.md),
plan-task (T##-PLAN.md), and REPLAN.md — previously undetected bypass
paths.

Replace regex checkbox fallback in retry state-reset with explicit
failure + stderr log. Direct markdown mutation on DB-unavailable
reintroduced the pattern the migration eliminated.

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

* chore(gsd): update stale comments, add legacy markers, DB-first queries

- state.ts: update module header and deriveState docstring to reflect
  DB-primary architecture. Add DB-first query to getActiveMilestoneId()
  with filesystem fallback. Add LEGACY marker on _deriveStateImpl().
- commands-maintenance.ts: add DB query before parseRoadmap() for stale
  branch cleanup.
- prompts: replace "toggles the checkbox" language with DB-accurate
  descriptions in execute-task.md and complete-slice.md.
- auto-recovery.ts: add LEGACY markers on !isDbAvailable() fallback
  branches.
- gsd-db.ts: add DEAD CODE annotations on sequence column definitions
  (no tool exposes sequence — always defaults to 0).

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

* fix(tools): preserve DB rows on render failure instead of rolling back

The plan-milestone and plan-slice handlers were rolling back DB rows when
file rendering failed, destroying parse-visible state needed for debugging.
DB rows now persist on render failure. Also guard rollback references to
non-existent tables (slice_planning, task_planning, milestone_planning).

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-24 15:55:26 -06:00
Jay The Reaper
bc278d12d9 feat(core): support for 'non-api-key' provider extensions like Claude Code CLI (#2382)
* feat(core): add generic native post-install hooks for package install

* feat(core): add before/after install/remove lifecycle hooks

* refactor(core): remove postInstall alias from lifecycle hook fallback

* feat(core): complete authMode support for keyless providers

The initial authMode implementation fixed model-registry, sdk, and
fallback-resolver but missed agent-session.ts (6 callsites) and
compaction-orchestrator.ts (2 callsites) that block externalCli
providers at runtime.

Architecture: separate readiness gating from credential retrieval.
- isProviderRequestReady(): authMode-aware readiness check
- getApiKey()/getApiKeyForProvider(): return undefined for
  externalCli/none providers instead of triggering auth errors
- All 8 callsites in agent-session and compaction-orchestrator
  now gate on readiness, not key presence
- Downstream signatures (compaction, branch-summarization) accept
  apiKey: string | undefined
- Replaced hardcoded ollama exception in discoverModels with
  isProviderRequestReady

Zero behavioral change for classic apiKey/oauth providers.

* feat(core): add isReady callback for provider readiness verification

Extensions can now provide an isReady() callback when registering any
provider. isProviderRequestReady() calls it before default auth checks,
allowing providers to verify actual reachability (CLI authenticated,
API key valid, service online) rather than relying solely on credential
presence.

* test(core): expand authMode test coverage

Cover all four auth modes (apiKey, oauth, externalCli, none),
isReady callback behavior, getProviderAuthMode defaults,
isProviderRequestReady for each mode, getAvailable filtering,
and getApiKey early-return for keyless providers.

* chore: remove provider-api-bridge files from this branch

These files implement GSD core → provider-api wiring (deps + tool
registry) and belong in a separate PR. Reverts register-extension.ts
to upstream state.
2026-03-24 15:50:12 -06:00
Lex Christopherson
dae38f797e fix(test): normalize CRLF in auto-stash-merge assertion for Windows
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 15:16:03 -06:00
Lex Christopherson
adfea1769d fix(test): swallow EPERM on Windows temp dir cleanup in auto-stash-merge test
Windows CI runners hold git file locks that prevent rmSync from removing
temp repos in finally blocks. Wrap cleanup in try-catch so a cleanup
failure doesn't fail the actual test.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 15:08:06 -06:00
Lex Christopherson
cc48cc9435 fix(gsd): add file-based fallbacks for DB-dependent code paths and fix CI test failures
The DB-backed planning migration (#2280) moved 6 core modules to DB-primary
queries but left no fallback when DB is unavailable, breaking 19 tests in CI.

Source fixes: add file-based fallbacks in auto-direct-dispatch, auto-prompts,
auto-worktree, dispatch-guard, reactive-graph, visualizer-data, workspace-index,
and skill-health. Windows fixes: CRLF normalization, EPERM retry on rmSync,
path normalization. Enable --experimental-test-isolation=process to prevent
cross-test DB state leakage.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 14:59:56 -06:00
Tom Boucher
67f47bea06 feat(docker): add official Docker sandbox template for isolated GSD auto mode (#2360)
Ship a Dockerfile.sandbox, docker-compose.yml, .env.example, and docs so
users can run GSD auto mode inside an isolated Docker sandbox (MicroVM)
without risk to the host filesystem, SSH keys, or other projects.

- Dockerfile.sandbox: Node 22 base, gsd-pi pre-installed, non-root user, port 3000
- docker-compose.yml: workspace volume mount, persistent .gsd state, env_file support
- .env.example: template for LLM provider keys and optional tool credentials
- docker/README.md: setup guide covering sandbox CLI, Compose, two-terminal workflow,
  credential injection, and network allowlisting
- .dockerignore: project-root ignore file for efficient Docker builds
- src/tests/docker-template.test.ts: 13 structural tests verifying all template files

Fixes #1544

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 13:57:59 -06:00
Jeremy McSpadden
8922f763ef ci(security): add base64-encoded directive scan to lint job (#2371)
Adds scripts/base64-scan.sh and a corresponding CI step to detect
prompt injection payloads that are base64-encoded to evade the existing
docs-prompt-injection-scan.sh check.
2026-03-24 13:34:25 -06:00
Jeremy McSpadden
e5138c86df docs(contributing): define execution-based review validation standard (#2364)
* docs(contributing): define execution-based review validation standard

Expand the Review process section to make explicit that reviewers are
expected to build and run tests locally — not just read the diff. Also
codifies what contributors must provide (regression tests for bug fixes,
failure-path tests for features) to unblock review.

Previously the section offered only logistics (PR size, response etiquette).
This adds the missing standard for what "reviewed" actually means.

* docs(contributing): add worktree checkout as explicit reviewer step 0

The prior commit defined build + test execution as the review standard
but omitted the prerequisite: checking out the branch locally before
reviewing. Without it the list implied reviewers could run commands
without having the branch. Also adds the closing line that correctness
claims require completing all five steps.
2026-03-24 13:33:51 -06:00
Lex Christopherson
c5c75b0273 fix(gsd): remove stale observabilityIssues reference in journal-integration test
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 13:29:07 -06:00
Lex Christopherson
976b7459e2 Merge branch 'next' into main — DB-backed planning tools (#2280, #2370)
PR #2280: Tool-driven planning state capture
- DB-backed planning tools (gsd_plan_milestone, gsd_plan_slice, gsd_plan_task,
  gsd_replan_slice, gsd_reassess_roadmap, gsd_complete_task, gsd_complete_slice)
- Schema v8-v10 with planning columns on milestones, slices, tasks tables
- Markdown renderer generates files from DB (DB is source of truth)
- Structural enforcement: rejects mutations to completed work
- Rogue file detection for planning unit types

Additional fixes included:
- fix(gsd): remove stale observability validator (false-positive warnings)
- fix(gsd): greenfield worktree check (warning instead of hard stop)
- fix(gsd): prevent planning data loss from destructive INSERT OR REPLACE (#2370)
- fix(gsd): remove post-unit migrateFromMarkdown hook (was overwriting DB data)
- fix(gsd): remove vestigial prompt instructions conflicting with tool workflow
- fix(gsd): remove Steps section duplication in task plan renderer

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 13:23:22 -06:00
Lex Christopherson
fa376bf816 merge: incorporate main into next (resolve 26 conflicts)
Merges 39 commits from main into next, including:
- WAL/journal runtime exclusion fixes (#2299)
- Memory and resource leak fixes (#2314)
- Freeform DECISIONS.md preservation (#2319)
- Per-prompt token cost display (#2357)
- Web UI project root switching (#2355)
- CODEOWNERS and team workflow docs (#2286)
- CI flake threshold fix (#2327)
- Various other bugfixes

All conflicts resolved preserving both PR #2280 DB-backed planning
functionality and main's bugfixes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 13:21:19 -06:00
Tom Boucher
ab0bb9dece fix(extensions): detect TypeScript syntax in .js extension files and suggest renaming to .ts (#2386)
When a user creates a .js extension file but writes TypeScript syntax in it,
the loader now detects common TS patterns (type annotations, interfaces, enums,
generics) and provides a clear error message suggesting to rename the file to
.ts, instead of the previous cryptic "Extension does not export a valid factory
function" or opaque jiti parse errors.

Fixes #2381

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 13:12:36 -06:00
Lex Christopherson
44ebe47c83 2.43.0-next.7 2026-03-24 09:52:34 -06:00
Lex Christopherson
651b77bf5f fix(gsd): prevent planning data loss from destructive upsert and post-unit re-import (#2370)
insertTask() used INSERT OR REPLACE which in SQLite does DELETE + INSERT,
zeroing planning columns (description, estimate, inputs, expected_output)
when callers like handleCompleteTask didn't pass them. Changed to
ON CONFLICT ... DO UPDATE SET with CASE/NULLIF preservation for planning
columns.

Removed post-unit migrateFromMarkdown hook that re-imported a lossy
markdown subset after every auto-mode unit, overwriting DB planning data.
Startup migration in auto-start.ts and dynamic-tools.ts remains.

Removed vestigial "MUST write file" prompt instructions that conflict with
the DB-backed tool workflow. Removed Steps section duplication in task plan
renderer that re-rendered description as garbled bullets.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 09:52:23 -06:00
Lex Christopherson
ef9a38c802 2.43.0-next.6 2026-03-24 07:43:26 -06:00
Lex Christopherson
cfc377fd9b fix(gsd): use correct notify severity type ("warning" not "warn")
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 07:43:17 -06:00
Jeremy McSpadden
9153506fba chore(contrib): add CODEOWNERS and team workflow docs (#2286)
* chore(contrib): add commit-msg hook, CODEOWNERS, team workflow docs

- Extend install-hooks.sh with commit-msg hook that enforces
  Conventional Commits format on every commit
- Add .github/CODEOWNERS mapping packages, CI, scripts, and
  security-sensitive files to @gsd-build/maintainers
- CONTRIBUTING.md: add Branching and commits section with naming
  convention, commit format, and rebase guidance
- CONTRIBUTING.md: add Working with GSD section covering mode: team,
  unique milestone IDs, and worktree isolation for multi-dev workflows
- CONTRIBUTING.md: surface npm run secret-scan:install-hook in Local
  development with explanation of both hooks it installs
- CONTRIBUTING.md: align AI disclosure section — no AI tool authorship
  in commits, Draft PR requirement for multi-phase agent work

* chore: remove install-hooks.sh — local git hook installation is too intrusive for a contributor PR
2026-03-24 07:35:40 -06:00
Tom Boucher
7a413bb84f fix(web): resolve compiled .js modules for all subprocess calls under node_modules (#2320)
Node v24 unconditionally refuses .ts files under node_modules/ — even
with --experimental-transform-types. When GSD is installed globally via
npm, every web service subprocess that loads a .ts extension module
crashes with ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING.

Add resolveSubprocessModule() and buildSubprocessPrefixArgs() to
ts-subprocess-flags.ts. When packageRoot is under node_modules/ and the
compiled dist/*.js file exists, subprocess calls use the compiled JS
directly without TS flags or the resolve-ts.mjs loader.

Updated all 14 web service files: auto-dashboard, bridge, captures,
cleanup, doctor, export, forensics, history, hooks, recovery-diagnostics,
settings, skill-health, undo, and visualizer.

Fixes #2279

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 07:34:41 -06:00
Tom Boucher
a0c0896a75 fix(test): increase perf assertion threshold to prevent CI flake (#2327)
The `deriveStateFromDb() <1ms` assertion failed at 1.050ms on GitHub
Actions runners under load. Increased threshold to 10ms — still catches
real regressions (10x) without flaking on CI jitter.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 07:32:42 -06:00
mastertyko
7ecf87829d fix: add missing SQLite WAL sidecars and journal to runtime exclusion lists (#2299)
gsd.db-shm, gsd.db-wal, journal/, and doctor-history.jsonl are always
created alongside gsd.db during normal operation but were missing from
both RUNTIME_EXCLUSION_PATHS (git-service.ts) and GSD_RUNTIME_PATTERNS
(gitignore.ts). This caused them to be staged by nativeAddAllWithExclusions,
left untracked by untrackRuntimeFiles, and omitted from .gitignore by
ensureGitignore — leading to squash merge failures when these files were
tracked and modified during milestone execution.

Closes #2296
2026-03-24 07:31:48 -06:00
Lex Christopherson
dd96ad3002 2.43.0-next.5 2026-03-24 07:30:49 -06:00
Lex Christopherson
7ca3ce04a4 fix(gsd): remove stale observability validator + fix greenfield worktree check
The observability validator checked for markdown headings (## Observability / Diagnostics,
## Observability Impact) that the DB-backed renderer never produces, causing false-positive
warnings on every dispatch. Removed entirely — the DB schema enforces structure at write time.

The worktree health check blocked execution in directories without recognized project files
(package.json, Cargo.toml, etc.), preventing greenfield projects from scaffolding. Downgraded
to a warning — .git check remains as the hard gate.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 07:27:48 -06:00
Jeremy McSpadden
867a4be297 fix(memory): fix memory and resource leaks across TUI, LSP, DB, and automation (#2314)
* fix(memory): fix memory and resource leaks across TUI, LSP, DB, and automation

Addresses all findings from a systematic memory leak audit across five
dimensions: event listeners, timers, file system handles, subscriptions/
closures, and GSD automation lifecycle.

Critical fixes:

rpc-client.ts: stderr .on("data") handler attached in start() was never
removed in stop(). Now stored as _stderrHandler and removed via
removeListener() on stop.

lsp/client.ts: Three process.on() handlers (beforeExit, SIGINT, SIGTERM)
registered at module load time with anonymous functions — impossible to
remove. Now stored as named references; new removeProcessHandlers() export
allows graceful teardown. stdout/stderr stream listeners in
startMessageReader/startStderrReader also stored per-client in
clientStreamHandlers map and removed in shutdownClient() and shutdownAll().

parallel-orchestrator.ts: spawnWorker() attached 5 listeners to child
process streams on every spawn with no removal on worker stop/respawn,
accumulating listeners indefinitely. Added cleanup() field to WorkerInfo;
called via removeAllListeners() on exit, graceful stop, stale detection,
and dead PID cleanup paths. Also: module-level state.workers Map was never
cleared between orchestration runs; startParallel() and resetOrchestrator()
now iterate and clean up all WorkerInfo entries before reassigning state.

scripts/watch-resources.js: fs.watch() return value was discarded (OS
watcher never closed) and the fallback setInterval handle was also
discarded (timer ran forever). Both now stored; process.on("exit") handler
closes/clears them.

gsd-db.ts: closeDatabase() did not checkpoint the WAL before closing —
.db-shm/.db-wal files accumulated on disk across crash-recovery cycles.
Now runs PRAGMA wal_checkpoint(TRUNCATE) before close. Also added a
one-time process.on("exit") handler in openDatabase() so the handle is
always closed even on unclean exits.

Medium fixes:

bg-shell/overlay.ts: 1-second refresh setInterval only cleared in
keyboard exit handler; abnormal teardown leaked the timer. Added dispose()
method that unconditionally clears it.

file-watcher.ts: pending debounce Map was scoped inside startFileWatcher()
making it inaccessible to stopFileWatcher(). Moved to module scope;
stopFileWatcher() now clears all pending timers and empties the map before
closing the watcher.

auto-supervisor.ts: registerSigtermHandler() could accumulate multiple
SIGTERM handlers if called without passing back the previous reference.
Added module-level _currentSigtermHandler; old handler is always removed
before registering the new one regardless of whether caller passes it.

Low-severity fixes:

print-mode.ts: session.subscribe() return value was discarded. Now stored
and called in a finally block to guarantee cleanup on both normal
completion and errors.

rpc-mode.ts: same — subscribe() unsubscribe now called in the shutdown
path before process.exit().

theme.ts: onThemeChangeCallback singleton silently overwrote any previous
subscriber. Converted to Set<() => void>; onThemeChange() now returns a
cleanup function. All four internal call sites updated to forEach().
Backward-compatible — existing callers that discard the return are unaffected.

* fix: ensure unsubscribe is called on error/abort in print-mode

The PR #2314 added unsubscribe storage but still called process.exit(1)
directly, bypassing the unsubscribe. Wrapped in try/finally to guarantee
cleanup runs before exit.
2026-03-24 07:23:36 -06:00
Tom Boucher
30daeeb8f4 fix(gsd): preserve freeform DECISIONS.md content on decision save (#2319)
`saveDecisionToDb` previously regenerated DECISIONS.md from DB state
unconditionally, which silently destroyed any freeform/prose content
since `parseDecisionsTable` only parses table rows.

Now detects whether the existing file is in canonical table format
(starts with "# Decisions Register" + has the standard table header).
When freeform content is detected, the original content is preserved
and a decisions table section is appended/updated at the end instead
of overwriting the entire file.

Fixes #2301

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 07:23:11 -06:00
Tom Boucher
6793489b78 fix(pi-ai): restore alibaba-coding-plan provider via models.custom.ts (#2350)
The alibaba-coding-plan provider (8 models) was silently dropped when
models.generated.ts was regenerated from models.dev in PR #2118. This
provider uses a proprietary DashScope endpoint not tracked by models.dev,
so regeneration removes it every time.

Add models.custom.ts for manually-maintained providers that don't exist
in models.dev. The model registry (models.ts) now merges both generated
and custom models at startup. Custom entries are additive and never
overwrite generated ones.

Restores: qwen3.5-plus, qwen3-max-2026-01-23, qwen3-coder-next,
qwen3-coder-plus, MiniMax-M2.5, glm-5, glm-4.7, kimi-k2.5

Fixes #2339

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 07:19:27 -06:00
Tom Boucher
eb30d3afd4 feat(gsd): show per-prompt token cost in footer behind show_token_cost preference (#2357)
Adds opt-in per-prompt cost display to the interactive footer. Users
enable it by setting `show_token_cost: true` in their preferences.md.
Disabled by default — the footer behavior is unchanged unless opted in.

Fixes #1515

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 07:18:57 -06:00
Tom Boucher
21f66058ad feat(web): add "Change project root" button to web UI (#2355)
Adds a visible control to change the devRoot directory from both the
project selection gate and the slide-out projects panel, so users no
longer need to hand-edit ~/.gsd/web-preferences.json.

- New /api/switch-root POST endpoint: validates path (exists, is dir),
  persists to web-preferences.json (clearing lastActiveProject), and
  returns discovered projects under the new root
- ProjectSelectionGate: shows current devRoot with "Change" link above
  the project list; also shows "Change project root" link when no
  projects are found under the current root
- ProjectsPanel: shows "Change" link next to the devRoot path in the
  slide-out header
- Both views use the existing FolderPickerDialog for directory browsing
- 17 tests covering path validation, preference persistence, tilde
  expansion, and end-to-end switch scenarios

Fixes #2264

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 07:18:05 -06:00
Tom Boucher
57c4939bee fix(doctor): skip false env_dependencies error in auto-worktrees (#2318)
* fix(test): increase perf assertion threshold to prevent CI flake

The `deriveStateFromDb() <1ms` assertion failed at 1.050ms on GitHub
Actions runners under load. Increased threshold to 10ms — still catches
real regressions (10x) without flaking on CI jitter.

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

* fix(doctor): skip false env_dependencies error in auto-worktrees

Auto-worktrees don't have their own node_modules by design — they
symlink to the project root's copy.  The doctor environment check
now resolves the project root (via .gsd/worktrees/ path segment or
GSD_WORKTREE env var) and checks its node_modules before reporting
an error.

Fixes #2303

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-24 07:17:52 -06:00
mastertyko
865dae2462 fix(gsd): auto-stash dirty files before squash merge and surface dirty filenames in error (#2298)
* fix: auto-stash dirty files before squash merge and surface dirty filenames in error

Two bugs in mergeMilestoneToMain caused milestone completion to fail when
the project root had pre-existing dirty tracked files:

Bug 1 — No auto-stash: clearProjectRootStateFiles only removes untracked
.gsd/ files. Any tracked dirty file elsewhere (e.g. .planning/work-state.json
with stash conflict markers) caused `git merge --squash` to reject with
"local changes would be overwritten". Fixed by adding a stash/pop wrapper
around the squash merge — dirty files are stashed before merge and restored
after commit. Stash is also popped on all error paths so local work is never
lost.

Bug 2 — Misleading error message: nativeMergeSquash discarded the filenames
from git stderr and the caller hardcoded blame on .gsd/ regardless of which
files were actually dirty. Fixed by parsing tab-indented filenames from git
stderr into a new `dirtyFiles` field on GitMergeResult, and surfacing them
in the error message.

Closes #2151

* ci: re-trigger CI (derive-state-db perf assertion is nondeterministic on slow runners)

* review: move #2151 tests to node:test format in separate file

Per review feedback, moved Tests 20 and 21 from the script-style
auto-worktree-milestone-merge.test.ts into a new auto-stash-merge.test.ts
using node:test's test() function and assert module.
2026-03-24 07:17:26 -06:00