mirror of
https://github.com/garrytan/gstack.git
synced 2026-09-09 14:38:59 +02:00
feat(review): generate review/design-checklist.md from the catalog
review/design-checklist.md was hand-written and its own header admitted it drifted from DESIGN_METHODOLOGY category 9. It is now rendered by scripts/resolvers/design-checklist.ts from lib/design-catalog.ts: category 1 lists every grep-detectable slop entry plus the legacy blacklist lines, sorted HIGH/MEDIUM/LOW, each with its heuristic and, where the detector knows the rule, its bracketed id (27 items, up from 6). The font blacklist renders from BANNED_FONTS. Categories 2-5, Instructions, Classification, Output Format, and Suppressions keep their prose. Title and slop heading are unchanged (test/skill-e2e-review.test.ts and hosts/opencode.ts key on them). gen-skill-docs writes the file for the Claude host only (a Claude-side runtime asset; other hosts copy or inline the render), honors --out-dir, and reports STALE/FRESH under --dry-run like sections do. test/design-checklist-sync.test.ts pins committed == generated, the host/out-dir scoping, and the dry-run freshness line. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
e34541c3bd
commit
11c2a33ae9
@@ -1,6 +1,7 @@
|
||||
<!-- GENERATED from lib/design-catalog.ts via scripts/resolvers/design-checklist.ts. Run: bun run gen:skill-docs -->
|
||||
# Design Review Checklist (Lite)
|
||||
|
||||
> **Subset of DESIGN_METHODOLOGY** — when adding items here, also update `generateDesignMethodology()` in `scripts/gen-skill-docs.ts`, and vice versa.
|
||||
> **Generated from the catalog.** Category 1 renders the grep-detectable slop entries of `lib/design-catalog.ts`, the same entries DESIGN_METHODOLOGY category 9 renders, so the two cannot drift. Edit the catalog, then run `bun run gen:skill-docs`.
|
||||
|
||||
## Instructions
|
||||
|
||||
@@ -26,6 +27,8 @@ Each item is tagged with a detection confidence level:
|
||||
- **[MEDIUM]** — Detectable via pattern aggregation or heuristic. Flag as findings but expect some noise.
|
||||
- **[LOW]** — Requires understanding visual intent. Present as: "Possible issue — verify visually or run /design-review."
|
||||
|
||||
A bracketed `[rule-id]` names the deterministic detector rule for the same pattern; a hit from the detector and a hit from this checklist at the same file:line are one finding.
|
||||
|
||||
---
|
||||
|
||||
## Classification
|
||||
@@ -68,21 +71,63 @@ If no frontend files changed: skip silently, no output.
|
||||
|
||||
## Categories
|
||||
|
||||
### 1. AI Slop Detection (6 items) — highest priority
|
||||
### 1. AI Slop Detection (27 items) — highest priority
|
||||
|
||||
These are the telltale signs of AI-generated UI that no designer at a respected studio would ship.
|
||||
|
||||
- **[MEDIUM]** Purple/violet/indigo gradient backgrounds or blue-to-purple color schemes. Look for `linear-gradient` with values in the `#6366f1`–`#8b5cf6` range, or CSS custom properties resolving to purple/violet.
|
||||
- **[HIGH]** Centered everything (`text-align: center` on all headings, descriptions, cards). Grep for `text-align: center` density: if more than 60% of text containers center, flag it.
|
||||
|
||||
- **[LOW]** The 3-column feature grid: icon-in-colored-circle + bold title + 2-line description, repeated 3x symmetrically. Look for a grid/flex container with exactly 3 children that each contain a circular element + heading + paragraph.
|
||||
- **[HIGH]** [side-tab] Colored left-border on cards (`border-left: 3px solid <accent>`). Grep for `border-left: <n>px solid` on card, callout, or list-item selectors.
|
||||
|
||||
- **[LOW]** Icons in colored circles as section decoration. Look for elements with `border-radius: 50%` + a background color used as decorative containers for icons.
|
||||
- **[HIGH]** system-ui or `-apple-system` as the PRIMARY display/body font — the "I gave up on typography" signal. Pick a real typeface. Grep `font-family` on body, headings, and base styles for `system-ui` or `-apple-system` as the first face in the stack.
|
||||
|
||||
- **[HIGH]** Centered everything: `text-align: center` on all headings, descriptions, and cards. Grep for `text-align: center` density — if >60% of text containers use center alignment, flag it.
|
||||
- **[HIGH]** [gradient-text] Emphasis is weight or size. Gradient text is emphasis in a costume. Grep for `background-clip: text` next to a gradient background.
|
||||
|
||||
- **[MEDIUM]** Uniform bubbly border-radius on every element: same large radius (16px+) applied to cards, buttons, inputs, containers uniformly. Aggregate `border-radius` values — if >80% use the same value ≥16px, flag it.
|
||||
- **[HIGH]** [bounce-easing] Overshoot and bounce curves on UI motion. Exponential ease-out from an already-visible default. Grep transitions and keyframes for cubic-bezier curves with a control point past 1, or `bounce` in animation names.
|
||||
|
||||
- **[MEDIUM]** Generic hero copy: "Welcome to [X]", "Unlock the power of...", "Your all-in-one solution for...", "Revolutionize your...", "Streamline your workflow". Grep HTML/JSX content for these patterns.
|
||||
- **[HIGH]** [dark-glow] Glowing edges on dark surfaces: look number two. Depth has an offset; a zero-offset colored halo is decoration. Grep `box-shadow` for a zero x/y offset with a large blur and a saturated color.
|
||||
|
||||
- **[HIGH]** [oversized-h1] Display type past 6rem on a page that is not a poster. Size is not hierarchy. Grep h1 and display selectors for font-size above 6rem or 96px.
|
||||
|
||||
- **[HIGH]** [extreme-negative-tracking] Letter-spacing below -0.04em on display type. Tight tracking is a taste; crushed tracking is a tell. Grep `letter-spacing` for values below -0.04em.
|
||||
|
||||
- **[MEDIUM]** [ai-color-palette] Purple/violet/indigo gradient backgrounds or blue-to-purple color schemes. Look for `linear-gradient` with values in the `#6366f1` to `#8b5cf6` range, or CSS custom properties resolving to purple/violet.
|
||||
|
||||
- **[MEDIUM]** Uniform bubbly border-radius on every element (same large radius on everything). Aggregate `border-radius` values: if more than 80% share one value of 16px or more, flag it. Pill radius on everything is the extreme case.
|
||||
|
||||
- **[MEDIUM]** Emoji as design elements (rockets in headings, emoji as bullet points). Grep headings, list items, and buttons for emoji code points used as icons or bullets.
|
||||
|
||||
- **[MEDIUM]** Generic hero copy ("Welcome to [X]", "Unlock the power of...", "Your all-in-one solution for..."). Grep HTML/JSX content for "Welcome to", "Unlock the power of", "Your all-in-one solution", "Revolutionize your", "Streamline your workflow".
|
||||
|
||||
- **[MEDIUM]** [overused-font] A training-data default as the display voice means you stopped looking. As body or UI on an Operate or Read surface, several of these are fine. Say which and why. Grep `font-family` for a listed face as the first face on display selectors (h1, h2, .hero, .display). Faces: Inter, Roboto, Arial, Helvetica, Open Sans, Lato, Montserrat, Poppins, Space Grotesk, Space Mono, Fraunces, Playfair Display, Cormorant, Lora, Crimson, Newsreader, Syne, IBM Plex Sans, IBM Plex Serif, DM Sans, DM Serif, Outfit, Plus Jakarta Sans, Instrument Sans, Geist.
|
||||
|
||||
- **[MEDIUM]** [pulsing-dot] A small circle pulsing forever next to "Live" or "Online". Motion that says nothing new after the first loop. Grep for infinite keyframe animations on small round elements.
|
||||
|
||||
- **[MEDIUM]** [kicker-above-heading] A kicker above a heading is the strongest default there is: the heading carries its own weight, so delete the label. If the user wants it anyway, comply and say the tradeoff once. Look for a short uppercase, tracked element immediately before an h1 or h2.
|
||||
|
||||
- **[MEDIUM]** [marketing-buzzword] "Seamless", "effortless", "supercharge", "streamline": words that describe nothing. Say what the product does. Grep visible copy for seamless, effortless, supercharge, streamline, revolutionize, unlock, empower, elevate.
|
||||
|
||||
- **[MEDIUM]** [theater-slop-phrase] "Built for the way you work", "Designed for teams like yours", "Meet your new...": phrases that perform a launch instead of describing one. Grep copy for "built for", "designed for", "meet your new", "ship faster", "the future of".
|
||||
|
||||
- **[MEDIUM]** [image-hover-transform] Scaling an image on hover. Motion with no information in it. Grep `:hover` rules on images for `transform: scale`.
|
||||
|
||||
- **[MEDIUM]** Gradient buttons as the primary call to action. One solid color the palette owns. Grep button and CTA selectors for gradient backgrounds.
|
||||
|
||||
- **[MEDIUM]** "Get Started" and "Learn More" as the only calls to action. Name the outcome the click buys. Grep buttons and links for "Get Started" and "Learn More" with no more specific CTA on the page.
|
||||
|
||||
- **[MEDIUM]** Frosted-glass panels with blurred backdrops as the default surface. One translucent layer where it explains depth, not everywhere. Grep for `backdrop-filter: blur` on more than one container.
|
||||
|
||||
- **[MEDIUM]** Monospace on labels and body copy to look technical. Mono is for code and data columns. Grep `font-family` for a monospace stack on non-code, non-tabular selectors.
|
||||
|
||||
- **[MEDIUM]** Selection color, caret, scrollbars, focus rings, underline offset, tabular numerals left at browser defaults. Theme them from the palette. Grep for `::selection`, `caret-color`, `accent-color`, `scrollbar-color`, `text-underline-offset`, `font-variant-numeric`: none present means none themed.
|
||||
|
||||
- **[LOW]** **The 3-column feature grid:** icon-in-colored-circle + bold title + 2-line description, repeated 3x symmetrically. THE most recognizable AI layout. Look for a grid/flex container with exactly 3 children that each contain a circular element + heading + paragraph.
|
||||
|
||||
- **[LOW]** Icons in colored circles as section decoration (SaaS starter template look). Look for elements with `border-radius: 50%` + a background color used as decorative containers for icons.
|
||||
|
||||
- **[LOW]** Decorative blobs, floating circles, wavy SVG dividers (if a section feels empty, it needs better content, not decoration).
|
||||
|
||||
- **[LOW]** Cookie-cutter section rhythm (hero → 3 features → testimonials → pricing → CTA, every section same height).
|
||||
|
||||
### 2. Typography (4 items)
|
||||
|
||||
@@ -92,7 +137,7 @@ These are the telltale signs of AI-generated UI that no designer at a respected
|
||||
|
||||
- **[HIGH]** Heading hierarchy skipping levels: `h1` followed by `h3` without an `h2` in the same file/component. Check HTML/JSX for heading tags.
|
||||
|
||||
- **[HIGH]** Blacklisted fonts: Papyrus, Comic Sans, Lobster, Impact, Jokerman. Grep `font-family` for these names.
|
||||
- **[HIGH]** Blacklisted fonts: Papyrus, Comic Sans, Lobster, Impact, Jokerman, Bleeding Cowboys, Permanent Marker, Bradley Hand, Brush Script, Hobo, Trajan, Raleway, Clash Display, Courier New. Grep `font-family` for these names.
|
||||
|
||||
### 3. Spacing & Layout (4 items)
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
import { discoverTemplates, discoverSectionTemplates } from './discover-skills';
|
||||
import { writeLlmsTxt } from './gen-llms-txt';
|
||||
import { generateDesignChecklistMd } from './resolvers/design-checklist';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import type { Host, TemplateContext } from './resolvers/types';
|
||||
@@ -1103,6 +1104,32 @@ for (const currentHost of hostsToRun) {
|
||||
});
|
||||
}
|
||||
|
||||
// ─── review/design-checklist.md (generated from lib/design-catalog.ts) ───
|
||||
// A Claude-side runtime asset: setup links it from review/ and the other
|
||||
// hosts copy or inline the Claude render (hosts/opencode.ts), so it is
|
||||
// written for the CLAUDE host only. Honors OUT_DIR (outputs-only rule) and
|
||||
// takes part in the DRY_RUN freshness gate exactly like sections above.
|
||||
if (currentHost === 'claude'
|
||||
&& !(currentHostConfig.generation.includeSkills?.length && !currentHostConfig.generation.includeSkills.includes('review'))
|
||||
&& !currentHostConfig.generation.skipSkills?.includes('review')) {
|
||||
const outputPath = path.join(OUT_DIR ?? ROOT, 'review', 'design-checklist.md');
|
||||
const relOutput = path.relative(OUT_DIR || ROOT, outputPath);
|
||||
const content = generateDesignChecklistMd();
|
||||
if (DRY_RUN) {
|
||||
const existing = fs.existsSync(outputPath) ? fs.readFileSync(outputPath, 'utf-8') : '';
|
||||
if (existing !== content) {
|
||||
console.log(`STALE: ${relOutput}`);
|
||||
hasChanges = true;
|
||||
} else {
|
||||
console.log(`FRESH: ${relOutput}`);
|
||||
}
|
||||
} else {
|
||||
if (OUT_DIR) fs.mkdirSync(path.dirname(outputPath), { recursive: true });
|
||||
fs.writeFileSync(outputPath, content);
|
||||
console.log(`GENERATED: ${relOutput}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Generate the OpenClaw orchestrator-injection docs (gstack-lite / gstack-full /
|
||||
// gstack-plan CLAUDE.md snippets). Sources live in openclaw/templates/ —
|
||||
// plain markdown, no placeholder resolution — and are copied byte-for-byte
|
||||
|
||||
@@ -0,0 +1,182 @@
|
||||
/**
|
||||
* Design checklist resolver — renders review/design-checklist.md from the catalog.
|
||||
*
|
||||
* The checklist is the one artifact both /review (Review Army Design specialist)
|
||||
* and /ship (DESIGN_REVIEW_LITE) read at runtime. It used to be hand-written
|
||||
* and its own header admitted it drifted from DESIGN_METHODOLOGY category 9.
|
||||
* Now category 1 renders from lib/design-catalog.ts (the same entries category
|
||||
* 9 renders), the font blacklist renders from BANNED_FONTS, and everything else
|
||||
* is fixed prose kept here. gen-skill-docs writes the file for the Claude host
|
||||
* only (it is a Claude-side runtime asset; other hosts copy or inline the
|
||||
* Claude render), honors --out-dir, and reports STALE/FRESH under --dry-run.
|
||||
*
|
||||
* Derived in part from pbakaus/impeccable (Apache-2.0), modified. See NOTICE.md.
|
||||
*/
|
||||
import { DESIGN_SLOP_CATALOG, BANNED_FONTS, type DesignSlopEntry } from '../../lib/design-catalog';
|
||||
|
||||
export const DESIGN_CHECKLIST_HEADER =
|
||||
'<!-- GENERATED from lib/design-catalog.ts via scripts/resolvers/design-checklist.ts. Run: bun run gen:skill-docs -->';
|
||||
|
||||
/** Title and category heading are load-bearing: test/skill-e2e-review.test.ts and hosts/opencode.ts key on them. */
|
||||
export const DESIGN_CHECKLIST_TITLE = 'Design Review Checklist (Lite)';
|
||||
export const DESIGN_CHECKLIST_SLOP_HEADING = 'AI Slop Detection';
|
||||
|
||||
const TIER_ORDER: Record<DesignSlopEntry['confidence'], number> = { HIGH: 0, MEDIUM: 1, LOW: 2 };
|
||||
|
||||
/** Category 1: slop entries a code reader can grep for, plus the legacy blacklist lines. */
|
||||
export function checklistSlopEntries(): DesignSlopEntry[] {
|
||||
return DESIGN_SLOP_CATALOG
|
||||
.filter(e => e.kind === 'slop' && (e.detect.includes('grep') || e.legacyBlacklist))
|
||||
.map((e, i) => ({ e, i }))
|
||||
.sort((a, b) => (TIER_ORDER[a.e.confidence] - TIER_ORDER[b.e.confidence]) || (a.i - b.i))
|
||||
.map(x => x.e);
|
||||
}
|
||||
|
||||
function endsWithPunctuation(s: string): boolean {
|
||||
return /[.!?]$/.test(s.trim());
|
||||
}
|
||||
|
||||
function renderSlopItem(e: DesignSlopEntry): string {
|
||||
const id = e.impeccableId ? ` [${e.impeccableId}]` : '';
|
||||
const prose = endsWithPunctuation(e.prose) ? e.prose : `${e.prose}.`;
|
||||
const heuristic = e.heuristic ? ` ${e.heuristic}` : '';
|
||||
const values = e.values ? ` Faces: ${e.values.join(', ')}.` : '';
|
||||
return `- **[${e.confidence}]**${id} ${prose}${heuristic}${values}`;
|
||||
}
|
||||
|
||||
/** Font names without role qualifiers, for a grep. */
|
||||
function bannedFontNames(): string[] {
|
||||
return BANNED_FONTS.map(f => f.replace(/\s*\(.*\)$/, ''));
|
||||
}
|
||||
|
||||
export function generateDesignChecklistMd(): string {
|
||||
const slop = checklistSlopEntries();
|
||||
return `${DESIGN_CHECKLIST_HEADER}
|
||||
# ${DESIGN_CHECKLIST_TITLE}
|
||||
|
||||
> **Generated from the catalog.** Category 1 renders the grep-detectable slop entries of \`lib/design-catalog.ts\`, the same entries DESIGN_METHODOLOGY category 9 renders, so the two cannot drift. Edit the catalog, then run \`bun run gen:skill-docs\`.
|
||||
|
||||
## Instructions
|
||||
|
||||
This checklist applies to **source code in the diff** — not rendered output. Read each changed frontend file (full file, not just diff hunks) and flag anti-patterns.
|
||||
|
||||
**Trigger:** Only run this checklist if the diff touches frontend files. Use \`gstack-diff-scope\` to detect:
|
||||
|
||||
\`\`\`bash
|
||||
source <(~/.claude/skills/gstack/bin/gstack-diff-scope <base> 2>/dev/null)
|
||||
\`\`\`
|
||||
|
||||
If \`SCOPE_FRONTEND=false\`, skip the entire design review silently.
|
||||
|
||||
**DESIGN.md calibration:** If \`DESIGN.md\` or \`design-system.md\` exists in the repo root, read it first. All findings are calibrated against the project's stated design system. Patterns explicitly blessed in DESIGN.md are NOT flagged. If no DESIGN.md exists, use universal design principles.
|
||||
|
||||
---
|
||||
|
||||
## Confidence Tiers
|
||||
|
||||
Each item is tagged with a detection confidence level:
|
||||
|
||||
- **[HIGH]** — Reliably detectable via grep/pattern match. Definitive findings.
|
||||
- **[MEDIUM]** — Detectable via pattern aggregation or heuristic. Flag as findings but expect some noise.
|
||||
- **[LOW]** — Requires understanding visual intent. Present as: "Possible issue — verify visually or run /design-review."
|
||||
|
||||
A bracketed \`[rule-id]\` names the deterministic detector rule for the same pattern; a hit from the detector and a hit from this checklist at the same file:line are one finding.
|
||||
|
||||
---
|
||||
|
||||
## Classification
|
||||
|
||||
**AUTO-FIX** (mechanical CSS fixes only — HIGH confidence, no design judgment needed):
|
||||
- \`outline: none\` without replacement → add \`outline: revert\` or \`&:focus-visible { outline: 2px solid currentColor; }\`
|
||||
- \`!important\` in new CSS → remove and fix specificity
|
||||
- \`font-size\` < 16px on body text → bump to 16px
|
||||
|
||||
**ASK** (everything else — requires design judgment):
|
||||
- All AI slop findings, typography structure, spacing choices, interaction state gaps, DESIGN.md violations
|
||||
|
||||
**LOW confidence items** → present as "Possible: [description]. Verify visually or run /design-review." Never AUTO-FIX.
|
||||
|
||||
---
|
||||
|
||||
## Output Format
|
||||
|
||||
\`\`\`
|
||||
Design Review: N issues (X auto-fixable, Y need input, Z possible)
|
||||
|
||||
**AUTO-FIXED:**
|
||||
- [file:line] Problem → fix applied
|
||||
|
||||
**NEEDS INPUT:**
|
||||
- [file:line] Problem description
|
||||
Recommended fix: suggested fix
|
||||
|
||||
**POSSIBLE (verify visually):**
|
||||
- [file:line] Possible issue — verify with /design-review
|
||||
\`\`\`
|
||||
|
||||
Optional: \`test_stub\` — skeleton test code for this finding using the project's test framework.
|
||||
|
||||
If no issues found: \`Design Review: No issues found.\`
|
||||
|
||||
If no frontend files changed: skip silently, no output.
|
||||
|
||||
---
|
||||
|
||||
## Categories
|
||||
|
||||
### 1. ${DESIGN_CHECKLIST_SLOP_HEADING} (${slop.length} items) — highest priority
|
||||
|
||||
These are the telltale signs of AI-generated UI that no designer at a respected studio would ship.
|
||||
|
||||
${slop.map(renderSlopItem).join('\n\n')}
|
||||
|
||||
### 2. Typography (4 items)
|
||||
|
||||
- **[HIGH]** Body text \`font-size\` < 16px. Grep for \`font-size\` declarations on \`body\`, \`p\`, \`.text\`, or base styles. Values below 16px (or 1rem when base is 16px) are flagged.
|
||||
|
||||
- **[HIGH]** More than 3 font families introduced in the diff. Count distinct \`font-family\` declarations. Flag if >3 unique families appear across changed files.
|
||||
|
||||
- **[HIGH]** Heading hierarchy skipping levels: \`h1\` followed by \`h3\` without an \`h2\` in the same file/component. Check HTML/JSX for heading tags.
|
||||
|
||||
- **[HIGH]** Blacklisted fonts: ${bannedFontNames().join(', ')}. Grep \`font-family\` for these names.
|
||||
|
||||
### 3. Spacing & Layout (4 items)
|
||||
|
||||
- **[MEDIUM]** Arbitrary spacing values not on a 4px or 8px scale, when DESIGN.md specifies a spacing scale. Check \`margin\`, \`padding\`, \`gap\` values against the stated scale. Only flag when DESIGN.md defines a scale.
|
||||
|
||||
- **[MEDIUM]** Fixed widths without responsive handling: \`width: NNNpx\` on containers without \`max-width\` or \`@media\` breakpoints. Risk of horizontal scroll on mobile.
|
||||
|
||||
- **[MEDIUM]** Missing \`max-width\` on text containers: body text or paragraph containers with no \`max-width\` set, allowing lines >75 characters. Check for \`max-width\` on text wrappers.
|
||||
|
||||
- **[HIGH]** \`!important\` in new CSS rules. Grep for \`!important\` in added lines. Almost always a specificity escape hatch that should be fixed properly.
|
||||
|
||||
### 4. Interaction States (3 items)
|
||||
|
||||
- **[MEDIUM]** Interactive elements (buttons, links, inputs) missing hover/focus states. Check if \`:hover\` and \`:focus\` or \`:focus-visible\` pseudo-classes exist for new interactive element styles.
|
||||
|
||||
- **[HIGH]** \`outline: none\` or \`outline: 0\` without a replacement focus indicator. Grep for \`outline:\\s*none\` or \`outline:\\s*0\`. This removes keyboard accessibility.
|
||||
|
||||
- **[LOW]** Touch targets < 44px on interactive elements. Check \`min-height\`/\`min-width\`/\`padding\` on buttons and links. Requires computing effective size from multiple properties — low confidence from code alone.
|
||||
|
||||
### 5. DESIGN.md Violations (3 items, conditional)
|
||||
|
||||
Only apply if \`DESIGN.md\` or \`design-system.md\` exists:
|
||||
|
||||
- **[MEDIUM]** Colors not in the stated palette. Compare color values in changed CSS against the palette defined in DESIGN.md.
|
||||
|
||||
- **[MEDIUM]** Fonts not in the stated typography section. Compare \`font-family\` values against DESIGN.md's font list.
|
||||
|
||||
- **[MEDIUM]** Spacing values outside the stated scale. Compare \`margin\`/\`padding\`/\`gap\` values against DESIGN.md's spacing scale.
|
||||
|
||||
---
|
||||
|
||||
## Suppressions
|
||||
|
||||
Do NOT flag:
|
||||
- Patterns explicitly documented in DESIGN.md as intentional choices
|
||||
- Third-party/vendor CSS files (node_modules, vendor directories)
|
||||
- CSS resets or normalize stylesheets
|
||||
- Test fixture files
|
||||
- Generated/minified CSS
|
||||
`;
|
||||
}
|
||||
@@ -64,8 +64,9 @@ ${ctx.paths.binDir}/gstack-review-log '{"skill":"design-review-lite","timestamp"
|
||||
Substitute: TIMESTAMP = ISO 8601 datetime, STATUS = "clean" if 0 findings or "issues_found", N = total findings, M = auto-fixed count, COMMIT = output of \`git rev-parse --short HEAD\`.${codexBlock}`;
|
||||
}
|
||||
|
||||
// NOTE: design-checklist.md is a subset of this methodology for code-level detection.
|
||||
// When adding items here, also update review/design-checklist.md, and vice versa.
|
||||
// NOTE: review/design-checklist.md is GENERATED (scripts/resolvers/design-checklist.ts)
|
||||
// from lib/design-catalog.ts, the same catalog category 9 below renders. Edit the
|
||||
// catalog, never the checklist; gen-skill-docs rewrites it.
|
||||
export function generateDesignMethodology(_ctx: TemplateContext): string {
|
||||
return `## Modes
|
||||
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
/**
|
||||
* review/design-checklist.md is generated from lib/design-catalog.ts by
|
||||
* scripts/resolvers/design-checklist.ts. These pins keep the committed file
|
||||
* in sync with the generator, keep the generator host-scoped (Claude only)
|
||||
* and --out-dir aware, and keep the two load-bearing strings other code keys
|
||||
* on (the title and the slop heading) in place.
|
||||
*/
|
||||
import { describe, test, expect } from 'bun:test';
|
||||
import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import { spawnSync } from 'child_process';
|
||||
import {
|
||||
generateDesignChecklistMd, checklistSlopEntries,
|
||||
DESIGN_CHECKLIST_HEADER, DESIGN_CHECKLIST_TITLE, DESIGN_CHECKLIST_SLOP_HEADING,
|
||||
} from '../scripts/resolvers/design-checklist';
|
||||
import { DESIGN_SLOP_CATALOG, BANNED_FONTS } from '../lib/design-catalog';
|
||||
import { AI_SLOP_BLACKLIST } from '../scripts/resolvers/constants';
|
||||
|
||||
const ROOT = path.join(import.meta.dir, '..');
|
||||
const CHECKLIST = path.join(ROOT, 'review', 'design-checklist.md');
|
||||
const GEN = path.join(ROOT, 'scripts', 'gen-skill-docs.ts');
|
||||
|
||||
function runGen(args: string[]) {
|
||||
return spawnSync(process.execPath, ['run', GEN, ...args], { cwd: ROOT, encoding: 'utf-8', timeout: 240_000 });
|
||||
}
|
||||
|
||||
describe('review/design-checklist.md is generated', () => {
|
||||
test('committed file equals the generator output', () => {
|
||||
expect(fs.readFileSync(CHECKLIST, 'utf-8')).toBe(generateDesignChecklistMd());
|
||||
});
|
||||
|
||||
test('carries the GENERATED header, the title, and the slop heading', () => {
|
||||
const md = fs.readFileSync(CHECKLIST, 'utf-8');
|
||||
expect(md.startsWith(DESIGN_CHECKLIST_HEADER + '\n')).toBe(true);
|
||||
expect(md).toContain(`# ${DESIGN_CHECKLIST_TITLE}`);
|
||||
expect(md).toContain(`### 1. ${DESIGN_CHECKLIST_SLOP_HEADING} (`);
|
||||
// Fixed sections other readers depend on.
|
||||
for (const h of ['## Instructions', '## Confidence Tiers', '## Classification', '## Output Format', '## Categories', '## Suppressions']) {
|
||||
expect(md).toContain(h);
|
||||
}
|
||||
});
|
||||
|
||||
test('category 1 renders every grep-detectable slop entry and every legacy line', () => {
|
||||
const md = generateDesignChecklistMd();
|
||||
const entries = checklistSlopEntries();
|
||||
expect(md).toContain(`(${entries.length} items)`);
|
||||
for (const e of entries) {
|
||||
expect(md).toContain(`**[${e.confidence}]**${e.impeccableId ? ` [${e.impeccableId}]` : ''} `);
|
||||
if (e.heuristic) expect(md).toContain(e.heuristic);
|
||||
}
|
||||
for (const line of AI_SLOP_BLACKLIST) {
|
||||
expect(md).toContain(line.replace(/\.$/, ''));
|
||||
}
|
||||
// Sorted HIGH → MEDIUM → LOW.
|
||||
const tiers = entries.map(e => e.confidence);
|
||||
const order = { HIGH: 0, MEDIUM: 1, LOW: 2 } as const;
|
||||
for (let i = 1; i < tiers.length; i++) expect(order[tiers[i]]).toBeGreaterThanOrEqual(order[tiers[i - 1]]);
|
||||
});
|
||||
|
||||
test('brackets only detector-known ids; quality entries stay out of category 1', () => {
|
||||
const md = generateDesignChecklistMd();
|
||||
for (const e of DESIGN_SLOP_CATALOG.filter(x => !x.impeccableId)) expect(md).not.toContain(`[${e.id}]`);
|
||||
for (const e of checklistSlopEntries()) expect(e.kind).toBe('slop');
|
||||
expect(md).toContain('[side-tab]');
|
||||
expect(md).toContain('[overused-font]');
|
||||
expect(md).toContain('Faces: Inter, Roboto');
|
||||
});
|
||||
|
||||
test('font blacklist renders from BANNED_FONTS without role qualifiers', () => {
|
||||
const md = generateDesignChecklistMd();
|
||||
expect(md).toContain('Blacklisted fonts: Papyrus, Comic Sans');
|
||||
expect(md).toContain('Courier New.');
|
||||
expect(md).not.toContain('(for body)');
|
||||
expect(BANNED_FONTS.length).toBeGreaterThan(5);
|
||||
});
|
||||
});
|
||||
|
||||
describe('gen-skill-docs writes the checklist for the Claude host only', () => {
|
||||
test('--host claude --out-dir renders it under the out dir; --host codex --out-dir does not', () => {
|
||||
const out = fs.mkdtempSync(path.join(os.tmpdir(), 'gstack-checklist-'));
|
||||
try {
|
||||
const before = fs.statSync(CHECKLIST).mtimeMs;
|
||||
const claude = runGen(['--host', 'claude', '--out-dir', out]);
|
||||
expect(claude.status).toBe(0);
|
||||
expect(claude.stdout).toContain('GENERATED: review/design-checklist.md');
|
||||
expect(fs.readFileSync(path.join(out, 'review', 'design-checklist.md'), 'utf-8')).toBe(generateDesignChecklistMd());
|
||||
|
||||
fs.rmSync(path.join(out, 'review'), { recursive: true, force: true });
|
||||
const codex = runGen(['--host', 'codex', '--out-dir', out]);
|
||||
expect(codex.status).toBe(0);
|
||||
expect(codex.stdout).not.toContain('design-checklist.md');
|
||||
expect(fs.existsSync(path.join(out, 'review', 'design-checklist.md'))).toBe(false);
|
||||
|
||||
// The tracked file was never touched by either --out-dir render.
|
||||
expect(fs.statSync(CHECKLIST).mtimeMs).toBe(before);
|
||||
} finally {
|
||||
fs.rmSync(out, { recursive: true, force: true });
|
||||
}
|
||||
}, 300_000);
|
||||
|
||||
test('--dry-run reports the checklist FRESH', () => {
|
||||
const r = runGen(['--dry-run']);
|
||||
expect(r.stdout).toContain('FRESH: review/design-checklist.md');
|
||||
expect(r.stdout).not.toContain('STALE: review/design-checklist.md');
|
||||
}, 240_000);
|
||||
});
|
||||
Reference in New Issue
Block a user