@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.
- package/dist/{accept-orchestration-DIGPJVUR.js → accept-orchestration-Z35I5AYN.js} +5 -5
- package/dist/{assessment-loader-T4GPBHLB.js → assessment-loader-C5EOUM47.js} +0 -1
- package/dist/{chunk-Y4XZWCHK.js → chunk-24AAVLME.js} +8 -8
- package/dist/{chunk-4N6AYEEA.js → chunk-3TWXFFZ3.js} +1 -1
- package/dist/{chunk-5S5CF3ER.js → chunk-4ZO3ZOPM.js} +19 -2141
- package/dist/{chunk-6RNYVBSG.js → chunk-CP6IZGUN.js} +4 -4
- package/dist/{chunk-M2XMTJHQ.js → chunk-DS5QY37M.js} +201 -287
- package/dist/chunk-F6EJKLF4.js +4971 -0
- package/dist/chunk-MW5DMGBB.js +255 -0
- package/dist/{chunk-KFHK6EBI.js → chunk-OSYMVGWX.js} +59 -3
- package/dist/{chunk-GY5KO3YZ.js → chunk-RDPXBMHK.js} +1 -1
- package/dist/{chunk-ADOBV4PH.js → chunk-UVI3OH3G.js} +6 -2127
- package/dist/{diff-J6C5IHPV.js → diff-PZAYCIAE.js} +5 -5
- package/dist/{dist-OLFOTUHS.js → dist-6SX5ZKKF.js} +2 -2
- package/dist/{dist-OMY7U6NR.js → dist-YB7T54QE.js} +1 -2
- package/dist/{doctor-TQYRF7KK.js → doctor-3YQ55536.js} +1 -1
- package/dist/drift-FH2UY64B.js +251 -0
- package/dist/{flow-7JUH6D4H.js → flow-MCKPJGRJ.js} +1 -1
- package/dist/{habits-ZJBAL4HD.js → habits-NC2TRMRV.js} +2 -2
- package/dist/{hooks-DLZEYHI3.js → hooks-JXYHVGIN.js} +1 -1
- package/dist/index.js +77 -51
- package/dist/mcp.js +5502 -9984
- package/dist/{orchestrate-FAV64G2R.js → orchestrate-BGRFBGBH.js} +5 -5
- package/dist/{plugin-update-checker-TWBWUSAG.js → plugin-update-checker-S3W4BUJO.js} +0 -1
- package/dist/portal-check-2HI4FFD6.js +42 -0
- package/dist/portal-compliance-KQCTAQTJ.js +18 -0
- package/dist/{providers-NQ67LO2Z.js → providers-IONB4YRJ.js} +1 -1
- package/dist/reindex-ZM6J53UP.js +11 -0
- package/dist/{sentinel-KDIGZWKT.js → sentinel-BGCISNIK.js} +1 -1
- package/dist/{server-NN7WDAZJ.js → server-3K3TTJH3.js} +1 -1
- package/dist/{shift-KJWSJLWN.js → shift-6I6N6RNK.js} +36 -8
- package/dist/{spawn-EO7B2UM3.js → spawn-WGFJ5RQZ.js} +5 -5
- package/dist/{task-loader-GUX4KS6N.js → task-loader-7M2FCBX6.js} +0 -1
- package/dist/{team-6CCNANKE.js → team-AFOKQ7YQ.js} +6 -6
- package/dist/{triage-B5W6GZLT.js → triage-MKKIWBSW.js} +2 -2
- package/dist/workspace-VBTW7OYL.js +271 -0
- package/package.json +2 -1
- package/dist/chunk-HPC3JAUP.js +0 -42
- /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-
|
|
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
|
|
12
|
-
#
|
|
13
|
-
#
|
|
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
|
-
#
|
|
17
|
-
#
|
|
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
|
-
#
|
|
30
|
-
|
|
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
|
-
|
|
648
|
-
|
|
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
|
}
|