devcouncil 0.2.0 → 0.2.2

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 (205) hide show
  1. package/README.md +144 -308
  2. package/bin/devcouncil.js +130 -32
  3. package/package.json +22 -19
  4. package/pyproject.toml +0 -66
  5. package/src/devcouncil/__init__.py +0 -0
  6. package/src/devcouncil/__main__.py +0 -4
  7. package/src/devcouncil/app/__init__.py +0 -28
  8. package/src/devcouncil/app/config.py +0 -320
  9. package/src/devcouncil/app/errors.py +0 -23
  10. package/src/devcouncil/app/events.py +0 -44
  11. package/src/devcouncil/app/orchestrator.py +0 -92
  12. package/src/devcouncil/app/project_status.py +0 -29
  13. package/src/devcouncil/app/run_context.py +0 -39
  14. package/src/devcouncil/app/state_machine.py +0 -108
  15. package/src/devcouncil/artifacts/__init__.py +0 -1
  16. package/src/devcouncil/artifacts/coverage.py +0 -96
  17. package/src/devcouncil/artifacts/graph.py +0 -163
  18. package/src/devcouncil/artifacts/migrations.py +0 -20
  19. package/src/devcouncil/artifacts/schemas.py +0 -23
  20. package/src/devcouncil/artifacts/serializer.py +0 -21
  21. package/src/devcouncil/artifacts/validators.py +0 -27
  22. package/src/devcouncil/assets/__init__.py +0 -1
  23. package/src/devcouncil/assets/devcouncil-logo.svg +0 -60
  24. package/src/devcouncil/assets/devcouncil_logo_premium.png +0 -0
  25. package/src/devcouncil/cli/__init__.py +0 -0
  26. package/src/devcouncil/cli/commands/__init__.py +0 -0
  27. package/src/devcouncil/cli/commands/agents.py +0 -292
  28. package/src/devcouncil/cli/commands/artifacts.py +0 -54
  29. package/src/devcouncil/cli/commands/ast.py +0 -22
  30. package/src/devcouncil/cli/commands/baseline.py +0 -35
  31. package/src/devcouncil/cli/commands/check.py +0 -209
  32. package/src/devcouncil/cli/commands/config.py +0 -115
  33. package/src/devcouncil/cli/commands/cost.py +0 -57
  34. package/src/devcouncil/cli/commands/dashboard.py +0 -31
  35. package/src/devcouncil/cli/commands/doctor.py +0 -340
  36. package/src/devcouncil/cli/commands/evidence.py +0 -48
  37. package/src/devcouncil/cli/commands/go.py +0 -656
  38. package/src/devcouncil/cli/commands/handoff.py +0 -69
  39. package/src/devcouncil/cli/commands/hook.py +0 -237
  40. package/src/devcouncil/cli/commands/init.py +0 -289
  41. package/src/devcouncil/cli/commands/integrate.py +0 -1515
  42. package/src/devcouncil/cli/commands/lsp.py +0 -20
  43. package/src/devcouncil/cli/commands/map.py +0 -112
  44. package/src/devcouncil/cli/commands/mcp_server.py +0 -18
  45. package/src/devcouncil/cli/commands/plan.py +0 -488
  46. package/src/devcouncil/cli/commands/prompt.py +0 -61
  47. package/src/devcouncil/cli/commands/repair.py +0 -89
  48. package/src/devcouncil/cli/commands/report.py +0 -137
  49. package/src/devcouncil/cli/commands/reset_demo_state.py +0 -33
  50. package/src/devcouncil/cli/commands/rollback.py +0 -59
  51. package/src/devcouncil/cli/commands/run.py +0 -289
  52. package/src/devcouncil/cli/commands/runs.py +0 -223
  53. package/src/devcouncil/cli/commands/scaffold.py +0 -32
  54. package/src/devcouncil/cli/commands/semantic.py +0 -47
  55. package/src/devcouncil/cli/commands/setup.py +0 -362
  56. package/src/devcouncil/cli/commands/shell.py +0 -73
  57. package/src/devcouncil/cli/commands/show.py +0 -76
  58. package/src/devcouncil/cli/commands/skills.py +0 -88
  59. package/src/devcouncil/cli/commands/status.py +0 -141
  60. package/src/devcouncil/cli/commands/tasks.py +0 -55
  61. package/src/devcouncil/cli/commands/trace.py +0 -88
  62. package/src/devcouncil/cli/commands/verify.py +0 -328
  63. package/src/devcouncil/cli/commands/version.py +0 -20
  64. package/src/devcouncil/cli/commands/watch.py +0 -574
  65. package/src/devcouncil/cli/commands/watch_fs.py +0 -40
  66. package/src/devcouncil/cli/main.py +0 -137
  67. package/src/devcouncil/council/__init__.py +0 -0
  68. package/src/devcouncil/council/prompts/__init__.py +0 -0
  69. package/src/devcouncil/council/prompts/arbiter.md +0 -19
  70. package/src/devcouncil/council/prompts/critic_a.md +0 -10
  71. package/src/devcouncil/council/prompts/critic_b.md +0 -10
  72. package/src/devcouncil/council/prompts/implementation_reviewer.md +0 -16
  73. package/src/devcouncil/council/prompts/planner_a.md +0 -16
  74. package/src/devcouncil/council/prompts/planner_b.md +0 -16
  75. package/src/devcouncil/council/prompts/rebuttal.md +0 -10
  76. package/src/devcouncil/council/prompts/spec_writer.md +0 -12
  77. package/src/devcouncil/domain/__init__.py +0 -0
  78. package/src/devcouncil/domain/assumption.py +0 -17
  79. package/src/devcouncil/domain/critique.py +0 -32
  80. package/src/devcouncil/domain/evidence.py +0 -47
  81. package/src/devcouncil/domain/gap.py +0 -52
  82. package/src/devcouncil/domain/requirement.py +0 -22
  83. package/src/devcouncil/domain/task.py +0 -55
  84. package/src/devcouncil/execution/__init__.py +0 -1
  85. package/src/devcouncil/execution/checkpoints.py +0 -246
  86. package/src/devcouncil/execution/context_builder.py +0 -60
  87. package/src/devcouncil/execution/executor.py +0 -15
  88. package/src/devcouncil/execution/fs_watcher.py +0 -180
  89. package/src/devcouncil/execution/handoff.py +0 -102
  90. package/src/devcouncil/execution/hook_policy.py +0 -253
  91. package/src/devcouncil/execution/patch.py +0 -77
  92. package/src/devcouncil/execution/paths.py +0 -14
  93. package/src/devcouncil/execution/permissions.py +0 -85
  94. package/src/devcouncil/execution/policy_engine.py +0 -343
  95. package/src/devcouncil/execution/prompt_builder.py +0 -671
  96. package/src/devcouncil/execution/shell_session.py +0 -225
  97. package/src/devcouncil/execution/task_runner.py +0 -170
  98. package/src/devcouncil/executors/__init__.py +0 -1
  99. package/src/devcouncil/executors/agent_registry.py +0 -575
  100. package/src/devcouncil/executors/coding_cli.py +0 -736
  101. package/src/devcouncil/executors/mini_swe.py +0 -73
  102. package/src/devcouncil/executors/native/__init__.py +0 -0
  103. package/src/devcouncil/executors/native/agent.py +0 -208
  104. package/src/devcouncil/executors/openhands.py +0 -71
  105. package/src/devcouncil/gating/__init__.py +0 -1
  106. package/src/devcouncil/gating/checks/__init__.py +0 -0
  107. package/src/devcouncil/gating/checks/clean_git.py +0 -52
  108. package/src/devcouncil/gating/checks/planned_files_check.py +0 -32
  109. package/src/devcouncil/gating/checks/requirement_coverage.py +0 -26
  110. package/src/devcouncil/gating/checks/secret_scan_check.py +0 -53
  111. package/src/devcouncil/gating/policy.py +0 -338
  112. package/src/devcouncil/hardware.py +0 -184
  113. package/src/devcouncil/indexing/__init__.py +0 -1
  114. package/src/devcouncil/indexing/ast_matcher.py +0 -168
  115. package/src/devcouncil/indexing/graph_index.py +0 -48
  116. package/src/devcouncil/indexing/lsp.py +0 -161
  117. package/src/devcouncil/indexing/repo_mapper.py +0 -1455
  118. package/src/devcouncil/indexing/semantic_index.py +0 -205
  119. package/src/devcouncil/integrations/actions.py +0 -146
  120. package/src/devcouncil/integrations/check.py +0 -423
  121. package/src/devcouncil/integrations/code_review_graph.py +0 -163
  122. package/src/devcouncil/integrations/github.py +0 -39
  123. package/src/devcouncil/integrations/github_intent.py +0 -142
  124. package/src/devcouncil/integrations/gitnexus.py +0 -62
  125. package/src/devcouncil/integrations/graphify.py +0 -34
  126. package/src/devcouncil/integrations/mcp/__init__.py +0 -0
  127. package/src/devcouncil/integrations/mcp/server.py +0 -2122
  128. package/src/devcouncil/integrations/opencode_devcouncil_plugin.mjs +0 -24
  129. package/src/devcouncil/integrations/pr_comments.py +0 -62
  130. package/src/devcouncil/live/__init__.py +0 -2
  131. package/src/devcouncil/live/cards.py +0 -349
  132. package/src/devcouncil/live/models.py +0 -63
  133. package/src/devcouncil/live/repair_prompt.py +0 -83
  134. package/src/devcouncil/live/reviewer.py +0 -70
  135. package/src/devcouncil/live/signals.py +0 -135
  136. package/src/devcouncil/live/summary.py +0 -34
  137. package/src/devcouncil/live/tasks.py +0 -18
  138. package/src/devcouncil/live/transcripts.py +0 -141
  139. package/src/devcouncil/llm/__init__.py +0 -1
  140. package/src/devcouncil/llm/cache.py +0 -42
  141. package/src/devcouncil/llm/model_defaults.yaml +0 -44
  142. package/src/devcouncil/llm/provider.py +0 -627
  143. package/src/devcouncil/llm/router.py +0 -310
  144. package/src/devcouncil/optimization/__init__.py +0 -1
  145. package/src/devcouncil/optimization/gepa_agent.py +0 -318
  146. package/src/devcouncil/planning/__init__.py +0 -1
  147. package/src/devcouncil/planning/arbiter_service.py +0 -57
  148. package/src/devcouncil/planning/correction_manifest.py +0 -303
  149. package/src/devcouncil/planning/critique_service.py +0 -71
  150. package/src/devcouncil/planning/plan_service.py +0 -60
  151. package/src/devcouncil/planning/prompt_enhancer_service.py +0 -167
  152. package/src/devcouncil/planning/repair_service.py +0 -39
  153. package/src/devcouncil/planning/spec_service.py +0 -70
  154. package/src/devcouncil/repo/__init__.py +0 -0
  155. package/src/devcouncil/repo/ci_scaffold.py +0 -157
  156. package/src/devcouncil/repo/gitignore.py +0 -123
  157. package/src/devcouncil/repo/sca.py +0 -374
  158. package/src/devcouncil/reporting/__init__.py +0 -0
  159. package/src/devcouncil/reporting/github_check.py +0 -32
  160. package/src/devcouncil/reporting/json_report.py +0 -30
  161. package/src/devcouncil/reporting/markdown_report.py +0 -83
  162. package/src/devcouncil/reporting/report_builder.py +0 -14
  163. package/src/devcouncil/skills/__init__.py +0 -19
  164. package/src/devcouncil/skills/library/README.md +0 -46
  165. package/src/devcouncil/skills/library/ai-training.md +0 -50
  166. package/src/devcouncil/skills/library/android.md +0 -50
  167. package/src/devcouncil/skills/library/backend.md +0 -52
  168. package/src/devcouncil/skills/library/core-engineering.md +0 -95
  169. package/src/devcouncil/skills/library/data-engineering.md +0 -47
  170. package/src/devcouncil/skills/library/desktop.md +0 -46
  171. package/src/devcouncil/skills/library/devops.md +0 -48
  172. package/src/devcouncil/skills/library/game-dev.md +0 -46
  173. package/src/devcouncil/skills/library/ios.md +0 -48
  174. package/src/devcouncil/skills/library/mobile-cross-platform.md +0 -46
  175. package/src/devcouncil/skills/library/security.md +0 -48
  176. package/src/devcouncil/skills/library/systems.md +0 -48
  177. package/src/devcouncil/skills/library/web.md +0 -47
  178. package/src/devcouncil/skills/library/windows.md +0 -47
  179. package/src/devcouncil/skills/registry.py +0 -330
  180. package/src/devcouncil/storage/__init__.py +0 -0
  181. package/src/devcouncil/storage/db.py +0 -147
  182. package/src/devcouncil/storage/models.py +0 -204
  183. package/src/devcouncil/storage/native.py +0 -557
  184. package/src/devcouncil/storage/repositories.py +0 -485
  185. package/src/devcouncil/telemetry/__init__.py +0 -0
  186. package/src/devcouncil/telemetry/cost.py +0 -140
  187. package/src/devcouncil/telemetry/model_pricing.yaml +0 -48
  188. package/src/devcouncil/telemetry/pricing.py +0 -28
  189. package/src/devcouncil/telemetry/traces.py +0 -146
  190. package/src/devcouncil/telemetry/tracker.py +0 -52
  191. package/src/devcouncil/ui/__init__.py +0 -1
  192. package/src/devcouncil/ui/dashboard.py +0 -423
  193. package/src/devcouncil/utils/__init__.py +0 -1
  194. package/src/devcouncil/utils/redaction.py +0 -147
  195. package/src/devcouncil/utils/subprocess_env.py +0 -69
  196. package/src/devcouncil/verification/__init__.py +0 -1
  197. package/src/devcouncil/verification/acceptance_compiler.py +0 -125
  198. package/src/devcouncil/verification/ad_hoc_check.py +0 -129
  199. package/src/devcouncil/verification/diff_coverage.py +0 -353
  200. package/src/devcouncil/verification/implementation_reviewer.py +0 -55
  201. package/src/devcouncil/verification/next_actions.py +0 -189
  202. package/src/devcouncil/verification/sandbox.py +0 -178
  203. package/src/devcouncil/verification/test_resolver.py +0 -91
  204. package/src/devcouncil/verification/verifier.py +0 -1548
  205. package/uv.lock +0 -1226
@@ -1,73 +0,0 @@
1
- import typer
2
- from pathlib import Path
3
- from rich.console import Console
4
-
5
- from devcouncil.cli.commands.init import initialize_project
6
- from devcouncil.execution.shell_session import GuardedShellSession
7
- from devcouncil.storage.db import get_db
8
- from devcouncil.storage.repositories import TaskRepository
9
-
10
- console = Console()
11
-
12
-
13
- def shell(
14
- task_id: str = typer.Argument(..., help="Task ID"),
15
- command: str | None = typer.Option(None, "--command", help="Run one guarded command and exit."),
16
- shell_name: str = typer.Option("auto", "--shell", help="Shell backend: auto, pwsh, bash, zsh."),
17
- force: bool = typer.Option(False, "--force", help="Reclaim a stale lease from a previous (possibly crashed) session."),
18
- project_root: Path = typer.Option(Path("."), "--project-root"),
19
- ):
20
- """
21
- Run guarded shell commands for a task.
22
- """
23
- root = project_root.expanduser().resolve()
24
- initialize_project(root, quiet=True)
25
- db = get_db(root)
26
- if not db:
27
- console.print("[red]DevCouncil not initialized.[/red]")
28
- raise typer.Exit(code=1)
29
-
30
- with db.get_session() as session:
31
- task = TaskRepository(session).get_by_id(task_id)
32
- if not task:
33
- console.print(f"[red]Task {task_id} not found.[/red]")
34
- raise typer.Exit(code=1)
35
-
36
- try:
37
- session_runner = GuardedShellSession(root, task, shell=shell_name)
38
- except ValueError as exc:
39
- console.print(f"[red]{exc}[/red]")
40
- raise typer.Exit(code=2)
41
- try:
42
- session_runner.start(force=force)
43
- except ValueError as exc:
44
- # A live or stale lease from a prior session blocks a new one. Don't dump
45
- # a traceback — tell the user how to recover.
46
- console.print(f"[red]{exc}[/red]")
47
- console.print(
48
- "[yellow]Another shell session may be active for this task. If it is "
49
- "stale (a previous session crashed), re-run with [bold]--force[/bold] to "
50
- "reclaim it.[/yellow]"
51
- )
52
- raise typer.Exit(code=2)
53
- try:
54
- if command:
55
- code = session_runner.run_one(command)
56
- raise typer.Exit(code=code)
57
-
58
- console.print(f"[cyan]Guarded shell for {task_id}. Type exit or quit to end.[/cyan]")
59
- while True:
60
- try:
61
- line = input(f"devcouncil:{task_id}> ")
62
- except (EOFError, KeyboardInterrupt):
63
- break
64
- normalized = line.strip()
65
- if normalized.lower() in {"exit", "quit"}:
66
- break
67
- if not normalized:
68
- continue
69
- code = session_runner.run_one(normalized)
70
- if code != 0:
71
- console.print(f"[yellow]Command exited with {code}[/yellow]")
72
- finally:
73
- session_runner.finish()
@@ -1,76 +0,0 @@
1
- import json
2
- from pathlib import Path
3
-
4
- import typer
5
- from rich.console import Console
6
- from rich.panel import Panel
7
- from devcouncil.cli.commands.init import initialize_project
8
- from devcouncil.storage.db import get_db
9
- from devcouncil.storage.repositories import TaskRepository, RequirementRepository
10
-
11
- app = typer.Typer()
12
- console = Console()
13
-
14
- @app.callback(invoke_without_command=True)
15
- def show(
16
- ctx: typer.Context,
17
- task_id: str = typer.Argument(..., help="ID of the task to show"),
18
- json_format: bool = typer.Option(False, "--json", help="Output machine-readable JSON."),
19
- project_root: Path = typer.Option(Path("."), "--project-root", help="Repository root containing .devcouncil/."),
20
- ):
21
- """
22
- Show details of a specific task.
23
- """
24
- if ctx.invoked_subcommand is not None:
25
- return
26
-
27
- root = project_root.expanduser().resolve()
28
- initialize_project(root, quiet=True)
29
- db = get_db(root)
30
- if not db:
31
- raise typer.Exit(code=1)
32
-
33
- with db.get_session() as session:
34
- task_repo = TaskRepository(session)
35
- req_repo = RequirementRepository(session)
36
-
37
- task = task_repo.get_by_id(task_id)
38
- if not task:
39
- console.print(f"[red]Task {task_id} not found.[/red]")
40
- raise typer.Exit(code=1)
41
-
42
- reqs = req_repo.get_all()
43
- req_map = {r.id: r for r in reqs}
44
-
45
- if json_format:
46
- linked_requirements = [
47
- req_map[req_id].model_dump()
48
- for req_id in task.requirement_ids
49
- if req_id in req_map
50
- ]
51
- typer.echo(json.dumps({
52
- "task": task.model_dump(),
53
- "linked_requirements": linked_requirements,
54
- }, indent=2))
55
- return
56
-
57
- output = f"[bold]Status:[/bold] {task.status}\n\n"
58
- output += f"[bold]Description:[/bold]\n{task.description}\n\n"
59
-
60
- output += "[bold]Linked Requirements:[/bold]\n"
61
- for req_id in task.requirement_ids:
62
- req = req_map.get(req_id)
63
- if req:
64
- output += f" - [cyan]{req.id}[/cyan]: {req.title}\n"
65
- else:
66
- output += f" - [cyan]{req_id}[/cyan]: (Requirement not found)\n"
67
-
68
- output += "\n[bold]Planned Files:[/bold]\n"
69
- for pf in task.planned_files:
70
- output += f" - {pf.path} ({pf.allowed_change}): {pf.reason}\n"
71
-
72
- output += "\n[bold]Expected Tests:[/bold]\n"
73
- for et in task.expected_tests:
74
- output += f" - {et}\n"
75
-
76
- console.print(Panel(output, title=f"Task {task.id}: {task.title}", expand=False))
@@ -1,88 +0,0 @@
1
- from pathlib import Path
2
-
3
- import typer
4
- from rich.console import Console
5
- from rich.table import Table
6
-
7
- from devcouncil.skills.registry import get_skill, load_skills, scaffold_skills, select_skills
8
-
9
- app = typer.Typer(help="Inspect and scaffold DevCouncil engineering skills for coding agents.")
10
- console = Console()
11
-
12
-
13
- def _is_repo_skill(skill, project_root: Path) -> bool:
14
- if skill.source_path is None:
15
- return False
16
- try:
17
- skill.source_path.resolve().relative_to(project_root.resolve())
18
- return True
19
- except ValueError:
20
- return False
21
-
22
-
23
- @app.callback(invoke_without_command=True)
24
- def skills(
25
- ctx: typer.Context,
26
- goal: str = typer.Option("", "--goal", help="Optional goal text; highlights the skills that would apply."),
27
- project_root: Path = typer.Option(Path("."), "--project-root", help="Repository root used for file-based skill triggers."),
28
- ):
29
- """List available skills and show which apply to this repository/goal."""
30
- if ctx.invoked_subcommand is not None:
31
- return
32
-
33
- root = project_root.expanduser().resolve()
34
- all_skills = load_skills(project_root=root)
35
- if not all_skills:
36
- console.print("[yellow]No skills found in the DevCouncil skills library.[/yellow]")
37
- raise typer.Exit()
38
-
39
- selected = {skill.name for skill in select_skills(goal, root)}
40
- table = Table(title="DevCouncil Skills")
41
- table.add_column("Skill", style="cyan")
42
- table.add_column("Source", justify="center")
43
- table.add_column("Applies", justify="center")
44
- table.add_column("Description")
45
- for skill in all_skills:
46
- applies = "always" if skill.always else ("yes" if skill.name in selected else "-")
47
- style = "green" if skill.name in selected else "dim"
48
- source = "repo" if _is_repo_skill(skill, root) else "library"
49
- table.add_row(skill.name, source, f"[{style}]{applies}[/{style}]", skill.description)
50
- console.print(table)
51
- console.print(
52
- "\nScaffold the applicable skills into this repo with: "
53
- "[bold]dev skills scaffold[/bold] (add a goal to widen selection, or --all)."
54
- )
55
-
56
-
57
- @app.command("show")
58
- def show(
59
- name: str = typer.Argument(..., help="Skill name, e.g. core-engineering or android."),
60
- project_root: Path = typer.Option(Path("."), "--project-root", help="Repository root, so repo-local skills are found too."),
61
- ):
62
- """Print the full body of a single skill."""
63
- skill = get_skill(name, project_root=project_root.expanduser().resolve())
64
- if skill is None:
65
- console.print(f"[red]No skill named '{name}'. Run 'dev skills' to list available skills.[/red]")
66
- raise typer.Exit(code=1)
67
- console.print(skill.to_skill_md())
68
-
69
-
70
- @app.command("scaffold")
71
- def scaffold(
72
- goal: str = typer.Argument("", help="Optional goal text used to widen domain-skill selection."),
73
- project_root: Path = typer.Option(Path("."), "--project-root", help="Repository root to scaffold skills into."),
74
- all_skills: bool = typer.Option(False, "--all", help="Scaffold every skill, not just the ones that apply."),
75
- ):
76
- """Write the applicable skills into <repo>/.claude/skills/<name>/SKILL.md."""
77
- root = project_root.expanduser().resolve()
78
- chosen = load_skills(project_root=root) if all_skills else select_skills(goal, root)
79
- written = scaffold_skills(root, chosen)
80
- if not written:
81
- console.print(
82
- f"[green]Skills already up to date in {root / '.claude' / 'skills'} "
83
- f"({len(chosen)} applicable).[/green]"
84
- )
85
- return
86
- console.print(f"[green]Wrote {len(written)} skill file(s):[/green]")
87
- for path in written:
88
- console.print(f" {path.relative_to(root).as_posix()}")
@@ -1,141 +0,0 @@
1
- from rich.console import Console
2
- from rich.table import Table
3
- from rich.panel import Panel
4
- from pathlib import Path
5
- import json
6
- import typer
7
- from devcouncil.cli.commands.init import initialize_project
8
- from devcouncil.app.project_status import compute_phase
9
- from devcouncil.storage.db import get_db
10
- from devcouncil.storage.repositories import ArtifactGraphRepository, StateRepository
11
- from devcouncil.telemetry.cost import CostEstimator, cost_by_task
12
- from devcouncil.live.summary import live_review_summary
13
-
14
- console = Console()
15
-
16
- def _status_payload(project_root: Path) -> dict:
17
- initialize_project(project_root, quiet=True)
18
- db = get_db(project_root)
19
- if not db:
20
- return {"initialized": False, "phase": "UNINITIALIZED"}
21
-
22
- with db.get_session() as session:
23
- graph_repo = ArtifactGraphRepository(session)
24
- graph = graph_repo.load_graph()
25
- summary = graph.coverage_summary()
26
-
27
- blocking_gaps = graph.blocking_gaps()
28
- state = StateRepository(session).get_state()
29
- phase = compute_phase(graph, state.current_phase if state else None)
30
-
31
- total_cost = 0.0
32
- log_file = project_root / ".devcouncil" / "logs" / "model_calls.jsonl"
33
- if log_file.exists():
34
- with open(log_file, "r", encoding="utf-8") as f:
35
- for line in f:
36
- try:
37
- entry = json.loads(line)
38
- total_cost += CostEstimator.estimate_cost(
39
- entry.get("response", {}).get("model", ""),
40
- entry.get("usage", {}),
41
- )
42
- except Exception:
43
- continue
44
-
45
- status_counts: dict[str, int] = {}
46
- for task in graph.tasks.values():
47
- status_counts[task.status] = status_counts.get(task.status, 0) + 1
48
-
49
- return {
50
- "initialized": True,
51
- "phase": phase,
52
- "coverage_summary": summary,
53
- "total_cost": total_cost,
54
- "cost_by_task": cost_by_task(project_root),
55
- "task_status_counts": status_counts,
56
- "blocking_gaps": [gap.model_dump() for gap in blocking_gaps],
57
- "live_review": live_review_summary(project_root),
58
- }
59
-
60
-
61
- def status(
62
- json_format: bool = typer.Option(False, "--json", help="Output machine-readable JSON."),
63
- fail_on_blocking: bool = typer.Option(
64
- False,
65
- "--fail-on-blocking",
66
- help="Exit non-zero when blocking gaps remain, so shell-driven agents can gate on $?.",
67
- ),
68
- project_root: Path = typer.Option(Path("."), "--project-root", help="Repository root containing .devcouncil/."),
69
- ):
70
- """
71
- Show the current status of the DevCouncil project.
72
- """
73
- root = project_root.expanduser().resolve()
74
- payload = _status_payload(root)
75
-
76
- def _maybe_fail() -> None:
77
- if fail_on_blocking and payload.get("blocking_gaps"):
78
- raise typer.Exit(code=1)
79
-
80
- if json_format:
81
- typer.echo(json.dumps(payload, indent=2))
82
- _maybe_fail()
83
- return
84
-
85
- if not payload["initialized"]:
86
- console.print("[yellow]DevCouncil state is not available in this directory.[/yellow]")
87
- return
88
-
89
- summary = payload["coverage_summary"]
90
- phase = payload["phase"]
91
- phase_colors = {
92
- "NEW": "yellow",
93
- "REQUIREMENTS_DRAFTED": "cyan",
94
- "PLAN_APPROVED": "green",
95
- "TASK_EXECUTING": "blue",
96
- "TASK_BLOCKED": "red",
97
- "PROJECT_DONE": "green bold",
98
- }
99
- phase_color = phase_colors.get(phase, "white")
100
-
101
- console.print(Panel(
102
- f"[bold]Phase:[/bold] [{phase_color}]{phase}[/{phase_color}]\n"
103
- f"[bold]Requirements:[/bold] {summary['total_requirements']} ({summary['requirements_without_tasks']} unmapped)\n"
104
- f"[bold]Tasks:[/bold] {summary['total_tasks']} ({summary['tasks_without_requirements']} orphaned)\n"
105
- f"[bold]Acceptance Criteria:[/bold] {summary['total_ac']} ({summary['ac_without_evidence']} unverified)\n"
106
- f"[bold]Gaps:[/bold] {summary['total_gaps']} ({summary['blocking_gaps']} blocking)\n"
107
- f"[bold]Live Review:[/bold] {payload['live_review']['cards']['critical_open']} open critical, "
108
- f"{len(payload['live_review']['blocking_cards'])} blocking in scope, "
109
- f"{payload['live_review']['pending_signals']} pending signal(s)\n"
110
- f"[bold]Total Cost:[/bold] ${payload['total_cost']:.4f}",
111
- title="DevCouncil Status",
112
- expand=False,
113
- ))
114
-
115
- if payload["task_status_counts"]:
116
- table = Table(title="Task Summary")
117
- table.add_column("Status", style="magenta")
118
- table.add_column("Count", justify="right")
119
- for state, count in sorted(payload["task_status_counts"].items()):
120
- table.add_row(state, str(count))
121
- console.print(table)
122
-
123
- cost_groups = payload.get("cost_by_task") or {}
124
- if cost_groups:
125
- cost_table = Table(title="Cost by Task")
126
- cost_table.add_column("Task", style="cyan")
127
- cost_table.add_column("Cost ($)", justify="right")
128
- cost_table.add_column("Calls", justify="right")
129
- for name, stats in sorted(cost_groups.items(), key=lambda kv: kv[1]["cost"], reverse=True):
130
- cost_table.add_row(name, f"{stats['cost']:.4f}", str(stats["calls"]))
131
- console.print(cost_table)
132
-
133
- blocking_gaps = payload["blocking_gaps"]
134
- if blocking_gaps:
135
- console.print(f"\n[red bold]WARNING: {len(blocking_gaps)} blocking gap(s) must be resolved:[/red bold]")
136
- for gap in blocking_gaps[:5]:
137
- console.print(f" - [red]{gap['id']}[/red]: {gap['description'][:80]}")
138
- if len(blocking_gaps) > 5:
139
- console.print(f" ... and {len(blocking_gaps) - 5} more. Run [bold]dev report[/bold] for details.")
140
-
141
- _maybe_fail()
@@ -1,55 +0,0 @@
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)
@@ -1,88 +0,0 @@
1
- import json
2
- import time
3
- from pathlib import Path
4
- from typing import Optional
5
-
6
- import typer
7
- from rich.console import Console
8
-
9
- from devcouncil.telemetry.traces import read_trace_events, read_trace_events_since
10
-
11
- app = typer.Typer(help="Inspect DevCouncil trace events.")
12
- console = Console()
13
-
14
-
15
- @app.command("tail")
16
- def tail(
17
- follow: bool = typer.Option(
18
- False, "--follow/--no-follow", "-f", help="Continue polling for new events (default is a single shot)."
19
- ),
20
- limit: int = typer.Option(50, "--limit", "-n", help="Maximum events to print before following."),
21
- jsonl: bool = typer.Option(True, "--jsonl/--pretty", help="Print JSONL or compact text rows."),
22
- since: Optional[int] = typer.Option(
23
- None,
24
- "--since",
25
- help="Byte-offset cursor from a previous run; emit only events after it (stateless incremental polling).",
26
- ),
27
- json_summary: bool = typer.Option(
28
- False,
29
- "--json",
30
- help="Emit a single {events, next_cursor} JSON object (incremental mode). Implies --no-follow.",
31
- ),
32
- project_root: Path = typer.Option(Path("."), "--project-root", help="Repository root containing .devcouncil/."),
33
- ):
34
- """Print the DevCouncil trace JSONL stream for replay or debugging.
35
-
36
- With ``--since <cursor> --no-follow`` (or ``--json``) a single-shot supervising
37
- agent gets only the events appended after the cursor plus a ``next_cursor`` to
38
- pass back on the next poll, so each poll is O(new) rather than O(all).
39
- """
40
- project_root = project_root.expanduser().resolve()
41
-
42
- # Incremental cursor mode: any of --since / --json / explicit --no-follow.
43
- incremental = since is not None or json_summary
44
- if incremental:
45
- events, next_cursor = read_trace_events_since(project_root, since)
46
- if json_summary:
47
- typer.echo(
48
- json.dumps(
49
- {
50
- "events": [event.model_dump(by_alias=True) for event in events],
51
- "next_cursor": next_cursor,
52
- }
53
- )
54
- )
55
- return
56
- for event in events:
57
- if jsonl:
58
- typer.echo(event.model_dump_json())
59
- else:
60
- console.print(
61
- f"{event.timestamp} {event.type} "
62
- f"{event.task_id or '-'} {event.summary or json.dumps(event.details)}"
63
- )
64
- console.print(f"[dim]next_cursor: {next_cursor}[/dim]")
65
- return
66
-
67
- printed = 0
68
-
69
- def emit_new(start_index: int) -> int:
70
- events = list(read_trace_events(project_root))
71
- selected = events[start_index:]
72
- for event in selected:
73
- if jsonl:
74
- typer.echo(event.model_dump_json())
75
- else:
76
- console.print(
77
- f"{event.timestamp} {event.type} "
78
- f"{event.task_id or '-'} {event.summary or json.dumps(event.details)}"
79
- )
80
- return len(events)
81
-
82
- events = list(read_trace_events(project_root))
83
- start = max(0, len(events) - limit)
84
- printed = emit_new(start)
85
-
86
- while follow:
87
- time.sleep(1)
88
- printed = emit_new(printed)