mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-10 02:30:39 +01:00
fix: craft audit capture, exceptions and trigger from /ship review (GSTA-92)
- craft-audit: no CRAFT_RESULT line means unavailable (exit 2 after a malformed --accepted record still printed findings); --accepted only for a /design-html finalized page, never for needs-proof or lorem; never add [Needs proof] markers to a live page; Aside capture via pg.evaluate; the saved raw DOM is deleted in the checking block and never reported - manifest trigger covers an explicit craft audit (skeleton -6 bytes; pins lowered to measured size, union ratio back under its cap) - autoFixEntries reads RENDERED; DESIGN_CRAFT_RULE needs exactly one lead - tests: checkerOnly contract, resolver throw paths, Persuade checks in both skills, resolver touchfile selection, exception state cells - CHANGELOG: host counts and the audit's new rules Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
1 parent
62c0fa1333
commit
31ee450399
12 files changed
+118
-55
No files matched your search
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -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');
|
||||
|
||||
@@ -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']) {
|
||||
|
||||
+12
-12
@@ -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,
|
||||
|
||||
Reference in new issue
Block a user