From 94bc492f578381dc2b1d7e9b002eb3961d4d63bf Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Thu, 20 Aug 2026 23:24:42 -0400 Subject: [PATCH] fix(#3645): tracked-source rule for planner/pattern-mapper path resolution (#3728) * test(#3645): failing-first agent tracked-source contract rows * fix(#3645): tracked-source rule for planner and pattern-mapper paths * Revert "fix(#3645): tracked-source rule for planner and pattern-mapper paths" This reverts commit 61f05e947bbdaf3b4897240c3819d349215744fb. * fix(#3645): tracked-source rule at the spawn seam and mapper gate * fix(#3645): review fixes - bounded block, ack merge assertion, git wording * fix(#3645): fit the tracked-source block under the 1168 ceiling * chore(#3645): backfill changeset pr number --------- Co-authored-by: sim --- .changeset/calm-deer-hum.md | 5 + agents/gsd-pattern-mapper.md | 11 +++ gsd-core/workflows/plan-phase.md | 14 +++ tests/agent-tracked-source-rule.test.cjs | 98 +++++++++++++++++++ .../3409-unreachable-guard-arms.json | 14 +-- .../3645-agents-tracked-source-rule.json | 7 ++ 6 files changed, 142 insertions(+), 7 deletions(-) create mode 100644 .changeset/calm-deer-hum.md create mode 100644 tests/agent-tracked-source-rule.test.cjs create mode 100644 tests/emitted-drift-acks/3645-agents-tracked-source-rule.json diff --git a/.changeset/calm-deer-hum.md b/.changeset/calm-deer-hum.md new file mode 100644 index 000000000..c445ae5cb --- /dev/null +++ b/.changeset/calm-deer-hum.md @@ -0,0 +1,5 @@ +--- +type: Fixed +pr: 3728 +--- +**`/gsd:plan-phase` no longer writes gitignored install-mirror paths into plans** — `files_modified` and artifact paths are now verified against `git ls-files` and resolved to tracked source (e.g. a plugin's own tree) instead of a runtime mirror under `.gsd/capabilities/`, whose edits died on every capability sync; paths inherited from PATTERNS.md are re-verified so one mirror path can no longer self-propagate across phases. (#3645) diff --git a/agents/gsd-pattern-mapper.md b/agents/gsd-pattern-mapper.md index c07288049..0f0d3764d 100644 --- a/agents/gsd-pattern-mapper.md +++ b/agents/gsd-pattern-mapper.md @@ -116,6 +116,17 @@ Grep("router\.(get|post|put|delete)", type: "ts") 3. Different role, same data flow — partial match 4. Most recently modified — prefer current patterns over legacy +**Tracked-source gate (#3645):** every analog path you name must be git-TRACKED +source, never a gitignored install/runtime mirror (e.g. `/.gsd/capabilities/ +/...` synced from a plugin's tracked tree). Before naming an analog whose +file exists on disk, verify `git ls-files -- ` prints it (non-empty = +tracked); if the closest analog is a gitignored mirror, substitute its tracked +origin (e.g. `plugins/*/.gsd/capabilities//...`, or root +`capabilities//...`). PATTERNS.md must never emit mirror paths — the +planner builds later phases on your output, so one mirror path self-propagates +across phases and the executor's edits die on the next capability sync. For +files inside a nested submodule, run the check from within the submodule. + ## Step 4: Extract Patterns from Analogs **Never re-read the same range.** For small files (≤ 2,000 lines), one `Read` call is enough — extract everything in that pass. For large files, multiple non-overlapping targeted reads are fine; what is forbidden is re-reading a range already in context. diff --git a/gsd-core/workflows/plan-phase.md b/gsd-core/workflows/plan-phase.md index 8f29695e4..63035b208 100644 --- a/gsd-core/workflows/plan-phase.md +++ b/gsd-core/workflows/plan-phase.md @@ -759,6 +759,20 @@ Historical findings already incorporated, explicitly deferred/rejected in PLAN.m **Phase requirement IDs (every ID MUST appear in a plan's `requirements` field):** {phase_req_ids} + +**Tracked-source paths (#3645):** Every path you write into PLAN.md — +`files_modified`, `must_haves.artifacts`, action paths, and paths inherited +from `{PATTERNS_PATH}` or prior-phase plans — must name git-tracked source, +never a gitignored install/runtime mirror (e.g. `/.gsd/capabilities//...` +synced from a plugin's tracked tree; executor edits to a mirror die on the +next capability sync). Verify existing-file paths with `git ls-files -- ` +(non-empty = tracked); resolve a gitignored hit to its tracked origin +(`plugins/*/.gsd/capabilities//...`, root `capabilities//...`). A +not-yet-existing path is a new file — keep the intended path. Re-verify +inherited paths: fix a mirror path, never inherit. Submodule files: check +from within the submodule. + + **Project instructions:** Read ./CLAUDE.md or ./.claude/CLAUDE.md if either exists — follow project-specific guidelines **Project skills:** Check .claude/skills/ or .agents/skills/ directory (if either exists) — read SKILL.md files, plans should account for project skill rules diff --git a/tests/agent-tracked-source-rule.test.cjs b/tests/agent-tracked-source-rule.test.cjs new file mode 100644 index 000000000..7a92dd8ee --- /dev/null +++ b/tests/agent-tracked-source-rule.test.cjs @@ -0,0 +1,98 @@ +/** + * #3645: gsd-planner and gsd-pattern-mapper must write only git-TRACKED + * source paths into PLAN.md / PATTERNS.md — never a gitignored install/ + * runtime mirror (e.g. /.gsd/capabilities//... synced from a + * plugin's tracked tree). Executors that trust a mirror path edit a copy + * whose changes die on the next sync; the wrong path also self-propagates + * across phases because pattern-mapper builds on prior phases' docs. + * + * Enforcement points: gsd-pattern-mapper.md carries the gate inline (its + * size tier has headroom); the PLANNER agent file is frozen under a + * 49152-LF-char cap asserted by four other suites, so the planner-side rule + * is projected onto its spawn contract in gsd-core/workflows/plan-phase.md + * (the #3297 precedent for requirements the spawned agent must honor). + * + * Shipped-content contract rows: the agent/workflow text IS the product the + * runtime loads, so asserting its contract lines tests the deployed behavior. + */ + +'use strict'; + +const { test, describe } = require('node:test'); +const assert = require('node:assert/strict'); +const fs = require('fs'); +const path = require('path'); + +const AGENTS_DIR = path.join(__dirname, '..', 'agents'); +const WORKFLOWS_DIR = path.join(__dirname, '..', 'gsd-core', 'workflows'); + +describe('#3645 — agents write only git-tracked source paths', () => { + // allow-test-rule: source-text-is-the-product (#3645) + // The workflow prompt block below IS the runtime instruction shipped to + // every plan-phase run; testing its content tests the deployed contract. + const planPhase = fs.readFileSync(path.join(WORKFLOWS_DIR, 'plan-phase.md'), 'utf8'); + + // allow-test-rule: source-text-is-the-product (#3645) + // The agent gate text IS the runtime instruction; testing it tests the + // deployed contract — if the tracked-source gate is absent, the agent + // does not enforce it. + const mapper = fs.readFileSync(path.join(AGENTS_DIR, 'gsd-pattern-mapper.md'), 'utf8'); + + test('planner spawn contract carries the #3645 tracked-source rule', () => { + const block = planPhase.split('')[1]?.split('')[0]; + assert.ok(block, 'plan-phase.md must carry the block in the planner spawn prompt (#3645)'); + assert.ok(/files_modified/.test(block) && /must_haves/.test(block), + 'the block must govern files_modified and must_haves paths'); + assert.ok(block.includes('git ls-files'), + 'the block must instruct git ls-files verification (#3645)'); + assert.ok(/GSD_SOURCE_MIRROR_SENTINEL|\.gsd\/capabilities/.test(block), + 'the block must name the gitignored install-mirror shape it rejects'); + assert.ok(/plugins\//.test(block), + 'the block must point at tracked plugin-source fallback locations'); + }); + + test('planner spawn contract re-verifies inherited PATTERNS.md paths (#3645)', () => { + const block = planPhase.split('')[1]?.split('')[0]; + assert.ok(/PATTERNS_PATH/.test(block) && /inherit/.test(block), + 'the block must cover paths inherited from {PATTERNS_PATH} and prior phases'); + }); + + test('gsd-pattern-mapper emits only tracked analog paths (#3645)', () => { + assert.ok(mapper.includes('git ls-files'), + 'gsd-pattern-mapper.md must verify analog paths via git ls-files (#3645)'); + assert.ok(mapper.includes('gitignored install/runtime mirror'), + 'the mapper must name the gitignored-mirror rejection explicitly (#3645)'); + assert.ok(mapper.includes('never emit mirror paths'), + 'PATTERNS.md output must be required to never carry mirror paths (#3645)'); + assert.ok(/plugins\//.test(mapper) && /capabilities\//.test(mapper), + 'the mapper must name tracked-origin fallback locations (#3645)'); + }); + + test('the frozen planner agent file is untouched by #3645', () => { + // The planner is pinned under a 49152-LF-char cap by four suites; the + // rule lives in its spawn contract instead. Guard the freeze: #3645 + // must not have grown the agent file past its baseline. + const src = fs.readFileSync(path.join(AGENTS_DIR, 'gsd-planner.md'), 'utf8'); + const lf = src.replace(/\r\n/g, '\n').length; + assert.ok(lf < 49152, `gsd-planner.md is ${lf} LF chars — must stay < 49152 (#3645 keeps the planner frozen; enforcement lives in plan-phase.md)`); + assert.ok(!src.includes('Tracked-source'), + 'the rule belongs in the spawn contract, not the frozen agent file (#3645)'); + }); + + test('growth ack recorded for the grown shipped files (#3645)', () => { + const acksDir = path.join(__dirname, 'emitted-drift-acks'); + const readAck = (name) => fs.readFileSync(path.join(acksDir, name), 'utf8'); + const mine = fs.readdirSync(acksDir).find((f) => f.includes('3645')); + assert.ok(mine, 'an emitted-drift-acks fragment for #3645 must exist'); + const mineSrc = readAck(mine); + assert.ok(mineSrc.includes('"gsd-pattern-mapper.md"'), + 'the #3645 fragment must acknowledge the grown mapper by its ack key'); + assert.ok(!mineSrc.includes('"gsd-planner.md"'), + 'gsd-planner.md is unchanged — no ack entry for it'); + // plan-phase.md's growth ack lives in the 3409 fragment (two ack sources + // 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)'); + }); +}); diff --git a/tests/emitted-drift-acks/3409-unreachable-guard-arms.json b/tests/emitted-drift-acks/3409-unreachable-guard-arms.json index 8ac4fb2f0..f8493dc96 100644 --- a/tests/emitted-drift-acks/3409-unreachable-guard-arms.json +++ b/tests/emitted-drift-acks/3409-unreachable-guard-arms.json @@ -1,12 +1,12 @@ { "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 \u2014 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). \u2014 #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 \u2014 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 \u2014 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). \u2014 #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 \u2014 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` \u2014 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). \u2014 #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\")` \u2014 `||` 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\" \u2014 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. \u2014 #3576 append: bare `references/.md` cites repaired to the canonical `gsd-core/references/.md` form (+36 bytes, 4 cite(s) \u00d7 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). \u2014 #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 block, plus a 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. \u2014 #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\u2019s absence (that skip silently dropped every other registered hook at the point).", - "session-report.md": "#3409: guarded `ls -la .planning/reports/SESSION_REPORT*.md 2>/dev/null || echo \"No previous reports\"` \u2014 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` \u2014 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)." + "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/.md` cites repaired to the canonical `gsd-core/references/.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 block, plus a 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 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)." } } diff --git a/tests/emitted-drift-acks/3645-agents-tracked-source-rule.json b/tests/emitted-drift-acks/3645-agents-tracked-source-rule.json new file mode 100644 index 000000000..e7f614a4a --- /dev/null +++ b/tests/emitted-drift-acks/3645-agents-tracked-source-rule.json @@ -0,0 +1,7 @@ +{ + "$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)" + } +}