aios-core 4.2.4 → 4.2.6

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.
@@ -0,0 +1,280 @@
1
+ 'use strict';
2
+
3
+ const { CodeGraphProvider } = require('./providers/code-graph-provider');
4
+
5
+ // --- Constants (adjustable, not hardcoded magic numbers) ---
6
+ const CIRCUIT_BREAKER_THRESHOLD = 3;
7
+ const CIRCUIT_BREAKER_RESET_MS = 60000;
8
+ const CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes
9
+
10
+ // Circuit breaker states
11
+ const CB_CLOSED = 'CLOSED';
12
+ const CB_OPEN = 'OPEN';
13
+ const CB_HALF_OPEN = 'HALF-OPEN';
14
+
15
+ /**
16
+ * CodeIntelClient — Central entry point for code intelligence.
17
+ *
18
+ * Features:
19
+ * - Provider auto-detection and registry
20
+ * - Circuit breaker pattern (threshold 3, reset 60s)
21
+ * - Session cache (Map-based, TTL 5min)
22
+ * - Graceful fallback (returns null without throw when no provider available)
23
+ * - Latency logging per capability
24
+ * - Cache hit/miss counters
25
+ */
26
+ class CodeIntelClient {
27
+ constructor(options = {}) {
28
+ this._providers = [];
29
+ this._activeProvider = null;
30
+ this._options = options;
31
+
32
+ // Circuit breaker state
33
+ this._cbState = CB_CLOSED;
34
+ this._cbFailures = 0;
35
+ this._cbOpenedAt = null;
36
+
37
+ // Session cache
38
+ this._cache = new Map();
39
+
40
+ // Metrics
41
+ this._cacheHits = 0;
42
+ this._cacheMisses = 0;
43
+ this._latencyLog = [];
44
+
45
+ // Warning dedup
46
+ this._noProviderWarned = false;
47
+
48
+ // Auto-register default providers
49
+ this._registerDefaultProviders(options);
50
+ }
51
+
52
+ /**
53
+ * Register default providers based on configuration.
54
+ * @private
55
+ */
56
+ _registerDefaultProviders(options) {
57
+ // Code Graph MCP is the primary (and currently only) provider
58
+ const codeGraphProvider = new CodeGraphProvider({
59
+ mcpServerName: options.mcpServerName || 'code-graph',
60
+ mcpCallFn: options.mcpCallFn || null,
61
+ });
62
+ this._providers.push(codeGraphProvider);
63
+ }
64
+
65
+ /**
66
+ * Register an additional provider.
67
+ * @param {import('./providers/provider-interface').CodeIntelProvider} provider
68
+ */
69
+ registerProvider(provider) {
70
+ this._providers.push(provider);
71
+ // Reset active provider so next call re-detects
72
+ this._activeProvider = null;
73
+ }
74
+
75
+ /**
76
+ * Detect and return the first available provider.
77
+ * @returns {import('./providers/provider-interface').CodeIntelProvider|null}
78
+ * @private
79
+ */
80
+ _detectProvider() {
81
+ if (this._activeProvider) return this._activeProvider;
82
+
83
+ for (const provider of this._providers) {
84
+ // A provider is considered "available" if it has a configured mcpCallFn
85
+ if (provider.options && typeof provider.options.mcpCallFn === 'function') {
86
+ this._activeProvider = provider;
87
+ return provider;
88
+ }
89
+ }
90
+
91
+ return null;
92
+ }
93
+
94
+ /**
95
+ * Check if code intelligence is available.
96
+ * @returns {boolean}
97
+ */
98
+ isCodeIntelAvailable() {
99
+ return this._detectProvider() !== null;
100
+ }
101
+
102
+ /**
103
+ * Execute a capability with circuit breaker, cache, and fallback.
104
+ * @param {string} capability - One of the 8 primitive capability names
105
+ * @param {Array} args - Arguments to pass to the capability
106
+ * @returns {Promise<*>} Result or null on fallback
107
+ */
108
+ async _executeCapability(capability, args) {
109
+ const startTime = Date.now();
110
+
111
+ // Check provider availability
112
+ const provider = this._detectProvider();
113
+ if (!provider) {
114
+ if (!this._noProviderWarned) {
115
+ console.warn('[code-intel] No provider available. Code intelligence features disabled.');
116
+ this._noProviderWarned = true;
117
+ }
118
+ return null;
119
+ }
120
+
121
+ // Check cache
122
+ const cacheKey = `${capability}:${JSON.stringify(args)}`;
123
+ const cached = this._getFromCache(cacheKey);
124
+ if (cached !== undefined) {
125
+ this._cacheHits++;
126
+ this._logLatency(capability, Date.now() - startTime, true);
127
+ return cached;
128
+ }
129
+ this._cacheMisses++;
130
+
131
+ // Check circuit breaker
132
+ if (this._cbState === CB_OPEN) {
133
+ if (Date.now() - this._cbOpenedAt >= CIRCUIT_BREAKER_RESET_MS) {
134
+ this._cbState = CB_HALF_OPEN;
135
+ } else {
136
+ this._logLatency(capability, Date.now() - startTime, false);
137
+ return null;
138
+ }
139
+ }
140
+
141
+ // Execute capability
142
+ try {
143
+ const result = await provider[capability](...args);
144
+ this._onSuccess();
145
+ this._putInCache(cacheKey, result);
146
+ this._logLatency(capability, Date.now() - startTime, false);
147
+ return result;
148
+ } catch (_error) {
149
+ this._onFailure();
150
+ this._logLatency(capability, Date.now() - startTime, false);
151
+ return null;
152
+ }
153
+ }
154
+
155
+ // --- Circuit breaker helpers ---
156
+
157
+ _onSuccess() {
158
+ this._cbFailures = 0;
159
+ if (this._cbState === CB_HALF_OPEN) {
160
+ this._cbState = CB_CLOSED;
161
+ }
162
+ }
163
+
164
+ _onFailure() {
165
+ this._cbFailures++;
166
+ if (this._cbFailures >= CIRCUIT_BREAKER_THRESHOLD) {
167
+ this._cbState = CB_OPEN;
168
+ this._cbOpenedAt = Date.now();
169
+ }
170
+ }
171
+
172
+ getCircuitBreakerState() {
173
+ // Re-check if open timer expired
174
+ if (this._cbState === CB_OPEN && Date.now() - this._cbOpenedAt >= CIRCUIT_BREAKER_RESET_MS) {
175
+ this._cbState = CB_HALF_OPEN;
176
+ }
177
+ return this._cbState;
178
+ }
179
+
180
+ // --- Cache helpers ---
181
+
182
+ _getFromCache(key) {
183
+ const entry = this._cache.get(key);
184
+ if (!entry) return undefined;
185
+ if (Date.now() - entry.timestamp > CACHE_TTL_MS) {
186
+ this._cache.delete(key);
187
+ return undefined;
188
+ }
189
+ return entry.value;
190
+ }
191
+
192
+ _putInCache(key, value) {
193
+ // Evict expired entries periodically (every 50 puts)
194
+ if (this._cache.size > 0 && this._cache.size % 50 === 0) {
195
+ this._evictExpired();
196
+ }
197
+ this._cache.set(key, { value, timestamp: Date.now() });
198
+ }
199
+
200
+ _evictExpired() {
201
+ const now = Date.now();
202
+ for (const [key, entry] of this._cache) {
203
+ if (now - entry.timestamp > CACHE_TTL_MS) {
204
+ this._cache.delete(key);
205
+ }
206
+ }
207
+ }
208
+
209
+ // --- Latency logging ---
210
+
211
+ _logLatency(capability, durationMs, isCacheHit) {
212
+ this._latencyLog.push({
213
+ capability,
214
+ durationMs,
215
+ isCacheHit,
216
+ timestamp: Date.now(),
217
+ });
218
+ }
219
+
220
+ // --- Metrics ---
221
+
222
+ getMetrics() {
223
+ return {
224
+ cacheHits: this._cacheHits,
225
+ cacheMisses: this._cacheMisses,
226
+ cacheHitRate:
227
+ this._cacheHits + this._cacheMisses > 0
228
+ ? this._cacheHits / (this._cacheHits + this._cacheMisses)
229
+ : 0,
230
+ circuitBreakerState: this.getCircuitBreakerState(),
231
+ latencyLog: this._latencyLog,
232
+ providerAvailable: this.isCodeIntelAvailable(),
233
+ activeProvider: this._activeProvider ? this._activeProvider.name : null,
234
+ };
235
+ }
236
+
237
+ // --- 8 Primitive Capabilities (public API) ---
238
+
239
+ async findDefinition(symbol, options) {
240
+ return this._executeCapability('findDefinition', [symbol, options]);
241
+ }
242
+
243
+ async findReferences(symbol, options) {
244
+ return this._executeCapability('findReferences', [symbol, options]);
245
+ }
246
+
247
+ async findCallers(symbol, options) {
248
+ return this._executeCapability('findCallers', [symbol, options]);
249
+ }
250
+
251
+ async findCallees(symbol, options) {
252
+ return this._executeCapability('findCallees', [symbol, options]);
253
+ }
254
+
255
+ async analyzeDependencies(path, options) {
256
+ return this._executeCapability('analyzeDependencies', [path, options]);
257
+ }
258
+
259
+ async analyzeComplexity(path, options) {
260
+ return this._executeCapability('analyzeComplexity', [path, options]);
261
+ }
262
+
263
+ async analyzeCodebase(path, options) {
264
+ return this._executeCapability('analyzeCodebase', [path, options]);
265
+ }
266
+
267
+ async getProjectStats(options) {
268
+ return this._executeCapability('getProjectStats', [options]);
269
+ }
270
+ }
271
+
272
+ module.exports = {
273
+ CodeIntelClient,
274
+ CIRCUIT_BREAKER_THRESHOLD,
275
+ CIRCUIT_BREAKER_RESET_MS,
276
+ CACHE_TTL_MS,
277
+ CB_CLOSED,
278
+ CB_OPEN,
279
+ CB_HALF_OPEN,
280
+ };
@@ -0,0 +1,159 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * CodeIntelEnricher — Composite capabilities built on top of primitive capabilities.
5
+ *
6
+ * Each method composes multiple primitive capabilities from CodeIntelClient
7
+ * to provide higher-level analysis functions used by AIOS tasks.
8
+ */
9
+ class CodeIntelEnricher {
10
+ /**
11
+ * @param {import('./code-intel-client').CodeIntelClient} client
12
+ */
13
+ constructor(client) {
14
+ this._client = client;
15
+ }
16
+
17
+ /**
18
+ * Assess impact of changes to given files.
19
+ * Composition: findReferences + analyzeComplexity
20
+ *
21
+ * @param {string[]} files - Files to assess impact for
22
+ * @returns {Promise<{references: Array, complexity: Object, blastRadius: number}|null>}
23
+ */
24
+ async assessImpact(files) {
25
+ if (!files || files.length === 0) return null;
26
+
27
+ try {
28
+ const results = await Promise.all(
29
+ files.map(async (file) => {
30
+ try {
31
+ const [refs, complexity] = await Promise.all([
32
+ this._client.findReferences(file),
33
+ this._client.analyzeComplexity(file),
34
+ ]);
35
+ return { file, references: refs, complexity };
36
+ } catch {
37
+ return { file, references: null, complexity: null };
38
+ }
39
+ }),
40
+ );
41
+
42
+ const allRefs = results.flatMap((r) => r.references || []);
43
+ const avgComplexity =
44
+ results.reduce((sum, r) => sum + ((r.complexity && r.complexity.score) || 0), 0) /
45
+ (results.length || 1);
46
+
47
+ return {
48
+ references: allRefs,
49
+ complexity: { average: avgComplexity, perFile: results },
50
+ blastRadius: allRefs.length,
51
+ };
52
+ } catch {
53
+ return null;
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Detect potential duplicates for a description/concept.
59
+ * Composition: findReferences + analyzeCodebase
60
+ *
61
+ * @param {string} description - Description of the capability to check
62
+ * @param {Object} [options] - Search options
63
+ * @returns {Promise<{matches: Array, codebaseOverview: Object}|null>}
64
+ */
65
+ async detectDuplicates(description, options = {}) {
66
+ try {
67
+ const [refs, codebase] = await Promise.all([
68
+ this._client.findReferences(description, options),
69
+ this._client.analyzeCodebase(options.path || '.', options),
70
+ ]);
71
+
72
+ if (!refs && !codebase) return null;
73
+
74
+ return {
75
+ matches: refs || [],
76
+ codebaseOverview: codebase || {},
77
+ };
78
+ } catch {
79
+ return null;
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Get coding conventions for a path.
85
+ * Composition: analyzeCodebase + getProjectStats
86
+ *
87
+ * @param {string} path - Path to analyze conventions for
88
+ * @returns {Promise<{patterns: Array, stats: Object}|null>}
89
+ */
90
+ async getConventions(path) {
91
+ try {
92
+ const [codebase, stats] = await Promise.all([
93
+ this._client.analyzeCodebase(path),
94
+ this._client.getProjectStats(),
95
+ ]);
96
+
97
+ if (!codebase && !stats) return null;
98
+
99
+ return {
100
+ patterns: (codebase && codebase.patterns) || [],
101
+ stats: stats || {},
102
+ };
103
+ } catch {
104
+ return null;
105
+ }
106
+ }
107
+
108
+ /**
109
+ * Find tests related to a symbol.
110
+ * Composition: findReferences (filtered for test/spec files)
111
+ *
112
+ * @param {string} symbol - Symbol to find tests for
113
+ * @returns {Promise<Array<{file: string, line: number, context: string}>|null>}
114
+ */
115
+ async findTests(symbol) {
116
+ try {
117
+ const refs = await this._client.findReferences(symbol);
118
+ if (!refs) return null;
119
+
120
+ return refs.filter((ref) => {
121
+ const file = (ref.file || '').toLowerCase();
122
+ return (
123
+ file.includes('test') ||
124
+ file.includes('spec') ||
125
+ file.includes('__tests__')
126
+ );
127
+ });
128
+ } catch {
129
+ return null;
130
+ }
131
+ }
132
+
133
+ /**
134
+ * Describe the project overview.
135
+ * Composition: analyzeCodebase + getProjectStats
136
+ *
137
+ * @param {string} [path] - Root path (defaults to '.')
138
+ * @returns {Promise<{codebase: Object, stats: Object}|null>}
139
+ */
140
+ async describeProject(path = '.') {
141
+ try {
142
+ const [codebase, stats] = await Promise.all([
143
+ this._client.analyzeCodebase(path),
144
+ this._client.getProjectStats(),
145
+ ]);
146
+
147
+ if (!codebase && !stats) return null;
148
+
149
+ return {
150
+ codebase: codebase || {},
151
+ stats: stats || {},
152
+ };
153
+ } catch {
154
+ return null;
155
+ }
156
+ }
157
+ }
158
+
159
+ module.exports = { CodeIntelEnricher };
@@ -0,0 +1,137 @@
1
+ 'use strict';
2
+
3
+ const { CodeIntelClient } = require('./code-intel-client');
4
+ const { CodeIntelEnricher } = require('./code-intel-enricher');
5
+ const { CodeIntelProvider, CAPABILITIES } = require('./providers/provider-interface');
6
+ const { CodeGraphProvider, TOOL_MAP } = require('./providers/code-graph-provider');
7
+
8
+ // Singleton client instance (lazily initialized)
9
+ let _defaultClient = null;
10
+ let _defaultEnricher = null;
11
+
12
+ /**
13
+ * Get the default CodeIntelClient singleton.
14
+ * @param {Object} [options] - Options to pass on first creation
15
+ * @returns {CodeIntelClient}
16
+ */
17
+ function getClient(options) {
18
+ if (!_defaultClient) {
19
+ _defaultClient = new CodeIntelClient(options);
20
+ }
21
+ return _defaultClient;
22
+ }
23
+
24
+ /**
25
+ * Get the default CodeIntelEnricher singleton.
26
+ * @param {Object} [options] - Options to pass to client on first creation
27
+ * @returns {CodeIntelEnricher}
28
+ */
29
+ function getEnricher(options) {
30
+ if (!_defaultEnricher) {
31
+ _defaultEnricher = new CodeIntelEnricher(getClient(options));
32
+ }
33
+ return _defaultEnricher;
34
+ }
35
+
36
+ /**
37
+ * Check if any code intelligence provider is available.
38
+ * @returns {boolean}
39
+ */
40
+ function isCodeIntelAvailable() {
41
+ if (_defaultClient) {
42
+ return _defaultClient.isCodeIntelAvailable();
43
+ }
44
+ return false;
45
+ }
46
+
47
+ /**
48
+ * Enrich a base result with code intelligence data.
49
+ * Graceful — never throws, returns baseResult unchanged on failure.
50
+ *
51
+ * @param {*} baseResult - The base result to enrich
52
+ * @param {Object} options - Enrichment options
53
+ * @param {string[]} options.capabilities - List of enricher capabilities to use
54
+ * @param {number} [options.timeout=5000] - Timeout in ms
55
+ * @param {string} [options.fallbackBehavior='warn-and-continue'] - Fallback strategy
56
+ * @returns {Promise<*>} Enriched result or baseResult on failure
57
+ */
58
+ async function enrichWithCodeIntel(baseResult, options = {}) {
59
+ if (!isCodeIntelAvailable()) {
60
+ return baseResult;
61
+ }
62
+
63
+ const enricher = getEnricher();
64
+ const enrichments = {};
65
+
66
+ try {
67
+ const timeout = options.timeout ?? 5000;
68
+ const capabilities = options.capabilities || [];
69
+
70
+ const capabilityArgs = {
71
+ assessImpact: () => [Array.isArray(options.files) ? options.files : []],
72
+ detectDuplicates: () => [options.description || '', options],
73
+ findTests: () => [options.symbol || ''],
74
+ getConventions: () => [options.target || '.'],
75
+ describeProject: () => [options.target || '.'],
76
+ };
77
+
78
+ const promises = capabilities.map(async (cap) => {
79
+ if (typeof enricher[cap] === 'function') {
80
+ let timer;
81
+ try {
82
+ const args = capabilityArgs[cap] ? capabilityArgs[cap]() : [options.target || '.'];
83
+ const result = await Promise.race([
84
+ enricher[cap](...args),
85
+ new Promise((_, reject) => {
86
+ timer = setTimeout(() => reject(new Error('timeout')), timeout);
87
+ }),
88
+ ]);
89
+ enrichments[cap] = result;
90
+ } finally {
91
+ clearTimeout(timer);
92
+ }
93
+ }
94
+ });
95
+
96
+ await Promise.allSettled(promises);
97
+ } catch (error) {
98
+ // Graceful — never throws, returns baseResult unchanged on failure
99
+ if (options.fallbackBehavior !== 'silent') {
100
+ console.warn('[code-intel] Enrichment failed, returning base result:', error.message);
101
+ }
102
+ return baseResult;
103
+ }
104
+
105
+ return { ...baseResult, _codeIntel: enrichments };
106
+ }
107
+
108
+ /**
109
+ * Reset singletons (for testing).
110
+ */
111
+ function _resetForTesting() {
112
+ _defaultClient = null;
113
+ _defaultEnricher = null;
114
+ }
115
+
116
+ module.exports = {
117
+ // Singletons
118
+ getClient,
119
+ getEnricher,
120
+
121
+ // Convenience
122
+ isCodeIntelAvailable,
123
+ enrichWithCodeIntel,
124
+
125
+ // Classes (for custom instances)
126
+ CodeIntelClient,
127
+ CodeIntelEnricher,
128
+ CodeIntelProvider,
129
+ CodeGraphProvider,
130
+
131
+ // Constants
132
+ CAPABILITIES,
133
+ TOOL_MAP,
134
+
135
+ // Testing
136
+ _resetForTesting,
137
+ };