feat(ralph): add autonomous project execution system

- router.py: add !approve, !status, !stop, !propose commands for project lifecycle management
- approved-tasks.json: coordination schema for evening→night→morning pipeline
- tools/ralph/: ralph.sh loop, prompt.md, prd-template.json
- cron/jobs.json: enable morning-report, evening-report, night-execute (23:00 opus)

Evening-report proposes features to approved-tasks.json as 'pending'; Marius
approves via !approve; night-execute launches ralph.sh per project.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-26 15:20:52 +00:00
parent bee409d164
commit 90c2a90b5e
6 changed files with 665 additions and 8 deletions

4
approved-tasks.json Normal file
View File

@@ -0,0 +1,4 @@
{
"projects": [],
"last_updated": null
}

View File

@@ -179,12 +179,12 @@
},
{
"name": "morning-report",
"cron": "30 9 * * *",
"cron": "30 8 * * *",
"channel": "echo-work",
"model": "sonnet",
"prompt": "RAPORT DIMINEAȚĂ - trimite pe EMAIL (Gmail: mmarius28@gmail.com)\n\n## CALENDAR\nVerifică calendarul:\n```bash\ncd ~/echo-core && source venv/bin/activate && python3 tools/calendar_check.py today\npython3 tools/calendar_check.py travel\npython3 tools/calendar_check.py week\n```\n\n## CITEȘTE CONTEXT\n- USER.md pentru programul lui Marius (luni-joi 15-16 liber)\n- memory/kb/insights/ pentru propuneri (ultimele 3 zile)\n- memory/approved-tasks.md pentru status proiecte/features\n\n## FORMAT EMAIL HTML\n- Font: 16px text, 18px titluri\n- Culori: albastru (#dbeafe) DONE, gri (#f3f4f6) PROGRAMAT, verde (#d1fae5) PROJECTS\n- Link-uri vizibile\n\n## STRUCTURA RAPORT\n\n### 1. CALENDAR\n- 📅 **AZI:** [evenimente]\n- 📅 **MÂINE:** [evenimente]\n- 📅 **PESTE 2 ZILE:** [dacă e GRUP, NLP, meeting mare]\n- 🚂 **TRAVEL:** Reminders bilete+cazare\n\n### 2. PROIECTE/FEATURES NOAPTEA 💻\n\nCitesc approved-tasks.md și raportez ce s-a realizat:\n\n**Format pentru fiecare proiect/feature [x]:**\n\n```html\n<div style=\"background: #d1fae5; padding: 15px; margin: 10px 0; border-radius: 8px;\">\n <h3>✅ P1 - Nume Proiect</h3>\n \n <p><strong>Status:</strong> X/Y stories complete</p>\n \n <p><strong>Stories realizate:</strong></p>\n <ul>\n <li>✅ US-001: Titlu story - implementat cu succes</li>\n <li>✅ US-002: Titlu story - quality checks pass</li>\n <li>🔄 US-003: Titlu story - în progres (blocat pe dependency)</li>\n </ul>\n \n <p><strong>Link:</strong> <a href=\"https://gitea.romfast.ro/romfast/PROJECT-NAME\">gitea.romfast.ro/romfast/PROJECT-NAME</a></p>\n \n <p><strong>Learnings:</strong> [din progress.txt - ce patterns am descoperit]</p>\n \n <p><strong>Next steps:</strong> [ce rămâne de făcut]</p>\n</div>\n```\n\n**Dacă NU s-au executat proiecte/features:**\n- Sari peste această secțiune\n\n### 3. STATUS GENERAL\n- Ce s-a făcut ieri (joburi, taskuri)\n- Git status ~/clawd\n- Joburi executate (YouTube, insights, etc.)\n\n### 4. PROPUNERI CU ZI ȘI ORĂ!\n\n**OBLIGATORIU:** Fiecare propunere TU+EU sau FAC TU trebuie să aibă ZI și ORĂ concrete!\n\nCategorii:\n- 🤖 **FAC EU** (0 efort) - execut singur\n- 🤝 **TU+EU** (eu pregătesc) - cu zi/oră!\n- 👤 **FAC TU** (template gata) - cu zi/oră!\n\nExemplu:\n- **A1 - Sesiune Dizolvare Vină** 🤝 TU+EU\n 📅 **Marți 3 feb, 15:00-15:30**\n Context + link sursă\n\nReguli programare:\n- Luni-Joi 15:00-16:00 = slot liber\n- Vineri-Duminică = NLP, evită\n- Verifică calendar să nu fie ocupat\n\n### 5. INSIGHTS DISPONIBILE\n\nListează insights-uri [ ] nepropuse încă (format scurt).\n\n### 6. CUM RĂSPUNZI\n- DA = aprob toate (cu zilele/orele propuse)\n- 1 pentru A1,A2 = execut ACUM\n- 2 pentru A3 = programez noapte\n- 3 pentru A5 = skip\n- Alt orar = \"A1 miercuri nu marți\"\n\n## TRIMITERE\npython3 /home/moltbot/echo-core/tools/email_send.py \"mmarius28@gmail.com\" \"Raport Dimineata DATA\" \"HTML_CONTENT\"\n\nNU trimite pe Discord - doar email.",
"enabled": true,
"prompt": "RAPORT DIMINEAȚĂ - trimite pe EMAIL (Gmail: mmarius28@gmail.com)\n\n## CALENDAR\nVerifică calendarul:\n```bash\ncd ~/echo-core && source venv/bin/activate && python3 tools/calendar_check.py today\npython3 tools/calendar_check.py travel\npython3 tools/calendar_check.py week\n```\n\n## CITEȘTE CONTEXT\n- USER.md pentru programul lui Marius (luni-joi 15-16 liber)\n- memory/kb/insights/ pentru propuneri (ultimele 3 zile)\n- /home/moltbot/echo-core/approved-tasks.json pentru status proiecte/features (câmpurile: name, status, started_at, pid)\n\n## FORMAT EMAIL HTML\n- Font: 16px text, 18px titluri\n- Culori: albastru (#dbeafe) DONE, gri (#f3f4f6) PROGRAMAT, verde (#d1fae5) PROJECTS\n- Link-uri vizibile\n\n## STRUCTURA RAPORT\n\n### 1. CALENDAR\n- 📅 **AZI:** [evenimente]\n- 📅 **MÂINE:** [evenimente]\n- 📅 **PESTE 2 ZILE:** [dacă e GRUP, NLP, meeting mare]\n- 🚂 **TRAVEL:** Reminders bilete+cazare\n\n### 2. PROIECTE/FEATURES NOAPTEA 💻\n\nCitesc /home/moltbot/echo-core/approved-tasks.json și raportez ce s-a realizat:\n(statusuri: pending, approved, running, complete, failed, stopped)\nPentru stories done/total: citesc /home/moltbot/workspace/{name}/scripts/ralph/prd.json\n\n**Format pentru fiecare proiect/feature [x]:**\n\n```html\n<div style=\"background: #d1fae5; padding: 15px; margin: 10px 0; border-radius: 8px;\">\n <h3>✅ P1 - Nume Proiect</h3>\n \n <p><strong>Status:</strong> X/Y stories complete</p>\n \n <p><strong>Stories realizate:</strong></p>\n <ul>\n <li>✅ US-001: Titlu story - implementat cu succes</li>\n <li>✅ US-002: Titlu story - quality checks pass</li>\n <li>🔄 US-003: Titlu story - în progres (blocat pe dependency)</li>\n </ul>\n \n <p><strong>Link:</strong> <a href=\"https://gitea.romfast.ro/romfast/PROJECT-NAME\">gitea.romfast.ro/romfast/PROJECT-NAME</a></p>\n \n <p><strong>Learnings:</strong> [din progress.txt - ce patterns am descoperit]</p>\n \n <p><strong>Next steps:</strong> [ce rămâne de făcut]</p>\n</div>\n```\n\n**Dacă NU s-au executat proiecte/features:**\n- Sari peste această secțiune\n\n### 3. STATUS GENERAL\n- Ce s-a făcut ieri (joburi, taskuri)\n- Git status ~/clawd\n- Joburi executate (YouTube, insights, etc.)\n\n### 4. PROPUNERI CU ZI ȘI ORĂ!\n\n**OBLIGATORIU:** Fiecare propunere TU+EU sau FAC TU trebuie să aibă ZI și ORĂ concrete!\n\nCategorii:\n- 🤖 **FAC EU** (0 efort) - execut singur\n- 🤝 **TU+EU** (eu pregătesc) - cu zi/oră!\n- 👤 **FAC TU** (template gata) - cu zi/oră!\n\nExemplu:\n- **A1 - Sesiune Dizolvare Vină** 🤝 TU+EU\n 📅 **Marți 3 feb, 15:00-15:30**\n Context + link sursă\n\nReguli programare:\n- Luni-Joi 15:00-16:00 = slot liber\n- Vineri-Duminică = NLP, evită\n- Verifică calendar să nu fie ocupat\n\n### 5. INSIGHTS DISPONIBILE\n\nListează insights-uri [ ] nepropuse încă (format scurt).\n\n### 6. CUM RĂSPUNZI\n- DA = aprob toate (cu zilele/orele propuse)\n- 1 pentru A1,A2 = execut ACUM\n- 2 pentru A3 = programez noapte\n- 3 pentru A5 = skip\n- Alt orar = \"A1 miercuri nu marți\"\n\n## TRIMITERE\npython3 /home/moltbot/echo-core/tools/email_send.py \"mmarius28@gmail.com\" \"Raport Dimineata DATA\" \"HTML_CONTENT\"\n\nNU trimite pe Discord - doar email.",
"allowed_tools": [],
"enabled": false,
"last_run": null,
"last_status": null,
"next_run": null
@@ -194,9 +194,9 @@
"cron": "0 21 * * *",
"channel": "echo-work",
"model": "sonnet",
"prompt": "RAPORT SEARĂ - trimite pe EMAIL (Gmail: mmarius28@gmail.com)\n\n## CALENDAR\nVerifică ce ai mâine și săptămâna:\n```bash\ncd ~/echo-core && source venv/bin/activate && python3 tools/calendar_check.py today\npython3 tools/calendar_check.py week\n```\n\n## CITEȘTE CONTEXT\n- USER.md pentru programul lui Marius (luni-joi 15-16 liber, vineri-dum NLP)\n- memory/kb/insights/YYYY-MM-DD.md pentru propuneri insights\n- memory/kb/youtube/ și memory/kb/articole/ pentru inspirație proiecte\n- memory/approved-tasks.md pentru status proiecte existente\n\n## FORMAT EMAIL HTML\n- Font: 16px text, 18px titluri\n- Culori: albastru (#dbeafe) DONE, gri (#f3f4f6) PROGRAMAT, verde (#d1fae5) PROJECTS\n- Link-uri vizibile\n\n## STRUCTURA RAPORT\n\n### 1. MÂINE\n- 📅 Evenimente calendar\n- 🚂 Travel reminders\n\n### 2. STATUS\n- Ce s-a făcut azi\n- Git status\n\n### 3. PROPUNERI CU ZI ȘI ORĂ!\n\n**OBLIGATORIU:** Fiecare propunere TU+EU sau FAC TU trebuie să aibă ZI și ORĂ concrete!\n\nReguli programare:\n- Luni-Joi 15:00-16:00 = slot liber\n- Vineri-Duminică = NLP, evită\n- Verifică calendar să nu fie ocupat\n- Sesiuni scurte: 15-30 min\n\n### 4. PROGRAME/PROIECTE PRACTICE 💻\n\n**CONTEXT OBLIGATORIU - citește înainte de a propune:**\n\n**Proiecte existente (PRIORITARE pentru features):**\n- **roa2web** (gitea.romfast.ro/romfast/roa2web) - FastAPI+Vue.js+Telegram bot\n - Are deja: balanță, facturi, trezorerie\n - Lipsesc: validări declarații ANAF, facturare valută/taxare inversă, notificări\n - Rapoarte ROA noi → FEATURE în roa2web, NU proiect separat!\n- **Chatbot Maria** (Flowise pe LXC 104, ngrok → romfast.ro/chatbot_maria.html)\n - Document store: XML, MD | Groq gratuit + Ollama embeddings + FAISS\n - Problema: răspunsuri nu sunt suficient de bune\n - Angajatul nou poate menține documentația (scrie TXT, trebuie converter)\n - Clientii îl accesează din programele ROA direct\n\n**Întrebări frecvente clienți (surse de proiecte):**\n- Erori validare declarații ANAF (D406, D394, D100 etc.)\n- Cum facturez în valută cu taxare inversă?\n- Probleme la instalări, inițializări firme noi, configurări\n\n**Reguli propuneri (80/20 STRICT):**\n- Impact mare pentru Marius → apoi pentru clienți ERP ROA\n- Inspirat din discovery (YouTube, articole, insights procesate)\n- Features roa2web > proiecte noi (integrare în existent)\n- Proiecte independente doar dacă NU se potrivesc în roa2web/Flowise\n\n**A. FEATURES PROIECTE EXISTENTE (2-3, PRIORITAR):**\n\nFormat:\n```\n### ⚡ F1 - Feature pentru [roa2web/chatbot]\n**Ce face:** Descriere scurtă\n**De ce:** Ce problemă rezolvă (ex: \"clienții întreabă X de 5 ori/săptămână\")\n**Complexitate:** S/M/L\n**Proiect:** roa2web / chatbot-maria\n```\n\n**B. PROIECTE NOI (max 1, doar dacă nu se integrează în existente):**\n\nFormat:\n```\n### 💻 P1 - Nume Proiect\n**De ce:** Cum se leagă de nevoile lui Marius/clienți\n**Impact:** Pentru Marius + pentru clienți\n**Efort:** Ore/zile realist\n**Stack:** Simplu (80/20)\n**Sursă:** [Link nota KB]\n```\n\n**NU propune:**\n- Proiecte complexe fără beneficiu clar\n- Proiecte duplicat cu ce există deja\n- Rapoarte ROA ca proiect separat (→ feature roa2web)\n\n### 5. INSIGHTS DISPONIBILE\nListează insights-uri [ ] nepropuse încă (format scurt).\n\n### 6. CUM RĂSPUNZI\n- DA = aprob toate (cu zilele/orele propuse)\n- 1 pentru A1,A2 = execut ACUM\n- 2 pentru A3 = programez noapte\n- 3 pentru A5 = skip\n- **F pentru F1,F3** = implementează features (joburi noapte)\n- **P pentru P1** = creează proiect nou (job noapte)\n- Alt orar = \"A1 miercuri nu marți\"\n\n## IMPLEMENTARE PROIECTE APROBATE\n\nCând Marius aprobă cu F sau P:\n1. Adaugă în memory/approved-tasks.md secțiunea \"Noaptea asta\"\n2. Night-execute (23:00) va:\n - Pentru proiecte noi: genera PRD cu ralph_prd_generator.py → ralph.sh\n - Pentru features roa2web: adaugă stories în prd.json existent → ralph.sh\n - Pentru chatbot: generează documentație nouă și uploadează în Flowise\n\n## TRIMITERE\npython3 /home/moltbot/echo-core/tools/email_send.py \"mmarius28@gmail.com\" \"Raport Seara DATA\" \"HTML_CONTENT\"\n\nNU trimite pe Discord - doar email.",
"enabled": true,
"prompt": "RAPORT SEARĂ - trimite pe EMAIL (Gmail: mmarius28@gmail.com)\n\n## CALENDAR\nVerifică ce ai mâine și săptămâna:\n```bash\ncd ~/echo-core && source venv/bin/activate && python3 tools/calendar_check.py today\npython3 tools/calendar_check.py week\n```\n\n## CITEȘTE CONTEXT\n- USER.md pentru programul lui Marius (luni-joi 15-16 liber, vineri-dum NLP)\n- memory/kb/insights/YYYY-MM-DD.md pentru propuneri insights\n- memory/kb/youtube/ și memory/kb/articole/ pentru inspirație proiecte\n- /home/moltbot/echo-core/approved-tasks.json pentru status proiecte existente (câmpurile: name, status, proposed_at)\n\n## FORMAT EMAIL HTML\n- Font: 16px text, 18px titluri\n- Culori: albastru (#dbeafe) DONE, gri (#f3f4f6) PROGRAMAT, verde (#d1fae5) PROJECTS\n- Link-uri vizibile\n\n## STRUCTURA RAPORT\n\n### 1. MÂINE\n- 📅 Evenimente calendar\n- 🚂 Travel reminders\n\n### 2. STATUS\n- Ce s-a făcut azi\n- Git status\n\n### 3. PROPUNERI CU ZI ȘI ORĂ!\n\n**OBLIGATORIU:** Fiecare propunere TU+EU sau FAC TU trebuie să aibă ZI și ORĂ concrete!\n\nReguli programare:\n- Luni-Joi 15:00-16:00 = slot liber\n- Vineri-Duminică = NLP, evită\n- Verifică calendar să nu fie ocupat\n- Sesiuni scurte: 15-30 min\n\n### 4. PROGRAME/PROIECTE PRACTICE 💻\n\n**CONTEXT OBLIGATORIU - citește înainte de a propune:**\n\n**Proiecte existente (PRIORITARE pentru features):**\n- **roa2web** (gitea.romfast.ro/romfast/roa2web) - FastAPI+Vue.js+Telegram bot\n - Are deja: balanță, facturi, trezorerie\n - Lipsesc: validări declarații ANAF, facturare valută/taxare inversă, notificări\n - Rapoarte ROA noi → FEATURE în roa2web, NU proiect separat!\n- **Chatbot Maria** (Flowise pe LXC 104, ngrok → romfast.ro/chatbot_maria.html)\n - Document store: XML, MD | Groq gratuit + Ollama embeddings + FAISS\n - Problema: răspunsuri nu sunt suficient de bune\n - Angajatul nou poate menține documentația (scrie TXT, trebuie converter)\n - Clientii îl accesează din programele ROA direct\n\n**Întrebări frecvente clienți (surse de proiecte):**\n- Erori validare declarații ANAF (D406, D394, D100 etc.)\n- Cum facturez în valută cu taxare inversă?\n- Probleme la instalări, inițializări firme noi, configurări\n\n**Reguli propuneri (80/20 STRICT):**\n- Impact mare pentru Marius → apoi pentru clienți ERP ROA\n- Inspirat din discovery (YouTube, articole, insights procesate)\n- Features roa2web > proiecte noi (integrare în existent)\n- Proiecte independente doar dacă NU se potrivesc în roa2web/Flowise\n\n**A. FEATURES PROIECTE EXISTENTE (2-3, PRIORITAR):**\n\nFormat:\n```\n### ⚡ F1 - Feature pentru [roa2web/chatbot]\n**Ce face:** Descriere scurtă\n**De ce:** Ce problemă rezolvă (ex: \"clienții întreabă X de 5 ori/săptămână\")\n**Complexitate:** S/M/L\n**Proiect:** roa2web / chatbot-maria\n```\n\n**B. PROIECTE NOI (max 1, doar dacă nu se integrează în existente):**\n\nFormat:\n```\n### 💻 P1 - Nume Proiect\n**De ce:** Cum se leagă de nevoile lui Marius/clienți\n**Impact:** Pentru Marius + pentru clienți\n**Efort:** Ore/zile realist\n**Stack:** Simplu (80/20)\n**Sursă:** [Link nota KB]\n```\n\n**NU propune:**\n- Proiecte complexe fără beneficiu clar\n- Proiecte duplicat cu ce există deja\n- Rapoarte ROA ca proiect separat (→ feature roa2web)\n\n### 5. INSIGHTS DISPONIBILE\nListează insights-uri [ ] nepropuse încă (format scurt).\n\n### 6. CUM RĂSPUNZI\n- DA = aprob toate (cu zilele/orele propuse)\n- 1 pentru A1,A2 = execut ACUM\n- 2 pentru A3 = programez noapte\n- 3 pentru A5 = skip\n- **F pentru F1,F3** = implementează features (joburi noapte)\n- **P pentru P1** = creează proiect nou (job noapte)\n- Alt orar = \"A1 miercuri nu marți\"\n\n## IMPLEMENTARE PROIECTE APROBATE\n\nCând propui features (F) sau proiecte (P), adaugă-le automat în /home/moltbot/echo-core/approved-tasks.json cu status 'pending':\n```bash\npython3 -c \"\nimport json, datetime\nf = open('/home/moltbot/echo-core/approved-tasks.json')\ndata = json.load(f); f.close()\ndata['projects'].append({'name': 'SLUG-PROIECT', 'description': 'DESCRIERE', 'status': 'pending', 'proposed_at': datetime.datetime.utcnow().isoformat(), 'approved_at': None, 'started_at': None, 'pid': None})\ndata['last_updated'] = datetime.datetime.utcnow().isoformat()\nopen('/home/moltbot/echo-core/approved-tasks.json', 'w').write(json.dumps(data, indent=2))\n\"\n```\n\nÎn email, arată lui Marius comanda de aprobare:\n`!approve SLUG-PROIECT` (trimite pe Discord/Telegram la Echo)\n\nNight-execute (23:00) va:\n - genera PRD cu ralph_prd_generator.py dacă nu există prd.json\n - lansa ralph.sh 15 iterații pentru fiecare proiect aprobat\n\n## TRIMITERE\npython3 /home/moltbot/echo-core/tools/email_send.py \"mmarius28@gmail.com\" \"Raport Seara DATA\" \"HTML_CONTENT\"\n\nNU trimite pe Discord - doar email.",
"allowed_tools": [],
"enabled": false,
"last_run": null,
"last_status": null,
"next_run": null
@@ -269,8 +269,24 @@
"prompt": "Heartbeat check. Rulează src/heartbeat.py printr-un scurt raport de status.\nDacă nu e nimic de raportat (email=0, calendar nu are evenimente <2h, kb ok), răspunde doar cu HEARTBEAT_OK și oprește-te — nu trimite mesaj.\nDacă e ceva: raport scurt pe Discord #echo-work.",
"allowed_tools": [],
"enabled": true,
"last_run": "2026-04-26T08:00:00.003149+00:00",
"last_run": "2026-04-26T14:00:00.003075+00:00",
"last_status": "ok",
"next_run": "2026-04-26T10:00:00+00:00"
"next_run": "2026-04-26T16:00:00+00:00"
},
{
"name": "night-execute",
"cron": "0 23 * * *",
"channel": "echo-work",
"model": "opus",
"enabled": true,
"prompt": "NIGHT-EXECUTE - Implementare autonoma proiecte aprobate\n\n## PASUL 1: Citeste proiectele aprobate\n\nCiteste /home/moltbot/echo-core/approved-tasks.json\nSelecteaza proiectele cu status='approved'\nDaca nu sunt proiecte aprobate: raporteaza pe Discord si opreste-te.\n\n## PASUL 2: Pentru fiecare proiect aprobat\n\n1. Verifica daca workspace-ul exista: /home/moltbot/workspace/{name}\n - Daca nu: git clone https://gitea.romfast.ro/romfast/{name}.git /home/moltbot/workspace/{name}\n\n2. Verifica daca prd.json exista: /home/moltbot/workspace/{name}/scripts/ralph/prd.json\n - Daca nu: ruleaza generatorul PRD:\n source .venv/bin/activate\n python3 tools/ralph_prd_generator.py \"{name}\" \"{description}\" /home/moltbot/workspace\n\n3. Lanseaza Ralph loop:\n cd /home/moltbot/workspace/{name}\n chmod +x scripts/ralph/ralph.sh\n mkdir -p scripts/ralph/logs\n nohup ./scripts/ralph/ralph.sh 15 > scripts/ralph/logs/ralph-$(date +%Y%m%d).log 2>&1 &\n echo $! > scripts/ralph/.ralph.pid\n\n4. Actualizeaza approved-tasks.json:\n - status: 'running'\n - started_at: timestamp curent\n - pid: PID din .ralph.pid\n\n## PASUL 3: Raport Discord\n\nTrimite pe echo-work:\n- Cate proiecte au pornit\n- PID-urile lor\n- 'morning-report va raporta progresul la 08:30'\n\n## REGULI IMPORTANTE\n\n- Nu modifica niciodata src/router.py, src/claude_session.py sau alte fisiere core echo-core prin Ralph\n- echo-core self-improvement NUMAI pe branch ralph/echo-improve, nu pe master\n- Daca ralph.sh esueaza: log in approved-tasks.json (status: failed, error: mesaj)\n- Delay 5 secunde intre proiecte pentru a evita rate limiting\n",
"allowed_tools": [
"Bash",
"Read",
"Write"
],
"last_run": null,
"last_status": null,
"next_run": null
}
]

View File

@@ -1,6 +1,11 @@
"""Echo Core message router — routes messages to Claude or commands."""
import json
import logging
import os
import signal
from datetime import datetime, timezone
from pathlib import Path
from typing import Callable
from src.config import Config
@@ -16,6 +21,8 @@ from src.claude_session import (
log = logging.getLogger(__name__)
APPROVED_TASKS_FILE = Path(__file__).parent.parent / "approved-tasks.json"
# Module-level config instance (lazy singleton)
_config: Config | None = None
@@ -45,6 +52,19 @@ def route_message(
"""
text = text.strip()
# Ralph commands (!approve, !status, !stop, !propose)
if text.lower().startswith("!approve ") or text.lower() == "!approve":
return _ralph_approve(text), True
if text.lower() == "!status" or text.lower().startswith("!status "):
return _ralph_status(text), True
if text.lower().startswith("!stop "):
return _ralph_stop(text), True
if text.lower().startswith("!propose "):
return _ralph_propose(text), True
# Text-based commands (not slash commands — these work in any adapter)
if text.lower() == "/clear":
default_model = _get_config().get("bot.default_model", "sonnet")
@@ -136,6 +156,168 @@ def _model_command(channel_id: str, text: str) -> str:
return f"Model changed to {choice}."
def _load_approved_tasks() -> dict:
"""Load approved-tasks.json, return empty structure if missing."""
if APPROVED_TASKS_FILE.exists():
return json.loads(APPROVED_TASKS_FILE.read_text())
return {"projects": [], "last_updated": None}
def _save_approved_tasks(data: dict) -> None:
data["last_updated"] = datetime.now(timezone.utc).isoformat()
APPROVED_TASKS_FILE.write_text(json.dumps(data, indent=2, ensure_ascii=False))
def _ralph_approve(text: str) -> str:
"""!approve P1,P2 sau !approve roa2web — aprobă proiecte pentru night-execute."""
parts = text.split(None, 1)
if len(parts) < 2:
data = _load_approved_tasks()
pending = [p for p in data["projects"] if p.get("status") == "pending"]
if not pending:
return "Niciun proiect pending. Folosește !propose <nume> <descriere> pentru a adăuga."
lines = [f"Proiecte pending (aprobă cu !approve <nume>):"]
for p in pending:
lines.append(f" - {p['name']}: {p['description'][:60]}")
return "\n".join(lines)
names_raw = parts[1].strip()
names = [n.strip() for n in names_raw.replace(",", " ").split() if n.strip()]
data = _load_approved_tasks()
approved = []
not_found = []
for name in names:
found = False
for p in data["projects"]:
if p["name"].lower() == name.lower():
p["status"] = "approved"
p["approved_at"] = datetime.now(timezone.utc).isoformat()
approved.append(p["name"])
found = True
break
if not found:
not_found.append(name)
if not_found:
return f"Nu am găsit proiectele: {', '.join(not_found)}. Verifică !status pentru lista completă."
_save_approved_tasks(data)
names_str = ", ".join(approved)
return f"✅ Aprobat pentru tonight: {names_str}\nNight-execute rulează la 23:00 și va implementa stories autonom."
def _ralph_status(text: str) -> str:
"""!status sau !status <proiect> — status Ralph pentru proiecte."""
parts = text.split(None, 1)
filter_name = parts[1].strip().lower() if len(parts) > 1 else None
data = _load_approved_tasks()
projects = data.get("projects", [])
if filter_name:
projects = [p for p in projects if filter_name in p["name"].lower()]
if not projects:
return "Niciun proiect în approved-tasks.json. Adaugă cu !propose."
lines = ["📊 Status proiecte Ralph:"]
for p in projects:
status = p.get("status", "unknown")
name = p["name"]
pid = p.get("pid")
started = p.get("started_at", "")[:16] if p.get("started_at") else "-"
# Verifică dacă procesul mai rulează
if pid and status == "running":
try:
os.kill(pid, 0)
running_indicator = f"🟢 PID {pid}"
except (ProcessLookupError, PermissionError):
running_indicator = "🔴 PID mort"
p["status"] = "stopped"
_save_approved_tasks(data)
else:
running_indicator = {"approved": "⏳ aștept 23:00", "pending": "📋 pending",
"complete": "✅ complet", "failed": "❌ eșuat", "stopped": "⏹ oprit"}.get(status, status)
# Stories complete din prd.json
prd_path = Path(f"/home/moltbot/workspace/{name}/scripts/ralph/prd.json")
stories_info = ""
if prd_path.exists():
try:
prd = json.loads(prd_path.read_text())
total = len(prd.get("userStories", []))
done = sum(1 for s in prd.get("userStories", []) if s.get("passes"))
stories_info = f" | Stories: {done}/{total}"
except Exception:
pass
lines.append(f" {name}: {running_indicator}{stories_info} | Start: {started}")
return "\n".join(lines)
def _ralph_stop(text: str) -> str:
"""!stop <proiect> — oprește Ralph loop pentru un proiect."""
parts = text.split(None, 1)
if len(parts) < 2:
return "Folosire: !stop <nume-proiect>"
name = parts[1].strip()
data = _load_approved_tasks()
for p in data["projects"]:
if p["name"].lower() == name.lower():
pid = p.get("pid")
if pid:
try:
os.kill(pid, signal.SIGTERM)
p["status"] = "stopped"
p["stopped_at"] = datetime.now(timezone.utc).isoformat()
_save_approved_tasks(data)
return f"⏹ Ralph oprit pentru {name} (PID {pid} terminat)."
except ProcessLookupError:
p["status"] = "stopped"
_save_approved_tasks(data)
return f"PID {pid} nu mai rula pentru {name}. Status actualizat."
except PermissionError:
return f"❌ Nu am permisiune să opresc PID {pid}."
else:
return f"{name} nu are un PID activ (status: {p.get('status', 'unknown')})."
return f"Proiect '{name}' nu găsit în approved-tasks.json."
def _ralph_propose(text: str) -> str:
"""!propose <nume> <descriere> — adaugă un proiect pentru aprobare."""
parts = text.split(None, 2)
if len(parts) < 3:
return "Folosire: !propose <nume-proiect> <descriere scurtă>\nEx: !propose roa2web Homepage redesign cu hero section și animații"
name = parts[1].strip()
description = parts[2].strip()
data = _load_approved_tasks()
for p in data["projects"]:
if p["name"].lower() == name.lower():
return f"Proiectul '{name}' există deja cu status: {p.get('status', 'unknown')}."
data["projects"].append({
"name": name,
"description": description,
"status": "pending",
"proposed_at": datetime.now(timezone.utc).isoformat(),
"approved_at": None,
"started_at": None,
"pid": None
})
_save_approved_tasks(data)
return f"📋 Adăugat: {name}\n{description}\n\nAprobă cu: !approve {name}"
def _get_channel_config(channel_id: str) -> dict | None:
"""Find channel config by ID."""
channels = _get_config().get("channels", {})

View File

@@ -0,0 +1,19 @@
{
"projectName": "feature-name",
"branchName": "ralph/feature-name",
"description": "Descriere scurtă a feature-ului",
"userStories": [
{
"id": "US-001",
"title": "Titlu story",
"description": "As a [user type], I want [feature] so that [benefit]",
"priority": 1,
"acceptanceCriteria": [
"Criteriu specific și verificabil",
"npm run typecheck passes"
],
"passes": false,
"notes": ""
}
]
}

207
tools/ralph/prompt.md Normal file
View File

@@ -0,0 +1,207 @@
# Ralph - Instrucțiuni pentru Iterație
Ești un agent autonom care implementează user stories dintr-un PRD. Aceasta este O SINGURĂ iterație - implementezi UN singur story și apoi te oprești.
## Workflow pentru această iterație
### 1. Citește contextul
- PRD-ul și progress.txt sunt furnizate în context
- Înțelege ce stories sunt deja complete (`passes: true`)
- Identifică următorul story de implementat (prioritate cea mai mică dintre cele incomplete)
- Notează `techStack.commands` din PRD pentru comenzile corecte
### 2. Management branch
- Verifică dacă ești pe branch-ul corect (specificat în `branchName` din PRD)
- Dacă nu, creează și checkout branch-ul:
```bash
git checkout -b <branchName>
```
- Dacă branch-ul există deja, doar checkout:
```bash
git checkout <branchName>
```
### 3. Selectează story-ul
- Alege story-ul cu cea mai mică prioritate care are `passes: false`
- Citește atent acceptance criteria
- Verifică câmpul `requiresBrowserCheck` - dacă e `true`, trebuie verificare vizuală
### 4. Implementare
- Implementează DOAR acest story
- Urmează patterns existente în codebase
- Fii minimal și focusat - nu adăuga funcționalități extra
### 5. Quality Checks
Rulează TOATE verificările înainte de commit. Folosește comenzile din `techStack.commands`:
```bash
# Folosește comenzile din prd.json techStack.commands:
{techStack.commands.typecheck} # Type checking
{techStack.commands.lint} # Linting
{techStack.commands.test} # Tests (dacă există)
```
**Comenzi standard per stack:**
| Stack | Typecheck | Lint | Test |
|-------|-----------|------|------|
| Next.js/TS | npm run typecheck | npm run lint | npm test |
| Node.js | npm run typecheck | npm run lint | npm test |
| Python | mypy . | ruff check . | python -m pytest |
| Go | - | golangci-lint run | go test ./... |
**IMPORTANT**: Nu face commit dacă verificările eșuează. Repară mai întâi.
### 6. Verificare Browser (pentru UI stories)
**DACĂ story-ul are `requiresBrowserCheck: true` sau implică UI:**
Folosește **agent-browser CLI** pentru verificare vizuală. Agent-browser e optimizat pentru agenți AI cu referințe compacte (@e1, @e2) care consumă minim tokeni.
#### 6.1 Pornește dev server-ul
```bash
# Folosește comanda din techStack.commands.start
{techStack.commands.start}
# Exemplu: npm run dev
```
Așteaptă să pornească (verifică output-ul pentru "ready" sau similar).
#### 6.2 Navighează la pagină
```bash
agent-browser navigate "http://localhost:{techStack.port}"
# Exemplu: agent-browser navigate "http://localhost:3000"
```
#### 6.3 Ia snapshot pentru verificare
```bash
agent-browser snapshot
```
Snapshot-ul returnează o listă de elemente cu referințe compacte:
```
@e1: heading "Welcome"
@e2: button "Login"
@e3: textbox "Email"
@e4: textbox "Password"
@e5: button "Submit"
```
**Verifică în snapshot:**
- Elementele cheie din acceptance criteria există
- Textul e corect
- Structura paginii e corectă
#### 6.4 Testează interacțiunile (dacă e cazul)
```bash
# Click pe un element
agent-browser click @e2
# Fill un input
agent-browser fill @e3 "test@example.com"
# Așteaptă o schimbare
agent-browser snapshot # verifică noua stare
```
#### 6.5 Salvează screenshot ca dovadă
```bash
agent-browser screenshot ./scripts/ralph/screenshots/US-{id}-$(date +%Y%m%d-%H%M%S).png
# Exemplu: agent-browser screenshot ./scripts/ralph/screenshots/US-001-20240115-143022.png
```
#### 6.6 Verifică erori
```bash
# Verifică console pentru erori
agent-browser console
```
**IMPORTANT**:
- NU marca story-ul complete dacă verificarea vizuală eșuează!
- Dacă găsești erori în browser, repară-le înainte de commit
- Screenshots sunt salvate în `scripts/ralph/screenshots/` pentru referință
### 7. Documentare (dacă ai descoperit ceva util)
Dacă ai descoperit patterns sau gotchas, actualizează `AGENTS.md` în directorul relevant:
- API patterns
- Dependențe non-evidente
- Convenții de cod
- Cum să testezi anumite funcționalități
### 8. Commit
Format commit message:
```
feat: [Story ID] - [Story Title]
```
### 9. Marchează story-ul ca complet
**CRITIC**: Actualizează `scripts/ralph/prd.json`:
- Setează `passes: true` pentru story-ul implementat
- Adaugă note relevante în câmpul `notes`
### 10. Actualizează progress.txt
Adaugă la sfârșitul fișierului `scripts/ralph/progress.txt`:
```markdown
## Iterație: [timestamp]
### Story implementat: [ID] - [Title]
### Status: Complete
### Verificări:
- Typecheck: PASS
- Lint: PASS
- Tests: PASS/SKIP
- Browser check: PASS/N/A
### Learnings:
- [Ce ai învățat]
- [Patterns descoperite]
### Next steps:
- [Ce rămâne de făcut]
---
```
## Reguli importante
1. **UN SINGUR STORY PE ITERAȚIE** - Nu implementa mai mult de un story
2. **TOATE CHECKS TREBUIE SĂ TREACĂ** - Nu face commit cu erori
3. **VERIFICARE BROWSER PENTRU UI** - Obligatorie dacă `requiresBrowserCheck: true`
4. **ACTUALIZEAZĂ prd.json** - Altfel iterația următoare va repeta munca
5. **FII CONCIS** - Nu over-engineer
## Comenzi agent-browser (referință rapidă)
```bash
# Navigare
agent-browser navigate "http://localhost:3000/page"
# Snapshot (vedere compactă a paginii)
agent-browser snapshot
# Click pe element (folosind ref din snapshot)
agent-browser click @e5
# Fill input
agent-browser fill @e3 "value"
# Screenshot
agent-browser screenshot ./path/to/file.png
# Console logs
agent-browser console
# Așteaptă text
agent-browser wait-for "Loading complete"
```
## Condiție de terminare
Dacă TOATE stories au `passes: true`, răspunde cu:
```
<promise>COMPLETE</promise>
```
---
ÎNCEPE IMPLEMENTAREA ACUM.

229
tools/ralph/ralph.sh Executable file
View File

@@ -0,0 +1,229 @@
#!/bin/bash
# Ralph pentru Claude Code - Loop autonom de agent AI
# Adaptat din Ralph original (snarktank/ralph) pentru Claude Code CLI
# Usage: ./ralph.sh [max_iterations] [project_dir]
set -e
MAX_ITERATIONS=${1:-10}
PROJECT_DIR=${2:-$(pwd)}
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PRD_FILE="$SCRIPT_DIR/prd.json"
PROGRESS_FILE="$SCRIPT_DIR/progress.txt"
ARCHIVE_DIR="$SCRIPT_DIR/archive"
SCREENSHOTS_DIR="$SCRIPT_DIR/screenshots"
LAST_BRANCH_FILE="$SCRIPT_DIR/.last-branch"
PROMPT_FILE="$SCRIPT_DIR/prompt.md"
# Verifică că jq este instalat
if ! command -v jq &> /dev/null; then
echo "Eroare: jq nu este instalat. Rulează: apt install jq"
exit 1
fi
# Verifică că claude este instalat
if ! command -v claude &> /dev/null; then
echo "Eroare: Claude Code CLI nu este instalat."
echo "Instalează cu: npm install -g @anthropic-ai/claude-code"
exit 1
fi
# Verifică agent-browser (opțional, pentru verificări UI)
if ! command -v agent-browser &> /dev/null; then
echo "Notă: agent-browser nu este instalat."
echo "Pentru verificări vizuale UI, instalează cu: npm install -g agent-browser && agent-browser install"
echo "Continuăm fără verificări browser..."
echo ""
fi
# Verifică existența fișierelor necesare
if [ ! -f "$PRD_FILE" ]; then
echo "Eroare: prd.json nu există în $SCRIPT_DIR"
echo "Generează mai întâi un PRD folosind skill-ul /prd și apoi /ralph"
exit 1
fi
if [ ! -f "$PROMPT_FILE" ]; then
echo "Eroare: prompt.md nu există în $SCRIPT_DIR"
exit 1
fi
# Arhivare rulare anterioară dacă branch-ul s-a schimbat
if [ -f "$PRD_FILE" ] && [ -f "$LAST_BRANCH_FILE" ]; then
CURRENT_BRANCH=$(jq -r '.branchName // empty' "$PRD_FILE" 2>/dev/null || echo "")
LAST_BRANCH=$(cat "$LAST_BRANCH_FILE" 2>/dev/null || echo "")
if [ -n "$CURRENT_BRANCH" ] && [ -n "$LAST_BRANCH" ] && [ "$CURRENT_BRANCH" != "$LAST_BRANCH" ]; then
DATE=$(date +%Y-%m-%d)
FOLDER_NAME=$(echo "$LAST_BRANCH" | sed 's|^ralph/||')
ARCHIVE_FOLDER="$ARCHIVE_DIR/$DATE-$FOLDER_NAME"
echo "Arhivare rulare anterioară: $LAST_BRANCH"
mkdir -p "$ARCHIVE_FOLDER"
[ -f "$PRD_FILE" ] && cp "$PRD_FILE" "$ARCHIVE_FOLDER/"
[ -f "$PROGRESS_FILE" ] && cp "$PROGRESS_FILE" "$ARCHIVE_FOLDER/"
echo " Arhivat în: $ARCHIVE_FOLDER"
# Reset progress file
echo "# Ralph Progress Log" > "$PROGRESS_FILE"
echo "Started: $(date)" >> "$PROGRESS_FILE"
echo "Branch: $CURRENT_BRANCH" >> "$PROGRESS_FILE"
echo "---" >> "$PROGRESS_FILE"
fi
fi
# Salvează branch-ul curent
if [ -f "$PRD_FILE" ]; then
CURRENT_BRANCH=$(jq -r '.branchName // empty' "$PRD_FILE" 2>/dev/null || echo "")
if [ -n "$CURRENT_BRANCH" ]; then
echo "$CURRENT_BRANCH" > "$LAST_BRANCH_FILE"
fi
fi
# Creează directoare necesare
mkdir -p "$SCRIPT_DIR/logs" "$SCRIPT_DIR/archive" "$SCRIPT_DIR/screenshots"
# Creează .gitignore dacă nu există
if [ ! -f "$PROJECT_DIR/.gitignore" ]; then
cat > "$PROJECT_DIR/.gitignore" << 'GITIGNORE'
# Python
__pycache__/
*.py[cod]
*.pyo
*.egg-info/
dist/
build/
.coverage
htmlcov/
.pytest_cache/
# Virtual environment
venv/
.venv/
# Ralph runtime
scripts/ralph/.ralph.pid
scripts/ralph/.last-branch
scripts/ralph/logs/
scripts/ralph/screenshots/
scripts/ralph/archive/
# IDE
.vscode/
.idea/
# OS
.DS_Store
Thumbs.db
GITIGNORE
echo "Created .gitignore"
fi
# Inițializare progress file dacă nu există
if [ ! -f "$PROGRESS_FILE" ]; then
echo "# Ralph Progress Log" > "$PROGRESS_FILE"
echo "Started: $(date)" >> "$PROGRESS_FILE"
echo "---" >> "$PROGRESS_FILE"
fi
# Funcție pentru a verifica dacă toate story-urile sunt complete
check_all_complete() {
local incomplete=$(jq '[.userStories[] | select(.passes != true)] | length' "$PRD_FILE" 2>/dev/null || echo "999")
[ "$incomplete" -eq 0 ]
}
# Afișare status inițial
echo ""
echo "======================================================================="
echo " RALPH pentru Claude Code - Agent Autonom "
echo "======================================================================="
PROJECT_NAME=$(jq -r '.projectName // "Unknown"' "$PRD_FILE")
BRANCH_NAME=$(jq -r '.branchName // "N/A"' "$PRD_FILE")
TOTAL_STORIES=$(jq '.userStories | length' "$PRD_FILE")
COMPLETE_STORIES=$(jq '[.userStories[] | select(.passes == true)] | length' "$PRD_FILE")
echo " Proiect: $PROJECT_NAME"
echo " Branch: $BRANCH_NAME"
echo " Stories: $COMPLETE_STORIES / $TOTAL_STORIES complete"
echo " Max iterații: $MAX_ITERATIONS"
echo " Screenshots: $SCREENSHOTS_DIR"
echo "======================================================================="
echo ""
# Verificare rapidă - poate toate sunt deja complete?
if check_all_complete; then
echo "Toate story-urile sunt deja complete!"
exit 0
fi
# Loop principal
for i in $(seq 1 $MAX_ITERATIONS); do
echo ""
echo "==================================================================="
echo " Ralph Iterația $i din $MAX_ITERATIONS"
echo "==================================================================="
# Status curent
COMPLETE_NOW=$(jq '[.userStories[] | select(.passes == true)] | length' "$PRD_FILE")
NEXT_STORY=$(jq -r '[.userStories[] | select(.passes != true)] | sort_by(.priority) | .[0] | "\(.id): \(.title)"' "$PRD_FILE")
echo " Progress: $COMPLETE_NOW / $TOTAL_STORIES stories complete"
echo " Next: $NEXT_STORY"
echo ""
# Pregătește prompt-ul cu context
FULL_PROMPT=$(cat <<EOF
# Context pentru această iterație Ralph
## PRD (prd.json):
$(cat "$PRD_FILE")
## Progress până acum (progress.txt):
$(cat "$PROGRESS_FILE")
## Instrucțiuni pentru această iterație:
$(cat "$PROMPT_FILE")
EOF
)
# Execută Claude Code în modul non-interactiv
LOG_FILE="$SCRIPT_DIR/logs/iteration-$i-$(date +%Y%m%d-%H%M%S).log"
mkdir -p "$SCRIPT_DIR/logs"
# --output-format json avoids streaming mode issues
echo "$FULL_PROMPT" | claude -p --dangerously-skip-permissions --output-format json 2>&1 | tee "$LOG_FILE" || true
OUTPUT=$(cat "$LOG_FILE")
# Verifică dacă toate task-urile sunt complete
if echo "$OUTPUT" | grep -q "<promise>COMPLETE</promise>"; then
echo ""
echo "==================================================================="
echo " RALPH A TERMINAT TOATE TASK-URILE!"
echo " Completat la iterația $i din $MAX_ITERATIONS"
echo "==================================================================="
exit 0
fi
# Verifică și prin prd.json
if check_all_complete; then
echo ""
echo "==================================================================="
echo " TOATE STORY-URILE DIN PRD SUNT COMPLETE!"
echo "==================================================================="
exit 0
fi
echo " Iterația $i completă. Continuăm..."
sleep 2
done
echo ""
echo "==================================================================="
echo " Ralph a atins limita de iterații ($MAX_ITERATIONS)"
echo " Verifică progress.txt pentru status."
echo "==================================================================="
echo ""
# Afișează stories incomplete
echo "Stories incomplete:"
jq -r '.userStories[] | select(.passes != true) | " - \(.id): \(.title)"' "$PRD_FILE"
exit 1