qualia-framework 3.1.0 → 3.2.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.
@@ -34,29 +34,6 @@ for f in "$HOOKS_DIR"/*.js; do
34
34
  fi
35
35
  done
36
36
 
37
- # --- block-env-edit.js ---
38
- echo ""
39
- echo "block-env-edit:"
40
-
41
- echo '{"tool_input":{"file_path":".env.local"}}' | $NODE "$HOOKS_DIR/block-env-edit.js" > /dev/null 2>&1
42
- assert_exit "blocks .env.local" 2 $?
43
-
44
- echo '{"tool_input":{"file_path":".env.production"}}' | $NODE "$HOOKS_DIR/block-env-edit.js" > /dev/null 2>&1
45
- assert_exit "blocks .env.production" 2 $?
46
-
47
- echo '{"tool_input":{"file_path":".env"}}' | $NODE "$HOOKS_DIR/block-env-edit.js" > /dev/null 2>&1
48
- assert_exit "blocks .env" 2 $?
49
-
50
- # Windows-style path with backslashes (normalized by the hook)
51
- echo '{"tool_input":{"file_path":"C:\\project\\.env.local"}}' | $NODE "$HOOKS_DIR/block-env-edit.js" > /dev/null 2>&1
52
- assert_exit "blocks windows .env.local" 2 $?
53
-
54
- echo '{"tool_input":{"file_path":"src/app.tsx"}}' | $NODE "$HOOKS_DIR/block-env-edit.js" > /dev/null 2>&1
55
- assert_exit "allows src/app.tsx" 0 $?
56
-
57
- echo '{"tool_input":{"file_path":"components/Footer.tsx"}}' | $NODE "$HOOKS_DIR/block-env-edit.js" > /dev/null 2>&1
58
- assert_exit "allows components/Footer.tsx" 0 $?
59
-
60
37
  # --- migration-guard.js ---
61
38
  echo ""
62
39
  echo "migration-guard:"
@@ -107,11 +84,11 @@ TMP=$(setup_guard_repo main OWNER)
107
84
  assert_exit "OWNER on main → allowed" 0 $?
108
85
  rm -rf "$TMP"
109
86
 
110
- # EMPLOYEE on main → blocked (exit 2)
87
+ # EMPLOYEE on main → blocked (exit 1)
111
88
  TMP=$(setup_guard_repo main EMPLOYEE)
112
89
  OUT=$(cd "$TMP/proj" && HOME="$TMP" $NODE "$HOOKS_DIR/branch-guard.js" 2>&1)
113
90
  RC=$?
114
- if [ "$RC" -eq 2 ] && echo "$OUT" | grep -q "BLOCKED" && echo "$OUT" | grep -q "main"; then
91
+ if [ "$RC" -eq 1 ] && echo "$OUT" | grep -q "BLOCKED" && echo "$OUT" | grep -q "main"; then
115
92
  echo " ✓ EMPLOYEE on main → blocked (BLOCKED in stdout)"
116
93
  PASS=$((PASS + 1))
117
94
  else
@@ -123,7 +100,7 @@ rm -rf "$TMP"
123
100
  # EMPLOYEE on master → blocked
124
101
  TMP=$(setup_guard_repo master EMPLOYEE)
125
102
  (cd "$TMP/proj" && HOME="$TMP" $NODE "$HOOKS_DIR/branch-guard.js" >/dev/null 2>&1)
126
- assert_exit "EMPLOYEE on master → blocked" 2 $?
103
+ assert_exit "EMPLOYEE on master → blocked" 1 $?
127
104
  rm -rf "$TMP"
128
105
 
129
106
  # EMPLOYEE on feature branch → allowed
@@ -138,13 +115,13 @@ TMP=$(setup_guard_repo feature/xyz OWNER)
138
115
  assert_exit "OWNER on feature/xyz → allowed" 0 $?
139
116
  rm -rf "$TMP"
140
117
 
141
- # Missing config → fails closed (block, exit 2)
118
+ # Missing config → fails closed (block, exit 1)
142
119
  TMP=$(mktemp -d)
143
120
  mkdir -p "$TMP/proj"
144
121
  (cd "$TMP/proj" && git init -q && git checkout -b feature/x -q 2>/dev/null)
145
122
  # NO .claude/.qualia-config.json
146
123
  (cd "$TMP/proj" && HOME="$TMP" $NODE "$HOOKS_DIR/branch-guard.js" >/dev/null 2>&1)
147
- assert_exit "missing config → blocked (fails closed)" 2 $?
124
+ assert_exit "missing config → blocked (fails closed)" 1 $?
148
125
  rm -rf "$TMP"
149
126
 
150
127
  # Malformed config JSON → fails closed
@@ -153,7 +130,7 @@ mkdir -p "$TMP/proj" "$TMP/.claude"
153
130
  (cd "$TMP/proj" && git init -q && git checkout -b feature/x -q 2>/dev/null)
154
131
  echo 'not json{' > "$TMP/.claude/.qualia-config.json"
155
132
  (cd "$TMP/proj" && HOME="$TMP" $NODE "$HOOKS_DIR/branch-guard.js" >/dev/null 2>&1)
156
- assert_exit "malformed config JSON → blocked" 2 $?
133
+ assert_exit "malformed config JSON → blocked" 1 $?
157
134
  rm -rf "$TMP"
158
135
 
159
136
  # Empty role field → fails closed
@@ -162,7 +139,7 @@ mkdir -p "$TMP/proj" "$TMP/.claude"
162
139
  (cd "$TMP/proj" && git init -q && git checkout -b feature/x -q 2>/dev/null)
163
140
  echo '{"role":""}' > "$TMP/.claude/.qualia-config.json"
164
141
  (cd "$TMP/proj" && HOME="$TMP" $NODE "$HOOKS_DIR/branch-guard.js" >/dev/null 2>&1)
165
- assert_exit "empty role field → blocked" 2 $?
142
+ assert_exit "empty role field → blocked" 1 $?
166
143
  rm -rf "$TMP"
167
144
 
168
145
  # --- pre-push.js ---
@@ -286,52 +263,6 @@ else
286
263
  fi
287
264
  rm -rf "$TMP"
288
265
 
289
- # --- pre-deploy-gate: Server Component / route handler exemptions ---
290
-
291
- # route.ts with service_role → exempt (always server-side)
292
- TMP=$(mktemp -d)
293
- mkdir -p "$TMP/app/api/auth"
294
- echo 'const key = process.env.SUPABASE_SERVICE_ROLE_KEY; export async function POST() {}' > "$TMP/app/api/auth/route.ts"
295
- OUT=$( (cd "$TMP" && $NODE "$HOOKS_DIR/pre-deploy-gate.js") 2>&1 )
296
- RC=$?
297
- assert_exit "route.ts with service_role → exempt (exit 0)" 0 $RC
298
- rm -rf "$TMP"
299
-
300
- # middleware.ts with service_role → exempt (always server-side)
301
- TMP=$(mktemp -d)
302
- echo 'import { service_role } from "./config"; export function middleware() {}' > "$TMP/middleware.ts"
303
- OUT=$( (cd "$TMP" && $NODE "$HOOKS_DIR/pre-deploy-gate.js") 2>&1 )
304
- RC=$?
305
- assert_exit "middleware.ts with service_role → exempt (exit 0)" 0 $RC
306
- rm -rf "$TMP"
307
-
308
- # File in app/api/ with service_role → exempt
309
- TMP=$(mktemp -d)
310
- mkdir -p "$TMP/app/api/webhook"
311
- echo 'const sr = "service_role"; export async function GET() { return new Response(sr); }' > "$TMP/app/api/webhook/route.js"
312
- OUT=$( (cd "$TMP" && $NODE "$HOOKS_DIR/pre-deploy-gate.js") 2>&1 )
313
- RC=$?
314
- assert_exit "app/api/ file with service_role → exempt (exit 0)" 0 $RC
315
- rm -rf "$TMP"
316
-
317
- # File with "use server" directive + service_role → exempt
318
- TMP=$(mktemp -d)
319
- mkdir -p "$TMP/app/admin"
320
- printf '"use server"\nconst key = process.env.SUPABASE_SERVICE_ROLE_KEY;\nexport async function deleteUser() {}\n' > "$TMP/app/admin/actions.ts"
321
- OUT=$( (cd "$TMP" && $NODE "$HOOKS_DIR/pre-deploy-gate.js") 2>&1 )
322
- RC=$?
323
- assert_exit "\"use server\" file with service_role → exempt (exit 0)" 0 $RC
324
- rm -rf "$TMP"
325
-
326
- # Regular app/page.tsx WITHOUT directive + service_role → still blocks
327
- TMP=$(mktemp -d)
328
- mkdir -p "$TMP/app/admin"
329
- echo 'const key = "service_role"; export default function Page() { return <div>{key}</div>; }' > "$TMP/app/admin/page.tsx"
330
- OUT=$( (cd "$TMP" && $NODE "$HOOKS_DIR/pre-deploy-gate.js") 2>&1 )
331
- RC=$?
332
- assert_exit "regular page.tsx with service_role → blocked (exit 1)" 1 $RC
333
- rm -rf "$TMP"
334
-
335
266
  # --- session-start.js — must exit 0 always ---
336
267
  echo ""
337
268
  echo "session-start:"
@@ -45,34 +45,6 @@ fail_case() {
45
45
  FAIL=$((FAIL + 1))
46
46
  }
47
47
 
48
- # Write a minimal valid plan file (passes content validation).
49
- # Usage: make_valid_plan "$TMP" 1
50
- make_valid_plan() {
51
- local dir="$1"
52
- local phase="${2:-1}"
53
- cat > "$dir/.planning/phase-${phase}-plan.md" <<'PLAN'
54
- ---
55
- phase: 1
56
- goal: "Test goal"
57
- tasks: 1
58
- waves: 1
59
- ---
60
-
61
- # Phase 1: Test
62
-
63
- Goal: Test goal
64
-
65
- ## Task 1 — Test task
66
- **Wave:** 1
67
- **Files:** src/test.ts
68
- **Action:** Create test file
69
- **Done when:** File exists
70
-
71
- ## Success Criteria
72
- - [ ] Test passes
73
- PLAN
74
- }
75
-
76
48
  echo "=== state.js Behavioral Tests ==="
77
49
  echo ""
78
50
 
@@ -154,7 +126,7 @@ echo "happy path transitions:"
154
126
 
155
127
  # 4. setup → planned (with plan file)
156
128
  TMP=$(make_project)
157
- make_valid_plan "$TMP" 1
129
+ touch "$TMP/.planning/phase-1-plan.md"
158
130
  OUT=$(cd "$TMP" && $NODE "$STATE_JS" transition --to planned 2>&1)
159
131
  EXIT=$?
160
132
  if [ "$EXIT" -eq 0 ] \
@@ -194,7 +166,7 @@ fi
194
166
 
195
167
  # 7. built → verified(fail) stays on phase 1, records verification=fail
196
168
  TMP=$(make_project)
197
- make_valid_plan "$TMP" 1
169
+ touch "$TMP/.planning/phase-1-plan.md"
198
170
  (cd "$TMP" && $NODE "$STATE_JS" transition --to planned >/dev/null 2>&1)
199
171
  (cd "$TMP" && $NODE "$STATE_JS" transition --to built --tasks-done 3 --tasks-total 5 >/dev/null 2>&1)
200
172
  touch "$TMP/.planning/phase-1-verification.md"
@@ -229,7 +201,7 @@ fi
229
201
 
230
202
  # 9. planned → verified fails (requires status=built)
231
203
  TMP=$(make_project)
232
- make_valid_plan "$TMP" 1
204
+ touch "$TMP/.planning/phase-1-plan.md"
233
205
  (cd "$TMP" && $NODE "$STATE_JS" transition --to planned >/dev/null 2>&1)
234
206
  touch "$TMP/.planning/phase-1-verification.md"
235
207
  OUT=$(cd "$TMP" && $NODE "$STATE_JS" transition --to verified --verification pass 2>&1)
@@ -257,7 +229,7 @@ fi
257
229
 
258
230
  # 11. built → verified with missing verification file → MISSING_FILE
259
231
  TMP=$(make_project)
260
- make_valid_plan "$TMP" 1
232
+ touch "$TMP/.planning/phase-1-plan.md"
261
233
  (cd "$TMP" && $NODE "$STATE_JS" transition --to planned >/dev/null 2>&1)
262
234
  (cd "$TMP" && $NODE "$STATE_JS" transition --to built --tasks-done 1 --tasks-total 1 >/dev/null 2>&1)
263
235
  # NO verification file
@@ -273,7 +245,7 @@ fi
273
245
 
274
246
  # 12. built → verified without --verification → MISSING_ARG
275
247
  TMP=$(make_project)
276
- make_valid_plan "$TMP" 1
248
+ touch "$TMP/.planning/phase-1-plan.md"
277
249
  (cd "$TMP" && $NODE "$STATE_JS" transition --to planned >/dev/null 2>&1)
278
250
  (cd "$TMP" && $NODE "$STATE_JS" transition --to built --tasks-done 1 --tasks-total 1 >/dev/null 2>&1)
279
251
  touch "$TMP/.planning/phase-1-verification.md"
@@ -290,13 +262,13 @@ fi
290
262
  # 13. → shipped without --deployed-url → MISSING_ARG
291
263
  # Must go through polished first, so fabricate state by transitioning through the full path.
292
264
  TMP=$(make_project)
293
- make_valid_plan "$TMP" 1
265
+ touch "$TMP/.planning/phase-1-plan.md"
294
266
  (cd "$TMP" && $NODE "$STATE_JS" transition --to planned >/dev/null 2>&1)
295
267
  (cd "$TMP" && $NODE "$STATE_JS" transition --to built --tasks-done 1 --tasks-total 1 >/dev/null 2>&1)
296
268
  touch "$TMP/.planning/phase-1-verification.md"
297
269
  (cd "$TMP" && $NODE "$STATE_JS" transition --to verified --verification pass >/dev/null 2>&1)
298
270
  # Now on phase 2, status=setup. Run phase 2 to completion.
299
- make_valid_plan "$TMP" 2
271
+ touch "$TMP/.planning/phase-2-plan.md"
300
272
  (cd "$TMP" && $NODE "$STATE_JS" transition --to planned >/dev/null 2>&1)
301
273
  (cd "$TMP" && $NODE "$STATE_JS" transition --to built --tasks-done 1 --tasks-total 1 >/dev/null 2>&1)
302
274
  touch "$TMP/.planning/phase-2-verification.md"
@@ -331,7 +303,7 @@ echo "gap cycle circuit breaker:"
331
303
 
332
304
  # 15. First gap closure: verified(fail) → planned, gap_cycles[1]=1
333
305
  TMP=$(make_project)
334
- make_valid_plan "$TMP" 1
306
+ touch "$TMP/.planning/phase-1-plan.md"
335
307
  touch "$TMP/.planning/phase-1-verification.md"
336
308
  (cd "$TMP" && $NODE "$STATE_JS" transition --to planned >/dev/null 2>&1)
337
309
  (cd "$TMP" && $NODE "$STATE_JS" transition --to built --tasks-done 1 --tasks-total 1 >/dev/null 2>&1)
@@ -374,7 +346,7 @@ fi
374
346
  # 18. verified(pass) resets gap_cycles[1] to 0
375
347
  # Set up a fresh project, do ONE failed cycle, then pass on the next attempt.
376
348
  TMP=$(make_project)
377
- make_valid_plan "$TMP" 1
349
+ touch "$TMP/.planning/phase-1-plan.md"
378
350
  touch "$TMP/.planning/phase-1-verification.md"
379
351
  (cd "$TMP" && $NODE "$STATE_JS" transition --to planned >/dev/null 2>&1)
380
352
  (cd "$TMP" && $NODE "$STATE_JS" transition --to built --tasks-done 1 --tasks-total 1 >/dev/null 2>&1)
@@ -493,7 +465,7 @@ fi
493
465
 
494
466
  # 26. Transition refuses on severity=error (missing Phase: header)
495
467
  TMP=$(make_project)
496
- make_valid_plan "$TMP" 1
468
+ touch "$TMP/.planning/phase-1-plan.md"
497
469
  sed -i.bak '/^Phase:/d' "$TMP/.planning/STATE.md"
498
470
  OUT=$(cd "$TMP" && $NODE "$STATE_JS" transition --to planned 2>&1)
499
471
  EXIT=$?
@@ -541,7 +513,7 @@ fi
541
513
 
542
514
  # 29. After fix, transition that was previously blocked now works
543
515
  TMP=$(make_project)
544
- make_valid_plan "$TMP" 1
516
+ touch "$TMP/.planning/phase-1-plan.md"
545
517
  sed -i.bak '/^Phase:/d' "$TMP/.planning/STATE.md"
546
518
  # Blocked before fix
547
519
  (cd "$TMP" && $NODE "$STATE_JS" transition --to planned 2>&1 | grep -q STATE_SCHEMA_ERROR) || \
@@ -557,156 +529,6 @@ else
557
529
  fail_case "after fix transition" "exit=$EXIT out=$OUT"
558
530
  fi
559
531
 
560
- # ─── Configurable gap cycle limit ────────────────────────
561
- echo ""
562
- echo "configurable gap cycle limit:"
563
-
564
- # 30. gap_cycle_limit=5 allows 3rd gap closure (would fail at default 2)
565
- TMP=$(make_project)
566
- make_valid_plan "$TMP" 1
567
- touch "$TMP/.planning/phase-1-verification.md"
568
- # Set custom limit in tracking.json
569
- TRACKING=$(cat "$TMP/.planning/tracking.json")
570
- echo "$TRACKING" | $NODE -e "
571
- const t = JSON.parse(require('fs').readFileSync(0,'utf8'));
572
- t.gap_cycle_limit = 5;
573
- process.stdout.write(JSON.stringify(t, null, 2));
574
- " > "$TMP/.planning/tracking.json"
575
- # Do 3 gap closure cycles
576
- (cd "$TMP" && $NODE "$STATE_JS" transition --to planned >/dev/null 2>&1)
577
- (cd "$TMP" && $NODE "$STATE_JS" transition --to built --tasks-done 1 --tasks-total 1 >/dev/null 2>&1)
578
- (cd "$TMP" && $NODE "$STATE_JS" transition --to verified --verification fail >/dev/null 2>&1)
579
- (cd "$TMP" && $NODE "$STATE_JS" transition --to planned >/dev/null 2>&1)
580
- (cd "$TMP" && $NODE "$STATE_JS" transition --to built --tasks-done 1 --tasks-total 1 >/dev/null 2>&1)
581
- (cd "$TMP" && $NODE "$STATE_JS" transition --to verified --verification fail >/dev/null 2>&1)
582
- # 3rd closure should succeed (limit is 5, we're at 2)
583
- OUT=$(cd "$TMP" && $NODE "$STATE_JS" transition --to planned 2>&1)
584
- EXIT=$?
585
- if [ "$EXIT" -eq 0 ] \
586
- && echo "$OUT" | grep -q '"ok": true'; then
587
- pass "gap_cycle_limit=5 allows 3rd closure (default would block)"
588
- else
589
- fail_case "custom gap limit" "exit=$EXIT out=$OUT"
590
- fi
591
-
592
- # 31. cmdCheck includes gap_cycle_limit in output
593
- TMP=$(make_project)
594
- OUT=$(cd "$TMP" && $NODE "$STATE_JS" check 2>&1)
595
- if echo "$OUT" | grep -q '"gap_cycle_limit":'; then
596
- pass "cmdCheck includes gap_cycle_limit in output"
597
- else
598
- fail_case "gap_cycle_limit in check" "out=$OUT"
599
- fi
600
-
601
- # ─── Plan content validation ────────────────────────────
602
- echo ""
603
- echo "plan content validation:"
604
-
605
- # 32. validate-plan accepts well-formed plan
606
- TMP=$(make_project)
607
- make_valid_plan "$TMP" 1
608
- OUT=$(cd "$TMP" && $NODE "$STATE_JS" validate-plan --phase 1 2>&1)
609
- EXIT=$?
610
- if [ "$EXIT" -eq 0 ] \
611
- && echo "$OUT" | grep -q '"action": "validate-plan"' \
612
- && echo "$OUT" | grep -q '"task_count": 1'; then
613
- pass "validate-plan accepts well-formed plan"
614
- else
615
- fail_case "validate well-formed plan" "exit=$EXIT out=$OUT"
616
- fi
617
-
618
- # 33. validate-plan rejects empty plan
619
- TMP=$(make_project)
620
- echo "" > "$TMP/.planning/phase-1-plan.md"
621
- OUT=$(cd "$TMP" && $NODE "$STATE_JS" validate-plan --phase 1 2>&1)
622
- EXIT=$?
623
- if [ "$EXIT" -eq 1 ] \
624
- && echo "$OUT" | grep -q '"error": "PLAN_VALIDATION_FAILED"'; then
625
- pass "validate-plan rejects empty plan"
626
- else
627
- fail_case "validate empty plan" "exit=$EXIT out=$OUT"
628
- fi
629
-
630
- # 34. validate-plan rejects plan missing Done when
631
- TMP=$(make_project)
632
- cat > "$TMP/.planning/phase-1-plan.md" <<'EOF'
633
- ---
634
- phase: 1
635
- goal: "Test"
636
- tasks: 1
637
- waves: 1
638
- ---
639
- ## Task 1 — Incomplete
640
- **Wave:** 1
641
- **Files:** test.ts
642
- **Action:** Do something
643
-
644
- ## Success Criteria
645
- - [ ] Works
646
- EOF
647
- OUT=$(cd "$TMP" && $NODE "$STATE_JS" validate-plan --phase 1 2>&1)
648
- EXIT=$?
649
- if [ "$EXIT" -eq 1 ] \
650
- && echo "$OUT" | grep -q "PLAN_VALIDATION_FAILED" \
651
- && echo "$OUT" | grep -q "Done when"; then
652
- pass "validate-plan rejects plan missing 'Done when'"
653
- else
654
- fail_case "validate missing done-when" "exit=$EXIT out=$OUT"
655
- fi
656
-
657
- # 35. Transition to planned with invalid plan content → INVALID_PLAN
658
- TMP=$(make_project)
659
- echo "# Empty plan with no tasks" > "$TMP/.planning/phase-1-plan.md"
660
- OUT=$(cd "$TMP" && $NODE "$STATE_JS" transition --to planned 2>&1)
661
- EXIT=$?
662
- if [ "$EXIT" -eq 1 ] \
663
- && echo "$OUT" | grep -q '"error": "INVALID_PLAN"'; then
664
- pass "transition → planned with invalid plan → INVALID_PLAN"
665
- else
666
- fail_case "transition invalid plan" "exit=$EXIT out=$OUT"
667
- fi
668
-
669
- # ─── Force flag ──────────────────────────────────────────
670
- echo ""
671
- echo "force flag:"
672
-
673
- # 36. --force bypasses precondition failure
674
- TMP=$(make_project)
675
- # setup → built should fail (requires planned first)
676
- OUT=$(cd "$TMP" && $NODE "$STATE_JS" transition --to built --force 2>&1)
677
- EXIT=$?
678
- if [ "$EXIT" -eq 0 ] \
679
- && echo "$OUT" | grep -q '"ok": true' \
680
- && echo "$OUT" | grep -q '"status": "built"'; then
681
- pass "--force bypasses precondition (setup → built)"
682
- else
683
- fail_case "force flag" "exit=$EXIT out=$OUT"
684
- fi
685
-
686
- # 37. --force does NOT bypass MISSING_FILE (planned without plan file)
687
- TMP=$(make_project)
688
- # No plan file exists — force should NOT help
689
- OUT=$(cd "$TMP" && $NODE "$STATE_JS" transition --to planned --force 2>&1)
690
- EXIT=$?
691
- if [ "$EXIT" -eq 1 ] \
692
- && echo "$OUT" | grep -q '"error": "MISSING_FILE"'; then
693
- pass "--force does NOT bypass MISSING_FILE"
694
- else
695
- fail_case "force vs MISSING_FILE" "exit=$EXIT out=$OUT"
696
- fi
697
-
698
- # 38. --force does NOT bypass INVALID_PLAN
699
- TMP=$(make_project)
700
- echo "# No tasks here" > "$TMP/.planning/phase-1-plan.md"
701
- OUT=$(cd "$TMP" && $NODE "$STATE_JS" transition --to planned --force 2>&1)
702
- EXIT=$?
703
- if [ "$EXIT" -eq 1 ] \
704
- && echo "$OUT" | grep -q '"error": "INVALID_PLAN"'; then
705
- pass "--force does NOT bypass INVALID_PLAN"
706
- else
707
- fail_case "force vs INVALID_PLAN" "exit=$EXIT out=$OUT"
708
- fi
709
-
710
532
  # ─── Summary ─────────────────────────────────────────────
711
533
  echo ""
712
534
  echo "=== Results: $PASS passed, $FAIL failed ==="
@@ -1,161 +0,0 @@
1
- # ERP API Contract
2
-
3
- The Qualia Framework optionally uploads session reports to the company ERP at `https://portal.qualiasolutions.net`. This document specifies the API shape.
4
-
5
- ## Configuration
6
-
7
- Stored in `~/.claude/.qualia-config.json`:
8
-
9
- ```json
10
- {
11
- "erp": {
12
- "enabled": true,
13
- "url": "https://portal.qualiasolutions.net",
14
- "api_key_file": ".erp-api-key"
15
- }
16
- }
17
- ```
18
-
19
- The API key is read from `~/.claude/.erp-api-key` (file mode 0600).
20
-
21
- ## Endpoints
22
-
23
- ### POST /api/v1/reports
24
-
25
- Upload a session report.
26
-
27
- **Headers:**
28
- ```
29
- Authorization: Bearer <api-key>
30
- Content-Type: application/json
31
- ```
32
-
33
- **Request Body:**
34
- ```json
35
- {
36
- "project": "client-project-name",
37
- "client": "Client Name",
38
- "phase": 2,
39
- "phase_name": "Authentication & Dashboard",
40
- "total_phases": 4,
41
- "status": "built",
42
- "tasks_done": 5,
43
- "tasks_total": 5,
44
- "verification": "pass",
45
- "gap_cycles": 0,
46
- "deployed_url": "https://client.vercel.app",
47
- "session_duration_minutes": 45,
48
- "commits": ["abc1234", "def5678"],
49
- "notes": "Completed auth flow, dashboard layout, and API routes.",
50
- "submitted_by": "Fawzi Goussous",
51
- "submitted_at": "2026-04-12T14:30:00Z"
52
- }
53
- ```
54
-
55
- **Response (200 OK):**
56
- ```json
57
- {
58
- "ok": true,
59
- "report_id": "rpt_abc123def456",
60
- "message": "Report received"
61
- }
62
- ```
63
-
64
- **Response (401 Unauthorized):**
65
- ```json
66
- {
67
- "ok": false,
68
- "error": "INVALID_API_KEY",
69
- "message": "API key is invalid or expired"
70
- }
71
- ```
72
-
73
- **Response (422 Unprocessable Entity):**
74
- ```json
75
- {
76
- "ok": false,
77
- "error": "VALIDATION_FAILED",
78
- "message": "Missing required field: project"
79
- }
80
- ```
81
-
82
- ### GET /api/v1/reports/:project
83
-
84
- Retrieve reports for a project.
85
-
86
- **Headers:**
87
- ```
88
- Authorization: Bearer <api-key>
89
- ```
90
-
91
- **Response (200 OK):**
92
- ```json
93
- {
94
- "ok": true,
95
- "reports": [
96
- {
97
- "report_id": "rpt_abc123def456",
98
- "phase": 2,
99
- "status": "built",
100
- "submitted_at": "2026-04-12T14:30:00Z",
101
- "submitted_by": "Fawzi Goussous"
102
- }
103
- ]
104
- }
105
- ```
106
-
107
- ### GET /api/v1/tracking/:project
108
-
109
- Retrieve current tracking state (same shape as tracking.json).
110
-
111
- **Headers:**
112
- ```
113
- Authorization: Bearer <api-key>
114
- ```
115
-
116
- **Response (200 OK):**
117
- ```json
118
- {
119
- "ok": true,
120
- "tracking": {
121
- "project": "client-project-name",
122
- "phase": 2,
123
- "total_phases": 4,
124
- "status": "built",
125
- "last_updated": "2026-04-12T14:30:00Z"
126
- }
127
- }
128
- ```
129
-
130
- ## Behavior
131
-
132
- - When `erp.enabled` is `false`, `/qualia-report` skips the upload silently.
133
- - When the API key file is missing or empty, the upload is skipped with a warning.
134
- - Network failures are non-blocking — the report is saved locally regardless.
135
- - The ERP reads `tracking.json` directly from git for real-time status (no API call needed for passive monitoring).
136
- - Reports are append-only — no update or delete endpoints exist.
137
-
138
- ## Required Fields
139
-
140
- | Field | Type | Required | Description |
141
- |-------|------|----------|-------------|
142
- | project | string | yes | Project slug from tracking.json |
143
- | phase | number | yes | Current phase number |
144
- | status | string | yes | Current status (setup, planned, built, verified, etc.) |
145
- | submitted_by | string | yes | Team member name |
146
- | submitted_at | string | yes | ISO 8601 timestamp |
147
-
148
- All other fields are optional but recommended for complete reporting.
149
-
150
- ## Rate Limits
151
-
152
- - 60 requests per minute per API key
153
- - Report body max size: 64KB
154
- - No batch endpoint — one report per request
155
-
156
- ## Security
157
-
158
- - API keys are per-user, not per-project
159
- - Keys expire after 90 days (re-issue via Fawzi)
160
- - All traffic is HTTPS-only
161
- - No PII beyond team member names is transmitted
@@ -1,52 +0,0 @@
1
- #!/usr/bin/env node
2
- // ~/.claude/hooks/block-env-edit.js — prevent editing .env files.
3
- // PreToolUse hook on Edit/Write tool calls. Reads tool input as JSON on stdin.
4
- // Exits 2 to BLOCK the tool call. Exits 0 to allow it.
5
- // Cross-platform (Windows/macOS/Linux).
6
-
7
- const fs = require("fs");
8
-
9
- const _traceStart = Date.now();
10
-
11
- function readInput() {
12
- try {
13
- const raw = fs.readFileSync(0, "utf8");
14
- return JSON.parse(raw);
15
- } catch {
16
- return {};
17
- }
18
- }
19
-
20
- const input = readInput();
21
- const file = (input.tool_input && (input.tool_input.file_path || input.tool_input.command)) || "";
22
-
23
- // Match .env, .env.local, .env.production, .env.*, etc.
24
- // Normalize separators so Windows paths (C:\project\.env.local) also match.
25
- const normalized = String(file).replace(/\\/g, "/");
26
-
27
- function _trace(hookName, result, extra) {
28
- try {
29
- const os = require("os");
30
- const path = require("path");
31
- const traceDir = path.join(os.homedir(), ".claude", ".qualia-traces");
32
- if (!fs.existsSync(traceDir)) fs.mkdirSync(traceDir, { recursive: true });
33
- const entry = {
34
- hook: hookName,
35
- result,
36
- timestamp: new Date().toISOString(),
37
- duration_ms: Date.now() - _traceStart,
38
- ...extra,
39
- };
40
- const file = path.join(traceDir, `${new Date().toISOString().split("T")[0]}.jsonl`);
41
- fs.appendFileSync(file, JSON.stringify(entry) + "\n");
42
- } catch {}
43
- }
44
-
45
- if (/\.env(\.|$)/.test(normalized)) {
46
- console.log("BLOCKED: Cannot edit environment files. Ask Fawzi to update secrets.");
47
- _trace("block-env-edit", "block", { file: normalized });
48
- process.exit(2);
49
- }
50
-
51
- _trace("block-env-edit", "allow");
52
- process.exit(0);