kin: auto-commit after pipeline
This commit is contained in:
parent
04cbbc563b
commit
b6f40a6ace
9 changed files with 1690 additions and 16 deletions
|
|
@ -65,9 +65,11 @@ def build_context(
|
|||
specs = _load_specialists()
|
||||
ctx["available_specialists"] = list(specs.get("specialists", {}).keys())
|
||||
ctx["routes"] = specs.get("routes", {})
|
||||
ctx["departments"] = specs.get("departments", {})
|
||||
except Exception:
|
||||
ctx["available_specialists"] = []
|
||||
ctx["routes"] = {}
|
||||
ctx["departments"] = {}
|
||||
|
||||
elif role == "architect":
|
||||
ctx["modules"] = models.get_modules(conn, project_id)
|
||||
|
|
@ -111,6 +113,41 @@ def build_context(
|
|||
conn, project_id, category="security",
|
||||
)
|
||||
|
||||
elif role.endswith("_head"):
|
||||
# Department head: load department config and previous handoff
|
||||
ctx["decisions"] = models.get_decisions(conn, project_id)
|
||||
ctx["modules"] = models.get_modules(conn, project_id)
|
||||
try:
|
||||
specs = _load_specialists()
|
||||
all_specs = specs.get("specialists", {})
|
||||
departments = specs.get("departments", {})
|
||||
spec = all_specs.get(role, {})
|
||||
dept_name = spec.get("department", "")
|
||||
dept_info = departments.get(dept_name, {})
|
||||
ctx["department"] = dept_name
|
||||
ctx["department_workers"] = dept_info.get("workers", [])
|
||||
ctx["department_description"] = dept_info.get("description", "")
|
||||
except Exception:
|
||||
ctx["department"] = ""
|
||||
ctx["department_workers"] = []
|
||||
ctx["department_description"] = ""
|
||||
# Previous handoff from another department (if any)
|
||||
try:
|
||||
dept = ctx.get("department")
|
||||
last_handoff = models.get_last_handoff(conn, task_id, to_department=dept)
|
||||
# Fallback: get latest handoff NOT from our own department
|
||||
# (avoids picking up our own outgoing handoff)
|
||||
if not last_handoff and dept:
|
||||
all_handoffs = models.get_handoffs_for_task(conn, task_id)
|
||||
for h in reversed(all_handoffs):
|
||||
if h.get("from_department") != dept:
|
||||
last_handoff = h
|
||||
break
|
||||
if last_handoff:
|
||||
ctx["incoming_handoff"] = last_handoff
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
else:
|
||||
# Unknown role — give decisions as fallback
|
||||
ctx["decisions"] = models.get_decisions(conn, project_id, limit=20)
|
||||
|
|
@ -175,6 +212,13 @@ def format_prompt(context: dict, role: str, prompt_template: str | None = None)
|
|||
prompt_path = PROMPTS_DIR / f"{role}.md"
|
||||
if prompt_path.exists():
|
||||
prompt_template = prompt_path.read_text()
|
||||
elif role.endswith("_head"):
|
||||
# Fallback: all department heads share the base department_head.md prompt
|
||||
dept_head_path = PROMPTS_DIR / "department_head.md"
|
||||
if dept_head_path.exists():
|
||||
prompt_template = dept_head_path.read_text()
|
||||
else:
|
||||
prompt_template = f"You are a {role}. Complete the task described below."
|
||||
else:
|
||||
prompt_template = f"You are a {role}. Complete the task described below."
|
||||
|
||||
|
|
@ -265,6 +309,22 @@ def format_prompt(context: dict, role: str, prompt_template: str | None = None)
|
|||
sections.append(f"- {name}: {steps}")
|
||||
sections.append("")
|
||||
|
||||
# Department context (department heads)
|
||||
dept = context.get("department")
|
||||
if dept:
|
||||
dept_desc = context.get("department_description", "")
|
||||
sections.append(f"## Department: {dept}" + (f" — {dept_desc}" if dept_desc else ""))
|
||||
sections.append("")
|
||||
dept_workers = context.get("department_workers")
|
||||
if dept_workers:
|
||||
sections.append(f"## Department workers: {', '.join(dept_workers)}")
|
||||
sections.append("")
|
||||
incoming_handoff = context.get("incoming_handoff")
|
||||
if incoming_handoff:
|
||||
sections.append("## Incoming handoff from previous department:")
|
||||
sections.append(json.dumps(incoming_handoff, ensure_ascii=False))
|
||||
sections.append("")
|
||||
|
||||
# Module hint (debugger)
|
||||
hint = context.get("module_hint")
|
||||
if hint:
|
||||
|
|
|
|||
48
core/db.py
48
core/db.py
|
|
@ -140,10 +140,29 @@ CREATE TABLE IF NOT EXISTS pipelines (
|
|||
total_cost_usd REAL,
|
||||
total_tokens INTEGER,
|
||||
total_duration_seconds INTEGER,
|
||||
parent_pipeline_id INTEGER REFERENCES pipelines(id),
|
||||
department TEXT,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
completed_at DATETIME
|
||||
);
|
||||
|
||||
-- Межотдельные handoff-ы (KIN-098)
|
||||
CREATE TABLE IF NOT EXISTS department_handoffs (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
pipeline_id INTEGER NOT NULL REFERENCES pipelines(id),
|
||||
task_id TEXT NOT NULL REFERENCES tasks(id),
|
||||
from_department TEXT NOT NULL,
|
||||
to_department TEXT,
|
||||
artifacts JSON,
|
||||
decisions_made JSON,
|
||||
blockers JSON,
|
||||
status TEXT DEFAULT 'pending',
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_handoffs_pipeline ON department_handoffs(pipeline_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_handoffs_task ON department_handoffs(task_id);
|
||||
|
||||
-- Post-pipeline хуки
|
||||
CREATE TABLE IF NOT EXISTS hooks (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
|
|
@ -591,6 +610,35 @@ def _migrate(conn: sqlite3.Connection):
|
|||
""")
|
||||
conn.commit()
|
||||
|
||||
# Migrate pipelines: add parent_pipeline_id and department columns (KIN-098)
|
||||
pipeline_cols = {r[1] for r in conn.execute("PRAGMA table_info(pipelines)").fetchall()}
|
||||
if "parent_pipeline_id" not in pipeline_cols:
|
||||
conn.execute("ALTER TABLE pipelines ADD COLUMN parent_pipeline_id INTEGER REFERENCES pipelines(id)")
|
||||
conn.commit()
|
||||
if "department" not in pipeline_cols:
|
||||
conn.execute("ALTER TABLE pipelines ADD COLUMN department TEXT")
|
||||
conn.commit()
|
||||
|
||||
# Create department_handoffs table (KIN-098)
|
||||
if "department_handoffs" not in existing_tables:
|
||||
conn.executescript("""
|
||||
CREATE TABLE IF NOT EXISTS department_handoffs (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
pipeline_id INTEGER NOT NULL REFERENCES pipelines(id),
|
||||
task_id TEXT NOT NULL REFERENCES tasks(id),
|
||||
from_department TEXT NOT NULL,
|
||||
to_department TEXT,
|
||||
artifacts JSON,
|
||||
decisions_made JSON,
|
||||
blockers JSON,
|
||||
status TEXT DEFAULT 'pending',
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_handoffs_pipeline ON department_handoffs(pipeline_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_handoffs_task ON department_handoffs(task_id);
|
||||
""")
|
||||
conn.commit()
|
||||
|
||||
# Rename legacy 'auto' → 'auto_complete' (KIN-063)
|
||||
conn.execute(
|
||||
"UPDATE projects SET execution_mode = 'auto_complete' WHERE execution_mode = 'auto'"
|
||||
|
|
|
|||
|
|
@ -473,12 +473,14 @@ def create_pipeline(
|
|||
project_id: str,
|
||||
route_type: str,
|
||||
steps: list | dict,
|
||||
parent_pipeline_id: int | None = None,
|
||||
department: str | None = None,
|
||||
) -> dict:
|
||||
"""Create a new pipeline run."""
|
||||
cur = conn.execute(
|
||||
"""INSERT INTO pipelines (task_id, project_id, route_type, steps)
|
||||
VALUES (?, ?, ?, ?)""",
|
||||
(task_id, project_id, route_type, _json_encode(steps)),
|
||||
"""INSERT INTO pipelines (task_id, project_id, route_type, steps, parent_pipeline_id, department)
|
||||
VALUES (?, ?, ?, ?, ?, ?)""",
|
||||
(task_id, project_id, route_type, _json_encode(steps), parent_pipeline_id, department),
|
||||
)
|
||||
conn.commit()
|
||||
row = conn.execute(
|
||||
|
|
@ -923,6 +925,68 @@ def delete_attachment(conn: sqlite3.Connection, attachment_id: int) -> bool:
|
|||
return cur.rowcount > 0
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Department Handoffs (KIN-098)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def create_handoff(
|
||||
conn: sqlite3.Connection,
|
||||
pipeline_id: int,
|
||||
task_id: str,
|
||||
from_department: str,
|
||||
to_department: str | None = None,
|
||||
artifacts: dict | None = None,
|
||||
decisions_made: list | None = None,
|
||||
blockers: list | None = None,
|
||||
status: str = "pending",
|
||||
) -> dict:
|
||||
"""Record a department handoff with artifacts for inter-department context."""
|
||||
cur = conn.execute(
|
||||
"""INSERT INTO department_handoffs
|
||||
(pipeline_id, task_id, from_department, to_department, artifacts, decisions_made, blockers, status)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)""",
|
||||
(pipeline_id, task_id, from_department, to_department,
|
||||
_json_encode(artifacts), _json_encode(decisions_made), _json_encode(blockers), status),
|
||||
)
|
||||
conn.commit()
|
||||
row = conn.execute(
|
||||
"SELECT * FROM department_handoffs WHERE id = ?", (cur.lastrowid,)
|
||||
).fetchone()
|
||||
return _row_to_dict(row)
|
||||
|
||||
|
||||
def get_handoffs_for_task(conn: sqlite3.Connection, task_id: str) -> list[dict]:
|
||||
"""Get all handoffs for a task ordered by creation time."""
|
||||
rows = conn.execute(
|
||||
"SELECT * FROM department_handoffs WHERE task_id = ? ORDER BY created_at",
|
||||
(task_id,),
|
||||
).fetchall()
|
||||
return _rows_to_list(rows)
|
||||
|
||||
|
||||
def get_last_handoff(
|
||||
conn: sqlite3.Connection,
|
||||
task_id: str,
|
||||
to_department: str | None = None,
|
||||
) -> dict | None:
|
||||
"""Get the most recent handoff for a task, optionally filtered by destination department."""
|
||||
if to_department:
|
||||
row = conn.execute(
|
||||
"""SELECT * FROM department_handoffs
|
||||
WHERE task_id = ? AND to_department = ?
|
||||
ORDER BY created_at DESC LIMIT 1""",
|
||||
(task_id, to_department),
|
||||
).fetchone()
|
||||
else:
|
||||
row = conn.execute(
|
||||
"""SELECT * FROM department_handoffs
|
||||
WHERE task_id = ?
|
||||
ORDER BY created_at DESC LIMIT 1""",
|
||||
(task_id,),
|
||||
).fetchone()
|
||||
return _row_to_dict(row)
|
||||
|
||||
|
||||
def get_chat_messages(
|
||||
conn: sqlite3.Connection,
|
||||
project_id: str,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue