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.
- package/.firebase/hosting.d2Vi.cache +2 -2
- package/README.md +68 -44
- package/bin/ft.js +1 -1
- package/bin/src/commands/agent.d.ts.map +1 -1
- package/bin/src/commands/agent.js +264 -92
- package/bin/src/commands/agent.js.map +1 -1
- package/bin/src/commands/audit.js +1 -1
- package/bin/src/commands/audit.js.map +1 -1
- package/bin/src/commands/authCommand.js +4 -4
- package/bin/src/commands/authCommand.js.map +1 -1
- package/bin/src/commands/brief.js +4 -4
- package/bin/src/commands/brief.js.map +1 -1
- package/bin/src/commands/configCommand.d.ts.map +1 -1
- package/bin/src/commands/configCommand.js +7 -7
- package/bin/src/commands/configCommand.js.map +1 -1
- package/bin/src/commands/dashboard.js +4 -4
- package/bin/src/commands/dashboard.js.map +1 -1
- package/bin/src/commands/history.js +5 -5
- package/bin/src/commands/history.js.map +1 -1
- package/bin/src/commands/init.js +26 -26
- package/bin/src/commands/init.js.map +1 -1
- package/bin/src/commands/login.d.ts.map +1 -1
- package/bin/src/commands/login.js +21 -21
- package/bin/src/commands/login.js.map +1 -1
- package/bin/src/commands/logout.js +1 -1
- package/bin/src/commands/logout.js.map +1 -1
- package/bin/src/commands/owners.js +5 -5
- package/bin/src/commands/owners.js.map +1 -1
- package/bin/src/commands/quota.d.ts.map +1 -0
- package/bin/src/commands/quota.js +48 -0
- package/bin/src/commands/quota.js.map +1 -0
- package/bin/src/commands/recommend.js +1 -1
- package/bin/src/commands/recommend.js.map +1 -1
- package/bin/src/commands/risky.d.ts.map +1 -1
- package/bin/src/commands/risky.js +9 -9
- package/bin/src/commands/risky.js.map +1 -1
- package/bin/src/commands/setup.js +24 -24
- package/bin/src/commands/setup.js.map +1 -1
- package/bin/src/commands/signup.d.ts.map +1 -1
- package/bin/src/commands/signup.js +7 -7
- package/bin/src/commands/signup.js.map +1 -1
- package/bin/src/commands/undo.js +8 -8
- package/bin/src/commands/undo.js.map +1 -1
- package/bin/src/commands/web.js +8 -8
- package/bin/src/commands/web.js.map +1 -1
- package/bin/src/discovery/config.d.ts.map +1 -1
- package/bin/src/discovery/config.js.map +1 -1
- package/bin/src/discovery/display.js +1 -1
- package/bin/src/discovery/display.js.map +1 -1
- package/bin/src/discovery/messages.js.map +1 -1
- package/bin/src/discovery/signals.d.ts.map +1 -1
- package/bin/src/discovery/signals.js.map +1 -1
- package/bin/src/engine/agentEngine.d.ts.map +1 -1
- package/bin/src/engine/agentEngine.js +414 -77
- package/bin/src/engine/agentEngine.js.map +1 -1
- package/bin/src/engine/git.d.ts.map +1 -1
- package/bin/src/engine/git.js.map +1 -1
- package/bin/src/engine/llm.d.ts.map +1 -1
- package/bin/src/engine/llm.js +174 -78
- package/bin/src/engine/llm.js.map +1 -1
- package/bin/src/engine/verifier.d.ts.map +1 -1
- package/bin/src/engine/verifier.js +6 -6
- package/bin/src/engine/verifier.js.map +1 -1
- package/bin/src/engine/web.d.ts.map +1 -1
- package/bin/src/engine/web.js +1 -1
- package/bin/src/engine/web.js.map +1 -1
- package/bin/src/index.js +78 -68
- package/bin/src/index.js.map +1 -1
- package/bin/src/server/server.js.map +1 -1
- package/bin/src/utils/auth.d.ts.map +1 -1
- package/bin/src/utils/auth.js.map +1 -1
- package/bin/src/utils/branding.js +7 -7
- package/bin/src/utils/branding.js.map +1 -1
- package/bin/src/utils/config.d.ts.map +1 -1
- package/bin/src/utils/config.js.map +1 -1
- package/bin/src/utils/email.d.ts.map +1 -1
- package/bin/src/utils/email.js +42 -42
- package/bin/src/utils/email.js.map +1 -1
- package/bin/src/utils/firebaseAuth.d.ts.map +1 -1
- package/bin/src/utils/firebaseAuth.js.map +1 -1
- package/bin/src/utils/holoseneAuth.d.ts.map +1 -1
- package/bin/src/utils/holoseneAuth.js +22 -22
- package/bin/src/utils/holoseneAuth.js.map +1 -1
- package/bin/src/utils/language.d.ts.map +1 -1
- package/bin/src/utils/language.js.map +1 -1
- package/bin/src/utils/markdown.js +4 -4
- package/bin/src/utils/markdown.js.map +1 -1
- package/bin/src/utils/updateCheck.js +5 -5
- package/bin/src/utils/updateCheck.js.map +1 -1
- package/package.json +2 -2
- 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(`
|
|
237
|
-
console.log(`
|
|
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(`
|
|
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
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
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.
|
|
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.
|
|
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
|
|
308
|
-
- **write_file**: Create new files or overwrite
|
|
309
|
-
- **edit_file**: Search
|
|
310
|
-
- **
|
|
311
|
-
- **
|
|
312
|
-
- **
|
|
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
|
-
###
|
|
315
|
-
- \`/help\`:
|
|
316
|
-
- \`/
|
|
317
|
-
- \`/
|
|
318
|
-
- \`/
|
|
319
|
-
- \`/
|
|
320
|
-
- \`/
|
|
321
|
-
- \`/
|
|
322
|
-
- \`/
|
|
323
|
-
|
|
324
|
-
|
|
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.'), '
|
|
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, '
|
|
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.'), '
|
|
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.'), '
|
|
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), '
|
|
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('
|
|
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
|
-
`
|
|
395
|
-
`
|
|
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('
|
|
399
|
-
`
|
|
400
|
-
`
|
|
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(
|
|
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),
|
|
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)}`, '
|
|
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('
|
|
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('
|
|
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
|
-
###
|
|
574
|
+
### Process Failure Diagnosis
|
|
446
575
|
Command: \`${fullCommand}\` (Exit Code ${exitCode})
|
|
447
576
|
|
|
448
|
-
###
|
|
577
|
+
### Root Cause Analysis
|
|
449
578
|
${analysis.rootCause}
|
|
450
579
|
|
|
451
|
-
###
|
|
580
|
+
### Suggested Fix
|
|
452
581
|
${analysis.suggestedFix}
|
|
453
|
-
${analysis.targetFileToFix ? `\n###
|
|
454
|
-
|
|
455
|
-
safeNote(renderMarkdown(diagMarkdown), '
|
|
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.'), '
|
|
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(
|
|
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)}`), '
|
|
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.`), '
|
|
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(
|
|
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(
|
|
685
|
+
safeNote(chalk.yellow(`️ Remaining verification issues:\n${healRes.remainingErrors.join('\n')}`), '️ Verification Warning');
|
|
557
686
|
}
|
|
558
|
-
const filePreviews = createdFilesSummary.map((f) => `####
|
|
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
|
-
###
|
|
689
|
+
### Created Files (${createdFilesSummary.length})
|
|
561
690
|
${createdFilesSummary.map(f => ` • \`${f.path}\``).join('\n')}
|
|
562
691
|
|
|
563
|
-
###
|
|
692
|
+
### Overview & Previews
|
|
564
693
|
${filePreviews}
|
|
565
|
-
|
|
566
|
-
safeNote(renderMarkdown(teardownContent), '
|
|
694
|
+
`.trim();
|
|
695
|
+
safeNote(renderMarkdown(teardownContent), ' Scout File Creation Teardown');
|
|
567
696
|
if (options?.yes || !process.stdout.isTTY) {
|
|
568
|
-
safeNote(`${chalk.green('
|
|
569
|
-
|
|
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: '
|
|
576
|
-
{ value: 'pr', label: '
|
|
577
|
-
{ value: 'revert', label: '
|
|
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('
|
|
598
|
-
|
|
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('
|
|
602
|
-
|
|
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.'), '
|
|
747
|
+
safeNote(chalk.yellow('No active goal specified.'), ' Scout Agent');
|
|
619
748
|
continue;
|
|
620
749
|
}
|
|
621
|
-
safeNote(`${chalk.bold.cyan('
|
|
622
|
-
`${chalk.dim('Goal:')} ${finalGoal}`, '
|
|
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
|
-
###
|
|
775
|
+
### Agent Goal
|
|
647
776
|
${finalGoal}
|
|
648
777
|
|
|
649
|
-
###
|
|
778
|
+
### Modified File(s)
|
|
650
779
|
${modifiedFiles.map((f) => ` • \`${f}\``).join('\n') || ' • None'}
|
|
651
780
|
|
|
652
|
-
###
|
|
781
|
+
### Execution Summary
|
|
653
782
|
${execResult.summary}
|
|
654
|
-
|
|
655
|
-
safeNote(renderMarkdown(teardownMd), '
|
|
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('
|
|
660
|
-
|
|
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: '
|
|
667
|
-
{ value: '
|
|
668
|
-
{ value: '
|
|
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('
|
|
689
|
-
|
|
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('
|
|
693
|
-
|
|
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
|