minovative-mind-cli 2.2.0 → 2.2.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.
@@ -10,7 +10,7 @@ import { changeLogger } from '../changeLogger.js';
10
10
  import { chatHistoryService } from '../chatHistoryService.js';
11
11
  import { printLogo, brandBg, brandFg } from '../../utils/logo.js';
12
12
  import { readPaste } from '../../utils/paste.js';
13
- import { setApprovalMode, getApprovalMode, isSubAgentsEnabled, setSubAgentsEnabled, } from '../agent-tools.js';
13
+ import { setApprovalMode, getApprovalMode, isSubAgentsEnabled, setSubAgentsEnabled } from '../agent-tools.js';
14
14
  import { ProxyChatSession, setGlobalActiveModel, getGlobalActiveModel, getGlobalLatestUsageMetadata } from '../ai.js';
15
15
  const execAsync = promisify(exec);
16
16
  /**
@@ -375,7 +375,10 @@ export async function handleSlashCommand(command, context) {
375
375
  let sessionTasks = [];
376
376
  for (const item of session.history) {
377
377
  if (item.role === 'user') {
378
- const textParts = item.parts.filter((p) => p.text).map((p) => p.text).join('');
378
+ const textParts = item.parts
379
+ .filter((p) => p.text)
380
+ .map((p) => p.text)
381
+ .join('');
379
382
  if (textParts && !textParts.includes('SYSTEM CHECK: Please objectively verify')) {
380
383
  p.log.step(pc.bgBlue(pc.white(` ${textParts} `)));
381
384
  }
@@ -433,7 +436,7 @@ export async function handleSlashCommand(command, context) {
433
436
  .map((p) => p.text)
434
437
  .join('');
435
438
  if (textParts.trim()) {
436
- console.log(`\n${pc.blue('◆')} ${pc.bold('Minovative Mind')} ${pc.dim('(Resumed)')}\n`);
439
+ console.log(`\n${pc.blue('◆')} ${pc.bold('Minovative Mind')} ${pc.dim('(History)')}\n`);
437
440
  const { marked } = await import('marked');
438
441
  const cleanText = textParts.replace(/\n([ \t]*\n){2,}/g, '\n\n');
439
442
  console.log(marked.parse(cleanText));
@@ -472,7 +475,7 @@ export async function handleSlashCommand(command, context) {
472
475
  const allRoots = workspaceRegistry.getAllRoots(workspaceRoot);
473
476
  const options = [];
474
477
  options.push({ value: 'add', label: 'Add Workspace' });
475
- const externalRoots = allRoots.filter(r => r.alias);
478
+ const externalRoots = allRoots.filter((r) => r.alias);
476
479
  if (externalRoots.length > 0) {
477
480
  options.push({ value: 'edit', label: 'Edit Workspace' });
478
481
  options.push({ value: 'remove', label: 'Remove Workspace' });
@@ -494,9 +497,9 @@ export async function handleSlashCommand(command, context) {
494
497
  return 'Alias is required';
495
498
  if (!/^[a-zA-Z0-9_-]+$/.test(val))
496
499
  return 'Only letters, numbers, hyphens, and underscores';
497
- if (workspaceRegistry.getAllRoots(workspaceRoot).some(r => r.alias === val))
500
+ if (workspaceRegistry.getAllRoots(workspaceRoot).some((r) => r.alias === val))
498
501
  return 'Alias already in use';
499
- }
502
+ },
500
503
  });
501
504
  if (p.isCancel(aliasStr))
502
505
  continue;
@@ -505,7 +508,7 @@ export async function handleSlashCommand(command, context) {
505
508
  validate: (val) => {
506
509
  if (!val)
507
510
  return 'Path is required';
508
- }
511
+ },
509
512
  });
510
513
  if (p.isCancel(rootPathStr))
511
514
  continue;
@@ -528,14 +531,14 @@ export async function handleSlashCommand(command, context) {
528
531
  }
529
532
  }
530
533
  else if (action === 'edit') {
531
- const editOptions = externalRoots.map(r => ({
534
+ const editOptions = externalRoots.map((r) => ({
532
535
  value: r.alias,
533
- label: `@${r.alias} -> ${r.root}`
536
+ label: `@${r.alias} -> ${r.root}`,
534
537
  }));
535
538
  editOptions.push({ value: 'cancel', label: 'Cancel' });
536
539
  const aliasToEdit = await p['select']({
537
540
  message: 'Select workspace to edit:',
538
- options: editOptions
541
+ options: editOptions,
539
542
  });
540
543
  if (p.isCancel(aliasToEdit) || aliasToEdit === 'cancel')
541
544
  continue;
@@ -550,9 +553,9 @@ export async function handleSlashCommand(command, context) {
550
553
  return 'Alias is required';
551
554
  if (!/^[a-zA-Z0-9_-]+$/.test(val))
552
555
  return 'Only letters, numbers, hyphens, and underscores';
553
- if (val !== ws.alias && workspaceRegistry.getAllRoots(workspaceRoot).some(r => r.alias === val))
556
+ if (val !== ws.alias && workspaceRegistry.getAllRoots(workspaceRoot).some((r) => r.alias === val))
554
557
  return 'Alias already in use';
555
- }
558
+ },
556
559
  });
557
560
  if (p.isCancel(newAliasStr))
558
561
  continue;
@@ -562,7 +565,7 @@ export async function handleSlashCommand(command, context) {
562
565
  validate: (val) => {
563
566
  if (!val)
564
567
  return 'Path is required';
565
- }
568
+ },
566
569
  });
567
570
  if (p.isCancel(newRootPathStr))
568
571
  continue;
@@ -592,14 +595,14 @@ export async function handleSlashCommand(command, context) {
592
595
  }
593
596
  }
594
597
  else if (action === 'remove') {
595
- const removeOptions = externalRoots.map(r => ({
598
+ const removeOptions = externalRoots.map((r) => ({
596
599
  value: r.alias,
597
- label: `@${r.alias} -> ${r.root}`
600
+ label: `@${r.alias} -> ${r.root}`,
598
601
  }));
599
602
  removeOptions.push({ value: 'cancel', label: 'Cancel' });
600
603
  const aliasToRemove = await p['select']({
601
604
  message: 'Select workspace to remove:',
602
- options: removeOptions
605
+ options: removeOptions,
603
606
  });
604
607
  if (p.isCancel(aliasToRemove) || aliasToRemove === 'cancel')
605
608
  continue;
@@ -689,10 +689,14 @@ async function executeSelfCorrectionLoop(chat, initialResult, workspaceRoot, inp
689
689
  let agentState = { targetAgent: effectiveTargetAgent };
690
690
  let previousChangeCount = changeLogger.getCurrentChangeSet()?.changes.length || 0;
691
691
  let intentVerified = false;
692
+ let isFixingCodeError = false;
692
693
  while (correctionAttempts <= MAX_CORRECTIONS) {
693
694
  if (finalText === '[Generation stopped by user]')
694
695
  break;
696
+ const historyLengthBefore = chat.getRawHistory().length;
695
697
  finalText = await processResponse(chat, result, workspaceRoot, inputHandler, agentState, signal);
698
+ const historyLengthAfter = chat.getRawHistory().length;
699
+ const usedTools = historyLengthAfter > historyLengthBefore;
696
700
  debugLog(`processResponse returned finalText (length ${finalText.length}): "${finalText.substring(0, 10)}..."`);
697
701
  if (finalText === '[Generation stopped by user]') {
698
702
  break;
@@ -702,13 +706,13 @@ async function executeSelfCorrectionLoop(chat, initialResult, workspaceRoot, inp
702
706
  }
703
707
  const currentChanges = changeLogger.getCurrentChangeSet()?.changes || [];
704
708
  // If the correction cycle is active but no new changes were registered, the model gave up
705
- if (correctionAttempts > 0 && currentChanges.length <= previousChangeCount) {
709
+ if (isFixingCodeError && currentChanges.length <= previousChangeCount && !usedTools) {
706
710
  if (correctionAttempts >= MAX_CORRECTIONS) {
707
711
  p.log.warn('Max self-correction attempts reached. Leaving remaining errors for manual review.');
708
712
  break;
709
713
  }
710
- debugLog('AI failed to modify any files during the correction attempt. Retrying...');
711
- const forcePrompt = `AUTOMATED SYSTEM CHECK: You did not modify any files. You MUST use your file modification tools to apply a fix for the previously mentioned errors. Do not just explain the issue.`;
714
+ debugLog('AI failed to execute tools or modify files during the correction attempt. Retrying...');
715
+ const forcePrompt = `AUTOMATED SYSTEM CHECK: You did not execute any tools or modify files. You MUST use your tools (such as run_command or modify_file) to investigate and apply a fix for the previously mentioned errors. Do not just explain the issue.`;
712
716
  spinner.start('Thinking (Correction)...');
713
717
  result = await chat.sendMessage(forcePrompt);
714
718
  spinner.stop();
@@ -774,7 +778,7 @@ CRITICAL:
774
778
  const changedFiles = currentChanges
775
779
  .filter((c) => c.action === 'create' || c.action === 'modify')
776
780
  .map((c) => c.filePath);
777
- if (changedFiles.length === 0)
781
+ if (changedFiles.length === 0 && !isFixingCodeError)
778
782
  break;
779
783
  // Stage 3: Static code analysis / verification
780
784
  p.log.step('Verifying modified files...');
@@ -800,6 +804,7 @@ CRITICAL:
800
804
  if (collector && correctionAttempts === 0)
801
805
  collector.recordVerificationResult(false);
802
806
  correctionAttempts++;
807
+ isFixingCodeError = true;
803
808
  if (collector)
804
809
  collector.recordSelfCorrection();
805
810
  if (correctionAttempts > MAX_CORRECTIONS) {
@@ -104,10 +104,22 @@ export class Orchestrator {
104
104
  p.log.step(pc.magenta(`Starting Wave ${wave.depth + 1} (${wave.taskIds.length} tasks):\n${taskDescriptions}`));
105
105
  const s = p.spinner();
106
106
  s.start(`Executing Wave ${wave.depth + 1}...`);
107
+ const agentStatuses = new Map();
108
+ const updateSpinner = () => {
109
+ const statuses = Array.from(agentStatuses.entries())
110
+ .map(([id, status]) => `${pc.cyan(id)}: ${pc.dim(status)}`)
111
+ .join(' │ ');
112
+ s.message(statuses || `Executing Wave ${wave.depth + 1}...`);
113
+ };
107
114
  const wavePromises = wave.taskIds.map(taskId => {
108
115
  const taskDef = graph.tasks.find(t => t.id === taskId);
109
116
  const globalContext = `Objective:\n${objective}\n\nContext:\n${contextInjection}`;
110
- return this.dispatchAgent(taskDef, globalContext, signal, (msg) => s.message(msg));
117
+ agentStatuses.set(taskDef.id, 'Starting...');
118
+ updateSpinner();
119
+ return this.dispatchAgent(taskDef, globalContext, signal, (msg) => {
120
+ agentStatuses.set(taskDef.id, msg);
121
+ updateSpinner();
122
+ });
111
123
  });
112
124
  // Run all agents in this wave concurrently
113
125
  const results = await Promise.all(wavePromises);
@@ -54,4 +54,4 @@ export declare function getScopedToolDeclarations(): (import("@google/generative
54
54
  * 3. Handles `post_message` and `read_messages` directly.
55
55
  * 4. Calls a heartbeat callback to notify the orchestrator this agent is alive.
56
56
  */
57
- export declare function executeScopedTool(name: string, args: Record<string, any>, workspaceRoot: string, agentId: string, bus: MessageBus, locks: FileLockRegistry, onHeartbeat: () => void): Promise<any>;
57
+ export declare function executeScopedTool(name: string, args: Record<string, any>, workspaceRoot: string, agentId: string, bus: MessageBus, locks: FileLockRegistry, onProgress: (msg?: string) => void): Promise<any>;
@@ -53,9 +53,9 @@ export function getScopedToolDeclarations() {
53
53
  * 3. Handles `post_message` and `read_messages` directly.
54
54
  * 4. Calls a heartbeat callback to notify the orchestrator this agent is alive.
55
55
  */
56
- export async function executeScopedTool(name, args, workspaceRoot, agentId, bus, locks, onHeartbeat) {
56
+ export async function executeScopedTool(name, args, workspaceRoot, agentId, bus, locks, onProgress) {
57
57
  // Update heartbeat so the orchestrator knows we are making progress
58
- onHeartbeat();
58
+ onProgress();
59
59
  const timestamp = Date.now();
60
60
  let actionDesc = 'Executed';
61
61
  let targetDesc = 'workspace';
@@ -99,7 +99,9 @@ export async function executeScopedTool(name, args, workspaceRoot, agentId, bus,
99
99
  lockedFile = args.filePath;
100
100
  if (lockedFile) {
101
101
  debugLog(`Agent "${agentId}" requesting lock for "${lockedFile}" (tool: ${name})`);
102
+ onProgress(`waiting for lock on ${lockedFile.split('/').pop()}...`);
102
103
  const lockRes = await locks.acquire(lockedFile, agentId);
104
+ onProgress(`acquired lock, executing...`);
103
105
  diffContext = lockRes.previousDiff;
104
106
  if (lockRes.forceReleased) {
105
107
  debugLog(`Agent "${agentId}" got forced lock on "${lockedFile}" (previous owner stalled)`);
@@ -66,8 +66,11 @@ export class SubAgentRunner {
66
66
  * Updates the heartbeat. Passed to `executeScopedTool` to ensure the agent
67
67
  * isn't marked as stalled while performing long-running commands.
68
68
  */
69
- pingHeartbeat = () => {
69
+ pingHeartbeat = (msg) => {
70
70
  this.lastHeartbeat = Date.now();
71
+ if (msg && this.onProgress) {
72
+ this.onProgress(msg);
73
+ }
71
74
  };
72
75
  /**
73
76
  * Executes the sub-agent with a health monitor harness.
@@ -127,7 +130,7 @@ export class SubAgentRunner {
127
130
  }
128
131
  this.pingHeartbeat();
129
132
  if (this.onProgress) {
130
- this.onProgress(`[${this.taskId}] executing ${call.name}...`);
133
+ this.onProgress(`executing ${call.name}...`);
131
134
  }
132
135
  debugLog(`SubAgent [${this.taskId}]: Executing tool ${call.name}`);
133
136
  let responseData;
@@ -54,6 +54,11 @@ export async function detectVerificationCommand(workspaceRoot) {
54
54
  return 'go build ./...';
55
55
  }
56
56
  catch { }
57
+ try {
58
+ await fs.access(path.join(workspaceRoot, 'CMakeLists.txt'));
59
+ return 'cmake -B build && cmake --build build';
60
+ }
61
+ catch { }
57
62
  try {
58
63
  await fs.access(path.join(workspaceRoot, 'pom.xml'));
59
64
  return 'mvn clean compile test-compile';
@@ -222,12 +227,30 @@ Please fix these errors using the modify_file tool.`;
222
227
  }
223
228
  import { auditFilePerformance, formatAuditForModel, formatAuditForTerminal, isAuditableFile } from '../utils/performanceAuditor.js';
224
229
  export async function verifyChangedFiles(workspaceRoot, filePaths, abortSignal) {
225
- if (filePaths.length === 0)
226
- return { errors: null, warnings: null };
227
230
  const errors = [];
228
231
  const perfAudits = [];
232
+ // Determine the effective verification root
233
+ let effectiveWorkspaceRoot = workspaceRoot;
234
+ if (filePaths.length > 0) {
235
+ let currentDir = path.dirname(filePaths[0]);
236
+ if (!path.isAbsolute(currentDir)) {
237
+ currentDir = path.resolve(workspaceRoot, currentDir);
238
+ }
239
+ let limit = 20;
240
+ while (currentDir !== path.dirname(currentDir) && limit > 0) {
241
+ const cmd = await detectVerificationCommand(currentDir);
242
+ if (cmd !== null) {
243
+ effectiveWorkspaceRoot = currentDir;
244
+ break;
245
+ }
246
+ if (currentDir === workspaceRoot)
247
+ break;
248
+ currentDir = path.dirname(currentDir);
249
+ limit--;
250
+ }
251
+ }
229
252
  // 1. Project-level build check (e.g., npm run build)
230
- const buildResult = await runVerification(workspaceRoot, abortSignal);
253
+ const buildResult = await runVerification(effectiveWorkspaceRoot, abortSignal);
231
254
  if (buildResult && !buildResult.success) {
232
255
  if (buildResult.aborted) {
233
256
  return '[Verification Aborted]';
@@ -65,5 +65,5 @@
65
65
  ]
66
66
  }
67
67
  },
68
- "version": "2.2.0"
68
+ "version": "2.2.2"
69
69
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "minovative-mind-cli",
3
3
  "description": "An automated AI agent powered by Vertex AI that helps you write software",
4
- "version": "2.2.0",
4
+ "version": "2.2.2",
5
5
  "author": "Daniel Ward",
6
6
  "bin": {
7
7
  "minovative-mind-cli": "bin/run.js"