adelie-ai 0.2.2 → 0.2.3

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.
@@ -18,6 +18,7 @@ from __future__ import annotations
18
18
  import json
19
19
  import re
20
20
  import shlex
21
+ import shutil
21
22
  import subprocess
22
23
  import sys
23
24
  from datetime import datetime
@@ -63,6 +64,29 @@ EXEC_TIMEOUT_RUN = 10 # Short timeout — we just check if it starts
63
64
  EXEC_TIMEOUT_DEPLOY = 180
64
65
 
65
66
 
67
+ def _detect_available_tools() -> str:
68
+ """Check which CLI tools are actually installed on this system."""
69
+ tools_to_check = [
70
+ "npm", "npx", "node", "yarn", "pnpm",
71
+ "pip", "pip3", "python", "python3",
72
+ "docker", "docker-compose", "podman",
73
+ "cargo", "go", "make",
74
+ "vite", "next",
75
+ ]
76
+ available = []
77
+ unavailable = []
78
+ for tool in tools_to_check:
79
+ if shutil.which(tool):
80
+ available.append(tool)
81
+ else:
82
+ unavailable.append(tool)
83
+
84
+ lines = [f"Available: {', '.join(available)}"]
85
+ if unavailable:
86
+ lines.append(f"NOT INSTALLED (do NOT use): {', '.join(unavailable)}")
87
+ return "\n".join(lines)
88
+
89
+
66
90
  def _diagnose_build_error(stderr: str, stdout: str = "") -> list[dict]:
67
91
  """
68
92
  Parse build error output to extract actionable file/line/error information.
@@ -397,7 +421,13 @@ def run_pipeline(
397
421
  except Exception:
398
422
  pass
399
423
 
424
+ # Detect available tools on this system
425
+ tools_info = _detect_available_tools()
426
+
400
427
  user_prompt = (
428
+ f"## System Tools\n{tools_info}\n\n"
429
+ f"CRITICAL: Only generate commands using tools listed as 'Available' above.\n"
430
+ f"Do NOT generate commands for tools listed as 'NOT INSTALLED'.\n\n"
401
431
  f"## Project Files\n\n"
402
432
  + "\n\n".join(file_list[:20])
403
433
  + f"\n\n## Max Tier: {max_tier}\n"
@@ -61,7 +61,6 @@ class Orchestrator:
61
61
  self.goal = goal or "Autonomously develop and improve the project"
62
62
  self.phase = phase
63
63
  self.state = LoopState.NORMAL
64
- self.loop_iteration = 0
65
64
  self.last_expert_output: dict | None = None
66
65
  self.last_error: str | None = None
67
66
  self._running = True
@@ -71,6 +70,12 @@ class Orchestrator:
71
70
  self._phase_recommendation: str | None = None # Recommended next phase
72
71
  self._pause_requested = False # Controlled by interactive CLI
73
72
 
73
+ # Restore persisted state from config.json
74
+ self.loop_iteration = 0
75
+ self._test_pass_history: list[bool] = []
76
+ self._review_score_history: list[int] = []
77
+ self._restore_state()
78
+
74
79
  # Loop detection
75
80
  self._loop_detector = LoopDetector()
76
81
 
@@ -89,9 +94,7 @@ class Orchestrator:
89
94
  # Process supervisor for spawned commands
90
95
  self.supervisor = ProcessSupervisor(max_concurrent=5)
91
96
 
92
- # Quality tracking for phase gates
93
- self._test_pass_history: list[bool] = [] # last N test results
94
- self._review_score_history: list[int] = [] # last N review scores
97
+
95
98
 
96
99
  # Graceful shutdown on Ctrl+C or SIGTERM
97
100
  signal.signal(signal.SIGINT, self._handle_signal)
@@ -302,16 +305,38 @@ class Orchestrator:
302
305
  return rule["next"].value
303
306
  return None
304
307
 
305
- def _save_phase(self) -> None:
306
- """Persist current phase to .adelie/config.json."""
308
+ def _save_state(self) -> None:
309
+ """Persist current phase, loop_iteration, and quality history to .adelie/config.json."""
307
310
  config_path = WORKSPACE_PATH.parent / "config.json"
308
311
  if config_path.exists():
309
312
  cfg_data = json.loads(config_path.read_text(encoding="utf-8"))
310
313
  else:
311
314
  cfg_data = {}
312
315
  cfg_data["phase"] = self.phase
316
+ cfg_data["loop_iteration"] = self.loop_iteration
317
+ cfg_data["test_pass_history"] = self._test_pass_history[-10:]
318
+ cfg_data["review_score_history"] = self._review_score_history[-10:]
313
319
  config_path.write_text(json.dumps(cfg_data, indent=2, ensure_ascii=False), encoding="utf-8")
314
320
 
321
+ def _save_phase(self) -> None:
322
+ """Persist current phase (alias for _save_state for backward compat)."""
323
+ self._save_state()
324
+
325
+ def _restore_state(self) -> None:
326
+ """Restore loop_iteration and quality history from config.json."""
327
+ config_path = WORKSPACE_PATH.parent / "config.json"
328
+ if not config_path.exists():
329
+ return
330
+ try:
331
+ cfg_data = json.loads(config_path.read_text(encoding="utf-8"))
332
+ self.loop_iteration = cfg_data.get("loop_iteration", 0)
333
+ self._test_pass_history = cfg_data.get("test_pass_history", [])
334
+ self._review_score_history = cfg_data.get("review_score_history", [])
335
+ if self.loop_iteration > 0:
336
+ console.print(f"[dim] ↻ Resumed from loop #{self.loop_iteration}[/dim]")
337
+ except Exception:
338
+ pass
339
+
315
340
  def _write_error_to_kb(self, error: Exception | str) -> None:
316
341
  """Directly write an error file to the KB errors/ folder."""
317
342
  ts = datetime.now().strftime("%Y%m%d_%H%M%S")
@@ -1378,6 +1403,9 @@ class Orchestrator:
1378
1403
  "state": self.state.value,
1379
1404
  })
1380
1405
 
1406
+ # Persist state for resume after Ctrl+C
1407
+ self._save_state()
1408
+
1381
1409
  def pause(self) -> None:
1382
1410
  """Request the orchestrator to pause before the next cycle."""
1383
1411
  self._pause_requested = True
@@ -21,6 +21,7 @@ from adelie.config import (
21
21
  GEMINI_API_KEY,
22
22
  GEMINI_MODEL,
23
23
  LLM_PROVIDER,
24
+ OLLAMA_API_KEY,
24
25
  OLLAMA_BASE_URL,
25
26
  OLLAMA_MODEL,
26
27
  )
@@ -201,8 +202,13 @@ Provide a detailed, factual answer. Note that you don't have access to
201
202
  real-time information, so state clearly when information might be outdated."""
202
203
 
203
204
  try:
205
+ headers = {"Content-Type": "application/json"}
206
+ if OLLAMA_API_KEY:
207
+ headers["Authorization"] = f"Bearer {OLLAMA_API_KEY}"
208
+
204
209
  resp = requests.post(
205
210
  url,
211
+ headers=headers,
206
212
  json={
207
213
  "model": use_model,
208
214
  "messages": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "adelie-ai",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
4
4
  "description": "Adelie — Self-Communicating Autonomous AI Loop CLI",
5
5
  "bin": {
6
6
  "adelie": "bin/adelie.js"