devcouncil 0.1.1 → 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.
- package/README.md +218 -96
- package/bin/devcouncil.js +130 -32
- package/package.json +7 -11
- package/pyproject.toml +0 -34
- package/src/devcouncil/__init__.py +0 -0
- package/src/devcouncil/__main__.py +0 -4
- package/src/devcouncil/app/__init__.py +0 -28
- package/src/devcouncil/app/config.py +0 -158
- package/src/devcouncil/app/errors.py +0 -23
- package/src/devcouncil/app/events.py +0 -44
- package/src/devcouncil/app/orchestrator.py +0 -92
- package/src/devcouncil/app/project_status.py +0 -29
- package/src/devcouncil/app/run_context.py +0 -39
- package/src/devcouncil/app/state_machine.py +0 -108
- package/src/devcouncil/artifacts/__init__.py +0 -1
- package/src/devcouncil/artifacts/coverage.py +0 -96
- package/src/devcouncil/artifacts/graph.py +0 -143
- package/src/devcouncil/artifacts/migrations.py +0 -20
- package/src/devcouncil/artifacts/schemas.py +0 -23
- package/src/devcouncil/artifacts/serializer.py +0 -21
- package/src/devcouncil/artifacts/validators.py +0 -27
- package/src/devcouncil/cli/__init__.py +0 -0
- package/src/devcouncil/cli/commands/__init__.py +0 -0
- package/src/devcouncil/cli/commands/artifacts.py +0 -51
- package/src/devcouncil/cli/commands/ast.py +0 -22
- package/src/devcouncil/cli/commands/baseline.py +0 -35
- package/src/devcouncil/cli/commands/config.py +0 -76
- package/src/devcouncil/cli/commands/dashboard.py +0 -26
- package/src/devcouncil/cli/commands/doctor.py +0 -140
- package/src/devcouncil/cli/commands/go.py +0 -237
- package/src/devcouncil/cli/commands/hook.py +0 -128
- package/src/devcouncil/cli/commands/init.py +0 -153
- package/src/devcouncil/cli/commands/integrate.py +0 -726
- package/src/devcouncil/cli/commands/lsp.py +0 -20
- package/src/devcouncil/cli/commands/map.py +0 -42
- package/src/devcouncil/cli/commands/mcp_server.py +0 -18
- package/src/devcouncil/cli/commands/plan.py +0 -327
- package/src/devcouncil/cli/commands/prompt.py +0 -50
- package/src/devcouncil/cli/commands/repair.py +0 -72
- package/src/devcouncil/cli/commands/report.py +0 -129
- package/src/devcouncil/cli/commands/reset_demo_state.py +0 -31
- package/src/devcouncil/cli/commands/rollback.py +0 -60
- package/src/devcouncil/cli/commands/run.py +0 -269
- package/src/devcouncil/cli/commands/setup.py +0 -223
- package/src/devcouncil/cli/commands/show.py +0 -76
- package/src/devcouncil/cli/commands/status.py +0 -117
- package/src/devcouncil/cli/commands/tasks.py +0 -55
- package/src/devcouncil/cli/commands/trace.py +0 -44
- package/src/devcouncil/cli/commands/verify.py +0 -193
- package/src/devcouncil/cli/commands/version.py +0 -20
- package/src/devcouncil/cli/commands/watch.py +0 -574
- package/src/devcouncil/cli/main.py +0 -88
- package/src/devcouncil/council/__init__.py +0 -0
- package/src/devcouncil/council/prompts/__init__.py +0 -0
- package/src/devcouncil/council/prompts/arbiter.md +0 -19
- package/src/devcouncil/council/prompts/critic_a.md +0 -10
- package/src/devcouncil/council/prompts/critic_b.md +0 -10
- package/src/devcouncil/council/prompts/implementation_reviewer.md +0 -16
- package/src/devcouncil/council/prompts/planner_a.md +0 -16
- package/src/devcouncil/council/prompts/planner_b.md +0 -16
- package/src/devcouncil/council/prompts/rebuttal.md +0 -10
- package/src/devcouncil/council/prompts/spec_writer.md +0 -12
- package/src/devcouncil/domain/__init__.py +0 -0
- package/src/devcouncil/domain/assumption.py +0 -17
- package/src/devcouncil/domain/critique.py +0 -32
- package/src/devcouncil/domain/evidence.py +0 -27
- package/src/devcouncil/domain/gap.py +0 -26
- package/src/devcouncil/domain/requirement.py +0 -22
- package/src/devcouncil/domain/task.py +0 -26
- package/src/devcouncil/execution/__init__.py +0 -1
- package/src/devcouncil/execution/context_builder.py +0 -60
- package/src/devcouncil/execution/executor.py +0 -15
- package/src/devcouncil/execution/hook_policy.py +0 -165
- package/src/devcouncil/execution/patch.py +0 -28
- package/src/devcouncil/execution/paths.py +0 -14
- package/src/devcouncil/execution/permissions.py +0 -92
- package/src/devcouncil/execution/prompt_builder.py +0 -59
- package/src/devcouncil/execution/task_runner.py +0 -166
- package/src/devcouncil/executors/__init__.py +0 -1
- package/src/devcouncil/executors/coding_cli.py +0 -112
- package/src/devcouncil/executors/mini_swe.py +0 -73
- package/src/devcouncil/executors/native/__init__.py +0 -0
- package/src/devcouncil/executors/native/agent.py +0 -107
- package/src/devcouncil/executors/openhands.py +0 -71
- package/src/devcouncil/gating/__init__.py +0 -1
- package/src/devcouncil/gating/checks/__init__.py +0 -0
- package/src/devcouncil/gating/checks/clean_git.py +0 -50
- package/src/devcouncil/gating/checks/planned_files_check.py +0 -32
- package/src/devcouncil/gating/checks/requirement_coverage.py +0 -26
- package/src/devcouncil/gating/checks/secret_scan_check.py +0 -34
- package/src/devcouncil/gating/policy.py +0 -190
- package/src/devcouncil/indexing/__init__.py +0 -1
- package/src/devcouncil/indexing/ast_matcher.py +0 -168
- package/src/devcouncil/indexing/graph_index.py +0 -48
- package/src/devcouncil/indexing/lsp.py +0 -120
- package/src/devcouncil/indexing/repo_mapper.py +0 -208
- package/src/devcouncil/indexing/symbol_index.py +0 -0
- package/src/devcouncil/integrations/code_review_graph.py +0 -163
- package/src/devcouncil/integrations/github.py +0 -39
- package/src/devcouncil/integrations/gitnexus.py +0 -27
- package/src/devcouncil/integrations/graphify.py +0 -34
- package/src/devcouncil/integrations/mcp/__init__.py +0 -0
- package/src/devcouncil/integrations/mcp/server.py +0 -599
- package/src/devcouncil/integrations/pr_comments.py +0 -62
- package/src/devcouncil/live/__init__.py +0 -2
- package/src/devcouncil/live/cards.py +0 -207
- package/src/devcouncil/live/models.py +0 -63
- package/src/devcouncil/live/repair_prompt.py +0 -83
- package/src/devcouncil/live/reviewer.py +0 -70
- package/src/devcouncil/live/signals.py +0 -135
- package/src/devcouncil/live/summary.py +0 -34
- package/src/devcouncil/live/tasks.py +0 -18
- package/src/devcouncil/live/transcripts.py +0 -138
- package/src/devcouncil/llm/__init__.py +0 -1
- package/src/devcouncil/llm/cache.py +0 -38
- package/src/devcouncil/llm/provider.py +0 -146
- package/src/devcouncil/llm/router.py +0 -125
- package/src/devcouncil/planning/__init__.py +0 -1
- package/src/devcouncil/planning/arbiter_service.py +0 -57
- package/src/devcouncil/planning/critique_service.py +0 -66
- package/src/devcouncil/planning/plan_service.py +0 -46
- package/src/devcouncil/planning/prompt_enhancer_service.py +0 -86
- package/src/devcouncil/planning/repair_service.py +0 -39
- package/src/devcouncil/planning/spec_service.py +0 -44
- package/src/devcouncil/repo/__init__.py +0 -0
- package/src/devcouncil/reporting/__init__.py +0 -0
- package/src/devcouncil/reporting/github_check.py +0 -32
- package/src/devcouncil/reporting/json_report.py +0 -20
- package/src/devcouncil/reporting/markdown_report.py +0 -68
- package/src/devcouncil/reporting/report_builder.py +0 -14
- package/src/devcouncil/storage/__init__.py +0 -0
- package/src/devcouncil/storage/db.py +0 -66
- package/src/devcouncil/storage/models.py +0 -83
- package/src/devcouncil/storage/repositories.py +0 -423
- package/src/devcouncil/telemetry/__init__.py +0 -0
- package/src/devcouncil/telemetry/cost.py +0 -34
- package/src/devcouncil/telemetry/traces.py +0 -91
- package/src/devcouncil/telemetry/tracker.py +0 -49
- package/src/devcouncil/ui/__init__.py +0 -1
- package/src/devcouncil/ui/dashboard.py +0 -122
- package/src/devcouncil/utils/__init__.py +0 -1
- package/src/devcouncil/utils/redaction.py +0 -141
- package/src/devcouncil/verification/__init__.py +0 -1
- package/src/devcouncil/verification/implementation_reviewer.py +0 -55
- package/src/devcouncil/verification/verifier.py +0 -530
- package/uv.lock +0 -1085
|
@@ -1,31 +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
|
-
):
|
|
16
|
-
"""Clear demo planning artifacts from the local DevCouncil state database."""
|
|
17
|
-
if not yes:
|
|
18
|
-
console.print("[red]Refusing to clear state without --yes.[/red]")
|
|
19
|
-
raise typer.Exit(code=1)
|
|
20
|
-
|
|
21
|
-
initialize_project(Path("."), quiet=True)
|
|
22
|
-
db = get_db()
|
|
23
|
-
if not db:
|
|
24
|
-
console.print("[red]DevCouncil state is unavailable in this directory.[/red]")
|
|
25
|
-
raise typer.Exit(code=1)
|
|
26
|
-
|
|
27
|
-
with db.get_session() as session:
|
|
28
|
-
for model in (EvidenceModel, GapModel, TaskModel, RequirementModel):
|
|
29
|
-
session.exec(delete(model))
|
|
30
|
-
|
|
31
|
-
console.print("[green]Cleared requirements, tasks, gaps, and evidence from local state.[/green]")
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import typer
|
|
2
|
-
import subprocess
|
|
3
|
-
from pathlib import Path
|
|
4
|
-
from rich.console import Console
|
|
5
|
-
|
|
6
|
-
app = typer.Typer()
|
|
7
|
-
console = Console()
|
|
8
|
-
|
|
9
|
-
@app.callback(invoke_without_command=True)
|
|
10
|
-
def rollback(
|
|
11
|
-
ctx: typer.Context,
|
|
12
|
-
task_id: str = typer.Argument(..., help="ID of the task to rollback"),
|
|
13
|
-
project_root: Path = typer.Option(Path("."), "--project-root", help="Repository root containing .devcouncil/."),
|
|
14
|
-
):
|
|
15
|
-
"""
|
|
16
|
-
Revert changes using a task's git checkpoint.
|
|
17
|
-
"""
|
|
18
|
-
if ctx.invoked_subcommand is not None:
|
|
19
|
-
return
|
|
20
|
-
|
|
21
|
-
root = project_root.expanduser().resolve()
|
|
22
|
-
checkpoint_file = root / ".devcouncil" / "checkpoints" / f"{task_id}-before.patch"
|
|
23
|
-
after_patch = root / ".devcouncil" / "checkpoints" / f"{task_id}-after.patch"
|
|
24
|
-
|
|
25
|
-
if not checkpoint_file.exists() and not after_patch.exists():
|
|
26
|
-
console.print(
|
|
27
|
-
f"[red]No checkpoint found for task {task_id}. Expected {after_patch} "
|
|
28
|
-
f"or {checkpoint_file}.[/red]"
|
|
29
|
-
)
|
|
30
|
-
raise typer.Exit(code=1)
|
|
31
|
-
|
|
32
|
-
console.print(f"Rolling back task [bold]{task_id}[/bold]...")
|
|
33
|
-
|
|
34
|
-
try:
|
|
35
|
-
if after_patch.exists():
|
|
36
|
-
# Reverse-apply the task's changes only
|
|
37
|
-
console.print(f"Applying reverse patch from [bold]{after_patch}[/bold]...")
|
|
38
|
-
subprocess.check_call(
|
|
39
|
-
["git", "apply", "-R", str(after_patch)],
|
|
40
|
-
cwd=root,
|
|
41
|
-
)
|
|
42
|
-
console.print(f"[green]Successfully rolled back task {task_id} changes.[/green]")
|
|
43
|
-
else:
|
|
44
|
-
# No after-patch, but we have the before-patch — warn and offer manual reset
|
|
45
|
-
console.print(
|
|
46
|
-
f"[yellow]No after-patch found at {after_patch}.[/yellow]\n"
|
|
47
|
-
f"The before-patch at {checkpoint_file} captured the state before the task ran.\n"
|
|
48
|
-
f"To manually reset:\n"
|
|
49
|
-
f" 1. [bold]git stash[/bold] (if you want to keep current changes)\n"
|
|
50
|
-
f" 2. [bold]git checkout -- .[/bold] (discard working tree changes)\n"
|
|
51
|
-
f" 3. [bold]git apply {checkpoint_file}[/bold] (restore pre-task state)"
|
|
52
|
-
)
|
|
53
|
-
except subprocess.CalledProcessError as e:
|
|
54
|
-
console.print(f"[red]Failed to apply reverse patch: {e}[/red]")
|
|
55
|
-
console.print("[yellow]The patch may conflict with current changes. Try resolving manually:[/yellow]")
|
|
56
|
-
console.print(f" git apply -R --3way {after_patch}")
|
|
57
|
-
raise typer.Exit(code=1)
|
|
58
|
-
except Exception as e:
|
|
59
|
-
console.print(f"[red]Failed to rollback: {e}[/red]")
|
|
60
|
-
raise typer.Exit(code=1)
|
|
@@ -1,269 +0,0 @@
|
|
|
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.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(".")):
|
|
39
|
-
"""Capture the diff after task execution for use by rollback."""
|
|
40
|
-
try:
|
|
41
|
-
from devcouncil.verification.verifier import Verifier
|
|
42
|
-
|
|
43
|
-
checkpoint_dir = project_root / ".devcouncil" / "checkpoints"
|
|
44
|
-
checkpoint_dir.mkdir(exist_ok=True)
|
|
45
|
-
diff = Verifier(project_root).get_diff()
|
|
46
|
-
if diff:
|
|
47
|
-
with open(checkpoint_dir / f"{task_id}-after.patch", "w", encoding="utf-8") as f:
|
|
48
|
-
f.write(diff)
|
|
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
|
|
133
|
-
try:
|
|
134
|
-
from devcouncil.verification.verifier import Verifier
|
|
135
|
-
|
|
136
|
-
checkpoint_dir = root / ".devcouncil" / "checkpoints"
|
|
137
|
-
checkpoint_dir.mkdir(exist_ok=True)
|
|
138
|
-
_capture_before_snapshot(task_id, root)
|
|
139
|
-
diff = Verifier(root).get_diff()
|
|
140
|
-
if diff:
|
|
141
|
-
with open(checkpoint_dir / f"{task_id}-before.patch", "w", encoding="utf-8") as f:
|
|
142
|
-
f.write(diff)
|
|
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]")
|
|
@@ -1,223 +0,0 @@
|
|
|
1
|
-
from pathlib import Path
|
|
2
|
-
import os
|
|
3
|
-
import shutil
|
|
4
|
-
import sys
|
|
5
|
-
|
|
6
|
-
import typer
|
|
7
|
-
import yaml
|
|
8
|
-
from rich.console import Console
|
|
9
|
-
from rich.panel import Panel
|
|
10
|
-
|
|
11
|
-
from devcouncil.app.config import load_config, load_local_secrets, provider_api_key_env_var
|
|
12
|
-
from devcouncil.cli.commands.doctor import render_doctor_check
|
|
13
|
-
from devcouncil.cli.commands.init import initialize_project
|
|
14
|
-
from devcouncil.cli.commands.integrate import (
|
|
15
|
-
_claude_command,
|
|
16
|
-
_codex_command,
|
|
17
|
-
_configure_native_hooks,
|
|
18
|
-
_configure,
|
|
19
|
-
_cursor_command,
|
|
20
|
-
_gemini_command,
|
|
21
|
-
)
|
|
22
|
-
from devcouncil.llm.provider import validate_model_provider
|
|
23
|
-
|
|
24
|
-
app = typer.Typer()
|
|
25
|
-
console = Console()
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
def _set_model_provider(project_root: Path, provider: str) -> None:
|
|
29
|
-
normalized = validate_model_provider(provider)
|
|
30
|
-
config_path = project_root / ".devcouncil" / "config.yaml"
|
|
31
|
-
raw_config = yaml.safe_load(config_path.read_text(encoding="utf-8")) or {}
|
|
32
|
-
raw_config.setdefault("models", {})
|
|
33
|
-
previous = raw_config["models"].get("provider", "openrouter")
|
|
34
|
-
raw_config["models"]["provider"] = normalized
|
|
35
|
-
config_path.write_text(yaml.dump(raw_config, default_flow_style=False), encoding="utf-8")
|
|
36
|
-
if previous != normalized:
|
|
37
|
-
console.print(f"[green]Updated model provider from {previous} to {normalized}.[/green]")
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
def _write_local_secret(project_root: Path, env_var: str, value: str) -> Path:
|
|
41
|
-
if "\n" in value or "\r" in value:
|
|
42
|
-
raise ValueError("API keys cannot contain newlines.")
|
|
43
|
-
secrets = load_local_secrets(project_root)
|
|
44
|
-
secrets[env_var] = value
|
|
45
|
-
path = project_root / ".devcouncil" / "secrets.env"
|
|
46
|
-
path.parent.mkdir(parents=True, exist_ok=True)
|
|
47
|
-
lines = [
|
|
48
|
-
"# Local DevCouncil secrets. This file is ignored by git.",
|
|
49
|
-
"# Process environment variables with the same name take precedence.",
|
|
50
|
-
*[f"{key}={val}" for key, val in sorted(secrets.items())],
|
|
51
|
-
"",
|
|
52
|
-
]
|
|
53
|
-
path.write_text("\n".join(lines), encoding="utf-8")
|
|
54
|
-
return path
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
def _configure_api_key(project_root: Path, api_key: str | None, skip_api_key: bool) -> None:
|
|
58
|
-
config = load_config(project_root)
|
|
59
|
-
provider = config.models.provider
|
|
60
|
-
env_var = provider_api_key_env_var(provider)
|
|
61
|
-
local_secrets = load_local_secrets(project_root)
|
|
62
|
-
|
|
63
|
-
if os.environ.get(env_var):
|
|
64
|
-
console.print(f"[green]{env_var} is already set in the environment.[/green]")
|
|
65
|
-
return
|
|
66
|
-
if local_secrets.get(env_var):
|
|
67
|
-
console.print(f"[green]{env_var} is already set in .devcouncil/secrets.env.[/green]")
|
|
68
|
-
return
|
|
69
|
-
if api_key:
|
|
70
|
-
_write_local_secret(project_root, env_var, api_key)
|
|
71
|
-
console.print(f"[green]Saved {env_var} to .devcouncil/secrets.env.[/green]")
|
|
72
|
-
return
|
|
73
|
-
if skip_api_key:
|
|
74
|
-
console.print(f"[yellow]Skipped {env_var} setup. Model-backed commands will ask again if it is missing.[/yellow]")
|
|
75
|
-
return
|
|
76
|
-
if not sys.stdin.isatty():
|
|
77
|
-
console.print(
|
|
78
|
-
f"[yellow]{env_var} is not set.[/yellow] "
|
|
79
|
-
f"Run [bold]dev setup --api-key YOUR_KEY[/bold] or set it in your shell before model-backed commands."
|
|
80
|
-
)
|
|
81
|
-
return
|
|
82
|
-
|
|
83
|
-
console.print()
|
|
84
|
-
console.print(Panel.fit(
|
|
85
|
-
"\n".join([
|
|
86
|
-
f"Provider: {provider}",
|
|
87
|
-
f"Required key: {env_var}",
|
|
88
|
-
"Press Enter without a value to skip for now.",
|
|
89
|
-
]),
|
|
90
|
-
title="Model API Key",
|
|
91
|
-
border_style="cyan",
|
|
92
|
-
))
|
|
93
|
-
entered = typer.prompt(f"{env_var}", default="", hide_input=True, show_default=False)
|
|
94
|
-
if not entered:
|
|
95
|
-
console.print(f"[yellow]Skipped {env_var} setup.[/yellow]")
|
|
96
|
-
return
|
|
97
|
-
_write_local_secret(project_root, env_var, entered)
|
|
98
|
-
console.print(f"[green]Saved {env_var} to .devcouncil/secrets.env.[/green]")
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
def _is_interactive_terminal() -> bool:
|
|
102
|
-
return sys.stdin.isatty()
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
def _configure_coding_cli_integrations(project_root: Path, apply: bool, gemini_scope: str) -> None:
|
|
106
|
-
console.print()
|
|
107
|
-
console.print("[bold]Coding CLI integration[/bold]")
|
|
108
|
-
commands = [
|
|
109
|
-
("Codex CLI", _codex_command(project_root)),
|
|
110
|
-
("Gemini CLI", _gemini_command(project_root, gemini_scope)),
|
|
111
|
-
("Claude Code", _claude_command(project_root, "local")),
|
|
112
|
-
("Cursor", _cursor_command(project_root)),
|
|
113
|
-
]
|
|
114
|
-
results = []
|
|
115
|
-
for tool, command in commands:
|
|
116
|
-
if apply and not shutil.which(command[0]):
|
|
117
|
-
console.print(f"[yellow]{tool} CLI not found on PATH. Skipping optional integration.[/yellow]")
|
|
118
|
-
continue
|
|
119
|
-
results.append(_configure(tool, command, apply))
|
|
120
|
-
_configure_native_hooks(project_root, "all", apply)
|
|
121
|
-
if apply and any(not ok for ok in results):
|
|
122
|
-
raise typer.Exit(code=1)
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
def _prompt_for_first_run_integrations(project_root: Path, apply: bool, gemini_scope: str) -> bool:
|
|
126
|
-
if not _is_interactive_terminal():
|
|
127
|
-
return False
|
|
128
|
-
|
|
129
|
-
console.print()
|
|
130
|
-
console.print(Panel.fit(
|
|
131
|
-
"\n".join([
|
|
132
|
-
"DevCouncil can configure supported coding CLIs now.",
|
|
133
|
-
"This adds MCP setup and native hook config for detected clients.",
|
|
134
|
-
"Missing optional clients are skipped.",
|
|
135
|
-
]),
|
|
136
|
-
title="Coding CLI Setup",
|
|
137
|
-
border_style="cyan",
|
|
138
|
-
))
|
|
139
|
-
if not typer.confirm("Set up coding CLI integrations now?", default=True):
|
|
140
|
-
console.print("[yellow]Skipped coding CLI integration setup.[/yellow]")
|
|
141
|
-
return False
|
|
142
|
-
|
|
143
|
-
_configure_coding_cli_integrations(project_root, apply=apply, gemini_scope=gemini_scope)
|
|
144
|
-
return True
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
@app.callback(invoke_without_command=True)
|
|
148
|
-
def setup(
|
|
149
|
-
ctx: typer.Context,
|
|
150
|
-
project_root: Path = typer.Option(
|
|
151
|
-
Path("."),
|
|
152
|
-
"--project-root",
|
|
153
|
-
help="Target project repository root. Defaults to the terminal's current directory.",
|
|
154
|
-
),
|
|
155
|
-
name: str | None = typer.Option(None, "--name", "-n", help="Project name for .devcouncil/config.yaml."),
|
|
156
|
-
integrate: bool = typer.Option(False, "--integrate", help="Configure supported coding CLI MCP integrations and native hooks."),
|
|
157
|
-
apply: bool = typer.Option(False, "--apply", help="Apply integration config instead of previewing commands."),
|
|
158
|
-
gemini_scope: str = typer.Option("project", "--gemini-scope", help="Gemini MCP config scope: project or user."),
|
|
159
|
-
provider: str | None = typer.Option(None, "--provider", help="Set models.provider before configuring the API key."),
|
|
160
|
-
api_key: str | None = typer.Option(None, "--api-key", help="Store the configured provider API key in local .devcouncil/secrets.env."),
|
|
161
|
-
skip_api_key: bool = typer.Option(False, "--skip-api-key", help="Skip the first-run model API key prompt."),
|
|
162
|
-
skip_integrations: bool = typer.Option(False, "--skip-integrations", help="Skip the first-run coding CLI integration prompt."),
|
|
163
|
-
):
|
|
164
|
-
"""
|
|
165
|
-
Initialize DevCouncil from a normal terminal in the target repository root.
|
|
166
|
-
|
|
167
|
-
Use the coding CLI later only for the generated dev prompt output.
|
|
168
|
-
"""
|
|
169
|
-
if ctx.invoked_subcommand is not None:
|
|
170
|
-
return
|
|
171
|
-
|
|
172
|
-
if gemini_scope not in {"project", "user"}:
|
|
173
|
-
console.print("[red]--gemini-scope must be 'project' or 'user'.[/red]")
|
|
174
|
-
raise typer.Exit(code=2)
|
|
175
|
-
|
|
176
|
-
root = project_root.expanduser().resolve()
|
|
177
|
-
created = initialize_project(root, project_name=name)
|
|
178
|
-
if not created:
|
|
179
|
-
console.print(f"[yellow]DevCouncil is already initialized at {root / '.devcouncil'}.[/yellow]")
|
|
180
|
-
|
|
181
|
-
if provider:
|
|
182
|
-
try:
|
|
183
|
-
_set_model_provider(root, provider)
|
|
184
|
-
except ValueError as e:
|
|
185
|
-
console.print(f"[red]{e}[/red]")
|
|
186
|
-
raise typer.Exit(code=2) from e
|
|
187
|
-
|
|
188
|
-
_configure_api_key(root, api_key, skip_api_key)
|
|
189
|
-
|
|
190
|
-
console.print()
|
|
191
|
-
render_doctor_check(root)
|
|
192
|
-
|
|
193
|
-
if integrate:
|
|
194
|
-
_configure_coding_cli_integrations(root, apply=apply, gemini_scope=gemini_scope)
|
|
195
|
-
elif created and not skip_integrations:
|
|
196
|
-
_prompt_for_first_run_integrations(root, apply=True, gemini_scope=gemini_scope)
|
|
197
|
-
|
|
198
|
-
console.print()
|
|
199
|
-
console.print(Panel.fit(
|
|
200
|
-
"\n".join([
|
|
201
|
-
"[bold]Next commands[/bold]",
|
|
202
|
-
f"Keep running DevCouncil commands in this terminal at: {root}",
|
|
203
|
-
"One-command agent path:",
|
|
204
|
-
"dev e2e \"Describe the implementation goal\"",
|
|
205
|
-
"",
|
|
206
|
-
"Manual sidecar path:",
|
|
207
|
-
"dev plan \"Describe the implementation goal\"",
|
|
208
|
-
"dev tasks",
|
|
209
|
-
"dev run TASK-001 --executor manual",
|
|
210
|
-
"dev prompt TASK-001",
|
|
211
|
-
"Paste only the dev prompt output into your coding CLI.",
|
|
212
|
-
"Paste only the dev prompt output into your coding CLI, or run directly:",
|
|
213
|
-
"dev run TASK-001 --executor codex",
|
|
214
|
-
"dev run TASK-001 --executor gemini",
|
|
215
|
-
"dev run TASK-001 --executor claude",
|
|
216
|
-
"dev verify TASK-001",
|
|
217
|
-
"",
|
|
218
|
-
"Use [bold]dev setup --integrate[/bold] to preview coding CLI MCP and native hook setup.",
|
|
219
|
-
"Use [bold]dev setup --integrate --apply[/bold] to configure detected clients.",
|
|
220
|
-
]),
|
|
221
|
-
title="DevCouncil is ready",
|
|
222
|
-
border_style="green",
|
|
223
|
-
))
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
import json
|
|
2
|
-
from pathlib import Path
|
|
3
|
-
|
|
4
|
-
import typer
|
|
5
|
-
from rich.console import Console
|
|
6
|
-
from rich.panel import Panel
|
|
7
|
-
from devcouncil.cli.commands.init import initialize_project
|
|
8
|
-
from devcouncil.storage.db import get_db
|
|
9
|
-
from devcouncil.storage.repositories import TaskRepository, RequirementRepository
|
|
10
|
-
|
|
11
|
-
app = typer.Typer()
|
|
12
|
-
console = Console()
|
|
13
|
-
|
|
14
|
-
@app.callback(invoke_without_command=True)
|
|
15
|
-
def show(
|
|
16
|
-
ctx: typer.Context,
|
|
17
|
-
task_id: str = typer.Argument(..., help="ID of the task to show"),
|
|
18
|
-
json_format: bool = typer.Option(False, "--json", help="Output machine-readable JSON."),
|
|
19
|
-
project_root: Path = typer.Option(Path("."), "--project-root", help="Repository root containing .devcouncil/."),
|
|
20
|
-
):
|
|
21
|
-
"""
|
|
22
|
-
Show details of a specific task.
|
|
23
|
-
"""
|
|
24
|
-
if ctx.invoked_subcommand is not None:
|
|
25
|
-
return
|
|
26
|
-
|
|
27
|
-
root = project_root.expanduser().resolve()
|
|
28
|
-
initialize_project(root, quiet=True)
|
|
29
|
-
db = get_db(root)
|
|
30
|
-
if not db:
|
|
31
|
-
raise typer.Exit(code=1)
|
|
32
|
-
|
|
33
|
-
with db.get_session() as session:
|
|
34
|
-
task_repo = TaskRepository(session)
|
|
35
|
-
req_repo = RequirementRepository(session)
|
|
36
|
-
|
|
37
|
-
task = task_repo.get_by_id(task_id)
|
|
38
|
-
if not task:
|
|
39
|
-
console.print(f"[red]Task {task_id} not found.[/red]")
|
|
40
|
-
raise typer.Exit(code=1)
|
|
41
|
-
|
|
42
|
-
reqs = req_repo.get_all()
|
|
43
|
-
req_map = {r.id: r for r in reqs}
|
|
44
|
-
|
|
45
|
-
if json_format:
|
|
46
|
-
linked_requirements = [
|
|
47
|
-
req_map[req_id].model_dump()
|
|
48
|
-
for req_id in task.requirement_ids
|
|
49
|
-
if req_id in req_map
|
|
50
|
-
]
|
|
51
|
-
typer.echo(json.dumps({
|
|
52
|
-
"task": task.model_dump(),
|
|
53
|
-
"linked_requirements": linked_requirements,
|
|
54
|
-
}, indent=2))
|
|
55
|
-
return
|
|
56
|
-
|
|
57
|
-
output = f"[bold]Status:[/bold] {task.status}\n\n"
|
|
58
|
-
output += f"[bold]Description:[/bold]\n{task.description}\n\n"
|
|
59
|
-
|
|
60
|
-
output += "[bold]Linked Requirements:[/bold]\n"
|
|
61
|
-
for req_id in task.requirement_ids:
|
|
62
|
-
req = req_map.get(req_id)
|
|
63
|
-
if req:
|
|
64
|
-
output += f" - [cyan]{req.id}[/cyan]: {req.title}\n"
|
|
65
|
-
else:
|
|
66
|
-
output += f" - [cyan]{req_id}[/cyan]: (Requirement not found)\n"
|
|
67
|
-
|
|
68
|
-
output += "\n[bold]Planned Files:[/bold]\n"
|
|
69
|
-
for pf in task.planned_files:
|
|
70
|
-
output += f" - {pf.path} ({pf.allowed_change}): {pf.reason}\n"
|
|
71
|
-
|
|
72
|
-
output += "\n[bold]Expected Tests:[/bold]\n"
|
|
73
|
-
for et in task.expected_tests:
|
|
74
|
-
output += f" - {et}\n"
|
|
75
|
-
|
|
76
|
-
console.print(Panel(output, title=f"Task {task.id}: {task.title}", expand=False))
|