chore: save WIP changes before audit fixes
- notifications: Telegram integration, richer stage/agent/QG notifications - plane_sync: explicit Plane state IDs, needs_input/in_review/blocked helpers, links in comments - launcher: deployer stage, model flag (opus), PR auto-create, REQUEST_CHANGES/tester/architect rollback+retry logic, partial check_reviewer_verdict path - qg/checks: add check_reviewer_verdict (substring-based, will be hardened in S-5) - stages: review->check_reviewer_verdict, testing->deployer agent - config: telegram_bot_token/chat_id settings
This commit is contained in:
@@ -11,16 +11,28 @@ PLANE_HEADERS = {"X-API-Key": settings.plane_api_token}
|
||||
WORKSPACE = settings.plane_workspace_slug
|
||||
PROJECT_ID = settings.plane_project_id or "7a79f0a9-5278-49cd-9007-9a338f238f9c"
|
||||
|
||||
# Plane state IDs
|
||||
PLANE_STATES = {
|
||||
"backlog": "113b24f6-cce8-4be9-9a22-a359b9cf0122",
|
||||
"todo": "2c7d3df3-9eb9-419b-92b7-d7d560bcdd10",
|
||||
"in_progress": "b873d9eb-993c-48cd-97ac-99a9b1623967",
|
||||
"needs_input": "babf08a3-ff4d-41f3-a821-5491aa29a8ac",
|
||||
"in_review": "38fb1f64-aa1e-48a3-92e0-0b109679046b",
|
||||
"blocked": "6c4543f9-ac47-4ef7-ae0f-070020dc9920",
|
||||
"done": "381a2833-3c4e-4be5-bd0f-be84cb946ad8",
|
||||
"cancelled": "b1cae7f9-961d-4889-a179-f3acea697d17",
|
||||
}
|
||||
|
||||
# Map orchestrator stages to Plane states
|
||||
STAGE_TO_STATE = {
|
||||
"created": "2c7d3df3-9eb9-419b-92b7-d7d560bcdd10", # Todo
|
||||
"analysis": "b873d9eb-993c-48cd-97ac-99a9b1623967", # In Progress
|
||||
"architecture": "b873d9eb-993c-48cd-97ac-99a9b1623967", # In Progress
|
||||
"development": "b873d9eb-993c-48cd-97ac-99a9b1623967", # In Progress
|
||||
"review": "b873d9eb-993c-48cd-97ac-99a9b1623967", # In Progress
|
||||
"testing": "b873d9eb-993c-48cd-97ac-99a9b1623967", # In Progress
|
||||
"deploy": "b873d9eb-993c-48cd-97ac-99a9b1623967", # In Progress
|
||||
"done": "381a2833-3c4e-4be5-bd0f-be84cb946ad8", # Done
|
||||
"created": PLANE_STATES["todo"],
|
||||
"analysis": PLANE_STATES["in_progress"],
|
||||
"architecture": PLANE_STATES["in_progress"],
|
||||
"development": PLANE_STATES["in_progress"],
|
||||
"review": PLANE_STATES["in_progress"],
|
||||
"testing": PLANE_STATES["in_progress"],
|
||||
"deploy": PLANE_STATES["in_progress"],
|
||||
"done": PLANE_STATES["done"],
|
||||
}
|
||||
|
||||
|
||||
@@ -108,13 +120,79 @@ def add_comment(work_item_id: str, text: str):
|
||||
logger.error(f"Failed to add comment to {work_item_id}: {e}")
|
||||
|
||||
|
||||
|
||||
def set_issue_needs_input(work_item_id: str):
|
||||
"""Set issue to 'Needs Input' state — waiting for stakeholder response."""
|
||||
_set_issue_state_direct(work_item_id, PLANE_STATES["needs_input"])
|
||||
|
||||
|
||||
def set_issue_in_review(work_item_id: str):
|
||||
"""Set issue to 'In Review' state — waiting for :approved: or :rejected:."""
|
||||
_set_issue_state_direct(work_item_id, PLANE_STATES["in_review"])
|
||||
|
||||
|
||||
def set_issue_blocked(work_item_id: str):
|
||||
"""Set issue to 'Blocked' state — manual intervention needed."""
|
||||
_set_issue_state_direct(work_item_id, PLANE_STATES["blocked"])
|
||||
|
||||
|
||||
def set_issue_in_progress(work_item_id: str):
|
||||
"""Set issue to 'In Progress' state — agent working."""
|
||||
_set_issue_state_direct(work_item_id, PLANE_STATES["in_progress"])
|
||||
|
||||
|
||||
def _set_issue_state_direct(work_item_id: str, state_id: str):
|
||||
"""Set issue state directly by state_id."""
|
||||
issue_id = find_issue_id(work_item_id)
|
||||
if not issue_id:
|
||||
logger.warning(f"Issue not found in Plane for {work_item_id}")
|
||||
return
|
||||
url = f"{PLANE_BASE}/workspaces/{WORKSPACE}/projects/{PROJECT_ID}/issues/{issue_id}/"
|
||||
try:
|
||||
resp = httpx.patch(url, headers=PLANE_HEADERS, json={"state": state_id}, timeout=10)
|
||||
resp.raise_for_status()
|
||||
logger.info(f"Plane: {work_item_id} state -> {state_id[:8]}...")
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to update Plane state for {work_item_id}: {e}")
|
||||
|
||||
|
||||
def notify_stage_change(work_item_id: str, old_stage: str, new_stage: str, agent: str = None):
|
||||
"""Notify Plane about stage transition."""
|
||||
"""Notify Plane about stage transition with links."""
|
||||
update_issue_state(work_item_id, new_stage)
|
||||
|
||||
msg = f"🔄 Stage: {old_stage} → {new_stage}"
|
||||
if agent:
|
||||
msg += f" (launching {agent})"
|
||||
|
||||
# Add relevant links
|
||||
gitea_base = "http://git.mva154.duckdns.org"
|
||||
try:
|
||||
from .db import get_db
|
||||
conn = get_db()
|
||||
row = conn.execute(
|
||||
"SELECT branch, repo FROM tasks WHERE work_item_id=?", (work_item_id,)
|
||||
).fetchone()
|
||||
conn.close()
|
||||
if row:
|
||||
branch, repo = row
|
||||
msg += chr(10) + "📂 Branch: [" + branch + "](" + gitea_base + "/admin/" + repo + "/src/branch/" + branch + ")"
|
||||
if new_stage in ("review", "testing", "deploy"):
|
||||
import httpx as _httpx
|
||||
from .config import settings
|
||||
_headers = {"Authorization": f"token {settings.gitea_token}"}
|
||||
_resp = _httpx.get(
|
||||
f"{settings.gitea_url}/api/v1/repos/{settings.gitea_owner}/{repo}/pulls",
|
||||
params={"state": "open", "head": branch},
|
||||
headers=_headers, timeout=5
|
||||
)
|
||||
if _resp.status_code == 200:
|
||||
_prs = _resp.json()
|
||||
if _prs:
|
||||
pr_num = _prs[0]["number"]
|
||||
msg += chr(10) + "🔗 PR: [#" + str(pr_num) + "](" + gitea_base + "/admin/" + repo + "/pulls/" + str(pr_num) + ")"
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
add_comment(work_item_id, msg)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user