qualia-framework 6.7.0 → 6.8.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/CHANGELOG.md +2304 -0
- package/FLAGS.md +73 -0
- package/SOUL.md +17 -0
- package/TROUBLESHOOTING.md +183 -0
- package/agents/plan-checker.md +4 -0
- package/agents/planner.md +8 -0
- package/agents/qa-browser.md +6 -2
- package/agents/research-synthesizer.md +4 -0
- package/agents/researcher.md +4 -0
- package/agents/roadmapper.md +4 -0
- package/agents/verifier.md +1 -1
- package/agents/visual-evaluator.md +8 -6
- package/bin/cli.js +7 -1
- package/bin/install.js +69 -7
- package/bin/runtime-manifest.js +1 -0
- package/bin/security-scan.js +24 -10
- package/bin/trust-score.js +34 -0
- package/docs/onboarding.html +1 -1
- package/hooks/migration-guard.js +4 -4
- package/hooks/pre-deploy-gate.js +14 -4
- package/hooks/stop-session-log.js +10 -7
- package/package.json +6 -2
- package/qualia-design/design-rubric.md +3 -1
- package/rules/architecture.md +1 -1
- package/rules/grounding.md +3 -1
- package/rules/speed.md +2 -2
- package/skills/qualia-idk/SKILL.md +3 -3
- package/skills/qualia-polish/REFERENCE.md +11 -6
- package/skills/qualia-polish/SKILL.md +20 -3
- package/skills/qualia-polish/scripts/loop.mjs +24 -6
- package/skills/qualia-polish/scripts/playwright-capture.mjs +89 -11
- package/skills/qualia-polish/scripts/vibe-tokens.mjs +57 -1
- package/skills/qualia-research/SKILL.md +1 -1
- package/skills/qualia-road/SKILL.md +6 -0
- package/skills/qualia-scope/SKILL.md +2 -2
- package/skills/qualia-secure/SKILL.md +5 -5
- package/templates/help.html +1 -1
- package/templates/knowledge/index.md +4 -4
- package/tests/bin.test.sh +4 -4
- package/tests/lib.test.sh +2 -2
|
@@ -26,7 +26,7 @@ import { homedir } from "node:os";
|
|
|
26
26
|
|
|
27
27
|
// ── Arg parsing ──────────────────────────────────────────────────────────
|
|
28
28
|
function parseArgs() {
|
|
29
|
-
const args = { url: null, out: null, viewports: [375, 768, 1440], wait: 1500, reducedMotion: false };
|
|
29
|
+
const args = { url: null, out: null, viewports: [375, 768, 1440], wait: 1500, reducedMotion: false, states: false, stateSelector: null };
|
|
30
30
|
for (let i = 2; i < argv.length; i++) {
|
|
31
31
|
const a = argv[i];
|
|
32
32
|
if (a === "--url" && argv[i + 1]) args.url = argv[++i];
|
|
@@ -35,15 +35,22 @@ function parseArgs() {
|
|
|
35
35
|
args.viewports = argv[++i].split(",").map((s) => parseInt(s, 10)).filter((n) => Number.isFinite(n) && n > 0);
|
|
36
36
|
} else if (a === "--wait" && argv[i + 1]) args.wait = parseInt(argv[++i], 10);
|
|
37
37
|
else if (a === "--reduced-motion") args.reducedMotion = true;
|
|
38
|
+
else if (a === "--states") args.states = true;
|
|
39
|
+
else if (a === "--state-selector" && argv[i + 1]) args.stateSelector = argv[++i];
|
|
38
40
|
else if (a === "--help" || a === "-h") {
|
|
39
41
|
console.log(`playwright-capture.mjs — Screenshot capture for /qualia-polish --loop
|
|
40
42
|
|
|
41
43
|
Usage:
|
|
42
|
-
node playwright-capture.mjs --url <url> --out <dir> [--viewports 375,768,1440] [--wait 1500] [--reduced-motion]
|
|
44
|
+
node playwright-capture.mjs --url <url> --out <dir> [--viewports 375,768,1440] [--wait 1500] [--reduced-motion] [--states]
|
|
43
45
|
|
|
44
46
|
Flags:
|
|
45
47
|
--reduced-motion Force prefers-reduced-motion: reduce in the captured page.
|
|
46
48
|
Use when the brief explicitly opts out of motion (a11y mode).
|
|
49
|
+
--states Also capture hover/focus interaction-state PNGs (Playwright
|
|
50
|
+
backend only — the Chrome-binary backend cannot drive
|
|
51
|
+
hover/focus from the CLI and silently skips them).
|
|
52
|
+
--state-selector CSS selector to target for --states (default: first
|
|
53
|
+
a, button, [role=button], or input on the page).
|
|
47
54
|
|
|
48
55
|
Backend selection (auto):
|
|
49
56
|
1. Playwright — import('playwright') if installed
|
|
@@ -64,11 +71,14 @@ function viewportName(width) {
|
|
|
64
71
|
if (width <= 900) return "tablet";
|
|
65
72
|
return "desktop";
|
|
66
73
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
74
|
+
// Initial render height for the Playwright context viewport. Capture is
|
|
75
|
+
// full-page (fullPage:true), so this is NOT a clamp on the captured PNG — it
|
|
76
|
+
// only sets the layout viewport before Playwright stitches the full scroll.
|
|
77
|
+
const INITIAL_RENDER_HEIGHT = 1080;
|
|
78
|
+
// Headless Chrome (binary backend) has no --full-page flag, so a tall window
|
|
79
|
+
// height approximates full-page capture. Replaces the old per-viewport clamp
|
|
80
|
+
// (812/1024/900) that cropped everything below the fold.
|
|
81
|
+
const FULLPAGE_WINDOW_HEIGHT = 8000;
|
|
72
82
|
|
|
73
83
|
// ── Backend: Playwright (preferred when available) ──────────────────────
|
|
74
84
|
async function captureViaPlaywright(args) {
|
|
@@ -84,7 +94,7 @@ async function captureViaPlaywright(args) {
|
|
|
84
94
|
browser = await chromium.launch({ headless: true });
|
|
85
95
|
for (const width of args.viewports) {
|
|
86
96
|
const name = viewportName(width);
|
|
87
|
-
const height =
|
|
97
|
+
const height = INITIAL_RENDER_HEIGHT;
|
|
88
98
|
const file = join(args.out, `${name}-${width}.png`);
|
|
89
99
|
try {
|
|
90
100
|
const ctxOpts = { viewport: { width, height }, deviceScaleFactor: 1 };
|
|
@@ -93,9 +103,14 @@ async function captureViaPlaywright(args) {
|
|
|
93
103
|
const page = await ctx.newPage();
|
|
94
104
|
await page.goto(args.url, { waitUntil: "networkidle", timeout: 30000 });
|
|
95
105
|
if (args.wait > 0) await page.waitForTimeout(args.wait);
|
|
96
|
-
await page.screenshot({ path: file, fullPage:
|
|
106
|
+
await page.screenshot({ path: file, fullPage: true });
|
|
107
|
+
const stateFiles = [];
|
|
108
|
+
if (args.states) {
|
|
109
|
+
const extra = await captureStates(page, args, name, width);
|
|
110
|
+
stateFiles.push(...extra);
|
|
111
|
+
}
|
|
97
112
|
await ctx.close();
|
|
98
|
-
results.push({ viewport: name, width, height, file, ok: true, backend: "playwright", reducedMotion: !!args.reducedMotion });
|
|
113
|
+
results.push({ viewport: name, width, height, file, ok: true, backend: "playwright", reducedMotion: !!args.reducedMotion, ...(stateFiles.length ? { state_captures: stateFiles } : {}) });
|
|
99
114
|
} catch (err) {
|
|
100
115
|
results.push({ viewport: name, width, height, file, ok: false, backend: "playwright", error: err.message });
|
|
101
116
|
}
|
|
@@ -106,6 +121,59 @@ async function captureViaPlaywright(args) {
|
|
|
106
121
|
return results;
|
|
107
122
|
}
|
|
108
123
|
|
|
124
|
+
// ── Optional interaction-state capture (--states, Playwright only) ───────
|
|
125
|
+
// Captures hover/focus/loading variant PNGs of the first interactive element
|
|
126
|
+
// (or --state-selector) so the evaluator can score Motion/States honestly
|
|
127
|
+
// instead of guessing from a single static above-fold frame. Best-effort: any
|
|
128
|
+
// individual state that can't be captured is recorded as ok:false, never throws.
|
|
129
|
+
async function captureStates(page, args, name, width) {
|
|
130
|
+
const out = [];
|
|
131
|
+
const selector = args.stateSelector
|
|
132
|
+
|| "a:visible, button:visible, [role=button]:visible, input:visible";
|
|
133
|
+
let target;
|
|
134
|
+
try {
|
|
135
|
+
target = page.locator(selector).first();
|
|
136
|
+
await target.waitFor({ state: "visible", timeout: 2000 });
|
|
137
|
+
} catch {
|
|
138
|
+
out.push({ state: "hover", viewport: name, width, ok: false, error: "no interactive target found" });
|
|
139
|
+
return out;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// hover
|
|
143
|
+
const hoverFile = join(args.out, `${name}-${width}-hover.png`);
|
|
144
|
+
try {
|
|
145
|
+
await target.hover({ timeout: 2000 });
|
|
146
|
+
await page.waitForTimeout(Math.min(args.wait, 400));
|
|
147
|
+
await target.screenshot({ path: hoverFile });
|
|
148
|
+
out.push({ state: "hover", viewport: name, width, file: hoverFile, ok: true });
|
|
149
|
+
} catch (e) {
|
|
150
|
+
out.push({ state: "hover", viewport: name, width, file: hoverFile, ok: false, error: e.message });
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// focus
|
|
154
|
+
const focusFile = join(args.out, `${name}-${width}-focus.png`);
|
|
155
|
+
try {
|
|
156
|
+
await target.focus({ timeout: 2000 });
|
|
157
|
+
await page.waitForTimeout(Math.min(args.wait, 400));
|
|
158
|
+
await target.screenshot({ path: focusFile });
|
|
159
|
+
out.push({ state: "focus", viewport: name, width, file: focusFile, ok: true });
|
|
160
|
+
} catch (e) {
|
|
161
|
+
out.push({ state: "focus", viewport: name, width, file: focusFile, ok: false, error: e.message });
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// loading — re-navigate and grab the earliest paint (commit) before networkidle
|
|
165
|
+
const loadingFile = join(args.out, `${name}-${width}-loading.png`);
|
|
166
|
+
try {
|
|
167
|
+
await page.goto(args.url, { waitUntil: "commit", timeout: 30000 });
|
|
168
|
+
await page.screenshot({ path: loadingFile });
|
|
169
|
+
out.push({ state: "loading", viewport: name, width, file: loadingFile, ok: true });
|
|
170
|
+
} catch (e) {
|
|
171
|
+
out.push({ state: "loading", viewport: name, width, file: loadingFile, ok: false, error: e.message });
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return out;
|
|
175
|
+
}
|
|
176
|
+
|
|
109
177
|
// ── Backend: Chrome/Chromium headless via spawn ─────────────────────────
|
|
110
178
|
function findChromeBinary() {
|
|
111
179
|
// 1. Playwright cached chromium (newest first)
|
|
@@ -136,7 +204,10 @@ function captureViaChromeBinary(args, binary) {
|
|
|
136
204
|
const results = [];
|
|
137
205
|
for (const width of args.viewports) {
|
|
138
206
|
const name = viewportName(width);
|
|
139
|
-
|
|
207
|
+
// Tall window so headless Chrome's --screenshot is not cropped to a fixed
|
|
208
|
+
// above-fold height. Classic headless has no --full-page flag, so a large
|
|
209
|
+
// height is the closest equivalent to full-page capture on this backend.
|
|
210
|
+
const height = FULLPAGE_WINDOW_HEIGHT;
|
|
140
211
|
const file = join(args.out, `${name}-${width}.png`);
|
|
141
212
|
const flags = [
|
|
142
213
|
"--headless=new",
|
|
@@ -144,6 +215,8 @@ function captureViaChromeBinary(args, binary) {
|
|
|
144
215
|
"--disable-gpu",
|
|
145
216
|
"--disable-dev-shm-usage",
|
|
146
217
|
"--hide-scrollbars",
|
|
218
|
+
// Constrain layout WIDTH; use a tall height (not the old per-viewport
|
|
219
|
+
// clamp) so the capture spans well below the fold.
|
|
147
220
|
`--window-size=${width},${height}`,
|
|
148
221
|
`--screenshot=${file}`,
|
|
149
222
|
`--virtual-time-budget=${Math.max(args.wait + 1000, 3000)}`,
|
|
@@ -189,6 +262,9 @@ async function main() {
|
|
|
189
262
|
}
|
|
190
263
|
|
|
191
264
|
const failed = captures.filter((c) => !c.ok).length;
|
|
265
|
+
// --states is only honored on the Playwright backend (the Chrome-binary
|
|
266
|
+
// backend cannot drive hover/focus from the CLI). Surface that it was skipped.
|
|
267
|
+
const statesSkipped = args.states && backendUsed === "chrome-binary";
|
|
192
268
|
console.log(JSON.stringify({
|
|
193
269
|
url: args.url,
|
|
194
270
|
output_dir: args.out,
|
|
@@ -196,6 +272,8 @@ async function main() {
|
|
|
196
272
|
captures,
|
|
197
273
|
total: captures.length,
|
|
198
274
|
failed,
|
|
275
|
+
...(args.states ? { states_requested: true } : {}),
|
|
276
|
+
...(statesSkipped ? { states_skipped: "chrome-binary backend cannot capture hover/focus/loading states" } : {}),
|
|
199
277
|
}, null, 2));
|
|
200
278
|
exit(failed > 0 ? 1 : 0);
|
|
201
279
|
}
|
|
@@ -19,8 +19,9 @@
|
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
21
|
import { readFileSync, writeFileSync, existsSync, readdirSync, statSync } from "node:fs";
|
|
22
|
-
import { join, basename, extname } from "node:path";
|
|
22
|
+
import path, { join, basename, extname } from "node:path";
|
|
23
23
|
import { argv, exit, cwd } from "node:process";
|
|
24
|
+
import { spawnSync } from "node:child_process";
|
|
24
25
|
|
|
25
26
|
function flag(name, fallback) {
|
|
26
27
|
const i = argv.indexOf(name);
|
|
@@ -41,6 +42,7 @@ if (!CMD || CMD === "--help" || CMD === "-h") {
|
|
|
41
42
|
Usage:
|
|
42
43
|
tokens.mjs sync --design <path> [--write]
|
|
43
44
|
tokens.mjs propose-variants --product <path> --design <path> --count <N>
|
|
45
|
+
tokens.mjs verify [--files a.css,b.tsx] # post-vibe gate: tsc --noEmit + slop-detect
|
|
44
46
|
|
|
45
47
|
See skills/qualia-polish/SKILL.md.
|
|
46
48
|
`);
|
|
@@ -331,11 +333,65 @@ function cmdProposeVariants() {
|
|
|
331
333
|
exit(0);
|
|
332
334
|
}
|
|
333
335
|
|
|
336
|
+
// ─── Post-vibe verify (script-side piece of W7.3) ─────────────────────
|
|
337
|
+
// Deterministic gate the orchestrator (SKILL.md Stage 6) calls AFTER a --vibe
|
|
338
|
+
// token swap, so a vibe pivot never ships unverified. This runs the two
|
|
339
|
+
// machine-checkable gates — `tsc --noEmit` and `slop-detect` on changed files.
|
|
340
|
+
// The screenshot + visual-evaluator pass remains orchestration (the parent
|
|
341
|
+
// session / another agent), not a script primitive.
|
|
342
|
+
function resolveSlopScript() {
|
|
343
|
+
const candidates = [
|
|
344
|
+
process.env.SLOP_DETECT_SCRIPT,
|
|
345
|
+
`${process.env.HOME}/.claude/bin/slop-detect.mjs`,
|
|
346
|
+
path.join(path.dirname(new URL(import.meta.url).pathname), "..", "..", "..", "bin", "slop-detect.mjs"),
|
|
347
|
+
].filter(Boolean);
|
|
348
|
+
return candidates.find((p) => existsSync(p)) || null;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function cmdVerify() {
|
|
352
|
+
const filesFlag = flag("--files", null);
|
|
353
|
+
const files = typeof filesFlag === "string"
|
|
354
|
+
? filesFlag.split(",").map((s) => s.trim()).filter(Boolean)
|
|
355
|
+
: [];
|
|
356
|
+
const gates = [];
|
|
357
|
+
|
|
358
|
+
// Gate 1 — tsc --noEmit (skipped if no tsconfig in cwd).
|
|
359
|
+
if (existsSync(join(cwd(), "tsconfig.json"))) {
|
|
360
|
+
const r = spawnSync("npx", ["tsc", "--noEmit"], { encoding: "utf8" });
|
|
361
|
+
gates.push({ gate: "tsc", ok: r.status === 0, output: (r.stderr || r.stdout || "").split("\n").slice(0, 20).join("\n") });
|
|
362
|
+
} else {
|
|
363
|
+
gates.push({ gate: "tsc", ok: true, skipped: "no tsconfig.json in cwd" });
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Gate 2 — slop-detect on the changed files (resolved like commit-fix does).
|
|
367
|
+
const slopScript = resolveSlopScript();
|
|
368
|
+
if (!slopScript) {
|
|
369
|
+
gates.push({ gate: "slop-detect", ok: true, skipped: "slop-detect.mjs not found" });
|
|
370
|
+
} else if (files.length === 0) {
|
|
371
|
+
gates.push({ gate: "slop-detect", ok: true, skipped: "no --files provided" });
|
|
372
|
+
} else {
|
|
373
|
+
const slopBin = process.env.SLOP_DETECT_BIN || "node";
|
|
374
|
+
const r = spawnSync(slopBin, [slopScript, ...files], { encoding: "utf8" });
|
|
375
|
+
gates.push({ gate: "slop-detect", ok: r.status !== 1, output: (r.stdout || "").split("\n").slice(0, 20).join("\n") });
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
const allPass = gates.every((g) => g.ok);
|
|
379
|
+
console.log(JSON.stringify({
|
|
380
|
+
command: "verify",
|
|
381
|
+
files,
|
|
382
|
+
gates,
|
|
383
|
+
pass: allPass,
|
|
384
|
+
note: "screenshot + visual-evaluator pass is orchestrated by SKILL.md Stage 6, not by this script",
|
|
385
|
+
}, null, 2));
|
|
386
|
+
exit(allPass ? 0 : 1);
|
|
387
|
+
}
|
|
388
|
+
|
|
334
389
|
// ─── Dispatch ────────────────────────────────────────────────────────
|
|
335
390
|
|
|
336
391
|
switch (CMD) {
|
|
337
392
|
case "sync": cmdSync(); break;
|
|
338
393
|
case "propose-variants": cmdProposeVariants(); break;
|
|
394
|
+
case "verify": cmdVerify(); break;
|
|
339
395
|
default:
|
|
340
396
|
console.error(`Unknown command: ${CMD}`);
|
|
341
397
|
exit(2);
|
|
@@ -124,5 +124,5 @@ node ${QUALIA_BIN}/qualia-ui.js end "PHASE {N} RESEARCH DONE" "/qualia-plan {N}"
|
|
|
124
124
|
1. **One session per run.** Don't research phases 1-5 in one call.
|
|
125
125
|
2. **Must produce a file.** Research in conversation only is worthless.
|
|
126
126
|
3. **Honor locked decisions.** Don't research alternatives to locked choices.
|
|
127
|
-
4. **Local-first.** Drain NotebookLM and
|
|
127
|
+
4. **Local-first.** Drain NotebookLM and the local memory (`projects/-home-qualia/memory/MEMORY.md`) before any external call. The team has already researched most domains we touch — querying existing notebooks is near-zero token cost AND higher-quality than fresh WebSearch.
|
|
128
128
|
5. **Context7 before WebFetch.** When you do go external, Context7 first for libraries; only WebFetch for non-library content (blog posts, case studies, post-mortems).
|
|
@@ -86,13 +86,19 @@ Before high-stakes phases, run alignment skills against `.planning/CONTEXT.md` (
|
|
|
86
86
|
## Auxiliary commands
|
|
87
87
|
```
|
|
88
88
|
Lost? → /qualia (state router — tells you the next command)
|
|
89
|
+
Don't-know? → /qualia-idk (deep diagnostic — three isolated scans + paste-ready command sequence)
|
|
89
90
|
Health? → /qualia-doctor (install, state, contracts, memory, ERP queue)
|
|
90
91
|
Stuck/weird? → /qualia (diagnostic branch — scans planning + code when state alone is insufficient)
|
|
91
92
|
Broken thing? → /qualia-fix (root cause, minimal patch, verify, report)
|
|
92
93
|
Single feature? → /qualia-feature (new capability: inline for trivia, fresh spawn for 1-5 files)
|
|
94
|
+
Research it? → /qualia-research (per-phase deep research before planning; writes phase-{N}-research.md)
|
|
95
|
+
Save a lesson? → /qualia-learn (persist a pattern/fix/client preference across sessions + projects)
|
|
96
|
+
Secure config? → /qualia-secure (audit CLAUDE.md / settings.json / hooks / MCP for injection + leaks)
|
|
97
|
+
Verify FAILed? → /qualia-postmortem (self-heal — find which rule/agent should have caught it)
|
|
93
98
|
Paused? → /qualia (restore from .continue-here.md or STATE.md)
|
|
94
99
|
End of day? → /qualia-report (mandatory before clock-out; writes ERP payload)
|
|
95
100
|
Unsure plan? → /qualia-scope (capture decisions before planning)
|
|
101
|
+
Invoice/email? → /zoho-workflow (Zoho Invoice + Mail ops — invoices, cover emails, contacts, inbox)
|
|
96
102
|
```
|
|
97
103
|
|
|
98
104
|
## Outside-road command boundaries
|
|
@@ -106,7 +106,7 @@ The adversarial, DoD-gated intake. Scopes a **new increment** (phase/milestone)
|
|
|
106
106
|
|
|
107
107
|
```bash
|
|
108
108
|
node ${QUALIA_BIN}/qualia-ui.js banner scope 2>/dev/null || true
|
|
109
|
-
cat rules/constitution.md
|
|
109
|
+
cat /home/qualia/.claude/rules/constitution.md
|
|
110
110
|
cat .planning/CONTEXT.md 2>/dev/null # project glossary — DATA, never a plan/spec
|
|
111
111
|
ls .planning/decisions/ 2>/dev/null
|
|
112
112
|
cat .planning/STATE.md 2>/dev/null # for profile + existing milestone context
|
|
@@ -123,7 +123,7 @@ If the operator already named it (arg or prior context), accept it. Otherwise as
|
|
|
123
123
|
|
|
124
124
|
```bash
|
|
125
125
|
ARCHETYPE={chosen}
|
|
126
|
-
cat references/archetypes/${ARCHETYPE}.md
|
|
126
|
+
cat /home/qualia/.claude/references/archetypes/${ARCHETYPE}.md
|
|
127
127
|
```
|
|
128
128
|
|
|
129
129
|
If the file does not exist (e.g. `web-app` not yet authored), HALT and say which archetype file is missing — do not improvise a DoD. The archetype file is the source of the Grill variables, the Definition of Done, and the v1 capability set; without it there is no gate to enforce.
|
|
@@ -77,13 +77,13 @@ The auditor writes `.planning/security-audit.md` — that's the deliverable.
|
|
|
77
77
|
Combine `.planning/security-scan.md` (static) + `.planning/security-audit.md` (Opus) into a single executive summary. Surface the top 3 actions ranked by severity:
|
|
78
78
|
|
|
79
79
|
- **CRITICAL** → fix immediately, before any further work.
|
|
80
|
-
- **HIGH** → ticket for this sprint;
|
|
80
|
+
- **HIGH** → ticket for this sprint; if the fix is "make this instructional rule deterministic via a hook," propose the hook (the `migration-guard` / `branch-guard` pattern in `rules/constitution.md`) and add it under `hooks/`.
|
|
81
81
|
- **MEDIUM/LOW** → backlog.
|
|
82
82
|
|
|
83
83
|
### Step 4. Close
|
|
84
84
|
|
|
85
85
|
```bash
|
|
86
|
-
node ${QUALIA_BIN}/qualia-ui.js end "SECURED" "/qualia-
|
|
86
|
+
node ${QUALIA_BIN}/qualia-ui.js end "SECURED" "/qualia-fix"
|
|
87
87
|
```
|
|
88
88
|
|
|
89
89
|
(Or omit the next-command if all findings are LOW.)
|
|
@@ -93,13 +93,13 @@ node ${QUALIA_BIN}/qualia-ui.js end "SECURED" "/qualia-hook-gen"
|
|
|
93
93
|
1. **Static pass is non-negotiable.** It's fast and deterministic — always runs.
|
|
94
94
|
2. **Opus pass is opt-in.** It costs tokens and time. Default to skipping unless the user explicitly asks for "deep audit" or the static pass triggers HIGH+ findings.
|
|
95
95
|
3. **No fake severity.** Per `rules/grounding.md`, every finding cites `file:line` and matches a category in the Severity Rubric. No hedging.
|
|
96
|
-
4. **Recommend deterministic fixes when possible.** A rule in CLAUDE.md is suggestive; a hook is enforced. The skill's bias is toward
|
|
96
|
+
4. **Recommend deterministic fixes when possible.** A rule in CLAUDE.md is suggestive; a hook is enforced. The skill's bias is toward proposing a deterministic hook (under `hooks/`) over "tell the agent to do X."
|
|
97
97
|
5. **Never auto-rotate secrets.** Flag and instruct. The user rotates manually with confirmation — secrets in CI variables are the user's domain.
|
|
98
98
|
|
|
99
99
|
## When NOT to use
|
|
100
100
|
|
|
101
101
|
- Application-level security review (use `/security-review` for OWASP-style code audit).
|
|
102
|
-
- Production deployment health (use `/qualia-doctor`
|
|
103
|
-
- Specific bug investigation (use `/qualia-
|
|
102
|
+
- Production deployment health (use `/qualia-doctor`).
|
|
103
|
+
- Specific bug investigation (use `/qualia-fix`).
|
|
104
104
|
|
|
105
105
|
`/qualia-secure` is specifically for **the agent's configuration**. The hooks, the rules, the tool scopes, the MCP servers — the surfaces Claude reads to decide what to do.
|
package/templates/help.html
CHANGED
|
@@ -350,7 +350,7 @@
|
|
|
350
350
|
<h3>Phase Depth — Optional</h3>
|
|
351
351
|
<p class="cmd-group-note">Used before / around /qualia-plan when a phase needs deeper context.</p>
|
|
352
352
|
<div class="commands">
|
|
353
|
-
<div class="cmd"><span class="cmd-name">/qualia-
|
|
353
|
+
<div class="cmd"><span class="cmd-name">/qualia-scope</span><span class="cmd-desc">Unified intake. No args: non-technical kickoff discovery before /qualia-new. With N: archetype-aware adversarial grill before /qualia-plan, gated on Definition-of-Done. Use for complex phases with regulatory, compliance, or architectural stakes. Creates .planning/phase-{N}-context.md that the planner honors as locked input.</span></div>
|
|
354
354
|
<div class="cmd"><span class="cmd-name">/qualia-research</span><span class="cmd-desc">Deep-research a niche domain or library BEFORE planning a specific phase. Spawns the researcher agent with Context7/WebFetch access. Writes to .planning/phase-{N}-research.md.</span></div>
|
|
355
355
|
<div class="cmd"><span class="cmd-name">/qualia-map</span><span class="cmd-desc">Map an existing codebase to infer architecture, stack, conventions, and what's already built. For brownfield projects — run BEFORE /qualia-new so Validated requirements get inferred from existing code.</span></div>
|
|
356
356
|
<div class="cmd"><span class="cmd-name">/qualia-milestone</span><span class="cmd-desc">Close out a completed milestone and prep the next one. Archives the current milestone's artifacts, updates REQUIREMENTS.md to mark v1 requirements Complete, and creates the next milestone roadmap.</span></div>
|
|
@@ -12,10 +12,10 @@ file first**, then jump to the specific file(s) that match.
|
|
|
12
12
|
|--------------------------|-------|
|
|
13
13
|
| "How do we usually X?" / patterns we've used before | `learned-patterns.md` |
|
|
14
14
|
| Recurring bug + fix recipes | `common-fixes.md` |
|
|
15
|
-
| Supabase auth, RLS, migrations, edge functions | `supabase-patterns.md` |
|
|
16
|
-
| Retell, ElevenLabs, voice agent flows | `voice-agent-patterns.md` |
|
|
17
|
-
| Where a project is deployed, env vars, domains | `deployment-map.md` |
|
|
18
|
-
| Who is on the team, their role, their access | `employees.md` |
|
|
15
|
+
| Supabase auth, RLS, migrations, edge functions | `supabase-patterns.md` *(created on first /qualia-learn)* |
|
|
16
|
+
| Retell, ElevenLabs, voice agent flows | `voice-agent-patterns.md` *(created on first /qualia-learn)* |
|
|
17
|
+
| Where a project is deployed, env vars, domains | `deployment-map.md` *(created on first /qualia-learn)* |
|
|
18
|
+
| Who is on the team, their role, their access | `employees.md` *(created on first /qualia-learn)* |
|
|
19
19
|
| What I worked on yesterday / last week | `daily-log/YYYY-MM-DD.md` |
|
|
20
20
|
| Memory layer architecture itself | `agents.md` |
|
|
21
21
|
|
package/tests/bin.test.sh
CHANGED
|
@@ -733,11 +733,11 @@ else
|
|
|
733
733
|
fail_case "qualia-flush retirement/install state"
|
|
734
734
|
fi
|
|
735
735
|
|
|
736
|
-
# 62.
|
|
737
|
-
if grep -q '"
|
|
738
|
-
pass "settings.env
|
|
736
|
+
# 62. CLAUDE_CODE_FORK_SUBAGENT=1 in settings.json (official env var, Claude Code v2.1.117+)
|
|
737
|
+
if grep -q '"CLAUDE_CODE_FORK_SUBAGENT": "1"' "$TMP/.claude/settings.json"; then
|
|
738
|
+
pass "settings.env CLAUDE_CODE_FORK_SUBAGENT=1 (forked subagents on by default)"
|
|
739
739
|
else
|
|
740
|
-
fail_case "
|
|
740
|
+
fail_case "CLAUDE_CODE_FORK_SUBAGENT not set"
|
|
741
741
|
fi
|
|
742
742
|
|
|
743
743
|
# 63. research-synthesizer agent has model: haiku frontmatter
|
package/tests/lib.test.sh
CHANGED
|
@@ -507,7 +507,7 @@ TMP=$(mktmp)
|
|
|
507
507
|
mkdir -p "$TMP/home/.claude/bin" "$TMP/home/.claude/hooks" "$TMP/home/.claude/knowledge/daily-log" "$TMP/home/.claude/qualia-design" "$TMP/home/.claude/agents" "$TMP/home/.claude/qualia-templates" "$TMP/project"
|
|
508
508
|
echo '{"installed_by":"Test","role":"OWNER","version":"6.3.0","erp":{"enabled":false}}' > "$TMP/home/.claude/.qualia-config.json"
|
|
509
509
|
touch "$TMP/home/.claude/CLAUDE.md" "$TMP/home/.claude/settings.json"
|
|
510
|
-
for f in runtime-manifest.js command-surface.js host-adapters.js state.js qualia-ui.js statusline.js knowledge.js knowledge-flush.js state-ledger.js plan-contract.js contract-runner.js harness-eval.js trust-score.js agent-runs.js slop-detect.mjs erp-retry.js work-packet.js report-payload.js project-snapshot.js codex-goal.js planning-hygiene.js prune-deprecated.js learning-candidates.js status-snapshot.js security-scan.js; do
|
|
510
|
+
for f in runtime-manifest.js command-surface.js host-adapters.js state.js qualia-ui.js statusline.js knowledge.js knowledge-flush.js state-ledger.js plan-contract.js contract-runner.js harness-eval.js trust-score.js agent-runs.js slop-detect.mjs erp-retry.js work-packet.js report-payload.js project-snapshot.js codex-goal.js planning-hygiene.js prune-deprecated.js learning-candidates.js status-snapshot.js security-scan.js auto-report.js; do
|
|
511
511
|
touch "$TMP/home/.claude/bin/$f"
|
|
512
512
|
done
|
|
513
513
|
for h in session-start.js auto-update.js branch-guard.js pre-push.js pre-deploy-gate.js migration-guard.js git-guardrails.js stop-session-log.js fawzi-approval-guard.js vercel-account-guard.js env-empty-guard.js supabase-destructive-guard.js; do
|
|
@@ -622,7 +622,7 @@ TMP=$(mktmp)
|
|
|
622
622
|
mkdir -p "$TMP/.claude/bin" "$TMP/.claude/hooks" "$TMP/.claude/knowledge/daily-log" "$TMP/.claude/qualia-design" "$TMP/.claude/agents" "$TMP/.claude/qualia-templates" "$TMP/project/.planning"
|
|
623
623
|
echo '{"installed_by":"Test","role":"OWNER","erp":{"enabled":false}}' > "$TMP/.claude/.qualia-config.json"
|
|
624
624
|
touch "$TMP/.claude/CLAUDE.md" "$TMP/.claude/settings.json"
|
|
625
|
-
for f in runtime-manifest.js command-surface.js host-adapters.js state.js qualia-ui.js statusline.js knowledge.js knowledge-flush.js state-ledger.js plan-contract.js contract-runner.js harness-eval.js trust-score.js agent-runs.js slop-detect.mjs erp-retry.js work-packet.js report-payload.js project-snapshot.js codex-goal.js planning-hygiene.js prune-deprecated.js learning-candidates.js status-snapshot.js security-scan.js; do
|
|
625
|
+
for f in runtime-manifest.js command-surface.js host-adapters.js state.js qualia-ui.js statusline.js knowledge.js knowledge-flush.js state-ledger.js plan-contract.js contract-runner.js harness-eval.js trust-score.js agent-runs.js slop-detect.mjs erp-retry.js work-packet.js report-payload.js project-snapshot.js codex-goal.js planning-hygiene.js prune-deprecated.js learning-candidates.js status-snapshot.js security-scan.js auto-report.js; do
|
|
626
626
|
touch "$TMP/.claude/bin/$f"
|
|
627
627
|
done
|
|
628
628
|
for h in session-start.js auto-update.js branch-guard.js pre-push.js pre-deploy-gate.js migration-guard.js git-guardrails.js stop-session-log.js fawzi-approval-guard.js vercel-account-guard.js env-empty-guard.js supabase-destructive-guard.js; do
|