pdd-cli 0.0.45__py3-none-any.whl → 0.0.90__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.
- pdd/__init__.py +4 -4
- pdd/agentic_common.py +863 -0
- pdd/agentic_crash.py +534 -0
- pdd/agentic_fix.py +1179 -0
- pdd/agentic_langtest.py +162 -0
- pdd/agentic_update.py +370 -0
- pdd/agentic_verify.py +183 -0
- pdd/auto_deps_main.py +15 -5
- pdd/auto_include.py +63 -5
- pdd/bug_main.py +3 -2
- pdd/bug_to_unit_test.py +2 -0
- pdd/change_main.py +11 -4
- pdd/cli.py +22 -1181
- pdd/cmd_test_main.py +73 -21
- pdd/code_generator.py +58 -18
- pdd/code_generator_main.py +672 -25
- pdd/commands/__init__.py +42 -0
- pdd/commands/analysis.py +248 -0
- pdd/commands/fix.py +140 -0
- pdd/commands/generate.py +257 -0
- pdd/commands/maintenance.py +174 -0
- pdd/commands/misc.py +79 -0
- pdd/commands/modify.py +230 -0
- pdd/commands/report.py +144 -0
- pdd/commands/templates.py +215 -0
- pdd/commands/utility.py +110 -0
- pdd/config_resolution.py +58 -0
- pdd/conflicts_main.py +8 -3
- pdd/construct_paths.py +258 -82
- pdd/context_generator.py +10 -2
- pdd/context_generator_main.py +113 -11
- pdd/continue_generation.py +47 -7
- pdd/core/__init__.py +0 -0
- pdd/core/cli.py +503 -0
- pdd/core/dump.py +554 -0
- pdd/core/errors.py +63 -0
- pdd/core/utils.py +90 -0
- pdd/crash_main.py +44 -11
- pdd/data/language_format.csv +71 -63
- pdd/data/llm_model.csv +20 -18
- pdd/detect_change_main.py +5 -4
- pdd/fix_code_loop.py +330 -76
- pdd/fix_error_loop.py +207 -61
- pdd/fix_errors_from_unit_tests.py +4 -3
- pdd/fix_main.py +75 -18
- pdd/fix_verification_errors.py +12 -100
- pdd/fix_verification_errors_loop.py +306 -272
- pdd/fix_verification_main.py +28 -9
- pdd/generate_output_paths.py +93 -10
- pdd/generate_test.py +16 -5
- pdd/get_jwt_token.py +9 -2
- pdd/get_run_command.py +73 -0
- pdd/get_test_command.py +68 -0
- pdd/git_update.py +70 -19
- pdd/incremental_code_generator.py +2 -2
- pdd/insert_includes.py +11 -3
- pdd/llm_invoke.py +1269 -103
- pdd/load_prompt_template.py +36 -10
- pdd/pdd_completion.fish +25 -2
- pdd/pdd_completion.sh +30 -4
- pdd/pdd_completion.zsh +79 -4
- pdd/postprocess.py +10 -3
- pdd/preprocess.py +228 -15
- pdd/preprocess_main.py +8 -5
- pdd/prompts/agentic_crash_explore_LLM.prompt +49 -0
- pdd/prompts/agentic_fix_explore_LLM.prompt +45 -0
- pdd/prompts/agentic_fix_harvest_only_LLM.prompt +48 -0
- pdd/prompts/agentic_fix_primary_LLM.prompt +85 -0
- pdd/prompts/agentic_update_LLM.prompt +1071 -0
- pdd/prompts/agentic_verify_explore_LLM.prompt +45 -0
- pdd/prompts/auto_include_LLM.prompt +100 -905
- pdd/prompts/detect_change_LLM.prompt +122 -20
- pdd/prompts/example_generator_LLM.prompt +22 -1
- pdd/prompts/extract_code_LLM.prompt +5 -1
- pdd/prompts/extract_program_code_fix_LLM.prompt +7 -1
- pdd/prompts/extract_prompt_update_LLM.prompt +7 -8
- pdd/prompts/extract_promptline_LLM.prompt +17 -11
- pdd/prompts/find_verification_errors_LLM.prompt +6 -0
- pdd/prompts/fix_code_module_errors_LLM.prompt +4 -2
- pdd/prompts/fix_errors_from_unit_tests_LLM.prompt +8 -0
- pdd/prompts/fix_verification_errors_LLM.prompt +22 -0
- pdd/prompts/generate_test_LLM.prompt +21 -6
- pdd/prompts/increase_tests_LLM.prompt +1 -5
- pdd/prompts/insert_includes_LLM.prompt +228 -108
- pdd/prompts/trace_LLM.prompt +25 -22
- pdd/prompts/unfinished_prompt_LLM.prompt +85 -1
- pdd/prompts/update_prompt_LLM.prompt +22 -1
- pdd/pytest_output.py +127 -12
- pdd/render_mermaid.py +236 -0
- pdd/setup_tool.py +648 -0
- pdd/simple_math.py +2 -0
- pdd/split_main.py +3 -2
- pdd/summarize_directory.py +49 -6
- pdd/sync_determine_operation.py +543 -98
- pdd/sync_main.py +81 -31
- pdd/sync_orchestration.py +1334 -751
- pdd/sync_tui.py +848 -0
- pdd/template_registry.py +264 -0
- pdd/templates/architecture/architecture_json.prompt +242 -0
- pdd/templates/generic/generate_prompt.prompt +174 -0
- pdd/trace.py +168 -12
- pdd/trace_main.py +4 -3
- pdd/track_cost.py +151 -61
- pdd/unfinished_prompt.py +49 -3
- pdd/update_main.py +549 -67
- pdd/update_model_costs.py +2 -2
- pdd/update_prompt.py +19 -4
- {pdd_cli-0.0.45.dist-info → pdd_cli-0.0.90.dist-info}/METADATA +19 -6
- pdd_cli-0.0.90.dist-info/RECORD +153 -0
- {pdd_cli-0.0.45.dist-info → pdd_cli-0.0.90.dist-info}/licenses/LICENSE +1 -1
- pdd_cli-0.0.45.dist-info/RECORD +0 -116
- {pdd_cli-0.0.45.dist-info → pdd_cli-0.0.90.dist-info}/WHEEL +0 -0
- {pdd_cli-0.0.45.dist-info → pdd_cli-0.0.90.dist-info}/entry_points.txt +0 -0
- {pdd_cli-0.0.45.dist-info → pdd_cli-0.0.90.dist-info}/top_level.txt +0 -0
pdd/agentic_crash.py
ADDED
|
@@ -0,0 +1,534 @@
|
|
|
1
|
+
# pdd/agentic_crash.py
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
import subprocess
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any, Iterable, Mapping
|
|
8
|
+
|
|
9
|
+
from rich.console import Console
|
|
10
|
+
|
|
11
|
+
from .agentic_common import get_available_agents, run_agentic_task
|
|
12
|
+
from .get_run_command import get_run_command_for_file
|
|
13
|
+
from .load_prompt_template import load_prompt_template
|
|
14
|
+
|
|
15
|
+
console = Console()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _require_existing_file(path: str | Path, description: str) -> Path:
|
|
19
|
+
"""
|
|
20
|
+
Normalize and validate a file path.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
path: Path-like or string pointing to a file.
|
|
24
|
+
description: Human-readable description used in error messages.
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
The resolved Path instance.
|
|
28
|
+
|
|
29
|
+
Raises:
|
|
30
|
+
ValueError: If the path string is empty.
|
|
31
|
+
FileNotFoundError: If the file does not exist.
|
|
32
|
+
FileNotFoundError: If the path exists but is not a file.
|
|
33
|
+
TypeError: If `path` is not a str or Path.
|
|
34
|
+
"""
|
|
35
|
+
if isinstance(path, str):
|
|
36
|
+
if not path.strip():
|
|
37
|
+
raise ValueError(f"{description} path is empty.")
|
|
38
|
+
p = Path(path.strip()).expanduser()
|
|
39
|
+
elif isinstance(path, Path):
|
|
40
|
+
p = path.expanduser()
|
|
41
|
+
else:
|
|
42
|
+
raise TypeError(
|
|
43
|
+
f"{description} must be a str or Path, got {type(path).__name__}."
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
if not p.exists():
|
|
47
|
+
raise FileNotFoundError(f"{description} does not exist: {p}")
|
|
48
|
+
if not p.is_file():
|
|
49
|
+
raise FileNotFoundError(f"{description} is not a file: {p}")
|
|
50
|
+
|
|
51
|
+
return p.resolve()
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _snapshot_mtimes(root: Path) -> dict[Path, float]:
|
|
55
|
+
"""
|
|
56
|
+
Take a snapshot of modification times for all files under a root directory.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
root: Directory to scan.
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
Mapping from absolute file paths to their modification times.
|
|
63
|
+
"""
|
|
64
|
+
mtimes: dict[Path, float] = {}
|
|
65
|
+
try:
|
|
66
|
+
for path in root.rglob("*"):
|
|
67
|
+
try:
|
|
68
|
+
if path.is_file():
|
|
69
|
+
mtimes[path.resolve()] = path.stat().st_mtime
|
|
70
|
+
except OSError:
|
|
71
|
+
# Ignore files we cannot stat
|
|
72
|
+
continue
|
|
73
|
+
except OSError:
|
|
74
|
+
# If the root cannot be traversed, just return empty
|
|
75
|
+
return {}
|
|
76
|
+
return mtimes
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _detect_changed_paths(
|
|
80
|
+
before: Mapping[Path, float],
|
|
81
|
+
after: Mapping[Path, float],
|
|
82
|
+
) -> list[Path]:
|
|
83
|
+
"""
|
|
84
|
+
Compute which files changed between two mtime snapshots.
|
|
85
|
+
|
|
86
|
+
A file is considered changed if:
|
|
87
|
+
- It existed before and its mtime changed, or
|
|
88
|
+
- It is new in the `after` snapshot, or
|
|
89
|
+
- It existed before but is missing in `after` (deleted).
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
before: Snapshot taken before the agent runs.
|
|
93
|
+
after: Snapshot taken after the agent runs.
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
List of Paths that changed.
|
|
97
|
+
"""
|
|
98
|
+
changed: list[Path] = []
|
|
99
|
+
|
|
100
|
+
# Modified or deleted files
|
|
101
|
+
for path, before_mtime in before.items():
|
|
102
|
+
after_mtime = after.get(path)
|
|
103
|
+
if after_mtime is None:
|
|
104
|
+
# Deleted
|
|
105
|
+
changed.append(path)
|
|
106
|
+
elif after_mtime != before_mtime:
|
|
107
|
+
# Modified
|
|
108
|
+
changed.append(path)
|
|
109
|
+
|
|
110
|
+
# New files
|
|
111
|
+
new_paths = set(after.keys()) - set(before.keys())
|
|
112
|
+
changed.extend(new_paths)
|
|
113
|
+
|
|
114
|
+
return changed
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _paths_to_relative_strings(paths: Iterable[Path], root: Path) -> list[str]:
|
|
118
|
+
"""
|
|
119
|
+
Convert absolute paths to strings, relative to a root when possible.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
paths: Iterable of Paths.
|
|
123
|
+
root: Root directory for relativization.
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
List of string paths.
|
|
127
|
+
"""
|
|
128
|
+
results: list[str] = []
|
|
129
|
+
for path in paths:
|
|
130
|
+
try:
|
|
131
|
+
rel = path.relative_to(root)
|
|
132
|
+
results.append(str(rel))
|
|
133
|
+
except ValueError:
|
|
134
|
+
# Path is outside root; return absolute
|
|
135
|
+
results.append(str(path))
|
|
136
|
+
return results
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _ensure_float(value: Any, default: float) -> float:
|
|
140
|
+
"""
|
|
141
|
+
Safely convert a value to float with a default.
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
value: Value to convert.
|
|
145
|
+
default: Default value if conversion fails.
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
Float value or the default.
|
|
149
|
+
"""
|
|
150
|
+
try:
|
|
151
|
+
return float(value) # type: ignore[arg-type]
|
|
152
|
+
except (TypeError, ValueError):
|
|
153
|
+
return default
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def _parse_agent_json(
|
|
157
|
+
raw_output: str,
|
|
158
|
+
*,
|
|
159
|
+
fallback_success: bool,
|
|
160
|
+
fallback_cost: float,
|
|
161
|
+
fallback_model: str | None,
|
|
162
|
+
verbose: bool,
|
|
163
|
+
quiet: bool,
|
|
164
|
+
) -> tuple[bool, str, float, str | None, list[str]]:
|
|
165
|
+
"""
|
|
166
|
+
Parse the JSON emitted by the agentic CLI.
|
|
167
|
+
|
|
168
|
+
The agent is expected to output a JSON string with fields:
|
|
169
|
+
- success: bool
|
|
170
|
+
- message: str
|
|
171
|
+
- cost: float
|
|
172
|
+
- model: str
|
|
173
|
+
- changed_files: list[str]
|
|
174
|
+
|
|
175
|
+
If parsing fails, fall back to the provided defaults.
|
|
176
|
+
|
|
177
|
+
Args:
|
|
178
|
+
raw_output: Raw stdout from the agent.
|
|
179
|
+
fallback_success: Success flag reported by run_agentic_task.
|
|
180
|
+
fallback_cost: Cost reported by run_agentic_task.
|
|
181
|
+
fallback_model: Provider/model reported by run_agentic_task.
|
|
182
|
+
verbose: Whether to log debug information.
|
|
183
|
+
quiet: Whether to suppress console output.
|
|
184
|
+
|
|
185
|
+
Returns:
|
|
186
|
+
Tuple of (success, message, cost, model, changed_files).
|
|
187
|
+
"""
|
|
188
|
+
success: bool = fallback_success
|
|
189
|
+
message: str = raw_output.strip() or "Agentic CLI produced no output."
|
|
190
|
+
cost: float = fallback_cost
|
|
191
|
+
model: str | None = fallback_model
|
|
192
|
+
changed_files: list[str] = []
|
|
193
|
+
|
|
194
|
+
if not raw_output.strip():
|
|
195
|
+
return success, message, cost, model, changed_files
|
|
196
|
+
|
|
197
|
+
text = raw_output.strip()
|
|
198
|
+
data: Any | None = None
|
|
199
|
+
|
|
200
|
+
try:
|
|
201
|
+
data = json.loads(text)
|
|
202
|
+
except json.JSONDecodeError:
|
|
203
|
+
# Try to extract a JSON object substring if the output is mixed.
|
|
204
|
+
start = text.find("{")
|
|
205
|
+
end = text.rfind("}")
|
|
206
|
+
if start != -1 and end != -1 and end > start:
|
|
207
|
+
try:
|
|
208
|
+
data = json.loads(text[start : end + 1])
|
|
209
|
+
except json.JSONDecodeError:
|
|
210
|
+
data = None
|
|
211
|
+
|
|
212
|
+
if not isinstance(data, dict):
|
|
213
|
+
if verbose and not quiet:
|
|
214
|
+
console.print(
|
|
215
|
+
"[yellow]Warning:[/yellow] Agent output was not valid JSON; "
|
|
216
|
+
"using raw output as message."
|
|
217
|
+
)
|
|
218
|
+
return success, message, cost, model, changed_files
|
|
219
|
+
|
|
220
|
+
# success
|
|
221
|
+
if "success" in data:
|
|
222
|
+
success = bool(data["success"])
|
|
223
|
+
|
|
224
|
+
# message
|
|
225
|
+
if "message" in data and isinstance(data["message"], str):
|
|
226
|
+
message = data["message"].strip() or message
|
|
227
|
+
|
|
228
|
+
# cost
|
|
229
|
+
if "cost" in data:
|
|
230
|
+
cost = _ensure_float(data["cost"], cost)
|
|
231
|
+
|
|
232
|
+
# model
|
|
233
|
+
if "model" in data and data["model"] is not None:
|
|
234
|
+
model = str(data["model"])
|
|
235
|
+
|
|
236
|
+
# changed_files
|
|
237
|
+
raw_changed = data.get("changed_files")
|
|
238
|
+
if isinstance(raw_changed, list):
|
|
239
|
+
changed_files = [str(item) for item in raw_changed]
|
|
240
|
+
|
|
241
|
+
return success, message, cost, model, changed_files
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def _run_program_file(
|
|
245
|
+
program_path: Path,
|
|
246
|
+
project_root: Path,
|
|
247
|
+
*,
|
|
248
|
+
verbose: bool,
|
|
249
|
+
quiet: bool,
|
|
250
|
+
) -> tuple[bool, str]:
|
|
251
|
+
"""
|
|
252
|
+
Run the program file to verify that the crash has been fixed.
|
|
253
|
+
|
|
254
|
+
Uses pdd.get_run_command.get_run_command_for_file to determine the
|
|
255
|
+
appropriate run command.
|
|
256
|
+
|
|
257
|
+
Args:
|
|
258
|
+
program_path: Path to the program file that previously crashed.
|
|
259
|
+
project_root: Directory to use as the working directory.
|
|
260
|
+
verbose: Whether to log detailed execution information.
|
|
261
|
+
quiet: Whether to suppress console output.
|
|
262
|
+
|
|
263
|
+
Returns:
|
|
264
|
+
Tuple (success, output_or_error_message).
|
|
265
|
+
"""
|
|
266
|
+
command = get_run_command_for_file(str(program_path))
|
|
267
|
+
|
|
268
|
+
if not command:
|
|
269
|
+
msg = (
|
|
270
|
+
f"No run command configured for program file '{program_path}'. "
|
|
271
|
+
"Unable to verify crash fix."
|
|
272
|
+
)
|
|
273
|
+
if not quiet:
|
|
274
|
+
console.print(f"[red]{msg}[/red]")
|
|
275
|
+
return False, msg
|
|
276
|
+
|
|
277
|
+
if verbose and not quiet:
|
|
278
|
+
console.print(
|
|
279
|
+
"[cyan]Verifying crash fix by running:[/cyan] "
|
|
280
|
+
f"[white]{command}[/white] (cwd={project_root})"
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
try:
|
|
284
|
+
result = subprocess.run(
|
|
285
|
+
command,
|
|
286
|
+
shell=True,
|
|
287
|
+
cwd=project_root,
|
|
288
|
+
capture_output=True,
|
|
289
|
+
text=True,
|
|
290
|
+
)
|
|
291
|
+
except OSError as exc:
|
|
292
|
+
msg = f"Failed to execute program '{program_path}': {exc}"
|
|
293
|
+
if not quiet:
|
|
294
|
+
console.print(f"[red]{msg}[/red]")
|
|
295
|
+
return False, msg
|
|
296
|
+
|
|
297
|
+
stdout = result.stdout or ""
|
|
298
|
+
stderr = result.stderr or ""
|
|
299
|
+
combined_output = (stdout + ("\n" + stderr if stderr else "")).strip()
|
|
300
|
+
|
|
301
|
+
if result.returncode == 0:
|
|
302
|
+
if verbose and not quiet:
|
|
303
|
+
console.print("[green]Program run succeeded without crashing.[/green]")
|
|
304
|
+
if combined_output:
|
|
305
|
+
console.print("[green]Program output:[/green]")
|
|
306
|
+
console.print(combined_output)
|
|
307
|
+
return True, combined_output or "Program completed successfully."
|
|
308
|
+
else:
|
|
309
|
+
msg = (
|
|
310
|
+
f"Program '{program_path}' exited with status {result.returncode}.\n"
|
|
311
|
+
f"{combined_output}"
|
|
312
|
+
).strip()
|
|
313
|
+
if not quiet:
|
|
314
|
+
console.print("[red]Verification run failed.[/red]")
|
|
315
|
+
if verbose and combined_output:
|
|
316
|
+
console.print(combined_output)
|
|
317
|
+
return False, msg
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def run_agentic_crash(
|
|
321
|
+
prompt_file: str | Path,
|
|
322
|
+
code_file: str | Path,
|
|
323
|
+
program_file: str | Path,
|
|
324
|
+
crash_log_file: str | Path,
|
|
325
|
+
*,
|
|
326
|
+
verbose: bool = False,
|
|
327
|
+
quiet: bool = False,
|
|
328
|
+
) -> tuple[bool, str, float, str | None, list[str]]:
|
|
329
|
+
"""
|
|
330
|
+
Agentic fallback for the PDD crash command.
|
|
331
|
+
|
|
332
|
+
When the normal LLM-based crash loop fails, this function delegates to an
|
|
333
|
+
agentic CLI with full codebase exploration capability. It runs in a
|
|
334
|
+
single-pass "explore" mode and then re-runs the program file to verify
|
|
335
|
+
whether the crash has been fixed.
|
|
336
|
+
|
|
337
|
+
The function:
|
|
338
|
+
|
|
339
|
+
1. Loads the `agentic_crash_explore_LLM` prompt template.
|
|
340
|
+
2. Records file mtimes under the project root before running the agent.
|
|
341
|
+
3. Invokes the agent in explore mode (single pass).
|
|
342
|
+
4. Parses the agent's JSON output:
|
|
343
|
+
- success (bool)
|
|
344
|
+
- message (str)
|
|
345
|
+
- cost (float)
|
|
346
|
+
- model (str)
|
|
347
|
+
- changed_files (list[str])
|
|
348
|
+
5. Detects actual file changes based on mtimes and merges them with the
|
|
349
|
+
agent-reported changed_files.
|
|
350
|
+
6. Runs the program file to verify the fix.
|
|
351
|
+
7. Returns a 5-tuple:
|
|
352
|
+
(success, message, cost, model, changed_files)
|
|
353
|
+
|
|
354
|
+
Args:
|
|
355
|
+
prompt_file: Path to the prompt file (source of truth).
|
|
356
|
+
code_file: Path to the generated code file that may need fixing.
|
|
357
|
+
program_file: Path to the program that crashed.
|
|
358
|
+
crash_log_file: Path to a log containing the crash traceback and
|
|
359
|
+
previous fix attempts.
|
|
360
|
+
verbose: If True, emit detailed logs.
|
|
361
|
+
quiet: If True, suppress console output (overrides `verbose`).
|
|
362
|
+
|
|
363
|
+
Returns:
|
|
364
|
+
Tuple (success, message, cost, model, changed_files):
|
|
365
|
+
success: Overall success after verification (agent + program run).
|
|
366
|
+
message: Human-readable summary including any error details.
|
|
367
|
+
cost: Estimated LLM cost reported by the agent (or 0.0 on failure).
|
|
368
|
+
model: Model/provider name used by the agent, if available.
|
|
369
|
+
changed_files: Unique list of changed files (relative to project
|
|
370
|
+
root when possible).
|
|
371
|
+
"""
|
|
372
|
+
# Normalize and validate inputs
|
|
373
|
+
try:
|
|
374
|
+
prompt_path = _require_existing_file(prompt_file, "Prompt file")
|
|
375
|
+
code_path = _require_existing_file(code_file, "Code file")
|
|
376
|
+
program_path = _require_existing_file(program_file, "Program file")
|
|
377
|
+
except (ValueError, FileNotFoundError, TypeError) as exc:
|
|
378
|
+
msg = f"Agentic crash fallback aborted due to invalid input: {exc}"
|
|
379
|
+
if not quiet:
|
|
380
|
+
console.print(f"[red]{msg}[/red]")
|
|
381
|
+
return False, msg, 0.0, None, []
|
|
382
|
+
|
|
383
|
+
crash_log_text: str = ""
|
|
384
|
+
crash_log_path = Path(crash_log_file).expanduser()
|
|
385
|
+
try:
|
|
386
|
+
if crash_log_path.exists() and crash_log_path.is_file():
|
|
387
|
+
crash_log_text = crash_log_path.read_text(encoding="utf-8")
|
|
388
|
+
elif verbose and not quiet:
|
|
389
|
+
console.print(
|
|
390
|
+
f"[yellow]Warning:[/yellow] Crash log file '{crash_log_path}' "
|
|
391
|
+
"does not exist or is not a regular file."
|
|
392
|
+
)
|
|
393
|
+
except OSError as exc:
|
|
394
|
+
if verbose and not quiet:
|
|
395
|
+
console.print(
|
|
396
|
+
f"[yellow]Warning:[/yellow] Could not read crash log file "
|
|
397
|
+
f"'{crash_log_path}': {exc}"
|
|
398
|
+
)
|
|
399
|
+
|
|
400
|
+
project_root = prompt_path.parent
|
|
401
|
+
|
|
402
|
+
if verbose and not quiet:
|
|
403
|
+
console.print("[cyan]Starting agentic crash fallback (explore mode)...[/cyan]")
|
|
404
|
+
console.print(f"Prompt file : [white]{prompt_path}[/white]")
|
|
405
|
+
console.print(f"Code file : [white]{code_path}[/white]")
|
|
406
|
+
console.print(f"Program file: [white]{program_path}[/white]")
|
|
407
|
+
console.print(f"Project root: [white]{project_root}[/white]")
|
|
408
|
+
|
|
409
|
+
# Check for available agent providers
|
|
410
|
+
agents = get_available_agents()
|
|
411
|
+
if not agents:
|
|
412
|
+
msg = (
|
|
413
|
+
"No agentic CLI providers detected. "
|
|
414
|
+
"Ensure the appropriate CLI tools are installed and API keys are set."
|
|
415
|
+
)
|
|
416
|
+
if not quiet:
|
|
417
|
+
console.print(f"[red]{msg}[/red]")
|
|
418
|
+
return False, msg, 0.0, None, []
|
|
419
|
+
|
|
420
|
+
if verbose and not quiet:
|
|
421
|
+
console.print(
|
|
422
|
+
"[green]Available agent providers:[/green] "
|
|
423
|
+
+ ", ".join(str(a) for a in agents)
|
|
424
|
+
)
|
|
425
|
+
|
|
426
|
+
# Load prompt template
|
|
427
|
+
template = load_prompt_template("agentic_crash_explore_LLM")
|
|
428
|
+
if not template:
|
|
429
|
+
msg = "Failed to load prompt template 'agentic_crash_explore_LLM'."
|
|
430
|
+
if not quiet:
|
|
431
|
+
console.print(f"[red]{msg}[/red]")
|
|
432
|
+
return False, msg, 0.0, None, []
|
|
433
|
+
|
|
434
|
+
# Prepare instruction for the agent (explore mode only).
|
|
435
|
+
previous_attempts = crash_log_text.strip() or "No previous attempts available."
|
|
436
|
+
try:
|
|
437
|
+
instruction = template.format(
|
|
438
|
+
prompt_path=str(prompt_path),
|
|
439
|
+
code_path=str(code_path),
|
|
440
|
+
program_path=str(program_path),
|
|
441
|
+
project_root=str(project_root),
|
|
442
|
+
previous_attempts=previous_attempts,
|
|
443
|
+
)
|
|
444
|
+
except Exception as exc:
|
|
445
|
+
msg = f"Error formatting agent prompt template: {exc}"
|
|
446
|
+
if not quiet:
|
|
447
|
+
console.print(f"[red]{msg}[/red]")
|
|
448
|
+
return False, msg, 0.0, None, []
|
|
449
|
+
|
|
450
|
+
# 1) Snapshot file mtimes before the agent runs
|
|
451
|
+
before_mtimes = _snapshot_mtimes(project_root)
|
|
452
|
+
|
|
453
|
+
# 2) Run the agent (single-pass, explore mode implied by the prompt)
|
|
454
|
+
try:
|
|
455
|
+
agent_cli_success, raw_output, base_cost, provider_used = run_agentic_task(
|
|
456
|
+
instruction=instruction,
|
|
457
|
+
cwd=project_root,
|
|
458
|
+
verbose=verbose,
|
|
459
|
+
quiet=quiet,
|
|
460
|
+
label="agentic_crash_explore",
|
|
461
|
+
)
|
|
462
|
+
except Exception as exc: # noqa: BLE001
|
|
463
|
+
msg = f"Agentic CLI invocation failed: {exc}"
|
|
464
|
+
if not quiet:
|
|
465
|
+
console.print(f"[red]{msg}[/red]")
|
|
466
|
+
# No JSON to parse; no changes we can reliably detect beyond this point.
|
|
467
|
+
return False, msg, 0.0, None, []
|
|
468
|
+
|
|
469
|
+
# 3) Snapshot mtimes after the agent completes
|
|
470
|
+
after_mtimes = _snapshot_mtimes(project_root)
|
|
471
|
+
changed_paths_by_mtime = _detect_changed_paths(before_mtimes, after_mtimes)
|
|
472
|
+
changed_files_from_fs = _paths_to_relative_strings(changed_paths_by_mtime, project_root)
|
|
473
|
+
|
|
474
|
+
# 4) Parse JSON emitted by the agent to get structured information
|
|
475
|
+
agent_success, agent_message, agent_cost, agent_model, agent_changed_files = (
|
|
476
|
+
_parse_agent_json(
|
|
477
|
+
raw_output,
|
|
478
|
+
fallback_success=agent_cli_success,
|
|
479
|
+
fallback_cost=base_cost,
|
|
480
|
+
fallback_model=provider_used,
|
|
481
|
+
verbose=verbose,
|
|
482
|
+
quiet=quiet,
|
|
483
|
+
)
|
|
484
|
+
)
|
|
485
|
+
|
|
486
|
+
# Merge changed_files from JSON and filesystem mtimes
|
|
487
|
+
all_changed_files_set = set(agent_changed_files)
|
|
488
|
+
all_changed_files_set.update(changed_files_from_fs)
|
|
489
|
+
all_changed_files = sorted(all_changed_files_set)
|
|
490
|
+
|
|
491
|
+
# 5) Run the program file after the agent's fix attempt to verify the fix
|
|
492
|
+
program_success, program_message = _run_program_file(
|
|
493
|
+
program_path=program_path,
|
|
494
|
+
project_root=project_root,
|
|
495
|
+
verbose=verbose,
|
|
496
|
+
quiet=quiet,
|
|
497
|
+
)
|
|
498
|
+
|
|
499
|
+
# Combine agent's view of success with verification result
|
|
500
|
+
overall_success = bool(agent_success) and bool(program_success)
|
|
501
|
+
|
|
502
|
+
if program_success:
|
|
503
|
+
# Verification succeeded
|
|
504
|
+
if agent_message:
|
|
505
|
+
combined_message = agent_message
|
|
506
|
+
else:
|
|
507
|
+
combined_message = "Agentic crash fix appears successful; program ran without crashing."
|
|
508
|
+
else:
|
|
509
|
+
# Verification failed; append details
|
|
510
|
+
verification_info = (
|
|
511
|
+
"Verification run failed; the program still crashes or exits with an error."
|
|
512
|
+
)
|
|
513
|
+
if agent_message:
|
|
514
|
+
combined_message = (
|
|
515
|
+
agent_message.rstrip()
|
|
516
|
+
+ "\n\n"
|
|
517
|
+
+ verification_info
|
|
518
|
+
+ "\n\nVerification details:\n"
|
|
519
|
+
+ program_message
|
|
520
|
+
)
|
|
521
|
+
else:
|
|
522
|
+
combined_message = verification_info + "\n\n" + program_message
|
|
523
|
+
|
|
524
|
+
if verbose and not quiet:
|
|
525
|
+
status_color = "green" if overall_success else "red"
|
|
526
|
+
console.print(
|
|
527
|
+
f"[{status_color}]Agentic crash fallback completed. "
|
|
528
|
+
f"Success={overall_success}[/]"
|
|
529
|
+
)
|
|
530
|
+
|
|
531
|
+
# If the agent did not provide a model name, fall back to provider_used
|
|
532
|
+
final_model = agent_model or provider_used
|
|
533
|
+
|
|
534
|
+
return overall_success, combined_message, agent_cost, final_model, all_changed_files
|