deriveState() was called ~7 times per dispatch cycle, each call re-reading
the entire .gsd/milestones/ tree from disk (~50-60 file reads per call,
~350-420 redundant reads per cycle). Add a 100ms TTL cache keyed by
basePath so repeated calls within the same dispatch cycle return the
cached result. Expose invalidateStateCache() and call it at every
mutation boundary in auto.ts: handleAgentEnd start, post-merge
re-derivations, and resume-from-pause.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Tests that write files and immediately call deriveState() got stale results
because the path resolution cache (dirEntryCache/dirListCache) returned
cached directory listings that didn't include newly written files.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds a "Custom (OpenAI-compatible)" provider option to the API key
flow in the onboarding wizard. When selected, prompts for base URL,
API key, and model ID, then writes the config to models.json.
Add explicit provider targeting for model preferences when the same
model ID exists across multiple providers (e.g., claude-sonnet-4-6
on both Anthropic and Bedrock).
Two formats supported:
- String: "bedrock/claude-sonnet-4-6"
- Object: { model: claude-sonnet-4-6, provider: bedrock }
The provider/model string format already worked in the resolution
code but was undocumented. This adds the provider field to the
object format and documents both approaches.
OpenRouter models use slash-separated IDs (e.g. "moonshotai/kimi-k2.5") where the
full string is the model ID on the "openrouter" provider. The auto-mode model
switcher incorrectly split on the first slash and treated the prefix as a provider
name, causing all OpenRouter preference models to fail resolution and fall back to
the default model for every phase.
Now the resolver first checks whether the slash-prefix is a known provider, and if
not (or if no match is found), falls back to matching the full string as a model ID
— consistent with model-resolver.ts.
Also improves the progress widget and notifications to show [PHASE] and
provider/model so users can confirm the correct model is active.
Closes#402
Systems with a buggy git-svn Perl module (notably Arch Linux) emit
"Duplicate specification" warnings on every git invocation. Filter
these from error messages and suppress git-svn loading via GIT_SVN_ID.
Also update repository URLs from stale glittercowboy/gsd-pi to
gsd-build/gsd-2.
loader.ts previously maintained a hardcoded list of bundled extension paths
for GSD_BUNDLED_EXTENSION_PATHS. This required manual updates whenever
extensions were added or removed, and created a consistency gap with
buildResourceLoader() which already discovers extensions dynamically.
Replace with runtime directory scanning that mirrors the discovery rules
in resource-loader.ts:
- Top-level .ts/.js files → extension entry point
- Directories with index.ts or index.js → extension entry point
- Directories without either (shared/, remote-questions/) → skipped
Benefits:
- Adding a new extension no longer requires editing loader.ts
- GSD_BUNDLED_EXTENSION_PATHS stays in sync with what buildResourceLoader()
loads in the main process — subagents now receive the same extensions
- Fixes: 5 extensions (google-search, mcporter, ttsr, universal-config,
voice) were loaded in the main process but missing from
GSD_BUNDLED_EXTENSION_PATHS, meaning subagents did not receive them
- Eliminates a common source of merge conflicts for contributors and forks
that add custom extensions
Prevent duplicate slice/dependency summaries from being inlined into
prompts when the same ID appears more than once. Uses a Set to track
already-included IDs in inlineDependencySummaries and
buildCompleteMilestonePrompt.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
parseRoadmap, parsePlan, parseSummary, and parseContinue are pure
functions that get called repeatedly with the same content during
deriveState dispatch cycles. A module-scoped Map keyed by a fast
composite key (length + first 100 chars + last 100 chars) avoids
redundant parsing. Cache caps at 50 entries and clears when full.
Exports clearParseCache() for explicit invalidation.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
resolveDir(), resolveFile(), and resolveTaskFiles() call readdirSync()
on every invocation with no caching. These are called dozens of times
per dispatch cycle through resolveMilestonePath, resolveSliceFile,
relTaskFile, etc. This adds a module-level Map cache for directory
listings with an exported clearPathCache() function for invalidation
at dispatch cycle boundaries and milestone transitions.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The dispatch gap watchdog is a safety net for when the dispatch chain
silently breaks. 30s is unnecessarily long and makes failures feel
sluggish. 5s is plenty of time to detect a stall without the wait.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
GSD's internal git commits (auto-commits, slice merges, runtime file cleanup)
trigger pre-commit hooks like lint-staged. When lint-staged runs formatters
(e.g. prettier) on already-formatted files, it detects no changes from its
tasks and blocks the commit with "Prevented an empty git commit!" — even
though there are legitimate staged changes.
This is a known incompatibility between lint-staged's backup/restore mechanism
and squash-merge commits. The code being committed already passed hooks when
originally committed on the slice branch, so re-running hooks on the merge
commit is redundant.
Adds --no-verify to all 7 internal commit calls in git-service.ts:
- writeIntegrationBranch() — records integration branch metadata
- smartStage() cleanup — untracks runtime files from git index
- commit() — general commit method used by GSD
- autoCommit() — auto-commits dirty working tree between units
- mergeSliceToMain() — pre-merge runtime file untracking (2 calls)
- mergeSliceToMain() — squash-merge commit and --no-ff amend
* fix: read resources from dist/ to prevent branch-drift in npm-link setups
initResources() reads extensions, prompts, skills, and agents from
src/resources/ — which points into the live working tree when gsd is
installed via npm link. Switching branches in the gsd repo changes
src/resources/ for ALL projects using gsd, causing stale or broken
extensions to be synced to ~/.gsd/agent/ on next launch.
Fix: the build step now copies src/resources/ to dist/resources/.
At runtime, resource-loader.ts and loader.ts prefer dist/resources/
(stable, set at build time) over src/resources/ (live working tree).
Fallback to src/resources/ is preserved for setups without a build.
Also adds npm run dev watch-resources watcher that syncs src/resources/
to dist/resources/ on file changes, running alongside tsc --watch.
* fix: cache prompt templates per session to prevent cross-session invalidation
When two gsd sessions run concurrently, the second session's
initResources() overwrites ~/.gsd/agent/ templates on disk. The first
session then reads a newer template that expects variables its in-memory
code doesn't know about, causing 'template declares {{X}} but no value
was provided' crashes that hang auto-mode indefinitely.
Fix: cache each template on first read. A running session uses the
template versions from when it first loaded them, immune to later
disk overwrites by other sessions.
Three bugs prevented native web_search from being used with Anthropic:
1. model_select never fires on session restore (SDK's modelsAreEqual
guard suppresses it when the default model matches the restored one),
so isAnthropicProvider stayed false and native search was never
injected. Fix: fall back to detecting "claude-" in the payload model
name when model_select hasn't fired.
2. Custom search tools (search-the-web, search_and_read) were only
removed from the payload when BRAVE_API_KEY was missing. With the
key present, the model saw both native and Brave tools and often
picked the Brave ones, which failed with network errors. Fix: always
remove custom search tools from Anthropic requests.
3. google_search (Gemini-based) was not included in the disable list,
so the model could call it even without GEMINI_API_KEY set. Fix: new
CUSTOM_SEARCH_TOOL_NAMES list covers all three custom search tools.
npx runs with --ignore-scripts, skipping postinstall. The loader now
creates node_modules/@gsd/* symlinks pointing to packages/* before
importing cli.js, so @gsd/* packages resolve without postinstall.
Bumps to v2.10.12.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Initial plan
* fix: detect and merge orphaned completed slice branches at startup to prevent infinite loop
Co-authored-by: glittercowboy <186001655+glittercowboy@users.noreply.github.com>
* Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
* Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
* Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
* fix: add missing closing brace in mergeOrphanedSliceBranches
The for-loop body was missing its closing brace, causing a parse error
that broke all tests importing auto.ts.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: glittercowboy <186001655+glittercowboy@users.noreply.github.com>
Co-authored-by: TÂCHES <afromanguy@me.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Lex Christopherson <lex@glittercowboy.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Node 22.22.0's --experimental-strip-types handles .ts import specifiers
differently, causing ERR_INVALID_TYPESCRIPT_SYNTAX in CI. The project
convention uses .js specifiers with a custom resolve hook that rewrites
them to .ts at test time.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Initial plan
* Add adaptive reconciliation, concrete remediation steps, and xxHash32 JS fallback
- auto.ts: final reconciliation pass before halting on loop detection
(runs skipExecuteTask to write blocker artifacts and advance pipeline)
- auto.ts: adaptive retry on prevCount>=2: write stub summary when still
missing after two agent sessions, so the agent has a recovery context
- auto.ts: buildLoopRemediationSteps() helper with concrete manual steps
when automatic reconciliation cannot resolve the unit
- xxhash/index.ts: pure-JS xxHash32 fallback when native addon does not
export xxHash32 (prevents 'native.xxHash32 is not a function' crash)
- idle-recovery.test.ts: tests for buildLoopRemediationSteps and loop-recovery
path in skipExecuteTask
Co-authored-by: glittercowboy <186001655+glittercowboy@users.noreply.github.com>
* Address code review feedback
- xxhash/index.ts: extract accumulate() helper to remove duplicate lane
processing lines in the 16-byte loop
- auto.ts: replace magic number 2 with named constant STUB_RECOVERY_THRESHOLD
- idle-recovery.test.ts: add comment explaining the 3 (== MAX_UNIT_DISPATCHES)
used in the loop-recovery test
Co-authored-by: glittercowboy <186001655+glittercowboy@users.noreply.github.com>
* Address PR review: verify post-state, fix stub wording, fix checkbox syntax, add fallback test
- auto.ts (MAX branch): check verifyExpectedArtifact() after skipExecuteTask()
before advancing; annotate both conditions explaining what each validates
- auto.ts (self-repair branch): guard advance with verifyExpectedArtifact() so
regex mismatches in the slice plan don't silently re-dispatch
- auto.ts (stub-recovery): write stub summary directly with "PARTIAL RECOVERY"
wording instead of using skipExecuteTask (which would claim "exhausted N attempts")
- auto.ts (remediation): fix checkbox example to "- [x] **${tid}:" (no trailing **)
- xxhash/index.ts: export xxHash32Fallback so CI can test JS path independently
- native/src/index.ts: re-export xxHash32Fallback
- xxhash.test.mjs: add "xxHash32Fallback (pure-JS path)" describe block that
validates the JS implementation against the reference on all test cases
Co-authored-by: glittercowboy <186001655+glittercowboy@users.noreply.github.com>
* chore: retrigger CI
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: glittercowboy <186001655+glittercowboy@users.noreply.github.com>
Co-authored-by: CI Trigger <noreply@github.com>
* feat(M001/S01): ID generation and config plumbing
Tasks:
- chore(M001/S01/T02): auto-commit after execute-task
- chore(M001/S01): auto-commit after plan-slice
- docs(S01): add slice plan
Branch: gsd/M001/S01
* feat(M001/S02): Regex hardening and backwards compat
Tasks:
- chore(M001/S02/T02): auto-commit after execute-task
- chore(M001/S02/T01): auto-commit after execute-task
- chore: untrack .gsd/ runtime files from git index
- docs(S02): add slice plan
Branch: gsd/M001/S02
* docs(M001/S03): UX — wizard toggle and documentation
Tasks:
- chore(M001/S03/T01): auto-commit after execute-task
- docs(S03): add slice plan
Branch: gsd/M001/S03
* test(M001/S04): Integration tests and end-to-end verification
Tasks:
- chore(M001/S04/T02): auto-commit after execute-task
- chore(M001/S04/T01): auto-commit after execute-task
- docs(S04): add slice plan
Branch: gsd/M001/S04
* chore(M001): record integration branch
* chore(M002): record integration branch
* docs(M002/S01): Format swap — production code, tests, and docs
Tasks:
- chore(gsd/M002/S01): auto-commit after pre-switch
- chore(M002/S01/T01): auto-commit after execute-task
- chore: untrack .gsd/ runtime files from git index
- docs(S01): add slice plan
Branch: gsd/M002/S01
* chore(M002): auto-commit after complete-milestone
* Updated to document that we don't automatically always squash to main if you started on a different branch (like a dev or feature branch)
* fix: replace vitest import with node:test in regex-hardening test
The test imported from 'vitest' which isn't installed, causing
ERR_MODULE_NOT_FOUND and failing the CI unit test step. All other
test files use node:test. Swapped the import and removed the
vitest conditional wrapper.
* chore: untrack .gsd/ (already gitignored)
* docs: fix stale 'main' references in merge comments and prompts
The slice merge code correctly resolves to the integration branch
(which may be a feature branch, worktree branch, etc.), but comments,
JSDoc, and prompt templates still said 'main' as if it were always
the literal main branch.
Updated git-service.ts, worktree.ts, system.md, and
guided-complete-slice.md to say 'integration branch' with a clear
explanation of what that means.
* Fixed preferences.md case mismatch; Added fallback for backwards compat
* Updated preferences file example to show new unique_milestone_ids setting
* Updated readme to explain best practice for working in teams
---------
Co-authored-by: TÂCHES <afromanguy@me.com>
Two bugs that interact to silently kill auto-mode:
1. smartStage() uses `git add -A` which respects .gitignore. When .gsd/
is gitignored (common — GSD's own baseline patterns only ignore runtime
files, but many projects ignore all of .gsd/), new planning artifacts
(CONTEXT.md, SUMMARY.md, PLAN.md, UAT.md, DECISIONS.md) are never
staged. They exist on disk but not in git. Squash-merges then delete
them on main because they appear as "removed relative to main."
Fix: after `git add -A`, force-add `.gsd/milestones/` and root
planning files. Runtime paths are still excluded by the subsequent
`git reset HEAD` step.
2. handleAgentEnd() has no reentrancy guard. Background job notifications
(async_bash results) trigger additional agent_end events while the
first handler is still running (it yields at every await). Concurrent
dispatchNextUnit() calls race on newSession() — one cancels the other,
silently stopping auto-mode. Combined with bug #1, the second
dispatchNextUnit call may find the active milestone's CONTEXT.md
missing (never committed, lost during branch switch) and stop with
"No context or roadmap yet."
Fix: boolean guard prevents concurrent execution. Reset in stopAuto()
so restarts aren't blocked.
Fixes #TBD
Co-authored-by: TÂCHES <afromanguy@me.com>
After a crash where complete-slice wrote SUMMARY+UAT but didn't mark
the roadmap [x], the idempotency check incorrectly reported the unit
as "done" (artifacts exist), while the state machine kept returning
the same complete-slice unit (roadmap shows [ ]). This caused
dispatchNextUnit to recurse forever.
Fix: verifyExpectedArtifact for complete-slice now also checks that
the slice is marked [x] in the roadmap. If not, it returns false so
the stale completion key is evicted and the unit re-runs.
Lenient if roadmap file is missing or corrupt (returns true).
Co-authored-by: glittercowboy <186001655+glittercowboy@users.noreply.github.com>