devcouncil 0.1.0 → 0.1.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.
Files changed (128) hide show
  1. package/LICENSE +201 -201
  2. package/README.md +62 -543
  3. package/package.json +1 -1
  4. package/pyproject.toml +29 -26
  5. package/src/devcouncil/__main__.py +4 -4
  6. package/src/devcouncil/app/__init__.py +28 -28
  7. package/src/devcouncil/app/config.py +135 -108
  8. package/src/devcouncil/app/errors.py +23 -23
  9. package/src/devcouncil/app/events.py +44 -44
  10. package/src/devcouncil/app/orchestrator.py +67 -67
  11. package/src/devcouncil/app/project_status.py +29 -0
  12. package/src/devcouncil/app/run_context.py +39 -39
  13. package/src/devcouncil/app/state_machine.py +108 -108
  14. package/src/devcouncil/artifacts/__init__.py +1 -1
  15. package/src/devcouncil/artifacts/coverage.py +96 -96
  16. package/src/devcouncil/artifacts/graph.py +143 -143
  17. package/src/devcouncil/artifacts/migrations.py +20 -20
  18. package/src/devcouncil/artifacts/schemas.py +23 -23
  19. package/src/devcouncil/artifacts/serializer.py +21 -21
  20. package/src/devcouncil/artifacts/validators.py +27 -27
  21. package/src/devcouncil/cli/commands/artifacts.py +51 -48
  22. package/src/devcouncil/cli/commands/ast.py +22 -0
  23. package/src/devcouncil/cli/commands/baseline.py +35 -32
  24. package/src/devcouncil/cli/commands/config.py +76 -54
  25. package/src/devcouncil/cli/commands/dashboard.py +26 -0
  26. package/src/devcouncil/cli/commands/doctor.py +86 -42
  27. package/src/devcouncil/cli/commands/go.py +237 -0
  28. package/src/devcouncil/cli/commands/hook.py +96 -29
  29. package/src/devcouncil/cli/commands/init.py +67 -56
  30. package/src/devcouncil/cli/commands/integrate.py +320 -14
  31. package/src/devcouncil/cli/commands/lsp.py +20 -0
  32. package/src/devcouncil/cli/commands/map.py +25 -21
  33. package/src/devcouncil/cli/commands/plan.py +257 -206
  34. package/src/devcouncil/cli/commands/prompt.py +36 -33
  35. package/src/devcouncil/cli/commands/repair.py +72 -69
  36. package/src/devcouncil/cli/commands/report.py +112 -54
  37. package/src/devcouncil/cli/commands/reset_demo_state.py +31 -28
  38. package/src/devcouncil/cli/commands/rollback.py +49 -47
  39. package/src/devcouncil/cli/commands/run.py +252 -207
  40. package/src/devcouncil/cli/commands/setup.py +159 -18
  41. package/src/devcouncil/cli/commands/show.py +76 -57
  42. package/src/devcouncil/cli/commands/status.py +117 -105
  43. package/src/devcouncil/cli/commands/tasks.py +55 -41
  44. package/src/devcouncil/cli/commands/trace.py +2 -1
  45. package/src/devcouncil/cli/commands/verify.py +158 -128
  46. package/src/devcouncil/cli/commands/version.py +20 -20
  47. package/src/devcouncil/cli/commands/watch.py +574 -0
  48. package/src/devcouncil/cli/main.py +42 -24
  49. package/src/devcouncil/council/prompts/arbiter.md +19 -19
  50. package/src/devcouncil/council/prompts/critic_a.md +10 -10
  51. package/src/devcouncil/council/prompts/critic_b.md +10 -10
  52. package/src/devcouncil/council/prompts/implementation_reviewer.md +16 -16
  53. package/src/devcouncil/council/prompts/planner_a.md +16 -16
  54. package/src/devcouncil/council/prompts/planner_b.md +16 -16
  55. package/src/devcouncil/council/prompts/rebuttal.md +10 -10
  56. package/src/devcouncil/council/prompts/spec_writer.md +12 -12
  57. package/src/devcouncil/domain/assumption.py +17 -17
  58. package/src/devcouncil/domain/critique.py +32 -32
  59. package/src/devcouncil/domain/evidence.py +27 -27
  60. package/src/devcouncil/domain/gap.py +26 -26
  61. package/src/devcouncil/domain/requirement.py +22 -22
  62. package/src/devcouncil/domain/task.py +26 -26
  63. package/src/devcouncil/execution/__init__.py +1 -1
  64. package/src/devcouncil/execution/context_builder.py +54 -54
  65. package/src/devcouncil/execution/executor.py +15 -15
  66. package/src/devcouncil/execution/hook_policy.py +24 -3
  67. package/src/devcouncil/execution/patch.py +28 -28
  68. package/src/devcouncil/execution/permissions.py +44 -44
  69. package/src/devcouncil/execution/prompt_builder.py +23 -23
  70. package/src/devcouncil/execution/task_runner.py +63 -63
  71. package/src/devcouncil/executors/__init__.py +1 -1
  72. package/src/devcouncil/executors/coding_cli.py +112 -0
  73. package/src/devcouncil/executors/mini_swe.py +63 -63
  74. package/src/devcouncil/executors/native/agent.py +81 -81
  75. package/src/devcouncil/executors/openhands.py +56 -56
  76. package/src/devcouncil/gating/__init__.py +1 -1
  77. package/src/devcouncil/gating/checks/clean_git.py +50 -45
  78. package/src/devcouncil/gating/checks/planned_files_check.py +32 -32
  79. package/src/devcouncil/gating/checks/requirement_coverage.py +26 -26
  80. package/src/devcouncil/gating/checks/secret_scan_check.py +34 -34
  81. package/src/devcouncil/gating/policy.py +157 -157
  82. package/src/devcouncil/indexing/__init__.py +1 -1
  83. package/src/devcouncil/indexing/ast_matcher.py +168 -0
  84. package/src/devcouncil/indexing/graph_index.py +48 -48
  85. package/src/devcouncil/indexing/lsp.py +120 -0
  86. package/src/devcouncil/indexing/repo_mapper.py +208 -204
  87. package/src/devcouncil/integrations/github.py +35 -35
  88. package/src/devcouncil/integrations/gitnexus.py +27 -27
  89. package/src/devcouncil/integrations/graphify.py +34 -34
  90. package/src/devcouncil/integrations/mcp/server.py +549 -96
  91. package/src/devcouncil/integrations/pr_comments.py +62 -0
  92. package/src/devcouncil/live/__init__.py +2 -0
  93. package/src/devcouncil/live/cards.py +207 -0
  94. package/src/devcouncil/live/models.py +63 -0
  95. package/src/devcouncil/live/repair_prompt.py +83 -0
  96. package/src/devcouncil/live/reviewer.py +70 -0
  97. package/src/devcouncil/live/signals.py +135 -0
  98. package/src/devcouncil/live/summary.py +34 -0
  99. package/src/devcouncil/live/tasks.py +18 -0
  100. package/src/devcouncil/live/transcripts.py +138 -0
  101. package/src/devcouncil/llm/__init__.py +1 -1
  102. package/src/devcouncil/llm/cache.py +38 -38
  103. package/src/devcouncil/llm/provider.py +146 -125
  104. package/src/devcouncil/llm/router.py +111 -111
  105. package/src/devcouncil/planning/__init__.py +1 -1
  106. package/src/devcouncil/planning/arbiter_service.py +57 -57
  107. package/src/devcouncil/planning/critique_service.py +66 -66
  108. package/src/devcouncil/planning/plan_service.py +46 -46
  109. package/src/devcouncil/planning/prompt_enhancer_service.py +86 -0
  110. package/src/devcouncil/planning/repair_service.py +39 -39
  111. package/src/devcouncil/planning/spec_service.py +44 -44
  112. package/src/devcouncil/reporting/github_check.py +32 -32
  113. package/src/devcouncil/reporting/json_report.py +20 -17
  114. package/src/devcouncil/reporting/markdown_report.py +68 -46
  115. package/src/devcouncil/reporting/report_builder.py +14 -14
  116. package/src/devcouncil/storage/db.py +66 -66
  117. package/src/devcouncil/storage/models.py +83 -83
  118. package/src/devcouncil/storage/repositories.py +299 -222
  119. package/src/devcouncil/telemetry/cost.py +34 -34
  120. package/src/devcouncil/telemetry/tracker.py +49 -49
  121. package/src/devcouncil/ui/__init__.py +1 -0
  122. package/src/devcouncil/ui/dashboard.py +122 -0
  123. package/src/devcouncil/utils/__init__.py +1 -1
  124. package/src/devcouncil/utils/redaction.py +141 -141
  125. package/src/devcouncil/verification/__init__.py +1 -1
  126. package/src/devcouncil/verification/implementation_reviewer.py +55 -55
  127. package/src/devcouncil/verification/verifier.py +319 -302
  128. package/uv.lock +1 -1
@@ -0,0 +1,237 @@
1
+ import asyncio
2
+ from pathlib import Path
3
+ from types import SimpleNamespace
4
+
5
+ import typer
6
+ from rich.console import Console
7
+
8
+ from devcouncil.cli.commands import plan as plan_command
9
+ from devcouncil.cli.commands import report as report_command
10
+ from devcouncil.cli.commands import run as run_command
11
+ from devcouncil.app.config import load_config
12
+ from devcouncil.cli.commands.init import initialize_project
13
+ from devcouncil.storage.db import get_db
14
+ from devcouncil.storage.repositories import ArtifactGraphRepository, StateRepository, TaskRepository
15
+ from devcouncil.app.state_machine import ProjectPhase
16
+ from devcouncil.live.summary import live_review_summary
17
+ from devcouncil.reporting.report_builder import ReportBuilder
18
+
19
+
20
+ console = Console()
21
+
22
+ SUPPORTED_EXECUTORS = {
23
+ "codex",
24
+ "codex-cli",
25
+ "gemini",
26
+ "gemini-cli",
27
+ "claude",
28
+ "claude-code",
29
+ "claude-cli",
30
+ "native",
31
+ "mini",
32
+ "openhands",
33
+ }
34
+
35
+ AGENT_REPORT_FILE = Path(".devcouncil/reports/latest.json")
36
+
37
+
38
+ def _normalize_executor(executor: str) -> str:
39
+ return executor.strip().lower().replace("_", "-")
40
+
41
+
42
+ def _configured_executor(root: Path) -> str:
43
+ try:
44
+ configured = load_config(root).execution.default_executor
45
+ except FileNotFoundError:
46
+ configured = "codex"
47
+ return _normalize_executor(configured or "codex")
48
+
49
+
50
+ def _load_tasks(root: Path):
51
+ db = get_db(root)
52
+ if not db:
53
+ return []
54
+ with db.get_session() as session:
55
+ return TaskRepository(session).get_all()
56
+
57
+
58
+ def _load_tasks_by_id(root: Path, task_ids: list[str]):
59
+ db = get_db(root)
60
+ if not db:
61
+ return [], task_ids
62
+ with db.get_session() as session:
63
+ repo = TaskRepository(session)
64
+ tasks = []
65
+ missing = []
66
+ for task_id in task_ids:
67
+ task = repo.get_by_id(task_id)
68
+ if task is None:
69
+ missing.append(task_id)
70
+ else:
71
+ tasks.append(task)
72
+ return tasks, missing
73
+
74
+
75
+ def _unique_task_ids(task_ids: list[str]) -> list[str]:
76
+ seen = set()
77
+ unique = []
78
+ for task_id in task_ids:
79
+ if task_id in seen:
80
+ continue
81
+ seen.add(task_id)
82
+ unique.append(task_id)
83
+ return unique
84
+
85
+
86
+ def _record_project_done(root: Path) -> None:
87
+ db = get_db(root)
88
+ if not db:
89
+ return
90
+ with db.get_session() as session:
91
+ StateRepository(session).record_phase(ProjectPhase.PROJECT_DONE.value)
92
+
93
+
94
+ def _record_project_blocked(root: Path) -> None:
95
+ db = get_db(root)
96
+ if not db:
97
+ return
98
+ with db.get_session() as session:
99
+ StateRepository(session).record_phase(ProjectPhase.TASK_BLOCKED.value)
100
+
101
+
102
+ def _render_final_report(root: Path, json_report: bool) -> str:
103
+ db = get_db(root)
104
+ if not db:
105
+ raise RuntimeError("DevCouncil state is unavailable in this directory.")
106
+ with db.get_session() as session:
107
+ graph = ArtifactGraphRepository(session).load_graph()
108
+ live_review = live_review_summary(root)
109
+ if json_report:
110
+ return ReportBuilder.build_json(graph, live_review=live_review)
111
+ return ReportBuilder.build_markdown(graph, live_review=live_review)
112
+
113
+
114
+ def _write_report_file(root: Path, report_file: Path, content: str) -> Path:
115
+ path = report_file.expanduser()
116
+ if not path.is_absolute():
117
+ path = root / path
118
+ path.parent.mkdir(parents=True, exist_ok=True)
119
+ path.write_text(content, encoding="utf-8")
120
+ return path
121
+
122
+
123
+ def _command_label(ctx: typer.Context) -> str:
124
+ command = ctx.info_name or "e2e"
125
+ return f"dev {command}"
126
+
127
+
128
+ def go(
129
+ ctx: typer.Context,
130
+ goal: str = typer.Argument(..., help="Implementation goal to plan, execute, verify, and report."),
131
+ executor: str | None = typer.Option(
132
+ None,
133
+ "--executor",
134
+ "-e",
135
+ help="Automated executor to use. Defaults to execution.default_executor in .devcouncil/config.yaml.",
136
+ ),
137
+ dry_run: bool = typer.Option(False, "--dry-run", help="Use mock planning responses for local smoke testing."),
138
+ continue_on_blocked: bool = typer.Option(
139
+ False,
140
+ "--continue-on-blocked",
141
+ help="Continue later tasks even if an earlier task is blocked by verification.",
142
+ ),
143
+ json_report: bool = typer.Option(False, "--json-report", "--json", help="Print the final report as JSON."),
144
+ report_file: Path | None = typer.Option(
145
+ None,
146
+ "--report-file",
147
+ help="Write the final report to a file. Relative paths resolve from --project-root.",
148
+ ),
149
+ agent: bool = typer.Option(
150
+ False,
151
+ "--agent",
152
+ help="Use coding-agent defaults: JSON report plus .devcouncil/reports/latest.json.",
153
+ ),
154
+ project_root: Path = typer.Option(Path("."), "--project-root", help="Repository root containing .devcouncil/."),
155
+ ):
156
+ """
157
+ Run the full DevCouncil loop in one command.
158
+ """
159
+ root = project_root.expanduser().resolve()
160
+ initialize_project(root, quiet=True)
161
+ if agent:
162
+ json_report = True
163
+ if report_file is None:
164
+ report_file = AGENT_REPORT_FILE
165
+
166
+ normalized_executor = _normalize_executor(executor) if executor else _configured_executor(root)
167
+ command_label = _command_label(ctx)
168
+ if normalized_executor == "manual":
169
+ console.print(
170
+ f"[red]`{command_label}` requires an automated executor. "
171
+ "Use `dev run TASK-ID --executor manual` for handoff mode.[/red]"
172
+ )
173
+ raise typer.Exit(code=2)
174
+ if normalized_executor not in SUPPORTED_EXECUTORS:
175
+ console.print(
176
+ f"[red]Unsupported executor for `{command_label}`: "
177
+ f"{normalized_executor}. Supported: {', '.join(sorted(SUPPORTED_EXECUTORS))}.[/red]"
178
+ )
179
+ raise typer.Exit(code=2)
180
+
181
+ console.print(f"[bold]Planning goal:[/bold] {goal}")
182
+ planned_task_ids = asyncio.run(plan_command.run_plan_flow(goal, dry_run=dry_run, persist=True, project_root=root))
183
+
184
+ task_ids = _unique_task_ids(planned_task_ids or [])
185
+ tasks, missing_task_ids = _load_tasks_by_id(root, task_ids)
186
+ if missing_task_ids:
187
+ console.print(f"[red]Planning returned task IDs that were not persisted: {', '.join(missing_task_ids)}[/red]")
188
+ raise typer.Exit(code=1)
189
+ if not tasks:
190
+ console.print("[red]Planning did not produce any approved tasks.[/red]")
191
+ raise typer.Exit(code=1)
192
+
193
+ failed: list[str] = []
194
+ executed_task_ids: list[str] = []
195
+ for task in tasks:
196
+ if task.status in {"verified", "done"}:
197
+ console.print(f"[green]Skipping {task.id}; already {task.status}.[/green]")
198
+ continue
199
+
200
+ console.print(f"\n[bold]Executing {task.id}[/bold] with [bold]{normalized_executor}[/bold]...")
201
+ executed_task_ids.append(task.id)
202
+ run_command.run(task.id, executor=normalized_executor, project_root=root)
203
+
204
+ latest = {item.id: item for item in _load_tasks(root)}.get(task.id)
205
+ latest_status = latest.status if latest else "missing"
206
+ if latest_status not in {"verified", "done"}:
207
+ failed.append(f"{task.id} ({latest_status})")
208
+ if latest_status != "blocked" or not continue_on_blocked:
209
+ console.print(f"[red]Stopping because {task.id} ended as {latest_status}.[/red]")
210
+ break
211
+
212
+ if not executed_task_ids:
213
+ failed.append("all planned tasks were already completed before execution")
214
+
215
+ if not failed:
216
+ _record_project_done(root)
217
+ else:
218
+ _record_project_blocked(root)
219
+
220
+ console.print("\n[bold]Final DevCouncil report[/bold]")
221
+ report_command.report(
222
+ SimpleNamespace(invoked_subcommand=None),
223
+ planning_only=False,
224
+ json_format=json_report,
225
+ github=False,
226
+ github_pr_comment=False,
227
+ gitlab_pr_comment=False,
228
+ project_root=root,
229
+ )
230
+ if report_file is not None:
231
+ output = _render_final_report(root, json_report=json_report)
232
+ written = _write_report_file(root, report_file, output)
233
+ console.print(f"[green]Final report written to {written}[/green]")
234
+
235
+ if failed:
236
+ console.print(f"\n[red]Unfinished task(s): {', '.join(failed)}[/red]")
237
+ raise typer.Exit(code=1)
@@ -7,21 +7,55 @@ from rich.console import Console
7
7
  from devcouncil.storage.db import get_db
8
8
  from devcouncil.storage.repositories import TaskRepository
9
9
  from devcouncil.execution.hook_policy import HookPolicy
10
+ from devcouncil.telemetry.traces import TraceLogger
11
+ from devcouncil.live.signals import write_signal
12
+ from devcouncil.live.tasks import active_task_id
10
13
 
11
14
  app = typer.Typer()
12
15
  console = Console()
13
16
 
14
17
 
15
- def _project_root() -> Path:
18
+ def _project_root(project_root: Path | None = None) -> Path:
19
+ if project_root:
20
+ return project_root.expanduser().resolve()
16
21
  configured = os.environ.get("DEVCOUNCIL_PROJECT_ROOT")
17
22
  return Path(configured).expanduser().resolve() if configured else Path(".").resolve()
18
-
23
+
24
+
25
+ def _active_task(root: Path):
26
+ db = get_db(root)
27
+ if not db:
28
+ return None
29
+ with db.get_session() as session:
30
+ task_repo = TaskRepository(session)
31
+ running_tasks = [t for t in task_repo.get_all() if t.status == "running"]
32
+ return running_tasks[0] if running_tasks else None
33
+
34
+
35
+ def _emit_decision(client: str, action: str, reason: str) -> None:
36
+ if action == "deny":
37
+ print(reason, file=sys.stderr)
38
+ raise typer.Exit(code=2)
39
+
40
+ if client in {"codex", "gemini"}:
41
+ payload = {"decision": "allow", "reason": reason, "suppressOutput": True}
42
+ if action == "warn":
43
+ payload["systemMessage"] = f"DevCouncil Warning: {reason}"
44
+ print(json.dumps(payload, separators=(",", ":")))
45
+ return
46
+
47
+ if action == "warn":
48
+ console.print(f"[yellow]DevCouncil Warning:[/yellow] {reason}")
49
+
50
+
19
51
  @app.command()
20
52
  def pre_tool_use(
21
- tool_call_json: str | None = typer.Argument(None, help="The JSON string of the tool call from Claude Code")
53
+ tool_call_json: str | None = typer.Argument(None, help="The JSON string of the tool call from the coding CLI."),
54
+ client: str = typer.Option("claude", "--client", help="Hook client: claude, codex, gemini, cursor, or generic."),
55
+ project_root: Path | None = typer.Option(None, "--project-root", help="Repository root containing .devcouncil/."),
22
56
  ):
23
- """
24
- Claude Code hook: Inspects a tool call before execution.
57
+ """
58
+ Coding CLI hook: Inspects a tool call before execution.
25
59
  Exits with code 2 to block unauthorized file writes.
26
60
  """
27
61
  try:
@@ -30,32 +64,65 @@ def pre_tool_use(
30
64
  if not tool_call_json.strip():
31
65
  raise typer.Exit(code=0)
32
66
  call_data = json.loads(tool_call_json)
33
- active_task = None
34
- root = _project_root()
35
- db = get_db(root)
36
- if db:
37
- with db.get_session() as session:
38
- task_repo = TaskRepository(session)
39
- running_tasks = [t for t in task_repo.get_all() if t.status == "running"]
40
- active_task = running_tasks[0] if running_tasks else None
67
+ normalized_client = client.lower()
68
+ root = _project_root(project_root)
69
+ active_task = _active_task(root)
41
70
 
42
71
  decision = HookPolicy(project_root=root).evaluate(call_data, active_task)
43
- if decision.action == "deny":
44
- console.print(f"[red]DevCouncil Blocked Action:[/red] {decision.reason}")
45
- sys.exit(2)
46
- if decision.action == "warn":
47
- console.print(f"[yellow]DevCouncil Warning:[/yellow] {decision.reason}")
72
+ _emit_decision(normalized_client, decision.action, decision.reason)
48
73
 
49
74
  except json.JSONDecodeError:
50
75
  raise typer.Exit(code=0)
51
-
52
- @app.command()
53
- def post_task():
54
- """
55
- Claude Code hook: Runs after a task is completed.
56
- Triggers deterministic verification.
57
- """
58
- console.print("[cyan]DevCouncil: Claude finished task. Triggering automatic verification...[/cyan]")
59
- # In a real environment, this would invoke 'dev verify <active-task>'
60
- # For the hook script, we just notify the user.
61
- console.print("Run [bold]dev verify[/bold] to finalize implementation evidence.")
76
+
77
+ @app.command()
78
+ def post_tool_use(
79
+ tool_call_json: str | None = typer.Argument(None, help="The JSON string of the completed tool call."),
80
+ client: str = typer.Option("claude", "--client", help="Hook client: claude, codex, gemini, cursor, or generic."),
81
+ project_root: Path | None = typer.Option(None, "--project-root", help="Repository root containing .devcouncil/."),
82
+ ):
83
+ """
84
+ Coding CLI hook: Records a post-tool-use checkpoint for native hook clients.
85
+ """
86
+ _ = tool_call_json if tool_call_json is not None else sys.stdin.read()
87
+ _ = _project_root(project_root)
88
+ if client.lower() in {"codex", "gemini"}:
89
+ print(json.dumps({"decision": "allow", "suppressOutput": True}, separators=(",", ":")))
90
+
91
+ @app.command()
92
+ def agent_response(
93
+ event_json: str | None = typer.Argument(None, help="The JSON hook payload from the coding CLI."),
94
+ client: str = typer.Option("claude", "--client", help="Hook client: claude, codex, gemini, cursor, or generic."),
95
+ project_root: Path | None = typer.Option(None, "--project-root", help="Repository root containing .devcouncil/."),
96
+ ):
97
+ """
98
+ Coding CLI hook: records that an agent response is ready for DevCouncil watch review.
99
+ """
100
+ payload_text = event_json if event_json is not None else sys.stdin.read()
101
+ root = _project_root(project_root)
102
+ try:
103
+ payload = json.loads(payload_text) if payload_text.strip() else {}
104
+ except json.JSONDecodeError:
105
+ payload = {"raw": payload_text}
106
+ if isinstance(payload, dict) and not any(key in payload for key in ("task_id", "taskId", "task")):
107
+ active_id = active_task_id(root)
108
+ if active_id:
109
+ payload["task_id"] = active_id
110
+ signal_path = write_signal(root, client.lower(), payload)
111
+ TraceLogger(root).log_event(
112
+ "agent_response_ready",
113
+ {"client": client.lower(), "signal": str(signal_path)},
114
+ summary=f"{client} response ready for critique-card review.",
115
+ )
116
+ if client.lower() in {"codex", "gemini"}:
117
+ print(json.dumps({"decision": "allow", "suppressOutput": True}, separators=(",", ":")))
118
+
119
+ @app.command()
120
+ def post_task():
121
+ """
122
+ Coding CLI hook: Runs after a task is completed.
123
+ Triggers deterministic verification.
124
+ """
125
+ console.print("[cyan]DevCouncil: coding agent finished task. Triggering automatic verification...[/cyan]")
126
+ # In a real environment, this would invoke 'dev verify <active-task>'
127
+ # For the hook script, we just notify the user.
128
+ console.print("Run [bold]dev verify[/bold] to finalize implementation evidence.")
@@ -1,52 +1,54 @@
1
- import copy
2
- import typer
3
- import yaml
4
- from rich.console import Console
5
- from pathlib import Path
6
- from devcouncil.storage.db import Database
7
- from devcouncil.integrations.gitnexus import GitNexusIntegration
8
- from devcouncil.integrations.graphify import GraphifyIntegration
9
-
10
- app = typer.Typer()
11
- console = Console()
12
-
1
+ import copy
2
+ import typer
3
+ import yaml
4
+ from rich.console import Console
5
+ from pathlib import Path
6
+ from devcouncil.storage.db import Database
7
+ from devcouncil.integrations.gitnexus import GitNexusIntegration
8
+ from devcouncil.integrations.graphify import GraphifyIntegration
9
+
10
+ app = typer.Typer()
11
+ console = Console()
12
+
13
13
  DEFAULT_CONFIG = {
14
- "project": {
15
- "name": "devcouncil-project",
16
- "root": ".",
17
- "default_branch": "main",
18
- },
19
- "models": {
20
- "provider": "openrouter",
21
- "roles": {
22
- "spec_writer": {"model": "anthropic/claude-3.5-sonnet"},
23
- "planner_a": {"model": "anthropic/claude-3.5-sonnet"},
24
- "planner_b": {"model": "google/gemini-pro-1.5"},
25
- "critic_a": {"model": "openai/gpt-4o"},
26
- "critic_b": {"model": "anthropic/claude-3-opus"},
27
- "arbiter": {"model": "openai/gpt-4o"},
28
- "native_agent": {"model": "anthropic/claude-3.5-sonnet"},
29
- "implementation_reviewer": {"model": "openai/gpt-4o"},
30
- }
31
- },
32
- "commands": {
33
- "test": ["pytest", "npm test"],
34
- "lint": ["flake8", "eslint"],
35
- "typecheck": ["mypy", "tsc"]
36
- },
37
- "gates": {
38
- "require_clean_git_before_task": True,
39
- "block_orphan_diffs": True,
40
- "block_missing_tests_for_high_requirements": True,
41
- "block_dependency_changes_without_approval": True,
42
- "block_schema_change_without_migration": True,
43
- "block_failed_commands": True
44
- },
45
- "execution": {
46
- "default_executor": "native",
47
- "max_repair_attempts": 3,
48
- "checkpoint_before_each_task": True
49
- },
14
+ "project": {
15
+ "name": "devcouncil-project",
16
+ "root": ".",
17
+ "default_branch": "main",
18
+ },
19
+ "models": {
20
+ "provider": "openrouter",
21
+ "roles": {
22
+ "spec_writer": {"model": "anthropic/claude-3.5-sonnet"},
23
+ "prompt_enhancer": {"model": "anthropic/claude-3.5-sonnet"},
24
+ "planner_a": {"model": "anthropic/claude-3.5-sonnet"},
25
+ "planner_b": {"model": "google/gemini-pro-1.5"},
26
+ "critic_a": {"model": "openai/gpt-4o"},
27
+ "critic_b": {"model": "anthropic/claude-3-opus"},
28
+ "arbiter": {"model": "openai/gpt-4o"},
29
+ "native_agent": {"model": "anthropic/claude-3.5-sonnet"},
30
+ "implementation_reviewer": {"model": "openai/gpt-4o"},
31
+ "live_reviewer": {"model": "openai/gpt-4o"},
32
+ }
33
+ },
34
+ "commands": {
35
+ "test": ["pytest", "npm test"],
36
+ "lint": ["flake8", "eslint"],
37
+ "typecheck": ["mypy", "tsc"]
38
+ },
39
+ "gates": {
40
+ "require_clean_git_before_task": True,
41
+ "block_orphan_diffs": True,
42
+ "block_missing_tests_for_high_requirements": True,
43
+ "block_dependency_changes_without_approval": True,
44
+ "block_schema_change_without_migration": True,
45
+ "block_failed_commands": True
46
+ },
47
+ "execution": {
48
+ "default_executor": "native",
49
+ "max_repair_attempts": 3,
50
+ "checkpoint_before_each_task": True
51
+ },
50
52
  "privacy": {
51
53
  "redact_env_vars": True,
52
54
  "redact_secrets_in_logs": True,
@@ -63,6 +65,12 @@ DEFAULT_CONFIG = {
63
65
  "command": "code-review-graph",
64
66
  "optional": True,
65
67
  },
68
+ "live_review": {
69
+ "enabled": True,
70
+ "cards_path": ".devcouncil/live/cards",
71
+ "signals_path": ".devcouncil/live/signals",
72
+ "default_client": "claude",
73
+ },
66
74
  }
67
75
  }
68
76
 
@@ -72,6 +80,7 @@ def initialize_project(
72
80
  project_name: str | None = None,
73
81
  with_gitnexus: bool = False,
74
82
  with_graphify: bool = False,
83
+ quiet: bool = False,
75
84
  ) -> bool:
76
85
  """Initialize DevCouncil project state.
77
86
 
@@ -82,7 +91,8 @@ def initialize_project(
82
91
  created = False
83
92
 
84
93
  if not dev_dir.exists():
85
- console.print("Initializing DevCouncil...")
94
+ if not quiet:
95
+ console.print("Initializing DevCouncil...")
86
96
  dev_dir.mkdir(exist_ok=True)
87
97
  (dev_dir / "runs").mkdir(exist_ok=True)
88
98
  (dev_dir / "cache").mkdir(exist_ok=True)
@@ -101,7 +111,8 @@ def initialize_project(
101
111
 
102
112
  db = Database(dev_dir / "state.sqlite")
103
113
  db.create_db_and_tables()
104
- console.print(f"[green]Successfully initialized DevCouncil in {dev_dir}[/green]")
114
+ if not quiet:
115
+ console.print(f"[green]Successfully initialized DevCouncil in {dev_dir}[/green]")
105
116
  created = True
106
117
 
107
118
  if with_gitnexus:
@@ -117,12 +128,12 @@ def initialize_project(
117
128
 
118
129
  @app.callback(invoke_without_command=True)
119
130
  def init(
120
- ctx: typer.Context,
121
- project_name: str = typer.Option(None, "--name", "-n", help="Project name"),
122
- with_gitnexus: bool = typer.Option(False, "--gitnexus", help="Initialize GitNexus structural awareness"),
123
- with_graphify: bool = typer.Option(False, "--graphify", help="Initialize Graphify knowledge graph engine"),
124
- ):
125
- """
131
+ ctx: typer.Context,
132
+ project_name: str = typer.Option(None, "--name", "-n", help="Project name"),
133
+ with_gitnexus: bool = typer.Option(False, "--gitnexus", help="Initialize GitNexus structural awareness"),
134
+ with_graphify: bool = typer.Option(False, "--graphify", help="Initialize Graphify knowledge graph engine"),
135
+ ):
136
+ """
126
137
  Initialize DevCouncil in the current directory.
127
138
  """
128
139
  if ctx.invoked_subcommand is not None: