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.
Files changed (65) hide show
  1. package/.firebase/hosting.d2Vi.cache +2 -2
  2. package/README.md +61 -38
  3. package/bin/ft.js +1 -1
  4. package/bin/src/commands/agent.d.ts.map +1 -1
  5. package/bin/src/commands/agent.js +198 -27
  6. package/bin/src/commands/agent.js.map +1 -1
  7. package/bin/src/commands/audit.js.map +1 -1
  8. package/bin/src/commands/authCommand.js.map +1 -1
  9. package/bin/src/commands/brief.js.map +1 -1
  10. package/bin/src/commands/configCommand.d.ts.map +1 -1
  11. package/bin/src/commands/configCommand.js.map +1 -1
  12. package/bin/src/commands/dashboard.js.map +1 -1
  13. package/bin/src/commands/history.js.map +1 -1
  14. package/bin/src/commands/init.js.map +1 -1
  15. package/bin/src/commands/login.d.ts.map +1 -1
  16. package/bin/src/commands/login.js.map +1 -1
  17. package/bin/src/commands/logout.js.map +1 -1
  18. package/bin/src/commands/owners.js.map +1 -1
  19. package/bin/src/commands/quota.js.map +1 -1
  20. package/bin/src/commands/recommend.js.map +1 -1
  21. package/bin/src/commands/risky.d.ts.map +1 -1
  22. package/bin/src/commands/risky.js.map +1 -1
  23. package/bin/src/commands/setup.js.map +1 -1
  24. package/bin/src/commands/signup.d.ts.map +1 -1
  25. package/bin/src/commands/signup.js.map +1 -1
  26. package/bin/src/commands/undo.js.map +1 -1
  27. package/bin/src/commands/web.js.map +1 -1
  28. package/bin/src/discovery/config.d.ts.map +1 -1
  29. package/bin/src/discovery/config.js.map +1 -1
  30. package/bin/src/discovery/display.js.map +1 -1
  31. package/bin/src/discovery/messages.js.map +1 -1
  32. package/bin/src/discovery/signals.d.ts.map +1 -1
  33. package/bin/src/discovery/signals.js.map +1 -1
  34. package/bin/src/engine/agentEngine.d.ts.map +1 -1
  35. package/bin/src/engine/agentEngine.js +369 -33
  36. package/bin/src/engine/agentEngine.js.map +1 -1
  37. package/bin/src/engine/git.d.ts.map +1 -1
  38. package/bin/src/engine/git.js.map +1 -1
  39. package/bin/src/engine/llm.d.ts.map +1 -1
  40. package/bin/src/engine/llm.js +57 -6
  41. package/bin/src/engine/llm.js.map +1 -1
  42. package/bin/src/engine/verifier.d.ts.map +1 -1
  43. package/bin/src/engine/verifier.js.map +1 -1
  44. package/bin/src/engine/web.d.ts.map +1 -1
  45. package/bin/src/engine/web.js.map +1 -1
  46. package/bin/src/index.js +4 -4
  47. package/bin/src/index.js.map +1 -1
  48. package/bin/src/server/server.js.map +1 -1
  49. package/bin/src/utils/auth.d.ts.map +1 -1
  50. package/bin/src/utils/auth.js.map +1 -1
  51. package/bin/src/utils/branding.js +7 -7
  52. package/bin/src/utils/branding.js.map +1 -1
  53. package/bin/src/utils/config.d.ts.map +1 -1
  54. package/bin/src/utils/config.js.map +1 -1
  55. package/bin/src/utils/email.d.ts.map +1 -1
  56. package/bin/src/utils/email.js.map +1 -1
  57. package/bin/src/utils/firebaseAuth.d.ts.map +1 -1
  58. package/bin/src/utils/firebaseAuth.js.map +1 -1
  59. package/bin/src/utils/holoseneAuth.d.ts.map +1 -1
  60. package/bin/src/utils/holoseneAuth.js.map +1 -1
  61. package/bin/src/utils/language.d.ts.map +1 -1
  62. package/bin/src/utils/language.js.map +1 -1
  63. package/bin/src/utils/markdown.js.map +1 -1
  64. package/bin/src/utils/updateCheck.js.map +1 -1
  65. 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;IACvE,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;QAC9E,MAAM,iBAAiB,GAAG,WAAW,CAAC,iBAAiB,IAAI,CAAC,CAAC;QAC7D,OAAO,CAAC,IAAI,CAAC;YACb,EAAE,EAAE,oBAAoB;YACxB,OAAO,EAAE,+CAA+C,iBAAiB,4CAA4C;SACpH,CAAC,CAAC;IACH,CAAC;IAED,0BAA0B;IAC1B,IAAI,WAAW,CAAC,qBAAqB,EAAE,CAAC;QACxC,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,GAAG,WAAW,CAAC,qBAAqB,CAAC;QACnE,OAAO,CAAC,IAAI,CAAC;YACb,EAAE,EAAE,eAAe;YACnB,OAAO,EAAE,wBAAwB,QAAQ,oBAAoB,UAAU,gCAAgC;SACtG,CAAC,CAAC;IACH,CAAC;IAED,+BAA+B;IAC/B,IAAI,WAAW,KAAK,SAAS,IAAI,WAAW,GAAG,IAAI,EAAE,CAAC;QACtD,MAAM,eAAe,GAAG,WAAW,CAAC,eAAe,IAAI,IAAI,CAAC,KAAK,CAAC,WAAW,GAAG,GAAG,CAAC,CAAC;QACrF,OAAO,CAAC,IAAI,CAAC;YACb,EAAE,EAAE,oBAAoB;YACxB,OAAO,EAAE,4CAA4C,eAAe,+CAA+C;SAClH,CAAC,CAAC;IACH,CAAC;IAED,OAAO,OAAO,CAAC;AAChB,CAAC"}
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,CACnC,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,GACjB;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,cAAc,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAwG9D;AAED,MAAM,WAAW,cAAc;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE;QACZ,IAAI,EAAE,MAAM,CAAC;QACb,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QAChC,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;KACnB,CAAC;CACF;AAED,eAAO,MAAM,WAAW,EAAE,cAAc,EA2GvC,CAAC;AAEF,MAAM,WAAW,gBAAgB;IAChC,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;CACjC;AAED,qBAAa,kBAAkB;IAC9B,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;IAE5B,YAAY,OAAO,CAAC,EAAE,gBAAgB,EAKrC;IAEM,gBAAgB,IAAI,MAAM,EAAE,CAElC;IAEM,UAAU,IAAI,UAAU,EAAE,CAEhC;IAEM,YAAY,IAAI,IAAI,CAI1B;IAED,OAAO,CAAC,YAAY;IA0CP,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAiRzF;IAED,OAAO,CAAC,mBAAmB;YA2Cb,gBAAgB;CAyQ9B"}
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. 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.
252
- 5. 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.
253
- 6. 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.
254
- 7. 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.
255
- 8. FROM-SCRATCH PROJECT CREATION & AUTOMATED TESTING PROTOCOL: When asked to build/create a project from scratch:
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
- 9. 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.
262
- 10. 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.
263
- 11. 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.
264
- 12. EDITING & DEBUGGING MASTERY PROTOCOL:
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
- // Autonomous Target File Discovery: Find relevant workspace files matching terms in userGoal
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
- // 1. Check direct path or filename matches
294
- for (const word of words) {
295
- if (word && word.includes('.') && !word.startsWith('.')) {
296
- const fullP = path.resolve(this.cwd, word);
297
- if (fs.existsSync(fullP) && fs.statSync(fullP).isFile()) {
298
- const relP = path.relative(this.cwd, fullP).replace(/\\/g, '/');
299
- if (!matchedFiles.includes(relP))
300
- matchedFiles.push(relP);
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
- // 2. If no direct path matched, search workspace file names for high-confidence keyword matches
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 - Auto-discovered file "${file}" (${lines.length} lines)]:\n\`\`\`\n${previewSnippet}\n\`\`\`\nNote: Use this complete file content as reference context for editing, debugging, or fulfilling the user goal.`,
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 = ['read_file', 'write_file', 'edit_file', 'run_command', 'list_dir', 'grep_search', 'glob_search', 'ask_user', 'task_completed'];
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
- const badge = chalk.bgCyan.black.bold(` ️ TOOL: ${name} `);
554
- console.log(`\n${badge} ${chalk.dim(JSON.stringify(args))}`);
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();