minovative-mind-cli 2.1.0 → 2.1.2
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 +4 -0
- package/dist/services/agent-tools.js +140 -0
- package/dist/services/contextAgent.js +25 -45
- package/oclif.manifest.json +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -15,6 +15,9 @@ the build/performance metrics are green.
|
|
|
15
15
|
[](https://oclif.io)
|
|
16
16
|
[](https://npmjs.org/package/minovative-mind-cli)
|
|
17
17
|
[](https://npmjs.org/package/minovative-mind-cli)
|
|
18
|
+
[](https://nodejs.org)
|
|
19
|
+
[](https://www.typescriptlang.org/)
|
|
20
|
+
[](https://github.com/quarantiine/minovative-mind-cli/blob/main/LICENSE.md)
|
|
18
21
|
|
|
19
22
|
- Official Website: [Main Website](https://www.minovativemind.dev/)
|
|
20
23
|
- Latest Updates: [Updates](https://www.minovativemind.dev/updates)
|
|
@@ -112,6 +115,7 @@ Hot-swap during a session using `/models`:
|
|
|
112
115
|
| `/commit` | Generate a conventional commit message from your diff |
|
|
113
116
|
| `/revert` | Undo changes from the last turn, or toggle the revert logger |
|
|
114
117
|
| `/chats` | View, resume, or delete previous chat sessions |
|
|
118
|
+
| `/workspaces` | Manage active workspaces and linked cross-repo aliases |
|
|
115
119
|
| `stop` | Abort generation immediately |
|
|
116
120
|
|
|
117
121
|
---
|
|
@@ -361,7 +361,147 @@ export async function readFile(workspaceRoot, filePath, startLine, endLine, targ
|
|
|
361
361
|
inlineData: { mimeType: 'application/pdf', data: base64Data },
|
|
362
362
|
};
|
|
363
363
|
}
|
|
364
|
+
// Prevent reading massive lockfiles
|
|
365
|
+
const isLockfile = ['package-lock.json', 'yarn.lock', 'poetry.lock', 'pnpm-lock.yaml'].some(file => filePath.toLowerCase().endsWith(file));
|
|
366
|
+
if (isLockfile) {
|
|
367
|
+
return {
|
|
368
|
+
output: '',
|
|
369
|
+
error: `Do not read lockfiles directly as they waste thousands of tokens. Please read the 'package.json', 'pyproject.toml', or equivalent source file to see dependencies.`,
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
// Handle SQLite databases natively by dumping the schema
|
|
373
|
+
if (filePath.toLowerCase().endsWith('.sqlite') || filePath.toLowerCase().endsWith('.db')) {
|
|
374
|
+
try {
|
|
375
|
+
const { stdout } = await execAsync(`sqlite3 "${absPath}" ".schema"`);
|
|
376
|
+
return {
|
|
377
|
+
output: `## Database Schema for ${path.basename(absPath)}\n\`\`\`sql\n${stdout}\n\`\`\``,
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
catch (e) {
|
|
381
|
+
return {
|
|
382
|
+
output: '',
|
|
383
|
+
error: `Could not parse SQLite DB natively. Ensure 'sqlite3' CLI is installed. Error: ${e instanceof Error ? e.message : String(e)}`,
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
}
|
|
364
387
|
let content = await fs.readFile(absPath, 'utf-8');
|
|
388
|
+
// Handle Jupyter Notebooks natively
|
|
389
|
+
if (filePath.toLowerCase().endsWith('.ipynb')) {
|
|
390
|
+
try {
|
|
391
|
+
const notebook = JSON.parse(content);
|
|
392
|
+
if (notebook && Array.isArray(notebook.cells)) {
|
|
393
|
+
const cleanCells = [];
|
|
394
|
+
for (const cell of notebook.cells) {
|
|
395
|
+
const sourceArray = Array.isArray(cell.source) ? cell.source : [cell.source || ''];
|
|
396
|
+
const source = sourceArray.join('');
|
|
397
|
+
if (cell.cell_type === 'markdown') {
|
|
398
|
+
cleanCells.push(source);
|
|
399
|
+
}
|
|
400
|
+
else if (cell.cell_type === 'code') {
|
|
401
|
+
let cellStr = '```python\n' + source + '\n```';
|
|
402
|
+
if (cell.outputs && Array.isArray(cell.outputs)) {
|
|
403
|
+
const textOutputs = [];
|
|
404
|
+
for (const out of cell.outputs) {
|
|
405
|
+
if (out.output_type === 'stream' && out.text) {
|
|
406
|
+
const text = Array.isArray(out.text) ? out.text.join('') : out.text;
|
|
407
|
+
textOutputs.push(text);
|
|
408
|
+
}
|
|
409
|
+
else if (out.data) {
|
|
410
|
+
if (out.data['text/plain']) {
|
|
411
|
+
const text = Array.isArray(out.data['text/plain']) ? out.data['text/plain'].join('') : out.data['text/plain'];
|
|
412
|
+
textOutputs.push(text);
|
|
413
|
+
}
|
|
414
|
+
else if (out.data['image/png'] || out.data['image/jpeg'] || out.data['image/svg+xml']) {
|
|
415
|
+
textOutputs.push('[Image Output]');
|
|
416
|
+
}
|
|
417
|
+
else if (out.data['text/html']) {
|
|
418
|
+
textOutputs.push('[HTML Output]');
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
if (textOutputs.length > 0) {
|
|
423
|
+
let joinedOutput = textOutputs.join('\n').trim();
|
|
424
|
+
const outLines = joinedOutput.split('\n');
|
|
425
|
+
if (outLines.length > 50) {
|
|
426
|
+
joinedOutput = outLines.slice(0, 50).join('\n') + '\n... (Output truncated)';
|
|
427
|
+
}
|
|
428
|
+
cellStr += '\n\n**Output:**\n```text\n' + joinedOutput + '\n```';
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
cleanCells.push(cellStr);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
content = cleanCells.join('\n\n');
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
catch (err) {
|
|
438
|
+
content = `<!-- Failed to parse .ipynb cleanly, showing raw JSON -->\n${content}`;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
// Handle CSV natively by formatting as a Markdown table
|
|
442
|
+
if (filePath.toLowerCase().endsWith('.csv')) {
|
|
443
|
+
try {
|
|
444
|
+
const rows = [];
|
|
445
|
+
let currentRow = [];
|
|
446
|
+
let currentCell = '';
|
|
447
|
+
let inQuotes = false;
|
|
448
|
+
for (let i = 0; i < content.length; i++) {
|
|
449
|
+
const char = content[i];
|
|
450
|
+
if (char === '"') {
|
|
451
|
+
if (inQuotes && content[i + 1] === '"') {
|
|
452
|
+
currentCell += '"';
|
|
453
|
+
i++;
|
|
454
|
+
}
|
|
455
|
+
else {
|
|
456
|
+
inQuotes = !inQuotes;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
else if (char === ',' && !inQuotes) {
|
|
460
|
+
currentRow.push(currentCell.trim().replace(/\n/g, ' ').replace(/\|/g, '\\|'));
|
|
461
|
+
currentCell = '';
|
|
462
|
+
}
|
|
463
|
+
else if ((char === '\n' || char === '\r') && !inQuotes) {
|
|
464
|
+
if (char === '\r' && content[i + 1] === '\n')
|
|
465
|
+
i++;
|
|
466
|
+
currentRow.push(currentCell.trim().replace(/\n/g, ' ').replace(/\|/g, '\\|'));
|
|
467
|
+
if (currentRow.some(cell => cell.length > 0)) {
|
|
468
|
+
rows.push(currentRow);
|
|
469
|
+
}
|
|
470
|
+
currentRow = [];
|
|
471
|
+
currentCell = '';
|
|
472
|
+
}
|
|
473
|
+
else {
|
|
474
|
+
currentCell += char;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
if (currentCell !== '' || currentRow.length > 0) {
|
|
478
|
+
currentRow.push(currentCell.trim().replace(/\n/g, ' ').replace(/\|/g, '\\|'));
|
|
479
|
+
if (currentRow.some(cell => cell.length > 0))
|
|
480
|
+
rows.push(currentRow);
|
|
481
|
+
}
|
|
482
|
+
if (rows.length > 0) {
|
|
483
|
+
const header = rows[0];
|
|
484
|
+
const MAX_CSV_ROWS = 250; // Generous truncation limit
|
|
485
|
+
const isTruncated = rows.length > MAX_CSV_ROWS;
|
|
486
|
+
const displayRows = isTruncated ? rows.slice(0, MAX_CSV_ROWS) : rows;
|
|
487
|
+
let mdTable = `| ${header.join(' | ')} |\n`;
|
|
488
|
+
mdTable += `| ${header.map(() => '---').join(' | ')} |\n`;
|
|
489
|
+
for (let i = 1; i < displayRows.length; i++) {
|
|
490
|
+
const row = displayRows[i];
|
|
491
|
+
while (row.length < header.length)
|
|
492
|
+
row.push('');
|
|
493
|
+
mdTable += `| ${row.slice(0, header.length).join(' | ')} |\n`;
|
|
494
|
+
}
|
|
495
|
+
if (isTruncated) {
|
|
496
|
+
mdTable += `\n*(Note: CSV truncated from ${rows.length} rows to ${MAX_CSV_ROWS} rows for safe viewing)*\n`;
|
|
497
|
+
}
|
|
498
|
+
content = mdTable;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
catch (err) {
|
|
502
|
+
content = `<!-- Failed to parse .csv as table, showing raw text -->\n${content}`;
|
|
503
|
+
}
|
|
504
|
+
}
|
|
365
505
|
if (targetElements && targetElements.length > 0) {
|
|
366
506
|
content = extractSymbols(content, filePath, targetElements);
|
|
367
507
|
}
|
|
@@ -4,7 +4,7 @@ import pc from 'picocolors';
|
|
|
4
4
|
import { createContextAgentSession, createIntentRouterSession, createWebSearchAgentSession, createExecutionComplexitySession, } from './ai.js';
|
|
5
5
|
import { evaluateInvestigationComplexity } from './investigationComplexity.js';
|
|
6
6
|
import { InvestigationOrchestrator } from './orchestration/investigationOrchestrator.js';
|
|
7
|
-
import { isSubAgentsEnabled,
|
|
7
|
+
import { isSubAgentsEnabled, executeTool } from './agent-tools.js';
|
|
8
8
|
import { debugLog } from '../utils/logger.js';
|
|
9
9
|
import { buildDependencyGraph } from '../utils/dependencyTracer.js';
|
|
10
10
|
import { runEphemeralScript } from '../utils/analysisRunner.js';
|
|
@@ -198,7 +198,7 @@ export async function gatherContext(workspaceRoot, userRequest, chatHistory = ''
|
|
|
198
198
|
let primaryProjectType = 'Unknown';
|
|
199
199
|
for (const { alias, root } of allRoots) {
|
|
200
200
|
const label = alias ? `@${alias} (${root})` : `Primary Workspace (${root})`;
|
|
201
|
-
const treeResult = await
|
|
201
|
+
const treeResult = await executeTool(root, 'list_directory', { dirPath: '.', maxDepth: 10 });
|
|
202
202
|
let tree = treeResult.output;
|
|
203
203
|
if (tree.length > 30000) {
|
|
204
204
|
tree = tree.substring(0, 30000) + '\\n... (Project tree truncated due to size)';
|
|
@@ -328,7 +328,7 @@ export async function gatherContext(workspaceRoot, userRequest, chatHistory = ''
|
|
|
328
328
|
debugLog(`Context Agent finished. Selected files: ${JSON.stringify(filesToRead)}`);
|
|
329
329
|
for (const filePath of filesToRead) {
|
|
330
330
|
if (!relevantFiles.has(filePath)) {
|
|
331
|
-
const readResult = await
|
|
331
|
+
const readResult = await executeTool(workspaceRoot, 'read_file', { filePath });
|
|
332
332
|
if (!readResult.error) {
|
|
333
333
|
relevantFiles.set(filePath, { text: readResult.output, inlineData: readResult.inlineData });
|
|
334
334
|
}
|
|
@@ -342,15 +342,23 @@ export async function gatherContext(workspaceRoot, userRequest, chatHistory = ''
|
|
|
342
342
|
// Execution Agent won't break imports when modifying/deleting/renaming.
|
|
343
343
|
const MAX_TOTAL_FILES = 15;
|
|
344
344
|
try {
|
|
345
|
-
const
|
|
345
|
+
const { resolveAndValidateMultiWorkspacePath } = await import('../utils/pathSecurity.js');
|
|
346
346
|
const autoDiscovered = new Set();
|
|
347
347
|
for (const filePath of filesToRead) {
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
348
|
+
try {
|
|
349
|
+
const resolved = resolveAndValidateMultiWorkspacePath(workspaceRoot, filePath);
|
|
350
|
+
const graph = await buildDependencyGraph(resolved.workspaceRoot);
|
|
351
|
+
const reverseDeps = graph.getImportedBy(resolved.relativePath);
|
|
352
|
+
for (const dep of reverseDeps) {
|
|
353
|
+
const aliasedDep = resolved.alias ? `@${resolved.alias}/${dep}` : dep;
|
|
354
|
+
if (!filesToRead.includes(aliasedDep) && !autoDiscovered.has(aliasedDep)) {
|
|
355
|
+
autoDiscovered.add(aliasedDep);
|
|
356
|
+
}
|
|
352
357
|
}
|
|
353
358
|
}
|
|
359
|
+
catch (e) {
|
|
360
|
+
// Ignore path resolution errors for trace dependencies
|
|
361
|
+
}
|
|
354
362
|
}
|
|
355
363
|
// Merge auto-discovered dependents, respecting the file cap
|
|
356
364
|
const remaining = MAX_TOTAL_FILES - relevantFiles.size;
|
|
@@ -359,7 +367,7 @@ export async function gatherContext(workspaceRoot, userRequest, chatHistory = ''
|
|
|
359
367
|
if (added >= remaining)
|
|
360
368
|
break;
|
|
361
369
|
if (!relevantFiles.has(dep)) {
|
|
362
|
-
const readResult = await
|
|
370
|
+
const readResult = await executeTool(workspaceRoot, 'read_file', { filePath: dep });
|
|
363
371
|
if (!readResult.error) {
|
|
364
372
|
relevantFiles.set(dep, { text: readResult.output, inlineData: readResult.inlineData });
|
|
365
373
|
added++;
|
|
@@ -389,7 +397,7 @@ export async function gatherContext(workspaceRoot, userRequest, chatHistory = ''
|
|
|
389
397
|
const filesToRead = args.files || [];
|
|
390
398
|
let output = '';
|
|
391
399
|
for (const filePath of filesToRead) {
|
|
392
|
-
const readResult = await
|
|
400
|
+
const readResult = await executeTool(workspaceRoot, 'read_file', { filePath });
|
|
393
401
|
if (!readResult.error) {
|
|
394
402
|
relevantFiles.set(filePath, { text: readResult.output, inlineData: readResult.inlineData });
|
|
395
403
|
output += `\n--- File: ${filePath} ---\n${readResult.output}\n`;
|
|
@@ -406,7 +414,7 @@ export async function gatherContext(workspaceRoot, userRequest, chatHistory = ''
|
|
|
406
414
|
});
|
|
407
415
|
}
|
|
408
416
|
else if (call.name === 'list_directory') {
|
|
409
|
-
const listRes = await
|
|
417
|
+
const listRes = await executeTool(workspaceRoot, 'list_directory', args);
|
|
410
418
|
functionResponses.push({
|
|
411
419
|
functionResponse: {
|
|
412
420
|
name: call.name,
|
|
@@ -418,7 +426,7 @@ export async function gatherContext(workspaceRoot, userRequest, chatHistory = ''
|
|
|
418
426
|
});
|
|
419
427
|
}
|
|
420
428
|
else if (call.name === 'search_codebase') {
|
|
421
|
-
const grepRes = await
|
|
429
|
+
const grepRes = await executeTool(workspaceRoot, 'grep_search', { ...args, workspace: 'all' });
|
|
422
430
|
functionResponses.push({
|
|
423
431
|
functionResponse: {
|
|
424
432
|
name: call.name,
|
|
@@ -427,44 +435,16 @@ export async function gatherContext(workspaceRoot, userRequest, chatHistory = ''
|
|
|
427
435
|
});
|
|
428
436
|
}
|
|
429
437
|
else if (call.name === 'semantic_search') {
|
|
430
|
-
const
|
|
431
|
-
const topK = args.topK || 5;
|
|
432
|
-
let output = '';
|
|
433
|
-
try {
|
|
434
|
-
const { getEmbeddingIndex } = await import('./embeddingIndex.js');
|
|
435
|
-
const index = getEmbeddingIndex();
|
|
436
|
-
if (!index.isReady()) {
|
|
437
|
-
// Lazy load from disk or build if missing
|
|
438
|
-
const loaded = await index.load(workspaceRoot);
|
|
439
|
-
if (!loaded) {
|
|
440
|
-
if (onProgress)
|
|
441
|
-
onProgress('Building semantic search index (first run)...');
|
|
442
|
-
await index.buildIndex(workspaceRoot, onProgress);
|
|
443
|
-
await index.save(workspaceRoot);
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
const results = await index.search(query, topK);
|
|
447
|
-
if (results.length === 0) {
|
|
448
|
-
output = 'No semantically similar code found. (Index might be empty or embedding failed)';
|
|
449
|
-
}
|
|
450
|
-
else {
|
|
451
|
-
output = results
|
|
452
|
-
.map((r) => `[Score: ${r.score.toFixed(3)}] ${r.filePath}:${r.startLine}-${r.endLine}\n${r.preview}`)
|
|
453
|
-
.join('\n---\n');
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
catch (e) {
|
|
457
|
-
output = `Semantic search failed: ${e.message}`;
|
|
458
|
-
}
|
|
438
|
+
const semRes = await executeTool(workspaceRoot, 'semantic_search', args);
|
|
459
439
|
functionResponses.push({
|
|
460
440
|
functionResponse: {
|
|
461
441
|
name: call.name,
|
|
462
|
-
response: { output },
|
|
442
|
+
response: { output: semRes.output },
|
|
463
443
|
},
|
|
464
444
|
});
|
|
465
445
|
}
|
|
466
446
|
else if (call.name === 'read_file') {
|
|
467
|
-
const readRes = await
|
|
447
|
+
const readRes = await executeTool(workspaceRoot, 'read_file', args);
|
|
468
448
|
if (!readRes.error) {
|
|
469
449
|
relevantFiles.set(args.filePath, { text: readRes.output, inlineData: readRes.inlineData });
|
|
470
450
|
}
|
|
@@ -506,7 +486,7 @@ export async function gatherContext(workspaceRoot, userRequest, chatHistory = ''
|
|
|
506
486
|
}
|
|
507
487
|
}
|
|
508
488
|
else if (call.name === 'find_dependencies') {
|
|
509
|
-
const depResult = await
|
|
489
|
+
const depResult = await executeTool(workspaceRoot, 'find_dependencies', args);
|
|
510
490
|
functionResponses.push({
|
|
511
491
|
functionResponse: {
|
|
512
492
|
name: call.name,
|
|
@@ -515,7 +495,7 @@ export async function gatherContext(workspaceRoot, userRequest, chatHistory = ''
|
|
|
515
495
|
});
|
|
516
496
|
}
|
|
517
497
|
else if (call.name === 'find_recent_changes') {
|
|
518
|
-
const recentRes = await
|
|
498
|
+
const recentRes = await executeTool(workspaceRoot, 'find_recent_changes', args);
|
|
519
499
|
functionResponses.push({
|
|
520
500
|
functionResponse: {
|
|
521
501
|
name: call.name,
|
package/oclif.manifest.json
CHANGED
package/package.json
CHANGED