kin: KIN-049 Кнопка Deploy на странице задачи после approve. Для каждого проекта настраивается deploy-команда (git push, scp, ssh restart). В Settings проекта.
This commit is contained in:
parent
860ef3f6c9
commit
d50bd703ae
11 changed files with 517 additions and 61 deletions
10
core/db.py
10
core/db.py
|
|
@ -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'"
|
||||
|
|
|
|||
|
|
@ -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"]
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue