Repository navigation
Commit 4e6b270
tooling(pm): check-skill-id-lint scans the published skills/ catalog too (#23002)
Fixes #22983
Clause-②: no — a repository gate script; no published schema, accept set
or public surface is touched
## What changes
`scripts/pm/check-skill-id-lint.mjs` gains a second scan root for the
published catalog: `PUBLISHED_SCAN_ROOT = 'skills'`, declared beside
`SCAN_ROOT` (which stays the internal corpus
`.claude/skills/pm-dispatch`), walked by the same `mdFilesUnder` walker
and held to the same cannot-read-is-red rule. The header's "What it
scans" paragraph names the new root and why: its reader is a customer's
agent with no tool that resolves an issue number into this tracker, so
the 2026-08-12 ruling applies a fortiori. `ID_PATTERN`, the
`LEGACY_EXACT` mechanism, `verdictFor` and its messages, and the
internal corpus's semantics are unchanged.
`run` is split into `lintTree(root)` (walk + verdicts, returned as data)
and a printer, so the self-test drives the real walk over throwaway
fixture trees instead of only the per-file verdict.
## The premise that did not hold: "0 hits today"
The card and the dispatch expected `git grep -c -E '#[0-9]{3,}'
origin/main -- 'skills/**'` to answer 0 files. At this branch's base
`6a40a09ed`, and at the card's own reading commit `246edf5b3`, it
answers **2 files**. Both hits are CSS hex colours, not citations:
- `skills/objectstack-data/rules/field-types.md:86`: `color: '#3498db'`
- `skills/objectstack-ui/rules/navigation.md:20`: `primaryColor:
'#4169E1'`
So the widening as specified would have landed red on two colours.
Ablation 1 below shows exactly that. The dispatch's own hex probe (`git
grep -n -E '#[0-9a-fA-F]{6}\b' -- 'skills/**'`) answers 10 lines in 5
files at the base. The route stays inside the ruling: no change to
`ID_PATTERN`, `LEGACY_EXACT` or the internal corpus's semantics, and no
edit to `skills/**`. **Under `skills/` only**, a match whose digit run
continues straight into a letter or underscore is not counted
(`NOT_A_CITATION_TAIL`), because the digits are the head of a longer
word and name no number. A citation keeps a non-word neighbour
(`#1234.`, `(#1234)`, end of line) and stays red. The shape that remains
is an all-decimal colour (`#000000`). The header names it as a known
false positive and the self-test pins it red, so the first real one gets
decided with the hit in hand.
## Derivation readings (`scripts/pm/dispatch-gates.mjs`, frozen files
untouched)
`node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack skills/objectstack-data/SKILL.md`
- before, at `6a40a09ed`: `30 command(s) — 21 pnpm, 9 direct node (22
matched by path, …)`, and `pnpm check:pm-skill-id-lint` is **absent**
- after, at `0a07f2f2c` and again at the final `6744bf9bc` (identical
lists): `31 command(s) — 22 pnpm, 9 direct node (23 matched by path,
…)`. The diff of the two lists is exactly one added line, `pnpm
check:pm-skill-id-lint`
The extractor refuses the bare `'skills'` as too generic (it has no
separator), so the gate declares `ROOT_DIR_WATCH_HINTS =
['skills/**/*.md']` beside the constant. This is the same idiom other
gates use. `extractWatchHints` over the gate now answers
`.claude/skills/pm-dispatch`, `.claude/agents/os-dev.md`,
`skills/**/*.md`, `AGENTS.md/**`. The hint covers 53 of the 65 tracked
files under `skills/`, and that set equals the gate's own walk in both
directions. It passes over the 12 eval JSON files, which the gate never
opens.
## Second file: `scripts/pm/bare-root-worklist.mjs` (outside the claim's
declared file surface)
`PUBLISHED_SCAN_ROOT = 'skills'` is a bare-root population literal. At
`0a07f2f2c`, `node scripts/pm/bare-root-worklist.mjs --self-test`
therefore refused it as FRESH (2 failures), and its printed remedy is to
record a verdict. The row now takes `DECLARED-NARROWER` with a new
pinned spelling, `published skill markdown` (segments skills / ** /
*.md). The worklist self-test holds it LIVE, PRECISE, COMPLETE and
SET-EQUAL to the gate's declared array: `OK self-test: 82 live row(s), …
47 recorded verdict(s) — none stale, none missing, none contradicted`.
The alternative would declare `skills/**` wholesale. That makes the row
REACHABLE with no ledger entry and keeps the diff to one file. It was
refused because it names the gate for 12 eval JSON files it never reads,
which the worklist's own refusal text calls "the costlier error".
Renaming the constant so the worklist cannot see it was not considered:
that file's header refuses that dodge by name.
## Readings
- **File count.** The gate before the change printed `✓
check-skill-id-lint: 34 file(s) clean`. After, it prints `✓
check-skill-id-lint: 87 file(s) clean`, a gain of **+53**. There are 53
`.md` files under `skills/` by `git ls-files skills | grep -c '\.md$'`
(and by `find`). The dispatch's `git ls-files 'skills/**/*.md' | wc -l`
answers **52**: under git's default pathspec, `**/` needs a separator
after `skills/`, so that count misses the catalog's top-level
`skills/README.md`. The walker reads that file, which is correct.
- **Self-test.** `✓ check-skill-id-lint self-test: 23 cases pass.` That
is the 14 existing rows plus 9 new ones, and the floor pin moves 14 to
23. The new rows: a `.md` under `skills/` carrying `#1234` is red and
names that file; one carrying none is green and walked (4 scanned in the
fixture); a letter-tailed colour is green; an all-decimal colour stays
red; the internal corpus still reds the same colour; a missing `skills/`
is red, not a skip; plus three rows pinning the watch-hint declaration
(declared for the root, names no other root, not itself the walk root).
## Ablations (one-shot, on committed state, restore proven)
Both mutations go through `scripts/ablation-replace.mjs` in WRAP mode,
with a shell `trap` restore on top. The gate file is byte-identical at
the final head (blob `9a1b0df4`).
1. **Hex rule disabled** (`if (published && NOT_A_CITATION_TAIL` changed
to `if (false && NOT_A_CITATION_TAIL`; anchor count 1 to 0, blob
`9a1b0df4` to `235341e2`). The self-test reds `published root: a colour
whose digits run into a letter -> green`. The gate goes red on exactly
the two colour lines above. Restored: blob equals HEAD and `git diff
HEAD` is empty.
2. **Published root dropped from the walk** (`[SCAN_ROOT,
PUBLISHED_SCAN_ROOT]` changed to `[SCAN_ROOT]`). The self-test reds 5
rows: `#1234` red, carrying none is green and walked, the colour row,
the all-decimal row, and the missing-root row. The gate itself falls
back to `34 file(s) clean`, so the self-test is what catches this.
Restored the same way.
## Gates: union at `6744bf9bc` (final commit)
The command list comes from `node scripts/pm/dispatch-gates.mjs
--commands --repo objectstack-ai/objectstack` with no paths (change set
2 paths vs merge base `6a40a09ed`). All 30 exited 0. Reconciled: `✓
dispatch-gates --ran: 30 derived famil(ies) accounted for — 30 run, 0
NOT-MEASURED (a DERIVED zero — all 30 recorded an exit code and none of
them is 3).`
- `pnpm check:pm-skill-id-lint`: `✓ check-skill-id-lint: 87 file(s)
clean (pattern /#[0-9]{3,}/g).`
- `pnpm check:watch-hint-literal`: `✓ check-watch-hint-literal: 73
declaration(s) across 4 rostered name(s) -- ROOT_DIR_WATCH_HINTS 49, …`
- `pnpm check:pm-dispatch-gates`: `✓ dispatch-gates self-test (fast
tier): 1828 cases pass; 6 slow section(s) deferred.`, exit 0
- `node scripts/pm/bare-root-worklist.mjs --self-test`: `OK self-test:
82 live row(s), 60 unreachable as spelled, 47 recorded verdict(s) — none
stale, none missing, none contradicted …`
- `pnpm check:nul-bytes`: `check-nul-bytes: OK (scanned 11000 text
file(s) …; no raw ASCII control bytes).`
- The other 25 also exited 0: ci-filter-parity, closing-keyword-parity
(+ self-test), comment-mask-corpus, declaration-mirrors (+ self-test),
scripts-symbol-anchors (+ self-test), self-test-wired (+ self-test),
self-test-workflow-commands (+ self-test), whole-set-label-write (+
self-test), agent-test-spelling, bash32-floor, cli-command-ids,
cross-package-test-inputs, driver-memory-census, entry-guard,
gitlink-declared, parse-guard, pnpm-filter-targets,
ratchet-remedy-authority, refd-timer-probe.
- Lint, narrowed and proven at `6744bf9bc`. ESLint reports 2 files, 0
errors and 0 warnings (counted from its `--format json` output, with
`--no-inline-config`). Both files are in the config's population
(`isPathIgnored` answers false for each). The config never enables
type-aware linting (no `parserOptions.project`), so this diff cannot
move the verdict on any untouched file.
- NOT MEASURED, left to CI: the `Test Core` job these paths schedule (7
CI-only steps, no local invocation), and the four type-check lanes. This
diff touches no TypeScript.
## Acceptance notes
- The comment above the `PM skill issue-ID lint` step in
`.github/workflows/lint.yml` still lists the scan set as the pm-dispatch
skill plus `os-dev.md`, and mentions "the one self-expiring legacy
waiver", which no longer exists. It already omitted `AGENTS.md` before
this change. The comment defers to the script header as authoritative,
and it is outside this card's file surface, so it is left as is.
- No changeset: `scripts/pm/**` publishes nothing, so this takes
`skip-changeset`.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01CinRQcE6T8SvMbhH8D3NhS)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 794177d commit 4e6b270
2 files changed
Lines changed: 185 additions & 40 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
539 | 539 | | |
540 | 540 | | |
541 | 541 | | |
| 542 | + | |
| 543 | + | |
| 544 | + | |
| 545 | + | |
| 546 | + | |
542 | 547 | | |
543 | 548 | | |
544 | 549 | | |
| |||
775 | 780 | | |
776 | 781 | | |
777 | 782 | | |
| 783 | + | |
| 784 | + | |
| 785 | + | |
| 786 | + | |
| 787 | + | |
| 788 | + | |
| 789 | + | |
| 790 | + | |
| 791 | + | |
| 792 | + | |
| 793 | + | |
| 794 | + | |
| 795 | + | |
| 796 | + | |
| 797 | + | |
| 798 | + | |
778 | 799 | | |
779 | 800 | | |
780 | 801 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
20 | 20 | | |
21 | 21 | | |
22 | 22 | | |
23 | | - | |
24 | | - | |
25 | | - | |
26 | | - | |
27 | | - | |
28 | | - | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
29 | 38 | | |
30 | 39 | | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
31 | 61 | | |
32 | 62 | | |
33 | 63 | | |
| |||
40 | 70 | | |
41 | 71 | | |
42 | 72 | | |
43 | | - | |
44 | | - | |
| 73 | + | |
| 74 | + | |
45 | 75 | | |
46 | 76 | | |
47 | | - | |
48 | | - | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
49 | 80 | | |
50 | 81 | | |
51 | 82 | | |
52 | 83 | | |
53 | 84 | | |
54 | 85 | | |
55 | 86 | | |
| 87 | + | |
| 88 | + | |
56 | 89 | | |
57 | 90 | | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
58 | 111 | | |
59 | 112 | | |
60 | 113 | | |
| |||
115 | 168 | | |
116 | 169 | | |
117 | 170 | | |
118 | | - | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
119 | 189 | | |
120 | | - | |
121 | | - | |
122 | | - | |
123 | | - | |
124 | | - | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
125 | 196 | | |
126 | | - | |
| 197 | + | |
127 | 198 | | |
128 | | - | |
129 | | - | |
130 | 199 | | |
131 | | - | |
| 200 | + | |
132 | 201 | | |
133 | 202 | | |
134 | 203 | | |
135 | 204 | | |
136 | | - | |
137 | | - | |
| 205 | + | |
138 | 206 | | |
139 | 207 | | |
140 | 208 | | |
141 | | - | |
142 | | - | |
| 209 | + | |
143 | 210 | | |
144 | 211 | | |
145 | | - | |
146 | | - | |
147 | | - | |
| 212 | + | |
| 213 | + | |
148 | 214 | | |
149 | | - | |
150 | | - | |
151 | 215 | | |
152 | | - | |
153 | | - | |
154 | | - | |
155 | | - | |
156 | | - | |
157 | | - | |
158 | | - | |
159 | | - | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
160 | 220 | | |
| 221 | + | |
161 | 222 | | |
162 | 223 | | |
163 | | - | |
164 | | - | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
165 | 235 | | |
166 | 236 | | |
167 | 237 | | |
| |||
211 | 281 | | |
212 | 282 | | |
213 | 283 | | |
| 284 | + | |
| 285 | + | |
| 286 | + | |
| 287 | + | |
| 288 | + | |
| 289 | + | |
| 290 | + | |
| 291 | + | |
| 292 | + | |
214 | 293 | | |
215 | 294 | | |
216 | 295 | | |
| |||
219 | 298 | | |
220 | 299 | | |
221 | 300 | | |
222 | | - | |
| 301 | + | |
223 | 302 | | |
224 | 303 | | |
| 304 | + | |
| 305 | + | |
| 306 | + | |
| 307 | + | |
| 308 | + | |
| 309 | + | |
| 310 | + | |
| 311 | + | |
| 312 | + | |
| 313 | + | |
| 314 | + | |
| 315 | + | |
| 316 | + | |
| 317 | + | |
| 318 | + | |
| 319 | + | |
| 320 | + | |
| 321 | + | |
| 322 | + | |
| 323 | + | |
| 324 | + | |
| 325 | + | |
| 326 | + | |
| 327 | + | |
| 328 | + | |
| 329 | + | |
| 330 | + | |
| 331 | + | |
| 332 | + | |
| 333 | + | |
| 334 | + | |
225 | 335 | | |
226 | 336 | | |
227 | 337 | | |
| |||
247 | 357 | | |
248 | 358 | | |
249 | 359 | | |
| 360 | + | |
| 361 | + | |
| 362 | + | |
| 363 | + | |
| 364 | + | |
| 365 | + | |
| 366 | + | |
| 367 | + | |
| 368 | + | |
| 369 | + | |
| 370 | + | |
| 371 | + | |
| 372 | + | |
| 373 | + | |
250 | 374 | | |
251 | 375 | | |
252 | 376 | | |
| |||
0 commit comments