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,117 +0,0 @@
|
|
|
1
|
-
from rich.console import Console
|
|
2
|
-
from rich.table import Table
|
|
3
|
-
from rich.panel import Panel
|
|
4
|
-
from pathlib import Path
|
|
5
|
-
import json
|
|
6
|
-
import typer
|
|
7
|
-
from devcouncil.cli.commands.init import initialize_project
|
|
8
|
-
from devcouncil.app.project_status import compute_phase
|
|
9
|
-
from devcouncil.storage.db import get_db
|
|
10
|
-
from devcouncil.storage.repositories import ArtifactGraphRepository, StateRepository
|
|
11
|
-
from devcouncil.telemetry.cost import CostEstimator
|
|
12
|
-
from devcouncil.live.summary import live_review_summary
|
|
13
|
-
|
|
14
|
-
console = Console()
|
|
15
|
-
|
|
16
|
-
def _status_payload(project_root: Path) -> dict:
|
|
17
|
-
initialize_project(project_root, quiet=True)
|
|
18
|
-
db = get_db(project_root)
|
|
19
|
-
if not db:
|
|
20
|
-
return {"initialized": False, "phase": "UNINITIALIZED"}
|
|
21
|
-
|
|
22
|
-
with db.get_session() as session:
|
|
23
|
-
graph_repo = ArtifactGraphRepository(session)
|
|
24
|
-
graph = graph_repo.load_graph()
|
|
25
|
-
summary = graph.coverage_summary()
|
|
26
|
-
|
|
27
|
-
blocking_gaps = graph.blocking_gaps()
|
|
28
|
-
state = StateRepository(session).get_state()
|
|
29
|
-
phase = compute_phase(graph, state.current_phase if state else None)
|
|
30
|
-
|
|
31
|
-
total_cost = 0.0
|
|
32
|
-
log_file = project_root / ".devcouncil" / "logs" / "model_calls.jsonl"
|
|
33
|
-
if log_file.exists():
|
|
34
|
-
with open(log_file, "r", encoding="utf-8") as f:
|
|
35
|
-
for line in f:
|
|
36
|
-
try:
|
|
37
|
-
entry = json.loads(line)
|
|
38
|
-
total_cost += CostEstimator.estimate_cost(
|
|
39
|
-
entry.get("response", {}).get("model", ""),
|
|
40
|
-
entry.get("usage", {}),
|
|
41
|
-
)
|
|
42
|
-
except Exception:
|
|
43
|
-
continue
|
|
44
|
-
|
|
45
|
-
status_counts: dict[str, int] = {}
|
|
46
|
-
for task in graph.tasks.values():
|
|
47
|
-
status_counts[task.status] = status_counts.get(task.status, 0) + 1
|
|
48
|
-
|
|
49
|
-
return {
|
|
50
|
-
"initialized": True,
|
|
51
|
-
"phase": phase,
|
|
52
|
-
"coverage_summary": summary,
|
|
53
|
-
"total_cost": total_cost,
|
|
54
|
-
"task_status_counts": status_counts,
|
|
55
|
-
"blocking_gaps": [gap.model_dump() for gap in blocking_gaps],
|
|
56
|
-
"live_review": live_review_summary(project_root),
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
def status(
|
|
61
|
-
json_format: bool = typer.Option(False, "--json", help="Output machine-readable JSON."),
|
|
62
|
-
project_root: Path = typer.Option(Path("."), "--project-root", help="Repository root containing .devcouncil/."),
|
|
63
|
-
):
|
|
64
|
-
"""
|
|
65
|
-
Show the current status of the DevCouncil project.
|
|
66
|
-
"""
|
|
67
|
-
root = project_root.expanduser().resolve()
|
|
68
|
-
payload = _status_payload(root)
|
|
69
|
-
if json_format:
|
|
70
|
-
typer.echo(json.dumps(payload, indent=2))
|
|
71
|
-
return
|
|
72
|
-
|
|
73
|
-
if not payload["initialized"]:
|
|
74
|
-
console.print("[yellow]DevCouncil state is not available in this directory.[/yellow]")
|
|
75
|
-
return
|
|
76
|
-
|
|
77
|
-
summary = payload["coverage_summary"]
|
|
78
|
-
phase = payload["phase"]
|
|
79
|
-
phase_colors = {
|
|
80
|
-
"NEW": "yellow",
|
|
81
|
-
"REQUIREMENTS_DRAFTED": "cyan",
|
|
82
|
-
"PLAN_APPROVED": "green",
|
|
83
|
-
"TASK_EXECUTING": "blue",
|
|
84
|
-
"TASK_BLOCKED": "red",
|
|
85
|
-
"PROJECT_DONE": "green bold",
|
|
86
|
-
}
|
|
87
|
-
phase_color = phase_colors.get(phase, "white")
|
|
88
|
-
|
|
89
|
-
console.print(Panel(
|
|
90
|
-
f"[bold]Phase:[/bold] [{phase_color}]{phase}[/{phase_color}]\n"
|
|
91
|
-
f"[bold]Requirements:[/bold] {summary['total_requirements']} ({summary['requirements_without_tasks']} unmapped)\n"
|
|
92
|
-
f"[bold]Tasks:[/bold] {summary['total_tasks']} ({summary['tasks_without_requirements']} orphaned)\n"
|
|
93
|
-
f"[bold]Acceptance Criteria:[/bold] {summary['total_ac']} ({summary['ac_without_evidence']} unverified)\n"
|
|
94
|
-
f"[bold]Gaps:[/bold] {summary['total_gaps']} ({summary['blocking_gaps']} blocking)\n"
|
|
95
|
-
f"[bold]Live Review:[/bold] {payload['live_review']['cards']['critical_open']} open critical, "
|
|
96
|
-
f"{len(payload['live_review']['blocking_cards'])} blocking in scope, "
|
|
97
|
-
f"{payload['live_review']['pending_signals']} pending signal(s)\n"
|
|
98
|
-
f"[bold]Total Cost:[/bold] ${payload['total_cost']:.4f}",
|
|
99
|
-
title="DevCouncil Status",
|
|
100
|
-
expand=False,
|
|
101
|
-
))
|
|
102
|
-
|
|
103
|
-
if payload["task_status_counts"]:
|
|
104
|
-
table = Table(title="Task Summary")
|
|
105
|
-
table.add_column("Status", style="magenta")
|
|
106
|
-
table.add_column("Count", justify="right")
|
|
107
|
-
for state, count in sorted(payload["task_status_counts"].items()):
|
|
108
|
-
table.add_row(state, str(count))
|
|
109
|
-
console.print(table)
|
|
110
|
-
|
|
111
|
-
blocking_gaps = payload["blocking_gaps"]
|
|
112
|
-
if blocking_gaps:
|
|
113
|
-
console.print(f"\n[red bold]WARNING: {len(blocking_gaps)} blocking gap(s) must be resolved:[/red bold]")
|
|
114
|
-
for gap in blocking_gaps[:5]:
|
|
115
|
-
console.print(f" - [red]{gap['id']}[/red]: {gap['description'][:80]}")
|
|
116
|
-
if len(blocking_gaps) > 5:
|
|
117
|
-
console.print(f" ... and {len(blocking_gaps) - 5} more. Run [bold]dev report[/bold] for details.")
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import json
|
|
2
|
-
from pathlib import Path
|
|
3
|
-
|
|
4
|
-
import typer
|
|
5
|
-
from rich.console import Console
|
|
6
|
-
from rich.table import Table
|
|
7
|
-
from devcouncil.cli.commands.init import initialize_project
|
|
8
|
-
from devcouncil.storage.db import get_db
|
|
9
|
-
from devcouncil.storage.repositories import TaskRepository
|
|
10
|
-
|
|
11
|
-
app = typer.Typer()
|
|
12
|
-
console = Console()
|
|
13
|
-
|
|
14
|
-
@app.callback(invoke_without_command=True)
|
|
15
|
-
def tasks(
|
|
16
|
-
ctx: typer.Context,
|
|
17
|
-
json_format: bool = typer.Option(False, "--json", help="Output machine-readable JSON."),
|
|
18
|
-
project_root: Path = typer.Option(Path("."), "--project-root", help="Repository root containing .devcouncil/."),
|
|
19
|
-
):
|
|
20
|
-
"""
|
|
21
|
-
List task graph and task gate status.
|
|
22
|
-
"""
|
|
23
|
-
if ctx.invoked_subcommand is not None:
|
|
24
|
-
return
|
|
25
|
-
|
|
26
|
-
root = project_root.expanduser().resolve()
|
|
27
|
-
initialize_project(root, quiet=True)
|
|
28
|
-
db = get_db(root)
|
|
29
|
-
if not db:
|
|
30
|
-
console.print("[red]DevCouncil state is unavailable in this directory.[/red]")
|
|
31
|
-
raise typer.Exit(code=1)
|
|
32
|
-
|
|
33
|
-
with db.get_session() as session:
|
|
34
|
-
task_repo = TaskRepository(session)
|
|
35
|
-
tasks_list = task_repo.get_all()
|
|
36
|
-
|
|
37
|
-
if json_format:
|
|
38
|
-
typer.echo(json.dumps({"tasks": [task.model_dump() for task in tasks_list]}, indent=2))
|
|
39
|
-
return
|
|
40
|
-
|
|
41
|
-
if not tasks_list:
|
|
42
|
-
console.print("No tasks found. Run 'dev plan' to generate tasks.")
|
|
43
|
-
return
|
|
44
|
-
|
|
45
|
-
table = Table(title="DevCouncil Tasks")
|
|
46
|
-
table.add_column("Task ID", style="cyan", no_wrap=True)
|
|
47
|
-
table.add_column("Title", style="white")
|
|
48
|
-
table.add_column("Status", style="magenta")
|
|
49
|
-
table.add_column("Linked Reqs", style="green")
|
|
50
|
-
|
|
51
|
-
for t in tasks_list:
|
|
52
|
-
reqs = ", ".join(t.requirement_ids)
|
|
53
|
-
table.add_row(t.id, t.title, t.status, reqs)
|
|
54
|
-
|
|
55
|
-
console.print(table)
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import json
|
|
2
|
-
import time
|
|
3
|
-
from pathlib import Path
|
|
4
|
-
|
|
5
|
-
import typer
|
|
6
|
-
from rich.console import Console
|
|
7
|
-
|
|
8
|
-
from devcouncil.telemetry.traces import read_trace_events
|
|
9
|
-
|
|
10
|
-
app = typer.Typer(help="Inspect DevCouncil trace events.")
|
|
11
|
-
console = Console()
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
@app.command("tail")
|
|
15
|
-
def tail(
|
|
16
|
-
follow: bool = typer.Option(False, "--follow", "-f", help="Continue polling for new events."),
|
|
17
|
-
limit: int = typer.Option(50, "--limit", "-n", help="Maximum events to print before following."),
|
|
18
|
-
jsonl: bool = typer.Option(True, "--jsonl/--pretty", help="Print JSONL or compact text rows."),
|
|
19
|
-
project_root: Path = typer.Option(Path("."), "--project-root", help="Repository root containing .devcouncil/."),
|
|
20
|
-
):
|
|
21
|
-
"""Print the DevCouncil trace JSONL stream for replay or debugging."""
|
|
22
|
-
project_root = project_root.expanduser().resolve()
|
|
23
|
-
printed = 0
|
|
24
|
-
|
|
25
|
-
def emit_new(start_index: int) -> int:
|
|
26
|
-
events = list(read_trace_events(project_root))
|
|
27
|
-
selected = events[start_index:]
|
|
28
|
-
for event in selected:
|
|
29
|
-
if jsonl:
|
|
30
|
-
typer.echo(event.model_dump_json())
|
|
31
|
-
else:
|
|
32
|
-
console.print(
|
|
33
|
-
f"{event.timestamp} {event.type} "
|
|
34
|
-
f"{event.task_id or '-'} {event.summary or json.dumps(event.details)}"
|
|
35
|
-
)
|
|
36
|
-
return len(events)
|
|
37
|
-
|
|
38
|
-
events = list(read_trace_events(project_root))
|
|
39
|
-
start = max(0, len(events) - limit)
|
|
40
|
-
printed = emit_new(start)
|
|
41
|
-
|
|
42
|
-
while follow:
|
|
43
|
-
time.sleep(1)
|
|
44
|
-
printed = emit_new(printed)
|
|
@@ -1,193 +0,0 @@
|
|
|
1
|
-
import typer
|
|
2
|
-
import asyncio
|
|
3
|
-
import json
|
|
4
|
-
from rich.console import Console
|
|
5
|
-
from rich.table import Table
|
|
6
|
-
from pathlib import Path
|
|
7
|
-
from typing import Optional
|
|
8
|
-
from devcouncil.cli.commands.init import initialize_project
|
|
9
|
-
from devcouncil.storage.db import get_db
|
|
10
|
-
from devcouncil.storage.repositories import TaskRepository, RequirementRepository, GapRepository, EvidenceRepository, StateRepository
|
|
11
|
-
from devcouncil.verification.verifier import Verifier
|
|
12
|
-
from devcouncil.llm.provider import create_provider, validate_model_provider
|
|
13
|
-
from devcouncil.llm.router import ModelRouter
|
|
14
|
-
from devcouncil.domain.evidence import CommandResult, DiffEvidence, TestEvidence
|
|
15
|
-
from devcouncil.app.config import load_config, get_api_key
|
|
16
|
-
from devcouncil.app.state_machine import ProjectPhase
|
|
17
|
-
from devcouncil.integrations.code_review_graph import CodeReviewGraphAdapter
|
|
18
|
-
from devcouncil.telemetry.traces import TraceLogger
|
|
19
|
-
|
|
20
|
-
console = Console()
|
|
21
|
-
MAX_RENDERED_GAPS = 20
|
|
22
|
-
|
|
23
|
-
def verify(
|
|
24
|
-
task_id: Optional[str] = typer.Argument(None, help="Optional ID of the task to verify"),
|
|
25
|
-
json_format: bool = typer.Option(False, "--json", help="Output machine-readable JSON."),
|
|
26
|
-
project_root: Path = typer.Option(Path("."), "--project-root", help="Repository root containing .devcouncil/."),
|
|
27
|
-
):
|
|
28
|
-
"""
|
|
29
|
-
Verify one task, or all tasks when TASK_ID is omitted.
|
|
30
|
-
"""
|
|
31
|
-
root = project_root.expanduser().resolve()
|
|
32
|
-
initialize_project(root, quiet=True)
|
|
33
|
-
db = get_db(root)
|
|
34
|
-
if not db:
|
|
35
|
-
if json_format:
|
|
36
|
-
typer.echo(json.dumps({"ok": False, "error": "DevCouncil state is unavailable in this directory."}, indent=2))
|
|
37
|
-
else:
|
|
38
|
-
console.print("[red]DevCouncil state is unavailable in this directory.[/red]")
|
|
39
|
-
return
|
|
40
|
-
|
|
41
|
-
with db.get_session() as session:
|
|
42
|
-
task_repo = TaskRepository(session)
|
|
43
|
-
req_repo = RequirementRepository(session)
|
|
44
|
-
gap_repo = GapRepository(session)
|
|
45
|
-
evidence_repo = EvidenceRepository(session)
|
|
46
|
-
|
|
47
|
-
tasks = [task_repo.get_by_id(task_id)] if task_id else task_repo.get_all()
|
|
48
|
-
tasks = [task for task in tasks if task is not None]
|
|
49
|
-
if not tasks:
|
|
50
|
-
missing = f"Task {task_id} not found." if task_id else "No tasks found to verify."
|
|
51
|
-
if json_format:
|
|
52
|
-
typer.echo(json.dumps({"ok": False, "error": missing}, indent=2))
|
|
53
|
-
else:
|
|
54
|
-
console.print(f"[red]{missing}[/red]")
|
|
55
|
-
return
|
|
56
|
-
|
|
57
|
-
reqs = req_repo.get_all()
|
|
58
|
-
|
|
59
|
-
# Load router for LLM review if possible
|
|
60
|
-
router = None
|
|
61
|
-
try:
|
|
62
|
-
config = load_config(root)
|
|
63
|
-
validate_model_provider(config.models.provider)
|
|
64
|
-
api_key = get_api_key(config.models.provider, root)
|
|
65
|
-
provider = create_provider(config.models.provider, api_key)
|
|
66
|
-
role_config = {name: role.model_dump() for name, role in config.models.roles.items()}
|
|
67
|
-
router = ModelRouter(provider, role_config)
|
|
68
|
-
except Exception:
|
|
69
|
-
pass
|
|
70
|
-
|
|
71
|
-
verifier = Verifier(root, router=router)
|
|
72
|
-
total_gaps = 0
|
|
73
|
-
blocked_tasks = 0
|
|
74
|
-
task_results = []
|
|
75
|
-
|
|
76
|
-
for task in tasks:
|
|
77
|
-
TraceLogger(root).log_event(
|
|
78
|
-
"task_verification_started",
|
|
79
|
-
{"task_id": task.id},
|
|
80
|
-
task_id=task.id,
|
|
81
|
-
summary=f"Verifying {task.id}",
|
|
82
|
-
)
|
|
83
|
-
graph_context = CodeReviewGraphAdapter(root).get_context(
|
|
84
|
-
[planned.path for planned in task.planned_files]
|
|
85
|
-
)
|
|
86
|
-
if graph_context.available:
|
|
87
|
-
TraceLogger(root).log_event(
|
|
88
|
-
"graph_context_loaded",
|
|
89
|
-
graph_context.model_dump(),
|
|
90
|
-
task_id=task.id,
|
|
91
|
-
summary=f"Loaded graph context for {task.id}",
|
|
92
|
-
)
|
|
93
|
-
StateRepository(session).record_phase(ProjectPhase.TASK_VERIFYING.value)
|
|
94
|
-
gap_repo.delete_for_task(task.id)
|
|
95
|
-
evidence_repo.delete_for_task(task.id)
|
|
96
|
-
|
|
97
|
-
gaps, evidence = asyncio.run(verifier.verify_task(task, reqs))
|
|
98
|
-
total_gaps += len(gaps)
|
|
99
|
-
|
|
100
|
-
for gap in gaps:
|
|
101
|
-
gap_repo.save(gap)
|
|
102
|
-
|
|
103
|
-
for ev in evidence:
|
|
104
|
-
if isinstance(ev, CommandResult):
|
|
105
|
-
evidence_repo.save_command_result(task.id, ev)
|
|
106
|
-
elif isinstance(ev, DiffEvidence):
|
|
107
|
-
evidence_repo.save_diff_evidence(ev)
|
|
108
|
-
elif isinstance(ev, TestEvidence):
|
|
109
|
-
evidence_repo.save_test_evidence(ev, task.id)
|
|
110
|
-
|
|
111
|
-
if not json_format:
|
|
112
|
-
_print_task_result(task.id, gaps)
|
|
113
|
-
|
|
114
|
-
if any(gap.blocking for gap in gaps):
|
|
115
|
-
task.status = "blocked"
|
|
116
|
-
blocked_tasks += 1
|
|
117
|
-
TraceLogger(root).log_event(
|
|
118
|
-
"gate_failed",
|
|
119
|
-
{"task_id": task.id, "gap_count": len(gaps)},
|
|
120
|
-
task_id=task.id,
|
|
121
|
-
summary=f"{task.id} blocked with {len(gaps)} gap(s)",
|
|
122
|
-
)
|
|
123
|
-
else:
|
|
124
|
-
task.status = "verified"
|
|
125
|
-
TraceLogger(root).log_event(
|
|
126
|
-
"task_verified",
|
|
127
|
-
{"task_id": task.id, "gap_count": len(gaps)},
|
|
128
|
-
task_id=task.id,
|
|
129
|
-
summary=f"{task.id} verified",
|
|
130
|
-
)
|
|
131
|
-
task_repo.save(task)
|
|
132
|
-
task_results.append({
|
|
133
|
-
"task_id": task.id,
|
|
134
|
-
"status": task.status,
|
|
135
|
-
"gap_count": len(gaps),
|
|
136
|
-
"blocking_gap_count": len([gap for gap in gaps if gap.blocking]),
|
|
137
|
-
"gaps": [gap.model_dump() for gap in gaps],
|
|
138
|
-
})
|
|
139
|
-
|
|
140
|
-
StateRepository(session).record_phase(
|
|
141
|
-
ProjectPhase.TASK_BLOCKED.value if blocked_tasks else ProjectPhase.TASK_VERIFIED.value
|
|
142
|
-
)
|
|
143
|
-
|
|
144
|
-
if json_format:
|
|
145
|
-
typer.echo(json.dumps({
|
|
146
|
-
"ok": blocked_tasks == 0,
|
|
147
|
-
"verified_tasks": len(tasks),
|
|
148
|
-
"blocked_tasks": blocked_tasks,
|
|
149
|
-
"total_gaps": total_gaps,
|
|
150
|
-
"tasks": task_results,
|
|
151
|
-
}, indent=2))
|
|
152
|
-
elif len(tasks) > 1:
|
|
153
|
-
if blocked_tasks:
|
|
154
|
-
console.print(
|
|
155
|
-
f"\n[yellow]Verified {len(tasks)} tasks: {blocked_tasks} blocked, "
|
|
156
|
-
f"{total_gaps} total gap(s).[/yellow]"
|
|
157
|
-
)
|
|
158
|
-
else:
|
|
159
|
-
console.print(f"\n[green]Verified {len(tasks)} tasks successfully.[/green]")
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
def _print_task_result(task_id: str, gaps):
|
|
163
|
-
if not gaps:
|
|
164
|
-
console.print(f"[green]Task {task_id} verified successfully! No gaps found.[/green]")
|
|
165
|
-
return
|
|
166
|
-
|
|
167
|
-
console.print(f"[yellow]Verification finished for task {task_id} with {len(gaps)} gaps:[/yellow]")
|
|
168
|
-
|
|
169
|
-
table = Table(title="Detected Gaps")
|
|
170
|
-
table.add_column("ID", style="cyan")
|
|
171
|
-
table.add_column("Severity", style="magenta")
|
|
172
|
-
table.add_column("Description", style="white")
|
|
173
|
-
table.add_column("Blocking", style="red")
|
|
174
|
-
|
|
175
|
-
for gap in gaps[:MAX_RENDERED_GAPS]:
|
|
176
|
-
table.add_row(
|
|
177
|
-
gap.id,
|
|
178
|
-
gap.severity,
|
|
179
|
-
gap.description,
|
|
180
|
-
"YES" if gap.blocking else "NO",
|
|
181
|
-
)
|
|
182
|
-
|
|
183
|
-
console.print(table)
|
|
184
|
-
if len(gaps) > MAX_RENDERED_GAPS:
|
|
185
|
-
console.print(
|
|
186
|
-
f"[yellow]Showing first {MAX_RENDERED_GAPS} of {len(gaps)} gaps. "
|
|
187
|
-
"Run [bold]dev report --json[/bold] for the full list.[/yellow]"
|
|
188
|
-
)
|
|
189
|
-
|
|
190
|
-
if any(gap.blocking for gap in gaps):
|
|
191
|
-
console.print(f"\n[red]Task {task_id} is BLOCKED due to critical gaps.[/red]")
|
|
192
|
-
else:
|
|
193
|
-
console.print(f"\n[green]Task {task_id} passed with non-blocking gaps.[/green]")
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import typer
|
|
2
|
-
from rich.console import Console
|
|
3
|
-
import importlib.metadata
|
|
4
|
-
|
|
5
|
-
app = typer.Typer()
|
|
6
|
-
console = Console()
|
|
7
|
-
|
|
8
|
-
@app.callback(invoke_without_command=True)
|
|
9
|
-
def version(ctx: typer.Context):
|
|
10
|
-
"""
|
|
11
|
-
Display the current version of DevCouncil.
|
|
12
|
-
"""
|
|
13
|
-
if ctx.invoked_subcommand is not None:
|
|
14
|
-
return
|
|
15
|
-
|
|
16
|
-
try:
|
|
17
|
-
ver = importlib.metadata.version("devcouncil")
|
|
18
|
-
console.print(f"DevCouncil version: [bold cyan]{ver}[/bold cyan]")
|
|
19
|
-
except importlib.metadata.PackageNotFoundError:
|
|
20
|
-
console.print("DevCouncil version: [yellow]unknown (editable/uninstalled)[/yellow]")
|