diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c1b60da5..87f5bda01 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ ### 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** (`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; 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; the saved page holds session data, so it is deleted in the block that checks it and never copied to the report; `--accepted` applies only to a page /design-html finalized, and never to `needs-proof` or `lorem`; a live page never gets a `[Needs proof: …]` marker); 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`. @@ -18,9 +18,10 @@ - `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. ### 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 13 hosts (+584 Claude, +614 external: the Section index the carve test requires and the STOP pointer) and plan-design-review on 12 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. +- 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. ## [1.91.226.0] - 2026-10-26 diff --git a/design-review/SKILL.md b/design-review/SKILL.md index dfc685e49..b13fc14a0 100644 --- a/design-review/SKILL.md +++ b/design-review/SKILL.md @@ -1573,9 +1573,9 @@ sections. Read a section in full before doing its step; do not work from memory. | When | Read this section | |------|-------------------| -| auditing a Persuade page or a redesign | `sections/craft-audit.md` | +| a craft, Persuade or redesign audit | `sections/craft-audit.md` | -> **STOP.** Before auditing a Persuade page or a redesign, Read `~/.claude/skills/gstack/design-review/sections/craft-audit.md` and execute it +> **STOP.** Before a craft, Persuade or redesign audit, Read `~/.claude/skills/gstack/design-review/sections/craft-audit.md` and execute it > in full. Do not work from memory — that section is the source of truth for this step. Record baseline design score and AI slop score at end of Phase 6. diff --git a/design-review/sections/craft-audit.md b/design-review/sections/craft-audit.md index e14d2d1a2..683b92e4b 100644 --- a/design-review/sections/craft-audit.md +++ b/design-review/sections/craft-audit.md @@ -2,17 +2,17 @@ ## Craft audit: Persuade pages and redesigns -Run this in Phases 1-6, on every audited page, when a page is Persuade (the classifier in the Design Hard Rules) or the user asked for a redesign or a craft audit. It adds what generated sites ship and a reviewer catches in seconds: invented content, missing ship basics, scroll-listener reveals, `100vh`, and a Persuade page's conversion basics. Its rules render from /design-html's craft section, so a page is audited against the rules it was built to. +Run this in Phases 1-6, on every audited page, when a page is Persuade (the classifier in the Design Hard Rules) or the user asked for a redesign or a craft audit. ### 1. Run the craft check on each page -With a URL, save the page in the script that opens it. Aside: add `await fs.writeFile(path.join(pwd, "{page}.page.html"), await pg.content());`. Fallback engine: `_TMP=$(mktemp -d "${TMPDIR:-/tmp}/gstack-craft.XXXXXX"); $B js 'document.documentElement.outerHTML' --out "$_TMP/{page}.page.html" --raw`. With no running app, use the page's HTML file in the repo. Then: +With a URL, save the page in the script that opens it. Aside: add `await fs.writeFile(path.join(pwd, "{page}.page.html"), await pg.evaluate(() => document.documentElement.outerHTML));`. Fallback engine: `_TMP=$(mktemp -d "${TMPDIR:-/tmp}/gstack-craft.XXXXXX"); $B js 'document.documentElement.outerHTML' --out "$_TMP/{page}.page.html" --raw`. With no running app, use the page's HTML file in the repo. A saved page holds session data: `rm -f` it in the block that checks it, and never copy it to the report directory. ```bash ~/.claude/skills/gstack/bin/gstack-design-craft ``` -Each `CRAFT_CHECK: ` line is one category 9 finding under that id; `CRAFT_RESULT: clean` adds none. For a /design-html page with a `finalized.json` beside it, add `--accepted `: each `CRAFT_ACCEPTED` line is a finding the user already accepted, listed with its reason, not filed again. Exit 2 (framework source, an empty or unreadable file): say the check was unavailable for that page and judge the tells below by eye. A hit the user's real content explains (a customer named Nexus, a supplied 50% figure) is noted, not filed. Saved pages stay out of the report directory; delete them after the check. +Each `CRAFT_CHECK: ` line is one category 9 finding under that id; `CRAFT_RESULT: clean` adds none; no `CRAFT_RESULT` line (framework source, an empty or unreadable file): say the check was unavailable for that page and judge the tells below by eye. For a page /design-html finalized, add `--accepted ` (under `$GSTACK_STATE_ROOT/projects/$SLUG/designs/`): 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 the user's real content explains (a customer named Nexus, a supplied 50% figure) is noted, not filed. Never add a `[Needs proof: …]` marker to a live page: file the claim, then cut it or ask. ### 2. What the check cannot see @@ -56,17 +56,8 @@ A failed check costs the page its one job: file it as a High-impact finding. ### 4. Fix order and stack (Phase 8) -Within one impact level, take the biggest visible gain at the least risk first: -1. Typeface and type roles -2. Color and surfaces -3. Hover, active and focus states -4. Layout and spacing: grid, max width, the spacing scale, nested radius -5. Motion -6. Generic components -7. Loading, empty and error states -8. Copy -9. Type-scale polish +Within one impact level, take the biggest visible gain at the least risk first: typeface and type roles; color and surfaces; hover, active and focus states; layout and spacing (grid, max width, spacing scale, nested radius); motion; generic components; loading, empty and error states; copy; type-scale polish. -Work inside the existing stack: no framework or styling migration, and check the dependency file before importing a library. If the project uses Tailwind, read the installed major version (`tailwindcss` in package.json or the lockfile) before touching its config: v3 configures in `tailwind.config.*`, v4 in CSS (`@import "tailwindcss"` and `@theme`). Write for that version only. +Work inside the existing stack: no framework or styling migration, and check the dependency file before importing a library. With Tailwind, read the installed major version (`tailwindcss` in package.json or the lockfile) before touching its config: v3 configures in `tailwind.config.*`, v4 in CSS (`@import "tailwindcss"` and `@theme`). Write for that version only. Adapted from Elaya's [redesign-skill](https://github.com/elayadesign/redesign-skill) and [ai-design-skills](https://github.com/elayadesign/ai-design-skills) (MIT, © 2026 Elaya), rewritten for gstack. See NOTICE.md. diff --git a/design-review/sections/craft-audit.md.tmpl b/design-review/sections/craft-audit.md.tmpl index da781318e..6d1621bc4 100644 --- a/design-review/sections/craft-audit.md.tmpl +++ b/design-review/sections/craft-audit.md.tmpl @@ -1,16 +1,16 @@ ## Craft audit: Persuade pages and redesigns -Run this in Phases 1-6, on every audited page, when a page is Persuade (the classifier in the Design Hard Rules) or the user asked for a redesign or a craft audit. It adds what generated sites ship and a reviewer catches in seconds: invented content, missing ship basics, scroll-listener reveals, `100vh`, and a Persuade page's conversion basics. Its rules render from /design-html's craft section, so a page is audited against the rules it was built to. +Run this in Phases 1-6, on every audited page, when a page is Persuade (the classifier in the Design Hard Rules) or the user asked for a redesign or a craft audit. ### 1. Run the craft check on each page -With a URL, save the page in the script that opens it. Aside: add `await fs.writeFile(path.join(pwd, "{page}.page.html"), await pg.content());`. Fallback engine: `_TMP=$(mktemp -d "${TMPDIR:-/tmp}/gstack-craft.XXXXXX"); $B js 'document.documentElement.outerHTML' --out "$_TMP/{page}.page.html" --raw`. With no running app, use the page's HTML file in the repo. Then: +With a URL, save the page in the script that opens it. Aside: add `await fs.writeFile(path.join(pwd, "{page}.page.html"), await pg.evaluate(() => document.documentElement.outerHTML));`. Fallback engine: `_TMP=$(mktemp -d "${TMPDIR:-/tmp}/gstack-craft.XXXXXX"); $B js 'document.documentElement.outerHTML' --out "$_TMP/{page}.page.html" --raw`. With no running app, use the page's HTML file in the repo. A saved page holds session data: `rm -f` it in the block that checks it, and never copy it to the report directory. ```bash {{BIN_DIR}}/gstack-design-craft ``` -Each `CRAFT_CHECK: ` line is one category 9 finding under that id; `CRAFT_RESULT: clean` adds none. For a /design-html page with a `finalized.json` beside it, add `--accepted `: each `CRAFT_ACCEPTED` line is a finding the user already accepted, listed with its reason, not filed again. Exit 2 (framework source, an empty or unreadable file): say the check was unavailable for that page and judge the tells below by eye. A hit the user's real content explains (a customer named Nexus, a supplied 50% figure) is noted, not filed. Saved pages stay out of the report directory; delete them after the check. +Each `CRAFT_CHECK: ` line is one category 9 finding under that id; `CRAFT_RESULT: clean` adds none; no `CRAFT_RESULT` line (framework source, an empty or unreadable file): say the check was unavailable for that page and judge the tells below by eye. For a page /design-html finalized, add `--accepted ` (under `$GSTACK_STATE_ROOT/projects/$SLUG/designs/`): 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 the user's real content explains (a customer named Nexus, a supplied 50% figure) is noted, not filed. Never add a `[Needs proof: …]` marker to a live page: file the claim, then cut it or ask. ### 2. What the check cannot see @@ -34,17 +34,8 @@ A failed check costs the page its one job: file it as a High-impact finding. ### 4. Fix order and stack (Phase 8) -Within one impact level, take the biggest visible gain at the least risk first: -1. Typeface and type roles -2. Color and surfaces -3. Hover, active and focus states -4. Layout and spacing: grid, max width, the spacing scale, nested radius -5. Motion -6. Generic components -7. Loading, empty and error states -8. Copy -9. Type-scale polish +Within one impact level, take the biggest visible gain at the least risk first: typeface and type roles; color and surfaces; hover, active and focus states; layout and spacing (grid, max width, spacing scale, nested radius); motion; generic components; loading, empty and error states; copy; type-scale polish. -Work inside the existing stack: no framework or styling migration, and check the dependency file before importing a library. If the project uses Tailwind, read the installed major version (`tailwindcss` in package.json or the lockfile) before touching its config: v3 configures in `tailwind.config.*`, v4 in CSS (`@import "tailwindcss"` and `@theme`). Write for that version only. +Work inside the existing stack: no framework or styling migration, and check the dependency file before importing a library. With Tailwind, read the installed major version (`tailwindcss` in package.json or the lockfile) before touching its config: v3 configures in `tailwind.config.*`, v4 in CSS (`@import "tailwindcss"` and `@theme`). Write for that version only. Adapted from Elaya's [redesign-skill](https://github.com/elayadesign/redesign-skill) and [ai-design-skills](https://github.com/elayadesign/ai-design-skills) (MIT, © 2026 Elaya), rewritten for gstack. See NOTICE.md. diff --git a/design-review/sections/manifest.json b/design-review/sections/manifest.json index f3d97220d..22ef3efb8 100644 --- a/design-review/sections/manifest.json +++ b/design-review/sections/manifest.json @@ -8,7 +8,7 @@ "id": "craft-audit", "file": "craft-audit.md", "title": "Craft audit: Persuade pages and redesigns", - "trigger": "auditing a Persuade page or a redesign" + "trigger": "a craft, Persuade or redesign audit" } ] } diff --git a/scripts/resolvers/design-checklist.ts b/scripts/resolvers/design-checklist.ts index a343cebc6..e32f8ee11 100644 --- a/scripts/resolvers/design-checklist.ts +++ b/scripts/resolvers/design-checklist.ts @@ -12,7 +12,7 @@ * * Derived in part from pbakaus/impeccable (Apache-2.0), modified. See NOTICE.md. */ -import { DESIGN_SLOP_CATALOG, RENDERED, BANNED_FONTS, type DesignSlopEntry } from '../../lib/design-catalog'; +import { RENDERED, BANNED_FONTS, type DesignSlopEntry } from '../../lib/design-catalog'; import { SENTINEL, DETECT_EXIT_ECHO } from '../../lib/design-detect-contract'; export const DESIGN_CHECKLIST_HEADER = @@ -34,7 +34,7 @@ export function checklistSlopEntries(): DesignSlopEntry[] { /** Catalog rules a review may fix without asking: mechanical CSS changes with HIGH confidence. */ export function autoFixEntries(): DesignSlopEntry[] { - return DESIGN_SLOP_CATALOG.filter(e => e.tier === 'auto-fix'); + return RENDERED.filter(e => e.tier === 'auto-fix'); } function endsWithPunctuation(s: string): boolean { diff --git a/scripts/resolvers/design-craft.ts b/scripts/resolvers/design-craft.ts index 22bdf5a8f..3bd24deb0 100644 --- a/scripts/resolvers/design-craft.ts +++ b/scripts/resolvers/design-craft.ts @@ -19,9 +19,9 @@ const CRAFT_SECTION = path.resolve(import.meta.dir, '../../design-html/sections/ export const DESIGN_CRAFT_RULE: ResolverFn = (_ctx, args) => { const lead = args?.join(':'); if (!lead) throw new Error('{{DESIGN_CRAFT_RULE:}} requires the bold lead of a craft paragraph'); - const paragraph = fs.readFileSync(CRAFT_SECTION, 'utf-8').split(/\n{2,}/).find(p => p.startsWith(`**${lead}`)); - if (!paragraph) throw new Error(`{{DESIGN_CRAFT_RULE:${lead}}}: no paragraph starting "**${lead}" in design-html/sections/craft.md.tmpl`); - return paragraph.trimEnd(); + const matches = fs.readFileSync(CRAFT_SECTION, 'utf-8').split(/\n{2,}/).filter(p => p.startsWith(`**${lead}.**`) || p.startsWith(`**${lead},`)); + if (matches.length !== 1) throw new Error(`{{DESIGN_CRAFT_RULE:${lead}}}: ${matches.length} paragraphs starting "**${lead}.**" or "**${lead}," in design-html/sections/craft.md.tmpl; need exactly one`); + return matches[0].trimEnd(); }; export const DESIGN_PERSUADE_CHECKS: ResolverFn = () => `1. **One primary action**, named; nothing competes with it above the fold, and the closing CTA repeats it. diff --git a/scripts/resolvers/sections.ts b/scripts/resolvers/sections.ts index 38a274482..a210b8e8a 100644 --- a/scripts/resolvers/sections.ts +++ b/scripts/resolvers/sections.ts @@ -25,8 +25,9 @@ const ROOT = path.resolve(import.meta.dir, '..', '..'); export const QA_ASSET_BLOCKER = 'If missing or unreadable, report a QA setup blocker and its affected probes as blocked; continue other safe probes (independent functional/static checks). Missing/unreadable assets block required QA.'; -/** Skills carved on every host: QA (portable assets) and those whose external - * render would exceed SKILL_BYTE_CEILING inlined (C4, #2777). */ +/** Skills carved on every host: QA (portable assets), those whose external + * render would exceed SKILL_BYTE_CEILING inlined (C4, #2777), and design-review, + * whose on-demand craft audit stays out of every skeleton (GSTA-92). */ const CARVED_ON_EVERY_HOST = ['qa', 'qa-only', 'ship', 'plan-ceo-review', 'office-hours', 'design-review']; /** Hosts read at most this many UTF-8 bytes of one SKILL.md (~40K tokens). */ diff --git a/test/design-craft-resolvers.test.ts b/test/design-craft-resolvers.test.ts new file mode 100644 index 000000000..2386f9f49 --- /dev/null +++ b/test/design-craft-resolvers.test.ts @@ -0,0 +1,38 @@ +/** + * Craft resolvers (scripts/resolvers/design-craft.ts) shared by /design-review's + * craft audit and /plan-design-review. + * + * Value: protects=DESIGN_CRAFT_RULE returns a craft paragraph with its bullets and fails generation on a missing or renamed lead; fails_when=the lookup returns '' or the first paragraph instead of throwing, or drops bullets; why_new=only the freshness golden exercises the resolver, and no test reaches its throw paths; seam=none; no_claim=agent use of the rendered rules (behavioral, no paid case) + * Value: protects=design-review and plan-design-review render the same five Persuade checks; fails_when=either surface drops or hand-forks the {{DESIGN_PERSUADE_CHECKS}} block; why_new=carve-guards checks one phrase in design-review only, nothing checks plan-design-review; seam=none; no_claim=that an agent applies the checks (eval) + */ +import { describe, expect, test } from 'bun:test'; +import * as fs from 'fs'; +import * as path from 'path'; +import { DESIGN_CRAFT_RULE, DESIGN_PERSUADE_CHECKS } from '../scripts/resolvers/design-craft'; +import type { TemplateContext } from '../scripts/resolvers/types'; + +const ROOT = path.resolve(import.meta.dir, '..'); +const read = (...p: string[]) => fs.readFileSync(path.join(ROOT, ...p), 'utf-8'); +const ctx = { skillName: 'design-review', tmplPath: 'design-review/sections/craft-audit.md.tmpl' } as TemplateContext; + +describe('craft resolvers', () => { + test('DESIGN_CRAFT_RULE renders the craft paragraph with its bullets; a missing or unknown lead fails generation', () => { + const motion = DESIGN_CRAFT_RULE(ctx, ['Motion']); + expect(motion).toStartWith('**Motion.**'); + expect(motion).toContain("Never an unthrottled `window.addEventListener('scroll')`"); + expect(motion).toContain('`min-height: 100dvh`, not `100vh`.'); + expect(motion).not.toContain('\n\n'); + expect(DESIGN_CRAFT_RULE(ctx, ['Claims come from the user'])).toStartWith('**Claims come from the user, never from you.**'); + expect(read('design-html', 'sections', 'craft.md')).toContain(motion); + expect(() => DESIGN_CRAFT_RULE(ctx, ['No such lead'])).toThrow('No such lead'); + expect(() => DESIGN_CRAFT_RULE(ctx, [])).toThrow('requires the bold lead'); + expect(() => DESIGN_CRAFT_RULE(ctx)).toThrow('requires the bold lead'); + }); + + test('design-review and plan-design-review carry the same five Persuade checks', () => { + const checks = DESIGN_PERSUADE_CHECKS(ctx); + expect(checks.split('\n').map(l => l.slice(0, 3))).toEqual(['1. ', '2. ', '3. ', '4. ', '5. ']); + expect(read('design-review', 'sections', 'craft-audit.md')).toContain(checks); + expect(read('plan-design-review', 'sections', 'review-sections.md')).toContain(checks); + }); +}); diff --git a/test/design-craft.test.ts b/test/design-craft.test.ts index 062c508e6..f17cab1a1 100644 --- a/test/design-craft.test.ts +++ b/test/design-craft.test.ts @@ -3,9 +3,10 @@ import * as fs from 'fs'; import * as os from 'os'; import * as path from 'path'; import { acceptFindings, BUZZWORDS, CRAFT_IDS, checkCraft, formatCraft, parseCraftExceptions, visibleText } from '../lib/design-craft'; -import { catalogEntry } from '../lib/design-catalog'; +import { catalogEntry, DESIGN_SLOP_CATALOG, detectorSlopEntries, judgmentTellEntries, renderCatalog, selectCatalog } from '../lib/design-catalog'; +import { checklistSlopEntries } from '../scripts/resolvers/design-checklist'; import { selectTests } from './helpers/test-selection'; -import { E2E_TOUCHFILES } from './helpers/touchfiles-data'; +import { E2E_TOUCHFILES, LLM_JUDGE_TOUCHFILES } from './helpers/touchfiles-data'; const ROOT = path.resolve(import.meta.dir, '..'); const BIN = path.join(ROOT, 'bin', 'gstack-design-craft'); @@ -59,6 +60,20 @@ describe('design-craft check', () => { for (const f of findings) expect(f.fix).toBe(catalogEntry(f.id)!.fix!); }); + // Value: protects=checker-only craft entries render into no skill prose or checklist, and each is a printed craft id; fails_when=a renderer reads DESIGN_SLOP_CATALOG, not RENDERED, or filters on fix; why_new=only goldens catch it, until regenerated; seam=none; no_claim=byte size of any one skill (size guard owns it) + test('checker-only catalog entries are craft ids that no rendered list carries; catalog entries with a fix still render', () => { + const checkerOnly = DESIGN_SLOP_CATALOG.filter(e => e.checkerOnly); + expect(checkerOnly.length).toBeGreaterThan(0); + const rendered = new Set([selectCatalog({}), detectorSlopEntries(), judgmentTellEntries(), checklistSlopEntries()].flat().map(e => e.id)); + const prose = renderCatalog({ kind: 'slop' }); + for (const e of checkerOnly) { + expect(CRAFT_IDS, e.id).toContain(e.id); + expect(rendered.has(e.id), e.id).toBe(false); + expect(prose, e.id).not.toContain(e.prose); + } + for (const id of ['marketing-buzzword', 'layout-transition']) expect(rendered.has(id), id).toBe(true); + }); + test('BUZZWORDS covers every word the catalog marketing-buzzword heuristic names', () => { const heuristic = catalogEntry('marketing-buzzword')?.heuristic ?? ''; const words = heuristic.replace(/^.*?for /, '').replace(/\.$/, '').split(/,\s*/); @@ -198,6 +213,25 @@ describe('accepted craft exceptions (finalized.json craft_exceptions)', () => { fs.rmSync(dir, { recursive: true, force: true }); } }); + + test('an exception the page no longer triggers prints nothing, and a record is read only with --accepted', () => { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'design-craft-cells-')); + try { + const page = path.join(dir, 'finalized.html'); + const record = path.join(dir, 'finalized.json'); + fs.writeFileSync(page, CLEAN); + fs.writeFileSync(record, JSON.stringify({ craft_exceptions: [{ id: 'placeholder-brand', count: 1, reason: 'Acme is the customer' }] })); + expect(run(page, '--accepted', record)).toMatchObject({ code: 0, out: 'CRAFT_RESULT: clean\n' }); + expect(acceptFindings([], [{ id: 'oops', count: 1, reason: 'x' }])).toEqual({ issues: [], accepted: [] }); + fs.writeFileSync(page, CLEAN.replace('Halvorsen Freight', 'Acme and Nexus')); + expect(run(page)).toMatchObject({ code: 1, out: 'CRAFT_CHECK: placeholder-brand 2 use a supplied customer, or a fitting name in sample data\nCRAFT_RESULT: 1 issue(s)\n' }); + fs.writeFileSync(page, CLEAN); + fs.writeFileSync(record, '{'); + expect(run(page)).toMatchObject({ code: 0, out: 'CRAFT_RESULT: clean\n', err: '' }); + } finally { + fs.rmSync(dir, { recursive: true, force: true }); + } + }); }); describe('design-shotgun to design-html handoff', () => { @@ -224,6 +258,13 @@ describe('design-shotgun to design-html handoff', () => { } }); + // Value: protects=a change to the shared craft resolver reruns the paid cases whose prompts it renders; fails_when=scripts/resolvers/design-craft.ts drops from those touchfile lists; why_new=the row above covers lib/design-craft.ts and the bin only; seam=none; no_claim=that the selected cases pass + test('a change to the craft resolvers selects the design-review and plan-design-review cases that render them', () => { + const e2e = selectTests(['scripts/resolvers/design-craft.ts'], E2E_TOUCHFILES).selected; + for (const name of ['design-review-fix', 'design-review-detector-shim', 'design-review-detector-shim-dom', 'design-review-plugin-handoff', 'plan-design-review-plan-mode']) expect(e2e).toContain(name); + expect(selectTests(['scripts/resolvers/design-craft.ts'], LLM_JUDGE_TOUCHFILES).selected).toContain('plan-design-review/SKILL.md passes'); + }); + test('design-html persists accepted exceptions and passes them back with --accepted', () => { const html = read('design-html', 'SKILL.md'); const craft = read('design-html', 'sections', 'craft.md'); diff --git a/test/external-host-carve.test.ts b/test/external-host-carve.test.ts index fd440186b..a89a8fe4e 100644 --- a/test/external-host-carve.test.ts +++ b/test/external-host-carve.test.ts @@ -65,7 +65,7 @@ describe('C4: external section pointers', () => { }); }); -describe('C4: ship, plan-ceo-review and office-hours are carved on every external host', () => { +describe('C4: ship, plan-ceo-review, office-hours and design-review are carved on every external host', () => { for (const config of EXTERNAL) { test(`${config.name}: every pointer in the carved skill resolves to a generated section file`, () => { for (const skill of ['ship', 'plan-ceo-review', 'office-hours', 'design-review']) { diff --git a/test/fixtures/skill-md-size-allowlist.json b/test/fixtures/skill-md-size-allowlist.json index de712bf56..d8d2b673e 100644 --- a/test/fixtures/skill-md-size-allowlist.json +++ b/test/fixtures/skill-md-size-allowlist.json @@ -4,16 +4,16 @@ "pins": { "agy/gstack-autoplan": 130504, "agy/gstack-design-consultation": 108184, - "agy/gstack-design-review": 144142, + "agy/gstack-design-review": 144136, "agy/gstack-land-and-deploy": 117722, "agy/gstack-plan-design-review": 141095, "agy/gstack-plan-devex-review": 135760, "agy/gstack-plan-eng-review": 152582, "agy/gstack-review": 124182, - "claude/design-review": 137115, + "claude/design-review": 137109, "codex/gstack-autoplan": 130628, "codex/gstack-design-consultation": 109452, - "codex/gstack-design-review": 145642, + "codex/gstack-design-review": 145636, "codex/gstack-land-and-deploy": 122272, "codex/gstack-office-hours": 102308, "codex/gstack-plan-design-review": 143548, @@ -23,7 +23,7 @@ "copilot/gstack-autoplan": 131637, "copilot/gstack-codex": 100235, "copilot/gstack-design-consultation": 109536, - "copilot/gstack-design-review": 145447, + "copilot/gstack-design-review": 145441, "copilot/gstack-land-and-deploy": 118977, "copilot/gstack-office-hours": 100464, "copilot/gstack-plan-design-review": 141966, @@ -32,7 +32,7 @@ "copilot/gstack-review": 125453, "cursor/gstack-autoplan": 129278, "cursor/gstack-design-consultation": 107222, - "cursor/gstack-design-review": 143121, + "cursor/gstack-design-review": 143115, "cursor/gstack-land-and-deploy": 116645, "cursor/gstack-plan-design-review": 140050, "cursor/gstack-plan-devex-review": 134591, @@ -41,7 +41,7 @@ "factory/gstack-autoplan": 131562, "factory/gstack-codex": 100655, "factory/gstack-design-consultation": 108670, - "factory/gstack-design-review": 144911, + "factory/gstack-design-review": 144905, "factory/gstack-land-and-deploy": 120817, "factory/gstack-office-hours": 101343, "factory/gstack-plan-design-review": 142831, @@ -49,34 +49,34 @@ "factory/gstack-plan-eng-review": 154550, "factory/gstack-review": 144493, "gbrain/gstack-autoplan": 129815, - "gbrain/gstack-design-review": 133456, + "gbrain/gstack-design-review": 133450, "gbrain/gstack-land-and-deploy": 117167, "gbrain/gstack-plan-design-review": 128937, "gbrain/gstack-plan-devex-review": 115129, "gbrain/gstack-plan-eng-review": 134168, "hermes/gstack-autoplan": 129905, - "hermes/gstack-design-review": 133563, + "hermes/gstack-design-review": 133557, "hermes/gstack-land-and-deploy": 117269, "hermes/gstack-plan-design-review": 129046, "hermes/gstack-plan-devex-review": 115215, "hermes/gstack-plan-eng-review": 134253, "kiro/gstack-autoplan": 129040, "kiro/gstack-design-consultation": 107064, - "kiro/gstack-design-review": 142935, + "kiro/gstack-design-review": 142929, "kiro/gstack-land-and-deploy": 116533, "kiro/gstack-plan-design-review": 139890, "kiro/gstack-plan-devex-review": 134431, "kiro/gstack-plan-eng-review": 151286, "kiro/gstack-review": 140526, "openclaw/gstack-autoplan": 129520, - "openclaw/gstack-design-review": 132261, + "openclaw/gstack-design-review": 132255, "openclaw/gstack-land-and-deploy": 116757, "openclaw/gstack-plan-design-review": 127788, "openclaw/gstack-plan-devex-review": 113968, "openclaw/gstack-plan-eng-review": 132565, "opencode/gstack-autoplan": 131640, "opencode/gstack-design-consultation": 109357, - "opencode/gstack-design-review": 145319, + "opencode/gstack-design-review": 145313, "opencode/gstack-land-and-deploy": 118762, "opencode/gstack-office-hours": 100298, "opencode/gstack-plan-design-review": 141820, @@ -85,7 +85,7 @@ "opencode/gstack-review": 142949, "slate/gstack-autoplan": 129159, "slate/gstack-design-consultation": 107143, - "slate/gstack-design-review": 143028, + "slate/gstack-design-review": 143022, "slate/gstack-land-and-deploy": 116589, "slate/gstack-plan-design-review": 139970, "slate/gstack-plan-devex-review": 134511,