alpiecode 10.5.1__tar.gz → 10.5.2__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.
Files changed (51) hide show
  1. {alpiecode-10.5.1 → alpiecode-10.5.2}/PKG-INFO +1 -1
  2. {alpiecode-10.5.1 → alpiecode-10.5.2}/pyproject.toml +1 -1
  3. {alpiecode-10.5.1 → alpiecode-10.5.2}/src/alpiecode.egg-info/PKG-INFO +1 -1
  4. {alpiecode-10.5.1 → alpiecode-10.5.2}/src/alpiecode.egg-info/SOURCES.txt +3 -1
  5. {alpiecode-10.5.1 → alpiecode-10.5.2}/src/codeagent/__init__.py +1 -1
  6. {alpiecode-10.5.1 → alpiecode-10.5.2}/src/codeagent/agent.py +70 -25
  7. alpiecode-10.5.2/src/codeagent/assessment.py +400 -0
  8. {alpiecode-10.5.1 → alpiecode-10.5.2}/src/codeagent/backends/local_backend.py +8 -0
  9. {alpiecode-10.5.1 → alpiecode-10.5.2}/src/codeagent/backends/openai_backend.py +11 -5
  10. {alpiecode-10.5.1 → alpiecode-10.5.2}/src/codeagent/cli.py +9 -3
  11. {alpiecode-10.5.1 → alpiecode-10.5.2}/src/codeagent/context.py +3 -7
  12. {alpiecode-10.5.1 → alpiecode-10.5.2}/src/codeagent/discovery.py +21 -74
  13. {alpiecode-10.5.1 → alpiecode-10.5.2}/src/codeagent/executor.py +96 -30
  14. {alpiecode-10.5.1 → alpiecode-10.5.2}/src/codeagent/orchestrator.py +162 -22
  15. {alpiecode-10.5.1 → alpiecode-10.5.2}/src/codeagent/prompt.py +21 -0
  16. {alpiecode-10.5.1 → alpiecode-10.5.2}/src/codeagent/system1.py +4 -1
  17. {alpiecode-10.5.1 → alpiecode-10.5.2}/test/test_context_and_trajectory.py +11 -0
  18. alpiecode-10.5.2/test/test_task_assessment.py +468 -0
  19. {alpiecode-10.5.1 → alpiecode-10.5.2}/README.md +0 -0
  20. {alpiecode-10.5.1 → alpiecode-10.5.2}/setup.cfg +0 -0
  21. {alpiecode-10.5.1 → alpiecode-10.5.2}/src/alpiecode/__init__.py +0 -0
  22. {alpiecode-10.5.1 → alpiecode-10.5.2}/src/alpiecode.egg-info/dependency_links.txt +0 -0
  23. {alpiecode-10.5.1 → alpiecode-10.5.2}/src/alpiecode.egg-info/entry_points.txt +0 -0
  24. {alpiecode-10.5.1 → alpiecode-10.5.2}/src/alpiecode.egg-info/requires.txt +0 -0
  25. {alpiecode-10.5.1 → alpiecode-10.5.2}/src/alpiecode.egg-info/top_level.txt +0 -0
  26. {alpiecode-10.5.1 → alpiecode-10.5.2}/src/codeagent/backends/__init__.py +0 -0
  27. {alpiecode-10.5.1 → alpiecode-10.5.2}/src/codeagent/backends/base.py +0 -0
  28. {alpiecode-10.5.1 → alpiecode-10.5.2}/src/codeagent/cache.py +0 -0
  29. {alpiecode-10.5.1 → alpiecode-10.5.2}/src/codeagent/client.py +0 -0
  30. {alpiecode-10.5.1 → alpiecode-10.5.2}/src/codeagent/compaction.py +0 -0
  31. {alpiecode-10.5.1 → alpiecode-10.5.2}/src/codeagent/config.py +0 -0
  32. {alpiecode-10.5.1 → alpiecode-10.5.2}/src/codeagent/doctor.py +0 -0
  33. {alpiecode-10.5.1 → alpiecode-10.5.2}/src/codeagent/dream_rsi.py +0 -0
  34. {alpiecode-10.5.1 → alpiecode-10.5.2}/src/codeagent/extension/alpiecode.vsix +0 -0
  35. {alpiecode-10.5.1 → alpiecode-10.5.2}/src/codeagent/git_ops.py +0 -0
  36. {alpiecode-10.5.1 → alpiecode-10.5.2}/src/codeagent/github.py +0 -0
  37. {alpiecode-10.5.1 → alpiecode-10.5.2}/src/codeagent/guardian.py +0 -0
  38. {alpiecode-10.5.1 → alpiecode-10.5.2}/src/codeagent/guardrails.py +0 -0
  39. {alpiecode-10.5.1 → alpiecode-10.5.2}/src/codeagent/ipython_ext.py +0 -0
  40. {alpiecode-10.5.1 → alpiecode-10.5.2}/src/codeagent/local_model.py +0 -0
  41. {alpiecode-10.5.1 → alpiecode-10.5.2}/src/codeagent/media.py +0 -0
  42. {alpiecode-10.5.1 → alpiecode-10.5.2}/src/codeagent/memory.py +0 -0
  43. {alpiecode-10.5.1 → alpiecode-10.5.2}/src/codeagent/metrics.py +0 -0
  44. {alpiecode-10.5.1 → alpiecode-10.5.2}/src/codeagent/progress.py +0 -0
  45. {alpiecode-10.5.1 → alpiecode-10.5.2}/src/codeagent/rephraser.py +0 -0
  46. {alpiecode-10.5.1 → alpiecode-10.5.2}/src/codeagent/server.py +0 -0
  47. {alpiecode-10.5.1 → alpiecode-10.5.2}/src/codeagent/session.py +0 -0
  48. {alpiecode-10.5.1 → alpiecode-10.5.2}/src/codeagent/tools.py +0 -0
  49. {alpiecode-10.5.1 → alpiecode-10.5.2}/src/codeagent/updater.py +0 -0
  50. {alpiecode-10.5.1 → alpiecode-10.5.2}/src/codeagent/vscode_installer.py +0 -0
  51. {alpiecode-10.5.1 → alpiecode-10.5.2}/test/test_dream_rsi.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: alpiecode
3
- Version: 10.5.1
3
+ Version: 10.5.2
4
4
  Summary: AlpieCode
5
5
  Requires-Python: >=3.9
6
6
  Requires-Dist: huggingface_hub>=0.20.0
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "alpiecode"
7
- version = "10.5.1"
7
+ version = "10.5.2"
8
8
  description = "AlpieCode"
9
9
  requires-python = ">=3.9"
10
10
  dependencies = [
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: alpiecode
3
- Version: 10.5.1
3
+ Version: 10.5.2
4
4
  Summary: AlpieCode
5
5
  Requires-Python: >=3.9
6
6
  Requires-Dist: huggingface_hub>=0.20.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
@@ -44,4 +45,5 @@ src/codeagent/backends/local_backend.py
44
45
  src/codeagent/backends/openai_backend.py
45
46
  src/codeagent/extension/alpiecode.vsix
46
47
  test/test_context_and_trajectory.py
47
- test/test_dream_rsi.py
48
+ test/test_dream_rsi.py
49
+ test/test_task_assessment.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.1"
20
+ __version__ = "10.5.2"
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
- ) -> list:
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 == "safety_ceiling" and verbose:
474
- if HAS_RICH:
475
- console.print(
476
- f"\n🛑 [bold red]Safety ceiling ({event.data['ceiling']} turns) reached.[/bold red]\n"
477
- "This is an emergency stop — the agent may be stuck in an unrecoverable loop.",
478
- style="bold red"
479
- )
480
- else:
481
- print(f"\n🛑 Safety ceiling ({event.data['ceiling']}) reached.")
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
- console.rule("[bold green]✅ Complete[/bold green]")
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" and verbose:
496
- progress = event.data.get("progress", {})
497
- if HAS_RICH:
498
- console.print(f"\n⚠️ [bold yellow]Safety ceiling ({event.data['max_turns']}) reached.[/bold yellow]", style="bold yellow")
499
- if progress:
500
- console.print(
501
- f" Progress: {progress.get('progress_turns', 0)}/{progress.get('total_turns', 0)} turns made progress, "
502
- f"{progress.get('files_created', 0)} files created, "
503
- f"{progress.get('stall_interventions', 0)} stall interventions",
504
- style="dim"
505
- )
506
- else:
507
- print(f"\n⚠️ Safety ceiling ({event.data['max_turns']}) reached.")
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
- return session.context.messages if "session" in locals() else []
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 for simple {"key": "val"} forms
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*(?:['"](.*?)['"]|([0-9.]+|true|false|null))"""
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(3)
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
- extracted[k] = v_str
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())