devcouncil 0.2.0 → 0.2.2

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 (205) hide show
  1. package/README.md +144 -308
  2. package/bin/devcouncil.js +130 -32
  3. package/package.json +22 -19
  4. package/pyproject.toml +0 -66
  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 -320
  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 -163
  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/assets/__init__.py +0 -1
  23. package/src/devcouncil/assets/devcouncil-logo.svg +0 -60
  24. package/src/devcouncil/assets/devcouncil_logo_premium.png +0 -0
  25. package/src/devcouncil/cli/__init__.py +0 -0
  26. package/src/devcouncil/cli/commands/__init__.py +0 -0
  27. package/src/devcouncil/cli/commands/agents.py +0 -292
  28. package/src/devcouncil/cli/commands/artifacts.py +0 -54
  29. package/src/devcouncil/cli/commands/ast.py +0 -22
  30. package/src/devcouncil/cli/commands/baseline.py +0 -35
  31. package/src/devcouncil/cli/commands/check.py +0 -209
  32. package/src/devcouncil/cli/commands/config.py +0 -115
  33. package/src/devcouncil/cli/commands/cost.py +0 -57
  34. package/src/devcouncil/cli/commands/dashboard.py +0 -31
  35. package/src/devcouncil/cli/commands/doctor.py +0 -340
  36. package/src/devcouncil/cli/commands/evidence.py +0 -48
  37. package/src/devcouncil/cli/commands/go.py +0 -656
  38. package/src/devcouncil/cli/commands/handoff.py +0 -69
  39. package/src/devcouncil/cli/commands/hook.py +0 -237
  40. package/src/devcouncil/cli/commands/init.py +0 -289
  41. package/src/devcouncil/cli/commands/integrate.py +0 -1515
  42. package/src/devcouncil/cli/commands/lsp.py +0 -20
  43. package/src/devcouncil/cli/commands/map.py +0 -112
  44. package/src/devcouncil/cli/commands/mcp_server.py +0 -18
  45. package/src/devcouncil/cli/commands/plan.py +0 -488
  46. package/src/devcouncil/cli/commands/prompt.py +0 -61
  47. package/src/devcouncil/cli/commands/repair.py +0 -89
  48. package/src/devcouncil/cli/commands/report.py +0 -137
  49. package/src/devcouncil/cli/commands/reset_demo_state.py +0 -33
  50. package/src/devcouncil/cli/commands/rollback.py +0 -59
  51. package/src/devcouncil/cli/commands/run.py +0 -289
  52. package/src/devcouncil/cli/commands/runs.py +0 -223
  53. package/src/devcouncil/cli/commands/scaffold.py +0 -32
  54. package/src/devcouncil/cli/commands/semantic.py +0 -47
  55. package/src/devcouncil/cli/commands/setup.py +0 -362
  56. package/src/devcouncil/cli/commands/shell.py +0 -73
  57. package/src/devcouncil/cli/commands/show.py +0 -76
  58. package/src/devcouncil/cli/commands/skills.py +0 -88
  59. package/src/devcouncil/cli/commands/status.py +0 -141
  60. package/src/devcouncil/cli/commands/tasks.py +0 -55
  61. package/src/devcouncil/cli/commands/trace.py +0 -88
  62. package/src/devcouncil/cli/commands/verify.py +0 -328
  63. package/src/devcouncil/cli/commands/version.py +0 -20
  64. package/src/devcouncil/cli/commands/watch.py +0 -574
  65. package/src/devcouncil/cli/commands/watch_fs.py +0 -40
  66. package/src/devcouncil/cli/main.py +0 -137
  67. package/src/devcouncil/council/__init__.py +0 -0
  68. package/src/devcouncil/council/prompts/__init__.py +0 -0
  69. package/src/devcouncil/council/prompts/arbiter.md +0 -19
  70. package/src/devcouncil/council/prompts/critic_a.md +0 -10
  71. package/src/devcouncil/council/prompts/critic_b.md +0 -10
  72. package/src/devcouncil/council/prompts/implementation_reviewer.md +0 -16
  73. package/src/devcouncil/council/prompts/planner_a.md +0 -16
  74. package/src/devcouncil/council/prompts/planner_b.md +0 -16
  75. package/src/devcouncil/council/prompts/rebuttal.md +0 -10
  76. package/src/devcouncil/council/prompts/spec_writer.md +0 -12
  77. package/src/devcouncil/domain/__init__.py +0 -0
  78. package/src/devcouncil/domain/assumption.py +0 -17
  79. package/src/devcouncil/domain/critique.py +0 -32
  80. package/src/devcouncil/domain/evidence.py +0 -47
  81. package/src/devcouncil/domain/gap.py +0 -52
  82. package/src/devcouncil/domain/requirement.py +0 -22
  83. package/src/devcouncil/domain/task.py +0 -55
  84. package/src/devcouncil/execution/__init__.py +0 -1
  85. package/src/devcouncil/execution/checkpoints.py +0 -246
  86. package/src/devcouncil/execution/context_builder.py +0 -60
  87. package/src/devcouncil/execution/executor.py +0 -15
  88. package/src/devcouncil/execution/fs_watcher.py +0 -180
  89. package/src/devcouncil/execution/handoff.py +0 -102
  90. package/src/devcouncil/execution/hook_policy.py +0 -253
  91. package/src/devcouncil/execution/patch.py +0 -77
  92. package/src/devcouncil/execution/paths.py +0 -14
  93. package/src/devcouncil/execution/permissions.py +0 -85
  94. package/src/devcouncil/execution/policy_engine.py +0 -343
  95. package/src/devcouncil/execution/prompt_builder.py +0 -671
  96. package/src/devcouncil/execution/shell_session.py +0 -225
  97. package/src/devcouncil/execution/task_runner.py +0 -170
  98. package/src/devcouncil/executors/__init__.py +0 -1
  99. package/src/devcouncil/executors/agent_registry.py +0 -575
  100. package/src/devcouncil/executors/coding_cli.py +0 -736
  101. package/src/devcouncil/executors/mini_swe.py +0 -73
  102. package/src/devcouncil/executors/native/__init__.py +0 -0
  103. package/src/devcouncil/executors/native/agent.py +0 -208
  104. package/src/devcouncil/executors/openhands.py +0 -71
  105. package/src/devcouncil/gating/__init__.py +0 -1
  106. package/src/devcouncil/gating/checks/__init__.py +0 -0
  107. package/src/devcouncil/gating/checks/clean_git.py +0 -52
  108. package/src/devcouncil/gating/checks/planned_files_check.py +0 -32
  109. package/src/devcouncil/gating/checks/requirement_coverage.py +0 -26
  110. package/src/devcouncil/gating/checks/secret_scan_check.py +0 -53
  111. package/src/devcouncil/gating/policy.py +0 -338
  112. package/src/devcouncil/hardware.py +0 -184
  113. package/src/devcouncil/indexing/__init__.py +0 -1
  114. package/src/devcouncil/indexing/ast_matcher.py +0 -168
  115. package/src/devcouncil/indexing/graph_index.py +0 -48
  116. package/src/devcouncil/indexing/lsp.py +0 -161
  117. package/src/devcouncil/indexing/repo_mapper.py +0 -1455
  118. package/src/devcouncil/indexing/semantic_index.py +0 -205
  119. package/src/devcouncil/integrations/actions.py +0 -146
  120. package/src/devcouncil/integrations/check.py +0 -423
  121. package/src/devcouncil/integrations/code_review_graph.py +0 -163
  122. package/src/devcouncil/integrations/github.py +0 -39
  123. package/src/devcouncil/integrations/github_intent.py +0 -142
  124. package/src/devcouncil/integrations/gitnexus.py +0 -62
  125. package/src/devcouncil/integrations/graphify.py +0 -34
  126. package/src/devcouncil/integrations/mcp/__init__.py +0 -0
  127. package/src/devcouncil/integrations/mcp/server.py +0 -2122
  128. package/src/devcouncil/integrations/opencode_devcouncil_plugin.mjs +0 -24
  129. package/src/devcouncil/integrations/pr_comments.py +0 -62
  130. package/src/devcouncil/live/__init__.py +0 -2
  131. package/src/devcouncil/live/cards.py +0 -349
  132. package/src/devcouncil/live/models.py +0 -63
  133. package/src/devcouncil/live/repair_prompt.py +0 -83
  134. package/src/devcouncil/live/reviewer.py +0 -70
  135. package/src/devcouncil/live/signals.py +0 -135
  136. package/src/devcouncil/live/summary.py +0 -34
  137. package/src/devcouncil/live/tasks.py +0 -18
  138. package/src/devcouncil/live/transcripts.py +0 -141
  139. package/src/devcouncil/llm/__init__.py +0 -1
  140. package/src/devcouncil/llm/cache.py +0 -42
  141. package/src/devcouncil/llm/model_defaults.yaml +0 -44
  142. package/src/devcouncil/llm/provider.py +0 -627
  143. package/src/devcouncil/llm/router.py +0 -310
  144. package/src/devcouncil/optimization/__init__.py +0 -1
  145. package/src/devcouncil/optimization/gepa_agent.py +0 -318
  146. package/src/devcouncil/planning/__init__.py +0 -1
  147. package/src/devcouncil/planning/arbiter_service.py +0 -57
  148. package/src/devcouncil/planning/correction_manifest.py +0 -303
  149. package/src/devcouncil/planning/critique_service.py +0 -71
  150. package/src/devcouncil/planning/plan_service.py +0 -60
  151. package/src/devcouncil/planning/prompt_enhancer_service.py +0 -167
  152. package/src/devcouncil/planning/repair_service.py +0 -39
  153. package/src/devcouncil/planning/spec_service.py +0 -70
  154. package/src/devcouncil/repo/__init__.py +0 -0
  155. package/src/devcouncil/repo/ci_scaffold.py +0 -157
  156. package/src/devcouncil/repo/gitignore.py +0 -123
  157. package/src/devcouncil/repo/sca.py +0 -374
  158. package/src/devcouncil/reporting/__init__.py +0 -0
  159. package/src/devcouncil/reporting/github_check.py +0 -32
  160. package/src/devcouncil/reporting/json_report.py +0 -30
  161. package/src/devcouncil/reporting/markdown_report.py +0 -83
  162. package/src/devcouncil/reporting/report_builder.py +0 -14
  163. package/src/devcouncil/skills/__init__.py +0 -19
  164. package/src/devcouncil/skills/library/README.md +0 -46
  165. package/src/devcouncil/skills/library/ai-training.md +0 -50
  166. package/src/devcouncil/skills/library/android.md +0 -50
  167. package/src/devcouncil/skills/library/backend.md +0 -52
  168. package/src/devcouncil/skills/library/core-engineering.md +0 -95
  169. package/src/devcouncil/skills/library/data-engineering.md +0 -47
  170. package/src/devcouncil/skills/library/desktop.md +0 -46
  171. package/src/devcouncil/skills/library/devops.md +0 -48
  172. package/src/devcouncil/skills/library/game-dev.md +0 -46
  173. package/src/devcouncil/skills/library/ios.md +0 -48
  174. package/src/devcouncil/skills/library/mobile-cross-platform.md +0 -46
  175. package/src/devcouncil/skills/library/security.md +0 -48
  176. package/src/devcouncil/skills/library/systems.md +0 -48
  177. package/src/devcouncil/skills/library/web.md +0 -47
  178. package/src/devcouncil/skills/library/windows.md +0 -47
  179. package/src/devcouncil/skills/registry.py +0 -330
  180. package/src/devcouncil/storage/__init__.py +0 -0
  181. package/src/devcouncil/storage/db.py +0 -147
  182. package/src/devcouncil/storage/models.py +0 -204
  183. package/src/devcouncil/storage/native.py +0 -557
  184. package/src/devcouncil/storage/repositories.py +0 -485
  185. package/src/devcouncil/telemetry/__init__.py +0 -0
  186. package/src/devcouncil/telemetry/cost.py +0 -140
  187. package/src/devcouncil/telemetry/model_pricing.yaml +0 -48
  188. package/src/devcouncil/telemetry/pricing.py +0 -28
  189. package/src/devcouncil/telemetry/traces.py +0 -146
  190. package/src/devcouncil/telemetry/tracker.py +0 -52
  191. package/src/devcouncil/ui/__init__.py +0 -1
  192. package/src/devcouncil/ui/dashboard.py +0 -423
  193. package/src/devcouncil/utils/__init__.py +0 -1
  194. package/src/devcouncil/utils/redaction.py +0 -147
  195. package/src/devcouncil/utils/subprocess_env.py +0 -69
  196. package/src/devcouncil/verification/__init__.py +0 -1
  197. package/src/devcouncil/verification/acceptance_compiler.py +0 -125
  198. package/src/devcouncil/verification/ad_hoc_check.py +0 -129
  199. package/src/devcouncil/verification/diff_coverage.py +0 -353
  200. package/src/devcouncil/verification/implementation_reviewer.py +0 -55
  201. package/src/devcouncil/verification/next_actions.py +0 -189
  202. package/src/devcouncil/verification/sandbox.py +0 -178
  203. package/src/devcouncil/verification/test_resolver.py +0 -91
  204. package/src/devcouncil/verification/verifier.py +0 -1548
  205. package/uv.lock +0 -1226
@@ -1,209 +0,0 @@
1
- """`dev check` — a one-shot audit of the current uncommitted changes.
2
-
3
- The lowest-friction entry point: no planning, no task graph. You let a coding
4
- agent change your repo, then `dev check` tells you what is out of scope, what
5
- edge cases look unhandled, what's risky, and whether any secrets leaked —
6
- grounded in the real diff. With ``--goal`` it also compiles acceptance checks
7
- from the stated intent and runs them as evidence.
8
- """
9
-
10
- from __future__ import annotations
11
-
12
- import asyncio
13
- import json
14
- import subprocess
15
- from pathlib import Path
16
-
17
- import typer
18
- from rich.console import Console
19
- from rich.table import Table
20
-
21
- from devcouncil.app.config import get_api_key, load_config
22
- from devcouncil.cli.commands.init import initialize_project
23
- from devcouncil.domain.task import Task
24
- from devcouncil.integrations.code_review_graph import CodeReviewGraphAdapter
25
- from devcouncil.integrations.github_intent import resolve_goal_intent
26
- from devcouncil.llm.provider import ProviderRequestError, create_provider, validate_model_provider
27
- from devcouncil.llm.router import ModelRouter, StructuredOutputError
28
- from devcouncil.verification.ad_hoc_check import AdHocCheckResult, run_working_tree_check
29
- from devcouncil.verification.implementation_reviewer import ImplementationReviewer
30
- from devcouncil.verification.verifier import Verifier
31
-
32
- console = Console()
33
-
34
-
35
- def _diff(root: Path, base: str | None) -> str:
36
- if not base:
37
- return Verifier(root).get_diff()
38
- try:
39
- return subprocess.check_output(
40
- ["git", "diff", base, "--"], cwd=root, text=True, encoding="utf-8", errors="replace"
41
- )
42
- except Exception:
43
- return ""
44
-
45
-
46
- def check(
47
- goal: str | None = typer.Option(None, "--goal", "-g", help="What the change was meant to do — sharpens the review and enables acceptance checks. Also accepts a GitHub issue/PR reference (#142, owner/repo#142, or a github.com URL)."),
48
- base: str | None = typer.Option(None, "--base", help="Diff against this git ref instead of the uncommitted working tree."),
49
- test_commands: list[str] | None = typer.Option(None, "--test", "-t", help="A verification command proving the change works (repeatable). Switches to the deterministic evidence gate."),
50
- verify: bool = typer.Option(False, "--verify", help="Run the deterministic evidence gate (orphan-diff, acceptance evidence, diff↔coverage, next actions) instead of the LLM audit. No provider keys needed."),
51
- enforce_coverage: bool = typer.Option(False, "--enforce-coverage", help="Evidence gate: block when the tests do not exercise the changed lines."),
52
- min_coverage: float = typer.Option(0.0, "--min-coverage", help="Evidence gate: minimum fraction of changed lines that must be exercised (implies --enforce-coverage)."),
53
- json_format: bool = typer.Option(False, "--json", help="Machine-readable output."),
54
- project_root: Path = typer.Option(Path("."), "--project-root", help="Repository root."),
55
- ):
56
- """Audit the current changes — scope, risks, missing edge cases, secrets — no planning required."""
57
- root = project_root.expanduser().resolve()
58
- initialize_project(root, quiet=True)
59
-
60
- # A --goal of "#142" or a GitHub issue/PR URL is a reference, not a spec —
61
- # expand it into the issue/PR title + body so acceptance checks and the review
62
- # are grounded in the real intent (same behavior as `dev go`).
63
- if goal:
64
- expanded_goal, intent_note = resolve_goal_intent(goal, root)
65
- if intent_note and not json_format:
66
- console.print(f"[dim]{intent_note}[/dim]")
67
- goal = expanded_goal
68
-
69
- # Evidence-gate mode: deterministic verification of the working-tree diff against an
70
- # inline requirement (--goal), with the diff↔coverage gate and the typed next-actions
71
- # contract. Provider-key-free — this is the lite path that lets you taste the gate.
72
- if verify or test_commands:
73
- result = run_working_tree_check(
74
- root,
75
- goal,
76
- test_commands=list(test_commands or []),
77
- enforce_coverage=enforce_coverage,
78
- min_ratio=min_coverage,
79
- )
80
- if json_format:
81
- typer.echo(json.dumps(result.to_dict(), indent=2))
82
- else:
83
- _render_gate(result)
84
- raise typer.Exit(code=0 if result.passed else 1)
85
-
86
- verifier = Verifier(root)
87
-
88
- diff = _diff(root, base)
89
- if not diff.strip():
90
- msg = "No changes to check (clean working tree)."
91
- typer.echo(json.dumps({"ok": True, "message": msg}) if json_format else msg)
92
- return
93
-
94
- changed_files = verifier.get_changed_files()
95
- secret_gaps = verifier.secret_scanner.scan_diff(diff, "check")
96
-
97
- # Blast radius: what do the changed files ripple into? Surfaced from the
98
- # structural graph when the code-review-graph integration is enabled, so a
99
- # reviewer sees the impact (and which tests to run) before approving.
100
- graph_context = CodeReviewGraphAdapter(root).get_context(changed_files)
101
-
102
- findings = []
103
- review_note = None
104
- try:
105
- config = load_config(root)
106
- validate_model_provider(config.models.provider)
107
- api_key = get_api_key(config.models.provider, root)
108
- provider = create_provider(config.models.provider, api_key, project_root=root)
109
- role_config = {name: role.model_dump() for name, role in config.models.roles.items()}
110
- router = ModelRouter(provider, role_config, project_root=root)
111
- synthetic = Task(
112
- id="CHECK",
113
- title="Ad-hoc change review",
114
- description=(
115
- goal
116
- or "Review these changes for correctness, missing edge cases, error handling, "
117
- "risky shortcuts, and scope creep."
118
- ),
119
- )
120
- review = asyncio.run(ImplementationReviewer(router).review_changes(synthetic, [], diff))
121
- findings = review.findings
122
- except (ProviderRequestError, StructuredOutputError) as exc:
123
- review_note = f"LLM review unavailable: {exc}"
124
- except Exception as exc: # pragma: no cover - best effort
125
- review_note = f"LLM review unavailable: {exc}"
126
-
127
- if json_format:
128
- typer.echo(json.dumps({
129
- "ok": not secret_gaps,
130
- "changed_files": changed_files,
131
- "secret_findings": [g.model_dump() for g in secret_gaps],
132
- "review_findings": [g.model_dump() for g in findings],
133
- "review_note": review_note,
134
- "blast_radius": {
135
- "available": graph_context.available,
136
- "impacted_files": graph_context.impacted_files,
137
- "related_tests": graph_context.related_tests,
138
- },
139
- }, indent=2))
140
- return
141
-
142
- console.print(f"[bold]Changed files ({len(changed_files)}):[/bold] " + ", ".join(changed_files[:20]) or "(none)")
143
- if secret_gaps:
144
- console.print(f"\n[red bold]⚠ Possible secrets in the diff ({len(secret_gaps)}):[/red bold]")
145
- for g in secret_gaps[:10]:
146
- console.print(f" - {g.description[:100]}")
147
- if findings:
148
- console.print(f"\n[bold]Review findings ({len(findings)}):[/bold]")
149
- for f in findings[:15]:
150
- sev = getattr(f, "severity", "info")
151
- colour = {"critical": "red", "high": "red", "medium": "yellow"}.get(sev, "white")
152
- console.print(f" - [{colour}]{sev}[/{colour}]: {f.description[:140]}")
153
- if graph_context.available and (graph_context.impacted_files or graph_context.related_tests):
154
- console.print("\n[bold]Blast radius[/bold] [dim](from the structural graph)[/dim]:")
155
- if graph_context.impacted_files:
156
- console.print(f" [cyan]Impacted files ({len(graph_context.impacted_files)}):[/cyan] "
157
- + ", ".join(graph_context.impacted_files[:15]))
158
- if graph_context.related_tests:
159
- console.print(f" [cyan]Related tests ({len(graph_context.related_tests)}):[/cyan] "
160
- + ", ".join(graph_context.related_tests[:15]))
161
- if not secret_gaps and not findings:
162
- console.print("\n[green]No secrets or review concerns found in the diff.[/green]")
163
- if review_note:
164
- console.print(f"\n[dim]{review_note}[/dim]")
165
- console.print(
166
- "\n[dim]Tip: `dev check --goal \"what this change should do\"` sharpens the review.[/dim]"
167
- )
168
- console.print(
169
- "[dim]Tip: `dev check --verify --test \"<cmd>\"` runs the deterministic evidence gate "
170
- "(no provider keys).[/dim]"
171
- )
172
-
173
-
174
- def _render_gate(result: AdHocCheckResult) -> None:
175
- """Render the deterministic evidence-gate result for humans."""
176
- if result.reason == "no_changes":
177
- console.print("[yellow]No working-tree changes to verify. Make a change first, then re-run.[/yellow]")
178
- return
179
-
180
- console.print(f"[bold]Checking:[/bold] {result.requirement}")
181
- console.print(f"[dim]{len(result.changed_files)} changed file(s) in scope.[/dim]")
182
- if result.diff_coverage and result.diff_coverage.measured:
183
- console.print(f"[dim]Diff coverage: {result.diff_coverage.summary}[/dim]")
184
-
185
- if not result.gaps:
186
- console.print("\n[green]Verified: the change is backed by passing evidence.[/green]")
187
- return
188
-
189
- table = Table(title="Findings")
190
- table.add_column("Type", style="cyan")
191
- table.add_column("Severity", style="magenta")
192
- table.add_column("Finding", style="white")
193
- table.add_column("Blocking", style="red")
194
- for gap in result.gaps[:20]:
195
- table.add_row(gap.gap_type, gap.severity, gap.description, "YES" if gap.blocking else "no")
196
- console.print(table)
197
-
198
- if result.next_actions:
199
- console.print("\n[bold]Next actions:[/bold]")
200
- for action in result.next_actions:
201
- location = ""
202
- if action.file:
203
- location = f" [dim]({action.file}{':' + str(action.line) if action.line else ''})[/dim]"
204
- console.print(f" • [[cyan]{action.category}[/cyan]] {action.action}{location}")
205
-
206
- if result.passed:
207
- console.print("\n[green]Verified with non-blocking signals only.[/green]")
208
- else:
209
- console.print("\n[red]Not verified: blocking gaps must be resolved.[/red]")
@@ -1,115 +0,0 @@
1
- import typer
2
- import yaml
3
- from pathlib import Path
4
- from rich.console import Console
5
- from devcouncil.cli.commands.init import parse_role_model_overrides
6
- from devcouncil.app.config import load_config
7
- from devcouncil.llm.provider import (
8
- SUPPORTED_MODEL_PROVIDERS,
9
- apply_provider_default_role_models,
10
- build_role_model_config,
11
- validate_model_provider,
12
- )
13
-
14
- app = typer.Typer(help="Manage DevCouncil configuration")
15
- console = Console()
16
-
17
- @app.command("models")
18
- def models(
19
- role: str = typer.Option(None, "--role", "-r", help="Specific role to show/edit"),
20
- model: str = typer.Option(None, "--model", "-m", help="New model string to set for the role, or every role when --role is omitted."),
21
- provider: str = typer.Option(None, "--provider", help="Set the model provider."),
22
- role_model: list[str] | None = typer.Option(
23
- None,
24
- "--role-model",
25
- help="Per-role model override in ROLE=MODEL form. Can be repeated.",
26
- ),
27
- project_root: Path = typer.Option(Path("."), "--project-root", help="Repository root containing .devcouncil/."),
28
- ):
29
- """View or edit model role configuration."""
30
- root = project_root.expanduser().resolve()
31
- try:
32
- load_config(root)
33
- except FileNotFoundError as e:
34
- console.print(f"[red]{e}[/red]")
35
- return
36
-
37
- config_path = root / ".devcouncil" / "config.yaml"
38
-
39
- with open(config_path) as f:
40
- raw_config = yaml.safe_load(f) or {}
41
-
42
- try:
43
- role_models = parse_role_model_overrides(role_model)
44
- except ValueError as e:
45
- console.print(f"[red]{e}[/red]")
46
- raise typer.Exit(code=2) from e
47
-
48
- if provider:
49
- try:
50
- normalized_provider = validate_model_provider(provider)
51
- except ValueError as e:
52
- console.print(f"[red]{e}[/red]")
53
- raise typer.Exit(code=2) from e
54
- raw_config.setdefault("models", {})
55
- previous = raw_config["models"].get("provider", "openrouter")
56
- raw_config["models"]["provider"] = normalized_provider
57
- updated_role_defaults = apply_provider_default_role_models(raw_config, previous, normalized_provider)
58
- with open(config_path, "w") as f:
59
- yaml.dump(raw_config, f, default_flow_style=False)
60
- if previous == normalized_provider:
61
- console.print(f"[green]Model provider remains '{normalized_provider}'.[/green]")
62
- else:
63
- console.print(f"[green]Updated model provider from '{previous}' to '{normalized_provider}'.[/green]")
64
- if updated_role_defaults:
65
- console.print(f"[green]Updated default role models for '{normalized_provider}'.[/green]")
66
- if not model and not role_models:
67
- return
68
-
69
- if not role and (model or role_models):
70
- raw_config.setdefault("models", {})
71
- configured_provider = validate_model_provider(raw_config["models"].get("provider", "openrouter"))
72
- raw_config["models"]["roles"] = build_role_model_config(
73
- configured_provider,
74
- model=model,
75
- role_models=role_models,
76
- )
77
- with open(config_path, "w") as f:
78
- yaml.dump(raw_config, f, default_flow_style=False)
79
- if model:
80
- console.print(f"[green]Updated all model roles to use '{model}'.[/green]")
81
- for selected_role, selected_model in role_models.items():
82
- console.print(f"[green]Updated '{selected_role}' to use model '{selected_model}'.[/green]")
83
- return
84
-
85
- if not role:
86
- console.print("[bold]Model Configuration[/bold]")
87
- configured_provider = raw_config.get("models", {}).get("provider", "openrouter")
88
- supported = ", ".join(SUPPORTED_MODEL_PROVIDERS)
89
- console.print(f" [cyan]provider[/cyan]: {configured_provider} (supported: {supported})")
90
- for r, m in raw_config.get("models", {}).get("roles", {}).items():
91
- console.print(f" [cyan]{r}[/cyan]: {m.get('model')}")
92
- return
93
-
94
- if not model:
95
- m = raw_config.get("models", {}).get("roles", {}).get(role)
96
- if m:
97
- console.print(f"[cyan]{role}[/cyan]: {m.get('model')}")
98
- else:
99
- console.print(f"[red]Role '{role}' not found.[/red]")
100
- return
101
-
102
- if "models" not in raw_config:
103
- raw_config["models"] = {"roles": {}}
104
- if "roles" not in raw_config["models"]:
105
- raw_config["models"]["roles"] = {}
106
-
107
- if role not in raw_config["models"]["roles"]:
108
- raw_config["models"]["roles"][role] = {}
109
-
110
- raw_config["models"]["roles"][role]["model"] = model
111
-
112
- with open(config_path, "w") as f:
113
- yaml.dump(raw_config, f, default_flow_style=False)
114
-
115
- console.print(f"[green]Updated '{role}' to use model '{model}'[/green]")
@@ -1,57 +0,0 @@
1
- import json
2
- from pathlib import Path
3
-
4
- import typer
5
- from rich.console import Console
6
- from rich.table import Table
7
-
8
- from devcouncil.telemetry.cost import group_cost
9
-
10
- app = typer.Typer(help="Inspect DevCouncil model-call cost, grouped by task and run.")
11
- console = Console()
12
-
13
-
14
- @app.command("show")
15
- def show(
16
- json_format: bool = typer.Option(False, "--json", help="Output machine-readable JSON."),
17
- project_root: Path = typer.Option(Path("."), "--project-root", help="Repository root containing .devcouncil/."),
18
- ):
19
- """Report estimated model-call cost grouped by task_id and run_id.
20
-
21
- Reads the local ``model_calls.jsonl`` ledger only — fully offline. Records
22
- written before per-task attribution (or made without a task/run context) are
23
- grouped under ``(unattributed)``.
24
- """
25
- root = project_root.expanduser().resolve()
26
- summary = group_cost(root)
27
-
28
- if json_format:
29
- typer.echo(json.dumps(summary, indent=2))
30
- return
31
-
32
- console.print(
33
- f"[bold]Total Cost:[/bold] ${summary['total_cost']:.4f} "
34
- f"across {summary['total_calls']} model call(s)"
35
- )
36
-
37
- def _render(title: str, groups: dict) -> None:
38
- if not groups:
39
- return
40
- table = Table(title=title)
41
- table.add_column("Group", style="cyan")
42
- table.add_column("Cost ($)", justify="right")
43
- table.add_column("Calls", justify="right")
44
- table.add_column("Prompt", justify="right")
45
- table.add_column("Completion", justify="right")
46
- for name, stats in sorted(groups.items(), key=lambda kv: kv[1]["cost"], reverse=True):
47
- table.add_row(
48
- name,
49
- f"{stats['cost']:.4f}",
50
- str(stats["calls"]),
51
- str(stats["prompt_tokens"]),
52
- str(stats["completion_tokens"]),
53
- )
54
- console.print(table)
55
-
56
- _render("Cost by Task", summary["by_task"])
57
- _render("Cost by Run", summary["by_run"])
@@ -1,31 +0,0 @@
1
- from pathlib import Path
2
- import webbrowser
3
-
4
- import typer
5
- from rich.console import Console
6
-
7
- from devcouncil.cli.commands.init import initialize_project
8
- from devcouncil.ui.dashboard import run_dashboard
9
-
10
- app = typer.Typer(help="Serve the live DevCouncil dashboard.")
11
- console = Console()
12
-
13
-
14
- @app.callback(invoke_without_command=True)
15
- def dashboard(
16
- ctx: typer.Context,
17
- host: str = typer.Option("127.0.0.1", "--host", help="Dashboard bind host."),
18
- port: int = typer.Option(8765, "--port", help="Dashboard bind port."),
19
- open_browser: bool = typer.Option(False, "--open", help="Open the dashboard URL in the default browser before serving."),
20
- project_root: Path = typer.Option(Path("."), "--project-root", help="Repository root containing .devcouncil/."),
21
- ):
22
- """Serve a local live dashboard with project status, tasks, coverage, and traces."""
23
- if ctx.invoked_subcommand is not None:
24
- return
25
- root = project_root.expanduser().resolve()
26
- initialize_project(root, quiet=True)
27
- url = f"http://{host}:{port}"
28
- console.print(f"Serving DevCouncil dashboard at {url}")
29
- if open_browser:
30
- webbrowser.open(url)
31
- run_dashboard(root, host=host, port=port)