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.
Files changed (45) hide show
  1. package/README.md +361 -1101
  2. package/bin/__tests__/check-version.spec.js +32 -57
  3. package/docs/ARCHITECTURE.md +66 -1
  4. package/docs/TODO.md +41 -0
  5. package/docs/images/ast_intelligence_01.svg +40 -0
  6. package/docs/images/ast_intelligence_02.svg +39 -0
  7. package/docs/images/ast_intelligence_03.svg +55 -0
  8. package/docs/images/ast_intelligence_04.svg +39 -0
  9. package/docs/images/ast_intelligence_05.svg +45 -0
  10. package/docs/images/logo.png +0 -0
  11. package/package.json +1 -1
  12. package/scripts/hooks-system/.audit_tmp/hook-metrics.jsonl +20 -0
  13. package/scripts/hooks-system/application/DIValidationService.js +43 -0
  14. package/scripts/hooks-system/application/__tests__/DIValidationService.spec.js +81 -0
  15. package/scripts/hooks-system/bin/__tests__/check-version.spec.js +37 -57
  16. package/scripts/hooks-system/bin/cli.js +109 -0
  17. package/scripts/hooks-system/config/di-rules.json +42 -0
  18. package/scripts/hooks-system/domain/ports/FileSystemPort.js +19 -0
  19. package/scripts/hooks-system/domain/strategies/ConcreteDependencyStrategy.js +78 -0
  20. package/scripts/hooks-system/domain/strategies/DIStrategy.js +31 -0
  21. package/scripts/hooks-system/infrastructure/adapters/NodeFileSystemAdapter.js +28 -0
  22. package/scripts/hooks-system/infrastructure/ast/ast-core.js +124 -0
  23. package/scripts/hooks-system/infrastructure/ast/backend/ast-backend.js +19 -1
  24. package/scripts/hooks-system/infrastructure/ast/backend/detectors/god-class-detector.js +28 -8
  25. package/scripts/hooks-system/infrastructure/ast/common/ast-common.js +133 -0
  26. package/scripts/hooks-system/infrastructure/ast/frontend/analyzers/__tests__/FrontendArchitectureDetector.spec.js +4 -1
  27. package/scripts/hooks-system/infrastructure/ast/ios/analyzers/__tests__/iOSASTIntelligentAnalyzer.spec.js +3 -1
  28. package/scripts/hooks-system/infrastructure/ast/ios/analyzers/iOSASTIntelligentAnalyzer.js +3 -2
  29. package/scripts/hooks-system/infrastructure/ast/ios/ast-ios.js +1 -1
  30. package/scripts/hooks-system/infrastructure/ast/ios/detectors/ios-ast-intelligent-strategies.js +40 -46
  31. package/scripts/hooks-system/infrastructure/cascade-hooks/README.md +114 -0
  32. package/scripts/hooks-system/infrastructure/cascade-hooks/cascade-hooks-config.json +20 -0
  33. package/scripts/hooks-system/infrastructure/cascade-hooks/claude-code-hook.sh +127 -0
  34. package/scripts/hooks-system/infrastructure/cascade-hooks/post-write-code-hook.js +72 -0
  35. package/scripts/hooks-system/infrastructure/cascade-hooks/pre-write-code-hook.js +167 -0
  36. package/scripts/hooks-system/infrastructure/cascade-hooks/universal-hook-adapter.js +186 -0
  37. package/scripts/hooks-system/infrastructure/mcp/ast-intelligence-automation.js +739 -24
  38. package/scripts/hooks-system/infrastructure/observability/MetricsCollector.js +221 -0
  39. package/scripts/hooks-system/infrastructure/observability/index.js +23 -0
  40. package/scripts/hooks-system/infrastructure/orchestration/__tests__/intelligent-audit.spec.js +177 -0
  41. package/scripts/hooks-system/infrastructure/orchestration/intelligent-audit.js +87 -1
  42. package/scripts/hooks-system/infrastructure/registry/StrategyRegistry.js +63 -0
  43. package/scripts/hooks-system/infrastructure/resilience/CircuitBreaker.js +229 -0
  44. package/scripts/hooks-system/infrastructure/resilience/RetryPolicy.js +141 -0
  45. 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
+ });
@@ -25,6 +25,88 @@ function deriveCategoryFromRuleId(ruleId) {
25
25
  return parts[0] || 'unknown';
26
26
  }
27
27
 
28
+ /**
29
+ * Generate semantic_snapshot automatically from current evidence state.
30
+ * This is the Semantic Memory Layer - derived, never manually input.
31
+ */
32
+ function generateSemanticSnapshot(evidence, violations, gateResult) {
33
+ const now = new Date();
34
+ const activePlatforms = Object.entries(evidence.platforms || {})
35
+ .filter(([, v]) => v.detected)
36
+ .map(([k]) => k);
37
+
38
+ const violationSummary = violations.length > 0
39
+ ? violations.slice(0, 5).map(v => `${v.severity}: ${v.ruleId || v.rule || 'unknown'}`).join('; ')
40
+ : 'No violations';
41
+
42
+ const healthScore = Math.max(0, 100 - (violations.length * 5) -
43
+ (violations.filter(v => v.severity === 'CRITICAL').length * 20) -
44
+ (violations.filter(v => v.severity === 'HIGH').length * 10));
45
+
46
+ return {
47
+ generated_at: now.toISOString(),
48
+ derivation_source: 'auto:updateAIEvidence',
49
+ context_hash: `ctx-${Date.now().toString(36)}`,
50
+ summary: {
51
+ health_score: healthScore,
52
+ gate_status: gateResult.passed ? 'PASSED' : 'FAILED',
53
+ active_platforms: activePlatforms,
54
+ violation_count: violations.length,
55
+ violation_preview: violationSummary,
56
+ branch: evidence.current_context?.current_branch || 'unknown',
57
+ session_id: evidence.session_id || 'unknown'
58
+ },
59
+ feature_state: {
60
+ ai_gate_enabled: true,
61
+ token_monitoring: evidence.watchers?.token_monitor?.enabled ?? true,
62
+ auto_refresh: evidence.watchers?.evidence_watcher?.auto_refresh ?? true,
63
+ protocol_3_active: evidence.protocol_3_questions?.answered ?? false
64
+ },
65
+ decisions: {
66
+ last_gate_decision: gateResult.passed ? 'allow' : 'block',
67
+ blocking_reason: gateResult.blockedBy || null,
68
+ recommended_action: gateResult.passed
69
+ ? 'proceed_with_development'
70
+ : 'fix_violations_before_commit'
71
+ }
72
+ };
73
+ }
74
+
75
+ /**
76
+ * Preserve or initialize human_intent layer.
77
+ * This is the Intentional Memory Layer - set by human, preserved across updates.
78
+ */
79
+ function preserveOrInitHumanIntent(existingEvidence) {
80
+ const existing = existingEvidence.human_intent;
81
+
82
+ if (existing && typeof existing === 'object' && existing.primary_goal) {
83
+ const expiresAt = existing.expires_at ? new Date(existing.expires_at) : null;
84
+ const isExpired = expiresAt && expiresAt < new Date();
85
+
86
+ if (!isExpired) {
87
+ return {
88
+ ...existing,
89
+ preserved_at: new Date().toISOString(),
90
+ preservation_count: (existing.preservation_count || 0) + 1
91
+ };
92
+ }
93
+ }
94
+
95
+ return {
96
+ primary_goal: null,
97
+ secondary_goals: [],
98
+ non_goals: [],
99
+ constraints: [],
100
+ confidence_level: 'unset',
101
+ set_by: null,
102
+ set_at: null,
103
+ expires_at: null,
104
+ preserved_at: new Date().toISOString(),
105
+ preservation_count: 0,
106
+ _hint: 'Set via CLI: ast-hooks intent --goal "your goal" or manually edit this file'
107
+ };
108
+ }
109
+
28
110
  function detectPlatformsFromStagedFiles(stagedFiles) {
29
111
  const platforms = new Set();
30
112
  const files = Array.isArray(stagedFiles) ? stagedFiles : [];
@@ -669,8 +751,12 @@ async function updateAIEvidence(violations, gateResult, tokenUsage) {
669
751
  }
670
752
  };
671
753
 
754
+ evidence.human_intent = preserveOrInitHumanIntent(evidence);
755
+
756
+ evidence.semantic_snapshot = generateSemanticSnapshot(evidence, violations, gateResult);
757
+
672
758
  fs.writeFileSync(evidencePath, JSON.stringify(evidence, null, 2));
673
- console.log('[Intelligent Audit] ✅ .AI_EVIDENCE.json updated with complete format (ai_gate, severity_metrics, token_usage, git_flow, watchers)');
759
+ console.log('[Intelligent Audit] ✅ .AI_EVIDENCE.json updated with complete format (ai_gate, severity_metrics, token_usage, git_flow, watchers, human_intent, semantic_snapshot)');
674
760
 
675
761
  const MacNotificationSender = require('../../application/services/notification/MacNotificationSender');
676
762
  const notificationSender = new MacNotificationSender(null);
@@ -0,0 +1,63 @@
1
+ const path = require('path');
2
+
3
+ class StrategyRegistry {
4
+ constructor(fileSystemPort, configPath) {
5
+ this.strategies = new Map();
6
+ this.fileSystemPort = fileSystemPort;
7
+ this.configPath = configPath;
8
+ this.config = null;
9
+ }
10
+
11
+ async loadStrategies() {
12
+ this.config = await this._loadConfig();
13
+ const strategiesDir = this.fileSystemPort.resolvePath(__dirname, '../..', 'domain', 'strategies');
14
+
15
+ const exists = await this.fileSystemPort.exists(strategiesDir);
16
+ if (!exists) {
17
+ throw new Error(`Strategies directory not found at ${strategiesDir}`);
18
+ }
19
+
20
+ const files = await this.fileSystemPort.readDir(strategiesDir);
21
+
22
+ for (const file of files) {
23
+ if (file.endsWith('Strategy.js') && !file.includes('DIStrategy.js')) {
24
+ const strategyPath = path.join(strategiesDir, file);
25
+ const StrategyClass = require(strategyPath);
26
+
27
+ if (typeof StrategyClass === 'function') {
28
+ const strategy = new StrategyClass(this.config.dependencyInjection);
29
+ this.strategies.set(strategy.id, strategy);
30
+ }
31
+ }
32
+ }
33
+ }
34
+
35
+ getStrategy(id) {
36
+ return this.strategies.get(id);
37
+ }
38
+
39
+ getAllStrategies() {
40
+ return Array.from(this.strategies.values());
41
+ }
42
+
43
+ findStrategiesForNode(node, context) {
44
+ return this.getAllStrategies().filter(strategy =>
45
+ strategy.canHandle(node, context)
46
+ );
47
+ }
48
+
49
+ async _loadConfig() {
50
+ try {
51
+ const configContent = await this.fileSystemPort.readFile(
52
+ this.configPath,
53
+ 'utf8'
54
+ );
55
+ return JSON.parse(configContent);
56
+ } catch (error) {
57
+ console.error(`Failed to load config from ${this.configPath}:`, error);
58
+ return { dependencyInjection: {} };
59
+ }
60
+ }
61
+ }
62
+
63
+ module.exports = StrategyRegistry;