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})
|
out({"ok": True, "count": len(rows), "rows": rows})
|
||||||
|
|
||||||
|
|
||||||
def cmd_show(conn, ticket_id):
|
def cmd_show(conn, ids, brief=False):
|
||||||
rows = query(conn, """SELECT t.*, p.title as parent_title
|
tickets = []
|
||||||
FROM tickets t LEFT JOIN tickets p ON t.parent_id = p.id
|
for ticket_id in ids:
|
||||||
WHERE t.id = ?""", (ticket_id,))
|
rows = query(conn, """SELECT t.*, p.title as parent_title
|
||||||
if not rows:
|
FROM tickets t LEFT JOIN tickets p ON t.parent_id = p.id
|
||||||
out({"ok": False, "error": f"Ticket #{ticket_id} not found"})
|
WHERE t.id = ?""", (ticket_id,))
|
||||||
return
|
if not rows:
|
||||||
ticket = rows[0]
|
tickets.append({"id": ticket_id, "error": f"Ticket #{ticket_id} not found"})
|
||||||
# Get children
|
continue
|
||||||
children = query(conn, "SELECT id, title, status, priority FROM tickets WHERE parent_id = ? ORDER BY id", (ticket_id,))
|
ticket = rows[0]
|
||||||
# Get dependencies (what blocks this)
|
# Get children
|
||||||
blockers = query(conn, """SELECT t.id, t.title, t.status FROM ticket_deps d
|
children = query(conn, "SELECT id, title, status, priority FROM tickets WHERE parent_id = ? ORDER BY id", (ticket_id,))
|
||||||
JOIN tickets t ON d.blocker_id = t.id
|
# Get dependencies (what blocks this)
|
||||||
WHERE d.blocked_id = ?""", (ticket_id,))
|
blockers = query(conn, """SELECT t.id, t.title, t.status FROM ticket_deps d
|
||||||
# Get dependents (what this blocks)
|
JOIN tickets t ON d.blocker_id = t.id
|
||||||
blocks = query(conn, """SELECT t.id, t.title, t.status FROM ticket_deps d
|
WHERE d.blocked_id = ?""", (ticket_id,))
|
||||||
JOIN tickets t ON d.blocked_id = t.id
|
# Get dependents (what this blocks)
|
||||||
WHERE d.blocker_id = ?""", (ticket_id,))
|
blocks = query(conn, """SELECT t.id, t.title, t.status FROM ticket_deps d
|
||||||
ticket["children"] = children
|
JOIN tickets t ON d.blocked_id = t.id
|
||||||
ticket["blocked_by"] = blockers
|
WHERE d.blocker_id = ?""", (ticket_id,))
|
||||||
ticket["blocks"] = blocks
|
ticket["children"] = children
|
||||||
out({"ok": True, "ticket": ticket})
|
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):
|
def cmd_done(conn, ids):
|
||||||
@@ -286,7 +330,7 @@ HELP = """ticket — project ticket CLI
|
|||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
ticket list [--status S] [--priority P] [--epic N] [--sprint N] [--assigned A]
|
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 done <id> [<id> ...] Mark tickets as done
|
||||||
ticket status <id> <new_status> Change ticket status
|
ticket status <id> <new_status> Change ticket status
|
||||||
ticket assign <id> <agent> Assign ticket to agent/branch
|
ticket assign <id> <agent> Assign ticket to agent/branch
|
||||||
@@ -320,10 +364,12 @@ def main():
|
|||||||
if cmd == "list":
|
if cmd == "list":
|
||||||
cmd_list(conn, args)
|
cmd_list(conn, args)
|
||||||
elif cmd == "show":
|
elif cmd == "show":
|
||||||
if not args:
|
brief = "--brief" in args
|
||||||
out({"ok": False, "error": "Usage: ticket show <id>"})
|
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:
|
else:
|
||||||
cmd_show(conn, int(args[0]))
|
cmd_show(conn, [int(a) for a in id_args], brief=brief)
|
||||||
elif cmd == "done":
|
elif cmd == "done":
|
||||||
if not args:
|
if not args:
|
||||||
out({"ok": False, "error": "Usage: ticket done <id> [<id> ...]"})
|
out({"ok": False, "error": "Usage: ticket done <id> [<id> ...]"})
|
||||||
|
|||||||
Reference in New Issue
Block a user