alpiecode 5.0.0__tar.gz → 5.1.1__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.
- {alpiecode-5.0.0 → alpiecode-5.1.1}/PKG-INFO +1 -1
- {alpiecode-5.0.0 → alpiecode-5.1.1}/pyproject.toml +1 -1
- {alpiecode-5.0.0 → alpiecode-5.1.1}/src/alpiecode.egg-info/PKG-INFO +1 -1
- {alpiecode-5.0.0 → alpiecode-5.1.1}/src/alpiecode.egg-info/SOURCES.txt +1 -0
- {alpiecode-5.0.0 → alpiecode-5.1.1}/src/codeagent/agent.py +25 -1
- {alpiecode-5.0.0 → alpiecode-5.1.1}/src/codeagent/cli.py +4 -0
- {alpiecode-5.0.0 → alpiecode-5.1.1}/src/codeagent/config.py +1 -1
- alpiecode-5.1.1/src/codeagent/discovery.py +572 -0
- {alpiecode-5.0.0 → alpiecode-5.1.1}/src/codeagent/orchestrator.py +20 -8
- {alpiecode-5.0.0 → alpiecode-5.1.1}/src/codeagent/prompt.py +126 -0
- {alpiecode-5.0.0 → alpiecode-5.1.1}/README.md +0 -0
- {alpiecode-5.0.0 → alpiecode-5.1.1}/setup.cfg +0 -0
- {alpiecode-5.0.0 → alpiecode-5.1.1}/src/alpiecode/__init__.py +0 -0
- {alpiecode-5.0.0 → alpiecode-5.1.1}/src/alpiecode.egg-info/dependency_links.txt +0 -0
- {alpiecode-5.0.0 → alpiecode-5.1.1}/src/alpiecode.egg-info/entry_points.txt +0 -0
- {alpiecode-5.0.0 → alpiecode-5.1.1}/src/alpiecode.egg-info/requires.txt +0 -0
- {alpiecode-5.0.0 → alpiecode-5.1.1}/src/alpiecode.egg-info/top_level.txt +0 -0
- {alpiecode-5.0.0 → alpiecode-5.1.1}/src/codeagent/__init__.py +0 -0
- {alpiecode-5.0.0 → alpiecode-5.1.1}/src/codeagent/backends/__init__.py +0 -0
- {alpiecode-5.0.0 → alpiecode-5.1.1}/src/codeagent/backends/base.py +0 -0
- {alpiecode-5.0.0 → alpiecode-5.1.1}/src/codeagent/backends/local_backend.py +0 -0
- {alpiecode-5.0.0 → alpiecode-5.1.1}/src/codeagent/backends/openai_backend.py +0 -0
- {alpiecode-5.0.0 → alpiecode-5.1.1}/src/codeagent/cache.py +0 -0
- {alpiecode-5.0.0 → alpiecode-5.1.1}/src/codeagent/client.py +0 -0
- {alpiecode-5.0.0 → alpiecode-5.1.1}/src/codeagent/compaction.py +0 -0
- {alpiecode-5.0.0 → alpiecode-5.1.1}/src/codeagent/context.py +0 -0
- {alpiecode-5.0.0 → alpiecode-5.1.1}/src/codeagent/doctor.py +0 -0
- {alpiecode-5.0.0 → alpiecode-5.1.1}/src/codeagent/executor.py +0 -0
- {alpiecode-5.0.0 → alpiecode-5.1.1}/src/codeagent/extension/alpiecode.vsix +0 -0
- {alpiecode-5.0.0 → alpiecode-5.1.1}/src/codeagent/github.py +0 -0
- {alpiecode-5.0.0 → alpiecode-5.1.1}/src/codeagent/guardian.py +0 -0
- {alpiecode-5.0.0 → alpiecode-5.1.1}/src/codeagent/ipython_ext.py +0 -0
- {alpiecode-5.0.0 → alpiecode-5.1.1}/src/codeagent/local_model.py +0 -0
- {alpiecode-5.0.0 → alpiecode-5.1.1}/src/codeagent/media.py +0 -0
- {alpiecode-5.0.0 → alpiecode-5.1.1}/src/codeagent/memory.py +0 -0
- {alpiecode-5.0.0 → alpiecode-5.1.1}/src/codeagent/server.py +0 -0
- {alpiecode-5.0.0 → alpiecode-5.1.1}/src/codeagent/session.py +0 -0
- {alpiecode-5.0.0 → alpiecode-5.1.1}/src/codeagent/tools.py +0 -0
- {alpiecode-5.0.0 → alpiecode-5.1.1}/src/codeagent/updater.py +0 -0
- {alpiecode-5.0.0 → alpiecode-5.1.1}/src/codeagent/vscode_installer.py +0 -0
|
@@ -156,6 +156,7 @@ def run_agent(
|
|
|
156
156
|
github_repo: str = None,
|
|
157
157
|
server_url: str = None,
|
|
158
158
|
complexity: str = None,
|
|
159
|
+
debug: bool = False,
|
|
159
160
|
) -> list:
|
|
160
161
|
"""Run non-interactive agent task with Rich presentation."""
|
|
161
162
|
workdir = workdir.resolve()
|
|
@@ -200,8 +201,27 @@ def run_agent(
|
|
|
200
201
|
_checkpoint(workdir, "checkpoint: start")
|
|
201
202
|
|
|
202
203
|
current_turn = 0
|
|
204
|
+
last_discovery = {}
|
|
205
|
+
|
|
203
206
|
for event in event_stream:
|
|
204
|
-
if event.type == "
|
|
207
|
+
if event.type == "discovery" and verbose:
|
|
208
|
+
last_discovery = event.data
|
|
209
|
+
if debug and HAS_RICH:
|
|
210
|
+
console.print(Panel(
|
|
211
|
+
f"[bold cyan]🔍 Autonomous Discovery Engine[/bold cyan]\n"
|
|
212
|
+
f"• Intent: [bold]{event.data.get('intent')}[/bold]\n"
|
|
213
|
+
f"• Complexity: [bold]{event.data.get('complexity')}[/bold]\n"
|
|
214
|
+
f"• OS: {event.data.get('os')} | Shell: {event.data.get('shell')}\n"
|
|
215
|
+
f"• Project: {event.data.get('project_type')} ({event.data.get('file_count', 0)} files)\n"
|
|
216
|
+
f"• Frameworks: {', '.join(event.data.get('frameworks', [])) or 'None'}",
|
|
217
|
+
title="Pre-Execution Intelligence",
|
|
218
|
+
border_style="dim cyan",
|
|
219
|
+
padding=(0, 1)
|
|
220
|
+
))
|
|
221
|
+
elif debug:
|
|
222
|
+
print(f"[Discovery] intent={event.data.get('intent')}, complexity={event.data.get('complexity')}, shell={event.data.get('shell')}, project={event.data.get('project_type')}")
|
|
223
|
+
|
|
224
|
+
elif event.type == "start" and verbose:
|
|
205
225
|
data = event.data
|
|
206
226
|
if HAS_RICH:
|
|
207
227
|
console.rule("[bold blue]Agent Started[/bold blue]")
|
|
@@ -223,6 +243,10 @@ def run_agent(
|
|
|
223
243
|
comp_label = {"qa": "Q&A (instant)", "low": "Low (fast)", "medium": "Medium (balanced)", "high": "High (thorough)"}.get(comp, comp)
|
|
224
244
|
comp_color = {"qa": "cyan", "low": "green", "medium": "yellow", "high": "red"}.get(comp, "white")
|
|
225
245
|
console.print(f"⚡ Complexity: [bold {comp_color}]{comp_label}[/bold {comp_color}]", style="dim")
|
|
246
|
+
if last_discovery:
|
|
247
|
+
intent_str = last_discovery.get('intent', 'create').title()
|
|
248
|
+
proj_str = last_discovery.get('project_type', 'empty')
|
|
249
|
+
console.print(f"🔍 Discovery: {intent_str} on {proj_str} project ({last_discovery.get('shell', 'bash')})", style="dim")
|
|
226
250
|
if cfg.enable_thinking:
|
|
227
251
|
console.print(f"🧠 Reasoning: [bold green]ON[/bold green]", style="dim")
|
|
228
252
|
else:
|
|
@@ -81,6 +81,7 @@ def main():
|
|
|
81
81
|
common.add_argument("--no-thinking", "--non-thinking", dest="no_thinking", action="store_true", help="Disable VLM reasoning mode")
|
|
82
82
|
common.add_argument("--no-update", action="store_true", help="Skip automatic update check")
|
|
83
83
|
common.add_argument("--quiet", action="store_true", help="Suppress per-turn logging")
|
|
84
|
+
common.add_argument("--debug", action="store_true", help="Show autonomous discovery and debug diagnostics")
|
|
84
85
|
|
|
85
86
|
parser = argparse.ArgumentParser(
|
|
86
87
|
prog="alpiecode",
|
|
@@ -170,6 +171,7 @@ def main():
|
|
|
170
171
|
video_path=getattr(args, "video", None),
|
|
171
172
|
url=getattr(args, "url", None),
|
|
172
173
|
github_repo=getattr(args, "github", None),
|
|
174
|
+
debug=getattr(args, "debug", False),
|
|
173
175
|
)
|
|
174
176
|
|
|
175
177
|
elif args.command == "chat":
|
|
@@ -196,6 +198,7 @@ def main():
|
|
|
196
198
|
video_path=getattr(args, "video", None),
|
|
197
199
|
url=getattr(args, "url", None),
|
|
198
200
|
github_repo=getattr(args, "github", None),
|
|
201
|
+
debug=getattr(args, "debug", False),
|
|
199
202
|
)
|
|
200
203
|
|
|
201
204
|
elif args.command == "doctor":
|
|
@@ -231,6 +234,7 @@ def main():
|
|
|
231
234
|
video_path=getattr(args, "video", None),
|
|
232
235
|
url=getattr(args, "url", None),
|
|
233
236
|
github_repo=getattr(args, "github", None),
|
|
237
|
+
debug=getattr(args, "debug", False),
|
|
234
238
|
)
|
|
235
239
|
|
|
236
240
|
elif args.command == "diff":
|
|
@@ -18,7 +18,7 @@ CONFIG_DIR = Path.home() / ".alpiecode"
|
|
|
18
18
|
CONFIG_PATH = CONFIG_DIR / "config.json"
|
|
19
19
|
|
|
20
20
|
# Config version — bump this when defaults change to trigger auto-migration
|
|
21
|
-
CONFIG_VERSION =
|
|
21
|
+
CONFIG_VERSION = 7 # v7: autonomous discovery engine (zero-flag flow)
|
|
22
22
|
|
|
23
23
|
DEFAULTS = {
|
|
24
24
|
"base_url": "http://20.245.200.125:8000/v1", # Primary endpoint
|
|
@@ -0,0 +1,572 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Discovery Engine for AlpieCode -- Zero-LLM Pre-Execution Intelligence.
|
|
3
|
+
|
|
4
|
+
Runs in under 50ms before the agent turn loop starts. Detects:
|
|
5
|
+
- Task intent (qa, create, modify, debug, explain)
|
|
6
|
+
- Environment (OS, shell, Python version, venv, git)
|
|
7
|
+
- Repository state (project type, frameworks, file count, entry points)
|
|
8
|
+
- Smart complexity (from intent + repo state combined)
|
|
9
|
+
|
|
10
|
+
All detection is heuristic/filesystem-based -- no LLM calls, no network.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import json
|
|
14
|
+
import os
|
|
15
|
+
import platform
|
|
16
|
+
import re
|
|
17
|
+
import shutil
|
|
18
|
+
import subprocess
|
|
19
|
+
import sys
|
|
20
|
+
from dataclasses import dataclass, field
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
from typing import List, Optional, Tuple
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# -- Task Context ----------------------------------------------------------
|
|
26
|
+
|
|
27
|
+
@dataclass
|
|
28
|
+
class TaskContext:
|
|
29
|
+
"""Pre-computed intelligence about the task and environment."""
|
|
30
|
+
|
|
31
|
+
# Intent
|
|
32
|
+
intent: str = "create" # qa, create, modify, debug, explain
|
|
33
|
+
complexity: str = "low" # qa, low, medium, high
|
|
34
|
+
|
|
35
|
+
# Environment
|
|
36
|
+
os_name: str = "linux" # linux, darwin, windows
|
|
37
|
+
shell: str = "bash" # bash, powershell, cmd, sh, wsl
|
|
38
|
+
python_cmd: str = "python3" # python3 or python (platform-dependent)
|
|
39
|
+
python_version: str = "" # e.g. "3.11.2"
|
|
40
|
+
has_venv: bool = False
|
|
41
|
+
has_git: bool = False
|
|
42
|
+
|
|
43
|
+
# Repository
|
|
44
|
+
project_type: str = "empty" # python, node, rust, go, java, empty, unknown
|
|
45
|
+
frameworks: List[str] = field(default_factory=list)
|
|
46
|
+
file_count: int = 0
|
|
47
|
+
has_tests: bool = False
|
|
48
|
+
entry_points: List[str] = field(default_factory=list)
|
|
49
|
+
dependencies: List[str] = field(default_factory=list)
|
|
50
|
+
|
|
51
|
+
# Budget (computed from complexity)
|
|
52
|
+
max_turns: int = 10
|
|
53
|
+
max_tokens: int = 8192
|
|
54
|
+
tool_set: str = "core" # none, core, full
|
|
55
|
+
enable_thinking: bool = False
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
# -- Intent Detection ------------------------------------------------------
|
|
59
|
+
|
|
60
|
+
# Question patterns -- user wants an answer, not code
|
|
61
|
+
_QA_STARTERS = [
|
|
62
|
+
"what is", "what are", "what\'s", "what does", "what do",
|
|
63
|
+
"explain", "how does", "how do", "why does", "why do", "why is",
|
|
64
|
+
"describe", "define", "compare", "difference between",
|
|
65
|
+
"tell me about", "who invented", "who created", "when was",
|
|
66
|
+
"can you explain", "could you explain", "please explain",
|
|
67
|
+
"what\'s the difference", "is it possible", "is there a way",
|
|
68
|
+
]
|
|
69
|
+
|
|
70
|
+
# Action verbs that indicate code generation
|
|
71
|
+
_ACTION_VERBS = [
|
|
72
|
+
"build", "create", "write", "make", "implement", "develop",
|
|
73
|
+
"generate", "code", "script", "program", "design", "set up",
|
|
74
|
+
"setup", "scaffold", "bootstrap", "initialize", "init",
|
|
75
|
+
]
|
|
76
|
+
|
|
77
|
+
# Modification verbs
|
|
78
|
+
_MODIFY_VERBS = [
|
|
79
|
+
"fix", "add", "modify", "change", "update", "remove", "delete",
|
|
80
|
+
"refactor", "migrate", "upgrade", "downgrade", "rename",
|
|
81
|
+
"move", "restructure", "reorganize", "optimize", "improve",
|
|
82
|
+
"convert", "transform", "replace", "swap",
|
|
83
|
+
]
|
|
84
|
+
|
|
85
|
+
# Debug verbs
|
|
86
|
+
_DEBUG_VERBS = [
|
|
87
|
+
"debug", "investigate", "diagnose", "troubleshoot", "trace",
|
|
88
|
+
"find the bug", "find the error", "find the issue",
|
|
89
|
+
"why is this", "what\'s wrong", "what is wrong",
|
|
90
|
+
]
|
|
91
|
+
|
|
92
|
+
# Explain verbs (when target is a file or code)
|
|
93
|
+
_EXPLAIN_VERBS = [
|
|
94
|
+
"explain this", "explain the", "walk me through",
|
|
95
|
+
"how does this work", "what does this do",
|
|
96
|
+
"analyze this", "review this", "read this",
|
|
97
|
+
]
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def detect_intent(task: str) -> str:
|
|
101
|
+
"""Classify task intent: qa, create, modify, debug, or explain.
|
|
102
|
+
|
|
103
|
+
Returns one of: 'qa', 'create', 'modify', 'debug', 'explain'
|
|
104
|
+
"""
|
|
105
|
+
t = task.lower().strip()
|
|
106
|
+
|
|
107
|
+
# Q&A: starts with a question pattern AND has no action verbs
|
|
108
|
+
all_action = _ACTION_VERBS + _MODIFY_VERBS
|
|
109
|
+
if any(t.startswith(pat) for pat in _QA_STARTERS):
|
|
110
|
+
if not any(re.search(r"\b" + re.escape(av) + r"\b", t) for av in all_action):
|
|
111
|
+
return "qa"
|
|
112
|
+
|
|
113
|
+
if t.endswith("?") and not any(re.search(r"\b" + re.escape(av) + r"\b", t) for av in all_action):
|
|
114
|
+
return "qa"
|
|
115
|
+
|
|
116
|
+
# Check if task starts with a create verb ("build a...", "create a...", "write a...")
|
|
117
|
+
if any(t.startswith(cv) for cv in ["build", "create", "write", "make", "implement", "develop", "generate", "code", "design"]):
|
|
118
|
+
return "create"
|
|
119
|
+
|
|
120
|
+
# Debug: explicitly asking to debug/investigate
|
|
121
|
+
if any(re.search(r"\b" + re.escape(dv) + r"\b", t) for dv in _DEBUG_VERBS):
|
|
122
|
+
return "debug"
|
|
123
|
+
|
|
124
|
+
# Explain: asking to explain existing code
|
|
125
|
+
if any(re.search(r"\b" + re.escape(ev) + r"\b", t) for ev in _EXPLAIN_VERBS):
|
|
126
|
+
return "explain"
|
|
127
|
+
|
|
128
|
+
# Modify: acting on existing code (e.g. "refactor", "fix", "update")
|
|
129
|
+
if any(re.search(r"\b" + re.escape(mv) + r"\b", t) for mv in ["refactor", "migrate", "fix", "update", "modify", "change", "delete", "remove", "rename"]):
|
|
130
|
+
return "modify"
|
|
131
|
+
|
|
132
|
+
# Create: building something new
|
|
133
|
+
if any(re.search(r"\b" + re.escape(av) + r"\b", t) for av in _ACTION_VERBS):
|
|
134
|
+
return "create"
|
|
135
|
+
|
|
136
|
+
if any(re.search(r"\b" + re.escape(mv) + r"\b", t) for mv in _MODIFY_VERBS):
|
|
137
|
+
return "modify"
|
|
138
|
+
|
|
139
|
+
if t.endswith("?"):
|
|
140
|
+
return "qa"
|
|
141
|
+
|
|
142
|
+
return "create"
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
# -- Environment Detection -------------------------------------------------
|
|
146
|
+
|
|
147
|
+
def detect_environment() -> dict:
|
|
148
|
+
"""Detect OS, shell, Python version, and runtime environment.
|
|
149
|
+
|
|
150
|
+
Returns dict with: os_name, shell, python_cmd, python_version
|
|
151
|
+
"""
|
|
152
|
+
os_name = platform.system().lower()
|
|
153
|
+
if os_name == "linux":
|
|
154
|
+
# Check if we are inside WSL
|
|
155
|
+
try:
|
|
156
|
+
with open("/proc/version", "r") as f:
|
|
157
|
+
version_info = f.read().lower()
|
|
158
|
+
if "microsoft" in version_info or "wsl" in version_info:
|
|
159
|
+
os_name = "wsl"
|
|
160
|
+
except (FileNotFoundError, PermissionError):
|
|
161
|
+
pass
|
|
162
|
+
|
|
163
|
+
# Shell detection
|
|
164
|
+
shell = _detect_shell(os_name)
|
|
165
|
+
|
|
166
|
+
# Python command
|
|
167
|
+
python_cmd = "python" if os_name == "windows" else "python3"
|
|
168
|
+
|
|
169
|
+
# Python version
|
|
170
|
+
python_version = f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}"
|
|
171
|
+
|
|
172
|
+
return {
|
|
173
|
+
"os_name": os_name if os_name != "wsl" else "linux",
|
|
174
|
+
"shell": "wsl" if os_name == "wsl" else shell,
|
|
175
|
+
"python_cmd": python_cmd,
|
|
176
|
+
"python_version": python_version,
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def _detect_shell(os_name: str) -> str:
|
|
181
|
+
"""Detect available shell, preferring WSL on Windows like Claude/Codex agents."""
|
|
182
|
+
|
|
183
|
+
if os_name == "windows":
|
|
184
|
+
# On Windows: prefer WSL (like Claude, Codex, Gemini agents do)
|
|
185
|
+
if _is_wsl_available():
|
|
186
|
+
return "wsl"
|
|
187
|
+
# Fallback to PowerShell
|
|
188
|
+
if shutil.which("powershell") or shutil.which("pwsh"):
|
|
189
|
+
return "powershell"
|
|
190
|
+
return "cmd"
|
|
191
|
+
|
|
192
|
+
if os_name in ("linux", "wsl"):
|
|
193
|
+
return "bash" # Standard on Linux/WSL
|
|
194
|
+
|
|
195
|
+
if os_name == "darwin":
|
|
196
|
+
return "bash" # Our commands work on both bash and zsh
|
|
197
|
+
|
|
198
|
+
return "sh"
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def _is_wsl_available() -> bool:
|
|
202
|
+
"""Check if WSL is installed and has at least one working distro."""
|
|
203
|
+
try:
|
|
204
|
+
result = subprocess.run(
|
|
205
|
+
["wsl", "--list", "--quiet"],
|
|
206
|
+
capture_output=True, text=True, timeout=3,
|
|
207
|
+
)
|
|
208
|
+
return result.returncode == 0 and bool(result.stdout.strip())
|
|
209
|
+
except (FileNotFoundError, subprocess.TimeoutExpired, OSError):
|
|
210
|
+
return False
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
# -- Repository Scanner ----------------------------------------------------
|
|
214
|
+
|
|
215
|
+
def scan_repository(workdir: Path) -> dict:
|
|
216
|
+
"""Quick scan of repository to detect project type, frameworks, etc.
|
|
217
|
+
|
|
218
|
+
Designed to run in under 30ms by checking only key indicator files.
|
|
219
|
+
"""
|
|
220
|
+
workdir = workdir.resolve()
|
|
221
|
+
|
|
222
|
+
has_git = (workdir / ".git").is_dir()
|
|
223
|
+
has_venv = (workdir / ".venv").is_dir() or (workdir / "venv").is_dir()
|
|
224
|
+
|
|
225
|
+
# Quick file count (capped scan)
|
|
226
|
+
file_count = _quick_file_count(workdir, max_files=500)
|
|
227
|
+
|
|
228
|
+
# Detect project type and frameworks by checking indicator files
|
|
229
|
+
project_type, frameworks = _detect_project(workdir)
|
|
230
|
+
|
|
231
|
+
# Find entry points
|
|
232
|
+
entry_points = _find_entry_points(workdir, project_type)
|
|
233
|
+
|
|
234
|
+
# Check for tests
|
|
235
|
+
has_tests = _has_test_files(workdir)
|
|
236
|
+
|
|
237
|
+
# Read key dependencies (fast -- just reads manifest files)
|
|
238
|
+
dependencies = _read_dependencies(workdir, project_type)
|
|
239
|
+
|
|
240
|
+
return {
|
|
241
|
+
"has_git": has_git,
|
|
242
|
+
"has_venv": has_venv,
|
|
243
|
+
"file_count": file_count,
|
|
244
|
+
"project_type": project_type,
|
|
245
|
+
"frameworks": frameworks,
|
|
246
|
+
"entry_points": entry_points,
|
|
247
|
+
"has_tests": has_tests,
|
|
248
|
+
"dependencies": dependencies,
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def _quick_file_count(workdir: Path, max_files: int = 500) -> int:
|
|
253
|
+
"""Count tracked files quickly, respecting .gitignore if possible."""
|
|
254
|
+
try:
|
|
255
|
+
result = subprocess.run(
|
|
256
|
+
["git", "ls-files", "--cached", "--others", "--exclude-standard"],
|
|
257
|
+
cwd=workdir, capture_output=True, text=True, timeout=3,
|
|
258
|
+
)
|
|
259
|
+
if result.returncode == 0 and result.stdout.strip():
|
|
260
|
+
return len(result.stdout.strip().splitlines())
|
|
261
|
+
except (FileNotFoundError, subprocess.TimeoutExpired):
|
|
262
|
+
pass
|
|
263
|
+
|
|
264
|
+
# Fallback: walk directory
|
|
265
|
+
count = 0
|
|
266
|
+
skip_dirs = {".git", "node_modules", "__pycache__", ".venv", "venv",
|
|
267
|
+
".tox", "dist", "build", ".eggs", ".mypy_cache"}
|
|
268
|
+
for root, dirs, files in os.walk(workdir):
|
|
269
|
+
dirs[:] = [d for d in dirs if d not in skip_dirs and not d.startswith(".")]
|
|
270
|
+
count += len(files)
|
|
271
|
+
if count >= max_files:
|
|
272
|
+
return count
|
|
273
|
+
return count
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def _detect_project(workdir: Path) -> Tuple[str, List[str]]:
|
|
277
|
+
"""Detect project type and frameworks from indicator files."""
|
|
278
|
+
frameworks = []
|
|
279
|
+
|
|
280
|
+
# Python indicators
|
|
281
|
+
has_pyproject = (workdir / "pyproject.toml").exists()
|
|
282
|
+
has_setup_py = (workdir / "setup.py").exists()
|
|
283
|
+
has_requirements = (workdir / "requirements.txt").exists()
|
|
284
|
+
has_pipfile = (workdir / "Pipfile").exists()
|
|
285
|
+
|
|
286
|
+
if has_pyproject or has_setup_py or has_requirements or has_pipfile:
|
|
287
|
+
frameworks = _detect_python_frameworks(workdir)
|
|
288
|
+
return "python", frameworks
|
|
289
|
+
|
|
290
|
+
# Node.js indicators
|
|
291
|
+
if (workdir / "package.json").exists():
|
|
292
|
+
frameworks = _detect_node_frameworks(workdir)
|
|
293
|
+
return "node", frameworks
|
|
294
|
+
|
|
295
|
+
# Rust
|
|
296
|
+
if (workdir / "Cargo.toml").exists():
|
|
297
|
+
return "rust", []
|
|
298
|
+
|
|
299
|
+
# Go
|
|
300
|
+
if (workdir / "go.mod").exists():
|
|
301
|
+
return "go", []
|
|
302
|
+
|
|
303
|
+
# Java
|
|
304
|
+
if (workdir / "pom.xml").exists() or (workdir / "build.gradle").exists():
|
|
305
|
+
return "java", []
|
|
306
|
+
|
|
307
|
+
# Check if there are any source files at all
|
|
308
|
+
for ext in (".py", ".js", ".ts", ".rs", ".go", ".java", ".c", ".cpp"):
|
|
309
|
+
for _ in workdir.glob(f"*{ext}"):
|
|
310
|
+
return "unknown", []
|
|
311
|
+
|
|
312
|
+
return "empty", []
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
def _detect_python_frameworks(workdir: Path) -> List[str]:
|
|
316
|
+
"""Detect Python frameworks from requirements/pyproject."""
|
|
317
|
+
frameworks = []
|
|
318
|
+
content = ""
|
|
319
|
+
|
|
320
|
+
for manifest in ["requirements.txt", "pyproject.toml", "setup.py", "Pipfile"]:
|
|
321
|
+
path = workdir / manifest
|
|
322
|
+
if path.exists():
|
|
323
|
+
try:
|
|
324
|
+
content += path.read_text(errors="replace").lower()
|
|
325
|
+
except Exception:
|
|
326
|
+
pass
|
|
327
|
+
|
|
328
|
+
framework_map = {
|
|
329
|
+
"fastapi": "fastapi",
|
|
330
|
+
"flask": "flask",
|
|
331
|
+
"django": "django",
|
|
332
|
+
"pytest": "pytest",
|
|
333
|
+
"sqlalchemy": "sqlalchemy",
|
|
334
|
+
"pydantic": "pydantic",
|
|
335
|
+
"celery": "celery",
|
|
336
|
+
"redis": "redis",
|
|
337
|
+
"pymongo": "mongodb",
|
|
338
|
+
"torch": "pytorch",
|
|
339
|
+
"tensorflow": "tensorflow",
|
|
340
|
+
"numpy": "numpy",
|
|
341
|
+
"pandas": "pandas",
|
|
342
|
+
"streamlit": "streamlit",
|
|
343
|
+
"gradio": "gradio",
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
for pkg, name in framework_map.items():
|
|
347
|
+
if pkg in content:
|
|
348
|
+
frameworks.append(name)
|
|
349
|
+
|
|
350
|
+
return frameworks
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
def _detect_node_frameworks(workdir: Path) -> List[str]:
|
|
354
|
+
"""Detect Node.js frameworks from package.json."""
|
|
355
|
+
frameworks = []
|
|
356
|
+
pkg_path = workdir / "package.json"
|
|
357
|
+
try:
|
|
358
|
+
data = json.loads(pkg_path.read_text(errors="replace"))
|
|
359
|
+
all_deps = {}
|
|
360
|
+
all_deps.update(data.get("dependencies", {}))
|
|
361
|
+
all_deps.update(data.get("devDependencies", {}))
|
|
362
|
+
|
|
363
|
+
framework_map = {
|
|
364
|
+
"react": "react", "next": "nextjs", "vue": "vue",
|
|
365
|
+
"angular": "angular", "express": "express",
|
|
366
|
+
"fastify": "fastify", "nest": "nestjs",
|
|
367
|
+
"jest": "jest", "mocha": "mocha",
|
|
368
|
+
"typescript": "typescript",
|
|
369
|
+
}
|
|
370
|
+
for pkg, name in framework_map.items():
|
|
371
|
+
if any(pkg in dep for dep in all_deps):
|
|
372
|
+
frameworks.append(name)
|
|
373
|
+
except Exception:
|
|
374
|
+
pass
|
|
375
|
+
return frameworks
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
def _find_entry_points(workdir: Path, project_type: str) -> List[str]:
|
|
379
|
+
"""Find likely entry point files."""
|
|
380
|
+
entry_names = [
|
|
381
|
+
"main.py", "app.py", "server.py", "manage.py", "run.py",
|
|
382
|
+
"index.py", "cli.py", "__main__.py",
|
|
383
|
+
"index.js", "index.ts", "app.js", "app.ts", "server.js", "server.ts",
|
|
384
|
+
"main.go", "main.rs", "Main.java",
|
|
385
|
+
]
|
|
386
|
+
found = []
|
|
387
|
+
for name in entry_names:
|
|
388
|
+
if (workdir / name).exists():
|
|
389
|
+
found.append(name)
|
|
390
|
+
# Also check src/ directory
|
|
391
|
+
if (workdir / "src" / name).exists():
|
|
392
|
+
found.append(f"src/{name}")
|
|
393
|
+
return found[:5]
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
def _has_test_files(workdir: Path) -> bool:
|
|
397
|
+
"""Check if the project has test files."""
|
|
398
|
+
test_indicators = ["tests", "test", "__tests__", "spec"]
|
|
399
|
+
for d in test_indicators:
|
|
400
|
+
if (workdir / d).is_dir():
|
|
401
|
+
return True
|
|
402
|
+
|
|
403
|
+
# Check for test_*.py or *_test.py files in root
|
|
404
|
+
try:
|
|
405
|
+
for f in workdir.iterdir():
|
|
406
|
+
if f.is_file() and (f.name.startswith("test_") or f.name.endswith("_test.py")):
|
|
407
|
+
return True
|
|
408
|
+
except PermissionError:
|
|
409
|
+
pass
|
|
410
|
+
return False
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
def _read_dependencies(workdir: Path, project_type: str) -> List[str]:
|
|
414
|
+
"""Read top-level dependency names (fast, no resolution)."""
|
|
415
|
+
deps = []
|
|
416
|
+
if project_type == "python":
|
|
417
|
+
req_path = workdir / "requirements.txt"
|
|
418
|
+
if req_path.exists():
|
|
419
|
+
try:
|
|
420
|
+
for line in req_path.read_text(errors="replace").splitlines():
|
|
421
|
+
line = line.strip()
|
|
422
|
+
if line and not line.startswith("#") and not line.startswith("-"):
|
|
423
|
+
name = re.split(r"[=<>!~;\[]", line)[0].strip()
|
|
424
|
+
if name:
|
|
425
|
+
deps.append(name)
|
|
426
|
+
except Exception:
|
|
427
|
+
pass
|
|
428
|
+
return deps[:20]
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
# -- Complexity Engine ------------------------------------------------------
|
|
432
|
+
|
|
433
|
+
def compute_complexity(intent: str, repo_info: dict, task: str) -> str:
|
|
434
|
+
"""Compute task complexity from intent + repo state + task text.
|
|
435
|
+
|
|
436
|
+
This is smarter than pure keyword matching because it considers
|
|
437
|
+
the actual project context.
|
|
438
|
+
|
|
439
|
+
Returns: 'qa', 'low', 'medium', or 'high'
|
|
440
|
+
"""
|
|
441
|
+
if intent == "qa":
|
|
442
|
+
return "qa"
|
|
443
|
+
|
|
444
|
+
task_lower = task.lower()
|
|
445
|
+
file_count = repo_info.get("file_count", 0)
|
|
446
|
+
frameworks = repo_info.get("frameworks", [])
|
|
447
|
+
project_type = repo_info.get("project_type", "empty")
|
|
448
|
+
|
|
449
|
+
# -- HIGH: explicitly complex multi-component tasks --
|
|
450
|
+
high_keywords = [
|
|
451
|
+
"full stack", "fullstack", "full-stack",
|
|
452
|
+
"microservice", "e-commerce", "ecommerce",
|
|
453
|
+
"docker", "kubernetes", "ci/cd", "pipeline",
|
|
454
|
+
"machine learning", "deep learning", "neural network",
|
|
455
|
+
"train a model", "training pipeline",
|
|
456
|
+
"authentication", "oauth", "jwt auth",
|
|
457
|
+
"database migration", "schema migration",
|
|
458
|
+
]
|
|
459
|
+
if any(kw in task_lower for kw in high_keywords):
|
|
460
|
+
return "high"
|
|
461
|
+
|
|
462
|
+
# Large codebase + modify/debug -> high
|
|
463
|
+
if intent in ("modify", "debug") and file_count > 50:
|
|
464
|
+
if any(kw in task_lower for kw in ["refactor", "restructure", "redesign", "rewrite", "migrate"]):
|
|
465
|
+
return "high"
|
|
466
|
+
|
|
467
|
+
# -- MEDIUM: multi-step tasks --
|
|
468
|
+
medium_keywords = [
|
|
469
|
+
"api", "rest api", "graphql", "server",
|
|
470
|
+
"game", "snake", "tetris", "chess", "pong", "sudoku",
|
|
471
|
+
"website", "web page", "web app", "webapp",
|
|
472
|
+
"test suite", "unit tests", "integration test",
|
|
473
|
+
"dashboard", "portfolio",
|
|
474
|
+
"react", "vue", "angular", "next.js",
|
|
475
|
+
"django", "flask app", "fastapi app",
|
|
476
|
+
]
|
|
477
|
+
if any(kw in task_lower for kw in medium_keywords):
|
|
478
|
+
return "medium"
|
|
479
|
+
|
|
480
|
+
# Modify/debug on existing project with frameworks -> medium
|
|
481
|
+
if intent in ("modify", "debug") and frameworks:
|
|
482
|
+
return "medium"
|
|
483
|
+
|
|
484
|
+
# Modify/debug on project with 10+ files -> medium
|
|
485
|
+
if intent in ("modify", "debug") and file_count > 10:
|
|
486
|
+
return "medium"
|
|
487
|
+
|
|
488
|
+
# Create on existing project (adding to it) -> medium
|
|
489
|
+
if intent == "create" and project_type not in ("empty", "unknown") and file_count > 5:
|
|
490
|
+
return "medium"
|
|
491
|
+
|
|
492
|
+
# -- LOW: everything else --
|
|
493
|
+
return "low"
|
|
494
|
+
|
|
495
|
+
|
|
496
|
+
# -- Complexity Configuration -----------------------------------------------
|
|
497
|
+
|
|
498
|
+
COMPLEXITY_CONFIG = {
|
|
499
|
+
"qa": {
|
|
500
|
+
"max_turns": 3,
|
|
501
|
+
"max_tokens": 4096,
|
|
502
|
+
"tool_set": "none",
|
|
503
|
+
"enable_thinking": False,
|
|
504
|
+
},
|
|
505
|
+
"low": {
|
|
506
|
+
"max_turns": 10,
|
|
507
|
+
"max_tokens": 8192,
|
|
508
|
+
"tool_set": "core",
|
|
509
|
+
"enable_thinking": False,
|
|
510
|
+
},
|
|
511
|
+
"medium": {
|
|
512
|
+
"max_turns": 20,
|
|
513
|
+
"max_tokens": 8192,
|
|
514
|
+
"tool_set": "full",
|
|
515
|
+
"enable_thinking": False,
|
|
516
|
+
},
|
|
517
|
+
"high": {
|
|
518
|
+
"max_turns": 40,
|
|
519
|
+
"max_tokens": 16384,
|
|
520
|
+
"tool_set": "full",
|
|
521
|
+
"enable_thinking": True,
|
|
522
|
+
},
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
|
|
526
|
+
# -- Main Entry Point -------------------------------------------------------
|
|
527
|
+
|
|
528
|
+
def build_task_context(task: str, workdir: Path) -> TaskContext:
|
|
529
|
+
"""Build complete TaskContext by running all discovery phases.
|
|
530
|
+
|
|
531
|
+
This is the single entry point -- call this from the orchestrator.
|
|
532
|
+
Runs in under 50ms total, no LLM calls, no network.
|
|
533
|
+
"""
|
|
534
|
+
# Phase 1: Intent
|
|
535
|
+
intent = detect_intent(task)
|
|
536
|
+
|
|
537
|
+
# Phase 2: Environment
|
|
538
|
+
env = detect_environment()
|
|
539
|
+
|
|
540
|
+
# Phase 3: Repository scan
|
|
541
|
+
repo = scan_repository(workdir)
|
|
542
|
+
|
|
543
|
+
# Phase 4: Complexity (uses intent + repo)
|
|
544
|
+
complexity = compute_complexity(intent, repo, task)
|
|
545
|
+
|
|
546
|
+
# Phase 5: Budget from complexity
|
|
547
|
+
budget = COMPLEXITY_CONFIG[complexity]
|
|
548
|
+
|
|
549
|
+
return TaskContext(
|
|
550
|
+
# Intent
|
|
551
|
+
intent=intent,
|
|
552
|
+
complexity=complexity,
|
|
553
|
+
# Environment
|
|
554
|
+
os_name=env["os_name"],
|
|
555
|
+
shell=env["shell"],
|
|
556
|
+
python_cmd=env["python_cmd"],
|
|
557
|
+
python_version=env["python_version"],
|
|
558
|
+
has_venv=repo["has_venv"],
|
|
559
|
+
has_git=repo["has_git"],
|
|
560
|
+
# Repository
|
|
561
|
+
project_type=repo["project_type"],
|
|
562
|
+
frameworks=repo["frameworks"],
|
|
563
|
+
file_count=repo["file_count"],
|
|
564
|
+
has_tests=repo["has_tests"],
|
|
565
|
+
entry_points=repo["entry_points"],
|
|
566
|
+
dependencies=repo["dependencies"],
|
|
567
|
+
# Budget
|
|
568
|
+
max_turns=budget["max_turns"],
|
|
569
|
+
max_tokens=budget["max_tokens"],
|
|
570
|
+
tool_set=budget["tool_set"],
|
|
571
|
+
enable_thinking=budget["enable_thinking"],
|
|
572
|
+
)
|
|
@@ -14,7 +14,8 @@ from .backends.openai_backend import OpenAIBackend
|
|
|
14
14
|
from .cache import get_cache
|
|
15
15
|
from .config import Config, is_server_reachable
|
|
16
16
|
from .memory import extract_and_save_memories
|
|
17
|
-
from .
|
|
17
|
+
from .discovery import build_task_context, COMPLEXITY_CONFIG
|
|
18
|
+
from .prompt import PromptBuilder, classify_task
|
|
18
19
|
from .session import Session, SessionManager
|
|
19
20
|
|
|
20
21
|
|
|
@@ -55,15 +56,25 @@ class AgentOrchestrator:
|
|
|
55
56
|
) -> Iterator[AgentEvent]:
|
|
56
57
|
"""Run full agent task loop. Yields AgentEvents."""
|
|
57
58
|
|
|
58
|
-
# ──
|
|
59
|
-
|
|
60
|
-
|
|
59
|
+
# ── Phase 0: Discovery — pre-compute task intelligence ──
|
|
60
|
+
task_context = build_task_context(task, session.workdir)
|
|
61
|
+
complexity = task_context.complexity
|
|
62
|
+
|
|
63
|
+
yield AgentEvent("discovery", {
|
|
64
|
+
"intent": task_context.intent,
|
|
65
|
+
"complexity": task_context.complexity,
|
|
66
|
+
"os": task_context.os_name,
|
|
67
|
+
"shell": task_context.shell,
|
|
68
|
+
"project_type": task_context.project_type,
|
|
69
|
+
"frameworks": task_context.frameworks,
|
|
70
|
+
"file_count": task_context.file_count,
|
|
71
|
+
})
|
|
61
72
|
|
|
62
73
|
comp_cfg = COMPLEXITY_CONFIG.get(complexity, COMPLEXITY_CONFIG["low"])
|
|
63
74
|
|
|
64
75
|
# ── Determine effective max_turns and max_tokens ──
|
|
65
|
-
effective_max_turns =
|
|
66
|
-
effective_max_tokens =
|
|
76
|
+
effective_max_turns = task_context.max_turns
|
|
77
|
+
effective_max_tokens = task_context.max_tokens
|
|
67
78
|
|
|
68
79
|
# User override: if they set --max-turns explicitly, respect it
|
|
69
80
|
if cfg.max_turns != 20: # 20 is new default, so non-default = explicit
|
|
@@ -102,7 +113,8 @@ class AgentOrchestrator:
|
|
|
102
113
|
# ── Configure tools & system prompt based on complexity ──
|
|
103
114
|
active_tools = self.prompt_builder.get_tools(is_offline=is_offline, complexity=complexity)
|
|
104
115
|
system_prompt = self.prompt_builder.build_system_prompt(
|
|
105
|
-
session.workdir, is_offline=is_offline, complexity=complexity
|
|
116
|
+
session.workdir, is_offline=is_offline, complexity=complexity,
|
|
117
|
+
task_context=task_context,
|
|
106
118
|
)
|
|
107
119
|
session.context.set_system_prompt(system_prompt)
|
|
108
120
|
|
|
@@ -126,7 +138,7 @@ class AgentOrchestrator:
|
|
|
126
138
|
})
|
|
127
139
|
|
|
128
140
|
# ── Adaptive thinking ──
|
|
129
|
-
enable_thinking = cfg.enable_thinking
|
|
141
|
+
enable_thinking = cfg.enable_thinking or task_context.enable_thinking
|
|
130
142
|
if enable_thinking and complexity in ("qa", "low"):
|
|
131
143
|
enable_thinking = False
|
|
132
144
|
yield AgentEvent("adaptive_mode", {"message": "Simple task detected, skipping deep reasoning."})
|
|
@@ -359,6 +359,89 @@ COMPLEXITY_CONFIG = {
|
|
|
359
359
|
}
|
|
360
360
|
|
|
361
361
|
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
# -- Shell-Aware Prompt Fragments ------------------------------------------
|
|
365
|
+
|
|
366
|
+
BASH_RULES = """\
|
|
367
|
+
## Shell Environment: Bash (Linux/macOS)
|
|
368
|
+
- You are ALREADY in the project root directory. NEVER run `cd /home/...` or `cd /testbed`
|
|
369
|
+
- Use `&&` to chain commands
|
|
370
|
+
- Use `export VAR=value` for environment variables
|
|
371
|
+
- Use `python3` (not `python` which may be Python 2)
|
|
372
|
+
- For testing python code without extra packages, use `python3 -m unittest`
|
|
373
|
+
- Background processes: `command &` (but AVOID for servers -- use TestClient)
|
|
374
|
+
- File paths use `/` forward slashes
|
|
375
|
+
"""
|
|
376
|
+
|
|
377
|
+
WSL_RULES = """\
|
|
378
|
+
## Shell Environment: WSL Bash (Windows Subsystem for Linux)
|
|
379
|
+
- You are ALREADY in the project root directory. NEVER run `cd /home/...` or `cd /testbed`
|
|
380
|
+
- Use bash syntax: `&&` to chain, `export VAR=value` for env vars
|
|
381
|
+
- Use `python3` (not `python`)
|
|
382
|
+
- For testing python code, use `python3 -m unittest`
|
|
383
|
+
- The working directory is a Linux path (e.g. /home/user/project)
|
|
384
|
+
- Do NOT use PowerShell or Windows commands (no `dir`, `type`, `$env:`)
|
|
385
|
+
- File paths use `/` forward slashes
|
|
386
|
+
- Network: `localhost` in WSL may differ from Windows -- use `127.0.0.1`
|
|
387
|
+
"""
|
|
388
|
+
|
|
389
|
+
POWERSHELL_RULES = """\
|
|
390
|
+
## Shell Environment: PowerShell (Windows)
|
|
391
|
+
- Use `;` to chain commands, NOT `&&` (PowerShell does not support `&&`)
|
|
392
|
+
- Use `$env:VAR = "value"` for environment variables, NOT `export VAR=value`
|
|
393
|
+
- Use `python` (not `python3`)
|
|
394
|
+
- Do NOT use `echo -e` or ANSI escapes -- use `Write-Output`
|
|
395
|
+
- Do NOT use `grep` -- use `Select-String` or `findstr`
|
|
396
|
+
- Do NOT use `&` for background processes -- they will fail
|
|
397
|
+
- File paths use `\\` but `/` also works in most cases
|
|
398
|
+
- Do NOT use `curl` -- use `Invoke-WebRequest` or Python's requests/httpx
|
|
399
|
+
"""
|
|
400
|
+
|
|
401
|
+
REPO_CONTEXT_TEMPLATE = """\
|
|
402
|
+
## Current Project Context
|
|
403
|
+
- Project type: {project_type}
|
|
404
|
+
- Frameworks: {frameworks}
|
|
405
|
+
- Tracked files: {file_count}
|
|
406
|
+
- Entry points: {entry_points}
|
|
407
|
+
- Has tests: {has_tests}
|
|
408
|
+
{extra}"""
|
|
409
|
+
|
|
410
|
+
INTENT_CREATE = """\
|
|
411
|
+
## Task Intent: Create New Code
|
|
412
|
+
- Write the COMPLETE, WORKING code on Turn 1 using write_file
|
|
413
|
+
- Do NOT explore the filesystem first -- start coding immediately
|
|
414
|
+
- Include ALL imports, ALL functions, ALL logic -- no stubs, no TODOs
|
|
415
|
+
- After writing, run with bash to verify
|
|
416
|
+
- Fix any errors with edit_file, then re-run
|
|
417
|
+
- When everything works: DONE: <summary>
|
|
418
|
+
"""
|
|
419
|
+
|
|
420
|
+
INTENT_MODIFY = """\
|
|
421
|
+
## Task Intent: Modify Existing Code
|
|
422
|
+
- Turn 1: Use read_file to read the relevant file(s)
|
|
423
|
+
- Turn 2: Use edit_file to make targeted changes
|
|
424
|
+
- Turn 3: Use bash to test the changes
|
|
425
|
+
- Fix any regressions, then: DONE: <summary>
|
|
426
|
+
"""
|
|
427
|
+
|
|
428
|
+
INTENT_DEBUG = """\
|
|
429
|
+
## Task Intent: Debug / Investigate
|
|
430
|
+
- Turn 1: Reproduce the issue -- run the failing command/test
|
|
431
|
+
- Turn 2: Read error output carefully, use read_file to examine source
|
|
432
|
+
- Turn 3: Fix the root cause with edit_file (not the symptom)
|
|
433
|
+
- Turn 4: Re-run to verify the fix
|
|
434
|
+
- DONE: <summary of what was wrong and how it was fixed>
|
|
435
|
+
"""
|
|
436
|
+
|
|
437
|
+
INTENT_EXPLAIN = """\
|
|
438
|
+
## Task Intent: Explain / Analyze
|
|
439
|
+
- Read the relevant file(s) with read_file
|
|
440
|
+
- Provide a clear, structured explanation
|
|
441
|
+
- Do NOT modify any files unless explicitly asked
|
|
442
|
+
- DONE: <explanation>
|
|
443
|
+
"""
|
|
444
|
+
|
|
362
445
|
class PromptBuilder:
|
|
363
446
|
"""Constructs system prompts and user content."""
|
|
364
447
|
|
|
@@ -367,7 +450,9 @@ class PromptBuilder:
|
|
|
367
450
|
workdir: Path,
|
|
368
451
|
is_offline: bool = False,
|
|
369
452
|
complexity: str = "low",
|
|
453
|
+
task_context=None,
|
|
370
454
|
) -> str:
|
|
455
|
+
"""Build system prompt, optionally enhanced with TaskContext discovery."""
|
|
371
456
|
if is_offline:
|
|
372
457
|
prompt = OFFLINE_SYSTEM_PROMPT
|
|
373
458
|
elif complexity == "high":
|
|
@@ -375,6 +460,47 @@ class PromptBuilder:
|
|
|
375
460
|
else:
|
|
376
461
|
prompt = SYSTEM_PROMPT
|
|
377
462
|
|
|
463
|
+
# -- Inject shell-aware rules from TaskContext --
|
|
464
|
+
if task_context is not None:
|
|
465
|
+
shell = getattr(task_context, "shell", "bash")
|
|
466
|
+
if shell == "powershell":
|
|
467
|
+
prompt += "\n\n" + POWERSHELL_RULES
|
|
468
|
+
elif shell == "wsl":
|
|
469
|
+
prompt += "\n\n" + WSL_RULES
|
|
470
|
+
elif shell == "bash":
|
|
471
|
+
prompt += "\n\n" + BASH_RULES
|
|
472
|
+
|
|
473
|
+
# Inject repo context (if not an empty dir)
|
|
474
|
+
if getattr(task_context, "project_type", "empty") != "empty":
|
|
475
|
+
frameworks_str = ", ".join(task_context.frameworks) if task_context.frameworks else "none detected"
|
|
476
|
+
entry_str = ", ".join(task_context.entry_points) if task_context.entry_points else "none found"
|
|
477
|
+
extra_lines = []
|
|
478
|
+
if task_context.has_venv:
|
|
479
|
+
extra_lines.append("- Virtual environment: detected (.venv)")
|
|
480
|
+
if task_context.dependencies:
|
|
481
|
+
extra_lines.append(f"- Key dependencies: {', '.join(task_context.dependencies[:8])}")
|
|
482
|
+
extra = "\n".join(extra_lines)
|
|
483
|
+
prompt += "\n\n" + REPO_CONTEXT_TEMPLATE.format(
|
|
484
|
+
project_type=task_context.project_type,
|
|
485
|
+
frameworks=frameworks_str,
|
|
486
|
+
file_count=task_context.file_count,
|
|
487
|
+
entry_points=entry_str,
|
|
488
|
+
has_tests="yes" if task_context.has_tests else "no",
|
|
489
|
+
extra=extra,
|
|
490
|
+
)
|
|
491
|
+
|
|
492
|
+
# Inject intent-specific workflow
|
|
493
|
+
intent = getattr(task_context, "intent", "create")
|
|
494
|
+
if intent == "create":
|
|
495
|
+
prompt += "\n\n" + INTENT_CREATE
|
|
496
|
+
elif intent == "modify":
|
|
497
|
+
prompt += "\n\n" + INTENT_MODIFY
|
|
498
|
+
elif intent == "debug":
|
|
499
|
+
prompt += "\n\n" + INTENT_DEBUG
|
|
500
|
+
elif intent == "explain":
|
|
501
|
+
prompt += "\n\n" + INTENT_EXPLAIN
|
|
502
|
+
# qa intent: no extra prompt needed (model answers directly)
|
|
503
|
+
|
|
378
504
|
memories = format_memories_for_prompt(workdir)
|
|
379
505
|
if memories:
|
|
380
506
|
prompt += f"\n\n{memories}"
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|