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,86 +0,0 @@
|
|
|
1
|
-
from pydantic import BaseModel, Field
|
|
2
|
-
|
|
3
|
-
from devcouncil.llm.router import ModelRouter
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
class PromptEnhancement(BaseModel):
|
|
7
|
-
original_goal: str
|
|
8
|
-
enhanced_goal: str
|
|
9
|
-
codebase_context: list[str] = Field(default_factory=list)
|
|
10
|
-
debate_focus: list[str] = Field(default_factory=list)
|
|
11
|
-
constraints: list[str] = Field(default_factory=list)
|
|
12
|
-
|
|
13
|
-
def normalized(self, original_goal: str) -> "PromptEnhancement":
|
|
14
|
-
enhanced_goal = self.enhanced_goal.strip() or original_goal
|
|
15
|
-
return self.model_copy(
|
|
16
|
-
update={
|
|
17
|
-
"original_goal": original_goal,
|
|
18
|
-
"enhanced_goal": enhanced_goal,
|
|
19
|
-
"codebase_context": _clean_items(self.codebase_context),
|
|
20
|
-
"debate_focus": _clean_items(self.debate_focus),
|
|
21
|
-
"constraints": _clean_items(self.constraints),
|
|
22
|
-
}
|
|
23
|
-
)
|
|
24
|
-
|
|
25
|
-
def debate_prompt(self) -> str:
|
|
26
|
-
sections = [
|
|
27
|
-
"# Enhanced Planning Prompt",
|
|
28
|
-
"",
|
|
29
|
-
"## Original user goal",
|
|
30
|
-
self.original_goal,
|
|
31
|
-
"",
|
|
32
|
-
"## Codebase-specific goal",
|
|
33
|
-
self.enhanced_goal,
|
|
34
|
-
]
|
|
35
|
-
if self.codebase_context:
|
|
36
|
-
sections.extend(["", "## Relevant codebase context"])
|
|
37
|
-
sections.extend(f"- {item}" for item in self.codebase_context)
|
|
38
|
-
if self.constraints:
|
|
39
|
-
sections.extend(["", "## Constraints to preserve"])
|
|
40
|
-
sections.extend(f"- {item}" for item in self.constraints)
|
|
41
|
-
if self.debate_focus:
|
|
42
|
-
sections.extend(["", "## Debate focus"])
|
|
43
|
-
sections.extend(f"- {item}" for item in self.debate_focus)
|
|
44
|
-
return "\n".join(sections)
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
class PromptEnhancerService:
|
|
48
|
-
def __init__(self, router: ModelRouter):
|
|
49
|
-
self.router = router
|
|
50
|
-
|
|
51
|
-
async def enhance_prompt(
|
|
52
|
-
self,
|
|
53
|
-
goal: str,
|
|
54
|
-
repo_map_json: str,
|
|
55
|
-
graph_context_json: str | None = None,
|
|
56
|
-
) -> PromptEnhancement:
|
|
57
|
-
prompt = f"""
|
|
58
|
-
Original user goal:
|
|
59
|
-
{goal}
|
|
60
|
-
|
|
61
|
-
Repository map:
|
|
62
|
-
{repo_map_json}
|
|
63
|
-
|
|
64
|
-
Code review graph context:
|
|
65
|
-
{graph_context_json or "{}"}
|
|
66
|
-
|
|
67
|
-
You are DevCouncil's codebase-specific prompt enhancer.
|
|
68
|
-
Rewrite the user goal into a better planning prompt before it is sent to the council debate.
|
|
69
|
-
|
|
70
|
-
Requirements:
|
|
71
|
-
- Preserve the user's intent exactly; do not add unrelated features.
|
|
72
|
-
- Make the goal specific to the mapped repository architecture, languages, tests, and likely ownership boundaries.
|
|
73
|
-
- Identify constraints the planners and critics must preserve.
|
|
74
|
-
- Identify debate focus areas that should force useful disagreement between pragmatic and production-readiness plans.
|
|
75
|
-
- Keep the enhanced_goal concise enough to be used as the goal for spec, planning, critique, and arbitration.
|
|
76
|
-
"""
|
|
77
|
-
enhancement = await self.router.complete_structured(
|
|
78
|
-
role="prompt_enhancer",
|
|
79
|
-
messages=[{"role": "user", "content": prompt}],
|
|
80
|
-
schema=PromptEnhancement,
|
|
81
|
-
)
|
|
82
|
-
return enhancement.normalized(goal)
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
def _clean_items(items: list[str]) -> list[str]:
|
|
86
|
-
return [item.strip() for item in items if item.strip()]
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
from typing import List
|
|
2
|
-
import json
|
|
3
|
-
from pydantic import BaseModel
|
|
4
|
-
from devcouncil.domain.gap import Gap
|
|
5
|
-
from devcouncil.domain.task import Task
|
|
6
|
-
from devcouncil.llm.router import ModelRouter
|
|
7
|
-
|
|
8
|
-
class RepairOutput(BaseModel):
|
|
9
|
-
suggested_tasks: List[Task]
|
|
10
|
-
|
|
11
|
-
class RepairService:
|
|
12
|
-
"""Uses LLM to infer focused repair tasks from blocking gaps."""
|
|
13
|
-
|
|
14
|
-
def __init__(self, router: ModelRouter):
|
|
15
|
-
self.router = router
|
|
16
|
-
|
|
17
|
-
async def generate_repair_plan(self, gaps: List[Gap], project_context: str) -> RepairOutput:
|
|
18
|
-
prompt = f"""
|
|
19
|
-
The following blocking gaps were detected during verification.
|
|
20
|
-
Gaps:
|
|
21
|
-
{json.dumps([g.model_dump() for g in gaps], indent=2)}
|
|
22
|
-
|
|
23
|
-
Project Context:
|
|
24
|
-
{project_context}
|
|
25
|
-
|
|
26
|
-
Your task is to generate focused implementation tasks to fix these gaps.
|
|
27
|
-
- Each task must have a clear description and recommended fix.
|
|
28
|
-
- Specify 'planned_files' that need modification (infer from gap evidence).
|
|
29
|
-
- Link each task to the relevant 'requirement_id' mentioned in the gap.
|
|
30
|
-
|
|
31
|
-
Return a JSON object with 'suggested_tasks'.
|
|
32
|
-
"""
|
|
33
|
-
messages = [{"role": "user", "content": prompt}]
|
|
34
|
-
|
|
35
|
-
return await self.router.complete_structured(
|
|
36
|
-
role="planner_a", # Pragmatic tech lead is best suited for repair task generation
|
|
37
|
-
messages=messages,
|
|
38
|
-
schema=RepairOutput
|
|
39
|
-
)
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
from typing import List
|
|
2
|
-
from pydantic import BaseModel
|
|
3
|
-
from devcouncil.domain.requirement import Requirement
|
|
4
|
-
from devcouncil.domain.assumption import Assumption
|
|
5
|
-
from devcouncil.llm.router import ModelRouter
|
|
6
|
-
|
|
7
|
-
class BlockingQuestion(BaseModel):
|
|
8
|
-
id: str
|
|
9
|
-
question: str
|
|
10
|
-
reason: str
|
|
11
|
-
|
|
12
|
-
class SpecOutput(BaseModel):
|
|
13
|
-
requirements: List[Requirement]
|
|
14
|
-
assumptions: List[Assumption]
|
|
15
|
-
blocking_questions: List[BlockingQuestion]
|
|
16
|
-
|
|
17
|
-
class SpecService:
|
|
18
|
-
def __init__(self, router: ModelRouter):
|
|
19
|
-
self.router = router
|
|
20
|
-
|
|
21
|
-
async def generate_spec(self, goal: str, repo_map_json: str) -> SpecOutput:
|
|
22
|
-
prompt = f"""
|
|
23
|
-
Goal: {goal}
|
|
24
|
-
|
|
25
|
-
Repository Map:
|
|
26
|
-
{repo_map_json}
|
|
27
|
-
|
|
28
|
-
Your task is to draft the initial software specification for this goal.
|
|
29
|
-
1. Identify functional and non-functional requirements.
|
|
30
|
-
2. Extract any assumptions you are making about the codebase or architecture.
|
|
31
|
-
3. List any blocking questions that the user must answer before implementation can proceed.
|
|
32
|
-
|
|
33
|
-
Each requirement MUST have clear acceptance criteria with verification methods.
|
|
34
|
-
Each assumption MUST have a confidence and impact level.
|
|
35
|
-
"""
|
|
36
|
-
messages = [
|
|
37
|
-
{"role": "user", "content": prompt}
|
|
38
|
-
]
|
|
39
|
-
|
|
40
|
-
return await self.router.complete_structured(
|
|
41
|
-
role="spec_writer",
|
|
42
|
-
messages=messages,
|
|
43
|
-
schema=SpecOutput
|
|
44
|
-
)
|
|
File without changes
|
|
File without changes
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
from devcouncil.artifacts.graph import ArtifactGraph
|
|
2
|
-
|
|
3
|
-
class GitHubCheckGenerator:
|
|
4
|
-
"""Generates GitHub Checks API payloads."""
|
|
5
|
-
|
|
6
|
-
@staticmethod
|
|
7
|
-
def generate(graph: ArtifactGraph) -> dict:
|
|
8
|
-
summary = graph.coverage_summary()
|
|
9
|
-
blocking_gaps = graph.blocking_gaps()
|
|
10
|
-
|
|
11
|
-
status = "completed"
|
|
12
|
-
conclusion = "failure" if summary["blocking_gaps"] > 0 else "success"
|
|
13
|
-
|
|
14
|
-
text = f"**Requirements**: {summary['total_requirements']} | "
|
|
15
|
-
text += f"**Tasks**: {summary['total_tasks']} | "
|
|
16
|
-
text += f"**Gaps**: {summary['blocking_gaps']} blocking\n\n"
|
|
17
|
-
|
|
18
|
-
if blocking_gaps:
|
|
19
|
-
text += "### Blocking Gaps\n"
|
|
20
|
-
for gap in blocking_gaps:
|
|
21
|
-
text += f"- **{gap.id}**: {gap.description}\n"
|
|
22
|
-
|
|
23
|
-
return {
|
|
24
|
-
"name": "DevCouncil Verification",
|
|
25
|
-
"status": status,
|
|
26
|
-
"conclusion": conclusion,
|
|
27
|
-
"output": {
|
|
28
|
-
"title": f"DevCouncil: {conclusion.capitalize()}",
|
|
29
|
-
"summary": f"Found {summary['blocking_gaps']} blocking gaps.",
|
|
30
|
-
"text": text
|
|
31
|
-
}
|
|
32
|
-
}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import json
|
|
2
|
-
from devcouncil.artifacts.graph import ArtifactGraph
|
|
3
|
-
|
|
4
|
-
class JsonReportGenerator:
|
|
5
|
-
"""Generates a JSON evidence report."""
|
|
6
|
-
|
|
7
|
-
@staticmethod
|
|
8
|
-
def generate(graph: ArtifactGraph, live_review: dict | None = None) -> str:
|
|
9
|
-
summary = graph.coverage_summary()
|
|
10
|
-
live_blockers = len((live_review or {}).get("blocking_cards", []))
|
|
11
|
-
|
|
12
|
-
report = {
|
|
13
|
-
"verdict": "blocked" if summary["blocking_gaps"] > 0 or live_blockers > 0 else "passed",
|
|
14
|
-
"coverage_summary": summary,
|
|
15
|
-
"blocking_gaps": [g.model_dump() for g in graph.blocking_gaps()]
|
|
16
|
-
}
|
|
17
|
-
if live_review is not None:
|
|
18
|
-
report["live_review"] = live_review
|
|
19
|
-
|
|
20
|
-
return json.dumps(report, indent=2)
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
from devcouncil.artifacts.graph import ArtifactGraph
|
|
2
|
-
|
|
3
|
-
class MarkdownReportGenerator:
|
|
4
|
-
"""Generates a Markdown evidence report."""
|
|
5
|
-
|
|
6
|
-
MAX_INLINE_GAPS = 25
|
|
7
|
-
|
|
8
|
-
@staticmethod
|
|
9
|
-
def generate(graph: ArtifactGraph, live_review: dict | None = None) -> str:
|
|
10
|
-
summary = graph.coverage_summary()
|
|
11
|
-
live_blockers = (live_review or {}).get("blocking_cards", [])
|
|
12
|
-
|
|
13
|
-
md_output = "# DevCouncil Report\n\n"
|
|
14
|
-
md_output += "## Verdict\n"
|
|
15
|
-
if summary["blocking_gaps"] > 0 or live_blockers:
|
|
16
|
-
parts = []
|
|
17
|
-
if summary["blocking_gaps"] > 0:
|
|
18
|
-
parts.append(f"{summary['blocking_gaps']} high-severity gap(s)")
|
|
19
|
-
if live_blockers:
|
|
20
|
-
parts.append(f"{len(live_blockers)} live-review blocker(s)")
|
|
21
|
-
md_output += f"**Blocked**: {', '.join(parts)} remain.\n\n"
|
|
22
|
-
else:
|
|
23
|
-
md_output += "**Passed**: Ready for release.\n\n"
|
|
24
|
-
|
|
25
|
-
md_output += "## Coverage Summary\n"
|
|
26
|
-
md_output += f"- **Requirements**: {summary['total_requirements']} ({summary['requirements_without_tasks']} unmapped)\n"
|
|
27
|
-
md_output += f"- **Tasks**: {summary['total_tasks']} ({summary['tasks_without_requirements']} orphaned)\n"
|
|
28
|
-
md_output += f"- **Evidence**: {summary['total_ac'] - summary['ac_without_evidence']}/{summary['total_ac']} AC verified\n\n"
|
|
29
|
-
|
|
30
|
-
md_output += "## Requirements Coverage Table\n"
|
|
31
|
-
md_output += "| Requirement | Task Mapping | Status |\n"
|
|
32
|
-
md_output += "|---|---|---|\n"
|
|
33
|
-
|
|
34
|
-
for req in graph.requirements.values():
|
|
35
|
-
linked_tasks = [t for t in graph.tasks.values() if req.id in t.requirement_ids]
|
|
36
|
-
task_str = ", ".join([t.id for t in linked_tasks]) if linked_tasks else "*None*"
|
|
37
|
-
status_str = "Covered" if linked_tasks else "**Unmapped**"
|
|
38
|
-
md_output += f"| {req.id} {req.title} | {task_str} | {status_str} |\n"
|
|
39
|
-
|
|
40
|
-
md_output += "\n## Blocking Gaps\n"
|
|
41
|
-
blocking_gaps = graph.blocking_gaps()
|
|
42
|
-
if not blocking_gaps:
|
|
43
|
-
md_output += "None.\n"
|
|
44
|
-
else:
|
|
45
|
-
for gap in blocking_gaps[:MarkdownReportGenerator.MAX_INLINE_GAPS]:
|
|
46
|
-
md_output += f"### {gap.id}: {gap.description}\n"
|
|
47
|
-
md_output += f"**Recommended fix**: {gap.recommended_fix}\n\n"
|
|
48
|
-
if len(blocking_gaps) > MarkdownReportGenerator.MAX_INLINE_GAPS:
|
|
49
|
-
remaining = len(blocking_gaps) - MarkdownReportGenerator.MAX_INLINE_GAPS
|
|
50
|
-
md_output += f"_Omitted {remaining} additional blocking gap(s). Use JSON output for the full list._\n"
|
|
51
|
-
|
|
52
|
-
if live_review is not None:
|
|
53
|
-
md_output += "\n## Live Review\n"
|
|
54
|
-
cards = live_review.get("cards", {})
|
|
55
|
-
md_output += f"- **Pending signals**: {live_review.get('pending_signals', 0)}\n"
|
|
56
|
-
md_output += f"- **Open cards**: {cards.get('open', 0)}\n"
|
|
57
|
-
md_output += f"- **Open critical cards**: {cards.get('critical_open', 0)}\n"
|
|
58
|
-
if not live_blockers:
|
|
59
|
-
md_output += "- **Blocking cards in scope**: None.\n"
|
|
60
|
-
else:
|
|
61
|
-
md_output += "\n### Blocking Live-Review Cards\n"
|
|
62
|
-
for card in live_blockers[:MarkdownReportGenerator.MAX_INLINE_GAPS]:
|
|
63
|
-
md_output += f"- **{card['id']}**"
|
|
64
|
-
if card.get("task_id"):
|
|
65
|
-
md_output += f" (`{card['task_id']}`)"
|
|
66
|
-
md_output += f": {card['summary']}\n"
|
|
67
|
-
|
|
68
|
-
return md_output
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
from devcouncil.artifacts.graph import ArtifactGraph
|
|
2
|
-
from devcouncil.reporting.markdown_report import MarkdownReportGenerator
|
|
3
|
-
from devcouncil.reporting.json_report import JsonReportGenerator
|
|
4
|
-
|
|
5
|
-
class ReportBuilder:
|
|
6
|
-
"""Builds reports in various formats from the artifact graph."""
|
|
7
|
-
|
|
8
|
-
@staticmethod
|
|
9
|
-
def build_markdown(graph: ArtifactGraph, live_review: dict | None = None) -> str:
|
|
10
|
-
return MarkdownReportGenerator.generate(graph, live_review=live_review)
|
|
11
|
-
|
|
12
|
-
@staticmethod
|
|
13
|
-
def build_json(graph: ArtifactGraph, live_review: dict | None = None) -> str:
|
|
14
|
-
return JsonReportGenerator.generate(graph, live_review=live_review)
|
|
File without changes
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
from contextlib import contextmanager
|
|
2
|
-
|
|
3
|
-
from sqlmodel import SQLModel, create_engine, Session
|
|
4
|
-
from sqlalchemy.exc import OperationalError
|
|
5
|
-
from pathlib import Path
|
|
6
|
-
from typing import Optional
|
|
7
|
-
|
|
8
|
-
from devcouncil.storage.models import SchemaVersionModel
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
SCHEMA_VERSION = 1
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
class Database:
|
|
15
|
-
def __init__(self, db_path: Path):
|
|
16
|
-
self.db_path = db_path
|
|
17
|
-
self.engine = create_engine(f"sqlite:///{db_path}")
|
|
18
|
-
|
|
19
|
-
def create_db_and_tables(self):
|
|
20
|
-
self._create_tables()
|
|
21
|
-
self.ensure_schema_version()
|
|
22
|
-
|
|
23
|
-
def ensure_schema_version(self):
|
|
24
|
-
self._create_tables()
|
|
25
|
-
with Session(self.engine) as session:
|
|
26
|
-
current = session.get(SchemaVersionModel, "singleton")
|
|
27
|
-
if current is None:
|
|
28
|
-
session.add(SchemaVersionModel(id="singleton", version=SCHEMA_VERSION))
|
|
29
|
-
session.commit()
|
|
30
|
-
return
|
|
31
|
-
if current.version != SCHEMA_VERSION:
|
|
32
|
-
raise RuntimeError(
|
|
33
|
-
f"Unsupported DevCouncil schema version {current.version}; "
|
|
34
|
-
f"expected {SCHEMA_VERSION}."
|
|
35
|
-
)
|
|
36
|
-
|
|
37
|
-
def _create_tables(self):
|
|
38
|
-
try:
|
|
39
|
-
SQLModel.metadata.create_all(self.engine)
|
|
40
|
-
except OperationalError as exc:
|
|
41
|
-
if "already exists" not in str(exc):
|
|
42
|
-
raise
|
|
43
|
-
|
|
44
|
-
@contextmanager
|
|
45
|
-
def get_session(self):
|
|
46
|
-
"""Yield a session with automatic commit/rollback/close."""
|
|
47
|
-
session = Session(self.engine)
|
|
48
|
-
try:
|
|
49
|
-
yield session
|
|
50
|
-
session.commit()
|
|
51
|
-
except Exception:
|
|
52
|
-
session.rollback()
|
|
53
|
-
raise
|
|
54
|
-
finally:
|
|
55
|
-
session.close()
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
def get_db(project_root: Path = Path(".")) -> Optional[Database]:
|
|
59
|
-
dev_dir = project_root / ".devcouncil"
|
|
60
|
-
if not dev_dir.exists():
|
|
61
|
-
return None
|
|
62
|
-
|
|
63
|
-
db_path = dev_dir / "state.sqlite"
|
|
64
|
-
db = Database(db_path)
|
|
65
|
-
db.ensure_schema_version()
|
|
66
|
-
return db
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
from sqlmodel import SQLModel, Field
|
|
2
|
-
from typing import Optional
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
class SchemaVersionModel(SQLModel, table=True):
|
|
6
|
-
__tablename__ = "schema_version"
|
|
7
|
-
id: str = Field(primary_key=True, default="singleton")
|
|
8
|
-
version: int
|
|
9
|
-
|
|
10
|
-
class RequirementModel(SQLModel, table=True):
|
|
11
|
-
__tablename__ = "requirements"
|
|
12
|
-
id: str = Field(primary_key=True)
|
|
13
|
-
title: str
|
|
14
|
-
description: str
|
|
15
|
-
priority: str
|
|
16
|
-
source: str
|
|
17
|
-
# Store complex lists as JSON strings for simplicity in SQLite
|
|
18
|
-
acceptance_criteria_json: str = Field(default="[]")
|
|
19
|
-
|
|
20
|
-
class AssumptionModel(SQLModel, table=True):
|
|
21
|
-
__tablename__ = "assumptions"
|
|
22
|
-
id: str = Field(primary_key=True)
|
|
23
|
-
statement: str
|
|
24
|
-
confidence: str
|
|
25
|
-
impact: str
|
|
26
|
-
reversible: bool
|
|
27
|
-
requires_user_confirmation: bool
|
|
28
|
-
linked_requirement_ids_json: str = Field(default="[]")
|
|
29
|
-
status: str = "open"
|
|
30
|
-
|
|
31
|
-
class TaskModel(SQLModel, table=True):
|
|
32
|
-
__tablename__ = "tasks"
|
|
33
|
-
id: str = Field(primary_key=True)
|
|
34
|
-
title: str
|
|
35
|
-
description: str
|
|
36
|
-
requirement_ids_json: str = Field(default="[]")
|
|
37
|
-
acceptance_criterion_ids_json: str = Field(default="[]")
|
|
38
|
-
planned_files_json: str = Field(default="[]")
|
|
39
|
-
expected_tests_json: str = Field(default="[]")
|
|
40
|
-
allowed_commands_json: str = Field(default="[]")
|
|
41
|
-
forbidden_changes_json: str = Field(default="[]")
|
|
42
|
-
status: str = "planned"
|
|
43
|
-
|
|
44
|
-
class EvidenceModel(SQLModel, table=True):
|
|
45
|
-
__tablename__ = "evidence"
|
|
46
|
-
id: Optional[int] = Field(default=None, primary_key=True)
|
|
47
|
-
type: str # "command", "diff", "test"
|
|
48
|
-
task_id: Optional[str] = None
|
|
49
|
-
requirement_id: Optional[str] = None
|
|
50
|
-
acceptance_criterion_id: Optional[str] = None
|
|
51
|
-
data_json: str
|
|
52
|
-
|
|
53
|
-
class GapModel(SQLModel, table=True):
|
|
54
|
-
__tablename__ = "gaps"
|
|
55
|
-
id: str = Field(primary_key=True)
|
|
56
|
-
severity: str
|
|
57
|
-
gap_type: str
|
|
58
|
-
requirement_id: Optional[str] = None
|
|
59
|
-
task_id: Optional[str] = None
|
|
60
|
-
description: str
|
|
61
|
-
evidence_json: str = Field(default="[]")
|
|
62
|
-
recommended_fix: str
|
|
63
|
-
blocking: bool
|
|
64
|
-
|
|
65
|
-
class CritiqueFindingModel(SQLModel, table=True):
|
|
66
|
-
__tablename__ = "critique_findings"
|
|
67
|
-
id: str = Field(primary_key=True)
|
|
68
|
-
source_agent: str
|
|
69
|
-
target_plan_id: str
|
|
70
|
-
severity: str
|
|
71
|
-
finding_type: str
|
|
72
|
-
claim: str
|
|
73
|
-
linked_requirement_id: Optional[str] = None
|
|
74
|
-
suggested_requirement: Optional[str] = None
|
|
75
|
-
suggested_task: Optional[str] = None
|
|
76
|
-
falsifiable_check: str
|
|
77
|
-
status: str = "open"
|
|
78
|
-
|
|
79
|
-
class ProjectStateModel(SQLModel, table=True):
|
|
80
|
-
__tablename__ = "project_state"
|
|
81
|
-
id: str = Field(primary_key=True, default="singleton")
|
|
82
|
-
current_phase: str
|
|
83
|
-
history_json: str = Field(default="[]")
|