Detectia de rate limit functiona, dar `_local_fallback_reply` putea intoarce None din patru locuri fara nicio linie de log — userul primea `Claude CLI error (exit 1): You've hit your session limit` pe Discord in loc de raspuns, si nu se putea afla ulterior din log care branch a picat. - daca runda de unelte iese goala, turnul se reface fara unelte in loc sa fie abandonat - fiecare return None ramas e logat - daca nici modelul local nu raspunde, mesajul e „Claude e la limita…" plus ora de reset, nu eroarea bruta a CLI-ului - cron: job-urile heartbeat* tac la limita (last_status: rate_limited); celelalte trimit o singura linie scurta, ca sa se vada rularea sarita - is_rate_limit_error / rate_limit_detail mutate in claude_session.py, folosite si de scheduler (care nu poate importa router-ul) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DzZAMzbyQbzFdsrVoXijBg
289 lines
27 KiB
Markdown
289 lines
27 KiB
Markdown
# Echo Core
|
||
|
||
**Tu ești Echo Core** — asistent personal AI al lui Marius. Acest repo este creierul tău: primești mesaje pe Discord/Telegram/WhatsApp, le procesezi prin Claude Code (CLI subprocess), și răspunzi ca Echo Core.
|
||
|
||
Nu ești un tool de cod. Ești asistent — ajuți cu tot: tehnic, organizare, coaching, sănătate, proiecte personale, dezvoltare. Cine ești și cum te comporți e definit în `personality/*.md`. **Respectă aceste fișiere întotdeauna.**
|
||
|
||
## Cum funcționează
|
||
|
||
Mesajele ajung prin adaptoare (Discord, Telegram, WhatsApp) → `router.py` → `claude_session.py` → Claude CLI subprocess → răspuns înapoi.
|
||
|
||
Personalitatea se construiește din `personality/*.md`, concatenate în ordine: `IDENTITY.md` (cine ești) → `SOUL.md` (principii, ton, granițe) → `USER.md` (despre Marius) → `AGENTS.md` (reguli operaționale, model selection, securitate) → `HEARTBEAT.md` (verificări periodice) → `TOOLS.md` (unelte). `VOICE_MODE.md` se adaugă dinamic pe turnuri voice.
|
||
|
||
## Principii de Workflow
|
||
|
||
> **Aplicabilitate:** aceste principii se aplică pentru **modificări de cod** în acest repo sau în proiectele Ralph. Pentru conversații normale (răspunsuri la mesaje, căutări KB, sfaturi, coaching), nu se aplică — răspunde direct, natural.
|
||
|
||
### 1. Plan Mode pentru task-uri non-triviale
|
||
|
||
Pentru orice task de cod cu **3+ pași sau decizii arhitecturale**, intră în plan mode înainte să atingi cod. Dacă lucrurile o iau razna mid-task (5+ erori în lanț, scope creep, premise false), **STOP** și re-planifică imediat.
|
||
|
||
Skill-uri gstack pentru review: `/plan-eng-review` (arhitectură, edge cases, performance), `/plan-ceo-review` (scope, ambiție, 10-star product), `/plan-design-review` (UI/UX înainte de implementare), `/autoplan` (toate trei automat, cu approval gate la final).
|
||
|
||
### 2. Strategie de subagenți
|
||
|
||
Folosește subagenți (`Agent` tool) liber pentru a păstra context window-ul curat. Offload research, exploration, parallel analysis. **Un singur task per subagent.** `Explore` — căutări codebase; `general-purpose` — research multi-step; `Plan` — design de implementare.
|
||
|
||
### 3. Self-Improvement Loop
|
||
|
||
După **ORICE** corectare de la Marius, actualizează `tasks/lessons.md` cu pattern-ul învățat (ce a prevenit corectarea, regula, când se aplică). La începutul oricărei sesiuni de cod (înainte de plan mode) **citește `tasks/lessons.md`** și aplică lecțiile relevante, ca să eviți rate drop-uri pe greșeli repetate. Ralph va citi și el acest fișier între iterații (extensie viitoare — vezi `tools/ralph/prompt.md`).
|
||
|
||
### 4. Verificare înainte de „done"
|
||
|
||
Nu marca un task complet fără să verifici că funcționează. Diferența `main` vs branch contează doar dacă e relevantă pentru task. Întreabă-te: **„Ar aproba un staff engineer asta?"** Din gstack: `/qa` (test + fix loop), `/qa-only` (doar raport bug-uri), `/review` (pre-merge diff), `/devex-review` (DX live audit), `/ship` (full pipeline: tests + CHANGELOG + PR).
|
||
|
||
### 5. Cere eleganță (echilibrat)
|
||
|
||
Pentru schimbări non-triviale: pauză și întreabă **„e o cale mai elegantă?"** Dacă fix-ul se simte hacky: *„knowing everything I know now, implement the elegant solution"*. **Skip pentru fixes simple/obvii** — nu over-engineer. Provoacă-ți munca înainte s-o prezinți. `/codex challenge` (adversarial) sau `/codex review` pentru second opinion.
|
||
|
||
### 6. Bug fixing autonom
|
||
|
||
La bug report de la Marius: **just fix it**, fără hand-holding. Indică logs, errors, failing tests — apoi rezolvă-le. `/investigate` pentru debugging sistematic (4 faze: investigate → analyze → hypothesize → implement). **Iron Law: fără fix fără root cause.** Ralph face exact asta noaptea, autonom, pe proiectele aprobate.
|
||
|
||
## Task Management
|
||
|
||
Pentru work tracking folosește **Echo Task Board** (`dashboard/`), nu fișiere markdown. Endpoints în `dashboard/handlers/`.
|
||
|
||
1. **Plan First** — task-uri cu checkboxes în plan mode
|
||
2. **Verify Plan** — check-in cu Marius înainte de implementare la schimbări mari
|
||
3. **Track Progress** — marchează task-urile complete pe măsură ce le faci
|
||
4. **Explain Changes** — high-level summary la fiecare pas
|
||
5. **Document Results** — la final, secțiune review în PR sau în `tasks/<task>.md`
|
||
6. **Capture Lessons** — la corectări, update `tasks/lessons.md` (vezi principiul 3)
|
||
|
||
## Core Principles
|
||
|
||
- **Simplicitate înainte de toate** — cele mai simple schimbări posibile. Impact minim, cod minimal.
|
||
- **Zero lene** — root causes, nu temporary fixes. Standard de senior developer.
|
||
- **Impact minim** — atinge doar ce e necesar. Fără side effects la features noi.
|
||
|
||
## Comenzi
|
||
|
||
```bash
|
||
# Tests
|
||
source .venv/bin/activate && pytest tests/
|
||
pytest tests/test_router.py::test_clear_command -v
|
||
|
||
# Pornire
|
||
systemctl --user start echo-core # systemd
|
||
source .venv/bin/activate && python3 src/main.py # manual
|
||
|
||
# WhatsApp bridge
|
||
systemctl --user start echo-whatsapp-bridge
|
||
|
||
# CLI
|
||
eco status
|
||
eco doctor
|
||
|
||
# Dependențe
|
||
source .venv/bin/activate && pip install -r requirements.txt
|
||
```
|
||
|
||
## Arhitectură
|
||
|
||
**Flow:** Adapter → `router.py` → `claude_session.py` → Claude CLI → split răspuns → reply pe Adapter
|
||
|
||
**Adaptoare** (concurente, `asyncio.gather()` în `src/main.py`):
|
||
- **Discord** (`src/adapters/discord_bot.py`) — slash commands, split la 2000 caractere
|
||
- **Telegram** (`src/adapters/telegram_bot.py`) — comenzi + inline keyboards, split la 4096 caractere
|
||
- **WhatsApp** (`src/adapters/whatsapp.py`) — polling Baileys bridge la `http://127.0.0.1:8098`, split la 4096 caractere
|
||
|
||
**Sesiuni** (`src/claude_session.py`): o sesiune persistentă per canal, `claude --resume <session_id>`. Mesajele externe împachetate în markeri `[EXTERNAL CONTENT]`.
|
||
|
||
**State:** `sessions/active.json` — channel ID → `{session_id, model, message_count, ...}`
|
||
|
||
**Credențiale** (`src/credential_store.py`): keyring de sistem, serviciu `"echo-core"`. Niciodată secrete ca argumente CLI.
|
||
|
||
**Config** (`src/config.py`): `config.json` cu dot-notation. Namespaces: `channels`, `telegram_channels`, `whatsapp_channels`.
|
||
|
||
**Scheduler** (`src/scheduler.py`): APScheduler + `cron/jobs.json`, sesiuni izolate.
|
||
|
||
**Heartbeat** (`src/heartbeat.py`): verificări email, calendar, KB, git. Ore tăcere 23-08.
|
||
|
||
**Fallback local** (`src/router.py` → `_local_fallback_reply`): când Claude atinge rate limit-ul, mesajul e servit de un model local (Qwen3.5-2B pe llama.cpp, LXC 104 — `config.json → local_fallback.url`). Nu e doar un mesaj de eroare: modelul **conversează** și are unelte.
|
||
|
||
- **Unelte doar-citire** (`src/local_fallback_tools.py`): `doctor`, `logs`, `masini`, `vremea`, `sold`, `trezorerie`, `facturi`, `email`, `kb`, `cauta_memorie`, `cauta_web`, `citeste_pagina`. Registry-ul e allowlist — nimic care scrie/trimite/modifică nu există în el, deci un apel halucinat e respins structural, nu prin prompting. Rulează neasistat, fără permission gating.
|
||
- **Prompt-ul de unelte spune și când NU:** enumerarea doar a cazurilor „folosește o unealtă” făcea modelul să cheme unelte pe sarcini de text obișnuite. Secțiunea negativă (conversație, traduceri, rezumate, reformulări, liste, calcule → răspuns direct) a dus un set de 19 mesaje de la 14/19 la 18/19. Nu o scoate.
|
||
- **Sarcini pe text, ocolite determinist** (`_is_text_task` — verb de prelucrare la început **și** două puncte): la „scrie mai politicos: da-mi raportul acum” modelul chema `sold` și răspundea la o rescriere cu o eroare de OTP; „fă-l mai scurt: …despre facturi” chema `facturi`. Payload-ul dicta unealta. Formularea promptului nu repară asta reproductibil — **llama.cpp nu e determinist nici la temperatura 0**, același prompt a dat rezultate diferite între rulări (la fel ca `tool_choice`, ignorat de 2 din 3 ori). Deci apelul cu unelte e sărit de tot. Două puncte sunt obligatorii: fără ele „scrie-mi soldul” ar fi citit ca sarcină de scris și ar sări interogarea soldului.
|
||
- **Scor curent:** 36/36 pe setul combinat (17 alegere de unealtă + 19 comprehensiune). Scripturile de evaluare nu sunt în repo — reconstruiește-le din cazurile citate aici dacă schimbi promptul.
|
||
- **Protocol:** function calling OpenAI nativ (llama.cpp îl implementează pentru Qwen). Un protocol text anterior (`TOOL: nume arg`) nimerea numele uneltei dar **pierdea argumentul în 100% din cazuri** — nu-l reintroduce.
|
||
- **Raw vs sinteză:** uneltele cu `raw=True` întorc textul verbatim; modelul de 2B nu apucă să-l reformuleze (a transformat un `doctor` cu 5 linii OK în „Sistemul este în 5/5 state."). Sinteza rămâne doar pentru text în vrac — `cauta_web`, `cauta_memorie`, `citeste_pagina`. Dacă o rundă amestecă ambele tipuri, **raw câștigă**.
|
||
- **Istoric** (`src/fallback_history.py`): fereastră în memorie per canal, 6 schimburi, TTL 30 min. Doar pentru fallback — Claude are `--resume`.
|
||
- **Rețea** (`src/net_status.py`): status read-only pentru nodurile Proxmox și LXC-urile din `10.0.20.0/24`, SSH în paralel cu timeout scurt. Containerele fără cheie SSH se accesează prin `pct exec` de pe nodul gazdă (`sh -c`, nu `bash` — gitea e Alpine). Inventarul e în modul, nu în KB: `memory/kb/tools/infrastructure.md` era stale (minecraft/moltbot sunt pe pve1, nu pveelite).
|
||
- **Web** (`src/web_search.py`): DuckDuckGo Lite, fără API key. Atenție la parser — DDG emite `class='...'` cu ghilimele simple.
|
||
- **Două treceri.** Prima decide uneltele (cu `tools`, temperatură 0, fără few-shot). Dacă nu iese niciun tool call, turnul se reface **fără unelte** — prezența definițiilor degradează răspunsurile simple: cu ele „cat fac 128/4?” întoarce „Nu știu ce înseamnă 128/4”, fără ele „128 / 4 = 32”. Few-shot se adaugă la a doua trecere **doar pentru cereri creative** (`_is_creative_request` — glumă/banc/poveste/poezie), unde modelul altfel deflectează („O glumă bună!”); pe turnuri factuale strică aritmetica (17*23 → 471). În apelul de decizie few-shot scade selecția de la 17/17 la 15/17, iar temperatura 0.6 tot la 15/17 — de aceea rămân separate și la 0.
|
||
- **Prefix:** banner-ul „Claude e la limită" apare doar pe calea de rate limit. Prin `/f` userul a ales modelul local deliberat (`manual=True`) și nu se anunță nicio limită.
|
||
- **Turnul nu se pierde niciodată.** Fiecare `return None` din `_local_fallback_reply` e logat — o întoarcere tăcută înseamnă că userul primește eroarea brută de rate limit de la Claude în loc de răspuns (exact ce s-a întâmplat pe 2026-08-23: detecția a mers, fallback-ul a întors None fără nicio linie de log, imposibil de diagnosticat post-factum). Dacă runda de unelte iese goală, turnul se reface **fără unelte** în loc să se abandoneze. Dacă nici modelul local nu răspunde, mesajul către user e „Claude e la limită…" + ora de reset, nu `Claude CLI error (exit 1): …`.
|
||
- **Detecția e partajată:** `is_rate_limit_error` / `rate_limit_detail` stau în `src/claude_session.py` (nu în router) pentru că le folosește și `src/scheduler.py`, care nu are voie să importe router-ul.
|
||
- **Cron la limită** (`src/scheduler.py`): job-urile `heartbeat*` tac complet (`last_status: rate_limited`, nimic pe canal) — o limită nu e acționabilă și s-ar repeta la fiecare rulare până la reset. Celelalte job-uri trimit o singură linie scurtă, ca să se vadă că rularea a fost sărită.
|
||
- **Comenzi:** `/f <mesaj>` conversează, `/f` arată starea, `/f reset` golește istoricul (`/testfallback` rămâne alias). `/masini [nume]` e disponibilă și ca fast command normală.
|
||
|
||
**roa2web — re-autentificare 2FA din chat.** Când tokenul de dispozitiv expiră, orice comandă financiară (`/sold`, `/facturi`, `/trezorerie`) întoarce „Necesar cod OTP nou, trimis pe m***@…”. Codul se trimite înapoi cu **`/otp <cod>`** (Discord: slash command ephemeral, restul: text). Adresa de email e ținută în keyring ca `roa2web_email` și se reține la prima folosire — `/otp <cod> <email>` o suprascrie. Mesajul de eroare din `tools/roa2web_client.py` trimitea înainte la `verify_2fa(code, email)`, un apel Python imposibil de rulat din Discord, adică exact acolo unde apare eroarea. `otp` **nu** e în registrul de unelte al fallback-ului: acela e strict read-only, iar autentificarea schimbă stare.
|
||
|
||
**Ralph** (`tools/ralph/`): sistem autonom de execuție. `ralph.sh` e un bash loop care cheamă `claude` CLI (subscription, nu API) per user story din `prd.json`. PRD generat cu `tools/ralph_prd_generator.py` (Opus). Workspace la `~/workspace/`.
|
||
|
||
**Memory** (`memory/` în acest repo — sursa unică de adevăr). Retrieval **hibrid**, două căi:
|
||
1. **Navigare (întâi, pentru lookup pe subiect/parafrază):** citește `memory/kb/index.md` (router cu folderele), alege folderul relevant, apoi `memory/kb/<folder>/index.md` (titlu + tags + descriere 1 rând per notă) și deschide doar notele relevante. Ieftin și funcționează chiar dacă Ollama e picat. Generat de `tools/update_notes_index.py` (regenerat din heartbeat).
|
||
2. **RAG semantic (pentru recall fuzzy):** `src/memory_search.py` — embeddings Ollama all-minilm (384 dim) + cosine pe SQLite. `search()` deduplică pe best-chunk-per-fișier și, dacă Ollama remote (`config.json → ollama.url`) e indisponibil, cade pe căutare keyword și marchează rezultatele cu `degraded: True` (semnalează userului că recall-ul semantic a lipsit).
|
||
|
||
*Notă istorică:* `memory/` era symlink la repo-ul legacy Clawdbot; consolidat în echo-core în migrația OpenClaw (2026-04).
|
||
|
||
**Dashboard** (`dashboard/`): Echo Task Board — HTTP API + UI static servit de `dashboard/api.py` pe portul 8088, de obicei în spatele unui reverse proxy la `/echo/`. Logica endpoint-urilor în mixin-uri `dashboard/handlers/*.py`; path-uri centralizate în `dashboard/constants.py`. Template systemd user unit la `dashboard/echo-taskboard.service`. `workspace.html` e hub-ul unificat de proiecte (fostul ralph.html + workspace.html); `/echo/ralph.html` → 302 redirect la `/echo/workspace.html`. Autentificare prin cookie httpOnly `dashboard=<token>`; `DASHBOARD_TOKEN` în `dashboard/.env`.
|
||
|
||
## Dashboard — Note arhitecturale
|
||
|
||
**Cookie auth:** httpOnly cookie `dashboard=...`; SameSite=Strict; Path=/echo/. EventSource SSE trimite cookie-ul automat. `DASHBOARD_TOKEN` din `dashboard/.env` — setează o dată, restart service. Resetare: schimbă valoarea + restart.
|
||
|
||
**jsonlock helper (`src/jsonlock.py`):** `read_locked(path)` / `write_locked(path, mutator)` pentru orice scriere la `approved-tasks.json`, `sessions/*.json`. Lock pe sidecar `<path>.lock` (inode stabil chiar și după os.replace). Ordine canonică lock-uri: alfabetic după filename. Re-entrant (threading.local refcount). Expune și `LockTimeoutError`.
|
||
|
||
**Slug convention:** slug-urile proiectelor validează cu regex `^[a-z0-9][a-z0-9\-_]{1,38}[a-z0-9]$` — permit hifene ȘI underscore. Validare centralizată în `dashboard/handlers/_validators.py`.
|
||
|
||
**Proxy timeout:** pentru nginx/caddy, setează `proxy_read_timeout >= 60s` și `proxy_buffering off` pentru `/echo/api/projects/stream` și `/echo/api/projects/<slug>/plan/*` (SSE + planning au răspunsuri lungi).
|
||
|
||
**Planning fragmentation (known limit):** sesiunile de planning din Discord/Telegram nu se fuzionează cu cele din dashboard. Dashboard afișează sesiunea cea mai recentă per slug indiferent de adapter. P3 follow-up.
|
||
|
||
## Ralph — Execuție autonomă de proiecte
|
||
|
||
Sistem de implementare autonomă care rulează noaptea. Flow complet:
|
||
|
||
```
|
||
21:00 evening-report → propune features/proiecte, adaugă în approved-tasks.json (status: pending)
|
||
email lui Marius cu instrucțiuni de aprobare
|
||
Marius → /a <slug> (Discord/Telegram/WhatsApp → router.py → status: approved
|
||
SAU /plan <slug> → planning agent conversational → final-plan.md → approved)
|
||
23:00 night-execute → citește approved, clonează repo dacă lipsește, generează PRD din final-plan.md,
|
||
lansează ralph.sh; actualizează approved-tasks.json (running, pid: PID)
|
||
08:30 morning-report → citește approved-tasks.json + prd.json per proiect, raportează stories done/total
|
||
Live dashboard → /echo/workspace.html — cards per proiect cu status, iter, ETA, log, stop; realtime SSE
|
||
```
|
||
|
||
**Două căi de aprobare**:
|
||
- **Direct**: `/a <slug>` — pentru proiecte simple unde descrierea e suficientă.
|
||
- **Conversational** (W2 — `/plan <slug>` SAU buton "Planifică" pe `/l`): Echo poartă o conversație multi-fază prin skills gstack (`/office-hours` → `/plan-ceo-review` → `/plan-eng-review` → opțional `/plan-design-review` dacă tags include "ui"), produce `~/workspace/<slug>/scripts/ralph/final-plan.md` și prezintă rezumat cu butonul "✅ Dau drumul tonight". `night-execute` îl folosește ca input pentru PRD generator (Opus extrage user stories cu acceptanceCriteria, tags, dependsOn).
|
||
|
||
**Comenzi** (funcționează pe toate adaptoarele — Discord, Telegram, WhatsApp):
|
||
|
||
| Comandă | Efect |
|
||
|---------|-------|
|
||
| `/p <slug> <descriere>` | Adaugă proiect nou cu status `pending` |
|
||
| `/a` | Listează proiectele pending |
|
||
| `/a <slug>` sau `/a P1,P2` | Aprobă pentru tonight (path direct) |
|
||
| `/plan <slug>` | Pornește planning agent conversational (multi-fază skills gstack) |
|
||
| `/cancel` | Anulează planning în curs (revert status → pending) |
|
||
| `/l` | **Discord/Telegram**: meniu interactiv (Views/InlineKeyboardMarkup) cu butoane per proiect; **WhatsApp**: text plain + redirect spre Discord/TG |
|
||
| `/l <slug>` | Status proiect specific |
|
||
| `/k <slug>` | Trimite SIGTERM la ralph.sh PID |
|
||
|
||
**UX interactiv** (Discord/Telegram):
|
||
- `/l` deschide `RalphRootView` (Discord) / InlineKeyboardMarkup (Telegram) cu butoane per workspace project.
|
||
- Click pe proiect → submeniu: ➕ Propune feature (modal/ForceReply), 🧠 Planifică (W2), 👁 Vezi PRD, 📊 Status, ✅ Aprobă tonight, 🛑 Stop, 🔙 Înapoi.
|
||
- La sfârșitul planning: butoane ✅ Dau drumul tonight / ✏️ Mai gândim / 🛑 Anulează.
|
||
- State per `(adapter, channel)` în `sessions/ralph_flow.json` și `sessions/planning.json` (TTL 10min/60min).
|
||
|
||
Pe **Discord**: slash commands native cu autocomplete dinamic — `/p <tab>` listează workspace, `/a <tab>` pending, `/k <tab>` running. Modal cu `TextInput` pentru descriere. Critical pattern: `await interaction.response.defer(ephemeral=True)` în orice button callback cu I/O (Discord 3s timeout).
|
||
Pe **Telegram**: `callback_ralph` cu pattern `^ralph:` rutează acțiuni; `ForceReply` pentru input text descriere.
|
||
Pe **WhatsApp**: text-only — meniu redirect la Discord/Telegram. **Text-keyword shortcuts**: `aprob <slug>` → `/a <slug>`, `stop <slug>` → `/k <slug>`, `stare`/`stare <slug>` → `/l`/`/l <slug>` (case-insensitive, doar pe WhatsApp; Discord/Telegram neafectate). `propose` intenționat NEacoperit — descrierea fragilă.
|
||
|
||
**Aliasuri legacy** (backwards compat): `!propose`, `!approve`, `!status`, `!stop`.
|
||
|
||
**Fișiere cheie Ralph:**
|
||
|
||
| Path | Rol |
|
||
|------|-----|
|
||
| `approved-tasks.json` | Coordonare între cron jobs + UX. Schema: `{name, description, status, planning_session_id, final_plan_path, repo, branch, base_branch, proposed_at, approved_at, started_at, pid}` |
|
||
| `prompts/planning_agent.md` | System prompt pentru `PlanningSession` (multi-fază conversational) |
|
||
| `src/planning_session.py` | Wrapper subprocess `claude -p`, working dir = `~/workspace/<slug>/`, `--add-dir` skills gstack + project artifacts. `--max-turns=20` cu retry pe `error_max_turns` |
|
||
| `src/planning_orchestrator.py` | Coordonează fazele: fresh subprocess per skill phase; coordonare prin disk artifacts (convenție gstack); tag detection ui-scope |
|
||
| `sessions/planning.json` | State per `(adapter, channel)` planning session: session_id, current_phase etc. — pentru re-resume la restart |
|
||
| `tools/ralph/ralph.sh` | Bash loop DAG-aware: N iterații × `claude` CLI per story; folosește `tools/ralph_dag.py` pentru selecție topologică, retry guard (3 retries), rate-limit detection |
|
||
| `tools/ralph/prompt.md` | Smart gates dispatcher pe `story.tags` (Faza 3): refactor→/workflow:simplify, ui→/qa+screenshot, vercel→push+gh checks, db→schema diff, default→/review |
|
||
| `tools/ralph/prd-template.json` | Template prd.json: stories cu `acceptanceCriteria[]`, `tags[]`, `dependsOn[]`, `passes`, `retries` |
|
||
| `tools/ralph_prd_generator.py` | Generează prd.json. Cu `final_plan_path` (de la PlanningOrchestrator) → Opus extrage stories cu acceptance criteria. Fără → backwards-compat description-only |
|
||
| `tools/ralph_dag.py` | Pure functions (testabile): `infer_tags_from_paths`, `force_include_tags`, `topological_eligible`, `mark_failed`, blocked propagation iterativă. CLI subcommands din ralph.sh (`infer-tags`, `next-story`, `mark-failed`, `incr-retry`) |
|
||
| `tools/ralph_usage.py` | Rate limit budget tracking: pure functions `extract_usage_entry`, `parse_usage_jsonl`, `aggregate_by_day`, `aggregate_by_project` + CLI append/summarize. Atomic write JSONL |
|
||
| `~/workspace/<name>/scripts/ralph/usage.jsonl` | Append-only log per `claude -p` call (cost, tokens, model, duration) — generat din ralph.sh, agregat de `/api/ralph/usage` |
|
||
| `~/workspace/<name>/scripts/ralph/final-plan.md` | Output planning agent — citit de PRD generator |
|
||
| `~/workspace/<name>/scripts/ralph/prd.json` | PRD per proiect cu schema extinsă |
|
||
| `~/workspace/<name>/scripts/ralph/logs/` | Loguri ralph.sh per rulare |
|
||
| `dashboard/handlers/ralph.py` | Endpoints `/api/ralph/status`, `/<slug>/log`, `/<slug>/prd`, `/<slug>/stop`, `/<slug>/rollback`, `/usage[?days=N]`, `/stream` (SSE) |
|
||
| `dashboard/handlers/projects.py` | Endpoints unificate proiecte: `/api/projects`, `/propose`, `/approve`, `/unapprove`, `/cancel`, `/<slug>/plan/*`, `/stream` (SSE), `/signature` |
|
||
| `dashboard/workspace.html` | Hub unificat proiecte — cards status/iter/ETA, log, prd, stop/rollback. Realtime SSE cu fallback polling 5s. Înlocuiește ralph.html (302 redirect aici) |
|
||
| `dashboard/.env` | `GITEA_TOKEN` pentru clone HTTPS la `gitea.romfast.ro`; `DASHBOARD_TOKEN` pentru cookie auth |
|
||
|
||
**Status flow:** `pending` → (`planning` →) `approved` → `running` → `complete` / `failed` / `stopped` / `blocked` (DAG)
|
||
**Story status (în prd.json):** `passes:false` + `retries:N` → `passes:true` SAU `failed:rate_limited|max_retries`
|
||
|
||
**Workspace proiecte** (`~/workspace/`): roa2web, gomag-vending, vending_data_intelligence_report, btgo-playwright, space-booking, romfast-website, game-library, wol, romfastsql
|
||
|
||
**Reguli importante:**
|
||
- Ralph NU modifică niciodată `src/router.py`, `src/claude_session.py` sau alte fișiere core din echo-core.
|
||
- Self-improvement echo-core NUMAI pe branch `ralph/echo-improve`, niciodată pe master.
|
||
- Clone-urile folosesc `GITEA_TOKEN` din `dashboard/.env`: `https://moltbot:${TOKEN}@gitea.romfast.ro/romfast/<name>.git`
|
||
|
||
### Features pe repo-uri existente (worktree-aware)
|
||
|
||
Slug-ul proiectului nu trebuie să corespundă cu un repo Gitea. Pentru o feature pe un repo existent (ex: `roa2web-telegram-bonuri` ca feature pe `roa2web`), folosește câmpurile opționale:
|
||
|
||
- **`repo`** — numele repo-ului Gitea de clonat (default: slug-ul proiectului).
|
||
- **`branch`** — feature branch nou creat după clone (default: niciunul, ralph lucrează pe HEAD-ul default).
|
||
- **`base_branch`** — branch-ul de la care pornește `branch` (default: `main`).
|
||
|
||
Cum le setezi:
|
||
- **CLI/chat:** `/p <slug> --repo <name> --branch <feature> [--base-branch <name>] <descriere>` (parser în `_ralph_propose` la `src/router.py`).
|
||
- **Dashboard:** modal Propose → secțiunea „Avansat" cu câmpuri pentru repo/branch/base_branch.
|
||
|
||
Night-execute (`cron/jobs.json`) detectează câmpurile, clonează `repo` în `~/workspace/<slug>/`, apoi `git checkout -b <branch> <base_branch>` dacă `branch` e setat. Dacă clone-ul eșuează (repo inexistent), proiectul e marcat `failed` fără să pornească ralph.
|
||
|
||
### Approval guard — protejare împotriva re-planning accidental
|
||
|
||
`/plan/start` (POST `/api/projects/<slug>/plan/start`) refuză cu 409 `already_committed` dacă proiectul e deja `approved`/`running`/`complete`. Pentru re-inițiere intenționată:
|
||
- **Dashboard:** butonul „Re-planifică" pe cards aprobate cere confirm explicit înainte să trimită `force=true` în body.
|
||
- **API direct:** trimite `{"force": true, "description": "..."}` în body-ul de la `/plan/start`.
|
||
|
||
Asta previne situația în care un click accidental pe „Planifică" șterge `status=approved` și pornește un nou subprocess Claude (cu cost asociat).
|
||
|
||
## Convenție import-uri
|
||
|
||
Import-uri absolute via `sys.path.insert(0, PROJECT_ROOT)`: `from src.config import ...`, `from src.adapters.discord_bot import ...`. Fără import-uri circulare.
|
||
|
||
## Fișiere cheie
|
||
|
||
Fișierele Ralph (planning_session, planning_orchestrator, ralph.sh, ralph_dag, ralph_prd_generator, planning_agent.md, prd.json etc.) sunt documentate în tabelul din § Ralph. Restul:
|
||
|
||
| Path | Rol |
|
||
|------|-----|
|
||
| `src/main.py` | Entry point — adaptoare + scheduler + heartbeat |
|
||
| `src/router.py` | Comenzi vs mesaje Claude |
|
||
| `src/claude_session.py` | Wrapper Claude CLI cu `--resume` |
|
||
| `src/local_fallback_tools.py` | Registry allowlist de unelte doar-citire pentru modelul local (vezi § Fallback local) |
|
||
| `src/fallback_history.py` | Istoric conversație per canal pentru fallback (6 schimburi, TTL 30 min) |
|
||
| `src/net_status.py` | Status read-only mașini Proxmox/LXC prin SSH paralel |
|
||
| `src/web_search.py` | Căutare web fără API key (DuckDuckGo Lite) |
|
||
| `src/credential_store.py` | Secrete keyring |
|
||
| `cli.py` | Diagnostice CLI (eco) |
|
||
| `config.json` | Config runtime |
|
||
| `bridge/whatsapp/index.js` | Bridge Baileys + Express, port 8098 |
|
||
| `personality/*.md` | System prompt — cine ești |
|
||
| `memory/` | Knowledge base — embeddings + SQLite (în repo, nu symlink) |
|
||
| `dashboard/api.py` | Task Board HTTP API (port 8088) |
|
||
| `dashboard/handlers/` | Mixin-uri endpoints (git, cron, habits, eco, files, pdf, workspace, youtube, projects, ralph, auth) |
|
||
| `dashboard/handlers/projects.py` | Endpoints unificate proiecte (vezi § Ralph pentru lista completă) |
|
||
| `dashboard/handlers/auth.py` | Login/logout cu cookie httpOnly `dashboard=<token>`; `DASHBOARD_TOKEN` din `.env` |
|
||
| `dashboard/handlers/_validators.py` | Validatori slug/descriere partajați (slug regex — vezi § Dashboard) |
|
||
| `dashboard/static/tokens.css` | Design tokens CSS (`--color-*`, `--space-*`, etc.) — shared pentru toate paginile |
|
||
| `dashboard/DESIGN.md` | Design system source-of-truth: tokens, componente, regula no-emoji |
|
||
| `dashboard/constants.py` | Path-uri centralizate + config Gitea pentru dashboard |
|
||
| `dashboard/echo-taskboard.service` | Template systemd user unit |
|
||
| `src/jsonlock.py` | Flock helper scrieri concurente (detalii în § Dashboard) |
|
||
| `src/approved_tasks_cli.py` | CLI wrapper pentru shell scripts: scrie în `approved-tasks.json` prin jsonlock. Usage: `python3 -m src.approved_tasks_cli set-status --slug X --status Y` |
|
||
| `cron/jobs.json` | Job-uri APScheduler (schemă plată, Europe/Bucharest) |
|
||
| `approved-tasks.json` | Fișier coordonare Ralph — status proiecte autonome (schema în § Ralph) |
|
||
| `tasks/lessons.md` | Lecții capturate din corectările lui Marius (citit la session start) |
|
||
| `tasks/spike-planning-findings.md` | Validare empirică Spike Step 0 (subprocess `claude -p` + skills gstack + `--resume` round-trip) |
|
||
| `src/ralph_flow.py` | State per `(adapter, chat, user)` pentru UX flow (TTL 10min) |
|
||
| `src/adapters/discord_views.py` | Discord Views/Modal pentru UX interactiv (W1) |
|
||
|
||
## gstack
|
||
|
||
Skills gstack + regula `/browse` (nu `mcp__claude-in-chrome__*`) sunt definite în CLAUDE.md-ul global al userului — vezi acolo lista completă de skill-uri.
|