minovative-mind-cli 2.9.1 → 2.11.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 +14 -6
- package/dist/services/agent/commandApproval.js +5 -2
- package/dist/services/agent/slashCommands.js +90 -53
- package/dist/services/agent-tools.d.ts +4 -3
- package/dist/services/agent-tools.js +32 -79
- package/dist/services/agent.d.ts +5 -6
- package/dist/services/agent.js +11 -15
- package/dist/services/ai.d.ts +21 -1
- package/dist/services/ai.js +236 -11
- package/dist/services/chatHistoryService.d.ts +95 -2
- package/dist/services/chatHistoryService.js +236 -9
- package/dist/services/contextAgent.js +196 -81
- package/dist/services/investigationComplexity.d.ts +1 -1
- package/dist/services/investigationComplexity.js +1 -1
- package/dist/services/orchestration/investigationAgent.js +101 -84
- package/dist/services/orchestration/investigationCache.d.ts +80 -5
- package/dist/services/orchestration/investigationCache.js +570 -41
- package/dist/services/orchestration/investigationOrchestrator.js +17 -4
- package/dist/services/orchestration/orchestrator.js +6 -3
- package/dist/services/orchestration/scopedTools.js +5 -0
- package/dist/services/orchestration/subAgent.d.ts +31 -1
- package/dist/services/orchestration/subAgent.js +153 -2
- package/dist/utils/analysisRunner.d.ts +29 -0
- package/dist/utils/analysisRunner.js +200 -5
- package/dist/utils/contextPrompts.d.ts +20 -4
- package/dist/utils/contextPrompts.js +158 -23
- package/dist/utils/historyPrompt.d.ts +92 -1
- package/dist/utils/historyPrompt.js +166 -2
- package/dist/utils/symbolExtractor.d.ts +12 -0
- package/dist/utils/symbolExtractor.js +946 -0
- package/dist/utils/systemPrompts.d.ts +5 -4
- package/dist/utils/systemPrompts.js +46 -14
- package/oclif.manifest.json +1 -1
- package/package.json +2 -2
|
@@ -135,56 +135,63 @@ export class InvestigationAgentRunner {
|
|
|
135
135
|
success = true;
|
|
136
136
|
break;
|
|
137
137
|
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
138
|
+
let isFinished = false;
|
|
139
|
+
if (onTool) {
|
|
140
|
+
for (const call of functionCalls) {
|
|
141
|
+
onTool(formatToolCall(call.name, (call.args || {})));
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
// Execute tool calls concurrently while preserving 1:1 Gemini response positional ordering
|
|
145
|
+
const functionResponses = await Promise.all(functionCalls.map(async (call) => {
|
|
146
|
+
if (crashed || abortSignal.aborted) {
|
|
147
|
+
return {
|
|
148
|
+
functionResponse: {
|
|
149
|
+
name: call.name,
|
|
150
|
+
response: { error: 'Operation aborted or agent stopped.' },
|
|
151
|
+
},
|
|
152
|
+
};
|
|
153
|
+
}
|
|
142
154
|
this.pingHeartbeat();
|
|
143
|
-
const args = call.args;
|
|
155
|
+
const args = (call.args || {});
|
|
144
156
|
const logPrefix = `[${this.agentLabel}]`;
|
|
145
|
-
if (onTool) {
|
|
146
|
-
onTool(formatToolCall(call.name, args));
|
|
147
|
-
}
|
|
148
157
|
if (call.name === 'finish_investigation') {
|
|
149
158
|
summary = args.summary || '';
|
|
150
159
|
const filesToRead = args.relevantFiles || [];
|
|
151
160
|
if (onProgress)
|
|
152
161
|
onProgress(`${logPrefix} Finished investigation (${filesToRead.length} files)`);
|
|
153
162
|
debugLog(`InvestigationAgent ${logPrefix}: Finished. Selected files: ${JSON.stringify(filesToRead)}`);
|
|
154
|
-
|
|
155
|
-
if (
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
const
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
relevantFiles.set(filePath, contentObj);
|
|
165
|
-
this.readCache.set(filePath, contentObj);
|
|
166
|
-
}
|
|
163
|
+
await Promise.all(filesToRead.map(async (filePath) => {
|
|
164
|
+
if (this.readCache.has(filePath)) {
|
|
165
|
+
relevantFiles.set(filePath, this.readCache.get(filePath));
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
const readResult = await readFile(this.workspaceRoot, filePath);
|
|
169
|
+
if (!readResult.error) {
|
|
170
|
+
const contentObj = { text: readResult.output, inlineData: readResult.inlineData };
|
|
171
|
+
relevantFiles.set(filePath, contentObj);
|
|
172
|
+
this.readCache.set(filePath, contentObj);
|
|
167
173
|
}
|
|
168
174
|
}
|
|
169
|
-
}
|
|
175
|
+
}));
|
|
170
176
|
isFinished = true;
|
|
171
177
|
success = true;
|
|
172
|
-
|
|
173
|
-
functionResponse: {
|
|
174
|
-
|
|
175
|
-
|
|
178
|
+
return {
|
|
179
|
+
functionResponse: {
|
|
180
|
+
name: call.name,
|
|
181
|
+
response: { output: 'Investigation finished.' },
|
|
182
|
+
},
|
|
183
|
+
};
|
|
176
184
|
}
|
|
177
185
|
else if (call.name === 'select_files') {
|
|
178
186
|
const filesToRead = args.files || [];
|
|
179
187
|
if (onProgress)
|
|
180
188
|
onProgress(`${logPrefix} Selected ${filesToRead.length} files`);
|
|
181
|
-
|
|
182
|
-
for (const filePath of filesToRead) {
|
|
189
|
+
const fileOutputs = await Promise.all(filesToRead.map(async (filePath) => {
|
|
183
190
|
// Check cache first
|
|
184
191
|
if (this.readCache.has(filePath)) {
|
|
185
192
|
const cached = this.readCache.get(filePath);
|
|
186
193
|
relevantFiles.set(filePath, cached);
|
|
187
|
-
|
|
194
|
+
return `\n--- File: ${filePath} ---\n${cached.text}\n`;
|
|
188
195
|
}
|
|
189
196
|
else {
|
|
190
197
|
const readResult = await readFile(this.workspaceRoot, filePath);
|
|
@@ -192,99 +199,98 @@ export class InvestigationAgentRunner {
|
|
|
192
199
|
const contentObj = { text: readResult.output, inlineData: readResult.inlineData };
|
|
193
200
|
relevantFiles.set(filePath, contentObj);
|
|
194
201
|
this.readCache.set(filePath, contentObj);
|
|
195
|
-
|
|
202
|
+
return `\n--- File: ${filePath} ---\n${readResult.output}\n`;
|
|
196
203
|
}
|
|
197
204
|
else {
|
|
198
|
-
|
|
205
|
+
return `\n--- File: ${filePath} (Error: ${readResult.error}) ---\n`;
|
|
199
206
|
}
|
|
200
207
|
}
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
208
|
+
}));
|
|
209
|
+
const output = fileOutputs.join('');
|
|
210
|
+
return {
|
|
211
|
+
functionResponse: {
|
|
212
|
+
name: call.name,
|
|
213
|
+
response: { output: output || 'No files selected.' },
|
|
214
|
+
},
|
|
215
|
+
};
|
|
205
216
|
}
|
|
206
217
|
else if (call.name === 'list_directory') {
|
|
207
218
|
if (onProgress)
|
|
208
|
-
onProgress(`${logPrefix} Listing: ${args.dirPath}`);
|
|
209
|
-
const listRes = await listDirectory(this.workspaceRoot, args.dirPath, args.maxDepth
|
|
210
|
-
|
|
219
|
+
onProgress(`${logPrefix} Listing directory: ${args.dirPath || '.'}`);
|
|
220
|
+
const listRes = await listDirectory(this.workspaceRoot, args.dirPath || '.', args.maxDepth);
|
|
221
|
+
return {
|
|
211
222
|
functionResponse: {
|
|
212
223
|
name: call.name,
|
|
213
|
-
response: {
|
|
224
|
+
response: {
|
|
225
|
+
output: listRes.output,
|
|
226
|
+
...(listRes.error ? { error: listRes.error } : {}),
|
|
227
|
+
},
|
|
214
228
|
},
|
|
215
|
-
}
|
|
229
|
+
};
|
|
216
230
|
}
|
|
217
231
|
else if (call.name === 'search_codebase') {
|
|
218
232
|
if (onProgress)
|
|
219
233
|
onProgress(`${logPrefix} Searching: "${args.pattern}"`);
|
|
220
|
-
const grepRes = await grepSearch(this.workspaceRoot, args.pattern, args.fileGlob);
|
|
221
|
-
|
|
222
|
-
functionResponse: {
|
|
223
|
-
|
|
234
|
+
const grepRes = await grepSearch(this.workspaceRoot, args.pattern || '', args.fileGlob, args.fixedStrings, args.dirPath);
|
|
235
|
+
return {
|
|
236
|
+
functionResponse: {
|
|
237
|
+
name: call.name,
|
|
238
|
+
response: { output: grepRes.error ? grepRes.error : grepRes.output },
|
|
239
|
+
},
|
|
240
|
+
};
|
|
224
241
|
}
|
|
225
242
|
else if (call.name === 'read_file') {
|
|
226
|
-
const filePath = args.filePath;
|
|
243
|
+
const filePath = args.filePath || '';
|
|
244
|
+
const startLine = args.startLine;
|
|
245
|
+
const endLine = args.endLine;
|
|
246
|
+
const targetElements = args.targetElements;
|
|
227
247
|
if (onProgress)
|
|
228
248
|
onProgress(`${logPrefix} Reading: ${filePath}`);
|
|
229
|
-
// Check cache for full-file reads
|
|
230
|
-
if (!
|
|
249
|
+
// Check read cache only for full-file reads
|
|
250
|
+
if (this.readCache.has(filePath) && !startLine && !endLine && (!targetElements || targetElements.length === 0)) {
|
|
231
251
|
const cached = this.readCache.get(filePath);
|
|
232
252
|
relevantFiles.set(filePath, cached);
|
|
233
|
-
|
|
253
|
+
return {
|
|
254
|
+
functionResponse: {
|
|
255
|
+
name: call.name,
|
|
256
|
+
response: { output: cached.text },
|
|
257
|
+
},
|
|
258
|
+
};
|
|
234
259
|
}
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
// Only cache full-file reads (partial reads shouldn't overwrite full content)
|
|
241
|
-
if (!args.startLine && !args.endLine && !args.targetElements) {
|
|
242
|
-
this.readCache.set(filePath, contentObj);
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
functionResponses.push({
|
|
246
|
-
functionResponse: { name: call.name, response: { output: readRes.error ? readRes.error : readRes.output } },
|
|
247
|
-
});
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
else if (call.name === 'perform_web_search') {
|
|
251
|
-
if (onProgress)
|
|
252
|
-
onProgress(`${logPrefix} Web search: "${args.query}"`);
|
|
253
|
-
try {
|
|
254
|
-
const { createWebSearchAgentSession } = await import('../ai.js');
|
|
255
|
-
const webSession = createWebSearchAgentSession();
|
|
256
|
-
const webResult = await webSession.sendMessage(`Please search the web for the following query and summarize your findings:\n"${args.query}"`, undefined, abortSignal, () => this.pingHeartbeat());
|
|
257
|
-
const searchSummary = webResult.response.text()?.trim() || 'No relevant information found.';
|
|
258
|
-
webSearchSummary += `\nQuery: ${args.query}\nFindings:\n${searchSummary}\n`;
|
|
259
|
-
functionResponses.push({ functionResponse: { name: call.name, response: { output: searchSummary } } });
|
|
260
|
-
}
|
|
261
|
-
catch (e) {
|
|
262
|
-
functionResponses.push({
|
|
263
|
-
functionResponse: { name: call.name, response: { error: e.message || 'Failed to search the web' } },
|
|
264
|
-
});
|
|
260
|
+
const readRes = await readFile(this.workspaceRoot, filePath, startLine, endLine, targetElements);
|
|
261
|
+
if (!readRes.error && !startLine && !endLine && (!targetElements || targetElements.length === 0)) {
|
|
262
|
+
const contentObj = { text: readRes.output, inlineData: readRes.inlineData };
|
|
263
|
+
relevantFiles.set(filePath, contentObj);
|
|
264
|
+
this.readCache.set(filePath, contentObj);
|
|
265
265
|
}
|
|
266
|
+
return {
|
|
267
|
+
functionResponse: {
|
|
268
|
+
name: call.name,
|
|
269
|
+
response: { output: readRes.error ? readRes.error : readRes.output },
|
|
270
|
+
},
|
|
271
|
+
};
|
|
266
272
|
}
|
|
267
273
|
else if (call.name === 'find_dependencies') {
|
|
268
274
|
if (onProgress)
|
|
269
275
|
onProgress(`${logPrefix} Tracing dependencies: ${args.filePath}`);
|
|
270
276
|
const depResult = await traceDependencies(this.workspaceRoot, args.filePath, args.direction, args.maxDepth);
|
|
271
|
-
|
|
277
|
+
return {
|
|
272
278
|
functionResponse: {
|
|
273
279
|
name: call.name,
|
|
274
280
|
response: { output: depResult.error ? depResult.error : depResult.output },
|
|
275
281
|
},
|
|
276
|
-
}
|
|
282
|
+
};
|
|
277
283
|
}
|
|
278
284
|
else if (call.name === 'find_recent_changes') {
|
|
279
285
|
if (onProgress)
|
|
280
286
|
onProgress(`${logPrefix} Finding recent changes`);
|
|
281
287
|
const recentRes = await findRecentChanges(this.workspaceRoot, args.dirPath, args.minutes, args.maxDepth);
|
|
282
|
-
|
|
288
|
+
return {
|
|
283
289
|
functionResponse: {
|
|
284
290
|
name: call.name,
|
|
285
291
|
response: { output: recentRes.error ? recentRes.error : recentRes.output },
|
|
286
292
|
},
|
|
287
|
-
}
|
|
293
|
+
};
|
|
288
294
|
}
|
|
289
295
|
else if (call.name === 'run_analysis_script') {
|
|
290
296
|
if (onProgress)
|
|
@@ -295,9 +301,20 @@ export class InvestigationAgentRunner {
|
|
|
295
301
|
const output = analysisResult.exitCode === 0
|
|
296
302
|
? analysisResult.stdout || '(script produced no output)'
|
|
297
303
|
: `Script failed (exit ${analysisResult.exitCode}):\n${analysisResult.stderr}`;
|
|
298
|
-
|
|
304
|
+
return {
|
|
305
|
+
functionResponse: {
|
|
306
|
+
name: call.name,
|
|
307
|
+
response: { output },
|
|
308
|
+
},
|
|
309
|
+
};
|
|
299
310
|
}
|
|
300
|
-
|
|
311
|
+
return {
|
|
312
|
+
functionResponse: {
|
|
313
|
+
name: call.name,
|
|
314
|
+
response: { output: `Unknown tool: ${call.name}` },
|
|
315
|
+
},
|
|
316
|
+
};
|
|
317
|
+
}));
|
|
301
318
|
if (crashed || abortSignal.aborted || isFinished)
|
|
302
319
|
break;
|
|
303
320
|
// Feed tool results back to the model
|
|
@@ -1,25 +1,100 @@
|
|
|
1
|
+
export interface SemanticMetadata {
|
|
2
|
+
topics: string[];
|
|
3
|
+
components: string[];
|
|
4
|
+
intent: string;
|
|
5
|
+
reasoning?: string;
|
|
6
|
+
}
|
|
1
7
|
export interface InvestigationCacheEntry {
|
|
2
8
|
promptHash: string;
|
|
9
|
+
normalizedPrompt: string;
|
|
10
|
+
topics?: string[];
|
|
11
|
+
components?: string[];
|
|
12
|
+
intent?: string;
|
|
13
|
+
semanticSignature?: string;
|
|
3
14
|
relevantFiles: string[];
|
|
4
15
|
summary: string;
|
|
5
16
|
workspaceFingerprint: string;
|
|
6
17
|
createdAt: number;
|
|
18
|
+
lastAccessedAt: number;
|
|
19
|
+
hitCount?: number;
|
|
7
20
|
}
|
|
8
21
|
export interface InvestigationCacheStore {
|
|
9
22
|
entries: Record<string, InvestigationCacheEntry>;
|
|
23
|
+
totalHits?: number;
|
|
24
|
+
totalMisses?: number;
|
|
25
|
+
}
|
|
26
|
+
export interface LookupInvestigationOptions {
|
|
27
|
+
abortSignal?: AbortSignal;
|
|
28
|
+
allowSemanticFallback?: boolean;
|
|
29
|
+
semanticSession?: any;
|
|
30
|
+
onProgress?: (message: string) => void;
|
|
31
|
+
fuzzyThreshold?: number;
|
|
32
|
+
semanticThreshold?: number;
|
|
10
33
|
}
|
|
34
|
+
export interface LookupInvestigationResult {
|
|
35
|
+
entry: InvestigationCacheEntry;
|
|
36
|
+
bytesSaved: number;
|
|
37
|
+
matchTier: 'exact' | 'fuzzy' | 'semantic';
|
|
38
|
+
similarityScore?: number;
|
|
39
|
+
}
|
|
40
|
+
export declare const MAX_CACHE_SIZE_BYTES: number;
|
|
41
|
+
export declare const DEFAULT_FUZZY_THRESHOLD = 0.85;
|
|
42
|
+
export declare const DEFAULT_SEMANTIC_THRESHOLD = 0.7;
|
|
43
|
+
/**
|
|
44
|
+
* Normalizes prompt by trimming, collapsing whitespace, and stripping common conversation prefixes.
|
|
45
|
+
*/
|
|
11
46
|
export declare function normalizePrompt(prompt: string): string;
|
|
47
|
+
/**
|
|
48
|
+
* Computes SHA-256 hash of a normalized prompt string.
|
|
49
|
+
*/
|
|
12
50
|
export declare function hashPrompt(normalized: string): string;
|
|
51
|
+
/**
|
|
52
|
+
* Generates a deterministic signature from topics, components, and intent.
|
|
53
|
+
*/
|
|
54
|
+
export declare function generateSemanticSignature(topics: string[], components: string[], intent?: string): string;
|
|
55
|
+
/**
|
|
56
|
+
* Offline heuristic semantic metadata extractor.
|
|
57
|
+
*/
|
|
58
|
+
export declare function extractHeuristicSemanticMetadata(prompt: string, relevantFiles?: string[]): SemanticMetadata;
|
|
59
|
+
/**
|
|
60
|
+
* Classifies prompt semantic intent and entity tags using AI or offline heuristic fallback.
|
|
61
|
+
*/
|
|
62
|
+
export declare function classifySemanticIntent(userPrompt: string, abortSignal?: AbortSignal, customSession?: any, timeoutMs?: number): Promise<SemanticMetadata | null>;
|
|
63
|
+
/**
|
|
64
|
+
* Computes a SHA-256 fingerprint of the relevant files' mtime and sizes.
|
|
65
|
+
*/
|
|
13
66
|
export declare function generateWorkspaceFingerprint(workspaceRoot: string, relevantFiles: string[]): Promise<string>;
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
67
|
+
/**
|
|
68
|
+
* Enforces LRU eviction to keep cache store below MAX_CACHE_SIZE_BYTES (5MB).
|
|
69
|
+
*/
|
|
70
|
+
export declare function pruneCacheStore(store: InvestigationCacheStore): void;
|
|
71
|
+
/**
|
|
72
|
+
* Looks up cached investigation results using a 2-Tier matching pipeline:
|
|
73
|
+
* - Tier 1A: Exact hash match
|
|
74
|
+
* - Tier 1B: Local fuzzy Levenshtein & token similarity match
|
|
75
|
+
* - Tier 2: AI semantic topic & component classification match
|
|
76
|
+
* All candidates validate the workspace fingerprint before returning.
|
|
77
|
+
*/
|
|
78
|
+
export declare function lookupInvestigation(workspaceRoot: string, userPrompt: string, options?: LookupInvestigationOptions): Promise<LookupInvestigationResult | null>;
|
|
79
|
+
/**
|
|
80
|
+
* Saves investigation context with semantic tagging, workspace fingerprint, and LRU pruning.
|
|
81
|
+
*/
|
|
82
|
+
export declare function saveInvestigation(workspaceRoot: string, userPrompt: string, relevantFiles: string[], summary: string, semanticMetadata?: Partial<SemanticMetadata>, abortSignal?: AbortSignal): Promise<void>;
|
|
83
|
+
/**
|
|
84
|
+
* Invalidates cache entries that reference any changed files.
|
|
85
|
+
*/
|
|
19
86
|
export declare function invalidateFilesFromInvestigationCache(workspaceRoot: string, changedFiles: string[]): Promise<void>;
|
|
87
|
+
/**
|
|
88
|
+
* Clears all entries in the investigation cache.
|
|
89
|
+
*/
|
|
90
|
+
export declare function clearInvestigationCache(workspaceRoot: string): void;
|
|
91
|
+
/**
|
|
92
|
+
* Returns cache statistics for inspection and telemetry.
|
|
93
|
+
*/
|
|
20
94
|
export declare function getInvestigationCacheStats(workspaceRoot: string): {
|
|
21
95
|
entries: number;
|
|
22
96
|
sizeBytes: number;
|
|
23
97
|
hitCount: number;
|
|
24
98
|
missCount: number;
|
|
99
|
+
topicsCount: number;
|
|
25
100
|
};
|