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