forked from Rockachopa/Timmy-time-dashboard
feat: add bug report ingestion pipeline with Forge dispatch (#99)
Replace the stub `handle_bug_report` handler with a real implementation that logs a decision trail and dispatches code_fix tasks to Forge for automated fixing. Add `POST /api/bugs/submit` endpoint and `timmy ingest-report` CLI command so AI test runners (Comet) can submit structured bug reports without manual copy-paste. - POST /api/bugs/submit: accepts JSON reports, creates bug_report tasks - timmy ingest-report: CLI for file/stdin JSON ingestion with --dry-run - handle_bug_report: logs decision trail to event_log, dispatches code_fix task to Forge with parent_task_id linking back to the bug - 18 TDD tests covering endpoint, handler, and CLI Co-authored-by: Alexander Payne <apayne@MM.local> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
committed by
GitHub
parent
2e92838033
commit
6e67c3b421
@@ -121,5 +121,104 @@ def down():
|
||||
subprocess.run(["docker", "compose", "down"], check=True)
|
||||
|
||||
|
||||
@app.command(name="ingest-report")
|
||||
def ingest_report(
|
||||
file: Optional[str] = typer.Argument(
|
||||
None, help="Path to JSON report file (reads stdin if omitted)",
|
||||
),
|
||||
dry_run: bool = typer.Option(
|
||||
False, "--dry-run", help="Validate report and show what would be created",
|
||||
),
|
||||
):
|
||||
"""Ingest a structured test report and create bug_report tasks.
|
||||
|
||||
Reads a JSON report with an array of bugs and creates one task per bug
|
||||
in the internal task queue. The task processor will then attempt to
|
||||
create GitHub Issues for each.
|
||||
|
||||
Examples:
|
||||
timmy ingest-report report.json
|
||||
timmy ingest-report --dry-run report.json
|
||||
cat report.json | timmy ingest-report
|
||||
"""
|
||||
import json
|
||||
import sys
|
||||
|
||||
# Read input
|
||||
if file:
|
||||
try:
|
||||
with open(file) as f:
|
||||
raw = f.read()
|
||||
except FileNotFoundError:
|
||||
typer.echo(f"File not found: {file}", err=True)
|
||||
raise typer.Exit(1)
|
||||
else:
|
||||
if sys.stdin.isatty():
|
||||
typer.echo("Reading from stdin (paste JSON, then Ctrl+D)...")
|
||||
raw = sys.stdin.read()
|
||||
|
||||
# Parse JSON
|
||||
try:
|
||||
data = json.loads(raw)
|
||||
except json.JSONDecodeError as exc:
|
||||
typer.echo(f"Invalid JSON: {exc}", err=True)
|
||||
raise typer.Exit(1)
|
||||
|
||||
reporter = data.get("reporter", "unknown")
|
||||
bugs = data.get("bugs", [])
|
||||
|
||||
if not bugs:
|
||||
typer.echo("No bugs in report.", err=True)
|
||||
raise typer.Exit(1)
|
||||
|
||||
typer.echo(f"Report: {len(bugs)} bug(s) from {reporter}")
|
||||
|
||||
if dry_run:
|
||||
for bug in bugs:
|
||||
typer.echo(f" [{bug.get('severity', '?')}] {bug.get('title', '(no title)')}")
|
||||
typer.echo("(dry run — no tasks created)")
|
||||
return
|
||||
|
||||
# Import and create tasks
|
||||
from swarm.task_queue.models import create_task
|
||||
|
||||
severity_map = {"P0": "urgent", "P1": "high", "P2": "normal"}
|
||||
created = 0
|
||||
for bug in bugs:
|
||||
title = bug.get("title", "")
|
||||
severity = bug.get("severity", "P2")
|
||||
description = bug.get("description", "")
|
||||
|
||||
if not title or not description:
|
||||
typer.echo(f" SKIP (missing title or description)")
|
||||
continue
|
||||
|
||||
# Format description with extra fields
|
||||
parts = [f"**Reporter:** {reporter}", f"**Severity:** {severity}", "", description]
|
||||
if bug.get("evidence"):
|
||||
parts += ["", "## Evidence", bug["evidence"]]
|
||||
if bug.get("root_cause"):
|
||||
parts += ["", "## Root Cause", bug["root_cause"]]
|
||||
if bug.get("fix_options"):
|
||||
parts += ["", "## Fix Options"]
|
||||
for i, fix in enumerate(bug["fix_options"], 1):
|
||||
parts.append(f"{i}. {fix}")
|
||||
|
||||
task = create_task(
|
||||
title=f"[{severity}] {title}",
|
||||
description="\n".join(parts),
|
||||
task_type="bug_report",
|
||||
assigned_to="timmy",
|
||||
created_by=reporter,
|
||||
priority=severity_map.get(severity, "normal"),
|
||||
requires_approval=False,
|
||||
auto_approve=True,
|
||||
)
|
||||
typer.echo(f" OK [{severity}] {title} → {task.id}")
|
||||
created += 1
|
||||
|
||||
typer.echo(f"\n{created} task(s) created.")
|
||||
|
||||
|
||||
def main():
|
||||
app()
|
||||
|
||||
Reference in New Issue
Block a user