@@ -10,11 +10,14 @@ import { fileURLToPath } from 'node:url';
1010import {
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
234237describe ( '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 . / - ] + \. m d : \d + .+ $ / ,
553+ ) ;
554+ expect ( SCAN_ROOTS . some ( ( r : string ) => row . startsWith ( `${ r } /` ) ) ) . toBe ( true ) ;
555+ }
556+ } ) ;
557+ } ) ;
0 commit comments