kin: KIN-016 Агенты должны уметь говорить 'не могу'. Если агент не может выполнить задачу (нет доступа, не понимает, выходит за компетенцию) — он должен вернуть status: blocked с причиной, а не пытаться угадывать. PM при получении blocked от агента — эскалирует к человеку через GUI (уведомление) и Telegram (когда будет).

This commit is contained in:
Gros Frumos 2026-03-16 09:13:34 +02:00
parent a605e9d110
commit d9172fc17c
35 changed files with 2375 additions and 23 deletions

View file

@ -72,14 +72,22 @@ def create_project(
forgejo_repo: str | None = None,
language: str = "ru",
execution_mode: str = "review",
project_type: str = "development",
ssh_host: str | None = None,
ssh_user: str | None = None,
ssh_key_path: str | None = None,
ssh_proxy_jump: str | None = None,
description: str | None = None,
) -> dict:
"""Create a new project and return it as dict."""
conn.execute(
"""INSERT INTO projects (id, name, path, tech_stack, status, priority,
pm_prompt, claude_md_path, forgejo_repo, language, execution_mode)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
pm_prompt, claude_md_path, forgejo_repo, language, execution_mode,
project_type, ssh_host, ssh_user, ssh_key_path, ssh_proxy_jump, description)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
(id, name, path, _json_encode(tech_stack), status, priority,
pm_prompt, claude_md_path, forgejo_repo, language, execution_mode),
pm_prompt, claude_md_path, forgejo_repo, language, execution_mode,
project_type, ssh_host, ssh_user, ssh_key_path, ssh_proxy_jump, description),
)
conn.commit()
return get_project(conn, id)
@ -612,3 +620,55 @@ def get_cost_summary(conn: sqlite3.Connection, days: int = 7) -> list[dict]:
ORDER BY total_cost_usd DESC
""", (f"-{days} days",)).fetchall()
return _rows_to_list(rows)
# ---------------------------------------------------------------------------
# Project Phases (KIN-059)
# ---------------------------------------------------------------------------
def create_phase(
conn: sqlite3.Connection,
project_id: str,
role: str,
phase_order: int,
) -> dict:
"""Create a research phase for a project."""
cur = conn.execute(
"""INSERT INTO project_phases (project_id, role, phase_order, status)
VALUES (?, ?, ?, 'pending')""",
(project_id, role, phase_order),
)
conn.commit()
row = conn.execute(
"SELECT * FROM project_phases WHERE id = ?", (cur.lastrowid,)
).fetchone()
return _row_to_dict(row)
def get_phase(conn: sqlite3.Connection, phase_id: int) -> dict | None:
"""Get a project phase by id."""
row = conn.execute(
"SELECT * FROM project_phases WHERE id = ?", (phase_id,)
).fetchone()
return _row_to_dict(row)
def list_phases(conn: sqlite3.Connection, project_id: str) -> list[dict]:
"""List all phases for a project ordered by phase_order."""
rows = conn.execute(
"SELECT * FROM project_phases WHERE project_id = ? ORDER BY phase_order",
(project_id,),
).fetchall()
return _rows_to_list(rows)
def update_phase(conn: sqlite3.Connection, phase_id: int, **fields) -> dict:
"""Update phase fields. Auto-sets updated_at."""
if not fields:
return get_phase(conn, phase_id)
fields["updated_at"] = datetime.now().isoformat()
sets = ", ".join(f"{k} = ?" for k in fields)
vals = list(fields.values()) + [phase_id]
conn.execute(f"UPDATE project_phases SET {sets} WHERE id = ?", vals)
conn.commit()
return get_phase(conn, phase_id)