chore: promote CHANGELOG for v1.10.0

This commit is contained in:
github-actions[bot]
2026-08-08 05:06:54 +00:00
parent 9335dc00d2
commit 68a04ccf8e
118 changed files with 155 additions and 610 deletions

View File

@@ -6,6 +6,161 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
## [Unreleased]
## [1.10.0] - 2026-08-08
### Added
- **A blocking catastrophic-shrink guard now protects curated `.planning/` artifacts from whole-file `Write` clobbers** — the new `PreToolUse` hook `gsd-write-guard.js` compares the pending `Write` payload against the file on disk and hard-blocks (`decision: 'block'`, exit 2) when the payload would collapse `ROADMAP.md`, a milestone roadmap (`.planning/milestones/*-ROADMAP.md`), or `STATE.md` below 40% of its current line count (files under 40 lines are exempt). The check is stateless per Write — each payload is compared against the file's current on-disk count, so the single-shot collapse is blocked while iterative erosion across individually-tolerated Writes is a disclosed non-goal. This is fix 3 of #973 — the only one enforced by code rather than by instructions to a model: fixes 1 and 2 (PR #989) are prose an agent may reason past and protect only audited agents, and #973 records an agent reading the existing advisory and reasoning past it while destroying three milestones of roadmap history. The guarantee is bounded, and the bound is worth stating precisely: this blocks accidental and single-shot collapse, and does not stop a determined agent — the sentinel below is a plain file, so an agent that would reason past an advisory can arm one with a single `Bash` call it is already permitted to make. What ships is the conversion of *ignore a sentence* into *take one deliberate, path-bound, single-use, auditable action* — a real improvement against the confused-agent threat #973 records, not a defense against an evader. Legitimate milestone resets bypass the guard mechanically: the workflow step writes the target's path into the single-use sentinel `.planning/.gsd-allow-shrink`, which the guard verifies (fresh, path-bound) and consumes — a per-step env var cannot reach a PreToolUse hook, so the sentinel is the transport code consults rather than prose an agent obeys; interactively, `GSD_ALLOW_PLANNING_SHRINK=1` still bypasses once. Both are named in the block message. Registered on the Claude plugin surface, the settings-json runtimes, Kimi, and the OpenCode/Kilo plugin buses; on Kimi the guard normalizes the native payload shape (`WriteFile`, `path`) and writes its block reason to stderr, so it engages there from day one (the #2304 dormancy class). (#2255) (#2301)
- **New how-to: [Take over a capability, reviewer lane, or EoS integration](../docs/how-to/take-over-a-capability-or-eos.md).** The capability ecosystem documented a complete forward lifecycle — develop, publish, version, import, update, remove, turn off — but nothing covering a change of *maintainer* for an entry that already exists. There is no `gsd capability transfer` command and no rename tooling, and `docs/registries/README.md` specifies submission and the narrow removal policy but never transfer, so a would-be adopter had no documented path and a reviewing maintainer had no stated bar.
The guide defines four takeover modes and the PR shape each one takes. **T1 — consensual handoff** keeps the `id` and the entry, changes only `repo` / `author` / `install` / `uninstall`, and requires a permalink to the outgoing author's public handoff comment in the entry's Discussion. That permalink is mandatory rather than advisory because entry-update authorship is not verified anywhere: `scripts/registry-schema.cjs` and `npm run validate:registry` check an entry's shape, not who is changing it, and the registry-entry PR template's "`repo` links to a repository I own" is a self-attestation — so a PR repointing `repo` and `author` at an unrelated account passes every automated gate, and the reviewing maintainer is the only control. **T2 — adoption fork** takes a new `id`, opens a new Discussion, and leaves the original entry untouched, because the narrow removal policy removes an entry only for illegal content, malware, spam, or a dead link and never for staleness or abandonment: an abandoned-but-working entry can never be reclaimed, so adoption is always additive and the original `id` stays taken. **T3 — first-party absorption** routes through `approved-feature` plus an ADR, lands under `capabilities/<id>/capability.json` per ADR-894, annotates rather than deletes the registry entry, and requires a migration note telling existing users to `gsd capability remove <old-id>` first — config keys are exclusive to one capability and skill/agent stems must be unique, so a first-party capability that collides with an installed overlay wins *silently*, leaving the user running code they did not think they were running. **T4 — retirement** is restricted to the four narrow grounds with evidence in the PR body.
Around the modes the guide adds an evidence pack, license and reserved-prefix and consent gates, a per-surface snapshot of the inherited user-visible contract (`loopExtensionPoints` / `hookKinds` / `configKeys` / `requires` / `runtimeCompat` for Feature Capabilities; `slug` / `flags` / `reviewsSection` uniqueness across the merged first-party and overlay set for reviewer lanes; `protocolVersion`, `interfacePoints`, `profile` and the eight ADR-1239 axes for EoS integrations), and an install-continuity checklist covering the failure modes that break existing consumers — `id` continuity, since consent is stored per `(realpath(projectRoot), capability id)` and an `id` change re-prompts every installed project and orphans the update path; re-stating `integrity` and `provenance` after a rebuild under new ownership; holding the executable-surface set steady so the handoff is not itself a consent event; and not narrowing `engines.gsd` without a matching `compatVersions` row. Post-takeover obligations note that a Release must be cut under the new repo, since there is no re-registration and both the shields badge and the `releases/latest` permalink render live from `repo`. The two enforcement gaps — unverified entry-update authorship, and the absence of any `id` migration path — are stated explicitly in the guide so the process is not mistaken for something CI verifies.
**Fixed alongside:** `.github/PULL_REQUEST_TEMPLATE/registry-entry.md` directed contributors to file their Discussion in a `Registry` category that does not exist. `docs/registries/README.md` names the category `EoS Registry` and explicitly notes the name is misleading because it carries threads for all three catalogs. Because `discussion` is a required field, the thread must exist *before* the entry's PR is opened — so a contributor following the template stalled at the first required step of the submission process. (#2999) (#3000)
- **MCP-capable hosts can now browse GSD's own workflows, references, and commands through the companion server** — the workflow and reference tree is served as MCP resources and the `/gsd-*` commands as MCP prompts, so a host lists and fetches just the content it needs instead of relying on the copied file tree alone. Workflow resources arrive composed exactly as the installer writes them; the file-copy install is unchanged and stays the default on every runtime. (#3072) (#3083)
- **UAT checkpoint frames now cover 9 more languages** — `response_language` values of Dutch, Polish, Russian, Ukrainian, Turkish, Hindi, Arabic, Vietnamese, or Indonesian render a localized checkpoint banner/instruction instead of silently falling back to the English frame (#2530). (#2564)
- **Agent-dispatch isolation guard.** An executor subagent dispatch that would run outside an isolated worktree is now hard-blocked when this dispatch's resolved isolation is harness-worktree, closing the #260-class main-checkout write path a prose-only instruction could silently skip — while correctly leaving legitimate sequential or orchestrator-managed dispatches (project opt-out, submodule intersection, diverged-base auto-degrade) untouched, since the guard reads the workflow's own resolved per-dispatch decision instead of a host's general capability. Covers a missing `isolation="worktree"` parameter on the `Agent()`/`Task()` dispatch, as well as a `subagentStart` dispatch whose session is not actually running in an isolated worktree, verified structurally since a session-level worktree flag carries no per-dispatch isolation parameter to check. (#3045) (#3069)
- **Unresolved `deferred-items.md` entries now reach the milestone-close audit.** `auditOpenArtifacts` gains `deferred_items` as a ninth scanned category, so an out-of-scope discovery a phase agent correctly recorded rather than fixed surfaces in `/gsd-complete-milestone`'s pre-close report alongside the other eight, and the existing `[R] Resolve / [A] Acknowledge / [C] Cancel` prompt applies to it. #2287 made the file readable at the phase boundary (`audit-uat`, `/gsd-progress` check 7); one boundary up it was still invisible, and phase directories archive to `milestones/vX.Y-phases/` by default (#1871), so an unresolved entry left the live tree without ever being triaged. The resolved/unresolved predicate is not reimplemented — the scanner lazily requires `uat.cjs`'s exported `parseDeferredItems`, so both boundaries agree by construction about what "open" means. **Behaviour change worth noting before you upgrade:** a project carrying unresolved deferred items will now see the `[R]/[A]/[C]` prompt at milestone close where close previously proceeded silently. That is the intended correction, but it surfaces pre-existing debt on the first run. (#2646) (#2983)
- **Reviewer lanes can now be listed for discovery** — a new how-to walks lane authors through publishing to the Reviewer Lane Registry: which of the three catalogs applies, opening the required discussion thread first, the fields that reject entries most often, and why registering once means GitHub Releases become the update channel. (#2904) (#2917)
- **Workflow markdown can now fragmentize into per-runtime-composed sections.** Authors can mark sections of a workflow file with in-file `<!-- gsd:section id= when= -->` markers; per-runtime emission strips the markers and composes the marked sections back byte-identical-or-smaller, piloted on `execute-phase.md`. (#2930) (#2972)
- **`gsd_run query context-predicates` — targeted lookups against the `CONTEXT.md` fact-store** — search predicates live by class, id prefix, or substring instead of reading the whole file, with a CI-guarded `docs/CONTEXT-INDEX.json` index kept in sync automatically. (#2928) (#2938)
### Changed
- **Agent definitions now share the workflow fragment pipeline** — a `<!-- gsd:section -->` marker in an `agents/*.md` file is stripped at install time on every emission path instead of shipping verbatim into the runtime, and the largest agents move their reference material into `gsd-core/references/` so they regain headroom under their size caps. (#2995) (#3058)
- **Windsurf command install no longer fails on an oversized description, and emitted artifacts are now checked against their host's byte limit** — the Windsurf workflow converter truncates a long description instead of throwing, matching the bound its sibling skill converter already applied, and a new per-runtime cap gate measures what each runtime actually receives rather than what the source files weigh. (#2931) (#2984)
- **`/gsd:debug` now initializes in one round-trip instead of three** — the workflow previously made three separate `gsd-tools` calls to assemble its context (`state.load`, `resolve-model`, and `config-get workflow.tdd_mode`); it now makes a single `init.debug` call carrying the same resolved values. (#3149) (#3154)
- **Documented the widened `when=` vocabulary and the per-workflow section manifest.** `docs/reference/workflow-fragments.md` now lists all 14 closed `when=` atoms, the two admission gates a new atom must clear, the manifest artifact's per-workflow `{workflows:{<name>:[...]}}` shape (absent key = degraded, empty array = computed-empty), and that boolean-flag membership in `InvocationFacts.flags` is token-presence, not value-truthiness. Also added the missing `--reset-phase-numbers` flag to `/gsd-new-milestone`'s argument-hint. (#2992) (#3013)
- **The extracted workflow fragment tree is now inventoried** — the 47 step files and 13 mode files that live under `gsd-core/workflows/<workflow>/` were invisible to `docs/INVENTORY-MANIFEST.json`, so a new one could ship with no row and no gate firing. They now have their own manifest families. (#2996) (#3061)
- **Workflow guidance now loads only the branch your invocation actually took** — thirteen more large workflows moved onto the fragment model, so running `/gsd-code-review` without `--fix` no longer loads the fix-dispatch branch, `/gsd-progress` without `--forensic` no longer loads the forensic audit, and so on across every migrated workflow. (#2994) (#3030)
- **Flag-gated workflow guidance is now actually loaded on demand** — `/gsd-plan-phase` reads its PRD-express, ADR-ingest, reviews-prerequisite, research-only and chunked-planning guidance only when the matching flag or config is active, instead of always inlining all six branches. This also repairs `/gsd-execute-phase --wave`, whose section gating never took effect because the workflow never forwarded the flag to the init bundle, so wave-filtering guidance was silently skipped on every run. (#2993) (#3019)
- **Budget-aware content composition is now a shared `context-composer` seam** — the priority-ordered trimming that kept cross-AI review prompts inside a model's context window was locked inside that one pipeline. It is now a reusable seam with an injectable budget unit, so later work can right-size what ships to each runtime. Review-prompt output is unchanged, proven byte-for-byte against a 50-case corpus captured from the previous implementation. (#2929) (#2958)
- **`/gsd-execute-phase` now loads only the branch guidance your invocation actually uses.** Running it without `--wave` no longer pulls the wave-filtering instructions into context, and a plain integer phase no longer loads the decimal-phase gap-closure branch. The init bundle reports which sections apply to each invocation and the workflow reads only those, so the orchestrator spends its context on the path it is actually taking. (#2932) (#2987)
### Fixed
- **Installing or updating GSD no longer destroys a user-authored `package.json` at the runtime config root** — the CommonJS marker (`{"type":"commonjs"}`) that pins GSD's staged `.js` scripts is now written into the directories GSD itself fills (`hooks/`, and `plugins/`/`extensions/` for the runtimes with a native plugin adapter) instead of over `<configRoot>/package.json`. Previously every install and every `/gsd-update` re-install overwrote that file unconditionally — no existence check, no merge, no backup — permanently destroying any `name`, `type`, `dependencies`, or `scripts` the user or host tool had put there. This hit 11 runtimes and was worst on OpenCode and Kilo, where the config-root `package.json` is the documented place to declare local-plugin npm dependencies. Install and uninstall now share one ownership predicate, so a `package.json` GSD did not write is never overwritten and never removed; uninstall still retires the marker left behind by earlier versions. (#2544) (#2593)
- **`workstream progress` / `workstream status` / `workstream list` no longer report a workstream's CURRENT milestone as "milestone complete" / 100% while phases in that milestone are unstarted, in progress, or failing verification.** Three coupled defects in the shared inventory derivation are fixed. (1) The shipped signal was project-lifetime rather than milestone-scoped — `workstreamMilestoneShipped()` returned true if ANY `*-ROADMAP.md` snapshot existed or `SHIPPED` appeared anywhere in `ROADMAP.md`, and since every previously shipped milestone leaves a permanent collapsed `<summary>✅ … SHIPPED</summary>` block, any workstream that had ever shipped was pinned to "milestone complete" forever (an over-correction from #1913). It now requires the CURRENT version's archived `milestones/<version>-ROADMAP.md` snapshot, or the current milestone's own ROADMAP line marked shipped; `REQUIREMENTS` snapshots are deliberately not accepted because they can be written at milestone start. (2) The completion percentage silently excluded phases declared for the current milestone but never scaffolded, while completed PRIOR-milestone phase directories inflated the numerator — both numerator and denominator are now scoped to the current milestone, whose phase set is read from the ROADMAP `## Progress` table (which lists phases with no directory) via the canonical `findTableWithColumns` parser, with the current version taken from the workstream `STATE.md` `milestone:` field rather than ROADMAP in-progress markers, which can be stale. (3) Phase completeness ignored the verification verdict — a phase with `SUMMARY` count ≥ `PLAN` count now counts as `in_progress` rather than `complete` when its verdict is an explicit failing one (`gaps_found`/`human_needed`); `missing`/`unknown`/`stale` are intentionally untouched so verifier-disabled projects do not regress to never-complete.
Two further denominator gaps are closed. A phase declared as a `## Progress` table row with **no `### Phase N` heading** was dropped by the heading-only count *even when other headings existed* (the regex counts 1 for a "1 heading + 1 table-only" roadmap), and milestone scoping could not cover it because a flat Progress table carries no per-phase milestone attribution — so greenfield and single-milestone projects kept the faulty count. When scoping cannot engage, the denominator is now the union of the Progress table's declared phase numbers and the phase directories, so neither source can shrink it. Separately, a sub-phase directory inserted mid-milestone (`30.1-…` under a table-declared phase 30) has no table row of its own and previously had no milestone attribution at all; it now inherits its parent phase's milestone and joins BOTH sides of the calculation — numerator-only would let `completed_phases` exceed a denominator that never counted it and cap back to 100%, reintroducing the reported defect. Attribution is one-directional (a sub-phase counts only when its parent is in the current milestone), so a follow-up created in a later milestone under an older parent is excluded rather than misattributed.
Membership and the denominator are derived from a single canonical phase-key surface, promoted to the phase-id owner module as `phaseKeyFromToken` / `phaseKeyFromDir` / `phaseKeyFromProse` / `parentPhaseKey` (previously a private pair in `state.cts`). Deriving one side of a comparison with a bespoke regex was itself a way to reproduce this issue: a padded `| 01. … |` table row never matched a `1-slug` directory, and a project-code-prefixed `PROJ-05-…` directory matched nothing at all — each silently zeroing or pinning the rollup while `phases[]` reported the opposite. Directory membership additionally consults `getMilestonePhaseFilter`, the module that owns milestone-phase filtering, which now accepts a workstream name so its `planningDir` resolution can target `.planning/workstreams/<ws>/` (a loop over workstreams cannot express that through `GSD_WORKSTREAM`) and exposes `versionScoped` so its phase count is never mistaken for a current-milestone denominator on an unversioned roadmap. "Milestone shipped" detection likewise moved to that module as `isMilestoneShippedInRoadmap`: heading and `<summary>` lines only — a bullet such as `- [x] 03-01: ship the v2.0 login endpoint ✅` is prose about a phase, not a milestone verdict — with the version token boundary-matched so a shipped `v2.0.1` heading cannot close `v2.0`. A ROADMAP row whose Milestone cell is blank or malformed now stays in the denominator instead of vanishing from both sides, and a stale directory colliding on phase number with a current one (Bug #2445's scenario) counts once; the Builder asserts `completed_phases <= denominator` and throws rather than letting `Math.min` round a contradiction up to 100%. `getMilestonePhaseFilter` still applies its own internal phase-id normaliser for directory matching rather than routing through `phase-id.cts`; the two signals are OR'd, so a divergence can only widen membership, never narrow it — but they remain two normalisers, not one.
A **declared-but-empty current milestone** is scoped rather than treated as unscoped. `STATE.md`'s `milestone:` field updates the moment `/gsd-new-milestone` writes the heading, while the `## Progress` table and phase sections land later; in that window nothing attributes a phase to the current milestone, scoping switched off entirely, and the fallback counted the project's whole phase history as both numerator and denominator — reporting 100% for a milestone with no work done, the same symptom by a different route. Three witnesses now distinguish that state, each covering a ROADMAP shape the others miss: `getMilestonePhaseFilter` gained `versionSectionFound` (the milestone's section exists but declares no phases — `versionScoped` cannot answer this, because a located-but-empty section falls through to the zero-count pass-all degrade that resets it), the existing `missingExplicitVersion` (a versioned roadmap with no section for this version), and a Progress table attributing every row to another milestone. A ROADMAP that attributes no versions anywhere matches none of them — its rows parse unattributed and stay in the current milestone — so free-form legacy projects keep their whole-roadmap count instead of regressing to 0%. Within an empty milestone, membership inverts: a phase directory belongs unless another milestone's row claims it, so a phase scaffolded before the roadmap catches up is counted rather than dropped from both sides. Scoping is now stated by the caller (`milestoneScoped`) instead of inferred from `currentMilestonePhaseCount > 0`, which could not represent "scoped and legitimately zero-phase".
**`status` is cross-validated against the milestone's own artifacts, not asserted from the shipped marker alone.** Scoping the marker to the current milestone stopped a PRIOR milestone pinning `status` to "milestone complete", but the marker was still echoed as fact for the current one — so a single payload could report `status: "milestone complete"` beside `progress_percent: 67`, which is this issue's own symptom reached through `status`. The two shipped signals are now distinguished and cross-checked at different strengths, because one check cannot serve both. A `heading` signal (an operator-typed `✅ SHIPPED` in the LIVE roadmap) is refused when the milestone's completion ratio is short, which also catches phases declared but never scaffolded. A `snapshot` signal (`milestones/<version>-ROADMAP.md`) is not gated on that ratio alone: the `milestone complete` run that writes it also moves the milestone's phase directories into `milestones/<version>-phases/` while copying — never truncating — the live ROADMAP, so a CLEAN archive reads 0/N by construction and a bare ratio gate would strip "milestone complete" from every archived milestone in every project. But a phase directory still present under `phases/` means the archive is not clean — a phase was added or reopened after it, reachable because `milestone complete` does not advance `STATE.md`'s `milestone:` field — and once that is true the ratio is meaningful again, so the snapshot check is the conjunction of the two. The `legacy` project-lifetime fallback is ungated by signal, as before. The cross-check as a whole engages only when milestone scoping is active, for ALL three signals and not just `legacy`: with scoping off the denominator is the whole-roadmap count and membership is everything, so there is no current-milestone artifact set to check a current-milestone claim against. When a marker is refused, the `STATE.md` field is not accepted as a fallback claim of completion either — in this window it commonly asserts the same thing — so against contradicting artifacts neither source can report the milestone complete.
Greenfield roadmaps without a versioned Progress table, and projects whose current milestone version cannot be determined, keep the previous behaviour. (#2562)
**Behaviour change for consumers of the inventory JSON:** `roadmap_phase_count`, `completed_phases` and `progress_percent` now describe the workstream's CURRENT milestone rather than its lifetime, and there is no schema signal marking the change. Anything reading those fields — including `getOtherActiveWorkstreamInventories`, which filters completed workstreams out of the active list — sees real movement: a post-v1.0 workstream that reported `milestone complete` / 100% will now report its actual in-flight progress. The inventory also gains `milestone_shipped_unverified`: true when a shipped marker fired for the current milestone but its artifacts contradicted it. It is distinct from `status_conflict`, which continues to report only the derived-vs-`STATE.md`-field disagreement. `workstream list`, `workstream status` and `workstream progress` all project the new field, so a refused marker is visible at the CLI rather than collapsing silently into a fallback `status`. (#2588)
- **Cursor now shows each GSD workflow once in the slash menu while keeping skills available for contextual model invocation** — Upgrades safely retire manifest-managed legacy `commands/gsd-*.md` duplicates, back up modified managed copies, and preserve unknown user-authored commands. (#2812)
- **Deleting a phase's verification report can no longer inflate workstream completion once that report has been seen.** Removing a `*-VERIFICATION.md` file after a failing `gaps_found` or `human_needed` verdict was recorded used to be indistinguishable from never having verified the phase at all, so `completed_phases` and `progress_percent` silently rose. `workstream status`/`list`/`progress` now remember the last real verdict observed per phase in a new `.verification-ledger.json` file alongside each workstream's `STATE.md`, so a failing verdict a prior read has already seen can't be erased by deleting its report. This adds a small write side effect to those previously read-only commands, and the file is a new tracked artifact under `.planning/workstreams/<name>/` for projects that commit their planning docs.
The ledger fails **closed**, not open: once a workstream has adopted it (the ledger file exists), a phase with no remembered entry — including one whose ledger entry can't be read because the file is corrupt or unreadable — is treated as not-yet-verified-and-blocking, not as safe-to-complete. A workstream that has never used the verifier is untouched (no ledger file is ever created for it), which is what keeps every existing project from dropping to `in_progress` the moment this ships.
**Three limitations, disclosed rather than silently left:** this is prospective only — a phase verified and its report deleted *before* this fix ships has no ledger entry and can't be recovered retroactively. Deleting the ledger file itself, not just the report, still returns that phase to pre-adoption behavior; this is inherent to any design where a wholly-absent ledger must be safe (the alternative is gating every never-verified phase in every project on upgrade), and is not something ledger design alone can close. And the ledger is not tamper-proof: anyone with write access to `.planning/workstreams/<name>/.verification-ledger.json` can hand-edit an entry to `"passed"` and the remembered value is trusted indefinitely — this is a *different* and arguably worse way to inflate completion than deleting the ledger (which at least resets to a visibly pre-adoption, untracked state), since an edited entry looks like genuine durable history. Integrity-checking the ledger's own content is out of scope for this fix. (#2645) (#3016)
- **`graphify query --budget <N>` now reports whether the budget was met** — the response carries `budget_met` and `budget_estimate` when a budget is requested. The estimate measures the response **as emitted** (the pretty-printed payload the caller is handed, wrapper keys included), so `budget_met` is a claim about the bytes you actually receive rather than about a smaller internal form. Seeds are retained unconditionally, so the seed set is a floor the edge-tier reduction cannot go below; previously a request for 500 tokens could return a ~119k-token payload with no signal that the budget was missed. The tier loop also now recomputes reachability and the estimate after each tier removal, so it stops as soon as the pruned result fits instead of dropping the next, higher-confidence tier unnecessarily. `--budget 0`, which the CLI accepts and forwards, is now honored as a (necessarily unmeetable, reported) budget instead of being silently treated as no budget. (#2738) (#2819)
- **`/gsd-spec-phase` now actually runs its edge-completeness and prohibition-completeness probes** — every gate-passed path reaches Step 5.5, and Step 5.5 now falls through to Step 5.6 instead of jumping past it. Previously all four gate-passed transitions went straight to SPEC generation and Step 5.5's own "all edges resolved" gate skipped the prohibition probe, so a SPEC could ship with an empty Edge Coverage section, an empty Prohibitions section, or both — and a weaker model following the prose literally would never notice. Since the probes are what carry must-NOT constraints and data-shape edges into `must_haves`, the plan and the verifier inherited the gap too. (#2733) (#2779)
- The api-coverage detector's negation-suppression check no longer takes superlinear time on long prose, which was hanging the verification gate (#2784, #3127). It also no longer fails to suppress a negated pair ("this phase integrates no external API") when the negation sits in any clause other than the first on a line — a latent offset bug made negation suppression a no-op for every clause after the first. (#3124)
- **`execute-phase` now warns when local commits are ahead of origin** — forking the phase branch from `origin/$DEFAULT_BRANCH` silently missed unpushed local commits (e.g. plan/research docs). A loud WARNING now names the divergence before the fork. (#2639) (#2981)
- **`broken-windows` capability no longer claims ship blocking is unconditional** — the description now states that `/gsd-ship` blocking applies only when `workflow.windows_enforce` is enabled (default `false`); ledger tracking is unaffected. (#2787) (#2814)
- **Worktree safety gates no longer report success when they could not check** — a git command that timed out (a locked index, a stalled network mount) was treated the same as "this is not a git repository", so the base-divergence gate answered "safe to run parallel worktrees" without ever resolving the fork base, and worktree-context resolution silently fell back to the current directory. The base-divergence gate now degrades to sequential execution instead of assuming safety. Worktree-context resolution still falls back to the current directory (there is no safer default), but now surfaces a loud warning that planning artifacts may be written to the wrong tree instead of silently trusting it. Worktree creation also no longer skips its root-confinement check when the caller omits the root. (#3050) (#3054)
- **roadmap.update-plan-progress no longer deletes hand-written annotations** — bumping the plan count used to swallow the rest of the Plans line, silently deleting any prose a human wrote after the count. The verb now replaces only the count token and leaves trailing text intact. (#2853) (#2916)
- **`detectApiIntegration` no longer triggers on negated prose** — a clause pairing an integration verb with an API noun but also containing a negation qualifier (`no`, `not`, `without`, `neither`, `nor`, etc.) is now suppressed. "This phase integrates no external API" no longer fires a false positive that halts verification. (#2784) (#3127)
- **Bug-report template version guidance corrected** — the template pointed reporters at `npm list -g`, which does not track what `/gsd-update` installs into the runtime home. It now points at the `gsd-file-manifest.json` version field that the installer writes. (#2998) (#3100)
- **`windows append`/`waive`/`fixed` no longer destroy prose below the JSON ledger** — the writer reconstructed the file from the parsed JSON ledger only, silently dropping any human-authored prose sections below the closing fence. The writer now preserves trailing prose across all write operations. (#2893) (#2975)
- **Gap-closure plans generated by `/gsd-plan-phase --gaps` now deterministically carry `gap_closure: true`** — the planner's frontmatter validator previously only checked plans against a schema that never required this field, so a gap-closure plan could silently omit it and `/gsd-execute-phase --gaps-only` would then match zero plans with no error. (#2847) (#3018)
- **`phase complete` no longer advances `next_phase` into 999.x backlog headings** — the roadmap heading scan (stage 2 of the next-phase cascade) accepted any higher-numbered heading without checking the sentinel convention, so a `Phase 999.1: Backlog Item` heading was treated as the next real phase. Sentinel phase ids (999.x backlog, 0.x drafts) are now skipped. (#2786) (#3130)
- **A split-parent phase marked complete in the ROADMAP is no longer permanently reported as `current_phase`** — a phase split into sub-phases (parent kept as shared context, zero plans by design) was stuck as `researched` because the roadmap-checkbox override required `completion.phase_complete` (always false for zero-plan phases). The override now fires for zero-plan phases when the roadmap checkbox is checked. (#3033) (#3114)
- **Dispatch flattening now honors the declared nesting depth budget, so runtimes that cannot host a backgrounded orchestrator plus a delegated leaf run inline instead of producing an unsupported depth-2 tree** — `shouldFlattenDispatch` checked only the two background booleans, so a host advertising `maxDepth:1` was told it may background, which under Codex MultiAgent V2 produced a depth-2 orchestration tree the declared contract forbids. The decision now also requires `nested` + a full subagent toolkit + a depth budget greater than 1 or unbounded, reusing the convention already in `degradationFor` and `_normalizeDispatchCallSpan`. Runtimes lacking any of those — codex at `maxDepth:1`, kimi with `nested:false`, kimi-code with a built-in-only toolkit — now correctly run inline, the safer path that keeps worktree isolation and verification in force; only cursor remains background-eligible. (#2939) (#3063)
- **pi installs no longer trigger pi's deprecated-directory startup warning, respect `PI_CODING_AGENT_DIR`, and never lose custom files during an update** — the shared hook bundle now installs to `gsd-hooks/` instead of `hooks/` (which pi reserves for its own deprecated extension location and warns about on every startup), with an upgrade migration retiring the old directory; pi's own `PI_CODING_AGENT_DIR` override is now honored when resolving where GSD writes; and `/gsd-update`'s custom-file detection now recognizes the renamed bundle, so user files placed under it are backed up before a clean install instead of being silently wiped. (#3023) (#3175)
- **`current_phase` no longer rewinds to an archived phase when STATE.md carries a historical `Phase:` line** — a stale `Phase:` or `**Phase:**` line in an archive section of a long-lived STATE.md silently overwrote `current_phase` on every state write, and because `current_phase` drives `gsd-progress` and `--next` routing the rewind sent work to the wrong phase. Phase extraction is now scoped to the `## Current Position` section (mirroring the existing `## Session` scoping for Stopped At / Paused At). (#2956) (#2961)
- **`/gsd-update --sync` no longer fails with MODULE_NOT_FOUND** — the sync-skills workflow shelled out to `gsd-core/bin/install.js`, which the installer never copies. Now uses `gsd-tools query skills-root` (which IS shipped) to resolve skills roots. (#3024) (#3195)
- **Pi no longer emits a `typebox unavailable` warning at every startup** — the warning fired because the Pi adapter attempts to `require('typebox')` (not a gsd-core dependency) and falls back to a plain JSON-Schema object on every startup. The fallback is the normal path; the warning is now suppressed. (#3022) (#3111)
- **`fish_add_path` no longer skips a directory whose name starts with a dash** — fish parses a leading-dash token as an option, so the suggested command silently added nothing; it now passes the end-of-options separator. Also fixes a `config.toml` written unparseable when a value carried a newline or NUL, an installer PATH hint that printed a header with nothing under it, and a reviewer lane that crashed instead of degrading when its conversation cache file held the literal `null`. (#3118) (#3124)
- **A halted plan no longer leaves its dependents on the runnable work list** — when a plan reaches a designed stop and its SUMMARY records `status: halted`, plans that depend on it (directly or transitively) are now reported as blocked, with the halted plan(s) named, instead of being offered to the executor as ordinary incomplete work. (#2830) (#3038)
- **Plan-phase now auto-recovers from a stalled planner or plan-checker spawn instead of hanging indefinitely** — when a planner/plan-checker subagent produces no completion marker and no fresh on-disk plan activity for a configurable threshold (`planner.stall_threshold_minutes`, default 10 minutes, checked every `planner.stall_detect_interval_minutes`, default 5), plan-phase now automatically surfaces the existing accept-plans/retry/stop recovery choice instead of waiting for a manual interrupt. Trade-off: a planner/plan-checker that finishes quickly is no longer detected instantly — completion is observed at most one `stall_detect_interval_minutes` (default 5 min) after it happens, in exchange for eliminating the previously-indefinite hang. (#2650)
**Hardened a repo-wide test-portability pattern (maintainer-authorized scope expansion): ten test files that extract a fenced bash block from a workflow `.md` file and execute it via `spawnSync`/`execFileSync` now normalize CRLF to LF at the point of reading the file**, before any fence-slicing or regex runs. A raw `readFileSync` followed by a bare `\n`-based regex against markdown fences is fragile by construction — it silently assumes LF regardless of how the bytes actually arrived — and this normalization removes that assumption at a single shared `readFileNormalized()` helper in `tests/helpers.cjs`, used by all ten call sites, so the next `.md`-extraction test is correct by default instead of needing to rediscover the fix independently. (Correction: this was NOT the cause of this PR's own `windows-latest` CI failure — `.gitattributes`' blanket `* text=auto eol=lf` means a Windows checkout of this repo never receives CRLF in the first place. That failure was a separate `bash -c` argv-transport defect in the #2650 test file itself, fixed alongside this.) (#3015)
- **`gsd-tools windows` no longer crashes on CRLF ledgers** — on repos with `core.autocrlf=true` (Windows default), the frontmatter parser threw on the last key of a CRLF `WINDOWS.md`, making the broken-windows status/waive/fixed subcommands unusable. (#3116) (#3137)
- **Installed third-party reviewer lanes can now be selected, planned, and invoked** — an installed `role:"reviewer"` capability was roster-visible and disclosed at install but `/gsd-review` (`gsd-tools review-lane sections|flags|plan|invoke`) built its lane map from the static first-party set only, so every third-party lane failed with "no such declared lane". The invocation surface now merges installed overlay reviewer lanes (first-party wins on collision, ADR-2782 D8). (#2927) (#3062)
- **Completing a phase no longer checks the box for a requirement the traceability table records as deferred or blocked** — the phase-completion write flipped the REQUIREMENTS.md checkbox unconditionally and kept the flip when the traceability row existed but rejected the same completion, so a requirement recorded as Deferred or Blocked read as shipped. The checkbox now rolls back when a row exists but rejects the write, matching the existing requirements mark-complete behavior so the two surfaces never silently disagree. (#3073)
- **Hotfix branches with auto cherry-pick no longer abort on already-applied commits** — cutting a hotfix from a tag whose `chore: sync next package version` commit applied empty (already present by content) aborted the entire create run. The cherry-pick error handler now distinguishes empty picks (no unmerged paths → skip) from genuine conflicts (unmerged paths → abort), and the job summary lists skipped-as-empty commits separately. (#2913) (#2970)
- **Installer `--help` now documents every supported runtime** — `--pi` and `--gemini` were accepted but omitted from the help output, making them invisible to users discovering runtime support via `--help`. A parity test now guards against future drift. (#3026) (#3112)
- Several guards that could not verify something previously reported the same result as everything is fine: a duplicate external job could dispatch past a corrupt sibling manifest, `state rebuild` could report success while phase-table reconciliation never ran, an unreadable lock body was treated as freely stealable at the same short window as a genuinely empty one, a staleness check that itself failed reported not stale, and `git base-branch` returned `main` whether it verified that or every git query timed out. These now fail closed instead of silently succeeding. (#3057) (#3088)
- **Updating GSD on Codex no longer deletes user settings from config.toml** — the config merge preserved content before the GSD marker block but discarded everything after it, so any model preference, MCP server, or profile added after a fresh install was wiped on every update. The merge now preserves genuine user TOML after the block by routing it through the existing section stripper, which removes only GSD-owned sections while keeping user tables, and #2406's leaked-section de-dup still holds. Re-merging is idempotent. (#3067)
- **`--kimi-code` reviewer lane is now selectable in `/gsd:review`** — the lane was declared, documented, and its flag resolved, but the review workflow's CLI detection and flag list omitted it (hardcoded to 11 of 12 lanes). Both now include Kimi CLI detection and the `--kimi-code` flag. (#3035) (#3115)
- **`query commit` no longer silently switches to a phase/milestone branch** — `git checkout -b` both created AND switched HEAD, resurrecting merged-and-deleted phase branches. Now uses `git branch` (create-only, no switch); the commit always lands on the current branch. Callers that want to be on the phase branch should use `execute-phase`'s branching step. (#3079) (#3141)
- **Package-legitimacy docs now match the registry-API gate** — `security-model.md`, `USER-GUIDE.md`, `ARCHITECTURE.md`, `COMMANDS.md`, `FEATURES.md`, and the planner's STRIDE template described the pre-ADR-0656 design (slopcheck as the install-or-degrade gate, unavailability degrading every package to [ASSUMED]). Docs now describe the actual registry-API verdict gate (npm/PyPI/crates.io), with slopcheck as an optional escalate-only adapter. The `ja-JP` mirror is fully aligned, and the mechanical portion of the same drift (command strings, table headers, and already-attested-term swaps) is corrected in the `zh-CN`, `ko-KR`, and `pt-BR` mirrors as well; the prose-composition remainder in those three locales is tracked separately in #3002. (#2775) (#3010)
- **`milestone complete` no longer silently disarms its unstarted-phase guard when STATE.md's `milestone:` field drifts** — the guard now runs whenever the ROADMAP can be scoped for the requested version (independent of STATE), and a STATE mismatch emits a WARNING naming both values instead of skipping the scan. (#2946) (#3081)
- **Trae IDE is now detected as its own runtime** — `/gsd-new-project` and `/gsd-ingest-docs` no longer fall through to the Claude default when run inside Trae, and a `--trae` install no longer writes a malformed `.claude/.trae/rules/` or `.trae/.trae/rules/` instruction-file path; it now resolves to the concrete `.trae/rules/rules.md`. (#2658) (#3006)
- **Project-local agents are detected across non-Claude runtimes** — GSD status and workflows now use a manifest-backed local installation before the global fallback. (#2623)
- **Malformed predicate declarations are now reported instead of silently dropped.** A doubled-dot id, a space in an id, a lowercase-leading class, and a value with an embedded CR/LF are each surfaced as a distinct `malformed` diagnostic reason instead of vanishing with no trace; the example parser (examples/dynamic-context-management/) was also brought back into parity with production and its own index is now drift-guarded by a new lint script. (#2944) (#2950)
- **Workflow shell blocks no longer abort under zsh when a glob matches nothing** — an unmatched glob inside a `for` word list aborted the entire shell block under zsh (macOS default shell), silently bypassing every statement after it, including the verify-phase decision-coverage gate. Each affected bash block now enables nullglob portably (`shopt -s nullglob 2>/dev/null; setopt NULL_GLOB 2>/dev/null`) so an unmatched glob expands to nothing and the loop is skipped cleanly under both shells. (#2962) (#3087)
- **`docs/json-errors.md` now documents the ExitError plain-text carve-out** — the page previously claimed every CLI error emits a structured JSON envelope on stderr, but usage errors (ExitError) intentionally emit plain text with their own exit code. The structured-envelope guidance is now scoped to non-usage failures, with the carve-out stated explicitly and a characterization test pinning both paths. (#2979) (#3093)
- **`review-lane` rejects an unknown subcommand instantly instead of after a dozen subprocess spawns** — an unrecognized subcommand fell through to the usage-error branch only after loading the capability registry and building a per-lane plan, which spawns one child process per lane. The error now fires before any of that work starts (~119ms instead of ~1288ms). (#3148) (#3192)
- **Worktree timeout guards now fire on Windows** — the checks that detect a timed-out git command required the process to report a SIGTERM signal, which Node does not guarantee on every platform, so on Windows they could silently never fire and the guard they protect would pass without having verified anything. The check is now a single shared predicate keyed on the timeout code alone. (#3050) (#3060)
- **`roadmap.analyze` now discovers non-numeric-leading phase ids** — the phase-heading and checklist discovery regexes required a digit-first id (e.g. `07`), so a project using letter-prefixed ids (e.g. `B7`) got `phase_count: 0` even though `get-phase`/`execute-phase` resolved the same ids fine. The regexes now accept an optional leading letter prefix. (#3036) (#3117)
- **`progress.completed_plans` no longer stays pinned after a gap-closure cycle** — when plan-phase re-planned a phase and added gap-closure plans, `total_plans` corrected upward but `completed_plans` was restored to its pre-growth value, so STATE.md showed `completed_plans < total_plans` permanently even after every plan (including the gap-closure ones) was summarized. `completed_plans` and `completed_phases` now ratchet up to the disk-derived count under the plan-phase progress opt-in (never deriving downward, preserving the curated-progress ratchet for unrelated edits). (#2969) (#3091)
- **`/gsd-audit-uat` now sees archived phases and table-shaped artifacts** — three silent false negatives are fixed: (1) the audit scanned only `.planning/phases/`, so a project whose milestones had been archived to `.planning/milestones/<version>-phases/` silently omitted those phases, and one with ALL phases archived hard-errored with "No phases directory found" instead of reporting its outstanding items; (2) a `deferred-items.md` recording entries as a GFM table yielded zero items; (3) a table-shaped `## Gaps` section likewise yielded zero items. Results now carry `archived_milestone` so consumers can label provenance. Same false-negative family as #2286/#2287, one document shape further out. (#2766) (#3082)
- **A Codex surface re-stage no longer creates a duplicate skill tree** — re-staging skills on a global Codex install wrote them to `$CODEX_HOME/skills` while the installer had correctly placed them in `$HOME/.agents/skills`, leaving two active GSD skill trees and no signal which one was live. The re-stage and the legacy dev-preferences migration now resolve the same destination the installer uses. (#2911) (#3049)
- **/gsd-verify-work diagnosis and interactive plan execution no longer halt on a stale worktree fork base** — when worktrees are enabled and local HEAD has advanced past `origin/HEAD` (the GSD steady state of committing every step and pushing only on request), the spawned debug/executor agent used to fork from the stale ref and hit a base-mismatch fatal mid-investigation with no recovery. Both dispatch sites now run the same pre-dispatch `worktree.base-check` gate the executor and quick-task paths already run, auto-degrading to sequential main-tree dispatch with an explanatory message. (#2649) (#2955)
- **Phases no longer leak archived data from another workstream** — resolving a phase in one workstream whose own directory doesn't exist yet no longer falls back to an unrelated workstream's (or a flat-mode project's) same-numbered archived phase; it correctly resolves as pending. (#2855) (#3008)
- **`npm test` no longer writes into the developer's live config directory** — `TEST_ENV_BASE` scrubbed 14 session-identity vars but omitted `CLAUDE_CONFIG_DIR`, `GSD_RUNTIME`, and `CODEX_HOME` (config-location vars that decide WHERE a child writes). The config-home resolver consults these before `HOME`, so an ambient value won unconditionally over a sandboxed `HOME`. All three are now blanked. (#2665) (#3134)
- **Multi-paragraph changeset bodies no longer truncate and lose their PR trailer** — `serializeChangelog` wrote bullet bodies verbatim, so an embedded newline became a column-0 line that `parseChangelog` treated as the end of the bullet, silently dropping the continuation and the `(#NNNN)` trailer. Continuation lines are now indented so the round-trip preserves content and attribution. (#3001) (#3101)
- **Cross-AI reviewer lanes no longer silently drop on Windows** — `deps.spawn` used `shell: false` with a bare binary name, which fails with ENOENT on Windows .cmd shims (npm-installed CLIs). Now applies the #2667 `cmd.exe /d /s /c` shim gate. Spawn errors (ENOENT, ETIMEDOUT) are also surfaced in the reviewer err file instead of being silently dropped. (#3086) (#3142)
- **A phase stranded between its last plan and verification can now be recovered** — if every plan carried a SUMMARY but the run never reached the verify step (most often because a checkpoint plan was retired yet still summarized), re-running execute-phase exited immediately and could never produce the missing VERIFICATION.md, so the recommended recovery command silently did nothing. It now resumes at the phase gates instead, with the code-review and regression gates still running. (#2868) (#3041)
- **Planning artifacts whose frontmatter is preceded by a UTF-8 byte-order mark no longer lose all their frontmatter fields** — the frontmatter parser's fence check required the opening dashes at byte zero, so a BOM written by Windows PowerShell or several editors made every field silently disappear. A leading BOM is now stripped before the check, so the fields parse identically to the no-BOM case. The no-frontmatter and thematic-break cases stay silent and empty as before. (#3076)
- **GSD-2 import no longer duplicates frontmatter in the generated SUMMARY.md** — importing a GSD-2 project whose task summaries were authored with CRLF line endings emitted the original GSD-2 frontmatter a second time, as body text, below the new one. Stripping now goes through the canonical line-ending-tolerant parser. (#2703) (#3027)
- **Codex skill adapter collaboration-tool vocabulary corrected** — the generated adapter documented an obsolete `wait(ids)` call (the real tool is `collaboration.wait_agent`), unconditionally instructed `close_agent` without a tool-visibility gate, and omitted the required `task_name` field and the `fork_turns` parameter. The adapter now names the real wait tool, disambiguates it from the unrelated exec-cell `functions.wait`, gates `close_agent` on schema visibility, and covers `task_name` + `fork_turns`. (#3004) (#3104)
- **Documentation now shows the command form that actually works** — reader-facing docs instructed users to type `/gsd:<command>`, a form no runtime registers, so copying it produced an unrecognized command. All 178 occurrences across 53 files, including the Japanese, Korean, Portuguese and Chinese mirrors, now use `/gsd-<command>`. A new lint keeps it from drifting back, while leaving the colon form intact where it is load-bearing — source artifacts, where install-time converters key on it — and preserving the genuine `/gsd-core:<command>` plugin namespace. (#2903) (#3047)
- **The composer's load-bearing-fragment guarantee is now enforced, not just documented** — ADR-1671 promised a deterministic gate proving no load-bearing content is dropped or shrunk when context is trimmed to fit a budget; only synthetic unit tests existed. The gate now runs against real declared strategies and fails if it would ever assert over nothing. (#3065) (#3068)
- **phase.complete no longer closes a phase while its plans are silently unexecuted** — a phase could previously close "complete" with an arbitrary number of plans missing a completion record (a confirmed incident closed a phase with 6/30 plans unexecuted, including its entire final scope). phase.complete now refuses, naming the unexecuted plans, unless they are explicitly retired via `status: superseded` frontmatter. (#2648) (#2953)
- **A worktree whose owner could not be probed is no longer deleted** — an orphan lock holding a process id above 2147483647 made the liveness check throw a type error rather than an errno error, which read as "owner is dead" and removed the worktree. Only "no such process" now means dead; every unrecognized outcome leaves the worktree alone. An unreadable lock timestamp also reported "too fresh", advising a wait that could never help, and now reports its own reason. (#3103) (#3106)
- **Spec-phase edge resolution vocabulary realigned to the code's `Status` enum** — the workflow prose in spec-phase.md, plan-phase.md, and ui-phase.md used the retired `covered`/`backstop`-as-status vocabulary that `validateResolution` rejects. Now uses `resolved` + `verification: explicit|backstop`. (#3132) (#3138)
- **Gate predicate `artifact-frontmatter-equals` is now implemented** — declared gates that use it are evaluated instead of erroring on an unrecognized kind. (#2785) (#2816)
- **`/gsd` commands in Pi now display their output** — the command handler returned output as a bare string, which Pi's ExtensionAPI silently dropped. It now returns Pi's structured `{ content: [{ type: 'text', text }] }` display shape (matching the `gsd_invoke` tool's proven contract), so success output and error messages are visible. (#2991) (#3097)
- **gsd-code-fixer no longer creates its review-fix worktree outside the project tree on Windows** — the worktree was hardcoded to a `/tmp/sv-...` mktemp path, which on Git Bash landed outside the repository (every file read inside it prompted for permission) and produced an un-removable short path. The worktree now lives repo-relative under `.claude/worktrees/`, the same location the executor worktrees use. (#2647) (#2942)
- **`/gsd-code-review` no longer picks a wrong diff base from unanchored commit-message grep** — the diff-base fallback searched all commit messages for the bare phase number as a substring, matching version strings, dates, and issue refs, then took the oldest match. The grep is now anchored to the phase-mention convention (`Phase N` with a word boundary), so the fail-closed branch is reachable when no commit genuinely references the phase. (#2989) (#3096)
- **`roadmap.analyze` no longer silently drops phases when the phase-listing heading isn't version-bearing** — if the phase list lives under a plain `## Phases` heading (the shipped greenfield template's own shape) and a later version-bearing progress/notes heading exists, the milestone scope previously latched onto the later heading and stripped every `### Phase N:` detail from the preamble, returning `phase_count: 0` with exit 0 and empty stderr. Phase details in the preamble are now preserved when the selected milestone section has none of its own. (#2947) (#3084)
- **`execGit` now reports `timedOut` on every result, and its return type is no longer misdeclared** — three modules hand-copied the shape of `execGit`'s result because the canonical type was not exported, and two of those copies declared `exitCode` as nullable when it can never be null. The shape is now declared once and reused, so a consumer can no longer be written against a contract the function does not honor. (#3071) (#3077)
- **Heavy workflow skills no longer fail on Claude with thinking disabled** — `effort: max` in plan-phase, execute-phase, and autonomous SKILL.md frontmatter was rejected by the Anthropic API (`400: effort 'max' is not supported when thinking is disabled`). The installer now clamps `max`/`xhigh` to `high` for Claude-runtime skills, the maximum value that works in both thinking states on all supported models. (#3039) (#3119)
- **`state.*` writes no longer flip the milestone or rewrite progress with whole-project counts** — when the stored milestone had no matching non-shipped ROADMAP heading, `buildStateFrontmatter` auto-derived a confidently-wrong milestone and clobbered the stored value + progress on every write. The disk scan now scopes to the STORED milestone explicitly, so a state write that doesn't change progress leaves the milestone and progress block untouched. (#3017) (#3105)
- **Project configs no longer inherit `runtime` from the machine-wide `~/.gsd/defaults.json`** — on machines with 2+ runtimes installed (e.g. Codex + Claude Code), the last installer's `runtime` value poisoned every new project, resolving agents to wrong model IDs. The key is now excluded from the defaults spread. (#2840) (#2985)
- **Nine compiled `.cjs` runtime artifacts under `gsd-core/bin/lib/` are no longer tracked in git** — they are ADR-457 build outputs of `src/*.cts` sources and were missing from `.gitignore`, letting the committed bytes silently drift from source (as happened to `api-coverage.cjs` in #2653). They now build fresh from source like their ~160 already-gitignored siblings. (#2657) (#3011)
- **Secret-scan no longer reports a false positive on the zh-CN verification-patterns translation** — the translated document carries the same illustrative placeholder examples as its English source, but the exclusion was never extended to the translation. The strict-mode scan now passes. (#3044) (#3122)
- **`state planned-phase` no longer overwrites authoritative `last_activity_desc`** — when the frontmatter and body had the same activity date but different descriptions, the write path preserved the date but overwrote the frontmatter's description with stale body prose. Same-date frontmatter desc is now preserved. (#3052) (#3140)
- **Claude Code plugin installs no longer silently disable all hooks** — the plugin manifest (`.claude-plugin/plugin.json`) explicitly declared `hooks/hooks.json`, which Claude Code also auto-loads by default, causing a duplicate-declaration rejection that silently disabled every hook (security guards, monitors, injection scanners). The redundant declaration is removed; Claude Code's auto-load path handles it. (#3029) (#3113)
- **`scripts/lint-compiled-artifact-sync.cjs` no longer fails on containerized checkouts owned by a different uid** — its internal `git` calls now scope `safe.directory` to the repo root per-invocation, so the guard runs instead of erroring with "detected dubious ownership" in any CI lane where the checkout owner differs from the running user. (#2657) (#3011)
- **`roadmap validate` now performs real structural validation** — it previously returned `{"warnings":[]}` (exit 0) for every input including empty files, garbage text, and missing files, providing false assurance. It now checks file existence/readability, emptiness, frontmatter well-formedness, and the presence of at least one phase entry, exiting non-zero on any warning (per its documented contract). The existing opt-in milestone-prefix consistency check is preserved. (#2978) (#3092)
- **Codex local capability metadata now matches project-scoped installs** — Remove the inert user-home override from the local skills descriptor, document the global/local skill roots, and reject user-home overrides across all local artifact-layout entries. (#2777) (#2831)
- **Documentation now consistently warns about `--dangerously-skip-permissions`** — the flag was presented without a caveat in the user guide, the onboarding tutorial, and all four translated locales (ja-JP, zh-CN, ko-KR, pt-BR), while the English first-project tutorial carried a proper caution. All occurrences now carry the same `[!CAUTION]` block. (#3043) (#3121)
- **`phase remove` now reports accurate state_updated and keeps STATE.md progress counters in sync** — the command reported `state_updated: true` based on file existence (always true) rather than actual content change, and the frontmatter `progress.total_phases`/`completed_phases`/`percent` counters went stale when the STATE.md body lacked a `Total Phases:` field (the no-op write guard skipped the frontmatter resync). (#2640) (#2974)
- **Unusable `last_activity` now emits a diagnostic** — a present-but-unparseable `last_activity` in STATE.md silently suppressed the idle-stranded recommendation. The fallback (`stale_activity: false`) stays for continuity, but a `last_activity_unparseable` warning is now emitted so the degradation is visible. (#3099) (#3139)
- **Statusline now shows GSD state in workstream mode** — the GSD-state segment used to silently disappear in workstream-mode projects with no root STATE.md, even with an active workstream selected; it now resolves the active workstream (env var or stored pointer) and shows its milestone/phase/progress, or an explicit "no active workstream" message when nothing resolves. (#2850) (#3012)
- **Published installs no longer crash on a script that can't load** — `scripts/gen-emitted-baseline.cjs` shipped in the npm tarball but required three modules from `tests/` (which does not ship), producing `MODULE_NOT_FOUND` at load time. The script is repo-only CI tooling and is now excluded from the tarball. A class-extinction guard test ensures no shipped script can require outside the shipped tree going forward. (#2858) (#2968)
- **A `--kimi-code` install now configures hooks in Kimi Code, not Kimi CLI** — installing GSD for Kimi Code wrote its lifecycle hooks, hook bundle and CommonJS marker into Kimi CLI's `~/.kimi/config.toml`, so Kimi Code itself received no hooks at all and a machine with only Kimi Code got a config file no product reads. Each Kimi product now uses its own root and its own environment override (`KIMI_SHARE_DIR` for Kimi CLI, `KIMI_CODE_HOME` for Kimi Code), and uninstalling one no longer removes the other's hooks. (#2755) (#3032)
- **Contributor PRs stop conflicting on a file they never meaningfully changed** — the emitted-drift acknowledgment moves from one shared `tests/emitted-drift-ack.json` every PR rewrote wholesale to per-PR fragments under `tests/emitted-drift-acks/`, so two PRs needing an acknowledgment can no longer collide with each other; the legacy file's 35 spent entries are migrated (not deleted) into a fragment so nothing is lost, and a next-only push guard now fails if the legacy shared file itself ever reappears, since every entry is scoped to the diff that introduced it and is spent the moment it merges. (#2914) (#2923)
- **Slug no longer ends with a trailing hyphen when truncated** — long titles whose 60-character cut landed on a word separator produced a slug ending in `-`, which then leaked into phase directory and branch names. The trailing-hyphen strip now runs after truncation. (#2849) (#2967)
- **MemPalace capture no longer silently disables itself when `capture_artifacts` is unset** — the skill gate used `!== true` (treating absent as disabled), but the capability schema defaults to enabled. Fixed to `=== false` (disabled only on explicit false). (#2641) (#2982)
- **Completing the last phase of a milestone no longer advances into a 0.x backlog sentinel row** — the phase-completion cascade's lowest-outstanding-phase override had no sentinel filter, so an unchecked backlog row like Phase 0.1 sorted below every real phase and was selected as the next phase, corrupting STATE.md and desyncing the current phase number from its name. The override now excludes sentinel-range phase ids via the existing isSentinelPhaseId predicate, so a real lower-numbered outstanding phase is still selected while backlog sentinels are skipped and the milestone completes cleanly. (#3070)
- **Research agents no longer call a context7 tool that doesn't exist** — four shipped docs instructed agents to call `mcp__context7__get-library-docs`, a tool the context7 MCP server does not register (it exposes only `resolve-library-id` and `query-docs`). Every research workflow that loaded the canonical doc-lookup reference either errored, fell back to the `ctx7` CLI, or fabricated a result. All sites now name `query-docs` with the registered `libraryId`/`query` params, the CLI-fallback rationale now describes the real project-scoped `.mcp.json` mechanism, and a parity guard fails the build if the banned name returns. (#2943) (#2963)
- **Workflow-backend worktree branches (`worktree-wf_*`) are now recognized by all worktree guards** — the Claude-orchestration Workflow backend created worktrees on branches none of the four guards recognized, causing the path-containment hook to fail open and the cleanup/executor commands to reject or silently drop entries. All four sites now accept the `worktree-wf_` namespace alongside `agent-*` / `worktree-agent-*`. (#3021) (#3109)
- **`phase_id_convention` set in `.planning/config.json` is no longer silently dropped** — the config loader's resolved-config constructor omitted the key despite it being in the valid-keys manifest, so the milestone-prefix validation check could only be activated via the ROADMAP frontmatter fallback. The key now survives resolution. (#2997) (#3098)
- **Agent-skills warnings now suggest the `global:` prefix when a bare name matches a global skill** — configuring a skill by bare name (e.g. `patch-coverage-check`) that exists as a global skill was silently skipped with no hint that the fix is `global:patch-coverage-check`. The skip warning now appends a hint when the bare name matches an existing global skill. (#2941) (#2973)
- **`/gsd-spike` no longer blends unrelated ideas' requirements together** — `.planning/spikes/MANIFEST.md` now scopes each idea's paragraph and Requirements under its own idea key, and `/gsd-spike --wrap-up` only emits a feature area's owning idea's requirements instead of the whole file. (#1700) (#3014)
- **`worktree cleanup-wave` no longer aborts the rest of a wave when one entry is blocked** — a blocked entry (mismatched branch/base, a deletion, a dirty worktree, or a failed merge/removal) now stays blocked with its existing reason code, while every other independently-clean entry in the wave still merges and is removed instead of being stranded unattempted. (#2852) (#3009)
- **Local `lint:changeset` and `lint:docs-required` now diff against `next` instead of `main`** — the local fallback was the release branch (`main`), which lags far behind the integration branch (`next`), so the lint always passed by finding fragments from other already-merged PRs in the oversized diff range. The local invocation now matches the base CI uses. (#2988) (#3095)
- **Non-Latin phase and milestone titles no longer produce empty slugs** — a Cyrillic title used to reduce to an empty slug, creating unnamed phase directories (bare numeric prefix like `01-`) and empty `milestone_slug` fields. Titles are now transliterated to ASCII before the slug filter, so a non-Latin title yields a usable slug. Latin-script output is unchanged. (#2848) (#2934)
- **`graphify` version detection now verifies tool identity** — a foreign binary named `graphify` on PATH that printed a plausible version string would silently report `compatible: true` with no warning. The check now confirms the `graphifyy` Python package via `importlib.metadata` before trusting the version, emitting a clear warning naming the mismatch when identity cannot be confirmed. (#3020) (#3107)
### Security
- **Prompt-injection scan no longer misses single-quoted `eval()`/`exec()` payloads on macOS, and no longer flags ordinary prose** — the patterns used a GNU-grep-only `\\x27` escape that BSD/macOS grep read as four literal characters, so single-quoted code-execution payloads went undetected there while passing on CI; separately, several patterns lacked a left word boundary and matched inside ordinary words (`fact as a`, `retrieval(`, `Jordan mode`). (#3023) (#3175)
- **Production dependency tree is clear of known advisories** — three transitive packages reached by `@anthropic-ai/claude-agent-sdk` carried published advisories: `fast-uri` (host confusion via a backslash authority introducer), `ip-address` (three SSRF / trust-boundary bypasses via leading-zero octets, CIDR-suffix suppression, and IPv4-mapped address misclassification), and `hono`. All three are lockfile-only, semver-in-range updates. (#2755) (#3032)
- **A directory name containing `$(…)` or a backtick no longer becomes a live command in your shell startup file** — the PATH-persistence suggestion escaped its `export PATH="…"` line for the `echo` that carries it, not for the rc file it lands in, so a substitution in the target directory survived into `~/.bashrc` and ran on every new shell. (#3118) (#3124)
## [1.9.1] - 2026-07-31
### Added