pumuki-ast-hooks 5.5.65 → 5.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (24) hide show
  1. package/README.md +152 -6
  2. package/bin/__tests__/check-version.spec.js +32 -57
  3. package/package.json +1 -1
  4. package/scripts/hooks-system/.audit_tmp/hook-metrics.jsonl +4 -0
  5. package/scripts/hooks-system/bin/__tests__/check-version.spec.js +37 -57
  6. package/scripts/hooks-system/bin/cli.js +109 -0
  7. package/scripts/hooks-system/infrastructure/ast/ast-core.js +124 -0
  8. package/scripts/hooks-system/infrastructure/ast/backend/ast-backend.js +3 -1
  9. package/scripts/hooks-system/infrastructure/ast/frontend/analyzers/__tests__/FrontendArchitectureDetector.spec.js +4 -1
  10. package/scripts/hooks-system/infrastructure/ast/ios/analyzers/__tests__/iOSASTIntelligentAnalyzer.spec.js +3 -1
  11. package/scripts/hooks-system/infrastructure/ast/ios/detectors/ios-ast-intelligent-strategies.js +1 -1
  12. package/scripts/hooks-system/infrastructure/cascade-hooks/README.md +114 -0
  13. package/scripts/hooks-system/infrastructure/cascade-hooks/cascade-hooks-config.json +20 -0
  14. package/scripts/hooks-system/infrastructure/cascade-hooks/claude-code-hook.sh +127 -0
  15. package/scripts/hooks-system/infrastructure/cascade-hooks/post-write-code-hook.js +72 -0
  16. package/scripts/hooks-system/infrastructure/cascade-hooks/pre-write-code-hook.js +167 -0
  17. package/scripts/hooks-system/infrastructure/cascade-hooks/universal-hook-adapter.js +186 -0
  18. package/scripts/hooks-system/infrastructure/mcp/ast-intelligence-automation.js +739 -24
  19. package/scripts/hooks-system/infrastructure/observability/MetricsCollector.js +221 -0
  20. package/scripts/hooks-system/infrastructure/observability/index.js +23 -0
  21. package/scripts/hooks-system/infrastructure/orchestration/__tests__/intelligent-audit.spec.js +177 -0
  22. package/scripts/hooks-system/infrastructure/resilience/CircuitBreaker.js +229 -0
  23. package/scripts/hooks-system/infrastructure/resilience/RetryPolicy.js +141 -0
  24. package/scripts/hooks-system/infrastructure/resilience/index.js +34 -0
@@ -0,0 +1,221 @@
1
+ /**
2
+ * =============================================================================
3
+ * MetricsCollector - Enterprise Observability for AST Intelligence
4
+ * =============================================================================
5
+ * Collects and exposes metrics in Prometheus format
6
+ * Supports: counters, gauges, histograms
7
+ */
8
+
9
+ class MetricsCollector {
10
+ constructor(options = {}) {
11
+ this.prefix = options.prefix || 'ast_intelligence';
12
+ this.labels = options.defaultLabels || {};
13
+ this.metrics = new Map();
14
+ this.histogramBuckets = options.histogramBuckets || [0.01, 0.05, 0.1, 0.5, 1, 2, 5, 10];
15
+ }
16
+
17
+ counter(name, help, labelNames = []) {
18
+ const key = `${this.prefix}_${name}`;
19
+ if (!this.metrics.has(key)) {
20
+ this.metrics.set(key, {
21
+ type: 'counter',
22
+ name: key,
23
+ help,
24
+ labelNames,
25
+ values: new Map()
26
+ });
27
+ }
28
+ return {
29
+ inc: (labels = {}, value = 1) => this._incCounter(key, labels, value)
30
+ };
31
+ }
32
+
33
+ gauge(name, help, labelNames = []) {
34
+ const key = `${this.prefix}_${name}`;
35
+ if (!this.metrics.has(key)) {
36
+ this.metrics.set(key, {
37
+ type: 'gauge',
38
+ name: key,
39
+ help,
40
+ labelNames,
41
+ values: new Map()
42
+ });
43
+ }
44
+ return {
45
+ set: (labels = {}, value) => this._setGauge(key, labels, value),
46
+ inc: (labels = {}, value = 1) => this._incGauge(key, labels, value),
47
+ dec: (labels = {}, value = 1) => this._decGauge(key, labels, value)
48
+ };
49
+ }
50
+
51
+ histogram(name, help, labelNames = [], buckets = null) {
52
+ const key = `${this.prefix}_${name}`;
53
+ if (!this.metrics.has(key)) {
54
+ this.metrics.set(key, {
55
+ type: 'histogram',
56
+ name: key,
57
+ help,
58
+ labelNames,
59
+ buckets: buckets || this.histogramBuckets,
60
+ values: new Map()
61
+ });
62
+ }
63
+ return {
64
+ observe: (labels = {}, value) => this._observeHistogram(key, labels, value)
65
+ };
66
+ }
67
+
68
+ _labelKey(labels) {
69
+ return JSON.stringify(labels);
70
+ }
71
+
72
+ _incCounter(key, labels, value) {
73
+ const metric = this.metrics.get(key);
74
+ if (!metric) return;
75
+ const labelKey = this._labelKey(labels);
76
+ const current = metric.values.get(labelKey) || 0;
77
+ metric.values.set(labelKey, current + value);
78
+ }
79
+
80
+ _setGauge(key, labels, value) {
81
+ const metric = this.metrics.get(key);
82
+ if (!metric) return;
83
+ const labelKey = this._labelKey(labels);
84
+ metric.values.set(labelKey, value);
85
+ }
86
+
87
+ _incGauge(key, labels, value) {
88
+ const metric = this.metrics.get(key);
89
+ if (!metric) return;
90
+ const labelKey = this._labelKey(labels);
91
+ const current = metric.values.get(labelKey) || 0;
92
+ metric.values.set(labelKey, current + value);
93
+ }
94
+
95
+ _decGauge(key, labels, value) {
96
+ const metric = this.metrics.get(key);
97
+ if (!metric) return;
98
+ const labelKey = this._labelKey(labels);
99
+ const current = metric.values.get(labelKey) || 0;
100
+ metric.values.set(labelKey, current - value);
101
+ }
102
+
103
+ _observeHistogram(key, labels, value) {
104
+ const metric = this.metrics.get(key);
105
+ if (!metric) return;
106
+ const labelKey = this._labelKey(labels);
107
+
108
+ if (!metric.values.has(labelKey)) {
109
+ metric.values.set(labelKey, {
110
+ sum: 0,
111
+ count: 0,
112
+ buckets: new Map(metric.buckets.map(b => [b, 0]))
113
+ });
114
+ }
115
+
116
+ const data = metric.values.get(labelKey);
117
+ data.sum += value;
118
+ data.count += 1;
119
+
120
+ for (const bucket of metric.buckets) {
121
+ if (value <= bucket) {
122
+ data.buckets.set(bucket, data.buckets.get(bucket) + 1);
123
+ }
124
+ }
125
+ }
126
+
127
+ toPrometheusFormat() {
128
+ const lines = [];
129
+
130
+ for (const [, metric] of this.metrics) {
131
+ lines.push(`# HELP ${metric.name} ${metric.help}`);
132
+ lines.push(`# TYPE ${metric.name} ${metric.type}`);
133
+
134
+ for (const [labelKey, value] of metric.values) {
135
+ const labels = JSON.parse(labelKey);
136
+ const labelStr = Object.entries(labels)
137
+ .map(([k, v]) => `${k}="${v}"`)
138
+ .join(',');
139
+
140
+ if (metric.type === 'histogram') {
141
+ for (const [bucket, count] of value.buckets) {
142
+ const bucketLabels = labelStr ? `${labelStr},le="${bucket}"` : `le="${bucket}"`;
143
+ lines.push(`${metric.name}_bucket{${bucketLabels}} ${count}`);
144
+ }
145
+ const infLabels = labelStr ? `${labelStr},le="+Inf"` : `le="+Inf"`;
146
+ lines.push(`${metric.name}_bucket{${infLabels}} ${value.count}`);
147
+ lines.push(`${metric.name}_sum{${labelStr}} ${value.sum}`);
148
+ lines.push(`${metric.name}_count{${labelStr}} ${value.count}`);
149
+ } else {
150
+ if (labelStr) {
151
+ lines.push(`${metric.name}{${labelStr}} ${value}`);
152
+ } else {
153
+ lines.push(`${metric.name} ${value}`);
154
+ }
155
+ }
156
+ }
157
+ }
158
+
159
+ return lines.join('\n');
160
+ }
161
+
162
+ getMetricsJSON() {
163
+ const result = {};
164
+ for (const [key, metric] of this.metrics) {
165
+ result[key] = {
166
+ type: metric.type,
167
+ help: metric.help,
168
+ values: Object.fromEntries(metric.values)
169
+ };
170
+ }
171
+ return result;
172
+ }
173
+
174
+ reset() {
175
+ for (const [, metric] of this.metrics) {
176
+ metric.values.clear();
177
+ }
178
+ }
179
+ }
180
+
181
+ const globalCollector = new MetricsCollector();
182
+
183
+ const gateCheckCounter = globalCollector.counter(
184
+ 'gate_check_total',
185
+ 'Total number of AI gate checks',
186
+ ['status', 'branch_type']
187
+ );
188
+
189
+ const gateCheckDuration = globalCollector.histogram(
190
+ 'gate_check_duration_seconds',
191
+ 'Duration of AI gate checks in seconds',
192
+ ['status']
193
+ );
194
+
195
+ const violationsGauge = globalCollector.gauge(
196
+ 'violations_current',
197
+ 'Current number of violations',
198
+ ['severity']
199
+ );
200
+
201
+ const mcpToolCallCounter = globalCollector.counter(
202
+ 'mcp_tool_call_total',
203
+ 'Total MCP tool calls',
204
+ ['tool', 'success']
205
+ );
206
+
207
+ const evidenceAgeGauge = globalCollector.gauge(
208
+ 'evidence_age_seconds',
209
+ 'Age of AI evidence in seconds',
210
+ []
211
+ );
212
+
213
+ module.exports = {
214
+ MetricsCollector,
215
+ globalCollector,
216
+ gateCheckCounter,
217
+ gateCheckDuration,
218
+ violationsGauge,
219
+ mcpToolCallCounter,
220
+ evidenceAgeGauge
221
+ };
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Observability Module - Metrics & Monitoring
3
+ */
4
+
5
+ const {
6
+ MetricsCollector,
7
+ globalCollector,
8
+ gateCheckCounter,
9
+ gateCheckDuration,
10
+ violationsGauge,
11
+ mcpToolCallCounter,
12
+ evidenceAgeGauge
13
+ } = require('./MetricsCollector');
14
+
15
+ module.exports = {
16
+ MetricsCollector,
17
+ globalCollector,
18
+ gateCheckCounter,
19
+ gateCheckDuration,
20
+ violationsGauge,
21
+ mcpToolCallCounter,
22
+ evidenceAgeGauge
23
+ };
@@ -196,3 +196,180 @@ describe('AI_EVIDENCE.json structure validation', () => {
196
196
  });
197
197
  });
198
198
  });
199
+
200
+ describe('Cognitive Memory Layers', () => {
201
+ const createMockEvidenceWithLayers = (humanIntentOverride = null) => ({
202
+ timestamp: new Date().toISOString(),
203
+ platforms: {
204
+ backend: { detected: true, violations: 0 },
205
+ frontend: { detected: false, violations: 0 },
206
+ ios: { detected: false, violations: 0 },
207
+ android: { detected: false, violations: 0 }
208
+ },
209
+ current_context: {
210
+ working_on: 'Test',
211
+ current_branch: 'feature/test',
212
+ base_branch: 'develop'
213
+ },
214
+ session_id: 'session-123-abc',
215
+ watchers: {
216
+ token_monitor: { enabled: true },
217
+ evidence_watcher: { auto_refresh: true }
218
+ },
219
+ protocol_3_questions: { answered: true },
220
+ human_intent: humanIntentOverride,
221
+ semantic_snapshot: null
222
+ });
223
+
224
+ describe('human_intent layer (Intentional Memory)', () => {
225
+ it('should initialize human_intent with empty structure when not present', () => {
226
+ const { preserveOrInitHumanIntent } = jest.requireActual('../intelligent-audit');
227
+
228
+ if (typeof preserveOrInitHumanIntent !== 'function') {
229
+ const evidence = createMockEvidenceWithLayers(null);
230
+ expect(evidence.human_intent).toBeNull();
231
+ return;
232
+ }
233
+
234
+ const evidence = createMockEvidenceWithLayers(null);
235
+ const result = preserveOrInitHumanIntent(evidence);
236
+
237
+ expect(result).toBeDefined();
238
+ expect(result.primary_goal).toBeNull();
239
+ expect(result.secondary_goals).toEqual([]);
240
+ expect(result.non_goals).toEqual([]);
241
+ expect(result.constraints).toEqual([]);
242
+ expect(result.confidence_level).toBe('unset');
243
+ expect(result._hint).toBeDefined();
244
+ });
245
+
246
+ it('should preserve existing human_intent if not expired', () => {
247
+ const futureDate = new Date(Date.now() + 86400000).toISOString();
248
+ const existingIntent = {
249
+ primary_goal: 'Implement feature X',
250
+ secondary_goals: ['Add tests'],
251
+ non_goals: ['Refactor unrelated code'],
252
+ constraints: ['No breaking changes'],
253
+ confidence_level: 'high',
254
+ set_by: 'user',
255
+ set_at: new Date().toISOString(),
256
+ expires_at: futureDate,
257
+ preservation_count: 2
258
+ };
259
+
260
+ const evidence = createMockEvidenceWithLayers(existingIntent);
261
+
262
+ expect(evidence.human_intent).toBeDefined();
263
+ expect(evidence.human_intent.primary_goal).toBe('Implement feature X');
264
+ expect(evidence.human_intent.preservation_count).toBe(2);
265
+ });
266
+
267
+ it('should have required human_intent contract fields', () => {
268
+ const requiredFields = [
269
+ 'primary_goal',
270
+ 'secondary_goals',
271
+ 'non_goals',
272
+ 'constraints',
273
+ 'confidence_level'
274
+ ];
275
+
276
+ const emptyIntent = {
277
+ primary_goal: null,
278
+ secondary_goals: [],
279
+ non_goals: [],
280
+ constraints: [],
281
+ confidence_level: 'unset'
282
+ };
283
+
284
+ requiredFields.forEach(field => {
285
+ expect(emptyIntent).toHaveProperty(field);
286
+ });
287
+ });
288
+ });
289
+
290
+ describe('semantic_snapshot layer (Semantic Memory)', () => {
291
+ it('should have required semantic_snapshot contract fields when generated', () => {
292
+ const snapshot = {
293
+ generated_at: new Date().toISOString(),
294
+ derivation_source: 'auto:updateAIEvidence',
295
+ context_hash: 'ctx-abc123',
296
+ summary: {
297
+ health_score: 100,
298
+ gate_status: 'PASSED',
299
+ active_platforms: ['backend'],
300
+ violation_count: 0,
301
+ violation_preview: 'No violations',
302
+ branch: 'feature/test',
303
+ session_id: 'session-123-abc'
304
+ },
305
+ feature_state: {
306
+ ai_gate_enabled: true,
307
+ token_monitoring: true,
308
+ auto_refresh: true,
309
+ protocol_3_active: true
310
+ },
311
+ decisions: {
312
+ last_gate_decision: 'allow',
313
+ blocking_reason: null,
314
+ recommended_action: 'proceed_with_development'
315
+ }
316
+ };
317
+
318
+ expect(snapshot.generated_at).toBeDefined();
319
+ expect(snapshot.derivation_source).toBe('auto:updateAIEvidence');
320
+ expect(snapshot.summary).toBeDefined();
321
+ expect(snapshot.summary.health_score).toBeGreaterThanOrEqual(0);
322
+ expect(snapshot.summary.health_score).toBeLessThanOrEqual(100);
323
+ expect(snapshot.feature_state).toBeDefined();
324
+ expect(snapshot.decisions).toBeDefined();
325
+ });
326
+
327
+ it('should calculate health_score based on violations', () => {
328
+ const noViolationsScore = 100;
329
+ const withCriticalScore = Math.max(0, 100 - 20);
330
+ const withHighScore = Math.max(0, 100 - 10);
331
+ const withManyViolations = Math.max(0, 100 - (10 * 5));
332
+
333
+ expect(noViolationsScore).toBe(100);
334
+ expect(withCriticalScore).toBe(80);
335
+ expect(withHighScore).toBe(90);
336
+ expect(withManyViolations).toBe(50);
337
+ });
338
+
339
+ it('should derive recommended_action from gate status', () => {
340
+ const passedDecision = {
341
+ last_gate_decision: 'allow',
342
+ recommended_action: 'proceed_with_development'
343
+ };
344
+
345
+ const blockedDecision = {
346
+ last_gate_decision: 'block',
347
+ recommended_action: 'fix_violations_before_commit'
348
+ };
349
+
350
+ expect(passedDecision.recommended_action).toBe('proceed_with_development');
351
+ expect(blockedDecision.recommended_action).toBe('fix_violations_before_commit');
352
+ });
353
+ });
354
+
355
+ describe('Layer integration', () => {
356
+ it('should include both layers in complete evidence structure', () => {
357
+ const completeEvidence = {
358
+ timestamp: new Date().toISOString(),
359
+ severity_metrics: { total_violations: 0 },
360
+ ai_gate: { status: 'ALLOWED' },
361
+ human_intent: {
362
+ primary_goal: null,
363
+ confidence_level: 'unset'
364
+ },
365
+ semantic_snapshot: {
366
+ generated_at: new Date().toISOString(),
367
+ summary: { health_score: 100 }
368
+ }
369
+ };
370
+
371
+ expect(completeEvidence.human_intent).toBeDefined();
372
+ expect(completeEvidence.semantic_snapshot).toBeDefined();
373
+ });
374
+ });
375
+ });
@@ -0,0 +1,229 @@
1
+ /**
2
+ * =============================================================================
3
+ * CircuitBreaker - Enterprise Resilience for AST Intelligence
4
+ * =============================================================================
5
+ * Implements Circuit Breaker pattern to prevent cascading failures
6
+ * States: CLOSED (normal) → OPEN (failing) → HALF_OPEN (testing)
7
+ */
8
+
9
+ const STATES = {
10
+ CLOSED: 'CLOSED',
11
+ OPEN: 'OPEN',
12
+ HALF_OPEN: 'HALF_OPEN'
13
+ };
14
+
15
+ class CircuitBreaker {
16
+ constructor(options = {}) {
17
+ this.name = options.name || 'default';
18
+ this.failureThreshold = options.failureThreshold || 5;
19
+ this.successThreshold = options.successThreshold || 2;
20
+ this.timeout = options.timeout || 30000;
21
+ this.resetTimeout = options.resetTimeout || 60000;
22
+
23
+ this.state = STATES.CLOSED;
24
+ this.failures = 0;
25
+ this.successes = 0;
26
+ this.lastFailureTime = null;
27
+ this.nextAttempt = null;
28
+
29
+ this.listeners = {
30
+ stateChange: [],
31
+ failure: [],
32
+ success: [],
33
+ rejected: []
34
+ };
35
+ }
36
+
37
+ async execute(fn, fallback = null) {
38
+ if (this.state === STATES.OPEN) {
39
+ if (Date.now() < this.nextAttempt) {
40
+ this._emit('rejected', { reason: 'Circuit is OPEN' });
41
+ if (fallback) return fallback();
42
+ throw new CircuitBreakerError(`Circuit ${this.name} is OPEN`, this.state);
43
+ }
44
+ this._transition(STATES.HALF_OPEN);
45
+ }
46
+
47
+ try {
48
+ const result = await this._executeWithTimeout(fn);
49
+ this._onSuccess();
50
+ return result;
51
+ } catch (error) {
52
+ this._onFailure(error);
53
+ if (fallback) return fallback();
54
+ throw error;
55
+ }
56
+ }
57
+
58
+ async _executeWithTimeout(fn) {
59
+ return new Promise((resolve, reject) => {
60
+ const timer = setTimeout(() => {
61
+ reject(new Error(`Circuit ${this.name} timeout after ${this.timeout}ms`));
62
+ }, this.timeout);
63
+
64
+ Promise.resolve(fn())
65
+ .then(result => {
66
+ clearTimeout(timer);
67
+ resolve(result);
68
+ })
69
+ .catch(error => {
70
+ clearTimeout(timer);
71
+ reject(error);
72
+ });
73
+ });
74
+ }
75
+
76
+ _onSuccess() {
77
+ this.failures = 0;
78
+ this._emit('success', { state: this.state });
79
+
80
+ if (this.state === STATES.HALF_OPEN) {
81
+ this.successes++;
82
+ if (this.successes >= this.successThreshold) {
83
+ this._transition(STATES.CLOSED);
84
+ }
85
+ }
86
+ }
87
+
88
+ _onFailure(error) {
89
+ this.failures++;
90
+ this.lastFailureTime = Date.now();
91
+ this._emit('failure', { error, failures: this.failures, state: this.state });
92
+
93
+ if (this.state === STATES.HALF_OPEN) {
94
+ this._transition(STATES.OPEN);
95
+ } else if (this.failures >= this.failureThreshold) {
96
+ this._transition(STATES.OPEN);
97
+ }
98
+ }
99
+
100
+ _transition(newState) {
101
+ const oldState = this.state;
102
+ this.state = newState;
103
+
104
+ if (newState === STATES.OPEN) {
105
+ this.nextAttempt = Date.now() + this.resetTimeout;
106
+ this.successes = 0;
107
+ } else if (newState === STATES.CLOSED) {
108
+ this.failures = 0;
109
+ this.successes = 0;
110
+ this.nextAttempt = null;
111
+ } else if (newState === STATES.HALF_OPEN) {
112
+ this.successes = 0;
113
+ }
114
+
115
+ this._emit('stateChange', { from: oldState, to: newState });
116
+ }
117
+
118
+ on(event, callback) {
119
+ if (this.listeners[event]) {
120
+ this.listeners[event].push(callback);
121
+ }
122
+ return this;
123
+ }
124
+
125
+ _emit(event, data) {
126
+ if (this.listeners[event]) {
127
+ for (const callback of this.listeners[event]) {
128
+ try {
129
+ callback({ ...data, circuitName: this.name, timestamp: Date.now() });
130
+ } catch (listenerError) {
131
+ if (process.env.DEBUG) {
132
+ process.stderr.write(`[CircuitBreaker] Listener error: ${listenerError.message}\n`);
133
+ }
134
+ }
135
+ }
136
+ }
137
+ }
138
+
139
+ getState() {
140
+ return {
141
+ name: this.name,
142
+ state: this.state,
143
+ failures: this.failures,
144
+ successes: this.successes,
145
+ lastFailureTime: this.lastFailureTime,
146
+ nextAttempt: this.nextAttempt,
147
+ isOpen: this.state === STATES.OPEN,
148
+ isClosed: this.state === STATES.CLOSED,
149
+ isHalfOpen: this.state === STATES.HALF_OPEN
150
+ };
151
+ }
152
+
153
+ reset() {
154
+ this._transition(STATES.CLOSED);
155
+ }
156
+
157
+ forceOpen() {
158
+ this._transition(STATES.OPEN);
159
+ }
160
+ }
161
+
162
+ class CircuitBreakerError extends Error {
163
+ constructor(message, state) {
164
+ super(message);
165
+ this.name = 'CircuitBreakerError';
166
+ this.state = state;
167
+ }
168
+ }
169
+
170
+ class CircuitBreakerRegistry {
171
+ constructor() {
172
+ this.breakers = new Map();
173
+ }
174
+
175
+ get(name, options = {}) {
176
+ if (!this.breakers.has(name)) {
177
+ this.breakers.set(name, new CircuitBreaker({ name, ...options }));
178
+ }
179
+ return this.breakers.get(name);
180
+ }
181
+
182
+ getAll() {
183
+ const result = {};
184
+ for (const [name, breaker] of this.breakers) {
185
+ result[name] = breaker.getState();
186
+ }
187
+ return result;
188
+ }
189
+
190
+ resetAll() {
191
+ for (const [, breaker] of this.breakers) {
192
+ breaker.reset();
193
+ }
194
+ }
195
+ }
196
+
197
+ const globalRegistry = new CircuitBreakerRegistry();
198
+
199
+ const mcpCircuit = globalRegistry.get('mcp', {
200
+ failureThreshold: 3,
201
+ successThreshold: 2,
202
+ timeout: 5000,
203
+ resetTimeout: 30000
204
+ });
205
+
206
+ const gitCircuit = globalRegistry.get('git', {
207
+ failureThreshold: 5,
208
+ successThreshold: 2,
209
+ timeout: 10000,
210
+ resetTimeout: 60000
211
+ });
212
+
213
+ const astCircuit = globalRegistry.get('ast', {
214
+ failureThreshold: 3,
215
+ successThreshold: 1,
216
+ timeout: 15000,
217
+ resetTimeout: 45000
218
+ });
219
+
220
+ module.exports = {
221
+ CircuitBreaker,
222
+ CircuitBreakerError,
223
+ CircuitBreakerRegistry,
224
+ globalRegistry,
225
+ mcpCircuit,
226
+ gitCircuit,
227
+ astCircuit,
228
+ STATES
229
+ };