ft-scout 3.0.6 â 3.0.7
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/bin/ft.js +1 -0
- package/bin/src/commands/agent.d.ts.map +1 -1
- package/bin/src/commands/agent.js +590 -150
- package/bin/src/commands/agent.js.map +1 -1
- package/bin/src/engine/llm.d.ts.map +1 -1
- package/bin/src/engine/llm.js +11 -7
- package/bin/src/engine/llm.js.map +1 -1
- package/bin/src/index.js +19 -75
- package/bin/src/index.js.map +1 -1
- package/bin/src/utils/branding.js +1 -1
- package/package.json +1 -48
- package/bin/src/commands/add.d.ts.map +0 -1
- package/bin/src/commands/add.js +0 -45
- package/bin/src/commands/add.js.map +0 -1
- package/bin/src/commands/chat.d.ts.map +0 -1
- package/bin/src/commands/chat.js +0 -197
- package/bin/src/commands/chat.js.map +0 -1
- package/bin/src/commands/create.d.ts.map +0 -1
- package/bin/src/commands/create.js +0 -160
- package/bin/src/commands/create.js.map +0 -1
- package/bin/src/commands/fix.d.ts.map +0 -1
- package/bin/src/commands/fix.js +0 -306
- package/bin/src/commands/fix.js.map +0 -1
- package/bin/src/commands/runCommand.d.ts.map +0 -1
- package/bin/src/commands/runCommand.js +0 -106
- package/bin/src/commands/runCommand.js.map +0 -1
- package/bin/src/commands/scan.d.ts.map +0 -1
- package/bin/src/commands/scan.js +0 -82
- package/bin/src/commands/scan.js.map +0 -1
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
-
import { spinner, text, isCancel, select } from '@clack/prompts';
|
|
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 } from '../engine/llm.js';
|
|
6
|
+
import { generateMultiCodeFix, identifyTargetFiles, analyzeCommandError, generateCodeScanReport, generateFeaturePlan, generateCodeFile, askRepoQuestion, } 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';
|
|
@@ -12,136 +12,575 @@ import { handleUndo } from './undo.js';
|
|
|
12
12
|
import { exec } from 'child_process';
|
|
13
13
|
import { promisify } from 'util';
|
|
14
14
|
const execAsync = promisify(exec);
|
|
15
|
+
const FT_DIR = path.join(process.cwd(), '.ft');
|
|
16
|
+
const SCAN_CACHE = path.join(FT_DIR, 'scan-report.json');
|
|
17
|
+
export function saveScanReport(issues) {
|
|
18
|
+
if (fs.existsSync(FT_DIR)) {
|
|
19
|
+
fs.writeFileSync(SCAN_CACHE, JSON.stringify(issues, null, 2));
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export function loadScanReport() {
|
|
23
|
+
if (!fs.existsSync(SCAN_CACHE))
|
|
24
|
+
return [];
|
|
25
|
+
try {
|
|
26
|
+
return JSON.parse(fs.readFileSync(SCAN_CACHE, 'utf-8'));
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return [];
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
export function findTargetFilesFromPrompt(userPrompt, specifiedFile, filesList) {
|
|
33
|
+
const norm = (p) => p.replace(/\\/g, '/').toLowerCase().trim();
|
|
34
|
+
if (specifiedFile && specifiedFile.trim()) {
|
|
35
|
+
const raw = specifiedFile.trim();
|
|
36
|
+
const splitItems = raw.split(',').map((s) => s.trim()).filter(Boolean);
|
|
37
|
+
const matchedFromFlag = [];
|
|
38
|
+
for (const item of splitItems) {
|
|
39
|
+
const normItem = norm(item);
|
|
40
|
+
const isExplicitExtPattern = item.startsWith('*') ||
|
|
41
|
+
item.startsWith('.') ||
|
|
42
|
+
(item.length <= 10 && !item.includes('/') && !item.includes('\\') && !filesList.some((f) => norm(f) === normItem || path.basename(norm(f)) === normItem));
|
|
43
|
+
if (isExplicitExtPattern) {
|
|
44
|
+
const cleanExt = normItem.replace(/^(\*|\.)+/, '');
|
|
45
|
+
if (cleanExt) {
|
|
46
|
+
const extFiles = filesList.filter((f) => norm(f).endsWith(`.${cleanExt}`));
|
|
47
|
+
matchedFromFlag.push(...extFiles);
|
|
48
|
+
}
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
const exact = filesList.find((f) => norm(f) === normItem);
|
|
52
|
+
if (exact) {
|
|
53
|
+
matchedFromFlag.push(exact);
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
const suffixMatches = filesList.filter((f) => norm(f).endsWith('/' + normItem));
|
|
57
|
+
if (suffixMatches.length > 0) {
|
|
58
|
+
matchedFromFlag.push(...suffixMatches);
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
const baseMatches = filesList.filter((f) => path.basename(norm(f)) === path.basename(normItem));
|
|
62
|
+
if (baseMatches.length > 0) {
|
|
63
|
+
matchedFromFlag.push(...baseMatches);
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
matchedFromFlag.push(item);
|
|
67
|
+
}
|
|
68
|
+
if (matchedFromFlag.length > 0) {
|
|
69
|
+
return Array.from(new Set(matchedFromFlag));
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
const rawWords = userPrompt.split(/\s+/).map((w) => w.replace(/["'(),;:!]/g, '').trim());
|
|
73
|
+
const cleanPromptWords = rawWords.map((w) => w.toLowerCase());
|
|
74
|
+
const lowerPrompt = userPrompt.toLowerCase();
|
|
75
|
+
const matchedByName = [];
|
|
76
|
+
for (const word of rawWords) {
|
|
77
|
+
if (word && word.includes('.')) {
|
|
78
|
+
const fullP = path.resolve(process.cwd(), word);
|
|
79
|
+
if (fs.existsSync(fullP) && fs.statSync(fullP).isFile()) {
|
|
80
|
+
const relP = path.relative(process.cwd(), fullP).replace(/\\/g, '/');
|
|
81
|
+
matchedByName.push(relP);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
for (const f of filesList) {
|
|
86
|
+
const normF = norm(f);
|
|
87
|
+
const base = path.basename(normF);
|
|
88
|
+
if (lowerPrompt.includes(normF) ||
|
|
89
|
+
cleanPromptWords.includes(normF) ||
|
|
90
|
+
(base.includes('.') && (lowerPrompt.includes(base) || cleanPromptWords.includes(base)))) {
|
|
91
|
+
matchedByName.push(f);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if (matchedByName.length > 0) {
|
|
95
|
+
return Array.from(new Set(matchedByName));
|
|
96
|
+
}
|
|
97
|
+
const bulkExtMatch = lowerPrompt.match(/(?:all|\*|in|from|for|across|every)?\s*(?:\*|\.)?([a-zA-Z0-9]{1,10})\s+files?\b|\*\.([a-zA-Z0-9]{1,10})/i);
|
|
98
|
+
if (bulkExtMatch) {
|
|
99
|
+
const ext = (bulkExtMatch[1] || bulkExtMatch[2])?.toLowerCase();
|
|
100
|
+
if (ext) {
|
|
101
|
+
const extFiles = filesList.filter((f) => norm(f).endsWith(`.${ext}`));
|
|
102
|
+
if (extFiles.length > 0) {
|
|
103
|
+
return Array.from(new Set(extFiles));
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return [];
|
|
108
|
+
}
|
|
15
109
|
export async function handleAgent(goalInput, options) {
|
|
16
110
|
const existingContext = loadContext();
|
|
17
111
|
const targetLang = normalizeLanguage(options?.lang) || normalizeLanguage(existingContext?.language);
|
|
18
112
|
const cwd = process.cwd();
|
|
19
113
|
const filesList = getDirectoryFiles(cwd);
|
|
20
114
|
const projectName = existingContext?.projectName || path.basename(cwd) || 'Codebase';
|
|
21
|
-
let
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
115
|
+
let initialGoal = goalInput || options?.goal;
|
|
116
|
+
let isFirstRun = true;
|
|
117
|
+
const welcomeBanner = `
|
|
118
|
+
${chalk.bold.cyan('đ¤ Scout Autonomous AI Agent Session Active')} ${targetLang ? `(${targetLang})` : ''}
|
|
119
|
+
${chalk.dim('Type your goal, task, or question. Press Ctrl+C or type "exit" to quit.')}
|
|
120
|
+
`.trim();
|
|
121
|
+
safeNote(welcomeBanner, 'đ¤ Scout Agent Session');
|
|
122
|
+
while (true) {
|
|
123
|
+
let rawGoal = undefined;
|
|
124
|
+
if (isFirstRun && (initialGoal || options?.scan || options?.add || options?.plan || options?.run || options?.create || options?.issue)) {
|
|
125
|
+
rawGoal = initialGoal;
|
|
126
|
+
isFirstRun = false;
|
|
31
127
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
if (fs.existsSync(fullPath)) {
|
|
50
|
-
const content = fs.readFileSync(fullPath, 'utf-8');
|
|
51
|
-
targetFilesMap[normKey(tf)] = content;
|
|
52
|
-
backups.push({ filePath: normKey(tf), originalContent: content });
|
|
128
|
+
else {
|
|
129
|
+
isFirstRun = false;
|
|
130
|
+
const userGoalInput = await text({
|
|
131
|
+
message: chalk.bold('Agent Prompt:'),
|
|
132
|
+
placeholder: 'Ask question, fix bug, run command, scan issues, create file... (Ctrl+C / exit to quit)',
|
|
133
|
+
validate: (val) => (!val || !val.trim() ? 'Please enter a prompt or Ctrl+C / exit to quit' : undefined),
|
|
134
|
+
});
|
|
135
|
+
if (isCancel(userGoalInput)) {
|
|
136
|
+
safeNote(chalk.yellow('Exiting Scout Agent session. Goodbye!'), 'đ Scout Agent Ended');
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
139
|
+
const trimmedInput = userGoalInput.trim();
|
|
140
|
+
if (['exit', 'quit', 'q', ':q'].includes(trimmedInput.toLowerCase())) {
|
|
141
|
+
safeNote(chalk.yellow('Exiting Scout Agent session. Goodbye!'), 'đ Scout Agent Ended');
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
rawGoal = trimmedInput;
|
|
53
145
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
146
|
+
if (!rawGoal || !rawGoal.trim()) {
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
const goalLower = rawGoal.toLowerCase().trim();
|
|
150
|
+
// -------------------------------------------------------------
|
|
151
|
+
// Mode 0: Q&A / Read-Only Question & File Content Inspection Mode
|
|
152
|
+
// -------------------------------------------------------------
|
|
153
|
+
const isQuestionIntent = /^(?:what|how|why|where|who|explain|show|read|display|tell|describe|list|is|does|can|could|view)\b/i.test(goalLower) ||
|
|
154
|
+
/\b(?:what's|whats|content of|code of|show code|read file|display file|explain code|how does|purpose of|wanna show|wanna see)\b/i.test(goalLower) ||
|
|
155
|
+
/\?\s*$/.test(goalLower);
|
|
156
|
+
const isActionIntent = Boolean(options?.create) ||
|
|
157
|
+
Boolean(options?.file) ||
|
|
158
|
+
Boolean(options?.issue) ||
|
|
159
|
+
Boolean(options?.run) ||
|
|
160
|
+
Boolean(options?.scan) ||
|
|
161
|
+
Boolean(options?.add) ||
|
|
162
|
+
Boolean(options?.plan) ||
|
|
163
|
+
/^(?:create|generate|make|build|fix|refactor|update|change|delete|remove|modify|patch|run|exec)\b/i.test(goalLower);
|
|
164
|
+
if (isQuestionIntent && !isActionIntent) {
|
|
165
|
+
const s = spinner();
|
|
166
|
+
s.start('đ¤ Scout Agent analyzing query & inspecting codebase...');
|
|
167
|
+
const matchedFiles = findTargetFilesFromPrompt(rawGoal, undefined, filesList);
|
|
168
|
+
let readfileOutput = '';
|
|
169
|
+
if (matchedFiles.length > 0) {
|
|
170
|
+
for (const f of matchedFiles.slice(0, 3)) {
|
|
171
|
+
const fullP = path.resolve(cwd, f);
|
|
172
|
+
if (fs.existsSync(fullP) && fs.statSync(fullP).isFile()) {
|
|
173
|
+
const fileContent = fs.readFileSync(fullP, 'utf-8');
|
|
174
|
+
readfileOutput += `### đ File: \`${f}\`\n\`\`\`ts\n${fileContent.slice(0, 8000)}${fileContent.length > 8000 ? '\n... (truncated)' : ''}\n\`\`\`\n\n`;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
let manifestContent = '';
|
|
179
|
+
const pkgPath = path.join(cwd, 'package.json');
|
|
180
|
+
if (fs.existsSync(pkgPath)) {
|
|
181
|
+
try {
|
|
182
|
+
manifestContent = fs.readFileSync(pkgPath, 'utf-8');
|
|
183
|
+
}
|
|
184
|
+
catch { }
|
|
72
185
|
}
|
|
186
|
+
const aiAnswer = await askRepoQuestion(rawGoal + (readfileOutput ? `\n\nReferenced File Content:\n${readfileOutput}` : ''), projectName, filesList, manifestContent, [], { language: targetLang, native: options?.native });
|
|
187
|
+
s.stop('Query analysis complete.');
|
|
188
|
+
const outputMd = readfileOutput ? `${readfileOutput}### đĄ AI Explanation & Answer\n${aiAnswer}` : aiAnswer;
|
|
189
|
+
safeNote(renderMarkdown(outputMd), 'đ Scout Agent Q&A Response');
|
|
190
|
+
continue;
|
|
73
191
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
192
|
+
// -------------------------------------------------------------
|
|
193
|
+
// Mode 1: Codebase Scan Mode
|
|
194
|
+
// -------------------------------------------------------------
|
|
195
|
+
const isScanIntent = options?.scan || /\b(?:scan|bug scan|scan codebase|check for bugs|find bugs|security scan)\b/i.test(goalLower);
|
|
196
|
+
if (isScanIntent) {
|
|
197
|
+
const s = spinner();
|
|
198
|
+
s.start('đ¤ Scout Agent: Performing read-only bug & vulnerability scan...');
|
|
199
|
+
let codeSample = '';
|
|
200
|
+
const pkgPath = path.join(cwd, 'package.json');
|
|
201
|
+
if (fs.existsSync(pkgPath)) {
|
|
202
|
+
codeSample += fs.readFileSync(pkgPath, 'utf-8').slice(0, 1000) + '\n';
|
|
203
|
+
}
|
|
204
|
+
const issues = await generateCodeScanReport(projectName, filesList, codeSample);
|
|
205
|
+
saveScanReport(issues);
|
|
206
|
+
s.stop('Bug scan complete.');
|
|
207
|
+
const severityBadge = (sev) => {
|
|
208
|
+
switch (sev.toLowerCase()) {
|
|
209
|
+
case 'high':
|
|
210
|
+
return chalk.bgRed.white.bold(' HIGH ');
|
|
211
|
+
case 'medium':
|
|
212
|
+
return chalk.bgYellow.black.bold(' MEDIUM ');
|
|
213
|
+
default:
|
|
214
|
+
return chalk.bgBlue.white.bold(' LOW ');
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
const reportLines = issues.map((iss) => `${chalk.bold.cyan(`[Issue #${iss.id}]`)} ${severityBadge(iss.severity)} ${chalk.bold(iss.title)}\n` +
|
|
218
|
+
` âĸ File: ${chalk.yellow(iss.file)}\n` +
|
|
219
|
+
` âĸ Detail: ${iss.description}`);
|
|
220
|
+
safeNote(`Found ${issues.length} potential issues matching past hotfix patterns:\n\n` +
|
|
221
|
+
reportLines.join('\n\n') +
|
|
222
|
+
`\n\n${chalk.bold.green('đĄ How to Auto-Fix Any Issue with Agent:')}\n` +
|
|
223
|
+
` âĸ Auto-fix Issue #${issues[0]?.id || 1}: ${chalk.bold.cyan(`scout agent --issue ${issues[0]?.id || 1}`)}\n` +
|
|
224
|
+
` âĸ Custom prompt auto-fix: ${chalk.bold.cyan('scout agent "Fix null check in src/index.ts"')}`, 'đ Scout Agent Bug Scan');
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
// -------------------------------------------------------------
|
|
228
|
+
// Mode 2: Feature Roadmap Plan Mode
|
|
229
|
+
// -------------------------------------------------------------
|
|
230
|
+
const addMatch = rawGoal ? (rawGoal.match(/^(?:plan|roadmap|feasibility|design|architect)\s+(.+)/i) || rawGoal.match(/^add\s+feature\s+(.+)/i)) : undefined;
|
|
231
|
+
const addIdea = options?.add || options?.plan || (addMatch ? addMatch[1] : undefined);
|
|
232
|
+
if (addIdea && !options?.create && !rawGoal?.match(/^(?:create|generate|make|build)\s+/i)) {
|
|
233
|
+
const s = spinner();
|
|
234
|
+
s.start(`đ¤ Scout Agent: Evaluating feasibility & generating roadmap for "${chalk.cyan(addIdea)}"...`);
|
|
235
|
+
const plan = await generateFeaturePlan(addIdea, projectName, filesList, targetLang, options?.native);
|
|
236
|
+
s.stop('Feasibility assessment & roadmap complete.');
|
|
237
|
+
safeNote(renderMarkdown(plan), `đ Feature Implementation Roadmap: "${addIdea}"`);
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
// -------------------------------------------------------------
|
|
241
|
+
// Mode 3: Run Command Execution & Auto-Fix Mode (--run)
|
|
242
|
+
// -------------------------------------------------------------
|
|
243
|
+
const runMatch = rawGoal ? rawGoal.match(/^(?:run|exec|execute|terminal)\s+["']?([^"']+)["']?$/i) : undefined;
|
|
244
|
+
const runCmdInput = options?.run || (runMatch ? runMatch[1] : undefined);
|
|
245
|
+
if (runCmdInput) {
|
|
246
|
+
const fullCommand = Array.isArray(runCmdInput) ? runCmdInput.join(' ') : runCmdInput;
|
|
247
|
+
safeNote(`Running command: ${chalk.bold.cyan(fullCommand)}\n${chalk.dim('Working directory: ' + cwd)}`, '⥠Scout Shell Execution');
|
|
248
|
+
const runSpinner = spinner();
|
|
249
|
+
runSpinner.start(`Executing \`${fullCommand}\`...`);
|
|
250
|
+
const outputBuffers = [];
|
|
251
|
+
const exitCode = await new Promise((resolve) => {
|
|
252
|
+
const proc = exec(fullCommand, { cwd });
|
|
253
|
+
proc.stdout?.on('data', (data) => {
|
|
254
|
+
const str = data.toString();
|
|
255
|
+
outputBuffers.push(str);
|
|
256
|
+
process.stdout.write(str);
|
|
257
|
+
});
|
|
258
|
+
proc.stderr?.on('data', (data) => {
|
|
259
|
+
const str = data.toString();
|
|
260
|
+
outputBuffers.push(str);
|
|
261
|
+
process.stderr.write(str);
|
|
262
|
+
});
|
|
263
|
+
proc.on('close', (code) => {
|
|
264
|
+
resolve(code ?? 0);
|
|
265
|
+
});
|
|
266
|
+
proc.on('error', (err) => {
|
|
267
|
+
outputBuffers.push(`Process Error: ${err.message}`);
|
|
268
|
+
resolve(1);
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
const fullOutput = outputBuffers.join('');
|
|
272
|
+
if (exitCode === 0) {
|
|
273
|
+
runSpinner.stop(chalk.green(`Command \`${fullCommand}\` executed successfully (Exit Code 0).`));
|
|
274
|
+
safeNote(`${chalk.green('â')} Process completed with exit code 0.`, 'â
Execution Successful');
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
runSpinner.stop(chalk.red(`Command \`${fullCommand}\` failed with exit code ${exitCode}.`));
|
|
278
|
+
const diagSpinner = spinner();
|
|
279
|
+
diagSpinner.start('Scout AI is analyzing process failure output...');
|
|
280
|
+
const analysis = await analyzeCommandError(fullCommand, exitCode, fullOutput, projectName, filesList, targetLang, options?.native);
|
|
281
|
+
diagSpinner.stop(chalk.yellow('Failure diagnosis complete.'));
|
|
282
|
+
const diagMarkdown = `
|
|
283
|
+
### đ´ Process Failure Diagnosis
|
|
284
|
+
Command: \`${fullCommand}\` (Exit Code ${exitCode})
|
|
285
|
+
|
|
286
|
+
### đ Root Cause Analysis
|
|
287
|
+
${analysis.rootCause}
|
|
288
|
+
|
|
289
|
+
### đĄ Suggested Fix
|
|
290
|
+
${analysis.suggestedFix}
|
|
291
|
+
${analysis.targetFileToFix ? `\n### đ¯ Suspected File\n\`${analysis.targetFileToFix}\`` : ''}
|
|
292
|
+
`.trim();
|
|
293
|
+
safeNote(renderMarkdown(diagMarkdown), 'â ī¸ Scout AI Failure Diagnosis');
|
|
294
|
+
const shouldFix = await confirm({
|
|
295
|
+
message: chalk.bold('Would you like Scout Agent to auto-fix the failing code automatically?'),
|
|
296
|
+
initialValue: true,
|
|
297
|
+
});
|
|
298
|
+
if (!isCancel(shouldFix) && shouldFix) {
|
|
299
|
+
rawGoal = `Fix runtime/build error from command \`${fullCommand}\`. Cause: ${analysis.rootCause}`;
|
|
300
|
+
options = { ...options, file: analysis.targetFileToFix };
|
|
301
|
+
}
|
|
302
|
+
else {
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
// -------------------------------------------------------------
|
|
307
|
+
// Mode 4: Create File Generation Mode (--create)
|
|
308
|
+
// -------------------------------------------------------------
|
|
309
|
+
const createMatch = rawGoal ? rawGoal.match(/(?:create|generate|make|build)\s+(?:a\s+)?(?:new\s+)?(?:file\s+)?([a-zA-Z0-9_\-./]+\.[a-zA-Z0-9]+)\b/i) : undefined;
|
|
310
|
+
const targetCreateFile = options?.create || (createMatch && !createMatch[1]?.includes('package.json') ? createMatch[1] : undefined);
|
|
311
|
+
if (targetCreateFile) {
|
|
312
|
+
const targetFilePath = targetCreateFile.trim();
|
|
313
|
+
let finalPrompt = rawGoal;
|
|
314
|
+
if (!finalPrompt || !finalPrompt.trim()) {
|
|
315
|
+
const userPromptInput = await text({
|
|
316
|
+
message: chalk.bold(`What should Scout generate inside ${chalk.cyan(targetFilePath)}?`),
|
|
317
|
+
placeholder: 'e.g. Create a utility module with functions for string formatting and timestamp parsing',
|
|
318
|
+
validate: (val) => (!val || !val.trim() ? 'Please provide a prompt describing the file requirements' : undefined),
|
|
319
|
+
});
|
|
320
|
+
if (isCancel(userPromptInput)) {
|
|
321
|
+
safeNote(chalk.yellow('File creation operation cancelled.'), '⨠Scout File Creation');
|
|
322
|
+
continue;
|
|
323
|
+
}
|
|
324
|
+
finalPrompt = userPromptInput.trim();
|
|
325
|
+
}
|
|
326
|
+
const resolvedPath = path.resolve(cwd, targetFilePath);
|
|
327
|
+
const fileExists = fs.existsSync(resolvedPath);
|
|
328
|
+
const originalContent = fileExists ? fs.readFileSync(resolvedPath, 'utf-8') : '__FT_WAS_NEW__';
|
|
329
|
+
const git = simpleGit();
|
|
330
|
+
const isRepo = await git.checkIsRepo();
|
|
331
|
+
let currentBranch = 'main';
|
|
332
|
+
if (isRepo) {
|
|
333
|
+
try {
|
|
334
|
+
const branchSummary = await git.branch();
|
|
335
|
+
currentBranch = branchSummary.current || 'main';
|
|
336
|
+
}
|
|
337
|
+
catch { }
|
|
338
|
+
}
|
|
339
|
+
const s = spinner();
|
|
340
|
+
s.start(`đ¤ Scout Agent: Generating code for ${targetFilePath}...`);
|
|
341
|
+
let genResult;
|
|
342
|
+
try {
|
|
343
|
+
genResult = await generateCodeFile(targetFilePath, finalPrompt, projectName, filesList, targetLang, options?.native);
|
|
344
|
+
}
|
|
345
|
+
catch (err) {
|
|
346
|
+
s.stop(chalk.red('Failed to generate code file.'));
|
|
347
|
+
safeNote(chalk.red(`Error: ${err?.message || String(err)}`), 'â ī¸ Code Generation Error');
|
|
348
|
+
continue;
|
|
349
|
+
}
|
|
350
|
+
const parentDir = path.dirname(resolvedPath);
|
|
351
|
+
if (!fs.existsSync(parentDir)) {
|
|
352
|
+
fs.mkdirSync(parentDir, { recursive: true });
|
|
353
|
+
}
|
|
80
354
|
try {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
355
|
+
fs.writeFileSync(resolvedPath, genResult.code, 'utf-8');
|
|
356
|
+
}
|
|
357
|
+
catch (writeErr) {
|
|
358
|
+
s.stop(chalk.red('Failed to write generated code to disk.'));
|
|
359
|
+
safeNote(chalk.red(`Write error: ${writeErr?.message || String(writeErr)}`), 'â ī¸ File Write Error');
|
|
360
|
+
continue;
|
|
361
|
+
}
|
|
362
|
+
recordHistoryAction({
|
|
363
|
+
command: `scout agent --create "${targetFilePath}" "${finalPrompt}"`,
|
|
364
|
+
description: `Scout created file ${targetFilePath}`,
|
|
365
|
+
previousBranch: currentBranch,
|
|
366
|
+
affectedFiles: [targetFilePath],
|
|
367
|
+
backups: [{ filePath: targetFilePath, originalContent }],
|
|
368
|
+
});
|
|
369
|
+
s.stop(chalk.green(`File ${targetFilePath} created successfully!`));
|
|
370
|
+
const teardownContent = `
|
|
371
|
+
### đ Created File
|
|
372
|
+
\`${targetFilePath}\`
|
|
373
|
+
|
|
374
|
+
### đĄ Description & Overview
|
|
375
|
+
${genResult.description}
|
|
376
|
+
|
|
377
|
+
### đģ Preview
|
|
378
|
+
\`\`\`
|
|
379
|
+
${genResult.code.slice(0, 500)}${genResult.code.length > 500 ? '\n... (truncated)' : ''}
|
|
380
|
+
\`\`\`
|
|
381
|
+
`.trim();
|
|
382
|
+
safeNote(renderMarkdown(teardownContent), '⨠Scout File Creation Teardown');
|
|
383
|
+
if (options?.yes || !process.stdout.isTTY) {
|
|
384
|
+
safeNote(`${chalk.green('â')} Saved ${chalk.bold.cyan(targetFilePath)} on branch ${chalk.bold.green(currentBranch)}.\n` +
|
|
385
|
+
`đĄ ${chalk.dim('You can revert this creation anytime by running:')} ${chalk.cyan('scout undo')}`, 'â
Creation Confirmed');
|
|
386
|
+
continue;
|
|
387
|
+
}
|
|
388
|
+
const actionChoice = await select({
|
|
389
|
+
message: chalk.bold('Review Scout Agent\'s generated file. What would you like to do?'),
|
|
390
|
+
options: [
|
|
391
|
+
{ value: 'keep', label: 'â
Keep File', hint: `Retain the newly created file directly on current branch (${currentBranch})` },
|
|
392
|
+
{ value: 'pr', label: 'đ Create Pull Request', hint: 'Push created file to a new branch & open a Pull Request' },
|
|
393
|
+
{ value: 'revert', label: 'âŠī¸ Revert / Delete File', hint: 'Remove the created file immediately' },
|
|
394
|
+
],
|
|
395
|
+
});
|
|
396
|
+
if (isCancel(actionChoice) || actionChoice === 'revert') {
|
|
397
|
+
const revSpinner = spinner();
|
|
398
|
+
revSpinner.start('Reverting Scout Agent\'s file creation...');
|
|
399
|
+
await handleUndo();
|
|
400
|
+
revSpinner.stop('Creation reverted successfully.');
|
|
401
|
+
continue;
|
|
402
|
+
}
|
|
403
|
+
if (actionChoice === 'pr') {
|
|
404
|
+
const prSpinner = spinner();
|
|
405
|
+
prSpinner.start('Creating Pull Request...');
|
|
406
|
+
const prRes = await createPullRequest({
|
|
407
|
+
branchPrefix: 'create',
|
|
408
|
+
title: `feat: Add ${targetFilePath}`,
|
|
409
|
+
affectedFiles: [targetFilePath],
|
|
410
|
+
commitMessage: `feat: Create ${targetFilePath}`,
|
|
411
|
+
});
|
|
412
|
+
prSpinner.stop('Pull Request workflow processed.');
|
|
413
|
+
safeNote(`${chalk.green('â')} ${prRes.message}\n` +
|
|
414
|
+
`đĄ ${chalk.dim('You remain on active branch:')} ${chalk.cyan(currentBranch)}`, 'đ Pull Request Status');
|
|
415
|
+
continue;
|
|
416
|
+
}
|
|
417
|
+
safeNote(`${chalk.green('â')} Saved ${chalk.bold.cyan(targetFilePath)} on branch ${chalk.bold.green(currentBranch)}.\n` +
|
|
418
|
+
`đĄ ${chalk.dim('You can revert this creation anytime by running:')} ${chalk.cyan('scout undo')}`, 'â
Creation Confirmed');
|
|
419
|
+
continue;
|
|
420
|
+
}
|
|
421
|
+
// -------------------------------------------------------------
|
|
422
|
+
// Mode 5: Autonomous Agent Loop & Code Fix Mode (Default)
|
|
423
|
+
// -------------------------------------------------------------
|
|
424
|
+
let finalGoal = rawGoal;
|
|
425
|
+
let specifiedTargetFile = options?.file;
|
|
426
|
+
if (!finalGoal && options?.issue) {
|
|
427
|
+
const report = loadScanReport();
|
|
428
|
+
const issueId = Number(options.issue);
|
|
429
|
+
const targetIssue = report.find((i) => i.id === issueId) || report[0];
|
|
430
|
+
if (targetIssue) {
|
|
431
|
+
finalGoal = `Fix issue #${targetIssue.id}: ${targetIssue.title} â ${targetIssue.description}`;
|
|
432
|
+
specifiedTargetFile = targetIssue.file;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
if (!finalGoal || !finalGoal.trim()) {
|
|
436
|
+
safeNote(chalk.yellow('No active goal specified.'), 'đ¤ Scout Agent');
|
|
437
|
+
continue;
|
|
438
|
+
}
|
|
439
|
+
// File Identification
|
|
440
|
+
let targetFiles = [];
|
|
441
|
+
if (specifiedTargetFile) {
|
|
442
|
+
targetFiles = findTargetFilesFromPrompt(finalGoal, specifiedTargetFile, filesList);
|
|
443
|
+
if (targetFiles.length === 0 && fs.existsSync(path.resolve(cwd, specifiedTargetFile))) {
|
|
444
|
+
targetFiles = [specifiedTargetFile];
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
if (targetFiles.length === 0) {
|
|
448
|
+
targetFiles = findTargetFilesFromPrompt(finalGoal, undefined, filesList);
|
|
449
|
+
}
|
|
450
|
+
if (targetFiles.length === 0) {
|
|
451
|
+
const sIdentify = spinner();
|
|
452
|
+
sIdentify.start(`đ¤ Scout Agent initializing autonomous workflow for: "${finalGoal}"...`);
|
|
453
|
+
const aiMatched = await identifyTargetFiles(finalGoal, projectName, filesList, targetLang, options?.native);
|
|
454
|
+
sIdentify.stop('Target files identified.');
|
|
455
|
+
if (aiMatched.length > 0) {
|
|
456
|
+
targetFiles = aiMatched;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
if (targetFiles.length === 0) {
|
|
460
|
+
let possibleNewFile = undefined;
|
|
461
|
+
if (specifiedTargetFile && specifiedTargetFile.trim()) {
|
|
462
|
+
const clean = specifiedTargetFile.trim();
|
|
463
|
+
if (!clean.startsWith('.') && !clean.startsWith('*') && clean.includes('.')) {
|
|
464
|
+
possibleNewFile = clean;
|
|
84
465
|
}
|
|
85
|
-
|
|
86
|
-
|
|
466
|
+
}
|
|
467
|
+
if (possibleNewFile) {
|
|
468
|
+
await handleAgent(finalGoal, { ...options, create: possibleNewFile });
|
|
469
|
+
continue;
|
|
470
|
+
}
|
|
471
|
+
safeNote(chalk.yellow(`Scout Agent could not identify specific files needing modification for goal: "${finalGoal}".\nPlease refine your request or specify target files with --file.`), 'â ī¸ Agent File Selection');
|
|
472
|
+
continue;
|
|
473
|
+
}
|
|
474
|
+
const s = spinner();
|
|
475
|
+
s.start(`đ¤ Scout Agent processing: "${finalGoal}" across file(s): ${targetFiles.join(', ')}...`);
|
|
476
|
+
const targetFilesMap = {};
|
|
477
|
+
const backups = [];
|
|
478
|
+
const normKey = (p) => p.replace(/\\/g, '/').replace(/^\.\//, '').trim();
|
|
479
|
+
for (const tf of targetFiles) {
|
|
480
|
+
const fullPath = path.resolve(cwd, tf);
|
|
481
|
+
if (fs.existsSync(fullPath)) {
|
|
482
|
+
const content = fs.readFileSync(fullPath, 'utf-8');
|
|
483
|
+
targetFilesMap[normKey(tf)] = content;
|
|
484
|
+
backups.push({ filePath: normKey(tf), originalContent: content });
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
// Code Modification
|
|
488
|
+
s.message('Applying initial code modifications...');
|
|
489
|
+
let fixResult = await generateMultiCodeFix(finalGoal, projectName, filesList, targetFilesMap, targetLang, options?.native, (completed, total, file) => {
|
|
490
|
+
s.message(`Modifying files (${completed}/${total}): ${file}...`);
|
|
491
|
+
});
|
|
492
|
+
const modifiedFiles = [];
|
|
493
|
+
for (const item of fixResult.files) {
|
|
494
|
+
if (item.fixedCode !== undefined) {
|
|
495
|
+
const k = normKey(item.targetFile);
|
|
496
|
+
const fullPath = path.resolve(cwd, k);
|
|
497
|
+
const orig = targetFilesMap[k] ?? '';
|
|
498
|
+
const normalizedFixed = item.fixedCode.replace(/\r\n/g, '\n').trim();
|
|
499
|
+
const normalizedOrig = orig.replace(/\r\n/g, '\n').trim();
|
|
500
|
+
if (normalizedFixed !== normalizedOrig) {
|
|
501
|
+
fs.mkdirSync(path.dirname(fullPath), { recursive: true });
|
|
502
|
+
fs.writeFileSync(fullPath, item.fixedCode, 'utf-8');
|
|
503
|
+
modifiedFiles.push(k);
|
|
87
504
|
}
|
|
88
505
|
}
|
|
89
|
-
catch { }
|
|
90
506
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
attempts++;
|
|
99
|
-
try {
|
|
100
|
-
await execAsync(verifyCommand, { cwd });
|
|
101
|
-
verificationSuccess = true;
|
|
102
|
-
s.message(`Verification command \`${verifyCommand}\` passed!`);
|
|
103
|
-
break;
|
|
507
|
+
// Autonomous Verification & Self-Healing Loop
|
|
508
|
+
let verifyCommand = options?.verifyCmd;
|
|
509
|
+
if (!verifyCommand) {
|
|
510
|
+
const cmdInPromptMatch = finalGoal.match(/(?:deploy|verify|test|run|build)(?:\s+(?:by\s+running|with|using|and\s+run|via))?\s+['"`]([^'"`]+)['"`]/i) ||
|
|
511
|
+
finalGoal.match(/(?:deploy|verify|test|run)\s+(?:by\s+running|with|using)\s+(.+?)(?:$|\!|\.|\,)/i);
|
|
512
|
+
if (cmdInPromptMatch && cmdInPromptMatch[1]) {
|
|
513
|
+
verifyCommand = cmdInPromptMatch[1].trim();
|
|
104
514
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
fs.writeFileSync(path.resolve(cwd, fileToFix), healedRes.files[0].fixedCode, 'utf-8');
|
|
117
|
-
if (!modifiedFiles.includes(fileToFix))
|
|
118
|
-
modifiedFiles.push(fileToFix);
|
|
515
|
+
}
|
|
516
|
+
if (!verifyCommand) {
|
|
517
|
+
const pkgPath = path.join(cwd, 'package.json');
|
|
518
|
+
if (fs.existsSync(pkgPath)) {
|
|
519
|
+
try {
|
|
520
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
521
|
+
if (pkg.scripts?.test && pkg.scripts.test !== 'echo "Error: no test specified" && exit 1') {
|
|
522
|
+
verifyCommand = 'npm test';
|
|
523
|
+
}
|
|
524
|
+
else if (pkg.scripts?.build) {
|
|
525
|
+
verifyCommand = 'npm run build';
|
|
119
526
|
}
|
|
120
527
|
}
|
|
528
|
+
catch { }
|
|
121
529
|
}
|
|
122
530
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
531
|
+
let verificationSuccess = true;
|
|
532
|
+
let attempts = 0;
|
|
533
|
+
const maxAttempts = options?.maxRetries || 3;
|
|
534
|
+
if (verifyCommand) {
|
|
535
|
+
s.message(`Running verification command: \`${verifyCommand}\`...`);
|
|
536
|
+
while (attempts < maxAttempts) {
|
|
537
|
+
attempts++;
|
|
538
|
+
try {
|
|
539
|
+
await execAsync(verifyCommand, { cwd });
|
|
540
|
+
verificationSuccess = true;
|
|
541
|
+
s.message(`Verification command \`${verifyCommand}\` passed!`);
|
|
542
|
+
break;
|
|
543
|
+
}
|
|
544
|
+
catch (cmdErr) {
|
|
545
|
+
verificationSuccess = false;
|
|
546
|
+
const output = (cmdErr.stdout || '') + '\n' + (cmdErr.stderr || '');
|
|
547
|
+
s.message(`Attempt ${attempts}/${maxAttempts}: Command failed. Diagnosing & self-healing...`);
|
|
548
|
+
const diagnosis = await analyzeCommandError(verifyCommand, cmdErr.code || 1, output, projectName, filesList, targetLang, options?.native);
|
|
549
|
+
const fileToFix = diagnosis.targetFileToFix || targetFiles[0] || filesList[0] || '';
|
|
550
|
+
if (fileToFix && fs.existsSync(path.resolve(cwd, fileToFix))) {
|
|
551
|
+
const currentContent = fs.readFileSync(path.resolve(cwd, fileToFix), 'utf-8');
|
|
552
|
+
const healPrompt = `Fix error running \`${verifyCommand}\`: ${diagnosis.rootCause}. Suggestion: ${diagnosis.suggestedFix}`;
|
|
553
|
+
const healedRes = await generateMultiCodeFix(healPrompt, projectName, filesList, { [fileToFix]: currentContent }, targetLang, options?.native);
|
|
554
|
+
if (healedRes.files[0]?.fixedCode) {
|
|
555
|
+
fs.writeFileSync(path.resolve(cwd, fileToFix), healedRes.files[0].fixedCode, 'utf-8');
|
|
556
|
+
if (!modifiedFiles.includes(fileToFix))
|
|
557
|
+
modifiedFiles.push(fileToFix);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
const git = simpleGit();
|
|
564
|
+
const isRepo = await git.checkIsRepo();
|
|
565
|
+
let currentBranch = 'main';
|
|
566
|
+
if (isRepo) {
|
|
567
|
+
try {
|
|
568
|
+
const branchSummary = await git.branch();
|
|
569
|
+
currentBranch = branchSummary.current || 'main';
|
|
570
|
+
}
|
|
571
|
+
catch { }
|
|
572
|
+
}
|
|
573
|
+
if (modifiedFiles.length > 0) {
|
|
574
|
+
recordHistoryAction({
|
|
575
|
+
command: `scout agent "${finalGoal}"`,
|
|
576
|
+
description: `Scout agent completed goal on ${modifiedFiles.length} file(s)`,
|
|
577
|
+
previousBranch: currentBranch,
|
|
578
|
+
affectedFiles: modifiedFiles,
|
|
579
|
+
backups,
|
|
580
|
+
});
|
|
581
|
+
}
|
|
582
|
+
s.stop(chalk.green(`đ¤ Scout Agent workflow completed for: "${finalGoal}"!`));
|
|
583
|
+
const teardownMd = `
|
|
145
584
|
### đ¯ Agent Goal
|
|
146
585
|
${finalGoal}
|
|
147
586
|
|
|
@@ -156,45 +595,46 @@ ${fixResult.howFixed}
|
|
|
156
595
|
|
|
157
596
|
### â
Verification Status
|
|
158
597
|
${verifyCommand ? (verificationSuccess ? `\`${verifyCommand}\` PASSED đ` : `\`${verifyCommand}\` FAILED (max retries reached) â ī¸`) : 'No automated verification script detected'}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
});
|
|
176
|
-
if (actionChoice === 'revert') {
|
|
177
|
-
const revSpinner = spinner();
|
|
178
|
-
revSpinner.start('Reverting Agent changes...');
|
|
179
|
-
await handleUndo();
|
|
180
|
-
revSpinner.stop('Changes reverted successfully.');
|
|
181
|
-
return;
|
|
182
|
-
}
|
|
183
|
-
if (actionChoice === 'pr') {
|
|
184
|
-
const prSpinner = spinner();
|
|
185
|
-
prSpinner.start('Creating Pull Request...');
|
|
186
|
-
const prRes = await createPullRequest({
|
|
187
|
-
branchPrefix: 'agent',
|
|
188
|
-
title: finalGoal,
|
|
189
|
-
affectedFiles: modifiedFiles,
|
|
190
|
-
commitMessage: `feat: ${finalGoal}`,
|
|
598
|
+
`.trim();
|
|
599
|
+
safeNote(renderMarkdown(teardownMd), 'đ¤ Scout Agent Autonomous Teardown');
|
|
600
|
+
if (modifiedFiles.length === 0)
|
|
601
|
+
continue;
|
|
602
|
+
if (options?.yes || !process.stdout.isTTY) {
|
|
603
|
+
safeNote(`${chalk.green('â')} Agent changes retained on ${modifiedFiles.length} file(s) (Branch: ${chalk.bold.green(currentBranch)}).\n` +
|
|
604
|
+
`đĄ ${chalk.dim('You can revert anytime by running:')} ${chalk.cyan('scout undo')}`, 'â
Agent Fix Confirmed');
|
|
605
|
+
continue;
|
|
606
|
+
}
|
|
607
|
+
const actionChoice = await select({
|
|
608
|
+
message: chalk.bold('Review Scout Agent result. What would you like to do?'),
|
|
609
|
+
options: [
|
|
610
|
+
{ value: 'keep', label: 'â
Keep Changes', hint: `Retain agent changes on current branch (${currentBranch})` },
|
|
611
|
+
{ value: 'pr', label: 'đ Create Pull Request', hint: 'Push fix to a new branch & open a Pull Request' },
|
|
612
|
+
{ value: 'revert', label: 'âŠī¸ Revert Changes', hint: 'Restore original code immediately' },
|
|
613
|
+
],
|
|
191
614
|
});
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
615
|
+
if (actionChoice === 'revert') {
|
|
616
|
+
const revSpinner = spinner();
|
|
617
|
+
revSpinner.start('Reverting Agent changes...');
|
|
618
|
+
await handleUndo();
|
|
619
|
+
revSpinner.stop('Changes reverted successfully.');
|
|
620
|
+
continue;
|
|
621
|
+
}
|
|
622
|
+
if (actionChoice === 'pr') {
|
|
623
|
+
const prSpinner = spinner();
|
|
624
|
+
prSpinner.start('Creating Pull Request...');
|
|
625
|
+
const prRes = await createPullRequest({
|
|
626
|
+
branchPrefix: 'agent',
|
|
627
|
+
title: finalGoal,
|
|
628
|
+
affectedFiles: modifiedFiles,
|
|
629
|
+
commitMessage: `feat: ${finalGoal}`,
|
|
630
|
+
});
|
|
631
|
+
prSpinner.stop('Pull Request workflow processed.');
|
|
632
|
+
safeNote(`${chalk.green('â')} ${prRes.message}\n` +
|
|
633
|
+
`đĄ ${chalk.dim('You remain on active branch:')} ${chalk.cyan(currentBranch)}`, 'đ Pull Request Status');
|
|
634
|
+
continue;
|
|
635
|
+
}
|
|
636
|
+
safeNote(`${chalk.green('â')} Agent changes retained on ${modifiedFiles.length} file(s) (Branch: ${chalk.bold.green(currentBranch)}).\n` +
|
|
637
|
+
`đĄ ${chalk.dim('You can revert these changes anytime by running:')} ${chalk.cyan('scout undo')}`, 'â
Agent Workflow Finished');
|
|
196
638
|
}
|
|
197
|
-
safeNote(`${chalk.green('â')} Agent changes retained on ${modifiedFiles.length} file(s) (Branch: ${chalk.bold.green(currentBranch)}).\n` +
|
|
198
|
-
`đĄ ${chalk.dim('You can revert these changes anytime by running:')} ${chalk.cyan('scout undo')}`, 'â
Agent Workflow Finished');
|
|
199
639
|
}
|
|
200
640
|
//# sourceMappingURL=agent.js.map
|