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

@ -410,6 +410,46 @@ def cost(ctx, period):
click.echo(f"\nTotal: ${total:.4f}")
# ===========================================================================
# approve
# ===========================================================================
@cli.command("approve")
@click.argument("task_id")
@click.option("--followup", is_flag=True, help="Generate follow-up tasks from pipeline results")
@click.option("--decision", "decision_text", default=None, help="Record a decision with this text")
@click.pass_context
def approve_task(ctx, task_id, followup, decision_text):
"""Approve a task (set status=done). Optionally generate follow-ups."""
from core.followup import generate_followups
conn = ctx.obj["conn"]
task = models.get_task(conn, task_id)
if not task:
click.echo(f"Task '{task_id}' not found.", err=True)
raise SystemExit(1)
models.update_task(conn, task_id, status="done")
click.echo(f"Approved: {task_id} → done")
if decision_text:
models.add_decision(
conn, task["project_id"], "decision", decision_text, decision_text,
task_id=task_id,
)
click.echo(f"Decision recorded.")
if followup:
click.echo("Generating follow-up tasks...")
created = generate_followups(conn, task_id)
if created:
click.echo(f"Created {len(created)} follow-up tasks:")
for t in created:
click.echo(f" {t['id']}: {t['title']} (pri {t['priority']})")
else:
click.echo("No follow-up tasks generated.")
# ===========================================================================
# run
# ===========================================================================