Files
adr-sml/scripts/check-claims.mjs
T
Pouya LajevardiandClaude Opus 5 79b19a7bd0
Build and deploy / build-and-deploy (push) Failing after 5s
feat: build step 5 — /practice/ and six area pages; check:claims gates §4 in dist
Step 5 ships /practice/ and the six practice-area pages (construction,
technology, energy, insurance, shareholder, cross-border) from one route, and
adds the mechanical §4 gate Pouya ruled for.

check:claims — §4 Forbidden becomes a build error
  scripts/check-claims.mjs greps dist/**/*.html for 10 patterns, each carrying
  the incident that put it there. It strips <style> and non-JSON-LD <script>
  first (a bare sweep for "leading" returned 26 hits, 25 of them
  var(--leading-body)), self-tests every pattern against fixtures before
  sweeping, and refuses a missing, empty or stale dist/. Wired into /build
  Phase 5 and both deploy paths.

Q54 — six conduct undertakings publish, and §4 gains a third class
  Conduct undertakings sit apart from credentials and offerings: the gate is
  that Pouya said it in terms. The strings live in CONDUCT_UNDERTAKINGS so a
  softening is one visible diff. (e) and (f) replace the third-person sentences
  already on /arbitration/ rather than joining them.

Q49, Q50 recorded as rulings. §7 records the SES us-east-1 stray identity's
deletion. R11 holds typescript at its current major, with the peer-range
reason recorded.

Three facts corrected, two of them already shipped
  - The LAT gloss said mediation "before filing and continuing after filing";
    the Tribunal names mediation for "Before you apply" only and its second
    sentence is about negotiation. An ellipsis in docs/01 had deleted it.
  - "Connection allocation" is not an Ontario term.
  - "The 2026 privacy statute" does not exist — Bill C-27 died without royal
    assent. Struck from docs/03 rather than corrected in place.

ADR Chambers struck from /arbitration/ and from docs/01 item 3 (Pouya,
2026-08-30): the source establishes what the firm publishes, not that an
outside neutral can be appointed under its rules.

claims-auditor gains a second lens — for every quoted source, whether the
sentence beneath stays inside what the quotation establishes. Four shipped
defects had that shape and none of them is greppable.

CLAUDE.md gains a convention: never truncate the output of a check you intend
to believe. `npm run check | tail -3` returns warnings, hints and a blank line
and drops the errors line; it was reported as passing four times while
astro check was exiting 1 with 10 type errors.

Gates, exit status read directly, not through a pipe:
  npm run check        exit=0
  npm run lint         exit=0
  npm run build        exit=0
  npm run check:claims exit=0

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Md3GndFqWPzK78xAoebsg5
2026-08-30 09:35:26 -04:00

539 lines
23 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env node
/**
* `npm run check:claims` — a mechanical gate on the SHIPPED OUTPUT.
*
* Pouya's ruling, 2026-08-29, and the reasoning is the point:
*
* "Your recurring failure this project is a specific shape: you write a rule
* into a header comment, then breach it in the file below, in the same
* change set. Q53's sweep, Q54's gate four times, the D13 'cannot' phrasing
* fifty lines under your own warning about it. That is not a discipline
* problem — prose in a comment does not govern the writing that follows it.
* The pattern that actually worked was mechanical: deleting the `class` prop
* so passing one is a build error. Nobody has breached that since."
*
* So this is not documentation. It is a build error.
*
* SCOPE — `dist/**\/*.html` AND NOTHING ELSE. Not `AGENTS.md`, not `docs/`, not
* `src/`. D19 bounds review to what ships, and §4's Forbidden table says in as
* many words that it "governs published pages… It is not a word filter over the
* repository's own documentation." A register that records a forbidden phrase in
* order to forbid it must not be failed by its own quotation of it.
*
* `<style>` BLOCKS AND NON-JSON-LD `<script>` BLOCKS ARE STRIPPED FIRST, and
* that is load-bearing rather than tidy. Measured against the step-4 build
* before this file existed: a bare case-insensitive sweep for `leading` returned
* **26 hits, 25 of them `var(--leading-body)`** inside Astro's inlined critical
* CSS. A check that cries wolf on every page on its first run is a check
* somebody deletes in week two. JSON-LD is deliberately NOT stripped — a claim
* in a `<script type="application/ld+json">` is still a claim, and docs/04 says
* so: "Marking an unheld credential as held in structured data is a
* misrepresentation that happens to be machine-readable."
*
* EVERY PATTERN CARRIES ITS `incident`, PRINTED ON FAILURE. Pouya's
* instruction: "so nobody deletes one for being noisy." A pattern whose cost is
* visible and whose reason is not gets deleted; this prints the reason at the
* moment the cost is felt.
*
* AND IT SELF-TESTS BEFORE IT SWEEPS. See `FIXTURES` below. A regex that has
* quietly stopped matching passes a clean sweep forever, which is the exact
* shape of AGENTS.md Q22 — a documented control that no longer existed.
*/
import { readdirSync, readFileSync, statSync } from 'node:fs';
import { join, relative, resolve } from 'node:path';
const ROOT = resolve(import.meta.dirname, '..');
const DIST = resolve(ROOT, process.argv[2] ?? 'dist');
/* Sources whose mtime, if newer than the newest built page, means this sweep
would be reading yesterday's output. `docs/` is absent on purpose: it directs
copy but does not produce it, so a spec edit does not stale the build. */
const SOURCE_DIRS = ['src', 'public'];
const SOURCE_FILES = ['astro.config.mjs', 'package.json'];
/**
* The patterns.
*
* `re` is matched against the page's text with `<style>` and non-JSON-LD
* `<script>` removed. All are global and case-insensitive.
*
* WORD BOUNDARIES ARE NOT DECORATION ON ANY OF THESE. `\bleading\b` does not
* match "pleadings" and `\blicensed\b` does not match "licensing" — both are
* words this site publishes, and both were live false positives when the
* patterns were first drafted. Loosening a boundary here re-creates the noise
* this file was built to avoid.
*/
const PATTERNS = [
{
id: 'licensure-of-pouya',
rule: 'D13 / §4 Forbidden — the site asserts the JD and nothing further.',
incident:
'§4 opened with "Pouya is a licensed legal professional", then "a legal ' +
'professional", then an assertion that LSO marketing rules apply — three ' +
'progressively weaker forms of the same unverified claim, each surviving a ' +
'sweep meant to remove it, in the section written to stop exactly that. ' +
'§4 does not establish licence status either way.',
re: /\blawyer\b|\bcalled to the bar\b|\blicensed\b|\bpost-nominal\b/gi,
note: '"licensing" and "licence" are NOT matched — /practice/technology/ ships "IP and licensing".',
},
{
id: 'acting-for-a-party',
rule: '§4 Forbidden — implication is the risk, not just the word.',
incident:
'§4 bars "acts for clients", "represents parties", "my clients", ' +
'"my litigation practice". The site sells neutrality; a partisan verb ' +
'undercuts the central claim before it reaches the licensure question. ' +
'Settlement counsel was struck from three specs for the same reason (Q42).',
re: /\bmy litigation practice\b|\bmy law practice\b|\bmy clients?\b|\bacts for\b|\bI act for\b|\brepresents?\b/gi,
note:
'"I do not act for a party" is the APPROVED sentence (/mediation/) and does ' +
'not match. THE SINGULAR WAS ADDED 2026-08-29: the pattern read ' +
'`my clients` and `/med-arb/` was shipping "The neutral who heard my ' +
'client in caucus" in its FAQ — the possessive belonged to the counsel ' +
'voicing the objection, not to Pouya, but the gate cannot tell and the ' +
'phrase is forbidden in his voice, so the copy changed to "our client" ' +
'and the pattern widened. Same shape as the capacity-phrasing hole: a ' +
'pattern narrow enough to miss the real form of the breach.',
},
{
id: 'capacity-phrasing',
rule: 'docs/03 — when a fact is [unestablished], write around the capacity question.',
incident:
'Attempt 1 "I do not give legal advice" was flagged as an ELECTION ' +
'(entitlement withheld by choice). Attempt 2 "I cannot give legal advice" ' +
'was flagged as a DENIAL of capacity. Both audits were right: they are ' +
'opposite answers to a question §4 holds neither side of. The shipped ' +
'sentence makes no capacity claim at all.',
re: /\b(cannot|can ?not|can't|do(es)? not|don't|will not|won't|unable to|not (qualified|permitted|able) to)\s+(give|provide|offer)\s+legal advice\b|\bI\s+(?:cannot|can ?not|can't|do not|don't|will not|won't)\b[^.<]{0,25}\badvis(?:e|ing)\b/gi,
note:
'THE SECOND ALTERNATIVE WAS ADDED 2026-08-29 BECAUSE THE FIRST MISSED A REAL ' +
'BREACH. `/practice/cross-cultural/` shipped "What I do not do is advise on the ' +
'law of another country" — the election form, narrowed by jurisdiction, which ' +
'makes it worse rather than better: naming only foreign law invites the reader ' +
'to supply the domestic half. `claims-auditor` caught it and noted, correctly, ' +
'that this pattern could not, because it required the literal object "legal ' +
'advice". The verb list is deliberately tight — give / provide / offer / advise ' +
'/ render — and the window is 25 characters, so the six approved first-person ' +
'negations this site ships ("I do not act for a party", "I do not run a case", ' +
'"I will not run a process", "I do not carry a number across the hall", "I will ' +
'not convert a mediation", "If I cannot decide a remaining issue") all stay ' +
'silent. Every one of them is in the mustNotMatch fixtures below.',
},
{
id: 'designation-name',
rule: "ADRIO's own form is Chartered Med-Arbitrator.",
incident:
'"Chartered Mediator-Arbitrator" is not the name of anything ADRIC or ' +
'ADRIO confers. docs/03 §About names the correct form and the source is ' +
'docs/reference/adrio-designations.md. A designation stated in a form its ' +
'own institution does not use reads as a designation not held.',
re: /Chartered Mediator[- ]Arbitrator/gi,
},
{
id: 'rule-set-name',
rule: 'ADRIC publishes the ADRIC National Mediation Rules.',
incident:
'`docs/01` §/mediation/ said "Model Mediation Rules", which is not the ' +
'name of anything ADRIC publishes — "Model" belongs to the Model Dispute ' +
'Resolution Clause INSIDE the rules. Caught at build step 4 only because ' +
'the rules were fetched rather than named from recall (R14).',
re: /Model Mediation Rules/gi,
},
{
id: 'counts-and-tenure',
rule: '§4 Forbidden — no count of matters, hours mediated, or years in practice.',
incident:
'The site this replaces carried "Since 2009", "sixteen years", London and ' +
'New York offices and a company number, all artefacts of a purchased ' +
'template and all false. §4: the practice is new, and small true numbers ' +
'do not persuade a sophisticated GC — they invite scrutiny.',
re: /\b\d[\d,]*\s*\+?\s*(matters|mediations|arbitrations|appointments|awards)\b|\b\d[\d,]*\s+years\s+in\b|\bsince\s+(19|20)\d{2}\b|\b(ten|eleven|twelve|thirteen|fourteen|fifteen|sixteen|seventeen|eighteen|nineteen|twenty|thirty|forty)[\s-]years\b/gi,
},
{
id: 'superlatives',
rule: '§4 Forbidden — unverifiable as written, and they read as insecure.',
incident:
'The reason §4 gives is editorial before it is regulatory: this audience ' +
'discounts everything after a superlative. AND THE FALSE POSITIVES ARE ' +
'REAL AND KNOWN — "pleadings" contains "leading" and every inlined ' +
'stylesheet contains var(--leading-body). Both are handled, by the word ' +
'boundary and by the <style> strip. Do not loosen the boundary and do not ' +
'delete this pattern because it once matched a token name.',
re: /\bleading\b|\bpremier\b|\btop[- ]rated\b|\bbest\b|\bworld[- ]class\b|\bunrivalled\b|\bunparalleled\b/gi,
},
/* --- Beyond Pouya's minimum list, 2026-08-29. Each names a real incident in
this repository and each has near-zero false-positive surface on this
site's vocabulary. Strike any of the three and the rest still stand. --- */
{
id: 'fabricated-founder',
rule: '§4 Forbidden — the fictitious founder from the template.',
incident:
'The site this replaces carried a founder who does not exist, a ' +
'testimonial attributed to a person who does not exist, invented matter ' +
'values and fabricated office locations. This register exists because of ' +
'that, and this is the one pattern whose match would be unambiguous.',
re: /\bS\.?\s?M\.?\s+Lawrence\b|\b07452218\b/gi,
},
{
id: 'q-arb-as-held',
rule: '§4 Forbidden — Q.Arb must never read as held, imminent, or nearly complete.',
incident:
'It commenced August 2026. The recurring drift is one word: docs/03 said ' +
'the Q.Arb "designation commenced in August 2026" — which reads as in ' +
'force since then, i.e. HELD — in copy an implementer is told to lift ' +
'verbatim. Found by claims-auditor 2026-08-28. §4 requires "pathway".',
re: /Q\.?\s?Arb\s+designation\b|\b(holds?|holding|awarded|conferred|earned|achieved)\s+(the\s+|a\s+)?Q\.?\s?Arb\b|Q\.?\s?Arb[^.<]{0,60}\b(nearly|almost) complete\b|Q\.?\s?Arb[^.<]{0,60}\bin progress\b/gi,
},
{
id: 'struck-universal-q39',
rule: 'Q39 — the class statement about arbitral gating was FALSE and was published once.',
incident:
'§4 carried "Anyone may be appointed an arbitrator in Ontario. Nothing in ' +
'law gates the role behind a designation." Pouya checked the ' +
'counter-example and confirmed it: "My assertion was FALSE as a ' +
'universal." Ontario family arbitrators must complete prescribed ' +
'training. The scoped replacement is COMMERCIAL arbitration and it is ' +
"Pouya's attributed position, not a verified fact. Swept four times; " +
'reached a public page once.',
re: /anyone (may|can) be appointed an arbitrator|nothing in law gates|no (licence|license|designation) is (required|needed) to (be appointed|arbitrate|act as an arbitrator)/gi,
},
];
/**
* THE INSTRUMENT CHECK, and it runs before every sweep.
*
* `mustMatch` — a pattern that has stopped firing passes a clean sweep forever
* and looks identical to a clean site. `mustNotMatch` — the known-legitimate
* strings this site actually publishes; if one of them starts failing, the
* pattern got looser, not the copy worse.
*
* CLAUDE.md, five times over: "a measurement is a claim about your instrument
* until you check the instrument."
*/
const FIXTURES = {
mustMatch: {
'licensure-of-pouya': [
'He is a lawyer.',
'called to the bar',
'a licensed neutral',
],
'acting-for-a-party': [
'my litigation practice',
'acts for clients',
'I act for the claimant',
'represents parties',
'my clients',
/* The singular, which the pattern missed until 2026-08-29. */
'the neutral who heard my client in caucus',
],
'capacity-phrasing': [
'I cannot give legal advice',
'I do not give legal advice',
"I can't provide legal advice",
/* The real breach the first form of this pattern could not catch. */
'What I do not do is advise on the law of another country',
'I do not advise on foreign law',
],
'designation-name': ['Chartered Mediator-Arbitrator'],
'rule-set-name': ['the Model Mediation Rules'],
'counts-and-tenure': [
'40 matters',
'120 mediations',
'16 years in ADR',
'Since 2009',
'sixteen years',
],
superlatives: [
'a leading neutral',
'premier',
'top-rated',
'the best mediator',
],
'fabricated-founder': ['S. M. Lawrence', 'Co. № 07452218'],
'q-arb-as-held': [
'the Q.Arb designation',
'holds Q.Arb',
'Q.Arb is nearly complete',
'Q.Arb, in progress',
],
'struck-universal-q39': [
'Anyone may be appointed an arbitrator in Ontario',
'nothing in law gates the role',
],
},
/* Every one of these is real published or spec-approved copy on this site. */
mustNotMatch: [
'I act as a neutral. I do not act for a party in a matter I take, and each party should have their own legal advice.',
'I run a process, I do not run a case for anybody in it.',
'I will not run a process whose shape nobody agreed to in advance.',
'I do not carry a number across the hall that I was not given to carry.',
'I will not convert a mediation into an arbitration on the day.',
'Commercial matters. I do not accept family arbitration.',
/* An OFFERING statement, not a capacity claim. The capacity pattern carried
`offer` for one pass and fired on this. */
'I do not offer family arbitration.',
/* Counsel's voice in /med-arb/'s FAQ, after the possessive was changed. */
'The neutral who heard our client in caucus then decides the case.',
/* Undertaking (c) — the nearest miss on the widened capacity pattern. */
'If I cannot decide a remaining issue without relying on something said to me in confidence, I say so and step out of the arbitral phase rather than decide on it.',
'Pleadings, disclosure, witnesses, experts, oral argument.',
/* Wrapped, because the wrapper IS what is being tested. The first draft of
this fixture was the bare declaration and the self-test failed on it —
correctly: an unwrapped token name is not something the strip can reach,
so the fixture was asserting the wrong thing. 25 of the 26 real hits were
inside <style>. */
'<style>.hero-lede{line-height:var(--leading-body);color:var(--text-secondary)}</style>',
'Software contracts, SLA and MSA breakdowns, data residency and processing, AI vendor diligence, IP and licensing.',
'The Q.Arb pathway commenced in August 2026; C.Med-Arb is the endpoint.',
'Chartered Med-Arbitrator',
'The ADRIC National Mediation Rules.',
'adopted a new edition effective 1 March 2025',
'Commercial arbitration in Ontario. I do not accept family arbitration.',
'Published as typical, not as a guarantee.',
'Liens, delay and change-order claims, scheduling, subcontract and deficiency disputes.',
],
};
/* --- machinery ----------------------------------------------------------- */
/* `relative()` escapes the root with `../../..` when the optional path argument
points outside the repo (which only a test run does). Show the plain absolute
path in that case — an unreadable path in a failure report is one more thing
between a reader and the match. */
function rel(p) {
const r = relative(ROOT, p);
return r && !r.startsWith('..') ? r : p;
}
function walk(dir) {
const out = [];
for (const entry of readdirSync(dir, { withFileTypes: true })) {
const full = join(dir, entry.name);
if (entry.isDirectory()) out.push(...walk(full));
else if (entry.name.endsWith('.html')) out.push(full);
}
return out;
}
function newestMtime(paths) {
let newest = 0;
let which = null;
const visit = (p) => {
const s = statSync(p);
if (s.isDirectory()) {
for (const e of readdirSync(p)) visit(join(p, e));
} else if (s.mtimeMs > newest) {
newest = s.mtimeMs;
which = p;
}
};
for (const p of paths) visit(p);
return { newest, which };
}
/**
* Strip what is not published prose or published data.
*
* `<style>` goes because Astro inlines critical CSS into every page and the
* token names collide with the superlative pattern. Non-JSON-LD `<script>` goes
* because a third-party analytics snippet is somebody else's vocabulary — there
* is none today (D15's Plausible is one line and not yet added) and there will
* be. `ld+json` STAYS: it is claim-bearing.
*
* Attributes are NOT stripped, deliberately. `<title>`, `<meta name=
* "description">`, `og:*` and image `alt` text are published copy and are among
* the places a claim is least likely to be re-read.
*/
function publishedText(html) {
return (
html
.replace(/<style\b[^>]*>[\s\S]*?<\/style>/gi, ' ')
.replace(
/<script\b(?![^>]*application\/ld\+json)[^>]*>[\s\S]*?<\/script>/gi,
' ',
)
/* Smart punctuation, so `cant` is matched by a pattern written `can't`.
Astro emits whatever the source used and the source uses curly quotes. */
.replace(/[‘’]/g, "'")
.replace(/[“”]/g, '"')
.replace(/&#(?:39|x27);/gi, "'")
.replace(/&amp;/gi, '&')
.replace(/&nbsp;|&#160;/gi, ' ')
);
}
function contextAt(text, index, length) {
const from = Math.max(0, index - 55);
const to = Math.min(text.length, index + length + 55);
const snip = text.slice(from, to).replace(/\s+/g, ' ');
return `${from > 0 ? '…' : ''}${snip}${to < text.length ? '…' : ''}`;
}
function lineOf(text, index) {
let line = 1;
for (let i = 0; i < index; i++) if (text.charCodeAt(i) === 10) line++;
return line;
}
function scan(text, re) {
const hits = [];
re.lastIndex = 0;
let m;
while ((m = re.exec(text)) !== null) {
hits.push({ match: m[0], index: m.index });
if (m[0].length === 0) re.lastIndex++;
}
return hits;
}
function selfTest() {
const failures = [];
for (const p of PATTERNS) {
for (const positive of FIXTURES.mustMatch[p.id] ?? []) {
if (scan(positive, p.re).length === 0) {
failures.push(
`${p.id}: STOPPED MATCHING its own fixture — ${JSON.stringify(positive)}`,
);
}
}
if (!(p.id in FIXTURES.mustMatch)) {
failures.push(
`${p.id}: has no fixture. Every pattern proves it still fires.`,
);
}
for (const negative of FIXTURES.mustNotMatch) {
const hits = scan(publishedText(negative), p.re);
if (hits.length > 0) {
failures.push(
`${p.id}: matched APPROVED copy — ${JSON.stringify(hits[0].match)} in ${JSON.stringify(negative)}`,
);
}
}
}
return failures;
}
/* --- run ----------------------------------------------------------------- */
let failed = false;
console.log(
'check:claims — AGENTS.md §4 Forbidden, enforced on shipped output',
);
console.log(` target: ${rel(DIST)}/**/*.html\n`);
// 1. The instrument, before the measurement.
const selfTestFailures = selfTest();
if (selfTestFailures.length > 0) {
console.error(
'SELF-TEST FAILED — the patterns cannot be trusted, so nothing was swept.\n',
);
for (const f of selfTestFailures) console.error(` ${f}`);
console.error(
'\nA pattern that no longer fires passes a clean sweep forever.',
);
process.exit(2);
}
console.log(
` self-test: ${PATTERNS.length} patterns fire on their fixtures and stay silent on ${FIXTURES.mustNotMatch.length} approved strings`,
);
// 2. The target has to exist and have content in it. An empty sweep is not a
// pass — CLAUDE.md: "a command that did not run is not evidence of absence."
let pages;
try {
pages = walk(DIST);
} catch (err) {
console.error(`\nFAILED: cannot read ${DIST}${err.message}`);
console.error(
'Run `npm run build` first. This checks the built output, not the source.',
);
process.exit(2);
}
if (pages.length === 0) {
console.error(`\nFAILED: no HTML under ${DIST}. Nothing was checked.`);
console.error(
'An empty sweep reads exactly like a clean one. Run `npm run build`.',
);
process.exit(2);
}
// 3. Staleness. A pass against yesterday's dist is the same false negative in
// slower motion.
const newestPage = newestMtime(pages);
const newestSource = newestMtime([
...SOURCE_DIRS.map((d) => join(ROOT, d)),
...SOURCE_FILES.map((f) => join(ROOT, f)),
]);
if (newestSource.newest > newestPage.newest) {
console.error(`\nFAILED: ${rel(DIST)} is older than the source.`);
console.error(` newest source: ${rel(newestSource.which)}`);
console.error(` newest page: ${rel(newestPage.which)}`);
console.error(
'\nThis would have checked a build that does not include your change.',
);
console.error('Run `npm run build`, then this again.');
process.exit(2);
}
console.log(` pages: ${pages.length}\n`);
for (const p of PATTERNS) {
const hits = [];
for (const page of pages) {
const text = publishedText(readFileSync(page, 'utf8'));
for (const hit of scan(text, p.re)) {
hits.push({
page: rel(page),
line: lineOf(text, hit.index),
match: hit.match,
context: contextAt(text, hit.index, hit.match.length),
});
}
}
if (hits.length === 0) {
console.log(` ok ${p.id}`);
continue;
}
failed = true;
console.error(
`\n FAIL ${p.id}${hits.length} match${hits.length === 1 ? '' : 'es'}`,
);
console.error(` rule: ${p.rule}`);
console.error(` incident: ${p.incident}`);
if (p.note) console.error(` note: ${p.note}`);
console.error('');
for (const hit of hits) {
console.error(
` ${hit.page}:${hit.line} ${JSON.stringify(hit.match)}`,
);
console.error(` ${hit.context}`);
}
}
if (failed) {
console.error('\n' + '-'.repeat(72));
console.error(
'A match is not yet a finding — read the context printed above before acting.',
);
console.error(
'This site has had a superlative sweep hit "pleadings" and a case-insensitive',
);
console.error(
'sweep for LSO hit "I aLSO practise". Read what matched, then either fix the',
);
console.error(
'copy or change this file DELIBERATELY, with an AGENTS.md Change Log entry.',
);
console.error('Do not delete a pattern to make a build pass.');
process.exit(1);
}
console.log(
'\nClean. Every pattern ran and every pattern is still firing on its fixture.',
);