devagent-cli 3.2.2__tar.gz → 3.3.0__tar.gz
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.
- {devagent_cli-3.2.2/devagent_cli.egg-info → devagent_cli-3.3.0}/PKG-INFO +22 -25
- {devagent_cli-3.2.2 → devagent_cli-3.3.0}/README.md +20 -23
- devagent_cli-3.3.0/devagent/__init__.py +1 -0
- {devagent_cli-3.2.2 → devagent_cli-3.3.0}/devagent/app/agent.py +35 -20
- {devagent_cli-3.2.2 → devagent_cli-3.3.0}/devagent/app/state.py +1 -0
- {devagent_cli-3.2.2 → devagent_cli-3.3.0}/devagent/cli.py +16 -1
- devagent_cli-3.3.0/devagent/tools/file_map.py +21 -0
- {devagent_cli-3.2.2 → devagent_cli-3.3.0}/devagent/tools/file_ops.py +22 -2
- devagent_cli-3.3.0/devagent/utils/ast_utils.py +83 -0
- {devagent_cli-3.2.2 → devagent_cli-3.3.0}/devagent/utils/config.py +2 -0
- {devagent_cli-3.2.2 → devagent_cli-3.3.0}/devagent/utils/safety.py +20 -3
- {devagent_cli-3.2.2 → devagent_cli-3.3.0/devagent_cli.egg-info}/PKG-INFO +22 -25
- {devagent_cli-3.2.2 → devagent_cli-3.3.0}/devagent_cli.egg-info/SOURCES.txt +2 -0
- {devagent_cli-3.2.2 → devagent_cli-3.3.0}/pyproject.toml +2 -2
- devagent_cli-3.2.2/devagent/__init__.py +0 -1
- {devagent_cli-3.2.2 → devagent_cli-3.3.0}/LICENSE +0 -0
- {devagent_cli-3.2.2 → devagent_cli-3.3.0}/devagent/app/__init__.py +0 -0
- {devagent_cli-3.2.2 → devagent_cli-3.3.0}/devagent/app/llm.py +0 -0
- {devagent_cli-3.2.2 → devagent_cli-3.3.0}/devagent/app/memory.py +0 -0
- {devagent_cli-3.2.2 → devagent_cli-3.3.0}/devagent/app/patcher.py +0 -0
- {devagent_cli-3.2.2 → devagent_cli-3.3.0}/devagent/app/planner.py +0 -0
- {devagent_cli-3.2.2 → devagent_cli-3.3.0}/devagent/app/reviewer.py +0 -0
- {devagent_cli-3.2.2 → devagent_cli-3.3.0}/devagent/app/sandbox.py +0 -0
- {devagent_cli-3.2.2 → devagent_cli-3.3.0}/devagent/tools/__init__.py +0 -0
- {devagent_cli-3.2.2 → devagent_cli-3.3.0}/devagent/tools/benchmark_runner.py +0 -0
- {devagent_cli-3.2.2 → devagent_cli-3.3.0}/devagent/tools/git_tools.py +0 -0
- {devagent_cli-3.2.2 → devagent_cli-3.3.0}/devagent/tools/linter.py +0 -0
- {devagent_cli-3.2.2 → devagent_cli-3.3.0}/devagent/tools/search.py +0 -0
- {devagent_cli-3.2.2 → devagent_cli-3.3.0}/devagent/tools/semantic_search.py +0 -0
- {devagent_cli-3.2.2 → devagent_cli-3.3.0}/devagent/tools/surgical_patcher.py +0 -0
- {devagent_cli-3.2.2 → devagent_cli-3.3.0}/devagent/tools/test_runner.py +0 -0
- {devagent_cli-3.2.2 → devagent_cli-3.3.0}/devagent/utils/__init__.py +0 -0
- {devagent_cli-3.2.2 → devagent_cli-3.3.0}/devagent/utils/logger.py +0 -0
- {devagent_cli-3.2.2 → devagent_cli-3.3.0}/devagent/utils/metrics.py +0 -0
- {devagent_cli-3.2.2 → devagent_cli-3.3.0}/devagent_cli.egg-info/dependency_links.txt +0 -0
- {devagent_cli-3.2.2 → devagent_cli-3.3.0}/devagent_cli.egg-info/entry_points.txt +0 -0
- {devagent_cli-3.2.2 → devagent_cli-3.3.0}/devagent_cli.egg-info/requires.txt +0 -0
- {devagent_cli-3.2.2 → devagent_cli-3.3.0}/devagent_cli.egg-info/top_level.txt +0 -0
- {devagent_cli-3.2.2 → devagent_cli-3.3.0}/setup.cfg +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: devagent-cli
|
|
3
|
-
Version: 3.
|
|
4
|
-
Summary:
|
|
3
|
+
Version: 3.3.0
|
|
4
|
+
Summary: A local autonomous coding agent CLI powered by Ollama.
|
|
5
5
|
Author: Vedant Jadhav
|
|
6
6
|
License: MIT
|
|
7
7
|
Keywords: ai,agent,coding,ollama,local,devagent,devagent-cli
|
|
@@ -49,26 +49,23 @@ Dynamic: license-file
|
|
|
49
49
|
|
|
50
50
|
</div>
|
|
51
51
|
|
|
52
|
-
##
|
|
52
|
+
## 🛡️ Why DevAgent?
|
|
53
53
|
|
|
54
|
-
|
|
54
|
+
DevAgent is built on a "Safety-First" architecture for **high-integrity developer infrastructure**. Unlike chatbots that guess code, DevAgent is a local autonomous agent that operates within a strictly observed environment.
|
|
55
55
|
|
|
56
|
-
|
|
56
|
+
### 📊 Empirical Validation
|
|
57
|
+
We don't hide our limitations. DevAgent v3.2.3 has been stress-tested against real-world, "messy" repositories, providing a transparent **[Benchmark Report](docs/benchmarks.md)**.
|
|
57
58
|
|
|
58
|
-
| |
|
|
59
|
+
| | Other Agents | DevAgent |
|
|
59
60
|
|---|---|---|
|
|
60
|
-
|
|
|
61
|
-
|
|
|
62
|
-
|
|
|
63
|
-
|
|
|
64
|
-
|
|
|
65
|
-
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
| Works offline | ❌ | ✅ 100% local via Ollama |
|
|
69
|
-
| Costs money | 💸 | ✅ Free forever |
|
|
70
|
-
|
|
71
|
-
> **Philosophy:** Execution > Reasoning. Tools > Hallucination. Retrieval > Huge Context. Reliability > Intelligence.
|
|
61
|
+
| **Safety Isolation** | ❌ | ✅ Strict Sandbox Mode |
|
|
62
|
+
| **Recovery** | ❌ | ✅ Git-native + Snapshot Rollback |
|
|
63
|
+
| **Validation** | ❌ | ✅ Empirical Stress Tests |
|
|
64
|
+
| **Transparency** | ❌ | ✅ Visible Failure Taxonomy |
|
|
65
|
+
| **Privacy** | ❌ | ✅ 100% Local (Ollama) |
|
|
66
|
+
| **Costs** | 💸 | ✅ Zero API Costs |
|
|
67
|
+
|
|
68
|
+
> **Philosophy:** Safety > Intelligence. Observability > Reasoning. Retrieval > Huge Context. Reliability > Hype.
|
|
72
69
|
|
|
73
70
|
---
|
|
74
71
|
|
|
@@ -141,13 +138,13 @@ devagent run --task "Fix the divide-by-zero bug" --root ./demo_project
|
|
|
141
138
|
| `devagent models` | List available Ollama models |
|
|
142
139
|
| `devagent version` | Show current version |
|
|
143
140
|
|
|
144
|
-
|
|
145
|
-
DevAgent is built for **
|
|
146
|
-
- **
|
|
147
|
-
- **Auto-Snapshot**: Creates a safety restore point before every
|
|
148
|
-
- **Rollback**: Revert
|
|
149
|
-
- **
|
|
150
|
-
- **
|
|
141
|
+
#### 🛡️ Reliability & Safety
|
|
142
|
+
DevAgent is built for **production-grade reliability**:
|
|
143
|
+
- **Isolated Sandbox**: Agent works in `sandbox_workspace/`, keeping your source clean until success.
|
|
144
|
+
- **Auto-Snapshot**: Creates a safety restore point before every execution.
|
|
145
|
+
- **Instant Rollback**: Revert agent changes with `devagent rollback`.
|
|
146
|
+
- **Traceability**: Every thought and tool call is logged to `logs/run.json`.
|
|
147
|
+
- **Environment Awareness**: Detects and uses your project's Python environment automatically.
|
|
151
148
|
|
|
152
149
|
#### 🕹️ Interactive Mode
|
|
153
150
|
Run with `--interactive` (or `-i`) to review colorized diffs before they are applied to your project.
|
|
@@ -23,26 +23,23 @@
|
|
|
23
23
|
|
|
24
24
|
</div>
|
|
25
25
|
|
|
26
|
-
##
|
|
26
|
+
## 🛡️ Why DevAgent?
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
DevAgent is built on a "Safety-First" architecture for **high-integrity developer infrastructure**. Unlike chatbots that guess code, DevAgent is a local autonomous agent that operates within a strictly observed environment.
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
### 📊 Empirical Validation
|
|
31
|
+
We don't hide our limitations. DevAgent v3.2.3 has been stress-tested against real-world, "messy" repositories, providing a transparent **[Benchmark Report](docs/benchmarks.md)**.
|
|
31
32
|
|
|
32
|
-
| |
|
|
33
|
+
| | Other Agents | DevAgent |
|
|
33
34
|
|---|---|---|
|
|
34
|
-
|
|
|
35
|
-
|
|
|
36
|
-
|
|
|
37
|
-
|
|
|
38
|
-
|
|
|
39
|
-
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
| Works offline | ❌ | ✅ 100% local via Ollama |
|
|
43
|
-
| Costs money | 💸 | ✅ Free forever |
|
|
44
|
-
|
|
45
|
-
> **Philosophy:** Execution > Reasoning. Tools > Hallucination. Retrieval > Huge Context. Reliability > Intelligence.
|
|
35
|
+
| **Safety Isolation** | ❌ | ✅ Strict Sandbox Mode |
|
|
36
|
+
| **Recovery** | ❌ | ✅ Git-native + Snapshot Rollback |
|
|
37
|
+
| **Validation** | ❌ | ✅ Empirical Stress Tests |
|
|
38
|
+
| **Transparency** | ❌ | ✅ Visible Failure Taxonomy |
|
|
39
|
+
| **Privacy** | ❌ | ✅ 100% Local (Ollama) |
|
|
40
|
+
| **Costs** | 💸 | ✅ Zero API Costs |
|
|
41
|
+
|
|
42
|
+
> **Philosophy:** Safety > Intelligence. Observability > Reasoning. Retrieval > Huge Context. Reliability > Hype.
|
|
46
43
|
|
|
47
44
|
---
|
|
48
45
|
|
|
@@ -115,13 +112,13 @@ devagent run --task "Fix the divide-by-zero bug" --root ./demo_project
|
|
|
115
112
|
| `devagent models` | List available Ollama models |
|
|
116
113
|
| `devagent version` | Show current version |
|
|
117
114
|
|
|
118
|
-
|
|
119
|
-
DevAgent is built for **
|
|
120
|
-
- **
|
|
121
|
-
- **Auto-Snapshot**: Creates a safety restore point before every
|
|
122
|
-
- **Rollback**: Revert
|
|
123
|
-
- **
|
|
124
|
-
- **
|
|
115
|
+
#### 🛡️ Reliability & Safety
|
|
116
|
+
DevAgent is built for **production-grade reliability**:
|
|
117
|
+
- **Isolated Sandbox**: Agent works in `sandbox_workspace/`, keeping your source clean until success.
|
|
118
|
+
- **Auto-Snapshot**: Creates a safety restore point before every execution.
|
|
119
|
+
- **Instant Rollback**: Revert agent changes with `devagent rollback`.
|
|
120
|
+
- **Traceability**: Every thought and tool call is logged to `logs/run.json`.
|
|
121
|
+
- **Environment Awareness**: Detects and uses your project's Python environment automatically.
|
|
125
122
|
|
|
126
123
|
#### 🕹️ Interactive Mode
|
|
127
124
|
Run with `--interactive` (or `-i`) to review colorized diffs before they are applied to your project.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "3.2.3"
|
|
@@ -30,6 +30,7 @@ from devagent.app.patcher import generate_diff, apply_patch, format_diff_summary
|
|
|
30
30
|
from devagent.app.memory import WorkingMemory, chunk_project, SemanticIndex
|
|
31
31
|
from devagent.tools.search import search_code
|
|
32
32
|
from devagent.tools.file_ops import read_file, write_file, list_files
|
|
33
|
+
from devagent.tools.file_map import get_file_map
|
|
33
34
|
from devagent.tools.test_runner import run_tests
|
|
34
35
|
from devagent.tools.linter import lint_code
|
|
35
36
|
from devagent.tools.git_tools import git_diff, git_status
|
|
@@ -47,7 +48,6 @@ You are a coding agent. Your task:
|
|
|
47
48
|
|
|
48
49
|
Project root: {project_root}
|
|
49
50
|
Current step: {step}/{max_steps}
|
|
50
|
-
Previous attempts: {attempts}
|
|
51
51
|
|
|
52
52
|
{plan_context}
|
|
53
53
|
|
|
@@ -59,9 +59,10 @@ Previous attempts: {attempts}
|
|
|
59
59
|
|
|
60
60
|
Decide the SINGLE next action. Choose ONE:
|
|
61
61
|
- list_files: <relative_path>
|
|
62
|
+
- get_file_map: <relative_path>
|
|
62
63
|
- search_code: <keyword>
|
|
63
64
|
- semantic_search: <query>
|
|
64
|
-
- read_file: <relative_path>
|
|
65
|
+
- read_file: <relative_path>[:L<start>-<end>]
|
|
65
66
|
- write_file: <relative_path>
|
|
66
67
|
- surgical_patch: <file> | <SEARCH> | <REPLACE>
|
|
67
68
|
- run_tests: <optional_path>
|
|
@@ -69,12 +70,11 @@ Decide the SINGLE next action. Choose ONE:
|
|
|
69
70
|
- git_diff
|
|
70
71
|
|
|
71
72
|
STRATEGY:
|
|
72
|
-
1. START by using 'run_tests' to identify the
|
|
73
|
-
2.
|
|
74
|
-
3.
|
|
75
|
-
4. USE '
|
|
76
|
-
5. ALWAYS use full relative paths
|
|
77
|
-
6. If tests pass but system errors remain, verify the file path is correct.
|
|
73
|
+
1. START by using 'run_tests' to identify the failure.
|
|
74
|
+
2. For large files (>50 lines), use 'get_file_map' FIRST to see the structure.
|
|
75
|
+
3. USE 'read_file' with line ranges (e.g. file.py:L10-L50) for targeted reading.
|
|
76
|
+
4. USE 'surgical_patch' for logic fixes. Format: file.py | <SEARCH> | <REPLACE>
|
|
77
|
+
5. ALWAYS use full relative paths.
|
|
78
78
|
|
|
79
79
|
Reply in this EXACT format (two lines only):
|
|
80
80
|
THOUGHT: <your reasoning>
|
|
@@ -92,21 +92,11 @@ CURRENT CODE:
|
|
|
92
92
|
|
|
93
93
|
{retrieval_context}
|
|
94
94
|
|
|
95
|
-
TASK: {task}
|
|
96
|
-
FILE: {file_path}
|
|
97
|
-
|
|
98
|
-
{error_context}
|
|
99
|
-
|
|
100
|
-
{retrieval_context}
|
|
101
|
-
|
|
102
|
-
CURRENT CODE:
|
|
103
|
-
{file_content}
|
|
104
|
-
|
|
105
95
|
Fix the bug. Output ONLY the COMPLETE Python code.
|
|
106
96
|
"""
|
|
107
97
|
|
|
108
98
|
EXTRACT_ACTION_PATTERN = re.compile(
|
|
109
|
-
r"ACTION:\s*(search_code|semantic_search|read_file|write_file|surgical_patch|run_tests|lint_code|list_files|git_diff)\s*:?\s*(.*)",
|
|
99
|
+
r"ACTION:\s*(get_file_map|search_code|semantic_search|read_file|write_file|surgical_patch|run_tests|lint_code|list_files|git_diff)\s*:?\s*(.*)",
|
|
110
100
|
re.IGNORECASE,
|
|
111
101
|
)
|
|
112
102
|
|
|
@@ -280,6 +270,12 @@ class Agent:
|
|
|
280
270
|
observation = self._execute_action(action_name, action_arg)
|
|
281
271
|
self.state.last_observation = observation
|
|
282
272
|
self.state.observations.append(observation[:2000])
|
|
273
|
+
|
|
274
|
+
self.state.explanations.append({
|
|
275
|
+
"type": "action",
|
|
276
|
+
"action": f"{action_name}: {action_arg}",
|
|
277
|
+
"reason": thought
|
|
278
|
+
})
|
|
283
279
|
|
|
284
280
|
# STEP 3 — GENERATE FIX (if we have a file in context)
|
|
285
281
|
code_fix = ""
|
|
@@ -290,8 +286,12 @@ class Agent:
|
|
|
290
286
|
code_fix = self._generate_fix()
|
|
291
287
|
self.state.last_code_fix = code_fix
|
|
292
288
|
|
|
293
|
-
# STEP 4 — SELF-REVIEW
|
|
289
|
+
# STEP 4 — SELF-REVIEW (with Synthetic Grounding)
|
|
294
290
|
if code_fix:
|
|
291
|
+
# Add a "Dry Run" lint check to the review context
|
|
292
|
+
_, lint_output = lint_code(self.state.current_file)
|
|
293
|
+
self.state.last_observation += f"\n[LINT] {lint_output}"
|
|
294
|
+
|
|
295
295
|
code_fix, review_text = self._self_review(code_fix)
|
|
296
296
|
self.state.last_review = review_text
|
|
297
297
|
|
|
@@ -460,6 +460,9 @@ class Agent:
|
|
|
460
460
|
self._extract_file_from_search(result)
|
|
461
461
|
return result
|
|
462
462
|
|
|
463
|
+
elif action_name == "get_file_map":
|
|
464
|
+
return get_file_map(action_arg, root)
|
|
465
|
+
|
|
463
466
|
elif action_name == "semantic_search":
|
|
464
467
|
result = semantic_search(action_arg, root)
|
|
465
468
|
# Try to extract file from results
|
|
@@ -555,9 +558,21 @@ class Agent:
|
|
|
555
558
|
print(f" [REVIEW] #{revision + 1}: {review_text[:100]}")
|
|
556
559
|
|
|
557
560
|
if approved:
|
|
561
|
+
self.state.explanations.append({
|
|
562
|
+
"type": "review",
|
|
563
|
+
"file": self.state.current_file,
|
|
564
|
+
"reason": review_text,
|
|
565
|
+
"status": "APPROVED"
|
|
566
|
+
})
|
|
558
567
|
return code_fix, review_text
|
|
559
568
|
|
|
560
569
|
self.metrics.patch_rejections += 1
|
|
570
|
+
self.state.explanations.append({
|
|
571
|
+
"type": "review",
|
|
572
|
+
"file": self.state.current_file,
|
|
573
|
+
"reason": review_text,
|
|
574
|
+
"status": "REJECTED"
|
|
575
|
+
})
|
|
561
576
|
print(f" [REVISE] Revising code (attempt {revision + 1})...")
|
|
562
577
|
code_fix = revise_code(code_fix, review_text, self.state.task)
|
|
563
578
|
code_fix = self._strip_code_fences(code_fix)
|
|
@@ -73,6 +73,7 @@ class AgentState:
|
|
|
73
73
|
# -- Trust & Confidence --
|
|
74
74
|
confidence_score: float = 0.0
|
|
75
75
|
confidence_reasons: list[str] = field(default_factory=list)
|
|
76
|
+
explanations: list[dict[str, str]] = field(default_factory=list)
|
|
76
77
|
|
|
77
78
|
def to_dict(self) -> dict[str, Any]:
|
|
78
79
|
"""Return a JSON-serialisable snapshot of the current state."""
|
|
@@ -142,7 +142,21 @@ def cmd_run(args):
|
|
|
142
142
|
if final_state.confidence_reasons:
|
|
143
143
|
console.print("\n[bold]Confidence Breakdown:[/bold]")
|
|
144
144
|
for reason in final_state.confidence_reasons:
|
|
145
|
-
console.print(f" [
|
|
145
|
+
console.print(f" [green]✓[/green] {reason}")
|
|
146
|
+
|
|
147
|
+
# Explain Mode
|
|
148
|
+
if config.explain and final_state.explanations:
|
|
149
|
+
console.print("\n" + "=" * 60)
|
|
150
|
+
console.print(" [bold cyan]TRACEABILITY REPORT (EXPLAIN MODE)[/bold cyan]")
|
|
151
|
+
console.print("=" * 60)
|
|
152
|
+
for exp in final_state.explanations:
|
|
153
|
+
if exp["type"] == "action":
|
|
154
|
+
console.print(f"\n[bold yellow]Selection:[/bold yellow] {exp['action']}")
|
|
155
|
+
console.print(f" [dim]Why:[/dim] {exp['reason']}")
|
|
156
|
+
elif exp["type"] == "review":
|
|
157
|
+
status_color = "green" if exp["status"] == "APPROVED" else "red"
|
|
158
|
+
console.print(f"\n[bold {status_color}]Review:[/bold {status_color}] {exp['file']} ({exp['status']})")
|
|
159
|
+
console.print(f" [dim]Logic:[/dim] {exp['reason']}")
|
|
146
160
|
|
|
147
161
|
# Sandbox apply / Dry Run
|
|
148
162
|
if sandbox and sandbox.is_active:
|
|
@@ -273,6 +287,7 @@ def main():
|
|
|
273
287
|
run_parser.add_argument("--auto-push", action="store_true", help="Auto-push after commit")
|
|
274
288
|
run_parser.add_argument("--interactive", "-i", action="store_true", help="Review changes before applying")
|
|
275
289
|
run_parser.add_argument("--dry-run", action="store_true", help="Run without applying any changes")
|
|
290
|
+
run_parser.add_argument("--explain", action="store_true", help="Explain why files were chosen and patches applied")
|
|
276
291
|
run_parser.add_argument("--verbose", action="store_true", help="Verbose output")
|
|
277
292
|
|
|
278
293
|
# Command: benchmark
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""
|
|
2
|
+
File Map Tool — Provides a structural overview of a file using AST.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
import os
|
|
7
|
+
from devagent.utils.ast_utils import get_file_structure, format_structure_summary
|
|
8
|
+
|
|
9
|
+
def get_file_map(file_path: str, root_dir: str = ".") -> str:
|
|
10
|
+
"""Return a hierarchical map of functions and classes in the file."""
|
|
11
|
+
full_path = os.path.join(root_dir, file_path)
|
|
12
|
+
if not os.path.exists(full_path):
|
|
13
|
+
return f"Error: File {file_path} not found."
|
|
14
|
+
|
|
15
|
+
symbols = get_file_structure(full_path)
|
|
16
|
+
if not symbols:
|
|
17
|
+
return f"Could not parse structure for {file_path} (possibly empty or non-python)."
|
|
18
|
+
|
|
19
|
+
summary = format_structure_summary(symbols)
|
|
20
|
+
header = f"### Structure of {file_path} ###\n"
|
|
21
|
+
return header + summary + "\n\nUse read_file with specific line ranges to see the implementation."
|
|
@@ -10,16 +10,36 @@ from pathlib import Path
|
|
|
10
10
|
|
|
11
11
|
def read_file(path: str) -> str:
|
|
12
12
|
"""Read a file and return its contents.
|
|
13
|
-
|
|
13
|
+
|
|
14
|
+
Supports range: path/to/file.py:L10-L50
|
|
14
15
|
Returns an error string on failure (never raises).
|
|
15
16
|
"""
|
|
16
17
|
try:
|
|
18
|
+
# Check for line range
|
|
19
|
+
line_range = None
|
|
20
|
+
if ":L" in path:
|
|
21
|
+
parts = path.split(":L")
|
|
22
|
+
path = parts[0]
|
|
23
|
+
line_range = parts[1]
|
|
24
|
+
|
|
17
25
|
p = Path(path)
|
|
18
26
|
if not p.exists():
|
|
19
27
|
return f"[ERROR] File not found: {path}"
|
|
20
28
|
if not p.is_file():
|
|
21
29
|
return f"[ERROR] Not a file: {path}"
|
|
22
|
-
|
|
30
|
+
|
|
31
|
+
lines = p.read_text(encoding="utf-8", errors="replace").splitlines()
|
|
32
|
+
|
|
33
|
+
if line_range:
|
|
34
|
+
try:
|
|
35
|
+
start, end = map(int, line_range.split("-"))
|
|
36
|
+
lines = lines[start-1:end]
|
|
37
|
+
header = f"### {path} (Lines {start}-{end}) ###\n"
|
|
38
|
+
return header + "\n".join(lines)
|
|
39
|
+
except ValueError:
|
|
40
|
+
return f"[ERROR] Invalid line range: {line_range}"
|
|
41
|
+
|
|
42
|
+
content = "\n".join(lines)
|
|
23
43
|
return content[:10000] # cap to protect LLM context
|
|
24
44
|
except Exception as exc: # noqa: BLE001
|
|
25
45
|
return f"[ERROR] Could not read {path}: {exc}"
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AST Utilities for DevAgent — Hierarchical mapping and structural analysis.
|
|
3
|
+
|
|
4
|
+
Helps the agent understand file structure without reading the whole file.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import ast
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from typing import List, Optional
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class CodeSymbol:
|
|
13
|
+
name: str
|
|
14
|
+
type: str # 'function', 'class', 'method'
|
|
15
|
+
start_line: int
|
|
16
|
+
end_line: int
|
|
17
|
+
docstring: Optional[str] = None
|
|
18
|
+
children: List['CodeSymbol'] = None
|
|
19
|
+
|
|
20
|
+
def __post_init__(self):
|
|
21
|
+
if self.children is None:
|
|
22
|
+
self.children = []
|
|
23
|
+
|
|
24
|
+
def get_file_structure(file_path: str) -> List[CodeSymbol]:
|
|
25
|
+
"""Parse a Python file and return a hierarchical list of symbols."""
|
|
26
|
+
try:
|
|
27
|
+
with open(file_path, "r", encoding="utf-8", errors="replace") as f:
|
|
28
|
+
content = f.read()
|
|
29
|
+
|
|
30
|
+
tree = ast.parse(content)
|
|
31
|
+
symbols = []
|
|
32
|
+
|
|
33
|
+
for node in ast.iter_child_nodes(tree):
|
|
34
|
+
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)):
|
|
35
|
+
symbol = _parse_node(node)
|
|
36
|
+
if symbol:
|
|
37
|
+
symbols.append(symbol)
|
|
38
|
+
|
|
39
|
+
return symbols
|
|
40
|
+
except Exception:
|
|
41
|
+
return []
|
|
42
|
+
|
|
43
|
+
def _parse_node(node: ast.AST) -> Optional[CodeSymbol]:
|
|
44
|
+
"""Recursively parse an AST node into a CodeSymbol."""
|
|
45
|
+
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
|
46
|
+
symbol = CodeSymbol(
|
|
47
|
+
name=node.name,
|
|
48
|
+
type="function",
|
|
49
|
+
start_line=node.lineno,
|
|
50
|
+
end_line=getattr(node, "end_lineno", node.lineno),
|
|
51
|
+
docstring=ast.get_docstring(node)
|
|
52
|
+
)
|
|
53
|
+
return symbol
|
|
54
|
+
|
|
55
|
+
elif isinstance(node, ast.ClassDef):
|
|
56
|
+
symbol = CodeSymbol(
|
|
57
|
+
name=node.name,
|
|
58
|
+
type="class",
|
|
59
|
+
start_line=node.lineno,
|
|
60
|
+
end_line=getattr(node, "end_lineno", node.lineno),
|
|
61
|
+
docstring=ast.get_docstring(node)
|
|
62
|
+
)
|
|
63
|
+
# Parse methods
|
|
64
|
+
for child in node.body:
|
|
65
|
+
if isinstance(child, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
|
66
|
+
method = _parse_node(child)
|
|
67
|
+
if method:
|
|
68
|
+
method.type = "method"
|
|
69
|
+
symbol.children.append(method)
|
|
70
|
+
return symbol
|
|
71
|
+
|
|
72
|
+
return None
|
|
73
|
+
|
|
74
|
+
def format_structure_summary(symbols: List[CodeSymbol], indent: int = 0) -> str:
|
|
75
|
+
"""Format symbols into a readable tree summary."""
|
|
76
|
+
lines = []
|
|
77
|
+
for s in symbols:
|
|
78
|
+
prefix = " " * indent
|
|
79
|
+
line = f"{prefix}- {s.type.capitalize()}: {s.name} (L{s.start_line}-{s.end_line})"
|
|
80
|
+
lines.append(line)
|
|
81
|
+
if s.children:
|
|
82
|
+
lines.append(format_structure_summary(s.children, indent + 1))
|
|
83
|
+
return "\n".join(lines)
|
|
@@ -54,6 +54,7 @@ class AgentConfig:
|
|
|
54
54
|
# ── Feature flags ──
|
|
55
55
|
sandbox: bool = True
|
|
56
56
|
dry_run: bool = False
|
|
57
|
+
explain: bool = False
|
|
57
58
|
auto_commit: bool = False
|
|
58
59
|
auto_push: bool = False # DISABLED by default — safety first
|
|
59
60
|
benchmark: bool = False
|
|
@@ -100,6 +101,7 @@ class AgentConfig:
|
|
|
100
101
|
verbose=getattr(args, "verbose", False),
|
|
101
102
|
sandbox=getattr(args, "sandbox", True),
|
|
102
103
|
dry_run=getattr(args, "dry_run", False),
|
|
104
|
+
explain=getattr(args, "explain", False),
|
|
103
105
|
auto_commit=getattr(args, "auto_commit", False),
|
|
104
106
|
auto_push=getattr(args, "auto_push", False),
|
|
105
107
|
benchmark=getattr(args, "benchmark", False),
|
|
@@ -5,6 +5,7 @@ Safety Manager — handles snapshots and rollbacks.
|
|
|
5
5
|
import os
|
|
6
6
|
import shutil
|
|
7
7
|
import time
|
|
8
|
+
import subprocess
|
|
8
9
|
from rich.console import Console
|
|
9
10
|
from devagent.tools.git_tools import is_git_repo
|
|
10
11
|
|
|
@@ -13,7 +14,7 @@ console = Console()
|
|
|
13
14
|
class SafetyManager:
|
|
14
15
|
def __init__(self, project_root: str):
|
|
15
16
|
self.root = os.path.abspath(project_root)
|
|
16
|
-
self.snapshot_dir = os.path.join(self.root, ".
|
|
17
|
+
self.snapshot_dir = os.path.join(self.root, ".devagent_backups")
|
|
17
18
|
|
|
18
19
|
def create_snapshot(self, task_id: str = "latest") -> str:
|
|
19
20
|
"""Create a safety snapshot of the current project state."""
|
|
@@ -40,13 +41,29 @@ class SafetyManager:
|
|
|
40
41
|
os.makedirs(os.path.dirname(target_file), exist_ok=True)
|
|
41
42
|
shutil.copy2(src_file, target_file)
|
|
42
43
|
|
|
44
|
+
# Save metadata
|
|
45
|
+
metadata = {
|
|
46
|
+
"task_id": task_id,
|
|
47
|
+
"timestamp": timestamp,
|
|
48
|
+
"root": self.root
|
|
49
|
+
}
|
|
50
|
+
with open(os.path.join(dest, "metadata.json"), "w") as f:
|
|
51
|
+
import json
|
|
52
|
+
json.dump(metadata, f, indent=2)
|
|
53
|
+
|
|
43
54
|
return dest
|
|
44
55
|
|
|
45
56
|
def rollback(self, snapshot_id: str = "latest") -> bool:
|
|
46
57
|
"""Rollback the project to a previous snapshot."""
|
|
47
58
|
if is_git_repo(self.root):
|
|
48
|
-
console.print("[bold yellow][SAFETY][/bold yellow] This is a Git repository.
|
|
49
|
-
|
|
59
|
+
console.print("[bold yellow][SAFETY][/bold yellow] This is a Git repository. Reverting all local changes...")
|
|
60
|
+
try:
|
|
61
|
+
subprocess.run(["git", "checkout", "."], cwd=self.root, check=True, capture_output=True)
|
|
62
|
+
console.print("[bold green]Git rollback complete.[/bold green]")
|
|
63
|
+
return True
|
|
64
|
+
except Exception as e:
|
|
65
|
+
console.print(f"[bold red][ERROR][/bold red] Git rollback failed: {e}")
|
|
66
|
+
return False
|
|
50
67
|
|
|
51
68
|
# Find latest snapshot if not specified
|
|
52
69
|
if not os.path.exists(self.snapshot_dir):
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: devagent-cli
|
|
3
|
-
Version: 3.
|
|
4
|
-
Summary:
|
|
3
|
+
Version: 3.3.0
|
|
4
|
+
Summary: A local autonomous coding agent CLI powered by Ollama.
|
|
5
5
|
Author: Vedant Jadhav
|
|
6
6
|
License: MIT
|
|
7
7
|
Keywords: ai,agent,coding,ollama,local,devagent,devagent-cli
|
|
@@ -49,26 +49,23 @@ Dynamic: license-file
|
|
|
49
49
|
|
|
50
50
|
</div>
|
|
51
51
|
|
|
52
|
-
##
|
|
52
|
+
## 🛡️ Why DevAgent?
|
|
53
53
|
|
|
54
|
-
|
|
54
|
+
DevAgent is built on a "Safety-First" architecture for **high-integrity developer infrastructure**. Unlike chatbots that guess code, DevAgent is a local autonomous agent that operates within a strictly observed environment.
|
|
55
55
|
|
|
56
|
-
|
|
56
|
+
### 📊 Empirical Validation
|
|
57
|
+
We don't hide our limitations. DevAgent v3.2.3 has been stress-tested against real-world, "messy" repositories, providing a transparent **[Benchmark Report](docs/benchmarks.md)**.
|
|
57
58
|
|
|
58
|
-
| |
|
|
59
|
+
| | Other Agents | DevAgent |
|
|
59
60
|
|---|---|---|
|
|
60
|
-
|
|
|
61
|
-
|
|
|
62
|
-
|
|
|
63
|
-
|
|
|
64
|
-
|
|
|
65
|
-
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
| Works offline | ❌ | ✅ 100% local via Ollama |
|
|
69
|
-
| Costs money | 💸 | ✅ Free forever |
|
|
70
|
-
|
|
71
|
-
> **Philosophy:** Execution > Reasoning. Tools > Hallucination. Retrieval > Huge Context. Reliability > Intelligence.
|
|
61
|
+
| **Safety Isolation** | ❌ | ✅ Strict Sandbox Mode |
|
|
62
|
+
| **Recovery** | ❌ | ✅ Git-native + Snapshot Rollback |
|
|
63
|
+
| **Validation** | ❌ | ✅ Empirical Stress Tests |
|
|
64
|
+
| **Transparency** | ❌ | ✅ Visible Failure Taxonomy |
|
|
65
|
+
| **Privacy** | ❌ | ✅ 100% Local (Ollama) |
|
|
66
|
+
| **Costs** | 💸 | ✅ Zero API Costs |
|
|
67
|
+
|
|
68
|
+
> **Philosophy:** Safety > Intelligence. Observability > Reasoning. Retrieval > Huge Context. Reliability > Hype.
|
|
72
69
|
|
|
73
70
|
---
|
|
74
71
|
|
|
@@ -141,13 +138,13 @@ devagent run --task "Fix the divide-by-zero bug" --root ./demo_project
|
|
|
141
138
|
| `devagent models` | List available Ollama models |
|
|
142
139
|
| `devagent version` | Show current version |
|
|
143
140
|
|
|
144
|
-
|
|
145
|
-
DevAgent is built for **
|
|
146
|
-
- **
|
|
147
|
-
- **Auto-Snapshot**: Creates a safety restore point before every
|
|
148
|
-
- **Rollback**: Revert
|
|
149
|
-
- **
|
|
150
|
-
- **
|
|
141
|
+
#### 🛡️ Reliability & Safety
|
|
142
|
+
DevAgent is built for **production-grade reliability**:
|
|
143
|
+
- **Isolated Sandbox**: Agent works in `sandbox_workspace/`, keeping your source clean until success.
|
|
144
|
+
- **Auto-Snapshot**: Creates a safety restore point before every execution.
|
|
145
|
+
- **Instant Rollback**: Revert agent changes with `devagent rollback`.
|
|
146
|
+
- **Traceability**: Every thought and tool call is logged to `logs/run.json`.
|
|
147
|
+
- **Environment Awareness**: Detects and uses your project's Python environment automatically.
|
|
151
148
|
|
|
152
149
|
#### 🕹️ Interactive Mode
|
|
153
150
|
Run with `--interactive` (or `-i`) to review colorized diffs before they are applied to your project.
|
|
@@ -14,6 +14,7 @@ devagent/app/sandbox.py
|
|
|
14
14
|
devagent/app/state.py
|
|
15
15
|
devagent/tools/__init__.py
|
|
16
16
|
devagent/tools/benchmark_runner.py
|
|
17
|
+
devagent/tools/file_map.py
|
|
17
18
|
devagent/tools/file_ops.py
|
|
18
19
|
devagent/tools/git_tools.py
|
|
19
20
|
devagent/tools/linter.py
|
|
@@ -22,6 +23,7 @@ devagent/tools/semantic_search.py
|
|
|
22
23
|
devagent/tools/surgical_patcher.py
|
|
23
24
|
devagent/tools/test_runner.py
|
|
24
25
|
devagent/utils/__init__.py
|
|
26
|
+
devagent/utils/ast_utils.py
|
|
25
27
|
devagent/utils/config.py
|
|
26
28
|
devagent/utils/logger.py
|
|
27
29
|
devagent/utils/metrics.py
|
|
@@ -4,8 +4,8 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "devagent-cli"
|
|
7
|
-
version = "3.
|
|
8
|
-
description = "
|
|
7
|
+
version = "3.3.0"
|
|
8
|
+
description = "A local autonomous coding agent CLI powered by Ollama."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
11
11
|
license = {text = "MIT"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "3.2.1"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|