qualia-framework 4.5.0 → 5.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/AGENTS.md +24 -0
  2. package/CLAUDE.md +12 -75
  3. package/README.md +23 -16
  4. package/agents/builder.md +9 -21
  5. package/agents/planner.md +8 -0
  6. package/agents/verifier.md +8 -0
  7. package/agents/visual-evaluator.md +132 -0
  8. package/bin/cli.js +54 -18
  9. package/bin/install.js +369 -29
  10. package/bin/qualia-ui.js +208 -1
  11. package/bin/slop-detect.mjs +5 -0
  12. package/bin/state.js +34 -1
  13. package/docs/install-redesign-builder-prompt.md +290 -0
  14. package/docs/install-redesign-pilot.md +234 -0
  15. package/docs/playwright-loop-builder-prompt.md +185 -0
  16. package/docs/playwright-loop-design-notes.md +108 -0
  17. package/docs/playwright-loop-pilot-results.md +170 -0
  18. package/docs/playwright-loop-review-2026-05-03.md +65 -0
  19. package/docs/playwright-loop-tester-prompt.md +213 -0
  20. package/docs/reviews/matt-pocock-skills-analysis.md +300 -0
  21. package/guide.md +9 -5
  22. package/hooks/env-empty-guard.js +74 -0
  23. package/hooks/pre-compact.js +19 -9
  24. package/hooks/pre-deploy-gate.js +8 -2
  25. package/hooks/pre-push.js +26 -12
  26. package/hooks/supabase-destructive-guard.js +62 -0
  27. package/hooks/vercel-account-guard.js +91 -0
  28. package/package.json +2 -1
  29. package/rules/design-brand.md +4 -0
  30. package/rules/design-laws.md +4 -0
  31. package/rules/design-product.md +4 -0
  32. package/rules/design-rubric.md +4 -0
  33. package/rules/grounding.md +4 -0
  34. package/skills/qualia-build/SKILL.md +40 -46
  35. package/skills/qualia-discuss/SKILL.md +51 -68
  36. package/skills/qualia-handoff/SKILL.md +1 -0
  37. package/skills/qualia-issues/SKILL.md +151 -0
  38. package/skills/qualia-map/SKILL.md +78 -35
  39. package/skills/qualia-new/REFERENCE.md +139 -0
  40. package/skills/qualia-new/SKILL.md +45 -121
  41. package/skills/qualia-optimize/REFERENCE.md +202 -0
  42. package/skills/qualia-optimize/SKILL.md +72 -237
  43. package/skills/qualia-plan/SKILL.md +58 -65
  44. package/skills/qualia-polish-loop/REFERENCE.md +265 -0
  45. package/skills/qualia-polish-loop/SKILL.md +201 -0
  46. package/skills/qualia-polish-loop/fixtures/broken.html +117 -0
  47. package/skills/qualia-polish-loop/fixtures/clean.html +196 -0
  48. package/skills/qualia-polish-loop/scripts/loop.mjs +302 -0
  49. package/skills/qualia-polish-loop/scripts/playwright-capture.mjs +197 -0
  50. package/skills/qualia-polish-loop/scripts/score.mjs +176 -0
  51. package/skills/qualia-report/SKILL.md +141 -200
  52. package/skills/qualia-research/SKILL.md +28 -33
  53. package/skills/qualia-road/SKILL.md +103 -0
  54. package/skills/qualia-ship/SKILL.md +1 -0
  55. package/skills/qualia-task/SKILL.md +1 -1
  56. package/skills/qualia-test/SKILL.md +50 -2
  57. package/skills/qualia-triage/SKILL.md +152 -0
  58. package/skills/qualia-verify/SKILL.md +63 -104
  59. package/skills/qualia-zoom/SKILL.md +51 -0
  60. package/skills/zoho-workflow/SKILL.md +1 -1
  61. package/templates/CONTEXT.md +36 -0
  62. package/templates/decisions/ADR-template.md +30 -0
  63. package/tests/bin.test.sh +451 -7
  64. package/tests/state.test.sh +58 -0
package/bin/qualia-ui.js CHANGED
@@ -522,6 +522,210 @@ function cmdPlanSummary(planPath) {
522
522
  console.log(` ${RULE_DIM}`);
523
523
  }
524
524
 
525
+ // ─── v5.1: live-progress primitives (consumed by install.js) ─────────
526
+ // Vanilla Node, zero deps. Auto-degrade to plain text when stdout is not
527
+ // a TTY so piped install logs stay readable (no orphan escape sequences,
528
+ // no \r overwrites, no spinner garbage).
529
+
530
+ const IS_TTY = !!(process.stdout && process.stdout.isTTY);
531
+ const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
532
+
533
+ function visibleLength(str) {
534
+ return String(str).replace(/\x1b\[[0-9;]*m/g, "").length;
535
+ }
536
+
537
+ // ANSI: clear current line and move cursor to column 0
538
+ function clearLine() {
539
+ process.stdout.write("\r\x1b[2K");
540
+ }
541
+ function hideCursor() { if (IS_TTY) process.stdout.write("\x1b[?25l"); }
542
+ function showCursor() { if (IS_TTY) process.stdout.write("\x1b[?25h"); }
543
+
544
+ // step(text) — prints " ⏳ text" then returns a handle:
545
+ // .ok(suffix?) → overwrites with " ✓ text [suffix]"
546
+ // .warn(message) → overwrites with " ! text — message"
547
+ // .fail(message) → overwrites with " ✗ text — message"
548
+ // In non-TTY mode, prints plain " → text" then " ✓ text" on a new line.
549
+ function step(text) {
550
+ const indent = " ";
551
+ if (!IS_TTY) {
552
+ // Skip the "doing" line in non-TTY mode — go straight to the result line
553
+ // when finalised. Avoids 2× line count in piped install logs.
554
+ let logged = false;
555
+ return {
556
+ ok(suffix) {
557
+ const tail = suffix ? ` ${DIM}${suffix}${RESET}` : "";
558
+ console.log(`${indent}${GREEN}✓${RESET} ${WHITE}${text}${RESET}${tail}`);
559
+ logged = true;
560
+ },
561
+ warn(msg) {
562
+ console.log(`${indent}${YELLOW}!${RESET} ${WHITE}${text}${RESET}${msg ? ` ${DIM}— ${msg}${RESET}` : ""}`);
563
+ logged = true;
564
+ },
565
+ fail(msg) {
566
+ console.log(`${indent}${RED}✗${RESET} ${WHITE}${text}${RESET}${msg ? ` ${DIM}— ${msg}${RESET}` : ""}`);
567
+ logged = true;
568
+ },
569
+ _logged() { return logged; },
570
+ };
571
+ }
572
+ process.stdout.write(`${indent}${DIM2}⏳${RESET} ${DIM}${text}${RESET}`);
573
+ return {
574
+ ok(suffix) {
575
+ clearLine();
576
+ const tail = suffix ? ` ${DIM}${suffix}${RESET}` : "";
577
+ process.stdout.write(`${indent}${GREEN}✓${RESET} ${WHITE}${text}${RESET}${tail}\n`);
578
+ },
579
+ warn(msg) {
580
+ clearLine();
581
+ process.stdout.write(`${indent}${YELLOW}!${RESET} ${WHITE}${text}${RESET}${msg ? ` ${DIM}— ${msg}${RESET}` : ""}\n`);
582
+ },
583
+ fail(msg) {
584
+ clearLine();
585
+ process.stdout.write(`${indent}${RED}✗${RESET} ${WHITE}${text}${RESET}${msg ? ` ${DIM}— ${msg}${RESET}` : ""}\n`);
586
+ },
587
+ };
588
+ }
589
+
590
+ // spinner(text) — Braille spinner, ticks every 100ms.
591
+ // stop({ status: "ok"|"warn"|"fail", message? })
592
+ // Non-TTY → prints " → text" then " ✓ text" / " ! text" / " ✗ text" on stop.
593
+ function spinner(text) {
594
+ const indent = " ";
595
+ if (!IS_TTY) {
596
+ return {
597
+ stop(result) {
598
+ const r = result || { status: "ok" };
599
+ if (r.status === "warn") {
600
+ console.log(`${indent}${YELLOW}!${RESET} ${WHITE}${text}${RESET}${r.message ? ` ${DIM}— ${r.message}${RESET}` : ""}`);
601
+ } else if (r.status === "fail" || r.status === "error") {
602
+ console.log(`${indent}${RED}✗${RESET} ${WHITE}${text}${RESET}${r.message ? ` ${DIM}— ${r.message}${RESET}` : ""}`);
603
+ } else {
604
+ console.log(`${indent}${GREEN}✓${RESET} ${WHITE}${text}${RESET}${r.message ? ` ${DIM}${r.message}${RESET}` : ""}`);
605
+ }
606
+ },
607
+ };
608
+ }
609
+ // Windows cmd.exe historically had spotty Braille rendering; modern Win10+
610
+ // Terminal handles it fine. The fallback path is the non-TTY branch above
611
+ // (covers piped logs); for interactive cmd.exe we accept that cosmetic risk
612
+ // — install still completes correctly even if frames render as boxes.
613
+ let i = 0;
614
+ hideCursor();
615
+ const render = () => {
616
+ clearLine();
617
+ process.stdout.write(`${indent}${TEAL}${SPINNER_FRAMES[i]}${RESET} ${DIM}${text}${RESET}`);
618
+ i = (i + 1) % SPINNER_FRAMES.length;
619
+ };
620
+ render();
621
+ const handle = setInterval(render, 100);
622
+ return {
623
+ stop(result) {
624
+ clearInterval(handle);
625
+ clearLine();
626
+ const r = result || { status: "ok" };
627
+ const tail = r.message ? ` ${DIM}${r.message}${RESET}` : "";
628
+ let glyph = `${GREEN}✓${RESET}`;
629
+ if (r.status === "warn") glyph = `${YELLOW}!${RESET}`;
630
+ if (r.status === "fail" || r.status === "error") glyph = `${RED}✗${RESET}`;
631
+ process.stdout.write(`${indent}${glyph} ${WHITE}${text}${RESET}${tail}\n`);
632
+ showCursor();
633
+ },
634
+ };
635
+ }
636
+
637
+ // progress(current, total, label) → renders a discrete progress bar line.
638
+ // Used by install.js for batch-copy phases (skills, templates) where step()
639
+ // would be too noisy. Caller updates by re-invoking; in TTY mode each call
640
+ // overwrites the previous line.
641
+ function progress(current, total, label) {
642
+ const indent = " ";
643
+ const width = 14;
644
+ const filled = total > 0 ? Math.round((current / total) * width) : 0;
645
+ const bar = `${TEAL}${"█".repeat(filled)}${DIM2}${"░".repeat(width - filled)}${RESET}`;
646
+ const line = `${indent}${bar} ${DIM}${current}/${total}${RESET} ${DIM}${label || ""}${RESET}`;
647
+ if (IS_TTY) {
648
+ clearLine();
649
+ process.stdout.write(line);
650
+ if (current >= total) process.stdout.write("\n");
651
+ } else if (current >= total) {
652
+ // Non-TTY: only emit the final line, avoid spamming the log per-tick.
653
+ console.log(line);
654
+ }
655
+ }
656
+
657
+ // box({title, lines, color}) — boxed multi-line panel.
658
+ // color is an ANSI escape string (default TEAL).
659
+ function box(opts) {
660
+ const title = opts.title || "";
661
+ const lines = Array.isArray(opts.lines) ? opts.lines : [];
662
+ const color = opts.color || TEAL;
663
+ const indent = " ";
664
+ const inner = Math.max(visibleLength(title), ...lines.map(visibleLength));
665
+ const width = Math.max(40, inner + 4);
666
+ const top = "─".repeat(width - 2);
667
+ console.log(`${indent}${color}┌${top}┐${RESET}`);
668
+ if (title) {
669
+ const padTitle = " ".repeat(Math.max(0, width - 4 - visibleLength(title)));
670
+ console.log(`${indent}${color}│${RESET} ${BOLD}${WHITE}${title}${RESET}${padTitle} ${color}│${RESET}`);
671
+ console.log(`${indent}${color}├${top}┤${RESET}`);
672
+ }
673
+ for (const line of lines) {
674
+ const pad = " ".repeat(Math.max(0, width - 4 - visibleLength(line)));
675
+ console.log(`${indent}${color}│${RESET} ${line}${pad} ${color}│${RESET}`);
676
+ }
677
+ console.log(`${indent}${color}└${top}┘${RESET}`);
678
+ }
679
+
680
+ // kv(label, value, options?) — left-padded label, value rendered in
681
+ // options.color (default WHITE). options.labelWidth pads label to column N.
682
+ function kv(label, value, options) {
683
+ const opts = options || {};
684
+ const valueColor = opts.color || WHITE;
685
+ const labelWidth = opts.labelWidth || 12;
686
+ const padded = pad(`${DIM}${label}${RESET}`, labelWidth + visibleLength(`${DIM}${RESET}`));
687
+ console.log(` ${padded}${valueColor}${value}${RESET}`);
688
+ }
689
+
690
+ // divider(width?) — variable-width dim divider.
691
+ function divider(width) {
692
+ const w = width || 48;
693
+ console.log(` ${DIM2}${"━".repeat(w)}${RESET}`);
694
+ }
695
+
696
+ // section(title, count?) — section header with optional right-aligned count.
697
+ function section(title, count) {
698
+ console.log("");
699
+ const right = (count !== undefined && count !== null && count !== "")
700
+ ? ` ${TEAL}${count}${RESET} ${DIM}✓${RESET}`
701
+ : "";
702
+ console.log(` ${TEAL}▸${RESET} ${WHITE}${BOLD}${title}${RESET}${right}`);
703
+ console.log(` ${DIM2}${"─".repeat(40)}${RESET}`);
704
+ }
705
+
706
+ // sectionClose(summary) — single-line summary after a section completes.
707
+ function sectionClose(summary) {
708
+ console.log(` ${DIM2}└─${RESET} ${DIM}${summary}${RESET}`);
709
+ }
710
+
711
+ module.exports = {
712
+ // Color constants — exported so install.js doesn't redefine them.
713
+ colors: { TEAL, TEAL_DIM, DIM, DIM2, GREEN, WHITE, YELLOW, RED, BLUE, RESET, BOLD },
714
+ IS_TTY,
715
+ // Live-progress primitives
716
+ step,
717
+ spinner,
718
+ progress,
719
+ box,
720
+ kv,
721
+ divider,
722
+ section,
723
+ sectionClose,
724
+ // Existing helpers (kept exposed for reuse)
725
+ pad,
726
+ visibleLength,
727
+ };
728
+
525
729
  function cmdUpdate(current, latest) {
526
730
  if (!current || !latest) return;
527
731
  console.log("");
@@ -535,7 +739,10 @@ function cmdUpdate(current, latest) {
535
739
  console.log("");
536
740
  }
537
741
 
538
- // ─── Main ────────────────────────────────────────────────
742
+ // ─── Main (CLI dispatch — only when executed directly, not when required) ─
743
+ if (require.main !== module) {
744
+ return;
745
+ }
539
746
  const [cmd, ...rest] = process.argv.slice(2);
540
747
  switch (cmd) {
541
748
  case "banner":
@@ -186,6 +186,11 @@ const SKIP_DIRS = new Set([
186
186
  "node_modules", ".next", "dist", "build", ".git", ".turbo",
187
187
  "coverage", ".cache", "out", ".vercel", ".vscode", ".idea",
188
188
  ".planning", ".qa-screenshots",
189
+ // v5.1: skip test fixtures by convention. Fixtures used as regression
190
+ // targets (e.g. /qualia-polish-loop's broken.html) intentionally violate
191
+ // the rules slop-detect enforces; scanning them flags real fixture bugs
192
+ // as production slop.
193
+ "fixtures", "__fixtures__",
189
194
  ]);
190
195
 
191
196
  function* walk(dir) {
package/bin/state.js CHANGED
@@ -735,6 +735,22 @@ function cmdTransition(opts) {
735
735
  function cmdInit(opts) {
736
736
  if (!opts.project) return output(fail("MISSING_ARG", "--project required"));
737
737
 
738
+ // v5.0: Throwaway-name guard. The insights report documents the user
739
+ // accidentally creating a project literally named "test-name-only" instead
740
+ // of the real client name. Names matching common scratch/test patterns get
741
+ // a 1-line confirmation step (bypass with --force).
742
+ if (!opts.force) {
743
+ const SUSPICIOUS = /^(test|tmp|temp|foo|bar|baz|scratch|demo|sample|untitled|new[\W_-]?project|example)(\b|[\W_-])/i;
744
+ if (SUSPICIOUS.test(opts.project)) {
745
+ return output(
746
+ fail(
747
+ "SUSPICIOUS_NAME",
748
+ `Project name '${opts.project}' looks temporary/test-like. If this is the real client project name, re-run with --force. Otherwise re-run with the actual project name.`
749
+ )
750
+ );
751
+ }
752
+ }
753
+
738
754
  // Refuse to clobber an active project unless --force.
739
755
  // Lifetime preservation runs lower in this fn — but current-phase fields
740
756
  // (phase, status, wave, tasks_done, tasks_total, gap_cycles) ARE wiped
@@ -1173,7 +1189,24 @@ function cmdCloseMilestone(opts) {
1173
1189
  t.milestone_name = readNextMilestoneNameFromJourney(t.milestone);
1174
1190
  t.last_updated = new Date().toISOString();
1175
1191
 
1176
- writeTracking(t);
1192
+ // v5.0 — journal-protected write. STATE.md is intentionally NOT mutated here
1193
+ // (the next /qualia-milestone step calls `state.js init --force` which fully
1194
+ // rewrites STATE.md for the new milestone). The journal still snapshots
1195
+ // tracking.json so a crash mid-write can be detected and recovered by the
1196
+ // next session via recoverFromJournal().
1197
+ const backupTracking = (() => {
1198
+ try { return fs.readFileSync(TRACKING_FILE, "utf8"); } catch { return null; }
1199
+ })();
1200
+ try {
1201
+ writeJournal(null, backupTracking);
1202
+ writeTracking(t);
1203
+ clearJournal();
1204
+ } catch (e) {
1205
+ // Restore tracking.json from backup if write failed mid-way.
1206
+ try { if (backupTracking) atomicWrite(TRACKING_FILE, backupTracking); } catch {}
1207
+ clearJournal();
1208
+ return output(fail("WRITE_ERROR", `close-milestone write failed: ${e.message}. tracking.json restored from backup.`));
1209
+ }
1177
1210
 
1178
1211
  _trace("close-milestone", "allow", {
1179
1212
  closed_milestone: closedMilestone,
@@ -0,0 +1,290 @@
1
+ # Qualia Framework — Install Redesign (v5.1.0 candidate)
2
+ ## Builder Agent Prompt
3
+
4
+ **Hand this entire file to a fresh Claude Code session.** Self-contained — no context from the originating session is needed. Same pattern as `docs/playwright-loop-builder-prompt.md` which produced v5.0's polish-loop work.
5
+
6
+ ---
7
+
8
+ ## What you are building
9
+
10
+ A redesigned installer for the Qualia Framework that:
11
+
12
+ 1. **Asks the user where to install** — Claude Code (`~/.claude/`), OpenAI Codex (`~/.codex/`), or both — and adapts the install per target
13
+ 2. **Shows real-time visual progress** during the install — what file is being read, what's being copied, what's being configured — not silent then suddenly done
14
+ 3. **Has top-notch cosmetics** throughout — every line of output is intentional and beautiful
15
+
16
+ The current installer (`bin/install.js`, ~830 lines) is functional but minimal. It copies files in batches and prints section headers ("Skills", "Templates", "Hooks") with terse `✓ {name}` lines. Fawzi wants the install to FEEL like a polished product — like the framework is announcing itself with care, not just dumping files.
17
+
18
+ ## Why this matters
19
+
20
+ - **First impression is the install.** Every Qualia user (Fawzi, Hasan, Moayad, Rama, Sally, future hires) experiences the framework first through `npx qualia-framework install`. A boring install signals a boring framework.
21
+ - **Cross-tool reach.** v5.0.0 already produces `AGENTS.md` (the cross-vendor open standard adopted by Codex, Cursor, Aider, Continue). Codex users could already use the framework today — they just don't know it because the installer only writes to `~/.claude/`. Multi-target makes that explicit.
22
+ - **Visual feedback is trust.** When the installer is silent for 5 seconds while it copies 32 skills + 12 hooks + 24 templates, the user wonders if it hung. Live progress makes the framework feel alive.
23
+
24
+ ## The repository you're working in
25
+
26
+ `/home/qualia-new/qualia-framework` — the Qualia Framework npm package, currently at v5.0.0 on branch `feat/env-empty-guard` (locally, not yet pushed). 274 tests pass. v5.0.0 release is queued for tomorrow morning publish to npm.
27
+
28
+ **Your work goes ON TOP of v5.0.0, as v5.1.0.** You'll bump version, add CHANGELOG entry, modify `bin/install.js`, possibly add `bin/install-cosmetics.js` helper, update tests.
29
+
30
+ ## Files to study before writing code
31
+
32
+ 1. `/home/qualia-new/qualia-framework/bin/install.js` — the current installer (~830 lines). Read it fully to understand the install phases (cleanup → CLAUDE.md/AGENTS.md → skills → agents → rules → hooks → settings.json → MCP → templates → knowledge layer → bin scripts → ERP config → final banner).
33
+ 2. `/home/qualia-new/qualia-framework/bin/qualia-ui.js` — the existing cosmetics helper (banners, dividers, end-cards, status). Reuse and extend; don't duplicate.
34
+ 3. `/home/qualia-new/qualia-framework/CLAUDE.md` and `AGENTS.md` — to understand what gets templated with `{{ROLE}}` for each install target
35
+ 4. `/home/qualia-new/qualia-framework/CHANGELOG.md` v5.0.0 entry — to match the writing style for v5.1.0
36
+ 5. `/home/qualia-new/qualia-framework/tests/bin.test.sh` — the install test patterns (lines ~440 onward have the install assertions you'll mirror)
37
+
38
+ ## The 3 deliverables
39
+
40
+ ### Deliverable 1 — Multi-target install (Claude Code / Codex / Both)
41
+
42
+ **Behavior:**
43
+
44
+ After the user enters their team code (existing flow), the installer asks:
45
+
46
+ ```
47
+ Where would you like to install Qualia?
48
+
49
+ [1] Claude Code only (recommended — full feature set)
50
+ [2] OpenAI Codex only (AGENTS.md + project-level rules)
51
+ [3] Both (max compatibility)
52
+
53
+ Choice [1]:
54
+ ```
55
+
56
+ Default is `1` (Claude Code only) for backward-compat. The user types `1`/`2`/`3` and Enter.
57
+
58
+ **Per-target install paths:**
59
+
60
+ | Target | Directory | What gets installed |
61
+ |---|---|---|
62
+ | Claude Code | `~/.claude/` | Everything as today (skills/, agents/, hooks/, rules/, qualia-templates/, knowledge/, settings.json, CLAUDE.md, AGENTS.md, bin/, .qualia-config.json) |
63
+ | Codex | `~/.codex/` (or wherever Codex config lives — research this) | A subset that Codex actually uses: `AGENTS.md` (the framework conventions), `instructions.md` if Codex uses one, possibly mirrored skills/ and templates/ if Codex's runtime supports skills (research this — it might not; in that case skip those) |
64
+ | Both | both directories | Run the Claude Code install first, then the Codex install |
65
+
66
+ **Critical research step you MUST do FIRST:**
67
+
68
+ Use `WebFetch` or `Bash` to research Codex CLI's actual configuration layout:
69
+
70
+ ```bash
71
+ # Check if user has codex installed
72
+ which codex 2>/dev/null
73
+ codex --version 2>/dev/null
74
+
75
+ # Look for documented config paths
76
+ ls ~/.codex/ 2>/dev/null
77
+ ls ~/.config/codex/ 2>/dev/null
78
+ ```
79
+
80
+ Search for: "OpenAI Codex CLI configuration directory", "Codex AGENTS.md", "Codex skills system" (if any).
81
+
82
+ If Codex CLI is too thin to support a meaningful framework install (e.g., it only reads AGENTS.md and that's it), say so explicitly in your output — don't pretend to install more than makes sense. Write a clear note in the CHANGELOG entry: "Codex install writes AGENTS.md only — Codex's runtime does not currently support skills/hooks/agents on disk."
83
+
84
+ **If Codex is not installed locally:**
85
+
86
+ Don't crash. Show a soft warning:
87
+
88
+ ```
89
+ ⚠ Codex CLI not detected on this system.
90
+ Installing AGENTS.md to ~/.codex/AGENTS.md anyway — Codex will pick it up
91
+ when you install via `npm install -g @openai/codex`.
92
+ ```
93
+
94
+ Then proceed with the Codex-side install (just the file writes).
95
+
96
+ ### Deliverable 2 — Real-time visual progress
97
+
98
+ **Current installer:** prints section headers, then a batch of `✓ {name}` lines all at once when each section completes. Looks like nothing → nothing → blast of green checkmarks.
99
+
100
+ **Redesigned installer:** every meaningful operation prints a "doing" line before, then updates to "done" after. Like a live activity log.
101
+
102
+ **Pattern (use Unicode box-drawing for the progression):**
103
+
104
+ ```
105
+ ▸ Skills (32)
106
+ ─────────────────────────────────────────
107
+ ⏳ Reading skills/ from framework...
108
+ ✓ Reading skills/ from framework
109
+ ⏳ Copying qualia-build...
110
+ ✓ qualia-build (3 files)
111
+ ⏳ Copying qualia-discuss...
112
+ ✓ qualia-discuss (1 file)
113
+ ⏳ Copying qualia-issues...
114
+ ✓ qualia-issues (1 file)
115
+ ...
116
+ ✓ Skills installed (32 total)
117
+ ```
118
+
119
+ Use ANSI escape sequences to OVERWRITE the `⏳` line with the `✓` line in place (cursor-up + clear-line) — so the output reads as a steadily-completing list, not a doubled set of lines.
120
+
121
+ **For long ops (template recursive copy, knowledge layer init, settings.json merge):**
122
+
123
+ Show a spinner that ticks every 100ms:
124
+
125
+ ```
126
+ ⠋ Merging settings.json (preserving 14 user keys)
127
+ ```
128
+
129
+ Implement as a tiny helper `startSpinner(text)` returning `stop({ status: "ok" | "warn" | "error", message?: string })`. Don't pull in `ora` or any external dep — write ~30 lines of vanilla Node using `process.stdout.write` + an interval.
130
+
131
+ **For each skill/agent/hook/rule/template copied:**
132
+
133
+ The current `ok({name})` print becomes a 2-line lifecycle:
134
+
135
+ 1. Before copy: `⏳ {name}` (dim, with a working glyph)
136
+ 2. After copy: `✓ {name}` (green, replacing the line above)
137
+
138
+ For very fast ops (single file, < 50ms) you can skip the "doing" state and go straight to ✓.
139
+
140
+ **Enhancements to existing `bin/qualia-ui.js`:**
141
+
142
+ Add these new helpers and export them:
143
+
144
+ - `spinner(text)` → `{ stop(result) }` — animated spinner with cursor-hide/show
145
+ - `step(text)` → returns a step-handle with `.ok(suffix?)`, `.warn(msg)`, `.fail(msg)` that overwrite the line
146
+ - `progress(current, total, label)` — render a bar like `[████████░░] 8/10 — Skills`
147
+ - `divider(width)` — variable-width dim divider
148
+ - `box({title, lines, color})` — boxed section like the welcome banner
149
+ - `kv(label, value, options?)` — left-padded label, right-aligned value, dim/highlight options
150
+
151
+ Preserve all existing `qualia-ui.js` exports — additive only.
152
+
153
+ ### Deliverable 3 — Top-notch cosmetics throughout
154
+
155
+ The full install output, end-to-end, should feel like a single intentional document. Specific things to fix:
156
+
157
+ **The current install banner** (the welcome card at the top with `⬢ Q U A L I A`) is good. Keep its bones, but consider:
158
+ - Subtle 2-color gradient on the title (teal → cyan) using truecolor escape
159
+ - A breath of vertical space (one blank line above and below)
160
+ - Version + framework subtitle aligned right of the hex glyph
161
+
162
+ **Section separators** between phases (Skills, Agents, Rules, Hooks, Templates, etc.):
163
+ - Currently: bold teal triangle + section name + dim divider
164
+ - Upgrade: add a 2-letter status indicator at the right margin showing pass count, like `▸ Skills 32 ✓`
165
+ - After each section completes, show a one-line "section summary" with the count and elapsed time: ` └─ 32 skills · 0.3s`
166
+
167
+ **Final summary card** (the "INSTALLED" banner at the end):
168
+ - Currently: name + role + version + 7 metric kv lines + Quick Start
169
+ - Upgrade: add a "Targets" row showing where you installed (`Claude Code · Codex · Both`)
170
+ - Add a "Time" row showing total install duration
171
+ - Add a contextual "First command to try" line based on whether the user has a `.planning/` already (suggest `/qualia` if they do, `/qualia-new` if not)
172
+
173
+ **Color palette discipline:**
174
+ - Match the existing OKLCH-tinted neutrals in `bin/qualia-ui.js`
175
+ - Don't introduce new colors — extend the existing teal/green/dim/white system
176
+ - Errors stay red (#ef4444), warns stay yellow (#eab308) — don't change these (muscle memory)
177
+
178
+ **Glyph rules:**
179
+ - ⬢ = brand mark, only for the welcome banner and final card
180
+ - ▸ = section header
181
+ - ✓ = success
182
+ - ⠋ ⠙ ⠹ ⠸ ⠼ ⠴ ⠦ ⠧ ⠇ ⠏ = spinner frames (Braille pattern dots)
183
+ - ⏳ = pending/in-flight
184
+ - ⚠ = warning
185
+ - ✗ = error
186
+ - └─ = section close
187
+ - · = inline separator
188
+
189
+ Don't introduce other glyphs without strong reason.
190
+
191
+ ## Hard constraints (non-negotiable)
192
+
193
+ 1. **Backward compatibility.** A user typing only their team code with no target choice (legacy stdin pipe `echo "QS-FAWZI-01" | npx qualia-framework install`) MUST still work — default to Claude Code only. Don't break the existing `bin.test.sh` install assertions.
194
+
195
+ 2. **No new external dependencies.** No `ora`, no `chalk`, no `ink`. Vanilla Node + ANSI escapes. The current installer is dependency-free; preserve that.
196
+
197
+ 3. **Cross-platform (Windows + macOS + Linux).** ANSI escapes work on modern Windows 10+ but verify the spinner doesn't break on `cmd.exe`. If `process.stdout.isTTY` is false (piped install), degrade gracefully — print plain `→ {name}` and `✓ {name}` lines instead of overwriting.
198
+
199
+ 4. **Atomic writes for new target paths.** When installing to `~/.codex/`, follow the same atomic-write (tmp + rename) pattern that v5.0 added for `settings.json` and CLAUDE.md.
200
+
201
+ 5. **Backups before overwrite** — same as v5.0's CLAUDE.md/settings.json discipline. If `~/.codex/AGENTS.md` exists with a different content hash than what we'd write, back it up to `~/.codex/AGENTS.md.bak.{ISO-timestamp}` before overwriting.
202
+
203
+ 6. **No surprise side effects.** Installing to Codex doesn't auto-install Codex CLI itself. If it's not present, write the files and tell the user to install Codex separately.
204
+
205
+ 7. **Slop-detect clean.** Run `node bin/slop-detect.mjs` on every modified `.md` file. The CHANGELOG and any new docs you write must pass.
206
+
207
+ 8. **Tests pass.** All 274 existing tests must continue to pass. Add new tests for: target selection (1/2/3 input), Codex install path creation, AGENTS.md backup-before-overwrite, spinner degradation when non-TTY, multi-target ("Both") flow.
208
+
209
+ ## Deliverables (the DONE definition)
210
+
211
+ Return DONE only when all of these are true:
212
+
213
+ 1. ✅ `bin/install.js` modified to ask the install-target question after the team code prompt
214
+ 2. ✅ Codex-target install actually writes to `~/.codex/` (or whatever the research found is correct)
215
+ 3. ✅ All file copies have a "before/after" 2-line lifecycle (or single-line for sub-50ms ops)
216
+ 4. ✅ `bin/qualia-ui.js` extended with `spinner()`, `step()`, `progress()`, `box()`, `kv()` helpers
217
+ 5. ✅ Final summary card shows install targets, total time, and contextual first-command suggestion
218
+ 6. ✅ TTY-degraded mode works (test: `echo QS-FAWZI-01 | node bin/install.js > /tmp/log.txt`; the log should be readable, no orphaned escape codes)
219
+ 7. ✅ Backups for existing AGENTS.md / instructions.md in Codex target dir
220
+ 8. ✅ All 274 v5.0 tests still pass + at least 5 new tests for v5.1 multi-target
221
+ 9. ✅ `node bin/slop-detect.mjs` clean on all modified .md files
222
+ 10. ✅ `package.json` bumped to 5.1.0
223
+ 11. ✅ CHANGELOG.md has a v5.1.0 entry with the same writing style as the v5.0.0 entry, including:
224
+ - The "why this matters" framing (first impression, cross-tool reach, visual trust)
225
+ - The 3 deliverables
226
+ - The Codex install scope (whatever the research determined)
227
+ - Honest caveats (what doesn't work, what's deferred)
228
+ 12. ✅ `docs/install-redesign-pilot.md` documenting:
229
+ - The full install output captured for each target choice (1, 2, 3)
230
+ - Screenshots or terminal-recording converted to ANSI text
231
+ - Timing measurements
232
+ - Any TTY-degradation issues found
233
+
234
+ ## Self-test scenarios (run all 3 before declaring DONE)
235
+
236
+ **Scenario 1 — Claude Code only.**
237
+ ```bash
238
+ TMP=$(mktemp -d)
239
+ echo -e "QS-FAWZI-01\n1\n" | HOME="$TMP" node bin/install.js > "$TMP/log.txt" 2>&1
240
+ ```
241
+ - Verify: `~/.claude/` populated as today. `~/.codex/` does not exist or is empty.
242
+ - Verify: `$TMP/log.txt` shows the new visual progress (every section has live updates).
243
+ - Verify: total install time is ≤ 2× the current install time (live updates shouldn't more than double cost).
244
+
245
+ **Scenario 2 — Codex only.**
246
+ ```bash
247
+ TMP=$(mktemp -d)
248
+ echo -e "QS-FAWZI-01\n2\n" | HOME="$TMP" node bin/install.js > "$TMP/log.txt" 2>&1
249
+ ```
250
+ - Verify: `~/.codex/AGENTS.md` exists with `Role: OWNER` substituted.
251
+ - Verify: `~/.claude/` is NOT populated (we picked target 2 only).
252
+ - Verify: log shows "Codex CLI not detected — file written anyway" if `which codex` fails.
253
+
254
+ **Scenario 3 — Both.**
255
+ ```bash
256
+ TMP=$(mktemp -d)
257
+ echo -e "QS-FAWZI-01\n3\n" | HOME="$TMP" node bin/install.js > "$TMP/log.txt" 2>&1
258
+ ```
259
+ - Verify: both `~/.claude/` and `~/.codex/` populated correctly.
260
+ - Verify: final summary card shows "Targets: Claude Code · Codex".
261
+
262
+ ## Things you MUST NOT do
263
+
264
+ - Don't change the team-code prompt itself (existing format works, has tests, don't break them).
265
+ - Don't add interactive yes/no prompts beyond the target-selection question — the install should still complete via stdin pipe with no extra input needed.
266
+ - Don't change `~/.claude/` install behavior beyond cosmetics. The semantic install logic (which files go where) stays exactly as v5.0.
267
+ - Don't wire Codex to any external API or auto-install. File copies only.
268
+ - Don't bump major version (still 5.x). This is a minor (5.1.0).
269
+ - Don't break the ERP API key save/load flow — `~/.claude/.erp-api-key` stays where it is.
270
+ - Don't drop the EXPERIMENTAL Playwright caveat in the v5.0.0 CHANGELOG — preserve it as historical context.
271
+
272
+ ## Reference: where v5.0's similar work happened
273
+
274
+ Look at how the polish-loop builder agent (commit `907312c`) handled a similar scope — building a flagship feature with self-tests, CHANGELOG, install assertions. Match its discipline:
275
+ - Per-iteration commits during pilot scenarios (`qpl-N:` prefix style — yours could be `install-redesign-N:`)
276
+ - Honest caveats in CHANGELOG (don't oversell)
277
+ - Pilot results doc with concrete measurements
278
+ - Trust-boundary comment in any new agent role file (none expected here, but if you create one, copy the pattern from `agents/builder.md` lines 11-17)
279
+
280
+ ## When you encounter unknowns
281
+
282
+ - **Codex config layout** — the most likely unknown. If web search + local probing don't give a clear answer, write the simplest version (just `~/.codex/AGENTS.md`) and document it explicitly: "Codex install scope: AGENTS.md only. If Codex's runtime later supports skills/hooks on disk, the framework can extend this."
283
+ - **TTY degradation edge cases** — if cmd.exe on Windows has issues, document and gate the spinner behind `process.platform !== "win32"` for the spinner specifically. The 2-line copy/✓ pattern should work everywhere.
284
+ - **Existing install test breakage** — if you can't avoid breaking a test, the test was overspecified. Update the test, but document WHY in the commit message.
285
+
286
+ ## One last thing
287
+
288
+ Fawzi (the framework owner, OWNER role) will read your output as the FIRST install he runs. He's tired tonight and trusting you to deliver. Honest reporting beats good-news theater. If something doesn't work cleanly, mark it experimental and say so — same discipline as the polish-loop EXPERIMENTAL Playwright caveat.
289
+
290
+ When you finish, write your DONE report at `docs/install-redesign-pilot.md` (file already mentioned above) and return a tight summary: targets working, lines of code added/changed, test count, slop-detect status, any BLOCKED items.