- Add unitType/unitId round-trip test for paused-session metadata.
- Add backward-compatibility test for legacy metadata without
unitType/unitId fields.
- Set resourceVersionOnStart on paused-session resume so resource
staleness detection works for resumed sessions.
- Re-register setLevelChangeCallback on resume so health-level
transition notifications fire after process restart.
- Persist unitType/unitId in paused-session metadata and restore them
on resume so recovery synthesis framing text shows the actual unit
instead of "unknown"/"unknown".
- Check worktreePath existence before showing "(worktree)" in resume
notification to avoid misleading the user when the worktree was
already torn down.
- Fix showDiscuss skip_milestone to use step:false (matching
discuss_draft and discuss_fresh) instead of hardcoded step:true.
- Initialize autoStartTime, originalModelId, originalModelProvider, and
autoModeStartModel on file-based paused-session resume path so
dashboard elapsed time, model restore on stop, and error-recovery
model fallback all work correctly.
- Validate worktreePath existence before using it as assessment base;
fall back to project root when the worktree has been torn down.
- Add tests for paused-session-only (no lock) with resumable disk state
and for worktreePath fallback when the directory no longer exists.
- Re-derive state from project root after stale/recoverable cleanup in
guided flow; the assessment may have derived state from a worktree
path that was cleaned up, leading to stale state being used downstream.
- Add test for the "none" classification path (clean startup with no
lock and no paused session) to close coverage gap.
- Remove redundant `void verboseMode` suppression (parameter is used at line 364)
- Simplify hasStrongRecoverySignal: `hasResumableDiskState || recoveryToolCallCount > 0`
(the previous expression had a redundant third disjunct)
- Remove extra blank line in auto.ts between stepMode assignment and lock-clear block
Unify dispatch rules and hooks into a flat rule registry, add structured event journal with causal tracing, expose journal query as an LLM tool, and adopt gsd_concept_action tool naming.
- RuleRegistry class absorbs dispatch rules + hooks into UnifiedRule objects with common when/where/then shape
- post-unit-hooks.ts refactored from 524 lines → 90-line thin facade delegating to the registry
- Event journal emits structured JSONL events with per-iteration flowId grouping and causedBy chains
- gsd_journal_query LLM-callable tool for AI self-debugging of autonomous runs
- 4 DB tools renamed to gsd_concept_action pattern with backward-compatible aliases
- 164 new tests, zero regressions
Closes#1763, closes#1764, closes#1766
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Use paused-session worktree metadata when deriving interrupted-session state so
worktree isolation decisions follow the authoritative disk state without
changing /gsd discuss auto handoff semantics.
Remove a dead running-state comparison after the early return in startAuto so
extension typechecking passes alongside the interrupted-session regressions.
syncProjectRootToWorktree used cpSync defaults which overwrote worktree-authoritative
files (VALIDATION.md, SUMMARY.md). This caused validate-milestone to loop infinitely
because its output got clobbered each iteration. Additionally, completed-units.json
was never forward-synced from project root to worktree, so after crash recovery the
worktree re-dispatched already-completed units.
- Add `{ force: false }` to safeCopyRecursive in syncProjectRootToWorktree so
existing worktree files are never overwritten (additive-only copy).
- Add forward-sync of completed-units.json from project root to worktree with
`{ force: true }` (project root is authoritative for completion state).
- Add regression tests covering both bugs and edge cases.
Fixes#1886
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The standalone Next.js bundle bakes import.meta.url at build time with
the Linux CI runner's absolute path. On Windows, fileURLToPath() rejects
the Unix file:// URL at module load time, crashing all API routes with
ERR_INVALID_FILE_URL_PATH before GSD_WEB_PACKAGE_ROOT can be checked.
Replace the eager top-level const with a lazy getter that:
1. Defers evaluation until GSD_WEB_PACKAGE_ROOT is actually absent
2. Catches the cross-platform fileURLToPath failure gracefully
3. Falls back to process.cwd() when the baked-in URL is invalid
4. Caches the result so the computation only runs once
Add regression tests verifying:
- GSD_WEB_PACKAGE_ROOT is used when set
- Lazy fallback returns a valid absolute path without throwing
- Memoization is stable across calls
- Module loads without crash (the original failure mode)
Closesgsd-build/gsd-2#1881
* feat: add made_by attribution field to decisions (human/agent/collaborative)
Add a 'made_by' field to the Decision type that tracks whether a
decision was made by the human, the agent, or collaboratively. This
enables ADR-style accountability — you can always tell who actually
made each call.
Schema:
- New DecisionMadeBy type: 'human' | 'agent' | 'collaborative'
- DB schema v3 → v4: ALTER TABLE decisions ADD COLUMN made_by
- Existing decisions default to 'agent' (backward compatible)
- DECISIONS.md gains a 'Made By' column
- Parser handles old 7-column format gracefully (defaults to 'agent')
Surfaces updated:
- gsd_save_decision tool accepts optional made_by parameter
- Markdown generator/parser round-trips the new column
- Prompt formatter shows attribution in LLM context
- Compact formatter includes made_by in pipe-separated output
- Worktree reconciliation includes made_by in conflict detection + merge
Tests: 476 assertions across 9 test suites, all passing.
* fix(gsd-db): resolve CI failures and address review findings
- Update memory-store.test.ts to expect schema version 4
- Recreate active_decisions view in v4 migration to pick up new made_by column
- Handle missing made_by column in older worktrees during reconciliation
- Optimize VALID_MADE_BY Set by moving it outside the parser loop
* fix(types): resolve missing made_by property errors in context-store and tests
Next.js 16 auto-detects web/proxy.ts as middleware, gating all /api/*
routes behind bearer token validation. The token was only cached in
memory (lost on page refresh) and extracted from the URL hash fragment
(cleared after first extraction). This caused 401 errors on page
refresh and broke the sendBeacon shutdown call which cannot set
custom headers.
Changes:
- Persist the auth token to sessionStorage after extracting from the
URL fragment so it survives page refreshes within the same tab
- Fall back to sessionStorage when the URL hash is absent (refresh,
bookmark without hash)
- Pass the auth token as a _token query parameter in the sendBeacon
shutdown call since sendBeacon cannot set Authorization headers
- Add regression tests for token persistence, sessionStorage fallback,
and sendBeacon authentication
Fixes#1851
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Several test files used assert.ok(Array.isArray(x)) or assert.ok(result)
patterns that verify structure/existence without checking actual values.
These pass even when the code returns wrong data.
- web-diagnostics-contract: Array.isArray() checks → deepEqual([], [])
for fields constructed as empty; DoctorFixResult uses deepEqual(["fix1"])
instead of Array.isArray + length; InstanceType<typeof GSDWorkspaceStore>
for type assertions from dynamic import
- skill-lifecycle: computeStaleAvoidList → deepEqual(result, []) since
nonexistent path must return empty
- blob-store: remove redundant assert.ok(retrieved) before deepEqual
- discovery-cache: assert.ok(entry) existence check → verify models[0].id
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Three changes to prevent data loss and persistent doctor errors in the
worktree merge-back lifecycle:
1. After nativeCommit in mergeMilestoneToMain, explicitly delete
.git/SQUASH_MSG. The native libgit2 path and git commit -F - on
some versions do not auto-remove it, causing doctor to report
corrupt_merge_state on every run.
2. Before worktree removal (step 11), check for uncommitted changes
and force a final auto-commit if dirty. This prevents code files
written by task agents from being destroyed by git worktree remove.
3. Invalidate the nativeHasChanges 10-second cache before the
post-unit auto-commit in auto-post-unit.ts. A stale false result
causes autoCommit to skip staging entirely.
Fixes#1853
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The buildRecoveryContext callback in auto/phases.ts returned an empty
object instead of a valid RecoveryContext. When the idle watchdog detected
a stalled tool and called recoverTimedOutUnit, basePath was undefined,
causing join(undefined, ".gsd") to throw "The path argument must be of
type string. Received undefined". The error left the session permanently
hung because the unit promise was never resolved.
Fixes#1855
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When .gsd/ is a symlink to an external state directory, git registers
worktrees at the resolved (real) path. If syncStateToProjectRoot later
creates a real .gsd/ directory that shadows the symlink, worktreePath()
computes a local path that diverges from git's registered path. The
stale local directory passes existsSync but is not a git worktree, so
nativeWorktreeRemove fails silently.
removeWorktree now queries nativeWorktreeList to find the actual
git-registered path by matching on branch name before attempting
removal, falling back to the computed path if the lookup fails.
Fixes#1852
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Node v24 forbids --experimental-strip-types for files under node_modules/.
When GSD is globally installed, all src/ files live under node_modules/gsd-pi/,
causing every subprocess worker to crash with ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING.
Bug 1: Extract resolveTypeStrippingFlag() into src/web/ts-subprocess-flags.ts.
When the package root is under node_modules/ and Node >= 22.7, the function
returns --experimental-transform-types (which handles node_modules paths).
All 15 service files and cli-entry.ts now call this function instead of
hardcoding --experimental-strip-types.
Bug 2: waitForBootReady() now tracks consecutive 5xx responses and aborts
after 3 in a row, including the response body in the error message.
Connection-level errors (transient during cold start) reset the counter.
Bug 3: The /api/boot route handler now wraps collectBootPayload() in
try/catch and returns { error: message } with status 500, matching the
error response pattern used by other API routes.
Fixes#1849
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add `/gsd fast [on|off|flex|status]` command for toggling OpenAI service
tiers, with `supportsServiceTier()` gating so the status bar icon only
appears on models that actually support service tiers (gpt-5.4 variants).
Fixes#1848
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- preferences.test.ts: hook config tests were testing Math.max/min and a
locally-constructed Set (testing JS builtins, not production code); replaced
with validatePreferences calls that exercise real clamping in
preferences-validation.ts and action validation for pre_dispatch_hooks.
assert.ok(prefs) existence checks replaced with assert.notEqual(prefs, null).
- routing-history.test.ts: removed assert.ok(history) and assert.ok(pattern)
guards that only verified object existence; assertions now go directly to
the values that matter.
* fix(auto): use PROJECT_FILES from detection.ts in worktree health check
The worktree health check introduced in #1833 hard-coded package.json
and src/ as the only valid project markers, blocking auto-mode dispatch
for Rust (Cargo.toml), Go (go.mod), Python (pyproject.toml), and 14
other ecosystems. Replace the JS-centric heuristic with the shared
PROJECT_FILES array from detection.ts which already covers 17+
ecosystems.
Fixes#1843
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: update test assertion to match new project files message
The health check now says "no recognized project files" instead of
"no package.json or src/" after broadening to PROJECT_FILES.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When tasks are [x] done but no T##-SUMMARY.md exists, doctor unchecks
the tasks but left the slice [x] done in the roadmap. The state machine
skips done slices, so unchecked tasks never re-execute and doctor fires
again on every start — infinite loop.
After unchecking tasks via task_done_missing_summary, also uncheck the
slice in the roadmap so the state machine re-enters the executing phase.
Fixes#1850
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: defend exit path against ESM module cache mismatch (#1839)
Wrap the stopAuto import/call in exit-command.ts with try/catch so
that a mid-session gsd-pi update (which causes stale ESM cache for
native-git-bridge.js exports) does not crash the /exit handler.
A warning is emitted instead. The user's work is already saved;
this path is cleanup only.
Fixes#1839
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: use "warning" not "warn" for notify severity type
TS2345: "warn" is not in the severity union type.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: update test assertion to match "warning" severity
The source was corrected to "warning" (valid union type), test must match.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Wire CLI flags through parseCliArgs → runWebCliBranch → launchWebMode
so users can bind to a custom host/port and whitelist CORS origins for
LAN/Tailscale access.
- Add webHost, webPort, webAllowedOrigins to CliFlags
- Parse --host, --port (validated 1-65535), --allowed-origins (csv)
- Forward into launchWebMode options
- Set GSD_WEB_ALLOWED_ORIGINS in subprocess env when provided
- Add allowedOrigins to WebModeLaunchOptions
Usage: gsd --web --host 0.0.0.0 --port 8080 --allowed-origins http://192.168.1.10:8080Closes#1847
When GSD is launched in a new empty directory that happens to be inside
an existing git repo (e.g. mkdir ~/Projects/newproject where ~/Projects
has a .git), repoIdentity() resolves to the parent repo's hash and
loads milestones from an unrelated project.
Add isInheritedRepo() to detect when basePath inherits a parent repo's
git root without having its own .gsd. When detected, git init creates
an independent repo so the directory gets a unique identity hash.
Legitimate subdirectory access (cd src/ inside an existing GSD project)
is preserved — the check only triggers when the parent repo has no .gsd.
Closes#1639
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When the worktree HEAD detaches and advances past the named milestone
branch, the branch ref becomes stale. The squash merge only captured
the stale ref, silently orphaning all commits between the branch ref
and the actual worktree HEAD.
Before the squash merge, compare the milestone branch ref with the
worktree's actual HEAD. If the branch ref is an ancestor of the
worktree HEAD, fast-forward the branch ref. If they have diverged,
throw a clear error instead of silently losing commits.
Guarded by worktreeCwd !== originalBasePath_ so non-worktree merge
paths (e.g. parallel-merge) are unaffected.
Fixes#1846
On Windows, paths embedded in bash command strings have backslashes
stripped by the shell (e.g. C:\Users\user becomes C:Useruser), causing
cd and other commands to fail silently. This left ~1.4 GB orphaned
worktree directories after milestone completion.
- Normalize all paths to forward slashes before embedding in the
subagent cmux bash script (cd, tee, process args)
- Add post-teardown orphan detection: warn and attempt rmSync fallback
if the worktree directory persists after removeWorktree
- Add regression tests for Windows path normalization
Closes#1436
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove hardcoded /^M\d{3}/ regex filter from syncGsdStateToWorktree and
syncWorktreeStateBack so milestone directories with non-standard names
(e.g. sprint-alpha, M001-abc123) are synced between main and worktree.
Closes#1547
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Update README "What's New" section with v2.41.0 highlights organized by
category: new features (web interface, doctor lifecycle), data loss
prevention (7 critical fixes), auto-mode stability, roadmap parser
improvements, state/git fixes, Windows/platform support, and DX.
- Add web-interface.md documenting the new browser-based UI
- Add web interface entry to docs/README.md index
- Move v2.39-v2.40 highlights to "Previous highlights" section
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The Windows CI runner's Application Data directory triggers EPERM
when webpack scans it. The web build is a Linux/macOS deployment
target and doesn't need to run on Windows.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The validate-pack step requires dist/web/standalone/server.js but
the build command didn't produce it. Add build-web-if-stale.cjs to
the build chain — it skips when up-to-date and rebuilds when needed.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When the ## Slices section exists but contains H3 prose headers instead of
checkboxes, parseRoadmapSlices returned an empty array because the prose
fallback was only invoked when the ## Slices heading was entirely absent.
Now, when the checkbox parser finds zero slices, it falls through to
parseProseSliceHeaders as a second-chance fallback.
Also adds a missing_slice_dir diagnostic in doctor.ts when resolveSlicePath
returns null, with auto-fix via mkdir.
Fixes#1711
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(auto): reject execute-task with zero tool calls as hallucinated
Adds two safeguards against agents that complete with exit 0 but make
no tool calls, producing hallucinated summaries:
1. Zero tool-call guard: after closeoutUnit snapshots metrics for an
execute-task, check the ledger for toolCalls === 0. If zero, log a
warning and skip adding the unit to completedUnits so the task is
retried on the next loop iteration instead of silently advancing.
2. Worktree health check: before dispatching an execute-task, verify
the basePath has a .git marker and at least one of package.json or
src/. A broken worktree causes agents to hallucinate since they
cannot read or write files. Stops auto-mode immediately on failure.
Fixes#1833
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: replace findLast with reverse().find() for ES2022 compat
findLast requires ES2023 lib target. The project uses ES2022.
Functionally identical: [...arr].reverse().find() with explicit type.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- MergeConflictError constructor fields (conflictedFiles, strategy,
branch, mainBranch, name, message content, instanceof checks)
- writeIntegrationBranch rejects gsd/quick/* ephemeral branches
- resolveMilestoneIntegrationBranch returns status:missing when no
metadata file exists
- resolveMilestoneIntegrationBranch returns status:missing when both
recorded and configured main_branch are absent (full fallback chain)
- buildTaskCommitMessage appends Resolves #N trailer when issueNumber set
- runPreMergeCheck skips gracefully when no package.json found
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
The spawnSync --import flag also receives a bare Windows path.
Convert it with pathToFileURL like the script import.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Bare "file://" + windowsPath produces invalid URLs on Windows
(e.g. file://D:\...). pathToFileURL correctly produces file:///D:/...
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When a milestone has only CONTEXT-DRAFT.md (no CONTEXT.md), the depends_on
frontmatter was silently ignored because _deriveStateImpl() only read from
CONTEXT.md. This caused dep-blocked milestones to be incorrectly promoted
to active status. Now all three dependency-reading paths fall back to
CONTEXT-DRAFT.md when CONTEXT.md is absent.
Fixes#1724
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
parseProseSliceHeaders() always set done:false regardless of ✓ or
(Complete) markers in the title, causing auto-mode to repeatedly
dispatch complete-slice for already-finished slices.
- Detect ✓ prefix before slice ID ("## ✓ S01: Title")
- Detect ✓ after separator ("## S01: ✓ Title")
- Detect (Complete) suffix ("## S01: Title (Complete)")
- Strip markers from title so downstream consumers get clean names
- Add prose format support to markSliceDoneInRoadmap
Fixes#1803
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add QUEUE.md and completed-units.json to the durable file whitelist in
both syncGsdStateToWorktree (forward sync) and syncWorktreeStateBack
(reverse sync). These files are written during milestone closeout but
were not being copied back to the project root, causing state loss on
worktree teardown.
Adds regression test verifying both files survive reverse sync.
Fixes#1787
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The @ file autocomplete triggered a synchronous native fuzzyFind call
that walks the entire directory tree. On large repos this blocked the
Node.js event loop and froze the TUI.
Three changes fix this:
1. Skip the fuzzy search when the query is empty (bare "@" with nothing
typed yet) — there is no point walking the full tree with no query.
2. Debounce the initial "@" keystroke instead of firing the search
synchronously, so rapid typing cancels pending walks and the search
only runs once the user pauses.
3. Deduplicate consecutive lookups using lastAutocompleteLookupPrefix
(previously declared but unused) to avoid redundant synchronous
searches when the prefix hasn't changed.
Fixes#1824
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>