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>
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>
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>
* 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>
* 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.
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>
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>
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>
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.
* 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.
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>
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>
* 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
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>
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>
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
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>
* 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.
`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>
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>
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>
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>
* 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>
* 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.
Local tsconfig excludes src/resources/ but CI compiles everything.
Record<string, unknown> for params broke handler calls since handlers
expect typed params (validated at runtime). Keep params: any with
eslint-disable annotation, type all other executor params properly.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Tool executor lambdas now use proper types (string, Record<string, unknown>,
AbortSignal | undefined) instead of any for all parameters.
registerAlias toolDef param also properly typed.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- github-sync/sync.ts: import parseRoadmap/parsePlan from parsers-legacy
- auto-worktree.ts: replace dangling roadmap.title with getMilestone() DB query
- markdown-renderer.ts: add explicit type annotations on lazy-loaded parser callbacks
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>