feat(db): auto-close picked-up tickets on sprint stop

sprint stop now marks in_progress/review tickets as done
automatically — agents often forget to update status after merging.
Backlog/ready tickets are left as carry-over candidates since they
were never started.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-18 11:56:46 +01:00
co-authored by Claude Opus 4.6
parent a91f042d2d
commit 9728eb5d1d
+24 -2
View File
@@ -358,7 +358,25 @@ def cmd_stop(args):
cancelled = [t for t in tickets if t["status"] == "cancelled"]
incomplete = [t for t in tickets if t["status"] not in ("done", "cancelled")]
# Complete
# Auto-close: mark picked-up tickets (in_progress, review) as done.
# Work merged to main before sprint close means the ticket is done —
# agents just forget to update status. Backlog/ready tickets were
# never started, so they stay as carry-over candidates.
picked_up_statuses = ("in_progress", "review")
picked_up = [t for t in incomplete if t["status"] in picked_up_statuses]
auto_closed = []
if picked_up:
conn = get_connection()
for t in picked_up:
conn.execute("UPDATE tickets SET status='done' WHERE id=?", (t["id"],))
auto_closed.append(t)
conn.commit()
conn.close()
# Move auto-closed into done count, remove from incomplete
done = done + auto_closed
incomplete = [t for t in incomplete if t["status"] not in picked_up_statuses]
# Complete the sprint
conn = get_connection()
conn.execute(
"UPDATE sprints SET status='completed', end_date=date('now') WHERE id=?",
@@ -372,10 +390,14 @@ def cmd_stop(args):
print(f"Done: {len(done)}/{len(tickets)}")
if cancelled:
print(f"Cancelled: {len(cancelled)}")
if auto_closed:
print(f"Auto-closed: {len(auto_closed)} tickets marked done on sprint close:")
for t in auto_closed:
print(f" #{t['id']}: {t['title']} ({t['status']} → done)")
print()
if incomplete:
print("Carry-over candidates (incomplete):")
print("Carry-over candidates (never started):")
format_ticket_table(incomplete)
print()