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,20 +0,0 @@
|
|
|
1
|
-
import json
|
|
2
|
-
from pathlib import Path
|
|
3
|
-
|
|
4
|
-
import typer
|
|
5
|
-
|
|
6
|
-
from devcouncil.indexing.lsp import LspInspector
|
|
7
|
-
|
|
8
|
-
app = typer.Typer(help="Inspect optional LSP integration readiness.")
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
@app.command("inspect")
|
|
12
|
-
def inspect(
|
|
13
|
-
project_root: Path = typer.Option(Path("."), "--project-root", help="Repository root."),
|
|
14
|
-
):
|
|
15
|
-
"""Print detected language servers and initialize payloads."""
|
|
16
|
-
root = project_root.expanduser().resolve()
|
|
17
|
-
if not root.exists():
|
|
18
|
-
typer.echo(json.dumps({"languages": [], "servers": [], "initialize_requests": {}, "error": f"{root} does not exist"}, indent=2))
|
|
19
|
-
raise typer.Exit(code=1)
|
|
20
|
-
typer.echo(LspInspector(root).summary_json())
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import json
|
|
2
|
-
from pathlib import Path
|
|
3
|
-
|
|
4
|
-
import typer
|
|
5
|
-
from rich.console import Console
|
|
6
|
-
|
|
7
|
-
from devcouncil.cli.commands.init import initialize_project
|
|
8
|
-
from devcouncil.indexing.repo_mapper import RepoMapper
|
|
9
|
-
from devcouncil.integrations.code_review_graph import CodeReviewGraphAdapter
|
|
10
|
-
from devcouncil.storage.db import get_db
|
|
11
|
-
|
|
12
|
-
console = Console()
|
|
13
|
-
status_console = Console(stderr=True)
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
def map_repo(
|
|
17
|
-
goal: str = typer.Argument("", help="Goal text used for candidate-file ranking."),
|
|
18
|
-
output: Path = typer.Option(
|
|
19
|
-
Path(".devcouncil/repo_map.json"),
|
|
20
|
-
"--output",
|
|
21
|
-
"-o",
|
|
22
|
-
help="Path to write repo_map.json.",
|
|
23
|
-
),
|
|
24
|
-
project_root: Path = typer.Option(Path("."), "--project-root", help="Repository root containing .devcouncil/."),
|
|
25
|
-
):
|
|
26
|
-
"""Build the deterministic repository map without calling an LLM."""
|
|
27
|
-
root = project_root.expanduser().resolve()
|
|
28
|
-
initialize_project(root, quiet=True)
|
|
29
|
-
if not get_db(root):
|
|
30
|
-
raise typer.Exit(code=1)
|
|
31
|
-
|
|
32
|
-
repo_map = RepoMapper(root).map_repo(goal)
|
|
33
|
-
graph_context = CodeReviewGraphAdapter(root).get_context()
|
|
34
|
-
output = output if output.is_absolute() else root / output
|
|
35
|
-
output.parent.mkdir(parents=True, exist_ok=True)
|
|
36
|
-
output.write_text(repo_map.model_dump_json(indent=2), encoding="utf-8")
|
|
37
|
-
if graph_context.available:
|
|
38
|
-
graph_output = output.with_name("code_review_graph_context.json")
|
|
39
|
-
graph_output.write_text(graph_context.model_dump_json(indent=2), encoding="utf-8")
|
|
40
|
-
status_console.print(f"[green]Wrote code-review-graph context to {graph_output}[/green]")
|
|
41
|
-
typer.echo(json.dumps(repo_map.model_dump(), indent=2))
|
|
42
|
-
status_console.print(f"[green]Wrote repository map to {output}[/green]")
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import asyncio
|
|
2
|
-
|
|
3
|
-
import typer
|
|
4
|
-
|
|
5
|
-
from devcouncil.integrations.mcp.server import run
|
|
6
|
-
|
|
7
|
-
app = typer.Typer()
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
@app.callback(invoke_without_command=True)
|
|
11
|
-
def mcp_server(ctx: typer.Context):
|
|
12
|
-
"""
|
|
13
|
-
Start the DevCouncil MCP server over stdio.
|
|
14
|
-
"""
|
|
15
|
-
if ctx.invoked_subcommand is not None:
|
|
16
|
-
return
|
|
17
|
-
|
|
18
|
-
asyncio.run(run())
|
|
@@ -1,327 +0,0 @@
|
|
|
1
|
-
import typer
|
|
2
|
-
import asyncio
|
|
3
|
-
import json
|
|
4
|
-
import datetime
|
|
5
|
-
from typing import Any
|
|
6
|
-
from rich.console import Console
|
|
7
|
-
from rich.panel import Panel
|
|
8
|
-
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
9
|
-
from pathlib import Path
|
|
10
|
-
|
|
11
|
-
from devcouncil.storage.db import get_db
|
|
12
|
-
from devcouncil.storage.repositories import (
|
|
13
|
-
GapRepository,
|
|
14
|
-
PlanningStateRepository,
|
|
15
|
-
)
|
|
16
|
-
from devcouncil.indexing.repo_mapper import RepoMapper
|
|
17
|
-
from devcouncil.integrations.code_review_graph import CodeReviewGraphAdapter
|
|
18
|
-
from devcouncil.llm.provider import MockProvider, create_provider, validate_model_provider
|
|
19
|
-
from devcouncil.llm.router import ModelRouter
|
|
20
|
-
from devcouncil.planning.spec_service import SpecService
|
|
21
|
-
from devcouncil.planning.prompt_enhancer_service import PromptEnhancerService
|
|
22
|
-
from devcouncil.planning.plan_service import PlanService
|
|
23
|
-
from devcouncil.planning.critique_service import CritiqueService
|
|
24
|
-
from devcouncil.planning.arbiter_service import ArbiterService
|
|
25
|
-
from devcouncil.gating.policy import GatePolicy
|
|
26
|
-
from devcouncil.app.orchestrator import Orchestrator
|
|
27
|
-
from devcouncil.app.state_machine import ProjectPhase
|
|
28
|
-
from devcouncil.app.config import ModelRoleConfig, load_config, get_api_key
|
|
29
|
-
from devcouncil.cli.commands.init import initialize_project
|
|
30
|
-
from devcouncil.telemetry.traces import TraceLogger
|
|
31
|
-
|
|
32
|
-
app = typer.Typer()
|
|
33
|
-
console = Console()
|
|
34
|
-
|
|
35
|
-
DEFAULT_PLANNING_MODEL = "anthropic/claude-3.5-sonnet"
|
|
36
|
-
REQUIRED_PLANNING_ROLES = (
|
|
37
|
-
"prompt_enhancer",
|
|
38
|
-
"spec_writer",
|
|
39
|
-
"planner_a",
|
|
40
|
-
"planner_b",
|
|
41
|
-
"critic_a",
|
|
42
|
-
"critic_b",
|
|
43
|
-
"arbiter",
|
|
44
|
-
)
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
def _decision_ids(items: list[Any]) -> set[str]:
|
|
48
|
-
ids: set[str] = set()
|
|
49
|
-
for item in items:
|
|
50
|
-
if isinstance(item, str):
|
|
51
|
-
ids.add(item)
|
|
52
|
-
elif isinstance(item, dict):
|
|
53
|
-
value = item.get("id") or item.get("finding_id")
|
|
54
|
-
if value:
|
|
55
|
-
ids.add(str(value))
|
|
56
|
-
return ids
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
def _reconcile_findings(findings, decision):
|
|
60
|
-
accepted_ids = set(decision.accepted_finding_ids)
|
|
61
|
-
rejected_ids = _decision_ids(decision.rejected_finding_ids)
|
|
62
|
-
reconciled = []
|
|
63
|
-
for finding in findings:
|
|
64
|
-
if finding.id in accepted_ids:
|
|
65
|
-
reconciled.append(finding.model_copy(update={"status": "converted"}))
|
|
66
|
-
elif finding.id in rejected_ids:
|
|
67
|
-
reconciled.append(finding.model_copy(update={"status": "rejected"}))
|
|
68
|
-
else:
|
|
69
|
-
reconciled.append(finding)
|
|
70
|
-
return reconciled
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
def _ensure_planning_roles(config) -> None:
|
|
74
|
-
fallback = config.models.roles.get("spec_writer")
|
|
75
|
-
if fallback is None and config.models.roles:
|
|
76
|
-
fallback = next(iter(config.models.roles.values()))
|
|
77
|
-
if fallback is None:
|
|
78
|
-
fallback = ModelRoleConfig(model=DEFAULT_PLANNING_MODEL)
|
|
79
|
-
|
|
80
|
-
for role in REQUIRED_PLANNING_ROLES:
|
|
81
|
-
config.models.roles.setdefault(role, fallback.model_copy())
|
|
82
|
-
|
|
83
|
-
async def run_plan_flow(
|
|
84
|
-
goal: str,
|
|
85
|
-
requirements_only: bool = False,
|
|
86
|
-
dry_run: bool = False,
|
|
87
|
-
persist: bool = True,
|
|
88
|
-
project_root: Path = Path("."),
|
|
89
|
-
):
|
|
90
|
-
root = project_root.expanduser().resolve()
|
|
91
|
-
initialize_project(root, quiet=True)
|
|
92
|
-
db = get_db(root)
|
|
93
|
-
if not db:
|
|
94
|
-
console.print("[red]DevCouncil state is unavailable in this directory.[/red]")
|
|
95
|
-
return []
|
|
96
|
-
|
|
97
|
-
# Load validated config
|
|
98
|
-
config = load_config(root)
|
|
99
|
-
_ensure_planning_roles(config)
|
|
100
|
-
|
|
101
|
-
api_key = None
|
|
102
|
-
if not dry_run:
|
|
103
|
-
try:
|
|
104
|
-
validate_model_provider(config.models.provider)
|
|
105
|
-
api_key = get_api_key(config.models.provider, root)
|
|
106
|
-
except ValueError as e:
|
|
107
|
-
console.print(f"[red]{e}[/red]")
|
|
108
|
-
return []
|
|
109
|
-
|
|
110
|
-
orchestrator = Orchestrator(root, persist_state=persist)
|
|
111
|
-
orchestrator.reset_state_machine(ProjectPhase.NEW)
|
|
112
|
-
run_id = datetime.datetime.now(datetime.timezone.utc).strftime("%Y%m%dT%H%M%SZ") + "-plan"
|
|
113
|
-
await orchestrator.start_run(run_id, goal)
|
|
114
|
-
|
|
115
|
-
if dry_run:
|
|
116
|
-
# Override config models to be unique roles for mock mapping
|
|
117
|
-
for role in REQUIRED_PLANNING_ROLES:
|
|
118
|
-
config.models.roles[role].model = f"mock/{role}"
|
|
119
|
-
|
|
120
|
-
provider = MockProvider()
|
|
121
|
-
provider.responses = {
|
|
122
|
-
"mock/prompt_enhancer": json.dumps({
|
|
123
|
-
"original_goal": goal,
|
|
124
|
-
"enhanced_goal": (
|
|
125
|
-
f"Plan and implement {goal} using the mapped repository's "
|
|
126
|
-
"existing patterns, tests, and verification gates."
|
|
127
|
-
),
|
|
128
|
-
"codebase_context": ["Use the repository map to target existing application and test structure."],
|
|
129
|
-
"debate_focus": ["Compare minimal implementation scope against production-readiness concerns."],
|
|
130
|
-
"constraints": [f"Do not broaden beyond: {goal}."]
|
|
131
|
-
}),
|
|
132
|
-
"mock/spec_writer": json.dumps({
|
|
133
|
-
"requirements": [{"id": "REQ-001", "title": "Mock Req", "description": "Desc", "priority": "high", "source": "user", "acceptance_criteria": []}],
|
|
134
|
-
"assumptions": [],
|
|
135
|
-
"blocking_questions": []
|
|
136
|
-
}),
|
|
137
|
-
"mock/planner_a": [
|
|
138
|
-
json.dumps({
|
|
139
|
-
"id": "PLAN-A", "rationale": "Simple", "tasks": [{"id": "TASK-001", "title": "Mock Task", "description": "Desc", "requirement_ids": ["REQ-001"], "acceptance_criterion_ids": [], "planned_files": [], "expected_tests": [], "allowed_commands": [], "status": "planned"}]
|
|
140
|
-
}),
|
|
141
|
-
json.dumps({"rebuttals": []})
|
|
142
|
-
],
|
|
143
|
-
"mock/planner_b": [
|
|
144
|
-
json.dumps({
|
|
145
|
-
"id": "PLAN-B", "rationale": "Robust", "tasks": [{"id": "TASK-001", "title": "Mock Task", "description": "Desc", "requirement_ids": ["REQ-001"], "acceptance_criterion_ids": [], "planned_files": [], "expected_tests": [], "allowed_commands": [], "status": "planned"}]
|
|
146
|
-
}),
|
|
147
|
-
json.dumps({"rebuttals": []})
|
|
148
|
-
],
|
|
149
|
-
"mock/critic_a": '{"findings": []}',
|
|
150
|
-
"mock/critic_b": '{"findings": []}',
|
|
151
|
-
"mock/arbiter": json.dumps({
|
|
152
|
-
"accepted_finding_ids": [], "rejected_finding_ids": [],
|
|
153
|
-
"final_requirements": [{"id": "REQ-001", "title": "Mock Req", "description": "Desc", "priority": "high", "source": "user", "acceptance_criteria": [{"id": "AC-1", "description": "Test it", "verification_method": "unit_test"}]}],
|
|
154
|
-
"final_tasks": [{"id": "TASK-001", "title": "Mock Task", "description": "Desc", "requirement_ids": ["REQ-001"], "acceptance_criterion_ids": ["AC-1"], "planned_files": [{"path": "test.py", "reason": "logic", "allowed_change": "modify"}], "expected_tests": [], "allowed_commands": [], "status": "planned"}]
|
|
155
|
-
}),
|
|
156
|
-
}
|
|
157
|
-
# Special case: PlanService calls use the same model names.
|
|
158
|
-
# I'll modify PlanService to use a slightly different role string if needed,
|
|
159
|
-
# but for Dry Run, let's just make the MockProvider return based on the schema requested.
|
|
160
|
-
else:
|
|
161
|
-
provider = create_provider(config.models.provider, api_key)
|
|
162
|
-
|
|
163
|
-
# Build role config after dry-run overrides so mocks are routed correctly.
|
|
164
|
-
role_config = {name: role.model_dump() for name, role in config.models.roles.items()}
|
|
165
|
-
router = ModelRouter(provider, role_config)
|
|
166
|
-
|
|
167
|
-
prompt_enhancer = PromptEnhancerService(router)
|
|
168
|
-
spec_service = SpecService(router)
|
|
169
|
-
plan_service = PlanService(router)
|
|
170
|
-
critique_service = CritiqueService(router)
|
|
171
|
-
arbiter_service = ArbiterService(router)
|
|
172
|
-
mapper = RepoMapper(root)
|
|
173
|
-
|
|
174
|
-
with Progress(
|
|
175
|
-
SpinnerColumn(),
|
|
176
|
-
TextColumn("[progress.description]{task.description}"),
|
|
177
|
-
transient=True,
|
|
178
|
-
) as progress:
|
|
179
|
-
# 1. Repo Map
|
|
180
|
-
progress.add_task(description="Mapping repository...", total=None)
|
|
181
|
-
repo_map = mapper.map_repo(goal)
|
|
182
|
-
repo_map_json = repo_map.model_dump_json(indent=2)
|
|
183
|
-
orchestrator.save_run_artifact("repo_map.json", json.loads(repo_map_json))
|
|
184
|
-
graph_context = CodeReviewGraphAdapter(root).get_context()
|
|
185
|
-
if graph_context.available:
|
|
186
|
-
orchestrator.save_run_artifact("code_review_graph_context.json", graph_context.model_dump())
|
|
187
|
-
await orchestrator.transition_to(ProjectPhase.REPO_MAPPED)
|
|
188
|
-
|
|
189
|
-
# 2. Codebase-specific prompt enhancement
|
|
190
|
-
progress.add_task(description="Enhancing prompt for codebase debate...", total=None)
|
|
191
|
-
prompt_enhancement = await prompt_enhancer.enhance_prompt(
|
|
192
|
-
goal,
|
|
193
|
-
repo_map_json,
|
|
194
|
-
graph_context.model_dump_json() if graph_context.available else None,
|
|
195
|
-
)
|
|
196
|
-
debate_goal = prompt_enhancement.debate_prompt()
|
|
197
|
-
orchestrator.save_run_artifact("prompt_enhancement.json", prompt_enhancement.model_dump())
|
|
198
|
-
TraceLogger(root).log_event(
|
|
199
|
-
"prompt_enhanced",
|
|
200
|
-
{
|
|
201
|
-
"original_goal": goal,
|
|
202
|
-
"enhanced_goal": prompt_enhancement.enhanced_goal,
|
|
203
|
-
"codebase_context_count": len(prompt_enhancement.codebase_context),
|
|
204
|
-
"constraint_count": len(prompt_enhancement.constraints),
|
|
205
|
-
"debate_focus_count": len(prompt_enhancement.debate_focus),
|
|
206
|
-
"artifact": f".devcouncil/runs/{run_id}/prompt_enhancement.json",
|
|
207
|
-
},
|
|
208
|
-
run_id=run_id,
|
|
209
|
-
summary="Prompt enhanced for codebase-specific debate.",
|
|
210
|
-
)
|
|
211
|
-
|
|
212
|
-
# 3. Spec / Requirements
|
|
213
|
-
progress.add_task(description="Generating requirements...", total=None)
|
|
214
|
-
spec_output = await spec_service.generate_spec(debate_goal, repo_map_json)
|
|
215
|
-
orchestrator.save_run_artifact("requirements.json", spec_output.model_dump())
|
|
216
|
-
await orchestrator.transition_to(ProjectPhase.REQUIREMENTS_DRAFTED)
|
|
217
|
-
|
|
218
|
-
if requirements_only:
|
|
219
|
-
console.print(Panel(f"Found {len(spec_output.requirements)} requirements.", title="Requirements Generated"))
|
|
220
|
-
return []
|
|
221
|
-
|
|
222
|
-
# 4. Independent Plans
|
|
223
|
-
progress.add_task(description="Generating Plan A (Pragmatic)...", total=None)
|
|
224
|
-
plan_a = await plan_service.generate_plan("planner_a", debate_goal, json.dumps([r.model_dump() for r in spec_output.requirements]), repo_map_json)
|
|
225
|
-
orchestrator.save_run_artifact("plan_a.json", plan_a.model_dump())
|
|
226
|
-
|
|
227
|
-
progress.add_task(description="Generating Plan B (Robust)...", total=None)
|
|
228
|
-
plan_b = await plan_service.generate_plan("planner_b", debate_goal, json.dumps([r.model_dump() for r in spec_output.requirements]), repo_map_json)
|
|
229
|
-
orchestrator.save_run_artifact("plan_b.json", plan_b.model_dump())
|
|
230
|
-
await orchestrator.transition_to(ProjectPhase.PLANS_GENERATED)
|
|
231
|
-
|
|
232
|
-
# 5. Cross-Critique
|
|
233
|
-
progress.add_task(description="Critiquing Plan B...", total=None)
|
|
234
|
-
critique_a = await critique_service.generate_critique("critic_a", plan_b.model_dump_json(), json.dumps([r.model_dump() for r in spec_output.requirements]))
|
|
235
|
-
orchestrator.save_run_artifact("critique_a.json", critique_a.model_dump())
|
|
236
|
-
|
|
237
|
-
progress.add_task(description="Critiquing Plan A...", total=None)
|
|
238
|
-
critique_b = await critique_service.generate_critique("critic_b", plan_a.model_dump_json(), json.dumps([r.model_dump() for r in spec_output.requirements]))
|
|
239
|
-
orchestrator.save_run_artifact("critique_b.json", critique_b.model_dump())
|
|
240
|
-
await orchestrator.transition_to(ProjectPhase.CRITIQUES_GENERATED)
|
|
241
|
-
|
|
242
|
-
# 6. Rebuttals
|
|
243
|
-
progress.add_task(description="Generating rebuttals...", total=None)
|
|
244
|
-
rebuttal_a = await critique_service.generate_rebuttal("planner_a", plan_a.model_dump_json(), critique_b.model_dump_json())
|
|
245
|
-
orchestrator.save_run_artifact("rebuttal_a.json", rebuttal_a.model_dump())
|
|
246
|
-
rebuttal_b = await critique_service.generate_rebuttal("planner_b", plan_b.model_dump_json(), critique_a.model_dump_json())
|
|
247
|
-
orchestrator.save_run_artifact("rebuttal_b.json", rebuttal_b.model_dump())
|
|
248
|
-
|
|
249
|
-
# 7. Arbitration
|
|
250
|
-
progress.add_task(description="Arbitrating final plan...", total=None)
|
|
251
|
-
decision = await arbiter_service.arbitrate(
|
|
252
|
-
debate_goal,
|
|
253
|
-
json.dumps([r.model_dump() for r in spec_output.requirements]),
|
|
254
|
-
plan_a.model_dump_json(),
|
|
255
|
-
plan_b.model_dump_json(),
|
|
256
|
-
critique_a.model_dump_json(),
|
|
257
|
-
critique_b.model_dump_json(),
|
|
258
|
-
rebuttal_a.model_dump_json(),
|
|
259
|
-
rebuttal_b.model_dump_json()
|
|
260
|
-
)
|
|
261
|
-
orchestrator.save_run_artifact("decision.json", decision.model_dump())
|
|
262
|
-
await orchestrator.transition_to(ProjectPhase.ARBITRATED)
|
|
263
|
-
reconciled_findings = _reconcile_findings([*critique_a.findings, *critique_b.findings], decision)
|
|
264
|
-
final_tasks = [task.model_copy(update={"status": "planned"}) for task in decision.final_tasks]
|
|
265
|
-
|
|
266
|
-
console.print("[green]Planning complete![/green]")
|
|
267
|
-
console.print(f"[blue]Prompt enhancement:[/blue] .devcouncil/runs/{run_id}/prompt_enhancement.json")
|
|
268
|
-
if dry_run:
|
|
269
|
-
console.print("[blue](DRY RUN: No actual LLM calls were made)[/blue]")
|
|
270
|
-
if not persist:
|
|
271
|
-
console.print("[blue](DRY RUN: Final requirements/tasks were not persisted)[/blue]")
|
|
272
|
-
console.print(f"Final Requirements: [bold]{len(decision.final_requirements)}[/bold]")
|
|
273
|
-
console.print(f"Final Tasks: [bold]{len(final_tasks)}[/bold]")
|
|
274
|
-
|
|
275
|
-
# 8. Check Gates
|
|
276
|
-
policy = GatePolicy()
|
|
277
|
-
result = policy.check_plan_approval(
|
|
278
|
-
decision.final_requirements,
|
|
279
|
-
final_tasks,
|
|
280
|
-
assumptions=spec_output.assumptions,
|
|
281
|
-
findings=reconciled_findings,
|
|
282
|
-
blocking_questions=spec_output.blocking_questions,
|
|
283
|
-
)
|
|
284
|
-
if persist:
|
|
285
|
-
with db.get_session() as session:
|
|
286
|
-
GapRepository(session).delete_plan_gaps()
|
|
287
|
-
|
|
288
|
-
if result.passed:
|
|
289
|
-
if persist:
|
|
290
|
-
with db.get_session() as session:
|
|
291
|
-
PlanningStateRepository(session).replace_active_plan(
|
|
292
|
-
decision.final_requirements,
|
|
293
|
-
spec_output.assumptions,
|
|
294
|
-
final_tasks,
|
|
295
|
-
reconciled_findings,
|
|
296
|
-
)
|
|
297
|
-
|
|
298
|
-
console.print("[green]Plan approved by gates.[/green]")
|
|
299
|
-
await orchestrator.transition_to(ProjectPhase.PLAN_APPROVED)
|
|
300
|
-
return [task.id for task in final_tasks]
|
|
301
|
-
else:
|
|
302
|
-
if persist:
|
|
303
|
-
with db.get_session() as session:
|
|
304
|
-
gap_repo = GapRepository(session)
|
|
305
|
-
for gap in result.gaps:
|
|
306
|
-
gap_repo.save(gap)
|
|
307
|
-
console.print("[yellow]Plan generated but failed gates. See status for gaps.[/yellow]")
|
|
308
|
-
await orchestrator.transition_to(ProjectPhase.AWAITING_USER_DECISIONS)
|
|
309
|
-
return []
|
|
310
|
-
|
|
311
|
-
@app.command()
|
|
312
|
-
def plan(
|
|
313
|
-
goal: str = typer.Argument(..., help="The goal of the implementation"),
|
|
314
|
-
requirements_only: bool = typer.Option(False, "--requirements-only", help="Only generate requirements"),
|
|
315
|
-
dry_run: bool = typer.Option(False, "--dry-run", help="Simulate planning without LLM calls"),
|
|
316
|
-
persist: bool = typer.Option(
|
|
317
|
-
False,
|
|
318
|
-
"--persist/--no-persist",
|
|
319
|
-
help="Persist dry-run planning artifacts into the main state database.",
|
|
320
|
-
),
|
|
321
|
-
project_root: Path = typer.Option(Path("."), "--project-root", help="Repository root containing .devcouncil/."),
|
|
322
|
-
):
|
|
323
|
-
"""
|
|
324
|
-
Run the full planning cycle (Repo map -> Spec -> Plan A/B -> Critique -> Arbiter).
|
|
325
|
-
"""
|
|
326
|
-
should_persist = persist or not dry_run
|
|
327
|
-
asyncio.run(run_plan_flow(goal, requirements_only, dry_run, should_persist, project_root))
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import typer
|
|
2
|
-
from rich.console import Console
|
|
3
|
-
from rich.markdown import Markdown
|
|
4
|
-
from devcouncil.cli.commands.init import initialize_project
|
|
5
|
-
from devcouncil.storage.db import get_db
|
|
6
|
-
from devcouncil.storage.repositories import TaskRepository, RequirementRepository
|
|
7
|
-
from pathlib import Path
|
|
8
|
-
|
|
9
|
-
from devcouncil.execution.prompt_builder import PromptBuilder
|
|
10
|
-
|
|
11
|
-
app = typer.Typer()
|
|
12
|
-
console = Console()
|
|
13
|
-
|
|
14
|
-
@app.callback(invoke_without_command=True)
|
|
15
|
-
def prompt(
|
|
16
|
-
ctx: typer.Context,
|
|
17
|
-
task_id: str = typer.Argument(..., help="ID of the task to generate a prompt for"),
|
|
18
|
-
pretty: bool = typer.Option(False, "--pretty", help="Render the prompt for terminal reading."),
|
|
19
|
-
project_root: Path = typer.Option(Path("."), "--project-root", help="Repository root containing .devcouncil/."),
|
|
20
|
-
):
|
|
21
|
-
"""
|
|
22
|
-
Generate a constrained prompt for 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
|
-
|
|
44
|
-
builder = PromptBuilder(root)
|
|
45
|
-
task_prompt = builder.build_task_prompt(task, reqs)
|
|
46
|
-
|
|
47
|
-
if pretty:
|
|
48
|
-
console.print(Markdown(task_prompt))
|
|
49
|
-
else:
|
|
50
|
-
typer.echo(task_prompt, nl=not task_prompt.endswith("\n"))
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import typer
|
|
2
|
-
from rich.console import Console
|
|
3
|
-
from devcouncil.storage.db import get_db
|
|
4
|
-
from devcouncil.storage.repositories import TaskRepository, GapRepository
|
|
5
|
-
from devcouncil.planning.repair_service import RepairService
|
|
6
|
-
from devcouncil.execution.context_builder import ContextBuilder
|
|
7
|
-
from devcouncil.llm.provider import create_provider, validate_model_provider
|
|
8
|
-
from devcouncil.llm.router import ModelRouter
|
|
9
|
-
from devcouncil.app.config import load_config, get_api_key
|
|
10
|
-
from devcouncil.cli.commands.init import initialize_project
|
|
11
|
-
import asyncio
|
|
12
|
-
from pathlib import Path
|
|
13
|
-
|
|
14
|
-
app = typer.Typer()
|
|
15
|
-
console = Console()
|
|
16
|
-
|
|
17
|
-
async def run_repair_flow():
|
|
18
|
-
initialize_project(Path("."), quiet=True)
|
|
19
|
-
db = get_db()
|
|
20
|
-
if not db:
|
|
21
|
-
console.print("[red]DevCouncil state is unavailable in this directory.[/red]")
|
|
22
|
-
return
|
|
23
|
-
|
|
24
|
-
with db.get_session() as session:
|
|
25
|
-
gap_repo = GapRepository(session)
|
|
26
|
-
task_repo = TaskRepository(session)
|
|
27
|
-
|
|
28
|
-
all_gaps = gap_repo.get_all()
|
|
29
|
-
blocking_gaps = [g for g in all_gaps if g.blocking]
|
|
30
|
-
|
|
31
|
-
if not blocking_gaps:
|
|
32
|
-
console.print("[green]No blocking gaps found. Nothing to repair![/green]")
|
|
33
|
-
return
|
|
34
|
-
|
|
35
|
-
console.print(f"Found [bold]{len(blocking_gaps)}[/bold] blocking gaps. Orchestrating repair plan...")
|
|
36
|
-
|
|
37
|
-
# Load router
|
|
38
|
-
try:
|
|
39
|
-
config = load_config(Path("."))
|
|
40
|
-
validate_model_provider(config.models.provider)
|
|
41
|
-
api_key = get_api_key(config.models.provider, Path("."))
|
|
42
|
-
except (FileNotFoundError, ValueError) as e:
|
|
43
|
-
console.print(f"[red]{e}[/red]")
|
|
44
|
-
return
|
|
45
|
-
|
|
46
|
-
provider = create_provider(config.models.provider, api_key)
|
|
47
|
-
role_config = {name: role.model_dump() for name, role in config.models.roles.items()}
|
|
48
|
-
router = ModelRouter(provider, role_config)
|
|
49
|
-
repair_service = RepairService(router)
|
|
50
|
-
context_builder = ContextBuilder(Path("."))
|
|
51
|
-
|
|
52
|
-
# Build minimal context for repair.
|
|
53
|
-
project_context = context_builder.get_structure_summary()
|
|
54
|
-
|
|
55
|
-
repair_output = await repair_service.generate_repair_plan(blocking_gaps, str(project_context))
|
|
56
|
-
|
|
57
|
-
for task in repair_output.suggested_tasks:
|
|
58
|
-
task.id = f"REPAIR-{task.id}"
|
|
59
|
-
task_repo.save(task)
|
|
60
|
-
console.print(f" - Created intelligent repair task [bold]{task.id}[/bold]: {task.title}")
|
|
61
|
-
|
|
62
|
-
console.print(f"\n[green]Successfully generated {len(repair_output.suggested_tasks)} repair tasks.[/green]")
|
|
63
|
-
|
|
64
|
-
@app.callback(invoke_without_command=True)
|
|
65
|
-
def repair(ctx: typer.Context):
|
|
66
|
-
"""
|
|
67
|
-
Convert blocking gaps into intelligent repair tasks using LLM inference.
|
|
68
|
-
"""
|
|
69
|
-
if ctx.invoked_subcommand is not None:
|
|
70
|
-
return
|
|
71
|
-
|
|
72
|
-
asyncio.run(run_repair_flow())
|
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
import typer
|
|
2
|
-
from rich.console import Console
|
|
3
|
-
from rich.markdown import Markdown
|
|
4
|
-
from devcouncil.storage.db import get_db
|
|
5
|
-
from devcouncil.storage.repositories import ArtifactGraphRepository
|
|
6
|
-
from devcouncil.reporting.report_builder import ReportBuilder
|
|
7
|
-
from devcouncil.integrations.github import GitHubIntegration
|
|
8
|
-
from devcouncil.integrations.pr_comments import GitHubPRCommenter, GitLabMRCommenter, build_pr_comment_body
|
|
9
|
-
from devcouncil.artifacts.graph import ArtifactGraph
|
|
10
|
-
from devcouncil.telemetry.traces import TraceLogger
|
|
11
|
-
from devcouncil.cli.commands.init import initialize_project
|
|
12
|
-
from devcouncil.live.summary import live_review_summary
|
|
13
|
-
import asyncio
|
|
14
|
-
import os
|
|
15
|
-
import subprocess
|
|
16
|
-
from pathlib import Path
|
|
17
|
-
|
|
18
|
-
app = typer.Typer()
|
|
19
|
-
console = Console()
|
|
20
|
-
|
|
21
|
-
async def run_github_report(graph: ArtifactGraph, project_root: Path):
|
|
22
|
-
token = os.environ.get("GITHUB_TOKEN")
|
|
23
|
-
repo = os.environ.get("GITHUB_REPOSITORY") # e.g. owner/repo
|
|
24
|
-
|
|
25
|
-
if not token or not repo:
|
|
26
|
-
console.print("[red]GITHUB_TOKEN and GITHUB_REPOSITORY must be set for GitHub reporting.[/red]")
|
|
27
|
-
return
|
|
28
|
-
|
|
29
|
-
try:
|
|
30
|
-
# Detect current SHA
|
|
31
|
-
sha = subprocess.check_output(["git", "rev-parse", "HEAD"], cwd=project_root).decode().strip()
|
|
32
|
-
integration = GitHubIntegration(token, repo, sha)
|
|
33
|
-
await integration.report_verification(graph)
|
|
34
|
-
console.print(f"[green]Successfully reported to GitHub PR Checks for {repo} at {sha[:7]}[/green]")
|
|
35
|
-
except Exception as e:
|
|
36
|
-
console.print(f"[red]Failed to report to GitHub: {e}[/red]")
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
async def run_github_pr_comment(graph: ArtifactGraph, live_review: dict | None = None):
|
|
40
|
-
token = os.environ.get("GITHUB_TOKEN")
|
|
41
|
-
repo = os.environ.get("GITHUB_REPOSITORY")
|
|
42
|
-
pull_number = os.environ.get("GITHUB_PR_NUMBER") or os.environ.get("PR_NUMBER")
|
|
43
|
-
if not token or not repo or not pull_number:
|
|
44
|
-
console.print("[red]GITHUB_TOKEN, GITHUB_REPOSITORY, and GITHUB_PR_NUMBER must be set for GitHub PR comments.[/red]")
|
|
45
|
-
return
|
|
46
|
-
try:
|
|
47
|
-
pull_number_int = int(pull_number)
|
|
48
|
-
except ValueError:
|
|
49
|
-
console.print("[red]GITHUB_PR_NUMBER must be an integer.[/red]")
|
|
50
|
-
return
|
|
51
|
-
commenter = GitHubPRCommenter(token, repo, pull_number_int)
|
|
52
|
-
await commenter.post_comment(build_pr_comment_body(graph, live_review=live_review))
|
|
53
|
-
console.print(f"[green]Posted DevCouncil PR comment to GitHub PR #{pull_number}.[/green]")
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
async def run_gitlab_mr_comment(graph: ArtifactGraph, live_review: dict | None = None):
|
|
57
|
-
token = os.environ.get("GITLAB_TOKEN")
|
|
58
|
-
project_id = os.environ.get("GITLAB_PROJECT_ID")
|
|
59
|
-
mr_iid = os.environ.get("GITLAB_MR_IID") or os.environ.get("CI_MERGE_REQUEST_IID")
|
|
60
|
-
base_url = os.environ.get("GITLAB_API_URL", "https://gitlab.com/api/v4")
|
|
61
|
-
if not token or not project_id or not mr_iid:
|
|
62
|
-
console.print("[red]GITLAB_TOKEN, GITLAB_PROJECT_ID, and GITLAB_MR_IID must be set for GitLab MR comments.[/red]")
|
|
63
|
-
return
|
|
64
|
-
try:
|
|
65
|
-
mr_iid_int = int(mr_iid)
|
|
66
|
-
except ValueError:
|
|
67
|
-
console.print("[red]GITLAB_MR_IID must be an integer.[/red]")
|
|
68
|
-
return
|
|
69
|
-
commenter = GitLabMRCommenter(token, project_id, mr_iid_int, base_url=base_url)
|
|
70
|
-
await commenter.post_comment(build_pr_comment_body(graph, live_review=live_review))
|
|
71
|
-
console.print(f"[green]Posted DevCouncil MR comment to GitLab MR !{mr_iid}.[/green]")
|
|
72
|
-
|
|
73
|
-
@app.callback(invoke_without_command=True)
|
|
74
|
-
def report(
|
|
75
|
-
ctx: typer.Context,
|
|
76
|
-
planning_only: bool = typer.Option(False, "--planning-only", help="Report only the planning phase status"),
|
|
77
|
-
json_format: bool = typer.Option(False, "--json", help="Output report in JSON format"),
|
|
78
|
-
github: bool = typer.Option(False, "--github", help="Post report to GitHub PR Checks"),
|
|
79
|
-
github_pr_comment: bool = typer.Option(False, "--github-pr-comment", help="Post report as a GitHub PR comment"),
|
|
80
|
-
gitlab_pr_comment: bool = typer.Option(False, "--gitlab-pr-comment", help="Post report as a GitLab merge request comment"),
|
|
81
|
-
project_root: Path = typer.Option(Path("."), "--project-root", help="Repository root containing .devcouncil/."),
|
|
82
|
-
):
|
|
83
|
-
"""
|
|
84
|
-
Produce final evidence report.
|
|
85
|
-
"""
|
|
86
|
-
if ctx.invoked_subcommand is not None:
|
|
87
|
-
return
|
|
88
|
-
|
|
89
|
-
root = project_root.expanduser().resolve()
|
|
90
|
-
initialize_project(root, quiet=True)
|
|
91
|
-
db = get_db(root)
|
|
92
|
-
if not db:
|
|
93
|
-
console.print("[red]DevCouncil state is unavailable in this directory.[/red]")
|
|
94
|
-
raise typer.Exit(code=1)
|
|
95
|
-
|
|
96
|
-
with db.get_session() as session:
|
|
97
|
-
graph_repo = ArtifactGraphRepository(session)
|
|
98
|
-
graph = graph_repo.load_graph()
|
|
99
|
-
live_review = live_review_summary(root)
|
|
100
|
-
TraceLogger(root).log_event(
|
|
101
|
-
"report_generated",
|
|
102
|
-
{
|
|
103
|
-
"json": json_format,
|
|
104
|
-
"github": github,
|
|
105
|
-
"github_pr_comment": github_pr_comment,
|
|
106
|
-
"gitlab_pr_comment": gitlab_pr_comment,
|
|
107
|
-
"planning_only": planning_only,
|
|
108
|
-
},
|
|
109
|
-
summary="Generated DevCouncil report",
|
|
110
|
-
)
|
|
111
|
-
|
|
112
|
-
if github:
|
|
113
|
-
asyncio.run(run_github_report(graph, root))
|
|
114
|
-
return
|
|
115
|
-
|
|
116
|
-
if github_pr_comment:
|
|
117
|
-
asyncio.run(run_github_pr_comment(graph, live_review=live_review))
|
|
118
|
-
return
|
|
119
|
-
|
|
120
|
-
if gitlab_pr_comment:
|
|
121
|
-
asyncio.run(run_gitlab_mr_comment(graph, live_review=live_review))
|
|
122
|
-
return
|
|
123
|
-
|
|
124
|
-
if json_format:
|
|
125
|
-
output = ReportBuilder.build_json(graph, live_review=live_review)
|
|
126
|
-
typer.echo(output)
|
|
127
|
-
else:
|
|
128
|
-
output = ReportBuilder.build_markdown(graph, live_review=live_review)
|
|
129
|
-
console.print(Markdown(output))
|