Skip to content

Commit 2eed6dc

Browse files
claude[bot]claude
andauthored
fix(pm-scripts): ratchet row for true-green.md, shared repo resolver in ci-failure, measured worklist verdict (#12096)
Three bounded scripts/pm fixes, one per card. check-skill-line-ratchet.mjs: add the missing CEILINGS row for .claude/skills/pm-dispatch/references/true-green.md at its landed count of 34, read from the ratchet's own run, headroom 0, same convention as every sibling references row. The adding PR could not carry it — its dispatch declared a closed file surface this script sat outside. ci-failure.mjs: import resolveSweepRepo from check-half-states.mjs and drop the hardcoded 'objectstack-ai/objectstack' default, so a verbatim copy in a sibling repo reads its own board via GITHUB_REPOSITORY instead of reporting confidently about objectstack's CI. Mirrors the sweeper's malformed-value refusal at the live entry point; the two offline branches stay exempt. bare-root-worklist.mjs: rewrite the check-declaration-mirrors row's `why` to its measured terms — a recursive walk admitted by extension, 2 of 261 (0.77%), not the copied "same top-level-only shape, 115 of 226". Re-derived the sibling percentages from each gate's own exported walk in the same pass: ratchet-remedy-authority 115/226 -> 144/261, runner-env-posture packages 1757/5049 -> 1794/5185 (2658 -> 2746 skipped tests) and examples 150/240 -> 150/241; skills-token-ratchet re-measured unchanged at 11 of 50. Docblock now records which rows carry a re-derived reading and warns against refreshing a denominator alone. Closes #11310, closes #11296, closes #11277 Claude-Session: https://claude.ai/code/session_01RMTpSRF5CjMmQBFfPtPCwJ Co-authored-by: Claude <noreply@anthropic.com>
1 parent 2ba4329 commit 2eed6dc

3 files changed

Lines changed: 76 additions & 7 deletions

File tree

scripts/pm/bare-root-worklist.mjs

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,27 @@ const POPULATION_CONSTANT = /^(?:[A-Z0-9_]*_ROOTS?|[A-Z0-9_]*_DIRS?|POPULATION|[
113113
* Every percentage below was measured on the tree, not estimated: numerator is
114114
* the files the gate's own walk filter admits, denominator the tracked files
115115
* under the subtree a declaration would name.
116+
*
117+
* ⚠️ A row's numbers date from the pass that WROTE that row, and the tree grows
118+
* under all of them — so they are not comparable across rows, and a denominator
119+
* here that disagrees with today's `trackedFiles()` is a stale reading, not a
120+
* different population. The failure this warns against is the one that produced
121+
* the `check-declaration-mirrors` row: its `why` was copied from the row above
122+
* it and was wrong in BOTH terms (a recursive extension filter recorded as
123+
* top-level-only, 2 files recorded as 115), and `--self-test` cannot catch it —
124+
* it audits keys and verdicts, never what a `why` SAYS, which is correct, since
125+
* a prose assertion cannot be mechanised. Only re-measuring catches this class.
126+
* ⛔ So never carry a sibling's numbers into a new row, and ⛔ never refresh a
127+
* denominator alone: pairing today's denominator with an older numerator mints a
128+
* ratio nothing ever measured, which is this defect wearing fresher digits.
129+
*
130+
* Re-derived on 2026-08-25 from each gate's own exported walk, and current as of
131+
* that tree: the two `scripts` rows (`corpusFiles()`, `mirrorFiles()`) and the
132+
* three `check:runner-env-posture` rows (`collectFiles()`); the
133+
* `check:skills-token-ratchet` row re-measured unchanged at 11 of 50. Every
134+
* other row still carries the numbers from the pass that wrote it, because its
135+
* gate exports no walk to drive and reproducing the filter by hand would be the
136+
* estimate this docblock refuses.
116137
*/
117138
const TRIAGE = new Map([
118139
// ── Taken: a strictly narrower subtree ────────────────────────────────────
@@ -179,12 +200,21 @@ const TRIAGE = new Map([
179200
}],
180201
['check:ratchet-remedy-authority SCRIPTS_DIR scripts', {
181202
verdict: 'REFUSE-UNSPELLABLE',
182-
why: 'reads the TOP LEVEL of the root only, and only two extensions — 115 of 226 (51%). The '
183-
+ 'idiom has no non-recursive spelling: a subtree hint claims every nested directory too',
203+
why: 'reads the TOP LEVEL of the root only, and only two extensions (`.mjs` and `.mts`) — 144 '
204+
+ 'of 261 (55%), re-derived from the gate own corpusFiles() walk. The idiom has no '
205+
+ 'non-recursive spelling: a subtree hint claims every nested directory too',
184206
}],
185207
['scripts/check-declaration-mirrors.mjs SCRIPTS_DIR scripts', {
186208
verdict: 'REFUSE-UNSPELLABLE',
187-
why: 'same top-level-only shape, 115 of 226 (51%)',
209+
why: 'a RECURSIVE walk admitted by EXTENSION — every `scripts/**/*.d.mts`, 2 of 261 (0.77%), '
210+
+ 'read from the gate own mirrorFiles(). NOT the shape of the row above it, and measured '
211+
+ 'here rather than inherited from it: mirrorFiles() descends into every nested directory '
212+
+ 'and its own docblock says so. What cannot be spelled here is the EXTENSION filter, not a '
213+
+ 'non-recursive walk — `scripts/**` is spellable and TRUE of this walk, and refused anyway '
214+
+ 'because it would name this gate for 261 files to reach 2. Same class as the '
215+
+ 'check:driver-conformance CASE_SETS_DIR and check:skills-token-ratchet SKILLS_DIR rows '
216+
+ 'below, so lifting the row-above non-recursive limit would leave this one exactly as '
217+
+ 'refused',
188218
}],
189219
// ── Refused: the population is a filter the idiom cannot spell ────────────
190220
['check:driver-conformance CASE_SETS_DIR packages', {
@@ -275,16 +305,16 @@ const TRIAGE = new Map([
275305
}],
276306
['check:runner-env-posture SCANNED_ROOTS packages', {
277307
verdict: 'REFUSE-UNSPELLABLE',
278-
why: 'non-test source beneath a `src` SEGMENT — 1757 of 5049 (35%). The segment is what makes '
308+
why: 'non-test source beneath a `src` SEGMENT — 1794 of 5185 (35%). The segment is what makes '
279309
+ 'this unspellable rather than merely wide: `packages/**/src/**` is the true population and '
280310
+ 'collapseHint reduces it to `packages`, so the only spellable claim also names every '
281-
+ 'package manifest, changelog, fixture and the 2658 test files this gate deliberately skips. '
311+
+ 'package manifest, changelog, fixture and the 2746 test files this gate deliberately skips. '
282312
+ 'Its nearest neighbour check:authz-resolver is REFUSE-WIDE at a similar 39% because ITS '
283313
+ 'population really is every non-test source under the root; this one is not',
284314
}],
285315
['check:runner-env-posture SCANNED_ROOTS examples', {
286316
verdict: 'REFUSE-UNSPELLABLE',
287-
why: '150 of 240 (63%), the same `src`-segment filter, refused with its packages half rather '
317+
why: '150 of 241 (62%), the same `src`-segment filter, refused with its packages half rather '
288318
+ 'than split: declaring the smaller root would name the gate on example cards and stay '
289319
+ 'silent on the package cards where product source actually lives',
290320
}],

scripts/pm/check-skill-line-ratchet.mjs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,17 @@ export const CEILINGS = new Map([
224224
// MERGED-tracking line, so that ceiling stays at 82 — no re-wrap, no cut.
225225
['.claude/skills/pm-dispatch/references/release-aftercare.md', 58],
226226
['.claude/skills/pm-dispatch/references/seat-post-protocol.md', 105],
227+
// Per-repo「真绿」跑法索引 — the canonical test invocation, the gates a CI-log
228+
// grep cannot see, and the local preflight, one fact per line per repo. Added
229+
// by the protocol-text family PR, which could not carry its own ceiling: that
230+
// dispatch declared a closed file surface (SKILL.md + os-dev.md + the new page
231+
// + one pointer line) with stop-on-breach, and this script sat outside it. A
232+
// pm-dispatch references file is read per seat session like every entry above,
233+
// so its absence here was a coverage gap, not the header's deliberate omission
234+
// (that one is the published `skills/` catalog, and only it). Set at the landed
235+
// line count read from this ratchet's own run — headroom 0, same convention as
236+
// the entries above.
237+
['.claude/skills/pm-dispatch/references/true-green.md', 34],
227238
// Lane job descriptions (maintainer ruling 2026-08-19: per-lane PM job
228239
// descriptions move from seat-post prose into versioned skill references).
229240
// Set at landed line counts (headroom 0, same convention as above).

scripts/pm/ci-failure.mjs

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -291,13 +291,26 @@ import {
291291
describeProbe,
292292
needsRepoProbe,
293293
parseRemaining,
294+
resolveSweepRepo,
294295
} from './check-half-states.mjs';
295296
import { PROXY_FLAG, PROXY_REARM_GUARD, proxyRearmPlan } from './check-governed-merges.mjs';
296297
import { isEntrypoint } from '../invoked-as.mjs';
297298

298299
const HERE = dirname(fileURLToPath(import.meta.url));
299300
const ROOT = resolve(HERE, '..', '..');
300-
const OWNER_REPO = process.env.PM_SWEEP_REPO ?? 'objectstack-ai/objectstack';
301+
// Which board this file reads, resolved by the sweeper's own resolver rather
302+
// than by a hardcoded default of its own: `PM_SWEEP_REPO` -> `GITHUB_REPOSITORY`
303+
// (what Actions sets to the repo the workflow is INSTALLED IN) -> the literal
304+
// default, a seat's terminal. The default the line used to carry is the one
305+
// shape that reads as correct in every review — on a runner the override is
306+
// normally unset, so a verbatim copy of this file in a sibling repo would read
307+
// OBJECTSTACK's Actions API and report about THIS repo's CI while its caller
308+
// believed it was reading its own. A wrong-repo answer is indistinguishable from
309+
// a right one by its shape; only the run ids give it away, and nobody checks run
310+
// ids against a repo they did not doubt. Same resolution order, same reasoning
311+
// and same refusal below as `check-half-states.mjs` — see `resolveSweepRepo`.
312+
const SWEEP_REPO = resolveSweepRepo(process.env);
313+
const OWNER_REPO = SWEEP_REPO.repo;
301314
const API = 'https://api.github.com';
302315
const TOKEN = process.env.GITHUB_TOKEN ?? process.env.GH_TOKEN ?? '';
303316

@@ -2427,6 +2440,21 @@ if (!invokedDirectly) {
24272440
} else if (process.argv.includes('--help') || process.argv.includes('-h')) {
24282441
console.log(usageText(readFileSync(fileURLToPath(import.meta.url), 'utf8')));
24292442
} else {
2443+
// A malformed sweep target is bad usage, refused BEFORE any request — the
2444+
// transport probe's second stage is already a repo-scoped read of this very
2445+
// string. Silently falling back to the default would read a board nobody
2446+
// asked for and render a confident report about it. The two offline branches
2447+
// above are exempt by construction: they make no request and must stay
2448+
// runnable in any container, whatever the environment carries.
2449+
if (!SWEEP_REPO.valid) {
2450+
console.error(
2451+
`ci-failure: ${SWEEP_REPO.source}=${JSON.stringify(SWEEP_REPO.repo)} is not an ` +
2452+
'`owner/name` repository. Refusing to fall back to a different board — a report about ' +
2453+
'the wrong repo reads exactly like a report about this one.',
2454+
);
2455+
process.exit(EXIT_UNDETERMINED);
2456+
}
2457+
24302458
// Transport before credentials: behind the session proxy an unproxied fetch
24312459
// answers 401 on every endpoint, and the probe below would classify that as a
24322460
// dead credential. The flag only takes effect at process start.

0 commit comments

Comments
 (0)