minovative-mind-cli 2.12.0 → 2.13.1

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 CHANGED
@@ -194,6 +194,19 @@ _⚠️\*Support is limited or requires custom local system tooling/environment
194
194
 
195
195
  ---
196
196
 
197
+ ## 💡 Performance & IDE Optimization
198
+
199
+ During heavy, multi-turn AI agent sessions, Minovative Mind CLI includes built-in architectural optimizations to eliminate IDE lag, CPU spikes, and dev-server interference:
200
+
201
+ - **Ephemeral Scratch Isolation (`os.tmpdir()`)**: Diagnostic probes, validation scripts, and benchmark tests execute in a sandboxed OS temp directory (`os.tmpdir()`) rather than writing disposable files into your workspace root. This prevents file watcher churn (`fsevents`, `inotify`), avoids Language Server Protocol (LSP) re-indexing storms (TSServer, Pyright, rust-analyzer), and prevents running dev servers (Vite, Turbopack, Nodemon) from triggering unneeded full-page reloads.
202
+ - **Automatic IDE Watcher Exclusions (`.vscode/settings.json`)**: On session startup, the CLI automatically and non-destructively ensures that `.minovativemind/**`, `.tmp/**`, and `scratch/**` are added to `files.watcherExclude` and `search.exclude`. This stops IDE background processes from burning CPU on internal telemetry, chat session logs, and cache files.
203
+ - **Clean Git Status Synchronization**: The CLI automatically ensures `.minovativemind/`, `.tmp/`, and `scratch/` are excluded from `.gitignore`, `.dockerignore`, and `.npmignore`, keeping your IDE's Source Control pane fast and responsive.
204
+ - **Terminal Render Efficiency**: For long-running evaluation sweeps (`eval`) or massive test outputs, minimizing the terminal pane or running it in the background pauses Electron/xterm.js GPU canvas repaints and frees up system resources.
205
+
206
+ For full tuning recommendations and configuration details, see the [Performance & IDE Optimization Guide](file:///Users/danielward/Developer/Work%20Projects/minovative-mind-cli/docs/PERFORMANCE_GUIDE.md).
207
+
208
+ ---
209
+
197
210
  ## Legal
198
211
 
199
212
  [Terms of Service](https://www.minovativemind.dev/legal/cli-terms) ·
@@ -8,6 +8,7 @@ import { startAgentLoop } from '../services/agent.js';
8
8
  import { getAuthorizedIdToken, login } from '../services/auth.js';
9
9
  import { printLogo, brandBg, brandFg } from '../utils/logo.js';
10
10
  import { updateWorkspaceStatus } from '../services/workspace.js';
11
+ import { optimizeWorkspaceIDESettings } from '../services/ideOptimization.js';
11
12
  /**
12
13
  * @class DefaultCommand
13
14
  * @extends Command
@@ -80,10 +81,11 @@ Chat Controls:
80
81
  }
81
82
  p.log.info(`${pc.dim('Workspace:')} ${brandFg(workspaceRoot)}`);
82
83
  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.`);
83
- // Update workspace status in the background
84
+ // Update workspace status and optimize IDE watcher settings in the background
84
85
  if (idToken) {
85
86
  updateWorkspaceStatus(idToken, workspaceRoot).catch(() => { });
86
87
  }
88
+ optimizeWorkspaceIDESettings(workspaceRoot).catch(() => { });
87
89
  const { workspaceRegistry } = await import('../services/workspaceRegistry.js');
88
90
  workspaceRegistry.init();
89
91
  await startAgentLoop(workspaceRoot, this.config.version);
@@ -2,6 +2,7 @@ import * as p from '@clack/prompts';
2
2
  import pc from 'picocolors';
3
3
  import { promises as fs } from 'node:fs';
4
4
  import path from 'node:path';
5
+ import os from 'node:os';
5
6
  import { exec } from 'node:child_process';
6
7
  import { promisify } from 'node:util';
7
8
  import crypto from 'node:crypto';
@@ -1052,10 +1053,11 @@ Strict Formatting Rules:
1052
1053
  .replace(/```\s*$/gm, '')
1053
1054
  .trim();
1054
1055
  commitSpinner.message('Committing...');
1055
- const tmpMsgPath = path.join(workspaceRoot, '.gemini-commit-msg.tmp');
1056
+ const nonce = `${Date.now()}-${Math.random().toString(36).substring(2, 7)}`;
1057
+ const tmpMsgPath = path.join(os.tmpdir(), `.mino-commit-msg-${nonce}.tmp`);
1056
1058
  await fs.writeFile(tmpMsgPath, commitMsg, 'utf-8');
1057
1059
  try {
1058
- await execAsync(`git commit -F .gemini-commit-msg.tmp`, { cwd: workspaceRoot });
1060
+ await execAsync(`git commit -F "${tmpMsgPath}"`, { cwd: workspaceRoot });
1059
1061
  }
1060
1062
  finally {
1061
1063
  await fs.rm(tmpMsgPath, { force: true });
@@ -91,7 +91,7 @@ export const toolDeclarations = [
91
91
  },
92
92
  {
93
93
  name: 'write_file',
94
- description: 'Create a new file or completely overwrite an existing file with the provided content. Use modify_file for targeted edits instead. For files in external workspaces, prefix the path with @alias/ (e.g., @backend/src/routes.ts).',
94
+ description: 'Create a new permanent file or completely overwrite an existing file with the provided content. Use modify_file for targeted edits instead. NEVER use write_file to create disposable test or scratch scripts in the workspace root — use run_debug_script instead to prevent IDE file watcher churn and dev-server reloads. For files in external workspaces, prefix the path with @alias/ (e.g., @backend/src/routes.ts).',
95
95
  parameters: {
96
96
  type: SchemaType.OBJECT,
97
97
  properties: {
@@ -278,7 +278,7 @@ export const toolDeclarations = [
278
278
  },
279
279
  {
280
280
  name: 'run_debug_script',
281
- description: 'Write a disposable script to a temporary file, execute it using the specified runtime, and return the exact standard output and standard error. ' +
281
+ description: 'Write a disposable script to a sandboxed temporary file in os.tmpdir(), execute it using the specified runtime, and return the exact standard output and standard error without polluting the workspace or triggering IDE file watchers. ' +
282
282
  'Use this to: (1) actively debug the codebase by inspecting variables or logging values, ' +
283
283
  '(2) validate your changes by importing the modified module and asserting expected behavior with edge-case inputs, ' +
284
284
  '(3) run quick sanity checks (e.g., verify a config file parses correctly, confirm exports are intact after a refactor, or check that a function returns the expected output), ' +
@@ -510,6 +510,7 @@ const DEFAULT_IGNORED_DIRS = new Set([
510
510
  '.tmp',
511
511
  'temp',
512
512
  'tmp',
513
+ 'scratch',
513
514
  '.minovativemind',
514
515
  ]);
515
516
  const DEFAULT_IGNORED_FILES = new Set([
@@ -755,7 +756,7 @@ export async function readFile(workspaceRoot, filePath, startLine, endLine, targ
755
756
  }
756
757
  else {
757
758
  const lines = content.split('\n');
758
- if (lines.length > 500) {
759
+ if (lines.length > 2500) {
759
760
  return {
760
761
  output: '',
761
762
  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.`,
@@ -80,6 +80,10 @@ function collapseHistoricalOutput(val, threshold = HISTORICAL_TOOL_OUTPUT_THRESH
80
80
  if (val.length <= threshold || val.includes('[Historical tool output collapsed')) {
81
81
  return val;
82
82
  }
83
+ // Never collapse workspace file contents or source code blocks
84
+ if (val.includes('<workspace_file') || val.includes('<content_data>')) {
85
+ return val;
86
+ }
83
87
  return `${val.substring(0, threshold)}${COLLAPSED_TOOL_OUTPUT_MARKER}`;
84
88
  }
85
89
  function truncatePartText(text) {
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Optimizes the `.vscode/settings.json` file in the given workspace directory.
3
+ * Performs an idempotent, non-destructive merge that preserves all existing user settings,
4
+ * formatting preferences, and custom watcher rules.
5
+ *
6
+ * @param targetRoot - The workspace root directory to optimize.
7
+ */
8
+ export declare function optimizeSingleWorkspaceIDESettings(targetRoot: string): Promise<void>;
9
+ /**
10
+ * Automatically and silently optimizes IDE settings and ignore rules across the primary workspace
11
+ * and all registered sub-workspaces in the background on startup.
12
+ *
13
+ * @param workspaceRoot - The primary workspace root directory.
14
+ */
15
+ export declare function optimizeWorkspaceIDESettings(workspaceRoot: string): Promise<void>;
@@ -0,0 +1,169 @@
1
+ import * as fs from 'node:fs';
2
+ import * as path from 'node:path';
3
+ import { debugLog } from '../utils/logger.js';
4
+ import { ensureIgnored } from '../utils/projectStorage.js';
5
+ import { workspaceRegistry } from './workspaceRegistry.js';
6
+ /**
7
+ * Recommended file watcher exclusion patterns to prevent IDE file watchers
8
+ * (fsevents, inotify, ReadDirectoryChangesW) and Language Server Protocol indexers
9
+ * from churning CPU on internal CLI state, logs, and temporary scratch files.
10
+ */
11
+ const RECOMMENDED_WATCHER_EXCLUDES = {
12
+ '**/.minovativemind/**': true,
13
+ '**/.tmp/**': true,
14
+ '**/tmp/**': true,
15
+ '**/scratch/**': true,
16
+ };
17
+ /**
18
+ * Recommended search exclusion patterns to keep agent internal data and temp files
19
+ * out of fuzzy file search (Cmd+P) and global text search (Cmd+Shift+F).
20
+ */
21
+ const RECOMMENDED_SEARCH_EXCLUDES = {
22
+ '**/.minovativemind': true,
23
+ '**/.tmp': true,
24
+ '**/scratch': true,
25
+ };
26
+ /**
27
+ * Safely strips single-line and multi-line comments and trailing commas from a JSONC string
28
+ * while strictly preserving string literals (such as glob patterns containing "//").
29
+ */
30
+ function parseJsonc(content) {
31
+ let insideString = false;
32
+ let escaped = false;
33
+ let result = '';
34
+ for (let i = 0; i < content.length; i++) {
35
+ const char = content[i];
36
+ const nextChar = content[i + 1];
37
+ if (insideString) {
38
+ result += char;
39
+ if (char === '\\' && !escaped) {
40
+ escaped = true;
41
+ }
42
+ else {
43
+ if (char === '"' && !escaped) {
44
+ insideString = false;
45
+ }
46
+ escaped = false;
47
+ }
48
+ continue;
49
+ }
50
+ if (char === '"') {
51
+ insideString = true;
52
+ result += char;
53
+ continue;
54
+ }
55
+ // Single-line comment // outside strings
56
+ if (char === '/' && nextChar === '/') {
57
+ const lineEnd = content.indexOf('\n', i + 2);
58
+ if (lineEnd === -1) {
59
+ break;
60
+ }
61
+ i = lineEnd - 1;
62
+ continue;
63
+ }
64
+ // Block comment /* ... */ outside strings
65
+ if (char === '/' && nextChar === '*') {
66
+ const blockEnd = content.indexOf('*/', i + 2);
67
+ if (blockEnd === -1) {
68
+ break;
69
+ }
70
+ i = blockEnd + 1;
71
+ continue;
72
+ }
73
+ result += char;
74
+ }
75
+ const sanitized = result.replace(/,\s*([}\]])/g, '$1').trim();
76
+ if (!sanitized) {
77
+ return {};
78
+ }
79
+ return JSON.parse(sanitized);
80
+ }
81
+ /**
82
+ * Optimizes the `.vscode/settings.json` file in the given workspace directory.
83
+ * Performs an idempotent, non-destructive merge that preserves all existing user settings,
84
+ * formatting preferences, and custom watcher rules.
85
+ *
86
+ * @param targetRoot - The workspace root directory to optimize.
87
+ */
88
+ export async function optimizeSingleWorkspaceIDESettings(targetRoot) {
89
+ try {
90
+ const vscodeDir = path.join(targetRoot, '.vscode');
91
+ const settingsPath = path.join(vscodeDir, 'settings.json');
92
+ let settings = {};
93
+ let fileExisted = false;
94
+ if (fs.existsSync(settingsPath)) {
95
+ try {
96
+ const raw = await fs.promises.readFile(settingsPath, 'utf-8');
97
+ settings = parseJsonc(raw);
98
+ fileExisted = true;
99
+ }
100
+ catch (err) {
101
+ debugLog(`Failed to parse existing .vscode/settings.json at ${settingsPath}: ${err instanceof Error ? err.message : String(err)}`);
102
+ return;
103
+ }
104
+ }
105
+ let modified = false;
106
+ // 1. Ensure files.watcherExclude
107
+ const watcherExclude = typeof settings['files.watcherExclude'] === 'object' && settings['files.watcherExclude'] !== null
108
+ ? { ...settings['files.watcherExclude'] }
109
+ : {};
110
+ for (const [pattern, val] of Object.entries(RECOMMENDED_WATCHER_EXCLUDES)) {
111
+ if (watcherExclude[pattern] === undefined) {
112
+ watcherExclude[pattern] = val;
113
+ modified = true;
114
+ }
115
+ }
116
+ if (modified || settings['files.watcherExclude'] === undefined) {
117
+ settings['files.watcherExclude'] = watcherExclude;
118
+ }
119
+ // 2. Ensure search.exclude
120
+ const searchExclude = typeof settings['search.exclude'] === 'object' && settings['search.exclude'] !== null
121
+ ? { ...settings['search.exclude'] }
122
+ : {};
123
+ for (const [pattern, val] of Object.entries(RECOMMENDED_SEARCH_EXCLUDES)) {
124
+ if (searchExclude[pattern] === undefined) {
125
+ searchExclude[pattern] = val;
126
+ modified = true;
127
+ }
128
+ }
129
+ if (modified || settings['search.exclude'] === undefined) {
130
+ settings['search.exclude'] = searchExclude;
131
+ }
132
+ // Only write if changes were made or if .vscode directory exists and settings are missing
133
+ if (modified) {
134
+ if (!fs.existsSync(vscodeDir)) {
135
+ await fs.promises.mkdir(vscodeDir, { recursive: true });
136
+ }
137
+ await fs.promises.writeFile(settingsPath, JSON.stringify(settings, null, 2) + '\n', 'utf-8');
138
+ debugLog(`Optimized IDE settings at ${settingsPath} (fileExisted=${fileExisted})`);
139
+ }
140
+ }
141
+ catch (err) {
142
+ debugLog(`Error optimizing IDE settings for ${targetRoot}: ${err instanceof Error ? err.message : String(err)}`);
143
+ }
144
+ }
145
+ /**
146
+ * Automatically and silently optimizes IDE settings and ignore rules across the primary workspace
147
+ * and all registered sub-workspaces in the background on startup.
148
+ *
149
+ * @param workspaceRoot - The primary workspace root directory.
150
+ */
151
+ export async function optimizeWorkspaceIDESettings(workspaceRoot) {
152
+ try {
153
+ // 1. Ensure ignore rules (.gitignore, .dockerignore, .minovativemindignore) are updated
154
+ ensureIgnored(workspaceRoot);
155
+ // 2. Optimize primary workspace IDE settings
156
+ await optimizeSingleWorkspaceIDESettings(workspaceRoot);
157
+ // 3. Optimize registered sub-workspaces if present
158
+ const subWorkspaces = workspaceRegistry.list();
159
+ for (const workspace of subWorkspaces) {
160
+ if (workspace.absolutePath && workspace.absolutePath !== workspaceRoot && fs.existsSync(workspace.absolutePath)) {
161
+ ensureIgnored(workspace.absolutePath);
162
+ await optimizeSingleWorkspaceIDESettings(workspace.absolutePath);
163
+ }
164
+ }
165
+ }
166
+ catch (err) {
167
+ debugLog(`Background IDE optimization encountered an error: ${err instanceof Error ? err.message : String(err)}`);
168
+ }
169
+ }
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @fileoverview Two-Layer Message Bus for Sub-Agent Orchestration.
2
+ * @file Two-Layer Message Bus for Sub-Agent Orchestration.
3
3
  *
4
4
  * Provides the core inter-agent communication primitive for the orchestration system.
5
5
  * Two layers of communication:
@@ -68,6 +68,27 @@ export interface CompletionSignal {
68
68
  summary: string;
69
69
  exports: Record<string, string>;
70
70
  }
71
+ export declare const MUTATING_TOOLS: Set<string>;
72
+ export interface BusQueryOptions {
73
+ agentId: string;
74
+ file?: string;
75
+ fromAgent?: string;
76
+ type?: 'discovery' | 'warning' | 'request' | 'completion';
77
+ onlyMutations?: boolean;
78
+ targetFiles?: string[];
79
+ dependsOn?: string[];
80
+ advanceCursor?: boolean;
81
+ }
82
+ interface CoalescedEntry {
83
+ agentId: string;
84
+ tool: string;
85
+ target: string;
86
+ action: string;
87
+ count: number;
88
+ status: 'success' | 'error';
89
+ resultSummary?: string;
90
+ }
91
+ export declare function coalesceActivityEntries(entries: ActivityEntry[]): CoalescedEntry[];
71
92
  /**
72
93
  * Two-layer, disk-backed message bus for sub-agent coordination.
73
94
  *
@@ -79,41 +100,35 @@ export interface CompletionSignal {
79
100
  * a CLI crash and resume from the last known state.
80
101
  */
81
102
  export declare class MessageBus {
82
- private activityLog;
83
- private signals;
103
+ /** Maximum semantic signals any single agent can post */
104
+ static readonly MAX_SIGNALS_PER_AGENT = 50;
84
105
  private activityCursors;
85
- private signalCursors;
86
- private persistQueue;
106
+ private activityLog;
87
107
  private readonly persistPath;
108
+ private persistQueue;
109
+ private signalCursors;
110
+ private signals;
88
111
  private readonly workspaceRoot;
89
- /** Maximum semantic signals any single agent can post */
90
- static readonly MAX_SIGNALS_PER_AGENT = 50;
91
112
  constructor(workspaceRoot: string, conversationId: string);
92
113
  /**
93
- * Records a tool execution into the activity log. Called by the scoped tool
94
- * wrapper in `scopedTools.ts` zero cost to the agent.
114
+ * Formats a batch of activity entries into a compact, human-readable string
115
+ * without token-wasteful column whitespace padding. Coalesces repeated actions.
95
116
  */
96
- logActivity(entry: ActivityEntry): void;
117
+ static formatActivityEntries(entries: ActivityEntry[]): string;
97
118
  /**
98
- * Posts a semantic signal from an agent. These cost tokens (the agent calls
99
- * `post_message`) but carry intent that raw tool logs cannot express.
100
- *
101
- * Enforces per-agent signal cap to prevent runaway agents from flooding the bus.
102
- *
103
- * @returns `true` if the signal was accepted, `false` if the agent hit the cap.
119
+ * Formats semantic signals into a readable string for agent context injection.
104
120
  */
105
- postSignal(signal: BusSignal): boolean;
121
+ static formatSignals(signals: BusSignal[]): string;
106
122
  /**
107
- * Retrieves all unread activity entries and semantic signals for a specific agent.
108
- * Advances the agent's cursors so subsequent calls return only new entries.
109
- *
110
- * Filters out the requesting agent's own entries (an agent doesn't need to
111
- * re-read its own tool logs or signals).
123
+ * Clears all bus state and removes the persistence file.
124
+ * Called when orchestration completes successfully (no crash recovery needed).
112
125
  */
113
- getUnread(agentId: string): {
114
- activities: ActivityEntry[];
115
- signals: BusSignal[];
116
- };
126
+ cleanup(): Promise<void>;
127
+ /**
128
+ * Returns all activity entries for a specific agent (used for dead agent
129
+ * recovery — collecting partial progress before re-dispatch).
130
+ */
131
+ getAgentActivity(agentId: string): ActivityEntry[];
117
132
  /**
118
133
  * Returns the complete, unfiltered bus state for PM reconciliation.
119
134
  * Used after all sub-agents complete to give the PM full visibility.
@@ -122,11 +137,6 @@ export declare class MessageBus {
122
137
  activities: ActivityEntry[];
123
138
  signals: BusSignal[];
124
139
  };
125
- /**
126
- * Returns all activity entries for a specific agent (used for dead agent
127
- * recovery — collecting partial progress before re-dispatch).
128
- */
129
- getAgentActivity(agentId: string): ActivityEntry[];
130
140
  /**
131
141
  * Returns the total number of activity entries and signals in the bus.
132
142
  * Used for terminal display and diagnostics.
@@ -136,14 +146,48 @@ export declare class MessageBus {
136
146
  signalCount: number;
137
147
  };
138
148
  /**
139
- * Formats a batch of activity entries into a compact, human-readable string
140
- * suitable for injection into an agent's context window.
149
+ * Retrieves all unread activity entries and semantic signals for a specific agent.
150
+ * Defaults to state-mutating events and unicast signal routing to eliminate token noise.
141
151
  */
142
- static formatActivityEntries(entries: ActivityEntry[]): string;
152
+ getUnread(agentId: string, options?: {
153
+ onlyMutations?: boolean;
154
+ advanceCursor?: boolean;
155
+ }): {
156
+ activities: ActivityEntry[];
157
+ signals: BusSignal[];
158
+ };
143
159
  /**
144
- * Formats semantic signals into a readable string for agent context injection.
160
+ * Records a tool execution into the activity log. Called by the scoped tool
161
+ * wrapper in `scopedTools.ts` — zero cost to the agent.
145
162
  */
146
- static formatSignals(signals: BusSignal[]): string;
163
+ logActivity(entry: ActivityEntry): void;
164
+ /**
165
+ * Peeks urgent signals (breaking warnings or direct requests) that are relevant
166
+ * to a sub-agent's task scope without advancing its cursor. Used for in-band notice
167
+ * delivery in scopedTools.
168
+ */
169
+ peekUrgentSignals(agentId: string, scope?: {
170
+ targetFiles?: string[];
171
+ dependsOn?: string[];
172
+ }): BusSignal[];
173
+ /**
174
+ * Posts a semantic signal from an agent. These cost tokens (the agent calls
175
+ * `post_message`) but carry intent that raw tool logs cannot express.
176
+ *
177
+ * Enforces per-agent signal cap to prevent runaway agents from flooding the bus.
178
+ *
179
+ * @returns `true` if the signal was accepted, `false` if the agent hit the cap.
180
+ */
181
+ postSignal(signal: BusSignal): boolean;
182
+ /**
183
+ * Performs an intelligent, scoped query against unread bus activity and signals.
184
+ * Filters out read-only noise for peer sub-agents, enforces unicast delivery for targeted
185
+ * requests, and supports selective querying by file, agent, or signal type.
186
+ */
187
+ queryBus(options: BusQueryOptions): {
188
+ activities: ActivityEntry[];
189
+ signals: BusSignal[];
190
+ };
147
191
  /**
148
192
  * Writes the full bus state to disk atomically. Called after every mutation
149
193
  * to ensure crash resilience. Uses fire-and-forget to avoid blocking the
@@ -155,9 +199,5 @@ export declare class MessageBus {
155
199
  * If the file doesn't exist or is corrupted, starts with a clean slate.
156
200
  */
157
201
  private restoreFromDisk;
158
- /**
159
- * Clears all bus state and removes the persistence file.
160
- * Called when orchestration completes successfully (no crash recovery needed).
161
- */
162
- cleanup(): Promise<void>;
163
202
  }
203
+ export {};