Skip to content

Commit b38821d

Browse files
hotlongclaude
andauthored
fix(lint): hold the partof gate's cited contract to the file it cites (#16774)
RELATION_CONTRACT presented itself as a verbatim quotation of the agent rules and carried a second sentence that file does not have. The sentence was true on the facts — the squash really is assembled from the commit messages — but it was written here, not quoted from there, and every red RULE 2 run printed it to an agent as the ruling's own words. Adding a sentence to a citation is the same failure as translating one. Trim the quotation to the sentence the rules file actually carries. The squash fact stays as the gate's own prose, where it already was twice: the RULE 2 header section and the printed finding both state it in the gate's own words, so it is not restated a third time. The self-test's existing case compares the printed finding with this file's own constant, so both sides moved together and the added sentence passed a check that already claimed to verify the citation. Three new cases read .claude/agents/os-dev.md and hold every sentence between the corner brackets to it verbatim, refusing a vacuous extraction and an unreadable source rather than passing quietly. Claude-Session: https://claude.ai/code/session_01P58euzUXCVJNwmhuPC9DXY Co-authored-by: Claude <noreply@anthropic.com>
1 parent 6d4d5d3 commit b38821d

1 file changed

Lines changed: 71 additions & 7 deletions

File tree

‎scripts/check-partof-closing-keyword.mjs‎

Lines changed: 71 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,7 @@ const SELF_TEST_BATTERIES = Object.freeze({
350350
'Context reading: presence, not truthiness.': 4,
351351
'The wiring itself. A gate whose workflow step is deleted or whose': 6,
352352
'The predicate source this gate reuses must still be there to reuse.': 1,
353-
'RULE 2 — every card-relation spelling in a commit message is a finding,': 19,
353+
'RULE 2 — every card-relation spelling in a commit message is a finding,': 22,
354354
'The regression fixture: the squash that assembled a contradiction no': 4,
355355
'RULE 2 delegates to the sweep extractors at the commit-message reading.': 3,
356356
'The commit list input. An absent, broken or empty list can never read': 8,
@@ -443,6 +443,17 @@ const PREDICATE_SOURCE = 'scripts/pm/check-half-states.mjs';
443443
/** The wiring that gives this gate a PR to judge. */
444444
const WIRING_WORKFLOW = '.github/workflows/partof-closing-keyword-guard.yml';
445445

446+
/**
447+
* The file RELATION_CONTRACT quotes — a REAL input, read by the self-test.
448+
*
449+
* Quoted as a path literal on purpose, unlike the paths in the header. The
450+
* derivation turns a quoted path in this file into a watch hint, and this one
451+
* is a hint that tells the truth: editing the sentence at the other end of it
452+
* breaks the citation pin below, so a card touching that file really does want
453+
* this gate run. The header's last section is the authority on the distinction.
454+
*/
455+
const AGENT_RULES_SOURCE = '.claude/agents/os-dev.md';
456+
446457
export const EXIT_CLEAN = 0;
447458
export const EXIT_CONTRADICTION = 1;
448459
export const EXIT_NOT_WIRED = 2;
@@ -458,18 +469,55 @@ export const COMMITS_FILE_ENV = 'PR_COMMITS_FILE';
458469
* copying it. Quoted verbatim, in its own language, because a translation of a
459470
* ruling is a rewrite of it.
460471
*
461-
* Named in prose rather than as a bare path literal on purpose: the
462-
* dispatch-gates derivation turns quoted path literals in this file into watch
463-
* hints, and a sentence with spaces in it cannot become one. The header's last
464-
* section is the authority on that.
472+
* ⛔ Nothing inside the corner brackets may be written HERE. An earlier revision
473+
* carried a second sentence in them — that the squash concatenates the commit
474+
* messages and can assemble a contradiction out of individually honest parts —
475+
* which was true on the facts and had never been in the rules file at all. That
476+
* is the same failure as a translation, in the other direction: it attributes to
477+
* the ruling a claim the ruling does not make, and it prints that attribution to
478+
* the very population that reads the rules file. The squash fact is this gate's
479+
* own, and this gate already states it in its own words twice — in the RULE 2
480+
* header section and in the printed finding below — so it is not restated here
481+
* a third time.
482+
*
483+
* The brackets are held to their source MECHANICALLY, not by care: the self-test
484+
* reads AGENT_RULES_SOURCE and requires every sentence between them to appear in
485+
* it verbatim. Comparing the printed finding with this constant cannot do that —
486+
* both sides move together when the constant is edited, which is precisely how
487+
* the added sentence survived a self-test that already claimed to check the
488+
* citation.
489+
*
490+
* The path is named in prose here rather than as a literal because it is spelled
491+
* once, as a literal, at AGENT_RULES_SOURCE; that declaration carries the
492+
* watch-hint note.
465493
*/
466494
const RELATION_CONTRACT =
467495
'The contract is written down in the agent rules at .claude/agents/os-dev.md — '
468-
+ '「PR 正文与 commit message 分开解析:卡片关系只在正文声明一次,commit ⛔ 不带卡片 trailer。'
469-
+ 'squash 会把全部 commit message 连成一条落地,逐条诚实拼成的一条自相矛盾。」 '
496+
+ '「PR 正文与 commit message 分开解析:卡片关系只在正文声明一次,commit ⛔ 不带卡片 trailer。」 '
470497
+ '(The PR body and the commit messages are parsed separately: the card relation is declared ONCE, '
471498
+ 'in the body, and a commit carries no card trailer.)';
472499

500+
/**
501+
* Every sentence inside the corner brackets of a citation, in order.
502+
*
503+
* Split rather than compared whole so the pin stays honest if the quotation ever
504+
* grows a second sentence legitimately: each is held to the source on its own,
505+
* and a sentence added here without being added there is named individually.
506+
*
507+
* Returns an empty array when there are no brackets at all, which the self-test
508+
* refuses explicitly — an extractor that silently found nothing would make the
509+
* citation pin vacuously green, the phantom-check shape this file exists to
510+
* refuse elsewhere.
511+
*/
512+
function citedSentences(text) {
513+
const quoted = /「([^」]*)」/.exec(String(text ?? ''));
514+
if (quoted === null) return [];
515+
return quoted[1]
516+
.split(/(?<=。)/)
517+
.map((sentence) => sentence.trim())
518+
.filter((sentence) => sentence !== '');
519+
}
520+
473521
/**
474522
* The commit rows the wiring gathered, as JSON Lines — one object per line,
475523
* `{ sha, message }`.
@@ -942,6 +990,22 @@ function selfTest() {
942990
named.includes(RELATION_CONTRACT),
943991
true,
944992
);
993+
// The case above compares the printed finding with THIS FILE'S constant, so
994+
// both sides move together whenever the constant is edited: a sentence that
995+
// was never in the rules file passes it, and one did — see the constant's
996+
// docblock. These three hold the quotation to its SOURCE instead. An absent
997+
// rules file reds here rather than passing quietly: a citation check that
998+
// cannot read the cited file has verified nothing.
999+
const agentRulesPath = join(ROOT, AGENT_RULES_SOURCE);
1000+
const agentRules = existsSync(agentRulesPath) ? readFileSync(agentRulesPath, 'utf8') : '';
1001+
const citedFromRules = citedSentences(RELATION_CONTRACT);
1002+
t(`the cited rules file is readable (${AGENT_RULES_SOURCE})`, agentRules !== '', true);
1003+
t('the citation carries quoted sentences at all (never a vacuous zero)', citedFromRules.length > 0, true);
1004+
t(
1005+
'every sentence inside the corner brackets is verbatim in the cited rules file',
1006+
citedFromRules.filter((sentence) => !agentRules.includes(sentence)),
1007+
[],
1008+
);
9451009
// The guidance text itself, pinned. This gate once told the author to push
9461010
// reworded commits while also forbidding a rewrite — unsatisfiable on a
9471011
// pushed branch, and a seat that read it instructed an amend and force-push.

0 commit comments

Comments
 (0)