minovative-mind-cli 2.14.1 → 2.14.3

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.
Files changed (47) hide show
  1. package/README.md +40 -59
  2. package/dist/services/agent/slashCommands.js +144 -13
  3. package/dist/services/agent/toolLoop.js +11 -2
  4. package/dist/services/agent/types.d.ts +9 -2
  5. package/dist/services/agent-tools.d.ts +20 -1
  6. package/dist/services/agent-tools.js +272 -47
  7. package/dist/services/agent.js +43 -18
  8. package/dist/services/ai.d.ts +9 -0
  9. package/dist/services/ai.js +71 -21
  10. package/dist/services/chatHistoryService.d.ts +5 -0
  11. package/dist/services/contextAgent.d.ts +14 -6
  12. package/dist/services/contextAgent.js +36 -10
  13. package/dist/services/orchestration/investigationAgent.js +31 -7
  14. package/dist/services/orchestration/investigationCache.js +19 -9
  15. package/dist/services/orchestration/readCache.d.ts +1 -0
  16. package/dist/services/orchestration/readCache.js +5 -2
  17. package/dist/services/orchestration/scopedTools.js +37 -10
  18. package/dist/services/orchestration/subAgent.js +16 -2
  19. package/dist/services/proxyClient.d.ts +6 -0
  20. package/dist/services/proxyClient.js +24 -10
  21. package/dist/services/sessionSettings.d.ts +66 -0
  22. package/dist/services/sessionSettings.js +126 -0
  23. package/dist/services/userProfileService.d.ts +14 -0
  24. package/dist/services/userProfileService.js +105 -3
  25. package/dist/services/verificationService.js +24 -2
  26. package/dist/utils/analysisRunner.d.ts +120 -8
  27. package/dist/utils/analysisRunner.js +946 -125
  28. package/dist/utils/antiCheatingGuard.d.ts +21 -0
  29. package/dist/utils/antiCheatingGuard.js +554 -0
  30. package/dist/utils/contextPrompts.d.ts +39 -0
  31. package/dist/utils/contextPrompts.js +81 -9
  32. package/dist/utils/contextRanker.d.ts +216 -0
  33. package/dist/utils/contextRanker.js +603 -0
  34. package/dist/utils/dependencyTracer/modules/graph.d.ts +4 -1
  35. package/dist/utils/dependencyTracer/modules/graph.js +11 -0
  36. package/dist/utils/dependencyTracer/modules/types.d.ts +10 -0
  37. package/dist/utils/dependencyTracer.d.ts +40 -2
  38. package/dist/utils/dependencyTracer.js +95 -3
  39. package/dist/utils/fileReadCache.d.ts +58 -0
  40. package/dist/utils/fileReadCache.js +162 -0
  41. package/dist/utils/projectStorage.js +2 -1
  42. package/dist/utils/symbolExtractor.d.ts +12 -0
  43. package/dist/utils/symbolExtractor.js +111 -15
  44. package/dist/utils/systemPrompts.d.ts +4 -3
  45. package/dist/utils/systemPrompts.js +66 -8
  46. package/oclif.manifest.json +1 -1
  47. package/package.json +1 -1
@@ -25,15 +25,18 @@ export interface ToolResult {
25
25
  mimeType: string;
26
26
  data: string;
27
27
  };
28
+ /** Optional structured JSON result extracted from the script execution. */
29
+ structuredResult?: unknown;
28
30
  }
29
31
  /**
30
32
  * Returns the list of available function declarations for Gemini function calling.
31
33
  *
32
- * @param options - Configuration options such as `isExecutionAgent`.
34
+ * @param options - Configuration options such as `isExecutionAgent` or `isResearchAgent`.
33
35
  * @returns An array of Google Generative AI `FunctionDeclaration` objects.
34
36
  */
35
37
  export declare function getToolDeclarations(options?: {
36
38
  isExecutionAgent?: boolean;
39
+ isResearchAgent?: boolean;
37
40
  }): FunctionDeclaration[];
38
41
  /**
39
42
  * FunctionDeclaration-compatible schema objects that describe
@@ -138,6 +141,22 @@ export declare function modifyFile(workspaceRoot: string, filePath: string, edit
138
141
  * @returns A promise resolving to a {@link ToolResult} containing the formatted tree output.
139
142
  */
140
143
  export declare function listDirectory(workspaceRoot: string, dirPath: string, maxDepth?: number): Promise<ToolResult>;
144
+ /**
145
+ * Truncates large string output using bounded head and tail windows with a structured metadata receipt.
146
+ * Preserves early context (e.g. invocation, build targets) and late context (e.g. error summaries, exit codes)
147
+ * while bounding total character and line count.
148
+ *
149
+ * @param text - The raw output text to truncate.
150
+ * @param options - Configuration options for character/line limits and custom receipt notices.
151
+ * @returns The windowed text containing head, structured metadata receipt, and tail.
152
+ */
153
+ export declare function truncateWithHeadTailWindow(text: string, options?: {
154
+ maxChars?: number;
155
+ maxLines?: number;
156
+ headRatio?: number;
157
+ receiptLabel?: string;
158
+ hint?: string;
159
+ }): string;
141
160
  /**
142
161
  * Condenses verbose command and test failure outputs into high-signal diagnostic error logs
143
162
  * with dynamic tail sizing and stack trace boundary snapping.
@@ -23,13 +23,15 @@ import { findBestMatch, applyMatch } from '../utils/fuzzyMatch.js';
23
23
  import { localValidate } from '../utils/localSyntaxValidator.js';
24
24
  import { validateAndFixSyntax, aiFuzzyMatch } from './agent/syntaxAgent.js';
25
25
  import { sanitizeForCDATA } from '../utils/contextPrompts.js';
26
- import { findDependencies, formatDependencyResult } from '../utils/dependencyTracer.js';
26
+ import { findDependencies, formatDependencyResult, invalidateDependencyGraph } from '../utils/dependencyTracer.js';
27
27
  import { atomicWriteFile } from '../utils/atomicWrite.js';
28
28
  import { EXCLUDED_EXTENSIONS } from '../utils/excludedExtensions.js';
29
29
  import { extractSymbols } from '../utils/symbolExtractor.js';
30
30
  import { getMetricCollector, recordRecoveryPrunedLogVolume } from './metrics.js';
31
31
  import { getCurrentAgentId } from '../utils/asyncContext.js';
32
32
  import { recordFileRead, hasAgentReadFile } from '../utils/fileReadGuard.js';
33
+ import { getCachedFileContent, invalidateFileReadCache, clearFileReadCache } from '../utils/fileReadCache.js';
34
+ import { detectAntiCheatingViolations, isCriticalConfigFile } from '../utils/antiCheatingGuard.js';
33
35
  import { runFuzzProbe, checkHeapDelta as runCheckHeapDelta, checkBehavioralDrift as runCheckBehavioralDrift, runEphemeralScript, } from '../utils/analysisRunner.js';
34
36
  import ignore from 'ignore';
35
37
  const execAsync = promisify(exec);
@@ -37,13 +39,24 @@ const execAsync = promisify(exec);
37
39
  /**
38
40
  * Returns the list of available function declarations for Gemini function calling.
39
41
  *
40
- * @param options - Configuration options such as `isExecutionAgent`.
42
+ * @param options - Configuration options such as `isExecutionAgent` or `isResearchAgent`.
41
43
  * @returns An array of Google Generative AI `FunctionDeclaration` objects.
42
44
  */
43
45
  export function getToolDeclarations(options) {
44
46
  if (options?.isExecutionAgent) {
45
47
  return toolDeclarations;
46
48
  }
49
+ if (options?.isResearchAgent) {
50
+ const excludedForResearch = new Set([
51
+ 'modify_file',
52
+ 'write_file',
53
+ 'delete_file',
54
+ 'rename_file',
55
+ 'create_todo_list',
56
+ 'update_todo_status',
57
+ ]);
58
+ return toolDeclarations.filter((tool) => !excludedForResearch.has(tool.name));
59
+ }
47
60
  return toolDeclarations.filter((tool) => tool.name !== 'create_todo_list' && tool.name !== 'update_todo_status' && tool.name !== 'finish_task');
48
61
  }
49
62
  /**
@@ -278,11 +291,15 @@ export const toolDeclarations = [
278
291
  },
279
292
  {
280
293
  name: 'run_debug_script',
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
- 'Use this to: (1) actively debug the codebase by inspecting variables or logging values, ' +
283
- '(2) validate your changes by importing the modified module and asserting expected behavior with edge-case inputs, ' +
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), ' +
285
- '(4) write lightweight ML scripts to solve complex problems e.g., polynomial regression to estimate Big-O complexity, fuzz testing with Markov chain input generation, ' +
294
+ 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, structured JSON result, and standard error without polluting the workspace or triggering IDE file watchers. ' +
295
+ 'Sandbox Features: ' +
296
+ '(1) Preloaded Helpers: "emitResult(data)" / "__emitResult(data)" outputs structured JSON payloads parsed directly into structuredResult; "inspectSymbols(target)" / "inspectObject(target)" inspects functions, classes, and properties. ' +
297
+ '(2) Module & Path Resolution: Automatically inherits tsconfig.json path aliases (@/*), NODE_PATH, and workspace virtualenvs (.venv, venv) for Python. ' +
298
+ '(3) Diagnostics Middleware: Provides actionable [SANDBOX DIAGNOSTIC] advisories for missing imports, ESM/CJS interop, and compiler errors. ' +
299
+ 'Use this to: (a) actively debug the codebase by inspecting variables or logging values, ' +
300
+ '(b) validate your changes by importing the modified module and asserting expected behavior with edge-case inputs, ' +
301
+ '(c) 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), ' +
302
+ '(d) write lightweight ML scripts to solve complex problems — e.g., polynomial regression to estimate Big-O complexity, fuzz testing with Markov chain input generation, ' +
286
303
  'output regression detection via statistical similarity scoring, or error message classification with Naive Bayes. ' +
287
304
  'Default to "node" for generic tasks as a safe baseline, but act like a native inhabitant of the host environment — if Python, Go, Rust, or host-native libraries are active in the project, leverage the host\'s native runtimes for maximum efficiency. ' +
288
305
  'Do not guess whether your code works — test it directly!',
@@ -622,7 +639,7 @@ export async function readFile(workspaceRoot, filePath, startLine, endLine, targ
622
639
  };
623
640
  }
624
641
  }
625
- let content = await fs.readFile(absPath, 'utf-8');
642
+ let content = await getCachedFileContent(absPath);
626
643
  // Handle Jupyter Notebooks natively
627
644
  if (filePath.toLowerCase().endsWith('.ipynb')) {
628
645
  try {
@@ -825,9 +842,15 @@ export async function writeFile(workspaceRoot, filePath, content) {
825
842
  };
826
843
  }
827
844
  }
845
+ const antiCheatError = detectAntiCheatingViolations(filePath, finalContent, existingContent ?? undefined);
846
+ if (antiCheatError) {
847
+ return { output: '', error: antiCheatError };
848
+ }
828
849
  await fs.mkdir(path.dirname(absPath), { recursive: true });
829
850
  changeLogger.logChange(filePath, existingContent, existingContent !== null ? 'modify' : 'create');
830
851
  await atomicWriteFile(absPath, finalContent, 'utf-8');
852
+ invalidateFileReadCache(absPath);
853
+ invalidateDependencyGraph(workspaceRoot);
831
854
  return { output: `Successfully wrote to "${filePath}".` };
832
855
  }
833
856
  catch (err) {
@@ -850,6 +873,12 @@ export async function writeFile(workspaceRoot, filePath, content) {
850
873
  */
851
874
  export async function deleteFile(workspaceRoot, filePath) {
852
875
  try {
876
+ if (isCriticalConfigFile(filePath)) {
877
+ return {
878
+ output: '',
879
+ error: `Anti-cheating violation: Deleting critical configuration file "${filePath}" is strictly prohibited. You must fix all build, compiler, type, and lint errors head-on in the source code.`,
880
+ };
881
+ }
853
882
  const absPath = resolveAndValidatePath(workspaceRoot, filePath);
854
883
  let existingContent = null;
855
884
  try {
@@ -860,6 +889,8 @@ export async function deleteFile(workspaceRoot, filePath) {
860
889
  }
861
890
  changeLogger.logChange(filePath, existingContent, 'delete');
862
891
  await fs.rm(absPath, { force: true });
892
+ invalidateFileReadCache(absPath);
893
+ invalidateDependencyGraph(workspaceRoot);
863
894
  return { output: `Successfully deleted "${filePath}".` };
864
895
  }
865
896
  catch (err) {
@@ -877,6 +908,12 @@ export async function deleteFile(workspaceRoot, filePath) {
877
908
  */
878
909
  export async function renameFile(workspaceRoot, sourcePath, targetPath) {
879
910
  try {
911
+ if (isCriticalConfigFile(sourcePath)) {
912
+ return {
913
+ output: '',
914
+ error: `Anti-cheating violation: Moving or renaming critical configuration file "${sourcePath}" is strictly prohibited. You must fix all build, compiler, type, and lint errors head-on in the source code.`,
915
+ };
916
+ }
880
917
  const absSource = resolveAndValidatePath(workspaceRoot, sourcePath);
881
918
  const absTarget = resolveAndValidatePath(workspaceRoot, targetPath);
882
919
  let existingContent = null;
@@ -892,6 +929,9 @@ export async function renameFile(workspaceRoot, sourcePath, targetPath) {
892
929
  changeLogger.logChange(targetPath, null, 'create');
893
930
  await fs.mkdir(path.dirname(absTarget), { recursive: true });
894
931
  await fs.rename(absSource, absTarget);
932
+ invalidateFileReadCache(absSource);
933
+ invalidateFileReadCache(absTarget);
934
+ invalidateDependencyGraph(workspaceRoot);
895
935
  return { output: `Successfully moved/renamed "${sourcePath}" to "${targetPath}".` };
896
936
  }
897
937
  catch (err) {
@@ -1014,10 +1054,16 @@ export async function modifyFile(workspaceRoot, filePath, edits) {
1014
1054
  };
1015
1055
  }
1016
1056
  }
1057
+ const antiCheatError = detectAntiCheatingViolations(filePath, finalModified, existing);
1058
+ if (antiCheatError) {
1059
+ return { output: '', error: antiCheatError };
1060
+ }
1017
1061
  changeLogger.logChange(filePath, existing, 'modify');
1018
1062
  await atomicWriteFile(absPath, finalModified, 'utf-8');
1063
+ invalidateFileReadCache(absPath);
1064
+ invalidateDependencyGraph(workspaceRoot);
1019
1065
  return {
1020
- output: `Successfully applied ${edits.length} edit(s) to "${filePath}".\\nStrategies used:\\n${strategies.join('\\n')}`,
1066
+ output: `Successfully applied ${edits.length} edit(s) to "${filePath}".\nStrategies used:\n${strategies.join('\n')}`,
1021
1067
  };
1022
1068
  }
1023
1069
  catch (err) {
@@ -1124,6 +1170,7 @@ const HIGH_SIGNAL_ERROR_PATTERNS = [
1124
1170
  /undefined reference/i,
1125
1171
  /ld: symbol\(s\) not found/i,
1126
1172
  /clang: error:/i,
1173
+ /gcc: error:/i,
1127
1174
  /referenced from:/i,
1128
1175
  /note: expanded from macro/i,
1129
1176
  /note: candidate:/i,
@@ -1133,29 +1180,59 @@ const HIGH_SIGNAL_ERROR_PATTERNS = [
1133
1180
  /TypeError:/i,
1134
1181
  /ImportError:/i,
1135
1182
  /ModuleNotFoundError:/i,
1183
+ /AttributeError:/i,
1184
+ /IndexError:/i,
1185
+ /KeyError:/i,
1186
+ /ValueError:/i,
1136
1187
  /FAILED \(failures=/i,
1137
1188
  /\bE\s{3}\b/,
1138
1189
  /\bFAIL\b/,
1139
- // Node.js / TypeScript / Jest / Mocha
1190
+ /\bFAILED\b/,
1191
+ /=== FAILURES ===/i,
1192
+ /=== ERRORS ===/i,
1193
+ // Sandbox Diagnostics & Structured Results
1194
+ /\[SANDBOX DIAGNOSTIC\]/i,
1195
+ /\[STRUCTURED RESULT\]/i,
1196
+ /Module resolution failure/i,
1197
+ /CommonJS require\(\) attempted to load an ES Module/i,
1198
+ // Node.js / TypeScript / Jest / Mocha / Vitest
1199
+ /TS\d+:/i,
1200
+ /TS\d{4,5}:/i,
1140
1201
  /SyntaxError:/i,
1141
1202
  /ReferenceError:/i,
1203
+ /TypeError:/i,
1204
+ /RangeError:/i,
1205
+ /URIError:/i,
1142
1206
  /Error: Cannot find module/i,
1207
+ /Cannot find module/i,
1208
+ /Cannot find name/i,
1209
+ /Type '.*' is not assignable to type/i,
1210
+ /Property '.*' does not exist on type/i,
1143
1211
  /\bFAIL\b/,
1144
1212
  /✕\s/,
1145
- /TS\d{4}:/,
1146
1213
  // Rust / Cargo
1147
1214
  /error\[E\d+\]:/i,
1148
1215
  /fatal runtime error:/i,
1149
1216
  /panicked at/i,
1217
+ /thread '.*' panicked at/i,
1218
+ /-->\s+.*:\d+:\d+/i,
1219
+ /could not compile/i,
1150
1220
  // Go
1151
1221
  /panic:/i,
1152
1222
  /cannot find package/i,
1153
1223
  /undefined:/i,
1154
1224
  /FAIL\t/,
1155
- // Java / JVM
1225
+ // Java / JVM / Kotlin / Scala
1156
1226
  /Exception in thread/i,
1157
1227
  /java\.lang\./i,
1158
1228
  /error: cannot find symbol/i,
1229
+ /NullPointerException/i,
1230
+ /ClassNotFoundException/i,
1231
+ // C# / .NET
1232
+ /CS\d{4}:/i,
1233
+ /Unhandled exception/i,
1234
+ // Swift
1235
+ /error:\s+/i,
1159
1236
  // Generic Fallbacks
1160
1237
  /\b(cannot|unable to|not found|command not found|no such file)\b/i,
1161
1238
  ];
@@ -1170,8 +1247,77 @@ const STACK_TRACE_START_PATTERNS = [
1170
1247
  /Traceback \(most recent call last\):/i,
1171
1248
  /Exception in thread/i,
1172
1249
  /panic:/i,
1250
+ /thread '.*' panicked at/i,
1251
+ /panicked at/i,
1173
1252
  /Error:\s*$/i,
1253
+ /TypeError:\s*$/i,
1254
+ /ReferenceError:\s*$/i,
1255
+ /AssertionError:\s*$/i,
1256
+ /=== FAILURES ===/i,
1257
+ /=== ERRORS ===/i,
1174
1258
  ];
1259
+ /**
1260
+ * Truncates large string output using bounded head and tail windows with a structured metadata receipt.
1261
+ * Preserves early context (e.g. invocation, build targets) and late context (e.g. error summaries, exit codes)
1262
+ * while bounding total character and line count.
1263
+ *
1264
+ * @param text - The raw output text to truncate.
1265
+ * @param options - Configuration options for character/line limits and custom receipt notices.
1266
+ * @returns The windowed text containing head, structured metadata receipt, and tail.
1267
+ */
1268
+ export function truncateWithHeadTailWindow(text, options) {
1269
+ if (!text || typeof text !== 'string')
1270
+ return '';
1271
+ const maxChars = options?.maxChars ?? 30_000;
1272
+ const maxLines = options?.maxLines ?? 500;
1273
+ const headRatio = options?.headRatio ?? 0.35;
1274
+ const receiptLabel = options?.receiptLabel ?? 'Output';
1275
+ const hint = options?.hint ?? 'Pipe command to a file and read it in chunks, or use grep.';
1276
+ const lines = text.split(/\r?\n/);
1277
+ if (text.length <= maxChars && lines.length <= maxLines) {
1278
+ return text;
1279
+ }
1280
+ const totalBytes = text.length;
1281
+ const totalLines = lines.length;
1282
+ // Calculate target characters for head and tail windows
1283
+ const receiptReserve = 500;
1284
+ const availableChars = Math.max(1000, maxChars - receiptReserve);
1285
+ const targetHeadChars = Math.floor(availableChars * headRatio);
1286
+ const targetTailChars = availableChars - targetHeadChars;
1287
+ // Max lines for head and tail
1288
+ const targetHeadLines = Math.max(5, Math.floor(maxLines * headRatio));
1289
+ const targetTailLines = Math.max(10, maxLines - targetHeadLines);
1290
+ // Accumulate head lines
1291
+ const headLines = [];
1292
+ let headCharsCount = 0;
1293
+ for (let i = 0; i < lines.length; i++) {
1294
+ const line = lines[i];
1295
+ if (headLines.length >= targetHeadLines ||
1296
+ (headLines.length > 0 && headCharsCount + line.length + 1 > targetHeadChars)) {
1297
+ break;
1298
+ }
1299
+ headLines.push(line);
1300
+ headCharsCount += line.length + 1;
1301
+ }
1302
+ // Accumulate tail lines backwards (ensuring no overlap with head)
1303
+ const tailLines = [];
1304
+ let tailCharsCount = 0;
1305
+ for (let i = lines.length - 1; i >= headLines.length; i--) {
1306
+ const line = lines[i];
1307
+ if (tailLines.length >= targetTailLines ||
1308
+ (tailLines.length > 0 && tailCharsCount + line.length + 1 > targetTailChars)) {
1309
+ break;
1310
+ }
1311
+ tailLines.unshift(line);
1312
+ tailCharsCount += line.length + 1;
1313
+ }
1314
+ const omittedLines = Math.max(0, totalLines - headLines.length - tailLines.length);
1315
+ const headText = headLines.join('\n');
1316
+ const tailText = tailLines.join('\n');
1317
+ const omittedBytes = Math.max(0, totalBytes - headText.length - tailText.length);
1318
+ const receipt = `\n\n... [${receiptLabel} truncated: ${omittedLines} lines (${omittedBytes} bytes) omitted. Showing ${headLines.length} head lines and ${tailLines.length} tail lines. Total: ${totalLines} lines (${totalBytes} bytes). ${hint}] ...\n\n`;
1319
+ return headText + receipt + tailText;
1320
+ }
1175
1321
  /**
1176
1322
  * Condenses verbose command and test failure outputs into high-signal diagnostic error logs
1177
1323
  * with dynamic tail sizing and stack trace boundary snapping.
@@ -1189,14 +1335,30 @@ export function extractHighSignalError(rawError) {
1189
1335
  }
1190
1336
  // Filter out noise lines (e.g. repetitive compiler warning flags)
1191
1337
  const nonNoiseLines = lines.filter((l) => !COMPILER_WARNING_NOISE_PATTERN.test(l));
1192
- // Find high-signal error lines
1338
+ // Find high-signal error lines and associated compiler frames/context lines
1193
1339
  const highSignalLines = [];
1194
1340
  const highSignalIndices = new Set();
1195
1341
  for (let i = 0; i < nonNoiseLines.length; i++) {
1196
1342
  const line = nonNoiseLines[i];
1197
1343
  if (HIGH_SIGNAL_ERROR_PATTERNS.some((p) => p.test(line))) {
1198
- highSignalLines.push(line);
1199
- highSignalIndices.add(i);
1344
+ if (!highSignalIndices.has(i)) {
1345
+ highSignalLines.push(line);
1346
+ highSignalIndices.add(i);
1347
+ }
1348
+ // Capture up to 5 immediate compiler frame continuation lines (e.g. code squiggles, expected/found, arrows)
1349
+ for (let j = 1; j <= 5 && i + j < nonNoiseLines.length; j++) {
1350
+ const nextLine = nonNoiseLines[i + j];
1351
+ if (/^\s*(\||-->|\^|~|expected|found|note:|candidate:)/i.test(nextLine) ||
1352
+ /^\s*\d+\s*\|/.test(nextLine)) {
1353
+ if (!highSignalIndices.has(i + j)) {
1354
+ highSignalLines.push(nextLine);
1355
+ highSignalIndices.add(i + j);
1356
+ }
1357
+ }
1358
+ else {
1359
+ break;
1360
+ }
1361
+ }
1200
1362
  }
1201
1363
  }
1202
1364
  // Dynamic tail sizing: Math.min(50, Math.max(15, Math.floor(totalLines * 0.15)))
@@ -1233,7 +1395,15 @@ export function extractHighSignalError(rawError) {
1233
1395
  else {
1234
1396
  resultBlocks.push(tailLines.join('\n'));
1235
1397
  }
1236
- const condensedResult = resultBlocks.join('\n\n');
1398
+ let condensedResult = resultBlocks.join('\n\n');
1399
+ // Bounded head/tail window check on condensed result if still oversized
1400
+ if (condensedResult.length > 30_000) {
1401
+ condensedResult = truncateWithHeadTailWindow(condensedResult, {
1402
+ maxChars: 30_000,
1403
+ receiptLabel: 'Diagnostic output',
1404
+ hint: 'Pipe to a file if you need full logs.',
1405
+ });
1406
+ }
1237
1407
  const condensedLines = condensedResult.split(/\r?\n/).length;
1238
1408
  const linesPruned = Math.max(0, lines.length - condensedLines);
1239
1409
  const charsPruned = Math.max(0, rawError.length - condensedResult.length);
@@ -1257,26 +1427,31 @@ export async function runCommand(workspaceRoot, command, abortSignal) {
1257
1427
  maxBuffer: 1024 * 1024 * 2, // 2 MB buffer
1258
1428
  signal: abortSignal,
1259
1429
  });
1260
- let output = [stdout, stderr].filter(Boolean).join('\n');
1261
- // Truncate command output to prevent memory blowout from massive build logs
1262
- const MAX_CMD_OUTPUT = 30_000;
1263
- if (output.length > MAX_CMD_OUTPUT) {
1264
- output =
1265
- output.substring(0, MAX_CMD_OUTPUT) +
1266
- `\n\n... (Output truncated: ${output.length} bytes exceeded 30KB limit. To view the rest, pipe the command to a file and read it in chunks, or use grep.)`;
1267
- }
1268
- return { output: output || '(command produced no output)' };
1430
+ // Invalidate caches as shell command may have modified the workspace filesystem
1431
+ clearFileReadCache();
1432
+ invalidateDependencyGraph(workspaceRoot);
1433
+ const output = [stdout, stderr].filter(Boolean).join('\n');
1434
+ // Bounded head/tail window truncation with structured metadata receipt
1435
+ const windowedOutput = truncateWithHeadTailWindow(output, {
1436
+ maxChars: 30_000,
1437
+ receiptLabel: 'Output',
1438
+ hint: 'To view the rest, pipe the command to a file and read it in chunks, or use grep.',
1439
+ });
1440
+ return { output: windowedOutput || '(command produced no output)' };
1269
1441
  }
1270
1442
  catch (err) {
1443
+ // Also invalidate in case of partial command execution or errors that wrote files
1444
+ clearFileReadCache();
1445
+ invalidateDependencyGraph(workspaceRoot);
1271
1446
  const message = err instanceof Error ? err.message : String(err);
1272
1447
  // Condense error output with high-signal extraction and dynamic tail sizing
1273
1448
  const condensedError = extractHighSignalError(message);
1274
- const MAX_ERR_OUTPUT = 30_000;
1275
- const truncatedMsg = condensedError.length > MAX_ERR_OUTPUT
1276
- ? condensedError.substring(0, MAX_ERR_OUTPUT) +
1277
- `\n\n... (Error output truncated: ${condensedError.length} bytes exceeded 30KB limit. Pipe to a file if you need full logs.)`
1278
- : condensedError;
1279
- return { output: '', error: `Command failed: ${truncatedMsg}` };
1449
+ const windowedError = truncateWithHeadTailWindow(condensedError, {
1450
+ maxChars: 30_000,
1451
+ receiptLabel: 'Error output',
1452
+ hint: 'Pipe to a file if you need full logs.',
1453
+ });
1454
+ return { output: '', error: `Command failed: ${windowedError}` };
1280
1455
  }
1281
1456
  }
1282
1457
  /**
@@ -1305,14 +1480,18 @@ export async function grepSearch(workspaceRoot, pattern, fileGlob, fixedStrings,
1305
1480
  }
1306
1481
  // Use ignore package to evaluate path globs (e.g. src/**/*.ts)
1307
1482
  const globMatcher = fileGlob ? ignore().add(fileGlob) : null;
1483
+ const MAX_SHOWN_MATCHES = 50;
1484
+ const MAX_SCAN_MATCHES = 500;
1308
1485
  const results = [];
1486
+ let totalMatches = 0;
1487
+ const matchedFiles = new Set();
1309
1488
  /**
1310
1489
  * Recursively walks directory entries to search for pattern matches.
1311
1490
  *
1312
1491
  * @param dir - Absolute path to the current directory being searched.
1313
1492
  */
1314
1493
  async function walk(dir) {
1315
- if (results.length >= 50)
1494
+ if (totalMatches >= MAX_SCAN_MATCHES)
1316
1495
  return;
1317
1496
  if (abortSignal?.aborted)
1318
1497
  return;
@@ -1324,7 +1503,7 @@ export async function grepSearch(workspaceRoot, pattern, fileGlob, fixedStrings,
1324
1503
  return;
1325
1504
  }
1326
1505
  for (const entry of entries) {
1327
- if (results.length >= 50)
1506
+ if (totalMatches >= MAX_SCAN_MATCHES)
1328
1507
  return;
1329
1508
  if (abortSignal?.aborted)
1330
1509
  return;
@@ -1351,12 +1530,16 @@ export async function grepSearch(workspaceRoot, pattern, fileGlob, fixedStrings,
1351
1530
  const content = await fs.readFile(filePath, 'utf-8');
1352
1531
  const lines = content.split(/\r?\n/);
1353
1532
  for (let i = 0; i < lines.length; i++) {
1354
- if (results.length >= 50)
1533
+ if (totalMatches >= MAX_SCAN_MATCHES)
1355
1534
  break;
1356
1535
  regexPattern.lastIndex = 0;
1357
1536
  if (regexPattern.test(lines[i])) {
1358
- const relativePath = path.relative(workspaceRoot, filePath).replace(/\\/g, '/');
1359
- results.push(`${relativePath}:${i + 1}:${lines[i]}`);
1537
+ totalMatches++;
1538
+ matchedFiles.add(relPath);
1539
+ if (results.length < MAX_SHOWN_MATCHES) {
1540
+ const relativePath = path.relative(workspaceRoot, filePath).replace(/\\/g, '/');
1541
+ results.push(`${relativePath}:${i + 1}:${lines[i]}`);
1542
+ }
1360
1543
  }
1361
1544
  }
1362
1545
  }
@@ -1373,8 +1556,18 @@ export async function grepSearch(workspaceRoot, pattern, fileGlob, fixedStrings,
1373
1556
  }
1374
1557
  if (results.length === 0)
1375
1558
  return { output: `No matches found for "${pattern}".` };
1376
- const resultText = results.join('\n') + (results.length >= 50 ? `\n\n... (more results truncated)` : '');
1377
- const wrappedResult = `<workspace_file path="grep_search_results">\n<content_data><![CDATA[\n${sanitizeForCDATA(resultText)}\n]]\\u200B></content_data>\n</workspace_file>`;
1559
+ let resultText = results.join('\n');
1560
+ if (totalMatches > results.length) {
1561
+ const moreSuffix = totalMatches >= MAX_SCAN_MATCHES ? '+' : '';
1562
+ resultText += `\n\n[Showing ${results.length}/${totalMatches}${moreSuffix} matches across ${matchedFiles.size} files. Refine query with fileGlob or dirPath to narrow results.]`;
1563
+ }
1564
+ // Window large grep text if character count is huge
1565
+ const windowedResult = truncateWithHeadTailWindow(resultText, {
1566
+ maxChars: 30_000,
1567
+ receiptLabel: 'Grep search results',
1568
+ hint: 'Refine query with fileGlob or dirPath to narrow results.',
1569
+ });
1570
+ const wrappedResult = `<workspace_file path="grep_search_results">\n<content_data><![CDATA[\n${sanitizeForCDATA(windowedResult)}\n]]\\u200B></content_data>\n</workspace_file>`;
1378
1571
  return { output: wrappedResult };
1379
1572
  }
1380
1573
  catch (err) {
@@ -1513,6 +1706,12 @@ export async function runDebugScript(workspaceRoot, language, code, abortSignal)
1513
1706
  if (res.exitCode !== 0) {
1514
1707
  finalOutput += `Script failed with exit code ${res.exitCode}.\n`;
1515
1708
  }
1709
+ if (res.structuredResult !== undefined) {
1710
+ const formattedStructured = typeof res.structuredResult === 'string'
1711
+ ? res.structuredResult
1712
+ : JSON.stringify(res.structuredResult, null, 2);
1713
+ finalOutput += `[STRUCTURED RESULT]\n${formattedStructured}\n`;
1714
+ }
1516
1715
  if (res.stdout) {
1517
1716
  finalOutput += `[STDOUT]\n${res.stdout}\n`;
1518
1717
  }
@@ -1523,11 +1722,17 @@ export async function runDebugScript(workspaceRoot, language, code, abortSignal)
1523
1722
  finalOutput = 'Script executed successfully with no output.';
1524
1723
  }
1525
1724
  const condensedOutput = extractHighSignalError(finalOutput.trim());
1725
+ const windowedOutput = truncateWithHeadTailWindow(condensedOutput, {
1726
+ maxChars: 30_000,
1727
+ receiptLabel: 'Debug script output',
1728
+ hint: 'Inspect specific variables or refine script if full logs are needed.',
1729
+ });
1526
1730
  return {
1527
- output: `<test_results>\n${sanitizeForCDATA(condensedOutput)}\n</test_results>`,
1731
+ output: `<test_results>\n${sanitizeForCDATA(windowedOutput)}\n</test_results>`,
1732
+ ...(res.structuredResult !== undefined ? { structuredResult: res.structuredResult } : {}),
1528
1733
  ...(res.exitCode !== 0
1529
1734
  ? {
1530
- error: `Script failed with exit code ${res.exitCode}:\n${extractHighSignalError(res.stderr || res.stdout || '')}`,
1735
+ error: `Script failed with exit code ${res.exitCode}:\n${truncateWithHeadTailWindow(extractHighSignalError(res.stderr || res.stdout || ''), { maxChars: 30_000, receiptLabel: 'Debug script error' })}`,
1531
1736
  }
1532
1737
  : {}),
1533
1738
  };
@@ -1573,6 +1778,7 @@ export async function executeFuzzProbe(workspaceRoot, code, language = 'node', c
1573
1778
  output += `\n[STDERR]\n${res.stderr}\n`;
1574
1779
  return {
1575
1780
  output: output.trim(),
1781
+ ...(res.structuredResult !== undefined ? { structuredResult: res.structuredResult } : {}),
1576
1782
  ...(res.exitCode !== 0 && !res.passed ? { error: `Fuzz probe failed with exit code ${res.exitCode}` } : {}),
1577
1783
  };
1578
1784
  }
@@ -1616,6 +1822,7 @@ export async function executeHeapDelta(workspaceRoot, code, language = 'node', c
1616
1822
  output += `\n[STDERR]\n${res.stderr}\n`;
1617
1823
  return {
1618
1824
  output: output.trim(),
1825
+ ...(res.structuredResult !== undefined ? { structuredResult: res.structuredResult } : {}),
1619
1826
  ...(res.exitCode !== 0 ? { error: `Heap delta failed with exit code ${res.exitCode}` } : {}),
1620
1827
  };
1621
1828
  }
@@ -1661,6 +1868,7 @@ export async function executeBehavioralDrift(workspaceRoot, baselineCode, candid
1661
1868
  output += `\n[STDERR]\n${res.stderr}\n`;
1662
1869
  return {
1663
1870
  output: output.trim(),
1871
+ ...(res.structuredResult !== undefined ? { structuredResult: res.structuredResult } : {}),
1664
1872
  ...(res.exitCode !== 0 ? { error: `Behavioral drift check failed with exit code ${res.exitCode}` } : {}),
1665
1873
  };
1666
1874
  }
@@ -1727,6 +1935,9 @@ function resolveWorkspaceArgs(primaryRoot, toolName, args) {
1727
1935
  async function crossWorkspaceGrep(primaryRoot, pattern, fileGlob, fixedStrings, dirPath, abortSignal) {
1728
1936
  const allRoots = workspaceRegistry.getAllRoots(primaryRoot);
1729
1937
  const allResults = [];
1938
+ let totalMatches = 0;
1939
+ const matchedFiles = new Set();
1940
+ const MAX_SHOWN_MATCHES = 80;
1730
1941
  for (const { alias, root } of allRoots) {
1731
1942
  const result = await grepSearch(root, pattern, fileGlob, fixedStrings, dirPath, abortSignal);
1732
1943
  if (result.error)
@@ -1740,10 +1951,14 @@ async function crossWorkspaceGrep(primaryRoot, pattern, fileGlob, fixedStrings,
1740
1951
  const prefix = alias ? `@${alias}/` : '';
1741
1952
  const lines = rawText.split('\n').filter((l) => l.trim());
1742
1953
  for (const line of lines) {
1743
- if (line.startsWith('...')) {
1744
- allResults.push(line);
1954
+ if (line.startsWith('...') || line.startsWith('[')) {
1955
+ continue;
1745
1956
  }
1746
- else {
1957
+ totalMatches++;
1958
+ const matchFile = line.split(':')[0];
1959
+ if (matchFile)
1960
+ matchedFiles.add(prefix ? `${prefix}${matchFile}` : matchFile);
1961
+ if (allResults.length < MAX_SHOWN_MATCHES) {
1747
1962
  // Lines are in format "./path:lineNum:content" — prepend alias prefix
1748
1963
  allResults.push(prefix ? line.replace(/^\.?\//, `@${alias}/`) : line);
1749
1964
  }
@@ -1752,9 +1967,16 @@ async function crossWorkspaceGrep(primaryRoot, pattern, fileGlob, fixedStrings,
1752
1967
  if (allResults.length === 0) {
1753
1968
  return { output: `No matches found for "${pattern}" across all workspaces.` };
1754
1969
  }
1755
- const limited = allResults.slice(0, 80);
1756
- const resultText = limited.join('\n') + (allResults.length > 80 ? `\n\n... (${allResults.length - 80} more results truncated)` : '');
1757
- const wrappedResult = `<workspace_file path="grep_search_results">\n<content_data><![CDATA[\n${sanitizeForCDATA(resultText)}\n]]\\u200B></content_data>\n</workspace_file>`;
1970
+ let resultText = allResults.join('\n');
1971
+ if (totalMatches > allResults.length) {
1972
+ resultText += `\n\n[Showing ${allResults.length}/${totalMatches} matches across ${matchedFiles.size} files in registered workspaces. Refine query to narrow results.]`;
1973
+ }
1974
+ const windowedResult = truncateWithHeadTailWindow(resultText, {
1975
+ maxChars: 30_000,
1976
+ receiptLabel: 'Cross-workspace grep results',
1977
+ hint: 'Refine query to narrow results.',
1978
+ });
1979
+ const wrappedResult = `<workspace_file path="grep_search_results">\n<content_data><![CDATA[\n${sanitizeForCDATA(windowedResult)}\n]]\\u200B></content_data>\n</workspace_file>`;
1758
1980
  return { output: wrappedResult };
1759
1981
  }
1760
1982
  const currentTasksByAgent = new Map();
@@ -1946,7 +2168,10 @@ export async function executeTool(workspaceRoot, toolName, args, abortSignal) {
1946
2168
  result = await runCommand(effectiveRoot, resolvedArgs.command, abortSignal);
1947
2169
  break;
1948
2170
  case 'run_debug_script':
1949
- result = await runDebugScript(effectiveRoot, resolvedArgs.language, resolvedArgs.code, abortSignal);
2171
+ case 'debug_script':
2172
+ case 'run_analysis_script':
2173
+ case 'analysis_script':
2174
+ result = await runDebugScript(effectiveRoot, resolvedArgs.language || 'auto', resolvedArgs.code, abortSignal);
1950
2175
  break;
1951
2176
  case 'grep_search': {
1952
2177
  const wsParam = resolvedArgs.workspace;