diff --git a/CHANGELOG.md b/CHANGELOG.md index 84de6c325..71c490dd4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,9 +5,9 @@ ### Changed - **The archetype the user picks on the /design-shotgun board now reaches /design-html.** Captions name the archetype id (`classic-hero`, `long-form-story`, `minimal-conversion`, `comparison`). On approval, approved.json also gets the chosen `archetype` and its `sections` outline, added with the Edit tool so no free text reaches a shell command. /design-html reads both in Case A, and the craft section uses them as given instead of choosing again or asking. - **Accepted craft exceptions persist.** /design-html's finalized.json records each accepted `CRAFT_CHECK` finding in `craft_exceptions` (`id`, `count`, `reason`) and carries forward earlier ones. A later run passes that file with `--accepted`, so a finding the user already accepted is reported as accepted instead of flagged again. A higher count is still flagged as a new issue. -- **/design-review audits a Persuade page or a redesign for craft, or on request** (`sections/craft-audit.md`, read in Phases 1-6; design-review is now carved on every host, so the audit is one lazy section, not inlined text). It runs `bin/gstack-design-craft` on each page's served HTML (or the repo file when no app runs) and files each `CRAFT_CHECK` line as a category 9 finding (no `CRAFT_RESULT` line means the check was unavailable, and the check prints none for a framework mount-node shell, a document whose body has no visible text, exiting 2; the saved page holds only inline CSS and JS, so tells in linked files are judged by eye; it also holds session data, so it is deleted in the block that checks it, whatever the exit code, and never copied to the report; `--accepted` takes that screen's finalized.json, only for a page /design-html finalized, and never covers `needs-proof` or `lorem`; on a live page a claim without proof is filed and asked about, never marked or cut); checks what the tool cannot see (claims only from the user, realistic sample content, the ship floor, a designed 404, cookie consent where required, the motion rules); files a failed Persuade check (one primary action, named objections, proof beside each claim, a real risk reversal, an index/noindex decision) as High impact; and in Phase 8 breaks impact ties with Elaya's fix order and reads the installed Tailwind major version before touching its config. The audit's rules render from /design-html's craft section, so the build and the audit cannot drift. +- **/design-review audits a Persuade page or a redesign for craft, or on request** (`sections/craft-audit.md`, read in Phases 1-6; design-review is now carved on every host, so the audit is one lazy section, not inlined text). It runs `bin/gstack-design-craft` on each page's served HTML (or the repo file when no app runs) and files each `CRAFT_CHECK` line as a category 9 finding (no `CRAFT_RESULT` line means the check was unavailable, and a textless framework mount-node shell is judged by eye too; the saved page holds only inline CSS and JS, so tells in linked files are judged by eye; it also holds session data, so it is deleted in the block that checks it, whatever the exit code, and never copied to the report; `--accepted` takes that screen's finalized.json, only for a page /design-html finalized, and never covers `needs-proof` or `lorem`; on a live page a claim without proof is filed and asked about, never marked or cut); checks what the tool cannot see (claims only from the user, realistic sample content, the ship floor, a designed 404, cookie consent where required, the motion rules); files a failed Persuade check (one primary action, named objections, proof beside each claim, a real risk reversal, an index/noindex decision) as High impact; and in Phase 8 breaks impact ties with Elaya's fix order and reads the installed Tailwind major version before touching its config. The audit's rules render from /design-html's craft section, so the build and the audit cannot drift. - **/plan-design-review**: a Persuade plan gets the same five checks in Pass 1, Pass 4 rejects unsourced proof, and the runner checklist names them. -- **The craft tells are catalog entries.** Placeholder people and brands, lorem, round sample numbers, `[Needs proof:` markers, "Oops" errors, scroll-listener reveals, dead `href="#"`, `alert()`, images without alt, `100vh`, and a missing title, description, favicon, skip link or `aria-current` live in `lib/design-catalog.ts` as `checkerOnly` entries: one id, one short line and the fix, reported by the craft check and kept out of Methodology category 9, `review/design-checklist.md` and the consultation bullets, so no skill carries their prose. `lib/design-craft.ts` prints only catalog ids, with each entry's `fix` text; `transition: all` now reports under the existing `layout-transition` id instead of `transition-all`. Code rules skip script bodies, so a hydration payload that repeats the markup is not counted twice, and scroll listeners are read from scripts and inline handlers. +- **The craft tells are catalog entries.** Placeholder people and brands, lorem, round sample numbers, `[Needs proof:` markers, "Oops" errors, scroll-listener reveals, dead `href="#"`, `alert()`, images without alt, `100vh`, and a missing title, description, favicon, skip link or `aria-current` live in `lib/design-catalog.ts` as `checkerOnly` entries: one id, one short line and the fix, reported by the craft check and kept out of Methodology category 9, `review/design-checklist.md` and the consultation bullets, so no skill carries their prose. `lib/design-craft.ts` prints only catalog ids, with each entry's `fix` text; `transition: all` now reports under the existing `layout-transition` id instead of `transition-all`. ### Added - **`gstack-design-craft --accepted `**: a finding whose id an exception covers, at no more than the accepted count, prints `CRAFT_ACCEPTED: ` and doesn't fail the check. `CRAFT_RESULT` adds the accepted count. Exit codes: 0 when only accepted findings remain, 1 for unaccepted ones, and 2 for an unknown argument or a missing, unreadable or malformed exceptions file. A bad exceptions file still gets the full check with nothing accepted, after a warning, so a broken record can't switch the craft check off. A finalized.json with no `craft_exceptions` field, written before this change, means no exceptions. @@ -18,7 +18,7 @@ - `test/helpers/placeholder-allowlist.ts` lists `` (path grammar), the saved page the craft audit passes to the check. - The design-review and plan-design-review paid cases and judges that read a design resolver now also select on `scripts/resolvers/design-craft.ts`, and `design-html-slop-gate` on `lib/design-catalog.ts`, where the craft check's ids and fixes now live. - `test/design-craft.test.ts`: every id the check prints is a grep-detectable catalog entry, and the fix it prints is the catalog's. design-review gets its first carve guard (skeleton, union and parity ratio at their measured sizes). -- `test/design-craft.test.ts` also pins that `checkerOnly` entries reach no rendered list while the craft check prints each one, that a change to `scripts/resolvers/design-craft.ts` selects the design-review and plan-design-review cases, and the exception cells a stale record, an over-count tell without `--accepted` and a malformed record on disk reach. New `test/design-craft-resolvers.test.ts`: `{{DESIGN_CRAFT_RULE}}` renders one paragraph with its bullets and fails generation on a missing or ambiguous lead, and both skills carry the same five Persuade checks; the generated craft audit keeps its outcome, exception and session-data rules (and the design-review carve guard checks the last one). `test/design-craft.test.ts` also covers the mount-node shell exit, a hydration payload counted once, and the needs-proof and stale-record exception cells. +- `test/design-craft.test.ts` also pins that `checkerOnly` entries reach no rendered list while the craft check prints each one, that a change to `scripts/resolvers/design-craft.ts` selects the design-review and plan-design-review cases, and the exception cells a stale record, an over-count tell without `--accepted` and a malformed record on disk reach. New `test/design-craft-resolvers.test.ts`: `{{DESIGN_CRAFT_RULE}}` renders one paragraph with its bullets and fails generation on a missing or ambiguous lead, and both skills carry the same five Persuade checks; the generated craft audit keeps its outcome, exception and session-data rules (and the design-review carve guard checks the last one). `test/design-craft.test.ts` also covers the needs-proof and stale-record exception cells. ### Gate integrity - Size budgets raised to measured sizes only (Garry 2026-10-25 "Do it" (GSTA-92)): the SKILL.md pins of design-review on 12 hosts (+578 Claude, +608 on the 11 external hosts: the Section index the carve test requires and the STOP pointer) and plan-design-review on the 11 external hosts (+1,082: the five Persuade plan checks). The craft tells are catalog entries the craft check reports with their fix, kept out of every rendered skill list, so design-consultation and the review checklist do not grow. No other threshold, budget, timeout, retry or trial count changed. diff --git a/bin/gstack-design-craft b/bin/gstack-design-craft index 6ce2b9589..1c92b4483 100755 --- a/bin/gstack-design-craft +++ b/bin/gstack-design-craft @@ -10,15 +10,14 @@ // exception covers (same id, count at most the accepted count) prints as // "CRAFT_ACCEPTED: " and does not fail the check; the // result line then adds " ( accepted)" or ", accepted". -// Exit 2: usage error, a non-HTML file, an unreadable or empty file, or a -// framework mount-node shell (a document whose body has no visible text), with +// Exit 2: usage error, a non-HTML file, or an unreadable or empty file, with // nothing checked; or an unreadable or malformed exceptions file, after a // warning and a full check with no exceptions accepted, so a bad record never // hides the page's tells. Head and navigation rules run only on a full HTML // document; a fragment gets the copy and code rules alone. import { readFileSync } from 'fs'; -import { acceptFindings, checkCraft, formatCraft, parseCraftExceptions, visibleText, type CraftException } from '../lib/design-craft'; +import { acceptFindings, checkCraft, formatCraft, parseCraftExceptions, type CraftException } from '../lib/design-craft'; const USAGE = 'Usage: gstack-design-craft [--accepted ]'; const [file, flag, acceptedFile, ...rest] = process.argv.slice(2); @@ -41,11 +40,6 @@ if (source.trim() === '') { console.error(`gstack-design-craft: ${file} is empty; nothing was checked`); process.exit(2); } -const body = source.match(/ ``` -Each `CRAFT_CHECK: ` line is one category 9 finding under that id; `CRAFT_RESULT: clean` adds none; no `CRAFT_RESULT` line (framework source, including a mount-node shell, or an empty or unreadable file): say the check was unavailable for that page and judge the tells below by eye. A saved page holds only inline CSS and JS: judge `100vh`, `transition: all`, scroll listeners and `alert()` in linked files by eye. For a page /design-html finalized, add `--accepted` with that screen's finalized.json (under `$GSTACK_STATE_ROOT/projects/$SLUG/designs/`; unsure: omit it): a `CRAFT_ACCEPTED` line is a finding the user accepted, listed with its reason, not filed again, except `needs-proof` and `lorem`, which a live page cannot carry. After a `cannot use exceptions` warning, file every line. A hit real user content explains (a customer named Nexus) is noted, not filed. On a live page, never mark or cut a claim as the rules below say: file it and ask for the proof. +Each `CRAFT_CHECK: ` line is one category 9 finding under that id; `CRAFT_RESULT: clean` adds none; no `CRAFT_RESULT` line (framework source or an empty or unreadable file) or a textless mount-node shell: say the check was unavailable for that page and judge the tells below by eye. A saved page holds only inline CSS and JS: judge `100vh`, `transition: all`, scroll listeners and `alert()` in linked files by eye. For a page /design-html finalized, add `--accepted` with that screen's finalized.json (under `$GSTACK_STATE_ROOT/projects/$SLUG/designs/`; unsure: omit it): a `CRAFT_ACCEPTED` line is a finding the user accepted, listed with its reason, not filed again, except `needs-proof` and `lorem`, which a live page cannot carry. After a `cannot use exceptions` warning, file every line. A hit real user content explains (a customer named Nexus) is noted, not filed. On a live page, never mark or cut a claim as the rules below say: file it and ask for the proof. ### 2. What the check cannot see diff --git a/design-review/sections/craft-audit.md.tmpl b/design-review/sections/craft-audit.md.tmpl index c8fce1c99..741196cdb 100644 --- a/design-review/sections/craft-audit.md.tmpl +++ b/design-review/sections/craft-audit.md.tmpl @@ -10,7 +10,7 @@ With a URL, save the page in the script that opens it. Aside: add `await fs.writ {{BIN_DIR}}/gstack-design-craft ``` -Each `CRAFT_CHECK: ` line is one category 9 finding under that id; `CRAFT_RESULT: clean` adds none; no `CRAFT_RESULT` line (framework source, including a mount-node shell, or an empty or unreadable file): say the check was unavailable for that page and judge the tells below by eye. A saved page holds only inline CSS and JS: judge `100vh`, `transition: all`, scroll listeners and `alert()` in linked files by eye. For a page /design-html finalized, add `--accepted` with that screen's finalized.json (under `$GSTACK_STATE_ROOT/projects/$SLUG/designs/`; unsure: omit it): a `CRAFT_ACCEPTED` line is a finding the user accepted, listed with its reason, not filed again, except `needs-proof` and `lorem`, which a live page cannot carry. After a `cannot use exceptions` warning, file every line. A hit real user content explains (a customer named Nexus) is noted, not filed. On a live page, never mark or cut a claim as the rules below say: file it and ask for the proof. +Each `CRAFT_CHECK: ` line is one category 9 finding under that id; `CRAFT_RESULT: clean` adds none; no `CRAFT_RESULT` line (framework source or an empty or unreadable file) or a textless mount-node shell: say the check was unavailable for that page and judge the tells below by eye. A saved page holds only inline CSS and JS: judge `100vh`, `transition: all`, scroll listeners and `alert()` in linked files by eye. For a page /design-html finalized, add `--accepted` with that screen's finalized.json (under `$GSTACK_STATE_ROOT/projects/$SLUG/designs/`; unsure: omit it): a `CRAFT_ACCEPTED` line is a finding the user accepted, listed with its reason, not filed again, except `needs-proof` and `lorem`, which a live page cannot carry. After a `cannot use exceptions` warning, file every line. A hit real user content explains (a customer named Nexus) is noted, not filed. On a live page, never mark or cut a claim as the rules below say: file it and ask for the proof. ### 2. What the check cannot see diff --git a/lib/design-craft.ts b/lib/design-craft.ts index a01b58c1b..2b63f5593 100644 --- a/lib/design-craft.ts +++ b/lib/design-craft.ts @@ -1,16 +1,15 @@ /** * Free craft check for a page (/design-html Step 4, /design-review craft * audit). Counts the tells that make a page read as generated or unfinished: - * placeholder content, AI cliché copy, scroll listeners, 100vh, dead links, and the missing ship-floor - * tags. Copy checks read the visible text only (script, style and tags - * removed); code checks read the source without script bodies, so a - * hydration payload that repeats the markup is not counted twice, and script - * checks read script bodies and inline handlers. Head checks run only on a full - * document. Adapted from Elaya's landing-page-design and redesign-skill - * (MIT); see NOTICE.md. Every id it prints is a lib/design-catalog.ts entry - * and the fix beside it is that entry's `fix` (test-enforced): buzzwords - * report under `marketing-buzzword`, whose heuristic words BUZZWORDS covers, - * and `transition: all` under `layout-transition`. + * placeholder content, AI cliché copy, scroll listeners, 100vh, dead links, + * and the missing ship-floor tags. Copy checks read the visible text only + * (script, style and tags removed); code checks read the source. Head checks + * run only on a full document. Adapted from Elaya's landing-page-design and + * redesign-skill (MIT); see NOTICE.md. Every id it prints is a + * lib/design-catalog.ts entry and the fix beside it is that entry's `fix` + * (test-enforced): buzzwords report under `marketing-buzzword`, whose + * heuristic words BUZZWORDS covers, and `transition: all` under + * `layout-transition`. */ import { catalogEntries } from './design-catalog'; @@ -51,7 +50,7 @@ const COUNT_RULES: CountRule[] = [ { id: 'round-number', on: 'text', pattern: /(?]*>([\s\S]*?)<\/script>/gi), ...html.matchAll(/\bon[a-z]+\s*=\s*"([^"]*)"/gi)].map(m => m[1]).join('\n'); - const source = html.replace(/(]*>)[\s\S]*?<\/script>/gi, '$1'); const findings: CraftFinding[] = []; for (const rule of COUNT_RULES) { - const count = (rule.on === 'text' ? text : rule.on === 'script' ? scripts : source).match(rule.pattern)?.length ?? 0; + const count = (rule.on === 'text' ? text : rule.on === 'script' ? scripts : html).match(rule.pattern)?.length ?? 0; if (count > 0) findings.push(finding(rule.id, count)); } - const vh = [...source.matchAll(/\b((?:min-|max-)?height)\s*:\s*100vh\b(\s*!important)?([^}]*)/g)] + const vh = [...html.matchAll(/\b((?:min-|max-)?height)\s*:\s*100vh\b(\s*!important)?([^}]*)/g)] .filter(m => m[2] || !new RegExp(`(? 0) findings.push(finding('vh-height', vh)); if (!/ e.id === f.id).sort((a, b) => b.count - a.count)[0]; - if (!exception) issues.push(f); - else if (f.count <= exception.count) accepted.push({ id: f.id, count: f.count, reason: exception.reason }); - else issues.push({ ...f, fix: `${f.fix} (${exception.count} accepted earlier: ${exception.reason})` }); + for (const finding of findings) { + const exception = exceptions.filter(e => e.id === finding.id).sort((a, b) => b.count - a.count)[0]; + if (!exception) issues.push(finding); + else if (finding.count <= exception.count) accepted.push({ id: finding.id, count: finding.count, reason: exception.reason }); + else issues.push({ ...finding, fix: `${finding.fix} (${exception.count} accepted earlier: ${exception.reason})` }); } return { issues, accepted }; } diff --git a/test/design-craft.test.ts b/test/design-craft.test.ts index 58f1c10f7..eee77c2a2 100644 --- a/test/design-craft.test.ts +++ b/test/design-craft.test.ts @@ -124,12 +124,6 @@ describe('design-craft check', () => { expect(checkCraft('

Pricing

Start free trial
')).toEqual([]); }); - test('markup rules skip script bodies, so a hydration payload that repeats the markup counts once', () => { - const page = ''; - expect(checkCraft(page).map(f => `${f.id} ${f.count}`)).toEqual(['layout-transition 1', 'dead-link 1', 'img-no-alt 1', 'vh-height 1']); - expect(checkCraft('

Hi

').map(f => f.id)).toEqual(['scroll-listener']); - }); - test('the CLI prints CRAFT_CHECK lines and exits 1 on tells, 0 when clean, 2 on usage', () => { const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'design-craft-')); try { @@ -147,10 +141,6 @@ describe('design-craft check', () => { expect(run(path.join(dir, 'empty.html')).code).toBe(2); fs.writeFileSync(path.join(dir, 'page.tsx'), 'export const P = () =>

Acme

;'); expect(run(path.join(dir, 'page.tsx'))).toMatchObject({ code: 2, out: '' }); - fs.writeFileSync(path.join(dir, 'index.html'), 'App\n
\n\n'); - const shell = run(path.join(dir, 'index.html')); - expect(shell).toMatchObject({ code: 2, out: '' }); - expect(shell.err).toContain('mount-node shell'); } finally { fs.rmSync(dir, { recursive: true, force: true }); }