qualia-framework 5.1.0 → 5.4.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 (53) hide show
  1. package/README.md +50 -26
  2. package/agents/builder.md +8 -0
  3. package/agents/plan-checker.md +10 -1
  4. package/agents/planner.md +1 -1
  5. package/agents/qa-browser.md +10 -0
  6. package/agents/research-synthesizer.md +10 -0
  7. package/agents/researcher.md +38 -2
  8. package/agents/roadmapper.md +10 -0
  9. package/agents/verifier.md +15 -3
  10. package/agents/visual-evaluator.md +1 -1
  11. package/bin/install.js +42 -0
  12. package/bin/state.js +155 -133
  13. package/docs/archive/session-report-2026-04-18.md +199 -0
  14. package/docs/archive/v4.0.0-review.md +288 -0
  15. package/docs/instruction-budget-audit.md +113 -0
  16. package/docs/polish-loop-supervised-run.md +111 -0
  17. package/guide.md +11 -4
  18. package/hooks/session-start.js +1 -1
  19. package/package.json +5 -2
  20. package/rules/architecture.md +125 -0
  21. package/rules/infrastructure.md +1 -2
  22. package/rules/speed.md +55 -0
  23. package/skills/qualia-help/SKILL.md +1 -1
  24. package/skills/qualia-hook-gen/SKILL.md +206 -0
  25. package/skills/qualia-map/SKILL.md +1 -1
  26. package/skills/qualia-milestone/SKILL.md +1 -1
  27. package/skills/qualia-new/SKILL.md +2 -2
  28. package/skills/qualia-optimize/REFERENCE.md +65 -2
  29. package/skills/qualia-optimize/SKILL.md +26 -1
  30. package/skills/qualia-polish/SKILL.md +3 -3
  31. package/skills/qualia-polish-loop/REFERENCE.md +1 -1
  32. package/skills/qualia-polish-loop/SKILL.md +3 -3
  33. package/skills/qualia-polish-loop/fixtures/broken.html +2 -2
  34. package/skills/qualia-polish-loop/scripts/loop.mjs +26 -5
  35. package/skills/qualia-polish-loop/scripts/playwright-capture.mjs +14 -5
  36. package/skills/qualia-polish-loop/scripts/score.mjs +1 -1
  37. package/skills/qualia-postmortem/SKILL.md +1 -1
  38. package/skills/qualia-prd/SKILL.md +199 -0
  39. package/skills/qualia-quick/SKILL.md +1 -1
  40. package/skills/qualia-research/SKILL.md +5 -3
  41. package/skills/qualia-road/SKILL.md +15 -5
  42. package/skills/qualia-task/SKILL.md +1 -1
  43. package/templates/PRODUCT.md +1 -1
  44. package/tests/bin.test.sh +155 -8
  45. package/tests/skills.test.sh +143 -0
  46. package/tests/slop-detect.test.sh +160 -0
  47. package/docs/playwright-loop-review-2026-05-03.md +0 -65
  48. /package/{rules → qualia-design}/design-brand.md +0 -0
  49. /package/{rules → qualia-design}/design-laws.md +0 -0
  50. /package/{rules → qualia-design}/design-product.md +0 -0
  51. /package/{rules → qualia-design}/design-reference.md +0 -0
  52. /package/{rules → qualia-design}/design-rubric.md +0 -0
  53. /package/{rules → qualia-design}/frontend.md +0 -0
package/bin/state.js CHANGED
@@ -537,6 +537,139 @@ function cmdCheck(opts) {
537
537
  });
538
538
  }
539
539
 
540
+ // ─── cmdTransition helpers ──────────────────────────────────────────────
541
+ // cmdTransition was a 195-line orchestrator that mixed validation, the
542
+ // note/activity short-circuit, per-status mutations, atomic write, and
543
+ // trace+output into one block. Split into focused helpers below; the
544
+ // orchestrator now reads top-to-bottom in ~50 lines. Behavior is
545
+ // preserved exactly — verified by the 59 state.test.sh assertions.
546
+
547
+ // Handles the note/activity short-circuit. Returns the success payload
548
+ // when the target is note/activity (caller should `return output(...)`),
549
+ // or null if not handled (caller proceeds with normal transition).
550
+ function applyNoteOrActivity(target, s, t, opts) {
551
+ if (target !== "note" && target !== "activity") return null;
552
+ if (opts.notes) t.notes = opts.notes;
553
+ // Count tasks from quick/task work toward lifetime
554
+ if (opts.tasks_done) {
555
+ const count = parseInt(opts.tasks_done) || 0;
556
+ if (count > 0) {
557
+ ensureLifetime(t);
558
+ t.lifetime.tasks_completed += count;
559
+ }
560
+ }
561
+ t.last_updated = new Date().toISOString();
562
+ writeTracking(t);
563
+ s.last_activity = opts.notes || "Activity logged";
564
+ writeStateMd(s);
565
+ return {
566
+ ok: true,
567
+ phase: s.phase,
568
+ status: s.status,
569
+ action: target,
570
+ };
571
+ }
572
+
573
+ // Per-status mutations. Each helper mutates `s` and `t` in place (JS
574
+ // objects are by-reference) and returns nothing. They DO NOT write to
575
+ // disk — the orchestrator commits everything atomically at the end.
576
+
577
+ function applyPlannedTransition(s, t, prevStatus, phase) {
578
+ // Gap closure: increment counter if coming from verified(fail).
579
+ if (prevStatus === "verified") {
580
+ if (!t.gap_cycles) t.gap_cycles = {};
581
+ t.gap_cycles[String(phase)] = (t.gap_cycles[String(phase)] || 0) + 1;
582
+ s.last_activity = `Gap closure #${t.gap_cycles[String(phase)]} planned (phase ${phase})`;
583
+ }
584
+ if (s.phases[phase - 1]) s.phases[phase - 1].status = "planned";
585
+ }
586
+
587
+ function applyBuiltTransition(s, t, opts, phase) {
588
+ t.tasks_done = parseInt(opts.tasks_done) || 0;
589
+ t.tasks_total = parseInt(opts.tasks_total) || 0;
590
+ t.wave = parseInt(opts.wave) || 0;
591
+ t.build_count = (parseInt(t.build_count) || 0) + 1;
592
+ s.last_activity = `Phase ${phase} built (${t.tasks_done}/${t.tasks_total} tasks)`;
593
+ if (s.phases[phase - 1]) s.phases[phase - 1].status = "built";
594
+ }
595
+
596
+ function applyVerifiedTransition(s, t, opts, phase) {
597
+ t.verification = opts.verification;
598
+ s.last_activity = `Phase ${phase} verified — ${opts.verification}`;
599
+ if (s.phases[phase - 1]) {
600
+ s.phases[phase - 1].status =
601
+ opts.verification === "pass" ? "verified" : "failed";
602
+ }
603
+
604
+ if (opts.verification !== "pass") return;
605
+
606
+ // Auto-advance on pass: accumulate into lifetime BEFORE resetting
607
+ // current counters so the totals are correct.
608
+ ensureLifetime(t);
609
+ t.lifetime.tasks_completed += (t.tasks_done || 0);
610
+ t.lifetime.phases_completed += 1;
611
+
612
+ if (phase < s.total_phases) {
613
+ s.phase = phase + 1;
614
+ s.phase_name = s.phases[phase]?.name || `Phase ${phase + 1}`;
615
+ s.status = "setup";
616
+ t.phase = s.phase;
617
+ t.phase_name = s.phase_name;
618
+ t.status = "setup";
619
+ t.verification = "pending";
620
+ t.tasks_done = 0;
621
+ t.tasks_total = 0;
622
+ s.last_activity = `Phase ${phase} passed — advancing to phase ${s.phase}`;
623
+ }
624
+ // Reset gap counter for the passed phase.
625
+ if (t.gap_cycles) t.gap_cycles[String(phase)] = 0;
626
+ }
627
+
628
+ function applyPolishedTransition(s) {
629
+ // Polish is a whole-project pass. Mark every already-passed phase as
630
+ // polished. (Previously only the last row was touched and was set to
631
+ // "verified" — wrong status string, lost current-phase context.)
632
+ for (const p of s.phases) {
633
+ const st = (p.status || "").toLowerCase();
634
+ if (st === "verified" || st === "polished" || st === "completed" || st === "complete") {
635
+ p.status = "polished";
636
+ }
637
+ }
638
+ }
639
+
640
+ function applyShippedTransition(t, opts) {
641
+ t.deployed_url = opts.deployed_url || "";
642
+ t.deploy_count = (parseInt(t.deploy_count) || 0) + 1;
643
+ }
644
+
645
+ // Atomically commit the new state + tracking. Writes a journal snapshot
646
+ // of the pre-transition files first; if the process dies between the
647
+ // two real writes, the next invocation restores both from the journal.
648
+ // Each individual write is torn-write safe (tmp + rename); the journal
649
+ // closes the gap between the two writes.
650
+ //
651
+ // Returns null on success, or a `fail(...)` object on write error
652
+ // (caller should pass through to `output(...)`).
653
+ function commitTransitionAtomic(s, t) {
654
+ const backupState = readState();
655
+ const backupTracking = (() => {
656
+ try { return fs.readFileSync(TRACKING_FILE, "utf8"); } catch { return null; }
657
+ })();
658
+ try {
659
+ writeJournal(backupState, backupTracking);
660
+ writeStateMd(s);
661
+ writeTracking(t);
662
+ clearJournal();
663
+ return null;
664
+ } catch (e) {
665
+ // Revert whichever file is out of sync with pre-transition state.
666
+ try { if (backupState) atomicWrite(STATE_FILE, backupState); } catch {}
667
+ try { if (backupTracking) atomicWrite(TRACKING_FILE, backupTracking); } catch {}
668
+ clearJournal();
669
+ return fail("WRITE_ERROR", e.message);
670
+ }
671
+ }
672
+
540
673
  function cmdTransition(opts) {
541
674
  const target = opts.to;
542
675
  if (!target) return output(fail("MISSING_ARG", "--to is required"));
@@ -544,9 +677,7 @@ function cmdTransition(opts) {
544
677
  const t = readTracking();
545
678
  const s = parseStateMd(readState());
546
679
  if (!t || !s) {
547
- return output(
548
- fail("NO_PROJECT", "No .planning/ found. Run /qualia-new.")
549
- );
680
+ return output(fail("NO_PROJECT", "No .planning/ found. Run /qualia-new."));
550
681
  }
551
682
 
552
683
  // Refuse transitions if STATE.md has schema errors (severity=error)
@@ -559,47 +690,21 @@ function cmdTransition(opts) {
559
690
  );
560
691
  }
561
692
 
562
- // Special: note/activity (no status change)
563
- if (target === "note" || target === "activity") {
564
- if (opts.notes) t.notes = opts.notes;
565
- // Count tasks from quick/task work toward lifetime
566
- if (opts.tasks_done) {
567
- const count = parseInt(opts.tasks_done) || 0;
568
- if (count > 0) {
569
- ensureLifetime(t);
570
- t.lifetime.tasks_completed += count;
571
- }
572
- }
573
- t.last_updated = new Date().toISOString();
574
- writeTracking(t);
575
- s.last_activity = opts.notes || "Activity logged";
576
- writeStateMd(s);
577
- return output({
578
- ok: true,
579
- phase: s.phase,
580
- status: s.status,
581
- action: target,
582
- });
583
- }
693
+ // Note/activity short-circuit (no status change, no precondition check)
694
+ const noteResult = applyNoteOrActivity(target, s, t, opts);
695
+ if (noteResult) return output(noteResult);
584
696
 
585
697
  const phase = parseInt(opts.phase) || s.phase;
586
698
 
587
699
  // Precondition check
588
- const check = checkPreconditions(
589
- { ...s, phase },
590
- target,
591
- { ...opts, phase }
592
- );
700
+ const check = checkPreconditions({ ...s, phase }, target, { ...opts, phase });
593
701
  if (!check.ok) {
594
- // Force bypasses status-ordering errors AND plan-content errors. The use case
595
- // is retroactive bookkeeping: a phase was built without /qualia-plan and the
596
- // user is catching STATE.md up to reality. `--force` never bypasses MISSING_FILE
597
- // or MISSING_ARG — those would leave the state machine pointing at nothing.
598
- const forceableErrors = [
599
- "PRECONDITION_FAILED",
600
- "GAP_CYCLE_LIMIT",
601
- "INVALID_PLAN",
602
- ];
702
+ // --force bypasses status-ordering and plan-content errors. The use case
703
+ // is retroactive bookkeeping: a phase was built without /qualia-plan and
704
+ // the user is catching STATE.md up to reality. --force never bypasses
705
+ // MISSING_FILE or MISSING_ARG — those would leave the state machine
706
+ // pointing at nothing.
707
+ const forceableErrors = ["PRECONDITION_FAILED", "GAP_CYCLE_LIMIT", "INVALID_PLAN"];
603
708
  if (opts.force && forceableErrors.includes(check.error)) {
604
709
  console.error(`WARNING: Forcing transition despite: ${check.message}`);
605
710
  } else {
@@ -609,109 +714,26 @@ function cmdTransition(opts) {
609
714
 
610
715
  const prevStatus = s.status;
611
716
 
612
- // Apply transition
717
+ // Apply common transition fields
613
718
  s.status = target;
614
719
  s.last_activity = `${target} (phase ${phase})`;
615
-
616
- // Update tracking fields
617
720
  t.status = target;
618
721
  t.phase = phase;
619
722
  t.phase_name = s.phases[phase - 1]?.name || s.phase_name;
620
723
  t.last_updated = new Date().toISOString();
621
724
 
622
- if (target === "planned") {
623
- // Gap closure: increment counter if coming from verified(fail)
624
- if (prevStatus === "verified") {
625
- if (!t.gap_cycles) t.gap_cycles = {};
626
- t.gap_cycles[String(phase)] = (t.gap_cycles[String(phase)] || 0) + 1;
627
- s.last_activity = `Gap closure #${t.gap_cycles[String(phase)]} planned (phase ${phase})`;
628
- }
629
- // Update roadmap
630
- if (s.phases[phase - 1]) s.phases[phase - 1].status = "planned";
631
- }
725
+ // Per-status mutations
726
+ if (target === "planned") applyPlannedTransition(s, t, prevStatus, phase);
727
+ if (target === "built") applyBuiltTransition(s, t, opts, phase);
728
+ if (target === "verified") applyVerifiedTransition(s, t, opts, phase);
729
+ if (target === "polished") applyPolishedTransition(s);
730
+ if (target === "shipped") applyShippedTransition(t, opts);
632
731
 
633
- if (target === "built") {
634
- t.tasks_done = parseInt(opts.tasks_done) || 0;
635
- t.tasks_total = parseInt(opts.tasks_total) || 0;
636
- t.wave = parseInt(opts.wave) || 0;
637
- t.build_count = (parseInt(t.build_count) || 0) + 1;
638
- s.last_activity = `Phase ${phase} built (${t.tasks_done}/${t.tasks_total} tasks)`;
639
- if (s.phases[phase - 1]) s.phases[phase - 1].status = "built";
640
- }
641
-
642
- if (target === "verified") {
643
- t.verification = opts.verification;
644
- s.last_activity = `Phase ${phase} verified — ${opts.verification}`;
645
- if (s.phases[phase - 1])
646
- s.phases[phase - 1].status =
647
- opts.verification === "pass" ? "verified" : "failed";
648
-
649
- // Auto-advance on pass
650
- if (opts.verification === "pass") {
651
- // Accumulate into lifetime BEFORE resetting current counters
652
- ensureLifetime(t);
653
- t.lifetime.tasks_completed += (t.tasks_done || 0);
654
- t.lifetime.phases_completed += 1;
655
-
656
- if (phase < s.total_phases) {
657
- s.phase = phase + 1;
658
- s.phase_name = s.phases[phase]?.name || `Phase ${phase + 1}`;
659
- s.status = "setup";
660
- t.phase = s.phase;
661
- t.phase_name = s.phase_name;
662
- t.status = "setup";
663
- t.verification = "pending";
664
- t.tasks_done = 0;
665
- t.tasks_total = 0;
666
- s.last_activity = `Phase ${phase} passed — advancing to phase ${s.phase}`;
667
- }
668
- // Reset gap counter for the passed phase
669
- if (t.gap_cycles) t.gap_cycles[String(phase)] = 0;
670
- }
671
- }
672
-
673
- if (target === "polished") {
674
- // Mark every passed phase as polished (polish is a whole-project pass).
675
- // Previously only the last roadmap row was touched, and was set to
676
- // "verified" — which both lost current-phase context and used the wrong
677
- // status string. Now we use "polished" on every row that's already at
678
- // verified or polished or completed.
679
- for (const p of s.phases) {
680
- const st = (p.status || "").toLowerCase();
681
- if (st === "verified" || st === "polished" || st === "completed" || st === "complete") {
682
- p.status = "polished";
683
- }
684
- }
685
- }
686
-
687
- if (target === "shipped") {
688
- t.deployed_url = opts.deployed_url || "";
689
- t.deploy_count = (parseInt(t.deploy_count) || 0) + 1;
690
- }
691
-
692
- // Write both files. We write a journal snapshot of the pre-transition
693
- // STATE.md + tracking.json first; if the process dies between the two
694
- // real writes, the next invocation will see the journal and restore both
695
- // files to the pre-transition state. Each individual write is torn-write
696
- // safe (tmp + rename); the journal closes the gap between the two.
697
- const backupState = readState();
698
- const backupTracking = (() => {
699
- try { return fs.readFileSync(TRACKING_FILE, "utf8"); } catch { return null; }
700
- })();
701
- try {
702
- writeJournal(backupState, backupTracking);
703
- writeStateMd(s);
704
- writeTracking(t);
705
- clearJournal();
706
- } catch (e) {
707
- // Revert whichever file is out of sync with pre-transition state.
708
- try { if (backupState) atomicWrite(STATE_FILE, backupState); } catch {}
709
- try { if (backupTracking) atomicWrite(TRACKING_FILE, backupTracking); } catch {}
710
- clearJournal();
711
- return output(fail("WRITE_ERROR", e.message));
712
- }
732
+ // Atomic commit
733
+ const writeError = commitTransitionAtomic(s, t);
734
+ if (writeError) return output(writeError);
713
735
 
714
- // Skill outcome scoring — log transition for analytics
736
+ // Trace transition for analytics
715
737
  _trace("state-transition", "allow", {
716
738
  phase: s.phase,
717
739
  status: s.status,
@@ -0,0 +1,199 @@
1
+ # Session Report — 2026-04-18
2
+
3
+ **Project:** qualia-framework
4
+ **Branch:** feature/full-journey (local, not pushed)
5
+ **Released:** v3.7.0 (staged on feature/story-file-plans) and **v4.0.0** (staged on feature/full-journey)
6
+ **Owner:** Fawzi Goussous
7
+ **Duration:** ~4 hours
8
+
9
+ ## What this session shipped
10
+
11
+ Two releases staged on separate feature branches, ready for push + merge + `npm publish`.
12
+
13
+ | Tag | Branch | Commits | Theme |
14
+ |---|---|---|---|
15
+ | **v3.7.0** | feature/story-file-plans | 1 commit (8ae5b0e) | Story-file plan format — every phase plan task carries inline Why, Acceptance Criteria, Validation, explicit Depends on |
16
+ | **v4.0.0** | feature/full-journey | 8 commits total (includes v3.7.0) | Full Journey release — /qualia-new maps the entire arc to handoff, --auto chains the Road end-to-end, milestone hierarchy locked, /qualia-idk rebuilt as diagnostician |
17
+
18
+ **Test suite:** 150 → 156 green (+6 v4 coverage, 0 regressions).
19
+ **Package:** version bumped 3.6.0 → 4.0.0.
20
+ **Release candidate:** v4.0.0 commit is `f790554` on feature/full-journey.
21
+
22
+ ## Background & trigger
23
+
24
+ Fawzi came in with three pain points from Sakani's ERP view:
25
+ 1. "Unphased Tasks" and "Phase 0: Central Bank Demo" rendering at the same tree level as "Milestone 1", "Milestone 2", etc. — milestones collapsed into single phases, numbering skipped (missing M5).
26
+ 2. `/qualia-new` stops at v1 — team improvises subsequent milestones, no clear path to handoff.
27
+ 3. Team members don't understand the framework's dev cycle. Need something a non-technical person can follow without getting lost.
28
+
29
+ Plus: "make it sexier," personalize the Qualia experience, enhance every command for best practices, ship a stable v4.0.
30
+
31
+ ## The approach
32
+
33
+ Split the work into labeled phases on a feature branch so commits tell the story:
34
+
35
+ ### v3.7.0 prerequisite — story-file plans
36
+ - `templates/plan.md` rewritten to 7-field story-file format (Wave, Files, Depends on, Why, Acceptance Criteria, Action, Validation, Context + optional Persona)
37
+ - `agents/planner.md` generates the new format
38
+ - `agents/plan-checker.md` validates the 7 fields and cross-checks wave↔deps consistency
39
+ - `agents/builder.md` reads rationale, respects Depends on, runs Validation before commit
40
+ - `agents/verifier.md` adds 3-layer check (phase Success Criteria + per-task AC + Verification Contract)
41
+ - `bin/qualia-ui.js` adds `plan-summary` command — terminal dashboard with colored persona chips, dependency arrows, AC/check counts per task
42
+ - `bin/state.js` accepts both legacy `Done when:` and new `Acceptance Criteria:` anchors (backward-compatible)
43
+
44
+ Committed on `feature/story-file-plans` as v3.7.0 (`8ae5b0e`). Standalone useful release; branched v4 off of it.
45
+
46
+ ### v4.0.0 — Full Journey, phased
47
+
48
+ **Phase A — Model foundation** (`2e371c2`)
49
+ - `templates/journey.md` — new JOURNEY.md schema (hard ceiling 5 milestones, hard floor 2, final milestone always Handoff with 4 fixed phases)
50
+ - `tracking.json` gains `milestone_name`, `milestones[]` (array of closed milestone summaries)
51
+ - `state.js close-milestone` readiness guards: MILESTONE_NOT_READY, MILESTONE_TOO_SMALL (both bypassable with --force)
52
+ - `state.js init` accepts `--milestone_name` and preserves milestones[] across re-init
53
+ - Tests: +4
54
+
55
+ **Phase B — Roadmapper + /qualia-new full-journey output** (`87af253`)
56
+ - `agents/roadmapper.md` rewritten: now produces JOURNEY.md (all milestones) + REQUIREMENTS.md (grouped by milestone) + ROADMAP.md (Milestone 1's phase detail only)
57
+ - **Dropped** the old "no review/deploy/handoff phases" rule. **Replaced** with: final milestone is always literally named "Handoff" with fixed 4 phases.
58
+ - `skills/qualia-new/SKILL.md` rewritten: always runs research (no more `workflow.research` gate), single approval on the whole journey, new `--auto` flag
59
+ - `agents/research-synthesizer.md` thinks across all milestones
60
+ - `skills/qualia-milestone/SKILL.md` reads next milestone from JOURNEY.md (no longer asks user)
61
+ - `templates/requirements.md` multi-milestone format with standard Handoff section (HAND-01..HAND-15)
62
+ - `templates/roadmap.md` scoped to current milestone only
63
+
64
+ **Phase C — Auto-chain wiring** (`400cd17`)
65
+ - `--auto` flag added to `/qualia-plan`, `/qualia-build`, `/qualia-verify`, `/qualia-milestone`
66
+ - Auto-chain decision table in `/qualia-verify`: PASS → next phase OR milestone close OR ship+handoff; FAIL → gap closure OR halt at limit
67
+ - Two human gates total per project: journey approval + each milestone boundary
68
+
69
+ **Phase D+E — Builder pre-inline + journey visualization** (`74dd26e`)
70
+ - Builder pre-inlines PROJECT.md + DESIGN.md + Context files into agent prompt BEFORE spawning (GSD-pattern borrowing, saves 3-5 Read calls per task)
71
+ - `qualia-ui.js journey-tree` renders JOURNEY.md as ASCII ladder (green shipped, teal current, dim future, [FINAL] tag for Handoff)
72
+ - `qualia-ui.js milestone-complete` celebration banner
73
+ - 5 new banner actions: milestone ◆, journey ◯, auto ⚡, research ◱, roadmap ◐
74
+ - `/qualia` router renders journey-tree for "you are here" orientation
75
+ - `/qualia-milestone` renders journey-tree + milestone-complete
76
+
77
+ **Phase F — Ship-side fields + handoff deliverables** (`b41a52d`)
78
+ - `state.js` bumps `build_count` on each 'built' transition, `deploy_count` on each 'shipped' (previously never incremented)
79
+ - `qualia-report` ERP payload now includes all v4 fields (project_id, team_id, git_remote, milestone_name, milestones[], build_count, deploy_count, session_started_at, last_pushed_at)
80
+ - `/qualia-handoff` rewritten: explicit 4 mandatory deliverables — production URL verified (HTTP + latency + auth), docs updated, `.planning/archive/` check, final ERP report
81
+
82
+ **Phase G — Smoke test fix + coverage** (`f62e753`)
83
+ - Bug fix: close-milestone summary was recording current phase's tasks, not cumulative milestone total. Fixed via `lifetime.tasks_completed − sum(prior milestones[].tasks_completed)`
84
+ - Tests: +2 (cumulative task count, build_count bump)
85
+
86
+ **Release (`f790554`)**
87
+ - Single unified v4.0.0 release commit
88
+ - Package bumped 3.7.0 → 4.0.0
89
+ - CHANGELOG.md v4.0.0 entry with full feature list + migration notes
90
+ - `/qualia-idk` rebuilt as real diagnostician (was briefly v4.0.1 in the branch history, folded into v4.0.0 per owner request)
91
+
92
+ ### The `/qualia-idk` rebuild (folded into v4.0.0)
93
+
94
+ Before: thin one-line alias to `/qualia`.
95
+
96
+ Now: interpretive diagnostic. When user says "I don't know what's going on" / "something feels off":
97
+ 1. Spawns **Plan view** Explore subagent — reads only `.planning/*`, reports what plan says
98
+ 2. Spawns **Code view** Explore subagent in parallel — reads only source code, reports what's actually built, cites file:line
99
+ 3. Synthesizes: structured "What I see / The mismatch / What I think is happening / What to do next" in plain language
100
+ 4. Maps recommendations to existing commands where applicable
101
+
102
+ `/qualia` description scoped back to mechanical state routing — no longer claims "idk/stuck/lost/confused" triggers (those route to `/qualia-idk` now).
103
+
104
+ ## Competitive research completed
105
+
106
+ During the session, ran deep research on competing Claude Code frameworks:
107
+ - **Superpowers** (Jesse Vincent) — 93K stars — rigid 5-phase gates, Visual Companion, cross-agent portability
108
+ - **BMAD-METHOD** — 43K stars — 12-19 named role agents, story files, expansion packs
109
+ - **gstack** (Garry Tan) — 74K stars — 23-role team, persistent browser daemon, /codex cross-model review
110
+ - **GSD** (Get Shit Done, v1+v2) — 54K + 6K stars — state-machine auto-advance, pre-inlined dispatch, adaptive replanning
111
+ - **SuperClaude, Agent OS, Context Engineering/PRP, Claude-Flow, Cursor 2.0, Windsurf** — broader landscape
112
+
113
+ Patterns borrowed in v4.0.0:
114
+ - **Story-file plan format** (BMAD) — inline rationale + acceptance criteria
115
+ - **State-machine auto-advance** (GSD v2) — the --auto chain
116
+ - **Pre-inline context at dispatch** (GSD v2) — builder's <pre-loaded-context> block
117
+ - **Journey-as-first-class-artifact** (NotebookLM synthesis of Qualia's own docs)
118
+
119
+ Patterns deferred to v4.1+:
120
+ - Visual/mockup generation (gstack design-shotgun, Superpowers Visual Companion)
121
+ - Cross-model review (gstack /codex)
122
+ - Cross-project vector memory (claude-mem, Claude-Flow)
123
+ - IDE integration
124
+ - Token-budget compression
125
+
126
+ ## Verification at checkpoint
127
+
128
+ ```
129
+ ✅ Git 8 commits clean on feature/full-journey
130
+ ✅ Tests 156/156 pass
131
+ ✅ Smoke state transitions + close-milestone + milestones[] summary verified on scratch project
132
+ ✅ UI journey-tree + milestone-complete render correctly with real fixtures
133
+ ✅ Backward compat older tracking.json without milestones[]/milestone_name still passes state.js check
134
+ ✅ No regressions Vs v3.6 baseline — all 150 existing tests still pass
135
+ ✅ Package version 4.0.0, ready for `npm publish`
136
+ ✅ Handoff V4_REVIEW.md written for next agent
137
+ ```
138
+
139
+ ## Pending (not done by this session)
140
+
141
+ - **Git push** — both branches are local only
142
+ - **Merge to main** — owner decides merge strategy (recommend v3.7.0 tag then v4.0.0 tag)
143
+ - **`npm publish`** — requires owner auth
144
+ - **Tag creation** — after merge
145
+ - **Live auto-chain verification** — smoke tests covered state machine; the --auto chain (which spawns planner→builder→verifier subagents in sequence) wasn't exercised end-to-end. Recommended first real test is on a throwaway project post-publish.
146
+ - **ERP-side updates** — v4 tracking.json fields are additive; ERP should accept them via its existing Zod strip-unknowns pattern, but worth verifying on deploy.
147
+
148
+ ## Files the next reviewer should eyeball
149
+
150
+ Priority order (highest leverage first):
151
+
152
+ 1. `V4_REVIEW.md` — this file lives in repo root, written as the handoff doc
153
+ 2. `CHANGELOG.md` [4.0.0] section — full feature list
154
+ 3. `templates/journey.md` — new artifact schema
155
+ 4. `agents/roadmapper.md` — biggest agent rewrite
156
+ 5. `skills/qualia-new/SKILL.md` — core flow, 14 steps
157
+ 6. `skills/qualia-idk/SKILL.md` — new diagnostic
158
+ 7. `skills/qualia-verify/SKILL.md` — auto-chain decision table
159
+ 8. `skills/qualia-handoff/SKILL.md` — 4 deliverables enforcement
160
+ 9. `bin/state.js` close-milestone (~975-1050) — readiness guards + summary append
161
+ 10. `bin/qualia-ui.js` journey-tree + milestone-complete functions
162
+
163
+ ## Recommended publish sequence
164
+
165
+ ```bash
166
+ # Push both branches
167
+ git push -u origin feature/story-file-plans
168
+ git push -u origin feature/full-journey
169
+
170
+ # Merge v3.7.0 to main, tag, push
171
+ git checkout main
172
+ git merge --ff-only feature/story-file-plans
173
+ git tag v3.7.0
174
+ git push origin main --tags
175
+
176
+ # Merge v4.0.0 to main, tag, push
177
+ git merge --ff-only feature/full-journey
178
+ git tag v4.0.0
179
+ git push origin main --tags
180
+
181
+ # Publish
182
+ npm publish
183
+ npm view qualia-framework version # should return 4.0.0
184
+ ```
185
+
186
+ ## Known limitations / open questions
187
+
188
+ 1. **Auto-chain not live-tested.** Unit tests cover state transitions; subagent chaining is covered by the skill prompt text but not exercised. First real project run will reveal edge cases.
189
+ 2. **Milestone-summary `tasks_completed` accuracy** depends on lifetime counter deltas. Works if close-milestone is the only consumer — verified in test suite.
190
+ 3. **/qualia-idk two-pass isolation** depends on Explore subagent respecting the "do not read .planning/" and "do not read source code" instructions. Prompt-level enforcement, not hard boundary.
191
+ 4. **Builder pre-inline** inflates prompt size for monorepos with large DESIGN.md + multiple context files. Acceptable for most projects; could be optimized in v4.1.
192
+
193
+ ## For questions
194
+
195
+ Contact: Fawzi Goussous — fawzi@qualiasolutions.net
196
+
197
+ ---
198
+
199
+ *Written 2026-04-18 at release-candidate time. Supersedes the 2026-04-17 session report covering v3.4.2 / v3.5.0 / v3.6.0.*