@a-company/paradigm 3.9.0 → 3.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/dist/{accept-orchestration-DIGPJVUR.js → accept-orchestration-Z35I5AYN.js} +5 -5
  2. package/dist/{assessment-loader-T4GPBHLB.js → assessment-loader-C5EOUM47.js} +0 -1
  3. package/dist/{chunk-Y4XZWCHK.js → chunk-24AAVLME.js} +8 -8
  4. package/dist/{chunk-4N6AYEEA.js → chunk-3TWXFFZ3.js} +1 -1
  5. package/dist/{chunk-5S5CF3ER.js → chunk-4ZO3ZOPM.js} +19 -2141
  6. package/dist/{chunk-6RNYVBSG.js → chunk-CP6IZGUN.js} +4 -4
  7. package/dist/{chunk-M2XMTJHQ.js → chunk-DS5QY37M.js} +201 -287
  8. package/dist/chunk-F6EJKLF4.js +4971 -0
  9. package/dist/chunk-MW5DMGBB.js +255 -0
  10. package/dist/{chunk-KFHK6EBI.js → chunk-OSYMVGWX.js} +59 -3
  11. package/dist/{chunk-GY5KO3YZ.js → chunk-RDPXBMHK.js} +1 -1
  12. package/dist/{chunk-ADOBV4PH.js → chunk-UVI3OH3G.js} +6 -2127
  13. package/dist/{diff-J6C5IHPV.js → diff-PZAYCIAE.js} +5 -5
  14. package/dist/{dist-OLFOTUHS.js → dist-6SX5ZKKF.js} +2 -2
  15. package/dist/{dist-OMY7U6NR.js → dist-YB7T54QE.js} +1 -2
  16. package/dist/{doctor-TQYRF7KK.js → doctor-3YQ55536.js} +1 -1
  17. package/dist/drift-FH2UY64B.js +251 -0
  18. package/dist/{flow-7JUH6D4H.js → flow-MCKPJGRJ.js} +1 -1
  19. package/dist/{habits-ZJBAL4HD.js → habits-NC2TRMRV.js} +2 -2
  20. package/dist/{hooks-DLZEYHI3.js → hooks-JXYHVGIN.js} +1 -1
  21. package/dist/index.js +77 -51
  22. package/dist/mcp.js +5502 -9984
  23. package/dist/{orchestrate-FAV64G2R.js → orchestrate-BGRFBGBH.js} +5 -5
  24. package/dist/{plugin-update-checker-TWBWUSAG.js → plugin-update-checker-S3W4BUJO.js} +0 -1
  25. package/dist/portal-check-2HI4FFD6.js +42 -0
  26. package/dist/portal-compliance-KQCTAQTJ.js +18 -0
  27. package/dist/{providers-NQ67LO2Z.js → providers-IONB4YRJ.js} +1 -1
  28. package/dist/reindex-ZM6J53UP.js +11 -0
  29. package/dist/{sentinel-KDIGZWKT.js → sentinel-BGCISNIK.js} +1 -1
  30. package/dist/{server-NN7WDAZJ.js → server-3K3TTJH3.js} +1 -1
  31. package/dist/{shift-KJWSJLWN.js → shift-6I6N6RNK.js} +36 -8
  32. package/dist/{spawn-EO7B2UM3.js → spawn-WGFJ5RQZ.js} +5 -5
  33. package/dist/{task-loader-GUX4KS6N.js → task-loader-7M2FCBX6.js} +0 -1
  34. package/dist/{team-6CCNANKE.js → team-AFOKQ7YQ.js} +6 -6
  35. package/dist/{triage-B5W6GZLT.js → triage-MKKIWBSW.js} +2 -2
  36. package/dist/workspace-VBTW7OYL.js +271 -0
  37. package/package.json +2 -1
  38. package/dist/chunk-HPC3JAUP.js +0 -42
  39. /package/dist/{chunk-CCG6KYBT.js → chunk-5N5LR2KS.js} +0 -0
@@ -1,14 +1,14 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ loadAgentsManifest
4
+ } from "./chunk-PMXRGPRQ.js";
2
5
  import {
3
6
  AuditLogger
4
7
  } from "./chunk-PBHIFAL4.js";
5
8
  import {
6
9
  getProvider,
7
10
  initializeProviders
8
- } from "./chunk-CCG6KYBT.js";
9
- import {
10
- loadAgentsManifest
11
- } from "./chunk-PMXRGPRQ.js";
11
+ } from "./chunk-5N5LR2KS.js";
12
12
  import {
13
13
  calculateCost,
14
14
  formatCost,
@@ -8,13 +8,19 @@ import { execSync } from "child_process";
8
8
  import chalk from "chalk";
9
9
 
10
10
  // src/commands/hooks/generated-hooks.ts
11
- var CLAUDE_CODE_STOP_HOOK = `#!/bin/sh
12
- # Paradigm Claude Code Stop Hook (v2)
13
- # Validates paradigm compliance before allowing the agent to finish.
14
- # Installed by: paradigm hooks install --claude-code
11
+ var COMMON_HOOK = `#!/bin/sh
12
+ # paradigm-common.sh \u2014 Shared compliance checks for Paradigm stop hooks
13
+ # Sourced by claude-code-stop.sh and cursor-stop.sh
15
14
  #
16
- # Hook type: Stop
17
- # Exit 0 = allow, Exit 2 = block with message
15
+ # Caller must set:
16
+ # CWD \u2014 Project root directory (already cd'd into)
17
+ # MODIFIED \u2014 Output of \`git diff --name-only HEAD\`
18
+ #
19
+ # Sets:
20
+ # VIOLATIONS \u2014 Newline-separated violation messages
21
+ # VIOLATION_COUNT \u2014 Number of violations
22
+ # ADVISORY \u2014 Non-blocking advisory text
23
+ # SOURCE_COUNT \u2014 Number of modified source files
18
24
  #
19
25
  # Checks:
20
26
  # 1. Source files modified without .purpose updates (threshold: 2+)
@@ -25,35 +31,9 @@ var CLAUDE_CODE_STOP_HOOK = `#!/bin/sh
25
31
  # 6. Aspect coverage advisory
26
32
  # 7. Lore entry expected for significant sessions (3+ source files)
27
33
  # 8. Blocking habits not satisfied (from paradigm_habits_check)
28
-
29
- # Read JSON from stdin (hook input)
30
- INPUT=$(cat)
31
-
32
- # Extract cwd from input (try jq first, fallback to grep)
33
- if command -v jq >/dev/null 2>&1; then
34
- CWD=$(echo "$INPUT" | jq -r '.cwd // empty' 2>/dev/null)
35
- else
36
- CWD=$(echo "$INPUT" | grep -o '"cwd"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*"cwd"[[:space:]]*:[[:space:]]*"//' | sed 's/"$//')
37
- fi
38
-
39
- if [ -z "$CWD" ]; then
40
- CWD="$(pwd)"
41
- fi
42
-
43
- # Not a paradigm project \u2014 pass
44
- if [ ! -d "$CWD/.paradigm" ]; then
45
- exit 0
46
- fi
47
-
48
- cd "$CWD" || exit 0
49
-
50
- # Get modified files (uncommitted changes)
51
- MODIFIED=$(git diff --name-only HEAD 2>/dev/null)
52
- if [ -z "$MODIFIED" ]; then
53
- # Clean up pending-review on pass
54
- rm -f ".paradigm/.pending-review"
55
- exit 0
56
- fi
34
+ # 9. Purpose-required patterns from config.yaml
35
+ # 10. Aspect drift detection with auto-heal
36
+ # 11. Portal gate implementation compliance
57
37
 
58
38
  VIOLATIONS=""
59
39
  VIOLATION_COUNT=0
@@ -297,6 +277,179 @@ if [ -f ".paradigm/.habits-blocking" ]; then
297
277
  VIOLATION_COUNT=$((VIOLATION_COUNT + 1))
298
278
  fi
299
279
 
280
+ # --- Check 9: Purpose-required patterns from config.yaml ---
281
+ CONFIG_FILE=".paradigm/config.yaml"
282
+ if [ -f "$CONFIG_FILE" ]; then
283
+ MISSING_REQUIRED=""
284
+ in_section=false
285
+ current_pattern=""
286
+
287
+ while IFS= read -r line; do
288
+ # Detect start of purpose-required section
289
+ case "$line" in
290
+ "purpose-required:"*|" purpose-required:"*)
291
+ in_section=true
292
+ continue
293
+ ;;
294
+ esac
295
+
296
+ if [ "$in_section" = true ]; then
297
+ # Check if we've left the section (new top-level or same-level key)
298
+ case "$line" in
299
+ " - pattern:"*)
300
+ current_pattern=$(echo "$line" | sed 's/.*pattern:[[:space:]]*//' | tr -d '"' | tr -d "'")
301
+ ;;
302
+ " depth:"*)
303
+ # We have pattern + depth \u2014 validate
304
+ if [ -n "$current_pattern" ]; then
305
+ for dir in $current_pattern; do
306
+ [ -d "$dir" ] || continue
307
+ if [ ! -f "$dir/.purpose" ]; then
308
+ # Deduplicate
309
+ case "$MISSING_REQUIRED" in
310
+ *"$dir"*) ;;
311
+ *) MISSING_REQUIRED="$MISSING_REQUIRED $dir" ;;
312
+ esac
313
+ fi
314
+ done
315
+ current_pattern=""
316
+ fi
317
+ ;;
318
+ [a-z]*|[A-Z]*)
319
+ # New top-level key \u2014 end of section
320
+ # Handle last pattern if depth wasn't specified
321
+ if [ -n "$current_pattern" ]; then
322
+ for dir in $current_pattern; do
323
+ [ -d "$dir" ] || continue
324
+ if [ ! -f "$dir/.purpose" ]; then
325
+ case "$MISSING_REQUIRED" in
326
+ *"$dir"*) ;;
327
+ *) MISSING_REQUIRED="$MISSING_REQUIRED $dir" ;;
328
+ esac
329
+ fi
330
+ done
331
+ current_pattern=""
332
+ fi
333
+ in_section=false
334
+ ;;
335
+ esac
336
+ fi
337
+ done < "$CONFIG_FILE"
338
+
339
+ # Handle last pattern if file ended while in section
340
+ if [ "$in_section" = true ] && [ -n "$current_pattern" ]; then
341
+ for dir in $current_pattern; do
342
+ [ -d "$dir" ] || continue
343
+ if [ ! -f "$dir/.purpose" ]; then
344
+ case "$MISSING_REQUIRED" in
345
+ *"$dir"*) ;;
346
+ *) MISSING_REQUIRED="$MISSING_REQUIRED $dir" ;;
347
+ esac
348
+ fi
349
+ done
350
+ fi
351
+
352
+ if [ -n "$MISSING_REQUIRED" ]; then
353
+ VIOLATIONS="$VIOLATIONS
354
+ - These directories match purpose-required patterns but have no .purpose file:
355
+ $MISSING_REQUIRED
356
+ Create .purpose files: paradigm_purpose_init + paradigm_purpose_add_component."
357
+ VIOLATION_COUNT=$((VIOLATION_COUNT + 1))
358
+ fi
359
+ fi
360
+
361
+ # --- Check 10: Aspect drift detection with auto-heal ---
362
+ if [ -f ".paradigm/aspect-graph.db" ]; then
363
+ DRIFT_RESULT=""
364
+ if command -v paradigm >/dev/null 2>&1; then
365
+ DRIFT_RESULT=$(paradigm drift check --json --auto-heal 2>/dev/null) || true
366
+ elif command -v npx >/dev/null 2>&1; then
367
+ DRIFT_RESULT=$(npx paradigm drift check --json --auto-heal 2>/dev/null) || true
368
+ fi
369
+
370
+ if [ -n "$DRIFT_RESULT" ]; then
371
+ DRIFTED_COUNT=$(echo "$DRIFT_RESULT" | grep -o '"driftedCount":[0-9]*' | sed 's/.*://')
372
+ HEALED_COUNT=$(echo "$DRIFT_RESULT" | grep -o '"healedCount":[0-9]*' | sed 's/.*://')
373
+
374
+ if [ -n "$HEALED_COUNT" ] && [ "$HEALED_COUNT" -gt 0 ] 2>/dev/null; then
375
+ echo "[paradigm] Auto-healed $HEALED_COUNT shifted anchor(s)." >&2
376
+ fi
377
+
378
+ if [ -n "$DRIFTED_COUNT" ] && [ "$DRIFTED_COUNT" -gt 0 ] 2>/dev/null; then
379
+ VIOLATIONS="$VIOLATIONS
380
+ - $DRIFTED_COUNT aspect anchor(s) have drifted (content genuinely changed).
381
+ Run paradigm_aspect_check to review. Update anchors in .purpose files."
382
+ VIOLATION_COUNT=$((VIOLATION_COUNT + 1))
383
+ fi
384
+ fi
385
+ fi
386
+
387
+ # --- Check 11: Portal gate implementation compliance ---
388
+ if [ -f "portal.yaml" ]; then
389
+ PORTAL_RESULT=""
390
+ if command -v paradigm >/dev/null 2>&1; then
391
+ PORTAL_RESULT=$(paradigm portal check --json 2>/dev/null) || true
392
+ elif command -v npx >/dev/null 2>&1; then
393
+ PORTAL_RESULT=$(npx paradigm portal check --json 2>/dev/null) || true
394
+ fi
395
+
396
+ if [ -n "$PORTAL_RESULT" ]; then
397
+ UNDECLARED=$(echo "$PORTAL_RESULT" | grep -o '"usedButUndeclaredCount":[0-9]*' | sed 's/.*://')
398
+
399
+ if [ -n "$UNDECLARED" ] && [ "$UNDECLARED" -gt 0 ] 2>/dev/null; then
400
+ UNDECLARED_LIST=$(echo "$PORTAL_RESULT" | grep -o '"usedButUndeclared":\\[[^]]*\\]' | sed 's/.*\\[//;s/\\].*//;s/"//g')
401
+ VIOLATIONS="$VIOLATIONS
402
+ - $UNDECLARED gate(s) used in code but not declared in portal.yaml:
403
+ $UNDECLARED_LIST
404
+ Add them to portal.yaml or use paradigm_portal_add_gate."
405
+ VIOLATION_COUNT=$((VIOLATION_COUNT + 1))
406
+ fi
407
+ fi
408
+ fi
409
+ `;
410
+ var CLAUDE_CODE_STOP_HOOK = `#!/bin/sh
411
+ # Paradigm Claude Code Stop Hook (v2)
412
+ # Validates paradigm compliance before allowing the agent to finish.
413
+ # Installed by: paradigm hooks install --claude-code
414
+ #
415
+ # Hook type: Stop
416
+ # Exit 0 = allow, Exit 2 = block with message
417
+ #
418
+ # Checks 1\u201311 are defined in paradigm-common.sh (shared with Cursor hook).
419
+
420
+ # Read JSON from stdin (hook input)
421
+ INPUT=$(cat)
422
+
423
+ # Extract cwd from input (try jq first, fallback to grep)
424
+ if command -v jq >/dev/null 2>&1; then
425
+ CWD=$(echo "$INPUT" | jq -r '.cwd // empty' 2>/dev/null)
426
+ else
427
+ CWD=$(echo "$INPUT" | grep -o '"cwd"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*"cwd"[[:space:]]*:[[:space:]]*"//' | sed 's/"$//')
428
+ fi
429
+
430
+ if [ -z "$CWD" ]; then
431
+ CWD="$(pwd)"
432
+ fi
433
+
434
+ # Not a paradigm project \u2014 pass
435
+ if [ ! -d "$CWD/.paradigm" ]; then
436
+ exit 0
437
+ fi
438
+
439
+ cd "$CWD" || exit 0
440
+
441
+ # Get modified files (uncommitted changes)
442
+ MODIFIED=$(git diff --name-only HEAD 2>/dev/null)
443
+ if [ -z "$MODIFIED" ]; then
444
+ # Clean up pending-review on pass
445
+ rm -f ".paradigm/.pending-review"
446
+ exit 0
447
+ fi
448
+
449
+ # Source shared compliance checks
450
+ SCRIPT_DIR=$(dirname "$0")
451
+ . "$SCRIPT_DIR/paradigm-common.sh"
452
+
300
453
  # --- Final verdict ---
301
454
  if [ "$VIOLATION_COUNT" -gt 0 ]; then
302
455
  echo "" >&2
@@ -585,15 +738,7 @@ var CURSOR_STOP_HOOK = `#!/bin/sh
585
738
  # Hook type: stop
586
739
  # Exit 0 = allow, Exit 2 = block with message
587
740
  #
588
- # Checks:
589
- # 1. Source files modified without .purpose updates (threshold: 2+)
590
- # 2. Modified source directories missing .purpose files entirely
591
- # 3. Route-like patterns added without portal.yaml updates
592
- # 4. Aspect anchor files that no longer exist
593
- # 5. Per-directory .purpose freshness (tracked via .pending-review)
594
- # 6. Aspect coverage advisory
595
- # 7. Lore entry expected for significant sessions (3+ source files)
596
- # 8. Blocking habits not satisfied (from paradigm_habits_check)
741
+ # Checks 1\u201311 are defined in paradigm-common.sh (shared with Claude Code hook).
597
742
 
598
743
  # Read JSON from stdin (hook input)
599
744
  INPUT=$(cat)
@@ -644,247 +789,9 @@ if [ -z "$MODIFIED" ]; then
644
789
  exit 0
645
790
  fi
646
791
 
647
- VIOLATIONS=""
648
- VIOLATION_COUNT=0
649
-
650
- # --- Check 1: Source files modified without .purpose updates ---
651
- SOURCE_COUNT=0
652
- PARADIGM_COUNT=0
653
-
654
- for file in $MODIFIED; do
655
- case "$file" in
656
- .paradigm/*|*.purpose|portal.yaml)
657
- PARADIGM_COUNT=$((PARADIGM_COUNT + 1))
658
- ;;
659
- *.md|*.lock|*.log|.gitignore|.env*|*.json) ;;
660
- *)
661
- SOURCE_COUNT=$((SOURCE_COUNT + 1))
662
- ;;
663
- esac
664
- done
665
-
666
- if [ "$SOURCE_COUNT" -gt 1 ] && [ "$PARADIGM_COUNT" -eq 0 ]; then
667
- VIOLATIONS="$VIOLATIONS
668
- - You modified $SOURCE_COUNT source files but 0 paradigm files (.purpose/portal.yaml).
669
- Update the nearest .purpose file for each modified code area."
670
- VIOLATION_COUNT=$((VIOLATION_COUNT + 1))
671
- fi
672
-
673
- # --- Check 2: Modified source directories missing .purpose files ---
674
- DIRS_WITHOUT_PURPOSE=""
675
-
676
- for file in $MODIFIED; do
677
- case "$file" in
678
- .paradigm/*|*.md|*.lock|*.log|.gitignore|.env*|*.json|*.purpose|portal.yaml) continue ;;
679
- esac
680
-
681
- dir=$(dirname "$file")
682
- # Walk up to find a .purpose file
683
- found_purpose=false
684
- check_dir="$dir"
685
- while [ "$check_dir" != "." ] && [ "$check_dir" != "" ]; do
686
- if [ -f "$check_dir/.purpose" ]; then
687
- found_purpose=true
688
- break
689
- fi
690
- check_dir=$(dirname "$check_dir")
691
- done
692
- # Also check root
693
- if [ "$found_purpose" = false ] && [ -f ".purpose" ]; then
694
- found_purpose=true
695
- fi
696
-
697
- if [ "$found_purpose" = false ]; then
698
- # Deduplicate directory names
699
- case "$DIRS_WITHOUT_PURPOSE" in
700
- *"$dir"*) ;;
701
- *) DIRS_WITHOUT_PURPOSE="$DIRS_WITHOUT_PURPOSE $dir" ;;
702
- esac
703
- fi
704
- done
705
-
706
- if [ -n "$DIRS_WITHOUT_PURPOSE" ]; then
707
- VIOLATIONS="$VIOLATIONS
708
- - These directories have modified source files but no .purpose file anywhere in their path:
709
- $DIRS_WITHOUT_PURPOSE
710
- Create a .purpose file using paradigm_purpose_init + paradigm_purpose_add_component."
711
- VIOLATION_COUNT=$((VIOLATION_COUNT + 1))
712
- fi
713
-
714
- # --- Check 3: Route patterns added without portal.yaml ---
715
- if [ -f "portal.yaml" ] || echo "$MODIFIED" | grep -q "portal.yaml"; then
716
- : # portal.yaml exists or was modified \u2014 OK
717
- else
718
- # Check if any modified files contain route-like patterns
719
- ROUTE_FILES=""
720
- for file in $MODIFIED; do
721
- case "$file" in
722
- *.ts|*.js|*.tsx|*.jsx|*.py|*.rs|*.go)
723
- if [ -f "$file" ]; then
724
- if grep -qE '\\.(get|post|put|patch|delete)\\s*\\(|router\\.|app\\.(get|post|put|delete)|@(Get|Post|Put|Delete)|#\\[actix_web::(get|post)' "$file" 2>/dev/null; then
725
- ROUTE_FILES="$ROUTE_FILES $file"
726
- fi
727
- fi
728
- ;;
729
- esac
730
- done
731
-
732
- if [ -n "$ROUTE_FILES" ]; then
733
- VIOLATIONS="$VIOLATIONS
734
- - Route/endpoint patterns found in modified files but no portal.yaml exists:
735
- $ROUTE_FILES
736
- Create portal.yaml with gate definitions. Use paradigm_gates_for_route for suggestions."
737
- VIOLATION_COUNT=$((VIOLATION_COUNT + 1))
738
- fi
739
- fi
740
-
741
- # --- Check 4: Aspect anchor files that no longer exist ---
742
- for purpose_file in $(find . -name ".purpose" -not -path "*/node_modules/*" -not -path "*/.git/*" 2>/dev/null); do
743
- if grep -q "anchors:" "$purpose_file" 2>/dev/null; then
744
- purpose_dir=$(dirname "$purpose_file")
745
- in_anchors=false
746
- while IFS= read -r line; do
747
- case "$line" in
748
- *"anchors:"*) in_anchors=true; continue ;;
749
- *"- "*)
750
- if [ "$in_anchors" = true ]; then
751
- anchor_path=$(echo "$line" | sed 's/.*- //' | sed 's/:.*//' | tr -d ' ')
752
- if [ -n "$anchor_path" ]; then
753
- # Try relative to .purpose dir first, then project root
754
- if [ ! -f "$purpose_dir/$anchor_path" ] && [ ! -f "./$anchor_path" ]; then
755
- VIOLATIONS="$VIOLATIONS
756
- - Aspect anchor '$anchor_path' in $purpose_file does not exist.
757
- Update the anchor or remove the stale aspect."
758
- VIOLATION_COUNT=$((VIOLATION_COUNT + 1))
759
- fi
760
- fi
761
- fi
762
- ;;
763
- *) in_anchors=false ;;
764
- esac
765
- done < "$purpose_file"
766
- fi
767
- done
768
-
769
- # --- Check 5: Per-directory .purpose freshness ---
770
- PENDING_FILE=".paradigm/.pending-review"
771
- if [ -f "$PENDING_FILE" ]; then
772
- STALE_PURPOSES=""
773
- while IFS= read -r tracked_file; do
774
- [ -z "$tracked_file" ] && continue
775
- # Find covering .purpose for this tracked file
776
- check_dir=$(dirname "$tracked_file")
777
- covering_purpose=""
778
- while [ "$check_dir" != "." ] && [ "$check_dir" != "" ]; do
779
- if [ -f "$check_dir/.purpose" ]; then
780
- covering_purpose="$check_dir/.purpose"
781
- break
782
- fi
783
- check_dir=$(dirname "$check_dir")
784
- done
785
- if [ -z "$covering_purpose" ] && [ -f ".purpose" ]; then
786
- covering_purpose=".purpose"
787
- fi
788
- # Check if covering .purpose was also modified
789
- if [ -n "$covering_purpose" ]; then
790
- if ! echo "$MODIFIED" | grep -qxF "$covering_purpose"; then
791
- # Deduplicate
792
- case "$STALE_PURPOSES" in
793
- *"$covering_purpose"*) ;;
794
- *) STALE_PURPOSES="$STALE_PURPOSES $covering_purpose" ;;
795
- esac
796
- fi
797
- fi
798
- done < "$PENDING_FILE"
799
-
800
- if [ -n "$STALE_PURPOSES" ]; then
801
- VIOLATIONS="$VIOLATIONS
802
- - These .purpose files cover modified source code but were NOT updated:
803
- $STALE_PURPOSES
804
- Update each with: #components, ~aspects (with anchors), !signals, \\$flows, ^gates."
805
- VIOLATION_COUNT=$((VIOLATION_COUNT + 1))
806
- fi
807
- fi
808
-
809
- # --- Check 6: Aspect coverage advisory ---
810
- ADVISORY=""
811
- HAS_ASPECTS=false
812
- for purpose_file in $(find . -name ".purpose" -not -path "*/node_modules/*" -not -path "*/.git/*" 2>/dev/null); do
813
- if grep -qE '^\\s*~' "$purpose_file" 2>/dev/null; then
814
- HAS_ASPECTS=true
815
- break
816
- fi
817
- done
818
-
819
- if [ "$HAS_ASPECTS" = true ] && [ "$SOURCE_COUNT" -gt 0 ]; then
820
- ASPECT_UPDATED=false
821
- for file in $MODIFIED; do
822
- case "$file" in
823
- *.purpose)
824
- if grep -qE '^\\s*~|anchors:|applies-to:' "$file" 2>/dev/null; then
825
- ASPECT_UPDATED=true
826
- break
827
- fi
828
- ;;
829
- esac
830
- done
831
-
832
- if [ "$ASPECT_UPDATED" = false ]; then
833
- ADVISORY=" This project defines ~aspects with code anchors. Check if existing
834
- ~aspects need updated anchors or applies-to patterns."
835
- fi
836
- fi
837
-
838
- # --- Check 7: Lore entry expected for significant sessions ---
839
- if [ "$SOURCE_COUNT" -ge 3 ] && [ -d ".paradigm/lore" ]; then
840
- LORE_RECORDED=false
841
-
842
- # Check git diff first (covers staged/committed lore)
843
- for file in $MODIFIED; do
844
- case "$file" in
845
- .paradigm/lore/entries/*.yaml|.paradigm/lore/entries/*/*.yaml)
846
- LORE_RECORDED=true
847
- break
848
- ;;
849
- esac
850
- done
851
-
852
- # Also check for recent lore on disk (covers MCP-written entries not yet staged)
853
- if [ "$LORE_RECORDED" = false ]; then
854
- TODAY=$(date -u +"%Y-%m-%d")
855
- if [ -d ".paradigm/lore/entries/$TODAY" ]; then
856
- ENTRY_COUNT=$(find ".paradigm/lore/entries/$TODAY" -name "*.yaml" 2>/dev/null | head -1)
857
- if [ -n "$ENTRY_COUNT" ]; then
858
- LORE_RECORDED=true
859
- fi
860
- fi
861
- fi
862
-
863
- if [ "$LORE_RECORDED" = false ]; then
864
- VIOLATIONS="$VIOLATIONS
865
- - You modified $SOURCE_COUNT source files but recorded no lore entry.
866
- Record your session: paradigm_lore_record (MCP) or paradigm lore record (CLI).
867
- Include: type, title, summary, and symbols_touched."
868
- VIOLATION_COUNT=$((VIOLATION_COUNT + 1))
869
- fi
870
- fi
871
-
872
- # --- Auto-evaluate on-stop habits via CLI ---
873
- if command -v paradigm >/dev/null 2>&1; then
874
- paradigm habits check --trigger on-stop --record --json 2>/dev/null || true
875
- elif command -v npx >/dev/null 2>&1; then
876
- npx paradigm habits check --trigger on-stop --record --json 2>/dev/null || true
877
- fi
878
-
879
- # --- Check 8: Blocking habits ---
880
- if [ -f ".paradigm/.habits-blocking" ]; then
881
- HABITS_BLOCKING=$(cat ".paradigm/.habits-blocking")
882
- VIOLATIONS="$VIOLATIONS
883
- - Blocking habit(s) not satisfied:
884
- $HABITS_BLOCKING
885
- Call paradigm_habits_check with trigger=\\"on-stop\\" after fixing the above."
886
- VIOLATION_COUNT=$((VIOLATION_COUNT + 1))
887
- fi
792
+ # Source shared compliance checks
793
+ SCRIPT_DIR=$(dirname "$0")
794
+ . "$SCRIPT_DIR/paradigm-common.sh"
888
795
 
889
796
  # --- Final verdict ---
890
797
  if [ "$VIOLATION_COUNT" -gt 0 ]; then
@@ -1412,7 +1319,7 @@ function cleanupProjectClaudeCodeHooks(rootDir) {
1412
1319
  const removed = [];
1413
1320
  const claudeHooksDir = path.join(rootDir, ".claude", "hooks");
1414
1321
  if (fs.existsSync(claudeHooksDir)) {
1415
- for (const hookName of ["paradigm-stop.sh", "paradigm-precommit.sh", "paradigm-postwrite.sh"]) {
1322
+ for (const hookName of ["paradigm-common.sh", "paradigm-stop.sh", "paradigm-precommit.sh", "paradigm-postwrite.sh"]) {
1416
1323
  const hookPath = path.join(claudeHooksDir, hookName);
1417
1324
  if (fs.existsSync(hookPath)) {
1418
1325
  fs.unlinkSync(hookPath);
@@ -1588,6 +1495,7 @@ async function hooksInstallCommand(options = {}) {
1588
1495
  const scriptsToValidate = [
1589
1496
  { name: "post-commit", content: POST_COMMIT_HOOK },
1590
1497
  { name: "pre-push", content: PRE_PUSH_HOOK },
1498
+ { name: "paradigm-common", content: COMMON_HOOK },
1591
1499
  { name: "claude-code-stop", content: CLAUDE_CODE_STOP_HOOK },
1592
1500
  { name: "claude-code-precommit", content: CLAUDE_CODE_PRECOMMIT_HOOK },
1593
1501
  { name: "claude-code-postwrite", content: CLAUDE_CODE_POSTWRITE_HOOK },
@@ -1711,6 +1619,9 @@ async function installClaudeCodeHooks(rootDir, force) {
1711
1619
  const claudeHooksDir = path.join(rootDir, ".claude", "hooks");
1712
1620
  fs.mkdirSync(claudeHooksDir, { recursive: true });
1713
1621
  const installed = [];
1622
+ const commonPath = path.join(claudeHooksDir, "paradigm-common.sh");
1623
+ fs.writeFileSync(commonPath, COMMON_HOOK, "utf8");
1624
+ fs.chmodSync(commonPath, "755");
1714
1625
  const hookScripts = [
1715
1626
  { name: "paradigm-stop.sh", content: CLAUDE_CODE_STOP_HOOK },
1716
1627
  { name: "paradigm-precommit.sh", content: CLAUDE_CODE_PRECOMMIT_HOOK },
@@ -1793,6 +1704,9 @@ async function installCursorHooks(rootDir, force) {
1793
1704
  const cursorHooksDir = path.join(rootDir, ".cursor", "hooks");
1794
1705
  fs.mkdirSync(cursorHooksDir, { recursive: true });
1795
1706
  const installed = [];
1707
+ const cursorCommonPath = path.join(cursorHooksDir, "paradigm-common.sh");
1708
+ fs.writeFileSync(cursorCommonPath, COMMON_HOOK, "utf8");
1709
+ fs.chmodSync(cursorCommonPath, "755");
1796
1710
  const hookScripts = [
1797
1711
  { name: "paradigm-session-start.sh", content: CURSOR_SESSION_START_HOOK },
1798
1712
  { name: "paradigm-stop.sh", content: CURSOR_STOP_HOOK },
@@ -2037,7 +1951,7 @@ async function hooksStatusCommand() {
2037
1951
  console.log(chalk.green(" Hooks are managed by the plugin \u2014 auto-updates with each session."));
2038
1952
  const claudeHooksDir = path.join(rootDir, ".claude", "hooks");
2039
1953
  const staleHooks = [];
2040
- for (const hookName of ["paradigm-stop.sh", "paradigm-precommit.sh", "paradigm-postwrite.sh"]) {
1954
+ for (const hookName of ["paradigm-common.sh", "paradigm-stop.sh", "paradigm-precommit.sh", "paradigm-postwrite.sh"]) {
2041
1955
  if (fs.existsSync(path.join(claudeHooksDir, hookName))) {
2042
1956
  staleHooks.push(hookName);
2043
1957
  }