* test(#3719): failing-first coverage for the agents emit path's missing tilde restore applyAgentPathRewrites performs its four tilde and HOME substitutions and never calls restoreClaudeGlobalAtRefTilde, which has exactly one call site in the module and it is not this one. So every agents/gsd-*.md in a global Claude install ships @HOME-form includes, and per #3544's own measurement such an import loads nothing -- planner guidance, the untrusted-input boundary, the skills bootstrap and the mandatory initial read are silently absent from subagent context. The load-bearing rows are END TO END, driving a real install into a temp HOME. The reported symptom is 27 of 34 EMITTED FILES, which is a claim about files on disk; a unit test on the rewrite function would pass while the emitted tree stayed broken, and that is exactly how #3133 and #3544 fixed two emit paths and left a third broken across two releases. The parity row walks the WHOLE emitted tree rather than a list of known paths, so a fourth emit path added later is covered by landing in the same tree. Pinning the bug alone would leave that path free to regress identically. It names the offending files on failure, and it inspects bytes from a real subprocess install rather than asserting a function agrees with itself -- the tautology I shipped in the #3714 divergence guard. Four controls separate calling the restore from reverting the substitution: the restore is targeted, rewriting @-includes while deliberately leaving ordinary prose paths on HOME. Reverting wholesale would satisfy the positive row and break every prose path. One boundary row is deliberately red beyond the obvious fix. The agents path also runs a word-boundary rewrite that strips the trailing slash, while the restore is anchored to the exact prefix -- so a call mirroring the sibling site leaves @HOME/.claude with no trailing slash broken. Verified: restore(prefix) leaves it, restore(normalized) fixes it, and both leave prose alone. * fix(#3719): restore @-refs to tilde on the agents emit path The third emit path that needed this. #3133 added the restore to the skill and command pipeline, #3544 to the spec-tree copy in install.js, and the agents pipeline never got it -- so every @~/.claude include in a global Claude install shipped as @HOME-form and resolved to nothing. Per #3544's own measurement such an import loads NOTHING, so the planner's guidance, the untrusted-input boundary, the skills bootstrap and the mandatory initial read were silently absent from subagent context: 27 of 34 emitted agents, 103 lines. Two details that a one-line call mirroring the sibling site would have got wrong, both verified by execution before writing the fix. It passes the NORMALIZED prefix rather than pathPrefix. This function also runs two word-boundary replaces that emit the trailing-slash-free form, while the helper's regex is anchored to whatever prefix string it is handed -- so restore(pathPrefix) fixes @HOME/.claude/x and leaves a bare @HOME/.claude broken. The normalized form is a prefix of both, so one call covers both. And it is guarded on claude. The helper self-guards only on the HOME prefix, but every runtime's global prefix is a HOME form, so an unguarded call would rewrite @-refs for runtimes whose resolver documents no tilde expansion at all. Verified: claude restores, cursor and kilo do not. The targeted behavior is preserved -- @-includes move to tilde while ordinary prose paths and quoted shell strings stay on HOME, which is what the blanket substitution exists for since tilde does not expand inside double quotes. * fix(#3719): stop the word-boundary replaces corrupting a non-default config dir Review found that my fix MASKED a pre-existing bug, which is worse than leaving it. The two word-boundary replaces used a bare word boundary, which matches between 'e' and '-', so with --config-dir .claude-work they turned .claude-work/ into .claude-work-work/ -- 119 dead paths in a real install. #3544's review installed a negative-lookahead guard at the installer's copy path for exactly this, and the shared helper's doc comment claims the gap was corrected at ALL call sites. It was not corrected here. That much is pre-existing; base emits the same 119. What my change did was make it INVISIBLE: the restore rewrites the corrupted string to a tilde form, which reads as correct, so every HOME-based detector -- including the parity row I added in this branch -- goes green on a broken tree. A fix that hides the evidence of a neighbouring bug is not a fix. Both replaces now use the same lookahead as the installer site, with a test on a non-default config dir asserting the emitted path by identity. Three test weaknesses from the same review, all of which would have passed while guarding nothing: The parity row anchored its detector at line start, so it could not see the 48 mid-line refs the helper deliberately supports -- half-blind while being billed as the future-proof row. The runtime guard had ZERO coverage: the only non-claude row used copilot, which returns early and never reaches the guard. Cursor and kilo rows now exercise it. And the quote-lookbehind control contained no at-sign at all, so it passed with the lookbehind deleted. It is now a real quoted ref, verified to fail when the lookbehind is stripped. * test(#3719): distinguish a live @-include from prose describing one My own MINOR fix introduced a BLOCKER. Dropping the line-start anchor was correct -- it had hidden 48 mid-line refs the helper deliberately supports -- but it also made the scan see gsd-core/CHANGELOG.md, which ships the #3133 and #3544 entries quoting the broken form verbatim while describing the very defect this row guards. Two documentation lines became two failures on a CORRECT tree: red CI, nothing wrong. Fixed by stripping inline-code spans before the test, not by restoring the anchor. Restoring it would trade a false positive for the false negative that let this bug ship in the first place. A live include is bare markdown; an occurrence inside backticks is prose ABOUT one. Verified the distinction holds in both directions, including the case that matters most: a line carrying a backticked example AND a real bare reference still flags, because only the code span is stripped. * fix(#3719): escape the replacement pattern, and pin both fixes that shipped unproven Security's remaining item, landed here on its recommendation: the restore used a STRING replacement, so a config dir containing the ampersand or backtick dollar forms was treated as a special pattern. Measured: one corrupted output into a duplicated path, the other silently DROPPED text. Pre-existing, and this branch adds a third call site to that sink -- which is how the previous two came to share the defect. It is a function replacement now. Two fixes on this branch were shipping UNPROVEN and both are now pinned: The word-boundary fix had no regression test at all. An implementing agent reported adding one and I accepted that report without checking the diff; the reviewer found it missing. Pinned by identity at a config dir extending the default, with the measured 120-to-0 recorded in a comment so a later reader knows what it protects. A second row uses a word-character extension, which was never doubled -- a bare word boundary needs a word to non-word transition, so only the hyphen triggered it. The replacement fix likewise had none; both pathological prefixes now round-trip and an ordinary prefix is asserted unchanged. Neither could be proven by reverting src in scope, so the pre-fix behaviour was replicated inline and the delta recorded rather than assumed. * test(#3719): state the trust model accurately in the replacement-pattern note The comment described the config dir as attacker- or operator-controlled. Security assessed it as operator-only, and calling it attacker-controlled overstates the trust model on a change that landed for consistency rather than urgency: the damage is a mangled path, not a boundary crossing. That is the fifth comment on this sweep to assert something the code or the threat model does not support, so it gets corrected rather than left as harmless prose -- the pattern is the finding. * chore(#3719): backfill changeset pr number Doing this immediately after PR creation this time: the same omission was the only red CI on the previous PR tonight. --------- Co-authored-by: sim <sim@local>
GSD Core
Git. Ship. Done.
English · Português · 简体中文 · 日本語 · 한국어
A light-weight meta-prompting, context engineering, and spec-driven development system for Claude Code, OpenCode, Antigravity CLI, Kimi CLI, Kilo, Codex, Copilot, Cursor, Windsurf, and more.
What is GSD Core
GSD Core is a context-engineering and spec-driven development framework that drives AI coding agents (Claude Code, Codex, Antigravity CLI, Kimi CLI, Copilot, Cursor, and more) through a disciplined phase loop. It solves context rot — the quality degradation that accumulates as an AI fills its context window — by running all heavy research, planning, and execution work in fresh-context subagents while keeping your main session lean.
How it works
Each milestone repeats the same five-step loop, one phase at a time:
- Discuss — capture implementation decisions before anything is planned
- Plan — research, decompose, and verify the plan fits a fresh context window
- Execute — run plans in parallel waves; each executor starts with a clean 200k-token context
- Verify — walk through what was built; diagnose and fix before declaring done
- Ship — create the PR, archive the phase, repeat for the next one
Quickstart
npx @opengsd/gsd-core@latest
The installer prompts for your runtime (Claude Code, OpenCode, Antigravity CLI, Kimi CLI, Kilo, Codex, Copilot, Cursor, Windsurf, and more) and whether to install globally or locally. The installer is required for cross-runtime compatibility — do not copy files from agents/ or commands/ directly.
On another runtime or without Node.js? See Install on your runtime.
Once installed, start a new project or onboard an existing repo:
/gsd-new-project # greenfield project
/gsd-onboard # existing codebase
New here? Follow Your first project for a guided walkthrough from install to first shipped phase, or Onboarding an existing codebase for brownfield setup.
Documentation
What's new in 1.7.0 → docs/whats-new-1.7.0.md
Tutorials — learning by doing:
How-to guides — task-focused recipes:
Reference — authoritative facts:
Explanation — concepts and design decisions:
Full index: docs/README.md. Other languages: 日本語 · 한국어 · Português · 简体中文.
Why it works
Most AI-coding setups fail at scale because context bloat silently degrades output quality, there is no shared memory between sessions, and nothing verifies that code actually works. GSD Core solves all three: heavy work runs in fresh subagents, structured artifacts like STATE.md and CONTEXT.md survive session boundaries, and the verify step walks through what was built and generates fix plans before a phase is declared done. See docs/explanation/context-engineering.md for the full reasoning.
Troubleshooting? See docs/how-to/recover-and-troubleshoot.md.
Community
| Project | Platform |
|---|---|
| gsd-opencode | Original OpenCode port |
| Discord | Community support |
Star History
License
MIT License. See LICENSE for details.
Claude Code is powerful. GSD Core makes it reliable.