Skip to content

fix(lint): hold the partof gate's cited contract to the file it cites - #16774

Merged
hotlong merged 1 commit into
mainfrom
claude/issue-16747-relation-contract-citation
Sep 8, 2026
Merged

fix(lint): hold the partof gate's cited contract to the file it cites#16774
hotlong merged 1 commit into
mainfrom
claude/issue-16747-relation-contract-citation

Conversation

@hotlong

@hotlong hotlong commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Fixes #16747

What was wrong

RELATION_CONTRACT in scripts/check-partof-closing-keyword.mjs presents itself as a verbatim quotation of this repo's agent rules — corner brackets, a docblock saying "Quoted verbatim, in its own language", and a self-test case named the finding CITES the contract rather than restating it in its own words. It carried two sentences. The rules file carries one.

Measured on cf33adbd8, the base of this branch:

$ git grep -n "连成一条落地"
scripts/check-partof-closing-keyword.mjs:469

$ git grep -n "卡片关系只在正文声明一次"
.claude/agents/os-dev.md:281
scripts/check-partof-closing-keyword.mjs:468

The second sentence exists in the gate and nowhere else in the tree. It is true on the facts — the squash really is assembled from the commit messages — so the defect is the attribution, not the content. AGENTS.md's Communication section binds it directly: 「引用中文裁决时保持原文、不翻译——改写引文就是改写裁决」. A citation that ADDS a sentence is that rule's failure in the other direction: it attributes to the ruling a claim the ruling does not make, and every red RULE 2 run printed that addition to an agent as the ruling's own words.

What changed

  1. RELATION_CONTRACT is trimmed to the sentence .claude/agents/os-dev.md line 281 actually carries.
  2. The squash fact stays as the gate's own prose, where it already was twice — the RULE 2 header section ("The squash message is assembled from the COMMITS") and the printed finding itself ("every commit message on this PR is concatenated into the one message that lands on the default branch"). It is not restated a third time.
  3. The docblock is rewritten so it is true of the trimmed constant, and records why nothing may be authored inside the brackets.
  4. Three self-test cases hold the quotation to its source; the RULE 2 battery floor moves 19 to 22 with them.

Why the existing self-test did not catch it

named.includes(RELATION_CONTRACT) compares the printed finding with this file's own constant. Both sides move together whenever the constant is edited, so a sentence that was never in the rules file passes it — which is exactly how the added sentence survived a case whose name claims to check the citation.

The new cases read .claude/agents/os-dev.md through AGENT_RULES_SOURCE and require every sentence between the corner brackets to appear in it verbatim. Two silent-green shapes are refused explicitly rather than left implicit: an unreadable rules file, and an extraction that finds zero sentences (which would make the whole pin vacuous).

AGENT_RULES_SOURCE is a quoted path literal on purpose, unlike the paths the header names unquoted: the derivation turns a quoted path in this file into a watch hint, and this hint tells the truth — editing that sentence at the other end breaks the pin here, so a card touching that file really does want this gate run.

Ablation — proof the new pin can fail

Fix committed first (cd09dbd68), then the second sentence put back inside the brackets, on disk, with a trap-restore:

HEAD blob                  76e6ea816da7a0bc42bb3c4c5a1f52e30f0f2427
before mutation            injected-text 0, bracket-close 1
after  mutation            injected-text 1, bracket-close 0
mutated blob               3708551ff32d6d9446195e05faf9498461760023   (moved)
ablation self-test exit     1
  ✗ every sentence inside the corner brackets is verbatim in the cited rules file
    (got ["squash 会把全部 commit message 连成一条落地,逐条诚实拼成的一条自相矛盾。"], want [])
restored blob              76e6ea816da7a0bc42bb3c4c5a1f52e30f0f2427   (== HEAD)
git diff HEAD              empty
post-restore injected-text 0
restored self-test exit     0    ✓ check-partof-closing-keyword self-test: 92 cases pass.

The failing case names the offending sentence rather than printing a bare false, so the next author is told which sentence is not in the source.

Gates

Derived in the worktree with no paths passed — node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack — 31 families, identical to the set the dispatch carried. All 31 run, every one exit 0, reconciled against the record:

Run reconciliation — 31 derived, 31 run, 0 NOT-MEASURED, 0 UNRUN.
✓ dispatch-gates --ran: 31 derived famil(ies) accounted for — 31 run, 0 NOT-MEASURED.

The two judgment lines that matter here, quoted from the gates themselves:

✓ check-partof-closing-keyword self-test: 92 cases pass.
✓ dispatch-gates self-test: 1552 cases pass.

pnpm check:pm-dispatch-gates was run detached with its output redirected and waited on in the foreground (it runs past the shell's timeout); check-dispatch-gates.mjs exits with the self-test's status, and that verdict line is printed only on the zero-failure path.

Governance: node scripts/pm/check-governed-merges.mjs --test scripts/check-partof-closing-keyword.mjs — exit 0, ✅ NOT governed, derived from the register rather than recalled.

skip-changeset

Nothing published moves. Repo-root scripts/ belongs to the private root package (private: true, no files[]), and a files[] entry is package-relative, so no workspace package can ship this path.

Merge reading

Base cf33adbd8. origin/main is 8b37a0973 at the time of writing — one commit ahead, touching AGENTS.md only, no overlap with this diff and none with the derived family. A driver-free merge-tree from a throwaway bare clone sharing the object store: exit 0, no conflicted paths.

验收备注


Generated by Claude Code

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.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P58euzUXCVJNwmhuPC9DXY
@github-actions github-actions Bot added the size/s label Sep 8, 2026
@hotlong hotlong added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 8, 2026 — with Claude
@hotlong
hotlong marked this pull request as ready for review September 8, 2026 05:12
@hotlong
hotlong enabled auto-merge September 8, 2026 05:12
@hotlong
hotlong added this pull request to the merge queue Sep 8, 2026
Merged via the queue into main with commit b38821d Sep 8, 2026
36 checks passed
@hotlong
hotlong deleted the claude/issue-16747-relation-contract-citation branch September 8, 2026 05:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/s skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants