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.
- package/README.md +144 -308
- package/bin/devcouncil.js +130 -32
- package/package.json +22 -19
- 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
package/bin/devcouncil.js
CHANGED
|
@@ -1,18 +1,129 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* npm `dev` / `devcouncil` shim.
|
|
5
|
+
*
|
|
6
|
+
* Live commands belong to the Go `devcouncil` binary, including `map` /
|
|
7
|
+
* `graph` / `ast` (which that binary execs `devmap` for). This file never
|
|
8
|
+
* invokes Python or uv, and it is not a second owner of any command.
|
|
9
|
+
*/
|
|
10
|
+
|
|
3
11
|
const { spawnSync } = require("node:child_process");
|
|
4
|
-
const {
|
|
12
|
+
const {
|
|
13
|
+
closeSync,
|
|
14
|
+
existsSync,
|
|
15
|
+
openSync,
|
|
16
|
+
readSync,
|
|
17
|
+
realpathSync,
|
|
18
|
+
statSync,
|
|
19
|
+
} = require("node:fs");
|
|
5
20
|
const path = require("node:path");
|
|
6
21
|
|
|
7
|
-
const
|
|
8
|
-
|
|
22
|
+
const WIN32 = process.platform === "win32";
|
|
23
|
+
|
|
24
|
+
function localBinDir() {
|
|
25
|
+
const home = process.env.HOME || process.env.USERPROFILE || "";
|
|
26
|
+
return home ? path.join(home, ".local", "bin") : "";
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function isSelf(file) {
|
|
30
|
+
try {
|
|
31
|
+
return realpathSync(file) === realpathSync(__filename);
|
|
32
|
+
} catch {
|
|
33
|
+
return path.resolve(file) === path.resolve(__filename);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function looksLikeNodeShim(file) {
|
|
38
|
+
const lower = file.toLowerCase();
|
|
39
|
+
if (
|
|
40
|
+
lower.endsWith(".js") ||
|
|
41
|
+
lower.endsWith(".cmd") ||
|
|
42
|
+
lower.endsWith(".bat") ||
|
|
43
|
+
lower.endsWith(".ps1")
|
|
44
|
+
) {
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
let fd;
|
|
48
|
+
try {
|
|
49
|
+
fd = openSync(file, "r");
|
|
50
|
+
const buf = Buffer.alloc(160);
|
|
51
|
+
const n = readSync(fd, buf, 0, 160, 0);
|
|
52
|
+
const line = buf.slice(0, n).toString("utf8").split(/\r?\n/, 1)[0] || "";
|
|
53
|
+
if (line.startsWith("#!") && /\bnode\b/i.test(line)) {
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
} catch {
|
|
57
|
+
return false;
|
|
58
|
+
} finally {
|
|
59
|
+
if (fd !== undefined) {
|
|
60
|
+
closeSync(fd);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function isNativeBin(file) {
|
|
67
|
+
if (!file || !existsSync(file)) {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
try {
|
|
71
|
+
if (statSync(file).isDirectory()) {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
} catch {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
if (isSelf(file) || looksLikeNodeShim(file)) {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function resolveBin(name, envKey) {
|
|
84
|
+
const override = process.env[envKey];
|
|
85
|
+
if (override) {
|
|
86
|
+
return override;
|
|
87
|
+
}
|
|
88
|
+
const dirs = (process.env.PATH || "").split(path.delimiter).filter(Boolean);
|
|
89
|
+
const local = localBinDir();
|
|
90
|
+
if (local) {
|
|
91
|
+
dirs.push(local);
|
|
92
|
+
}
|
|
93
|
+
const exts = WIN32 ? [".exe", ".com", ""] : [""];
|
|
94
|
+
for (const dir of dirs) {
|
|
95
|
+
for (const ext of exts) {
|
|
96
|
+
const candidate = path.join(dir, name + ext);
|
|
97
|
+
if (isNativeBin(candidate)) {
|
|
98
|
+
return candidate;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function spawnEnv() {
|
|
106
|
+
const env = { ...process.env };
|
|
107
|
+
const local = localBinDir();
|
|
108
|
+
if (local && existsSync(local)) {
|
|
109
|
+
const current = env.PATH || env.Path || "";
|
|
110
|
+
const parts = current.split(path.delimiter);
|
|
111
|
+
if (!parts.includes(local)) {
|
|
112
|
+
env.PATH = `${local}${path.delimiter}${current}`;
|
|
113
|
+
if (WIN32) {
|
|
114
|
+
env.Path = env.PATH;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return env;
|
|
119
|
+
}
|
|
9
120
|
|
|
10
121
|
function run(command, args) {
|
|
11
122
|
return spawnSync(command, args, {
|
|
12
123
|
cwd: process.cwd(),
|
|
13
124
|
stdio: "inherit",
|
|
14
|
-
shell:
|
|
15
|
-
env:
|
|
125
|
+
shell: false,
|
|
126
|
+
env: spawnEnv(),
|
|
16
127
|
});
|
|
17
128
|
}
|
|
18
129
|
|
|
@@ -21,42 +132,29 @@ function fail(message) {
|
|
|
21
132
|
process.exit(1);
|
|
22
133
|
}
|
|
23
134
|
|
|
24
|
-
function
|
|
25
|
-
const check = spawnSync("uv", ["--version"], {
|
|
26
|
-
stdio: "ignore",
|
|
27
|
-
shell: process.platform === "win32",
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
if (check.status === 0) {
|
|
31
|
-
return;
|
|
32
|
-
}
|
|
33
|
-
|
|
135
|
+
function missing(name, hint) {
|
|
34
136
|
fail(
|
|
35
137
|
[
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
" macOS/Linux: curl -LsSf https://astral.sh/uv/install.sh | sh",
|
|
39
|
-
' Windows: powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"',
|
|
40
|
-
"",
|
|
41
|
-
"Then rerun:",
|
|
42
|
-
" devcouncil --help",
|
|
138
|
+
`DevCouncil requires the ${name} binary on PATH (or ~/.local/bin).`,
|
|
139
|
+
hint,
|
|
43
140
|
].join("\n")
|
|
44
141
|
);
|
|
45
142
|
}
|
|
46
143
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
144
|
+
const args = process.argv.slice(2);
|
|
145
|
+
|
|
146
|
+
const goBin = resolveBin("devcouncil", "DEVCOUNCIL_BIN");
|
|
147
|
+
if (!goBin) {
|
|
148
|
+
missing(
|
|
149
|
+
"devcouncil",
|
|
150
|
+
WIN32
|
|
151
|
+
? "Build: go -C backend/go_orchestrator build -o %USERPROFILE%\\.local\\bin\\devcouncil.exe ./cmd/devcouncil\nAlso copy it to %USERPROFILE%\\.local\\bin\\dev.exe (scripts/install.ps1)."
|
|
152
|
+
: "Build: go -C backend/go_orchestrator build -o ~/.local/bin/devcouncil ./cmd/devcouncil\nAlso: ln -sf devcouncil ~/.local/bin/dev"
|
|
50
153
|
);
|
|
51
154
|
}
|
|
52
155
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const args = process.argv.slice(2);
|
|
56
|
-
const result = run("uv", ["run", "--project", packageRoot, "devcouncil", ...args]);
|
|
57
|
-
|
|
156
|
+
const result = run(goBin, args);
|
|
58
157
|
if (result.error) {
|
|
59
|
-
fail(`Failed to start
|
|
158
|
+
fail(`Failed to start devcouncil: ${result.error.message}`);
|
|
60
159
|
}
|
|
61
|
-
|
|
62
160
|
process.exit(result.status ?? 1);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "devcouncil",
|
|
3
|
-
"version": "0.2.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.2.2",
|
|
4
|
+
"description": "Code intelligence and deterministic verification for AI coding agents: symbol-level code graph, blast radius, dead-code analysis, and MCP servers for Claude Code, Cursor, and Codex",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://github.com/bharathvbcr/DevCouncil#readme",
|
|
7
7
|
"repository": {
|
|
@@ -17,7 +17,21 @@
|
|
|
17
17
|
"coding-agent",
|
|
18
18
|
"devtools",
|
|
19
19
|
"orchestration",
|
|
20
|
-
"verification"
|
|
20
|
+
"verification",
|
|
21
|
+
"code-graph",
|
|
22
|
+
"code-intelligence",
|
|
23
|
+
"codebase-context",
|
|
24
|
+
"mcp",
|
|
25
|
+
"mcp-server",
|
|
26
|
+
"model-context-protocol",
|
|
27
|
+
"claude-code",
|
|
28
|
+
"cursor",
|
|
29
|
+
"static-analysis",
|
|
30
|
+
"tree-sitter",
|
|
31
|
+
"dead-code",
|
|
32
|
+
"blast-radius",
|
|
33
|
+
"call-graph",
|
|
34
|
+
"repo-map"
|
|
21
35
|
],
|
|
22
36
|
"bin": {
|
|
23
37
|
"devcouncil": "bin/devcouncil.js",
|
|
@@ -25,28 +39,17 @@
|
|
|
25
39
|
},
|
|
26
40
|
"files": [
|
|
27
41
|
"bin/",
|
|
28
|
-
"src/**/*.py",
|
|
29
|
-
"src/**/*.md",
|
|
30
|
-
"src/**/*.png",
|
|
31
|
-
"src/**/*.svg",
|
|
32
|
-
"src/**/*.yaml",
|
|
33
|
-
"src/**/*.mjs",
|
|
34
|
-
"pyproject.toml",
|
|
35
|
-
"uv.lock",
|
|
36
42
|
"README.md",
|
|
37
43
|
"LICENSE"
|
|
38
44
|
],
|
|
39
45
|
"scripts": {
|
|
40
|
-
"install:dev": "uv tool install --force .",
|
|
41
|
-
"install:editable": "uv pip install -e .",
|
|
42
|
-
"dev": "uv run devcouncil",
|
|
43
46
|
"pack:check": "npm pack --dry-run",
|
|
44
|
-
"smoke:wheel": "uv run python scripts/check-wheel-assets.py",
|
|
45
47
|
"smoke:package": "node scripts/npm-runtime-smoke.mjs",
|
|
46
|
-
"
|
|
47
|
-
"
|
|
48
|
-
"
|
|
49
|
-
"
|
|
48
|
+
"smoke:registry": "node scripts/npm-registry-smoke.mjs",
|
|
49
|
+
"check:release": "node scripts/check-release.mjs",
|
|
50
|
+
"check:workflows": "node scripts/check-workflows.mjs",
|
|
51
|
+
"test:scripts": "node --test scripts/check-release.test.mjs scripts/check-workflows.test.mjs",
|
|
52
|
+
"ci:local": "node scripts/ci-local.mjs"
|
|
50
53
|
},
|
|
51
54
|
"engines": {
|
|
52
55
|
"node": ">=18"
|
package/pyproject.toml
DELETED
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
[project]
|
|
2
|
-
name = "devcouncil"
|
|
3
|
-
version = "0.2.0"
|
|
4
|
-
description = "Gated orchestrator for AI-assisted software development"
|
|
5
|
-
readme = "README.md"
|
|
6
|
-
requires-python = ">=3.12"
|
|
7
|
-
dependencies = [
|
|
8
|
-
"typer>=0.12.3",
|
|
9
|
-
"rich>=13.7.1",
|
|
10
|
-
"pydantic>=2.7.1",
|
|
11
|
-
"pyyaml>=6.0.1",
|
|
12
|
-
"sqlmodel>=0.0.19",
|
|
13
|
-
"httpx>=0.27.0",
|
|
14
|
-
"gitpython>=3.1.49",
|
|
15
|
-
"mcp>=1.27.0",
|
|
16
|
-
"gepa>=0.1.1",
|
|
17
|
-
"watchdog>=4.0.0",
|
|
18
|
-
]
|
|
19
|
-
|
|
20
|
-
[project.urls]
|
|
21
|
-
Homepage = "https://github.com/bharathvbcr/DevCouncil"
|
|
22
|
-
Repository = "https://github.com/bharathvbcr/DevCouncil.git"
|
|
23
|
-
Issues = "https://github.com/bharathvbcr/DevCouncil/issues"
|
|
24
|
-
|
|
25
|
-
[project.scripts]
|
|
26
|
-
dev = "devcouncil.cli.main:app"
|
|
27
|
-
devcouncil = "devcouncil.cli.main:app"
|
|
28
|
-
|
|
29
|
-
[dependency-groups]
|
|
30
|
-
dev = [
|
|
31
|
-
"pytest>=8.2.0",
|
|
32
|
-
"ruff>=0.4.4",
|
|
33
|
-
"mypy>=1.10.0",
|
|
34
|
-
"types-pyyaml>=6.0.12.20260408",
|
|
35
|
-
# Used only to exercise the diff↔coverage path end-to-end in tests. DevCouncil
|
|
36
|
-
# never requires coverage at runtime — it uses whatever the target repo provides.
|
|
37
|
-
"coverage>=7.4",
|
|
38
|
-
]
|
|
39
|
-
|
|
40
|
-
[tool.uv]
|
|
41
|
-
# Security floors for transitive dependencies (Dependabot advisories, 2026-06).
|
|
42
|
-
# These are not direct deps; constraint-dependencies pins their minimum patched
|
|
43
|
-
# version in the resolver without adding them to the dependency tree, so they can
|
|
44
|
-
# never resolve back below the fix.
|
|
45
|
-
constraint-dependencies = [
|
|
46
|
-
"cryptography>=48.0.1",
|
|
47
|
-
"starlette>=1.3.1",
|
|
48
|
-
"pyjwt>=2.13.0",
|
|
49
|
-
"python-multipart>=0.0.31",
|
|
50
|
-
"pydantic-settings>=2.14.2",
|
|
51
|
-
"idna>=3.15",
|
|
52
|
-
]
|
|
53
|
-
|
|
54
|
-
[tool.pytest.ini_options]
|
|
55
|
-
testpaths = ["tests"]
|
|
56
|
-
|
|
57
|
-
[build-system]
|
|
58
|
-
requires = ["hatchling"]
|
|
59
|
-
build-backend = "hatchling.build"
|
|
60
|
-
|
|
61
|
-
[tool.hatch.build.targets.wheel.force-include]
|
|
62
|
-
"src/devcouncil/llm/model_defaults.yaml" = "devcouncil/llm/model_defaults.yaml"
|
|
63
|
-
"src/devcouncil/telemetry/model_pricing.yaml" = "devcouncil/telemetry/model_pricing.yaml"
|
|
64
|
-
"src/devcouncil/assets/devcouncil-logo.svg" = "devcouncil/assets/devcouncil-logo.svg"
|
|
65
|
-
"src/devcouncil/assets/devcouncil_logo_premium.png" = "devcouncil/assets/devcouncil_logo_premium.png"
|
|
66
|
-
"src/devcouncil/integrations/opencode_devcouncil_plugin.mjs" = "devcouncil/integrations/opencode_devcouncil_plugin.mjs"
|
|
File without changes
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
from devcouncil.app.state_machine import StateMachine, ProjectPhase
|
|
2
|
-
from devcouncil.app.run_context import RunContext
|
|
3
|
-
from devcouncil.app.orchestrator import Orchestrator
|
|
4
|
-
from devcouncil.app.events import EventBus, bus, EventTypes
|
|
5
|
-
from devcouncil.app.errors import (
|
|
6
|
-
DevCouncilError,
|
|
7
|
-
GatingError,
|
|
8
|
-
ConfigurationError,
|
|
9
|
-
OrchestrationError,
|
|
10
|
-
ExecutionError,
|
|
11
|
-
VerificationError,
|
|
12
|
-
)
|
|
13
|
-
|
|
14
|
-
__all__ = [
|
|
15
|
-
"StateMachine",
|
|
16
|
-
"ProjectPhase",
|
|
17
|
-
"RunContext",
|
|
18
|
-
"Orchestrator",
|
|
19
|
-
"EventBus",
|
|
20
|
-
"bus",
|
|
21
|
-
"EventTypes",
|
|
22
|
-
"DevCouncilError",
|
|
23
|
-
"GatingError",
|
|
24
|
-
"ConfigurationError",
|
|
25
|
-
"OrchestrationError",
|
|
26
|
-
"ExecutionError",
|
|
27
|
-
"VerificationError",
|
|
28
|
-
]
|
|
@@ -1,320 +0,0 @@
|
|
|
1
|
-
"""Typed configuration loader with Pydantic validation.
|
|
2
|
-
|
|
3
|
-
Replaces scattered yaml.safe_load() calls with a single validated config service.
|
|
4
|
-
"""
|
|
5
|
-
|
|
6
|
-
from __future__ import annotations
|
|
7
|
-
|
|
8
|
-
import os
|
|
9
|
-
import shutil
|
|
10
|
-
import subprocess
|
|
11
|
-
from pathlib import Path
|
|
12
|
-
from typing import Dict, List
|
|
13
|
-
|
|
14
|
-
from pydantic import BaseModel, Field
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
class ModelRoleConfig(BaseModel):
|
|
18
|
-
model: str
|
|
19
|
-
temperature: float = 0.0
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
class ModelsConfig(BaseModel):
|
|
23
|
-
provider: str = "openrouter"
|
|
24
|
-
roles: Dict[str, ModelRoleConfig] = Field(default_factory=dict)
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
class ProjectConfig(BaseModel):
|
|
28
|
-
name: str = "devcouncil-project"
|
|
29
|
-
root: str = "."
|
|
30
|
-
default_branch: str = "main"
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
class CommandsConfig(BaseModel):
|
|
34
|
-
test: List[str] = Field(default_factory=list)
|
|
35
|
-
lint: List[str] = Field(default_factory=list)
|
|
36
|
-
typecheck: List[str] = Field(default_factory=list)
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
class VerificationSandboxConfig(BaseModel):
|
|
40
|
-
docker_image: str = "python:3.12-slim"
|
|
41
|
-
docker_setup_commands: List[str] = Field(default_factory=list)
|
|
42
|
-
nix_flake_attr: str | None = None
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
class DiffCoverageConfig(BaseModel):
|
|
46
|
-
"""Diff↔coverage gating: prove the *changed* lines were exercised by tests.
|
|
47
|
-
|
|
48
|
-
``measure`` runs the diff-coverage analysis and records it as evidence (and a
|
|
49
|
-
non-blocking signal) whenever the target repo's coverage tooling is present.
|
|
50
|
-
``enforce`` promotes an unexercised diff to a *blocking* gap. Enforcement is
|
|
51
|
-
off by default so the signal is visible before it ever gates — a passing test
|
|
52
|
-
that does not touch the new code is surfaced first, then teams opt in to
|
|
53
|
-
blocking. ``min_ratio`` of 0.0 means "require at least one changed executable
|
|
54
|
-
line to be exercised"; a higher value demands that fraction of changed lines.
|
|
55
|
-
"""
|
|
56
|
-
|
|
57
|
-
measure: bool = True
|
|
58
|
-
enforce: bool = False
|
|
59
|
-
min_ratio: float = 0.0
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
class VerificationConfig(BaseModel):
|
|
63
|
-
sandbox: VerificationSandboxConfig = Field(default_factory=VerificationSandboxConfig)
|
|
64
|
-
diff_coverage: DiffCoverageConfig = Field(default_factory=DiffCoverageConfig)
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
class GatesConfig(BaseModel):
|
|
68
|
-
require_clean_git_before_task: bool = True
|
|
69
|
-
block_orphan_diffs: bool = True
|
|
70
|
-
block_missing_tests_for_high_requirements: bool = True
|
|
71
|
-
block_dependency_changes_without_approval: bool = True
|
|
72
|
-
block_schema_change_without_migration: bool = True
|
|
73
|
-
block_failed_commands: bool = True
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
class ExecutionConfig(BaseModel):
|
|
77
|
-
default_executor: str = "manual"
|
|
78
|
-
max_repair_attempts: int = 3
|
|
79
|
-
checkpoint_before_each_task: bool = True
|
|
80
|
-
command_timeout: int = 300
|
|
81
|
-
stream_cli_output: bool = False
|
|
82
|
-
# Default lifetime of an MCP task lease. A crashed/disconnected agent's lease
|
|
83
|
-
# auto-expires after this, so the task frees up without a human running force.
|
|
84
|
-
lease_ttl_seconds: int = 1800
|
|
85
|
-
# When true, the post-task coding-CLI hook runs deterministic verification of the
|
|
86
|
-
# active task (and records gaps) instead of only printing a reminder. Off by default
|
|
87
|
-
# so hooks stay fast/cheap unless a team opts in.
|
|
88
|
-
verify_on_post_task: bool = False
|
|
89
|
-
cursor_resume_mode: str = "off"
|
|
90
|
-
coding_cli_probe_order: List[str] = Field(default_factory=list)
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
class PrivacyConfig(BaseModel):
|
|
94
|
-
redact_env_vars: bool = True
|
|
95
|
-
redact_secrets_in_logs: bool = True
|
|
96
|
-
store_prompts_locally: bool = True
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
class AgentFlowIntegrationConfig(BaseModel):
|
|
100
|
-
enabled: bool = False
|
|
101
|
-
trace_path: str = ".devcouncil/logs/traces.jsonl"
|
|
102
|
-
mode: str = "jsonl"
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
class CodeReviewGraphIntegrationConfig(BaseModel):
|
|
106
|
-
enabled: bool = False
|
|
107
|
-
command: str = "code-review-graph"
|
|
108
|
-
optional: bool = True
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
class LiveReviewIntegrationConfig(BaseModel):
|
|
112
|
-
enabled: bool = True
|
|
113
|
-
cards_path: str = ".devcouncil/live/cards"
|
|
114
|
-
signals_path: str = ".devcouncil/live/signals"
|
|
115
|
-
default_client: str = "claude"
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
class WarpIntegrationConfig(BaseModel):
|
|
119
|
-
enabled: bool = False
|
|
120
|
-
command: str = "oz"
|
|
121
|
-
run_mode: str = "local"
|
|
122
|
-
mcp_config_path: str = ".devcouncil/integrations/warp-mcp.json"
|
|
123
|
-
profile: str | None = None
|
|
124
|
-
model: str | None = None
|
|
125
|
-
environment: str | None = None
|
|
126
|
-
share: List[str] = Field(default_factory=list)
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
class OpenCodeIntegrationConfig(BaseModel):
|
|
130
|
-
enabled: bool = False
|
|
131
|
-
config_path: str = "opencode.json"
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
class AntigravityIntegrationConfig(BaseModel):
|
|
135
|
-
enabled: bool = False
|
|
136
|
-
mcp_config_path: str = ".agents/mcp_config.json"
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
class CursorIntegrationConfig(BaseModel):
|
|
140
|
-
enabled: bool = False
|
|
141
|
-
config_path: str = ".cursor/mcp.json"
|
|
142
|
-
hooks_path: str = ".cursor/hooks.json"
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
class AiderIntegrationConfig(BaseModel):
|
|
146
|
-
enabled: bool = False
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
class CliAgentProfileConfig(BaseModel):
|
|
150
|
-
description: str = ""
|
|
151
|
-
timeout_seconds: int | None = None
|
|
152
|
-
prompt_preamble: str = ""
|
|
153
|
-
require_explicit_confirmation: bool = False
|
|
154
|
-
# Per-profile CLI containment overrides. Empty/None reproduce today's behavior
|
|
155
|
-
# exactly so a profile that only sets a prompt preamble is a no-op on the
|
|
156
|
-
# subprocess invocation. ``extra_args`` are appended verbatim to the resolved
|
|
157
|
-
# command, ``permission_mode`` is translated into the right per-CLI flag where
|
|
158
|
-
# known (and overly-permissive flags are dropped for stricter modes), and
|
|
159
|
-
# ``model`` overrides the model flag for CLIs that accept one.
|
|
160
|
-
extra_args: List[str] = Field(default_factory=list)
|
|
161
|
-
permission_mode: str | None = None
|
|
162
|
-
model: str | None = None
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
class CustomCliAgentConfig(BaseModel):
|
|
166
|
-
command: str
|
|
167
|
-
args: List[str] = Field(default_factory=list)
|
|
168
|
-
input_mode: str = "stdin"
|
|
169
|
-
prompt_arg: str | None = None
|
|
170
|
-
timeout_seconds: int | None = None
|
|
171
|
-
env: Dict[str, str] = Field(default_factory=dict)
|
|
172
|
-
display_name: str | None = None
|
|
173
|
-
kind: str = "custom"
|
|
174
|
-
supports_mcp: bool = False
|
|
175
|
-
supports_diff_review: bool = False
|
|
176
|
-
default_profile: str = "default"
|
|
177
|
-
help_command: List[str] = Field(default_factory=list)
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
class CliAgentsIntegrationConfig(BaseModel):
|
|
181
|
-
enabled: bool = True
|
|
182
|
-
profiles: Dict[str, CliAgentProfileConfig] = Field(default_factory=dict)
|
|
183
|
-
agents: Dict[str, CustomCliAgentConfig] = Field(default_factory=dict)
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
class McpIntegrationConfig(BaseModel):
|
|
187
|
-
write_task_scope_to_config: bool = False
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
class IntegrationsConfig(BaseModel):
|
|
191
|
-
mcp: McpIntegrationConfig = Field(default_factory=McpIntegrationConfig)
|
|
192
|
-
agent_flow: AgentFlowIntegrationConfig = Field(default_factory=AgentFlowIntegrationConfig)
|
|
193
|
-
code_review_graph: CodeReviewGraphIntegrationConfig = Field(default_factory=CodeReviewGraphIntegrationConfig)
|
|
194
|
-
live_review: LiveReviewIntegrationConfig = Field(default_factory=LiveReviewIntegrationConfig)
|
|
195
|
-
cursor: CursorIntegrationConfig = Field(default_factory=CursorIntegrationConfig)
|
|
196
|
-
aider: AiderIntegrationConfig = Field(default_factory=AiderIntegrationConfig)
|
|
197
|
-
antigravity: AntigravityIntegrationConfig = Field(default_factory=AntigravityIntegrationConfig)
|
|
198
|
-
warp: WarpIntegrationConfig = Field(default_factory=WarpIntegrationConfig)
|
|
199
|
-
opencode: OpenCodeIntegrationConfig = Field(default_factory=OpenCodeIntegrationConfig)
|
|
200
|
-
cli_agents: CliAgentsIntegrationConfig = Field(default_factory=CliAgentsIntegrationConfig)
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
class ProviderConfig(BaseModel):
|
|
204
|
-
sort: str = "price"
|
|
205
|
-
allow_fallbacks: bool = True
|
|
206
|
-
require_parameters: bool = True
|
|
207
|
-
data_collection: str = "deny"
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
class DevCouncilConfig(BaseModel):
|
|
211
|
-
"""Top-level validated configuration."""
|
|
212
|
-
|
|
213
|
-
project: ProjectConfig = Field(default_factory=ProjectConfig)
|
|
214
|
-
models: ModelsConfig = Field(default_factory=ModelsConfig)
|
|
215
|
-
provider: ProviderConfig = Field(default_factory=ProviderConfig)
|
|
216
|
-
commands: CommandsConfig = Field(default_factory=CommandsConfig)
|
|
217
|
-
gates: GatesConfig = Field(default_factory=GatesConfig)
|
|
218
|
-
execution: ExecutionConfig = Field(default_factory=ExecutionConfig)
|
|
219
|
-
verification: VerificationConfig = Field(default_factory=VerificationConfig)
|
|
220
|
-
privacy: PrivacyConfig = Field(default_factory=PrivacyConfig)
|
|
221
|
-
integrations: IntegrationsConfig = Field(default_factory=IntegrationsConfig)
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
def load_config(project_root: Path = Path(".")) -> DevCouncilConfig:
|
|
225
|
-
"""Load and validate .devcouncil/config.yaml.
|
|
226
|
-
|
|
227
|
-
Returns DevCouncilConfig with defaults for any missing fields.
|
|
228
|
-
Raises FileNotFoundError if config doesn't exist.
|
|
229
|
-
"""
|
|
230
|
-
import yaml # type: ignore[import-untyped]
|
|
231
|
-
|
|
232
|
-
config_path = project_root / ".devcouncil" / "config.yaml"
|
|
233
|
-
if not config_path.exists():
|
|
234
|
-
raise FileNotFoundError(f"Config not found at {config_path}. Run 'dev init' first.")
|
|
235
|
-
|
|
236
|
-
with open(config_path, encoding="utf-8") as f:
|
|
237
|
-
try:
|
|
238
|
-
raw = yaml.safe_load(f) or {}
|
|
239
|
-
except yaml.YAMLError as exc:
|
|
240
|
-
raise ValueError(
|
|
241
|
-
f"Invalid YAML in {config_path}: {exc}. Fix the syntax or re-run 'dev init'."
|
|
242
|
-
) from exc
|
|
243
|
-
|
|
244
|
-
return DevCouncilConfig.model_validate(raw)
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
def provider_api_key_env_var(provider: str = "openrouter") -> str:
|
|
248
|
-
normalized = provider.strip().lower().replace("-", "").replace("_", "")
|
|
249
|
-
env_map = {
|
|
250
|
-
"openrouter": "OPENROUTER_API_KEY",
|
|
251
|
-
"vertexai": "VERTEXAI_ACCESS_TOKEN",
|
|
252
|
-
"doubleword": "DOUBLEWORD_API_KEY",
|
|
253
|
-
"ollama": "OLLAMA_API_KEY",
|
|
254
|
-
"openai": "OPENAI_API_KEY",
|
|
255
|
-
"anthropic": "ANTHROPIC_API_KEY",
|
|
256
|
-
}
|
|
257
|
-
return env_map.get(normalized, f"{normalized.upper()}_API_KEY")
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
def _normalized_provider_name(provider: str) -> str:
|
|
261
|
-
return provider.strip().lower().replace("-", "").replace("_", "")
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
def load_local_secrets(project_root: Path = Path(".")) -> Dict[str, str]:
|
|
265
|
-
secrets_path = project_root / ".devcouncil" / "secrets.env"
|
|
266
|
-
if not secrets_path.exists():
|
|
267
|
-
return {}
|
|
268
|
-
|
|
269
|
-
secrets: Dict[str, str] = {}
|
|
270
|
-
for line in secrets_path.read_text(encoding="utf-8").splitlines():
|
|
271
|
-
stripped = line.strip()
|
|
272
|
-
if not stripped or stripped.startswith("#") or "=" not in stripped:
|
|
273
|
-
continue
|
|
274
|
-
key, value = stripped.split("=", 1)
|
|
275
|
-
secrets[key.strip()] = value.strip().strip('"').strip("'")
|
|
276
|
-
return secrets
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
def get_gcloud_access_token() -> str | None:
|
|
280
|
-
executable = shutil.which("gcloud")
|
|
281
|
-
if not executable:
|
|
282
|
-
return None
|
|
283
|
-
try:
|
|
284
|
-
token = subprocess.check_output(
|
|
285
|
-
[executable, "auth", "print-access-token"],
|
|
286
|
-
stderr=subprocess.STDOUT,
|
|
287
|
-
text=True,
|
|
288
|
-
encoding="utf-8",
|
|
289
|
-
errors="replace",
|
|
290
|
-
timeout=10,
|
|
291
|
-
).strip()
|
|
292
|
-
except Exception:
|
|
293
|
-
return None
|
|
294
|
-
return token or None
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
def get_api_key(provider: str = "openrouter", project_root: Path = Path(".")) -> str:
|
|
298
|
-
"""Retrieve the API key for the configured provider from environment.
|
|
299
|
-
|
|
300
|
-
Raises ValueError if not found.
|
|
301
|
-
"""
|
|
302
|
-
env_var = provider_api_key_env_var(provider)
|
|
303
|
-
key = os.environ.get(env_var) or load_local_secrets(project_root).get(env_var)
|
|
304
|
-
if not key and _normalized_provider_name(provider) == "vertexai":
|
|
305
|
-
key = get_gcloud_access_token()
|
|
306
|
-
if not key and _normalized_provider_name(provider) == "ollama":
|
|
307
|
-
# Ollama is a local server and needs no API key; an explicitly-set
|
|
308
|
-
# OLLAMA_API_KEY still flows through above if present.
|
|
309
|
-
return ""
|
|
310
|
-
if not key:
|
|
311
|
-
extra = (
|
|
312
|
-
" You can also authenticate with 'gcloud auth login' for vertexai."
|
|
313
|
-
if _normalized_provider_name(provider) == "vertexai"
|
|
314
|
-
else ""
|
|
315
|
-
)
|
|
316
|
-
raise ValueError(
|
|
317
|
-
f"API key not found. Set {env_var} in your environment or run 'dev setup'. "
|
|
318
|
-
f"Provider: {provider}.{extra}"
|
|
319
|
-
)
|
|
320
|
-
return key
|