adelie-ai 0.3.1 → 0.3.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.
- package/adelie/__init__.py +1 -1
- package/adelie/agents/coder_ai.py +45 -0
- package/adelie/agents/expert_ai.py +3 -2
- package/adelie/config.py +1 -0
- package/adelie/llm_client.py +2 -1
- package/adelie/orchestrator.py +49 -1
- package/package.json +1 -1
package/adelie/__init__.py
CHANGED
|
@@ -44,6 +44,41 @@ Output a single valid JSON array of files to create/update."""
|
|
|
44
44
|
SYSTEM_PROMPT = load_prompt("coder", _FALLBACK_PROMPT)
|
|
45
45
|
|
|
46
46
|
|
|
47
|
+
def _get_framework_guidelines(framework: str) -> str:
|
|
48
|
+
"""Return framework-specific coding rules to inject into coder prompt."""
|
|
49
|
+
guidelines = {
|
|
50
|
+
"nextjs": (
|
|
51
|
+
"\n## ⚠️ Next.js App Router — MANDATORY Rules\n"
|
|
52
|
+
"- Any component using useState, useEffect, onClick, or other client hooks "
|
|
53
|
+
"MUST start with `'use client';` as the FIRST line.\n"
|
|
54
|
+
"- `layout.tsx` is a SERVER component — NEVER use client-side hooks, "
|
|
55
|
+
"context providers, or event handlers in it without `'use client'`.\n"
|
|
56
|
+
"- Dynamic routes use `[param]`, `[...catch]`, `[[...optional]]` syntax — "
|
|
57
|
+
"these are valid directory names.\n"
|
|
58
|
+
"- API routes go in `src/app/api/` as `route.ts` files.\n"
|
|
59
|
+
"- Use `next/navigation` (not `next/router`) for App Router.\n"
|
|
60
|
+
"- The package name for lucide icons is `lucide-react` (NOT `@lucide/react`).\n\n"
|
|
61
|
+
),
|
|
62
|
+
"nuxt": (
|
|
63
|
+
"\n## ⚠️ Nuxt 3 — MANDATORY Rules\n"
|
|
64
|
+
"- Use `<script setup lang='ts'>` for composition API.\n"
|
|
65
|
+
"- Auto-imports: `ref`, `computed`, `useRoute` etc. are auto-imported.\n"
|
|
66
|
+
"- Pages go in `pages/`, layouts in `layouts/`.\n\n"
|
|
67
|
+
),
|
|
68
|
+
"sveltekit": (
|
|
69
|
+
"\n## ⚠️ SvelteKit — MANDATORY Rules\n"
|
|
70
|
+
"- Pages go in `src/routes/` as `+page.svelte`.\n"
|
|
71
|
+
"- Server-only code uses `+page.server.ts` or `+server.ts`.\n"
|
|
72
|
+
"- Layouts use `+layout.svelte`.\n\n"
|
|
73
|
+
),
|
|
74
|
+
"vite": (
|
|
75
|
+
"\n## ⚠️ Vite + React — MANDATORY Rules\n"
|
|
76
|
+
"- Entry point is `index.html` referencing `src/main.tsx`.\n"
|
|
77
|
+
"- Use `vite.config.ts` with `@vitejs/plugin-react`.\n\n"
|
|
78
|
+
),
|
|
79
|
+
}
|
|
80
|
+
return guidelines.get(framework, "")
|
|
81
|
+
|
|
47
82
|
def _get_coder_log_dir(layer: int, coder_name: str) -> Path:
|
|
48
83
|
"""Get or create the log directory for a coder."""
|
|
49
84
|
log_dir = CODER_ROOT / "layer" / str(layer) / coder_name
|
|
@@ -174,6 +209,16 @@ def run_coder(
|
|
|
174
209
|
f"## Lower Layer Coder Logs\n{lower_logs}\n\n"
|
|
175
210
|
)
|
|
176
211
|
|
|
212
|
+
# Bug #7: Inject framework-specific coding rules to prevent common mistakes
|
|
213
|
+
try:
|
|
214
|
+
from adelie.agents.expert_ai import _detect_framework
|
|
215
|
+
fw = _detect_framework(workspace_root)
|
|
216
|
+
fw_guidelines = _get_framework_guidelines(fw)
|
|
217
|
+
if fw_guidelines:
|
|
218
|
+
user_prompt += fw_guidelines
|
|
219
|
+
except Exception:
|
|
220
|
+
pass
|
|
221
|
+
|
|
177
222
|
if feedback:
|
|
178
223
|
user_prompt += (
|
|
179
224
|
f"## ⚠️ REVIEWER FEEDBACK (FIX THESE ISSUES)\n{feedback}\n\n"
|
|
@@ -444,8 +444,9 @@ def _get_scaffolding_need() -> str:
|
|
|
444
444
|
return ""
|
|
445
445
|
|
|
446
446
|
lines = [
|
|
447
|
-
"
|
|
448
|
-
"
|
|
447
|
+
"ℹ️ SCAFFOLDING NOTE: The following entry files are missing.",
|
|
448
|
+
"If no scaffolding coder has been created yet, create ONE scaffolding task (layer 0).",
|
|
449
|
+
"If scaffolding was ALREADY attempted in a previous cycle, SKIP this and focus on feature tasks:",
|
|
449
450
|
]
|
|
450
451
|
for c in checks:
|
|
451
452
|
lines.append(f" - {c['file']}: {c['desc']}")
|
package/adelie/config.py
CHANGED
|
@@ -25,6 +25,7 @@ GEMINI_MODEL: str = os.getenv("GEMINI_MODEL", "gemini-2.0-flash")
|
|
|
25
25
|
OLLAMA_BASE_URL: str = os.getenv("OLLAMA_BASE_URL", "http://localhost:11434")
|
|
26
26
|
OLLAMA_MODEL: str = os.getenv("OLLAMA_MODEL", "llama3.2")
|
|
27
27
|
OLLAMA_API_KEY: str = os.getenv("OLLAMA_API_KEY", "") # For Ollama Cloud
|
|
28
|
+
LLM_TIMEOUT: int = int(os.getenv("LLM_TIMEOUT", "300")) # Request timeout in seconds
|
|
28
29
|
|
|
29
30
|
# ── Model Fallback ───────────────────────────────────────────────────────────
|
|
30
31
|
# Comma-separated fallback chain, e.g. "gemini:gemini-2.5-flash,gemini:gemini-2.0-flash,ollama:llama3.2"
|
package/adelie/llm_client.py
CHANGED
|
@@ -23,6 +23,7 @@ from adelie.config import (
|
|
|
23
23
|
GEMINI_API_KEY,
|
|
24
24
|
GEMINI_MODEL,
|
|
25
25
|
LLM_PROVIDER,
|
|
26
|
+
LLM_TIMEOUT,
|
|
26
27
|
OLLAMA_API_KEY,
|
|
27
28
|
OLLAMA_BASE_URL,
|
|
28
29
|
OLLAMA_MODEL,
|
|
@@ -362,7 +363,7 @@ def _generate_ollama_model(
|
|
|
362
363
|
headers["Authorization"] = f"Bearer {OLLAMA_API_KEY}"
|
|
363
364
|
|
|
364
365
|
try:
|
|
365
|
-
resp = requests.post(url, json=payload, headers=headers, timeout=
|
|
366
|
+
resp = requests.post(url, json=payload, headers=headers, timeout=LLM_TIMEOUT)
|
|
366
367
|
resp.raise_for_status()
|
|
367
368
|
except requests.ConnectionError:
|
|
368
369
|
raise ConnectionError(
|
package/adelie/orchestrator.py
CHANGED
|
@@ -112,6 +112,9 @@ class Orchestrator:
|
|
|
112
112
|
self._last_assembled_contexts: list | None = None
|
|
113
113
|
self._last_build_errors: list[dict] = []
|
|
114
114
|
|
|
115
|
+
# Track coder/reviewer results for next cycle's Expert AI context (Bug #4)
|
|
116
|
+
self._last_coder_result: dict | None = None
|
|
117
|
+
|
|
115
118
|
# Auto-scan: if KB is empty and project has existing code, scan first
|
|
116
119
|
self._auto_scan_done = False
|
|
117
120
|
|
|
@@ -181,6 +184,10 @@ class Orchestrator:
|
|
|
181
184
|
# Include recent build errors for Expert AI context
|
|
182
185
|
if self._last_build_errors:
|
|
183
186
|
state["build_errors"] = self._last_build_errors[:3] # 최대 3개
|
|
187
|
+
# Include last cycle's coder/reviewer result so Expert AI can avoid
|
|
188
|
+
# re-issuing tasks that previously failed (Bug #4)
|
|
189
|
+
if self._last_coder_result:
|
|
190
|
+
state["last_coder_result"] = self._last_coder_result
|
|
184
191
|
return state
|
|
185
192
|
|
|
186
193
|
def get_agent_context(self, agent_type: AgentType | str) -> dict:
|
|
@@ -976,7 +983,10 @@ class Orchestrator:
|
|
|
976
983
|
)] or all_written_files
|
|
977
984
|
|
|
978
985
|
for retry in range(MAX_REVIEW_RETRIES + 1):
|
|
979
|
-
|
|
986
|
+
# Bug #6: pass STAGING_ROOT so reviewer reads files from
|
|
987
|
+
# staging (where Coder wrote them), not PROJECT_ROOT.
|
|
988
|
+
from adelie.agents.coder_ai import STAGING_ROOT as _REVIEW_STAGING
|
|
989
|
+
review = run_review(coder_name=name, written_files=task_files, workspace_root=_REVIEW_STAGING)
|
|
980
990
|
score = review.get("overall_score", 5)
|
|
981
991
|
self._review_score_history.append(score)
|
|
982
992
|
|
|
@@ -1005,6 +1015,44 @@ class Orchestrator:
|
|
|
1005
1015
|
self._emit_agent_end("Reviewer", f"error: {e}")
|
|
1006
1016
|
else:
|
|
1007
1017
|
self._emit_agent_end("Reviewer", "approved" if reviewer_approved else "rejected")
|
|
1018
|
+
|
|
1019
|
+
# Bug #3: Log review failures to KB so Expert AI avoids re-issuing
|
|
1020
|
+
# the exact same task that was rejected.
|
|
1021
|
+
if not reviewer_approved and all_written_files:
|
|
1022
|
+
failed_files = [f.get("filepath", "") for f in all_written_files]
|
|
1023
|
+
review_summary = review.get("summary", "N/A") if review else "N/A"
|
|
1024
|
+
review_issues = ""
|
|
1025
|
+
if review:
|
|
1026
|
+
for issue in review.get("issues", [])[:5]:
|
|
1027
|
+
review_issues += f"- [{issue.get('severity')}] {issue.get('title')}: {issue.get('suggestion', '')}\n"
|
|
1028
|
+
failure_note = (
|
|
1029
|
+
f"# Review Failure Log (Cycle #{self.loop_iteration})\n\n"
|
|
1030
|
+
f"The following files were rejected by Reviewer AI after "
|
|
1031
|
+
f"{MAX_REVIEW_RETRIES + 1} attempts:\n"
|
|
1032
|
+
+ "\n".join(f"- `{f}`" for f in failed_files) + "\n\n"
|
|
1033
|
+
f"**Review Summary**: {review_summary}\n\n"
|
|
1034
|
+
f"**Issues**:\n{review_issues}\n"
|
|
1035
|
+
f"Expert AI should NOT re-assign the same task until "
|
|
1036
|
+
f"the underlying issue is resolved.\n"
|
|
1037
|
+
)
|
|
1038
|
+
failure_path = WORKSPACE_PATH / "errors" / f"review_failure_{self.loop_iteration}.md"
|
|
1039
|
+
failure_path.parent.mkdir(parents=True, exist_ok=True)
|
|
1040
|
+
failure_path.write_text(failure_note, encoding="utf-8")
|
|
1041
|
+
retriever.update_index(
|
|
1042
|
+
f"errors/{failure_path.name}",
|
|
1043
|
+
tags=["error", "review-failure"],
|
|
1044
|
+
summary=f"Review rejected cycle #{self.loop_iteration}: {', '.join(failed_files[:3])}",
|
|
1045
|
+
)
|
|
1046
|
+
console.print(f"[yellow]📝 Review failure logged to KB for Expert AI awareness[/yellow]")
|
|
1047
|
+
|
|
1048
|
+
# Bug #4: Record coder/reviewer results for next cycle context
|
|
1049
|
+
self._last_coder_result = {
|
|
1050
|
+
"cycle": self.loop_iteration,
|
|
1051
|
+
"files_written": len(all_written_files),
|
|
1052
|
+
"reviewer_approved": reviewer_approved,
|
|
1053
|
+
"review_score": review.get("overall_score", 0) if review else 0,
|
|
1054
|
+
"review_summary": review.get("summary", "")[:300] if review else "",
|
|
1055
|
+
}
|
|
1008
1056
|
elif all_written_files and self.phase != "initial":
|
|
1009
1057
|
# Reviewer not scheduled this cycle — auto-approve staged files
|
|
1010
1058
|
reviewer_approved = True
|