qualia-framework 7.2.1 → 7.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +20 -0
- package/.claude-plugin/plugin.json +17 -0
- package/AGENTS.md +1 -1
- package/CHANGELOG.md +56 -0
- package/CLAUDE.md +1 -1
- package/README.md +17 -4
- package/TROUBLESHOOTING.md +8 -7
- package/agents/verifier.md +1 -1
- package/bin/agent-status.js +115 -11
- package/bin/auto-report.js +15 -7
- package/bin/cli.js +173 -4
- package/bin/erp-retry.js +92 -8
- package/bin/install.js +134 -6
- package/bin/qualia-doctor.js +115 -1
- package/bin/state.js +102 -13
- package/bin/verify-panel.js +409 -0
- package/docs/onboarding.html +1 -1
- package/hooks/branch-guard.js +19 -5
- package/hooks/fawzi-approval-guard.js +16 -3
- package/hooks/hooks.json +60 -0
- package/hooks/migration-guard.js +143 -66
- package/hooks/session-start.js +27 -0
- package/package.json +3 -1
- package/skills/qualia/SKILL.md +26 -13
- package/skills/qualia-build/SKILL.md +20 -9
- package/skills/qualia-verify/SKILL.md +43 -5
- package/templates/instructions.md +2 -2
- package/tests/bin.test.sh +183 -0
- package/tests/hooks.test.sh +124 -0
- package/tests/install-smoke.test.sh +14 -0
- package/tests/instructions.test.sh +2 -2
- package/tests/lib.test.sh +149 -0
- package/tests/plugin-manifest.test.sh +168 -0
- package/tests/refs.test.sh +64 -0
- package/tests/run-all.sh +1 -0
- package/tests/state.test.sh +174 -0
- package/tests/verify-panel.test.sh +236 -0
|
@@ -157,6 +157,242 @@ rm -rf "$TMP"
|
|
|
157
157
|
$NODE "$VP" /nonexistent/panel.json >/dev/null 2>&1
|
|
158
158
|
assert_exit "missing panel file → exit 2" 2 $?
|
|
159
159
|
|
|
160
|
+
# --- skeptic tally: pure function is deterministic + ignores stray prose ---
|
|
161
|
+
TALLY1=$($NODE -e "console.log(JSON.stringify(require('$VP').tallySkepticVotes('REAL — reachable\nNOT_REAL — handled\nREAL — yep\n\nrandom prose\nnot_real — lowercase ok')))")
|
|
162
|
+
TALLY2=$($NODE -e "console.log(JSON.stringify(require('$VP').tallySkepticVotes('REAL — reachable\nNOT_REAL — handled\nREAL — yep\n\nrandom prose\nnot_real — lowercase ok')))")
|
|
163
|
+
assert_eq "tally is deterministic (same input → same counts)" "$TALLY1" "$TALLY2"
|
|
164
|
+
assert_contains "tally counts REAL votes" "$TALLY1" '"real":2'
|
|
165
|
+
assert_contains "tally counts NOT_REAL (incl lowercase, not mistaken for REAL)" "$TALLY1" '"notReal":2'
|
|
166
|
+
|
|
167
|
+
# --- skeptic subcommand: writes votes onto the matching finding (mechanical, no hand-edit) ---
|
|
168
|
+
TMP=$(mktemp -d); mkdir -p "$TMP/.planning"
|
|
169
|
+
cat > "$TMP/.planning/phase-9-panel.json" <<'EOF'
|
|
170
|
+
{ "phase": 9, "lenses": ["security"], "findings": [
|
|
171
|
+
{ "lens":"security", "file":"lib/auth.ts", "line":42, "severity":"CRITICAL", "title":"service_role reachable client-side", "votes": {"real":0,"notReal":0} }
|
|
172
|
+
] }
|
|
173
|
+
EOF
|
|
174
|
+
KEY="lib/auth.ts:42:service role reachable client side"
|
|
175
|
+
printf '%s\n' "REAL — confirmed reachable" "REAL — still reachable" "NOT_REAL — handled" \
|
|
176
|
+
| (cd "$TMP" && $NODE "$VP" skeptic 9 "$KEY" >/dev/null 2>&1)
|
|
177
|
+
assert_exit "skeptic subcommand exits 0 on a matching key" 0 $?
|
|
178
|
+
WROTE=$(cat "$TMP/.planning/phase-9-panel.json")
|
|
179
|
+
assert_contains "skeptic wrote real count" "$WROTE" '"real": 2'
|
|
180
|
+
assert_contains "skeptic wrote notReal count" "$WROTE" '"notReal": 1'
|
|
181
|
+
# determinism end-to-end: identical replies → identical counts on re-run
|
|
182
|
+
printf '%s\n' "REAL — confirmed reachable" "REAL — still reachable" "NOT_REAL — handled" \
|
|
183
|
+
| (cd "$TMP" && $NODE "$VP" skeptic 9 "$KEY" >/dev/null 2>&1)
|
|
184
|
+
WROTE2=$(cat "$TMP/.planning/phase-9-panel.json")
|
|
185
|
+
assert_eq "skeptic tally is reproducible (same replies → same JSON)" "$WROTE" "$WROTE2"
|
|
186
|
+
# the tallied 2✓/1✗ CRITICAL survives (majority-survives) → aggregator still FAILs
|
|
187
|
+
(cd "$TMP" && $NODE "$VP" .planning/phase-9-panel.json >/dev/null 2>&1)
|
|
188
|
+
assert_exit "tallied CRITICAL (2✓/1✗) survives → FAIL" 1 $?
|
|
189
|
+
rm -rf "$TMP"
|
|
190
|
+
|
|
191
|
+
# --- skeptic subcommand: majority not-real, written by the tally, flips to PASS ---
|
|
192
|
+
TMP=$(mktemp -d); mkdir -p "$TMP/.planning"
|
|
193
|
+
cat > "$TMP/.planning/phase-9-panel.json" <<'EOF'
|
|
194
|
+
{ "phase": 9, "lenses": ["security"], "findings": [
|
|
195
|
+
{ "lens":"security", "file":"lib/auth.ts", "line":42, "severity":"CRITICAL", "title":"false alarm", "votes": {"real":0,"notReal":0} }
|
|
196
|
+
] }
|
|
197
|
+
EOF
|
|
198
|
+
printf '%s\n' "NOT_REAL — false positive" "NOT_REAL — already guarded" "REAL — unsure" \
|
|
199
|
+
| (cd "$TMP" && $NODE "$VP" skeptic 9 "lib/auth.ts:42:false alarm" >/dev/null 2>&1)
|
|
200
|
+
(cd "$TMP" && $NODE "$VP" .planning/phase-9-panel.json >/dev/null 2>&1)
|
|
201
|
+
assert_exit "tallied majority NOT_REAL kills CRITICAL → PASS" 0 $?
|
|
202
|
+
rm -rf "$TMP"
|
|
203
|
+
|
|
204
|
+
# --- skeptic subcommand: unknown key → exit 2 (cannot silently no-op a tally) ---
|
|
205
|
+
TMP=$(mktemp -d); mkdir -p "$TMP/.planning"
|
|
206
|
+
cat > "$TMP/.planning/phase-9-panel.json" <<'EOF'
|
|
207
|
+
{ "phase": 9, "lenses": ["security"], "findings": [
|
|
208
|
+
{ "lens":"security", "file":"lib/auth.ts", "line":42, "severity":"CRITICAL", "title":"real one", "votes": {"real":0,"notReal":0} }
|
|
209
|
+
] }
|
|
210
|
+
EOF
|
|
211
|
+
printf '%s\n' "REAL — x" | (cd "$TMP" && $NODE "$VP" skeptic 9 "does/not:1:exist" >/dev/null 2>&1)
|
|
212
|
+
assert_exit "skeptic unknown key → exit 2" 2 $?
|
|
213
|
+
rm -rf "$TMP"
|
|
214
|
+
|
|
215
|
+
# --- execution lens: a failing build script → CRITICAL panel-execution.json that assemble() counts ---
|
|
216
|
+
TMP=$(mktemp -d); mkdir -p "$TMP/.planning"
|
|
217
|
+
cat > "$TMP/package.json" <<'EOF'
|
|
218
|
+
{ "name": "fixture-red", "scripts": { "build": "exit 7" } }
|
|
219
|
+
EOF
|
|
220
|
+
(cd "$TMP" && $NODE "$VP" execution 11 >/dev/null 2>&1)
|
|
221
|
+
assert_exit "execution lens with a red build → exit 1" 1 $?
|
|
222
|
+
[ -f "$TMP/.planning/phase-11-panel-execution.json" ] && { echo " ✓ execution lens writes panel-execution.json"; PASS=$((PASS+1)); } || { echo " ✗ no panel-execution.json"; FAIL=$((FAIL+1)); }
|
|
223
|
+
EXEC=$(cat "$TMP/.planning/phase-11-panel-execution.json")
|
|
224
|
+
assert_contains "execution failure is CRITICAL" "$EXEC" '"severity": "CRITICAL"'
|
|
225
|
+
assert_contains "execution failure names the build" "$EXEC" 'npm run build failed'
|
|
226
|
+
# assemble() folds the execution lens into the panel like any other lens file
|
|
227
|
+
(cd "$TMP" && $NODE "$VP" assemble 11 >/dev/null 2>&1)
|
|
228
|
+
ASM=$(cat "$TMP/.planning/phase-11-panel.json")
|
|
229
|
+
assert_contains "assemble tags execution lens" "$ASM" '"lens": "execution"'
|
|
230
|
+
# and the assembled panel aggregates to FAIL (the CRITICAL survives, unvoted)
|
|
231
|
+
(cd "$TMP" && $NODE "$VP" .planning/phase-11-panel.json >/dev/null 2>&1)
|
|
232
|
+
assert_exit "execution CRITICAL → assembled panel FAILs" 1 $?
|
|
233
|
+
rm -rf "$TMP"
|
|
234
|
+
|
|
235
|
+
# --- execution lens: absent tools → SKIPPED, not FAIL (fail-soft) ---
|
|
236
|
+
TMP=$(mktemp -d); mkdir -p "$TMP/.planning"
|
|
237
|
+
# no tsconfig.json, no package.json → every check is inapplicable
|
|
238
|
+
(cd "$TMP" && $NODE "$VP" execution 12 >/dev/null 2>&1)
|
|
239
|
+
assert_exit "execution lens with no checks → exit 0 (fail-soft)" 0 $?
|
|
240
|
+
EXEC=$(cat "$TMP/.planning/phase-12-panel-execution.json")
|
|
241
|
+
assert_eq "absent tools produce an empty finding array (SKIPPED, not FAIL)" "[]" "$(echo "$EXEC" | tr -d '[:space:]')"
|
|
242
|
+
rm -rf "$TMP"
|
|
243
|
+
|
|
244
|
+
# --- execution lens: a no-op default test script is SKIPPED, not run as a failure ---
|
|
245
|
+
TMP=$(mktemp -d); mkdir -p "$TMP/.planning"
|
|
246
|
+
cat > "$TMP/package.json" <<'EOF'
|
|
247
|
+
{ "name": "fixture-notest", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" } }
|
|
248
|
+
EOF
|
|
249
|
+
(cd "$TMP" && $NODE "$VP" execution 13 >/dev/null 2>&1)
|
|
250
|
+
assert_exit "npm-init default 'no test specified' is SKIPPED → exit 0" 0 $?
|
|
251
|
+
EXEC=$(cat "$TMP/.planning/phase-13-panel-execution.json")
|
|
252
|
+
assert_eq "default no-op test produces no finding" "[]" "$(echo "$EXEC" | tr -d '[:space:]')"
|
|
253
|
+
rm -rf "$TMP"
|
|
254
|
+
|
|
255
|
+
# --- execution lens: a passing build script → no finding (present + exit 0 = PASS) ---
|
|
256
|
+
TMP=$(mktemp -d); mkdir -p "$TMP/.planning"
|
|
257
|
+
cat > "$TMP/package.json" <<'EOF'
|
|
258
|
+
{ "name": "fixture-green", "scripts": { "build": "exit 0" } }
|
|
259
|
+
EOF
|
|
260
|
+
(cd "$TMP" && $NODE "$VP" execution 14 >/dev/null 2>&1)
|
|
261
|
+
assert_exit "execution lens with a green build → exit 0" 0 $?
|
|
262
|
+
EXEC=$(cat "$TMP/.planning/phase-14-panel-execution.json")
|
|
263
|
+
assert_eq "a passing check produces no finding" "[]" "$(echo "$EXEC" | tr -d '[:space:]')"
|
|
264
|
+
rm -rf "$TMP"
|
|
265
|
+
|
|
266
|
+
# ============================================================================
|
|
267
|
+
# verdict aggregator (ADR-0002) — fold machine-JSON gates into one deterministic
|
|
268
|
+
# PASS/FAIL. The aggregator only makes the existing AND deterministic; it must
|
|
269
|
+
# not start blocking anything that passes today (no-regression principle).
|
|
270
|
+
# ============================================================================
|
|
271
|
+
|
|
272
|
+
# --- gate recorder: a failing gate's REAL exit code → blocking CRITICAL artifact ---
|
|
273
|
+
TMP=$(mktemp -d); mkdir -p "$TMP/.planning"
|
|
274
|
+
(cd "$TMP" && $NODE "$VP" gate 20 dep-verify --exit 1 >/dev/null 2>&1)
|
|
275
|
+
assert_exit "gate recorder exits 0 (it records, never blocks)" 0 $?
|
|
276
|
+
[ -f "$TMP/.planning/phase-20-gate-dep-verify.json" ] && { echo " ✓ gate writes phase-20-gate-dep-verify.json"; PASS=$((PASS+1)); } || { echo " ✗ no gate artifact"; FAIL=$((FAIL+1)); }
|
|
277
|
+
G=$(cat "$TMP/.planning/phase-20-gate-dep-verify.json")
|
|
278
|
+
assert_contains "gate records CRITICAL on non-zero exit" "$G" '"severity": "CRITICAL"'
|
|
279
|
+
assert_contains "gate records blocking:true for dep-verify" "$G" '"blocking": true'
|
|
280
|
+
assert_contains "gate records a finding on failure" "$G" '"findings"'
|
|
281
|
+
rm -rf "$TMP"
|
|
282
|
+
|
|
283
|
+
# --- gate recorder: exit 0 → clean artifact, empty findings ---
|
|
284
|
+
TMP=$(mktemp -d); mkdir -p "$TMP/.planning"
|
|
285
|
+
(cd "$TMP" && $NODE "$VP" gate 21 slop-detect --exit 0 >/dev/null 2>&1)
|
|
286
|
+
G=$(cat "$TMP/.planning/phase-21-gate-slop-detect.json")
|
|
287
|
+
assert_contains "clean gate has empty findings" "$G" '"findings": []'
|
|
288
|
+
rm -rf "$TMP"
|
|
289
|
+
|
|
290
|
+
# --- verdict: a blocking gate's block condition flips the verdict to FAIL ---
|
|
291
|
+
TMP=$(mktemp -d); mkdir -p "$TMP/.planning"
|
|
292
|
+
(cd "$TMP" && $NODE "$VP" gate 22 dep-verify --exit 1 >/dev/null 2>&1)
|
|
293
|
+
(cd "$TMP" && $NODE "$VP" verdict 22 >/dev/null 2>&1)
|
|
294
|
+
assert_exit "blocking gate CRITICAL → verdict FAIL (exit 1)" 1 $?
|
|
295
|
+
OUT=$(cd "$TMP" && $NODE "$VP" verdict 22 --json 2>&1)
|
|
296
|
+
assert_contains "verdict FAIL on blocking gate" "$OUT" '"verdict": "FAIL"'
|
|
297
|
+
rm -rf "$TMP"
|
|
298
|
+
|
|
299
|
+
# --- verdict: slop-detect block condition (CRITICAL slop, exit 1) also flips ---
|
|
300
|
+
TMP=$(mktemp -d); mkdir -p "$TMP/.planning"
|
|
301
|
+
(cd "$TMP" && $NODE "$VP" gate 23 slop-detect --exit 1 >/dev/null 2>&1)
|
|
302
|
+
(cd "$TMP" && $NODE "$VP" verdict 23 >/dev/null 2>&1)
|
|
303
|
+
assert_exit "slop-detect CRITICAL → verdict FAIL (exit 1)" 1 $?
|
|
304
|
+
rm -rf "$TMP"
|
|
305
|
+
|
|
306
|
+
# --- verdict: harness-eval hard FAIL flips; per-suite qualia-eval CRITICAL flips ---
|
|
307
|
+
TMP=$(mktemp -d); mkdir -p "$TMP/.planning"
|
|
308
|
+
(cd "$TMP" && $NODE "$VP" gate 24 harness-eval --exit 1 >/dev/null 2>&1)
|
|
309
|
+
(cd "$TMP" && $NODE "$VP" verdict 24 >/dev/null 2>&1)
|
|
310
|
+
assert_exit "harness-eval hard FAIL → verdict FAIL" 1 $?
|
|
311
|
+
rm -rf "$TMP"
|
|
312
|
+
TMP=$(mktemp -d); mkdir -p "$TMP/.planning"
|
|
313
|
+
(cd "$TMP" && $NODE "$VP" gate 25 eval-rag --exit 1 --title "qualia-eval suite rag FAILED" >/dev/null 2>&1)
|
|
314
|
+
(cd "$TMP" && $NODE "$VP" verdict 25 >/dev/null 2>&1)
|
|
315
|
+
assert_exit "per-suite qualia-eval FAIL → verdict FAIL" 1 $?
|
|
316
|
+
OUT=$(cd "$TMP" && $NODE "$VP" verdict 25 --json 2>&1)
|
|
317
|
+
assert_contains "failing suite recorded as its own CRITICAL" "$OUT" 'suite rag FAILED'
|
|
318
|
+
rm -rf "$TMP"
|
|
319
|
+
|
|
320
|
+
# --- NO REGRESSION: a non-blocking finding (soft harness-eval MEDIUM) does NOT flip ---
|
|
321
|
+
TMP=$(mktemp -d); mkdir -p "$TMP/.planning"
|
|
322
|
+
(cd "$TMP" && $NODE "$VP" gate 26 harness-eval --exit 1 --severity MEDIUM >/dev/null 2>&1)
|
|
323
|
+
G=$(cat "$TMP/.planning/phase-26-gate-harness-eval.json")
|
|
324
|
+
assert_contains "soft sub-check recorded MEDIUM" "$G" '"severity": "MEDIUM"'
|
|
325
|
+
assert_contains "MEDIUM gate is non-blocking" "$G" '"blocking": false'
|
|
326
|
+
(cd "$TMP" && $NODE "$VP" verdict 26 >/dev/null 2>&1)
|
|
327
|
+
assert_exit "non-blocking MEDIUM gate → verdict still PASS (no regression)" 0 $?
|
|
328
|
+
OUT=$(cd "$TMP" && $NODE "$VP" verdict 26 --json 2>&1)
|
|
329
|
+
assert_contains "non-blocking finding still recorded/visible" "$OUT" '"MEDIUM": 1'
|
|
330
|
+
rm -rf "$TMP"
|
|
331
|
+
|
|
332
|
+
# --- HIGH slop recorded but non-blocking does NOT flip (block condition is CRITICAL) ---
|
|
333
|
+
# slop-detect runs at --severity=critical, so a HIGH-only result exits 0 → clean gate.
|
|
334
|
+
TMP=$(mktemp -d); mkdir -p "$TMP/.planning"
|
|
335
|
+
(cd "$TMP" && $NODE "$VP" gate 27 slop-detect --exit 0 >/dev/null 2>&1)
|
|
336
|
+
(cd "$TMP" && $NODE "$VP" verdict 27 >/dev/null 2>&1)
|
|
337
|
+
assert_exit "HIGH/MEDIUM slop (gate exit 0) → verdict PASS" 0 $?
|
|
338
|
+
rm -rf "$TMP"
|
|
339
|
+
|
|
340
|
+
# --- verdict: a clean run (all gates exit 0, no panel findings) → PASS ---
|
|
341
|
+
TMP=$(mktemp -d); mkdir -p "$TMP/.planning"
|
|
342
|
+
(cd "$TMP" && $NODE "$VP" gate 28 dep-verify --exit 0 >/dev/null 2>&1)
|
|
343
|
+
(cd "$TMP" && $NODE "$VP" gate 28 slop-detect --exit 0 >/dev/null 2>&1)
|
|
344
|
+
(cd "$TMP" && $NODE "$VP" gate 28 harness-eval --exit 0 >/dev/null 2>&1)
|
|
345
|
+
(cd "$TMP" && $NODE "$VP" verdict 28 >/dev/null 2>&1)
|
|
346
|
+
assert_exit "clean run (all gates green) → verdict PASS (exit 0)" 0 $?
|
|
347
|
+
rm -rf "$TMP"
|
|
348
|
+
|
|
349
|
+
# --- verdict folds the panel lenses AND gates together (execution lens + gate) ---
|
|
350
|
+
TMP=$(mktemp -d); mkdir -p "$TMP/.planning"
|
|
351
|
+
cat > "$TMP/.planning/phase-29-panel-security.json" <<'EOF'
|
|
352
|
+
[{"file":"auth.ts","line":1,"severity":"CRITICAL","title":"leak"}]
|
|
353
|
+
EOF
|
|
354
|
+
(cd "$TMP" && $NODE "$VP" gate 29 slop-detect --exit 0 >/dev/null 2>&1)
|
|
355
|
+
(cd "$TMP" && $NODE "$VP" verdict 29 >/dev/null 2>&1)
|
|
356
|
+
assert_exit "panel CRITICAL folds into verdict even with clean gates → FAIL" 1 $?
|
|
357
|
+
OUT=$(cd "$TMP" && $NODE "$VP" verdict 29 --json 2>&1)
|
|
358
|
+
assert_contains "verdict surfaces the panel finding" "$OUT" 'leak'
|
|
359
|
+
rm -rf "$TMP"
|
|
360
|
+
|
|
361
|
+
# --- DETERMINISM: same artifacts → identical verdict twice ---
|
|
362
|
+
TMP=$(mktemp -d); mkdir -p "$TMP/.planning"
|
|
363
|
+
cat > "$TMP/.planning/phase-30-panel-correctness.json" <<'EOF'
|
|
364
|
+
[{"file":"a.ts","line":3,"severity":"HIGH","title":"off by one"}]
|
|
365
|
+
EOF
|
|
366
|
+
(cd "$TMP" && $NODE "$VP" gate 30 dep-verify --exit 1 >/dev/null 2>&1)
|
|
367
|
+
(cd "$TMP" && $NODE "$VP" gate 30 harness-eval --exit 1 --severity MEDIUM >/dev/null 2>&1)
|
|
368
|
+
V1=$(cd "$TMP" && $NODE "$VP" verdict 30 --json 2>&1)
|
|
369
|
+
V2=$(cd "$TMP" && $NODE "$VP" verdict 30 --json 2>&1)
|
|
370
|
+
assert_eq "verdict is deterministic (same artifacts → identical JSON)" "$V1" "$V2"
|
|
371
|
+
(cd "$TMP" && $NODE "$VP" verdict 30 >/dev/null 2>&1)
|
|
372
|
+
assert_exit "deterministic run with a blocking gate → FAIL both times" 1 $?
|
|
373
|
+
rm -rf "$TMP"
|
|
374
|
+
|
|
375
|
+
# --- verdict --write emits the verdict artifacts ---
|
|
376
|
+
TMP=$(mktemp -d); mkdir -p "$TMP/.planning"
|
|
377
|
+
(cd "$TMP" && $NODE "$VP" gate 31 dep-verify --exit 1 >/dev/null 2>&1)
|
|
378
|
+
(cd "$TMP" && $NODE "$VP" verdict 31 --write >/dev/null 2>&1)
|
|
379
|
+
[ -f "$TMP/.planning/phase-31-verdict.json" ] && { echo " ✓ verdict writes phase-31-verdict.json"; PASS=$((PASS+1)); } || { echo " ✗ no verdict json"; FAIL=$((FAIL+1)); }
|
|
380
|
+
[ -f "$TMP/.planning/phase-31-verdict.md" ] && { echo " ✓ verdict writes phase-31-verdict.md"; PASS=$((PASS+1)); } || { echo " ✗ no verdict md"; FAIL=$((FAIL+1)); }
|
|
381
|
+
rm -rf "$TMP"
|
|
382
|
+
|
|
383
|
+
# --- verdict honors skeptic votes already on the panel (does not re-run them) ---
|
|
384
|
+
# A panel.json with a CRITICAL skeptics killed (majority not-real) → verdict PASS.
|
|
385
|
+
TMP=$(mktemp -d); mkdir -p "$TMP/.planning"
|
|
386
|
+
cat > "$TMP/.planning/phase-32-panel.json" <<'EOF'
|
|
387
|
+
{ "phase": 32, "lenses": ["security"], "findings": [
|
|
388
|
+
{ "lens":"security", "file":"x.ts", "line":1, "severity":"CRITICAL", "title":"false alarm", "votes": {"real":1,"notReal":2} }
|
|
389
|
+
] }
|
|
390
|
+
EOF
|
|
391
|
+
(cd "$TMP" && $NODE "$VP" gate 32 slop-detect --exit 0 >/dev/null 2>&1)
|
|
392
|
+
(cd "$TMP" && $NODE "$VP" verdict 32 >/dev/null 2>&1)
|
|
393
|
+
assert_exit "verdict honors skeptic-killed CRITICAL → PASS" 0 $?
|
|
394
|
+
rm -rf "$TMP"
|
|
395
|
+
|
|
160
396
|
echo ""
|
|
161
397
|
echo "=== Results: $PASS passed, $FAIL failed ==="
|
|
162
398
|
[ "$FAIL" -eq 0 ] && exit 0 || exit 1
|