devcouncil 0.1.0

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 (125) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +643 -0
  3. package/bin/devcouncil.js +62 -0
  4. package/package.json +47 -0
  5. package/pyproject.toml +31 -0
  6. package/src/devcouncil/__init__.py +0 -0
  7. package/src/devcouncil/__main__.py +4 -0
  8. package/src/devcouncil/app/__init__.py +28 -0
  9. package/src/devcouncil/app/config.py +131 -0
  10. package/src/devcouncil/app/errors.py +23 -0
  11. package/src/devcouncil/app/events.py +44 -0
  12. package/src/devcouncil/app/orchestrator.py +92 -0
  13. package/src/devcouncil/app/run_context.py +39 -0
  14. package/src/devcouncil/app/state_machine.py +108 -0
  15. package/src/devcouncil/artifacts/__init__.py +1 -0
  16. package/src/devcouncil/artifacts/coverage.py +96 -0
  17. package/src/devcouncil/artifacts/graph.py +143 -0
  18. package/src/devcouncil/artifacts/migrations.py +20 -0
  19. package/src/devcouncil/artifacts/schemas.py +23 -0
  20. package/src/devcouncil/artifacts/serializer.py +21 -0
  21. package/src/devcouncil/artifacts/validators.py +27 -0
  22. package/src/devcouncil/cli/__init__.py +0 -0
  23. package/src/devcouncil/cli/commands/__init__.py +0 -0
  24. package/src/devcouncil/cli/commands/artifacts.py +48 -0
  25. package/src/devcouncil/cli/commands/baseline.py +32 -0
  26. package/src/devcouncil/cli/commands/config.py +54 -0
  27. package/src/devcouncil/cli/commands/doctor.py +96 -0
  28. package/src/devcouncil/cli/commands/hook.py +61 -0
  29. package/src/devcouncil/cli/commands/init.py +142 -0
  30. package/src/devcouncil/cli/commands/integrate.py +420 -0
  31. package/src/devcouncil/cli/commands/map.py +38 -0
  32. package/src/devcouncil/cli/commands/mcp_server.py +18 -0
  33. package/src/devcouncil/cli/commands/plan.py +276 -0
  34. package/src/devcouncil/cli/commands/prompt.py +47 -0
  35. package/src/devcouncil/cli/commands/repair.py +69 -0
  36. package/src/devcouncil/cli/commands/report.py +71 -0
  37. package/src/devcouncil/cli/commands/reset_demo_state.py +28 -0
  38. package/src/devcouncil/cli/commands/rollback.py +58 -0
  39. package/src/devcouncil/cli/commands/run.py +224 -0
  40. package/src/devcouncil/cli/commands/setup.py +82 -0
  41. package/src/devcouncil/cli/commands/show.py +57 -0
  42. package/src/devcouncil/cli/commands/status.py +105 -0
  43. package/src/devcouncil/cli/commands/tasks.py +41 -0
  44. package/src/devcouncil/cli/commands/trace.py +43 -0
  45. package/src/devcouncil/cli/commands/verify.py +163 -0
  46. package/src/devcouncil/cli/commands/version.py +20 -0
  47. package/src/devcouncil/cli/main.py +70 -0
  48. package/src/devcouncil/council/__init__.py +0 -0
  49. package/src/devcouncil/council/prompts/__init__.py +0 -0
  50. package/src/devcouncil/council/prompts/arbiter.md +19 -0
  51. package/src/devcouncil/council/prompts/critic_a.md +10 -0
  52. package/src/devcouncil/council/prompts/critic_b.md +10 -0
  53. package/src/devcouncil/council/prompts/implementation_reviewer.md +16 -0
  54. package/src/devcouncil/council/prompts/planner_a.md +16 -0
  55. package/src/devcouncil/council/prompts/planner_b.md +16 -0
  56. package/src/devcouncil/council/prompts/rebuttal.md +10 -0
  57. package/src/devcouncil/council/prompts/spec_writer.md +12 -0
  58. package/src/devcouncil/domain/__init__.py +0 -0
  59. package/src/devcouncil/domain/assumption.py +17 -0
  60. package/src/devcouncil/domain/critique.py +32 -0
  61. package/src/devcouncil/domain/evidence.py +27 -0
  62. package/src/devcouncil/domain/gap.py +26 -0
  63. package/src/devcouncil/domain/requirement.py +22 -0
  64. package/src/devcouncil/domain/task.py +26 -0
  65. package/src/devcouncil/execution/__init__.py +1 -0
  66. package/src/devcouncil/execution/context_builder.py +60 -0
  67. package/src/devcouncil/execution/executor.py +15 -0
  68. package/src/devcouncil/execution/hook_policy.py +144 -0
  69. package/src/devcouncil/execution/patch.py +28 -0
  70. package/src/devcouncil/execution/paths.py +14 -0
  71. package/src/devcouncil/execution/permissions.py +92 -0
  72. package/src/devcouncil/execution/prompt_builder.py +59 -0
  73. package/src/devcouncil/execution/task_runner.py +166 -0
  74. package/src/devcouncil/executors/__init__.py +1 -0
  75. package/src/devcouncil/executors/mini_swe.py +73 -0
  76. package/src/devcouncil/executors/native/__init__.py +0 -0
  77. package/src/devcouncil/executors/native/agent.py +107 -0
  78. package/src/devcouncil/executors/openhands.py +71 -0
  79. package/src/devcouncil/gating/__init__.py +1 -0
  80. package/src/devcouncil/gating/checks/__init__.py +0 -0
  81. package/src/devcouncil/gating/checks/clean_git.py +45 -0
  82. package/src/devcouncil/gating/checks/planned_files_check.py +32 -0
  83. package/src/devcouncil/gating/checks/requirement_coverage.py +26 -0
  84. package/src/devcouncil/gating/checks/secret_scan_check.py +34 -0
  85. package/src/devcouncil/gating/policy.py +190 -0
  86. package/src/devcouncil/indexing/__init__.py +1 -0
  87. package/src/devcouncil/indexing/graph_index.py +48 -0
  88. package/src/devcouncil/indexing/repo_mapper.py +204 -0
  89. package/src/devcouncil/indexing/symbol_index.py +0 -0
  90. package/src/devcouncil/integrations/code_review_graph.py +163 -0
  91. package/src/devcouncil/integrations/github.py +39 -0
  92. package/src/devcouncil/integrations/gitnexus.py +27 -0
  93. package/src/devcouncil/integrations/graphify.py +34 -0
  94. package/src/devcouncil/integrations/mcp/__init__.py +0 -0
  95. package/src/devcouncil/integrations/mcp/server.py +146 -0
  96. package/src/devcouncil/llm/__init__.py +1 -0
  97. package/src/devcouncil/llm/cache.py +38 -0
  98. package/src/devcouncil/llm/provider.py +125 -0
  99. package/src/devcouncil/llm/router.py +125 -0
  100. package/src/devcouncil/planning/__init__.py +1 -0
  101. package/src/devcouncil/planning/arbiter_service.py +57 -0
  102. package/src/devcouncil/planning/critique_service.py +66 -0
  103. package/src/devcouncil/planning/plan_service.py +46 -0
  104. package/src/devcouncil/planning/repair_service.py +39 -0
  105. package/src/devcouncil/planning/spec_service.py +44 -0
  106. package/src/devcouncil/repo/__init__.py +0 -0
  107. package/src/devcouncil/reporting/__init__.py +0 -0
  108. package/src/devcouncil/reporting/github_check.py +32 -0
  109. package/src/devcouncil/reporting/json_report.py +17 -0
  110. package/src/devcouncil/reporting/markdown_report.py +46 -0
  111. package/src/devcouncil/reporting/report_builder.py +14 -0
  112. package/src/devcouncil/storage/__init__.py +0 -0
  113. package/src/devcouncil/storage/db.py +66 -0
  114. package/src/devcouncil/storage/models.py +83 -0
  115. package/src/devcouncil/storage/repositories.py +346 -0
  116. package/src/devcouncil/telemetry/__init__.py +0 -0
  117. package/src/devcouncil/telemetry/cost.py +34 -0
  118. package/src/devcouncil/telemetry/traces.py +91 -0
  119. package/src/devcouncil/telemetry/tracker.py +49 -0
  120. package/src/devcouncil/utils/__init__.py +1 -0
  121. package/src/devcouncil/utils/redaction.py +141 -0
  122. package/src/devcouncil/verification/__init__.py +1 -0
  123. package/src/devcouncil/verification/implementation_reviewer.py +55 -0
  124. package/src/devcouncil/verification/verifier.py +513 -0
  125. package/uv.lock +1085 -0
@@ -0,0 +1,420 @@
1
+ import shutil
2
+ import subprocess
3
+ import sys
4
+ from pathlib import Path
5
+
6
+ import typer
7
+ import yaml
8
+ from rich.console import Console
9
+ from rich.table import Table
10
+
11
+ app = typer.Typer(help="Set up DevCouncil integrations with coding CLIs.")
12
+ setup_app = typer.Typer(help="Set up optional external companion integrations.")
13
+ app.add_typer(setup_app, name="setup")
14
+ console = Console()
15
+
16
+ SUPPORTED_TOOLS = ("codex", "gemini")
17
+
18
+
19
+ def _project_root(path: Path | None) -> Path:
20
+ return (path or Path(".")).expanduser().resolve()
21
+
22
+
23
+ def _server_args(project_root: Path) -> list[str]:
24
+ return ["devcouncil", "mcp-server"]
25
+
26
+
27
+ def _codex_command(project_root: Path) -> list[str]:
28
+ return [
29
+ "codex",
30
+ "mcp",
31
+ "add",
32
+ "devcouncil",
33
+ "--env",
34
+ f"DEVCOUNCIL_PROJECT_ROOT={project_root}",
35
+ "--",
36
+ *_server_args(project_root),
37
+ ]
38
+
39
+
40
+ def _gemini_command(project_root: Path, scope: str) -> list[str]:
41
+ return [
42
+ "gemini",
43
+ "mcp",
44
+ "add",
45
+ "--scope",
46
+ scope,
47
+ "--env",
48
+ f"DEVCOUNCIL_PROJECT_ROOT={project_root}",
49
+ "devcouncil",
50
+ *_server_args(project_root),
51
+ ]
52
+
53
+
54
+ def _format_command(command: list[str]) -> str:
55
+ return subprocess.list2cmdline(command)
56
+
57
+
58
+ def _run(command: list[str]) -> int:
59
+ executable = shutil.which(command[0])
60
+ if not executable:
61
+ return 127
62
+ resolved = [executable, *command[1:]]
63
+ use_shell = sys.platform == "win32" and Path(executable).suffix.lower() in {".bat", ".cmd", ".ps1"}
64
+ invocation = subprocess.list2cmdline(resolved) if use_shell else resolved
65
+ result = subprocess.run(invocation, text=True, shell=use_shell)
66
+ return result.returncode
67
+
68
+
69
+ def _run_capture(command: list[str], timeout: int = 10) -> tuple[int, str]:
70
+ executable = shutil.which(command[0])
71
+ if not executable:
72
+ return 127, f"{command[0]} not found on PATH"
73
+
74
+ resolved = [executable, *command[1:]]
75
+ use_shell = sys.platform == "win32" and Path(executable).suffix.lower() in {".bat", ".cmd", ".ps1"}
76
+ invocation = subprocess.list2cmdline(resolved) if use_shell else resolved
77
+ try:
78
+ result = subprocess.run(
79
+ invocation,
80
+ capture_output=True,
81
+ text=True,
82
+ encoding="utf-8",
83
+ errors="replace",
84
+ shell=use_shell,
85
+ timeout=timeout,
86
+ )
87
+ except subprocess.TimeoutExpired:
88
+ return 124, "timed out"
89
+ return result.returncode, (result.stdout + result.stderr).strip()
90
+
91
+
92
+ def _config_path(project_root: Path) -> Path:
93
+ return project_root / ".devcouncil" / "config.yaml"
94
+
95
+
96
+ def _load_raw_config(project_root: Path) -> dict:
97
+ path = _config_path(project_root)
98
+ if not path.exists():
99
+ return {}
100
+ return yaml.safe_load(path.read_text(encoding="utf-8")) or {}
101
+
102
+
103
+ def _save_raw_config(project_root: Path, config: dict) -> None:
104
+ path = _config_path(project_root)
105
+ path.parent.mkdir(parents=True, exist_ok=True)
106
+ path.write_text(yaml.safe_dump(config, sort_keys=False), encoding="utf-8")
107
+
108
+
109
+ def _print_command(tool: str, command: list[str], apply: bool):
110
+ if apply:
111
+ console.print(f"[cyan]Configuring {tool} MCP integration...[/cyan]")
112
+ else:
113
+ console.print(f"[bold]{tool}[/bold]")
114
+ console.print(_format_command(command), soft_wrap=True)
115
+
116
+
117
+ def _configure(tool: str, command: list[str], apply: bool) -> bool:
118
+ executable = command[0]
119
+ if not shutil.which(executable):
120
+ console.print(f"[yellow]{tool} CLI not found on PATH. Install it first, then rerun this command.[/yellow]")
121
+ console.print(_format_command(command), soft_wrap=True)
122
+ return False
123
+
124
+ _print_command(tool, command, apply)
125
+ if not apply:
126
+ return True
127
+
128
+ code = _run(command)
129
+ if code == 0:
130
+ console.print(f"[green]{tool} integration configured.[/green]")
131
+ return True
132
+
133
+ console.print(f"[red]{tool} integration command failed with exit code {code}.[/red]")
134
+ console.print("You can rerun it manually:")
135
+ console.print(_format_command(command), soft_wrap=True)
136
+ return False
137
+
138
+
139
+ @app.callback(invoke_without_command=True)
140
+ def overview(ctx: typer.Context):
141
+ """
142
+ Show integration options for supported coding CLIs.
143
+ """
144
+ if ctx.invoked_subcommand is not None:
145
+ return
146
+
147
+ table = Table(title="DevCouncil Coding CLI Integrations")
148
+ table.add_column("Tool", style="cyan")
149
+ table.add_column("Setup command", style="green")
150
+ table.add_column("Notes")
151
+ table.add_row("Codex CLI", "dev integrate codex --apply", "Adds DevCouncil as a stdio MCP server.")
152
+ table.add_row("Gemini CLI", "dev integrate gemini --apply", "Adds DevCouncil as a project-scoped stdio MCP server.")
153
+ table.add_row("Both", "dev integrate all --apply", "Runs both setup commands.")
154
+ console.print(table)
155
+ console.print("\nRun without [bold]--apply[/bold] to preview the exact commands first.")
156
+
157
+
158
+ @app.command("doctor")
159
+ def integrations_doctor():
160
+ """Check optional integration tools and local client wiring prerequisites."""
161
+ table = Table(title="DevCouncil Integration Doctor")
162
+ table.add_column("Integration", style="cyan")
163
+ table.add_column("Status")
164
+ table.add_column("Notes")
165
+
166
+ checks = [
167
+ ("Agent Flow", "agent-flow-app", "Optional live/replay visualizer for trace JSONL."),
168
+ ("code-review-graph", "code-review-graph", "Optional structural graph context adapter."),
169
+ ("Claude Code", "claude", "Optional hook runtime for pre-tool-use enforcement."),
170
+ ("Codex CLI", "codex", "Optional MCP client and headless executor companion."),
171
+ ("Gemini CLI", "gemini", "Optional MCP client companion."),
172
+ ]
173
+ for label, executable, notes in checks:
174
+ found = shutil.which(executable)
175
+ table.add_row(label, "[green]OK[/green]" if found else "[yellow]Missing[/yellow]", found or notes)
176
+
177
+ config = _config_path(Path("."))
178
+ table.add_row(
179
+ "DevCouncil config",
180
+ "[green]OK[/green]" if config.exists() else "[red]Missing[/red]",
181
+ str(config) if config.exists() else "Run dev init first.",
182
+ )
183
+ console.print(table)
184
+
185
+
186
+ @app.command("codex")
187
+ def codex(
188
+ apply: bool = typer.Option(False, "--apply", help="Run the setup command instead of printing it."),
189
+ project_root: Path | None = typer.Option(None, "--project-root", help="Repository root containing .devcouncil/."),
190
+ ):
191
+ """
192
+ Set up DevCouncil MCP tools for Codex CLI.
193
+ """
194
+ root = _project_root(project_root)
195
+ command = _codex_command(root)
196
+ ok = _configure("Codex CLI", command, apply)
197
+ if not ok and apply:
198
+ raise typer.Exit(code=1)
199
+
200
+
201
+ @app.command("gemini")
202
+ def gemini(
203
+ apply: bool = typer.Option(False, "--apply", help="Run the setup command instead of printing it."),
204
+ project_root: Path | None = typer.Option(None, "--project-root", help="Repository root containing .devcouncil/."),
205
+ scope: str = typer.Option("project", "--scope", help="Gemini MCP config scope: project or user."),
206
+ ):
207
+ """
208
+ Set up DevCouncil MCP tools for Gemini CLI.
209
+ """
210
+ if scope not in {"project", "user"}:
211
+ console.print("[red]--scope must be 'project' or 'user'.[/red]")
212
+ raise typer.Exit(code=2)
213
+
214
+ root = _project_root(project_root)
215
+ command = _gemini_command(root, scope)
216
+ ok = _configure("Gemini CLI", command, apply)
217
+ if not ok and apply:
218
+ raise typer.Exit(code=1)
219
+
220
+
221
+ @app.command("all")
222
+ def all_tools(
223
+ apply: bool = typer.Option(False, "--apply", help="Run setup commands instead of printing them."),
224
+ project_root: Path | None = typer.Option(None, "--project-root", help="Repository root containing .devcouncil/."),
225
+ gemini_scope: str = typer.Option("project", "--gemini-scope", help="Gemini MCP config scope: project or user."),
226
+ ):
227
+ """
228
+ Set up DevCouncil MCP tools for every supported coding CLI found on PATH.
229
+ """
230
+ if gemini_scope not in {"project", "user"}:
231
+ console.print("[red]--gemini-scope must be 'project' or 'user'.[/red]")
232
+ raise typer.Exit(code=2)
233
+
234
+ root = _project_root(project_root)
235
+ results = [
236
+ _configure("Codex CLI", _codex_command(root), apply),
237
+ _configure("Gemini CLI", _gemini_command(root, gemini_scope), apply),
238
+ ]
239
+ if apply and not all(results):
240
+ raise typer.Exit(code=1)
241
+
242
+
243
+ @app.command("check")
244
+ def check(
245
+ project_root: Path | None = typer.Option(None, "--project-root", help="Repository root containing .devcouncil/."),
246
+ ):
247
+ """
248
+ Check whether DevCouncil is ready to integrate with coding CLIs.
249
+ """
250
+ root = _project_root(project_root)
251
+ table = Table(title="DevCouncil Integration Check")
252
+ table.add_column("Check", style="cyan")
253
+ table.add_column("Status", style="magenta")
254
+ table.add_column("Details")
255
+
256
+ failures = 0
257
+
258
+ def add(ok: bool, name: str, details: str):
259
+ nonlocal failures
260
+ table.add_row(name, "[green]OK[/green]" if ok else "[red]FAIL[/red]", details)
261
+ if not ok:
262
+ failures += 1
263
+
264
+ add((root / ".devcouncil").exists(), "Project state", str(root / ".devcouncil"))
265
+
266
+ devcouncil_path = shutil.which("devcouncil")
267
+ add(devcouncil_path is not None, "devcouncil CLI", devcouncil_path or "Install DevCouncil first.")
268
+
269
+ code, output = _run_capture(["devcouncil", "--help"])
270
+ add(code == 0, "devcouncil command", output.splitlines()[0] if output else "No output")
271
+
272
+ code, output = _run_capture(["codex", "--version"])
273
+ add(code == 0, "Codex CLI", output.splitlines()[0] if output else "Optional; install Codex to use this integration.")
274
+
275
+ code, output = _run_capture(["gemini", "--version"])
276
+ add(code == 0, "Gemini CLI", output.splitlines()[0] if output else "Optional; install Gemini CLI to use this integration.")
277
+
278
+ try:
279
+ from mcp import ClientSession, StdioServerParameters
280
+ from mcp.client.stdio import stdio_client
281
+
282
+ async def _list_tools() -> list[str]:
283
+ import os
284
+
285
+ env = os.environ.copy()
286
+ env["DEVCOUNCIL_PROJECT_ROOT"] = str(root)
287
+ params = StdioServerParameters(
288
+ command=sys.executable,
289
+ args=["-m", "devcouncil", "mcp-server"],
290
+ cwd=str(root),
291
+ env=env,
292
+ )
293
+ async with stdio_client(params) as (read, write):
294
+ async with ClientSession(read, write) as session:
295
+ await session.initialize()
296
+ tools = await session.list_tools()
297
+ return [tool.name for tool in tools.tools]
298
+
299
+ import asyncio
300
+
301
+ tools = asyncio.run(_list_tools())
302
+ expected = {"devcouncil_status", "devcouncil_report", "devcouncil_get_task"}
303
+ add(expected.issubset(set(tools)), "MCP server", ", ".join(tools))
304
+ except Exception as exc:
305
+ add(False, "MCP server", str(exc))
306
+
307
+ console.print(table)
308
+ if failures:
309
+ console.print("\n[yellow]Fix failed checks, then run:[/yellow] dev integrate all --apply")
310
+ raise typer.Exit(code=1)
311
+
312
+ console.print("\n[green]Ready.[/green] Run: dev integrate all --apply")
313
+
314
+
315
+ @setup_app.command("agent-flow")
316
+ def setup_agent_flow(
317
+ apply: bool = typer.Option(False, "--apply", help="Write DevCouncil config instead of previewing."),
318
+ project_root: Path | None = typer.Option(None, "--project-root", help="Repository root containing .devcouncil/."),
319
+ ):
320
+ """Configure DevCouncil trace output for Agent Flow-style JSONL replay."""
321
+ root = _project_root(project_root)
322
+ trace_path = root / ".devcouncil" / "logs" / "traces.jsonl"
323
+ console.print("[bold]Agent Flow setup[/bold]")
324
+ console.print(f"Trace JSONL: {trace_path}")
325
+ console.print("Replay/tail locally with: dev trace tail --follow")
326
+ console.print("External visualizers can watch the trace JSONL path above.")
327
+
328
+ if not apply:
329
+ console.print("[yellow]Preview only. Rerun with --apply to record this integration in config.[/yellow]")
330
+ return
331
+
332
+ config = _load_raw_config(root)
333
+ integrations = config.setdefault("integrations", {})
334
+ integrations["agent_flow"] = {
335
+ "enabled": True,
336
+ "trace_path": str(trace_path),
337
+ "mode": "jsonl",
338
+ }
339
+ _save_raw_config(root, config)
340
+ docs_dir = root / ".devcouncil" / "integrations"
341
+ docs_dir.mkdir(parents=True, exist_ok=True)
342
+ (docs_dir / "agent-flow.md").write_text(
343
+ "\n".join([
344
+ "# Agent Flow",
345
+ "",
346
+ f"DevCouncil writes trace events to `{trace_path}`.",
347
+ "",
348
+ "Local replay:",
349
+ "",
350
+ "```bash",
351
+ "dev trace tail --follow",
352
+ "```",
353
+ "",
354
+ "External visualizers can watch the JSONL file directly. DevCouncil does not modify global editor or Claude Code settings from this setup command.",
355
+ "",
356
+ ]),
357
+ encoding="utf-8",
358
+ )
359
+ console.print("[green]Agent Flow trace integration recorded in .devcouncil/config.yaml.[/green]")
360
+
361
+
362
+ @setup_app.command("code-review-graph")
363
+ def setup_code_review_graph(
364
+ apply: bool = typer.Option(False, "--apply", help="Write DevCouncil config and ignore file."),
365
+ project_root: Path | None = typer.Option(None, "--project-root", help="Repository root containing .devcouncil/."),
366
+ ):
367
+ """Configure optional code-review-graph context enrichment."""
368
+ root = _project_root(project_root)
369
+ executable = shutil.which("code-review-graph")
370
+ ignore_path = root / ".code-review-graphignore"
371
+ console.print("[bold]code-review-graph setup[/bold]")
372
+ console.print(f"Binary: {executable or 'not found on PATH'}")
373
+ console.print("Install separately with: pipx install code-review-graph")
374
+ console.print("Build graph separately with: code-review-graph build")
375
+
376
+ if not apply:
377
+ console.print("[yellow]Preview only. Rerun with --apply to record this integration.[/yellow]")
378
+ return
379
+
380
+ if not ignore_path.exists():
381
+ ignore_path.write_text(
382
+ "\n".join([
383
+ ".devcouncil/**",
384
+ ".git/**",
385
+ ".venv/**",
386
+ "dist/**",
387
+ "node_modules/**",
388
+ "",
389
+ ]),
390
+ encoding="utf-8",
391
+ )
392
+ console.print(f"[green]Created {ignore_path}.[/green]")
393
+
394
+ config = _load_raw_config(root)
395
+ integrations = config.setdefault("integrations", {})
396
+ integrations["code_review_graph"] = {
397
+ "enabled": True,
398
+ "command": "code-review-graph",
399
+ "optional": True,
400
+ }
401
+ _save_raw_config(root, config)
402
+ docs_dir = root / ".devcouncil" / "integrations"
403
+ docs_dir.mkdir(parents=True, exist_ok=True)
404
+ (docs_dir / "code-review-graph.md").write_text(
405
+ "\n".join([
406
+ "# code-review-graph",
407
+ "",
408
+ "Install and build the graph outside DevCouncil:",
409
+ "",
410
+ "```bash",
411
+ "pipx install code-review-graph",
412
+ "code-review-graph build",
413
+ "```",
414
+ "",
415
+ "DevCouncil uses this as an optional context adapter for mapping, prompts, verification traces, and MCP graph context.",
416
+ "",
417
+ ]),
418
+ encoding="utf-8",
419
+ )
420
+ console.print("[green]code-review-graph adapter recorded in .devcouncil/config.yaml.[/green]")
@@ -0,0 +1,38 @@
1
+ import json
2
+ from pathlib import Path
3
+
4
+ import typer
5
+ from rich.console import Console
6
+
7
+ from devcouncil.indexing.repo_mapper import RepoMapper
8
+ from devcouncil.integrations.code_review_graph import CodeReviewGraphAdapter
9
+ from devcouncil.storage.db import get_db
10
+
11
+ console = Console()
12
+ status_console = Console(stderr=True)
13
+
14
+
15
+ def map_repo(
16
+ goal: str = typer.Argument("", help="Goal text used for candidate-file ranking."),
17
+ output: Path = typer.Option(
18
+ Path(".devcouncil/repo_map.json"),
19
+ "--output",
20
+ "-o",
21
+ help="Path to write repo_map.json.",
22
+ ),
23
+ ):
24
+ """Build the deterministic repository map without calling an LLM."""
25
+ if not get_db():
26
+ console.print("[red]DevCouncil not initialized. Run 'dev init' first.[/red]")
27
+ raise typer.Exit(code=1)
28
+
29
+ repo_map = RepoMapper(Path(".")).map_repo(goal)
30
+ graph_context = CodeReviewGraphAdapter(Path(".")).get_context()
31
+ output.parent.mkdir(parents=True, exist_ok=True)
32
+ output.write_text(repo_map.model_dump_json(indent=2), encoding="utf-8")
33
+ if graph_context.available:
34
+ graph_output = output.with_name("code_review_graph_context.json")
35
+ graph_output.write_text(graph_context.model_dump_json(indent=2), encoding="utf-8")
36
+ status_console.print(f"[green]Wrote code-review-graph context to {graph_output}[/green]")
37
+ typer.echo(json.dumps(repo_map.model_dump(), indent=2))
38
+ status_console.print(f"[green]Wrote repository map to {output}[/green]")
@@ -0,0 +1,18 @@
1
+ import asyncio
2
+
3
+ import typer
4
+
5
+ from devcouncil.integrations.mcp.server import run
6
+
7
+ app = typer.Typer()
8
+
9
+
10
+ @app.callback(invoke_without_command=True)
11
+ def mcp_server(ctx: typer.Context):
12
+ """
13
+ Start the DevCouncil MCP server over stdio.
14
+ """
15
+ if ctx.invoked_subcommand is not None:
16
+ return
17
+
18
+ asyncio.run(run())