kin: KIN-049 Кнопка Deploy на странице задачи после approve. Для каждого проекта настраивается deploy-команда (git push, scp, ssh restart). В Settings проекта.

This commit is contained in:
Gros Frumos 2026-03-16 08:21:13 +02:00
parent 860ef3f6c9
commit d50bd703ae
11 changed files with 517 additions and 61 deletions

View file

@ -22,6 +22,7 @@ CREATE TABLE IF NOT EXISTS projects (
forgejo_repo TEXT,
language TEXT DEFAULT 'ru',
execution_mode TEXT NOT NULL DEFAULT 'review',
deploy_command TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
@ -44,6 +45,7 @@ CREATE TABLE IF NOT EXISTS tasks (
blocked_reason TEXT,
dangerously_skipped BOOLEAN DEFAULT 0,
revise_comment TEXT,
category TEXT DEFAULT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
@ -244,10 +246,18 @@ def _migrate(conn: sqlite3.Connection):
conn.execute("ALTER TABLE tasks ADD COLUMN revise_comment TEXT")
conn.commit()
if "category" not in task_cols:
conn.execute("ALTER TABLE tasks ADD COLUMN category TEXT DEFAULT NULL")
conn.commit()
if "obsidian_vault_path" not in proj_cols:
conn.execute("ALTER TABLE projects ADD COLUMN obsidian_vault_path TEXT")
conn.commit()
if "deploy_command" not in proj_cols:
conn.execute("ALTER TABLE projects ADD COLUMN deploy_command TEXT")
conn.commit()
# Migrate audit_log table (KIN-021)
existing_tables = {r[0] for r in conn.execute(
"SELECT name FROM sqlite_master WHERE type='table'"

View file

@ -48,21 +48,6 @@ def _collect_pipeline_output(conn: sqlite3.Connection, task_id: str) -> str:
return "\n".join(parts)
def _next_task_id(conn: sqlite3.Connection, project_id: str) -> str:
"""Generate the next sequential task ID for a project."""
prefix = project_id.upper()
existing = models.list_tasks(conn, project_id=project_id)
max_num = 0
for t in existing:
tid = t["id"]
if tid.startswith(prefix + "-"):
try:
num = int(tid.split("-", 1)[1])
max_num = max(max_num, num)
except ValueError:
pass
return f"{prefix}-{max_num + 1:03d}"
def generate_followups(
conn: sqlite3.Connection,
@ -154,7 +139,7 @@ def generate_followups(
"options": ["rerun", "manual_task", "skip"],
})
else:
new_id = _next_task_id(conn, project_id)
new_id = models.next_task_id(conn, project_id)
brief_dict = {"source": f"followup:{task_id}"}
if item.get("type"):
brief_dict["route_type"] = item["type"]
@ -206,7 +191,7 @@ def resolve_pending_action(
return None
if choice == "manual_task":
new_id = _next_task_id(conn, project_id)
new_id = models.next_task_id(conn, project_id)
brief_dict = {"source": f"followup:{task_id}", "task_type": "manual_escalation"}
if item.get("type"):
brief_dict["route_type"] = item["type"]

View file

@ -16,6 +16,11 @@ VALID_TASK_STATUSES = [
VALID_COMPLETION_MODES = {"auto_complete", "review"}
TASK_CATEGORIES = [
"SEC", "UI", "API", "INFRA", "BIZ", "DB",
"ARCH", "TEST", "PERF", "DOCS", "FIX", "OBS",
]
def validate_completion_mode(value: str) -> str:
"""Validate completion mode from LLM output. Falls back to 'review' if invalid."""
@ -132,6 +137,44 @@ def update_project(conn: sqlite3.Connection, id: str, **fields) -> dict:
# Tasks
# ---------------------------------------------------------------------------
def next_task_id(
conn: sqlite3.Connection,
project_id: str,
category: str | None = None,
) -> str:
"""Generate next task ID.
Without category: PROJ-001 (backward-compatible old format)
With category: PROJ-CAT-001 (new format, per-category counter)
"""
prefix = project_id.upper()
existing = list_tasks(conn, project_id=project_id)
if category:
cat_prefix = f"{prefix}-{category}-"
max_num = 0
for t in existing:
tid = t["id"]
if tid.startswith(cat_prefix):
try:
max_num = max(max_num, int(tid[len(cat_prefix):]))
except ValueError:
pass
return f"{prefix}-{category}-{max_num + 1:03d}"
else:
# Old format: global max across project (integers only, skip CAT-NNN)
max_num = 0
for t in existing:
tid = t["id"]
if tid.startswith(prefix + "-"):
suffix = tid[len(prefix) + 1:]
try:
max_num = max(max_num, int(suffix))
except ValueError:
pass
return f"{prefix}-{max_num + 1:03d}"
def create_task(
conn: sqlite3.Connection,
id: str,
@ -145,16 +188,17 @@ def create_task(
spec: dict | None = None,
forgejo_issue_id: int | None = None,
execution_mode: str | None = None,
category: str | None = None,
) -> dict:
"""Create a task linked to a project."""
conn.execute(
"""INSERT INTO tasks (id, project_id, title, status, priority,
assigned_role, parent_task_id, brief, spec, forgejo_issue_id,
execution_mode)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
execution_mode, category)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
(id, project_id, title, status, priority, assigned_role,
parent_task_id, _json_encode(brief), _json_encode(spec),
forgejo_issue_id, execution_mode),
forgejo_issue_id, execution_mode, category),
)
conn.commit()
return get_task(conn, id)