qualia-framework 6.2.9 → 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 (93) hide show
  1. package/AGENTS.md +1 -0
  2. package/CLAUDE.md +1 -0
  3. package/README.md +26 -30
  4. package/agents/builder.md +7 -7
  5. package/agents/planner.md +39 -3
  6. package/agents/research-synthesizer.md +1 -1
  7. package/agents/researcher.md +3 -3
  8. package/agents/roadmapper.md +7 -7
  9. package/agents/verifier.md +18 -6
  10. package/agents/visual-evaluator.md +8 -7
  11. package/bin/cli.js +160 -16
  12. package/bin/command-surface.js +71 -0
  13. package/bin/contract-runner.js +219 -0
  14. package/bin/harness-eval.js +296 -0
  15. package/bin/host-adapters.js +66 -0
  16. package/bin/install.js +116 -172
  17. package/bin/knowledge-flush.js +21 -10
  18. package/bin/knowledge.js +1 -1
  19. package/bin/plan-contract.js +99 -2
  20. package/bin/planning-hygiene.js +262 -0
  21. package/bin/project-snapshot.js +20 -0
  22. package/bin/report-payload.js +18 -0
  23. package/bin/runtime-manifest.js +35 -0
  24. package/bin/state-ledger.js +184 -0
  25. package/bin/state.js +330 -20
  26. package/bin/trust-score.js +268 -0
  27. package/bin/work-packet.js +228 -0
  28. package/docs/erp-contract.md +81 -1
  29. package/docs/onboarding.html +4 -14
  30. package/guide.md +16 -16
  31. package/hooks/fawzi-approval-guard.js +143 -0
  32. package/hooks/pre-deploy-gate.js +74 -1
  33. package/hooks/session-start.js +29 -1
  34. package/package.json +1 -1
  35. package/qualia-design/design-rubric.md +17 -5
  36. package/qualia-design/frontend.md +6 -2
  37. package/qualia-design/graphics.md +47 -0
  38. package/rules/codex-goal.md +1 -1
  39. package/rules/command-output.md +35 -0
  40. package/rules/one-opinion.md +2 -2
  41. package/rules/speed.md +0 -1
  42. package/skills/qualia/SKILL.md +12 -12
  43. package/skills/qualia-build/SKILL.md +20 -14
  44. package/skills/qualia-discuss/SKILL.md +10 -10
  45. package/skills/qualia-doctor/SKILL.md +140 -0
  46. package/skills/qualia-feature/SKILL.md +24 -22
  47. package/skills/qualia-fix/SKILL.md +216 -0
  48. package/skills/qualia-handoff/SKILL.md +9 -9
  49. package/skills/qualia-learn/SKILL.md +11 -11
  50. package/skills/qualia-map/SKILL.md +2 -2
  51. package/skills/qualia-milestone/SKILL.md +15 -15
  52. package/skills/qualia-new/REFERENCE.md +9 -9
  53. package/skills/qualia-new/SKILL.md +14 -14
  54. package/skills/qualia-optimize/REFERENCE.md +1 -1
  55. package/skills/qualia-optimize/SKILL.md +23 -16
  56. package/skills/qualia-plan/SKILL.md +23 -13
  57. package/skills/qualia-polish/REFERENCE.md +15 -15
  58. package/skills/qualia-polish/SKILL.md +81 -21
  59. package/skills/qualia-polish/scripts/loop.mjs +3 -3
  60. package/skills/qualia-polish/scripts/score.mjs +9 -3
  61. package/skills/{qualia-vibe/scripts/extract.mjs → qualia-polish/scripts/vibe-extract.mjs} +5 -5
  62. package/skills/{qualia-vibe/scripts/tokens.mjs → qualia-polish/scripts/vibe-tokens.mjs} +6 -6
  63. package/skills/qualia-postmortem/SKILL.md +9 -9
  64. package/skills/qualia-report/SKILL.md +23 -23
  65. package/skills/qualia-research/SKILL.md +5 -5
  66. package/skills/qualia-review/SKILL.md +28 -12
  67. package/skills/qualia-road/SKILL.md +30 -22
  68. package/skills/qualia-ship/SKILL.md +31 -24
  69. package/skills/qualia-test/SKILL.md +5 -5
  70. package/skills/qualia-verify/SKILL.md +45 -23
  71. package/skills/zoho-workflow/SKILL.md +1 -1
  72. package/templates/help.html +11 -20
  73. package/tests/bin.test.sh +178 -76
  74. package/tests/hooks.test.sh +81 -1
  75. package/tests/install-smoke.test.sh +35 -5
  76. package/tests/lib.test.sh +432 -0
  77. package/tests/published-install-smoke.test.sh +4 -3
  78. package/tests/refs.test.sh +9 -4
  79. package/tests/runner.js +32 -28
  80. package/tests/skills.test.sh +4 -4
  81. package/tests/state.test.sh +133 -3
  82. package/skills/qualia-debug/SKILL.md +0 -185
  83. package/skills/qualia-flush/SKILL.md +0 -198
  84. package/skills/qualia-help/SKILL.md +0 -74
  85. package/skills/qualia-hook-gen/SKILL.md +0 -206
  86. package/skills/qualia-idk/SKILL.md +0 -166
  87. package/skills/qualia-issues/SKILL.md +0 -151
  88. package/skills/qualia-pause/SKILL.md +0 -68
  89. package/skills/qualia-resume/SKILL.md +0 -52
  90. package/skills/qualia-skill-new/SKILL.md +0 -173
  91. package/skills/qualia-triage/SKILL.md +0 -152
  92. package/skills/qualia-vibe/SKILL.md +0 -226
  93. package/skills/qualia-zoom/SKILL.md +0 -51
@@ -154,6 +154,46 @@ echo '{"role":""}' > "$TMP/.claude/.qualia-config.json"
154
154
  assert_exit "empty role field → blocked" 2 $?
155
155
  rm -rf "$TMP"
156
156
 
157
+ # --- fawzi-approval-guard.js ---
158
+ echo ""
159
+ echo "fawzi-approval-guard:"
160
+
161
+ TMP=$(mktemp -d)
162
+ mkdir -p "$TMP/.claude"
163
+ cat > "$TMP/.claude/.qualia-config.json" <<'EOF'
164
+ {"code":"QS-HASAN-02","installed_by":"Hasan","role":"EMPLOYEE","erp":{"enabled":false}}
165
+ EOF
166
+ OUT=$(echo '{"tool_input":{"content":"Fawzi said ok, ship it now."}}' | HOME="$TMP" $NODE "$HOOKS_DIR/fawzi-approval-guard.js" 2>&1)
167
+ RC=$?
168
+ if [ "$RC" -eq 0 ] \
169
+ && [ -z "$OUT" ] \
170
+ && [ -f "$TMP/.claude/.approval-policy-events.json" ] \
171
+ && grep -q '"proxy_owner_approval_claim"' "$TMP/.claude/.approval-policy-events.json" \
172
+ && grep -q '"total": 1' "$TMP/.claude/.approval-policy-events.json"; then
173
+ echo " ✓ employee proxy approval claim → silently recorded"
174
+ PASS=$((PASS + 1))
175
+ else
176
+ echo " ✗ employee proxy approval claim record failed (exit=$RC out=$OUT)"
177
+ FAIL=$((FAIL + 1))
178
+ fi
179
+ rm -rf "$TMP"
180
+
181
+ TMP=$(mktemp -d)
182
+ mkdir -p "$TMP/.claude"
183
+ cat > "$TMP/.claude/.qualia-config.json" <<'EOF'
184
+ {"code":"QS-FAWZI-11","installed_by":"Fawzi Goussous","role":"OWNER","erp":{"enabled":false}}
185
+ EOF
186
+ echo '{"tool_input":{"content":"Fawzi said ok, ship it now."}}' | HOME="$TMP" $NODE "$HOOKS_DIR/fawzi-approval-guard.js" >/dev/null 2>&1
187
+ RC=$?
188
+ if [ "$RC" -eq 0 ] && [ ! -f "$TMP/.claude/.approval-policy-events.json" ]; then
189
+ echo " ✓ OWNER phrasing → not recorded as employee violation"
190
+ PASS=$((PASS + 1))
191
+ else
192
+ echo " ✗ OWNER phrasing should not record (exit=$RC)"
193
+ FAIL=$((FAIL + 1))
194
+ fi
195
+ rm -rf "$TMP"
196
+
157
197
  # --- pre-push.js ---
158
198
  echo ""
159
199
  echo "pre-push:"
@@ -184,6 +224,27 @@ rm -rf "$TMP"
184
224
  echo ""
185
225
  echo "pre-deploy-gate:"
186
226
 
227
+ TMP=$(mktemp -d)
228
+ mkdir -p "$TMP/.claude" "$TMP/proj/.planning"
229
+ cat > "$TMP/.claude/.qualia-config.json" <<'EOF'
230
+ {"code":"QS-HASAN-02","installed_by":"Hasan","role":"EMPLOYEE"}
231
+ EOF
232
+ cat > "$TMP/proj/.planning/tracking.json" <<'EOF'
233
+ {"status":"built","verification":"pending","next_command":"/qualia-report"}
234
+ EOF
235
+ OUT=$(cd "$TMP/proj" && echo '{"tool_input":{"command":"QUALIA_SHIP_FORCE=1 vercel --prod"}}' | HOME="$TMP" $NODE "$HOOKS_DIR/pre-deploy-gate.js" 2>&1)
236
+ RC=$?
237
+ if [ "$RC" -eq 2 ] \
238
+ && echo "$OUT" | grep -q "OWNER-only" \
239
+ && echo "$OUT" | grep -q "/qualia-report"; then
240
+ echo " ✓ employee force ship → blocked with short next command"
241
+ PASS=$((PASS + 1))
242
+ else
243
+ echo " ✗ employee force ship block failed (exit=$RC out=$OUT)"
244
+ FAIL=$((FAIL + 1))
245
+ fi
246
+ rm -rf "$TMP"
247
+
187
248
  # Empty project (no package.json, no tsconfig) → nothing to gate → exit 0
188
249
  TMP=$(mktemp -d)
189
250
  (cd "$TMP" && $NODE "$HOOKS_DIR/pre-deploy-gate.js" >/dev/null 2>&1)
@@ -338,6 +399,25 @@ Status: setup
338
399
  EOF
339
400
  (cd "$TMP" && $NODE "$HOOKS_DIR/session-start.js" >/dev/null 2>&1)
340
401
  assert_exit "exits 0 with STATE.md" 0 $?
402
+
403
+ cat > "$TMP/.planning/work-packet.json" <<'EOF'
404
+ {
405
+ "id": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
406
+ "project_id": "7b5d3b4e-2b8a-4de4-91a1-9b2f3182f5ef",
407
+ "deadline_date": "2026-06-01",
408
+ "next_command": "/qualia-verify",
409
+ "project": { "name": "Acme Portal" }
410
+ }
411
+ EOF
412
+ OUT=$(cd "$TMP" && $NODE "$HOOKS_DIR/session-start.js" 2>&1)
413
+ RC=$?
414
+ if [ "$RC" -eq 0 ] && echo "$OUT" | grep -q "Acme Portal" && echo "$OUT" | grep -q "/qualia-verify"; then
415
+ echo " ✓ renders local ERP work packet context"
416
+ PASS=$((PASS + 1))
417
+ else
418
+ echo " ✗ ERP work packet context missing (exit=$RC)"
419
+ FAIL=$((FAIL + 1))
420
+ fi
341
421
  rm -rf "$TMP"
342
422
 
343
423
  # pre-compact.js removed in v6.2.0 — state.js journal provides crash safety.
@@ -348,7 +428,7 @@ echo "auto-update:"
348
428
 
349
429
  TMP=$(mktemp -d)
350
430
  mkdir -p "$TMP/.claude"
351
- echo '{"code":"QS-FAWZI-01","version":"99.99.99"}' > "$TMP/.claude/.qualia-config.json"
431
+ echo '{"code":"QS-FAWZI-11","version":"99.99.99"}' > "$TMP/.claude/.qualia-config.json"
352
432
  HOME="$TMP" $NODE "$HOOKS_DIR/auto-update.js" >/dev/null 2>&1
353
433
  assert_exit "exits 0 (fast path)" 0 $?
354
434
  # Should now have cache file
@@ -61,8 +61,15 @@ fi
61
61
 
62
62
  tar -xzf "$TARBALL_PATH" -C "$TMP" 2>"$TMP/tar.err"
63
63
  if [ -f "$TMP/package/bin/install.js" ] \
64
+ && [ -f "$TMP/package/bin/runtime-manifest.js" ] \
65
+ && [ -f "$TMP/package/bin/command-surface.js" ] \
66
+ && [ -f "$TMP/package/bin/host-adapters.js" ] \
64
67
  && [ -f "$TMP/package/bin/report-payload.js" ] \
68
+ && [ -f "$TMP/package/bin/work-packet.js" ] \
65
69
  && [ -f "$TMP/package/bin/project-snapshot.js" ] \
70
+ && [ -f "$TMP/package/bin/harness-eval.js" ] \
71
+ && [ -f "$TMP/package/bin/planning-hygiene.js" ] \
72
+ && [ -f "$TMP/package/bin/state-ledger.js" ] \
66
73
  && [ -f "$TMP/package/AGENTS.md" ] \
67
74
  && [ -f "$TMP/package/CLAUDE.md" ]; then
68
75
  pass "tarball contains installer + Claude/Codex instruction roots"
@@ -70,7 +77,7 @@ else
70
77
  fail_case "tarball missing install surfaces" "$(cat "$TMP/tar.err" 2>/dev/null)"
71
78
  fi
72
79
 
73
- 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
74
81
  EXIT=$?
75
82
  if [ "$EXIT" -eq 0 ]; then
76
83
  pass "packaged installer exits 0 for target=Both"
@@ -89,14 +96,27 @@ fi
89
96
  if [ -f "$HOME_DIR/.codex/AGENTS.md" ] \
90
97
  && [ -f "$HOME_DIR/.codex/hooks.json" ] \
91
98
  && [ -f "$HOME_DIR/.codex/config.toml" ] \
99
+ && [ -f "$HOME_DIR/.codex/bin/runtime-manifest.js" ] \
100
+ && [ -f "$HOME_DIR/.codex/bin/command-surface.js" ] \
101
+ && [ -f "$HOME_DIR/.codex/bin/host-adapters.js" ] \
92
102
  && [ -f "$HOME_DIR/.codex/bin/statusline.js" ] \
103
+ && [ -f "$HOME_DIR/.codex/bin/state-ledger.js" ] \
104
+ && [ -f "$HOME_DIR/.codex/bin/contract-runner.js" ] \
105
+ && [ -f "$HOME_DIR/.codex/bin/harness-eval.js" ] \
106
+ && [ -f "$HOME_DIR/.codex/bin/trust-score.js" ] \
107
+ && [ -f "$HOME_DIR/.codex/bin/work-packet.js" ] \
93
108
  && [ -f "$HOME_DIR/.codex/bin/project-snapshot.js" ] \
109
+ && [ -f "$HOME_DIR/.codex/bin/planning-hygiene.js" ] \
94
110
  && [ -f "$HOME_DIR/.codex/agents/planner.toml" ] \
95
111
  && [ -f "$HOME_DIR/.codex/skills/qualia-new/SKILL.md" ] \
96
112
  && [ -f "$HOME_DIR/.codex/qualia-references/questioning.md" ] \
97
113
  && grep -q "Role: OWNER" "$HOME_DIR/.codex/AGENTS.md" \
98
114
  && ! grep -q "{{ROLE}}" "$HOME_DIR/.codex/AGENTS.md" \
115
+ && grep -q "status_line" "$HOME_DIR/.codex/config.toml" \
116
+ && grep -q "model-with-reasoning" "$HOME_DIR/.codex/config.toml" \
117
+ && grep -q "task-progress" "$HOME_DIR/.codex/config.toml" \
99
118
  && grep -q "pre-deploy-gate.js" "$HOME_DIR/.codex/hooks.json" \
119
+ && ! grep -R "\${QUALIA_BIN}" "$HOME_DIR/.codex/skills" >/dev/null 2>&1 \
100
120
  && ! grep -R "\.claude/bin" "$HOME_DIR/.codex/skills" >/dev/null 2>&1; then
101
121
  pass "Codex runtime installed with OWNER role"
102
122
  else
@@ -104,19 +124,29 @@ else
104
124
  fi
105
125
 
106
126
  if [ -d "$HOME_DIR/.claude/hooks" ] \
107
- && [ "$(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" ] \
108
129
  && [ ! -f "$HOME_DIR/.claude/hooks/pre-compact.js" ]; then
109
- pass "packaged install has 11 hooks and no pre-compact"
130
+ pass "packaged install has 12 hooks and no pre-compact"
110
131
  else
111
132
  fail_case "packaged hook set mismatch"
112
133
  fi
113
134
 
114
135
  if [ -f "$HOME_DIR/.claude/bin/report-payload.js" ] \
136
+ && [ -f "$HOME_DIR/.claude/bin/work-packet.js" ] \
115
137
  && [ -f "$HOME_DIR/.claude/bin/project-snapshot.js" ] \
138
+ && [ -f "$HOME_DIR/.claude/bin/runtime-manifest.js" ] \
139
+ && [ -f "$HOME_DIR/.claude/bin/command-surface.js" ] \
140
+ && [ -f "$HOME_DIR/.claude/bin/host-adapters.js" ] \
141
+ && [ -f "$HOME_DIR/.claude/bin/state-ledger.js" ] \
142
+ && [ -f "$HOME_DIR/.claude/bin/contract-runner.js" ] \
143
+ && [ -f "$HOME_DIR/.claude/bin/harness-eval.js" ] \
144
+ && [ -f "$HOME_DIR/.claude/bin/trust-score.js" ] \
145
+ && [ -f "$HOME_DIR/.claude/bin/planning-hygiene.js" ] \
116
146
  && grep -q "report-payload.js" "$HOME_DIR/.claude/skills/qualia-report/SKILL.md"; then
117
- pass "packaged install includes ERP report/snapshot helpers"
147
+ pass "packaged install includes ERP report/snapshot + contract helpers"
118
148
  else
119
- fail_case "packaged install missing ERP report/snapshot helpers"
149
+ fail_case "packaged install missing ERP report/snapshot/contract helpers"
120
150
  fi
121
151
 
122
152
  PKG_VERSION=$("$NODE" -e "console.log(require(process.argv[1]).version)" "$TMP/package/package.json")
package/tests/lib.test.sh CHANGED
@@ -149,6 +149,123 @@ console.log(d2.drift ? "DRIFT-DETECTED" : "FAIL2");
149
149
  ' | tail -1 > /tmp/qfdrift.out
150
150
  [ "$(cat /tmp/qfdrift.out)" = "DRIFT-DETECTED" ] && ok "drift detection" || fail "drift detection: $(cat /tmp/qfdrift.out)"
151
151
 
152
+ # CLI validate
153
+ TMP=$(mktmp)
154
+ cat > "$TMP/contract.json" <<JSON
155
+ {
156
+ "version": 1,
157
+ "phase": 1,
158
+ "goal": "contract cli",
159
+ "why": "prove validator is executable",
160
+ "generated_at": "2026-05-23T00:00:00Z",
161
+ "generated_by": "manual",
162
+ "source_plan_hash": "",
163
+ "success_criteria": ["contract validates"],
164
+ "tasks": [{
165
+ "id": "T1",
166
+ "title": "Create evidence",
167
+ "wave": 1,
168
+ "depends_on": [],
169
+ "files_modify": [],
170
+ "files_create": ["out.txt"],
171
+ "files_delete": [],
172
+ "acceptance_criteria": ["out.txt exists"],
173
+ "action": "Create out.txt",
174
+ "context_files": [],
175
+ "verification": [{ "type": "file-exists", "path": "out.txt" }]
176
+ }]
177
+ }
178
+ JSON
179
+ OUT=$($NODE "$PC" validate "$TMP/contract.json" 2>&1)
180
+ [ "$OUT" = "VALID $TMP/contract.json" ] && ok "plan-contract CLI validates contract" || fail "plan-contract CLI validate: $OUT"
181
+
182
+ # ─── contract-runner ───────────────────────────────────────
183
+
184
+ CR="$FRAMEWORK_DIR/bin/contract-runner.js"
185
+ $NODE --check "$CR" >/dev/null 2>&1 && ok "contract-runner.js parses" || fail "contract-runner.js parse"
186
+
187
+ TMP=$(mktmp)
188
+ mkdir -p "$TMP/src" "$TMP/.planning"
189
+ echo "export const wired = true;" > "$TMP/src/feature.ts"
190
+ cat > "$TMP/.planning/phase-1-contract.json" <<JSON
191
+ {
192
+ "version": 1,
193
+ "phase": 1,
194
+ "goal": "runner pass",
195
+ "why": "prove checks execute",
196
+ "generated_at": "2026-05-23T00:00:00Z",
197
+ "generated_by": "manual",
198
+ "source_plan_hash": "",
199
+ "success_criteria": ["all checks pass"],
200
+ "tasks": [{
201
+ "id": "T1",
202
+ "title": "Check feature",
203
+ "wave": 1,
204
+ "depends_on": [],
205
+ "files_modify": ["src/feature.ts"],
206
+ "files_create": [],
207
+ "files_delete": [],
208
+ "acceptance_criteria": ["feature exists and is wired"],
209
+ "action": "Validate feature",
210
+ "context_files": [],
211
+ "verification": [
212
+ { "type": "file-exists", "path": "src/feature.ts", "must_contain": "wired" },
213
+ { "type": "grep-match", "path": "src/feature.ts", "pattern": "wired\\\\s*=\\\\s*true", "expect": "present" },
214
+ { "type": "command-exit", "command": "printf", "args": ["contract-ok"], "expected_exit": 0, "expect_stdout_match": "contract-ok" },
215
+ { "type": "behavioral", "description": "evidence file includes wired flag", "evidence_required": [{ "path": "src/feature.ts", "description": "feature evidence", "matcher": "wired" }] }
216
+ ]
217
+ }]
218
+ }
219
+ JSON
220
+ OUT=$(cd "$TMP" && $NODE "$CR" .planning/phase-1-contract.json 2>&1)
221
+ if echo "$OUT" | grep -q "PASS phase 1: 4 check" && [ -f "$TMP/.planning/evidence/phase-1-contract-run.json" ]; then
222
+ ok "contract-runner executes passing contract and writes evidence"
223
+ else
224
+ fail "contract-runner pass/evidence: $OUT"
225
+ fi
226
+
227
+ OUT=$(cd "$TMP" && $NODE "$CR" .planning/phase-1-contract.json --json 2>/dev/null)
228
+ if echo "$OUT" | grep -q '"ok": true' && echo "$OUT" | grep -q '"checked": 4'; then
229
+ ok "contract-runner --json reports checked count"
230
+ else
231
+ fail "contract-runner json output: $OUT"
232
+ fi
233
+
234
+ TMP=$(mktmp)
235
+ mkdir -p "$TMP/.planning"
236
+ cat > "$TMP/.planning/phase-1-contract.json" <<JSON
237
+ {
238
+ "version": 1,
239
+ "phase": 1,
240
+ "goal": "runner fail",
241
+ "why": "prove failures block",
242
+ "generated_at": "2026-05-23T00:00:00Z",
243
+ "generated_by": "manual",
244
+ "source_plan_hash": "",
245
+ "success_criteria": ["failure is detected"],
246
+ "tasks": [{
247
+ "id": "T1",
248
+ "title": "Missing file",
249
+ "wave": 1,
250
+ "depends_on": [],
251
+ "files_modify": [],
252
+ "files_create": [],
253
+ "files_delete": [],
254
+ "acceptance_criteria": ["missing file fails"],
255
+ "action": "Validate missing file",
256
+ "context_files": [],
257
+ "verification": [{ "type": "file-exists", "path": "missing.txt" }]
258
+ }]
259
+ }
260
+ JSON
261
+ OUT=$(cd "$TMP" && $NODE "$CR" .planning/phase-1-contract.json 2>&1)
262
+ EXIT=$?
263
+ if [ "$EXIT" -eq 1 ] && echo "$OUT" | grep -q "missing file"; then
264
+ ok "contract-runner fails closed on failed check"
265
+ else
266
+ fail "contract-runner failed-check behavior: exit=$EXIT out=$OUT"
267
+ fi
268
+
152
269
  # ─── agent-runs ──────────────────────────────────────────
153
270
 
154
271
  AR="$FRAMEWORK_DIR/bin/agent-runs.js"
@@ -233,6 +350,321 @@ console.log("removed:" + r.removed);
233
350
  ')
234
351
  [ "$RES" = "removed:1" ] && ok "prune --before removes old records" || fail "prune: $RES"
235
352
 
353
+ # ─── state-ledger ────────────────────────────────────────
354
+
355
+ SL="$FRAMEWORK_DIR/bin/state-ledger.js"
356
+ $NODE --check "$SL" >/dev/null 2>&1 && ok "state-ledger.js parses" || fail "state-ledger.js parse"
357
+
358
+ HA="$FRAMEWORK_DIR/bin/host-adapters.js"
359
+ $NODE --check "$HA" >/dev/null 2>&1 && ok "host-adapters.js parses" || fail "host-adapters.js parse"
360
+ RES=$($NODE -e '
361
+ const { renderText } = require("'"$HA"'");
362
+ const c = renderText("node ${QUALIA_BIN}/state.js @${QUALIA_AGENTS}/planner.md", "codex");
363
+ const d = renderText("node ${QUALIA_BIN}/state.js @${QUALIA_AGENTS}/planner.md", "claude");
364
+ console.log(c.includes(".codex/bin/state.js") && c.includes(".codex/agents/planner.md") && d.includes(".claude/bin/state.js") ? "HOST-RENDER-OK" : c + "\n" + d);
365
+ ')
366
+ [ "$RES" = "HOST-RENDER-OK" ] && ok "host-adapters renders Qualia path tokens per host" || fail "host-adapters render: $RES"
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
+
392
+ TMP=$(mktmp)
393
+ RES=$(cd "$TMP" && $NODE -e '
394
+ const ledger = require("'"$SL"'");
395
+ const fs = require("fs");
396
+ fs.mkdirSync(".planning", { recursive: true });
397
+ ledger.append(process.cwd(), {
398
+ action: "init",
399
+ status_after: "setup",
400
+ phase_after: 1,
401
+ state_raw_after: "state-1",
402
+ tracking_raw_after: "{\"status\":\"setup\"}"
403
+ });
404
+ ledger.append(process.cwd(), {
405
+ action: "transition",
406
+ status_before: "setup",
407
+ status_after: "planned",
408
+ phase_before: 1,
409
+ phase_after: 1,
410
+ state_raw_before: "state-1",
411
+ state_raw_after: "state-2",
412
+ tracking_raw_before: "{\"status\":\"setup\"}",
413
+ tracking_raw_after: "{\"status\":\"planned\"}",
414
+ evidence_refs: [".planning/phase-1-plan.md"]
415
+ });
416
+ const v = ledger.validate(process.cwd());
417
+ const lines = fs.readFileSync(ledger.ledgerPath(process.cwd()), "utf8").trim().split(/\n/);
418
+ console.log(v.ok && v.count === 2 && lines.length === 2 ? "CHAIN-OK" : JSON.stringify(v));
419
+ ')
420
+ [ "$RES" = "CHAIN-OK" ] && ok "state-ledger appends and validates hash chain" || fail "state-ledger chain: $RES"
421
+
422
+ RES=$(cd "$TMP" && $NODE -e '
423
+ const ledger = require("'"$SL"'");
424
+ const fs = require("fs");
425
+ const file = ledger.ledgerPath(process.cwd());
426
+ const lines = fs.readFileSync(file, "utf8").trim().split(/\n/);
427
+ const first = JSON.parse(lines[0]);
428
+ first.status_after = "tampered";
429
+ lines[0] = JSON.stringify(first);
430
+ fs.writeFileSync(file, lines.join("\n") + "\n");
431
+ const v = ledger.validate(process.cwd());
432
+ console.log(!v.ok && v.errors.some(e => /event_hash/.test(e)) ? "TAMPER-DETECTED" : JSON.stringify(v));
433
+ ')
434
+ [ "$RES" = "TAMPER-DETECTED" ] && ok "state-ledger detects tampered event" || fail "state-ledger tamper: $RES"
435
+
436
+ # ─── trust-score ─────────────────────────────────────────
437
+
438
+ PH="$FRAMEWORK_DIR/bin/planning-hygiene.js"
439
+ $NODE --check "$PH" >/dev/null 2>&1 && ok "planning-hygiene.js parses" || fail "planning-hygiene.js parse"
440
+
441
+ TMP=$(mktmp)
442
+ mkdir -p "$TMP/.planning"
443
+ touch "$TMP/.planning/PROJECT.md"
444
+ touch "$TMP/.planning/phase-1-plan.md"
445
+ OUT=$(cd "$TMP" && $NODE "$PH" scan 2>&1)
446
+ EXIT=$?
447
+ if [ "$EXIT" -eq 0 ] && echo "$OUT" | grep -q "clean"; then
448
+ ok "planning-hygiene clean project passes"
449
+ else
450
+ fail "planning-hygiene clean project: exit=$EXIT out=$OUT"
451
+ fi
452
+
453
+ TMP=$(mktmp)
454
+ mkdir -p "$TMP/.planning"
455
+ echo debug > "$TMP/.planning/DEBUG-2026-05-23-0100.md"
456
+ echo fix > "$TMP/.planning/FIX-2026-05-23-0101.md"
457
+ echo review > "$TMP/.planning/REVIEW.md"
458
+ echo image > "$TMP/.planning/vibe-after.png"
459
+ OUT=$(cd "$TMP" && $NODE "$PH" scan --json 2>&1)
460
+ EXIT=$?
461
+ if [ "$EXIT" -eq 1 ] \
462
+ && echo "$OUT" | grep -q '"status": "needs_organizing"' \
463
+ && echo "$OUT" | grep -q 'reports/debug/DEBUG-2026-05-23-0100.md' \
464
+ && echo "$OUT" | grep -q 'reports/fix/FIX-2026-05-23-0101.md' \
465
+ && echo "$OUT" | grep -q 'reports/review/REVIEW.md' \
466
+ && echo "$OUT" | grep -q 'assets/vibe/vibe-after.png'; then
467
+ ok "planning-hygiene routes loose artifacts"
468
+ else
469
+ fail "planning-hygiene loose routing: exit=$EXIT out=$OUT"
470
+ fi
471
+
472
+ OUT=$(cd "$TMP" && $NODE "$PH" organize --write 2>&1)
473
+ EXIT=$?
474
+ if [ "$EXIT" -eq 0 ] \
475
+ && [ -f "$TMP/.planning/reports/debug/DEBUG-2026-05-23-0100.md" ] \
476
+ && [ -f "$TMP/.planning/reports/fix/FIX-2026-05-23-0101.md" ] \
477
+ && [ -f "$TMP/.planning/reports/review/REVIEW.md" ] \
478
+ && [ -f "$TMP/.planning/assets/vibe/vibe-after.png" ]; then
479
+ ok "planning-hygiene organize moves loose artifacts"
480
+ else
481
+ fail "planning-hygiene organize: exit=$EXIT out=$OUT"
482
+ fi
483
+
484
+ TS="$FRAMEWORK_DIR/bin/trust-score.js"
485
+ $NODE --check "$TS" >/dev/null 2>&1 && ok "trust-score.js parses" || fail "trust-score.js parse"
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
+
605
+ TMP=$(mktmp)
606
+ OUT=$(HOME="$TMP" $NODE "$TS" --json 2>/dev/null)
607
+ EXIT=$?
608
+ if [ "$EXIT" -eq 1 ] && echo "$OUT" | grep -q '"status": "FAIL"'; then
609
+ ok "trust-score fails closed when no install exists"
610
+ else
611
+ fail "trust-score no-install behavior: exit=$EXIT out=$OUT"
612
+ fi
613
+
614
+ TMP=$(mktmp)
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"
616
+ echo '{"installed_by":"Test","role":"OWNER","erp":{"enabled":false}}' > "$TMP/.claude/.qualia-config.json"
617
+ touch "$TMP/.claude/CLAUDE.md" "$TMP/.claude/settings.json"
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
619
+ touch "$TMP/.claude/bin/$f"
620
+ done
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
622
+ touch "$TMP/.claude/hooks/$h"
623
+ done
624
+ touch "$TMP/.claude/knowledge/index.md" "$TMP/.claude/knowledge/agents.md"
625
+ for f in design-laws.md design-rubric.md design-brand.md design-product.md design-reference.md frontend.md graphics.md; do
626
+ touch "$TMP/.claude/qualia-design/$f"
627
+ done
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
629
+ mkdir -p "$TMP/.claude/skills/$s"
630
+ touch "$TMP/.claude/skills/$s/SKILL.md"
631
+ done
632
+ touch "$TMP/.claude/agents/visual-evaluator.md" "$TMP/.claude/qualia-guide.md" "$TMP/.claude/qualia-templates/help.html"
633
+ cat > "$TMP/project/.planning/STATE.md" <<'EOF'
634
+ Phase: 1 of 1 — Trust
635
+ Status: planned
636
+ EOF
637
+ echo '{"phase":1,"status":"planned"}' > "$TMP/project/.planning/tracking.json"
638
+ cat > "$TMP/project/.planning/phase-1-plan.md" <<'EOF'
639
+ ---
640
+ goal: trust
641
+ ---
642
+ # Plan
643
+ ## Task 1
644
+ **Acceptance Criteria:**
645
+ - ok
646
+ ## Success Criteria
647
+ - [ ] ok
648
+ EOF
649
+ $NODE -e '
650
+ const ledger = require("'"$SL"'");
651
+ ledger.append("'"$TMP"'/project", {
652
+ action: "init",
653
+ status_after: "planned",
654
+ phase_after: 1,
655
+ state_raw_after: "Phase: 1 of 1\nStatus: planned\n",
656
+ tracking_raw_after: "{\"phase\":1,\"status\":\"planned\"}"
657
+ });
658
+ ' >/dev/null
659
+ OUT=$(cd "$TMP/project" && HOME="$TMP" $NODE "$TS" --json 2>/dev/null)
660
+ if echo "$OUT" | grep -q '"status": "DEGRADED"' \
661
+ && echo "$OUT" | grep -q '"score": 88' \
662
+ && echo "$OUT" | grep -q 'JSON contract missing'; then
663
+ ok "trust-score reports 88 degraded when only contract is missing"
664
+ else
665
+ fail "trust-score degraded contract score: $OUT"
666
+ fi
667
+
236
668
  echo ""
237
669
  echo "lib.test.sh: $PASS passed, $FAIL failed"
238
670
  [ "$FAIL" -eq 0 ]
@@ -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