Skip to content

fix(scripts): make check-cli-command-ids' literal-declaration self-test able to fail - #12759

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-12472-cli-command-ids-selftest-pin
Aug 27, 2026
Merged

fix(scripts): make check-cli-command-ids' literal-declaration self-test able to fail#12759
os-zhuang merged 1 commit into
mainfrom
claude/issue-12472-cli-command-ids-selftest-pin

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Fixes#12472

check-cli-command-ids --self-test carried a case pinning that ROOT_DIR_WATCH_HINTS is
spelled as a source literal rather than computed. The case could not fail: it searched
the whole file for a needle it spelled inline, so includes found that needle in the
assertion rather than in the declaration, and the case was satisfied by its own text.

Reproduced before fixing, on origin/main at 168941cea

Rewriting only the declaration into the computed form the case exists to reject:

declaration line, mutated on disk:
184: const ROOT_DIR_WATCH_HINTS = POPULATION_ROOTS.map((r) => `${r}/**`);
self-test verdict on that tree:
check-cli-command-ids self-test: 38 cases pass
SELFTEST_EXIT=0

Not one case reddened. That confirms the card's reading of the neighbouring case too: it
asserts the array's runtime value, which the computed form still satisfies, so it does
not close the gap.

The harm is real, not theoretical

Measured against the extractor itself (extractWatchHints in scripts/pm/dispatch-gates.mjs):

declaration spellingsubtree hints recovered
literal["scripts/**"]
computed[]

So the computed form really does leave the gate unnameable by every dispatch brief. The
self-test's own copies of the hint cannot stand in for the declaration, because
extractWatchHints runs maskSelfTests(maskComments(source)) and blanks the whole
selfTest body before scanning.

Why this does not use the assembled-needle shape from its sibling

The card suggests the remedy landed in scripts/check-objectql-double-limit.mjs — assemble
the needle so the searched byte sequence exists nowhere but the declaration. Measured
insufficient here, on disk.
That file spells its hint exactly twice (declaration,
assertion), so un-spelling the assertion leaves the declaration as the only copy. This file
spells it a third time, in the runtime-value case just below. Applying the
assembled-needle remedy alone on top of a computed declaration:

assembled needle present : 1
inline needle gone : 0
computed decl present : 1
remaining literal spellings anywhere in file:
545: ROOT_DIR_WATCH_HINTS.includes('scripts/**')
check-cli-command-ids self-test: 38 cases pass
SELFTEST_EXIT=0

Still cannot fail. So the fix scopes the search to the declaration statement and derives
the needle from the declared values — scope is the fix, the derived needle is the hygiene.
Being generic over ROOT_DIR_WATCH_HINTS rather than pinning one string, it also grows with
the declaration. A companion case asserts the declaration statement is located exactly once,
so the pin cannot silently judge nothing.

The declaration stays a literal, as the ruling requires; nothing in this change makes it
computed.

Ablation, both directions, on disk

Mutation and restore each proved on disk by anchor counts, and the restore leg additionally
by git hash-object against the HEAD blob plus an empty git diff HEAD.

legdeclaration on diskverdict
mutatecomputed1 of 39 case(s) failedthe declaration is spelled as a LITERAL in this source, not computed, SELFTEST_EXIT=1
restoreliteralcheck-cli-command-ids self-test: 39 cases pass, git diff HEAD empty

No build step is involved: this gate is run directly from source by node, so there is no
dist/ for a stale artifact to hide in.

Sibling survey

scripts/check-parse-guard.mjs is still cleared, and for a slightly stronger reason than
the card states: it carries no own-source readFileSync(fileURLToPath(import.meta.url)) pin
at all, so it has no assertion of this shape to be phantom. Swept all 13 gates carrying the
ROOT_DIR_WATCH_HINTS idiom — exactly two carry an own-source pin
(check-cli-command-ids.mjs, fixed here, and check-objectql-double-limit.mjs, whose hint
is spelled once). No third gate has grown the shape, so nothing is widened into this PR.

Gates

Re-derived for the actual diff with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack
(no path arguments — the script takes its own change set from the merge base); it named the
same families as the dispatch brief, adding none. Union run against c20665400, the final
commit, all green:

check:agent-test-spelling · check:bash32-floor · check:cli-command-ids
check:cross-package-test-inputs · check:entry-guard · check:parse-guard
check:pnpm-filter-targets · check:nul-bytes
node scripts/check-ci-filter-parity.mjs · node scripts/check-cross-package-test-inputs.mjs
node scripts/pm/bare-root-worklist.mjs --self-test · check:pm-dispatch-gates

Both convention-triggered ledger obligations were run and are satisfied without a new row —
this change adds no population root and moves no spelling:

bare-root-worklist --self-test:
OK self-test: 46 live row(s), 39 unreachable as spelled, 39 recorded verdict(s)
— none stale, none missing, none contradicted.
check:pm-dispatch-gates:
dispatch-gates self-test: 719 cases pass.

check-ci-filter-parity.mjs first reported PREREQUISITE NOT MET (it imports yaml, absent
from a fresh worktree); pnpm install was run and it then passed — recorded here because the
first reading measured nothing and must not be read as either colour.

Repo-wide pnpm lint is left to CI, which runs the farm exactly once regardless.

skip-changeset: the only file touched is scripts/check-cli-command-ids.mjs, a repo-root
gate script that is not part of any published package, so nothing ships to a consumer.


Generated by Claude Code

…st able to fail
The case pinning that ROOT_DIR_WATCH_HINTS is spelled as a source literal
searched the WHOLE file for a needle it spelled inline, so `includes` found
that needle in the assertion itself. Rewriting the declaration into the
computed form the case exists to reject left the self-test fully green --
38 of 38 cases passing -- while `extractWatchHints` recovered no hint at all
and the gate went unnameable by every dispatch brief.
Scope the search to the declaration statement and derive the needle from the
declared values. Assembling the needle alone is not sufficient here: unlike
check-objectql-double-limit.mjs, this file spells the hint a third time in the
runtime-value case below, so a whole-file search finds that copy and stays
green on the computed form.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PfaSTikked61BkcsB5Rn69
@os-zhuangos-zhuang added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 27, 2026 — with Claude
@os-zhuang
os-zhuang marked this pull request as ready for review August 27, 2026 17:17
@os-zhuang
os-zhuang enabled auto-merge August 27, 2026 17:17
@os-zhuang
os-zhuang added this pull request to the merge queueAug 27, 2026
Merged via the queue into main with commit 0acdd2bAug 27, 2026
34 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-12472-cli-command-ids-selftest-pin branch August 27, 2026 17:40
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/sskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

check-cli-command-ids' "declaration is spelled as a LITERAL" self-test assertion cannot fail — the needle appears in the assertion's own source text

2 participants

@os-zhuang@claude