"""Review Queue CLI: list, show, and resolve review items.""" from __future__ import annotations import argparse import asyncio import json import sys from libs.common.config import get_settings from libs.common.logging import configure_logging, get_logger from libs.db.session import get_session from libs.review.queue import list_review_items, resolve_review_item logger = get_logger(__name__) def _fmt_item(item: object) -> str: """Format a ReviewItem for display.""" lines = [ f" review_id : {item.review_id}", # type: ignore[attr-defined] f" entity_type : {item.entity_type}", # type: ignore[attr-defined] f" entity_id : {item.entity_id}", # type: ignore[attr-defined] f" priority : {item.priority}", # type: ignore[attr-defined] f" status : {item.status}", # type: ignore[attr-defined] f" reasons : {item.reason_codes}", # type: ignore[attr-defined] f" created_at : {item.created_at_utc}", # type: ignore[attr-defined] ] reviewer_id = getattr(item, "reviewer_id", None) if reviewer_id: lines.append(f" reviewer : {reviewer_id}") if item.resolution_type: # type: ignore[attr-defined] lines.append(f" resolution : {item.resolution_type}") # type: ignore[attr-defined] lines.append(f" root_cause : {item.root_cause}") # type: ignore[attr-defined] lines.append(f" notes : {item.notes}") # type: ignore[attr-defined] return "\n".join(lines) async def cmd_list(args: argparse.Namespace) -> None: async with get_session() as session: items = await list_review_items( session, status=args.status, priority=getattr(args, "priority", None), ) if not items: print("No review items found.") return print(f"Found {len(items)} review item(s):\n") for item in items: print(f"[{item.priority}] {item.review_id}") # type: ignore[attr-defined] print(f" entity: {item.entity_type}/{item.entity_id}") # type: ignore[attr-defined] print(f" status: {item.status} | reasons: {item.reason_codes}") # type: ignore[attr-defined] print() async def cmd_show(args: argparse.Namespace) -> None: from sqlalchemy import select from libs.db.models import ReviewItem async with get_session() as session: import uuid result = await session.execute( select(ReviewItem).where(ReviewItem.review_id == uuid.UUID(args.review_id)) ) item = result.scalar_one_or_none() if item is None: print(f"Review item {args.review_id} not found.", file=sys.stderr) sys.exit(1) print(_fmt_item(item)) if item.snapshot_refs: # type: ignore[attr-defined] print(f" snapshot_refs: {json.dumps(item.snapshot_refs, indent=4)}") # type: ignore[attr-defined] if item.suggested_overrides: # type: ignore[attr-defined] print(f" suggested_overrides: {json.dumps(item.suggested_overrides, indent=4)}") # type: ignore[attr-defined] async def cmd_resolve(args: argparse.Namespace) -> None: async with get_session() as session: item = await resolve_review_item( session=session, review_id=args.review_id, reviewer_id=args.reviewer, resolution_type=args.resolution_type, root_cause=args.root_cause or "", notes=args.notes or "", field_overrides=json.loads(args.overrides) if args.overrides else None, ) await session.commit() print(f"Resolved review item {item.review_id} as '{item.resolution_type}'.") # type: ignore[attr-defined] def build_parser() -> argparse.ArgumentParser: p = argparse.ArgumentParser(description="ACE-F Review Queue CLI") sub = p.add_subparsers(dest="command", required=True) # list p_list = sub.add_parser("list", help="List review items") p_list.add_argument("--status", default="open", help="Filter by status (default: open)") p_list.add_argument("--priority", default=None, help="Filter by priority (P0/P1/P2)") # show p_show = sub.add_parser("show", help="Show review item details") p_show.add_argument("review_id", help="UUID of the review item") # resolve p_resolve = sub.add_parser("resolve", help="Resolve a review item") p_resolve.add_argument("review_id", help="UUID of the review item") p_resolve.add_argument("--reviewer", required=True, help="Reviewer ID/name") p_resolve.add_argument( "--resolution-type", required=True, choices=["accepted", "corrected", "wont_fix", "escalated"], ) p_resolve.add_argument("--root-cause", default="", help="Root cause description") p_resolve.add_argument("--notes", default="", help="Free-text notes") p_resolve.add_argument("--overrides", default=None, help="JSON string of field overrides") return p def main() -> None: settings = get_settings() configure_logging(settings.log_level) parser = build_parser() args = parser.parse_args() handlers = { "list": cmd_list, "show": cmd_show, "resolve": cmd_resolve, } handler = handlers.get(args.command) if handler is None: parser.print_help() sys.exit(1) asyncio.run(handler(args)) if __name__ == "__main__": main()