fix(kanban): preserve dashboard completion summaries

This commit is contained in:
LeonSGP43 2026-05-05 11:07:13 +08:00 committed by Teknium
parent cca8587d35
commit 354502ee48
6 changed files with 236 additions and 4 deletions

View file

@ -343,6 +343,27 @@ def build_parser(parent_subparsers: argparse._SubParsersAction) -> argparse.Argu
help='JSON dict of structured facts (e.g. \'{"changed_files": [...], '
'"tests_run": 12}\'). Stored on the closing run.')
p_edit = sub.add_parser(
"edit",
help="Edit recovery fields on an already-completed task",
)
p_edit.add_argument("task_id")
p_edit.add_argument(
"--result",
required=True,
help="Backfilled task result text for a done task",
)
p_edit.add_argument(
"--summary",
default=None,
help="Structured handoff summary. Falls back to --result if omitted.",
)
p_edit.add_argument(
"--metadata",
default=None,
help="JSON dict of structured facts to store on the latest completed run.",
)
p_block = sub.add_parser("block", help="Mark one or more tasks blocked")
p_block.add_argument("task_id")
p_block.add_argument("reason", nargs="*", help="Reason (also appended as a comment)")
@ -581,6 +602,7 @@ def kanban_command(args: argparse.Namespace) -> int:
"claim": _cmd_claim,
"comment": _cmd_comment,
"complete": _cmd_complete,
"edit": _cmd_edit,
"block": _cmd_block,
"unblock": _cmd_unblock,
"archive": _cmd_archive,
@ -1187,6 +1209,34 @@ def _cmd_complete(args: argparse.Namespace) -> int:
return 0 if not failed else 1
def _cmd_edit(args: argparse.Namespace) -> int:
raw_meta = getattr(args, "metadata", None)
metadata = None
if raw_meta:
try:
metadata = json.loads(raw_meta)
if not isinstance(metadata, dict):
raise ValueError("must be a JSON object")
except (ValueError, json.JSONDecodeError) as exc:
print(f"kanban: --metadata: {exc}", file=sys.stderr)
return 2
with kb.connect() as conn:
if not kb.edit_completed_task_result(
conn,
args.task_id,
result=args.result,
summary=getattr(args, "summary", None),
metadata=metadata,
):
print(
f"cannot edit {args.task_id} (unknown id or task is not done)",
file=sys.stderr,
)
return 1
print(f"Edited {args.task_id}")
return 0
def _cmd_block(args: argparse.Namespace) -> int:
reason = " ".join(args.reason).strip() if args.reason else None
author = _profile_author()

View file

@ -1917,6 +1917,73 @@ def complete_task(
return True
def edit_completed_task_result(
conn: sqlite3.Connection,
task_id: str,
*,
result: str,
summary: Optional[str] = None,
metadata: Optional[dict] = None,
) -> bool:
"""Backfill the user-visible result for an already completed task."""
handoff_summary = summary if summary is not None else result
with write_txn(conn):
row = conn.execute(
"SELECT status FROM tasks WHERE id = ?", (task_id,),
).fetchone()
if not row or row["status"] != "done":
return False
conn.execute(
"UPDATE tasks SET result = ? WHERE id = ?",
(result, task_id),
)
run = conn.execute(
"""
SELECT id FROM task_runs
WHERE task_id = ?
AND outcome = 'completed'
ORDER BY COALESCE(ended_at, started_at, 0) DESC, id DESC
LIMIT 1
""",
(task_id,),
).fetchone()
run_id = int(run["id"]) if run else None
if run_id is None:
run_id = _synthesize_ended_run(
conn, task_id,
outcome="completed",
summary=handoff_summary,
metadata=metadata,
)
else:
conn.execute(
"UPDATE task_runs SET summary = ? WHERE id = ?",
(handoff_summary, run_id),
)
if metadata is not None:
conn.execute(
"UPDATE task_runs SET metadata = ? WHERE id = ?",
(json.dumps(metadata, ensure_ascii=False), run_id),
)
ev_summary = (
handoff_summary.strip().splitlines()[0][:400]
if handoff_summary else ""
)
_append_event(
conn, task_id, "edited",
{
"fields": (
["result", "summary"]
+ (["metadata"] if metadata is not None else [])
),
"result_len": len(result) if result else 0,
"summary": ev_summary or None,
},
run_id=run_id,
)
return True
def block_task(
conn: sqlite3.Connection,
task_id: str,