qualia-framework 6.2.10 → 6.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/AGENTS.md +1 -0
- package/CLAUDE.md +1 -0
- package/README.md +16 -23
- package/bin/cli.js +49 -2
- package/bin/command-surface.js +71 -0
- package/bin/harness-eval.js +296 -0
- package/bin/install.js +17 -20
- package/bin/knowledge-flush.js +21 -10
- package/bin/knowledge.js +1 -1
- package/bin/project-snapshot.js +20 -0
- package/bin/report-payload.js +18 -0
- package/bin/runtime-manifest.js +3 -0
- package/bin/state.js +31 -0
- package/bin/trust-score.js +3 -11
- package/bin/work-packet.js +228 -0
- package/docs/erp-contract.md +81 -1
- package/docs/onboarding.html +0 -11
- package/guide.md +14 -15
- package/hooks/fawzi-approval-guard.js +143 -0
- package/hooks/pre-deploy-gate.js +74 -1
- package/hooks/session-start.js +29 -1
- package/package.json +1 -1
- package/qualia-design/frontend.md +2 -2
- package/rules/codex-goal.md +1 -1
- package/rules/one-opinion.md +2 -2
- package/rules/speed.md +0 -1
- package/skills/qualia/SKILL.md +4 -4
- package/skills/qualia-feature/SKILL.md +1 -1
- package/skills/qualia-fix/SKILL.md +4 -4
- package/skills/qualia-learn/SKILL.md +1 -1
- package/skills/qualia-polish/REFERENCE.md +1 -1
- package/skills/qualia-polish/SKILL.md +19 -4
- package/skills/{qualia-vibe/scripts/extract.mjs → qualia-polish/scripts/vibe-extract.mjs} +4 -4
- package/skills/{qualia-vibe/scripts/tokens.mjs → qualia-polish/scripts/vibe-tokens.mjs} +6 -6
- package/skills/qualia-road/SKILL.md +15 -20
- package/skills/qualia-ship/SKILL.md +12 -5
- package/skills/qualia-verify/SKILL.md +9 -1
- package/templates/help.html +1 -12
- package/tests/bin.test.sh +144 -72
- package/tests/hooks.test.sh +81 -1
- package/tests/install-smoke.test.sh +13 -3
- package/tests/lib.test.sh +145 -3
- package/tests/published-install-smoke.test.sh +4 -3
- package/tests/refs.test.sh +9 -4
- package/tests/runner.js +29 -28
- package/tests/state.test.sh +68 -0
- package/skills/qualia-debug/SKILL.md +0 -193
- package/skills/qualia-flush/SKILL.md +0 -198
- package/skills/qualia-help/SKILL.md +0 -74
- package/skills/qualia-hook-gen/SKILL.md +0 -206
- package/skills/qualia-idk/SKILL.md +0 -166
- package/skills/qualia-issues/SKILL.md +0 -151
- package/skills/qualia-pause/SKILL.md +0 -68
- package/skills/qualia-resume/SKILL.md +0 -52
- package/skills/qualia-skill-new/SKILL.md +0 -173
- package/skills/qualia-triage/SKILL.md +0 -152
- package/skills/qualia-vibe/SKILL.md +0 -229
- package/skills/qualia-zoom/SKILL.md +0 -51
|
@@ -62,9 +62,12 @@ fi
|
|
|
62
62
|
tar -xzf "$TARBALL_PATH" -C "$TMP" 2>"$TMP/tar.err"
|
|
63
63
|
if [ -f "$TMP/package/bin/install.js" ] \
|
|
64
64
|
&& [ -f "$TMP/package/bin/runtime-manifest.js" ] \
|
|
65
|
+
&& [ -f "$TMP/package/bin/command-surface.js" ] \
|
|
65
66
|
&& [ -f "$TMP/package/bin/host-adapters.js" ] \
|
|
66
67
|
&& [ -f "$TMP/package/bin/report-payload.js" ] \
|
|
68
|
+
&& [ -f "$TMP/package/bin/work-packet.js" ] \
|
|
67
69
|
&& [ -f "$TMP/package/bin/project-snapshot.js" ] \
|
|
70
|
+
&& [ -f "$TMP/package/bin/harness-eval.js" ] \
|
|
68
71
|
&& [ -f "$TMP/package/bin/planning-hygiene.js" ] \
|
|
69
72
|
&& [ -f "$TMP/package/bin/state-ledger.js" ] \
|
|
70
73
|
&& [ -f "$TMP/package/AGENTS.md" ] \
|
|
@@ -74,7 +77,7 @@ else
|
|
|
74
77
|
fail_case "tarball missing install surfaces" "$(cat "$TMP/tar.err" 2>/dev/null)"
|
|
75
78
|
fi
|
|
76
79
|
|
|
77
|
-
printf 'QS-FAWZI-
|
|
80
|
+
printf 'QS-FAWZI-11\n3\n' | HOME="$HOME_DIR" "$NODE" "$TMP/package/bin/install.js" >"$TMP/install.log" 2>&1
|
|
78
81
|
EXIT=$?
|
|
79
82
|
if [ "$EXIT" -eq 0 ]; then
|
|
80
83
|
pass "packaged installer exits 0 for target=Both"
|
|
@@ -94,11 +97,14 @@ if [ -f "$HOME_DIR/.codex/AGENTS.md" ] \
|
|
|
94
97
|
&& [ -f "$HOME_DIR/.codex/hooks.json" ] \
|
|
95
98
|
&& [ -f "$HOME_DIR/.codex/config.toml" ] \
|
|
96
99
|
&& [ -f "$HOME_DIR/.codex/bin/runtime-manifest.js" ] \
|
|
100
|
+
&& [ -f "$HOME_DIR/.codex/bin/command-surface.js" ] \
|
|
97
101
|
&& [ -f "$HOME_DIR/.codex/bin/host-adapters.js" ] \
|
|
98
102
|
&& [ -f "$HOME_DIR/.codex/bin/statusline.js" ] \
|
|
99
103
|
&& [ -f "$HOME_DIR/.codex/bin/state-ledger.js" ] \
|
|
100
104
|
&& [ -f "$HOME_DIR/.codex/bin/contract-runner.js" ] \
|
|
105
|
+
&& [ -f "$HOME_DIR/.codex/bin/harness-eval.js" ] \
|
|
101
106
|
&& [ -f "$HOME_DIR/.codex/bin/trust-score.js" ] \
|
|
107
|
+
&& [ -f "$HOME_DIR/.codex/bin/work-packet.js" ] \
|
|
102
108
|
&& [ -f "$HOME_DIR/.codex/bin/project-snapshot.js" ] \
|
|
103
109
|
&& [ -f "$HOME_DIR/.codex/bin/planning-hygiene.js" ] \
|
|
104
110
|
&& [ -f "$HOME_DIR/.codex/agents/planner.toml" ] \
|
|
@@ -118,19 +124,23 @@ else
|
|
|
118
124
|
fi
|
|
119
125
|
|
|
120
126
|
if [ -d "$HOME_DIR/.claude/hooks" ] \
|
|
121
|
-
&& [ "$(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 ' ')" = "12" ] \
|
|
128
|
+
&& [ -f "$HOME_DIR/.claude/hooks/fawzi-approval-guard.js" ] \
|
|
122
129
|
&& [ ! -f "$HOME_DIR/.claude/hooks/pre-compact.js" ]; then
|
|
123
|
-
pass "packaged install has
|
|
130
|
+
pass "packaged install has 12 hooks and no pre-compact"
|
|
124
131
|
else
|
|
125
132
|
fail_case "packaged hook set mismatch"
|
|
126
133
|
fi
|
|
127
134
|
|
|
128
135
|
if [ -f "$HOME_DIR/.claude/bin/report-payload.js" ] \
|
|
136
|
+
&& [ -f "$HOME_DIR/.claude/bin/work-packet.js" ] \
|
|
129
137
|
&& [ -f "$HOME_DIR/.claude/bin/project-snapshot.js" ] \
|
|
130
138
|
&& [ -f "$HOME_DIR/.claude/bin/runtime-manifest.js" ] \
|
|
139
|
+
&& [ -f "$HOME_DIR/.claude/bin/command-surface.js" ] \
|
|
131
140
|
&& [ -f "$HOME_DIR/.claude/bin/host-adapters.js" ] \
|
|
132
141
|
&& [ -f "$HOME_DIR/.claude/bin/state-ledger.js" ] \
|
|
133
142
|
&& [ -f "$HOME_DIR/.claude/bin/contract-runner.js" ] \
|
|
143
|
+
&& [ -f "$HOME_DIR/.claude/bin/harness-eval.js" ] \
|
|
134
144
|
&& [ -f "$HOME_DIR/.claude/bin/trust-score.js" ] \
|
|
135
145
|
&& [ -f "$HOME_DIR/.claude/bin/planning-hygiene.js" ] \
|
|
136
146
|
&& grep -q "report-payload.js" "$HOME_DIR/.claude/skills/qualia-report/SKILL.md"; then
|
package/tests/lib.test.sh
CHANGED
|
@@ -365,6 +365,30 @@ console.log(c.includes(".codex/bin/state.js") && c.includes(".codex/agents/plann
|
|
|
365
365
|
')
|
|
366
366
|
[ "$RES" = "HOST-RENDER-OK" ] && ok "host-adapters renders Qualia path tokens per host" || fail "host-adapters render: $RES"
|
|
367
367
|
|
|
368
|
+
CS="$FRAMEWORK_DIR/bin/command-surface.js"
|
|
369
|
+
$NODE --check "$CS" >/dev/null 2>&1 && ok "command-surface.js parses" || fail "command-surface.js parse"
|
|
370
|
+
RES=$($NODE -e '
|
|
371
|
+
const cs = require("'"$CS"'");
|
|
372
|
+
console.log(cs.ACTIVE_SKILLS.length === 23 && cs.RETIRED_SKILLS.includes("qualia-debug") && cs.RETIRED_SKILLS.includes("qualia-vibe") ? "SURFACE-OK" : JSON.stringify(cs));
|
|
373
|
+
')
|
|
374
|
+
[ "$RES" = "SURFACE-OK" ] && ok "command-surface defines 23 active skills and retired folds" || fail "command-surface manifest: $RES"
|
|
375
|
+
RES=$($NODE -e '
|
|
376
|
+
const fs = require("fs");
|
|
377
|
+
const path = require("path");
|
|
378
|
+
const root = "'"$FRAMEWORK_DIR"'";
|
|
379
|
+
const { ACTIVE_SKILLS, RETIRED_SKILLS } = require(path.join(root, "bin/command-surface.js"));
|
|
380
|
+
const dirs = fs.readdirSync(path.join(root, "skills"), { withFileTypes: true })
|
|
381
|
+
.filter((d) => d.isDirectory())
|
|
382
|
+
.map((d) => d.name)
|
|
383
|
+
.sort();
|
|
384
|
+
const active = [...ACTIVE_SKILLS].sort();
|
|
385
|
+
const extra = dirs.filter((d) => !active.includes(d));
|
|
386
|
+
const missing = active.filter((d) => !dirs.includes(d));
|
|
387
|
+
const retiredPresent = RETIRED_SKILLS.filter((d) => dirs.includes(d));
|
|
388
|
+
console.log(!extra.length && !missing.length && !retiredPresent.length ? "SKILL-DIRS-OK" : JSON.stringify({ extra, missing, retiredPresent }));
|
|
389
|
+
')
|
|
390
|
+
[ "$RES" = "SKILL-DIRS-OK" ] && ok "skills directory matches active command surface only" || fail "skill directory surface: $RES"
|
|
391
|
+
|
|
368
392
|
TMP=$(mktmp)
|
|
369
393
|
RES=$(cd "$TMP" && $NODE -e '
|
|
370
394
|
const ledger = require("'"$SL"'");
|
|
@@ -460,6 +484,124 @@ fi
|
|
|
460
484
|
TS="$FRAMEWORK_DIR/bin/trust-score.js"
|
|
461
485
|
$NODE --check "$TS" >/dev/null 2>&1 && ok "trust-score.js parses" || fail "trust-score.js parse"
|
|
462
486
|
|
|
487
|
+
HE="$FRAMEWORK_DIR/bin/harness-eval.js"
|
|
488
|
+
$NODE --check "$HE" >/dev/null 2>&1 && ok "harness-eval.js parses" || fail "harness-eval.js parse"
|
|
489
|
+
|
|
490
|
+
TMP=$(mktmp)
|
|
491
|
+
OUT=$(cd "$TMP" && $NODE "$HE" --json 2>/dev/null)
|
|
492
|
+
EXIT=$?
|
|
493
|
+
if [ "$EXIT" -eq 1 ] && echo "$OUT" | grep -q '"status": "FAIL"' && echo "$OUT" | grep -q 'No .planning directory'; then
|
|
494
|
+
ok "harness-eval fails closed without planning state"
|
|
495
|
+
else
|
|
496
|
+
fail "harness-eval no-planning behavior: exit=$EXIT out=$OUT"
|
|
497
|
+
fi
|
|
498
|
+
|
|
499
|
+
TMP=$(mktmp)
|
|
500
|
+
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"
|
|
501
|
+
echo '{"installed_by":"Test","role":"OWNER","version":"6.3.0","erp":{"enabled":false}}' > "$TMP/home/.claude/.qualia-config.json"
|
|
502
|
+
touch "$TMP/home/.claude/CLAUDE.md" "$TMP/home/.claude/settings.json"
|
|
503
|
+
for f in runtime-manifest.js command-surface.js host-adapters.js state.js qualia-ui.js statusline.js knowledge.js knowledge-flush.js state-ledger.js plan-contract.js contract-runner.js harness-eval.js trust-score.js agent-runs.js slop-detect.mjs erp-retry.js work-packet.js report-payload.js project-snapshot.js codex-goal.js planning-hygiene.js; do
|
|
504
|
+
touch "$TMP/home/.claude/bin/$f"
|
|
505
|
+
done
|
|
506
|
+
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
|
|
507
|
+
touch "$TMP/home/.claude/hooks/$h"
|
|
508
|
+
done
|
|
509
|
+
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"
|
|
510
|
+
for f in design-laws.md design-rubric.md design-brand.md design-product.md design-reference.md frontend.md graphics.md; do
|
|
511
|
+
touch "$TMP/home/.claude/qualia-design/$f"
|
|
512
|
+
done
|
|
513
|
+
for s in qualia qualia-new qualia-discuss qualia-map qualia-research qualia-plan qualia-build qualia-verify qualia-fix qualia-feature qualia-review qualia-optimize qualia-polish qualia-test qualia-milestone qualia-ship qualia-handoff qualia-report qualia-doctor qualia-road qualia-learn qualia-postmortem zoho-workflow; do
|
|
514
|
+
mkdir -p "$TMP/home/.claude/skills/$s"
|
|
515
|
+
touch "$TMP/home/.claude/skills/$s/SKILL.md"
|
|
516
|
+
done
|
|
517
|
+
(
|
|
518
|
+
cd "$TMP/project" || exit 1
|
|
519
|
+
HOME="$TMP/home" $NODE "$FRAMEWORK_DIR/bin/state.js" init --project "HarnessEvalProject" --phases '[{"name":"Foundation","goal":"Eval"}]' >/dev/null 2>&1
|
|
520
|
+
)
|
|
521
|
+
cat > "$TMP/project/eval-target.txt" <<'EOF'
|
|
522
|
+
contract ok
|
|
523
|
+
EOF
|
|
524
|
+
cat > "$TMP/project/.planning/phase-1-plan.md" <<'EOF'
|
|
525
|
+
# Phase 1 Plan
|
|
526
|
+
|
|
527
|
+
## Task 1
|
|
528
|
+
Create the eval target.
|
|
529
|
+
|
|
530
|
+
**Acceptance Criteria:**
|
|
531
|
+
- eval target exists
|
|
532
|
+
|
|
533
|
+
## Success Criteria
|
|
534
|
+
- [x] eval target exists
|
|
535
|
+
EOF
|
|
536
|
+
$NODE -e '
|
|
537
|
+
const fs = require("fs");
|
|
538
|
+
const path = require("path");
|
|
539
|
+
const pc = require("'"$PC"'");
|
|
540
|
+
const root = "'"$TMP"'/project";
|
|
541
|
+
const plan = fs.readFileSync(path.join(root, ".planning/phase-1-plan.md"), "utf8");
|
|
542
|
+
const trackingPath = path.join(root, ".planning/tracking.json");
|
|
543
|
+
const tracking = JSON.parse(fs.readFileSync(trackingPath, "utf8"));
|
|
544
|
+
Object.assign(tracking, {
|
|
545
|
+
project_id: "harness-eval-project",
|
|
546
|
+
erp_project_id: "7b5d3b4e-2b8a-4de4-91a1-9b2f3182f5ef",
|
|
547
|
+
phase_name: "Foundation",
|
|
548
|
+
tasks_done: 1,
|
|
549
|
+
tasks_total: 1
|
|
550
|
+
});
|
|
551
|
+
fs.writeFileSync(trackingPath, JSON.stringify(tracking, null, 2) + "\n");
|
|
552
|
+
const contract = {
|
|
553
|
+
version: 1,
|
|
554
|
+
phase: 1,
|
|
555
|
+
goal: "Harness eval positive path",
|
|
556
|
+
why: "Prove scoring writes artifacts and passes on machine evidence",
|
|
557
|
+
generated_at: "2026-05-23T00:00:00Z",
|
|
558
|
+
generated_by: "manual",
|
|
559
|
+
source_plan_hash: pc.hashPlan(plan),
|
|
560
|
+
success_criteria: ["eval target exists"],
|
|
561
|
+
tasks: [{
|
|
562
|
+
id: "T1",
|
|
563
|
+
title: "Eval target",
|
|
564
|
+
wave: 1,
|
|
565
|
+
depends_on: [],
|
|
566
|
+
files_modify: [],
|
|
567
|
+
files_create: ["eval-target.txt"],
|
|
568
|
+
files_delete: [],
|
|
569
|
+
acceptance_criteria: ["eval target exists"],
|
|
570
|
+
action: "Create eval target",
|
|
571
|
+
context_files: [],
|
|
572
|
+
verification: [
|
|
573
|
+
{ type: "file-exists", path: "eval-target.txt", must_contain: "contract ok" },
|
|
574
|
+
{ type: "command-exit", command: process.execPath, args: ["-e", "console.log(\"ok\")"], expected_exit: 0, expect_stdout_match: "ok" }
|
|
575
|
+
]
|
|
576
|
+
}]
|
|
577
|
+
};
|
|
578
|
+
fs.writeFileSync(path.join(root, ".planning/phase-1-contract.json"), JSON.stringify(contract, null, 2) + "\n");
|
|
579
|
+
fs.writeFileSync(path.join(root, ".planning/phase-1-verification.md"), "Result: PASS\n\nEvidence checked.\n");
|
|
580
|
+
'
|
|
581
|
+
OUT=$(cd "$TMP/project" && HOME="$TMP/home" $NODE "$HE" --phase 1 --run --write --json 2>/dev/null)
|
|
582
|
+
EXIT=$?
|
|
583
|
+
if [ "$EXIT" -eq 0 ] \
|
|
584
|
+
&& echo "$OUT" | grep -q '"status": "PASS"' \
|
|
585
|
+
&& echo "$OUT" | grep -q '"machine_evidence"' \
|
|
586
|
+
&& [ -f "$TMP/project/.planning/evidence/phase-1-contract-run.json" ] \
|
|
587
|
+
&& ls "$TMP/project"/.planning/evals/harness-eval-*.json >/dev/null 2>&1 \
|
|
588
|
+
&& ls "$TMP/project"/.planning/evals/harness-eval-*.md >/dev/null 2>&1; then
|
|
589
|
+
ok "harness-eval passes with machine evidence and writes artifacts"
|
|
590
|
+
else
|
|
591
|
+
fail "harness-eval positive path: exit=$EXIT out=$OUT"
|
|
592
|
+
fi
|
|
593
|
+
RES=$(cd "$TMP/project" && HOME="$TMP/home" $NODE -e '
|
|
594
|
+
const path = require("path");
|
|
595
|
+
const { latestEval } = require("'"$HE"'");
|
|
596
|
+
const { buildPayload } = require("'"$FRAMEWORK_DIR"'/bin/report-payload.js");
|
|
597
|
+
const { buildSnapshot } = require("'"$FRAMEWORK_DIR"'/bin/project-snapshot.js");
|
|
598
|
+
const ev = latestEval(process.cwd());
|
|
599
|
+
const payload = buildPayload({ cwd: process.cwd(), home: "'"$TMP"'/home", env: { SUBMITTED_BY: "Test", SUBMITTED_AT: "2026-05-23T00:30:00Z" } });
|
|
600
|
+
const snapshot = buildSnapshot({ cwd: process.cwd(), home: "'"$TMP"'/home", now: "2026-05-23T00:30:00Z" });
|
|
601
|
+
console.log(ev && payload.harness_eval && snapshot.quality.harness_eval && payload.harness_eval.score === ev.score && snapshot.quality.harness_eval.score === ev.score ? "ERP-EVAL-OK" : JSON.stringify({ ev, payload: payload.harness_eval, snapshot: snapshot.quality.harness_eval }));
|
|
602
|
+
')
|
|
603
|
+
[ "$RES" = "ERP-EVAL-OK" ] && ok "latest harness eval flows into ERP report and project snapshot" || fail "ERP harness eval wiring: $RES"
|
|
604
|
+
|
|
463
605
|
TMP=$(mktmp)
|
|
464
606
|
OUT=$(HOME="$TMP" $NODE "$TS" --json 2>/dev/null)
|
|
465
607
|
EXIT=$?
|
|
@@ -473,17 +615,17 @@ TMP=$(mktmp)
|
|
|
473
615
|
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"
|
|
474
616
|
echo '{"installed_by":"Test","role":"OWNER","erp":{"enabled":false}}' > "$TMP/.claude/.qualia-config.json"
|
|
475
617
|
touch "$TMP/.claude/CLAUDE.md" "$TMP/.claude/settings.json"
|
|
476
|
-
for f in runtime-manifest.js host-adapters.js state.js qualia-ui.js statusline.js knowledge.js knowledge-flush.js state-ledger.js plan-contract.js contract-runner.js trust-score.js agent-runs.js slop-detect.mjs erp-retry.js report-payload.js project-snapshot.js codex-goal.js planning-hygiene.js; do
|
|
618
|
+
for f in runtime-manifest.js command-surface.js host-adapters.js state.js qualia-ui.js statusline.js knowledge.js knowledge-flush.js state-ledger.js plan-contract.js contract-runner.js harness-eval.js trust-score.js agent-runs.js slop-detect.mjs erp-retry.js work-packet.js report-payload.js project-snapshot.js codex-goal.js planning-hygiene.js; do
|
|
477
619
|
touch "$TMP/.claude/bin/$f"
|
|
478
620
|
done
|
|
479
|
-
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 vercel-account-guard.js env-empty-guard.js supabase-destructive-guard.js; do
|
|
621
|
+
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
|
|
480
622
|
touch "$TMP/.claude/hooks/$h"
|
|
481
623
|
done
|
|
482
624
|
touch "$TMP/.claude/knowledge/index.md" "$TMP/.claude/knowledge/agents.md"
|
|
483
625
|
for f in design-laws.md design-rubric.md design-brand.md design-product.md design-reference.md frontend.md graphics.md; do
|
|
484
626
|
touch "$TMP/.claude/qualia-design/$f"
|
|
485
627
|
done
|
|
486
|
-
for s in qualia-
|
|
628
|
+
for s in qualia qualia-new qualia-discuss qualia-map qualia-research qualia-plan qualia-build qualia-verify qualia-fix qualia-feature qualia-review qualia-optimize qualia-polish qualia-test qualia-milestone qualia-ship qualia-handoff qualia-report qualia-doctor qualia-road qualia-learn qualia-postmortem zoho-workflow; do
|
|
487
629
|
mkdir -p "$TMP/.claude/skills/$s"
|
|
488
630
|
touch "$TMP/.claude/skills/$s/SKILL.md"
|
|
489
631
|
done
|
|
@@ -69,7 +69,7 @@ else
|
|
|
69
69
|
exit 1
|
|
70
70
|
fi
|
|
71
71
|
|
|
72
|
-
printf 'QS-FAWZI-
|
|
72
|
+
printf 'QS-FAWZI-11\n3\n' >"$TMP/install.input"
|
|
73
73
|
timeout "$INSTALL_TIMEOUT_SECONDS" env \
|
|
74
74
|
HOME="$HOME_DIR" \
|
|
75
75
|
NPM_CONFIG_CACHE="$CACHE_DIR" \
|
|
@@ -102,9 +102,10 @@ else
|
|
|
102
102
|
fi
|
|
103
103
|
|
|
104
104
|
if [ -d "$HOME_DIR/.claude/hooks" ] \
|
|
105
|
-
&& [ "$(find "$HOME_DIR/.claude/hooks" -maxdepth 1 -name '*.js' | wc -l | tr -d ' ')" = "
|
|
105
|
+
&& [ "$(find "$HOME_DIR/.claude/hooks" -maxdepth 1 -name '*.js' | wc -l | tr -d ' ')" = "12" ] \
|
|
106
|
+
&& [ -f "$HOME_DIR/.claude/hooks/fawzi-approval-guard.js" ] \
|
|
106
107
|
&& [ ! -f "$HOME_DIR/.claude/hooks/pre-compact.js" ]; then
|
|
107
|
-
pass "public install has
|
|
108
|
+
pass "public install has 12 hooks and no pre-compact"
|
|
108
109
|
else
|
|
109
110
|
fail_case "public install hook set mismatch"
|
|
110
111
|
fi
|
package/tests/refs.test.sh
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
# Qualia Framework — surface-drift guard
|
|
3
3
|
# Greps every active surface for backtick-quoted /qualia-{name} command references.
|
|
4
|
-
# Asserts each name has a matching
|
|
4
|
+
# Asserts each name is in bin/command-surface.js and has a matching SKILL.md.
|
|
5
5
|
#
|
|
6
6
|
# Why: v5.7 + v5.8 removed /qualia-quick, /qualia-task, /qualia-prd, /qualia-design,
|
|
7
7
|
# /qualia-polish-loop. Three user-facing files (rules/speed.md, templates/help.html,
|
|
@@ -81,6 +81,11 @@ SCAN_FILES=$(
|
|
|
81
81
|
# We capture name + file:line so we can show context per failure.
|
|
82
82
|
declare -A SEEN_REFS
|
|
83
83
|
declare -A REF_LOCATIONS
|
|
84
|
+
declare -A ACTIVE_SKILL_MAP
|
|
85
|
+
|
|
86
|
+
while IFS= read -r skill; do
|
|
87
|
+
[ -n "$skill" ] && ACTIVE_SKILL_MAP["/$skill"]=1
|
|
88
|
+
done < <("$NODE" -e 'const {ACTIVE_SKILLS}=require(process.argv[1]); for (const s of ACTIVE_SKILLS) console.log(s)' "$FRAMEWORK_ROOT/bin/command-surface.js")
|
|
84
89
|
|
|
85
90
|
while IFS= read -r file; do
|
|
86
91
|
# Pattern A: backtick-quoted commands. Allow trailing flag/word but only capture base name.
|
|
@@ -124,11 +129,11 @@ for ref in $(printf '%s\n' "${!SEEN_REFS[@]}" | sort); do
|
|
|
124
129
|
name="${ref#/}"
|
|
125
130
|
skill_dir="$SKILLS_DIR/$name"
|
|
126
131
|
locations="${REF_LOCATIONS[$ref]}"
|
|
127
|
-
if [ -d "$skill_dir" ] && [ -f "$skill_dir/SKILL.md" ]; then
|
|
128
|
-
pass "$ref → skills/$name/SKILL.md"
|
|
132
|
+
if [ "${ACTIVE_SKILL_MAP[$ref]:-}" = "1" ] && [ -d "$skill_dir" ] && [ -f "$skill_dir/SKILL.md" ]; then
|
|
133
|
+
pass "$ref → active skills/$name/SKILL.md"
|
|
129
134
|
continue
|
|
130
135
|
fi
|
|
131
|
-
fail_case "$ref" "
|
|
136
|
+
fail_case "$ref" "not an active shipped command or missing skills/$name/SKILL.md — referenced by: $locations"
|
|
132
137
|
done
|
|
133
138
|
|
|
134
139
|
PACKAGE_VERSION="$("$NODE" -e 'console.log(require(process.argv[1]).version)' "$FRAMEWORK_ROOT/package.json" 2>/dev/null || echo "")"
|
package/tests/runner.js
CHANGED
|
@@ -212,7 +212,7 @@ describe("CLI", () => {
|
|
|
212
212
|
it("team list works", () => {
|
|
213
213
|
const r = run("cli.js", ["team", "list"]);
|
|
214
214
|
assert.equal(r.status, 0);
|
|
215
|
-
assert.match(stripAnsi(r.stdout), /QS-FAWZI-
|
|
215
|
+
assert.match(stripAnsi(r.stdout), /QS-FAWZI-11/);
|
|
216
216
|
});
|
|
217
217
|
|
|
218
218
|
it("traces handles missing traces dir", () => {
|
|
@@ -230,7 +230,7 @@ describe("CLI", () => {
|
|
|
230
230
|
try {
|
|
231
231
|
fs.mkdirSync(path.join(tmpHome, ".claude"), { recursive: true });
|
|
232
232
|
fs.writeFileSync(path.join(tmpHome, ".claude", ".qualia-config.json"), JSON.stringify({
|
|
233
|
-
code: "QS-FAWZI-
|
|
233
|
+
code: "QS-FAWZI-11",
|
|
234
234
|
installed_by: "Fawzi Goussous",
|
|
235
235
|
role: "OWNER",
|
|
236
236
|
version: "2.8.1",
|
|
@@ -1607,7 +1607,7 @@ describe("Hooks", () => {
|
|
|
1607
1607
|
try {
|
|
1608
1608
|
fs.mkdirSync(path.join(tmpHome, ".claude"), { recursive: true });
|
|
1609
1609
|
fs.writeFileSync(path.join(tmpHome, ".claude", ".qualia-config.json"), JSON.stringify({
|
|
1610
|
-
code: "QS-FAWZI-
|
|
1610
|
+
code: "QS-FAWZI-11", version: "99.99.99",
|
|
1611
1611
|
}));
|
|
1612
1612
|
const r = spawnSync(NODE, [path.join(HOOKS, "auto-update.js")], {
|
|
1613
1613
|
encoding: "utf8", timeout: 5000,
|
|
@@ -2441,7 +2441,7 @@ describe("qualia-ui.js", () => {
|
|
|
2441
2441
|
try {
|
|
2442
2442
|
fs.mkdirSync(path.join(tmpHome, ".claude"), { recursive: true });
|
|
2443
2443
|
fs.writeFileSync(path.join(tmpHome, ".claude", ".qualia-config.json"), JSON.stringify({
|
|
2444
|
-
code: "QS-FAWZI-
|
|
2444
|
+
code: "QS-FAWZI-11",
|
|
2445
2445
|
installed_by: "Fawzi Goussous",
|
|
2446
2446
|
role: "OWNER",
|
|
2447
2447
|
version: "2.8.1",
|
|
@@ -2535,7 +2535,7 @@ describe("install.js", () => {
|
|
|
2535
2535
|
it("valid code installs everything", () => {
|
|
2536
2536
|
const tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), "qualia-install-"));
|
|
2537
2537
|
try {
|
|
2538
|
-
const r = runInstall("QS-FAWZI-
|
|
2538
|
+
const r = runInstall("QS-FAWZI-11", tmpHome);
|
|
2539
2539
|
assert.equal(r.status, 0);
|
|
2540
2540
|
assert.ok(fs.existsSync(path.join(tmpHome, ".claude", "skills", "qualia", "SKILL.md")));
|
|
2541
2541
|
assert.ok(fs.existsSync(path.join(tmpHome, ".claude", "hooks", "session-start.js")));
|
|
@@ -2556,9 +2556,9 @@ describe("install.js", () => {
|
|
|
2556
2556
|
it("config JSON has correct fields", () => {
|
|
2557
2557
|
const tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), "qualia-install-"));
|
|
2558
2558
|
try {
|
|
2559
|
-
runInstall("QS-FAWZI-
|
|
2559
|
+
runInstall("QS-FAWZI-11", tmpHome);
|
|
2560
2560
|
const config = JSON.parse(fs.readFileSync(path.join(tmpHome, ".claude", ".qualia-config.json"), "utf8"));
|
|
2561
|
-
assert.equal(config.code, "QS-FAWZI-
|
|
2561
|
+
assert.equal(config.code, "QS-FAWZI-11");
|
|
2562
2562
|
assert.equal(config.installed_by, "Fawzi Goussous");
|
|
2563
2563
|
assert.equal(config.role, "OWNER");
|
|
2564
2564
|
} finally {
|
|
@@ -2569,7 +2569,7 @@ describe("install.js", () => {
|
|
|
2569
2569
|
it("CLAUDE.md role placeholder replaced", () => {
|
|
2570
2570
|
const tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), "qualia-install-"));
|
|
2571
2571
|
try {
|
|
2572
|
-
runInstall("QS-FAWZI-
|
|
2572
|
+
runInstall("QS-FAWZI-11", tmpHome);
|
|
2573
2573
|
const claude = fs.readFileSync(path.join(tmpHome, ".claude", "CLAUDE.md"), "utf8");
|
|
2574
2574
|
assert.match(claude, /Role: OWNER/);
|
|
2575
2575
|
assert.doesNotMatch(claude, /\{\{ROLE\}\}/);
|
|
@@ -2578,12 +2578,13 @@ describe("install.js", () => {
|
|
|
2578
2578
|
}
|
|
2579
2579
|
});
|
|
2580
2580
|
|
|
2581
|
-
it("
|
|
2581
|
+
it("12 hooks installed (v6.2.11: proxy approval guard added; v6.2.0: pre-compact removed)", () => {
|
|
2582
2582
|
const tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), "qualia-install-"));
|
|
2583
2583
|
try {
|
|
2584
|
-
runInstall("QS-FAWZI-
|
|
2584
|
+
runInstall("QS-FAWZI-11", tmpHome);
|
|
2585
2585
|
const hooks = fs.readdirSync(path.join(tmpHome, ".claude", "hooks")).filter(f => f.endsWith(".js"));
|
|
2586
|
-
assert.equal(hooks.length,
|
|
2586
|
+
assert.equal(hooks.length, 12, `expected 12 hooks, got ${hooks.length}: ${hooks.join(", ")}`);
|
|
2587
|
+
assert.ok(hooks.includes("fawzi-approval-guard.js"), "fawzi-approval-guard.js must be installed");
|
|
2587
2588
|
assert.ok(!hooks.includes("pre-compact.js"), "pre-compact.js must NOT be installed (removed in v6.2.0)");
|
|
2588
2589
|
} finally {
|
|
2589
2590
|
fs.rmSync(tmpHome, { recursive: true, force: true });
|
|
@@ -2593,7 +2594,7 @@ describe("install.js", () => {
|
|
|
2593
2594
|
it("settings.json has hooks and statusLine", () => {
|
|
2594
2595
|
const tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), "qualia-install-"));
|
|
2595
2596
|
try {
|
|
2596
|
-
runInstall("QS-FAWZI-
|
|
2597
|
+
runInstall("QS-FAWZI-11", tmpHome);
|
|
2597
2598
|
const settings = fs.readFileSync(path.join(tmpHome, ".claude", "settings.json"), "utf8");
|
|
2598
2599
|
assert.match(settings, /SessionStart/);
|
|
2599
2600
|
assert.match(settings, /PreToolUse/);
|
|
@@ -2606,10 +2607,10 @@ describe("install.js", () => {
|
|
|
2606
2607
|
it("lowercase code is normalized", () => {
|
|
2607
2608
|
const tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), "qualia-install-"));
|
|
2608
2609
|
try {
|
|
2609
|
-
const r = runInstall("qs-fawzi-
|
|
2610
|
+
const r = runInstall("qs-fawzi-11", tmpHome);
|
|
2610
2611
|
assert.equal(r.status, 0);
|
|
2611
2612
|
const config = JSON.parse(fs.readFileSync(path.join(tmpHome, ".claude", ".qualia-config.json"), "utf8"));
|
|
2612
|
-
assert.equal(config.code, "QS-FAWZI-
|
|
2613
|
+
assert.equal(config.code, "QS-FAWZI-11");
|
|
2613
2614
|
} finally {
|
|
2614
2615
|
fs.rmSync(tmpHome, { recursive: true, force: true });
|
|
2615
2616
|
}
|
|
@@ -2618,10 +2619,10 @@ describe("install.js", () => {
|
|
|
2618
2619
|
it("O/0 typo tolerance in code suffix", () => {
|
|
2619
2620
|
const tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), "qualia-install-"));
|
|
2620
2621
|
try {
|
|
2621
|
-
const r = runInstall("QS-
|
|
2622
|
+
const r = runInstall("QS-HASAN-O2", tmpHome);
|
|
2622
2623
|
assert.equal(r.status, 0);
|
|
2623
2624
|
const config = JSON.parse(fs.readFileSync(path.join(tmpHome, ".claude", ".qualia-config.json"), "utf8"));
|
|
2624
|
-
assert.equal(config.code, "QS-
|
|
2625
|
+
assert.equal(config.code, "QS-HASAN-02");
|
|
2625
2626
|
} finally {
|
|
2626
2627
|
fs.rmSync(tmpHome, { recursive: true, force: true });
|
|
2627
2628
|
}
|
|
@@ -2672,10 +2673,10 @@ describe("install.js", () => {
|
|
|
2672
2673
|
it("whitespace-padded code is accepted", () => {
|
|
2673
2674
|
const tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), "qualia-install-"));
|
|
2674
2675
|
try {
|
|
2675
|
-
const r = runInstall(" QS-FAWZI-
|
|
2676
|
+
const r = runInstall(" QS-FAWZI-11 ", tmpHome);
|
|
2676
2677
|
assert.equal(r.status, 0);
|
|
2677
2678
|
const config = JSON.parse(fs.readFileSync(path.join(tmpHome, ".claude", ".qualia-config.json"), "utf8"));
|
|
2678
|
-
assert.equal(config.code, "QS-FAWZI-
|
|
2679
|
+
assert.equal(config.code, "QS-FAWZI-11");
|
|
2679
2680
|
} finally {
|
|
2680
2681
|
fs.rmSync(tmpHome, { recursive: true, force: true });
|
|
2681
2682
|
}
|
|
@@ -2689,7 +2690,7 @@ describe("install.js", () => {
|
|
|
2689
2690
|
customKey: "preserved",
|
|
2690
2691
|
env: { MY_CUSTOM_VAR: "hello" },
|
|
2691
2692
|
}));
|
|
2692
|
-
const r = runInstall("QS-FAWZI-
|
|
2693
|
+
const r = runInstall("QS-FAWZI-11", tmpHome);
|
|
2693
2694
|
assert.equal(r.status, 0);
|
|
2694
2695
|
const settings = JSON.parse(fs.readFileSync(path.join(tmpHome, ".claude", "settings.json"), "utf8"));
|
|
2695
2696
|
assert.equal(settings.customKey, "preserved");
|
|
@@ -2704,7 +2705,7 @@ describe("install.js", () => {
|
|
|
2704
2705
|
it("knowledge files created on first install", () => {
|
|
2705
2706
|
const tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), "qualia-install-"));
|
|
2706
2707
|
try {
|
|
2707
|
-
runInstall("QS-FAWZI-
|
|
2708
|
+
runInstall("QS-FAWZI-11", tmpHome);
|
|
2708
2709
|
assert.ok(fs.existsSync(path.join(tmpHome, ".claude", "knowledge", "learned-patterns.md")));
|
|
2709
2710
|
assert.ok(fs.existsSync(path.join(tmpHome, ".claude", "knowledge", "common-fixes.md")));
|
|
2710
2711
|
assert.ok(fs.existsSync(path.join(tmpHome, ".claude", "knowledge", "client-prefs.md")));
|
|
@@ -2716,10 +2717,10 @@ describe("install.js", () => {
|
|
|
2716
2717
|
it("re-install preserves user edits in knowledge files", () => {
|
|
2717
2718
|
const tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), "qualia-install-"));
|
|
2718
2719
|
try {
|
|
2719
|
-
runInstall("QS-FAWZI-
|
|
2720
|
+
runInstall("QS-FAWZI-11", tmpHome);
|
|
2720
2721
|
fs.appendFileSync(path.join(tmpHome, ".claude", "knowledge", "learned-patterns.md"),
|
|
2721
2722
|
"\n## CUSTOM LEARNING — DO NOT OVERWRITE\n");
|
|
2722
|
-
runInstall("QS-FAWZI-
|
|
2723
|
+
runInstall("QS-FAWZI-11", tmpHome);
|
|
2723
2724
|
const content = fs.readFileSync(path.join(tmpHome, ".claude", "knowledge", "learned-patterns.md"), "utf8");
|
|
2724
2725
|
assert.match(content, /CUSTOM LEARNING/);
|
|
2725
2726
|
} finally {
|
|
@@ -2732,7 +2733,7 @@ describe("install.js", () => {
|
|
|
2732
2733
|
const tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), "qualia-install-"));
|
|
2733
2734
|
try {
|
|
2734
2735
|
// Fresh install first, then inject a user-owned hook, then reinstall.
|
|
2735
|
-
runInstall("QS-FAWZI-
|
|
2736
|
+
runInstall("QS-FAWZI-11", tmpHome);
|
|
2736
2737
|
const settingsPath = path.join(tmpHome, ".claude", "settings.json");
|
|
2737
2738
|
const before = JSON.parse(fs.readFileSync(settingsPath, "utf8"));
|
|
2738
2739
|
|
|
@@ -2746,7 +2747,7 @@ describe("install.js", () => {
|
|
|
2746
2747
|
before.hooks.PreToolUse = [userHook, ...(before.hooks.PreToolUse || [])];
|
|
2747
2748
|
fs.writeFileSync(settingsPath, JSON.stringify(before, null, 2));
|
|
2748
2749
|
|
|
2749
|
-
const r = runInstall("QS-FAWZI-
|
|
2750
|
+
const r = runInstall("QS-FAWZI-11", tmpHome);
|
|
2750
2751
|
assert.equal(r.status, 0);
|
|
2751
2752
|
const after = JSON.parse(fs.readFileSync(settingsPath, "utf8"));
|
|
2752
2753
|
const allCmds = [];
|
|
@@ -2770,7 +2771,7 @@ describe("install.js", () => {
|
|
|
2770
2771
|
it("templates copied to qualia-templates/", () => {
|
|
2771
2772
|
const tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), "qualia-install-"));
|
|
2772
2773
|
try {
|
|
2773
|
-
runInstall("QS-FAWZI-
|
|
2774
|
+
runInstall("QS-FAWZI-11", tmpHome);
|
|
2774
2775
|
const tmplDir = path.join(tmpHome, ".claude", "qualia-templates");
|
|
2775
2776
|
assert.ok(fs.existsSync(tmplDir));
|
|
2776
2777
|
const files = fs.readdirSync(tmplDir);
|
|
@@ -2783,7 +2784,7 @@ describe("install.js", () => {
|
|
|
2783
2784
|
it("agents copied", () => {
|
|
2784
2785
|
const tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), "qualia-install-"));
|
|
2785
2786
|
try {
|
|
2786
|
-
runInstall("QS-FAWZI-
|
|
2787
|
+
runInstall("QS-FAWZI-11", tmpHome);
|
|
2787
2788
|
const agentDir = path.join(tmpHome, ".claude", "agents");
|
|
2788
2789
|
assert.ok(fs.existsSync(agentDir));
|
|
2789
2790
|
const files = fs.readdirSync(agentDir);
|
|
@@ -2796,7 +2797,7 @@ describe("install.js", () => {
|
|
|
2796
2797
|
it("rules copied", () => {
|
|
2797
2798
|
const tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), "qualia-install-"));
|
|
2798
2799
|
try {
|
|
2799
|
-
runInstall("QS-FAWZI-
|
|
2800
|
+
runInstall("QS-FAWZI-11", tmpHome);
|
|
2800
2801
|
const rulesDir = path.join(tmpHome, ".claude", "rules");
|
|
2801
2802
|
assert.ok(fs.existsSync(rulesDir));
|
|
2802
2803
|
const files = fs.readdirSync(rulesDir);
|
|
@@ -2812,7 +2813,7 @@ describe("install.js", () => {
|
|
|
2812
2813
|
it("config version matches package.json", () => {
|
|
2813
2814
|
const tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), "qualia-install-"));
|
|
2814
2815
|
try {
|
|
2815
|
-
runInstall("QS-FAWZI-
|
|
2816
|
+
runInstall("QS-FAWZI-11", tmpHome);
|
|
2816
2817
|
const config = JSON.parse(fs.readFileSync(path.join(tmpHome, ".claude", ".qualia-config.json"), "utf8"));
|
|
2817
2818
|
assert.equal(config.version, PKG_VERSION);
|
|
2818
2819
|
} finally {
|
package/tests/state.test.sh
CHANGED
|
@@ -75,6 +75,47 @@ Goal: Test goal
|
|
|
75
75
|
PLAN
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
+
make_valid_contract() {
|
|
79
|
+
local dir="$1"
|
|
80
|
+
local phase="${2:-1}"
|
|
81
|
+
cat > "$dir/.planning/phase-${phase}-contract.json" <<'JSON'
|
|
82
|
+
{
|
|
83
|
+
"version": 1,
|
|
84
|
+
"phase": 1,
|
|
85
|
+
"goal": "Test goal",
|
|
86
|
+
"why": "Exercise machine evidence",
|
|
87
|
+
"generated_at": "2026-05-23T00:00:00.000Z",
|
|
88
|
+
"generated_by": "manual",
|
|
89
|
+
"source_plan_hash": "",
|
|
90
|
+
"success_criteria": ["Machine check passes"],
|
|
91
|
+
"tasks": [
|
|
92
|
+
{
|
|
93
|
+
"id": "T1",
|
|
94
|
+
"title": "Machine check",
|
|
95
|
+
"wave": 1,
|
|
96
|
+
"depends_on": [],
|
|
97
|
+
"persona": "none",
|
|
98
|
+
"files_modify": [],
|
|
99
|
+
"files_create": [],
|
|
100
|
+
"files_delete": [],
|
|
101
|
+
"acceptance_criteria": ["Node command exits 0"],
|
|
102
|
+
"action": "Run deterministic evidence check",
|
|
103
|
+
"context_files": [],
|
|
104
|
+
"verification": [
|
|
105
|
+
{
|
|
106
|
+
"type": "command-exit",
|
|
107
|
+
"command": "node",
|
|
108
|
+
"args": ["-e", "process.exit(0)"],
|
|
109
|
+
"expected_exit": 0,
|
|
110
|
+
"timeout_ms": 5000
|
|
111
|
+
}
|
|
112
|
+
]
|
|
113
|
+
}
|
|
114
|
+
]
|
|
115
|
+
}
|
|
116
|
+
JSON
|
|
117
|
+
}
|
|
118
|
+
|
|
78
119
|
echo "=== state.js Behavioral Tests ==="
|
|
79
120
|
echo ""
|
|
80
121
|
|
|
@@ -213,6 +254,33 @@ else
|
|
|
213
254
|
fail_case "built → verified(pass) auto-advance" "exit=$EXIT out=$OUT"
|
|
214
255
|
fi
|
|
215
256
|
|
|
257
|
+
# 6b. verified(pass) refuses when a contract exists but machine evidence is missing
|
|
258
|
+
TMP=$(make_project)
|
|
259
|
+
make_valid_plan "$TMP" 1
|
|
260
|
+
make_valid_contract "$TMP" 1
|
|
261
|
+
(cd "$TMP" && $NODE "$STATE_JS" transition --to planned >/dev/null 2>&1)
|
|
262
|
+
(cd "$TMP" && $NODE "$STATE_JS" transition --to built --tasks-done 1 --tasks-total 1 >/dev/null 2>&1)
|
|
263
|
+
echo "result: PASS" > "$TMP/.planning/phase-1-verification.md"
|
|
264
|
+
OUT=$(cd "$TMP" && $NODE "$STATE_JS" transition --to verified --verification pass 2>&1)
|
|
265
|
+
EXIT=$?
|
|
266
|
+
if [ "$EXIT" -ne 0 ] && echo "$OUT" | grep -q '"error": "MISSING_EVIDENCE"'; then
|
|
267
|
+
pass "verified(pass) refuses missing machine evidence when contract exists"
|
|
268
|
+
else
|
|
269
|
+
fail_case "verified(pass) missing evidence guard" "exit=$EXIT out=$OUT"
|
|
270
|
+
fi
|
|
271
|
+
|
|
272
|
+
# 6c. verified(pass) succeeds after contract-runner writes passing evidence
|
|
273
|
+
(cd "$TMP" && $NODE "$FRAMEWORK_DIR/bin/contract-runner.js" .planning/phase-1-contract.json >/dev/null 2>&1)
|
|
274
|
+
OUT=$(cd "$TMP" && $NODE "$STATE_JS" transition --to verified --verification pass 2>&1)
|
|
275
|
+
EXIT=$?
|
|
276
|
+
if [ "$EXIT" -eq 0 ] \
|
|
277
|
+
&& echo "$OUT" | grep -q '"ok": true' \
|
|
278
|
+
&& echo "$OUT" | grep -q '"phase": 2'; then
|
|
279
|
+
pass "verified(pass) accepts passing machine evidence"
|
|
280
|
+
else
|
|
281
|
+
fail_case "verified(pass) with machine evidence" "exit=$EXIT out=$OUT"
|
|
282
|
+
fi
|
|
283
|
+
|
|
216
284
|
# 7. built → verified(fail) stays on phase 1, records verification=fail
|
|
217
285
|
TMP=$(make_project)
|
|
218
286
|
make_valid_plan "$TMP" 1
|