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.
Files changed (205) hide show
  1. package/README.md +191 -253
  2. package/bin/devcouncil.js +130 -32
  3. package/package.json +6 -17
  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
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 { existsSync } = require("node:fs");
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 packageRoot = path.resolve(__dirname, "..");
8
- const pyproject = path.join(packageRoot, "pyproject.toml");
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: process.platform === "win32",
15
- env: process.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 ensureUv() {
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
- "DevCouncil requires uv to run from the npm package.",
37
- "Install uv first:",
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
- if (!existsSync(pyproject)) {
48
- fail(
49
- "DevCouncil npm package is missing pyproject.toml. Reinstall the package and try again."
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
- ensureUv();
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 DevCouncil: ${result.error.message}`);
158
+ fail(`Failed to start devcouncil: ${result.error.message}`);
60
159
  }
61
-
62
160
  process.exit(result.status ?? 1);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "devcouncil",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Gated orchestrator for AI-assisted software development",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://github.com/bharathvbcr/DevCouncil#readme",
@@ -25,28 +25,17 @@
25
25
  },
26
26
  "files": [
27
27
  "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
28
  "README.md",
37
29
  "LICENSE"
38
30
  ],
39
31
  "scripts": {
40
- "install:dev": "uv tool install --force .",
41
- "install:editable": "uv pip install -e .",
42
- "dev": "uv run devcouncil",
43
32
  "pack:check": "npm pack --dry-run",
44
- "smoke:wheel": "uv run python scripts/check-wheel-assets.py",
45
33
  "smoke:package": "node scripts/npm-runtime-smoke.mjs",
46
- "test": "uv run pytest",
47
- "lint": "uv run ruff check .",
48
- "typecheck": "uv run mypy src",
49
- "check": "uv run ruff check . && uv run mypy src && uv run pytest"
34
+ "smoke:registry": "node scripts/npm-registry-smoke.mjs",
35
+ "check:release": "node scripts/check-release.mjs",
36
+ "check:workflows": "node scripts/check-workflows.mjs",
37
+ "test:scripts": "node --test scripts/check-release.test.mjs scripts/check-workflows.test.mjs",
38
+ "ci:local": "node scripts/ci-local.mjs"
50
39
  },
51
40
  "engines": {
52
41
  "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,4 +0,0 @@
1
- from devcouncil.cli.main import app
2
-
3
- if __name__ == "__main__":
4
- app()
@@ -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
@@ -1,23 +0,0 @@
1
- class DevCouncilError(Exception):
2
- """Base exception for all DevCouncil errors."""
3
- pass
4
-
5
- class GatingError(DevCouncilError):
6
- """Raised when a task or plan fails a mandatory gate."""
7
- pass
8
-
9
- class ConfigurationError(DevCouncilError):
10
- """Raised when the project configuration is invalid or missing."""
11
- pass
12
-
13
- class OrchestrationError(DevCouncilError):
14
- """Raised when the orchestrator encounters an unexpected state."""
15
- pass
16
-
17
- class ExecutionError(DevCouncilError):
18
- """Raised when an external or internal executor fails to run a task."""
19
- pass
20
-
21
- class VerificationError(DevCouncilError):
22
- """Raised when the verifier fails to run its checks properly."""
23
- pass