fix(#3078): sweep all-spent ack fragments on next, name the collision remedy (#3823)

* fix(#3078): sweep all-spent ack fragments on next, name the collision remedy

`guard-no-ack-on-next` only ever watched the legacy tests/emitted-drift-ack.json.
#2914 exempted the fragment directory on the premise that a persisting fragment
"cannot conflict with any other PR". Fragments do not share a FILE, but they do
share a PATH KEY SPACE, and a path claimed by two sources is a hard failure in
the same script -- so a fully-spent fragment on next owns keys it can no longer
gate, and the next PR to grow one of those paths can declare it neither there
(spent) nor in its own fragment (duplicate). Measured at the sweep: 45 fragments
owning 403 paths, up from 13/272 at triage 19 days earlier.

- `assertNoAllSpentFragments` fails a fragment only when EVERY surviving entry is
  spent against the copy at HEAD^, so a partially spent fragment -- and the
  re-arm-by-appending route #2639/#2993 ship on -- keeps working.
- `ackProse` duplicates the gate's zero-width/whitespace stripping across the
  scripts-ship/tests-do-not line, bounded by a prose-parity test.
- The guard job's checkout takes fetch-depth: 2; at depth 1 HEAD^ is absent and
  every fragment reads as brand-new, i.e. the guard passes vacuously.
- The duplicate-ack error now names both resolutions, since the guard is
  post-merge by design and cannot stop the colliding PR.
- All 45 spent fragments deleted, 0000-legacy-migration.json included, and the
  three tests that pinned its permanence corrected.

Verification is the remote runner (gsd-test), not a local suite.

Closes #3078

* fix(#3078): make the prose-parity test two-sided, cover the git seam, base on the pre-push tip

Three review findings, all fixed:

- The parity test was a tautology: it checked ACK_INVISIBLE against a
  hardcoded list matching its own definition, never against the gate. The
  gate's INVISIBLE and its reason normalizer (hoisted out of diffEmitted as
  normalizeAckReason) are now exported for that sole purpose, and the test
  sweeps 0x00-0xFFFF against both surfaces. Mutation-checked: adding a
  codepoint to one side and not the other now fails.
- resolveBaseRef, readFragmentAtRef and assertUsableBaseRef had zero direct
  coverage -- the tests reimplemented the git reads in a local helper, so the
  ls-tree-vs-show discrimination, the root-commit fallback and the
  option-injection guard were never executed. All are exported and tested
  against real temp repositories now, plus an end-to-end --base-ref subprocess.
- HEAD^ is not 'the state of next before this push'. The default branch allows
  REBASE merges, so one push can carry N commits, and a 2-commit rebase-merge
  whose first commit adds a fragment would be told to git rm it on the very
  push that introduced it. CI now passes github.event.before via --base-ref and
  fetches it explicitly; HEAD^ remains only the local fallback.

Also adds the safe.directory guard every other git call in this repo carries
(#2767), and stops naming the deleted migration fragment by filename in
CONTEXT.md, which tripped lint-removed-but-needed.

Refs #3078

* fix(#3078): keep the fragment directory alive after the sweep empties it

Sweeping every fragment leaves the directory untracked, and check-glossary-refs
then fails: CONTEXT.md references tests/emitted-drift-acks, which no longer
exists. The empty directory IS the intended steady state, so it has to survive
its own remedy.

Adds tests/emitted-drift-acks/README.md documenting the create/use/delete
lifecycle where a contributor actually meets it, matching the existing
tests/qa/smell-acks/README.md precedent. Every reader filters on .json, so the
README is invisible to the gate.

Also sweeps #3809's ack fragment, which the rebase onto origin/next brought in
and the new guard immediately reported as all-spent -- its own remedy applied.

Refs #3078

* fix(#3078): guard the added tests' git calls, drop a second fragment-existence pin

Both defects surfaced by the remote runner (linux-node24, 4/37445 failed).

- The new --base-ref E2E test ran `git rev-parse HEAD` against the checkout
  without the #2767 safe.directory guard. The runner mounts the repo at a path
  owned by another uid, so git refused every operation there with 'detected
  dubious ownership'. Every git call the new tests make now names its own
  specific directory as safe, via one local helper, mirroring safeDirArgs in
  helpers/emitted-runtime.cjs.
- tests/agent-tracked-source-rule.test.cjs pinned the existence and contents of
  the 3645 and 3409 ack fragments. That is a merged PR's paperwork, not live
  behavior: once the growth is in next's baseline the acks are spent and this
  PR's guard sweeps them. The third assertion pinned the hand-appended
  workaround for the exact collision #3078 removes. Deleted; #3645's real
  protection is the two behavioral tests above it, untouched.

Also restores #3809's ack fragment, which merged one commit before this branch.
Deleting an ack in the same window as its introducing PR races any consumer
whose baseline predates it -- the runner's container proved it, resolving
origin/next to 8ed105c8a where the file is still 13847. The backlog sweep is
this PR's scope; that fragment is left for the guard's own first run.

Adds the rule to the fragment README so the class stops recurring.

Refs #3078

* test(#3078): derive the E2E guard expectation from the fragment inventory

The --base-ref E2E test asserted exit 0 while passing the checkout's own HEAD
as the base ref. HEAD-as-base makes every present fragment byte-identical to
itself, so all of them are trivially all-spent and the guard correctly exits 1.
The test only ever passed because the directory happened to be empty when it
was written; restoring #3809's fragment made it fail. The script was right and
the test was wrong.

The degenerate base ref is kept deliberately -- it is what makes 'spent'
trivially true and therefore deterministic -- but the expectation is now
derived from listFragmentFiles() at runtime: zero fragments means exit 0 and
the no-survivors line, N fragments means exit 1 with every name and its git rm.
Proven state-independent by running the suite with the fragment present, with
the directory emptied, and with it restored.

The option-shaped --base-ref rejection is split into its own test, unchanged.

Refs #3078

* chore(#3078): backfill PR number into the changeset fragment (pr:0 -> pr:3823)

---------

Co-authored-by: sim <sim@local>
This commit is contained in:
Tom Boucher
2026-08-24 15:24:30 -04:00
committed by GitHub
parent 8442d984b9
commit a84f756303
57 changed files with 1598 additions and 1351 deletions

View File

@@ -0,0 +1,5 @@
---
type: Fixed
pr: 3823
---
**A merged acknowledgment fragment no longer hard-blocks a later PR that grows the same workflow.** The `guard-no-ack-on-next` job only ever watched the legacy `tests/emitted-drift-ack.json`, on the premise that per-PR fragments cannot conflict. They do not share a file, but they do share a path key space — so a fully-spent fragment on `next` kept owning paths it could no longer gate, and the next PR to touch one of them could declare it neither there nor in its own fragment. The guard now sweeps fully-spent fragments, the duplicate-ack error names both resolutions, and the 45 spent fragments on `next` are removed. (#3078)

View File

@@ -838,6 +838,36 @@ jobs:
timeout-minutes: 1 timeout-minutes: 1
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
# The guard compares each surviving fragment against its copy at the tip of
# `next` BEFORE this push. Depth 2 covers the script's local `HEAD^` fallback;
# at the default depth of 1 even that commit is absent, `git rev-parse HEAD^`
# fails, every fragment reads as brand-new, and the job passes VACUOUSLY —
# exactly how the legacy-file half spent months guarding a file that had not
# existed since #2914 (#3078).
fetch-depth: 2
- name: Assert tests/emitted-drift-ack.json is absent # `github.event.before` is the authoritative pre-push tip, and `HEAD^` is NOT a
run: node scripts/lint-emitted-drift-ack.cjs --guard-next # substitute for it: the default branch allows REBASE merges
# (.github/rulesets/main-protection.json, allowed_merge_methods), so one push can
# carry N commits. With `HEAD^` a 2-commit rebase-merge whose first commit adds a
# fragment would read that fragment as already-present and demand `git rm` on the
# very push that introduced it. Zeros mean the branch was just created: there is no
# pre-push tip, so the guard is given none and falls back to resolving one locally.
- name: Fetch the pre-push tip of next
if: github.event.before != '0000000000000000000000000000000000000000'
env:
BEFORE: ${{ github.event.before }}
run: git fetch --no-tags --depth=1 origin "$BEFORE"
- name: Assert no spent ack survives on next (legacy file and fragments)
env:
BEFORE: ${{ github.event.before }}
# The sha goes through the environment rather than `${{ }}` interpolation into the
# script body, so nothing from the event can ever be parsed as shell.
run: |
if [ "$BEFORE" = "0000000000000000000000000000000000000000" ]; then
node scripts/lint-emitted-drift-ack.cjs --guard-next
else
node scripts/lint-emitted-drift-ack.cjs --guard-next --base-ref "$BEFORE"
fi

File diff suppressed because one or more lines are too long

View File

@@ -1012,22 +1012,42 @@ file every such PR touches guarantees a merge conflict between any two of them (
conflicting PRs in one open queue collided on this file and nothing else), and it means conflicting PRs in one open queue collided on this file and nothing else), and it means
spent, already-merged entries pile up on `next`. A fragment per PR — the same shape spent, already-merged entries pile up on `next`. A fragment per PR — the same shape
`.changeset/` already uses for the identical problem — means two PRs can never conflict on `.changeset/` already uses for the identical problem — means two PRs can never conflict on
this seam again, and a fragment left on `next` after merge is inert rather than a shared this seam again. Two ack sources (two fragments, or a fragment and the legacy file) may
cell. Two ack sources (two fragments, or a fragment and the legacy file) may **never** name **never** name the same path; that is a hard, loudly-reported error, not a silent
the same path; that is a hard, loudly-reported error, not a silent last-wins. last-wins.
`tests/emitted-drift-ack.json` (the legacy single file, specifically — NOT the fragment **When two sources collide (#3078).** The error names both resolutions, because which one
directory) must never persist on `next` (#2914): every entry is scoped to the diff that applies depends on the fragment that already owns the path. If the owning fragment is
introduced it, so once merged it is, by definition, already at the base — spent and inert, already **merged**, its entry is spent — it is at the base, so it gates nothing — and the
regardless of shape, and its persistence is what makes it a shared merge-conflict cell. A answer is to delete it (`git rm tests/emitted-drift-acks/<owner>.json`) and keep your own.
fragment persisting on `next` is harmless, since fragments are independently named and If it is still **live** on your branch, append your explanation to its existing entry
cannot conflict with anything, so this guard is deliberately scoped to the legacy file instead; that re-arms it, and re-arming deliberately costs an actual new sentence, because
alone. This is enforced only on `next` itself, by the `guard-no-ack-on-next` job in the reason is the whole artifact a reviewer reads. Never rename the path to dodge the
error, and never declare it twice.
**Neither ack source may persist on `next`, and a fragment is not exempt (#3078).**
`tests/emitted-drift-ack.json`, the legacy single file, must never survive there at all:
every entry is scoped to the diff that introduced it, so once merged it is by definition
already at the base — spent and inert, regardless of shape — and its persistence is what
makes it a shared merge-conflict cell. A **fragment** is judged on a different rule but the
same law. #2914 originally exempted the fragment directory on the premise that a persisting
fragment is harmless, since fragments are independently named and cannot conflict. That
premise was wrong: fragments do not share a *file*, but they do share a *path key space*,
and a path claimed by two sources is the hard error above. So a fully-spent fragment left on
`next` owns keys it can no longer gate, and the next PR that grows one of those paths can
declare it neither there (spent) nor in its own fragment (duplicate) — the exact wall #2914
removed for the legacy file, one level down. A fragment is therefore swept once **every**
entry in it is spent; a **partially** spent one is left alone, which is what keeps the
re-arm-by-appending route above working. Both rules are enforced only on `next` itself, by
the `guard-no-ack-on-next` job in
`.github/workflows/test.yml` (push-to-`next` trigger, `.github/workflows/test.yml` (push-to-`next` trigger,
`scripts/lint-emitted-drift-ack.cjs --guard-next`), never as a PR-lane check — a PR-lane `scripts/lint-emitted-drift-ack.cjs --guard-next`), never as a PR-lane check — a PR-lane
"base ack must be absent" check would red every open PR the moment one landed (the #2768 "base ack must be absent" check would red every open PR the moment one landed (the #2768
shape #2789 exists to prevent). If you ever see the legacy file present on `next`, delete shape #2789 exists to prevent), which means the job alerts **after** the merge and cannot
it; do not try to make it well-formed. stop the offending PR — that is why the collision error above has to teach the resolution
too. If you ever see the legacy file present on `next`, delete it; do not try to make it
well-formed. If the job names a spent fragment, run the `git rm` it prints — that is the
whole remedy, and there is nothing to regenerate.
`npm run regen:derived` still exists for the artifacts that ARE committed and derived — `npm run regen:derived` still exists for the artifacts that ARE committed and derived —
`sync-manifest-versions`, the ADR index, the capability matrix, the inventory manifest, `sync-manifest-versions`, the ADR index, the capability matrix, the inventory manifest,

File diff suppressed because one or more lines are too long

View File

@@ -142,15 +142,24 @@ The differential attribution check reports the file and the byte delta. To resol
section and `CONTEXT.md`'s `### Emitted Artifact Provenance` entry. Name the section and `CONTEXT.md`'s `### Emitted Artifact Provenance` entry. Name the
fragment for your issue or PR (something nobody else is using) — the failure fragment for your issue or PR (something nobody else is using) — the failure
output prints a minimal valid document you can paste. This is deliberately a output prints a minimal valid document you can paste. This is deliberately a
per-PR fragment, not one shared file: fragments can never conflict across per-PR fragment, not one shared file: two fragments can never *merge-conflict*
PRs, and a fragment appearing in your diff *is* the visible signal. If the with each other, and a fragment appearing in your diff *is* the visible signal.
failure instead names a path a merged PR already acknowledged (a **spent** They do, however, share a path key space. If the failure instead names a path a
entry sitting in an existing fragment), reword that fragment's `reason` in merged PR already acknowledged (a **spent** entry sitting in an existing
place to explain the new ripple — do not add a duplicate entry for the same fragment), you have two routes and the error text names both: `git rm` that
path; two ack sources naming the same path is a hard, loudly-reported error. fragment if every entry in it is spent — it gates nothing and only holds the
keys — or, if it is still live, reword/extend its `reason` in place to explain
the new ripple. Either way, do not add a duplicate entry for the same path; two
ack sources naming the same path is a hard, loudly-reported error.
The legacy single `tests/emitted-drift-ack.json` is still read and unioned The legacy single `tests/emitted-drift-ack.json` is still read and unioned
in for branches that carry it, but new acknowledgments never go there. in for branches that carry it, but new acknowledgments never go there.
3. **Or shrink it instead of acknowledging.** Prefer extraction when the growth 3. **Delete your fragment once it has merged (#3078).** A fragment on `next` is
spent by definition — its prose is already at the base, so it can no longer
clear anything — while still owning its path keys, which walls off the next PR
that grows one of them. The `guard-no-ack-on-next` job reds `next` and prints
the exact `git rm` for every fully-spent fragment. A *partially* spent fragment
is deliberately left alone.
4. **Or shrink it instead of acknowledging.** Prefer extraction when the growth
is incidental: for a workflow, move per-mode bodies to is incidental: for a workflow, move per-mode bodies to
`workflows/<name>/modes/`, templates to `workflows/<name>/templates/`, and `workflows/<name>/modes/`, templates to `workflows/<name>/templates/`, and
shared prose to `gsd-core/references/`; for an agent, lift shared boilerplate shared prose to `gsd-core/references/`; for an agent, lift shared boilerplate

File diff suppressed because one or more lines are too long

View File

@@ -32,12 +32,46 @@
const fs = require('node:fs'); const fs = require('node:fs');
const path = require('node:path'); const path = require('node:path');
const { execFileSync } = require('node:child_process');
const ACK_VERSION = 1; const ACK_VERSION = 1;
const ACK_REPO_PATH = 'tests/emitted-drift-ack.json'; const ACK_REPO_PATH = 'tests/emitted-drift-ack.json';
const ACK_DIR_REPO_PATH = 'tests/emitted-drift-acks'; const ACK_DIR_REPO_PATH = 'tests/emitted-drift-acks';
const REPO_ROOT = path.join(__dirname, '..'); const REPO_ROOT = path.join(__dirname, '..');
/**
* Upper bound on any one git call made by the `--guard-next` lane (#3078).
*
* Every subprocess this repo spawns is bounded (CLAUDE.md -> KNOWN DEFECTS, "Unbounded
* Subprocesses": 5-30s for git). The guard reads one directory listing plus one blob per
* surviving fragment, all against local objects, so 15s is generous — but an unbounded
* `execFileSync` on a wedged git is an indefinite hang in a job whose whole timeout
* budget is one minute.
*/
const GIT_TIMEOUT_MS = 15_000;
/**
* Characters that render as nothing: soft hyphen, the zero-width family, word joiner,
* BOM. Stripped before ack reasons are compared, so an invisible edit cannot make a spent
* acknowledgment look re-armed.
*
* DUPLICATED from `INVISIBLE` in `tests/helpers/emitted-diff.cjs`, for the same reason
* every other constant here is duplicated rather than required: `scripts/` ships in the
* npm package and `tests/` does not, so the require would be MODULE_NOT_FOUND once
* published (see this file's top-of-file comment). The two are held together by the
* prose-parity test in `tests/emitted-attribution.test.cjs`, which enumerates the gate's
* own codepoints and fails if this list stops covering them.
*
* Spelled as codepoints on purpose — a literal character class here would itself be
* invisible in review, which is the exact failure being defended against.
*/
const ACK_INVISIBLE = new RegExp(
`[${[0x00AD, 0x200B, 0x200C, 0x200D, 0x2060, 0xFEFF]
.map((c) => `\\u${c.toString(16).toUpperCase().padStart(4, '0')}`)
.join('')}]`,
'g',
);
/** /**
* Upper bound on how many fragment files `listFragmentFiles` may return in one * Upper bound on how many fragment files `listFragmentFiles` may return in one
* `readdirSync` pass. Mirrors `MAX_ACK_FRAGMENTS` in `tests/helpers/emitted-diff.cjs` — * `readdirSync` pass. Mirrors `MAX_ACK_FRAGMENTS` in `tests/helpers/emitted-diff.cjs` —
@@ -215,6 +249,132 @@ function declaredKeys(raw) {
return Object.keys(paths).filter((k) => !RESERVED_ACK_KEYS.has(k)); return Object.keys(paths).filter((k) => !RESERVED_ACK_KEYS.has(k));
} }
/**
* Normalize an ack reason to the prose a reviewer actually reads.
*
* Mirrors the gate's own `prose()` inside `diffEmitted` (`tests/helpers/emitted-diff.cjs`)
* exactly: strip invisibles, collapse internal whitespace, trim. Re-arming a spent ack is
* legitimate — it is how a contributor says "this is a NEW ripple, and here is why" — but
* it must cost an ACTUAL explanation, so a doubled space, a CRLF, or a U+200B may never
* make a spent entry look live. Bounded by the parity test named on ACK_INVISIBLE.
*/
function ackProse(reason) {
return reason.replace(ACK_INVISIBLE, '').replace(/\s+/g, ' ').trim();
}
/**
* The declared entries of one ack document as `path key -> normalized prose`, or `null`
* when the document cannot be trusted to answer the question.
*
* `null` is NOT "no entries" — it is "do not draw a conclusion from this file". A document
* that will not parse, is not an object, has a non-object `paths`, carries a reasonless or
* non-string entry, or names a RESERVED_ACK_KEY cannot be shown to be spent, and the
* conservative direction here is to leave it alone: `validateAckText` (run by `lint:ci`,
* pre-merge) owns SHAPE and already blocks such a document from reaching the base, while
* this guard owns LIFECYCLE. Sweeping a file we could not read would delete an
* acknowledgment on the strength of a parse failure.
*
* An ABSENT document (`raw === null`) is a genuine empty entry set — the fragment simply
* did not exist at that ref, so nothing it declares now has a counterpart there.
*/
function ackEntries(raw) {
if (raw === null) return new Map();
let doc;
try {
doc = JSON.parse(raw);
} catch {
return null;
}
if (!isPlainObject(doc)) return null;
const paths = doc.paths;
if (paths === undefined) return new Map();
if (!isPlainObject(paths)) return null;
const entries = new Map();
for (const [rel, value] of Object.entries(paths)) {
if (RESERVED_ACK_KEYS.has(rel)) return null;
const reason = isPlainObject(value) ? value.reason : value;
if (typeof reason !== 'string') return null;
entries.set(rel, ackProse(reason));
}
return entries;
}
/**
* assertNoAllSpentFragments — the fragment half of the `next`-lane guard (#3078).
*
* #2914 split the single shared ack file into per-PR fragments and deliberately exempted
* the fragment directory from `assertAbsentOnNext`, on the premise that a persistent
* fragment "cannot conflict with any other PR". That premise does not hold. Fragments do
* not share a FILE, but they do share a PATH KEY SPACE, and `main()` below treats a path
* claimed by two sources as a hard failure. So a merged fragment is not harmless: every
* entry it leaves on `next` is spent by definition — its prose is already at the base, so
* it gates nothing — while still owning its key, and the next PR that grows the same
* workflow can declare it neither in the owning fragment (spent) nor in its own
* (duplicate). That is the exact failure #2914 fixed for the legacy file, reintroduced one
* level down. Measured on `next` when this landed: 45 fragments owning 403 paths.
*
* Unlike the legacy file, PRESENCE alone is not the failure — a fragment landed by the
* very push being guarded is the healthy case for every ack-carrying PR. The failure is
* INERTNESS: every surviving entry's prose already matches the copy at the base ref, so
* the fragment can no longer clear a delta for anyone. A PARTIALLY spent fragment is left
* alone; only an entirely inert one is cruft. That distinction is why the base side is
* required, and it is what keeps the re-arm-by-appending route working (#2639, #2993).
*
* An ENTRYLESS document is vacuously all-spent and swept for the same reason
* `validateAckText` refuses to let one be committed: it acknowledges nothing.
*
* Pure — no fs, no git, no clock. `main()` does the reading.
*
* @param {Array<{name: string, currentRaw: string|null, baseRaw: string|null}>} fragments
* @returns {{ ok: boolean, message: string, sweepable: string[] }}
*/
function assertNoAllSpentFragments(fragments) {
const sweepable = [];
for (const { name, currentRaw, baseRaw } of fragments) {
const current = ackEntries(currentRaw);
if (current === null) continue; // unreadable — `validateAckText` owns that verdict
const base = ackEntries(baseRaw);
if (base === null) continue;
const allSpent = [...current].every(([rel, prose]) => base.get(rel) === prose);
if (allSpent) sweepable.push({ name, entries: current.size });
}
if (sweepable.length === 0) {
return {
ok: true,
message: `ok guard-no-ack-on-next: no all-spent fragment survives in ${ACK_DIR_REPO_PATH}/`,
sweepable: [],
};
}
const lines = sweepable.map(
({ name, entries }) => ` - ${ACK_DIR_REPO_PATH}/${name} (${entries} entr${entries === 1 ? 'y' : 'ies'}, all spent)`
+ `\n remedy: git rm ${ACK_DIR_REPO_PATH}/${name}`,
);
return {
ok: false,
sweepable: sweepable.map(({ name }) => name),
message: [
`guard-no-ack-on-next: ${sweepable.length} fully-spent ack fragment(s) survive on next.`,
'',
...lines,
'',
'Every entry in these fragments is already at the base, so each is spent and gates '
+ 'nothing (#2789) — but it still OWNS its path keys. The next PR that grows one of '
+ 'those paths can declare it neither here (spent) nor in its own fragment (a '
+ 'duplicate ack is a hard failure), so a spent fragment left behind is a wall, not '
+ 'harmless cruft (#3078).',
'',
'A partially spent fragment is deliberately NOT reported: only an entirely inert one '
+ 'is swept, so appending prose to a live entry to re-arm it keeps working.',
].join('\n'),
};
}
/** /**
* assertAbsentOnNext — the `next`-lane guard (#2914), invoked only by the * assertAbsentOnNext — the `next`-lane guard (#2914), invoked only by the
* `guard-no-ack-on-next` workflow job on push to `next`, never in `lint:ci`. * `guard-no-ack-on-next` workflow job on push to `next`, never in `lint:ci`.
@@ -231,6 +391,12 @@ function declaredKeys(raw) {
* instant one landed). It is safe only because it runs on `next` itself, asserting a fact * instant one landed). It is safe only because it runs on `next` itself, asserting a fact
* about `next`'s own tree, never about any PR's diff against it. * about `next`'s own tree, never about any PR's diff against it.
* *
* Scoped to the LEGACY FILE ONLY — the fragment directory is guarded by
* `assertNoAllSpentFragments` above, on a stricter-to-state but weaker-to-apply rule
* (inertness, not presence). #3078 corrected the original premise that a persisting
* fragment "cannot conflict with any other PR": fragments share a path key space even
* though they do not share a file.
*
* @param {boolean} present whether ACK_REPO_PATH exists in the tree being checked * @param {boolean} present whether ACK_REPO_PATH exists in the tree being checked
* @returns {{ ok: boolean, message: string }} * @returns {{ ok: boolean, message: string }}
*/ */
@@ -248,9 +414,10 @@ function assertAbsentOnNext(present) {
+ 'whether it is otherwise well-formed.', + 'whether it is otherwise well-formed.',
'', '',
'#2914: acks now go in per-PR fragments under tests/emitted-drift-acks/, one file per ' '#2914: acks now go in per-PR fragments under tests/emitted-drift-acks/, one file per '
+ 'PR, never this single shared file -- a persistent fragment there is harmless (every ' + 'PR, never this single shared file. A fragment cannot MERGE-CONFLICT with another '
+ 'fragment is independently named, so it cannot conflict with any other PR), which is ' + "PR's fragment, but it does own its path keys, so a fully-spent one left on next "
+ 'why only THIS legacy file is guarded here, never the fragment directory.', + 'still blocks the next PR that grows the same path (#3078) -- fragments are guarded '
+ 'separately, on inertness rather than on presence.',
'', '',
'CONTRIBUTING.md: "When you remove the last entry from tests/emitted-drift-ack.json, ' 'CONTRIBUTING.md: "When you remove the last entry from tests/emitted-drift-ack.json, '
+ 'delete the file too -- its presence is the alarm."', + 'delete the file too -- its presence is the alarm."',
@@ -260,13 +427,123 @@ function assertAbsentOnNext(present) {
}; };
} }
/**
* Every git call declares the SPECIFIC directory it operates on as safe, mirroring
* `safeDirArgs` in `tests/helpers/emitted-runtime.cjs` (#2767): a checkout mounted at a
* path owned by a different uid makes git refuse EVERY operation there with "detected
* dubious ownership", and this guard's whole value is that it fails LOUDLY on a real
* fault rather than degrading to "no base, nothing spent". Never the `*` wildcard, which
* would mark every repository on the machine safe. Duplicated rather than imported for
* the reason stated at the top of this file: `scripts/` ships in the npm package and
* `tests/` does not.
*/
function git(args, { cwd = REPO_ROOT } = {}) {
return execFileSync('git', ['-c', `safe.directory=${path.resolve(cwd)}`, ...args], {
cwd,
encoding: 'utf8',
timeout: GIT_TIMEOUT_MS,
maxBuffer: 16 * 1024 * 1024,
stdio: ['ignore', 'pipe', 'pipe'],
});
}
/**
* The LOCAL/MANUAL fallback for "the commit `next` was at BEFORE this push" — `HEAD^`,
* or `null` on a root commit. Correct only when the push it is standing in for carries
* exactly one commit.
*
* CI never relies on this: it passes the authoritative pre-push tip explicitly via
* `--base-ref` (`github.event.before`, wired in `.github/workflows/test.yml`), because
* the default branch's ruleset allows REBASE merges
* (`.github/rulesets/main-protection.json`, `allowed_merge_methods`), so a single push
* event can land N commits at once. `HEAD^` steps back exactly one commit — for a
* 2-commit rebase-merge whose first commit adds a fragment and whose second is
* unrelated, `HEAD^` would land on the first commit, read the fragment as already
* present there, and demand `git rm` on the very push that introduced it (#3078). This
* function exists purely as the manual-run / single-commit-push fallback.
*
* Two steps on purpose. `HEAD` is resolved first, which proves git runs and the working
* directory is a readable repository; only then is a failure to resolve `HEAD^` read as
* "this commit has no parent". A single blanket try/catch would collapse "git is broken"
* into "there is no base", and a guard with no base sweeps nothing — it would pass
* vacuously, which is precisely how the legacy-file job spent months guarding a file that
* had not existed since #2914 (#3078).
*/
function resolveBaseRef({ cwd = REPO_ROOT, run = git } = {}) {
run(['rev-parse', '--verify', 'HEAD'], { cwd });
try {
return run(['rev-parse', '--verify', 'HEAD^'], { cwd }).trim();
} catch {
return null; // root commit — nothing can be spent against it
}
}
/**
* Raw text of one ack fragment AT `base`, or `null` when it is simply not there.
*
* Mirrors `readAckFileAtRef` in `tests/helpers/emitted-runtime.cjs` (duplicated across the
* ships/does-not-ship line, as everything else here is): `git show` alone cannot tell a
* bogus ref from an absent path — both say "does not exist in" — so absence is established
* with `ls-tree`, which exits 0 with empty output when the path is not there and non-zero
* on a real fault. A genuine git failure THROWS rather than degrading to `null`, because
* "could not read the base" read as "absent at the base" would make every fragment look
* brand-new and silently disarm the sweep.
*/
function readFragmentAtRef(base, name, { cwd = REPO_ROOT, run = git } = {}) {
const repoPath = `${ACK_DIR_REPO_PATH}/${name}`;
const listing = run(['ls-tree', '--name-only', base, '--', repoPath], { cwd });
if (listing.trim() === '') return null;
return run(['show', `${base}:${repoPath}`], { cwd });
}
/**
* A base ref must not begin with `-`: `execFileSync`'s array form stops shell
* metacharacters but not git's own option parsing, and `git show` honors diff options
* including `--output=<file>`, which WRITES. Same guard, same reason, as
* `readAckFileAtRef`'s.
*/
function assertUsableBaseRef(ref) {
if (typeof ref !== 'string' || ref === '' || ref.startsWith('-')) {
throw new Error(
`lint-emitted-drift-ack: refusing to read fragments at ${JSON.stringify(ref)} — a base `
+ 'ref must be a non-empty string that does not begin with "-", which git would parse '
+ 'as an option.',
);
}
return ref;
}
function main() { function main() {
const legacyFile = path.join(REPO_ROOT, ...ACK_REPO_PATH.split('/')); const legacyFile = path.join(REPO_ROOT, ...ACK_REPO_PATH.split('/'));
if (process.argv.includes('--guard-next')) { if (process.argv.includes('--guard-next')) {
const result = assertAbsentOnNext(fs.existsSync(legacyFile)); const legacy = assertAbsentOnNext(fs.existsSync(legacyFile));
console.log(result.message); console.log(legacy.message);
if (!result.ok) process.exitCode = 1;
// The fragment half (#3078). CI always passes `--base-ref` (the pre-push tip of
// `next`, `github.event.before`), because the default branch allows REBASE merges
// and one push can carry N commits — `HEAD^` alone is not "the state of next before
// this push" in that case. `resolveBaseRef()`'s `HEAD^` is only the fallback for a
// manual run or a single-commit push, where the two agree. NOTE the job's checkout
// must fetch at least depth 2 for the `HEAD^` fallback to resolve at all, and must
// separately fetch the `--base-ref` commit itself, or every fragment reads as
// brand-new.
const baseFlag = process.argv.indexOf('--base-ref');
const baseRef = baseFlag === -1
? resolveBaseRef()
: assertUsableBaseRef(process.argv[baseFlag + 1]);
const dir = path.join(REPO_ROOT, ...ACK_DIR_REPO_PATH.split('/'));
const fragments = listFragmentFiles(dir).map((name) => ({
name,
currentRaw: readIfPresent(path.join(dir, name)),
baseRaw: baseRef === null ? null : readFragmentAtRef(baseRef, name),
}));
const sweep = assertNoAllSpentFragments(fragments);
console.log(sweep.message);
if (!legacy.ok || !sweep.ok) process.exitCode = 1;
return; return;
} }
@@ -301,7 +578,12 @@ function main() {
problems.push( problems.push(
`duplicate ack for "${key}": declared in both ${owner.get(key)} and ${label}. ` `duplicate ack for "${key}": declared in both ${owner.get(key)} and ${label}. `
+ 'Two ack sources (fragments, or a fragment and the legacy file) may never ' + 'Two ack sources (fragments, or a fragment and the legacy file) may never '
+ 'name the same path — rename or merge them.', + 'name the same path. Resolve it one of two ways, depending on the owner: if '
+ `${owner.get(key)} is already merged, its entry is SPENT and gates nothing — `
+ `delete it (git rm ${owner.get(key)}) and keep your own. If it is still live `
+ 'on this branch, APPEND your explanation to its existing entry instead, which '
+ 're-arms it — re-arming deliberately costs actual new prose. Do not rename '
+ 'the path to dodge this.',
); );
continue; continue;
} }
@@ -335,10 +617,19 @@ if (require.main === module) main();
module.exports = { module.exports = {
validateAckText, validateAckText,
assertAbsentOnNext, assertAbsentOnNext,
assertNoAllSpentFragments,
ackProse,
ackEntries,
declaredKeys, declaredKeys,
listFragmentFiles, listFragmentFiles,
ACK_VERSION, ACK_VERSION,
ACK_REPO_PATH, ACK_REPO_PATH,
ACK_DIR_REPO_PATH, ACK_DIR_REPO_PATH,
ACK_INVISIBLE,
MAX_ACK_FRAGMENTS, MAX_ACK_FRAGMENTS,
git,
resolveBaseRef,
readFragmentAtRef,
assertUsableBaseRef,
GIT_TIMEOUT_MS,
}; };

View File

@@ -79,20 +79,15 @@ describe('#3645 — agents write only git-tracked source paths', () => {
'the rule belongs in the spawn contract, not the frozen agent file (#3645)'); 'the rule belongs in the spawn contract, not the frozen agent file (#3645)');
}); });
test('growth ack recorded for the grown shipped files (#3645)', () => { // A prior version of this suite pinned the EXISTENCE and CONTENTS of the `3645` and
const acksDir = path.join(__dirname, 'emitted-drift-acks'); // `3409` emitted-drift-acks fragments. An ack is scoped to the diff that introduced
const readAck = (name) => fs.readFileSync(path.join(acksDir, name), 'utf8'); // it (#2789's ack-lifecycle law): once #3645 merged and its growth is in `next`'s
const mine = fs.readdirSync(acksDir).find((f) => f.includes('3645')); // baseline, neither fragment gates anything — both are spent, and #3078's
assert.ok(mine, 'an emitted-drift-acks fragment for #3645 must exist'); // `guard-no-ack-on-next` sweeps them. A test may therefore never pin a fragment's
const mineSrc = readAck(mine); // existence or its prose; a fragment that is correctly swept would fail the pinning
assert.ok(mineSrc.includes('"gsd-pattern-mapper.md"'), // test for a reason that has nothing to do with the behavior it was meant to protect.
'the #3645 fragment must acknowledge the grown mapper by its ack key'); // #3645's actual protection is the two behavioral tests above — the mapper emitting
assert.ok(!mineSrc.includes('"gsd-planner.md"'), // only tracked analog paths, and the frozen planner file staying untouched — which
'gsd-planner.md is unchanged — no ack entry for it'); // this change leaves exactly as they were. The growth itself is protected by `next`'s
// plan-phase.md's growth ack lives in the 3409 fragment (two ack sources // emitted baseline (the differential attribution check), not by the spent fragment.
// may never name the same path — the merge precedent).
const planPhaseAck = readAck('3409-unreachable-guard-arms.json');
assert.ok(planPhaseAck.includes('"plan-phase.md"') && planPhaseAck.includes('#3645 append'),
'the plan-phase.md growth reason must be appended to the 3409 fragment (#3645)');
});
}); });

View File

@@ -78,7 +78,14 @@ const { EXPECTED_MANIFEST_COUNT, loadManifests } = require('./helpers/emitted-pr
const { const {
validateAckText, validateAckText,
assertAbsentOnNext, assertAbsentOnNext,
assertNoAllSpentFragments,
ackProse,
ackEntries,
ACK_INVISIBLE,
MAX_ACK_FRAGMENTS: MAX_ACK_FRAGMENTS_LINT, MAX_ACK_FRAGMENTS: MAX_ACK_FRAGMENTS_LINT,
resolveBaseRef,
readFragmentAtRef,
assertUsableBaseRef,
} = require('../scripts/lint-emitted-drift-ack.cjs'); } = require('../scripts/lint-emitted-drift-ack.cjs');
const { const {
ACK_VERSION, ACK_VERSION,
@@ -87,6 +94,8 @@ const {
NEW_FILE_CAP, NEW_FILE_CAP,
MAX_ACK_FRAGMENTS, MAX_ACK_FRAGMENTS,
REMEDIATION, REMEDIATION,
INVISIBLE,
normalizeAckReason,
sourceSatisfiedBy, sourceSatisfiedBy,
parseAck, parseAck,
mergeAckSources, mergeAckSources,
@@ -934,9 +943,18 @@ test('assertAbsentOnNext fails when the file is present with entries, naming the
assert.match(r.message, /git rm tests\/emitted-drift-ack\.json/, 'the remedy must be named, not just the problem'); assert.match(r.message, /git rm tests\/emitted-drift-ack\.json/, 'the remedy must be named, not just the problem');
assert.match( assert.match(
r.message, /tests\/emitted-drift-acks\//, r.message, /tests\/emitted-drift-acks\//,
'#2914: the message must explain that acks now go in per-PR fragments, and that a ' '#2914: the message must explain that acks now go in per-PR fragments',
+ 'persisting fragment (unlike this legacy file) is harmless',
); );
// #3078 removed the premise this used to assert: a persisting fragment is NOT harmless
// just because it does not share a FILE with another PR — fragments share a PATH KEY
// SPACE, and a duplicate path across two sources is a hard failure in main(), so a
// fully-spent fragment left behind still owns keys it can no longer gate.
assert.doesNotMatch(
r.message, /cannot conflict with any other PR/,
'#3078: this premise was false and has been removed — a fragment cannot MERGE-CONFLICT '
+ 'with another PR\'s fragment, but it can still collide on a path key',
);
assert.match(r.message, /#3078/);
}); });
test('assertAbsentOnNext fed from a real next-like tree: absent passes, present fails (regression, #2914)', () => { test('assertAbsentOnNext fed from a real next-like tree: absent passes, present fails (regression, #2914)', () => {
@@ -958,6 +976,803 @@ test('assertAbsentOnNext fed from a real next-like tree: absent passes, present
assert.match(r.message, /exists on next/); assert.match(r.message, /exists on next/);
}); });
// ─── guard-no-ack-on-next: fully-spent FRAGMENTS are guarded on inertness (#3078) ──
//
// #2914 exempted the fragment directory from `assertAbsentOnNext` on the premise that
// a persistent fragment "cannot conflict with any other PR". That premise is false:
// fragments do not share a FILE, but they do share a PATH KEY SPACE, and a duplicate
// path declared by two sources is a hard failure in `main()` below. So a merged,
// fully-spent fragment still OWNS its path keys, and the next PR that grows one of
// those paths can declare it neither there (spent — `parseAck`'s `isSpent`/`prose`
// contract) nor in its own fragment (a cross-source duplicate). Unlike the legacy
// file, PRESENCE alone is not the failure here — a fragment landed by the very push
// being guarded is the healthy case for every ack-carrying PR. The failure is
// INERTNESS: every surviving entry's prose already matches the base ref's copy, so
// the fragment can no longer clear a delta for anyone.
const doc = (paths) => JSON.stringify({ version: ACK_VERSION, paths });
const frag = (name, current, base) => ({ name, currentRaw: current, baseRaw: base });
// ── A. assertNoAllSpentFragments — the pure lifecycle rule ──────────────────
test('ackEntries: a literal "null" document is unreadable, distinct from an entryless object document', () => {
// The distinction assertNoAllSpentFragments's "literal null" test above depends on:
// an entryless OBJECT document is a genuine empty entry set (`new Map()`), while the
// TEXT "null" parses to the JS value `null`, fails isPlainObject, and returns the
// "cannot trust this document" sentinel instead.
assert.deepEqual(ackEntries('{}'), new Map());
assert.deepEqual(ackEntries('{"version":1,"paths":{}}'), new Map());
assert.equal(ackEntries('null'), null);
assert.equal(ackEntries(null) instanceof Map, true, 'an ABSENT fragment (raw === null) is a genuine empty entry set');
assert.deepEqual(ackEntries(null), new Map());
});
test('assertNoAllSpentFragments: zero fragments is ok, vacuously', () => {
const r = assertNoAllSpentFragments([]);
assert.ok(r.ok);
assert.match(r.message, /no all-spent fragment survives/);
assert.deepEqual(r.sweepable, []);
});
test('assertNoAllSpentFragments: a fragment absent at the base is live — the healthy shape of every fresh PR', () => {
// A fragment landed by the very push being guarded is the NORMAL case for every
// ack-carrying PR. Reporting it would red `next` on every such merge.
const r = assertNoAllSpentFragments([
frag('a.json', doc({ 'x.md': { reason: 'why' } }), null),
]);
assert.ok(r.ok);
assert.deepEqual(r.sweepable, []);
});
test('assertNoAllSpentFragments: a fully-spent fragment is reported, naming the file, "all spent", and the exact git rm remedy (n=1, n=3)', () => {
for (const entries of [
{ 'x.md': { reason: 'why' } },
{ 'a.md': { reason: 'a' }, 'b.md': { reason: 'b' }, 'c.md': { reason: 'c' } },
]) {
const raw = doc(entries);
const r = assertNoAllSpentFragments([frag('spent.json', raw, raw)]);
assert.ok(!r.ok, `${Object.keys(entries).length} entr(y/ies) must still be reported all-spent`);
assert.match(r.message, /spent\.json/);
assert.match(r.message, /all spent/);
assert.match(r.message, new RegExp(escapeRegex(`git rm ${ACK_DIR_REPO_PATH}/spent.json`)));
assert.deepEqual(r.sweepable, ['spent.json']);
}
});
test('assertNoAllSpentFragments: a partially spent fragment (one entry new) is left alone', () => {
// Named explicitly as a must-not-change: appending a new entry beside an
// already-spent one must not get swept out from under the live one.
const r = assertNoAllSpentFragments([
frag(
'mixed.json',
doc({ x: { reason: 'x-reason' }, y: { reason: 'y-reason' } }),
doc({ x: { reason: 'x-reason' } }),
),
]);
assert.ok(r.ok);
assert.deepEqual(r.sweepable, []);
});
test('assertNoAllSpentFragments: re-arming by appending genuinely new prose keeps working (#2639, #2993)', () => {
const r = assertNoAllSpentFragments([
frag('r.json', doc({ x: { reason: 'a brand new explanation' } }), doc({ x: { reason: 'the original explanation' } })),
]);
assert.ok(r.ok, 're-arming by appending genuinely new prose must not be swept');
});
test('assertNoAllSpentFragments: a zero-information reword never looks like a re-arm — doubled whitespace, leading/trailing whitespace, CRLF vs LF', () => {
const cases = [
['doubled internal whitespace', 'the original explanation', 'the original explanation'],
['leading/trailing whitespace', ' the original explanation ', 'the original explanation'],
['CRLF vs LF inside the reason', 'the original\r\nexplanation', 'the original\nexplanation'],
];
for (const [label, current, base] of cases) {
const r = assertNoAllSpentFragments([
frag('r.json', doc({ x: { reason: current } }), doc({ x: { reason: base } })),
]);
assert.ok(!r.ok, `${label}: a zero-information reword must still read as spent`);
assert.deepEqual(r.sweepable, ['r.json'], label);
}
});
test('assertNoAllSpentFragments: each invisible codepoint alone must not re-arm a spent entry', () => {
const codepoints = [0x00AD, 0x200B, 0x200C, 0x200D, 0x2060, 0xFEFF];
for (const cp of codepoints) {
const base = 'the original explanation';
const current = `the${String.fromCodePoint(cp)} original explanation`;
const r = assertNoAllSpentFragments([
frag('r.json', doc({ x: { reason: current } }), doc({ x: { reason: base } })),
]);
assert.ok(!r.ok, `U+${cp.toString(16).toUpperCase()} alone must not re-arm a spent entry`);
}
});
test('assertNoAllSpentFragments: every SURVIVING entry is spent, even after one entry is dropped', () => {
const base = doc({ x: { reason: 'x-reason' }, y: { reason: 'y-reason' } });
const current = doc({ x: { reason: 'x-reason' } });
const r = assertNoAllSpentFragments([frag('drop.json', current, base)]);
assert.ok(!r.ok);
assert.deepEqual(r.sweepable, ['drop.json']);
});
test('assertNoAllSpentFragments: disjoint key sets between current and base is not spent', () => {
const r = assertNoAllSpentFragments([
frag('disjoint.json', doc({ x: { reason: 'x' } }), doc({ y: { reason: 'y' } })),
]);
assert.ok(r.ok);
});
test('assertNoAllSpentFragments: an entryless-but-parseable-object document is vacuously all-spent (n=0 boundary)', () => {
// Vacuously all-spent: `[...new Map()].every(...)` is true, so an object document
// declaring zero entries is swept for the same reason validateAckText refuses to let
// one be committed — it acknowledges nothing.
for (const raw of ['{}', '{"version":1}', '{"version":1,"paths":{}}']) {
const r = assertNoAllSpentFragments([frag('empty.json', raw, raw)]);
assert.ok(!r.ok, `${raw} must be swept`);
assert.deepEqual(r.sweepable, ['empty.json'], raw);
}
});
test('assertNoAllSpentFragments: a document of literally "null" is UNREADABLE here, not entryless, so it is left alone', () => {
// Distinct from the object-shaped entryless case above: `ackEntries` parses the text
// "null" to the JS value `null`, which fails `isPlainObject` and returns `null`
// (its "cannot trust this document" sentinel) — NOT `new Map()`. So this fragment is
// skipped by assertNoAllSpentFragments entirely, same as any other unreadable
// document (see the next test) — it is never counted as vacuously spent, unlike
// validateAckText's POLICY layer, which does reject "null" (present-but-entryless).
// The two surfaces are allowed to differ here: this guard owns LIFECYCLE, not shape.
const r = assertNoAllSpentFragments([frag('literal-null.json', 'null', 'null')]);
assert.ok(r.ok, 'a literal "null" document must not be swept by this guard');
assert.deepEqual(r.sweepable, []);
});
test('assertNoAllSpentFragments: a document it cannot trust to answer the question is left alone, never swept', () => {
// validateAckText / lint:ci owns SHAPE; this guard owns LIFECYCLE. Sweeping a
// fragment on the strength of a parse failure would delete an acknowledgment for
// the wrong reason.
const validBase = doc({ x: { reason: 'x' } });
for (const currentRaw of ['{ not json', '[]', '42', '{"paths":[]}', '{"paths":{"x":42}}']) {
const r = assertNoAllSpentFragments([frag('bad.json', currentRaw, validBase)]);
assert.ok(r.ok, `unreadable current ${currentRaw} must not be swept`);
assert.deepEqual(r.sweepable, []);
}
const r2 = assertNoAllSpentFragments([frag('bad-base.json', validBase, '{ not json')]);
assert.ok(r2.ok, 'an unreadable base must not be swept either');
assert.deepEqual(r2.sweepable, []);
});
test('assertNoAllSpentFragments: naming ONLY the inert fragments makes the remedy safe to apply blindly', () => {
const spentA = doc({ a: { reason: 'a' } });
const spentB = doc({ b: { reason: 'b' } });
const live = doc({ c: { reason: 'c-new' } });
const liveBase = doc({ c: { reason: 'c-old' } });
const r = assertNoAllSpentFragments([
frag('spent-a.json', spentA, spentA),
frag('spent-b.json', spentB, spentB),
frag('live-c.json', live, liveBase),
]);
assert.ok(!r.ok);
assert.deepEqual([...r.sweepable].sort(), ['spent-a.json', 'spent-b.json']);
assert.doesNotMatch(r.message, /live-c\.json/);
});
test('assertNoAllSpentFragments: a __proto__ key is never mistaken for a spent entry, and Object.prototype stays untouched', () => {
// A `[key]` computed property, never a literal `{ __proto__: ... }` — the literal
// form is special-cased by JS to SET THE PROTOTYPE rather than create an own
// property, which would make `paths` serialize as `{}` and this test vacuous.
const key = '__proto__';
const raw = JSON.stringify({ version: ACK_VERSION, paths: { [key]: { reason: 'hostile' } } });
const parsedPaths = JSON.parse(raw).paths;
assert.deepEqual(Object.keys(parsedPaths), ['__proto__'], 'JSON.parse must create a genuine own key');
const r = assertNoAllSpentFragments([frag('proto.json', raw, raw)]);
assert.ok(r.ok, '__proto__ makes the document unreadable to ackEntries, so it must never be swept');
assert.deepEqual(r.sweepable, []);
assert.equal(({}).reason, undefined, 'Object.prototype must stay untouched throughout');
});
test('assertNoAllSpentFragments: a bare-string reason and an object-shaped reason are compared on prose alone', () => {
const bare = doc({ x: 'why' });
const bareR = assertNoAllSpentFragments([frag('bare.json', bare, bare)]);
assert.ok(!bareR.ok, 'a bare-string reason on both sides must still be recognized as spent');
const objCurrent = doc({ x: { reason: 'why' } });
const bareBase = doc({ x: 'why' });
const shapeR = assertNoAllSpentFragments([frag('shape.json', objCurrent, bareBase)]);
assert.ok(!shapeR.ok, 'a shape change alone must not re-arm — parseAck accepts both shapes');
});
test('assertNoAllSpentFragments: a decorative "runtime" field beside an unchanged reason does not re-arm (runtime is deliberately not compared)', () => {
const base = doc({ x: { reason: 'why' } });
const current = doc({ x: { reason: 'why', runtime: 'claude' } });
const r = assertNoAllSpentFragments([frag('runtime.json', current, base)]);
assert.ok(!r.ok, 'runtime carries no explanation and must not re-arm a byte-identical reason');
});
// ── B. prose parity with the gate (the generative-fix-divergence gate) ──────
//
// `scripts/` ships in the npm package and `tests/` does not, so `ackProse` MUST
// duplicate `emitted-diff.cjs`'s `prose()`. This section is the parity assertion
// that fails when they diverge.
test('ACK_INVISIBLE and the gate\'s own INVISIBLE are the identical regex, not a second hand-typed copy (#3078)', () => {
// scripts/ ships in the npm package and tests/ does not, so ACK_INVISIBLE is a literal
// duplicate of INVISIBLE (tests/helpers/emitted-diff.cjs) rather than a require across
// that line — see both files' top-of-file comments. A divergence here means an
// invisible reword can re-arm a spent ack on the real `--guard-next` gate and NOT on
// this suite (or vice versa), which is exactly the class of drift a parity test that
// compares a hardcoded literal against its own definition can never catch.
assert.equal(
ACK_INVISIBLE.source, INVISIBLE.source,
'scripts/lint-emitted-drift-ack.cjs\'s ACK_INVISIBLE and tests/helpers/emitted-diff.cjs\'s '
+ 'INVISIBLE must declare the identical character class — they are duplicated only because '
+ 'scripts/ ships in the npm package and tests/ does not',
);
assert.equal(
ACK_INVISIBLE.flags, INVISIBLE.flags,
'both are duplicated for the same reason and must agree on flags too (both carry "g", '
+ 'which is exactly what makes .test() below stateful and in need of a lastIndex reset)',
);
// Derive the codepoint list from the REAL gate regex rather than hardcoding one here.
// A hardcoded list would pass even if someone added a codepoint to only one side — the
// exact divergence this test exists to catch. `.test()` on a `g`-flagged regex advances
// `lastIndex` as a side effect, so it is reset before and after every call.
for (let cp = 0x0000; cp <= 0xFFFF; cp++) {
const ch = String.fromCodePoint(cp);
INVISIBLE.lastIndex = 0;
const gateStrips = INVISIBLE.test(ch);
INVISIBLE.lastIndex = 0;
ACK_INVISIBLE.lastIndex = 0;
const ackStrips = ACK_INVISIBLE.test(ch);
ACK_INVISIBLE.lastIndex = 0;
const hex = cp.toString(16).toUpperCase().padStart(4, '0');
assert.equal(ackStrips, gateStrips, `U+${hex}: ACK_INVISIBLE and INVISIBLE disagree on whether it is invisible`);
if (gateStrips) {
assert.equal(ackProse(`a${ch}b`), 'ab', `ackProse must strip U+${hex}`);
assert.equal(normalizeAckReason(`a${ch}b`), 'ab', `normalizeAckReason must strip U+${hex}`);
} else if (!/\s/.test(ch)) {
// Not invisible, and not plain whitespace either (whitespace-collapse behavior
// depends on adjacency, so it is exercised separately below) — must survive as-is.
assert.equal(ackProse(`a${ch}b`), `a${ch}b`, `ackProse must NOT strip U+${hex}`);
assert.equal(normalizeAckReason(`a${ch}b`), `a${ch}b`, `normalizeAckReason must NOT strip U+${hex}`);
}
}
});
test('normalizeAckReason (the gate\'s own prose normalizer) and ackProse agree byte-for-byte over a behavioral corpus (#3078)', () => {
const invisibleCps = [0x00AD, 0x200B, 0x200C, 0x200D, 0x2060, 0xFEFF];
const corpus = [
'identical text',
'doubled internal space',
' leading and trailing space ',
'crlf\r\nline',
'lf\nline',
'tab\ttab',
'nbsp nbsp', // U+00A0 NBSP
'ideographic space', // U+3000 IDEOGRAPHIC SPACE
'em space', // U+2003 EM SPACE
...invisibleCps.map((cp) => `invisible${String.fromCodePoint(cp)}codepoint`),
'',
' \t\n ',
];
for (const reason of corpus) {
assert.equal(
normalizeAckReason(reason), ackProse(reason),
`normalizeAckReason and ackProse diverge on ${JSON.stringify(reason)}`,
);
}
});
test('ackProse and the sweep guard agree on what counts as "the same explanation, reworded"', () => {
const corpus = [
['identical', 'same words', 'same words', true],
['doubled space', 'same words', 'same words', true],
['leading/trailing space', ' same words ', 'same words', true],
['CRLF vs LF', 'same\r\nwords', 'same\nwords', true],
['NBSP vs space', 'same words', 'same words', true],
['ideographic space vs space', 'same words', 'same words', true],
['EM SPACE vs space', 'same words', 'same words', true],
['zero-width inserted', 'same​words', 'samewords', true],
['genuinely different words', 'same words', 'different words', false],
];
for (const [label, a, b, expectedSameProse] of corpus) {
assert.equal(
ackProse(a) === ackProse(b), expectedSameProse,
`${label}: ackProse must agree it is${expectedSameProse ? '' : ' NOT'} the same explanation`,
);
const gate = assertNoAllSpentFragments([
frag('r.json', doc({ x: { reason: a } }), doc({ x: { reason: b } })),
]);
assert.equal(
!gate.ok, expectedSameProse,
`${label}: a divergence here means the sweep guard and the gate disagree about what `
+ 're-arms an ack',
);
}
});
test('property: normalizeAckReason (the gate) and ackProse agree for arbitrary strings, seeded (#3078)', () => {
// Two-sided over unconstrained input, not just the constructed corpus above — a
// divergence anywhere in fc.string()'s space fails here, not just at the handful of
// codepoints the corpus test happens to name.
fc.assert(
fc.property(
fc.string(),
(s) => {
assert.equal(normalizeAckReason(s), ackProse(s));
},
),
{ seed: 3078, numRuns: 200 },
);
});
test('property: ackProse and normalizeAckReason agree, and both are invariant under expanding existing whitespace and inserting invisible codepoints, seeded (#3078)', () => {
const invisibleChars = [0x00AD, 0x200B, 0x200C, 0x200D, 0x2060, 0xFEFF].map((cp) => String.fromCodePoint(cp));
const word = fc.string({ minLength: 1, maxLength: 8 }).filter((s) => !/\s/.test(s));
fc.assert(
fc.property(
fc.array(word, { minLength: 1, maxLength: 6 }),
fc.constantFrom(' ', '\t', '\n', '\r\n', ' ', ' ', ' '),
fc.array(fc.constantFrom(...invisibleChars), { minLength: 0, maxLength: 6 }),
(parts, whitespaceRun, invisibles) => {
const base = parts.join(' ');
// Every existing single space widened to a longer whitespace RUN — never
// introduces whitespace where none existed, so the collapsed result cannot
// change.
const expanded = parts.join(whitespaceRun);
// Invisible codepoints inserted anywhere are stripped outright, never
// collapsed to a space, so they never introduce a new word boundary.
const withInvisibles = invisibles.reduce((s, ch) => ch + s, base);
const padded = ` ${base} `;
// Two-sided on every one of these forms, not just the base string.
for (const candidate of [base, expanded, withInvisibles, padded]) {
assert.equal(normalizeAckReason(candidate), ackProse(candidate));
}
assert.equal(ackProse(expanded), ackProse(base));
assert.equal(ackProse(withInvisibles), ackProse(base));
assert.equal(ackProse(padded), ackProse(base));
},
),
{ seed: 3078, numRuns: 200 },
);
});
// ── C. --guard-next wiring against a REAL git repository ────────────────────
//
// The pure function above is fed hand-built strings; this section proves the
// WIRING — real commits, real `git show` reads — because a guard that resolves no
// base passes vacuously, and that is exactly how the legacy half went blind.
// #2767: the remote runner mounts the repo at a path owned by another uid, and git then
// refuses every operation there with "detected dubious ownership". Names the SPECIFIC
// directory, never the `*` wildcard. Mirrors `safeDirArgs` in helpers/emitted-runtime.cjs.
const gitIn = (dir, args) => execFileSync(
'git', ['-c', `safe.directory=${path.resolve(dir)}`, ...args],
{ cwd: dir, encoding: 'utf8', timeout: 15000 },
);
function makeGuardNextRepo() {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-guard-next-repo-'));
const runGit = (args) => gitIn(dir, args);
runGit(['init', '-q', '-b', 'next']);
function commit(msg) {
runGit(['-c', 'user.email=test@example.com', '-c', 'user.name=Test', 'add', '-A']);
runGit(['-c', 'user.email=test@example.com', '-c', 'user.name=Test', 'commit', '-q', '-m', msg]);
return runGit(['rev-parse', 'HEAD']).trim();
}
function writeFrag(name, obj) {
const fragDir = path.join(dir, ...ACK_DIR_REPO_PATH.split('/'));
fs.mkdirSync(fragDir, { recursive: true });
fs.writeFileSync(path.join(fragDir, name), JSON.stringify(obj));
}
return { dir, commit, writeFrag };
}
// Section C used to reimplement git-reading in a local `readFragAtCommit` helper and
// feed only the PURE `assertNoAllSpentFragments`, so the real wiring — the
// `ls-tree`-then-`show` absence-vs-fault discrimination, the `HEAD`-then-`HEAD^`
// two-step, the root-commit fallback, and the option-injection guard — was never
// executed by any test (#3078 review). C1-C4 below now call the REAL
// `readFragmentAtRef`, exported from `scripts/lint-emitted-drift-ack.cjs` for exactly
// this purpose.
test('C1: real repo — a fragment added in commit 2 vs commit 1 (root, no fragment) is live', () => {
const repo = makeGuardNextRepo();
try {
fs.writeFileSync(path.join(repo.dir, 'README.md'), 'root\n');
const c1 = repo.commit('root');
repo.writeFrag('a.json', { version: ACK_VERSION, paths: { 'x.md': { reason: 'why' } } });
repo.commit('add fragment');
const r = assertNoAllSpentFragments([
frag(
'a.json',
readFragmentAtRef('HEAD', 'a.json', { cwd: repo.dir }),
readFragmentAtRef(c1, 'a.json', { cwd: repo.dir }),
),
]);
assert.ok(r.ok, 'a fragment absent at the base is live');
} finally {
cleanup(repo.dir);
}
});
test('C2: real repo — an unrelated file change leaves an unchanged fragment fully spent', () => {
const repo = makeGuardNextRepo();
try {
repo.writeFrag('a.json', { version: ACK_VERSION, paths: { 'x.md': { reason: 'why' } } });
const c2 = repo.commit('add fragment');
fs.writeFileSync(path.join(repo.dir, 'README.md'), 'unrelated\n');
repo.commit('unrelated change');
const r = assertNoAllSpentFragments([
frag(
'a.json',
readFragmentAtRef('HEAD', 'a.json', { cwd: repo.dir }),
readFragmentAtRef(c2, 'a.json', { cwd: repo.dir }),
),
]);
assert.ok(!r.ok, 'unchanged fragment against its own prior commit is fully spent');
} finally {
cleanup(repo.dir);
}
});
test('C3: real repo — appending prose to the owning entry re-arms it', () => {
const repo = makeGuardNextRepo();
try {
repo.writeFrag('a.json', { version: ACK_VERSION, paths: { 'x.md': { reason: 'the original explanation' } } });
const c2 = repo.commit('add fragment');
repo.writeFrag('a.json', {
version: ACK_VERSION,
paths: { 'x.md': { reason: 'the original explanation, now covering a new ripple too' } },
});
repo.commit('reword');
const r = assertNoAllSpentFragments([
frag(
'a.json',
readFragmentAtRef('HEAD', 'a.json', { cwd: repo.dir }),
readFragmentAtRef(c2, 'a.json', { cwd: repo.dir }),
),
]);
assert.ok(r.ok, 'genuinely new prose re-arms the entry');
} finally {
cleanup(repo.dir);
}
});
test('C4: real repo — a fragment at the ROOT commit compared against a null base is live', () => {
const repo = makeGuardNextRepo();
try {
repo.writeFrag('a.json', { version: ACK_VERSION, paths: { 'x.md': { reason: 'why' } } });
repo.commit('root with fragment');
// The base is passed as a literal `null`, not a call to readFragmentAtRef(null, …)
// — this mirrors main()'s own `baseRef === null ? null : readFragmentAtRef(...)`
// branch for a root commit, where resolveBaseRef() has already returned null.
const r = assertNoAllSpentFragments([
frag('a.json', readFragmentAtRef('HEAD', 'a.json', { cwd: repo.dir }), null),
]);
assert.ok(r.ok, 'a root commit has no base — resolveBaseRef returns null and nothing can be spent against it');
} finally {
cleanup(repo.dir);
}
});
// ── C2 (direct). Direct coverage of the real git seam (#3078 review) ────────
//
// C1-C4 above exercise `readFragmentAtRef` only through `assertNoAllSpentFragments`'s
// verdict, which cannot distinguish "read the wrong thing" from "read nothing" if both
// happen to produce the same pure-function outcome. The tests below assert on
// `readFragmentAtRef`, `resolveBaseRef`, and `assertUsableBaseRef` DIRECTLY, plus one
// end-to-end run of the real script as a subprocess — the only thing that exercises
// `main()`'s own argv parsing. `makeGuardNextRepo()` above already satisfies the "one
// makeRepo() helper" shape this needs (mkdtempSync, `git init -q -b next`, per-commit
// identity via `-c user.email=... -c user.name=...`, never mutating global git config),
// so it is reused rather than duplicated.
test('readFragmentAtRef: returns null for a fragment genuinely absent at that ref — distinguished via ls-tree, not a git-show error message (healthy steady state)', () => {
const repo = makeGuardNextRepo();
try {
fs.writeFileSync(path.join(repo.dir, 'README.md'), 'root\n');
const c1 = repo.commit('root, no fragment yet');
repo.writeFrag('a.json', { version: ACK_VERSION, paths: { 'x.md': { reason: 'why' } } });
repo.commit('add fragment');
assert.equal(
readFragmentAtRef(c1, 'a.json', { cwd: repo.dir }), null,
'a fragment not yet added at that ref must read as null, not throw',
);
} finally {
cleanup(repo.dir);
}
});
test('readFragmentAtRef: returns the exact bytes committed at that ref, which differ from an uncommitted working-tree edit — proves it reads the REF, not the tree', () => {
const repo = makeGuardNextRepo();
try {
repo.writeFrag('a.json', { version: ACK_VERSION, paths: { 'x.md': { reason: 'original' } } });
const c1 = repo.commit('add fragment');
// Deliberately left UNCOMMITTED — only the working tree carries this edit.
repo.writeFrag('a.json', { version: ACK_VERSION, paths: { 'x.md': { reason: 'edited after the commit' } } });
const atRef = readFragmentAtRef(c1, 'a.json', { cwd: repo.dir });
const onDisk = fs.readFileSync(path.join(repo.dir, ...ACK_DIR_REPO_PATH.split('/'), 'a.json'), 'utf8');
assert.equal(JSON.parse(atRef).paths['x.md'].reason, 'original');
assert.notEqual(atRef, onDisk, 'the ref read must not pick up the uncommitted working-tree edit');
} finally {
cleanup(repo.dir);
}
});
test('readFragmentAtRef: throws on a ref that does not exist — a failed base read must be an error, never a silent null', () => {
const repo = makeGuardNextRepo();
try {
repo.writeFrag('a.json', { version: ACK_VERSION, paths: { 'x.md': { reason: 'why' } } });
repo.commit('add fragment');
// "could not read the base" read as "absent at the base" would make every fragment
// look brand-new and disarm the sweep — this must throw, not return null.
assert.throws(
() => readFragmentAtRef('not-a-real-ref-3078', 'a.json', { cwd: repo.dir }),
/not-a-real-ref-3078/,
'a bad ref must throw, naming itself',
);
} finally {
cleanup(repo.dir);
}
});
test('resolveBaseRef: returns the parent sha on a two-commit repo, matching `git rev-parse HEAD^` as a 40-hex string', () => {
const repo = makeGuardNextRepo();
try {
fs.writeFileSync(path.join(repo.dir, 'README.md'), 'one\n');
const c1 = repo.commit('first');
fs.writeFileSync(path.join(repo.dir, 'README.md'), 'two\n');
repo.commit('second');
const expected = gitIn(repo.dir, ['rev-parse', 'HEAD^']).trim();
const actual = resolveBaseRef({ cwd: repo.dir });
assert.match(actual, /^[0-9a-f]{40}$/, 'must be a 40-hex sha');
assert.equal(actual, expected);
assert.equal(actual, c1);
} finally {
cleanup(repo.dir);
}
});
test('resolveBaseRef: returns null on a root commit (the ONLY way null is reached) and THROWS when pointed at a directory that is not a git repository at all', () => {
const repo = makeGuardNextRepo();
try {
fs.writeFileSync(path.join(repo.dir, 'README.md'), 'root\n');
repo.commit('root, no parent');
assert.equal(resolveBaseRef({ cwd: repo.dir }), null, 'a root commit has no parent');
} finally {
cleanup(repo.dir);
}
// The pair this matters for: a blanket try/catch around BOTH the HEAD and HEAD^
// resolutions would silently collapse "git is broken here" into "there is no base",
// and a guard with no base sweeps nothing — it would pass vacuously, unnoticed by any
// test, which is precisely how the legacy-file job spent months guarding a file that
// had not existed since #2914 (#3078). resolveBaseRef resolves HEAD FIRST, unguarded,
// specifically so a broken repository throws instead of returning null.
const notARepo = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-not-a-repo-'));
try {
assert.throws(() => resolveBaseRef({ cwd: notARepo }));
} finally {
cleanup(notARepo);
}
});
test('assertUsableBaseRef: rejects an option-shaped ref, an empty string, null, and a non-string; returns a normal sha unchanged', () => {
// `git show` honors diff options including `--output=<file>`, which WRITES a file —
// an option-shaped ref is a real hazard, not a hypothetical one.
for (const bad of ['-x', '--output=/tmp/gsd-3078-pwned', '', null, 42, {}]) {
assert.throws(
() => assertUsableBaseRef(bad),
/would parse|option/,
`${JSON.stringify(bad)} must be rejected as unusable`,
);
}
const sha = 'a'.repeat(40);
assert.equal(assertUsableBaseRef(sha), sha, 'a normal 40-hex sha must be returned unchanged');
});
test('E2E: --guard-next --base-ref runs the real script as a subprocess against this checkout, deriving its expected outcome from the live fragment inventory', () => {
// Passing THIS checkout's own HEAD as --base-ref is the deliberate degenerate case: with
// a clean tree, the working-tree copy of every fragment in tests/emitted-drift-acks/
// equals its committed copy at HEAD, so "spent" is trivially true for every fragment
// that happens to exist right now. That degeneracy is exactly what makes the expected
// exit code and output DERIVABLE from the inventory at runtime rather than a number
// hand-picked when the directory happened to be empty (#3078 regression: it was empty
// when this test was written, then a fragment was restored and the hardcoded
// exit-0/count-2 expectation went stale). Whether the directory holds zero fragments or
// N, the assertion below is the correct one either way.
const scriptPath = path.join(REPO_ROOT, 'scripts', 'lint-emitted-drift-ack.cjs');
const head = gitIn(REPO_ROOT, ['rev-parse', 'HEAD']).trim();
const { listFragmentFiles } = require('../scripts/lint-emitted-drift-ack.cjs');
const fragDir = path.join(REPO_ROOT, 'tests', 'emitted-drift-acks');
const fragments = listFragmentFiles(fragDir);
let status = 0;
let out = '';
try {
out = execFileSync(
process.execPath,
[scriptPath, '--guard-next', '--base-ref', head],
{ cwd: REPO_ROOT, encoding: 'utf8', timeout: 30000 },
);
} catch (err) {
status = err.status;
out = (err.stdout || '') + (err.stderr || '');
}
// The legacy-file half is independent of the fragment inventory and always reports ok
// on this checkout (the legacy file was deleted by #2914) — this is the "legacy-file
// line" half of --guard-next's two halves.
assert.equal(
out.split('\n').filter((l) => l.startsWith('ok guard-no-ack-on-next:')).length >= 1, true,
'the legacy-file guard must print its own ok line regardless of fragment state',
);
if (fragments.length === 0) {
assert.equal(status, 0, 'zero fragments: the guard must exit 0');
assert.match(out, /no all-spent fragment survives/, 'the fragment-sweep half must also print its ok line');
} else {
assert.notEqual(status, 0, `${fragments.length} fragment(s) at HEAD are trivially all-spent against themselves`);
assert.match(out, new RegExp(`${fragments.length} fully-spent ack fragment\\(s\\) survive on next`));
for (const name of fragments) {
assert.ok(out.includes(name), `sweep output must name ${name}`);
assert.match(
out, new RegExp(`git rm[^\\n]*${escapeRegex(name)}`),
`sweep output must print a git rm remedy line for ${name}`,
);
}
}
});
test('E2E: --guard-next rejects an option-shaped --base-ref', () => {
// main()'s argv parsing is the only thing this exercises that the unit tests above
// cannot: an option-shaped --base-ref must be rejected, not silently accepted.
const scriptPath = path.join(REPO_ROOT, 'scripts', 'lint-emitted-drift-ack.cjs');
let threw = false;
let status;
let stderr = '';
try {
execFileSync(
process.execPath,
[scriptPath, '--guard-next', '--base-ref', '-x'],
{ cwd: REPO_ROOT, encoding: 'utf8', timeout: 30000 },
);
} catch (err) {
threw = true;
status = err.status;
stderr = err.stderr || '';
}
assert.ok(threw, 'an option-shaped --base-ref must make the guard exit non-zero');
assert.notEqual(status, 0);
assert.match(stderr, /would parse|option/);
});
// ── D. the collision shape end-to-end — the issue's own regression criterion ─
//
// #3078 "Done when": a merged, fully-spent fragment must not silently occupy a path
// key it can no longer gate. All three arms below must hold together: (1) a
// cross-source duplicate is a hard failure naming the remedy, (2) leaving the
// owning entry untouched is spent and gates nothing, (3) appending prose re-arms
// it, and (4) once the spent owner is swept, a new fragment can declare the path
// cleanly.
test('D1/D2: two fragments naming the same path is a hard failure, naming both files, git rm, re-arms, and APPEND', () => {
const scriptPath = path.join(REPO_ROOT, 'scripts', 'lint-emitted-drift-ack.cjs');
const fragDir = path.join(REPO_ROOT, ...ACK_DIR_REPO_PATH.split('/'));
const nameA = 'zzz-3078-test-collision-a.json';
const nameB = 'zzz-3078-test-collision-b.json';
const pathA = path.join(fragDir, nameA);
const pathB = path.join(fragDir, nameB);
const collisionKey = 'zzz-3078-test-collision-path.md';
try {
// The fragment directory does not necessarily exist yet — #2914's directory is
// created on first use, and `listFragmentFiles` treats an absent one as zero
// fragments, so a clean checkout may not have it.
fs.mkdirSync(fragDir, { recursive: true });
fs.writeFileSync(pathA, JSON.stringify({ version: ACK_VERSION, paths: { [collisionKey]: { reason: 'a' } } }));
fs.writeFileSync(pathB, JSON.stringify({ version: ACK_VERSION, paths: { [collisionKey]: { reason: 'b' } } }));
let stderr = '';
let threw = false;
try {
execFileSync(process.execPath, [scriptPath], { cwd: REPO_ROOT, encoding: 'utf8', timeout: 15000 });
} catch (err) {
threw = true;
stderr = err.stderr || '';
}
assert.ok(threw, 'a cross-source duplicate must exit non-zero');
assert.match(stderr, new RegExp(`duplicate ack for "${escapeRegex(collisionKey)}"`));
assert.match(stderr, new RegExp(escapeRegex(nameA)));
assert.match(stderr, new RegExp(escapeRegex(nameB)));
assert.match(stderr, /git rm /);
assert.match(stderr, /re-arms/);
assert.match(stderr, /APPEND/);
} finally {
// Must NOT survive the test — a real duplicate left behind would red the repo's
// own lint:ci the next time anyone runs it. cleanup() cannot be used here: it
// refuses any path outside the known OS temp roots, and these fixtures are
// deliberately created inside the real repo's tests/emitted-drift-acks/.
// eslint-disable-next-line local/no-raw-rmsync-in-tests -- not a temp dir; cleaning up files created inside the real repo tree
fs.rmSync(pathA, { force: true });
// eslint-disable-next-line local/no-raw-rmsync-in-tests -- not a temp dir; cleaning up files created inside the real repo tree
fs.rmSync(pathB, { force: true });
}
});
test('D3: leaving the owning fragment entry untouched is spent and gates nothing', () => {
const r = diffEmitted({
baseline: mf({ [WORKFLOW_KEY]: 'aaa' }),
current: mf({ [WORKFLOW_KEY]: 'bbb' }),
changedPaths: [],
ack: { version: ACK_VERSION, paths: { [WORKFLOW_KEY]: { reason: 'already explained' } } },
baseAck: { version: ACK_VERSION, paths: { [WORKFLOW_KEY]: { reason: 'already explained' } } },
});
assert.deepEqual(r.spentAcks, [WORKFLOW_KEY]);
assert.equal(r.acked.length, 0, 'a spent entry must not clear the new delta');
assert.deepEqual(r.unattributable.map((u) => u.rel), [WORKFLOW_KEY]);
});
test('D4: appending prose to the owning fragment entry re-arms it — #2639/#2993 ship depending on this route', () => {
const r = diffEmitted({
baseline: mf({ [WORKFLOW_KEY]: 'aaa' }),
current: mf({ [WORKFLOW_KEY]: 'bbb' }),
changedPaths: [],
ack: {
version: ACK_VERSION,
paths: { [WORKFLOW_KEY]: { reason: 'already explained, and now this NEW ripple too' } },
},
baseAck: { version: ACK_VERSION, paths: { [WORKFLOW_KEY]: { reason: 'already explained' } } },
});
assert.deepEqual(r.spentAcks, []);
assert.equal(r.acked.length, 1, '#2639/#2993 ship depending on this re-arm route continuing to work');
assert.equal(r.unattributable.length, 0);
});
test('D5: once the spent owner is swept, declaring the same path in a new fragment is clean', () => {
// Before the sweep: the owning fragment is fully spent (assertNoAllSpentFragments
// names it) but still present — this is the #3078 hazard.
const spentDoc = doc({ [WORKFLOW_KEY]: { reason: 'already explained' } });
const before = assertNoAllSpentFragments([frag('owner.json', spentDoc, spentDoc)]);
assert.ok(!before.ok);
assert.deepEqual(before.sweepable, ['owner.json']);
// After the sweep (fragment deleted, per the remedy): a NEW fragment can declare
// the same path with zero collision, exercised through the real duplicate-detection
// path (mergeAckSources), not just the pure sweep guard above.
const { merged, errors } = mergeAckSources([
{
source: 'tests/emitted-drift-acks/new.json',
doc: { version: ACK_VERSION, paths: { [WORKFLOW_KEY]: { reason: 'a fresh explanation' } } },
},
]);
assert.deepEqual(errors, [], 'no duplicate arises once the spent owner is gone');
assert.ok(merged.paths[WORKFLOW_KEY]);
const parsed = parseAck(merged);
assert.equal(parsed.entries.size, 1);
assert.deepEqual(parsed.errors, []);
});
// ─── Per-PR ack fragments: mergeAckSources + readAckSources (#2914) ────────── // ─── Per-PR ack fragments: mergeAckSources + readAckSources (#2914) ──────────
// //
// #2914 replaces the single shared tests/emitted-drift-ack.json with per-PR fragments // #2914 replaces the single shared tests/emitted-drift-ack.json with per-PR fragments
@@ -1273,66 +2088,50 @@ test('listAckFragmentFilesAtRef: exactly MAX_ACK_FRAGMENTS entries passes, one o
); );
}); });
test('the migrated fragment physically lives at ACK_DIR/0000-legacy-migration.json on this checkout', () => { // ─── The migration pin is gone: #3078 emptied the directory it protected (#3078) ──
// `includes`, not `deepEqual`, on purpose: other PRs merging their own fragments over // #2914 migrated the legacy shared file's 35 entries into the legacy-migration bucket
// time must not make this permanent regression test fail — it only pins THIS // fragment and this file pinned that fragment's continued existence, on the premise
// fragment's continued existence at the real, non-injected path. // that a fragment left on `next` is inert-but-harmless. #3078 removed that premise:
const fragmentPath = path.join(ACK_DIR, '0000-legacy-migration.json'); // fragments don't share a file, but they DO share a path key space, so a fully-spent
assert.ok(fs.existsSync(fragmentPath), 'the migration must have landed at the real fragment directory path'); // fragment owns keys it can no longer gate, and the next PR to grow one of those paths
assert.ok(listAckFragmentFiles(ACK_DIR).includes('0000-legacy-migration.json')); // is hard-blocked. `--guard-next` now sweeps all-spent fragments, and the whole
}); // directory was emptied — the legacy-migration bucket included. The maintainer's
// decision on #3078 states plainly it was never permanent.
//
// The three tests that pinned it are gone rather than adapted: each asserted the
// presence of a specific merged fragment, which is exactly the state the guard now
// forbids. What they protected is covered instead by the fragment-lifecycle section
// (`assertNoAllSpentFragments`) added by #3078, and #2733's own control-flow
// protection lives in `tests/spec-phase-probe-reachability.test.cjs`, which never
// depended on the ack. The #2733 spec-phase.md byte-delta entry (31987 -> 31997) is
// spent by construction: `next`'s published emitted baseline has carried the
// post-#2733 bytes since that PR merged, so `sizeBaseline === sizeCurrent` and there
// is no growth left for it to clear.
test('the migrated legacy fragment still clears the real #2733 spec-phase.md growth (#2914 migration)', () => { test('an empty fragment directory is the healthy steady state, end to end (#3078)', () => {
// The whole point of MIGRATING rather than deleting: no acknowledgment is lost, only // Absent directory is zero fragments, not a fault.
// relocated. This pins the migrated fragment's spec-phase.md entry to the exact const missingDir = path.join(REPO_ROOT, 'tests', 'no-such-emitted-drift-acks-dir');
// growth #2733 introduced (31987 -> 31997 bytes), so a regression here would have assert.deepEqual(listAckFragmentFiles(missingDir), []);
// reproduced the ratchet failure a real verification run already caught once.
const fragmentPath = path.join(REPO_ROOT, 'tests', 'emitted-drift-acks', '0000-legacy-migration.json');
const doc = JSON.parse(fs.readFileSync(fragmentPath, 'utf8'));
const entry = doc.paths['spec-phase.md'];
assert.ok(entry, 'the migrated fragment must still carry the spec-phase.md entry from #2733');
assert.match(entry.reason, /31987 -> 31997/, 'the exact byte delta must survive the migration');
// Isolated to JUST this one entry, not the whole 35-entry document: the migrated // On THIS checkout, ACK_DIR may or may not exist — other PRs merge fragments over
// fragment carries 34 OTHER already-spent entries for OTHER paths, which this // time, so this must not become a new pin either way. Assert the invariant that
// minimal repro's baseline/current never touches — including the full document here // holds regardless.
// would report those 34 as freshly-stale (nothing in THIS synthetic diff consumes assert.ok(Array.isArray(listAckFragmentFiles(ACK_DIR)));
// them), which is a fact about this test's narrow fixture, not about the migration. assert.ok(assertNoAllSpentFragments([]).ok);
// The deadlock-avoidance path the empty state now takes every day: a tree carrying
// no ack never consults the base, which is what keeps a repair PR landable — and is
// now the steady state rather than the exception.
const r = diffEmitted({ const r = diffEmitted({
baseline: mf({}), baseline: mf({}),
current: mf({}), current: mf({}),
changedPaths: [], changedPaths: [],
sizeBaseline: { 'spec-phase.md': 31987 }, ack: null,
sizeCurrent: { 'spec-phase.md': 31997 },
ack: { version: ACK_VERSION, paths: { 'spec-phase.md': entry } },
baseAck: null, baseAck: null,
}); });
assert.equal(r.grown.length, 1); assert.ok(r.ok);
assert.equal(r.grown[0].acked, true, 'the migrated entry must still clear the growth it was written for');
assert.deepEqual(r.staleAcks, []); assert.deepEqual(r.staleAcks, []);
assert.ok(r.ok); assert.deepEqual(r.spentAcks, []);
});
test('the full migrated document is safe to land: every entry is SPENT against the legacy file at base, none stale', () => {
// The actual migration PR's real shape: `next` still carries the legacy file with
// these SAME 35 entries (until this PR removes it), so every entry in the new
// fragment is already "spent" (base already explains it) rather than "live" — this
// PR introduces no NEW ripple of its own, it only relocates old acknowledgments.
// Getting this wrong (e.g. losing a reason's exact text in the move) would turn a
// spent entry into a freshly-unexplained "stale" one and red this very migration PR.
const fragmentPath = path.join(REPO_ROOT, 'tests', 'emitted-drift-acks', '0000-legacy-migration.json');
const doc = JSON.parse(fs.readFileSync(fragmentPath, 'utf8'));
const r = diffEmitted({
baseline: mf({}),
current: mf({}),
changedPaths: [],
ack: doc,
baseAck: doc, // the legacy file at `next` HEAD, byte-identical, before this PR removes it
});
assert.deepEqual(r.staleAcks, [], 'nothing in the migrated document should read as freshly unexplained');
assert.equal(r.spentAcks.length, Object.keys(doc.paths).length, 'every migrated entry must be recognized as already spent');
assert.ok(r.ok);
}); });
// ─── readAckFileAtRef: the base-side reader (#2789) ────────────────────────── // ─── readAckFileAtRef: the base-side reader (#2789) ──────────────────────────

View File

@@ -1,42 +0,0 @@
{
"version": 1,
"paths": {
"agents/gsd-advisor-researcher.toml": "#2834: Codex agent TOML now carries model-routing fields on first install.",
"agents/gsd-ai-researcher.toml": "#2834: Codex agent TOML now carries model-routing fields on first install.",
"agents/gsd-assumptions-analyzer.toml": "#2834: Codex agent TOML now carries model-routing fields on first install.",
"agents/gsd-code-reviewer.toml": "#2834: Codex agent TOML now carries model-routing fields on first install.",
"agents/gsd-codebase-mapper.toml": "#2834: Codex agent TOML now carries model-routing fields on first install.",
"agents/gsd-debug-session-manager.toml": "#2834: Codex agent TOML now carries model-routing fields on first install.",
"agents/gsd-debugger.toml": "#2834: Codex agent TOML now carries model-routing fields on first install.",
"agents/gsd-doc-classifier.toml": "#2834: Codex agent TOML now carries model-routing fields on first install.",
"agents/gsd-doc-synthesizer.toml": "#2834: Codex agent TOML now carries model-routing fields on first install.",
"agents/gsd-doc-verifier.toml": "#2834: Codex agent TOML now carries model-routing fields on first install.",
"agents/gsd-doc-writer.toml": "#2834: Codex agent TOML now carries model-routing fields on first install.",
"agents/gsd-domain-researcher.toml": "#2834: Codex agent TOML now carries model-routing fields on first install.",
"agents/gsd-eval-auditor.toml": "#2834: Codex agent TOML now carries model-routing fields on first install.",
"agents/gsd-eval-planner.toml": "#2834: Codex agent TOML now carries model-routing fields on first install.",
"agents/gsd-executor.toml": "#2834: Codex agent TOML now carries model-routing fields on first install.",
"agents/gsd-framework-selector.toml": "#2834: Codex agent TOML now carries model-routing fields on first install.",
"agents/gsd-integration-checker.toml": "#2834: Codex agent TOML now carries model-routing fields on first install.",
"agents/gsd-intel-updater.toml": "#2834: Codex agent TOML now carries model-routing fields on first install.",
"agents/gsd-mempalace-curator.toml": "#2834: Codex agent TOML now carries model-routing fields on first install.",
"agents/gsd-nyquist-auditor.toml": "#2834: Codex agent TOML now carries model-routing fields on first install.",
"agents/gsd-pattern-mapper.toml": "#2834: Codex agent TOML now carries model-routing fields on first install.",
"agents/gsd-phase-researcher.toml": "#2834: Codex agent TOML now carries model-routing fields on first install.",
"agents/gsd-plan-checker.toml": "#2834: Codex agent TOML now carries model-routing fields on first install.",
"agents/gsd-planner.toml": "#2834: Codex agent TOML now carries model-routing fields on first install.",
"agents/gsd-project-researcher.toml": "#2834: Codex agent TOML now carries model-routing fields on first install.",
"agents/gsd-research-synthesizer.toml": "#2834: Codex agent TOML now carries model-routing fields on first install.",
"agents/gsd-roadmapper.toml": "#2834: Codex agent TOML now carries model-routing fields on first install.",
"agents/gsd-security-auditor.toml": "#2834: Codex agent TOML now carries model-routing fields on first install.",
"agents/gsd-ui-auditor.toml": "#2834: Codex agent TOML now carries model-routing fields on first install.",
"agents/gsd-ui-checker.toml": "#2834: Codex agent TOML now carries model-routing fields on first install.",
"agents/gsd-ui-researcher.toml": "#2834: Codex agent TOML now carries model-routing fields on first install.",
"agents/gsd-user-profiler.toml": "#2834: Codex agent TOML now carries model-routing fields on first install.",
"agents/gsd-verifier.toml": "#2834: Codex agent TOML now carries model-routing fields on first install.",
"gsd-code-fixer.md": "#2647: the three worktree-path sites (setup_worktree bash, concrete-steps prose, critical_rules) replaced the hardcoded /tmp/sv- mktemp path with a repo-relative .claude/worktrees/rf-<phase>-<pid>-<epoch> path, and added a defense-in-depth padded_phase validation at the sink (the agent prompt is a literal bash contract any caller can spawn; the orchestrator validates upstream but the sink now self-defends against path-traversal/branch-name injection). On Windows/Git Bash the /tmp path landed outside the project tree (outside the session permission allowlist, prompting on every read) and mktemp's MAX_PATH substitute was un-removable; .claude/worktrees/ is the same dir the harness-managed executor worktrees use (gitignored via .claude/, inside the permission scope). Growth is the path-resolution bash (main_repo via `git worktree list --porcelain | awk`) + the $$-PID/epoch uniqueness replacing mktemp's XXXXXX + the padded_phase guard + the #2647 rationale comments at each site. Supersedes the prior #2825 attribution, whose gated-bash + guardrail growth is already in next.",
"spec-phase.md": {
"reason": "#2733: five transitions in gsd-core/workflows/spec-phase.md were re-pointed so control reaches the mandatory Step 5.5 edge-completeness and Step 5.6 prohibition-completeness probes, which no path could reach before. Four upstream gate-passed jumps went from 'Jump to Step 6' to 'Jump to Step 5.5', and Step 5.5's own terminal soft gate at :305 went from 'proceed to Step 6' to 'proceed to Step 5.6' so the common all-edges-resolved path stops skipping the prohibition probe. The +10 bytes is exactly those five targets growing by 2 bytes each ('Step 6' -> 'Step 5.5' / 'Step 5.6'); it is the literal fix, not incidental prose growth, and cannot be avoided without leaving a probe unreachable. Verified: 31987 -> 31997 bytes, DEFAULT tier, cap 40960. #3132: realigned retired covered/backstop-as-status vocab to resolved+verification. #3102: Step 5.5 now RENDERS the edge-probe coverage report into the model's context (a raw printf of $COVERAGE after the well-formedness guard) and binds those rows in the resolution loop and --auto as a deterministic FLOOR, plus the corrected block comment; load-bearing workflow instruction that makes ADR-550 D7b (deterministic propose + LLM resolve) real at runtime and honors ADR-857 sec98's recall gap (floor, not ceiling). 32238 -> 34056 bytes (+1818), DEFAULT tier, cap 40960. #2773: Step 5.5 now tells a `response_language` project that the edge-probe `$REQS_JSON` payload is engine input rather than user-facing output, so each requirement's `text` carries a faithful English translation while the SPEC keeps its original language and requirement ids stay unchanged. The shape cues in src/edge-probe.cts are English word-boundary regexes, so prose in another language matched nothing, classified to zero shapes, and put every row in the `unclassified` sentinel (#1110) — the taxonomy contributed nothing. Growth is that instruction plus the pointer to the authored `shapes` override for prose that classifies to zero even in English (measured: the issue's own repro sentence returns [] in English too, so translation is necessary but not sufficient), and a one-line fix an isolated security review surfaced in the same block — the empty/placeholder guard exited without `rm -f \"$REQS_JSON\"` while its engine-failure sibling below it did, stranding the SPEC requirement text in TMPDIR on every failed run. The instruction sits BEFORE the heredoc deliberately: the `$APPLICABLE = 0` warning fires only when every requirement is unclassified, so a partly-classified non-English spec would otherwise slip through with no signal. Load-bearing runtime instruction; the compiled engine is deliberately untouched (the `lang`-hint / per-language cue-set fix is out of scope per the #2773 triage). 34020 -> 35730 bytes (+1710), DEFAULT tier, cap 40960."
}
}
}

View File

@@ -1,8 +0,0 @@
{
"version": 1,
"paths": {
"spike.md": "#1700: create_manifest step now derives an explicit idea key and restructures the .planning/spikes/MANIFEST.md template from one flat top-level `## Idea` / `## Requirements` pair (single-idea only) to `## Ideas` > `### {idea-key}` subsections, each carrying its own scoped Requirements list — the fix for the leak where one idea's requirements bled into another idea's build via spike-wrap-up.md. Growth is: the idea-key derivation instructions and the expanded MANIFEST.md template (new/existing-idea/pre-#1700-flat-shape-migration branches) in create_manifest; an Idea column added to the `## Spikes` table header everywhere it's referenced; an `idea:` field added to the build_spikes README frontmatter template; the reground step scoping its established-requirements check to the current idea only; the frontier_mode load step and 'Get Alignment and Execute' step describing per-idea sections instead of one flat idea; and two success_criteria bullets updated to call out idea-key scoping. This is the write side of the fix, not incidental prose.",
"spike-wrap-up.md": "#1700: gather/synthesize/write_skill steps now resolve each spike's idea key (from its README `idea:` frontmatter, falling back to the `## Spikes` table's Idea column for pre-#1700 spikes) and scope which Requirements they read and re-emit, so a feature-area reference or the generated spike-findings SKILL.md never pulls in an unrelated idea's requirements — the read/re-emit side of the #1700 fix. Growth is: the idea-key resolution instructions added to the gather step; the synthesize step's Requirements block rewritten to pull ONLY from the owning idea key(s) with an explicit 'never include a requirement from an idea key that has no spike in this group' rule; the write_skill step's <context> and <requirements> blocks scoped to the wrapped idea key(s) instead of 'the whole MANIFEST.md'; and one new success_criteria bullet forbidding blended requirements across ideas.",
"sketch.md": "#1700: sketch.md's build_sketches step (b) reads spikes/MANIFEST.md's Requirements for non-negotiable design constraints; once spike.md restructures that file from one flat Requirements section to per-idea `### {idea-key}` sections (same PR), sketch.md's single-flat-section instruction would silently go stale — collateral of the seam this fix touches, required so sketch.md keeps reading the file correctly rather than a defect introduced by this change. Growth is the one clause distinguishing per-idea sections and instructing the reader to check the Requirements list of the relevant idea key (or all of them if none clearly matches)."
}
}

View File

@@ -1 +0,0 @@
{"version":1,"paths":{"progress.md":{"reason":"#1762: Route V.missing and Route V.unknown now consume the dynamic ${VERIFICATION_NEXT_ACTION} block (matching the existing V.gaps/V.human format) instead of a hardcoded duplicate string, so the routing text stays reassuring and in sync with src/verification.cts's reworded next_action — deliberate growth, not drift."}}}

View File

@@ -1,6 +0,0 @@
{
"version": 1,
"paths": {
"gsd-plan-checker.md": "#1954: Dimension 3 gains sub-dimension 3b (undeclared / temporal coupling), plus one success-criteria line. Growth is the new sub-dimension only — no existing text was rewritten. The addition is deliberately dense rather than extracted: ADR-1610 Decision 4 names eager `@`-import relocation as proxy-gaming (it shrinks the measured file while leaving loaded context unchanged or larger), legitimate extraction is Read-at-step lazy, and this agent has no lazy-read seam; issue #1954's approved scope is also explicitly 'No new files'. After the change the file sits at 48810 bytes against the LARGE tier hard cap of 49152 (tests/agent-size-budget.test.cjs), i.e. 342 bytes of headroom. That is deliberate and disclosed: the cap is not crossed and is not raised, but the next contributor who needs room in this agent must do a lazy extraction rather than add prose. Content justification: Dimension 3 proves declared dependency edges resolve and are acyclic, and execute-phase's intra-wave guard proves same-wave plans do not overlap in files_modified — neither axis sees an undeclared edge, so two same-wave plans coupled through a shared config key, table, migration, env var, singleton or cache (or through one plan's produced state) pass plan-check and fail intermittently under parallel execution. 3b is advisory only (WARNING, never blocker, per the issue's rejected-alternatives list) and reuses the existing dependency_correctness finding key so no consumer sees a new dimension. — #2401 append (merged into this fragment because two ack sources may never name the same path): the Verify Command Path Resolvability dimension is now a short stub pointing at the extracted gsd-core/references/verify-command-path-resolvability.md (the full dimension body — including the {VERIFY_PATHS} probe-consumption rules and severity/reason table — moved out to stay under the LARGE cap). The file is now 49064 bytes, 88 bytes of headroom under the same 49152 LARGE cap. — #3003 append (merged into this fragment because two ack sources may never name the same path): Dimension 3b's description of what the wave guard already covers now names both declared-scope channels (`files_modified`/`files_deleted`), so a delete-vs-modify pair between same-wave plans is attributed once on the file axis rather than re-reported as undeclared coupling. +43 bytes, landing at 49107 with 45 bytes of headroom under the same cap — the 88-byte warning above still stands and is now tighter."
}
}

View File

@@ -1,6 +0,0 @@
{
"version": 1,
"paths": {
"cleanup.md": "#2142: adds retroactive quick-task archival — identify_quick_tasks step (target-milestone selection), dry-run summary + AskUserQuestion, archive_quick_tasks step using the narrow `milestone.archive-quick` command (not `milestone.complete --archive-quick`, which cannot be safely re-run against an already-completed milestone — see the step's own note), and success_criteria updates; the command was renamed from `quick.archive` to `milestone.archive-quick` (code-review FIX 1: fold under the existing `milestone` namespace, no new top-level command). 10319 -> 14941 bytes. (complete-milestone.md's #2142 growth is acknowledged in tests/emitted-drift-acks/3409-unreachable-guard-arms.json — two ack sources may never name the same path.)"
}
}

View File

@@ -1,6 +0,0 @@
{
"version": 1,
"paths": {
"explore.md": "#2229 adds the three-way claim disposition (admit / refute / abstain) plus the Unresolved ledger to the /gsd-explore Step-3 research pass, so a surfaced research claim carries its grounding instead of being folded into confident prose unverified. The growth is the contract itself, written inline in the workflow body: the refute-then-ground-or-abstain spawn instruction, an authority-based decision procedure separating refute from abstain (without it the two arms describe the same event), the Unresolved-ledger output template and its five-value reason enum, a defined destination for an untagged finding, the two guards (conflict-abstention and the tier floor), and the Step 4-5 rule carrying the disposition into crystallized artifacts so an abstain is not laundered into flat prose downstream. That guidance is deliberately NOT relocated into an eagerly @-imported reference, which ADR-1610 Decision 4 names as gaming the size proxy (only lazy, Read-at-step extraction counts). Round 11 REDUCED the file: the tier floor now reads one signal (query resolve-model --pick tier) instead of two proxies plus a both-empty fail-safe, which retired the two-signal rationale, the resolveModelInternal precedence explanation, and two of the three disclosed residuals; the canonical gsd_run preamble also moved to an unconditional Step-1 block so declining the research offer cannot leave Step 5's commit unbootstrapped (still exactly one preamble per file, per runtime-launcher-parity). 11127 -> 21297 bytes (+10170 vs next base), DEFAULT tier, cap 40960 - 52% of budget."
}
}

View File

@@ -1,8 +0,0 @@
{
"version": 1,
"paths": {
"review.md": {
"reason": "#2295: the write_reviews step gained a models:/model_sources: render instruction plus a frontmatter example, teaching it to surface the per-lane resolved model recorded by resolveSpawnModel/runOpenAiCompatible (src/review-lane-runner.cts). Deliberate growth in runtime-loaded workflow text for the new feature, not converter drift."
}
}
}

View File

@@ -1,6 +0,0 @@
{
"version": 1,
"paths": {
"gsd-roadmapper.md": "#2255 round 10 Blocker 1: Step 7 now arms the gsd-write-guard single-use sentinel before each curated write. The roadmapper Writes both .planning/ROADMAP.md and .planning/STATE.md wholesale, and /gsd:new-milestone spawns it against the OUTGOING milestone's files — new-milestone's phases.clear archives phase DIRECTORIES, never ROADMAP.md, so nothing compacts it first and no ordering rule forces /gsd:complete-milestone to run beforehand. Measured against the shipped hook at the #973 file size (292 lines), a new 4-phase roadmap lands at 18.2% and an 8-phase one at 31.8%: both hard-blocked. The +1130 bytes are that wiring plus the rationale a future editor needs to keep it — the arming is per-target because the sentinel is path-bound and single-use, and each is gated on [ -f ] so the /gsd:new-project path (ENOENT-exempt) strands no unconsumed token. This is the escape-hatch binding the guard must carry to avoid blocking a first-party flow, not incidental prose growth."
}
}

View File

@@ -1,7 +0,0 @@
{
"$comment": "Growth ack (#2914 fragment). Reason: #2398 adds the consensus gate to the CYCLE_SUMMARY contract in plan-review-convergence.md step 5a — a claim-class split (existence-class needs source-grounding or corroboration; judgment-class counts unless its raiser opens with an evidence-quality discount marker), plus the all-marked fail-open and the leading-marker requirement. 30889 -> 33566 LF bytes (+2677). Marker literals are pinned to what gsd-core/bin/lib/review-lane-runner.cjs actually emits by a parity test in tests/plan-review-convergence.test.cjs, so the gate cannot key on a signal nothing produces. gsd-core/references/reviewer-instances.md also grew (+1823B) but is deliberately NOT acked here: currentSizes() (tests/helpers/emitted-runtime.cjs:916-929) scans only gsd-core/workflows/ and agents/, so references/ is outside the growth ratchet entirely and an entry for it would be inert. Its emitted-hash ripple is attributable to this diff and needs no acknowledgment.",
"version": 1,
"paths": {
"plan-review-convergence.md": "gsd-core/workflows/plan-review-convergence.md +2677B: consensus gate on newly-raised HIGHs, split by claim class, resolving B2 from closed PR #2417 (#2398)"
}
}

View File

@@ -1,6 +0,0 @@
{
"version": 1,
"paths": {
"settings.md": "#2486 \u2014 the Worktrees question now branches on the runtime's declared dispatch.isolation capability (resolved via the sentinel-free inspect-dispatch-isolation query) instead of offering Claude-only worktree isolation everywhere. Growth is the isolation-none branch, its explanatory notice, and the tri-state pre-selection rule for the broken-inheritance repair."
}
}

View File

@@ -1,8 +0,0 @@
{
"version": 1,
"paths": {
"code-review.md": {
"reason": "#2554: +5376 bytes vs next. resolve_depth was rewritten to call the new path-scoped depth resolver (src/code-review-depth.cts) — the two config reads, the JSON payload construction that passes config via env vars and the changed-file list via stdin, an executable misconfiguration guard that halts the review instead of a prose instruction, the provenance/warning/downgrade output, and the large-scope downgrade relocated out of compute_file_scope so one step owns depth. Replaces the spent 3503-diff-base-scope-anchor.json fragment (its code-review.md entry was consumed when #3503 merged at 49b60070a and can no longer clear anything — the base file is exactly the 34435-byte baseline this growth is measured against), mirroring how #3503 itself replaced the spent 3191-unanchored-grep-sites.json."
}
}
}

View File

@@ -1,25 +0,0 @@
{
"version": 1,
"paths": {
"gsd-core/references/checkpoints.md": "#2658: mentions CLAUDE.md in prose. convertClaudeToTraeMarkdown's CLAUDE.md replacement target changed from the bare directory '.trae/rules/' to the concrete file '.trae/rules/rules.md' for every CLAUDE.md mention (bare, ./-prefixed, backtick-wrapped, and the buggy .claude/-prefixed form that previously produced a malformed doubled path) \u2014 so trae-emitted output differs for every file that mentions CLAUDE.md, not only the ones that hit the reported bug. Content is otherwise byte-identical.",
"gsd-core/references/debugger-bug-taxonomy.md": "#2658: same CLAUDE.md replacement-target change as checkpoints.md above (bare directory '.trae/rules/' -> concrete file '.trae/rules/rules.md').",
"gsd-core/references/debugger-fix-acceptance.md": "#2658: same CLAUDE.md replacement-target change as checkpoints.md above (bare directory '.trae/rules/' -> concrete file '.trae/rules/rules.md').",
"gsd-core/references/debugger-repro-hardening.md": "#2658: same CLAUDE.md replacement-target change as checkpoints.md above (bare directory '.trae/rules/' -> concrete file '.trae/rules/rules.md').",
"gsd-core/references/debugger-sbfl.md": "#2658: same CLAUDE.md replacement-target change as checkpoints.md above (bare directory '.trae/rules/' -> concrete file '.trae/rules/rules.md').",
"gsd-core/references/git-integration.md": "#2658: same CLAUDE.md replacement-target change as checkpoints.md above (bare directory '.trae/rules/' -> concrete file '.trae/rules/rules.md').",
"gsd-core/references/planner-human-verify-mode.md": "#2658: same CLAUDE.md replacement-target change as checkpoints.md above (bare directory '.trae/rules/' -> concrete file '.trae/rules/rules.md').",
"gsd-core/templates/README.md": "#2658: same CLAUDE.md replacement-target change as checkpoints.md above (bare directory '.trae/rules/' -> concrete file '.trae/rules/rules.md').",
"gsd-core/templates/claude-md.md": "#2658: same CLAUDE.md replacement-target change as checkpoints.md above (bare directory '.trae/rules/' -> concrete file '.trae/rules/rules.md').",
"gsd-core/workflows/help/modes/full.md": "#2658: same CLAUDE.md replacement-target change as checkpoints.md above (bare directory '.trae/rules/' -> concrete file '.trae/rules/rules.md').",
"gsd-core/workflows/milestone-summary.md": "#2658: same CLAUDE.md replacement-target change as checkpoints.md above (bare directory '.trae/rules/' -> concrete file '.trae/rules/rules.md').",
"gsd-core/workflows/progress.md": "#2658: same CLAUDE.md replacement-target change as checkpoints.md above (bare directory '.trae/rules/' -> concrete file '.trae/rules/rules.md').",
"gsd-core/workflows/quick.md": "#2658: same CLAUDE.md replacement-target change as checkpoints.md above (bare directory '.trae/rules/' -> concrete file '.trae/rules/rules.md').",
"gsd-core/workflows/sketch-wrap-up.md": "#2658: same CLAUDE.md replacement-target change as checkpoints.md above (bare directory '.trae/rules/' -> concrete file '.trae/rules/rules.md').",
"gsd-core/workflows/spike-wrap-up.md": "#2658: same CLAUDE.md replacement-target change as checkpoints.md above (bare directory '.trae/rules/' -> concrete file '.trae/rules/rules.md').",
"gsd-core/workflows/update.md": "#2658: same CLAUDE.md replacement-target change as checkpoints.md above (bare directory '.trae/rules/' -> concrete file '.trae/rules/rules.md').",
"skills/gsd-ns-project/skills/profile-user/SKILL.md": "#2658: derived (via commands/gsd/profile-user.md, which mentions CLAUDE.md) through convertClaudeToTraeMarkdown; same replacement-target change as checkpoints.md above.",
"skills/gsd-ns-review/skills/code-review/SKILL.md": "#2658: derived (via commands/gsd/code-review.md, which mentions CLAUDE.md) through convertClaudeToTraeMarkdown; same replacement-target change as checkpoints.md above.",
"ingest-docs.md": "#2658: runtime-detection block gained a `/.trae/` path-based line and a `TRAE_CONFIG_DIR` env-var fallback line (the same trae-detection gap found in new-project.md, fixed here too since it is the identical defect in a sibling workflow). \u2014 #3423 append (epic #1891 F8): also grew with the <files_to_read> -> <required_reading> tag rename (15 chars/block, tag-token-only delta). \u2014 #3576 append: bare `references/<name>.md` cites repaired to the canonical `gsd-core/references/<name>.md` form (+36 bytes, 4 cite(s) \u00d7 9). Dead-pointer fix; no content change. \u2014 #3602 append: gains CLASSIFIER_MODEL/SYNTHESIZER_MODEL/ROADMAPPER_MODEL resolve-model bindings, a #2517 model-omit-on-inherit marker + rule block, and model= on all three subagent dispatch shapes (classifier prose fan-out, synthesizer Agent block, roadmapper Agent block) so dynamic_routing/model_profile tiers apply instead of the caller's session model.",
"new-project.md": "#2658: runtime-detection block gained a `/.trae/` path-based line and a `TRAE_CONFIG_DIR` env-var fallback line, so trae resolves to RUNTIME=trae instead of falling through to the claude default. \u2014 #3423 append (epic #1891 F8): also grew with the <files_to_read> -> <required_reading> tag rename (15 chars/block, tag-token-only delta)."
}
}

View File

@@ -1,63 +0,0 @@
{
"version": 1,
"paths": {
".kimi-code/hooks/gsd-check-update-worker.js": "#2755: kimi-code installs its hook bundle into Kimi Code own root (~/.kimi-code) instead of Kimi CLI ~/.kimi. The script bytes are unchanged - identical to what ~/.kimi previously received - so no source file under hooks/ moved and the diff cannot attribute the ripple. Only the install destination changed, at resolveKimiHooksTomlDir in src/runtime-homes.cts.",
".kimi-code/hooks/gsd-check-update.js": "#2755: kimi-code installs its hook bundle into Kimi Code own root (~/.kimi-code) instead of Kimi CLI ~/.kimi. The script bytes are unchanged - identical to what ~/.kimi previously received - so no source file under hooks/ moved and the diff cannot attribute the ripple. Only the install destination changed, at resolveKimiHooksTomlDir in src/runtime-homes.cts.",
".kimi-code/hooks/gsd-config-reload.js": "#2755: kimi-code installs its hook bundle into Kimi Code own root (~/.kimi-code) instead of Kimi CLI ~/.kimi. The script bytes are unchanged - identical to what ~/.kimi previously received - so no source file under hooks/ moved and the diff cannot attribute the ripple. Only the install destination changed, at resolveKimiHooksTomlDir in src/runtime-homes.cts.",
".kimi-code/hooks/gsd-context-monitor.js": "#2755: kimi-code installs its hook bundle into Kimi Code own root (~/.kimi-code) instead of Kimi CLI ~/.kimi. The script bytes are unchanged - identical to what ~/.kimi previously received - so no source file under hooks/ moved and the diff cannot attribute the ripple. Only the install destination changed, at resolveKimiHooksTomlDir in src/runtime-homes.cts.",
".kimi-code/hooks/gsd-cursor-post-tool.js": "#2755: kimi-code installs its hook bundle into Kimi Code own root (~/.kimi-code) instead of Kimi CLI ~/.kimi. The script bytes are unchanged - identical to what ~/.kimi previously received - so no source file under hooks/ moved and the diff cannot attribute the ripple. Only the install destination changed, at resolveKimiHooksTomlDir in src/runtime-homes.cts.",
".kimi-code/hooks/gsd-cursor-pre-tool.js": "#2755: kimi-code installs its hook bundle into Kimi Code own root (~/.kimi-code) instead of Kimi CLI ~/.kimi. The script bytes are unchanged - identical to what ~/.kimi previously received - so no source file under hooks/ moved and the diff cannot attribute the ripple. Only the install destination changed, at resolveKimiHooksTomlDir in src/runtime-homes.cts.",
".kimi-code/hooks/gsd-cursor-session-start.js": "#2755: kimi-code installs its hook bundle into Kimi Code own root (~/.kimi-code) instead of Kimi CLI ~/.kimi. The script bytes are unchanged - identical to what ~/.kimi previously received - so no source file under hooks/ moved and the diff cannot attribute the ripple. Only the install destination changed, at resolveKimiHooksTomlDir in src/runtime-homes.cts.",
".kimi-code/hooks/gsd-cursor-stop.js": "#2755: kimi-code installs its hook bundle into Kimi Code own root (~/.kimi-code) instead of Kimi CLI ~/.kimi. The script bytes are unchanged - identical to what ~/.kimi previously received - so no source file under hooks/ moved and the diff cannot attribute the ripple. Only the install destination changed, at resolveKimiHooksTomlDir in src/runtime-homes.cts.",
".kimi-code/hooks/gsd-cursor-subagent-start.js": "#2755: kimi-code installs its hook bundle into Kimi Code own root (~/.kimi-code) instead of Kimi CLI ~/.kimi. The script bytes are unchanged - identical to what ~/.kimi previously received - so no source file under hooks/ moved and the diff cannot attribute the ripple. Only the install destination changed, at resolveKimiHooksTomlDir in src/runtime-homes.cts.",
".kimi-code/hooks/gsd-cursor-subagent-stop.js": "#2755: kimi-code installs its hook bundle into Kimi Code own root (~/.kimi-code) instead of Kimi CLI ~/.kimi. The script bytes are unchanged - identical to what ~/.kimi previously received - so no source file under hooks/ moved and the diff cannot attribute the ripple. Only the install destination changed, at resolveKimiHooksTomlDir in src/runtime-homes.cts.",
".kimi-code/hooks/gsd-ensure-canonical-path.js": "#2755: kimi-code installs its hook bundle into Kimi Code own root (~/.kimi-code) instead of Kimi CLI ~/.kimi. The script bytes are unchanged - identical to what ~/.kimi previously received - so no source file under hooks/ moved and the diff cannot attribute the ripple. Only the install destination changed, at resolveKimiHooksTomlDir in src/runtime-homes.cts.",
".kimi-code/hooks/gsd-graphify-update.sh": "#2755: kimi-code installs its hook bundle into Kimi Code own root (~/.kimi-code) instead of Kimi CLI ~/.kimi. The script bytes are unchanged - identical to what ~/.kimi previously received - so no source file under hooks/ moved and the diff cannot attribute the ripple. Only the install destination changed, at resolveKimiHooksTomlDir in src/runtime-homes.cts.",
".kimi-code/hooks/gsd-phase-boundary.sh": "#2755: kimi-code installs its hook bundle into Kimi Code own root (~/.kimi-code) instead of Kimi CLI ~/.kimi. The script bytes are unchanged - identical to what ~/.kimi previously received - so no source file under hooks/ moved and the diff cannot attribute the ripple. Only the install destination changed, at resolveKimiHooksTomlDir in src/runtime-homes.cts.",
".kimi-code/hooks/gsd-prompt-guard.js": "#2755: kimi-code installs its hook bundle into Kimi Code own root (~/.kimi-code) instead of Kimi CLI ~/.kimi. The script bytes are unchanged - identical to what ~/.kimi previously received - so no source file under hooks/ moved and the diff cannot attribute the ripple. Only the install destination changed, at resolveKimiHooksTomlDir in src/runtime-homes.cts.",
".kimi-code/hooks/gsd-read-guard.js": "#2755: kimi-code installs its hook bundle into Kimi Code own root (~/.kimi-code) instead of Kimi CLI ~/.kimi. The script bytes are unchanged - identical to what ~/.kimi previously received - so no source file under hooks/ moved and the diff cannot attribute the ripple. Only the install destination changed, at resolveKimiHooksTomlDir in src/runtime-homes.cts.",
".kimi-code/hooks/gsd-read-injection-scanner.js": "#2755: kimi-code installs its hook bundle into Kimi Code own root (~/.kimi-code) instead of Kimi CLI ~/.kimi. The script bytes are unchanged - identical to what ~/.kimi previously received - so no source file under hooks/ moved and the diff cannot attribute the ripple. Only the install destination changed, at resolveKimiHooksTomlDir in src/runtime-homes.cts.",
".kimi-code/hooks/gsd-session-state.sh": "#2755: kimi-code installs its hook bundle into Kimi Code own root (~/.kimi-code) instead of Kimi CLI ~/.kimi. The script bytes are unchanged - identical to what ~/.kimi previously received - so no source file under hooks/ moved and the diff cannot attribute the ripple. Only the install destination changed, at resolveKimiHooksTomlDir in src/runtime-homes.cts.",
".kimi-code/hooks/gsd-statusline.js": "#2755: kimi-code installs its hook bundle into Kimi Code own root (~/.kimi-code) instead of Kimi CLI ~/.kimi. The script bytes are unchanged - identical to what ~/.kimi previously received - so no source file under hooks/ moved and the diff cannot attribute the ripple. Only the install destination changed, at resolveKimiHooksTomlDir in src/runtime-homes.cts.",
".kimi-code/hooks/gsd-update-banner.js": "#2755: kimi-code installs its hook bundle into Kimi Code own root (~/.kimi-code) instead of Kimi CLI ~/.kimi. The script bytes are unchanged - identical to what ~/.kimi previously received - so no source file under hooks/ moved and the diff cannot attribute the ripple. Only the install destination changed, at resolveKimiHooksTomlDir in src/runtime-homes.cts.",
".kimi-code/hooks/gsd-validate-commit.sh": "#2755: kimi-code installs its hook bundle into Kimi Code own root (~/.kimi-code) instead of Kimi CLI ~/.kimi. The script bytes are unchanged - identical to what ~/.kimi previously received - so no source file under hooks/ moved and the diff cannot attribute the ripple. Only the install destination changed, at resolveKimiHooksTomlDir in src/runtime-homes.cts.",
".kimi-code/hooks/gsd-windsurf-pre-command.js": "#2755: kimi-code installs its hook bundle into Kimi Code own root (~/.kimi-code) instead of Kimi CLI ~/.kimi. The script bytes are unchanged - identical to what ~/.kimi previously received - so no source file under hooks/ moved and the diff cannot attribute the ripple. Only the install destination changed, at resolveKimiHooksTomlDir in src/runtime-homes.cts.",
".kimi-code/hooks/gsd-windsurf-pre-write.js": "#2755: kimi-code installs its hook bundle into Kimi Code own root (~/.kimi-code) instead of Kimi CLI ~/.kimi. The script bytes are unchanged - identical to what ~/.kimi previously received - so no source file under hooks/ moved and the diff cannot attribute the ripple. Only the install destination changed, at resolveKimiHooksTomlDir in src/runtime-homes.cts.",
".kimi-code/hooks/gsd-workflow-guard.js": "#2755: kimi-code installs its hook bundle into Kimi Code own root (~/.kimi-code) instead of Kimi CLI ~/.kimi. The script bytes are unchanged - identical to what ~/.kimi previously received - so no source file under hooks/ moved and the diff cannot attribute the ripple. Only the install destination changed, at resolveKimiHooksTomlDir in src/runtime-homes.cts.",
".kimi-code/hooks/gsd-worktree-path-guard.js": "#2755: kimi-code installs its hook bundle into Kimi Code own root (~/.kimi-code) instead of Kimi CLI ~/.kimi. The script bytes are unchanged - identical to what ~/.kimi previously received - so no source file under hooks/ moved and the diff cannot attribute the ripple. Only the install destination changed, at resolveKimiHooksTomlDir in src/runtime-homes.cts.",
".kimi-code/hooks/gsd-write-guard.js": "#2755: kimi-code installs its hook bundle into Kimi Code own root (~/.kimi-code) instead of Kimi CLI ~/.kimi. The script bytes are unchanged - identical to what ~/.kimi previously received - so no source file under hooks/ moved and the diff cannot attribute the ripple. Only the install destination changed, at resolveKimiHooksTomlDir in src/runtime-homes.cts.",
".kimi-code/hooks/lib/cursor-workspace.js": "#2755: kimi-code installs its hook bundle into Kimi Code own root (~/.kimi-code) instead of Kimi CLI ~/.kimi. The script bytes are unchanged - identical to what ~/.kimi previously received - so no source file under hooks/ moved and the diff cannot attribute the ripple. Only the install destination changed, at resolveKimiHooksTomlDir in src/runtime-homes.cts.",
".kimi-code/hooks/lib/git-cmd.js": "#2755: kimi-code installs its hook bundle into Kimi Code own root (~/.kimi-code) instead of Kimi CLI ~/.kimi. The script bytes are unchanged - identical to what ~/.kimi previously received - so no source file under hooks/ moved and the diff cannot attribute the ripple. Only the install destination changed, at resolveKimiHooksTomlDir in src/runtime-homes.cts.",
".kimi-code/hooks/lib/gsd-graphify-rebuild.sh": "#2755: kimi-code installs its hook bundle into Kimi Code own root (~/.kimi-code) instead of Kimi CLI ~/.kimi. The script bytes are unchanged - identical to what ~/.kimi previously received - so no source file under hooks/ moved and the diff cannot attribute the ripple. Only the install destination changed, at resolveKimiHooksTomlDir in src/runtime-homes.cts.",
".kimi-code/hooks/managed-hooks-registry.cjs": "#2755: kimi-code installs its hook bundle into Kimi Code own root (~/.kimi-code) instead of Kimi CLI ~/.kimi. The script bytes are unchanged - identical to what ~/.kimi previously received - so no source file under hooks/ moved and the diff cannot attribute the ripple. Only the install destination changed, at resolveKimiHooksTomlDir in src/runtime-homes.cts.",
".kimi/hooks/gsd-check-update-worker.js": "#2755: the mirror of the .kimi-code entries above. kimi-code no longer emits into Kimi CLI native root at all, so this path disappears from the kimi-code manifest. Content unchanged; kimi own manifest still emits the same path and is untouched by this PR.",
".kimi/hooks/gsd-check-update.js": "#2755: the mirror of the .kimi-code entries above. kimi-code no longer emits into Kimi CLI native root at all, so this path disappears from the kimi-code manifest. Content unchanged; kimi own manifest still emits the same path and is untouched by this PR.",
".kimi/hooks/gsd-config-reload.js": "#2755: the mirror of the .kimi-code entries above. kimi-code no longer emits into Kimi CLI native root at all, so this path disappears from the kimi-code manifest. Content unchanged; kimi own manifest still emits the same path and is untouched by this PR.",
".kimi/hooks/gsd-context-monitor.js": "#2755: the mirror of the .kimi-code entries above. kimi-code no longer emits into Kimi CLI native root at all, so this path disappears from the kimi-code manifest. Content unchanged; kimi own manifest still emits the same path and is untouched by this PR.",
".kimi/hooks/gsd-cursor-post-tool.js": "#2755: the mirror of the .kimi-code entries above. kimi-code no longer emits into Kimi CLI native root at all, so this path disappears from the kimi-code manifest. Content unchanged; kimi own manifest still emits the same path and is untouched by this PR.",
".kimi/hooks/gsd-cursor-pre-tool.js": "#2755: the mirror of the .kimi-code entries above. kimi-code no longer emits into Kimi CLI native root at all, so this path disappears from the kimi-code manifest. Content unchanged; kimi own manifest still emits the same path and is untouched by this PR.",
".kimi/hooks/gsd-cursor-session-start.js": "#2755: the mirror of the .kimi-code entries above. kimi-code no longer emits into Kimi CLI native root at all, so this path disappears from the kimi-code manifest. Content unchanged; kimi own manifest still emits the same path and is untouched by this PR.",
".kimi/hooks/gsd-cursor-stop.js": "#2755: the mirror of the .kimi-code entries above. kimi-code no longer emits into Kimi CLI native root at all, so this path disappears from the kimi-code manifest. Content unchanged; kimi own manifest still emits the same path and is untouched by this PR.",
".kimi/hooks/gsd-cursor-subagent-start.js": "#2755: the mirror of the .kimi-code entries above. kimi-code no longer emits into Kimi CLI native root at all, so this path disappears from the kimi-code manifest. Content unchanged; kimi own manifest still emits the same path and is untouched by this PR.",
".kimi/hooks/gsd-cursor-subagent-stop.js": "#2755: the mirror of the .kimi-code entries above. kimi-code no longer emits into Kimi CLI native root at all, so this path disappears from the kimi-code manifest. Content unchanged; kimi own manifest still emits the same path and is untouched by this PR.",
".kimi/hooks/gsd-ensure-canonical-path.js": "#2755: the mirror of the .kimi-code entries above. kimi-code no longer emits into Kimi CLI native root at all, so this path disappears from the kimi-code manifest. Content unchanged; kimi own manifest still emits the same path and is untouched by this PR.",
".kimi/hooks/gsd-graphify-update.sh": "#2755: the mirror of the .kimi-code entries above. kimi-code no longer emits into Kimi CLI native root at all, so this path disappears from the kimi-code manifest. Content unchanged; kimi own manifest still emits the same path and is untouched by this PR.",
".kimi/hooks/gsd-phase-boundary.sh": "#2755: the mirror of the .kimi-code entries above. kimi-code no longer emits into Kimi CLI native root at all, so this path disappears from the kimi-code manifest. Content unchanged; kimi own manifest still emits the same path and is untouched by this PR.",
".kimi/hooks/gsd-prompt-guard.js": "#2755: the mirror of the .kimi-code entries above. kimi-code no longer emits into Kimi CLI native root at all, so this path disappears from the kimi-code manifest. Content unchanged; kimi own manifest still emits the same path and is untouched by this PR.",
".kimi/hooks/gsd-read-guard.js": "#2755: the mirror of the .kimi-code entries above. kimi-code no longer emits into Kimi CLI native root at all, so this path disappears from the kimi-code manifest. Content unchanged; kimi own manifest still emits the same path and is untouched by this PR.",
".kimi/hooks/gsd-read-injection-scanner.js": "#2755: the mirror of the .kimi-code entries above. kimi-code no longer emits into Kimi CLI native root at all, so this path disappears from the kimi-code manifest. Content unchanged; kimi own manifest still emits the same path and is untouched by this PR.",
".kimi/hooks/gsd-session-state.sh": "#2755: the mirror of the .kimi-code entries above. kimi-code no longer emits into Kimi CLI native root at all, so this path disappears from the kimi-code manifest. Content unchanged; kimi own manifest still emits the same path and is untouched by this PR.",
".kimi/hooks/gsd-statusline.js": "#2755: the mirror of the .kimi-code entries above. kimi-code no longer emits into Kimi CLI native root at all, so this path disappears from the kimi-code manifest. Content unchanged; kimi own manifest still emits the same path and is untouched by this PR.",
".kimi/hooks/gsd-update-banner.js": "#2755: the mirror of the .kimi-code entries above. kimi-code no longer emits into Kimi CLI native root at all, so this path disappears from the kimi-code manifest. Content unchanged; kimi own manifest still emits the same path and is untouched by this PR.",
".kimi/hooks/gsd-validate-commit.sh": "#2755: the mirror of the .kimi-code entries above. kimi-code no longer emits into Kimi CLI native root at all, so this path disappears from the kimi-code manifest. Content unchanged; kimi own manifest still emits the same path and is untouched by this PR.",
".kimi/hooks/gsd-windsurf-pre-command.js": "#2755: the mirror of the .kimi-code entries above. kimi-code no longer emits into Kimi CLI native root at all, so this path disappears from the kimi-code manifest. Content unchanged; kimi own manifest still emits the same path and is untouched by this PR.",
".kimi/hooks/gsd-windsurf-pre-write.js": "#2755: the mirror of the .kimi-code entries above. kimi-code no longer emits into Kimi CLI native root at all, so this path disappears from the kimi-code manifest. Content unchanged; kimi own manifest still emits the same path and is untouched by this PR.",
".kimi/hooks/gsd-workflow-guard.js": "#2755: the mirror of the .kimi-code entries above. kimi-code no longer emits into Kimi CLI native root at all, so this path disappears from the kimi-code manifest. Content unchanged; kimi own manifest still emits the same path and is untouched by this PR.",
".kimi/hooks/gsd-worktree-path-guard.js": "#2755: the mirror of the .kimi-code entries above. kimi-code no longer emits into Kimi CLI native root at all, so this path disappears from the kimi-code manifest. Content unchanged; kimi own manifest still emits the same path and is untouched by this PR.",
".kimi/hooks/gsd-write-guard.js": "#2755: the mirror of the .kimi-code entries above. kimi-code no longer emits into Kimi CLI native root at all, so this path disappears from the kimi-code manifest. Content unchanged; kimi own manifest still emits the same path and is untouched by this PR.",
".kimi/hooks/lib/cursor-workspace.js": "#2755: the mirror of the .kimi-code entries above. kimi-code no longer emits into Kimi CLI native root at all, so this path disappears from the kimi-code manifest. Content unchanged; kimi own manifest still emits the same path and is untouched by this PR.",
".kimi/hooks/lib/git-cmd.js": "#2755: the mirror of the .kimi-code entries above. kimi-code no longer emits into Kimi CLI native root at all, so this path disappears from the kimi-code manifest. Content unchanged; kimi own manifest still emits the same path and is untouched by this PR.",
".kimi/hooks/lib/gsd-graphify-rebuild.sh": "#2755: the mirror of the .kimi-code entries above. kimi-code no longer emits into Kimi CLI native root at all, so this path disappears from the kimi-code manifest. Content unchanged; kimi own manifest still emits the same path and is untouched by this PR.",
".kimi/hooks/managed-hooks-registry.cjs": "#2755: the mirror of the .kimi-code entries above. kimi-code no longer emits into Kimi CLI native root at all, so this path disappears from the kimi-code manifest. Content unchanged; kimi own manifest still emits the same path and is untouched by this PR."
}
}

View File

@@ -1,8 +0,0 @@
{
"version": 1,
"paths": {
"gsd-planner.md": {
"reason": "#2775: the STRIDE supply-chain row for npm/pip/cargo installs was rewritten from 'slopcheck + blocking human checkpoint for [ASSUMED]/[SUS]' to 'package-legitimacy gate + blocking human checkpoint for [ASSUMED]/[SUS]', matching ADR-0656 (registry-API verdicts are the gate; slopcheck is an optional escalate-only adapter no shipped configuration wires). The +14 bytes is the corrected mitigation description agents read at plan time, not incidental prose growth. #3565 appends the fenced `## Return Markers` section (~1.2 KB) enumerating the six exact stall-watch dispatch markers plan-phase.md matches on — the new registry lint requires every declared marker to be emitted in-fence by its producer, and the planner previously documented none of them anywhere. #2401 append (merged into this fragment because two ack sources may never name the same path): the 'Inherit the command that already worked' paragraph is now a short pointer to the extracted gsd-core/references/planner-verify-command-grounding.md (prior_verify_commands inheritance, npm --prefix grounding rules, and the guessing prohibition all moved to the reference). The file was 49274 bytes (49077 chars) immediately before the #3003 and #3299 arms below, both of which supersede that figure, well under the XL tier's 57344-byte cap, and 49077 chars (LF-normalized) under the separate 49152-char cap asserted by tests/planner-decomposition.test.cjs, tests/precondition-element.test.cjs, tests/reversibility-tagging.test.cjs, and tests/security.test.cjs. #3003 append (merged into this fragment because two ack sources may never name the same path): the implicit-dependency wave rule now computes same-wave overlap over files_modified PLUS files_deleted, so a plan deleting a file another same-wave plan edits is pushed to a later wave instead of racing it -- one branch removing what the other is writing is the sharpest conflict there is, and reading files_modified alone scored that pair conflict-free. Deliberately terse (+56 LF chars, landing at 49133 against the 49152-char cap named above, 19 chars of headroom). The `# Implicit dependency: files_modified overlap forces a later wave.` comment is left VERBATIM: tests/parallel-dependent-plans.test.cjs matches that exact unbackticked substring, so rewording it to name the new channel reds the suite -- the files_deleted addition rides in the pseudocode and the Rule sentence instead: the rationale is carried in docs/reference/plan-md.md, which has no cap. The next contributor who needs room in this agent must extract, not add prose. — #3299 append: the tracer task-shape template's <verify> now wraps its command in <automated>. The Nyquist Rule earlier in the file already required every <verify> to carry <automated>, but this tracer-specific template still showed the legacy bare-text form, so a planner following its own most specific template emitted tracers the #3299 feedback gate can never auto-continue on (the gate requires a <verify> carrying only <automated>) — making the fix unreachable on the default tracer-first path. Caught in peer review. Written on ONE line, matching the one-line <verify><automated> example under docs/reference/plan-md.md's \"## Reversibility\" heading (cited by section, not line: an earlier \":207\" citation was accurate when written and drifted with a later merge of next). Sizes re-measured against this merge of next, which landed the #3003 arm above: the file is 49146 chars after this change (49343 bytes), +13 chars on next's 49133, leaving 6 chars under the 49152-CHAR cap asserted independently by tests/planner-decomposition.test.cjs, tests/precondition-element.test.cjs, tests/reversibility-tagging.test.cjs and tests/security.test.cjs (all four assert `< 49152`, so 49146 passes). The 49090 / 62-chars-of-headroom figures this arm carried in earlier rounds were measured before #3003 and are superseded. Note the cap counts CHARACTERS, not bytes, and `wc -m` silently reports bytes under LC_ALL=C, so measure in a UTF-8 locale. The three-line form crossed the cap; the placeholder lost the word 'runnable' to buy that room — 'a real END-TO-END check' still carries the operative requirement, and the neighbouring prose already says the tracer carries the same <verify> and validation as any auto task. With 6 chars left this file is effectively full: the next contributor needing room here must extract to a reference file, not bump the cap."
}
}
}

View File

@@ -1,6 +0,0 @@
{
"version": 1,
"paths": {
"ship.md": "#2783: the ship-note protocol adds a bounded merge-state poll and an empty recovery commit when a skip token leaves a PR blocked with no checks. #3559: the ship:pre preflight replaced two hardcoded capId arms (security, broken-windows) with a single generic loop over every active kind==\"gate\" entry, dispatching by check shape through the generic predicate evaluator (ADR-2008/#2008) and honoring each gate's own blocking/onError — the same dispatch contract execute:wave:post, execute:post and plan:post already implement and references/loop-hook-dispatch.md already specifies. Before this, a third-party capability's blocking ship:pre gate was resolved and evaluable and then silently dropped, so a phase shipped past its own declared gate with no warning. Growth is the generic two-step contract (command-failure routed per onError, then the block decision routed per blocking), the every-other-capId evaluator arm, and the empty-activeHooks and unsupported-check-shape arms; the two named branches are retained verbatim inside the loop so their fail-closed semantics are unchanged and no gate is enforced twice. The generic arm also carries the in-context validation contract loop-hook-dispatch.md mandates for ref.command: #3559 is the first time a THIRD-PARTY manifest string reaches a shell at ship:pre, and gates[].check is not one of the four executable surfaces the install consent prompt discloses, so an unvalidated check.query would be a consent-model bypass. 33726 -> 36784 bytes (+3058), DEFAULT tier, cap 40960."
}
}

View File

@@ -1,8 +0,0 @@
{
"$comment": "Growth ack (#2914 fragment). Reason: #2845 makes a UI-SPEC component inventory falsifiable. gsd-ui-checker.md gains Dimension 7 (Inventory Provenance) — BLOCK/FLAG/PASS criteria, the two-shape provenance grammar it keys on, an unfilled-placeholder BLOCK rule, an example issue, and the allowlist-downgrade rule; 14118 -> 18180 LF bytes (+4062, DEFAULT cap 24576). gsd-ui-researcher.md gains the <component_inventory_gate> enumeration ladder and the identical grammar line it must record; 19557 -> 21474 (+1917, DEFAULT cap 24576). gsd-core/workflows/ui-phase.md is byte-unchanged (6->7 is same-width). The grammar line is byte-identical across template, checker and researcher, and the dimension roster is pinned across thirteen surfaces, by tests/ui-spec-inventory-provenance.test.cjs.",
"version": 1,
"paths": {
"gsd-ui-checker.md": "agents/gsd-ui-checker.md +4062B: Dimension 7 Inventory Provenance — an unsourced component inventory is a defect and is downgraded from a closed allowlist to a non-exhaustive list (#2845)",
"gsd-ui-researcher.md": "agents/gsd-ui-researcher.md +1917B: component-inventory enumeration gate — enumerate from the installed package and record command, count, resolved package@version and date (#2845)"
}
}

View File

@@ -1,6 +0,0 @@
{
"version": 1,
"paths": {
"gsd-executor.md": "#2943: the context7 doc-lookup block's ctx7 CLI-fallback rationale was rewritten to describe the real mechanism (custom subagents cannot see project-scoped .mcp.json; they inherit only user-scoped ~/.claude/mcp.json), replacing the wrong anthropics/claude-code#13898 'tools: frontmatter restriction' attribution. The tool name was also corrected (get-library-docs -> query-docs) and the params renamed (context7CompatibleLibraryId/topic -> libraryId/query). The +95 bytes is the longer-but-accurate mechanism description; it is the literal fix for the mis-attribution, not incidental prose growth, and the rationale must be correct because agents read it to decide when to fall back to the CLI. #3021 amendment: branch allow-list regex widened to accept worktree-wf_* (Workflow backend naming) alongside agent-*/worktree-agent-*. \u2014 #3210 append: the unmet-<precondition> branch now reports the returned checkpoint with **Gate:** blocking-human (both auto-mode bypass layers key on that gate; without it auto-mode silently auto-approved the checkpoint with a synthetic 'approved'), the auto-mode human-verify rule names precondition-unmet checkpoints as exempt, and checkpoint_return_format's Gate line notes precondition-unmet checkpoints report blocking-human; +134 bytes, still under the 49152 cap. \u2014 #3576 append: bare `references/<name>.md` cites repaired to the canonical `gsd-core/references/<name>.md` form (+18 bytes, 2 cite(s) \u00d7 9). Dead-pointer fix; no content change."
}
}

View File

@@ -1,8 +0,0 @@
{
"version": 1,
"paths": {
"gsd-integration-checker.md": "#2962: 1 bash block (line ~98 SUMMARY iteration) gained the nullglob shim for zsh portability of the for-glob loop.",
"resume-project.md": "#2962: 1 bash block (line ~66 plans-without-summaries scan) gained the nullglob shim for zsh portability of the for-glob loop.",
"audit-milestone.md": "#2962: 1 bash block (line ~126 requirements_completed extraction) gained the nullglob shim for zsh portability of the for-glob loop."
}
}

View File

@@ -1,8 +0,0 @@
{
"version": 1,
"paths": {
"pr-branch.md": {
"reason": "#3679 re-arm: +14 lines vs the #2971-acknowledged size. The #2971 rewrite already fixed the deletion class this issue reported (rm -f --ignore-unmatch + checkout HEAD restore — pre-existing target-tracked planning files survive); #3679 adds the piece the brief still required of verify: a PLANNING_DELETIONS count over git diff --name-status with a second must-be-0 gate beside $FORBIDDEN (name-only counting cannot see status, so a deleted allowed/structural planning path verified clean), plus its display line. Prior reason kept below for the audit trail. — #2971: +5729 bytes vs next. Adds the planning.pr_strict filter mode and repairs two verified defects in the same cherry-pick loop. The growth is four things, none of them prose padding: (1) the canonical TRANSIENT_DIRS/STRUCTURAL_RE declarations plus their per-mode FILTER_PATHS/FORBIDDEN_RE projections, which replace two duplicated hardcoded lists so create_pr_branch and verify can no longer disagree about what the filter promised; (2) a rewritten create_pr_branch loop — the old `git rm -r --cached` staged a DELETION of any .planning/ path the target branch already tracked, and left the picked file untracked on disk so a later commit touching that path aborted with \"untracked working tree files would be overwritten\" and every remaining commit was dropped, both reproduced against real git before the fix; the replacement forces filtered paths back to HEAD in index and worktree, halts on a conflict outside the filter instead of improvising, and skips a commit left empty by filtering; (3) a mode-derived verify step plus the advisory line naming the .planning/ paths default mode deliberately keeps, so correcting the assertion does not trade a permanently-wrong signal for silence; (4) a clean-working-tree precondition, required because the corrected filter now removes files from the working tree. Comments carry the why for each, because every one of them is a place a future edit would otherwise reintroduce the defect."
}
}
}

View File

@@ -1,6 +0,0 @@
{
"version": 1,
"paths": {
"update.md": "#2994 (epic #1671 Phase 6.3, FINAL slice): fragmentizes update.md onto the marker grammar, admitting `state:next-channel` (channel-banner section) and a new dedicated `init.update` entry point (cmdInitUpdate). update.md previously carried NO `gsd_run query init.*` call at all; the new INIT_UPDATE fetch (reusing the already-resolved $GSD_TOOLS from get_installed_version, dual .cjs/PATH-shim invocation style, matching the pre-existing update-context call) plus its surrounding comment outweighs the single `<!-- gsd:section -->` marker-pair stub that replaces the extracted channel-banner prose (now living in gsd-core/workflows/update/steps/channel-banner.md). Net SOURCE growth is +703 bytes (26,024 -> 26,727). The pre-existing TAG=\"next\"/TAG=\"latest\" case-statement in parse_update_channel is deliberately left byte-identical — issue #815's regression test (tests/issue-815-update-next-channel.test.cjs) asserts that literal text stays in the workflow, since the npm dist-tag selection must run in the workflow's own shell before any gsd_run round-trip. The EMITTED artifact composeWorkflow produces at install time still includes the extracted prose verbatim (markers strip, gap+section bodies re-join byte-for-byte) when the atom is unresolved (section_manifest null -> read-everything fallback), so installed behavior is unchanged; only the SOURCE file's on-disk byte count moves."
}
}

View File

@@ -1,7 +0,0 @@
{
"$comment": "Growth ack (#2914 fragment). RE-ARMED for #3683 (+238B, gated extract-learnings wiring in auto_copy_learnings); prior #3003 reason retained after the em-dash. Reason: #3003 threads a plan-declared deletion list from plan frontmatter to the cleanup-wave deletions guard. execute-phase.md gains --deletions \"$PLAN_DELETIONS\" on the record-agent call; 92326 -> 92356 LF bytes (+30). Supersedes the spent #2856 fragment entry (tests/emitted-drift-acks/2856-live-dom-uat.json, merged into next so its ripple is already absorbed at the base and it can no longer clear anything), which also named execute-phase.md and would otherwise double-ack the same path — the same supersede that entry itself performed on the spent #3370 fragment, which had performed it on #3324. Only this path needs an entry: currentSizes() (tests/helpers/emitted-runtime.cjs:916-929) reads gsd-core/workflows/ and agents/ with a NON-recursive readdirSync that skips directories, so the two other grown shipped files are outside the growth ratchet — gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md (+885) sits in a subdirectory and gsd-core/templates/phase-prompt.md (+285) is under templates/. Their emitted-hash ripples are attributable to this diff and need no acknowledgment.",
"version": 1,
"paths": {
"execute-phase.md": "gsd-core/workflows/execute-phase.md +1786B: #3684 wires condition 3 of discover_and_group_plans to roadmap.analyze's roadmap_complete (bash read + three-way branch) — the verified-but-never-marked-complete shape now resumes at update_roadmap instead of exiting clean forever. Supersedes the spent #3683 entry (gated extract-learnings in auto_copy_learnings), same chain #3683 ← #3003 ← #2856 ← #3370 ← #3324."
}
}

View File

@@ -1,76 +0,0 @@
{
"version": 1,
"paths": {
".agents/skills/gsd-add-tests/SKILL.md": "#3004: Codex adapter builder (getCodexSkillAdapterHeader in bin/install.js) changed collaboration-tool vocabulary \u2014 wait(ids)\u2192wait_agent, gated close_agent, added task_name+fork_turns. Every Codex SKILL.md embeds this adapter block, so all ripple.#3423 append (epic #1891 F8): body also moved with the <files_to_read> -> <required_reading> rename in its embedded workflow/command/capability sources (the skills-from-commands rule expects a commands/gsd change this diff does not carry) \u2014 deliberate, tag-token-only delta.",
".agents/skills/gsd-ai-integration-phase/SKILL.md": "#3004: Codex adapter builder (getCodexSkillAdapterHeader in bin/install.js) changed collaboration-tool vocabulary \u2014 wait(ids)\u2192wait_agent, gated close_agent, added task_name+fork_turns. Every Codex SKILL.md embeds this adapter block, so all ripple.#3423 append (epic #1891 F8): body also moved with the <files_to_read> -> <required_reading> rename in its embedded workflow/command/capability sources (the skills-from-commands rule expects a commands/gsd change this diff does not carry) \u2014 deliberate, tag-token-only delta.",
".agents/skills/gsd-audit-fix/SKILL.md": "#3004: Codex adapter builder (getCodexSkillAdapterHeader in bin/install.js) changed collaboration-tool vocabulary \u2014 wait(ids)\u2192wait_agent, gated close_agent, added task_name+fork_turns. Every Codex SKILL.md embeds this adapter block, so all ripple.#3423 append (epic #1891 F8): body also moved with the <files_to_read> -> <required_reading> rename in its embedded workflow/command/capability sources (the skills-from-commands rule expects a commands/gsd change this diff does not carry) \u2014 deliberate, tag-token-only delta.",
".agents/skills/gsd-audit-milestone/SKILL.md": "#3004: Codex adapter builder (getCodexSkillAdapterHeader in bin/install.js) changed collaboration-tool vocabulary \u2014 wait(ids)\u2192wait_agent, gated close_agent, added task_name+fork_turns. Every Codex SKILL.md embeds this adapter block, so all ripple.#3423 append (epic #1891 F8): body also moved with the <files_to_read> -> <required_reading> rename in its embedded workflow/command/capability sources (the skills-from-commands rule expects a commands/gsd change this diff does not carry) \u2014 deliberate, tag-token-only delta.",
".agents/skills/gsd-audit-uat/SKILL.md": "#3004: Codex adapter builder (getCodexSkillAdapterHeader in bin/install.js) changed collaboration-tool vocabulary \u2014 wait(ids)\u2192wait_agent, gated close_agent, added task_name+fork_turns. Every Codex SKILL.md embeds this adapter block, so all ripple.#3423 append (epic #1891 F8): body also moved with the <files_to_read> -> <required_reading> rename in its embedded workflow/command/capability sources (the skills-from-commands rule expects a commands/gsd change this diff does not carry) \u2014 deliberate, tag-token-only delta.",
".agents/skills/gsd-autonomous/SKILL.md": "#3004: Codex adapter builder (getCodexSkillAdapterHeader in bin/install.js) changed collaboration-tool vocabulary \u2014 wait(ids)\u2192wait_agent, gated close_agent, added task_name+fork_turns. Every Codex SKILL.md embeds this adapter block, so all ripple.#3423 append (epic #1891 F8): body also moved with the <files_to_read> -> <required_reading> rename in its embedded workflow/command/capability sources (the skills-from-commands rule expects a commands/gsd change this diff does not carry) \u2014 deliberate, tag-token-only delta.",
".agents/skills/gsd-capture/SKILL.md": "#3004: Codex adapter builder (getCodexSkillAdapterHeader in bin/install.js) changed collaboration-tool vocabulary \u2014 wait(ids)\u2192wait_agent, gated close_agent, added task_name+fork_turns. Every Codex SKILL.md embeds this adapter block, so all ripple.#3423 append (epic #1891 F8): body also moved with the <files_to_read> -> <required_reading> rename in its embedded workflow/command/capability sources (the skills-from-commands rule expects a commands/gsd change this diff does not carry) \u2014 deliberate, tag-token-only delta.",
".agents/skills/gsd-cleanup/SKILL.md": "#3004: Codex adapter builder (getCodexSkillAdapterHeader in bin/install.js) changed collaboration-tool vocabulary \u2014 wait(ids)\u2192wait_agent, gated close_agent, added task_name+fork_turns. Every Codex SKILL.md embeds this adapter block, so all ripple.#3423 append (epic #1891 F8): body also moved with the <files_to_read> -> <required_reading> rename in its embedded workflow/command/capability sources (the skills-from-commands rule expects a commands/gsd change this diff does not carry) \u2014 deliberate, tag-token-only delta.",
".agents/skills/gsd-code-review/SKILL.md": "#3004: Codex adapter builder (getCodexSkillAdapterHeader in bin/install.js) changed collaboration-tool vocabulary \u2014 wait(ids)\u2192wait_agent, gated close_agent, added task_name+fork_turns. Every Codex SKILL.md embeds this adapter block, so all ripple.",
".agents/skills/gsd-complete-milestone/SKILL.md": "#3004: Codex adapter builder (getCodexSkillAdapterHeader in bin/install.js) changed collaboration-tool vocabulary \u2014 wait(ids)\u2192wait_agent, gated close_agent, added task_name+fork_turns. Every Codex SKILL.md embeds this adapter block, so all ripple.#3423 append (epic #1891 F8): body also moved with the <files_to_read> -> <required_reading> rename in its embedded workflow/command/capability sources (the skills-from-commands rule expects a commands/gsd change this diff does not carry) \u2014 deliberate, tag-token-only delta.",
".agents/skills/gsd-config/SKILL.md": "#3004: Codex adapter builder (getCodexSkillAdapterHeader in bin/install.js) changed collaboration-tool vocabulary \u2014 wait(ids)\u2192wait_agent, gated close_agent, added task_name+fork_turns. Every Codex SKILL.md embeds this adapter block, so all ripple.#3423 append (epic #1891 F8): body also moved with the <files_to_read> -> <required_reading> rename in its embedded workflow/command/capability sources (the skills-from-commands rule expects a commands/gsd change this diff does not carry) \u2014 deliberate, tag-token-only delta.",
".agents/skills/gsd-debug/SKILL.md": "#3004: Codex adapter builder (getCodexSkillAdapterHeader in bin/install.js) changed collaboration-tool vocabulary \u2014 wait(ids)\u2192wait_agent, gated close_agent, added task_name+fork_turns. Every Codex SKILL.md embeds this adapter block, so all ripple.#3423 append (epic #1891 F8): body also moved with the <files_to_read> -> <required_reading> rename in its embedded workflow/command/capability sources (the skills-from-commands rule expects a commands/gsd change this diff does not carry) \u2014 deliberate, tag-token-only delta.",
".agents/skills/gsd-discuss-phase/SKILL.md": "#3004: Codex adapter builder (getCodexSkillAdapterHeader in bin/install.js) changed collaboration-tool vocabulary \u2014 wait(ids)\u2192wait_agent, gated close_agent, added task_name+fork_turns. Every Codex SKILL.md embeds this adapter block, so all ripple.#3423 append (epic #1891 F8): body also moved with the <files_to_read> -> <required_reading> rename in its embedded workflow/command/capability sources (the skills-from-commands rule expects a commands/gsd change this diff does not carry) \u2014 deliberate, tag-token-only delta.",
".agents/skills/gsd-docs-update/SKILL.md": "#3004: Codex adapter builder (getCodexSkillAdapterHeader in bin/install.js) changed collaboration-tool vocabulary \u2014 wait(ids)\u2192wait_agent, gated close_agent, added task_name+fork_turns. Every Codex SKILL.md embeds this adapter block, so all ripple.#3423 append (epic #1891 F8): body also moved with the <files_to_read> -> <required_reading> rename in its embedded workflow/command/capability sources (the skills-from-commands rule expects a commands/gsd change this diff does not carry) \u2014 deliberate, tag-token-only delta.",
".agents/skills/gsd-eval-review/SKILL.md": "#3004: Codex adapter builder (getCodexSkillAdapterHeader in bin/install.js) changed collaboration-tool vocabulary \u2014 wait(ids)\u2192wait_agent, gated close_agent, added task_name+fork_turns. Every Codex SKILL.md embeds this adapter block, so all ripple.#3423 append (epic #1891 F8): body also moved with the <files_to_read> -> <required_reading> rename in its embedded workflow/command/capability sources (the skills-from-commands rule expects a commands/gsd change this diff does not carry) \u2014 deliberate, tag-token-only delta.",
".agents/skills/gsd-execute-phase/SKILL.md": "#3004: Codex adapter builder (getCodexSkillAdapterHeader in bin/install.js) changed collaboration-tool vocabulary \u2014 wait(ids)\u2192wait_agent, gated close_agent, added task_name+fork_turns. Every Codex SKILL.md embeds this adapter block, so all ripple.",
".agents/skills/gsd-explore/SKILL.md": "#3004: Codex adapter builder (getCodexSkillAdapterHeader in bin/install.js) changed collaboration-tool vocabulary \u2014 wait(ids)\u2192wait_agent, gated close_agent, added task_name+fork_turns. Every Codex SKILL.md embeds this adapter block, so all ripple.#3423 append (epic #1891 F8): body also moved with the <files_to_read> -> <required_reading> rename in its embedded workflow/command/capability sources (the skills-from-commands rule expects a commands/gsd change this diff does not carry) \u2014 deliberate, tag-token-only delta.",
".agents/skills/gsd-extract-learnings/SKILL.md": "#3004: Codex adapter builder (getCodexSkillAdapterHeader in bin/install.js) changed collaboration-tool vocabulary \u2014 wait(ids)\u2192wait_agent, gated close_agent, added task_name+fork_turns. Every Codex SKILL.md embeds this adapter block, so all ripple.#3423 append (epic #1891 F8): body also moved with the <files_to_read> -> <required_reading> rename in its embedded workflow/command/capability sources (the skills-from-commands rule expects a commands/gsd change this diff does not carry) \u2014 deliberate, tag-token-only delta.",
".agents/skills/gsd-fast/SKILL.md": "#3004: Codex adapter builder (getCodexSkillAdapterHeader in bin/install.js) changed collaboration-tool vocabulary \u2014 wait(ids)\u2192wait_agent, gated close_agent, added task_name+fork_turns. Every Codex SKILL.md embeds this adapter block, so all ripple.#3423 append (epic #1891 F8): body also moved with the <files_to_read> -> <required_reading> rename in its embedded workflow/command/capability sources (the skills-from-commands rule expects a commands/gsd change this diff does not carry) \u2014 deliberate, tag-token-only delta.",
".agents/skills/gsd-forensics/SKILL.md": "#3004: Codex adapter builder (getCodexSkillAdapterHeader in bin/install.js) changed collaboration-tool vocabulary \u2014 wait(ids)\u2192wait_agent, gated close_agent, added task_name+fork_turns. Every Codex SKILL.md embeds this adapter block, so all ripple.#3423 append (epic #1891 F8): body also moved with the <files_to_read> -> <required_reading> rename in its embedded workflow/command/capability sources (the skills-from-commands rule expects a commands/gsd change this diff does not carry) \u2014 deliberate, tag-token-only delta.",
".agents/skills/gsd-graphify/SKILL.md": "#3004: Codex adapter builder (getCodexSkillAdapterHeader in bin/install.js) changed collaboration-tool vocabulary \u2014 wait(ids)\u2192wait_agent, gated close_agent, added task_name+fork_turns. Every Codex SKILL.md embeds this adapter block, so all ripple.#3423 append (epic #1891 F8): body also moved with the <files_to_read> -> <required_reading> rename in its embedded workflow/command/capability sources (the skills-from-commands rule expects a commands/gsd change this diff does not carry) \u2014 deliberate, tag-token-only delta.",
".agents/skills/gsd-health/SKILL.md": "#3004: Codex adapter builder (getCodexSkillAdapterHeader in bin/install.js) changed collaboration-tool vocabulary \u2014 wait(ids)\u2192wait_agent, gated close_agent, added task_name+fork_turns. Every Codex SKILL.md embeds this adapter block, so all ripple.#3423 append (epic #1891 F8): body also moved with the <files_to_read> -> <required_reading> rename in its embedded workflow/command/capability sources (the skills-from-commands rule expects a commands/gsd change this diff does not carry) \u2014 deliberate, tag-token-only delta.",
".agents/skills/gsd-help/SKILL.md": "#3004: Codex adapter builder (getCodexSkillAdapterHeader in bin/install.js) changed collaboration-tool vocabulary \u2014 wait(ids)\u2192wait_agent, gated close_agent, added task_name+fork_turns. Every Codex SKILL.md embeds this adapter block, so all ripple.#3423 append (epic #1891 F8): body also moved with the <files_to_read> -> <required_reading> rename in its embedded workflow/command/capability sources (the skills-from-commands rule expects a commands/gsd change this diff does not carry) \u2014 deliberate, tag-token-only delta.",
".agents/skills/gsd-import/SKILL.md": "#3004: Codex adapter builder (getCodexSkillAdapterHeader in bin/install.js) changed collaboration-tool vocabulary \u2014 wait(ids)\u2192wait_agent, gated close_agent, added task_name+fork_turns. Every Codex SKILL.md embeds this adapter block, so all ripple.#3423 append (epic #1891 F8): body also moved with the <files_to_read> -> <required_reading> rename in its embedded workflow/command/capability sources (the skills-from-commands rule expects a commands/gsd change this diff does not carry) \u2014 deliberate, tag-token-only delta.",
".agents/skills/gsd-inbox/SKILL.md": "#3004: Codex adapter builder (getCodexSkillAdapterHeader in bin/install.js) changed collaboration-tool vocabulary \u2014 wait(ids)\u2192wait_agent, gated close_agent, added task_name+fork_turns. Every Codex SKILL.md embeds this adapter block, so all ripple.#3423 append (epic #1891 F8): body also moved with the <files_to_read> -> <required_reading> rename in its embedded workflow/command/capability sources (the skills-from-commands rule expects a commands/gsd change this diff does not carry) \u2014 deliberate, tag-token-only delta.",
".agents/skills/gsd-ingest-docs/SKILL.md": "#3004: Codex adapter builder (getCodexSkillAdapterHeader in bin/install.js) changed collaboration-tool vocabulary \u2014 wait(ids)\u2192wait_agent, gated close_agent, added task_name+fork_turns. Every Codex SKILL.md embeds this adapter block, so all ripple.#3423 append (epic #1891 F8): body also moved with the <files_to_read> -> <required_reading> rename in its embedded workflow/command/capability sources (the skills-from-commands rule expects a commands/gsd change this diff does not carry) \u2014 deliberate, tag-token-only delta.",
".agents/skills/gsd-manager/SKILL.md": "#3004: Codex adapter builder (getCodexSkillAdapterHeader in bin/install.js) changed collaboration-tool vocabulary \u2014 wait(ids)\u2192wait_agent, gated close_agent, added task_name+fork_turns. Every Codex SKILL.md embeds this adapter block, so all ripple.#3423 append (epic #1891 F8): body also moved with the <files_to_read> -> <required_reading> rename in its embedded workflow/command/capability sources (the skills-from-commands rule expects a commands/gsd change this diff does not carry) \u2014 deliberate, tag-token-only delta.",
".agents/skills/gsd-map-codebase/SKILL.md": "#3004: Codex adapter builder (getCodexSkillAdapterHeader in bin/install.js) changed collaboration-tool vocabulary \u2014 wait(ids)\u2192wait_agent, gated close_agent, added task_name+fork_turns. Every Codex SKILL.md embeds this adapter block, so all ripple.#3423 append (epic #1891 F8): body also moved with the <files_to_read> -> <required_reading> rename in its embedded workflow/command/capability sources (the skills-from-commands rule expects a commands/gsd change this diff does not carry) \u2014 deliberate, tag-token-only delta.",
".agents/skills/gsd-mempalace-capture/SKILL.md": "#3004: Codex adapter builder (getCodexSkillAdapterHeader in bin/install.js) changed collaboration-tool vocabulary \u2014 wait(ids)\u2192wait_agent, gated close_agent, added task_name+fork_turns. Every Codex SKILL.md embeds this adapter block, so all ripple.#3423 append (epic #1891 F8): body also moved with the <files_to_read> -> <required_reading> rename in its embedded workflow/command/capability sources (the skills-from-commands rule expects a commands/gsd change this diff does not carry) \u2014 deliberate, tag-token-only delta.",
".agents/skills/gsd-mempalace-recall/SKILL.md": "#3004: Codex adapter builder (getCodexSkillAdapterHeader in bin/install.js) changed collaboration-tool vocabulary \u2014 wait(ids)\u2192wait_agent, gated close_agent, added task_name+fork_turns. Every Codex SKILL.md embeds this adapter block, so all ripple.#3423 append (epic #1891 F8): body also moved with the <files_to_read> -> <required_reading> rename in its embedded workflow/command/capability sources (the skills-from-commands rule expects a commands/gsd change this diff does not carry) \u2014 deliberate, tag-token-only delta.",
".agents/skills/gsd-milestone-summary/SKILL.md": "#3004: Codex adapter builder (getCodexSkillAdapterHeader in bin/install.js) changed collaboration-tool vocabulary \u2014 wait(ids)\u2192wait_agent, gated close_agent, added task_name+fork_turns. Every Codex SKILL.md embeds this adapter block, so all ripple.#3423 append (epic #1891 F8): body also moved with the <files_to_read> -> <required_reading> rename in its embedded workflow/command/capability sources (the skills-from-commands rule expects a commands/gsd change this diff does not carry) \u2014 deliberate, tag-token-only delta.",
".agents/skills/gsd-mvp-phase/SKILL.md": "#3004: Codex adapter builder (getCodexSkillAdapterHeader in bin/install.js) changed collaboration-tool vocabulary \u2014 wait(ids)\u2192wait_agent, gated close_agent, added task_name+fork_turns. Every Codex SKILL.md embeds this adapter block, so all ripple.#3423 append (epic #1891 F8): body also moved with the <files_to_read> -> <required_reading> rename in its embedded workflow/command/capability sources (the skills-from-commands rule expects a commands/gsd change this diff does not carry) \u2014 deliberate, tag-token-only delta.",
".agents/skills/gsd-new-milestone/SKILL.md": "#3004: Codex adapter builder (getCodexSkillAdapterHeader in bin/install.js) changed collaboration-tool vocabulary \u2014 wait(ids)\u2192wait_agent, gated close_agent, added task_name+fork_turns. Every Codex SKILL.md embeds this adapter block, so all ripple.",
".agents/skills/gsd-new-project/SKILL.md": "#3004: Codex adapter builder (getCodexSkillAdapterHeader in bin/install.js) changed collaboration-tool vocabulary \u2014 wait(ids)\u2192wait_agent, gated close_agent, added task_name+fork_turns. Every Codex SKILL.md embeds this adapter block, so all ripple.#3423 append (epic #1891 F8): body also moved with the <files_to_read> -> <required_reading> rename in its embedded workflow/command/capability sources (the skills-from-commands rule expects a commands/gsd change this diff does not carry) \u2014 deliberate, tag-token-only delta.",
".agents/skills/gsd-next/SKILL.md": "#3004: Codex adapter builder (getCodexSkillAdapterHeader in bin/install.js) changed collaboration-tool vocabulary \u2014 wait(ids)\u2192wait_agent, gated close_agent, added task_name+fork_turns. Every Codex SKILL.md embeds this adapter block, so all ripple.#3423 append (epic #1891 F8): body also moved with the <files_to_read> -> <required_reading> rename in its embedded workflow/command/capability sources (the skills-from-commands rule expects a commands/gsd change this diff does not carry) \u2014 deliberate, tag-token-only delta.",
".agents/skills/gsd-ns-context/SKILL.md": "#3004: Codex adapter builder (getCodexSkillAdapterHeader in bin/install.js) changed collaboration-tool vocabulary \u2014 wait(ids)\u2192wait_agent, gated close_agent, added task_name+fork_turns. Every Codex SKILL.md embeds this adapter block, so all ripple.#3423 append (epic #1891 F8): body also moved with the <files_to_read> -> <required_reading> rename in its embedded workflow/command/capability sources (the skills-from-commands rule expects a commands/gsd change this diff does not carry) \u2014 deliberate, tag-token-only delta.",
".agents/skills/gsd-ns-ideate/SKILL.md": "#3004: Codex adapter builder (getCodexSkillAdapterHeader in bin/install.js) changed collaboration-tool vocabulary \u2014 wait(ids)\u2192wait_agent, gated close_agent, added task_name+fork_turns. Every Codex SKILL.md embeds this adapter block, so all ripple.#3423 append (epic #1891 F8): body also moved with the <files_to_read> -> <required_reading> rename in its embedded workflow/command/capability sources (the skills-from-commands rule expects a commands/gsd change this diff does not carry) \u2014 deliberate, tag-token-only delta.",
".agents/skills/gsd-ns-manage/SKILL.md": "#3004: Codex adapter builder (getCodexSkillAdapterHeader in bin/install.js) changed collaboration-tool vocabulary \u2014 wait(ids)\u2192wait_agent, gated close_agent, added task_name+fork_turns. Every Codex SKILL.md embeds this adapter block, so all ripple.#3423 append (epic #1891 F8): body also moved with the <files_to_read> -> <required_reading> rename in its embedded workflow/command/capability sources (the skills-from-commands rule expects a commands/gsd change this diff does not carry) \u2014 deliberate, tag-token-only delta.",
".agents/skills/gsd-ns-project/SKILL.md": "#3004: Codex adapter builder (getCodexSkillAdapterHeader in bin/install.js) changed collaboration-tool vocabulary \u2014 wait(ids)\u2192wait_agent, gated close_agent, added task_name+fork_turns. Every Codex SKILL.md embeds this adapter block, so all ripple.#3423 append (epic #1891 F8): body also moved with the <files_to_read> -> <required_reading> rename in its embedded workflow/command/capability sources (the skills-from-commands rule expects a commands/gsd change this diff does not carry) \u2014 deliberate, tag-token-only delta.",
".agents/skills/gsd-ns-review/SKILL.md": "#3004: Codex adapter builder (getCodexSkillAdapterHeader in bin/install.js) changed collaboration-tool vocabulary \u2014 wait(ids)\u2192wait_agent, gated close_agent, added task_name+fork_turns. Every Codex SKILL.md embeds this adapter block, so all ripple.#3423 append (epic #1891 F8): body also moved with the <files_to_read> -> <required_reading> rename in its embedded workflow/command/capability sources (the skills-from-commands rule expects a commands/gsd change this diff does not carry) \u2014 deliberate, tag-token-only delta.",
".agents/skills/gsd-ns-workflow/SKILL.md": "#3004: Codex adapter builder (getCodexSkillAdapterHeader in bin/install.js) changed collaboration-tool vocabulary \u2014 wait(ids)\u2192wait_agent, gated close_agent, added task_name+fork_turns. Every Codex SKILL.md embeds this adapter block, so all ripple.#3423 append (epic #1891 F8): body also moved with the <files_to_read> -> <required_reading> rename in its embedded workflow/command/capability sources (the skills-from-commands rule expects a commands/gsd change this diff does not carry) \u2014 deliberate, tag-token-only delta.",
".agents/skills/gsd-onboard/SKILL.md": "#3004: Codex adapter builder (getCodexSkillAdapterHeader in bin/install.js) changed collaboration-tool vocabulary \u2014 wait(ids)\u2192wait_agent, gated close_agent, added task_name+fork_turns. Every Codex SKILL.md embeds this adapter block, so all ripple.#3423 append (epic #1891 F8): body also moved with the <files_to_read> -> <required_reading> rename in its embedded workflow/command/capability sources (the skills-from-commands rule expects a commands/gsd change this diff does not carry) \u2014 deliberate, tag-token-only delta.",
".agents/skills/gsd-pause-work/SKILL.md": "#3004: Codex adapter builder (getCodexSkillAdapterHeader in bin/install.js) changed collaboration-tool vocabulary \u2014 wait(ids)\u2192wait_agent, gated close_agent, added task_name+fork_turns. Every Codex SKILL.md embeds this adapter block, so all ripple.#3423 append (epic #1891 F8): body also moved with the <files_to_read> -> <required_reading> rename in its embedded workflow/command/capability sources (the skills-from-commands rule expects a commands/gsd change this diff does not carry) \u2014 deliberate, tag-token-only delta.",
".agents/skills/gsd-phase/SKILL.md": "#3004: Codex adapter builder (getCodexSkillAdapterHeader in bin/install.js) changed collaboration-tool vocabulary \u2014 wait(ids)\u2192wait_agent, gated close_agent, added task_name+fork_turns. Every Codex SKILL.md embeds this adapter block, so all ripple.#3423 append (epic #1891 F8): body also moved with the <files_to_read> -> <required_reading> rename in its embedded workflow/command/capability sources (the skills-from-commands rule expects a commands/gsd change this diff does not carry) \u2014 deliberate, tag-token-only delta.",
".agents/skills/gsd-plan-phase/SKILL.md": "#3004: Codex adapter builder (getCodexSkillAdapterHeader in bin/install.js) changed collaboration-tool vocabulary \u2014 wait(ids)\u2192wait_agent, gated close_agent, added task_name+fork_turns. Every Codex SKILL.md embeds this adapter block, so all ripple.#3423 append (epic #1891 F8): body also moved with the <files_to_read> -> <required_reading> rename in its embedded workflow/command/capability sources (the skills-from-commands rule expects a commands/gsd change this diff does not carry) \u2014 deliberate, tag-token-only delta.",
".agents/skills/gsd-plan-review-convergence/SKILL.md": "#3004: Codex adapter builder (getCodexSkillAdapterHeader in bin/install.js) changed collaboration-tool vocabulary \u2014 wait(ids)\u2192wait_agent, gated close_agent, added task_name+fork_turns. Every Codex SKILL.md embeds this adapter block, so all ripple.#3423 append (epic #1891 F8): body also moved with the <files_to_read> -> <required_reading> rename in its embedded workflow/command/capability sources (the skills-from-commands rule expects a commands/gsd change this diff does not carry) \u2014 deliberate, tag-token-only delta.",
".agents/skills/gsd-pr-branch/SKILL.md": "#3004: Codex adapter builder (getCodexSkillAdapterHeader in bin/install.js) changed collaboration-tool vocabulary \u2014 wait(ids)\u2192wait_agent, gated close_agent, added task_name+fork_turns. Every Codex SKILL.md embeds this adapter block, so all ripple.#3423 append (epic #1891 F8): body also moved with the <files_to_read> -> <required_reading> rename in its embedded workflow/command/capability sources (the skills-from-commands rule expects a commands/gsd change this diff does not carry) \u2014 deliberate, tag-token-only delta.",
".agents/skills/gsd-profile-user/SKILL.md": "#3004: Codex adapter builder (getCodexSkillAdapterHeader in bin/install.js) changed collaboration-tool vocabulary \u2014 wait(ids)\u2192wait_agent, gated close_agent, added task_name+fork_turns. Every Codex SKILL.md embeds this adapter block, so all ripple.#3423 append (epic #1891 F8): body also moved with the <files_to_read> -> <required_reading> rename in its embedded workflow/command/capability sources (the skills-from-commands rule expects a commands/gsd change this diff does not carry) \u2014 deliberate, tag-token-only delta.",
".agents/skills/gsd-progress/SKILL.md": "#3004: Codex adapter builder (getCodexSkillAdapterHeader in bin/install.js) changed collaboration-tool vocabulary \u2014 wait(ids)\u2192wait_agent, gated close_agent, added task_name+fork_turns. Every Codex SKILL.md embeds this adapter block, so all ripple.#3423 append (epic #1891 F8): body also moved with the <files_to_read> -> <required_reading> rename in its embedded workflow/command/capability sources (the skills-from-commands rule expects a commands/gsd change this diff does not carry) \u2014 deliberate, tag-token-only delta.",
".agents/skills/gsd-quick/SKILL.md": "#3004: Codex adapter builder (getCodexSkillAdapterHeader in bin/install.js) changed collaboration-tool vocabulary \u2014 wait(ids)\u2192wait_agent, gated close_agent, added task_name+fork_turns. Every Codex SKILL.md embeds this adapter block, so all ripple.",
".agents/skills/gsd-resume-work/SKILL.md": "#3004: Codex adapter builder (getCodexSkillAdapterHeader in bin/install.js) changed collaboration-tool vocabulary \u2014 wait(ids)\u2192wait_agent, gated close_agent, added task_name+fork_turns. Every Codex SKILL.md embeds this adapter block, so all ripple.#3423 append (epic #1891 F8): body also moved with the <files_to_read> -> <required_reading> rename in its embedded workflow/command/capability sources (the skills-from-commands rule expects a commands/gsd change this diff does not carry) \u2014 deliberate, tag-token-only delta.",
".agents/skills/gsd-review/SKILL.md": "#3004: Codex adapter builder (getCodexSkillAdapterHeader in bin/install.js) changed collaboration-tool vocabulary \u2014 wait(ids)\u2192wait_agent, gated close_agent, added task_name+fork_turns. Every Codex SKILL.md embeds this adapter block, so all ripple.#3423 append (epic #1891 F8): body also moved with the <files_to_read> -> <required_reading> rename in its embedded workflow/command/capability sources (the skills-from-commands rule expects a commands/gsd change this diff does not carry) \u2014 deliberate, tag-token-only delta.",
".agents/skills/gsd-review-backlog/SKILL.md": "#3004: Codex adapter builder (getCodexSkillAdapterHeader in bin/install.js) changed collaboration-tool vocabulary \u2014 wait(ids)\u2192wait_agent, gated close_agent, added task_name+fork_turns. Every Codex SKILL.md embeds this adapter block, so all ripple.#3423 append (epic #1891 F8): body also moved with the <files_to_read> -> <required_reading> rename in its embedded workflow/command/capability sources (the skills-from-commands rule expects a commands/gsd change this diff does not carry) \u2014 deliberate, tag-token-only delta.",
".agents/skills/gsd-secure-phase/SKILL.md": "#3004: Codex adapter builder (getCodexSkillAdapterHeader in bin/install.js) changed collaboration-tool vocabulary \u2014 wait(ids)\u2192wait_agent, gated close_agent, added task_name+fork_turns. Every Codex SKILL.md embeds this adapter block, so all ripple.#3423 append (epic #1891 F8): body also moved with the <files_to_read> -> <required_reading> rename in its embedded workflow/command/capability sources (the skills-from-commands rule expects a commands/gsd change this diff does not carry) \u2014 deliberate, tag-token-only delta.",
".agents/skills/gsd-settings/SKILL.md": "#3004: Codex adapter builder (getCodexSkillAdapterHeader in bin/install.js) changed collaboration-tool vocabulary \u2014 wait(ids)\u2192wait_agent, gated close_agent, added task_name+fork_turns. Every Codex SKILL.md embeds this adapter block, so all ripple.#3423 append (epic #1891 F8): body also moved with the <files_to_read> -> <required_reading> rename in its embedded workflow/command/capability sources (the skills-from-commands rule expects a commands/gsd change this diff does not carry) \u2014 deliberate, tag-token-only delta.",
".agents/skills/gsd-ship/SKILL.md": "#3004: Codex adapter builder (getCodexSkillAdapterHeader in bin/install.js) changed collaboration-tool vocabulary \u2014 wait(ids)\u2192wait_agent, gated close_agent, added task_name+fork_turns. Every Codex SKILL.md embeds this adapter block, so all ripple.#3423 append (epic #1891 F8): body also moved with the <files_to_read> -> <required_reading> rename in its embedded workflow/command/capability sources (the skills-from-commands rule expects a commands/gsd change this diff does not carry) \u2014 deliberate, tag-token-only delta.",
".agents/skills/gsd-sketch/SKILL.md": "#3004: Codex adapter builder (getCodexSkillAdapterHeader in bin/install.js) changed collaboration-tool vocabulary \u2014 wait(ids)\u2192wait_agent, gated close_agent, added task_name+fork_turns. Every Codex SKILL.md embeds this adapter block, so all ripple.#3423 append (epic #1891 F8): body also moved with the <files_to_read> -> <required_reading> rename in its embedded workflow/command/capability sources (the skills-from-commands rule expects a commands/gsd change this diff does not carry) \u2014 deliberate, tag-token-only delta.",
".agents/skills/gsd-spec-phase/SKILL.md": "#3004: Codex adapter builder (getCodexSkillAdapterHeader in bin/install.js) changed collaboration-tool vocabulary \u2014 wait(ids)\u2192wait_agent, gated close_agent, added task_name+fork_turns. Every Codex SKILL.md embeds this adapter block, so all ripple.#3423 append (epic #1891 F8): body also moved with the <files_to_read> -> <required_reading> rename in its embedded workflow/command/capability sources (the skills-from-commands rule expects a commands/gsd change this diff does not carry) \u2014 deliberate, tag-token-only delta.",
".agents/skills/gsd-spike/SKILL.md": "#3004: Codex adapter builder (getCodexSkillAdapterHeader in bin/install.js) changed collaboration-tool vocabulary \u2014 wait(ids)\u2192wait_agent, gated close_agent, added task_name+fork_turns. Every Codex SKILL.md embeds this adapter block, so all ripple.#3423 append (epic #1891 F8): body also moved with the <files_to_read> -> <required_reading> rename in its embedded workflow/command/capability sources (the skills-from-commands rule expects a commands/gsd change this diff does not carry) \u2014 deliberate, tag-token-only delta.",
".agents/skills/gsd-stats/SKILL.md": "#3004: Codex adapter builder (getCodexSkillAdapterHeader in bin/install.js) changed collaboration-tool vocabulary \u2014 wait(ids)\u2192wait_agent, gated close_agent, added task_name+fork_turns. Every Codex SKILL.md embeds this adapter block, so all ripple.#3423 append (epic #1891 F8): body also moved with the <files_to_read> -> <required_reading> rename in its embedded workflow/command/capability sources (the skills-from-commands rule expects a commands/gsd change this diff does not carry) \u2014 deliberate, tag-token-only delta.",
".agents/skills/gsd-surface/SKILL.md": "#3004: Codex adapter builder (getCodexSkillAdapterHeader in bin/install.js) changed collaboration-tool vocabulary \u2014 wait(ids)\u2192wait_agent, gated close_agent, added task_name+fork_turns. Every Codex SKILL.md embeds this adapter block, so all ripple.#3423 append (epic #1891 F8): body also moved with the <files_to_read> -> <required_reading> rename in its embedded workflow/command/capability sources (the skills-from-commands rule expects a commands/gsd change this diff does not carry) \u2014 deliberate, tag-token-only delta.",
".agents/skills/gsd-thread/SKILL.md": "#3004: Codex adapter builder (getCodexSkillAdapterHeader in bin/install.js) changed collaboration-tool vocabulary \u2014 wait(ids)\u2192wait_agent, gated close_agent, added task_name+fork_turns. Every Codex SKILL.md embeds this adapter block, so all ripple.#3423 append (epic #1891 F8): body also moved with the <files_to_read> -> <required_reading> rename in its embedded workflow/command/capability sources (the skills-from-commands rule expects a commands/gsd change this diff does not carry) \u2014 deliberate, tag-token-only delta.",
".agents/skills/gsd-ui-phase/SKILL.md": "#3004: Codex adapter builder (getCodexSkillAdapterHeader in bin/install.js) changed collaboration-tool vocabulary \u2014 wait(ids)\u2192wait_agent, gated close_agent, added task_name+fork_turns. Every Codex SKILL.md embeds this adapter block, so all ripple.#3423 append (epic #1891 F8): body also moved with the <files_to_read> -> <required_reading> rename in its embedded workflow/command/capability sources (the skills-from-commands rule expects a commands/gsd change this diff does not carry) \u2014 deliberate, tag-token-only delta.",
".agents/skills/gsd-ui-review/SKILL.md": "#3004: Codex adapter builder (getCodexSkillAdapterHeader in bin/install.js) changed collaboration-tool vocabulary \u2014 wait(ids)\u2192wait_agent, gated close_agent, added task_name+fork_turns. Every Codex SKILL.md embeds this adapter block, so all ripple.#3423 append (epic #1891 F8): body also moved with the <files_to_read> -> <required_reading> rename in its embedded workflow/command/capability sources (the skills-from-commands rule expects a commands/gsd change this diff does not carry) \u2014 deliberate, tag-token-only delta.",
".agents/skills/gsd-ultraplan-phase/SKILL.md": "#3004: Codex adapter builder (getCodexSkillAdapterHeader in bin/install.js) changed collaboration-tool vocabulary \u2014 wait(ids)\u2192wait_agent, gated close_agent, added task_name+fork_turns. Every Codex SKILL.md embeds this adapter block, so all ripple.#3423 append (epic #1891 F8): body also moved with the <files_to_read> -> <required_reading> rename in its embedded workflow/command/capability sources (the skills-from-commands rule expects a commands/gsd change this diff does not carry) \u2014 deliberate, tag-token-only delta.",
".agents/skills/gsd-undo/SKILL.md": "#3004: Codex adapter builder (getCodexSkillAdapterHeader in bin/install.js) changed collaboration-tool vocabulary \u2014 wait(ids)\u2192wait_agent, gated close_agent, added task_name+fork_turns. Every Codex SKILL.md embeds this adapter block, so all ripple.#3423 append (epic #1891 F8): body also moved with the <files_to_read> -> <required_reading> rename in its embedded workflow/command/capability sources (the skills-from-commands rule expects a commands/gsd change this diff does not carry) \u2014 deliberate, tag-token-only delta.",
".agents/skills/gsd-update/SKILL.md": "#3004: Codex adapter builder (getCodexSkillAdapterHeader in bin/install.js) changed collaboration-tool vocabulary \u2014 wait(ids)\u2192wait_agent, gated close_agent, added task_name+fork_turns. Every Codex SKILL.md embeds this adapter block, so all ripple.#3423 append (epic #1891 F8): body also moved with the <files_to_read> -> <required_reading> rename in its embedded workflow/command/capability sources (the skills-from-commands rule expects a commands/gsd change this diff does not carry) \u2014 deliberate, tag-token-only delta.",
".agents/skills/gsd-validate-phase/SKILL.md": "#3004: Codex adapter builder (getCodexSkillAdapterHeader in bin/install.js) changed collaboration-tool vocabulary \u2014 wait(ids)\u2192wait_agent, gated close_agent, added task_name+fork_turns. Every Codex SKILL.md embeds this adapter block, so all ripple.#3423 append (epic #1891 F8): body also moved with the <files_to_read> -> <required_reading> rename in its embedded workflow/command/capability sources (the skills-from-commands rule expects a commands/gsd change this diff does not carry) \u2014 deliberate, tag-token-only delta.",
".agents/skills/gsd-verify-work/SKILL.md": "#3004: Codex adapter builder (getCodexSkillAdapterHeader in bin/install.js) changed collaboration-tool vocabulary \u2014 wait(ids)\u2192wait_agent, gated close_agent, added task_name+fork_turns. Every Codex SKILL.md embeds this adapter block, so all ripple.",
".agents/skills/gsd-workspace/SKILL.md": "#3004: Codex adapter builder (getCodexSkillAdapterHeader in bin/install.js) changed collaboration-tool vocabulary \u2014 wait(ids)\u2192wait_agent, gated close_agent, added task_name+fork_turns. Every Codex SKILL.md embeds this adapter block, so all ripple.#3423 append (epic #1891 F8): body also moved with the <files_to_read> -> <required_reading> rename in its embedded workflow/command/capability sources (the skills-from-commands rule expects a commands/gsd change this diff does not carry) \u2014 deliberate, tag-token-only delta.",
".agents/skills/gsd-workstreams/SKILL.md": "#3004: Codex adapter builder (getCodexSkillAdapterHeader in bin/install.js) changed collaboration-tool vocabulary \u2014 wait(ids)\u2192wait_agent, gated close_agent, added task_name+fork_turns. Every Codex SKILL.md embeds this adapter block, so all ripple.#3423 append (epic #1891 F8): body also moved with the <files_to_read> -> <required_reading> rename in its embedded workflow/command/capability sources (the skills-from-commands rule expects a commands/gsd change this diff does not carry) \u2014 deliberate, tag-token-only delta."
}
}

View File

@@ -1,179 +0,0 @@
{
"version": 1,
"paths": {
"gsd-hooks/gsd-agent-isolation-guard.js": {
"reason": "#3023: pi reserves its own hooks/ directory, so the installer now stages the shared hook bundle under gsd-hooks/ for the pi runtime instead of hooks/. This file's bytes are unchanged from hooks/gsd-agent-isolation-guard.js — only the emitted install path moved."
},
"gsd-hooks/gsd-check-update.js": {
"reason": "#3023: pi reserves its own hooks/ directory, so the installer now stages the shared hook bundle under gsd-hooks/ for the pi runtime instead of hooks/. This file's bytes are unchanged from hooks/gsd-check-update.js — only the emitted install path moved."
},
"gsd-hooks/gsd-config-reload.js": {
"reason": "#3023: pi reserves its own hooks/ directory, so the installer now stages the shared hook bundle under gsd-hooks/ for the pi runtime instead of hooks/. This file's bytes are unchanged from hooks/gsd-config-reload.js — only the emitted install path moved."
},
"gsd-hooks/gsd-context-monitor.js": {
"reason": "#3023: pi reserves its own hooks/ directory, so the installer now stages the shared hook bundle under gsd-hooks/ for the pi runtime instead of hooks/. This file's bytes are unchanged from hooks/gsd-context-monitor.js — only the emitted install path moved."
},
"gsd-hooks/gsd-cursor-post-tool.js": {
"reason": "#3023: pi reserves its own hooks/ directory, so the installer now stages the shared hook bundle under gsd-hooks/ for the pi runtime instead of hooks/. This file's bytes are unchanged from hooks/gsd-cursor-post-tool.js — only the emitted install path moved."
},
"gsd-hooks/gsd-cursor-pre-tool.js": {
"reason": "#3023: pi reserves its own hooks/ directory, so the installer now stages the shared hook bundle under gsd-hooks/ for the pi runtime instead of hooks/. This file's bytes are unchanged from hooks/gsd-cursor-pre-tool.js — only the emitted install path moved."
},
"gsd-hooks/gsd-cursor-session-start.js": {
"reason": "#3023: pi reserves its own hooks/ directory, so the installer now stages the shared hook bundle under gsd-hooks/ for the pi runtime instead of hooks/. This file's bytes are unchanged from hooks/gsd-cursor-session-start.js — only the emitted install path moved."
},
"gsd-hooks/gsd-cursor-stop.js": {
"reason": "#3023: pi reserves its own hooks/ directory, so the installer now stages the shared hook bundle under gsd-hooks/ for the pi runtime instead of hooks/. This file's bytes are unchanged from hooks/gsd-cursor-stop.js — only the emitted install path moved."
},
"gsd-hooks/gsd-cursor-subagent-start.js": {
"reason": "#3023: pi reserves its own hooks/ directory, so the installer now stages the shared hook bundle under gsd-hooks/ for the pi runtime instead of hooks/. This file's bytes are unchanged from hooks/gsd-cursor-subagent-start.js — only the emitted install path moved."
},
"gsd-hooks/gsd-cursor-subagent-stop.js": {
"reason": "#3023: pi reserves its own hooks/ directory, so the installer now stages the shared hook bundle under gsd-hooks/ for the pi runtime instead of hooks/. This file's bytes are unchanged from hooks/gsd-cursor-subagent-stop.js — only the emitted install path moved."
},
"gsd-hooks/gsd-ensure-canonical-path.js": {
"reason": "#3023: pi reserves its own hooks/ directory, so the installer now stages the shared hook bundle under gsd-hooks/ for the pi runtime instead of hooks/. This file's bytes are unchanged from hooks/gsd-ensure-canonical-path.js — only the emitted install path moved."
},
"gsd-hooks/gsd-graphify-update.sh": {
"reason": "#3023: pi reserves its own hooks/ directory, so the installer now stages the shared hook bundle under gsd-hooks/ for the pi runtime instead of hooks/. This file's bytes are unchanged from hooks/gsd-graphify-update.sh — only the emitted install path moved."
},
"gsd-hooks/gsd-phase-boundary.sh": {
"reason": "#3023: pi reserves its own hooks/ directory, so the installer now stages the shared hook bundle under gsd-hooks/ for the pi runtime instead of hooks/. This file's bytes are unchanged from hooks/gsd-phase-boundary.sh — only the emitted install path moved."
},
"gsd-hooks/gsd-prompt-guard.js": {
"reason": "#3023: pi reserves its own hooks/ directory, so the installer now stages the shared hook bundle under gsd-hooks/ for the pi runtime instead of hooks/. This file's bytes are unchanged from hooks/gsd-prompt-guard.js — only the emitted install path moved."
},
"gsd-hooks/gsd-read-guard.js": {
"reason": "#3023: pi reserves its own hooks/ directory, so the installer now stages the shared hook bundle under gsd-hooks/ for the pi runtime instead of hooks/. This file's bytes are unchanged from hooks/gsd-read-guard.js — only the emitted install path moved."
},
"gsd-hooks/gsd-session-state.sh": {
"reason": "#3023: pi reserves its own hooks/ directory, so the installer now stages the shared hook bundle under gsd-hooks/ for the pi runtime instead of hooks/. This file's bytes are unchanged from hooks/gsd-session-state.sh — only the emitted install path moved."
},
"gsd-hooks/gsd-statusline.js": {
"reason": "#3023: pi reserves its own hooks/ directory, so the installer now stages the shared hook bundle under gsd-hooks/ for the pi runtime instead of hooks/. This file's bytes are unchanged from hooks/gsd-statusline.js — only the emitted install path moved."
},
"gsd-hooks/gsd-update-banner.js": {
"reason": "#3023: pi reserves its own hooks/ directory, so the installer now stages the shared hook bundle under gsd-hooks/ for the pi runtime instead of hooks/. This file's bytes are unchanged from hooks/gsd-update-banner.js — only the emitted install path moved."
},
"gsd-hooks/gsd-validate-commit.sh": {
"reason": "#3023: pi reserves its own hooks/ directory, so the installer now stages the shared hook bundle under gsd-hooks/ for the pi runtime instead of hooks/. This file's bytes are unchanged from hooks/gsd-validate-commit.sh — only the emitted install path moved."
},
"gsd-hooks/gsd-windsurf-pre-command.js": {
"reason": "#3023: pi reserves its own hooks/ directory, so the installer now stages the shared hook bundle under gsd-hooks/ for the pi runtime instead of hooks/. This file's bytes are unchanged from hooks/gsd-windsurf-pre-command.js — only the emitted install path moved."
},
"gsd-hooks/gsd-windsurf-pre-write.js": {
"reason": "#3023: pi reserves its own hooks/ directory, so the installer now stages the shared hook bundle under gsd-hooks/ for the pi runtime instead of hooks/. This file's bytes are unchanged from hooks/gsd-windsurf-pre-write.js — only the emitted install path moved."
},
"gsd-hooks/gsd-workflow-guard.js": {
"reason": "#3023: pi reserves its own hooks/ directory, so the installer now stages the shared hook bundle under gsd-hooks/ for the pi runtime instead of hooks/. This file's bytes are unchanged from hooks/gsd-workflow-guard.js — only the emitted install path moved."
},
"gsd-hooks/gsd-worktree-path-guard.js": {
"reason": "#3023: pi reserves its own hooks/ directory, so the installer now stages the shared hook bundle under gsd-hooks/ for the pi runtime instead of hooks/. This file's bytes are unchanged from hooks/gsd-worktree-path-guard.js — only the emitted install path moved."
},
"gsd-hooks/gsd-write-guard.js": {
"reason": "#3023: pi reserves its own hooks/ directory, so the installer now stages the shared hook bundle under gsd-hooks/ for the pi runtime instead of hooks/. This file's bytes are unchanged from hooks/gsd-write-guard.js — only the emitted install path moved."
},
"gsd-hooks/lib/cursor-workspace.js": {
"reason": "#3023: pi reserves its own hooks/ directory, so the installer now stages the shared hook bundle under gsd-hooks/ for the pi runtime instead of hooks/. This file's bytes are unchanged from hooks/lib/cursor-workspace.js — only the emitted install path moved."
},
"gsd-hooks/lib/git-cmd.js": {
"reason": "#3023: pi reserves its own hooks/ directory, so the installer now stages the shared hook bundle under gsd-hooks/ for the pi runtime instead of hooks/. This file's bytes are unchanged from hooks/lib/git-cmd.js — only the emitted install path moved."
},
"gsd-hooks/lib/gsd-graphify-rebuild.sh": {
"reason": "#3023: pi reserves its own hooks/ directory, so the installer now stages the shared hook bundle under gsd-hooks/ for the pi runtime instead of hooks/. This file's bytes are unchanged from hooks/lib/gsd-graphify-rebuild.sh — only the emitted install path moved."
},
"gsd-hooks/lib/isolation-sentinel.js": {
"reason": "#3023: pi reserves its own hooks/ directory, so the installer now stages the shared hook bundle under gsd-hooks/ for the pi runtime instead of hooks/. This file's bytes are unchanged from hooks/lib/isolation-sentinel.js — only the emitted install path moved."
},
"gsd-hooks/managed-hooks-registry.cjs": {
"reason": "#3023: pi reserves its own hooks/ directory, so the installer now stages the shared hook bundle under gsd-hooks/ for the pi runtime instead of hooks/. This file's bytes are unchanged from hooks/managed-hooks-registry.cjs — only the emitted install path moved."
},
"hooks/gsd-agent-isolation-guard.js": {
"reason": "#3023: pi no longer emits this file under hooks/ (its reserved directory); the installer now stages the same unchanged bytes under gsd-hooks/gsd-agent-isolation-guard.js for the pi runtime, so this path disappears from pi's manifest without its source changing."
},
"hooks/gsd-check-update.js": {
"reason": "#3023: pi no longer emits this file under hooks/ (its reserved directory); the installer now stages the same unchanged bytes under gsd-hooks/gsd-check-update.js for the pi runtime, so this path disappears from pi's manifest without its source changing."
},
"hooks/gsd-config-reload.js": {
"reason": "#3023: pi no longer emits this file under hooks/ (its reserved directory); the installer now stages the same unchanged bytes under gsd-hooks/gsd-config-reload.js for the pi runtime, so this path disappears from pi's manifest without its source changing."
},
"hooks/gsd-context-monitor.js": {
"reason": "#3023: pi no longer emits this file under hooks/ (its reserved directory); the installer now stages the same unchanged bytes under gsd-hooks/gsd-context-monitor.js for the pi runtime, so this path disappears from pi's manifest without its source changing."
},
"hooks/gsd-cursor-post-tool.js": {
"reason": "#3023: pi no longer emits this file under hooks/ (its reserved directory); the installer now stages the same unchanged bytes under gsd-hooks/gsd-cursor-post-tool.js for the pi runtime, so this path disappears from pi's manifest without its source changing."
},
"hooks/gsd-cursor-pre-tool.js": {
"reason": "#3023: pi no longer emits this file under hooks/ (its reserved directory); the installer now stages the same unchanged bytes under gsd-hooks/gsd-cursor-pre-tool.js for the pi runtime, so this path disappears from pi's manifest without its source changing."
},
"hooks/gsd-cursor-session-start.js": {
"reason": "#3023: pi no longer emits this file under hooks/ (its reserved directory); the installer now stages the same unchanged bytes under gsd-hooks/gsd-cursor-session-start.js for the pi runtime, so this path disappears from pi's manifest without its source changing."
},
"hooks/gsd-cursor-stop.js": {
"reason": "#3023: pi no longer emits this file under hooks/ (its reserved directory); the installer now stages the same unchanged bytes under gsd-hooks/gsd-cursor-stop.js for the pi runtime, so this path disappears from pi's manifest without its source changing."
},
"hooks/gsd-cursor-subagent-start.js": {
"reason": "#3023: pi no longer emits this file under hooks/ (its reserved directory); the installer now stages the same unchanged bytes under gsd-hooks/gsd-cursor-subagent-start.js for the pi runtime, so this path disappears from pi's manifest without its source changing."
},
"hooks/gsd-cursor-subagent-stop.js": {
"reason": "#3023: pi no longer emits this file under hooks/ (its reserved directory); the installer now stages the same unchanged bytes under gsd-hooks/gsd-cursor-subagent-stop.js for the pi runtime, so this path disappears from pi's manifest without its source changing."
},
"hooks/gsd-ensure-canonical-path.js": {
"reason": "#3023: pi no longer emits this file under hooks/ (its reserved directory); the installer now stages the same unchanged bytes under gsd-hooks/gsd-ensure-canonical-path.js for the pi runtime, so this path disappears from pi's manifest without its source changing."
},
"hooks/gsd-graphify-update.sh": {
"reason": "#3023: pi no longer emits this file under hooks/ (its reserved directory); the installer now stages the same unchanged bytes under gsd-hooks/gsd-graphify-update.sh for the pi runtime, so this path disappears from pi's manifest without its source changing."
},
"hooks/gsd-phase-boundary.sh": {
"reason": "#3023: pi no longer emits this file under hooks/ (its reserved directory); the installer now stages the same unchanged bytes under gsd-hooks/gsd-phase-boundary.sh for the pi runtime, so this path disappears from pi's manifest without its source changing."
},
"hooks/gsd-prompt-guard.js": {
"reason": "#3023: pi no longer emits this file under hooks/ (its reserved directory); the installer now stages the same unchanged bytes under gsd-hooks/gsd-prompt-guard.js for the pi runtime, so this path disappears from pi's manifest without its source changing."
},
"hooks/gsd-read-guard.js": {
"reason": "#3023: pi no longer emits this file under hooks/ (its reserved directory); the installer now stages the same unchanged bytes under gsd-hooks/gsd-read-guard.js for the pi runtime, so this path disappears from pi's manifest without its source changing."
},
"hooks/gsd-session-state.sh": {
"reason": "#3023: pi no longer emits this file under hooks/ (its reserved directory); the installer now stages the same unchanged bytes under gsd-hooks/gsd-session-state.sh for the pi runtime, so this path disappears from pi's manifest without its source changing."
},
"hooks/gsd-statusline.js": {
"reason": "#3023: pi no longer emits this file under hooks/ (its reserved directory); the installer now stages the same unchanged bytes under gsd-hooks/gsd-statusline.js for the pi runtime, so this path disappears from pi's manifest without its source changing."
},
"hooks/gsd-update-banner.js": {
"reason": "#3023: pi no longer emits this file under hooks/ (its reserved directory); the installer now stages the same unchanged bytes under gsd-hooks/gsd-update-banner.js for the pi runtime, so this path disappears from pi's manifest without its source changing."
},
"hooks/gsd-validate-commit.sh": {
"reason": "#3023: pi no longer emits this file under hooks/ (its reserved directory); the installer now stages the same unchanged bytes under gsd-hooks/gsd-validate-commit.sh for the pi runtime, so this path disappears from pi's manifest without its source changing."
},
"hooks/gsd-windsurf-pre-command.js": {
"reason": "#3023: pi no longer emits this file under hooks/ (its reserved directory); the installer now stages the same unchanged bytes under gsd-hooks/gsd-windsurf-pre-command.js for the pi runtime, so this path disappears from pi's manifest without its source changing."
},
"hooks/gsd-windsurf-pre-write.js": {
"reason": "#3023: pi no longer emits this file under hooks/ (its reserved directory); the installer now stages the same unchanged bytes under gsd-hooks/gsd-windsurf-pre-write.js for the pi runtime, so this path disappears from pi's manifest without its source changing."
},
"hooks/gsd-workflow-guard.js": {
"reason": "#3023: pi no longer emits this file under hooks/ (its reserved directory); the installer now stages the same unchanged bytes under gsd-hooks/gsd-workflow-guard.js for the pi runtime, so this path disappears from pi's manifest without its source changing."
},
"hooks/gsd-worktree-path-guard.js": {
"reason": "#3023: pi no longer emits this file under hooks/ (its reserved directory); the installer now stages the same unchanged bytes under gsd-hooks/gsd-worktree-path-guard.js for the pi runtime, so this path disappears from pi's manifest without its source changing."
},
"hooks/gsd-write-guard.js": {
"reason": "#3023: pi no longer emits this file under hooks/ (its reserved directory); the installer now stages the same unchanged bytes under gsd-hooks/gsd-write-guard.js for the pi runtime, so this path disappears from pi's manifest without its source changing."
},
"hooks/lib/cursor-workspace.js": {
"reason": "#3023: pi no longer emits this file under hooks/ (its reserved directory); the installer now stages the same unchanged bytes under gsd-hooks/lib/cursor-workspace.js for the pi runtime, so this path disappears from pi's manifest without its source changing."
},
"hooks/lib/git-cmd.js": {
"reason": "#3023: pi no longer emits this file under hooks/ (its reserved directory); the installer now stages the same unchanged bytes under gsd-hooks/lib/git-cmd.js for the pi runtime, so this path disappears from pi's manifest without its source changing."
},
"hooks/lib/gsd-graphify-rebuild.sh": {
"reason": "#3023: pi no longer emits this file under hooks/ (its reserved directory); the installer now stages the same unchanged bytes under gsd-hooks/lib/gsd-graphify-rebuild.sh for the pi runtime, so this path disappears from pi's manifest without its source changing."
},
"hooks/lib/isolation-sentinel.js": {
"reason": "#3023: pi no longer emits this file under hooks/ (its reserved directory); the installer now stages the same unchanged bytes under gsd-hooks/lib/isolation-sentinel.js for the pi runtime, so this path disappears from pi's manifest without its source changing."
},
"hooks/managed-hooks-registry.cjs": {
"reason": "#3023: pi no longer emits this file under hooks/ (its reserved directory); the installer now stages the same unchanged bytes under gsd-hooks/managed-hooks-registry.cjs for the pi runtime, so this path disappears from pi's manifest without its source changing."
}
}
}

View File

@@ -1,8 +0,0 @@
{
"version": 1,
"paths": {
"sync-skills.md": {
"reason": "#3025 (option b, user decision): sync-skills refused cross-runtime sync. Skill content/layout is runtime-specific (the installer applies per-runtime converters/adapter headers/brand swaps/layout rules) and grok/gemini alias another runtime's skills root, so a verbatim cross-runtime cp -r corrupted destination skills and could damage a runtime the user never named; #3024 (closed) un-masked it. This PR adds: (1) a Step 1 runtime-id SHAPE validation guard (^[a-z0-9][a-z0-9-]*$) that rejects shell-metachar --from/--to values before any echo/heredoc/[[ ]] interpolation, hardening a command-substitution injection vector in the new (and pre-existing) error messages; (2) a FUNCTIONAL Step 1 cross-runtime refuse guard (a bash loop over TO_RUNTIMES that exits 1 with an installer pointer when any destination != FROM_RUNTIME), placed before Step 2 resolution and Step 5's rm -rf/cp -r so cross-runtime can never reach the copy; identity sync (--from == --to) stays a no-op. Growth is: the shape-validation guard (is_runtime_id + loop), the cross-runtime refuse guard loop + cat<<EOF heredoc error block, the new validation bullets, the rewritten Step 5 comment (the prior non-functional #3025 comment is replaced), and the strengthened Limitations bullet. No code module or path-resolution changes (out of scope per triage)."
}
}
}

View File

@@ -1,6 +0,0 @@
{
"version": 1,
"paths": {
"ui-phase.md": "#3132: realigned resolution loop and write-back format from covered/backstop-as-status to resolved+verification. — #3423 append (epic #1891 F8): also grew with the <files_to_read> -> <required_reading> tag rename (15 chars/block, tag-token-only delta)."
}
}

View File

@@ -1,41 +0,0 @@
{
"version": 1,
"paths": {
"skills/gsd-ns-manage/skills/stats/SKILL.md": {
"reason": "#3151: convertClaudeCommandToClaudeSkill no longer emits effort: into skill frontmatter (a static effort value invalidates the caller's prompt cache at both scope boundaries). The converter (src/runtime-artifact-conversion.cts + duplicate bin/install.js) changed, moving these emitted skill hashes without a source-command change; this ack covers that converter-driven emission drop across all runtime layouts (per ADR-2719/#3039 precedent)."
},
"skills/gsd-ns-workflow/skills/autonomous/SKILL.md": {
"reason": "#3151: convertClaudeCommandToClaudeSkill no longer emits effort: into skill frontmatter (a static effort value invalidates the caller's prompt cache at both scope boundaries). The converter (src/runtime-artifact-conversion.cts + duplicate bin/install.js) changed, moving these emitted skill hashes without a source-command change; this ack covers that converter-driven emission drop across all runtime layouts (per ADR-2719/#3039 precedent)."
},
"skills/gsd-ns-workflow/skills/execute-phase/SKILL.md": {
"reason": "#3151: convertClaudeCommandToClaudeSkill no longer emits effort: into skill frontmatter (a static effort value invalidates the caller's prompt cache at both scope boundaries). The converter (src/runtime-artifact-conversion.cts + duplicate bin/install.js) changed, moving these emitted skill hashes without a source-command change; this ack covers that converter-driven emission drop across all runtime layouts (per ADR-2719/#3039 precedent)."
},
"skills/gsd-ns-workflow/skills/next/SKILL.md": {
"reason": "#3151: convertClaudeCommandToClaudeSkill no longer emits effort: into skill frontmatter (a static effort value invalidates the caller's prompt cache at both scope boundaries). The converter (src/runtime-artifact-conversion.cts + duplicate bin/install.js) changed, moving these emitted skill hashes without a source-command change; this ack covers that converter-driven emission drop across all runtime layouts (per ADR-2719/#3039 precedent)."
},
"skills/gsd-ns-workflow/skills/plan-phase/SKILL.md": {
"reason": "#3151: convertClaudeCommandToClaudeSkill no longer emits effort: into skill frontmatter (a static effort value invalidates the caller's prompt cache at both scope boundaries). The converter (src/runtime-artifact-conversion.cts + duplicate bin/install.js) changed, moving these emitted skill hashes without a source-command change; this ack covers that converter-driven emission drop across all runtime layouts (per ADR-2719/#3039 precedent)."
},
"skills/gsd-ns-workflow/skills/progress/SKILL.md": {
"reason": "#3151: convertClaudeCommandToClaudeSkill no longer emits effort: into skill frontmatter (a static effort value invalidates the caller's prompt cache at both scope boundaries). The converter (src/runtime-artifact-conversion.cts + duplicate bin/install.js) changed, moving these emitted skill hashes without a source-command change; this ack covers that converter-driven emission drop across all runtime layouts (per ADR-2719/#3039 precedent)."
},
"skills/gsd/gsd-ns-manage/skills/stats/SKILL.md": {
"reason": "#3151: convertClaudeCommandToClaudeSkill no longer emits effort: into skill frontmatter (a static effort value invalidates the caller's prompt cache at both scope boundaries). The converter (src/runtime-artifact-conversion.cts + duplicate bin/install.js) changed, moving these emitted skill hashes without a source-command change; this ack covers that converter-driven emission drop across all runtime layouts (per ADR-2719/#3039 precedent)."
},
"skills/gsd/gsd-ns-workflow/skills/autonomous/SKILL.md": {
"reason": "#3151: convertClaudeCommandToClaudeSkill no longer emits effort: into skill frontmatter (a static effort value invalidates the caller's prompt cache at both scope boundaries). The converter (src/runtime-artifact-conversion.cts + duplicate bin/install.js) changed, moving these emitted skill hashes without a source-command change; this ack covers that converter-driven emission drop across all runtime layouts (per ADR-2719/#3039 precedent)."
},
"skills/gsd/gsd-ns-workflow/skills/execute-phase/SKILL.md": {
"reason": "#3151: convertClaudeCommandToClaudeSkill no longer emits effort: into skill frontmatter (a static effort value invalidates the caller's prompt cache at both scope boundaries). The converter (src/runtime-artifact-conversion.cts + duplicate bin/install.js) changed, moving these emitted skill hashes without a source-command change; this ack covers that converter-driven emission drop across all runtime layouts (per ADR-2719/#3039 precedent)."
},
"skills/gsd/gsd-ns-workflow/skills/next/SKILL.md": {
"reason": "#3151: convertClaudeCommandToClaudeSkill no longer emits effort: into skill frontmatter (a static effort value invalidates the caller's prompt cache at both scope boundaries). The converter (src/runtime-artifact-conversion.cts + duplicate bin/install.js) changed, moving these emitted skill hashes without a source-command change; this ack covers that converter-driven emission drop across all runtime layouts (per ADR-2719/#3039 precedent)."
},
"skills/gsd/gsd-ns-workflow/skills/plan-phase/SKILL.md": {
"reason": "#3151: convertClaudeCommandToClaudeSkill no longer emits effort: into skill frontmatter (a static effort value invalidates the caller's prompt cache at both scope boundaries). The converter (src/runtime-artifact-conversion.cts + duplicate bin/install.js) changed, moving these emitted skill hashes without a source-command change; this ack covers that converter-driven emission drop across all runtime layouts (per ADR-2719/#3039 precedent)."
},
"skills/gsd/gsd-ns-workflow/skills/progress/SKILL.md": {
"reason": "#3151: convertClaudeCommandToClaudeSkill no longer emits effort: into skill frontmatter (a static effort value invalidates the caller's prompt cache at both scope boundaries). The converter (src/runtime-artifact-conversion.cts + duplicate bin/install.js) changed, moving these emitted skill hashes without a source-command change; this ack covers that converter-driven emission drop across all runtime layouts (per ADR-2719/#3039 precedent)."
}
}
}

View File

@@ -1,6 +0,0 @@
{
"version": 1,
"paths": {
"next.md": "#3184 (epic #3180 Phase 2): Route 0 (resume_incomplete_phase) grows by one branch that reads the new `scope` field `roadmap analyze` emits. Before this, Route 0 treated a well-formed `{phases: []}` as a clean scan and fell through to routing as though the incomplete-phase invariant (#160) had been checked and passed — the silent disarm #3165 reports, where a milestone window truncated by a closed-milestone heading yields an empty phase list with exit 0 and no error. The added `elif [ \"$ROADMAP_SCOPE\" != \"complete\" ]` arm routes that case into the SAME warn-and-fall-through branch the existing `roadmap.analyze failed` arm already uses, so a non-answer is no longer indistinguishable from a genuinely empty scan. The growth is the new arm's three warning lines, its explanatory comment, the `ROADMAP_SCOPE` assignment, and one success-criteria bullet. Adding this to the workflow rather than only to the CLI is deliberate: a spec review of this phase found that emitting `scope` without a consumer left #3165's actual symptom reproducing, so the field would have been a diagnostic nobody reads."
}
}

View File

@@ -1,6 +0,0 @@
{
"version": 1,
"paths": {
"mvp-phase.md": "#3186 (epic #3180 Phase 4, ADR-3180 §7.4, disk-strict per #2957 maintainer decision): removes the `PHASE_COMPLETE` ROADMAP-checkbox override this workflow ORed into its completion check (`if [[ \"$DISK_STATUS\" == \"complete\" || \"$PHASE_COMPLETE\" == \"true\" ]]`) — a ticked checkbox is a human annotation with no machine authority, and the prompt layer was one of the (previously undiscovered) sites still trusting it. The `PHASE_COMPLETE=$(...jq -r '.roadmap_complete // false')` assignment line is deleted outright; `DISK_STATUS` alone (already routed through the canonical `isPhaseComplete` owner via `roadmap.analyze`) now decides completion. Growth is a 4-line explanatory comment documenting why the OR was removed and that this is the same predicate the read and write paths share — the deleted assignment line and the shortened `if` condition are smaller than what they replace, so the net +233 bytes is entirely the comment, not new control flow. See ADR-3180 §7.4 Decision 4(d) (the prompt layer is in scope) and the design doc `.gsd/phase/refactor-3186-phase-completion-predicate/40-design.md` row `mvp-phase.md:49`."
}
}

View File

@@ -1,6 +0,0 @@
{
"version": 1,
"paths": {
"code-review-fix.md": "#3190: the --auto convergence loop rewrote REVIEW.md every re-review iteration but never recommitted it (committed REVIEW.md stayed at iteration 1, contradicting the committed REVIEW-FIX.md), and the commit-fix step's two inline frontmatter validators exported REVIEW_PATH into a node -e body that reads process.env.FIX_REPORT_PATH — so HAS_STATUS/FIX_FRONTMATTER were always empty and REVIEW-FIX.md was never committed at all. The fix renames the exported env var to FIX_REPORT_PATH at both validator sites, stages the converged REVIEW.md alongside REVIEW-FIX.md in the single --auto docs commit (guarded on AUTO_MODE so non-auto single-pass runs are untouched), and removes the spent .iterN.md backups on successful convergence (retained on degradation for post-mortem). Growth is the deployed contract for those three coupled fixes plus inline rationale comments; the regression test is tests/code-review-fix-pipeline-regression.test.cjs. — #3423 append (epic #1891 F8): also grew with the <files_to_read> -> <required_reading> tag rename (15 chars/block, tag-token-only delta; no prose changed)."
}
}

View File

@@ -1,6 +0,0 @@
{
"version": 1,
"paths": {
"autonomous.md": "#3210: handle_blocker's 'Fix and retry' path gained a retry ceiling — a per-phase-step RETRY_COUNT that escalates to a terminal needs_human halt (STATE.md '## Needs Human' row + unmet items surfaced) after 3 failed fix attempts, instead of re-presenting the options indefinitely. This is the loop-fixing behavior the issue title names; +704 bytes is the ceiling rule itself, not incidental prose. (The matching gsd-executor.md growth is acknowledged in the 2943 fragment and the execute-phase.md growth in the 3370 fragment, which already name those paths.)"
}
}

View File

@@ -1,6 +0,0 @@
{
"version": 1,
"paths": {
"edit-phase.md": "#3262: adds the mechanical milestone-scope guard the issue says this workflow lacks. `write_updated_phase` now captures `gsd_run query roadmap milestone-scope` (the new read-only window-identity probe: scope + declared phase ids) BEFORE the in-place section splice, re-derives it AFTER, and on any `scope`/`phases` difference restores the original section text and exits with an explicit ERROR — the same block-before-write shape as the workflow's existing `validate_depends_on` gate, which is untouched. One success-criteria checkbox and one anti-pattern line accompany it. Net growth is the two small probe invocations plus the rollback/error contract (~1.1 KB over the 12,927-byte base, well inside the DEFAULT 40 KiB hard cap); no prose was extracted or moved. Companion code changes (src/roadmap-parser.cts, src/roadmap.cts, src/phase.cts, src/command-aliases.cts, src/roadmap-command-router.cts) are self-attributing."
}
}

View File

@@ -1,8 +0,0 @@
{
"version": 1,
"paths": {
"health.md": {
"reason": "#3309 (epic #3180 Phase 11, ADR-3180): the `<error_codes>`/`<repair_actions>` tables and their footnote are GENERATED by `scripts/gen-health-docs.cjs` from the live `RULES` table, replacing a hand-maintained 16-code table — growth here is a deliberate, expected consequence of that generator doing its job as `RULES` grows, not accidental bloat. #2873 (epic #2866 Phase 4a) adds health rule W028 (\"A GSD-owned install scope shadows another on this machine\"), growing `RULES` from 31 to 32 entries and the generated `<error_codes>` table from 34 to 35 rows (an incremental 84-byte growth on top of #3309's original generation). #3586 (epic #2292 Phase 2) adds health rule W029 (\"`.planning/` is gitignored but still tracked by git\"), growing `RULES` from 32 to 33 entries and the generated `<error_codes>` table from 35 to 36 rows (a further incremental 141-byte growth). Regeneration is verified deterministic via `node scripts/gen-health-docs.cjs --check` (wired into `npm run lint:generated-sync`). NOTE for the next code added: this file is amended IN PLACE rather than joined by a sibling fragment — `health.md` is generated and grows on every new rule, and two ack sources naming one path is a hard error (`mergeAckSources`), so a per-PR fragment is not an option here. The base-side copy is spent, so the amendment is what makes the ack live for your diff."
}
}
}

View File

@@ -1,8 +0,0 @@
{
"version": 1,
"paths": {
"verify-work.md": {
"reason": "#3357: the `03-VERIFICATION.md` staleness check now resolves the phase's own report via `gsd_run query verification.resolve-file \"$PHASE_DIR\" --raw` instead of `ls \"${PHASE_DIR}\"/*-VERIFICATION.md | head -1`, routing through the same shared resolver seam as transition.md so both call sites agree on which file is canonical. Growth is +13 bytes (38,983 -> 38,996), the delta between the old ls/head-1 pipeline and the gsd_run call. #3559: the generic gate-dispatch arm gained the in-context validation contract for manifest-supplied check values. gates[].check is not one of the four executable surfaces the install consent prompt discloses, so a capability consented to as declarative-only could still reach a shell through an unvalidated check.query interpolated into a command substitution. The reference (references/loop-hook-dispatch.md) stated this requirement for step -> ref.command and omitted it for gate; that omission is the root cause and is now closed at the reference plus all four dispatch sites. Growth is one validation paragraph per site. 38996 -> 39107 bytes (+111). \u2014 #3606 append: verify:post consumer gains generic step dispatch (deferral to loop-hook-dispatch.md) before the secure-phase specialization, so ui/security/mempalace/nyquist steps registered at verify:post actually run instead of being filtered out by the one-skill narrow."
}
}
}

View File

@@ -1,6 +0,0 @@
{
"version": 1,
"paths": {
"execute-plan.md": "#3370: the Pattern A dispatch prompt spec gained the gate-semantics clause (gate=\"blocking\" (the default) is auto-approvable in auto-mode per the executor's own checkpoint protocol, gate=\"blocking-human\" always surfaces to a human; add no instruction overriding that protocol), closing the identically-shaped dispatch-time gap on the single-plan path named in the issue. Growth ~248 bytes (38913 -> 39161, still under the DEFAULT 40 KiB ceiling). Supersedes the spent #2652 fragment (merged into next), which also named execute-plan.md and would otherwise double-ack the same path. #3659 appends --mode \"$ISOLATION\" to the #2649 pre-dispatch base-check and corrects the baseRef restore-advice to the orchestrator/harness split (+154B; the harness does not read baseRef, #48). Deliberate growth. — #3299 append: the tracer feedback gate's interactive branch, which read `Interactive: STOP -> return a checkpoint:human-verify` and keyed on auto-mode alone, now branches on HUMAN_VERIFY_MODE — under the documented `end-of-phase` default an automated-only tracer `<verify>` is re-run and continues to expansion with no checkpoint. Growth is 796 bytes (39315 -> 40111, still under the DEFAULT_CAP of 40960 LF bytes, 849 bytes of headroom; the earlier 39161 -> 39957 / 1003 figures were measured before the 2026-08-22 merge of next and are superseded): that branch plus a `HUMAN_VERIFY_MODE=` read alongside the existing RUNTIME/USE_WORKTREES reads in parse_segments. The read carries an explicit `--default end-of-phase` because `workflow.human_verify_mode` is absent from SCHEMA_DEFAULTS (src/config.cts): a bare config-get exits non-zero with `Key not found` on any project whose config.json predates #3309, which is every pre-existing project and the reporter's exact config. The three carve-outs (`<human-check>`, `gate=\"blocking-human\"`, `mid-flight`) are stated inline here rather than delegated to gsd-core/references/dispatch-isolation-gate.md, because this file is the inline dispatch path used for step-by-step / non-Claude-Code execution, where agents/gsd-executor.md is never loaded. This entry carries #3370's own reason forward verbatim above rather than replacing it — that growth is in the base and still needs its account. The blocking-human STOP is evaluated BEFORE the auto-mode branch here, matching golden rule 6 in checkpoints.md — the earlier ordering let an autonomous run continue past a blocking-human tracer. — #3028 append: the checkpoint_protocol step's one-line display spec described the old drawn box (`CHECKPOINT: [Type]` box … `YOUR ACTION: [signal]`). Under #3028 a checkpoint is a `### CHECKPOINT: [Type]` heading, a `---` break and a bolded action prompt, so the spec now reads `### CHECKPOINT: [Type]` heading → Progress {X}/{Y} → Task name → type-specific content → `---` → `**YOUR ACTION: [signal]**`. Growth is 22 bytes (40111 -> 40133, still under the DEFAULT_CAP of 40960 LF bytes, 827 bytes of headroom) — the naming of the two new Markdown tokens and the heading marker. Appended here rather than filed as a new fragment because a growth ack keys on the BARE FILENAME and #3370 already declares `execute-plan.md`; two sources naming one path is a hard duplicate-key error, so this follows the same supersede-by-append route #3370 itself took for the spent #2652 fragment. Every other file this change touches SHRANK, which needs no acknowledgment."
}
}

View File

@@ -1,12 +0,0 @@
{
"version": 1,
"paths": {
"gsd-phase-researcher.md": "#3409: guarded `cat \"$phase_dir\"/*-CONTEXT.md` against nullglob wiping the pattern to zero operands when no CONTEXT.md exists — a bare `cat` with no operands blocks reading stdin (hangs the agent) instead of the `2>/dev/null` guard ever firing, since a stalled read is not a failing exit. Now checks `${_CTX[0]}` is a real path before invoking cat. Growth is the array-guard idiom itself (+43 bytes). — #2951 append (merged into this fragment because two ack sources may never name the same path; the #3409 entry above is at the base and therefore spent, and a second fragment naming this path is a hard failure in scripts/lint-emitted-drift-ack.cjs): +2352 bytes, 44250 -> 46602 (LARGE tier, cap 49152, 2550 bytes headroom). Adds the absent-evidence provenance rule to the claim-provenance section — the third member of the family alongside the package-name and in-repo-value rules. A compatibility claim resting on MISSING metadata (no python_requires, no engines field, no per-version classifier, no changelog entry, no matching support-matrix row) no longer earns [VERIFIED] however authoritative the source consulted; the only route from an absence to [VERIFIED] is a positive falsification attempt with its failing output pasted, and everything short of that is [ASSUMED], which the file already routes to \"needs user confirmation before becoming a locked decision\". Growth is inline prose in the agent body, deliberately NOT relocated into an eagerly @-imported reference, which ADR-1610 Decision 4 names as gaming the size proxy.",
"gsd-verifier.md": "#3409: same nullglob-hang fix as gsd-phase-researcher.md, applied to `cat \"$PHASE_DIR\"/*-VERIFICATION.md` in Step 0 — an absent VERIFICATION.md previously left a zero-operand `cat` blocking on stdin instead of falling through to first-verification mode. Growth is the array-guard idiom (+49 bytes). — #3206 append (merged into this fragment because two ack sources may never name the same path): +52 bytes, 49098 -> 49150 (2 under the LARGE cap). The growth is the literal fix for the term 5b used undefined: the compressed explicit-evidence definition inlined at 5b (+34 net on the rewritten line — the trailing honest-verifier cite there is dropped as superseded by the inline definition; honest-verifier.md stays cited at 5c) plus gsd-core/ path-prefix repairs on the two 404ing bare references/ cites at 5c (honest-verifier.md) and the MVP-mode section (verify-mvp-mode.md) (+9 each). Lazy extraction remains untakeable in this change: the large extractable blocks are content-pinned by tests that read the agent file directly (tests/verifier-behavior-unverified.test.cjs, tests/verification-overrides.test.cjs), so extraction is its own coordinated change.",
"complete-milestone.md": "#3409: guarded `cat .planning/phases/*-*/*-SUMMARY.md` — with `shopt -s nullglob` active in this block's preamble (#2962), zero matching phase summaries collapses the glob to nothing and a bare `cat` blocks reading stdin rather than producing empty output, wedging the milestone-completion review. Growth is the array-existence-check idiom (+73 bytes, two glob segments makes this longer than the single-glob sites). — #2142 append (merged into this fragment because two ack sources may never name the same path): +1605 bytes, 40498 -> 42103. The `archive_milestone` step now documents the opt-in `--archive-quick` quick-task archival flag (default OFF, deliberately NOT symmetrical with phase archival's default-ON posture), folds the AskUserQuestion decision for it into the SAME `milestone.complete` invocation (avoiding a redundant second call), and states the known bucket-all provenance limit.",
"discuss-phase-assumptions.md": "#3409: replaced the unreachable `AUTO_MODE=$(gsd_run query check auto-mode --pick active 2>/dev/null || echo \"false\")` — `||` never fires because the query exits 0 with empty stdout when the field is absent, not a failure, so AUTO_MODE silently ended up empty rather than \"false\" — with a two-line capture-then-default (`AUTO_MODE=\"${AUTO_MODE:-false}\"`) that actually reaches the fallback. Growth is the extra default-assignment line (+19 bytes).",
"plan-phase.md": "#3409: three sites. `AUTO_CHAIN` and `PHASE_REQ_IDS` get the same unreachable-`||`-fallback fix as discuss-phase-assumptions.md (empty-but-successful `gsd_run query` output never triggered `|| echo`, now uses `${VAR:-default}`); `PRIOR_SUMMARIES` additionally swapped `gsd_run query phases.list --pick summaries_total` for `--type summaries --pick count` since the old pick key produced the same unreachable-fallback failure mode for the walking-skeleton check. Net growth across the three sites is +39 bytes. — #3576 append: bare `references/<name>.md` cites repaired to the canonical `gsd-core/references/<name>.md` form (+36 bytes, 4 cite(s) × 9). Dead-pointer fix; no content change. #3559: the generic gate-dispatch arm gained the in-context validation contract for manifest-supplied check values. gates[].check is not one of the four executable surfaces the install consent prompt discloses, so a capability consented to as declarative-only could still reach a shell through an unvalidated check.query interpolated into a command substitution. The reference (references/loop-hook-dispatch.md) stated this requirement for step -> ref.command and omitted it for gate; that omission is the root cause and is now closed at the reference plus all four dispatch sites. Growth is one validation paragraph per site. 90516 -> 90627 bytes (+111). — #2401 append (merged into this fragment because two ack sources may never name the same path): plan-phase.md now dispatches the deterministic `gsd_run check verify-command-paths` probe before spawning the plan-check pass and interpolates its result into the verification prompt as {VERIFY_PATHS} inside a new <verify_command_path_probe> block, plus a <proven_verify_commands> block carrying prior_verify_commands into planning context so the planner can reuse a proven path instead of re-deriving one. 90627 -> 92807 bytes (+2180). Deliberate runtime-loaded workflow text for the new feature, not converter drift. — #3606 append: plan:pre gains generic contribution dispatch (deferral covering every `into` target, not just planner); plan:post gains generic step + contribution dispatch and its skip condition no longer keys on the gap-analysis gate’s absence (that skip silently dropped every other registered hook at the point). #3645 append (merged here because two ack sources may never name the same path): plan-phase.md also grew ~+1040 for the <tracked_source_paths> block in the gsd-planner spawn prompt — files_modified/must_haves/artifact/action/PATTERNS-inherited paths must be git-tracked source (git ls-files verification, tracked-origin fallback, re-verify inherited paths).",
"session-report.md": "#3409: guarded `ls -la .planning/reports/SESSION_REPORT*.md 2>/dev/null || echo \"No previous reports\"` — with nullglob active, zero prior reports collapses the pattern to nothing and `ls -la` with no operands lists the current directory (a successful exit, wrong output) instead of failing into the `|| echo` fallback, so the report-existence check silently printed a directory listing. Replaced with an array-existence check that only lists when a real report file is present. Growth is the guard idiom (+58 bytes).",
"transition.md": "#3409: guarded `cat .planning/phases/XX-current/*-SUMMARY.md` — same nullglob-hang defect as complete-milestone.md's phase-summary read: zero summaries left a bare `cat` blocking on stdin instead of proceeding with no summary content during PROJECT.md evolution. Growth is the array-existence-check idiom (+73 bytes)."
}
}

View File

@@ -1,7 +0,0 @@
{
"version": 1,
"paths": {
"debug.md": "#3448: both auto-resume paragraphs (Section 1c continue-path return handling and Section 4's non-terminal branch — textual duplicates, both fixed for symmetry) grow to append the resume parameters to the re-spawn: `resume: true`, `resume_status`, `resume_next_action`, both sourced from `.planning/debug/{slug}.md`, plus the explicit not-parameter-identical-to-a-cold-start rationale. Before this, the respawn carried identical session_params, so the recorded next action and the prior-checkpoints-already-answered disposition never reached the respawned manager — the auto-resume stall behind the no-progress guard. Growth is +963 bytes (21,315 -> 22,278), far under the 40,960 DEFAULT tier cap. The anti-loop guard paragraphs (next_action-only heuristic + absolute 3-resume hard cap) are untouched.",
"gsd-debug-session-manager.md": "#3448: <session_parameters> documents the new `resume`/`resume_status`/`resume_next_action` trio, and the Step 2 gsd-debugger prompt template gains a conditional <resume_directive> block carrying the recorded next action and the prior-checkpoints-already-answered disposition, DATA_START/DATA_END-bounded per the existing Step 3d checkpoint-response shape — the seam that lets the manager consume what the orchestrator now forwards. Growth is +822 bytes (19,751 -> 20,573). Terminal paths, CHECKPOINT REACHED handling, and the commit step are untouched."
}
}

View File

@@ -1,8 +0,0 @@
{
"version": 1,
"paths": {
"gsd-mempalace-curator.md": {
"reason": "#3479: the diary_journal and mirror_kg task gates changed from positive presence ('when <key> is true') to disabled-only-on-explicit-false ('unless <key> !== false — registry default is true, an absent key means enabled'), matching the registry-declared defaults. The ~124-byte growth is the two inline absence-semantics clarifications. #3565 appends the standard <required_reading> MUST-Read gate clause (~200 B): ship.md emits a required-reading block to an agent whose instructions never referenced the gate — the F8 defect one layer up, caught by the new read-tag arm."
}
}
}

View File

@@ -1,287 +0,0 @@
{
"paths": {
"gsd-core/references/context-budget.md": {
"reason": "#3544: the gsd-core/ spec-tree emit path now runs restoreClaudeGlobalAtRefTilde over @-file-reference lines, rewriting every @$HOME/gsd-core/... include to the @~/gsd-core/... tilde form Claude Code actually expands (verified: @$HOME references silently resolved to nothing on a live global install, 54 includes across 22 files). This is the same correction #3133 already applies to skill/command bodies via _applyRuntimeRewrites's 'claude' case; this fragment covers the gsd-core/ workflows, references, and templates tree, which never had it. bin/install.js's bare-form '~/.claude'/'$HOME/.claude' substitution regexes were also tightened from a trailing \\b to (?![\\w-]), so a --config-dir extending '.claude' (e.g. '.claude-work') no longer gets its own emitted prefix re-mangled; that fix does not itself change any byte in this fragment's paths under the default global install measured here, but is part of the same commit and is named for completeness."
},
"gsd-core/references/edge-probe.md": {
"reason": "#3544: the gsd-core/ spec-tree emit path now runs restoreClaudeGlobalAtRefTilde over @-file-reference lines, rewriting every @$HOME/gsd-core/... include to the @~/gsd-core/... tilde form Claude Code actually expands (verified: @$HOME references silently resolved to nothing on a live global install, 54 includes across 22 files). This is the same correction #3133 already applies to skill/command bodies via _applyRuntimeRewrites's 'claude' case; this fragment covers the gsd-core/ workflows, references, and templates tree, which never had it. bin/install.js's bare-form '~/.claude'/'$HOME/.claude' substitution regexes were also tightened from a trailing \\b to (?![\\w-]), so a --config-dir extending '.claude' (e.g. '.claude-work') no longer gets its own emitted prefix re-mangled; that fix does not itself change any byte in this fragment's paths under the default global install measured here, but is part of the same commit and is named for completeness."
},
"gsd-core/references/honest-verifier.md": {
"reason": "#3544: the gsd-core/ spec-tree emit path now runs restoreClaudeGlobalAtRefTilde over @-file-reference lines, rewriting every @$HOME/gsd-core/... include to the @~/gsd-core/... tilde form Claude Code actually expands (verified: @$HOME references silently resolved to nothing on a live global install, 54 includes across 22 files). This is the same correction #3133 already applies to skill/command bodies via _applyRuntimeRewrites's 'claude' case; this fragment covers the gsd-core/ workflows, references, and templates tree, which never had it. bin/install.js's bare-form '~/.claude'/'$HOME/.claude' substitution regexes were also tightened from a trailing \\b to (?![\\w-]), so a --config-dir extending '.claude' (e.g. '.claude-work') no longer gets its own emitted prefix re-mangled; that fix does not itself change any byte in this fragment's paths under the default global install measured here, but is part of the same commit and is named for completeness."
},
"gsd-core/references/model-profile-resolution.md": {
"reason": "#3544: the gsd-core/ spec-tree emit path now runs restoreClaudeGlobalAtRefTilde over @-file-reference lines, rewriting every @$HOME/gsd-core/... include to the @~/gsd-core/... tilde form Claude Code actually expands (verified: @$HOME references silently resolved to nothing on a live global install, 54 includes across 22 files). This is the same correction #3133 already applies to skill/command bodies via _applyRuntimeRewrites's 'claude' case; this fragment covers the gsd-core/ workflows, references, and templates tree, which never had it. bin/install.js's bare-form '~/.claude'/'$HOME/.claude' substitution regexes were also tightened from a trailing \\b to (?![\\w-]), so a --config-dir extending '.claude' (e.g. '.claude-work') no longer gets its own emitted prefix re-mangled; that fix does not itself change any byte in this fragment's paths under the default global install measured here, but is part of the same commit and is named for completeness."
},
"gsd-core/references/planner-guidance.md": {
"reason": "#3544: the gsd-core/ spec-tree emit path now runs restoreClaudeGlobalAtRefTilde over @-file-reference lines, rewriting every @$HOME/gsd-core/... include to the @~/gsd-core/... tilde form Claude Code actually expands (verified: @$HOME references silently resolved to nothing on a live global install, 54 includes across 22 files). This is the same correction #3133 already applies to skill/command bodies via _applyRuntimeRewrites's 'claude' case; this fragment covers the gsd-core/ workflows, references, and templates tree, which never had it. bin/install.js's bare-form '~/.claude'/'$HOME/.claude' substitution regexes were also tightened from a trailing \\b to (?![\\w-]), so a --config-dir extending '.claude' (e.g. '.claude-work') no longer gets its own emitted prefix re-mangled; that fix does not itself change any byte in this fragment's paths under the default global install measured here, but is part of the same commit and is named for completeness."
},
"gsd-core/references/planner-mvp-mode.md": {
"reason": "#3544: the gsd-core/ spec-tree emit path now runs restoreClaudeGlobalAtRefTilde over @-file-reference lines, rewriting every @$HOME/gsd-core/... include to the @~/gsd-core/... tilde form Claude Code actually expands (verified: @$HOME references silently resolved to nothing on a live global install, 54 includes across 22 files). This is the same correction #3133 already applies to skill/command bodies via _applyRuntimeRewrites's 'claude' case; this fragment covers the gsd-core/ workflows, references, and templates tree, which never had it. bin/install.js's bare-form '~/.claude'/'$HOME/.claude' substitution regexes were also tightened from a trailing \\b to (?![\\w-]), so a --config-dir extending '.claude' (e.g. '.claude-work') no longer gets its own emitted prefix re-mangled; that fix does not itself change any byte in this fragment's paths under the default global install measured here, but is part of the same commit and is named for completeness."
},
"gsd-core/references/prohibition-probe.md": {
"reason": "#3544: the gsd-core/ spec-tree emit path now runs restoreClaudeGlobalAtRefTilde over @-file-reference lines, rewriting every @$HOME/gsd-core/... include to the @~/gsd-core/... tilde form Claude Code actually expands (verified: @$HOME references silently resolved to nothing on a live global install, 54 includes across 22 files). This is the same correction #3133 already applies to skill/command bodies via _applyRuntimeRewrites's 'claude' case; this fragment covers the gsd-core/ workflows, references, and templates tree, which never had it. bin/install.js's bare-form '~/.claude'/'$HOME/.claude' substitution regexes were also tightened from a trailing \\b to (?![\\w-]), so a --config-dir extending '.claude' (e.g. '.claude-work') no longer gets its own emitted prefix re-mangled; that fix does not itself change any byte in this fragment's paths under the default global install measured here, but is part of the same commit and is named for completeness."
},
"gsd-core/references/thinking-models-planning.md": {
"reason": "#3544: the gsd-core/ spec-tree emit path now runs restoreClaudeGlobalAtRefTilde over @-file-reference lines, rewriting every @$HOME/gsd-core/... include to the @~/gsd-core/... tilde form Claude Code actually expands (verified: @$HOME references silently resolved to nothing on a live global install, 54 includes across 22 files). This is the same correction #3133 already applies to skill/command bodies via _applyRuntimeRewrites's 'claude' case; this fragment covers the gsd-core/ workflows, references, and templates tree, which never had it. bin/install.js's bare-form '~/.claude'/'$HOME/.claude' substitution regexes were also tightened from a trailing \\b to (?![\\w-]), so a --config-dir extending '.claude' (e.g. '.claude-work') no longer gets its own emitted prefix re-mangled; that fix does not itself change any byte in this fragment's paths under the default global install measured here, but is part of the same commit and is named for completeness."
},
"gsd-core/references/verification-patterns.md": {
"reason": "#3544: the gsd-core/ spec-tree emit path now runs restoreClaudeGlobalAtRefTilde over @-file-reference lines, rewriting every @$HOME/gsd-core/... include to the @~/gsd-core/... tilde form Claude Code actually expands (verified: @$HOME references silently resolved to nothing on a live global install, 54 includes across 22 files). This is the same correction #3133 already applies to skill/command bodies via _applyRuntimeRewrites's 'claude' case; this fragment covers the gsd-core/ workflows, references, and templates tree, which never had it. bin/install.js's bare-form '~/.claude'/'$HOME/.claude' substitution regexes were also tightened from a trailing \\b to (?![\\w-]), so a --config-dir extending '.claude' (e.g. '.claude-work') no longer gets its own emitted prefix re-mangled; that fix does not itself change any byte in this fragment's paths under the default global install measured here, but is part of the same commit and is named for completeness."
},
"gsd-core/references/verifier-phase-gates.md": {
"reason": "#3544: the gsd-core/ spec-tree emit path now runs restoreClaudeGlobalAtRefTilde over @-file-reference lines, rewriting every @$HOME/gsd-core/... include to the @~/gsd-core/... tilde form Claude Code actually expands (verified: @$HOME references silently resolved to nothing on a live global install, 54 includes across 22 files). This is the same correction #3133 already applies to skill/command bodies via _applyRuntimeRewrites's 'claude' case; this fragment covers the gsd-core/ workflows, references, and templates tree, which never had it. bin/install.js's bare-form '~/.claude'/'$HOME/.claude' substitution regexes were also tightened from a trailing \\b to (?![\\w-]), so a --config-dir extending '.claude' (e.g. '.claude-work') no longer gets its own emitted prefix re-mangled; that fix does not itself change any byte in this fragment's paths under the default global install measured here, but is part of the same commit and is named for completeness."
},
"gsd-core/templates/codebase/structure.md": {
"reason": "#3544: the gsd-core/ spec-tree emit path now runs restoreClaudeGlobalAtRefTilde over @-file-reference lines, rewriting every @$HOME/gsd-core/... include to the @~/gsd-core/... tilde form Claude Code actually expands (verified: @$HOME references silently resolved to nothing on a live global install, 54 includes across 22 files). This is the same correction #3133 already applies to skill/command bodies via _applyRuntimeRewrites's 'claude' case; this fragment covers the gsd-core/ workflows, references, and templates tree, which never had it. bin/install.js's bare-form '~/.claude'/'$HOME/.claude' substitution regexes were also tightened from a trailing \\b to (?![\\w-]), so a --config-dir extending '.claude' (e.g. '.claude-work') no longer gets its own emitted prefix re-mangled; that fix does not itself change any byte in this fragment's paths under the default global install measured here, but is part of the same commit and is named for completeness."
},
"gsd-core/templates/phase-prompt.md": {
"reason": "#3544: the gsd-core/ spec-tree emit path now runs restoreClaudeGlobalAtRefTilde over @-file-reference lines, rewriting every @$HOME/gsd-core/... include to the @~/gsd-core/... tilde form Claude Code actually expands (verified: @$HOME references silently resolved to nothing on a live global install, 54 includes across 22 files). This is the same correction #3133 already applies to skill/command bodies via _applyRuntimeRewrites's 'claude' case; this fragment covers the gsd-core/ workflows, references, and templates tree, which never had it. bin/install.js's bare-form '~/.claude'/'$HOME/.claude' substitution regexes were also tightened from a trailing \\b to (?![\\w-]), so a --config-dir extending '.claude' (e.g. '.claude-work') no longer gets its own emitted prefix re-mangled; that fix does not itself change any byte in this fragment's paths under the default global install measured here, but is part of the same commit and is named for completeness."
},
"gsd-core/workflows/ai-integration-phase.md": {
"reason": "#3544: the gsd-core/ spec-tree emit path now runs restoreClaudeGlobalAtRefTilde over @-file-reference lines, rewriting every @$HOME/gsd-core/... include to the @~/gsd-core/... tilde form Claude Code actually expands (verified: @$HOME references silently resolved to nothing on a live global install, 54 includes across 22 files). This is the same correction #3133 already applies to skill/command bodies via _applyRuntimeRewrites's 'claude' case; this fragment covers the gsd-core/ workflows, references, and templates tree, which never had it. bin/install.js's bare-form '~/.claude'/'$HOME/.claude' substitution regexes were also tightened from a trailing \\b to (?![\\w-]), so a --config-dir extending '.claude' (e.g. '.claude-work') no longer gets its own emitted prefix re-mangled; that fix does not itself change any byte in this fragment's paths under the default global install measured here, but is part of the same commit and is named for completeness."
},
"gsd-core/workflows/autonomous.md": {
"reason": "#3544: the gsd-core/ spec-tree emit path now runs restoreClaudeGlobalAtRefTilde over @-file-reference lines, rewriting every @$HOME/gsd-core/... include to the @~/gsd-core/... tilde form Claude Code actually expands (verified: @$HOME references silently resolved to nothing on a live global install, 54 includes across 22 files). This is the same correction #3133 already applies to skill/command bodies via _applyRuntimeRewrites's 'claude' case; this fragment covers the gsd-core/ workflows, references, and templates tree, which never had it. bin/install.js's bare-form '~/.claude'/'$HOME/.claude' substitution regexes were also tightened from a trailing \\b to (?![\\w-]), so a --config-dir extending '.claude' (e.g. '.claude-work') no longer gets its own emitted prefix re-mangled; that fix does not itself change any byte in this fragment's paths under the default global install measured here, but is part of the same commit and is named for completeness."
},
"gsd-core/workflows/discuss-phase.md": {
"reason": "#3544: the gsd-core/ spec-tree emit path now runs restoreClaudeGlobalAtRefTilde over @-file-reference lines, rewriting every @$HOME/gsd-core/... include to the @~/gsd-core/... tilde form Claude Code actually expands (verified: @$HOME references silently resolved to nothing on a live global install, 54 includes across 22 files). This is the same correction #3133 already applies to skill/command bodies via _applyRuntimeRewrites's 'claude' case; this fragment covers the gsd-core/ workflows, references, and templates tree, which never had it. bin/install.js's bare-form '~/.claude'/'$HOME/.claude' substitution regexes were also tightened from a trailing \\b to (?![\\w-]), so a --config-dir extending '.claude' (e.g. '.claude-work') no longer gets its own emitted prefix re-mangled; that fix does not itself change any byte in this fragment's paths under the default global install measured here, but is part of the same commit and is named for completeness."
},
"gsd-core/workflows/discuss-phase/modes/power.md": {
"reason": "#3544: the gsd-core/ spec-tree emit path now runs restoreClaudeGlobalAtRefTilde over @-file-reference lines, rewriting every @$HOME/gsd-core/... include to the @~/gsd-core/... tilde form Claude Code actually expands (verified: @$HOME references silently resolved to nothing on a live global install, 54 includes across 22 files). This is the same correction #3133 already applies to skill/command bodies via _applyRuntimeRewrites's 'claude' case; this fragment covers the gsd-core/ workflows, references, and templates tree, which never had it. bin/install.js's bare-form '~/.claude'/'$HOME/.claude' substitution regexes were also tightened from a trailing \\b to (?![\\w-]), so a --config-dir extending '.claude' (e.g. '.claude-work') no longer gets its own emitted prefix re-mangled; that fix does not itself change any byte in this fragment's paths under the default global install measured here, but is part of the same commit and is named for completeness."
},
"gsd-core/workflows/eval-review.md": {
"reason": "#3544: the gsd-core/ spec-tree emit path now runs restoreClaudeGlobalAtRefTilde over @-file-reference lines, rewriting every @$HOME/gsd-core/... include to the @~/gsd-core/... tilde form Claude Code actually expands (verified: @$HOME references silently resolved to nothing on a live global install, 54 includes across 22 files). This is the same correction #3133 already applies to skill/command bodies via _applyRuntimeRewrites's 'claude' case; this fragment covers the gsd-core/ workflows, references, and templates tree, which never had it. bin/install.js's bare-form '~/.claude'/'$HOME/.claude' substitution regexes were also tightened from a trailing \\b to (?![\\w-]), so a --config-dir extending '.claude' (e.g. '.claude-work') no longer gets its own emitted prefix re-mangled; that fix does not itself change any byte in this fragment's paths under the default global install measured here, but is part of the same commit and is named for completeness."
},
"gsd-core/workflows/execute-phase.md": {
"reason": "#3544: the gsd-core/ spec-tree emit path now runs restoreClaudeGlobalAtRefTilde over @-file-reference lines, rewriting every @$HOME/gsd-core/... include to the @~/gsd-core/... tilde form Claude Code actually expands (verified: @$HOME references silently resolved to nothing on a live global install, 54 includes across 22 files). This is the same correction #3133 already applies to skill/command bodies via _applyRuntimeRewrites's 'claude' case; this fragment covers the gsd-core/ workflows, references, and templates tree, which never had it. bin/install.js's bare-form '~/.claude'/'$HOME/.claude' substitution regexes were also tightened from a trailing \\b to (?![\\w-]), so a --config-dir extending '.claude' (e.g. '.claude-work') no longer gets its own emitted prefix re-mangled; that fix does not itself change any byte in this fragment's paths under the default global install measured here, but is part of the same commit and is named for completeness."
},
"gsd-core/workflows/execute-plan.md": {
"reason": "#3544: the gsd-core/ spec-tree emit path now runs restoreClaudeGlobalAtRefTilde over @-file-reference lines, rewriting every @$HOME/gsd-core/... include to the @~/gsd-core/... tilde form Claude Code actually expands (verified: @$HOME references silently resolved to nothing on a live global install, 54 includes across 22 files). This is the same correction #3133 already applies to skill/command bodies via _applyRuntimeRewrites's 'claude' case; this fragment covers the gsd-core/ workflows, references, and templates tree, which never had it. bin/install.js's bare-form '~/.claude'/'$HOME/.claude' substitution regexes were also tightened from a trailing \\b to (?![\\w-]), so a --config-dir extending '.claude' (e.g. '.claude-work') no longer gets its own emitted prefix re-mangled; that fix does not itself change any byte in this fragment's paths under the default global install measured here, but is part of the same commit and is named for completeness."
},
"gsd-core/workflows/explore.md": {
"reason": "#3544: the gsd-core/ spec-tree emit path now runs restoreClaudeGlobalAtRefTilde over @-file-reference lines, rewriting every @$HOME/gsd-core/... include to the @~/gsd-core/... tilde form Claude Code actually expands (verified: @$HOME references silently resolved to nothing on a live global install, 54 includes across 22 files). This is the same correction #3133 already applies to skill/command bodies via _applyRuntimeRewrites's 'claude' case; this fragment covers the gsd-core/ workflows, references, and templates tree, which never had it. bin/install.js's bare-form '~/.claude'/'$HOME/.claude' substitution regexes were also tightened from a trailing \\b to (?![\\w-]), so a --config-dir extending '.claude' (e.g. '.claude-work') no longer gets its own emitted prefix re-mangled; that fix does not itself change any byte in this fragment's paths under the default global install measured here, but is part of the same commit and is named for completeness."
},
"gsd-core/workflows/mvp-phase.md": {
"reason": "#3544: the gsd-core/ spec-tree emit path now runs restoreClaudeGlobalAtRefTilde over @-file-reference lines, rewriting every @$HOME/gsd-core/... include to the @~/gsd-core/... tilde form Claude Code actually expands (verified: @$HOME references silently resolved to nothing on a live global install, 54 includes across 22 files). This is the same correction #3133 already applies to skill/command bodies via _applyRuntimeRewrites's 'claude' case; this fragment covers the gsd-core/ workflows, references, and templates tree, which never had it. bin/install.js's bare-form '~/.claude'/'$HOME/.claude' substitution regexes were also tightened from a trailing \\b to (?![\\w-]), so a --config-dir extending '.claude' (e.g. '.claude-work') no longer gets its own emitted prefix re-mangled; that fix does not itself change any byte in this fragment's paths under the default global install measured here, but is part of the same commit and is named for completeness."
},
"gsd-core/workflows/onboard.md": {
"reason": "#3544: the gsd-core/ spec-tree emit path now runs restoreClaudeGlobalAtRefTilde over @-file-reference lines, rewriting every @$HOME/gsd-core/... include to the @~/gsd-core/... tilde form Claude Code actually expands (verified: @$HOME references silently resolved to nothing on a live global install, 54 includes across 22 files). This is the same correction #3133 already applies to skill/command bodies via _applyRuntimeRewrites's 'claude' case; this fragment covers the gsd-core/ workflows, references, and templates tree, which never had it. bin/install.js's bare-form '~/.claude'/'$HOME/.claude' substitution regexes were also tightened from a trailing \\b to (?![\\w-]), so a --config-dir extending '.claude' (e.g. '.claude-work') no longer gets its own emitted prefix re-mangled; that fix does not itself change any byte in this fragment's paths under the default global install measured here, but is part of the same commit and is named for completeness."
},
"gsd-core/workflows/plan-phase.md": {
"reason": "#3544: the gsd-core/ spec-tree emit path now runs restoreClaudeGlobalAtRefTilde over @-file-reference lines, rewriting every @$HOME/gsd-core/... include to the @~/gsd-core/... tilde form Claude Code actually expands (verified: @$HOME references silently resolved to nothing on a live global install, 54 includes across 22 files). This is the same correction #3133 already applies to skill/command bodies via _applyRuntimeRewrites's 'claude' case; this fragment covers the gsd-core/ workflows, references, and templates tree, which never had it. bin/install.js's bare-form '~/.claude'/'$HOME/.claude' substitution regexes were also tightened from a trailing \\b to (?![\\w-]), so a --config-dir extending '.claude' (e.g. '.claude-work') no longer gets its own emitted prefix re-mangled; that fix does not itself change any byte in this fragment's paths under the default global install measured here, but is part of the same commit and is named for completeness."
},
"gsd-core/workflows/plan-review-convergence.md": {
"reason": "#3544: the gsd-core/ spec-tree emit path now runs restoreClaudeGlobalAtRefTilde over @-file-reference lines, rewriting every @$HOME/gsd-core/... include to the @~/gsd-core/... tilde form Claude Code actually expands (verified: @$HOME references silently resolved to nothing on a live global install, 54 includes across 22 files). This is the same correction #3133 already applies to skill/command bodies via _applyRuntimeRewrites's 'claude' case; this fragment covers the gsd-core/ workflows, references, and templates tree, which never had it. bin/install.js's bare-form '~/.claude'/'$HOME/.claude' substitution regexes were also tightened from a trailing \\b to (?![\\w-]), so a --config-dir extending '.claude' (e.g. '.claude-work') no longer gets its own emitted prefix re-mangled; that fix does not itself change any byte in this fragment's paths under the default global install measured here, but is part of the same commit and is named for completeness."
},
"gsd-core/workflows/profile-user.md": {
"reason": "#3544: the gsd-core/ spec-tree emit path now runs restoreClaudeGlobalAtRefTilde over @-file-reference lines, rewriting every @$HOME/gsd-core/... include to the @~/gsd-core/... tilde form Claude Code actually expands (verified: @$HOME references silently resolved to nothing on a live global install, 54 includes across 22 files). This is the same correction #3133 already applies to skill/command bodies via _applyRuntimeRewrites's 'claude' case; this fragment covers the gsd-core/ workflows, references, and templates tree, which never had it. bin/install.js's bare-form '~/.claude'/'$HOME/.claude' substitution regexes were also tightened from a trailing \\b to (?![\\w-]), so a --config-dir extending '.claude' (e.g. '.claude-work') no longer gets its own emitted prefix re-mangled; that fix does not itself change any byte in this fragment's paths under the default global install measured here, but is part of the same commit and is named for completeness."
},
"gsd-core/workflows/resume-project.md": {
"reason": "#3544: the gsd-core/ spec-tree emit path now runs restoreClaudeGlobalAtRefTilde over @-file-reference lines, rewriting every @$HOME/gsd-core/... include to the @~/gsd-core/... tilde form Claude Code actually expands (verified: @$HOME references silently resolved to nothing on a live global install, 54 includes across 22 files). This is the same correction #3133 already applies to skill/command bodies via _applyRuntimeRewrites's 'claude' case; this fragment covers the gsd-core/ workflows, references, and templates tree, which never had it. bin/install.js's bare-form '~/.claude'/'$HOME/.claude' substitution regexes were also tightened from a trailing \\b to (?![\\w-]), so a --config-dir extending '.claude' (e.g. '.claude-work') no longer gets its own emitted prefix re-mangled; that fix does not itself change any byte in this fragment's paths under the default global install measured here, but is part of the same commit and is named for completeness."
},
"gsd-core/workflows/secure-phase.md": {
"reason": "#3544: the gsd-core/ spec-tree emit path now runs restoreClaudeGlobalAtRefTilde over @-file-reference lines, rewriting every @$HOME/gsd-core/... include to the @~/gsd-core/... tilde form Claude Code actually expands (verified: @$HOME references silently resolved to nothing on a live global install, 54 includes across 22 files). This is the same correction #3133 already applies to skill/command bodies via _applyRuntimeRewrites's 'claude' case; this fragment covers the gsd-core/ workflows, references, and templates tree, which never had it. bin/install.js's bare-form '~/.claude'/'$HOME/.claude' substitution regexes were also tightened from a trailing \\b to (?![\\w-]), so a --config-dir extending '.claude' (e.g. '.claude-work') no longer gets its own emitted prefix re-mangled; that fix does not itself change any byte in this fragment's paths under the default global install measured here, but is part of the same commit and is named for completeness."
},
"gsd-core/workflows/sketch.md": {
"reason": "#3544: the gsd-core/ spec-tree emit path now runs restoreClaudeGlobalAtRefTilde over @-file-reference lines, rewriting every @$HOME/gsd-core/... include to the @~/gsd-core/... tilde form Claude Code actually expands (verified: @$HOME references silently resolved to nothing on a live global install, 54 includes across 22 files). This is the same correction #3133 already applies to skill/command bodies via _applyRuntimeRewrites's 'claude' case; this fragment covers the gsd-core/ workflows, references, and templates tree, which never had it. bin/install.js's bare-form '~/.claude'/'$HOME/.claude' substitution regexes were also tightened from a trailing \\b to (?![\\w-]), so a --config-dir extending '.claude' (e.g. '.claude-work') no longer gets its own emitted prefix re-mangled; that fix does not itself change any byte in this fragment's paths under the default global install measured here, but is part of the same commit and is named for completeness."
},
"gsd-core/workflows/spec-phase.md": {
"reason": "#3544: the gsd-core/ spec-tree emit path now runs restoreClaudeGlobalAtRefTilde over @-file-reference lines, rewriting every @$HOME/gsd-core/... include to the @~/gsd-core/... tilde form Claude Code actually expands (verified: @$HOME references silently resolved to nothing on a live global install, 54 includes across 22 files). This is the same correction #3133 already applies to skill/command bodies via _applyRuntimeRewrites's 'claude' case; this fragment covers the gsd-core/ workflows, references, and templates tree, which never had it. bin/install.js's bare-form '~/.claude'/'$HOME/.claude' substitution regexes were also tightened from a trailing \\b to (?![\\w-]), so a --config-dir extending '.claude' (e.g. '.claude-work') no longer gets its own emitted prefix re-mangled; that fix does not itself change any byte in this fragment's paths under the default global install measured here, but is part of the same commit and is named for completeness."
},
"gsd-core/workflows/transition.md": {
"reason": "#3544: the gsd-core/ spec-tree emit path now runs restoreClaudeGlobalAtRefTilde over @-file-reference lines, rewriting every @$HOME/gsd-core/... include to the @~/gsd-core/... tilde form Claude Code actually expands (verified: @$HOME references silently resolved to nothing on a live global install, 54 includes across 22 files). This is the same correction #3133 already applies to skill/command bodies via _applyRuntimeRewrites's 'claude' case; this fragment covers the gsd-core/ workflows, references, and templates tree, which never had it. bin/install.js's bare-form '~/.claude'/'$HOME/.claude' substitution regexes were also tightened from a trailing \\b to (?![\\w-]), so a --config-dir extending '.claude' (e.g. '.claude-work') no longer gets its own emitted prefix re-mangled; that fix does not itself change any byte in this fragment's paths under the default global install measured here, but is part of the same commit and is named for completeness."
},
"gsd-core/workflows/ui-phase.md": {
"reason": "#3544: the gsd-core/ spec-tree emit path now runs restoreClaudeGlobalAtRefTilde over @-file-reference lines, rewriting every @$HOME/gsd-core/... include to the @~/gsd-core/... tilde form Claude Code actually expands (verified: @$HOME references silently resolved to nothing on a live global install, 54 includes across 22 files). This is the same correction #3133 already applies to skill/command bodies via _applyRuntimeRewrites's 'claude' case; this fragment covers the gsd-core/ workflows, references, and templates tree, which never had it. bin/install.js's bare-form '~/.claude'/'$HOME/.claude' substitution regexes were also tightened from a trailing \\b to (?![\\w-]), so a --config-dir extending '.claude' (e.g. '.claude-work') no longer gets its own emitted prefix re-mangled; that fix does not itself change any byte in this fragment's paths under the default global install measured here, but is part of the same commit and is named for completeness."
},
"gsd-core/workflows/ui-review.md": {
"reason": "#3544: the gsd-core/ spec-tree emit path now runs restoreClaudeGlobalAtRefTilde over @-file-reference lines, rewriting every @$HOME/gsd-core/... include to the @~/gsd-core/... tilde form Claude Code actually expands (verified: @$HOME references silently resolved to nothing on a live global install, 54 includes across 22 files). This is the same correction #3133 already applies to skill/command bodies via _applyRuntimeRewrites's 'claude' case; this fragment covers the gsd-core/ workflows, references, and templates tree, which never had it. bin/install.js's bare-form '~/.claude'/'$HOME/.claude' substitution regexes were also tightened from a trailing \\b to (?![\\w-]), so a --config-dir extending '.claude' (e.g. '.claude-work') no longer gets its own emitted prefix re-mangled; that fix does not itself change any byte in this fragment's paths under the default global install measured here, but is part of the same commit and is named for completeness."
},
"gsd-core/workflows/undo.md": {
"reason": "#3544: the gsd-core/ spec-tree emit path now runs restoreClaudeGlobalAtRefTilde over @-file-reference lines, rewriting every @$HOME/gsd-core/... include to the @~/gsd-core/... tilde form Claude Code actually expands (verified: @$HOME references silently resolved to nothing on a live global install, 54 includes across 22 files). This is the same correction #3133 already applies to skill/command bodies via _applyRuntimeRewrites's 'claude' case; this fragment covers the gsd-core/ workflows, references, and templates tree, which never had it. bin/install.js's bare-form '~/.claude'/'$HOME/.claude' substitution regexes were also tightened from a trailing \\b to (?![\\w-]), so a --config-dir extending '.claude' (e.g. '.claude-work') no longer gets its own emitted prefix re-mangled; that fix does not itself change any byte in this fragment's paths under the default global install measured here, but is part of the same commit and is named for completeness."
},
"gsd-core/workflows/validate-phase.md": {
"reason": "#3544: the gsd-core/ spec-tree emit path now runs restoreClaudeGlobalAtRefTilde over @-file-reference lines, rewriting every @$HOME/gsd-core/... include to the @~/gsd-core/... tilde form Claude Code actually expands (verified: @$HOME references silently resolved to nothing on a live global install, 54 includes across 22 files). This is the same correction #3133 already applies to skill/command bodies via _applyRuntimeRewrites's 'claude' case; this fragment covers the gsd-core/ workflows, references, and templates tree, which never had it. bin/install.js's bare-form '~/.claude'/'$HOME/.claude' substitution regexes were also tightened from a trailing \\b to (?![\\w-]), so a --config-dir extending '.claude' (e.g. '.claude-work') no longer gets its own emitted prefix re-mangled; that fix does not itself change any byte in this fragment's paths under the default global install measured here, but is part of the same commit and is named for completeness."
},
"gsd-core/workflows/verify-work.md": {
"reason": "#3544: the gsd-core/ spec-tree emit path now runs restoreClaudeGlobalAtRefTilde over @-file-reference lines, rewriting every @$HOME/gsd-core/... include to the @~/gsd-core/... tilde form Claude Code actually expands (verified: @$HOME references silently resolved to nothing on a live global install, 54 includes across 22 files). This is the same correction #3133 already applies to skill/command bodies via _applyRuntimeRewrites's 'claude' case; this fragment covers the gsd-core/ workflows, references, and templates tree, which never had it. bin/install.js's bare-form '~/.claude'/'$HOME/.claude' substitution regexes were also tightened from a trailing \\b to (?![\\w-]), so a --config-dir extending '.claude' (e.g. '.claude-work') no longer gets its own emitted prefix re-mangled; that fix does not itself change any byte in this fragment's paths under the default global install measured here, but is part of the same commit and is named for completeness."
},
"gsd-core/workflows/verify-work/steps/mvp-uat-framing.md": {
"reason": "#3544: the gsd-core/ spec-tree emit path now runs restoreClaudeGlobalAtRefTilde over @-file-reference lines, rewriting every @$HOME/gsd-core/... include to the @~/gsd-core/... tilde form Claude Code actually expands (verified: @$HOME references silently resolved to nothing on a live global install, 54 includes across 22 files). This is the same correction #3133 already applies to skill/command bodies via _applyRuntimeRewrites's 'claude' case; this fragment covers the gsd-core/ workflows, references, and templates tree, which never had it. bin/install.js's bare-form '~/.claude'/'$HOME/.claude' substitution regexes were also tightened from a trailing \\b to (?![\\w-]), so a --config-dir extending '.claude' (e.g. '.claude-work') no longer gets its own emitted prefix re-mangled; that fix does not itself change any byte in this fragment's paths under the default global install measured here, but is part of the same commit and is named for completeness."
},
"skills/gsd-add-tests/SKILL.md": {
"reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install."
},
"skills/gsd-ai-integration-phase/SKILL.md": {
"reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install."
},
"skills/gsd-audit-fix/SKILL.md": {
"reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install."
},
"skills/gsd-audit-milestone/SKILL.md": {
"reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install."
},
"skills/gsd-audit-uat/SKILL.md": {
"reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install."
},
"skills/gsd-autonomous/SKILL.md": {
"reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install."
},
"skills/gsd-capture/SKILL.md": {
"reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install."
},
"skills/gsd-cleanup/SKILL.md": {
"reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install."
},
"skills/gsd-code-review/SKILL.md": {
"reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install."
},
"skills/gsd-complete-milestone/SKILL.md": {
"reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install."
},
"skills/gsd-config/SKILL.md": {
"reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install."
},
"skills/gsd-debug/SKILL.md": {
"reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install."
},
"skills/gsd-docs-update/SKILL.md": {
"reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install."
},
"skills/gsd-eval-review/SKILL.md": {
"reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install."
},
"skills/gsd-execute-phase/SKILL.md": {
"reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install."
},
"skills/gsd-explore/SKILL.md": {
"reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install."
},
"skills/gsd-extract-learnings/SKILL.md": {
"reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install."
},
"skills/gsd-fast/SKILL.md": {
"reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install."
},
"skills/gsd-forensics/SKILL.md": {
"reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install."
},
"skills/gsd-health/SKILL.md": {
"reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install."
},
"skills/gsd-help/SKILL.md": {
"reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install."
},
"skills/gsd-import/SKILL.md": {
"reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install."
},
"skills/gsd-inbox/SKILL.md": {
"reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install."
},
"skills/gsd-ingest-docs/SKILL.md": {
"reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install."
},
"skills/gsd-manager/SKILL.md": {
"reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install."
},
"skills/gsd-map-codebase/SKILL.md": {
"reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install."
},
"skills/gsd-milestone-summary/SKILL.md": {
"reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install."
},
"skills/gsd-mvp-phase/SKILL.md": {
"reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install."
},
"skills/gsd-new-milestone/SKILL.md": {
"reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install."
},
"skills/gsd-new-project/SKILL.md": {
"reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install."
},
"skills/gsd-next/SKILL.md": {
"reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install."
},
"skills/gsd-onboard/SKILL.md": {
"reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install."
},
"skills/gsd-pause-work/SKILL.md": {
"reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install."
},
"skills/gsd-phase/SKILL.md": {
"reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install."
},
"skills/gsd-plan-phase/SKILL.md": {
"reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install."
},
"skills/gsd-plan-review-convergence/SKILL.md": {
"reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install."
},
"skills/gsd-pr-branch/SKILL.md": {
"reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install."
},
"skills/gsd-profile-user/SKILL.md": {
"reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install."
},
"skills/gsd-progress/SKILL.md": {
"reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install."
},
"skills/gsd-quick/SKILL.md": {
"reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install."
},
"skills/gsd-resume-work/SKILL.md": {
"reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install."
},
"skills/gsd-review/SKILL.md": {
"reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install."
},
"skills/gsd-secure-phase/SKILL.md": {
"reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install."
},
"skills/gsd-settings/SKILL.md": {
"reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install."
},
"skills/gsd-ship/SKILL.md": {
"reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install."
},
"skills/gsd-sketch/SKILL.md": {
"reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install."
},
"skills/gsd-spec-phase/SKILL.md": {
"reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install."
},
"skills/gsd-spike/SKILL.md": {
"reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install."
},
"skills/gsd-stats/SKILL.md": {
"reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install."
},
"skills/gsd-thread/SKILL.md": {
"reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install."
},
"skills/gsd-ui-phase/SKILL.md": {
"reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install."
},
"skills/gsd-ui-review/SKILL.md": {
"reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install."
},
"skills/gsd-ultraplan-phase/SKILL.md": {
"reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install."
},
"skills/gsd-undo/SKILL.md": {
"reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install."
},
"skills/gsd-update/SKILL.md": {
"reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install."
},
"skills/gsd-validate-phase/SKILL.md": {
"reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install."
},
"skills/gsd-verify-work/SKILL.md": {
"reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install."
},
"skills/gsd-workspace/SKILL.md": {
"reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install."
}
},
"version": 1
}

View File

@@ -1,6 +0,0 @@
{
"version": 1,
"paths": {
"gsd-user-profiler.md": "#3565: gained the standard <required_reading> MUST-Read gate clause. profile-user.md sends a required-reading block; the agent's instructions never referenced the gate, so the clause could not fire — the F8 defect one layer up, caught by the new read-tag arm. +~200 bytes."
}
}

View File

@@ -1,7 +0,0 @@
{
"version": 1,
"paths": {
"import.md": "#3576: four bare `references/<name>.md` cites repaired to the canonical `gsd-core/references/<name>.md` form (+36 bytes, 4 \u00d7 9). Dead-pointer fix; no content change. \u2014 #3602 append: gains a CHECKER_MODEL resolve-model binding, the #2517 marker + rule block, and model=\"{CHECKER_MODEL}\" on the gsd-plan-checker Agent block so the checker honors dynamic_routing/model_profile tiers.",
"gsd-doc-synthesizer.md": "#3576: two bare `references/<name>.md` cites repaired to the canonical `gsd-core/references/<name>.md` form (+18 bytes, 2 \u00d7 9). Dead-pointer fix; no content change."
}
}

View File

@@ -1,11 +0,0 @@
{
"version": 1,
"paths": {
"fast.md": {
"reason": "#3585: the commit step ran `git add -A`, sweeping .planning/ into every /gsd:fast commit regardless of commit_docs. Gating it needs `gsd_run` in that block, so the launcher preamble was MOVED from the later log_to_state step into the commit step (moved, not duplicated \u2014 log_to_state now relies on the one-preamble-per-workflow convention every other workflow uses). Growth is the preamble's new position plus four lines of guard; the commit_docs=true path stays byte-identical to the previous unconditional `git add -A`."
},
"new-milestone.md": {
"reason": "#3585: `git add .planning/milestones/ .planning/phases/` was ungated, and the correctly-gated `query commit` that follows it skips under commit_docs:false \u2014 leaving those paths in the index for the next commit to absorb. Growth is the executable commit_docs guard wrapping the stage, plus one sentence recording that the unstaged archive move is deliberate rather than a bug. \u2014 #3576 append: bare `references/<name>.md` cites repaired to the canonical `gsd-core/references/<name>.md` form (+18 bytes, 2 cite(s) \u00d7 9). Dead-pointer fix; no content change."
}
}
}

View File

@@ -1,17 +0,0 @@
{
"version": 1,
"paths": {
"audit-fix.md": {
"reason": "#3602: the issue's file-level grep audit missed this file \u2014 its gsd-executor spawn is dispatch-shaped. Gains an EXECUTOR_MODEL resolve-model binding, the #2517 marker + rule block, and model= on the executor Agent block. Deliberate growth, not converter drift."
},
"diagnose-issues.md": {
"reason": "#3602: same class \u2014 gsd-debugger spawned with no model resolution. Gains a DEBUGGER_MODEL resolve-model assignment in the pre-spawn block, the #2517 marker + rule block, and model=\"{DEBUGGER_MODEL}\" on the debugger Agent block. Deliberate growth, not converter drift. #3659 appends --mode \"$ISOLATION\" to both #2649 base-check calls (+40B): the isolation mode now gates the baseref-head suppress (harness ignores the setting, #48). Deliberate growth."
},
"profile-user.md": {
"reason": "#3602: same class via a Task-tool prose spawn the issue's Agent()-shaped audit could not see. Gains a PROFILER_MODEL resolve-model binding, the #2517 marker + rule block, and a model=\"{PROFILER_MODEL}\" pass/omit instruction on the Task-tool spawn. Deliberate growth, not converter drift."
},
"docs-update.md": {
"reason": "#3602 (isolated adversarial review finding): the --verify-only step's gsd-doc-verifier spawn had no model resolution \u2014 doc_writer_model covers only the writer spawns, and docs-init emits no verifier field. Gains a DOC_VERIFIER_MODEL resolve-model assignment in the init block, a model=\"{DOC_VERIFIER_MODEL}\" pass/omit instruction at the verifier spawn, and the #2517 blockquote's variable list extended. Deliberate growth, not converter drift."
}
}
}

View File

@@ -1,8 +0,0 @@
{
"version": 1,
"paths": {
"quick.md": {
"reason": "#3606: the execute:post consumer gains generic step dispatch (deferral to loop-hook-dispatch.md) before the code-review specialization, so refactor-trigger's ref.command step and any other registered execute:post steps actually run on /gsd:quick runs instead of being filtered out by the one-skill narrow. Deliberate growth, not converter drift. #3659 appends --mode \"$ISOLATION\" to both #1941 base-check calls (+40B): the isolation mode now gates the baseref-head suppress \u2014 the runtime harness does not read project-settings baseRef (#48), so harness mode must compare instead of suppressing. Deliberate growth."
}
}
}

View File

@@ -1,7 +0,0 @@
{
"$comment": "Growth ack (#2914 fragment). Reason: #3645 adds the tracked-source rule (git ls-files verification, gitignored-mirror rejection, tracked-origin fallback, submodule note). gsd-pattern-mapper.md 12487 -> 13326 (+839, DEFAULT cap 24576). The planner-side rule lives in plan-phase.md (ack appended to the 3409 fragment — two ack sources may never name the same path). gsd-planner.md is UNCHANGED (frozen under 49152 LF chars by four suites).",
"version": 1,
"paths": {
"gsd-pattern-mapper.md": "agents/gsd-pattern-mapper.md +839B: Tracked-source gate on analog selection; PATTERNS.md never emits mirror paths (#3645)"
}
}

View File

@@ -1,7 +0,0 @@
{
"$comment": "Growth ack (#2914 fragment). Reason: #3651 corrects the two broken prescriptions in settings-integrations.md — the review.models section states the registry-derived settable rule (per-lane modelConfigKey, nine lanes enumerated once as full review.models.* keys, keyless lanes cursor/qwen/coderabbit named) instead of a nonexistent dynamic-key pattern claim, and the agent_skills section prescribes the JSON array write form plus the split-on-commas instruction (quote-bearing entries rejected). settings-integrations.md 16257 -> 18309 LF bytes (+2052, DEFAULT cap 40960). Pinned by the #3651 rows in tests/settings-integrations.test.cjs (workflow text must match the registry's settable set).",
"version": 1,
"paths": {
"settings-integrations.md": "gsd-core/workflows/settings-integrations.md +2052B: registry-derived review.models settable rule + agent_skills JSON-array prescription (#3651)"
}
}

View File

@@ -0,0 +1,64 @@
# tests/emitted-drift-acks/
Per-PR acknowledgment fragments for the differential attribution check
(`tests/emitted-attribution.test.cjs`, ADR-2719 / #2789 / #2914).
**This directory being empty is the healthy steady state.** A fragment appearing
in a diff *is* the alarm; a fragment sitting here on `next` is spent cruft.
`README.md` is not a fragment — every reader filters on `.json` — and exists so
the directory (which `CONTEXT.md` and `CONTRIBUTING.md` both reference by path)
survives the sweep that empties it.
## The lifecycle, in three steps
1. **The gate names its own remedy.** When an emitted-artifact hash moves, or a
`gsd-core/workflows/*.md` / `agents/gsd-*.md` file grows, and your diff cannot
explain it, the failure output tells you which key to use and prints a minimal
valid document to paste. Create a NEW fragment named for your issue or PR
(`<NNNN>-<slug>.json`) — never reuse someone else's, and never revive the
legacy single `tests/emitted-drift-ack.json`.
2. **Note the two key spaces.** The message says which one applies. An
unattributable **hash** ripple is keyed on the emitted path
(`skills/gsd-add-tests/SKILL.md`); **growth** is keyed on the bare filename as
it appears under `gsd-core/workflows/` or `agents/` (`explore.md`).
3. **Delete the fragment once it has merged (#3078).** Every entry is scoped to
the diff that introduced it, so the moment it lands on `next` its prose is
already at the base — it is spent and can no longer clear anything, while
still owning its path keys. The `guard-no-ack-on-next` job reds `next` and
prints the exact `git rm`. Run it.
## Why the sweep exists
Fragments end the *file* conflict the single shared document caused. They do not
end the *key* conflict: two ack sources may never name the same path, and that is
a hard, loudly-reported error. So a fully-spent fragment left here walls off every
path it owns — the next PR to grow one of them can declare it neither in the
owning fragment (spent, gates nothing) nor in its own (duplicate). #2914 assumed a
persisting fragment was harmless; #3078 measured the cost at 45 fragments owning
403 paths and made the guard sweep them.
A **partially** spent fragment is deliberately left alone. That asymmetry is what
keeps the re-arm route working: appending prose to a live entry re-arms it, and
re-arming deliberately costs an actual new sentence — the comparison strips
zero-width characters and collapses whitespace precisely so a zero-information
edit cannot fake one.
## Never pin a fragment in a test
A fragment is deleted the moment it has merged (see step 3 above), so any test
that asserts one exists, or asserts its contents, will fail the instant
`guard-no-ack-on-next` sweeps it — and that failure has nothing to do with the
behavior the fragment once explained. This has already cost two suites:
`tests/emitted-attribution.test.cjs`'s three `#2914` migration pins, and
`tests/agent-tracked-source-rule.test.cjs`'s `#3645` growth-ack pin. What a test
may legitimately assert is the BEHAVIOR the ack explains, or the guard's own
verdict (`assertNoAllSpentFragments`, `assertAbsentOnNext`) — never the
paperwork.
## Do not regenerate anything
There is no baseline file to re-run a generator over; #2724 deleted it. If you
find yourself hunting for one, that is the predictable wrong guess.
See `CONTRIBUTING.md` → "Editing shipped content", `docs/TESTING-SUITES.md`, and
`CONTEXT.md`'s `RULESET.EMITTED_ATTRIBUTION` for the full model.

View File

@@ -96,6 +96,26 @@ const INVISIBLE = new RegExp(
'g', 'g',
); );
/**
* The single definition of "the prose a reviewer actually reads" for an ack reason.
*
* It is exported for exactly one reason: `scripts/lint-emitted-drift-ack.cjs` carries
* its own duplicate (`ackProse`) rather than requiring this file, because `scripts/`
* ships in the published npm package and `tests/` does not — a `require('../tests/...')`
* from a shipped script would work in this repo and break for every installed user
* (#3078). That duplication is unavoidable, but leaving both copies unexported meant
* nothing could ever compare them: the "parity test" this module's comments promised
* was checking the script against itself. Exporting this lets a real test hold the
* script's copy to this one.
*
* The invisible-stripping (`INVISIBLE`) and whitespace-collapse below are anti-gaming
* defences, not incidental normalization — see the comments at the two call sites in
* `diffEmitted`. Do not weaken either side of the duplicate without weakening both.
*/
function normalizeAckReason(reason) {
return reason.replace(INVISIBLE, '').replace(/\s+/g, ' ').trim();
}
/** /**
* A brand-new workflow/agent file — absent from the baseline, present now — must * A brand-new workflow/agent file — absent from the baseline, present now — must
* still stay under the Codex `project_doc_max_bytes` anchor (ADR-1610 Decision * still stay under the Codex `project_doc_max_bytes` anchor (ADR-1610 Decision
@@ -490,7 +510,7 @@ function diffEmitted({
// while being literally INVISIBLE in the diff — the purest form of "no new // while being literally INVISIBLE in the diff — the purest form of "no new
// explanation". Built from codepoints rather than literal characters, because a // explanation". Built from codepoints rather than literal characters, because a
// literal class would itself be unreviewable in this file. // literal class would itself be unreviewable in this file.
const prose = (reason) => reason.replace(INVISIBLE, '').replace(/\s+/g, ' ').trim(); const prose = normalizeAckReason;
const isSpent = (rel, entry) => { const isSpent = (rel, entry) => {
const prior = baseAcks.get(rel); const prior = baseAcks.get(rel);
return prior !== undefined && prose(prior.reason) === prose(entry.reason); return prior !== undefined && prose(prior.reason) === prose(entry.reason);
@@ -841,6 +861,8 @@ module.exports = {
NEW_FILE_CAP, NEW_FILE_CAP,
MAX_ACK_FRAGMENTS, MAX_ACK_FRAGMENTS,
REMEDIATION, REMEDIATION,
INVISIBLE,
normalizeAckReason,
sourceSatisfiedBy, sourceSatisfiedBy,
parseAck, parseAck,
mergeAckSources, mergeAckSources,