qualia-framework 6.22.0 → 7.0.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 +186 -0
- package/bin/batch-plan.js +111 -0
- package/bin/command-surface.js +1 -0
- package/bin/design-tokens.js +131 -0
- package/bin/erp-event.js +177 -0
- package/bin/erp-retry.js +12 -1
- package/bin/host-adapters.js +13 -1
- package/bin/install.js +23 -0
- package/bin/knowledge-flush.js +6 -3
- package/bin/recall.js +172 -0
- package/bin/repo-map.js +188 -0
- package/bin/runtime-manifest.js +6 -0
- package/bin/vault-access.js +82 -0
- package/docs/erp-contract.md +35 -0
- package/mcp/memory-mcp/server.js +257 -0
- package/package.json +4 -2
- package/qualia-design/design-dials.md +72 -0
- package/qualia-design/design-reference.md +24 -0
- package/rules/access.md +42 -0
- package/skills/qualia-build/SKILL.md +31 -0
- package/skills/qualia-map/SKILL.md +15 -0
- package/skills/qualia-new/SKILL.md +14 -0
- package/skills/qualia-polish/SKILL.md +3 -2
- package/skills/qualia-recall/SKILL.md +76 -0
- package/skills/qualia-verify/SKILL.md +8 -0
- package/templates/DESIGN.md +15 -0
- package/tests/batch-plan.test.sh +56 -0
- package/tests/design-tokens.test.sh +53 -0
- package/tests/erp-event.test.sh +78 -0
- package/tests/lib.test.sh +29 -4
- package/tests/recall.test.sh +91 -0
- package/tests/repo-map.test.sh +70 -0
- package/tests/run-all.sh +5 -0
- package/tests/runner.js +363 -33
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# batch-plan.test.sh — bin/batch-plan.js (/qualia-build --batch split, R20)
|
|
3
|
+
# Run: bash tests/batch-plan.test.sh
|
|
4
|
+
|
|
5
|
+
PASS=0
|
|
6
|
+
FAIL=0
|
|
7
|
+
BIN_DIR="$(cd "$(dirname "$0")/../bin" && pwd)"
|
|
8
|
+
NODE="${NODE:-node}"
|
|
9
|
+
BP="$BIN_DIR/batch-plan.js"
|
|
10
|
+
|
|
11
|
+
assert_exit() { if [ "$2" = "$3" ]; then echo " ✓ $1"; PASS=$((PASS+1)); else echo " ✗ $1 (expected exit $2, got $3)"; FAIL=$((FAIL+1)); fi; }
|
|
12
|
+
assert_contains() { if echo "$2" | grep -qF -- "$3"; then echo " ✓ $1"; PASS=$((PASS+1)); else echo " ✗ $1 (missing '$3')"; FAIL=$((FAIL+1)); fi; }
|
|
13
|
+
jcheck() { echo "$1" | $NODE -e "let s='';process.stdin.on('data',d=>s+=d).on('end',()=>{const p=JSON.parse(s);process.exit(($2)?0:1)})"; }
|
|
14
|
+
|
|
15
|
+
echo "batch-plan.test.sh — bin/batch-plan.js"
|
|
16
|
+
echo ""
|
|
17
|
+
|
|
18
|
+
$NODE -c "$BP" 2>/dev/null && { echo " ✓ syntax valid"; PASS=$((PASS+1)); } || { echo " ✗ syntax invalid"; FAIL=$((FAIL+1)); }
|
|
19
|
+
|
|
20
|
+
FILES=$(seq -f "src/f%g.ts" 1 23)
|
|
21
|
+
|
|
22
|
+
# ── split math ──
|
|
23
|
+
JSON=$($NODE "$BP" $FILES --batch-size 10 --max-workers 3 --json 2>&1); assert_exit "valid run → exit 0" 0 $?
|
|
24
|
+
jcheck "$JSON" "p.total_files===23" && { echo " ✓ counts all files"; PASS=$((PASS+1)); } || { echo " ✗ total_files"; FAIL=$((FAIL+1)); }
|
|
25
|
+
jcheck "$JSON" "p.batch_count===3" && { echo " ✓ ceil(23/10)=3 batches"; PASS=$((PASS+1)); } || { echo " ✗ batch_count"; FAIL=$((FAIL+1)); }
|
|
26
|
+
jcheck "$JSON" "p.batches.every(b=>b.files.length<=10)" && { echo " ✓ every batch ≤ batch-size"; PASS=$((PASS+1)); } || { echo " ✗ batch size cap"; FAIL=$((FAIL+1)); }
|
|
27
|
+
|
|
28
|
+
# ── disjoint + complete: every file in exactly one batch ──
|
|
29
|
+
jcheck "$JSON" "(()=>{const a=p.batches.flatMap(b=>b.files);return a.length===23&&new Set(a).size===23})()" \
|
|
30
|
+
&& { echo " ✓ file-disjoint + complete (each file exactly once)"; PASS=$((PASS+1)); } || { echo " ✗ disjointness"; FAIL=$((FAIL+1)); }
|
|
31
|
+
|
|
32
|
+
# ── waves cap at max-workers ──
|
|
33
|
+
jcheck "$JSON" "p.waves.every(w=>w.length<=3)" && { echo " ✓ waves capped at max-workers"; PASS=$((PASS+1)); } || { echo " ✗ wave cap"; FAIL=$((FAIL+1)); }
|
|
34
|
+
jcheck "$JSON" "typeof p.staging_branch==='string'&&Array.isArray(p.waves)&&Array.isArray(p.batches)" \
|
|
35
|
+
&& { echo " ✓ --json shape (staging_branch/waves/batches)"; PASS=$((PASS+1)); } || { echo " ✗ json shape"; FAIL=$((FAIL+1)); }
|
|
36
|
+
|
|
37
|
+
# ── dedup ──
|
|
38
|
+
jcheck "$($NODE "$BP" a a b --json 2>&1)" "p.total_files===2" && { echo " ✓ de-dups repeated files"; PASS=$((PASS+1)); } || { echo " ✗ dedup"; FAIL=$((FAIL+1)); }
|
|
39
|
+
|
|
40
|
+
# ── --from FILE + stdin ──
|
|
41
|
+
TMP=$(mktemp -d); printf 'x.ts\ny.ts\nz.ts\n' > "$TMP/list.txt"
|
|
42
|
+
jcheck "$($NODE "$BP" --from "$TMP/list.txt" --json 2>&1)" "p.total_files===3" && { echo " ✓ reads --from FILE"; PASS=$((PASS+1)); } || { echo " ✗ --from file"; FAIL=$((FAIL+1)); }
|
|
43
|
+
jcheck "$(printf 'a\nb\n' | $NODE "$BP" --from - --json 2>&1)" "p.total_files===2" && { echo " ✓ reads --from - (stdin)"; PASS=$((PASS+1)); } || { echo " ✗ --from stdin"; FAIL=$((FAIL+1)); }
|
|
44
|
+
|
|
45
|
+
# ── human output mentions staging branch ──
|
|
46
|
+
assert_contains "human output names staging branch" "$($NODE "$BP" a b --staging mig/x 2>&1)" "mig/x"
|
|
47
|
+
|
|
48
|
+
# ── edge cases ──
|
|
49
|
+
$NODE "$BP" --from /no/such/list.txt >/dev/null 2>&1; assert_exit "missing --from file → exit 2" 2 $?
|
|
50
|
+
EOUT=$($NODE "$BP" 2>&1); assert_exit "no files → exit 0" 0 $?
|
|
51
|
+
assert_contains "no files → friendly message" "$EOUT" "nothing to migrate"
|
|
52
|
+
|
|
53
|
+
rm -rf "$TMP"
|
|
54
|
+
echo ""
|
|
55
|
+
echo "=== Results: $PASS passed, $FAIL failed ==="
|
|
56
|
+
[ "$FAIL" -eq 0 ]
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# design-tokens.test.sh — bin/design-tokens.js (per-client token registry, R10)
|
|
3
|
+
# Run: bash tests/design-tokens.test.sh
|
|
4
|
+
|
|
5
|
+
PASS=0
|
|
6
|
+
FAIL=0
|
|
7
|
+
BIN_DIR="$(cd "$(dirname "$0")/../bin" && pwd)"
|
|
8
|
+
NODE="${NODE:-node}"
|
|
9
|
+
DT="$BIN_DIR/design-tokens.js"
|
|
10
|
+
|
|
11
|
+
assert_exit() { if [ "$2" = "$3" ]; then echo " ✓ $1"; PASS=$((PASS+1)); else echo " ✗ $1 (expected exit $2, got $3)"; FAIL=$((FAIL+1)); fi; }
|
|
12
|
+
assert_contains() { if echo "$2" | grep -qF -- "$3"; then echo " ✓ $1"; PASS=$((PASS+1)); else echo " ✗ $1 (missing '$3')"; FAIL=$((FAIL+1)); fi; }
|
|
13
|
+
|
|
14
|
+
echo "design-tokens.test.sh — bin/design-tokens.js"
|
|
15
|
+
echo ""
|
|
16
|
+
|
|
17
|
+
$NODE -c "$DT" 2>/dev/null && { echo " ✓ syntax valid"; PASS=$((PASS+1)); } || { echo " ✗ syntax invalid"; FAIL=$((FAIL+1)); }
|
|
18
|
+
|
|
19
|
+
TMP=$(mktemp -d)
|
|
20
|
+
|
|
21
|
+
# ── init ──
|
|
22
|
+
$NODE "$DT" init --out "$TMP/tokens.json" >/dev/null 2>&1; assert_exit "init → exit 0" 0 $?
|
|
23
|
+
INITJSON=$(cat "$TMP/tokens.json")
|
|
24
|
+
echo "$INITJSON" | $NODE -e "let s='';process.stdin.on('data',d=>s+=d).on('end',()=>{const j=JSON.parse(s);process.exit(j.color&&j.font?0:1)})" \
|
|
25
|
+
&& { echo " ✓ init writes valid registry JSON (color+font)"; PASS=$((PASS+1)); } || { echo " ✗ init JSON shape"; FAIL=$((FAIL+1)); }
|
|
26
|
+
|
|
27
|
+
# ── compile → CSS ──
|
|
28
|
+
CSS=$($NODE "$DT" compile "$TMP/tokens.json" 2>&1); assert_exit "compile → exit 0" 0 $?
|
|
29
|
+
assert_contains "emits :root block" "$CSS" ":root {"
|
|
30
|
+
assert_contains "flattens nested color → var" "$CSS" "--color-accent:"
|
|
31
|
+
assert_contains "flattens nested font → var" "$CSS" "--font-sans:"
|
|
32
|
+
assert_contains "flattens nested radius → var" "$CSS" "--radius-md:"
|
|
33
|
+
assert_contains "carries the hardcoded-hex ban reminder" "$CSS" "ABS-HEX-IN-JSX"
|
|
34
|
+
|
|
35
|
+
# ── compile --json (flat map) ──
|
|
36
|
+
FLAT=$($NODE "$DT" compile "$TMP/tokens.json" --json 2>&1)
|
|
37
|
+
echo "$FLAT" | $NODE -e "let s='';process.stdin.on('data',d=>s+=d).on('end',()=>{const j=JSON.parse(s);process.exit(j['color-bg']?0:1)})" \
|
|
38
|
+
&& { echo " ✓ --json flat var map valid"; PASS=$((PASS+1)); } || { echo " ✗ --json flat map"; FAIL=$((FAIL+1)); }
|
|
39
|
+
|
|
40
|
+
# ── compile --out writes file ──
|
|
41
|
+
$NODE "$DT" compile "$TMP/tokens.json" --out "$TMP/tokens.css" >/dev/null 2>&1
|
|
42
|
+
[ -f "$TMP/tokens.css" ] && grep -q "^:root" "$TMP/tokens.css" && { echo " ✓ compile --out writes tokens.css"; PASS=$((PASS+1)); } || { echo " ✗ compile --out"; FAIL=$((FAIL+1)); }
|
|
43
|
+
|
|
44
|
+
# ── error paths ──
|
|
45
|
+
$NODE "$DT" compile /no/such/file.json >/dev/null 2>&1; assert_exit "missing file → exit 2" 2 $?
|
|
46
|
+
echo '{ broken' > "$TMP/bad.json"
|
|
47
|
+
$NODE "$DT" compile "$TMP/bad.json" >/dev/null 2>&1; assert_exit "invalid JSON → exit 2" 2 $?
|
|
48
|
+
$NODE "$DT" bogus-cmd >/dev/null 2>&1; assert_exit "unknown command → exit 2" 2 $?
|
|
49
|
+
|
|
50
|
+
rm -rf "$TMP"
|
|
51
|
+
echo ""
|
|
52
|
+
echo "=== Results: $PASS passed, $FAIL failed ==="
|
|
53
|
+
[ "$FAIL" -eq 0 ]
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# erp-event.test.sh — bin/erp-event.js (framework EMIT side of the R14 event log)
|
|
3
|
+
# Run: bash tests/erp-event.test.sh
|
|
4
|
+
|
|
5
|
+
PASS=0
|
|
6
|
+
FAIL=0
|
|
7
|
+
BIN_DIR="$(cd "$(dirname "$0")/../bin" && pwd)"
|
|
8
|
+
NODE="${NODE:-node}"
|
|
9
|
+
EE="$BIN_DIR/erp-event.js"
|
|
10
|
+
|
|
11
|
+
assert_exit() { if [ "$2" = "$3" ]; then echo " ✓ $1"; PASS=$((PASS+1)); else echo " ✗ $1 (expected exit $2, got $3)"; FAIL=$((FAIL+1)); fi; }
|
|
12
|
+
assert_contains() { if echo "$2" | grep -qF -- "$3"; then echo " ✓ $1"; PASS=$((PASS+1)); else echo " ✗ $1 (missing '$3')"; FAIL=$((FAIL+1)); fi; }
|
|
13
|
+
assert_absent() { if echo "$2" | grep -qF -- "$3"; then echo " ✗ $1 (unexpected '$3')"; FAIL=$((FAIL+1)); else echo " ✓ $1"; PASS=$((PASS+1)); fi; }
|
|
14
|
+
|
|
15
|
+
echo "erp-event.test.sh — bin/erp-event.js"
|
|
16
|
+
echo ""
|
|
17
|
+
|
|
18
|
+
$NODE -c "$EE" 2>/dev/null && { echo " ✓ syntax valid"; PASS=$((PASS+1)); } || { echo " ✗ syntax invalid"; FAIL=$((FAIL+1)); }
|
|
19
|
+
|
|
20
|
+
# ── unit: build + sign (the cross-repo contract) ──
|
|
21
|
+
$NODE -e '
|
|
22
|
+
const { buildSignedEvent, signingContent, computeSignature } = require("'"$EE"'");
|
|
23
|
+
const cfg = { code: "QS-FAWZI-11", installed_by: "Fawzi", role: "OWNER" };
|
|
24
|
+
const b = buildSignedEvent("verify_pass", { id: "evt1", now: 1750000000, apiKey: "qlt_k", config: cfg, targets: [{type:"project",ref:"acme"}] });
|
|
25
|
+
const ok =
|
|
26
|
+
b.event.action === "verify_pass" &&
|
|
27
|
+
b.event.actor.code === "QS-FAWZI-11" && b.event.actor.role === "OWNER" &&
|
|
28
|
+
b.headers["Qualia-Event-Id"] === "evt1" &&
|
|
29
|
+
b.headers["Qualia-Event-Timestamp"] === "1750000000" &&
|
|
30
|
+
// signature must equal a fresh recompute over `${id}.${ts}.${payload}` (the ERP verify)
|
|
31
|
+
b.headers["Qualia-Signature"] === computeSignature(signingContent("evt1","1750000000",b.payload), "qlt_k") &&
|
|
32
|
+
// and must NOT match a wrong key
|
|
33
|
+
b.headers["Qualia-Signature"] !== computeSignature(signingContent("evt1","1750000000",b.payload), "qlt_other");
|
|
34
|
+
process.exit(ok ? 0 : 1);
|
|
35
|
+
' && { echo " ✓ builds envelope + signature byte-matches the ERP verify algorithm"; PASS=$((PASS+1)); } || { echo " ✗ build/sign contract"; FAIL=$((FAIL+1)); }
|
|
36
|
+
|
|
37
|
+
# unsigned when no key: no Qualia-Signature header, still a valid envelope
|
|
38
|
+
$NODE -e '
|
|
39
|
+
const { buildSignedEvent } = require("'"$EE"'");
|
|
40
|
+
const b = buildSignedEvent("session_started", { id:"e", now:1, apiKey:"", config:{} });
|
|
41
|
+
process.exit(b.headers["Qualia-Signature"] === undefined && b.headers["Qualia-Event-Id"] === "e" ? 0 : 1);
|
|
42
|
+
' && { echo " ✓ no API key → unsigned envelope (Bearer still authenticates server-side)"; PASS=$((PASS+1)); } || { echo " ✗ unsigned path"; FAIL=$((FAIL+1)); }
|
|
43
|
+
|
|
44
|
+
# ── CLI behavior with a temp install home ──
|
|
45
|
+
HOME_DIR=$(mktemp -d)
|
|
46
|
+
mkdir -p "$HOME_DIR/bin"
|
|
47
|
+
cp "$BIN_DIR/erp-retry.js" "$HOME_DIR/bin/erp-retry.js"
|
|
48
|
+
printf '{"role":"OWNER","code":"QS-FAWZI-11","installed_by":"Fawzi","erp":{"enabled":true,"url":"https://example.test"}}' > "$HOME_DIR/.qualia-config.json"
|
|
49
|
+
printf 'qlt_testkey' > "$HOME_DIR/.erp-api-key"
|
|
50
|
+
RUN(){ QUALIA_HOME="$HOME_DIR" $NODE "$EE" "$@"; }
|
|
51
|
+
|
|
52
|
+
# dry-run: does NOT enqueue
|
|
53
|
+
OUT=$(RUN emit verify_pass --target project:x --dry-run --json 2>&1); assert_exit "dry-run → exit 0" 0 $?
|
|
54
|
+
assert_contains "dry-run builds a signature" "$OUT" "Qualia-Signature"
|
|
55
|
+
[ ! -f "$HOME_DIR/.erp-retry-queue.json" ] && { echo " ✓ dry-run does not enqueue"; PASS=$((PASS+1)); } || { echo " ✗ dry-run enqueued"; FAIL=$((FAIL+1)); }
|
|
56
|
+
|
|
57
|
+
# real emit: enqueues an item carrying the signed headers + /events url
|
|
58
|
+
RUN emit build_wave_started --target project:x --project 7b5d3b4e-2b8a-4de4-91a1-9b2f3182f5ef >/dev/null 2>&1
|
|
59
|
+
Q=$(cat "$HOME_DIR/.erp-retry-queue.json" 2>/dev/null)
|
|
60
|
+
assert_contains "emit enqueues to /api/v1/events" "$Q" "/api/v1/events"
|
|
61
|
+
assert_contains "queued item carries the signature header" "$Q" "Qualia-Signature"
|
|
62
|
+
assert_contains "queued item carries the event action" "$Q" "build_wave_started"
|
|
63
|
+
|
|
64
|
+
# ERP disabled → skipped, no enqueue
|
|
65
|
+
HOME2=$(mktemp -d)
|
|
66
|
+
printf '{"role":"OWNER","erp":{"enabled":false}}' > "$HOME2/.qualia-config.json"
|
|
67
|
+
OUT=$(QUALIA_HOME="$HOME2" $NODE "$EE" emit verify_fail 2>&1); assert_exit "erp disabled → exit 0" 0 $?
|
|
68
|
+
assert_contains "erp disabled → skipped" "$OUT" "skipped"
|
|
69
|
+
[ ! -f "$HOME2/.erp-retry-queue.json" ] && { echo " ✓ erp disabled → no enqueue"; PASS=$((PASS+1)); } || { echo " ✗ enqueued while disabled"; FAIL=$((FAIL+1)); }
|
|
70
|
+
|
|
71
|
+
# bad invocation
|
|
72
|
+
$NODE "$EE" >/dev/null 2>&1; assert_exit "no action → exit 2" 2 $?
|
|
73
|
+
$NODE "$EE" emit >/dev/null 2>&1; assert_exit "emit without action → exit 2" 2 $?
|
|
74
|
+
|
|
75
|
+
rm -rf "$HOME_DIR" "$HOME2"
|
|
76
|
+
echo ""
|
|
77
|
+
echo "=== Results: $PASS passed, $FAIL failed ==="
|
|
78
|
+
[ "$FAIL" -eq 0 ]
|
package/tests/lib.test.sh
CHANGED
|
@@ -365,6 +365,31 @@ console.log(c.includes(".codex/bin/state.js") && c.includes(".codex/agents/plann
|
|
|
365
365
|
')
|
|
366
366
|
[ "$RES" = "HOST-RENDER-OK" ] && ok "host-adapters renders Qualia path tokens per host" || fail "host-adapters render: $RES"
|
|
367
367
|
|
|
368
|
+
RES=$($NODE -e '
|
|
369
|
+
const { adapter, hostForHome } = require("'"$HA"'");
|
|
370
|
+
const c = adapter("claude"), x = adapter("codex");
|
|
371
|
+
const ok =
|
|
372
|
+
c.agentCli === "claude" && JSON.stringify(c.agentExec("P")) === JSON.stringify(["-p","P"]) &&
|
|
373
|
+
x.agentCli === "codex" && JSON.stringify(x.agentExec("P")) === JSON.stringify(["exec","P"]) &&
|
|
374
|
+
hostForHome("/x/.codex") === "codex" && hostForHome("/x/.claude") === "claude";
|
|
375
|
+
console.log(ok ? "ADAPTER-EXEC-OK" : JSON.stringify({c:c.agentExec("P"),x:x.agentExec("P")}));
|
|
376
|
+
')
|
|
377
|
+
[ "$RES" = "ADAPTER-EXEC-OK" ] && ok "host-adapters owns agent-CLI invocation (agentCli/agentExec) + hostForHome" || fail "host-adapters agent-exec: $RES"
|
|
378
|
+
|
|
379
|
+
# ─── R9 design dials: the 3 dials exist in the reference AND the DESIGN template ─
|
|
380
|
+
DIALS="$FRAMEWORK_DIR/qualia-design/design-dials.md"
|
|
381
|
+
DTMPL="$FRAMEWORK_DIR/templates/DESIGN.md"
|
|
382
|
+
RES=$($NODE -e '
|
|
383
|
+
const fs = require("fs");
|
|
384
|
+
const ref = fs.readFileSync("'"$DIALS"'", "utf8");
|
|
385
|
+
const tpl = fs.readFileSync("'"$DTMPL"'", "utf8");
|
|
386
|
+
const dials = ["DESIGN_VARIANCE", "MOTION_INTENSITY", "VISUAL_DENSITY"];
|
|
387
|
+
const refOk = dials.every(d => ref.includes(d)) && /ban\b/i.test(ref) && /do instead/i.test(ref);
|
|
388
|
+
const tplOk = dials.every(d => tpl.includes(d));
|
|
389
|
+
console.log(refOk && tplOk ? "DIALS-OK" : "FAIL ref="+refOk+" tpl="+tplOk);
|
|
390
|
+
')
|
|
391
|
+
[ "$RES" = "DIALS-OK" ] && ok "design dials + ban→do-instead present in design-dials.md AND DESIGN.md template" || fail "design dials: $RES"
|
|
392
|
+
|
|
368
393
|
CS="$FRAMEWORK_DIR/bin/command-surface.js"
|
|
369
394
|
$NODE --check "$CS" >/dev/null 2>&1 && ok "command-surface.js parses" || fail "command-surface.js parse"
|
|
370
395
|
RES=$($NODE -e '
|
|
@@ -507,14 +532,14 @@ TMP=$(mktmp)
|
|
|
507
532
|
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
533
|
echo '{"installed_by":"Test","role":"OWNER","version":"6.3.0","erp":{"enabled":false}}' > "$TMP/home/.claude/.qualia-config.json"
|
|
509
534
|
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 agent-status.js analyze-gate.js verify-panel.js wave-plan.js eval-runner.js branch-hygiene.js last-report.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 project-sync.js codex-goal.js planning-hygiene.js prune-deprecated.js learning-candidates.js status-snapshot.js security-scan.js auto-report.js; do
|
|
535
|
+
for f in runtime-manifest.js command-surface.js host-adapters.js state.js qualia-ui.js statusline.js knowledge.js knowledge-flush.js recall.js vault-access.js repo-map.js design-tokens.js batch-plan.js state-ledger.js plan-contract.js contract-runner.js agent-status.js analyze-gate.js verify-panel.js wave-plan.js eval-runner.js branch-hygiene.js last-report.js harness-eval.js trust-score.js agent-runs.js slop-detect.mjs erp-retry.js erp-event.js work-packet.js report-payload.js project-snapshot.js project-sync.js codex-goal.js planning-hygiene.js prune-deprecated.js learning-candidates.js status-snapshot.js security-scan.js auto-report.js; do
|
|
511
536
|
touch "$TMP/home/.claude/bin/$f"
|
|
512
537
|
done
|
|
513
538
|
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
|
|
514
539
|
touch "$TMP/home/.claude/hooks/$h"
|
|
515
540
|
done
|
|
516
541
|
touch "$TMP/home/.claude/knowledge/index.md" "$TMP/home/.claude/knowledge/agents.md" "$TMP/home/.claude/agents/visual-evaluator.md" "$TMP/home/.claude/qualia-guide.md" "$TMP/home/.claude/qualia-templates/help.html"
|
|
517
|
-
for f in design-laws.md design-rubric.md design-brand.md design-product.md design-reference.md frontend.md graphics.md; do
|
|
542
|
+
for f in design-laws.md design-rubric.md design-brand.md design-product.md design-reference.md design-dials.md frontend.md graphics.md; do
|
|
518
543
|
touch "$TMP/home/.claude/qualia-design/$f"
|
|
519
544
|
done
|
|
520
545
|
for s in $($NODE -e 'console.log(require(process.argv[1]).ACTIVE_SKILLS.join(" "))' "$CS"); do
|
|
@@ -622,14 +647,14 @@ TMP=$(mktmp)
|
|
|
622
647
|
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
648
|
echo '{"installed_by":"Test","role":"OWNER","erp":{"enabled":false}}' > "$TMP/.claude/.qualia-config.json"
|
|
624
649
|
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 agent-status.js analyze-gate.js verify-panel.js wave-plan.js eval-runner.js branch-hygiene.js last-report.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 project-sync.js codex-goal.js planning-hygiene.js prune-deprecated.js learning-candidates.js status-snapshot.js security-scan.js auto-report.js; do
|
|
650
|
+
for f in runtime-manifest.js command-surface.js host-adapters.js state.js qualia-ui.js statusline.js knowledge.js knowledge-flush.js recall.js vault-access.js repo-map.js design-tokens.js batch-plan.js state-ledger.js plan-contract.js contract-runner.js agent-status.js analyze-gate.js verify-panel.js wave-plan.js eval-runner.js branch-hygiene.js last-report.js harness-eval.js trust-score.js agent-runs.js slop-detect.mjs erp-retry.js erp-event.js work-packet.js report-payload.js project-snapshot.js project-sync.js codex-goal.js planning-hygiene.js prune-deprecated.js learning-candidates.js status-snapshot.js security-scan.js auto-report.js; do
|
|
626
651
|
touch "$TMP/.claude/bin/$f"
|
|
627
652
|
done
|
|
628
653
|
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
|
|
629
654
|
touch "$TMP/.claude/hooks/$h"
|
|
630
655
|
done
|
|
631
656
|
touch "$TMP/.claude/knowledge/index.md" "$TMP/.claude/knowledge/agents.md"
|
|
632
|
-
for f in design-laws.md design-rubric.md design-brand.md design-product.md design-reference.md frontend.md graphics.md; do
|
|
657
|
+
for f in design-laws.md design-rubric.md design-brand.md design-product.md design-reference.md design-dials.md frontend.md graphics.md; do
|
|
633
658
|
touch "$TMP/.claude/qualia-design/$f"
|
|
634
659
|
done
|
|
635
660
|
for s in $($NODE -e 'console.log(require(process.argv[1]).ACTIVE_SKILLS.join(" "))' "$CS"); do
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# recall.test.sh — bin/recall.js (read-side memory recall, role-filtered vault)
|
|
3
|
+
# Run: bash tests/recall.test.sh
|
|
4
|
+
|
|
5
|
+
PASS=0
|
|
6
|
+
FAIL=0
|
|
7
|
+
BIN_DIR="$(cd "$(dirname "$0")/../bin" && pwd)"
|
|
8
|
+
NODE="${NODE:-node}"
|
|
9
|
+
RECALL="$BIN_DIR/recall.js"
|
|
10
|
+
|
|
11
|
+
assert_exit() { if [ "$2" = "$3" ]; then echo " ✓ $1"; PASS=$((PASS+1)); else echo " ✗ $1 (expected exit $2, got $3)"; FAIL=$((FAIL+1)); fi; }
|
|
12
|
+
assert_contains() { if echo "$2" | grep -qF "$3"; then echo " ✓ $1"; PASS=$((PASS+1)); else echo " ✗ $1 (missing '$3')"; FAIL=$((FAIL+1)); fi; }
|
|
13
|
+
assert_absent() { if echo "$2" | grep -qF "$3"; then echo " ✗ $1 (LEAK: found '$3')"; FAIL=$((FAIL+1)); else echo " ✓ $1"; PASS=$((PASS+1)); fi; }
|
|
14
|
+
|
|
15
|
+
echo "recall.test.sh — bin/recall.js"
|
|
16
|
+
echo ""
|
|
17
|
+
|
|
18
|
+
$NODE -c "$RECALL" 2>/dev/null && { echo " ✓ syntax valid"; PASS=$((PASS+1)); } || { echo " ✗ syntax invalid"; FAIL=$((FAIL+1)); }
|
|
19
|
+
|
|
20
|
+
# ── Fixture: a temp knowledge home + a temp vault with an access manifest ──
|
|
21
|
+
HOME_DIR=$(mktemp -d)
|
|
22
|
+
VAULT=$(mktemp -d)
|
|
23
|
+
mkdir -p "$HOME_DIR/knowledge" "$VAULT/wiki/concepts" "$VAULT/wiki/_meta"
|
|
24
|
+
|
|
25
|
+
# knowledge layer hit
|
|
26
|
+
cat > "$HOME_DIR/knowledge/learned-patterns.md" <<'EOF'
|
|
27
|
+
# Patterns
|
|
28
|
+
- The zebrafish pattern: batch writes before fanout.
|
|
29
|
+
EOF
|
|
30
|
+
# vault ALL_ROLES hit
|
|
31
|
+
cat > "$VAULT/wiki/concepts/notes.md" <<'EOF'
|
|
32
|
+
A lesson about the zebrafish approach to retries.
|
|
33
|
+
EOF
|
|
34
|
+
# vault OWNER_ONLY hit (the access manifest itself names wiki/_meta/access.md)
|
|
35
|
+
cat > "$VAULT/wiki/_meta/access.md" <<'EOF'
|
|
36
|
+
# Vault Access Manifest
|
|
37
|
+
## OWNER_ONLY
|
|
38
|
+
| Path | Why |
|
|
39
|
+
|---|---|
|
|
40
|
+
| `Clients/*.md` | commercial |
|
|
41
|
+
| `wiki/_meta/access.md` | this file (zebrafish marker) |
|
|
42
|
+
## ALL_ROLES
|
|
43
|
+
| `wiki/concepts/` | lessons |
|
|
44
|
+
EOF
|
|
45
|
+
|
|
46
|
+
export QUALIA_MEMORY_ROOT="$VAULT"
|
|
47
|
+
RUN() { QUALIA_HOME="$HOME_DIR" "$@"; }
|
|
48
|
+
|
|
49
|
+
# ── Invocation guards ──
|
|
50
|
+
RUN $NODE "$RECALL" >/dev/null 2>&1; assert_exit "no query → exit 2" 2 $?
|
|
51
|
+
RUN $NODE "$RECALL" zebrafish --scope bogus >/dev/null 2>&1; assert_exit "bad scope → exit 2" 2 $?
|
|
52
|
+
|
|
53
|
+
# ── Hits across both stores ──
|
|
54
|
+
OUT=$(QUALIA_ROLE=OWNER RUN $NODE "$RECALL" zebrafish 2>&1); assert_exit "valid query → exit 0" 0 $?
|
|
55
|
+
assert_contains "knowledge-layer hit" "$OUT" "learned-patterns.md"
|
|
56
|
+
assert_contains "vault ALL_ROLES hit" "$OUT" "concepts/notes.md"
|
|
57
|
+
|
|
58
|
+
# ── Role enforcement (the security boundary) ──
|
|
59
|
+
OWNER_OUT=$(QUALIA_ROLE=OWNER RUN $NODE "$RECALL" zebrafish --scope vault 2>&1)
|
|
60
|
+
assert_contains "OWNER sees OWNER_ONLY path" "$OWNER_OUT" "_meta/access.md"
|
|
61
|
+
EMP_OUT=$(QUALIA_ROLE=EMPLOYEE RUN $NODE "$RECALL" zebrafish --scope vault 2>&1)
|
|
62
|
+
assert_contains "EMPLOYEE still sees ALL_ROLES" "$EMP_OUT" "concepts/notes.md"
|
|
63
|
+
assert_absent "EMPLOYEE blocked from OWNER_ONLY" "$EMP_OUT" "_meta/access.md"
|
|
64
|
+
# fail-closed: no role config + no QUALIA_ROLE → RESTRICTED
|
|
65
|
+
NOROLE_OUT=$(QUALIA_HOME="$VAULT" env -u QUALIA_ROLE $NODE "$RECALL" zebrafish --scope vault 2>&1)
|
|
66
|
+
assert_absent "RESTRICTED (fail-closed) blocked from OWNER_ONLY" "$NOROLE_OUT" "_meta/access.md"
|
|
67
|
+
|
|
68
|
+
# ── JSON shape ──
|
|
69
|
+
JSON=$(QUALIA_ROLE=OWNER RUN $NODE "$RECALL" zebrafish --json 2>&1)
|
|
70
|
+
echo "$JSON" | $NODE -e "let s='';process.stdin.on('data',d=>s+=d).on('end',()=>{const j=JSON.parse(s);if(j.role&&j.scope==='all'&&typeof j.total==='number'&&Array.isArray(j.knowledge)&&Array.isArray(j.vault))process.exit(0);process.exit(1)})" \
|
|
71
|
+
&& { echo " ✓ --json valid shape (role/scope/total/knowledge/vault)"; PASS=$((PASS+1)); } \
|
|
72
|
+
|| { echo " ✗ --json shape"; FAIL=$((FAIL+1)); }
|
|
73
|
+
|
|
74
|
+
# ── Scope isolation ──
|
|
75
|
+
KONLY=$(QUALIA_ROLE=OWNER RUN $NODE "$RECALL" zebrafish --scope knowledge 2>&1)
|
|
76
|
+
assert_contains "scope=knowledge includes knowledge" "$KONLY" "learned-patterns.md"
|
|
77
|
+
assert_absent "scope=knowledge excludes vault" "$KONLY" "concepts/notes.md"
|
|
78
|
+
VONLY=$(QUALIA_ROLE=OWNER RUN $NODE "$RECALL" zebrafish --scope vault 2>&1)
|
|
79
|
+
assert_absent "scope=vault excludes knowledge" "$VONLY" "learned-patterns.md"
|
|
80
|
+
|
|
81
|
+
# ── Graceful degradation ──
|
|
82
|
+
NOVAULT=$(QUALIA_HOME="$HOME_DIR" QUALIA_MEMORY_ROOT="/tmp/qualia-no-such-vault-$$" QUALIA_ROLE=OWNER $NODE "$RECALL" zebrafish 2>&1); RC=$?
|
|
83
|
+
assert_exit "missing vault → still exit 0" 0 $RC
|
|
84
|
+
assert_contains "missing vault → knowledge still searched" "$NOVAULT" "learned-patterns.md"
|
|
85
|
+
ZERO=$(QUALIA_ROLE=OWNER RUN $NODE "$RECALL" "noyzzqxmatch" 2>&1); assert_exit "zero hits → exit 0" 0 $?
|
|
86
|
+
assert_contains "zero hits → friendly message" "$ZERO" "no matches"
|
|
87
|
+
|
|
88
|
+
rm -rf "$HOME_DIR" "$VAULT"
|
|
89
|
+
echo ""
|
|
90
|
+
echo "=== Results: $PASS passed, $FAIL failed ==="
|
|
91
|
+
[ "$FAIL" -eq 0 ]
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# repo-map.test.sh — bin/repo-map.js (zero-dep symbol map for /qualia-map)
|
|
3
|
+
# Run: bash tests/repo-map.test.sh
|
|
4
|
+
|
|
5
|
+
PASS=0
|
|
6
|
+
FAIL=0
|
|
7
|
+
BIN_DIR="$(cd "$(dirname "$0")/../bin" && pwd)"
|
|
8
|
+
NODE="${NODE:-node}"
|
|
9
|
+
RM="$BIN_DIR/repo-map.js"
|
|
10
|
+
|
|
11
|
+
assert_exit() { if [ "$2" = "$3" ]; then echo " ✓ $1"; PASS=$((PASS+1)); else echo " ✗ $1 (expected exit $2, got $3)"; FAIL=$((FAIL+1)); fi; }
|
|
12
|
+
assert_contains() { if echo "$2" | grep -qF "$3"; then echo " ✓ $1"; PASS=$((PASS+1)); else echo " ✗ $1 (missing '$3')"; FAIL=$((FAIL+1)); fi; }
|
|
13
|
+
assert_absent() { if echo "$2" | grep -qF "$3"; then echo " ✗ $1 (unexpected '$3')"; FAIL=$((FAIL+1)); else echo " ✓ $1"; PASS=$((PASS+1)); fi; }
|
|
14
|
+
|
|
15
|
+
echo "repo-map.test.sh — bin/repo-map.js"
|
|
16
|
+
echo ""
|
|
17
|
+
|
|
18
|
+
$NODE -c "$RM" 2>/dev/null && { echo " ✓ syntax valid"; PASS=$((PASS+1)); } || { echo " ✗ syntax invalid"; FAIL=$((FAIL+1)); }
|
|
19
|
+
|
|
20
|
+
# ── Fixture repo ──
|
|
21
|
+
REPO=$(mktemp -d)
|
|
22
|
+
mkdir -p "$REPO/src" "$REPO/node_modules/pkg" "$REPO/api"
|
|
23
|
+
cat > "$REPO/src/widget.ts" <<'EOF'
|
|
24
|
+
export function makeWidget() {}
|
|
25
|
+
export class WidgetStore {}
|
|
26
|
+
export const WIDGET_LIMIT = 5;
|
|
27
|
+
export type WidgetId = string;
|
|
28
|
+
function internalHelper() {}
|
|
29
|
+
EOF
|
|
30
|
+
cat > "$REPO/api/handler.py" <<'EOF'
|
|
31
|
+
class Handler:
|
|
32
|
+
pass
|
|
33
|
+
def serve():
|
|
34
|
+
pass
|
|
35
|
+
async def serve_async():
|
|
36
|
+
pass
|
|
37
|
+
EOF
|
|
38
|
+
cat > "$REPO/node_modules/pkg/index.js" <<'EOF'
|
|
39
|
+
export function shouldBeIgnored() {}
|
|
40
|
+
EOF
|
|
41
|
+
|
|
42
|
+
# ── Symbol extraction ──
|
|
43
|
+
OUT=$($NODE "$RM" "$REPO" 2>&1); assert_exit "valid dir → exit 0" 0 $?
|
|
44
|
+
assert_contains "extracts JS export function" "$OUT" "makeWidget"
|
|
45
|
+
assert_contains "extracts JS export class" "$OUT" "WidgetStore"
|
|
46
|
+
assert_contains "extracts JS export const" "$OUT" "WIDGET_LIMIT"
|
|
47
|
+
assert_contains "extracts JS export type" "$OUT" "WidgetId"
|
|
48
|
+
assert_contains "extracts Python def" "$OUT" "serve"
|
|
49
|
+
assert_contains "extracts Python class" "$OUT" "Handler"
|
|
50
|
+
assert_absent "ignores node_modules" "$OUT" "shouldBeIgnored"
|
|
51
|
+
|
|
52
|
+
# ── JSON shape ──
|
|
53
|
+
JSON=$($NODE "$RM" "$REPO" --json 2>&1)
|
|
54
|
+
echo "$JSON" | $NODE -e "let s='';process.stdin.on('data',d=>s+=d).on('end',()=>{const j=JSON.parse(s);if(typeof j.total_files==='number'&&typeof j.total_symbols==='number'&&Array.isArray(j.files)&&j.files[0].symbols)process.exit(0);process.exit(1)})" \
|
|
55
|
+
&& { echo " ✓ --json valid shape (total_files/total_symbols/files[].symbols)"; PASS=$((PASS+1)); } \
|
|
56
|
+
|| { echo " ✗ --json shape"; FAIL=$((FAIL+1)); }
|
|
57
|
+
# densest file first (widget.ts has 5 symbols, handler.py has 3)
|
|
58
|
+
FIRST=$(echo "$JSON" | $NODE -e "let s='';process.stdin.on('data',d=>s+=d).on('end',()=>{console.log(JSON.parse(s).files[0].file)})")
|
|
59
|
+
[ "$FIRST" = "src/widget.ts" ] && { echo " ✓ ranks densest file first"; PASS=$((PASS+1)); } || { echo " ✗ ranking (got '$FIRST')"; FAIL=$((FAIL+1)); }
|
|
60
|
+
|
|
61
|
+
# ── Edge cases ──
|
|
62
|
+
$NODE "$RM" /no/such/dir-xyz >/dev/null 2>&1; assert_exit "missing dir → exit 2" 2 $?
|
|
63
|
+
EMPTY=$(mktemp -d)
|
|
64
|
+
EOUT=$($NODE "$RM" "$EMPTY" 2>&1); assert_exit "empty dir → exit 0" 0 $?
|
|
65
|
+
assert_contains "empty dir → 0 source files" "$EOUT" "0 source files"
|
|
66
|
+
|
|
67
|
+
rm -rf "$REPO" "$EMPTY"
|
|
68
|
+
echo ""
|
|
69
|
+
echo "=== Results: $PASS passed, $FAIL failed ==="
|
|
70
|
+
[ "$FAIL" -eq 0 ]
|