qualia-framework-v2 2.6.0 → 2.8.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.
@@ -0,0 +1,398 @@
1
+ #!/bin/bash
2
+ # Qualia Framework v2 — state.js behavioral tests
3
+ # Run: bash tests/state.test.sh
4
+
5
+ PASS=0
6
+ FAIL=0
7
+ # Resolve STATE_JS to an ABSOLUTE path so `cd` inside subshells doesn't break it.
8
+ STATE_JS="$(cd "$(dirname "$0")/../bin" && pwd)/state.js"
9
+ NODE="${NODE:-node}"
10
+
11
+ # Track tmp dirs we create so we can clean them up on exit
12
+ TMP_DIRS=()
13
+ cleanup() {
14
+ for d in "${TMP_DIRS[@]}"; do
15
+ [ -d "$d" ] && rm -rf "$d"
16
+ done
17
+ }
18
+ trap cleanup EXIT
19
+
20
+ # Make a fresh temp project with 2 phases, already initialized.
21
+ # Prints the absolute path to the new tmp dir (does NOT cd).
22
+ make_project() {
23
+ local TMP
24
+ TMP=$(mktemp -d)
25
+ TMP_DIRS+=("$TMP")
26
+ (
27
+ cd "$TMP" || exit 1
28
+ $NODE "$STATE_JS" init \
29
+ --project "TestProject" \
30
+ --phases '[{"name":"Foundation","goal":"Auth"},{"name":"Core","goal":"Features"}]' \
31
+ >/dev/null 2>&1
32
+ )
33
+ echo "$TMP"
34
+ }
35
+
36
+ # pass "name" — record a passing assertion
37
+ pass() {
38
+ echo " ✓ $1"
39
+ PASS=$((PASS + 1))
40
+ }
41
+
42
+ # fail "name" "detail"
43
+ fail_case() {
44
+ echo " ✗ $1${2:+ — $2}"
45
+ FAIL=$((FAIL + 1))
46
+ }
47
+
48
+ echo "=== state.js Behavioral Tests ==="
49
+ echo ""
50
+
51
+ # Sanity check
52
+ if [ ! -f "$STATE_JS" ]; then
53
+ echo "FATAL: state.js not found at $STATE_JS"
54
+ exit 1
55
+ fi
56
+
57
+ # ─── Basic I/O ───────────────────────────────────────────
58
+ echo "basic I/O:"
59
+
60
+ # 1. cmdInit produces valid tracking.json + STATE.md
61
+ TMP=$(mktemp -d); TMP_DIRS+=("$TMP")
62
+ (
63
+ cd "$TMP" || exit 1
64
+ $NODE "$STATE_JS" init \
65
+ --project "TestProject" \
66
+ --phases '[{"name":"Foundation","goal":"Auth"},{"name":"Core","goal":"Features"}]' \
67
+ >/tmp/qualia-state-test.out 2>&1
68
+ )
69
+ INIT_EXIT=$?
70
+ if [ "$INIT_EXIT" -eq 0 ] \
71
+ && [ -f "$TMP/.planning/tracking.json" ] \
72
+ && [ -f "$TMP/.planning/STATE.md" ] \
73
+ && grep -q '"ok": true' /tmp/qualia-state-test.out \
74
+ && grep -q '"action": "init"' /tmp/qualia-state-test.out; then
75
+ pass "cmdInit creates tracking.json + STATE.md"
76
+ else
77
+ fail_case "cmdInit creates tracking.json + STATE.md" "exit=$INIT_EXIT"
78
+ fi
79
+
80
+ # tracking.json content sanity
81
+ if grep -q '"project": "TestProject"' "$TMP/.planning/tracking.json" \
82
+ && grep -q '"total_phases": 2' "$TMP/.planning/tracking.json" \
83
+ && grep -q '"phase": 1' "$TMP/.planning/tracking.json" \
84
+ && grep -q '"status": "setup"' "$TMP/.planning/tracking.json"; then
85
+ pass "cmdInit tracking.json has correct fields"
86
+ else
87
+ fail_case "cmdInit tracking.json fields"
88
+ fi
89
+
90
+ # STATE.md content sanity
91
+ if grep -q 'Phase: 1 of 2 — Foundation' "$TMP/.planning/STATE.md" \
92
+ && grep -q 'Status: setup' "$TMP/.planning/STATE.md"; then
93
+ pass "cmdInit STATE.md has correct header"
94
+ else
95
+ fail_case "cmdInit STATE.md header"
96
+ fi
97
+
98
+ # 2. cmdCheck reads back init state
99
+ OUT=$(cd "$TMP" && $NODE "$STATE_JS" check 2>&1)
100
+ CHECK_EXIT=$?
101
+ if [ "$CHECK_EXIT" -eq 0 ] \
102
+ && echo "$OUT" | grep -q '"ok": true' \
103
+ && echo "$OUT" | grep -q '"phase": 1' \
104
+ && echo "$OUT" | grep -q '"status": "setup"' \
105
+ && echo "$OUT" | grep -q '"total_phases": 2'; then
106
+ pass "cmdCheck returns phase=1 status=setup total_phases=2"
107
+ else
108
+ fail_case "cmdCheck returns init state" "exit=$CHECK_EXIT"
109
+ fi
110
+
111
+ # 3. cmdCheck with no project → ok:false NO_PROJECT, exit 1
112
+ TMP2=$(mktemp -d); TMP_DIRS+=("$TMP2")
113
+ OUT=$(cd "$TMP2" && $NODE "$STATE_JS" check 2>&1)
114
+ CHECK_EXIT=$?
115
+ if [ "$CHECK_EXIT" -eq 1 ] \
116
+ && echo "$OUT" | grep -q '"ok": false' \
117
+ && echo "$OUT" | grep -q '"error": "NO_PROJECT"'; then
118
+ pass "cmdCheck without .planning → NO_PROJECT, exit 1"
119
+ else
120
+ fail_case "cmdCheck NO_PROJECT" "exit=$CHECK_EXIT"
121
+ fi
122
+
123
+ # ─── Happy path transitions ──────────────────────────────
124
+ echo ""
125
+ echo "happy path transitions:"
126
+
127
+ # 4. setup → planned (with plan file)
128
+ TMP=$(make_project)
129
+ touch "$TMP/.planning/phase-1-plan.md"
130
+ OUT=$(cd "$TMP" && $NODE "$STATE_JS" transition --to planned 2>&1)
131
+ EXIT=$?
132
+ if [ "$EXIT" -eq 0 ] \
133
+ && echo "$OUT" | grep -q '"ok": true' \
134
+ && echo "$OUT" | grep -q '"status": "planned"' \
135
+ && echo "$OUT" | grep -q '"previous_status": "setup"'; then
136
+ pass "setup → planned succeeds with plan file"
137
+ else
138
+ fail_case "setup → planned" "exit=$EXIT out=$OUT"
139
+ fi
140
+
141
+ # 5. planned → built (records tasks_done/tasks_total)
142
+ OUT=$(cd "$TMP" && $NODE "$STATE_JS" transition --to built --tasks-done 5 --tasks-total 5 2>&1)
143
+ EXIT=$?
144
+ if [ "$EXIT" -eq 0 ] \
145
+ && echo "$OUT" | grep -q '"ok": true' \
146
+ && echo "$OUT" | grep -q '"status": "built"' \
147
+ && grep -q '"tasks_done": 5' "$TMP/.planning/tracking.json" \
148
+ && grep -q '"tasks_total": 5' "$TMP/.planning/tracking.json"; then
149
+ pass "planned → built records tasks_done/tasks_total"
150
+ else
151
+ fail_case "planned → built" "exit=$EXIT"
152
+ fi
153
+
154
+ # 6. built → verified(pass) auto-advances to phase 2, resets status to setup
155
+ touch "$TMP/.planning/phase-1-verification.md"
156
+ OUT=$(cd "$TMP" && $NODE "$STATE_JS" transition --to verified --verification pass 2>&1)
157
+ EXIT=$?
158
+ if [ "$EXIT" -eq 0 ] \
159
+ && echo "$OUT" | grep -q '"ok": true' \
160
+ && echo "$OUT" | grep -q '"phase": 2' \
161
+ && echo "$OUT" | grep -q '"status": "setup"'; then
162
+ pass "built → verified(pass) auto-advances phase and resets to setup"
163
+ else
164
+ fail_case "built → verified(pass) auto-advance" "exit=$EXIT out=$OUT"
165
+ fi
166
+
167
+ # 7. built → verified(fail) stays on phase 1, records verification=fail
168
+ TMP=$(make_project)
169
+ touch "$TMP/.planning/phase-1-plan.md"
170
+ (cd "$TMP" && $NODE "$STATE_JS" transition --to planned >/dev/null 2>&1)
171
+ (cd "$TMP" && $NODE "$STATE_JS" transition --to built --tasks-done 3 --tasks-total 5 >/dev/null 2>&1)
172
+ touch "$TMP/.planning/phase-1-verification.md"
173
+ OUT=$(cd "$TMP" && $NODE "$STATE_JS" transition --to verified --verification fail 2>&1)
174
+ EXIT=$?
175
+ if [ "$EXIT" -eq 0 ] \
176
+ && echo "$OUT" | grep -q '"ok": true' \
177
+ && echo "$OUT" | grep -q '"phase": 1' \
178
+ && echo "$OUT" | grep -q '"status": "verified"' \
179
+ && echo "$OUT" | grep -q '"verification": "fail"'; then
180
+ pass "built → verified(fail) stays on phase 1"
181
+ else
182
+ fail_case "built → verified(fail)" "exit=$EXIT out=$OUT"
183
+ fi
184
+
185
+ # ─── Precondition failures ───────────────────────────────
186
+ echo ""
187
+ echo "precondition failures:"
188
+
189
+ # 8. setup → built fails with PRECONDITION_FAILED
190
+ TMP=$(make_project)
191
+ OUT=$(cd "$TMP" && $NODE "$STATE_JS" transition --to built 2>&1)
192
+ EXIT=$?
193
+ if [ "$EXIT" -eq 1 ] \
194
+ && echo "$OUT" | grep -q '"ok": false' \
195
+ && echo "$OUT" | grep -q '"error": "PRECONDITION_FAILED"' \
196
+ && echo "$OUT" | grep -q "Cannot go from 'setup' to 'built'"; then
197
+ pass "setup → built fails with PRECONDITION_FAILED"
198
+ else
199
+ fail_case "setup → built precondition" "exit=$EXIT out=$OUT"
200
+ fi
201
+
202
+ # 9. planned → verified fails (requires status=built)
203
+ TMP=$(make_project)
204
+ touch "$TMP/.planning/phase-1-plan.md"
205
+ (cd "$TMP" && $NODE "$STATE_JS" transition --to planned >/dev/null 2>&1)
206
+ touch "$TMP/.planning/phase-1-verification.md"
207
+ OUT=$(cd "$TMP" && $NODE "$STATE_JS" transition --to verified --verification pass 2>&1)
208
+ EXIT=$?
209
+ if [ "$EXIT" -eq 1 ] \
210
+ && echo "$OUT" | grep -q '"error": "PRECONDITION_FAILED"' \
211
+ && echo "$OUT" | grep -q "Cannot go from 'planned' to 'verified'"; then
212
+ pass "planned → verified fails (requires built)"
213
+ else
214
+ fail_case "planned → verified precondition" "exit=$EXIT out=$OUT"
215
+ fi
216
+
217
+ # 10. planned with missing plan file → MISSING_FILE
218
+ TMP=$(make_project)
219
+ # no phase-1-plan.md created
220
+ OUT=$(cd "$TMP" && $NODE "$STATE_JS" transition --to planned 2>&1)
221
+ EXIT=$?
222
+ if [ "$EXIT" -eq 1 ] \
223
+ && echo "$OUT" | grep -q '"error": "MISSING_FILE"' \
224
+ && echo "$OUT" | grep -q "phase-1-plan.md"; then
225
+ pass "setup → planned fails without plan file (MISSING_FILE)"
226
+ else
227
+ fail_case "setup → planned MISSING_FILE" "exit=$EXIT out=$OUT"
228
+ fi
229
+
230
+ # 11. built → verified with missing verification file → MISSING_FILE
231
+ TMP=$(make_project)
232
+ touch "$TMP/.planning/phase-1-plan.md"
233
+ (cd "$TMP" && $NODE "$STATE_JS" transition --to planned >/dev/null 2>&1)
234
+ (cd "$TMP" && $NODE "$STATE_JS" transition --to built --tasks-done 1 --tasks-total 1 >/dev/null 2>&1)
235
+ # NO verification file
236
+ OUT=$(cd "$TMP" && $NODE "$STATE_JS" transition --to verified --verification pass 2>&1)
237
+ EXIT=$?
238
+ if [ "$EXIT" -eq 1 ] \
239
+ && echo "$OUT" | grep -q '"error": "MISSING_FILE"' \
240
+ && echo "$OUT" | grep -q "phase-1-verification.md"; then
241
+ pass "built → verified fails without verification file (MISSING_FILE)"
242
+ else
243
+ fail_case "built → verified MISSING_FILE" "exit=$EXIT out=$OUT"
244
+ fi
245
+
246
+ # 12. built → verified without --verification → MISSING_ARG
247
+ TMP=$(make_project)
248
+ touch "$TMP/.planning/phase-1-plan.md"
249
+ (cd "$TMP" && $NODE "$STATE_JS" transition --to planned >/dev/null 2>&1)
250
+ (cd "$TMP" && $NODE "$STATE_JS" transition --to built --tasks-done 1 --tasks-total 1 >/dev/null 2>&1)
251
+ touch "$TMP/.planning/phase-1-verification.md"
252
+ OUT=$(cd "$TMP" && $NODE "$STATE_JS" transition --to verified 2>&1)
253
+ EXIT=$?
254
+ if [ "$EXIT" -eq 1 ] \
255
+ && echo "$OUT" | grep -q '"error": "MISSING_ARG"' \
256
+ && echo "$OUT" | grep -q "verification"; then
257
+ pass "built → verified without --verification → MISSING_ARG"
258
+ else
259
+ fail_case "built → verified MISSING_ARG" "exit=$EXIT out=$OUT"
260
+ fi
261
+
262
+ # 13. → shipped without --deployed-url → MISSING_ARG
263
+ # Must go through polished first, so fabricate state by transitioning through the full path.
264
+ TMP=$(make_project)
265
+ touch "$TMP/.planning/phase-1-plan.md"
266
+ (cd "$TMP" && $NODE "$STATE_JS" transition --to planned >/dev/null 2>&1)
267
+ (cd "$TMP" && $NODE "$STATE_JS" transition --to built --tasks-done 1 --tasks-total 1 >/dev/null 2>&1)
268
+ touch "$TMP/.planning/phase-1-verification.md"
269
+ (cd "$TMP" && $NODE "$STATE_JS" transition --to verified --verification pass >/dev/null 2>&1)
270
+ # Now on phase 2, status=setup. Run phase 2 to completion.
271
+ touch "$TMP/.planning/phase-2-plan.md"
272
+ (cd "$TMP" && $NODE "$STATE_JS" transition --to planned >/dev/null 2>&1)
273
+ (cd "$TMP" && $NODE "$STATE_JS" transition --to built --tasks-done 1 --tasks-total 1 >/dev/null 2>&1)
274
+ touch "$TMP/.planning/phase-2-verification.md"
275
+ (cd "$TMP" && $NODE "$STATE_JS" transition --to verified --verification pass >/dev/null 2>&1)
276
+ # Status should now be "verified" on last phase (no auto-advance past last phase)
277
+ (cd "$TMP" && $NODE "$STATE_JS" transition --to polished >/dev/null 2>&1)
278
+ # Now try ship without deployed-url
279
+ OUT=$(cd "$TMP" && $NODE "$STATE_JS" transition --to shipped 2>&1)
280
+ EXIT=$?
281
+ if [ "$EXIT" -eq 1 ] \
282
+ && echo "$OUT" | grep -q '"error": "MISSING_ARG"' \
283
+ && echo "$OUT" | grep -q "deployed-url"; then
284
+ pass "→ shipped without --deployed-url → MISSING_ARG"
285
+ else
286
+ fail_case "→ shipped MISSING_ARG" "exit=$EXIT out=$OUT"
287
+ fi
288
+
289
+ # 14. Unknown target --to frobnicate → INVALID_STATUS
290
+ TMP=$(make_project)
291
+ OUT=$(cd "$TMP" && $NODE "$STATE_JS" transition --to frobnicate 2>&1)
292
+ EXIT=$?
293
+ if [ "$EXIT" -eq 1 ] \
294
+ && echo "$OUT" | grep -q '"error": "INVALID_STATUS"'; then
295
+ pass "--to frobnicate → INVALID_STATUS"
296
+ else
297
+ fail_case "invalid target" "exit=$EXIT out=$OUT"
298
+ fi
299
+
300
+ # ─── Gap cycle circuit breaker ───────────────────────────
301
+ echo ""
302
+ echo "gap cycle circuit breaker:"
303
+
304
+ # 15. First gap closure: verified(fail) → planned, gap_cycles[1]=1
305
+ TMP=$(make_project)
306
+ touch "$TMP/.planning/phase-1-plan.md"
307
+ touch "$TMP/.planning/phase-1-verification.md"
308
+ (cd "$TMP" && $NODE "$STATE_JS" transition --to planned >/dev/null 2>&1)
309
+ (cd "$TMP" && $NODE "$STATE_JS" transition --to built --tasks-done 1 --tasks-total 1 >/dev/null 2>&1)
310
+ (cd "$TMP" && $NODE "$STATE_JS" transition --to verified --verification fail >/dev/null 2>&1)
311
+ OUT=$(cd "$TMP" && $NODE "$STATE_JS" transition --to planned 2>&1)
312
+ EXIT=$?
313
+ if [ "$EXIT" -eq 0 ] \
314
+ && echo "$OUT" | grep -q '"ok": true' \
315
+ && echo "$OUT" | grep -q '"gap_cycles": 1'; then
316
+ pass "first gap closure: verified(fail) → planned, gap_cycles=1"
317
+ else
318
+ fail_case "first gap closure" "exit=$EXIT out=$OUT"
319
+ fi
320
+
321
+ # 16. Second gap closure: gap_cycles[1]=2
322
+ (cd "$TMP" && $NODE "$STATE_JS" transition --to built --tasks-done 1 --tasks-total 1 >/dev/null 2>&1)
323
+ (cd "$TMP" && $NODE "$STATE_JS" transition --to verified --verification fail >/dev/null 2>&1)
324
+ OUT=$(cd "$TMP" && $NODE "$STATE_JS" transition --to planned 2>&1)
325
+ EXIT=$?
326
+ if [ "$EXIT" -eq 0 ] \
327
+ && echo "$OUT" | grep -q '"ok": true' \
328
+ && echo "$OUT" | grep -q '"gap_cycles": 2'; then
329
+ pass "second gap closure: gap_cycles=2"
330
+ else
331
+ fail_case "second gap closure" "exit=$EXIT out=$OUT"
332
+ fi
333
+
334
+ # 17. Third gap closure attempt → GAP_CYCLE_LIMIT
335
+ (cd "$TMP" && $NODE "$STATE_JS" transition --to built --tasks-done 1 --tasks-total 1 >/dev/null 2>&1)
336
+ (cd "$TMP" && $NODE "$STATE_JS" transition --to verified --verification fail >/dev/null 2>&1)
337
+ OUT=$(cd "$TMP" && $NODE "$STATE_JS" transition --to planned 2>&1)
338
+ EXIT=$?
339
+ if [ "$EXIT" -eq 1 ] \
340
+ && echo "$OUT" | grep -q '"error": "GAP_CYCLE_LIMIT"'; then
341
+ pass "third gap closure attempt blocked (GAP_CYCLE_LIMIT)"
342
+ else
343
+ fail_case "gap cycle limit" "exit=$EXIT out=$OUT"
344
+ fi
345
+
346
+ # 18. verified(pass) resets gap_cycles[1] to 0
347
+ # Set up a fresh project, do ONE failed cycle, then pass on the next attempt.
348
+ TMP=$(make_project)
349
+ touch "$TMP/.planning/phase-1-plan.md"
350
+ touch "$TMP/.planning/phase-1-verification.md"
351
+ (cd "$TMP" && $NODE "$STATE_JS" transition --to planned >/dev/null 2>&1)
352
+ (cd "$TMP" && $NODE "$STATE_JS" transition --to built --tasks-done 1 --tasks-total 1 >/dev/null 2>&1)
353
+ (cd "$TMP" && $NODE "$STATE_JS" transition --to verified --verification fail >/dev/null 2>&1)
354
+ # gap_cycles[1] is now 0 before the gap closure; becomes 1 after
355
+ (cd "$TMP" && $NODE "$STATE_JS" transition --to planned >/dev/null 2>&1)
356
+ (cd "$TMP" && $NODE "$STATE_JS" transition --to built --tasks-done 1 --tasks-total 1 >/dev/null 2>&1)
357
+ (cd "$TMP" && $NODE "$STATE_JS" transition --to verified --verification pass >/dev/null 2>&1)
358
+ # After pass, gap_cycles[1] should be reset to 0 in tracking.json
359
+ if grep -q '"1": 0' "$TMP/.planning/tracking.json"; then
360
+ pass "verified(pass) resets gap_cycles[1] to 0"
361
+ else
362
+ fail_case "gap cycle reset on pass"
363
+ fi
364
+
365
+ # ─── Special transitions ─────────────────────────────────
366
+ echo ""
367
+ echo "special transitions:"
368
+
369
+ # 19. --to note --notes "foo" succeeds, records notes
370
+ TMP=$(make_project)
371
+ OUT=$(cd "$TMP" && $NODE "$STATE_JS" transition --to note --notes "hello world" 2>&1)
372
+ EXIT=$?
373
+ if [ "$EXIT" -eq 0 ] \
374
+ && echo "$OUT" | grep -q '"ok": true' \
375
+ && echo "$OUT" | grep -q '"action": "note"' \
376
+ && echo "$OUT" | grep -q '"status": "setup"' \
377
+ && grep -q '"notes": "hello world"' "$TMP/.planning/tracking.json"; then
378
+ pass "--to note records notes, status unchanged"
379
+ else
380
+ fail_case "--to note" "exit=$EXIT out=$OUT"
381
+ fi
382
+
383
+ # 20. --to activity succeeds without status change
384
+ OUT=$(cd "$TMP" && $NODE "$STATE_JS" transition --to activity 2>&1)
385
+ EXIT=$?
386
+ if [ "$EXIT" -eq 0 ] \
387
+ && echo "$OUT" | grep -q '"ok": true' \
388
+ && echo "$OUT" | grep -q '"action": "activity"' \
389
+ && echo "$OUT" | grep -q '"status": "setup"'; then
390
+ pass "--to activity succeeds without status change"
391
+ else
392
+ fail_case "--to activity" "exit=$EXIT out=$OUT"
393
+ fi
394
+
395
+ # ─── Summary ─────────────────────────────────────────────
396
+ echo ""
397
+ echo "=== Results: $PASS passed, $FAIL failed ==="
398
+ [ "$FAIL" -eq 0 ] && exit 0 || exit 1
@@ -1,56 +0,0 @@
1
- #!/bin/bash
2
- # Qualia auto-update — checks once per day, updates silently in background
3
- # Runs as PreToolUse hook. Cached so it's a no-op most of the time.
4
-
5
- CLAUDE_DIR="$HOME/.claude"
6
- CACHE_FILE="$CLAUDE_DIR/.qualia-last-update-check"
7
- CONFIG_FILE="$CLAUDE_DIR/.qualia-config.json"
8
- LOCK_FILE="$CLAUDE_DIR/.qualia-updating"
9
- MAX_AGE=86400 # 24 hours in seconds
10
-
11
- # Exit fast if recently checked (most common path — single stat call)
12
- if [ -f "$CACHE_FILE" ]; then
13
- LAST_CHECK=$(cat "$CACHE_FILE" 2>/dev/null || echo 0)
14
- NOW=$(date +%s)
15
- AGE=$((NOW - LAST_CHECK))
16
- if [ "$AGE" -lt "$MAX_AGE" ]; then
17
- exit 0
18
- fi
19
- fi
20
-
21
- # Exit if already updating
22
- [ -f "$LOCK_FILE" ] && exit 0
23
-
24
- # Update cache timestamp immediately (prevents concurrent checks)
25
- date +%s > "$CACHE_FILE"
26
-
27
- # Run the actual check + update in background so we don't block the user
28
- (
29
- trap 'rm -f "$LOCK_FILE"' EXIT
30
- touch "$LOCK_FILE"
31
-
32
- # Get installed version
33
- INSTALLED=$(node -e "try{console.log(JSON.parse(require('fs').readFileSync('$CONFIG_FILE','utf8')).version)}catch{console.log('0.0.0')}" 2>/dev/null)
34
- [ -z "$INSTALLED" ] && INSTALLED="0.0.0"
35
-
36
- # Get latest from npm (5s timeout)
37
- LATEST=$(npm view qualia-framework-v2 version 2>/dev/null)
38
- [ -z "$LATEST" ] && exit 0
39
-
40
- # Compare versions
41
- NEEDS_UPDATE=$(node -e "
42
- const a='$LATEST'.split('.').map(Number), b='$INSTALLED'.split('.').map(Number);
43
- for(let i=0;i<3;i++){if(a[i]>b[i]){console.log('yes');process.exit()}if(a[i]<b[i]){process.exit()}}
44
- " 2>/dev/null)
45
-
46
- if [ "$NEEDS_UPDATE" = "yes" ]; then
47
- # Get saved install code
48
- CODE=$(node -e "try{console.log(JSON.parse(require('fs').readFileSync('$CONFIG_FILE','utf8')).code)}catch{}" 2>/dev/null)
49
- [ -z "$CODE" ] && exit 0
50
-
51
- # Run silent update
52
- npx qualia-framework-v2@latest install <<< "$CODE" > /dev/null 2>&1
53
- fi
54
- ) &
55
-
56
- exit 0
@@ -1,11 +0,0 @@
1
- #!/bin/bash
2
- # Prevent Claude from editing .env files
3
- # Claude Code hooks receive JSON on stdin with tool_input.file_path
4
-
5
- INPUT=$(cat)
6
- FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // .tool_input.command // ""' 2>/dev/null)
7
-
8
- if [[ "$FILE" == *.env* ]] || [[ "$FILE" == *".env.local"* ]] || [[ "$FILE" == *".env.production"* ]]; then
9
- echo "BLOCKED: Cannot edit environment files. Ask Fawzi to update secrets."
10
- exit 2
11
- fi
@@ -1,27 +0,0 @@
1
- #!/bin/bash
2
- # Block non-OWNER push to main/master
3
- # Reads role from ~/.claude/.qualia-config.json (machine-readable source of truth)
4
-
5
- BRANCH=$(git branch --show-current 2>/dev/null)
6
- CONFIG="$HOME/.claude/.qualia-config.json"
7
-
8
- if [ ! -f "$CONFIG" ]; then
9
- echo "BLOCKED: ~/.claude/.qualia-config.json missing. Run: npx qualia-framework-v2 install"
10
- exit 1
11
- fi
12
-
13
- # Extract role without jq dependency (installers may not have jq)
14
- ROLE=$(node -e "try{console.log(JSON.parse(require('fs').readFileSync('$CONFIG','utf8')).role||'')}catch{}" 2>/dev/null)
15
-
16
- if [ -z "$ROLE" ]; then
17
- echo "BLOCKED: Cannot determine role from $CONFIG. Defaulting to deny."
18
- exit 1
19
- fi
20
-
21
- if [[ "$BRANCH" == "main" || "$BRANCH" == "master" ]]; then
22
- if [[ "$ROLE" != "OWNER" ]]; then
23
- echo "BLOCKED: Employees cannot push to $BRANCH. Create a feature branch first."
24
- echo "Run: git checkout -b feature/your-feature-name"
25
- exit 1
26
- fi
27
- fi
@@ -1,43 +0,0 @@
1
- #!/bin/bash
2
- # Catch dangerous SQL patterns in migration files
3
- # Runs as PreToolUse hook on Write/Edit of migration files
4
-
5
- INPUT=$(cat)
6
- FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // ""' 2>/dev/null)
7
- CONTENT=$(echo "$INPUT" | jq -r '.tool_input.content // .tool_input.new_string // ""' 2>/dev/null)
8
-
9
- # Only check migration/SQL files
10
- case "$FILE" in
11
- *migration*|*migrate*|*.sql) ;;
12
- *) exit 0 ;;
13
- esac
14
-
15
- ERRORS=""
16
-
17
- # DROP TABLE without safeguards
18
- if echo "$CONTENT" | grep -qi "DROP TABLE" && ! echo "$CONTENT" | grep -qi "IF EXISTS"; then
19
- ERRORS="${ERRORS}\n ✗ DROP TABLE without IF EXISTS"
20
- fi
21
-
22
- # DELETE without WHERE
23
- if echo "$CONTENT" | grep -qi "DELETE FROM" && ! echo "$CONTENT" | grep -qi "WHERE"; then
24
- ERRORS="${ERRORS}\n ✗ DELETE FROM without WHERE clause"
25
- fi
26
-
27
- # TRUNCATE (almost always wrong in migrations)
28
- if echo "$CONTENT" | grep -qi "TRUNCATE"; then
29
- ERRORS="${ERRORS}\n ✗ TRUNCATE detected — are you sure?"
30
- fi
31
-
32
- # CREATE TABLE without RLS
33
- if echo "$CONTENT" | grep -qi "CREATE TABLE" && ! echo "$CONTENT" | grep -qi "ENABLE ROW LEVEL SECURITY"; then
34
- ERRORS="${ERRORS}\n ✗ CREATE TABLE without ENABLE ROW LEVEL SECURITY"
35
- fi
36
-
37
- if [ -n "$ERRORS" ]; then
38
- echo "◆ Migration guard — dangerous patterns found:"
39
- echo -e "$ERRORS"
40
- echo ""
41
- echo "Fix these before proceeding. If intentional, ask Fawzi to approve."
42
- exit 2
43
- fi
@@ -1,11 +0,0 @@
1
- #!/bin/bash
2
- # Save state before context compression
3
-
4
- if [ -f ".planning/STATE.md" ]; then
5
- echo "QUALIA: Saving state before compaction..."
6
- # State is in git — just ensure it's committed
7
- if git diff --name-only .planning/STATE.md 2>/dev/null | grep -q STATE; then
8
- git add .planning/STATE.md
9
- git commit -m "state: pre-compaction save" 2>/dev/null
10
- fi
11
- fi
@@ -1,50 +0,0 @@
1
- #!/bin/bash
2
- # Quality gates before production deploy
3
-
4
- echo "◆ Pre-deploy gate..."
5
-
6
- # TypeScript check
7
- if [ -f "tsconfig.json" ]; then
8
- if ! npx tsc --noEmit 2>/dev/null; then
9
- echo "BLOCKED: TypeScript errors. Fix before deploying."
10
- exit 1
11
- fi
12
- echo " ✓ TypeScript"
13
- fi
14
-
15
- # Lint check
16
- if [ -f "package.json" ] && grep -q '"lint"' package.json; then
17
- if ! npm run lint 2>/dev/null; then
18
- echo "BLOCKED: Lint errors. Fix before deploying."
19
- exit 1
20
- fi
21
- echo " ✓ Lint"
22
- fi
23
-
24
- # Test check
25
- if [ -f "package.json" ] && grep -q '"test"' package.json; then
26
- if ! npm test 2>/dev/null; then
27
- echo "BLOCKED: Tests failed. Fix before deploying."
28
- exit 1
29
- fi
30
- echo " ✓ Tests"
31
- fi
32
-
33
- # Build check
34
- if [ -f "package.json" ] && grep -q '"build"' package.json; then
35
- if ! npm run build 2>/dev/null; then
36
- echo "BLOCKED: Build failed. Fix before deploying."
37
- exit 1
38
- fi
39
- echo " ✓ Build"
40
- fi
41
-
42
- # Security: no service_role in client code
43
- LEAKS=$(grep -r "service_role" app/ components/ src/ 2>/dev/null | grep -v node_modules | grep -v ".server." | wc -l)
44
- if [ "$LEAKS" -gt 0 ]; then
45
- echo "BLOCKED: service_role found in client code. Remove before deploying."
46
- exit 1
47
- fi
48
- echo " ✓ Security"
49
-
50
- echo "◆ All gates passed."
package/hooks/pre-push.sh DELETED
@@ -1,28 +0,0 @@
1
- #!/bin/bash
2
- # Update tracking.json timestamps before push
3
- # State.js handles phase/status sync — this just updates commit hash and timestamp
4
-
5
- TRACKING=".planning/tracking.json"
6
-
7
- if [ -f "$TRACKING" ]; then
8
- if ! command -v node &>/dev/null; then
9
- echo "WARNING: node not found, skipping tracking sync" >&2
10
- exit 0
11
- fi
12
-
13
- LAST_COMMIT=$(git log --oneline -1 --format="%h" 2>/dev/null)
14
- NOW=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
15
-
16
- node -e "
17
- const fs = require('fs');
18
- try {
19
- const t = JSON.parse(fs.readFileSync('$TRACKING', 'utf8'));
20
- t.last_commit = '${LAST_COMMIT}';
21
- t.last_updated = '${NOW}';
22
- fs.writeFileSync('$TRACKING', JSON.stringify(t, null, 2) + '\n');
23
- } catch (e) {
24
- process.stderr.write('WARNING: tracking sync failed: ' + e.message + '\n');
25
- }
26
- "
27
- git add "$TRACKING" 2>/dev/null
28
- fi
@@ -1,35 +0,0 @@
1
- #!/bin/bash
2
- # Qualia session start — show branded context panel on every new session.
3
- # Delegates to qualia-ui.js so formatting matches the rest of the framework.
4
-
5
- UI="$HOME/.claude/bin/qualia-ui.js"
6
- STATE=".planning/STATE.md"
7
-
8
- # Fallback if qualia-ui.js is missing (first install before mirror)
9
- if [ ! -f "$UI" ]; then
10
- if [ -f "$STATE" ]; then
11
- PHASE=$(grep "^Phase:" "$STATE" 2>/dev/null | head -1)
12
- STATUS=$(grep "^Status:" "$STATE" 2>/dev/null | head -1)
13
- echo "QUALIA: Project loaded. $PHASE | $STATUS"
14
- echo "QUALIA: Run /qualia for next step."
15
- elif [ -f ".continue-here.md" ]; then
16
- echo "QUALIA: Handoff file found. Read .continue-here.md to resume."
17
- else
18
- echo "QUALIA: No project detected. Run /qualia-new to start."
19
- fi
20
- exit 0
21
- fi
22
-
23
- # Branded banner for every session start
24
- if [ -f "$STATE" ]; then
25
- node "$UI" banner router
26
- # Read next command from state.js and suggest it
27
- NEXT=$(node "$HOME/.claude/bin/state.js" check 2>/dev/null | node -e "try{let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{const j=JSON.parse(d);process.stdout.write(j.next_command||'')})}" 2>/dev/null)
28
- [ -n "$NEXT" ] && node "$UI" info "Run $NEXT to continue"
29
- elif [ -f ".continue-here.md" ]; then
30
- node "$UI" banner router
31
- node "$UI" warn "Handoff found — read .continue-here.md to resume"
32
- else
33
- node "$UI" banner router
34
- node "$UI" info "No project detected. Run /qualia-new to start."
35
- fi