Coverage for little_loops / cli / issues / sequence.py: 89%
38 statements
« prev ^ index » next coverage.py v7.12.0, created at 2026-05-22 16:19 -0500
« prev ^ index » next coverage.py v7.12.0, created at 2026-05-22 16:19 -0500
1"""ll-issues sequence: Suggest dependency-ordered implementation sequence."""
3from __future__ import annotations
5import argparse
6from typing import TYPE_CHECKING
8from little_loops.cli.output import PRIORITY_COLOR, TYPE_COLOR, colorize, print_json
10if TYPE_CHECKING:
11 from little_loops.config import BRConfig
14def cmd_sequence(config: BRConfig, args: argparse.Namespace) -> int:
15 """Output a dependency-ordered list of active issues with rationale.
17 Args:
18 config: Project configuration
19 args: Parsed arguments with .limit and optional .type attributes
21 Returns:
22 Exit code (0 = success)
23 """
24 from little_loops.dependency_graph import DependencyGraph
25 from little_loops.issue_parser import find_issues
27 type_prefixes = {args.type} if getattr(args, "type", None) else None
28 issues = find_issues(config, type_prefixes=type_prefixes)
30 if not issues:
31 print("No active issues found.")
32 return 0
34 graph = DependencyGraph.from_issues(issues)
36 try:
37 ordered = graph.topological_sort()
38 except ValueError as exc:
39 print(f"Warning: dependency cycle detected — {exc}")
40 ordered = issues # fall back to priority order
42 limit = args.limit
43 shown = ordered[:limit]
45 if getattr(args, "json", False):
46 type_filter = getattr(args, "type", None)
47 print_json(
48 [
49 {
50 "id": issue.issue_id,
51 "priority": issue.priority,
52 "title": issue.title,
53 "path": str(issue.path),
54 "blocked_by": sorted(graph.blocked_by.get(issue.issue_id, set())),
55 "blocks": issue.blocks,
56 **({"type_filter": type_filter} if type_filter else {}),
57 }
58 for issue in shown
59 ]
60 )
61 return 0
63 print(f"Suggested implementation sequence ({len(shown)} of {len(ordered)} issues):\n")
64 for issue in shown:
65 blockers = graph.blocked_by.get(issue.issue_id, set())
66 if blockers:
67 rationale = f"blocked by: {', '.join(sorted(blockers))}"
68 else:
69 rationale = "no blockers"
70 issue_prefix = issue.issue_id.split("-", 1)[0]
71 colored_id = colorize(issue.issue_id, TYPE_COLOR.get(issue_prefix, "0"))
72 colored_pri = colorize(issue.priority, PRIORITY_COLOR.get(issue.priority, "0"))
73 print(f" [{colored_pri}, {rationale}] {colored_id}: {issue.title}")
75 if len(ordered) > limit:
76 remaining = len(ordered) - limit
77 print(f"\n … +{remaining} more (use --limit to show more)")
79 return 0