@a-company/paradigm 5.21.2 → 5.22.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.
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import*as e from'fs';import*as r from'path';import*as E from'os';import {execSync}from'child_process';import o from'chalk';var y=`#!/bin/sh
2
+ import*as e from'fs';import*as r from'path';import*as E from'os';import {execSync}from'child_process';import o from'chalk';var C=`#!/bin/sh
3
3
  # paradigm-common.sh \u2014 Shared compliance checks for Paradigm stop hooks
4
4
  # Sourced by claude-code-stop.sh and cursor-stop.sh
5
5
  #
@@ -30,6 +30,8 @@ import*as e from'fs';import*as r from'path';import*as E from'os';import {execSyn
30
30
  # 9. Purpose-required patterns from config.yaml
31
31
  # 10. Aspect drift detection with auto-heal (from unified compliance-check)
32
32
  # 11. Portal gate implementation compliance (from unified compliance-check)
33
+ # 12. Graduation failure tracking (auto-demotion)
34
+ # 13. Orchestration required for complex tasks
33
35
 
34
36
  VIOLATIONS=""
35
37
  VIOLATION_COUNT=0
@@ -37,6 +39,35 @@ AUTO_FIXED=""
37
39
  AUTO_FIX_COUNT=0
38
40
  PARADIGM_AUTO_FIX="\${PARADIGM_AUTO_FIX:-0}"
39
41
 
42
+ # --- Load enforcement config ---
43
+ ENFORCEMENT_MAP=""
44
+ if command -v paradigm >/dev/null 2>&1; then
45
+ ENFORCEMENT_MAP=$(paradigm enforcement resolve --json 2>/dev/null) || true
46
+ fi
47
+
48
+ # Helper: get severity for a check ID
49
+ _check_severity() {
50
+ _id="$1"
51
+ _default="$2"
52
+ if [ -z "$ENFORCEMENT_MAP" ]; then
53
+ echo "$_default"
54
+ return
55
+ fi
56
+ _val=$(echo "$ENFORCEMENT_MAP" | grep -o "\\"$_id\\":\\"[^\\"]*\\"" | sed 's/.*":\\"//' | sed 's/"//')
57
+ if [ -n "$_val" ]; then
58
+ echo "$_val"
59
+ else
60
+ echo "$_default"
61
+ fi
62
+ }
63
+
64
+ # Read orchestration threshold
65
+ ORCH_THRESHOLD=3
66
+ if [ -n "$ENFORCEMENT_MAP" ]; then
67
+ _ot=$(echo "$ENFORCEMENT_MAP" | grep -o '"orchestrationThreshold":[0-9]*' | sed 's/.*://')
68
+ [ -n "$_ot" ] && ORCH_THRESHOLD="$_ot"
69
+ fi
70
+
40
71
  # --- Cache .purpose file paths (avoid repeated find scans) ---
41
72
  PURPOSE_CACHE=".paradigm/.purpose-paths"
42
73
  if [ -f "$PURPOSE_CACHE" ]; then
@@ -103,17 +134,24 @@ for file in $MODIFIED; do
103
134
  done
104
135
 
105
136
  # Only flag if there are uncovered directories AND zero paradigm files were touched
106
- if [ -n "$UNCOVERED_SOURCE_DIRS" ] && [ "$PARADIGM_COUNT" -eq 0 ]; then
137
+ _SEV=$(_check_severity "purpose-coverage" "block")
138
+ if [ "$_SEV" != "off" ] && [ -n "$UNCOVERED_SOURCE_DIRS" ] && [ "$PARADIGM_COUNT" -eq 0 ]; then
107
139
  # Count uncovered dirs
108
140
  UNCOVERED_DIR_COUNT=0
109
141
  for _d in $UNCOVERED_SOURCE_DIRS; do
110
142
  UNCOVERED_DIR_COUNT=$((UNCOVERED_DIR_COUNT + 1))
111
143
  done
112
- VIOLATIONS="$VIOLATIONS
144
+ if [ "$_SEV" = "block" ]; then
145
+ VIOLATIONS="$VIOLATIONS
113
146
  - You modified source files in $UNCOVERED_DIR_COUNT director(ies) without updating their .purpose files:
114
147
  $UNCOVERED_SOURCE_DIRS
115
148
  Update the nearest .purpose file for each modified code area."
116
- VIOLATION_COUNT=$((VIOLATION_COUNT + 1))
149
+ VIOLATION_COUNT=$((VIOLATION_COUNT + 1))
150
+ else
151
+ ADVISORY="$ADVISORY
152
+ - (warn) Source files in $UNCOVERED_DIR_COUNT director(ies) modified without .purpose updates:
153
+ $UNCOVERED_SOURCE_DIRS"
154
+ fi
117
155
  fi
118
156
 
119
157
  # --- Check 2: Modified source directories missing .purpose files ---
@@ -149,7 +187,8 @@ for file in $MODIFIED; do
149
187
  fi
150
188
  done
151
189
 
152
- if [ -n "$DIRS_WITHOUT_PURPOSE" ]; then
190
+ _SEV=$(_check_severity "purpose-exists" "block")
191
+ if [ "$_SEV" != "off" ] && [ -n "$DIRS_WITHOUT_PURPOSE" ]; then
153
192
  if [ "$PARADIGM_AUTO_FIX" = "1" ]; then
154
193
  # Auto-fix: create stub .purpose files for directories missing them
155
194
  for dir in $DIRS_WITHOUT_PURPOSE; do
@@ -165,86 +204,107 @@ PURPOSEEOF
165
204
  - Created stub .purpose in $dir (update descriptions)"
166
205
  AUTO_FIX_COUNT=$((AUTO_FIX_COUNT + 1))
167
206
  done
168
- else
207
+ elif [ "$_SEV" = "block" ]; then
169
208
  VIOLATIONS="$VIOLATIONS
170
209
  - These directories have modified source files but no .purpose file anywhere in their path:
171
210
  $DIRS_WITHOUT_PURPOSE
172
211
  Create a .purpose file using paradigm_purpose_init + paradigm_purpose_add_component."
173
212
  VIOLATION_COUNT=$((VIOLATION_COUNT + 1))
213
+ else
214
+ ADVISORY="$ADVISORY
215
+ - (warn) Directories with modified source files but no .purpose file:
216
+ $DIRS_WITHOUT_PURPOSE"
174
217
  fi
175
218
  fi
176
219
 
177
220
  # --- Check 3: Route patterns added without portal.yaml ---
178
- if [ -f "portal.yaml" ] || echo "$MODIFIED" | grep -q "portal.yaml"; then
179
- : # portal.yaml exists or was modified \u2014 OK
180
- else
181
- # Check if any modified files contain route-like patterns
182
- # Skip: test/spec/fixture files, markdown, and comment-only matches
183
- ROUTE_FILES=""
184
- for file in $MODIFIED; do
185
- # Skip test, spec, and fixture files entirely
186
- case "$file" in
187
- *test*|*spec*|*fixture*|*__tests__*|*__mocks__*|*.test.*|*.spec.*) continue ;;
188
- esac
189
- case "$file" in
190
- *.ts|*.js|*.tsx|*.jsx|*.py|*.rs|*.go)
191
- if [ -f "$file" ]; then
192
- # Grep for route patterns, then filter out comment lines and description strings
193
- ROUTE_MATCH=$(grep -nE '\\.(get|post|put|patch|delete)\\s*\\(|router\\.|app\\.(get|post|put|delete)|@(Get|Post|Put|Delete)|#\\[actix_web::(get|post)' "$file" 2>/dev/null \\
194
- | grep -v '^\\s*[0-9]*:\\s*//' \\
195
- | grep -v '^\\s*[0-9]*:\\s*\\*' \\
196
- | grep -v '^\\s*[0-9]*:\\s*#' \\
197
- | grep -v '^\\s*[0-9]*:\\s*<!--' \\
198
- | grep -v 'description:' \\
199
- | grep -v 'summary:' \\
200
- | grep -v 'comment:' \\
201
- | grep -v '@example' \\
202
- | grep -v '@see' \\
203
- || true)
204
- if [ -n "$ROUTE_MATCH" ]; then
205
- ROUTE_FILES="$ROUTE_FILES $file"
221
+ _SEV=$(_check_severity "portal-gates" "block")
222
+ if [ "$_SEV" != "off" ]; then
223
+ if [ -f "portal.yaml" ] || echo "$MODIFIED" | grep -q "portal.yaml"; then
224
+ : # portal.yaml exists or was modified \u2014 OK
225
+ else
226
+ # Check if any modified files contain route-like patterns
227
+ # Skip: test/spec/fixture files, markdown, and comment-only matches
228
+ ROUTE_FILES=""
229
+ for file in $MODIFIED; do
230
+ # Skip test, spec, and fixture files entirely
231
+ case "$file" in
232
+ *test*|*spec*|*fixture*|*__tests__*|*__mocks__*|*.test.*|*.spec.*) continue ;;
233
+ esac
234
+ case "$file" in
235
+ *.ts|*.js|*.tsx|*.jsx|*.py|*.rs|*.go)
236
+ if [ -f "$file" ]; then
237
+ # Grep for route patterns, then filter out comment lines and description strings
238
+ ROUTE_MATCH=$(grep -nE '\\.(get|post|put|patch|delete)\\s*\\(|router\\.|app\\.(get|post|put|delete)|@(Get|Post|Put|Delete)|#\\[actix_web::(get|post)' "$file" 2>/dev/null \\
239
+ | grep -v '^\\s*[0-9]*:\\s*//' \\
240
+ | grep -v '^\\s*[0-9]*:\\s*\\*' \\
241
+ | grep -v '^\\s*[0-9]*:\\s*#' \\
242
+ | grep -v '^\\s*[0-9]*:\\s*<!--' \\
243
+ | grep -v 'description:' \\
244
+ | grep -v 'summary:' \\
245
+ | grep -v 'comment:' \\
246
+ | grep -v '@example' \\
247
+ | grep -v '@see' \\
248
+ || true)
249
+ if [ -n "$ROUTE_MATCH" ]; then
250
+ ROUTE_FILES="$ROUTE_FILES $file"
251
+ fi
206
252
  fi
207
- fi
208
- ;;
209
- esac
210
- done
253
+ ;;
254
+ esac
255
+ done
211
256
 
212
- if [ -n "$ROUTE_FILES" ]; then
213
- VIOLATIONS="$VIOLATIONS
257
+ if [ -n "$ROUTE_FILES" ]; then
258
+ if [ "$_SEV" = "block" ]; then
259
+ VIOLATIONS="$VIOLATIONS
214
260
  - Route/endpoint patterns found in modified files but no portal.yaml exists:
215
261
  $ROUTE_FILES
216
262
  Create portal.yaml with gate definitions. Use paradigm_gates_for_route for suggestions."
217
- VIOLATION_COUNT=$((VIOLATION_COUNT + 1))
263
+ VIOLATION_COUNT=$((VIOLATION_COUNT + 1))
264
+ else
265
+ ADVISORY="$ADVISORY
266
+ - (warn) Route/endpoint patterns found without portal.yaml:
267
+ $ROUTE_FILES"
268
+ fi
269
+ fi
218
270
  fi
219
271
  fi
220
272
 
221
273
  # --- Check 4: Aspect anchor files that no longer exist ---
222
- for purpose_file in $PURPOSE_PATHS; do
223
- if grep -q "anchors:" "$purpose_file" 2>/dev/null; then
224
- purpose_dir=$(dirname "$purpose_file")
225
- in_anchors=false
226
- while IFS= read -r line; do
227
- case "$line" in
228
- *"anchors:"*) in_anchors=true; continue ;;
229
- *"- "*)
230
- if [ "$in_anchors" = true ]; then
231
- anchor_path=$(echo "$line" | sed 's/.*- //' | sed 's/:.*//' | tr -d ' ')
232
- if [ -n "$anchor_path" ]; then
233
- # Try relative to .purpose dir first, then project root
234
- if [ ! -f "$purpose_dir/$anchor_path" ] && [ ! -f "./$anchor_path" ]; then
235
- VIOLATIONS="$VIOLATIONS
274
+ _SEV=$(_check_severity "aspect-anchors" "block")
275
+ if [ "$_SEV" != "off" ]; then
276
+ for purpose_file in $PURPOSE_PATHS; do
277
+ if grep -q "anchors:" "$purpose_file" 2>/dev/null; then
278
+ purpose_dir=$(dirname "$purpose_file")
279
+ in_anchors=false
280
+ while IFS= read -r line; do
281
+ case "$line" in
282
+ *"anchors:"*) in_anchors=true; continue ;;
283
+ *"- "*)
284
+ if [ "$in_anchors" = true ]; then
285
+ anchor_path=$(echo "$line" | sed 's/.*- //' | sed 's/:.*//' | tr -d ' ')
286
+ if [ -n "$anchor_path" ]; then
287
+ # Try relative to .purpose dir first, then project root
288
+ if [ ! -f "$purpose_dir/$anchor_path" ] && [ ! -f "./$anchor_path" ]; then
289
+ if [ "$_SEV" = "block" ]; then
290
+ VIOLATIONS="$VIOLATIONS
236
291
  - Aspect anchor '$anchor_path' in $purpose_file does not exist.
237
292
  Update the anchor or remove the stale aspect."
238
- VIOLATION_COUNT=$((VIOLATION_COUNT + 1))
293
+ VIOLATION_COUNT=$((VIOLATION_COUNT + 1))
294
+ else
295
+ ADVISORY="$ADVISORY
296
+ - (warn) Aspect anchor '$anchor_path' in $purpose_file does not exist."
297
+ fi
298
+ fi
239
299
  fi
240
300
  fi
241
- fi
242
- ;;
243
- *) in_anchors=false ;;
244
- esac
245
- done < "$purpose_file"
246
- fi
247
- done
301
+ ;;
302
+ *) in_anchors=false ;;
303
+ esac
304
+ done < "$purpose_file"
305
+ fi
306
+ done
307
+ fi
248
308
 
249
309
  # --- Check 5: Per-directory .purpose freshness ---
250
310
  # Uses .pending-review if available, plus a git-based fallback that cross-references
@@ -328,51 +388,62 @@ for file in $MODIFIED; do
328
388
  fi
329
389
  done
330
390
 
331
- if [ -n "$STALE_PURPOSES" ]; then
332
- VIOLATIONS="$VIOLATIONS
391
+ _SEV=$(_check_severity "purpose-freshness" "warn")
392
+ if [ "$_SEV" != "off" ] && [ -n "$STALE_PURPOSES" ]; then
393
+ if [ "$_SEV" = "block" ]; then
394
+ VIOLATIONS="$VIOLATIONS
333
395
  - These .purpose files cover modified source code but were NOT updated:
334
396
  $STALE_PURPOSES
335
397
  Update each with: #components, ~aspects (with anchors), !signals, \\$flows, ^gates."
336
- VIOLATION_COUNT=$((VIOLATION_COUNT + 1))
398
+ VIOLATION_COUNT=$((VIOLATION_COUNT + 1))
399
+ else
400
+ ADVISORY="$ADVISORY
401
+ - (warn) These .purpose files cover modified source code but were NOT updated:
402
+ $STALE_PURPOSES"
403
+ fi
337
404
  fi
338
405
 
339
406
  # --- Check 6: Aspect coverage advisory ---
340
407
  ADVISORY=""
341
- HAS_ASPECTS=false
342
- for purpose_file in $PURPOSE_PATHS; do
343
- if grep -qE '^\\s*~' "$purpose_file" 2>/dev/null; then
344
- HAS_ASPECTS=true
345
- break
346
- fi
347
- done
348
-
349
- if [ "$HAS_ASPECTS" = true ] && [ "$SOURCE_COUNT" -gt 0 ]; then
350
- ASPECT_UPDATED=false
351
- for file in $MODIFIED; do
352
- case "$file" in
353
- *.purpose)
354
- if grep -qE '^\\s*~|anchors:|applies-to:' "$file" 2>/dev/null; then
355
- ASPECT_UPDATED=true
356
- break
357
- fi
358
- ;;
359
- esac
408
+ _SEV=$(_check_severity "aspect-advisory" "warn")
409
+ if [ "$_SEV" != "off" ]; then
410
+ HAS_ASPECTS=false
411
+ for purpose_file in $PURPOSE_PATHS; do
412
+ if grep -qE '^\\s*~' "$purpose_file" 2>/dev/null; then
413
+ HAS_ASPECTS=true
414
+ break
415
+ fi
360
416
  done
361
417
 
362
- if [ "$ASPECT_UPDATED" = false ]; then
363
- ADVISORY=" This project defines ~aspects with code anchors. Check if existing
418
+ if [ "$HAS_ASPECTS" = true ] && [ "$SOURCE_COUNT" -gt 0 ]; then
419
+ ASPECT_UPDATED=false
420
+ for file in $MODIFIED; do
421
+ case "$file" in
422
+ *.purpose)
423
+ if grep -qE '^\\s*~|anchors:|applies-to:' "$file" 2>/dev/null; then
424
+ ASPECT_UPDATED=true
425
+ break
426
+ fi
427
+ ;;
428
+ esac
429
+ done
430
+
431
+ if [ "$ASPECT_UPDATED" = false ]; then
432
+ ADVISORY=" This project defines ~aspects with code anchors. Check if existing
364
433
  ~aspects need updated anchors or applies-to patterns."
434
+ fi
365
435
  fi
366
436
  fi
367
437
 
368
438
  # --- Check 7: Lore entry expected for significant sessions ---
369
- if [ "$SOURCE_COUNT" -ge 3 ] && [ -d ".paradigm/lore" ]; then
439
+ _SEV=$(_check_severity "lore-required" "block")
440
+ if [ "$_SEV" != "off" ] && [ "$SOURCE_COUNT" -ge 3 ] && [ -d ".paradigm/lore" ]; then
370
441
  LORE_RECORDED=false
371
442
 
372
443
  # Check git diff first (covers staged/committed lore)
373
444
  for file in $MODIFIED; do
374
445
  case "$file" in
375
- .paradigm/lore/entries/*.yaml|.paradigm/lore/entries/*/*.yaml)
446
+ .paradigm/lore/entries/*.yaml|.paradigm/lore/entries/*/*.yaml|.paradigm/lore/entries/*.lore|.paradigm/lore/entries/*/*.lore)
376
447
  LORE_RECORDED=true
377
448
  break
378
449
  ;;
@@ -383,7 +454,7 @@ if [ "$SOURCE_COUNT" -ge 3 ] && [ -d ".paradigm/lore" ]; then
383
454
  if [ "$LORE_RECORDED" = false ]; then
384
455
  TODAY=$(date -u +"%Y-%m-%d")
385
456
  if [ -d ".paradigm/lore/entries/$TODAY" ]; then
386
- ENTRY_COUNT=$(find ".paradigm/lore/entries/$TODAY" -name "*.yaml" 2>/dev/null | head -1)
457
+ ENTRY_COUNT=$(find ".paradigm/lore/entries/$TODAY" \\( -name "*.yaml" -o -name "*.lore" \\) 2>/dev/null | head -1)
387
458
  if [ -n "$ENTRY_COUNT" ]; then
388
459
  LORE_RECORDED=true
389
460
  fi
@@ -424,12 +495,15 @@ LOREEOF
424
495
  AUTO_FIXED="$AUTO_FIXED
425
496
  - Created stub lore entry $LORE_ID (update with real summary)"
426
497
  AUTO_FIX_COUNT=$((AUTO_FIX_COUNT + 1))
427
- else
498
+ elif [ "$_SEV" = "block" ]; then
428
499
  VIOLATIONS="$VIOLATIONS
429
500
  - You modified $SOURCE_COUNT source files but recorded no lore entry.
430
501
  Record your session: paradigm_lore_record (MCP) or paradigm lore record (CLI).
431
502
  Include: type, title, summary, and symbols_touched."
432
503
  VIOLATION_COUNT=$((VIOLATION_COUNT + 1))
504
+ else
505
+ ADVISORY="$ADVISORY
506
+ - (warn) $SOURCE_COUNT source files modified without a lore entry."
433
507
  fi
434
508
  fi
435
509
  fi
@@ -446,17 +520,23 @@ fi
446
520
 
447
521
  if [ -n "$COMPLIANCE_RESULT" ]; then
448
522
  # --- Check 8: Blocking habits (from unified result) ---
449
- # The compliance-check command writes .habits-blocking marker internally
450
- if [ -f ".paradigm/.habits-blocking" ]; then
523
+ _SEV=$(_check_severity "habits-blocking" "block")
524
+ if [ "$_SEV" != "off" ] && [ -f ".paradigm/.habits-blocking" ]; then
451
525
  HABITS_BLOCKING=$(cat ".paradigm/.habits-blocking")
452
- VIOLATIONS="$VIOLATIONS
526
+ if [ "$_SEV" = "block" ]; then
527
+ VIOLATIONS="$VIOLATIONS
453
528
  - Blocking habit(s) not satisfied:
454
529
  $HABITS_BLOCKING
455
530
  Fix: satisfy the habit(s), or change severity to 'warn' in .paradigm/habits.yaml to make advisory."
456
- VIOLATION_COUNT=$((VIOLATION_COUNT + 1))
531
+ VIOLATION_COUNT=$((VIOLATION_COUNT + 1))
532
+ else
533
+ ADVISORY="$ADVISORY
534
+ - (warn) Blocking habit(s) not satisfied: $HABITS_BLOCKING"
535
+ fi
457
536
  fi
458
537
 
459
538
  # --- Check 10: Aspect drift (from unified result) ---
539
+ _SEV=$(_check_severity "drift-detection" "block")
460
540
  DRIFTED_COUNT=$(echo "$COMPLIANCE_RESULT" | grep -o '"driftedCount":[0-9]*' | sed 's/.*://')
461
541
  HEALED_COUNT=$(echo "$COMPLIANCE_RESULT" | grep -o '"healedCount":[0-9]*' | sed 's/.*://')
462
542
 
@@ -464,39 +544,57 @@ if [ -n "$COMPLIANCE_RESULT" ]; then
464
544
  echo "[paradigm] Auto-healed $HEALED_COUNT shifted anchor(s)." >&2
465
545
  fi
466
546
 
467
- if [ -n "$DRIFTED_COUNT" ] && [ "$DRIFTED_COUNT" -gt 0 ] 2>/dev/null; then
468
- VIOLATIONS="$VIOLATIONS
547
+ if [ "$_SEV" != "off" ] && [ -n "$DRIFTED_COUNT" ] && [ "$DRIFTED_COUNT" -gt 0 ] 2>/dev/null; then
548
+ if [ "$_SEV" = "block" ]; then
549
+ VIOLATIONS="$VIOLATIONS
469
550
  - $DRIFTED_COUNT aspect anchor(s) have drifted (content genuinely changed).
470
551
  Run paradigm_aspect_check to review. Update anchors in .purpose files."
471
- VIOLATION_COUNT=$((VIOLATION_COUNT + 1))
552
+ VIOLATION_COUNT=$((VIOLATION_COUNT + 1))
553
+ else
554
+ ADVISORY="$ADVISORY
555
+ - (warn) $DRIFTED_COUNT aspect anchor(s) have drifted."
556
+ fi
472
557
  fi
473
558
 
474
559
  # --- Check 11: Portal gate compliance (from unified result) ---
560
+ _SEV=$(_check_severity "portal-compliance" "block")
475
561
  UNDECLARED=$(echo "$COMPLIANCE_RESULT" | grep -o '"usedButUndeclaredCount":[0-9]*' | sed 's/.*://')
476
562
 
477
- if [ -n "$UNDECLARED" ] && [ "$UNDECLARED" -gt 0 ] 2>/dev/null; then
563
+ if [ "$_SEV" != "off" ] && [ -n "$UNDECLARED" ] && [ "$UNDECLARED" -gt 0 ] 2>/dev/null; then
478
564
  UNDECLARED_LIST=$(echo "$COMPLIANCE_RESULT" | grep -o '"usedButUndeclared":\\[[^]]*\\]' | sed 's/.*\\[//;s/\\].*//;s/"//g')
479
- VIOLATIONS="$VIOLATIONS
565
+ if [ "$_SEV" = "block" ]; then
566
+ VIOLATIONS="$VIOLATIONS
480
567
  - $UNDECLARED gate(s) used in code but not declared in portal.yaml:
481
568
  $UNDECLARED_LIST
482
569
  Add them to portal.yaml or use paradigm_portal_add_gate."
483
- VIOLATION_COUNT=$((VIOLATION_COUNT + 1))
570
+ VIOLATION_COUNT=$((VIOLATION_COUNT + 1))
571
+ else
572
+ ADVISORY="$ADVISORY
573
+ - (warn) $UNDECLARED gate(s) used in code but not declared in portal.yaml: $UNDECLARED_LIST"
574
+ fi
484
575
  fi
485
576
  else
486
577
  # Fallback: check habits blocking marker even if compliance-check unavailable
487
- if [ -f ".paradigm/.habits-blocking" ]; then
578
+ _SEV=$(_check_severity "habits-blocking" "block")
579
+ if [ "$_SEV" != "off" ] && [ -f ".paradigm/.habits-blocking" ]; then
488
580
  HABITS_BLOCKING=$(cat ".paradigm/.habits-blocking")
489
- VIOLATIONS="$VIOLATIONS
581
+ if [ "$_SEV" = "block" ]; then
582
+ VIOLATIONS="$VIOLATIONS
490
583
  - Blocking habit(s) not satisfied:
491
584
  $HABITS_BLOCKING
492
585
  Fix: satisfy the habit(s), or change severity to 'warn' in .paradigm/habits.yaml to make advisory."
493
- VIOLATION_COUNT=$((VIOLATION_COUNT + 1))
586
+ VIOLATION_COUNT=$((VIOLATION_COUNT + 1))
587
+ else
588
+ ADVISORY="$ADVISORY
589
+ - (warn) Blocking habit(s) not satisfied: $HABITS_BLOCKING"
590
+ fi
494
591
  fi
495
592
  fi
496
593
 
497
594
  # --- Check 9: Purpose-required patterns from config.yaml ---
595
+ _SEV=$(_check_severity "purpose-required-patterns" "block")
498
596
  CONFIG_FILE=".paradigm/config.yaml"
499
- if [ -f "$CONFIG_FILE" ]; then
597
+ if [ "$_SEV" != "off" ] && [ -f "$CONFIG_FILE" ]; then
500
598
  MISSING_REQUIRED=""
501
599
  in_section=false
502
600
  current_pattern=""
@@ -582,19 +680,24 @@ PURPOSEEOF
582
680
  - Created stub .purpose in $dir (purpose-required pattern)"
583
681
  AUTO_FIX_COUNT=$((AUTO_FIX_COUNT + 1))
584
682
  done
585
- else
683
+ elif [ "$_SEV" = "block" ]; then
586
684
  VIOLATIONS="$VIOLATIONS
587
685
  - These directories match purpose-required patterns but have no .purpose file:
588
686
  $MISSING_REQUIRED
589
687
  Create .purpose files: paradigm_purpose_init + paradigm_purpose_add_component."
590
688
  VIOLATION_COUNT=$((VIOLATION_COUNT + 1))
689
+ else
690
+ ADVISORY="$ADVISORY
691
+ - (warn) Directories matching purpose-required patterns without .purpose file:
692
+ $MISSING_REQUIRED"
591
693
  fi
592
694
  fi
593
695
  fi
594
696
 
595
697
  # --- Check 12: Graduation failure tracking ---
596
698
  # When violations occur for graduated habits, record failures for auto-demotion.
597
- if [ "$VIOLATION_COUNT" -gt 0 ] && [ -f ".paradigm/graduation.yaml" ]; then
699
+ _SEV=$(_check_severity "graduation-tracking" "warn")
700
+ if [ "$_SEV" != "off" ] && [ "$VIOLATION_COUNT" -gt 0 ] && [ -f ".paradigm/graduation.yaml" ]; then
598
701
  GRAD_FAILURES_DIR=".paradigm/.graduation-failures"
599
702
  mkdir -p "$GRAD_FAILURES_DIR" 2>/dev/null
600
703
 
@@ -639,6 +742,23 @@ if [ "$VIOLATION_COUNT" -gt 0 ] && [ -f ".paradigm/graduation.yaml" ]; then
639
742
  fi
640
743
  done
641
744
  fi
745
+
746
+ # --- Check 13: Orchestration required for complex tasks ---
747
+ _SEV=$(_check_severity "orchestration-required" "warn")
748
+ if [ "$_SEV" != "off" ] && [ "$SOURCE_COUNT" -ge "$ORCH_THRESHOLD" ]; then
749
+ if [ ! -f ".paradigm/.orchestrated" ]; then
750
+ if [ "$_SEV" = "block" ]; then
751
+ VIOLATIONS="$VIOLATIONS
752
+ - You modified $SOURCE_COUNT source files without running orchestration.
753
+ Run paradigm_orchestrate_inline mode=\\"plan\\" first.
754
+ Override: paradigm enforcement override orchestration-required warn"
755
+ VIOLATION_COUNT=$((VIOLATION_COUNT + 1))
756
+ else
757
+ ADVISORY="$ADVISORY
758
+ - (orchestration) $SOURCE_COUNT files modified without team orchestration."
759
+ fi
760
+ fi
761
+ fi
642
762
  `,R=`#!/bin/sh
643
763
  # Paradigm Claude Code Stop Hook (v2)
644
764
  # Validates paradigm compliance before allowing the agent to finish.
@@ -647,7 +767,7 @@ fi
647
767
  # Hook type: Stop
648
768
  # Exit 0 = allow, Exit 2 = block with message
649
769
  #
650
- # Checks 1\u201311 are defined in paradigm-common.sh (shared with Cursor hook).
770
+ # Checks 1\u201313 are defined in paradigm-common.sh (shared with Cursor hook).
651
771
 
652
772
  # Read JSON from stdin (hook input)
653
773
  INPUT=$(cat)
@@ -747,13 +867,14 @@ rm -f ".paradigm/.pending-review"
747
867
  rm -f ".paradigm/.habits-blocking"
748
868
  rm -f ".paradigm/.session-started"
749
869
  rm -f ".paradigm/.purpose-paths"
870
+ rm -f ".paradigm/.orchestrated"
750
871
 
751
872
  exit 0
752
- `,U=`#!/bin/sh
873
+ `,A=`#!/bin/sh
753
874
  # Legacy afterFileEdit hook \u2014 replaced by paradigm-posttooluse.sh (postToolUse)
754
875
  # Kept as a no-op because Claude Code expects the file to exist.
755
876
  exit 0
756
- `,A=`#!/bin/sh
877
+ `,U=`#!/bin/sh
757
878
  # Paradigm Claude Code Pre-Commit Hook
758
879
  # Intercepts git commit Bash calls and auto-rebuilds the index.
759
880
  # Installed by: paradigm hooks install --claude-code
@@ -798,7 +919,7 @@ done
798
919
 
799
920
  # Never block \u2014 exit 0
800
921
  exit 0
801
- `,P=`#!/bin/sh
922
+ `,D=`#!/bin/sh
802
923
  # Paradigm Cursor Session Start Hook
803
924
  # Fires before the agent does anything \u2014 injects additional_context
804
925
  # that acts as a deterministic system prompt (not subject to context compaction).
@@ -906,7 +1027,7 @@ exit 0
906
1027
  # Hook type: stop
907
1028
  # Exit 0 = allow, Exit 2 = block with message
908
1029
  #
909
- # Checks 1\u201311 are defined in paradigm-common.sh (shared with Claude Code hook).
1030
+ # Checks 1\u201313 are defined in paradigm-common.sh (shared with Claude Code hook).
910
1031
 
911
1032
  # Read JSON from stdin (hook input)
912
1033
  INPUT=$(cat)
@@ -1028,9 +1149,10 @@ rm -f ".paradigm/.habits-blocking"
1028
1149
  rm -f ".paradigm/.stop-hook-active"
1029
1150
  rm -f ".paradigm/.session-started"
1030
1151
  rm -f ".paradigm/.purpose-paths"
1152
+ rm -f ".paradigm/.orchestrated"
1031
1153
 
1032
1154
  exit 0
1033
- `,D=`#!/bin/sh
1155
+ `,P=`#!/bin/sh
1034
1156
  # Legacy afterFileEdit hook \u2014 replaced by paradigm-posttooluse.sh (postToolUse)
1035
1157
  # Kept as a no-op because Cursor expects the file to exist.
1036
1158
  exit 0
@@ -1348,9 +1470,9 @@ if [ "$PENDING_COUNT" -ge 30 ]; then
1348
1470
  fi
1349
1471
 
1350
1472
  exit 0
1351
- `;function x(){try{let i=r.join(E.homedir(),".claude","settings.json");if(!e.existsSync(i))return {active:!1};if(!JSON.parse(e.readFileSync(i,"utf8")).enabledPlugins?.["paradigm@a-paradigm"])return {active:!1};let d=r.join(E.homedir(),".claude","plugins","cache","a-paradigm","paradigm");if(!e.existsSync(d))return {active:!1};let a=e.readdirSync(d).filter(s=>e.statSync(r.join(d,s)).isDirectory()).sort().reverse();if(a.length===0)return {active:!1};let p=r.join(d,a[0]),l=r.join(p,"hooks","hooks.json");return e.existsSync(l)?{active:!0,cacheVersion:a[0]}:{active:!1}}catch{return {active:false}}}function X(){try{let i=x();if(!i.active||!i.cacheVersion)return {compatible:!0};let h=r.join(E.homedir(),".claude","plugins","cache","a-paradigm","paradigm",i.cacheVersion,"hooks.json");if(!e.existsSync(h))return {compatible:!0};let d=JSON.parse(e.readFileSync(h,"utf8")).compatibleVersions;if(!d)return {compatible:!0};let a=W();if(!a)return {compatible:!0};let p=d.split(/\s+/);for(let l of p){let s=l.match(/^(>=?|<=?)\s*(\d+\.\d+\.\d+)/);if(!s)continue;let[,t,n]=s,f=Y(a,n);if(t===">="&&f<0)return {compatible:!1,message:`Plugin requires paradigm ${d}, current: ${a}`};if(t===">"&&f<=0)return {compatible:!1,message:`Plugin requires paradigm ${d}, current: ${a}`};if(t==="<="&&f>0)return {compatible:!1,message:`Plugin requires paradigm ${d}, current: ${a}`};if(t==="<"&&f>=0)return {compatible:!1,message:`Plugin requires paradigm ${d}, current: ${a}`}}return {compatible:!0}}catch{return {compatible:true}}}function W(){try{let i=r.join(r.dirname(new URL(import.meta.url).pathname),"..","..","package.json");return JSON.parse(e.readFileSync(i,"utf8")).version||null}catch{return null}}function Y(i,h){let c=i.split(".").map(Number),d=h.split(".").map(Number);for(let a=0;a<3;a++){if((c[a]||0)<(d[a]||0))return -1;if((c[a]||0)>(d[a]||0))return 1}return 0}function J(i){let h=[],c=r.join(i,".claude","hooks");if(e.existsSync(c)){for(let a of ["paradigm-common.sh","paradigm-stop.sh","paradigm-precommit.sh","paradigm-postwrite.sh"]){let p=r.join(c,a);e.existsSync(p)&&(e.unlinkSync(p),h.push(a));}try{e.readdirSync(c).length===0&&e.rmdirSync(c);}catch{}}let d=r.join(i,".claude","settings.json");if(e.existsSync(d))try{let a=JSON.parse(e.readFileSync(d,"utf8")),p=a.hooks;if(p){let l=!1;for(let[s,t]of Object.entries(p)){if(!Array.isArray(t))continue;let n=t.filter(f=>!JSON.stringify(f).includes("paradigm-"));n.length!==t.length&&(l=!0,n.length===0?delete p[s]:p[s]=n);}l&&(Object.keys(p).length===0?delete a.hooks:a.hooks=p,e.writeFileSync(d,JSON.stringify(a,null,2)+`
1352
- `,"utf8"),h.push("settings.json hooks"));}}catch{}return {cleaned:h.length>0,removed:h}}function q(i,h){try{let c=r.join(E.tmpdir(),`paradigm-hook-validate-${Date.now()}.sh`);e.writeFileSync(c,i,"utf8");try{return execSync(`bash -n "${c}" 2>&1`,{encoding:"utf-8"}),null}catch(d){return `${h}: bash syntax error \u2014 ${d.message?.split(`
1353
- `)[0]||"unknown error"}`}finally{try{e.unlinkSync(c);}catch{}}}catch{return null}}var H=`#!/bin/sh
1473
+ `;function x(){try{let i=r.join(E.homedir(),".claude","settings.json");if(!e.existsSync(i))return {active:!1};if(!JSON.parse(e.readFileSync(i,"utf8")).enabledPlugins?.["paradigm@a-paradigm"])return {active:!1};let d=r.join(E.homedir(),".claude","plugins","cache","a-paradigm","paradigm");if(!e.existsSync(d))return {active:!1};let a=e.readdirSync(d).filter(s=>e.statSync(r.join(d,s)).isDirectory()).sort().reverse();if(a.length===0)return {active:!1};let p=r.join(d,a[0]),l=r.join(p,"hooks","hooks.json");return e.existsSync(l)?{active:!0,cacheVersion:a[0]}:{active:!1}}catch{return {active:false}}}function Y(){try{let i=x();if(!i.active||!i.cacheVersion)return {compatible:!0};let h=r.join(E.homedir(),".claude","plugins","cache","a-paradigm","paradigm",i.cacheVersion,"hooks.json");if(!e.existsSync(h))return {compatible:!0};let d=JSON.parse(e.readFileSync(h,"utf8")).compatibleVersions;if(!d)return {compatible:!0};let a=X();if(!a)return {compatible:!0};let p=d.split(/\s+/);for(let l of p){let s=l.match(/^(>=?|<=?)\s*(\d+\.\d+\.\d+)/);if(!s)continue;let[,t,n]=s,f=W(a,n);if(t===">="&&f<0)return {compatible:!1,message:`Plugin requires paradigm ${d}, current: ${a}`};if(t===">"&&f<=0)return {compatible:!1,message:`Plugin requires paradigm ${d}, current: ${a}`};if(t==="<="&&f>0)return {compatible:!1,message:`Plugin requires paradigm ${d}, current: ${a}`};if(t==="<"&&f>=0)return {compatible:!1,message:`Plugin requires paradigm ${d}, current: ${a}`}}return {compatible:!0}}catch{return {compatible:true}}}function X(){try{let i=r.join(r.dirname(new URL(import.meta.url).pathname),"..","..","package.json");return JSON.parse(e.readFileSync(i,"utf8")).version||null}catch{return null}}function W(i,h){let c=i.split(".").map(Number),d=h.split(".").map(Number);for(let a=0;a<3;a++){if((c[a]||0)<(d[a]||0))return -1;if((c[a]||0)>(d[a]||0))return 1}return 0}function q(i){let h=[],c=r.join(i,".claude","hooks");if(e.existsSync(c)){for(let a of ["paradigm-common.sh","paradigm-stop.sh","paradigm-precommit.sh","paradigm-postwrite.sh"]){let p=r.join(c,a);e.existsSync(p)&&(e.unlinkSync(p),h.push(a));}try{e.readdirSync(c).length===0&&e.rmdirSync(c);}catch{}}let d=r.join(i,".claude","settings.json");if(e.existsSync(d))try{let a=JSON.parse(e.readFileSync(d,"utf8")),p=a.hooks;if(p){let l=!1;for(let[s,t]of Object.entries(p)){if(!Array.isArray(t))continue;let n=t.filter(f=>!JSON.stringify(f).includes("paradigm-"));n.length!==t.length&&(l=!0,n.length===0?delete p[s]:p[s]=n);}l&&(Object.keys(p).length===0?delete a.hooks:a.hooks=p,e.writeFileSync(d,JSON.stringify(a,null,2)+`
1474
+ `,"utf8"),h.push("settings.json hooks"));}}catch{}return {cleaned:h.length>0,removed:h}}function J(i,h){try{let c=r.join(E.tmpdir(),`paradigm-hook-validate-${Date.now()}.sh`);e.writeFileSync(c,i,"utf8");try{return execSync(`bash -n "${c}" 2>&1`,{encoding:"utf-8"}),null}catch(d){return `${h}: bash syntax error \u2014 ${d.message?.split(`
1475
+ `)[0]||"unknown error"}`}finally{try{e.unlinkSync(c);}catch{}}}catch{return null}}var F=`#!/bin/sh
1354
1476
  # Paradigm post-commit hook - captures history from commits
1355
1477
  # Installed by: paradigm hooks install
1356
1478
 
@@ -1434,7 +1556,7 @@ if [ -n "$SYMBOLS" ] && [ -d ".paradigm/history" ]; then
1434
1556
 
1435
1557
  echo "[paradigm] History entry $ID recorded"
1436
1558
  fi
1437
- `,j=`#!/bin/sh
1559
+ `,H=`#!/bin/sh
1438
1560
  # Paradigm pre-push hook - reindex history before pushing
1439
1561
  # Installed by: paradigm hooks install
1440
1562
 
@@ -1444,12 +1566,12 @@ if [ -d ".paradigm/history" ] && [ -f ".paradigm/history/log.jsonl" ]; then
1444
1566
  fi
1445
1567
  `;async function te(i={}){let h=process.cwd(),c=i.dryRun||false;c&&console.log(o.cyan(`
1446
1568
  [dry-run] Showing what would be installed:
1447
- `));let d=i.claudeCode&&!i.postCommit&&!i.prePush&&!i.cursor,a=i.cursor&&!i.postCommit&&!i.prePush&&!i.claudeCode;if(!c){let s=[{name:"post-commit",content:H},{name:"pre-push",content:j},{name:"paradigm-common",content:y},{name:"claude-code-stop",content:R},{name:"claude-code-precommit",content:A},{name:"claude-code-postwrite",content:U},{name:"cursor-session-start",content:P},{name:"cursor-stop",content:v},{name:"cursor-precommit",content:b},{name:"cursor-postwrite",content:D},{name:"cursor-pretooluse",content:L},{name:"cursor-posttooluse",content:w}];for(let t of s){let n=q(t.content,t.name);if(n){console.log(o.red(`Hook syntax error: ${n}`)),console.log(o.gray("Aborting installation. Fix the hook script and try again."));return}}}let p=X();if(p.compatible||(console.log(o.yellow(`
1569
+ `));let d=i.claudeCode&&!i.postCommit&&!i.prePush&&!i.cursor,a=i.cursor&&!i.postCommit&&!i.prePush&&!i.claudeCode;if(!c){let s=[{name:"post-commit",content:F},{name:"pre-push",content:H},{name:"paradigm-common",content:C},{name:"claude-code-stop",content:R},{name:"claude-code-precommit",content:U},{name:"claude-code-postwrite",content:A},{name:"cursor-session-start",content:D},{name:"cursor-stop",content:v},{name:"cursor-precommit",content:b},{name:"cursor-postwrite",content:P},{name:"cursor-pretooluse",content:L},{name:"cursor-posttooluse",content:w}];for(let t of s){let n=J(t.content,t.name);if(n){console.log(o.red(`Hook syntax error: ${n}`)),console.log(o.gray("Aborting installation. Fix the hook script and try again."));return}}}let p=Y();if(p.compatible||(console.log(o.yellow(`
1448
1570
  \u26A0 ${p.message}`)),console.log(o.gray(` Hook installation will continue, but behavior may differ from plugin expectations.
1449
- `))),!d&&!a){let s=r.join(h,".git");if(!e.existsSync(s)){console.log(o.red("Not a git repository."));return}let t=r.join(s,"hooks"),n=!i.postCommit&&!i.prePush&&!i.claudeCode,f=[];if(n||i.postCommit){let u=r.join(t,"post-commit");if(c){let g=e.existsSync(u)&&!i.force?"skip (exists)":"install";console.log(o.gray(` post-commit: ${g} \u2192 ${u}`));}else e.existsSync(u)&&!i.force?e.readFileSync(u,"utf8").includes("paradigm")?console.log(o.gray("post-commit hook already installed by paradigm")):console.log(o.yellow("post-commit hook exists. Use --force to overwrite.")):(e.mkdirSync(t,{recursive:true}),e.writeFileSync(u,H),e.chmodSync(u,"755"),f.push("post-commit"));}if(n||i.prePush){let u=r.join(t,"pre-push");if(c){let g=e.existsSync(u)&&!i.force?"skip (exists)":"install";console.log(o.gray(` pre-push: ${g} \u2192 ${u}`));}else e.existsSync(u)&&!i.force?e.readFileSync(u,"utf8").includes("paradigm")?console.log(o.gray("pre-push hook already installed by paradigm")):console.log(o.yellow("pre-push hook exists. Use --force to overwrite.")):(e.mkdirSync(t,{recursive:true}),e.writeFileSync(u,j),e.chmodSync(u,"755"),f.push("pre-push"));}!c&&f.length>0&&console.log(o.green(`Git hooks installed: ${f.join(", ")}`));let _=r.join(h,".paradigm/history");!e.existsSync(_)&&!c&&console.log(o.gray("Tip: Run `paradigm history init` to initialize history tracking"));}let l=!i.postCommit&&!i.prePush&&!i.claudeCode&&!i.cursor;(l||i.claudeCode)&&(c?(console.log(o.gray(" Claude Code hooks: would install paradigm-stop.sh, paradigm-precommit.sh, paradigm-postwrite.sh")),console.log(o.gray(` \u2192 ${r.join(h,".claude","hooks")}/`)),console.log(o.gray(" \u2192 Update .claude/settings.json with hook configuration"))):await B(h,i.force)),(l||i.cursor)&&(c?(console.log(o.gray(" Cursor hooks: would install paradigm-session-start.sh, paradigm-stop.sh, paradigm-precommit.sh, paradigm-postwrite.sh, paradigm-pretooluse.sh, paradigm-posttooluse.sh")),console.log(o.gray(` \u2192 ${r.join(h,".cursor","hooks")}/`)),console.log(o.gray(" \u2192 Update .cursor/hooks.json"))):await K(h,i.force)),c&&console.log(o.cyan(`
1571
+ `))),!d&&!a){let s=r.join(h,".git");if(!e.existsSync(s)){console.log(o.red("Not a git repository."));return}let t=r.join(s,"hooks"),n=!i.postCommit&&!i.prePush&&!i.claudeCode,f=[];if(n||i.postCommit){let u=r.join(t,"post-commit");if(c){let g=e.existsSync(u)&&!i.force?"skip (exists)":"install";console.log(o.gray(` post-commit: ${g} \u2192 ${u}`));}else e.existsSync(u)&&!i.force?e.readFileSync(u,"utf8").includes("paradigm")?console.log(o.gray("post-commit hook already installed by paradigm")):console.log(o.yellow("post-commit hook exists. Use --force to overwrite.")):(e.mkdirSync(t,{recursive:true}),e.writeFileSync(u,F),e.chmodSync(u,"755"),f.push("post-commit"));}if(n||i.prePush){let u=r.join(t,"pre-push");if(c){let g=e.existsSync(u)&&!i.force?"skip (exists)":"install";console.log(o.gray(` pre-push: ${g} \u2192 ${u}`));}else e.existsSync(u)&&!i.force?e.readFileSync(u,"utf8").includes("paradigm")?console.log(o.gray("pre-push hook already installed by paradigm")):console.log(o.yellow("pre-push hook exists. Use --force to overwrite.")):(e.mkdirSync(t,{recursive:true}),e.writeFileSync(u,H),e.chmodSync(u,"755"),f.push("pre-push"));}!c&&f.length>0&&console.log(o.green(`Git hooks installed: ${f.join(", ")}`));let O=r.join(h,".paradigm/history");!e.existsSync(O)&&!c&&console.log(o.gray("Tip: Run `paradigm history init` to initialize history tracking"));}let l=!i.postCommit&&!i.prePush&&!i.claudeCode&&!i.cursor;(l||i.claudeCode)&&(c?(console.log(o.gray(" Claude Code hooks: would install paradigm-stop.sh, paradigm-precommit.sh, paradigm-postwrite.sh")),console.log(o.gray(` \u2192 ${r.join(h,".claude","hooks")}/`)),console.log(o.gray(" \u2192 Update .claude/settings.json with hook configuration"))):await B(h,i.force)),(l||i.cursor)&&(c?(console.log(o.gray(" Cursor hooks: would install paradigm-session-start.sh, paradigm-stop.sh, paradigm-precommit.sh, paradigm-postwrite.sh, paradigm-pretooluse.sh, paradigm-posttooluse.sh")),console.log(o.gray(` \u2192 ${r.join(h,".cursor","hooks")}/`)),console.log(o.gray(" \u2192 Update .cursor/hooks.json"))):await K(h,i.force)),c&&console.log(o.cyan(`
1450
1572
  [dry-run] No changes made.
1451
- `));}async function B(i,h){let c=x();if(c.active){console.log(o.cyan(` Paradigm plugin v${c.cacheVersion} is active \u2014 hooks are managed by the plugin.`));let{cleaned:O,removed:$}=J(i);console.log(O?o.green(` Cleaned up stale project hooks: ${$.join(", ")}`):o.gray(" No stale project hooks to clean up.")),console.log(o.gray(" Plugin hooks auto-update with each session \u2014 no manual install needed."));return}let d=r.join(i,".claude","hooks");e.mkdirSync(d,{recursive:true});let a=[],p=r.join(d,"paradigm-common.sh");e.writeFileSync(p,y,"utf8"),e.chmodSync(p,"755");let l=[{name:"paradigm-stop.sh",content:R},{name:"paradigm-precommit.sh",content:A},{name:"paradigm-postwrite.sh",content:U}];for(let O of l){let $=r.join(d,O.name);if(e.existsSync($)&&!h){console.log(o.gray(` ${O.name}: already installed`));continue}e.writeFileSync($,O.content,"utf8"),e.chmodSync($,"755"),a.push(O.name);}let s=r.join(i,".claude","settings.json"),t={};if(e.existsSync(s))try{t=JSON.parse(e.readFileSync(s,"utf8"));}catch{}let n=t.hooks||{},f={hooks:[{type:"command",command:'bash "$CLAUDE_PROJECT_DIR/.claude/hooks/paradigm-stop.sh"',timeout:10}]},_={matcher:"Bash",hooks:[{type:"command",command:'bash "$CLAUDE_PROJECT_DIR/.claude/hooks/paradigm-precommit.sh"',timeout:30}]},u=n.Stop||[];u.some(O=>JSON.stringify(O).includes("paradigm-stop.sh"))||u.push(f),n.Stop=u;let S=n.PreToolUse||[];S.some(O=>JSON.stringify(O).includes("paradigm-precommit.sh"))||S.push(_),n.PreToolUse=S;let F={matcher:"Edit,Write",hooks:[{type:"command",command:'bash "$CLAUDE_PROJECT_DIR/.claude/hooks/paradigm-postwrite.sh"',timeout:5}]},T=n.PostToolUse||[];T.some(O=>JSON.stringify(O).includes("paradigm-postwrite.sh"))||T.push(F),n.PostToolUse=T,t.hooks=n,e.writeFileSync(s,JSON.stringify(t,null,2)+`
1452
- `,"utf8"),a.length>0&&console.log(o.green(`Claude Code hooks installed: ${a.join(", ")}`)),console.log(o.green("Claude Code settings.json updated with hook configuration"));}async function K(i,h){let c=r.join(i,".cursor","hooks");e.mkdirSync(c,{recursive:true});let d=[],a=r.join(c,"paradigm-common.sh");e.writeFileSync(a,y,"utf8"),e.chmodSync(a,"755");let p=[{name:"paradigm-session-start.sh",content:P},{name:"paradigm-stop.sh",content:v},{name:"paradigm-precommit.sh",content:b},{name:"paradigm-postwrite.sh",content:D},{name:"paradigm-pretooluse.sh",content:L},{name:"paradigm-posttooluse.sh",content:w}];for(let m of p){let k=r.join(c,m.name);if(e.existsSync(k)&&!h){console.log(o.gray(` ${m.name}: already installed (Cursor)`));continue}e.writeFileSync(k,m.content,"utf8"),e.chmodSync(k,"755"),d.push(m.name);}let l=r.join(i,".cursor","hooks.json"),s={};if(e.existsSync(l))try{s=JSON.parse(e.readFileSync(l,"utf8"));}catch{}s.version=1;let t=s.hooks||{},n={command:".cursor/hooks/paradigm-session-start.sh",timeout:5},f={command:".cursor/hooks/paradigm-stop.sh",timeout:10,loop_limit:3},_={command:".cursor/hooks/paradigm-postwrite.sh",timeout:5},u={command:".cursor/hooks/paradigm-precommit.sh",matcher:"git commit",timeout:30},g=t.sessionStart||[];g.some(m=>JSON.stringify(m).includes("paradigm-session-start.sh"))||g.push(n),t.sessionStart=g;let I=t.stop||[];I.some(m=>JSON.stringify(m).includes("paradigm-stop.sh"))||I.push(f),t.stop=I;let T=t.afterFileEdit||[];T.some(m=>JSON.stringify(m).includes("paradigm-postwrite.sh"))||T.push(_),t.afterFileEdit=T;let O={command:".cursor/hooks/paradigm-pretooluse.sh",matcher:"Edit|Write",timeout:5},$=t.preToolUse||[];$.some(m=>JSON.stringify(m).includes("paradigm-pretooluse.sh"))||$.push(O),t.preToolUse=$;let V={command:".cursor/hooks/paradigm-posttooluse.sh",matcher:"Edit|Write",timeout:5},C=t.postToolUse||[];C.some(m=>JSON.stringify(m).includes("paradigm-posttooluse.sh"))||C.push(V),t.postToolUse=C;let N=t.beforeShellExecution||[];N.some(m=>JSON.stringify(m).includes("paradigm-precommit.sh"))||N.push(u),t.beforeShellExecution=N,s.hooks=t,e.writeFileSync(l,JSON.stringify(s,null,2)+`
1573
+ `));}async function B(i,h){let c=x();if(c.active){console.log(o.cyan(` Paradigm plugin v${c.cacheVersion} is active \u2014 hooks are managed by the plugin.`));let{cleaned:_,removed:$}=q(i);console.log(_?o.green(` Cleaned up stale project hooks: ${$.join(", ")}`):o.gray(" No stale project hooks to clean up.")),console.log(o.gray(" Plugin hooks auto-update with each session \u2014 no manual install needed."));return}let d=r.join(i,".claude","hooks");e.mkdirSync(d,{recursive:true});let a=[],p=r.join(d,"paradigm-common.sh");e.writeFileSync(p,C,"utf8"),e.chmodSync(p,"755");let l=[{name:"paradigm-stop.sh",content:R},{name:"paradigm-precommit.sh",content:U},{name:"paradigm-postwrite.sh",content:A}];for(let _ of l){let $=r.join(d,_.name);if(e.existsSync($)&&!h){console.log(o.gray(` ${_.name}: already installed`));continue}e.writeFileSync($,_.content,"utf8"),e.chmodSync($,"755"),a.push(_.name);}let s=r.join(i,".claude","settings.json"),t={};if(e.existsSync(s))try{t=JSON.parse(e.readFileSync(s,"utf8"));}catch{}let n=t.hooks||{},f={hooks:[{type:"command",command:'bash "$CLAUDE_PROJECT_DIR/.claude/hooks/paradigm-stop.sh"',timeout:10}]},O={matcher:"Bash",hooks:[{type:"command",command:'bash "$CLAUDE_PROJECT_DIR/.claude/hooks/paradigm-precommit.sh"',timeout:30}]},u=n.Stop||[];u.some(_=>JSON.stringify(_).includes("paradigm-stop.sh"))||u.push(f),n.Stop=u;let T=n.PreToolUse||[];T.some(_=>JSON.stringify(_).includes("paradigm-precommit.sh"))||T.push(O),n.PreToolUse=T;let V={matcher:"Edit,Write",hooks:[{type:"command",command:'bash "$CLAUDE_PROJECT_DIR/.claude/hooks/paradigm-postwrite.sh"',timeout:5}]},S=n.PostToolUse||[];S.some(_=>JSON.stringify(_).includes("paradigm-postwrite.sh"))||S.push(V),n.PostToolUse=S,t.hooks=n,e.writeFileSync(s,JSON.stringify(t,null,2)+`
1574
+ `,"utf8"),a.length>0&&console.log(o.green(`Claude Code hooks installed: ${a.join(", ")}`)),console.log(o.green("Claude Code settings.json updated with hook configuration"));}async function K(i,h){let c=r.join(i,".cursor","hooks");e.mkdirSync(c,{recursive:true});let d=[],a=r.join(c,"paradigm-common.sh");e.writeFileSync(a,C,"utf8"),e.chmodSync(a,"755");let p=[{name:"paradigm-session-start.sh",content:D},{name:"paradigm-stop.sh",content:v},{name:"paradigm-precommit.sh",content:b},{name:"paradigm-postwrite.sh",content:P},{name:"paradigm-pretooluse.sh",content:L},{name:"paradigm-posttooluse.sh",content:w}];for(let m of p){let N=r.join(c,m.name);if(e.existsSync(N)&&!h){console.log(o.gray(` ${m.name}: already installed (Cursor)`));continue}e.writeFileSync(N,m.content,"utf8"),e.chmodSync(N,"755"),d.push(m.name);}let l=r.join(i,".cursor","hooks.json"),s={};if(e.existsSync(l))try{s=JSON.parse(e.readFileSync(l,"utf8"));}catch{}s.version=1;let t=s.hooks||{},n={command:".cursor/hooks/paradigm-session-start.sh",timeout:5},f={command:".cursor/hooks/paradigm-stop.sh",timeout:10,loop_limit:3},O={command:".cursor/hooks/paradigm-postwrite.sh",timeout:5},u={command:".cursor/hooks/paradigm-precommit.sh",matcher:"git commit",timeout:30},g=t.sessionStart||[];g.some(m=>JSON.stringify(m).includes("paradigm-session-start.sh"))||g.push(n),t.sessionStart=g;let I=t.stop||[];I.some(m=>JSON.stringify(m).includes("paradigm-stop.sh"))||I.push(f),t.stop=I;let S=t.afterFileEdit||[];S.some(m=>JSON.stringify(m).includes("paradigm-postwrite.sh"))||S.push(O),t.afterFileEdit=S;let _={command:".cursor/hooks/paradigm-pretooluse.sh",matcher:"Edit|Write",timeout:5},$=t.preToolUse||[];$.some(m=>JSON.stringify(m).includes("paradigm-pretooluse.sh"))||$.push(_),t.preToolUse=$;let j={command:".cursor/hooks/paradigm-posttooluse.sh",matcher:"Edit|Write",timeout:5},y=t.postToolUse||[];y.some(m=>JSON.stringify(m).includes("paradigm-posttooluse.sh"))||y.push(j),t.postToolUse=y;let k=t.beforeShellExecution||[];k.some(m=>JSON.stringify(m).includes("paradigm-precommit.sh"))||k.push(u),t.beforeShellExecution=k,s.hooks=t,e.writeFileSync(l,JSON.stringify(s,null,2)+`
1453
1575
  `,"utf8"),d.length>0&&console.log(o.green(`Cursor hooks installed: ${d.join(", ")}`)),console.log(o.green("Cursor hooks.json updated with hook configuration"));}async function re(i={}){let h=process.cwd(),c=i.dryRun||false;if(c&&console.log(o.cyan(`
1454
1576
  [dry-run] Showing what would be removed:
1455
1577
  `)),!i.cursor){let d=r.join(h,".git");if(!e.existsSync(d)){console.log(o.red("Not a git repository."));return}let a=r.join(d,"hooks"),p=[];for(let l of ["post-commit","pre-push"]){let s=r.join(a,l);e.existsSync(s)&&e.readFileSync(s,"utf8").includes("paradigm")&&(c?console.log(o.gray(` Would remove: ${s}`)):e.unlinkSync(s),p.push(l));}c?p.length===0&&console.log(o.gray(" No paradigm git hooks to remove")):p.length>0?console.log(o.green(`Git hooks removed: ${p.join(", ")}`)):console.log(o.gray("No paradigm git hooks found to remove"));}if(i.cursor){let d=r.join(h,".cursor","hooks"),a=[];for(let l of ["paradigm-session-start.sh","paradigm-stop.sh","paradigm-precommit.sh","paradigm-postwrite.sh","paradigm-pretooluse.sh","paradigm-posttooluse.sh"]){let s=r.join(d,l);e.existsSync(s)&&(c?console.log(o.gray(` Would remove: ${s}`)):e.unlinkSync(s),a.push(l));}let p=r.join(h,".cursor","hooks.json");if(e.existsSync(p))if(c)console.log(o.gray(` Would clean paradigm entries from: ${p}`));else try{let l=JSON.parse(e.readFileSync(p,"utf8")),s=l.hooks||{};for(let t of ["sessionStart","stop","afterFileEdit","beforeShellExecution","preToolUse","postToolUse"])Array.isArray(s[t])&&(s[t]=s[t].filter(n=>!JSON.stringify(n).includes("paradigm-")),s[t].length===0&&delete s[t]);l.hooks=s,e.writeFileSync(p,JSON.stringify(l,null,2)+`
@@ -1457,10 +1579,10 @@ fi
1457
1579
  [dry-run] No changes made.
1458
1580
  `));}async function ae(){let i=process.cwd(),h=r.join(i,".git");if(e.existsSync(h)){console.log(o.magenta(`
1459
1581
  Git Hooks Status
1460
- `));let l=r.join(h,"hooks"),s=["post-commit","pre-push"];for(let n of s){let f=r.join(l,n);e.existsSync(f)?e.readFileSync(f,"utf8").includes("paradigm")?console.log(o.green(` ${n}: installed (paradigm)`)):console.log(o.yellow(` ${n}: exists (other)`)):console.log(o.gray(` ${n}: not installed`));}console.log();let t=r.join(i,".paradigm/history");if(e.existsSync(t)){let n=r.join(t,"log.jsonl");if(e.existsSync(n)){let _=e.readFileSync(n,"utf8").split(`
1461
- `).filter(u=>u.trim()).length;console.log(o.white(` History entries: ${_}`));}}else console.log(o.gray(" History: not initialized")),console.log(o.gray(" Run `paradigm history init` to enable"));}else console.log(o.gray(`
1582
+ `));let l=r.join(h,"hooks"),s=["post-commit","pre-push"];for(let n of s){let f=r.join(l,n);e.existsSync(f)?e.readFileSync(f,"utf8").includes("paradigm")?console.log(o.green(` ${n}: installed (paradigm)`)):console.log(o.yellow(` ${n}: exists (other)`)):console.log(o.gray(` ${n}: not installed`));}console.log();let t=r.join(i,".paradigm/history");if(e.existsSync(t)){let n=r.join(t,"log.jsonl");if(e.existsSync(n)){let O=e.readFileSync(n,"utf8").split(`
1583
+ `).filter(u=>u.trim()).length;console.log(o.white(` History entries: ${O}`));}}else console.log(o.gray(" History: not initialized")),console.log(o.gray(" Run `paradigm history init` to enable"));}else console.log(o.gray(`
1462
1584
  Not a git repository (git hooks N/A)
1463
1585
  `));console.log(o.magenta(` Claude Code Hooks Status
1464
- `));let c=x();if(c.active){console.log(o.cyan(` Plugin: paradigm v${c.cacheVersion} (active)`)),console.log(o.green(" Hooks are managed by the plugin \u2014 auto-updates with each session."));let l=r.join(i,".claude","hooks"),s=[];for(let f of ["paradigm-common.sh","paradigm-stop.sh","paradigm-precommit.sh","paradigm-postwrite.sh"])e.existsSync(r.join(l,f))&&s.push(f);let t=r.join(i,".claude","settings.json"),n=false;if(e.existsSync(t))try{let f=JSON.parse(e.readFileSync(t,"utf8"));n=JSON.stringify(f.hooks||{}).includes("paradigm-");}catch{}(s.length>0||n)&&(console.log(o.yellow(` WARNING: Stale project hooks detected (${s.join(", ")}${n?", settings.json entries":""})`)),console.log(o.yellow(" These shadow the plugin hooks and may run outdated logic.")),console.log(o.gray(" Run `paradigm hooks install --claude-code` to clean them up.")));}else {console.log(o.gray(" Plugin: not active (using project-level hooks)"));let l=r.join(i,".claude","hooks"),s=["paradigm-stop.sh","paradigm-precommit.sh","paradigm-postwrite.sh"];for(let n of s){let f=r.join(l,n);e.existsSync(f)?console.log(o.green(` ${n}: installed`)):console.log(o.gray(` ${n}: not installed`));}let t=r.join(i,".claude","settings.json");if(e.existsSync(t))try{let f=JSON.parse(e.readFileSync(t,"utf8")).hooks||{},_=JSON.stringify(f.Stop||[]).includes("paradigm-stop.sh"),u=JSON.stringify(f.PreToolUse||[]).includes("paradigm-precommit.sh"),g=JSON.stringify(f.PostToolUse||[]).includes("paradigm-postwrite.sh");console.log(o.gray(` settings.json Stop hook: ${_?"configured":"missing"}`)),console.log(o.gray(` settings.json PreToolUse hook: ${u?"configured":"missing"}`)),console.log(o.gray(` settings.json PostToolUse hook: ${g?"configured":"missing"}`));}catch{console.log(o.yellow(" settings.json: parse error"));}else console.log(o.gray(" settings.json: not found"));}console.log(o.magenta(`
1586
+ `));let c=x();if(c.active){console.log(o.cyan(` Plugin: paradigm v${c.cacheVersion} (active)`)),console.log(o.green(" Hooks are managed by the plugin \u2014 auto-updates with each session."));let l=r.join(i,".claude","hooks"),s=[];for(let f of ["paradigm-common.sh","paradigm-stop.sh","paradigm-precommit.sh","paradigm-postwrite.sh"])e.existsSync(r.join(l,f))&&s.push(f);let t=r.join(i,".claude","settings.json"),n=false;if(e.existsSync(t))try{let f=JSON.parse(e.readFileSync(t,"utf8"));n=JSON.stringify(f.hooks||{}).includes("paradigm-");}catch{}(s.length>0||n)&&(console.log(o.yellow(` WARNING: Stale project hooks detected (${s.join(", ")}${n?", settings.json entries":""})`)),console.log(o.yellow(" These shadow the plugin hooks and may run outdated logic.")),console.log(o.gray(" Run `paradigm hooks install --claude-code` to clean them up.")));}else {console.log(o.gray(" Plugin: not active (using project-level hooks)"));let l=r.join(i,".claude","hooks"),s=["paradigm-stop.sh","paradigm-precommit.sh","paradigm-postwrite.sh"];for(let n of s){let f=r.join(l,n);e.existsSync(f)?console.log(o.green(` ${n}: installed`)):console.log(o.gray(` ${n}: not installed`));}let t=r.join(i,".claude","settings.json");if(e.existsSync(t))try{let f=JSON.parse(e.readFileSync(t,"utf8")).hooks||{},O=JSON.stringify(f.Stop||[]).includes("paradigm-stop.sh"),u=JSON.stringify(f.PreToolUse||[]).includes("paradigm-precommit.sh"),g=JSON.stringify(f.PostToolUse||[]).includes("paradigm-postwrite.sh");console.log(o.gray(` settings.json Stop hook: ${O?"configured":"missing"}`)),console.log(o.gray(` settings.json PreToolUse hook: ${u?"configured":"missing"}`)),console.log(o.gray(` settings.json PostToolUse hook: ${g?"configured":"missing"}`));}catch{console.log(o.yellow(" settings.json: parse error"));}else console.log(o.gray(" settings.json: not found"));}console.log(o.magenta(`
1465
1587
  Cursor Hooks Status
1466
- `));let d=r.join(i,".cursor","hooks"),a=["paradigm-session-start.sh","paradigm-stop.sh","paradigm-precommit.sh","paradigm-postwrite.sh","paradigm-pretooluse.sh","paradigm-posttooluse.sh"];for(let l of a){let s=r.join(d,l);e.existsSync(s)?console.log(o.green(` ${l}: installed`)):console.log(o.gray(` ${l}: not installed`));}let p=r.join(i,".cursor","hooks.json");if(e.existsSync(p))try{let s=JSON.parse(e.readFileSync(p,"utf8")).hooks||{},t=JSON.stringify(s.sessionStart||[]).includes("paradigm-session-start.sh"),n=JSON.stringify(s.stop||[]).includes("paradigm-stop.sh"),f=JSON.stringify(s.afterFileEdit||[]).includes("paradigm-postwrite.sh"),_=JSON.stringify(s.beforeShellExecution||[]).includes("paradigm-precommit.sh"),u=JSON.stringify(s.preToolUse||[]).includes("paradigm-pretooluse.sh"),g=JSON.stringify(s.postToolUse||[]).includes("paradigm-posttooluse.sh");console.log(o.gray(` hooks.json sessionStart: ${t?"configured":"missing"}`)),console.log(o.gray(` hooks.json stop: ${n?"configured":"missing"}`)),console.log(o.gray(` hooks.json afterFileEdit: ${f?"configured":"missing"}`)),console.log(o.gray(` hooks.json preToolUse: ${u?"configured":"missing"}`)),console.log(o.gray(` hooks.json postToolUse: ${g?"configured":"missing"}`)),console.log(o.gray(` hooks.json beforeShellExecution: ${_?"configured":"missing"}`));}catch{console.log(o.yellow(" hooks.json: parse error"));}else console.log(o.gray(" hooks.json: not found"));console.log();}export{te as a,re as b,ae as c};
1588
+ `));let d=r.join(i,".cursor","hooks"),a=["paradigm-session-start.sh","paradigm-stop.sh","paradigm-precommit.sh","paradigm-postwrite.sh","paradigm-pretooluse.sh","paradigm-posttooluse.sh"];for(let l of a){let s=r.join(d,l);e.existsSync(s)?console.log(o.green(` ${l}: installed`)):console.log(o.gray(` ${l}: not installed`));}let p=r.join(i,".cursor","hooks.json");if(e.existsSync(p))try{let s=JSON.parse(e.readFileSync(p,"utf8")).hooks||{},t=JSON.stringify(s.sessionStart||[]).includes("paradigm-session-start.sh"),n=JSON.stringify(s.stop||[]).includes("paradigm-stop.sh"),f=JSON.stringify(s.afterFileEdit||[]).includes("paradigm-postwrite.sh"),O=JSON.stringify(s.beforeShellExecution||[]).includes("paradigm-precommit.sh"),u=JSON.stringify(s.preToolUse||[]).includes("paradigm-pretooluse.sh"),g=JSON.stringify(s.postToolUse||[]).includes("paradigm-posttooluse.sh");console.log(o.gray(` hooks.json sessionStart: ${t?"configured":"missing"}`)),console.log(o.gray(` hooks.json stop: ${n?"configured":"missing"}`)),console.log(o.gray(` hooks.json afterFileEdit: ${f?"configured":"missing"}`)),console.log(o.gray(` hooks.json preToolUse: ${u?"configured":"missing"}`)),console.log(o.gray(` hooks.json postToolUse: ${g?"configured":"missing"}`)),console.log(o.gray(` hooks.json beforeShellExecution: ${O?"configured":"missing"}`));}catch{console.log(o.yellow(" hooks.json: parse error"));}else console.log(o.gray(" hooks.json: not found"));console.log();}export{te as a,re as b,ae as c};