alpiecode 10.5.1__tar.gz → 10.5.3__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-10.5.1 → alpiecode-10.5.3}/PKG-INFO +1 -1
- {alpiecode-10.5.1 → alpiecode-10.5.3}/pyproject.toml +1 -1
- {alpiecode-10.5.1 → alpiecode-10.5.3}/src/alpiecode.egg-info/PKG-INFO +1 -1
- {alpiecode-10.5.1 → alpiecode-10.5.3}/src/alpiecode.egg-info/SOURCES.txt +5 -1
- {alpiecode-10.5.1 → alpiecode-10.5.3}/src/codeagent/__init__.py +1 -1
- {alpiecode-10.5.1 → alpiecode-10.5.3}/src/codeagent/agent.py +70 -25
- alpiecode-10.5.3/src/codeagent/assessment.py +400 -0
- {alpiecode-10.5.1 → alpiecode-10.5.3}/src/codeagent/backends/local_backend.py +8 -0
- {alpiecode-10.5.1 → alpiecode-10.5.3}/src/codeagent/backends/openai_backend.py +11 -5
- {alpiecode-10.5.1 → alpiecode-10.5.3}/src/codeagent/cli.py +9 -3
- {alpiecode-10.5.1 → alpiecode-10.5.3}/src/codeagent/context.py +3 -7
- {alpiecode-10.5.1 → alpiecode-10.5.3}/src/codeagent/discovery.py +21 -74
- {alpiecode-10.5.1 → alpiecode-10.5.3}/src/codeagent/executor.py +96 -30
- {alpiecode-10.5.1 → alpiecode-10.5.3}/src/codeagent/orchestrator.py +182 -74
- {alpiecode-10.5.1 → alpiecode-10.5.3}/src/codeagent/progress.py +27 -2
- {alpiecode-10.5.1 → alpiecode-10.5.3}/src/codeagent/prompt.py +21 -0
- {alpiecode-10.5.1 → alpiecode-10.5.3}/src/codeagent/system1.py +119 -15
- alpiecode-10.5.3/src/codeagent/verifier.py +498 -0
- {alpiecode-10.5.1 → alpiecode-10.5.3}/test/test_context_and_trajectory.py +11 -0
- alpiecode-10.5.3/test/test_task_assessment.py +569 -0
- alpiecode-10.5.3/test/test_verifier.py +197 -0
- {alpiecode-10.5.1 → alpiecode-10.5.3}/README.md +0 -0
- {alpiecode-10.5.1 → alpiecode-10.5.3}/setup.cfg +0 -0
- {alpiecode-10.5.1 → alpiecode-10.5.3}/src/alpiecode/__init__.py +0 -0
- {alpiecode-10.5.1 → alpiecode-10.5.3}/src/alpiecode.egg-info/dependency_links.txt +0 -0
- {alpiecode-10.5.1 → alpiecode-10.5.3}/src/alpiecode.egg-info/entry_points.txt +0 -0
- {alpiecode-10.5.1 → alpiecode-10.5.3}/src/alpiecode.egg-info/requires.txt +0 -0
- {alpiecode-10.5.1 → alpiecode-10.5.3}/src/alpiecode.egg-info/top_level.txt +0 -0
- {alpiecode-10.5.1 → alpiecode-10.5.3}/src/codeagent/backends/__init__.py +0 -0
- {alpiecode-10.5.1 → alpiecode-10.5.3}/src/codeagent/backends/base.py +0 -0
- {alpiecode-10.5.1 → alpiecode-10.5.3}/src/codeagent/cache.py +0 -0
- {alpiecode-10.5.1 → alpiecode-10.5.3}/src/codeagent/client.py +0 -0
- {alpiecode-10.5.1 → alpiecode-10.5.3}/src/codeagent/compaction.py +0 -0
- {alpiecode-10.5.1 → alpiecode-10.5.3}/src/codeagent/config.py +0 -0
- {alpiecode-10.5.1 → alpiecode-10.5.3}/src/codeagent/doctor.py +0 -0
- {alpiecode-10.5.1 → alpiecode-10.5.3}/src/codeagent/dream_rsi.py +0 -0
- {alpiecode-10.5.1 → alpiecode-10.5.3}/src/codeagent/extension/alpiecode.vsix +0 -0
- {alpiecode-10.5.1 → alpiecode-10.5.3}/src/codeagent/git_ops.py +0 -0
- {alpiecode-10.5.1 → alpiecode-10.5.3}/src/codeagent/github.py +0 -0
- {alpiecode-10.5.1 → alpiecode-10.5.3}/src/codeagent/guardian.py +0 -0
- {alpiecode-10.5.1 → alpiecode-10.5.3}/src/codeagent/guardrails.py +0 -0
- {alpiecode-10.5.1 → alpiecode-10.5.3}/src/codeagent/ipython_ext.py +0 -0
- {alpiecode-10.5.1 → alpiecode-10.5.3}/src/codeagent/local_model.py +0 -0
- {alpiecode-10.5.1 → alpiecode-10.5.3}/src/codeagent/media.py +0 -0
- {alpiecode-10.5.1 → alpiecode-10.5.3}/src/codeagent/memory.py +0 -0
- {alpiecode-10.5.1 → alpiecode-10.5.3}/src/codeagent/metrics.py +0 -0
- {alpiecode-10.5.1 → alpiecode-10.5.3}/src/codeagent/rephraser.py +0 -0
- {alpiecode-10.5.1 → alpiecode-10.5.3}/src/codeagent/server.py +0 -0
- {alpiecode-10.5.1 → alpiecode-10.5.3}/src/codeagent/session.py +0 -0
- {alpiecode-10.5.1 → alpiecode-10.5.3}/src/codeagent/tools.py +0 -0
- {alpiecode-10.5.1 → alpiecode-10.5.3}/src/codeagent/updater.py +0 -0
- {alpiecode-10.5.1 → alpiecode-10.5.3}/src/codeagent/vscode_installer.py +0 -0
- {alpiecode-10.5.1 → alpiecode-10.5.3}/test/test_dream_rsi.py +0 -0
|
@@ -9,6 +9,7 @@ src/alpiecode.egg-info/requires.txt
|
|
|
9
9
|
src/alpiecode.egg-info/top_level.txt
|
|
10
10
|
src/codeagent/__init__.py
|
|
11
11
|
src/codeagent/agent.py
|
|
12
|
+
src/codeagent/assessment.py
|
|
12
13
|
src/codeagent/cache.py
|
|
13
14
|
src/codeagent/cli.py
|
|
14
15
|
src/codeagent/client.py
|
|
@@ -37,6 +38,7 @@ src/codeagent/session.py
|
|
|
37
38
|
src/codeagent/system1.py
|
|
38
39
|
src/codeagent/tools.py
|
|
39
40
|
src/codeagent/updater.py
|
|
41
|
+
src/codeagent/verifier.py
|
|
40
42
|
src/codeagent/vscode_installer.py
|
|
41
43
|
src/codeagent/backends/__init__.py
|
|
42
44
|
src/codeagent/backends/base.py
|
|
@@ -44,4 +46,6 @@ src/codeagent/backends/local_backend.py
|
|
|
44
46
|
src/codeagent/backends/openai_backend.py
|
|
45
47
|
src/codeagent/extension/alpiecode.vsix
|
|
46
48
|
test/test_context_and_trajectory.py
|
|
47
|
-
test/test_dream_rsi.py
|
|
49
|
+
test/test_dream_rsi.py
|
|
50
|
+
test/test_task_assessment.py
|
|
51
|
+
test/test_verifier.py
|
|
@@ -17,7 +17,7 @@ Supports:
|
|
|
17
17
|
from pathlib import Path
|
|
18
18
|
from typing import Optional, Any, Dict
|
|
19
19
|
|
|
20
|
-
__version__ = "10.5.
|
|
20
|
+
__version__ = "10.5.3"
|
|
21
21
|
|
|
22
22
|
def run(task: str, workdir: str = ".", reasoning_level: str = "thinking", **kwargs):
|
|
23
23
|
"""Run an autonomous coding task programmatically."""
|
|
@@ -193,6 +193,18 @@ def _checkpoint(workdir: Path, message: str) -> None:
|
|
|
193
193
|
|
|
194
194
|
# ── Main CLI Agent Functions ──────────────────────────────────────────
|
|
195
195
|
|
|
196
|
+
class AgentRunResult(list):
|
|
197
|
+
"""List of session context messages with execution status."""
|
|
198
|
+
|
|
199
|
+
def __init__(self, messages=None, success=True, outcome="success"):
|
|
200
|
+
super().__init__(messages or [])
|
|
201
|
+
self.success = success
|
|
202
|
+
self.outcome = outcome
|
|
203
|
+
|
|
204
|
+
def __bool__(self):
|
|
205
|
+
return self.success
|
|
206
|
+
|
|
207
|
+
|
|
196
208
|
def run_agent(
|
|
197
209
|
task: str,
|
|
198
210
|
workdir: Path,
|
|
@@ -205,7 +217,7 @@ def run_agent(
|
|
|
205
217
|
server_url: str = None,
|
|
206
218
|
complexity: str = None,
|
|
207
219
|
debug: bool = False,
|
|
208
|
-
) ->
|
|
220
|
+
) -> AgentRunResult:
|
|
209
221
|
"""Run non-interactive agent task with Rich presentation."""
|
|
210
222
|
workdir = workdir.resolve()
|
|
211
223
|
_ensure_git(workdir)
|
|
@@ -256,6 +268,9 @@ def run_agent(
|
|
|
256
268
|
last_discovery = {}
|
|
257
269
|
has_streamed_tokens = False
|
|
258
270
|
has_printed_anything = False
|
|
271
|
+
task_success = True
|
|
272
|
+
task_outcome = "success"
|
|
273
|
+
task_failure_message = ""
|
|
259
274
|
|
|
260
275
|
for event in event_stream:
|
|
261
276
|
if event.type == "token" and verbose:
|
|
@@ -470,17 +485,39 @@ def run_agent(
|
|
|
470
485
|
f"Cache Hits: {data.get('cache_hits')}"
|
|
471
486
|
)
|
|
472
487
|
|
|
473
|
-
elif event.type == "
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
488
|
+
elif event.type == "failed":
|
|
489
|
+
task_success = False
|
|
490
|
+
task_outcome = event.data.get("reason", "failed")
|
|
491
|
+
task_failure_message = event.data.get("message", "Task failed.")
|
|
492
|
+
if verbose:
|
|
493
|
+
if HAS_RICH:
|
|
494
|
+
console.print(Panel(
|
|
495
|
+
f"[bold red]❌ Task Failed[/bold red]\n{task_failure_message}",
|
|
496
|
+
border_style="red",
|
|
497
|
+
title="Failure Notice"
|
|
498
|
+
))
|
|
499
|
+
else:
|
|
500
|
+
print(f"\n❌ Task Failed: {task_failure_message}")
|
|
501
|
+
|
|
502
|
+
elif event.type == "safety_ceiling":
|
|
503
|
+
task_success = False
|
|
504
|
+
if task_outcome == "success":
|
|
505
|
+
task_outcome = "safety_ceiling"
|
|
506
|
+
if verbose:
|
|
507
|
+
if HAS_RICH:
|
|
508
|
+
console.print(
|
|
509
|
+
f"\n🛑 [bold red]Safety ceiling ({event.data['ceiling']} turns) reached.[/bold red]\n"
|
|
510
|
+
"This is an emergency stop — the agent may be stuck in an unrecoverable loop.",
|
|
511
|
+
style="bold red"
|
|
512
|
+
)
|
|
513
|
+
else:
|
|
514
|
+
print(f"\n🛑 Safety ceiling ({event.data['ceiling']}) reached.")
|
|
482
515
|
|
|
483
516
|
elif event.type == "done":
|
|
517
|
+
if not event.data.get("success", True):
|
|
518
|
+
task_success = False
|
|
519
|
+
if task_outcome == "success":
|
|
520
|
+
task_outcome = "failed"
|
|
484
521
|
if has_streamed_tokens:
|
|
485
522
|
sys.stdout.write("\n\n")
|
|
486
523
|
sys.stdout.flush()
|
|
@@ -490,23 +527,31 @@ def run_agent(
|
|
|
490
527
|
has_printed_anything = True
|
|
491
528
|
_checkpoint(workdir, "checkpoint: done")
|
|
492
529
|
if debug and HAS_RICH:
|
|
493
|
-
|
|
530
|
+
if task_success:
|
|
531
|
+
console.rule("[bold green]✅ Complete[/bold green]")
|
|
532
|
+
else:
|
|
533
|
+
console.rule("[bold red]❌ Failed[/bold red]")
|
|
494
534
|
|
|
495
|
-
elif event.type == "max_turns_reached"
|
|
496
|
-
|
|
497
|
-
if
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
535
|
+
elif event.type == "max_turns_reached":
|
|
536
|
+
task_success = False
|
|
537
|
+
if task_outcome == "success":
|
|
538
|
+
task_outcome = "max_turns_reached"
|
|
539
|
+
if verbose:
|
|
540
|
+
progress = event.data.get("progress", {})
|
|
541
|
+
if HAS_RICH:
|
|
542
|
+
console.print(f"\n⚠️ [bold yellow]Safety ceiling ({event.data['max_turns']}) reached.[/bold yellow]", style="bold yellow")
|
|
543
|
+
if progress:
|
|
544
|
+
console.print(
|
|
545
|
+
f" Progress: {progress.get('progress_turns', 0)}/{progress.get('total_turns', 0)} turns made progress, "
|
|
546
|
+
f"{progress.get('files_created', 0)} files created, "
|
|
547
|
+
f"{progress.get('stall_interventions', 0)} stall interventions",
|
|
548
|
+
style="dim"
|
|
549
|
+
)
|
|
550
|
+
else:
|
|
551
|
+
print(f"\n⚠️ Safety ceiling ({event.data['max_turns']}) reached.")
|
|
508
552
|
|
|
509
|
-
|
|
553
|
+
msgs = session.context.messages if "session" in locals() else []
|
|
554
|
+
return AgentRunResult(msgs, success=task_success, outcome=task_outcome)
|
|
510
555
|
|
|
511
556
|
|
|
512
557
|
def run_chat(workdir: Path, cfg: Config, verbose: bool = True, debug: bool = False) -> None:
|
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Task Assessment Engine for AlpieCode.
|
|
3
|
+
|
|
4
|
+
Replaces brittle heuristic keyword matching with structured, multi-factor analysis:
|
|
5
|
+
- Intent: (qa, explain, create, modify, debug)
|
|
6
|
+
- Scope: (local, file, module, system)
|
|
7
|
+
- Ambiguity: (clear, moderate, ambiguous)
|
|
8
|
+
- Repository impact: (none, isolated, cross_file, systemic)
|
|
9
|
+
- Dependency impact: (none, local_imports, external_deps, environment)
|
|
10
|
+
- Risk: (low, medium, high)
|
|
11
|
+
- Verification required & methods: (syntax, unit_tests, execution)
|
|
12
|
+
- Estimated tool calls: (0 to 12+)
|
|
13
|
+
- Execution Strategy: (fast_path vs reasoning_path)
|
|
14
|
+
|
|
15
|
+
Deterministic, sub-15ms execution, zero LLM calls, zero network.
|
|
16
|
+
Provides 100% backward compatibility with legacy complexity ('qa', 'low', 'medium', 'high').
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
import os
|
|
20
|
+
import re
|
|
21
|
+
from dataclasses import dataclass, field
|
|
22
|
+
from enum import Enum
|
|
23
|
+
from pathlib import Path
|
|
24
|
+
from typing import Any, Dict, List, Optional, Set
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class TaskIntent(str, Enum):
|
|
28
|
+
QA = "qa"
|
|
29
|
+
EXPLAIN = "explain"
|
|
30
|
+
CREATE = "create"
|
|
31
|
+
MODIFY = "modify"
|
|
32
|
+
DEBUG = "debug"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class TaskScope(str, Enum):
|
|
36
|
+
LOCAL = "local" # Standalone script, simple function, no repository dependencies
|
|
37
|
+
FILE = "file" # Single existing file or isolated component
|
|
38
|
+
MODULE = "module" # Multiple files within a module/feature
|
|
39
|
+
SYSTEM = "system" # Cross-cutting, architecture overhaul, multi-service, full-stack
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class AmbiguityLevel(str, Enum):
|
|
43
|
+
CLEAR = "clear" # Unambiguous requirements, explicit inputs/outputs or targets
|
|
44
|
+
MODERATE = "moderate" # Clear goal, but implementation or design details are open
|
|
45
|
+
AMBIGUOUS = "ambiguous" # Vague, underspecified, or exploratory request
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class RepositoryImpact(str, Enum):
|
|
49
|
+
NONE = "none" # Read-only or purely conceptual (QA, explain)
|
|
50
|
+
ISOLATED = "isolated" # Single leaf file, scratch script, or single test
|
|
51
|
+
CROSS_FILE = "cross_file" # Spans multiple files or impacts inter-module imports
|
|
52
|
+
SYSTEMIC = "systemic" # Modifies configs, central architecture, schemas, or root manifests
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class DependencyImpact(str, Enum):
|
|
56
|
+
NONE = "none" # Standard library or existing in-tree code only
|
|
57
|
+
LOCAL_IMPORTS = "local_imports" # Inter-module project imports
|
|
58
|
+
EXTERNAL_DEPS = "external_deps" # Introduces or alters 3rd party packages
|
|
59
|
+
ENVIRONMENT = "environment" # Modifies docker, containers, env vars, or system packages
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class RiskLevel(str, Enum):
|
|
63
|
+
LOW = "low"
|
|
64
|
+
MEDIUM = "medium"
|
|
65
|
+
HIGH = "high"
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class ExecutionStrategy(str, Enum):
|
|
69
|
+
FAST_PATH = "fast_path" # Direct execution: Execute -> Finish
|
|
70
|
+
REASONING_PATH = "reasoning_path" # Plan -> Execute -> Verify -> (Pass -> Finish | Fail -> Re-plan/Reason)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@dataclass
|
|
74
|
+
class TaskAssessment:
|
|
75
|
+
"""Rigorous, multi-factor intelligence assessing a software task before execution."""
|
|
76
|
+
|
|
77
|
+
intent: str
|
|
78
|
+
scope: str
|
|
79
|
+
ambiguity: str
|
|
80
|
+
repo_impact: str
|
|
81
|
+
dep_impact: str
|
|
82
|
+
risk: str
|
|
83
|
+
verification_required: bool
|
|
84
|
+
verification_methods: List[str] = field(default_factory=list)
|
|
85
|
+
estimated_tool_calls: int = 1
|
|
86
|
+
strategy: str = ExecutionStrategy.FAST_PATH.value
|
|
87
|
+
rationale: str = ""
|
|
88
|
+
|
|
89
|
+
@property
|
|
90
|
+
def legacy_complexity(self) -> str:
|
|
91
|
+
"""Transparent mapping to legacy complexity level for 100% backward compatibility."""
|
|
92
|
+
if self.intent == TaskIntent.QA.value:
|
|
93
|
+
return "qa"
|
|
94
|
+
if self.strategy == ExecutionStrategy.FAST_PATH.value:
|
|
95
|
+
return "low"
|
|
96
|
+
# Reasoning path:
|
|
97
|
+
if (
|
|
98
|
+
self.scope == TaskScope.SYSTEM.value
|
|
99
|
+
or self.risk == RiskLevel.HIGH.value
|
|
100
|
+
or self.repo_impact == RepositoryImpact.SYSTEMIC.value
|
|
101
|
+
):
|
|
102
|
+
return "high"
|
|
103
|
+
return "medium"
|
|
104
|
+
|
|
105
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
106
|
+
return {
|
|
107
|
+
"intent": self.intent,
|
|
108
|
+
"scope": self.scope,
|
|
109
|
+
"ambiguity": self.ambiguity,
|
|
110
|
+
"repo_impact": self.repo_impact,
|
|
111
|
+
"dep_impact": self.dep_impact,
|
|
112
|
+
"risk": self.risk,
|
|
113
|
+
"verification_required": self.verification_required,
|
|
114
|
+
"verification_methods": list(self.verification_methods),
|
|
115
|
+
"estimated_tool_calls": self.estimated_tool_calls,
|
|
116
|
+
"strategy": self.strategy,
|
|
117
|
+
"rationale": self.rationale,
|
|
118
|
+
"legacy_complexity": self.legacy_complexity,
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
# ── Pattern Dictionaries for Sub-15ms Deterministic Analysis ───────────────
|
|
123
|
+
|
|
124
|
+
_QA_INDICATORS = [
|
|
125
|
+
r"^\s*(what\s+is|what\s+are|what\'s|what\s+does|what\s+do)\b",
|
|
126
|
+
r"^\s*(explain|describe|define|compare|contrast)\b",
|
|
127
|
+
r"^\s*(how\s+does|how\s+do|why\s+does|why\s+do|why\s+is)\b",
|
|
128
|
+
r"^\s*(can\s+you\s+explain|could\s+you\s+explain|tell\s+me\s+about)\b",
|
|
129
|
+
r"^\s*(who\s+invented|when\s+was|difference\s+between)\b",
|
|
130
|
+
r"^\s*(is\s+it\s+possible|is\s+there\s+a\s+way)\b",
|
|
131
|
+
]
|
|
132
|
+
|
|
133
|
+
_EXPLAIN_INDICATORS = [
|
|
134
|
+
r"\b(explain\s+this|explain\s+the|walk\s+me\s+through|how\s+does\s+this\s+work)\b",
|
|
135
|
+
r"\b(what\s+does\s+this\s+do|analyze\s+this|review\s+this|code\s+review)\b",
|
|
136
|
+
r"\b(read\s+and\s+explain|summarize\s+this\s+code)\b",
|
|
137
|
+
]
|
|
138
|
+
|
|
139
|
+
_DEBUG_INDICATORS = [
|
|
140
|
+
r"\b(debug|diagnose|troubleshoot|trace|fix\s+bug|fix\s+the\s+bug)\b",
|
|
141
|
+
r"\b(find\s+the\s+bug|find\s+the\s+issue|why\s+is\s+this\s+failing)\b",
|
|
142
|
+
r"\b(traceback|stacktrace|exception|segfault|segmentation\s+fault)\b",
|
|
143
|
+
r"\b(memory\s+leak|leak\s+during|deadlock|race\s+condition|hangs|hanging)\b",
|
|
144
|
+
r"\b(failing\s+test|test\s+failure|broken\s+build|syntax\s+error)\b",
|
|
145
|
+
r"\b(investigate\s+why|investigate\s+error|investigate\s+failure|investigate\s+leak)\b",
|
|
146
|
+
]
|
|
147
|
+
|
|
148
|
+
_MODIFY_INDICATORS = [
|
|
149
|
+
r"\b(modify|update|change|refactor|restructure|reorganize)\b",
|
|
150
|
+
r"\b(add\s+feature|extend|integrate|migrate|upgrade|optimize|improve)\b",
|
|
151
|
+
r"\b(convert|transform|replace|swap|rename|remove\s+from|delete\s+from)\b",
|
|
152
|
+
r"\b(add\s+endpoint|add\s+route|add\s+method|add\s+support\s+for)\b",
|
|
153
|
+
]
|
|
154
|
+
|
|
155
|
+
_STANDALONE_ALGORITHMS = {
|
|
156
|
+
"palindrome", "fibonacci", "quicksort", "mergesort", "bubblesort", "heapsort",
|
|
157
|
+
"binary search", "binary_search", "factorial", "hello world", "calculator",
|
|
158
|
+
"is_palindrome", "check palindrome", "reverse string", "reverse_string",
|
|
159
|
+
"prime number", "prime_number", "anagram", "todo script", "simple script",
|
|
160
|
+
"gcd", "lcm", "matrix multiplication", "linked list", "binary tree",
|
|
161
|
+
"two sum", "fizzbuzz", "fizz buzz", "string reversal",
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
_SYSTEM_INDICATORS = [
|
|
165
|
+
r"\b(full\s*stack|fullstack|full-stack)\b",
|
|
166
|
+
r"\b(microservice|microservices|distributed\s+system)\b",
|
|
167
|
+
r"\b(docker|kubernetes|k8s|container|docker-compose|dockerfile)\b",
|
|
168
|
+
r"\b(ci\/cd|pipeline|github\s+actions|jenkins)\b",
|
|
169
|
+
r"\b(database\s+migration|schema\s+migration|alembic|prisma\s+migrate)\b",
|
|
170
|
+
r"\b(authentication|oauth|jwt\s+auth|sso|rbac)\b",
|
|
171
|
+
r"\b(machine\s+learning|deep\s+learning|neural\s+network|training\s+pipeline)\b",
|
|
172
|
+
r"\b(architectural|architecture\s+overhaul|system-wide|entire\s+repo)\b",
|
|
173
|
+
]
|
|
174
|
+
|
|
175
|
+
_HIGH_RISK_INDICATORS = [
|
|
176
|
+
r"\b(delete|drop\s+database|drop\s+table|truncate|rm\s+-rf|destroy)\b",
|
|
177
|
+
r"\b(migration|schema\s+change|database\s+schema)\b",
|
|
178
|
+
r"\b(credential|secret|token|password|auth\s+bypass)\b",
|
|
179
|
+
r"\b(memory\s+leak|deadlock|race\s+condition|concurrency)\b",
|
|
180
|
+
r"\b(restructure\s+all|rewrite\s+all|refactor\s+entire)\b",
|
|
181
|
+
]
|
|
182
|
+
|
|
183
|
+
_ENVIRONMENT_INDICATORS = [
|
|
184
|
+
r"\b(docker|docker-compose|k8s|kubernetes|container|helm)\b",
|
|
185
|
+
r"\b(ci\/cd|workflow|pipeline|github\s+actions)\b",
|
|
186
|
+
r"\b(environment\s+variable|\.env|env\s+vars)\b",
|
|
187
|
+
r"\b(virtualenv|venv|pip\s+install|npm\s+install|cargo\s+add)\b",
|
|
188
|
+
]
|
|
189
|
+
|
|
190
|
+
_EXTERNAL_DEP_INDICATORS = [
|
|
191
|
+
r"\b(install|dependency|package|requirements\.txt|package\.json|cargo\.toml)\b",
|
|
192
|
+
r"\b(pydantic|fastapi|flask|django|react|vue|angular|express|torch|pandas|numpy)\b",
|
|
193
|
+
]
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def assess_task(
|
|
197
|
+
task: str,
|
|
198
|
+
repo_info: Optional[Dict[str, Any]] = None,
|
|
199
|
+
env_info: Optional[Dict[str, Any]] = None,
|
|
200
|
+
) -> TaskAssessment:
|
|
201
|
+
"""Evaluate task across 8 dimensions and select execution strategy.
|
|
202
|
+
|
|
203
|
+
Runs in <15ms. Pure deterministic Python logic, no network or LLM latency.
|
|
204
|
+
"""
|
|
205
|
+
if not task or not task.strip():
|
|
206
|
+
return TaskAssessment(
|
|
207
|
+
intent=TaskIntent.CREATE.value,
|
|
208
|
+
scope=TaskScope.LOCAL.value,
|
|
209
|
+
ambiguity=AmbiguityLevel.CLEAR.value,
|
|
210
|
+
repo_impact=RepositoryImpact.NONE.value,
|
|
211
|
+
dep_impact=DependencyImpact.NONE.value,
|
|
212
|
+
risk=RiskLevel.LOW.value,
|
|
213
|
+
verification_required=False,
|
|
214
|
+
verification_methods=[],
|
|
215
|
+
estimated_tool_calls=1,
|
|
216
|
+
strategy=ExecutionStrategy.FAST_PATH.value,
|
|
217
|
+
rationale="Empty task string defaulted to fast local execution.",
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
task_clean = task.strip()
|
|
221
|
+
task_lower = task_clean.lower()
|
|
222
|
+
repo = repo_info or {}
|
|
223
|
+
file_count = repo.get("file_count", 0)
|
|
224
|
+
has_tests = repo.get("has_tests", False)
|
|
225
|
+
frameworks = repo.get("frameworks", [])
|
|
226
|
+
project_type = repo.get("project_type", "empty")
|
|
227
|
+
entry_points = repo.get("entry_points", [])
|
|
228
|
+
|
|
229
|
+
# ── 1. Intent Classification ──
|
|
230
|
+
intent = TaskIntent.CREATE.value
|
|
231
|
+
if any(re.search(pat, task_lower) for pat in _QA_INDICATORS) and not any(
|
|
232
|
+
kw in task_lower for kw in ["write", "create", "implement", "build", "fix", "code", "script"]
|
|
233
|
+
):
|
|
234
|
+
intent = TaskIntent.QA.value
|
|
235
|
+
elif any(re.search(pat, task_lower) for pat in _EXPLAIN_INDICATORS) and not any(
|
|
236
|
+
kw in task_lower for kw in ["modify", "fix", "implement", "create", "refactor"]
|
|
237
|
+
):
|
|
238
|
+
intent = TaskIntent.EXPLAIN.value
|
|
239
|
+
elif any(re.search(pat, task_lower) for pat in _DEBUG_INDICATORS):
|
|
240
|
+
intent = TaskIntent.DEBUG.value
|
|
241
|
+
elif any(re.search(pat, task_lower) for pat in _MODIFY_INDICATORS):
|
|
242
|
+
intent = TaskIntent.MODIFY.value
|
|
243
|
+
elif intent == TaskIntent.CREATE.value and any(re.search(pat, task_lower) for pat in _QA_INDICATORS):
|
|
244
|
+
# Fallback question check
|
|
245
|
+
intent = TaskIntent.QA.value
|
|
246
|
+
|
|
247
|
+
# ── 2. Scope Evaluation ──
|
|
248
|
+
# Check if task is a standalone algorithm or single-file script
|
|
249
|
+
is_standalone_algo = any(algo in task_lower for algo in _STANDALONE_ALGORITHMS)
|
|
250
|
+
has_project_ref = any(
|
|
251
|
+
p in task_lower for p in [
|
|
252
|
+
"this project", "this repo", "our repo", "our project", "existing codebase",
|
|
253
|
+
"in this codebase", "in the project", "entry point", "route", "database",
|
|
254
|
+
]
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
# Check mentioned files
|
|
258
|
+
mentioned_files: List[str] = []
|
|
259
|
+
file_pattern = r"\b[a-zA-Z0-9_\-\./]+\.(?:py|js|ts|jsx|tsx|html|css|json|yaml|yml|toml|rs|go|java|sh|md)\b"
|
|
260
|
+
for match in re.finditer(file_pattern, task):
|
|
261
|
+
m_str = match.group(0)
|
|
262
|
+
if not m_str.startswith("http") and "/" in m_str or "." in m_str:
|
|
263
|
+
mentioned_files.append(m_str)
|
|
264
|
+
|
|
265
|
+
is_system_task = any(re.search(pat, task_lower) for pat in _SYSTEM_INDICATORS)
|
|
266
|
+
|
|
267
|
+
if intent == TaskIntent.QA.value:
|
|
268
|
+
scope = TaskScope.LOCAL.value
|
|
269
|
+
elif is_system_task or (file_count > 50 and intent in (TaskIntent.MODIFY.value, TaskIntent.DEBUG.value) and any(w in task_lower for w in ["all", "across", "entire", "every", "repo"])):
|
|
270
|
+
scope = TaskScope.SYSTEM.value
|
|
271
|
+
elif (is_standalone_algo or "simple script" in task_lower or "write a script" in task_lower) and not has_project_ref and (project_type == "empty" or not mentioned_files):
|
|
272
|
+
scope = TaskScope.LOCAL.value
|
|
273
|
+
elif len(mentioned_files) == 1 and not is_system_task and intent in (TaskIntent.MODIFY.value, TaskIntent.EXPLAIN.value):
|
|
274
|
+
scope = TaskScope.FILE.value
|
|
275
|
+
elif len(mentioned_files) > 1 or file_count > 5 or frameworks or intent in (TaskIntent.MODIFY.value, TaskIntent.DEBUG.value):
|
|
276
|
+
scope = TaskScope.MODULE.value
|
|
277
|
+
elif project_type != "empty" and file_count > 0:
|
|
278
|
+
scope = TaskScope.FILE.value
|
|
279
|
+
else:
|
|
280
|
+
scope = TaskScope.LOCAL.value
|
|
281
|
+
|
|
282
|
+
# ── 3. Ambiguity Evaluation ──
|
|
283
|
+
word_count = len(task_clean.split())
|
|
284
|
+
has_concrete_target = bool(mentioned_files) or is_standalone_algo
|
|
285
|
+
has_error_message = bool(re.search(r"(error:|exception:|failed with|traceback|syntaxerror|typeerror)", task_lower))
|
|
286
|
+
|
|
287
|
+
if word_count < 6 and not has_concrete_target and not has_error_message:
|
|
288
|
+
ambiguity = AmbiguityLevel.AMBIGUOUS.value
|
|
289
|
+
elif any(phrase in task_lower for phrase in ["make it better", "improve it", "fix it", "clean it up", "optimize it"]) and not has_concrete_target:
|
|
290
|
+
ambiguity = AmbiguityLevel.AMBIGUOUS.value
|
|
291
|
+
elif (has_concrete_target or has_error_message or word_count > 25) and not any(w in task_lower for w in ["maybe", "somehow", "explore"]):
|
|
292
|
+
ambiguity = AmbiguityLevel.CLEAR.value
|
|
293
|
+
else:
|
|
294
|
+
ambiguity = AmbiguityLevel.MODERATE.value
|
|
295
|
+
|
|
296
|
+
# ── 4. Repository Impact Evaluation ──
|
|
297
|
+
if intent in (TaskIntent.QA.value, TaskIntent.EXPLAIN.value):
|
|
298
|
+
repo_impact = RepositoryImpact.NONE.value
|
|
299
|
+
elif scope == TaskScope.SYSTEM.value or any(cfg in task_lower for cfg in ["pyproject.toml", "package.json", "cargo.toml", "docker-compose", "alembic"]):
|
|
300
|
+
repo_impact = RepositoryImpact.SYSTEMIC.value
|
|
301
|
+
elif scope == TaskScope.MODULE.value or len(mentioned_files) > 1:
|
|
302
|
+
repo_impact = RepositoryImpact.CROSS_FILE.value
|
|
303
|
+
elif scope == TaskScope.FILE.value:
|
|
304
|
+
repo_impact = RepositoryImpact.ISOLATED.value
|
|
305
|
+
elif scope == TaskScope.LOCAL.value:
|
|
306
|
+
repo_impact = RepositoryImpact.ISOLATED.value
|
|
307
|
+
else:
|
|
308
|
+
repo_impact = RepositoryImpact.CROSS_FILE.value
|
|
309
|
+
|
|
310
|
+
# ── 5. Dependency Impact Evaluation ──
|
|
311
|
+
if any(re.search(pat, task_lower) for pat in _ENVIRONMENT_INDICATORS):
|
|
312
|
+
dep_impact = DependencyImpact.ENVIRONMENT.value
|
|
313
|
+
elif any(re.search(pat, task_lower) for pat in _EXTERNAL_DEP_INDICATORS) and any(w in task_lower for w in ["install", "add package", "new lib", "require"]):
|
|
314
|
+
dep_impact = DependencyImpact.EXTERNAL_DEPS.value
|
|
315
|
+
elif scope in (TaskScope.MODULE.value, TaskScope.SYSTEM.value) or len(mentioned_files) > 0:
|
|
316
|
+
dep_impact = DependencyImpact.LOCAL_IMPORTS.value
|
|
317
|
+
else:
|
|
318
|
+
dep_impact = DependencyImpact.NONE.value
|
|
319
|
+
|
|
320
|
+
# ── 6. Risk Evaluation ──
|
|
321
|
+
if any(re.search(pat, task_lower) for pat in _HIGH_RISK_INDICATORS) or scope == TaskScope.SYSTEM.value:
|
|
322
|
+
risk = RiskLevel.HIGH.value
|
|
323
|
+
elif intent in (TaskIntent.QA.value, TaskIntent.EXPLAIN.value) or (scope == TaskScope.LOCAL.value and ambiguity == AmbiguityLevel.CLEAR.value):
|
|
324
|
+
risk = RiskLevel.LOW.value
|
|
325
|
+
elif intent == TaskIntent.DEBUG.value or repo_impact in (RepositoryImpact.CROSS_FILE.value, RepositoryImpact.SYSTEMIC.value):
|
|
326
|
+
risk = RiskLevel.MEDIUM.value
|
|
327
|
+
elif intent == TaskIntent.MODIFY.value:
|
|
328
|
+
risk = RiskLevel.MEDIUM.value
|
|
329
|
+
else:
|
|
330
|
+
risk = RiskLevel.LOW.value
|
|
331
|
+
|
|
332
|
+
# ── 7. Verification Requirements & Methods ──
|
|
333
|
+
verification_methods: List[str] = []
|
|
334
|
+
if intent in (TaskIntent.QA.value, TaskIntent.EXPLAIN.value):
|
|
335
|
+
verification_required = False
|
|
336
|
+
else:
|
|
337
|
+
verification_required = True
|
|
338
|
+
# Code tasks require verification
|
|
339
|
+
if project_type == "python" or any(f.endswith(".py") for f in mentioned_files) or "python" in task_lower:
|
|
340
|
+
verification_methods.append("syntax")
|
|
341
|
+
elif project_type == "node" or any(f.endswith((".js", ".ts")) for f in mentioned_files) or "javascript" in task_lower:
|
|
342
|
+
verification_methods.append("syntax")
|
|
343
|
+
|
|
344
|
+
if has_tests or "test" in task_lower or "pytest" in frameworks or "jest" in frameworks:
|
|
345
|
+
verification_methods.append("unit_tests")
|
|
346
|
+
|
|
347
|
+
if scope in (TaskScope.LOCAL.value, TaskScope.FILE.value) and not verification_methods:
|
|
348
|
+
verification_methods.append("execution")
|
|
349
|
+
|
|
350
|
+
# ── 8. Estimated Tool Calls ──
|
|
351
|
+
if intent == TaskIntent.QA.value:
|
|
352
|
+
estimated_tool_calls = 0
|
|
353
|
+
elif intent == TaskIntent.EXPLAIN.value:
|
|
354
|
+
estimated_tool_calls = 1 if mentioned_files else 0
|
|
355
|
+
elif scope == TaskScope.LOCAL.value and risk == RiskLevel.LOW.value:
|
|
356
|
+
estimated_tool_calls = 2 # write_file + optional verify run
|
|
357
|
+
elif scope == TaskScope.FILE.value:
|
|
358
|
+
estimated_tool_calls = 3 # read_file + edit_file + verify
|
|
359
|
+
elif scope == TaskScope.MODULE.value:
|
|
360
|
+
estimated_tool_calls = 6 # multi-file reads, edits, verification runs
|
|
361
|
+
else: # SYSTEM
|
|
362
|
+
estimated_tool_calls = 10
|
|
363
|
+
|
|
364
|
+
# ── 9. Strategy Routing (Fast Path vs Reasoning Path) ──
|
|
365
|
+
# Fast path: standalone algorithms, direct single scripts, direct Q&A / explain
|
|
366
|
+
# Reasoning path: debugging, refactoring, multi-file modules, systems, or moderate/high risk
|
|
367
|
+
if intent == TaskIntent.QA.value:
|
|
368
|
+
strategy = ExecutionStrategy.FAST_PATH.value
|
|
369
|
+
rationale = "Direct question answering without filesystem modifications."
|
|
370
|
+
elif intent == TaskIntent.EXPLAIN.value and scope in (TaskScope.LOCAL.value, TaskScope.FILE.value):
|
|
371
|
+
strategy = ExecutionStrategy.FAST_PATH.value
|
|
372
|
+
rationale = "Read and explain task with localized read-only scope."
|
|
373
|
+
elif (
|
|
374
|
+
scope == TaskScope.LOCAL.value
|
|
375
|
+
and risk == RiskLevel.LOW.value
|
|
376
|
+
and ambiguity == AmbiguityLevel.CLEAR.value
|
|
377
|
+
and intent == TaskIntent.CREATE.value
|
|
378
|
+
):
|
|
379
|
+
strategy = ExecutionStrategy.FAST_PATH.value
|
|
380
|
+
rationale = "Self-contained local script/algorithm with clear requirements and low risk."
|
|
381
|
+
else:
|
|
382
|
+
strategy = ExecutionStrategy.REASONING_PATH.value
|
|
383
|
+
rationale = (
|
|
384
|
+
f"Multi-factor assessment indicates {scope} scope with {risk} risk and {intent} intent; "
|
|
385
|
+
"requires Plan -> Execute -> Verify pipeline."
|
|
386
|
+
)
|
|
387
|
+
|
|
388
|
+
return TaskAssessment(
|
|
389
|
+
intent=intent,
|
|
390
|
+
scope=scope,
|
|
391
|
+
ambiguity=ambiguity,
|
|
392
|
+
repo_impact=repo_impact,
|
|
393
|
+
dep_impact=dep_impact,
|
|
394
|
+
risk=risk,
|
|
395
|
+
verification_required=verification_required,
|
|
396
|
+
verification_methods=verification_methods,
|
|
397
|
+
estimated_tool_calls=estimated_tool_calls,
|
|
398
|
+
strategy=strategy,
|
|
399
|
+
rationale=rationale,
|
|
400
|
+
)
|
|
@@ -128,6 +128,14 @@ class LocalBackend:
|
|
|
128
128
|
clean["content"] = content
|
|
129
129
|
sanitized.append(clean)
|
|
130
130
|
|
|
131
|
+
if sanitized and sanitized[-1].get("role") == "user" and "[Tool Result" in sanitized[-1].get("content", ""):
|
|
132
|
+
prompt_hint = (
|
|
133
|
+
"\n\n[Next Action: Output your next <tool_call> (e.g. write_file or bash) immediately. "
|
|
134
|
+
"Do NOT output conversational text. When all files are created and verified, output: DONE: <summary>.]"
|
|
135
|
+
)
|
|
136
|
+
if prompt_hint not in sanitized[-1]["content"]:
|
|
137
|
+
sanitized[-1]["content"] += prompt_hint
|
|
138
|
+
|
|
131
139
|
return sanitized
|
|
132
140
|
|
|
133
141
|
def chat_completion(
|
|
@@ -48,6 +48,9 @@ def _repair_and_parse_json_arguments(raw_args: str) -> dict:
|
|
|
48
48
|
# 3. Structural repair for streaming cutoffs / trailing commas
|
|
49
49
|
repaired = text
|
|
50
50
|
|
|
51
|
+
# Strip any trailing incomplete key without value (e.g. `, "file` from streaming cutoff)
|
|
52
|
+
repaired = re.sub(r',\s*["\']?[a-zA-Z0-9_-]*\s*$', '', repaired)
|
|
53
|
+
|
|
51
54
|
# Remove trailing commas like `..., }` or `..., ]`
|
|
52
55
|
repaired = re.sub(r",\s*([\}\]])", r"\1", repaired)
|
|
53
56
|
|
|
@@ -83,15 +86,18 @@ def _repair_and_parse_json_arguments(raw_args: str) -> dict:
|
|
|
83
86
|
except Exception:
|
|
84
87
|
pass
|
|
85
88
|
|
|
86
|
-
# 4. If still failing, attempt regex key-value extraction
|
|
89
|
+
# 4. If still failing, attempt regex key-value extraction with proper string escape support
|
|
87
90
|
extracted = {}
|
|
88
|
-
pattern = r"""['"]([a-zA-Z0-9_-]+)['"]\s*:\s*(?:[
|
|
91
|
+
pattern = r"""['"]([a-zA-Z0-9_-]+)['"]\s*:\s*(?:(?:"((?:\\.|[^"\\])*)"|'((?:\\.|[^'\\])*)')|([0-9.]+|true|false|null))"""
|
|
89
92
|
for kv in re.finditer(pattern, text, re.IGNORECASE):
|
|
90
93
|
k = kv.group(1)
|
|
91
|
-
v_str = kv.group(2)
|
|
92
|
-
v_lit = kv.group(
|
|
94
|
+
v_str = kv.group(2) if kv.group(2) is not None else kv.group(3)
|
|
95
|
+
v_lit = kv.group(4)
|
|
93
96
|
if v_str is not None:
|
|
94
|
-
|
|
97
|
+
try:
|
|
98
|
+
extracted[k] = json.loads(f'"{v_str}"')
|
|
99
|
+
except Exception:
|
|
100
|
+
extracted[k] = v_str
|
|
95
101
|
elif v_lit is not None:
|
|
96
102
|
try:
|
|
97
103
|
extracted[k] = json.loads(v_lit.lower())
|