#!/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.");