ft-scout 6.0.4 → 6.0.8

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 (91) hide show
  1. package/.firebase/hosting.d2Vi.cache +2 -2
  2. package/README.md +68 -44
  3. package/bin/ft.js +1 -1
  4. package/bin/src/commands/agent.d.ts.map +1 -1
  5. package/bin/src/commands/agent.js +264 -92
  6. package/bin/src/commands/agent.js.map +1 -1
  7. package/bin/src/commands/audit.js +1 -1
  8. package/bin/src/commands/audit.js.map +1 -1
  9. package/bin/src/commands/authCommand.js +4 -4
  10. package/bin/src/commands/authCommand.js.map +1 -1
  11. package/bin/src/commands/brief.js +4 -4
  12. package/bin/src/commands/brief.js.map +1 -1
  13. package/bin/src/commands/configCommand.d.ts.map +1 -1
  14. package/bin/src/commands/configCommand.js +7 -7
  15. package/bin/src/commands/configCommand.js.map +1 -1
  16. package/bin/src/commands/dashboard.js +4 -4
  17. package/bin/src/commands/dashboard.js.map +1 -1
  18. package/bin/src/commands/history.js +5 -5
  19. package/bin/src/commands/history.js.map +1 -1
  20. package/bin/src/commands/init.js +26 -26
  21. package/bin/src/commands/init.js.map +1 -1
  22. package/bin/src/commands/login.d.ts.map +1 -1
  23. package/bin/src/commands/login.js +21 -21
  24. package/bin/src/commands/login.js.map +1 -1
  25. package/bin/src/commands/logout.js +1 -1
  26. package/bin/src/commands/logout.js.map +1 -1
  27. package/bin/src/commands/owners.js +5 -5
  28. package/bin/src/commands/owners.js.map +1 -1
  29. package/bin/src/commands/quota.d.ts.map +1 -0
  30. package/bin/src/commands/quota.js +48 -0
  31. package/bin/src/commands/quota.js.map +1 -0
  32. package/bin/src/commands/recommend.js +1 -1
  33. package/bin/src/commands/recommend.js.map +1 -1
  34. package/bin/src/commands/risky.d.ts.map +1 -1
  35. package/bin/src/commands/risky.js +9 -9
  36. package/bin/src/commands/risky.js.map +1 -1
  37. package/bin/src/commands/setup.js +24 -24
  38. package/bin/src/commands/setup.js.map +1 -1
  39. package/bin/src/commands/signup.d.ts.map +1 -1
  40. package/bin/src/commands/signup.js +7 -7
  41. package/bin/src/commands/signup.js.map +1 -1
  42. package/bin/src/commands/undo.js +8 -8
  43. package/bin/src/commands/undo.js.map +1 -1
  44. package/bin/src/commands/web.js +8 -8
  45. package/bin/src/commands/web.js.map +1 -1
  46. package/bin/src/discovery/config.d.ts.map +1 -1
  47. package/bin/src/discovery/config.js.map +1 -1
  48. package/bin/src/discovery/display.js +1 -1
  49. package/bin/src/discovery/display.js.map +1 -1
  50. package/bin/src/discovery/messages.js.map +1 -1
  51. package/bin/src/discovery/signals.d.ts.map +1 -1
  52. package/bin/src/discovery/signals.js.map +1 -1
  53. package/bin/src/engine/agentEngine.d.ts.map +1 -1
  54. package/bin/src/engine/agentEngine.js +414 -77
  55. package/bin/src/engine/agentEngine.js.map +1 -1
  56. package/bin/src/engine/git.d.ts.map +1 -1
  57. package/bin/src/engine/git.js.map +1 -1
  58. package/bin/src/engine/llm.d.ts.map +1 -1
  59. package/bin/src/engine/llm.js +174 -78
  60. package/bin/src/engine/llm.js.map +1 -1
  61. package/bin/src/engine/verifier.d.ts.map +1 -1
  62. package/bin/src/engine/verifier.js +6 -6
  63. package/bin/src/engine/verifier.js.map +1 -1
  64. package/bin/src/engine/web.d.ts.map +1 -1
  65. package/bin/src/engine/web.js +1 -1
  66. package/bin/src/engine/web.js.map +1 -1
  67. package/bin/src/index.js +78 -68
  68. package/bin/src/index.js.map +1 -1
  69. package/bin/src/server/server.js.map +1 -1
  70. package/bin/src/utils/auth.d.ts.map +1 -1
  71. package/bin/src/utils/auth.js.map +1 -1
  72. package/bin/src/utils/branding.js +7 -7
  73. package/bin/src/utils/branding.js.map +1 -1
  74. package/bin/src/utils/config.d.ts.map +1 -1
  75. package/bin/src/utils/config.js.map +1 -1
  76. package/bin/src/utils/email.d.ts.map +1 -1
  77. package/bin/src/utils/email.js +42 -42
  78. package/bin/src/utils/email.js.map +1 -1
  79. package/bin/src/utils/firebaseAuth.d.ts.map +1 -1
  80. package/bin/src/utils/firebaseAuth.js.map +1 -1
  81. package/bin/src/utils/holoseneAuth.d.ts.map +1 -1
  82. package/bin/src/utils/holoseneAuth.js +22 -22
  83. package/bin/src/utils/holoseneAuth.js.map +1 -1
  84. package/bin/src/utils/language.d.ts.map +1 -1
  85. package/bin/src/utils/language.js.map +1 -1
  86. package/bin/src/utils/markdown.js +4 -4
  87. package/bin/src/utils/markdown.js.map +1 -1
  88. package/bin/src/utils/updateCheck.js +5 -5
  89. package/bin/src/utils/updateCheck.js.map +1 -1
  90. package/package.json +2 -2
  91. package/web/app.js +927 -927
@@ -3,14 +3,14 @@ import path from 'path';
3
3
  import { spinner, text, isCancel, select, confirm } from '@clack/prompts';
4
4
  import chalk from 'chalk';
5
5
  import { simpleGit } from 'simple-git';
6
- import { generateMultiCodeFix, identifyTargetFiles, analyzeCommandError, generateCodeScanReport, generateFeaturePlan, generateCodeFile, askRepoQuestion, } from '../engine/llm.js';
6
+ import { generateMultiCodeFix, identifyTargetFiles, analyzeCommandError, generateCodeScanReport, generateFeaturePlan, generateCodeFile, askRepoQuestion, getActiveModelInfo, } from '../engine/llm.js';
7
7
  import { loadContext, recordHistoryAction, getDirectoryFiles } from '../utils/config.js';
8
8
  import { renderMarkdown, safeNote } from '../utils/markdown.js';
9
9
  import { normalizeLanguage } from '../utils/language.js';
10
10
  import { createPullRequest } from '../engine/git.js';
11
11
  import { handleUndo } from './undo.js';
12
- import { AgentExecutionLoop } from '../engine/agentEngine.js';
13
- import { verifyAndSelfHealFiles, executeSmartCommand } from '../engine/verifier.js';
12
+ import { AgentExecutionLoop, buildDirectoryTree } from '../engine/agentEngine.js';
13
+ import { verifyAndSelfHealFiles, executeSmartCommand, runProjectVerification } from '../engine/verifier.js';
14
14
  import { exec } from 'child_process';
15
15
  import { promisify } from 'util';
16
16
  const execAsync = promisify(exec);
@@ -233,8 +233,8 @@ export async function handleAgent(goalInput, options) {
233
233
  console.log(`\n${chalk.bold.red('Credit Quota Exhausted!')}`);
234
234
  console.log(chalk.yellow(tierCheck.reason || 'Credit limit reached for Free Tier.'));
235
235
  console.log(chalk.cyan('\nTo continue using Scout CLI AI Agent:'));
236
- console.log(` 1. Upgrade to ${chalk.bold.green('Pro Tier ($19/mo)')} in dashboard (${chalk.underline('scout dashboard')})`);
237
- console.log(` 2. Or enable ${chalk.bold.magenta('BYOK Mode')} in dashboard to use your custom API key / proxy endpoint.\n`);
236
+ console.log(` 1. Upgrade to ${chalk.bold.green('Pro Tier ($19/mo)')} in dashboard (${chalk.underline('scout dashboard')})`);
237
+ console.log(` 2. Or enable ${chalk.bold.magenta('BYOK Mode')} in dashboard to use your custom API key / proxy endpoint.\n`);
238
238
  return;
239
239
  }
240
240
  const creditRes = consumeCredit(1);
@@ -251,7 +251,7 @@ export async function handleAgent(goalInput, options) {
251
251
  console.log(`${chalk.dim(`Scout Credit Consumed | ${creditRes.remaining} credits remaining`)}`);
252
252
  if (creditRes.isLowCredit) {
253
253
  console.log(`\n${chalk.bold.yellow('Low Credit Warning:')} Only ${chalk.bold(creditRes.remaining)} credits remaining on your current plan.`);
254
- console.log(` ${chalk.cyan('Run `scout dashboard` to upgrade your tier or activate BYOK mode.')}\n`);
254
+ console.log(` ${chalk.cyan('Run `scout dashboard` to upgrade your tier or activate BYOK mode.')}\n`);
255
255
  }
256
256
  }
257
257
  const existingContext = loadContext();
@@ -267,16 +267,26 @@ export async function handleAgent(goalInput, options) {
267
267
  language: targetLang,
268
268
  projectName,
269
269
  });
270
+ const modelInfo = getActiveModelInfo();
271
+ const fallbackPipeline = modelInfo.fallbackChain.slice(1).join(' ➔ ');
270
272
  const welcomeBanner = `
271
273
  ${chalk.bold.cyan('Scout Autonomous AI Agent')} ${targetLang ? `(${targetLang})` : ''}
274
+ ${chalk.bold('Active Model:')} ${chalk.bold.green(modelInfo.currentActiveModel)}
275
+ ${chalk.bold('Fallback Pipeline:')} ${chalk.dim(fallbackPipeline)}
276
+
272
277
  ${chalk.dim('Type your goal, or use slash commands:')}
273
- ${chalk.cyan('/help')} - Show agent tools & commands
274
- ${chalk.cyan('/clear')} - Reset conversation context
275
- ${chalk.cyan('/undo')} - Revert last file modifications
276
- ${chalk.cyan('/scan')} - Run vulnerability scan
277
- ${chalk.cyan('/plan')} - Generate implementation roadmap
278
- ${chalk.cyan('/exit')} - Exit session
279
- `.trim();
278
+ ${chalk.cyan('/help')} - Show agent tools & commands (14 tools)
279
+ ${chalk.cyan('/tree')} - View workspace directory tree
280
+ ${chalk.cyan('/stats')} - View real-time session & model telemetry
281
+ ${chalk.cyan('/test')} - Run automated project test suite
282
+ ${chalk.cyan('/diff')} - Show git diff of uncommitted changes
283
+ ${chalk.cyan('/export')} - Save session report to .ft/
284
+ ${chalk.cyan('/undo')} - Revert last file modifications
285
+ ${chalk.cyan('/clear')} - Reset conversation context
286
+ ${chalk.cyan('/scan')} - Run vulnerability scan
287
+ ${chalk.cyan('/plan')} - Generate implementation roadmap
288
+ ${chalk.cyan('/exit')} - Exit session
289
+ `.trim();
280
290
  safeNote(welcomeBanner, 'Scout Agent Active Session');
281
291
  while (true) {
282
292
  let rawGoal = undefined;
@@ -293,35 +303,154 @@ ${chalk.dim('Type your goal, or use slash commands:')}
293
303
  validate: (val) => (!val || !val.trim() ? 'Please enter a prompt or /exit to quit' : undefined),
294
304
  });
295
305
  if (isCancel(userGoalInput)) {
296
- safeNote(chalk.yellow('Exiting Scout Agent session. Goodbye!'), '👋 Scout Agent Ended');
306
+ safeNote(chalk.yellow('Exiting Scout Agent session. see you soon!'), 'Scout Agent Ended');
297
307
  break;
298
308
  }
299
309
  const trimmedInput = userGoalInput.trim();
300
310
  if (['exit', 'quit', 'q', ':q', '/exit', '/quit'].includes(trimmedInput.toLowerCase())) {
301
- safeNote(chalk.yellow('Exiting Scout Agent session. Goodbye!'), '👋 Scout Agent Ended');
311
+ safeNote(chalk.yellow('Exiting Scout Agent session. see you soon!'), 'Scout Agent Ended');
302
312
  break;
303
313
  }
304
314
  if (trimmedInput.toLowerCase() === '/help') {
305
315
  const helpText = `
306
- ### 🛠️ Scout Agent Tools
307
- - **read_file**: Read content & line ranges of codebase files.
308
- - **write_file**: Create new files or overwrite existing code.
309
- - **edit_file**: Search-and-replace exact code snippets.
310
- - **run_command**: Execute shell commands live (\`npm test\`, \`build\`).
311
- - **grep_search**: Regex/string search across all files.
312
- - **glob_search**: Pattern search for workspace files.
316
+ ### 🛠️ Scout Autonomous Agent Tools (14 Available)
317
+ - **read_file**: Read content & line ranges of workspace files.
318
+ - **write_file**: Create new files or overwrite code directly.
319
+ - **edit_file**: Search & replace exact target code snippets.
320
+ - **multi_edit_file**: Apply batch snippet replacements in a single step.
321
+ - **run_command**: Execute live terminal commands (\`npm test\`, \`build\`).
322
+ - **tree_view**: Generate visual ASCII directory tree structures.
323
+ - **file_info**: Get file statistics, size, lines, & modification time.
324
+ - **grep_search**: String/regex pattern search across codebase.
325
+ - **glob_search**: Filter workspace files by file pattern.
326
+ - **fetch_url**: Fetch web API documentation or URL content.
327
+ - **git_diff**: Retrieve uncommitted workspace git diff.
328
+ - **ask_user**: Request missing secret credentials or API keys.
329
+ - **task_completed**: Conclude autonomous goal execution with teardown.
313
330
 
314
- ### Slash Commands
315
- - \`/help\`: View this assistance screen.
316
- - \`/diff\`: Display colorized git diff of uncommitted workspace changes.
317
- - \`/clear\` or \`/compact\`: Clear session context window.
318
- - \`/undo\`: Revert last changes applied by Scout Agent.
319
- - \`/scan\`: Perform read-only security & bug scan.
320
- - \`/plan <idea>\`: Generate feature feasibility roadmap.
321
- - \`/history\`: View recent Scout actions.
322
- - \`/exit\`: Quit Scout Agent session.
323
- `.trim();
324
- safeNote(renderMarkdown(helpText), '⚡ Scout Slash Commands & Tools');
331
+ ### 🚀 Interactive Slash Commands
332
+ - \`/help\`: Show this tools & commands guide.
333
+ - \`/tree [dir]\`: Display workspace directory tree structure.
334
+ - \`/status\` or \`/stats\`: Show session telemetry & statistics.
335
+ - \`/test [cmd]\`: Run project verification test suite.
336
+ - \`/explain <file>\`: Get deep architectural breakdown of a file.
337
+ - \`/export\` or \`/save-session\`: Save session report to \`.ft/session-<timestamp>.md\`.
338
+ - \`/diff\`: Display colorized git diff of uncommitted changes.
339
+ - \`/clear\` or \`/compact\`: Reset agent conversation context.
340
+ - \`/undo\`: Revert last file modifications.
341
+ - \`/history\`: View recent Scout action logs.
342
+ - \`/exit\`: End Scout Agent session.
343
+ `.trim();
344
+ safeNote(renderMarkdown(helpText), ' Scout Slash Commands & Tools');
345
+ continue;
346
+ }
347
+ if (trimmedInput.toLowerCase().startsWith('/tree')) {
348
+ const parts = trimmedInput.split(/\s+/);
349
+ const targetDir = parts[1] || '.';
350
+ const fullDir = path.resolve(process.cwd(), targetDir);
351
+ if (!fs.existsSync(fullDir) || !fs.statSync(fullDir).isDirectory()) {
352
+ safeNote(chalk.yellow(`Directory not found: "${targetDir}"`), '️ Directory Tree Error');
353
+ }
354
+ else {
355
+ const treeStr = buildDirectoryTree(fullDir, '', 0, 3);
356
+ safeNote(`Directory Tree for "${targetDir}":\n${targetDir}/\n${treeStr}`, ' Workspace Directory Tree');
357
+ }
358
+ continue;
359
+ }
360
+ if (['/status', '/stats'].includes(trimmedInput.toLowerCase())) {
361
+ const stats = agentLoop.getStats();
362
+ const modifiedFiles = agentLoop.getModifiedFiles();
363
+ const backups = agentLoop.getBackups();
364
+ const git = simpleGit();
365
+ let currentBranch = 'main';
366
+ try {
367
+ const branchSummary = await git.branch();
368
+ currentBranch = branchSummary.current || 'main';
369
+ }
370
+ catch { }
371
+ const elapsedSec = Math.round(stats.elapsedMs / 1000);
372
+ const activeModel = getActiveModelInfo();
373
+ const fallbackStatus = activeModel.lastFallbackInfo
374
+ ? `Switched from \`${activeModel.lastFallbackInfo.previousModel}\` ➔ \`${activeModel.lastFallbackInfo.fallbackModel}\` (${activeModel.lastFallbackInfo.reason})`
375
+ : 'None (Primary model actively responding)';
376
+ const statsMd = `
377
+ ### 📊 Scout Session & AI Model Telemetry
378
+ - **Active AI Model**: \`${activeModel.currentActiveModel}\`
379
+ - **Primary AI Model**: \`${activeModel.primaryModel}\`
380
+ - **Fallback Chain**: \`${activeModel.fallbackChain.join(' ➔ ')}\`
381
+ - **Last Fallback Event**: ${fallbackStatus}
382
+ - **Active Branch**: \`${currentBranch}\`
383
+ - **Elapsed Session Time**: \`${elapsedSec}s\`
384
+ - **Steps Executed**: \`${stats.totalSteps}\`
385
+ - **Total Tool Calls**: \`${stats.totalToolCalls}\`
386
+ - **Modified Files**: \`${modifiedFiles.length}\` (${modifiedFiles.map(f => `\`${f}\``).join(', ') || 'None'})
387
+ - **Stored File Backups**: \`${backups.length}\`
388
+ `.trim();
389
+ safeNote(renderMarkdown(statsMd), ' Scout Session & Model Telemetry');
390
+ continue;
391
+ }
392
+ if (trimmedInput.toLowerCase().startsWith('/test')) {
393
+ const parts = trimmedInput.split(/\s+/);
394
+ const customCmd = parts.slice(1).join(' ') || options?.verifyCmd;
395
+ const testSpin = spinner();
396
+ testSpin.start(`Running project verification test suite ${customCmd ? `(\`${customCmd}\`)` : ''}...`);
397
+ const testRes = await runProjectVerification(process.cwd(), customCmd);
398
+ if (testRes.valid) {
399
+ testSpin.stop(chalk.green('Project verification tests passed cleanly!'));
400
+ safeNote(`${chalk.green(' Verification Passed:')}\n\`\`\`\n${testRes.output.slice(0, 3000)}\n\`\`\``, ' Test Suite Clean');
401
+ }
402
+ else {
403
+ testSpin.stop(chalk.red('Project verification test suite failed.'));
404
+ safeNote(`${chalk.red('️ Verification Failed:')}\n${testRes.errors.join('\n')}\n\`\`\`\n${testRes.output.slice(0, 3000)}\n\`\`\``, ' Test Failure Warning');
405
+ }
406
+ continue;
407
+ }
408
+ if (trimmedInput.toLowerCase().startsWith('/explain')) {
409
+ const parts = trimmedInput.split(/\s+/);
410
+ const targetPath = parts[1];
411
+ if (!targetPath) {
412
+ safeNote(chalk.yellow('Usage: /explain <filePath> (e.g. /explain bin/src/index.ts)'), ' Explain Command');
413
+ continue;
414
+ }
415
+ const fullP = path.resolve(process.cwd(), targetPath);
416
+ if (!fs.existsSync(fullP)) {
417
+ safeNote(chalk.yellow(`File not found: ${targetPath}`), ' Explain Error');
418
+ continue;
419
+ }
420
+ const explainSpin = spinner();
421
+ explainSpin.start(`Analyzing architecture and structure of ${targetPath}...`);
422
+ const content = fs.readFileSync(fullP, 'utf-8');
423
+ const explanation = await askRepoQuestion(`Provide a deep architectural breakdown, primary purpose, component breakdown, and code explanation for file "${targetPath}":\n\`\`\`\n${content.slice(0, 6000)}\n\`\`\``, projectName, filesList, '', [], { language: targetLang });
424
+ explainSpin.stop('Analysis complete.');
425
+ safeNote(renderMarkdown(explanation), ` Architectural Breakdown: ${targetPath}`);
426
+ continue;
427
+ }
428
+ if (['/export', '/save-session'].includes(trimmedInput.toLowerCase())) {
429
+ const ftDir = path.join(process.cwd(), '.ft');
430
+ if (!fs.existsSync(ftDir))
431
+ fs.mkdirSync(ftDir, { recursive: true });
432
+ const timeStamp = new Date().toISOString().replace(/[:.]/g, '-');
433
+ const sessionPath = path.join(ftDir, `session-${timeStamp}.md`);
434
+ const stats = agentLoop.getStats();
435
+ const modifiedFiles = agentLoop.getModifiedFiles();
436
+ const history = (await import('../utils/config.js')).loadHistory();
437
+ const exportContent = `# Scout Agent Session Report
438
+ Date: ${new Date().toLocaleString()}
439
+ Project: ${projectName}
440
+
441
+ ## Session Statistics
442
+ - Total Steps: ${stats.totalSteps}
443
+ - Tool Calls: ${stats.totalToolCalls}
444
+ - Modified Files Count: ${modifiedFiles.length}
445
+
446
+ ## Modified Files
447
+ ${modifiedFiles.map(f => `- ${f}`).join('\n') || 'None'}
448
+
449
+ ## Action History
450
+ ${history.slice(0, 15).map(h => `- [${h.timestamp}] ${h.command}: ${h.description}`).join('\n')}
451
+ `;
452
+ fs.writeFileSync(sessionPath, exportContent, 'utf-8');
453
+ safeNote(chalk.green(`Session report saved to: ${chalk.bold.cyan(path.relative(process.cwd(), sessionPath))}`), ' Session Exported');
325
454
  continue;
326
455
  }
327
456
  if (trimmedInput.toLowerCase() === '/diff') {
@@ -329,22 +458,22 @@ ${chalk.dim('Type your goal, or use slash commands:')}
329
458
  const { execSync } = await import('child_process');
330
459
  const gitDiff = execSync('git diff', { encoding: 'utf-8', cwd: process.cwd() });
331
460
  if (!gitDiff || !gitDiff.trim()) {
332
- safeNote(chalk.green('No uncommitted changes found in the workspace.'), '🔍 Workspace Git Diff');
461
+ safeNote(chalk.green('No uncommitted changes found in the workspace.'), ' Workspace Git Diff');
333
462
  }
334
463
  else {
335
464
  const previewLines = gitDiff.split('\n');
336
465
  const truncated = previewLines.length > 200 ? previewLines.slice(0, 200).join('\n') + `\n... [truncated, ${previewLines.length} total lines]` : gitDiff;
337
- safeNote(truncated, '📜 Workspace Git Diff');
466
+ safeNote(truncated, ' Workspace Git Diff');
338
467
  }
339
468
  }
340
469
  catch {
341
- safeNote(chalk.yellow('Could not retrieve git diff. Ensure this directory is a git repository.'), '⚠️ Git Diff Failed');
470
+ safeNote(chalk.yellow('Could not retrieve git diff. Ensure this directory is a git repository.'), ' Git Diff Failed');
342
471
  }
343
472
  continue;
344
473
  }
345
474
  if (['/clear', '/compact'].includes(trimmedInput.toLowerCase())) {
346
475
  agentLoop.resetContext();
347
- safeNote(chalk.green('Agent context reset successfully.'), '🧹 Session Context Cleared');
476
+ safeNote(chalk.green('Agent context reset successfully.'), ' Session Context Cleared');
348
477
  continue;
349
478
  }
350
479
  if (trimmedInput.toLowerCase() === '/undo') {
@@ -354,7 +483,7 @@ ${chalk.dim('Type your goal, or use slash commands:')}
354
483
  if (trimmedInput.toLowerCase() === '/history') {
355
484
  const history = (await import('../utils/config.js')).loadHistory();
356
485
  const historyMd = history.slice(0, 10).map((h) => `- **${h.timestamp}**: \`${h.command}\` — ${h.description}`).join('\n') || 'No action history recorded yet.';
357
- safeNote(renderMarkdown(historyMd), '📜 Scout History Log');
486
+ safeNote(renderMarkdown(historyMd), ' Scout History Log');
358
487
  continue;
359
488
  }
360
489
  rawGoal = trimmedInput;
@@ -371,7 +500,7 @@ ${chalk.dim('Type your goal, or use slash commands:')}
371
500
  const isScanIntent = Boolean(options?.scan || (rawGoal && rawGoal.trim().startsWith('/scan')));
372
501
  if (isScanIntent) {
373
502
  const s = spinner();
374
- s.start('🤖 Scout Agent: Performing read-only bug & vulnerability scan...');
503
+ s.start(' Scout Agent: Performing read-only bug & vulnerability scan...');
375
504
  let codeSample = '';
376
505
  const pkgPath = path.join(cwd, 'package.json');
377
506
  if (fs.existsSync(pkgPath)) {
@@ -391,13 +520,13 @@ ${chalk.dim('Type your goal, or use slash commands:')}
391
520
  }
392
521
  };
393
522
  const reportLines = issues.map((iss) => `${chalk.bold.cyan(`[Issue #${iss.id}]`)} ${severityBadge(iss.severity)} ${chalk.bold(iss.title)}\n` +
394
- ` • File: ${chalk.yellow(iss.file)}\n` +
395
- ` • Detail: ${iss.description}`);
523
+ ` • File: ${chalk.yellow(iss.file)}\n` +
524
+ ` • Detail: ${iss.description}`);
396
525
  safeNote(`Found ${issues.length} potential issues matching past hotfix patterns:\n\n` +
397
526
  reportLines.join('\n\n') +
398
- `\n\n${chalk.bold.green('💡 How to Auto-Fix Any Issue with Agent:')}\n` +
399
- ` • Auto-fix Issue #${issues[0]?.id || 1}: ${chalk.bold.cyan(`scout agent --issue ${issues[0]?.id || 1}`)}\n` +
400
- ` • Custom prompt auto-fix: ${chalk.bold.cyan('scout agent "Fix null check in src/index.ts"')}`, '🔍 Scout Agent Bug Scan');
527
+ `\n\n${chalk.bold.green(' How to Auto-Fix Any Issue with Agent:')}\n` +
528
+ ` • Auto-fix Issue #${issues[0]?.id || 1}: ${chalk.bold.cyan(`scout agent --issue ${issues[0]?.id || 1}`)}\n` +
529
+ ` • Custom prompt auto-fix: ${chalk.bold.cyan('scout agent "Fix null check in src/index.ts"')}`, ' Scout Agent Bug Scan');
401
530
  continue;
402
531
  }
403
532
  // -------------------------------------------------------------
@@ -407,10 +536,10 @@ ${chalk.dim('Type your goal, or use slash commands:')}
407
536
  const addIdea = options?.add || options?.plan || (planMatch ? planMatch[1] : undefined);
408
537
  if (addIdea && !options?.create) {
409
538
  const s = spinner();
410
- s.start(`🤖 Scout Agent: Evaluating feasibility & generating roadmap for "${chalk.cyan(addIdea)}"...`);
539
+ s.start(` Scout Agent: Evaluating feasibility & generating roadmap for "${chalk.cyan(addIdea)}"...`);
411
540
  const plan = await generateFeaturePlan(addIdea, projectName, filesList, targetLang, options?.native);
412
541
  s.stop('Feasibility assessment & roadmap complete.');
413
- safeNote(renderMarkdown(plan), `🚀 Feature Implementation Roadmap: "${addIdea}"`);
542
+ safeNote(renderMarkdown(plan), ` Feature Implementation Roadmap: "${addIdea}"`);
414
543
  continue;
415
544
  }
416
545
  // -------------------------------------------------------------
@@ -420,20 +549,20 @@ ${chalk.dim('Type your goal, or use slash commands:')}
420
549
  const runCmdInput = options?.run || (runMatch ? runMatch[1] : undefined);
421
550
  if (runCmdInput) {
422
551
  const fullCommand = Array.isArray(runCmdInput) ? runCmdInput.join(' ') : runCmdInput;
423
- safeNote(`Running command: ${chalk.bold.cyan(fullCommand)}\n${chalk.dim('Working directory: ' + cwd)}`, ' Scout Shell Execution');
552
+ safeNote(`Running command: ${chalk.bold.cyan(fullCommand)}\n${chalk.dim('Working directory: ' + cwd)}`, ' Scout Shell Execution');
424
553
  const runSpinner = spinner();
425
554
  runSpinner.start(`Executing \`${fullCommand}\`...`);
426
555
  const res = await executeSmartCommand(fullCommand, cwd, 3000);
427
556
  if (res.isBackgroundServer) {
428
557
  runSpinner.stop(chalk.green(`Command \`${fullCommand}\` started in background (PID ${res.pid}).`));
429
- safeNote(`${chalk.green('')} Background server process started (PID ${res.pid}). Output so far:\n\`\`\`\n${res.output}\n\`\`\``, ' Background Execution Active');
558
+ safeNote(`${chalk.green('')} Background server process started (PID ${res.pid}). Output so far:\n\`\`\`\n${res.output}\n\`\`\``, ' Background Execution Active');
430
559
  continue;
431
560
  }
432
561
  const exitCode = res.exitCode;
433
562
  const fullOutput = res.output;
434
563
  if (exitCode === 0) {
435
564
  runSpinner.stop(chalk.green(`Command \`${fullCommand}\` executed successfully (Exit Code 0).`));
436
- safeNote(`${chalk.green('')} Process completed with exit code 0.`, ' Execution Successful');
565
+ safeNote(`${chalk.green('')} Process completed with exit code 0.`, ' Execution Successful');
437
566
  continue;
438
567
  }
439
568
  runSpinner.stop(chalk.red(`Command \`${fullCommand}\` failed with exit code ${exitCode}.`));
@@ -442,17 +571,17 @@ ${chalk.dim('Type your goal, or use slash commands:')}
442
571
  const analysis = await analyzeCommandError(fullCommand, exitCode, fullOutput, projectName, filesList, targetLang, options?.native);
443
572
  diagSpinner.stop(chalk.yellow('Failure diagnosis complete.'));
444
573
  const diagMarkdown = `
445
- ### 🔴 Process Failure Diagnosis
574
+ ### Process Failure Diagnosis
446
575
  Command: \`${fullCommand}\` (Exit Code ${exitCode})
447
576
 
448
- ### 🔍 Root Cause Analysis
577
+ ### Root Cause Analysis
449
578
  ${analysis.rootCause}
450
579
 
451
- ### 💡 Suggested Fix
580
+ ### Suggested Fix
452
581
  ${analysis.suggestedFix}
453
- ${analysis.targetFileToFix ? `\n### 🎯 Suspected File\n\`${analysis.targetFileToFix}\`` : ''}
454
- `.trim();
455
- safeNote(renderMarkdown(diagMarkdown), '⚠️ Scout AI Failure Diagnosis');
582
+ ${analysis.targetFileToFix ? `\n### Suspected File\n\`${analysis.targetFileToFix}\`` : ''}
583
+ `.trim();
584
+ safeNote(renderMarkdown(diagMarkdown), ' Scout AI Failure Diagnosis');
456
585
  const shouldFix = await confirm({
457
586
  message: chalk.bold('Would you like Scout Agent to auto-fix the failing code automatically?'),
458
587
  initialValue: true,
@@ -480,7 +609,7 @@ ${analysis.targetFileToFix ? `\n### 🎯 Suspected File\n\`${analysis.targetFile
480
609
  validate: (val) => (!val || !val.trim() ? 'Please provide a prompt describing the file requirements' : undefined),
481
610
  });
482
611
  if (isCancel(userPromptInput)) {
483
- safeNote(chalk.yellow('File creation operation cancelled.'), ' Scout File Creation');
612
+ safeNote(chalk.yellow('File creation operation cancelled.'), ' Scout File Creation');
484
613
  continue;
485
614
  }
486
615
  finalPrompt = userPromptInput.trim();
@@ -499,7 +628,7 @@ ${analysis.targetFileToFix ? `\n### 🎯 Suspected File\n\`${analysis.targetFile
499
628
  catch { }
500
629
  }
501
630
  const s = spinner();
502
- s.start(`🤖 Scout Agent: Generating ${targetCreateFiles.length} file(s) (${targetCreateFiles.join(', ')})...`);
631
+ s.start(` Scout Agent: Generating ${targetCreateFiles.length} file(s) (${targetCreateFiles.join(', ')})...`);
503
632
  let hasError = false;
504
633
  for (const targetFilePath of targetCreateFiles) {
505
634
  const resolvedPath = path.resolve(cwd, targetFilePath);
@@ -523,14 +652,14 @@ ${analysis.targetFileToFix ? `\n### 🎯 Suspected File\n\`${analysis.targetFile
523
652
  catch (err) {
524
653
  hasError = true;
525
654
  s.stop(chalk.red(`Failed to generate code for ${targetFilePath}.`));
526
- safeNote(chalk.red(`Error: ${err?.message || String(err)}`), '⚠️ Code Generation Error');
655
+ safeNote(chalk.red(`Error: ${err?.message || String(err)}`), ' Code Generation Error');
527
656
  break;
528
657
  }
529
658
  }
530
659
  if (hasError || createdFilesSummary.length === 0) {
531
660
  if (!hasError) {
532
661
  s.stop(chalk.red('Failed to generate code files.'));
533
- safeNote(chalk.yellow(`Scout AI returned empty code content. Please refine your prompt describing what code to write.`), '⚠️ Empty Code Generated');
662
+ safeNote(chalk.yellow(`Scout AI returned empty code content. Please refine your prompt describing what code to write.`), ' Empty Code Generated');
534
663
  }
535
664
  continue;
536
665
  }
@@ -550,31 +679,31 @@ ${analysis.targetFileToFix ? `\n### 🎯 Suspected File\n\`${analysis.targetFile
550
679
  native: options?.native,
551
680
  });
552
681
  if (healRes.verifiedFiles.length > 0) {
553
- safeNote(chalk.green(`✓ Automated Verification Confirmed: ${healRes.verifiedFiles.length} file(s) syntax & build verified clean!`), ' Code Verification Clean');
682
+ safeNote(chalk.green(` Automated Verification Confirmed: ${healRes.verifiedFiles.length} file(s) syntax & build verified clean!`), ' Code Verification Clean');
554
683
  }
555
684
  if (healRes.remainingErrors.length > 0) {
556
- safeNote(chalk.yellow(`⚠️ Remaining verification issues:\n${healRes.remainingErrors.join('\n')}`), '⚠️ Verification Warning');
685
+ safeNote(chalk.yellow(`️ Remaining verification issues:\n${healRes.remainingErrors.join('\n')}`), ' Verification Warning');
557
686
  }
558
- const filePreviews = createdFilesSummary.map((f) => `#### 📄 \`${f.path}\`\n${f.description}\n\`\`\`\n${f.code.slice(0, 400)}${f.code.length > 400 ? '\n... (truncated)' : ''}\n\`\`\``).join('\n\n');
687
+ const filePreviews = createdFilesSummary.map((f) => `#### \`${f.path}\`\n${f.description}\n\`\`\`\n${f.code.slice(0, 400)}${f.code.length > 400 ? '\n... (truncated)' : ''}\n\`\`\``).join('\n\n');
559
688
  const teardownContent = `
560
- ### 📄 Created Files (${createdFilesSummary.length})
689
+ ### Created Files (${createdFilesSummary.length})
561
690
  ${createdFilesSummary.map(f => ` • \`${f.path}\``).join('\n')}
562
691
 
563
- ### 💡 Overview & Previews
692
+ ### Overview & Previews
564
693
  ${filePreviews}
565
- `.trim();
566
- safeNote(renderMarkdown(teardownContent), ' Scout File Creation Teardown');
694
+ `.trim();
695
+ safeNote(renderMarkdown(teardownContent), ' Scout File Creation Teardown');
567
696
  if (options?.yes || !process.stdout.isTTY) {
568
- safeNote(`${chalk.green('')} Saved ${chalk.bold.cyan(affectedFiles.length + ' file(s)')} on branch ${chalk.bold.green(currentBranch)}.\n` +
569
- `💡 ${chalk.dim('You can revert this creation anytime by running:')} ${chalk.cyan('scout undo')}`, ' Creation Confirmed');
697
+ safeNote(`${chalk.green('')} Saved ${chalk.bold.cyan(affectedFiles.length + ' file(s)')} on branch ${chalk.bold.green(currentBranch)}.\n` +
698
+ ` ${chalk.dim('You can revert this creation anytime by running:')} ${chalk.cyan('scout undo')}`, ' Creation Confirmed');
570
699
  continue;
571
700
  }
572
701
  const actionChoice = await select({
573
702
  message: chalk.bold('Review Scout Agent\'s generated file(s). What would you like to do?'),
574
703
  options: [
575
- { value: 'keep', label: ' Keep Files', hint: `Retain newly created files directly on current branch (${currentBranch})` },
576
- { value: 'pr', label: '🔀 Create Pull Request', hint: 'Push created files to a new branch & open a Pull Request' },
577
- { value: 'revert', label: '↩️ Revert / Delete Files', hint: 'Remove created files immediately' },
704
+ { value: 'keep', label: ' Keep Files', hint: `Retain newly created files directly on current branch (${currentBranch})` },
705
+ { value: 'pr', label: ' Create Pull Request', hint: 'Push created files to a new branch & open a Pull Request' },
706
+ { value: 'revert', label: ' Revert / Delete Files', hint: 'Remove created files immediately' },
578
707
  ],
579
708
  });
580
709
  if (isCancel(actionChoice) || actionChoice === 'revert') {
@@ -594,12 +723,12 @@ ${filePreviews}
594
723
  commitMessage: `feat: Create ${affectedFiles.join(', ')}`,
595
724
  });
596
725
  prSpinner.stop('Pull Request workflow processed.');
597
- safeNote(`${chalk.green('')} ${prRes.message}\n` +
598
- `💡 ${chalk.dim('You remain on active branch:')} ${chalk.cyan(currentBranch)}`, '🔀 Pull Request Status');
726
+ safeNote(`${chalk.green('')} ${prRes.message}\n` +
727
+ ` ${chalk.dim('You remain on active branch:')} ${chalk.cyan(currentBranch)}`, ' Pull Request Status');
599
728
  continue;
600
729
  }
601
- safeNote(`${chalk.green('')} Saved ${chalk.bold.cyan(affectedFiles.length + ' file(s)')} on branch ${chalk.bold.green(currentBranch)}.\n` +
602
- `💡 ${chalk.dim('You can revert this creation anytime by running:')} ${chalk.cyan('scout undo')}`, ' Creation Confirmed');
730
+ safeNote(`${chalk.green('')} Saved ${chalk.bold.cyan(affectedFiles.length + ' file(s)')} on branch ${chalk.bold.green(currentBranch)}.\n` +
731
+ ` ${chalk.dim('You can revert this creation anytime by running:')} ${chalk.cyan('scout undo')}`, ' Creation Confirmed');
603
732
  continue;
604
733
  }
605
734
  // -------------------------------------------------------------
@@ -615,11 +744,11 @@ ${filePreviews}
615
744
  }
616
745
  }
617
746
  if (!finalGoal || !finalGoal.trim()) {
618
- safeNote(chalk.yellow('No active goal specified.'), '🤖 Scout Agent');
747
+ safeNote(chalk.yellow('No active goal specified.'), ' Scout Agent');
619
748
  continue;
620
749
  }
621
- safeNote(`${chalk.bold.cyan('🤖 Scout Autonomous Execution Initialized')}\n` +
622
- `${chalk.dim('Goal:')} ${finalGoal}`, ' Scout Agent Execution');
750
+ safeNote(`${chalk.bold.cyan(' Scout Autonomous Execution Initialized')}\n` +
751
+ `${chalk.dim('Goal:')} ${finalGoal}`, ' Scout Agent Execution');
623
752
  const execResult = await agentLoop.executeGoal(finalGoal);
624
753
  const modifiedFiles = agentLoop.getModifiedFiles();
625
754
  const backups = agentLoop.getBackups();
@@ -643,29 +772,31 @@ ${filePreviews}
643
772
  });
644
773
  }
645
774
  const teardownMd = `
646
- ### 🎯 Agent Goal
775
+ ### Agent Goal
647
776
  ${finalGoal}
648
777
 
649
- ### 📄 Modified File(s)
778
+ ### Modified File(s)
650
779
  ${modifiedFiles.map((f) => ` • \`${f}\``).join('\n') || ' • None'}
651
780
 
652
- ### 💡 Execution Summary
781
+ ### Execution Summary
653
782
  ${execResult.summary}
654
- `.trim();
655
- safeNote(renderMarkdown(teardownMd), '🤖 Scout Agent Teardown Report');
783
+ `.trim();
784
+ safeNote(renderMarkdown(teardownMd), ' Scout Agent Teardown Report');
656
785
  if (modifiedFiles.length === 0)
657
786
  continue;
658
787
  if (options?.yes || !process.stdout.isTTY) {
659
- safeNote(`${chalk.green('')} Agent changes retained on ${modifiedFiles.length} file(s) (Branch: ${chalk.bold.green(currentBranch)}).\n` +
660
- `💡 ${chalk.dim('You can revert anytime by running:')} ${chalk.cyan('scout undo')}`, ' Agent Execution Confirmed');
788
+ safeNote(`${chalk.green('')} Agent changes retained on ${modifiedFiles.length} file(s) (Branch: ${chalk.bold.green(currentBranch)}).\n` +
789
+ ` ${chalk.dim('You can revert anytime by running:')} ${chalk.cyan('scout undo')}`, ' Agent Execution Confirmed');
661
790
  continue;
662
791
  }
663
792
  const actionChoice = await select({
664
793
  message: chalk.bold('Review Scout Agent result. What would you like to do?'),
665
794
  options: [
666
- { value: 'keep', label: ' Keep Changes', hint: `Retain agent changes on current branch (${currentBranch})` },
667
- { value: 'pr', label: '🔀 Create Pull Request', hint: 'Push fix to a new branch & open a Pull Request' },
668
- { value: 'revert', label: '↩️ Revert Changes', hint: 'Restore original code immediately' },
795
+ { value: 'keep', label: ' Keep Changes', hint: `Retain agent changes on current branch (${currentBranch})` },
796
+ { value: 'test', label: ' Run Verification Tests', hint: 'Execute test suite to confirm zero regressions' },
797
+ { value: 'pr', label: ' Create Pull Request', hint: 'Push fix to a new branch & open a Pull Request' },
798
+ { value: 'export', label: ' Export Session Report', hint: 'Save detailed session teardown report to .ft/' },
799
+ { value: 'revert', label: '️ Revert Changes', hint: 'Restore original code immediately' },
669
800
  ],
670
801
  });
671
802
  if (actionChoice === 'revert') {
@@ -675,6 +806,47 @@ ${execResult.summary}
675
806
  revSpinner.stop('Changes reverted successfully.');
676
807
  continue;
677
808
  }
809
+ if (actionChoice === 'test') {
810
+ const testSpin = spinner();
811
+ testSpin.start('Running project verification test suite...');
812
+ const testRes = await runProjectVerification(process.cwd(), options?.verifyCmd);
813
+ if (testRes.valid) {
814
+ testSpin.stop(chalk.green('Project verification tests passed cleanly!'));
815
+ safeNote(`${chalk.green(' Verification Passed:')}\n\`\`\`\n${testRes.output.slice(0, 3000)}\n\`\`\``, ' Test Suite Clean');
816
+ }
817
+ else {
818
+ testSpin.stop(chalk.red('Project verification test suite failed.'));
819
+ safeNote(`${chalk.red('️ Verification Failed:')}\n${testRes.errors.join('\n')}\n\`\`\`\n${testRes.output.slice(0, 3000)}\n\`\`\``, ' Test Failure Warning');
820
+ }
821
+ continue;
822
+ }
823
+ if (actionChoice === 'export') {
824
+ const ftDir = path.join(process.cwd(), '.ft');
825
+ if (!fs.existsSync(ftDir))
826
+ fs.mkdirSync(ftDir, { recursive: true });
827
+ const timeStamp = new Date().toISOString().replace(/[:.]/g, '-');
828
+ const sessionPath = path.join(ftDir, `session-${timeStamp}.md`);
829
+ const stats = agentLoop.getStats();
830
+ const exportContent = `# Scout Agent Session Report
831
+ Date: ${new Date().toLocaleString()}
832
+ Project: ${projectName}
833
+ Goal: ${finalGoal}
834
+
835
+ ## Session Statistics
836
+ - Total Steps: ${stats.totalSteps}
837
+ - Tool Calls: ${stats.totalToolCalls}
838
+ - Modified Files Count: ${modifiedFiles.length}
839
+
840
+ ## Modified Files
841
+ ${modifiedFiles.map(f => `- ${f}`).join('\n') || 'None'}
842
+
843
+ ## Execution Summary
844
+ ${execResult.summary}
845
+ `;
846
+ fs.writeFileSync(sessionPath, exportContent, 'utf-8');
847
+ safeNote(chalk.green(`Session report saved to: ${chalk.bold.cyan(path.relative(process.cwd(), sessionPath))}`), ' Session Exported');
848
+ continue;
849
+ }
678
850
  if (actionChoice === 'pr') {
679
851
  const prSpinner = spinner();
680
852
  prSpinner.start('Creating Pull Request...');
@@ -685,12 +857,12 @@ ${execResult.summary}
685
857
  commitMessage: `feat: ${finalGoal}`,
686
858
  });
687
859
  prSpinner.stop('Pull Request workflow processed.');
688
- safeNote(`${chalk.green('')} ${prRes.message}\n` +
689
- `💡 ${chalk.dim('You remain on active branch:')} ${chalk.cyan(currentBranch)}`, '🔀 Pull Request Status');
860
+ safeNote(`${chalk.green('')} ${prRes.message}\n` +
861
+ ` ${chalk.dim('You remain on active branch:')} ${chalk.cyan(currentBranch)}`, ' Pull Request Status');
690
862
  continue;
691
863
  }
692
- safeNote(`${chalk.green('')} Agent changes retained on ${modifiedFiles.length} file(s) (Branch: ${chalk.bold.green(currentBranch)}).\n` +
693
- `💡 ${chalk.dim('You can revert these changes anytime by running:')} ${chalk.cyan('scout undo')}`, ' Agent Workflow Finished');
864
+ safeNote(`${chalk.green('')} Agent changes retained on ${modifiedFiles.length} file(s) (Branch: ${chalk.bold.green(currentBranch)}).\n` +
865
+ ` ${chalk.dim('You can revert these changes anytime by running:')} ${chalk.cyan('scout undo')}`, ' Agent Workflow Finished');
694
866
  }
695
867
  }
696
868
  //# sourceMappingURL=agent.js.map