qualia-framework 5.3.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.
- package/README.md +50 -26
- package/agents/builder.md +8 -0
- package/agents/plan-checker.md +10 -1
- package/agents/planner.md +1 -1
- package/agents/qa-browser.md +10 -0
- package/agents/research-synthesizer.md +10 -0
- package/agents/researcher.md +38 -2
- package/agents/roadmapper.md +10 -0
- package/agents/verifier.md +15 -3
- package/agents/visual-evaluator.md +1 -1
- package/bin/install.js +42 -0
- package/bin/state.js +155 -133
- package/docs/archive/session-report-2026-04-18.md +199 -0
- package/docs/archive/v4.0.0-review.md +288 -0
- package/docs/instruction-budget-audit.md +113 -0
- package/guide.md +11 -4
- package/hooks/session-start.js +1 -1
- package/package.json +5 -2
- package/rules/architecture.md +125 -0
- package/rules/infrastructure.md +1 -2
- package/rules/speed.md +55 -0
- package/skills/qualia-help/SKILL.md +1 -1
- package/skills/qualia-map/SKILL.md +1 -1
- package/skills/qualia-milestone/SKILL.md +1 -1
- package/skills/qualia-new/SKILL.md +2 -2
- package/skills/qualia-optimize/REFERENCE.md +2 -2
- package/skills/qualia-optimize/SKILL.md +1 -1
- package/skills/qualia-polish/SKILL.md +3 -3
- package/skills/qualia-polish-loop/REFERENCE.md +1 -1
- package/skills/qualia-polish-loop/SKILL.md +3 -3
- package/skills/qualia-polish-loop/fixtures/broken.html +2 -2
- package/skills/qualia-polish-loop/scripts/score.mjs +1 -1
- package/skills/qualia-postmortem/SKILL.md +1 -1
- package/skills/qualia-quick/SKILL.md +1 -1
- package/skills/qualia-research/SKILL.md +5 -3
- package/skills/qualia-road/SKILL.md +15 -5
- package/skills/qualia-task/SKILL.md +1 -1
- package/templates/PRODUCT.md +1 -1
- package/tests/bin.test.sh +3 -3
- package/tests/skills.test.sh +143 -0
- package/tests/slop-detect.test.sh +160 -0
- /package/{rules → qualia-design}/design-brand.md +0 -0
- /package/{rules → qualia-design}/design-laws.md +0 -0
- /package/{rules → qualia-design}/design-product.md +0 -0
- /package/{rules → qualia-design}/design-reference.md +0 -0
- /package/{rules → qualia-design}/design-rubric.md +0 -0
- /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
|
-
//
|
|
563
|
-
|
|
564
|
-
|
|
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
|
-
//
|
|
595
|
-
// is retroactive bookkeeping: a phase was built without /qualia-plan and
|
|
596
|
-
// user is catching STATE.md up to reality.
|
|
597
|
-
// or MISSING_ARG — those would leave the state machine
|
|
598
|
-
|
|
599
|
-
|
|
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
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
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
|
-
|
|
634
|
-
|
|
635
|
-
|
|
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
|
-
//
|
|
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.*
|