10 tests that run against the installed gsd binary after npm publish:
1. headless query returns valid JSON
2. Empty project → pre-planning phase
3. Milestone with roadmap → planning phase
4. All tasks done → summarizing phase
5. Complete milestone → complete phase
6. Stale auto.lock doesn't block --version
7. Crash recovery query works with stale lock
8. Non-TTY exits quickly with clean error
9. Version skew detected before TTY check
10. --help works (native addon loads or falls back)
Wired into pipeline.yml test-verify job after fixture tests
and before @next promotion.
These catch the state machine / infrastructure bugs from #1308
that unit tests can't reach — they exercise deriveState through
the real gsd binary with real .gsd/ directory structures.
Part of #1308
Replaces 30+ inline `unitId.split("/")` + destructuring patterns across
16 production files with a single `parseUnitId()` helper that returns
`{ milestone, slice?, task? }`. If the unit ID format ever changes,
only one function needs updating.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The retry lock acquisition path (from stale lock recovery in #1251)
used a 5-minute stale threshold and no onCompromised handler, while
the primary path used 30 minutes and a graceful flag-based handler.
This mismatch meant locks acquired via the retry path would throw
ECOMPROMISED (uncaught, crashes process) if the event loop stalled
for >5 minutes — which happens during long LLM operations.
Fixed:
- Stale timeout: 300_000 → 1_800_000 (matches primary)
- Added onCompromised handler (sets _lockCompromised flag)
- Added process.on('exit') safety net (matches primary)
Also: reporter is on Node v25.6.1 which is unsupported — GSD requires
Node >=22.0.0 with 24 LTS recommended.
Fixes#1304
makeTreeWritable used statSync which follows symlinks. On NixOS and
nix-darwin, ~/.gsd/agent/bin/ contains symlinks to the immutable Nix
store (/run/current-system/sw/bin/). Attempting to chmod those targets
crashed GSD on startup with EPERM.
Changes:
- Use lstatSync instead of statSync — detects symlinks without
following them
- Skip symlinks entirely (they don't carry own permissions, targets
may be immutable)
- Added try/catch around chmodSync as safety net for any remaining
permission errors on unusual filesystems
Secondary analysis: rmSync with force:true already handles symlinks
correctly (removes the link, not the target). cpSync with force:true
replaces symlinks with regular files (desired behavior for resource
sync).
Fixes#1298
Consolidate the repeated `err instanceof Error ? err.message : String(err)`
pattern into a single `getErrorMessage(err)` utility. Reduces visual noise in
catch blocks across 20 files in the GSD extension.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: consolidate DB-fallback inline functions in auto-prompts
Extract shared inlineFromDbOrFile() helper that encapsulates the
repeated pattern of checking DB availability, dynamically importing
context-store, running a query, formatting results, and falling back
to the filesystem. The three public functions (inlineDecisionsFromDb,
inlineRequirementsFromDb, inlineProjectFromDb) become thin wrappers
that pass only the differing query/format logic as a callback.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: update source-level test to match refactored DB-fallback function name
The context-compression test greps auto-prompts.ts source for
`inlineGsdRootFile(base, "project.md"` which was replaced by
`inlineProjectFromDb(base)` in the consolidation refactor.
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(gsd): skip non-artifact UAT dispatch in auto-mode
Non-artifact-driven UATs (human-experience, live-runtime, mixed) were
dispatched only to write a "surfaced-for-human-review" verdict, which
then blocked the verdict gate and killed auto-mode progression. Auto
now only dispatches artifact-driven UATs it can actually execute.
- checkNeedsRunUat returns null for non-artifact-driven UAT types
- Remove pauseAfterDispatch flag (always artifact-driven now)
- Strip human-review template path from run-uat prompt
- Remove dead pause-after-UAT logic from auto.ts
- Add test for non-artifact UAT skip + stale replay guard
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: update buildRunUatPrompt call in direct dispatch after signature change
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add onCompromised handler to prevent uncaught throw in setTimeout
- Increase stale threshold from 5min to 30min for laptop sleep safety
- Release OS lock explicitly in SIGTERM handler
The test asserted that captureIntegrationBranch commits metadata to git,
but #1258 intentionally stopped committing .gsd/ artifacts.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
PR #1242 moved .gsd/ state to ~/.gsd/projects/<hash>/ with a symlink.
Git refuses to track files through symlinks, making commit_docs: true
fundamentally broken. Remove the preference and all conditional logic:
- .gsd/ is always gitignored (blanket ignore, no runtime-pattern approach)
- smartStage() always excludes .gsd/ from commits
- Prompt builders always say "do not commit planning artifacts"
- writeIntegrationBranch() writes metadata to disk without committing
- Init wizard no longer asks about commit_docs or bootstrap-commits .gsd/
- Validation emits a deprecation warning if commit_docs is still set
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds a new bundled extension that proactively checks and refreshes AWS
credentials for Bedrock model users.
Startup (session_start):
- Runs 'aws sts get-caller-identity' with the profile extracted from
the configured awsAuthRefresh command
- If credentials are expired, runs the refresh command (e.g. aws sso login)
before the user sends their first prompt
- Shows 'AWS Bedrock login confirmed ✓' when credentials are valid
Mid-session (before_provider_request):
- Re-verifies credentials every 15 minutes before Bedrock API calls
- Catches credential expiry during long sessions without needing retry logic
Zero changes to base files — the entire feature is a single extension file.
Only activates when awsAuthRefresh is set in settings.json and the current
model uses bedrock-converse-stream.
The git clean -fd .gsd/ added in #1239 was too aggressive — it could
remove untracked milestone and planning files on projects where .gsd/
isn't fully gitignored (e.g., manage_gitignore: false).
Replaced with explicit removal of only runtime state files:
- STATE.md, completed-units.json, auto.lock, gsd.db
- .gsd/runtime/ directory
Milestone directories, DECISIONS.md, REQUIREMENTS.md, PROJECT.md and
all other planning artifacts are never touched.
Fixes#1250
After discussing a slice, the LLM writes S0x-CONTEXT.md. The discuss
loop re-evaluates but hits stale parse caches, showing the slice as
'not discussed' even though the context file exists on disk.
Added invalidateAllCaches() at the top of each loop iteration.
Fixes#1244
The prose fallback parser only matched H2 (## S01:) headers with
colon/dash separators. LLMs produce many variants that silently
produced 0 slices, permanently blocking auto-mode.
Expanded the regex from:
/^##\s+(?:Slice\s+)?(S\d+)[:\s—–-]+\s*(.+)/gm
to:
/^#{1,4}\s+\*{0,2}(?:Slice\s+)?(S\d+)\*{0,2}[:\s.—–-]*\s*(.+)/gm
Now handles:
- H1 through H4 headers (# ## ### ####)
- Bold-wrapped: **S01: Title**, **S01**: Title
- Dot separator: S01. Title
- Space-only separator: S01 Title (no punctuation)
- Non-zero-padded IDs: S1, S01, S001
- No-space after colon: S01:Title
- All previous separators: colon, hyphen, em dash, en dash
Also strips trailing bold markers from titles and skips matches
with empty titles.
Fixes#1243
* fix: clean up stranded .gsd.lock/ directory to prevent false lock conflicts
Three fixes for stranded proper-lockfile lock directories:
1. releaseSessionLock: explicitly removes .gsd.lock/ after releasing
the OS lock and deleting auto.lock
2. acquireSessionLock: when lock acquisition fails, checks if auto.lock
is missing or the owning PID is dead. If so, removes the stale
.gsd.lock/ dir and retries acquisition instead of failing.
3. process.on('exit') handler: registered at lock acquisition time as
a safety net — cleans up .gsd.lock/ on normal process exit if
releaseSessionLock wasn't called.
Fixes#1245
* fix: move gsdDir declaration before try/catch to fix TS2304 scope error
gsdDir was declared inside the try block but referenced in the catch
block's retry logic, causing 'Cannot find name gsdDir' build failures.
* feat: add -w/--worktree CLI flag to start in an isolated worktree
Enables `gsd -w` to auto-create a randomly-named worktree (adjective-verbing-noun
pattern) and `gsd -w my-feature` for named worktrees. Reuses existing worktree
infrastructure under .gsd/worktrees/ with worktree/<name> branches.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: full worktree lifecycle — subcommands, auto-commit on exit, status banners
Major improvements to the -w/--worktree system:
- `gsd worktree list` — show worktrees with status (files changed, commits, dirty)
- `gsd worktree merge [name]` — squash-merge into main and clean up
- `gsd worktree clean` — remove all merged/empty worktrees
- `gsd worktree remove <name>` — remove with --force safety gate
- `gsd -w` (no name) resumes the only active worktree instead of creating a new one
- `gsd -w` with multiple active worktrees shows a picker
- Auto-commit dirty work on session exit (session_shutdown hook)
- Status banner on normal `gsd` launch when unmerged worktrees exist
- Full help text with lifecycle documentation (`gsd worktree --help`)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Every extension gets a declarative extension-manifest.json (id, tier,
provides, dependencies). A persistent registry at ~/.gsd/extensions/registry.json
tracks enabled/disabled state. `gsd extensions` command family (list, enable,
disable, info) lets users manage extensions without touching source code.
Registry gate filters disabled extensions in loader.ts and resource-loader.ts
before paths reach loadExtensions(). Zero breakage: extensions without manifests
default to enabled, fresh installs have an empty registry.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move mutable .gsd/ state from inside the project directory to
~/.gsd/projects/<repo-hash>/, replacing it with a symlink. All worktrees
share the same external state — eliminating the entire bidirectional
sync layer (~370 lines) that was the source of 15+ bug fixes.
Key changes:
- repo-identity.ts: repoIdentity(), externalGsdRoot(), ensureGsdSymlink()
- gsdRoot() resolves through symlinks via realpathSync
- migrate-external.ts: automatic migration with atomic rollback
- resource-version.ts: kept utilities from deleted sync module
- Worktree detection uses git metadata (.git file) instead of path parsing
- gitignore simplified to single .gsd entry
- Doctor checks for failed_migration and broken_symlink
Deleted: auto-worktree-sync.ts, copyWorktreeDb, reconcileWorktreeDb,
reconcilePlanCheckboxes, copyPlanningArtifacts, dual state derivation.
Net: -1271 lines across 38 files. 0 sync ops per dispatch cycle.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The `name` field in `src/resources/skills/react-best-practices/SKILL.md`
was set to `vercel-react-best-practices`, which does not match the parent
directory `react-best-practices`. This triggers a validation warning on
startup: 'name "vercel-react-best-practices" does not match parent
directory "react-best-practices"'.
Updated the name field to `react-best-practices` to match the directory.
Two changes:
1. checkNeedsRunUat now reads the verdict from UAT-RESULT. Only skips
re-running UAT when verdict is PASS/passed. Non-PASS verdicts (FAIL,
surfaced-for-human-review) no longer silently advance.
2. Added uat-verdict-gate dispatch rule between run-uat and
reassess-roadmap. When uat_dispatch is enabled, scans all completed
slices for non-PASS UAT verdicts and stops auto-mode if found.
This prevents advancing to the next slice when a UAT failed or
needs human review.
Fixes#1231
After milestone creation, the file parse caches could still hold
stale results from before the ROADMAP was written. /gsd discuss checks
for ROADMAP but hits the cache, gets null, and reports 'No roadmap yet'
even though the file exists on disk.
Added invalidateAllCaches() at the top of showDiscuss() to ensure
fresh reads from disk.
Fixes#1236
On Windows, executables like npx, tsc, and typescript-language-server
are .cmd batch scripts. Node.js's spawn() can't find them without
shell: true because it looks for exact binary names, not .cmd wrappers.
This caused ENOENT crashes during auto-mode when the LSP tried to
spawn npx tsc --noEmit for TypeScript diagnostics.
Added shell: true conditional on process.platform === 'win32' in the
LSP client's spawn call. Unix platforms are unaffected.
Fixes#1222