chore: promote CHANGELOG for v1.14.0
This commit is contained in:
112
CHANGELOG.md
112
CHANGELOG.md
@@ -6,6 +6,118 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [1.14.0] - 2026-09-14
|
||||
|
||||
### Added
|
||||
|
||||
- **The phase-directory membership seam threads the phase ID convention through the completion chain** — the #3511 seam (`isPhaseArtifact` / `scopeToPhase`) now takes the same optional convention every other read-path helper does, and the completion chain threads it: `state json` / `state sync`'s completed-phase counting, the planning snapshot, roadmap analysis, `state validate`'s drift scan, the verification-report resolver, and `phase complete`'s actual completion gate. A bracket directory therefore scopes its listing by its real phase token instead of the include-everything ambiguity fail-safe, so a cross-phase stray (`01-VERIFICATION.md` misfiled into phase 03's directory) can no longer supply the pass/fail verdict for a bracket phase — the same protection #3511 already gives legacy directories, **on the call sites this PR threads**.
|
||||
|
||||
Call sites that do not yet resolve a convention keep the documented include-everything fail-safe on bracket directories, and this PR changes nothing for them: the aggregate scans (`uat`, `audit`, `init`'s projections, `gap-checker`, `phase-locator`); **`phase complete`'s advisory pre-scan** (`cmdPhaseComplete`, `src/phase.cts`), whose UAT and VERIFICATION warning sweeps still call the seam convention-lessly and can therefore surface a spurious warning for a cross-phase stray, although that scan cannot pass or block completion; and **the workstream inventory's per-phase completion projection** (`src/workstream-inventory.cts`), which calls the now-convention-aware `isPhaseComplete` without resolving a convention to pass it and can therefore still project a bracket phase complete or incomplete from a cross-phase stray. Threading those readers is follow-up-slice work alongside the epic's other convention-less readers. A project on any convention other than `"bracket"` is unaffected. (#4142) (#3644)
|
||||
- **`workflow.compact_content` now actually does something: `plan-phase` is the first workflow split into a spine + detail file.** With the key off (default), nothing changes — the spine reads the deferred elaboration back in before continuing, so the instruction set is identical to today. With it on, that read is skipped and the orchestrator runs on the terser spine alone, which is complete enough to plan a phase correctly on its own. The check and the resolution rule live in one shared reference (`gsd-core/references/compact-content-gate.md`) that future splits reference instead of restating. (#4402) (#4471)
|
||||
- **A new offline benchmark reports the token savings from compact-content splits** — `npm run benchmark:compact-content` measures, per registered `workflow.compact_content` spine/detail split, the token count with and without the split active using a pinned tokenizer, and prints the reduction against a committed baseline without ever failing CI. (#4404) (#4502)
|
||||
- **Broken-windows ledger entries now record which milestone they belong to** — `windows append` stamps a new `milestone` field from the workstream's resolved milestone version. Phase numbers are unique only within one active phases directory, so two milestones routinely produced entries sharing the same phase number with nothing to distinguish them; `/gsd-ship`'s open-count gate could be silently blocked by another, already-shipped milestone's entries. Absence (an entry recorded before this field existed) reads as null — existing ledgers keep working with no migration. (#4583)
|
||||
- **Five more workflow spines split into a terser form under `workflow.compact_content`** — `execute-phase`, `docs-update`, `new-project`, `verify-work`, and `complete-milestone` join `plan-phase` (#4402), bringing the total to six, each moving genuinely optional or rare content (interactive-mode flows, off-by-default features, gap-closure loops, cross-AI delegation, branch-merge mechanics) into a deferred `<workflow>/detail/*.md` elaboration read only when the key is off; several pre-existing structural drift guards pin exact wording in specific spine steps (crash-resume detection, checkpoint auto-approval, learnings extraction, revision-conflict handling), so those sections keep their full text in the spine rather than deferring it. The refreshed benchmark reports a 15.66% aggregate token reduction across the six splits. The remaining eagerly-included workflows were reviewed and recorded as not worth splitting, with reasons, in `docs/PARTITION-RULES.md`. (#4405) (#4536)
|
||||
- **Compact content mode is now discoverable, not just settable.** `/gsd-new-project` asks about it at init time and `/gsd-settings`/`/gsd-config` toggle it on an already-initialized project, closing out the #4139 compact-content epic. (#4408) (#4587)
|
||||
- **`workflow.compact_content` now also covers lazily-read workflow fragments and planning-artifact templates.** With the key on, `help --full`'s reference doc and generated `SUMMARY.md`/`USER-SETUP.md` templates resolve to a terser `.compact.md` sibling at the point of their existing `Read` — two independent, complete files, picked per the same shared gate Phase 5 introduced (`gsd-core/references/compact-content-gate.md`). With the key off (default), nothing changes. (#4540)
|
||||
- **`workflow.compact_content` splits now have a real CI guard.** Any workflow spine + `detail/*.md` split is enforced forever: completeness once at split time, disjointness and registration on every PR, and protected content (guardrails, output-format contracts, few-shot examples, security language, machine-parsed headings) that can never leave the spine, moved or not. Ordinary content moves between spine and detail need a `Boundary-Move-Declared` commit trailer naming the spine, mirroring ADR-3942's emitted-drift-ack trailers. The partition rule and the protected-content list live in one place, `docs/PARTITION-RULES.md`. (#4403) (#4497)
|
||||
- **`/gsd:code-review` can now optionally corroborate its internal review with registered external reviewer lanes** — new roster-derived flags dispatch a bounded, read-only source review through each selected lane; findings are re-verified against real source and folded into the existing `REVIEW.md`. Bare `/gsd:code-review` (no flag) is unchanged. (#4323)
|
||||
- **check decision-coverage-plan accepts --context <path>** — same convention as sibling check verbs. (#4130) (#4374)
|
||||
- **`workflow.compact_content` is now a registered, validated, documented project config key.** It resolves to `false` when absent and is readable via `config-get`; no content branches on it yet. (#4401) (#4441)
|
||||
- **Compact agent-persona payloads for non-Claude runtime dispatch, selected by `workflow.compact_content`.** When the key is on, the AGENTS-native persona fallback (kimi-code, opencode, kilo, and similar runtimes without named-subagent dispatch) now serves a token-minimized `.compact.md` variant of the agent's persona instead of the full file, chosen by the same CLI seam (`gsd_run query agent-skills`) that already resolves this content in code rather than prose. An agent with no compact variant registered falls back to the canonical persona and discloses the fallback in the payload itself, so nothing is ever served silently or left empty. (#4407) (#4553)
|
||||
|
||||
### Changed
|
||||
|
||||
- **Planning guidance now prefers the first sufficient implementation option** — existing project behavior, standard-library or native platform capability, installed dependencies, and only then minimum new implementation, without reducing required scope or verification. (#4118)
|
||||
- **21 GSD skills now declare `Grep` in `allowed-tools`** — cleanup, complete-milestone, config, debug, graphify, health, mempalace-capture, mempalace-recall, new-milestone, new-project, next, pause-work, phase, pr-branch, resume-work, review-backlog, settings, stats, thread, workspace, and workstreams can now use the dedicated structured-search tool instead of shelling out through Bash grep. (#4397)
|
||||
- **Context-monitor WARNING/CRITICAL fire-points are now readable from `.planning/config.json`** — `hooks.context_warning_threshold` (default 35) and `hooks.context_critical_threshold` (default 25) move the two rungs per project, so a tuned fire-point survives an update instead of being re-staged away with the managed hook file. Absent keys resolve to today's 35/25, so existing projects are unchanged. An unusable value falls back per key; both revert to their defaults only when the resolved pair violates `critical < warning`. The keys are root-project settings — the hook reads `<cwd>/.planning/config.json` only, and they are read by that hook and nothing else, so on a runtime where it is not installed (Codex, per #2586) both keys are stored and validated but inert. `config-set` refuses the two endpoints that can never take effect — a warning of 0 and a critical of 100 — because `critical < warning` has no legal partner for either, and an absent key now reports the shipped default (35/25) instead of "Key not found". (#4285) (#4366)
|
||||
- **The path-containment predicate is now a single exported seam** — `security.cjs` no longer exports `validatePath`. Containment is decided in exactly one place and resolved two ways: `assertWithinRoot` (throws) and `tryWithinRoot` (returns null) resolve symlinks, while `assertWithinRootLexical` and `tryWithinRootLexical` use string resolution alone and never touch the filesystem, for the few callers that must preserve a symlink rather than resolve it or that validate a destination before it exists. `requireSafePath` is preserved as an alias of the throwing form. All of them return a branded `ContainedPath` so a validated path cannot be silently swapped for an unvalidated one. The per-call-site `{ allowAbsolute: true }` flag is replaced by the named `PathAcceptance` policy, which states what it actually permits: an absolute path outside the root was always rejected and still is. The traversal rejection text `Path escapes allowed directory: <resolved> is outside <base>` is preserved verbatim, and no command changes what it accepts or rejects. Three rejection MESSAGES are reworded, none of which now reveals a host path it previously hid: `state.cts`'s `<label> path rejected: …` becomes `<label> path validation failed: …`, and the sub-repo and agent-skills warnings name the condition instead of echoing the predicate's error string. (#4653) (#4672)
|
||||
- **Pending todos now render as one bounded bullet per todo in STATE.md.** Each capture used to append to a single run-on sentence in "### Pending Todos", growing unbounded and wrecking `git diff` readability; captures now produce one bullet per todo, capped at 240 characters, with a fail-safe refresh that leaves the section untouched on a malformed lookup. (#2618) (#4384)
|
||||
- **Codex no longer installs a context-monitor hook that could never fire.** `gsd-context-monitor.js` read a remaining-context bridge file only Claude Code's statusline hook writes, so every one of its Codex hook-event registrations was a guaranteed silent no-op. Fresh Codex installs no longer copy or register it; a reinstall over an older install now removes the stale registrations and the orphaned script. Agent-facing context warnings and phase/lifecycle display are documented as unsupported on Codex until a real metrics producer exists for that runtime. (#2586) (#4367)
|
||||
- **The codebase drift check now reports real drift** instead of flagging every file in the repository on every run. Mapping a codebase records the point it was mapped at, so the check compares against that point, and it skips with a reason when no such record exists. (#4124)
|
||||
- **Size-cap checks expose pressure before the hard limit** — workflow and agent suites report every capped file's remaining headroom and flag files past the 95% reserved margin. (#4261) (#4418)
|
||||
- **Every path-containment check in the tree now routes through one predicate, enforced by lint** — around two dozen hand-rolled containment comparisons were still scattered across installers, capability lifecycle, research storage and command routing; each now takes its decision from the canonical predicate while keeping its own behavior. A new lint rule bans the hand-rolled shape and a discarded containment answer, so a reintroduced copy fails the build. Two rejection messages in capability module loading collapse into one, and a missing module now reports as a module-resolution failure rather than a file-not-found. (#4654) (#4674)
|
||||
|
||||
### Removed
|
||||
|
||||
- **Removed 8 unreferenced planning-artifact scaffolding templates under `gsd-core/templates/`** (`claude-md.md`, four of the seven `codebase/` brownfield-mapping templates — `concerns.md`, `conventions.md`, `integrations.md`, `structure.md` — plus `debug-subagent-prompt.md` and `discovery.md`) — confirmed, file by file, to have zero references anywhere in workflow prose, agent/command definitions, compiled source, or tests, and (for the deleted set specifically) no surviving basename reference anywhere in the tree either. `codebase/architecture.md`, `codebase/stack.md`, and `continue-here.md` were kept: their basenames collide with unrelated, genuinely live concepts documented across many files (a user's generated `.planning/codebase/*.md` output, and the real `.continue-here.md` pause-work artifact), so deleting them would have required rewording numerous translated docs to describe something else entirely. (#4540)
|
||||
|
||||
### Fixed
|
||||
|
||||
- **`gsd-tools state begin-phase` without `--phase` now exits non-zero and writes nothing** — previously a missing, empty, or flag-shaped phase argument was silently accepted and wrote a null-phase STATE.md (removing `current_phase`/`current_phase_name` from frontmatter and serialising the literal `Phase null` into three body locations), and took a milestone claim for the phase "null". (#4138) (#4380)
|
||||
- **`/gsd-update --reapply` no longer re-grafts customizations that upstream already adopted** — the documented `Incorporated` per-file status is now computed by a deterministic pre-flight classifier (hash-validated pristine baseline + every significant user-added line already present verbatim in the new version), so superseded patches are reported as already upstream instead of being silently re-applied on every future update cycle. (#4136) (#4373)
|
||||
- **The decision-coverage gate now reads phase-prefixed decision IDs** — a CONTEXT.md whose decisions use D4-01-style IDs (a digit-run phase prefix) no longer reports could-not-parse for the whole file; its decisions are counted and coverage-checked like any other, and a typo'd prefix (D4x-01) still fails loud. (#4130) (#4357)
|
||||
- **`/gsd:update` no longer misreports a global install as LOCAL when the shell sits in $HOME** — running the update from a home-directory shell drove the installer's --local arm (settings.local.json + the #338 relocation) against a global install; the preferred-config-dir fast path now applies the same same-path dedup the rest of the detection cascade always has. (#4197) (#4413)
|
||||
- **A progress bar is full only at 100%** — every bar-drawing surface (`progress` in table and bar format, `stats`, `state update-progress`, the STATE.md progress line written by `state sync`, and the gsd2 import writer) now draws through one render kernel, `renderProgressBar`, beside the completion-ratio kernel in `phase-lifecycle`. The six inline copies of `Math.round((percent / 100) * width)` each rounded to a full bar before the percent reached 100: at the 10-cell width every percent from 95 up drew `[██████████]`, at the 20-cell width every percent from 98 up, so a project at 19/20 plans was visually indistinguishable from a shipped one beside a number that said otherwise. Below 100 the fill is now held one cell short; only those percents move (95-99 at width 10, 98-99 at width 20), every other value in 0-100 renders exactly as before. A null or non-finite percent still renders an empty bar, and an out-of-range percent is clamped instead of throwing `RangeError` from `'░'.repeat` as the inline form did at 120%. (#4473)
|
||||
- **`roadmap update-plan-progress` no longer false-greens on checklist-form ROADMAPs** — a phase whose entry is a `- [ ] **Phase N: …**` checklist bullet with no writable Progress-table row or detail section now declines with `updated: false` and a typed `missing_phase_details` reason, leaving ROADMAP.md byte-identical, instead of reporting success off an unrelated checkbox mark while the phase row stayed untouched and blank lines were injected mid-sentence in other phases' entries. (#4247) (#4468)
|
||||
- **`validate.health` no longer flags `.planning/PATTERNS.md` as an unrecognized file.** The graduation workflow (`/gsd-extract-learnings`) writes this file on gsd-core's own instruction, but the artifact registry was never updated to recognize it -- every repo that had run the graduation scan sat permanently at `status: degraded`. (#4282) (#4618)
|
||||
- **`state begin-phase` no longer rewrites prose that merely quotes a bold field label** — a `**Status:**` (or any served field label) quoted mid-sentence inside prose captured the field rewrite and silently destroyed the rest of its line; the bold form is now anchored to line start, so only the real field updates. Frontmatter round-trip through begin-phase (custom keys, progress subkeys, milestone identity without a ROADMAP) is pinned with regression tests. (#4243) (#4453)
|
||||
- **The reapply verifier now headlines its baseline coverage instead of reading as fully verified when most files were skipped** — after a multi-version update, /gsd-update --reapply reports 'Baseline coverage: N of M file(s)' in the verifier summary, the reapply output, and the installer's update log; on git-managed config dirs the verifier additionally recovers pristine baselines from history by recorded hash, so files upstream heavily changed are diff-verified instead of skipped; an opt-in --min-baseline-coverage <0..1> flag lets cautious operators fail the gate (exit 3) below a coverage threshold. (#4135) (#4376)
|
||||
- **`/gsd-pr-branch` no longer silently drops a planning-only commit that mixes a structural `.planning/` path (STATE.md, ROADMAP.md, etc.) with a transient or other planning path** — such a commit matched none of the classification's four arms and was excluded, which could break `STATE.md`'s per-commit revision chain in default mode. A fifth arm now covers this shape and includes it, same as a mixed code+planning commit. (#4447) (#4537)
|
||||
- **`milestone_name` no longer corrupts to ")" for a first-milestone ROADMAP whose H1 puts the version after the name** — a punctuation-only heading remainder (e.g. the closing paren of `# Roadmap: Project — Name (v1.13)`) is refused as a name, so `init.*` output reports `null` instead of garbage, and the roadmapper agent now templates the canonical version-free H1. (#4134) (#4358)
|
||||
- **The catastrophic-shrink write-guard now protects workstream- and project-scoped planning files** — `hooks/gsd-write-guard.js`'s curated-file patterns only matched root-level `.planning/STATE.md`/`ROADMAP.md`/milestone archives, so a large-shrink Write to a workstream-scoped (`.planning/[<project>/]workstreams/<ws>/...`) or project-only-scoped (`.planning/<project>/...`) copy of the same files was never blocked. Found while fixing #4455's workstream-scoped path resolution, which makes such writes reachable via `/gsd-complete-milestone`'s own instructions. (#4542)
|
||||
- **`restore-custom-files` no longer re-offers a file that is already byte-identical to its backup** — such an entry is reported as `already_present`, excluded from `eligible_count` and `restored_count`, and never rewritten under `--apply`, so the update workflow's restore prompt settles after one successful restore instead of asking again on every update. (#4558) (#4599)
|
||||
- **A working executor is no longer interrupted or told to "Finalize immediately"** — execute-phase's stall threshold now measures time without progress rather than total runtime, an executor with commits and recent activity is left alone until its SUMMARY lands, and a missing local test/build process no longer counts as idleness. (#4218) (#4391)
|
||||
- **`roadmap analyze` no longer mints a phantom phase from a mid-line mention** — a sentence, blockquote, or inline-code-span reference to a `### Phase N:`-shaped heading anywhere in the ROADMAP was previously counted as a real phase, inflating `phase_count` and able to collide on a phase number with a real heading nearby. The phase-heading extraction is now anchored to line start, matching this repo's other heading parsers. (#4578)
|
||||
- **`query verification.status` now resolves a bare `VERIFICATION.md` like `verification.resolve-file` does** — a phase whose only verification report was a bare `VERIFICATION.md` was reported as `missing` and told to re-run `/gsd-execute-phase` even though the report said `status: passed` and `verification.resolve-file` resolved it in the same directory. (#4187) (#4388)
|
||||
- **A merged-and-deleted phase branch is no longer resurrected by a post-merge phase-scoped commit** — `query commit` re-created the deleted branch and moved HEAD onto it (the #3079 hijack reopened by #3363); the create arm now requires a genuinely new phase (no committed history touching the phase directory, caller on the resolved base branch) and otherwise commits in place with a disclosed warning. and refusing to recreate an absent phase branch when the caller is off the resolved base branch. The milestone arm keeps its existence-only guard in this fix (its state-3 exposure is unchanged and named at the guard site) but now also requires the base branch before creating. (#4055) (#4694)
|
||||
- **`git commit` with a large `-m` message is no longer slow** — the commit-message validator hook computed the text after the message with a pattern match that is quadratic in the message length, on the path every commit takes and before the pass/fail branch, so conforming and non-conforming messages cost the same: 10.0s at a 64KB message, 30.2s at 112KB. Claude Code blocks on PreToolUse hooks, so that was dead time in front of the user. The suffix is now derived by arithmetic from the match already located on the preceding line — byte-identical output, flat 0.2s at every size measured. (#4492) (#4539)
|
||||
- **`state update` no longer reports a same-value write as a missing field** — updating `Last Activity` (or any other body-sourced frontmatter key) to the value it already holds reported `updated: false` with a "not found in STATE.md" message telling the caller to add a line that was already there at the correct value. Any day `gsd-ship` runs before `gsd-extract-learnings`, both write today's date to the same field, so the second call always hit this. (#4488) (#4581)
|
||||
- **A three-segment (or deeper) phase id no longer breaks phase-number validation or extraction** — code-review, code-review-fix, the gsd-code-fixer agent (both variants), execute-plan's plan-filename parsing, and plan-phase's --research-phase flag all re-derived a two-segment-max regex; a nested phase like 23.1.2 was rejected outright or silently truncated to the wrong id. All six sites now accept an arbitrary number of dotted segments, matching the canonical grammar. (#4568) (#4646)
|
||||
- **`commit --files` now reports which explicitly-named paths were skipped** — a path named in `--files` that no longer exists on disk was silently dropped from the commit (guarding against staging an unwanted deletion), but the result reported unqualified success with no way to tell a partial commit from a complete one. The result now includes `skipped_files` naming any dropped path, present only when something was actually skipped. (#4454) (#4538)
|
||||
- **`/gsd-new-project`'s sub-repo detection now finds linked git worktrees** — a linked worktree's `.git` is a file rather than a directory, and the previous detection predicate silently excluded it from the multi-repo prompt. (#4548)
|
||||
- **Secret-free `.env` templates with a qualifier are readable again** — the read guard compared everything after `.env.` as one token against a set of final extensions, so a committed template like `.env.local.example` was refused and the reader was pushed toward the real secret file it exists to replace. Classification now keys on the final extension. (#4580) (#4659)
|
||||
- **A Kimi surface change no longer corrupts the installed agent tree** — `applySurface` now materializes the `kimi-agents` kind recursively (`gsd.yaml`, `gsd.md`, `subagents/gsd-*.{yaml,md}`) instead of writing `gsdgsd.md` and dropping the YAML and subagents, prunes only GSD-owned Kimi files, and stages with the same context a fresh install uses. (#4211) (#4371)
|
||||
- **`progress.completed_phases` and `percent` are now derived from the ROADMAP's own milestone Complete rows and never move downward on a state write** — previously every default-resync verb (`state record-session`, `add-decision`, `begin-phase`, `phase complete` itself) recomputed the counter from a disk scan that drops any completed phase whose verification reads `stale` (a summary committed or edited after it) or is missing, so the stored value was silently reverted to the under-count on every write and hand-corrections never survived. The scan now floors the numerator at the milestone-scoped ROADMAP Complete-row count (same gate and scope as the denominator), the write path enforces the schema-declared `progress-ratchet` (totals correct both directions, completed counters up-only, percent recomputed from the surviving counters), and `phase complete` passes its post-completion ROADMAP-derived counters through the transition so the completing phase's own write increments. (#4129) (#4359)
|
||||
- **Todos stay visible under a workstream** — todos are root-scoped shared state, but every code reader resolved them through the workstream-aware planning dir, so with a workstream active todos read as empty, `todo complete` refused existing files, and the milestone-close audit-open gate passed with pending todos on disk. (#4256) (#4479)
|
||||
- **parseDecisions no longer backtracks quadratically on pathological single bullets** — output unchanged on all legal inputs. (#4130) (#4374)
|
||||
- **Explicit model pins now hold on the Claude runtime** — set `model_profile_overrides.claude.<tier>` (e.g. pin the opus tier to `claude-opus-4-7`) and the resolver silently returned the bare tier alias anyway, and a fully-qualified Claude model ID in `model_overrides` was warn-dropped to tier resolution even though the configuration docs promise any fully-qualified model ID is valid; both are now resolved as configured (values naming the current tier default still collapse to their alias, so nothing changes for unpinned installs), and the docs now state the claude-runtime pin contract including the `fable` alias. (#4192) (#4396)
|
||||
- **`/gsd-code-review --files` no longer silently widens back to the whole phase** — Tier 3's SUMMARY/diff cross-check ran regardless of an explicit `--files` override, appending the rest of the phase's changed files onto a scope the user had deliberately narrowed. (#4552)
|
||||
- **`state begin-phase` no longer rewrites prose that merely quotes the Current-focus field label** — the bold-form rewrite was unanchored, so a bold label quoted mid-sentence elsewhere in the body (e.g. a historical note documenting the format) captured the update and silently destroyed the rest of its line while the real field went unset. Same fix shape as the #4243 fix to the shared field-replacement helper: anchored to line start, same-line whitespace only. (#4577)
|
||||
- **Windows path-confinement is now actually verified** — the external-descriptor write-confinement check resolved paths through the ambient `path` module, so its Windows semantics (drive letters, UNC paths, separator handling) were only ever exercised when the suite happened to run on Windows, and never with Windows-specific inputs. A Windows-only escape was therefore unverified on every platform. The check now accepts an optional path implementation, and drive-letter, UNC, traversal and prefix-boundary escapes are covered deterministically. (#4641) (#4643)
|
||||
- **`phase.add --ws` now numbers the next phase from the workstream's own roadmap** — in a project with sibling git worktrees, `phase.add`/`phase.add-batch` with `--ws` minted a phase number pulled from the root roadmap's maximum (e.g. Phase 40 in a workstream whose own roadmap stopped at Phase 2), creating a `40-<slug>` directory and a `Depends on: Phase 39` entry pointing at a phase that does not exist in the workstream. The sibling-worktree widening horizon is now scoped like every other number source: a workstream-scoped allocation counts numbers held by the same workstream in sibling worktrees only. (#4225) (#4450)
|
||||
- **`/gsd-complete-milestone`'s safety commit and every `/gsd-init`-family command now correctly treat PROJECT.md as a file shared across workstreams, not a per-workstream file** — a #4455 follow-up regression (and one pre-existing, adjacent bug) resolved PROJECT.md through the workstream-scoped path instead of the documented shared root path, so under an active workstream the safety commit silently missed the real PROJECT.md and every init command's `project_title` field silently disappeared. (#4543)
|
||||
- **`update_codebase_map` (execute-plan.md) now scopes its diff to the current milestone** — its diff-base derivation used an unbounded commit-subject search that, on a milestone reusing a phase number, picked up the previous milestone's same-numbered phase and mis-attributed its files to the codebase map. (#4549)
|
||||
- **`gsd capability install` no longer rejects capabilities whose `requires` names a first-party or already-installed capability** — install-time validation was seeded with a candidate-only map, making any non-empty `requires` unsatisfiable; it now sees the full merged registry (first-party + committed overlays + candidate), so requires resolution, cycle and tier checks, and central config-key exclusivity all actually run at install, agreeing with load time. (#3929) (#4691)
|
||||
- **Roadmap phase tables now require an explicit name column** — ordinary status tables can no longer mint bogus names or hide missing phase details. (#4511)
|
||||
- **`/gsd-update --reapply` no longer reports no_baseline when a hash-matching gsd-pristine/ snapshot is stored without the gsd-core/ prefix** — the verifier and the installer now resolve the baseline by the recorded SHA-256 and relocate the orphaned snapshot to its canonical path on the next update, so the correct baseline is finally consumed instead of sitting unusable forever. (#4145) (#4364)
|
||||
- **Milestone-name, branch-name, and phase-insert allocation bugs consolidated at the seam** — a punctuation-only 🚧-bullet name (e.g. a malformed `🚧 **v3.3** ---`) could surface as a real milestone name in two of three capture sites; an undeliverable `phase_slug` produced a branch name ending in the literal `-phase` instead of dropping the segment; `phase insert` (and `phase next-decimal`) could silently reallocate a decimal sub-phase number that existed only as a roadmap checklist bullet, with no way to request a sibling instead of always nesting one level deeper. All three are now single, shared implementations (`hasNameableContent`, `renderPhaseBranchName`, `scanExistingDecimalPhaseNumbers`) applied everywhere the concept is used instead of each consumer reimplementing it independently, with the phase-id anti-divergence guard extended to catch a re-derivation of any of them — and, separately, to catch banned $((10#...)) shell arithmetic on phase-number variables in workflow/reference docs. `phase insert` gains a `--sibling` flag. (#4126, #4433, #4569, #4634) (#4640)
|
||||
- **Executor dispatches are no longer refused when a phase correctly degrades to sequential execution.** The isolation guards identified a dispatch by regex-scraping model-authored prose, which returned identifiers in a different namespace from the ones the run-scoped sentinel records — so a fresh decision was discarded on every executor dispatch and every legitimate `ISOLATION=none` degrade was denied, leaving the work unrun. Dispatch identity now has one owner for both the emitted format and the parser that reads it back. (#4594) (#4693)
|
||||
- **Fixed an intermittent commit-hook failure (SIGPIPE race)** — `gsd-validate-commit.sh`'s subject/config extraction used `echo|head -1`-style pipes under `set -euo pipefail`; a real (multi-line) commit message or configured commit-type list could occasionally trip a SIGPIPE that aborted the whole hook instead of the intended pass/reject, appearing as a spurious `git commit` failure. Replaced with pure bash parameter expansion, eliminating the race entirely. (#4537)
|
||||
- **`execute-phase` no longer fails on a decimal or multi-segment phase** — an inserted phase (`01.1`) or an N-segment phase (`23.1.2`) hit a hard shell arithmetic syntax error at the very first gate (`safe_resume_gate`, which runs unconditionally before any executor dispatches), aborting the workflow before it could do anything. The phase number's leading integer segment is now zero-stripped for the commit-scope regex while the rest is kept as an escaped-dot string, instead of forcing the whole value through base-10 arithmetic. A plain integer phase is unaffected. (#4619) (#4644)
|
||||
- **Sequential phase execution stays on the orchestrator's checkout** — non-isolated executors now receive the orchestrator's validated root as a literal prompt pin and halt loudly before any write or commit when their actual root differs, instead of silently committing onto whatever checkout their spawn cwd resolved to. (#4254) (#4476)
|
||||
- **Verification examples no longer tell agents to grep .env files** — `verification-patterns.md` and `user-setup.md` documented reading `.env`/`.env.local` directly to verify environment variables, which every covered runtime's secret-read guard denies. The environment-variable checks now read the environment (`printenv`) instead of the file, and a broken placeholder-filter regex (`grep -v "a|b|c"`, where `|` is a literal BRE character) is replaced with a working case-insensitive check. (#4440) (#4500)
|
||||
- **The worktree-path guard no longer fails open under CI/process load** — it combined three sequential `git` subprocess spawns into one, cutting the worktree-escape check's worst-case latency so a busy runner can no longer push the guard past its own timeout into a silent allow. (#4515) (#4575)
|
||||
- **Non-Copilot artifacts no longer include Copilot-only tool guidance** — the shared conversion pipeline filters audience-specific notes from commands, skills, and workflow assets while preserving runtime-neutral fallbacks. (#4482) (#4532)
|
||||
- **Managed hooks no longer break on keg-only Homebrew node** — on a Homebrew Node installed as a versioned, unlinked formula (e.g. node@24), every managed hook failed at invocation with `/bin/sh: <prefix>/bin/node: No such file or directory`; the Homebrew path rewrite now verifies the stable symlink exists before using it and keeps the working install path otherwise. (#4137) (#4375)
|
||||
- **STATE.md field reads now target declared field lines** — prose lookalikes are ignored while indented bold fields remain readable, keeping CLI output, sync diagnostics, and writers aligned. (#4510)
|
||||
- **progress-percent bold fields no longer rewrite mid-sentence lookalikes** — anchored to line-start like #4243's stateReplaceField fix. (#4243 follow-up; supersedes the #2177 bold-anywhere reading per maintainer ruling) (#4474)
|
||||
- **Structural pre-pass now documents that its fallow scope has no upper bound** — the phase-directory-anchored base is correct and lockstep with Tier 3's own scope step, but nothing bounds the tip, so reviewing an earlier phase after a later one has landed could silently pull the later phase's files into the audit. The limitation is now documented at the point the scope is derived. (#4574)
|
||||
- **`/gsd-execute-phase` no longer closes a finished executor as `turn_aborted`** — an executor whose plan SUMMARY and matching commits are already on disk is now reconciled as complete when its session ends abnormally, instead of waiting indefinitely for a terminal response and failing. (#4217) (#4442)
|
||||
- **Corrected the native-plugin-install docs' parity claim** — the doc previously said the plugin path and the npm installer differ only in namespace and lifecycle. They also differ in whether install-time config applies at all: the native plugin path never runs GSD's install engine, so config like `agent_tools` that the npm installer bakes into generated artifacts at install time silently never applies there, even after `claude plugin update`. (#4484) (#4579)
|
||||
- **`state planned-phase` now requires a present `--phase` before writing** — missing, empty, and flag-shaped values exit non-zero with STATE.md byte-identical, while phase zero remains valid. (#4383) (#4534)
|
||||
- **Pending-todo bullets in STATE.md now show a date, not a full timestamp** — `renderPendingTodosMarkdown` was echoing the todo's `created` frontmatter verbatim (a full ISO-8601 instant) into the rendered `[…]` bracket, instead of the date-only `[date]` format documented in `docs/reference/state-md.md` and `docs/COMMANDS.md`. (#4439) (#4494)
|
||||
- **Parallel ledger writers no longer silently lose windows entries** — two concurrent `gsd_run windows append` (or waive/fixed) invocations both reported success while one entry vanished from `WINDOWS.md`, false-greening the /gsd-ship gate; the mutating commands now serialize on a cross-process ledger lock and refuse with a typed `windows_ledger_lock` error only when a live writer holds it past the retry budget. (#3780) (#4681)
|
||||
- **`hooks.commit_types` and `hooks.community` are now settable via `config-set`** — both keys are consumed by shipped hooks (`hooks/gsd-validate-commit.sh`), and `hooks.commit_types` is documented in `docs/COMMANDS.md`, but neither was registered in `config-schema.manifest.json`'s `validKeys`, so `config-set` rejected them with "Unknown config key" — the only way to configure either was hand-editing `.planning/config.json`. (#4443) (#4501)
|
||||
- **A hung bounded test check no longer leaks a permanent CPU-pegging orphan process.** `node --test`'s per-file worker subprocess (the process default since Node 22) survived a timed-out check's own kill signal, which only reached the direct runner -- the worker was reparented to PID 1 and could busy-loop forever, consuming a full core, with no visible indication anything was wrong. The bounded check now reaps the whole process tree (POSIX process-group SIGKILL, Windows `taskkill /T /F`) when its own timeout fires. (#3660) (#4615)
|
||||
- **`npm run check:env`'s npm-version check no longer misreports a timeout as a missing binary** — every `spawnSync` failure mode (ENOENT, a signal-killed timeout under load, a non-zero exit) used to collapse into one message, "npm binary not found on PATH." Discovered live: an unrelated PR's Windows CI shard failed this check twice under heavy concurrent test load, and the message made a real timeout indistinguishable from npm genuinely being absent. The reason is now reported accurately, and the check's own timeout was raised from 10s to 15s to match this repo's other npm-subprocess calls. (#4460) (#4572)
|
||||
- **`config-set --dry-run` now actually previews instead of writing** — the flag was silently accepted and ignored, so a probing call still mutated `.planning/config.json` for real; a second dry-run's `previousValue` proved the first had persisted. Both mutating branches (a real set, and the `config-set <key> null` unset path) now honor `--dry-run`, reporting a `dry_run: true` / `would_update` or `would_unset` preview with the current value and writing nothing. Validation and secret masking run identically whether or not `--dry-run` is passed. (#4444) (#4504)
|
||||
- **`execute-plan.md` no longer trips its own size-tier cap.** The workflow file had drifted 21 bytes past its DEFAULT-tier hard cap (introduced by #4540's compact-content variant wiring), which failed `next`'s own test run and blocked every other PR's merge gate. Two wording trims restore headroom; the instruction set is unchanged. (#4555)
|
||||
- **`/gsd-quick`'s post-execute review no longer scopes past its own last commit** — the review-scoping step diffed against bare HEAD instead of the quick task's own newest commit, so any later commit landing on the same tree before the review ran (a worktree merge-back, a shared tree) was silently folded into the quick task's own code-review scope. (#4571)
|
||||
- **macOS todo rendering no longer drops the Needs clause under long temp paths** — the 240-char bound is now deterministic w.r.t. base-path length. (#4384 regression) (#4416)
|
||||
- **`/gsd-new-milestone --ws <name>` now correctly scopes every downstream operation to the requested workstream** — the parsed `--ws` flag was silently dropped by every step after the one that parsed it (each workflow step runs in its own shell), so `init.new-milestone`, `state.milestone-switch`, `phases.clear`, the phase-archive `git add`, and the requirements/roadmap/milestone-start commits all operated on the wrong (ambient or root) scope instead of the explicitly requested workstream. (#4545)
|
||||
- **`/gsd-autonomous` and `/gsd-complete-milestone` now correctly scope STATE/ROADMAP/MILESTONES/PROJECT/REQUIREMENTS reads and writes to the active workstream** — with `GSD_WORKSTREAM` set, these two workflows previously still read and wrote the root `.planning/` copies of these files instead of the selected workstream's own files, silently ignoring or corrupting the wrong scope's planning state (and, for `/gsd-complete-milestone`'s safety commit, silently missing the actual files just archived). `todos` remains the one deliberately shared, root-scoped exception (#4256). (#4542)
|
||||
- **W002 no longer fires on quoted commands in STATE.md** — the health check read GSD's own command names (like ``/gsd-execute-phase 5`` in a ledger row) and anything inside backticks as phase references, so healthy multi-workstream projects reported degraded with false warnings; under an active workstream the warning now also says its declared-phase list is workstream-scoped (`... are declared in workstream <name>`) instead of making an unqualified project-wide claim. (#4257) (#4486)
|
||||
- **`commit --files` can now record a file move without a directory pathspec** — a new `--files-removed <paths>` list declares the deletions the caller intends: each named file, or each tracked-but-absent file under a named directory, is staged as a deletion and joins the commit pathspec. Previously the #2014 skip-if-missing guard meant the only form that recorded a move was a directory entry in `--files`, which also committed any unrelated file sitting in that directory — in the unattended end-of-phase todo sweep, a concurrent session's in-flight todo landed under a phase-close message with no warning, while the file-precise form left the old path's deletion dangling and the todo tracked at both paths. `--files` keeps its skip-if-missing contract unchanged; a `--files-removed` file entry that is still present on disk fails the commit closed, and an index entry that is absent by design (a submodule gitlink, a skip-worktree or assume-unchanged path, an unmerged or intent-to-add entry) is never taken for a removal. A staging failure rolls back every removal the call made with its recorded mode and blob, including on an unborn `HEAD` (best-effort, as the existing addition-side reset is). The `execute-phase` todo sweep and the `cleanup` archive commit now name their removals instead of their directories. (#4253)
|
||||
- **`state` no longer guesses the STATE.md `status` token from substrings of the status prose** — a status line mentioning `.planning/` (or Italian `verifica`, `completezza`, `fasi complete`) no longer silently becomes `status: planning`/`verifying`/`completed`; recognized vocabulary values keep normalizing and unrecognized prose stays visible, `state record-session` without arguments now errors instead of writing, and stray `*-SUMMARY.md` files without a plan twin stay excluded from `progress.completed_plans` recounts. (#4186) (#4381)
|
||||
|
||||
### Security
|
||||
|
||||
- **The secret-read guard no longer lets a trailing dot or space alias past it** — Windows strips trailing dots and spaces from every path component, so `.env.`, `.env ` and `.secrets.` all resolve to the protected file while the guard treated them as unrelated names and allowed the read. Names are now normalized before classification, and the Read, Grep and Bash arms share one path-segmentation rule instead of two that disagreed on backslash paths. (#4651) (#4659)
|
||||
- **Patched a CPU-exhaustion issue in the vendored YAML parser (`js-yaml` 4.3.2)** — merge-key processing in YAML documents now counts empty mapping merges toward the existing `maxTotalMergeKeys` limit, closing a gap upstream backported from 5.4.1 (nodeca/js-yaml#797). (#4565)
|
||||
- **Installed capability skills can no longer be redirected or leaked through a symlink** — the three install paths that confine a capability skill name relied on a lexical check, which cannot see a symlink. A link planted at the destination let `mkdirSync` succeed silently and the SKILL.md write land outside the install root, and a link planted at a capability's own SKILL.md was followed by `statSync` so an outside file's contents were installed as a skill body. All three now refuse to write or read through a link. (#4636) (#4672)
|
||||
- **Path containment at every boundary that takes a directory or filename from the command line** — `todo complete` followed a traversal name outside the todos root and moved the file it found there, `check predicate --phase-dir` let a blocking gate return a passing verdict on evidence from a directory the caller chose, and the shared `resolvePath` helper — used by `check decision-coverage-plan` and `check gap-analysis.plan-post` — accepted a phase directory outside the project. All boundaries now validate against their managed root and reject with a usage error before touching the filesystem. (#4327, #4354) (#4666)
|
||||
- **Pinned the transitive `hono` dependency to `>=4.13.5`** — fixes a moderate-severity path-traversal/DoS advisory chain (GHSA-gqvv-2mrq-wpjv, GHSA-g6gw-c38x-mqfc, GHSA-crvj-82cr-hjcx) in `hono <4.13.5`, pulled in transitively via `@anthropic-ai/claude-agent-sdk` -> `@modelcontextprotocol/sdk`. Discovered as a newly-published advisory blocking `tests/npm-integrity-gate.test.cjs` while verifying an unrelated PR; fixed inline per this repo's no-defer policy rather than left for a separate PR. (#4513) (#4560)
|
||||
|
||||
## [1.13.0] - 2026-09-06
|
||||
|
||||
### Added
|
||||
|
||||
Reference in New Issue
Block a user