feat: build step 5 — /practice/ and six area pages; check:claims gates §4 in dist
Build and deploy / build-and-deploy (push) Failing after 5s

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
This commit is contained in:
Pouya Lajevardi
2026-08-30 09:35:26 -04:00
co-authored by Claude Opus 5
parent f3138a0a79
commit 79b19a7bd0
32 changed files with 6715 additions and 116 deletions
+538
View File
@@ -0,0 +1,538 @@
#!/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.',
);