@miller-tech/uap 1.13.12 → 1.13.13

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.
@@ -1272,7 +1272,7 @@ export declare const ModelConfigSchema: z.ZodObject<{
1272
1272
  }, "strip", z.ZodTypeAny, {
1273
1273
  id: string;
1274
1274
  name: string;
1275
- provider: "custom" | "anthropic" | "deepseek" | "openai" | "zhipu" | "ollama";
1275
+ provider: "anthropic" | "deepseek" | "openai" | "zhipu" | "ollama" | "custom";
1276
1276
  apiModel: string;
1277
1277
  maxContextTokens: number;
1278
1278
  capabilities: string[];
@@ -1283,7 +1283,7 @@ export declare const ModelConfigSchema: z.ZodObject<{
1283
1283
  }, {
1284
1284
  id: string;
1285
1285
  name: string;
1286
- provider: "custom" | "anthropic" | "deepseek" | "openai" | "zhipu" | "ollama";
1286
+ provider: "anthropic" | "deepseek" | "openai" | "zhipu" | "ollama" | "custom";
1287
1287
  apiModel: string;
1288
1288
  endpoint?: string | undefined;
1289
1289
  apiKeyEnvVar?: string | undefined;
@@ -1303,12 +1303,12 @@ export declare const RoutingRuleSchema: z.ZodObject<{
1303
1303
  priority: number;
1304
1304
  complexity?: "low" | "medium" | "high" | "critical" | undefined;
1305
1305
  keywords?: string[] | undefined;
1306
- taskType?: "planning" | "coding" | "refactoring" | "bug-fix" | "review" | "documentation" | undefined;
1306
+ taskType?: "planning" | "review" | "coding" | "refactoring" | "bug-fix" | "documentation" | undefined;
1307
1307
  }, {
1308
1308
  targetRole: "planner" | "executor" | "reviewer" | "fallback";
1309
1309
  complexity?: "low" | "medium" | "high" | "critical" | undefined;
1310
1310
  keywords?: string[] | undefined;
1311
- taskType?: "planning" | "coding" | "refactoring" | "bug-fix" | "review" | "documentation" | undefined;
1311
+ taskType?: "planning" | "review" | "coding" | "refactoring" | "bug-fix" | "documentation" | undefined;
1312
1312
  priority?: number | undefined;
1313
1313
  }>;
1314
1314
  export declare const MultiModelSchema: z.ZodObject<{
@@ -1328,7 +1328,7 @@ export declare const MultiModelSchema: z.ZodObject<{
1328
1328
  }, "strip", z.ZodTypeAny, {
1329
1329
  id: string;
1330
1330
  name: string;
1331
- provider: "custom" | "anthropic" | "deepseek" | "openai" | "zhipu" | "ollama";
1331
+ provider: "anthropic" | "deepseek" | "openai" | "zhipu" | "ollama" | "custom";
1332
1332
  apiModel: string;
1333
1333
  maxContextTokens: number;
1334
1334
  capabilities: string[];
@@ -1340,7 +1340,7 @@ export declare const MultiModelSchema: z.ZodObject<{
1340
1340
  }, {
1341
1341
  id: string;
1342
1342
  name: string;
1343
- provider: "custom" | "anthropic" | "deepseek" | "openai" | "zhipu" | "ollama";
1343
+ provider: "anthropic" | "deepseek" | "openai" | "zhipu" | "ollama" | "custom";
1344
1344
  apiModel: string;
1345
1345
  endpoint?: string | undefined;
1346
1346
  apiKeyEnvVar?: string | undefined;
@@ -1377,12 +1377,12 @@ export declare const MultiModelSchema: z.ZodObject<{
1377
1377
  priority: number;
1378
1378
  complexity?: "low" | "medium" | "high" | "critical" | undefined;
1379
1379
  keywords?: string[] | undefined;
1380
- taskType?: "planning" | "coding" | "refactoring" | "bug-fix" | "review" | "documentation" | undefined;
1380
+ taskType?: "planning" | "review" | "coding" | "refactoring" | "bug-fix" | "documentation" | undefined;
1381
1381
  }, {
1382
1382
  targetRole: "planner" | "executor" | "reviewer" | "fallback";
1383
1383
  complexity?: "low" | "medium" | "high" | "critical" | undefined;
1384
1384
  keywords?: string[] | undefined;
1385
- taskType?: "planning" | "coding" | "refactoring" | "bug-fix" | "review" | "documentation" | undefined;
1385
+ taskType?: "planning" | "review" | "coding" | "refactoring" | "bug-fix" | "documentation" | undefined;
1386
1386
  priority?: number | undefined;
1387
1387
  }>, "many">>;
1388
1388
  costOptimization: z.ZodOptional<z.ZodObject<{
@@ -1443,7 +1443,7 @@ export declare const MultiModelSchema: z.ZodObject<{
1443
1443
  models: (string | {
1444
1444
  id: string;
1445
1445
  name: string;
1446
- provider: "custom" | "anthropic" | "deepseek" | "openai" | "zhipu" | "ollama";
1446
+ provider: "anthropic" | "deepseek" | "openai" | "zhipu" | "ollama" | "custom";
1447
1447
  apiModel: string;
1448
1448
  maxContextTokens: number;
1449
1449
  capabilities: string[];
@@ -1465,7 +1465,7 @@ export declare const MultiModelSchema: z.ZodObject<{
1465
1465
  priority: number;
1466
1466
  complexity?: "low" | "medium" | "high" | "critical" | undefined;
1467
1467
  keywords?: string[] | undefined;
1468
- taskType?: "planning" | "coding" | "refactoring" | "bug-fix" | "review" | "documentation" | undefined;
1468
+ taskType?: "planning" | "review" | "coding" | "refactoring" | "bug-fix" | "documentation" | undefined;
1469
1469
  }[] | undefined;
1470
1470
  costOptimization?: {
1471
1471
  enabled: boolean;
@@ -1492,7 +1492,7 @@ export declare const MultiModelSchema: z.ZodObject<{
1492
1492
  models?: (string | {
1493
1493
  id: string;
1494
1494
  name: string;
1495
- provider: "custom" | "anthropic" | "deepseek" | "openai" | "zhipu" | "ollama";
1495
+ provider: "anthropic" | "deepseek" | "openai" | "zhipu" | "ollama" | "custom";
1496
1496
  apiModel: string;
1497
1497
  endpoint?: string | undefined;
1498
1498
  apiKeyEnvVar?: string | undefined;
@@ -1512,7 +1512,7 @@ export declare const MultiModelSchema: z.ZodObject<{
1512
1512
  targetRole: "planner" | "executor" | "reviewer" | "fallback";
1513
1513
  complexity?: "low" | "medium" | "high" | "critical" | undefined;
1514
1514
  keywords?: string[] | undefined;
1515
- taskType?: "planning" | "coding" | "refactoring" | "bug-fix" | "review" | "documentation" | undefined;
1515
+ taskType?: "planning" | "review" | "coding" | "refactoring" | "bug-fix" | "documentation" | undefined;
1516
1516
  priority?: number | undefined;
1517
1517
  }[] | undefined;
1518
1518
  costOptimization?: {
@@ -2583,7 +2583,7 @@ export declare const AgentContextConfigSchema: z.ZodObject<{
2583
2583
  }, "strip", z.ZodTypeAny, {
2584
2584
  id: string;
2585
2585
  name: string;
2586
- provider: "custom" | "anthropic" | "deepseek" | "openai" | "zhipu" | "ollama";
2586
+ provider: "anthropic" | "deepseek" | "openai" | "zhipu" | "ollama" | "custom";
2587
2587
  apiModel: string;
2588
2588
  maxContextTokens: number;
2589
2589
  capabilities: string[];
@@ -2595,7 +2595,7 @@ export declare const AgentContextConfigSchema: z.ZodObject<{
2595
2595
  }, {
2596
2596
  id: string;
2597
2597
  name: string;
2598
- provider: "custom" | "anthropic" | "deepseek" | "openai" | "zhipu" | "ollama";
2598
+ provider: "anthropic" | "deepseek" | "openai" | "zhipu" | "ollama" | "custom";
2599
2599
  apiModel: string;
2600
2600
  endpoint?: string | undefined;
2601
2601
  apiKeyEnvVar?: string | undefined;
@@ -2632,12 +2632,12 @@ export declare const AgentContextConfigSchema: z.ZodObject<{
2632
2632
  priority: number;
2633
2633
  complexity?: "low" | "medium" | "high" | "critical" | undefined;
2634
2634
  keywords?: string[] | undefined;
2635
- taskType?: "planning" | "coding" | "refactoring" | "bug-fix" | "review" | "documentation" | undefined;
2635
+ taskType?: "planning" | "review" | "coding" | "refactoring" | "bug-fix" | "documentation" | undefined;
2636
2636
  }, {
2637
2637
  targetRole: "planner" | "executor" | "reviewer" | "fallback";
2638
2638
  complexity?: "low" | "medium" | "high" | "critical" | undefined;
2639
2639
  keywords?: string[] | undefined;
2640
- taskType?: "planning" | "coding" | "refactoring" | "bug-fix" | "review" | "documentation" | undefined;
2640
+ taskType?: "planning" | "review" | "coding" | "refactoring" | "bug-fix" | "documentation" | undefined;
2641
2641
  priority?: number | undefined;
2642
2642
  }>, "many">>;
2643
2643
  costOptimization: z.ZodOptional<z.ZodObject<{
@@ -2698,7 +2698,7 @@ export declare const AgentContextConfigSchema: z.ZodObject<{
2698
2698
  models: (string | {
2699
2699
  id: string;
2700
2700
  name: string;
2701
- provider: "custom" | "anthropic" | "deepseek" | "openai" | "zhipu" | "ollama";
2701
+ provider: "anthropic" | "deepseek" | "openai" | "zhipu" | "ollama" | "custom";
2702
2702
  apiModel: string;
2703
2703
  maxContextTokens: number;
2704
2704
  capabilities: string[];
@@ -2720,7 +2720,7 @@ export declare const AgentContextConfigSchema: z.ZodObject<{
2720
2720
  priority: number;
2721
2721
  complexity?: "low" | "medium" | "high" | "critical" | undefined;
2722
2722
  keywords?: string[] | undefined;
2723
- taskType?: "planning" | "coding" | "refactoring" | "bug-fix" | "review" | "documentation" | undefined;
2723
+ taskType?: "planning" | "review" | "coding" | "refactoring" | "bug-fix" | "documentation" | undefined;
2724
2724
  }[] | undefined;
2725
2725
  costOptimization?: {
2726
2726
  enabled: boolean;
@@ -2747,7 +2747,7 @@ export declare const AgentContextConfigSchema: z.ZodObject<{
2747
2747
  models?: (string | {
2748
2748
  id: string;
2749
2749
  name: string;
2750
- provider: "custom" | "anthropic" | "deepseek" | "openai" | "zhipu" | "ollama";
2750
+ provider: "anthropic" | "deepseek" | "openai" | "zhipu" | "ollama" | "custom";
2751
2751
  apiModel: string;
2752
2752
  endpoint?: string | undefined;
2753
2753
  apiKeyEnvVar?: string | undefined;
@@ -2767,7 +2767,7 @@ export declare const AgentContextConfigSchema: z.ZodObject<{
2767
2767
  targetRole: "planner" | "executor" | "reviewer" | "fallback";
2768
2768
  complexity?: "low" | "medium" | "high" | "critical" | undefined;
2769
2769
  keywords?: string[] | undefined;
2770
- taskType?: "planning" | "coding" | "refactoring" | "bug-fix" | "review" | "documentation" | undefined;
2770
+ taskType?: "planning" | "review" | "coding" | "refactoring" | "bug-fix" | "documentation" | undefined;
2771
2771
  priority?: number | undefined;
2772
2772
  }[] | undefined;
2773
2773
  costOptimization?: {
@@ -3092,7 +3092,7 @@ export declare const AgentContextConfigSchema: z.ZodObject<{
3092
3092
  models: (string | {
3093
3093
  id: string;
3094
3094
  name: string;
3095
- provider: "custom" | "anthropic" | "deepseek" | "openai" | "zhipu" | "ollama";
3095
+ provider: "anthropic" | "deepseek" | "openai" | "zhipu" | "ollama" | "custom";
3096
3096
  apiModel: string;
3097
3097
  maxContextTokens: number;
3098
3098
  capabilities: string[];
@@ -3114,7 +3114,7 @@ export declare const AgentContextConfigSchema: z.ZodObject<{
3114
3114
  priority: number;
3115
3115
  complexity?: "low" | "medium" | "high" | "critical" | undefined;
3116
3116
  keywords?: string[] | undefined;
3117
- taskType?: "planning" | "coding" | "refactoring" | "bug-fix" | "review" | "documentation" | undefined;
3117
+ taskType?: "planning" | "review" | "coding" | "refactoring" | "bug-fix" | "documentation" | undefined;
3118
3118
  }[] | undefined;
3119
3119
  costOptimization?: {
3120
3120
  enabled: boolean;
@@ -3353,7 +3353,7 @@ export declare const AgentContextConfigSchema: z.ZodObject<{
3353
3353
  models?: (string | {
3354
3354
  id: string;
3355
3355
  name: string;
3356
- provider: "custom" | "anthropic" | "deepseek" | "openai" | "zhipu" | "ollama";
3356
+ provider: "anthropic" | "deepseek" | "openai" | "zhipu" | "ollama" | "custom";
3357
3357
  apiModel: string;
3358
3358
  endpoint?: string | undefined;
3359
3359
  apiKeyEnvVar?: string | undefined;
@@ -3373,7 +3373,7 @@ export declare const AgentContextConfigSchema: z.ZodObject<{
3373
3373
  targetRole: "planner" | "executor" | "reviewer" | "fallback";
3374
3374
  complexity?: "low" | "medium" | "high" | "critical" | undefined;
3375
3375
  keywords?: string[] | undefined;
3376
- taskType?: "planning" | "coding" | "refactoring" | "bug-fix" | "review" | "documentation" | undefined;
3376
+ taskType?: "planning" | "review" | "coding" | "refactoring" | "bug-fix" | "documentation" | undefined;
3377
3377
  priority?: number | undefined;
3378
3378
  }[] | undefined;
3379
3379
  costOptimization?: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@miller-tech/uap",
3
- "version": "1.13.12",
3
+ "version": "1.13.13",
4
4
  "description": "Autonomous AI agent memory system with CLAUDE.md protocol enforcement",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -0,0 +1,250 @@
1
+ #!/usr/bin/env bash
2
+ # ============================================================
3
+ # UAP Loop Protection & Token Budget Circuit Breaker
4
+ # ============================================================
5
+ # Shared library sourced by all UAP hooks.
6
+ # Tracks hook invocation frequency and detects runaway loops
7
+ # that waste tokens (and money) by emitting the same system
8
+ # reminders, build-gate warnings, or compliance blocks
9
+ # hundreds of times in a single session.
10
+ #
11
+ # MECHANISM:
12
+ # - Maintains a lightweight state file per session
13
+ # - Counts hook invocations per type within sliding windows
14
+ # - Suppresses redundant output after thresholds are hit
15
+ # - Logs suppressed events for post-mortem analysis
16
+ # - Provides hard circuit-breaker after extreme loop counts
17
+ #
18
+ # USAGE (source from any hook script):
19
+ # source "$(dirname "$0")/loop-protection.sh"
20
+ # if lp_should_suppress "post-tool-use-edit-write"; then
21
+ # exit 0 # skip output, loop detected
22
+ # fi
23
+ # lp_record_invocation "post-tool-use-edit-write"
24
+ #
25
+ # CONFIGURATION (environment variables):
26
+ # UAP_LP_DISABLED=1 Disable loop protection entirely
27
+ # UAP_LP_SOFT_LIMIT=15 Warn after N invocations per hook (default: 15)
28
+ # UAP_LP_HARD_LIMIT=50 Suppress output after N (default: 50)
29
+ # UAP_LP_CIRCUIT_BREAK=200 Hard stop — emit circuit breaker msg (default: 200)
30
+ # UAP_LP_WINDOW_SECS=300 Sliding window in seconds (default: 300 = 5 min)
31
+ # UAP_LP_DEDUP_SECS=5 Min seconds between identical outputs (default: 5)
32
+ # ============================================================
33
+
34
+ # Guard against re-sourcing
35
+ if [ "${_UAP_LOOP_PROTECTION_LOADED:-}" = "1" ]; then
36
+ return 0 2>/dev/null || true
37
+ fi
38
+ _UAP_LOOP_PROTECTION_LOADED=1
39
+
40
+ # --- Configuration ---
41
+ LP_DISABLED="${UAP_LP_DISABLED:-0}"
42
+ LP_SOFT_LIMIT="${UAP_LP_SOFT_LIMIT:-15}"
43
+ LP_HARD_LIMIT="${UAP_LP_HARD_LIMIT:-50}"
44
+ LP_CIRCUIT_BREAK="${UAP_LP_CIRCUIT_BREAK:-200}"
45
+ LP_WINDOW_SECS="${UAP_LP_WINDOW_SECS:-300}"
46
+ LP_DEDUP_SECS="${UAP_LP_DEDUP_SECS:-5}"
47
+
48
+ # --- State file location ---
49
+ LP_PROJECT_DIR="${CLAUDE_PROJECT_DIR:-${FACTORY_PROJECT_DIR:-${CURSOR_PROJECT_DIR:-.}}}"
50
+ LP_STATE_DIR="${LP_PROJECT_DIR}/.uap/loop-protection"
51
+ LP_SESSION_ID="${UAP_SESSION_ID:-${SESSION_ID:-default}}"
52
+ LP_STATE_FILE="${LP_STATE_DIR}/session-${LP_SESSION_ID}.state"
53
+ LP_LOG_FILE="${LP_STATE_DIR}/loop-events.log"
54
+
55
+ # --- Ensure state directory exists ---
56
+ _lp_init() {
57
+ if [ "$LP_DISABLED" = "1" ]; then
58
+ return 0
59
+ fi
60
+ mkdir -p "$LP_STATE_DIR" 2>/dev/null || true
61
+ }
62
+
63
+ # --- Get current epoch seconds (portable) ---
64
+ _lp_now() {
65
+ date +%s 2>/dev/null || echo "0"
66
+ }
67
+
68
+ # --- Read counter for a hook type from state file ---
69
+ # Format: hook_type|count|first_ts|last_ts|suppressed_count
70
+ _lp_read_state() {
71
+ local hook_type="$1"
72
+ if [ ! -f "$LP_STATE_FILE" ]; then
73
+ echo "0|0|0|0"
74
+ return
75
+ fi
76
+ local line
77
+ line=$(grep "^${hook_type}|" "$LP_STATE_FILE" 2>/dev/null | tail -1)
78
+ if [ -z "$line" ]; then
79
+ echo "0|0|0|0"
80
+ return
81
+ fi
82
+ echo "$line" | cut -d'|' -f2-5
83
+ }
84
+
85
+ # --- Write/update counter for a hook type ---
86
+ _lp_write_state() {
87
+ local hook_type="$1"
88
+ local count="$2"
89
+ local first_ts="$3"
90
+ local last_ts="$4"
91
+ local suppressed="$5"
92
+
93
+ # Atomic update: remove old line, append new
94
+ if [ -f "$LP_STATE_FILE" ]; then
95
+ grep -v "^${hook_type}|" "$LP_STATE_FILE" > "${LP_STATE_FILE}.tmp" 2>/dev/null || true
96
+ mv "${LP_STATE_FILE}.tmp" "$LP_STATE_FILE" 2>/dev/null || true
97
+ fi
98
+ echo "${hook_type}|${count}|${first_ts}|${last_ts}|${suppressed}" >> "$LP_STATE_FILE"
99
+ }
100
+
101
+ # --- Log a loop event for post-mortem ---
102
+ _lp_log_event() {
103
+ local level="$1"
104
+ local hook_type="$2"
105
+ local message="$3"
106
+ local now
107
+ now=$(_lp_now)
108
+ echo "${now}|${level}|${hook_type}|${message}" >> "$LP_LOG_FILE" 2>/dev/null || true
109
+ }
110
+
111
+ # ============================================================
112
+ # PUBLIC API
113
+ # ============================================================
114
+
115
+ # Check if a hook invocation should be suppressed.
116
+ # Returns 0 (true/suppress) if the hook has been called too many times.
117
+ # Returns 1 (false/allow) if the hook should proceed normally.
118
+ lp_should_suppress() {
119
+ local hook_type="${1:-unknown}"
120
+
121
+ if [ "$LP_DISABLED" = "1" ]; then
122
+ return 1 # don't suppress
123
+ fi
124
+
125
+ _lp_init
126
+
127
+ local state
128
+ state=$(_lp_read_state "$hook_type")
129
+ local count first_ts last_ts suppressed
130
+ count=$(echo "$state" | cut -d'|' -f1)
131
+ first_ts=$(echo "$state" | cut -d'|' -f2)
132
+ last_ts=$(echo "$state" | cut -d'|' -f3)
133
+ suppressed=$(echo "$state" | cut -d'|' -f4)
134
+
135
+ local now
136
+ now=$(_lp_now)
137
+
138
+ # Reset window if first_ts is too old
139
+ if [ "$first_ts" != "0" ] && [ $((now - first_ts)) -gt "$LP_WINDOW_SECS" ]; then
140
+ count=0
141
+ first_ts="$now"
142
+ suppressed=0
143
+ fi
144
+
145
+ # Dedup: if called within LP_DEDUP_SECS of last call, always suppress
146
+ if [ "$last_ts" != "0" ] && [ $((now - last_ts)) -lt "$LP_DEDUP_SECS" ]; then
147
+ suppressed=$((suppressed + 1))
148
+ _lp_write_state "$hook_type" "$count" "$first_ts" "$now" "$suppressed"
149
+ return 0 # suppress (dedup)
150
+ fi
151
+
152
+ # Check thresholds
153
+ if [ "$count" -ge "$LP_CIRCUIT_BREAK" ]; then
154
+ # Circuit breaker: emit ONE final warning then suppress everything
155
+ if [ "$suppressed" -eq 0 ] || [ $((count % LP_CIRCUIT_BREAK)) -eq 0 ]; then
156
+ _lp_log_event "CIRCUIT_BREAK" "$hook_type" "count=${count} in window"
157
+ fi
158
+ suppressed=$((suppressed + 1))
159
+ _lp_write_state "$hook_type" "$count" "$first_ts" "$now" "$suppressed"
160
+ return 0 # suppress
161
+ fi
162
+
163
+ if [ "$count" -ge "$LP_HARD_LIMIT" ]; then
164
+ # Hard limit: suppress output, log
165
+ if [ $((count % 10)) -eq 0 ]; then
166
+ _lp_log_event "HARD_LIMIT" "$hook_type" "count=${count} suppressed=${suppressed}"
167
+ fi
168
+ suppressed=$((suppressed + 1))
169
+ _lp_write_state "$hook_type" "$count" "$first_ts" "$now" "$suppressed"
170
+ return 0 # suppress
171
+ fi
172
+
173
+ return 1 # allow
174
+ }
175
+
176
+ # Record a hook invocation (call AFTER producing output).
177
+ lp_record_invocation() {
178
+ local hook_type="${1:-unknown}"
179
+
180
+ if [ "$LP_DISABLED" = "1" ]; then
181
+ return 0
182
+ fi
183
+
184
+ _lp_init
185
+
186
+ local state
187
+ state=$(_lp_read_state "$hook_type")
188
+ local count first_ts last_ts suppressed
189
+ count=$(echo "$state" | cut -d'|' -f1)
190
+ first_ts=$(echo "$state" | cut -d'|' -f2)
191
+ last_ts=$(echo "$state" | cut -d'|' -f3)
192
+ suppressed=$(echo "$state" | cut -d'|' -f4)
193
+
194
+ local now
195
+ now=$(_lp_now)
196
+
197
+ # Reset window if expired
198
+ if [ "$first_ts" = "0" ] || [ $((now - first_ts)) -gt "$LP_WINDOW_SECS" ]; then
199
+ count=1
200
+ first_ts="$now"
201
+ suppressed=0
202
+ else
203
+ count=$((count + 1))
204
+ fi
205
+
206
+ _lp_write_state "$hook_type" "$count" "$first_ts" "$now" "$suppressed"
207
+
208
+ # Emit warnings at soft limit
209
+ if [ "$count" -eq "$LP_SOFT_LIMIT" ]; then
210
+ _lp_log_event "SOFT_LIMIT" "$hook_type" "count=${count} - approaching rate limit"
211
+ echo "[UAP-LOOP-PROTECTION] Hook '${hook_type}' has fired ${count} times in ${LP_WINDOW_SECS}s. Output will be suppressed after ${LP_HARD_LIMIT} to prevent token waste." >&2
212
+ fi
213
+ }
214
+
215
+ # Get the circuit breaker warning message (for hooks that want to emit it).
216
+ lp_circuit_breaker_message() {
217
+ local hook_type="${1:-unknown}"
218
+ local state
219
+ state=$(_lp_read_state "$hook_type")
220
+ local count
221
+ count=$(echo "$state" | cut -d'|' -f1)
222
+ local suppressed
223
+ suppressed=$(echo "$state" | cut -d'|' -f4)
224
+
225
+ echo "[UAP-CIRCUIT-BREAKER] Hook '${hook_type}' triggered ${count} times (${suppressed} suppressed). This is a runaway loop. Review your approach — repeated hook warnings are consuming tokens without progress."
226
+ }
227
+
228
+ # Get loop protection stats as a summary string.
229
+ lp_stats() {
230
+ if [ ! -f "$LP_STATE_FILE" ]; then
231
+ echo "No loop protection data for this session."
232
+ return
233
+ fi
234
+ echo "=== UAP Loop Protection Stats ==="
235
+ while IFS='|' read -r hook count first_ts last_ts suppressed; do
236
+ echo " ${hook}: ${count} calls, ${suppressed} suppressed"
237
+ done < "$LP_STATE_FILE"
238
+ echo "================================="
239
+ }
240
+
241
+ # Reset state for a specific hook or all hooks.
242
+ lp_reset() {
243
+ local hook_type="${1:-}"
244
+ if [ -z "$hook_type" ]; then
245
+ rm -f "$LP_STATE_FILE" 2>/dev/null || true
246
+ elif [ -f "$LP_STATE_FILE" ]; then
247
+ grep -v "^${hook_type}|" "$LP_STATE_FILE" > "${LP_STATE_FILE}.tmp" 2>/dev/null || true
248
+ mv "${LP_STATE_FILE}.tmp" "$LP_STATE_FILE" 2>/dev/null || true
249
+ fi
250
+ }
@@ -5,6 +5,15 @@
5
5
  # Always exits 0 (never blocks).
6
6
  set -euo pipefail
7
7
 
8
+ # --- Loop Protection: suppress if compaction is happening in rapid succession ---
9
+ HOOK_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
10
+ if [ -f "${HOOK_DIR}/loop-protection.sh" ]; then
11
+ source "${HOOK_DIR}/loop-protection.sh"
12
+ if lp_should_suppress "post-compact"; then
13
+ exit 0
14
+ fi
15
+ fi
16
+
8
17
  PROJECT_DIR="${CLAUDE_PROJECT_DIR:-${FACTORY_PROJECT_DIR:-${CURSOR_PROJECT_DIR:-.}}}"
9
18
  DB_PATH="${PROJECT_DIR}/agents/data/memory/short_term.db"
10
19
  COORD_DB="${PROJECT_DIR}/agents/data/coordination/coordination.db"
@@ -108,4 +117,9 @@ fi
108
117
 
109
118
  output+="</system-reminder>"$'\n'
110
119
 
120
+ # --- Record invocation for loop tracking ---
121
+ if type lp_record_invocation &>/dev/null; then
122
+ lp_record_invocation "post-compact"
123
+ fi
124
+
111
125
  echo "$output"
@@ -6,6 +6,16 @@
6
6
  # Always exits 0 (never blocks).
7
7
  set -euo pipefail
8
8
 
9
+ # --- Loop Protection: suppress output if firing too fast ---
10
+ HOOK_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
+ if [ -f "${HOOK_DIR}/loop-protection.sh" ]; then
12
+ source "${HOOK_DIR}/loop-protection.sh"
13
+ if lp_should_suppress "post-tool-edit-write"; then
14
+ cat > /dev/null # drain stdin
15
+ exit 0
16
+ fi
17
+ fi
18
+
9
19
  # Read tool input from stdin (JSON)
10
20
  INPUT=$(cat)
11
21
 
@@ -35,4 +45,9 @@ if [ ! -f "${BACKUP_DIR}/${RELATIVE_PATH}" ] 2>/dev/null; then
35
45
  fi
36
46
  fi
37
47
 
48
+ # --- Record invocation for loop tracking ---
49
+ if type lp_record_invocation &>/dev/null; then
50
+ lp_record_invocation "post-tool-edit-write"
51
+ fi
52
+
38
53
  exit 0
@@ -7,6 +7,15 @@
7
7
  # Fails safely - never blocks the agent.
8
8
  set -euo pipefail
9
9
 
10
+ # --- Loop Protection: suppress if compaction is looping ---
11
+ HOOK_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
12
+ if [ -f "${HOOK_DIR}/loop-protection.sh" ]; then
13
+ source "${HOOK_DIR}/loop-protection.sh"
14
+ if lp_should_suppress "pre-compact"; then
15
+ exit 0
16
+ fi
17
+ fi
18
+
10
19
  PROJECT_DIR="${CLAUDE_PROJECT_DIR:-${FACTORY_PROJECT_DIR:-${CURSOR_PROJECT_DIR:-.}}}"
11
20
  DB_PATH="${PROJECT_DIR}/agents/data/memory/short_term.db"
12
21
  COORD_DB="${PROJECT_DIR}/agents/data/coordination/coordination.db"
@@ -5,6 +5,12 @@
5
5
  # Enforces: iac-pipeline-enforcement, worktree-enforcement, git safety policies.
6
6
  set -euo pipefail
7
7
 
8
+ # --- Loop Protection: track frequency of blocking events ---
9
+ HOOK_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
10
+ if [ -f "${HOOK_DIR}/loop-protection.sh" ]; then
11
+ source "${HOOK_DIR}/loop-protection.sh"
12
+ fi
13
+
8
14
  # Read tool input from stdin (JSON)
9
15
  INPUT=$(cat)
10
16
 
@@ -5,6 +5,12 @@
5
5
  # Enforces: worktree-file-guard, worktree-enforcement policies.
6
6
  set -euo pipefail
7
7
 
8
+ # --- Loop Protection: track frequency of blocking events ---
9
+ HOOK_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
10
+ if [ -f "${HOOK_DIR}/loop-protection.sh" ]; then
11
+ source "${HOOK_DIR}/loop-protection.sh"
12
+ fi
13
+
8
14
  # Read tool input from stdin (JSON)
9
15
  INPUT=$(cat)
10
16
 
@@ -40,5 +46,9 @@ if echo "$FILE_PATH" | grep -q '\.worktrees/'; then
40
46
  fi
41
47
 
42
48
  # BLOCK: path is outside worktrees and not exempt
49
+ # Record the block event for loop detection
50
+ if type lp_record_invocation &>/dev/null; then
51
+ lp_record_invocation "pre-tool-edit-block"
52
+ fi
43
53
  echo '{"decision":"block","reason":"WORKTREE POLICY VIOLATION: File path is outside .worktrees/. All edits must target files inside a worktree. Run: uap worktree create <slug> then edit files in .worktrees/NNN-<slug>/. See policies/worktree-file-guard.md"}' >&2
44
54
  exit 2