minovative-mind-cli 1.0.3 → 1.1.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.
@@ -24,7 +24,7 @@ export default class DefaultCommand extends Command {
24
24
  idToken = await getAuthorizedIdToken();
25
25
  }
26
26
  p.log.info(`${pc.dim('Workspace:')} ${pc.cyan(workspaceRoot)}`);
27
- p.log.info(`${pc.dim('Commands:')} Type ${pc.yellow('/')} to open the command menu. Type ${pc.yellow('exit')} to leave.`);
27
+ p.log.info(`${pc.dim('Commands:')} Type ${pc.yellow('/')} to open the command menu and "${pc.yellow('stop')}" to stop the ai generation. Type ${pc.yellow('exit')} to leave.`);
28
28
  await startAgentLoop(workspaceRoot, this.config.version);
29
29
  }
30
30
  }
@@ -24,13 +24,13 @@ export declare function modifyFile(workspaceRoot: string, filePath: string, edit
24
24
  replaceContent: string;
25
25
  }>): Promise<ToolResult>;
26
26
  export declare function listDirectory(workspaceRoot: string, dirPath: string, maxDepth?: number): Promise<ToolResult>;
27
- export declare function runCommand(workspaceRoot: string, command: string): Promise<ToolResult>;
27
+ export declare function runCommand(workspaceRoot: string, command: string, abortSignal?: AbortSignal): Promise<ToolResult>;
28
28
  export declare function grepSearch(workspaceRoot: string, pattern: string, fileGlob?: string): Promise<ToolResult>;
29
29
  export declare function traceDependencies(workspaceRoot: string, filePath: string, direction?: string, maxDepth?: number): Promise<ToolResult>;
30
30
  export declare function findRecentChanges(workspaceRoot: string, dirPath?: string, minutes?: number, maxDepth?: number): Promise<ToolResult>;
31
- export declare function createAndRunTest(workspaceRoot: string, language: string, code: string): Promise<ToolResult>;
31
+ export declare function runDebugScript(workspaceRoot: string, language: string, code: string, abortSignal?: AbortSignal): Promise<ToolResult>;
32
32
  /**
33
33
  * Dispatches a function call from the model to the appropriate local tool.
34
34
  * Returns the tool result as a string to feed back to the model.
35
35
  */
36
- export declare function executeTool(workspaceRoot: string, toolName: string, args: Record<string, unknown>): Promise<ToolResult>;
36
+ export declare function executeTool(workspaceRoot: string, toolName: string, args: Record<string, unknown>, abortSignal?: AbortSignal): Promise<ToolResult>;
@@ -222,8 +222,8 @@ export const toolDeclarations = [
222
222
  },
223
223
  },
224
224
  {
225
- name: 'create_and_run_test',
226
- description: 'Write a snippet of code (e.g. a unit test or debug script) to a temporary file, execute it using the specified runtime, and return the exact standard output and standard error logs. Use this to actively test and debug code during execution and verification loops.',
225
+ name: 'run_debug_script',
226
+ description: 'Write a debug script (using console.log, print, etc.) to a temporary file, execute it using the specified runtime, and return the exact standard output and standard error. Use this to actively debug the codebase, inspect variables, or solve problems during execution.',
227
227
  parameters: {
228
228
  type: SchemaType.OBJECT,
229
229
  properties: {
@@ -312,6 +312,15 @@ export async function readFile(workspaceRoot, filePath, startLine, endLine, targ
312
312
  }
313
313
  content = lines.slice(start - 1, end).join('\n');
314
314
  }
315
+ else {
316
+ const lines = content.split('\n');
317
+ if (lines.length > 1500) {
318
+ return {
319
+ output: '',
320
+ error: `File is too large (${lines.length} lines). You MUST use startLine/endLine or targetElements to read specific chunks instead of dumping the whole file.`,
321
+ };
322
+ }
323
+ }
315
324
  const attrLines = startLine || endLine ? ` lines="${startLine || 1}-${endLine || 'end'}"` : '';
316
325
  const attrTargets = targetElements && targetElements.length > 0 ? ` elements="${targetElements.join(',')}"` : '';
317
326
  const wrappedContent = `<workspace_file path="${filePath}"${attrLines}${attrTargets}>\n<content_data><![CDATA[\n${sanitizeForCDATA(content)}\n]]></content_data>\n</workspace_file>`;
@@ -437,7 +446,9 @@ export async function modifyFile(workspaceRoot, filePath, edits) {
437
446
  }
438
447
  changeLogger.logChange(filePath, existing, 'modify');
439
448
  await atomicWriteFile(absPath, modified, 'utf-8');
440
- return { output: `Successfully applied ${edits.length} edit(s) to "${filePath}".\\nStrategies used:\\n${strategies.join('\\n')}` };
449
+ return {
450
+ output: `Successfully applied ${edits.length} edit(s) to "${filePath}".\\nStrategies used:\\n${strategies.join('\\n')}`,
451
+ };
441
452
  }
442
453
  catch (err) {
443
454
  if (attempt === MAX_MODIFY_RETRIES) {
@@ -504,12 +515,13 @@ export async function listDirectory(workspaceRoot, dirPath, maxDepth = 3) {
504
515
  };
505
516
  }
506
517
  }
507
- export async function runCommand(workspaceRoot, command) {
518
+ export async function runCommand(workspaceRoot, command, abortSignal) {
508
519
  try {
509
520
  const { stdout, stderr } = await execAsync(command, {
510
521
  cwd: workspaceRoot,
511
522
  timeout: 60_000, // 60 second timeout
512
523
  maxBuffer: 1024 * 1024 * 2, // 2 MB buffer
524
+ signal: abortSignal,
513
525
  });
514
526
  let output = [stdout, stderr].filter(Boolean).join('\n');
515
527
  // Truncate command output to prevent memory blowout from massive build logs
@@ -574,7 +586,7 @@ export async function grepSearch(workspaceRoot, pattern, fileGlob) {
574
586
  }
575
587
  export async function traceDependencies(workspaceRoot, filePath, direction, maxDepth) {
576
588
  try {
577
- const dir = (direction === 'forward' || direction === 'reverse') ? direction : 'both';
589
+ const dir = direction === 'forward' || direction === 'reverse' ? direction : 'both';
578
590
  const depth = maxDepth && maxDepth > 0 ? Math.min(maxDepth, 5) : 3;
579
591
  const result = await findDependencies(workspaceRoot, filePath, dir, depth);
580
592
  const formatted = formatDependencyResult(result);
@@ -590,7 +602,7 @@ export async function findRecentChanges(workspaceRoot, dirPath = '.', minutes =
590
602
  try {
591
603
  const absPath = resolveAndValidatePath(workspaceRoot, dirPath);
592
604
  const { ignoredDirs, ignoredFiles } = await getIgnoredPaths(workspaceRoot);
593
- const thresholdMs = Date.now() - (minutes * 60 * 1000);
605
+ const thresholdMs = Date.now() - minutes * 60 * 1000;
594
606
  const recentFiles = [];
595
607
  async function walk(currentPath, depth) {
596
608
  if (depth > maxDepth)
@@ -636,7 +648,7 @@ export async function findRecentChanges(workspaceRoot, dirPath = '.', minutes =
636
648
  // Sort by most recently modified first
637
649
  recentFiles.sort((a, b) => b.mtime - a.mtime);
638
650
  // Format output
639
- const lines = recentFiles.map(f => {
651
+ const lines = recentFiles.map((f) => {
640
652
  const minsAgo = Math.max(0, Math.round((Date.now() - f.mtime) / 60000));
641
653
  return `- ${f.path} (${minsAgo} minutes ago)`;
642
654
  });
@@ -653,7 +665,7 @@ export async function findRecentChanges(workspaceRoot, dirPath = '.', minutes =
653
665
  }
654
666
  }
655
667
  // ─── Tool Dispatcher ─────────────────────────────────────────────────
656
- export async function createAndRunTest(workspaceRoot, language, code) {
668
+ export async function runDebugScript(workspaceRoot, language, code, abortSignal) {
657
669
  const extMap = {
658
670
  node: '.js',
659
671
  'ts-node': '.ts',
@@ -691,7 +703,7 @@ export async function createAndRunTest(workspaceRoot, language, code) {
691
703
  return { output: '', error: `Unsupported language runtime: ${language}` };
692
704
  }
693
705
  try {
694
- const { stdout, stderr } = await execAsync(cmd, { cwd: workspaceRoot, timeout: 15_000 });
706
+ const { stdout, stderr } = await execAsync(cmd, { cwd: workspaceRoot, timeout: 15_000, signal: abortSignal });
695
707
  const out = stdout.trim();
696
708
  const errOut = stderr.trim();
697
709
  let finalOutput = '';
@@ -715,7 +727,10 @@ export async function createAndRunTest(workspaceRoot, language, code) {
715
727
  }
716
728
  }
717
729
  catch (err) {
718
- return { output: '', error: `Failed to create or run test script: ${err instanceof Error ? err.message : String(err)}` };
730
+ return {
731
+ output: '',
732
+ error: `Failed to create or run test script: ${err instanceof Error ? err.message : String(err)}`,
733
+ };
719
734
  }
720
735
  finally {
721
736
  try {
@@ -734,7 +749,7 @@ export async function createAndRunTest(workspaceRoot, language, code) {
734
749
  * Dispatches a function call from the model to the appropriate local tool.
735
750
  * Returns the tool result as a string to feed back to the model.
736
751
  */
737
- export async function executeTool(workspaceRoot, toolName, args) {
752
+ export async function executeTool(workspaceRoot, toolName, args, abortSignal) {
738
753
  switch (toolName) {
739
754
  case 'read_file':
740
755
  return readFile(workspaceRoot, args.filePath, args.startLine, args.endLine, args.targetElements);
@@ -749,9 +764,9 @@ export async function executeTool(workspaceRoot, toolName, args) {
749
764
  case 'list_directory':
750
765
  return listDirectory(workspaceRoot, args.dirPath, args.maxDepth ?? 3);
751
766
  case 'run_command':
752
- return runCommand(workspaceRoot, args.command);
753
- case 'create_and_run_test':
754
- return createAndRunTest(workspaceRoot, args.language, args.code);
767
+ return runCommand(workspaceRoot, args.command, abortSignal);
768
+ case 'run_debug_script':
769
+ return runDebugScript(workspaceRoot, args.language, args.code, abortSignal);
755
770
  case 'grep_search':
756
771
  return grepSearch(workspaceRoot, args.pattern, args.fileGlob);
757
772
  case 'find_dependencies':
@@ -1,21 +1,145 @@
1
+ /**
2
+ * @fileoverview Core agent execution service.
3
+ *
4
+ * This file coordinates the central execution environment for Minovative Mind,
5
+ * an AI-powered developer CLI. It orchestrates:
6
+ *
7
+ * 1. **Interactive REPL Loop**: Collects multiline user input, supports slash-commands,
8
+ * reverting changes, switching models, auto-commit, and clipboard paste injection.
9
+ * 2. **Context-Aware Investigation**: Gathers repo-wide context and compresses it using
10
+ * low-cost, high-speed LLM summarization.
11
+ * 3. **Autonomous Tool Loop (processResponse)**: Allows the AI model to execute file-system,
12
+ * shell-command, and search operations, feeding outcomes back in a recursive, multi-turn loop.
13
+ * 4. **Asynchronous Interrupt Handling (AsyncInputHandler)**: Intercepts keyboard events
14
+ * on standard input to let users pause execution, queue live feedback, or abort generation.
15
+ * 5. **Self-Correction Pipeline**: Compares modified files against TS/Linter diagnostics,
16
+ * providing feedback on syntax or semantic failures back to the AI for self-healing.
17
+ */
1
18
  import * as p from '@clack/prompts';
19
+ /**
20
+ * Asynchronous Input Handler (AsyncInputHandler)
21
+ *
22
+ * This class intercepts user keyboard input from standard input (stdin) while
23
+ * background processes (e.g., Gemini model generations or long shell tool runs)
24
+ * are executing. It provides seamless execution management by letting users:
25
+ *
26
+ * 1. **Pause execution** at any time by pressing a key.
27
+ * 2. **Queue feedback** ("chained messages") without waiting for the entire run to finish.
28
+ * 3. **Force abort** the current model or tool run by entering "stop" or "stop!".
29
+ *
30
+ * ### Raw Mode & Terminal States
31
+ * In normal CLI execution, Node.js waits for a line feed (Enter) before emitting input.
32
+ * To intercept immediate keystrokes, we put `process.stdin` into *raw mode*.
33
+ * While in raw mode, we listen for direct data buffers.
34
+ * To avoid visual conflicts with our logging output and Clack's CLI spinners,
35
+ * we dynamically pause spinners, detach listeners, disable raw mode, open standard
36
+ * interactive text-prompt forms, and resume raw mode and spinners upon completion.
37
+ */
2
38
  export declare class AsyncInputHandler {
39
+ /** Queue of pending user feedback/instructions typed during active background execution */
3
40
  private queue;
41
+ /** Guard flag preventing multiple simultaneous input prompt overlays */
4
42
  private isPrompting;
43
+ /** Reference to the Clack CLI spinner which must be paused/restarted during prompts */
5
44
  private spinner;
45
+ /** Stores the terminal's raw mode configuration state before handler activation */
6
46
  private originalRawMode;
47
+ /** Indicates whether the input handler is currently inactive/stopped */
7
48
  private stopped;
49
+ /** Reference to the AbortController controlling the active AI request to trigger cancellations */
8
50
  private ac;
51
+ /**
52
+ * Registers the active AbortController for the current AI request.
53
+ * This is triggered when the user commands a process cancel (e.g., typing "stop").
54
+ *
55
+ * @param ac - The AbortController controlling the current generation.
56
+ */
9
57
  setAbortController(ac: AbortController): void;
58
+ /**
59
+ * Determines if a user-prompt dialog is actively running.
60
+ * Useful for coordinating other console logging output to avoid UI overlap.
61
+ *
62
+ * @returns True if a text prompt is currently displayed, false otherwise.
63
+ */
10
64
  isCurrentlyPrompting(): boolean;
65
+ /**
66
+ * Blocks and waits until any active prompting action is completed.
67
+ * Guarantees terminal stdout is clean before resuming logs.
68
+ */
11
69
  waitForPrompt(): Promise<void>;
70
+ /**
71
+ * Core stdin event listener. Detects pressed keys, pauses background visual elements,
72
+ * handles exit interrupts, and opens a Clack text dialog for input.
73
+ *
74
+ * Handles recovery of the standard input stream and raw mode states even if
75
+ * errors occur during prompt initialization.
76
+ *
77
+ * @param chunk - The raw terminal buffer containing keypress data.
78
+ * @private
79
+ */
12
80
  private onData;
81
+ /**
82
+ * Starts intercepting keystrokes and enables raw terminal processing.
83
+ * Saves the original raw mode configuration to ensure a clean restoration later.
84
+ *
85
+ * @param spinner - The current active Clack spinner UI reference, if any.
86
+ */
13
87
  start(spinner?: ReturnType<typeof p.spinner>): void;
88
+ /**
89
+ * Disables raw mode, stops intercepting keystrokes, and restores
90
+ * the terminal stdin stream to its original raw/cooked state.
91
+ */
14
92
  stop(): void;
93
+ /**
94
+ * Retrieves and flushes all user feedback messages accumulated during execution.
95
+ *
96
+ * @returns A concatenated string of all queued user messages separated by newlines,
97
+ * or an empty string if nothing was queued.
98
+ */
15
99
  getAndClear(): string;
16
100
  }
17
101
  /**
18
102
  * Starts the interactive agent chat loop.
19
103
  * Runs until the user types "exit", "quit", or presses Ctrl+C.
104
+ *
105
+ * @param workspaceRoot - The root directory of the workspace.
106
+ * @param version - The active version of the CLI utility.
107
+ */
108
+ /**
109
+ * Starts and orchestrates the primary interactive command-line interface (REPL) loop.
110
+ *
111
+ * This is the central control loop of the CLI application. It runs indefinitely until
112
+ * the user explicitly issues an exit directive (e.g., typing "exit", "quit", `/` command menu selections,
113
+ * or using keyboard breaks like Ctrl+C).
114
+ *
115
+ * ### Architectural Pipeline:
116
+ * 1. **User Input Collection**:
117
+ * - Captures multiline text strings by checking for trailing backslash characters (`\`).
118
+ * - Prevents visual terminal glitches during multi-line typing on varying themes.
119
+ * - Automatically handles command menu redirection when users enter a forward slash (`/`).
120
+ * - Intercepts and filters OS clipboard paste buffers to allow inserting massive scripts cleanly.
121
+ *
122
+ * 2. **Slash Commands Processing**:
123
+ * - `/paste`: Interactively captures large copy-pasted blocks using EOF tracking.
124
+ * - `/clear`: Hard-clears active terminal histories and resets session state.
125
+ * - `/models`: Dynamic runtime model hot-swapping (e.g., swapping between Flash and Pro variants).
126
+ * - `/debug`: Toggles active runtime execution telemetry logging.
127
+ * - `/auto-approve`: Grants blanket terminal script permissions to bypass prompt approval blocks.
128
+ * - `/revert`: Pops the most recent change-set log and rolls back mutated files to original states.
129
+ * - `/commit`: Performs git diff staging, executes model summaries to write micro-commits, and executes local git commits.
130
+ *
131
+ * 3. **Workspace Context Gathering & Intention Routing**:
132
+ * - Leverages the Context Agent (`gatherContext`) to perform exploratory scans of the active repository.
133
+ * - Identifies user intention to hot-swap system prompt strategies (`CHAT` for conversational assistance vs. `EXECUTE` for multi-turn modifications).
134
+ * - Compresses gathered repo structures and file contents using high-speed Flash-Lite engines to respect context boundaries and budget costs.
135
+ *
136
+ * 4. **Model Execution & Self-Correction Feedback Loop**:
137
+ * - Passes the target request to Gemini alongside compressed workspace injections.
138
+ * - Recursively processes tool invocations via `processResponse`.
139
+ * - Runs static validation checks (`verifyChangedFiles`) against mutated files to check for compiler/linter bugs.
140
+ * - If files fail validation, enters a self-healing loop by submitting raw diagnostic errors directly back to the AI for remediation.
141
+ *
142
+ * @param workspaceRoot - The relative or absolute path representing the active workspace environment.
143
+ * @param version - The Semantic Version string of the active tool distribution.
20
144
  */
21
145
  export declare function startAgentLoop(workspaceRoot: string, version: string): Promise<void>;