devcouncil 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (128) hide show
  1. package/LICENSE +201 -201
  2. package/README.md +62 -543
  3. package/package.json +1 -1
  4. package/pyproject.toml +29 -26
  5. package/src/devcouncil/__main__.py +4 -4
  6. package/src/devcouncil/app/__init__.py +28 -28
  7. package/src/devcouncil/app/config.py +135 -108
  8. package/src/devcouncil/app/errors.py +23 -23
  9. package/src/devcouncil/app/events.py +44 -44
  10. package/src/devcouncil/app/orchestrator.py +67 -67
  11. package/src/devcouncil/app/project_status.py +29 -0
  12. package/src/devcouncil/app/run_context.py +39 -39
  13. package/src/devcouncil/app/state_machine.py +108 -108
  14. package/src/devcouncil/artifacts/__init__.py +1 -1
  15. package/src/devcouncil/artifacts/coverage.py +96 -96
  16. package/src/devcouncil/artifacts/graph.py +143 -143
  17. package/src/devcouncil/artifacts/migrations.py +20 -20
  18. package/src/devcouncil/artifacts/schemas.py +23 -23
  19. package/src/devcouncil/artifacts/serializer.py +21 -21
  20. package/src/devcouncil/artifacts/validators.py +27 -27
  21. package/src/devcouncil/cli/commands/artifacts.py +51 -48
  22. package/src/devcouncil/cli/commands/ast.py +22 -0
  23. package/src/devcouncil/cli/commands/baseline.py +35 -32
  24. package/src/devcouncil/cli/commands/config.py +76 -54
  25. package/src/devcouncil/cli/commands/dashboard.py +26 -0
  26. package/src/devcouncil/cli/commands/doctor.py +86 -42
  27. package/src/devcouncil/cli/commands/go.py +237 -0
  28. package/src/devcouncil/cli/commands/hook.py +96 -29
  29. package/src/devcouncil/cli/commands/init.py +67 -56
  30. package/src/devcouncil/cli/commands/integrate.py +320 -14
  31. package/src/devcouncil/cli/commands/lsp.py +20 -0
  32. package/src/devcouncil/cli/commands/map.py +25 -21
  33. package/src/devcouncil/cli/commands/plan.py +257 -206
  34. package/src/devcouncil/cli/commands/prompt.py +36 -33
  35. package/src/devcouncil/cli/commands/repair.py +72 -69
  36. package/src/devcouncil/cli/commands/report.py +112 -54
  37. package/src/devcouncil/cli/commands/reset_demo_state.py +31 -28
  38. package/src/devcouncil/cli/commands/rollback.py +49 -47
  39. package/src/devcouncil/cli/commands/run.py +252 -207
  40. package/src/devcouncil/cli/commands/setup.py +159 -18
  41. package/src/devcouncil/cli/commands/show.py +76 -57
  42. package/src/devcouncil/cli/commands/status.py +117 -105
  43. package/src/devcouncil/cli/commands/tasks.py +55 -41
  44. package/src/devcouncil/cli/commands/trace.py +2 -1
  45. package/src/devcouncil/cli/commands/verify.py +158 -128
  46. package/src/devcouncil/cli/commands/version.py +20 -20
  47. package/src/devcouncil/cli/commands/watch.py +574 -0
  48. package/src/devcouncil/cli/main.py +42 -24
  49. package/src/devcouncil/council/prompts/arbiter.md +19 -19
  50. package/src/devcouncil/council/prompts/critic_a.md +10 -10
  51. package/src/devcouncil/council/prompts/critic_b.md +10 -10
  52. package/src/devcouncil/council/prompts/implementation_reviewer.md +16 -16
  53. package/src/devcouncil/council/prompts/planner_a.md +16 -16
  54. package/src/devcouncil/council/prompts/planner_b.md +16 -16
  55. package/src/devcouncil/council/prompts/rebuttal.md +10 -10
  56. package/src/devcouncil/council/prompts/spec_writer.md +12 -12
  57. package/src/devcouncil/domain/assumption.py +17 -17
  58. package/src/devcouncil/domain/critique.py +32 -32
  59. package/src/devcouncil/domain/evidence.py +27 -27
  60. package/src/devcouncil/domain/gap.py +26 -26
  61. package/src/devcouncil/domain/requirement.py +22 -22
  62. package/src/devcouncil/domain/task.py +26 -26
  63. package/src/devcouncil/execution/__init__.py +1 -1
  64. package/src/devcouncil/execution/context_builder.py +54 -54
  65. package/src/devcouncil/execution/executor.py +15 -15
  66. package/src/devcouncil/execution/hook_policy.py +24 -3
  67. package/src/devcouncil/execution/patch.py +28 -28
  68. package/src/devcouncil/execution/permissions.py +44 -44
  69. package/src/devcouncil/execution/prompt_builder.py +23 -23
  70. package/src/devcouncil/execution/task_runner.py +63 -63
  71. package/src/devcouncil/executors/__init__.py +1 -1
  72. package/src/devcouncil/executors/coding_cli.py +112 -0
  73. package/src/devcouncil/executors/mini_swe.py +63 -63
  74. package/src/devcouncil/executors/native/agent.py +81 -81
  75. package/src/devcouncil/executors/openhands.py +56 -56
  76. package/src/devcouncil/gating/__init__.py +1 -1
  77. package/src/devcouncil/gating/checks/clean_git.py +50 -45
  78. package/src/devcouncil/gating/checks/planned_files_check.py +32 -32
  79. package/src/devcouncil/gating/checks/requirement_coverage.py +26 -26
  80. package/src/devcouncil/gating/checks/secret_scan_check.py +34 -34
  81. package/src/devcouncil/gating/policy.py +157 -157
  82. package/src/devcouncil/indexing/__init__.py +1 -1
  83. package/src/devcouncil/indexing/ast_matcher.py +168 -0
  84. package/src/devcouncil/indexing/graph_index.py +48 -48
  85. package/src/devcouncil/indexing/lsp.py +120 -0
  86. package/src/devcouncil/indexing/repo_mapper.py +208 -204
  87. package/src/devcouncil/integrations/github.py +35 -35
  88. package/src/devcouncil/integrations/gitnexus.py +27 -27
  89. package/src/devcouncil/integrations/graphify.py +34 -34
  90. package/src/devcouncil/integrations/mcp/server.py +549 -96
  91. package/src/devcouncil/integrations/pr_comments.py +62 -0
  92. package/src/devcouncil/live/__init__.py +2 -0
  93. package/src/devcouncil/live/cards.py +207 -0
  94. package/src/devcouncil/live/models.py +63 -0
  95. package/src/devcouncil/live/repair_prompt.py +83 -0
  96. package/src/devcouncil/live/reviewer.py +70 -0
  97. package/src/devcouncil/live/signals.py +135 -0
  98. package/src/devcouncil/live/summary.py +34 -0
  99. package/src/devcouncil/live/tasks.py +18 -0
  100. package/src/devcouncil/live/transcripts.py +138 -0
  101. package/src/devcouncil/llm/__init__.py +1 -1
  102. package/src/devcouncil/llm/cache.py +38 -38
  103. package/src/devcouncil/llm/provider.py +146 -125
  104. package/src/devcouncil/llm/router.py +111 -111
  105. package/src/devcouncil/planning/__init__.py +1 -1
  106. package/src/devcouncil/planning/arbiter_service.py +57 -57
  107. package/src/devcouncil/planning/critique_service.py +66 -66
  108. package/src/devcouncil/planning/plan_service.py +46 -46
  109. package/src/devcouncil/planning/prompt_enhancer_service.py +86 -0
  110. package/src/devcouncil/planning/repair_service.py +39 -39
  111. package/src/devcouncil/planning/spec_service.py +44 -44
  112. package/src/devcouncil/reporting/github_check.py +32 -32
  113. package/src/devcouncil/reporting/json_report.py +20 -17
  114. package/src/devcouncil/reporting/markdown_report.py +68 -46
  115. package/src/devcouncil/reporting/report_builder.py +14 -14
  116. package/src/devcouncil/storage/db.py +66 -66
  117. package/src/devcouncil/storage/models.py +83 -83
  118. package/src/devcouncil/storage/repositories.py +299 -222
  119. package/src/devcouncil/telemetry/cost.py +34 -34
  120. package/src/devcouncil/telemetry/tracker.py +49 -49
  121. package/src/devcouncil/ui/__init__.py +1 -0
  122. package/src/devcouncil/ui/dashboard.py +122 -0
  123. package/src/devcouncil/utils/__init__.py +1 -1
  124. package/src/devcouncil/utils/redaction.py +141 -141
  125. package/src/devcouncil/verification/__init__.py +1 -1
  126. package/src/devcouncil/verification/implementation_reviewer.py +55 -55
  127. package/src/devcouncil/verification/verifier.py +319 -302
  128. package/uv.lock +1 -1
@@ -1,41 +1,55 @@
1
- import typer
2
- from rich.console import Console
3
- from rich.table import Table
4
- from devcouncil.storage.db import get_db
5
- from devcouncil.storage.repositories import TaskRepository
6
-
7
- app = typer.Typer()
8
- console = Console()
9
-
10
- @app.callback(invoke_without_command=True)
11
- def tasks(ctx: typer.Context):
12
- """
13
- List task graph and task gate status.
14
- """
15
- if ctx.invoked_subcommand is not None:
16
- return
17
-
18
- db = get_db()
19
- if not db:
20
- console.print("[red]DevCouncil not initialized. Run 'dev init' first.[/red]")
21
- raise typer.Exit(code=1)
22
-
23
- with db.get_session() as session:
24
- task_repo = TaskRepository(session)
25
- tasks_list = task_repo.get_all()
26
-
27
- if not tasks_list:
28
- console.print("No tasks found. Run 'dev plan' to generate tasks.")
29
- return
30
-
31
- table = Table(title="DevCouncil Tasks")
32
- table.add_column("Task ID", style="cyan", no_wrap=True)
33
- table.add_column("Title", style="white")
34
- table.add_column("Status", style="magenta")
35
- table.add_column("Linked Reqs", style="green")
36
-
37
- for t in tasks_list:
38
- reqs = ", ".join(t.requirement_ids)
39
- table.add_row(t.id, t.title, t.status, reqs)
40
-
41
- console.print(table)
1
+ import json
2
+ from pathlib import Path
3
+
4
+ import typer
5
+ from rich.console import Console
6
+ from rich.table import Table
7
+ from devcouncil.cli.commands.init import initialize_project
8
+ from devcouncil.storage.db import get_db
9
+ from devcouncil.storage.repositories import TaskRepository
10
+
11
+ app = typer.Typer()
12
+ console = Console()
13
+
14
+ @app.callback(invoke_without_command=True)
15
+ def tasks(
16
+ ctx: typer.Context,
17
+ json_format: bool = typer.Option(False, "--json", help="Output machine-readable JSON."),
18
+ project_root: Path = typer.Option(Path("."), "--project-root", help="Repository root containing .devcouncil/."),
19
+ ):
20
+ """
21
+ List task graph and task gate status.
22
+ """
23
+ if ctx.invoked_subcommand is not None:
24
+ return
25
+
26
+ root = project_root.expanduser().resolve()
27
+ initialize_project(root, quiet=True)
28
+ db = get_db(root)
29
+ if not db:
30
+ console.print("[red]DevCouncil state is unavailable in this directory.[/red]")
31
+ raise typer.Exit(code=1)
32
+
33
+ with db.get_session() as session:
34
+ task_repo = TaskRepository(session)
35
+ tasks_list = task_repo.get_all()
36
+
37
+ if json_format:
38
+ typer.echo(json.dumps({"tasks": [task.model_dump() for task in tasks_list]}, indent=2))
39
+ return
40
+
41
+ if not tasks_list:
42
+ console.print("No tasks found. Run 'dev plan' to generate tasks.")
43
+ return
44
+
45
+ table = Table(title="DevCouncil Tasks")
46
+ table.add_column("Task ID", style="cyan", no_wrap=True)
47
+ table.add_column("Title", style="white")
48
+ table.add_column("Status", style="magenta")
49
+ table.add_column("Linked Reqs", style="green")
50
+
51
+ for t in tasks_list:
52
+ reqs = ", ".join(t.requirement_ids)
53
+ table.add_row(t.id, t.title, t.status, reqs)
54
+
55
+ console.print(table)
@@ -16,9 +16,10 @@ def tail(
16
16
  follow: bool = typer.Option(False, "--follow", "-f", help="Continue polling for new events."),
17
17
  limit: int = typer.Option(50, "--limit", "-n", help="Maximum events to print before following."),
18
18
  jsonl: bool = typer.Option(True, "--jsonl/--pretty", help="Print JSONL or compact text rows."),
19
+ project_root: Path = typer.Option(Path("."), "--project-root", help="Repository root containing .devcouncil/."),
19
20
  ):
20
21
  """Print the DevCouncil trace JSONL stream for replay or debugging."""
21
- project_root = Path(".")
22
+ project_root = project_root.expanduser().resolve()
22
23
  printed = 0
23
24
 
24
25
  def emit_new(start_index: int) -> int:
@@ -1,105 +1,120 @@
1
- import typer
2
- import asyncio
3
- from rich.console import Console
4
- from rich.table import Table
5
- from pathlib import Path
6
- from typing import Optional
7
- from devcouncil.storage.db import get_db
8
- from devcouncil.storage.repositories import TaskRepository, RequirementRepository, GapRepository, EvidenceRepository, StateRepository
9
- from devcouncil.verification.verifier import Verifier
10
- from devcouncil.llm.provider import OpenRouterProvider
11
- from devcouncil.llm.router import ModelRouter
12
- from devcouncil.domain.evidence import CommandResult, DiffEvidence, TestEvidence
1
+ import typer
2
+ import asyncio
3
+ import json
4
+ from rich.console import Console
5
+ from rich.table import Table
6
+ from pathlib import Path
7
+ from typing import Optional
8
+ from devcouncil.cli.commands.init import initialize_project
9
+ from devcouncil.storage.db import get_db
10
+ from devcouncil.storage.repositories import TaskRepository, RequirementRepository, GapRepository, EvidenceRepository, StateRepository
11
+ from devcouncil.verification.verifier import Verifier
12
+ from devcouncil.llm.provider import create_provider, validate_model_provider
13
+ from devcouncil.llm.router import ModelRouter
14
+ from devcouncil.domain.evidence import CommandResult, DiffEvidence, TestEvidence
13
15
  from devcouncil.app.config import load_config, get_api_key
14
16
  from devcouncil.app.state_machine import ProjectPhase
15
17
  from devcouncil.integrations.code_review_graph import CodeReviewGraphAdapter
16
18
  from devcouncil.telemetry.traces import TraceLogger
17
-
18
- console = Console()
19
- MAX_RENDERED_GAPS = 20
20
-
21
- def verify(
22
- task_id: Optional[str] = typer.Argument(None, help="Optional ID of the task to verify"),
23
- ):
24
- """
25
- Verify one task, or all tasks when TASK_ID is omitted.
26
- """
27
- db = get_db()
28
- if not db:
29
- console.print("[red]DevCouncil not initialized. Run 'dev init' first.[/red]")
30
- return
31
-
32
- with db.get_session() as session:
33
- task_repo = TaskRepository(session)
34
- req_repo = RequirementRepository(session)
35
- gap_repo = GapRepository(session)
36
- evidence_repo = EvidenceRepository(session)
37
-
38
- tasks = [task_repo.get_by_id(task_id)] if task_id else task_repo.get_all()
39
- tasks = [task for task in tasks if task is not None]
40
- if not tasks:
41
- missing = f"Task {task_id} not found." if task_id else "No tasks found to verify."
42
- console.print(f"[red]{missing}[/red]")
43
- return
44
-
45
- reqs = req_repo.get_all()
46
-
47
- # Load router for LLM review if possible
48
- router = None
49
- try:
50
- config = load_config(Path("."))
51
- api_key = get_api_key(config.models.provider)
52
- provider = OpenRouterProvider(api_key)
53
- role_config = {name: role.model_dump() for name, role in config.models.roles.items()}
54
- router = ModelRouter(provider, role_config)
55
- except Exception:
56
- pass
57
-
58
- verifier = Verifier(Path("."), router=router)
59
- total_gaps = 0
60
- blocked_tasks = 0
61
-
19
+
20
+ console = Console()
21
+ MAX_RENDERED_GAPS = 20
22
+
23
+ def verify(
24
+ task_id: Optional[str] = typer.Argument(None, help="Optional ID of the task to verify"),
25
+ json_format: bool = typer.Option(False, "--json", help="Output machine-readable JSON."),
26
+ project_root: Path = typer.Option(Path("."), "--project-root", help="Repository root containing .devcouncil/."),
27
+ ):
28
+ """
29
+ Verify one task, or all tasks when TASK_ID is omitted.
30
+ """
31
+ root = project_root.expanduser().resolve()
32
+ initialize_project(root, quiet=True)
33
+ db = get_db(root)
34
+ if not db:
35
+ if json_format:
36
+ typer.echo(json.dumps({"ok": False, "error": "DevCouncil state is unavailable in this directory."}, indent=2))
37
+ else:
38
+ console.print("[red]DevCouncil state is unavailable in this directory.[/red]")
39
+ return
40
+
41
+ with db.get_session() as session:
42
+ task_repo = TaskRepository(session)
43
+ req_repo = RequirementRepository(session)
44
+ gap_repo = GapRepository(session)
45
+ evidence_repo = EvidenceRepository(session)
46
+
47
+ tasks = [task_repo.get_by_id(task_id)] if task_id else task_repo.get_all()
48
+ tasks = [task for task in tasks if task is not None]
49
+ if not tasks:
50
+ missing = f"Task {task_id} not found." if task_id else "No tasks found to verify."
51
+ if json_format:
52
+ typer.echo(json.dumps({"ok": False, "error": missing}, indent=2))
53
+ else:
54
+ console.print(f"[red]{missing}[/red]")
55
+ return
56
+
57
+ reqs = req_repo.get_all()
58
+
59
+ # Load router for LLM review if possible
60
+ router = None
61
+ try:
62
+ config = load_config(root)
63
+ validate_model_provider(config.models.provider)
64
+ api_key = get_api_key(config.models.provider, root)
65
+ provider = create_provider(config.models.provider, api_key)
66
+ role_config = {name: role.model_dump() for name, role in config.models.roles.items()}
67
+ router = ModelRouter(provider, role_config)
68
+ except Exception:
69
+ pass
70
+
71
+ verifier = Verifier(root, router=router)
72
+ total_gaps = 0
73
+ blocked_tasks = 0
74
+ task_results = []
75
+
62
76
  for task in tasks:
63
- TraceLogger(Path(".")).log_event(
77
+ TraceLogger(root).log_event(
64
78
  "task_verification_started",
65
79
  {"task_id": task.id},
66
80
  task_id=task.id,
67
81
  summary=f"Verifying {task.id}",
68
82
  )
69
- graph_context = CodeReviewGraphAdapter(Path(".")).get_context(
83
+ graph_context = CodeReviewGraphAdapter(root).get_context(
70
84
  [planned.path for planned in task.planned_files]
71
85
  )
72
86
  if graph_context.available:
73
- TraceLogger(Path(".")).log_event(
87
+ TraceLogger(root).log_event(
74
88
  "graph_context_loaded",
75
89
  graph_context.model_dump(),
76
90
  task_id=task.id,
77
91
  summary=f"Loaded graph context for {task.id}",
78
92
  )
79
93
  StateRepository(session).record_phase(ProjectPhase.TASK_VERIFYING.value)
80
- gap_repo.delete_for_task(task.id)
81
- evidence_repo.delete_for_task(task.id)
82
-
83
- gaps, evidence = asyncio.run(verifier.verify_task(task, reqs))
84
- total_gaps += len(gaps)
85
-
86
- for gap in gaps:
87
- gap_repo.save(gap)
88
-
89
- for ev in evidence:
90
- if isinstance(ev, CommandResult):
91
- evidence_repo.save_command_result(task.id, ev)
92
- elif isinstance(ev, DiffEvidence):
93
- evidence_repo.save_diff_evidence(ev)
94
- elif isinstance(ev, TestEvidence):
95
- evidence_repo.save_test_evidence(ev, task.id)
96
-
97
- _print_task_result(task.id, gaps)
98
-
94
+ gap_repo.delete_for_task(task.id)
95
+ evidence_repo.delete_for_task(task.id)
96
+
97
+ gaps, evidence = asyncio.run(verifier.verify_task(task, reqs))
98
+ total_gaps += len(gaps)
99
+
100
+ for gap in gaps:
101
+ gap_repo.save(gap)
102
+
103
+ for ev in evidence:
104
+ if isinstance(ev, CommandResult):
105
+ evidence_repo.save_command_result(task.id, ev)
106
+ elif isinstance(ev, DiffEvidence):
107
+ evidence_repo.save_diff_evidence(ev)
108
+ elif isinstance(ev, TestEvidence):
109
+ evidence_repo.save_test_evidence(ev, task.id)
110
+
111
+ if not json_format:
112
+ _print_task_result(task.id, gaps)
113
+
99
114
  if any(gap.blocking for gap in gaps):
100
115
  task.status = "blocked"
101
116
  blocked_tasks += 1
102
- TraceLogger(Path(".")).log_event(
117
+ TraceLogger(root).log_event(
103
118
  "gate_failed",
104
119
  {"task_id": task.id, "gap_count": len(gaps)},
105
120
  task_id=task.id,
@@ -107,57 +122,72 @@ def verify(
107
122
  )
108
123
  else:
109
124
  task.status = "verified"
110
- TraceLogger(Path(".")).log_event(
125
+ TraceLogger(root).log_event(
111
126
  "task_verified",
112
127
  {"task_id": task.id, "gap_count": len(gaps)},
113
128
  task_id=task.id,
114
129
  summary=f"{task.id} verified",
115
130
  )
116
131
  task_repo.save(task)
117
-
118
- StateRepository(session).record_phase(
119
- ProjectPhase.TASK_BLOCKED.value if blocked_tasks else ProjectPhase.TASK_VERIFIED.value
120
- )
121
-
122
- if len(tasks) > 1:
123
- if blocked_tasks:
124
- console.print(
125
- f"\n[yellow]Verified {len(tasks)} tasks: {blocked_tasks} blocked, "
126
- f"{total_gaps} total gap(s).[/yellow]"
127
- )
128
- else:
129
- console.print(f"\n[green]Verified {len(tasks)} tasks successfully.[/green]")
130
-
131
-
132
- def _print_task_result(task_id: str, gaps):
133
- if not gaps:
134
- console.print(f"[green]Task {task_id} verified successfully! No gaps found.[/green]")
135
- return
136
-
137
- console.print(f"[yellow]Verification finished for task {task_id} with {len(gaps)} gaps:[/yellow]")
138
-
139
- table = Table(title="Detected Gaps")
140
- table.add_column("ID", style="cyan")
141
- table.add_column("Severity", style="magenta")
142
- table.add_column("Description", style="white")
143
- table.add_column("Blocking", style="red")
144
-
145
- for gap in gaps[:MAX_RENDERED_GAPS]:
146
- table.add_row(
147
- gap.id,
148
- gap.severity,
149
- gap.description,
150
- "YES" if gap.blocking else "NO",
151
- )
152
-
153
- console.print(table)
154
- if len(gaps) > MAX_RENDERED_GAPS:
155
- console.print(
156
- f"[yellow]Showing first {MAX_RENDERED_GAPS} of {len(gaps)} gaps. "
157
- "Run [bold]dev report --json[/bold] for the full list.[/yellow]"
158
- )
159
-
160
- if any(gap.blocking for gap in gaps):
161
- console.print(f"\n[red]Task {task_id} is BLOCKED due to critical gaps.[/red]")
162
- else:
163
- console.print(f"\n[green]Task {task_id} passed with non-blocking gaps.[/green]")
132
+ task_results.append({
133
+ "task_id": task.id,
134
+ "status": task.status,
135
+ "gap_count": len(gaps),
136
+ "blocking_gap_count": len([gap for gap in gaps if gap.blocking]),
137
+ "gaps": [gap.model_dump() for gap in gaps],
138
+ })
139
+
140
+ StateRepository(session).record_phase(
141
+ ProjectPhase.TASK_BLOCKED.value if blocked_tasks else ProjectPhase.TASK_VERIFIED.value
142
+ )
143
+
144
+ if json_format:
145
+ typer.echo(json.dumps({
146
+ "ok": blocked_tasks == 0,
147
+ "verified_tasks": len(tasks),
148
+ "blocked_tasks": blocked_tasks,
149
+ "total_gaps": total_gaps,
150
+ "tasks": task_results,
151
+ }, indent=2))
152
+ elif len(tasks) > 1:
153
+ if blocked_tasks:
154
+ console.print(
155
+ f"\n[yellow]Verified {len(tasks)} tasks: {blocked_tasks} blocked, "
156
+ f"{total_gaps} total gap(s).[/yellow]"
157
+ )
158
+ else:
159
+ console.print(f"\n[green]Verified {len(tasks)} tasks successfully.[/green]")
160
+
161
+
162
+ def _print_task_result(task_id: str, gaps):
163
+ if not gaps:
164
+ console.print(f"[green]Task {task_id} verified successfully! No gaps found.[/green]")
165
+ return
166
+
167
+ console.print(f"[yellow]Verification finished for task {task_id} with {len(gaps)} gaps:[/yellow]")
168
+
169
+ table = Table(title="Detected Gaps")
170
+ table.add_column("ID", style="cyan")
171
+ table.add_column("Severity", style="magenta")
172
+ table.add_column("Description", style="white")
173
+ table.add_column("Blocking", style="red")
174
+
175
+ for gap in gaps[:MAX_RENDERED_GAPS]:
176
+ table.add_row(
177
+ gap.id,
178
+ gap.severity,
179
+ gap.description,
180
+ "YES" if gap.blocking else "NO",
181
+ )
182
+
183
+ console.print(table)
184
+ if len(gaps) > MAX_RENDERED_GAPS:
185
+ console.print(
186
+ f"[yellow]Showing first {MAX_RENDERED_GAPS} of {len(gaps)} gaps. "
187
+ "Run [bold]dev report --json[/bold] for the full list.[/yellow]"
188
+ )
189
+
190
+ if any(gap.blocking for gap in gaps):
191
+ console.print(f"\n[red]Task {task_id} is BLOCKED due to critical gaps.[/red]")
192
+ else:
193
+ console.print(f"\n[green]Task {task_id} passed with non-blocking gaps.[/green]")
@@ -1,20 +1,20 @@
1
- import typer
2
- from rich.console import Console
3
- import importlib.metadata
4
-
5
- app = typer.Typer()
6
- console = Console()
7
-
8
- @app.callback(invoke_without_command=True)
9
- def version(ctx: typer.Context):
10
- """
11
- Display the current version of DevCouncil.
12
- """
13
- if ctx.invoked_subcommand is not None:
14
- return
15
-
16
- try:
17
- ver = importlib.metadata.version("devcouncil")
18
- console.print(f"DevCouncil version: [bold cyan]{ver}[/bold cyan]")
19
- except importlib.metadata.PackageNotFoundError:
20
- console.print("DevCouncil version: [yellow]unknown (editable/uninstalled)[/yellow]")
1
+ import typer
2
+ from rich.console import Console
3
+ import importlib.metadata
4
+
5
+ app = typer.Typer()
6
+ console = Console()
7
+
8
+ @app.callback(invoke_without_command=True)
9
+ def version(ctx: typer.Context):
10
+ """
11
+ Display the current version of DevCouncil.
12
+ """
13
+ if ctx.invoked_subcommand is not None:
14
+ return
15
+
16
+ try:
17
+ ver = importlib.metadata.version("devcouncil")
18
+ console.print(f"DevCouncil version: [bold cyan]{ver}[/bold cyan]")
19
+ except importlib.metadata.PackageNotFoundError:
20
+ console.print("DevCouncil version: [yellow]unknown (editable/uninstalled)[/yellow]")