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.
Files changed (146) hide show
  1. package/README.md +218 -96
  2. package/bin/devcouncil.js +130 -32
  3. package/package.json +7 -11
  4. package/pyproject.toml +0 -34
  5. package/src/devcouncil/__init__.py +0 -0
  6. package/src/devcouncil/__main__.py +0 -4
  7. package/src/devcouncil/app/__init__.py +0 -28
  8. package/src/devcouncil/app/config.py +0 -158
  9. package/src/devcouncil/app/errors.py +0 -23
  10. package/src/devcouncil/app/events.py +0 -44
  11. package/src/devcouncil/app/orchestrator.py +0 -92
  12. package/src/devcouncil/app/project_status.py +0 -29
  13. package/src/devcouncil/app/run_context.py +0 -39
  14. package/src/devcouncil/app/state_machine.py +0 -108
  15. package/src/devcouncil/artifacts/__init__.py +0 -1
  16. package/src/devcouncil/artifacts/coverage.py +0 -96
  17. package/src/devcouncil/artifacts/graph.py +0 -143
  18. package/src/devcouncil/artifacts/migrations.py +0 -20
  19. package/src/devcouncil/artifacts/schemas.py +0 -23
  20. package/src/devcouncil/artifacts/serializer.py +0 -21
  21. package/src/devcouncil/artifacts/validators.py +0 -27
  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 +0 -51
  25. package/src/devcouncil/cli/commands/ast.py +0 -22
  26. package/src/devcouncil/cli/commands/baseline.py +0 -35
  27. package/src/devcouncil/cli/commands/config.py +0 -76
  28. package/src/devcouncil/cli/commands/dashboard.py +0 -26
  29. package/src/devcouncil/cli/commands/doctor.py +0 -140
  30. package/src/devcouncil/cli/commands/go.py +0 -237
  31. package/src/devcouncil/cli/commands/hook.py +0 -128
  32. package/src/devcouncil/cli/commands/init.py +0 -153
  33. package/src/devcouncil/cli/commands/integrate.py +0 -726
  34. package/src/devcouncil/cli/commands/lsp.py +0 -20
  35. package/src/devcouncil/cli/commands/map.py +0 -42
  36. package/src/devcouncil/cli/commands/mcp_server.py +0 -18
  37. package/src/devcouncil/cli/commands/plan.py +0 -327
  38. package/src/devcouncil/cli/commands/prompt.py +0 -50
  39. package/src/devcouncil/cli/commands/repair.py +0 -72
  40. package/src/devcouncil/cli/commands/report.py +0 -129
  41. package/src/devcouncil/cli/commands/reset_demo_state.py +0 -31
  42. package/src/devcouncil/cli/commands/rollback.py +0 -60
  43. package/src/devcouncil/cli/commands/run.py +0 -269
  44. package/src/devcouncil/cli/commands/setup.py +0 -223
  45. package/src/devcouncil/cli/commands/show.py +0 -76
  46. package/src/devcouncil/cli/commands/status.py +0 -117
  47. package/src/devcouncil/cli/commands/tasks.py +0 -55
  48. package/src/devcouncil/cli/commands/trace.py +0 -44
  49. package/src/devcouncil/cli/commands/verify.py +0 -193
  50. package/src/devcouncil/cli/commands/version.py +0 -20
  51. package/src/devcouncil/cli/commands/watch.py +0 -574
  52. package/src/devcouncil/cli/main.py +0 -88
  53. package/src/devcouncil/council/__init__.py +0 -0
  54. package/src/devcouncil/council/prompts/__init__.py +0 -0
  55. package/src/devcouncil/council/prompts/arbiter.md +0 -19
  56. package/src/devcouncil/council/prompts/critic_a.md +0 -10
  57. package/src/devcouncil/council/prompts/critic_b.md +0 -10
  58. package/src/devcouncil/council/prompts/implementation_reviewer.md +0 -16
  59. package/src/devcouncil/council/prompts/planner_a.md +0 -16
  60. package/src/devcouncil/council/prompts/planner_b.md +0 -16
  61. package/src/devcouncil/council/prompts/rebuttal.md +0 -10
  62. package/src/devcouncil/council/prompts/spec_writer.md +0 -12
  63. package/src/devcouncil/domain/__init__.py +0 -0
  64. package/src/devcouncil/domain/assumption.py +0 -17
  65. package/src/devcouncil/domain/critique.py +0 -32
  66. package/src/devcouncil/domain/evidence.py +0 -27
  67. package/src/devcouncil/domain/gap.py +0 -26
  68. package/src/devcouncil/domain/requirement.py +0 -22
  69. package/src/devcouncil/domain/task.py +0 -26
  70. package/src/devcouncil/execution/__init__.py +0 -1
  71. package/src/devcouncil/execution/context_builder.py +0 -60
  72. package/src/devcouncil/execution/executor.py +0 -15
  73. package/src/devcouncil/execution/hook_policy.py +0 -165
  74. package/src/devcouncil/execution/patch.py +0 -28
  75. package/src/devcouncil/execution/paths.py +0 -14
  76. package/src/devcouncil/execution/permissions.py +0 -92
  77. package/src/devcouncil/execution/prompt_builder.py +0 -59
  78. package/src/devcouncil/execution/task_runner.py +0 -166
  79. package/src/devcouncil/executors/__init__.py +0 -1
  80. package/src/devcouncil/executors/coding_cli.py +0 -112
  81. package/src/devcouncil/executors/mini_swe.py +0 -73
  82. package/src/devcouncil/executors/native/__init__.py +0 -0
  83. package/src/devcouncil/executors/native/agent.py +0 -107
  84. package/src/devcouncil/executors/openhands.py +0 -71
  85. package/src/devcouncil/gating/__init__.py +0 -1
  86. package/src/devcouncil/gating/checks/__init__.py +0 -0
  87. package/src/devcouncil/gating/checks/clean_git.py +0 -50
  88. package/src/devcouncil/gating/checks/planned_files_check.py +0 -32
  89. package/src/devcouncil/gating/checks/requirement_coverage.py +0 -26
  90. package/src/devcouncil/gating/checks/secret_scan_check.py +0 -34
  91. package/src/devcouncil/gating/policy.py +0 -190
  92. package/src/devcouncil/indexing/__init__.py +0 -1
  93. package/src/devcouncil/indexing/ast_matcher.py +0 -168
  94. package/src/devcouncil/indexing/graph_index.py +0 -48
  95. package/src/devcouncil/indexing/lsp.py +0 -120
  96. package/src/devcouncil/indexing/repo_mapper.py +0 -208
  97. package/src/devcouncil/indexing/symbol_index.py +0 -0
  98. package/src/devcouncil/integrations/code_review_graph.py +0 -163
  99. package/src/devcouncil/integrations/github.py +0 -39
  100. package/src/devcouncil/integrations/gitnexus.py +0 -27
  101. package/src/devcouncil/integrations/graphify.py +0 -34
  102. package/src/devcouncil/integrations/mcp/__init__.py +0 -0
  103. package/src/devcouncil/integrations/mcp/server.py +0 -599
  104. package/src/devcouncil/integrations/pr_comments.py +0 -62
  105. package/src/devcouncil/live/__init__.py +0 -2
  106. package/src/devcouncil/live/cards.py +0 -207
  107. package/src/devcouncil/live/models.py +0 -63
  108. package/src/devcouncil/live/repair_prompt.py +0 -83
  109. package/src/devcouncil/live/reviewer.py +0 -70
  110. package/src/devcouncil/live/signals.py +0 -135
  111. package/src/devcouncil/live/summary.py +0 -34
  112. package/src/devcouncil/live/tasks.py +0 -18
  113. package/src/devcouncil/live/transcripts.py +0 -138
  114. package/src/devcouncil/llm/__init__.py +0 -1
  115. package/src/devcouncil/llm/cache.py +0 -38
  116. package/src/devcouncil/llm/provider.py +0 -146
  117. package/src/devcouncil/llm/router.py +0 -125
  118. package/src/devcouncil/planning/__init__.py +0 -1
  119. package/src/devcouncil/planning/arbiter_service.py +0 -57
  120. package/src/devcouncil/planning/critique_service.py +0 -66
  121. package/src/devcouncil/planning/plan_service.py +0 -46
  122. package/src/devcouncil/planning/prompt_enhancer_service.py +0 -86
  123. package/src/devcouncil/planning/repair_service.py +0 -39
  124. package/src/devcouncil/planning/spec_service.py +0 -44
  125. package/src/devcouncil/repo/__init__.py +0 -0
  126. package/src/devcouncil/reporting/__init__.py +0 -0
  127. package/src/devcouncil/reporting/github_check.py +0 -32
  128. package/src/devcouncil/reporting/json_report.py +0 -20
  129. package/src/devcouncil/reporting/markdown_report.py +0 -68
  130. package/src/devcouncil/reporting/report_builder.py +0 -14
  131. package/src/devcouncil/storage/__init__.py +0 -0
  132. package/src/devcouncil/storage/db.py +0 -66
  133. package/src/devcouncil/storage/models.py +0 -83
  134. package/src/devcouncil/storage/repositories.py +0 -423
  135. package/src/devcouncil/telemetry/__init__.py +0 -0
  136. package/src/devcouncil/telemetry/cost.py +0 -34
  137. package/src/devcouncil/telemetry/traces.py +0 -91
  138. package/src/devcouncil/telemetry/tracker.py +0 -49
  139. package/src/devcouncil/ui/__init__.py +0 -1
  140. package/src/devcouncil/ui/dashboard.py +0 -122
  141. package/src/devcouncil/utils/__init__.py +0 -1
  142. package/src/devcouncil/utils/redaction.py +0 -141
  143. package/src/devcouncil/verification/__init__.py +0 -1
  144. package/src/devcouncil/verification/implementation_reviewer.py +0 -55
  145. package/src/devcouncil/verification/verifier.py +0 -530
  146. package/uv.lock +0 -1085
@@ -1,237 +0,0 @@
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)
@@ -1,128 +0,0 @@
1
- import typer
2
- import json
3
- import os
4
- import sys
5
- from pathlib import Path
6
- from rich.console import Console
7
- from devcouncil.storage.db import get_db
8
- from devcouncil.storage.repositories import TaskRepository
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
13
-
14
- app = typer.Typer()
15
- console = Console()
16
-
17
-
18
- def _project_root(project_root: Path | None = None) -> Path:
19
- if project_root:
20
- return project_root.expanduser().resolve()
21
- configured = os.environ.get("DEVCOUNCIL_PROJECT_ROOT")
22
- return Path(configured).expanduser().resolve() if configured else Path(".").resolve()
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
-
51
- @app.command()
52
- def pre_tool_use(
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/."),
56
- ):
57
- """
58
- Coding CLI hook: Inspects a tool call before execution.
59
- Exits with code 2 to block unauthorized file writes.
60
- """
61
- try:
62
- if tool_call_json is None:
63
- tool_call_json = sys.stdin.read()
64
- if not tool_call_json.strip():
65
- raise typer.Exit(code=0)
66
- call_data = json.loads(tool_call_json)
67
- normalized_client = client.lower()
68
- root = _project_root(project_root)
69
- active_task = _active_task(root)
70
-
71
- decision = HookPolicy(project_root=root).evaluate(call_data, active_task)
72
- _emit_decision(normalized_client, decision.action, decision.reason)
73
-
74
- except json.JSONDecodeError:
75
- raise typer.Exit(code=0)
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,153 +0,0 @@
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
- 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
- "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
- },
52
- "privacy": {
53
- "redact_env_vars": True,
54
- "redact_secrets_in_logs": True,
55
- "store_prompts_locally": True
56
- },
57
- "integrations": {
58
- "agent_flow": {
59
- "enabled": False,
60
- "trace_path": ".devcouncil/logs/traces.jsonl",
61
- "mode": "jsonl",
62
- },
63
- "code_review_graph": {
64
- "enabled": False,
65
- "command": "code-review-graph",
66
- "optional": True,
67
- },
68
- "live_review": {
69
- "enabled": True,
70
- "cards_path": ".devcouncil/live/cards",
71
- "signals_path": ".devcouncil/live/signals",
72
- "default_client": "claude",
73
- },
74
- }
75
- }
76
-
77
-
78
- def initialize_project(
79
- project_root: Path = Path("."),
80
- project_name: str | None = None,
81
- with_gitnexus: bool = False,
82
- with_graphify: bool = False,
83
- quiet: bool = False,
84
- ) -> bool:
85
- """Initialize DevCouncil project state.
86
-
87
- Returns True when a fresh .devcouncil directory was created.
88
- """
89
- project_root = project_root.resolve()
90
- dev_dir = project_root / ".devcouncil"
91
- created = False
92
-
93
- if not dev_dir.exists():
94
- if not quiet:
95
- console.print("Initializing DevCouncil...")
96
- dev_dir.mkdir(exist_ok=True)
97
- (dev_dir / "runs").mkdir(exist_ok=True)
98
- (dev_dir / "cache").mkdir(exist_ok=True)
99
- (dev_dir / "checkpoints").mkdir(exist_ok=True)
100
- (dev_dir / "logs").mkdir(exist_ok=True)
101
-
102
- config_path = dev_dir / "config.yaml"
103
- config = copy.deepcopy(DEFAULT_CONFIG)
104
- if project_name:
105
- config["project"]["name"] = project_name
106
- else:
107
- config["project"]["name"] = project_root.name
108
-
109
- with open(config_path, "w") as f:
110
- yaml.dump(config, f, default_flow_style=False)
111
-
112
- db = Database(dev_dir / "state.sqlite")
113
- db.create_db_and_tables()
114
- if not quiet:
115
- console.print(f"[green]Successfully initialized DevCouncil in {dev_dir}[/green]")
116
- created = True
117
-
118
- if with_gitnexus:
119
- nexus = GitNexusIntegration(project_root)
120
- nexus.initialize()
121
-
122
- if with_graphify:
123
- graphify = GraphifyIntegration(project_root)
124
- graphify.initialize()
125
-
126
- return created
127
-
128
-
129
- @app.callback(invoke_without_command=True)
130
- def init(
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
- """
137
- Initialize DevCouncil in the current directory.
138
- """
139
- if ctx.invoked_subcommand is not None:
140
- return
141
-
142
- dev_dir = Path(".devcouncil")
143
- if dev_dir.exists() and not (with_gitnexus or with_graphify):
144
- console.print("[yellow]DevCouncil is already initialized in this directory.[/yellow]")
145
- console.print("Use --gitnexus or --graphify to add upgrade paths.")
146
- raise typer.Exit()
147
-
148
- initialize_project(
149
- Path("."),
150
- project_name=project_name,
151
- with_gitnexus=with_gitnexus,
152
- with_graphify=with_graphify,
153
- )