devcouncil 0.2.0 → 0.2.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 (205) hide show
  1. package/README.md +191 -253
  2. package/bin/devcouncil.js +130 -32
  3. package/package.json +6 -17
  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,61 +0,0 @@
1
- import json
2
- from typing import NoReturn
3
- import typer
4
- from rich.console import Console
5
- from rich.markdown import Markdown
6
- from devcouncil.cli.commands.init import initialize_project
7
- from devcouncil.storage.db import get_db
8
- from devcouncil.storage.repositories import TaskRepository, RequirementRepository
9
- from pathlib import Path
10
-
11
- from devcouncil.execution.prompt_builder import PromptBuilder
12
-
13
- app = typer.Typer()
14
- console = Console()
15
-
16
- @app.callback(invoke_without_command=True)
17
- def prompt(
18
- ctx: typer.Context,
19
- task_id: str = typer.Argument(..., help="ID of the task to generate a prompt for"),
20
- pretty: bool = typer.Option(False, "--pretty", help="Render the prompt for terminal reading."),
21
- json_format: bool = typer.Option(False, "--json", help="Output machine-readable JSON: {ok, task_id, prompt}."),
22
- project_root: Path = typer.Option(Path("."), "--project-root", help="Repository root containing .devcouncil/."),
23
- ):
24
- """
25
- Generate a constrained prompt for a specific task.
26
- """
27
- if ctx.invoked_subcommand is not None:
28
- return
29
-
30
- def _fail(message: str) -> NoReturn:
31
- if json_format:
32
- typer.echo(json.dumps({"ok": False, "task_id": task_id, "error": message}, indent=2))
33
- else:
34
- console.print(f"[red]{message}[/red]")
35
- raise typer.Exit(code=1)
36
-
37
- root = project_root.expanduser().resolve()
38
- initialize_project(root, quiet=True)
39
- db = get_db(root)
40
- if not db:
41
- _fail("DevCouncil state is unavailable in this directory.")
42
-
43
- with db.get_session() as session:
44
- task_repo = TaskRepository(session)
45
- req_repo = RequirementRepository(session)
46
-
47
- task = task_repo.get_by_id(task_id)
48
- if not task:
49
- _fail(f"Task {task_id} not found.")
50
-
51
- reqs = req_repo.get_all()
52
-
53
- builder = PromptBuilder(root)
54
- task_prompt = builder.build_task_prompt(task, reqs)
55
-
56
- if json_format:
57
- typer.echo(json.dumps({"ok": True, "task_id": task_id, "prompt": task_prompt}, indent=2))
58
- elif pretty:
59
- console.print(Markdown(task_prompt))
60
- else:
61
- typer.echo(task_prompt, nl=not task_prompt.endswith("\n"))
@@ -1,89 +0,0 @@
1
- import typer
2
- from rich.console import Console
3
- from devcouncil.storage.db import get_db
4
- from devcouncil.storage.repositories import TaskRepository, GapRepository
5
- from devcouncil.planning.repair_service import RepairService
6
- from devcouncil.execution.context_builder import ContextBuilder
7
- from devcouncil.llm.provider import create_provider, validate_model_provider
8
- from devcouncil.llm.router import ModelRouter
9
- from devcouncil.app.config import load_config, get_api_key
10
- from devcouncil.cli.commands.init import initialize_project
11
- import asyncio
12
- from pathlib import Path
13
-
14
- app = typer.Typer()
15
- console = Console()
16
-
17
- async def run_repair_flow(project_root: Path = Path(".")):
18
- root = project_root.expanduser().resolve()
19
- initialize_project(root, quiet=True)
20
- db = get_db(root)
21
- if not db:
22
- console.print("[red]DevCouncil state is unavailable in this directory.[/red]")
23
- return
24
-
25
- with db.get_session() as session:
26
- gap_repo = GapRepository(session)
27
- task_repo = TaskRepository(session)
28
-
29
- all_gaps = gap_repo.get_all()
30
- blocking_gaps = [g for g in all_gaps if g.blocking]
31
-
32
- if not blocking_gaps:
33
- console.print("[green]No blocking gaps found. Nothing to repair![/green]")
34
- return
35
-
36
- console.print(f"Found [bold]{len(blocking_gaps)}[/bold] blocking gaps. Orchestrating repair plan...")
37
-
38
- # Load router when credentials are available. Correction manifests have a
39
- # deterministic fallback path, so missing model credentials must not block
40
- # repair artifact generation.
41
- repair_service = None
42
- try:
43
- config = load_config(root)
44
- validate_model_provider(config.models.provider)
45
- api_key = get_api_key(config.models.provider, root)
46
- except (FileNotFoundError, ValueError) as e:
47
- console.print(f"[yellow]{e}[/yellow]")
48
- else:
49
- provider = create_provider(config.models.provider, api_key, project_root=root)
50
- role_config = {name: role.model_dump() for name, role in config.models.roles.items()}
51
- router = ModelRouter(provider, role_config, project_root=root)
52
- repair_service = RepairService(router)
53
- context_builder = ContextBuilder(root)
54
-
55
- # Build minimal context for repair.
56
- project_context = context_builder.get_structure_summary()
57
-
58
- from devcouncil.planning.correction_manifest import write_correction_manifest
59
-
60
- task_ids = {gap.task_id for gap in blocking_gaps if gap.task_id}
61
- for scoped_task_id in task_ids:
62
- if scoped_task_id:
63
- path = write_correction_manifest(root, scoped_task_id, repair_service=repair_service)
64
- if path:
65
- console.print(f" - Wrote correction manifest [dim]{path}[/dim]")
66
-
67
- repair_count = 0
68
- if repair_service is not None:
69
- repair_output = await repair_service.generate_repair_plan(blocking_gaps, str(project_context))
70
- for task in repair_output.suggested_tasks:
71
- task.id = f"REPAIR-{task.id}"
72
- task_repo.save(task)
73
- repair_count += 1
74
- console.print(f" - Created intelligent repair task [bold]{task.id}[/bold]: {task.title}")
75
-
76
- console.print(f"\n[green]Successfully generated {repair_count} repair tasks.[/green]")
77
-
78
- @app.callback(invoke_without_command=True)
79
- def repair(
80
- ctx: typer.Context,
81
- project_root: Path = typer.Option(Path("."), "--project-root", help="Repository root containing .devcouncil/."),
82
- ):
83
- """
84
- Convert blocking gaps into intelligent repair tasks using LLM inference.
85
- """
86
- if ctx.invoked_subcommand is not None:
87
- return
88
-
89
- asyncio.run(run_repair_flow(project_root))
@@ -1,137 +0,0 @@
1
- import typer
2
- from rich.console import Console
3
- from rich.markdown import Markdown
4
- from devcouncil.storage.db import get_db
5
- from devcouncil.storage.repositories import ArtifactGraphRepository
6
- from devcouncil.reporting.report_builder import ReportBuilder
7
- from devcouncil.integrations.github import GitHubIntegration
8
- from devcouncil.integrations.pr_comments import GitHubPRCommenter, GitLabMRCommenter, build_pr_comment_body
9
- from devcouncil.artifacts.graph import ArtifactGraph
10
- from devcouncil.telemetry.traces import TraceLogger
11
- from devcouncil.cli.commands.init import initialize_project
12
- from devcouncil.live.summary import live_review_summary
13
- import asyncio
14
- import os
15
- import subprocess
16
- from pathlib import Path
17
-
18
- app = typer.Typer()
19
- console = Console()
20
-
21
- async def run_github_report(graph: ArtifactGraph, project_root: Path):
22
- token = os.environ.get("GITHUB_TOKEN")
23
- repo = os.environ.get("GITHUB_REPOSITORY") # e.g. owner/repo
24
-
25
- if not token or not repo:
26
- console.print("[red]GITHUB_TOKEN and GITHUB_REPOSITORY must be set for GitHub reporting.[/red]")
27
- return
28
-
29
- try:
30
- # Detect current SHA
31
- sha = subprocess.check_output(["git", "rev-parse", "HEAD"], cwd=project_root).decode().strip()
32
- integration = GitHubIntegration(token, repo, sha)
33
- await integration.report_verification(graph)
34
- console.print(f"[green]Successfully reported to GitHub PR Checks for {repo} at {sha[:7]}[/green]")
35
- except Exception as e:
36
- console.print(f"[red]Failed to report to GitHub: {e}[/red]")
37
-
38
-
39
- async def run_github_pr_comment(graph: ArtifactGraph, live_review: dict | None = None):
40
- token = os.environ.get("GITHUB_TOKEN")
41
- repo = os.environ.get("GITHUB_REPOSITORY")
42
- pull_number = os.environ.get("GITHUB_PR_NUMBER") or os.environ.get("PR_NUMBER")
43
- if not token or not repo or not pull_number:
44
- console.print("[red]GITHUB_TOKEN, GITHUB_REPOSITORY, and GITHUB_PR_NUMBER must be set for GitHub PR comments.[/red]")
45
- return
46
- try:
47
- pull_number_int = int(pull_number)
48
- except ValueError:
49
- console.print("[red]GITHUB_PR_NUMBER must be an integer.[/red]")
50
- return
51
- commenter = GitHubPRCommenter(token, repo, pull_number_int)
52
- await commenter.post_comment(build_pr_comment_body(graph, live_review=live_review))
53
- console.print(f"[green]Posted DevCouncil PR comment to GitHub PR #{pull_number}.[/green]")
54
-
55
-
56
- async def run_gitlab_mr_comment(graph: ArtifactGraph, live_review: dict | None = None):
57
- token = os.environ.get("GITLAB_TOKEN")
58
- project_id = os.environ.get("GITLAB_PROJECT_ID")
59
- mr_iid = os.environ.get("GITLAB_MR_IID") or os.environ.get("CI_MERGE_REQUEST_IID")
60
- base_url = os.environ.get("GITLAB_API_URL", "https://gitlab.com/api/v4")
61
- if not token or not project_id or not mr_iid:
62
- console.print("[red]GITLAB_TOKEN, GITLAB_PROJECT_ID, and GITLAB_MR_IID must be set for GitLab MR comments.[/red]")
63
- return
64
- try:
65
- mr_iid_int = int(mr_iid)
66
- except ValueError:
67
- console.print("[red]GITLAB_MR_IID must be an integer.[/red]")
68
- return
69
- commenter = GitLabMRCommenter(token, project_id, mr_iid_int, base_url=base_url)
70
- await commenter.post_comment(build_pr_comment_body(graph, live_review=live_review))
71
- console.print(f"[green]Posted DevCouncil MR comment to GitLab MR !{mr_iid}.[/green]")
72
-
73
- @app.callback(invoke_without_command=True)
74
- def report(
75
- ctx: typer.Context,
76
- planning_only: bool = typer.Option(False, "--planning-only", help="Report only the planning phase status"),
77
- json_format: bool = typer.Option(False, "--json", help="Output report in JSON format"),
78
- github: bool = typer.Option(False, "--github", help="Post report to GitHub PR Checks"),
79
- github_pr_comment: bool = typer.Option(False, "--github-pr-comment", help="Post report as a GitHub PR comment"),
80
- gitlab_pr_comment: bool = typer.Option(False, "--gitlab-pr-comment", help="Post report as a GitLab merge request comment"),
81
- fail_on_blocking: bool = typer.Option(
82
- False,
83
- "--fail-on-blocking",
84
- help="Exit non-zero when blocking gaps remain, so shell-driven agents can gate on $?.",
85
- ),
86
- project_root: Path = typer.Option(Path("."), "--project-root", help="Repository root containing .devcouncil/."),
87
- ):
88
- """
89
- Produce final evidence report.
90
- """
91
- if ctx.invoked_subcommand is not None:
92
- return
93
-
94
- root = project_root.expanduser().resolve()
95
- initialize_project(root, quiet=True)
96
- db = get_db(root)
97
- if not db:
98
- console.print("[red]DevCouncil state is unavailable in this directory.[/red]")
99
- raise typer.Exit(code=1)
100
-
101
- with db.get_session() as session:
102
- graph_repo = ArtifactGraphRepository(session)
103
- graph = graph_repo.load_graph()
104
- live_review = live_review_summary(root)
105
- TraceLogger(root).log_event(
106
- "report_generated",
107
- {
108
- "json": json_format,
109
- "github": github,
110
- "github_pr_comment": github_pr_comment,
111
- "gitlab_pr_comment": gitlab_pr_comment,
112
- "planning_only": planning_only,
113
- },
114
- summary="Generated DevCouncil report",
115
- )
116
-
117
- if github:
118
- asyncio.run(run_github_report(graph, root))
119
- return
120
-
121
- if github_pr_comment:
122
- asyncio.run(run_github_pr_comment(graph, live_review=live_review))
123
- return
124
-
125
- if gitlab_pr_comment:
126
- asyncio.run(run_gitlab_mr_comment(graph, live_review=live_review))
127
- return
128
-
129
- if json_format:
130
- output = ReportBuilder.build_json(graph, live_review=live_review)
131
- typer.echo(output)
132
- else:
133
- output = ReportBuilder.build_markdown(graph, live_review=live_review)
134
- console.print(Markdown(output))
135
-
136
- if fail_on_blocking and graph.blocking_gaps():
137
- raise typer.Exit(code=1)
@@ -1,33 +0,0 @@
1
- import typer
2
- from rich.console import Console
3
- from sqlmodel import delete
4
- from pathlib import Path
5
-
6
- from devcouncil.storage.db import get_db
7
- from devcouncil.storage.models import EvidenceModel, GapModel, RequirementModel, TaskModel
8
- from devcouncil.cli.commands.init import initialize_project
9
-
10
- console = Console()
11
-
12
-
13
- def reset_demo_state(
14
- yes: bool = typer.Option(False, "--yes", help="Confirm clearing planning/demo artifacts."),
15
- project_root: Path = typer.Option(Path("."), "--project-root", help="Repository root containing .devcouncil/."),
16
- ):
17
- """Clear demo planning artifacts from the local DevCouncil state database."""
18
- if not yes:
19
- console.print("[red]Refusing to clear state without --yes.[/red]")
20
- raise typer.Exit(code=1)
21
-
22
- root = project_root.expanduser().resolve()
23
- initialize_project(root, quiet=True)
24
- db = get_db(root)
25
- if not db:
26
- console.print("[red]DevCouncil state is unavailable in this directory.[/red]")
27
- raise typer.Exit(code=1)
28
-
29
- with db.get_session() as session:
30
- for model in (EvidenceModel, GapModel, TaskModel, RequirementModel):
31
- session.exec(delete(model))
32
-
33
- console.print("[green]Cleared requirements, tasks, gaps, and evidence from local state.[/green]")
@@ -1,59 +0,0 @@
1
- import typer
2
- from pathlib import Path
3
- from rich.console import Console
4
-
5
- from devcouncil.execution.checkpoints import CheckpointService
6
-
7
- app = typer.Typer()
8
- console = Console()
9
-
10
- @app.callback(invoke_without_command=True)
11
- def rollback(
12
- ctx: typer.Context,
13
- task_id: str = typer.Argument(..., help="ID of the task to rollback"),
14
- project_root: Path = typer.Option(Path("."), "--project-root", help="Repository root containing .devcouncil/."),
15
- ):
16
- """
17
- Revert changes using a task's git checkpoint.
18
- """
19
- if ctx.invoked_subcommand is not None:
20
- return
21
-
22
- root = project_root.expanduser().resolve()
23
- checkpoint_dir = root / ".devcouncil" / "checkpoints"
24
- checkpoint_file = checkpoint_dir / f"{task_id}-before.patch"
25
- after_patch = checkpoint_dir / f"{task_id}-after.patch"
26
- service = CheckpointService(root)
27
-
28
- if not checkpoint_file.exists() and not after_patch.exists():
29
- before_ref = CheckpointService.REF_BEFORE.format(task_id=task_id)
30
- after_ref = CheckpointService.REF_AFTER.format(task_id=task_id)
31
- if not service._ref_exists(before_ref) and not service._ref_exists(after_ref):
32
- console.print(
33
- f"[red]No checkpoint found for task {task_id}. Expected {after_patch} "
34
- f"or {checkpoint_file}.[/red]"
35
- )
36
- raise typer.Exit(code=1)
37
-
38
- console.print(f"Rolling back task [bold]{task_id}[/bold]...")
39
- result = service.rollback(task_id)
40
- if "failed" in result.message.lower() or result.message.startswith("No checkpoint"):
41
- console.print(f"[yellow]{result.message}[/yellow]")
42
- if checkpoint_file.exists():
43
- console.print(
44
- f"The before-patch at {checkpoint_file} captured the state before the task ran.\n"
45
- f"To manually reset:\n"
46
- f" 1. [bold]git stash[/bold] (if you want to keep current changes)\n"
47
- f" 2. [bold]git checkout -- .[/bold] (discard working tree changes)\n"
48
- f" 3. [bold]git apply {checkpoint_file}[/bold] (restore pre-task state)"
49
- )
50
- elif after_patch.exists():
51
- console.print(
52
- f"Only the after-patch at {after_patch} exists (it captured the task's changes).\n"
53
- f"To manually revert those changes from the working tree:\n"
54
- f" 1. [bold]git apply --stat {after_patch}[/bold] (inspect what the task changed)\n"
55
- f" 2. [bold]git apply -R {after_patch}[/bold] (reverse-apply the task's changes)"
56
- )
57
- raise typer.Exit(code=1)
58
-
59
- console.print(f"[green]Successfully rolled back task {task_id}.[/green] {result.message}")
@@ -1,289 +0,0 @@
1
- import typer
2
- from rich.console import Console
3
- from pathlib import Path
4
- from devcouncil.storage.db import get_db
5
- from devcouncil.storage.repositories import TaskRepository, RequirementRepository
6
- from devcouncil.executors.mini_swe import MiniSWEExecutor
7
- from devcouncil.executors.openhands import OpenHandsExecutor
8
- from devcouncil.executors.coding_cli import CodingCliExecutor
9
- from devcouncil.executors.agent_registry import (
10
- AGENT_ALIASES,
11
- BUILTIN_CODING_EXECUTOR_NAMES,
12
- load_cli_agent_specs,
13
- )
14
- from devcouncil.executors.native.agent import NativeAgent
15
- from devcouncil.llm.provider import create_provider, validate_model_provider
16
- from devcouncil.llm.router import ModelRouter
17
- from devcouncil.app.config import load_config, get_api_key
18
- from devcouncil.domain.evidence import CommandResult, DiffEvidence, TestEvidence
19
- from devcouncil.storage.repositories import GapRepository, EvidenceRepository, StateRepository
20
- from devcouncil.verification.verifier import Verifier
21
- from devcouncil.app.state_machine import ProjectPhase
22
- from devcouncil.cli.commands.init import initialize_project
23
- from devcouncil.telemetry.traces import TraceLogger
24
-
25
- console = Console()
26
- CODING_EXECUTOR_ALIASES = {name: name for name in BUILTIN_CODING_EXECUTOR_NAMES} | AGENT_ALIASES
27
-
28
- CODING_EXECUTORS = set(CODING_EXECUTOR_ALIASES.keys())
29
-
30
-
31
- def _custom_cli_agents(project_root: Path) -> set[str]:
32
- specs = load_cli_agent_specs(project_root)
33
- return {name for name, spec in specs.items() if not spec.built_in}
34
-
35
- def _current_changed_files(project_root: Path = Path(".")) -> list[str]:
36
- from devcouncil.verification.verifier import Verifier
37
-
38
- return Verifier(project_root).get_changed_files()
39
-
40
- def _capture_after_patch(task_id: str, project_root: Path = Path(".")):
41
- """Capture the diff after task execution for use by rollback."""
42
- try:
43
- from devcouncil.execution.checkpoints import CheckpointService
44
-
45
- CheckpointService(project_root).create_after(task_id)
46
- except Exception:
47
- pass # Non-critical — don't block execution
48
-
49
- def _capture_before_snapshot(task_id: str, project_root: Path = Path(".")):
50
- from devcouncil.execution.checkpoints import CheckpointService
51
-
52
- CheckpointService(project_root).create_before(task_id)
53
-
54
- def _record_project_phase(session, phase: ProjectPhase):
55
- StateRepository(session).record_phase(phase.value)
56
-
57
- def _verify_after_execution(session, task, reqs, router=None, project_root: Path = Path(".")) -> bool:
58
- """Run deterministic verification after an automated executor finishes."""
59
- import asyncio
60
-
61
- verifier = Verifier(project_root, router=router)
62
- gaps, evidence = asyncio.run(verifier.verify_task(task, reqs))
63
-
64
- gap_repo = GapRepository(session)
65
- evidence_repo = EvidenceRepository(session)
66
- gap_repo.delete_for_task(task.id)
67
- evidence_repo.delete_for_task(task.id)
68
-
69
- for gap in gaps:
70
- gap_repo.save(gap)
71
-
72
- for ev in evidence:
73
- if isinstance(ev, CommandResult):
74
- evidence_repo.save_command_result(task.id, ev)
75
- elif isinstance(ev, DiffEvidence):
76
- evidence_repo.save_diff_evidence(ev)
77
- elif isinstance(ev, TestEvidence):
78
- evidence_repo.save_test_evidence(ev, task.id)
79
-
80
- task.status = "blocked" if any(g.blocking for g in gaps) else "verified"
81
- return task.status == "verified"
82
-
83
-
84
- def _record_agent_verification(project_root: Path, task_id: str, executor: str, run_id: str | None, verified: bool) -> None:
85
- TraceLogger(project_root).log_event(
86
- "agent_run_verified",
87
- {"agent": executor, "verified": verified},
88
- run_id=run_id,
89
- task_id=task_id,
90
- summary=f"{executor} verification {'passed' if verified else 'blocked'} for {task_id}",
91
- )
92
-
93
- def run(
94
- task_id: str = typer.Argument(..., help="ID of the task to run"),
95
- executor: str = typer.Option(
96
- "manual",
97
- "--executor",
98
- "-e",
99
- help=(
100
- "Executor to use (manual, mini, openhands, native-preview, "
101
- "codex, gemini, claude, opencode, antigravity, warp, cursor, aider, "
102
- "copilot, goose, amp, qwen, crush, or a configured agent)"
103
- ),
104
- ),
105
- profile: str | None = typer.Option(None, "--profile", help="CLI-agent execution profile: default, yolo, prod, or a configured profile."),
106
- stream: bool = typer.Option(
107
- False,
108
- "--stream",
109
- help="Stream coding CLI stdout/stderr live (also enabled by execution.stream_cli_output).",
110
- ),
111
- project_root: Path = typer.Option(Path("."), "--project-root", help="Repository root containing .devcouncil/."),
112
- ):
113
- """
114
- Execute a specific task.
115
- """
116
- root = project_root.expanduser().resolve()
117
- initialize_project(root, quiet=True)
118
- db = get_db(root)
119
- if not db:
120
- console.print("[red]DevCouncil state is unavailable in this directory.[/red]")
121
- return
122
-
123
- with db.get_session() as session:
124
- task_repo = TaskRepository(session)
125
- task = task_repo.get_by_id(task_id)
126
- if not task:
127
- console.print(f"[red]Task {task_id} not found.[/red]")
128
- return
129
-
130
- from devcouncil.gating.policy import GatePolicy
131
- gate_policy = GatePolicy()
132
- gate_result = gate_policy.check_task_ready(task, root)
133
- if not gate_result.passed:
134
- console.print(f"[red]Task {task_id} is not ready for execution.[/red]")
135
- for gap in gate_result.gaps:
136
- if gap.blocking:
137
- console.print(f" - [red][BLOCKING][/red] {gap.description} (Fix: {gap.recommended_fix})")
138
- return
139
-
140
- console.print(f"Running task [bold]{task_id}[/bold] using [bold]{executor}[/bold] executor...")
141
-
142
- # 1. Create Git checkpoint
143
- try:
144
- from devcouncil.execution.checkpoints import CheckpointService
145
-
146
- result = CheckpointService(root).create_before(task_id)
147
- if result.patch_path:
148
- console.print(f"Created git checkpoint at {result.patch_path}")
149
- elif result.git_ref_created:
150
- console.print(f"Created git checkpoint ref {result.ref}")
151
- except Exception as e:
152
- console.print(f"[yellow]Warning: Failed to create git checkpoint: {e}[/yellow]")
153
-
154
- executor = executor.strip().lower().replace("_", "-")
155
- if executor not in CODING_EXECUTORS and executor not in _custom_cli_agents(root):
156
- ignored = [flag for flag, value in (("--profile", profile), ("--stream", stream)) if value]
157
- if ignored:
158
- console.print(
159
- f"[yellow]{' and '.join(ignored)} only apply to coding CLI executors and are ignored for '{executor}'.[/yellow]"
160
- )
161
- if executor == "manual":
162
- _record_project_phase(session, ProjectPhase.TASK_EXECUTING)
163
- task.status = "running"
164
- task_repo.save(task)
165
- console.print(f"\n[green]Task {task_id} is now marked as RUNNING.[/green]")
166
- console.print("Use 'dev prompt TASK-ID' to get the prompt for this task.")
167
- console.print("When finished, use 'dev verify TASK-ID' to check the results.")
168
- elif executor in CODING_EXECUTORS or executor in _custom_cli_agents(root):
169
- _record_project_phase(session, ProjectPhase.TASK_EXECUTING)
170
- req_repo = RequirementRepository(session)
171
- reqs = req_repo.get_all()
172
- cli_client = CODING_EXECUTOR_ALIASES.get(executor, executor)
173
- cli_executor = CodingCliExecutor(root, cli_client, profile=profile, stream_output=stream or None)
174
- exec_result = cli_executor.run_task(task, reqs)
175
- _capture_after_patch(task_id, root)
176
- if exec_result.success:
177
- _record_project_phase(session, ProjectPhase.TASK_VERIFYING)
178
- verified = _verify_after_execution(session, task, reqs, project_root=root)
179
- _record_agent_verification(root, task.id, cli_client, getattr(cli_executor, "last_run_id", None), verified)
180
- _record_project_phase(
181
- session,
182
- ProjectPhase.TASK_VERIFIED if verified else ProjectPhase.TASK_BLOCKED,
183
- )
184
- task_repo.save(task)
185
- run_id = getattr(cli_executor, "last_run_id", None)
186
- if run_id:
187
- run_dir = root / ".devcouncil" / "runs" / run_id
188
- console.print(f"Run artifacts: [dim]{run_dir}[/dim]")
189
- transcript_path = getattr(cli_executor, "last_transcript_path", None)
190
- if transcript_path:
191
- console.print(f"Transcript: [dim]{transcript_path}[/dim]")
192
- if verified:
193
- console.print(f"\n[green]{executor.upper()} finished and task {task_id} verified.[/green]")
194
- else:
195
- console.print(f"\n[yellow]{executor.upper()} finished, but task {task_id} is blocked by verification gaps.[/yellow]")
196
- else:
197
- console.print(f"\n[red]{executor.upper()} failed to start or execute: {exec_result.message}[/red]")
198
- elif executor == "mini":
199
- _record_project_phase(session, ProjectPhase.TASK_EXECUTING)
200
- req_repo = RequirementRepository(session)
201
- reqs = req_repo.get_all()
202
- mini_executor = MiniSWEExecutor(root)
203
- exec_result = mini_executor.run_task(task, reqs)
204
- _capture_after_patch(task_id, root)
205
- if exec_result.success:
206
- _record_project_phase(session, ProjectPhase.TASK_VERIFYING)
207
- verified = _verify_after_execution(session, task, reqs, project_root=root)
208
- _record_project_phase(
209
- session,
210
- ProjectPhase.TASK_VERIFIED if verified else ProjectPhase.TASK_BLOCKED,
211
- )
212
- task_repo.save(task)
213
- if verified:
214
- console.print(f"\n[green]mini-SWE-agent finished and task {task_id} verified.[/green]")
215
- else:
216
- console.print(f"\n[yellow]mini-SWE-agent finished, but task {task_id} is blocked by verification gaps.[/yellow]")
217
- else:
218
- console.print("\n[red]mini-SWE-agent failed to start or execute.[/red]")
219
- elif executor == "openhands":
220
- _record_project_phase(session, ProjectPhase.TASK_EXECUTING)
221
- req_repo = RequirementRepository(session)
222
- reqs = req_repo.get_all()
223
- oh_executor = OpenHandsExecutor(root)
224
- exec_result = oh_executor.run_task(task, reqs)
225
- _capture_after_patch(task_id, root)
226
- if exec_result.success:
227
- _record_project_phase(session, ProjectPhase.TASK_VERIFYING)
228
- verified = _verify_after_execution(session, task, reqs, project_root=root)
229
- _record_project_phase(
230
- session,
231
- ProjectPhase.TASK_VERIFIED if verified else ProjectPhase.TASK_BLOCKED,
232
- )
233
- task_repo.save(task)
234
- if verified:
235
- console.print(f"\n[green]OpenHands finished and task {task_id} verified.[/green]")
236
- else:
237
- console.print(f"\n[yellow]OpenHands finished, but task {task_id} is blocked by verification gaps.[/yellow]")
238
- else:
239
- console.print("\n[red]OpenHands failed to start or execute.[/red]")
240
- elif executor in {"native", "native-preview"}:
241
- # Load config for model routing and permissions
242
- try:
243
- config = load_config(root)
244
- validate_model_provider(config.models.provider)
245
- api_key = get_api_key(config.models.provider, root)
246
- except (FileNotFoundError, ValueError) as e:
247
- console.print(f"[red]{e}[/red]")
248
- return
249
-
250
- provider = create_provider(config.models.provider, api_key, project_root=root)
251
- role_config = {name: role.model_dump() for name, role in config.models.roles.items()}
252
- router = ModelRouter(provider, role_config, project_root=root)
253
-
254
- # Setup Permission System
255
- from devcouncil.execution.permissions import PermissionPolicy, PermissionManager
256
- from devcouncil.execution.task_runner import TaskRunner
257
-
258
- # Populate policy from config commands
259
- allowed_cmds = config.commands.test + config.commands.lint + config.commands.typecheck
260
- permission_policy = PermissionPolicy(
261
- allowed_shell_commands=allowed_cmds,
262
- )
263
- perm_manager = PermissionManager(permission_policy, root)
264
- task_runner = TaskRunner(root, perm_manager)
265
-
266
- req_repo = RequirementRepository(session)
267
- reqs = req_repo.get_all()
268
-
269
- _record_project_phase(session, ProjectPhase.TASK_EXECUTING)
270
- agent = NativeAgent(router, task_runner)
271
- exec_result = agent.run_task(task, reqs)
272
- _capture_after_patch(task_id, root)
273
-
274
- if exec_result.success:
275
- _record_project_phase(session, ProjectPhase.TASK_VERIFYING)
276
- verified = _verify_after_execution(session, task, reqs, router=router, project_root=root)
277
- _record_project_phase(
278
- session,
279
- ProjectPhase.TASK_VERIFIED if verified else ProjectPhase.TASK_BLOCKED,
280
- )
281
- task_repo.save(task)
282
- if verified:
283
- console.print(f"\n[green]Native agent finished and task {task_id} verified.[/green]")
284
- else:
285
- console.print(f"\n[yellow]Native agent finished, but task {task_id} is blocked by verification gaps.[/yellow]")
286
- else:
287
- console.print("\n[red]Native agent failed during execution.[/red]")
288
- else:
289
- console.print(f"[red]Executor {executor} not yet implemented.[/red]")