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.
Files changed (58) hide show
  1. package/AGENTS.md +1 -0
  2. package/CLAUDE.md +1 -0
  3. package/README.md +16 -23
  4. package/bin/cli.js +49 -2
  5. package/bin/command-surface.js +71 -0
  6. package/bin/harness-eval.js +296 -0
  7. package/bin/install.js +17 -20
  8. package/bin/knowledge-flush.js +21 -10
  9. package/bin/knowledge.js +1 -1
  10. package/bin/project-snapshot.js +20 -0
  11. package/bin/report-payload.js +18 -0
  12. package/bin/runtime-manifest.js +3 -0
  13. package/bin/state.js +31 -0
  14. package/bin/trust-score.js +3 -11
  15. package/bin/work-packet.js +228 -0
  16. package/docs/erp-contract.md +81 -1
  17. package/docs/onboarding.html +0 -11
  18. package/guide.md +14 -15
  19. package/hooks/fawzi-approval-guard.js +143 -0
  20. package/hooks/pre-deploy-gate.js +74 -1
  21. package/hooks/session-start.js +29 -1
  22. package/package.json +1 -1
  23. package/qualia-design/frontend.md +2 -2
  24. package/rules/codex-goal.md +1 -1
  25. package/rules/one-opinion.md +2 -2
  26. package/rules/speed.md +0 -1
  27. package/skills/qualia/SKILL.md +4 -4
  28. package/skills/qualia-feature/SKILL.md +1 -1
  29. package/skills/qualia-fix/SKILL.md +4 -4
  30. package/skills/qualia-learn/SKILL.md +1 -1
  31. package/skills/qualia-polish/REFERENCE.md +1 -1
  32. package/skills/qualia-polish/SKILL.md +19 -4
  33. package/skills/{qualia-vibe/scripts/extract.mjs → qualia-polish/scripts/vibe-extract.mjs} +4 -4
  34. package/skills/{qualia-vibe/scripts/tokens.mjs → qualia-polish/scripts/vibe-tokens.mjs} +6 -6
  35. package/skills/qualia-road/SKILL.md +15 -20
  36. package/skills/qualia-ship/SKILL.md +12 -5
  37. package/skills/qualia-verify/SKILL.md +9 -1
  38. package/templates/help.html +1 -12
  39. package/tests/bin.test.sh +144 -72
  40. package/tests/hooks.test.sh +81 -1
  41. package/tests/install-smoke.test.sh +13 -3
  42. package/tests/lib.test.sh +145 -3
  43. package/tests/published-install-smoke.test.sh +4 -3
  44. package/tests/refs.test.sh +9 -4
  45. package/tests/runner.js +29 -28
  46. package/tests/state.test.sh +68 -0
  47. package/skills/qualia-debug/SKILL.md +0 -193
  48. package/skills/qualia-flush/SKILL.md +0 -198
  49. package/skills/qualia-help/SKILL.md +0 -74
  50. package/skills/qualia-hook-gen/SKILL.md +0 -206
  51. package/skills/qualia-idk/SKILL.md +0 -166
  52. package/skills/qualia-issues/SKILL.md +0 -151
  53. package/skills/qualia-pause/SKILL.md +0 -68
  54. package/skills/qualia-resume/SKILL.md +0 -52
  55. package/skills/qualia-skill-new/SKILL.md +0 -173
  56. package/skills/qualia-triage/SKILL.md +0 -152
  57. package/skills/qualia-vibe/SKILL.md +0 -229
  58. 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-01\n3\n' | HOME="$HOME_DIR" "$NODE" "$TMP/package/bin/install.js" >"$TMP/install.log" 2>&1
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 ' ')" = "11" ] \
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 11 hooks and no pre-compact"
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-doctor qualia-road qualia-resume qualia-pause qualia-report qualia-polish qualia-vibe; do
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-01\n3\n' >"$TMP/install.input"
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 ' ')" = "11" ] \
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 11 hooks and no pre-compact"
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
@@ -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 skills/qualia-{name}/SKILL.md.
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" "no skills/$name/SKILL.md — referenced by: $locations"
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-01/);
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-01",
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-01", version: "99.99.99",
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-01",
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-01", tmpHome);
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-01", tmpHome);
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-01");
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-01", tmpHome);
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("11 hooks installed (v6.2.0: pre-compact removed; v5.0: vercel-account-guard, env-empty-guard, supabase-destructive-guard added)", () => {
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-01", tmpHome);
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, 11, `expected 11 hooks, got ${hooks.length}: ${hooks.join(", ")}`);
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-01", tmpHome);
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-01", tmpHome);
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-01");
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-FAWZI-O1", tmpHome);
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-FAWZI-01");
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-01 ", tmpHome);
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-01");
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-01", tmpHome);
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-01", tmpHome);
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-01", tmpHome);
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-01", tmpHome);
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-01", tmpHome);
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-01", tmpHome);
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-01", tmpHome);
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-01", tmpHome);
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-01", tmpHome);
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-01", tmpHome);
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 {
@@ -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