chore: back-merge main into next (b0ccf790)
This commit is contained in:
@@ -1,6 +0,0 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3472
|
||||
---
|
||||
|
||||
**`withPlanningLock` no longer reports a phantom "held by a live process" timeout when `.planning/` cannot be created** — a best-effort `try { platformEnsureDir(...) } catch { /* ok */ }` swallowed the real mkdir failure (EACCES/ENOSPC/EROFS), so the subsequent lock write failed with ENOENT (parent missing), and because ENOENT is in the lock's retry set (added for a Docker overlay-fs race) the loop spun the full 10 s budget before throwing a misattributed contention error that pointed operators at a nonexistent lock-holder. The mkdir failure now propagates immediately with its real filesystem errno and message, so an unwritable or full disk is reported as itself, not as concurrent-writer contention. The Docker overlay-fs ENOENT *lock-write* race (directory present) is still retried as before, and every code path where `.planning/` already exists or can be created is unchanged. Part of epic #1879 (distinguish "absent" from "corrupt/permission-denied" across engine read paths). (#1884)
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
type: Changed
|
||||
pr: 3431
|
||||
---
|
||||
<!-- docs-exempt: internal prompt-precision change to the plan-phase workflow's AI-keyword gate; no docs surface documents the keyword list or the gate's trigger keywords (re-verified on next @ 7976b1ca0 — no docs/ file mentions the list's distinctive tokens) -->
|
||||
**The `plan-phase` AI-integration capability gate no longer lists substring-collidable keywords** — bare `eval` (a substring of ordinary phase-goal words like `evaluation` and `retrieval`) is replaced by `llm eval`, and the under-specified `ai system` is dropped, per maintainer triage on the linked issue. The gate is a capability prompt, not a hard block, so this is a precision improvement: phase goals like "add evaluation metrics" or "build the retrieval layer" no longer invite a spurious AI-SPEC branch, and genuinely AI-flavored goals still match on the precise framework and technique names. (#2115)
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
type: Changed
|
||||
pr: 2543
|
||||
---
|
||||
**`/gsd-explore` research passes now disposition each surfaced claim three ways** — **admit** (survives a prompted-to-refute pass and is grounded in a source, shown with the source), **refute** (a source contradicts it, dropped or corrected), or **abstain** (unverifiable, or a source-vs-prior conflict). Abstained claims go to a separate **Unresolved** ledger instead of being smoothed into confident prose, so you can see what the research could not stand behind. Refute and abstain are separated by whether the disagreeing source is *authoritative for that claim* — a blog post contradicting your `engines` field is an abstain, the `engines` field itself is a refute — and your own prior belief is never authoritative alone. A finding that comes back with no disposition at all is ledgered as an abstain rather than silently dropped or asserted as prose. Two guards ship with it: conflict-abstention (a source-vs-prior conflict routes to the ledger, not a silent pick-a-side) and a tier floor (a would-be admit is presented as an abstain when the researcher's resolved tier is budget-level or could not be determined, because an under-tiered or unverified researcher over-defers to whatever source it was handed; corrections are unaffected). Keying the floor on the resolved tier rather than the model id keeps it working on non-Claude installs, where the model id is often blank or substituted by the runtime. The floor narrows this gap rather than closing it — a config that deliberately repoints one tier at another tier's model can still report a higher tier than what actually runs. Claims-side analogue of the honest verifier. (#2229)
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 2571
|
||||
---
|
||||
**The idle/staleness detector now fires when `last_activity` carries a description** — a `last_activity` written in the shape `templates/state.md` prescribes (`[YYYY-MM-DD] — [What happened]`) parsed to `NaN`, and because the detector treats an unparseable value as "not stale" it failed open to `false`. Any project whose `last_activity` kept its description was never reported idle, no matter how long it had sat. The leading date is now parsed out of the value, so the description no longer blinds the only staleness signal in the front door. An impossible calendar date such as `2026-02-30` is now rejected outright rather than silently rolling forward to a real — and wrong — date. (#2570)
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
type: Changed
|
||||
pr: 2622
|
||||
---
|
||||
**STATE.md now records the commit it was written against** — a new `state_head` frontmatter stamp lets `/gsd-health` and smart-entry report how far the codebase has moved since STATE.md was last written, so a long-stale STATE.md can be discounted rather than read at face value. Health adds advisory `W024` once the gap reaches 20 commits. This is a freshness proxy, not a drift measurement: the count includes commits that never touched anything STATE.md describes, and the stamp refreshes on any state write — so it is always worded as approximate and never gates anything. The stamp is omitted entirely when the commit cannot be resolved to the project's *own* repository — a project nested inside an unrelated checkout reports unknown rather than borrowing that repo's freshness. (#2573)
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 2818
|
||||
---
|
||||
**`/gsd-ship` now detects and recovers a PR wedged by the ship-note commit** — when the `[ci skip]` ship note leaves required checks unstarted, ship re-triggers CI instead of leaving the PR unmergeable. (#2783)
|
||||
|
||||
*Note: This introduces a latency tradeoff. All `/gsd-ship` invocations now poll GitHub PR state for up to 15 seconds to ensure the commit was processed and check if recovery is needed, even for repositories without required checks.*
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3391
|
||||
---
|
||||
**`spec-phase` Step 5.5 now surfaces the edge-probe's proposed edges to the resolution loop instead of discarding them** — the deterministic coverage report was computed, validated, then reduced to a single applicable-count, so the resolution loop re-derived edge categories from requirement prose and the engine's proposals never reached it. The report is now rendered into context and its rows are consumed as a *floor* the model unions with its own classification (still adding any category the classifier missed), so the written `## Edge Coverage` reflects the engine's deterministic taxonomy rather than model-invented categories; `--auto` gets the same floor. (#3102)
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3205
|
||||
---
|
||||
**`/gsd-quick --validate` no longer trusts a verification result it cannot actually read** — quick parsed the verifier's status by grepping the whole report rather than its frontmatter, so a `status:` line in the report's prose could be picked up alongside or instead of the real one, staleness was never detected at all, and a range of valid and malformed reports alike resolved to a value no routing arm matched — leaving the orchestrator to improvise at the moment the pipeline had failed. Quick now reads the same frontmatter-anchored, staleness-aware `verification.status` query that `execute-phase`, `verify-work` and `progress` already use, and routes `missing` / `unknown` / `stale` through an explicit arm instead of falling through. (#3174)
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3435
|
||||
---
|
||||
**The verifier's non-inferable (`backstop`) abstention rule now defines "explicit evidence" where the verifier is guaranteed to read it.** Step 3 item 5b used the term undefined — its definition was stranded in `gsd-core/references/honest-verifier.md` behind a stale `references/` cite that does not resolve, so the term fell back to the verifier's default notion of evidence (symbol presence + wiring), the exact false-pass the #1154 abstention protocol exists to refuse. 5b now carries the definition inline (a passing wired held-out/property-based test or directly observed behavior; presence + wiring never qualifies), the AFK never-silent/never-halt completion line and the `insufficient_spec`-vs-manual-UAT distinction ship in the eagerly-loaded `verifier-phase-gates.md` reference, and the agent file's three stale bare `references/` cites are gone: the two at 5c and the MVP-mode section now resolve under the `gsd-core/` prefix, and 5b's is superseded by the inline definition itself. (#3206)
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3528
|
||||
---
|
||||
|
||||
**Autonomous/auto-mode no longer auto-approves unmet `<precondition>` checkpoints, and the blocker loop now halts `needs_human` instead of retrying forever** — the checkpoint an executor returns when a task's `<precondition>` is unmet (an unmet `user_setup` step, a missing env var, an absent prior-phase artifact) now carries `gate="blocking-human"`, which both auto-mode bypass layers (executor checkpoint protocol and execute-phase checkpoint handling) honor, so it always stops for a human instead of being silently approved with a synthetic "approved" and then failing `<verify>` on the still-missing prerequisite. Independently, `/gsd:autonomous`'s blocker handler now counts "Fix and retry" attempts per phase step and, after 3 failed attempts, escalates to a terminal `needs_human` halt that surfaces the unmet items and records a `## Needs Human` STATE.md row, ending the observed multi-hour retry loops on operator-gated plans. (#3210)
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3522
|
||||
---
|
||||
**`state add-roadmap-evolution` and `state add-decision` no longer persist a literal `Phase ?` when `--phase` is omitted** — both commands built their entry from the raw CLI flag's `?` fallback instead of the phase already recorded in STATE.md, even with `current_phase: 3` present in frontmatter. Both now resolve the phase through a strict write-path ladder (frontmatter `current_phase` → body `Current Phase` → `Phase: X of Y` scoped strictly to `## Current Position`), leaving `?` only when genuinely unresolvable; an explicit `--phase` still wins. The resolver deliberately does not reuse the read-path `resolveStatePhase`, whose document-wide fallback could adopt a stale historical `| Phase | N |` table row. A guard test now sweeps `src/*.cts` for any new raw `phase || '?'` call site. (#3481)
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3521
|
||||
---
|
||||
**Frontmatter round-trips no longer double backslashes on every state write** — `escapeDoubleQuoted` escaped `\`, `"`, and control characters on each serialize while the parser only stripped the outer quote delimiters, so every read-modify-write cycle doubled existing escapes (2ⁿ−1 backslashes after n cycles). `syncStateFrontmatter` carries `last_activity_desc` through that seam on every state command, growing STATE.md unboundedly — the reported 134 MB file OOMed `state.record-session` after 26 writes. Double-quoted scalars are now un-escaped on parse via the exact inverse of the escaper, making serialize→parse a fixed point; unrecognized escapes are kept literally so hand-authored files parse unchanged. (#3497)
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3526
|
||||
---
|
||||
|
||||
**`/gsd:code-review` now derives the phase diff base from GSD's own commit scopes instead of a prose phrase, ending silently wrong review scopes** — the diff base fed to the reviewer file-list fallback, the SUMMARY↔diff cross-check union, the reviewer agent's `diff_base`, and the fallow `--changed-since` structural pass was greped from commit messages for the literal "Phase N" and kept the oldest match, so any prose mention anywhere in history (a planning commit deferring work "to Phase N per D-09", a doc commit using "### Phase N" as a format example) silently set the base months before the phase existed — on a real repo ~4 phases too early, inflating the reviewer's reading list ~78% with no warning — while GSD's own commits (`docs(phase-N):`, `feat(N-MM):`, `docs(N):`), which never contain the literal phrase, were never matched at all. All three derivations now anchor on the subject-line conventional-commit phase scope (both padded `06` and unpadded `6` spellings, since workflows emit the unpadded roadmap number), commit bodies can no longer capture the base, and a history with no scope-style commits fails loudly with the existing no-base warning and `--files` escape hatch instead of silently picking an arbitrary commit. (#3503)
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3525
|
||||
---
|
||||
|
||||
**`uat_path` is now pinned to the phase's own UAT artifact instead of being picked by unsorted directory-listing order** — both `uat_path` projections (`init plan-phase` and `init phase-op`) selected the phase's `*-UAT.md` with a bare first-match `.find()` that had no phase-membership check and no ordering, so a stray or cross-phase `04-UAT.md` sitting in phase 03's directory could become phase 03's `uat_path`, and which file won was filesystem-dependent (creation order on APFS, hash order on ext4/XFS) — meaning two machines on the same commit could emit different `uat_path` values for the same phase, sending downstream workflows to read another phase's UAT state. Both sites now route through a shared phase-pinned resolver (`resolveUatFile`, sibling of the `resolveVerificationFile` rule from #3357/#3492): the phase's own `<token>-UAT.md` always wins, otherwise the alphabetically-first dashed candidate, deterministically on every machine. (#3518)
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3527
|
||||
---
|
||||
|
||||
**MemPalace sub-features whose defaults are enabled now run when their config keys are absent** — the earlier `capture_artifacts` absent-key fix (#2982) had been applied to only one of six hand-written config gates; the remaining gates for `mempalace.mirror_kg` (knowledge-graph mirroring in the capture and recall skills, their command mirrors, and the curator agent) and `mempalace.diary_journal` (per-agent diary entries at ship) still required the key to be explicitly present and `true`, so a project that enabled MemPalace without writing every sub-toggle silently never mirrored KG facts or wrote diary entries, with no warning. All six gates now treat an absent key as enabled (matching the capability registry's declared `default: true`) and disable the behavior only on an explicit `false`; default-off switches (`mempalace.enabled`, `cross_project_tunnels`) still require explicit opt-in, and a registry-parity regression test keeps future default-true keys from reintroducing the inversion. (#3479)
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3635
|
||||
---
|
||||
**Roadmap `Plans:` lines keep their hand-written text instead of being overwritten with a plan count** — `roadmap update-plan-progress` replaced everything after the `Plans:` label whenever the line did not already begin with a canonical `N/N plans` token, silently destroying freeform prose, a `TBD` note, or a hand-written annotation. A sentence that wrapped onto a second line lost only its first line, leaving the continuation stranded so the roadmap asserted something nobody wrote — at exit 0, in a diff that read as a routine count bump. The count is now written only over a real count token or the fresh-template placeholder, and a single-plan phase (`1 plan`) is recognized rather than frozen. (#3584)
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3650
|
||||
---
|
||||
**Running a capability's own test suite no longer silently deactivates it** — `bundleContentHash` digested every entry under a capability bundle with no exclusions, so ordinary Python bytecode caching (`__pycache__/*.pyc`, written by any plain `python3` run) changed the consent-binding hash. The capability then reported `inactive` with no error and no warning, and `loop render-hooks` quietly dropped its step and gate — indistinguishable from never having installed it. An *empty* `__pycache__` directory was enough to trigger it, since the digest binds directory existence. Only a `*.pyc`/`*.pyo` file sitting directly inside a `__pycache__` directory is now excluded from the digest; a `.pyc`/`.pyo` file anywhere else stays bound, since a sourceless legacy `.pyc` there is still importable and executable. A `__pycache__`/`.pytest_cache` directory has only its own marker suppressed — its contents still bind the digest normally. `node_modules` and other executable content stay bound, excluded entries still count toward the walk's caps, and the filter runs after the symlink rejection so it cannot smuggle one past. (#3631)
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
type: Changed
|
||||
pr: 3199
|
||||
---
|
||||
**Live-plan counting now has one owner, so `superseded` plans stop being scheduled and nested-layout phases stop reporting zero** — `scanPhasePlans` is the sole source of which plans exist and which are outstanding. Twenty-one call sites that re-derived it from filenames now route through it, so a plan marked `status: superseded` is no longer scheduled into an execute-phase wave, phases using the nested `plans/` layout no longer report zero plans, and stray summaries no longer inflate completion. (#3183)
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3536
|
||||
---
|
||||
**Corrected `model-profiles.md`: `model` and `effort` do not resolve through one shared precedence ladder** — the reference previously claimed a `models[phase_type]` or `dynamic_routing` override flips both, and that an effort config change takes effect like a model change. In reality effort (claude runtime) is baked into agent frontmatter at install time and requires `node gsd-tools.cjs effort sync --apply` to change; Codex agents pin `model_reasoning_effort` in generated `.toml` files. (#3530)
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3377
|
||||
---
|
||||
**`requirements mark-complete` now flips the traceability row when `## Traceability` holds more than one table** — `updateTableCell` no longer binds to the first table in the section; it scans for the table that actually carries the requested column. A section with a phase-summary table above the requirement rows previously made the Status write silently bail (`table_unmatched`) while the checkbox still flipped, leaving the row at `Pending` indefinitely. Single-table sections are unchanged. (#3255)
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
type: Added
|
||||
pr: 3542
|
||||
---
|
||||
**`resolve-execution` now tells the truth about what the agent will run at** — the query reported only the config-cascade effort, which is not what an installed agent uses when its `effort:` frontmatter was hand-stripped or drifted. `--json` adds `effort_effective` (read from the installed agent frontmatter for the claude runtime; `"inherit"` when the key is absent) and `effort_effective_source` (`frontmatter` | `frontmatter-absent` | `resolved`). All existing fields, including `--pick effort`, are unchanged. (#3534)
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3429
|
||||
---
|
||||
**`init execute-phase` no longer hands a directory slug to the phase-start flow as the phase display name.** When a phase's working directory already exists on disk, the disk-lookup path derived `phase_name` from the directory-name remainder — itself an already-slugified value (`phase.add` writes `${num}-${slug}` dirs) — so `phase_name` and `phase_slug` came out byte-identical. The execute-phase workflow forwards `phase_name` into `state begin-phase --name`, which wrote that raw slug into STATE.md's `current_phase_name` on every phase start (`loop-termination-and-baseline-correctness` instead of `Loop-Termination and Baseline Correctness`). `init execute-phase` now prefers the ROADMAP's curated display name (`### Phase N: <Name>`) for `phase_name`, matching the no-disk fallback path that already did this correctly; `phase_slug` is unchanged so branch-name construction is unaffected. The `state begin-phase` override mechanism (#2821/#2736) is untouched. (#3171)
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3281
|
||||
---
|
||||
**Two GSD workflows told agents that a Claude Code `Agent()` spawn blocks until the subagent finishes** — Claude Code backgrounds subagents by default, so `/gsd-execute-phase` could treat a wave as returned when it had not, and `/gsd-debug` lost its session-manager handoff in exactly the way #2196 was filed to fix. The dispatch notes now match this package's own shipped capability matrix, and both debug spawns carry the `run_in_background: false` opt-out they always needed. (#3177)
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
type: Added
|
||||
pr: 3323
|
||||
---
|
||||
**The install manifest now records which runtime and scope wrote it** — a global and a project-local install used to write two `gsd-file-manifest.json` files that neither named their own runtime nor their own scope, so nothing could answer "which GSD surfaces are installed, where". The manifest gains `manifestVersion`, `runtime` and `scope`, and a new read-only Installed Surface Resolver reads both scopes at once. Manifests written by earlier versions are read without error and need no reinstall. (#2872)
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
type: Changed
|
||||
pr: 3318
|
||||
---
|
||||
|
||||
**A percentage is now withheld everywhere its scope is not `COMPLETE`, not just at the sites Phase 3 reached** — closing ADR-3180 §7.6 rule 4 at the two remaining gaps an isolated review caught: `state json`'s `buildStateFrontmatter` no longer hardcodes `SCOPE.COMPLETE` when deriving `progress.percent` (it now threads the real `listMilestonePhaseDirs` scope through `_diskScanCache`, including its prose-fallback path, so a genuinely unreadable `.planning/phases` directory can no longer surface a stale or falsely-earned number there while every other surface withholds), and `roadmap analyze --json` now exposes the scope that actually gates `progress_percent` as its own `progress_scope` field — distinct from the top-level `scope` (heading-windowing identity) — so a consumer can tell *why* `progress_percent` is `null` from the JSON alone instead of seeing `scope: "complete"` next to an unexplained `null`. `state update-progress` also now writes a `[gsd-tools] WARNING:` line to stderr when it silently no-ops on a non-`COMPLETE` scope, so the skip is not visible only to a JSON `reason` field most callers never read. **`state sync` now also withholds**: it no longer hardcodes `SCOPE.COMPLETE` when deriving the percentage it writes into `STATE.md`'s body — a non-`COMPLETE` scope (confirmed reproducible on `TRUNCATED` and `UNSCOPED` fixtures, not just the previously-checked `UNREADABLE` case) skips the `Progress:` write entirely and records a `Progress: skipped — …(#3217)` entry in `changes`, instead of persisting a fabricated percentage that could disagree with the same write's own (already-scoped) frontmatter `progress:` block. `0` under a genuinely `COMPLETE` scope is unaffected and still renders. Tier-2: `progress_percent`, `percent`, and `plan_percent` are `number | null`; `computeProgressPercent` requires a `scope` argument; `roadmap analyze --json` gains a new `progress_scope` field; `state sync --raw`'s `changes` array can now contain a scope-skip entry and correspondingly withhold a `Progress:` body write it would previously have made. (#3217)
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3478
|
||||
---
|
||||
Executor dispatch prompts now state checkpoint gate semantics: gate="blocking" (the default) is auto-approvable in auto-mode, only gate="blocking-human" always surfaces to a human. The phase-level and single-plan-level orchestrators no longer leave room to compose dispatch text that refuses auto-approval, which stalled autonomous runs at ordinary blocking checkpoints.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
type: Changed
|
||||
pr: 3237
|
||||
---
|
||||
**`gsd-plan-checker` now flags same-wave plans that are coupled but don't say so** — two plans in the same wave that share mutable state (a config key, table, migration, env var, singleton) or depend on each other's execution order, with no `depends_on` edge between them, are reported as an advisory Dimension 3 finding. The coupling gets settled at plan time instead of surfacing as an intermittent failure during parallel execution. `docs/AGENTS.md`'s plan-checker entry, which claimed eight verification dimensions and listed eight names matching none of the agent's actual fifteen, is corrected to the real list in the same change. (#1954)
|
||||
@@ -1,9 +0,0 @@
|
||||
---
|
||||
type: Changed
|
||||
pr: 3283
|
||||
---
|
||||
state validate now runs its drift scan for STATE.md files whose phase lives only in frontmatter, instead of silently skipping the scan and reporting a false-clean result (#3162); it also no longer lets a frontmatter status: key shadow the body Status field. Its output gains a scope field (complete/truncated/unscoped/unreadable) reporting whether the check could actually run — valid still means no drift was found, and is not derived from scope. (#3187)
|
||||
|
||||
state complete-phase's idempotency guard now consults frontmatter current_phase (via the same fallback chain as state validate), so a STATE.md whose phase lives only in frontmatter is no longer silently rolled back on a re-run of `state complete-phase --phase N`. It also gains a new refusal path: when the frontmatter cannot be parsed, the command now errors out ("Unable to read STATE.md frontmatter; refusing to run complete-phase to avoid a destructive rollback") instead of guessing. (#3187)
|
||||
|
||||
workstream list/status/progress's per-workstream state projection (status, current_phase, last_activity) now resolves those fields from frontmatter when the body has no corresponding field, instead of reporting them absent — a frontmatter-only STATE.md's workstream inventory output changes accordingly. (#3187)
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
type: Changed
|
||||
pr: 3539
|
||||
---
|
||||
**`effort.routing_tier_defaults` now merges over the built-in tier defaults instead of replacing them** — previously, creating an `effort` block without `routing_tier_defaults` silently disabled the built-in tier ladder (light:low / standard:high / heavy:xhigh), collapsing every non-overridden agent to `high`; one `agent_overrides` entry could reshape 20+ agents you never named. A partial block now fills gaps from the built-ins, and an invalid value falls back to that tier's built-in. (#3531)
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3369
|
||||
---
|
||||
**`gsd-tools validate health` no longer flags `.planning/WINDOWS.md` as an unrecognized file** — the broken-windows ledger that gsd-core's own `windows` command writes is now registered as a canonical `.planning/` artifact. Previously the W019 warning advised archiving or deleting a file that, with `workflow.windows_enforce` on, gates `/gsd-ship`. (#3224)
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
type: Changed
|
||||
pr: 3501
|
||||
---
|
||||
**milestone complete no longer lets a stale STATE.md body line overwrite fresher frontmatter** — it wrote through a path that re-derived frontmatter from the body with no preservation pass, so a stale Stopped-at line silently replaced a newer curated value, exactly as phase complete did before it was fixed. It now runs the same preservation the rest of the write path uses, and reports each field it protected in a new preservation_warnings array instead of staying silent about the divergence. (#3469)
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
type: Changed
|
||||
pr: 3416
|
||||
---
|
||||
**GSD now requires Node 24 or newer** — the `engines.node` floor moves from 22 to 24, and the Node 22 test lane is retired. Node 22 entered Maintenance LTS and this project tracks the Active LTS line; the change is what lets regex escaping delegate to the built-in `RegExp.escape` instead of a hand-rolled implementation. If you are on Node 22, upgrade before updating GSD.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3438
|
||||
---
|
||||
gap-analysis check gap-analysis.plan-post no longer reports prose trailing the requirement ID list as missing requirements. ROADMAP Requirements lines routinely carry locked-decision annotations, ambiguity scores, and prohibition notes after the ID list; passing that value verbatim into --phase-req-ids previously caused every prose word to be reported as an individually-missing requirement, drowning the real coverage signal. Tokens that cannot be requirement IDs (prose, punctuation, dates) are now dropped after range expansion.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3487
|
||||
---
|
||||
state.patch now reports a field as updated only when its post-write on-disk value matches the requested value; fields the write pipeline re-derives away (e.g. current_phase, current_phase_name) are reported as failed instead of phantom updated
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
type: Added
|
||||
pr: 3609
|
||||
---
|
||||
**Opt-in `.git/hooks/pre-commit` guard for `commit_docs`** — `gsd-tools commit-docs-guard enable`/`disable` writes (or removes) a pre-commit hook that shells out to the existing `check-commit` verb, refusing a commit that stages `.planning/` files while `commit_docs` resolves to `false`. Closes the one bypass earlier phases of epic #2292 could not reach: a plain `git add -A && git commit` run by hand or by a script outside GSD's own tooling. Fully opt-in by maintainer narrowing — no install path wires it in by default (regression-locked by `tests/commands.test.cjs`'s E2 row); `enable` refuses rather than overwrites an existing foreign `pre-commit` hook, refuses when `core.hooksPath` would make the written hook inert, and resolves the real hooks directory via `git rev-parse --git-path hooks` so a linked worktree or submodule (where `.git` is a file) is handled correctly rather than assuming a literal `.git/hooks` path. The hook is identified by a stable `# gsd-core:commit-docs-guard` marker line, checked by presence rather than byte-equality. (#3588)
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
type: Added
|
||||
pr: 3568
|
||||
---
|
||||
**installRuntimeArtifacts() now returns the plan it executed** — per kind, per scope, including on the combined OpenCode/Kilo family path that previously returned nothing — so an install's correctness is a value a caller can assert, not something only re-readable from disk afterward. Install IO routes through a new injectable fs seam (`install-fs-adapter.cts`), letting a full install run end-to-end against a fake adapter with no real destination filesystem contact; failures still throw rather than becoming a value, and a best-effort cleanup that fails is now visible in the return instead of silently swallowed. Writes on disk are unchanged. Completes ADR-58's never-landed `cleanup` rollout step. (#2874)
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
type: Added
|
||||
pr: 3253
|
||||
---
|
||||
**Capability skills are now named at the install consent prompt** — installing a third-party capability whose only contribution was skills printed "ships no executable surfaces (declarative only)" and listed nothing, even though each `SKILL.md` body lands verbatim in your agent's instruction context. The pre-install disclosure now names every contributed skill in its own section and states plainly that the bodies are not content-scanned. Values interpolated into the prompt are escaped across every disclosed surface, so a crafted name can no longer forge additional lines of disclosure text. No stored consent is disturbed and no re-consent prompt fires. (#3248)
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3600
|
||||
---
|
||||
**User profile and dev-preferences files are no longer lost when an install or uninstall is interrupted.** These files were held only in memory while GSD deleted and rebuilt the directory containing them, so pressing Ctrl-C — or any crash during the copy — destroyed them permanently. On the main install path that window spanned the entire gsd-core tree rebuild. They are now staged to disk before anything is deleted, and any copy orphaned by an interrupted run is restored automatically on the next install or uninstall. (#1874)
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3434
|
||||
---
|
||||
**`/gsd:code-review-fix <phase> --auto` now commits the converged REVIEW.md alongside REVIEW-FIX.md and reliably commits REVIEW-FIX.md at all** — the --auto re-review loop overwrote REVIEW.md every iteration but the workflow's single docs commit staged only REVIEW-FIX.md, so the committed REVIEW.md stayed at iteration 1 and contradicted the committed REVIEW-FIX.md (and the converged REVIEW.md plus .iterN.md backups survived only as uncommitted working-tree state). Separately, the two inline frontmatter validators exported REVIEW_PATH into a node -e body that reads process.env.FIX_REPORT_PATH, so the status check was always empty and REVIEW-FIX.md was never committed (the user was wrongly told the agent produced malformed output). The validators now export FIX_REPORT_PATH, the --auto commit stages REVIEW.md too, and spent .iterN.md backups are removed on successful convergence (retained on degradation). Non-auto single-pass runs are unchanged. (#3190)
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3558
|
||||
---
|
||||
**Three shell guards that could never fire now do** — the planner's Walking Skeleton mode never activated on any project, phase planning recorded an empty requirement list instead of `TBD`, and completing a milestone with no phase summaries could hang instead of finishing. Each read a value that came back empty on success, so the fallback written to handle it was unreachable. (#3409)
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3596
|
||||
---
|
||||
**Shipped workflow/agent citations resolve again** — 43 backticked `references/<name>.md` cites across 19 shipped files were dead pointers from every install location; all repaired to the canonical `gsd-core/references/<name>.md` form, and a new sweep gate fails the build on any future bare cite across the runtime-loaded trees. (#3576)
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3480
|
||||
---
|
||||
A genuinely milestone-sectioned ROADMAP whose STATE.md asserts a milestone token matching no heading no longer has progress.total_phases clobbered to the on-disk phase-directory count (e.g. 25 -> 4) on every state-mutating command. The stored total is preserved (or the key omitted when nothing is stored), a stderr warning names the unbounded milestone token, and progress.percent stays withheld as before.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3486
|
||||
---
|
||||
Phase-directory collisions in .planning/phases/ (two in-scope dirs normalizing to the same phase number) no longer resolve by filesystem mtime — a checkout-order signal that made progress.total_plans and completed_plans differ across clones of the same commit. The survivor is now chosen deterministically by lexicographic directory name, and the collision is surfaced as a stderr warning naming both directories.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3437
|
||||
---
|
||||
code-review: every phase diff-base derivation now uses the same anchored, POSIX-portable phase-mention grep. Fixes wrong review scope from /gsd:code-review when a phase has no SUMMARY artifacts: the reviewer diff_base and the fallow --changed-since base no longer resolve to old unrelated commits whose messages merely contain the phase digits, and the anchored search now actually matches on macOS (the previous \b word boundary is not POSIX ERE and silently matched nothing there).
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
type: Changed
|
||||
pr: 3264
|
||||
---
|
||||
**Worktree-wave merges now warn when a plan branch committed outside its declared scope** — the `execute-phase` cleanup gauntlet compares each branch's actual committed diff against the `files_modified` the plan declared and reports every path outside it. Advisory only: the merge still proceeds and the exit status is unchanged. (#2596)
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
type: Removed
|
||||
pr: 3272
|
||||
---
|
||||
**The undocumented `runtime.hostBehaviors.reviewerCli` capability field has been removed** — it was superseded by the declared `reviewer` body in 1.9.0 and kept working for one release as a derived alias. A manifest that still sets it contributes no reviewer lane and now reports a non-fatal warning naming the capability, at build time on stderr and at install time through the overlay loader; nothing crashes and no other behavior changes. Every shipped reviewer lane already declares a `reviewer` body, so the roster is unchanged — if you maintain an out-of-tree runtime descriptor that relied on the flag, declare a `reviewer` body to restore the lane. (#2801)
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3400
|
||||
---
|
||||
**`phase add` no longer files new phases inside archived roadmap history** — the insertion point used the file's last horizontal rule, which on a long roadmap sits deep in shipped/archive content, so new phases landed under an unrelated archived phase's heading instead of at the end of the active phase list. Insertion is now scoped to the current milestone. (#3163)
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3589
|
||||
---
|
||||
**Agent isolation guard enforces on multi-runtime machines** — the isolation guard (and Cursor's subagent-start fallback) resolved the project runtime from the host-wide ~/.gsd/defaults.json, which names whichever runtime installed last; on machines with two runtimes this confidently picked the wrong runtime and silently disabled executor worktree policing. Both now read the per-install .gsd-runtime marker above that file. (#3566)
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3634
|
||||
---
|
||||
**`state update-progress` no longer writes two different completion percentages in one call** — the verb printed plan throughput (summaries/plans) to stdout and into the body `Progress:` bar, while the same write independently derived the frontmatter `progress.percent` as the deliberate `min(plan, phase)` cap. Mid-phase, when plan throughput runs ahead of phase completion, STATE.md contradicted itself and `state json` disagreed with the command that had just written it — silently, at exit 0. All surfaces now derive from the single canonical computation, and its reported plan counts come from the same milestone window as the percent, so the verb's own output can no longer disagree with itself. When that computation withholds a percent, the verb withholds too rather than substituting a different metric. The min-cap definition is unchanged. (#3583)
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
type: Changed
|
||||
pr: 3223
|
||||
---
|
||||
**Progress percentages now come from one owner** — every `.planning/` completion percentage the CLI reports is computed by a single shared function instead of six hand-inlined copies, so a rounding or ceiling fix can no longer land on one command and silently miss the others. Reported values are unchanged. (#3180)
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
type: Changed
|
||||
pr: 3633
|
||||
---
|
||||
**Fallow binary resolution now shares the platform seam** — resolving the fallow binary uses the same PATH/PATHEXT logic as every other spawn, so on Windows a `fallow.cmd` shim resolves correctly and an extensionless npm shim is no longer picked up in its place. `node_modules/.bin` is still searched before `PATH`, and the POSIX executable-bit check is unchanged. (#3618)
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3425
|
||||
---
|
||||
**GSD skills no longer override the caller's effort level** (#3151) — invoking `/gsd-plan-phase`, `/gsd-execute-phase`, `/gsd-autonomous`, `/gsd-next`, `/gsd-progress`, or `/gsd-stats` previously set `output_config.effort` to a static value baked into the skill frontmatter; when that differed from the session's effort (which it did ~76% of the time), it invalidated the entire prompt cache at both scope boundaries (skill entry and exit). These skills now run at the session's existing effort level (no `effort:` emitted into SKILL.md). The elevated-effort intent is preserved on the source command files; only the skill-frontmatter emission is dropped. The separate agent-effort surface is unaffected.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3563
|
||||
---
|
||||
**Global OpenCode/Kilo installs no longer pin a tier-default model over your session selection** — a project's `model_profile: "inherit"` was invisible to the install-time resolver on global installs (it probes from the install dir and never reaches the project), so the `balanced` default silently baked e.g. `anthropic/claude-opus-4-8` into the agent frontmatter, which those runtimes use over the live `/model` selection — producing "Model not found" on providers without that exact id. A profile that cannot be verified now bakes no `model:` line, so subagents follow the session model as documented; declare `model_profile` in `~/.gsd/defaults.json` to pin tiers machine-wide. (#3543)
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3594
|
||||
---
|
||||
**`phase remove` no longer corrupts STATE.md after removing an inserted (decimal) phase** — the removed-phase write prepended a second, partially-wrong frontmatter block (and left the phase's ROADMAP heading behind, so total_phases kept counting it); removal now updates STATE.md in place as a single block, drops the heading, and clamps phase counts at zero. (#3572)
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3405
|
||||
---
|
||||
**`validate health --backfill` now works without also passing `--repair`** — previously it silently did nothing unless `--repair` was also set, due to an unreachable internal gate.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3452
|
||||
---
|
||||
gsd-health's STATE/ROADMAP staleness warning (W011) now reads the current phase from the YAML frontmatter format gsd-tools itself writes (current_phase), in addition to the legacy prose, canonical body, and pipe-table forms, and suppresses the warning when the recorded status reports completion in the state writer's own vocabulary (status: completed). The stale-worktree warning (W027) no longer advises unconditional forced removal: its remediation now directs checking for uncommitted work first (git -C <path> status --porcelain), removing non-destructively when clean, with --force presented as an explicit opt-in to discard changes.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3614
|
||||
---
|
||||
**Completing one phase no longer marks the whole milestone done** — `state complete-phase` wrote the body prose `Phase N complete`, and the status normalizer matches `complete` as a substring, so finishing phase 2 of 4 collapsed the milestone-level STATE.md frontmatter to `status: completed` while the very same call correctly recorded `completed_phases: 2` of `total_phases: 4`. Downstream automation that gates on milestone status — auto-advance, archival, ship gating — was told a half-open milestone was finished. Milestone status is now derived from those counters instead of from phase-level prose. (#3578)
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
type: Removed
|
||||
pr: 3564
|
||||
---
|
||||
**Two workflow files that shipped to every runtime but were never loaded are gone** — `discovery-phase.md` and `plan-milestone-gaps.md` had no command, agent, or skill referencing them, and `docs/INVENTORY.md` claimed callers for one that did not exist. A new lint rule now fails the build if any shipped workflow becomes unreachable again. (#3560)
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3607
|
||||
---
|
||||
**`progress`, `stats`, and `query progress` now report a real percentage inside a workstream** — under `--ws`, these commands counted the workstream's own phases and plans but read the milestone window from the project root, which `workstream create` has already migrated away. The scope resolved as unreadable and the percentage was withheld, so a fully-complete workstream reported no progress at all. **`milestone complete` no longer archives every phase directory when its milestone window is unreadable** — it previously fell back to moving everything on disk in that case; it now declines to archive and reports why, leaving the phase directories in place. (#3597)
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
type: Security
|
||||
pr: 3517
|
||||
---
|
||||
**MCP server configs are now explicitly flagged as unconfined in the capability consent prompt** — a capability's MCP servers can legitimately point at commands, args, env, and working directories anywhere on the machine (unlike its hooks, which are confined to the installed bundle), and the consent disclosure now says so plainly for every spawned server instead of leaving the asymmetry unstated. (#3515)
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3591
|
||||
---
|
||||
**`gsd-tools stats` no longer counts phantom phases from inline code** — prose mentioning `### Phase N:` inside an inline code span (e.g. a roadmap explaining its own numbering) inflated phases_total with a never-completing Not-Started row and deflated completion percent; stats now requires the same digit-bearing phase id shape roadmap analyze uses, so the two agree. (#3569)
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
type: Changed
|
||||
pr: 3405
|
||||
---
|
||||
**`validate health` splits two previously-conflated warning codes into their own codes** — W021 now covers only the phase-id-convention mismatch it originally meant; the STATE-vs-ROADMAP milestone-complete mismatch it used to also report moves to the new W026. Likewise W017 now covers only orphan worktrees; the stale-worktree case moves to the new W027.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3488
|
||||
---
|
||||
parseDeferredItems now counts heading-delimited deferred items as ONE entry (a heading plus its descriptive sub-bullets) instead of one per bullet, across flat, container-heading, and mixed-depth files; headless one-bullet-per-item files are unchanged. A bolded `- **Status:** resolved` marker now resolves its item instead of surfacing as a bogus unresolved entry.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3230
|
||||
---
|
||||
**`state.record-session` no longer shrinks your phase count** — a project whose ROADMAP declares more phases than it has directories on disk (phases 5 and 6 planned but not started yet) had `progress.total_phases` silently overwritten with the directory count, converging on the right number only once the last phase directory happened to exist. A flat roadmap carrying an ordinary heading like `## Progress` was being misread as milestone-sectioned. Known limit: two milestone sections carrying no version token, no status marker and not the word "Milestone" are still not detected as sectioning. (#3204)
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
type: Added
|
||||
pr: 3290
|
||||
---
|
||||
**`validate agents` now reports Codex `.toml` model posture, not just presence** — on a `codex` install it flags any agent whose `.toml` pins a GSD tier alias or a `claude-*` id (which Codex rejects with a 400, so the agent never spawns) or carries a `model_reasoning_effort` with no `model`. Previously the check confirmed only that agent files existed, so a stale install from before the passive-model posture reported healthy right up until a typed agent failed to start. Read-only — it names the offending agent and value and never edits your files. Reports `not_codex` and reads nothing on other runtimes. (#3242)
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3455
|
||||
---
|
||||
Parallel phases running in the same working tree no longer corrupt STATE.md silently: state.begin-phase, state.advance-plan and phase.complete now consult a milestone claim (.planning/milestone.lock) keyed by phase + session id, and surface a visible milestone_conflict warning (stderr plus a typed JSON field, and phase.complete's warnings[]) when another live session holds a different phase — instead of silently overwriting the single Current Position slot.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
type: Added
|
||||
pr: 3296
|
||||
---
|
||||
**`effort sync` now repairs stale Codex `.toml` files without a reinstall** — on a `codex` install it strips a `model` pin that Codex rejects (a tier alias or a `claude-*` id) and an orphaned `model_reasoning_effort`, so agents fall back to the always-available session model. An explicit real-Codex pin is left alone. It is a **dry run by default** — pass `--apply` to write — and only the offending lines are removed: line endings, BOM, comments, key order, and any keys you added by hand are preserved byte-for-byte, so a repair is a two-line diff rather than a reformatted file. A file that cannot be parsed is refused and reported, never partially rewritten, and writes are atomic. Pairs with `validate agents`, which detects the same drift. The `claude` path is unchanged. (#3243)
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3562
|
||||
---
|
||||
**`/gsd-map-codebase --fast` now actually runs the fast scan** — the flag routed to "the scan workflow" in prose but named no path any runtime could resolve, and the command loaded only the full four-agent map workflow, so `scan.md` was never read and the single-agent scan was improvised rather than executed. (#3561)
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
type: Changed
|
||||
pr: 3407
|
||||
---
|
||||
**`validate consistency`'s `warnings` are now coded diagnostics** — each entry is a `{code, message, fix, repairable}` object instead of a bare string. Findings that overlap with `validate health` (a phase in ROADMAP.md with no directory on disk, or vice versa) now carry the exact same `W006`/`W007` codes `validate health` already uses for them, so there's one vocabulary for that finding, not two. The four subjects unique to this command (phase/plan numbering gaps, orphan summaries, plans missing `wave` frontmatter) get a new `C001`-`C004` code range.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
type: Changed
|
||||
pr: 2559
|
||||
---
|
||||
**Digit-leading phase names now resolve consistently by bare number** — phases such as "24/7 Autonomy", "80/20 Cleanup", and "12-Factor Refactor" now resolve across every phase verb instead of appearing missing; ambiguous directory collisions now fail loudly with their candidate paths instead of silently selecting the first match. `/gsd` and `/gsd:progress` also stop under-reporting: their verify-failed check shares the same directory selection, so a failed verification in one of these phases is surfaced rather than read as a healthy phase, and phase directories carrying a project-code prefix (`MEM-05-…`) are no longer skipped by that check entirely. The same selection now backs every remaining consumer that had resolved directories on its own, so `phases list`, `phase remove`, `phase next-decimal`, the schema-drift gate, the init-manager overview, `roadmap analyze`, and the milestone-completion and health consistency checks stop reporting these phases as having no directory. `/gsd-health` no longer reports one of these phases as both missing from disk and absent from the roadmap at the same time (W006 + W007), and `phase remove` now refuses — without deleting or renumbering anything — when two directories claim the same bare phase number. `phase remove` also stops writing a phase count one too high into STATE.md when the phase it just deleted was one of these digit-leading directories (#2528).
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3208
|
||||
---
|
||||
**State validation properly detects drift** — Resolved an issue where state validation would silently fail to detect drift because it skipped scanning entirely when the shipped template lacked a specific field.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3446
|
||||
---
|
||||
Phase writes now guard the current milestone's scope. phase add/add-batch/insert reject a description containing a level 1-3 heading with a milestone marker (version token, status marker, or the word Milestone) before anything is written, and the edit-phase workflow captures roadmap milestone-scope (new read-only probe) around its in-place section write and rolls the edit back with an explicit error if the milestone window's scope or phase set changed.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 2728
|
||||
---
|
||||
**`/gsd-quick` and the UAT-diagnosis step no longer abort with a FATAL on a non-Claude runtime that can actually isolate** — both dispatch sites resolved worktree isolation from a hardcoded `RUNTIME != "claude"` test, so every non-Claude host was refused regardless of what it could actually do. They now read the negotiated `dispatch.isolation` capability (#2584), and installs for runtimes that declare worktree support no longer stamp `workflow.use_worktrees` to `false`, which had pre-empted that negotiation. A runtime is judged by what it declares rather than by its name. A host that declares no isolation primitive at all still fails closed when worktrees are explicitly enabled — that FATAL is the fail-closed contract, not the bug — and a host whose isolation model the single-agent sites cannot express degrades to sequential, one agent at a time, on the main working tree.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
type: Added
|
||||
pr: 3540
|
||||
---
|
||||
**`~/.gsd/defaults.json` shadowing is now diagnosed instead of silent** — in any project with a `.planning/config.json`, global model-side keys (`model_profile`, `model_overrides`, `models`, `dynamic_routing`, `runtime`, …) were silently ignored for model resolution; a file named `defaults.json` applied to no real project with no signal. GSD now prints a one-time stderr warning naming the shadowed keys. Resolution precedence is unchanged; global `effort` keeps working via effort sync and never warns. (#3532)
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
type: Removed
|
||||
pr: 3422
|
||||
---
|
||||
**Removed the orphaned `verify-phase` workflow (~40 KB shipped to every runtime, never loaded)** — its still-live verification gates (decision-coverage validation, test-quality audit, infrastructure-phase human-verification scoping) moved to a reference the verifier agent actually loads, so they run again instead of shipping as dead prose; installs are ~40 KB lighter and PRs to the verifier no longer mirror a dead twin to keep lockstep tests green. (#1891)
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
type: Changed
|
||||
pr: 3407
|
||||
---
|
||||
**`state validate`'s `warnings` are now coded diagnostics, and the `drift` field is gone** — each entry is a `{code, severity, message, remedy}` object (seven codes, `S001`-`S007`) naming exactly what STATE.md disagrees with the filesystem about and how to fix it, instead of a bare string. The separate `drift` object every response used to carry is removed entirely; every condition it used to report (a conflicting phase reference, a missing phases directory, a plan-count mismatch, a stale executing status) is now one of the seven coded warnings, so no information is lost, it's just structured. `valid` and `scope` are unchanged.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3436
|
||||
---
|
||||
Reviewer lanes that declare source-grounded evidence are now verified at run time: a review citing zero file:line source evidence is stamped [reviewed-without-source-citations] and down-weighted in the Consensus Summary, instead of silently riding its declared evidence class at full weight (gemini plan-only reviews were measured doing exactly this).
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
type: Changed
|
||||
pr: 3327
|
||||
---
|
||||
**/gsd-progress and /gsd-execute-plan stop counting superseded plans as outstanding work** — seven prompt-layer sites across execute-plan.md, plan-phase.md, plan-review-convergence.md and progress.md counted plans with a raw `ls *-PLAN.md | wc -l`, so a plan marked `status: superseded` was still counted as outstanding, a phase on the nested plans/ layout (#3139) reported zero plans it actually had, and loosely-named plan files were missed entirely. Every site now calls `phase find`, which gains three additive fields — `plan_count`/`summary_count` (live, superseded excluded — 'how much is left') and `plan_count_all` (physical, every plan on disk — 'what did the planner write') — so what a workflow shows and what `phase find` reports for the same phase are now the same number. This also fixes a dead route: progress.md's Route 0 resume-incomplete-phase check read `.plans`/`.summaries` arrays that its producer, roadmap.analyze, never emitted (it emits plan_count/summary_count scalars), so both counts were always 0 and the check had never fired at all — it now fires correctly. **This is a behavior change you'll notice:** plan/summary counts shown by these workflows will move — toward being correct. (#3218)
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
type: Changed
|
||||
pr: 3405
|
||||
---
|
||||
**`validate health --repair` no longer resets config.json or regenerates STATE.md automatically** — these two repairs are destructive (they lose custom settings or session history), so they're now reported with their fix described but never auto-applied; run the suggested command yourself to apply them.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3476
|
||||
---
|
||||
Managed /gsd:debug auto-resume no longer stalls after an answered checkpoint: the respawned session manager now receives the recorded next action and checkpoint status, plus the disposition that prior checkpoints were already answered, so the debug loop proceeds on the persisted next step instead of stopping behind the no-progress guard.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3570
|
||||
---
|
||||
**Concurrent Claude Code sessions no longer share one active-workstream pointer** — Claude Code exports its session id as `CLAUDE_CODE_SESSION_ID`, but the session-identity probe only listened for `CLAUDE_SESSION_ID`, so session-scoped workstream isolation never engaged on Claude Code: every session in a working tree resolved through the single shared `.planning/active-workstream` pointer, and a `STATE.md` update belonging to one workstream could be written silently into another's directory. The probe now accepts `CLAUDE_CODE_SESSION_ID` (no other key's precedence changed); concurrent sessions each keep their own session-scoped pointer again. (#3557)
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3453
|
||||
---
|
||||
plan-phase: a completed --gaps planning run's Next Up handoff now recommends /gsd:execute-phase <N> --gaps-only (matching the gap-closure scope just planned) instead of the whole-phase /gsd:execute-phase <N>. Standard and --reviews runs are unchanged.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3590
|
||||
---
|
||||
**GSD no longer commits `.planning/` files you told it to ignore** — several workflow steps staged planning artifacts with raw `git add`, bypassing the `commit_docs` setting and the `.gitignore` auto-detect entirely, so planning docs reached shared history anyway. (#3585)
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
type: Added
|
||||
pr: 3649
|
||||
---
|
||||
**`/gsd-review` now records which model each reviewer actually used** — REVIEWS.md frontmatter gains `models:` and `model_sources:`, so an unpinned lane's verdict is no longer attributable to an unknown model. (#2295)
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3393
|
||||
---
|
||||
**Global Claude installs load skill content correctly again** — the installer rewrote `@~/.claude/` file references to `@$HOME/.claude/`, which Claude Code does not expand, silently leaving every GSD skill with an empty execution_context (the model got scaffolding but never the workflow body). @-references now stay on the tilde form Claude resolves. (#3133)
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
type: Changed
|
||||
pr: 3443
|
||||
---
|
||||
**Gap-closure planning no longer documents a completion marker nothing reads** — the planner emitted `## GAP CLOSURE PLANS CREATED` but no workflow had a dispatch branch for it, so completion was always detected via the `gap_closure: true` fix-plan artifacts anyway; the dead marker is retired and the artifact route (verify-work `--gaps` spawn → plans → `execute-phase --gaps-only`) is now the documented contract. (#3440)
|
||||
<!-- docs-exempt: the retirement and its replacement contract live in gsd-core/references/planner-guidance.md, the runtime-loaded reference for this seam; no docs/ page documents the marker today (verified) and none is owed for its removal -->
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
type: Changed
|
||||
pr: 3222
|
||||
---
|
||||
**Progress, stats, and phase listings now stay within the current milestone.** `progress`, `stats`, and `phases list` no longer count backlog (`999.*`) or pre-milestone (`0-*`) directories as current-milestone phases, and `phases clear` / `milestone complete` no longer delete or archive those directories. `phases list --phase` and `--include-archived` are unaffected, since they intentionally look up or list beyond the current milestone. (#3185)
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3285
|
||||
---
|
||||
**Twenty-five folded test suites no longer run twice on every CI lane** — three consolidated install suites each carried a verbatim second copy of a contiguous run of folded regression blocks (~5,800 lines), left behind by a stale-base re-application during the test-consolidation epic. Every duplicated block registered and passed twice, so nothing reported it, and a contributor fixing one of those regressions could edit one copy and leave the other asserting the old behavior with the suite still green. The duplicates are deleted, and a new `local/no-duplicate-fold-marker` ESLint rule fails the build if a folded suite ever appears twice in one host file again. (#3271)
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3555
|
||||
---
|
||||
**Items left unresolved when a milestone closes are no longer invisible to every later audit** — `query audit-open`'s four phase-scoped scanners read only `.planning/phases/`, so once a milestone closed and its phase directories moved to `.planning/milestones/vX.Y-phases/`, any UAT gap, verification gap, context question or deferred item still open at that moment vanished from the pre-close audit permanently. In a fully-archived project the scanners returned nothing at all, which is indistinguishable from a clean tree — and because the audit sums every category into one `has_open_items` boolean, that could report a clean close it had not verified. All four now scan the archived milestone directories as well, and each item says which milestone it came from. (#3458)
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3599
|
||||
---
|
||||
**`roadmap` tools recognize table-style phase listings** — a ROADMAP whose current-milestone phases are declared as markdown table rows (`| 20 | … |`) reported phase_count: 0 and found: false across roadmap.analyze, roadmap.get-phase, init.phase-op, and the milestone filter; all four surfaces now resolve table-declared phases (progress tables and fenced examples excluded). (#3577)
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3616
|
||||
---
|
||||
**A terminal session now follows the workstream your repo says is active** — with `.planning/active-workstream` naming a workstream, any invocation that had never run `workstream use` silently resolved the flat `.planning/` tree instead: it misreported milestone, phase and progress on reads, and wrote to the superseded flat `STATE.md`. Because the stale tree is well-formed, nothing warned, and the documented workaround was to prepend `GSD_WORKSTREAM=` or `--ws` to every command. A session that has never set its own pointer now inherits the repo marker. Session isolation is unchanged — a session that owns a pointer is never repointed — and the two workstream-mode fail-safe guards now say whether a marker exists but failed to resolve, instead of claiming none is set. Note that clearing a session's pointer returns it to inheriting the marker rather than forcing flat mode. (#3579)
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3363
|
||||
---
|
||||
**`branching_strategy: "phase"`/`"milestone"` once again lands the first strategy-scoped commit on the strategy branch** — `gsd-tools query commit` now creates *and* switches to a brand-new phase/milestone branch (restoring the #1278 intent), instead of creating it without switching and leaving the commit on the base branch. The #3079 protection is preserved: an *already-existing* strategy branch is still never silently switched to (it warns and commits on the current branch). The first fresh create is now logged to stderr instead of being silent, and the misleading "already exists" warning no longer recurs on every subsequent commit once HEAD is on the strategy branch. (#3207)
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3535
|
||||
---
|
||||
**A file belonging to another phase no longer blocks the phase you are in** — sixteen scans (plus the single-pick fallback inside `resolveVerificationFile`) collected verification and UAT artifacts from a phase directory without checking they belonged to that phase, so a stray or copied file such as `04-VERIFICATION.md` sitting in phase 03's directory contributed its status to phase 03. The worst case was not cosmetic: a stray file carrying `gaps_found` or `human_needed` pushed a blocker that flipped the UAT-passed predicate to false, and `transition` gates on that — so a leftover file could refuse to let a phase advance. Some scans could also claim the opposite, reporting verification passed on the strength of a file the phase does not own. All of them now check phase membership. Where a directory's own phase cannot be determined from its name, every file is still included, so no scan silently loses a phase's real blockers; where it can, a phase holding only another phase's report now correctly reports having none of its own rather than adopting it. (#3511)
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
type: Changed
|
||||
pr: 3276
|
||||
---
|
||||
**Codex agents now inherit the session model instead of getting a pinned per-tier model** — if you install for `codex` with a `runtime` set and any `model_profile` other than `inherit`, GSD no longer writes a `model` (or `model_reasoning_effort`) line into `~/.codex/agents/<agent>.toml`. This fixes typed agents failing to spawn with `400 invalid_request_error: "The 'sonnet' model is not supported when using Codex with a ChatGPT account"`, which degraded the whole plan/execute flow to a generic-agent fallback. **To keep pinning a model, set an explicit real-Codex id in `model_overrides`** (e.g. `{"model_overrides": {"gsd-planner": "gpt-5.6-sol"}}`) — that path is unchanged. The installer prints a one-time notice when it drops a pin. Codex-only; all other runtimes are untouched. (#3241)
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3499
|
||||
---
|
||||
**The build no longer requires Node 24: `escapeRegex` falls back to an in-file metachar escape when `RegExp.escape` is absent** (#3498) — `RegExp.escape` is ES2026 (Node 24+), and `src/pattern.cts` called it unconditionally, so `npm run build` itself failed on Node 22 (`gen-loop-host-contract` consumes the module), breaking the gsd-test `linux-node22` verification lane. The seam now prefers the built-in when present and falls back otherwise — still the single owner of escaping (#3212 invariant preserved). Behavior on Node 24+ is unchanged; match behavior below Node 24 is verified equivalent by regression tests that neuter `RegExp.escape` in a child process.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3387
|
||||
---
|
||||
**Full-line `#` comments in `.planning/STATE.md` (and every frontmatter surface) now survive a mutating write** — `parseYamlRegion` carries column-0 comments through to `reconstructFrontmatter` via a Symbol-keyed channel, and `syncStateFrontmatter` propagates that channel across its fresh-rebuild of the frontmatter object, so a comment like `# NOTE: current_phase is hand-maintained` is no longer silently destroyed on the next `state` verb. Comment-less frontmatter is unchanged; data identity (keys/values/arrays/nested) is preserved alongside the comments. (#3257)
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3459
|
||||
---
|
||||
A plan SUMMARY whose frontmatter declares status: blocked is no longer counted as a completed plan. Previously both the progress counters written to STATE.md (state planned-phase / begin-phase / record-session) and the phase-plan-index read path paired PLAN and SUMMARY files by filename existence alone, so a blocked plan counted as done and was omitted from the incomplete list. Filename existence remains the fallback when a SUMMARY carries no status field, and status: halted summaries still count as completion records (a designed stop), so untouched projects are unaffected.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3419
|
||||
---
|
||||
**Auto-chain phase completion now runs the same post-processing as a normal transition** (#1526) — completing a phase via `/gsd:execute-phase` (auto-chain) previously skipped the transition workflow's graduation scan, session-continuity, project-reference, accumulated-context, and current-position updates, leaving project state different from a normal transition. execute-phase now delegates post-completion processing to the transition workflow (post-completion mode: skips re-verify + re-running `phase.complete` to avoid a double-write). Identity/standalone transition behavior is unchanged.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
type: Changed
|
||||
pr: 3250
|
||||
---
|
||||
**`gsd-verifier` now says *why* a verified truth holds, not just that it does** — a truth that reaches `✓ VERIFIED` is additionally classified against three incidental-reliance patterns (an undeclared precondition, an ordering or side effect nothing enforces, a truth that is only true under the test fixture) and, when one matches, is reported as `✓ VERIFIED (coincidental-reliance)` with an entry in the new `coincidental_reliance_items` frontmatter list naming what to harden. Purely advisory: the base `✓ VERIFIED` token is unchanged, the truth still counts toward the score, the overall `status` is unaffected, and no human-verification item is emitted — a passing phase still passes. Only a consumer matching the truth-row verdict cell for exact equality (rather than as a substring) needs to tolerate the suffix. Two limits stated up front: the check is endogenous, and so measurably weaker than the exogenous `backstop` tag `gsd-core/references/honest-verifier.md` routes on — advisory status is the consequence, and its precision is unmeasured; and `gsd-core/workflows/verify-phase.md` is not edited, receiving the rule through its eager import of the verification-report template rather than a second inline copy, because it sits 29 bytes under its size hard cap. (#1955)
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
type: Changed
|
||||
pr: 3278
|
||||
---
|
||||
**Install scope is now resolved once, as a value** — the installer and the modules downstream of it no longer each re-derive whether an install is global or local from a bare string. One module owns the scope axis and reports its config home, its per-scope settings file, and whether it requires a consent record. No behavior changes for any install. (#2870)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user