ft-scout 6.0.5 → 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 +61 -38
- package/bin/ft.js +1 -1
- package/bin/src/commands/agent.d.ts.map +1 -1
- package/bin/src/commands/agent.js +198 -27
- package/bin/src/commands/agent.js.map +1 -1
- package/bin/src/commands/audit.js.map +1 -1
- package/bin/src/commands/authCommand.js.map +1 -1
- 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.map +1 -1
- package/bin/src/commands/dashboard.js.map +1 -1
- package/bin/src/commands/history.js.map +1 -1
- 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.map +1 -1
- package/bin/src/commands/logout.js.map +1 -1
- package/bin/src/commands/owners.js.map +1 -1
- package/bin/src/commands/quota.js.map +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.map +1 -1
- 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.map +1 -1
- package/bin/src/commands/undo.js.map +1 -1
- 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.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 +369 -33
- 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 +57 -6
- 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.map +1 -1
- package/bin/src/engine/web.d.ts.map +1 -1
- package/bin/src/engine/web.js.map +1 -1
- package/bin/src/index.js +4 -4
- 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.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.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.map +1 -1
- package/bin/src/utils/updateCheck.js.map +1 -1
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"signals.js","sourceRoot":"","sources":["signals.ts"],"names":[],"mappings":"AAaA,MAAM,UAAU,cAAc,CAAC,WAAyC;
|
|
1
|
+
{"version":3,"file":"signals.js","sourceRoot":"","sources":["signals.ts"],"names":[],"mappings":"AAaA,MAAM,UAAU,cAAc,CAAC,WAAyC;IACpE,IAAI,CAAC,WAAW;QAAE,OAAO,EAAE,CAAC;IAE5B,MAAM,OAAO,GAAa,EAAE,CAAC;IAE7B,+BAA+B;IAC/B,MAAM,eAAe,GAAG,WAAW,CAAC,UAAU,CAAC,CAAC,CAAC,WAAW,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC;IAC3F,MAAM,WAAW,GAAG,WAAW,CAAC,oBAAoB,CAAC;IAErD,IAAI,eAAe,KAAK,CAAC,IAAI,WAAW,KAAK,SAAS,IAAI,WAAW,GAAG,GAAG,EAAE,CAAC;QAC1E,MAAM,iBAAiB,GAAG,WAAW,CAAC,iBAAiB,IAAI,CAAC,CAAC;QAC7D,OAAO,CAAC,IAAI,CAAC;YACT,EAAE,EAAE,oBAAoB;YACxB,OAAO,EAAE,+CAA+C,iBAAiB,4CAA4C;SACxH,CAAC,CAAC;IACP,CAAC;IAED,0BAA0B;IAC1B,IAAI,WAAW,CAAC,qBAAqB,EAAE,CAAC;QACpC,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,GAAG,WAAW,CAAC,qBAAqB,CAAC;QACnE,OAAO,CAAC,IAAI,CAAC;YACT,EAAE,EAAE,eAAe;YACnB,OAAO,EAAE,wBAAwB,QAAQ,oBAAoB,UAAU,gCAAgC;SAC1G,CAAC,CAAC;IACP,CAAC;IAED,+BAA+B;IAC/B,IAAI,WAAW,KAAK,SAAS,IAAI,WAAW,GAAG,IAAI,EAAE,CAAC;QAClD,MAAM,eAAe,GAAG,WAAW,CAAC,eAAe,IAAI,IAAI,CAAC,KAAK,CAAC,WAAW,GAAG,GAAG,CAAC,CAAC;QACrF,OAAO,CAAC,IAAI,CAAC;YACT,EAAE,EAAE,oBAAoB;YACxB,OAAO,EAAE,4CAA4C,eAAe,+CAA+C;SACtH,CAAC,CAAC;IACP,CAAC;IAED,OAAO,OAAO,CAAC;AACnB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agentEngine.d.ts","sourceRoot":"","sources":["agentEngine.ts"],"names":[],"mappings":"AAMA,OAAO,EAA0C,KAAK,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAO7F,wBAAgB,oBAAoB,
|
|
1
|
+
{"version":3,"file":"agentEngine.d.ts","sourceRoot":"","sources":["agentEngine.ts"],"names":[],"mappings":"AAMA,OAAO,EAA0C,KAAK,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAO7F,wBAAgB,oBAAoB,CAChC,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,GACpB;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,cAAc,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAwG9D;AAED,MAAM,WAAW,cAAc;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE;QACR,IAAI,EAAE,MAAM,CAAC;QACb,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QAChC,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;KACvB,CAAC;CACL;AAED,eAAO,MAAM,WAAW,EAAE,cAAc,EAgLvC,CAAC;AAEF,MAAM,WAAW,gBAAgB;IAC7B,WAAW,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAClC,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACpC;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,SAAK,EAAE,YAAY,SAAI,EAAE,QAAQ,SAAI,GAAG,MAAM,CAyCvG;AAED,qBAAa,kBAAkB;IAC3B,OAAO,CAAC,GAAG,CAAS;IACpB,OAAO,CAAC,eAAe,CAAa;IACpC,OAAO,CAAC,aAAa,CAA0B;IAC/C,OAAO,CAAC,OAAO,CAAoB;IACnC,OAAO,CAAC,WAAW,CAAU;IAC7B,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,SAAS,CAAsB;IACvC,OAAO,CAAC,cAAc,CAAa;IACnC,OAAO,CAAC,WAAW,CAAa;IAEhC,YAAY,OAAO,CAAC,EAAE,gBAAgB,EAKrC;IAEM,gBAAgB,IAAI,MAAM,EAAE,CAElC;IAEM,UAAU,IAAI,UAAU,EAAE,CAEhC;IAEM,QAAQ,IAAI;QACf,UAAU,EAAE,MAAM,CAAC;QACnB,cAAc,EAAE,MAAM,CAAC;QACvB,kBAAkB,EAAE,MAAM,CAAC;QAC3B,SAAS,EAAE,MAAM,CAAC;KACrB,CAOA;IAEM,YAAY,IAAI,IAAI,CAO1B;IAED,OAAO,CAAC,YAAY;IA6CP,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAgUzF;IAED,OAAO,CAAC,mBAAmB;YA+Cb,gBAAgB;CAuajC"}
|
|
@@ -186,6 +186,75 @@ export const AGENT_TOOLS = [
|
|
|
186
186
|
required: ['pattern'],
|
|
187
187
|
},
|
|
188
188
|
},
|
|
189
|
+
{
|
|
190
|
+
name: 'tree_view',
|
|
191
|
+
description: 'Generate a visual ASCII/Unicode directory tree of a workspace path to visualize folder structures.',
|
|
192
|
+
parameters: {
|
|
193
|
+
type: 'object',
|
|
194
|
+
properties: {
|
|
195
|
+
path: { type: 'string', description: 'Relative path to directory (default "." for workspace root)' },
|
|
196
|
+
maxDepth: { type: 'number', description: 'Maximum depth limit for tree traversal (default 3)' },
|
|
197
|
+
},
|
|
198
|
+
required: [],
|
|
199
|
+
},
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
name: 'file_info',
|
|
203
|
+
description: 'Get file statistics including file size, total line count, file extension, and last modification timestamp.',
|
|
204
|
+
parameters: {
|
|
205
|
+
type: 'object',
|
|
206
|
+
properties: {
|
|
207
|
+
path: { type: 'string', description: 'Relative path to the target file' },
|
|
208
|
+
},
|
|
209
|
+
required: ['path'],
|
|
210
|
+
},
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
name: 'multi_edit_file',
|
|
214
|
+
description: 'Apply multiple code snippet replacements across a file in a single step to execute complex refactoring efficiently.',
|
|
215
|
+
parameters: {
|
|
216
|
+
type: 'object',
|
|
217
|
+
properties: {
|
|
218
|
+
path: { type: 'string', description: 'Relative path to the target file' },
|
|
219
|
+
edits: {
|
|
220
|
+
type: 'array',
|
|
221
|
+
description: 'Array of target and replacement pairs to apply sequentially',
|
|
222
|
+
items: {
|
|
223
|
+
type: 'object',
|
|
224
|
+
properties: {
|
|
225
|
+
target: { type: 'string', description: 'Exact code snippet to replace' },
|
|
226
|
+
replacement: { type: 'string', description: 'New code replacement snippet' },
|
|
227
|
+
},
|
|
228
|
+
required: ['target', 'replacement'],
|
|
229
|
+
},
|
|
230
|
+
},
|
|
231
|
+
reason: { type: 'string', description: 'Explanation of why these multi-edits are being applied' },
|
|
232
|
+
},
|
|
233
|
+
required: ['path', 'edits'],
|
|
234
|
+
},
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
name: 'fetch_url',
|
|
238
|
+
description: 'Fetch content or documentation from an external HTTP/HTTPS URL for API docs, technical references, or specs.',
|
|
239
|
+
parameters: {
|
|
240
|
+
type: 'object',
|
|
241
|
+
properties: {
|
|
242
|
+
url: { type: 'string', description: 'The absolute HTTP or HTTPS URL to fetch' },
|
|
243
|
+
},
|
|
244
|
+
required: ['url'],
|
|
245
|
+
},
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
name: 'git_diff',
|
|
249
|
+
description: 'View current uncommitted git changes across the workspace or for a target file path.',
|
|
250
|
+
parameters: {
|
|
251
|
+
type: 'object',
|
|
252
|
+
properties: {
|
|
253
|
+
path: { type: 'string', description: 'Optional relative file path filter' },
|
|
254
|
+
},
|
|
255
|
+
required: [],
|
|
256
|
+
},
|
|
257
|
+
},
|
|
189
258
|
{
|
|
190
259
|
name: 'ask_user',
|
|
191
260
|
description: 'ONLY use for missing API keys or secret credentials. DO NOT use to ask the user what code to write or to re-confirm their prompt—take immediate autonomous tool action instead.',
|
|
@@ -209,6 +278,51 @@ export const AGENT_TOOLS = [
|
|
|
209
278
|
},
|
|
210
279
|
},
|
|
211
280
|
];
|
|
281
|
+
export function buildDirectoryTree(dirPath, prefix = '', currentDepth = 0, maxDepth = 3) {
|
|
282
|
+
if (currentDepth >= maxDepth)
|
|
283
|
+
return `${prefix}└── ... [depth limit reached]\n`;
|
|
284
|
+
if (!fs.existsSync(dirPath) || !fs.statSync(dirPath).isDirectory())
|
|
285
|
+
return '';
|
|
286
|
+
let result = '';
|
|
287
|
+
let entries = [];
|
|
288
|
+
try {
|
|
289
|
+
entries = fs.readdirSync(dirPath).filter((e) => !['.git', 'node_modules', '.ft', 'dist', 'bin/src/index.js'].includes(e));
|
|
290
|
+
}
|
|
291
|
+
catch {
|
|
292
|
+
return '';
|
|
293
|
+
}
|
|
294
|
+
entries.sort((a, b) => {
|
|
295
|
+
try {
|
|
296
|
+
const aIsDir = fs.statSync(path.join(dirPath, a)).isDirectory();
|
|
297
|
+
const bIsDir = fs.statSync(path.join(dirPath, b)).isDirectory();
|
|
298
|
+
if (aIsDir && !bIsDir)
|
|
299
|
+
return -1;
|
|
300
|
+
if (!aIsDir && bIsDir)
|
|
301
|
+
return 1;
|
|
302
|
+
}
|
|
303
|
+
catch { }
|
|
304
|
+
return a.localeCompare(b);
|
|
305
|
+
});
|
|
306
|
+
entries.forEach((entry, idx) => {
|
|
307
|
+
const isLast = idx === entries.length - 1;
|
|
308
|
+
const connector = isLast ? '└── ' : '├── ';
|
|
309
|
+
const childPath = path.join(dirPath, entry);
|
|
310
|
+
let isDir = false;
|
|
311
|
+
try {
|
|
312
|
+
isDir = fs.statSync(childPath).isDirectory();
|
|
313
|
+
}
|
|
314
|
+
catch { }
|
|
315
|
+
if (isDir) {
|
|
316
|
+
result += `${prefix}${connector}${entry}/\n`;
|
|
317
|
+
const childPrefix = prefix + (isLast ? ' ' : '│ ');
|
|
318
|
+
result += buildDirectoryTree(childPath, childPrefix, currentDepth + 1, maxDepth);
|
|
319
|
+
}
|
|
320
|
+
else {
|
|
321
|
+
result += `${prefix}${connector}${entry}\n`;
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
return result;
|
|
325
|
+
}
|
|
212
326
|
export class AgentExecutionLoop {
|
|
213
327
|
cwd;
|
|
214
328
|
historyMessages = [];
|
|
@@ -217,6 +331,9 @@ export class AgentExecutionLoop {
|
|
|
217
331
|
autoApprove;
|
|
218
332
|
maxSteps;
|
|
219
333
|
projectName;
|
|
334
|
+
startTime = Date.now();
|
|
335
|
+
totalToolCalls = 0;
|
|
336
|
+
currentStep = 0;
|
|
220
337
|
constructor(options) {
|
|
221
338
|
this.cwd = process.cwd();
|
|
222
339
|
this.autoApprove = Boolean(options?.autoApprove);
|
|
@@ -229,10 +346,21 @@ export class AgentExecutionLoop {
|
|
|
229
346
|
getBackups() {
|
|
230
347
|
return this.backups;
|
|
231
348
|
}
|
|
349
|
+
getStats() {
|
|
350
|
+
return {
|
|
351
|
+
totalSteps: this.currentStep,
|
|
352
|
+
totalToolCalls: this.totalToolCalls,
|
|
353
|
+
modifiedFilesCount: this.modifiedFiles.size,
|
|
354
|
+
elapsedMs: Date.now() - this.startTime,
|
|
355
|
+
};
|
|
356
|
+
}
|
|
232
357
|
resetContext() {
|
|
233
358
|
this.historyMessages = [];
|
|
234
359
|
this.modifiedFiles.clear();
|
|
235
360
|
this.backups = [];
|
|
361
|
+
this.startTime = Date.now();
|
|
362
|
+
this.totalToolCalls = 0;
|
|
363
|
+
this.currentStep = 0;
|
|
236
364
|
}
|
|
237
365
|
systemPrompt() {
|
|
238
366
|
const filesList = getDirectoryFiles(this.cwd).slice(0, 100);
|
|
@@ -248,23 +376,26 @@ Core Directives & Behavioral Guidelines:
|
|
|
248
376
|
1. HIGH AGENCY & AUTONOMY: You act autonomously through step-by-step tool invocation to solve complex coding tasks, debug issues, build features, create projects from scratch, or answer architectural questions.
|
|
249
377
|
2. MULTILINGUAL & INTENT COMPREHENSION: Understand user intent in any language (English, Hinglish like "likho", "bnao", "code karo", "fix karo", "samjha do", Hindi, Spanish, etc.).
|
|
250
378
|
3. AUTONOMOUS TARGET FILE DISCOVERY: You have full autonomy to locate, inspect, and modify target workspace files. When given a goal without an explicit file path, use auto-discovered target files, \`grep_search\`, or \`glob_search\` to locate relevant code before editing.
|
|
251
|
-
4.
|
|
252
|
-
5.
|
|
253
|
-
6.
|
|
254
|
-
7.
|
|
255
|
-
8.
|
|
379
|
+
4. ACTION-FIRST & DIRECT EXECUTION PRIORITY: When given a goal to fix, edit, modify, or create files, TAKE CODE MODIFICATION ACTIONS (\`write_file\`, \`edit_file\`, or \`run_command\`) IMMEDIATELY within the first 1-3 steps! DO NOT waste multiple steps reading unrelated asset files, sound generators, raw text files, init files, or requirements files unless specifically requested by the user.
|
|
380
|
+
5. PRE-LOADED CONTEXT RULE: If target files or auto-discovered files are already previewed in your context window history, DO NOT call \`read_file\` or \`list_dir\` on them again! Proceed directly to writing/editing code.
|
|
381
|
+
6. COMPLETE FILE CREATION & EDITING: Whenever asked to write, edit, create, implement, or fix code, YOU MUST CREATE / MODIFY ALL NECESSARY FILES required to completely fulfill the prompt (e.g. main source files, component files, styles, configs, tests, documentation). CALL \`write_file\` or \`edit_file\` for EVERY necessary file to save changes directly to disk in the workspace! Always provide a clear \`reason\` argument for why the edit is made.
|
|
382
|
+
7. RUN & VERIFY COMMANDS: Execute shell commands, tests, builds, and formatters using \`run_command\`. If a command or build fails, read the full error log, diagnose the root cause, and auto-fix the failing code immediately.
|
|
383
|
+
8. NO CODE TRUNCATION OR PLACEHOLDERS: Maintain 100% code integrity. NEVER replace existing code or HTML tags with placeholder comments (e.g. \`<!-- remaining content -->\` or \`// TODO: rest of code\`) or stripped skeleton structures. Always output complete, functional code.
|
|
384
|
+
9. REFORMATTING MINIFIED / LARGE FILES: When asked to format, beautify, or un-minify HTML, JS, CSS, or JSON files, DO NOT attempt to rewrite the file content manually using \`write_file\` or \`edit_file\` (which causes token truncation and content loss). ALWAYS use \`run_command\` to execute CLI formatters (e.g. \`npx prettier --write <file>\`, \`npx html-beautify -r <file>\`, \`npx js-beautify <file>\`, or run a quick Node script) to reformat files in-place on disk.
|
|
385
|
+
10. FROM-SCRATCH PROJECT CREATION & AUTOMATED TESTING PROTOCOL: When asked to build/create a project from scratch:
|
|
256
386
|
a. Scaffold Architecture: Create configuration/manifest files (\`package.json\`, \`tsconfig.json\`, \`pyproject.toml\`, etc.) and folder structure using \`write_file\` or \`run_command\`.
|
|
257
387
|
b. Write Source Implementation: Create all necessary source files with fully functional, complete, production-ready code (no skeleton placeholders or TODOs).
|
|
258
388
|
c. Write Automated Tests: Create comprehensive unit/integration test suite files (e.g. \`test/*.test.ts\`, \`tests/test_*.py\`, etc.).
|
|
259
389
|
d. Execute Tests & Build Verification: Run test suites and builds via \`run_command\` (e.g. \`npm test\`, \`npx vitest run\`, \`pytest\`, \`cargo test\`, \`go test ./...\`).
|
|
260
390
|
e. Self-Heal & Pass: If tests or builds fail, inspect output logs, edit files using \`edit_file\` or \`write_file\` to resolve errors, re-run tests until 100% passing, and only call \`task_completed\` when all checks pass cleanly.
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
391
|
+
11. CRITICAL TERMINATION RULE: As soon as you have finished creating/modifying the necessary files, running verification checks, or answering the user request, YOU MUST CALL \`task_completed\` tool immediately with a clear summary of your work.
|
|
392
|
+
12. PRODUCTION-GRADE CODE QUALITY: Every line of code you write or edit MUST be clean, elegant, modular, production-ready, highly readable, self-documenting, formatted, and strictly typed.
|
|
393
|
+
13. HIGH AUTONOMY & NO DOUBLE PROMPTING: NEVER call \`ask_user\` to ask what code to write, what features to implement, or to re-confirm the prompt. Take immediate autonomous action using \`write_file\`, \`edit_file\`, \`run_command\`, \`read_file\`, or \`grep_search\` using best software engineering practices. ONLY call \`ask_user\` if a critical secret credential (API key/password) is strictly required.
|
|
394
|
+
14. EDITING & DEBUGGING MASTERY PROTOCOL:
|
|
265
395
|
a. Full Context Reading: When editing or fixing bugs in existing files, inspect full code context. For files under 250 lines, reference the complete file content before modifying.
|
|
266
396
|
b. Guaranteed Edit Success: Use \`edit_file\` for precise snippet updates. If \`edit_file\` returns a target snippet mismatch error, IMMEDIATELY call \`write_file\` with the complete corrected file code so the edit is 100% applied without getting stuck!
|
|
267
397
|
c. Automated Error Diagnostics: After editing, check compiler/syntax outputs and test runner logs. If an error is detected, auto-heal the code immediately before concluding.
|
|
398
|
+
15. THOUGHT TRANSPARENCY PROTOCOL: In EVERY step before calling any tools or ending a turn, you MUST provide a clear 1-2 sentence explanation in your text response describing your current reasoning, what file/action you are taking, and why you are taking it.
|
|
268
399
|
|
|
269
400
|
When returning tool calls, use standard OpenAI function calling format or JSON tool call payload format:
|
|
270
401
|
\`\`\`json
|
|
@@ -278,33 +409,61 @@ When returning tool calls, use standard OpenAI function calling format or JSON t
|
|
|
278
409
|
async executeGoal(userGoal) {
|
|
279
410
|
this.resetContext();
|
|
280
411
|
this.historyMessages.push({ role: 'system', content: this.systemPrompt() });
|
|
281
|
-
//
|
|
412
|
+
// Robust Target File Discovery: Extract file paths/names directly from userGoal without destroying backslashes
|
|
282
413
|
const filesList = getDirectoryFiles(this.cwd);
|
|
283
|
-
const stopWords = new Set([
|
|
284
|
-
'the', 'a', 'an', 'in', 'is', 'for', 'to', 'of', 'and', 'or', 'on', 'at', 'by', 'from',
|
|
285
|
-
'with', 'this', 'that', 'it', 'code', 'file', 'files', 'fix', 'add', 'make', 'create',
|
|
286
|
-
'update', 'change', 'modify', 'edit', 'write', 'implement', 'bro', 'please', 'scout',
|
|
287
|
-
'agent', 'bug', 'error', 'issue', 'help', 'app', 'project', 'script', 'function',
|
|
288
|
-
'folder', 'dir', 'directory', 'testing', 'inside', 'open', 'vs', 'vscode', 'py', 'python',
|
|
289
|
-
'new', 'build', 'run', 'test', 'suite'
|
|
290
|
-
]);
|
|
291
|
-
const words = userGoal.split(/[\s,;:\/\\]+/).map((w) => w.replace(/["'(),;:!]/g, '').trim());
|
|
292
414
|
const matchedFiles = [];
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
415
|
+
const isIgnoredFile = (filePath) => {
|
|
416
|
+
const norm = filePath.replace(/\\/g, '/').toLowerCase();
|
|
417
|
+
const ext = path.extname(norm);
|
|
418
|
+
const ignoredExts = ['.pyc', '.pyo', '.png', '.jpg', '.jpeg', '.gif', '.ico', '.exe', '.bin', '.dll', '.so', '.dylib', '.zip', '.tar', '.gz'];
|
|
419
|
+
return (norm.includes('__pycache__/') ||
|
|
420
|
+
norm.includes('.git/') ||
|
|
421
|
+
norm.includes('node_modules/') ||
|
|
422
|
+
norm.includes('.ft/') ||
|
|
423
|
+
ignoredExts.includes(ext));
|
|
424
|
+
};
|
|
425
|
+
// Match potential paths (e.g. C:\path\to\file.py, pacman_game/game_map.py, game_map.py)
|
|
426
|
+
const pathMatches = userGoal.match(/(?:[a-zA-Z]:[\\/][^\s"':;,]+|\.?[\\/][^\s"':;,]+\.[a-zA-Z0-9]+|[a-zA-Z0-9_\-./\\]+\.[a-zA-Z0-9]+)/g) || [];
|
|
427
|
+
for (const rawPath of pathMatches) {
|
|
428
|
+
const cleanP = rawPath.replace(/^["'()]+|["'()]+$/g, '').trim();
|
|
429
|
+
if (!cleanP || cleanP.length < 3)
|
|
430
|
+
continue;
|
|
431
|
+
const fullP = path.isAbsolute(cleanP) ? cleanP : path.resolve(this.cwd, cleanP);
|
|
432
|
+
if (fs.existsSync(fullP) && fs.statSync(fullP).isFile()) {
|
|
433
|
+
const relP = path.relative(this.cwd, fullP).replace(/\\/g, '/');
|
|
434
|
+
if (!isIgnoredFile(relP) && !matchedFiles.includes(relP))
|
|
435
|
+
matchedFiles.push(relP);
|
|
436
|
+
}
|
|
437
|
+
else {
|
|
438
|
+
// Check if any file in filesList matches or ends with the extracted relative path or filename
|
|
439
|
+
const normToken = cleanP.replace(/\\/g, '/').toLowerCase();
|
|
440
|
+
const baseName = path.basename(normToken);
|
|
441
|
+
for (const f of filesList) {
|
|
442
|
+
if (isIgnoredFile(f))
|
|
443
|
+
continue;
|
|
444
|
+
const normF = f.replace(/\\/g, '/').toLowerCase();
|
|
445
|
+
if (normF === normToken || normF.endsWith('/' + normToken) || (baseName.includes('.') && normF.endsWith('/' + baseName))) {
|
|
446
|
+
if (!matchedFiles.includes(f))
|
|
447
|
+
matchedFiles.push(f);
|
|
448
|
+
}
|
|
301
449
|
}
|
|
302
450
|
}
|
|
303
451
|
}
|
|
304
|
-
//
|
|
452
|
+
// Keyword fallback if no direct path matched
|
|
305
453
|
if (matchedFiles.length === 0) {
|
|
454
|
+
const stopWords = new Set([
|
|
455
|
+
'the', 'a', 'an', 'in', 'is', 'for', 'to', 'of', 'and', 'or', 'on', 'at', 'by', 'from',
|
|
456
|
+
'with', 'this', 'that', 'it', 'code', 'file', 'files', 'fix', 'add', 'make', 'create',
|
|
457
|
+
'update', 'change', 'modify', 'edit', 'write', 'implement', 'bro', 'please', 'scout',
|
|
458
|
+
'agent', 'bug', 'error', 'issue', 'help', 'app', 'project', 'script', 'function',
|
|
459
|
+
'folder', 'dir', 'directory', 'testing', 'inside', 'open', 'vs', 'vscode', 'py', 'python',
|
|
460
|
+
'new', 'build', 'run', 'test', 'suite'
|
|
461
|
+
]);
|
|
462
|
+
const words = userGoal.split(/[\s,;:\/\\]+/).map((w) => w.replace(/["'(),;:!]/g, '').trim());
|
|
306
463
|
const keywords = words.filter((w) => w.length >= 3 && !stopWords.has(w.toLowerCase()));
|
|
307
464
|
for (const f of filesList) {
|
|
465
|
+
if (isIgnoredFile(f))
|
|
466
|
+
continue;
|
|
308
467
|
if (matchedFiles.length >= 3)
|
|
309
468
|
break;
|
|
310
469
|
const normF = f.replace(/\\/g, '/').toLowerCase();
|
|
@@ -334,7 +493,7 @@ When returning tool calls, use standard OpenAI function calling format or JSON t
|
|
|
334
493
|
: lines.slice(0, 100).map((l, i) => `${i + 1} | ${l}`).join('\n') + `\n... [truncated, file has ${lines.length} lines. Call read_file to view remaining lines]`;
|
|
335
494
|
this.historyMessages.push({
|
|
336
495
|
role: 'user',
|
|
337
|
-
content: `[Reference Workspace Context -
|
|
496
|
+
content: `[Reference Workspace Context - Pre-loaded target file "${file}" (${lines.length} lines)]:\n\`\`\`\n${previewSnippet}\n\`\`\`\nCRITICAL DIRECTIVE: The target file "${file}" is ALREADY fully pre-loaded into your reference context above. DO NOT call read_file or list_dir for this file again! Immediately begin calling write_file, edit_file, or run_command to satisfy the user goal.`,
|
|
338
497
|
});
|
|
339
498
|
}
|
|
340
499
|
catch {
|
|
@@ -353,9 +512,11 @@ When returning tool calls, use standard OpenAI function calling format or JSON t
|
|
|
353
512
|
console.log(chalk.gray(`Estimated completion time: ${estTimeText} (Max ${this.maxSteps} steps)`));
|
|
354
513
|
let step = 0;
|
|
355
514
|
let finalSummary = '';
|
|
515
|
+
let consecutiveReads = 0;
|
|
356
516
|
const stepDurations = [];
|
|
357
517
|
while (step < this.maxSteps) {
|
|
358
518
|
step++;
|
|
519
|
+
this.currentStep = step;
|
|
359
520
|
const stepStartTime = Date.now();
|
|
360
521
|
const avgStepMs = stepDurations.length > 0
|
|
361
522
|
? stepDurations.reduce((a, b) => a + b, 0) / stepDurations.length
|
|
@@ -400,6 +561,11 @@ When returning tool calls, use standard OpenAI function calling format or JSON t
|
|
|
400
561
|
}
|
|
401
562
|
const msg = sanitizeMessage(choice.message);
|
|
402
563
|
this.historyMessages.push(msg);
|
|
564
|
+
// Render assistant's thought/reasoning text if provided on this turn
|
|
565
|
+
const textContent = msg.content || '';
|
|
566
|
+
if (textContent.trim()) {
|
|
567
|
+
safeNote(renderMarkdown(textContent), `Scout Thought (Step ${step})`);
|
|
568
|
+
}
|
|
403
569
|
// Check native tool calls
|
|
404
570
|
if (msg.tool_calls && msg.tool_calls.length > 0) {
|
|
405
571
|
stepSpinner.stop(chalk.green(`Step ${step}: Scout issued ${msg.tool_calls.length} tool call(s).`));
|
|
@@ -417,6 +583,18 @@ When returning tool calls, use standard OpenAI function calling format or JSON t
|
|
|
417
583
|
tool_call_id: tc.id,
|
|
418
584
|
content: `Tool Execution Result (${fnName}):\n${toolResult.result}`,
|
|
419
585
|
});
|
|
586
|
+
if (fnName === 'read_file' || fnName === 'list_dir' || fnName === 'glob_search' || fnName === 'grep_search') {
|
|
587
|
+
consecutiveReads++;
|
|
588
|
+
if (consecutiveReads >= 2) {
|
|
589
|
+
this.historyMessages.push({
|
|
590
|
+
role: 'user',
|
|
591
|
+
content: 'SYSTEM ACTION NUDGE: You have issued multiple read/exploration tool calls in a row. Stop reading files now and execute write_file, edit_file, or run_command immediately to satisfy the user goal.',
|
|
592
|
+
});
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
else if (fnName === 'write_file' || fnName === 'edit_file' || fnName === 'run_command') {
|
|
596
|
+
consecutiveReads = 0;
|
|
597
|
+
}
|
|
420
598
|
if (fnName === 'task_completed') {
|
|
421
599
|
finalSummary = args.summary || toolResult.result;
|
|
422
600
|
if (this.modifiedFiles.size > 0) {
|
|
@@ -436,7 +614,6 @@ When returning tool calls, use standard OpenAI function calling format or JSON t
|
|
|
436
614
|
continue;
|
|
437
615
|
}
|
|
438
616
|
// Check text response or fallback JSON tool call
|
|
439
|
-
const textContent = msg.content || '';
|
|
440
617
|
stepSpinner.stop(chalk.blue(`Step ${step} thinking complete.`));
|
|
441
618
|
const parsedJsonTool = this.extractJsonToolCall(textContent);
|
|
442
619
|
if (parsedJsonTool) {
|
|
@@ -521,7 +698,11 @@ When returning tool calls, use standard OpenAI function calling format or JSON t
|
|
|
521
698
|
}
|
|
522
699
|
return { tool, args };
|
|
523
700
|
}
|
|
524
|
-
const knownTools = [
|
|
701
|
+
const knownTools = [
|
|
702
|
+
'read_file', 'write_file', 'edit_file', 'run_command', 'list_dir',
|
|
703
|
+
'grep_search', 'glob_search', 'tree_view', 'file_info', 'multi_edit_file',
|
|
704
|
+
'fetch_url', 'git_diff', 'ask_user', 'task_completed'
|
|
705
|
+
];
|
|
525
706
|
for (const toolName of knownTools) {
|
|
526
707
|
const tagRegex = new RegExp(`<${toolName}>([\\s\\S]*?)<\\/${toolName}>`, 'i');
|
|
527
708
|
const match = content.match(tagRegex);
|
|
@@ -550,8 +731,37 @@ When returning tool calls, use standard OpenAI function calling format or JSON t
|
|
|
550
731
|
return null;
|
|
551
732
|
}
|
|
552
733
|
async dispatchToolCall(name, args) {
|
|
553
|
-
|
|
554
|
-
|
|
734
|
+
this.totalToolCalls++;
|
|
735
|
+
let actionDesc = '';
|
|
736
|
+
if (name === 'read_file')
|
|
737
|
+
actionDesc = `Reading file: ${args.path || ''}`;
|
|
738
|
+
else if (name === 'write_file')
|
|
739
|
+
actionDesc = `Writing/Creating file: ${args.path || ''}`;
|
|
740
|
+
else if (name === 'edit_file')
|
|
741
|
+
actionDesc = `Editing file: ${args.path || ''}`;
|
|
742
|
+
else if (name === 'multi_edit_file')
|
|
743
|
+
actionDesc = `Multi-editing file: ${args.path || ''}`;
|
|
744
|
+
else if (name === 'run_command')
|
|
745
|
+
actionDesc = `Executing command: ${args.command || args.cmd || ''}`;
|
|
746
|
+
else if (name === 'list_dir')
|
|
747
|
+
actionDesc = `Listing directory: ${args.path || '.'}`;
|
|
748
|
+
else if (name === 'tree_view')
|
|
749
|
+
actionDesc = `Generating directory tree: ${args.path || '.'}`;
|
|
750
|
+
else if (name === 'file_info')
|
|
751
|
+
actionDesc = `Inspecting file stats: ${args.path || ''}`;
|
|
752
|
+
else if (name === 'grep_search')
|
|
753
|
+
actionDesc = `Searching pattern: "${args.query || args.pattern || ''}"`;
|
|
754
|
+
else if (name === 'glob_search')
|
|
755
|
+
actionDesc = `Finding files: "${args.pattern || ''}"`;
|
|
756
|
+
else if (name === 'fetch_url')
|
|
757
|
+
actionDesc = `Fetching URL: ${args.url || ''}`;
|
|
758
|
+
else if (name === 'git_diff')
|
|
759
|
+
actionDesc = `Retrieving workspace git diff`;
|
|
760
|
+
else if (name === 'task_completed')
|
|
761
|
+
actionDesc = `Task finished`;
|
|
762
|
+
const badge = chalk.bgCyan.black.bold(` 🛠️ TOOL: ${name} `);
|
|
763
|
+
const descText = actionDesc ? chalk.yellow.bold(` → ${actionDesc}`) : '';
|
|
764
|
+
console.log(`\n${badge}${descText} ${chalk.dim(JSON.stringify(args))}`);
|
|
555
765
|
switch (name) {
|
|
556
766
|
case 'read_file': {
|
|
557
767
|
const targetPath = String(args.path || '').trim();
|
|
@@ -748,6 +958,132 @@ When returning tool calls, use standard OpenAI function calling format or JSON t
|
|
|
748
958
|
const matched = allFiles.filter((f) => f.toLowerCase().includes(pattern) || path.basename(f).toLowerCase().endsWith(pattern.replace('*', '')));
|
|
749
959
|
return { result: `Glob search "${pattern}" matched ${matched.length} file(s):\n${matched.slice(0, 30).join('\n')}` };
|
|
750
960
|
}
|
|
961
|
+
case 'tree_view': {
|
|
962
|
+
const relPath = String(args.path || '.').trim();
|
|
963
|
+
const maxDepth = Number(args.maxDepth) || 3;
|
|
964
|
+
const fullDir = path.resolve(this.cwd, relPath);
|
|
965
|
+
if (!fs.existsSync(fullDir) || !fs.statSync(fullDir).isDirectory()) {
|
|
966
|
+
return { result: `Directory not found for tree_view: "${relPath}"` };
|
|
967
|
+
}
|
|
968
|
+
const treeStr = buildDirectoryTree(fullDir, '', 0, maxDepth);
|
|
969
|
+
return { result: `Directory Tree for "${relPath}":\n${relPath}/\n${treeStr}` };
|
|
970
|
+
}
|
|
971
|
+
case 'file_info': {
|
|
972
|
+
const targetPath = String(args.path || '').trim();
|
|
973
|
+
const fullPath = path.isAbsolute(targetPath) ? targetPath : path.resolve(this.cwd, targetPath);
|
|
974
|
+
if (!fs.existsSync(fullPath)) {
|
|
975
|
+
return { result: `Error: Path "${targetPath}" does not exist.` };
|
|
976
|
+
}
|
|
977
|
+
const stat = fs.statSync(fullPath);
|
|
978
|
+
if (stat.isDirectory()) {
|
|
979
|
+
const entries = fs.readdirSync(fullPath);
|
|
980
|
+
return { result: `Directory Stats: "${targetPath}"\n • Total Entries: ${entries.length}\n • Created: ${stat.birthtime.toISOString()}\n • Modified: ${stat.mtime.toISOString()}` };
|
|
981
|
+
}
|
|
982
|
+
const content = fs.readFileSync(fullPath, 'utf-8');
|
|
983
|
+
const lineCount = content.split('\n').length;
|
|
984
|
+
const ext = path.extname(targetPath) || 'none';
|
|
985
|
+
return {
|
|
986
|
+
result: `File Info: "${targetPath}"\n` +
|
|
987
|
+
` • Size: ${(stat.size / 1024).toFixed(2)} KB (${stat.size} bytes)\n` +
|
|
988
|
+
` • Total Lines: ${lineCount}\n` +
|
|
989
|
+
` • Extension: ${ext}\n` +
|
|
990
|
+
` • Last Modified: ${stat.mtime.toISOString()}\n` +
|
|
991
|
+
` • Absolute Path: ${fullPath}`
|
|
992
|
+
};
|
|
993
|
+
}
|
|
994
|
+
case 'multi_edit_file': {
|
|
995
|
+
const targetPath = String(args.path || '').trim();
|
|
996
|
+
const edits = Array.isArray(args.edits) ? args.edits : [];
|
|
997
|
+
const reason = String(args.reason || args.description || 'Applied multiple code snippet edits.');
|
|
998
|
+
if (edits.length === 0) {
|
|
999
|
+
return { result: `Error: No edits provided for multi_edit_file on "${targetPath}".` };
|
|
1000
|
+
}
|
|
1001
|
+
const fullPath = path.isAbsolute(targetPath) ? targetPath : path.resolve(this.cwd, targetPath);
|
|
1002
|
+
if (!fs.existsSync(fullPath)) {
|
|
1003
|
+
return { result: `Error: File "${targetPath}" does not exist.` };
|
|
1004
|
+
}
|
|
1005
|
+
let currentContent = fs.readFileSync(fullPath, 'utf-8');
|
|
1006
|
+
const origContent = currentContent;
|
|
1007
|
+
let appliedCount = 0;
|
|
1008
|
+
const errors = [];
|
|
1009
|
+
for (let i = 0; i < edits.length; i++) {
|
|
1010
|
+
const ed = edits[i];
|
|
1011
|
+
let target = String(ed.target || '');
|
|
1012
|
+
let replacement = String(ed.replacement || '');
|
|
1013
|
+
if (target.includes('\\n') && !target.includes('\n'))
|
|
1014
|
+
target = target.replace(/\\n/g, '\n').replace(/\\t/g, '\t');
|
|
1015
|
+
if (replacement.includes('\\n') && !replacement.includes('\n'))
|
|
1016
|
+
replacement = replacement.replace(/\\n/g, '\n').replace(/\\t/g, '\t');
|
|
1017
|
+
const replaceRes = robustSnippetReplace(currentContent, target, replacement);
|
|
1018
|
+
if (replaceRes.success) {
|
|
1019
|
+
currentContent = replaceRes.updatedContent;
|
|
1020
|
+
appliedCount++;
|
|
1021
|
+
}
|
|
1022
|
+
else {
|
|
1023
|
+
errors.push(`Edit #${i + 1} failed: ${replaceRes.error}`);
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
if (appliedCount === 0) {
|
|
1027
|
+
return { result: `Error: All ${edits.length} multi-edits failed on "${targetPath}":\n${errors.join('\n')}` };
|
|
1028
|
+
}
|
|
1029
|
+
this.backups.push({ filePath: targetPath, originalContent: origContent });
|
|
1030
|
+
fs.writeFileSync(fullPath, currentContent, 'utf-8');
|
|
1031
|
+
this.modifiedFiles.add(targetPath);
|
|
1032
|
+
safeNote(` • File: ${chalk.bold.cyan(targetPath)}\n` +
|
|
1033
|
+
` • Multi-edits applied: ${chalk.bold.green(appliedCount + '/' + edits.length)}\n` +
|
|
1034
|
+
` • Why: ${reason}`, ` Applied ${appliedCount} edits to ${targetPath}`);
|
|
1035
|
+
const syntaxCheck = await checkFileSyntax(targetPath, this.cwd);
|
|
1036
|
+
let syntaxNotice = syntaxCheck.valid ? 'Syntax check passed cleanly.' : `\n\n️ AUTOMATED SYNTAX ERROR DETECTED:\n${syntaxCheck.errors.join('\n')}`;
|
|
1037
|
+
return {
|
|
1038
|
+
result: `Successfully applied ${appliedCount}/${edits.length} edit snippet(s) to "${targetPath}". Rationale: ${reason}.${errors.length > 0 ? ` Warnings: ${errors.join('; ')}` : ''} ${syntaxNotice}`
|
|
1039
|
+
};
|
|
1040
|
+
}
|
|
1041
|
+
case 'fetch_url': {
|
|
1042
|
+
const targetUrl = String(args.url || '').trim();
|
|
1043
|
+
if (!targetUrl.startsWith('http://') && !targetUrl.startsWith('https://')) {
|
|
1044
|
+
return { result: `Error: Invalid URL scheme. URL must start with http:// or https://` };
|
|
1045
|
+
}
|
|
1046
|
+
const fetchSpin = spinner();
|
|
1047
|
+
fetchSpin.start(chalk.cyan(` Fetching web URL ${targetUrl}...`));
|
|
1048
|
+
try {
|
|
1049
|
+
const resp = await fetch(targetUrl, { headers: { 'User-Agent': 'Scout-CLI-Agent/6.0' } });
|
|
1050
|
+
if (!resp.ok) {
|
|
1051
|
+
fetchSpin.stop(chalk.red(`Failed to fetch URL: HTTP ${resp.status} ${resp.statusText}`));
|
|
1052
|
+
return { result: `HTTP Error ${resp.status}: ${resp.statusText}` };
|
|
1053
|
+
}
|
|
1054
|
+
let bodyText = await resp.text();
|
|
1055
|
+
fetchSpin.stop(chalk.green(` Fetched URL ${targetUrl} (${bodyText.length} chars)`));
|
|
1056
|
+
if (bodyText.includes('<html') || bodyText.includes('<body')) {
|
|
1057
|
+
bodyText = bodyText
|
|
1058
|
+
.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '')
|
|
1059
|
+
.replace(/<style\b[^<]*(?:(?!<\/style>)<[^<]*)*<\/style>/gi, '')
|
|
1060
|
+
.replace(/<[^>]+>/g, ' ')
|
|
1061
|
+
.replace(/\s+/g, ' ')
|
|
1062
|
+
.trim();
|
|
1063
|
+
}
|
|
1064
|
+
const preview = bodyText.slice(0, 6000) + (bodyText.length > 6000 ? `\n... [truncated, ${bodyText.length} total chars]` : '');
|
|
1065
|
+
return { result: `Content fetched from ${targetUrl}:\n\n${preview}` };
|
|
1066
|
+
}
|
|
1067
|
+
catch (err) {
|
|
1068
|
+
fetchSpin.stop(chalk.red(`Fetch error for ${targetUrl}: ${err?.message || String(err)}`));
|
|
1069
|
+
return { result: `Fetch error: ${err?.message || String(err)}` };
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
case 'git_diff': {
|
|
1073
|
+
const targetPath = args.path ? String(args.path).trim() : '';
|
|
1074
|
+
try {
|
|
1075
|
+
const { execSync } = await import('child_process');
|
|
1076
|
+
const diffCmd = targetPath ? `git diff -- "${targetPath}"` : 'git diff';
|
|
1077
|
+
const diffOutput = execSync(diffCmd, { encoding: 'utf-8', cwd: this.cwd });
|
|
1078
|
+
if (!diffOutput || !diffOutput.trim()) {
|
|
1079
|
+
return { result: 'No uncommitted git changes found in workspace.' };
|
|
1080
|
+
}
|
|
1081
|
+
return { result: `Git Diff Output:\n${diffOutput.slice(0, 8000)}` };
|
|
1082
|
+
}
|
|
1083
|
+
catch (err) {
|
|
1084
|
+
return { result: `Could not retrieve git diff: ${err?.message || String(err)}` };
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
751
1087
|
case 'ask_user': {
|
|
752
1088
|
const questionText = String(args.question || args.prompt || 'Please provide input:').trim();
|
|
753
1089
|
const askSpin = spinner();
|