@probelabs/probe 0.6.0-rc254 → 0.6.0-rc256

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 (53) hide show
  1. package/README.md +166 -3
  2. package/bin/binaries/probe-v0.6.0-rc256-aarch64-apple-darwin.tar.gz +0 -0
  3. package/bin/binaries/probe-v0.6.0-rc256-aarch64-unknown-linux-musl.tar.gz +0 -0
  4. package/bin/binaries/probe-v0.6.0-rc256-x86_64-apple-darwin.tar.gz +0 -0
  5. package/bin/binaries/probe-v0.6.0-rc256-x86_64-pc-windows-msvc.zip +0 -0
  6. package/bin/binaries/probe-v0.6.0-rc256-x86_64-unknown-linux-musl.tar.gz +0 -0
  7. package/build/agent/ProbeAgent.d.ts +1 -1
  8. package/build/agent/ProbeAgent.js +39 -23
  9. package/build/agent/acp/tools.js +2 -1
  10. package/build/agent/acp/tools.test.js +2 -1
  11. package/build/agent/bashDefaults.js +75 -6
  12. package/build/agent/index.js +1752 -426
  13. package/build/agent/mcp/xmlBridge.js +3 -2
  14. package/build/agent/schemaUtils.js +127 -0
  15. package/build/agent/tools.js +0 -28
  16. package/build/delegate.js +3 -0
  17. package/build/index.js +2 -0
  18. package/build/tools/common.js +26 -8
  19. package/build/tools/edit.js +457 -65
  20. package/build/tools/fileTracker.js +318 -0
  21. package/build/tools/fuzzyMatch.js +271 -0
  22. package/build/tools/hashline.js +131 -0
  23. package/build/tools/lineEditHeuristics.js +138 -0
  24. package/build/tools/symbolEdit.js +119 -0
  25. package/build/tools/vercel.js +40 -9
  26. package/cjs/agent/ProbeAgent.cjs +1863 -528
  27. package/cjs/index.cjs +1891 -554
  28. package/index.d.ts +189 -1
  29. package/package.json +1 -1
  30. package/src/agent/ProbeAgent.d.ts +1 -1
  31. package/src/agent/ProbeAgent.js +39 -23
  32. package/src/agent/acp/tools.js +2 -1
  33. package/src/agent/acp/tools.test.js +2 -1
  34. package/src/agent/bashDefaults.js +75 -6
  35. package/src/agent/index.js +18 -7
  36. package/src/agent/mcp/xmlBridge.js +3 -2
  37. package/src/agent/schemaUtils.js +127 -0
  38. package/src/agent/tools.js +0 -28
  39. package/src/delegate.js +3 -0
  40. package/src/index.js +2 -0
  41. package/src/tools/common.js +26 -8
  42. package/src/tools/edit.js +457 -65
  43. package/src/tools/fileTracker.js +318 -0
  44. package/src/tools/fuzzyMatch.js +271 -0
  45. package/src/tools/hashline.js +131 -0
  46. package/src/tools/lineEditHeuristics.js +138 -0
  47. package/src/tools/symbolEdit.js +119 -0
  48. package/src/tools/vercel.js +40 -9
  49. package/bin/binaries/probe-v0.6.0-rc254-aarch64-apple-darwin.tar.gz +0 -0
  50. package/bin/binaries/probe-v0.6.0-rc254-aarch64-unknown-linux-musl.tar.gz +0 -0
  51. package/bin/binaries/probe-v0.6.0-rc254-x86_64-apple-darwin.tar.gz +0 -0
  52. package/bin/binaries/probe-v0.6.0-rc254-x86_64-pc-windows-msvc.zip +0 -0
  53. package/bin/binaries/probe-v0.6.0-rc254-x86_64-unknown-linux-musl.tar.gz +0 -0
package/README.md CHANGED
@@ -23,6 +23,7 @@ During installation, the package will automatically download the appropriate pro
23
23
  - **Search Code**: Search for patterns in your codebase using Elasticsearch-like query syntax
24
24
  - **Query Code**: Find specific code structures using tree-sitter patterns
25
25
  - **Extract Code**: Extract code blocks from files based on file paths and line numbers
26
+ - **Edit Code**: AI-powered code editing with text replacement (fuzzy matching), AST-aware symbol replace/insert across 16 languages, and line-targeted editing with optional hash-based integrity verification
26
27
  - **AI Tools Integration**: Ready-to-use tools for Vercel AI SDK, LangChain, and other AI frameworks
27
28
  - **System Message**: Default system message for AI assistants with instructions on using probe tools
28
29
  - **Cross-Platform**: Works on Windows, macOS, and Linux
@@ -91,7 +92,7 @@ const agent = new ProbeAgent({
91
92
  path: '/path/to/your/project',
92
93
  provider: 'anthropic', // or 'openai', 'google'
93
94
  model: 'claude-3-5-sonnet-20241022', // Optional: override model
94
- allowEdit: false, // Optional: enable code modification
95
+ allowEdit: true, // Optional: enable edit + create tools for code modification
95
96
  debug: true, // Optional: enable debug logging
96
97
  allowedTools: ['*'], // Optional: filter available tools (see Tool Filtering below)
97
98
  enableMcp: true, // Optional: enable MCP tool integration
@@ -292,8 +293,7 @@ const agent5 = new ProbeAgent({
292
293
  - `listFiles` - List files and directories
293
294
  - `searchFiles` - Find files by glob pattern
294
295
  - `bash` - Execute bash commands (requires `enableBash: true`)
295
- - `implement` - Implement features with aider (requires `allowEdit: true`)
296
- - `edit` - Edit files with exact string replacement (requires `allowEdit: true`)
296
+ - `edit` - Edit files with text replacement, AST-aware symbol editing, or line-targeted editing (requires `allowEdit: true`)
297
297
  - `create` - Create new files (requires `allowEdit: true`)
298
298
  - `delegate` - Delegate tasks to subagents (requires `enableDelegate: true`)
299
299
  - `attempt_completion` - Signal task completion
@@ -327,6 +327,168 @@ probe agent "Analyze the architecture" --allowed-tools all
327
327
  - Blocked tools will not appear in the system message and cannot be executed
328
328
  - Use `allowedTools: []` for pure conversational AI without code analysis tools
329
329
 
330
+ ### Code Editing (Edit & Create Tools)
331
+
332
+ Probe provides built-in code modification tools that work across all interfaces. The `edit` tool supports four modes: text-based find/replace with fuzzy matching, AST-aware symbol replacement, symbol insertion, and line-targeted editing with optional hash-based integrity verification.
333
+
334
+ #### Enabling Edit Tools
335
+
336
+ **CLI Agent:**
337
+ ```bash
338
+ # Enable with --allow-edit flag
339
+ probe agent "Fix the bug in auth.js" --allow-edit --path ./my-project
340
+
341
+ # Enable with environment variable
342
+ ALLOW_EDIT=1 probe agent "Refactor the login flow" --path ./my-project
343
+ ```
344
+
345
+ **SDK (ProbeAgent):**
346
+ ```javascript
347
+ import { ProbeAgent } from '@probelabs/probe';
348
+
349
+ const agent = new ProbeAgent({
350
+ path: '/path/to/project',
351
+ allowEdit: true, // enables edit + create tools
352
+ // hashLines defaults to true when allowEdit is true (line integrity verification)
353
+ provider: 'anthropic'
354
+ });
355
+
356
+ // The agent can now modify files when answering questions
357
+ const answer = await agent.answer("Fix the off-by-one error in calculateTotal");
358
+ ```
359
+
360
+ **SDK (Standalone Tools):**
361
+ ```javascript
362
+ import { editTool, createTool } from '@probelabs/probe';
363
+
364
+ const edit = editTool({
365
+ allowedFolders: ['/path/to/project'],
366
+ cwd: '/path/to/project'
367
+ });
368
+
369
+ const create = createTool({
370
+ allowedFolders: ['/path/to/project'],
371
+ cwd: '/path/to/project'
372
+ });
373
+ ```
374
+
375
+ #### Edit Tool Modes
376
+
377
+ | Mode | Parameters | Use Case |
378
+ |------|-----------|----------|
379
+ | **Text edit** | `old_string` + `new_string` | Small, precise changes: fix a condition, rename a variable, update a value |
380
+ | **Symbol replace** | `symbol` + `new_string` | Replace an entire function/class/method by name (AST-aware, 16 languages) |
381
+ | **Symbol insert** | `symbol` + `new_string` + `position` | Insert new code before or after an existing symbol |
382
+ | **Line-targeted edit** | `start_line` + `new_string` | Edit specific lines from extract/search output; ideal for changes inside large functions |
383
+
384
+ #### Edit Tool Parameters
385
+
386
+ | Parameter | Required | Description |
387
+ |-----------|----------|-------------|
388
+ | `file_path` | Yes | Path to the file to edit (absolute or relative to cwd) |
389
+ | `new_string` | Yes | Replacement text or new code content |
390
+ | `old_string` | No | Text to find and replace. Copy verbatim from the file. |
391
+ | `replace_all` | No | Replace all occurrences (default: `false`, text mode only) |
392
+ | `symbol` | No | Code symbol name for AST-aware editing (e.g. `"myFunction"`, `"MyClass.myMethod"`) |
393
+ | `position` | No | `"before"` or `"after"` — insert near the symbol or line instead of replacing it |
394
+ | `start_line` | No | Line reference for line-targeted editing (e.g. `"42"` or `"42:ab"` with hash) |
395
+ | `end_line` | No | End of line range, inclusive (e.g. `"55"` or `"55:cd"`). Defaults to `start_line`. |
396
+
397
+ #### Examples
398
+
399
+ **Text edit — find and replace:**
400
+ ```javascript
401
+ await edit.execute({
402
+ file_path: 'src/main.js',
403
+ old_string: 'return false;',
404
+ new_string: 'return true;'
405
+ });
406
+ // => "Successfully edited src/main.js (1 replacement)"
407
+ ```
408
+
409
+ **Text edit — fuzzy matching handles whitespace differences:**
410
+ ```javascript
411
+ // File has: " const x = 1;" (2-space indent)
412
+ // Your search string omits the indent — fuzzy matching finds it anyway
413
+ await edit.execute({
414
+ file_path: 'src/main.js',
415
+ old_string: 'const x = 1;',
416
+ new_string: ' const x = 2;'
417
+ });
418
+ // => "Successfully edited src/main.js (1 replacement, matched via line-trimmed)"
419
+ ```
420
+
421
+ **Symbol replace — rewrite a function by name:**
422
+ ```javascript
423
+ await edit.execute({
424
+ file_path: 'src/utils.js',
425
+ symbol: 'calculateTotal',
426
+ new_string: `function calculateTotal(items) {
427
+ return items.reduce((sum, item) => sum + item.price * item.quantity, 0);
428
+ }`
429
+ });
430
+ // => "Successfully replaced symbol "calculateTotal" in src/utils.js (was lines 10-15, now 3 lines)"
431
+ ```
432
+
433
+ **Symbol insert — add a new function after an existing one:**
434
+ ```javascript
435
+ await edit.execute({
436
+ file_path: 'src/utils.js',
437
+ symbol: 'calculateTotal',
438
+ position: 'after',
439
+ new_string: `function calculateTax(total, rate) {
440
+ return total * rate;
441
+ }`
442
+ });
443
+ // => "Successfully inserted 3 lines after symbol "calculateTotal" in src/utils.js (at line 16)"
444
+ ```
445
+
446
+ **Line-targeted edit — edit specific lines from extract output:**
447
+ ```javascript
448
+ // After extracting "src/order.js#processOrder" and seeing lines 143-145
449
+ await edit.execute({
450
+ file_path: 'src/order.js',
451
+ start_line: '143',
452
+ end_line: '145',
453
+ new_string: ' const items = order.items.filter(i => i.active);'
454
+ });
455
+ // => "Successfully edited src/order.js (lines 143-145 replaced with 1 line)"
456
+ ```
457
+
458
+ **Line-targeted edit — with hash integrity verification:**
459
+ ```javascript
460
+ // Use the hash from extract output (e.g. "143:cd") for verification
461
+ await edit.execute({
462
+ file_path: 'src/order.js',
463
+ start_line: '143:cd',
464
+ new_string: ' const items = order.items.filter(i => i.active);'
465
+ });
466
+ // If hash matches: edit succeeds
467
+ // If hash doesn't match: error with updated reference for retry
468
+ ```
469
+
470
+ **Create a new file:**
471
+ ```javascript
472
+ await create.execute({
473
+ file_path: 'src/newModule.js',
474
+ content: `export function greet(name) {
475
+ return \`Hello, \${name}!\`;
476
+ }`
477
+ });
478
+ // => "Successfully created src/newModule.js (52 bytes)"
479
+ ```
480
+
481
+ #### Key Features
482
+
483
+ - **Fuzzy matching**: Text mode automatically handles minor whitespace and indentation differences between your search string and the actual file content. Four strategies are tried in order: exact match, line-trimmed, whitespace-normalized, indent-flexible.
484
+ - **AST-aware symbol editing**: Symbol mode uses tree-sitter to locate functions, classes, and methods by name across 16 languages (JavaScript, TypeScript, Python, Rust, Go, Java, C, C++, Ruby, PHP, Swift, Kotlin, Scala, C#, Lua, Zig).
485
+ - **Auto-indentation**: Symbol mode automatically adjusts the indentation of `new_string` to match the original symbol's indentation level.
486
+ - **Line-targeted editing**: Use line numbers from `extract`/`search` output to make precise edits inside large functions. Optional hash-based integrity verification detects stale references.
487
+ - **Extract→Edit workflow**: Extract a function by symbol name (e.g. `"file.js#myFunction"`), then use the line numbers from the output to surgically edit specific lines with `start_line`/`end_line`.
488
+ - **File state tracking**: Automatic per-session tracking ensures edits are never based on stale content. Files read via `search`/`extract` are tracked; edits to unread or modified files are blocked with recovery instructions.
489
+ - **Self-healing errors**: All error messages include specific recovery instructions telling you (or the LLM) exactly how to fix the call and retry.
490
+ - **Security**: Path traversal attacks are blocked — all file operations are restricted to `allowedFolders`.
491
+
330
492
  ### Using as an MCP Server
331
493
 
332
494
  Probe includes a built-in MCP (Model Context Protocol) server for integration with AI assistants:
@@ -844,6 +1006,7 @@ probe mcp --timeout 60
844
1006
 
845
1007
  ## Additional Documentation
846
1008
 
1009
+ - [Edit & Create Tools](./docs/EDIT_CREATE_TOOLS.md) - Code editing with fuzzy matching, AST-aware symbol editing, and file creation
847
1010
  - [Context Window Compaction](./CONTEXT_COMPACTION.md) - Automatic conversation history compression
848
1011
  - [MCP Integration](./MCP_INTEGRATION_SUMMARY.md) - Model Context Protocol support details
849
1012
  - [Delegate Tool](./DELEGATE_TOOL_README.md) - Task distribution to subagents
@@ -35,7 +35,7 @@ export interface ProbeAgentOptions {
35
35
  systemPrompt?: string;
36
36
  /** Predefined prompt type (persona) */
37
37
  promptType?: 'code-explorer' | 'code-searcher' | 'engineer' | 'code-review' | 'support' | 'architect';
38
- /** Allow the use of the 'implement' tool for code editing */
38
+ /** Allow the use of the 'edit' and 'create' tools for code editing */
39
39
  allowEdit?: boolean;
40
40
  /** Enable the delegate tool for task distribution to subagents */
41
41
  enableDelegate?: boolean;
@@ -57,7 +57,6 @@ import {
57
57
  useSkillToolDefinition,
58
58
  readImageToolDefinition,
59
59
  attemptCompletionToolDefinition,
60
- implementToolDefinition,
61
60
  editToolDefinition,
62
61
  createToolDefinition,
63
62
  googleSearchToolDefinition,
@@ -66,6 +65,7 @@ import {
66
65
  parseXmlToolCallWithThinking
67
66
  } from './tools.js';
68
67
  import { createMessagePreview, detectUnrecognizedToolCall, detectStuckResponse, areBothStuckResponses } from '../tools/common.js';
68
+ import { FileTracker } from '../tools/fileTracker.js';
69
69
  import {
70
70
  createWrappedTools,
71
71
  listFilesToolInstance,
@@ -83,7 +83,8 @@ import {
83
83
  isJsonSchemaDefinition,
84
84
  createSchemaDefinitionCorrectionPrompt,
85
85
  validateAndFixMermaidResponse,
86
- tryAutoWrapForSimpleSchema
86
+ tryAutoWrapForSimpleSchema,
87
+ tryExtractValidJsonPrefix
87
88
  } from './schemaUtils.js';
88
89
  import { removeThinkingTags, extractThinkingContent } from './xmlParsingUtils.js';
89
90
  import { predefinedPrompts } from './shared/prompts.js';
@@ -178,7 +179,7 @@ export class ProbeAgent {
178
179
  * @param {string} [options.customPrompt] - Custom prompt to replace the default system message
179
180
  * @param {string} [options.systemPrompt] - Alias for customPrompt; takes precedence when both are provided
180
181
  * @param {string} [options.promptType] - Predefined prompt type (code-explorer, code-searcher, architect, code-review, support)
181
- * @param {boolean} [options.allowEdit=false] - Allow the use of the 'implement' tool
182
+ * @param {boolean} [options.allowEdit=false] - Allow the use of the 'edit' and 'create' tools
182
183
  * @param {boolean} [options.enableDelegate=false] - Enable the delegate tool for task distribution to subagents
183
184
  * @param {boolean} [options.enableExecutePlan=false] - Enable the execute_plan DSL orchestration tool
184
185
  * @param {string} [options.architectureFileName] - Architecture context filename to embed from repo root (defaults to AGENTS.md with CLAUDE.md fallback; ARCHITECTURE.md is always included when present)
@@ -229,6 +230,7 @@ export class ProbeAgent {
229
230
  this.customPrompt = options.systemPrompt || options.customPrompt || null;
230
231
  this.promptType = options.promptType || 'code-explorer';
231
232
  this.allowEdit = !!options.allowEdit;
233
+ this.hashLines = options.hashLines !== undefined ? !!options.hashLines : this.allowEdit;
232
234
  this.enableDelegate = !!options.enableDelegate;
233
235
  this.enableExecutePlan = !!options.enableExecutePlan;
234
236
  this.debug = options.debug || process.env.DEBUG === '1';
@@ -328,7 +330,8 @@ export class ProbeAgent {
328
330
  if (this.debug) {
329
331
  console.log(`[DEBUG] Generated session ID for agent: ${this.sessionId}`);
330
332
  console.log(`[DEBUG] Maximum tool iterations configured: ${MAX_TOOL_ITERATIONS}`);
331
- console.log(`[DEBUG] Allow Edit (implement tool): ${this.allowEdit}`);
333
+ console.log(`[DEBUG] Allow Edit: ${this.allowEdit}`);
334
+ console.log(`[DEBUG] Hash Lines: ${this.hashLines}`);
332
335
  console.log(`[DEBUG] Search delegation enabled: ${this.searchDelegate}`);
333
336
  console.log(`[DEBUG] Workspace root: ${this.workspaceRoot}`);
334
337
  console.log(`[DEBUG] Working directory (cwd): ${this.cwd}`);
@@ -831,9 +834,12 @@ export class ProbeAgent {
831
834
  cwd: this.cwd,
832
835
  workspaceRoot: this.workspaceRoot,
833
836
  allowedFolders: this.allowedFolders,
837
+ // File state tracking for safe multi-edit workflows (only when editing is enabled)
838
+ fileTracker: this.allowEdit ? new FileTracker({ debug: this.debug }) : null,
834
839
  outline: this.outline,
835
840
  searchDelegate: this.searchDelegate,
836
841
  allowEdit: this.allowEdit,
842
+ hashLines: this.hashLines,
837
843
  enableDelegate: this.enableDelegate,
838
844
  enableExecutePlan: this.enableExecutePlan,
839
845
  enableBash: this.enableBash,
@@ -2553,16 +2559,12 @@ ${extractGuidance}
2553
2559
  }
2554
2560
 
2555
2561
  // Edit tools (require both allowEdit flag AND allowedTools permission)
2556
- if (this.allowEdit && isToolAllowed('implement')) {
2557
- toolDefinitions += `${implementToolDefinition}\n`;
2558
- }
2559
2562
  if (this.allowEdit && isToolAllowed('edit')) {
2560
2563
  toolDefinitions += `${editToolDefinition}\n`;
2561
2564
  }
2562
2565
  if (this.allowEdit && isToolAllowed('create')) {
2563
2566
  toolDefinitions += `${createToolDefinition}\n`;
2564
2567
  }
2565
-
2566
2568
  // Bash tool (require both enableBash flag AND allowedTools permission)
2567
2569
  if (this.enableBash && isToolAllowed('bash')) {
2568
2570
  toolDefinitions += `${bashToolDefinition}\n`;
@@ -2645,7 +2647,7 @@ The configuration is loaded from src/config.js lines 15-25 which contains the da
2645
2647
  availableToolsList += '- query: Search code using structural AST patterns.\n';
2646
2648
  }
2647
2649
  if (isToolAllowed('extract')) {
2648
- availableToolsList += '- extract: Extract specific code blocks or lines from files.\n';
2650
+ availableToolsList += '- extract: Extract specific code blocks or lines from files. Use with symbol targets (e.g. "file.js#funcName") to get line numbers for line-targeted editing.\n';
2649
2651
  }
2650
2652
  if (isToolAllowed('listFiles')) {
2651
2653
  availableToolsList += '- listFiles: List files and directories in a specified location.\n';
@@ -2662,11 +2664,8 @@ The configuration is loaded from src/config.js lines 15-25 which contains the da
2662
2664
  if (isToolAllowed('readImage')) {
2663
2665
  availableToolsList += '- readImage: Read and load an image file for AI analysis.\n';
2664
2666
  }
2665
- if (this.allowEdit && isToolAllowed('implement')) {
2666
- availableToolsList += '- implement: Implement a feature or fix a bug using aider.\n';
2667
- }
2668
2667
  if (this.allowEdit && isToolAllowed('edit')) {
2669
- availableToolsList += '- edit: Edit files using exact string replacement.\n';
2668
+ availableToolsList += '- edit: Edit files using text replacement, AST-aware symbol operations, or line-targeted editing.\n';
2670
2669
  }
2671
2670
  if (this.allowEdit && isToolAllowed('create')) {
2672
2671
  availableToolsList += '- create: Create new files with specified content.\n';
@@ -2757,8 +2756,14 @@ Follow these instructions carefully:
2757
2756
  8. Once the task is fully completed, use the '<attempt_completion>' tool to provide the final result. This is the ONLY way to signal completion.
2758
2757
  9. Prefer concise and focused search queries. Use specific keywords and phrases to narrow down results.${this.allowEdit ? `
2759
2758
  10. When modifying files, choose the appropriate tool:
2760
- - Use 'edit' for precise changes to existing files (requires exact string match)
2761
- - Use 'create' for new files or complete file rewrites` : ''}
2759
+ - Use 'edit' for all code modifications:
2760
+ * For small changes (a line or a few lines), use old_string + new_string — copy old_string verbatim from the file.
2761
+ * For rewriting entire functions/classes/methods, use the symbol parameter instead (no exact text matching needed).
2762
+ * For editing specific lines from search/extract output, use start_line (and optionally end_line) with the line numbers shown in the output.${this.hashLines ? ' Line references include content hashes (e.g. "42:ab") for integrity verification.' : ''}
2763
+ * For editing inside large functions: first use extract with the symbol target (e.g. "file.js#myFunction") to see the function with line numbers${this.hashLines ? ' and hashes' : ''}, then use start_line/end_line to surgically edit specific lines within it.
2764
+ - Use 'create' for new files or complete file rewrites.
2765
+ - If an edit fails, read the error message — it tells you exactly how to fix the call and retry.
2766
+ - The system tracks which files you've seen via search/extract. If you try to edit a file you haven't read, or one that changed since you last read it, the edit will fail with instructions to re-read first. Always use extract before editing to ensure you have current file content.` : ''}
2762
2767
  </instructions>
2763
2768
  `;
2764
2769
 
@@ -3023,8 +3028,9 @@ Follow these instructions carefully:
3023
3028
  // +1 for schema formatting
3024
3029
  // +2 for potential Mermaid validation retries (can be multiple diagrams)
3025
3030
  // +1 for potential JSON correction
3026
- const baseMaxIterations = this.maxIterations || MAX_TOOL_ITERATIONS;
3027
- const maxIterations = options.schema ? baseMaxIterations + 4 : baseMaxIterations;
3031
+ // _maxIterationsOverride: used by correction calls to cap iterations (issue #447)
3032
+ const baseMaxIterations = options._maxIterationsOverride || this.maxIterations || MAX_TOOL_ITERATIONS;
3033
+ const maxIterations = (options._maxIterationsOverride) ? baseMaxIterations : (options.schema ? baseMaxIterations + 4 : baseMaxIterations);
3028
3034
 
3029
3035
  // Check if we're using CLI-based engines which handle their own agentic loop
3030
3036
  const isClaudeCode = this.clientApiProvider === 'claude-code' || process.env.USE_CLAUDE_CODE === 'true';
@@ -3420,8 +3426,11 @@ Follow these instructions carefully:
3420
3426
  validTools.push('attempt_completion');
3421
3427
 
3422
3428
  // Edit tools (require both allowEdit flag AND allowedTools permission)
3423
- if (this.allowEdit && this.allowedTools.isEnabled('implement')) {
3424
- validTools.push('implement', 'edit', 'create');
3429
+ if (this.allowEdit && this.allowedTools.isEnabled('edit')) {
3430
+ validTools.push('edit');
3431
+ }
3432
+ if (this.allowEdit && this.allowedTools.isEnabled('create')) {
3433
+ validTools.push('create');
3425
3434
  }
3426
3435
  // Bash tool (require both enableBash flag AND allowedTools permission)
3427
3436
  if (this.enableBash && this.allowedTools.isEnabled('bash')) {
@@ -3804,6 +3813,7 @@ Follow these instructions carefully:
3804
3813
  mcpConfigPath: this.mcpConfigPath, // Inherit MCP config path
3805
3814
  enableBash: this.enableBash, // Inherit bash enablement
3806
3815
  bashConfig: this.bashConfig, // Inherit bash configuration
3816
+ allowEdit: this.allowEdit, // Inherit edit/create permission
3807
3817
  allowedTools: allowedToolsForDelegate, // Inherit allowed tools from parent
3808
3818
  debug: this.debug,
3809
3819
  tracer: this.tracer
@@ -4685,11 +4695,14 @@ Convert your previous response content into actual JSON data that follows this s
4685
4695
  0
4686
4696
  );
4687
4697
 
4698
+ // Strip schema from correction options to prevent inflated iteration budget (issue #447)
4699
+ const { schema: _unusedSchema1, ...schemaDefCorrectionOptions } = options;
4688
4700
  finalResult = await this.answer(schemaDefinitionPrompt, [], {
4689
- ...options,
4701
+ ...schemaDefCorrectionOptions,
4690
4702
  _schemaFormatted: true,
4691
4703
  _skipValidation: true, // Skip validation in recursive correction calls to prevent loops
4692
- _completionPromptProcessed: true // Prevent cascading completion prompts in retry calls
4704
+ _completionPromptProcessed: true, // Prevent cascading completion prompts in retry calls
4705
+ _maxIterationsOverride: 3 // Correction should complete in 1-2 iterations (issue #447)
4693
4706
  });
4694
4707
  finalResult = cleanSchemaResponse(finalResult);
4695
4708
  validation = validateJsonResponse(finalResult);
@@ -4745,12 +4758,15 @@ Convert your previous response content into actual JSON data that follows this s
4745
4758
  );
4746
4759
  }
4747
4760
 
4761
+ // Strip schema from correction options to prevent inflated iteration budget (issue #447)
4762
+ const { schema: _unusedSchema2, ...correctionOptions } = options;
4748
4763
  finalResult = await this.answer(correctionPrompt, [], {
4749
- ...options,
4764
+ ...correctionOptions,
4750
4765
  _schemaFormatted: true,
4751
4766
  _skipValidation: true, // Skip validation in recursive correction calls to prevent loops
4752
4767
  _disableTools: true, // Only allow attempt_completion - prevent AI from using search/query tools
4753
- _completionPromptProcessed: true // Prevent cascading completion prompts in retry calls
4768
+ _completionPromptProcessed: true, // Prevent cascading completion prompts in retry calls
4769
+ _maxIterationsOverride: 3 // Correction should complete in 1-2 iterations (issue #447)
4754
4770
  });
4755
4771
  finalResult = cleanSchemaResponse(finalResult);
4756
4772
 
@@ -162,7 +162,8 @@ export class ACPToolManager {
162
162
  return ToolCallKind.extract;
163
163
  case 'delegate':
164
164
  return ToolCallKind.execute;
165
- case 'implement':
165
+ case 'edit':
166
+ case 'create':
166
167
  return ToolCallKind.edit;
167
168
  default:
168
169
  return ToolCallKind.execute;
@@ -117,7 +117,8 @@ describe('ACPToolManager', () => {
117
117
  expect(toolManager.getToolKind('query')).toBe(ToolCallKind.query);
118
118
  expect(toolManager.getToolKind('extract')).toBe(ToolCallKind.extract);
119
119
  expect(toolManager.getToolKind('delegate')).toBe(ToolCallKind.execute);
120
- expect(toolManager.getToolKind('implement')).toBe(ToolCallKind.edit);
120
+ expect(toolManager.getToolKind('edit')).toBe(ToolCallKind.edit);
121
+ expect(toolManager.getToolKind('create')).toBe(ToolCallKind.edit);
121
122
  expect(toolManager.getToolKind('unknown')).toBe(ToolCallKind.execute);
122
123
  });
123
124
  });
@@ -30,13 +30,45 @@ export const DEFAULT_ALLOW_PATTERNS = [
30
30
  'tree', 'tree:*',
31
31
 
32
32
  // Git read-only operations
33
- 'git:status', 'git:log', 'git:log:*', 'git:diff', 'git:diff:*',
33
+ 'git:status', 'git:status:*', 'git:log', 'git:log:*', 'git:diff', 'git:diff:*',
34
34
  'git:show', 'git:show:*', 'git:branch', 'git:branch:*',
35
35
  'git:tag', 'git:tag:*', 'git:describe', 'git:describe:*',
36
36
  'git:remote', 'git:remote:*', 'git:config:*',
37
- 'git:blame', 'git:blame:*', 'git:shortlog', 'git:reflog',
38
- 'git:ls-files', 'git:ls-tree', 'git:rev-parse', 'git:rev-list',
37
+ 'git:blame', 'git:blame:*', 'git:shortlog', 'git:shortlog:*', 'git:reflog', 'git:reflog:*',
38
+ 'git:ls-files', 'git:ls-files:*', 'git:ls-tree', 'git:ls-tree:*',
39
+ 'git:ls-remote', 'git:ls-remote:*',
40
+ 'git:rev-parse', 'git:rev-parse:*', 'git:rev-list', 'git:rev-list:*',
41
+ 'git:cat-file', 'git:cat-file:*',
42
+ 'git:diff-tree', 'git:diff-tree:*', 'git:diff-files', 'git:diff-files:*',
43
+ 'git:diff-index', 'git:diff-index:*',
44
+ 'git:for-each-ref', 'git:for-each-ref:*',
45
+ 'git:merge-base', 'git:merge-base:*',
46
+ 'git:name-rev', 'git:name-rev:*',
47
+ 'git:count-objects', 'git:count-objects:*',
48
+ 'git:verify-commit', 'git:verify-commit:*', 'git:verify-tag', 'git:verify-tag:*',
49
+ 'git:check-ignore', 'git:check-ignore:*', 'git:check-attr', 'git:check-attr:*',
50
+ 'git:stash:list', 'git:stash:show', 'git:stash:show:*',
51
+ 'git:worktree:list', 'git:worktree:list:*',
52
+ 'git:notes:list', 'git:notes:show', 'git:notes:show:*',
39
53
  'git:--version', 'git:help', 'git:help:*',
54
+
55
+ // GitHub CLI (gh) read-only operations
56
+ 'gh:--version', 'gh:help', 'gh:help:*', 'gh:status',
57
+ 'gh:auth:status', 'gh:auth:status:*',
58
+ 'gh:issue:list', 'gh:issue:list:*', 'gh:issue:view', 'gh:issue:view:*',
59
+ 'gh:issue:status', 'gh:issue:status:*',
60
+ 'gh:pr:list', 'gh:pr:list:*', 'gh:pr:view', 'gh:pr:view:*',
61
+ 'gh:pr:status', 'gh:pr:status:*', 'gh:pr:diff', 'gh:pr:diff:*',
62
+ 'gh:pr:checks', 'gh:pr:checks:*',
63
+ 'gh:repo:list', 'gh:repo:list:*', 'gh:repo:view', 'gh:repo:view:*',
64
+ 'gh:release:list', 'gh:release:list:*', 'gh:release:view', 'gh:release:view:*',
65
+ 'gh:run:list', 'gh:run:list:*', 'gh:run:view', 'gh:run:view:*',
66
+ 'gh:workflow:list', 'gh:workflow:list:*', 'gh:workflow:view', 'gh:workflow:view:*',
67
+ 'gh:gist:list', 'gh:gist:list:*', 'gh:gist:view', 'gh:gist:view:*',
68
+ 'gh:search:issues', 'gh:search:issues:*', 'gh:search:prs', 'gh:search:prs:*',
69
+ 'gh:search:repos', 'gh:search:repos:*', 'gh:search:code', 'gh:search:code:*',
70
+ 'gh:search:commits', 'gh:search:commits:*',
71
+ 'gh:api', 'gh:api:*',
40
72
 
41
73
  // Package managers (information only)
42
74
  'npm:list', 'npm:ls', 'npm:view', 'npm:info', 'npm:show',
@@ -165,9 +197,46 @@ export const DEFAULT_DENY_PATTERNS = [
165
197
  'sysctl:-w:*',
166
198
 
167
199
  // Dangerous git operations
168
- 'git:push', 'git:push:*', 'git:force', 'git:reset:--hard:*',
169
- 'git:clean:-fd', 'git:rm:*', 'git:commit', 'git:merge',
170
- 'git:rebase', 'git:cherry-pick', 'git:stash:drop',
200
+ 'git:push', 'git:push:*', 'git:force', 'git:reset', 'git:reset:*',
201
+ 'git:clean', 'git:clean:*', 'git:rm', 'git:rm:*',
202
+ 'git:commit', 'git:commit:*', 'git:merge', 'git:merge:*',
203
+ 'git:rebase', 'git:rebase:*', 'git:cherry-pick', 'git:cherry-pick:*',
204
+ 'git:stash:drop', 'git:stash:drop:*', 'git:stash:pop', 'git:stash:pop:*',
205
+ 'git:stash:push', 'git:stash:push:*', 'git:stash:clear',
206
+ 'git:branch:-d', 'git:branch:-d:*', 'git:branch:-D', 'git:branch:-D:*',
207
+ 'git:branch:--delete', 'git:branch:--delete:*',
208
+ 'git:tag:-d', 'git:tag:-d:*', 'git:tag:--delete', 'git:tag:--delete:*',
209
+ 'git:remote:remove', 'git:remote:remove:*', 'git:remote:rm', 'git:remote:rm:*',
210
+ 'git:checkout:--force', 'git:checkout:--force:*',
211
+ 'git:checkout:-f', 'git:checkout:-f:*',
212
+ 'git:submodule:deinit', 'git:submodule:deinit:*',
213
+ 'git:notes:add', 'git:notes:add:*', 'git:notes:remove', 'git:notes:remove:*',
214
+ 'git:worktree:add', 'git:worktree:add:*',
215
+ 'git:worktree:remove', 'git:worktree:remove:*',
216
+
217
+ // Dangerous GitHub CLI (gh) write operations
218
+ 'gh:issue:create', 'gh:issue:create:*', 'gh:issue:close', 'gh:issue:close:*',
219
+ 'gh:issue:delete', 'gh:issue:delete:*', 'gh:issue:edit', 'gh:issue:edit:*',
220
+ 'gh:issue:reopen', 'gh:issue:reopen:*',
221
+ 'gh:issue:comment', 'gh:issue:comment:*',
222
+ 'gh:pr:create', 'gh:pr:create:*', 'gh:pr:close', 'gh:pr:close:*',
223
+ 'gh:pr:merge', 'gh:pr:merge:*', 'gh:pr:edit', 'gh:pr:edit:*',
224
+ 'gh:pr:reopen', 'gh:pr:reopen:*', 'gh:pr:review', 'gh:pr:review:*',
225
+ 'gh:pr:comment', 'gh:pr:comment:*',
226
+ 'gh:repo:create', 'gh:repo:create:*', 'gh:repo:delete', 'gh:repo:delete:*',
227
+ 'gh:repo:fork', 'gh:repo:fork:*', 'gh:repo:rename', 'gh:repo:rename:*',
228
+ 'gh:repo:archive', 'gh:repo:archive:*', 'gh:repo:clone', 'gh:repo:clone:*',
229
+ 'gh:release:create', 'gh:release:create:*', 'gh:release:delete', 'gh:release:delete:*',
230
+ 'gh:release:edit', 'gh:release:edit:*',
231
+ 'gh:run:cancel', 'gh:run:cancel:*', 'gh:run:rerun', 'gh:run:rerun:*',
232
+ 'gh:workflow:run', 'gh:workflow:run:*',
233
+ 'gh:workflow:enable', 'gh:workflow:enable:*', 'gh:workflow:disable', 'gh:workflow:disable:*',
234
+ 'gh:gist:create', 'gh:gist:create:*', 'gh:gist:delete', 'gh:gist:delete:*',
235
+ 'gh:gist:edit', 'gh:gist:edit:*',
236
+ 'gh:secret:set', 'gh:secret:set:*', 'gh:secret:delete', 'gh:secret:delete:*',
237
+ 'gh:variable:set', 'gh:variable:set:*', 'gh:variable:delete', 'gh:variable:delete:*',
238
+ 'gh:label:create', 'gh:label:create:*', 'gh:label:delete', 'gh:label:delete:*',
239
+ 'gh:ssh-key:add', 'gh:ssh-key:add:*', 'gh:ssh-key:delete', 'gh:ssh-key:delete:*',
171
240
 
172
241
  // File system mounting and partitioning
173
242
  'mount', 'mount:*', 'umount', 'umount:*', 'fdisk', 'fdisk:*',