diff --git a/CLAUDE.md b/CLAUDE.md index 81338e5..331e0d0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -199,7 +199,7 @@ Pe **WhatsApp**: text-only — meniu redirect la Discord/Telegram. **Text-keywor | Path | Rol | |------|-----| -| `approved-tasks.json` | Coordonare între cron jobs + UX. Schema: `{name, description, status, planning_session_id, final_plan_path, proposed_at, approved_at, started_at, pid}` | +| `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` cu working dir = `~/workspace//`, `--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; coordinează prin disk artifacts gstack convention; tag detection ui-scope | @@ -229,6 +229,29 @@ Pe **WhatsApp**: text-only — meniu redirect la Discord/Telegram. **Text-keywor - 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/.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`, `branch`, `base_branch`: + +- **`repo`** — numele repo-ului Gitea de clonat (default: slug-ul proiectului). +- **`branch`** — feature branch nou care va fi creat după clone (default: niciunul, ralph lucrează pe HEAD-ul default). +- **`base_branch`** — branch-ul de la care porneste `branch` (default: `main`). + +Cum le setezi: +- **CLI/chat:** `/p --repo --branch [--base-branch ] ` (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 și clonează `repo` în `~/workspace//`, apoi `git checkout -b ` dacă `branch` e setat. Dacă clone-ul eșuează (repo inexistent), proiectul e marcat `failed` fără să mai pornească ralph. + +### Approval guard — protejare împotriva re-planning accidental + +`/plan/start` (POST `/api/projects//plan/start`) refuză cu 409 `already_committed` dacă proiectul e deja `approved`/`running`/`complete`. Pentru a re-iniția planning-ul 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. diff --git a/cron/jobs.json b/cron/jobs.json index d0ffa84..778eaa8 100644 --- a/cron/jobs.json +++ b/cron/jobs.json @@ -269,9 +269,9 @@ "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-05-05T06:00:00.002794+00:00", + "last_run": "2026-05-05T08:00:00.002199+00:00", "last_status": "ok", - "next_run": "2026-05-05T08:00:00+00:00" + "next_run": "2026-05-05T10:00:00+00:00" }, { "name": "night-execute", @@ -279,7 +279,7 @@ "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: TOKEN=$(grep GITEA_TOKEN /home/moltbot/echo-core/dashboard/.env | cut -d= -f2) && git clone https://moltbot:${TOKEN}@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", + "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\nPentru un proiect cu schema extinsa (campuri optionale {repo, branch, base_branch}):\n - {name} = slug-ul proiectului (cheia 'name' din JSON)\n - {repo} = numele repo-ului Gitea (default = {name} daca nu e setat)\n - {branch} = feature branch nou (None inseamna 'lucreaza pe HEAD-ul default al repo-ului')\n - {base_branch} = branch-ul de la care porneste {branch} (default 'main')\n\n1. Verifica daca workspace-ul exista: /home/moltbot/workspace/{name}\n - Daca NU exista:\n TOKEN=$(grep GITEA_TOKEN /home/moltbot/echo-core/dashboard/.env | cut -d= -f2)\n git clone https://moltbot:${TOKEN}@gitea.romfast.ro/romfast/{repo}.git /home/moltbot/workspace/{name}\n # NOTA: cloneaza {repo}, nu {name}, ca sa suporte features pe repo-uri existente\n # (ex: slug='roa2web-bonuri', repo='roa2web')\n cd /home/moltbot/workspace/{name}\n # Daca {branch} e setat: creeaza branch nou de la {base_branch}\n if [ -n \"{branch}\" ]; then\n git fetch origin {base_branch:-main}\n git checkout {base_branch:-main}\n git checkout -b {branch} 2>/dev/null || git checkout {branch}\n fi\n - Daca EXISTA workspace-ul si {branch} e setat: asigura-te ca esti pe {branch}:\n cd /home/moltbot/workspace/{name}\n git checkout {branch} 2>/dev/null || git checkout -b {branch} {base_branch:-main}\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- Pentru cele cu {branch} setat, mentioneaza branch-ul activ\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- Daca git clone esueaza (repo inexistent): log status='failed' cu mesajul, NU continua cu PRD/ralph\n- Delay 5 secunde intre proiecte pentru a evita rate limiting\n", "allowed_tools": [ "Bash", "Read", diff --git a/dashboard/handlers/projects.py b/dashboard/handlers/projects.py index b2097b3..b40f742 100644 --- a/dashboard/handlers/projects.py +++ b/dashboard/handlers/projects.py @@ -629,6 +629,9 @@ class ProjectsHandlers: return # parse_json_body already sent 400 slug = (body.get("slug") or "").strip() description = (body.get("description") or "").strip() + repo = (body.get("repo") or "").strip() or None + branch = (body.get("branch") or "").strip() or None + base_branch = (body.get("base_branch") or "").strip() or None slug_err = validate_slug(slug) desc_err = validate_description(description) @@ -659,6 +662,9 @@ class ProjectsHandlers: "status": "pending", "planning_session_id": None, "final_plan_path": None, + "repo": repo, + "branch": branch, + "base_branch": base_branch, "proposed_at": _now_iso(), "approved_at": None, "started_at": None, @@ -825,13 +831,30 @@ class ProjectsHandlers: if body is None: return description = (body.get("description") or "").strip() + force = bool(body.get("force")) + + # Approval guard: refuse to silently overwrite approved/running/complete + # projects. Caller must opt in with force=true (the "Re-planifică" path). + existing_proj = _find_project(_read_approved(), slug) + if existing_proj and not force: + current_status = (existing_proj.get("status") or "").lower() + if current_status in {"approved", "running", "complete"}: + self.send_json({ + "error": "already_committed", + "slug": slug, + "status": current_status, + "message": ( + f"Proiectul `{slug}` e deja `{current_status}`. " + "Folosește «Re-planifică» (cu force=true) ca să reiei planning-ul." + ), + }, 409) + return # Description fallback: use the existing approved-tasks description so # users can re-open planning on a project they already proposed. if not description: - existing = _find_project(_read_approved(), slug) - if existing: - description = (existing.get("description") or "").strip() + if existing_proj: + description = (existing_proj.get("description") or "").strip() desc_err = validate_description(description) if description else "description required" if desc_err: self.send_json({"error": "invalid_description", "message": desc_err}, 400) @@ -847,6 +870,9 @@ class ProjectsHandlers: "status": "planning", "planning_session_id": None, "final_plan_path": None, + "repo": None, + "branch": None, + "base_branch": None, "proposed_at": _now_iso(), "approved_at": None, "started_at": None, diff --git a/dashboard/workspace.html b/dashboard/workspace.html index 3aed7d7..d910102 100644 --- a/dashboard/workspace.html +++ b/dashboard/workspace.html @@ -930,6 +930,30 @@ Planifică cu Echo imediat după propunere +
+ Avansat — feature pe repo existent +

+ Setează doar dacă slug-ul nu corespunde unui repo Gitea (e o feature pe alt repo). +

+
+ + +
+
+ + +
+
+ + +
+