qualia-framework 6.2.9 → 6.3.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/AGENTS.md +1 -0
- package/CLAUDE.md +1 -0
- package/README.md +26 -30
- package/agents/builder.md +7 -7
- package/agents/planner.md +39 -3
- package/agents/research-synthesizer.md +1 -1
- package/agents/researcher.md +3 -3
- package/agents/roadmapper.md +7 -7
- package/agents/verifier.md +18 -6
- package/agents/visual-evaluator.md +8 -7
- package/bin/cli.js +160 -16
- package/bin/command-surface.js +71 -0
- package/bin/contract-runner.js +219 -0
- package/bin/harness-eval.js +296 -0
- package/bin/host-adapters.js +66 -0
- package/bin/install.js +116 -172
- package/bin/knowledge-flush.js +21 -10
- package/bin/knowledge.js +1 -1
- package/bin/plan-contract.js +99 -2
- package/bin/planning-hygiene.js +262 -0
- package/bin/project-snapshot.js +20 -0
- package/bin/report-payload.js +18 -0
- package/bin/runtime-manifest.js +35 -0
- package/bin/state-ledger.js +184 -0
- package/bin/state.js +330 -20
- package/bin/trust-score.js +268 -0
- package/bin/work-packet.js +228 -0
- package/docs/erp-contract.md +81 -1
- package/docs/onboarding.html +4 -14
- package/guide.md +16 -16
- package/hooks/fawzi-approval-guard.js +143 -0
- package/hooks/pre-deploy-gate.js +74 -1
- package/hooks/session-start.js +29 -1
- package/package.json +1 -1
- package/qualia-design/design-rubric.md +17 -5
- package/qualia-design/frontend.md +6 -2
- package/qualia-design/graphics.md +47 -0
- package/rules/codex-goal.md +1 -1
- package/rules/command-output.md +35 -0
- package/rules/one-opinion.md +2 -2
- package/rules/speed.md +0 -1
- package/skills/qualia/SKILL.md +12 -12
- package/skills/qualia-build/SKILL.md +20 -14
- package/skills/qualia-discuss/SKILL.md +10 -10
- package/skills/qualia-doctor/SKILL.md +140 -0
- package/skills/qualia-feature/SKILL.md +24 -22
- package/skills/qualia-fix/SKILL.md +216 -0
- package/skills/qualia-handoff/SKILL.md +9 -9
- package/skills/qualia-learn/SKILL.md +11 -11
- package/skills/qualia-map/SKILL.md +2 -2
- package/skills/qualia-milestone/SKILL.md +15 -15
- package/skills/qualia-new/REFERENCE.md +9 -9
- package/skills/qualia-new/SKILL.md +14 -14
- package/skills/qualia-optimize/REFERENCE.md +1 -1
- package/skills/qualia-optimize/SKILL.md +23 -16
- package/skills/qualia-plan/SKILL.md +23 -13
- package/skills/qualia-polish/REFERENCE.md +15 -15
- package/skills/qualia-polish/SKILL.md +81 -21
- package/skills/qualia-polish/scripts/loop.mjs +3 -3
- package/skills/qualia-polish/scripts/score.mjs +9 -3
- package/skills/{qualia-vibe/scripts/extract.mjs → qualia-polish/scripts/vibe-extract.mjs} +5 -5
- package/skills/{qualia-vibe/scripts/tokens.mjs → qualia-polish/scripts/vibe-tokens.mjs} +6 -6
- package/skills/qualia-postmortem/SKILL.md +9 -9
- package/skills/qualia-report/SKILL.md +23 -23
- package/skills/qualia-research/SKILL.md +5 -5
- package/skills/qualia-review/SKILL.md +28 -12
- package/skills/qualia-road/SKILL.md +30 -22
- package/skills/qualia-ship/SKILL.md +31 -24
- package/skills/qualia-test/SKILL.md +5 -5
- package/skills/qualia-verify/SKILL.md +45 -23
- package/skills/zoho-workflow/SKILL.md +1 -1
- package/templates/help.html +11 -20
- package/tests/bin.test.sh +178 -76
- package/tests/hooks.test.sh +81 -1
- package/tests/install-smoke.test.sh +35 -5
- package/tests/lib.test.sh +432 -0
- package/tests/published-install-smoke.test.sh +4 -3
- package/tests/refs.test.sh +9 -4
- package/tests/runner.js +32 -28
- package/tests/skills.test.sh +4 -4
- package/tests/state.test.sh +133 -3
- package/skills/qualia-debug/SKILL.md +0 -185
- package/skills/qualia-flush/SKILL.md +0 -198
- package/skills/qualia-help/SKILL.md +0 -74
- package/skills/qualia-hook-gen/SKILL.md +0 -206
- package/skills/qualia-idk/SKILL.md +0 -166
- package/skills/qualia-issues/SKILL.md +0 -151
- package/skills/qualia-pause/SKILL.md +0 -68
- package/skills/qualia-resume/SKILL.md +0 -52
- package/skills/qualia-skill-new/SKILL.md +0 -173
- package/skills/qualia-triage/SKILL.md +0 -152
- package/skills/qualia-vibe/SKILL.md +0 -226
- package/skills/qualia-zoom/SKILL.md +0 -51
package/bin/install.js
CHANGED
|
@@ -4,6 +4,9 @@ const { createInterface } = require("readline");
|
|
|
4
4
|
const path = require("path");
|
|
5
5
|
const fs = require("fs");
|
|
6
6
|
const ui = require("./qualia-ui.js");
|
|
7
|
+
const { RUNTIME_BIN_SCRIPTS, binFiles } = require("./runtime-manifest.js");
|
|
8
|
+
const { ACTIVE_SKILLS, RETIRED_SKILLS } = require("./command-surface.js");
|
|
9
|
+
const { renderText } = require("./host-adapters.js");
|
|
7
10
|
|
|
8
11
|
// ─── Colors (kept for legacy log lines; new sections route through qualia-ui) ─
|
|
9
12
|
const TEAL = "\x1b[38;2;0;206;209m";
|
|
@@ -24,12 +27,30 @@ const TARGET_CLAUDE_ONLY = "1";
|
|
|
24
27
|
const TARGET_CODEX_ONLY = "2";
|
|
25
28
|
const TARGET_BOTH = "3";
|
|
26
29
|
|
|
30
|
+
const CODEX_STATUS_LINE = [
|
|
31
|
+
"model-with-reasoning",
|
|
32
|
+
"task-progress",
|
|
33
|
+
"current-dir",
|
|
34
|
+
"git-branch",
|
|
35
|
+
"context-used",
|
|
36
|
+
"five-hour-limit",
|
|
37
|
+
"weekly-limit",
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
const CODEX_STATUS_LINE_BLOCK = [
|
|
41
|
+
"# Added by qualia-framework — Codex native bottom status line.",
|
|
42
|
+
"[tui]",
|
|
43
|
+
`status_line = ${JSON.stringify(CODEX_STATUS_LINE)}`,
|
|
44
|
+
"status_line_use_colors = true",
|
|
45
|
+
"",
|
|
46
|
+
].join("\n");
|
|
47
|
+
|
|
27
48
|
// Total install timer — set in main(), read by the final summary card.
|
|
28
49
|
const installStart = Date.now();
|
|
29
50
|
|
|
30
51
|
// ─── Team codes ──────────────────────────────────────────
|
|
31
52
|
const DEFAULT_TEAM = {
|
|
32
|
-
"QS-FAWZI-
|
|
53
|
+
"QS-FAWZI-11": {
|
|
33
54
|
name: "Fawzi Goussous",
|
|
34
55
|
role: "OWNER",
|
|
35
56
|
description: "Company owner. Full access. Can push to main, approve deploys, edit secrets.",
|
|
@@ -125,14 +146,8 @@ function copyTree(src, dest) {
|
|
|
125
146
|
}
|
|
126
147
|
}
|
|
127
148
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
.replaceAll("~/.claude/", "~/.codex/")
|
|
131
|
-
.replaceAll("$HOME/.claude/", "$HOME/.codex/")
|
|
132
|
-
.replaceAll("${HOME}/.claude/", "${HOME}/.codex/")
|
|
133
|
-
.replaceAll("@~/.claude/", "@~/.codex/")
|
|
134
|
-
.replaceAll(".claude/", ".codex/");
|
|
135
|
-
}
|
|
149
|
+
const claudeText = (content) => renderText(content, "claude");
|
|
150
|
+
const codexText = (content) => renderText(content, "codex");
|
|
136
151
|
|
|
137
152
|
function copyTextTransform(src, dest, transform) {
|
|
138
153
|
const destDir = path.dirname(dest);
|
|
@@ -156,6 +171,45 @@ function copyTreeTransform(src, dest, transform) {
|
|
|
156
171
|
}
|
|
157
172
|
}
|
|
158
173
|
|
|
174
|
+
function ensureCodexStatusLineConfig(existing) {
|
|
175
|
+
const statusLine = `status_line = ${JSON.stringify(CODEX_STATUS_LINE)}`;
|
|
176
|
+
const colors = "status_line_use_colors = true";
|
|
177
|
+
if (!existing || !existing.trim()) {
|
|
178
|
+
return [
|
|
179
|
+
"# Created by qualia-framework install.",
|
|
180
|
+
"# User settings can be added normally; Qualia runtime lives in AGENTS.md, hooks.json, agents/, and bin/.",
|
|
181
|
+
"",
|
|
182
|
+
"[features]",
|
|
183
|
+
"hooks = true",
|
|
184
|
+
"plugin_hooks = true",
|
|
185
|
+
"",
|
|
186
|
+
"# Codex's native status line is rendered at the bottom of the TUI.",
|
|
187
|
+
"# It supports a fixed list of built-in segment names. Custom command-backed",
|
|
188
|
+
"# status lines are not supported in Codex 0.133, so Qualia phase/project",
|
|
189
|
+
"# context is rendered by the SessionStart banner while the native bottom",
|
|
190
|
+
"# line keeps model, task, directory, git, context, and limit state visible.",
|
|
191
|
+
CODEX_STATUS_LINE_BLOCK,
|
|
192
|
+
].join("\n");
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
let next = existing;
|
|
196
|
+
if (!/^\[tui\]\s*$/m.test(next)) {
|
|
197
|
+
return `${next.replace(/\s*$/, "\n\n")}${CODEX_STATUS_LINE_BLOCK}`;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const tuiMatch = next.match(/^\[tui\]\s*$(?:\n(?!\[)[^\n]*)*/m);
|
|
201
|
+
if (!tuiMatch) return `${next.replace(/\s*$/, "\n\n")}${CODEX_STATUS_LINE_BLOCK}`;
|
|
202
|
+
|
|
203
|
+
let tuiBlock = tuiMatch[0];
|
|
204
|
+
if (!/^\s*status_line\s*=/m.test(tuiBlock)) {
|
|
205
|
+
tuiBlock = tuiBlock.replace(/^\[tui\]\s*$/m, `[tui]\n${statusLine}`);
|
|
206
|
+
}
|
|
207
|
+
if (!/^\s*status_line_use_colors\s*=/m.test(tuiBlock)) {
|
|
208
|
+
tuiBlock = `${tuiBlock.replace(/\s*$/, "")}\n${colors}`;
|
|
209
|
+
}
|
|
210
|
+
return `${next.slice(0, tuiMatch.index)}${tuiBlock}${next.slice(tuiMatch.index + tuiMatch[0].length)}`;
|
|
211
|
+
}
|
|
212
|
+
|
|
159
213
|
function backupIfDifferent(dest, nextContent, label) {
|
|
160
214
|
if (!fs.existsSync(dest)) return false;
|
|
161
215
|
try {
|
|
@@ -207,9 +261,7 @@ function parseAgentMarkdown(content) {
|
|
|
207
261
|
|
|
208
262
|
function renderCodexAgentToml(markdown, filenameFallback) {
|
|
209
263
|
const parsed = parseAgentMarkdown(markdown);
|
|
210
|
-
const body = parsed.body
|
|
211
|
-
.replaceAll("~/.claude/", "~/.codex/")
|
|
212
|
-
.replaceAll("@~/.claude/", "@~/.codex/");
|
|
264
|
+
const body = codexText(parsed.body);
|
|
213
265
|
const name = (parsed.name || filenameFallback || "").replace(/^qualia-/, "");
|
|
214
266
|
const description = parsed.description || "Qualia Framework specialist agent.";
|
|
215
267
|
return [
|
|
@@ -224,11 +276,7 @@ function renderCodexAgentToml(markdown, filenameFallback) {
|
|
|
224
276
|
// Pruned from BOTH ~/.claude/skills/ and ~/.codex/skills/ on every install run
|
|
225
277
|
// so the active surface matches what the framework currently ships.
|
|
226
278
|
const DEPRECATED_SKILLS = [
|
|
227
|
-
|
|
228
|
-
"qualia-quick", // v5.7.0 — folded into qualia-feature
|
|
229
|
-
"qualia-polish-loop", // v5.8.0 — folded into qualia-polish --loop
|
|
230
|
-
"qualia-design", // v4 wave 2 — folded into scope-adaptive qualia-polish
|
|
231
|
-
"qualia-prd", // v5.8.0 — surface cleanup
|
|
279
|
+
...RETIRED_SKILLS,
|
|
232
280
|
];
|
|
233
281
|
|
|
234
282
|
function pruneDeprecatedSkills(baseDir) {
|
|
@@ -440,8 +488,8 @@ function targetLabel(t) {
|
|
|
440
488
|
}
|
|
441
489
|
|
|
442
490
|
// ─── Resolve team code (tolerates case + O/0 typo in suffix) ─
|
|
443
|
-
// Accepts
|
|
444
|
-
//
|
|
491
|
+
// Accepts lowercase codes and common letter-O typos in numeric suffixes,
|
|
492
|
+
// then returns the canonical key if found, else null.
|
|
445
493
|
// Only normalizes O→0 in the segment AFTER the last dash — "QS-MOAYAD-03"
|
|
446
494
|
// contains a real "O" in the name and must not be mangled.
|
|
447
495
|
function resolveTeamCode(input) {
|
|
@@ -470,7 +518,7 @@ async function main() {
|
|
|
470
518
|
if (!member) {
|
|
471
519
|
console.log("");
|
|
472
520
|
log(`${RED}✗${RESET} Invalid code: "${rawCode}". Get your install code from Fawzi.`);
|
|
473
|
-
log(`${DIM} Tip: codes use digit zero, not letter O. Format: QS-NAME
|
|
521
|
+
log(`${DIM} Tip: codes use digit zero, not letter O. Format: QS-NAME-##${RESET}`);
|
|
474
522
|
console.log("");
|
|
475
523
|
process.exit(1);
|
|
476
524
|
}
|
|
@@ -498,23 +546,22 @@ async function main() {
|
|
|
498
546
|
|
|
499
547
|
// ─── Skills ──────────────────────────────────────────
|
|
500
548
|
const skillsDir = path.join(FRAMEWORK_DIR, "skills");
|
|
501
|
-
const skills = fs
|
|
502
|
-
.readdirSync(skillsDir)
|
|
503
|
-
.filter((d) => fs.statSync(path.join(skillsDir, d)).isDirectory());
|
|
549
|
+
const skills = ACTIVE_SKILLS.filter((d) => fs.existsSync(path.join(skillsDir, d, "SKILL.md")));
|
|
504
550
|
|
|
505
551
|
printSection("Skills");
|
|
506
552
|
const claudePruned = pruneDeprecatedSkills(CLAUDE_DIR);
|
|
507
553
|
for (const name of claudePruned) ok(`pruned deprecated: ${name}`);
|
|
508
554
|
for (const skill of skills) {
|
|
509
555
|
try {
|
|
510
|
-
|
|
556
|
+
copyTextTransform(
|
|
511
557
|
path.join(skillsDir, skill, "SKILL.md"),
|
|
512
|
-
path.join(CLAUDE_DIR, "skills", skill, "SKILL.md")
|
|
558
|
+
path.join(CLAUDE_DIR, "skills", skill, "SKILL.md"),
|
|
559
|
+
claudeText
|
|
513
560
|
);
|
|
514
561
|
// Copy REFERENCE.md if the skill has one (progressive-disclosure pattern)
|
|
515
562
|
const refSrc = path.join(skillsDir, skill, "REFERENCE.md");
|
|
516
563
|
if (fs.existsSync(refSrc)) {
|
|
517
|
-
|
|
564
|
+
copyTextTransform(refSrc, path.join(CLAUDE_DIR, "skills", skill, "REFERENCE.md"), claudeText);
|
|
518
565
|
}
|
|
519
566
|
// v5.1: Copy scripts/ subfolder if present (e.g. qualia-polish ships
|
|
520
567
|
// playwright-capture.mjs, loop.mjs, score.mjs that the --loop mode
|
|
@@ -537,7 +584,7 @@ async function main() {
|
|
|
537
584
|
const agentsDir = path.join(FRAMEWORK_DIR, "agents");
|
|
538
585
|
for (const file of fs.readdirSync(agentsDir)) {
|
|
539
586
|
try {
|
|
540
|
-
|
|
587
|
+
copyTextTransform(path.join(agentsDir, file), path.join(CLAUDE_DIR, "agents", file), claudeText);
|
|
541
588
|
ok(file);
|
|
542
589
|
} catch (e) {
|
|
543
590
|
warn(`${file} — ${e.message}`);
|
|
@@ -549,7 +596,7 @@ async function main() {
|
|
|
549
596
|
const rulesDir = path.join(FRAMEWORK_DIR, "rules");
|
|
550
597
|
for (const file of fs.readdirSync(rulesDir)) {
|
|
551
598
|
try {
|
|
552
|
-
|
|
599
|
+
copyTextTransform(path.join(rulesDir, file), path.join(CLAUDE_DIR, "rules", file), claudeText);
|
|
553
600
|
ok(file);
|
|
554
601
|
} catch (e) {
|
|
555
602
|
warn(`${file} — ${e.message}`);
|
|
@@ -590,7 +637,7 @@ async function main() {
|
|
|
590
637
|
if (fs.existsSync(designDir)) {
|
|
591
638
|
for (const file of fs.readdirSync(designDir)) {
|
|
592
639
|
try {
|
|
593
|
-
|
|
640
|
+
copyTextTransform(path.join(designDir, file), path.join(designDest, file), claudeText);
|
|
594
641
|
ok(file);
|
|
595
642
|
} catch (e) {
|
|
596
643
|
warn(`${file} — ${e.message}`);
|
|
@@ -651,10 +698,10 @@ async function main() {
|
|
|
651
698
|
const destPath = path.join(tmplDest, entry.name);
|
|
652
699
|
try {
|
|
653
700
|
if (entry.isDirectory()) {
|
|
654
|
-
|
|
701
|
+
copyTreeTransform(srcPath, destPath, claudeText);
|
|
655
702
|
ok(`${entry.name}/ (directory)`);
|
|
656
703
|
} else {
|
|
657
|
-
|
|
704
|
+
copyTextTransform(srcPath, destPath, claudeText);
|
|
658
705
|
ok(entry.name);
|
|
659
706
|
}
|
|
660
707
|
} catch (e) {
|
|
@@ -685,7 +732,7 @@ async function main() {
|
|
|
685
732
|
if (fs.existsSync(dest)) {
|
|
686
733
|
log(`${DIM}${file} (kept — user has customized)${RESET}`);
|
|
687
734
|
} else {
|
|
688
|
-
|
|
735
|
+
copyTextTransform(src, dest, claudeText);
|
|
689
736
|
ok(`${file} (initialized)`);
|
|
690
737
|
}
|
|
691
738
|
} catch (e) {
|
|
@@ -702,7 +749,7 @@ async function main() {
|
|
|
702
749
|
if (!fs.existsSync(refDest)) fs.mkdirSync(refDest, { recursive: true });
|
|
703
750
|
for (const file of fs.readdirSync(refDir)) {
|
|
704
751
|
try {
|
|
705
|
-
|
|
752
|
+
copyTextTransform(path.join(refDir, file), path.join(refDest, file), claudeText);
|
|
706
753
|
ok(file);
|
|
707
754
|
} catch (e) {
|
|
708
755
|
warn(`${file} — ${e.message}`);
|
|
@@ -741,7 +788,7 @@ async function main() {
|
|
|
741
788
|
try { fs.copyFileSync(claudeDest, bak); ok(`Backed up existing CLAUDE.md → ${path.basename(bak)}`); } catch {}
|
|
742
789
|
}
|
|
743
790
|
}
|
|
744
|
-
fs.writeFileSync(claudeDest, claudeMd, "utf8");
|
|
791
|
+
fs.writeFileSync(claudeDest, claudeText(claudeMd), "utf8");
|
|
745
792
|
ok(`Configured as ${member.role}`);
|
|
746
793
|
} catch (e) {
|
|
747
794
|
warn(`CLAUDE.md — ${e.message}`);
|
|
@@ -752,84 +799,23 @@ async function main() {
|
|
|
752
799
|
try {
|
|
753
800
|
const binDest = path.join(CLAUDE_DIR, "bin");
|
|
754
801
|
if (!fs.existsSync(binDest)) fs.mkdirSync(binDest, { recursive: true });
|
|
755
|
-
|
|
756
|
-
path.join(
|
|
757
|
-
path.join(
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
path.join(FRAMEWORK_DIR, "bin", "qualia-ui.js"),
|
|
762
|
-
path.join(binDest, "qualia-ui.js")
|
|
763
|
-
);
|
|
764
|
-
fs.chmodSync(path.join(binDest, "qualia-ui.js"), 0o755);
|
|
765
|
-
ok("qualia-ui.js (cosmetics library)");
|
|
766
|
-
copy(
|
|
767
|
-
path.join(FRAMEWORK_DIR, "bin", "statusline.js"),
|
|
768
|
-
path.join(binDest, "statusline.js")
|
|
769
|
-
);
|
|
770
|
-
ok("statusline.js (status bar renderer)");
|
|
771
|
-
copy(
|
|
772
|
-
path.join(FRAMEWORK_DIR, "bin", "knowledge.js"),
|
|
773
|
-
path.join(binDest, "knowledge.js")
|
|
774
|
-
);
|
|
775
|
-
fs.chmodSync(path.join(binDest, "knowledge.js"), 0o755);
|
|
776
|
-
ok("knowledge.js (memory-layer loader)");
|
|
777
|
-
copy(
|
|
778
|
-
path.join(FRAMEWORK_DIR, "bin", "knowledge-flush.js"),
|
|
779
|
-
path.join(binDest, "knowledge-flush.js")
|
|
780
|
-
);
|
|
781
|
-
fs.chmodSync(path.join(binDest, "knowledge-flush.js"), 0o755);
|
|
782
|
-
ok("knowledge-flush.js (cron-runnable flush)");
|
|
783
|
-
copy(
|
|
784
|
-
path.join(FRAMEWORK_DIR, "bin", "plan-contract.js"),
|
|
785
|
-
path.join(binDest, "plan-contract.js")
|
|
786
|
-
);
|
|
787
|
-
ok("plan-contract.js (plan JSON validator)");
|
|
788
|
-
copy(
|
|
789
|
-
path.join(FRAMEWORK_DIR, "bin", "agent-runs.js"),
|
|
790
|
-
path.join(binDest, "agent-runs.js")
|
|
791
|
-
);
|
|
792
|
-
ok("agent-runs.js (agent telemetry writer)");
|
|
793
|
-
copy(
|
|
794
|
-
path.join(FRAMEWORK_DIR, "bin", "slop-detect.mjs"),
|
|
795
|
-
path.join(binDest, "slop-detect.mjs")
|
|
796
|
-
);
|
|
797
|
-
fs.chmodSync(path.join(binDest, "slop-detect.mjs"), 0o755);
|
|
798
|
-
ok("slop-detect.mjs (anti-pattern scanner — runs pre-commit on frontend builds)");
|
|
799
|
-
copy(
|
|
800
|
-
path.join(FRAMEWORK_DIR, "bin", "erp-retry.js"),
|
|
801
|
-
path.join(binDest, "erp-retry.js")
|
|
802
|
-
);
|
|
803
|
-
fs.chmodSync(path.join(binDest, "erp-retry.js"), 0o755);
|
|
804
|
-
ok("erp-retry.js (ERP report retry queue — drained by session-start hook and erp-flush CLI)");
|
|
805
|
-
copy(
|
|
806
|
-
path.join(FRAMEWORK_DIR, "bin", "report-payload.js"),
|
|
807
|
-
path.join(binDest, "report-payload.js")
|
|
808
|
-
);
|
|
809
|
-
fs.chmodSync(path.join(binDest, "report-payload.js"), 0o755);
|
|
810
|
-
ok("report-payload.js (Framework -> ERP report payload builder)");
|
|
811
|
-
copy(
|
|
812
|
-
path.join(FRAMEWORK_DIR, "bin", "project-snapshot.js"),
|
|
813
|
-
path.join(binDest, "project-snapshot.js")
|
|
814
|
-
);
|
|
815
|
-
fs.chmodSync(path.join(binDest, "project-snapshot.js"), 0o755);
|
|
816
|
-
ok("project-snapshot.js (ERP/admin project progress snapshot)");
|
|
817
|
-
copy(
|
|
818
|
-
path.join(FRAMEWORK_DIR, "bin", "codex-goal.js"),
|
|
819
|
-
path.join(binDest, "codex-goal.js")
|
|
820
|
-
);
|
|
821
|
-
fs.chmodSync(path.join(binDest, "codex-goal.js"), 0o755);
|
|
822
|
-
ok("codex-goal.js (Codex /goal objective + token-budget suggester)");
|
|
802
|
+
for (const script of RUNTIME_BIN_SCRIPTS) {
|
|
803
|
+
const out = path.join(binDest, script.file);
|
|
804
|
+
copy(path.join(FRAMEWORK_DIR, "bin", script.file), out);
|
|
805
|
+
try { fs.chmodSync(out, 0o755); } catch {}
|
|
806
|
+
ok(script.label);
|
|
807
|
+
}
|
|
823
808
|
} catch (e) {
|
|
824
809
|
warn(`scripts — ${e.message}`);
|
|
825
810
|
}
|
|
826
811
|
|
|
827
812
|
// ─── Guide ─────────────────────────────────────────────
|
|
828
813
|
try {
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
814
|
+
copyTextTransform(
|
|
815
|
+
path.join(FRAMEWORK_DIR, "guide.md"),
|
|
816
|
+
path.join(CLAUDE_DIR, "qualia-guide.md"),
|
|
817
|
+
claudeText
|
|
818
|
+
);
|
|
833
819
|
ok("guide.md");
|
|
834
820
|
} catch (e) {
|
|
835
821
|
warn(`guide.md — ${e.message}`);
|
|
@@ -877,7 +863,7 @@ Recurring issues and their solutions.
|
|
|
877
863
|
## Install code "Invalid" — user typed letter O instead of digit 0
|
|
878
864
|
**Symptom:** \`npx qualia-framework install\` rejects \`QS-NAME-O1\` (letter O in suffix).
|
|
879
865
|
**Cause:** Team codes use digit zero (\`-01\`, \`-02\`, etc.), not letter O.
|
|
880
|
-
**Fix:** Since v2.8.1, install.js auto-normalizes: \`QS-
|
|
866
|
+
**Fix:** Since v2.8.1, install.js auto-normalizes: \`QS-HASAN-O2\` → \`QS-HASAN-02\`. The normalization only touches the segment after the last dash, so \`QS-MOAYAD-03\` (real O in name) is preserved.
|
|
881
867
|
**Framework version:** Fixed in v2.8.1.
|
|
882
868
|
|
|
883
869
|
---
|
|
@@ -1048,6 +1034,7 @@ Client-specific preferences, design choices, and requirements. Loaded by \`/qual
|
|
|
1048
1034
|
excludeDefault: true,
|
|
1049
1035
|
tips: [
|
|
1050
1036
|
"⬢ Lost? Type /qualia for the next step",
|
|
1037
|
+
"⬢ Broken behavior? Use /qualia-fix for root cause, patch, verify",
|
|
1051
1038
|
"⬢ Single feature? Use /qualia-feature, it auto-scopes",
|
|
1052
1039
|
"⬢ End of day? /qualia-report submits your shift before clock-out",
|
|
1053
1040
|
"⬢ Context isolation: every task gets a fresh AI brain",
|
|
@@ -1069,6 +1056,7 @@ Client-specific preferences, design choices, and requirements. Loaded by \`/qual
|
|
|
1069
1056
|
"session-start.js", "auto-update.js", "branch-guard.js", "pre-push.js",
|
|
1070
1057
|
"pre-deploy-gate.js", "migration-guard.js", "pre-compact.js",
|
|
1071
1058
|
"git-guardrails.js", "stop-session-log.js",
|
|
1059
|
+
"fawzi-approval-guard.js",
|
|
1072
1060
|
// v5.0 — insights-driven destructive-op + wrong-account guards
|
|
1073
1061
|
"vercel-account-guard.js", "env-empty-guard.js", "supabase-destructive-guard.js",
|
|
1074
1062
|
]);
|
|
@@ -1094,6 +1082,7 @@ Client-specific preferences, design choices, and requirements. Loaded by \`/qual
|
|
|
1094
1082
|
hooks: [
|
|
1095
1083
|
{ type: "command", command: nodeCmd("auto-update.js"), timeout: 5 },
|
|
1096
1084
|
{ type: "command", command: nodeCmd("git-guardrails.js"), timeout: 5, statusMessage: "⬢ Checking git safety..." },
|
|
1085
|
+
{ type: "command", command: nodeCmd("fawzi-approval-guard.js"), timeout: 5 },
|
|
1097
1086
|
{ type: "command", if: "Bash(git push*)", command: nodeCmd("branch-guard.js"), timeout: 5, statusMessage: "⬢ Checking branch permissions..." },
|
|
1098
1087
|
{ type: "command", if: "Bash(git push*)", command: nodeCmd("pre-push.js"), timeout: 15, statusMessage: "⬢ Syncing tracking..." },
|
|
1099
1088
|
{ type: "command", if: "Bash(vercel --prod*)", command: nodeCmd("pre-deploy-gate.js"), timeout: 180, statusMessage: "⬢ Running quality gates..." },
|
|
@@ -1106,6 +1095,7 @@ Client-specific preferences, design choices, and requirements. Loaded by \`/qual
|
|
|
1106
1095
|
{
|
|
1107
1096
|
matcher: "Edit|Write",
|
|
1108
1097
|
hooks: [
|
|
1098
|
+
{ type: "command", command: nodeCmd("fawzi-approval-guard.js"), timeout: 5 },
|
|
1109
1099
|
{ type: "command", if: "Edit(*migration*)|Write(*migration*)|Edit(*.sql)|Write(*.sql)", command: nodeCmd("migration-guard.js"), timeout: 10, statusMessage: "⬢ Checking migration safety..." },
|
|
1110
1100
|
],
|
|
1111
1101
|
},
|
|
@@ -1179,7 +1169,7 @@ Client-specific preferences, design choices, and requirements. Loaded by \`/qual
|
|
|
1179
1169
|
fs.writeFileSync(settingsTmp, JSON.stringify(settings, null, 2));
|
|
1180
1170
|
fs.renameSync(settingsTmp, settingsPath);
|
|
1181
1171
|
|
|
1182
|
-
ok("Hooks: session-start, auto-update, branch-guard, pre-push, migration-guard, deploy-gate, git-guardrails, stop-session-log, vercel-account-guard, env-empty-guard, supabase-destructive-guard");
|
|
1172
|
+
ok("Hooks: session-start, auto-update, branch-guard, pre-push, migration-guard, deploy-gate, git-guardrails, stop-session-log, fawzi-approval-guard, vercel-account-guard, env-empty-guard, supabase-destructive-guard");
|
|
1183
1173
|
ok("Status line + spinner configured");
|
|
1184
1174
|
ok("Environment variables + permissions");
|
|
1185
1175
|
|
|
@@ -1216,10 +1206,7 @@ function printSummary({ member, target, claudeInstalled }) {
|
|
|
1216
1206
|
const hooksSource = path.join(FRAMEWORK_DIR, "hooks");
|
|
1217
1207
|
const rulesDir = path.join(FRAMEWORK_DIR, "rules");
|
|
1218
1208
|
const tmplDir = path.join(FRAMEWORK_DIR, "templates");
|
|
1219
|
-
const
|
|
1220
|
-
const skillCount = fs
|
|
1221
|
-
.readdirSync(skillsDir)
|
|
1222
|
-
.filter((d) => fs.statSync(path.join(skillsDir, d)).isDirectory()).length;
|
|
1209
|
+
const skillCount = ACTIVE_SKILLS.length;
|
|
1223
1210
|
const agentCount = fs.readdirSync(agentsDir).filter((f) => f.endsWith(".md")).length;
|
|
1224
1211
|
const hookCount = fs.readdirSync(hooksSource).length;
|
|
1225
1212
|
const ruleCount = fs.readdirSync(rulesDir).length;
|
|
@@ -1264,6 +1251,7 @@ function printSummary({ member, target, claudeInstalled }) {
|
|
|
1264
1251
|
}
|
|
1265
1252
|
console.log("");
|
|
1266
1253
|
console.log(` ${DIM}New project?${RESET} ${TEAL}/qualia-new${RESET}`);
|
|
1254
|
+
console.log(` ${DIM}Broken thing?${RESET} ${TEAL}/qualia-fix${RESET}`);
|
|
1267
1255
|
console.log(` ${DIM}Single feature?${RESET} ${TEAL}/qualia-feature${RESET}`);
|
|
1268
1256
|
console.log(` ${DIM}End of day?${RESET} ${TEAL}/qualia-report${RESET} ${DIM}(shift submission)${RESET}`);
|
|
1269
1257
|
console.log(` ${DIM}Stuck?${RESET} ${TEAL}/qualia${RESET}`);
|
|
@@ -1341,53 +1329,20 @@ async function installCodex(member, target) {
|
|
|
1341
1329
|
}
|
|
1342
1330
|
|
|
1343
1331
|
// Codex treats config.toml as optional, but doctor reports a warning when it
|
|
1344
|
-
// is absent.
|
|
1345
|
-
//
|
|
1332
|
+
// is absent. Keep user settings intact while guaranteeing the native bottom
|
|
1333
|
+
// status line is present.
|
|
1346
1334
|
try {
|
|
1347
1335
|
const configToml = path.join(CODEX_DIR, "config.toml");
|
|
1336
|
+
const existing = fs.existsSync(configToml) ? fs.readFileSync(configToml, "utf8") : "";
|
|
1337
|
+
const next = ensureCodexStatusLineConfig(existing);
|
|
1348
1338
|
if (!fs.existsSync(configToml)) {
|
|
1349
|
-
atomicWrite(configToml,
|
|
1350
|
-
"# Created by qualia-framework install.",
|
|
1351
|
-
"# User settings can be added normally; Qualia runtime lives in AGENTS.md, hooks.json, agents/, and bin/.",
|
|
1352
|
-
"",
|
|
1353
|
-
"[features]",
|
|
1354
|
-
"hooks = true",
|
|
1355
|
-
"plugin_hooks = true",
|
|
1356
|
-
"",
|
|
1357
|
-
"# Codex's built-in status line is rendered at the bottom of the TUI.",
|
|
1358
|
-
"# It takes an ARRAY of pre-defined segment names; Codex does NOT support",
|
|
1359
|
-
"# custom-command status lines (unlike Claude's settings.json statusLine),",
|
|
1360
|
-
"# so the Qualia phase/state info is rendered via the SessionStart banner",
|
|
1361
|
-
"# at the top of the session instead. The segment list below mirrors the",
|
|
1362
|
-
"# Codex default rich layout.",
|
|
1363
|
-
"[tui]",
|
|
1364
|
-
'status_line = ["model-with-reasoning", "task-progress", "current-dir", "git-branch", "context-used", "five-hour-limit", "weekly-limit"]',
|
|
1365
|
-
"status_line_use_colors = true",
|
|
1366
|
-
"",
|
|
1367
|
-
].join("\n"));
|
|
1339
|
+
atomicWrite(configToml, next);
|
|
1368
1340
|
ok("config.toml (minimal Codex config)");
|
|
1341
|
+
} else if (next !== existing) {
|
|
1342
|
+
atomicWrite(configToml, next);
|
|
1343
|
+
ok("config.toml (Codex bottom status line)");
|
|
1369
1344
|
} else {
|
|
1370
|
-
|
|
1371
|
-
// every other user setting untouched.
|
|
1372
|
-
try {
|
|
1373
|
-
const existing = fs.readFileSync(configToml, "utf8");
|
|
1374
|
-
if (!/^\[tui\]/m.test(existing) && !/^status_line\s*=/m.test(existing)) {
|
|
1375
|
-
const append = [
|
|
1376
|
-
"",
|
|
1377
|
-
"# Added by qualia-framework — Codex bottom status line.",
|
|
1378
|
-
"[tui]",
|
|
1379
|
-
'status_line = ["model-with-reasoning", "task-progress", "current-dir", "git-branch", "context-used", "five-hour-limit", "weekly-limit"]',
|
|
1380
|
-
"status_line_use_colors = true",
|
|
1381
|
-
"",
|
|
1382
|
-
].join("\n");
|
|
1383
|
-
fs.appendFileSync(configToml, append);
|
|
1384
|
-
ok("config.toml (appended [tui] status line block)");
|
|
1385
|
-
} else {
|
|
1386
|
-
log(`${DIM}config.toml (kept — user has customized)${RESET}`);
|
|
1387
|
-
}
|
|
1388
|
-
} catch {
|
|
1389
|
-
log(`${DIM}config.toml (kept — user has customized)${RESET}`);
|
|
1390
|
-
}
|
|
1345
|
+
log(`${DIM}config.toml (kept — Codex status line already wired)${RESET}`);
|
|
1391
1346
|
}
|
|
1392
1347
|
} catch (e) {
|
|
1393
1348
|
warn(`Codex config.toml — ${e.message}`);
|
|
@@ -1436,20 +1391,7 @@ async function installCodex(member, target) {
|
|
|
1436
1391
|
try {
|
|
1437
1392
|
const binDest = path.join(CODEX_DIR, "bin");
|
|
1438
1393
|
if (!fs.existsSync(binDest)) fs.mkdirSync(binDest, { recursive: true });
|
|
1439
|
-
const scripts =
|
|
1440
|
-
"state.js",
|
|
1441
|
-
"qualia-ui.js",
|
|
1442
|
-
"statusline.js",
|
|
1443
|
-
"knowledge.js",
|
|
1444
|
-
"knowledge-flush.js",
|
|
1445
|
-
"plan-contract.js",
|
|
1446
|
-
"agent-runs.js",
|
|
1447
|
-
"slop-detect.mjs",
|
|
1448
|
-
"erp-retry.js",
|
|
1449
|
-
"report-payload.js",
|
|
1450
|
-
"project-snapshot.js",
|
|
1451
|
-
"codex-goal.js",
|
|
1452
|
-
];
|
|
1394
|
+
const scripts = binFiles();
|
|
1453
1395
|
for (const script of scripts) {
|
|
1454
1396
|
const src = path.join(FRAMEWORK_DIR, "bin", script);
|
|
1455
1397
|
const out = path.join(binDest, script);
|
|
@@ -1487,9 +1429,9 @@ async function installCodex(member, target) {
|
|
|
1487
1429
|
const rulesDest = path.join(CODEX_DIR, "rules");
|
|
1488
1430
|
if (!fs.existsSync(rulesDest)) fs.mkdirSync(rulesDest, { recursive: true });
|
|
1489
1431
|
for (const file of fs.readdirSync(rulesDir)) {
|
|
1490
|
-
|
|
1432
|
+
copyTextTransform(path.join(rulesDir, file), path.join(rulesDest, file), codexText);
|
|
1491
1433
|
}
|
|
1492
|
-
|
|
1434
|
+
copyTreeTransform(path.join(FRAMEWORK_DIR, "qualia-design"), path.join(CODEX_DIR, "qualia-design"), codexText);
|
|
1493
1435
|
ok("rules/ + qualia-design/");
|
|
1494
1436
|
} catch (e) {
|
|
1495
1437
|
warn(`Codex rules/design — ${e.message}`);
|
|
@@ -1503,9 +1445,9 @@ async function installCodex(member, target) {
|
|
|
1503
1445
|
const skillsDest = path.join(CODEX_DIR, "skills");
|
|
1504
1446
|
const codexPruned = pruneDeprecatedSkills(CODEX_DIR);
|
|
1505
1447
|
for (const name of codexPruned) ok(`pruned deprecated: ${name}`);
|
|
1506
|
-
for (const skill of
|
|
1448
|
+
for (const skill of ACTIVE_SKILLS) {
|
|
1507
1449
|
const src = path.join(skillsSrc, skill);
|
|
1508
|
-
if (!fs.statSync(src).isDirectory()) continue;
|
|
1450
|
+
if (!fs.existsSync(src) || !fs.statSync(src).isDirectory()) continue;
|
|
1509
1451
|
copyTreeTransform(src, path.join(skillsDest, skill), codexText);
|
|
1510
1452
|
}
|
|
1511
1453
|
ok("skills/");
|
|
@@ -1582,6 +1524,7 @@ async function installCodex(member, target) {
|
|
|
1582
1524
|
hooks: [
|
|
1583
1525
|
{ type: "command", command: nodeCmd("auto-update.js"), timeout: 5, statusMessage: "Qualia update check..." },
|
|
1584
1526
|
{ type: "command", command: nodeCmd("git-guardrails.js"), timeout: 5, statusMessage: "Qualia git safety..." },
|
|
1527
|
+
{ type: "command", command: nodeCmd("fawzi-approval-guard.js"), timeout: 5 },
|
|
1585
1528
|
{ type: "command", command: nodeCmd("branch-guard.js"), timeout: 5 },
|
|
1586
1529
|
{ type: "command", command: nodeCmd("pre-push.js"), timeout: 15 },
|
|
1587
1530
|
{ type: "command", command: nodeCmd("pre-deploy-gate.js"), timeout: 180 },
|
|
@@ -1593,6 +1536,7 @@ async function installCodex(member, target) {
|
|
|
1593
1536
|
{
|
|
1594
1537
|
matcher: "Edit|Write",
|
|
1595
1538
|
hooks: [
|
|
1539
|
+
{ type: "command", command: nodeCmd("fawzi-approval-guard.js"), timeout: 5 },
|
|
1596
1540
|
{ type: "command", command: nodeCmd("migration-guard.js"), timeout: 10 },
|
|
1597
1541
|
],
|
|
1598
1542
|
},
|
package/bin/knowledge-flush.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// ~/.claude/bin/knowledge-flush.js — non-interactive memory-layer flush.
|
|
3
3
|
//
|
|
4
|
-
//
|
|
5
|
-
// CI/scheduled job) without
|
|
4
|
+
// Runs the Qualia memory flush prompt from cron (or systemd timer, or any
|
|
5
|
+
// CI/scheduled job) without installing a separate slash command. Closes the
|
|
6
6
|
// memory loop end-to-end:
|
|
7
7
|
//
|
|
8
8
|
// Stop hook (auto, every turn) → <install-home>/knowledge/daily-log/{date}.md
|
|
9
9
|
// THIS SCRIPT (weekly cron) → spawns the installed agent CLI
|
|
10
|
-
//
|
|
10
|
+
// flush prompt → promotes raw → curated tier
|
|
11
11
|
// bin/knowledge.js (every spawn) → reads index.md → reaches the right file
|
|
12
12
|
//
|
|
13
13
|
// Usage:
|
|
@@ -86,10 +86,9 @@ function which(cmd) {
|
|
|
86
86
|
return null;
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
-
// Pass-through args (so `--days 14`, `--dry-run`, `--project X`
|
|
90
|
-
//
|
|
91
|
-
//
|
|
92
|
-
// the daily-log is genuinely empty.
|
|
89
|
+
// Pass-through args (so `--days 14`, `--dry-run`, `--project X` are visible to
|
|
90
|
+
// the agent prompt). We only parse `--days` locally to short-circuit when the
|
|
91
|
+
// daily-log is genuinely empty.
|
|
93
92
|
const argv = process.argv.slice(2);
|
|
94
93
|
const flagIdx = argv.indexOf("--days");
|
|
95
94
|
const days = flagIdx >= 0 ? parseInt(argv[flagIdx + 1], 10) || 7 : 7;
|
|
@@ -128,9 +127,21 @@ if (!dailyLogHasRecentEntries(days)) {
|
|
|
128
127
|
|
|
129
128
|
// ── Run ──────────────────────────────────────────────────
|
|
130
129
|
// `claude -p "<prompt>"` and `codex exec "<prompt>"` run a single
|
|
131
|
-
// non-interactive turn.
|
|
132
|
-
//
|
|
133
|
-
const
|
|
130
|
+
// non-interactive turn. Keep the prompt self-contained so no separate flush
|
|
131
|
+
// slash command needs to be installed.
|
|
132
|
+
const argsText = argv.join(" ").trim() || "(none)";
|
|
133
|
+
const prompt = [
|
|
134
|
+
"Run the Qualia memory flush.",
|
|
135
|
+
"",
|
|
136
|
+
`Arguments: ${argsText}`,
|
|
137
|
+
`Install home: ${QUALIA_HOME}`,
|
|
138
|
+
"",
|
|
139
|
+
"Read recent markdown files from knowledge/daily-log under the install home.",
|
|
140
|
+
"Promote recurring patterns, decisions, fixes, and client preferences into the curated knowledge tier using bin/knowledge.js append.",
|
|
141
|
+
"Do not promote one-off noise. If --dry-run is present, report planned promotions without writing.",
|
|
142
|
+
"If --project NAME is present, limit promotions to that project.",
|
|
143
|
+
"Finish with one line starting exactly: ⬢ Flushed daily-log",
|
|
144
|
+
].join("\n");
|
|
134
145
|
|
|
135
146
|
const cliArgs = IS_CODEX_INSTALL ? ["exec", prompt] : ["-p", prompt];
|
|
136
147
|
const result = spawnSync(agentBin, cliArgs, {
|
package/bin/knowledge.js
CHANGED
|
@@ -79,7 +79,7 @@ function readSafe(p) {
|
|
|
79
79
|
// 3. Path with "/" → treat as relative to knowledge dir (concepts/foo)
|
|
80
80
|
// 4. Bare name → look in top-level first; if missing, search known
|
|
81
81
|
// subdirectories (concepts/, daily-log/) for an exact match. This
|
|
82
|
-
// means
|
|
82
|
+
// means the memory flush can write to concepts/voice-agent-call-state.md
|
|
83
83
|
// and skills can later run `knowledge.js load voice-agent-call-state`
|
|
84
84
|
// without knowing it lives in a subdirectory.
|
|
85
85
|
function resolveFile(name) {
|