qualia-framework 3.1.0 → 3.2.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/CLAUDE.md +4 -3
- package/README.md +5 -10
- package/agents/planner.md +0 -52
- package/agents/verifier.md +32 -180
- package/bin/cli.js +9 -403
- package/bin/install.js +61 -113
- package/bin/qualia-ui.js +15 -0
- package/bin/state.js +6 -200
- package/bin/statusline.js +4 -4
- package/hooks/auto-update.js +30 -8
- package/hooks/branch-guard.js +2 -23
- package/hooks/migration-guard.js +0 -23
- package/hooks/pre-compact.js +0 -20
- package/hooks/pre-deploy-gate.js +0 -39
- package/hooks/pre-push.js +0 -20
- package/hooks/session-start.js +44 -0
- package/package.json +4 -5
- package/skills/qualia/SKILL.md +0 -1
- package/skills/qualia-build/SKILL.md +0 -18
- package/skills/qualia-design/SKILL.md +8 -14
- package/skills/qualia-learn/SKILL.md +4 -27
- package/skills/qualia-optimize/SKILL.md +417 -0
- package/skills/qualia-polish/SKILL.md +117 -167
- package/skills/qualia-report/SKILL.md +8 -17
- package/skills/qualia-review/SKILL.md +41 -126
- package/skills/qualia-verify/SKILL.md +1 -1
- package/templates/DESIGN.md +102 -440
- package/templates/plan.md +0 -14
- package/tests/bin.test.sh +6 -20
- package/tests/hooks.test.sh +7 -76
- package/tests/state.test.sh +11 -189
- package/docs/erp-contract.md +0 -161
- package/hooks/block-env-edit.js +0 -52
- package/rules/infrastructure.md +0 -87
- package/skills/qualia-help/SKILL.md +0 -60
- package/skills/qualia-test/SKILL.md +0 -134
- package/templates/help.html +0 -476
- package/tests/runner.js +0 -1956
package/hooks/auto-update.js
CHANGED
|
@@ -66,6 +66,11 @@ try {
|
|
|
66
66
|
|
|
67
67
|
// Fork the check-and-update into a detached background process so the hook
|
|
68
68
|
// returns immediately and Claude Code is never blocked.
|
|
69
|
+
//
|
|
70
|
+
// OWNER: silent auto-install (unchanged behavior).
|
|
71
|
+
// EMPLOYEE: write a sticky notification file — session-start.js renders a
|
|
72
|
+
// banner every session until they run the update manually. Fawzi (OWNER)
|
|
73
|
+
// never sees the banner because his framework auto-updates ahead of it.
|
|
69
74
|
const script = `
|
|
70
75
|
const fs = require("fs");
|
|
71
76
|
const path = require("path");
|
|
@@ -73,6 +78,7 @@ try {
|
|
|
73
78
|
const CLAUDE_DIR = ${JSON.stringify(CLAUDE_DIR)};
|
|
74
79
|
const LOCK_FILE = ${JSON.stringify(LOCK_FILE)};
|
|
75
80
|
const CONFIG_FILE = ${JSON.stringify(CONFIG_FILE)};
|
|
81
|
+
const NOTIF_FILE = path.join(CLAUDE_DIR, ".qualia-update-available.json");
|
|
76
82
|
const cfg = ${JSON.stringify(cfg)};
|
|
77
83
|
try {
|
|
78
84
|
fs.writeFileSync(LOCK_FILE, String(process.pid));
|
|
@@ -82,7 +88,7 @@ try {
|
|
|
82
88
|
shell: process.platform === "win32",
|
|
83
89
|
});
|
|
84
90
|
const latest = ((r.stdout || "").trim());
|
|
85
|
-
if (!latest) { fs.unlinkSync(LOCK_FILE); process.exit(0); }
|
|
91
|
+
if (!latest) { try { fs.unlinkSync(LOCK_FILE); } catch {} process.exit(0); }
|
|
86
92
|
const cmp = (a, b) => {
|
|
87
93
|
const pa = a.split(".").map(Number), pb = b.split(".").map(Number);
|
|
88
94
|
for (let i = 0; i < 3; i++) {
|
|
@@ -92,13 +98,29 @@ try {
|
|
|
92
98
|
return 0;
|
|
93
99
|
};
|
|
94
100
|
if (cmp(latest, cfg.version) > 0) {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
101
|
+
if (cfg.role === "OWNER") {
|
|
102
|
+
// Silent auto-install for OWNER — no notification banner ever shown.
|
|
103
|
+
spawnSync("npx", ["qualia-framework@latest", "install"], {
|
|
104
|
+
input: cfg.code + "\\n",
|
|
105
|
+
timeout: 120000,
|
|
106
|
+
stdio: ["pipe", "ignore", "ignore"],
|
|
107
|
+
shell: process.platform === "win32",
|
|
108
|
+
});
|
|
109
|
+
try { fs.unlinkSync(NOTIF_FILE); } catch {}
|
|
110
|
+
} else {
|
|
111
|
+
// EMPLOYEE: write sticky notification. session-start.js will render
|
|
112
|
+
// a visible banner every session until the employee runs the update.
|
|
113
|
+
try {
|
|
114
|
+
fs.writeFileSync(NOTIF_FILE, JSON.stringify({
|
|
115
|
+
current: cfg.version,
|
|
116
|
+
latest: latest,
|
|
117
|
+
detected_at: new Date().toISOString(),
|
|
118
|
+
}, null, 2));
|
|
119
|
+
} catch {}
|
|
120
|
+
}
|
|
121
|
+
} else {
|
|
122
|
+
// Already up to date — clear any stale notification file.
|
|
123
|
+
try { fs.unlinkSync(NOTIF_FILE); } catch {}
|
|
102
124
|
}
|
|
103
125
|
} catch {}
|
|
104
126
|
try { fs.unlinkSync(LOCK_FILE); } catch {}
|
package/hooks/branch-guard.js
CHANGED
|
@@ -10,30 +10,11 @@ const path = require("path");
|
|
|
10
10
|
const os = require("os");
|
|
11
11
|
const { spawnSync } = require("child_process");
|
|
12
12
|
|
|
13
|
-
const _traceStart = Date.now();
|
|
14
|
-
|
|
15
13
|
const CONFIG = path.join(os.homedir(), ".claude", ".qualia-config.json");
|
|
16
14
|
|
|
17
|
-
function _trace(hookName, result, extra) {
|
|
18
|
-
try {
|
|
19
|
-
const traceDir = path.join(os.homedir(), ".claude", ".qualia-traces");
|
|
20
|
-
if (!fs.existsSync(traceDir)) fs.mkdirSync(traceDir, { recursive: true });
|
|
21
|
-
const entry = {
|
|
22
|
-
hook: hookName,
|
|
23
|
-
result,
|
|
24
|
-
timestamp: new Date().toISOString(),
|
|
25
|
-
duration_ms: Date.now() - _traceStart,
|
|
26
|
-
...extra,
|
|
27
|
-
};
|
|
28
|
-
const file = path.join(traceDir, `${new Date().toISOString().split("T")[0]}.jsonl`);
|
|
29
|
-
fs.appendFileSync(file, JSON.stringify(entry) + "\n");
|
|
30
|
-
} catch {}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
15
|
function fail(msg) {
|
|
34
16
|
console.log(msg);
|
|
35
|
-
|
|
36
|
-
process.exit(2);
|
|
17
|
+
process.exit(1);
|
|
37
18
|
}
|
|
38
19
|
|
|
39
20
|
let role = "";
|
|
@@ -59,10 +40,8 @@ if (branch === "main" || branch === "master") {
|
|
|
59
40
|
if (role !== "OWNER") {
|
|
60
41
|
console.log(`BLOCKED: Employees cannot push to ${branch}. Create a feature branch first.`);
|
|
61
42
|
console.log("Run: git checkout -b feature/your-feature-name");
|
|
62
|
-
|
|
63
|
-
process.exit(2);
|
|
43
|
+
process.exit(1);
|
|
64
44
|
}
|
|
65
45
|
}
|
|
66
46
|
|
|
67
|
-
_trace("branch-guard", "allow");
|
|
68
47
|
process.exit(0);
|
package/hooks/migration-guard.js
CHANGED
|
@@ -6,8 +6,6 @@
|
|
|
6
6
|
|
|
7
7
|
const fs = require("fs");
|
|
8
8
|
|
|
9
|
-
const _traceStart = Date.now();
|
|
10
|
-
|
|
11
9
|
function readInput() {
|
|
12
10
|
try {
|
|
13
11
|
const raw = fs.readFileSync(0, "utf8");
|
|
@@ -22,27 +20,8 @@ const ti = input.tool_input || {};
|
|
|
22
20
|
const file = String(ti.file_path || "").replace(/\\/g, "/");
|
|
23
21
|
const content = String(ti.content || ti.new_string || "");
|
|
24
22
|
|
|
25
|
-
function _trace(hookName, result, extra) {
|
|
26
|
-
try {
|
|
27
|
-
const os = require("os");
|
|
28
|
-
const path = require("path");
|
|
29
|
-
const traceDir = path.join(os.homedir(), ".claude", ".qualia-traces");
|
|
30
|
-
if (!fs.existsSync(traceDir)) fs.mkdirSync(traceDir, { recursive: true });
|
|
31
|
-
const entry = {
|
|
32
|
-
hook: hookName,
|
|
33
|
-
result,
|
|
34
|
-
timestamp: new Date().toISOString(),
|
|
35
|
-
duration_ms: Date.now() - _traceStart,
|
|
36
|
-
...extra,
|
|
37
|
-
};
|
|
38
|
-
const filePath = path.join(traceDir, `${new Date().toISOString().split("T")[0]}.jsonl`);
|
|
39
|
-
fs.appendFileSync(filePath, JSON.stringify(entry) + "\n");
|
|
40
|
-
} catch {}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
23
|
// Only inspect migration/SQL files
|
|
44
24
|
if (!/migration|migrate|\.sql$/i.test(file)) {
|
|
45
|
-
_trace("migration-guard", "allow", { reason: "non-migration file" });
|
|
46
25
|
process.exit(0);
|
|
47
26
|
}
|
|
48
27
|
|
|
@@ -75,9 +54,7 @@ if (errors.length > 0) {
|
|
|
75
54
|
}
|
|
76
55
|
console.log("");
|
|
77
56
|
console.log("Fix these before proceeding. If intentional, ask Fawzi to approve.");
|
|
78
|
-
_trace("migration-guard", "block", { errors });
|
|
79
57
|
process.exit(2);
|
|
80
58
|
}
|
|
81
59
|
|
|
82
|
-
_trace("migration-guard", "allow");
|
|
83
60
|
process.exit(0);
|
package/hooks/pre-compact.js
CHANGED
|
@@ -7,8 +7,6 @@ const fs = require("fs");
|
|
|
7
7
|
const path = require("path");
|
|
8
8
|
const { spawnSync } = require("child_process");
|
|
9
9
|
|
|
10
|
-
const _traceStart = Date.now();
|
|
11
|
-
|
|
12
10
|
const STATE_FILE = path.join(".planning", "STATE.md");
|
|
13
11
|
|
|
14
12
|
try {
|
|
@@ -31,22 +29,4 @@ try {
|
|
|
31
29
|
// Silent — never block compaction
|
|
32
30
|
}
|
|
33
31
|
|
|
34
|
-
function _trace(hookName, result, extra) {
|
|
35
|
-
try {
|
|
36
|
-
const os = require("os");
|
|
37
|
-
const traceDir = path.join(os.homedir(), ".claude", ".qualia-traces");
|
|
38
|
-
if (!fs.existsSync(traceDir)) fs.mkdirSync(traceDir, { recursive: true });
|
|
39
|
-
const entry = {
|
|
40
|
-
hook: hookName,
|
|
41
|
-
result,
|
|
42
|
-
timestamp: new Date().toISOString(),
|
|
43
|
-
duration_ms: Date.now() - _traceStart,
|
|
44
|
-
...extra,
|
|
45
|
-
};
|
|
46
|
-
const file = path.join(traceDir, `${new Date().toISOString().split("T")[0]}.jsonl`);
|
|
47
|
-
fs.appendFileSync(file, JSON.stringify(entry) + "\n");
|
|
48
|
-
} catch {}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
_trace("pre-compact", "allow");
|
|
52
32
|
process.exit(0);
|
package/hooks/pre-deploy-gate.js
CHANGED
|
@@ -9,25 +9,6 @@ const fs = require("fs");
|
|
|
9
9
|
const path = require("path");
|
|
10
10
|
const { spawnSync } = require("child_process");
|
|
11
11
|
|
|
12
|
-
const _traceStart = Date.now();
|
|
13
|
-
|
|
14
|
-
function _trace(hookName, result, extra) {
|
|
15
|
-
try {
|
|
16
|
-
const os = require("os");
|
|
17
|
-
const traceDir = path.join(os.homedir(), ".claude", ".qualia-traces");
|
|
18
|
-
if (!fs.existsSync(traceDir)) fs.mkdirSync(traceDir, { recursive: true });
|
|
19
|
-
const entry = {
|
|
20
|
-
hook: hookName,
|
|
21
|
-
result,
|
|
22
|
-
timestamp: new Date().toISOString(),
|
|
23
|
-
duration_ms: Date.now() - _traceStart,
|
|
24
|
-
...extra,
|
|
25
|
-
};
|
|
26
|
-
const file = path.join(traceDir, `${new Date().toISOString().split("T")[0]}.jsonl`);
|
|
27
|
-
fs.appendFileSync(file, JSON.stringify(entry) + "\n");
|
|
28
|
-
} catch {}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
12
|
function runGate(label, cmd, args, { required = true } = {}) {
|
|
32
13
|
const r = spawnSync(cmd, args, {
|
|
33
14
|
stdio: "ignore",
|
|
@@ -40,7 +21,6 @@ function runGate(label, cmd, args, { required = true } = {}) {
|
|
|
40
21
|
}
|
|
41
22
|
if (required) {
|
|
42
23
|
console.log(`BLOCKED: ${label} errors. Fix before deploying.`);
|
|
43
|
-
_trace("pre-deploy-gate", "block", { gate: label });
|
|
44
24
|
process.exit(1);
|
|
45
25
|
}
|
|
46
26
|
return false;
|
|
@@ -80,27 +60,10 @@ function scanServiceRoleLeaks() {
|
|
|
80
60
|
const leaks = [];
|
|
81
61
|
for (const root of roots) {
|
|
82
62
|
for (const file of walk(root)) {
|
|
83
|
-
// --- Path-based skips (no I/O needed) ---
|
|
84
|
-
|
|
85
63
|
// Skip server-only files (convention: *.server.ts, server/ dirs)
|
|
86
64
|
if (/\.server\.|[\\/]server[\\/]/.test(file)) continue;
|
|
87
|
-
|
|
88
|
-
// Skip App Router route handlers (always server-side)
|
|
89
|
-
if (/[\\/]route\.(ts|tsx|js|jsx|mjs)$/.test(file)) continue;
|
|
90
|
-
|
|
91
|
-
// Skip middleware (always server-side)
|
|
92
|
-
if (/[\\/]middleware\.(ts|tsx|js|jsx|mjs)$/.test(file)) continue;
|
|
93
|
-
|
|
94
|
-
// Skip files in app/api/ directory (always server-side)
|
|
95
|
-
if (/[\\/]app[\\/]api[\\/]/.test(file)) continue;
|
|
96
|
-
|
|
97
|
-
// --- Content-based checks (requires reading file) ---
|
|
98
65
|
try {
|
|
99
66
|
const content = fs.readFileSync(file, "utf8");
|
|
100
|
-
|
|
101
|
-
// Skip files with "use server" directive (Server Actions / Server Components)
|
|
102
|
-
if (/^["']use server["']/m.test(content)) continue;
|
|
103
|
-
|
|
104
67
|
if (/service_role/.test(content)) {
|
|
105
68
|
leaks.push(file);
|
|
106
69
|
}
|
|
@@ -139,11 +102,9 @@ if (leaks.length > 0) {
|
|
|
139
102
|
for (const f of leaks.slice(0, 10)) {
|
|
140
103
|
console.log(` ✗ ${f}`);
|
|
141
104
|
}
|
|
142
|
-
_trace("pre-deploy-gate", "block", { gate: "security", leaks: leaks.slice(0, 10) });
|
|
143
105
|
process.exit(1);
|
|
144
106
|
}
|
|
145
107
|
console.log(" ✓ Security");
|
|
146
108
|
console.log("⬢ All gates passed.");
|
|
147
109
|
|
|
148
|
-
_trace("pre-deploy-gate", "allow");
|
|
149
110
|
process.exit(0);
|
package/hooks/pre-push.js
CHANGED
|
@@ -8,8 +8,6 @@ const fs = require("fs");
|
|
|
8
8
|
const path = require("path");
|
|
9
9
|
const { spawnSync } = require("child_process");
|
|
10
10
|
|
|
11
|
-
const _traceStart = Date.now();
|
|
12
|
-
|
|
13
11
|
const TRACKING = path.join(".planning", "tracking.json");
|
|
14
12
|
|
|
15
13
|
try {
|
|
@@ -32,22 +30,4 @@ try {
|
|
|
32
30
|
process.stderr.write(`WARNING: tracking sync failed: ${err.message}\n`);
|
|
33
31
|
}
|
|
34
32
|
|
|
35
|
-
function _trace(hookName, result, extra) {
|
|
36
|
-
try {
|
|
37
|
-
const os = require("os");
|
|
38
|
-
const traceDir = path.join(os.homedir(), ".claude", ".qualia-traces");
|
|
39
|
-
if (!fs.existsSync(traceDir)) fs.mkdirSync(traceDir, { recursive: true });
|
|
40
|
-
const entry = {
|
|
41
|
-
hook: hookName,
|
|
42
|
-
result,
|
|
43
|
-
timestamp: new Date().toISOString(),
|
|
44
|
-
duration_ms: Date.now() - _traceStart,
|
|
45
|
-
...extra,
|
|
46
|
-
};
|
|
47
|
-
const file = path.join(traceDir, `${new Date().toISOString().split("T")[0]}.jsonl`);
|
|
48
|
-
fs.appendFileSync(file, JSON.stringify(entry) + "\n");
|
|
49
|
-
} catch {}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
_trace("pre-push", "allow");
|
|
53
33
|
process.exit(0);
|
package/hooks/session-start.js
CHANGED
|
@@ -45,6 +45,46 @@ function getNextCommand() {
|
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
function maybeShowUpdateBanner() {
|
|
49
|
+
// Sticky framework update notification for EMPLOYEEs. Populated by
|
|
50
|
+
// auto-update.js when it detects a newer qualia-framework version on npm.
|
|
51
|
+
// OWNER auto-updates silently, so this file is never created for Fawzi.
|
|
52
|
+
try {
|
|
53
|
+
const notifFile = path.join(HOME, ".claude", ".qualia-update-available.json");
|
|
54
|
+
if (!fs.existsSync(notifFile)) return;
|
|
55
|
+
|
|
56
|
+
const cfg = readConfig();
|
|
57
|
+
// Belt-and-suspenders: even if a stale notification exists, OWNER never sees it.
|
|
58
|
+
if (cfg.role === "OWNER") {
|
|
59
|
+
try { fs.unlinkSync(notifFile); } catch {}
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const notif = JSON.parse(fs.readFileSync(notifFile, "utf8"));
|
|
64
|
+
|
|
65
|
+
// If the user has already updated (cfg.version >= notif.latest), clear the file.
|
|
66
|
+
if (cfg.version && notif.latest) {
|
|
67
|
+
const cmp = (a, b) => {
|
|
68
|
+
const pa = String(a).split(".").map(Number);
|
|
69
|
+
const pb = String(b).split(".").map(Number);
|
|
70
|
+
for (let i = 0; i < 3; i++) {
|
|
71
|
+
if ((pa[i] || 0) > (pb[i] || 0)) return 1;
|
|
72
|
+
if ((pa[i] || 0) < (pb[i] || 0)) return -1;
|
|
73
|
+
}
|
|
74
|
+
return 0;
|
|
75
|
+
};
|
|
76
|
+
if (cmp(cfg.version, notif.latest) >= 0) {
|
|
77
|
+
try { fs.unlinkSync(notifFile); } catch {}
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
runUi("update", notif.current || cfg.version || "?", notif.latest || "?");
|
|
83
|
+
} catch {
|
|
84
|
+
// Never fail the session start.
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
48
88
|
function fallbackText() {
|
|
49
89
|
// If qualia-ui.js is missing, emit plain text. Keeps the session informative
|
|
50
90
|
// even on a broken install.
|
|
@@ -66,6 +106,10 @@ function fallbackText() {
|
|
|
66
106
|
}
|
|
67
107
|
|
|
68
108
|
try {
|
|
109
|
+
// Sticky update notification — shown before anything else every session
|
|
110
|
+
// until the employee runs `npx qualia-framework@latest install`.
|
|
111
|
+
maybeShowUpdateBanner();
|
|
112
|
+
|
|
69
113
|
if (!fs.existsSync(UI)) {
|
|
70
114
|
fallbackText();
|
|
71
115
|
} else if (fs.existsSync(STATE_FILE)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "qualia-framework",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.2.0",
|
|
4
4
|
"description": "Claude Code workflow framework by Qualia Solutions. Plan, build, verify, ship.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"qualia-framework": "./bin/cli.js"
|
|
@@ -19,11 +19,11 @@
|
|
|
19
19
|
"license": "MIT",
|
|
20
20
|
"repository": {
|
|
21
21
|
"type": "git",
|
|
22
|
-
"url": "git+https://github.com/
|
|
22
|
+
"url": "git+https://github.com/QualiasolutionsCY/qualia-framework.git"
|
|
23
23
|
},
|
|
24
|
-
"homepage": "https://github.com/
|
|
24
|
+
"homepage": "https://github.com/QualiasolutionsCY/qualia-framework#readme",
|
|
25
25
|
"scripts": {
|
|
26
|
-
"test": "
|
|
26
|
+
"test": "bash tests/hooks.test.sh && bash tests/state.test.sh && bash tests/bin.test.sh && bash tests/statusline.test.sh"
|
|
27
27
|
},
|
|
28
28
|
"files": [
|
|
29
29
|
"bin/",
|
|
@@ -33,7 +33,6 @@
|
|
|
33
33
|
"skills/",
|
|
34
34
|
"templates/",
|
|
35
35
|
"tests/",
|
|
36
|
-
"docs/",
|
|
37
36
|
"CLAUDE.md",
|
|
38
37
|
"guide.md"
|
|
39
38
|
],
|
package/skills/qualia/SKILL.md
CHANGED
|
@@ -47,7 +47,6 @@ Use the state.js JSON output plus gathered context:
|
|
|
47
47
|
| `handed-off` | status == "handed_off" | → `/qualia-report` then done |
|
|
48
48
|
| `blocked` | STATE.md lists blockers or same error 3+ times | → Analyze, suggest `/qualia-debug` |
|
|
49
49
|
| `bug-loop` | Same files edited 3+ times, user frustrated | → Different approach, `/qualia-debug` |
|
|
50
|
-
| `need-tests` | User mentions "tests", "coverage", "test this" | → `/qualia-test` |
|
|
51
50
|
|
|
52
51
|
**Employee escalation:** If role is EMPLOYEE and situation is `gap-limit` or `bug-loop`, suggest: "Want to flag this for Fawzi?"
|
|
53
52
|
|
|
@@ -21,24 +21,6 @@ cat .planning/phase-{N}-plan.md
|
|
|
21
21
|
|
|
22
22
|
Parse: tasks, waves, file references.
|
|
23
23
|
|
|
24
|
-
### 1b. Create Recovery Point
|
|
25
|
-
|
|
26
|
-
Before executing any tasks, tag current HEAD for rollback:
|
|
27
|
-
|
|
28
|
-
```bash
|
|
29
|
-
git tag -f "pre-build-phase-{N}" HEAD 2>/dev/null
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
```bash
|
|
33
|
-
node ~/.claude/bin/qualia-ui.js info "Recovery point: pre-build-phase-{N}"
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
If a wave fails and the user needs to roll back:
|
|
37
|
-
```bash
|
|
38
|
-
git reset --hard pre-build-phase-{N}
|
|
39
|
-
node ~/.claude/bin/state.js transition --to planned --force
|
|
40
|
-
```
|
|
41
|
-
|
|
42
24
|
### 2. Execute Waves
|
|
43
25
|
|
|
44
26
|
```bash
|
|
@@ -25,7 +25,7 @@ node ~/.claude/bin/qualia-ui.js banner design
|
|
|
25
25
|
cat .planning/DESIGN.md 2>/dev/null || echo "NO_DESIGN"
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
-
If DESIGN.md exists → it
|
|
28
|
+
If DESIGN.md exists → use it. If not → use Qualia defaults: distinctive fonts, sharp accents, layered backgrounds, no card grids, no blue-purple gradients, full-width layouts.
|
|
29
29
|
|
|
30
30
|
### 2. Find Target Files
|
|
31
31
|
|
|
@@ -41,25 +41,19 @@ Evaluate each file on: AI slop detection, visual hierarchy, typography, color, s
|
|
|
41
41
|
|
|
42
42
|
### 4. Fix Everything
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
**Typography:** Replace generic fonts (Inter, Arial) with distinctive ones. Proper type scale, line-height 1.5-1.7 body.
|
|
45
45
|
|
|
46
|
-
**
|
|
46
|
+
**Color:** Cohesive palette from DESIGN.md or brand. Sharp accent for CTAs. WCAG AA contrast.
|
|
47
47
|
|
|
48
|
-
**
|
|
48
|
+
**Layout:** Full-width with fluid padding `clamp(1rem, 5vw, 4rem)`. NO hardcoded max-width caps. Prose gets `max-width: 65ch`.
|
|
49
49
|
|
|
50
|
-
**
|
|
50
|
+
**Spacing:** Consistent scale (8px grid). Generous whitespace between sections, tight within groups.
|
|
51
51
|
|
|
52
|
-
**
|
|
52
|
+
**Motion:** CSS transitions 200-300ms on hover/focus. Staggered entrance animations. `prefers-reduced-motion` respected.
|
|
53
53
|
|
|
54
|
-
**
|
|
54
|
+
**States:** Loading skeleton/spinner on async ops. Error states on data fetches. Empty states on lists. Hover/focus/active/disabled on interactive elements.
|
|
55
55
|
|
|
56
|
-
**
|
|
57
|
-
|
|
58
|
-
**States:** Loading skeleton/spinner on async ops. Error states on data fetches. Empty states on lists. Hover/focus/active/disabled on every interactive element.
|
|
59
|
-
|
|
60
|
-
**Responsive (§8):** Apply collapsing strategy from table. Mobile-first. Touch targets 44x44px min. No horizontal scroll.
|
|
61
|
-
|
|
62
|
-
**Anti-Slop (§12):** Run grep patterns from the detection table. Every match = mandatory fix.
|
|
56
|
+
**Responsive:** Mobile-first. Touch targets 44x44px min. Stack on mobile, expand on desktop. No horizontal scroll.
|
|
63
57
|
|
|
64
58
|
**Kill:** Card grids → varied layouts. Generic heroes → distinctive. Blue-purple gradients → brand colors. Static pages → purposeful motion. Fixed widths → fluid.
|
|
65
59
|
|
|
@@ -46,40 +46,17 @@ What did you learn?
|
|
|
46
46
|
3. Client preference — client-specific requirement
|
|
47
47
|
```
|
|
48
48
|
|
|
49
|
-
### 2.
|
|
50
|
-
|
|
51
|
-
Before saving, check if a similar entry already exists:
|
|
52
|
-
|
|
53
|
-
```bash
|
|
54
|
-
# Search for the title (case-insensitive substring match)
|
|
55
|
-
grep -i "{title keywords}" ~/.claude/knowledge/{type}.md 2>/dev/null
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
If a near-match exists (title is similar to an existing entry):
|
|
59
|
-
- Show the existing entry to the user
|
|
60
|
-
- Ask: "A similar entry exists. Update it, create a new one, or skip?"
|
|
61
|
-
- If update: replace the existing entry. If new: append. If skip: done.
|
|
62
|
-
|
|
63
|
-
### 3. Format Entry
|
|
64
|
-
|
|
65
|
-
Each entry gets a unique ID and ISO timestamp for dedup and ordering:
|
|
49
|
+
### 2. Format Entry
|
|
66
50
|
|
|
67
51
|
```markdown
|
|
68
|
-
|
|
69
|
-
---
|
|
70
|
-
|
|
71
|
-
### {Title}
|
|
72
|
-
**ID:** {random 8-char hex, e.g. a3f7c1e9}
|
|
73
|
-
**Date:** {ISO 8601, e.g. 2026-04-11}
|
|
52
|
+
### {Title} ({date})
|
|
74
53
|
**Project:** {current project name or "general"}
|
|
75
54
|
**Context:** {brief context — what you were building when you learned this}
|
|
76
55
|
|
|
77
56
|
{The learning — be specific enough that future-you understands without context}
|
|
78
57
|
```
|
|
79
58
|
|
|
80
|
-
###
|
|
81
|
-
|
|
82
|
-
Append-only — never overwrite the file, always add at the end:
|
|
59
|
+
### 3. Append to Knowledge File
|
|
83
60
|
|
|
84
61
|
```bash
|
|
85
62
|
# Append to the right file
|
|
@@ -90,7 +67,7 @@ echo "{formatted entry}" >> ~/.claude/knowledge/{type}.md
|
|
|
90
67
|
- Fix → `~/.claude/knowledge/common-fixes.md`
|
|
91
68
|
- Client pref → `~/.claude/knowledge/client-prefs.md`
|
|
92
69
|
|
|
93
|
-
###
|
|
70
|
+
### 4. Confirm
|
|
94
71
|
|
|
95
72
|
```
|
|
96
73
|
⬢ Saved to {file}
|