qualia-framework 7.1.0 → 7.2.1

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.
@@ -222,6 +222,21 @@ fi
222
222
  ```
223
223
  Do NOT manually edit STATE.md or tracking.json — state.js handles both.
224
224
 
225
+ ```bash
226
+ # Per-phase celebration card. Reads the freshly-updated state so the phase
227
+ # number, name, and milestone progress bar are accurate. Streak = phases this
228
+ # employee shipped today (from the trace log); omitted if unavailable. Every
229
+ # metric self-omits when missing, so this never fabricates.
230
+ PHASE_NUM=$(node ${QUALIA_BIN}/state.js check 2>/dev/null | node -pe 'try{JSON.parse(require("fs").readFileSync(0)).phase||""}catch{""}')
231
+ PHASE_NAME=$(node ${QUALIA_BIN}/state.js check 2>/dev/null | node -pe 'try{JSON.parse(require("fs").readFileSync(0)).phase_name||""}catch{""}')
232
+ TOTAL_PHASES=$(node ${QUALIA_BIN}/state.js check 2>/dev/null | node -pe 'try{JSON.parse(require("fs").readFileSync(0)).total_phases||""}catch{""}')
233
+ node ${QUALIA_BIN}/qualia-ui.js phase-complete "${PHASE_NUM:-?}" "$PHASE_NAME" \
234
+ ${TASKS_DONE:+tasks=$TASKS_DONE} \
235
+ ${PHASE_NUM:+mdone=$PHASE_NUM} ${TOTAL_PHASES:+mtotal=$TOTAL_PHASES} \
236
+ ${STREAK:+streak=$STREAK} \
237
+ next=/qualia-handoff
238
+ ```
239
+
225
240
  ```bash
226
241
  node ${QUALIA_BIN}/qualia-ui.js end "SHIPPED" "/qualia-handoff"
227
242
  ```
@@ -2,6 +2,14 @@
2
2
  name: zoho-workflow
3
3
  description: "Zoho Invoice + Mail ops via ERP-first routing. Invoices from templates, cover emails, contacts, inbox, payment reminders. Triggers: 'invoice this client', 'send an email', 'check inbox', 'create a Zoho contact', 'payment reminder'."
4
4
  tags: [zoho, invoice, email, billing, crm]
5
+ # Money-moving + client-comms skill — least-privilege scope. ERP-first routing
6
+ # (mcp__qualia-erp__*) is preferred; raw Zoho Books/Mail are the fallback surface.
7
+ allowed-tools:
8
+ - Bash
9
+ - Read
10
+ - mcp__qualia-erp__*
11
+ - mcp__claude_ai_Zoho_Books__*
12
+ - mcp__claude_ai_Mail__*
5
13
  ---
6
14
 
7
15
  # Zoho Workflow
@@ -0,0 +1,23 @@
1
+ {
2
+ "_comment": "R6 golden verifier fixture — a SEEDED STUB the verifier MUST score FAIL. This proves the deterministic verifier (bin/verify-panel.js) cannot default-pass. The fixture contains (a) a surviving CRITICAL finding voted real by majority, and (b) an unvoted HIGH (INSUFFICIENT-EVIDENCE-style: unverified != disproven). Both survive the skeptic round, so the verdict MUST be FAIL (exit 1). If verify-panel ever regresses to a default-3/PASS, r6-golden.test.sh turns red.",
3
+ "phase": 99,
4
+ "lenses": ["security", "correctness"],
5
+ "findings": [
6
+ {
7
+ "lens": "security",
8
+ "file": "lib/supabase/client.ts",
9
+ "line": 12,
10
+ "severity": "CRITICAL",
11
+ "title": "service_role key imported into a client component (auth bypass)",
12
+ "votes": { "real": 3, "notReal": 0 }
13
+ },
14
+ {
15
+ "lens": "correctness",
16
+ "file": "app/api/checkout/route.ts",
17
+ "line": 47,
18
+ "severity": "HIGH",
19
+ "title": "INSUFFICIENT EVIDENCE — payment idempotency unverified, no test backs the happy path",
20
+ "votes": { "real": 0, "notReal": 0 }
21
+ }
22
+ ]
23
+ }
@@ -0,0 +1,171 @@
1
+ #!/bin/bash
2
+ # journey-spine.test.sh — lifecycle cosmetic layer (bin/qualia-ui.js spine/onboard/
3
+ # phase-complete/clockout + barTicks + statusline progress bar).
4
+ # Run: bash tests/journey-spine.test.sh
5
+
6
+ PASS=0
7
+ FAIL=0
8
+ BIN_DIR="$(cd "$(dirname "$0")/../bin" && pwd)"
9
+ NODE="${NODE:-node}"
10
+ UI="$BIN_DIR/qualia-ui.js"
11
+ STATE="$BIN_DIR/state.js"
12
+ STATUSLINE="$BIN_DIR/statusline.js"
13
+
14
+ # strip ANSI for column-accurate assertions
15
+ strip() { sed 's/\x1b\[[0-9;]*m//g'; }
16
+
17
+ assert_contains() { if echo "$2" | strip | grep -qF -- "$3"; then echo " ✓ $1"; PASS=$((PASS+1)); else echo " ✗ $1 (missing '$3')"; FAIL=$((FAIL+1)); fi; }
18
+ assert_not_contains() { if echo "$2" | strip | grep -qF -- "$3"; then echo " ✗ $1 (should not contain '$3')"; FAIL=$((FAIL+1)); else echo " ✓ $1"; PASS=$((PASS+1)); fi; }
19
+
20
+ echo "journey-spine.test.sh — lifecycle cosmetic layer"
21
+ echo ""
22
+
23
+ $NODE -c "$UI" 2>/dev/null && { echo " ✓ qualia-ui syntax valid"; PASS=$((PASS+1)); } || { echo " ✗ qualia-ui syntax invalid"; FAIL=$((FAIL+1)); }
24
+
25
+ # ── barTicks: scaling + clamping ──
26
+ TICKS=$($NODE -e 'const u=require(process.argv[1]); console.log(["a"+u.barTicks(4,5),"b"+u.barTicks(0,5),"c"+u.barTicks(5,5),"d"+u.barTicks(9,5),"e"+u.barTicks(2,0)].join("\n"))' "$UI" | strip)
27
+ assert_contains "barTicks 4/5 → 4 filled" "$TICKS" "a▰▰▰▰▱"
28
+ assert_contains "barTicks 0/5 → all empty" "$TICKS" "b▱▱▱▱▱"
29
+ assert_contains "barTicks 5/5 → all filled" "$TICKS" "c▰▰▰▰▰"
30
+ assert_contains "barTicks clamps over-fill to 5" "$TICKS" "d▰▰▰▰▰"
31
+ assert_contains "barTicks total<1 → empty string" "$TICKS" "e"
32
+
33
+ # ── onboard card ──
34
+ ONB=$($NODE "$UI" onboard "Maria" 2>&1)
35
+ assert_contains "onboard greets by name" "$ONB" "welcome aboard, Maria"
36
+ assert_contains "onboard shows milestone flow" "$ONB" "Plan"
37
+ assert_contains "onboard shows the first move" "$ONB" "/qualia"
38
+
39
+ # ── phase-complete card ──
40
+ PC=$($NODE "$UI" phase-complete 3 "Checkout flow" tasks=6 mdone=4 mtotal=5 streak=3 next=/qualia-build nextname="Order confirmation" 2>&1)
41
+ assert_contains "phase-complete shows phase + name" "$PC" "PHASE 3 COMPLETE"
42
+ assert_contains "phase-complete shows task count" "$PC" "6 tasks"
43
+ assert_contains "phase-complete shows milestone bar" "$PC" "4/5 phases"
44
+ assert_contains "phase-complete 'one more' at 4/5" "$PC" "one more to close the milestone"
45
+ assert_contains "phase-complete shows streak" "$PC" "3 phases shipped today"
46
+ assert_contains "phase-complete shows next command" "$PC" "/qualia-build"
47
+ # streak of 1 must NOT render (not a streak)
48
+ PC1=$($NODE "$UI" phase-complete 1 "Cart" streak=1 2>&1)
49
+ assert_not_contains "phase-complete hides streak=1" "$PC1" "shipped today"
50
+ # milestone complete wording at full
51
+ PCFULL=$($NODE "$UI" phase-complete 5 "Polish" mdone=5 mtotal=5 2>&1)
52
+ assert_contains "phase-complete 'ready to close' at 5/5" "$PCFULL" "milestone ready to close"
53
+
54
+ # ── clockout card ──
55
+ CO=$($NODE "$UI" clockout "Maria" date="Thu Jun 25" phases=3 tasks=14 commits=9 mdone=4 mtotal=5 streak=4 erp=ok 2>&1)
56
+ assert_contains "clockout names the employee" "$CO" "CLOCK OUT"
57
+ assert_contains "clockout shows shipped phases" "$CO" "3 phases"
58
+ assert_contains "clockout shows commits" "$CO" "9 commits"
59
+ assert_contains "clockout shows milestone %" "$CO" "80%"
60
+ assert_contains "clockout shows streak" "$CO" "4 days"
61
+ assert_contains "clockout confirms ERP upload" "$CO" "uploaded to ERP"
62
+ CO_Q=$($NODE "$UI" clockout "Maria" erp=queued 2>&1)
63
+ assert_contains "clockout queued state" "$CO_Q" "queued"
64
+
65
+ # ── spine: needs a multi-milestone project fixture ──
66
+ TMP=$(mktemp -d)
67
+ (
68
+ cd "$TMP"
69
+ $NODE "$STATE" init --project "acme-portal" \
70
+ --phases '[{"name":"Cart","goal":"x"},{"name":"Checkout","goal":"y"},{"name":"Receipts","goal":"z"}]' >/dev/null 2>&1
71
+ mkdir -p .planning
72
+ cat > .planning/JOURNEY.md <<'JEOF'
73
+ ---
74
+ project: "acme-portal"
75
+ ---
76
+ ## Milestone 1 · Foundations
77
+ 1. **Auth**
78
+ ## Milestone 2 · Commerce
79
+ 1. **Cart**
80
+ ## Milestone 3 · Growth
81
+ 1. **Referrals**
82
+ ## Milestone 4 · Handoff
83
+ 1. **Docs**
84
+ JEOF
85
+ )
86
+ SPINE=$(cd "$TMP" && $NODE "$UI" spine 2>&1)
87
+ assert_contains "spine renders the Journey label" "$SPINE" "Journey"
88
+ assert_contains "spine renders all 4 milestones" "$SPINE" "M4"
89
+ assert_contains "spine shows current marker ◆" "$SPINE" "◆"
90
+ assert_contains "spine shows 'you are here'" "$SPINE" "you are here"
91
+
92
+ # caret column == current-marker column (exact alignment)
93
+ ALIGN=$(cd "$TMP" && $NODE "$UI" spine 2>&1 | strip | $NODE -e '
94
+ let s=""; process.stdin.on("data",d=>s+=d).on("end",()=>{
95
+ const lines=s.split("\n").filter(l=>l.length);
96
+ const spine=lines.find(l=>l.includes("Journey"));
97
+ const caret=lines.find(l=>l.includes("you are here"));
98
+ if(!spine||!caret){console.log("NOLINES");return;}
99
+ const markerCol=spine.indexOf("◆");
100
+ const caretCol=caret.indexOf("↑");
101
+ console.log(markerCol===caretCol?"ALIGNED":("MISALIGNED "+markerCol+" vs "+caretCol));
102
+ });
103
+ ')
104
+ assert_contains "spine caret aligns under current marker" "$ALIGN" "ALIGNED"
105
+
106
+ # spine self-skips with <2 milestones (single-milestone project)
107
+ TMP2=$(mktemp -d)
108
+ ( cd "$TMP2" && $NODE "$STATE" init --project "solo" --phases '[{"name":"A","goal":"x"}]' >/dev/null 2>&1 )
109
+ SPINE2=$(cd "$TMP2" && $NODE "$UI" spine 2>&1)
110
+ assert_not_contains "spine self-skips single-milestone project" "$SPINE2" "you are here"
111
+
112
+ # spine outside any project → no crash, no output
113
+ SPINE3=$(cd / && $NODE "$UI" spine 2>&1)
114
+ assert_not_contains "spine no-ops outside a project" "$SPINE3" "Journey"
115
+
116
+ # ── statusline: the always-visible progress bar ──
117
+ TMP3=$(mktemp -d); mkdir -p "$TMP3/.planning"
118
+ cat > "$TMP3/.planning/tracking.json" <<'TEOF'
119
+ {"phase":3,"total_phases":5,"status":"building","milestone":2,"milestone_name":"Commerce","tasks_done":2,"tasks_total":6,"blockers":[]}
120
+ TEOF
121
+ SL=$(printf '{"workspace":{"current_dir":"%s"},"cost":{"total_cost_usd":0},"duration_ms":0}' "$TMP3" | $NODE "$STATUSLINE" 2>&1)
122
+ assert_contains "statusline shows P3/5" "$SL" "P3/5"
123
+ assert_contains "statusline shows the tick bar" "$SL" "▰▰▰▱▱"
124
+
125
+ # ── skill-wiring smoke: run the ACTUAL ship/report closing bash ──
126
+ # These mirror the exact variable plumbing in skills/qualia-ship/SKILL.md and
127
+ # skills/qualia-report/SKILL.md (state.js parsing + ${VAR:+…} expansions), so a
128
+ # regression in the wiring — not just the renderer — is caught.
129
+ TMP4=$(mktemp -d)
130
+ ( cd "$TMP4" && $NODE "$STATE" init --project "wired" \
131
+ --phases '[{"name":"Cart","goal":"x"},{"name":"Checkout","goal":"y"},{"name":"Receipts","goal":"z"}]' >/dev/null 2>&1 )
132
+
133
+ # ---- /qualia-ship closing block (verbatim plumbing) ----
134
+ SHIP_OUT=$(cd "$TMP4" && {
135
+ PHASE_NUM=$($NODE "$STATE" check 2>/dev/null | $NODE -pe 'try{JSON.parse(require("fs").readFileSync(0)).phase||""}catch{""}')
136
+ PHASE_NAME=$($NODE "$STATE" check 2>/dev/null | $NODE -pe 'try{JSON.parse(require("fs").readFileSync(0)).phase_name||""}catch{""}')
137
+ TOTAL_PHASES=$($NODE "$STATE" check 2>/dev/null | $NODE -pe 'try{JSON.parse(require("fs").readFileSync(0)).total_phases||""}catch{""}')
138
+ TASKS_DONE=4
139
+ $NODE "$UI" phase-complete "${PHASE_NUM:-?}" "$PHASE_NAME" \
140
+ ${TASKS_DONE:+tasks=$TASKS_DONE} \
141
+ ${PHASE_NUM:+mdone=$PHASE_NUM} ${TOTAL_PHASES:+mtotal=$TOTAL_PHASES} \
142
+ next=/qualia-handoff
143
+ } 2>&1)
144
+ assert_contains "ship wiring → resolves phase number into card" "$SHIP_OUT" "PHASE 1 COMPLETE"
145
+ assert_contains "ship wiring → tasks plumbed through" "$SHIP_OUT" "4 tasks"
146
+ assert_contains "ship wiring → milestone bar plumbed" "$SHIP_OUT" "phases"
147
+ assert_contains "ship wiring → next command set" "$SHIP_OUT" "/qualia-handoff"
148
+ assert_not_contains "ship wiring → no literal \${VAR" "$SHIP_OUT" '${'
149
+
150
+ # ---- /qualia-report closing block (verbatim plumbing) ----
151
+ REPORT_OUT=$(cd "$TMP4" && {
152
+ EMP_NAME=$($NODE -pe 'try{JSON.parse(require("fs").readFileSync(process.env.HOME+"/.claude/.qualia-config.json","utf8")).installed_by||""}catch{""}' 2>/dev/null)
153
+ MDONE=$($NODE "$STATE" check 2>/dev/null | $NODE -pe 'try{JSON.parse(require("fs").readFileSync(0)).phase||""}catch{""}')
154
+ MTOTAL=$($NODE "$STATE" check 2>/dev/null | $NODE -pe 'try{JSON.parse(require("fs").readFileSync(0)).total_phases||""}catch{""}')
155
+ COUNT=9
156
+ ERP_RESULT=ok
157
+ $NODE "$UI" clockout "$EMP_NAME" date="2026-06-25" \
158
+ ${COUNT:+commits=$COUNT} \
159
+ ${MDONE:+mdone=$MDONE} ${MTOTAL:+mtotal=$MTOTAL} \
160
+ ${ERP_RESULT:+erp=$ERP_RESULT}
161
+ } 2>&1)
162
+ assert_contains "report wiring → renders CLOCK OUT" "$REPORT_OUT" "CLOCK OUT"
163
+ assert_contains "report wiring → commit count plumbed" "$REPORT_OUT" "9 commits"
164
+ assert_contains "report wiring → milestone % plumbed" "$REPORT_OUT" "%"
165
+ assert_contains "report wiring → erp result plumbed" "$REPORT_OUT" "uploaded to ERP"
166
+ assert_not_contains "report wiring → no literal \${VAR" "$REPORT_OUT" '${'
167
+
168
+ rm -rf "$TMP" "$TMP2" "$TMP3" "$TMP4"
169
+ echo ""
170
+ echo "=== Results: $PASS passed, $FAIL failed ==="
171
+ [ "$FAIL" -eq 0 ]
package/tests/lib.test.sh CHANGED
@@ -532,7 +532,7 @@ TMP=$(mktmp)
532
532
  mkdir -p "$TMP/home/.claude/bin" "$TMP/home/.claude/hooks" "$TMP/home/.claude/knowledge/daily-log" "$TMP/home/.claude/qualia-design" "$TMP/home/.claude/agents" "$TMP/home/.claude/qualia-templates" "$TMP/project"
533
533
  echo '{"installed_by":"Test","role":"OWNER","version":"6.3.0","erp":{"enabled":false}}' > "$TMP/home/.claude/.qualia-config.json"
534
534
  touch "$TMP/home/.claude/CLAUDE.md" "$TMP/home/.claude/settings.json"
535
- for f in runtime-manifest.js command-surface.js host-adapters.js state.js qualia-ui.js statusline.js knowledge.js knowledge-flush.js recall.js vault-access.js repo-map.js design-tokens.js batch-plan.js state-ledger.js plan-contract.js contract-runner.js agent-status.js analyze-gate.js verify-panel.js wave-plan.js eval-runner.js branch-hygiene.js last-report.js harness-eval.js trust-score.js agent-runs.js slop-detect.mjs dep-verify.mjs erp-retry.js erp-event.js work-packet.js report-payload.js project-snapshot.js project-sync.js codex-goal.js planning-hygiene.js prune-deprecated.js learning-candidates.js status-snapshot.js security-scan.js auto-report.js; do
535
+ for f in runtime-manifest.js command-surface.js host-adapters.js state.js qualia-ui.js statusline.js knowledge.js knowledge-flush.js recall.js vault-access.js repo-map.js design-tokens.js batch-plan.js state-ledger.js plan-contract.js contract-runner.js agent-status.js analyze-gate.js verify-panel.js wave-plan.js eval-runner.js branch-hygiene.js last-report.js harness-eval.js trust-score.js agent-runs.js slop-detect.mjs dep-verify.mjs erp-retry.js erp-event.js work-packet.js report-payload.js project-snapshot.js project-sync.js codex-goal.js planning-hygiene.js prune-deprecated.js learning-candidates.js status-snapshot.js security-scan.js qualia-doctor.js auto-report.js; do
536
536
  touch "$TMP/home/.claude/bin/$f"
537
537
  done
538
538
  for h in session-start.js auto-update.js branch-guard.js pre-push.js pre-deploy-gate.js migration-guard.js git-guardrails.js stop-session-log.js fawzi-approval-guard.js vercel-account-guard.js env-empty-guard.js supabase-destructive-guard.js secret-guard.js; do
@@ -647,7 +647,7 @@ TMP=$(mktmp)
647
647
  mkdir -p "$TMP/.claude/bin" "$TMP/.claude/hooks" "$TMP/.claude/knowledge/daily-log" "$TMP/.claude/qualia-design" "$TMP/.claude/agents" "$TMP/.claude/qualia-templates" "$TMP/project/.planning"
648
648
  echo '{"installed_by":"Test","role":"OWNER","erp":{"enabled":false}}' > "$TMP/.claude/.qualia-config.json"
649
649
  touch "$TMP/.claude/CLAUDE.md" "$TMP/.claude/settings.json"
650
- for f in runtime-manifest.js command-surface.js host-adapters.js state.js qualia-ui.js statusline.js knowledge.js knowledge-flush.js recall.js vault-access.js repo-map.js design-tokens.js batch-plan.js state-ledger.js plan-contract.js contract-runner.js agent-status.js analyze-gate.js verify-panel.js wave-plan.js eval-runner.js branch-hygiene.js last-report.js harness-eval.js trust-score.js agent-runs.js slop-detect.mjs dep-verify.mjs erp-retry.js erp-event.js work-packet.js report-payload.js project-snapshot.js project-sync.js codex-goal.js planning-hygiene.js prune-deprecated.js learning-candidates.js status-snapshot.js security-scan.js auto-report.js; do
650
+ for f in runtime-manifest.js command-surface.js host-adapters.js state.js qualia-ui.js statusline.js knowledge.js knowledge-flush.js recall.js vault-access.js repo-map.js design-tokens.js batch-plan.js state-ledger.js plan-contract.js contract-runner.js agent-status.js analyze-gate.js verify-panel.js wave-plan.js eval-runner.js branch-hygiene.js last-report.js harness-eval.js trust-score.js agent-runs.js slop-detect.mjs dep-verify.mjs erp-retry.js erp-event.js work-packet.js report-payload.js project-snapshot.js project-sync.js codex-goal.js planning-hygiene.js prune-deprecated.js learning-candidates.js status-snapshot.js security-scan.js qualia-doctor.js auto-report.js; do
651
651
  touch "$TMP/.claude/bin/$f"
652
652
  done
653
653
  for h in session-start.js auto-update.js branch-guard.js pre-push.js pre-deploy-gate.js migration-guard.js git-guardrails.js stop-session-log.js fawzi-approval-guard.js vercel-account-guard.js env-empty-guard.js supabase-destructive-guard.js secret-guard.js; do
@@ -0,0 +1,136 @@
1
+ #!/bin/bash
2
+ # memory-loop.test.sh — the one-loop contract: knowledge-flush vault dual-write
3
+ # (ITEM 13) + qualia-doctor freshness gates (ITEM 14b).
4
+ #
5
+ # Pure-function tests, no agent CLI, no live vault. Each builds a temp QUALIA_HOME
6
+ # and QUALIA_MEMORY so the loop logic is exercised hermetically.
7
+ #
8
+ # Run: bash tests/memory-loop.test.sh
9
+
10
+ PASS=0
11
+ FAIL=0
12
+ DIR="$(cd "$(dirname "$0")" && pwd)"
13
+ BIN_DIR="$(cd "$DIR/../bin" && pwd)"
14
+ NODE="${NODE:-node}"
15
+ FLUSH="$BIN_DIR/knowledge-flush.js"
16
+ DOCTOR="$BIN_DIR/qualia-doctor.js"
17
+
18
+ assert_contains() {
19
+ local name="$1" hay="$2" needle="$3"
20
+ if echo "$hay" | grep -qF "$needle"; then echo " ✓ $name"; PASS=$((PASS+1));
21
+ else echo " ✗ $name (missing '$needle' in: $hay)"; FAIL=$((FAIL+1)); fi
22
+ }
23
+ assert_ok() {
24
+ local name="$1" rc="$2"
25
+ if [ "$rc" -eq 0 ]; then echo " ✓ $name"; PASS=$((PASS+1));
26
+ else echo " ✗ $name (rc=$rc)"; FAIL=$((FAIL+1)); fi
27
+ }
28
+
29
+ echo "memory-loop.test.sh — dual-write + freshness gates"
30
+ echo ""
31
+
32
+ # --- syntax ---
33
+ $NODE -c "$FLUSH" 2>/dev/null && { echo " ✓ knowledge-flush.js syntax"; PASS=$((PASS+1)); } || { echo " ✗ knowledge-flush.js syntax"; FAIL=$((FAIL+1)); }
34
+ $NODE -c "$DOCTOR" 2>/dev/null && { echo " ✓ qualia-doctor.js syntax"; PASS=$((PASS+1)); } || { echo " ✗ qualia-doctor.js syntax"; FAIL=$((FAIL+1)); }
35
+
36
+ # ── ITEM 13: dualWriteVault mirrors curated concepts into the vault ──────────
37
+ TMP=$(mktemp -d); mkdir -p "$TMP/vault"
38
+ OUT=$(QUALIA_HOME="$TMP/home" QUALIA_MEMORY="$TMP/vault" $NODE -e '
39
+ const fs=require("fs"),path=require("path");
40
+ const kh=process.env.QUALIA_HOME;
41
+ fs.mkdirSync(path.join(kh,"knowledge","concepts"),{recursive:true});
42
+ fs.writeFileSync(path.join(kh,"knowledge","learned-patterns.md"),"# Patterns\n- always RLS\n");
43
+ fs.writeFileSync(path.join(kh,"knowledge","concepts","voice-call-state.md"),"# Voice\nstate\n");
44
+ const m=require("'"$FLUSH"'");
45
+ const r1=m.dualWriteVault();
46
+ const files1=fs.readdirSync(m.VAULT_CONCEPTS_DIR).sort();
47
+ const r2=m.dualWriteVault();
48
+ const files2=fs.readdirSync(m.VAULT_CONCEPTS_DIR).sort();
49
+ console.log("detail="+r1.event_detail);
50
+ console.log("written="+r1.written);
51
+ console.log("idempotent="+(files1.length===files2.length && files1.join(",")===files2.join(",")));
52
+ console.log("files="+files2.join(","));
53
+ const sample=fs.readFileSync(path.join(m.VAULT_CONCEPTS_DIR,files2[0]),"utf8");
54
+ console.log("hasFrontmatter="+sample.includes("source: qualia-framework/knowledge-flush"));
55
+ ' 2>&1)
56
+ assert_contains "dual-write mirrors concepts (vault-mirrored)" "$OUT" "detail=vault-mirrored"
57
+ assert_contains "dual-write wrote 2 files" "$OUT" "written=2"
58
+ assert_contains "dual-write is idempotent" "$OUT" "idempotent=true"
59
+ assert_contains "dual-write emits a concept-prefixed subdir file" "$OUT" "concept-voice-call-state.md"
60
+ assert_contains "dual-write emits curated top-level file" "$OUT" "learned-patterns.md"
61
+ assert_contains "dual-write stamps provenance front-matter" "$OUT" "hasFrontmatter=true"
62
+ rm -rf "$TMP"
63
+
64
+ # --- vault absent → graceful skip, no crash ---
65
+ TMP=$(mktemp -d)
66
+ OUT=$(QUALIA_HOME="$TMP/home" QUALIA_MEMORY="$TMP/does-not-exist" $NODE -e '
67
+ const fs=require("fs"),path=require("path");
68
+ const kh=process.env.QUALIA_HOME;
69
+ fs.mkdirSync(path.join(kh,"knowledge"),{recursive:true});
70
+ fs.writeFileSync(path.join(kh,"knowledge","learned-patterns.md"),"# P\n- x\n");
71
+ const m=require("'"$FLUSH"'");
72
+ const r=m.dualWriteVault();
73
+ console.log("detail="+r.event_detail+" written="+r.written);
74
+ ' 2>&1)
75
+ assert_ok "vault-absent does not crash" $?
76
+ assert_contains "vault-absent is skipped gracefully" "$OUT" "detail=vault-absent"
77
+ rm -rf "$TMP"
78
+
79
+ # ── ITEM 14b: freshness gates ───────────────────────────────────────────────
80
+ # Healthy loop: fresh daily-log, recent flush log, fresh export with matching
81
+ # coverage and no deprecated rows → all PASS.
82
+ TMP=$(mktemp -d)
83
+ KH="$TMP/home"; VAULT="$TMP/vault"
84
+ mkdir -p "$KH/knowledge/daily-log"
85
+ TODAY=$(date +%Y-%m-%d)
86
+ echo "# log" > "$KH/knowledge/daily-log/$TODAY.md"
87
+ printf '{"timestamp":"%s","event":"ok"}\n' "$(date -Iseconds)" > "$KH/.qualia-flush.log"
88
+ mkdir -p "$VAULT/wiki/sessions/concepts" "$VAULT/wiki/_export/concepts"
89
+ echo "# c1" > "$VAULT/wiki/sessions/concepts/c1.md"
90
+ echo "# c1 exported" > "$VAULT/wiki/_export/concepts/c1.md"
91
+ OUT=$(QUALIA_HOME="$KH" QUALIA_MEMORY="$VAULT" $NODE "$DOCTOR" --json 2>&1)
92
+ assert_contains "healthy daily-log → PASS" "$OUT" '"gate": "daily-log freshness"'
93
+ HEALTHY_FAILS=$(echo "$OUT" | grep -c '"status": "FAIL"')
94
+ if [ "$HEALTHY_FAILS" -eq 0 ]; then echo " ✓ healthy loop has zero FAIL gates"; PASS=$((PASS+1)); else echo " ✗ healthy loop has $HEALTHY_FAILS FAIL gate(s): $OUT"; FAIL=$((FAIL+1)); fi
95
+ rm -rf "$TMP"
96
+
97
+ # Broken loop: no daily-log, no flush log, no export → FAILs + --exit-code 1.
98
+ TMP=$(mktemp -d)
99
+ mkdir -p "$TMP/home/knowledge" "$TMP/vault"
100
+ OUT=$(QUALIA_HOME="$TMP/home" QUALIA_MEMORY="$TMP/vault" $NODE "$DOCTOR" --json 2>&1)
101
+ assert_contains "broken loop flags stale daily-log" "$OUT" 'no daily-log entries'
102
+ assert_contains "broken loop flags missing flush log" "$OUT" 'flush never ran'
103
+ assert_contains "broken loop flags missing export" "$OUT" 'export never ran'
104
+ QUALIA_HOME="$TMP/home" QUALIA_MEMORY="$TMP/vault" $NODE "$DOCTOR" --exit-code >/dev/null 2>&1
105
+ RC=$?
106
+ if [ "$RC" -eq 1 ]; then echo " ✓ broken loop --exit-code returns 1"; PASS=$((PASS+1)); else echo " ✗ broken loop --exit-code returned $RC"; FAIL=$((FAIL+1)); fi
107
+ rm -rf "$TMP"
108
+
109
+ # Coverage gate: source has 2 concepts, export has 1 → FAIL (stale/partial).
110
+ TMP=$(mktemp -d)
111
+ KH="$TMP/home"; VAULT="$TMP/vault"
112
+ mkdir -p "$KH/knowledge/daily-log"
113
+ echo "# log" > "$KH/knowledge/daily-log/$(date +%Y-%m-%d).md"
114
+ printf '{"timestamp":"%s","event":"ok"}\n' "$(date -Iseconds)" > "$KH/.qualia-flush.log"
115
+ mkdir -p "$VAULT/wiki/sessions/concepts" "$VAULT/wiki/_export/concepts"
116
+ echo "# a" > "$VAULT/wiki/sessions/concepts/a.md"
117
+ echo "# b" > "$VAULT/wiki/sessions/concepts/b.md"
118
+ echo "# a" > "$VAULT/wiki/_export/concepts/a.md"
119
+ OUT=$(QUALIA_HOME="$KH" QUALIA_MEMORY="$VAULT" $NODE "$DOCTOR" --json 2>&1)
120
+ assert_contains "partial export → coverage FAIL" "$OUT" 'export is stale or partial'
121
+ rm -rf "$TMP"
122
+
123
+ # Deprecated gate: an export file tagged deprecated → FAIL.
124
+ TMP=$(mktemp -d)
125
+ KH="$TMP/home"; VAULT="$TMP/vault"
126
+ mkdir -p "$KH/knowledge/daily-log" "$VAULT/wiki/_export"
127
+ echo "# log" > "$KH/knowledge/daily-log/$(date +%Y-%m-%d).md"
128
+ printf '{"timestamp":"%s","event":"ok"}\n' "$(date -Iseconds)" > "$KH/.qualia-flush.log"
129
+ printf -- '---\ntags: [deprecated, old]\n---\n# stale\n' > "$VAULT/wiki/_export/old.md"
130
+ OUT=$(QUALIA_HOME="$KH" QUALIA_MEMORY="$VAULT" $NODE "$DOCTOR" --json 2>&1)
131
+ assert_contains "deprecated row in export → FAIL" "$OUT" 'deprecated file(s)'
132
+ rm -rf "$TMP"
133
+
134
+ echo ""
135
+ echo "=== Results: $PASS passed, $FAIL failed ==="
136
+ [ "$FAIL" -eq 0 ] && exit 0 || exit 1
@@ -0,0 +1,66 @@
1
+ #!/bin/bash
2
+ # r6-golden.test.sh — R6 golden verifier fixture.
3
+ #
4
+ # Proves the deterministic verifier (bin/verify-panel.js) CANNOT default-pass.
5
+ # The fixture tests/fixtures/r6-golden-fail-panel.json is a SEEDED STUB
6
+ # engineered so the only correct verdict is FAIL: it carries a surviving
7
+ # CRITICAL (voted real 3-0) plus an unvoted HIGH (INSUFFICIENT-EVIDENCE style —
8
+ # unverified is not disproven, so it survives the skeptic round).
9
+ #
10
+ # If verify-panel ever regresses to scoring 3/PASS on input it cannot fully
11
+ # clear, this turns red. It is the framework gating its OWN verifier the way it
12
+ # gates user projects (the missing R6 piece from the v7 audit, F3).
13
+ #
14
+ # CONSTRAINT: this test ONLY adds the fixture wiring. It does not touch the
15
+ # anti-default-3 logic, the gates, or Codex parity — it observes them.
16
+ #
17
+ # Run: bash tests/r6-golden.test.sh
18
+
19
+ PASS=0
20
+ FAIL=0
21
+ DIR="$(cd "$(dirname "$0")" && pwd)"
22
+ BIN_DIR="$(cd "$DIR/../bin" && pwd)"
23
+ NODE="${NODE:-node}"
24
+ VP="$BIN_DIR/verify-panel.js"
25
+ FIXTURE="$DIR/fixtures/r6-golden-fail-panel.json"
26
+
27
+ assert_exit() {
28
+ local name="$1" expected="$2" actual="$3"
29
+ if [ "$expected" = "$actual" ]; then echo " ✓ $name"; PASS=$((PASS+1));
30
+ else echo " ✗ $name (expected exit $expected, got $actual)"; FAIL=$((FAIL+1)); fi
31
+ }
32
+ assert_contains() {
33
+ local name="$1" hay="$2" needle="$3"
34
+ if echo "$hay" | grep -qF "$needle"; then echo " ✓ $name"; PASS=$((PASS+1));
35
+ else echo " ✗ $name (missing '$needle')"; FAIL=$((FAIL+1)); fi
36
+ }
37
+ assert_absent() {
38
+ local name="$1" hay="$2" needle="$3"
39
+ if echo "$hay" | grep -qF "$needle"; then echo " ✗ $name (found forbidden '$needle')"; FAIL=$((FAIL+1));
40
+ else echo " ✓ $name"; PASS=$((PASS+1)); fi
41
+ }
42
+
43
+ echo "r6-golden.test.sh — verifier cannot default-pass"
44
+ echo ""
45
+
46
+ # Fixture must exist (precondition).
47
+ [ -f "$FIXTURE" ] && { echo " ✓ golden fixture present"; PASS=$((PASS+1)); } \
48
+ || { echo " ✗ golden fixture missing: $FIXTURE"; FAIL=$((FAIL+1)); }
49
+
50
+ # The golden stub MUST FAIL (exit 1). This is the anti-default-pass proof.
51
+ $NODE "$VP" "$FIXTURE" >/dev/null 2>&1
52
+ assert_exit "golden stub → FAIL (exit 1), not a default PASS" 1 $?
53
+
54
+ OUT=$($NODE "$VP" "$FIXTURE" --json 2>&1)
55
+ assert_contains "verdict is FAIL" "$OUT" '"verdict": "FAIL"'
56
+ assert_absent "verdict is NOT PASS" "$OUT" '"verdict": "PASS"'
57
+ # ok must be false — the contract kernel treats this as a phase fail.
58
+ assert_contains "ok:false (phase does not ship)" "$OUT" '"ok": false'
59
+ # The surviving CRITICAL is the load-bearing reason it cannot pass.
60
+ assert_contains "surviving CRITICAL preserved" "$OUT" '"severity": "CRITICAL"'
61
+ # The unvoted HIGH survives (unverified != disproven).
62
+ assert_contains "unvoted HIGH survives the skeptic round" "$OUT" '"severity": "HIGH"'
63
+
64
+ echo ""
65
+ echo "=== Results: $PASS passed, $FAIL failed ==="
66
+ [ "$FAIL" -eq 0 ] && exit 0 || exit 1
@@ -184,6 +184,39 @@ for pattern in "${forbidden_surface_patterns[@]}"; do
184
184
  fi
185
185
  done
186
186
 
187
+ # ── Count-drift guard ─────────────────────────────────────────────────────────
188
+ # The orientation docs (README.md / guide.md) hardcode skill/hook/rule counts.
189
+ # These rot every time a skill/hook/rule is added. Assert each stated count
190
+ # matches the live directory listing so the numbers can't silently drift again.
191
+ # skills/ = directory entries
192
+ # hooks/ = *.js files
193
+ # rules/ = *.md files
194
+ SKILL_COUNT=$(find "$FRAMEWORK_ROOT/skills" -mindepth 1 -maxdepth 1 -type d | wc -l | tr -d ' ')
195
+ HOOK_COUNT=$(find "$FRAMEWORK_ROOT/hooks" -maxdepth 1 -name '*.js' | wc -l | tr -d ' ')
196
+ RULE_COUNT=$(find "$FRAMEWORK_ROOT/rules" -maxdepth 1 -name '*.md' | wc -l | tr -d ' ')
197
+
198
+ # assert_count <label> <expected-actual> <file> <regex-with-NUM-placeholder>
199
+ # Fails if the file contains a "<N> <label>" claim where N != actual.
200
+ assert_count() {
201
+ local label="$1" actual="$2" file="$3" pattern="$4"
202
+ local bad
203
+ # Find every "<digits> <label>" mention; flag any whose number != actual.
204
+ # grep -oE (no -n) yields just the matched "<N> label" substrings; pull the
205
+ # leading integer from each and compare. (No line-number prefix to confuse it.)
206
+ bad=$(grep -oE "$pattern" "$file" 2>/dev/null | grep -oE '^[0-9]+' | grep -vx "$actual" || true)
207
+ if [ -n "$bad" ]; then
208
+ fail_case "count drift: $label in $(basename "$file")" \
209
+ "stated $(echo "$bad" | paste -sd, -) but actual is $actual — regenerate from the directory listing"
210
+ else
211
+ pass "$(basename "$file") $label count = $actual (matches $label/)"
212
+ fi
213
+ }
214
+
215
+ assert_count "skills" "$SKILL_COUNT" "$FRAMEWORK_ROOT/README.md" '[0-9]+ (installed )?skills'
216
+ assert_count "hooks" "$HOOK_COUNT" "$FRAMEWORK_ROOT/README.md" '[0-9]+ hooks'
217
+ assert_count "rules" "$RULE_COUNT" "$FRAMEWORK_ROOT/README.md" '[0-9]+ (installed )?rules'
218
+ assert_count "skills" "$SKILL_COUNT" "$FRAMEWORK_ROOT/guide.md" '[0-9]+ active skills'
219
+
187
220
  echo ""
188
221
  echo "Results: $PASS passed, $FAIL failed"
189
222
 
package/tests/run-all.sh CHANGED
@@ -35,6 +35,9 @@ SUITES=(
35
35
  "repo-map"
36
36
  "design-tokens"
37
37
  "batch-plan"
38
+ "r6-golden"
39
+ "memory-loop"
40
+ "journey-spine"
38
41
  )
39
42
 
40
43
  FAILED=()