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>
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>
verifyExpectedArtifact() returned true when resolveExpectedArtifactPath()
returned null, conflating "unit type has no artifact" with "slice directory
missing on disk". This caused /gsd auto to infinitely skip and re-dispatch
the same stale completed-unit entry until OOM.
Now only replan-slice (the sole type with no verifiable artifact) passes
on null; all other types return false, triggering the existing eviction
logic that removes the stale key and re-runs the unit.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
writeIntegrationBranch now intentionally updates when the branch changes
(#300). Updated the stale "idempotent — doesn't overwrite" test to assert
the new behavior, and added a separate test for same-branch idempotency.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
MergeConflictError used `public readonly` constructor parameter properties,
which are not supported by Node's --experimental-strip-types mode (type
stripping only, no TS-to-JS transforms). This crashed 19 test files on import.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When auto-mode merges a completed slice and hits code conflicts in
non-.gsd files, dispatch a fix-merge session to resolve them instead
of hard-resetting and stopping. This eliminates the #1 cause of
unnecessary auto-mode stops.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When plan-milestone writes SECRETS-MANIFEST.md, the secrets gate at
startAuto has already fired (manifest didn't exist yet). Without a
re-check, the model proceeds into plan-slice / execute-task with no
real credentials and mocks external services — cascading risk into
every downstream slice.
Added a secrets re-check gate inside dispatchNextUnit that runs before
every unit dispatch. getManifestStatus is cheap (file read + .env
scan) so the check is a no-op when no manifest exists or all secrets
are already collected. Idempotent: skips keys already present in .env.
Closes#303
writeIntegrationBranch was unconditionally skipping if any integration
branch was already recorded, even if the user started auto-mode from a
different branch. Now it only skips when the recorded branch matches —
if it differs, the record is updated so slices merge to the correct target.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The merge flow checked out the slice branch mid-merge to untrack runtime
files, which failed when .gsd/STATE.md had uncommitted working tree changes.
Instead, strip runtime files from the staged merge result post-merge — no
branch switching needed.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The merge flow checked out the slice branch mid-merge to untrack runtime
files, which failed when .gsd/STATE.md had uncommitted working tree changes.
Instead, strip runtime files from the staged merge result post-merge — no
branch switching needed.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>