diff --git a/.changeset/1367-claude-local-flat-command-layout.md b/.changeset/1367-claude-local-flat-command-layout.md deleted file mode 100644 index 2f52fa98e..000000000 --- a/.changeset/1367-claude-local-flat-command-layout.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -type: Fixed -pr: 1367 ---- -**Project-local Claude Code install now produces `/gsd-` (hyphen) slash commands** — the installer was writing command files to `.claude/commands/gsd/.md` (subdirectory with bare names), causing Claude Code to namespace them as `/gsd:` (colon form). The fix writes flat `gsd-.md` files at `.claude/commands/` level so Claude Code registers `/gsd-` (hyphen form), matching hooks, statusline, and all cross-command references. Legacy `commands/gsd/` directories from prior installs are cleaned up on reinstall and uninstall, with `dev-preferences.md` preserved. (#1367) diff --git a/.changeset/1369-wave-stale-base-recheck.md b/.changeset/1369-wave-stale-base-recheck.md deleted file mode 100644 index 53b5ef200..000000000 --- a/.changeset/1369-wave-stale-base-recheck.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -type: Fixed -pr: 1369 ---- -**`execute-phase` now 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, the `worktree.base-check` auto-degrade only ran once at initialize time; after Wave N merged and tracking commits advanced orchestrator HEAD past `origin/HEAD`, Wave N+1 worktrees were still forked from `origin/HEAD` (Claude Code's "fresh" base), causing both agents to immediately halt with `FATAL: worktree base mismatch` from the `worktree_branch_check` guard. Second, `WAVE_WORKTREE_MANIFEST` was 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 the `execute_waves` "For each wave" loop re-runs `worktree.base-check` before every wave's dispatch (when divergence is detected, `USE_WORKTREES` is overridden to `false` for that wave); step 7c between waves unsets `WAVE_WORKTREE_MANIFEST` so wave N+1 creates a fresh per-wave manifest, and re-asserts `worktree.baseRef:"head"` (idempotent) so the Claude Code harness re-reads the live HEAD on the next dispatch. The permanent fix remains setting `worktree.baseRef:"head"` in `.claude/settings.local.json` (see #683). diff --git a/.changeset/1452-context-guard-mode.md b/.changeset/1452-context-guard-mode.md deleted file mode 100644 index aacfa6713..000000000 --- a/.changeset/1452-context-guard-mode.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -type: Added -pr: 1452 ---- -**`workflow.context_guard_mode` config key** — proactive context-exhaustion guard for `execute-phase`. Before each wave, the orchestrator self-assesses context pressure using the degradation signals defined in `context-budget.md`. Values: `warn` (default — emit warning and recommend `/gsd:pause-work` when POOR tier detected), `auto` (automatically invoke `/gsd:pause-work` before next wave), `off` (disable). Set via `gsd config-set workflow.context_guard_mode auto` for fully autonomous checkpoint behaviour. (#1452) diff --git a/.changeset/1520-mktemp-suffix-randomization.md b/.changeset/1520-mktemp-suffix-randomization.md deleted file mode 100644 index d3137c715..000000000 --- a/.changeset/1520-mktemp-suffix-randomization.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -type: Fixed -pr: 1550 ---- -**Workflow temp files now randomize correctly on BSD/macOS** — several workflows called `mktemp` with templates where `XXXXXX` was followed by a `.json`/`.md` suffix (e.g. `gsd-worktree-wave-XXXXXX.json`, `gsd-pr-body.XXXXXX.md`). BSD/macOS `mktemp` only substitutes `XXXXXX` when 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) diff --git a/.changeset/1532-core-lock-liveness.md b/.changeset/1532-core-lock-liveness.md deleted file mode 100644 index a42f81bf0..000000000 --- a/.changeset/1532-core-lock-liveness.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -type: Fixed -pr: 1532 ---- -**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 bare `mtime` timer 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 on `process.kill(pid,0)` liveness with a deadman ceiling above the wait budget (pid-reuse backstop), `withPlanningLock` no longer force-steals a live holder on timeout (and can no longer leak an uncaught `EEXIST`), `writeStateMd` computes its disk scan inside the lock, and `acquireStateLock` no 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. diff --git a/.changeset/1619-mise-shim-node-path.md b/.changeset/1619-mise-shim-node-path.md deleted file mode 100644 index ccba5b8cc..000000000 --- a/.changeset/1619-mise-shim-node-path.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -type: Fixed -pr: 1621 ---- -**`normalizeNodePath` now maps pruned mise node paths to the stable shim (#1619)** — `resolveNodeRunner()` bakes `process.execPath` into managed `.js` hook commands. Node realpaths execPath, so under mise it resolves to `/installs/node//bin/node` — a concrete version mise prunes on `mise up`, after which every managed hook fails to spawn (`No such file or directory` on every SessionStart and tool event), the same ephemeral-path failure #977 fixed for fnm and #3181 for Homebrew. `normalizeNodePath` now rewrites a mise versioned install path to the stable sibling shim `/shims/node` (`.exe` preserved on Windows) when that shim exists, deriving `` from execPath so a custom `MISE_DATA_DIR` works, and falling back to the raw execPath unchanged otherwise. (#1619) diff --git a/.changeset/297bb145.md b/.changeset/297bb145.md deleted file mode 100644 index 8f75ef990..000000000 --- a/.changeset/297bb145.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -type: Fixed -pr: 1483 ---- - -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(). - - diff --git a/.changeset/agile-koalas-tumble.md b/.changeset/agile-koalas-tumble.md deleted file mode 100644 index c9904dae5..000000000 --- a/.changeset/agile-koalas-tumble.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -type: Fixed -pr: 1656 ---- -**`frontmatter set` / `frontmatter merge` no longer destroy `must_haves` object-lists** — changing one frontmatter field (e.g. `wave`) silently dropped every `provides:` value and collapsed `must_haves.artifacts`/`.prohibitions` from 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 unrelated `must_haves` blocks survive verbatim. (#1572) diff --git a/.changeset/bold-bears-wave.md b/.changeset/bold-bears-wave.md deleted file mode 100644 index 8538f7570..000000000 --- a/.changeset/bold-bears-wave.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -type: Fixed -pr: 1537 ---- -**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.json` previously resolved `runtime=claude` and enabled git worktree isolation, which only Claude Code's `isolation="worktree"` can honor — risking main-checkout edits while the workflow believed agents were isolated. Every non-Claude install now resolves its own runtime identity, defaults `workflow.use_worktrees` to `false`, 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) diff --git a/.changeset/capability-routers-via-hub.md b/.changeset/capability-routers-via-hub.md deleted file mode 100644 index 35987dee1..000000000 --- a/.changeset/capability-routers-via-hub.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -type: Changed -pr: 1647 ---- -**Capability commands now emit dispatch audit records** — `graphify`, `intel`, `audit-uat`, and `audit-open` now route through the Command Routing Hub per ADR-959 §III(B), so `GSD_AUDIT=1` traces, the structured stderr JSON error envelope, and the typed Result contract cover them uniformly with all other command families. JSON-error `reason` values (`usage`, `sdk_unknown_command`) are preserved byte-identical. (#1646) diff --git a/.changeset/clever-orcas-sprint.md b/.changeset/clever-orcas-sprint.md deleted file mode 100644 index 1ed82cd08..000000000 --- a/.changeset/clever-orcas-sprint.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -type: Changed -pr: 1611 ---- -**`/gsd-verify-work` now routes UAT deterministically from a structured `coverage:` block on SUMMARY.md** — deliverables proven by passing tests (`human_judgment: false` with a non-empty all-`pass` `verification` list) are auto-passed (`source: automated`, no prompt), and only judgment-dependent or unverified deliverables are presented for human sign-off. SUMMARYs without a `coverage:` block fall back to the previous prose-based extraction, byte-identical. Authored by `execute-plan` and validated by the new `gsd-tools uat classify-coverage` verb. diff --git a/.changeset/clever-quails-snooze.md b/.changeset/clever-quails-snooze.md deleted file mode 100644 index ca43a8956..000000000 --- a/.changeset/clever-quails-snooze.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -type: Fixed -pr: 1456 ---- -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. diff --git a/.changeset/clever-seals-rest.md b/.changeset/clever-seals-rest.md deleted file mode 100644 index 002301d9e..000000000 --- a/.changeset/clever-seals-rest.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -type: Changed -pr: 1438 ---- -**Thread `isGlobal` install scope through the descriptor-driven `convertedAgentsKind` / `stageAgentsForRuntimeWithConverter` plumbing** — a prerequisite for the ADR-1235 agent-conversion cutover. No runtime declares a converted `agents` kind yet; the `capability.json` wiring is deferred to a follow-up that first ships the ADR-1235 §0 byte-for-byte parity harness (so the `/gsd:surface` / `--materialize` consumer can mirror the legacy agent pipeline before the kind goes live). The legacy `bin/install.js` agent loop remains authoritative, so installed agent output is unchanged. (#1173) - - diff --git a/.changeset/curious-eagles-purr.md b/.changeset/curious-eagles-purr.md deleted file mode 100644 index d0bb8852c..000000000 --- a/.changeset/curious-eagles-purr.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -type: Fixed -pr: 1500 ---- -**`workflow.mvp_mode` now accepted by `config-set`; three undocumented workflow keys added to references** — `workflow.mvp_mode`, `workflow.code_review_command`, and `workflow.plan_chunked` were consumed by planning-pipeline code but could not be set via `config-set` (they were missing from `VALID_CONFIG_KEYS`) or discovered via reference docs. All three are now in the schema and documented in `references/planning-config.md`. (#1500) diff --git a/.changeset/daring-foxes-greet.md b/.changeset/daring-foxes-greet.md deleted file mode 100644 index b235dc1f9..000000000 --- a/.changeset/daring-foxes-greet.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -type: Fixed -pr: 1631 ---- -**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. diff --git a/.changeset/daring-lemurs-rally.md b/.changeset/daring-lemurs-rally.md deleted file mode 100644 index 6d276cae4..000000000 --- a/.changeset/daring-lemurs-rally.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -type: Fixed -pr: 1536 ---- -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. diff --git a/.changeset/daring-ravens-wake.md b/.changeset/daring-ravens-wake.md deleted file mode 100644 index 556ce613b..000000000 --- a/.changeset/daring-ravens-wake.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -type: Changed -pr: 1421 ---- -**`/gsd-review` now asks external reviewers to verify plan claims against the source** — the reviewer prompt requires opening the referenced files, citing `file:line` evidence + 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) diff --git a/.changeset/eager-mice-cheer.md b/.changeset/eager-mice-cheer.md deleted file mode 100644 index 70b7afde0..000000000 --- a/.changeset/eager-mice-cheer.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -type: Fixed -pr: 1534 ---- -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. diff --git a/.changeset/eager-moles-wake.md b/.changeset/eager-moles-wake.md deleted file mode 100644 index 6b2623de4..000000000 --- a/.changeset/eager-moles-wake.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -type: Changed -pr: 1583 ---- -**eval-auditor scoring moved into a deterministic `eval.score` query 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. diff --git a/.changeset/eager-wolves-run.md b/.changeset/eager-wolves-run.md deleted file mode 100644 index 50a4616d3..000000000 --- a/.changeset/eager-wolves-run.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -type: Fixed -pr: 1418 ---- -**All GSD agents load on Gemini again** — the Claude `Skill`/`SlashCommand` tools were converted to an invalid `skill` tool that Gemini rejects, aborting the load of 22 of 34 agents. They are now excluded from the Gemini and Gemini-backed Antigravity agent `tools:` frontmatter, the same way `AskUserQuestion` already is. (#1394) diff --git a/.changeset/feat-1416-resolution-convention.md b/.changeset/feat-1416-resolution-convention.md deleted file mode 100644 index 439e9e4f4..000000000 --- a/.changeset/feat-1416-resolution-convention.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -type: Added -pr: 1425 ---- -**`agent-skills --json` IR gains an additive `value: { block, skills_count }` field** formalizing the `Resolution` convention for config-interpreting read verbs; no breaking change. The new `src/resolution.cts` module exports `Resolution { value, configured, reason, warnings }` (the canonical envelope) and `makeResolution()` (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 is `warnings: string[]`; a single generic across read+write verbs was rejected by the deletion test (ADR-1411 P3 amendment). (Part of #1411, P3 / #1416.) diff --git a/.changeset/feat-1463-capability-outdated.md b/.changeset/feat-1463-capability-outdated.md deleted file mode 100644 index 6b4249349..000000000 --- a/.changeset/feat-1463-capability-outdated.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -type: Added -pr: 1488 ---- -**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: git `ls-remote --tags`, npm `view … version`, local re-read; tarball → `manual`, registry → `unknown`). A capability is reported `outdated` only 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 each `npm view` line'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 reported `pinned` — never `outdated`, since `update` will not move it. A bare git ref (`#`) is classified at the remote with a bounded `git ls-remote`: a ref that resolves to a tag is `pinned`, while a **mutable branch** ref is never `pinned` (it degrades to `unknown`, since the installed commit is not recorded to compare against). Each capability is classified `outdated` / `current` / `pinned` / `manual` / `unknown`; subprocesses are bounded (git ≤30s, npm ≤60s) and a failing or unsupported peek degrades that row to `unknown` instead of crashing the command. `--json` emits the records array; the default prints a table. (#1463) diff --git a/.changeset/fierce-quails-wave.md b/.changeset/fierce-quails-wave.md deleted file mode 100644 index 6d3cec582..000000000 --- a/.changeset/fierce-quails-wave.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -type: Fixed -pr: 1442 ---- -**Antigravity config-dir resolution no longer shadows the active runtime** — when more than one of `~/.gemini/antigravity`, `antigravity-ide`, or `antigravity-cli` exists, GSD now resolves to the directory it actually installed into (marked by `gsd-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). diff --git a/.changeset/fix-1414-project-root-walkup.md b/.changeset/fix-1414-project-root-walkup.md deleted file mode 100644 index 8be12617d..000000000 --- a/.changeset/fix-1414-project-root-walkup.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -type: Changed -pr: 1423 ---- -**`gsd-tools` now resolves the project root from a descendant subdirectory** — `findProjectRoot` walks 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) - - - diff --git a/.changeset/fix-1415-loadconfig-provenance.md b/.changeset/fix-1415-loadconfig-provenance.md deleted file mode 100644 index c5ce74b4f..000000000 --- a/.changeset/fix-1415-loadconfig-provenance.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -type: Fixed -pr: 1424 ---- -**`gsd-tools query agent-skills` no longer silently drops a configured agent's skills under cwd or workstream drift** — `cmdAgentSkills` now anchors to the project root via `findProjectRoot` before loading config, so invoking it from a descendant subdirectory or with a `GSD_WORKSTREAM` that has no scoped config correctly resolves the configured `agent_skills` block. A new `loadConfigResolved(cwd, options) → { config, source, degraded }` function reports provenance alongside the config object: `source` distinguishes `'root' | 'workstream' | 'builtin-defaults' | 'global-defaults'`; `degraded:true` signals a workstream was requested but its config.json was absent. The `--json` IR of `agent-skills` gains four new fields — `configured` (bool), `reason` (`'resolved' | 'not_configured' | 'configured_empty' | 'configured_unresolved'`), `source`, and `degraded` — making silent failures visible and testable. A `configured_empty` or `configured_unresolved` agent emits a `stderr WARNING`; an unconfigured agent stays silent. (Closes #1366. Part of #1411, P2 / #1415.) diff --git a/.changeset/fix-1422-1447-projectroot-milestone-guards.md b/.changeset/fix-1422-1447-projectroot-milestone-guards.md deleted file mode 100644 index a2c23456a..000000000 --- a/.changeset/fix-1422-1447-projectroot-milestone-guards.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -type: Fixed -pr: 1484 ---- -**`findProjectRoot` now respects explicit `sub_repos` config over implicit `.git`** — when a parent workspace's `.planning/config.json` lists a child directory in `sub_repos`, that declaration takes precedence over the child's own `.git/` directory. Previously, if the child had both `.planning/` and `.git/`, the `.git` heuristic fired first and resolved to the child rather than the parent workspace, making the `sub_repos` declaration 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) - - diff --git a/.changeset/fix-1445-1446-progress-backlog-exclusion-and-ratchet.md b/.changeset/fix-1445-1446-progress-backlog-exclusion-and-ratchet.md deleted file mode 100644 index 9f4d8f62d..000000000 --- a/.changeset/fix-1445-1446-progress-backlog-exclusion-and-ratchet.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -type: Fixed -pr: 1490 ---- - -**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. diff --git a/.changeset/fix-1459-trust-model.md b/.changeset/fix-1459-trust-model.md deleted file mode 100644 index 6dff8d0df..000000000 --- a/.changeset/fix-1459-trust-model.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -type: Fixed -pr: 1473 ---- -**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 module `src/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` — a `sha512` over *every* regular file in the bundle, manifest AND artifacts AND identity, symlinks and non-regular entries rejected, bounded), **not** the repo-plantable ledger `integrity` (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 swap `capability.json` for 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 list` reports `status: inactive` with 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, and `trust revoke`, so an install from a sub-directory is not immediately inactive. The disclosure signature now also covers each MCP server's `transport`/`url`/`headers` (non-stdio endpoints), `env`, `cwd`, and the *raw* args array, plus a command module's `router`, 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 list` and `gsd capability trust revoke [--project ]`. 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 shared `isValidLedgerEntry` validator 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 structural `kind: 'unconsented'` discriminant that `gsd capability list` filters 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`/`upgradeCapability` warn 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 list` now exposes the stored `disclosureSignature` and `contentHash` for diffing; and when `GSD_HOME` resolves 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-`Buffer` reader, never a lossy UTF-8 decode) so neither a `NUL` embedded in file content can fake a file boundary (the old `relpath + NUL + content + NUL` framing was non-injective) nor can two binary artifacts that differ only in invalid-UTF-8 bytes collide on `U+FFFD`; empty directories are bound via typed directory markers so adding/removing one changes the hash. `recordProjectConsent`/`revokeProjectConsent` now **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 future `envFile`/`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 on `fs.realpathSync` (fail-safe to `path.resolve`), so a **symlinked `GSD_HOME` aliasing 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 reads `capability.json` through the shared **bounded** fd reader (regular-file + size cap) instead of a raw `fs.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 to `U+FFFD`) no longer collide; and (4) the `gsd capability trust revoke` CLI 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 (`materializeHookFragments` and cross-capability validation) for a project-scope overlay, so a forged in-repo bundle whose `fragment.path` points 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-depth `materializeHookFragments` now 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 list` now reads each project `capability.json` through the same bounded reader instead of a raw `fs.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's `canonicalDir` realpath **failure** is now strictly fail-safe — a candidate that would be classified trusted-`global` but whose `realpathSync` throws (a race/odd-FS, e.g. a symlinked `GSD_HOME` aliasing the project root) is reclassified conservatively to consent-required `project`, 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 when `realpath(global)` AND `realpath(project)` BOTH succeed AND resolve to DIFFERENT physical paths; the prior one-sided rule (demote only a realpath-failed *global* candidate) still let a **symlinked `GSD_HOME` aliasing 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-required `project` whenever a genuine project root exists — while a genuinely non-existent project overlay (ENOENT) or a distinct real global root still stays trusted; (2) `bundleContentHash` now **bounds the enumeration itself** — it streams each directory via `fs.opendirSync` + `readSync` and 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 project `remove` no longer silently swallows the revoke-on-lock-failure throw — `revokeProjectConsent` throws on a consent-lock failure (a stale consent record a byte-identical re-drop could reactivate against), so `removeCapability` now surfaces it via a stderr warning naming the record AND a `consentRevokeFailed`/`consentRevokeWarning` flag on the result, which the CLI reports as a non-clean removal (telling the user to run `gsd capability trust revoke`). diff --git a/.changeset/fix-1460-integrity-confinement.md b/.changeset/fix-1460-integrity-confinement.md deleted file mode 100644 index 74e451310..000000000 --- a/.changeset/fix-1460-integrity-confinement.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -type: Fixed -pr: 1481 ---- - -**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) diff --git a/.changeset/fix-1461-overlay-crash-dos.md b/.changeset/fix-1461-overlay-crash-dos.md deleted file mode 100644 index 422ac4b15..000000000 --- a/.changeset/fix-1461-overlay-crash-dos.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -type: Fixed -pr: 1475 ---- -**The capability loader never crashes on a single malformed overlay, and untrusted manifest/tar reads are size-bounded (ADR-1244 D2 invariant)** — `loadRegistry` now makes the WHOLE per-candidate overlay-processing body total: ANY throw from ANY validator or step (including the committed `validateCapability`, which dereferences a malformed array entry such as `gates: [null]` / `steps: [null]` / `contributions: [null]` before its shape check) drops just that one overlay with a skip-warning instead of escaping the loader, while `gatePointsOf` is hardened to be total over null/non-array/malformed gates. The final canonical `buildRegistry` compose 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.commandRoots` in 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 untrusted `capability.json` (tarball / npm / git / local) via the shared bounded fd-reader (regular-file + 8 MiB cap) instead of a raw `fs.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; and `stageValidated` now 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 of `copyDirRecursive` / `git clone` / `npm pack` / `tar -x` that were previously only timeout-bounded. `copyDirRecursive` itself is now STREAMING and BUDGETED: it enumerates each directory via `fs.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 former `fs.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-member `tar`-header size parse (`parseTarMemberSize`, which mis-anchored on BSD `tar -tv` owner/group columns such as a `Jan` group → fail-open) was REMOVED in favor of that non-spoofable staged-dir budget; `assertSafeTarMembers` keeps its unambiguous traversal / symlink / hardlink NAME and TYPE guards. (#1461) - - diff --git a/.changeset/fix-1462-ledger-corruption.md b/.changeset/fix-1462-ledger-corruption.md deleted file mode 100644 index b7d691dfe..000000000 --- a/.changeset/fix-1462-ledger-corruption.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -type: Fixed -pr: 1469 ---- -**Capability ledger: fail closed on corruption, with durable atomic writes and a race-safe install lock.** A corrupt or unreadable `.gsd-capabilities.json` is now left in place and surfaced (not silently overwritten) — `install`/`update`/`remove`/`list`/`reconcile` fail 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 in `settings.json`/`hooks.json`). Ledger writes are atomic and crash-durable (exclusive temp file + `fsync` of 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.) diff --git a/.changeset/fix-planner-verify-gate-gaps-5f59a168.md b/.changeset/fix-planner-verify-gate-gaps-5f59a168.md deleted file mode 100644 index d200bb474..000000000 --- a/.changeset/fix-planner-verify-gate-gaps-5f59a168.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -type: Fixed -pr: 1482 ---- - -fix(#1478,#1479,#1480): prohibit ungrounded baselines, error-suppressing fallbacks, and stale-artifact authority in planner verify blocks - - diff --git a/.changeset/fix-pr-branch-sub-repos-git-c.md b/.changeset/fix-pr-branch-sub-repos-git-c.md deleted file mode 100644 index dfb4ed34a..000000000 --- a/.changeset/fix-pr-branch-sub-repos-git-c.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -type: Fixed -pr: 667 ---- - -**`/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 ` so no shell-state assumptions are made. diff --git a/.changeset/gallant-geese-run.md b/.changeset/gallant-geese-run.md deleted file mode 100644 index c4c370747..000000000 --- a/.changeset/gallant-geese-run.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -type: Fixed -pr: 1654 ---- -**`/gsd-new-project` AI Models prompt now exposes the `adaptive` model profile** — both onboarding paths (auto-mode and interactive) listed only Balanced/Quality/Budget/Inherit, so the `adaptive` profile (role-based cost optimization across Claude/Codex/Gemini/OpenRouter/local) was unreachable through `/gsd-new-project` despite 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 both `config-new-project` example payloads list `adaptive`. (#1516) diff --git a/.changeset/gentle-badgers-purr.md b/.changeset/gentle-badgers-purr.md deleted file mode 100644 index 0f028af85..000000000 --- a/.changeset/gentle-badgers-purr.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -type: Fixed -pr: 1457 ---- -**`--raw` CLI 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) diff --git a/.changeset/gentle-finches-roam.md b/.changeset/gentle-finches-roam.md deleted file mode 100644 index ba1296a64..000000000 --- a/.changeset/gentle-finches-roam.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -type: Added -pr: 1457 ---- -**`gsd capability` management 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) diff --git a/.changeset/gentle-jaguars-munch.md b/.changeset/gentle-jaguars-munch.md deleted file mode 100644 index 8419e74b2..000000000 --- a/.changeset/gentle-jaguars-munch.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -type: Fixed -pr: 1661 ---- -**`gsd install`/upgrade now recovers a malformed `~/.gsd/defaults.json` instead of leaving it broken** — a `defaults.json` containing a valid-JSON-but-non-object value (`null`, `[]`, a number, or a string) bypassed the parse `catch` and flowed through unrecovered: `null` threw a TypeError (swallowed by the outer guard, logging a confusing "Could not write" warning and leaving the file as `null`), 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 and `resolve_model_ids` defaults normally. diff --git a/.changeset/happy-badgers-hop.md b/.changeset/happy-badgers-hop.md deleted file mode 100644 index b45404944..000000000 --- a/.changeset/happy-badgers-hop.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -type: Security -pr: 1585 ---- -**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-researcher` and `gsd-domain-researcher`, both web-ingress) isolate fetched/read content as data-not-instructions via a shared `untrusted-input-boundary` reference — this prompt-level boundary is what keeps an injection from being *followed*. An opt-in `security.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. diff --git a/.changeset/humble-cranes-wake.md b/.changeset/humble-cranes-wake.md deleted file mode 100644 index db3b3aa05..000000000 --- a/.changeset/humble-cranes-wake.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -type: Fixed -pr: 1568 ---- -**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 in `progress.total_phases` but could never be counted complete, freezing the milestone below 100% (e.g. 5/6 = 83%) with `state sync --verify` reporting no drift. Both STATE counting paths (`state json` and `state 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) diff --git a/.changeset/humble-sloths-click.md b/.changeset/humble-sloths-click.md deleted file mode 100644 index c1339c406..000000000 --- a/.changeset/humble-sloths-click.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -type: Added -pr: 1440 ---- -**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 via `loadRegistry({ 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. diff --git a/.changeset/humble-wasps-click.md b/.changeset/humble-wasps-click.md deleted file mode 100644 index 3e1670dc4..000000000 --- a/.changeset/humble-wasps-click.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -type: Fixed -pr: 1519 ---- -**Codex installs no longer run with unsafe Claude-style worktree isolation** — a Codex install with a runtime-neutral `.planning/config.json` was resolving its runtime as Claude and enabling git worktree isolation, which Codex's `spawn_agent` cannot 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 resolve `runtime=codex`, default `workflow.use_worktrees` to `false`, and fail closed when worktrees are forced on. (#1515) diff --git a/.changeset/jolly-hawks-run.md b/.changeset/jolly-hawks-run.md deleted file mode 100644 index f87aae45b..000000000 --- a/.changeset/jolly-hawks-run.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -type: Fixed -pr: 1622 ---- -**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) diff --git a/.changeset/jolly-orcas-snooze.md b/.changeset/jolly-orcas-snooze.md deleted file mode 100644 index 319a7664b..000000000 --- a/.changeset/jolly-orcas-snooze.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -type: Fixed -pr: 1638 ---- -**Capability `settings.json` hooks no longer fire on every tool and no longer fail when non-executable** — installing a capability that declared a tool-scoped `PreToolUse`/`PostToolUse` hook wrote the entry with no `matcher`, so a guard intended for only `Write|Edit` fired on every tool call (including `Bash`) 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 via `git`/tarball that lost the executable bit failed with `Permission denied` on every matching call. Install now honors a declared `matcher` (absent = match-all, unchanged for existing capabilities) and emits a `node`-prefixed command for `.js`/`.cjs`/`.mjs` hooks so they run regardless of file-mode bits. (#1634) diff --git a/.changeset/kind-deer-rest.md b/.changeset/kind-deer-rest.md deleted file mode 100644 index 716c7b71b..000000000 --- a/.changeset/kind-deer-rest.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -type: Fixed -pr: 1633 ---- -**`/gsd:secure-phase` now 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 resolves `workflow.security_asvs_level` and `workflow.security_block_on` from config (`--raw`) before the auditor handoff. (#1625) diff --git a/.changeset/kind-dogs-dart.md b/.changeset/kind-dogs-dart.md deleted file mode 100644 index f70851ca7..000000000 --- a/.changeset/kind-dogs-dart.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -type: Added -pr: 1436 ---- -**Capability manifests are now versioned** — every `capability.json` carries a required semver `version`, plus optional `engines.gsd`, `compatVersions`, `integrity` and `provenance` fields, 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. diff --git a/.changeset/kind-moles-hop.md b/.changeset/kind-moles-hop.md deleted file mode 100644 index 1b4f60704..000000000 --- a/.changeset/kind-moles-hop.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -type: Fixed -pr: 1548 ---- -**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. diff --git a/.changeset/kind-ravens-hum.md b/.changeset/kind-ravens-hum.md deleted file mode 100644 index 14c4e2d75..000000000 --- a/.changeset/kind-ravens-hum.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -type: Fixed -pr: 1635 ---- -**The security audit gate now respects `workflow.security_block_on` severity** — `/gsd:secure-phase` previously blocked phase advancement on *any* open threat regardless of severity, so the documented `security_block_on` threshold 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 configured `security_block_on` severity count toward the blocking gate (`SECURITY.md threats_open`); `none` disables blocking, and a missing/unparseable severity fails closed as critical. (#1626) diff --git a/.changeset/lively-orcas-bark.md b/.changeset/lively-orcas-bark.md deleted file mode 100644 index fb532e5a1..000000000 --- a/.changeset/lively-orcas-bark.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -type: Security -pr: 1449 ---- -**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.gsd` are verified before any code is staged, install never executes capability code, and reserved `gsd-`/`gsd-core-`/`anthropic-` namespaces are refused. `capabilities.strict_known_registries` gates which sources may be installed (`[]` = local-only lockdown; host-based allowlist otherwise) and `capabilities.auto_update` is off by default, re-prompting whenever a new version's executable set changes. An install ledger makes `remove` surgical (strips only the capability's own shared-config entries, preserving your hand-edits) and `update` an atomic, crash-safe stage-then-swap. (#1449) diff --git a/.changeset/lively-orcas-roam.md b/.changeset/lively-orcas-roam.md deleted file mode 100644 index 0cd9159a7..000000000 --- a/.changeset/lively-orcas-roam.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -type: Fixed -pr: 1504 ---- -`verify codebase-drift` now reads `workflow.drift_action` and `workflow.drift_threshold` from the correct nested config shape — previously both keys silently no-oped because `loadConfig()` returns a flattened object and `config?.workflow` was always `undefined`. diff --git a/.changeset/merry-deer-greet.md b/.changeset/merry-deer-greet.md deleted file mode 100644 index f88908e6d..000000000 --- a/.changeset/merry-deer-greet.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -type: Added -pr: 722 ---- -**`/gsd-capture --list-seeds` audits 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 the `gsd-tools list-seeds` command. Previously seeds could only be created or auto-surfaced at `/gsd-new-milestone`, with no way to browse them on demand (#441). diff --git a/.changeset/noble-bears-munch.md b/.changeset/noble-bears-munch.md deleted file mode 100644 index 0a4904317..000000000 --- a/.changeset/noble-bears-munch.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -type: Fixed -pr: 1665 ---- -**`check.decision-coverage-plan` no longer false-passes when CONTEXT.md decisions use the titled-colon bullet form** — `parseDecisions` recognized 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. diff --git a/.changeset/noble-deer-parade.md b/.changeset/noble-deer-parade.md deleted file mode 100644 index 956b27fc7..000000000 --- a/.changeset/noble-deer-parade.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -type: Added -pr: 1443 ---- -**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.gsd` compatibility, full conformance validation) and stages a bundle **without executing any capability code** (copy/extract only — `npm pack --ignore-scripts`, never `npm 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 upcoming `gsd capability install` command. diff --git a/.changeset/noble-goats-chatter.md b/.changeset/noble-goats-chatter.md deleted file mode 100644 index 70876ae09..000000000 --- a/.changeset/noble-goats-chatter.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -type: Added -pr: 1458 ---- -**Capability matrix reference** — a generated catalogue (`docs/reference/capability-matrix.md`) of every first-party capability's role, tier, extension points, hook kinds, and `engines.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) diff --git a/.changeset/patient-tunas-jump.md b/.changeset/patient-tunas-jump.md deleted file mode 100644 index b20068e55..000000000 --- a/.changeset/patient-tunas-jump.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -type: Fixed -pr: 1499 ---- -**`npm version` no longer leaves `capability-registry.cjs` stale** — the `version` npm 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 where `gen-capability-registry.cjs --check` failed. (#1498) - - diff --git a/.changeset/plucky-badgers-sprint.md b/.changeset/plucky-badgers-sprint.md deleted file mode 100644 index 29cd19efd..000000000 --- a/.changeset/plucky-badgers-sprint.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -type: Fixed -pr: 1616 ---- -**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) diff --git a/.changeset/plucky-tigers-dart.md b/.changeset/plucky-tigers-dart.md deleted file mode 100644 index 755246961..000000000 --- a/.changeset/plucky-tigers-dart.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -type: Added -pr: 1450 ---- -**Third-party capabilities can ship dispatchable CLI commands (ADR-1244 Phase 5)** — a capability that declares a `commands` family is now dispatched by `gsd-tools ` via the registry, the same seam the first-party `graphify`/`intel`/`audit` commands 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) diff --git a/.changeset/plucky-wolves-run.md b/.changeset/plucky-wolves-run.md deleted file mode 100644 index ab7ba11f2..000000000 --- a/.changeset/plucky-wolves-run.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -type: Fixed -pr: 1664 ---- -**`frontmatter set` on an object-list field now fails closed instead of silently doing nothing** — setting `must_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 set` now 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). diff --git a/.changeset/prohibition-causation-control.md b/.changeset/prohibition-causation-control.md deleted file mode 100644 index f79aaaf3f..000000000 --- a/.changeset/prohibition-causation-control.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -type: Changed -pr: 1518 ---- -**verify-phase test-tier prohibition fail-first can now prove the RED is caused by the violation's _content_** — the `node-test` machine-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 because `GSD_PROHIB_SUBJECT` is set. An optional fifth flat scalar `check_clean_fixture` (→ `CheckDescriptor.cleanFixture`) threads a KNOWN-CLEAN control subject through `projectProhibitions` + `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) diff --git a/.changeset/proud-birds-howl.md b/.changeset/proud-birds-howl.md deleted file mode 100644 index 30c7759e8..000000000 --- a/.changeset/proud-birds-howl.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -type: Fixed -pr: 1632 ---- -**`config-set` now 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) diff --git a/.changeset/proud-sloths-glide.md b/.changeset/proud-sloths-glide.md deleted file mode 100644 index 5ab590c3d..000000000 --- a/.changeset/proud-sloths-glide.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -type: Fixed -pr: 1574 ---- -**OpenCode and other AGENTS-native runtimes now get a root `AGENTS.md` from `/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 shared `getProjectInstructionFile(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. diff --git a/.changeset/proud-sloths-wander.md b/.changeset/proud-sloths-wander.md deleted file mode 100644 index 039947ed7..000000000 --- a/.changeset/proud-sloths-wander.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -type: Fixed -pr: 1539 ---- -`roadmap upgrade` now rejects an unsupported or malformed `--convention` value (including the `--convention=` form) instead of silently running the milestone-prefixed migration, and no longer hard-exits inside the command-routing hub. diff --git a/.changeset/rapid-bears-hum.md b/.changeset/rapid-bears-hum.md deleted file mode 100644 index 5b37db54b..000000000 --- a/.changeset/rapid-bears-hum.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -type: Added -pr: 1597 ---- -**Plugin installs now expose GSD skills** — when GSD is installed as a Claude Code plugin (`claude plugin install`), its skills are available via `gsd-core:` the native way. Previously, plugin-only installs lacked the skill surface because `bin/install.js` never ran; agents that preload `global:gsd-core:` (PR #1261) now resolve against plugin-provided skills. (#1596) diff --git a/.changeset/rapid-jays-wave.md b/.changeset/rapid-jays-wave.md deleted file mode 100644 index 4a26ec0f2..000000000 --- a/.changeset/rapid-jays-wave.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -type: Fixed -pr: 1663 ---- -**`phase complete` no 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 (matching `5`, `05`, `005` in either direction), so the existing row is upserted regardless of padding. diff --git a/.changeset/sharp-eagles-wake.md b/.changeset/sharp-eagles-wake.md deleted file mode 100644 index 4deb758d2..000000000 --- a/.changeset/sharp-eagles-wake.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -type: Fixed -pr: 1653 ---- -**Non-Claude installs no longer rewrite an explicit `resolve_model_ids: true` to "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 defaults `resolve_model_ids` to "omit" when it is absent or falsy; an explicit `true` is preserved. (#1569) diff --git a/.changeset/silly-goats-fly.md b/.changeset/silly-goats-fly.md deleted file mode 100644 index 888ba9f90..000000000 --- a/.changeset/silly-goats-fly.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -type: Fixed -pr: 1543 ---- -A failed `roadmap upgrade --apply` now 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. diff --git a/.changeset/sturdy-birds-climb.md b/.changeset/sturdy-birds-climb.md deleted file mode 100644 index 3e8d3e2f6..000000000 --- a/.changeset/sturdy-birds-climb.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -type: Fixed -pr: 1409 ---- -**Codex runtime no longer crashes on startup** — every `gsd-tools` command previously aborted with `Cannot find module '../../../package.json'` on Codex, whose runtime root has no `package.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 installed `gsd-core/VERSION` (validated semver), so `gsd-tools` loads on every runtime and never emits `version: undefined`. (#1383) diff --git a/.changeset/sturdy-goats-run.md b/.changeset/sturdy-goats-run.md deleted file mode 100644 index cf9ef3e43..000000000 --- a/.changeset/sturdy-goats-run.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -type: Fixed -pr: 1630 ---- -**`/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. diff --git a/.changeset/sturdy-jays-roam.md b/.changeset/sturdy-jays-roam.md deleted file mode 100644 index 3a1a20bbd..000000000 --- a/.changeset/sturdy-jays-roam.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -type: Added -pr: 1448 ---- -Added a validated `gsd-tools worktree record-agent` writer verb that appends a per-agent entry to the wave cleanup manifest, validating every field at write time with the same rules the `cleanup-wave` reader 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) diff --git a/.changeset/sturdy-jays-run.md b/.changeset/sturdy-jays-run.md deleted file mode 100644 index d7eed29b5..000000000 --- a/.changeset/sturdy-jays-run.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -type: Fixed -pr: 1410 ---- -**`query agent-skills` no longer returns empty output on Windows** — the plain (non-`--json`) path wrote the `` block then immediately called `process.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 same `writeAllSync` helper the `--json` path uses. (#1400) diff --git a/.changeset/sturdy-tigers-caper.md b/.changeset/sturdy-tigers-caper.md deleted file mode 100644 index a3420e698..000000000 --- a/.changeset/sturdy-tigers-caper.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -type: Fixed -pr: 1662 ---- -**`phase complete` now updates the By-Phase table on CRLF (Windows) STATE.md files** — the By-Phase table matcher required bare `\n` line 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. diff --git a/.changeset/sturdy-wasps-sprint.md b/.changeset/sturdy-wasps-sprint.md deleted file mode 100644 index db0c22e18..000000000 --- a/.changeset/sturdy-wasps-sprint.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -type: Fixed -pr: 1453 ---- -clean up stale get-shit-done paths in Codex and Kimi skill mirrors on upgrade (#1453) diff --git a/.changeset/sturdy-wasps-swim.md b/.changeset/sturdy-wasps-swim.md deleted file mode 100644 index f499355f6..000000000 --- a/.changeset/sturdy-wasps-swim.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -type: Fixed -pr: 1437 ---- -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 diff --git a/.changeset/sunny-deer-roar.md b/.changeset/sunny-deer-roar.md deleted file mode 100644 index ce3ebed31..000000000 --- a/.changeset/sunny-deer-roar.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -type: Fixed -pr: 1552 ---- -roadmap analyze no longer reports phantom missing_phase_details for milestone-prefixed (M-NN) phase IDs diff --git a/.changeset/sunny-newts-dance.md b/.changeset/sunny-newts-dance.md deleted file mode 100644 index 18b10fdab..000000000 --- a/.changeset/sunny-newts-dance.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -type: Fixed -pr: 1636 ---- -**`workflow.security_asvs_level` now 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 new `references/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) diff --git a/.changeset/wise-geese-roam.md b/.changeset/wise-geese-roam.md deleted file mode 100644 index a52991619..000000000 --- a/.changeset/wise-geese-roam.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -type: Added -pr: 1419 ---- -**`gap-analysis --phase-req-ids` now expands numeric ID ranges** — a same-prefix ascending equal-width range like `SEL-01..SEL-03` expands to `SEL-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) - - diff --git a/.changeset/wise-ibex-dart.md b/.changeset/wise-ibex-dart.md deleted file mode 100644 index 8eca06c62..000000000 --- a/.changeset/wise-ibex-dart.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -type: Fixed -pr: 1541 ---- -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. diff --git a/.changeset/wise-zebras-run.md b/.changeset/wise-zebras-run.md deleted file mode 100644 index 5774f9db0..000000000 --- a/.changeset/wise-zebras-run.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -type: Fixed -pr: 1376 ---- -**Misconfigured agent skills no longer fail silently** — when an agent's configured `agent_skills` paths all fail to resolve (e.g. a missing `SKILL.md`), `gsd-tools query agent-skills` now emits an aggregate warning to stderr and adds a `warnings[]` field to its `--json` output, instead of returning an empty block with no signal. (#1376) diff --git a/.changeset/witty-finches-hum.md b/.changeset/witty-finches-hum.md deleted file mode 100644 index 69a45d9ac..000000000 --- a/.changeset/witty-finches-hum.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -type: Fixed -pr: 1386 ---- -**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) and `gap-analysis` previously 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) diff --git a/.changeset/witty-jays-jump.md b/.changeset/witty-jays-jump.md deleted file mode 100644 index 925b333ed..000000000 --- a/.changeset/witty-jays-jump.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -type: Changed -pr: 727 ---- -**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 ''` line alongside the zsh/bash suggestions (the previous `export PATH=…` commands are inert in fish). It also stops the false-positive "not on your PATH" warning for fish users whose `fish_user_paths`/`config.fish` already 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. diff --git a/.changeset/witty-wasps-munch.md b/.changeset/witty-wasps-munch.md deleted file mode 100644 index 53f8c8301..000000000 --- a/.changeset/witty-wasps-munch.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -type: Added -pr: 1595 ---- -**`/gsd-plan-phase` now flags a stale codebase map before planning** — the `drift` capability runs its codebase-drift check at `plan: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 existing `execute:wave:post` gate. Gated on a new `workflow.plan_drift_precheck` toggle (default on), independent of `workflow.schema_drift_gate`, so autonomous/CI runs can silence the plan-time advisory without disabling the execute-time gates. diff --git a/.changeset/zesty-elks-click.md b/.changeset/zesty-elks-click.md deleted file mode 100644 index 64e174d31..000000000 --- a/.changeset/zesty-elks-click.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -type: Fixed -pr: 1655 ---- -**`phase complete` no longer double-counts Total plans completed velocity on re-run** — re-running `phase complete` on 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) diff --git a/.changeset/zesty-orcas-sing.md b/.changeset/zesty-orcas-sing.md deleted file mode 100644 index 1e36fbb31..000000000 --- a/.changeset/zesty-orcas-sing.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -type: Fixed -pr: 1640 ---- -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. diff --git a/CHANGELOG.md b/CHANGELOG.md index bdbd4d65d..3dbc738ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,111 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). ## [Unreleased] +## [1.6.0] - 2026-06-24 + +### Added + +- **`workflow.context_guard_mode` config key** — proactive context-exhaustion guard for `execute-phase`. Before each wave, the orchestrator self-assesses context pressure using the degradation signals defined in `context-budget.md`. Values: `warn` (default — emit warning and recommend `/gsd:pause-work` when POOR tier detected), `auto` (automatically invoke `/gsd:pause-work` before next wave), `off` (disable). Set via `gsd config-set workflow.context_guard_mode auto` for fully autonomous checkpoint behaviour. (#1452) (#1452) +- **`agent-skills --json` IR gains an additive `value: { block, skills_count }` field** formalizing the `Resolution` convention for config-interpreting read verbs; no breaking change. The new `src/resolution.cts` module exports `Resolution { value, configured, reason, warnings }` (the canonical envelope) and `makeResolution()` (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 is `warnings: 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: git `ls-remote --tags`, npm `view … version`, local re-read; tarball → `manual`, registry → `unknown`). A capability is reported `outdated` only 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 each `npm view` line'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 reported `pinned` — never `outdated`, since `update` will not move it. A bare git ref (`#`) is classified at the remote with a bounded `git ls-remote`: a ref that resolves to a tag is `pinned`, while a **mutable branch** ref is never `pinned` (it degrades to `unknown`, since the installed commit is not recorded to compare against). Each capability is classified `outdated` / `current` / `pinned` / `manual` / `unknown`; subprocesses are bounded (git ≤30s, npm ≤60s) and a failing or unsupported peek degrades that row to `unknown` instead of crashing the command. `--json` emits the records array; the default prints a table. (#1463) (#1488) +- **`gsd capability` management 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 via `loadRegistry({ 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.json` carries a required semver `version`, plus optional `engines.gsd`, `compatVersions`, `integrity` and `provenance` fields, 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-seeds` audits 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 the `gsd-tools list-seeds` command. 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.gsd` compatibility, full conformance validation) and stages a bundle **without executing any capability code** (copy/extract only — `npm pack --ignore-scripts`, never `npm 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 upcoming `gsd capability install` command. (#1443) +- **Capability matrix reference** — a generated catalogue (`docs/reference/capability-matrix.md`) of every first-party capability's role, tier, extension points, hook kinds, and `engines.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 `commands` family is now dispatched by `gsd-tools ` via the registry, the same seam the first-party `graphify`/`intel`/`audit` commands 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 via `gsd-core:` the native way. Previously, plugin-only installs lacked the skill surface because `bin/install.js` never ran; agents that preload `global:gsd-core:` (PR #1261) now resolve against plugin-provided skills. (#1596) (#1597) +- Added a validated `gsd-tools worktree record-agent` writer verb that appends a per-agent entry to the wave cleanup manifest, validating every field at write time with the same rules the `cleanup-wave` reader 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-ids` now expands numeric ID ranges** — a same-prefix ascending equal-width range like `SEL-01..SEL-03` expands to `SEL-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-phase` now flags a stale codebase map before planning** — the `drift` capability runs its codebase-drift check at `plan: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 existing `execute:wave:post` gate. Gated on a new `workflow.plan_drift_precheck` toggle (default on), independent of `workflow.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`, and `audit-open` now route through the Command Routing Hub per ADR-959 §III(B), so `GSD_AUDIT=1` traces, the structured stderr JSON error envelope, and the typed Result contract cover them uniformly with all other command families. JSON-error `reason` values (`usage`, `sdk_unknown_command`) are preserved byte-identical. (#1646) (#1647) +- **`/gsd-verify-work` now routes UAT deterministically from a structured `coverage:` block on SUMMARY.md** — deliverables proven by passing tests (`human_judgment: false` with a non-empty all-`pass` `verification` list) are auto-passed (`source: automated`, no prompt), and only judgment-dependent or unverified deliverables are presented for human sign-off. SUMMARYs without a `coverage:` block fall back to the previous prose-based extraction, byte-identical. Authored by `execute-plan` and validated by the new `gsd-tools uat classify-coverage` verb. (#1611) +- **Thread `isGlobal` install scope through the descriptor-driven `convertedAgentsKind` / `stageAgentsForRuntimeWithConverter` plumbing** — a prerequisite for the ADR-1235 agent-conversion cutover. No runtime declares a converted `agents` kind yet; the `capability.json` wiring is deferred to a follow-up that first ships the ADR-1235 §0 byte-for-byte parity harness (so the `/gsd:surface` / `--materialize` consumer can mirror the legacy agent pipeline before the kind goes live). The legacy `bin/install.js` agent loop remains authoritative, so installed agent output is unchanged. (#1173) (#1438) +- **`/gsd-review` now asks external reviewers to verify plan claims against the source** — the reviewer prompt requires opening the referenced files, citing `file:line` evidence + 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.score` query 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-tools` now resolves the project root from a descendant subdirectory** — `findProjectRoot` walks 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-test` machine-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 because `GSD_PROHIB_SUBJECT` is set. An optional fifth flat scalar `check_clean_fixture` (→ `CheckDescriptor.cleanFixture`) threads a KNOWN-CLEAN control subject through `projectProhibitions` + `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 ''` line alongside the zsh/bash suggestions (the previous `export PATH=…` commands are inert in fish). It also stops the false-positive "not on your PATH" warning for fish users whose `fish_user_paths`/`config.fish` already 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-` (hyphen) slash commands** — the installer was writing command files to `.claude/commands/gsd/.md` (subdirectory with bare names), causing Claude Code to namespace them as `/gsd:` (colon form). The fix writes flat `gsd-.md` files at `.claude/commands/` level so Claude Code registers `/gsd-` (hyphen form), matching hooks, statusline, and all cross-command references. Legacy `commands/gsd/` directories from prior installs are cleaned up on reinstall and uninstall, with `dev-preferences.md` preserved. (#1367) (#1367) +- **`execute-phase` now 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, the `worktree.base-check` auto-degrade only ran once at initialize time; after Wave N merged and tracking commits advanced orchestrator HEAD past `origin/HEAD`, Wave N+1 worktrees were still forked from `origin/HEAD` (Claude Code's "fresh" base), causing both agents to immediately halt with `FATAL: worktree base mismatch` from the `worktree_branch_check` guard. Second, `WAVE_WORKTREE_MANIFEST` was 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 the `execute_waves` "For each wave" loop re-runs `worktree.base-check` before every wave's dispatch (when divergence is detected, `USE_WORKTREES` is overridden to `false` for that wave); step 7c between waves unsets `WAVE_WORKTREE_MANIFEST` so wave N+1 creates a fresh per-wave manifest, and re-asserts `worktree.baseRef:"head"` (idempotent) so the Claude Code harness re-reads the live HEAD on the next dispatch. The permanent fix remains setting `worktree.baseRef:"head"` in `.claude/settings.local.json` (see #683). (#1369) +- **Workflow temp files now randomize correctly on BSD/macOS** — several workflows called `mktemp` with templates where `XXXXXX` was followed by a `.json`/`.md` suffix (e.g. `gsd-worktree-wave-XXXXXX.json`, `gsd-pr-body.XXXXXX.md`). BSD/macOS `mktemp` only substitutes `XXXXXX` when 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 bare `mtime` timer 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 on `process.kill(pid,0)` liveness with a deadman ceiling above the wait budget (pid-reuse backstop), `withPlanningLock` no longer force-steals a live holder on timeout (and can no longer leak an uncaught `EEXIST`), `writeStateMd` computes its disk scan inside the lock, and `acquireStateLock` no 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) +- **`normalizeNodePath` now maps pruned mise node paths to the stable shim (#1619)** — `resolveNodeRunner()` bakes `process.execPath` into managed `.js` hook commands. Node realpaths execPath, so under mise it resolves to `/installs/node//bin/node` — a concrete version mise prunes on `mise up`, after which every managed hook fails to spawn (`No such file or directory` on every SessionStart and tool event), the same ephemeral-path failure #977 fixed for fnm and #3181 for Homebrew. `normalizeNodePath` now rewrites a mise versioned install path to the stable sibling shim `/shims/node` (`.exe` preserved on Windows) when that shim exists, deriving `` from execPath so a custom `MISE_DATA_DIR` works, 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 merge` no longer destroy `must_haves` object-lists** — changing one frontmatter field (e.g. `wave`) silently dropped every `provides:` value and collapsed `must_haves.artifacts`/`.prohibitions` from 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 unrelated `must_haves` blocks 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.json` previously resolved `runtime=claude` and enabled git worktree isolation, which only Claude Code's `isolation="worktree"` can honor — risking main-checkout edits while the workflow believed agents were isolated. Every non-Claude install now resolves its own runtime identity, defaults `workflow.use_worktrees` to `false`, 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_mode` now accepted by `config-set`; three undocumented workflow keys added to references** — `workflow.mvp_mode`, `workflow.code_review_command`, and `workflow.plan_chunked` were consumed by planning-pipeline code but could not be set via `config-set` (they were missing from `VALID_CONFIG_KEYS`) or discovered via reference docs. All three are now in the schema and documented in `references/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`/`SlashCommand` tools were converted to an invalid `skill` tool that Gemini rejects, aborting the load of 22 of 34 agents. They are now excluded from the Gemini and Gemini-backed Antigravity agent `tools:` frontmatter, the same way `AskUserQuestion` already is. (#1394) (#1418) +- **Antigravity config-dir resolution no longer shadows the active runtime** — when more than one of `~/.gemini/antigravity`, `antigravity-ide`, or `antigravity-cli` exists, GSD now resolves to the directory it actually installed into (marked by `gsd-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-skills` no longer silently drops a configured agent's skills under cwd or workstream drift** — `cmdAgentSkills` now anchors to the project root via `findProjectRoot` before loading config, so invoking it from a descendant subdirectory or with a `GSD_WORKSTREAM` that has no scoped config correctly resolves the configured `agent_skills` block. A new `loadConfigResolved(cwd, options) → { config, source, degraded }` function reports provenance alongside the config object: `source` distinguishes `'root' | 'workstream' | 'builtin-defaults' | 'global-defaults'`; `degraded:true` signals a workstream was requested but its config.json was absent. The `--json` IR of `agent-skills` gains four new fields — `configured` (bool), `reason` (`'resolved' | 'not_configured' | 'configured_empty' | 'configured_unresolved'`), `source`, and `degraded` — making silent failures visible and testable. A `configured_empty` or `configured_unresolved` agent emits a `stderr WARNING`; an unconfigured agent stays silent. (Closes #1366. Part of #1411, P2 / #1415.) (#1424) +- **`findProjectRoot` now respects explicit `sub_repos` config over implicit `.git`** — when a parent workspace's `.planning/config.json` lists a child directory in `sub_repos`, that declaration takes precedence over the child's own `.git/` directory. Previously, if the child had both `.planning/` and `.git/`, the `.git` heuristic fired first and resolved to the child rather than the parent workspace, making the `sub_repos` declaration 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 module `src/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` — a `sha512` over *every* regular file in the bundle, manifest AND artifacts AND identity, symlinks and non-regular entries rejected, bounded), **not** the repo-plantable ledger `integrity` (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 swap `capability.json` for 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 list` reports `status: inactive` with 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, and `trust revoke`, so an install from a sub-directory is not immediately inactive. The disclosure signature now also covers each MCP server's `transport`/`url`/`headers` (non-stdio endpoints), `env`, `cwd`, and the *raw* args array, plus a command module's `router`, 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 list` and `gsd capability trust revoke [--project ]`. 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 shared `isValidLedgerEntry` validator 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 structural `kind: 'unconsented'` discriminant that `gsd capability list` filters 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`/`upgradeCapability` warn 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 list` now exposes the stored `disclosureSignature` and `contentHash` for diffing; and when `GSD_HOME` resolves 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-`Buffer` reader, never a lossy UTF-8 decode) so neither a `NUL` embedded in file content can fake a file boundary (the old `relpath + NUL + content + NUL` framing was non-injective) nor can two binary artifacts that differ only in invalid-UTF-8 bytes collide on `U+FFFD`; empty directories are bound via typed directory markers so adding/removing one changes the hash. `recordProjectConsent`/`revokeProjectConsent` now **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 future `envFile`/`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 on `fs.realpathSync` (fail-safe to `path.resolve`), so a **symlinked `GSD_HOME` aliasing 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 reads `capability.json` through the shared **bounded** fd reader (regular-file + size cap) instead of a raw `fs.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 to `U+FFFD`) no longer collide; and (4) the `gsd capability trust revoke` CLI 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 (`materializeHookFragments` and cross-capability validation) for a project-scope overlay, so a forged in-repo bundle whose `fragment.path` points 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-depth `materializeHookFragments` now 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 list` now reads each project `capability.json` through the same bounded reader instead of a raw `fs.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's `canonicalDir` realpath **failure** is now strictly fail-safe — a candidate that would be classified trusted-`global` but whose `realpathSync` throws (a race/odd-FS, e.g. a symlinked `GSD_HOME` aliasing the project root) is reclassified conservatively to consent-required `project`, 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 when `realpath(global)` AND `realpath(project)` BOTH succeed AND resolve to DIFFERENT physical paths; the prior one-sided rule (demote only a realpath-failed *global* candidate) still let a **symlinked `GSD_HOME` aliasing 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-required `project` whenever a genuine project root exists — while a genuinely non-existent project overlay (ENOENT) or a distinct real global root still stays trusted; (2) `bundleContentHash` now **bounds the enumeration itself** — it streams each directory via `fs.opendirSync` + `readSync` and 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 project `remove` no longer silently swallows the revoke-on-lock-failure throw — `revokeProjectConsent` throws on a consent-lock failure (a stale consent record a byte-identical re-drop could reactivate against), so `removeCapability` now surfaces it via a stderr warning naming the record AND a `consentRevokeFailed`/`consentRevokeWarning` flag on the result, which the CLI reports as a non-clean removal (telling the user to run `gsd 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)** — `loadRegistry` now makes the WHOLE per-candidate overlay-processing body total: ANY throw from ANY validator or step (including the committed `validateCapability`, which dereferences a malformed array entry such as `gates: [null]` / `steps: [null]` / `contributions: [null]` before its shape check) drops just that one overlay with a skip-warning instead of escaping the loader, while `gatePointsOf` is hardened to be total over null/non-array/malformed gates. The final canonical `buildRegistry` compose 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.commandRoots` in 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 untrusted `capability.json` (tarball / npm / git / local) via the shared bounded fd-reader (regular-file + 8 MiB cap) instead of a raw `fs.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; and `stageValidated` now 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 of `copyDirRecursive` / `git clone` / `npm pack` / `tar -x` that were previously only timeout-bounded. `copyDirRecursive` itself is now STREAMING and BUDGETED: it enumerates each directory via `fs.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 former `fs.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-member `tar`-header size parse (`parseTarMemberSize`, which mis-anchored on BSD `tar -tv` owner/group columns such as a `Jan` group → fail-open) was REMOVED in favor of that non-spoofable staged-dir budget; `assertSafeTarMembers` keeps 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.json` is now left in place and surfaced (not silently overwritten) — `install`/`update`/`remove`/`list`/`reconcile` fail 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 in `settings.json`/`hooks.json`). Ledger writes are atomic and crash-durable (exclusive temp file + `fsync` of 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 ` so no shell-state assumptions are made. (#667) +- **`/gsd-new-project` AI Models prompt now exposes the `adaptive` model profile** — both onboarding paths (auto-mode and interactive) listed only Balanced/Quality/Budget/Inherit, so the `adaptive` profile (role-based cost optimization across Claude/Codex/Gemini/OpenRouter/local) was unreachable through `/gsd-new-project` despite 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 both `config-new-project` example payloads list `adaptive`. (#1516) (#1654) +- **`--raw` CLI 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.json` instead of leaving it broken** — a `defaults.json` containing a valid-JSON-but-non-object value (`null`, `[]`, a number, or a string) bypassed the parse `catch` and flowed through unrecovered: `null` threw a TypeError (swallowed by the outer guard, logging a confusing "Could not write" warning and leaving the file as `null`), 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 and `resolve_model_ids` defaults 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 in `progress.total_phases` but could never be counted complete, freezing the milestone below 100% (e.g. 5/6 = 83%) with `state sync --verify` reporting no drift. Both STATE counting paths (`state json` and `state 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.json` was resolving its runtime as Claude and enabling git worktree isolation, which Codex's `spawn_agent` cannot 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 resolve `runtime=codex`, default `workflow.use_worktrees` to `false`, 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.json` hooks no longer fire on every tool and no longer fail when non-executable** — installing a capability that declared a tool-scoped `PreToolUse`/`PostToolUse` hook wrote the entry with no `matcher`, so a guard intended for only `Write|Edit` fired on every tool call (including `Bash`) 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 via `git`/tarball that lost the executable bit failed with `Permission denied` on every matching call. Install now honors a declared `matcher` (absent = match-all, unchanged for existing capabilities) and emits a `node`-prefixed command for `.js`/`.cjs`/`.mjs` hooks so they run regardless of file-mode bits. (#1634) (#1638) +- **`/gsd:secure-phase` now 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 resolves `workflow.security_asvs_level` and `workflow.security_block_on` from 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_on` severity** — `/gsd:secure-phase` previously blocked phase advancement on *any* open threat regardless of severity, so the documented `security_block_on` threshold 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 configured `security_block_on` severity count toward the blocking gate (`SECURITY.md threats_open`); `none` disables blocking, and a missing/unparseable severity fails closed as critical. (#1626) (#1635) +- `verify codebase-drift` now reads `workflow.drift_action` and `workflow.drift_threshold` from the correct nested config shape — previously both keys silently no-oped because `loadConfig()` returns a flattened object and `config?.workflow` was always `undefined`. (#1504) +- **`check.decision-coverage-plan` no longer false-passes when CONTEXT.md decisions use the titled-colon bullet form** — `parseDecisions` recognized 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 version` no longer leaves `capability-registry.cjs` stale** — the `version` npm 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 where `gen-capability-registry.cjs --check` failed. (#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 set` on an object-list field now fails closed instead of silently doing nothing** — setting `must_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 set` now 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-set` now 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.md` from `/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 shared `getProjectInstructionFile(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 upgrade` now rejects an unsupported or malformed `--convention` value (including the `--convention=` form) instead of silently running the milestone-prefixed migration, and no longer hard-exits inside the command-routing hub. (#1539) +- **`phase complete` no 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 (matching `5`, `05`, `005` in either direction), so the existing row is upserted regardless of padding. (#1663) +- **Non-Claude installs no longer rewrite an explicit `resolve_model_ids: true` to "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 defaults `resolve_model_ids` to "omit" when it is absent or falsy; an explicit `true` is preserved. (#1569) (#1653) +- A failed `roadmap upgrade --apply` now 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-tools` command previously aborted with `Cannot find module '../../../package.json'` on Codex, whose runtime root has no `package.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 installed `gsd-core/VERSION` (validated semver), so `gsd-tools` loads on every runtime and never emits `version: 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-skills` no longer returns empty output on Windows** — the plain (non-`--json`) path wrote the `` block then immediately called `process.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 same `writeAllSync` helper the `--json` path uses. (#1400) (#1410) +- **`phase complete` now updates the By-Phase table on CRLF (Windows) STATE.md files** — the By-Phase table matcher required bare `\n` line 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_level` now 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 new `references/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_skills` paths all fail to resolve (e.g. a missing `SKILL.md`), `gsd-tools query agent-skills` now emits an aggregate warning to stderr and adds a `warnings[]` field to its `--json` output, 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) and `gap-analysis` previously 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 complete` no longer double-counts Total plans completed velocity on re-run** — re-running `phase complete` on 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-researcher` and `gsd-domain-researcher`, both web-ingress) isolate fetched/read content as data-not-instructions via a shared `untrusted-input-boundary` reference — this prompt-level boundary is what keeps an injection from being *followed*. An opt-in `security.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.gsd` are verified before any code is staged, install never executes capability code, and reserved `gsd-`/`gsd-core-`/`anthropic-` namespaces are refused. `capabilities.strict_known_registries` gates which sources may be installed (`[]` = local-only lockdown; host-based allowlist otherwise) and `capabilities.auto_update` is off by default, re-prompting whenever a new version's executable set changes. An install ledger makes `remove` surgical (strips only the capability's own shared-config entries, preserving your hand-edits) and `update` an atomic, crash-safe stage-then-swap. (#1449) (#1449) + ## [1.5.0] - 2026-06-17 ### Added