'use strict'; /** * UI-SPEC component-inventory provenance (#2845). * * Two contracts are under test, and both are SHARED FORMATS spread across surfaces * with no generator — the `DEFECT.GENERATIVE-FIX-DIVERGENCE` class: * * 1. The gsd-ui-checker DIMENSION ROSTER, asserted independently on twelve surfaces * (eight English, four translated). Adding Dimension 7 is only correct if every * one of them moves together; a count-only guard would miss a relabel, and a * guard that reads only the real tree never executes its own failure branch. * Every parity assertion below is therefore paired with a synthetic MUTATION case. * * 2. The PROVENANCE-LINE GRAMMAR, emitted by `gsd-core/templates/UI-SPEC.md` * (`## Component Inventory`) and consumed by `agents/gsd-ui-checker.md` * (Dimension 7). One format, two surfaces, opposite directions — the two must * quote it byte-identically or the checker keys on a shape the template never emits. * * The units under test are the shipped markdown documents themselves: their text IS * what the runtime loads (CONTRIBUTING.md — `source-text-is-the-product`). Structure is * asserted on parsed, typed records (`{ n, label }`, token sets, violation objects) * rather than on raw substrings, per CONTRIBUTING.md's "Prohibited: Raw Text Matching". * * Three assertions are a deliberate exception: Dimension 7's allowlist-downgrade * wording, its not-applicable PASS clause, and the template's non-exhaustive clause are * CONTRACT PROSE — the sentence itself is the deliverable an agent reads at runtime, so * there is no typed IR to assert on instead. That is the `source-text-is-the-product` * category, not an escape from the rule. * * They carry NO `allow-test-rule` marker, deliberately and against first instinct. * `no-source-grep` only inspects reads of `.cjs`/`.cts`/`.js`/`.mjs`/`.mts`/`.ts` paths, * never `.md` ones, so a marker here suppresses nothing: it lands in the gate's * "unverified" bucket, which is ceilinged. Measured on 2026-08-21 — adding three markers * took that count 280 -> 281 and FAILED `scripts/lint-allow-test-rule-refs.cjs`. Raising * the ceiling for markers that suppress nothing is the "just bump the baseline" weakening * the ratchet exists to prevent, so the honest answer is no marker and this note. * * See https://github.com/open-gsd/gsd-core/issues/2845 */ const { describe, test } = require('node:test'); const assert = require('node:assert/strict'); const fs = require('node:fs'); const path = require('node:path'); const fc = require('fast-check'); const ROOT = path.join(__dirname, '..'); // ─── Shipped surfaces ───────────────────────────────────────────────────────── const SURFACE = Object.freeze({ CHECKER: 'agents/gsd-ui-checker.md', RESEARCHER: 'agents/gsd-ui-researcher.md', TEMPLATE: 'gsd-core/templates/UI-SPEC.md', WORKFLOW: 'gsd-core/workflows/ui-phase.md', FEATURES: 'docs/FEATURES.md', HOWTO: 'docs/how-to/design-a-ui-phase.md', FEATURES_JA: 'docs/ja-JP/FEATURES.md', HOWTO_JA: 'docs/ja-JP/how-to/design-a-ui-phase.md', FEATURES_ZH: 'docs/zh-CN/FEATURES.md', HOWTO_ZH: 'docs/zh-CN/how-to/design-a-ui-phase.md', FEATURES_KO: 'docs/ko-KR/FEATURES.md', HOWTO_KO: 'docs/ko-KR/how-to/design-a-ui-phase.md', HOWTO_PT: 'docs/pt-BR/how-to/design-a-ui-phase.md', PROBE_REFERENCE: 'gsd-core/references/ui-consideration-probe.md', }); function readShipped(rel) { const abs = path.join(ROOT, rel); return fs.existsSync(abs) ? fs.readFileSync(abs, 'utf8') : ''; } /** CRLF-normalize before any line splitting — a raw `\n` split leaves `\r` on every * captured label and makes a Windows checkout parse differently (CRLF bug class). */ const lf = (text) => String(text == null ? '' : text).replace(/\r\n/g, '\n'); // ─── Parsers → typed IR ─────────────────────────────────────────────────────── /** Line-oriented scan yielding `{ n, label }` for every line matching `re`. * `skipFenced` drops lines inside ``` blocks — a `## Dimension 8:` shown as an * EXAMPLE inside a fence is documentation, not a roster entry. The verdict block * is itself fenced, so its parser must NOT skip fences. */ function matchLines(text, re, { skipFenced = false } = {}) { const out = []; let inFence = false; for (const line of lf(text).split('\n')) { if (skipFenced && /^\s*```/.test(line)) { inFence = !inFence; continue; } if (inFence) continue; const m = re.exec(line); if (m) out.push({ n: Number(m[1]), label: m[2].trim() }); } return out; } /** `## Dimension :