minovative-mind-cli 2.11.5 → 2.13.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.
Files changed (43) hide show
  1. package/README.md +27 -1
  2. package/dist/commands/chat.js +3 -1
  3. package/dist/commands/eval.d.ts +22 -0
  4. package/dist/commands/eval.js +141 -0
  5. package/dist/index.d.ts +1 -0
  6. package/dist/index.js +1 -0
  7. package/dist/services/agent/slashCommands.js +4 -2
  8. package/dist/services/agent/toolLoop.d.ts +4 -0
  9. package/dist/services/agent/toolLoop.js +61 -10
  10. package/dist/services/agent-tools.d.ts +5 -5
  11. package/dist/services/agent-tools.js +150 -11
  12. package/dist/services/contextAgent.d.ts +1 -0
  13. package/dist/services/contextAgent.js +29 -6
  14. package/dist/services/ideOptimization.d.ts +15 -0
  15. package/dist/services/ideOptimization.js +169 -0
  16. package/dist/services/metrics.d.ts +10 -0
  17. package/dist/services/metrics.js +24 -0
  18. package/dist/services/orchestration/messageBus.d.ts +81 -41
  19. package/dist/services/orchestration/messageBus.js +242 -98
  20. package/dist/services/orchestration/orchestrator.d.ts +6 -6
  21. package/dist/services/orchestration/orchestrator.js +32 -21
  22. package/dist/services/orchestration/scopedTools.d.ts +7 -1
  23. package/dist/services/orchestration/scopedTools.js +45 -9
  24. package/dist/services/orchestration/subAgent.d.ts +19 -17
  25. package/dist/services/orchestration/subAgent.js +98 -81
  26. package/dist/services/swebench/gitDiffExtractor.d.ts +57 -0
  27. package/dist/services/swebench/gitDiffExtractor.js +209 -0
  28. package/dist/services/swebench/index.d.ts +4 -0
  29. package/dist/services/swebench/index.js +4 -0
  30. package/dist/services/swebench/instanceLoader.d.ts +21 -0
  31. package/dist/services/swebench/instanceLoader.js +171 -0
  32. package/dist/services/swebench/sweBenchRunnerService.d.ts +38 -0
  33. package/dist/services/swebench/sweBenchRunnerService.js +618 -0
  34. package/dist/services/swebench/types.d.ts +167 -0
  35. package/dist/services/swebench/types.js +7 -0
  36. package/dist/services/verificationService.js +3 -0
  37. package/dist/utils/fuzzyMatch.d.ts +51 -21
  38. package/dist/utils/fuzzyMatch.js +37 -122
  39. package/dist/utils/projectStorage.js +10 -5
  40. package/dist/utils/systemPrompts.d.ts +1 -1
  41. package/dist/utils/systemPrompts.js +10 -4
  42. package/oclif.manifest.json +137 -1
  43. package/package.json +1 -1
@@ -0,0 +1,618 @@
1
+ import * as path from 'node:path';
2
+ import * as fs from 'node:fs';
3
+ import { loadInstancesFromFile, filterInstances, appendPrediction, savePredictions, } from './instanceLoader.js';
4
+ import { extractGitPatch, isGitRepository, resetWorkspaceRepo, cloneOrEnsureRepo, checkoutBaseCommit, } from './gitDiffExtractor.js';
5
+ import { setApprovalMode } from '../agent-tools.js';
6
+ import { executeSingleTurn } from '../agent.js';
7
+ import { createSharedChatSession } from '../ai.js';
8
+ import { AsyncInputHandler } from '../agent/inputHandler.js';
9
+ import { getRecoveryMetrics, resetRecoveryMetrics } from '../metrics.js';
10
+ export const DEFAULT_MODEL_NAME = 'minovative-mind-agent';
11
+ export const DEFAULT_TIMEOUT_MS = 10 * 60 * 1000; // 10 minutes per instance
12
+ /**
13
+ * Formats a SWE-bench instance into a comprehensive problem prompt for the agent.
14
+ */
15
+ export function buildInstancePrompt(instance) {
16
+ const parts = [
17
+ `You are an expert software engineer resolving an issue in the repository ${instance.repo}.`,
18
+ '',
19
+ '# Problem Statement',
20
+ instance.problem_statement.trim(),
21
+ ];
22
+ if (instance.hints_text && instance.hints_text.trim().length > 0) {
23
+ parts.push('', '# Hints', instance.hints_text.trim());
24
+ }
25
+ parts.push('', '# Instructions', '1. Investigate the repository to locate the relevant files, functions, and logic.', '2. Implement precise code edits to resolve the problem described above.', '3. Ensure your solution is clean, robust, and does not alter unrelated files or tests.');
26
+ return parts.join('\n');
27
+ }
28
+ import * as os from 'node:os';
29
+ /**
30
+ * Resolves the workspace directory for an instance based on configuration.
31
+ */
32
+ export function resolveInstanceWorkspaceDir(instance, options = {}) {
33
+ if (options.workspaceDirMap) {
34
+ if (options.workspaceDirMap[instance.instance_id]) {
35
+ return path.resolve(options.workspaceDirMap[instance.instance_id]);
36
+ }
37
+ if (options.workspaceDirMap[instance.repo]) {
38
+ return path.resolve(options.workspaceDirMap[instance.repo]);
39
+ }
40
+ }
41
+ const baseDir = options.workspacesBaseDir || (options.autoCloneRepos ? path.join(os.homedir(), '.cache', 'swe-bench-repos') : undefined);
42
+ if (baseDir) {
43
+ // Check if instance_id folder exists or repo name folder
44
+ const byId = path.resolve(baseDir, instance.instance_id);
45
+ if (fs.existsSync(byId)) {
46
+ return byId;
47
+ }
48
+ const sanitizedRepo = instance.repo.replace('/', '__');
49
+ const byRepo = path.resolve(baseDir, sanitizedRepo);
50
+ if (fs.existsSync(byRepo)) {
51
+ return byRepo;
52
+ }
53
+ return byRepo;
54
+ }
55
+ return options.defaultDir ? path.resolve(options.defaultDir) : process.cwd();
56
+ }
57
+ /**
58
+ * Default agent execution loop implementation running mmcli executeSingleTurn.
59
+ */
60
+ async function defaultAgentExecutor(params) {
61
+ resetRecoveryMetrics();
62
+ const chat = createSharedChatSession();
63
+ const inputHandler = new AsyncInputHandler();
64
+ const chatSessionState = {
65
+ id: `swebench-${params.instance.instance_id}-${Date.now()}`,
66
+ title: `SWE-bench: ${params.instance.instance_id}`,
67
+ totalTokens: 0,
68
+ totalInputTokens: 0,
69
+ totalOutputTokens: 0,
70
+ totalCreditsUsed: 0,
71
+ latestUsageMetadata: undefined,
72
+ };
73
+ await executeSingleTurn(params.workspaceDir, params.prompt, chat, inputHandler, chatSessionState, false);
74
+ const cached = chatSessionState.latestUsageMetadata?.cachedTokens ||
75
+ chatSessionState.latestUsageMetadata?.cachedContentTokenCount ||
76
+ 0;
77
+ const input = chatSessionState.totalInputTokens || 0;
78
+ const output = chatSessionState.totalOutputTokens || 0;
79
+ const total = chatSessionState.totalTokens || input + output;
80
+ const recoveryMetrics = getRecoveryMetrics();
81
+ return {
82
+ tokens: {
83
+ inputTokens: input,
84
+ outputTokens: output,
85
+ cachedTokens: cached,
86
+ totalTokens: total,
87
+ },
88
+ recoveryMetrics,
89
+ };
90
+ }
91
+ /**
92
+ * Executes the mmcli agent loop on a single SWE-bench instance and extracts the resulting patch.
93
+ */
94
+ export async function runSWEBenchInstance(instance, options = {}) {
95
+ const startTime = Date.now();
96
+ const modelName = options.modelName || DEFAULT_MODEL_NAME;
97
+ const workspaceDir = options.workspaceDir ? path.resolve(options.workspaceDir) : process.cwd();
98
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
99
+ const logs = [];
100
+ const log = (msg) => {
101
+ logs.push(`[${new Date().toISOString()}] ${msg}`);
102
+ if (options.onLog) {
103
+ options.onLog(msg);
104
+ }
105
+ };
106
+ log(`Starting evaluation on instance ${instance.instance_id} (${instance.repo})`);
107
+ let executionSuccess = false;
108
+ let turnCount = 0;
109
+ let executionError;
110
+ let instanceTokens;
111
+ let instanceRecoveryMetrics;
112
+ let patch = '';
113
+ try {
114
+ setApprovalMode('skip-all');
115
+ resetRecoveryMetrics();
116
+ // 1. Prepare Workspace (Auto-clone / Reset to base commit if requested)
117
+ let isRepo = await isGitRepository(workspaceDir);
118
+ if (!isRepo && options.autoCloneRepos && instance.repo) {
119
+ log(`Repository not found locally. Auto-cloning ${instance.repo} into ${workspaceDir}...`);
120
+ await cloneOrEnsureRepo(instance.repo, workspaceDir);
121
+ isRepo = true;
122
+ }
123
+ if (isRepo && instance.base_commit) {
124
+ if (options.resetWorkspace !== false) {
125
+ log(`Resetting workspace to base commit ${instance.base_commit}...`);
126
+ await checkoutBaseCommit(workspaceDir, instance.base_commit);
127
+ await resetWorkspaceRepo(workspaceDir);
128
+ }
129
+ }
130
+ // 2. Construct Problem Prompt
131
+ const prompt = buildInstancePrompt(instance);
132
+ // 3. Execute mmcli Agent Loop with Timeout (or skip if dryRun)
133
+ if (options.dryRun) {
134
+ log(`Dry run enabled - skipping agent execution loop`);
135
+ executionSuccess = true;
136
+ }
137
+ else {
138
+ log(`Executing agent loop for problem statement...`);
139
+ const executor = options.agentExecutor || defaultAgentExecutor;
140
+ const prevSkipVerification = process.env.MMCLI_SKIP_VERIFICATION;
141
+ process.env.MMCLI_SKIP_VERIFICATION = 'true';
142
+ try {
143
+ const executeWithTimeout = async () => {
144
+ const execRes = await executor({
145
+ instance,
146
+ workspaceDir,
147
+ prompt,
148
+ abortSignal: options.abortSignal,
149
+ onLog: options.onLog,
150
+ });
151
+ if (execRes && typeof execRes === 'object') {
152
+ if ('tokens' in execRes && execRes.tokens) {
153
+ instanceTokens = execRes.tokens;
154
+ }
155
+ if ('recoveryMetrics' in execRes && execRes.recoveryMetrics) {
156
+ instanceRecoveryMetrics = execRes.recoveryMetrics;
157
+ }
158
+ }
159
+ if (!instanceRecoveryMetrics) {
160
+ instanceRecoveryMetrics = getRecoveryMetrics();
161
+ }
162
+ turnCount++;
163
+ };
164
+ const timeoutPromise = new Promise((_, reject) => {
165
+ const timer = setTimeout(() => {
166
+ reject(new Error(`Instance evaluation timed out after ${timeoutMs}ms`));
167
+ }, timeoutMs);
168
+ if (options.abortSignal) {
169
+ options.abortSignal.addEventListener('abort', () => {
170
+ clearTimeout(timer);
171
+ reject(new Error('Instance evaluation aborted by signal'));
172
+ });
173
+ }
174
+ });
175
+ await Promise.race([executeWithTimeout(), timeoutPromise]);
176
+ executionSuccess = true;
177
+ log(`Agent execution completed successfully`);
178
+ }
179
+ finally {
180
+ if (prevSkipVerification !== undefined) {
181
+ process.env.MMCLI_SKIP_VERIFICATION = prevSkipVerification;
182
+ }
183
+ else {
184
+ delete process.env.MMCLI_SKIP_VERIFICATION;
185
+ }
186
+ }
187
+ }
188
+ }
189
+ catch (err) {
190
+ const msg = err instanceof Error ? err.message : String(err);
191
+ executionError = msg;
192
+ log(`Agent execution failed: ${msg}`);
193
+ }
194
+ finally {
195
+ // 4. Extract Git Diff Patch
196
+ try {
197
+ const isRepo = await isGitRepository(workspaceDir);
198
+ if (isRepo) {
199
+ log(`Extracting git diff patch against base commit ${instance.base_commit || 'HEAD'}...`);
200
+ patch = await extractGitPatch(workspaceDir, {
201
+ baseCommit: instance.base_commit,
202
+ includeUntracked: true,
203
+ });
204
+ log(`Extracted patch size: ${patch.length} characters`);
205
+ }
206
+ else {
207
+ log(`Skipping patch extraction: workspace is not a git repository`);
208
+ }
209
+ }
210
+ catch (err) {
211
+ const msg = err instanceof Error ? err.message : String(err);
212
+ log(`Failed to extract git diff patch: ${msg}`);
213
+ if (!executionError) {
214
+ executionError = `Patch extraction error: ${msg}`;
215
+ }
216
+ }
217
+ }
218
+ const prediction = {
219
+ instance_id: instance.instance_id,
220
+ model_patch: patch,
221
+ model_name_or_path: modelName,
222
+ };
223
+ return {
224
+ instance_id: instance.instance_id,
225
+ repo: instance.repo,
226
+ base_commit: instance.base_commit,
227
+ prediction,
228
+ success: executionSuccess && !executionError,
229
+ executionTimeMs: Date.now() - startTime,
230
+ diffSizeChars: patch.length,
231
+ turnCount,
232
+ tokens: instanceTokens,
233
+ recoveryMetrics: instanceRecoveryMetrics || getRecoveryMetrics(),
234
+ error: executionError,
235
+ logs,
236
+ };
237
+ }
238
+ /**
239
+ * Runs evaluation across a suite of SWE-bench instances.
240
+ */
241
+ export async function runSWEBenchEvaluation(options) {
242
+ const startTime = Date.now();
243
+ const logger = options.logger || console;
244
+ // 1. Load instances
245
+ let instances = [];
246
+ if (options.instances && options.instances.length > 0) {
247
+ instances = filterInstances(options.instances, {
248
+ instanceIds: options.instanceIds,
249
+ repo: options.repo,
250
+ limit: options.limit,
251
+ offset: options.offset,
252
+ shuffle: options.shuffle,
253
+ seed: options.seed,
254
+ });
255
+ }
256
+ else if (options.instancesPath) {
257
+ instances = await loadInstancesFromFile(options.instancesPath, {
258
+ instanceIds: options.instanceIds,
259
+ repo: options.repo,
260
+ limit: options.limit,
261
+ offset: options.offset,
262
+ shuffle: options.shuffle,
263
+ seed: options.seed,
264
+ });
265
+ }
266
+ else {
267
+ throw new Error('Either `instances` array or `instancesPath` must be provided.');
268
+ }
269
+ const total = instances.length;
270
+ logger.info?.(`Loaded ${total} SWE-bench instances for evaluation`);
271
+ const results = [];
272
+ const concurrency = Math.max(1, options.concurrency || 1);
273
+ let completed = 0;
274
+ // 2. Execute instances with concurrency limit
275
+ const queue = [...instances];
276
+ async function worker() {
277
+ while (queue.length > 0) {
278
+ const instance = queue.shift();
279
+ if (!instance)
280
+ break;
281
+ const workspaceDir = resolveInstanceWorkspaceDir(instance, {
282
+ workspacesBaseDir: options.workspacesBaseDir,
283
+ workspaceDirMap: options.workspaceDirMap,
284
+ autoCloneRepos: options.autoCloneRepos,
285
+ defaultDir: process.cwd(),
286
+ });
287
+ options.onProgress?.({
288
+ completed,
289
+ total,
290
+ currentInstanceId: instance.instance_id,
291
+ status: 'running',
292
+ elapsedMs: Date.now() - startTime,
293
+ });
294
+ let result;
295
+ try {
296
+ result = await runSWEBenchInstance(instance, {
297
+ workspaceDir,
298
+ modelName: options.modelName,
299
+ maxTurns: options.maxTurns,
300
+ timeoutMs: options.timeoutMsPerInstance,
301
+ resetWorkspace: options.resetWorkspaceBeforeRun,
302
+ autoCloneRepos: options.autoCloneRepos,
303
+ dryRun: options.dryRun,
304
+ verbose: options.verbose,
305
+ agentExecutor: options.agentExecutor,
306
+ onLog: (msg) => {
307
+ if (options.verbose) {
308
+ logger.info?.(`[${instance.instance_id}] ${msg}`);
309
+ }
310
+ else {
311
+ logger.debug?.(`[${instance.instance_id}] ${msg}`);
312
+ }
313
+ },
314
+ });
315
+ }
316
+ catch (err) {
317
+ const msg = err instanceof Error ? err.message : String(err);
318
+ result = {
319
+ instance_id: instance.instance_id,
320
+ repo: instance.repo,
321
+ base_commit: instance.base_commit,
322
+ prediction: {
323
+ instance_id: instance.instance_id,
324
+ model_patch: '',
325
+ model_name_or_path: options.modelName || DEFAULT_MODEL_NAME,
326
+ },
327
+ success: false,
328
+ executionTimeMs: 0,
329
+ diffSizeChars: 0,
330
+ error: msg,
331
+ };
332
+ }
333
+ results.push(result);
334
+ completed++;
335
+ // Stream prediction to output file immediately if configured
336
+ if (options.outputPredictionsPath) {
337
+ try {
338
+ await appendPrediction(result.prediction, options.outputPredictionsPath);
339
+ }
340
+ catch (err) {
341
+ logger.warn?.(`Failed to append prediction to ${options.outputPredictionsPath}: ${err}`);
342
+ }
343
+ }
344
+ options.onInstanceComplete?.(result);
345
+ options.onProgress?.({
346
+ completed,
347
+ total,
348
+ currentInstanceId: instance.instance_id,
349
+ status: result.success ? 'success' : 'error',
350
+ elapsedMs: Date.now() - startTime,
351
+ error: result.error,
352
+ });
353
+ logger.info?.(`[${completed}/${total}] Instance ${instance.instance_id} finished: ${result.success ? 'SUCCESS' : 'FAILED'} (diff size: ${result.diffSizeChars} chars, time: ${(result.executionTimeMs / 1000).toFixed(1)}s)`);
354
+ }
355
+ }
356
+ const workers = Array.from({ length: concurrency }, () => worker());
357
+ await Promise.all(workers);
358
+ const succeeded = results.filter((r) => r.success).length;
359
+ const failed = results.filter((r) => !r.success).length;
360
+ const durationMs = Date.now() - startTime;
361
+ const metrics = calculateEvaluationMetrics(results, durationMs, options.modelName || DEFAULT_MODEL_NAME);
362
+ // Ensure full predictions file is saved cleanly if output path given
363
+ if (options.outputPredictionsPath) {
364
+ const predictions = results.map((r) => r.prediction);
365
+ await savePredictions(predictions, options.outputPredictionsPath);
366
+ }
367
+ const summary = {
368
+ total,
369
+ succeeded,
370
+ failed,
371
+ skipped: total - (succeeded + failed),
372
+ durationMs,
373
+ predictionsFilePath: options.outputPredictionsPath,
374
+ reportFilePath: options.reportPath,
375
+ metrics,
376
+ results,
377
+ };
378
+ if (options.reportPath) {
379
+ try {
380
+ await saveEvaluationReport(summary, options.reportPath);
381
+ }
382
+ catch (err) {
383
+ logger.warn?.(`Failed to write evaluation report to ${options.reportPath}: ${err}`);
384
+ }
385
+ }
386
+ return summary;
387
+ }
388
+ /**
389
+ * Computes aggregate metrics across SWE-bench evaluation results including token usage,
390
+ * repository pass rates, diff sizes, and timing statistics.
391
+ */
392
+ export function calculateEvaluationMetrics(results, totalDurationMs, modelName = DEFAULT_MODEL_NAME) {
393
+ let totalTokens = 0;
394
+ let totalInputTokens = 0;
395
+ let totalOutputTokens = 0;
396
+ let totalCachedTokens = 0;
397
+ let totalDiffCharacters = 0;
398
+ let minDurationMs = results.length > 0 ? Infinity : 0;
399
+ let maxDurationMs = 0;
400
+ let totalCircuitBreakerTrips = 0;
401
+ let totalPrunedLines = 0;
402
+ let totalPrunedChars = 0;
403
+ const repoMap = {};
404
+ for (const res of results) {
405
+ if (res.tokens) {
406
+ totalTokens += res.tokens.totalTokens || 0;
407
+ totalInputTokens += res.tokens.inputTokens || 0;
408
+ totalOutputTokens += res.tokens.outputTokens || 0;
409
+ totalCachedTokens += res.tokens.cachedTokens || 0;
410
+ }
411
+ if (res.recoveryMetrics) {
412
+ totalCircuitBreakerTrips += res.recoveryMetrics.circuitBreakerTrips || 0;
413
+ totalPrunedLines += res.recoveryMetrics.prunedLines || 0;
414
+ totalPrunedChars += res.recoveryMetrics.prunedChars || 0;
415
+ }
416
+ totalDiffCharacters += res.diffSizeChars || 0;
417
+ if (res.executionTimeMs < minDurationMs)
418
+ minDurationMs = res.executionTimeMs;
419
+ if (res.executionTimeMs > maxDurationMs)
420
+ maxDurationMs = res.executionTimeMs;
421
+ const repoKey = res.repo || 'unknown';
422
+ if (!repoMap[repoKey]) {
423
+ repoMap[repoKey] = { total: 0, succeeded: 0, failed: 0, totalDurationMs: 0 };
424
+ }
425
+ repoMap[repoKey].total++;
426
+ if (res.success) {
427
+ repoMap[repoKey].succeeded++;
428
+ }
429
+ else {
430
+ repoMap[repoKey].failed++;
431
+ }
432
+ repoMap[repoKey].totalDurationMs += res.executionTimeMs;
433
+ }
434
+ if (minDurationMs === Infinity)
435
+ minDurationMs = 0;
436
+ const repoBreakdown = {};
437
+ for (const [repo, stats] of Object.entries(repoMap)) {
438
+ repoBreakdown[repo] = {
439
+ total: stats.total,
440
+ succeeded: stats.succeeded,
441
+ failed: stats.failed,
442
+ passRatePercent: stats.total > 0 ? Number(((stats.succeeded / stats.total) * 100).toFixed(1)) : 0,
443
+ averageDurationMs: stats.total > 0 ? Math.round(stats.totalDurationMs / stats.total) : 0,
444
+ };
445
+ }
446
+ const cacheHitRatePercent = totalInputTokens > 0 ? Number(((totalCachedTokens / totalInputTokens) * 100).toFixed(1)) : 0;
447
+ const platformName = process.platform === 'darwin'
448
+ ? 'Darwin (macOS)'
449
+ : process.platform === 'win32'
450
+ ? 'Windows'
451
+ : 'Linux';
452
+ const isAppleSilicon = process.platform === 'darwin' && process.arch === 'arm64';
453
+ return {
454
+ totalTokens,
455
+ totalInputTokens,
456
+ totalOutputTokens,
457
+ totalCachedTokens,
458
+ cacheHitRatePercent,
459
+ averageDurationMs: results.length > 0 ? Math.round(totalDurationMs / results.length) : 0,
460
+ minDurationMs,
461
+ maxDurationMs,
462
+ totalDiffCharacters,
463
+ averageDiffCharacters: results.length > 0 ? Math.round(totalDiffCharacters / results.length) : 0,
464
+ recoveryMetrics: {
465
+ totalCircuitBreakerTrips,
466
+ totalPrunedLines,
467
+ totalPrunedChars,
468
+ },
469
+ repoBreakdown,
470
+ environment: {
471
+ platform: isAppleSilicon ? `${platformName} (Apple Silicon)` : platformName,
472
+ architecture: process.arch,
473
+ nodeVersion: process.version,
474
+ modelName,
475
+ timestamp: new Date().toISOString(),
476
+ },
477
+ };
478
+ }
479
+ /**
480
+ * Saves a structured, public-ready evaluation report JSON file containing summary statistics,
481
+ * token economics, repository breakdown, and individual instance outcomes.
482
+ */
483
+ export async function saveEvaluationReport(summary, reportPath) {
484
+ const resolved = path.resolve(reportPath);
485
+ await fs.promises.mkdir(path.dirname(resolved), { recursive: true });
486
+ const instances = summary.results.map((r) => ({
487
+ instance_id: r.instance_id,
488
+ repo: r.repo,
489
+ base_commit: r.base_commit,
490
+ success: r.success,
491
+ execution_time_seconds: Number((r.executionTimeMs / 1000).toFixed(1)),
492
+ diff_size_characters: r.diffSizeChars,
493
+ turn_count: r.turnCount ?? 1,
494
+ tokens: r.tokens
495
+ ? {
496
+ input: r.tokens.inputTokens,
497
+ output: r.tokens.outputTokens,
498
+ cached: r.tokens.cachedTokens,
499
+ total: r.tokens.totalTokens,
500
+ }
501
+ : undefined,
502
+ recovery_metrics: r.recoveryMetrics
503
+ ? {
504
+ circuit_breaker_trips: r.recoveryMetrics.circuitBreakerTrips,
505
+ pruned_log_lines: r.recoveryMetrics.prunedLines,
506
+ pruned_log_characters: r.recoveryMetrics.prunedChars,
507
+ }
508
+ : undefined,
509
+ error: r.error,
510
+ }));
511
+ const report = {
512
+ benchmark: 'SWE-bench Lite',
513
+ timestamp: summary.metrics?.environment.timestamp || new Date().toISOString(),
514
+ environment: summary.metrics?.environment,
515
+ summary: {
516
+ total_instances: summary.total,
517
+ succeeded: summary.succeeded,
518
+ failed: summary.failed,
519
+ skipped: summary.skipped,
520
+ success_rate_percent: summary.total > 0 ? Number(((summary.succeeded / summary.total) * 100).toFixed(1)) : 0,
521
+ total_duration_seconds: Number((summary.durationMs / 1000).toFixed(1)),
522
+ average_duration_seconds: summary.total > 0 ? Number((summary.durationMs / 1000 / summary.total).toFixed(1)) : 0,
523
+ total_diff_characters: summary.metrics?.totalDiffCharacters || 0,
524
+ average_patch_characters: summary.metrics?.averageDiffCharacters || 0,
525
+ },
526
+ recovery_metrics: summary.metrics?.recoveryMetrics
527
+ ? {
528
+ circuit_breaker_trips: summary.metrics.recoveryMetrics.totalCircuitBreakerTrips,
529
+ pruned_log_lines: summary.metrics.recoveryMetrics.totalPrunedLines,
530
+ pruned_log_characters: summary.metrics.recoveryMetrics.totalPrunedChars,
531
+ }
532
+ : undefined,
533
+ token_economics: summary.metrics
534
+ ? {
535
+ total_tokens: summary.metrics.totalTokens,
536
+ total_input_tokens: summary.metrics.totalInputTokens,
537
+ total_output_tokens: summary.metrics.totalOutputTokens,
538
+ total_cached_tokens: summary.metrics.totalCachedTokens,
539
+ cache_hit_rate_percent: summary.metrics.cacheHitRatePercent,
540
+ }
541
+ : undefined,
542
+ repository_breakdown: summary.metrics?.repoBreakdown || {},
543
+ instances,
544
+ };
545
+ await fs.promises.writeFile(resolved, JSON.stringify(report, null, 2), 'utf-8');
546
+ }
547
+ /**
548
+ * Formats a clean, high-impact ASCII terminal summary dashboard for SWE-bench evaluation runs.
549
+ */
550
+ export function formatEvaluationReportSummary(summary) {
551
+ const m = summary.metrics;
552
+ const totalSec = (summary.durationMs / 1000).toFixed(1);
553
+ const avgSec = m
554
+ ? (m.averageDurationMs / 1000).toFixed(1)
555
+ : (summary.durationMs / 1000 / Math.max(1, summary.total)).toFixed(1);
556
+ const passRate = summary.total > 0 ? ((summary.succeeded / summary.total) * 100).toFixed(1) : '0.0';
557
+ const lines = [];
558
+ lines.push(`\n======================================================================`);
559
+ lines.push(` 🏆 MINOVATIVE MIND SWE-BENCH EVALUATION SUMMARY REPORT`);
560
+ lines.push(`======================================================================`);
561
+ lines.push(``);
562
+ lines.push(`📈 EXECUTION & RESOLUTION METRICS`);
563
+ lines.push(` • Total Instances Evaluated : ${summary.total}`);
564
+ lines.push(` • Succeeded Runs : ${summary.succeeded} / ${summary.total} (${passRate}%)`);
565
+ lines.push(` • Failed / Timed Out : ${summary.failed}`);
566
+ if (summary.skipped > 0) {
567
+ lines.push(` • Skipped : ${summary.skipped}`);
568
+ }
569
+ lines.push(` • Total Duration : ${totalSec}s`);
570
+ lines.push(` • Average Time / Instance : ${avgSec}s`);
571
+ if (m) {
572
+ lines.push(``);
573
+ lines.push(`💻 CODE GENERATION & PATCH METRICS`);
574
+ lines.push(` • Total Diff Volume : ${m.totalDiffCharacters.toLocaleString()} characters`);
575
+ lines.push(` • Average Patch Size : ${m.averageDiffCharacters.toLocaleString()} characters / instance`);
576
+ if (m.recoveryMetrics &&
577
+ (m.recoveryMetrics.totalCircuitBreakerTrips > 0 || m.recoveryMetrics.totalPrunedLines > 0)) {
578
+ lines.push(``);
579
+ lines.push(`🔧 RECOVERY & CIRCUIT BREAKER METRICS`);
580
+ lines.push(` • Circuit Breaker Advisories: ${m.recoveryMetrics.totalCircuitBreakerTrips} interventions (prevented repetitive command loops)`);
581
+ lines.push(` • Error Log Volume Pruned : ${m.recoveryMetrics.totalPrunedLines.toLocaleString()} lines (${m.recoveryMetrics.totalPrunedChars.toLocaleString()} characters stripped)`);
582
+ }
583
+ if (m.totalTokens > 0) {
584
+ lines.push(``);
585
+ lines.push(`🪙 TOKEN USAGE & CONTEXT CACHING`);
586
+ lines.push(` • Total Tokens Processed : ${m.totalTokens.toLocaleString()}`);
587
+ lines.push(` • Input Tokens : ${m.totalInputTokens.toLocaleString()}`);
588
+ lines.push(` • Output Tokens : ${m.totalOutputTokens.toLocaleString()}`);
589
+ lines.push(` • Cached Tokens : ${m.totalCachedTokens.toLocaleString()} (${m.cacheHitRatePercent}% CACHE HIT)`);
590
+ }
591
+ const repos = Object.entries(m.repoBreakdown);
592
+ if (repos.length > 0) {
593
+ lines.push(``);
594
+ lines.push(`📂 REPOSITORY BREAKDOWN`);
595
+ lines.push(` ┌──────────────────────────────┬────────────┬─────────────┬─────────────┐`);
596
+ lines.push(` │ Repository │ Succeeded │ Failed │ Pass Rate │`);
597
+ lines.push(` ├──────────────────────────────┼────────────┼─────────────┼─────────────┤`);
598
+ for (const [repo, stat] of repos) {
599
+ const repoPadded = repo.padEnd(28).substring(0, 28);
600
+ const succPadded = `${stat.succeeded} / ${stat.total}`.padEnd(10);
601
+ const failPadded = `${stat.failed}`.padEnd(11);
602
+ const ratePadded = `${stat.passRatePercent}%`.padEnd(11);
603
+ lines.push(` │ ${repoPadded} │ ${succPadded} │ ${failPadded} │ ${ratePadded} │`);
604
+ }
605
+ lines.push(` └──────────────────────────────┴────────────┴─────────────┴─────────────┘`);
606
+ }
607
+ }
608
+ lines.push(``);
609
+ lines.push(`📁 ARTIFACTS`);
610
+ if (summary.predictionsFilePath) {
611
+ lines.push(` • Predictions File : ${summary.predictionsFilePath}`);
612
+ }
613
+ if (summary.reportFilePath) {
614
+ lines.push(` • Detailed Report : ${summary.reportFilePath}`);
615
+ }
616
+ lines.push(`======================================================================\n`);
617
+ return lines.join('\n');
618
+ }