Skip to content

Commit 1eea507

Browse files
yinlianghuiclaude
andauthored
feat(devx): gate CLI command-id string literals outside the CLI package (#12225)
* feat(devx): gate CLI command-id literals outside the CLI package (#12016) A CLI command id spelled as a string literal outside `packages/cli` has nothing tying it to the command file it names. Rename the command, update the CLI and the docs but not the engine, and `driver-sql`'s drift warning keeps printing a route that no longer exists — a stale hint inside an otherwise-correct message, which no suite reads as wrong: the driver's own pin asserts the message CONTAINS the constant, and the constant still matches itself. The card offered the gate on the argument that the constant "is unlikely to stay the only such string" — a claim about a population nobody had counted. Counted first: 274 command-id literals across 98 files outside the CLI package, 14 of them in `schema-drift.ts` itself. The same warning message that interpolates the named constant also spells "os migrate apply" inline, as a bare literal with no constant and no pin at all. The gate derives ids from declared data (`oclif.bin` plus every `bin` key; `src/commands/**` paths), never a table, and resolves topic directories as well as command files. Its self-test pins the #12016 rename itself: the same literal that resolves before the rename resolves to nothing after it. That case is what caught a plain longest-prefix rule quietly rescuing `os migrate multi-value-columns` as `os migrate` — blind to exactly the failure the gate exists for. Two declared ledgers, both self-retiring: three self-test fixtures, and one real violation the first live run found in `packages/spec` (#12223). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_af22b339-91b5-5814-8b9b-2453fc5b3f68 * fix(devx): exclude the command-id gate's own source from its own population A checker cannot be its own evidence. Every negative fixture in this file — `os migrate nonexistent-command`, `os demo`, `os nope nope` — is an unresolvable id ON PURPOSE, because that is what a self-test for this gate is made of. Scanning them made the gate red in proportion to how well it was tested, with "delete the tests" as the only way back to green. Found the honest way rather than reasoned out: `populationFiles` reads `git ls-files`, so the gate ran green while its own source was still UNTRACKED and reded on the first run after it was committed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_af22b339-91b5-5814-8b9b-2453fc5b3f68 * fix(devx): declare the command-id gate's scripts/ subtree so a dispatch can name it `dispatch-gates` derives WHICH gates a card must run by matching path literals in each gate's source against the card's changed files, and `hintCovers` REFUSES a bare single-segment literal as too generic — a measured refusal (+139084 fabricated pairs) that stays. `populationFiles` spelled its admission predicate as `f.startsWith('scripts/')`, which collapses to `scripts`, so this gate would have "landed already invisible": never named for a card touching its own population, scoring the same quiet green for every card in the tree. Remedy (a), not (b), and the difference is what the gate READS. `'scripts/'` here is an ADMISSION predicate, not an exclusion or a fixture path: every tracked source file under that root is in the population with no further test, and seven of them already carry findings. So the subtree spelling is a true statement about the scan, and declaring it is the `ROOT_DIR_WATCH_HINTS` idiom (`check-role-word.mjs`, `check-examples-live-imports.mjs`), not a fabricated lead. The roots are hoisted to POPULATION_ROOTS so the predicate and the declaration cannot drift, and four self-test cases pin both directions: every separator-less root is declared as `<root>/**`, nothing is declared that the gate does not walk whole, the declaration is a source LITERAL (a computed `${r}/**` builds no hint at all), and the population really reaches across the root. `packages/**` is deliberately NOT declared — this gate opens each package's `src` subtree and its package.json, not the root entire. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_af22b339-91b5-5814-8b9b-2453fc5b3f68 --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 18c432e commit 1eea507

3 files changed

Lines changed: 600 additions & 0 deletions

File tree

‎.github/workflows/lint.yml‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1604,6 +1604,18 @@ jobs:
16041604
- name: CLI test child-env guard
16051605
run: pnpm check:cli-test-child-env
16061606

1607+
# #12016. A CLI command id spelled as a string literal OUTSIDE the CLI
1608+
# package has nothing tying it to the command file it names: rename the
1609+
# command, update the CLI and the docs, and the engine's own drift warning
1610+
# keeps printing a route that no longer exists — a stale hint inside an
1611+
# otherwise-correct message, which no suite reads as wrong because
1612+
# driver-sql's pin asserts the message contains the constant and the
1613+
# constant still matches itself. 274 such literals across 98 files were
1614+
# measured before the gate was written; the sibling sweep INSIDE
1615+
# packages/cli is #11465 / #12177, and the two populations do not overlap.
1616+
- name: CLI command-id literals resolve
1617+
run: pnpm check:cli-command-ids
1618+
16071619
# #4093 follow-up. Discovery tells a consumer an absent capability is
16081620
# absent AND what to install. The first half has been carefully honest
16091621
# since #2462/#4000; the second was invented from the slot name, so ten

‎package.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@
7373
"check:org-identifier": "node scripts/check-org-identifier.mjs --self-test && node scripts/check-org-identifier.mjs",
7474
"check:runner-env-posture": "node scripts/check-runner-env-posture.mjs --self-test && node scripts/check-runner-env-posture.mjs",
7575
"check:cli-test-child-env": "node scripts/check-cli-test-child-env.mjs --self-test && node scripts/check-cli-test-child-env.mjs",
76+
"check:cli-command-ids": "node scripts/check-cli-command-ids.mjs --self-test && node scripts/check-cli-command-ids.mjs",
7677
"check:authz-resolver": "node scripts/check-single-authz-resolver.mjs --self-test && node scripts/check-single-authz-resolver.mjs",
7778
"check:slot-lookup": "node scripts/check-slot-lookup-ratchet.mjs --self-test && node scripts/check-slot-lookup-ratchet.mjs",
7879
"check:query-options-erasure": "node scripts/check-query-options-erasure-ratchet.mjs --self-test && node scripts/check-query-options-erasure-ratchet.mjs",

0 commit comments

Comments
 (0)