From 4ba6b55070c731cbb1ea1120a31b187097428a2d Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 29 Aug 2026 17:13:12 +0000 Subject: [PATCH] docs(pm): describe check-dispatch-gates' watch hints by shape, not by count MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The gate's header said its watch hints were "the one constant below: this gate is matched for a card that edits the tool, and for nothing else". It carries three module-body constants — TOOL, plus SURFACE_MODULE and FRAME_MODULE, declared with their own docblocks — so the sentence was a false negative claim about the derivation, in the file whose subject is derivation correctness. Four sites in this file said it. Two of them are the ones the finding names; the other two are the same claim in different spellings — the backticking measurement ("the same single hint the file ships with") and the TOOL docblock, which said "two watch hints" and had itself gone stale a second time when the third constant landed. All four are now count-free: they name the constants' shape and point at the declarations, where each constant's reason already lives. A re-count would re-rot at the fourth declaration, which is exactly how this survived the second and third. Measured on the parent commit: extractWatchHints over this file returns scripts/pm/dispatch-gates.mjs, scripts/i18n-bundle-surface.mjs and scripts/check-skill-frame-sync.mjs; the gate's step declares no job filter and lint.yml no trigger paths, so hints plus gate-file identity are the whole of what reaches this family. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01CPrUz21stTFhJRUirdc4yw --- scripts/pm/check-dispatch-gates.mjs | 40 +++++++++++++++++++++-------- 1 file changed, 30 insertions(+), 10 deletions(-) diff --git a/scripts/pm/check-dispatch-gates.mjs b/scripts/pm/check-dispatch-gates.mjs index b8262098a1..35b41c5b05 100644 --- a/scripts/pm/check-dispatch-gates.mjs +++ b/scripts/pm/check-dispatch-gates.mjs @@ -83,8 +83,20 @@ * * A separate gate file is also what the other two pm gates look like * (check-skill-line-ratchet.mjs, check-skill-id-lint.mjs). Its watch hints are - * the one constant below: this gate is matched for a card that edits the tool, - * and for nothing else — which is the blind spot #8162 is about. + * the module-body constants below — the tool it runs, plus every module whose + * edits move this gate's verdict without leaving a hint the extractor can find. + * Two routes reach this gate and there is no third: a card editing any of those + * constants, through the hint; a card editing this file, through identity. The + * step is invoked with no job filter and lint.yml declares no trigger paths, so + * there is no third provenance to inherit — which is the blind spot #8162 is + * about. + * + * ⛔ That sentence names the constants' SHAPE, not their number, for the reason + * the ⛔ note above gives. Written as a count it said "the one constant" across + * two later declarations (#9116) and stayed green the whole time: the self-test + * pins the SET with containment assertions, and nothing counts the prose. Each + * constant carries its own reason at its declaration below, which is where a + * fourth one would be read and where the enumeration therefore lives. * * ## Why the paths above are unquoted, and why that is no longer required * @@ -99,10 +111,12 @@ * * The extractor no longer works that way: extractWatchHints opens with * maskComments, whose own docblock names this file as the specimen it retires. - * Measured on this tree, with every repo path this header names rewritten into - * backticks: 1 hint under today's extractor — scripts/pm/dispatch-gates.mjs, - * the same single hint the file ships with — against 10 under the pre-masking - * one, reaching spec, objectql, plugins, drivers, .claude/agents and .changeset + * Re-measured on 74049254d4, the parent of the commit that rewrote this + * paragraph, with every repo path this header names rewritten into backticks: + * today's extractor returns exactly the hints the file ships with — the + * module-body constants below, and nothing out of the header — against the ten + * the pre-masking extractor returned when this section was first written, + * reaching spec, objectql, plugins, drivers, .claude/agents and .changeset * exactly as the incident describes. Comment masking alone accounts for the * difference: masking self-test bodies instead changes nothing here, because * this file has none. @@ -110,9 +124,12 @@ * So the unquoting is no longer load-bearing, and this section is history * rather than an instruction: quoting a path in a comment here is now free, and * the paths stay unquoted because rewriting them buys nothing. What is NOT free - * is a path literal in a module body — masking cannot reach one — so the one - * quoted path below is still the one input this gate genuinely has, and that is - * the rule to carry into a new gate's header rather than the unquoting. + * is a path literal in a module body — masking cannot reach one — so the quoted + * paths below are still exactly this gate's watch hints, each one deliberate + * rather than a by-product of how a sentence was typed. Only TOOL is a file this + * gate itself reads; the other two are declared couplings, named because an edit + * to them moves this gate's verdict with no hint to derive it from. That is the + * rule to carry into a new gate's header rather than the unquoting. * * Nothing else belongs in this file. Assertions go in the tool's own self-test, * beside the code they judge; this is the CI invocation and its reason. @@ -124,7 +141,10 @@ import process from 'node:process'; const ROOT = new URL('../..', import.meta.url).pathname; -/** The tool under test, repo-relative — and one of this gate's two watch hints. */ +/** + * The tool under test, repo-relative — the one path declared here that this gate + * actually reads, and one of its watch hints. + */ const TOOL = 'scripts/pm/dispatch-gates.mjs'; /**