172 KiB
Changelog
All notable changes to GSD will be documented in this file.
Format follows Keep a Changelog.
Unreleased
[1.6.1] - 2026-07-01
Added
- Claude Sonnet 5 is now the
standard(sonnet) tier model. The model catalog and provider presets resolve the sonnet/standard tier toclaude-sonnet-5(GA 2026-06-30) across the Anthropic-backed runtimes (claude,copilot, and theanthropic/anthropic-fablepresets), plus the OpenRouter-styleanthropic/claude-sonnet-5foropencode/hermes, replacing the supersededclaude-sonnet-4-6. Opus and Haiku tiers are unchanged. (#1847) (#1848)
Fixed
milestone completeandroadmap analyzenow exclude the Phase 0 / Phase 999 backlog sentinels. A milestone whose only directory-less ROADMAP heading is a backlog sentinel can be completed without--force, androadmap analyzeno longer counts the sentinel inphase_countor routesnext_phaseinto it. Completes the^999exclusion #1445 added to the progress denominators. (#1691)phase.completeno longer reports a falseis_last_phaseon a<details>-wrapped checkbox checklist (#1591, #1752) — when the active milestone's phase checklist was written as- [ ] Phase N:checkbox items inside a<details>block and the next phase had no directory on disk yet (still in planning),phase.complete'sisLastPhaseroadmap-enumeration fallback used a heading-only pattern (/#{2,4}\s*Phase…/) that never matched checkbox items. It returnedis_last_phase: true, next_phase: nullon a mid-milestone phase and — via the milestone-complete cascade — wrongly flipped STATE.md toMilestone completeand decrementedprogress.total_phases(e.g. 8 → 7). The pattern now matches both heading-style (### Phase N:) and checkbox-list phases (- [ ] Phase N:/- [x] Phase N:);extractCurrentMilestonealready surfaces the<details>-wrapped checklist correctly, so no parser change was needed. Only the reproducedphase.completefallback is changed; the heading-only sibling patterns elsewhere inphase.ctsare untouched. (#1819)- Windows: stop double-quoting $CLAUDE_PROJECT_DIR-anchored managed node hook paths during the #2979 legacy rewrite, which produced ""$CLAUDE_PROJECT_DIR"/..." and broke every node managed hook with MODULE_NOT_FOUND (PreToolUse-guard deadlock). (#1746)
[1.6.0] - 2026-06-24
Added
workflow.context_guard_modeconfig key — proactive context-exhaustion guard forexecute-phase. Before each wave, the orchestrator self-assesses context pressure using the degradation signals defined incontext-budget.md. Values:warn(default — emit warning and recommend/gsd:pause-workwhen POOR tier detected),auto(automatically invoke/gsd:pause-workbefore next wave),off(disable). Set viagsd config-set workflow.context_guard_mode autofor fully autonomous checkpoint behaviour. (#1452) (#1452)agent-skills --jsonIR gains an additivevalue: { block, skills_count }field formalizing theResolution<T>convention for config-interpreting read verbs; no breaking change. The newsrc/resolution.ctsmodule exportsResolution<T> { value, configured, reason, warnings }(the canonical envelope) andmakeResolution<T>()(the builder);AgentSkillsValue { block, skills_count }is the first adopter. All existing flat fields (agent_type,block,skills_count,warnings,configured,reason,source,degraded) are retained for back-compat. Capability-state and capability-writer keep their existing JSON shapes unchanged; only doc comments are added naming them the canonical read-verb and mutation-verb envelopes respectively. The shared seam across all shapes iswarnings: string[]; a single generic across read+write verbs was rejected by the deletion test (ADR-1411 P3 amendment). (Part of #1411, P3 / #1416.) (#1425)- Added
gsd capability outdated— a new subcommand that light-peeks each installed overlay capability's recorded source for the latest version that re-resolving that source would install and reports which have an update available (ADR-1244 D6 per-source matrix: gitls-remote --tags, npmview … version, local re-read; tarball →manual, registry →unknown). A capability is reportedoutdatedonly if re-resolving its recorded source would fetch a newer version: an npm range (@^1) resolves to the highest version matching the range (read from eachnpm viewline's canonical version field, so a version-like substring in the package name never poisons the result), and a source pinned to an immutable ref (git#sha:/#tag:) or an exact npm version is reportedpinned— neveroutdated, sinceupdatewill not move it. A bare git ref (#<ref>) is classified at the remote with a boundedgit ls-remote: a ref that resolves to a tag ispinned, while a mutable branch ref is neverpinned(it degrades tounknown, since the installed commit is not recorded to compare against). Each capability is classifiedoutdated/current/pinned/manual/unknown; subprocesses are bounded (git ≤30s, npm ≤60s) and a failing or unsupported peek degrades that row tounknowninstead of crashing the command.--jsonemits the records array; the default prints a table. (#1463) (#1488) gsd capabilitymanagement command — install, update, remove, list, disable, and enable GSD capabilities (first-party and third-party overlays) from a registry / git / npm / tarball / local source, wiring the ADR-1244 lifecycle (source resolver, ledger, consent + integrity trust gate) to a user-facing CLI. (#1457) (#1457)- Runtime capability registry overlay — installed third-party capabilities (under
~/.gsd/capabilities/or a project's.gsd/capabilities/) are now composed into the registry at runtime vialoadRegistry({ includeInstalled }): validated against the same conformance invariants as first-party, first-party-wins on any collision, skipped-with-a-warning when incompatible with the running GSD version (engines.gsd), with gate-kind capabilities failing closed. Installed overlays are toggable via surface and federate their config keys (cwd-aware) exactly like first-party. Foundation (ADR-1244 Phase 2) for capability install/upgrade/remove. (#1440) - Capability manifests are now versioned — every
capability.jsoncarries a required semverversion, plus optionalengines.gsd,compatVersions,integrityandprovenancefields, enforced by the capability conformance validator. First-party capabilities are version-stamped in lockstep with the GSD release. Foundation (ADR-1244 Phase 1) for installing, upgrading, and removing capabilities in later releases. (#1436) /gsd-capture --list-seedsaudits parked seeds — a new read-only listing of.planning/seeds/showing each seed's ID, status, scope, and trigger, with an optional status filter (e.g.--list-seeds dormant). Backed by thegsd-tools list-seedscommand. Previously seeds could only be created or auto-surfaced at/gsd-new-milestone, with no way to browse them on demand (#441). (#722)- Capability source resolver + install ledger —
resolveCapabilitySource(spec)fetches a capability from a local path, git repo, npm package, or tarball URL, verifies it (sha512 integrity before staging,engines.gsdcompatibility, full conformance validation) and stages a bundle without executing any capability code (copy/extract only —npm pack --ignore-scripts, nevernpm install; symlink/tar-slip/shell-metacharacter/unsafe-transport inputs rejected). A per-runtime ledger records what each install wrote for atomic, reversible upgrade/remove. Foundation (ADR-1244 Phase 3) for the upcominggsd capability installcommand. (#1443) - Capability matrix reference — a generated catalogue (
docs/reference/capability-matrix.md) of every first-party capability's role, tier, extension points, hook kinds, andengines.gsd, generated from the committed registry and kept honest by a CI drift guard so it can never fall out of sync with the actual capability set. (#1458) (#1458) - Third-party capabilities can ship dispatchable CLI commands (ADR-1244 Phase 5) — a capability that declares a
commandsfamily is now dispatched bygsd-tools <family>via the registry, the same seam the first-partygraphify/intel/auditcommands already use. Third-party command dispatch runs only for an installed, consented capability (a committed ledger entry) and loads the router module strictly from that capability's own install root (basename + realpath confinement, rejecting..traversal and symlink escape); a bundle merely present on disk with no install record keeps its declarative surfaces but is never command-dispatchable. (#1450) (#1450) - Plugin installs now expose GSD skills — when GSD is installed as a Claude Code plugin (
claude plugin install), its skills are available viagsd-core:<skill>the native way. Previously, plugin-only installs lacked the skill surface becausebin/install.jsnever ran; agents that preloadglobal:gsd-core:<skill>(PR #1261) now resolve against plugin-provided skills. (#1596) (#1597) - Added a validated
gsd-tools worktree record-agentwriter verb that appends a per-agent entry to the wave cleanup manifest, validating every field at write time with the same rules thecleanup-wavereader enforces (write-strict--agent-id) and failing loudly with a recovery hint instead of silently appending an under-populated entry. The execute-phase orchestrator now records each spawned worktree through this verb. (#1448) (#1448) gap-analysis --phase-req-idsnow expands numeric ID ranges — a same-prefix ascending equal-width range likeSEL-01..SEL-03expands toSEL-01, SEL-02, SEL-03(zero-pad preserved) instead of being treated as one literal ID that gap-analysis then reports as missing. Ambiguous tokens (mismatched prefix, descending, differing width, non-numeric, >1000 span) stay literal. (#1269) (#1419)/gsd-plan-phasenow flags a stale codebase map before planning — thedriftcapability runs its codebase-drift check atplan:pre(non-blocking, warn-only), so a stale STRUCTURE.md is surfaced before the planner is spawned instead of being discovered mid-execution by the existingexecute:wave:postgate. Gated on a newworkflow.plan_drift_prechecktoggle (default on), independent ofworkflow.schema_drift_gate, so autonomous/CI runs can silence the plan-time advisory without disabling the execute-time gates. (#1595)
Changed
- Capability commands now emit dispatch audit records —
graphify,intel,audit-uat, andaudit-opennow route through the Command Routing Hub per ADR-959 §III(B), soGSD_AUDIT=1traces, the structured stderr JSON error envelope, and the typed Result contract cover them uniformly with all other command families. JSON-errorreasonvalues (usage,sdk_unknown_command) are preserved byte-identical. (#1646) (#1647) /gsd-verify-worknow routes UAT deterministically from a structuredcoverage:block on SUMMARY.md — deliverables proven by passing tests (human_judgment: falsewith a non-empty all-passverificationlist) are auto-passed (source: automated, no prompt), and only judgment-dependent or unverified deliverables are presented for human sign-off. SUMMARYs without acoverage:block fall back to the previous prose-based extraction, byte-identical. Authored byexecute-planand validated by the newgsd-tools uat classify-coverageverb. (#1611)- Thread
isGlobalinstall scope through the descriptor-drivenconvertedAgentsKind/stageAgentsForRuntimeWithConverterplumbing — a prerequisite for the ADR-1235 agent-conversion cutover. No runtime declares a convertedagentskind yet; thecapability.jsonwiring is deferred to a follow-up that first ships the ADR-1235 §0 byte-for-byte parity harness (so the/gsd:surface/--materializeconsumer can mirror the legacy agent pipeline before the kind goes live). The legacybin/install.jsagent loop remains authoritative, so installed agent output is unchanged. (#1173) (#1438) /gsd-reviewnow asks external reviewers to verify plan claims against the source — the reviewer prompt requires opening the referenced files, citingfile:lineevidence + mechanism, and tracing asserted behavior, with a graceful-degradation clause for reviewers that have no file access. This turns every capable agentic reviewer into a real second source instead of a plan-text paraphraser. (#1318) (#1421)- eval-auditor scoring moved into a deterministic
eval.scorequery verb (LLM-playbook principle 10) — coverage/infra/overall arithmetic and verdict banding are computed in code (gsd-tools query eval.score) instead of by the model. Based on arXiv 2601.15130 (Plausibility Trap / DPDM), 2508.15754 (Tool-Integrated Reasoning), 2507.10281 (Table Agent); 2504.00406 / 2510.15955 supporting. (#1583) gsd-toolsnow resolves the project root from a descendant subdirectory —findProjectRootwalks up to the nearest ancestor directory containing.planning/so config loads correctly when invoked outside the project root; previously it fell through to defaults for plain descendant paths (cwd-drift gap #1366). Sub_repos, multiRepo, and.git-based heuristics retain priority. (Part of #1411, P1 / #1414) (#1423)- verify-phase test-tier prohibition fail-first can now prove the RED is caused by the violation's content — the
node-testmachine-proof (#1279) confirmed a known-bad subject drives the negative test RED, but could not tell a genuine content-violation from a deceptive test that reds merely becauseGSD_PROHIB_SUBJECTis set. An optional fifth flat scalarcheck_clean_fixture(→CheckDescriptor.cleanFixture) threads a KNOWN-CLEAN control subject throughprojectProhibitions+descriptorFromProjection; when present the prover also runs the check against it and requires GREEN, so fail-first is proven only when the check is RED on the violation and GREEN on the clean subject (content-dependent). It is opt-in and additive: absent a clean fixture the prover behaves exactly as it did post-#1314 (no control, documented residual), preserving the zero-authoring compose path; the lint-rule kind needs no analog. (#1346) (#1518) - fish-shell support in the post-install PATH suggestion. When a directory is not on your PATH, the installer now prints a fish-native
fish_add_path '<dir>'line alongside the zsh/bash suggestions (the previousexport PATH=…commands are inert in fish). It also stops the false-positive "not on your PATH" warning for fish users whosefish_user_paths/config.fishalready covers the directory, detected via a read-only probe of fish's config (no fish subprocess, no writes). No change for bash/zsh/PowerShell/cmd/Git-Bash users. (#727)
Fixed
- Project-local Claude Code install now produces
/gsd-<cmd>(hyphen) slash commands — the installer was writing command files to.claude/commands/gsd/<cmd>.md(subdirectory with bare names), causing Claude Code to namespace them as/gsd:<cmd>(colon form). The fix writes flatgsd-<cmd>.mdfiles at.claude/commands/level so Claude Code registers/gsd-<cmd>(hyphen form), matching hooks, statusline, and all cross-command references. Legacycommands/gsd/directories from prior installs are cleaned up on reinstall and uninstall, withdev-preferences.mdpreserved. (#1367) (#1367) execute-phasenow re-checks the worktree fork base at the start of every wave and resets the wave manifest between waves (#1369) — two compounding issues caused wave N+1 worktrees to be created from the stale pre-wave-N commit. First, theworktree.base-checkauto-degrade only ran once at initialize time; after Wave N merged and tracking commits advanced orchestrator HEAD pastorigin/HEAD, Wave N+1 worktrees were still forked fromorigin/HEAD(Claude Code's "fresh" base), causing both agents to immediately halt withFATAL: worktree base mismatchfrom theworktree_branch_checkguard. Second,WAVE_WORKTREE_MANIFESTwas never unset between waves, so wave N+1 would reuse the consumed wave-N manifest file, causing the step 5.5 manifest guard (#3384) to block on subsequent waves. Two safeguards fix this: step 0.5 in theexecute_waves"For each wave" loop re-runsworktree.base-checkbefore every wave's dispatch (when divergence is detected,USE_WORKTREESis overridden tofalsefor that wave); step 7c between waves unsetsWAVE_WORKTREE_MANIFESTso wave N+1 creates a fresh per-wave manifest, and re-assertsworktree.baseRef:"head"(idempotent) so the Claude Code harness re-reads the live HEAD on the next dispatch. The permanent fix remains settingworktree.baseRef:"head"in.claude/settings.local.json(see #683). (#1369)- Workflow temp files now randomize correctly on BSD/macOS — several workflows called
mktempwith templates whereXXXXXXwas followed by a.json/.mdsuffix (e.g.gsd-worktree-wave-XXXXXX.json,gsd-pr-body.XXXXXX.md). BSD/macOSmktemponly substitutesXXXXXXwhen it is the final path component, so those templates returned a literal, non-randomized path, letting concurrent workflow runs collide on the same temp manifest/body file (one run overwriting or consuming another's). The fix creates a suffixless temp then renames to add the extension — portable across BSD + GNU. Affected:execute-phase,quick,spec-phase,ship,profile-user. (#1520) (#1550) - Core-path file locks now verify the holder process is alive before stealing a stale lock (#1532) — the STATE.md write lock (
acquireStateLock) and the.planning/workspace lock (withPlanningLock) previously stole locks on a baremtimetimer with no liveness check, so a live-but-slow holder (e.g. a deep.planning/scan on slow NFS) could have its lock stolen mid-write, corrupting STATE.md or losing an update. Both locks now gate stealing onprocess.kill(pid,0)liveness with a deadman ceiling above the wait budget (pid-reuse backstop),withPlanningLockno longer force-steals a live holder on timeout (and can no longer leak an uncaughtEEXIST),writeStateMdcomputes its disk scan inside the lock, andacquireStateLockno longer leaks a file descriptor or strands an empty lock on a recoverable write error. The steal itself is now race-safe: a lock is never stolen while its body is still being written (the create→pid-write window), and stealing uses an atomic rename with an identity re-confirm so two waiters can no longer both reclaim the same lock and end up holding it concurrently. The uncontended path is unchanged. (#1532) normalizeNodePathnow maps pruned mise node paths to the stable shim (#1619) —resolveNodeRunner()bakesprocess.execPathinto managed.jshook commands. Node realpaths execPath, so under mise it resolves to<data>/installs/node/<ver>/bin/node— a concrete version mise prunes onmise up, after which every managed hook fails to spawn (No such file or directoryon every SessionStart and tool event), the same ephemeral-path failure #977 fixed for fnm and #3181 for Homebrew.normalizeNodePathnow rewrites a mise versioned install path to the stable sibling shim<data>/shims/node(.exepreserved on Windows) when that shim exists, deriving<data>from execPath so a customMISE_DATA_DIRworks, and falling back to the raw execPath unchanged otherwise. (#1619) (#1621)
fix(#1472): validate health is now workstream-aware — PROJECT.md and config.json are resolved from .planning/ root, while ROADMAP.md, STATE.md, and phases/ follow the workstream-scoped path; previously both sets were routed through planningDir() causing false E002/E003/E004/W003 when GSD_WORKSTREAM is set.
fix(#1454): validate health W017 no longer suggests removing the active session's worktree — stale-worktree findings are now skipped when the worktree path matches or is an ancestor of process.cwd(). (#1483)
frontmatter set/frontmatter mergeno longer destroymust_havesobject-lists — changing one frontmatter field (e.g.wave) silently dropped everyprovides:value and collapsedmust_haves.artifacts/.prohibitionsfrom a structured[{path, provides}]list into a malformed inline array, because the whole frontmatter was round-tripped through a lossy parse→serialize path that flattens object-list items to scalar strings. The write now preserves the original raw text for any structurally-unchanged top-level key and regenerates only the field that actually changed, so unrelatedmust_havesblocks survive verbatim. (#1572) (#1656)- Non-Claude runtime installs now resolve their own runtime and never attempt Claude-only worktree isolation — on any non-Claude install (Cursor, Gemini, Qwen, etc.) a runtime-neutral
.planning/config.jsonpreviously resolvedruntime=claudeand enabled git worktree isolation, which only Claude Code'sisolation="worktree"can honor — risking main-checkout edits while the workflow believed agents were isolated. Every non-Claude install now resolves its own runtime identity, defaultsworkflow.use_worktreestofalse, fails closed if worktrees are forced on, and runs plan/execute inline in the manager/autonomous flows since only Codex can background-nest the pipeline's subagents. (#1521) (#1537) - Phase-aware commands now resolve project-code-prefixed ROADMAP headings such as MANIFOLD-117, while the roadmapper is instructed to keep project_code out of phase headings. (#1456)
workflow.mvp_modenow accepted byconfig-set; three undocumented workflow keys added to references —workflow.mvp_mode,workflow.code_review_command, andworkflow.plan_chunkedwere consumed by planning-pipeline code but could not be set viaconfig-set(they were missing fromVALID_CONFIG_KEYS) or discovered via reference docs. All three are now in the schema and documented inreferences/planning-config.md. (#1500) (#1500)- Windsurf reinstall removes legacy .devin/skills/ artifacts — pre-#1615 installs wrote skills under .devin/skills/gsd-/ (Devin Desktop layout, #1085). #1615 moved Windsurf to .windsurf/workflows/ but never cleaned up the old layout. Reinstalls now remove GSD-managed .devin/skills/gsd- dirs; user-owned content is preserved. (#1631)
- adr-parser now classifies 9 previously-dropped punctuated ADR headers (Trade-offs, Non-Goals, Won't Do, Follow-up, How We'll Know, etc.) into their intended buckets instead of leaving them unmapped. (#1536)
- Add prototype-pollution guard to the workstream/root config merge (_deepMergeConfig) so a config.json with a proto/constructor/prototype key can no longer spoof unset config flags. (#1534)
- All GSD agents load on Gemini again — the Claude
Skill/SlashCommandtools were converted to an invalidskilltool that Gemini rejects, aborting the load of 22 of 34 agents. They are now excluded from the Gemini and Gemini-backed Antigravity agenttools:frontmatter, the same wayAskUserQuestionalready is. (#1394) (#1418) - Antigravity config-dir resolution no longer shadows the active runtime — when more than one of
~/.gemini/antigravity,antigravity-ide, orantigravity-cliexists, GSD now resolves to the directory it actually installed into (marked bygsd-core/VERSION) instead of whichever directory existed first. Fixes silent misresolution where a CLI user who also had the Antigravity-IDE dir present was sent to the legacy dir (regression from #217). (#1442) gsd-tools query agent-skillsno longer silently drops a configured agent's skills under cwd or workstream drift —cmdAgentSkillsnow anchors to the project root viafindProjectRootbefore loading config, so invoking it from a descendant subdirectory or with aGSD_WORKSTREAMthat has no scoped config correctly resolves the configuredagent_skillsblock. A newloadConfigResolved(cwd, options) → { config, source, degraded }function reports provenance alongside the config object:sourcedistinguishes'root' | 'workstream' | 'builtin-defaults' | 'global-defaults';degraded:truesignals a workstream was requested but its config.json was absent. The--jsonIR ofagent-skillsgains four new fields —configured(bool),reason('resolved' | 'not_configured' | 'configured_empty' | 'configured_unresolved'),source, anddegraded— making silent failures visible and testable. Aconfigured_emptyorconfigured_unresolvedagent emits astderr WARNING; an unconfigured agent stays silent. (Closes #1366. Part of #1411, P2 / #1415.) (#1424)findProjectRootnow respects explicitsub_reposconfig over implicit.git— when a parent workspace's.planning/config.jsonlists a child directory insub_repos, that declaration takes precedence over the child's own.git/directory. Previously, if the child had both.planning/and.git/, the.githeuristic fired first and resolved to the child rather than the parent workspace, making thesub_reposdeclaration ineffective. (#1422)
phases clear now refuses to delete phase directories with uncommitted changes — cmdPhasesClear runs git status --porcelain over the phases directory before executing any deletion. If uncommitted or staged-but-not-committed files are found it aborts with a clear error message, preventing silent data loss at new-milestone time. Pass --force to bypass the guard when archival is already complete. Non-git projects are unaffected. (#1447, data-loss fix) (#1484)
999.x backlog phases are now excluded from total_phases, and total_phases can correct downward — deriveProgressFromRoadmap counted all progress-table rows whose phase cell started with a digit, so a 999.1 Backlog row inflated total_phases by one per entry (#1445). The same overcounting occurred in getMilestonePhaseFilter (which feeds isDirInMilestone and phaseDirs) and in the roadmapPhaseCount loop in buildStateFrontmatter. All three sites now filter phase tokens matching /^999\b/, consistent with the existing exclusion in init.cts. Additionally, shouldPreserveExistingProgress included total_phases in its ratchet check, preventing the counter from decreasing once set too high — e.g. after a 999.x fix or a ROADMAP correction (#1446). total_phases is now always taken from the freshly derived value; only completed_phases, total_plans, and completed_plans retain ratchet behaviour. (#1490)
- Capability trust model was bypassable for project-scope third-party capabilities (#1459). The consent signal for a project-scope capability was its in-repo project ledger — but a project ledger is repo-plantable, so cloning or forging a repository activated that capability's executable surfaces (hooks, MCP servers) AND its declarative loop surfaces (steps, gates, contributions, federated config) AND its command dispatch with no user decision on the machine running it. The fix moves the authoritative consent signal off the repo tree into a new user-owned consent store at
${GSD_HOME||homedir()}/.gsd/consent.json(new leaf modulesrc/capability-consent.cts): a bounded, non-throwing, atomically-written store keyed by(realpath(projectRoot), capability id). The security binding is a recomputed full-bundle content hash (bundleContentHash— asha512over every regular file in the bundle, manifest AND artifacts AND identity, symlinks and non-regular entries rejected, bounded), not the repo-plantable ledgerintegrity(which is''for path/git/dir installs — a degenerate'' === '') and not the executable-only disclosure signature (which is constant for a declarative-only capability, so a repo-write attacker could swapcapability.jsonfor a malicious gate/contribution while consent still matched). The loader recomputes the bundle content hash at load and activates a project-scope overlay — declarative surfaces and command dispatch alike — only when it matches the consent record on this machine; any tamper (a swapped declarative manifest, an edited hook script, an empty-integrity local install) changes the hash and leaves the capability discovered but inactive (gsd capability listreportsstatus: inactivewith a reason). Global-scope overlays (under the user's own home) remain trusted without a per-project record. The lifecycle records consent (bound to the installed bundle's content hash) on a consented project install/upgrade and revokes it on remove, deriving the project root through one canonical helper (consentProjectRoot) shared by the install record site, the loader lookup, andtrust revoke, so an install from a sub-directory is not immediately inactive. The disclosure signature now also covers each MCP server'stransport/url/headers(non-stdio endpoints),env,cwd, and the raw args array, plus a command module'srouter, and every surface line is JSON-encoded (no delimiter-injection collisions) — so a swapped remote endpoint, header, environment (e.g.NODE_OPTIONS=--require evil.js), entry point, or non-string arg forces re-consent. The consent store serializes concurrent cross-project writes under a lockfile (no lost updates), enforces its record cap at write time, uses a collision-safe on-disk key for paths containing spaces, and tolerates a vanished directory on the durability fsync. New CLI:gsd capability trust listandgsd capability trust revoke <id> [--project <path>]. The loader's per-scope ledger read now goes through the shared bounded fd reader (a repo-planted FIFO ledger can no longer hang the loader) and reuses the ledger's sharedisValidLedgerEntryvalidator for committed-entry parity. Integration hardening: the overlay consumers (capability-state,loop-resolver, the federated config-loader/config-schema) now thread the consent home (GSD_HOME) explicitly to the loader so a consented project capability is never looked up at the wrong home; the loader's discovered-but-inactive warning carries a structuralkind: 'unconsented'discriminant thatgsd capability listfilters on (rather than matching the reason prose); a reconcile rollback that deletes a project-scope bundle also revokes its now-stale consent so an identical re-drop stays inactive;installCapability/upgradeCapabilitywarn on stderr when a project install supplies no consent store and when the consent-store write fails (the install still succeeds — a consent-store IO error never fails an otherwise-successful install);gsd capability trust listnow exposes the storeddisclosureSignatureandcontentHashfor diffing; and whenGSD_HOMEresolves equal to a genuine project root an in-repo bundle still requires a consent record (it is not deduped as trusted-global). Convergence hardening: the content-hash canonicalization — the security binding itself — is now injective and lossless. It length-FRAMES every component (an entry count, then per entry a type tag, the uint32 path length + path bytes, and for files the uint64 content length + the raw content bytes read via a new raw-Bufferreader, never a lossy UTF-8 decode) so neither aNULembedded in file content can fake a file boundary (the oldrelpath + NUL + content + NULframing was non-injective) nor can two binary artifacts that differ only in invalid-UTF-8 bytes collide onU+FFFD; empty directories are bound via typed directory markers so adding/removing one changes the hash.recordProjectConsent/revokeProjectConsentnow throw rather than perform an unlocked read-modify-write when the consent-store lock cannot be acquired (the lifecycle already treats a consent-write failure as non-fatal and warns, so an install still succeeds). The consent lock and the lifecycle lock are now ONE shared hardened primitive (src/capability-lock.cts) — process-start-time liveness identity + a hard deadman — so the consent lock can no longer stale-steal a slow-but-live writer (the old mtime-only 60 s steal could). Finally, the MCP disclosure signature now folds in a stable hash of the full server config object the writer persists (not only the whitelisted fields), so an upgrade that changes any host-honored field outside the whitelist (a futureenvFile/workingDir/launch option) still forces re-consent. A final convergence pass closes four residual gaps: (1) the loader's overlay-root dedup and the CB-3 "project root == global home ⇒ require consent" comparison are now keyed onfs.realpathSync(fail-safe topath.resolve), so a symlinkedGSD_HOMEaliasing the project root can no longer slip an in-repo bundle into the trusted-global slot — it still requires a consent record; (2) the loader readscapability.jsonthrough the shared bounded fd reader (regular-file + size cap) instead of a rawfs.readFileSync, so a project-planted FIFO/device or oversized manifest skips the overlay (warning) rather than hanging or OOM-ing the loop; (3) the PATH component of the content hash is now hashed from raw directory-entry bytes (a{ encoding: 'buffer' }walk, separator normalized at the byte level), so two bundle files whose names differ only in invalid-UTF-8 bytes (which a string decode would collapse toU+FFFD) no longer collide; and (4) thegsd capability trust revokeCLI now catches the consent-store lock-acquire failure and emits a clean, actionable error instead of surfacing a raw stack. A further convergence pass closes three more residual gaps and documents one irreducible limit: (1) the loader's user-owned consent gate now runs before the heavy pre-activation work (materializeHookFragmentsand cross-capability validation) for a project-scope overlay, so a forged in-repo bundle whosefragment.pathpoints at an in-bundle FIFO/oversized file is skipped (unconsented → inactive) without ever reading that fragment — closing a pre-consent hang/OOM (the gate's decision is unchanged; only the work-ordering moved), and as defense-in-depthmaterializeHookFragmentsnow reads each fragment body through the shared bounded fd reader (regular-file + size cap) so a FIFO/device/oversized fragment becomes an un-materializable-fragment validation error rather than a blocking read on any scope; (2)gsd capability listnow reads each projectcapability.jsonthrough the same bounded reader instead of a rawfs.readFileSync, so a project-planted FIFO/device or oversized manifest omits that entry's metadata and exits cleanly rather than hanging/OOM-ing the list; (3) the loader'scanonicalDirrealpath failure is now strictly fail-safe — a candidate that would be classified trusted-globalbut whoserealpathSyncthrows (a race/odd-FS, e.g. a symlinkedGSD_HOMEaliasing the project root) is reclassified conservatively to consent-requiredproject, so it can no longer park an aliased project tree in the trusted-global slot (a non-existent global dir still resolves to a harmless no-op scan). Finally, an honest in-code comment at the loader consent gate documents the irreducible filesystem-primitive TOCTOU residual: the content hash binds the bundle at verification time, but a local writer racing between that verification and the capability's later execution can still mutate the bundle files — closing this window would require fd-pinned execution or an atomic content snapshot (native support not available at this layer); any persisted tamper is still caught on the next load (mirrors the #1462 lock-release residual — a documented real limit, not a dismissal). A final deep-convergence pass closes three more gaps: (1) the realpath fail-safe is now two-sided — a global overlay root is trusted (consent-free) ONLY whenrealpath(global)ANDrealpath(project)BOTH succeed AND resolve to DIFFERENT physical paths; the prior one-sided rule (demote only a realpath-failed global candidate) still let a symlinkedGSD_HOMEaliasing the project root bypass consent when the GLOBAL candidate realpathed fine but the PROJECT candidate's realpath failed (the keys never collided, so the in-repo bundle stayed in the no-consent global slot), so distinctness that cannot be proven (either side throws, or both resolve equal) now demotes the global to consent-requiredprojectwhenever a genuine project root exists — while a genuinely non-existent project overlay (ENOENT) or a distinct real global root still stays trusted; (2)bundleContentHashnow bounds the enumeration itself — it streams each directory viafs.opendirSync+readSyncand throws the moment a cumulative entry counter exceeds the cap, BEFORE collecting/sorting a whole directory, so a malicious unconsented bundle with a huge single directory (or a deep tree) can no longer force unbounded memory/CPU before fail-closing (the cap is cumulative across the recursive walk; determinism is preserved by sorting the bounded set); and (3) a projectremoveno longer silently swallows the revoke-on-lock-failure throw —revokeProjectConsentthrows on a consent-lock failure (a stale consent record a byte-identical re-drop could reactivate against), soremoveCapabilitynow surfaces it via a stderr warning naming the record AND aconsentRevokeFailed/consentRevokeWarningflag on the result, which the CLI reports as a non-clean removal (telling the user to rungsd capability trust revoke). (#1473)
Capability --integrity is now verified or rejected per source, and hook commands are confined to the bundle — a supplied --integrity pin was silently dropped for npm, git, and local capability sources (only the tarball source verified it), so a user could believe content was pinned when it was not. npm now verifies the pin over the npm pack .tgz bytes; git and local sources, which have no single hashable artifact, now reject a supplied --integrity with an actionable error instead of ignoring it. Separately, a capability hook's relative script was written verbatim as the hook command, so it resolved against the working directory (not the capability bundle) at hook-exec time and a crafted relative path could escape the bundle; the command is now resolved against the capability's own install dir and realpath-confined to it, then written as an absolute path. That absolute command is consumed by a shell, which exposed two further problems now fixed: (1) a manifest could ship a file literally named run.sh; touch /tmp/pwn (filenames may legally contain ;, spaces, $, backtick, |, newline) and declare it as the hook script, so the emitted command injected a second shell command even though the file lived inside the bundle — the validator now rejects any hook script path outside a conservative [A-Za-z0-9._/-] allowlist (no whitespace, shell metacharacters, leading -, absolute path, or ..), failing the install/load loudly, and the confinement helper mirrors the same rejection defensively; (2) the absolute path begins with the install-home directory, which commonly contains spaces (e.g. /Users/Bob Smith/.claude/...) and word-split or broke when written unquoted — the emitted command is now POSIX single-quoted so the install prefix can neither break nor inject. (#1460) (#1481)
- The capability loader never crashes on a single malformed overlay, and untrusted manifest/tar reads are size-bounded (ADR-1244 D2 invariant) —
loadRegistrynow makes the WHOLE per-candidate overlay-processing body total: ANY throw from ANY validator or step (including the committedvalidateCapability, which dereferences a malformed array entry such asgates: [null]/steps: [null]/contributions: [null]before its shape check) drops just that one overlay with a skip-warning instead of escaping the loader, whilegatePointsOfis hardened to be total over null/non-array/malformed gates. The final canonicalbuildRegistrycompose stays guarded: a throw on the merged set falls back to the frozen first-party registry plus a warning, records each dropped gate-declaring overlay's declared gate as blocked (incompatibleGateCapIds/blockedGates) so a dropped blocking gate FAILS CLOSED, AND now clears_overlay.commandRootsin the fallback so no dropped overlay retains a stale command root. Previously a malformed-array throw or a compose throw escaped the loader and crashed every consumer (loop-resolver, config-loader, surface, capability-state, gsd-tools). On the source side, the capability resolver/staging now reads every untrustedcapability.json(tarball / npm / git / local) via the shared bounded fd-reader (regular-file + 8 MiB cap) instead of a rawfs.readFileSync, so an oversized or FIFO/non-regular extracted-or-local manifest can no longer OOM or hang the resolver; the fetch (realHttpsGet) bounds the downloaded response to 64 MiB; andstageValidatednow enforces ONE uniform aggregate byte-budget (MAX_STAGED_BUNDLE_BYTES, 128 MiB) over the STAGED bundle directory via a bounded streaming walk (cumulative byte + entry counters; symlink / non-regular entries rejected) at the common staging chokepoint — AFTER staging and BEFORE validation/promotion — so a huge source tree, git repo, npm package, or gzip/tar bomb is rejected (and its staging dir cleaned up) before promotion, uniformly bounding the RESULT ofcopyDirRecursive/git clone/npm pack/tar -xthat were previously only timeout-bounded.copyDirRecursiveitself is now STREAMING and BUDGETED: it enumerates each directory viafs.opendirSync+dir.readSync()(one entry at a time) and threads CUMULATIVE entry (MAX_STAGED_BUNDLE_ENTRIES, 100k) and byte (MAX_STAGED_BUNDLE_BYTES) counters through the recursion, failing closed the MOMENT either cap is exceeded DURING the copy — closing a residual where the formerfs.readdirSync(src, { withFileTypes: true })materialized the ENTIRE directory-entry array into memory at staging time (BEFORE the post-copy budget walk), so a hostile source whose tree held a directory of millions of tiny files (fetch < 64 MiB, but a colossal dirent array) could OOM the process during the copy before the budget could fail closed; the post-copy walk is retained as a cheap belt-and-suspenders re-verification on what actually landed in staging. The spoofable per-membertar-header size parse (parseTarMemberSize, which mis-anchored on BSDtar -tvowner/group columns such as aJangroup → fail-open) was REMOVED in favor of that non-spoofable staged-dir budget;assertSafeTarMemberskeeps its unambiguous traversal / symlink / hardlink NAME and TYPE guards. (#1461) (#1475) - Capability ledger: fail closed on corruption, with durable atomic writes and a race-safe install lock. A corrupt or unreadable
.gsd-capabilities.jsonis now left in place and surfaced (not silently overwritten) —install/update/remove/list/reconcilefail closed and report it, so a corrupt ledger can no longer wipe prior capabilities' tracked files and shared-config fragments (which previously left unremovable orphans insettings.json/hooks.json). Ledger writes are atomic and crash-durable (exclusive temp file +fsyncof file and directory + rename, with temp cleanup on failure). The per-capability lock is race-safe: a holder is identified by(pid, process start-time, hostname), so a reused PID cannot deadlock recovery and a verifiably-live holder is never stolen, with a hard deadman timeout for unverifiable or cross-host holders. Untrusted ledger and lock reads are bounded (regular-file + size caps; FIFOs/devices rejected) and validated through a single shared entry validator (prototype-safe ids, DoS length caps). (#1462, ADR-1244.) (#1469)
fix(#1478,#1479,#1480): prohibit ungrounded baselines, error-suppressing fallbacks, and stale-artifact authority in planner verify blocks (#1482)
/gsd:pr-branch now handles sub-repos defined in config — when planning.sub_repos is set, the command scans each sub-repo for uncommitted changes and offers to create a branch, commit, push, and open a companion PR per sub-repo. Previously, sub-repos were silently ignored because all git commands ran against the shell's current directory instead of the intended repo path. All sub-repo git operations now use git -C <repo> so no shell-state assumptions are made. (#667)
/gsd-new-projectAI Models prompt now exposes theadaptivemodel profile — both onboarding paths (auto-mode and interactive) listed only Balanced/Quality/Budget/Inherit, so theadaptiveprofile (role-based cost optimization across Claude/Codex/Gemini/OpenRouter/local) was unreachable through/gsd-new-projectdespite being a first-class catalog entry and documented in CONFIGURATION.md. Both prompts now use the proven two-question split (Q1: Adaptive / Standard tier / Inherit; Q2: Quality / Balanced / Budget) already shipped for/gsd:settings(#3784), and bothconfig-new-projectexample payloads listadaptive. (#1516) (#1654)--rawCLI commands no longer drop stdout on the error path — a command that emitted a JSON result/error envelope and then exited non-zero previously lost all of stdout (the output-capture wrapper discarded its buffer when the command threw to set a non-zero exit); the buffer is now flushed before the error propagates. (#1457) (#1457)gsd install/upgrade now recovers a malformed~/.gsd/defaults.jsoninstead of leaving it broken — adefaults.jsoncontaining a valid-JSON-but-non-object value (null,[], a number, or a string) bypassed the parsecatchand flowed through unrecovered:nullthrew a TypeError (swallowed by the outer guard, logging a confusing "Could not write" warning and leaving the file asnull), while[]/42/"str"silently kept their broken shape on every install. The non-Claude finishInstall step now resets any non-object parse result to a fresh{}before reading/writing it, so the file is repaired andresolve_model_idsdefaults normally. (#1661)- Shipped milestones with a retired/folded phase now reach 100% — a phase struck through in ROADMAP (marked
[x], with a directory but no completion artifact) was counted inprogress.total_phasesbut could never be counted complete, freezing the milestone below 100% (e.g. 5/6 = 83%) withstate sync --verifyreporting no drift. Both STATE counting paths (state jsonandstate sync) now exclude retired phases — detected from GFM strikethrough whose subject is the phase on a checklist/heading line — from both the phase-dir set and the heading count, via the canonical phase-id helpers so numeric, decimal, and project-code IDs match alike. (#1514) (#1568) - Codex installs no longer run with unsafe Claude-style worktree isolation — a Codex install with a runtime-neutral
.planning/config.jsonwas resolving its runtime as Claude and enabling git worktree isolation, which Codex'sspawn_agentcannot honor; the Codex fail-closed guard was also silently dead because runtime/worktree config was read JSON-quoted and broke shell equality checks. Codex-emitted workflows now resolveruntime=codex, defaultworkflow.use_worktreestofalse, and fail closed when worktrees are forced on. (#1515) (#1519) - Windsurf installs expose /gsd- commands in Cascade again* — Windsurf runtime installs now emit workflow files under .windsurf/workflows instead of dead skills-only artifacts. (#1615) (#1622)
- Capability
settings.jsonhooks no longer fire on every tool and no longer fail when non-executable — installing a capability that declared a tool-scopedPreToolUse/PostToolUsehook wrote the entry with nomatcher, so a guard intended for onlyWrite|Editfired on every tool call (includingBash) and a fail-closed guard could block the whole session; the emitted command was also a bare script path, so a.js-family hook delivered viagit/tarball that lost the executable bit failed withPermission deniedon every matching call. Install now honors a declaredmatcher(absent = match-all, unchanged for existing capabilities) and emits anode-prefixed command for.js/.cjs/.mjshooks so they run regardless of file-mode bits. (#1634) (#1638) /gsd:secure-phasenow honors the configured ASVS level and block threshold — the security auditor previously received unsubstituted{SECURITY_ASVS}/{SECURITY_BLOCK_ON}placeholder text because secure-phase.md never assigned those variables. It now resolvesworkflow.security_asvs_levelandworkflow.security_block_onfrom config (--raw) before the auditor handoff. (#1625) (#1633)- Phase transitions now require fresh canonical verification - implementation-complete phases no longer advance when verification is missing, gap-bearing, human-pending, or stale relative to phase summaries. (#1548)
- The security audit gate now respects
workflow.security_block_onseverity —/gsd:secure-phasepreviously blocked phase advancement on any open threat regardless of severity, so the documentedsecurity_block_onthreshold had no effect (and the auditor's block vocabulary didn't even match the config enum). Threats now carry a per-threat Severity (critical|high|medium|low), and only open threats at or above the configuredsecurity_block_onseverity count toward the blocking gate (SECURITY.md threats_open);nonedisables blocking, and a missing/unparseable severity fails closed as critical. (#1626) (#1635) verify codebase-driftnow readsworkflow.drift_actionandworkflow.drift_thresholdfrom the correct nested config shape — previously both keys silently no-oped becauseloadConfig()returns a flattened object andconfig?.workflowwas alwaysundefined. (#1504)check.decision-coverage-planno longer false-passes when CONTEXT.md decisions use the titled-colon bullet form —parseDecisionsrecognized the colon-immediate (- **D-NN:** text) and em-dash (- **D-NN — title** body) forms but dropped the titled-colon form (- **D-NN: Title.** body, where a title sits between the colon and the closing**) via the parse-miss guard. When all decisions used the titled convention, the parser returned 0 decisions and the coverage gate passed vacuously. A third per-form regex (checked last, a strict superset of the colon form) now parses the titled-colon form; id and[tags]trackability are honored. (#1665)npm versionno longer leavescapability-registry.cjsstale — theversionnpm lifecycle script now regenerates and stages the capability registry after stamping new version strings into all capability manifests, preventing the 1.6.0-rc regression wheregen-capability-registry.cjs --checkfailed. (#1498) (#1499)- Antigravity installs all GSD slash-command skills where AGY can discover them — concrete skills such as /gsd-progress and /gsd-verify-work now land directly under the Antigravity skills directory instead of router-nested folders. (#1614) (#1616)
frontmatter seton an object-list field now fails closed instead of silently doing nothing — settingmust_haves(or another object-list field) to a value whose lossy parse projection matched the original's was a silent no-op: the command reported{updated:true}but the change never applied (the writer's scalar-only parser had flattened both to the same shape).frontmatter setnow detects a no-op write for dict-valued fields and surfaces a clear error directing the user to edit the file directly. Scalars and scalar arrays round-trip faithfully, so idempotent sets of those still report{updated:true}(no false positive). (#1664)config-setnow rejects invalid config values instead of storing them silently — out-of-enum strings, JSON array/object coercion (e.g.["high"]stored as an array in a scalar key), and wrong-typed values for capability-registry-owned keys are validated against each key's declared schema at set time. Previously these were accepted and persisted, mis-configuring GSD. (#1628) (#1632)- OpenCode and other AGENTS-native runtimes now get a root
AGENTS.mdfrom/gsd:new-project— the workflow hardcoded a codex-only branch that sent every other runtime to.claude/CLAUDE.md, a location OpenCode never loads. A sharedgetProjectInstructionFile(runtime)policy (claude→.claude/CLAUDE.md, codex/opencode/kilo/kimi→AGENTS.md, copilot→.github/copilot-instructions.md, antigravity/gemini→GEMINI.md) is now the single source of truth consumed by both the new-project workflow and the generate-claude-md path, with a parity test guarding drift. (#1574) roadmap upgradenow rejects an unsupported or malformed--conventionvalue (including the--convention=form) instead of silently running the milestone-prefixed migration, and no longer hard-exits inside the command-routing hub. (#1539)phase completeno longer duplicates a By-Phase row when the phase number's padding differs — completing a phase by its unpadded number (e.g.phase complete 5) against an existing zero-padded By-Phase row (| 05 |) appended a second| 5 |row instead of updating it, double-counting the phase in any column sum. The row matcher now canonicalizes a numeric phase to its integer form (matching5,05,005in either direction), so the existing row is upserted regardless of padding. (#1663)- Non-Claude installs no longer rewrite an explicit
resolve_model_ids: trueto "omit" — Codex, OpenCode, Gemini, and the other non-Claude runtimes were silently clobbering the deliberate opt-in to full materialized model IDs on every install/upgrade, so generated agent manifests inherited the active chat model instead of pinning the resolved model. The finish step now only defaultsresolve_model_idsto "omit" when it is absent or falsy; an explicittrueis preserved. (#1569) (#1653) - A failed
roadmap upgrade --applynow actually rolls back .planning/ even when it is gitignored (commit_docs:false), instead of reporting a successful rollback while leaving the workspace half-migrated. Rollback is surgical and no longer runs a whole-repo git reset --hard. (#1543) - Codex runtime no longer crashes on startup — every
gsd-toolscommand previously aborted withCannot find module '../../../package.json'on Codex, whose runtime root has nopackage.json, because a module in the loader chain did a top-level require of it. The version emitted into Hermes skill frontmatter is now sourced lazily from the installedgsd-core/VERSION(validated semver), sogsd-toolsloads on every runtime and never emitsversion: undefined. (#1383) (#1409) /gsd-*commands in Windsurf Cascade resolve their command bodies — Windsurf slash-command workflows delegate to canonical command bodies at gsd-core/commands/gsd/X.md, but the install never copied those files. Commands appeared in the/menu yet silently failed when invoked because the LLM was told to read a missing file. Installs now copy commands/gsd/*.md into the workflow delegation target. (#1630)query agent-skillsno longer returns empty output on Windows — the plain (non---json) path wrote the<agent_skills>block then immediately calledprocess.exit(0), which truncated the async stdout buffer on Windows pipes/files so every${AGENT_SKILLS_*}workflow capture expanded empty and configured per-agent skills were silently dropped. It now flushes synchronously via the samewriteAllSynchelper the--jsonpath uses. (#1400) (#1410)phase completenow updates the By-Phase table on CRLF (Windows) STATE.md files — the By-Phase table matcher required bare\nline endings, so a STATE.md written or hand-edited with CRLF (\r\n) was treated as having no table: the completed phase's row was never upserted (and, with the velocity-from-table derivation, the total went stale). The matcher is now CRLF-tolerant (\r?\n) on the header/separator/lookahead, so CRLF STATE.md files are handled identically to LF. (#1662)- clean up stale get-shit-done paths in Codex and Kimi skill mirrors on upgrade (#1453) (#1453)
- add phase.list-plans to gsd-tools — the command was referenced in agents/gsd-plan-checker.md but was missing from the router, causing 'Unknown phase subcommand' on every invocation (#1437)
- roadmap analyze no longer reports phantom missing_phase_details for milestone-prefixed (M-NN) phase IDs (#1552)
workflow.security_asvs_levelnow actually scales security rigor — it was display-only (the planner hardcoded ASVS L1 and the auditor only echoed the level), so L2/L3 behaved identically to L1. The configured ASVS level now scales both planner threat-disposition rigor and auditor verification depth (L1 grep-presence → L2 boundary/vector checks → L3 end-to-end trace), defined in a newreferences/security-asvs-levels.md; the secure-phase clean-phase short-circuit now spawns the auditor at L2/L3 so deep verification runs even when the preliminary grep classification is clean. (#1627) (#1636)- Atomic file writes now retry a transient rename lock on Windows (a reader holding the target open) instead of falling back to a non-atomic write that could let a concurrent reader observe a truncated STATE.md/ROADMAP.md. (#1541)
- Misconfigured agent skills no longer fail silently — when an agent's configured
agent_skillspaths all fail to resolve (e.g. a missingSKILL.md),gsd-tools query agent-skillsnow emits an aggregate warning to stderr and adds awarnings[]field to its--jsonoutput, instead of returning an empty block with no signal. (#1376) (#1376) - Decision-coverage gate now reads markdown-header and em-dash decisions, and fails loud when it can't parse them —
check.decision-coverage-plan(a blocking gate) andgap-analysispreviously extracted zero decisions from a populated CONTEXT.md that recorded its decisions under markdown headers (## Locked decisions) or with em-dash bullets (- **D-1 — title**), and silently reported a clean pass — so real decisions went un-checked. Decisions in those shapes are now recognized, and when decision-shaped content cannot be parsed (or a- **D-NN**bullet is malformed), the gate fails loud with a format-mismatch message instead of passing. (#1386) (#1386) phase completeno longer double-counts Total plans completed velocity on re-run — re-runningphase completeon an already-complete phase incremented the velocity total each time (2 -> 4 -> 6 ...), because the metric re-read the cumulative total and blind-added the phase's plan count on every invocation. The total is now derived from the By-Phase table's Plans column (the same source the table upserts against), so re-completing a phase upserts the same row and the sum stays stable — and a hand-edited inflated total self-heals to the true sum on the next completion. (#1582) (#1655)- verify schema-drift now resolves the target phase by its canonical token instead of substring containment, so a non-existent phase no longer silently matches a token-superstring phase (e.g. "1" matching "11-expansion") and runs the drift gate against the wrong phase. (#1640)
Security
- Prompt-injection defence extended to the untrusted-input surface (LLM-playbook principle 12) — the read-injection scanner (a pattern-based pre-filter) now also scans WebFetch/WebSearch output (closing the largest untrusted channel at ingress), and the 10 research/doc-ingest agents (issue #1577 AC #2's named eight plus
gsd-ai-researcherandgsd-domain-researcher, both web-ingress) isolate fetched/read content as data-not-instructions via a shareduntrusted-input-boundaryreference — this prompt-level boundary is what keeps an injection from being followed. An opt-insecurity.injection_blocking(registered config key; default advisory, unchanged) upgrades HIGH-confidence detections to a PostToolUse circuit-breaker: since the hook runs after the fetch,decision: "block"halts the agent's next step rather than redacting the already-fetched content (it is not a redactor). Based on arXiv 2506.05739 (PPA), 2507.15219 (PromptArmor), 2504.20472, 2503.00061. (#1585) - Third-party capability trust gate (ADR-1244 Phase 4) — installing a capability from a git/npm/tarball/local source now discloses every executable surface it ships (hooks, command modules, MCP servers, with the actual commands) and requires explicit consent before anything is promoted; integrity (sha512) and
engines.gsdare verified before any code is staged, install never executes capability code, and reservedgsd-/gsd-core-/anthropic-namespaces are refused.capabilities.strict_known_registriesgates which sources may be installed ([]= local-only lockdown; host-based allowlist otherwise) andcapabilities.auto_updateis off by default, re-prompting whenever a new version's executable set changes. An install ledger makesremovesurgical (strips only the capability's own shared-config entries, preserving your hand-edits) andupdatean atomic, crash-safe stage-then-swap. (#1449) (#1449)
[1.5.0] - 2026-06-17
Added
gen-capability-registrynow rejects duplicate artifact producers at the same Loop Extension Point — if two capabilitystepsdeclareproduces: [<same artifact>]at the same point, the generator throws at gen time naming the artifact, the point, and the producing capability ids, instead of letting the topological sort pick a winner silently (which left ADR-857 Decision #6's data-flow contract undefined). The check counts distinct(capId, stepIdx)producer steps, so a single step listing an artifact twice does not false-positive. ADR-894 §4's enumerated cross-capability invariant list gains the artifact-production-uniqueness rule. (#1123) (#1131)gsd-tools drift-guard— deterministic plan-drift severity/authority decisions (ADR-22). The plan-review source-grounding pass now classifies cited-symbol drift through a tested seam (5-rung authority ladder,grep→intelauto-upgrade, severity mapping, rung≥3 hard-block) instead of re-deriving the rules from workflow prose on each run. (#1190) (#1242)/gsd-progress --next --auto --convergenow routes planning through plan-review convergence. ADR-15's designated primary convergence surface is wired into the progress/next workflow (previously only/gsd-autonomous --convergehonored it; on/gsd-progressthe flag was silently dropped). Accepts--cross-aias an alias plus reviewer flags and--max-cycles N, and is gated onworkflow.plan_review_convergence. (#1190) (#1237)
gsd-tools query teams-status + a plan-phase warning detect claude-code agent-teams — GSD's multi-agent orchestration can stall under claude-code's experimental agent-teams (a subagent's completion can fail to route back to the orchestrator). A new read-only query teams-status command reports { active, runtime, env_present, source } (and --active for a clean shell guard), and /gsd:plan-phase now emits a single non-fatal warning when agent-teams is detected, recommending you disable it for GSD workflows. The detector only activates on the claude runtime with CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS strictly truthy — every other runtime and the teams-off path are completely unaffected. (#1355) (#1371)
- spec-phase: prohibition probe — a prose-orchestrated Step 5.6 that surfaces the unwritten must-NOT constraints (values/safety/ethics) a feature could silently become but the spec never forbids. Two stages per requirement: an adversarial recall question ("what could this silently become that the author would NOT want?") then a one-pass precision classifier that drops routine engineering and keeps genuine prohibitions. Confirmed prohibitions become NEGATIVE SPEC acceptance criteria carrying a
test/judgmentverification tier, which plan-phase lifts into themust_haves.prohibitionssibling block (truthsuntouched). Judgment-tier items soft-gate at verify time (never silent, never hard-halt); unwired test-tier items fail closed. The recall stage is model-driven (no compiled engine); canon-bound concerns (OWASP/GDPR/fairness) are referred to/gsd:secure-phase. Additive and optional: existing SPECs without a Prohibitions section remain valid. Second adapter of theprobe-coreresolution model (ADR-550 Decision 7). (#1149) - Optional
## Business Contextsection in the PROJECT.md template — a four-field block (Customer, Revenue model, Success metric, Strategy notes) for monetized or customer-facing projects, positioned between Core Value and Requirements. Optional by default (an HTML comment tells non-business projects to delete it), capped at four one-line fields to stay a constraint reference rather than a business plan, and reviewed at each milestone by/gsd-complete-milestonewhen present. (#72) (#756) - Async external jobs can now defer an Execute step legally (
external_job_waiting). An Execute step that dispatches a long-running external job and commits a.planning/async-jobs/<job>.jsonmanifest — deferringSUMMARY.md— is now recognized as a legal deferred state, not an illegal partial-plan state.execute-phasesafe-resume,resume-project, andpause-workreconcile against the manifest instead of re-dispatching (which would duplicate the external compute). This defines the versioned, scheduler-agnostic manifest stability contract consumed by the core loop; the scheduler adapter that produces manifests is the capability half (#1164). (#1165) (#1221) - MemPalace memory capability (opt-in) — adds cross-session/cross-project recall and verbatim+temporal-KG capture at GSD loop boundaries via the MemPalace MCP server and CLI; disabled by default, skip-on-error. (#1201) (#1201)
- spec-phase: spec-completeness edge-probe — a taxonomy-driven Step 5.5 that walks each SPEC requirement against a closed 8-category edge taxonomy (boundary, adjacency, empty/degenerate, encoding, ordering, precision, idempotency, concurrency), proposes concrete candidate edges, and resolves each to covered/dismissed/backstop/unresolved. covered edges add acceptance criteria the planner lifts into must_haves.truths; a soft gate flags unresolved edges. Additive and optional: existing SPECs without an Edge Coverage section remain valid. (#584)
phase uat-passedpredicate — new runtime-neutral command evaluates HUMAN-UAT results with markdown-aware parsing (ignores frontmatter, fenced code, blockquotes, and HTML comments) and reports pass only when every required check passes. (#1063) (#1063)- Bug-report issues that lack a valid GSD Version are now auto-closed on open by a new
version-gate.ymlGitHub Actions workflow. GitHub Issue Forms only enforcerequired: truein the web UI, so issues filed via the REST API,gh issue create, or AI reporters can arrive without a version; values likeidk,_No response_, or an empty field are treated as missing. Affected issues receive a closing comment with instructions to add the version (e.g.1.18.0) and reopen; maintainers can add theversion-exemptlabel to opt an issue out. (#1181) - Kimi CLI runtime support is now documented and installable — users can install global GSD Agent Skills with
--kimi --global, invoke them as/skill:gsd-*, and launch the generated custom agent explicitly withkimi --agent-file. The custom-agent (--agent-file) surface targets the legacy/Pythonkimi-clicontract; newer Kimi Code (@moonshot-ai/kimi-code) consumes the same/skill:gsd-*skills via--skills-dirinstead. (#743) agent_skillscan now reference Claude-Code plugin-provided skills via the namespacedglobal:<plugin>:<skill>form (e.g.global:coderabbit:code-review). On the Claude runtime the agent's skills block emits a by-name Skill-tool load directive that resolves the plugin skill (no plugin-cache path is read); path-resolvable skills keep the existing@-include unchanged; on non-Claude runtimes a namespaced entry is skipped with a warning. The 22 agent_skills-consumer agents now carry theSkilltool so they can load plugin-provided skills. (#1261)gsd-tools capability set— turn capabilities on/off and gate hooks from one command. Adds the write side of the capability system (ADR-857/ADR-1213):capability set <id> --on|--offtoggles a capability through the runtime surface (the canonical on/off switch) and--gate <key>=<true|false>toggles a hook within an enabled capability, then re-resolves and reports — so disabling a capability is consistent across surface and config ("off means off") as a write-time invariant./gsd:settingscapability hook-gates now route through it. (#1213) (#1225)
Changed
- Added an opt-in
anthropic-fablemodel policy provider preset for Claude Fable 5 high-budget routing while preserving the existing Anthropic Opus 4.8 defaults andanthropicprovider preset. (#1014) (#1015) - Windsurf/Devin workspace skills now install to the canonical
.devin/skills/directory — fresh workspace installs write skills under.devin/skills/(Devin Desktop's documented preferred location) instead of.windsurf/skills/; the legacy.windsurf/skills/layout is still recognized. The global~/.codeium/windsurf/skills/path is unchanged. (#1093) (#1093)
loadCentralConfigKeys now fails loud on a malformed central config-schema instead of silently returning an empty Set — ENOENT (the schema legitimately absent) still returns an empty Set silently, but a JSON parse error or any other read failure now writes a prominent stderr warning naming the schema file and throws ExitError(1). Previously a single catch (_) swallowed parse errors too, so a merge-conflict marker or truncated write in config-schema.manifest.json made every capability config key look non-central — the config-key collision / pending-migration gate fired zero warnings and --check passed clean, defeating the gate invisibly. (#1124) (#1131)
- Capability hook rendering now consumes resolved Capability State —
gsd-tools loop render-hooksuses the same installed/surfaced/configured state reported bygsd-tools capability state, so disabling a migrated capability at the runtime surface removes its workflow hooks even when config defaults are enabled. Migrated capability config keys remain accepted through the generated capability registry/federated config path instead of duplicated centralVALID_CONFIG_KEYSentries. (#1136) (#1153)
Added ADR-857 Phase 6 capstone conformance coverage so migrated Capability activation keys cannot be read directly from host loop workflows, Capability-owned config keys stay out of the central schema, and the host loop workflow size budgets remain documented. The verify-work UI automation preflight now resolves UI activation through the Capability hook registry instead of reading workflow.ui_phase directly. (#1158)
- ADR-857 phase 6 complete: optional features are now Capabilities, not inline loop branches.
tdd,schema-gate,drift,gap-analysis, andprofile-pipelineare migrated out of the five-step host loop into declarative Capabilities (loop hooks + a command family); their config keys are federated to capability ownership; and theplan-phase/execute-phaseworkflow bodies shrink accordingly. Two previously-declared-but-dead capability gates now actually fire — the security ship-time gate (ship:pre) and the UI safety gate (execute:wave:post) — and the phase-6 conformance gate is hardened to be un-gameable (rejects empty stubs, requires loop-body shrink, verifies hook dispatch and gate-result contracts). Behavior is preserved, verified across five adversarial review passes. (#1139, #1167, #1168, #1169) (#1183)
Test-tier prohibitions are now a real, provable gate instead of a permanent, unsatisfiable gaps_found — the deferred ENFORCEMENT half of ADR-550 Decision 5d (the "heavy half" that #644 / PR #1149 deferred) has landed. A new deterministic check prohibition-enforcement sub-command (authored as src/prohibition-enforcement.cts, compiled by build:lib to the gitignored gsd-core/bin/lib/prohibition-enforcement.cjs) is the missing PRODUCER: it locates the wired mechanical check, runs it for a genuine non-vacuous pass, builds enforcementEvidence, and emits the dispositionForProhibition() verdict. The previously-unreachable green branch in dispositionForProhibition() is now reachable from the live pipeline — a test-tier prohibition with a genuinely-passing wired check disposes green and can reach passed, while a missing, non-attested, or non-passing check hard-gates (flagged, never green → gaps_found) in BOTH interactive and autonomous modes (ADR-550 D4 / D3). verify-phase.md wires the consumer; the green/fail-closed policy in src/probe-core.cts is untouched. Both wired-check kinds are accepted (ADR-550 D2): a node --test negative test (requiring a real reported test — an empty file, which node --test counts as one passing "test", does NOT green) AND a lint/AST rule run as eslint --format json filtered by ruleId (so plugin rules like local/* load — bare --rule cannot), anchored on the in-tree local/no-source-grep rule (dogfooding, ADR-550 D4). This enforcement seam is the concrete instance of ADR-857 open-question §147 and lands on the core verify rail, never in capabilities/ (D6). (#1259)
Honest scope — failFirst is caller-attested, not yet machine-proven. This lands the execution + non-vacuous-pass half: the producer requires the caller to attest failFirst: true and the check to genuinely run and pass. It does NOT yet independently prove the check fails-on-violation (the literal regression-must-fail-first property) — cheap proof of that at verify time needs running the check against a known violation fixture, which is a tracked follow-up (#1279). The red-first property currently rests on caller attestation, surfaced transparently in the evidence record.
Correction to the issue body (#1259): the issue's "96 invalid/error negative-proof cases" figure is wrong. For the no-source-grep anchor specifically, the genuine regression-must-fail-first proofs are its two invalid cases (the .includes() and .match() blocks) in tests/eslint-rules.test.cjs — not 96. The anchor argument is unaffected (those two cases ARE real fail-first proofs); only the count was off. (#1273)
- The test-tier prohibition gate now has a deterministic SOURCE for its wired check — a resolved
test-tiermust_haves.prohibitionsitem MAY carry an optionalcheckdescriptor authored at spec-phase: the flat-scalar keyscheck_kind(node-test|lint-rule),check_target, andcheck_rule(lint-rule only).projectProhibitionsprojects these scalars deterministically and verify-phase reads them back (viadescriptorFromProjection) to locate the check handed tocheck prohibition-enforcement— so a wired, passing test closes the gap with zero manual descriptor authoring (previously the verify-phase LLM had to invent{kind, target, rule}each run, #1259). This extends the ADR-550 Decision 3 prohibition-item shape (ratified in a dated 2026-06-15 ADR-550 addendum). The descriptor is optional and fully backward-compatible — a prohibition with no descriptor parses and disposes byte-identically to today — and fail-closed: a partial, invalid, or absent descriptor falls through to the producer's existing fail-closed locate, never a silent green. The descriptor is represented as flat scalars (not a nestedcheck:{}object) to keep the sharedparseMustHavesBlockround-trip regression-free. Out of scope: machine-proven fail-first (#1279) and thedispositionForProhibitionpolicy stay unchanged. (#1278) (#1301)
Test-tier prohibition fail-first is now MACHINE-PROVEN, not caller-attested — the deferred literal regression-must-fail-first property of ADR-550 Decision 4 (the gap #1259 / PR #1273 left as a tracked follow-up) has landed. The check prohibition-enforcement producer (src/prohibition-enforcement.cts, compiled by build:lib to the gitignored gsd-core/bin/lib/prohibition-enforcement.cjs) no longer trusts the caller's failFirst attestation: before a clean, non-vacuous pass can dispose a test-tier prohibition green, the new defaultProveFailFirst prover independently RUNS the wired check against a KNOWN VIOLATION and confirms it goes RED. Attestation is gone from the green AND (passed = proof.provenFailFirst === true && run.passed === true); any other outcome — passes-on-violation, can't-prove, throws, times out, or no violation source — hard-gates in BOTH interactive and autonomous modes (ADR-550 D4 / D3). The evidence record gains a failFirstProof field recording HOW fail-first was proven (FF-07). A caller can no longer green a toothless check.
The violation is sourced from a new descriptor field, CheckDescriptor.violationFixture — an author-supplied path to a known-bad subject. For a lint-rule the prover lints that fixture and requires the rule id to appear in the JSON report (the rule must have teeth); for a node-test the prover spawns the negative test with the subject injected through the GSD_PROHIB_SUBJECT env convention and requires a NON-VACUOUS red — # fail >= 1 AND a failing test named distinctly from the file (isNonVacuousNodeTestRed), so a violation fixture that merely CRASHES the test at load is not mistaken for the negative assertion firing red (symmetric with the clean-pass non-vacuity guard). The node-test prover also requires the violationFixture to EXIST (resolved against cwd) before spawning — a missing/typo'd path fail-CLOSES rather than letting an honest test's ENOENT crash forge a green (symmetric with the lint path's file-result guard). The deterministic spec→verify path composes end-to-end: a fourth flat scalar check_violation_fixture is projected by projectProhibitions and read back by descriptorFromProjection (rides both kinds), so a prohibition authored with all four check_* scalars machine-proves fail-first and greens through the projection alone — zero hand-authoring at verify time (#1278 + #1279 + #1346; round-trip pinned by a fast-check property + CHK-03(D) + an end-to-end COMPOSE capstone). One documented residual remains under #1346: the node-test proof confirms the fixture exists and the check reds, but cannot generically prove the red was caused by the subject's content rather than by the env merely being set. The lint-rule path is fully shippable now and is dogfooded against the in-tree local/no-source-grep rule; the node-test path ships its mechanism (a fixture-bearing descriptor IS machine-proven) and is exercised by SYNTHETIC temp fixtures — there is no live in-tree node --test prohibition to dogfood. CheckDescriptor.failFirst is DEMOTED, not removed (FF-08): it is kept as a non-authoritative hint so the #1259 route-JSON shape and the CheckDescriptor type stay backward-compatible mid-migration, but no path greens on it alone. The green/fail-closed policy in src/probe-core.cts (dispositionForProhibition, reads only evidence.length > 0) is untouched; the evidence array shape is additive. This closes ADR-550's D5d follow-up — see the dated 2026-06-15 ADR-550 addendum. (#1279)
PR-review flag — GSD_PROHIB_SUBJECT + violationFixture are PROPOSED, renamable conventions. Both are net-new surface with ZERO live in-tree consumers (no in-tree node-test prohibition yet; node-test proof runs only on synthetic test fixtures, the real dogfood stays the lint-rule). They are forward-looking scaffolding, so a later rename — or replacing the env var with an argv — is a mechanical, zero-migration find/replace. Surfacing them here so the maintainer can ratify, rename, or replace them at PR review with no migration cost, exactly as #1278's ADR addendum was reviewed at PR time. The failFirst demotion is likewise open to weighing outright removal; the keep-as-hint rationale is recorded in the ADR addendum. (#1314)
- Read-only verifier/auditor agents now ship a Claude-Code
disallowedToolsdeny-list — the installer injects a framework-level write-tool deny-list into the Claude copies of the read-only verifier/auditor agents (gsd-verifier, gsd-plan-checker, gsd-integration-checker, gsd-doc-verifier, gsd-eval-auditor, gsd-ui-auditor, gsd-ui-checker) so write actions are blocked even if a tool grant is inherited. Injected for Claude only; other runtimes are unaffected. (#1081) (#1081)
Antigravity workspace skills now install to the canonical .agents/ directory — fresh installs write workspace artifacts under .agents/ (the Google-Codelabs-documented base) instead of .agent/; the legacy .agent/ layout is still recognized so existing installs keep working. The global ~/.gemini/antigravity/ path is unchanged. (#1090) (#1090)
devin-desktop runtime alias for the Windsurf→Devin Desktop rebrand — the windsurf runtime now also answers to devin-desktop (CLI --devin-desktop), aiding discoverability after Cognition rebranded Windsurf as Devin Desktop. All paths are unchanged — global skills still install to ~/.codeium/windsurf/skills/. (#1086) (#1086)
- Remove dead
loadConfigexport fromconfiguration.cts— superseded byconfig-loader.cts(ADR-857 phase 2e, #885). All live callers already importloadConfigfromconfig-loader.cjsor thecore.cjsback-compat re-export; exhaustive grep confirms zero callers importing it fromconfiguration.cjs.configuration.cjsnow provides only the pure normalization and defaults primitives (normalizeLegacyKeys,mergeDefaults,migrateOnDisk,CONFIG_DEFAULTS) thatconfig-loader.cjsdepends on. (#893) (#893) - audit(#779): correct stale model-catalog IDs verified against live provider sources. The gemini opus default
gemini-3-pro→gemini-3.1-pro-preview(the baregemini-3-proID is undefined in gemini-cli source — onlygemini-3-pro-preview/gemini-3.1-pro-previewexist) and the codex sonnet defaultgpt-5.3-codex→gpt-5.4(deprecated per OpenAI's Codex models page); the same two IDs are also updated in thegoogle/openaiprovider-preset entries.qwen3-coder-nextwas verified valid (callable on Alibaba Model Studio) and left unchanged. Adds a regression guard against the retired IDs and a sourcing/verification note in CONFIGURATION.md. Catalog IDs are internal defaults; users who pinned the old IDs must update their config. (#1047) - INVENTORY.md no longer carries
(N shipped)count scalars — the hand-maintained absolute counts collided silently on merge (two branches each bumping the same integer to N+1 while the merged tree held N+2), red-flagging CI on the merge commit across all platforms. The manifest's name-set is now the sole registry, anchors are count-free and stable, and a guard test blocks re-adding a count. (#1179) (#1179) - Edge-probe
precisionprobe text now names tie-breaking / rounding-mode (half-up vs half-to-even, ceil/floor/truncate), so a surfaced precision edge cues the most common rounding failure mode. Prose-only; firing rule and the 8-category core unchanged. (#1108) - Capability manifests now declare runtime compatibility through a validated runtimeCompat contract, and runtime descriptor interpreters now read artifact layout, skills-home, and hook-surface facts directly from runtime Capability descriptors instead of parallel runtime-name allowlists or fallbacks. This preserves existing supported runtime behavior while making future descriptor-backed runtimes additive. (#1157)
- Planning-time research, AI integration, and pattern mapping now participate through Capability declarations and rendered plan:pre hooks, with developer documentation for building GSD capabilities. (#1141)
- The planner now blocks plans that would self-trip their own verify gate — when an acceptance criterion negative-greps for a literal (
grep -c 'LIT' file == 0) and that same literal appears verbatim in an<action>body, plan creation now fails at write time instead of letting the executor waste cycles on a comment-text echo at commit time. Unquoted/ambiguous grep targets warn instead of failing; add<!-- planner-discipline-allow: LIT -->to allowlist a legitimate occurrence. (#1062) (#1062) - Namespace router skills now nest their concrete sub-skills at install time (#69). On runtimes with non-recursive skill loaders (Claude global, Cline, Qwen, Hermes, Augment, Trae, Antigravity) the installer emits the 6
gsd-ns-*routers as the only top-level skill bundles and nests the ~61 concrete skills under<router>/skills/<name>/SKILL.md, cutting the eager skill-listing overhead to ≈6 entries. Concrete skills stay reachable via the router'sRead skills/<name>/SKILL.mdrouting table. Breaking: on those runtimes the concrete skills are no longer invocable by bare name through the Skill tool / top-level listing — route via the namespace router (or the unchanged/gsd-*slash command where a commands surface exists). Legacy top-levelgsd-<concrete>/skill dirs are removed on upgrade. Recursive/unconfirmed loaders (Cursor, Codex, Copilot, Windsurf, CodeBuddy, OpenCode, Kilo) keep the flat layout. (#883) - Graphify now respects surface/profile state, not just
graphify.enabled—gsd-tools graphifyis off unless graphify is installed AND surfaced ANDgraphify.enabledis true (previously only the config key was checked). The gate is now runtime-aware: Codex/Cursor/etc. read their own runtime's surface instead of~/.claude. (#1313) (#1313) - Isolated-executor recovery now fails safe — when an isolated (worktree) executor run is rejected (you decline to merge it) or over-reached the requested scope,
/gsd:execute-phaseand/gsd:quickno longer default or propose recovery by editing the primary checkout (main). The orchestrator halts safely and offers a fresh, narrowly-scoped worktree or inspect/discard; editing the primary checkout requires explicit, clearly-labeled confirmation. (#1292) (#1303) - Migrate code review, security, and Nyquist verification workflows to ADR-857 capability hooks. (#1147)
- Intel and loop-hook rendering now honor the single capability
activestate —gsd-tools intelgates through the shared resolver (consistency; intel stays governed byintel.enabled), and loop-hook rendering now suppresses a config-disabled capability's hooks via the capability-levelactivegate (fail-closed), not just per-hookwhen. (#1315) (#1315) - Added no-drift guard tests (
tests/issue-57-runtime-install-no-drift.test.cjs) that protect the Runtime Install Policy Module boundary (ADR-58) and the explicit Runtime Config Adapter Registry (#60). They fail loudly when supported-runtime metadata is added to an installer call site (allRuntimes, the interactiveruntimeMapmenu) without a matching registry adapter entry, or when config-mutation dispatch escapes the registry's declared install surfaces — catching reintroduction of the scattered per-runtime branching those seams removed. (#867) - Edge-probe now surfaces a zero-classification requirement (non-empty prose, no shape cue matched, no
shapesoverride) as a single softunclassified — review manuallycandidate instead of silently dropping it. Dismissible like any edge;shapes: []opt-out stays silent; TAXONOMY unchanged. (#1117) - Capability state now reports a tri-state
active—gsd-tools capability stateadds anactivefield per capability (installed && surfaced && config-enabled), alongside the existingenabled(installed && surfaced). InternalisCapabilityActive(capId, cwd)lets consumers honor the single resolved on/off answer. (#1311) (#1311) gsd-verifierno longer marks behavior-dependent must-havesVERIFIEDon symbol presence alone — a truth that asserts a state transition or a cancellation/cleanup/ordering invariant is markedPRESENT_BEHAVIOR_UNVERIFIEDwhen no test exercises it: excluded from theverified_truthsscore, reported as abehavior_unverifiedcount, and routed to human verification, so a clean N/N now certifies behavioral evidence rather than mere symbol presence. (#966) (#1271)verify plan-structurewarns on cross-task region-scope conflicts (#968) — when a plan task's file-wide negative grep (! grep -Eq 'PAT' file/grep -c 'PAT' file == 0) bans a construct a sibling task legitimately requires elsewhere in the same file, plan validation now surfaces a warning pointing to the new region/function-scoped negative-gate idiom (documented in the gsd-planner guidance and the planner-antipatterns reference, with a worked banned-in-X / required-in-Y example). Warn-only: it never errors and never changesvalid. (#1320) (#1320)
Fixed
gsd-intel-updaternow writes the canonical intel filenames thegsd-tools intelCLI actually reads — the agent was instructed to emit short names (files.json,apis.json,deps.json) and a markdownarch.md, but the intel library reads onlyfile-roles.json,api-map.json,dependency-graph.json, andarch-decisions.json(JSON). After/gsd:map-codebase --query refreshthe output was orphaned, sointel status/validatereported the files missing andintel queryreturned nothing. The agent now emits the canonical long names and structuredarch-decisions.json. (#1000) (#1037)- Installer no longer appends a duplicate managed hook when it is registered via an HTTP route — a hook re-registered as a
type:"http"entry (local hook-server routing) carries its identity only inurl, which the installer's presence check ignored, so a stock command duplicate was appended on every install/update and the hook ran twice per event. The presence check now also inspectsh.url. (#1004) (#1032) /gsd-code-review's fallow structural pre-pass now actually runs and delivers findings — it invoked fallow with flags no published fallow version accepts (--json,--profile,--stdin-files), so the pre-pass failed on every run and silently degraded (the structural-findings feature never delivered on any fallow version). It now uses fallow's real CLI (audit --format json --quiet,--changed-sincefor phase scope, and--max-crapmapped from thecode_quality.fallow.profilepreset: minimal→50, standard→30, strict→15), treats fallow's exit code 1 ("issues found") as a successful run instead of a crash (gating on a valid JSON report, not the exit code), and normalizes fallow's realaudit --format jsonschema (dead_code.*,duplication.clone_groups) into the reviewer's<structural_findings>contract. The report normalizer — previously dead code parsing a schema fallow never shipped — is wired to the real schema and exercised against real fallow output. (#1012) (#1044)worktree base-checknow honors a user/globalworktree.baseRef:"head"(andCLAUDE_CONFIG_DIR) — base-check resolvedbaseReffrom the project checkout's.claude/only, so a machine-wideheadset via/config(the layer the harness itself honors) was invisible. On any phase/feature lane it returnedshouldDegrade:trueandexecute-phasesilently forced sequential execution, losing the parallel worktree execution the user configured. Resolution now falls back to the user/globalsettings.json(viagetGlobalConfigDir('claude'), honoringCLAUDE_CONFIG_DIR) below the existing project-local and project-shared layers. (#1013) (#1038)- Agent SDK/state/commit steps now resolve
gsd-toolson shim-only installs for every runtime — sourceagents/*.md(gsd-planner,gsd-executor,gsd-verifier,gsd-plan-checker, …) invoked baregsd-tools …, which fails withcommand not foundon shim-only installs where the binary is only reachable as<runtime-home>/gsd-core/bin/gsd-tools.cjsand is not onPATH. The agent then silently skipped init/state/validate/commit ceremony. #725 fixed only Codex's conversion layer; the source agents were never migrated, so the bug persisted on Claude Code and every other runtime that consumes the source agents directly. All 12gsd-tools-calling agents now carry the canonical multi-runtimegsd_runresolver (the same preamble the workflow launchers use — covering claude/codex/cursor/gemini/copilot/windsurf/augment/trae/qwen/cline/opencode/kilo/hermes/antigravity homes),gsd-phase-researcher's stale claude-only resolver is upgraded to the canonical one, and the launcher-parity + bare-call regression guards are extended toagents/so no runtime can silently regress. (#1041) (#1045) gsd-tools generate-claude-mdno longer clobbers a hand-craftedCLAUDE.md, and defaults the Claude-runtime output to./.claude/CLAUDE.md—/gsd-new-projectwrote a repo-rootCLAUDE.mdfull of broad project documentation, overwriting/diluting an existing hand-authored instruction file. Now: (1) an existing instruction file that contains no GSD section markers (a hand-crafted file) is left untouched and the command reportsaction: "skipped"— pass--forceto overwrite intentionally (the flag was already parsed but ignored); (2) the default output for Claude-family runtimes is./.claude/CLAUDE.md(a valid project-scoped memory location) instead of repo-root./CLAUDE.md, so generated content does not pollute a repo-root file. The config default (claude_md_path), the project config template, and the new-project workflow are aligned to the new location. Codex projects still writeAGENTS.md. (#1098) (#1118)state record-sessionupdates an existing## Session Continuitysection in place instead of appending a duplicate## Sessionblock — on a freshly bootstrapped project (workstream / gsd2-import / new-project templates all emit## Session Continuity), the auto-create path recognised only the normalized## Sessionheading, so it appended a second session block. It now inserts only the missing canonical fields after the## Session Continuityheading, preserving the heading and any existing prose, and the snapshot / frontmatter readers recognise that heading. (The originally reportedrecorded:false-yet-mutated symptom was already resolved by #944/#948.) (#1101) (#1113)roadmap annotate-dependenciesno longer fuses the preceding summary line onto thePlans:header — when the match regex's(?:^|\n)anchor consumed a leading newline (mid-string match), the replacement dropped it, producing corrupted output like**Plans:** 3 plansPlans:. The replacement now re-emits the leading newline when present. (#1103) (#1111)/gsd-progressno longer reports a phase as complete (and routes to the next phase) when its verification endedhuman_neededorgaps_found— routing derived completeness from plan/summary counts only and never consulted theverification.statusquery (the seam built in #651). A new Step 1.7 consults it for the current phase, and the routing table sendsgaps_foundto/gsd:plan-phase {phase} --gaps(Route V.gaps) andhuman_neededto/gsd:verify-work {phase}(Route V.human) before the generic complete row.passed,missing(unverified), andunknownstill route as complete, so unverified phases are not falsely blocked. (#1107) (#1116)write-profilenow writesUSER-PROFILE.mdto the active runtime's config home instead of always~/.claude— under Codex,gsd-tools query write-profilewrote~/.claude/gsd-core/USER-PROFILE.mdwhile Codexdiscuss-phaseadvisor-mode (installed under~/.codex) checked the Codex home and never found it, so advisor-mode silently stayed disabled. The default output path is now resolved via the runtime-awaregetGlobalConfigDir(GSD_RUNTIME/config.runtime→ e.g.~/.codexfor Codex), matching how the runtime's own workflows resolve it — mirroringgenerate-dev-preferences. Claude is unchanged (~/.claude); an explicit--outputstill wins. (#1114) (#1119)/gsd:reviewno longer produces a silent empty Codex review on codex-cli < 0.137 — thecodex execinvocation passed--dangerously-bypass-hook-trust(added in codex 0.137.0) unconditionally and discarded stderr, so on older CLIs codex exited withunexpected argumentbefore reading the prompt and the empty output was treated as a completed review. The flag is now capability-probed (codex exec --help | grep) and applied via$CODEX_BYPASS_FLAGonly when supported, codex stderr is captured to a.errfile instead of/dev/null, and an empty Codex output is replaced with a diagnostic so a broken reviewer is surfaced rather than silently skipped. (#1115) (#1122)sandbox_modeemission in Codex TOML is now gated on the runtime descriptor'ssandboxTieraxis — previouslyinstallCodexConfigemittedsandbox_modeunconditionally from a hardcoded policy map regardless of whether the runtime descriptor declared a sandbox tier, making the descriptor field cosmetic.resolveInstallPlannow projectssandboxTierfrom the capability registry, andgenerateCodexAgentToml/installCodexConfiggate emission onsandboxTier !== 'none'. The per-agent mode tableCODEX_AGENT_SANDBOXremains GSD agent policy (not a runtime-descriptor property). For codex (sandboxTier === 'codex-agent-sandbox') output is byte-identical to before; for all other runtimes (sandboxTier === 'none')sandbox_modeis correctly omitted.resolveInstallPlannow fails loud (throwsTypeError) on a missing or invalidsandboxTierdescriptor axis rather than silently coercing garbage to'none', preventing a corrupt/stale registry from silently dropping sandbox enforcement. Full removal of the per-agent registration-tax map remains tracked under #1138. (#1151) (#1152)- Installed runtimes no longer silently disable
verify:postgates — in a global skills-runtime install (e.g. Codex at~/.codex), thecommands/gsdsource tree is absent, so capability-state resolved an empty skill manifest. The full-profile*sentinel then materialized to an empty surfaced set, marking every capabilitysurfaced=false→enabled=false. The result:gsd-tools loop render-hooks verify:postreturnedactiveHooks: []even withsecurity_enforcementandnyquist_validationenabled, so the security and Nyquist gates never fired. Capability-state now falls back to the installed<configDir>/skills/gsd-*/SKILL.mdlayout when the source tree is unreachable, soverify:postagain includessecurity -> secure-phaseandnyquist -> validate-phase. (#1206) (#1206) gsd installno longer warns thatsettings.local.json"may be malformed" when the file contains a valid JSONnull.readSettingsnow treats a successfully-parsednullas empty settings ({}) instead of collapsing it into the parse-failure path, so a literal-nullsettings file is preserved silently; genuinely unparseable files still emit the warning. (#1191) (#1233)- gsd-tools no longer crashes at load on a fresh install — the installer omitted
scripts/fix-slash-commands.cjs, whichcommand-rosterrequires at module load, so everygsd-toolscommand failed with MODULE_NOT_FOUND. The installer now ships it (with a smoke assertion), andreadCmdNames()tolerates a missing commands directory. (#1240) (#1240) state begin-phase/complete-phasenow advance the frontmatterstatusfor pipe-tableSTATE.md, not only inlineStatus:files. The status update matched the YAML frontmatterstatus:line first and never updated a body| Status | … |cell, so the frontmatterstatusfroze (e.g. stuck atplanning); it now transitions correctly (planning → executing → completed) regardless of whether the bodyStatusis inline or pipe-table. (#1255) (#1256)state planned-phasenow advances the pipe-tableStatuscell (and frontmatterstatus), andstate begin-phasenow updates the Current Position| Phase |/| Plan |cells instead of prepending stray inline lines. Systemic follow-up to #1255:planned-phaseran its body-field replacements on the full file content, so the YAML frontmatterstatus:line was matched before the body| Status | … |cell and the status never reachedReady to execute; andbegin-phasehad pipe-table branches only forStatus/Last activity, so for pipe-tableSTATE.mdthePhase/Planrows were left stale while a spurious inlinePhase: N — EXECUTINGline was prepended. Both handlers now strip frontmatter before body-field replacement and update pipe-table cells in place, matching the inline-format behaviour. (#1257) (#1260)
Parallel worktree execution now has executor-authored cleanup metadata — executor agents capture their worktree path, branch, and expected base before task commits and return a parseable metadata block for execute-phase to prefer over runtime harness metadata. (#1297) (#1349)
UAT resume now accepts paused checkpoints — uat render-checkpoint treats a non-structured paused Current Test placeholder as a resume signal and derives the checkpoint from the first pending UAT test instead of failing as malformed. (#1300) (#1350)
phase complete now preserves prose-block STATE phase names — template-shaped Current Position prose now advances with the next phase name, avoids missing-field warnings, and keeps Last activity: on the template em-dash delimiter. (#1316) (#1351)
Claude skill installs now avoid rejected xhigh effort frontmatter — heavyweight GSD skills now ship with portable effort: max, and the Claude skill converter normalizes any remaining xhigh source effort before writing SKILL.md. (#1319) (#1352)
Glued letter-prefix phase directories now resolve correctly -- phase lookup now recognizes tokens like P0.3 and M1-2 from directory names, so phase commands can find their plans instead of reporting none found. (#1324) (#1353)
Update backups now ignore preserved shared skills and hooks -- /gsd-update custom-file detection now mirrors installer cleanup scope for shared runtime roots, so non-gsd-* skills and hooks are not copied into backup folders unnecessarily. (#1325) (#1354)
Codex skills no longer show up twice in autocomplete — GSD's Codex install wrote an agents/openai.yaml sidecar under every managed gsd-* skill directory, and recent Codex builds index both SKILL.md and the sidecar, so each skill appeared twice (once as gsd-foo, once as a humanized foo display name). The installer now stops emitting these sidecars and removes stale ones left by prior installs (pruning the empty agents/ directory), while preserving user-owned skill directories. Codex discovers GSD skills via SKILL.md alone. (#1326) (#1360)
The worktree path guard no longer blocks ordinary writes in non-GSD git worktrees — the gsd-worktree-path-guard PreToolUse hook fired for every Write/Edit in any linked git worktree, so Claude Code plan-mode writing its plan to ~/.claude/plans/<slug>.md from a manually-created worktree was hard-blocked. The hook now only enforces inside a GSD isolated-executor worktree (branch worktree-agent-*) and fails open when a target resolves to no git repository, while still blocking writes that escape to a different git root (the #260 protection) or into a repository's .git internals. (#1342) (#1361)
check.decision-coverage-plan no longer reports a false pass when a D-NN decision header has text before the colon — parseDecisions previously dropped any - **D-NN …:** bullet whose header contained a (parenthetical), em-dash, or other prose before the :**, silently narrowing the trackable set so the blocking coverage gate green-lit a phase whose dropped decisions were never checked. The parser now tolerates a freeform run before the colon (preserving [bracket] tags) and warns on any D-NN bullet it still cannot parse instead of dropping it. (#1343) (#1358)
Codex hooks.json is now always written in the nested { "hooks": { … } } shape Codex expects — the writer previously echoed back whatever shape it read, so an empty, absent, or legacy top-level hooks.json ({ "SessionStart": [...] }) stayed in the legacy shape that current Codex can reject or warn on. Every write now canonicalizes to the nested form, lifting any legacy top-level event entries (including mixed nested+top-level files) under hooks without dropping user-owned entries. Managed-hook dedup/removal is unchanged. (#1348) (#1363)
gsd install --cursor no longer leaves bare ~/.claude paths in installed artifacts — the Cursor install branch only rewrote the trailing-slash .claude forms, so bare ~/.claude / $HOME/.claude references survived into installed skills and workflows (e.g. gsd-surface, gsd-graphify, plan-phase, autonomous) and tripped the post-install "unreplaced .claude path reference(s)" warning, pointing at a directory that doesn't exist on a Cursor-only install. The Cursor branch now rewrites bare forms too (mirroring the Trae/Augment/Copilot branches), using a (?![\w-]) lookahead so .claude-plugin / .claudeignore are not corrupted. (#1356) (#1368)
/gsd-new-projectand/gsd-new-milestonenow self-heal when the research synthesizer returnsSUMMARY.mdinline instead of writing it — under some context loads thegsd-research-synthesizeragent hits an LLM false-refusal (fabricating a non-existent write restriction) and returns the SUMMARY.md content in its reply rather than writing.planning/research/SUMMARY.md. Prompt hardening (#240) reduced but did not eliminate this. Both workflows now verify the file exists after the synthesizer returns and, if it is missing but content came back inline, the orchestrator persists it before spawninggsd-roadmapper— so the roadmapper never fails with "SUMMARY.md not found". (#222) (#1042)- Codex agent TOML generation no longer pins
model_reasoning_effortwhen the agent is intentionally inheriting the active Codex chat model. GSD still emits bothmodelandmodel_reasoning_effortwhen a per-agent model override orruntime: "codex"resolver pins the model, avoiding the confusing partial state where the model followed Codex UI selection while effort followed GSD catalog defaults. (#838) (#842) - profile-pipeline temp output now lands under the reaped GSD temp root.
cmdExtractMessagesandcmdProfileSamplepreviously created their output directories directly inos.tmpdir()root (gsd-pipeline-*/gsd-profile-*), whichreapStaleTempFilesnever scans (it only scansGSD_TEMP_DIR = os.tmpdir()/gsd). The directories accumulated forever. Both sites now callensureGsdTempDir()and create underGSD_TEMP_DIR. Also adds missingafter/afterEachteardown to four test fixtures that leakedgsd-*temp dirs on everynpm testrun. (#866) (#879) getMilestonePhaseFilternow excludes phase headings inside fenced code blocks (``` or~~~) — consistent with the fence-aware behavior ofextractCurrentMilestone. Previously, a### Phase N:line inside a fenced block was wrongly counted as a real phase. (#875) (#880)gsd_runlauncher shim now probes all non-Claude runtime homes before failing. The shim's last-resort detection previously stopped at$HOME/.claude, causing a false-positive fatal error on every non-Claude runtime (Hermes, Cursor, Codex, Copilot, Windsurf, Augment, Trae, Qwen, CodeBuddy, Cline, Grok, Antigravity, OpenCode, Kilo) whenRUNTIME_DIRwas unset andgsd-toolswas not onPATH. The snippet now probes each runtime's config directory (respectingHERMES_HOME,CURSOR_CONFIG_DIR,CODEX_HOME, etc. with sensible$HOME-relative defaults) before emitting the install error. (#903)validate healthandvalidate consistencyno longer emit false-positive W007 warnings for projects using checklist-style ROADMAP.md phases.buildRoadmapPhaseVariants()insrc/validate.ctspreviously used only a heading-style regex (## Phase N: name), silently ignoring the supported checklist format (- [x] **Phase N: name**). This caused every on-disk phase directory to trigger W007 ("exists on disk but not in ROADMAP.md") when the project's ROADMAP used checklist-only notation. The fix adds a second regex pass mirroring the existingbuildNotStartedPhaseVariants()approach. Additionally,cmdValidateConsistency()insrc/verify.ctshad a duplicate inline heading-only regex with the same gap — refactored to delegate tobuildRoadmapPhaseVariants()(DRY). (#892) (#893)init execute-phaseandcmdCommitnow produce correctbranch_namewhenproject_codeis set — the{phase}substitution inphase_branch_templatenow callsnormalizePhaseName(), stripping the project-code prefix and zero-padding the number, so the generated branch is e.g.gsd/phase-01-foundationinstead ofgsd/phase-CK-01-foundation. Both the execute-phase output path (src/init.cts) and the pre-execution commit path (src/commands.cts) are fixed. (#904) (#904)syncStateFrontmatterno longer stripscurrent_phase,current_phase_name,current_plan, andprogressfromSTATE.md— when body annotations are absent (e.g. after an agent rewrites the body), the existing frontmatter values for those scalars are now preserved, mirroring the fallback already applied incmdStateJson. (#905) (#905)- Top-level Claude Code
/gsd-plan-phasenow always spawns the researcher/planner/plan-checker agents instead of collapsing them inline — a<runtime_compatibility>block after</available_agent_types>makes the Agent-availability requirement explicit and documents that the workflow fails-closed (stops with a clear log message) in genuinely Agent-less contexts; seven "ORCHESTRATOR RULE — CODEX RUNTIME" labels are renamed to "ALL RUNTIMES" so the guard applies universally;execute-phase.mdscopes its existing "Other runtimes" inline-fallback prose to non-Claude contexts, preserving the #853 backgrounded-agent behaviour. (#913) (#913) /gsd-plan-phase,/gsd-execute-phase,/gsd-autonomousno longer carrycontext: fork— these are spawning orchestrators; a forked subagent context has noAgenttool, preventing them from spawning the subagents they require.effort: xhighis preserved. Fixes/gsd:autonomoushalting with "running as a forked subagent" on 1.4.1 (#921). Also replaces the introspection-based Agent-availability check inplan-phase's<runtime_compatibility>block with an attempt-based gate: the workflow now always attempts theAgent()call and only stops if a real tool-unavailable error is returned, eliminating false-negative aborts in top-level sessions (#922). (#921)- Claude global install reverted to flat skill layout so concrete skills are discoverable. PR #883 introduced nested skill layout for Claude at
~/.claude/skills/gsd-ns-<router>/skills/<stem>/SKILL.md, but Claude Code's skill discovery scans only one level under~/.claude/skills/— nested concrete skills were never listed in the Skill-tool available-skills list and directSkill(skill="gsd-plan-phase")calls stopped working. This fix reverts Claude to the flat layout (~/.claude/skills/gsd-<name>/SKILL.md) so all ~61 concrete skills are top-level and immediately discoverable. The 6 other runtimes that confirmed non-recursive scanning (cline, qwen, hermes, augment, trae, antigravity) retain their nested layout. (#924) (#924) gsd-context-monitor.jsnow echoes the actual invoking hook event name — instead of hardcodinghookEventName: "PostToolUse"(or"AfterTool"for Gemini), the hook readsdata.hook_event_namefrom the stdin payload and falls back to the runtime heuristic only when the field is absent or blank; this fixes Claude Code rejecting hook output with"expected Stop but got PostToolUse"when the monitor is invoked by the Stop, SubagentStop, or PreCompact hooks registered in PR #821. (#925) (#926)- Fix
--reapplyverifier false-positives on post-#604-rename installs caused by two gaps in pristine-baseline handling:
Gap 1 (verify-reapply-patches.cjs): when backup-meta.json records a pristine_hash for a file but gsd-pristine/ has no corresponding snapshot on disk, the verifier fell to over-broad mode (every upstream-changed line treated as a user-added requirement) and produced FAIL_USER_LINES_MISSING false positives. Fix: return advisory OK_NO_BASELINE reason (non-blocking, exit 0) when a recorded hash is present but the pristine file is absent — the verifier cannot reason correctly without a baseline and must not block.
Gap 2 (new migration 004-prune-stale-pristine-get-shit-done): migration 003 removed legacy get-shit-done/ runtime files but left gsd-pristine/get-shit-done/ orphan snapshots in place. Those stale snapshots referenced get-shit-done/... key paths that no longer match the active gsd-core/... layout, contributing to FAIL_INSTALLED_MISSING false reports. Fix: add a new migration (not editing 003, to preserve its checksum) that removes all files under gsd-pristine/get-shit-done/. (#934) (#935)
/gsd-updatechangelog preview no longer silently fails — the installer now copiesscripts/changeset/andscripts/lib/into the runtime config dir so$GSD_DIR/scripts/changeset/cli.cjsresolves at runtime;update.mdwas updated to use the correct installed path and to surface an explicit error if the CLI is missing rather than swallowing it. (#935)plan-review-convergencenow runsgsd-plan-phaseinline instead of insideAgent()— both sites that previously wrappedgsd-plan-phaseinAgent()(initial planning + replan loop) have been changed to bareSkill()calls at depth 0. On Claude Code, a depth-1 Agent has no Agent tool, so a wrappedplan-phasecould never spawngsd-plannerorgsd-plan-checker— the replan loop silently failed to produce a revised plan whenever HIGH concerns were found. Running plan-phase inline from the depth-0 orchestrator (which retains the Agent tool) restores the full planner→checker sub-agent chain. A new structural guard test (bug-936-no-nested-spawner-wrap.test.cjs) statically scans all workflow files and fails if any workflow wraps a spawner orchestrator inAgent()without aRUNTIME != claudecarve-out, preventing regression. (#936) (#939)--json-errorsnow emits a structured error even when a handler throws unexpectedly — an unexpected (non-ExitError) throw fell through to a raw stack trace on stderr, breaking SDK structured-error parsing. (#965) (#987)verify key-linksdocs now correctly statefrom:/to:are relative file paths — the reference implied component/endpoint values the verifier never supported, so locator-style links failed with a misleading 'Source file not found' and the author'spattern:was never evaluated. (#967) (#990)- Fixed a test-infrastructure regression (#996) where bug-969 hardening tests deleted the shared
gsd-core/bin/lib/core.cjsduring concurrent runs and the build tsbuildinfo lived inside the copied install tree, intermittently failing CI with MODULE_NOT_FOUND/ENOENT. The destructive tests now run hermetically against a temp project, and the tsbuildinfo moved out ofgsd-core/bin/. (#969) (#1002) gsd-plannernow ships theEdittool, so it can no longer destroyROADMAP.mdvia a whole-fileWrite— the planner hadWritebut notEdit(the #571/#581 writer-agent gap), so an in-place ROADMAP edit fell back to a full overwrite that truncated committed milestone history. Theupdate_roadmapstep now directs scopedEditcalls and explicitly forbids passing the full file toWrite. (#973) (#989)graphify query --budgetwith no value now errors instead of silently ignoring the budget — a trailing--budgetparsed asNaNand was treated as 'no budget', so the query ran unbounded with no warning. (#974) (#986)- The installer now resolves a stable fnm node path instead of the ephemeral multishell shim on Windows — managed
.jshooks were pinned tofnm_multishells/<id>/node.exe, a per-shell-session path fnm later deletes, breaking every managed hook until reinstall. (#977) (#992) gsd-tools milestone complete --forcenow actually overrides the unstarted-phase guard — the dispatcher never parsed--force, so the guard's own documented escape hatch was inert. (#978) (#982)
Trae and Windsurf installs no longer leak unreplaced ~/.claude / $HOME/.claude paths — both converters only rewrote trailing-slash .claude/ forms, so bare home-path references survived conversion and pointed users at the wrong config dir; bare forms are now rewritten (Codex/Cline #570/#782 parity) and CLAUDE_CONFIG_DIR maps to the runtime's own var, with .claude-plugin preserved. (#983) (#995)
- Claude Code plugin installs no longer fail with empty
@~/.claude/gsd-core/...includes — agents, commands, and templates@-include the canonical~/.claude/gsd-core/path, but a marketplace plugin install (claude plugin install) never creates that directory, so every include resolved to nothing and agents (e.g. the executor) failed. A newSessionStarthook (gsd-ensure-canonical-path.js) symlinks the canonical path's immutable subdirs (bin,contexts,references,templates,workflows) to the plugin's bundled tree. It is a no-op in classicbin/install.jsinstalls, preserves user-generated files (e.g.USER-PROFILE.md), prunes stale links so it self-heals afterclaude plugin update, and uses Windows junctions. (#1207) (#1207) /gsd-code-review,/gsd-code-review --fix, and/gsd-eval-reviewnow inject configuredagent_skillsinto their subagents — these review-family workflows previously spawned their reviewer/fixer/auditor agents (including the--autore-review/re-fix loops) without the project-configured skill and rule context, so anyagent_skillsset forgsd-code-reviewer,gsd-code-fixer, orgsd-eval-auditorwere silently ignored. They now query and inject those skills like the ~20 sibling workflows. (#1005)phase completeno longer rewrites an existing roadmap completion date — repeat runs on an already-Completephase preserve the recordedYYYY-MM-DDdate (4- and 5-column layouts); empty/-/non-date cells are still stamped with the current date. (#1177)- Legacy ROADMAP projects no longer get deprecation-warning spam — the free-form ROADMAP warning fired on every command regardless of phase_id_convention; it now only warns when the milestone-prefixed convention is explicitly set and unmet. (#1218) (#1218)
- Forking workflows target wrong base branch on
masterrepos whenorigin/HEADis unset —execute-phase,quick,ship,complete-milestone, andpr-branchdetection bash fell through to a hardcodedmainfallback wheneverorigin/HEADwas absent (common ingit init+remote add+fetchwithoutset-head, CI checkouts, and worktrees), causing GSD to fork phase branches off a non-existentmainonmasterrepos. Replaced with a singlegsd_run query git.base-branchresolver that walks the full precedence ladder: config override →origin/HEADsymref →git remote show origin→ local branch presence →"main". (#1198) (#1198) query user-story.validatenow works —mvp-phaseandverify-workworkflows both invoked this command to validate "As a / I want to / so that" user stories, but no CJS handler existed; every call errored with "Unknown command: user-story". (#1193) (#1193)- Context meter no longer sticks at 100% — the statusline reserved-buffer math was inverted, pinning usage at 100% whenever CLAUDE_CODE_AUTO_COMPACT_WINDOW equalled the total window. (#1194) (#1211)
- Roadmapper honors phase_id_convention — new-project roadmaps now use milestone-prefixed phase IDs when phase_id_convention is set, instead of ignoring the default. (#1205) (#1215)
phase complete no longer emits false warnings from historical verification metadata or deferred requirement IDs — two distinct false-positive warning bugs: (A) the verification-status check used a full-text regex that matched previous_status: gaps_found in the file body, triggering an "unresolved gaps" warning even when the current frontmatter status: passed; the check now reads only the frontmatter status key via extractFrontmatter. (B) requirement IDs under explicitly deferred/backlog/future/v2 section headings in REQUIREMENTS.md were flagged as missing from the Traceability table; the check now skips any section whose heading matches those terms. (#1197) (#1197)
- verify key-links no longer fails on planned future files — a from: link whose file is declared in a current/upcoming wave plan’s files_modified is now reported pending instead of a hard missing-file failure. (#1202) (#1219)
state patchandstate record-sessionno longer corrupt STATE.md — a no-match patch no longer rewrites the file (was resettingmilestone_nameand resurrecting a stalestopped_at), andrecord-sessionnow persists--stopped-at/--resume-fileeven when the body lacks the exact labels. (#952)/gsd-updateno longer flagsmanaged-hooks-registry.cjsas a custom file — the shipped hook is now recorded in the file manifest, eliminating a perpetual false-positive custom-file warning. (#953)gsd-toolsno longer throwsEAGAINor truncates output under heavy load — the CLI's stdout/stderr writes now retry the transientEAGAIN/EINTRerrnos and handle short writes when the output stream is a full non-blocking pipe (e.g. the parallel test runner), instead of throwing or silently dropping bytes. (#1009)- Quick worktree execution now accepts parent-or-plan bases for pre-dispatch plan commits — quick mode records the parent and plan commit around the pre-dispatch PLAN.md commit, lets the worktree guard accept either approved base, materializes the plan from git objects when a runtime forks from the parent, and teaches cleanup to validate the same allowed-base set. (#1265) (#1347)
phase addno longer reuses an existing phase number when that phase exists only as a roadmap bullet — the next-number scan now counts phases listed only as- [ ] **Phase N: ...**bullets (all checkbox variants, with or without a title), in addition to### Phase N:section headers and on-disk phase directories, so a bullet-only phase is no longer shadowed andphase addappends after the highest used number. (#1249)- Preserve curated STATE.md progress frontmatter when
state patchupdates non-progress fields, while still allowing progress-related fields to resync from disk-derived project state. (#1345) - The installer no longer re-adds a duplicate managed hook when the user registered it in
command+args(wrapped) form — the presence checks only inspectedh.command, so an args-form wrapper (a common Windows windowless-launcher mitigation) was invisible and a stock entry was appended on every install/update, running the hook twice. (#976) (#994) cmdSkillManifestnow discovers concrete skills nested undergsd-ns-*routers (<root>/gsd-ns-<router>/skills/<stem>/SKILL.md), sogsd-healthandgsd-settingsreport the correct count on nested-layout runtimes (cline, qwen, hermes, augment, trae, antigravity). The scan is scoped togsd-ns-*router dirs only — unrelated user dirs that happen to have askills/subdirectory are not traversed. Dual-routed concretes (same skill installed under two routers) are deduped by name within each root. (#929) (#929)- state record-session no longer pins a CPU core forever — acquireStateLock busy-spun at 100% CPU when a recoverable errno (e.g. ENOENT from a removed worktree) persisted, because that retry path skipped the backoff sleep and the 30s time budget. Every retry path is now bounded and backed off. (#1236) (#1236)
/gsd-managerand/gsd-autonomous --interactiveno longer silently skip worktree isolation and independent verification on Claude Code. They dispatched plan/execute as background agents, but a backgrounded Claude Code agent has no Agent/Task tool and cannot spawn the nested executors, plan-checker, or verifier — so isolation and verification silently never ran. Both workflows now resolve the runtime and run plan/execute inline on Claude Code; background dispatch is kept on runtimes that support nested subagents. (#863)- Researcher agents can now invoke Perplexity —
gsd-phase-researcherandgsd-project-researcherreferencedmcp__perplexity__*in their provider dispatch tables but never granted it in theirtools:allowlist, so Perplexity web research silently fell through to the next provider. The grant is now generated from the researcher profiles, with a parity guard that fails if a future dispatch-table provider is added without its tool grant. (#1284) (#1288) - Init phase lookups now resolve active phases whose canonical details live in a flat Phase Details block outside the current milestone summary, restoring requirement coverage for plan/execute/phase-op flows. (#1344)
- Installer no longer leaks
gsd-cmd-rewrites-*temp directories. Each install that emitted slash commands left onefs.mkdtempSyncdirectory under the system temp root; ontmpfs/tmphosts these accumulated and consumed RAM-backed storage.installRuntimeArtifacts()now removes the temp copy in afinallyonce command files are copied. (#862) validate agents(andvalidate health) now cross-reference the install manifest to detect manifest-backed Codex agent pair drift: when a generatedagents/gsd-*.md/agents/gsd-*.tomlpair has one side missing on disk, the agent is reported as incomplete andagents_foundisfalse(previously a false-healthyagents_found: true, missing: []).validate healthnames the incomplete agents and recommends re-running the installer. The check no-ops when no manifest is present. (#1058) (#1079)- The
map-codebaseanddocs-updateworkflows no longer collect background sub-agent results with the deprecated Claude CodeTaskOutputtool — they keeprun_in_background=trueon the spawn andReadeach agent'soutputFile(from theasync_launchedresult) once it reports completion, removing theTaskOutput(block=true)main-session hang surface (anthropics/claude-code#20236). Completion-marker contracts and on-disk verification are unchanged, and the non-Claude runtime fallbacks are preserved. (#1362) model_policyis now honored on the defaultclauderuntime — including theanthropic-fableClaude Fable 5 preset. Policy-resolved model IDs map to Claude Code agent aliases (e.g.claude-fable-5→fable), and IDs without a Claude alias warn and fall back to the configured tier. Forward-port of #1133 (originally shipped on the 1.4.5 hotfix line). (#1133) (#1133)/gsd-plan-review-convergencenow blocks on actionable review findings outside PLAN.md (#724). The convergence summary contract includes current_actionable alongside current_high, and reviews-mode planning/checking requires actionable MEDIUM/LOW feedback to be incorporated or explicitly deferred in executable PLAN.md content. (#728)- Config docs/prompts now match the consumers —
workflow.subagent_timeoutis documented in milliseconds (default 300000), not "seconds (default 600)" (a user who entered 600 got a 600 ms timeout);review.models.<cli>is documented as a bare model id injected into--model/-m, not a shell command; andworkflow.test_command/workflow.build_command(consumed by verify-phase, execute-phase, audit-fix, and the post-merge gate) are now accepted byconfig setand documented. (#1296) (#1299) changeset new --pr 0now accepted at creation — the required-field guard treated the integer 0 as a missing--prflag, so the documentedpr: 0placeholder could not be authored via the CLI. (#1231) (#1231)$gsd-quickCodex adapter no longer assumes typedspawn_agent(agent_type=...)— documents that typed planner/executor spawning needs the agent_type-capable Codex schema and provides a clearly-labeled generic-subagent fallback when onlymulti_agent_v1is exposed. (#958)state updateandroadmap update-plan-progressnow handle current Markdown artifact shapes — state field read/replace works on table-formatSTATE.md(| Status | … |), androadmap update-plan-progressinserts missing per-plan checklist rows (filling partial gaps), toleratesPlans:/**Plans:**/**Plans**:, and scopes changes to the active milestone. (#1172)state planned-phasenow advances the Status field when the prior phase left aComplete ✓(checkmark) or bareCompleteterminal status. Previously such a status matched no known template default, so the transition was silently skipped and the state machine stayed stuck on the prior phase. Caveat-bearing statuses (e.g.Complete but needs manual QA) remain preserved. (#1070) (#1078)state.*writes no longer silently revert the STATE.md frontmatterstatus/stopped_at— an incidental write (e.g.state record-session) that doesn't change the body'sStatus:/Stopped at:source field now preserves the existing frontmatter value instead of re-deriving it from possibly-stale body text. Legitimate transitions (e.g.begin-phase/complete-phase, which do update the body Status) still re-derive normally, so a verified-complete phase can no longer be flipped back toverifyingby an unrelated write. (#1252)audit-openno longer false-flags completed quick tasks — quick-task SUMMARYs now carrystatus: completein frontmatter by construction, so the milestone-close auditor stops reporting finished quick tasks as[unknown]. (#951)- Workspace (local) Antigravity and Copilot skill installs no longer point at the global config home — a local install rewrote
~/.claude/references inSKILL.mdbodies to the global~/.gemini/antigravity//~/.copilot/paths instead of the workspace-relative.agent//.github/, because the skills layout wrapper passed the runtime name into the converter'sisGlobalparameter slot. (#1092) (#1092) - Fix the workflow gsd_run launcher being unreachable in later bash blocks on runtimes that run each fenced block in a fresh shell (e.g. Claude Code): ship a standalone gsd-core/bin/gsd_run executable and have the per-file preamble persist the launcher's bin dir onto PATH via CLAUDE_ENV_FILE, with the inline function definition kept as the fallback for all other runtimes. (#1084)
/gsd:phase insertand/gsd:phase --editno longer dead-end recording Roadmap Evolution —query state.add-roadmap-evolutionwas rejected as "SDK-only" with an error that pointed back at the very command that just failed, and no CJS handler existed after the SDK retirement. The handler is now implemented in CJS, so the insert/edit phase workflows append the### Roadmap Evolutionentry under## Accumulated Context(creating the subsection if missing, deduping identical entries) as documented. (#1148) (#1148)- Corrected the installer
--helpprofile skill counts:corenow shows 8 (was 7) andstandardshows 14 (was 13), both derived fromPROFILESso they can't drift again; thefullline drops the stale hardcoded66forall skills. (#834) (#847) - Codex-installed GSD skills and agents no longer rely on a bare
gsd-toolsexecutable — generated Codex surfaces now call the bundled shim, and workflow launchers can resolve the Codex shim-only install path. (#731)
Wire the discuss loop step for capability hooks — capabilities can now register discuss:pre/discuss:post hooks (e.g. discuss-time context recall and CONTEXT capture); previously discuss was contract-declared but structurally unwireable. Also collapses the host-loop file set to a single source of truth and adds an authoring-time guard rejecting hooks at unwired extension points. (#1199) (#1199)
/gsd-autonomous --convergenow routes phase planning through plan-review convergence instead of silently ignoring the flag. (#711) (#729)- Hermes skills now install at skills/gsd/gsd-/SKILL.md with name gsd-, restoring canonical /gsd- dispatch that was broken by the bare-stem prefix introduced in #3664. (#955)
[1.4.5] - 2026-06-12
Fixed
model_policyis now honored on the defaultclauderuntime — including theanthropic-fableClaude Fable 5 preset. Policy-resolved model IDs map to Claude Code agent aliases (e.g.claude-fable-5→fable), and IDs without a Claude alias warn and fall back to the configured tier. Previously the entiremodel_policyblock was silently ignored onclaude. (#1133) (#1133)
[1.4.4] - 2026-06-11
Changed
- Added an opt-in
anthropic-fablemodel policy provider preset for Claude Fable 5 high-budget routing while preserving the existing Anthropic Opus 4.8 defaults andanthropicprovider preset. (#1014) (#1015)
[1.4.3] - 2026-06-09
Fixed
- Fix
--reapplyverifier false-positives on post-#604-rename installs caused by two gaps in pristine-baseline handling:
Gap 1 (verify-reapply-patches.cjs): when backup-meta.json records a pristine_hash for a file but gsd-pristine/ has no corresponding snapshot on disk, the verifier fell to over-broad mode (every upstream-changed line treated as a user-added requirement) and produced FAIL_USER_LINES_MISSING false positives. Fix: return advisory OK_NO_BASELINE reason (non-blocking, exit 0) when a recorded hash is present but the pristine file is absent — the verifier cannot reason correctly without a baseline and must not block.
Gap 2 (new migration 004-prune-stale-pristine-snapshots): migration 003 removed legacy get-shit-done/ runtime files but left gsd-pristine/get-shit-done/ orphan snapshots in place. Those stale snapshots referenced get-shit-done/... key paths that no longer match the active gsd-core/... layout, contributing to FAIL_INSTALLED_MISSING false reports. Fix: add a new migration (not editing 003, to preserve its checksum) that removes all files under gsd-pristine/get-shit-done/. (#934) (#937)
/gsd-updatechangelog preview no longer silently fails — the installer now copiesscripts/changeset/andscripts/lib/into the runtime config dir so$GSD_DIR/scripts/changeset/cli.cjsresolves at runtime;update.mdwas updated to use the correct installed path and to surface an explicit error if the CLI is missing rather than swallowing it. (#938)plan-review-convergencenow runsgsd-plan-phaseinline instead of insideAgent()— both sites that previously wrappedgsd-plan-phaseinAgent()(initial planning + replan loop) have been changed to bareSkill()calls at depth 0. On Claude Code, a depth-1 Agent has no Agent tool, so a wrappedplan-phasecould never spawngsd-plannerorgsd-plan-checker— the replan loop silently failed to produce a revised plan whenever HIGH concerns were found. Running plan-phase inline from the depth-0 orchestrator (which retains the Agent tool) restores the full planner→checker sub-agent chain. A new structural guard test (bug-936-no-nested-spawner-wrap.test.cjs) statically scans all workflow files and fails if any workflow wraps a spawner orchestrator inAgent()without aRUNTIME != claudecarve-out, preventing regression. (#936) (#939)
[1.4.2] - 2026-06-09
Fixed
/gsd-plan-phase,/gsd-execute-phase,/gsd-autonomousno longer carrycontext: fork— these are spawning orchestrators; a forked subagent context has noAgenttool, preventing them from spawning the subagents they require.effort: xhighis preserved. Fixes/gsd:autonomoushalting with "running as a forked subagent" on 1.4.1 (#921). Also replaces the introspection-based Agent-availability check inplan-phase's<runtime_compatibility>block with an attempt-based gate: the workflow now always attempts theAgent()call and only stops if a real tool-unavailable error is returned, eliminating false-negative aborts in top-level sessions (#922). (#921)gsd-context-monitor.jsnow echoes the actual invoking hook event name — instead of hardcodinghookEventName: "PostToolUse"(or"AfterTool"for Gemini), the hook readsdata.hook_event_namefrom the stdin payload and falls back to the runtime heuristic only when the field is absent or blank; this fixes Claude Code rejecting hook output with"expected Stop but got PostToolUse"when the monitor is invoked by the Stop, SubagentStop, or PreCompact hooks registered in PR #821. (#925) (#926)
[1.4.1] - 2026-06-09
Changed
- Added no-drift guard tests (
tests/issue-57-runtime-install-no-drift.test.cjs) that protect the Runtime Install Policy Module boundary (ADR-58) and the explicit Runtime Config Adapter Registry (#60). They fail loudly when supported-runtime metadata is added to an installer call site (allRuntimes, the interactiveruntimeMapmenu) without a matching registry adapter entry, or when config-mutation dispatch escapes the registry's declared install surfaces — catching reintroduction of the scattered per-runtime branching those seams removed. (#867)
Fixed
- profile-pipeline temp output now lands under the reaped GSD temp root.
cmdExtractMessagesandcmdProfileSamplepreviously created their output directories directly inos.tmpdir()root (gsd-pipeline-*/gsd-profile-*), whichreapStaleTempFilesnever scans (it only scansGSD_TEMP_DIR = os.tmpdir()/gsd). The directories accumulated forever. Both sites now callensureGsdTempDir()and create underGSD_TEMP_DIR. Also adds missingafter/afterEachteardown to four test fixtures that leakedgsd-*temp dirs on everynpm testrun. (#866) (#879) gsd_runlauncher shim now probes all non-Claude runtime homes before failing. The shim's last-resort detection previously stopped at$HOME/.claude, causing a false-positive fatal error on every non-Claude runtime (Hermes, Cursor, Codex, Copilot, Windsurf, Augment, Trae, Qwen, CodeBuddy, Cline, Grok, Antigravity, OpenCode, Kilo) whenRUNTIME_DIRwas unset andgsd-toolswas not onPATH. The snippet now probes each runtime's config directory (respectingHERMES_HOME,CURSOR_CONFIG_DIR,CODEX_HOME, etc. with sensible$HOME-relative defaults) before emitting the install error. (#903)validate healthandvalidate consistencyno longer emit false-positive W007 warnings for projects using checklist-style ROADMAP.md phases.buildRoadmapPhaseVariants()insrc/validate.ctspreviously used only a heading-style regex (## Phase N: name), silently ignoring the supported checklist format (- [x] **Phase N: name**). This caused every on-disk phase directory to trigger W007 ("exists on disk but not in ROADMAP.md") when the project's ROADMAP used checklist-only notation. The fix adds a second regex pass mirroring the existingbuildNotStartedPhaseVariants()approach. Additionally,cmdValidateConsistency()insrc/verify.ctshad a duplicate inline heading-only regex with the same gap — refactored to delegate tobuildRoadmapPhaseVariants()(DRY). (#892) (#893)init execute-phaseandcmdCommitnow produce correctbranch_namewhenproject_codeis set — the{phase}substitution inphase_branch_templatenow callsnormalizePhaseName(), stripping the project-code prefix and zero-padding the number, so the generated branch is e.g.gsd/phase-01-foundationinstead ofgsd/phase-CK-01-foundation. Both the execute-phase output path (src/init.cts) and the pre-execution commit path (src/commands.cts) are fixed. (#904) (#904)syncStateFrontmatterno longer stripscurrent_phase,current_phase_name,current_plan, andprogressfromSTATE.md— when body annotations are absent (e.g. after an agent rewrites the body), the existing frontmatter values for those scalars are now preserved, mirroring the fallback already applied incmdStateJson. (#905) (#905)- Top-level Claude Code
/gsd-plan-phasenow always spawns the researcher/planner/plan-checker agents instead of collapsing them inline — a<runtime_compatibility>block after</available_agent_types>makes the Agent-availability requirement explicit and documents that the workflow fails-closed (stops with a clear log message) in genuinely Agent-less contexts; seven "ORCHESTRATOR RULE — CODEX RUNTIME" labels are renamed to "ALL RUNTIMES" so the guard applies universally;execute-phase.mdscopes its existing "Other runtimes" inline-fallback prose to non-Claude contexts, preserving the #853 backgrounded-agent behaviour. (#913) (#913) /gsd-managerand/gsd-autonomous --interactiveno longer silently skip worktree isolation and independent verification on Claude Code. They dispatched plan/execute as background agents, but a backgrounded Claude Code agent has no Agent/Task tool and cannot spawn the nested executors, plan-checker, or verifier — so isolation and verification silently never ran. Both workflows now resolve the runtime and run plan/execute inline on Claude Code; background dispatch is kept on runtimes that support nested subagents. (#863)- Installer no longer leaks
gsd-cmd-rewrites-*temp directories. Each install that emitted slash commands left onefs.mkdtempSyncdirectory under the system temp root; ontmpfs/tmphosts these accumulated and consumed RAM-backed storage.installRuntimeArtifacts()now removes the temp copy in afinallyonce command files are copied. (#862) - Corrected the installer
--helpprofile skill counts:corenow shows 8 (was 7) andstandardshows 14 (was 13), both derived fromPROFILESso they can't drift again; thefullline drops the stale hardcoded66forall skills. (#834) (#847)
[1.4.0] - 2026-06-08
Added
- Research is now cached, curated-first, and code-governed — a content-addressed Research Store (per-source TTL), a single provider waterfall with confidence tiers, and registry-API package legitimacy replace the per-agent prose waterfall and the slopcheck bolt-on. (#664) Confidence is now verification-evidence-driven: provider identity alone no longer yields HIGH; HIGH requires ground-truth corroboration (e.g.
legitimacyVerdict: 'OK'), authority alone caps at MEDIUM, and SLOP caps at LOW. (#664) /gsd:plan-phasenow accepts a--granularity <coarse|standard|fine>flag to override the configured planning granularity for a single invocation. The flag takes precedence overgranularities.planning, top-levelgranularity, andplanning.granularityconfig. Invalid values are rejected. (#703) (#750)- gsd-core can now be installed as a native Claude Code plugin — a new
.claude-plugin/plugin.jsonmanifest enables installing gsd-core viaclaude plugin installor the zero-friction~/.claude/skills/auto-load path (gsd-core@skills-dir), with slash commands auto-namespaced as/gsd-core:<command>(e.g./gsd-core:plan-phase) and lifecycle management viaclaude plugin enable|disable|update. gsd-core's always-on guard and update hooks are wired for the plugin path throughhooks/hooks.jsonusing${CLAUDE_PLUGIN_ROOT}. This is additive — the existing npm / file-copy installer is unchanged. (#797) - Installer pre-populates
permissions.allow/denyfor Claude Code — fresh Claude Code installs now receive GSD's known-safe tool-call patterns (Bash(npx gsd-core *),Read(.planning/*),Write(.planning/*),Read(STATE.md),Write(STATE.md)) insettings.jsonout of the box, eliminating first-run approval prompts. Adenyblock for credential files (Read(.env),Read(.env.*),Read(.secrets)) is also added for defense-in-depth. The merge is additive and idempotent; existing user-set entries are preserved. Uninstall removes only GSD-owned entries. (#768) (#819)
Added: register newly-available Claude Code lifecycle hooks — SubagentStop, Stop, PreCompact (all wired to gsd-context-monitor for context-headroom warnings), and FileChanged (matcher: config.json, wired to new gsd-config-reload.js hook that hot-reloads .planning/config.json context mid-session). Also updates hooks/hooks.json (plugin manifest) and managed-hooks-registry for drift-guard coverage (#770). (#821)
- Gemini installs now register three additional hook events —
BeforeAgent,AfterAgent, andBeforeModel— wired togsd-context-monitor.jsfor per-turn context headroom tracking. Previously onlySessionStart,BeforeTool, andAfterToolwere registered. The installer also detectshooksConfig.enabled: falsein the user's Geminisettings.jsonand emits a clear warning, surfacing the silent failure mode where all hooks are registered but never execute. (#776) (#829) - Cross-runtime command enrichment in the installer. Gemini CLI commands now use native
{{args}}interpolation (translated from Claude's$ARGUMENTS) so typed arguments interpolate into the prompt body, and/gsd:progressinjects live project state via a fixed, injection-safe!{cat .planning/STATE.md 2>/dev/null}shell block. Qwen Code skills now carry a numericpriorityfield so the most-used main-loop workflows (new-project,plan-phase,execute-phase, …) surface first in the/skillslist. The OpenCode per-commandmodel/agent/subtaskenrichment was evaluated and intentionally not implemented —modelwould reintroduce the ProviderModelNotFoundError regression that the converter deliberately guards against for non-Anthropic providers (#1156),subtask/agentchange execution semantics for GSD's interactive commands, andvariantis not in the OpenCode command schema. (#778) (#825) - Emit native on-demand skills (
skills/<name>/SKILL.md) for the OpenCode-family runtimes (OpenCode and Kilo) at install time, in addition to the existing flatcommand/and file-basedagents/surfaces. OpenCode and Kilo share a config schema and both discover skills fromskills/<name>/SKILL.md; the installer now stages each GSD command as a skill with minimal, spec-compliant frontmatter (namematching the directory,description1–1024 chars) via a shared OpenCode-family skill writer. Skills respect the active install profile (core/minimal stage only their subset) and are removed on uninstall. (#784) (#810) gsd install --cursornow writes.cursor/commands/gsd-<name>.mdin addition to the existing.cursor/skills/surface. Cursor 1.6 introduced plain-markdown slash commands (no frontmatter) in.cursor/commands/; they appear in the/menu in the Agent input. Each command file is generated from the same source as the skill but with frontmatter stripped and Cursor-specific content transforms applied (convertClaudeCommandToCursorCommand). The skills surface is unchanged — both surfaces are written on every install. (#803)- The GitHub Copilot installer now reaches lifecycle-hook and instruction parity with other first-class runtimes. It emits a self-contained
sessionStarthook config (.github/hooks/gsd-session.jsonfor local installs,~/.copilot/hooks/gsd-session.jsonfor global) and writesAGENTS.mdat the repository root (which Copilot CLI reads as primary instructions) alongsidecopilot-instructions.md. The hook is an inlinecommandhook with no separate script file, so it cannot dangle. Both artifacts are removed — with user-authored content preserved — on--uninstall. (#786) (#804) - Elevate the Cline runtime to hook parity. The installer now emits the Cline
.clinerules/directory form (.clinerules/gsd.md) instead of a single.clinerulesfile, adds a.clinerules/hooks/PreToolUselifecycle hook (Cline v3.36+ JSON stdin →{cancel,errorMessage,contextModification}protocol; guards.planning/artifacts and fails open), and merges GSD instructions into the cross-tool global~/.agents/AGENTS.mdtarget on global installs. A legacy single-file.clinerulesis migrated to the directory form in place, and--uninstallremoves the new artifacts and strips the GSD block from~/.agents/AGENTS.md. (#787) (#803) - Qwen Code installs now register three additional hook events that Qwen Code supports beyond Claude Code:
SubagentStop,Stop, andPreCompact— all wired togsd-context-monitor.jsfor context headroom tracking at subagent completion, model stop, and pre-compaction. These events are Qwen-only; Claude Code installs are unchanged.UserPromptSubmitis deferred:gsd-prompt-guardexits unlesstool_nameisWrite|Edit, making it a no-op for that payload shape. (#788) (#807) - CodeBuddy (Tencent) installs now emit
/gsd-*slash commands. A--codebuddyinstall writescommands/gsd-<name>.mdfiles to~/.codebuddy/commands/so GSD workflows are invokable from CodeBuddy's/menu (/gsd-phase,/gsd-ship, etc.), matching the integration depth of other fully-elevated runtimes (#789). The existingskills/gsd-<name>/SKILL.mdfiles are now emitted withuser-invocable: falseso they stay out of the/menu — the commands surface is the single/entry point (no duplicate entries) and skills remain available for model invocation. Subagents (~/.codebuddy/agents/) were already emitted and are unchanged. Uninstall removes thegsd-*command files while preserving user-owned commands. Nomcp.jsonis written — gsd ships no MCP server and CodeBuddy'smcp.jsononly registers external MCP servers.
- Augment installs now emit slash command definitions alongside skills. A global
--augmentinstall writescommands/gsd-<name>.mdfiles to~/.augment/commands/in addition to the existingskills/gsd-<name>/SKILL.mdfiles, matching the integration depth of other fully-elevated runtimes and allowing Auggie users to invoke GSD as slash commands (/gsd-phase,/gsd-ship, etc.) without manual configuration (#790). Content rewrites (path normalisation and Augment-specific branding) are applied at install time. Uninstall removes thegsd-*command files while preserving user-owned commands.mcpServersregistration is explicitly excluded — gsd ships no MCP server and does not register third-party servers. (#801) - Issues are now checked for duplicates when opened: a no-LLM title-similarity check posts a challenge comment and applies a
possible-duplicatelabel when a new issue closely matches existing open ones. Flagged issues that go unanswered for 24h are auto-closed as duplicates (reply, or react 👎 to the bot comment, to keep one open); a reply clears the label and routes toneeds-maintainer-review. (#836) (#843) - Cursor now receives GSD lifecycle hooks via
.cursor/hooks.json— a sessionStart hook injects the current workflow state as context at session start, and a postToolUse hook nudges the agent to update.planning/after write-class operations, bringing Cursor to baseline hook parity with Gemini and Claude Code. (#777) - Gemini CLI extension package — gsd-core now ships a
gemini-extension.jsonmanifest (plus aGEMINI.mdcontext payload) at the repository root, so Gemini CLI users can install, update, and remove GSD through Gemini's own extension lifecycle:gemini extensions install https://github.com/open-gsd/gsd-core,gemini extensions update gsd-core,gemini extensions uninstall gsd-core, andgemini extensions link <path>for local dev. The extension is discoverable ingemini extensions listand loads GSD's operating context into every session. Additive — the existingnpx gsd-core --geminiinstaller (which provides the/gsd:*slash commands) is unchanged. (#775) (#775) - New
agent_skills_security.trusted_global_rootsconfig — opt-in allowlist of trusted root directories so symlinkedglobal:agent skills whose real path resolves outside the default skills dir (e.g.~/.claude/skills) are accepted; default[]is byte-identical and preserves the symlink-escape guard. (#754) - Added
/gsd-update --next(alias--rc) to install or refresh from the@nextRC dist-tag (ADR #660). A newparse_update_channelworkflow step resolves the channel from$ARGUMENTS; the version check and all three npx install invocations thread$TAGinstead of hardcoding@latest. When--nextis used the version-comparison output gains aChannel: next (RC)banner so the user knows they are leaving the stable line; omitting the flag keeps@latestbehavior byte-for-byte unchanged.check-latest-version.cjsgainsALLOWED_TAGS,buildViewArgs, andresolveTagexports, with an allowlist guard (enforced at both the CLI and function boundary) that rejects any dist-tag other thanlatest/next. (#815) (#839)
Changed
/gsd:plan-phase --research-phase <N>now auto-uses an existingRESEARCH.mdinstead of prompting update/view/skip. When research already exists and neither--researchnor--viewis passed, it emits a one-line notice and exits cleanly, matching the promptless behavior of standard/gsd:plan-phase <N>. Pass--researchto force-refresh or--viewto print the existing research. (#159) (#718)- Retire the installer's one-off runtime directory helpers (
getGlobalDir/getOpencodeGlobalDir/getKiloGlobalDir) and consolidate per-runtime global config-dir resolution onto the single canonical projectionruntime-homes:getGlobalConfigDir, extended with the--config-diroverride and the opencode/kilo*_CONFIGfile-path precedence. Behavior-preserving across all 15 install runtimes. (#56) (#802) - Make per-runtime config-mutation dispatch in the installer explicit: a new runtime config adapter registry maps each supported runtime to a typed config intent (install surface, shared-settings gate, finish-phase permission writer), and
install()/finishInstall()dispatch by resolved intent instead of inlineruntime === '...'branching. Behavior-preserving; unknown runtimes now fail loudly. (#60) (#795) - Verification status routing is now owned by a single queryable seam —
ship.mdandexecute-phase.mdboth consumegsd_run query verification.statusinstead of re-deriving thepassed/gaps_found/human_neededrouting independently; the query returnsnext_actionandnext_commandso per-status prose no longer needs to be kept in sync across files. This also fixes the broad-grep status misread inexecute-phase.mdwhere a bodystatus:line (in a code block or copied artifact) could concatenate with the frontmatter value and misroute a valid passed phase; a parity test fails if a new verifier status value lacks a route. (#651) (#755) - Agent
color:frontmatter now uses Claude Code's documented named colors (red/blue/green/yellow/purple/orange/pink/cyan) instead of hex values or the undocumentedmagenta, so the intended per-agent TUI color differentiation renders reliably across the Claude Code runtime. Display-only metadata; no behavior change. (#771) (#823) - Codex installs now register three additional stable hook events (
SubagentStart,Stop,PostToolUse) wired togsd-context-monitor.js, matching the full event coverage available since Codex CLI stabilised these hooks. TheSessionStarthook entry gains acommandWindowsfield on Windows installs so the.cmdshim is used for native execution (Git Bash/MSYS cannot POSIX-execnode.exedirectly). Both new-event registration and uninstall paths handle the flat{ "EventName": [...] }and nested{ "hooks": { "EventName": [...] } }hooks.json shapes.gsd-context-monitor.jsand its Windows.cmdsibling are added to the managed-hook allowlist so idempotent re-runs de-duplicate entries correctly. (#772) (#827) - Codex CLI installs now emit two enrichments per agent and skill. Agent TOML enrichment: light-tier agents (haiku-equivalent,
routingTier: "light"in model-catalog.json) getservice_tier = "flex"andmodel_verbosity = "low"appended to their agent TOML, telling the Codex scheduler to use the flex tier (lower cost, background processing) and suppress verbose token output. Skill TUI chip: each installedgsd-*skill directory now receives anagents/openai.yamlfile withinterface.display_nameandinterface.short_description, making the skill appear in the Codex/skillspicker with a human-readable name and description drawn from the skill's existing short-description frontmatter. Both enrichments are additive and backward-compatible with Codex CLI ≥ 0.130.0. (#774) (#828) - Cline global installs now emit skills, not just rules: gsd writes skills to
~/.cline/skills/<name>/SKILL.mdfor Cline ≥ v3.48.0 (see Cline skills docs), in addition to the existing.clinerulesfile. EachSKILL.mdcarriesname/descriptionfrontmatter (agentskills.io) with paths rewritten to the.cline/convention. Local installs remain.clinerules-only. The.clinerulesrules file continues to be emitted for compatibility, and upgrading over an existing rules-only install emits the new skills on the next run. (#809) - Workflow size budget now measures bytes, not lines (#717).
tests/workflow-size-budget.test.cjsre-bases its tier ceilings (XL/LARGE/DEFAULT) from line counts to byte counts — deterministic, no tokenizer, and matching the unit vendors bound on (Codex's 32,768-byte project_doc_max_bytes cap). The #597 tighten-only ratchet and per-file semantics are unchanged; the budget's caching-independent quality rationale (context rot / attention budget) is now documented. (#719) - The
gsd-verifieragent no longer re-runs the full workspace test suite once per must-have during Step 7b spot-checks — it enumerates tests to prove existence and runs a single named test to prove a pass, invoking the full suite at most once per verification. (#753) /gsd-plan-phase,/gsd-execute-phase,/gsd-autonomousnow run in an isolated forked context on Claude Code —context: forkin skill frontmatter protects the main session's context budget. These three heavy skills also declareeffort: xhigh; quick-status skills/gsd-progressand/gsd-statsdeclareeffort: low. The installer preserves both fields when converting commands to Claude SKILL.md files. Runtimes that do not recognise these fields silently ignore them — no behaviour change on non-Claude runtimes. (#769)/gsd:plan-phaseand/gsd:execute-phaseno longer eagerly load MVP-only guidance on non-MVP runs — the MVP planner rules, user-story template, Walking-Skeleton template, and MVP+TDD halt-report reference are now Read lazily by the planner/executor only when MVP / Walking-Skeleton / MVP+TDD mode is active, in both the workflow files and thegsd-planner/gsd-executoragent definitions, instead of being@-imported into every run. Behaviour is unchanged; non-MVP planning/execution simply carries less context. (#720) (#746)
Automated codex exec invocations in the review workflow now include --ephemeral (no session-state accumulation across automated/CI runs) and --dangerously-bypass-hook-trust (skip hook-trust prompts for hooks managed by gsd-core itself). These flags apply only to the non-interactive reviewer invocations in gsd-core/workflows/review.md. (#773) (#824)
- Codex slash-command conversion no longer corrupts inline-wrapped
/gsd-…file paths — the install-time converter now identifies a real/gsd-<command>mention by positive boundaries (opening delimiter + no path continuation) instead of an unbounded preceding-character denylist, closing the path-corruption class (#637 → #704) by construction while still converting legitimate backtick-wrapped mentions. (#747) - The release pipeline now automatically runs
changeset renderduring the finalize job, promoting.changeset/fragments into a datedCHANGELOG.mdsection before publishing — previously a manual step that was routinely skipped (leaving v1.3.0 and v1.3.1 unpromoted, #690). A new--allow-emptyflag prevents the verify gate from hard-failing on no-change releases by emitting a dated heading with a_No notable changes._placeholder when there are zero fragments. (#715)
Fixed
/gsd-review --cursornow actually invokes the Cursor agent. Detection probes thecursor-agentheadless binary instead of thecursorIDE launcher, the invocation calls the singlecursor-agentbinary in print mode (not the two-tokencursor agent, which the IDE treats as a file path), and the review prompt is passed as a file-path argument rather than piped to stdin (whichcursor-agent -pignores). On failure the captured stderr is surfaced instead of a silent empty result. (#686)- No more "gsd-core" console-window flash on Windows. Every gsd-core child process now passes
windowsHide: true: the context monitor'srecord-sessionspawn, theexecGit/execNpm/execToolhelpers inshell-command-projection, thegsd-worktree-path-guardandgsd-workflow-guardhook git probes,check-command-router'sgit logcall, and theroadmap-upgradegit status/rev-parse/reset/clean calls — matching the existinggsd-check-updatespawn.execNpm(which usesshell: true→cmd.exeand runs on every SessionStart, i.e. every/clear) and the worktree-path guard (which runs on every Edit/Write in a worktree) were the most visible offenders. No behavior change on macOS/Linux, where the flag is ignored. (#688) /gsd-review --agyno longer hangs the whole review on large prompts. On a big, file-path-rich prompt Antigravity'sagy -pagentic Cascade can loop on itscode_search/grep steps and never converge. The invocation now passes agy's own--print-timeoutflag (its native print-mode cap) so a stalled run self-terminates through the tool's own mechanism; on a non-zero exit any partial output is discarded so the existing transcript fallback / "review failed" stub take over. (#689)- The roadmap parser now resolves fresh phases of the current milestone in multi-milestone roadmaps.
extractCurrentMilestone()scoped the current-milestone window to its## Phaseschecklist subsection and stopped at the milestone's own## Milestone … (Phase Details)heading, so the### Phase N:detail headers fell out of scope. Any command backed by the parser —init.phase-op(and therefore/gsd:discuss-phaseand/gsd:plan-phase),state,roadmap list, andvalidate health(W006) — could not resolve phases of any milestone after the first until a.planning/phases/directory already existed, blocking discuss/plan. The parser now also includes the current milestone's(Phase Details)section in scope, anchored to the selected milestone's version token so sibling sub-milestones do not cross-pollinate. (#730) (#748) getGlobalSkillsBase('kilo')now resolves to~/.kilo/skills— where Kilo Code actually discovers global skills — instead of~/.config/kilo/skills. Per Kilo Code docs, global skills live in the.kilodirectory within HOME (~/.kilo/skills/), independent of the XDG-based config dir at~/.config/kilo. The kilo.jsonc config dir (~/.config/kilo) and thecommand/path used by the installer are correct and unchanged. Blast radius: this corrects the resolved skills-base path used by doctor/status checks and agent-skills-block resolution (init.cjs); the installer writes commands (not skills) for Kilo, so no files were previously being written to the wrong location. (#806)- Honor the
COPILOT_HOMEenvironment variable when resolving the GitHub Copilot global config directory. Previously a global--copilotinstall ignoredCOPILOT_HOMEand wrote all artifacts (skills, agents,copilot-instructions.md, the session hook) to~/.copiloteven when the user had relocated their Copilot home, making them undiscoverable by Copilot CLI. Resolution now follows--config-dir>COPILOT_CONFIG_DIR>COPILOT_HOME>~/.copilot, mirroring the existingCODEX_HOMEhandling. Uninstall uses the same resolver and stays symmetric. (#812) (#814) - Release version bumps now keep runtime manifest versions in sync —
.claude-plugin/plugin.jsonandgemini-extension.jsonare stamped to matchpackage.jsonon everynpm version, unblocking RC/finalize releases. New version-bearing manifests must be registered inscripts/sync-manifest-versions.cjs(enforced by a regression test). (#845) npx @opengsd/gsd-coreupgrades no longer abort with "applied migration checksum changed" — an already-applied installer migration whose recorded checksum drifted (e.g. a shipped body was edited) is now detected and reconciled automatically on the next install, instead of hard-failing the upgrade. Replaces the published-checksum allowlist with general self-healing recovery plus a CI baseline lock. (#675)/gsd-import,/gsd-plan-review-convergence, and/gsd-spec-phasenow run on global installs — these workflows resolvegsd-toolsvia the runtime launcher instead of a hardcoded$HOMEpath, so they no longer falsely report the tool as "not found" (and stop short) when only a global/shim install is present and no project-local runtime exists. (#642)- Worktree wave-cleanup no longer fails when the phase SUMMARY is committed —
rescueSummaryArtifactsno longer copies an already-committed SUMMARY into the main checkout, which previously causedgit merge --no-ffto abort with a permanentmerge_failed(#706). (#709) - Phase execution no longer halts with
exit 42(worktree base mismatch) when run on a branch diverged from the default branch (#683). Claude Code forks worktree-isolated executors off the repository default branch (origin/HEAD), so running/gsd-execute-phaseon an unmerged milestone/feature branch left every executor without the phase's plan files and tripped theworktree-branch-checkguard (100% reproducible, all OSes). Execute-phase now detects this before dispatch and automatically degrades to sequential execution on the main working tree, recommending the permanent fixworktree.baseRef:"head". Both fresh installs and upgrades of GSD Core setworktree.baseRef:"head"in.claude/settings.local.jsonautomatically (no-clobber) whenworkflow.use_worktreesis enabled (the default);gsd-tools worktree set-baserefremains available for manual use (e.g. after toggling worktrees on later). Theexit 42guard remains as a backstop. (#749) - Codex install no longer corrupts launcher paths — shell path segments like
${VAR}/gsd-core/and$(cmd)/gsd-local-patchesare no longer rewritten into a literal$gsd-coretoken during Codex markdown conversion (#704). (#710) /gsd:surfaceno longer corrupts installed skill paths — re-surfacing (profile/enable/disable/reset) now applies the same per-runtime path rewrites as install, so SKILL.md bodies keep the correct install target instead of reverting to the converter's default~/.claudepaths. (#817)/gsd:graphify,/gsd:import, and planning agents now resolvegsd-toolson global/shim-only installs — agent and command surfaces that invoked a hardcoded$HOME/.claude/...gsd-tools.cjspath now route through the resolvedgsd_runlauncher, so the step no longer reports the tool "not found" when there is no project-local runtime. (#707)/gsd:surfaceno longer mis-names or orphans runtime command files — re-surfacing now writes the samegsd--prefixed command filenames as a fresh install for flat command dirs (Cursor, Augment, OpenCode, Kilo) and preserves user-authored command files instead of deleting them. (#822)/gsd:updatereliably previews release notes again — promotes the 1.3.x changelog into dated[1.3.0]/[1.3.1]sections, stops deleting the temp changelog before the human-readable render (no more(changelog unavailable)), and adds a release gate that blocks publishing a version whoseCHANGELOG.mdsection was never promoted. (#694)
Security
gsd-tools config-setprototype-pollution guard hardened and regression-tested. The guard that blocks__proto__,prototype, andconstructorsegments in dotted config keys now uses inline literal comparisons at each property-write site (instead of a pre-loopSetcheck), so CodeQL'sjs/prototype-pollution-utilityanalysis recognises it as a sanitising barrier and code-scanning alert #26 clears. Runtime behaviour is unchanged from #663. Added regression tests that drive schema-valid dynamic-prefix keys (agent_skills.__proto__,agent_skills.constructor,features.__proto__,review.models.constructor) all the way to the guard — these reachsetConfigValuepast the schema gate and were previously the guard's only untested attack surface. (#751) (#752)- Hardened roadmap-phase parsing and config writes — resolved ReDoS in phase-heading/plan-filename regexes (validate/verify/commands/phase), blocked prototype-pollution through dotted config keys in
config-set, and pinnedqs >= 6.15.2(DoS advisory). (#665)
1.3.1 - 2026-06-04
Security
- Bumped
honoto clear a moderate npm advisory carried transitively in the dependency tree. (#670)
Fixed
- Installer-migration checksum drift no longer blocks upgrades — the updater now self-heals when a shipped migration's recorded checksum has drifted, reconciling the stored checksum instead of aborting. Restores upgrades across all OSes after shipped migration bodies were edited in a prior release. (#670)
1.3.0 - 2026-06-04
Added
- Vertical MVP Slice mode —
--mvpflag on/gsd-plan-phaseswitches the planner from horizontal layer decomposition to vertical feature-slice decomposition (UI→API→DB in one task sequence). On Phase 1 of a new project with no prior phase summaries, also emitsSKELETON.mdvia Walking Skeleton mode. Composable with--tdd:--mvp --tddproduces vertical slices where every behavior-adding task starts with a failing test. Phase-level persistence via**Mode:** mvpin ROADMAP.md applies--mvpautomatically without the flag. (#78) /gsd-mvp-phasecommand — guided MVP planning: prompts for a user story (As a / I want to / So that), runs SPIDR story-splitting check (Spike/Paths/Interfaces/Data/Rules axes), writes**Mode:** mvpto ROADMAP.md, then delegates to/gsd-plan-phase. (#78)- MVP-aware UAT framing in
verify-phase— when a phase hasmode: mvp, the verifier generates a user-flow-first UAT script (walks the feature as a user would) before any technical checks. (#78) - MVP progress and stats display —
progressandstatscommands show Walking Skeleton completion status and per-feature-slice status lines for MVP-mode phases. (#78) - Six MVP reference files —
planner-mvp-mode.md,skeleton-template.md,user-story-template.md,spidr-splitting.md,execute-mvp-tdd.md,verify-mvp-mode.md— loaded by the planner, executor, and verifier agents when MVP mode is active. (#78) - Milestone-prefixed phase ID convention (M-NN) for globally unique phase IDs within a project (#39)
getMilestoneFromPhaseId()andgetPhaseDirFromPhaseId()helpers in core.cjs (#39)- W021 validation rule: fires when a phase ID's integer prefix mismatches its enclosing milestone section (#39)
gsd-tools roadmap validatesubcommand for convention compliance checking (#39)gsd-tools roadmap upgrade --convention milestone-prefixedmigration tool (dry-run by default,--applyto mutate) (#39)phase_id_conventionconfig field (null|'milestone-prefixed'|'free-form'), defaults tonull(legacy free-form, no breaking change) (#39)
Fixed
isDirInMilestonenow correctly matches M-NN-style phase directories against milestone-prefixed ROADMAP headings (#39)searchPhaseInContentheading regex now tolerates[bracket-token]scope prefix (e.g.,### [GSD] Phase 2-01:) (#39)- README version guidance now uses npm/package metadata as the source of truth — README, localized READMEs, and the docs index no longer present archived release-note or canary-stream numbers as the current GSD Core package version. (#545)
1.2.0 - 2026-05-31
1.2.0 is the current stable @opengsd/gsd-core release. It resumes the public package line after the release-version validation recovery documented in ADR 218 and makes @opengsd/gsd-core / gsd-core the canonical package and CLI identity.
Added
- Plan-vs-codebase drift guard — plan review can verify generated plans against live source symbols before execution so hallucinated files, APIs, or commands are caught earlier. (#487)
- Single Package Identity seam — package name, CLI identity, update checks, and installer identity are centralized so
@opengsd/gsd-corestays consistent across runtime surfaces. (#499, #517, #521) - Cross-provider effort controls and fast-mode-aware routing — model-effort selection works across providers and can adjust routing for faster workflows. (#463)
- Current public docs and install identity — README/docs now advertise GSD Core,
@opengsd/gsd-core, and thegsd-corebinary as the canonical user-facing surface. (#519, #523, #540)
Changed
- SDK shim retired from installer/runtime docs — workflows now route through
gsd-tools; dead SDK-shim verification and stale SDK-generated banners were removed. (#522, #515, #510) - Release numbering recovered at
1.2.0— leading-zero release inputs are invalid and duplicate-version checks fail early before publish work begins. See ADR 218. - CI/test selection is more precise — affected-test selection now widens docs/test-impact correctly and avoids under-testing relevant PRs. (#495)
Fixed
- Planning writes are more reliable — phase completion writes are transactional and no longer corrupt milestone progress counters. (#465, #514)
- Roadmap and milestone parsing no longer leak stale phase details into active milestone state. (#513)
/gsd:updatedetects local Antigravity.agentinstalls and repo-local Claude installs correctly. (#512, #476)- Package identity registration no longer regresses update/runtime detection. (#521)
Legacy Release History
Release notes for every version published before the project was renamed to @opengsd/gsd-core — the retired get-shit-done-cc / get-shit-done-redux lineage, versions 1.0.0 → 1.42.x plus pre-release and canary builds — have been rolled up into a single archive:
➡️ docs/RELEASE-NOTES-LEGACY.md
Those legacy 1.x numbers belong to the previous package line and predate the current @opengsd/gsd-core versioning, which restarts at 1.0.0. They are preserved verbatim-in-spirit (condensed) in the archive and intentionally kept out of this file so the two version streams cannot collide.