qualia-framework 4.0.0 → 4.0.5
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/CLAUDE.md +23 -11
- package/agents/plan-checker.md +1 -1
- package/agents/roadmapper.md +10 -5
- package/bin/cli.js +139 -17
- package/bin/install.js +47 -47
- package/bin/qualia-ui.js +2 -2
- package/bin/state.js +126 -9
- package/bin/statusline.js +63 -38
- package/docs/erp-contract.md +49 -2
- package/guide.md +1 -1
- package/hooks/migration-guard.js +23 -9
- package/hooks/pre-compact.js +39 -11
- package/hooks/pre-deploy-gate.js +3 -4
- package/hooks/pre-push.js +6 -3
- package/hooks/session-start.js +8 -8
- package/package.json +1 -1
- package/rules/frontend.md +5 -13
- package/skills/qualia/SKILL.md +5 -0
- package/skills/qualia-build/SKILL.md +10 -0
- package/skills/qualia-debug/SKILL.md +6 -0
- package/skills/qualia-design/SKILL.md +9 -1
- package/skills/qualia-discuss/SKILL.md +6 -0
- package/skills/qualia-handoff/SKILL.md +5 -0
- package/skills/qualia-help/SKILL.md +18 -4
- package/skills/qualia-idk/SKILL.md +6 -0
- package/skills/qualia-learn/SKILL.md +6 -0
- package/skills/qualia-map/SKILL.md +7 -0
- package/skills/qualia-milestone/SKILL.md +6 -0
- package/skills/qualia-new/SKILL.md +31 -4
- package/skills/qualia-optimize/SKILL.md +8 -0
- package/skills/qualia-pause/SKILL.md +5 -0
- package/skills/qualia-plan/SKILL.md +11 -1
- package/skills/qualia-polish/SKILL.md +8 -0
- package/skills/qualia-quick/SKILL.md +7 -0
- package/skills/qualia-report/SKILL.md +146 -60
- package/skills/qualia-research/SKILL.md +7 -0
- package/skills/qualia-resume/SKILL.md +3 -0
- package/skills/qualia-review/SKILL.md +7 -0
- package/skills/qualia-ship/SKILL.md +5 -0
- package/skills/qualia-skill-new/SKILL.md +6 -0
- package/skills/qualia-task/SKILL.md +8 -1
- package/skills/qualia-test/SKILL.md +7 -0
- package/skills/qualia-verify/SKILL.md +8 -0
- package/templates/help.html +4 -4
- package/templates/tracking.json +1 -0
- package/tests/hooks.test.sh +5 -5
- package/tests/runner.js +310 -3
package/CLAUDE.md
CHANGED
|
@@ -23,25 +23,37 @@ Next.js 16+, React 19, TypeScript, Supabase, Vercel. Voice: Retell AI, ElevenLab
|
|
|
23
23
|
|
|
24
24
|
## The Road (how projects flow)
|
|
25
25
|
|
|
26
|
+
v4 hierarchy: **Project → Journey → Milestones (2–5, Handoff always last) → Phases (2–5 tasks each) → Tasks (one commit, one verification contract).**
|
|
27
|
+
|
|
26
28
|
```
|
|
27
|
-
/qualia-new
|
|
29
|
+
/qualia-new → kickoff + parallel research + JOURNEY.md (all milestones upfront)
|
|
30
|
+
add --auto to chain the whole road end-to-end
|
|
28
31
|
↓
|
|
29
|
-
For each phase:
|
|
30
|
-
/qualia-plan
|
|
31
|
-
/qualia-build
|
|
32
|
-
/qualia-verify
|
|
32
|
+
For each milestone, for each phase:
|
|
33
|
+
/qualia-plan → plan the phase (planner + plan-checker revision loop, fresh context)
|
|
34
|
+
/qualia-build → build it (builder subagents per task, wave-based parallel)
|
|
35
|
+
/qualia-verify → goal-backward check (verifier agent, fresh context)
|
|
33
36
|
↓
|
|
34
|
-
/qualia-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
+
/qualia-milestone → close milestone, archive artifacts, prep next (human gate)
|
|
38
|
+
↓ (repeat for each milestone until Handoff)
|
|
39
|
+
Final milestone = Handoff:
|
|
40
|
+
/qualia-polish → design/UX pass (Phase 1 of Handoff)
|
|
41
|
+
(content + SEO) → Phase 2
|
|
42
|
+
(final QA) → Phase 3
|
|
43
|
+
/qualia-ship → deploy to production (quality gates → deploy → verify)
|
|
44
|
+
/qualia-handoff → 4 deliverables: credentials, doc, final update, report
|
|
37
45
|
↓
|
|
38
46
|
Done.
|
|
39
47
|
|
|
40
|
-
Lost?
|
|
41
|
-
|
|
42
|
-
|
|
48
|
+
Lost? → /qualia (state router — tells you the next command)
|
|
49
|
+
Stuck/weird? → /qualia-idk (diagnostic — spawns plan-view + code-view agents in parallel)
|
|
50
|
+
Quick fix? → /qualia-quick (skip planning for small tasks)
|
|
51
|
+
Paused? → /qualia-resume (restore from .continue-here.md or STATE.md)
|
|
52
|
+
End of day? → /qualia-report (mandatory before clock-out; writes ERP payload)
|
|
43
53
|
```
|
|
44
54
|
|
|
55
|
+
**Human gates:** journey approval after `/qualia-new`, then one at each milestone boundary via `/qualia-milestone`. `--auto` runs everything between gates automatically.
|
|
56
|
+
|
|
45
57
|
## Context Isolation
|
|
46
58
|
Every task runs in a fresh subagent context. Task 50 gets the same quality as Task 1.
|
|
47
59
|
- Planner gets: PROJECT.md + phase requirements
|
package/agents/plan-checker.md
CHANGED
|
@@ -34,7 +34,7 @@ Plan must have YAML frontmatter with:
|
|
|
34
34
|
|
|
35
35
|
**FAIL if:** frontmatter missing, incomplete, or `goal` differs from ROADMAP.md.
|
|
36
36
|
|
|
37
|
-
### Rule 2: Every task has the
|
|
37
|
+
### Rule 2: Every task has the 7 mandatory story-file fields
|
|
38
38
|
|
|
39
39
|
Each `## Task N — title` block must include ALL of these:
|
|
40
40
|
|
package/agents/roadmapper.md
CHANGED
|
@@ -85,14 +85,18 @@ Use the research SUMMARY.md as your starting point. Don't force-fit the template
|
|
|
85
85
|
- **Phases** — 2-5 phases. For Milestone 1, include full detail (goal + success criteria). For M2..M{N-1}, names + one-line goals are enough (progressive detail — full detail gets written when that milestone opens). For Handoff, use the fixed 4-phase template.
|
|
86
86
|
- **Requirements covered** — list the REQ-IDs this milestone delivers
|
|
87
87
|
|
|
88
|
-
### 4. Build ROADMAP.md —
|
|
88
|
+
### 4. Build ROADMAP.md — Milestone 1's phases (progressive detail by default)
|
|
89
89
|
|
|
90
|
-
|
|
90
|
+
Check the `<full_detail>` flag in your prompt:
|
|
91
91
|
|
|
92
|
-
|
|
92
|
+
**`full_detail=false` (default):** Only Milestone 1 gets full phase detail in ROADMAP.md. Future milestones stay as sketches in JOURNEY.md until they open. This matches progressive-detail planning and is the recommended default.
|
|
93
|
+
|
|
94
|
+
**`full_detail=true`:** Write full phase detail for EVERY milestone (M1..Handoff) in ROADMAP.md, sectioned by milestone. Use when the client wants a fully-committed plan at kickoff. Trade-off: M2+ detail often needs revision as M1 ships — flag this in your summary.
|
|
95
|
+
|
|
96
|
+
For each phase in the milestone(s) you're detailing:
|
|
93
97
|
- **Name** + **goal** (one line)
|
|
94
98
|
- **Success criteria** — 2-5 observable user-facing behaviors
|
|
95
|
-
- **Requirements covered** — REQ-IDs from REQUIREMENTS.md
|
|
99
|
+
- **Requirements covered** — REQ-IDs from REQUIREMENTS.md section for that milestone
|
|
96
100
|
|
|
97
101
|
### 5. Validate Coverage
|
|
98
102
|
|
|
@@ -104,7 +108,8 @@ Before writing, verify:
|
|
|
104
108
|
- [ ] Final milestone is literally named "Handoff" with the 4 standard phases
|
|
105
109
|
- [ ] No milestone depends on a later milestone
|
|
106
110
|
- [ ] Milestone 1 has full phase-level detail (goals + success criteria) ready for `/qualia-plan 1`
|
|
107
|
-
- [ ] M2..M{N-1} have phase names + one-line goals (sketch, not full detail)
|
|
111
|
+
- [ ] If `full_detail=false` (default): M2..M{N-1} have phase names + one-line goals (sketch, not full detail)
|
|
112
|
+
- [ ] If `full_detail=true`: every milestone in ROADMAP.md has full phase detail; flag this mode explicitly in your summary output
|
|
108
113
|
|
|
109
114
|
If any check fails, fix it. The orchestrator trusts your output.
|
|
110
115
|
|
package/bin/cli.js
CHANGED
|
@@ -126,21 +126,35 @@ function cmdUpdate() {
|
|
|
126
126
|
// non-Qualia entries in settings.json (other hooks, user env vars, etc.).
|
|
127
127
|
// --yes / -y skips the confirmation prompt for scripted use.
|
|
128
128
|
|
|
129
|
-
//
|
|
130
|
-
// any other hooks the user dropped in there are left alone.
|
|
129
|
+
// Current Qualia hook filenames — only these are removed from ~/.claude/hooks/,
|
|
130
|
+
// any other hooks the user dropped in there are left alone. The LEGACY set
|
|
131
|
+
// lists hooks that were shipped by older framework versions but have since
|
|
132
|
+
// been removed; uninstall still tries to clean them so old installs get a
|
|
133
|
+
// clean removal.
|
|
131
134
|
const QUALIA_HOOK_FILES = [
|
|
132
135
|
"session-start.js",
|
|
133
136
|
"auto-update.js",
|
|
134
137
|
"branch-guard.js",
|
|
135
138
|
"pre-push.js",
|
|
136
|
-
"block-env-edit.js",
|
|
137
139
|
"migration-guard.js",
|
|
138
140
|
"pre-deploy-gate.js",
|
|
139
141
|
"pre-compact.js",
|
|
140
142
|
];
|
|
143
|
+
const QUALIA_LEGACY_HOOK_FILES = [
|
|
144
|
+
"block-env-edit.js", // removed in v3.2.0
|
|
145
|
+
];
|
|
141
146
|
|
|
142
|
-
//
|
|
143
|
-
const QUALIA_AGENT_FILES = [
|
|
147
|
+
// 8 Qualia agents — only these are removed.
|
|
148
|
+
const QUALIA_AGENT_FILES = [
|
|
149
|
+
"planner.md",
|
|
150
|
+
"builder.md",
|
|
151
|
+
"verifier.md",
|
|
152
|
+
"qa-browser.md",
|
|
153
|
+
"plan-checker.md",
|
|
154
|
+
"researcher.md",
|
|
155
|
+
"research-synthesizer.md",
|
|
156
|
+
"roadmapper.md",
|
|
157
|
+
];
|
|
144
158
|
|
|
145
159
|
// 3 Qualia bin scripts.
|
|
146
160
|
const QUALIA_BIN_FILES = ["state.js", "qualia-ui.js", "statusline.js"];
|
|
@@ -210,14 +224,14 @@ function cleanSettingsJson(counters) {
|
|
|
210
224
|
};
|
|
211
225
|
|
|
212
226
|
if (settings.hooks && typeof settings.hooks === "object") {
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
227
|
+
// Iterate every hook event key, not a hardcoded subset — future hook
|
|
228
|
+
// events added by Claude Code or the framework get cleaned automatically.
|
|
229
|
+
for (const key of Object.keys(settings.hooks)) {
|
|
230
|
+
const cleaned = filterHookArray(settings.hooks[key]);
|
|
231
|
+
if (cleaned && cleaned.length > 0) {
|
|
232
|
+
settings.hooks[key] = cleaned;
|
|
233
|
+
} else {
|
|
234
|
+
delete settings.hooks[key];
|
|
221
235
|
}
|
|
222
236
|
}
|
|
223
237
|
// If hooks is now empty, remove it entirely.
|
|
@@ -305,8 +319,8 @@ async function cmdUninstall() {
|
|
|
305
319
|
safeUnlink(path.join(CLAUDE_DIR, "agents", f), counters);
|
|
306
320
|
}
|
|
307
321
|
|
|
308
|
-
// Hooks —
|
|
309
|
-
for (const f of QUALIA_HOOK_FILES) {
|
|
322
|
+
// Hooks — current set plus any legacy hook filenames from older versions.
|
|
323
|
+
for (const f of [...QUALIA_HOOK_FILES, ...QUALIA_LEGACY_HOOK_FILES]) {
|
|
310
324
|
safeUnlink(path.join(CLAUDE_DIR, "hooks", f), counters);
|
|
311
325
|
}
|
|
312
326
|
|
|
@@ -552,7 +566,7 @@ function cmdMigrate() {
|
|
|
552
566
|
|
|
553
567
|
// Check PreToolUse hooks — ensure all critical hooks are present
|
|
554
568
|
const requiredBashHooks = ["auto-update.js", "branch-guard.js", "pre-push.js", "pre-deploy-gate.js"];
|
|
555
|
-
const requiredEditHooks = ["
|
|
569
|
+
const requiredEditHooks = ["migration-guard.js"];
|
|
556
570
|
|
|
557
571
|
if (!settings.hooks.PreToolUse) settings.hooks.PreToolUse = [];
|
|
558
572
|
|
|
@@ -644,7 +658,7 @@ function cmdMigrate() {
|
|
|
644
658
|
if (!settings.mcpServers["next-devtools"]) {
|
|
645
659
|
settings.mcpServers["next-devtools"] = {
|
|
646
660
|
command: "npx",
|
|
647
|
-
args: ["next-devtools-mcp@
|
|
661
|
+
args: ["next-devtools-mcp@latest"],
|
|
648
662
|
disabled: false,
|
|
649
663
|
};
|
|
650
664
|
changes++;
|
|
@@ -755,6 +769,109 @@ function cmdAnalytics() {
|
|
|
755
769
|
console.log("");
|
|
756
770
|
}
|
|
757
771
|
|
|
772
|
+
// ─── ERP Ping ───────────────────────────────────────────
|
|
773
|
+
// Synthetic POST to ERP /api/v1/reports to verify connectivity, auth key
|
|
774
|
+
// validity, and endpoint health. Uses a distinct dry_run=true flag in the
|
|
775
|
+
// payload so receivers can filter these out of real report views.
|
|
776
|
+
|
|
777
|
+
function cmdErpPing() {
|
|
778
|
+
banner();
|
|
779
|
+
console.log("");
|
|
780
|
+
|
|
781
|
+
const cfg = readConfig();
|
|
782
|
+
const erpUrl = (cfg.erp && cfg.erp.url) || "https://portal.qualiasolutions.net";
|
|
783
|
+
const erpEnabled = !(cfg.erp && cfg.erp.enabled === false);
|
|
784
|
+
const keyFile = path.join(CLAUDE_DIR, ".erp-api-key");
|
|
785
|
+
|
|
786
|
+
console.log(` ${DIM}URL:${RESET} ${WHITE}${erpUrl}${RESET}`);
|
|
787
|
+
console.log(` ${DIM}Enabled:${RESET} ${erpEnabled ? `${GREEN}yes${RESET}` : `${YELLOW}no (erp.enabled=false)${RESET}`}`);
|
|
788
|
+
|
|
789
|
+
let apiKey = "";
|
|
790
|
+
try {
|
|
791
|
+
apiKey = fs.readFileSync(keyFile, "utf8").trim();
|
|
792
|
+
} catch {}
|
|
793
|
+
if (!apiKey) {
|
|
794
|
+
console.log(` ${DIM}Key:${RESET} ${RED}missing${RESET} ${DIM}(${keyFile})${RESET}`);
|
|
795
|
+
console.log("");
|
|
796
|
+
console.log(` ${RED}✗ Cannot ping — no API key. Ask Fawzi for one.${RESET}`);
|
|
797
|
+
console.log("");
|
|
798
|
+
process.exit(1);
|
|
799
|
+
}
|
|
800
|
+
console.log(` ${DIM}Key:${RESET} ${GREEN}present${RESET} ${DIM}(${apiKey.length} bytes)${RESET}`);
|
|
801
|
+
console.log("");
|
|
802
|
+
|
|
803
|
+
if (!erpEnabled) {
|
|
804
|
+
console.log(` ${YELLOW}ERP is disabled in config. Enable with:${RESET}`);
|
|
805
|
+
console.log(` ${DIM} qualia-framework erp-ping --enable${RESET}`);
|
|
806
|
+
console.log("");
|
|
807
|
+
process.exit(1);
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
const payload = JSON.stringify({
|
|
811
|
+
project: "qualia-framework-erp-ping",
|
|
812
|
+
project_id: "ping",
|
|
813
|
+
team_id: "qualia-solutions",
|
|
814
|
+
client_report_id: "QS-PING-00",
|
|
815
|
+
phase: 0,
|
|
816
|
+
phase_name: "ping",
|
|
817
|
+
status: "setup",
|
|
818
|
+
milestone: 0,
|
|
819
|
+
milestone_name: "ping",
|
|
820
|
+
submitted_by: cfg.installed_by || "ping",
|
|
821
|
+
submitted_at: new Date().toISOString(),
|
|
822
|
+
notes: "ERP PING — synthetic connectivity test, safe to ignore",
|
|
823
|
+
dry_run: true,
|
|
824
|
+
});
|
|
825
|
+
|
|
826
|
+
const started = Date.now();
|
|
827
|
+
const r = spawnSync("curl", [
|
|
828
|
+
"-sS", "-X", "POST",
|
|
829
|
+
"-H", `Authorization: Bearer ${apiKey}`,
|
|
830
|
+
"-H", "Content-Type: application/json",
|
|
831
|
+
"-d", payload,
|
|
832
|
+
"--max-time", "10",
|
|
833
|
+
"-w", "\n__HTTP__%{http_code}",
|
|
834
|
+
`${erpUrl}/api/v1/reports`,
|
|
835
|
+
], { encoding: "utf8", timeout: 12000 });
|
|
836
|
+
|
|
837
|
+
const duration = Date.now() - started;
|
|
838
|
+
const raw = (r.stdout || "") + (r.stderr || "");
|
|
839
|
+
const httpMatch = raw.match(/__HTTP__(\d+)/);
|
|
840
|
+
const httpCode = httpMatch ? httpMatch[1] : "—";
|
|
841
|
+
const body = raw.replace(/\n?__HTTP__\d+/, "").trim();
|
|
842
|
+
|
|
843
|
+
console.log(` ${DIM}Response:${RESET} ${WHITE}HTTP ${httpCode}${RESET} ${DIM}(${duration}ms)${RESET}`);
|
|
844
|
+
if (body) {
|
|
845
|
+
try {
|
|
846
|
+
const j = JSON.parse(body);
|
|
847
|
+
if (j.ok && j.report_id) {
|
|
848
|
+
console.log(` ${DIM}report_id:${RESET} ${GREEN}${j.report_id}${RESET}`);
|
|
849
|
+
}
|
|
850
|
+
if (!j.ok && j.error) {
|
|
851
|
+
console.log(` ${DIM}error:${RESET} ${RED}${j.error}${RESET} ${DIM}${j.message || ""}${RESET}`);
|
|
852
|
+
}
|
|
853
|
+
} catch {
|
|
854
|
+
console.log(` ${DIM}body:${RESET} ${WHITE}${body.slice(0, 200)}${RESET}`);
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
console.log("");
|
|
858
|
+
|
|
859
|
+
if (httpCode === "200") {
|
|
860
|
+
console.log(` ${GREEN}✓ ERP reachable, key valid, endpoint healthy.${RESET}`);
|
|
861
|
+
console.log("");
|
|
862
|
+
process.exit(0);
|
|
863
|
+
}
|
|
864
|
+
if (httpCode === "401") {
|
|
865
|
+
console.log(` ${RED}✗ API key rejected. Ask Fawzi for a fresh key.${RESET}`);
|
|
866
|
+
} else if (httpCode === "—") {
|
|
867
|
+
console.log(` ${RED}✗ No response — DNS, TLS, or network issue.${RESET}`);
|
|
868
|
+
} else {
|
|
869
|
+
console.log(` ${YELLOW}! Unexpected response. Check ERP status.${RESET}`);
|
|
870
|
+
}
|
|
871
|
+
console.log("");
|
|
872
|
+
process.exit(1);
|
|
873
|
+
}
|
|
874
|
+
|
|
758
875
|
function cmdHelp() {
|
|
759
876
|
banner();
|
|
760
877
|
console.log("");
|
|
@@ -767,6 +884,7 @@ function cmdHelp() {
|
|
|
767
884
|
console.log(` qualia-framework ${TEAL}team${RESET} Manage team members (${DIM}list|add|remove${RESET})`);
|
|
768
885
|
console.log(` qualia-framework ${TEAL}traces${RESET} View recent hook telemetry`);
|
|
769
886
|
console.log(` qualia-framework ${TEAL}analytics${RESET} Show outcome scoring & gap cycle stats`);
|
|
887
|
+
console.log(` qualia-framework ${TEAL}erp-ping${RESET} Verify ERP connectivity + API key`);
|
|
770
888
|
console.log("");
|
|
771
889
|
console.log(` ${WHITE}After install:${RESET}`);
|
|
772
890
|
console.log(` ${TG}/qualia${RESET} What should I do next?`);
|
|
@@ -819,6 +937,10 @@ switch (cmd) {
|
|
|
819
937
|
case "stats":
|
|
820
938
|
cmdAnalytics();
|
|
821
939
|
break;
|
|
940
|
+
case "erp-ping":
|
|
941
|
+
case "ping":
|
|
942
|
+
cmdErpPing();
|
|
943
|
+
break;
|
|
822
944
|
default:
|
|
823
945
|
cmdHelp();
|
|
824
946
|
}
|
package/bin/install.js
CHANGED
|
@@ -517,7 +517,18 @@ Client-specific preferences, design choices, and requirements. Loaded by \`/qual
|
|
|
517
517
|
ok(".erp-api-key (from $QUALIA_ERP_KEY)");
|
|
518
518
|
} else if (fs.existsSync(erpKeyFile)) {
|
|
519
519
|
try { fs.chmodSync(erpKeyFile, 0o600); } catch {}
|
|
520
|
-
|
|
520
|
+
// Sanity check: warn on a clearly-empty/placeholder key. Genuine tokens
|
|
521
|
+
// from the ERP are ≥ 20 bytes; under 10 is almost certainly a mistake.
|
|
522
|
+
try {
|
|
523
|
+
const existingKey = fs.readFileSync(erpKeyFile, "utf8").trim();
|
|
524
|
+
if (existingKey.length < 10) {
|
|
525
|
+
warn(`.erp-api-key exists but looks truncated (${existingKey.length} bytes) — verify with 'qualia-framework erp-ping'`);
|
|
526
|
+
} else {
|
|
527
|
+
ok(".erp-api-key (existing — preserved)");
|
|
528
|
+
}
|
|
529
|
+
} catch {
|
|
530
|
+
ok(".erp-api-key (existing — preserved)");
|
|
531
|
+
}
|
|
521
532
|
} else {
|
|
522
533
|
// Disable ERP in the config we just wrote.
|
|
523
534
|
try {
|
|
@@ -599,16 +610,23 @@ Client-specific preferences, design choices, and requirements. Loaded by \`/qual
|
|
|
599
610
|
// bash/Git Bash requirement on Windows.
|
|
600
611
|
const hd = path.join(CLAUDE_DIR, "hooks");
|
|
601
612
|
const nodeCmd = (hookFile) => `node "${path.join(hd, hookFile)}"`;
|
|
602
|
-
|
|
613
|
+
const QUALIA_HOOK_SET = new Set([
|
|
614
|
+
"session-start.js", "auto-update.js", "branch-guard.js", "pre-push.js",
|
|
615
|
+
"pre-deploy-gate.js", "migration-guard.js", "pre-compact.js",
|
|
616
|
+
]);
|
|
617
|
+
const isQualiaHookCmd = (cmd) => {
|
|
618
|
+
if (typeof cmd !== "string") return false;
|
|
619
|
+
for (const h of QUALIA_HOOK_SET) if (cmd.includes(h)) return true;
|
|
620
|
+
return false;
|
|
621
|
+
};
|
|
622
|
+
|
|
623
|
+
// Our canonical hook definitions, grouped per event/matcher.
|
|
624
|
+
const qualiaHooks = {
|
|
603
625
|
SessionStart: [
|
|
604
626
|
{
|
|
605
627
|
matcher: ".*",
|
|
606
628
|
hooks: [
|
|
607
|
-
{
|
|
608
|
-
type: "command",
|
|
609
|
-
command: nodeCmd("session-start.js"),
|
|
610
|
-
timeout: 5,
|
|
611
|
-
},
|
|
629
|
+
{ type: "command", command: nodeCmd("session-start.js"), timeout: 5 },
|
|
612
630
|
],
|
|
613
631
|
},
|
|
614
632
|
],
|
|
@@ -616,44 +634,16 @@ Client-specific preferences, design choices, and requirements. Loaded by \`/qual
|
|
|
616
634
|
{
|
|
617
635
|
matcher: "Bash",
|
|
618
636
|
hooks: [
|
|
619
|
-
{
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
},
|
|
624
|
-
{
|
|
625
|
-
type: "command",
|
|
626
|
-
if: "Bash(git push*)",
|
|
627
|
-
command: nodeCmd("branch-guard.js"),
|
|
628
|
-
timeout: 5,
|
|
629
|
-
statusMessage: "⬢ Checking branch permissions...",
|
|
630
|
-
},
|
|
631
|
-
{
|
|
632
|
-
type: "command",
|
|
633
|
-
if: "Bash(git push*)",
|
|
634
|
-
command: nodeCmd("pre-push.js"),
|
|
635
|
-
timeout: 15,
|
|
636
|
-
statusMessage: "⬢ Syncing tracking...",
|
|
637
|
-
},
|
|
638
|
-
{
|
|
639
|
-
type: "command",
|
|
640
|
-
if: "Bash(vercel --prod*)",
|
|
641
|
-
command: nodeCmd("pre-deploy-gate.js"),
|
|
642
|
-
timeout: 180,
|
|
643
|
-
statusMessage: "⬢ Running quality gates...",
|
|
644
|
-
},
|
|
637
|
+
{ type: "command", command: nodeCmd("auto-update.js"), timeout: 5 },
|
|
638
|
+
{ type: "command", if: "Bash(git push*)", command: nodeCmd("branch-guard.js"), timeout: 5, statusMessage: "⬢ Checking branch permissions..." },
|
|
639
|
+
{ type: "command", if: "Bash(git push*)", command: nodeCmd("pre-push.js"), timeout: 15, statusMessage: "⬢ Syncing tracking..." },
|
|
640
|
+
{ type: "command", if: "Bash(vercel --prod*)", command: nodeCmd("pre-deploy-gate.js"), timeout: 180, statusMessage: "⬢ Running quality gates..." },
|
|
645
641
|
],
|
|
646
642
|
},
|
|
647
643
|
{
|
|
648
644
|
matcher: "Edit|Write",
|
|
649
645
|
hooks: [
|
|
650
|
-
{
|
|
651
|
-
type: "command",
|
|
652
|
-
if: "Edit(*migration*)|Write(*migration*)|Edit(*.sql)|Write(*.sql)",
|
|
653
|
-
command: nodeCmd("migration-guard.js"),
|
|
654
|
-
timeout: 10,
|
|
655
|
-
statusMessage: "⬢ Checking migration safety...",
|
|
656
|
-
},
|
|
646
|
+
{ type: "command", if: "Edit(*migration*)|Write(*migration*)|Edit(*.sql)|Write(*.sql)", command: nodeCmd("migration-guard.js"), timeout: 10, statusMessage: "⬢ Checking migration safety..." },
|
|
657
647
|
],
|
|
658
648
|
},
|
|
659
649
|
],
|
|
@@ -661,17 +651,27 @@ Client-specific preferences, design choices, and requirements. Loaded by \`/qual
|
|
|
661
651
|
{
|
|
662
652
|
matcher: "compact",
|
|
663
653
|
hooks: [
|
|
664
|
-
{
|
|
665
|
-
type: "command",
|
|
666
|
-
command: nodeCmd("pre-compact.js"),
|
|
667
|
-
timeout: 15,
|
|
668
|
-
statusMessage: "⬢ Saving state...",
|
|
669
|
-
},
|
|
654
|
+
{ type: "command", command: nodeCmd("pre-compact.js"), timeout: 15, statusMessage: "⬢ Saving state..." },
|
|
670
655
|
],
|
|
671
656
|
},
|
|
672
657
|
],
|
|
673
658
|
};
|
|
674
659
|
|
|
660
|
+
// Merge user hooks: strip Qualia-owned commands, preserve everything else.
|
|
661
|
+
if (!settings.hooks || typeof settings.hooks !== "object") settings.hooks = {};
|
|
662
|
+
for (const event of Object.keys(qualiaHooks)) {
|
|
663
|
+
const existing = Array.isArray(settings.hooks[event]) ? settings.hooks[event] : [];
|
|
664
|
+
// Remove Qualia-owned command entries from each matcher block, drop empty blocks.
|
|
665
|
+
const cleaned = [];
|
|
666
|
+
for (const block of existing) {
|
|
667
|
+
if (!block || !Array.isArray(block.hooks)) continue;
|
|
668
|
+
const kept = block.hooks.filter((h) => !isQualiaHookCmd(h && h.command));
|
|
669
|
+
if (kept.length > 0) cleaned.push({ ...block, hooks: kept });
|
|
670
|
+
}
|
|
671
|
+
// Append our canonical blocks after the preserved user ones.
|
|
672
|
+
settings.hooks[event] = [...cleaned, ...qualiaHooks[event]];
|
|
673
|
+
}
|
|
674
|
+
|
|
675
675
|
// Permissions — no restrictions on env files or branches.
|
|
676
676
|
// Everyone can read/write .env, push to main.
|
|
677
677
|
if (!settings.permissions) settings.permissions = {};
|
|
@@ -684,7 +684,7 @@ Client-specific preferences, design choices, and requirements. Loaded by \`/qual
|
|
|
684
684
|
if (!settings.mcpServers["next-devtools"]) {
|
|
685
685
|
settings.mcpServers["next-devtools"] = {
|
|
686
686
|
command: "npx",
|
|
687
|
-
args: ["next-devtools-mcp@
|
|
687
|
+
args: ["next-devtools-mcp@latest"],
|
|
688
688
|
disabled: false,
|
|
689
689
|
};
|
|
690
690
|
ok("MCP: next-devtools (runtime error visibility for Next.js projects)");
|
package/bin/qualia-ui.js
CHANGED
|
@@ -320,10 +320,10 @@ function cmdJourneyTree(journeyPath) {
|
|
|
320
320
|
|
|
321
321
|
// Project name from frontmatter if present
|
|
322
322
|
const projMatch = content.match(/^project:\s*"?(.+?)"?\s*$/m);
|
|
323
|
-
const
|
|
323
|
+
const projName = projMatch ? projMatch[1] : projectName();
|
|
324
324
|
|
|
325
325
|
console.log("");
|
|
326
|
-
console.log(` ${TEAL}${BOLD}◯${RESET} ${WHITE}${BOLD}JOURNEY${RESET} ${DIM}▸${RESET} ${WHITE}${
|
|
326
|
+
console.log(` ${TEAL}${BOLD}◯${RESET} ${WHITE}${BOLD}JOURNEY${RESET} ${DIM}▸${RESET} ${WHITE}${projName}${RESET}`);
|
|
327
327
|
console.log(` ${RULE_DIM}`);
|
|
328
328
|
console.log(` ${DIM}${milestones.length} milestones · currently at M${currentMilestone}${RESET}`);
|
|
329
329
|
console.log("");
|