devcouncil 0.1.0 → 0.2.0

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 (190) hide show
  1. package/LICENSE +201 -201
  2. package/README.md +197 -494
  3. package/package.json +9 -2
  4. package/pyproject.toml +62 -27
  5. package/src/devcouncil/__main__.py +4 -4
  6. package/src/devcouncil/app/__init__.py +28 -28
  7. package/src/devcouncil/app/config.py +297 -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 +163 -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/assets/__init__.py +1 -0
  22. package/src/devcouncil/assets/devcouncil-logo.svg +60 -0
  23. package/src/devcouncil/assets/devcouncil_logo_premium.png +0 -0
  24. package/src/devcouncil/cli/commands/agents.py +292 -0
  25. package/src/devcouncil/cli/commands/artifacts.py +54 -48
  26. package/src/devcouncil/cli/commands/ast.py +22 -0
  27. package/src/devcouncil/cli/commands/baseline.py +35 -32
  28. package/src/devcouncil/cli/commands/check.py +209 -0
  29. package/src/devcouncil/cli/commands/config.py +115 -54
  30. package/src/devcouncil/cli/commands/cost.py +57 -0
  31. package/src/devcouncil/cli/commands/dashboard.py +31 -0
  32. package/src/devcouncil/cli/commands/doctor.py +291 -47
  33. package/src/devcouncil/cli/commands/evidence.py +48 -0
  34. package/src/devcouncil/cli/commands/go.py +656 -0
  35. package/src/devcouncil/cli/commands/handoff.py +69 -0
  36. package/src/devcouncil/cli/commands/hook.py +209 -33
  37. package/src/devcouncil/cli/commands/init.py +204 -57
  38. package/src/devcouncil/cli/commands/integrate.py +1171 -76
  39. package/src/devcouncil/cli/commands/lsp.py +20 -0
  40. package/src/devcouncil/cli/commands/map.py +96 -22
  41. package/src/devcouncil/cli/commands/plan.py +422 -210
  42. package/src/devcouncil/cli/commands/prompt.py +48 -34
  43. package/src/devcouncil/cli/commands/repair.py +89 -69
  44. package/src/devcouncil/cli/commands/report.py +120 -54
  45. package/src/devcouncil/cli/commands/reset_demo_state.py +33 -28
  46. package/src/devcouncil/cli/commands/rollback.py +55 -54
  47. package/src/devcouncil/cli/commands/run.py +285 -220
  48. package/src/devcouncil/cli/commands/runs.py +223 -0
  49. package/src/devcouncil/cli/commands/scaffold.py +32 -0
  50. package/src/devcouncil/cli/commands/semantic.py +47 -0
  51. package/src/devcouncil/cli/commands/setup.py +300 -20
  52. package/src/devcouncil/cli/commands/shell.py +73 -0
  53. package/src/devcouncil/cli/commands/show.py +76 -57
  54. package/src/devcouncil/cli/commands/skills.py +88 -0
  55. package/src/devcouncil/cli/commands/status.py +141 -105
  56. package/src/devcouncil/cli/commands/tasks.py +55 -41
  57. package/src/devcouncil/cli/commands/trace.py +49 -4
  58. package/src/devcouncil/cli/commands/verify.py +293 -128
  59. package/src/devcouncil/cli/commands/version.py +20 -20
  60. package/src/devcouncil/cli/commands/watch.py +574 -0
  61. package/src/devcouncil/cli/commands/watch_fs.py +40 -0
  62. package/src/devcouncil/cli/main.py +92 -25
  63. package/src/devcouncil/council/prompts/arbiter.md +19 -19
  64. package/src/devcouncil/council/prompts/critic_a.md +10 -10
  65. package/src/devcouncil/council/prompts/critic_b.md +10 -10
  66. package/src/devcouncil/council/prompts/implementation_reviewer.md +16 -16
  67. package/src/devcouncil/council/prompts/planner_a.md +16 -16
  68. package/src/devcouncil/council/prompts/planner_b.md +16 -16
  69. package/src/devcouncil/council/prompts/rebuttal.md +10 -10
  70. package/src/devcouncil/council/prompts/spec_writer.md +12 -12
  71. package/src/devcouncil/domain/assumption.py +17 -17
  72. package/src/devcouncil/domain/critique.py +32 -32
  73. package/src/devcouncil/domain/evidence.py +47 -27
  74. package/src/devcouncil/domain/gap.py +52 -26
  75. package/src/devcouncil/domain/requirement.py +22 -22
  76. package/src/devcouncil/domain/task.py +55 -26
  77. package/src/devcouncil/execution/__init__.py +1 -1
  78. package/src/devcouncil/execution/checkpoints.py +246 -0
  79. package/src/devcouncil/execution/context_builder.py +54 -54
  80. package/src/devcouncil/execution/executor.py +15 -15
  81. package/src/devcouncil/execution/fs_watcher.py +180 -0
  82. package/src/devcouncil/execution/handoff.py +102 -0
  83. package/src/devcouncil/execution/hook_policy.py +186 -77
  84. package/src/devcouncil/execution/patch.py +77 -28
  85. package/src/devcouncil/execution/permissions.py +52 -59
  86. package/src/devcouncil/execution/policy_engine.py +343 -0
  87. package/src/devcouncil/execution/prompt_builder.py +650 -38
  88. package/src/devcouncil/execution/shell_session.py +225 -0
  89. package/src/devcouncil/execution/task_runner.py +68 -64
  90. package/src/devcouncil/executors/__init__.py +1 -1
  91. package/src/devcouncil/executors/agent_registry.py +575 -0
  92. package/src/devcouncil/executors/coding_cli.py +736 -0
  93. package/src/devcouncil/executors/mini_swe.py +63 -63
  94. package/src/devcouncil/executors/native/agent.py +186 -85
  95. package/src/devcouncil/executors/openhands.py +56 -56
  96. package/src/devcouncil/gating/__init__.py +1 -1
  97. package/src/devcouncil/gating/checks/clean_git.py +52 -45
  98. package/src/devcouncil/gating/checks/planned_files_check.py +32 -32
  99. package/src/devcouncil/gating/checks/requirement_coverage.py +26 -26
  100. package/src/devcouncil/gating/checks/secret_scan_check.py +53 -34
  101. package/src/devcouncil/gating/policy.py +315 -167
  102. package/src/devcouncil/hardware.py +184 -0
  103. package/src/devcouncil/indexing/__init__.py +1 -1
  104. package/src/devcouncil/indexing/ast_matcher.py +168 -0
  105. package/src/devcouncil/indexing/graph_index.py +48 -48
  106. package/src/devcouncil/indexing/lsp.py +161 -0
  107. package/src/devcouncil/indexing/repo_mapper.py +1455 -204
  108. package/src/devcouncil/indexing/semantic_index.py +205 -0
  109. package/src/devcouncil/integrations/actions.py +146 -0
  110. package/src/devcouncil/integrations/check.py +423 -0
  111. package/src/devcouncil/integrations/github.py +35 -35
  112. package/src/devcouncil/integrations/github_intent.py +142 -0
  113. package/src/devcouncil/integrations/gitnexus.py +62 -27
  114. package/src/devcouncil/integrations/graphify.py +34 -34
  115. package/src/devcouncil/integrations/mcp/server.py +2072 -96
  116. package/src/devcouncil/integrations/opencode_devcouncil_plugin.mjs +24 -0
  117. package/src/devcouncil/integrations/pr_comments.py +62 -0
  118. package/src/devcouncil/live/__init__.py +2 -0
  119. package/src/devcouncil/live/cards.py +349 -0
  120. package/src/devcouncil/live/models.py +63 -0
  121. package/src/devcouncil/live/repair_prompt.py +83 -0
  122. package/src/devcouncil/live/reviewer.py +70 -0
  123. package/src/devcouncil/live/signals.py +135 -0
  124. package/src/devcouncil/live/summary.py +34 -0
  125. package/src/devcouncil/live/tasks.py +18 -0
  126. package/src/devcouncil/live/transcripts.py +141 -0
  127. package/src/devcouncil/llm/__init__.py +1 -1
  128. package/src/devcouncil/llm/cache.py +42 -38
  129. package/src/devcouncil/llm/model_defaults.yaml +44 -0
  130. package/src/devcouncil/llm/provider.py +627 -125
  131. package/src/devcouncil/llm/router.py +303 -118
  132. package/src/devcouncil/optimization/__init__.py +1 -0
  133. package/src/devcouncil/optimization/gepa_agent.py +318 -0
  134. package/src/devcouncil/planning/__init__.py +1 -1
  135. package/src/devcouncil/planning/arbiter_service.py +57 -57
  136. package/src/devcouncil/planning/correction_manifest.py +303 -0
  137. package/src/devcouncil/planning/critique_service.py +71 -66
  138. package/src/devcouncil/planning/plan_service.py +60 -46
  139. package/src/devcouncil/planning/prompt_enhancer_service.py +167 -0
  140. package/src/devcouncil/planning/repair_service.py +39 -39
  141. package/src/devcouncil/planning/spec_service.py +70 -44
  142. package/src/devcouncil/repo/ci_scaffold.py +157 -0
  143. package/src/devcouncil/repo/gitignore.py +123 -0
  144. package/src/devcouncil/repo/sca.py +374 -0
  145. package/src/devcouncil/reporting/github_check.py +32 -32
  146. package/src/devcouncil/reporting/json_report.py +30 -17
  147. package/src/devcouncil/reporting/markdown_report.py +83 -46
  148. package/src/devcouncil/reporting/report_builder.py +14 -14
  149. package/src/devcouncil/skills/__init__.py +19 -0
  150. package/src/devcouncil/skills/library/README.md +46 -0
  151. package/src/devcouncil/skills/library/ai-training.md +50 -0
  152. package/src/devcouncil/skills/library/android.md +50 -0
  153. package/src/devcouncil/skills/library/backend.md +52 -0
  154. package/src/devcouncil/skills/library/core-engineering.md +95 -0
  155. package/src/devcouncil/skills/library/data-engineering.md +47 -0
  156. package/src/devcouncil/skills/library/desktop.md +46 -0
  157. package/src/devcouncil/skills/library/devops.md +48 -0
  158. package/src/devcouncil/skills/library/game-dev.md +46 -0
  159. package/src/devcouncil/skills/library/ios.md +48 -0
  160. package/src/devcouncil/skills/library/mobile-cross-platform.md +46 -0
  161. package/src/devcouncil/skills/library/security.md +48 -0
  162. package/src/devcouncil/skills/library/systems.md +48 -0
  163. package/src/devcouncil/skills/library/web.md +47 -0
  164. package/src/devcouncil/skills/library/windows.md +47 -0
  165. package/src/devcouncil/skills/registry.py +330 -0
  166. package/src/devcouncil/storage/db.py +147 -66
  167. package/src/devcouncil/storage/models.py +204 -83
  168. package/src/devcouncil/storage/native.py +557 -0
  169. package/src/devcouncil/storage/repositories.py +388 -249
  170. package/src/devcouncil/telemetry/cost.py +140 -34
  171. package/src/devcouncil/telemetry/model_pricing.yaml +48 -0
  172. package/src/devcouncil/telemetry/pricing.py +28 -0
  173. package/src/devcouncil/telemetry/traces.py +62 -7
  174. package/src/devcouncil/telemetry/tracker.py +52 -49
  175. package/src/devcouncil/ui/__init__.py +1 -0
  176. package/src/devcouncil/ui/dashboard.py +423 -0
  177. package/src/devcouncil/utils/__init__.py +1 -1
  178. package/src/devcouncil/utils/redaction.py +147 -141
  179. package/src/devcouncil/utils/subprocess_env.py +69 -0
  180. package/src/devcouncil/verification/__init__.py +1 -1
  181. package/src/devcouncil/verification/acceptance_compiler.py +125 -0
  182. package/src/devcouncil/verification/ad_hoc_check.py +129 -0
  183. package/src/devcouncil/verification/diff_coverage.py +353 -0
  184. package/src/devcouncil/verification/implementation_reviewer.py +55 -55
  185. package/src/devcouncil/verification/next_actions.py +189 -0
  186. package/src/devcouncil/verification/sandbox.py +178 -0
  187. package/src/devcouncil/verification/test_resolver.py +91 -0
  188. package/src/devcouncil/verification/verifier.py +1342 -307
  189. package/uv.lock +205 -64
  190. package/src/devcouncil/indexing/symbol_index.py +0 -0
@@ -1,224 +1,289 @@
1
1
  import typer
2
- import json
3
- from rich.console import Console
4
- from pathlib import Path
5
- from devcouncil.storage.db import get_db
6
- from devcouncil.storage.repositories import TaskRepository, RequirementRepository
7
- from devcouncil.executors.mini_swe import MiniSWEExecutor
8
- from devcouncil.executors.openhands import OpenHandsExecutor
9
- from devcouncil.executors.native.agent import NativeAgent
10
- from devcouncil.llm.provider import OpenRouterProvider
11
- from devcouncil.llm.router import ModelRouter
12
- from devcouncil.app.config import load_config, get_api_key
13
- from devcouncil.domain.evidence import CommandResult, DiffEvidence, TestEvidence
14
- from devcouncil.storage.repositories import GapRepository, EvidenceRepository, StateRepository
15
- from devcouncil.verification.verifier import Verifier
16
- from devcouncil.app.state_machine import ProjectPhase
17
-
18
- console = Console()
19
-
20
- def _current_changed_files() -> list[str]:
21
- from devcouncil.verification.verifier import Verifier
22
-
23
- return Verifier(Path(".")).get_changed_files()
24
-
25
- def _capture_after_patch(task_id: str):
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(".")):
26
41
  """Capture the diff after task execution for use by rollback."""
27
42
  try:
28
- from devcouncil.verification.verifier import Verifier
29
-
30
- checkpoint_dir = Path(".devcouncil/checkpoints")
31
- checkpoint_dir.mkdir(exist_ok=True)
32
- diff = Verifier(Path(".")).get_diff()
33
- if diff:
34
- with open(checkpoint_dir / f"{task_id}-after.patch", "w", encoding="utf-8") as f:
35
- f.write(diff)
36
- except Exception:
37
- pass # Non-critical — don't block execution
38
-
39
- def _capture_before_snapshot(task_id: str):
40
- checkpoint_dir = Path(".devcouncil/checkpoints")
41
- checkpoint_dir.mkdir(exist_ok=True)
42
- snapshot = {
43
- "task_id": task_id,
44
- "changed_files": _current_changed_files(),
45
- }
46
- (checkpoint_dir / f"{task_id}-before.json").write_text(
47
- json.dumps(snapshot, indent=2),
48
- encoding="utf-8",
49
- )
50
-
51
- def _record_project_phase(session, phase: ProjectPhase):
52
- StateRepository(session).record_phase(phase.value)
53
-
54
- def _verify_after_execution(session, task, reqs, router=None) -> bool:
55
- """Run deterministic verification after an automated executor finishes."""
56
- import asyncio
57
-
58
- verifier = Verifier(Path("."), router=router)
59
- gaps, evidence = asyncio.run(verifier.verify_task(task, reqs))
60
-
61
- gap_repo = GapRepository(session)
62
- evidence_repo = EvidenceRepository(session)
63
- gap_repo.delete_for_task(task.id)
64
- evidence_repo.delete_for_task(task.id)
65
-
66
- for gap in gaps:
67
- gap_repo.save(gap)
68
-
69
- for ev in evidence:
70
- if isinstance(ev, CommandResult):
71
- evidence_repo.save_command_result(task.id, ev)
72
- elif isinstance(ev, DiffEvidence):
73
- evidence_repo.save_diff_evidence(ev)
74
- elif isinstance(ev, TestEvidence):
75
- evidence_repo.save_test_evidence(ev, task.id)
76
-
77
- task.status = "blocked" if any(g.blocking for g in gaps) else "verified"
78
- return task.status == "verified"
79
-
80
- def run(
81
- task_id: str = typer.Argument(..., help="ID of the task to run"),
82
- executor: str = typer.Option("manual", "--executor", "-e", help="Executor to use (manual, shell)"),
83
- ):
84
- """
85
- Execute a specific task.
86
- """
87
- db = get_db()
88
- if not db:
89
- console.print("[red]DevCouncil not initialized. Run 'dev init' first.[/red]")
90
- return
91
-
92
- with db.get_session() as session:
93
- task_repo = TaskRepository(session)
94
- task = task_repo.get_by_id(task_id)
95
- if not task:
96
- console.print(f"[red]Task {task_id} not found.[/red]")
97
- return
98
-
99
- from devcouncil.gating.policy import GatePolicy
100
- policy = GatePolicy()
101
- gate_result = policy.check_task_ready(task, Path("."))
102
- if not gate_result.passed:
103
- console.print(f"[red]Task {task_id} is not ready for execution.[/red]")
104
- for gap in gate_result.gaps:
105
- if gap.blocking:
106
- console.print(f" - [red][BLOCKING][/red] {gap.description} (Fix: {gap.recommended_fix})")
107
- return
108
-
109
- console.print(f"Running task [bold]{task_id}[/bold] using [bold]{executor}[/bold] executor...")
110
-
111
- # 1. Create Git checkpoint
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
112
143
  try:
113
- from devcouncil.verification.verifier import Verifier
114
-
115
- checkpoint_dir = Path(".devcouncil/checkpoints")
116
- checkpoint_dir.mkdir(exist_ok=True)
117
- _capture_before_snapshot(task_id)
118
- diff = Verifier(Path(".")).get_diff()
119
- if diff:
120
- with open(checkpoint_dir / f"{task_id}-before.patch", "w", encoding="utf-8") as f:
121
- f.write(diff)
122
- console.print(f"Created git checkpoint at {checkpoint_dir}/{task_id}-before.patch")
123
- except Exception as e:
124
- console.print(f"[yellow]Warning: Failed to create git checkpoint: {e}[/yellow]")
125
-
126
- if executor == "manual":
127
- _record_project_phase(session, ProjectPhase.TASK_EXECUTING)
128
- task.status = "running"
129
- task_repo.save(task)
130
- console.print(f"\n[green]Task {task_id} is now marked as RUNNING.[/green]")
131
- console.print("Use 'dev prompt TASK-ID' to get the prompt for this task.")
132
- console.print("When finished, use 'dev verify TASK-ID' to check the results.")
133
- elif executor == "mini":
134
- _record_project_phase(session, ProjectPhase.TASK_EXECUTING)
135
- req_repo = RequirementRepository(session)
136
- reqs = req_repo.get_all()
137
- mini_executor = MiniSWEExecutor(Path("."))
138
- exec_result = mini_executor.run_task(task, reqs)
139
- _capture_after_patch(task_id)
140
- if exec_result.success:
141
- _record_project_phase(session, ProjectPhase.TASK_VERIFYING)
142
- verified = _verify_after_execution(session, task, reqs)
143
- _record_project_phase(
144
- session,
145
- ProjectPhase.TASK_VERIFIED if verified else ProjectPhase.TASK_BLOCKED,
146
- )
147
- task_repo.save(task)
148
- if verified:
149
- console.print(f"\n[green]mini-SWE-agent finished and task {task_id} verified.[/green]")
150
- else:
151
- console.print(f"\n[yellow]mini-SWE-agent finished, but task {task_id} is blocked by verification gaps.[/yellow]")
152
- else:
153
- console.print("\n[red]mini-SWE-agent failed to start or execute.[/red]")
154
- elif executor == "openhands":
155
- _record_project_phase(session, ProjectPhase.TASK_EXECUTING)
156
- req_repo = RequirementRepository(session)
157
- reqs = req_repo.get_all()
158
- oh_executor = OpenHandsExecutor(Path("."))
159
- exec_result = oh_executor.run_task(task, reqs)
160
- _capture_after_patch(task_id)
161
- if exec_result.success:
162
- _record_project_phase(session, ProjectPhase.TASK_VERIFYING)
163
- verified = _verify_after_execution(session, task, reqs)
164
- _record_project_phase(
165
- session,
166
- ProjectPhase.TASK_VERIFIED if verified else ProjectPhase.TASK_BLOCKED,
167
- )
168
- task_repo.save(task)
169
- if verified:
170
- console.print(f"\n[green]OpenHands finished and task {task_id} verified.[/green]")
171
- else:
172
- console.print(f"\n[yellow]OpenHands finished, but task {task_id} is blocked by verification gaps.[/yellow]")
173
- else:
174
- console.print("\n[red]OpenHands failed to start or execute.[/red]")
175
- elif executor == "native":
176
- # Load config for model routing and permissions
177
- try:
178
- config = load_config(Path("."))
179
- api_key = get_api_key(config.models.provider)
180
- except (FileNotFoundError, ValueError) as e:
181
- console.print(f"[red]{e}[/red]")
182
- return
183
-
184
- provider = OpenRouterProvider(api_key)
185
- role_config = {name: role.model_dump() for name, role in config.models.roles.items()}
186
- router = ModelRouter(provider, role_config)
187
-
188
- # Setup Permission System
189
- from devcouncil.execution.permissions import PermissionPolicy, PermissionManager
190
- from devcouncil.execution.task_runner import TaskRunner
191
-
192
- # Populate policy from config commands
193
- allowed_cmds = config.commands.test + config.commands.lint + config.commands.typecheck
194
- policy = PermissionPolicy(
195
- allowed_shell_commands=allowed_cmds,
196
- )
197
- perm_manager = PermissionManager(policy, Path("."))
198
- task_runner = TaskRunner(Path("."), perm_manager)
199
-
200
- req_repo = RequirementRepository(session)
201
- reqs = req_repo.get_all()
202
-
203
- import asyncio
204
- _record_project_phase(session, ProjectPhase.TASK_EXECUTING)
205
- agent = NativeAgent(router, task_runner)
206
- exec_result = asyncio.run(agent.run_task(task, reqs))
207
- _capture_after_patch(task_id)
208
-
209
- if exec_result.success:
210
- _record_project_phase(session, ProjectPhase.TASK_VERIFYING)
211
- verified = _verify_after_execution(session, task, reqs, router=router)
212
- _record_project_phase(
213
- session,
214
- ProjectPhase.TASK_VERIFIED if verified else ProjectPhase.TASK_BLOCKED,
215
- )
216
- task_repo.save(task)
217
- if verified:
218
- console.print(f"\n[green]Native agent finished and task {task_id} verified.[/green]")
219
- else:
220
- console.print(f"\n[yellow]Native agent finished, but task {task_id} is blocked by verification gaps.[/yellow]")
221
- else:
222
- console.print("\n[red]Native agent failed during execution.[/red]")
223
- else:
224
- console.print(f"[red]Executor {executor} not yet implemented.[/red]")
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]")