Files
msd-core/.changeset
0xdhx 003d982c83 fix(#4623): keep repo-wide planning docs out of the verification digest, and accept --files on verification.fingerprint (#4749)
* fix(#4623): keep repo-wide planning docs out of the verification digest, and accept --files on verification.fingerprint

Two defects in the covered-input fingerprint (#4155), one issue.

1. `computeCoveredDigest` hashed the whole bytes of every declared path
   uniformly, so `.planning/ROADMAP.md` and `.planning/REQUIREMENTS.md` —
   which every phase rewrites as ordinary bookkeeping, and which the closing
   phase's own `phase.complete` / `requirements mark-complete` rewrite AFTER
   the verifier ran — flipped every phase that declared them to `stale` on
   zero implementation change, and from there `isPhaseComplete` →
   `init.manager` → `complete-milestone`'s `ALL_PHASES_VERIFIED` gate.
   Fingerprint v2 leaves any direct child of a planning root out of the
   hash: `.planning/` itself, plus the phase's own planning root (the parent
   of its `phases/`, so `planningDir`'s `<project>/` and `workstreams/<ws>/`
   layouts are covered without the digest knowing what a workstream is —
   `sharedPlanningRoots` / `isSharedPlanningDoc`, defined by position rather
   than a name list so the set cannot drift; a root is accepted only when the
   phase dir sits under a `phases/` directory inside `.planning/`). Such a path is still validated
   exactly as every other covered path (confined, present, a regular file —
   the fail-closed contract is unchanged); only its bytes are ignored, and a
   declaration made only of shared documents fails closed like an empty one.
   A stored digest names its version, and `readVerificationStatus` now
   recomputes under THAT version (`parseFingerprintVersion`,
   `KNOWN_FINGERPRINT_VERSIONS`): a legacy v1 report keeps v1 semantics
   until it is re-fingerprinted, so the upgrade alone stales nothing; a
   version this build cannot recompute fails closed.

2. `verification.fingerprint` received a raw positional slice, so
   `--files a`, `--files "a,b"` and `--files a --files b` all put the literal
   token into the covered set and failed closed as "a covered file is
   missing, unreadable, or escapes the project root" — the message that
   convinced the reporting project the digest was permanently
   unrecomputable. `parseFingerprintFileArgs` accepts every form (plus
   `--files=a,b`, freely mixed with bare positionals), treats any other
   `--flag` and an empty `--files` value as usage errors that say so, and
   the phase-dir argument must now be an existing directory: omitting it
   used to take the first covered file as the phase dir and print a
   plausible digest over the rest at exit 0.

Regression tests (tests/verification-status.test.cjs, #4623 block): the
cross-phase case from the report, the same-phase `requirements
mark-complete` / `phase.complete` cases from the thread, a workstream-scoped
root, v1-preserved / unknown-version-stale, the fail-closed cases (missing,
directory, escaping symlink, all-shared), every `--files` form against the
bare form, the unknown-flag / empty-value / omitted-phase-dir errors, and
AC5's zero-file error. Verified failing against the pre-fix source: 29 of 34
fail, the 7 that pass pin behaviour the fix must leave unchanged.

Docs: CONTEXT.md Verification Module, agents/gsd-verifier.md's
covered_files instruction (rewritten in place — the file sits 21 bytes under
its LARGE hard cap), gsd-core/templates/verification-report.md.

Fixes #4623

Emitted-Drift-Ack-Growth: gsd-verifier.md — the #4155 covered_files instruction now states that planning-root docs are digest-inert (#4623); +18 bytes, under the LARGE cap
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DCMY8P8s6dp4g3Rxu3nNAi

* chore(#4623): set changeset fragment pr to 4749

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: Tom Boucher <trekkie@nomorestars.com>
2026-09-16 05:26:44 -04:00
..

Changeset Fragments

This directory holds per-PR CHANGELOG fragments. Every PR with user-facing changes drops one (or more) <random-name>.md files here describing its CHANGELOG entry. Fragments are consolidated into the top-level CHANGELOG.md at release time.

Why

Two PRs that both edit the ### Fixed block of CHANGELOG.md always conflict on merge — git can't pick a serialization order without human input. Two PRs that each add a fresh .changeset/<unique-name>.md never conflict because they don't share lines.

See #2975 for the full rationale.

Adding a fragment

node scripts/changeset/new.cjs \
  --type Fixed \
  --pr 1234 \
  --body "fix the thing — explain the user-visible change in one sentence"

This writes .changeset/<adjective>-<noun>-<noun>.md with frontmatter and a body. Three random words → concurrent PRs don't collide.

Format

---
type: Fixed
pr: 1234
---
**`/gsd-foo` no longer drops trailing slashes** — explain the user-visible change.

Allowed type: values follow Keep a Changelog: Added, Changed, Deprecated, Removed, Fixed, Security.

Opting out

PRs that legitimately have no user-facing impact can add the no-changelog label. CI honors it. When unsure, add the fragment.

At release time

Promotion is automatic. The release workflow's finalize job runs:

node scripts/changeset/cli.cjs render --version vX.Y.Z --date YYYY-MM-DD --allow-empty

This reads every fragment, groups bullets by type:, replaces ## [Unreleased] with a new ## [vX.Y.Z] - YYYY-MM-DD block, opens a fresh ## [Unreleased] above, and deletes consumed fragments. The --allow-empty flag ensures a no-change release still gets a dated heading (with a _No notable changes._ placeholder). A subsequent verify step confirms the promotion landed correctly. Maintainers do not run this by hand.

Archived fragments

.changeset/archived/ holds fragments for already-shipped releases (≤ 1.3.1), retained for provenance. Their content was hand-curated into the dated ## [1.x.y] sections of CHANGELOG.md during the #690 backfill — they were never consumed by render. All changeset tooling enumerates .changeset/ non-recursively, so archived fragments are never picked up or rendered. Do not move them back to the top level.