Skip to content

Commit 029a188

Browse files
committed
feat(engine): add EditFile block for deterministic file editing
Add comprehensive EditFile executor supporting 6 operation types: - replace_text: exact text find/replace - replace_lines: line range replacement - insert_lines: insert at specific line number - delete_lines: delete line range - patch: apply unified diff patches - regex_replace: pattern-based replacement Features: - Atomic transactions (all-or-nothing by default) - Automatic backup creation before edits - Dry-run mode for previewing changes - Comprehensive diff generation - Path traversal protection Includes test workflow demonstrating all operation types and snapshot-based regression test.
1 parent 947a5ca commit 029a188

7 files changed

Lines changed: 1260 additions & 8 deletions

File tree

README.md

Lines changed: 204 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ Individual tasks within a workflow. Available block types:
150150
- `Shell` - Run shell commands
151151
- `LLMCall` - Call AI/LLM APIs
152152
- `HttpCall` - Make HTTP requests
153-
- `CreateFile`, `ReadFile`, `RenderTemplate` - File operations
153+
- `CreateFile`, `ReadFile`, `EditFile`, `RenderTemplate` - File operations
154154
- `Workflow` - Call other workflows (composition)
155155
- `Prompt` - Interactive user prompts
156156
- `ReadJSONState`, `WriteJSONState`, `MergeJSONState` - State management
@@ -422,6 +422,124 @@ blocks:
422422
{{blocks.process_files.metadata.count}} # Total count
423423
```
424424

425+
### ✏️ Deterministic File Editing
426+
427+
The `EditFile` block provides powerful deterministic file editing with multiple operation strategies. Unlike simple find-replace, EditFile supports atomic transactions, backup creation, and comprehensive diff generation.
428+
429+
**Key Features:**
430+
- ✅ 6 operation types (replace_text, replace_lines, insert_lines, delete_lines, patch, regex_replace)
431+
- ✅ Atomic transactions (all-or-nothing by default)
432+
- ✅ Automatic backup creation before editing
433+
- ✅ Dry-run mode for previewing changes
434+
- ✅ Comprehensive diff generation
435+
- ✅ Path traversal protection
436+
437+
**Simple Text Replacement:**
438+
```yaml
439+
- id: update_config
440+
type: EditFile
441+
inputs:
442+
path: "config.yaml"
443+
operations:
444+
- type: replace_text
445+
old_text: "debug: false"
446+
new_text: "debug: true"
447+
```
448+
449+
**Line-Based Operations:**
450+
```yaml
451+
- id: update_imports
452+
type: EditFile
453+
inputs:
454+
path: "main.py"
455+
operations:
456+
# Replace specific lines
457+
- type: replace_lines
458+
start_line: 5
459+
end_line: 7
460+
content: |
461+
import os
462+
import sys
463+
from pathlib import Path
464+
465+
# Insert new lines
466+
- type: insert_lines
467+
line: 10
468+
content: |
469+
# New feature initialization
470+
initialize_feature()
471+
472+
# Delete lines
473+
- type: delete_lines
474+
start_line: 20
475+
end_line: 25
476+
```
477+
478+
**Regex Patterns:**
479+
```yaml
480+
- id: update_version
481+
type: EditFile
482+
inputs:
483+
path: "pyproject.toml"
484+
operations:
485+
- type: regex_replace
486+
pattern: 'version = "(\d+\.\d+)\.\d+"'
487+
replacement: 'version = "\1.99"'
488+
flags: [MULTILINE]
489+
```
490+
491+
**Patch Application:**
492+
```yaml
493+
- id: apply_patch
494+
type: EditFile
495+
inputs:
496+
path: "source.py"
497+
operations:
498+
- type: patch
499+
patch: |
500+
--- a/source.py
501+
+++ b/source.py
502+
@@ -1,3 +1,3 @@
503+
-def old_function():
504+
+def new_function():
505+
pass
506+
```
507+
508+
**Advanced Options:**
509+
```yaml
510+
- id: safe_edit
511+
type: EditFile
512+
inputs:
513+
path: "important.txt"
514+
operations: [...]
515+
create_if_missing: true # Create file if doesn't exist
516+
backup: true # Create .bak backup (default: true)
517+
dry_run: false # Preview without applying (default: false)
518+
atomic: true # All-or-nothing (default: true)
519+
encoding: "utf-8" # File encoding
520+
```
521+
522+
**Outputs:**
523+
```yaml
524+
# Access edit statistics
525+
{{blocks.update_file.outputs.operations_applied}} # Number of operations
526+
{{blocks.update_file.outputs.lines_added}} # Lines added
527+
{{blocks.update_file.outputs.lines_removed}} # Lines removed
528+
{{blocks.update_file.outputs.lines_modified}} # Lines modified
529+
{{blocks.update_file.outputs.diff}} # Unified diff
530+
{{blocks.update_file.outputs.backup_path}} # Backup file path
531+
{{blocks.update_file.succeeded}} # Success status
532+
{{blocks.update_file.outputs.errors}} # Error messages (if any)
533+
```
534+
535+
**Use Cases:**
536+
- Configuration file updates
537+
- Code refactoring and renaming
538+
- Version number bumps
539+
- Import statement management
540+
- Automated code fixes
541+
- Multi-file synchronized updates
542+
425543
### 🔄 Workflow Composition
426544

427545
Build complex workflows from simple reusable pieces:
@@ -887,6 +1005,90 @@ outputs:
8871005
type: str
8881006
```
8891007

1008+
### Example 5: Automated Version Bump
1009+
1010+
```yaml
1011+
name: version-bump
1012+
description: Automatically bump version in multiple files
1013+
tags: [versioning, automation]
1014+
1015+
inputs:
1016+
bump_type:
1017+
type: str
1018+
description: Type of version bump (major, minor, patch)
1019+
default: "patch"
1020+
1021+
blocks:
1022+
- id: get_current_version
1023+
type: Shell
1024+
inputs:
1025+
command: "grep -oP 'version = \"\\K[^\"]+' pyproject.toml"
1026+
1027+
- id: calculate_new_version
1028+
type: Shell
1029+
depends_on: [get_current_version]
1030+
inputs:
1031+
command: |
1032+
VERSION="{{blocks.get_current_version.outputs.stdout}}"
1033+
IFS='.' read -r major minor patch <<< "$VERSION"
1034+
case "{{inputs.bump_type}}" in
1035+
major) echo "$((major + 1)).0.0" ;;
1036+
minor) echo "$major.$((minor + 1)).0" ;;
1037+
patch) echo "$major.$minor.$((patch + 1))" ;;
1038+
esac
1039+
1040+
- id: update_pyproject
1041+
type: EditFile
1042+
depends_on: [calculate_new_version]
1043+
inputs:
1044+
path: "pyproject.toml"
1045+
backup: true
1046+
operations:
1047+
- type: regex_replace
1048+
pattern: 'version = "\d+\.\d+\.\d+"'
1049+
replacement: 'version = "{{blocks.calculate_new_version.outputs.stdout}}"'
1050+
1051+
- id: update_init
1052+
type: EditFile
1053+
depends_on: [calculate_new_version]
1054+
inputs:
1055+
path: "src/__init__.py"
1056+
backup: true
1057+
operations:
1058+
- type: regex_replace
1059+
pattern: '__version__ = "\d+\.\d+\.\d+"'
1060+
replacement: '__version__ = "{{blocks.calculate_new_version.outputs.stdout}}"'
1061+
1062+
- id: update_changelog
1063+
type: EditFile
1064+
depends_on: [calculate_new_version]
1065+
inputs:
1066+
path: "CHANGELOG.md"
1067+
backup: true
1068+
operations:
1069+
- type: insert_lines
1070+
line: 3
1071+
content: |
1072+
## [{{blocks.calculate_new_version.outputs.stdout}}] - $(date +%Y-%m-%d)
1073+
1074+
### Changed
1075+
- Version bump to {{blocks.calculate_new_version.outputs.stdout}}
1076+
1077+
outputs:
1078+
old_version:
1079+
value: "{{blocks.get_current_version.outputs.stdout}}"
1080+
type: str
1081+
new_version:
1082+
value: "{{blocks.calculate_new_version.outputs.stdout}}"
1083+
type: str
1084+
files_updated:
1085+
value: "{{blocks.update_pyproject.succeeded}} and {{blocks.update_init.succeeded}} and {{blocks.update_changelog.succeeded}}"
1086+
type: bool
1087+
total_changes:
1088+
value: "{{blocks.update_pyproject.outputs.lines_modified}} + {{blocks.update_init.outputs.lines_modified}} + {{blocks.update_changelog.outputs.lines_added}}"
1089+
type: num
1090+
```
1091+
8901092
---
8911093

8921094
## Development
@@ -955,7 +1157,7 @@ workflows-mcp/
9551157
│ ├── engine/ # Workflow execution engine
9561158
│ │ ├── executor_base.py # Base executor class
9571159
│ │ ├── executors_core.py # Shell, Workflow executors
958-
│ │ ├── executors_file.py # File operation executors
1160+
│ │ ├── executors_file.py # File operation executors (CreateFile, ReadFile, EditFile, RenderTemplate)
9591161
│ │ ├── executors_http.py # HTTP call executor
9601162
│ │ ├── executors_llm.py # LLM call executor
9611163
│ │ ├── executors_state.py # State management executors

0 commit comments

Comments
 (0)