teddy-cli 0.1.0__py3-none-any.whl
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.
- teddy_cli-0.1.0.dist-info/LICENSE +677 -0
- teddy_cli-0.1.0.dist-info/METADATA +33 -0
- teddy_cli-0.1.0.dist-info/RECORD +143 -0
- teddy_cli-0.1.0.dist-info/WHEEL +4 -0
- teddy_cli-0.1.0.dist-info/entry_points.txt +3 -0
- teddy_executor/__init__.py +1 -0
- teddy_executor/__main__.py +335 -0
- teddy_executor/adapters/__init__.py +0 -0
- teddy_executor/adapters/inbound/__init__.py +0 -0
- teddy_executor/adapters/inbound/cli_formatter.py +107 -0
- teddy_executor/adapters/inbound/cli_helpers.py +249 -0
- teddy_executor/adapters/inbound/console_plan_reviewer.py +69 -0
- teddy_executor/adapters/inbound/session_cli_handlers.py +366 -0
- teddy_executor/adapters/inbound/textual_plan_reviewer.py +78 -0
- teddy_executor/adapters/inbound/textual_plan_reviewer_app.py +367 -0
- teddy_executor/adapters/inbound/textual_plan_reviewer_editor.py +281 -0
- teddy_executor/adapters/inbound/textual_plan_reviewer_execution.py +213 -0
- teddy_executor/adapters/inbound/textual_plan_reviewer_helpers.py +308 -0
- teddy_executor/adapters/inbound/textual_plan_reviewer_logic.py +345 -0
- teddy_executor/adapters/inbound/textual_plan_reviewer_previews.py +227 -0
- teddy_executor/adapters/inbound/textual_plan_reviewer_widgets.py +246 -0
- teddy_executor/adapters/outbound/__init__.py +7 -0
- teddy_executor/adapters/outbound/console_interactor.py +212 -0
- teddy_executor/adapters/outbound/console_interactor_ask_loop.py +121 -0
- teddy_executor/adapters/outbound/console_interactor_helpers.py +95 -0
- teddy_executor/adapters/outbound/console_tooling.py +62 -0
- teddy_executor/adapters/outbound/filesystem_helpers.py +61 -0
- teddy_executor/adapters/outbound/litellm_adapter.py +462 -0
- teddy_executor/adapters/outbound/local_file_system_adapter.py +300 -0
- teddy_executor/adapters/outbound/local_repo_tree_generator.py +96 -0
- teddy_executor/adapters/outbound/openrouter_hydrator.py +89 -0
- teddy_executor/adapters/outbound/shell_adapter.py +344 -0
- teddy_executor/adapters/outbound/shell_command_builder.py +105 -0
- teddy_executor/adapters/outbound/system_environment_adapter.py +62 -0
- teddy_executor/adapters/outbound/system_environment_inspector.py +54 -0
- teddy_executor/adapters/outbound/system_time_adapter.py +22 -0
- teddy_executor/adapters/outbound/web_scraper_adapter.py +346 -0
- teddy_executor/adapters/outbound/web_searcher_adapter.py +122 -0
- teddy_executor/adapters/outbound/yaml_config_adapter.py +105 -0
- teddy_executor/container.py +333 -0
- teddy_executor/core/__init__.py +0 -0
- teddy_executor/core/domain/__init__.py +0 -0
- teddy_executor/core/domain/models/__init__.py +44 -0
- teddy_executor/core/domain/models/action_ports.py +28 -0
- teddy_executor/core/domain/models/change_set.py +10 -0
- teddy_executor/core/domain/models/exceptions.py +40 -0
- teddy_executor/core/domain/models/execution_report.py +65 -0
- teddy_executor/core/domain/models/orchestrator_ports.py +26 -0
- teddy_executor/core/domain/models/plan.py +85 -0
- teddy_executor/core/domain/models/planning_ports.py +43 -0
- teddy_executor/core/domain/models/project_context.py +56 -0
- teddy_executor/core/domain/models/report_assembly_data.py +18 -0
- teddy_executor/core/domain/models/session.py +17 -0
- teddy_executor/core/domain/models/shell_output.py +12 -0
- teddy_executor/core/domain/models/web_search_results.py +26 -0
- teddy_executor/core/ports/__init__.py +0 -0
- teddy_executor/core/ports/inbound/__init__.py +0 -0
- teddy_executor/core/ports/inbound/edit_simulator.py +33 -0
- teddy_executor/core/ports/inbound/get_context_use_case.py +32 -0
- teddy_executor/core/ports/inbound/init.py +15 -0
- teddy_executor/core/ports/inbound/plan_parser.py +52 -0
- teddy_executor/core/ports/inbound/plan_reviewer.py +44 -0
- teddy_executor/core/ports/inbound/plan_validator.py +26 -0
- teddy_executor/core/ports/inbound/planning_use_case.py +30 -0
- teddy_executor/core/ports/inbound/run_plan_use_case.py +60 -0
- teddy_executor/core/ports/outbound/__init__.py +34 -0
- teddy_executor/core/ports/outbound/config_service.py +29 -0
- teddy_executor/core/ports/outbound/environment_inspector.py +30 -0
- teddy_executor/core/ports/outbound/execution_report_assembler.py +19 -0
- teddy_executor/core/ports/outbound/file_system_manager.py +131 -0
- teddy_executor/core/ports/outbound/llm_client.py +90 -0
- teddy_executor/core/ports/outbound/markdown_report_formatter.py +26 -0
- teddy_executor/core/ports/outbound/prompt_manager.py +55 -0
- teddy_executor/core/ports/outbound/repo_tree_generator.py +17 -0
- teddy_executor/core/ports/outbound/session_loop_guard.py +16 -0
- teddy_executor/core/ports/outbound/session_manager.py +97 -0
- teddy_executor/core/ports/outbound/session_repository.py +65 -0
- teddy_executor/core/ports/outbound/shell_executor.py +24 -0
- teddy_executor/core/ports/outbound/system_environment.py +25 -0
- teddy_executor/core/ports/outbound/time_service.py +28 -0
- teddy_executor/core/ports/outbound/user_interactor.py +126 -0
- teddy_executor/core/ports/outbound/web_scraper.py +24 -0
- teddy_executor/core/ports/outbound/web_searcher.py +25 -0
- teddy_executor/core/services/__init__.py +0 -0
- teddy_executor/core/services/action_changeset_builder.py +90 -0
- teddy_executor/core/services/action_diff_manager.py +110 -0
- teddy_executor/core/services/action_dispatcher.py +142 -0
- teddy_executor/core/services/action_executor.py +209 -0
- teddy_executor/core/services/action_factory.py +197 -0
- teddy_executor/core/services/action_parser_complex.py +216 -0
- teddy_executor/core/services/action_parser_strategies.py +84 -0
- teddy_executor/core/services/context_service.py +437 -0
- teddy_executor/core/services/edit_simulator.py +128 -0
- teddy_executor/core/services/execution_orchestrator.py +295 -0
- teddy_executor/core/services/execution_report_assembler.py +62 -0
- teddy_executor/core/services/init_service.py +80 -0
- teddy_executor/core/services/markdown_plan_parser.py +309 -0
- teddy_executor/core/services/markdown_report_formatter.py +143 -0
- teddy_executor/core/services/parser_infrastructure.py +222 -0
- teddy_executor/core/services/parser_metadata.py +153 -0
- teddy_executor/core/services/parser_reporting.py +267 -0
- teddy_executor/core/services/plan_validator.py +82 -0
- teddy_executor/core/services/planning_service.py +242 -0
- teddy_executor/core/services/prompt_manager.py +146 -0
- teddy_executor/core/services/session_lifecycle_manager.py +228 -0
- teddy_executor/core/services/session_loop_guard.py +46 -0
- teddy_executor/core/services/session_orchestrator.py +538 -0
- teddy_executor/core/services/session_planner.py +43 -0
- teddy_executor/core/services/session_pruning_service.py +438 -0
- teddy_executor/core/services/session_replanner.py +105 -0
- teddy_executor/core/services/session_repository.py +194 -0
- teddy_executor/core/services/session_service.py +529 -0
- teddy_executor/core/services/templates/execution_report.md.j2 +290 -0
- teddy_executor/core/services/validation_rules/__init__.py +4 -0
- teddy_executor/core/services/validation_rules/edit.py +207 -0
- teddy_executor/core/services/validation_rules/edit_matcher.py +247 -0
- teddy_executor/core/services/validation_rules/edit_matcher_heuristics.py +84 -0
- teddy_executor/core/services/validation_rules/execute.py +37 -0
- teddy_executor/core/services/validation_rules/filesystem.py +73 -0
- teddy_executor/core/services/validation_rules/helpers.py +178 -0
- teddy_executor/core/services/validation_rules/message.py +29 -0
- teddy_executor/core/utils/__init__.py +1 -0
- teddy_executor/core/utils/diff.py +57 -0
- teddy_executor/core/utils/io.py +75 -0
- teddy_executor/core/utils/markdown.py +131 -0
- teddy_executor/core/utils/serialization.py +39 -0
- teddy_executor/core/utils/string.py +351 -0
- teddy_executor/prompts.py +45 -0
- teddy_executor/registries/__init__.py +1 -0
- teddy_executor/registries/infrastructure.py +147 -0
- teddy_executor/registries/reviewer.py +57 -0
- teddy_executor/registries/validators.py +47 -0
- teddy_executor/resources/__init__.py +1 -0
- teddy_executor/resources/config/.gitignore +2 -0
- teddy_executor/resources/config/__init__.py +1 -0
- teddy_executor/resources/config/config.yaml +49 -0
- teddy_executor/resources/config/init.context +5 -0
- teddy_executor/resources/config/prompts/architect.xml +462 -0
- teddy_executor/resources/config/prompts/assistant.xml +336 -0
- teddy_executor/resources/config/prompts/debugger.xml +456 -0
- teddy_executor/resources/config/prompts/developer.xml +481 -0
- teddy_executor/resources/config/prompts/pathfinder.xml +502 -0
- teddy_executor/resources/config/prompts/prototyper.xml +425 -0
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
{#
|
|
2
|
+
Unified template for the concise Markdown report.
|
|
3
|
+
Renders both execution outcomes and pre-flight validation failures.
|
|
4
|
+
|
|
5
|
+
NOTE ON WHITESPACE CONTROL:
|
|
6
|
+
This template uses manual whitespace control (`-%}`) on blocks that are
|
|
7
|
+
followed by blank lines. The Jinja2 `trim_blocks=True` setting only
|
|
8
|
+
removes the *first* newline after a tag, not subsequent blank lines.
|
|
9
|
+
The manual control is necessary to prevent extra newlines in the final
|
|
10
|
+
rendered output when conditional sections are not rendered.
|
|
11
|
+
#}
|
|
12
|
+
{% macro render_resource(path, content) %}
|
|
13
|
+
{% if path.startswith('http:') or path.startswith('https:') %}
|
|
14
|
+
### [{{ path }}]({{ path }})
|
|
15
|
+
{% else %}
|
|
16
|
+
### [{{ path }}]({% if not path.startswith('/') %}/{% endif %}{{ path }})
|
|
17
|
+
{% endif %}
|
|
18
|
+
{{ content | fence }}{{ path | language_from_path }}
|
|
19
|
+
{{ content }}
|
|
20
|
+
{{ content | fence }}
|
|
21
|
+
{% endmacro %}
|
|
22
|
+
|
|
23
|
+
{% macro render_action_details(log) %}
|
|
24
|
+
{# Handle MESSAGE actions first: render content in a fenced codeblock #}
|
|
25
|
+
{% if log.action_type.upper() == 'MESSAGE' and log.details %}
|
|
26
|
+
{% if log.details is mapping and log.details.get('content') %}
|
|
27
|
+
- **User Reply:**
|
|
28
|
+
{{ log.details.content | trim | fence }}
|
|
29
|
+
{{ log.details.content | trim }}
|
|
30
|
+
{{ log.details.content | trim | fence }}
|
|
31
|
+
{% else %}
|
|
32
|
+
- **User Reply:**
|
|
33
|
+
{{ log.details | trim | fence }}
|
|
34
|
+
{{ log.details | trim }}
|
|
35
|
+
{{ log.details | trim | fence }}
|
|
36
|
+
{% endif %}
|
|
37
|
+
|
|
38
|
+
{% elif log.details -%}
|
|
39
|
+
{% if log.details is mapping -%}
|
|
40
|
+
{% if log.details.get("error") -%}
|
|
41
|
+
- **Error:** {{ log.details.error }}
|
|
42
|
+
{% endif -%}
|
|
43
|
+
{% if log.details.get("original_details") -%}
|
|
44
|
+
- **Error:** {{ log.details.original_details }}
|
|
45
|
+
{% endif -%}
|
|
46
|
+
{% if log.details.get("return_code") is not none -%}
|
|
47
|
+
- **Return Code:** `{{ log.details.return_code }}`
|
|
48
|
+
{% endif -%}
|
|
49
|
+
{% if log.details.get("failed_command") -%}
|
|
50
|
+
- **Failed Command:** `{{ log.details.failed_command }}`
|
|
51
|
+
{% endif -%}
|
|
52
|
+
{% if log.details.get("similarity_scores") -%}
|
|
53
|
+
{% set scores_list = log.details.get("similarity_scores") -%}
|
|
54
|
+
{% if scores_list | length == 1 -%}
|
|
55
|
+
- **Similarity Score:** {{ "%.2f" | format(scores_list[0]) }}
|
|
56
|
+
{% else -%}
|
|
57
|
+
- **Similarity Scores:** {% for s in scores_list %}{{ "%.2f" | format(s) }}{% if not loop.last %}, {% endif %}{% endfor %}
|
|
58
|
+
{% endif %}
|
|
59
|
+
|
|
60
|
+
{% elif log.details.get("similarity_score") is not none -%}
|
|
61
|
+
- **Similarity Score:** {{ "%.2f" | format(log.details.get("similarity_score")) }}
|
|
62
|
+
|
|
63
|
+
{% endif -%}
|
|
64
|
+
{% if log.details.get("stdout") -%}
|
|
65
|
+
#### `stdout`
|
|
66
|
+
{{ log.details.stdout | trim | fence }}text
|
|
67
|
+
{{ log.details.stdout | trim }}
|
|
68
|
+
{{ log.details.stdout | trim | fence }}
|
|
69
|
+
|
|
70
|
+
{% endif -%}
|
|
71
|
+
{% if log.details.get("stderr") -%}
|
|
72
|
+
#### `stderr`
|
|
73
|
+
{{ log.details.stderr | trim | fence }}text
|
|
74
|
+
{{ log.details.stderr | trim }}
|
|
75
|
+
{{ log.details.stderr | trim | fence }}
|
|
76
|
+
|
|
77
|
+
{% endif -%}
|
|
78
|
+
{% if log.details.get("diff") -%}
|
|
79
|
+
#### `diff`
|
|
80
|
+
{{ log.details.diff | trim | fence }}diff
|
|
81
|
+
{{ log.details.diff | trim }}
|
|
82
|
+
{{ log.details.diff | trim | fence }}
|
|
83
|
+
|
|
84
|
+
{% endif -%}
|
|
85
|
+
{# Render generic details if no specific fields matched, but avoid rendering if it's just content #}
|
|
86
|
+
{% if not log.details.get("error") and not log.details.get("return_code") and not log.details.get("stdout") and not log.details.get("stderr") and not log.details.get("content") and not log.details.get("diff") and not log.details.get("failed_command") and not log.details.get("similarity_scores") and not log.details.get("similarity_score") and not log.details.get("query_results") %}
|
|
87
|
+
- **Details:** `{{ log.details }}`
|
|
88
|
+
{% endif -%}
|
|
89
|
+
{% else -%}
|
|
90
|
+
- **Details:** `{{ log.details }}`
|
|
91
|
+
{% endif -%}
|
|
92
|
+
{% endif %}
|
|
93
|
+
{% endmacro -%}
|
|
94
|
+
|
|
95
|
+
{% macro render_header(report) -%}
|
|
96
|
+
# Execution Report: {{ report.plan_title | default('Untitled Plan') }}
|
|
97
|
+
{% set status_val = report.run_summary.status.value | default(report.run_summary.status) -%}
|
|
98
|
+
- **Overall Status:** {{ "Validation Failed" if status_val == "VALIDATION_FAILED" else status_val }}
|
|
99
|
+
- **Execution Start Time:** {{ format_datetime(report.run_summary.start_time) }}
|
|
100
|
+
- **Execution End Time:** {{ format_datetime(report.run_summary.end_time) }}
|
|
101
|
+
{%- endmacro %}
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
{{ render_header(report) }}
|
|
105
|
+
|
|
106
|
+
{% if report.validation_result -%}
|
|
107
|
+
## Validation Errors:
|
|
108
|
+
{% for error in report.validation_result -%}
|
|
109
|
+
{{ error | trim }}
|
|
110
|
+
{% if not loop.last %}
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
{% endif %}
|
|
115
|
+
{% endfor %}
|
|
116
|
+
{% endif -%}
|
|
117
|
+
|
|
118
|
+
{% if report.validation_ast -%}
|
|
119
|
+
{{ report.validation_ast }}
|
|
120
|
+
{% endif -%}
|
|
121
|
+
{% if report.failed_resources and not is_session %}
|
|
122
|
+
|
|
123
|
+
## Resource Contents
|
|
124
|
+
{% for path, content in report.failed_resources.items() %}
|
|
125
|
+
{{ render_resource(path, content) }}
|
|
126
|
+
{% endfor %}
|
|
127
|
+
{%- endif %}
|
|
128
|
+
{% if report.user_request %}
|
|
129
|
+
|
|
130
|
+
## User Request
|
|
131
|
+
{{ report.user_request | fence }}text
|
|
132
|
+
{{ report.user_request }}
|
|
133
|
+
{{ report.user_request | fence }}
|
|
134
|
+
{% endif %}
|
|
135
|
+
|
|
136
|
+
{# Extract logs that should display resource content #}
|
|
137
|
+
{% set report_ns = namespace(content_logs=[]) %}
|
|
138
|
+
{% for log in report.action_logs %}
|
|
139
|
+
{% if log.details and log.details is mapping and log.details.get('content') %}
|
|
140
|
+
{# Always include content for failed actions or successful READ actions (unless in a session). #}
|
|
141
|
+
{% set is_read = log.action_type.lower() == 'read' %}
|
|
142
|
+
{% set is_success = log.status.value == 'SUCCESS' %}
|
|
143
|
+
{% set is_failure = log.status.value == 'FAILURE' %}
|
|
144
|
+
|
|
145
|
+
{% if is_failure or (is_read and is_success) %}
|
|
146
|
+
{% set report_ns.content_logs = report_ns.content_logs + [log] %}
|
|
147
|
+
{% endif %}
|
|
148
|
+
{% endif %}
|
|
149
|
+
{% endfor -%}
|
|
150
|
+
|
|
151
|
+
{% if report_ns.content_logs and not is_session %}
|
|
152
|
+
## Resource Contents
|
|
153
|
+
{% for log in report_ns.content_logs %}
|
|
154
|
+
{% set res_path = log.params.get('File Path') or log.params.get('Resource') or log.params.get('resource') or log.params.get('path') %}
|
|
155
|
+
{{ render_resource(res_path, log.details.content) }}
|
|
156
|
+
{% endfor %}
|
|
157
|
+
{% endif -%}
|
|
158
|
+
## Action Log
|
|
159
|
+
{% for log in report.action_logs %}
|
|
160
|
+
{% set action_type = log.action_type.upper() %}
|
|
161
|
+
{% set params = log.params or {} %}
|
|
162
|
+
{% set target = '' %}
|
|
163
|
+
|
|
164
|
+
{# Determine Header Target #}
|
|
165
|
+
{% if action_type == 'EXECUTE' %}
|
|
166
|
+
{% set target = '"' ~ (params.get('description') or params.get('Description') or 'Execute Command') ~ '"' %}
|
|
167
|
+
{% elif action_type == 'RESEARCH' %}
|
|
168
|
+
{% set target = params.get('description') or params.get('Description') or 'Research' %}
|
|
169
|
+
{% else %}
|
|
170
|
+
{# Use Markdown Link for CREATE/EDIT/READ #}
|
|
171
|
+
{% set full_path = params.get('File Path') or params.get('file_path') or params.get('Resource') or params.get('resource') or params.get('path') or '' %}
|
|
172
|
+
{# Check for URL schemes http and https #}
|
|
173
|
+
{% if full_path.startswith('http:') or full_path.startswith('https:') %}
|
|
174
|
+
{% set target = '[' ~ full_path ~ '](' ~ full_path ~ ')' %}
|
|
175
|
+
{% else %}
|
|
176
|
+
{% set target = '[' ~ (full_path | basename) ~ '](' ~ ('/' ~ full_path if not full_path.startswith('/') else full_path) ~ ')' %}
|
|
177
|
+
{% endif %}
|
|
178
|
+
{% endif %}
|
|
179
|
+
|
|
180
|
+
{% if log.modified_fields -%}
|
|
181
|
+
{% set modified_tag = " (user modified: " ~ (log.modified_fields | join(', ')) ~ ")" -%}
|
|
182
|
+
{% elif log.modified -%}
|
|
183
|
+
{% set modified_tag = " (modified)" -%}
|
|
184
|
+
{% else -%}
|
|
185
|
+
{% set modified_tag = "" -%}
|
|
186
|
+
{% endif -%}
|
|
187
|
+
{% if target %}
|
|
188
|
+
### `{{ action_type }}`{{ modified_tag }}: {{ target }}
|
|
189
|
+
{% else -%}
|
|
190
|
+
### `{{ action_type }}`{{ modified_tag }}
|
|
191
|
+
{% endif -%}
|
|
192
|
+
{% set log_status = log.status.value | default(log.status) -%}
|
|
193
|
+
- **Status:** {{ log_status }}
|
|
194
|
+
{% if action_type == 'READ' and not params.get('lines') %}
|
|
195
|
+
- **Note:** Up-to-date contents are provided under `Resource Contents`.
|
|
196
|
+
{% endif %}
|
|
197
|
+
{% if action_type in ['CREATE', 'EDIT'] -%}
|
|
198
|
+
{% set path_val = params.get('File Path') or params.get('file_path') or params.get('path') -%}
|
|
199
|
+
{% if path_val -%}
|
|
200
|
+
- **File Path:** `{{ path_val }}`
|
|
201
|
+
{%- endif %}
|
|
202
|
+
{%- endif %}
|
|
203
|
+
{# Params Rendering - Flattened #}
|
|
204
|
+
{% if action_type == 'EXECUTE' %}
|
|
205
|
+
{% set outcome = params.get('expected_outcome') or params.get('Expected Outcome') %}
|
|
206
|
+
{% if outcome %}
|
|
207
|
+
- **Expected outcome:** {{ outcome }}
|
|
208
|
+
{% endif %}
|
|
209
|
+
|
|
210
|
+
{% set setup = params.get('setup') or params.get('Setup') %}
|
|
211
|
+
{% if setup %}
|
|
212
|
+
- **Setup:** `{{ setup }}`
|
|
213
|
+
{% endif %}
|
|
214
|
+
|
|
215
|
+
{% set allow_failure = params.get('allow_failure') or params.get('Allow Failure') %}
|
|
216
|
+
{% if allow_failure is not none %}
|
|
217
|
+
- **Allow Failure:** `{{ allow_failure | lower }}`
|
|
218
|
+
{% endif %}
|
|
219
|
+
|
|
220
|
+
{% set cmd = params.get('command') or params.get('cmd') %}
|
|
221
|
+
{% if cmd %}
|
|
222
|
+
|
|
223
|
+
- **Command:** {% if '\n' in cmd %}
|
|
224
|
+
{{ '\n' }}{{ cmd | fence }}shell
|
|
225
|
+
{{ cmd }}
|
|
226
|
+
{{ cmd | fence }}
|
|
227
|
+
{% else %}`{{ cmd }}`{% endif %}
|
|
228
|
+
{% endif %}
|
|
229
|
+
|
|
230
|
+
{% else %}
|
|
231
|
+
{# Generic Params for CREATE, EDIT, READ, RESEARCH etc #}
|
|
232
|
+
{% if params %}
|
|
233
|
+
{# Use namespace for persistent ignored_keys across if-blocks #}
|
|
234
|
+
{% set param_ns = namespace(ignored=['content', 'find', 'replace', 'edits', 'similarity_scores', 'similarity_score', 'similarity threshold', 'similarity_threshold', 'metadata used file path alias']) %}
|
|
235
|
+
|
|
236
|
+
{% if action_type in ['EXECUTE', 'RESEARCH'] %}
|
|
237
|
+
{% set param_ns.ignored = param_ns.ignored + ['description', 'Description'] %}
|
|
238
|
+
{% endif %}
|
|
239
|
+
{% if action_type == 'RESEARCH' %}
|
|
240
|
+
{% set param_ns.ignored = param_ns.ignored + ['queries'] %}
|
|
241
|
+
{% endif %}
|
|
242
|
+
|
|
243
|
+
{% for key, value in params.items() %}
|
|
244
|
+
{% set k_lower = key.lower() %}
|
|
245
|
+
{% if k_lower not in param_ns.ignored %}
|
|
246
|
+
{# Normalize key to Title Case #}
|
|
247
|
+
{% set display_key = key.replace('_', ' ').title() %}
|
|
248
|
+
{# Map plumbing keys back to human readable names for display #}
|
|
249
|
+
{% if k_lower == 'similarity_threshold' %}{% set display_key = 'Similarity Threshold' %}{% endif %}
|
|
250
|
+
{% if k_lower == 'match_all' %}{% set display_key = 'Match All' %}{% endif %}
|
|
251
|
+
{# Skip path params in body since they are in header #}
|
|
252
|
+
{% if k_lower not in ['file path', 'file_path', 'resource', 'path'] %}
|
|
253
|
+
- **{{ display_key }}:** {{ value }}
|
|
254
|
+
{% endif %}
|
|
255
|
+
{% endif %}
|
|
256
|
+
{% endfor %}
|
|
257
|
+
{% endif %}
|
|
258
|
+
{% endif %}
|
|
259
|
+
|
|
260
|
+
{% if action_type == 'RESEARCH' and log.details and log.details is mapping and log.details.get('query_results') %}
|
|
261
|
+
{% for qr in log.details.get('query_results', []) %}
|
|
262
|
+
|
|
263
|
+
#### Query: "{{ qr['query'] }}"
|
|
264
|
+
{% for sr in qr['results'] %}
|
|
265
|
+
{{ loop.index }}. `{{ sr['href'] }}`
|
|
266
|
+
- **Title:** {{ sr['title'] }}
|
|
267
|
+
- **Description:** {{ sr['description'] }}
|
|
268
|
+
{% endfor %}
|
|
269
|
+
{% endfor %}
|
|
270
|
+
|
|
271
|
+
_**Hint:** NOW you MUST use READ on the most promising results)_
|
|
272
|
+
{% endif %}
|
|
273
|
+
|
|
274
|
+
{% if action_type == 'READ' and log.details and log.details is mapping and log.details.get('content') and params.get('lines') %}
|
|
275
|
+
{{ log.details.content | trim | fence }}
|
|
276
|
+
{{ log.details.content | trim }}
|
|
277
|
+
{{ log.details.content | trim | fence }}
|
|
278
|
+
{% endif %}
|
|
279
|
+
|
|
280
|
+
{{ render_action_details(log) }}
|
|
281
|
+
{% else %}
|
|
282
|
+
*(No actions were executed.)*
|
|
283
|
+
{% endfor %}
|
|
284
|
+
{% if report.validation_result %}
|
|
285
|
+
|
|
286
|
+
## Validation Recovery
|
|
287
|
+
1. Identify the root cause of the failure based on your system instructions (MRP) & context.
|
|
288
|
+
2. Submit a corrected plan focusing strictly on the failed/problematic action(s).
|
|
289
|
+
3. Defer the remaining steps of the original plan to subsequent turns.
|
|
290
|
+
{% endif %}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Validation rules for the 'EDIT' action.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
from teddy_executor.core.domain.models.plan import (
|
|
9
|
+
ActionData,
|
|
10
|
+
DEFAULT_SIMILARITY_THRESHOLD,
|
|
11
|
+
)
|
|
12
|
+
from teddy_executor.core.ports.outbound import IConfigService, IFileSystemManager
|
|
13
|
+
from teddy_executor.core.services.validation_rules.edit_matcher import (
|
|
14
|
+
find_best_match_and_diff,
|
|
15
|
+
)
|
|
16
|
+
from teddy_executor.core.services.validation_rules.helpers import (
|
|
17
|
+
BaseActionValidator,
|
|
18
|
+
ContextPaths,
|
|
19
|
+
PlanValidationError,
|
|
20
|
+
ValidationError,
|
|
21
|
+
ValidationResult,
|
|
22
|
+
resolve_similarity_threshold,
|
|
23
|
+
validate_path_is_safe,
|
|
24
|
+
)
|
|
25
|
+
from teddy_executor.core.utils.markdown import get_fence_for_content
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class EditActionValidator(BaseActionValidator):
|
|
29
|
+
"""Validator for the 'EDIT' action."""
|
|
30
|
+
|
|
31
|
+
def __init__(
|
|
32
|
+
self, file_system_manager: IFileSystemManager, config_service: IConfigService
|
|
33
|
+
):
|
|
34
|
+
super().__init__(file_system_manager)
|
|
35
|
+
self._config_service = config_service
|
|
36
|
+
|
|
37
|
+
def validate(
|
|
38
|
+
self,
|
|
39
|
+
action: ActionData,
|
|
40
|
+
context_paths: Optional[ContextPaths] = None,
|
|
41
|
+
) -> ValidationResult:
|
|
42
|
+
"""
|
|
43
|
+
Validates an 'edit' action.
|
|
44
|
+
"""
|
|
45
|
+
path_str = (
|
|
46
|
+
action.params.get("path")
|
|
47
|
+
or action.params.get("file_path")
|
|
48
|
+
or action.params.get("File Path")
|
|
49
|
+
or action.params.get("path")
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
if not isinstance(path_str, str):
|
|
53
|
+
return []
|
|
54
|
+
|
|
55
|
+
try:
|
|
56
|
+
validate_path_is_safe(path_str, "EDIT")
|
|
57
|
+
|
|
58
|
+
# Context Check: Removed as per 02-04-Context Automation slice.
|
|
59
|
+
# EDIT actions are now allowed on any valid path, relying on the matcher.
|
|
60
|
+
|
|
61
|
+
if not self._file_system_manager.path_exists(path_str):
|
|
62
|
+
raise PlanValidationError(
|
|
63
|
+
f"File to edit does not exist: {path_str}",
|
|
64
|
+
file_path=path_str,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
except (PlanValidationError, FileNotFoundError) as e:
|
|
68
|
+
return [
|
|
69
|
+
ValidationError(
|
|
70
|
+
message=getattr(e, "message", str(e)),
|
|
71
|
+
file_path=getattr(e, "file_path", path_str),
|
|
72
|
+
offending_node=action.node,
|
|
73
|
+
)
|
|
74
|
+
]
|
|
75
|
+
|
|
76
|
+
action_errors: ValidationResult = []
|
|
77
|
+
content = self._file_system_manager.read_raw_file(path_str)
|
|
78
|
+
|
|
79
|
+
# Use centralized similarity threshold resolution
|
|
80
|
+
threshold = resolve_similarity_threshold(self._config_service, action.params)
|
|
81
|
+
|
|
82
|
+
match_all = action.params.get("match_all", False)
|
|
83
|
+
edits = action.params.get("edits")
|
|
84
|
+
if isinstance(edits, list):
|
|
85
|
+
for edit in edits:
|
|
86
|
+
# Local override from edit metadata takes precedence
|
|
87
|
+
local_match_all = edit.get("match_all", match_all)
|
|
88
|
+
for err in _validate_single_edit(
|
|
89
|
+
edit, content, path_str, threshold, match_all=local_match_all
|
|
90
|
+
):
|
|
91
|
+
# Attach specific FIND CodeBlock node for surgical diagnostics
|
|
92
|
+
# Fallback to action node if find_node is missing
|
|
93
|
+
offending_node = edit.get("find_node") or action.node
|
|
94
|
+
action_errors.append(
|
|
95
|
+
ValidationError(
|
|
96
|
+
message=err.message,
|
|
97
|
+
file_path=err.file_path,
|
|
98
|
+
offending_node=offending_node,
|
|
99
|
+
)
|
|
100
|
+
)
|
|
101
|
+
return action_errors
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _validate_single_edit(
|
|
105
|
+
edit: dict,
|
|
106
|
+
content: str,
|
|
107
|
+
file_path: str,
|
|
108
|
+
threshold: Optional[float] = None,
|
|
109
|
+
match_all: bool = False,
|
|
110
|
+
) -> ValidationResult:
|
|
111
|
+
"""Validates a single edit dictionary."""
|
|
112
|
+
errors: ValidationResult = []
|
|
113
|
+
find_block = edit.get("find")
|
|
114
|
+
replace_block = edit.get("replace")
|
|
115
|
+
|
|
116
|
+
if os.environ.get("TEDDY_DEBUG"):
|
|
117
|
+
print("\n--- TEDDY DEBUG: PlanValidator ---")
|
|
118
|
+
print(f"File: {file_path}")
|
|
119
|
+
print(f"Content (repr): {repr(content)}")
|
|
120
|
+
print(f"Find Block (repr): {repr(find_block)}")
|
|
121
|
+
print("--- END TEDDY DEBUG ---\n")
|
|
122
|
+
|
|
123
|
+
if isinstance(find_block, str):
|
|
124
|
+
if find_block == replace_block:
|
|
125
|
+
# Treat identical blocks as a successful no-op (02-04-Context Automation slice)
|
|
126
|
+
return []
|
|
127
|
+
|
|
128
|
+
# Use the resilient matcher for all matching logic
|
|
129
|
+
matcher_kwargs = {}
|
|
130
|
+
if threshold is not None:
|
|
131
|
+
matcher_kwargs["threshold"] = threshold
|
|
132
|
+
|
|
133
|
+
diff_text, score, is_ambiguous, offset = find_best_match_and_diff(
|
|
134
|
+
content, find_block, **matcher_kwargs
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
effective_threshold = (
|
|
138
|
+
threshold if threshold is not None else DEFAULT_SIMILARITY_THRESHOLD
|
|
139
|
+
)
|
|
140
|
+
fence = get_fence_for_content(find_block)
|
|
141
|
+
|
|
142
|
+
if is_ambiguous and not match_all:
|
|
143
|
+
errors.append(
|
|
144
|
+
ValidationError(
|
|
145
|
+
message=(
|
|
146
|
+
f"The `FIND` block is ambiguous in: {file_path}\n"
|
|
147
|
+
f"**Similarity Score:** {score:.2f}\n"
|
|
148
|
+
f"**FIND Block:**\n"
|
|
149
|
+
f"{fence}\n{find_block}\n{fence}\n"
|
|
150
|
+
"**Hint:** Please provide a larger FIND block to uniquely identify the section, refactor the code to avoid duplication. Alternatively you can use `Match All: true` to change all occurrences in the file at once."
|
|
151
|
+
),
|
|
152
|
+
file_path=str(file_path),
|
|
153
|
+
)
|
|
154
|
+
)
|
|
155
|
+
elif score < effective_threshold:
|
|
156
|
+
error_msg = (
|
|
157
|
+
f"The `FIND` block could not be located in the file: "
|
|
158
|
+
f"{file_path}\n"
|
|
159
|
+
f"**Similarity Score:** {score:.2f}\n"
|
|
160
|
+
f"**Similarity Threshold:** {effective_threshold:.2f}\n"
|
|
161
|
+
f"**FIND Block:**\n"
|
|
162
|
+
f"{fence}\n{find_block}\n{fence}\n"
|
|
163
|
+
)
|
|
164
|
+
if diff_text:
|
|
165
|
+
# Prepend standard diff headers for high-clarity diagnostics
|
|
166
|
+
# ndiff(find, actual) means '-' is Provided and '+' is Actual
|
|
167
|
+
formatted_diff = f"--- Provided\n+++ Actual\n{diff_text}"
|
|
168
|
+
diff_fence = get_fence_for_content(formatted_diff)
|
|
169
|
+
error_msg += (
|
|
170
|
+
f"**Closest Match Diff:**\n{diff_fence}diff\n"
|
|
171
|
+
f"{formatted_diff}\n{diff_fence}\n"
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
hint = _get_already_applied_hint(
|
|
175
|
+
content, replace_block, effective_threshold, matcher_kwargs
|
|
176
|
+
)
|
|
177
|
+
error_msg += f"**Hint:** {hint}"
|
|
178
|
+
errors.append(ValidationError(message=error_msg, file_path=str(file_path)))
|
|
179
|
+
return errors
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def _get_already_applied_hint(
|
|
183
|
+
content: str,
|
|
184
|
+
replace_block: Optional[str],
|
|
185
|
+
threshold: float,
|
|
186
|
+
matcher_kwargs: dict,
|
|
187
|
+
) -> str:
|
|
188
|
+
"""Detects if the REPLACE block is already present in the content."""
|
|
189
|
+
replace_score = 0.0
|
|
190
|
+
if isinstance(replace_block, str):
|
|
191
|
+
_, replace_score, _, _ = find_best_match_and_diff(
|
|
192
|
+
content, replace_block, **matcher_kwargs
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
if replace_score >= threshold:
|
|
196
|
+
return (
|
|
197
|
+
"The FIND block was not found, but the REPLACE block is already "
|
|
198
|
+
"present. This change might have already been applied."
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
return (
|
|
202
|
+
"Review the provided diff and make sure to match the target content "
|
|
203
|
+
"exactly, including whitespace and indentations."
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
# Removed legacy functional validation rule in favor of EditActionValidator class.
|