minovative-mind-cli 2.11.5 → 2.12.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.
package/README.md CHANGED
@@ -76,12 +76,25 @@ Works in any terminal — SSH, Docker, CI pipelines, Vim, Windows Command Prompt
76
76
  - Solved one of the hardest logic puzzles on the platform by engineering a mathematically rigorous pruning algorithm based on the Rearrangement Inequality.
77
77
  - Completely bypassed naive backtracking (10! permutations).
78
78
  - Achieved sub-millisecond execution speeds (~140 microseconds) with zero heap allocations during recursion.
79
- - Autonomously navigated the Go toolchain (\`go test -bench=.\`).
79
+ - Autonomously navigated the Go toolchain (`go test -bench=.`).
80
80
  - Parsed its own architectural performance warnings to optimize the solution.
81
81
  - [Alphametics Summary](https://github.com/Quarantiine/polyglot-benchmark-mmcli/blob/main/go/exercises/practice/alphametics/minovative-tries/attempt_1.md)
82
82
 
83
+ ### 🔬 Automated SWE-bench Evaluation Runner
84
+
85
+ Evaluate the agent on SWE-bench Lite datasets with automated repo isolation, patch extraction, and public-ready telemetry reporting:
86
+
87
+ ```bash
88
+ # Run SWE-bench evaluation and generate JSON report
89
+ minovative-mind-cli eval -i test_instances.jsonl -o predictions.jsonl -r evaluation_report.json --autoClone
90
+
91
+ # Run with specific repository filter and concurrency
92
+ minovative-mind-cli eval -i instances.jsonl --repo astropy/astropy --concurrency 2
93
+ ```
94
+
83
95
  ---
84
96
 
97
+
85
98
  ## Models
86
99
 
87
100
  Hot-swap during a session using `/models`:
@@ -0,0 +1,22 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class EvalCommand extends Command {
3
+ static description: string;
4
+ static examples: string[];
5
+ static flags: {
6
+ instances: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
7
+ output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
8
+ report: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
9
+ model: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
10
+ maxTurns: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
11
+ timeout: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
12
+ repo: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
13
+ instanceId: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
14
+ limit: import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
15
+ workspaceDir: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
16
+ autoClone: import("@oclif/core/interfaces").BooleanFlag<boolean>;
17
+ dryRun: import("@oclif/core/interfaces").BooleanFlag<boolean>;
18
+ concurrency: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
19
+ verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
20
+ };
21
+ run(): Promise<void>;
22
+ }
@@ -0,0 +1,141 @@
1
+ import { Command, Flags } from '@oclif/core';
2
+ import * as path from 'node:path';
3
+ import * as fs from 'node:fs';
4
+ import { runSWEBenchEvaluation, formatEvaluationReportSummary, } from '../services/swebench/index.js';
5
+ export default class EvalCommand extends Command {
6
+ static description = 'Execute automated evaluation on SWE-bench Lite benchmark instances';
7
+ static examples = [
8
+ '<%= config.bin %> <%= command.id %> -i instances.json -o predictions.jsonl',
9
+ '<%= config.bin %> <%= command.id %> --instances ./test_instances.jsonl --output ./predictions.jsonl --report ./evaluation_report.json',
10
+ '<%= config.bin %> <%= command.id %> --instances test_instances.jsonl --repo astropy/astropy --limit 5',
11
+ '<%= config.bin %> <%= command.id %> --instances test_instances.jsonl --instance-id astropy__astropy-12907',
12
+ '<%= config.bin %> <%= command.id %> --instances test_instances.jsonl --dry-run',
13
+ ];
14
+ static flags = {
15
+ instances: Flags.string({
16
+ char: 'i',
17
+ description: 'Path to input instances file (JSON or JSONL format)',
18
+ required: true,
19
+ }),
20
+ output: Flags.string({
21
+ char: 'o',
22
+ description: 'Path to write generated predictions JSONL file',
23
+ default: 'predictions.jsonl',
24
+ }),
25
+ report: Flags.string({
26
+ char: 'r',
27
+ description: 'Path to export comprehensive evaluation report JSON',
28
+ }),
29
+ model: Flags.string({
30
+ char: 'm',
31
+ description: 'Model name tag for prediction metadata',
32
+ default: 'minovative-mind-agent',
33
+ }),
34
+ maxTurns: Flags.integer({
35
+ description: 'Maximum agent execution turns per benchmark instance',
36
+ default: 30,
37
+ }),
38
+ timeout: Flags.integer({
39
+ description: 'Timeout in milliseconds per instance (default: 600000ms = 10min)',
40
+ default: 600000,
41
+ }),
42
+ repo: Flags.string({
43
+ description: 'Filter instances by repository name (e.g. astropy/astropy)',
44
+ }),
45
+ instanceId: Flags.string({
46
+ description: 'Filter by specific instance ID (e.g. astropy__astropy-12907)',
47
+ }),
48
+ limit: Flags.integer({
49
+ description: 'Limit the number of instances to evaluate',
50
+ }),
51
+ workspaceDir: Flags.string({
52
+ char: 'w',
53
+ description: 'Base workspace directory where repo checkouts reside',
54
+ }),
55
+ autoClone: Flags.boolean({
56
+ description: 'Automatically clone repository if not found locally in workspace directory',
57
+ default: false,
58
+ }),
59
+ dryRun: Flags.boolean({
60
+ description: 'Validate instances and workspace setup without invoking the agent loop',
61
+ default: false,
62
+ }),
63
+ concurrency: Flags.integer({
64
+ char: 'c',
65
+ description: 'Number of instances to evaluate concurrently (default: 1)',
66
+ default: 1,
67
+ }),
68
+ verbose: Flags.boolean({
69
+ char: 'v',
70
+ description: 'Show detailed per-turn logs during evaluation',
71
+ default: false,
72
+ }),
73
+ };
74
+ async run() {
75
+ const { flags } = await this.parse(EvalCommand);
76
+ const instancesPath = path.isAbsolute(flags.instances)
77
+ ? flags.instances
78
+ : path.resolve(process.cwd(), flags.instances);
79
+ const outputPath = path.isAbsolute(flags.output)
80
+ ? flags.output
81
+ : path.resolve(process.cwd(), flags.output);
82
+ const reportPath = flags.report
83
+ ? path.isAbsolute(flags.report)
84
+ ? flags.report
85
+ : path.resolve(process.cwd(), flags.report)
86
+ : undefined;
87
+ if (!fs.existsSync(instancesPath)) {
88
+ this.error(`Instances file not found: ${instancesPath}`);
89
+ return;
90
+ }
91
+ this.log(`\n======================================================`);
92
+ this.log(` MINOVATIVE SWE-BENCH LITE EVALUATION RUNNER`);
93
+ this.log(`======================================================`);
94
+ this.log(`Instances File : ${instancesPath}`);
95
+ this.log(`Output Location : ${outputPath}`);
96
+ if (reportPath)
97
+ this.log(`Report Location : ${reportPath}`);
98
+ this.log(`Model Identifier : ${flags.model}`);
99
+ this.log(`Max Turns : ${flags.maxTurns}`);
100
+ this.log(`Timeout / Inst : ${flags.timeout}ms`);
101
+ if (flags.repo)
102
+ this.log(`Repo Filter : ${flags.repo}`);
103
+ if (flags.instanceId)
104
+ this.log(`Instance Filter : ${flags.instanceId}`);
105
+ if (flags.limit)
106
+ this.log(`Instance Limit : ${flags.limit}`);
107
+ if (flags.dryRun)
108
+ this.log(`Execution Mode : Dry Run (No agent invocation)`);
109
+ if (flags.autoClone)
110
+ this.log(`Auto-Clone Repos : Enabled`);
111
+ this.log(`======================================================\n`);
112
+ const evalOptions = {
113
+ instancesPath,
114
+ outputPredictionsPath: outputPath,
115
+ reportPath,
116
+ modelName: flags.model,
117
+ maxTurns: flags.maxTurns,
118
+ timeoutMsPerInstance: flags.timeout,
119
+ repo: flags.repo,
120
+ instanceIds: flags.instanceId ? [flags.instanceId] : undefined,
121
+ limit: flags.limit,
122
+ workspacesBaseDir: flags.workspaceDir,
123
+ autoCloneRepos: flags.autoClone,
124
+ dryRun: flags.dryRun,
125
+ concurrency: flags.concurrency,
126
+ verbose: flags.verbose,
127
+ onProgress: (progress) => {
128
+ const statusStr = progress.status === 'success' ? 'Completed' : progress.status === 'running' ? 'Running' : progress.status;
129
+ this.log(`[${progress.completed}/${progress.total}] Instance: ${progress.currentInstanceId} (${statusStr})`);
130
+ },
131
+ };
132
+ try {
133
+ const summary = await runSWEBenchEvaluation(evalOptions);
134
+ this.log(formatEvaluationReportSummary(summary));
135
+ }
136
+ catch (err) {
137
+ const msg = err instanceof Error ? err.message : String(err);
138
+ this.error(`SWE-bench evaluation failed: ${msg}`);
139
+ }
140
+ }
141
+ }
package/dist/index.d.ts CHANGED
@@ -1 +1,2 @@
1
1
  export { run } from '@oclif/core';
2
+ export * as swebench from './services/swebench/index.js';
package/dist/index.js CHANGED
@@ -1 +1,2 @@
1
1
  export { run } from '@oclif/core';
2
+ export * as swebench from './services/swebench/index.js';
@@ -37,3 +37,7 @@ export declare function formatToolCall(name: string, args: Record<string, unknow
37
37
  * @returns A promise that resolves to the final text response.
38
38
  */
39
39
  export declare function processResponse(chat: ProxyChatSession, result: any, workspaceRoot: string, inputHandler: AsyncInputHandler, agentState: AgentState, abortSignal: AbortSignal): Promise<string>;
40
+ /**
41
+ * System advisory appended when consecutive command failures exceed the circuit breaker threshold.
42
+ */
43
+ export declare const CONSECUTIVE_COMMAND_FAILURE_ADVISORY = "\n\n\u26A0\uFE0F SYSTEM ADVISORY: Multiple consecutive command failures detected. If you are troubleshooting a build or test failure, inspect the build configuration file (e.g., package.json, setup.py, Cargo.toml, Makefile, CMakeLists.txt) or relevant source code to identify the root cause before retrying.";
@@ -6,7 +6,7 @@ import { executeTool } from '../agent-tools.js';
6
6
  import { getPlanExecutionConfig } from '../ai.js';
7
7
  import { routeIntent } from '../contextAgent.js';
8
8
  import { requestCommandApproval } from './commandApproval.js';
9
- import { getMetricCollector } from '../metrics.js';
9
+ import { getMetricCollector, recordRecoveryCircuitBreakerTrip } from '../metrics.js';
10
10
  import { trackTask } from '../../utils/taskVisualizer.js';
11
11
  // ─── Constants ───────────────────────────────────────────────────────
12
12
  /**
@@ -175,6 +175,8 @@ export async function processResponse(chat, result, workspaceRoot, inputHandler,
175
175
  // Recovery thresholds for handling unexpected empty API payloads
176
176
  let emptyRetryCount = 0;
177
177
  const MAX_EMPTY_RETRIES = 5;
178
+ // Consecutive command failure tracking for circuit breaker
179
+ const commandFailureState = { consecutiveFailures: 0 };
178
180
  // Loop while the model keeps requesting tool calls
179
181
  while (true) {
180
182
  turnCount++;
@@ -251,7 +253,7 @@ export async function processResponse(chat, result, workspaceRoot, inputHandler,
251
253
  const collector = getMetricCollector();
252
254
  if (collector)
253
255
  collector.recordToolTurn();
254
- const execRes = await executeToolCalls(functionCalls, workspaceRoot, inputHandler, abortSignal);
256
+ const execRes = await executeToolCalls(functionCalls, workspaceRoot, inputHandler, abortSignal, commandFailureState);
255
257
  if (execRes.aborted) {
256
258
  return '[Generation stopped by user]';
257
259
  }
@@ -294,8 +296,25 @@ export async function processResponse(chat, result, workspaceRoot, inputHandler,
294
296
  response = followUp.response;
295
297
  }
296
298
  }
297
- async function executeToolCalls(functionCalls, workspaceRoot, inputHandler, abortSignal) {
299
+ /**
300
+ * System advisory appended when consecutive command failures exceed the circuit breaker threshold.
301
+ */
302
+ export const CONSECUTIVE_COMMAND_FAILURE_ADVISORY = '\n\n⚠️ SYSTEM ADVISORY: Multiple consecutive command failures detected. If you are troubleshooting a build or test failure, inspect the build configuration file (e.g., package.json, setup.py, Cargo.toml, Makefile, CMakeLists.txt) or relevant source code to identify the root cause before retrying.';
303
+ /**
304
+ * Executes a sequence of tool calls returned by the AI model.
305
+ * Enforces security gates, tracks telemetry, applies circuit breakers for consecutive command failures,
306
+ * and passes the tool execution results back to the conversation loop.
307
+ *
308
+ * @param functionCalls - Array of function call parts from the Gemini API.
309
+ * @param workspaceRoot - Root directory of the primary active workspace.
310
+ * @param inputHandler - Async input listener for handling user steering/abort.
311
+ * @param abortSignal - Signal to cancel ongoing execution turns.
312
+ * @param commandFailureState - Mutable container tracking consecutive command failure counts across turns.
313
+ * @returns An object containing tool responses formatted for Gemini API, plus an aborted flag.
314
+ */
315
+ async function executeToolCalls(functionCalls, workspaceRoot, inputHandler, abortSignal, commandFailureState) {
298
316
  const toolResponses = [];
317
+ const state = commandFailureState ?? { consecutiveFailures: 0 };
299
318
  for (const fc of functionCalls) {
300
319
  await inputHandler.waitForPrompt();
301
320
  if (abortSignal.aborted) {
@@ -330,24 +349,56 @@ async function executeToolCalls(functionCalls, workspaceRoot, inputHandler, abor
330
349
  return await executeTool(workspaceRoot, toolName, toolArgs, abortSignal);
331
350
  });
332
351
  await inputHandler.waitForPrompt();
333
- if (toolResult.error) {
334
- debugLog(`Raw Tool Error for ${toolName}: ${toolResult.error}`);
352
+ let toolError = toolResult.error;
353
+ // Circuit Breaker State Tracking:
354
+ // Reset counter to 0 immediately upon:
355
+ // 1. A successful (zero-exit / no error) run_command or run_debug_script.
356
+ // 2. Any investigative tool call (read_file, grep_search, list_directory, find_dependencies, run_analysis_script).
357
+ // 3. Any file mutation (modify_file, write_file, delete_file, rename_file).
358
+ const isCommandTool = toolName === 'run_command' || toolName === 'run_debug_script';
359
+ const isInvestigativeOrMutationTool = [
360
+ 'read_file',
361
+ 'grep_search',
362
+ 'list_directory',
363
+ 'find_dependencies',
364
+ 'find_recent_changes',
365
+ 'run_analysis_script',
366
+ 'modify_file',
367
+ 'write_file',
368
+ 'delete_file',
369
+ 'rename_file',
370
+ ].includes(toolName);
371
+ if (isCommandTool) {
372
+ if (toolError) {
373
+ state.consecutiveFailures++;
374
+ if (state.consecutiveFailures >= 2) {
375
+ toolError += CONSECUTIVE_COMMAND_FAILURE_ADVISORY;
376
+ recordRecoveryCircuitBreakerTrip();
377
+ }
378
+ }
379
+ else {
380
+ state.consecutiveFailures = 0;
381
+ }
382
+ }
383
+ else if (isInvestigativeOrMutationTool) {
384
+ state.consecutiveFailures = 0;
385
+ }
386
+ if (toolError) {
387
+ debugLog(`Raw Tool Error for ${toolName}: ${toolError}`);
335
388
  // Do not spam the user with expected chunking warnings
336
- if (!toolResult.error.includes('File is too large')) {
389
+ if (!toolError.includes('File is too large')) {
337
390
  // Truncate the error message for the terminal UI to prevent console clutter
338
391
  // (e.g. hiding the large file previews sent to the AI)
339
- const displayError = toolResult.error.split('\n')[0].substring(0, 100);
392
+ const displayError = toolError.split('\n')[0].substring(0, 100);
340
393
  p.log.warn(`${pc.red('Tool error:')} [${displayError}]`);
341
394
  }
342
395
  }
343
- if (!toolResult.error && (toolName === 'write_file' || toolName === 'modify_file' || toolName === 'delete_file')) {
344
- }
345
396
  toolResponses.push({
346
397
  functionResponse: {
347
398
  name: toolName,
348
399
  response: {
349
400
  output: toolResult.output,
350
- ...(toolResult.error ? { error: toolResult.error } : {}),
401
+ ...(toolError ? { error: toolError } : {}),
351
402
  ...(toolResult.inlineData ? { inlineData: toolResult.inlineData } : {}),
352
403
  },
353
404
  },
@@ -139,13 +139,13 @@ export declare function modifyFile(workspaceRoot: string, filePath: string, edit
139
139
  */
140
140
  export declare function listDirectory(workspaceRoot: string, dirPath: string, maxDepth?: number): Promise<ToolResult>;
141
141
  /**
142
- * Executes a shell command within the workspace root with a timeout and buffer limit.
142
+ * Condenses verbose command and test failure outputs into high-signal diagnostic error logs
143
+ * with dynamic tail sizing and stack trace boundary snapping.
143
144
  *
144
- * @param workspaceRoot - Absolute path to the workspace root directory.
145
- * @param command - The shell command string to execute.
146
- * @param abortSignal - Optional AbortSignal to cancel execution.
147
- * @returns A promise resolving to a {@link ToolResult} containing stdout/stderr or an error.
145
+ * @param rawError - The full error or standard error string.
146
+ * @returns The condensed high-signal diagnostic output.
148
147
  */
148
+ export declare function extractHighSignalError(rawError: string): string;
149
149
  export declare function runCommand(workspaceRoot: string, command: string, abortSignal?: AbortSignal): Promise<ToolResult>;
150
150
  /**
151
151
  * Performs a grep text or regular expression search across files in the workspace.
@@ -27,7 +27,7 @@ import { findDependencies, formatDependencyResult } from '../utils/dependencyTra
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
- import { getMetricCollector } from './metrics.js';
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
33
  import { runFuzzProbe, checkHeapDelta as runCheckHeapDelta, checkBehavioralDrift as runCheckBehavioralDrift, runEphemeralScript, } from '../utils/analysisRunner.js';
@@ -1110,6 +1110,137 @@ export async function listDirectory(workspaceRoot, dirPath, maxDepth = 3) {
1110
1110
  * @param abortSignal - Optional AbortSignal to cancel execution.
1111
1111
  * @returns A promise resolving to a {@link ToolResult} containing stdout/stderr or an error.
1112
1112
  */
1113
+ /**
1114
+ * High-signal error patterns across major languages and compiler toolchains.
1115
+ * Linker notes and candidate diagnostic details are explicitly retained.
1116
+ */
1117
+ const HIGH_SIGNAL_ERROR_PATTERNS = [
1118
+ // C / C++ / Cython / Clang / GCC
1119
+ /fatal error:/i,
1120
+ /#error/i,
1121
+ /\berror:\b/i,
1122
+ /undefined symbol:/i,
1123
+ /undefined reference/i,
1124
+ /ld: symbol\(s\) not found/i,
1125
+ /clang: error:/i,
1126
+ /referenced from:/i,
1127
+ /note: expanded from macro/i,
1128
+ /note: candidate:/i,
1129
+ // Python / Pytest
1130
+ /Traceback \(most recent call last\):/i,
1131
+ /AssertionError:/i,
1132
+ /TypeError:/i,
1133
+ /ImportError:/i,
1134
+ /ModuleNotFoundError:/i,
1135
+ /FAILED \(failures=/i,
1136
+ /\bE\s{3}\b/,
1137
+ /\bFAIL\b/,
1138
+ // Node.js / TypeScript / Jest / Mocha
1139
+ /SyntaxError:/i,
1140
+ /ReferenceError:/i,
1141
+ /Error: Cannot find module/i,
1142
+ /\bFAIL\b/,
1143
+ /✕\s/,
1144
+ /TS\d{4}:/,
1145
+ // Rust / Cargo
1146
+ /error\[E\d+\]:/i,
1147
+ /fatal runtime error:/i,
1148
+ /panicked at/i,
1149
+ // Go
1150
+ /panic:/i,
1151
+ /cannot find package/i,
1152
+ /undefined:/i,
1153
+ /FAIL\t/,
1154
+ // Java / JVM
1155
+ /Exception in thread/i,
1156
+ /java\.lang\./i,
1157
+ /error: cannot find symbol/i,
1158
+ // Generic Fallbacks
1159
+ /\b(cannot|unable to|not found|command not found|no such file)\b/i,
1160
+ ];
1161
+ /**
1162
+ * Noise filter pattern for compiler warning churn when total output is extensive.
1163
+ */
1164
+ const COMPILER_WARNING_NOISE_PATTERN = /-W(deprecated-declarations|unused-variable|unused-parameter|unused-function|sign-compare|ignored-qualifiers|nullability-completeness)/i;
1165
+ /**
1166
+ * Start boundary patterns for common multi-line stack traces.
1167
+ */
1168
+ const STACK_TRACE_START_PATTERNS = [
1169
+ /Traceback \(most recent call last\):/i,
1170
+ /Exception in thread/i,
1171
+ /panic:/i,
1172
+ /Error:\s*$/i,
1173
+ ];
1174
+ /**
1175
+ * Condenses verbose command and test failure outputs into high-signal diagnostic error logs
1176
+ * with dynamic tail sizing and stack trace boundary snapping.
1177
+ *
1178
+ * @param rawError - The full error or standard error string.
1179
+ * @returns The condensed high-signal diagnostic output.
1180
+ */
1181
+ export function extractHighSignalError(rawError) {
1182
+ if (!rawError || typeof rawError !== 'string')
1183
+ return '';
1184
+ // Threshold passthrough: if < 3000 chars or < 50 lines, return untouched
1185
+ const lines = rawError.split(/\r?\n/);
1186
+ if (rawError.length < 3000 && lines.length < 50) {
1187
+ return rawError;
1188
+ }
1189
+ // Filter out noise lines (e.g. repetitive compiler warning flags)
1190
+ const nonNoiseLines = lines.filter((l) => !COMPILER_WARNING_NOISE_PATTERN.test(l));
1191
+ // Find high-signal error lines
1192
+ const highSignalLines = [];
1193
+ const highSignalIndices = new Set();
1194
+ for (let i = 0; i < nonNoiseLines.length; i++) {
1195
+ const line = nonNoiseLines[i];
1196
+ if (HIGH_SIGNAL_ERROR_PATTERNS.some((p) => p.test(line))) {
1197
+ highSignalLines.push(line);
1198
+ highSignalIndices.add(i);
1199
+ }
1200
+ }
1201
+ // Dynamic tail sizing: Math.min(50, Math.max(15, Math.floor(totalLines * 0.15)))
1202
+ const dynamicTailCount = Math.min(50, Math.max(15, Math.floor(nonNoiseLines.length * 0.15)));
1203
+ let tailStartIndex = Math.max(0, nonNoiseLines.length - dynamicTailCount);
1204
+ // Stack-trace boundary snapping: if a stack trace starts up to 15 lines above tailStartIndex, snap upward
1205
+ const lookbackMax = Math.max(0, tailStartIndex - 15);
1206
+ for (let i = tailStartIndex; i >= lookbackMax; i--) {
1207
+ if (STACK_TRACE_START_PATTERNS.some((p) => p.test(nonNoiseLines[i]))) {
1208
+ tailStartIndex = i;
1209
+ break;
1210
+ }
1211
+ }
1212
+ const tailLines = nonNoiseLines.slice(tailStartIndex);
1213
+ // If high-signal lines are all already present within the tail, return tail directly (with note if truncated)
1214
+ const isHighSignalInTail = Array.from(highSignalIndices).every((idx) => idx >= tailStartIndex);
1215
+ if (isHighSignalInTail && tailStartIndex === 0) {
1216
+ return nonNoiseLines.join('\n');
1217
+ }
1218
+ const resultBlocks = [];
1219
+ // Add high signal summary if there are high-signal lines occurring before the tail
1220
+ const precedingHighSignal = highSignalLines.filter((_, idx) => {
1221
+ const originalIndex = Array.from(highSignalIndices)[idx];
1222
+ return originalIndex < tailStartIndex;
1223
+ });
1224
+ if (precedingHighSignal.length > 0) {
1225
+ resultBlocks.push(`--- [High-Signal Diagnostic Highlights (${precedingHighSignal.length} findings)] ---\n` +
1226
+ precedingHighSignal.join('\n'));
1227
+ }
1228
+ if (tailStartIndex > 0) {
1229
+ resultBlocks.push(`--- [Output Tail (last ${tailLines.length} of ${nonNoiseLines.length} lines)] ---\n` +
1230
+ tailLines.join('\n'));
1231
+ }
1232
+ else {
1233
+ resultBlocks.push(tailLines.join('\n'));
1234
+ }
1235
+ const condensedResult = resultBlocks.join('\n\n');
1236
+ const condensedLines = condensedResult.split(/\r?\n/).length;
1237
+ const linesPruned = Math.max(0, lines.length - condensedLines);
1238
+ const charsPruned = Math.max(0, rawError.length - condensedResult.length);
1239
+ if (linesPruned > 0 || charsPruned > 0) {
1240
+ recordRecoveryPrunedLogVolume(linesPruned, charsPruned);
1241
+ }
1242
+ return condensedResult;
1243
+ }
1113
1244
  export async function runCommand(workspaceRoot, command, abortSignal) {
1114
1245
  // Reject sudo or interactive root commands to prevent non-interactive subshell deadlocks and security escalation
1115
1246
  if (/\bsudo\b/i.test(command)) {
@@ -1137,12 +1268,13 @@ export async function runCommand(workspaceRoot, command, abortSignal) {
1137
1268
  }
1138
1269
  catch (err) {
1139
1270
  const message = err instanceof Error ? err.message : String(err);
1140
- // Also truncate error output
1271
+ // Condense error output with high-signal extraction and dynamic tail sizing
1272
+ const condensedError = extractHighSignalError(message);
1141
1273
  const MAX_ERR_OUTPUT = 30_000;
1142
- const truncatedMsg = message.length > MAX_ERR_OUTPUT
1143
- ? message.substring(0, MAX_ERR_OUTPUT) +
1144
- `\n\n... (Error output truncated: ${message.length} bytes exceeded 30KB limit. Pipe to a file if you need full logs.)`
1145
- : message;
1274
+ const truncatedMsg = condensedError.length > MAX_ERR_OUTPUT
1275
+ ? condensedError.substring(0, MAX_ERR_OUTPUT) +
1276
+ `\n\n... (Error output truncated: ${condensedError.length} bytes exceeded 30KB limit. Pipe to a file if you need full logs.)`
1277
+ : condensedError;
1146
1278
  return { output: '', error: `Command failed: ${truncatedMsg}` };
1147
1279
  }
1148
1280
  }
@@ -1389,15 +1521,21 @@ export async function runDebugScript(workspaceRoot, language, code, abortSignal)
1389
1521
  if (!finalOutput.trim()) {
1390
1522
  finalOutput = 'Script executed successfully with no output.';
1391
1523
  }
1524
+ const condensedOutput = extractHighSignalError(finalOutput.trim());
1392
1525
  return {
1393
- output: `<test_results>\n${sanitizeForCDATA(finalOutput.trim())}\n</test_results>`,
1394
- ...(res.exitCode !== 0 ? { error: `Script failed with exit code ${res.exitCode}` } : {}),
1526
+ output: `<test_results>\n${sanitizeForCDATA(condensedOutput)}\n</test_results>`,
1527
+ ...(res.exitCode !== 0
1528
+ ? {
1529
+ error: `Script failed with exit code ${res.exitCode}:\n${extractHighSignalError(res.stderr || res.stdout || '')}`,
1530
+ }
1531
+ : {}),
1395
1532
  };
1396
1533
  }
1397
1534
  catch (err) {
1535
+ const msg = err instanceof Error ? err.message : String(err);
1398
1536
  return {
1399
1537
  output: '',
1400
- error: `Failed to execute debug script: ${err instanceof Error ? err.message : String(err)}`,
1538
+ error: `Failed to execute debug script: ${extractHighSignalError(msg)}`,
1401
1539
  };
1402
1540
  }
1403
1541
  }
@@ -10,6 +10,7 @@ export interface ContextAgentResult {
10
10
  fromMemoryBank?: boolean;
11
11
  isParallel?: boolean;
12
12
  }
13
+ export declare function detectProjectType(workspaceRoot: string): Promise<string>;
13
14
  export interface IntentRoute {
14
15
  needsContext: boolean;
15
16
  targetAgent: 'CHAT' | 'EXECUTE';
@@ -51,8 +51,15 @@ function boundToolOutput(output) {
51
51
  truncationMarker: `\n\n... [Tool output truncated: exceeded ${MAX_TOOL_OUTPUT_TOKENS} tokens limit] ...\n`,
52
52
  });
53
53
  }
54
- async function detectProjectType(workspaceRoot) {
54
+ export async function detectProjectType(workspaceRoot) {
55
55
  const types = [];
56
+ // Detect Host Platform & Architecture
57
+ const platformName = process.platform === 'darwin' ? 'Darwin' : process.platform === 'win32' ? 'Windows' : 'Linux';
58
+ const isAppleSilicon = process.platform === 'darwin' && process.arch === 'arm64';
59
+ const hostDesc = isAppleSilicon
60
+ ? `Host: ${platformName} ${process.arch} (Apple Silicon)`
61
+ : `Host: ${platformName} ${process.arch}`;
62
+ types.push(hostDesc);
56
63
  const fileExists = async (fileName) => {
57
64
  try {
58
65
  await fs.access(path.join(workspaceRoot, fileName));
@@ -62,6 +69,13 @@ async function detectProjectType(workspaceRoot) {
62
69
  return false;
63
70
  }
64
71
  };
72
+ // C / C++ / Native Build Toolchains
73
+ if (await fileExists('CMakeLists.txt'))
74
+ types.push('CMake');
75
+ if (await fileExists('Makefile'))
76
+ types.push('Makefile');
77
+ if (await fileExists('meson.build'))
78
+ types.push('Meson');
65
79
  // Node.js Ecosystem
66
80
  if (await fileExists('package.json')) {
67
81
  types.push('Node.js');
@@ -93,17 +107,17 @@ async function detectProjectType(workspaceRoot) {
93
107
  types.push('NestJS');
94
108
  if (deps['vite'])
95
109
  types.push('Vite');
96
- if (deps['tailwindcss'])
97
- types.push('Tailwind CSS');
98
- if (deps['firebase'])
99
- types.push('Firebase');
100
110
  }
101
111
  catch { }
102
112
  if (await fileExists('tsconfig.json'))
103
113
  types.push('TypeScript');
104
114
  }
105
115
  // Python Ecosystem
106
- if ((await fileExists('pyproject.toml')) || (await fileExists('requirements.txt')) || (await fileExists('Pipfile'))) {
116
+ if ((await fileExists('pyproject.toml')) ||
117
+ (await fileExists('requirements.txt')) ||
118
+ (await fileExists('setup.py')) ||
119
+ (await fileExists('setup.cfg')) ||
120
+ (await fileExists('Pipfile'))) {
107
121
  types.push('Python');
108
122
  try {
109
123
  const reqs = (await fileExists('requirements.txt'))
@@ -119,6 +133,15 @@ async function detectProjectType(workspaceRoot) {
119
133
  types.push('Flask');
120
134
  if (combined.includes('fastapi'))
121
135
  types.push('FastAPI');
136
+ if (combined.includes('cython') || (await fileExists('setup.py'))) {
137
+ try {
138
+ const files = await fs.readdir(workspaceRoot);
139
+ if (files.some((f) => f.endsWith('.pyx') || f.endsWith('.pxd') || f.endsWith('.c') || f.endsWith('.cpp'))) {
140
+ types.push('Cython/C-Extensions');
141
+ }
142
+ }
143
+ catch { }
144
+ }
122
145
  }
123
146
  catch { }
124
147
  }
@@ -19,6 +19,8 @@ export interface MetricCollector {
19
19
  recordCacheHit(cacheType?: 'investigation' | 'read'): void;
20
20
  recordCacheMiss(cacheType?: 'investigation' | 'read'): void;
21
21
  recordCachePerformance(cacheType: 'investigation' | 'read', durationMs: number): void;
22
+ recordCircuitBreakerTrip?(): void;
23
+ recordPrunedLogVolume?(lines: number, chars: number): void;
22
24
  recordWriteFailure?(): void;
23
25
  recordModifyFailure?(): void;
24
26
  recordToolFailure?(toolName?: string): void;
@@ -33,3 +35,11 @@ export declare function getTurnTotals(): {
33
35
  outputTokens: number;
34
36
  cachedTokens: number;
35
37
  };
38
+ export declare function recordRecoveryCircuitBreakerTrip(): void;
39
+ export declare function recordRecoveryPrunedLogVolume(lines: number, chars: number): void;
40
+ export declare function getRecoveryMetrics(): {
41
+ circuitBreakerTrips: number;
42
+ prunedLines: number;
43
+ prunedChars: number;
44
+ };
45
+ export declare function resetRecoveryMetrics(): void;