devcouncil 0.2.0 → 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 +191 -253
- package/bin/devcouncil.js +130 -32
- package/package.json +6 -17
- package/pyproject.toml +0 -66
- 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 -320
- 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 -163
- 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/assets/__init__.py +0 -1
- package/src/devcouncil/assets/devcouncil-logo.svg +0 -60
- package/src/devcouncil/assets/devcouncil_logo_premium.png +0 -0
- package/src/devcouncil/cli/__init__.py +0 -0
- package/src/devcouncil/cli/commands/__init__.py +0 -0
- package/src/devcouncil/cli/commands/agents.py +0 -292
- package/src/devcouncil/cli/commands/artifacts.py +0 -54
- package/src/devcouncil/cli/commands/ast.py +0 -22
- package/src/devcouncil/cli/commands/baseline.py +0 -35
- package/src/devcouncil/cli/commands/check.py +0 -209
- package/src/devcouncil/cli/commands/config.py +0 -115
- package/src/devcouncil/cli/commands/cost.py +0 -57
- package/src/devcouncil/cli/commands/dashboard.py +0 -31
- package/src/devcouncil/cli/commands/doctor.py +0 -340
- package/src/devcouncil/cli/commands/evidence.py +0 -48
- package/src/devcouncil/cli/commands/go.py +0 -656
- package/src/devcouncil/cli/commands/handoff.py +0 -69
- package/src/devcouncil/cli/commands/hook.py +0 -237
- package/src/devcouncil/cli/commands/init.py +0 -289
- package/src/devcouncil/cli/commands/integrate.py +0 -1515
- package/src/devcouncil/cli/commands/lsp.py +0 -20
- package/src/devcouncil/cli/commands/map.py +0 -112
- package/src/devcouncil/cli/commands/mcp_server.py +0 -18
- package/src/devcouncil/cli/commands/plan.py +0 -488
- package/src/devcouncil/cli/commands/prompt.py +0 -61
- package/src/devcouncil/cli/commands/repair.py +0 -89
- package/src/devcouncil/cli/commands/report.py +0 -137
- package/src/devcouncil/cli/commands/reset_demo_state.py +0 -33
- package/src/devcouncil/cli/commands/rollback.py +0 -59
- package/src/devcouncil/cli/commands/run.py +0 -289
- package/src/devcouncil/cli/commands/runs.py +0 -223
- package/src/devcouncil/cli/commands/scaffold.py +0 -32
- package/src/devcouncil/cli/commands/semantic.py +0 -47
- package/src/devcouncil/cli/commands/setup.py +0 -362
- package/src/devcouncil/cli/commands/shell.py +0 -73
- package/src/devcouncil/cli/commands/show.py +0 -76
- package/src/devcouncil/cli/commands/skills.py +0 -88
- package/src/devcouncil/cli/commands/status.py +0 -141
- package/src/devcouncil/cli/commands/tasks.py +0 -55
- package/src/devcouncil/cli/commands/trace.py +0 -88
- package/src/devcouncil/cli/commands/verify.py +0 -328
- package/src/devcouncil/cli/commands/version.py +0 -20
- package/src/devcouncil/cli/commands/watch.py +0 -574
- package/src/devcouncil/cli/commands/watch_fs.py +0 -40
- package/src/devcouncil/cli/main.py +0 -137
- 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 -47
- package/src/devcouncil/domain/gap.py +0 -52
- package/src/devcouncil/domain/requirement.py +0 -22
- package/src/devcouncil/domain/task.py +0 -55
- package/src/devcouncil/execution/__init__.py +0 -1
- package/src/devcouncil/execution/checkpoints.py +0 -246
- package/src/devcouncil/execution/context_builder.py +0 -60
- package/src/devcouncil/execution/executor.py +0 -15
- package/src/devcouncil/execution/fs_watcher.py +0 -180
- package/src/devcouncil/execution/handoff.py +0 -102
- package/src/devcouncil/execution/hook_policy.py +0 -253
- package/src/devcouncil/execution/patch.py +0 -77
- package/src/devcouncil/execution/paths.py +0 -14
- package/src/devcouncil/execution/permissions.py +0 -85
- package/src/devcouncil/execution/policy_engine.py +0 -343
- package/src/devcouncil/execution/prompt_builder.py +0 -671
- package/src/devcouncil/execution/shell_session.py +0 -225
- package/src/devcouncil/execution/task_runner.py +0 -170
- package/src/devcouncil/executors/__init__.py +0 -1
- package/src/devcouncil/executors/agent_registry.py +0 -575
- package/src/devcouncil/executors/coding_cli.py +0 -736
- 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 -208
- 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 -52
- 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 -53
- package/src/devcouncil/gating/policy.py +0 -338
- package/src/devcouncil/hardware.py +0 -184
- 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 -161
- package/src/devcouncil/indexing/repo_mapper.py +0 -1455
- package/src/devcouncil/indexing/semantic_index.py +0 -205
- package/src/devcouncil/integrations/actions.py +0 -146
- package/src/devcouncil/integrations/check.py +0 -423
- package/src/devcouncil/integrations/code_review_graph.py +0 -163
- package/src/devcouncil/integrations/github.py +0 -39
- package/src/devcouncil/integrations/github_intent.py +0 -142
- package/src/devcouncil/integrations/gitnexus.py +0 -62
- 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 -2122
- package/src/devcouncil/integrations/opencode_devcouncil_plugin.mjs +0 -24
- package/src/devcouncil/integrations/pr_comments.py +0 -62
- package/src/devcouncil/live/__init__.py +0 -2
- package/src/devcouncil/live/cards.py +0 -349
- 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 -141
- package/src/devcouncil/llm/__init__.py +0 -1
- package/src/devcouncil/llm/cache.py +0 -42
- package/src/devcouncil/llm/model_defaults.yaml +0 -44
- package/src/devcouncil/llm/provider.py +0 -627
- package/src/devcouncil/llm/router.py +0 -310
- package/src/devcouncil/optimization/__init__.py +0 -1
- package/src/devcouncil/optimization/gepa_agent.py +0 -318
- package/src/devcouncil/planning/__init__.py +0 -1
- package/src/devcouncil/planning/arbiter_service.py +0 -57
- package/src/devcouncil/planning/correction_manifest.py +0 -303
- package/src/devcouncil/planning/critique_service.py +0 -71
- package/src/devcouncil/planning/plan_service.py +0 -60
- package/src/devcouncil/planning/prompt_enhancer_service.py +0 -167
- package/src/devcouncil/planning/repair_service.py +0 -39
- package/src/devcouncil/planning/spec_service.py +0 -70
- package/src/devcouncil/repo/__init__.py +0 -0
- package/src/devcouncil/repo/ci_scaffold.py +0 -157
- package/src/devcouncil/repo/gitignore.py +0 -123
- package/src/devcouncil/repo/sca.py +0 -374
- 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 -30
- package/src/devcouncil/reporting/markdown_report.py +0 -83
- package/src/devcouncil/reporting/report_builder.py +0 -14
- package/src/devcouncil/skills/__init__.py +0 -19
- package/src/devcouncil/skills/library/README.md +0 -46
- package/src/devcouncil/skills/library/ai-training.md +0 -50
- package/src/devcouncil/skills/library/android.md +0 -50
- package/src/devcouncil/skills/library/backend.md +0 -52
- package/src/devcouncil/skills/library/core-engineering.md +0 -95
- package/src/devcouncil/skills/library/data-engineering.md +0 -47
- package/src/devcouncil/skills/library/desktop.md +0 -46
- package/src/devcouncil/skills/library/devops.md +0 -48
- package/src/devcouncil/skills/library/game-dev.md +0 -46
- package/src/devcouncil/skills/library/ios.md +0 -48
- package/src/devcouncil/skills/library/mobile-cross-platform.md +0 -46
- package/src/devcouncil/skills/library/security.md +0 -48
- package/src/devcouncil/skills/library/systems.md +0 -48
- package/src/devcouncil/skills/library/web.md +0 -47
- package/src/devcouncil/skills/library/windows.md +0 -47
- package/src/devcouncil/skills/registry.py +0 -330
- package/src/devcouncil/storage/__init__.py +0 -0
- package/src/devcouncil/storage/db.py +0 -147
- package/src/devcouncil/storage/models.py +0 -204
- package/src/devcouncil/storage/native.py +0 -557
- package/src/devcouncil/storage/repositories.py +0 -485
- package/src/devcouncil/telemetry/__init__.py +0 -0
- package/src/devcouncil/telemetry/cost.py +0 -140
- package/src/devcouncil/telemetry/model_pricing.yaml +0 -48
- package/src/devcouncil/telemetry/pricing.py +0 -28
- package/src/devcouncil/telemetry/traces.py +0 -146
- package/src/devcouncil/telemetry/tracker.py +0 -52
- package/src/devcouncil/ui/__init__.py +0 -1
- package/src/devcouncil/ui/dashboard.py +0 -423
- package/src/devcouncil/utils/__init__.py +0 -1
- package/src/devcouncil/utils/redaction.py +0 -147
- package/src/devcouncil/utils/subprocess_env.py +0 -69
- package/src/devcouncil/verification/__init__.py +0 -1
- package/src/devcouncil/verification/acceptance_compiler.py +0 -125
- package/src/devcouncil/verification/ad_hoc_check.py +0 -129
- package/src/devcouncil/verification/diff_coverage.py +0 -353
- package/src/devcouncil/verification/implementation_reviewer.py +0 -55
- package/src/devcouncil/verification/next_actions.py +0 -189
- package/src/devcouncil/verification/sandbox.py +0 -178
- package/src/devcouncil/verification/test_resolver.py +0 -91
- package/src/devcouncil/verification/verifier.py +0 -1548
- package/uv.lock +0 -1226
|
@@ -1,328 +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, DiffCoverageEvidence, TestEvidence
|
|
15
|
-
from devcouncil.domain.gap import Gap
|
|
16
|
-
from devcouncil.verification.next_actions import split_next_actions
|
|
17
|
-
from devcouncil.app.config import load_config, get_api_key
|
|
18
|
-
from devcouncil.app.state_machine import ProjectPhase
|
|
19
|
-
from devcouncil.integrations.code_review_graph import CodeReviewGraphAdapter
|
|
20
|
-
from devcouncil.telemetry.traces import TraceLogger
|
|
21
|
-
|
|
22
|
-
console = Console()
|
|
23
|
-
MAX_RENDERED_GAPS = 20
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
def reconcile_cross_task_acceptance(
|
|
27
|
-
gaps: list[Gap], proven_acs: set[str]
|
|
28
|
-
) -> list[Gap]:
|
|
29
|
-
"""Drop a task's blocking ``acceptance_criteria_unproven`` gaps whose criterion is
|
|
30
|
-
already proven by passing evidence in another task.
|
|
31
|
-
|
|
32
|
-
Acceptance criteria are requirement-level, not task-private: when the planner splits
|
|
33
|
-
"implement X" and "add tests for X" into separate tasks that share criteria, the
|
|
34
|
-
implement task would otherwise stay blocked for criteria the test task proved. The
|
|
35
|
-
caller passes ``proven_acs`` gathered from passing evidence re-run against the current
|
|
36
|
-
tree, so a regression would have failed the test and excluded the criterion — only
|
|
37
|
-
genuinely-satisfied criteria are cleared. Returns the gaps to keep."""
|
|
38
|
-
return [
|
|
39
|
-
gap
|
|
40
|
-
for gap in gaps
|
|
41
|
-
if not (
|
|
42
|
-
gap.blocking
|
|
43
|
-
and gap.gap_type == "acceptance_criteria_unproven"
|
|
44
|
-
and gap.acceptance_criterion_id in proven_acs
|
|
45
|
-
)
|
|
46
|
-
]
|
|
47
|
-
|
|
48
|
-
def verify(
|
|
49
|
-
task_id: Optional[str] = typer.Argument(None, help="Optional ID of the task to verify"),
|
|
50
|
-
sandbox: str = typer.Option("local", "--sandbox", help="Verification sandbox: local, docker, or nix."),
|
|
51
|
-
json_format: bool = typer.Option(False, "--json", help="Output machine-readable JSON."),
|
|
52
|
-
project_root: Path = typer.Option(Path("."), "--project-root", help="Repository root containing .devcouncil/."),
|
|
53
|
-
):
|
|
54
|
-
"""
|
|
55
|
-
Verify one task, or all tasks when TASK_ID is omitted.
|
|
56
|
-
"""
|
|
57
|
-
root = project_root.expanduser().resolve()
|
|
58
|
-
initialize_project(root, quiet=True)
|
|
59
|
-
db = get_db(root)
|
|
60
|
-
if not db:
|
|
61
|
-
if json_format:
|
|
62
|
-
typer.echo(json.dumps({"ok": False, "error": "DevCouncil state is unavailable in this directory."}, indent=2))
|
|
63
|
-
else:
|
|
64
|
-
console.print("[red]DevCouncil state is unavailable in this directory.[/red]")
|
|
65
|
-
return
|
|
66
|
-
|
|
67
|
-
with db.get_session() as session:
|
|
68
|
-
task_repo = TaskRepository(session)
|
|
69
|
-
req_repo = RequirementRepository(session)
|
|
70
|
-
gap_repo = GapRepository(session)
|
|
71
|
-
evidence_repo = EvidenceRepository(session)
|
|
72
|
-
|
|
73
|
-
tasks = [task_repo.get_by_id(task_id)] if task_id else task_repo.get_all()
|
|
74
|
-
tasks = [task for task in tasks if task is not None]
|
|
75
|
-
if not tasks:
|
|
76
|
-
missing = f"Task {task_id} not found." if task_id else "No tasks found to verify."
|
|
77
|
-
if json_format:
|
|
78
|
-
typer.echo(json.dumps({"ok": False, "error": missing}, indent=2))
|
|
79
|
-
else:
|
|
80
|
-
console.print(f"[red]{missing}[/red]")
|
|
81
|
-
return
|
|
82
|
-
|
|
83
|
-
reqs = req_repo.get_all()
|
|
84
|
-
|
|
85
|
-
# Load router for LLM review if possible
|
|
86
|
-
router = None
|
|
87
|
-
try:
|
|
88
|
-
config = load_config(root)
|
|
89
|
-
validate_model_provider(config.models.provider)
|
|
90
|
-
api_key = get_api_key(config.models.provider, root)
|
|
91
|
-
provider = create_provider(config.models.provider, api_key, project_root=root)
|
|
92
|
-
role_config = {name: role.model_dump() for name, role in config.models.roles.items()}
|
|
93
|
-
router = ModelRouter(provider, role_config, project_root=root)
|
|
94
|
-
except Exception:
|
|
95
|
-
pass
|
|
96
|
-
|
|
97
|
-
from devcouncil.verification.sandbox import get_sandbox
|
|
98
|
-
|
|
99
|
-
verifier = Verifier(root, router=router)
|
|
100
|
-
total_gaps = 0
|
|
101
|
-
blocked_tasks = 0
|
|
102
|
-
task_results = []
|
|
103
|
-
# Cross-task acceptance reconciliation state: a criterion proven by passing
|
|
104
|
-
# evidence in ANY task is proven for every task that shares it.
|
|
105
|
-
proven_acs: set[str] = set()
|
|
106
|
-
per_task_gaps: dict[str, list] = {}
|
|
107
|
-
|
|
108
|
-
for task in tasks:
|
|
109
|
-
if sandbox != "local":
|
|
110
|
-
commands = task.expected_tests or task.allowed_commands
|
|
111
|
-
sandbox_result = get_sandbox(sandbox, root).run(task, commands, reqs)
|
|
112
|
-
if sandbox_result.status == "unsupported":
|
|
113
|
-
message = f"Sandbox {sandbox} is unavailable."
|
|
114
|
-
if json_format:
|
|
115
|
-
typer.echo(json.dumps({"ok": False, "error": message, "sandbox": sandbox}, indent=2))
|
|
116
|
-
else:
|
|
117
|
-
console.print(f"[red]{message}[/red]")
|
|
118
|
-
return
|
|
119
|
-
if sandbox_result.status == "failed":
|
|
120
|
-
task.status = "blocked"
|
|
121
|
-
blocked_tasks += 1
|
|
122
|
-
task_repo.save(task)
|
|
123
|
-
task_results.append({
|
|
124
|
-
"task_id": task.id,
|
|
125
|
-
"status": task.status,
|
|
126
|
-
"sandbox": sandbox,
|
|
127
|
-
"gap_count": 1,
|
|
128
|
-
"blocking_gap_count": 1,
|
|
129
|
-
"gaps": [],
|
|
130
|
-
})
|
|
131
|
-
if json_format:
|
|
132
|
-
typer.echo(json.dumps({
|
|
133
|
-
"ok": False,
|
|
134
|
-
"task_id": task.id,
|
|
135
|
-
"sandbox": sandbox,
|
|
136
|
-
"commands": sandbox_result.commands,
|
|
137
|
-
}, indent=2))
|
|
138
|
-
else:
|
|
139
|
-
console.print(f"[red]{task.id} failed in {sandbox} sandbox.[/red]")
|
|
140
|
-
continue
|
|
141
|
-
task.status = "verified"
|
|
142
|
-
task_repo.save(task)
|
|
143
|
-
task_results.append({
|
|
144
|
-
"task_id": task.id,
|
|
145
|
-
"status": task.status,
|
|
146
|
-
"sandbox": sandbox,
|
|
147
|
-
"gap_count": 0,
|
|
148
|
-
"blocking_gap_count": 0,
|
|
149
|
-
"gaps": [],
|
|
150
|
-
})
|
|
151
|
-
if not json_format:
|
|
152
|
-
console.print(f"[green]{task.id} passed in {sandbox} sandbox.[/green]")
|
|
153
|
-
continue
|
|
154
|
-
TraceLogger(root).log_event(
|
|
155
|
-
"task_verification_started",
|
|
156
|
-
{"task_id": task.id},
|
|
157
|
-
task_id=task.id,
|
|
158
|
-
summary=f"Verifying {task.id}",
|
|
159
|
-
)
|
|
160
|
-
graph_context = CodeReviewGraphAdapter(root).get_context(
|
|
161
|
-
[planned.path for planned in task.planned_files]
|
|
162
|
-
)
|
|
163
|
-
if graph_context.available:
|
|
164
|
-
TraceLogger(root).log_event(
|
|
165
|
-
"graph_context_loaded",
|
|
166
|
-
graph_context.model_dump(),
|
|
167
|
-
task_id=task.id,
|
|
168
|
-
summary=f"Loaded graph context for {task.id}",
|
|
169
|
-
)
|
|
170
|
-
StateRepository(session).record_phase(ProjectPhase.TASK_VERIFYING.value)
|
|
171
|
-
gap_repo.delete_for_task(task.id)
|
|
172
|
-
evidence_repo.delete_for_task(task.id)
|
|
173
|
-
|
|
174
|
-
gaps, evidence = asyncio.run(verifier.verify_task(task, reqs))
|
|
175
|
-
outcome = verifier.last_outcome
|
|
176
|
-
total_gaps += len(gaps)
|
|
177
|
-
|
|
178
|
-
for gap in gaps:
|
|
179
|
-
gap_repo.save(gap)
|
|
180
|
-
|
|
181
|
-
for ev in evidence:
|
|
182
|
-
if isinstance(ev, CommandResult):
|
|
183
|
-
evidence_repo.save_command_result(task.id, ev)
|
|
184
|
-
elif isinstance(ev, DiffCoverageEvidence):
|
|
185
|
-
evidence_repo.save_diff_coverage_evidence(ev)
|
|
186
|
-
elif isinstance(ev, DiffEvidence):
|
|
187
|
-
evidence_repo.save_diff_evidence(ev)
|
|
188
|
-
elif isinstance(ev, TestEvidence):
|
|
189
|
-
evidence_repo.save_test_evidence(ev, task.id)
|
|
190
|
-
if ev.status == "passed" and ev.acceptance_criterion_id:
|
|
191
|
-
proven_acs.add(ev.acceptance_criterion_id)
|
|
192
|
-
|
|
193
|
-
per_task_gaps[task.id] = gaps
|
|
194
|
-
if not json_format:
|
|
195
|
-
_print_task_result(task.id, gaps)
|
|
196
|
-
|
|
197
|
-
if any(gap.blocking for gap in gaps):
|
|
198
|
-
task.status = "blocked"
|
|
199
|
-
blocked_tasks += 1
|
|
200
|
-
TraceLogger(root).log_event(
|
|
201
|
-
"gate_failed",
|
|
202
|
-
{"task_id": task.id, "gap_count": len(gaps)},
|
|
203
|
-
task_id=task.id,
|
|
204
|
-
summary=f"{task.id} blocked with {len(gaps)} gap(s)",
|
|
205
|
-
)
|
|
206
|
-
else:
|
|
207
|
-
task.status = "verified"
|
|
208
|
-
TraceLogger(root).log_event(
|
|
209
|
-
"task_verified",
|
|
210
|
-
{"task_id": task.id, "gap_count": len(gaps)},
|
|
211
|
-
task_id=task.id,
|
|
212
|
-
summary=f"{task.id} verified",
|
|
213
|
-
)
|
|
214
|
-
task_repo.save(task)
|
|
215
|
-
blocking_actions, advisory_actions = split_next_actions(gaps)
|
|
216
|
-
task_results.append({
|
|
217
|
-
"task_id": task.id,
|
|
218
|
-
"status": task.status,
|
|
219
|
-
"gap_count": len(gaps),
|
|
220
|
-
"blocking_gap_count": len([gap for gap in gaps if gap.blocking]),
|
|
221
|
-
"gaps": [gap.model_dump() for gap in gaps],
|
|
222
|
-
"next_actions": [action.model_dump() for action in blocking_actions],
|
|
223
|
-
"advisory_actions": [action.model_dump() for action in advisory_actions],
|
|
224
|
-
"verification_mode": outcome.mode if outcome else "unknown",
|
|
225
|
-
"compiler_active": outcome.compiler_active if outcome else False,
|
|
226
|
-
"diff_empty": outcome.diff_empty if outcome else False,
|
|
227
|
-
"coverage_measured": outcome.coverage_measured if outcome else False,
|
|
228
|
-
"coverage_skipped_reason": outcome.coverage_skipped_reason if outcome else None,
|
|
229
|
-
})
|
|
230
|
-
|
|
231
|
-
# Cross-task acceptance reconciliation (only meaningful across the full set).
|
|
232
|
-
# The planner sometimes splits "implement X" and "add tests for X" into separate
|
|
233
|
-
# tasks that share acceptance criteria; the implement task would otherwise stay
|
|
234
|
-
# blocked for criteria the test task already proved. A criterion proven by passing
|
|
235
|
-
# evidence in ANY task is proven for every task that shares it. Evidence was re-run
|
|
236
|
-
# against the current tree, so a regression would have failed the test and the AC
|
|
237
|
-
# would not be in proven_acs — this clears only genuinely-satisfied criteria.
|
|
238
|
-
if task_id is None and proven_acs:
|
|
239
|
-
for task in tasks:
|
|
240
|
-
gaps = per_task_gaps.get(task.id, [])
|
|
241
|
-
kept = reconcile_cross_task_acceptance(gaps, proven_acs)
|
|
242
|
-
if len(kept) == len(gaps):
|
|
243
|
-
continue
|
|
244
|
-
gap_repo.delete_for_task(task.id)
|
|
245
|
-
for gap in kept:
|
|
246
|
-
gap_repo.save(gap)
|
|
247
|
-
per_task_gaps[task.id] = kept
|
|
248
|
-
if task.status == "blocked" and not any(gap.blocking for gap in kept):
|
|
249
|
-
task.status = "verified"
|
|
250
|
-
blocked_tasks = max(0, blocked_tasks - 1)
|
|
251
|
-
TraceLogger(root).log_event(
|
|
252
|
-
"task_reconciled",
|
|
253
|
-
{"task_id": task.id, "cross_task_proven": True},
|
|
254
|
-
task_id=task.id,
|
|
255
|
-
summary=f"{task.id} verified via cross-task acceptance reconciliation",
|
|
256
|
-
)
|
|
257
|
-
task_repo.save(task)
|
|
258
|
-
for result in task_results:
|
|
259
|
-
if result["task_id"] == task.id:
|
|
260
|
-
blocking_actions, advisory_actions = split_next_actions(kept)
|
|
261
|
-
result["status"] = task.status
|
|
262
|
-
result["gap_count"] = len(kept)
|
|
263
|
-
result["blocking_gap_count"] = len([gap for gap in kept if gap.blocking])
|
|
264
|
-
result["gaps"] = [gap.model_dump() for gap in kept]
|
|
265
|
-
result["next_actions"] = [action.model_dump() for action in blocking_actions]
|
|
266
|
-
result["advisory_actions"] = [action.model_dump() for action in advisory_actions]
|
|
267
|
-
|
|
268
|
-
StateRepository(session).record_phase(
|
|
269
|
-
ProjectPhase.TASK_BLOCKED.value if blocked_tasks else ProjectPhase.TASK_VERIFIED.value
|
|
270
|
-
)
|
|
271
|
-
|
|
272
|
-
if json_format:
|
|
273
|
-
typer.echo(json.dumps({
|
|
274
|
-
"ok": blocked_tasks == 0,
|
|
275
|
-
"verified_tasks": len(tasks),
|
|
276
|
-
"blocked_tasks": blocked_tasks,
|
|
277
|
-
"total_gaps": total_gaps,
|
|
278
|
-
"tasks": task_results,
|
|
279
|
-
}, indent=2))
|
|
280
|
-
elif len(tasks) > 1:
|
|
281
|
-
if blocked_tasks:
|
|
282
|
-
console.print(
|
|
283
|
-
f"\n[yellow]Verified {len(tasks)} tasks: {blocked_tasks} blocked, "
|
|
284
|
-
f"{total_gaps} total gap(s).[/yellow]"
|
|
285
|
-
)
|
|
286
|
-
else:
|
|
287
|
-
console.print(f"\n[green]Verified {len(tasks)} tasks successfully.[/green]")
|
|
288
|
-
|
|
289
|
-
# Exit-code contract (so shell-driven agents can gate on $?):
|
|
290
|
-
# 0 = all verified, no blocking gaps
|
|
291
|
-
# 1 = at least one task is blocked by a verification gap
|
|
292
|
-
# Argument/state errors above return early with their own message and exit 0.
|
|
293
|
-
if blocked_tasks:
|
|
294
|
-
raise typer.Exit(code=1)
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
def _print_task_result(task_id: str, gaps):
|
|
298
|
-
if not gaps:
|
|
299
|
-
console.print(f"[green]Task {task_id} verified successfully! No gaps found.[/green]")
|
|
300
|
-
return
|
|
301
|
-
|
|
302
|
-
console.print(f"[yellow]Verification finished for task {task_id} with {len(gaps)} gaps:[/yellow]")
|
|
303
|
-
|
|
304
|
-
table = Table(title="Detected Gaps")
|
|
305
|
-
table.add_column("ID", style="cyan")
|
|
306
|
-
table.add_column("Severity", style="magenta")
|
|
307
|
-
table.add_column("Description", style="white")
|
|
308
|
-
table.add_column("Blocking", style="red")
|
|
309
|
-
|
|
310
|
-
for gap in gaps[:MAX_RENDERED_GAPS]:
|
|
311
|
-
table.add_row(
|
|
312
|
-
gap.id,
|
|
313
|
-
gap.severity,
|
|
314
|
-
gap.description,
|
|
315
|
-
"YES" if gap.blocking else "NO",
|
|
316
|
-
)
|
|
317
|
-
|
|
318
|
-
console.print(table)
|
|
319
|
-
if len(gaps) > MAX_RENDERED_GAPS:
|
|
320
|
-
console.print(
|
|
321
|
-
f"[yellow]Showing first {MAX_RENDERED_GAPS} of {len(gaps)} gaps. "
|
|
322
|
-
"Run [bold]dev report --json[/bold] for the full list.[/yellow]"
|
|
323
|
-
)
|
|
324
|
-
|
|
325
|
-
if any(gap.blocking for gap in gaps):
|
|
326
|
-
console.print(f"\n[red]Task {task_id} is BLOCKED due to critical gaps.[/red]")
|
|
327
|
-
else:
|
|
328
|
-
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]")
|