mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-01 06:24:41 +01:00
* feat(design-html): craft section and free craft check, adapted from Elaya design skills (GSTA-92) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix(GSTA-92): restore the full CHANGELOG under the Unreleased entry; drop a $-digit literal from craft.md; count only craft-check executions in design-html-slop-gate Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * feat(design-shotgun,design-html): carry the chosen archetype into approved.json; persist accepted craft exceptions in finalized.json (GSTA-92 phase 2) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * test(touchfiles): design-html-slop-gate selects on the craft check it runs (GSTA-92) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix: pre-landing review fixes (GSTA-92) A malformed or missing --accepted exceptions file now gets the full craft check with nothing accepted, after a warning, still exiting 2. Before, it printed nothing, and the craft section treats a missing CRAFT_RESULT as 'check unavailable', so one bad finalized.json switched the gate off (found by both adversarial passes). The header comment and the craft section now document the accepted-count suffix on CRAFT_RESULT. Tests cover duplicate carried-forward exception ids and the slop-gate touchfiles selection. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix: scope --accepted to the same page; record current counts (GSTA-92) The craft section passed --accepted whenever a finalized.json sat beside the page, so a same-day Start fresh build inherited the old page's exceptions. It now passes them only when evolving or rerunning a page it finalized, records the count the check reports now, and writes [] when nothing was accepted instead of the template's placeholder entry. A test pins exit 2 for a clean page with a broken exceptions file. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * docs: note the archetype handoff in the shotgun and design-html loops (GSTA-92) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> --------- Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
55 lines
2.5 KiB
TypeScript
Executable File
55 lines
2.5 KiB
TypeScript
Executable File
#!/usr/bin/env -S bun --no-env-file --config=/dev/null
|
|
// gstack-design-craft — free craft check for a generated page (/design-html Step 4).
|
|
//
|
|
// Usage: gstack-design-craft <file.html> [--accepted <finalized.json>]
|
|
//
|
|
// Prints one "CRAFT_CHECK: <id> <count> <fix>" line per tell, then
|
|
// "CRAFT_RESULT: clean" (exit 0) or "CRAFT_RESULT: <n> issue(s)" (exit 1).
|
|
// --accepted reads the craft_exceptions a finalized.json recorded: a tell an
|
|
// exception covers (same id, count at most the accepted count) prints as
|
|
// "CRAFT_ACCEPTED: <id> <count> <reason>" and does not fail the check; the
|
|
// result line then adds " (<k> accepted)" or ", <k> accepted".
|
|
// 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, type CraftException } from '../lib/design-craft';
|
|
|
|
const USAGE = 'Usage: gstack-design-craft <file.html> [--accepted <finalized.json>]';
|
|
const [file, flag, acceptedFile, ...rest] = process.argv.slice(2);
|
|
if (!file || (flag !== undefined && (flag !== '--accepted' || !acceptedFile)) || rest.length > 0) {
|
|
console.error(USAGE);
|
|
process.exit(2);
|
|
}
|
|
if (!/\.html?$/i.test(file)) {
|
|
console.error(`gstack-design-craft: ${file} is not an HTML file; check the rendered HTML preview instead`);
|
|
process.exit(2);
|
|
}
|
|
let source: string;
|
|
try {
|
|
source = readFileSync(file, 'utf-8');
|
|
} catch (err) {
|
|
console.error(`gstack-design-craft: cannot read ${file}: ${(err as Error).message}`);
|
|
process.exit(2);
|
|
}
|
|
if (source.trim() === '') {
|
|
console.error(`gstack-design-craft: ${file} is empty; nothing was checked`);
|
|
process.exit(2);
|
|
}
|
|
let exceptions: CraftException[] = [];
|
|
let badExceptions = false;
|
|
if (acceptedFile) {
|
|
try {
|
|
exceptions = parseCraftExceptions(readFileSync(acceptedFile, 'utf-8'));
|
|
} catch (err) {
|
|
console.error(`gstack-design-craft: cannot use exceptions from ${acceptedFile}: ${(err as Error).message}; checked with none accepted`);
|
|
badExceptions = true;
|
|
}
|
|
}
|
|
const { issues, accepted } = acceptFindings(checkCraft(source), exceptions);
|
|
console.log(formatCraft(issues, accepted));
|
|
process.exit(badExceptions ? 2 : issues.length === 0 ? 0 : 1);
|