qualia-framework 6.14.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/AGENTS.md +8 -5
- package/CHANGELOG.md +316 -0
- package/CLAUDE.md +3 -1
- package/agents/roadmapper.md +16 -14
- package/bin/agent-status.js +24 -11
- package/bin/batch-plan.js +111 -0
- package/bin/branch-hygiene.js +135 -0
- package/bin/command-surface.js +2 -0
- package/bin/compile-instructions.js +82 -0
- package/bin/design-tokens.js +131 -0
- package/bin/erp-event.js +177 -0
- package/bin/erp-retry.js +12 -1
- package/bin/eval-runner.js +218 -0
- package/bin/host-adapters.js +84 -12
- package/bin/install.js +44 -13
- package/bin/knowledge-flush.js +6 -3
- package/bin/last-report.js +207 -0
- package/bin/project-sync.js +315 -0
- package/bin/recall.js +172 -0
- package/bin/repo-map.js +188 -0
- package/bin/runtime-manifest.js +12 -0
- package/bin/state.js +112 -1
- package/bin/vault-access.js +82 -0
- package/bin/verify-panel.js +294 -0
- package/bin/wave-plan.js +211 -0
- package/docs/erp-contract.md +180 -0
- package/mcp/memory-mcp/server.js +257 -0
- package/package.json +6 -3
- package/qualia-design/design-dials.md +72 -0
- package/qualia-design/design-reference.md +24 -0
- package/rules/access.md +42 -0
- package/rules/codex-goal.md +28 -26
- package/rules/infrastructure.md +1 -1
- package/skills/qualia/SKILL.md +6 -0
- package/skills/qualia-build/SKILL.md +43 -9
- package/skills/qualia-eval/SKILL.md +83 -0
- package/skills/qualia-feature/SKILL.md +20 -4
- package/skills/qualia-fix/SKILL.md +13 -1
- package/skills/qualia-map/SKILL.md +15 -0
- package/skills/qualia-milestone/SKILL.md +12 -6
- package/skills/qualia-new/REFERENCE.md +6 -4
- package/skills/qualia-new/SKILL.md +41 -15
- package/skills/qualia-plan/SKILL.md +2 -2
- package/skills/qualia-polish/SKILL.md +3 -2
- package/skills/qualia-recall/SKILL.md +76 -0
- package/skills/qualia-report/SKILL.md +10 -0
- package/skills/qualia-scope/SKILL.md +3 -3
- package/skills/qualia-ship/SKILL.md +34 -4
- package/skills/qualia-update/SKILL.md +4 -0
- package/skills/qualia-verify/SKILL.md +53 -24
- package/templates/DESIGN.md +15 -0
- package/templates/instructions.md +32 -0
- package/templates/journey.md +1 -1
- package/templates/project-discovery.md +30 -23
- package/templates/requirements.md +7 -7
- package/tests/agent-status.test.sh +15 -0
- package/tests/batch-plan.test.sh +56 -0
- package/tests/branch-hygiene.test.sh +93 -0
- package/tests/design-tokens.test.sh +53 -0
- package/tests/erp-event.test.sh +78 -0
- package/tests/eval-runner.test.sh +147 -0
- package/tests/instructions.test.sh +109 -0
- package/tests/last-report.test.sh +156 -0
- package/tests/lib.test.sh +29 -4
- package/tests/project-sync.test.sh +175 -0
- package/tests/recall.test.sh +91 -0
- package/tests/repo-map.test.sh +70 -0
- package/tests/run-all.sh +12 -0
- package/tests/runner.js +363 -33
- package/tests/state.test.sh +92 -0
- package/tests/verify-panel.test.sh +162 -0
- package/tests/wave-plan.test.sh +153 -0
|
@@ -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,93 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# branch-hygiene.test.sh — bin/branch-hygiene.js (clock-out stranded-branch sweep)
|
|
3
|
+
# Run: bash tests/branch-hygiene.test.sh
|
|
4
|
+
|
|
5
|
+
PASS=0
|
|
6
|
+
FAIL=0
|
|
7
|
+
BIN_DIR="$(cd "$(dirname "$0")/../bin" && pwd)"
|
|
8
|
+
NODE="${NODE:-node}"
|
|
9
|
+
BH="$BIN_DIR/branch-hygiene.js"
|
|
10
|
+
|
|
11
|
+
assert_exit() {
|
|
12
|
+
local name="$1" expected="$2" actual="$3"
|
|
13
|
+
if [ "$expected" = "$actual" ]; then echo " ✓ $name"; PASS=$((PASS+1));
|
|
14
|
+
else echo " ✗ $name (expected exit $expected, got $actual)"; FAIL=$((FAIL+1)); fi
|
|
15
|
+
}
|
|
16
|
+
assert_contains() {
|
|
17
|
+
local name="$1" hay="$2" needle="$3"
|
|
18
|
+
if echo "$hay" | grep -qF "$needle"; then echo " ✓ $name"; PASS=$((PASS+1));
|
|
19
|
+
else echo " ✗ $name (missing '$needle' in: $hay)"; FAIL=$((FAIL+1)); fi
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
# fresh repo on main with one commit; prints the dir (caller rm -rf)
|
|
23
|
+
setup_repo() {
|
|
24
|
+
local tmp
|
|
25
|
+
tmp=$(mktemp -d)
|
|
26
|
+
(cd "$tmp" \
|
|
27
|
+
&& git init -q \
|
|
28
|
+
&& git checkout -q -b main 2>/dev/null \
|
|
29
|
+
&& git config user.email t@t.com && git config user.name T \
|
|
30
|
+
&& echo seed > seed.txt && git add seed.txt && git commit -q -m seed)
|
|
31
|
+
echo "$tmp"
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
echo "branch-hygiene.test.sh — bin/branch-hygiene.js"
|
|
35
|
+
echo ""
|
|
36
|
+
|
|
37
|
+
$NODE -c "$BH" 2>/dev/null && { echo " ✓ syntax valid"; PASS=$((PASS+1)); } || { echo " ✗ syntax invalid"; FAIL=$((FAIL+1)); }
|
|
38
|
+
|
|
39
|
+
# --- not a git repo → exit 2 ---
|
|
40
|
+
TMP=$(mktemp -d)
|
|
41
|
+
(cd "$TMP" && $NODE "$BH" >/dev/null 2>&1)
|
|
42
|
+
assert_exit "not a git repo → exit 2" 2 $?
|
|
43
|
+
rm -rf "$TMP"
|
|
44
|
+
|
|
45
|
+
# --- clean: only main → exit 0 ---
|
|
46
|
+
TMP=$(setup_repo)
|
|
47
|
+
(cd "$TMP" && $NODE "$BH" >/dev/null 2>&1)
|
|
48
|
+
assert_exit "clean repo (main only) → exit 0" 0 $?
|
|
49
|
+
OUT=$(cd "$TMP" && $NODE "$BH" 2>&1)
|
|
50
|
+
assert_contains "reports clean" "$OUT" "clean"
|
|
51
|
+
rm -rf "$TMP"
|
|
52
|
+
|
|
53
|
+
# --- stranded feature branch ahead of main → exit 1, listed ---
|
|
54
|
+
TMP=$(setup_repo)
|
|
55
|
+
(cd "$TMP" && git checkout -q -b feat/stranded && echo work > w.txt && git add w.txt && git commit -q -m "wip work")
|
|
56
|
+
(cd "$TMP" && $NODE "$BH" >/dev/null 2>&1)
|
|
57
|
+
assert_exit "branch ahead of main → exit 1" 1 $?
|
|
58
|
+
OUT=$(cd "$TMP" && $NODE "$BH" 2>&1)
|
|
59
|
+
assert_contains "lists the stranded branch" "$OUT" "feat/stranded"
|
|
60
|
+
assert_contains "shows commits ahead" "$OUT" "+1 commit"
|
|
61
|
+
# json shape
|
|
62
|
+
OUT=$(cd "$TMP" && $NODE "$BH" --json 2>&1)
|
|
63
|
+
assert_contains "json stranded entry" "$OUT" '"branch": "feat/stranded"'
|
|
64
|
+
assert_contains "json ahead count" "$OUT" '"ahead": 1'
|
|
65
|
+
rm -rf "$TMP"
|
|
66
|
+
|
|
67
|
+
# --- once integrated (ff-merged) into main → no longer stranded → exit 0 ---
|
|
68
|
+
TMP=$(setup_repo)
|
|
69
|
+
(cd "$TMP" && git checkout -q -b feat/done && echo x > x.txt && git add x.txt && git commit -q -m "done work")
|
|
70
|
+
(cd "$TMP" && git checkout -q main && git merge -q --ff-only feat/done)
|
|
71
|
+
(cd "$TMP" && $NODE "$BH" >/dev/null 2>&1)
|
|
72
|
+
assert_exit "ff-merged branch no longer stranded → exit 0" 0 $?
|
|
73
|
+
rm -rf "$TMP"
|
|
74
|
+
|
|
75
|
+
# --- master as the base branch is detected ---
|
|
76
|
+
TMP=$(mktemp -d)
|
|
77
|
+
(cd "$TMP" && git init -q && git checkout -q -b master 2>/dev/null && git config user.email t@t.com && git config user.name T && echo s > s.txt && git add s.txt && git commit -q -m s)
|
|
78
|
+
(cd "$TMP" && git checkout -q -b feature && echo y > y.txt && git add y.txt && git commit -q -m y)
|
|
79
|
+
OUT=$(cd "$TMP" && $NODE "$BH" --json 2>&1)
|
|
80
|
+
assert_contains "detects master as base" "$OUT" '"base": "master"'
|
|
81
|
+
assert_contains "stranded vs master" "$OUT" '"branch": "feature"'
|
|
82
|
+
rm -rf "$TMP"
|
|
83
|
+
|
|
84
|
+
# --- library: analyze() returns structured result ---
|
|
85
|
+
TMP=$(setup_repo)
|
|
86
|
+
(cd "$TMP" && git checkout -q -b b1 && echo a>a && git add a && git commit -q -m a)
|
|
87
|
+
RES=$($NODE -e "console.log(JSON.stringify(require('$BH').analyze('$TMP').stranded.length))" 2>&1)
|
|
88
|
+
assert_contains "analyze() finds 1 stranded" "$RES" "1"
|
|
89
|
+
rm -rf "$TMP"
|
|
90
|
+
|
|
91
|
+
echo ""
|
|
92
|
+
echo "=== Results: $PASS passed, $FAIL failed ==="
|
|
93
|
+
[ "$FAIL" -eq 0 ] && exit 0 || exit 1
|
|
@@ -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 ]
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# eval-runner.test.sh — bin/eval-runner.js (layered AI-feature eval, R7)
|
|
3
|
+
# Run: bash tests/eval-runner.test.sh
|
|
4
|
+
|
|
5
|
+
PASS=0
|
|
6
|
+
FAIL=0
|
|
7
|
+
BIN_DIR="$(cd "$(dirname "$0")/../bin" && pwd)"
|
|
8
|
+
NODE="${NODE:-node}"
|
|
9
|
+
ER="$BIN_DIR/eval-runner.js"
|
|
10
|
+
|
|
11
|
+
assert_exit() {
|
|
12
|
+
local name="$1" expected="$2" actual="$3"
|
|
13
|
+
if [ "$expected" = "$actual" ]; then echo " ✓ $name"; PASS=$((PASS+1));
|
|
14
|
+
else echo " ✗ $name (expected exit $expected, got $actual)"; FAIL=$((FAIL+1)); fi
|
|
15
|
+
}
|
|
16
|
+
assert_contains() {
|
|
17
|
+
local name="$1" hay="$2" needle="$3"
|
|
18
|
+
if echo "$hay" | grep -qF "$needle"; then echo " ✓ $name"; PASS=$((PASS+1));
|
|
19
|
+
else echo " ✗ $name (missing '$needle' in: $hay)"; FAIL=$((FAIL+1)); fi
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
echo "eval-runner.test.sh — bin/eval-runner.js"
|
|
23
|
+
echo ""
|
|
24
|
+
|
|
25
|
+
$NODE -c "$ER" 2>/dev/null && { echo " ✓ syntax valid"; PASS=$((PASS+1)); } || { echo " ✗ syntax invalid"; FAIL=$((FAIL+1)); }
|
|
26
|
+
|
|
27
|
+
# --- all deterministic assertions pass → PASS ---
|
|
28
|
+
TMP=$(mktemp -d)
|
|
29
|
+
cat > "$TMP/s.json" <<'EOF'
|
|
30
|
+
{ "feature":"support-chat", "cases":[
|
|
31
|
+
{ "name":"refund", "output":"We refund within 30 days.", "latency_ms":1200, "cost_usd":0.008,
|
|
32
|
+
"assert":[
|
|
33
|
+
{"type":"contains","value":"30 days"},
|
|
34
|
+
{"type":"not_contains","value":"I cannot help"},
|
|
35
|
+
{"type":"regex","value":"\\b30\\b"},
|
|
36
|
+
{"type":"min_length","value":5},
|
|
37
|
+
{"type":"max_latency_ms","value":2000},
|
|
38
|
+
{"type":"max_cost_usd","value":0.02}
|
|
39
|
+
] } ] }
|
|
40
|
+
EOF
|
|
41
|
+
$NODE "$ER" "$TMP/s.json" >/dev/null 2>&1
|
|
42
|
+
assert_exit "all deterministic pass → exit 0" 0 $?
|
|
43
|
+
OUT=$($NODE "$ER" "$TMP/s.json" 2>&1)
|
|
44
|
+
assert_contains "reports EVAL PASS" "$OUT" "EVAL PASS"
|
|
45
|
+
rm -rf "$TMP"
|
|
46
|
+
|
|
47
|
+
# --- a failing contains → FAIL with the failing assertion shown ---
|
|
48
|
+
TMP=$(mktemp -d)
|
|
49
|
+
cat > "$TMP/s.json" <<'EOF'
|
|
50
|
+
{ "feature":"chat", "cases":[
|
|
51
|
+
{ "name":"refusal", "output":"I cannot help with that.",
|
|
52
|
+
"assert":[ {"type":"contains","value":"30 days"}, {"type":"not_contains","value":"I cannot help"} ] } ] }
|
|
53
|
+
EOF
|
|
54
|
+
$NODE "$ER" "$TMP/s.json" >/dev/null 2>&1
|
|
55
|
+
assert_exit "failing assertion → exit 1" 1 $?
|
|
56
|
+
OUT=$($NODE "$ER" "$TMP/s.json" 2>&1)
|
|
57
|
+
assert_contains "shows failing case" "$OUT" "refusal"
|
|
58
|
+
rm -rf "$TMP"
|
|
59
|
+
|
|
60
|
+
# --- latency over budget → FAIL ---
|
|
61
|
+
TMP=$(mktemp -d)
|
|
62
|
+
cat > "$TMP/s.json" <<'EOF'
|
|
63
|
+
{ "feature":"chat", "cases":[
|
|
64
|
+
{ "name":"slow", "output":"ok", "latency_ms":5000, "assert":[ {"type":"max_latency_ms","value":2000} ] } ] }
|
|
65
|
+
EOF
|
|
66
|
+
$NODE "$ER" "$TMP/s.json" >/dev/null 2>&1
|
|
67
|
+
assert_exit "latency over budget → exit 1" 1 $?
|
|
68
|
+
# missing latency metric but asserted → fail (no silent pass)
|
|
69
|
+
cat > "$TMP/s2.json" <<'EOF'
|
|
70
|
+
{ "feature":"chat", "cases":[ { "name":"no-metric", "output":"ok", "assert":[ {"type":"max_latency_ms","value":2000} ] } ] }
|
|
71
|
+
EOF
|
|
72
|
+
$NODE "$ER" "$TMP/s2.json" >/dev/null 2>&1
|
|
73
|
+
assert_exit "asserting latency with none recorded → exit 1" 1 $?
|
|
74
|
+
rm -rf "$TMP"
|
|
75
|
+
|
|
76
|
+
# --- json_path + json_valid ---
|
|
77
|
+
TMP=$(mktemp -d)
|
|
78
|
+
cat > "$TMP/s.json" <<'EOF'
|
|
79
|
+
{ "feature":"rag", "cases":[
|
|
80
|
+
{ "name":"structured", "output":"{\"answer\":\"yes\",\"sources\":[{\"id\":\"doc1\"}]}",
|
|
81
|
+
"assert":[
|
|
82
|
+
{"type":"json_valid"},
|
|
83
|
+
{"type":"json_path","path":"answer","equals":"yes"},
|
|
84
|
+
{"type":"json_path","path":"sources.0.id","contains":"doc"}
|
|
85
|
+
] } ] }
|
|
86
|
+
EOF
|
|
87
|
+
$NODE "$ER" "$TMP/s.json" >/dev/null 2>&1
|
|
88
|
+
assert_exit "json_path + json_valid pass → exit 0" 0 $?
|
|
89
|
+
# invalid json under json_valid → fail
|
|
90
|
+
cat > "$TMP/bad.json" <<'EOF'
|
|
91
|
+
{ "feature":"rag", "cases":[ { "name":"notjson", "output":"not json", "assert":[ {"type":"json_valid"} ] } ] }
|
|
92
|
+
EOF
|
|
93
|
+
$NODE "$ER" "$TMP/bad.json" >/dev/null 2>&1
|
|
94
|
+
assert_exit "json_valid on non-json → exit 1" 1 $?
|
|
95
|
+
rm -rf "$TMP"
|
|
96
|
+
|
|
97
|
+
# --- llm_rubric: verdict pass / fail / pending ---
|
|
98
|
+
TMP=$(mktemp -d)
|
|
99
|
+
cat > "$TMP/pass.json" <<'EOF'
|
|
100
|
+
{ "feature":"chat", "cases":[ { "name":"r", "output":"grounded answer", "assert":[ {"type":"llm_rubric","rubric":"grounded","verdict":"pass"} ] } ] }
|
|
101
|
+
EOF
|
|
102
|
+
$NODE "$ER" "$TMP/pass.json" >/dev/null 2>&1
|
|
103
|
+
assert_exit "llm_rubric verdict=pass → exit 0" 0 $?
|
|
104
|
+
cat > "$TMP/fail.json" <<'EOF'
|
|
105
|
+
{ "feature":"chat", "cases":[ { "name":"r", "output":"hallucination", "assert":[ {"type":"llm_rubric","rubric":"grounded","verdict":"fail"} ] } ] }
|
|
106
|
+
EOF
|
|
107
|
+
$NODE "$ER" "$TMP/fail.json" >/dev/null 2>&1
|
|
108
|
+
assert_exit "llm_rubric verdict=fail → exit 1" 1 $?
|
|
109
|
+
cat > "$TMP/pending.json" <<'EOF'
|
|
110
|
+
{ "feature":"chat", "cases":[ { "name":"r", "output":"x", "assert":[ {"type":"llm_rubric","rubric":"grounded"} ] } ] }
|
|
111
|
+
EOF
|
|
112
|
+
$NODE "$ER" "$TMP/pending.json" >/dev/null 2>&1
|
|
113
|
+
assert_exit "unjudged llm_rubric → PENDING → exit 1 (no silent pass)" 1 $?
|
|
114
|
+
OUT=$($NODE "$ER" "$TMP/pending.json" 2>&1)
|
|
115
|
+
assert_contains "reports unjudged rubric count" "$OUT" "unjudged"
|
|
116
|
+
rm -rf "$TMP"
|
|
117
|
+
|
|
118
|
+
# --- output_file resolution + --write artifact ---
|
|
119
|
+
TMP=$(mktemp -d); mkdir -p "$TMP/.planning"
|
|
120
|
+
echo -n "the answer is 42" > "$TMP/out.txt"
|
|
121
|
+
cat > "$TMP/s.json" <<'EOF'
|
|
122
|
+
{ "feature":"file-feature", "cases":[ { "name":"fromfile", "output_file":"out.txt", "assert":[ {"type":"contains","value":"42"} ] } ] }
|
|
123
|
+
EOF
|
|
124
|
+
(cd "$TMP" && $NODE "$ER" s.json --write >/dev/null 2>&1)
|
|
125
|
+
assert_exit "output_file case passes → exit 0" 0 $?
|
|
126
|
+
[ -f "$TMP/.planning/evals/eval-file-feature.json" ] && { echo " ✓ --write emits eval artifact"; PASS=$((PASS+1)); } || { echo " ✗ no eval artifact"; FAIL=$((FAIL+1)); }
|
|
127
|
+
# missing output_file → fail, not crash
|
|
128
|
+
cat > "$TMP/miss.json" <<'EOF'
|
|
129
|
+
{ "feature":"x", "cases":[ { "name":"gone", "output_file":"nope.txt", "assert":[ {"type":"contains","value":"y"} ] } ] }
|
|
130
|
+
EOF
|
|
131
|
+
(cd "$TMP" && $NODE "$ER" miss.json >/dev/null 2>&1)
|
|
132
|
+
assert_exit "missing output_file → exit 1 (graceful)" 1 $?
|
|
133
|
+
rm -rf "$TMP"
|
|
134
|
+
|
|
135
|
+
# --- library: runAssertion unit ---
|
|
136
|
+
U=$($NODE -e "const e=require('$ER'); console.log(e.runAssertion({type:'contains',value:'hi'},{output:'oh hi there'}).ok)" 2>&1)
|
|
137
|
+
assert_contains "runAssertion contains true" "$U" "true"
|
|
138
|
+
U=$($NODE -e "const e=require('$ER'); console.log(e.getPath({a:{b:[{c:9}]}},'a.b.0.c'))" 2>&1)
|
|
139
|
+
assert_contains "getPath nested+index" "$U" "9"
|
|
140
|
+
|
|
141
|
+
# --- malformed suite → exit 2 ---
|
|
142
|
+
$NODE "$ER" /nonexistent-suite.json >/dev/null 2>&1
|
|
143
|
+
assert_exit "missing suite → exit 2" 2 $?
|
|
144
|
+
|
|
145
|
+
echo ""
|
|
146
|
+
echo "=== Results: $PASS passed, $FAIL failed ==="
|
|
147
|
+
[ "$FAIL" -eq 0 ] && exit 0 || exit 1
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# instructions.test.sh — single-source instruction compile (R4) + host adapter
|
|
3
|
+
# contract (R5). Run: bash tests/instructions.test.sh
|
|
4
|
+
|
|
5
|
+
PASS=0
|
|
6
|
+
FAIL=0
|
|
7
|
+
FRAMEWORK_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
|
8
|
+
NODE="${NODE:-node}"
|
|
9
|
+
HA="$FRAMEWORK_DIR/bin/host-adapters.js"
|
|
10
|
+
CI="$FRAMEWORK_DIR/bin/compile-instructions.js"
|
|
11
|
+
|
|
12
|
+
assert_exit() {
|
|
13
|
+
local name="$1" expected="$2" actual="$3"
|
|
14
|
+
if [ "$expected" = "$actual" ]; then echo " ✓ $name"; PASS=$((PASS+1));
|
|
15
|
+
else echo " ✗ $name (expected exit $expected, got $actual)"; FAIL=$((FAIL+1)); fi
|
|
16
|
+
}
|
|
17
|
+
assert_eq() {
|
|
18
|
+
local name="$1" expected="$2" actual="$3"
|
|
19
|
+
if [ "$expected" = "$actual" ]; then echo " ✓ $name"; PASS=$((PASS+1));
|
|
20
|
+
else echo " ✗ $name (expected '$expected', got '$actual')"; FAIL=$((FAIL+1)); fi
|
|
21
|
+
}
|
|
22
|
+
assert_contains() {
|
|
23
|
+
local name="$1" hay="$2" needle="$3"
|
|
24
|
+
if echo "$hay" | grep -qF "$needle"; then echo " ✓ $name"; PASS=$((PASS+1));
|
|
25
|
+
else echo " ✗ $name (missing '$needle')"; FAIL=$((FAIL+1)); fi
|
|
26
|
+
}
|
|
27
|
+
refute_contains() {
|
|
28
|
+
local name="$1" hay="$2" needle="$3"
|
|
29
|
+
if echo "$hay" | grep -qF "$needle"; then echo " ✗ $name (found '$needle')"; FAIL=$((FAIL+1));
|
|
30
|
+
else echo " ✓ $name"; PASS=$((PASS+1)); fi
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
echo "instructions.test.sh — single-source compile (R4) + host contract (R5)"
|
|
34
|
+
echo ""
|
|
35
|
+
|
|
36
|
+
# syntax
|
|
37
|
+
$NODE -c "$HA" 2>/dev/null && { echo " ✓ host-adapters syntax valid"; PASS=$((PASS+1)); } || { echo " ✗ host-adapters syntax"; FAIL=$((FAIL+1)); }
|
|
38
|
+
$NODE -c "$CI" 2>/dev/null && { echo " ✓ compile-instructions syntax valid"; PASS=$((PASS+1)); } || { echo " ✗ compile-instructions syntax"; FAIL=$((FAIL+1)); }
|
|
39
|
+
|
|
40
|
+
# --- R4: drift guard — committed files match the canonical ---
|
|
41
|
+
$NODE "$CI" --check >/dev/null 2>&1
|
|
42
|
+
assert_exit "compile-instructions --check passes (no drift)" 0 $?
|
|
43
|
+
|
|
44
|
+
# A canonical edit not reflected in the artifacts is caught by --check.
|
|
45
|
+
TMPCANON=$(mktemp)
|
|
46
|
+
cp "$FRAMEWORK_DIR/templates/instructions.md" "$TMPCANON"
|
|
47
|
+
printf '\nDRIFT-MARKER\n' >> "$FRAMEWORK_DIR/templates/instructions.md"
|
|
48
|
+
$NODE "$CI" --check >/dev/null 2>&1
|
|
49
|
+
RC=$?
|
|
50
|
+
cp "$TMPCANON" "$FRAMEWORK_DIR/templates/instructions.md" # restore immediately
|
|
51
|
+
rm -f "$TMPCANON"
|
|
52
|
+
assert_exit "drift guard fails (exit 1) when canonical edited but not recompiled" 1 $RC
|
|
53
|
+
# confirm restore worked
|
|
54
|
+
$NODE "$CI" --check >/dev/null 2>&1
|
|
55
|
+
assert_exit "canonical restored, --check green again" 0 $?
|
|
56
|
+
|
|
57
|
+
# --- R4: both artifacts share an identical body, differ only in footer ---
|
|
58
|
+
CLAUDE_BODY=$(sed -n '/# Qualia Framework/,/state router tells you the next command/p' "$FRAMEWORK_DIR/CLAUDE.md")
|
|
59
|
+
AGENTS_BODY=$(sed -n '/# Qualia Framework/,/state router tells you the next command/p' "$FRAMEWORK_DIR/AGENTS.md")
|
|
60
|
+
if [ "$CLAUDE_BODY" = "$AGENTS_BODY" ]; then
|
|
61
|
+
echo " ✓ CLAUDE.md and AGENTS.md bodies are identical (no drift)"; PASS=$((PASS+1));
|
|
62
|
+
else
|
|
63
|
+
echo " ✗ CLAUDE.md and AGENTS.md bodies diverge"; FAIL=$((FAIL+1));
|
|
64
|
+
fi
|
|
65
|
+
# host-specific footer survives
|
|
66
|
+
assert_contains "CLAUDE.md keeps Claude footer" "$(cat "$FRAMEWORK_DIR/CLAUDE.md")" "this file stays under 25 lines"
|
|
67
|
+
assert_contains "AGENTS.md keeps cross-vendor footer" "$(cat "$FRAMEWORK_DIR/AGENTS.md")" "cross-vendor compatibility"
|
|
68
|
+
refute_contains "AGENTS.md does NOT carry the Claude-only footer" "$(cat "$FRAMEWORK_DIR/AGENTS.md")" "this file stays under 25 lines"
|
|
69
|
+
# both carry the generated header + role placeholder for install to fill
|
|
70
|
+
assert_contains "CLAUDE.md has generated header" "$(head -1 "$FRAMEWORK_DIR/CLAUDE.md")" "GENERATED from templates/instructions.md"
|
|
71
|
+
assert_contains "AGENTS.md keeps {{ROLE}} for install" "$(cat "$FRAMEWORK_DIR/AGENTS.md")" "{{ROLE}}"
|
|
72
|
+
|
|
73
|
+
# --- R5: adapter contract is the single source of per-host facts ---
|
|
74
|
+
assert_eq "claude instructionFile" "CLAUDE.md" "$($NODE -e "console.log(require('$HA').adapter('claude').instructionFile)")"
|
|
75
|
+
assert_eq "codex instructionFile" "AGENTS.md" "$($NODE -e "console.log(require('$HA').adapter('codex').instructionFile)")"
|
|
76
|
+
assert_eq "claude configFile" "settings.json" "$($NODE -e "console.log(require('$HA').adapter('claude').configFile)")"
|
|
77
|
+
assert_eq "codex configFile" "config.toml" "$($NODE -e "console.log(require('$HA').adapter('codex').configFile)")"
|
|
78
|
+
assert_eq "codex agentExt" ".toml" "$($NODE -e "console.log(require('$HA').adapter('codex').agentExt)")"
|
|
79
|
+
assert_eq "unknown host throws" "throws" "$($NODE -e "try{require('$HA').adapter('cursor');console.log('no')}catch(e){console.log('throws')}")"
|
|
80
|
+
|
|
81
|
+
# --- R5: stripHostBlocks keeps only the matching host's block ---
|
|
82
|
+
SRC='before
|
|
83
|
+
<!--QUALIA-HOST claude-->
|
|
84
|
+
CLAUDE_ONLY
|
|
85
|
+
<!--/QUALIA-HOST-->
|
|
86
|
+
<!--QUALIA-HOST codex-->
|
|
87
|
+
CODEX_ONLY
|
|
88
|
+
<!--/QUALIA-HOST-->
|
|
89
|
+
after'
|
|
90
|
+
CLAUDE_OUT=$($NODE -e "console.log(require('$HA').stripHostBlocks(process.argv[1],'claude'))" "$SRC")
|
|
91
|
+
assert_contains "claude strip keeps CLAUDE_ONLY" "$CLAUDE_OUT" "CLAUDE_ONLY"
|
|
92
|
+
refute_contains "claude strip drops CODEX_ONLY" "$CLAUDE_OUT" "CODEX_ONLY"
|
|
93
|
+
CODEX_OUT=$($NODE -e "console.log(require('$HA').stripHostBlocks(process.argv[1],'codex'))" "$SRC")
|
|
94
|
+
assert_contains "codex strip keeps CODEX_ONLY" "$CODEX_OUT" "CODEX_ONLY"
|
|
95
|
+
refute_contains "codex strip drops CLAUDE_ONLY" "$CODEX_OUT" "CLAUDE_ONLY"
|
|
96
|
+
|
|
97
|
+
# --- R5: compileInstructions applies naming but leaves tokens/role for install ---
|
|
98
|
+
NAMED=$($NODE -e "console.log(require('$HA').compileInstructions('Use Claude Code now. Role: {{ROLE}} at \${QUALIA_BIN}','codex'))")
|
|
99
|
+
assert_contains "codex compile swaps the product name" "$NAMED" "Use Codex now"
|
|
100
|
+
assert_contains "codex compile leaves {{ROLE}} for install" "$NAMED" "{{ROLE}}"
|
|
101
|
+
assert_contains "codex compile leaves \${QUALIA_BIN} token for install" "$NAMED" '${QUALIA_BIN}'
|
|
102
|
+
|
|
103
|
+
# --- R5: renderText still resolves paths (regression) ---
|
|
104
|
+
RENDERED=$($NODE -e "console.log(require('$HA').renderText('bin at \${QUALIA_BIN}','claude'))")
|
|
105
|
+
assert_contains "renderText resolves QUALIA_BIN" "$RENDERED" "/.claude/bin"
|
|
106
|
+
|
|
107
|
+
echo ""
|
|
108
|
+
echo "=== Results: $PASS passed, $FAIL failed ==="
|
|
109
|
+
[ "$FAIL" -eq 0 ] && exit 0 || exit 1
|