Skip to content

Commit a73e3fe

Browse files
committed
tooling(pm): narrow the frame-sync COPIES table to the two surviving copies
check-skill-frame-sync watched four copies across three files; the batch-3 ruling (item 4, option B) leaves two, both PM-side. The dev-side entries are removed and every count that was spelled out is now derived from the table. - COPIES loses internal-dev and published-dev; the header, the COPIES docblock and the count-disagreement message stop spelling "four". - The same-file overlap guard stays. It is vacuous today (no two copies share a file) but re-declaring such a pair is one edit away, and it is the check that makes such a pair prove it compares two different sections. - Self-test: the three cases driven through the removed ids are retargeted at the surviving copies, and a new case pins the reduction itself -- the dev-side text reappearing in the now-undeclared agent definition is refused by the anti-dormancy scan. Its measured limit is written next to it: the scan's population is per FILE, so a second copy pasted back into a file that is already declared is not refused, and was not before this change either. - check-skill-frame-freshness imported COPIES but spelled one frame file's path in five fixtures; those now derive it from the table, which is what the file's own #8024 rule asks for. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1
1 parent cc7d32e commit a73e3fe

2 files changed

Lines changed: 117 additions & 67 deletions

File tree

‎scripts/check-skill-frame-freshness.mjs‎

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,12 @@
1212
// ruled explicitly that they stay two scripts, and the reason is worth stating
1313
// because "just add it to the other script" is the obvious wrong move:
1414
//
15-
// check:skill-frame-sync "are the FOUR COPIES IN THIS TREE isomorphic?"
15+
// check:skill-frame-sync "are THIS TREE's COPIES isomorphic to each other?"
1616
// → compares copy against copy, one tree.
1717
// check:skill-frame-freshness "is THIS TREE's frame current with origin/main?"
1818
// → compares this tree against a remote ref.
1919
//
20-
// They are independent: a tree that is 173 commits behind, whose four copies are
20+
// They are independent: a tree that is 173 commits behind, whose copies are
2121
// CONSISTENTLY the old two-axis frame, is **green** on the sync gate — correctly
2222
// so, the copies really are isomorphic — while being exactly the defect #5866
2323
// reports. The self-test pins that independence rather than asserting it: the
@@ -37,10 +37,15 @@
3737
// hypothetical, and the channel is still live — at the time this gate was
3838
// written the shared checkout was 59 commits behind on a different branch.
3939
//
40-
// The frame lives in three files / four copies; COPIES and AXIS_MAP are imported
41-
// from check-skill-frame-sync.mjs so that "the frame's structure" has exactly ONE
42-
// definition. Forking those anchors into this script would reproduce, in the
43-
// gates themselves, the hand-copied-text disease they exist to police.
40+
// COPIES and AXIS_MAP are imported from check-skill-frame-sync.mjs so that "the
41+
// frame's structure" has exactly ONE definition — how many copies there are, and
42+
// which files hold them, is that table's answer and never a second list here.
43+
// Forking those anchors into this script would reproduce, in the gates
44+
// themselves, the hand-copied-text disease they exist to police. (It was four
45+
// copies in three files until the 2026-09-03 batch-3 ruling, item 4 option B,
46+
// dropped the two dev-side copies; nothing in this file had to move for that
47+
// except the self-test fixtures below, which used to SPELL one of the dropped
48+
// files instead of deriving it.)
4449
//
4550
// STRUCTURE, NEVER BYTES
4651
// ----------------------
@@ -131,6 +136,21 @@ const DEFAULT_FETCH_TIMEOUT_MS = 20_000;
131136

132137
const FRAME_FILES = [...new Set(COPIES.map((c) => c.file))];
133138

139+
/**
140+
* The copy the self-test's file-level fixtures operate on — the one they delete,
141+
* withhold from a ref, or make unparseable. DERIVED, never spelled: these cases
142+
* named `.claude/agents/os-dev.md` until the 2026-09-03 reduction stopped it
143+
* being a frame file, and a spelled path would have gone on asserting about a
144+
* document the frame no longer lives in. Same rule as everything else in this
145+
* file (#8024): locate the fixture the way the gate locates the real thing.
146+
*
147+
* First entry, so the choice is deterministic and non-empty for any COPIES table
148+
* the sync gate can start up with (an empty one fails that gate first).
149+
*/
150+
const SAMPLE_COPY_ID = COPIES[0].id;
151+
const SAMPLE_FRAME_FILE = COPIES[0].file;
152+
const SAMPLE_FRAME_FILE_RX = new RegExp(SAMPLE_FRAME_FILE.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'));
153+
134154
// ---------------------------------------------------------------------------
135155
// git plumbing
136156
// ---------------------------------------------------------------------------
@@ -827,13 +847,13 @@ function selfTest() {
827847
label: 'stale tree + authoritative ref → ERROR naming the stale files (the #5866 shape)',
828848
run: () => evaluate({ root: dir, ref: current }),
829849
expect: 'error',
830-
wants: [/STRUCTURALLY BEHIND/, /\.claude\/agents\/os-dev\.md/, /3 axes: long-term-soundness/, /4 axes: business-need/, new RegExp(REMEDY.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))],
850+
wants: [/STRUCTURALLY BEHIND/, SAMPLE_FRAME_FILE_RX, /3 axes: long-term-soundness/, /4 axes: business-need/, new RegExp(REMEDY.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))],
831851
});
832852
cases.push({
833853
label: 'the SAME stale tree, fetch impossible → degrades to WARN, exit 0, same diagnosis',
834854
run: () => evaluate({ root: dir }),
835855
expect: 'warn',
836-
wants: [/STRUCTURALLY BEHIND/, /\.claude\/agents\/os-dev\.md/],
856+
wants: [/STRUCTURALLY BEHIND/, SAMPLE_FRAME_FILE_RX],
837857
alsoAssert: (v) => (v.clamped ? null : 'expected the verdict to be marked as clamped'),
838858
});
839859
// The independence proof: the sync gate is GREEN on this very fixture.
@@ -894,14 +914,14 @@ function selfTest() {
894914
// --- 6: a framework file main has and we do not --------------------------
895915
{
896916
const { dir, b: current } = linear('missing-here', real, real);
897-
git(['rm', '-q', '.claude/agents/os-dev.md'], { cwd: dir });
917+
git(['rm', '-q', SAMPLE_FRAME_FILE], { cwd: dir });
898918
const without = commitAll(dir, 'drop a framework file');
899919
setOriginMain(dir, current);
900920
cases.push({
901921
label: 'a framework file exists on the ref but not in the tree → ERROR',
902922
run: () => evaluate({ root: dir, ref: current }),
903923
expect: 'error',
904-
wants: [/MISSING from this working tree/, /os-dev\.md/],
924+
wants: [/MISSING from this working tree/, SAMPLE_FRAME_FILE_RX],
905925
alsoAssert: () => (without ? null : 'fixture did not commit'),
906926
});
907927
}
@@ -910,9 +930,9 @@ function selfTest() {
910930
{
911931
const dir = makeRepo('missing-there');
912932
temps.push(dir);
913-
const partial = new Map([...real].filter(([f]) => f !== '.claude/agents/os-dev.md'));
933+
const partial = new Map([...real].filter(([f]) => f !== SAMPLE_FRAME_FILE));
914934
writeFiles(dir, partial);
915-
const refSha = commitAll(dir, 'main without the dev-agent definition');
935+
const refSha = commitAll(dir, 'main without one framework file');
916936
writeFiles(dir, real);
917937
commitAll(dir, 'tree adds it');
918938
setOriginMain(dir, refSha);
@@ -926,7 +946,7 @@ function selfTest() {
926946

927947
// --- 8: the frame moved on main, our anchors predate it ------------------
928948
{
929-
const moved = withUnreadableCopy(real, 'internal-dev');
949+
const moved = withUnreadableCopy(real, SAMPLE_COPY_ID);
930950
const { dir, b: current } = linear('anchors', real, moved);
931951
git(['checkout', '-q', git(['rev-parse', 'HEAD~1'], { cwd: dir }).stdout.trim()], { cwd: dir });
932952
setOriginMain(dir, current);
@@ -940,7 +960,7 @@ function selfTest() {
940960

941961
// --- 9: our own tree does not parse --------------------------------------
942962
{
943-
const broken = withUnreadableCopy(real, 'internal-dev');
963+
const broken = withUnreadableCopy(real, SAMPLE_COPY_ID);
944964
const { dir, b: current } = linear('broken-here', broken, real);
945965
git(['checkout', '-q', git(['rev-parse', 'HEAD~1'], { cwd: dir }).stdout.trim()], { cwd: dir });
946966
setOriginMain(dir, current);

0 commit comments

Comments
 (0)