Skip to content

Commit 2df07b7

Browse files
committed
test(scripts): pin the bare-any guard both directions and the widened 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
1 parent 9f12a8a commit 2df07b7

1 file changed

Lines changed: 87 additions & 6 deletions

File tree

‎scripts/__tests__/check-skill-examples.test.ts‎

Lines changed: 87 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,14 @@ import { fileURLToPath } from 'node:url';
1010
import {
1111
EXIT_CODES,
1212
JSON_FENCE_LANGUAGES,
13+
KNOWN_BARE_ANY_EXAMPLES,
1314
MARKER,
1415
SCAN_ROOTS,
1516
TS_FENCE_LANGUAGES,
17+
bareAnyRowKey,
1618
buildFilterArgs,
1719
fenceSpans,
20+
findBareAny,
1821
listGuides,
1922
parseJsonFence,
2023
scanSkillFences,
@@ -232,11 +235,14 @@ describe('JSON fences — `json` is strict, `jsonc` is exactly two things looser
232235
});
233236

234237
describe('the scan surface is a decision, stated here rather than read off the walker', () => {
235-
it('walks `skills` and, today, nothing else', () => {
236-
// `.claude/skills/**` is deliberately outside this gate for now, the way
237-
// `check-skills-paths.mjs`'s prefix allow-list was: widening it is a
238-
// separate, visible edit with its own measurement, not a silent drift.
239-
expect(SCAN_ROOTS).toEqual(['skills']);
238+
it('walks the published bundle AND `.claude/skills`, and nothing else', () => {
239+
// objectui#7463 item 3 widened this to `.claude/skills`, the same widening
240+
// `check-skills-paths.mjs` took in objectui#7358 and for the same reason:
241+
// when objectui#7251 moved the contributor-only guides out of `skills/`,
242+
// a gate rooted only at `skills` silently stopped looking at them. It
243+
// stays a stated decision with a measurement, never a silent drift — the
244+
// widening added 9 candidate fences and ZERO marked ones.
245+
expect(SCAN_ROOTS).toEqual(['skills', '.claude/skills']);
240246
});
241247

242248
it('collects every `.md` under the roots, recursively and in a stable order', () => {
@@ -271,7 +277,11 @@ describe('the scan surface is a decision, stated here rather than read off the w
271277
// nothing at all.
272278
const guides = listGuides(repoRoot) as string[];
273279
expect(guides.length).toBeGreaterThan(5);
274-
expect(guides.every((g) => g.startsWith('skills/'))).toBe(true);
280+
expect(guides.every((g) => SCAN_ROOTS.some((r) => g.startsWith(`${r}/`)))).toBe(true);
281+
// Both roots must actually be non-empty in this checkout, or the widening
282+
// would be a root list nothing reads — the vacuity this leg exists to deny.
283+
expect(guides.some((g) => g.startsWith('skills/'))).toBe(true);
284+
expect(guides.some((g) => g.startsWith('.claude/skills/'))).toBe(true);
275285
});
276286
});
277287

@@ -474,3 +484,74 @@ describe('the harness is imported, not re-rolled', () => {
474484
expect(source).not.toContain('ts.createCompilerHost');
475485
});
476486
});
487+
488+
/**
489+
* objectui#7463 item 1 — the bare-`any` assertion, ported from objectstack's
490+
* `packages/spec/scripts/check-skill-examples.ts`.
491+
*
492+
* The NEGATIVE half carries the weight. A bare `any` erases checking wholesale,
493+
* but an `any` nested in a larger type is a much broader question with a much
494+
* larger baseline, and a guard that flagged it would red on prose that is not
495+
* wrong — which is how gates get deleted. That boundary is the whole design, so
496+
* it is pinned rather than left to the implementation.
497+
*/
498+
describe('the bare-`any` assertion', () => {
499+
it.each([
500+
['a parameter', 'export function f(ctx: any) { return ctx; }', 'parameter `ctx`'],
501+
['a variable', 'export const x: any = 1;', 'variable `x`'],
502+
['an interface property', 'export interface I { p: any }', 'property `p`'],
503+
['a class property', 'export class C { p: any = 1; }', 'property `p`'],
504+
['a type alias', 'export type A = any;', 'type alias `A`'],
505+
['a return type', 'export function g(): any { return 1; }', 'return type'],
506+
['an arrow return type', 'export const h = (): any => 1;', 'return type'],
507+
['a method signature return', 'export interface J { m(): any }', 'return type'],
508+
['an `as any` cast', 'export const y = ({} as any);', '`as any` assertion'],
509+
['a `satisfies any`', 'export const z = ({} satisfies any);', '`satisfies any` assertion'],
510+
])('flags %s', (_label, code, want) => {
511+
const hits = findBareAny(code) as { where: string }[];
512+
expect(hits.map((h) => h.where)).toEqual([want]);
513+
});
514+
515+
it.each([
516+
['Record<string, any>', 'export const a: Record<string, any> = {};'],
517+
['any[]', 'export const b: any[] = [];'],
518+
['Array<any>', 'export const c: Array<any> = [];'],
519+
['Promise<any>', 'export async function d(): Promise<any> { return 1; }'],
520+
['a union arm', 'export const e: string | any[] = [];'],
521+
['the word "any" in a string or a comment', 'export const f = "any"; // any of them'],
522+
])('does NOT flag a nested `any` in %s', (_label, code) => {
523+
expect(findBareAny(code)).toEqual([]);
524+
});
525+
526+
it('parses as TSX, so a JSX example is not mis-read as a type assertion', () => {
527+
// `compileSnippets` parses every block as TSX regardless of the fence
528+
// label. A guard walking a different tree would be reporting about a
529+
// program `tsc` never judged.
530+
expect(findBareAny('export const El = () => <div className="x">hi</div>;')).toEqual([]);
531+
});
532+
533+
it('yields nothing rather than throwing on a block too broken to parse', () => {
534+
// The `tsc` syntax leg owns that verdict; this guard must not double-report
535+
// it, and must not crash the run either.
536+
expect(() => findBareAny('export const three: = ;')).not.toThrow();
537+
});
538+
539+
it('builds a baseline row key naming the guide, the fence line and the position', () => {
540+
const block = { doc: 'skills/objectui/guides/x.md', fenceLine: 42 };
541+
const finding = (findBareAny('export function f(ctx: any) {}') as { where: string }[])[0];
542+
expect(bareAnyRowKey(block, finding)).toBe('skills/objectui/guides/x.md:42 parameter `ctx`');
543+
});
544+
545+
it('declares its baseline as a shrink-only Set of verbatim rows', () => {
546+
// Every row must be shaped like a key this gate can actually produce, or it
547+
// would sit in the list forever covering nothing — a parked exemption
548+
// wearing a ratchet's clothes.
549+
expect(KNOWN_BARE_ANY_EXAMPLES).toBeInstanceOf(Set);
550+
for (const row of KNOWN_BARE_ANY_EXAMPLES as Set<string>) {
551+
expect(row, `baseline row is not \`GUIDE:LINE POSITION\`: ${row}`).toMatch(
552+
/^[\w./-]+\.md:\d+ .+$/,
553+
);
554+
expect(SCAN_ROOTS.some((r: string) => row.startsWith(`${r}/`))).toBe(true);
555+
}
556+
});
557+
});

0 commit comments

Comments
 (0)