alpiecode 10.5.2__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.
Files changed (53) hide show
  1. {alpiecode-10.5.2 → alpiecode-10.5.3}/PKG-INFO +1 -1
  2. {alpiecode-10.5.2 → alpiecode-10.5.3}/pyproject.toml +1 -1
  3. {alpiecode-10.5.2 → alpiecode-10.5.3}/src/alpiecode.egg-info/PKG-INFO +1 -1
  4. {alpiecode-10.5.2 → alpiecode-10.5.3}/src/alpiecode.egg-info/SOURCES.txt +3 -1
  5. {alpiecode-10.5.2 → alpiecode-10.5.3}/src/codeagent/__init__.py +1 -1
  6. {alpiecode-10.5.2 → alpiecode-10.5.3}/src/codeagent/orchestrator.py +55 -87
  7. {alpiecode-10.5.2 → alpiecode-10.5.3}/src/codeagent/progress.py +27 -2
  8. {alpiecode-10.5.2 → alpiecode-10.5.3}/src/codeagent/system1.py +115 -14
  9. alpiecode-10.5.3/src/codeagent/verifier.py +498 -0
  10. {alpiecode-10.5.2 → alpiecode-10.5.3}/test/test_task_assessment.py +101 -0
  11. alpiecode-10.5.3/test/test_verifier.py +197 -0
  12. {alpiecode-10.5.2 → alpiecode-10.5.3}/README.md +0 -0
  13. {alpiecode-10.5.2 → alpiecode-10.5.3}/setup.cfg +0 -0
  14. {alpiecode-10.5.2 → alpiecode-10.5.3}/src/alpiecode/__init__.py +0 -0
  15. {alpiecode-10.5.2 → alpiecode-10.5.3}/src/alpiecode.egg-info/dependency_links.txt +0 -0
  16. {alpiecode-10.5.2 → alpiecode-10.5.3}/src/alpiecode.egg-info/entry_points.txt +0 -0
  17. {alpiecode-10.5.2 → alpiecode-10.5.3}/src/alpiecode.egg-info/requires.txt +0 -0
  18. {alpiecode-10.5.2 → alpiecode-10.5.3}/src/alpiecode.egg-info/top_level.txt +0 -0
  19. {alpiecode-10.5.2 → alpiecode-10.5.3}/src/codeagent/agent.py +0 -0
  20. {alpiecode-10.5.2 → alpiecode-10.5.3}/src/codeagent/assessment.py +0 -0
  21. {alpiecode-10.5.2 → alpiecode-10.5.3}/src/codeagent/backends/__init__.py +0 -0
  22. {alpiecode-10.5.2 → alpiecode-10.5.3}/src/codeagent/backends/base.py +0 -0
  23. {alpiecode-10.5.2 → alpiecode-10.5.3}/src/codeagent/backends/local_backend.py +0 -0
  24. {alpiecode-10.5.2 → alpiecode-10.5.3}/src/codeagent/backends/openai_backend.py +0 -0
  25. {alpiecode-10.5.2 → alpiecode-10.5.3}/src/codeagent/cache.py +0 -0
  26. {alpiecode-10.5.2 → alpiecode-10.5.3}/src/codeagent/cli.py +0 -0
  27. {alpiecode-10.5.2 → alpiecode-10.5.3}/src/codeagent/client.py +0 -0
  28. {alpiecode-10.5.2 → alpiecode-10.5.3}/src/codeagent/compaction.py +0 -0
  29. {alpiecode-10.5.2 → alpiecode-10.5.3}/src/codeagent/config.py +0 -0
  30. {alpiecode-10.5.2 → alpiecode-10.5.3}/src/codeagent/context.py +0 -0
  31. {alpiecode-10.5.2 → alpiecode-10.5.3}/src/codeagent/discovery.py +0 -0
  32. {alpiecode-10.5.2 → alpiecode-10.5.3}/src/codeagent/doctor.py +0 -0
  33. {alpiecode-10.5.2 → alpiecode-10.5.3}/src/codeagent/dream_rsi.py +0 -0
  34. {alpiecode-10.5.2 → alpiecode-10.5.3}/src/codeagent/executor.py +0 -0
  35. {alpiecode-10.5.2 → alpiecode-10.5.3}/src/codeagent/extension/alpiecode.vsix +0 -0
  36. {alpiecode-10.5.2 → alpiecode-10.5.3}/src/codeagent/git_ops.py +0 -0
  37. {alpiecode-10.5.2 → alpiecode-10.5.3}/src/codeagent/github.py +0 -0
  38. {alpiecode-10.5.2 → alpiecode-10.5.3}/src/codeagent/guardian.py +0 -0
  39. {alpiecode-10.5.2 → alpiecode-10.5.3}/src/codeagent/guardrails.py +0 -0
  40. {alpiecode-10.5.2 → alpiecode-10.5.3}/src/codeagent/ipython_ext.py +0 -0
  41. {alpiecode-10.5.2 → alpiecode-10.5.3}/src/codeagent/local_model.py +0 -0
  42. {alpiecode-10.5.2 → alpiecode-10.5.3}/src/codeagent/media.py +0 -0
  43. {alpiecode-10.5.2 → alpiecode-10.5.3}/src/codeagent/memory.py +0 -0
  44. {alpiecode-10.5.2 → alpiecode-10.5.3}/src/codeagent/metrics.py +0 -0
  45. {alpiecode-10.5.2 → alpiecode-10.5.3}/src/codeagent/prompt.py +0 -0
  46. {alpiecode-10.5.2 → alpiecode-10.5.3}/src/codeagent/rephraser.py +0 -0
  47. {alpiecode-10.5.2 → alpiecode-10.5.3}/src/codeagent/server.py +0 -0
  48. {alpiecode-10.5.2 → alpiecode-10.5.3}/src/codeagent/session.py +0 -0
  49. {alpiecode-10.5.2 → alpiecode-10.5.3}/src/codeagent/tools.py +0 -0
  50. {alpiecode-10.5.2 → alpiecode-10.5.3}/src/codeagent/updater.py +0 -0
  51. {alpiecode-10.5.2 → alpiecode-10.5.3}/src/codeagent/vscode_installer.py +0 -0
  52. {alpiecode-10.5.2 → alpiecode-10.5.3}/test/test_context_and_trajectory.py +0 -0
  53. {alpiecode-10.5.2 → alpiecode-10.5.3}/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.2
3
+ Version: 10.5.3
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.2"
7
+ version = "10.5.3"
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.2
3
+ Version: 10.5.3
4
4
  Summary: AlpieCode
5
5
  Requires-Python: >=3.9
6
6
  Requires-Dist: huggingface_hub>=0.20.0
@@ -38,6 +38,7 @@ src/codeagent/session.py
38
38
  src/codeagent/system1.py
39
39
  src/codeagent/tools.py
40
40
  src/codeagent/updater.py
41
+ src/codeagent/verifier.py
41
42
  src/codeagent/vscode_installer.py
42
43
  src/codeagent/backends/__init__.py
43
44
  src/codeagent/backends/base.py
@@ -46,4 +47,5 @@ src/codeagent/backends/openai_backend.py
46
47
  src/codeagent/extension/alpiecode.vsix
47
48
  test/test_context_and_trajectory.py
48
49
  test/test_dream_rsi.py
49
- test/test_task_assessment.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.2"
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."""
@@ -334,7 +334,11 @@ class AgentOrchestrator:
334
334
  base_enable_thinking = task_context.enable_thinking
335
335
 
336
336
  task_metrics.record_context_prep((time.time() - ctx_prep_start) * 1000.0)
337
- # ── Goal-driven turn loop (no fixed limit) ──
337
+ # Extract explicitly named deliverable files from task prompt (e.g. auth.py, test_auth.py)
338
+ named_deliverables = list(dict.fromkeys(
339
+ re.findall(r"\b([a-zA-Z0-9_\-]+\.(?:py|js|ts|html|css|json|toml|sh))\b", task)
340
+ ))
341
+
338
342
  progress_monitor = ProgressMonitor()
339
343
  turn = 0
340
344
  prior_turn_had_error = False
@@ -368,6 +372,9 @@ class AgentOrchestrator:
368
372
  # ── Stall Detection & Corrective Intervention ──
369
373
  if progress_monitor.is_stalled(threshold=3):
370
374
  advice = progress_monitor.get_stall_advice()
375
+ uncreated = [f for f in named_deliverables if f not in session_touched_files]
376
+ if uncreated:
377
+ advice += f"\nNote: The following files requested in the task have not been created yet: {', '.join(uncreated)}. Create them now using write_file."
371
378
  session.context.add_user_message(advice)
372
379
  yield AgentEvent("stall_intervention", {
373
380
  "turn": turn,
@@ -675,97 +682,58 @@ class AgentOrchestrator:
675
682
  verify_attempts = getattr(session, "_auto_verify_attempts", 0)
676
683
 
677
684
  if has_code_files and not auto_verify_passed and verify_attempts < 2:
678
- # 1. In-process Python syntax verification for all modified Python files
679
- syntax_errs = []
680
- for tf in session_touched_files:
681
- if tf.endswith(".py"):
682
- full_tf = session.workdir / tf
683
- if full_tf.exists():
684
- try:
685
- import ast as _ast
686
- _ast.parse(full_tf.read_text(encoding="utf-8", errors="replace"))
687
- except SyntaxError as se:
688
- syntax_errs.append(f"- '{tf}' line {se.lineno}: {se.msg}")
689
-
690
- if syntax_errs:
685
+ from .verifier import IntelligentVerificationEngine, format_replan_directive
686
+ v_engine = IntelligentVerificationEngine(session.workdir)
687
+
688
+ events_queue = []
689
+ v_res = v_engine.run_full_verification(
690
+ task=task,
691
+ session_touched_files=session_touched_files,
692
+ task_context=task_context,
693
+ run_bash_fn=session.executor.dispatch.get("bash", lambda a: ""),
694
+ is_offline=is_offline,
695
+ successful_commands=getattr(session.executor, "successful_bash_commands", None),
696
+ events_queue=events_queue,
697
+ )
698
+
699
+ for ev_name, ev_data in events_queue:
700
+ yield AgentEvent(ev_name, ev_data)
701
+
702
+ if not v_res.passed:
691
703
  session._auto_verify_attempts = verify_attempts + 1
692
- err_msg = "\n".join(syntax_errs)
693
- session.context.add_user_message(
694
- f"[VERIFICATION FAILED -> RE-PLAN & REASON]\nSyntax error(s) detected in your code:\n{err_msg}\n\n"
695
- "Reasoning Path Directive:\n"
696
- "1. Analyze why these syntax errors occurred.\n"
697
- "2. Re-plan the fix.\n"
698
- "3. Use edit_file to apply the corrected code.\n"
699
- "4. Verify before marking DONE."
700
- )
701
- yield AgentEvent("verification_failed", {
702
- "type": "syntax",
703
- "errors": syntax_errs,
704
- })
705
- yield AgentEvent("replan", {
704
+ replan_prompt = format_replan_directive(v_res, is_offline=is_offline)
705
+ session.context.add_user_message(replan_prompt)
706
+
707
+ v_type = "syntax" if v_res.tier == "syntax" else ("tests" if "test" in v_res.tier else v_res.tier)
708
+ v_event_data = {
709
+ "type": v_type,
710
+ "tier": v_res.tier,
711
+ "error_type": v_res.error_type,
712
+ "message": v_res.message,
713
+ "failing_file": v_res.failing_file,
714
+ }
715
+ if v_res.errors:
716
+ v_event_data["errors"] = v_res.errors
717
+ if v_res.command:
718
+ v_event_data["command"] = v_res.command
719
+ v_event_data["exit_code"] = v_res.exit_code
720
+ yield AgentEvent("verification_failed", v_event_data)
721
+
722
+ replan_trigger = "syntax_errors" if v_res.tier == "syntax" else ("test_failure" if "test" in v_res.tier else f"{v_res.tier}_failure")
723
+ replan_data = {
706
724
  "turn": turn,
707
725
  "strategy": strategy,
708
- "trigger": "syntax_errors",
709
- "details": syntax_errs,
710
- })
726
+ "trigger": replan_trigger,
727
+ "details": v_res.errors or v_res.message,
728
+ }
729
+ if v_res.command:
730
+ replan_data["command"] = v_res.command
731
+ replan_data["exit_code"] = v_res.exit_code
732
+ yield AgentEvent("replan", replan_data)
733
+
711
734
  prior_turn_had_error = True
712
735
  continue
713
736
 
714
- # 2. Automated test verification if project has tests
715
- if task_context.has_tests and not getattr(session, "_has_run_tests_in_session", False):
716
- session._auto_verify_attempts = verify_attempts + 1
717
- session._has_run_tests_in_session = True
718
- test_cmd = "python3 -m pytest -q --tb=short" if task_context.project_type == "python" else "npm test" if task_context.project_type == "node" else None
719
- if test_cmd:
720
- yield AgentEvent("status", {
721
- "phase": "verifying",
722
- "message": f"Auto-running verification test suite ({test_cmd})...",
723
- })
724
- t_res = session.executor.dispatch.get("bash", lambda a: "")({"command": test_cmd})
725
- t_exit = 0
726
- try:
727
- import json as _json
728
- json_str = t_res
729
- if "{" in json_str:
730
- json_str = json_str[json_str.find("{"):]
731
- t_data = _json.loads(json_str)
732
- t_exit = t_data.get("exit_code", 0)
733
- t_out = t_data.get("stdout", "")
734
- if t_data.get("stderr"):
735
- t_out += "\n" + t_data.get("stderr")
736
- except Exception:
737
- t_out = str(t_res)
738
- if "exit_code=" in t_res:
739
- import re as _re
740
- m = _re.search(r"exit_code=(\d+)", t_res)
741
- if m:
742
- t_exit = int(m.group(1))
743
-
744
- if t_exit != 0:
745
- session.context.add_user_message(
746
- f"[VERIFICATION FAILED -> RE-PLAN & REASON]\nTest suite failed (exit_code={t_exit}) after your changes:\n"
747
- f"{t_out[:1200]}\n\n"
748
- "Reasoning Path Directive:\n"
749
- "1. Diagnose the root cause of the test failure.\n"
750
- "2. Re-plan your implementation or fix the broken assertion/logic.\n"
751
- "3. Apply targeted fixes with edit_file.\n"
752
- "4. Verify the test suite passes before marking completion."
753
- )
754
- yield AgentEvent("verification_failed", {
755
- "type": "tests",
756
- "command": test_cmd,
757
- "exit_code": t_exit,
758
- })
759
- yield AgentEvent("replan", {
760
- "turn": turn,
761
- "strategy": strategy,
762
- "trigger": "test_failure",
763
- "command": test_cmd,
764
- "exit_code": t_exit,
765
- })
766
- prior_turn_had_error = True
767
- continue
768
-
769
737
  session._auto_verify_passed = True
770
738
 
771
739
  # Cache if single-turn
@@ -863,7 +831,7 @@ class AgentOrchestrator:
863
831
  turn=turn,
864
832
  tool_calls=[{'name': tc.name, 'arguments': tc.arguments} for tc in tool_calls],
865
833
  tool_results=[{'name': res.name, 'content': res.content[:200], 'duration_ms': res.duration_ms} for res in results],
866
- error_types=[system1.triage_error(res.content)[0].value for res in results if system1.triage_error(res.content)[0].value != 'none'],
834
+ error_types=[system1.triage_error(res.content, tool_name=res.name)[0].value for res in results if system1.triage_error(res.content, tool_name=res.name)[0].value != 'none'],
867
835
  had_error=prior_turn_had_error,
868
836
  duration_ms=(time.time() - llm_call_start) * 1000.0,
869
837
  tokens_estimated=max_tokens,
@@ -126,10 +126,25 @@ class ProgressMonitor:
126
126
  snap.errors.append(content[:100])
127
127
 
128
128
  # Determine progress
129
+ # Read-only bash inspection commands (cat, head, ls, wc, etc.) do NOT count as progress on their own
130
+ import re
131
+ read_only_verbs = {"cat", "head", "tail", "ls", "file", "wc", "stat", "echo", "pwd", "which", "type"}
132
+ has_productive_bash = False
133
+ for tc in tool_calls:
134
+ name = tc.get("name", "") if isinstance(tc, dict) else getattr(tc, "name", "")
135
+ if name == "bash":
136
+ cmd = (tc.get("arguments", {}) if isinstance(tc, dict) else getattr(tc, "arguments", {})).get("command", "")
137
+ # Strip shell redirections like 2>&1, >/dev/null, etc. before tokenizing
138
+ cleaned_cmd = re.sub(r"\d*>\s*&?\d*|\d*<\s*&?\d*", "", cmd)
139
+ parts = [p.strip() for p in re.split(r"\s*(?:&&|\|\||[;|])\s*", cleaned_cmd) if p.strip()]
140
+ is_pure_read = bool(parts) and all(p.split()[0].lower() in read_only_verbs for p in parts if p.split())
141
+ if not is_pure_read:
142
+ has_productive_bash = True
143
+
129
144
  snap.had_progress = bool(
130
145
  snap.files_created
131
146
  or snap.files_modified
132
- or (snap.bash_exit_codes and 0 in snap.bash_exit_codes)
147
+ or (snap.bash_exit_codes and 0 in snap.bash_exit_codes and has_productive_bash)
133
148
  )
134
149
 
135
150
  # No tools called at all = not progress (empty turn)
@@ -190,12 +205,22 @@ class ProgressMonitor:
190
205
  f"Take a step back: what is the simplest path to a working solution?"
191
206
  )
192
207
 
208
+ if all(
209
+ not s.files_created and not s.files_modified and any(t in ("read_file", "list_files", "bash") for t in s.tools_called)
210
+ for s in recent
211
+ ):
212
+ return (
213
+ "[SYSTEM - PROGRESS MONITOR] Stalled in read-only inspection for 3 consecutive turns. "
214
+ "You are repeatedly viewing or inspecting files without creating or editing deliverables. "
215
+ "Stop inspecting. Proceed immediately to implementing the next deliverable: use write_file or edit_file to create remaining code/test files, or run verification tests."
216
+ )
217
+
193
218
  if all(snap.errors or (snap.bash_exit_codes and 0 not in snap.bash_exit_codes) for snap in recent):
194
219
  err_msg = f"\nLast error encountered: {last_error_text}\n" if last_error_text else ""
195
220
  return (
196
221
  "[SYSTEM - PROGRESS MONITOR] You have encountered errors for 3 consecutive turns. "
197
222
  f"STOP retrying the same approach.{err_msg} Instead:\n"
198
- "1. Use read_file to examine the FULL current state of the file(s) you\'re editing\n"
223
+ "1. Use read_file to examine the FULL current state of the file(s) you're editing\n"
199
224
  "2. Identify the root cause of the error (not the symptom)\n"
200
225
  "3. Make ONE comprehensive fix that addresses all issues\n"
201
226
  "If the task approach is fundamentally wrong, start with a simpler design."
@@ -75,6 +75,8 @@ class System1Controller:
75
75
  self._action_history: List[str] = []
76
76
  self._loop_count: Dict[str, int] = {}
77
77
  self._file_write_counts: Dict[str, int] = {}
78
+ self._file_read_counts: Dict[str, int] = {}
79
+ self._ls_count: int = 0
78
80
  self.total_interventions: int = 0
79
81
  self.total_llm_skips: int = 0
80
82
 
@@ -89,6 +91,8 @@ class System1Controller:
89
91
  self._action_history.clear()
90
92
  self._loop_count.clear()
91
93
  self._file_write_counts.clear()
94
+ self._file_read_counts.clear()
95
+ self._ls_count = 0
92
96
 
93
97
  # ── 1. Test Result Checking ─────────────────────────────────────────
94
98
 
@@ -141,13 +145,35 @@ class System1Controller:
141
145
 
142
146
  # ── 2. Error Triage ─────────────────────────────────────────────────
143
147
 
144
- def triage_error(self, err_text: str) -> Tuple[ErrorType, Optional[str], Optional[int]]:
148
+ def triage_error(self, err_text: str, tool_name: Optional[str] = None) -> Tuple[ErrorType, Optional[str], Optional[int]]:
145
149
  """Classify error into ErrorType and extract file & line."""
146
150
  if not err_text or not err_text.strip():
147
151
  return ErrorType.NONE, None, None
148
152
 
149
- # Truncation error or syntax cutoff due to unexpected EOF
153
+ # 1. Read-only tools (read_file, list_files, file_search, read_symbol)
154
+ if tool_name in ("read_file", "list_files", "file_search", "read_symbol"):
155
+ stripped = err_text.strip()
156
+ # Only an error if the tool explicitly reported failure
157
+ if stripped.lower().startswith("error:") or stripped.startswith("FileNotFoundError") or "file not found" in stripped.lower():
158
+ return ErrorType.UNKNOWN, None, None
159
+ # Otherwise, read_file output is source code, NOT an execution error!
160
+ return ErrorType.NONE, None, None
161
+
162
+ # Numbered lines from _read_file: " 1 | ..."
163
+ if re.match(r"^\s*\d+\s*\|", err_text.lstrip()):
164
+ return ErrorType.NONE, None, None
165
+
166
+ # Successful write_file: "wrote N bytes to ..."
167
+ if err_text.startswith("wrote ") and " bytes to " in err_text:
168
+ return ErrorType.NONE, None, None
169
+
170
+ # Successful bash command: exit_code is 0 and no failure banner
171
+ if ('"exit_code": 0' in err_text or '"exit_code":0' in err_text) and "⚠️ COMMAND FAILED" not in err_text:
172
+ return ErrorType.NONE, None, None
173
+
150
174
  lower_err = err_text.lower()
175
+
176
+ # Truncation error or syntax cutoff due to unexpected EOF
151
177
  if "truncated" in lower_err or "unexpected end of input" in lower_err or "unexpected eof" in lower_err:
152
178
  file_match = re.search(r"for\s+['\"]?([a-zA-Z0-9_\.\-\/]+)['\"]?", err_text) or re.search(r"in\s+['\"]?([a-zA-Z0-9_\.\-\/]+)['\"]?", err_text)
153
179
  target = file_match.group(1) if file_match else None
@@ -166,22 +192,26 @@ class System1Controller:
166
192
  return ErrorType.DEPENDENCY, None, None
167
193
 
168
194
  # Assertion failure
169
- if "assertionerror" in lower_err or "fail:" in lower_err:
195
+ if "assertionerror" in lower_err or "fail:" in lower_err or "assertion error" in lower_err:
170
196
  return ErrorType.ASSERTION, None, None
171
197
 
172
198
  # Timeout
173
199
  if "timed out" in lower_err or "timeout" in lower_err:
174
200
  return ErrorType.TIMEOUT, None, None
175
201
 
176
- # Runtime / general error (only if output actually contains error signals)
177
- error_signals = [
178
- "error:", "exception:", "traceback", "failed", "errno",
179
- "permission denied", "not found", "cannot", "invalid",
180
- "nameerror", "typeerror", "valueerror", "keyerror",
181
- "indexerror", "runtimeerror", "oserror", "ioerror",
182
- "filenotfounderror", "importerror", "attributeerror",
202
+ # Runtime / general error (detect actual execution crashes and tracebacks, not code words)
203
+ real_error_patterns = [
204
+ r"traceback \(most recent call last\):",
205
+ r"\b(nameerror|typeerror|valueerror|keyerror|indexerror|runtimeerror|oserror|ioerror|filenotfounderror|importerror|attributeerror|permissionerror|zerodivisionerror|recursionerror):\s",
206
+ r"⚠️\s*command failed",
207
+ r"^error:\s",
208
+ r"^fatal:\s",
209
+ r"command failed:",
210
+ r"\berrno\s+\d+",
211
+ r"permission denied",
212
+ r"no such file or directory",
183
213
  ]
184
- if any(sig in lower_err for sig in error_signals):
214
+ if any(re.search(pat, lower_err, re.MULTILINE) for pat in real_error_patterns):
185
215
  return ErrorType.UNKNOWN, None, None
186
216
 
187
217
  # Normal output (no error detected) → do NOT treat as error
@@ -269,7 +299,66 @@ class System1Controller:
269
299
  confidence=0.95,
270
300
  )
271
301
 
272
- err_type, target_file, target_line = self.triage_error(tool_result)
302
+ # Inspection loop detection: repeatedly reading/inspecting the same file
303
+ if tool_name == "read_file":
304
+ path = str(tool_args.get("path") or tool_args.get("file") or tool_args.get("filepath") or "").strip()
305
+ if path:
306
+ count = self._file_read_counts.get(path, 0) + 1
307
+ self._file_read_counts[path] = count
308
+ if count >= 2:
309
+ self.total_interventions += 1
310
+ directive = (
311
+ f"[SYSTEM 1 DIRECTIVE] Inspection loop detected: You have already read and inspected '{path}'. "
312
+ f"Stop re-reading '{path}'. Proceed immediately to implementing the next deliverable: "
313
+ "create any remaining files (e.g. test files) using write_file, or run test verification."
314
+ )
315
+ return JevDecision(
316
+ action=ActionCategory.RETRY_WITH_DIRECTIVE,
317
+ error_type=ErrorType.NONE,
318
+ directive=directive,
319
+ confidence=0.95,
320
+ )
321
+
322
+ # Inspection loop detection for bash (cat, head, tail, ls)
323
+ if tool_name == "bash":
324
+ cmd = str(tool_args.get("command", "")).strip().lower()
325
+ if any(cmd.startswith(v) or f" {v}" in cmd for v in ("cat ", "head ", "tail ", "less ")):
326
+ parts = cmd.split()
327
+ for p in parts[1:]:
328
+ clean_p = p.strip("'\"")
329
+ if clean_p.endswith((".py", ".js", ".ts", ".html", ".css", ".json", ".toml")):
330
+ count = self._file_read_counts.get(clean_p, 0) + 1
331
+ self._file_read_counts[clean_p] = count
332
+ if count >= 2:
333
+ self.total_interventions += 1
334
+ directive = (
335
+ f"[SYSTEM 1 DIRECTIVE] Inspection loop detected: You are inspecting '{clean_p}' via bash. "
336
+ f"'{clean_p}' already exists and has been reviewed. Stop re-reading it. "
337
+ "Proceed immediately to implement the NEXT required file or run test verification."
338
+ )
339
+ return JevDecision(
340
+ action=ActionCategory.RETRY_WITH_DIRECTIVE,
341
+ error_type=ErrorType.NONE,
342
+ directive=directive,
343
+ confidence=0.95,
344
+ )
345
+ if cmd.startswith("ls") or " ls " in cmd or "ls -la" in cmd:
346
+ self._ls_count += 1
347
+ if self._ls_count >= 2:
348
+ self.total_interventions += 1
349
+ directive = (
350
+ "[SYSTEM 1 DIRECTIVE] Repeated directory listing detected. "
351
+ "Stop inspecting directory contents. Proceed immediately to creating the next required file "
352
+ "using write_file, or run test verification."
353
+ )
354
+ return JevDecision(
355
+ action=ActionCategory.RETRY_WITH_DIRECTIVE,
356
+ error_type=ErrorType.NONE,
357
+ directive=directive,
358
+ confidence=0.95,
359
+ )
360
+
361
+ err_type, target_file, target_line = self.triage_error(tool_result, tool_name=tool_name)
273
362
 
274
363
  # 1. Loop detection (using Dream-RSI adaptive thresholds)
275
364
  if err_type != ErrorType.NONE and self.detect_loop(tool_name, tool_args, tool_result, err_type=err_type):
@@ -288,10 +377,22 @@ class System1Controller:
288
377
  target_line=target_line,
289
378
  directive=directive,
290
379
  )
380
+ elif tool_name == "bash":
381
+ cmd = str(tool_args.get("command", ""))
382
+ directive = (
383
+ f"[SYSTEM 1 DIRECTIVE] Repeated command failure loop detected for: '{cmd[:80]}'.\n"
384
+ "Do NOT re-run the same failing command. Fix the root cause in the source code using edit_file or adjust the command arguments."
385
+ )
386
+ return JevDecision(
387
+ action=ActionCategory.RETRY_WITH_DIRECTIVE,
388
+ confidence=0.95,
389
+ error_type=err_type,
390
+ directive=directive,
391
+ )
291
392
  else:
292
393
  directive = (
293
- f"[SYSTEM 1 INTERVENTION] Repeated failure loop detected for {tool_name}.\n"
294
- "Change strategy: inspect workspace state or error details before retrying."
394
+ f"[SYSTEM 1 DIRECTIVE] Repeated failure loop detected for {tool_name}.\n"
395
+ "Do NOT repeat the same failing call. Proceed with an alternative action or next deliverable."
295
396
  )
296
397
  return JevDecision(
297
398
  action=ActionCategory.RETRY_WITH_DIRECTIVE,