feat(refresh): make designbook-refresh orchestrator + drift-triage runbook (WHYNOT-WP-0002 T09)
scripts/designbook-refresh.mjs chains the automatable steps (check->pull->sync->ir->adapt-lit->parity) and stops for the human drift-triage step, propagating the adapter-contract exit codes (3=stop for triage, 4=parity fail). Gating steps call the node scripts directly so make doesn't collapse the 3/4 codes to 2. Best-effort steps (check/pull/sync) warn and continue; --no-pull /--no-check/--no-parity flags. Documented the loop in stack-and-commands.md and a step-6 drift-resolution runbook in designbook/README.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
73
scripts/designbook-refresh.mjs
Normal file
73
scripts/designbook-refresh.mjs
Normal file
@@ -0,0 +1,73 @@
|
||||
#!/usr/bin/env node
|
||||
// =============================================================
|
||||
// designbook-refresh.mjs — the refresh orchestrator (WHYNOT-WP-0002 · T09)
|
||||
//
|
||||
// One routine to keep the Lit stack current with the canonical React designbook.
|
||||
// Runs the *automatable* steps (1–5, 7) and stops for the *human* step (6) when
|
||||
// drift is detected, honouring the adapter-contract exit codes:
|
||||
//
|
||||
// 1. make designbook-check — has the cloud designbook moved? (best-effort)
|
||||
// 2. make designbook-pull — pull the React designbook → designbook/ (best-effort)
|
||||
// 3. make designbook-sync — record the diff → RecentChanges.md (best-effort)
|
||||
// 4. make ir — re-extract the IR (the blueprint) (gate)
|
||||
// 5. make adapt-lit — tokens + scaffold + drift (drift gate → 3)
|
||||
// 6. (human) resolve drift — adapters/lit/drift/*.md ← STOP here on drift
|
||||
// 7. make parity-lit — contract + visual parity (parity gate → 4)
|
||||
//
|
||||
// Best-effort steps (1–3) need network / the local `claude` binary / llm-connect;
|
||||
// their failure warns and continues (the IR re-extracts from the current mirror).
|
||||
// Steps 4/5/7 are deterministic and gate. Exit: highest applicable adapter code —
|
||||
// 0 ok · 2 usage · 3 drift (stop for triage) · 4 parity failure · 5 internal.
|
||||
//
|
||||
// Flags: --no-check --no-pull --no-parity (skip the matching step)
|
||||
// =============================================================
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { join, dirname } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const REPO = join(dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const args = new Set(process.argv.slice(2));
|
||||
|
||||
function run(label, cmd, { gate = false } = {}) {
|
||||
console.log(`\n\x1b[1m▶ ${label}\x1b[0m (${cmd})`);
|
||||
const r = spawnSync(cmd, { cwd: REPO, shell: true, stdio: "inherit" });
|
||||
const code = r.status == null ? 5 : r.status;
|
||||
if (code !== 0 && !gate) console.log(`\x1b[33m ↪ step exited ${code} — best-effort, continuing.\x1b[0m`);
|
||||
return code;
|
||||
}
|
||||
|
||||
function done(code, msg) {
|
||||
console.log(`\n\x1b[1m${code === 0 ? "✓" : "■"} designbook-refresh: ${msg}\x1b[0m`);
|
||||
process.exit(code);
|
||||
}
|
||||
|
||||
// 1–3: best-effort (never abort the refresh).
|
||||
if (!args.has("--no-check")) run("1/7 cloud-ahead check", "make designbook-check");
|
||||
if (!args.has("--no-pull")) run("2/7 pull React designbook", "make designbook-pull");
|
||||
run("3/7 record diff (RecentChanges.md)", "make designbook-sync");
|
||||
|
||||
// Gating steps call the node scripts DIRECTLY (not via make, which collapses any
|
||||
// recipe failure to exit 2 and would hide the adapter's 3/4 drift/parity codes).
|
||||
const N = JSON.stringify(process.execPath);
|
||||
|
||||
// 4: IR extraction — deterministic gate; without it nothing downstream is valid.
|
||||
const irCode = run("4/7 extract IR", `${N} scripts/ir-extract.mjs`, { gate: true });
|
||||
if (irCode !== 0) done(irCode === 5 ? 5 : 2, `IR extraction failed (exit ${irCode}). Fix the designbook/ source before refreshing.`);
|
||||
|
||||
// 5: adapt-lit — drift gate. Exit 3 ⇒ stop for human triage (step 6).
|
||||
const adaptCode = run("5/7 adapt Lit (tokens + scaffold + drift)", `${N} adapters/lit/adapt.mjs`, { gate: true });
|
||||
if (adaptCode === 3) {
|
||||
done(3, "DRIFT detected (step 6 is yours). Resolve adapters/lit/drift/*.md per " +
|
||||
".claude/rules/designbook-propagation.md, then re-run `make designbook-refresh --no-pull` to re-check + run parity.");
|
||||
}
|
||||
if (adaptCode !== 0) done(adaptCode === 2 ? 2 : 5, `adapt-lit failed (exit ${adaptCode}).`);
|
||||
|
||||
// 6: human drift resolution — only reached when adapt-lit is clean.
|
||||
|
||||
// 7: parity gate.
|
||||
if (args.has("--no-parity")) done(0, "tokens + scaffold clean; parity skipped (--no-parity).");
|
||||
const parityCode = run("7/7 parity (contract + visual)", `${N} adapters/lit/parity.mjs`, { gate: true });
|
||||
if (parityCode === 4) done(4, "PARITY FAILURE — see adapters/lit/parity/_parity.json.");
|
||||
if (parityCode !== 0) done(parityCode === 2 ? 2 : 5, `parity-lit failed (exit ${parityCode}).`);
|
||||
|
||||
done(0, "in sync — IR extracted, Lit adapted with no drift, parity passed.");
|
||||
Reference in New Issue
Block a user