/** * Doc ↔ CLI contract — every `npx … tsx …/index.ts` invocation written in a * SKILL.md / reference / emitted message uses only flags the CLI DECLARES, * and every `--rules` id it names is one the pack implements (or stubs). * * Seven documented invocations could not run before 2026-09-24 (`run-smoke * --spec`, `audit-dev-wire --module-path` from audit-fix, `aggregate-component- * registry --spec` from scaffold-doc, `/ui-components --theme-only`, ui-polish * `--audit/--apply`, `scaffold-tests --layer`, …) — the MANDATORY Phase 3e * runtime smoke had never run in a pipeline. This test makes that class * impossible to ship again. */ import { describe, it, expect } from 'vitest' import { readFileSync } from 'node:fs' import { join, relative } from 'node:path' import { SKILLS_ROOT, declaredOptions, extractInvocations, listFiles, resolveCliSource, type Invocation, } from './helpers/cli-options.js' import type { RuleRegistry } from '../audit-rules.js' import { DEV_CORE_RULES } from '../../development/audit-dev-core/cli/audit-dev-core/types.js' import { DEV_DAT_RULES } from '../../development/audit-dev-data/cli/audit-dev-data/types.js' import { DEV_DOM_RULES } from '../../development/audit-dev-domain/cli/audit-dev-domain/types.js' import { DEV_API_RULES } from '../../development/audit-dev-api/cli/audit-dev-api/types.js' import { DEV_WIRE_RULES } from '../../development/audit-dev-wire/cli/audit-dev-wire/types.js' import { DEV_UI_RULES } from '../../development/audit-dev-frontend/cli/audit-dev-frontend/rules.js' import { IMPLEMENTED_RULES as PWA_RULES } from '../../development/audit-dev-pwa/cli/audit-dev-pwa/audit.js' import { IMPLEMENTED_RULES as XAPI_RULES } from '../../development/audit-dev-external-api/cli/audit-dev-external-api/audit.js' import { expandRuleIds, ruleRegistryFromTokens } from '../../development/frontend/ui-polish/cli/ui-polish/audit.js' import type { TokensFile } from '../../development/frontend/ui-polish/cli/ui-polish/types.js' /** ui-polish has no literal registry: its rule ids ARE tokens.json `hardRules[]` (a short id such as `R7` expands before the check). */ const UI_POLISH_RULES: RuleRegistry = ruleRegistryFromTokens( JSON.parse(readFileSync(join(SKILLS_ROOT, 'development/frontend/ui-polish/tokens.json'), 'utf-8')) as TokensFile, ) /** Rule registries by CLI path (under templates/skills). */ const REGISTRIES: Record = { 'development/audit-dev-core/cli/audit-dev-core/index.ts': DEV_CORE_RULES, 'development/audit-dev-data/cli/audit-dev-data/index.ts': DEV_DAT_RULES, 'development/audit-dev-domain/cli/audit-dev-domain/index.ts': DEV_DOM_RULES, 'development/audit-dev-api/cli/audit-dev-api/index.ts': DEV_API_RULES, 'development/audit-dev-wire/cli/audit-dev-wire/index.ts': DEV_WIRE_RULES, 'development/audit-dev-frontend/cli/audit-dev-frontend/index.ts': DEV_UI_RULES, 'development/audit-dev-pwa/cli/audit-dev-pwa/index.ts': { implemented: PWA_RULES }, 'development/audit-dev-external-api/cli/audit-dev-external-api/index.ts': { implemented: XAPI_RULES }, 'development/frontend/ui-polish/cli/ui-polish/index.ts': UI_POLISH_RULES, } /** Per-CLI expansion of a documented id BEFORE the membership check — ui-polish accepts `R7` for `R7-error-banner-anatomy`. */ const EXPANDERS: Record string[]> = { 'development/frontend/ui-polish/cli/ui-polish/index.ts': (ids) => expandRuleIds(ids, UI_POLISH_RULES.implemented), } const rel = (p: string): string => relative(SKILLS_ROOT, p).replace(/\\/g, '/') function collect(keep: (p: string) => boolean): Invocation[] { const out: Invocation[] = [] for (const file of listFiles(SKILLS_ROOT, keep)) { out.push(...extractInvocations(readFileSync(file, 'utf-8'), rel(file))) } return out } interface Violation { where: string; cli: string; problem: string } function check(invocations: Invocation[]): { violations: Violation[]; checked: number; unresolved: string[]; unparsed: string[] } { const violations: Violation[] = [] const unresolved = new Set() const unparsed = new Set() let checked = 0 for (const inv of invocations) { const source = resolveCliSource(inv.written, join(SKILLS_ROOT, inv.file)) if (source === null) { if (!/[<>{}…]/.test(inv.written) && !inv.written.includes('...')) unresolved.add(`${inv.written} (${inv.file}:${inv.line})`) continue } const cli = rel(source) const declared = declaredOptions(source) if (declared.style === 'unknown') { unparsed.add(cli); continue } checked++ for (const flag of inv.flags) { if (!declared.flags.has(flag)) { violations.push({ where: `${inv.file}:${inv.line}`, cli, problem: `--${flag} is not a declared option (declared: ${[...declared.flags].sort().join(', ')})` }) } } const registry = REGISTRIES[cli] if (registry && inv.rules.length > 0) { const known = new Set([...registry.implemented, ...Object.keys(registry.stubs ?? {})]) const ids = EXPANDERS[cli] ? EXPANDERS[cli](inv.rules) : inv.rules for (const id of ids) { if (!known.has(id)) violations.push({ where: `${inv.file}:${inv.line}`, cli, problem: `--rules ${id} is neither implemented nor stubbed by the pack` }) } } } return { violations, checked, unresolved: [...unresolved].sort(), unparsed: [...unparsed].sort() } } const format = (v: Violation[]): string => v.map((x) => `${x.where} → ${x.cli}: ${x.problem}`).join('\n') describe('documented CLI invocations (.md) use declared flags', () => { const invocations = collect((p) => p.endsWith('.md')) const result = check(invocations) it('finds the documented invocations (sanity: the scan is not empty)', () => { expect(invocations.length).toBeGreaterThan(150) expect(result.checked).toBeGreaterThan(120) }) it('every documented path resolves to a CLI under templates/skills', () => { expect(result.unresolved, result.unresolved.join('\n')).toEqual([]) }) it('every flag of every invocation is declared by the CLI, and every --rules id exists', () => { expect(result.violations, format(result.violations)).toEqual([]) }) it('every invoked CLI has a parser this test understands (else list it in CUSTOM_PARSERS)', () => { expect(result.unparsed, result.unparsed.join('\n')).toEqual([]) }) it('the documented ui-polish --rules invocation is checked against tokens.json (short ids expand, an unknown id would be flagged)', () => { const inv = invocations.find((i) => i.written.includes('ui-polish') && i.rules.length > 0) expect(inv, 'expected a documented ui-polish --rules invocation (documentation/steps/step-03-validate.md)').toBeDefined() const expanded = expandRuleIds(inv!.rules, UI_POLISH_RULES.implemented) expect(expanded.length).toBeGreaterThanOrEqual(inv!.rules.length) expect(expanded.every((id) => UI_POLISH_RULES.implemented.includes(id))).toBe(true) expect(expandRuleIds(['R99'], UI_POLISH_RULES.implemented)).toEqual(['R99']) expect(UI_POLISH_RULES.implemented).toContain('R7-error-banner-anatomy') }) }) describe('invocations EMITTED by CLI sources (.ts messages, nextSteps, wiring commands) use declared flags', () => { const invocations = collect((p) => p.endsWith('.ts') && !/[\\/]__tests__[\\/]/.test(p) && !p.endsWith('.test.ts')) const result = check(invocations) it('finds emitted invocations (sanity)', () => { expect(invocations.length).toBeGreaterThan(10) }) it('every emitted invocation uses declared flags (scaffold-doc → aggregate-component-registry, validate messages…)', () => { expect(result.violations, format(result.violations)).toEqual([]) expect(result.unresolved, result.unresolved.join('\n')).toEqual([]) }) })