feat(scripts): assert no bare any in a marked skill example, and scan .claude/skills too - #7490
Merged
Merged
Conversation
…an `.claude/skills` too Two of the three hardening items objectui#7463 records against `check-skill-examples.mjs`. The third — bounding root-devDependency resolution — is measured here and escalated rather than taken, because the harness that would carry the bound is shared. The bare-`any` assertion is ported from objectstack's `packages/spec/scripts/check-skill-examples.ts` with its scope intact: the annotation must BE `any` in a position that erases checking (parameter, variable / property / return annotation, type alias, `as any` / `satisfies any` / angle-bracket assertion). A nested `any` (`Record<string, any>`, `any[]`, `Promise<any>`) is deliberately allowed — that boundary is the zero-false-positive line. One deliberate divergence: the walk parses every block as TSX because `compileSnippets` does, so the guard reads the same tree `tsc` judged. Measured under `--measure` before arming: 4 findings in 3 marked fences, 10 over the whole candidate population. All four are declared verbatim in `KNOWN_BARE_ANY_EXAMPLES`, a shrink-only ratchet whose rows fail as STALE when their red goes away. None is unmarked or re-pointed: one of them faithfully restates `ComponentInput.defaultValue`, which really is `any` in `packages/types/src/base.ts`, and the other three are testing idiom. Zero new red on day one. `SCAN_ROOTS` gains `.claude/skills`, the widening `check-skills-paths.mjs` took in objectui#7358. Opt-in stays opt-in: 18 to 20 guides and 112 to 121 ts fences, and zero new MARKED fences — no marker is added under `.claude/skills/` here. Exit-2 PRECONDITION NOT MET semantics are unchanged, `--build-filter` output is byte-identical, and the marked-population shrink-only ratchet is still not taken. `--self-test` grows 21 legs: ten in-scope positions found, six nested `any`s not found, JSX not mis-parsed, and the baseline pinned in both directions. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1
…ed scan roots The two `SCAN_ROOTS` pins asserted the pre-widening root list; they now assert the widened one, and the non-vacuity leg additionally requires BOTH roots to be non-empty in this checkout, so a root list nothing reads cannot pass. New pins for the bare-`any` assertion. The negative half is the load-bearing one: ten in-scope positions must be found and six nested `any`s must not be, because that boundary is the reason a red from this assertion means something. Plus the TSX ScriptKind (a JSX example must not be mis-read as a type assertion), the no-throw contract on an unparseable block, the baseline row-key shape, and a check that every declared row is shaped like a key the gate can actually produce and sits under a scanned root. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1
This was referenced Sep 3, 2026
os-litant
marked this pull request as ready for review
September 3, 2026 10:07
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of #7463
Two of the card's three hardening items on
check-skill-examples.mjsare implemented. Item 2 is measured and escalated, not taken: the bound's two candidate homes lead to materially different edits, and the brief for this flight says stop there rather than guess.Every population below was measured before the assertion was armed, and re-measured at head
2df07b7.Item 1 — the bare-
anyassertion (implemented)Ported from objectstack's
packages/spec/scripts/check-skill-examples.tswith its scope intact: the annotation must beanyin a position that erases checking — a parameter, a variable / property / return annotation, a type alias, or anas any/satisfies any/ angle-bracket assertion. Ananynested inside a larger type is deliberately allowed:Recordof string toany,any[],Promiseofany. That boundary is the zero-false-positive line, and holding it is what keeps a red meaning broken.One deliberate divergence from objectstack, and why
objectstack picks
ScriptKindoff the fence label. The harness this gate compiles through —compileSnippetsincheck-doc-snippet-types.mjs— parses every block as TSX regardless of label, for reasons its own header records. So this walk uses TSX too. A guard that walked a different tree from the onetscjudged would be exactly the dormant checker this file's docblocks warn about.Visible consequence: an angle-bracket
anyassertion is JSX under TSX, so it is a parse failure — already red through the syntax leg, one exit code earlier, never reaching this walk. Its arm is kept in the position table so the rule stays whole if the harness's ScriptKind ever changes, and the header says so rather than leaving a reader to discover a dead branch.Measured population, before arming
--measureover every candidate fence, marked or not:.claude/skillsfences (item 3's new candidates)The 4 would-be-red rows, each declared verbatim
None is unmarked or re-pointed. All four are carried in
KNOWN_BARE_ANY_EXAMPLES, a shrink-only ratchet in the shapeKNOWN_UNTAUGHT_EVAL_TOKENSuses incheck-skill-eval-tokens.mjs: a row whose red goes away fails as stale, so the list can only shrink.skills/objectui/guides/plugin-development.md:92defaultValueComponentInput.defaultValuereally isanyatpackages/types/src/base.ts:550. The honest fix is to the platform type; changing the guide alone would make it lie about the contract it restates.skills/objectui/guides/testing.md:60as anyassertionvalidateSchema({} as any)— feeding the validator something invalid on purpose. Which idiom the testing guide should teach for that (as unknown as T, a ts-expect-error) is a question about the guide.skills/objectui/guides/testing.md:208mockClientskills/objectui/guides/testing.md:208as anyassertionEach needs a per-row skills judgement that a gate-hardening change is not the place to make. Declaring them keeps that judgement a visible piece of work instead of a silent exemption — and the stale direction means a row cannot be parked forever.
Item 2 — bounding root-devDependency resolution (MEASURED, ESCALATED, no code)
What was measured
The marked population's unmapped specifiers are exactly
@playwright/testandvitest. Both are declared by the repository root and both resolve out of/node_modules, where pnpm symlinks the root's own dependency set. Neither is declared by any package a marked fence imports, so neither is covered by the workspace map or the declared-dependency map. The inherited UNDECLARED control does not close this: it bounds resolution against transitive packages, which pnpm leaves only under.pnpm/— a different leak.Under
--measurethe unmapped set is 12: six root-declared and installed (@playwright/test,@testing-library/react,msw,react,vite,vitest), and six not installed at the root at all (four@objectstack/*, plus@tailwindcss/viteand@vitejs/plugin-react) — those already fail to resolve, which is the existing bound working.Why this stops here
The harness is shared by exactly two production consumers:
check-doc-snippet-types.mjsitself and this gate. Measured blast radius of putting the bound incompileSnippets:That is a verdict move on a gate this flight does not own, over a documentation surface with its own owner. The card's own words are that the harness "carries the same edge" and asks where the bound should live; the two readings produce materially different edits, so per the flight brief this item returns for a decision rather than being guessed.
The options, on the four axes
Option A — bound inside
compileSnippets, unconditionally (both gates).Option B — bound per gate (a parameter
compileSnippetsaccepts, default off).Option C — do nothing; keep naming the specifiers on every run.
Unmapped specifiersline already stops the leak from being silent, which is what the landing flight chose deliberately.Recommendation: Option A, led by long-term soundness (weighted at least 50%): it is the only option that leaves one answer to one question, and it is the option the frame's contract-first and declared-equals-enforced axes both select. The one docs snippet it reds is a real finding, not collateral — that snippet's green rests on this workspace's devDependencies rather than on anything its reader installs. Option A does need a call this flight cannot make on its own: whether that snippet is fixed, or declared, and by whom. Option B is not recommended at any weighting — it buys one avoided docs edit with a permanent second resolution regime in shared code.
Item 3 —
SCAN_ROOTSwidened to.claude/skills(implemented).claude/skillsdoes exist in objectui (4 markdown files), so this is code, not a note. Widened the waycheck-skills-paths.mjswas in #7358, and for the same measured reason: when #7251 moved the two contributor-only guides out ofskills/, that gate stopped looking at them and nothing turned red.Widening a root is not arming it. Opt-in is the design, so this adds candidates, not coverage:
All 9 new candidates are
typescriptfences in.claude/skills/objectui-contributor/— 5 inguides/console-development.md, 4 inrules/no-touch-zones.md. The twoSKILL.mdfiles carry none. No marker was added to any file under.claude/skillsin this PR; that is the surface owner's step. None of the 9 carries a bareanyeither, so item 1 gains nothing to declare from item 3.Reverse verification
Each leg: commit first, mutate under a
trap ... EXIT INT TERMrestoring an absolute path, prove the mutation on disk by grep count before reading any verdict, then prove the restore bygit hash-objectequal to the HEAD blob plus an emptygit diff HEAD.Leg A — an undeclared bare
anyin a marked fence must redMutated
skills/objectui/guides/i18n.md(marked fence at line 39), injecting a bareanyvariable.0 -> 1; worktree blob7c2f01fe…differs from HEAD blobfc0d4ad3…[bare-any] skills/objectui/guides/i18n.md:44:35 variable ... is annotated any, summary line5 finding(s) ... 1 NOT declaredfc0d4ad3…(equal to HEAD), injected-text count0,git diff HEADemptyLeg B — the ratchet's shrink-only direction
Mutated
skills/objectui/guides/testing.mdso a declared row's red goes away ({} as anybecomes{} as unknown as never).1 -> 0, injected-text count0 -> 1; bloba88336f9…differs from HEAD601d98fd…skills/objectui/guides/testing.md:60 as any assertion [stale-baseline] this row is no longer red — delete its line601d98fd…, removed-text count back to1,git diff HEADemptyLeg C — the widened root is real coverage, not a cosmetic list
Appended a marked fence carrying a bare
anyto.claude/skills/verify/SKILL.md.0 -> 1; blob38816807…differs from HEAD68b48b7e…[bare-any] .claude/skills/verify/SKILL.md:50:42 parameter v, andMarked: 18 ts fence(s)(up from 17) — the widened root both scans and gates the fileSCAN_ROOTSnarrowed back toskillsalone sees 0.claude/skillsguides and 0 findings. Without this control the leg would only show the gate working, not that the widening is what made it reach.68b48b7e…, injected-text count0,git diff HEADemptyLeg D — classifying three unrelated red tests
Three failures in
scripts/__tests__are pre-existing and independent of this change. Proven rather than asserted: reverted all three of my changed files to the merge-basee307c98(proof on disk: the narrowSCAN_ROOTSline present,findBareAnyabsent), re-ran both suites, and the same 3 tests failed identically. Neither suite referencescheck-skill-examples. Restore proof: all three blobs equal their HEAD blobs,git diff HEADandgit status --porcelainboth empty.They are built-tree-sensitive:
check-sdui-registration-pinsexpectspackages/app-shell/src/...tsxand getsdist/...js, and the twocheck-readme-exportslegs are explicitly about "BOTH build states". This gate requires a built tree, so running its suite after the build surfaces them. Reported as out-of-scope findings, not fixed here.Gate table
Every exit code captured by redirect before any pipe. Verdicts quoted from each gate's own printed line.
node scripts/check-skill-examples.mjs(after the filtered build it derives)Every marked skill example holds up against the built types.—Bare any: 4 finding(s) across 3 selected fence(s); 4 declared ... 0 NOT declared, 0 declared row(s) no longer red.--self-test✓ check-skill-examples self-test: 42 cases pass (... the bare-any guard in both directions with its shrink-only baseline ...)(21 new legs)--measureStarting population — ts: 18/121 pass; json: 39/56 pass.—Bare any would-be population — 10 finding(s) over every candidate, of which 4 sit in a MARKED fence.--build-filterpnpm exec vitest run --maxWorkers=2 scripts/__tests__Test Files 2 failed, 98 passed (100)/Tests 3 failed, 2883 passed (2886)— the only failures are the 3 pre-existing ones classified in Leg D;check-skill-examples.test.tsalone: 65 passed, exit 0pnpm exec tsc -p tsconfig.scripts.json --noEmiteslint scripts/check-skill-examples.mjs --format jsonpnpm lint(repo-wide)Tasks: 47 successful, 47 total— 4220 files linted repo-wide, 0 errors (11832 pre-existing warnings, unchanged in kind)node scripts/check-pre-install-import-graph.mjs✅ check-pre-install-import-graph: OK — 23 pre-install step(s) in 18 job(s) run 21 scripts/ gate(s); 23 module(s) walked, every non-relative leaf a node builtin.node scripts/check-changeset-presence.mjs✅ No source or published contract of a released package changed in this range, so no changeset is owed.node scripts/check-governed-queue-guard.mjs --testover the final path list✅ NOT GOVERNED — 2 path(s) checked against 5 governed surface(s); none matched.The governed-queue verdict is NOT GOVERNED and that is the expected answer: this change set touches only
scripts/, its tests, andcontent/docs/— no file underskills/or.claude/skills/is modified. The PR stays draft either way.Control-byte scan over the changed files (
grep -naPfor C0/C1 and DEL) returned no matches.Per-file before and after
scripts/check-skill-examples.mjsscripts/__tests__/check-skill-examples.test.tscontent/docs/guide/ci-cd-pipeline.mdNo file under the published
skillsbundle or under.claude/skillsis touched, so the published-skill line budget does not apply.Invariants held
PRECONDITION NOT METsemantics unchanged — unbuilt tree, failed harness control and empty marked population all still leave throughcouldNotRun. A bareanyand a stale row are verdicts about a guide, so they leave through exit 1, alongside the orphan marker.skills/objectui/— 123 TypeScript fences and 63 JSON fences are checked by nothing #7359 step 3) is not taken here.--build-filteroutput byte-identical, soREQUIRED_CONTEXTSand the workflow are unchanged.NOT MEASURED, and why
check-skill-examples.mjsis not type-checked.tsconfig.scripts.jsonsetsallowJs: truewithcheckJs: falseon purpose (its own comment records the measurement behind that choice). Confirmed with--listFiles: the file is in the program, so the.tstests' use of the new exports (findBareAny,bareAnyRowKey,KNOWN_BARE_ANY_EXAMPLES) is checked — but errors inside the.mjsare not reported. A cleantschere is a statement about the callers, not about the module body.Out-of-scope findings
Recorded for the seat to dedup and file — not fixed here, and no cards filed by me.
scripts/__tests__/check-sdui-registration-pins.test.ts:146— the pin expects the source modulepackages/app-shell/src/console/connect/ConnectAgentWidget.tsxand getspackages/app-shell/dist/console/connect/ConnectAgentWidget.json a built tree. The derivation prefersdistonce it exists, so this pin passes only on an unbuilt checkout. Proven pre-existing ate307c98(Leg D).scripts/__tests__/check-readme-exports.test.ts:899and:950— both legs of "hides ONLY omissions, and the tree says so in BOTH build states" / "finds no fabricated or wrong-path import when built" fail on a built tree (excerptsNotJudgedis 1, expected 3). Same class as (1), same proof.packages/types/src/base.ts:550—defaultValue?: anyin the exportedComponentInputinterface. Not a guide defect: it is why row 1 of the baseline exists. Tightening it tounknownis the contract-first fix, and it is a platform-type change with its own consumers, so it wants its own card.🤖 Generated with Claude Code
https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1