pumuki-ast-hooks 5.5.60 → 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.
- package/README.md +361 -1101
- package/bin/__tests__/check-version.spec.js +32 -57
- package/docs/ARCHITECTURE.md +66 -1
- package/docs/TODO.md +41 -0
- package/docs/images/ast_intelligence_01.svg +40 -0
- package/docs/images/ast_intelligence_02.svg +39 -0
- package/docs/images/ast_intelligence_03.svg +55 -0
- package/docs/images/ast_intelligence_04.svg +39 -0
- package/docs/images/ast_intelligence_05.svg +45 -0
- package/docs/images/logo.png +0 -0
- package/package.json +1 -1
- package/scripts/hooks-system/.audit_tmp/hook-metrics.jsonl +20 -0
- package/scripts/hooks-system/application/DIValidationService.js +43 -0
- package/scripts/hooks-system/application/__tests__/DIValidationService.spec.js +81 -0
- package/scripts/hooks-system/bin/__tests__/check-version.spec.js +37 -57
- package/scripts/hooks-system/bin/cli.js +109 -0
- package/scripts/hooks-system/config/di-rules.json +42 -0
- package/scripts/hooks-system/domain/ports/FileSystemPort.js +19 -0
- package/scripts/hooks-system/domain/strategies/ConcreteDependencyStrategy.js +78 -0
- package/scripts/hooks-system/domain/strategies/DIStrategy.js +31 -0
- package/scripts/hooks-system/infrastructure/adapters/NodeFileSystemAdapter.js +28 -0
- package/scripts/hooks-system/infrastructure/ast/ast-core.js +124 -0
- package/scripts/hooks-system/infrastructure/ast/backend/ast-backend.js +19 -1
- package/scripts/hooks-system/infrastructure/ast/backend/detectors/god-class-detector.js +28 -8
- package/scripts/hooks-system/infrastructure/ast/common/ast-common.js +133 -0
- package/scripts/hooks-system/infrastructure/ast/frontend/analyzers/__tests__/FrontendArchitectureDetector.spec.js +4 -1
- package/scripts/hooks-system/infrastructure/ast/ios/analyzers/__tests__/iOSASTIntelligentAnalyzer.spec.js +3 -1
- package/scripts/hooks-system/infrastructure/ast/ios/analyzers/iOSASTIntelligentAnalyzer.js +3 -2
- package/scripts/hooks-system/infrastructure/ast/ios/ast-ios.js +1 -1
- package/scripts/hooks-system/infrastructure/ast/ios/detectors/ios-ast-intelligent-strategies.js +40 -46
- package/scripts/hooks-system/infrastructure/cascade-hooks/README.md +114 -0
- package/scripts/hooks-system/infrastructure/cascade-hooks/cascade-hooks-config.json +20 -0
- package/scripts/hooks-system/infrastructure/cascade-hooks/claude-code-hook.sh +127 -0
- package/scripts/hooks-system/infrastructure/cascade-hooks/post-write-code-hook.js +72 -0
- package/scripts/hooks-system/infrastructure/cascade-hooks/pre-write-code-hook.js +167 -0
- package/scripts/hooks-system/infrastructure/cascade-hooks/universal-hook-adapter.js +186 -0
- package/scripts/hooks-system/infrastructure/mcp/ast-intelligence-automation.js +739 -24
- package/scripts/hooks-system/infrastructure/observability/MetricsCollector.js +221 -0
- package/scripts/hooks-system/infrastructure/observability/index.js +23 -0
- package/scripts/hooks-system/infrastructure/orchestration/__tests__/intelligent-audit.spec.js +177 -0
- package/scripts/hooks-system/infrastructure/orchestration/intelligent-audit.js +87 -1
- package/scripts/hooks-system/infrastructure/registry/StrategyRegistry.js +63 -0
- package/scripts/hooks-system/infrastructure/resilience/CircuitBreaker.js +229 -0
- package/scripts/hooks-system/infrastructure/resilience/RetryPolicy.js +141 -0
- package/scripts/hooks-system/infrastructure/resilience/index.js +34 -0
|
@@ -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
|
+
};
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* =============================================================================
|
|
3
|
+
* RetryPolicy - Enterprise Resilience for AST Intelligence
|
|
4
|
+
* =============================================================================
|
|
5
|
+
* Configurable retry strategies with exponential backoff, jitter, and hooks
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
class RetryPolicy {
|
|
9
|
+
constructor(options = {}) {
|
|
10
|
+
this.maxRetries = options.maxRetries ?? 3;
|
|
11
|
+
this.baseDelay = options.baseDelay ?? 1000;
|
|
12
|
+
this.maxDelay = options.maxDelay ?? 30000;
|
|
13
|
+
this.backoffMultiplier = options.backoffMultiplier ?? 2;
|
|
14
|
+
this.jitter = options.jitter ?? true;
|
|
15
|
+
this.retryOn = options.retryOn || this._defaultRetryOn;
|
|
16
|
+
this.onRetry = options.onRetry || (() => { });
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
_defaultRetryOn(error) {
|
|
20
|
+
if (error.name === 'CircuitBreakerError') return false;
|
|
21
|
+
if (error.code === 'ECONNREFUSED') return true;
|
|
22
|
+
if (error.code === 'ETIMEDOUT') return true;
|
|
23
|
+
if (error.code === 'ENOTFOUND') return true;
|
|
24
|
+
if (error.message?.includes('timeout')) return true;
|
|
25
|
+
if (error.message?.includes('rate limit')) return true;
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
_calculateDelay(attempt) {
|
|
30
|
+
let delay = this.baseDelay * Math.pow(this.backoffMultiplier, attempt);
|
|
31
|
+
delay = Math.min(delay, this.maxDelay);
|
|
32
|
+
|
|
33
|
+
if (this.jitter) {
|
|
34
|
+
const jitterRange = delay * 0.2;
|
|
35
|
+
delay = delay - jitterRange + (Math.random() * jitterRange * 2);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return Math.round(delay);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async execute(fn) {
|
|
42
|
+
let lastError;
|
|
43
|
+
|
|
44
|
+
for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
|
|
45
|
+
try {
|
|
46
|
+
return await fn();
|
|
47
|
+
} catch (error) {
|
|
48
|
+
lastError = error;
|
|
49
|
+
|
|
50
|
+
if (attempt === this.maxRetries) {
|
|
51
|
+
throw new RetryExhaustedError(
|
|
52
|
+
`All ${this.maxRetries + 1} attempts failed`,
|
|
53
|
+
lastError,
|
|
54
|
+
attempt + 1
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const shouldRetry = typeof this.retryOn === 'function'
|
|
59
|
+
? this.retryOn(error, attempt)
|
|
60
|
+
: true;
|
|
61
|
+
|
|
62
|
+
if (!shouldRetry) {
|
|
63
|
+
throw error;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const delay = this._calculateDelay(attempt);
|
|
67
|
+
|
|
68
|
+
this.onRetry({
|
|
69
|
+
attempt: attempt + 1,
|
|
70
|
+
maxRetries: this.maxRetries,
|
|
71
|
+
delay,
|
|
72
|
+
error: error.message
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
await this._sleep(delay);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
throw lastError;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
_sleep(ms) {
|
|
83
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
static immediate(maxRetries = 3) {
|
|
87
|
+
return new RetryPolicy({
|
|
88
|
+
maxRetries,
|
|
89
|
+
baseDelay: 0,
|
|
90
|
+
jitter: false
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
static linear(maxRetries = 3, delay = 1000) {
|
|
95
|
+
return new RetryPolicy({
|
|
96
|
+
maxRetries,
|
|
97
|
+
baseDelay: delay,
|
|
98
|
+
backoffMultiplier: 1,
|
|
99
|
+
jitter: false
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
static exponential(maxRetries = 3, baseDelay = 1000) {
|
|
104
|
+
return new RetryPolicy({
|
|
105
|
+
maxRetries,
|
|
106
|
+
baseDelay,
|
|
107
|
+
backoffMultiplier: 2,
|
|
108
|
+
jitter: true
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
static aggressive(maxRetries = 5, baseDelay = 500) {
|
|
113
|
+
return new RetryPolicy({
|
|
114
|
+
maxRetries,
|
|
115
|
+
baseDelay,
|
|
116
|
+
backoffMultiplier: 1.5,
|
|
117
|
+
maxDelay: 10000,
|
|
118
|
+
jitter: true
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
class RetryExhaustedError extends Error {
|
|
124
|
+
constructor(message, lastError, attempts) {
|
|
125
|
+
super(message);
|
|
126
|
+
this.name = 'RetryExhaustedError';
|
|
127
|
+
this.lastError = lastError;
|
|
128
|
+
this.attempts = attempts;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async function withRetry(fn, options = {}) {
|
|
133
|
+
const policy = new RetryPolicy(options);
|
|
134
|
+
return policy.execute(fn);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
module.exports = {
|
|
138
|
+
RetryPolicy,
|
|
139
|
+
RetryExhaustedError,
|
|
140
|
+
withRetry
|
|
141
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resilience Module - Circuit Breaker & Retry Policies
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const {
|
|
6
|
+
CircuitBreaker,
|
|
7
|
+
CircuitBreakerError,
|
|
8
|
+
CircuitBreakerRegistry,
|
|
9
|
+
globalRegistry,
|
|
10
|
+
mcpCircuit,
|
|
11
|
+
gitCircuit,
|
|
12
|
+
astCircuit,
|
|
13
|
+
STATES
|
|
14
|
+
} = require('./CircuitBreaker');
|
|
15
|
+
|
|
16
|
+
const {
|
|
17
|
+
RetryPolicy,
|
|
18
|
+
RetryExhaustedError,
|
|
19
|
+
withRetry
|
|
20
|
+
} = require('./RetryPolicy');
|
|
21
|
+
|
|
22
|
+
module.exports = {
|
|
23
|
+
CircuitBreaker,
|
|
24
|
+
CircuitBreakerError,
|
|
25
|
+
CircuitBreakerRegistry,
|
|
26
|
+
globalRegistry,
|
|
27
|
+
mcpCircuit,
|
|
28
|
+
gitCircuit,
|
|
29
|
+
astCircuit,
|
|
30
|
+
STATES,
|
|
31
|
+
RetryPolicy,
|
|
32
|
+
RetryExhaustedError,
|
|
33
|
+
withRetry
|
|
34
|
+
};
|