@a-company/paradigm 3.1.5 → 3.5.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-CWZNCGZX.js → accept-orchestration-DIGPJVUR.js} +6 -5
- package/dist/{aggregate-W7Q6VIM2.js → aggregate-V4KPR3RW.js} +2 -2
- package/dist/{beacon-B47XSTL7.js → beacon-XRXL5KZB.js} +2 -2
- package/dist/{chunk-4LGLU2LO.js → chunk-2E2RTBSM.js} +533 -182
- package/dist/{chunk-YCLN7WXV.js → chunk-2QNZ6PVD.js} +219 -35
- package/dist/{chunk-UM54F7G5.js → chunk-4N6AYEEA.js} +1 -1
- package/dist/{chunk-MVXJVRFI.js → chunk-5TUAVVIG.js} +65 -1
- package/dist/{chunk-5C4SGQKH.js → chunk-6P4IFIK2.js} +4 -2
- package/dist/{chunk-WS5KM7OL.js → chunk-6RNYVBSG.js} +1 -1
- package/dist/{chunk-N6PJAPDE.js → chunk-AK5M6KJB.js} +18 -0
- package/dist/{chunk-VZ7CXFRZ.js → chunk-CRICL4FQ.js} +1004 -17
- package/dist/{chunk-MC7XC7XQ.js → chunk-GZDFVP2N.js} +20 -13
- package/dist/chunk-HPC3JAUP.js +42 -0
- package/dist/chunk-IRVA7NKV.js +657 -0
- package/dist/{chunk-ZPN7MXRA.js → chunk-KFHK6EBI.js} +184 -1
- package/dist/{chunk-UUZ2DMG5.js → chunk-KWDTBXP2.js} +1 -1
- package/dist/{chunk-DRUDZKIT.js → chunk-M2XMTJHQ.js} +693 -70
- package/dist/{chunk-PW2EXJQT.js → chunk-MRENOFTR.js} +24 -1
- package/dist/{chunk-QS36NGWV.js → chunk-QHJGB5TV.js} +1 -1
- package/dist/chunk-UI3XXVJ6.js +449 -0
- package/dist/{chunk-AD2LSCHB.js → chunk-Y4XZWCHK.js} +40 -74
- package/dist/{constellation-K3CIQCHI.js → constellation-GNK5DIMH.js} +2 -2
- package/dist/{cost-AEK6R7HK.js → cost-AGO5N7DD.js} +1 -1
- package/dist/{cursorrules-KI5QWHIX.js → cursorrules-LQFA7M62.js} +2 -2
- package/dist/{delete-W67IVTLJ.js → delete-3YXAJ5AA.js} +12 -1
- package/dist/{diff-AJJ5H6HV.js → diff-J6C5IHPV.js} +6 -5
- package/dist/{dist-2F7NO4H4-KSL6SJIO.js → dist-AG5JNIZU-XSEZ2LLK.js} +28 -3
- package/dist/dist-JOHRYQUA.js +7294 -0
- package/dist/{dist-NHJQVVUW.js → dist-Q6SAZI7X.js} +2 -2
- package/dist/{dist-GPQ4LAY3.js → dist-YP2CO4TG.js} +24 -6
- package/dist/{doctor-JBIV5PMN.js → doctor-TQYRF7KK.js} +2 -2
- package/dist/{edit-Y7XPYSMK.js → edit-EOMPXOG5.js} +1 -1
- package/dist/flow-7JUH6D4H.js +185 -0
- package/dist/global-AXILUM5X.js +136 -0
- package/dist/{habits-FA65W77Y.js → habits-CHP4EW5H.js} +234 -5
- package/dist/{hooks-JKWO44WH.js → hooks-DLZEYHI3.js} +1 -1
- package/dist/index.js +125 -100
- package/dist/{lint-HXKTWRNO.js → lint-N4LMMEXH.js} +141 -1
- package/dist/{list-R3QWW4SC.js → list-JKBJ7ESH.js} +1 -1
- package/dist/mcp.js +9273 -6515
- package/dist/{orchestrate-4ZH5GUQH.js → orchestrate-FAV64G2R.js} +6 -5
- package/dist/{probe-OYCP4JYG.js → probe-X3J2JX62.js} +18 -3
- package/dist/{promote-E6NBZ3BK.js → promote-HZH5E5CO.js} +1 -1
- package/dist/{providers-4PGPZEWP.js → providers-NQ67LO2Z.js} +1 -1
- package/dist/{record-OHQNWOUP.js → record-EECZ3E4I.js} +1 -1
- package/dist/{remember-6VZ74B7E.js → remember-3KJZGDUG.js} +1 -1
- package/dist/{review-RUHX25A5.js → review-BF26ILZB.js} +1 -1
- package/dist/{ripple-SBQOSTZD.js → ripple-JIUAMBLA.js} +2 -2
- package/dist/sentinel-ZTL224IG.js +63 -0
- package/dist/{server-MV4HNFVF.js → server-MZBYDXJY.js} +4193 -9
- package/dist/{setup-DF4F3ICN.js → setup-363IB6MO.js} +1 -1
- package/dist/{setup-JHBPZAG7.js → setup-UKJ3VGHI.js} +4 -4
- package/dist/{shift-2LQFQP4P.js → shift-KDVYB6CR.js} +16 -13
- package/dist/{show-WTOJXUTN.js → show-SAMTXEHG.js} +1 -1
- package/dist/{snapshot-GTVPRYZG.js → snapshot-KCMONZAO.js} +2 -2
- package/dist/{spawn-BJRQA2NR.js → spawn-EO7B2UM3.js} +2 -2
- package/dist/{summary-5SBFO7QK.js → summary-E2PU4UN2.js} +3 -3
- package/dist/{switch-6EANJ7O6.js → switch-CC2KACXO.js} +1 -1
- package/dist/{sync-5KSTPJ4B.js → sync-5VJPZQNX.js} +2 -2
- package/dist/sync-llms-7QDA3ZWC.js +166 -0
- package/dist/{team-NWP2KJAB.js → team-6CCNANKE.js} +7 -6
- package/dist/{test-MA5TWJQV.js → test-DK2RWLTK.js} +91 -8
- package/dist/{thread-JCJVRUQR.js → thread-RNSLADXN.js} +18 -2
- package/dist/{timeline-P7BARFLI.js → timeline-TJDVVVA3.js} +1 -1
- package/dist/{triage-TBIWJA6R.js → triage-PXMU3RWV.js} +2 -2
- package/dist/university-content/courses/para-101.json +2 -1
- package/dist/university-content/courses/para-201.json +102 -3
- package/dist/university-content/courses/para-301.json +14 -11
- package/dist/university-content/courses/para-401.json +57 -3
- package/dist/university-content/courses/para-501.json +204 -6
- package/dist/university-content/plsat/v3.0.json +808 -3
- package/dist/university-content/reference.json +270 -0
- package/dist/{upgrade-TIYFQYPO.js → upgrade-RBSE4M6I.js} +1 -1
- package/dist/{validate-QEEY6KFS.js → validate-2LTHHORX.js} +1 -1
- package/dist/{watch-4LT4O6K7.js → watch-NBPOMOEX.js} +76 -0
- package/dist/{watch-2XEYUH43.js → watch-PAEH6MOG.js} +1 -1
- package/package.json +1 -1
- package/dist/chunk-GWM2WRXL.js +0 -1095
- package/dist/sentinel-WB7GIK4V.js +0 -43
- /package/dist/{chunk-TAP5N3HH.js → chunk-CCG6KYBT.js} +0 -0
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import * as fs from "fs";
|
|
5
5
|
import * as path from "path";
|
|
6
6
|
import * as os from "os";
|
|
7
|
+
import { execSync } from "child_process";
|
|
7
8
|
import chalk from "chalk";
|
|
8
9
|
|
|
9
10
|
// src/commands/hooks/generated-hooks.ts
|
|
@@ -248,6 +249,8 @@ fi
|
|
|
248
249
|
# --- Check 7: Lore entry expected for significant sessions ---
|
|
249
250
|
if [ "$SOURCE_COUNT" -ge 3 ] && [ -d ".paradigm/lore" ]; then
|
|
250
251
|
LORE_RECORDED=false
|
|
252
|
+
|
|
253
|
+
# Check git diff first (covers staged/committed lore)
|
|
251
254
|
for file in $MODIFIED; do
|
|
252
255
|
case "$file" in
|
|
253
256
|
.paradigm/lore/entries/*.yaml|.paradigm/lore/entries/*/*.yaml)
|
|
@@ -257,6 +260,17 @@ if [ "$SOURCE_COUNT" -ge 3 ] && [ -d ".paradigm/lore" ]; then
|
|
|
257
260
|
esac
|
|
258
261
|
done
|
|
259
262
|
|
|
263
|
+
# Also check for recent lore on disk (covers MCP-written entries not yet staged)
|
|
264
|
+
if [ "$LORE_RECORDED" = false ]; then
|
|
265
|
+
TODAY=$(date -u +"%Y-%m-%d")
|
|
266
|
+
if [ -d ".paradigm/lore/entries/$TODAY" ]; then
|
|
267
|
+
ENTRY_COUNT=$(find ".paradigm/lore/entries/$TODAY" -name "*.yaml" 2>/dev/null | head -1)
|
|
268
|
+
if [ -n "$ENTRY_COUNT" ]; then
|
|
269
|
+
LORE_RECORDED=true
|
|
270
|
+
fi
|
|
271
|
+
fi
|
|
272
|
+
fi
|
|
273
|
+
|
|
260
274
|
if [ "$LORE_RECORDED" = false ]; then
|
|
261
275
|
VIOLATIONS="$VIOLATIONS
|
|
262
276
|
- You modified $SOURCE_COUNT source files but recorded no lore entry.
|
|
@@ -460,6 +474,107 @@ for f in .paradigm/scan-index.json .paradigm/navigator.yaml .paradigm/flow-index
|
|
|
460
474
|
done
|
|
461
475
|
|
|
462
476
|
# Never block \u2014 exit 0
|
|
477
|
+
exit 0
|
|
478
|
+
`;
|
|
479
|
+
var CURSOR_SESSION_START_HOOK = `#!/bin/sh
|
|
480
|
+
# Paradigm Cursor Session Start Hook
|
|
481
|
+
# Fires before the agent does anything \u2014 injects additional_context
|
|
482
|
+
# that acts as a deterministic system prompt (not subject to context compaction).
|
|
483
|
+
# Installed by: paradigm hooks install --cursor
|
|
484
|
+
#
|
|
485
|
+
# Hook type: sessionStart
|
|
486
|
+
# Output: JSON with additional_context + continue: true
|
|
487
|
+
# Exit 0 always (never blocks)
|
|
488
|
+
|
|
489
|
+
# Read JSON from stdin (hook input)
|
|
490
|
+
INPUT=$(cat)
|
|
491
|
+
|
|
492
|
+
# Extract workspace root from Cursor's input
|
|
493
|
+
if command -v jq >/dev/null 2>&1; then
|
|
494
|
+
CWD=$(echo "$INPUT" | jq -r '.workspace_roots[0] // empty' 2>/dev/null)
|
|
495
|
+
else
|
|
496
|
+
CWD=$(echo "$INPUT" | grep -o '"workspace_roots"[[:space:]]*:[[:space:]]*\\["[^"]*"' | head -1 | sed 's/.*\\["//' | sed 's/"$//')
|
|
497
|
+
fi
|
|
498
|
+
|
|
499
|
+
if [ -z "$CWD" ]; then
|
|
500
|
+
CWD="$(pwd)"
|
|
501
|
+
fi
|
|
502
|
+
|
|
503
|
+
# Not a paradigm project \u2014 pass without injection
|
|
504
|
+
if [ ! -d "$CWD/.paradigm" ]; then
|
|
505
|
+
echo '{"continue":true}'
|
|
506
|
+
exit 0
|
|
507
|
+
fi
|
|
508
|
+
|
|
509
|
+
# Build the additional_context payload
|
|
510
|
+
# This is injected as a system-level context that survives context compaction.
|
|
511
|
+
|
|
512
|
+
# Detect project characteristics for task-size guidance
|
|
513
|
+
HAS_PORTAL="false"
|
|
514
|
+
if [ -f "$CWD/portal.yaml" ]; then
|
|
515
|
+
HAS_PORTAL="true"
|
|
516
|
+
fi
|
|
517
|
+
|
|
518
|
+
HAS_LORE="false"
|
|
519
|
+
if [ -d "$CWD/.paradigm/lore" ]; then
|
|
520
|
+
HAS_LORE="true"
|
|
521
|
+
fi
|
|
522
|
+
|
|
523
|
+
HAS_FLOWS="false"
|
|
524
|
+
if [ -f "$CWD/.paradigm/flow-index.json" ]; then
|
|
525
|
+
HAS_FLOWS="true"
|
|
526
|
+
fi
|
|
527
|
+
|
|
528
|
+
# Build context string (escaped for JSON)
|
|
529
|
+
CONTEXT="MANDATORY PARADIGM PROTOCOL \u2014 These rules are NON-NEGOTIABLE:\\\\n\\\\n"
|
|
530
|
+
CONTEXT="\${CONTEXT}Rule 1: SESSION BOOKENDS\\\\n"
|
|
531
|
+
CONTEXT="\${CONTEXT} - FIRST action: Call paradigm_session_recover() to load previous session context\\\\n"
|
|
532
|
+
CONTEXT="\${CONTEXT} - LAST action before finishing: Call paradigm_pm_postflight() with filesModified and symbolsTouched\\\\n\\\\n"
|
|
533
|
+
|
|
534
|
+
CONTEXT="\${CONTEXT}Rule 2: .PURPOSE UPDATES\\\\n"
|
|
535
|
+
CONTEXT="\${CONTEXT} - Every source file you modify MUST have a covering .purpose file\\\\n"
|
|
536
|
+
CONTEXT="\${CONTEXT} - Update the nearest .purpose file with: #components, ~aspects (with anchors), !signals\\\\n"
|
|
537
|
+
CONTEXT="\${CONTEXT} - The STOP HOOK WILL BLOCK you if .purpose files are not updated\\\\n\\\\n"
|
|
538
|
+
|
|
539
|
+
CONTEXT="\${CONTEXT}Rule 3: RIPPLE BEFORE MODIFY\\\\n"
|
|
540
|
+
CONTEXT="\${CONTEXT} - Before modifying any existing symbol, call paradigm_ripple({ symbol: \\\\\\"#symbol-name\\\\\\" })\\\\n"
|
|
541
|
+
CONTEXT="\${CONTEXT} - This shows the blast radius \u2014 what else will break if you change it\\\\n\\\\n"
|
|
542
|
+
|
|
543
|
+
CONTEXT="\${CONTEXT}ESSENTIAL MCP TOOLS:\\\\n"
|
|
544
|
+
CONTEXT="\${CONTEXT} paradigm_session_recover() \u2014 Load previous session (call FIRST)\\\\n"
|
|
545
|
+
CONTEXT="\${CONTEXT} paradigm_ripple({ symbol }) \u2014 Check impact before modifying\\\\n"
|
|
546
|
+
CONTEXT="\${CONTEXT} paradigm_pm_postflight({ filesModified, symbolsTouched }) \u2014 Compliance check (call LAST)\\\\n"
|
|
547
|
+
CONTEXT="\${CONTEXT} paradigm_purpose_add_component({ path, name, description }) \u2014 Register code units\\\\n"
|
|
548
|
+
CONTEXT="\${CONTEXT} paradigm_reindex() \u2014 Rebuild indexes after .purpose changes\\\\n"
|
|
549
|
+
CONTEXT="\${CONTEXT} paradigm_lore_record({ type, title, summary, symbols_touched }) \u2014 Record session\\\\n"
|
|
550
|
+
|
|
551
|
+
if [ "$HAS_PORTAL" = "true" ]; then
|
|
552
|
+
CONTEXT="\${CONTEXT} paradigm_gates_for_route({ method, path }) \u2014 Get auth gate suggestions for endpoints\\\\n"
|
|
553
|
+
CONTEXT="\${CONTEXT} paradigm_portal_add_route({ method, path, gates }) \u2014 Register route with gates\\\\n"
|
|
554
|
+
fi
|
|
555
|
+
|
|
556
|
+
CONTEXT="\${CONTEXT}\\\\nTASK-SIZE TIERS:\\\\n"
|
|
557
|
+
CONTEXT="\${CONTEXT} 1 file: Session bookends only (recover + postflight)\\\\n"
|
|
558
|
+
CONTEXT="\${CONTEXT} 2-3 files: + ripple before modify + update .purpose files\\\\n"
|
|
559
|
+
CONTEXT="\${CONTEXT} 3+ files: + full workflow (ripple, .purpose, lore entry"
|
|
560
|
+
|
|
561
|
+
if [ "$HAS_PORTAL" = "true" ]; then
|
|
562
|
+
CONTEXT="\${CONTEXT}, portal.yaml for routes"
|
|
563
|
+
fi
|
|
564
|
+
|
|
565
|
+
if [ "$HAS_FLOWS" = "true" ]; then
|
|
566
|
+
CONTEXT="\${CONTEXT}, flow validation"
|
|
567
|
+
fi
|
|
568
|
+
|
|
569
|
+
CONTEXT="\${CONTEXT})\\\\n"
|
|
570
|
+
|
|
571
|
+
if [ "$HAS_LORE" = "true" ]; then
|
|
572
|
+
CONTEXT="\${CONTEXT}\\\\nLORE: This project tracks session history. Record a lore entry when modifying 3+ source files.\\\\n"
|
|
573
|
+
fi
|
|
574
|
+
|
|
575
|
+
# Output JSON to stdout
|
|
576
|
+
printf '{"additional_context":"%s","continue":true}\\n' "$CONTEXT"
|
|
577
|
+
|
|
463
578
|
exit 0
|
|
464
579
|
`;
|
|
465
580
|
var CURSOR_STOP_HOOK = `#!/bin/sh
|
|
@@ -501,6 +616,26 @@ fi
|
|
|
501
616
|
|
|
502
617
|
cd "$CWD" || exit 0
|
|
503
618
|
|
|
619
|
+
# --- Loop guard: prevent infinite retry loops ---
|
|
620
|
+
# Cursor's stop hook with loop_limit fires repeatedly. Cap retries at 3.
|
|
621
|
+
LOOP_GUARD_FILE=".paradigm/.stop-hook-active"
|
|
622
|
+
if [ -f "$LOOP_GUARD_FILE" ]; then
|
|
623
|
+
RETRY_COUNT=$(cat "$LOOP_GUARD_FILE" 2>/dev/null | tr -d '[:space:]')
|
|
624
|
+
RETRY_COUNT=\${RETRY_COUNT:-0}
|
|
625
|
+
if [ "$RETRY_COUNT" -ge 3 ]; then
|
|
626
|
+
# Max retries reached \u2014 allow session to end to avoid infinite loop
|
|
627
|
+
echo "[paradigm] Stop hook: max retries (3) reached. Allowing session to end." >&2
|
|
628
|
+
rm -f "$LOOP_GUARD_FILE"
|
|
629
|
+
rm -f ".paradigm/.pending-review"
|
|
630
|
+
rm -f ".paradigm/.habits-blocking"
|
|
631
|
+
exit 0
|
|
632
|
+
fi
|
|
633
|
+
RETRY_COUNT=$((RETRY_COUNT + 1))
|
|
634
|
+
echo "$RETRY_COUNT" > "$LOOP_GUARD_FILE"
|
|
635
|
+
else
|
|
636
|
+
echo "1" > "$LOOP_GUARD_FILE"
|
|
637
|
+
fi
|
|
638
|
+
|
|
504
639
|
# Get modified files (uncommitted changes)
|
|
505
640
|
MODIFIED=$(git diff --name-only HEAD 2>/dev/null)
|
|
506
641
|
if [ -z "$MODIFIED" ]; then
|
|
@@ -703,6 +838,8 @@ fi
|
|
|
703
838
|
# --- Check 7: Lore entry expected for significant sessions ---
|
|
704
839
|
if [ "$SOURCE_COUNT" -ge 3 ] && [ -d ".paradigm/lore" ]; then
|
|
705
840
|
LORE_RECORDED=false
|
|
841
|
+
|
|
842
|
+
# Check git diff first (covers staged/committed lore)
|
|
706
843
|
for file in $MODIFIED; do
|
|
707
844
|
case "$file" in
|
|
708
845
|
.paradigm/lore/entries/*.yaml|.paradigm/lore/entries/*/*.yaml)
|
|
@@ -712,6 +849,17 @@ if [ "$SOURCE_COUNT" -ge 3 ] && [ -d ".paradigm/lore" ]; then
|
|
|
712
849
|
esac
|
|
713
850
|
done
|
|
714
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
|
+
|
|
715
863
|
if [ "$LORE_RECORDED" = false ]; then
|
|
716
864
|
VIOLATIONS="$VIOLATIONS
|
|
717
865
|
- You modified $SOURCE_COUNT source files but recorded no lore entry.
|
|
@@ -756,6 +904,13 @@ if [ "$VIOLATION_COUNT" -gt 0 ]; then
|
|
|
756
904
|
echo " 4. paradigm_reindex \u2014 rebuild indexes after updates" >&2
|
|
757
905
|
echo " 5. paradigm_lore_record \u2014 record session lore entry" >&2
|
|
758
906
|
echo " 6. paradigm_habits_check \u2014 evaluate habit compliance" >&2
|
|
907
|
+
|
|
908
|
+
# Output followup_message JSON to stdout for Cursor's compliance loop.
|
|
909
|
+
# Cursor auto-submits this as the next user message, creating a retry loop.
|
|
910
|
+
# Escape violations for JSON embedding (newlines \u2192 \\n, quotes \u2192 \\", backslash \u2192 \\\\)
|
|
911
|
+
ESCAPED_VIOLATIONS=$(printf '%s' "$VIOLATIONS" | sed 's/\\\\/\\\\\\\\/g' | sed 's/"/\\\\"/g' | sed ':a;N;$!ba;s/\\n/\\\\n/g')
|
|
912
|
+
printf '{"followup_message":"Paradigm compliance check found %d violation(s). Fix these:\\\\n%s\\\\nThen try finishing again."}\\n' "$VIOLATION_COUNT" "$ESCAPED_VIOLATIONS"
|
|
913
|
+
|
|
759
914
|
exit 2
|
|
760
915
|
fi
|
|
761
916
|
|
|
@@ -766,19 +921,25 @@ if [ -n "$ADVISORY" ]; then
|
|
|
766
921
|
echo "$ADVISORY" >&2
|
|
767
922
|
fi
|
|
768
923
|
|
|
769
|
-
# Clean up pending-review on pass
|
|
924
|
+
# Clean up pending-review and loop guard on pass
|
|
770
925
|
rm -f ".paradigm/.pending-review"
|
|
771
926
|
rm -f ".paradigm/.habits-blocking"
|
|
927
|
+
rm -f ".paradigm/.stop-hook-active"
|
|
772
928
|
|
|
773
929
|
exit 0
|
|
774
930
|
`;
|
|
775
931
|
var CURSOR_POSTWRITE_HOOK = `#!/bin/sh
|
|
776
|
-
# Paradigm Cursor PostWrite Hook (v2)
|
|
777
|
-
# Fires after file edits.
|
|
778
|
-
# Tracks modified source files in .paradigm/.pending-review
|
|
779
|
-
# and outputs compliance reminders.
|
|
932
|
+
# Paradigm Cursor PostWrite Hook (v2) \u2014 LEGACY
|
|
933
|
+
# Fires after file edits via Cursor's afterFileEdit hook type.
|
|
780
934
|
# Installed by: paradigm hooks install --cursor
|
|
781
935
|
#
|
|
936
|
+
# IMPORTANT: Cursor ignores all output (stdout + stderr) from afterFileEdit hooks.
|
|
937
|
+
# This hook's advisory messages are INVISIBLE to the agent. The postToolUse hook
|
|
938
|
+
# (cursor-posttooluse.sh) is now the primary advisory mechanism.
|
|
939
|
+
#
|
|
940
|
+
# This hook is kept for backward compatibility and background file tracking only.
|
|
941
|
+
# Both preToolUse and stop hooks depend on the .pending-review file this writes.
|
|
942
|
+
#
|
|
782
943
|
# Hook type: afterFileEdit
|
|
783
944
|
# Exit 0 always (never blocks \u2014 advisory only)
|
|
784
945
|
|
|
@@ -856,18 +1017,8 @@ if [ -z "$found_purpose" ] && [ -f ".purpose" ]; then
|
|
|
856
1017
|
found_purpose=".purpose"
|
|
857
1018
|
fi
|
|
858
1019
|
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
echo "" >&2
|
|
862
|
-
echo "[paradigm] No .purpose file covers $file_dir/" >&2
|
|
863
|
-
echo " Create one: paradigm_purpose_init + paradigm_purpose_add_component" >&2
|
|
864
|
-
echo " $PENDING_COUNT file(s) pending review. The stop hook WILL BLOCK." >&2
|
|
865
|
-
elif [ "$PENDING_COUNT" -gt 0 ] && [ "$((PENDING_COUNT % 3))" -eq 0 ]; then
|
|
866
|
-
echo "" >&2
|
|
867
|
-
echo "[paradigm] $PENDING_COUNT source file(s) modified. Update $found_purpose:" >&2
|
|
868
|
-
echo " -> #components, ~aspects (with anchors), !signals, \\$flows, ^gates" >&2
|
|
869
|
-
echo " The stop hook WILL BLOCK if .purpose files aren't updated." >&2
|
|
870
|
-
fi
|
|
1020
|
+
# NOTE: No stderr output here \u2014 Cursor ignores afterFileEdit output.
|
|
1021
|
+
# Advisory messages are handled by cursor-posttooluse.sh (postToolUse hook).
|
|
871
1022
|
|
|
872
1023
|
exit 0
|
|
873
1024
|
`;
|
|
@@ -915,6 +1066,263 @@ for f in .paradigm/scan-index.json .paradigm/navigator.yaml .paradigm/flow-index
|
|
|
915
1066
|
done
|
|
916
1067
|
|
|
917
1068
|
# Never block \u2014 exit 0
|
|
1069
|
+
exit 0
|
|
1070
|
+
`;
|
|
1071
|
+
var CURSOR_PRETOOLUSE_HOOK = `#!/bin/sh
|
|
1072
|
+
# Paradigm Cursor PreToolUse Hook \u2014 Graduated Blocking
|
|
1073
|
+
# Fires BEFORE the agent calls Edit or Write.
|
|
1074
|
+
# Uses graduated enforcement based on uncovered source edits.
|
|
1075
|
+
# Installed by: paradigm hooks install --cursor
|
|
1076
|
+
#
|
|
1077
|
+
# Hook type: preToolUse
|
|
1078
|
+
# Matcher: Edit|Write
|
|
1079
|
+
# Exit 0 = allow, Exit 2 = block with stderr message
|
|
1080
|
+
#
|
|
1081
|
+
# Graduated enforcement:
|
|
1082
|
+
# 1-2 uncovered edits \u2192 silent pass (exit 0)
|
|
1083
|
+
# 3-4 uncovered edits \u2192 warn via stderr (exit 0)
|
|
1084
|
+
# 5+ uncovered edits \u2192 BLOCK (exit 2 + stderr)
|
|
1085
|
+
|
|
1086
|
+
# Read JSON from stdin (hook input)
|
|
1087
|
+
INPUT=$(cat)
|
|
1088
|
+
|
|
1089
|
+
# Extract tool_name and file_path from preToolUse input
|
|
1090
|
+
if command -v jq >/dev/null 2>&1; then
|
|
1091
|
+
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // empty' 2>/dev/null)
|
|
1092
|
+
FILE_PATH=$(echo "$INPUT" | jq -r '.file_path // .input.file_path // empty' 2>/dev/null)
|
|
1093
|
+
else
|
|
1094
|
+
TOOL_NAME=$(echo "$INPUT" | grep -o '"tool_name"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"tool_name"[[:space:]]*:[[:space:]]*"//' | sed 's/"$//')
|
|
1095
|
+
FILE_PATH=$(echo "$INPUT" | grep -o '"file_path"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"file_path"[[:space:]]*:[[:space:]]*"//' | sed 's/"$//')
|
|
1096
|
+
fi
|
|
1097
|
+
|
|
1098
|
+
# Must have a file path to check
|
|
1099
|
+
if [ -z "$FILE_PATH" ]; then
|
|
1100
|
+
exit 0
|
|
1101
|
+
fi
|
|
1102
|
+
|
|
1103
|
+
# Extract workspace root
|
|
1104
|
+
if command -v jq >/dev/null 2>&1; then
|
|
1105
|
+
CWD=$(echo "$INPUT" | jq -r '.workspace_roots[0] // empty' 2>/dev/null)
|
|
1106
|
+
else
|
|
1107
|
+
CWD=$(echo "$INPUT" | grep -o '"workspace_roots"[[:space:]]*:[[:space:]]*\\["[^"]*"' | head -1 | sed 's/.*\\["//' | sed 's/"$//')
|
|
1108
|
+
fi
|
|
1109
|
+
|
|
1110
|
+
if [ -z "$CWD" ]; then
|
|
1111
|
+
CWD="$(pwd)"
|
|
1112
|
+
fi
|
|
1113
|
+
|
|
1114
|
+
# Not a paradigm project \u2014 pass
|
|
1115
|
+
if [ ! -d "$CWD/.paradigm" ]; then
|
|
1116
|
+
exit 0
|
|
1117
|
+
fi
|
|
1118
|
+
|
|
1119
|
+
cd "$CWD" || exit 0
|
|
1120
|
+
|
|
1121
|
+
# Convert to relative path
|
|
1122
|
+
REL_PATH="$FILE_PATH"
|
|
1123
|
+
case "$FILE_PATH" in
|
|
1124
|
+
"$CWD"/*) REL_PATH=$(echo "$FILE_PATH" | sed "s|^$CWD/||") ;;
|
|
1125
|
+
esac
|
|
1126
|
+
|
|
1127
|
+
# If still absolute, file is outside project \u2014 skip
|
|
1128
|
+
case "$REL_PATH" in
|
|
1129
|
+
/*) exit 0 ;;
|
|
1130
|
+
esac
|
|
1131
|
+
|
|
1132
|
+
# Skip non-source files (paradigm metadata, docs, config)
|
|
1133
|
+
case "$REL_PATH" in
|
|
1134
|
+
*.purpose|portal.yaml|*.md|*.lock|*.log|*.json|*.yaml|*.yml|.gitignore|.env*) exit 0 ;;
|
|
1135
|
+
esac
|
|
1136
|
+
|
|
1137
|
+
# Skip .paradigm, .claude, and .cursor directories
|
|
1138
|
+
case "$REL_PATH" in
|
|
1139
|
+
.paradigm/*|.claude/*|.cursor/*) exit 0 ;;
|
|
1140
|
+
esac
|
|
1141
|
+
|
|
1142
|
+
# Check if target file has a covering .purpose file
|
|
1143
|
+
dir=$(dirname "$REL_PATH")
|
|
1144
|
+
has_purpose=false
|
|
1145
|
+
|
|
1146
|
+
while [ "$dir" != "." ] && [ "$dir" != "/" ] && [ "$dir" != "" ]; do
|
|
1147
|
+
if [ -f "$dir/.purpose" ]; then
|
|
1148
|
+
has_purpose=true
|
|
1149
|
+
break
|
|
1150
|
+
fi
|
|
1151
|
+
dir=$(dirname "$dir")
|
|
1152
|
+
done
|
|
1153
|
+
|
|
1154
|
+
# Check root .purpose
|
|
1155
|
+
if [ "$has_purpose" = false ] && [ -f ".purpose" ]; then
|
|
1156
|
+
has_purpose=true
|
|
1157
|
+
fi
|
|
1158
|
+
|
|
1159
|
+
# If this file already has .purpose coverage, always allow
|
|
1160
|
+
if [ "$has_purpose" = true ]; then
|
|
1161
|
+
exit 0
|
|
1162
|
+
fi
|
|
1163
|
+
|
|
1164
|
+
# Count uncovered source edits from .pending-review
|
|
1165
|
+
PENDING_FILE=".paradigm/.pending-review"
|
|
1166
|
+
UNCOVERED_COUNT=0
|
|
1167
|
+
|
|
1168
|
+
if [ -f "$PENDING_FILE" ]; then
|
|
1169
|
+
while IFS= read -r tracked_file; do
|
|
1170
|
+
[ -z "$tracked_file" ] && continue
|
|
1171
|
+
# Check if this tracked file has .purpose coverage
|
|
1172
|
+
check_dir=$(dirname "$tracked_file")
|
|
1173
|
+
found=false
|
|
1174
|
+
while [ "$check_dir" != "." ] && [ "$check_dir" != "/" ] && [ "$check_dir" != "" ]; do
|
|
1175
|
+
if [ -f "$check_dir/.purpose" ]; then
|
|
1176
|
+
found=true
|
|
1177
|
+
break
|
|
1178
|
+
fi
|
|
1179
|
+
check_dir=$(dirname "$check_dir")
|
|
1180
|
+
done
|
|
1181
|
+
if [ "$found" = false ] && [ -f ".purpose" ]; then
|
|
1182
|
+
found=true
|
|
1183
|
+
fi
|
|
1184
|
+
if [ "$found" = false ]; then
|
|
1185
|
+
UNCOVERED_COUNT=$((UNCOVERED_COUNT + 1))
|
|
1186
|
+
fi
|
|
1187
|
+
done < "$PENDING_FILE"
|
|
1188
|
+
fi
|
|
1189
|
+
|
|
1190
|
+
# Include the current file (not yet tracked)
|
|
1191
|
+
UNCOVERED_COUNT=$((UNCOVERED_COUNT + 1))
|
|
1192
|
+
|
|
1193
|
+
# Graduated enforcement
|
|
1194
|
+
if [ "$UNCOVERED_COUNT" -le 2 ]; then
|
|
1195
|
+
# Silent pass \u2014 don't slow down small fixes
|
|
1196
|
+
exit 0
|
|
1197
|
+
elif [ "$UNCOVERED_COUNT" -le 4 ]; then
|
|
1198
|
+
# Warn but allow
|
|
1199
|
+
echo "" >&2
|
|
1200
|
+
echo "[paradigm] Warning: $UNCOVERED_COUNT source files modified without .purpose coverage." >&2
|
|
1201
|
+
echo " Update the nearest .purpose file before the stop hook blocks you." >&2
|
|
1202
|
+
echo " Use: paradigm_purpose_init + paradigm_purpose_add_component" >&2
|
|
1203
|
+
exit 0
|
|
1204
|
+
else
|
|
1205
|
+
# Block \u2014 too many uncovered edits
|
|
1206
|
+
echo "" >&2
|
|
1207
|
+
echo "[paradigm] BLOCKED: $UNCOVERED_COUNT source files modified without .purpose coverage." >&2
|
|
1208
|
+
echo " You must update .purpose files before continuing." >&2
|
|
1209
|
+
echo " Steps:" >&2
|
|
1210
|
+
echo " 1. paradigm_purpose_init \u2014 create .purpose in uncovered directories" >&2
|
|
1211
|
+
echo " 2. paradigm_purpose_add_component \u2014 register code units" >&2
|
|
1212
|
+
echo " 3. paradigm_reindex \u2014 rebuild the index" >&2
|
|
1213
|
+
echo " Then retry your edit." >&2
|
|
1214
|
+
exit 2
|
|
1215
|
+
fi
|
|
1216
|
+
`;
|
|
1217
|
+
var CURSOR_POSTTOOLUSE_HOOK = `#!/bin/sh
|
|
1218
|
+
# Paradigm Cursor PostToolUse Hook \u2014 Advisory Feedback
|
|
1219
|
+
# Fires AFTER the agent calls Edit or Write.
|
|
1220
|
+
# Tracks modified source files and outputs advisory the agent can see.
|
|
1221
|
+
# Installed by: paradigm hooks install --cursor
|
|
1222
|
+
#
|
|
1223
|
+
# Hook type: postToolUse
|
|
1224
|
+
# Matcher: Edit|Write
|
|
1225
|
+
# Exit 0 always (never blocks \u2014 advisory only)
|
|
1226
|
+
#
|
|
1227
|
+
# Unlike afterFileEdit, postToolUse output is visible to the Cursor agent.
|
|
1228
|
+
# This is the primary advisory mechanism for Cursor enforcement.
|
|
1229
|
+
|
|
1230
|
+
# Read JSON from stdin (hook input)
|
|
1231
|
+
INPUT=$(cat)
|
|
1232
|
+
|
|
1233
|
+
# Extract file_path from postToolUse input
|
|
1234
|
+
if command -v jq >/dev/null 2>&1; then
|
|
1235
|
+
FILE_PATH=$(echo "$INPUT" | jq -r '.file_path // .input.file_path // empty' 2>/dev/null)
|
|
1236
|
+
else
|
|
1237
|
+
FILE_PATH=$(echo "$INPUT" | grep -o '"file_path"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"file_path"[[:space:]]*:[[:space:]]*"//' | sed 's/"$//')
|
|
1238
|
+
fi
|
|
1239
|
+
|
|
1240
|
+
if [ -z "$FILE_PATH" ]; then
|
|
1241
|
+
exit 0
|
|
1242
|
+
fi
|
|
1243
|
+
|
|
1244
|
+
# Extract workspace root
|
|
1245
|
+
if command -v jq >/dev/null 2>&1; then
|
|
1246
|
+
CWD=$(echo "$INPUT" | jq -r '.workspace_roots[0] // empty' 2>/dev/null)
|
|
1247
|
+
else
|
|
1248
|
+
CWD=$(echo "$INPUT" | grep -o '"workspace_roots"[[:space:]]*:[[:space:]]*\\["[^"]*"' | head -1 | sed 's/.*\\["//' | sed 's/"$//')
|
|
1249
|
+
fi
|
|
1250
|
+
|
|
1251
|
+
if [ -z "$CWD" ]; then
|
|
1252
|
+
CWD="$(pwd)"
|
|
1253
|
+
fi
|
|
1254
|
+
|
|
1255
|
+
# Not a paradigm project \u2014 pass
|
|
1256
|
+
if [ ! -d "$CWD/.paradigm" ]; then
|
|
1257
|
+
exit 0
|
|
1258
|
+
fi
|
|
1259
|
+
|
|
1260
|
+
cd "$CWD" || exit 0
|
|
1261
|
+
|
|
1262
|
+
# Convert to relative path
|
|
1263
|
+
REL_PATH="$FILE_PATH"
|
|
1264
|
+
case "$FILE_PATH" in
|
|
1265
|
+
"$CWD"/*) REL_PATH=$(echo "$FILE_PATH" | sed "s|^$CWD/||") ;;
|
|
1266
|
+
esac
|
|
1267
|
+
|
|
1268
|
+
# If still absolute, file is outside project \u2014 skip
|
|
1269
|
+
case "$REL_PATH" in
|
|
1270
|
+
/*) exit 0 ;;
|
|
1271
|
+
esac
|
|
1272
|
+
|
|
1273
|
+
# Skip non-source files
|
|
1274
|
+
case "$REL_PATH" in
|
|
1275
|
+
*.purpose|portal.yaml|*.md|*.lock|*.log|*.json|*.yaml|*.yml|.gitignore|.env*) exit 0 ;;
|
|
1276
|
+
esac
|
|
1277
|
+
|
|
1278
|
+
# Skip .paradigm, .claude, and .cursor directories
|
|
1279
|
+
case "$REL_PATH" in
|
|
1280
|
+
.paradigm/*|.claude/*|.cursor/*) exit 0 ;;
|
|
1281
|
+
esac
|
|
1282
|
+
|
|
1283
|
+
# Track: append to .paradigm/.pending-review (deduplicated)
|
|
1284
|
+
PENDING_FILE=".paradigm/.pending-review"
|
|
1285
|
+
if [ -f "$PENDING_FILE" ]; then
|
|
1286
|
+
if ! grep -qxF "$REL_PATH" "$PENDING_FILE" 2>/dev/null; then
|
|
1287
|
+
echo "$REL_PATH" >> "$PENDING_FILE"
|
|
1288
|
+
fi
|
|
1289
|
+
else
|
|
1290
|
+
echo "$REL_PATH" > "$PENDING_FILE"
|
|
1291
|
+
fi
|
|
1292
|
+
|
|
1293
|
+
# Count pending files
|
|
1294
|
+
PENDING_COUNT=$(wc -l < "$PENDING_FILE" | tr -d ' ')
|
|
1295
|
+
|
|
1296
|
+
# Walk up from the file's directory to find a .purpose file
|
|
1297
|
+
dir=$(dirname "$REL_PATH")
|
|
1298
|
+
found_purpose=""
|
|
1299
|
+
|
|
1300
|
+
while [ "$dir" != "." ] && [ "$dir" != "/" ] && [ "$dir" != "" ]; do
|
|
1301
|
+
if [ -f "$dir/.purpose" ]; then
|
|
1302
|
+
found_purpose="$dir/.purpose"
|
|
1303
|
+
break
|
|
1304
|
+
fi
|
|
1305
|
+
dir=$(dirname "$dir")
|
|
1306
|
+
done
|
|
1307
|
+
|
|
1308
|
+
# Check root .purpose
|
|
1309
|
+
if [ -z "$found_purpose" ] && [ -f ".purpose" ]; then
|
|
1310
|
+
found_purpose=".purpose"
|
|
1311
|
+
fi
|
|
1312
|
+
|
|
1313
|
+
if [ -z "$found_purpose" ]; then
|
|
1314
|
+
file_dir=$(dirname "$REL_PATH")
|
|
1315
|
+
echo "" >&2
|
|
1316
|
+
echo "[paradigm] No .purpose file covers $file_dir/" >&2
|
|
1317
|
+
echo " Create one: paradigm_purpose_init + paradigm_purpose_add_component" >&2
|
|
1318
|
+
echo " $PENDING_COUNT file(s) pending review. The stop hook WILL BLOCK." >&2
|
|
1319
|
+
elif [ "$PENDING_COUNT" -gt 0 ] && [ "$((PENDING_COUNT % 3))" -eq 0 ]; then
|
|
1320
|
+
echo "" >&2
|
|
1321
|
+
echo "[paradigm] $PENDING_COUNT source file(s) modified. Update $found_purpose:" >&2
|
|
1322
|
+
echo " -> #components, ~aspects (with anchors), !signals, \\$flows, ^gates" >&2
|
|
1323
|
+
echo " The stop hook WILL BLOCK if .purpose files aren't updated." >&2
|
|
1324
|
+
fi
|
|
1325
|
+
|
|
918
1326
|
exit 0
|
|
919
1327
|
`;
|
|
920
1328
|
|
|
@@ -938,6 +1346,68 @@ function isParadigmPluginActive() {
|
|
|
938
1346
|
return { active: false };
|
|
939
1347
|
}
|
|
940
1348
|
}
|
|
1349
|
+
function checkPluginVersionCompatibility() {
|
|
1350
|
+
try {
|
|
1351
|
+
const pluginInfo = isParadigmPluginActive();
|
|
1352
|
+
if (!pluginInfo.active || !pluginInfo.cacheVersion) {
|
|
1353
|
+
return { compatible: true };
|
|
1354
|
+
}
|
|
1355
|
+
const hooksJsonPath = path.join(
|
|
1356
|
+
os.homedir(),
|
|
1357
|
+
".claude",
|
|
1358
|
+
"plugins",
|
|
1359
|
+
"cache",
|
|
1360
|
+
"a-paradigm",
|
|
1361
|
+
"paradigm",
|
|
1362
|
+
pluginInfo.cacheVersion,
|
|
1363
|
+
"hooks.json"
|
|
1364
|
+
);
|
|
1365
|
+
if (!fs.existsSync(hooksJsonPath)) {
|
|
1366
|
+
return { compatible: true };
|
|
1367
|
+
}
|
|
1368
|
+
const hooksData = JSON.parse(fs.readFileSync(hooksJsonPath, "utf8"));
|
|
1369
|
+
const compatibleVersions = hooksData.compatibleVersions;
|
|
1370
|
+
if (!compatibleVersions) {
|
|
1371
|
+
return { compatible: true };
|
|
1372
|
+
}
|
|
1373
|
+
const currentVersion = getCurrentParadigmVersion();
|
|
1374
|
+
if (!currentVersion) {
|
|
1375
|
+
return { compatible: true };
|
|
1376
|
+
}
|
|
1377
|
+
const parts = compatibleVersions.split(/\s+/);
|
|
1378
|
+
for (const part of parts) {
|
|
1379
|
+
const match = part.match(/^(>=?|<=?)\s*(\d+\.\d+\.\d+)/);
|
|
1380
|
+
if (!match) continue;
|
|
1381
|
+
const [, op, ver] = match;
|
|
1382
|
+
const cmp = compareVersions(currentVersion, ver);
|
|
1383
|
+
if (op === ">=" && cmp < 0) return { compatible: false, message: `Plugin requires paradigm ${compatibleVersions}, current: ${currentVersion}` };
|
|
1384
|
+
if (op === ">" && cmp <= 0) return { compatible: false, message: `Plugin requires paradigm ${compatibleVersions}, current: ${currentVersion}` };
|
|
1385
|
+
if (op === "<=" && cmp > 0) return { compatible: false, message: `Plugin requires paradigm ${compatibleVersions}, current: ${currentVersion}` };
|
|
1386
|
+
if (op === "<" && cmp >= 0) return { compatible: false, message: `Plugin requires paradigm ${compatibleVersions}, current: ${currentVersion}` };
|
|
1387
|
+
}
|
|
1388
|
+
return { compatible: true };
|
|
1389
|
+
} catch {
|
|
1390
|
+
return { compatible: true };
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
function getCurrentParadigmVersion() {
|
|
1394
|
+
try {
|
|
1395
|
+
const pkgPath = path.join(path.dirname(new URL(import.meta.url).pathname), "..", "..", "package.json");
|
|
1396
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
|
|
1397
|
+
return pkg.version || null;
|
|
1398
|
+
} catch {
|
|
1399
|
+
return null;
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
function compareVersions(a, b) {
|
|
1403
|
+
const pa = a.split(".").map(Number);
|
|
1404
|
+
const pb = b.split(".").map(Number);
|
|
1405
|
+
for (let i = 0; i < 3; i++) {
|
|
1406
|
+
if ((pa[i] || 0) < (pb[i] || 0)) return -1;
|
|
1407
|
+
if ((pa[i] || 0) > (pb[i] || 0)) return 1;
|
|
1408
|
+
}
|
|
1409
|
+
return 0;
|
|
1410
|
+
}
|
|
941
1411
|
function cleanupProjectClaudeCodeHooks(rootDir) {
|
|
942
1412
|
const removed = [];
|
|
943
1413
|
const claudeHooksDir = path.join(rootDir, ".claude", "hooks");
|
|
@@ -993,6 +1463,25 @@ function cleanupProjectClaudeCodeHooks(rootDir) {
|
|
|
993
1463
|
}
|
|
994
1464
|
return { cleaned: removed.length > 0, removed };
|
|
995
1465
|
}
|
|
1466
|
+
function validateBashSyntax(scriptContent, scriptName) {
|
|
1467
|
+
try {
|
|
1468
|
+
const tmpPath = path.join(os.tmpdir(), `paradigm-hook-validate-${Date.now()}.sh`);
|
|
1469
|
+
fs.writeFileSync(tmpPath, scriptContent, "utf8");
|
|
1470
|
+
try {
|
|
1471
|
+
execSync(`bash -n "${tmpPath}" 2>&1`, { encoding: "utf-8" });
|
|
1472
|
+
return null;
|
|
1473
|
+
} catch (err) {
|
|
1474
|
+
return `${scriptName}: bash syntax error \u2014 ${err.message?.split("\n")[0] || "unknown error"}`;
|
|
1475
|
+
} finally {
|
|
1476
|
+
try {
|
|
1477
|
+
fs.unlinkSync(tmpPath);
|
|
1478
|
+
} catch {
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1481
|
+
} catch {
|
|
1482
|
+
return null;
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
996
1485
|
var POST_COMMIT_HOOK = `#!/bin/sh
|
|
997
1486
|
# Paradigm post-commit hook - captures history from commits
|
|
998
1487
|
# Installed by: paradigm hooks install
|
|
@@ -1089,8 +1578,41 @@ fi
|
|
|
1089
1578
|
`;
|
|
1090
1579
|
async function hooksInstallCommand(options = {}) {
|
|
1091
1580
|
const rootDir = process.cwd();
|
|
1581
|
+
const dryRun = options.dryRun || false;
|
|
1582
|
+
if (dryRun) {
|
|
1583
|
+
console.log(chalk.cyan("\n [dry-run] Showing what would be installed:\n"));
|
|
1584
|
+
}
|
|
1092
1585
|
const onlyClaudeCode = options.claudeCode && !options.postCommit && !options.prePush && !options.cursor;
|
|
1093
1586
|
const onlyCursor = options.cursor && !options.postCommit && !options.prePush && !options.claudeCode;
|
|
1587
|
+
if (!dryRun) {
|
|
1588
|
+
const scriptsToValidate = [
|
|
1589
|
+
{ name: "post-commit", content: POST_COMMIT_HOOK },
|
|
1590
|
+
{ name: "pre-push", content: PRE_PUSH_HOOK },
|
|
1591
|
+
{ name: "claude-code-stop", content: CLAUDE_CODE_STOP_HOOK },
|
|
1592
|
+
{ name: "claude-code-precommit", content: CLAUDE_CODE_PRECOMMIT_HOOK },
|
|
1593
|
+
{ name: "claude-code-postwrite", content: CLAUDE_CODE_POSTWRITE_HOOK },
|
|
1594
|
+
{ name: "cursor-session-start", content: CURSOR_SESSION_START_HOOK },
|
|
1595
|
+
{ name: "cursor-stop", content: CURSOR_STOP_HOOK },
|
|
1596
|
+
{ name: "cursor-precommit", content: CURSOR_PRECOMMIT_HOOK },
|
|
1597
|
+
{ name: "cursor-postwrite", content: CURSOR_POSTWRITE_HOOK },
|
|
1598
|
+
{ name: "cursor-pretooluse", content: CURSOR_PRETOOLUSE_HOOK },
|
|
1599
|
+
{ name: "cursor-posttooluse", content: CURSOR_POSTTOOLUSE_HOOK }
|
|
1600
|
+
];
|
|
1601
|
+
for (const script of scriptsToValidate) {
|
|
1602
|
+
const err = validateBashSyntax(script.content, script.name);
|
|
1603
|
+
if (err) {
|
|
1604
|
+
console.log(chalk.red(`Hook syntax error: ${err}`));
|
|
1605
|
+
console.log(chalk.gray("Aborting installation. Fix the hook script and try again."));
|
|
1606
|
+
return;
|
|
1607
|
+
}
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1610
|
+
const compat = checkPluginVersionCompatibility();
|
|
1611
|
+
if (!compat.compatible) {
|
|
1612
|
+
console.log(chalk.yellow(`
|
|
1613
|
+
\u26A0 ${compat.message}`));
|
|
1614
|
+
console.log(chalk.gray(" Hook installation will continue, but behavior may differ from plugin expectations.\n"));
|
|
1615
|
+
}
|
|
1094
1616
|
if (!onlyClaudeCode && !onlyCursor) {
|
|
1095
1617
|
const gitDir = path.join(rootDir, ".git");
|
|
1096
1618
|
if (!fs.existsSync(gitDir)) {
|
|
@@ -1098,53 +1620,79 @@ async function hooksInstallCommand(options = {}) {
|
|
|
1098
1620
|
return;
|
|
1099
1621
|
}
|
|
1100
1622
|
const hooksDir = path.join(gitDir, "hooks");
|
|
1101
|
-
|
|
1102
|
-
const installAll2 = !options.postCommit && !options.prePush && !options.claudeCode;
|
|
1623
|
+
const installAllGit = !options.postCommit && !options.prePush && !options.claudeCode;
|
|
1103
1624
|
const installed = [];
|
|
1104
|
-
if (
|
|
1625
|
+
if (installAllGit || options.postCommit) {
|
|
1105
1626
|
const hookPath = path.join(hooksDir, "post-commit");
|
|
1106
|
-
if (
|
|
1107
|
-
const
|
|
1108
|
-
|
|
1109
|
-
|
|
1627
|
+
if (dryRun) {
|
|
1628
|
+
const action = fs.existsSync(hookPath) && !options.force ? "skip (exists)" : "install";
|
|
1629
|
+
console.log(chalk.gray(` post-commit: ${action} \u2192 ${hookPath}`));
|
|
1630
|
+
} else {
|
|
1631
|
+
if (fs.existsSync(hookPath) && !options.force) {
|
|
1632
|
+
const content = fs.readFileSync(hookPath, "utf8");
|
|
1633
|
+
if (!content.includes("paradigm")) {
|
|
1634
|
+
console.log(chalk.yellow("post-commit hook exists. Use --force to overwrite."));
|
|
1635
|
+
} else {
|
|
1636
|
+
console.log(chalk.gray("post-commit hook already installed by paradigm"));
|
|
1637
|
+
}
|
|
1110
1638
|
} else {
|
|
1111
|
-
|
|
1639
|
+
fs.mkdirSync(hooksDir, { recursive: true });
|
|
1640
|
+
fs.writeFileSync(hookPath, POST_COMMIT_HOOK);
|
|
1641
|
+
fs.chmodSync(hookPath, "755");
|
|
1642
|
+
installed.push("post-commit");
|
|
1112
1643
|
}
|
|
1113
|
-
} else {
|
|
1114
|
-
fs.writeFileSync(hookPath, POST_COMMIT_HOOK);
|
|
1115
|
-
fs.chmodSync(hookPath, "755");
|
|
1116
|
-
installed.push("post-commit");
|
|
1117
1644
|
}
|
|
1118
1645
|
}
|
|
1119
|
-
if (
|
|
1646
|
+
if (installAllGit || options.prePush) {
|
|
1120
1647
|
const hookPath = path.join(hooksDir, "pre-push");
|
|
1121
|
-
if (
|
|
1122
|
-
const
|
|
1123
|
-
|
|
1124
|
-
|
|
1648
|
+
if (dryRun) {
|
|
1649
|
+
const action = fs.existsSync(hookPath) && !options.force ? "skip (exists)" : "install";
|
|
1650
|
+
console.log(chalk.gray(` pre-push: ${action} \u2192 ${hookPath}`));
|
|
1651
|
+
} else {
|
|
1652
|
+
if (fs.existsSync(hookPath) && !options.force) {
|
|
1653
|
+
const content = fs.readFileSync(hookPath, "utf8");
|
|
1654
|
+
if (!content.includes("paradigm")) {
|
|
1655
|
+
console.log(chalk.yellow("pre-push hook exists. Use --force to overwrite."));
|
|
1656
|
+
} else {
|
|
1657
|
+
console.log(chalk.gray("pre-push hook already installed by paradigm"));
|
|
1658
|
+
}
|
|
1125
1659
|
} else {
|
|
1126
|
-
|
|
1660
|
+
fs.mkdirSync(hooksDir, { recursive: true });
|
|
1661
|
+
fs.writeFileSync(hookPath, PRE_PUSH_HOOK);
|
|
1662
|
+
fs.chmodSync(hookPath, "755");
|
|
1663
|
+
installed.push("pre-push");
|
|
1127
1664
|
}
|
|
1128
|
-
} else {
|
|
1129
|
-
fs.writeFileSync(hookPath, PRE_PUSH_HOOK);
|
|
1130
|
-
fs.chmodSync(hookPath, "755");
|
|
1131
|
-
installed.push("pre-push");
|
|
1132
1665
|
}
|
|
1133
1666
|
}
|
|
1134
|
-
if (installed.length > 0) {
|
|
1667
|
+
if (!dryRun && installed.length > 0) {
|
|
1135
1668
|
console.log(chalk.green(`Git hooks installed: ${installed.join(", ")}`));
|
|
1136
1669
|
}
|
|
1137
1670
|
const historyDir = path.join(rootDir, ".paradigm/history");
|
|
1138
|
-
if (!fs.existsSync(historyDir)) {
|
|
1671
|
+
if (!fs.existsSync(historyDir) && !dryRun) {
|
|
1139
1672
|
console.log(chalk.gray("Tip: Run `paradigm history init` to initialize history tracking"));
|
|
1140
1673
|
}
|
|
1141
1674
|
}
|
|
1142
1675
|
const installAll = !options.postCommit && !options.prePush && !options.claudeCode && !options.cursor;
|
|
1143
1676
|
if (installAll || options.claudeCode) {
|
|
1144
|
-
|
|
1677
|
+
if (dryRun) {
|
|
1678
|
+
console.log(chalk.gray(" Claude Code hooks: would install paradigm-stop.sh, paradigm-precommit.sh, paradigm-postwrite.sh"));
|
|
1679
|
+
console.log(chalk.gray(` \u2192 ${path.join(rootDir, ".claude", "hooks")}/`));
|
|
1680
|
+
console.log(chalk.gray(" \u2192 Update .claude/settings.json with hook configuration"));
|
|
1681
|
+
} else {
|
|
1682
|
+
await installClaudeCodeHooks(rootDir, options.force);
|
|
1683
|
+
}
|
|
1145
1684
|
}
|
|
1146
1685
|
if (installAll || options.cursor) {
|
|
1147
|
-
|
|
1686
|
+
if (dryRun) {
|
|
1687
|
+
console.log(chalk.gray(" Cursor hooks: would install paradigm-session-start.sh, paradigm-stop.sh, paradigm-precommit.sh, paradigm-postwrite.sh, paradigm-pretooluse.sh, paradigm-posttooluse.sh"));
|
|
1688
|
+
console.log(chalk.gray(` \u2192 ${path.join(rootDir, ".cursor", "hooks")}/`));
|
|
1689
|
+
console.log(chalk.gray(" \u2192 Update .cursor/hooks.json"));
|
|
1690
|
+
} else {
|
|
1691
|
+
await installCursorHooks(rootDir, options.force);
|
|
1692
|
+
}
|
|
1693
|
+
}
|
|
1694
|
+
if (dryRun) {
|
|
1695
|
+
console.log(chalk.cyan("\n [dry-run] No changes made.\n"));
|
|
1148
1696
|
}
|
|
1149
1697
|
}
|
|
1150
1698
|
async function installClaudeCodeHooks(rootDir, force) {
|
|
@@ -1246,9 +1794,12 @@ async function installCursorHooks(rootDir, force) {
|
|
|
1246
1794
|
fs.mkdirSync(cursorHooksDir, { recursive: true });
|
|
1247
1795
|
const installed = [];
|
|
1248
1796
|
const hookScripts = [
|
|
1797
|
+
{ name: "paradigm-session-start.sh", content: CURSOR_SESSION_START_HOOK },
|
|
1249
1798
|
{ name: "paradigm-stop.sh", content: CURSOR_STOP_HOOK },
|
|
1250
1799
|
{ name: "paradigm-precommit.sh", content: CURSOR_PRECOMMIT_HOOK },
|
|
1251
|
-
{ name: "paradigm-postwrite.sh", content: CURSOR_POSTWRITE_HOOK }
|
|
1800
|
+
{ name: "paradigm-postwrite.sh", content: CURSOR_POSTWRITE_HOOK },
|
|
1801
|
+
{ name: "paradigm-pretooluse.sh", content: CURSOR_PRETOOLUSE_HOOK },
|
|
1802
|
+
{ name: "paradigm-posttooluse.sh", content: CURSOR_POSTTOOLUSE_HOOK }
|
|
1252
1803
|
];
|
|
1253
1804
|
for (const hook of hookScripts) {
|
|
1254
1805
|
const destPath = path.join(cursorHooksDir, hook.name);
|
|
@@ -1270,9 +1821,14 @@ async function installCursorHooks(rootDir, force) {
|
|
|
1270
1821
|
}
|
|
1271
1822
|
hooksConfig.version = 1;
|
|
1272
1823
|
const hooks = hooksConfig.hooks || {};
|
|
1824
|
+
const paradigmSessionStartEntry = {
|
|
1825
|
+
command: ".cursor/hooks/paradigm-session-start.sh",
|
|
1826
|
+
timeout: 5
|
|
1827
|
+
};
|
|
1273
1828
|
const paradigmStopEntry = {
|
|
1274
1829
|
command: ".cursor/hooks/paradigm-stop.sh",
|
|
1275
|
-
timeout: 10
|
|
1830
|
+
timeout: 10,
|
|
1831
|
+
loop_limit: 3
|
|
1276
1832
|
};
|
|
1277
1833
|
const paradigmPostwriteEntry = {
|
|
1278
1834
|
command: ".cursor/hooks/paradigm-postwrite.sh",
|
|
@@ -1283,6 +1839,14 @@ async function installCursorHooks(rootDir, force) {
|
|
|
1283
1839
|
matcher: "git commit",
|
|
1284
1840
|
timeout: 30
|
|
1285
1841
|
};
|
|
1842
|
+
const sessionStartHooks = hooks.sessionStart || [];
|
|
1843
|
+
const hasParadigmSessionStart = sessionStartHooks.some(
|
|
1844
|
+
(h) => JSON.stringify(h).includes("paradigm-session-start.sh")
|
|
1845
|
+
);
|
|
1846
|
+
if (!hasParadigmSessionStart) {
|
|
1847
|
+
sessionStartHooks.push(paradigmSessionStartEntry);
|
|
1848
|
+
}
|
|
1849
|
+
hooks.sessionStart = sessionStartHooks;
|
|
1286
1850
|
const stopHooks = hooks.stop || [];
|
|
1287
1851
|
const hasParadigmStop = stopHooks.some(
|
|
1288
1852
|
(h) => JSON.stringify(h).includes("paradigm-stop.sh")
|
|
@@ -1299,6 +1863,32 @@ async function installCursorHooks(rootDir, force) {
|
|
|
1299
1863
|
afterFileEditHooks.push(paradigmPostwriteEntry);
|
|
1300
1864
|
}
|
|
1301
1865
|
hooks.afterFileEdit = afterFileEditHooks;
|
|
1866
|
+
const paradigmPretoolUseEntry = {
|
|
1867
|
+
command: ".cursor/hooks/paradigm-pretooluse.sh",
|
|
1868
|
+
matcher: "Edit|Write",
|
|
1869
|
+
timeout: 5
|
|
1870
|
+
};
|
|
1871
|
+
const preToolUseHooks = hooks.preToolUse || [];
|
|
1872
|
+
const hasParadigmPretoolUse = preToolUseHooks.some(
|
|
1873
|
+
(h) => JSON.stringify(h).includes("paradigm-pretooluse.sh")
|
|
1874
|
+
);
|
|
1875
|
+
if (!hasParadigmPretoolUse) {
|
|
1876
|
+
preToolUseHooks.push(paradigmPretoolUseEntry);
|
|
1877
|
+
}
|
|
1878
|
+
hooks.preToolUse = preToolUseHooks;
|
|
1879
|
+
const paradigmPosttoolUseEntry = {
|
|
1880
|
+
command: ".cursor/hooks/paradigm-posttooluse.sh",
|
|
1881
|
+
matcher: "Edit|Write",
|
|
1882
|
+
timeout: 5
|
|
1883
|
+
};
|
|
1884
|
+
const postToolUseHooks = hooks.postToolUse || [];
|
|
1885
|
+
const hasParadigmPosttoolUse = postToolUseHooks.some(
|
|
1886
|
+
(h) => JSON.stringify(h).includes("paradigm-posttooluse.sh")
|
|
1887
|
+
);
|
|
1888
|
+
if (!hasParadigmPosttoolUse) {
|
|
1889
|
+
postToolUseHooks.push(paradigmPosttoolUseEntry);
|
|
1890
|
+
}
|
|
1891
|
+
hooks.postToolUse = postToolUseHooks;
|
|
1302
1892
|
const beforeShellHooks = hooks.beforeShellExecution || [];
|
|
1303
1893
|
const hasParadigmPrecommit = beforeShellHooks.some(
|
|
1304
1894
|
(h) => JSON.stringify(h).includes("paradigm-precommit.sh")
|
|
@@ -1316,6 +1906,10 @@ async function installCursorHooks(rootDir, force) {
|
|
|
1316
1906
|
}
|
|
1317
1907
|
async function hooksUninstallCommand(options = {}) {
|
|
1318
1908
|
const rootDir = process.cwd();
|
|
1909
|
+
const dryRun = options.dryRun || false;
|
|
1910
|
+
if (dryRun) {
|
|
1911
|
+
console.log(chalk.cyan("\n [dry-run] Showing what would be removed:\n"));
|
|
1912
|
+
}
|
|
1319
1913
|
if (!options.cursor) {
|
|
1320
1914
|
const gitDir = path.join(rootDir, ".git");
|
|
1321
1915
|
if (!fs.existsSync(gitDir)) {
|
|
@@ -1329,53 +1923,76 @@ async function hooksUninstallCommand(options = {}) {
|
|
|
1329
1923
|
if (fs.existsSync(hookPath)) {
|
|
1330
1924
|
const content = fs.readFileSync(hookPath, "utf8");
|
|
1331
1925
|
if (content.includes("paradigm")) {
|
|
1332
|
-
|
|
1926
|
+
if (dryRun) {
|
|
1927
|
+
console.log(chalk.gray(` Would remove: ${hookPath}`));
|
|
1928
|
+
} else {
|
|
1929
|
+
fs.unlinkSync(hookPath);
|
|
1930
|
+
}
|
|
1333
1931
|
removed.push(hookName);
|
|
1334
1932
|
}
|
|
1335
1933
|
}
|
|
1336
1934
|
}
|
|
1337
|
-
if (
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1935
|
+
if (!dryRun) {
|
|
1936
|
+
if (removed.length > 0) {
|
|
1937
|
+
console.log(chalk.green(`Git hooks removed: ${removed.join(", ")}`));
|
|
1938
|
+
} else {
|
|
1939
|
+
console.log(chalk.gray("No paradigm git hooks found to remove"));
|
|
1940
|
+
}
|
|
1941
|
+
} else if (removed.length === 0) {
|
|
1942
|
+
console.log(chalk.gray(" No paradigm git hooks to remove"));
|
|
1341
1943
|
}
|
|
1342
1944
|
}
|
|
1343
1945
|
if (options.cursor) {
|
|
1344
1946
|
const cursorHooksDir = path.join(rootDir, ".cursor", "hooks");
|
|
1345
1947
|
const cursorRemoved = [];
|
|
1346
|
-
for (const hookName of ["paradigm-stop.sh", "paradigm-precommit.sh", "paradigm-postwrite.sh"]) {
|
|
1948
|
+
for (const hookName of ["paradigm-session-start.sh", "paradigm-stop.sh", "paradigm-precommit.sh", "paradigm-postwrite.sh", "paradigm-pretooluse.sh", "paradigm-posttooluse.sh"]) {
|
|
1347
1949
|
const hookPath = path.join(cursorHooksDir, hookName);
|
|
1348
1950
|
if (fs.existsSync(hookPath)) {
|
|
1349
|
-
|
|
1951
|
+
if (dryRun) {
|
|
1952
|
+
console.log(chalk.gray(` Would remove: ${hookPath}`));
|
|
1953
|
+
} else {
|
|
1954
|
+
fs.unlinkSync(hookPath);
|
|
1955
|
+
}
|
|
1350
1956
|
cursorRemoved.push(hookName);
|
|
1351
1957
|
}
|
|
1352
1958
|
}
|
|
1353
1959
|
const hooksJsonPath = path.join(rootDir, ".cursor", "hooks.json");
|
|
1354
1960
|
if (fs.existsSync(hooksJsonPath)) {
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
)
|
|
1363
|
-
|
|
1364
|
-
|
|
1961
|
+
if (dryRun) {
|
|
1962
|
+
console.log(chalk.gray(` Would clean paradigm entries from: ${hooksJsonPath}`));
|
|
1963
|
+
} else {
|
|
1964
|
+
try {
|
|
1965
|
+
const hooksConfig = JSON.parse(fs.readFileSync(hooksJsonPath, "utf8"));
|
|
1966
|
+
const hooks = hooksConfig.hooks || {};
|
|
1967
|
+
for (const key of ["sessionStart", "stop", "afterFileEdit", "beforeShellExecution", "preToolUse", "postToolUse"]) {
|
|
1968
|
+
if (Array.isArray(hooks[key])) {
|
|
1969
|
+
hooks[key] = hooks[key].filter(
|
|
1970
|
+
(h) => !JSON.stringify(h).includes("paradigm-")
|
|
1971
|
+
);
|
|
1972
|
+
if (hooks[key].length === 0) {
|
|
1973
|
+
delete hooks[key];
|
|
1974
|
+
}
|
|
1365
1975
|
}
|
|
1366
1976
|
}
|
|
1977
|
+
hooksConfig.hooks = hooks;
|
|
1978
|
+
fs.writeFileSync(hooksJsonPath, JSON.stringify(hooksConfig, null, 2) + "\n", "utf8");
|
|
1979
|
+
} catch {
|
|
1367
1980
|
}
|
|
1368
|
-
hooksConfig.hooks = hooks;
|
|
1369
|
-
fs.writeFileSync(hooksJsonPath, JSON.stringify(hooksConfig, null, 2) + "\n", "utf8");
|
|
1370
|
-
} catch {
|
|
1371
1981
|
}
|
|
1372
1982
|
}
|
|
1373
|
-
if (
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1983
|
+
if (!dryRun) {
|
|
1984
|
+
if (cursorRemoved.length > 0) {
|
|
1985
|
+
console.log(chalk.green(`Cursor hooks removed: ${cursorRemoved.join(", ")}`));
|
|
1986
|
+
} else {
|
|
1987
|
+
console.log(chalk.gray("No paradigm Cursor hooks found to remove"));
|
|
1988
|
+
}
|
|
1989
|
+
} else if (cursorRemoved.length === 0) {
|
|
1990
|
+
console.log(chalk.gray(" No paradigm Cursor hooks to remove"));
|
|
1377
1991
|
}
|
|
1378
1992
|
}
|
|
1993
|
+
if (dryRun) {
|
|
1994
|
+
console.log(chalk.cyan("\n [dry-run] No changes made.\n"));
|
|
1995
|
+
}
|
|
1379
1996
|
}
|
|
1380
1997
|
async function hooksStatusCommand() {
|
|
1381
1998
|
const rootDir = process.cwd();
|
|
@@ -1471,7 +2088,7 @@ async function hooksStatusCommand() {
|
|
|
1471
2088
|
}
|
|
1472
2089
|
console.log(chalk.magenta("\n Cursor Hooks Status\n"));
|
|
1473
2090
|
const cursorHooksDir = path.join(rootDir, ".cursor", "hooks");
|
|
1474
|
-
const cursorHooks = ["paradigm-stop.sh", "paradigm-precommit.sh", "paradigm-postwrite.sh"];
|
|
2091
|
+
const cursorHooks = ["paradigm-session-start.sh", "paradigm-stop.sh", "paradigm-precommit.sh", "paradigm-postwrite.sh", "paradigm-pretooluse.sh", "paradigm-posttooluse.sh"];
|
|
1475
2092
|
for (const hookName of cursorHooks) {
|
|
1476
2093
|
const hookPath = path.join(cursorHooksDir, hookName);
|
|
1477
2094
|
if (fs.existsSync(hookPath)) {
|
|
@@ -1485,11 +2102,17 @@ async function hooksStatusCommand() {
|
|
|
1485
2102
|
try {
|
|
1486
2103
|
const hooksJson = JSON.parse(fs.readFileSync(cursorHooksJsonPath, "utf8"));
|
|
1487
2104
|
const hooks = hooksJson.hooks || {};
|
|
2105
|
+
const hasSessionStart = JSON.stringify(hooks.sessionStart || []).includes("paradigm-session-start.sh");
|
|
1488
2106
|
const hasStop = JSON.stringify(hooks.stop || []).includes("paradigm-stop.sh");
|
|
1489
2107
|
const hasPostwrite = JSON.stringify(hooks.afterFileEdit || []).includes("paradigm-postwrite.sh");
|
|
1490
2108
|
const hasPrecommit = JSON.stringify(hooks.beforeShellExecution || []).includes("paradigm-precommit.sh");
|
|
2109
|
+
const hasPretoolUse = JSON.stringify(hooks.preToolUse || []).includes("paradigm-pretooluse.sh");
|
|
2110
|
+
const hasPosttoolUse = JSON.stringify(hooks.postToolUse || []).includes("paradigm-posttooluse.sh");
|
|
2111
|
+
console.log(chalk.gray(` hooks.json sessionStart: ${hasSessionStart ? "configured" : "missing"}`));
|
|
1491
2112
|
console.log(chalk.gray(` hooks.json stop: ${hasStop ? "configured" : "missing"}`));
|
|
1492
2113
|
console.log(chalk.gray(` hooks.json afterFileEdit: ${hasPostwrite ? "configured" : "missing"}`));
|
|
2114
|
+
console.log(chalk.gray(` hooks.json preToolUse: ${hasPretoolUse ? "configured" : "missing"}`));
|
|
2115
|
+
console.log(chalk.gray(` hooks.json postToolUse: ${hasPosttoolUse ? "configured" : "missing"}`));
|
|
1493
2116
|
console.log(chalk.gray(` hooks.json beforeShellExecution: ${hasPrecommit ? "configured" : "missing"}`));
|
|
1494
2117
|
} catch {
|
|
1495
2118
|
console.log(chalk.yellow(" hooks.json: parse error"));
|