qualia-framework 7.0.1 → 7.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +76 -0
- package/bin/cli.js +6 -1
- package/bin/dep-verify.mjs +328 -0
- package/bin/install.js +15 -0
- package/bin/qualia-ui.js +186 -1
- package/bin/report-payload.js +4 -1
- package/bin/runtime-manifest.js +1 -0
- package/bin/state.js +11 -4
- package/bin/statusline.js +7 -1
- package/bin/trust-score.js +1 -1
- package/hooks/pre-deploy-gate.js +54 -3
- package/hooks/secret-guard.js +162 -0
- package/hooks/session-start.js +1 -0
- package/package.json +4 -1
- package/skills/qualia-build/SKILL.md +8 -1
- package/skills/qualia-report/SKILL.md +11 -0
- package/skills/qualia-ship/SKILL.md +17 -1
- package/skills/qualia-verify/SKILL.md +11 -4
- package/tests/bin.test.sh +2 -2
- package/tests/dep-verify.test.sh +247 -0
- package/tests/hooks.test.sh +97 -0
- package/tests/install-smoke.test.sh +4 -3
- package/tests/journey-spine.test.sh +171 -0
- package/tests/lib.test.sh +4 -4
- package/tests/run-all.sh +4 -0
- package/tests/runner.js +2 -2
- package/tests/runtime-parity.test.sh +62 -0
- package/tests/secret-guard.test.sh +92 -0
- package/tests/state.test.sh +35 -0
|
@@ -76,9 +76,11 @@ Verify the {L} concerns of this phase. Every finding needs file:line evidence an
|
|
|
76
76
|
Write your findings as a JSON array to .planning/phase-{N}-panel-{L}.json:
|
|
77
77
|
[{\"file\":\"path\",\"line\":N,\"severity\":\"CRITICAL|HIGH|MEDIUM|LOW\",\"title\":\"one-line claim\"}]
|
|
78
78
|
Empty array [] if the {L} lens is clean. Also append a human '## {L} lens' section to .planning/phase-{N}-verification.md.
|
|
79
|
-
", subagent_type="qualia-verifier", description="Verify phase {N} — {L} lens")
|
|
79
|
+
", subagent_type="qualia-verifier", model="sonnet", description="Verify phase {N} — {L} lens")
|
|
80
80
|
```
|
|
81
81
|
|
|
82
|
+
**Model routing.** Panel verifiers do *finding* work — grep code against acceptance criteria and the shared machine evidence — which is high-recall but low-judgment, so they run on **`sonnet`**. Spend the frontier model where the verdict is actually decided: the skeptic vote (§3c step 2). This is the intelligent-model-routing OpEx lever applied to verification — cheaper on the wide finding pass, frontier on the narrow adjudication.
|
|
83
|
+
|
|
82
84
|
### 3b. Browser QA (if phase touched frontend)
|
|
83
85
|
|
|
84
86
|
If plan Files include `.tsx`/`.jsx`/`.css`/`.scss` or `app/`/`pages/`/`components/` paths, spawn browser QA parallel:
|
|
@@ -129,6 +131,8 @@ Return exactly one line: REAL — {file:line reason} OR NOT_REAL — {file:l
|
|
|
129
131
|
", subagent_type="qualia-verifier", description="Skeptic {i}/3 — {title}")
|
|
130
132
|
```
|
|
131
133
|
|
|
134
|
+
Skeptics deliberately **omit `model=`** so they inherit the session's frontier model: their REAL/NOT_REAL judgment is what flips a CRITICAL/HIGH verdict, and that is the one step in the pipeline where model strength most changes the outcome. Route cheap on the finding pass, never on the adjudication.
|
|
135
|
+
|
|
132
136
|
Tally each finding's votes into `.planning/phase-{N}-panel.json` (`votes.real` / `votes.notReal`).
|
|
133
137
|
|
|
134
138
|
**3. Aggregate** deterministically:
|
|
@@ -196,13 +200,16 @@ Write the deterministic eval artifact before changing state:
|
|
|
196
200
|
node ${QUALIA_BIN}/harness-eval.js --phase {N} --run --write
|
|
197
201
|
```
|
|
198
202
|
|
|
199
|
-
Run the zero-token
|
|
203
|
+
Run the zero-token deterministic gates (same role as `migration-guard`/`branch-guard` — each exits non-zero on a hard fault). A non-zero exit is a verification FAIL, not a soft note:
|
|
200
204
|
|
|
201
205
|
```bash
|
|
202
|
-
node ${QUALIA_BIN}/slop-detect.mjs --severity=critical
|
|
206
|
+
node ${QUALIA_BIN}/slop-detect.mjs --severity=critical # CRITICAL design tells (the slop half)
|
|
207
|
+
node ${QUALIA_BIN}/dep-verify.mjs --severity=critical # hallucinated/slopsquatted imports (the correctness half)
|
|
203
208
|
```
|
|
204
209
|
|
|
205
|
-
|
|
210
|
+
`dep-verify` flags any import whose package is BOTH undeclared in `package.json` AND absent from `node_modules` — the exact signature of an AI-invented or typosquatted dependency (the #1 named AI-generated-code security failure mode). It is the correctness/security companion to the design-focused `slop-detect`.
|
|
211
|
+
|
|
212
|
+
The phase is PASS only if ALL of these agree: the panel verdict (§3c `verify-panel.js` exit 0), the harness-eval status, the anti-slop scan, and the dependency scan. If any is FAIL/non-zero, mark the phase FAIL. The state machine also refuses PASS when a contract exists but `.planning/evidence/phase-{N}-contract-run.json` is missing/failing, or when the verification report contains `INSUFFICIENT EVIDENCE`.
|
|
206
213
|
|
|
207
214
|
```bash
|
|
208
215
|
node ${QUALIA_BIN}/state.js transition --to verified --phase {N} --verification {pass|fail} --evidence .planning/evals/harness-eval-*.json
|
package/tests/bin.test.sh
CHANGED
|
@@ -495,8 +495,8 @@ fi
|
|
|
495
495
|
# usage-capture added in v6.9.1 — UserPromptSubmit telemetry capture;
|
|
496
496
|
# task-write-guard added in v6.13 — R1 runtime plan-contract file-scope guard)
|
|
497
497
|
HOOK_COUNT=$(ls "$TMP/.claude/hooks/"*.js 2>/dev/null | wc -l)
|
|
498
|
-
if [ "$HOOK_COUNT" -eq
|
|
499
|
-
pass "
|
|
498
|
+
if [ "$HOOK_COUNT" -eq 16 ]; then
|
|
499
|
+
pass "16 hooks installed in hooks/ (incl. task-write-guard + secret-guard)"
|
|
500
500
|
else
|
|
501
501
|
fail_case "hook count" "got $HOOK_COUNT"
|
|
502
502
|
fi
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Qualia Framework — bin/dep-verify.mjs behavior tests
|
|
3
|
+
# Verifies the hallucinated/slopsquatted-dependency gate catches what it claims:
|
|
4
|
+
# imports that are neither declared in package.json nor installed in node_modules.
|
|
5
|
+
#
|
|
6
|
+
# Run: bash tests/dep-verify.test.sh
|
|
7
|
+
|
|
8
|
+
PASS=0
|
|
9
|
+
FAIL=0
|
|
10
|
+
DEP_VERIFY="$(cd "$(dirname "$0")/../bin" && pwd)/dep-verify.mjs"
|
|
11
|
+
NODE="${NODE:-node}"
|
|
12
|
+
|
|
13
|
+
TMP_DIRS=()
|
|
14
|
+
cleanup() {
|
|
15
|
+
for d in "${TMP_DIRS[@]}"; do
|
|
16
|
+
[ -d "$d" ] && rm -rf "$d"
|
|
17
|
+
done
|
|
18
|
+
}
|
|
19
|
+
trap cleanup EXIT
|
|
20
|
+
|
|
21
|
+
mktmp() {
|
|
22
|
+
local TMP
|
|
23
|
+
TMP=$(mktemp -d)
|
|
24
|
+
TMP_DIRS+=("$TMP")
|
|
25
|
+
echo "$TMP"
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
pass() { echo " ✓ $1"; PASS=$((PASS + 1)); }
|
|
29
|
+
fail_case() { echo " ✗ $1"; echo " $2"; FAIL=$((FAIL + 1)); }
|
|
30
|
+
|
|
31
|
+
echo "dep-verify.test.sh — bin/dep-verify.mjs behavioral tests"
|
|
32
|
+
echo ""
|
|
33
|
+
|
|
34
|
+
# ── Sanity: file exists and parses ────────────────────────────────────
|
|
35
|
+
if [ ! -f "$DEP_VERIFY" ]; then
|
|
36
|
+
fail_case "dep-verify exists" "$DEP_VERIFY not found"
|
|
37
|
+
echo "=== Results: $PASS passed, $FAIL failed ==="
|
|
38
|
+
exit 1
|
|
39
|
+
fi
|
|
40
|
+
pass "dep-verify.mjs exists at expected path"
|
|
41
|
+
|
|
42
|
+
if $NODE --check "$DEP_VERIFY" 2>/dev/null; then
|
|
43
|
+
pass "dep-verify.mjs syntax is valid"
|
|
44
|
+
else
|
|
45
|
+
fail_case "syntax check" "node --check failed on $DEP_VERIFY"
|
|
46
|
+
fi
|
|
47
|
+
|
|
48
|
+
# ── Declared dependency: should pass (exit 0) ─────────────────────────
|
|
49
|
+
TMP=$(mktmp)
|
|
50
|
+
cat > "$TMP/package.json" <<'EOF'
|
|
51
|
+
{ "name": "fixture", "dependencies": { "react": "^19.0.0" } }
|
|
52
|
+
EOF
|
|
53
|
+
cat > "$TMP/declared.tsx" <<'EOF'
|
|
54
|
+
import { useState } from 'react';
|
|
55
|
+
import { thing } from './local-module';
|
|
56
|
+
import fs from 'node:fs';
|
|
57
|
+
export const x = useState;
|
|
58
|
+
EOF
|
|
59
|
+
EXIT_CODE=0
|
|
60
|
+
$NODE "$DEP_VERIFY" "$TMP/declared.tsx" >/dev/null 2>&1 || EXIT_CODE=$?
|
|
61
|
+
if [ "$EXIT_CODE" = "0" ]; then
|
|
62
|
+
pass "exits 0 when imports are declared / relative / builtin"
|
|
63
|
+
else
|
|
64
|
+
fail_case "declared deps" "expected exit 0, got $EXIT_CODE"
|
|
65
|
+
fi
|
|
66
|
+
|
|
67
|
+
# ── Hallucinated package: undeclared AND not installed → exit 1 ────────
|
|
68
|
+
TMP2=$(mktmp)
|
|
69
|
+
cat > "$TMP2/package.json" <<'EOF'
|
|
70
|
+
{ "name": "fixture", "dependencies": { "react": "^19.0.0" } }
|
|
71
|
+
EOF
|
|
72
|
+
cat > "$TMP2/bad.tsx" <<'EOF'
|
|
73
|
+
import { toast } from 'react-use-magic-toast-9000';
|
|
74
|
+
export const t = toast;
|
|
75
|
+
EOF
|
|
76
|
+
EXIT_CODE=0
|
|
77
|
+
OUT=$($NODE "$DEP_VERIFY" "$TMP2/bad.tsx" 2>&1) || EXIT_CODE=$?
|
|
78
|
+
if [ "$EXIT_CODE" = "1" ] && echo "$OUT" | grep -q "react-use-magic-toast-9000"; then
|
|
79
|
+
pass "exits 1 and names the hallucinated package"
|
|
80
|
+
else
|
|
81
|
+
fail_case "hallucinated dep" "exit=$EXIT_CODE out=$(echo "$OUT" | head -c 120)"
|
|
82
|
+
fi
|
|
83
|
+
|
|
84
|
+
# ── Installed-but-undeclared (phantom): should NOT flag ───────────────
|
|
85
|
+
TMP3=$(mktmp)
|
|
86
|
+
cat > "$TMP3/package.json" <<'EOF'
|
|
87
|
+
{ "name": "fixture", "dependencies": {} }
|
|
88
|
+
EOF
|
|
89
|
+
mkdir -p "$TMP3/node_modules/left-pad"
|
|
90
|
+
cat > "$TMP3/uses-installed.js" <<'EOF'
|
|
91
|
+
const lp = require('left-pad');
|
|
92
|
+
module.exports = lp;
|
|
93
|
+
EOF
|
|
94
|
+
EXIT_CODE=0
|
|
95
|
+
$NODE "$DEP_VERIFY" "$TMP3/uses-installed.js" >/dev/null 2>&1 || EXIT_CODE=$?
|
|
96
|
+
if [ "$EXIT_CODE" = "0" ]; then
|
|
97
|
+
pass "does NOT flag a package that is installed in node_modules (low false positives)"
|
|
98
|
+
else
|
|
99
|
+
fail_case "installed phantom" "expected exit 0, got $EXIT_CODE"
|
|
100
|
+
fi
|
|
101
|
+
|
|
102
|
+
# ── tsconfig path alias should be ignored ─────────────────────────────
|
|
103
|
+
TMP4=$(mktmp)
|
|
104
|
+
cat > "$TMP4/package.json" <<'EOF'
|
|
105
|
+
{ "name": "fixture", "dependencies": {} }
|
|
106
|
+
EOF
|
|
107
|
+
cat > "$TMP4/tsconfig.json" <<'EOF'
|
|
108
|
+
{ "compilerOptions": { "paths": { "@app/*": ["src/*"] } } }
|
|
109
|
+
EOF
|
|
110
|
+
cat > "$TMP4/alias.tsx" <<'EOF'
|
|
111
|
+
import { Button } from '@app/components/button';
|
|
112
|
+
export const B = Button;
|
|
113
|
+
EOF
|
|
114
|
+
EXIT_CODE=0
|
|
115
|
+
$NODE "$DEP_VERIFY" "$TMP4/alias.tsx" >/dev/null 2>&1 || EXIT_CODE=$?
|
|
116
|
+
if [ "$EXIT_CODE" = "0" ]; then
|
|
117
|
+
pass "ignores tsconfig path aliases (@app/*)"
|
|
118
|
+
else
|
|
119
|
+
fail_case "path alias" "expected exit 0, got $EXIT_CODE"
|
|
120
|
+
fi
|
|
121
|
+
|
|
122
|
+
# ── Commented-out import must not trigger ─────────────────────────────
|
|
123
|
+
TMP5=$(mktmp)
|
|
124
|
+
cat > "$TMP5/package.json" <<'EOF'
|
|
125
|
+
{ "name": "fixture", "dependencies": {} }
|
|
126
|
+
EOF
|
|
127
|
+
cat > "$TMP5/commented.ts" <<'EOF'
|
|
128
|
+
// import { x } from 'definitely-not-real-pkg';
|
|
129
|
+
export const y = 1;
|
|
130
|
+
EOF
|
|
131
|
+
EXIT_CODE=0
|
|
132
|
+
$NODE "$DEP_VERIFY" "$TMP5/commented.ts" >/dev/null 2>&1 || EXIT_CODE=$?
|
|
133
|
+
if [ "$EXIT_CODE" = "0" ]; then
|
|
134
|
+
pass "ignores commented-out imports"
|
|
135
|
+
else
|
|
136
|
+
fail_case "commented import" "expected exit 0, got $EXIT_CODE"
|
|
137
|
+
fi
|
|
138
|
+
|
|
139
|
+
# ── String/regex awareness: import sharing a line with a // -bearing
|
|
140
|
+
# string or regex literal must STILL be caught (no silent false negative) ──
|
|
141
|
+
TMP6=$(mktmp)
|
|
142
|
+
cat > "$TMP6/package.json" <<'EOF'
|
|
143
|
+
{ "name": "fixture", "dependencies": {} }
|
|
144
|
+
EOF
|
|
145
|
+
cat > "$TMP6/sameline.ts" <<'EOF'
|
|
146
|
+
const re = /url:\/\//; import { x } from 'hallucinated-sameline';
|
|
147
|
+
EOF
|
|
148
|
+
EXIT_CODE=0
|
|
149
|
+
OUT=$($NODE "$DEP_VERIFY" "$TMP6/sameline.ts" 2>&1) || EXIT_CODE=$?
|
|
150
|
+
if [ "$EXIT_CODE" = "1" ] && echo "$OUT" | grep -q "hallucinated-sameline"; then
|
|
151
|
+
pass "catches an import on the same line as a regex literal (no false negative)"
|
|
152
|
+
else
|
|
153
|
+
fail_case "regex-sameline FN" "exit=$EXIT_CODE out=$(echo "$OUT" | head -c 120)"
|
|
154
|
+
fi
|
|
155
|
+
|
|
156
|
+
# ── Import syntax INSIDE a string literal must NOT be flagged (no FP) ──
|
|
157
|
+
TMP7=$(mktmp)
|
|
158
|
+
cat > "$TMP7/package.json" <<'EOF'
|
|
159
|
+
{ "name": "fixture", "dependencies": {} }
|
|
160
|
+
EOF
|
|
161
|
+
cat > "$TMP7/strlit.ts" <<'EOF'
|
|
162
|
+
export const example = "import { fake } from 'made-up-package'";
|
|
163
|
+
EOF
|
|
164
|
+
EXIT_CODE=0
|
|
165
|
+
$NODE "$DEP_VERIFY" "$TMP7/strlit.ts" >/dev/null 2>&1 || EXIT_CODE=$?
|
|
166
|
+
if [ "$EXIT_CODE" = "0" ]; then
|
|
167
|
+
pass "does NOT flag import syntax inside a string literal (no false positive)"
|
|
168
|
+
else
|
|
169
|
+
fail_case "string-literal FP" "expected exit 0, got $EXIT_CODE"
|
|
170
|
+
fi
|
|
171
|
+
|
|
172
|
+
# ── Scoped hallucinated package → flagged and named ───────────────────
|
|
173
|
+
TMP8=$(mktmp)
|
|
174
|
+
cat > "$TMP8/package.json" <<'EOF'
|
|
175
|
+
{ "name": "fixture", "dependencies": {} }
|
|
176
|
+
EOF
|
|
177
|
+
cat > "$TMP8/scoped.tsx" <<'EOF'
|
|
178
|
+
import { thing } from '@hallucinated-scope/nonexistent-pkg';
|
|
179
|
+
export const t = thing;
|
|
180
|
+
EOF
|
|
181
|
+
EXIT_CODE=0
|
|
182
|
+
OUT=$($NODE "$DEP_VERIFY" "$TMP8/scoped.tsx" 2>&1) || EXIT_CODE=$?
|
|
183
|
+
if [ "$EXIT_CODE" = "1" ] && echo "$OUT" | grep -q "@hallucinated-scope/nonexistent-pkg"; then
|
|
184
|
+
pass "flags a scoped hallucinated package (@scope/name)"
|
|
185
|
+
else
|
|
186
|
+
fail_case "scoped dep" "exit=$EXIT_CODE out=$(echo "$OUT" | head -c 120)"
|
|
187
|
+
fi
|
|
188
|
+
|
|
189
|
+
# ── Protocol specifier (virtual:/https:) must NOT be flagged ──────────
|
|
190
|
+
TMP9=$(mktmp)
|
|
191
|
+
cat > "$TMP9/package.json" <<'EOF'
|
|
192
|
+
{ "name": "fixture", "dependencies": {} }
|
|
193
|
+
EOF
|
|
194
|
+
cat > "$TMP9/proto.ts" <<'EOF'
|
|
195
|
+
import config from 'virtual:generated-config';
|
|
196
|
+
export const c = config;
|
|
197
|
+
EOF
|
|
198
|
+
EXIT_CODE=0
|
|
199
|
+
$NODE "$DEP_VERIFY" "$TMP9/proto.ts" >/dev/null 2>&1 || EXIT_CODE=$?
|
|
200
|
+
if [ "$EXIT_CODE" = "0" ]; then
|
|
201
|
+
pass "ignores protocol specifiers (virtual:, https:)"
|
|
202
|
+
else
|
|
203
|
+
fail_case "protocol specifier" "expected exit 0, got $EXIT_CODE"
|
|
204
|
+
fi
|
|
205
|
+
|
|
206
|
+
# ── Directory scan: flags src/, skips node_modules/ (production invocation) ──
|
|
207
|
+
TMPD=$(mktmp)
|
|
208
|
+
cat > "$TMPD/package.json" <<'EOF'
|
|
209
|
+
{ "name": "fixture", "dependencies": { "react": "^19.0.0" } }
|
|
210
|
+
EOF
|
|
211
|
+
mkdir -p "$TMPD/src" "$TMPD/node_modules/some-pkg"
|
|
212
|
+
echo "import { useState } from 'react';" > "$TMPD/src/good.ts"
|
|
213
|
+
echo "import { z } from 'ghost-pkg-in-src';" > "$TMPD/src/bad.ts"
|
|
214
|
+
echo "import { q } from 'ghost-inside-node-modules';" > "$TMPD/node_modules/some-pkg/index.ts"
|
|
215
|
+
EXIT_CODE=0
|
|
216
|
+
OUT=$($NODE "$DEP_VERIFY" "$TMPD" 2>&1) || EXIT_CODE=$?
|
|
217
|
+
if [ "$EXIT_CODE" = "1" ] && echo "$OUT" | grep -q "ghost-pkg-in-src" && ! echo "$OUT" | grep -q "ghost-inside-node-modules"; then
|
|
218
|
+
pass "directory scan flags src/ and skips node_modules/"
|
|
219
|
+
else
|
|
220
|
+
fail_case "directory scan" "exit=$EXIT_CODE out=$(echo "$OUT" | head -c 160)"
|
|
221
|
+
fi
|
|
222
|
+
|
|
223
|
+
# ── --json flag produces JSON output (exit 1 + ok:false on findings) ──
|
|
224
|
+
EXIT_CODE=0
|
|
225
|
+
JSON_OUT=$($NODE "$DEP_VERIFY" --json "$TMP2/bad.tsx" 2>/dev/null) || EXIT_CODE=$?
|
|
226
|
+
if [ "$EXIT_CODE" = "1" ] \
|
|
227
|
+
&& echo "$JSON_OUT" | head -1 | grep -qE "^[\{\[]" \
|
|
228
|
+
&& echo "$JSON_OUT" | grep -q '"findings"' \
|
|
229
|
+
&& echo "$JSON_OUT" | grep -q '"ok": false'; then
|
|
230
|
+
pass "--json produces JSON with ok:false and exits 1 on findings"
|
|
231
|
+
else
|
|
232
|
+
fail_case "--json output" "exit=$EXIT_CODE first line: '$(echo "$JSON_OUT" | head -c 80)'"
|
|
233
|
+
fi
|
|
234
|
+
|
|
235
|
+
# ── Missing path → invocation error (exit 2) ──────────────────────────
|
|
236
|
+
EXIT_CODE=0
|
|
237
|
+
$NODE "$DEP_VERIFY" /nonexistent/path/xyz >/dev/null 2>&1 || EXIT_CODE=$?
|
|
238
|
+
if [ "$EXIT_CODE" = "2" ]; then
|
|
239
|
+
pass "exits 2 on a missing path (invocation error)"
|
|
240
|
+
else
|
|
241
|
+
fail_case "missing path" "expected exit 2, got $EXIT_CODE"
|
|
242
|
+
fi
|
|
243
|
+
|
|
244
|
+
echo ""
|
|
245
|
+
echo "=== Results: $PASS passed, $FAIL failed ==="
|
|
246
|
+
|
|
247
|
+
[ "$FAIL" = "0" ]
|
package/tests/hooks.test.sh
CHANGED
|
@@ -494,6 +494,103 @@ else
|
|
|
494
494
|
fi
|
|
495
495
|
rm -rf "$TMP" "$QH"
|
|
496
496
|
|
|
497
|
+
# --- pre-deploy-gate: dependency-verification gate (dep-verify.mjs wired into ship) ---
|
|
498
|
+
# Build a tmp QUALIA_HOME carrying bin/dep-verify.mjs so the gate can find it.
|
|
499
|
+
DEP_SRC="$(cd "$(dirname "$0")/../bin" && pwd)/dep-verify.mjs"
|
|
500
|
+
|
|
501
|
+
# Hallucinated import (undeclared + not installed) → blocked with diagnostic
|
|
502
|
+
TMP=$(mktemp -d)
|
|
503
|
+
QH=$(mktemp -d)
|
|
504
|
+
mkdir -p "$QH/bin" "$TMP/app"
|
|
505
|
+
cp "$DEP_SRC" "$QH/bin/dep-verify.mjs"
|
|
506
|
+
echo '{"name":"x","dependencies":{}}' > "$TMP/package.json"
|
|
507
|
+
echo "import { z } from 'totally-made-up-pkg-xyz';" > "$TMP/app/page.tsx"
|
|
508
|
+
OUT=$( (cd "$TMP" && QUALIA_HOME="$QH" $NODE "$HOOKS_DIR/pre-deploy-gate.js") 2>&1 )
|
|
509
|
+
RC=$?
|
|
510
|
+
if [ "$RC" -eq 2 ] && echo "$OUT" | grep -qi "hallucinated"; then
|
|
511
|
+
echo " ✓ hallucinated import → blocked by dependency gate (exit 2)"
|
|
512
|
+
PASS=$((PASS + 1))
|
|
513
|
+
else
|
|
514
|
+
echo " ✗ hallucinated import → dependency block (exit=$RC out=$OUT)"
|
|
515
|
+
FAIL=$((FAIL + 1))
|
|
516
|
+
fi
|
|
517
|
+
rm -rf "$TMP" "$QH"
|
|
518
|
+
|
|
519
|
+
# Clean file (no external imports) → dependency gate passes → exit 0
|
|
520
|
+
TMP=$(mktemp -d)
|
|
521
|
+
QH=$(mktemp -d)
|
|
522
|
+
mkdir -p "$QH/bin" "$TMP/app"
|
|
523
|
+
cp "$DEP_SRC" "$QH/bin/dep-verify.mjs"
|
|
524
|
+
echo '{"name":"x","dependencies":{}}' > "$TMP/package.json"
|
|
525
|
+
echo 'export default function P(){return null;}' > "$TMP/app/page.tsx"
|
|
526
|
+
OUT=$( (cd "$TMP" && QUALIA_HOME="$QH" $NODE "$HOOKS_DIR/pre-deploy-gate.js") 2>&1 )
|
|
527
|
+
RC=$?
|
|
528
|
+
if [ "$RC" -eq 0 ] && echo "$OUT" | grep -q "Dependencies"; then
|
|
529
|
+
echo " ✓ clean imports → dependency gate passes (exit 0)"
|
|
530
|
+
PASS=$((PASS + 1))
|
|
531
|
+
else
|
|
532
|
+
echo " ✗ clean imports → dependency gate passes (exit=$RC out=$OUT)"
|
|
533
|
+
FAIL=$((FAIL + 1))
|
|
534
|
+
fi
|
|
535
|
+
rm -rf "$TMP" "$QH"
|
|
536
|
+
|
|
537
|
+
# QUALIA_SKIP_DEPCHECK=1 by non-OWNER → blocked (OWNER-only escape)
|
|
538
|
+
TMP=$(mktemp -d)
|
|
539
|
+
QH=$(mktemp -d)
|
|
540
|
+
mkdir -p "$QH/bin" "$TMP/app"
|
|
541
|
+
cp "$DEP_SRC" "$QH/bin/dep-verify.mjs"
|
|
542
|
+
echo '{"role":"EMPLOYEE"}' > "$QH/.qualia-config.json"
|
|
543
|
+
echo '{"name":"x","dependencies":{}}' > "$TMP/package.json"
|
|
544
|
+
echo "import { z } from 'totally-made-up-pkg-xyz';" > "$TMP/app/page.tsx"
|
|
545
|
+
OUT=$( (cd "$TMP" && QUALIA_SKIP_DEPCHECK=1 QUALIA_HOME="$QH" $NODE "$HOOKS_DIR/pre-deploy-gate.js") 2>&1 )
|
|
546
|
+
RC=$?
|
|
547
|
+
if [ "$RC" -eq 2 ] && echo "$OUT" | grep -q "OWNER-only"; then
|
|
548
|
+
echo " ✓ QUALIA_SKIP_DEPCHECK by non-OWNER → blocked (OWNER-only)"
|
|
549
|
+
PASS=$((PASS + 1))
|
|
550
|
+
else
|
|
551
|
+
echo " ✗ QUALIA_SKIP_DEPCHECK non-OWNER block (exit=$RC out=$OUT)"
|
|
552
|
+
FAIL=$((FAIL + 1))
|
|
553
|
+
fi
|
|
554
|
+
rm -rf "$TMP" "$QH"
|
|
555
|
+
|
|
556
|
+
# QUALIA_SKIP_DEPCHECK=1 by OWNER → allowed through (the escape valve works)
|
|
557
|
+
TMP=$(mktemp -d)
|
|
558
|
+
QH=$(mktemp -d)
|
|
559
|
+
mkdir -p "$QH/bin" "$TMP/app"
|
|
560
|
+
cp "$DEP_SRC" "$QH/bin/dep-verify.mjs"
|
|
561
|
+
echo '{"role":"OWNER"}' > "$QH/.qualia-config.json"
|
|
562
|
+
echo '{"name":"x","dependencies":{}}' > "$TMP/package.json"
|
|
563
|
+
echo "import { z } from 'totally-made-up-pkg-xyz';" > "$TMP/app/page.tsx"
|
|
564
|
+
OUT=$( (cd "$TMP" && QUALIA_SKIP_DEPCHECK=1 QUALIA_HOME="$QH" $NODE "$HOOKS_DIR/pre-deploy-gate.js") 2>&1 )
|
|
565
|
+
RC=$?
|
|
566
|
+
if [ "$RC" -eq 0 ] && echo "$OUT" | grep -qi "Dependency check skipped"; then
|
|
567
|
+
echo " ✓ QUALIA_SKIP_DEPCHECK by OWNER → allowed (escape valve works)"
|
|
568
|
+
PASS=$((PASS + 1))
|
|
569
|
+
else
|
|
570
|
+
echo " ✗ QUALIA_SKIP_DEPCHECK OWNER allow (exit=$RC out=$OUT)"
|
|
571
|
+
FAIL=$((FAIL + 1))
|
|
572
|
+
fi
|
|
573
|
+
rm -rf "$TMP" "$QH"
|
|
574
|
+
|
|
575
|
+
# Fail-closed: a crashing/non-completing dep-verify must BLOCK, not pass
|
|
576
|
+
TMP=$(mktemp -d)
|
|
577
|
+
QH=$(mktemp -d)
|
|
578
|
+
mkdir -p "$QH/bin" "$TMP/app"
|
|
579
|
+
# A stub scanner that exits 2 (invocation error) — must be treated as FAIL.
|
|
580
|
+
printf '#!/usr/bin/env node\nprocess.exit(2);\n' > "$QH/bin/dep-verify.mjs"
|
|
581
|
+
echo '{"name":"x","dependencies":{}}' > "$TMP/package.json"
|
|
582
|
+
echo 'export default function P(){return null;}' > "$TMP/app/page.tsx"
|
|
583
|
+
OUT=$( (cd "$TMP" && QUALIA_HOME="$QH" $NODE "$HOOKS_DIR/pre-deploy-gate.js") 2>&1 )
|
|
584
|
+
RC=$?
|
|
585
|
+
if [ "$RC" -eq 2 ] && echo "$OUT" | grep -qi "did not complete"; then
|
|
586
|
+
echo " ✓ dep-verify non-completion → blocked, not silently passed (fail-closed)"
|
|
587
|
+
PASS=$((PASS + 1))
|
|
588
|
+
else
|
|
589
|
+
echo " ✗ dep-verify fail-closed (exit=$RC out=$OUT)"
|
|
590
|
+
FAIL=$((FAIL + 1))
|
|
591
|
+
fi
|
|
592
|
+
rm -rf "$TMP" "$QH"
|
|
593
|
+
|
|
497
594
|
# Scanner absent (older install) → gate skips silently → exit 0
|
|
498
595
|
TMP=$(mktemp -d)
|
|
499
596
|
QH=$(mktemp -d)
|
|
@@ -124,12 +124,13 @@ else
|
|
|
124
124
|
fi
|
|
125
125
|
|
|
126
126
|
if [ -d "$HOME_DIR/.claude/hooks" ] \
|
|
127
|
-
&& [ "$(find "$HOME_DIR/.claude/hooks" -maxdepth 1 -name '*.js' | wc -l | tr -d ' ')" = "
|
|
127
|
+
&& [ "$(find "$HOME_DIR/.claude/hooks" -maxdepth 1 -name '*.js' | wc -l | tr -d ' ')" = "16" ] \
|
|
128
128
|
&& [ -f "$HOME_DIR/.claude/hooks/fawzi-approval-guard.js" ] \
|
|
129
129
|
&& [ -f "$HOME_DIR/.claude/hooks/pre-compact.js" ] \
|
|
130
130
|
&& [ -f "$HOME_DIR/.claude/hooks/usage-capture.js" ] \
|
|
131
|
-
&& [ -f "$HOME_DIR/.claude/hooks/task-write-guard.js" ]
|
|
132
|
-
|
|
131
|
+
&& [ -f "$HOME_DIR/.claude/hooks/task-write-guard.js" ] \
|
|
132
|
+
&& [ -f "$HOME_DIR/.claude/hooks/secret-guard.js" ]; then
|
|
133
|
+
pass "packaged install has 16 hooks including task-write-guard + secret-guard"
|
|
133
134
|
else
|
|
134
135
|
fail_case "packaged hook set mismatch"
|
|
135
136
|
fi
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# journey-spine.test.sh — lifecycle cosmetic layer (bin/qualia-ui.js spine/onboard/
|
|
3
|
+
# phase-complete/clockout + barTicks + statusline progress bar).
|
|
4
|
+
# Run: bash tests/journey-spine.test.sh
|
|
5
|
+
|
|
6
|
+
PASS=0
|
|
7
|
+
FAIL=0
|
|
8
|
+
BIN_DIR="$(cd "$(dirname "$0")/../bin" && pwd)"
|
|
9
|
+
NODE="${NODE:-node}"
|
|
10
|
+
UI="$BIN_DIR/qualia-ui.js"
|
|
11
|
+
STATE="$BIN_DIR/state.js"
|
|
12
|
+
STATUSLINE="$BIN_DIR/statusline.js"
|
|
13
|
+
|
|
14
|
+
# strip ANSI for column-accurate assertions
|
|
15
|
+
strip() { sed 's/\x1b\[[0-9;]*m//g'; }
|
|
16
|
+
|
|
17
|
+
assert_contains() { if echo "$2" | strip | grep -qF -- "$3"; then echo " ✓ $1"; PASS=$((PASS+1)); else echo " ✗ $1 (missing '$3')"; FAIL=$((FAIL+1)); fi; }
|
|
18
|
+
assert_not_contains() { if echo "$2" | strip | grep -qF -- "$3"; then echo " ✗ $1 (should not contain '$3')"; FAIL=$((FAIL+1)); else echo " ✓ $1"; PASS=$((PASS+1)); fi; }
|
|
19
|
+
|
|
20
|
+
echo "journey-spine.test.sh — lifecycle cosmetic layer"
|
|
21
|
+
echo ""
|
|
22
|
+
|
|
23
|
+
$NODE -c "$UI" 2>/dev/null && { echo " ✓ qualia-ui syntax valid"; PASS=$((PASS+1)); } || { echo " ✗ qualia-ui syntax invalid"; FAIL=$((FAIL+1)); }
|
|
24
|
+
|
|
25
|
+
# ── barTicks: scaling + clamping ──
|
|
26
|
+
TICKS=$($NODE -e 'const u=require(process.argv[1]); console.log(["a"+u.barTicks(4,5),"b"+u.barTicks(0,5),"c"+u.barTicks(5,5),"d"+u.barTicks(9,5),"e"+u.barTicks(2,0)].join("\n"))' "$UI" | strip)
|
|
27
|
+
assert_contains "barTicks 4/5 → 4 filled" "$TICKS" "a▰▰▰▰▱"
|
|
28
|
+
assert_contains "barTicks 0/5 → all empty" "$TICKS" "b▱▱▱▱▱"
|
|
29
|
+
assert_contains "barTicks 5/5 → all filled" "$TICKS" "c▰▰▰▰▰"
|
|
30
|
+
assert_contains "barTicks clamps over-fill to 5" "$TICKS" "d▰▰▰▰▰"
|
|
31
|
+
assert_contains "barTicks total<1 → empty string" "$TICKS" "e"
|
|
32
|
+
|
|
33
|
+
# ── onboard card ──
|
|
34
|
+
ONB=$($NODE "$UI" onboard "Maria" 2>&1)
|
|
35
|
+
assert_contains "onboard greets by name" "$ONB" "welcome aboard, Maria"
|
|
36
|
+
assert_contains "onboard shows milestone flow" "$ONB" "Plan"
|
|
37
|
+
assert_contains "onboard shows the first move" "$ONB" "/qualia"
|
|
38
|
+
|
|
39
|
+
# ── phase-complete card ──
|
|
40
|
+
PC=$($NODE "$UI" phase-complete 3 "Checkout flow" tasks=6 mdone=4 mtotal=5 streak=3 next=/qualia-build nextname="Order confirmation" 2>&1)
|
|
41
|
+
assert_contains "phase-complete shows phase + name" "$PC" "PHASE 3 COMPLETE"
|
|
42
|
+
assert_contains "phase-complete shows task count" "$PC" "6 tasks"
|
|
43
|
+
assert_contains "phase-complete shows milestone bar" "$PC" "4/5 phases"
|
|
44
|
+
assert_contains "phase-complete 'one more' at 4/5" "$PC" "one more to close the milestone"
|
|
45
|
+
assert_contains "phase-complete shows streak" "$PC" "3 phases shipped today"
|
|
46
|
+
assert_contains "phase-complete shows next command" "$PC" "/qualia-build"
|
|
47
|
+
# streak of 1 must NOT render (not a streak)
|
|
48
|
+
PC1=$($NODE "$UI" phase-complete 1 "Cart" streak=1 2>&1)
|
|
49
|
+
assert_not_contains "phase-complete hides streak=1" "$PC1" "shipped today"
|
|
50
|
+
# milestone complete wording at full
|
|
51
|
+
PCFULL=$($NODE "$UI" phase-complete 5 "Polish" mdone=5 mtotal=5 2>&1)
|
|
52
|
+
assert_contains "phase-complete 'ready to close' at 5/5" "$PCFULL" "milestone ready to close"
|
|
53
|
+
|
|
54
|
+
# ── clockout card ──
|
|
55
|
+
CO=$($NODE "$UI" clockout "Maria" date="Thu Jun 25" phases=3 tasks=14 commits=9 mdone=4 mtotal=5 streak=4 erp=ok 2>&1)
|
|
56
|
+
assert_contains "clockout names the employee" "$CO" "CLOCK OUT"
|
|
57
|
+
assert_contains "clockout shows shipped phases" "$CO" "3 phases"
|
|
58
|
+
assert_contains "clockout shows commits" "$CO" "9 commits"
|
|
59
|
+
assert_contains "clockout shows milestone %" "$CO" "80%"
|
|
60
|
+
assert_contains "clockout shows streak" "$CO" "4 days"
|
|
61
|
+
assert_contains "clockout confirms ERP upload" "$CO" "uploaded to ERP"
|
|
62
|
+
CO_Q=$($NODE "$UI" clockout "Maria" erp=queued 2>&1)
|
|
63
|
+
assert_contains "clockout queued state" "$CO_Q" "queued"
|
|
64
|
+
|
|
65
|
+
# ── spine: needs a multi-milestone project fixture ──
|
|
66
|
+
TMP=$(mktemp -d)
|
|
67
|
+
(
|
|
68
|
+
cd "$TMP"
|
|
69
|
+
$NODE "$STATE" init --project "acme-portal" \
|
|
70
|
+
--phases '[{"name":"Cart","goal":"x"},{"name":"Checkout","goal":"y"},{"name":"Receipts","goal":"z"}]' >/dev/null 2>&1
|
|
71
|
+
mkdir -p .planning
|
|
72
|
+
cat > .planning/JOURNEY.md <<'JEOF'
|
|
73
|
+
---
|
|
74
|
+
project: "acme-portal"
|
|
75
|
+
---
|
|
76
|
+
## Milestone 1 · Foundations
|
|
77
|
+
1. **Auth**
|
|
78
|
+
## Milestone 2 · Commerce
|
|
79
|
+
1. **Cart**
|
|
80
|
+
## Milestone 3 · Growth
|
|
81
|
+
1. **Referrals**
|
|
82
|
+
## Milestone 4 · Handoff
|
|
83
|
+
1. **Docs**
|
|
84
|
+
JEOF
|
|
85
|
+
)
|
|
86
|
+
SPINE=$(cd "$TMP" && $NODE "$UI" spine 2>&1)
|
|
87
|
+
assert_contains "spine renders the Journey label" "$SPINE" "Journey"
|
|
88
|
+
assert_contains "spine renders all 4 milestones" "$SPINE" "M4"
|
|
89
|
+
assert_contains "spine shows current marker ◆" "$SPINE" "◆"
|
|
90
|
+
assert_contains "spine shows 'you are here'" "$SPINE" "you are here"
|
|
91
|
+
|
|
92
|
+
# caret column == current-marker column (exact alignment)
|
|
93
|
+
ALIGN=$(cd "$TMP" && $NODE "$UI" spine 2>&1 | strip | $NODE -e '
|
|
94
|
+
let s=""; process.stdin.on("data",d=>s+=d).on("end",()=>{
|
|
95
|
+
const lines=s.split("\n").filter(l=>l.length);
|
|
96
|
+
const spine=lines.find(l=>l.includes("Journey"));
|
|
97
|
+
const caret=lines.find(l=>l.includes("you are here"));
|
|
98
|
+
if(!spine||!caret){console.log("NOLINES");return;}
|
|
99
|
+
const markerCol=spine.indexOf("◆");
|
|
100
|
+
const caretCol=caret.indexOf("↑");
|
|
101
|
+
console.log(markerCol===caretCol?"ALIGNED":("MISALIGNED "+markerCol+" vs "+caretCol));
|
|
102
|
+
});
|
|
103
|
+
')
|
|
104
|
+
assert_contains "spine caret aligns under current marker" "$ALIGN" "ALIGNED"
|
|
105
|
+
|
|
106
|
+
# spine self-skips with <2 milestones (single-milestone project)
|
|
107
|
+
TMP2=$(mktemp -d)
|
|
108
|
+
( cd "$TMP2" && $NODE "$STATE" init --project "solo" --phases '[{"name":"A","goal":"x"}]' >/dev/null 2>&1 )
|
|
109
|
+
SPINE2=$(cd "$TMP2" && $NODE "$UI" spine 2>&1)
|
|
110
|
+
assert_not_contains "spine self-skips single-milestone project" "$SPINE2" "you are here"
|
|
111
|
+
|
|
112
|
+
# spine outside any project → no crash, no output
|
|
113
|
+
SPINE3=$(cd / && $NODE "$UI" spine 2>&1)
|
|
114
|
+
assert_not_contains "spine no-ops outside a project" "$SPINE3" "Journey"
|
|
115
|
+
|
|
116
|
+
# ── statusline: the always-visible progress bar ──
|
|
117
|
+
TMP3=$(mktemp -d); mkdir -p "$TMP3/.planning"
|
|
118
|
+
cat > "$TMP3/.planning/tracking.json" <<'TEOF'
|
|
119
|
+
{"phase":3,"total_phases":5,"status":"building","milestone":2,"milestone_name":"Commerce","tasks_done":2,"tasks_total":6,"blockers":[]}
|
|
120
|
+
TEOF
|
|
121
|
+
SL=$(printf '{"workspace":{"current_dir":"%s"},"cost":{"total_cost_usd":0},"duration_ms":0}' "$TMP3" | $NODE "$STATUSLINE" 2>&1)
|
|
122
|
+
assert_contains "statusline shows P3/5" "$SL" "P3/5"
|
|
123
|
+
assert_contains "statusline shows the tick bar" "$SL" "▰▰▰▱▱"
|
|
124
|
+
|
|
125
|
+
# ── skill-wiring smoke: run the ACTUAL ship/report closing bash ──
|
|
126
|
+
# These mirror the exact variable plumbing in skills/qualia-ship/SKILL.md and
|
|
127
|
+
# skills/qualia-report/SKILL.md (state.js parsing + ${VAR:+…} expansions), so a
|
|
128
|
+
# regression in the wiring — not just the renderer — is caught.
|
|
129
|
+
TMP4=$(mktemp -d)
|
|
130
|
+
( cd "$TMP4" && $NODE "$STATE" init --project "wired" \
|
|
131
|
+
--phases '[{"name":"Cart","goal":"x"},{"name":"Checkout","goal":"y"},{"name":"Receipts","goal":"z"}]' >/dev/null 2>&1 )
|
|
132
|
+
|
|
133
|
+
# ---- /qualia-ship closing block (verbatim plumbing) ----
|
|
134
|
+
SHIP_OUT=$(cd "$TMP4" && {
|
|
135
|
+
PHASE_NUM=$($NODE "$STATE" check 2>/dev/null | $NODE -pe 'try{JSON.parse(require("fs").readFileSync(0)).phase||""}catch{""}')
|
|
136
|
+
PHASE_NAME=$($NODE "$STATE" check 2>/dev/null | $NODE -pe 'try{JSON.parse(require("fs").readFileSync(0)).phase_name||""}catch{""}')
|
|
137
|
+
TOTAL_PHASES=$($NODE "$STATE" check 2>/dev/null | $NODE -pe 'try{JSON.parse(require("fs").readFileSync(0)).total_phases||""}catch{""}')
|
|
138
|
+
TASKS_DONE=4
|
|
139
|
+
$NODE "$UI" phase-complete "${PHASE_NUM:-?}" "$PHASE_NAME" \
|
|
140
|
+
${TASKS_DONE:+tasks=$TASKS_DONE} \
|
|
141
|
+
${PHASE_NUM:+mdone=$PHASE_NUM} ${TOTAL_PHASES:+mtotal=$TOTAL_PHASES} \
|
|
142
|
+
next=/qualia-handoff
|
|
143
|
+
} 2>&1)
|
|
144
|
+
assert_contains "ship wiring → resolves phase number into card" "$SHIP_OUT" "PHASE 1 COMPLETE"
|
|
145
|
+
assert_contains "ship wiring → tasks plumbed through" "$SHIP_OUT" "4 tasks"
|
|
146
|
+
assert_contains "ship wiring → milestone bar plumbed" "$SHIP_OUT" "phases"
|
|
147
|
+
assert_contains "ship wiring → next command set" "$SHIP_OUT" "/qualia-handoff"
|
|
148
|
+
assert_not_contains "ship wiring → no literal \${VAR" "$SHIP_OUT" '${'
|
|
149
|
+
|
|
150
|
+
# ---- /qualia-report closing block (verbatim plumbing) ----
|
|
151
|
+
REPORT_OUT=$(cd "$TMP4" && {
|
|
152
|
+
EMP_NAME=$($NODE -pe 'try{JSON.parse(require("fs").readFileSync(process.env.HOME+"/.claude/.qualia-config.json","utf8")).installed_by||""}catch{""}' 2>/dev/null)
|
|
153
|
+
MDONE=$($NODE "$STATE" check 2>/dev/null | $NODE -pe 'try{JSON.parse(require("fs").readFileSync(0)).phase||""}catch{""}')
|
|
154
|
+
MTOTAL=$($NODE "$STATE" check 2>/dev/null | $NODE -pe 'try{JSON.parse(require("fs").readFileSync(0)).total_phases||""}catch{""}')
|
|
155
|
+
COUNT=9
|
|
156
|
+
ERP_RESULT=ok
|
|
157
|
+
$NODE "$UI" clockout "$EMP_NAME" date="2026-06-25" \
|
|
158
|
+
${COUNT:+commits=$COUNT} \
|
|
159
|
+
${MDONE:+mdone=$MDONE} ${MTOTAL:+mtotal=$MTOTAL} \
|
|
160
|
+
${ERP_RESULT:+erp=$ERP_RESULT}
|
|
161
|
+
} 2>&1)
|
|
162
|
+
assert_contains "report wiring → renders CLOCK OUT" "$REPORT_OUT" "CLOCK OUT"
|
|
163
|
+
assert_contains "report wiring → commit count plumbed" "$REPORT_OUT" "9 commits"
|
|
164
|
+
assert_contains "report wiring → milestone % plumbed" "$REPORT_OUT" "%"
|
|
165
|
+
assert_contains "report wiring → erp result plumbed" "$REPORT_OUT" "uploaded to ERP"
|
|
166
|
+
assert_not_contains "report wiring → no literal \${VAR" "$REPORT_OUT" '${'
|
|
167
|
+
|
|
168
|
+
rm -rf "$TMP" "$TMP2" "$TMP3" "$TMP4"
|
|
169
|
+
echo ""
|
|
170
|
+
echo "=== Results: $PASS passed, $FAIL failed ==="
|
|
171
|
+
[ "$FAIL" -eq 0 ]
|
package/tests/lib.test.sh
CHANGED
|
@@ -532,10 +532,10 @@ TMP=$(mktmp)
|
|
|
532
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"
|
|
533
533
|
echo '{"installed_by":"Test","role":"OWNER","version":"6.3.0","erp":{"enabled":false}}' > "$TMP/home/.claude/.qualia-config.json"
|
|
534
534
|
touch "$TMP/home/.claude/CLAUDE.md" "$TMP/home/.claude/settings.json"
|
|
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
|
|
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 dep-verify.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
|
|
536
536
|
touch "$TMP/home/.claude/bin/$f"
|
|
537
537
|
done
|
|
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
|
|
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 secret-guard.js; do
|
|
539
539
|
touch "$TMP/home/.claude/hooks/$h"
|
|
540
540
|
done
|
|
541
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"
|
|
@@ -647,10 +647,10 @@ TMP=$(mktmp)
|
|
|
647
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"
|
|
648
648
|
echo '{"installed_by":"Test","role":"OWNER","erp":{"enabled":false}}' > "$TMP/.claude/.qualia-config.json"
|
|
649
649
|
touch "$TMP/.claude/CLAUDE.md" "$TMP/.claude/settings.json"
|
|
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
|
|
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 dep-verify.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
|
|
651
651
|
touch "$TMP/.claude/bin/$f"
|
|
652
652
|
done
|
|
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
|
|
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 secret-guard.js; do
|
|
654
654
|
touch "$TMP/.claude/hooks/$h"
|
|
655
655
|
done
|
|
656
656
|
touch "$TMP/.claude/knowledge/index.md" "$TMP/.claude/knowledge/agents.md"
|
package/tests/run-all.sh
CHANGED
|
@@ -18,6 +18,9 @@ SUITES=(
|
|
|
18
18
|
"refs"
|
|
19
19
|
"install-smoke"
|
|
20
20
|
"slop-detect"
|
|
21
|
+
"dep-verify"
|
|
22
|
+
"runtime-parity"
|
|
23
|
+
"secret-guard"
|
|
21
24
|
"agent-status"
|
|
22
25
|
"analyze-gate"
|
|
23
26
|
"instructions"
|
|
@@ -32,6 +35,7 @@ SUITES=(
|
|
|
32
35
|
"repo-map"
|
|
33
36
|
"design-tokens"
|
|
34
37
|
"batch-plan"
|
|
38
|
+
"journey-spine"
|
|
35
39
|
)
|
|
36
40
|
|
|
37
41
|
FAILED=()
|