feat(db): add multi-ID show and --brief flag to ticket CLI
ticket show now accepts multiple IDs (e.g. ticket show 110 111 112). --brief flag outputs compact human-readable summaries instead of JSON, useful for sprint planning scans. Single-ID JSON output unchanged. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+72
-26
@@ -141,28 +141,72 @@ def cmd_list(conn, args):
|
||||
out({"ok": True, "count": len(rows), "rows": rows})
|
||||
|
||||
|
||||
def cmd_show(conn, ticket_id):
|
||||
rows = query(conn, """SELECT t.*, p.title as parent_title
|
||||
FROM tickets t LEFT JOIN tickets p ON t.parent_id = p.id
|
||||
WHERE t.id = ?""", (ticket_id,))
|
||||
if not rows:
|
||||
out({"ok": False, "error": f"Ticket #{ticket_id} not found"})
|
||||
return
|
||||
ticket = rows[0]
|
||||
# Get children
|
||||
children = query(conn, "SELECT id, title, status, priority FROM tickets WHERE parent_id = ? ORDER BY id", (ticket_id,))
|
||||
# Get dependencies (what blocks this)
|
||||
blockers = query(conn, """SELECT t.id, t.title, t.status FROM ticket_deps d
|
||||
JOIN tickets t ON d.blocker_id = t.id
|
||||
WHERE d.blocked_id = ?""", (ticket_id,))
|
||||
# Get dependents (what this blocks)
|
||||
blocks = query(conn, """SELECT t.id, t.title, t.status FROM ticket_deps d
|
||||
JOIN tickets t ON d.blocked_id = t.id
|
||||
WHERE d.blocker_id = ?""", (ticket_id,))
|
||||
ticket["children"] = children
|
||||
ticket["blocked_by"] = blockers
|
||||
ticket["blocks"] = blocks
|
||||
out({"ok": True, "ticket": ticket})
|
||||
def cmd_show(conn, ids, brief=False):
|
||||
tickets = []
|
||||
for ticket_id in ids:
|
||||
rows = query(conn, """SELECT t.*, p.title as parent_title
|
||||
FROM tickets t LEFT JOIN tickets p ON t.parent_id = p.id
|
||||
WHERE t.id = ?""", (ticket_id,))
|
||||
if not rows:
|
||||
tickets.append({"id": ticket_id, "error": f"Ticket #{ticket_id} not found"})
|
||||
continue
|
||||
ticket = rows[0]
|
||||
# Get children
|
||||
children = query(conn, "SELECT id, title, status, priority FROM tickets WHERE parent_id = ? ORDER BY id", (ticket_id,))
|
||||
# Get dependencies (what blocks this)
|
||||
blockers = query(conn, """SELECT t.id, t.title, t.status FROM ticket_deps d
|
||||
JOIN tickets t ON d.blocker_id = t.id
|
||||
WHERE d.blocked_id = ?""", (ticket_id,))
|
||||
# Get dependents (what this blocks)
|
||||
blocks = query(conn, """SELECT t.id, t.title, t.status FROM ticket_deps d
|
||||
JOIN tickets t ON d.blocked_id = t.id
|
||||
WHERE d.blocker_id = ?""", (ticket_id,))
|
||||
ticket["children"] = children
|
||||
ticket["blocked_by"] = blockers
|
||||
ticket["blocks"] = blocks
|
||||
tickets.append(ticket)
|
||||
if brief:
|
||||
_print_brief(tickets)
|
||||
elif len(tickets) == 1:
|
||||
out({"ok": True, "ticket": tickets[0]})
|
||||
else:
|
||||
out({"ok": True, "count": len(tickets), "tickets": tickets})
|
||||
|
||||
|
||||
def _print_brief(tickets):
|
||||
for i, t in enumerate(tickets):
|
||||
if "error" in t:
|
||||
print(f"#{t['id']}: NOT FOUND")
|
||||
continue
|
||||
# Header line
|
||||
print(f"#{t['id']}: {t['title']}")
|
||||
# Metadata line
|
||||
parts = [f"{t['type']}", f"P:{t['priority']}", f"S:{t['status']}"]
|
||||
if t.get("assigned_to"):
|
||||
parts.append(f"@{t['assigned_to']}")
|
||||
if t.get("parent_id"):
|
||||
parts.append(f"Epic:#{t['parent_id']} ({t.get('parent_title', '?')})")
|
||||
if t.get("sprint_id"):
|
||||
parts.append(f"Sprint:{t['sprint_id']}")
|
||||
if t.get("decision_ref"):
|
||||
parts.append(f"Ref:{t['decision_ref']}")
|
||||
print(f" {' | '.join(parts)}")
|
||||
# Description
|
||||
desc = t.get("description") or ""
|
||||
if desc:
|
||||
# Truncate long descriptions
|
||||
if len(desc) > 200:
|
||||
desc = desc[:197] + "..."
|
||||
print(f" {desc}")
|
||||
# Dependencies
|
||||
if t.get("blocked_by"):
|
||||
blockers = ", ".join(f"#{b['id']} ({b['status']})" for b in t["blocked_by"])
|
||||
print(f" Blocked by: {blockers}")
|
||||
if t.get("blocks"):
|
||||
blocks = ", ".join(f"#{b['id']}" for b in t["blocks"])
|
||||
print(f" Blocks: {blocks}")
|
||||
if i < len(tickets) - 1:
|
||||
print()
|
||||
|
||||
|
||||
def cmd_done(conn, ids):
|
||||
@@ -286,7 +330,7 @@ HELP = """ticket — project ticket CLI
|
||||
|
||||
Usage:
|
||||
ticket list [--status S] [--priority P] [--epic N] [--sprint N] [--assigned A]
|
||||
ticket show <id> Full ticket detail with deps and children
|
||||
ticket show [--brief] <id> [<id>...] Full ticket detail (--brief for summary)
|
||||
ticket done <id> [<id> ...] Mark tickets as done
|
||||
ticket status <id> <new_status> Change ticket status
|
||||
ticket assign <id> <agent> Assign ticket to agent/branch
|
||||
@@ -320,10 +364,12 @@ def main():
|
||||
if cmd == "list":
|
||||
cmd_list(conn, args)
|
||||
elif cmd == "show":
|
||||
if not args:
|
||||
out({"ok": False, "error": "Usage: ticket show <id>"})
|
||||
brief = "--brief" in args
|
||||
id_args = [a for a in args if a != "--brief"]
|
||||
if not id_args:
|
||||
out({"ok": False, "error": "Usage: ticket show [--brief] <id> [<id> ...]"})
|
||||
else:
|
||||
cmd_show(conn, int(args[0]))
|
||||
cmd_show(conn, [int(a) for a in id_args], brief=brief)
|
||||
elif cmd == "done":
|
||||
if not args:
|
||||
out({"ok": False, "error": "Usage: ticket done <id> [<id> ...]"})
|
||||
|
||||
Reference in New Issue
Block a user