* feat(web): make web UI mobile responsive
Fixes#2274
Add mobile-first responsive design to the GSD web UI:
- Viewport meta tag via Next.js Viewport export
- Collapsible sidebar as slide-out drawer on mobile with hamburger menu
- Milestone explorer as right-side drawer on mobile with bottom bar toggle
- Responsive header: hide project label, scope badge, beta badge on small screens
- Dashboard: responsive grid (1col mobile -> 2col sm -> 4col xl), responsive padding
- Status bar: hide secondary info on small screens, responsive text sizing
- Touch-friendly 44px minimum tap targets on mobile nav items
- Mobile CSS utilities in globals.css (overlay, drawer transitions)
- 19 structural tests verifying responsive classes exist in key components
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* ci: retrigger after stale check
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When the user's home directory is a git repo (e.g. dotfile managers like
yadm), isInheritedRepo() found ~/.gsd and concluded that subdirectories
were part of an existing GSD project — loading the wrong project state.
Extract isProjectGsd() to distinguish a project .gsd (symlink to external
state, or legacy directory) from the global ~/.gsd state directory by
comparing against the resolved GSD_HOME path.
Fixes#2393
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
After migration to DB-backed state, milestones on disk that were never
imported into the DB became invisible. deriveStateFromDb now scans the
milestones directory and injects synthetic entries for any disk-only
milestones, then re-sorts to maintain canonical order.
Fixes#2416
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The dispatch-time writeUnitRuntimeRecord call in runUnitPhase did not
reset recoveryAttempts, so the counter from a prior execution's timeout
carried over to subsequent dispatches. This caused re-dispatched units
to be instantly skipped (recoveryAttempts >= maxRecoveryAttempts) with
no steering message or second chance.
Add `recoveryAttempts: 0` to the dispatch-time runtime record write so
each execution starts with its full recovery budget.
Fixes#2322
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Worktree teardown with --force destroyed uncommitted changes in
submodule directories. Now detects .gitmodules, checks submodule
status for uncommitted changes, and stashes them before removal.
When submodules have dirty state, attempts non-force removal first.
Fixes#2337
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When bootstrapAutoSession finds a survivor milestone branch and the
derived state phase is "complete", recovery was skipped entirely because
the survivor branch detection only triggered for phase === "pre-planning".
This left the milestone worktree/branch alive and routed bootstrap into
showSmartEntry instead of running finalization (merge, cleanup).
Changes:
- Broaden survivor branch detection to also check phase === "complete"
- Add explicit finalization path: when hasSurvivorBranch && phase ===
"complete", call resolver.mergeAndExit() to run the pending merge and
worktree cleanup, then re-derive state so the normal flow continues
- After finalization, clear hasSurvivorBranch so the "all milestones
complete" or "next milestone" path runs correctly
Fixes#2358
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Collects a snapshot of all milestones (status, dependencies, slice progress,
queue order) and dispatches a prompt that turns Claude into a reorganization
assistant. Supports reordering, parking, unparking, discarding, adding
milestones, and updating dependencies through conversation.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The completed-units-metrics-sync source-scanning test used a 700-char
window that was too small when Windows CRLF line endings inflated byte
offsets, causing the archive keyword check to miss by ~2 chars.
Widens the window to 1200 chars and lowercases the comparison so
"Archive" and "cpSync" match regardless of case or line ending style.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add `full_plan_md` TEXT column to the tasks table, following the
established `full_summary_md` pattern. When populated,
`renderTaskPlanFromDb()` writes the stored markdown directly instead
of regenerating a minimal version from individual DB fields.
- DB schema: add `full_plan_md` column (migration v11)
- `TaskPlanningRecord` / `upsertTaskPlanning`: accept and persist `fullPlanMd`
- `renderTaskPlanFromDb`: prefer `full_plan_md` when non-empty
- plan-task, plan-slice, replan-slice tools: pass `fullPlanMd` through
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add inline rendering to gsd_decision_save, gsd_requirement_update,
gsd_summary_save, and gsd_milestone_generate_id so the TUI shows
meaningful context during and after tool execution instead of generic
static labels.
Before: '⏳ Save Decision' (no context)
After: '⏳ decision_save [architecture] Use SQLite — better-sqlite3'
'✓ Decision D042 saved → DECISIONS.md'
Follows the established pattern from context7 and search-the-web:
{toolTitle bold name} {accent primary arg} {muted/dim metadata}
Closes#2236
stopAuto Step 4 previously always called exitMilestone(preserveBranch: true),
which preserved the worktree branch but never merged it back. When auto-mode
stopped after complete-milestone, the code stayed stranded on the worktree branch.
Now checks if the milestone has a SUMMARY file (completion signal) and calls
mergeAndExit instead, so completed milestone code reaches main.
Fixes#2317
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Shows absolute timestamps (date + time) on user prompts (right-aligned
above the message) and assistant replies (below the response). Format
is configurable via /settings → Timestamp format:
- date-time-iso: 2026-03-24 10:34 (default)
- date-time-us: 03-24-2026 10:34 AM
Setting persists in settings.json as timestampFormat.
- Added formatTimestamp utility with ISO and US format support
- Updated UserMessageComponent and AssistantMessageComponent
- Added timestampFormat to SettingsManager with getter/setter
- Added to /settings UI for runtime switching
- Unit tests for all format variants including AM/PM edge cases
AI-assisted: This change was authored with Claude (AI pair programming).
Adds a new `/gsd mcp` slash command that shows configured MCP servers,
their connection status, and available tools. Supports two subcommands:
- `/gsd mcp status` (default) — overview of all servers
- `/gsd mcp check <server>` — detailed info for a specific server
Exports a `getConnectionStatus()` helper from the mcp-client extension
so the command can query live connection state.
Fixes#1489
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Doctor flagged missing_slice_dir and missing_tasks_dir as ERROR for
slices with status "pending" — slices that plan-milestone inserted but
haven't been dispatched yet. These directories are created lazily by
ensurePreconditions() at dispatch time, so their absence is expected.
Preserve the DB status field in the slice mapping and skip directory
checks entirely for pending slices.
Closes#2446
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(gsd): migrate completion/validation prompts to DB-backed tools and fix pipeline inconsistencies (#2444)
- Create gsd_validate_milestone tool (handler + DB registration) using assessments table
- Update complete-milestone.md to use gsd_complete_milestone instead of manual file writes
- Update validate-milestone.md to use gsd_validate_milestone + gsd_reassess_roadmap for remediation
- Add buildSkillActivationBlock() to 4 missing prompt builders (complete-milestone, validate-milestone, run-uat, reassess-roadmap)
- Remove dead completedSliceSummaryPath variable from reassess-roadmap builder
- Remove dead "degraded fallback" sections from replan-slice.md and reassess-roadmap.md
- Fix plan-slice.md double-tool instruction (gsd_plan_slice already persists tasks)
- Fix inconsistent commit/write instructions in complete-milestone.md
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: update tests for new tool registration and prompt changes
- Add gsd_validate_milestone to tool-naming RENAME_MAP (24→26 tools)
- Update prompt-contracts assertions for removed fallback text and singular DB tool phrasing
- Restore {{roadmapPath}}, {{assessmentPath}}, {{planPath}}, {{replanPath}} template vars in prompts for context
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: restore {{milestoneSummaryPath}} template var in complete-milestone prompt
Test expects the milestone summary path reference in the prompt content.
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 a file already exists on disk and the new content is <50% of the
existing file size, skip the disk write and store the existing file
content in the DB instead. This prevents data loss when research prompts
write full content via `write` then `gsd_summary_save` is called with
an abbreviated summary.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
MergeConflictError from squash merge was caught silently in
worktree-resolver's mergeAndExit, so the auto loop retried the
merge forever. Now:
1. worktree-resolver re-throws MergeConflictError after cleanup
2. auto/phases.ts catches it at all 3 mergeAndExit call sites
3. On conflict, stops the loop with a clear error message
Fixes#2330
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: complete offline mode support for local-only model setups
- Add isLocalModel() to detect localhost/127.0.0.1/0.0.0.0/::1/unix sockets
- Add isAllLocalChain() to verify all registry models are local
- Validate --offline flag rejects remote models with clear error
- Auto-enable PI_OFFLINE when all configured models are local
- Return dummy API key for local models to skip auth validation
- Filter web search results in offline mode (chat-controller + tool-execution)
- Add ECONNREFUSED/ENOTFOUND/ENETUNREACH to INFRA_ERROR_CODES for immediate
failure (no retry) when network is intentionally unavailable
- Add comprehensive test suite (17 tests)
Fixes#2341
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(test): update infra-error test for new offline-mode error codes
The offline mode feature added ECONNREFUSED, ENOTFOUND, and ENETUNREACH
to INFRA_ERROR_CODES but the test still asserted size === 6. Update the
count to 9 and add detection tests for the three new codes.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
classifyProviderError now recognizes terminated, connection reset, connection
refused, fetch failed, and other network errors as transient. These get a 15s
backoff delay and auto-resume instead of being treated as permanent failures
requiring manual intervention.
Fixes#2309
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Two bugs fixed:
1. completed-units.json was wiped to [] on milestone transition, losing all
tracking data. Now archived to completed-units-{MID}.json before reset.
2. metrics.json was never synced between worktree and project root. Added to
syncStateToProjectRoot, syncWorktreeStateBack, and syncGsdStateToWorktree
file lists.
Fixes#2313
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Added parseEstimateMinutes() to parse estimate strings like "30m", "2h",
"1h30m" into minutes. startUnitSupervision now looks up the task estimate
from the DB and scales soft/hard timeouts accordingly. A 30m task gets 3x
the default timeout, a 2h task gets 12x. Idle timeout is not scaled
because idle is idle regardless of task size.
Also added taskEstimate field to SupervisionContext interface for explicit
estimate passing from callers.
Fixes#2243
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three bugs prevented auto_pr from ever creating a PR:
1. auto_pr was gated on `pushed` flag which requires auto_push to also be
true. Changed condition to `!nothingToCommit` so auto_pr works independently.
2. phases.ts called createDraftPR AFTER mergeAndExit (when we're back on main
and the milestone branch may not exist on remote). Removed duplicate PR
creation from phases.ts — it's already handled inside mergeMilestoneToMain.
3. createDraftPR in git-service.ts lacked --head and --base parameters, so
gh would create a PR from whatever branch was current. Added optional
opts parameter with head/base support.
Fixes#2302
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
gsd_milestone_generate_id creates a minimal DB row (status: 'queued')
via INSERT OR IGNORE when generating an ID. This ensures milestones
created via /gsd queue or multi-milestone discuss are visible to the
state machine from the moment they get an ID, rather than relying on
the safety-net reconciliation in deriveStateFromDb().
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(gsd): reconcile disk-only milestones into DB in deriveStateFromDb
Milestones created via /gsd queue (or by complete-milestone writing a
next CONTEXT.md) are never inserted into the DB because the migration
guard in auto-start.ts only runs when gsd.db does not yet exist.
deriveStateFromDb() called getAllMilestones() (DB-only) with no disk
fallback, so these queued milestones were invisible to the state machine.
When all DB-tracked milestones completed, phase='complete' fired and
auto-mode stopped even though untracked milestones existed on disk.
Fix: add an incremental disk→DB reconciliation step inside
deriveStateFromDb() that compares findMilestoneIds() against DB rows
and calls insertMilestone() (INSERT OR IGNORE) for any non-ghost
directory that has no DB row. Re-queries only when rows were inserted.
Adds a regression test that reproduces the exact scenario from #2416:
M001 complete in DB, M002 queued on disk only → before fix phase was
'complete', after fix phase is 'pre-planning' with both milestones
visible in the registry.
Closes#2416
* fix: add missing closing brace for describe block in test
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Jeremy McSpadden <jeremy@fluxlabs.net>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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>
* 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>
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>
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>