devcouncil 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (128) hide show
  1. package/LICENSE +201 -201
  2. package/README.md +62 -543
  3. package/package.json +1 -1
  4. package/pyproject.toml +29 -26
  5. package/src/devcouncil/__main__.py +4 -4
  6. package/src/devcouncil/app/__init__.py +28 -28
  7. package/src/devcouncil/app/config.py +135 -108
  8. package/src/devcouncil/app/errors.py +23 -23
  9. package/src/devcouncil/app/events.py +44 -44
  10. package/src/devcouncil/app/orchestrator.py +67 -67
  11. package/src/devcouncil/app/project_status.py +29 -0
  12. package/src/devcouncil/app/run_context.py +39 -39
  13. package/src/devcouncil/app/state_machine.py +108 -108
  14. package/src/devcouncil/artifacts/__init__.py +1 -1
  15. package/src/devcouncil/artifacts/coverage.py +96 -96
  16. package/src/devcouncil/artifacts/graph.py +143 -143
  17. package/src/devcouncil/artifacts/migrations.py +20 -20
  18. package/src/devcouncil/artifacts/schemas.py +23 -23
  19. package/src/devcouncil/artifacts/serializer.py +21 -21
  20. package/src/devcouncil/artifacts/validators.py +27 -27
  21. package/src/devcouncil/cli/commands/artifacts.py +51 -48
  22. package/src/devcouncil/cli/commands/ast.py +22 -0
  23. package/src/devcouncil/cli/commands/baseline.py +35 -32
  24. package/src/devcouncil/cli/commands/config.py +76 -54
  25. package/src/devcouncil/cli/commands/dashboard.py +26 -0
  26. package/src/devcouncil/cli/commands/doctor.py +86 -42
  27. package/src/devcouncil/cli/commands/go.py +237 -0
  28. package/src/devcouncil/cli/commands/hook.py +96 -29
  29. package/src/devcouncil/cli/commands/init.py +67 -56
  30. package/src/devcouncil/cli/commands/integrate.py +320 -14
  31. package/src/devcouncil/cli/commands/lsp.py +20 -0
  32. package/src/devcouncil/cli/commands/map.py +25 -21
  33. package/src/devcouncil/cli/commands/plan.py +257 -206
  34. package/src/devcouncil/cli/commands/prompt.py +36 -33
  35. package/src/devcouncil/cli/commands/repair.py +72 -69
  36. package/src/devcouncil/cli/commands/report.py +112 -54
  37. package/src/devcouncil/cli/commands/reset_demo_state.py +31 -28
  38. package/src/devcouncil/cli/commands/rollback.py +49 -47
  39. package/src/devcouncil/cli/commands/run.py +252 -207
  40. package/src/devcouncil/cli/commands/setup.py +159 -18
  41. package/src/devcouncil/cli/commands/show.py +76 -57
  42. package/src/devcouncil/cli/commands/status.py +117 -105
  43. package/src/devcouncil/cli/commands/tasks.py +55 -41
  44. package/src/devcouncil/cli/commands/trace.py +2 -1
  45. package/src/devcouncil/cli/commands/verify.py +158 -128
  46. package/src/devcouncil/cli/commands/version.py +20 -20
  47. package/src/devcouncil/cli/commands/watch.py +574 -0
  48. package/src/devcouncil/cli/main.py +42 -24
  49. package/src/devcouncil/council/prompts/arbiter.md +19 -19
  50. package/src/devcouncil/council/prompts/critic_a.md +10 -10
  51. package/src/devcouncil/council/prompts/critic_b.md +10 -10
  52. package/src/devcouncil/council/prompts/implementation_reviewer.md +16 -16
  53. package/src/devcouncil/council/prompts/planner_a.md +16 -16
  54. package/src/devcouncil/council/prompts/planner_b.md +16 -16
  55. package/src/devcouncil/council/prompts/rebuttal.md +10 -10
  56. package/src/devcouncil/council/prompts/spec_writer.md +12 -12
  57. package/src/devcouncil/domain/assumption.py +17 -17
  58. package/src/devcouncil/domain/critique.py +32 -32
  59. package/src/devcouncil/domain/evidence.py +27 -27
  60. package/src/devcouncil/domain/gap.py +26 -26
  61. package/src/devcouncil/domain/requirement.py +22 -22
  62. package/src/devcouncil/domain/task.py +26 -26
  63. package/src/devcouncil/execution/__init__.py +1 -1
  64. package/src/devcouncil/execution/context_builder.py +54 -54
  65. package/src/devcouncil/execution/executor.py +15 -15
  66. package/src/devcouncil/execution/hook_policy.py +24 -3
  67. package/src/devcouncil/execution/patch.py +28 -28
  68. package/src/devcouncil/execution/permissions.py +44 -44
  69. package/src/devcouncil/execution/prompt_builder.py +23 -23
  70. package/src/devcouncil/execution/task_runner.py +63 -63
  71. package/src/devcouncil/executors/__init__.py +1 -1
  72. package/src/devcouncil/executors/coding_cli.py +112 -0
  73. package/src/devcouncil/executors/mini_swe.py +63 -63
  74. package/src/devcouncil/executors/native/agent.py +81 -81
  75. package/src/devcouncil/executors/openhands.py +56 -56
  76. package/src/devcouncil/gating/__init__.py +1 -1
  77. package/src/devcouncil/gating/checks/clean_git.py +50 -45
  78. package/src/devcouncil/gating/checks/planned_files_check.py +32 -32
  79. package/src/devcouncil/gating/checks/requirement_coverage.py +26 -26
  80. package/src/devcouncil/gating/checks/secret_scan_check.py +34 -34
  81. package/src/devcouncil/gating/policy.py +157 -157
  82. package/src/devcouncil/indexing/__init__.py +1 -1
  83. package/src/devcouncil/indexing/ast_matcher.py +168 -0
  84. package/src/devcouncil/indexing/graph_index.py +48 -48
  85. package/src/devcouncil/indexing/lsp.py +120 -0
  86. package/src/devcouncil/indexing/repo_mapper.py +208 -204
  87. package/src/devcouncil/integrations/github.py +35 -35
  88. package/src/devcouncil/integrations/gitnexus.py +27 -27
  89. package/src/devcouncil/integrations/graphify.py +34 -34
  90. package/src/devcouncil/integrations/mcp/server.py +549 -96
  91. package/src/devcouncil/integrations/pr_comments.py +62 -0
  92. package/src/devcouncil/live/__init__.py +2 -0
  93. package/src/devcouncil/live/cards.py +207 -0
  94. package/src/devcouncil/live/models.py +63 -0
  95. package/src/devcouncil/live/repair_prompt.py +83 -0
  96. package/src/devcouncil/live/reviewer.py +70 -0
  97. package/src/devcouncil/live/signals.py +135 -0
  98. package/src/devcouncil/live/summary.py +34 -0
  99. package/src/devcouncil/live/tasks.py +18 -0
  100. package/src/devcouncil/live/transcripts.py +138 -0
  101. package/src/devcouncil/llm/__init__.py +1 -1
  102. package/src/devcouncil/llm/cache.py +38 -38
  103. package/src/devcouncil/llm/provider.py +146 -125
  104. package/src/devcouncil/llm/router.py +111 -111
  105. package/src/devcouncil/planning/__init__.py +1 -1
  106. package/src/devcouncil/planning/arbiter_service.py +57 -57
  107. package/src/devcouncil/planning/critique_service.py +66 -66
  108. package/src/devcouncil/planning/plan_service.py +46 -46
  109. package/src/devcouncil/planning/prompt_enhancer_service.py +86 -0
  110. package/src/devcouncil/planning/repair_service.py +39 -39
  111. package/src/devcouncil/planning/spec_service.py +44 -44
  112. package/src/devcouncil/reporting/github_check.py +32 -32
  113. package/src/devcouncil/reporting/json_report.py +20 -17
  114. package/src/devcouncil/reporting/markdown_report.py +68 -46
  115. package/src/devcouncil/reporting/report_builder.py +14 -14
  116. package/src/devcouncil/storage/db.py +66 -66
  117. package/src/devcouncil/storage/models.py +83 -83
  118. package/src/devcouncil/storage/repositories.py +299 -222
  119. package/src/devcouncil/telemetry/cost.py +34 -34
  120. package/src/devcouncil/telemetry/tracker.py +49 -49
  121. package/src/devcouncil/ui/__init__.py +1 -0
  122. package/src/devcouncil/ui/dashboard.py +122 -0
  123. package/src/devcouncil/utils/__init__.py +1 -1
  124. package/src/devcouncil/utils/redaction.py +141 -141
  125. package/src/devcouncil/verification/__init__.py +1 -1
  126. package/src/devcouncil/verification/implementation_reviewer.py +55 -55
  127. package/src/devcouncil/verification/verifier.py +319 -302
  128. package/uv.lock +1 -1
@@ -1,224 +1,269 @@
1
1
  import typer
2
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):
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.coding_cli import CodingCliExecutor
10
+ from devcouncil.executors.native.agent import NativeAgent
11
+ from devcouncil.llm.provider import create_provider, validate_model_provider
12
+ from devcouncil.llm.router import ModelRouter
13
+ from devcouncil.app.config import load_config, get_api_key
14
+ from devcouncil.domain.evidence import CommandResult, DiffEvidence, TestEvidence
15
+ from devcouncil.storage.repositories import GapRepository, EvidenceRepository, StateRepository
16
+ from devcouncil.verification.verifier import Verifier
17
+ from devcouncil.app.state_machine import ProjectPhase
18
+ from devcouncil.cli.commands.init import initialize_project
19
+
20
+ console = Console()
21
+ CODING_EXECUTOR_ALIASES = {
22
+ "codex": "codex",
23
+ "codex-cli": "codex",
24
+ "gemini": "gemini",
25
+ "gemini-cli": "gemini",
26
+ "claude": "claude",
27
+ "claude-code": "claude",
28
+ "claude-cli": "claude",
29
+ }
30
+
31
+ CODING_EXECUTORS = set(CODING_EXECUTOR_ALIASES.keys())
32
+
33
+ def _current_changed_files(project_root: Path = Path(".")) -> list[str]:
34
+ from devcouncil.verification.verifier import Verifier
35
+
36
+ return Verifier(project_root).get_changed_files()
37
+
38
+ def _capture_after_patch(task_id: str, project_root: Path = Path(".")):
26
39
  """Capture the diff after task execution for use by rollback."""
27
40
  try:
28
41
  from devcouncil.verification.verifier import Verifier
29
42
 
30
- checkpoint_dir = Path(".devcouncil/checkpoints")
43
+ checkpoint_dir = project_root / ".devcouncil" / "checkpoints"
31
44
  checkpoint_dir.mkdir(exist_ok=True)
32
- diff = Verifier(Path(".")).get_diff()
45
+ diff = Verifier(project_root).get_diff()
33
46
  if diff:
34
47
  with open(checkpoint_dir / f"{task_id}-after.patch", "w", encoding="utf-8") as f:
35
48
  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
49
+ except Exception:
50
+ pass # Non-critical — don't block execution
51
+
52
+ def _capture_before_snapshot(task_id: str, project_root: Path = Path(".")):
53
+ checkpoint_dir = project_root / ".devcouncil" / "checkpoints"
54
+ checkpoint_dir.mkdir(exist_ok=True)
55
+ snapshot = {
56
+ "task_id": task_id,
57
+ "changed_files": _current_changed_files(project_root),
58
+ }
59
+ (checkpoint_dir / f"{task_id}-before.json").write_text(
60
+ json.dumps(snapshot, indent=2),
61
+ encoding="utf-8",
62
+ )
63
+
64
+ def _record_project_phase(session, phase: ProjectPhase):
65
+ StateRepository(session).record_phase(phase.value)
66
+
67
+ def _verify_after_execution(session, task, reqs, router=None, project_root: Path = Path(".")) -> bool:
68
+ """Run deterministic verification after an automated executor finishes."""
69
+ import asyncio
70
+
71
+ verifier = Verifier(project_root, router=router)
72
+ gaps, evidence = asyncio.run(verifier.verify_task(task, reqs))
73
+
74
+ gap_repo = GapRepository(session)
75
+ evidence_repo = EvidenceRepository(session)
76
+ gap_repo.delete_for_task(task.id)
77
+ evidence_repo.delete_for_task(task.id)
78
+
79
+ for gap in gaps:
80
+ gap_repo.save(gap)
81
+
82
+ for ev in evidence:
83
+ if isinstance(ev, CommandResult):
84
+ evidence_repo.save_command_result(task.id, ev)
85
+ elif isinstance(ev, DiffEvidence):
86
+ evidence_repo.save_diff_evidence(ev)
87
+ elif isinstance(ev, TestEvidence):
88
+ evidence_repo.save_test_evidence(ev, task.id)
89
+
90
+ task.status = "blocked" if any(g.blocking for g in gaps) else "verified"
91
+ return task.status == "verified"
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="Executor to use (manual, mini, openhands, native, codex, codex-cli, gemini, gemini-cli, claude, claude-code, claude-cli)",
100
+ ),
101
+ project_root: Path = typer.Option(Path("."), "--project-root", help="Repository root containing .devcouncil/."),
102
+ ):
103
+ """
104
+ Execute a specific task.
105
+ """
106
+ root = project_root.expanduser().resolve()
107
+ initialize_project(root, quiet=True)
108
+ db = get_db(root)
109
+ if not db:
110
+ console.print("[red]DevCouncil state is unavailable in this directory.[/red]")
111
+ return
112
+
113
+ with db.get_session() as session:
114
+ task_repo = TaskRepository(session)
115
+ task = task_repo.get_by_id(task_id)
116
+ if not task:
117
+ console.print(f"[red]Task {task_id} not found.[/red]")
118
+ return
119
+
120
+ from devcouncil.gating.policy import GatePolicy
121
+ policy = GatePolicy()
122
+ gate_result = policy.check_task_ready(task, root)
123
+ if not gate_result.passed:
124
+ console.print(f"[red]Task {task_id} is not ready for execution.[/red]")
125
+ for gap in gate_result.gaps:
126
+ if gap.blocking:
127
+ console.print(f" - [red][BLOCKING][/red] {gap.description} (Fix: {gap.recommended_fix})")
128
+ return
129
+
130
+ console.print(f"Running task [bold]{task_id}[/bold] using [bold]{executor}[/bold] executor...")
131
+
132
+ # 1. Create Git checkpoint
112
133
  try:
113
134
  from devcouncil.verification.verifier import Verifier
114
135
 
115
- checkpoint_dir = Path(".devcouncil/checkpoints")
136
+ checkpoint_dir = root / ".devcouncil" / "checkpoints"
116
137
  checkpoint_dir.mkdir(exist_ok=True)
117
- _capture_before_snapshot(task_id)
118
- diff = Verifier(Path(".")).get_diff()
138
+ _capture_before_snapshot(task_id, root)
139
+ diff = Verifier(root).get_diff()
119
140
  if diff:
120
141
  with open(checkpoint_dir / f"{task_id}-before.patch", "w", encoding="utf-8") as f:
121
142
  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]")
143
+ console.print(f"Created git checkpoint at {checkpoint_dir}/{task_id}-before.patch")
144
+ except Exception as e:
145
+ console.print(f"[yellow]Warning: Failed to create git checkpoint: {e}[/yellow]")
146
+
147
+ executor = executor.strip().lower().replace("_", "-")
148
+ if executor == "manual":
149
+ _record_project_phase(session, ProjectPhase.TASK_EXECUTING)
150
+ task.status = "running"
151
+ task_repo.save(task)
152
+ console.print(f"\n[green]Task {task_id} is now marked as RUNNING.[/green]")
153
+ console.print("Use 'dev prompt TASK-ID' to get the prompt for this task.")
154
+ console.print("When finished, use 'dev verify TASK-ID' to check the results.")
155
+ elif executor in CODING_EXECUTORS:
156
+ _record_project_phase(session, ProjectPhase.TASK_EXECUTING)
157
+ req_repo = RequirementRepository(session)
158
+ reqs = req_repo.get_all()
159
+ cli_client = CODING_EXECUTOR_ALIASES[executor]
160
+ cli_executor = CodingCliExecutor(root, cli_client)
161
+ exec_result = cli_executor.run_task(task, reqs)
162
+ _capture_after_patch(task_id, root)
163
+ if exec_result.success:
164
+ _record_project_phase(session, ProjectPhase.TASK_VERIFYING)
165
+ verified = _verify_after_execution(session, task, reqs, project_root=root)
166
+ _record_project_phase(
167
+ session,
168
+ ProjectPhase.TASK_VERIFIED if verified else ProjectPhase.TASK_BLOCKED,
169
+ )
170
+ task_repo.save(task)
171
+ if verified:
172
+ console.print(f"\n[green]{executor.upper()} finished and task {task_id} verified.[/green]")
173
+ else:
174
+ console.print(f"\n[yellow]{executor.upper()} finished, but task {task_id} is blocked by verification gaps.[/yellow]")
175
+ else:
176
+ console.print(f"\n[red]{executor.upper()} failed to start or execute: {exec_result.message}[/red]")
177
+ elif executor == "mini":
178
+ _record_project_phase(session, ProjectPhase.TASK_EXECUTING)
179
+ req_repo = RequirementRepository(session)
180
+ reqs = req_repo.get_all()
181
+ mini_executor = MiniSWEExecutor(root)
182
+ exec_result = mini_executor.run_task(task, reqs)
183
+ _capture_after_patch(task_id, root)
184
+ if exec_result.success:
185
+ _record_project_phase(session, ProjectPhase.TASK_VERIFYING)
186
+ verified = _verify_after_execution(session, task, reqs, project_root=root)
187
+ _record_project_phase(
188
+ session,
189
+ ProjectPhase.TASK_VERIFIED if verified else ProjectPhase.TASK_BLOCKED,
190
+ )
191
+ task_repo.save(task)
192
+ if verified:
193
+ console.print(f"\n[green]mini-SWE-agent finished and task {task_id} verified.[/green]")
194
+ else:
195
+ console.print(f"\n[yellow]mini-SWE-agent finished, but task {task_id} is blocked by verification gaps.[/yellow]")
196
+ else:
197
+ console.print("\n[red]mini-SWE-agent failed to start or execute.[/red]")
198
+ elif executor == "openhands":
199
+ _record_project_phase(session, ProjectPhase.TASK_EXECUTING)
200
+ req_repo = RequirementRepository(session)
201
+ reqs = req_repo.get_all()
202
+ oh_executor = OpenHandsExecutor(root)
203
+ exec_result = oh_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]OpenHands finished and task {task_id} verified.[/green]")
215
+ else:
216
+ console.print(f"\n[yellow]OpenHands finished, but task {task_id} is blocked by verification gaps.[/yellow]")
217
+ else:
218
+ console.print("\n[red]OpenHands failed to start or execute.[/red]")
219
+ elif executor == "native":
220
+ # Load config for model routing and permissions
221
+ try:
222
+ config = load_config(root)
223
+ validate_model_provider(config.models.provider)
224
+ api_key = get_api_key(config.models.provider, root)
225
+ except (FileNotFoundError, ValueError) as e:
226
+ console.print(f"[red]{e}[/red]")
227
+ return
228
+
229
+ provider = create_provider(config.models.provider, api_key)
230
+ role_config = {name: role.model_dump() for name, role in config.models.roles.items()}
231
+ router = ModelRouter(provider, role_config)
232
+
233
+ # Setup Permission System
234
+ from devcouncil.execution.permissions import PermissionPolicy, PermissionManager
235
+ from devcouncil.execution.task_runner import TaskRunner
236
+
237
+ # Populate policy from config commands
238
+ allowed_cmds = config.commands.test + config.commands.lint + config.commands.typecheck
239
+ policy = PermissionPolicy(
240
+ allowed_shell_commands=allowed_cmds,
241
+ )
242
+ perm_manager = PermissionManager(policy, root)
243
+ task_runner = TaskRunner(root, perm_manager)
244
+
245
+ req_repo = RequirementRepository(session)
246
+ reqs = req_repo.get_all()
247
+
248
+ import asyncio
249
+ _record_project_phase(session, ProjectPhase.TASK_EXECUTING)
250
+ agent = NativeAgent(router, task_runner)
251
+ exec_result = asyncio.run(agent.run_task(task, reqs))
252
+ _capture_after_patch(task_id, root)
253
+
254
+ if exec_result.success:
255
+ _record_project_phase(session, ProjectPhase.TASK_VERIFYING)
256
+ verified = _verify_after_execution(session, task, reqs, router=router, project_root=root)
257
+ _record_project_phase(
258
+ session,
259
+ ProjectPhase.TASK_VERIFIED if verified else ProjectPhase.TASK_BLOCKED,
260
+ )
261
+ task_repo.save(task)
262
+ if verified:
263
+ console.print(f"\n[green]Native agent finished and task {task_id} verified.[/green]")
264
+ else:
265
+ console.print(f"\n[yellow]Native agent finished, but task {task_id} is blocked by verification gaps.[/yellow]")
266
+ else:
267
+ console.print("\n[red]Native agent failed during execution.[/red]")
268
+ else:
269
+ console.print(f"[red]Executor {executor} not yet implemented.[/red]")