Add follow-up task generation on approve

When approving a task, PM agent analyzes pipeline output and creates
follow-up tasks automatically (e.g. security audit → 8 fix tasks).

core/followup.py:
  generate_followups() — collects pipeline output, runs followup agent,
  parses JSON task list, creates tasks with parent_task_id linkage.
  Handles: bare arrays, {tasks:[...]} wrappers, invalid JSON, empty.

agents/prompts/followup.md — PM prompt for analyzing results and
  creating actionable follow-up tasks with priority from severity.

CLI: kin approve <task_id> [--followup] [--decision "text"]
API: POST /api/tasks/{id}/approve {create_followups: true}
  Returns {status, decision, followup_tasks: [...]}

Frontend (TaskDetail approve modal):
  - Checkbox "Create follow-up tasks" (default ON)
  - Loading state during generation
  - Results view: list of created tasks with links to /task/:id

ProjectView: tasks show "from VDOL-001" for follow-ups.

13 new tests (followup), 125 total, all passing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
johnfrum1234 2026-03-15 15:02:58 +02:00
parent f7830d484c
commit 9264415776
8 changed files with 426 additions and 17 deletions

View file

@ -181,11 +181,14 @@ class TaskApprove(BaseModel):
decision_title: str | None = None
decision_description: str | None = None
decision_type: str = "decision"
create_followups: bool = False
@app.post("/api/tasks/{task_id}/approve")
def approve_task(task_id: str, body: TaskApprove | None = None):
"""Approve a task: set status=done, optionally add a decision."""
"""Approve a task: set status=done, optionally add decision and create follow-ups."""
from core.followup import generate_followups
conn = get_conn()
t = models.get_task(conn, task_id)
if not t:
@ -199,8 +202,15 @@ def approve_task(task_id: str, body: TaskApprove | None = None):
body.decision_title, body.decision_description or body.decision_title,
task_id=task_id,
)
followup_tasks = []
if body and body.create_followups:
followup_tasks = generate_followups(conn, task_id)
conn.close()
return {"status": "done", "decision": decision}
return {
"status": "done",
"decision": decision,
"followup_tasks": followup_tasks,
}
class TaskReject(BaseModel):