devcouncil 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +191 -253
- package/bin/devcouncil.js +130 -32
- package/package.json +6 -17
- package/pyproject.toml +0 -66
- package/src/devcouncil/__init__.py +0 -0
- package/src/devcouncil/__main__.py +0 -4
- package/src/devcouncil/app/__init__.py +0 -28
- package/src/devcouncil/app/config.py +0 -320
- package/src/devcouncil/app/errors.py +0 -23
- package/src/devcouncil/app/events.py +0 -44
- package/src/devcouncil/app/orchestrator.py +0 -92
- package/src/devcouncil/app/project_status.py +0 -29
- package/src/devcouncil/app/run_context.py +0 -39
- package/src/devcouncil/app/state_machine.py +0 -108
- package/src/devcouncil/artifacts/__init__.py +0 -1
- package/src/devcouncil/artifacts/coverage.py +0 -96
- package/src/devcouncil/artifacts/graph.py +0 -163
- package/src/devcouncil/artifacts/migrations.py +0 -20
- package/src/devcouncil/artifacts/schemas.py +0 -23
- package/src/devcouncil/artifacts/serializer.py +0 -21
- package/src/devcouncil/artifacts/validators.py +0 -27
- package/src/devcouncil/assets/__init__.py +0 -1
- package/src/devcouncil/assets/devcouncil-logo.svg +0 -60
- package/src/devcouncil/assets/devcouncil_logo_premium.png +0 -0
- package/src/devcouncil/cli/__init__.py +0 -0
- package/src/devcouncil/cli/commands/__init__.py +0 -0
- package/src/devcouncil/cli/commands/agents.py +0 -292
- package/src/devcouncil/cli/commands/artifacts.py +0 -54
- package/src/devcouncil/cli/commands/ast.py +0 -22
- package/src/devcouncil/cli/commands/baseline.py +0 -35
- package/src/devcouncil/cli/commands/check.py +0 -209
- package/src/devcouncil/cli/commands/config.py +0 -115
- package/src/devcouncil/cli/commands/cost.py +0 -57
- package/src/devcouncil/cli/commands/dashboard.py +0 -31
- package/src/devcouncil/cli/commands/doctor.py +0 -340
- package/src/devcouncil/cli/commands/evidence.py +0 -48
- package/src/devcouncil/cli/commands/go.py +0 -656
- package/src/devcouncil/cli/commands/handoff.py +0 -69
- package/src/devcouncil/cli/commands/hook.py +0 -237
- package/src/devcouncil/cli/commands/init.py +0 -289
- package/src/devcouncil/cli/commands/integrate.py +0 -1515
- package/src/devcouncil/cli/commands/lsp.py +0 -20
- package/src/devcouncil/cli/commands/map.py +0 -112
- package/src/devcouncil/cli/commands/mcp_server.py +0 -18
- package/src/devcouncil/cli/commands/plan.py +0 -488
- package/src/devcouncil/cli/commands/prompt.py +0 -61
- package/src/devcouncil/cli/commands/repair.py +0 -89
- package/src/devcouncil/cli/commands/report.py +0 -137
- package/src/devcouncil/cli/commands/reset_demo_state.py +0 -33
- package/src/devcouncil/cli/commands/rollback.py +0 -59
- package/src/devcouncil/cli/commands/run.py +0 -289
- package/src/devcouncil/cli/commands/runs.py +0 -223
- package/src/devcouncil/cli/commands/scaffold.py +0 -32
- package/src/devcouncil/cli/commands/semantic.py +0 -47
- package/src/devcouncil/cli/commands/setup.py +0 -362
- package/src/devcouncil/cli/commands/shell.py +0 -73
- package/src/devcouncil/cli/commands/show.py +0 -76
- package/src/devcouncil/cli/commands/skills.py +0 -88
- package/src/devcouncil/cli/commands/status.py +0 -141
- package/src/devcouncil/cli/commands/tasks.py +0 -55
- package/src/devcouncil/cli/commands/trace.py +0 -88
- package/src/devcouncil/cli/commands/verify.py +0 -328
- package/src/devcouncil/cli/commands/version.py +0 -20
- package/src/devcouncil/cli/commands/watch.py +0 -574
- package/src/devcouncil/cli/commands/watch_fs.py +0 -40
- package/src/devcouncil/cli/main.py +0 -137
- package/src/devcouncil/council/__init__.py +0 -0
- package/src/devcouncil/council/prompts/__init__.py +0 -0
- package/src/devcouncil/council/prompts/arbiter.md +0 -19
- package/src/devcouncil/council/prompts/critic_a.md +0 -10
- package/src/devcouncil/council/prompts/critic_b.md +0 -10
- package/src/devcouncil/council/prompts/implementation_reviewer.md +0 -16
- package/src/devcouncil/council/prompts/planner_a.md +0 -16
- package/src/devcouncil/council/prompts/planner_b.md +0 -16
- package/src/devcouncil/council/prompts/rebuttal.md +0 -10
- package/src/devcouncil/council/prompts/spec_writer.md +0 -12
- package/src/devcouncil/domain/__init__.py +0 -0
- package/src/devcouncil/domain/assumption.py +0 -17
- package/src/devcouncil/domain/critique.py +0 -32
- package/src/devcouncil/domain/evidence.py +0 -47
- package/src/devcouncil/domain/gap.py +0 -52
- package/src/devcouncil/domain/requirement.py +0 -22
- package/src/devcouncil/domain/task.py +0 -55
- package/src/devcouncil/execution/__init__.py +0 -1
- package/src/devcouncil/execution/checkpoints.py +0 -246
- package/src/devcouncil/execution/context_builder.py +0 -60
- package/src/devcouncil/execution/executor.py +0 -15
- package/src/devcouncil/execution/fs_watcher.py +0 -180
- package/src/devcouncil/execution/handoff.py +0 -102
- package/src/devcouncil/execution/hook_policy.py +0 -253
- package/src/devcouncil/execution/patch.py +0 -77
- package/src/devcouncil/execution/paths.py +0 -14
- package/src/devcouncil/execution/permissions.py +0 -85
- package/src/devcouncil/execution/policy_engine.py +0 -343
- package/src/devcouncil/execution/prompt_builder.py +0 -671
- package/src/devcouncil/execution/shell_session.py +0 -225
- package/src/devcouncil/execution/task_runner.py +0 -170
- package/src/devcouncil/executors/__init__.py +0 -1
- package/src/devcouncil/executors/agent_registry.py +0 -575
- package/src/devcouncil/executors/coding_cli.py +0 -736
- package/src/devcouncil/executors/mini_swe.py +0 -73
- package/src/devcouncil/executors/native/__init__.py +0 -0
- package/src/devcouncil/executors/native/agent.py +0 -208
- package/src/devcouncil/executors/openhands.py +0 -71
- package/src/devcouncil/gating/__init__.py +0 -1
- package/src/devcouncil/gating/checks/__init__.py +0 -0
- package/src/devcouncil/gating/checks/clean_git.py +0 -52
- package/src/devcouncil/gating/checks/planned_files_check.py +0 -32
- package/src/devcouncil/gating/checks/requirement_coverage.py +0 -26
- package/src/devcouncil/gating/checks/secret_scan_check.py +0 -53
- package/src/devcouncil/gating/policy.py +0 -338
- package/src/devcouncil/hardware.py +0 -184
- package/src/devcouncil/indexing/__init__.py +0 -1
- package/src/devcouncil/indexing/ast_matcher.py +0 -168
- package/src/devcouncil/indexing/graph_index.py +0 -48
- package/src/devcouncil/indexing/lsp.py +0 -161
- package/src/devcouncil/indexing/repo_mapper.py +0 -1455
- package/src/devcouncil/indexing/semantic_index.py +0 -205
- package/src/devcouncil/integrations/actions.py +0 -146
- package/src/devcouncil/integrations/check.py +0 -423
- package/src/devcouncil/integrations/code_review_graph.py +0 -163
- package/src/devcouncil/integrations/github.py +0 -39
- package/src/devcouncil/integrations/github_intent.py +0 -142
- package/src/devcouncil/integrations/gitnexus.py +0 -62
- package/src/devcouncil/integrations/graphify.py +0 -34
- package/src/devcouncil/integrations/mcp/__init__.py +0 -0
- package/src/devcouncil/integrations/mcp/server.py +0 -2122
- package/src/devcouncil/integrations/opencode_devcouncil_plugin.mjs +0 -24
- package/src/devcouncil/integrations/pr_comments.py +0 -62
- package/src/devcouncil/live/__init__.py +0 -2
- package/src/devcouncil/live/cards.py +0 -349
- package/src/devcouncil/live/models.py +0 -63
- package/src/devcouncil/live/repair_prompt.py +0 -83
- package/src/devcouncil/live/reviewer.py +0 -70
- package/src/devcouncil/live/signals.py +0 -135
- package/src/devcouncil/live/summary.py +0 -34
- package/src/devcouncil/live/tasks.py +0 -18
- package/src/devcouncil/live/transcripts.py +0 -141
- package/src/devcouncil/llm/__init__.py +0 -1
- package/src/devcouncil/llm/cache.py +0 -42
- package/src/devcouncil/llm/model_defaults.yaml +0 -44
- package/src/devcouncil/llm/provider.py +0 -627
- package/src/devcouncil/llm/router.py +0 -310
- package/src/devcouncil/optimization/__init__.py +0 -1
- package/src/devcouncil/optimization/gepa_agent.py +0 -318
- package/src/devcouncil/planning/__init__.py +0 -1
- package/src/devcouncil/planning/arbiter_service.py +0 -57
- package/src/devcouncil/planning/correction_manifest.py +0 -303
- package/src/devcouncil/planning/critique_service.py +0 -71
- package/src/devcouncil/planning/plan_service.py +0 -60
- package/src/devcouncil/planning/prompt_enhancer_service.py +0 -167
- package/src/devcouncil/planning/repair_service.py +0 -39
- package/src/devcouncil/planning/spec_service.py +0 -70
- package/src/devcouncil/repo/__init__.py +0 -0
- package/src/devcouncil/repo/ci_scaffold.py +0 -157
- package/src/devcouncil/repo/gitignore.py +0 -123
- package/src/devcouncil/repo/sca.py +0 -374
- package/src/devcouncil/reporting/__init__.py +0 -0
- package/src/devcouncil/reporting/github_check.py +0 -32
- package/src/devcouncil/reporting/json_report.py +0 -30
- package/src/devcouncil/reporting/markdown_report.py +0 -83
- package/src/devcouncil/reporting/report_builder.py +0 -14
- package/src/devcouncil/skills/__init__.py +0 -19
- package/src/devcouncil/skills/library/README.md +0 -46
- package/src/devcouncil/skills/library/ai-training.md +0 -50
- package/src/devcouncil/skills/library/android.md +0 -50
- package/src/devcouncil/skills/library/backend.md +0 -52
- package/src/devcouncil/skills/library/core-engineering.md +0 -95
- package/src/devcouncil/skills/library/data-engineering.md +0 -47
- package/src/devcouncil/skills/library/desktop.md +0 -46
- package/src/devcouncil/skills/library/devops.md +0 -48
- package/src/devcouncil/skills/library/game-dev.md +0 -46
- package/src/devcouncil/skills/library/ios.md +0 -48
- package/src/devcouncil/skills/library/mobile-cross-platform.md +0 -46
- package/src/devcouncil/skills/library/security.md +0 -48
- package/src/devcouncil/skills/library/systems.md +0 -48
- package/src/devcouncil/skills/library/web.md +0 -47
- package/src/devcouncil/skills/library/windows.md +0 -47
- package/src/devcouncil/skills/registry.py +0 -330
- package/src/devcouncil/storage/__init__.py +0 -0
- package/src/devcouncil/storage/db.py +0 -147
- package/src/devcouncil/storage/models.py +0 -204
- package/src/devcouncil/storage/native.py +0 -557
- package/src/devcouncil/storage/repositories.py +0 -485
- package/src/devcouncil/telemetry/__init__.py +0 -0
- package/src/devcouncil/telemetry/cost.py +0 -140
- package/src/devcouncil/telemetry/model_pricing.yaml +0 -48
- package/src/devcouncil/telemetry/pricing.py +0 -28
- package/src/devcouncil/telemetry/traces.py +0 -146
- package/src/devcouncil/telemetry/tracker.py +0 -52
- package/src/devcouncil/ui/__init__.py +0 -1
- package/src/devcouncil/ui/dashboard.py +0 -423
- package/src/devcouncil/utils/__init__.py +0 -1
- package/src/devcouncil/utils/redaction.py +0 -147
- package/src/devcouncil/utils/subprocess_env.py +0 -69
- package/src/devcouncil/verification/__init__.py +0 -1
- package/src/devcouncil/verification/acceptance_compiler.py +0 -125
- package/src/devcouncil/verification/ad_hoc_check.py +0 -129
- package/src/devcouncil/verification/diff_coverage.py +0 -353
- package/src/devcouncil/verification/implementation_reviewer.py +0 -55
- package/src/devcouncil/verification/next_actions.py +0 -189
- package/src/devcouncil/verification/sandbox.py +0 -178
- package/src/devcouncil/verification/test_resolver.py +0 -91
- package/src/devcouncil/verification/verifier.py +0 -1548
- package/uv.lock +0 -1226
|
@@ -1,223 +0,0 @@
|
|
|
1
|
-
"""`dev runs` — list and inspect per-run agent manifests.
|
|
2
|
-
|
|
3
|
-
Coding-CLI executors write a manifest at
|
|
4
|
-
``.devcouncil/runs/<run-id>/agent-run.json`` (prompt file, executor, profile,
|
|
5
|
-
resolved command, exit status, run metadata). These commands let a developer or a
|
|
6
|
-
supervisor list and inspect those runs without reading raw JSON, and flag a run
|
|
7
|
-
whose status is still ``running`` but whose manifest has gone stale (the executor
|
|
8
|
-
process likely crashed) as ``orphaned``.
|
|
9
|
-
"""
|
|
10
|
-
|
|
11
|
-
from __future__ import annotations
|
|
12
|
-
|
|
13
|
-
import json
|
|
14
|
-
import time
|
|
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.utils.redaction import redact_text
|
|
22
|
-
|
|
23
|
-
app = typer.Typer(help="List and inspect coding-agent run manifests.")
|
|
24
|
-
console = Console()
|
|
25
|
-
|
|
26
|
-
# A run still marked ``running`` whose manifest has not been touched for longer
|
|
27
|
-
# than this is treated as orphaned (the executor process likely died). Used as a
|
|
28
|
-
# sane default; can be overridden per-call with --orphan-after.
|
|
29
|
-
_DEFAULT_ORPHAN_AFTER_SECONDS = 600
|
|
30
|
-
|
|
31
|
-
# Transcript/log files (in priority order) whose tail `dev runs show` surfaces.
|
|
32
|
-
_TRANSCRIPT_CANDIDATES = ("transcript.txt", "transcript.log", "output.log", "run.log")
|
|
33
|
-
_TRANSCRIPT_TAIL_LINES = 40
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
def _runs_dir(project_root: Path) -> Path:
|
|
37
|
-
return project_root / ".devcouncil" / "runs"
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
def _orphan_after_seconds(project_root: Path) -> int:
|
|
41
|
-
"""Threshold (seconds) after which a stale ``running`` manifest is orphaned.
|
|
42
|
-
|
|
43
|
-
Config-driven when available (execution.lease_ttl_seconds is a reasonable
|
|
44
|
-
proxy for "how long a live run can plausibly stay quiet"); falls back to a
|
|
45
|
-
sane default so the command works before `dev init`."""
|
|
46
|
-
try:
|
|
47
|
-
from devcouncil.app.config import load_config
|
|
48
|
-
|
|
49
|
-
ttl = int(load_config(project_root).execution.lease_ttl_seconds)
|
|
50
|
-
if ttl > 0:
|
|
51
|
-
return ttl
|
|
52
|
-
except Exception:
|
|
53
|
-
pass
|
|
54
|
-
return _DEFAULT_ORPHAN_AFTER_SECONDS
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
def _load_manifest(manifest_path: Path) -> dict | None:
|
|
58
|
-
try:
|
|
59
|
-
data = json.loads(manifest_path.read_text(encoding="utf-8"))
|
|
60
|
-
except (OSError, json.JSONDecodeError):
|
|
61
|
-
return None
|
|
62
|
-
return data if isinstance(data, dict) else None
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
def _is_orphaned(manifest: dict, manifest_path: Path, *, orphan_after: int, now: float) -> bool:
|
|
66
|
-
"""A run is orphaned when it is still ``running`` but its manifest file has
|
|
67
|
-
not been updated within the threshold — i.e. no heartbeat, executor gone."""
|
|
68
|
-
if manifest.get("status") != "running":
|
|
69
|
-
return False
|
|
70
|
-
try:
|
|
71
|
-
mtime = manifest_path.stat().st_mtime
|
|
72
|
-
except OSError:
|
|
73
|
-
return False
|
|
74
|
-
return (now - mtime) > max(0, orphan_after)
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
def _run_summary(manifest: dict, manifest_path: Path, *, orphan_after: int, now: float) -> dict:
|
|
78
|
-
return {
|
|
79
|
-
"run_id": manifest.get("run_id") or manifest_path.parent.name,
|
|
80
|
-
"task_id": manifest.get("task_id"),
|
|
81
|
-
"agent": manifest.get("agent"),
|
|
82
|
-
"profile": manifest.get("profile"),
|
|
83
|
-
"status": manifest.get("status"),
|
|
84
|
-
"started_at": manifest.get("started_at") or manifest.get("timestamp"),
|
|
85
|
-
"finished_at": manifest.get("finished_at"),
|
|
86
|
-
"returncode": manifest.get("returncode"),
|
|
87
|
-
"orphaned": _is_orphaned(manifest, manifest_path, orphan_after=orphan_after, now=now),
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
def _collect_runs(project_root: Path, *, orphan_after: int) -> list[dict]:
|
|
92
|
-
runs_dir = _runs_dir(project_root)
|
|
93
|
-
if not runs_dir.is_dir():
|
|
94
|
-
return []
|
|
95
|
-
now = time.time()
|
|
96
|
-
summaries: list[tuple[float, dict]] = []
|
|
97
|
-
for manifest_path in runs_dir.glob("*/agent-run.json"):
|
|
98
|
-
manifest = _load_manifest(manifest_path)
|
|
99
|
-
if manifest is None:
|
|
100
|
-
continue
|
|
101
|
-
try:
|
|
102
|
-
sort_key = manifest_path.stat().st_mtime
|
|
103
|
-
except OSError:
|
|
104
|
-
sort_key = 0.0
|
|
105
|
-
summaries.append((sort_key, _run_summary(manifest, manifest_path, orphan_after=orphan_after, now=now)))
|
|
106
|
-
# Newest first.
|
|
107
|
-
summaries.sort(key=lambda item: item[0], reverse=True)
|
|
108
|
-
return [summary for _, summary in summaries]
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
def _find_transcript(run_dir: Path, manifest: dict) -> Path | None:
|
|
112
|
-
recorded = manifest.get("transcript")
|
|
113
|
-
if isinstance(recorded, str) and recorded:
|
|
114
|
-
candidate = Path(recorded)
|
|
115
|
-
if not candidate.is_absolute():
|
|
116
|
-
candidate = run_dir / recorded
|
|
117
|
-
if candidate.is_file():
|
|
118
|
-
return candidate
|
|
119
|
-
for name in _TRANSCRIPT_CANDIDATES:
|
|
120
|
-
candidate = run_dir / name
|
|
121
|
-
if candidate.is_file():
|
|
122
|
-
return candidate
|
|
123
|
-
return None
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
def _transcript_tail(path: Path, *, lines: int = _TRANSCRIPT_TAIL_LINES) -> str:
|
|
127
|
-
"""Return the redacted tail of a transcript file (best-effort)."""
|
|
128
|
-
try:
|
|
129
|
-
content = path.read_text(encoding="utf-8", errors="replace")
|
|
130
|
-
except OSError:
|
|
131
|
-
return ""
|
|
132
|
-
tail = content.splitlines()[-lines:]
|
|
133
|
-
return redact_text("\n".join(tail))
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
@app.command("list")
|
|
137
|
-
def list_runs(
|
|
138
|
-
json_output: bool = typer.Option(False, "--json", help="Emit machine-readable JSON."),
|
|
139
|
-
limit: int = typer.Option(20, "--limit", help="Maximum number of runs to show."),
|
|
140
|
-
status: str | None = typer.Option(None, "--status", help="Filter by run status (e.g. running, finished, failed, timeout)."),
|
|
141
|
-
project_root: Path = typer.Option(Path("."), "--project-root", help="Repository root containing .devcouncil/."),
|
|
142
|
-
) -> None:
|
|
143
|
-
"""List recorded coding-agent runs, newest first."""
|
|
144
|
-
root = project_root.expanduser().resolve()
|
|
145
|
-
orphan_after = _orphan_after_seconds(root)
|
|
146
|
-
runs = _collect_runs(root, orphan_after=orphan_after)
|
|
147
|
-
if status:
|
|
148
|
-
runs = [run for run in runs if run.get("status") == status]
|
|
149
|
-
if limit > 0:
|
|
150
|
-
runs = runs[:limit]
|
|
151
|
-
|
|
152
|
-
if json_output:
|
|
153
|
-
console.print_json(data={"runs": runs, "count": len(runs)})
|
|
154
|
-
return
|
|
155
|
-
|
|
156
|
-
if not runs:
|
|
157
|
-
console.print("[dim]No agent runs found under .devcouncil/runs/.[/dim]")
|
|
158
|
-
return
|
|
159
|
-
|
|
160
|
-
table = Table(title="Agent runs")
|
|
161
|
-
table.add_column("Run ID", overflow="fold")
|
|
162
|
-
table.add_column("Task")
|
|
163
|
-
table.add_column("Agent")
|
|
164
|
-
table.add_column("Profile")
|
|
165
|
-
table.add_column("Status")
|
|
166
|
-
table.add_column("Started")
|
|
167
|
-
for run in runs:
|
|
168
|
-
status_text = str(run.get("status") or "?")
|
|
169
|
-
if run.get("orphaned"):
|
|
170
|
-
status_text = f"[red]{status_text} (orphaned)[/red]"
|
|
171
|
-
table.add_row(
|
|
172
|
-
str(run.get("run_id") or ""),
|
|
173
|
-
str(run.get("task_id") or ""),
|
|
174
|
-
str(run.get("agent") or ""),
|
|
175
|
-
str(run.get("profile") or ""),
|
|
176
|
-
status_text,
|
|
177
|
-
str(run.get("started_at") or ""),
|
|
178
|
-
)
|
|
179
|
-
console.print(table)
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
@app.command("show")
|
|
183
|
-
def show_run(
|
|
184
|
-
run_id: str = typer.Argument(..., help="The run id to inspect."),
|
|
185
|
-
json_output: bool = typer.Option(False, "--json", help="Emit machine-readable JSON."),
|
|
186
|
-
project_root: Path = typer.Option(Path("."), "--project-root", help="Repository root containing .devcouncil/."),
|
|
187
|
-
) -> None:
|
|
188
|
-
"""Show the full manifest for a run plus a redacted transcript tail."""
|
|
189
|
-
root = project_root.expanduser().resolve()
|
|
190
|
-
run_dir = _runs_dir(root) / run_id
|
|
191
|
-
manifest_path = run_dir / "agent-run.json"
|
|
192
|
-
manifest = _load_manifest(manifest_path)
|
|
193
|
-
if manifest is None:
|
|
194
|
-
if json_output:
|
|
195
|
-
console.print_json(data={"ok": False, "error": f"Run {run_id} not found.", "run_id": run_id})
|
|
196
|
-
else:
|
|
197
|
-
console.print(f"[red]Run {run_id} not found under .devcouncil/runs/.[/red]")
|
|
198
|
-
raise typer.Exit(code=1)
|
|
199
|
-
|
|
200
|
-
orphan_after = _orphan_after_seconds(root)
|
|
201
|
-
orphaned = _is_orphaned(manifest, manifest_path, orphan_after=orphan_after, now=time.time())
|
|
202
|
-
transcript_path = _find_transcript(run_dir, manifest)
|
|
203
|
-
transcript_tail = _transcript_tail(transcript_path) if transcript_path else ""
|
|
204
|
-
|
|
205
|
-
if json_output:
|
|
206
|
-
console.print_json(data={
|
|
207
|
-
"ok": True,
|
|
208
|
-
"run_id": run_id,
|
|
209
|
-
"manifest": manifest,
|
|
210
|
-
"orphaned": orphaned,
|
|
211
|
-
"transcript_path": str(transcript_path) if transcript_path else None,
|
|
212
|
-
"transcript_tail": transcript_tail,
|
|
213
|
-
})
|
|
214
|
-
return
|
|
215
|
-
|
|
216
|
-
console.print_json(data=manifest)
|
|
217
|
-
if orphaned:
|
|
218
|
-
console.print("[red]This run is orphaned: still marked running but its manifest is stale.[/red]")
|
|
219
|
-
if transcript_path:
|
|
220
|
-
console.print(f"\n[bold]Transcript tail[/bold] [dim]({transcript_path})[/dim]:")
|
|
221
|
-
console.print(transcript_tail or "[dim](empty)[/dim]")
|
|
222
|
-
else:
|
|
223
|
-
console.print("\n[dim]No transcript file found for this run.[/dim]")
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
from pathlib import Path
|
|
2
|
-
|
|
3
|
-
import typer
|
|
4
|
-
from rich.console import Console
|
|
5
|
-
|
|
6
|
-
from devcouncil.repo.ci_scaffold import WORKFLOW_RELPATH, detect_stacks, scaffold_ci
|
|
7
|
-
|
|
8
|
-
console = Console()
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
def scaffold_ci_command(
|
|
12
|
-
project_root: Path = typer.Option(Path("."), "--project-root", help="Repository root to scaffold CI into."),
|
|
13
|
-
force: bool = typer.Option(False, "--force", help="Overwrite an existing devcouncil.yml workflow."),
|
|
14
|
-
):
|
|
15
|
-
"""Write a starter GitHub Actions workflow derived from the configured commands."""
|
|
16
|
-
root = project_root.expanduser().resolve()
|
|
17
|
-
if not (root / ".devcouncil").exists():
|
|
18
|
-
console.print("[red]DevCouncil is not initialized here. Run 'dev setup' first.[/red]")
|
|
19
|
-
raise typer.Exit(code=1)
|
|
20
|
-
|
|
21
|
-
target = scaffold_ci(root, force=force)
|
|
22
|
-
if target is None:
|
|
23
|
-
console.print(
|
|
24
|
-
f"[yellow]{WORKFLOW_RELPATH.as_posix()} already exists. "
|
|
25
|
-
f"Re-run with --force to overwrite.[/yellow]"
|
|
26
|
-
)
|
|
27
|
-
return
|
|
28
|
-
|
|
29
|
-
stacks = detect_stacks(root)
|
|
30
|
-
detected = ", ".join(sorted(stacks)) if stacks else "none auto-detected"
|
|
31
|
-
console.print(f"[green]Wrote {target.relative_to(root).as_posix()} (stacks: {detected}).[/green]")
|
|
32
|
-
console.print("[dim]Review the setup/install steps and commands before relying on it.[/dim]")
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import json
|
|
2
|
-
import typer
|
|
3
|
-
from pathlib import Path
|
|
4
|
-
from rich.console import Console
|
|
5
|
-
|
|
6
|
-
from devcouncil.cli.commands.init import initialize_project
|
|
7
|
-
from devcouncil.indexing.semantic_index import SemanticIndex
|
|
8
|
-
|
|
9
|
-
app = typer.Typer(help="Semantic snapshots and diffs.")
|
|
10
|
-
console = Console()
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
@app.command("snapshot")
|
|
14
|
-
def snapshot(
|
|
15
|
-
task_id: str = typer.Argument(...),
|
|
16
|
-
stage: str = typer.Option("before", "--stage", help="before or after"),
|
|
17
|
-
project_root: Path = typer.Option(Path("."), "--project-root"),
|
|
18
|
-
json_format: bool = typer.Option(False, "--json"),
|
|
19
|
-
):
|
|
20
|
-
root = project_root.expanduser().resolve()
|
|
21
|
-
initialize_project(root, quiet=True)
|
|
22
|
-
if stage not in {"before", "after"}:
|
|
23
|
-
console.print("[red]--stage must be before or after[/red]")
|
|
24
|
-
raise typer.Exit(code=2)
|
|
25
|
-
path = SemanticIndex(root).create_snapshot(task_id, stage)
|
|
26
|
-
payload = {"task_id": task_id, "stage": stage, "path": str(path)}
|
|
27
|
-
if json_format:
|
|
28
|
-
typer.echo(json.dumps(payload, indent=2))
|
|
29
|
-
else:
|
|
30
|
-
console.print(f"[green]Wrote semantic snapshot:[/green] {path}")
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
@app.command("diff")
|
|
34
|
-
def semantic_diff(
|
|
35
|
-
task_id: str = typer.Argument(...),
|
|
36
|
-
project_root: Path = typer.Option(Path("."), "--project-root"),
|
|
37
|
-
json_format: bool = typer.Option(False, "--json"),
|
|
38
|
-
):
|
|
39
|
-
root = project_root.expanduser().resolve()
|
|
40
|
-
initialize_project(root, quiet=True)
|
|
41
|
-
result = SemanticIndex(root).diff(task_id)
|
|
42
|
-
if json_format:
|
|
43
|
-
typer.echo(json.dumps(result, indent=2))
|
|
44
|
-
else:
|
|
45
|
-
console.print(f"[cyan]Summary:[/cyan] {result['summary']}")
|
|
46
|
-
for item in result["classifications"]:
|
|
47
|
-
console.print(f" - {item['type']}: {item.get('path', '')}")
|
|
@@ -1,362 +0,0 @@
|
|
|
1
|
-
from pathlib import Path
|
|
2
|
-
import os
|
|
3
|
-
import shutil
|
|
4
|
-
import sys
|
|
5
|
-
|
|
6
|
-
import typer
|
|
7
|
-
import yaml
|
|
8
|
-
from rich.console import Console
|
|
9
|
-
from rich.panel import Panel
|
|
10
|
-
|
|
11
|
-
from devcouncil.app.config import (
|
|
12
|
-
_normalized_provider_name,
|
|
13
|
-
get_gcloud_access_token,
|
|
14
|
-
load_config,
|
|
15
|
-
load_local_secrets,
|
|
16
|
-
provider_api_key_env_var,
|
|
17
|
-
)
|
|
18
|
-
from devcouncil.cli.commands.doctor import render_doctor_check
|
|
19
|
-
from devcouncil.cli.commands.init import initialize_project, parse_role_model_overrides
|
|
20
|
-
from devcouncil.cli.commands.integrate import (
|
|
21
|
-
_claude_command,
|
|
22
|
-
_codex_command,
|
|
23
|
-
_configure_antigravity,
|
|
24
|
-
_configure_cursor,
|
|
25
|
-
_configure_native_hooks,
|
|
26
|
-
_configure_opencode,
|
|
27
|
-
_configure_warp,
|
|
28
|
-
_configure,
|
|
29
|
-
_gemini_command,
|
|
30
|
-
)
|
|
31
|
-
from devcouncil.llm.provider import apply_provider_default_role_models, build_role_model_config, validate_model_provider
|
|
32
|
-
|
|
33
|
-
app = typer.Typer()
|
|
34
|
-
console = Console()
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
def _set_model_provider(project_root: Path, provider: str) -> None:
|
|
38
|
-
normalized = validate_model_provider(provider)
|
|
39
|
-
config_path = project_root / ".devcouncil" / "config.yaml"
|
|
40
|
-
raw_config = yaml.safe_load(config_path.read_text(encoding="utf-8")) or {}
|
|
41
|
-
raw_config.setdefault("models", {})
|
|
42
|
-
previous = raw_config["models"].get("provider", "openrouter")
|
|
43
|
-
raw_config["models"]["provider"] = normalized
|
|
44
|
-
updated_role_defaults = apply_provider_default_role_models(raw_config, previous, normalized)
|
|
45
|
-
config_path.write_text(yaml.dump(raw_config, default_flow_style=False), encoding="utf-8")
|
|
46
|
-
if previous != normalized:
|
|
47
|
-
console.print(f"[green]Updated model provider from {previous} to {normalized}.[/green]")
|
|
48
|
-
if updated_role_defaults:
|
|
49
|
-
console.print(f"[green]Updated default role models for {normalized}.[/green]")
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
def _set_model_roles(
|
|
53
|
-
project_root: Path,
|
|
54
|
-
model: str | None = None,
|
|
55
|
-
role_models: dict[str, str] | None = None,
|
|
56
|
-
) -> None:
|
|
57
|
-
if not model and not role_models:
|
|
58
|
-
return
|
|
59
|
-
|
|
60
|
-
config_path = project_root / ".devcouncil" / "config.yaml"
|
|
61
|
-
raw_config = yaml.safe_load(config_path.read_text(encoding="utf-8")) or {}
|
|
62
|
-
raw_config.setdefault("models", {})
|
|
63
|
-
provider = validate_model_provider(raw_config["models"].get("provider", "openrouter"))
|
|
64
|
-
raw_config["models"]["roles"] = build_role_model_config(
|
|
65
|
-
provider,
|
|
66
|
-
model=model,
|
|
67
|
-
role_models=role_models,
|
|
68
|
-
)
|
|
69
|
-
config_path.write_text(yaml.dump(raw_config, default_flow_style=False), encoding="utf-8")
|
|
70
|
-
if model:
|
|
71
|
-
console.print(f"[green]Updated all model roles to use {model}.[/green]")
|
|
72
|
-
for role, selected_model in (role_models or {}).items():
|
|
73
|
-
console.print(f"[green]Updated {role} to use {selected_model}.[/green]")
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
def _write_local_secret(project_root: Path, env_var: str, value: str) -> Path:
|
|
77
|
-
if "\n" in value or "\r" in value:
|
|
78
|
-
raise ValueError("API keys cannot contain newlines.")
|
|
79
|
-
secrets = load_local_secrets(project_root)
|
|
80
|
-
secrets[env_var] = value
|
|
81
|
-
path = project_root / ".devcouncil" / "secrets.env"
|
|
82
|
-
path.parent.mkdir(parents=True, exist_ok=True)
|
|
83
|
-
lines = [
|
|
84
|
-
"# Local DevCouncil secrets. This file is ignored by git.",
|
|
85
|
-
"# Process environment variables with the same name take precedence.",
|
|
86
|
-
*[f"{key}={val}" for key, val in sorted(secrets.items())],
|
|
87
|
-
"",
|
|
88
|
-
]
|
|
89
|
-
path.write_text("\n".join(lines), encoding="utf-8")
|
|
90
|
-
return path
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
def _configure_vertexai_settings(
|
|
94
|
-
project_root: Path,
|
|
95
|
-
provider: str,
|
|
96
|
-
vertex_project: str | None,
|
|
97
|
-
vertex_location: str | None,
|
|
98
|
-
) -> None:
|
|
99
|
-
if provider != "vertexai":
|
|
100
|
-
return
|
|
101
|
-
|
|
102
|
-
local_secrets = load_local_secrets(project_root)
|
|
103
|
-
if vertex_project:
|
|
104
|
-
_write_local_secret(project_root, "VERTEXAI_PROJECT", vertex_project)
|
|
105
|
-
console.print("[green]Saved VERTEXAI_PROJECT to .devcouncil/secrets.env.[/green]")
|
|
106
|
-
elif not (
|
|
107
|
-
os.environ.get("VERTEXAI_PROJECT")
|
|
108
|
-
or os.environ.get("GOOGLE_CLOUD_PROJECT")
|
|
109
|
-
or local_secrets.get("VERTEXAI_PROJECT")
|
|
110
|
-
or local_secrets.get("GOOGLE_CLOUD_PROJECT")
|
|
111
|
-
):
|
|
112
|
-
console.print(
|
|
113
|
-
"[yellow]VERTEXAI_PROJECT is not set. "
|
|
114
|
-
"Set it in your shell or rerun setup with --vertex-project PROJECT_ID.[/yellow]"
|
|
115
|
-
)
|
|
116
|
-
|
|
117
|
-
if vertex_location:
|
|
118
|
-
_write_local_secret(project_root, "VERTEXAI_LOCATION", vertex_location)
|
|
119
|
-
console.print("[green]Saved VERTEXAI_LOCATION to .devcouncil/secrets.env.[/green]")
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
def _configure_api_key(project_root: Path, api_key: str | None, skip_api_key: bool) -> None:
|
|
123
|
-
config = load_config(project_root)
|
|
124
|
-
provider = config.models.provider
|
|
125
|
-
if _normalized_provider_name(provider) == "ollama":
|
|
126
|
-
console.print(
|
|
127
|
-
"[green]Ollama uses a local server (default http://localhost:11434); no API key required.[/green]"
|
|
128
|
-
)
|
|
129
|
-
return
|
|
130
|
-
env_var = provider_api_key_env_var(provider)
|
|
131
|
-
local_secrets = load_local_secrets(project_root)
|
|
132
|
-
|
|
133
|
-
if os.environ.get(env_var):
|
|
134
|
-
console.print(f"[green]{env_var} is already set in the environment.[/green]")
|
|
135
|
-
return
|
|
136
|
-
if local_secrets.get(env_var):
|
|
137
|
-
console.print(f"[green]{env_var} is already set in .devcouncil/secrets.env.[/green]")
|
|
138
|
-
return
|
|
139
|
-
if api_key:
|
|
140
|
-
_write_local_secret(project_root, env_var, api_key)
|
|
141
|
-
console.print(f"[green]Saved {env_var} to .devcouncil/secrets.env.[/green]")
|
|
142
|
-
return
|
|
143
|
-
if provider == "vertexai" and get_gcloud_access_token():
|
|
144
|
-
console.print("[green]Vertex AI access token is available from gcloud auth print-access-token.[/green]")
|
|
145
|
-
return
|
|
146
|
-
if skip_api_key:
|
|
147
|
-
console.print(f"[yellow]Skipped {env_var} setup. Model-backed commands will ask again if it is missing.[/yellow]")
|
|
148
|
-
return
|
|
149
|
-
if not sys.stdin.isatty():
|
|
150
|
-
console.print(
|
|
151
|
-
f"[yellow]{env_var} is not set.[/yellow] "
|
|
152
|
-
f"Run [bold]dev setup --api-key YOUR_KEY[/bold] or set it in your shell before model-backed commands."
|
|
153
|
-
)
|
|
154
|
-
return
|
|
155
|
-
|
|
156
|
-
console.print()
|
|
157
|
-
console.print(Panel.fit(
|
|
158
|
-
"\n".join([
|
|
159
|
-
f"Provider: {provider}",
|
|
160
|
-
f"Required key: {env_var}",
|
|
161
|
-
"Press Enter without a value to skip for now.",
|
|
162
|
-
]),
|
|
163
|
-
title="Model API Key",
|
|
164
|
-
border_style="cyan",
|
|
165
|
-
))
|
|
166
|
-
entered = typer.prompt(f"{env_var}", default="", hide_input=True, show_default=False)
|
|
167
|
-
if not entered:
|
|
168
|
-
console.print(f"[yellow]Skipped {env_var} setup.[/yellow]")
|
|
169
|
-
return
|
|
170
|
-
_write_local_secret(project_root, env_var, entered)
|
|
171
|
-
console.print(f"[green]Saved {env_var} to .devcouncil/secrets.env.[/green]")
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
def _is_interactive_terminal() -> bool:
|
|
175
|
-
return sys.stdin.isatty()
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
def _configure_coding_cli_integrations(project_root: Path, apply: bool, gemini_scope: str) -> None:
|
|
179
|
-
console.print()
|
|
180
|
-
console.print("[bold]Coding CLI integration[/bold]")
|
|
181
|
-
commands = [
|
|
182
|
-
("Codex CLI", _codex_command(project_root)),
|
|
183
|
-
("Gemini CLI", _gemini_command(project_root, gemini_scope)),
|
|
184
|
-
("Claude Code", _claude_command(project_root, "local")),
|
|
185
|
-
]
|
|
186
|
-
results = []
|
|
187
|
-
for tool, command in commands:
|
|
188
|
-
if apply and not shutil.which(command[0]):
|
|
189
|
-
console.print(f"[yellow]{tool} CLI not found on PATH. Skipping optional integration.[/yellow]")
|
|
190
|
-
continue
|
|
191
|
-
results.append(_configure(tool, command, apply))
|
|
192
|
-
results.append(_configure_cursor(project_root, apply))
|
|
193
|
-
results.append(_configure_opencode(project_root, apply))
|
|
194
|
-
results.append(_configure_antigravity(project_root, apply))
|
|
195
|
-
results.append(_configure_warp(project_root, apply))
|
|
196
|
-
_configure_native_hooks(project_root, "all", apply)
|
|
197
|
-
if apply and any(not ok for ok in results):
|
|
198
|
-
raise typer.Exit(code=1)
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
def _prompt_for_first_run_integrations(project_root: Path, apply: bool, gemini_scope: str) -> bool:
|
|
202
|
-
if not _is_interactive_terminal():
|
|
203
|
-
return False
|
|
204
|
-
|
|
205
|
-
console.print()
|
|
206
|
-
console.print(Panel.fit(
|
|
207
|
-
"\n".join([
|
|
208
|
-
"DevCouncil can configure supported coding CLIs now.",
|
|
209
|
-
"This adds MCP setup and native hook config for detected clients.",
|
|
210
|
-
"Missing optional clients are skipped.",
|
|
211
|
-
]),
|
|
212
|
-
title="Coding CLI Setup",
|
|
213
|
-
border_style="cyan",
|
|
214
|
-
))
|
|
215
|
-
if not typer.confirm("Set up coding CLI integrations now?", default=True):
|
|
216
|
-
console.print("[yellow]Skipped coding CLI integration setup.[/yellow]")
|
|
217
|
-
return False
|
|
218
|
-
|
|
219
|
-
_configure_coding_cli_integrations(project_root, apply=apply, gemini_scope=gemini_scope)
|
|
220
|
-
return True
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
@app.callback(invoke_without_command=True)
|
|
224
|
-
def setup(
|
|
225
|
-
ctx: typer.Context,
|
|
226
|
-
project_root: Path = typer.Option(
|
|
227
|
-
Path("."),
|
|
228
|
-
"--project-root",
|
|
229
|
-
help="Target project repository root. Defaults to the terminal's current directory.",
|
|
230
|
-
),
|
|
231
|
-
name: str | None = typer.Option(None, "--name", "-n", help="Project name for .devcouncil/config.yaml."),
|
|
232
|
-
integrate: bool = typer.Option(False, "--integrate", help="Configure supported coding CLI MCP integrations and native hooks."),
|
|
233
|
-
apply: bool = typer.Option(False, "--apply", help="Apply integration config instead of previewing commands."),
|
|
234
|
-
gemini_scope: str = typer.Option("project", "--gemini-scope", help="Gemini MCP config scope: project or user."),
|
|
235
|
-
provider: str | None = typer.Option(None, "--provider", help="Set models.provider before configuring the API key."),
|
|
236
|
-
model: str | None = typer.Option(None, "--model", "-m", help="Model id to use for every default role."),
|
|
237
|
-
role_model: list[str] | None = typer.Option(
|
|
238
|
-
None,
|
|
239
|
-
"--role-model",
|
|
240
|
-
help="Per-role model override in ROLE=MODEL form. Can be repeated.",
|
|
241
|
-
),
|
|
242
|
-
api_key: str | None = typer.Option(None, "--api-key", help="Store the configured provider API key in local .devcouncil/secrets.env."),
|
|
243
|
-
vertex_project: str | None = typer.Option(None, "--vertex-project", help="Store VERTEXAI_PROJECT for the vertexai provider."),
|
|
244
|
-
vertex_location: str | None = typer.Option(None, "--vertex-location", help="Store VERTEXAI_LOCATION for the vertexai provider. Defaults to global."),
|
|
245
|
-
skip_api_key: bool = typer.Option(False, "--skip-api-key", help="Skip the first-run model API key prompt."),
|
|
246
|
-
skip_integrations: bool = typer.Option(False, "--skip-integrations", help="Skip the first-run coding CLI integration prompt."),
|
|
247
|
-
skip_map: bool = typer.Option(False, "--skip-map", help="Skip generating repo_map.json and agent guides on init."),
|
|
248
|
-
skip_skills: bool = typer.Option(False, "--skip-skills", help="Skip scaffolding engineering skills into .claude/skills/ on init."),
|
|
249
|
-
scaffold_ci: bool = typer.Option(False, "--scaffold-ci", help="Write a starter .github/workflows/devcouncil.yml from the configured commands."),
|
|
250
|
-
):
|
|
251
|
-
"""
|
|
252
|
-
Initialize DevCouncil from a normal terminal in the target repository root.
|
|
253
|
-
|
|
254
|
-
Use the coding CLI later only for the generated dev prompt output.
|
|
255
|
-
"""
|
|
256
|
-
if ctx.invoked_subcommand is not None:
|
|
257
|
-
return
|
|
258
|
-
|
|
259
|
-
if gemini_scope not in {"project", "user"}:
|
|
260
|
-
console.print("[red]--gemini-scope must be 'project' or 'user'.[/red]")
|
|
261
|
-
raise typer.Exit(code=2)
|
|
262
|
-
|
|
263
|
-
root = project_root.expanduser().resolve()
|
|
264
|
-
try:
|
|
265
|
-
role_models = parse_role_model_overrides(role_model)
|
|
266
|
-
initial_provider = validate_model_provider(provider) if provider else "openrouter"
|
|
267
|
-
except ValueError as e:
|
|
268
|
-
console.print(f"[red]{e}[/red]")
|
|
269
|
-
raise typer.Exit(code=2) from e
|
|
270
|
-
|
|
271
|
-
# Size the default local model to the host's memory ceiling (unified RAM on Apple
|
|
272
|
-
# Silicon, VRAM on a discrete GPU, system RAM otherwise) instead of the static 7b,
|
|
273
|
-
# so users on any OS get a capable council out of the box. Only when the user chose
|
|
274
|
-
# Ollama and did not pin a model.
|
|
275
|
-
if initial_provider == "ollama" and model is None:
|
|
276
|
-
from devcouncil import hardware
|
|
277
|
-
|
|
278
|
-
host = hardware.describe_host()
|
|
279
|
-
model = host.recommended_ollama_model
|
|
280
|
-
console.print(
|
|
281
|
-
f"[green]Detected {host.chip_label} ({host.memory_label}); "
|
|
282
|
-
f"defaulting Ollama model to {model}.[/green] "
|
|
283
|
-
"Override with --model, and pull it first: "
|
|
284
|
-
f"[bold]ollama pull {model}[/bold]."
|
|
285
|
-
)
|
|
286
|
-
elif initial_provider == "ollama" and model is not None:
|
|
287
|
-
# Model pinned explicitly — still remind the user it must be pulled locally.
|
|
288
|
-
console.print(
|
|
289
|
-
f"[green]Using Ollama model {model}.[/green] "
|
|
290
|
-
f"Pull it first if needed: [bold]ollama pull {model}[/bold]."
|
|
291
|
-
)
|
|
292
|
-
|
|
293
|
-
created = initialize_project(
|
|
294
|
-
root,
|
|
295
|
-
project_name=name,
|
|
296
|
-
model_provider=initial_provider,
|
|
297
|
-
model=model,
|
|
298
|
-
role_models=role_models,
|
|
299
|
-
with_map=not skip_map,
|
|
300
|
-
with_skills=not skip_skills,
|
|
301
|
-
)
|
|
302
|
-
if not created:
|
|
303
|
-
console.print(f"[yellow]DevCouncil is already initialized at {root / '.devcouncil'}.[/yellow]")
|
|
304
|
-
|
|
305
|
-
if provider:
|
|
306
|
-
try:
|
|
307
|
-
_set_model_provider(root, provider)
|
|
308
|
-
except ValueError as e:
|
|
309
|
-
console.print(f"[red]{e}[/red]")
|
|
310
|
-
raise typer.Exit(code=2) from e
|
|
311
|
-
|
|
312
|
-
_set_model_roles(root, model=model, role_models=role_models)
|
|
313
|
-
|
|
314
|
-
configured_provider = load_config(root).models.provider
|
|
315
|
-
_configure_vertexai_settings(root, configured_provider, vertex_project, vertex_location)
|
|
316
|
-
_configure_api_key(root, api_key, skip_api_key)
|
|
317
|
-
|
|
318
|
-
console.print()
|
|
319
|
-
render_doctor_check(root)
|
|
320
|
-
|
|
321
|
-
if scaffold_ci:
|
|
322
|
-
from devcouncil.repo.ci_scaffold import WORKFLOW_RELPATH, scaffold_ci as scaffold_ci_workflow
|
|
323
|
-
|
|
324
|
-
written = scaffold_ci_workflow(root)
|
|
325
|
-
if written is None:
|
|
326
|
-
console.print(f"[yellow]{WORKFLOW_RELPATH.as_posix()} already exists; left unchanged.[/yellow]")
|
|
327
|
-
else:
|
|
328
|
-
console.print(f"[green]Wrote starter CI workflow {written.relative_to(root).as_posix()}.[/green]")
|
|
329
|
-
|
|
330
|
-
if integrate:
|
|
331
|
-
_configure_coding_cli_integrations(root, apply=apply, gemini_scope=gemini_scope)
|
|
332
|
-
elif created and not skip_integrations:
|
|
333
|
-
_prompt_for_first_run_integrations(root, apply=True, gemini_scope=gemini_scope)
|
|
334
|
-
|
|
335
|
-
console.print()
|
|
336
|
-
console.print(Panel.fit(
|
|
337
|
-
"\n".join([
|
|
338
|
-
"[bold]Next commands[/bold]",
|
|
339
|
-
f"Keep running DevCouncil commands in this terminal at: {root}",
|
|
340
|
-
"One-command agent path:",
|
|
341
|
-
"dev e2e \"Describe the implementation goal\"",
|
|
342
|
-
"",
|
|
343
|
-
"Manual sidecar path:",
|
|
344
|
-
"dev plan \"Describe the implementation goal\"",
|
|
345
|
-
"dev tasks",
|
|
346
|
-
"dev run TASK-001 --executor manual",
|
|
347
|
-
"dev prompt TASK-001",
|
|
348
|
-
"Paste only the dev prompt output into your coding CLI.",
|
|
349
|
-
"Paste only the dev prompt output into your coding CLI, or run directly:",
|
|
350
|
-
"dev run TASK-001 --executor codex",
|
|
351
|
-
"dev run TASK-001 --executor gemini",
|
|
352
|
-
"dev run TASK-001 --executor claude",
|
|
353
|
-
"dev run TASK-001 --executor opencode",
|
|
354
|
-
"dev run TASK-001 --executor antigravity",
|
|
355
|
-
"dev verify TASK-001",
|
|
356
|
-
"",
|
|
357
|
-
"Use [bold]dev setup --integrate[/bold] to preview coding CLI MCP and native hook setup.",
|
|
358
|
-
"Use [bold]dev setup --integrate --apply[/bold] to configure detected clients.",
|
|
359
|
-
]),
|
|
360
|
-
title="DevCouncil is ready",
|
|
361
|
-
border_style="green",
|
|
362
|
-
))
|