Found while re-pricing #14290 (STAGE-THEN-RUN edge) on 1f2a02b. Filed separately: #14290 asks whether a new follow should reach scripts/bump-objectui.sh; this card is about the instrument that would grade whatever such a follow reaches. Fixing this is a precondition for #14290, not part of it.
The defect
extractWatchHints masks its source with maskedModuleBody, which understands // and block comments. It does not understand #. But PROGRAM_TEXT_TARGET in the same file already admits .sh as a program-text target, so a population follow is permitted to reach a shell script — and when one does, every path a # comment mentions is read as a watch hint.
Minimal fixtures, all against scripts/fixture.sh:
hints source
1 # see `scripts/bump-objectui.selftest.sh` for the self-test (comment, backtick prose)
1 # see "scripts/bump-objectui.selftest.sh" for the self-test (comment, quoted prose)
1 bash 'scripts/bump-objectui.selftest.sh' (real invocation)
0 // see `scripts/bump-objectui.selftest.sh` (JS comment — masked)
The first three are indistinguishable. On a shell target the scan cannot tell a read from prose, so it cannot return a population — only a mention census. Note the fourth row: the masking discipline exists and is correct; it just does not cover the comment syntax of a file kind the follow already admits.
Two spellings feed it. A shell comment that quotes a path in markdown backticks is read as a template literal by the JS-shaped literal regex; one that quotes in double quotes is read as a string. Both land in the hint set.
Measured blast radius, tree-wide
25 tracked .sh files; 14 spell at least one hint.
total hints as spelled 35
same, after blanking `#` comment lines 7
.sh files whose hint population is 100% prose 7/14
scripts/bump-objectui.sh is the sharpest: 7 hints as-is, 0 after blanking # lines. Every one of its seven — .changeset/*.md, scripts/bump-objectui.selftest.sh, .github/workflows/cut-rc.yml, docs/releases-maintenance.md, .changeset/console-82a94170c405.md, .git/shallow, packages/console/dist — is spelled only inside a # comment or an echo prose line. One of them, .changeset/*.md, also has a genuine code site (line 596, CS_FILE=...), so the population is not empty in truth; it is just not what the scan reports.
Why it is latent today, and why that is the danger
Measured on this tree: 0 family-to-.sh population edges exist. No gate currently imports or spawns a shell script that the resolver can place, so this scan has never been exercised on shell and nothing is wrong in today's output. The first follow that reaches a .sh inherits prose silently, on its first run.
That is the direction this tool errs against everywhere. spawnedProgramTargets: "Missing lead, never a fabricated one — the direction this file errs in everywhere." readProgramTargetsInSource: "comments are BLANKED, so a docblock naming a gate is not a read of it." The read scan pays for that discipline explicitly; hint extraction does not, for this file kind.
The declaration escape hatch does not cover it either. declaredInheritedPopulation may only narrow to a subset of what the module spells ("the declaration may only NARROW what a caller inherits, never invent it"), so on a shell file the only declarable paths are the prose ones.
Remedy shape (not prescribing)
Mask # comments when the scanned source is shell-kind, so the three fixtures above read 0 / 0 / 1. The file-kind test already exists next door in PROGRAM_TEXT_TARGET. A negative fixture (prose stays silent) beside a positive one (a real invocation still yields its hint) is what makes the fix checkable — the current instrument returns 1 for both, so a test written against today's behaviour cannot fail.
Re-check
node -e "import('./scripts/pm/dispatch-gates.mjs').then(m=>{const t=m.watchHintTree();const p=m.extractWatchHints('# see \`docs/releases-maintenance.md\`\n','scripts/x.sh',{tree:t}).length;const c=m.extractWatchHints('cat \"docs/releases-maintenance.md\"\n','scripts/x.sh',{tree:t}).length;console.log(p===0&&c===1?'CALIBRATED':'NOT CALIBRATED')})"
Prints NOT CALIBRATED today.
No assignee, per the finding convention. Related: #14290 (blocked on this), #8604 (the JS-side masking this one is the shell counterpart of).
Generated by Claude Code
Found while re-pricing #14290 (STAGE-THEN-RUN edge) on
1f2a02b. Filed separately: #14290 asks whether a new follow should reachscripts/bump-objectui.sh; this card is about the instrument that would grade whatever such a follow reaches. Fixing this is a precondition for #14290, not part of it.The defect
extractWatchHintsmasks its source withmaskedModuleBody, which understands//and block comments. It does not understand#. ButPROGRAM_TEXT_TARGETin the same file already admits.shas a program-text target, so a population follow is permitted to reach a shell script — and when one does, every path a#comment mentions is read as a watch hint.Minimal fixtures, all against
scripts/fixture.sh:The first three are indistinguishable. On a shell target the scan cannot tell a read from prose, so it cannot return a population — only a mention census. Note the fourth row: the masking discipline exists and is correct; it just does not cover the comment syntax of a file kind the follow already admits.
Two spellings feed it. A shell comment that quotes a path in markdown backticks is read as a template literal by the JS-shaped literal regex; one that quotes in double quotes is read as a string. Both land in the hint set.
Measured blast radius, tree-wide
25 tracked
.shfiles; 14 spell at least one hint.scripts/bump-objectui.shis the sharpest: 7 hints as-is, 0 after blanking#lines. Every one of its seven —.changeset/*.md,scripts/bump-objectui.selftest.sh,.github/workflows/cut-rc.yml,docs/releases-maintenance.md,.changeset/console-82a94170c405.md,.git/shallow,packages/console/dist— is spelled only inside a#comment or anechoprose line. One of them,.changeset/*.md, also has a genuine code site (line 596,CS_FILE=...), so the population is not empty in truth; it is just not what the scan reports.Why it is latent today, and why that is the danger
Measured on this tree: 0 family-to-
.shpopulation edges exist. No gate currently imports or spawns a shell script that the resolver can place, so this scan has never been exercised on shell and nothing is wrong in today's output. The first follow that reaches a.shinherits prose silently, on its first run.That is the direction this tool errs against everywhere.
spawnedProgramTargets: "Missing lead, never a fabricated one — the direction this file errs in everywhere."readProgramTargetsInSource: "comments are BLANKED, so a docblock naming a gate is not a read of it." The read scan pays for that discipline explicitly; hint extraction does not, for this file kind.The declaration escape hatch does not cover it either.
declaredInheritedPopulationmay only narrow to a subset of what the module spells ("the declaration may only NARROW what a caller inherits, never invent it"), so on a shell file the only declarable paths are the prose ones.Remedy shape (not prescribing)
Mask
#comments when the scanned source is shell-kind, so the three fixtures above read 0 / 0 / 1. The file-kind test already exists next door inPROGRAM_TEXT_TARGET. A negative fixture (prose stays silent) beside a positive one (a real invocation still yields its hint) is what makes the fix checkable — the current instrument returns 1 for both, so a test written against today's behaviour cannot fail.Re-check
Prints
NOT CALIBRATEDtoday.No assignee, per the finding convention. Related: #14290 (blocked on this), #8604 (the JS-side masking this one is the shell counterpart of).
Generated by Claude Code