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
@@ -1,374 +0,0 @@
1
- """Best-effort software-composition analysis (dependency vulnerability awareness).
2
-
3
- DevCouncil does not bundle a vulnerability database and must never reach the
4
- network on its own initiative. This module is a thin, *offline-safe* wrapper
5
- around whatever auditors the developer already has installed (``pip-audit``,
6
- ``npm audit``, ``osv-scanner``): it detects the auditor + the project's
7
- lockfiles, runs the local tool with a bounded timeout and a sanitized subprocess
8
- environment, and parses the output into a structured list of dependency risks.
9
-
10
- Design contract:
11
- - **Never raises.** Every public entry point swallows tool/parse/OS errors and
12
- degrades to an empty result, so a missing tool or malformed output can never
13
- break ``dev map`` / prompt building / CI scaffolding.
14
- - **Opt-in / local-only by default.** Nothing here runs unless a caller asks for
15
- it; ``ScaScanner.scan`` is the only thing that shells out.
16
- - **Injectable runner.** ``ScaScanner`` takes an ``auditor_runner`` callable so
17
- tests (and offline environments) can feed canned auditor output without any
18
- network access or installed tools.
19
- """
20
-
21
- from __future__ import annotations
22
-
23
- import json
24
- import shutil
25
- import subprocess
26
- from collections.abc import Callable
27
- from dataclasses import asdict, dataclass
28
- from pathlib import Path
29
-
30
- from devcouncil.utils.subprocess_env import clean_subprocess_env
31
-
32
- # Default per-auditor timeout. Bounded so a slow/hung auditor can't stall the map.
33
- DEFAULT_TIMEOUT = 60
34
-
35
-
36
- @dataclass(frozen=True)
37
- class DependencyRisk:
38
- """A single dependency vulnerability finding (auditor-agnostic shape)."""
39
-
40
- package: str
41
- installed_version: str
42
- severity: str
43
- advisory_id: str
44
- summary: str
45
-
46
- def as_dict(self) -> dict[str, str]:
47
- return asdict(self)
48
-
49
-
50
- @dataclass(frozen=True)
51
- class AuditorResult:
52
- """Raw output of one auditor invocation; ``returncode`` < 0 means it didn't run."""
53
-
54
- returncode: int
55
- stdout: str
56
- stderr: str
57
-
58
-
59
- # An auditor runner takes the auditor's argv and the project root, and returns the
60
- # captured result. Injected so tests need no installed tools or network access.
61
- AuditorRunner = Callable[[list[str], Path], AuditorResult]
62
-
63
-
64
- @dataclass(frozen=True)
65
- class _Auditor:
66
- name: str # logical auditor name (pip-audit / npm / osv-scanner)
67
- executable: str # the executable to look for on PATH
68
- stack: str # which stack it covers (python / node)
69
- # Lockfiles whose presence makes this auditor relevant for the repo.
70
- lockfiles: tuple[str, ...]
71
-
72
-
73
- # Ordered so the most specific / preferred auditor for each stack comes first.
74
- _AUDITORS: tuple[_Auditor, ...] = (
75
- _Auditor(
76
- name="pip-audit",
77
- executable="pip-audit",
78
- stack="python",
79
- lockfiles=("uv.lock", "requirements.txt", "poetry.lock"),
80
- ),
81
- _Auditor(
82
- name="npm",
83
- executable="npm",
84
- stack="node",
85
- lockfiles=("package-lock.json", "yarn.lock", "pnpm-lock.yaml"),
86
- ),
87
- _Auditor(
88
- name="osv-scanner",
89
- executable="osv-scanner",
90
- stack="any",
91
- lockfiles=(
92
- "uv.lock",
93
- "requirements.txt",
94
- "poetry.lock",
95
- "package-lock.json",
96
- "yarn.lock",
97
- "pnpm-lock.yaml",
98
- "go.sum",
99
- "Cargo.lock",
100
- ),
101
- ),
102
- )
103
-
104
-
105
- def _default_runner(timeout: int) -> AuditorRunner:
106
- """Real subprocess runner: bounded timeout + sanitized env; never raises."""
107
-
108
- def run(argv: list[str], project_root: Path) -> AuditorResult:
109
- try:
110
- completed = subprocess.run(
111
- argv,
112
- cwd=project_root,
113
- capture_output=True,
114
- text=True,
115
- encoding="utf-8",
116
- errors="replace",
117
- timeout=timeout,
118
- env=clean_subprocess_env(),
119
- )
120
- except subprocess.TimeoutExpired:
121
- return AuditorResult(returncode=-1, stdout="", stderr="timed out")
122
- except (FileNotFoundError, OSError) as exc:
123
- return AuditorResult(returncode=-1, stdout="", stderr=str(exc))
124
- return AuditorResult(
125
- returncode=completed.returncode,
126
- stdout=completed.stdout or "",
127
- stderr=completed.stderr or "",
128
- )
129
-
130
- return run
131
-
132
-
133
- class ScaScanner:
134
- """Detects locally-available auditors + lockfiles and runs them best-effort.
135
-
136
- Pass ``auditor_runner`` to inject the subprocess behaviour (tests do this to
137
- return canned auditor output offline). When omitted, a real bounded-timeout,
138
- clean-env subprocess runner is used.
139
- """
140
-
141
- def __init__(
142
- self,
143
- project_root: Path,
144
- *,
145
- auditor_runner: AuditorRunner | None = None,
146
- timeout: int = DEFAULT_TIMEOUT,
147
- which: Callable[[str], str | None] = shutil.which,
148
- ):
149
- self.project_root = Path(project_root)
150
- self.timeout = timeout
151
- self._which = which
152
- # When a runner is injected (tests / offline), the runner itself decides
153
- # whether a tool "exists"; we skip the PATH gate so canned output flows.
154
- self._injected = auditor_runner is not None
155
- self._runner: AuditorRunner = auditor_runner or _default_runner(timeout)
156
-
157
- # -- detection -----------------------------------------------------------
158
-
159
- def _has_lockfile(self, auditor: _Auditor) -> bool:
160
- return any((self.project_root / name).exists() for name in auditor.lockfiles)
161
-
162
- def _is_runnable(self, auditor: _Auditor) -> bool:
163
- """Relevant to the repo (lockfile present) and runnable (on PATH or injected)."""
164
- if not self._has_lockfile(auditor):
165
- return False
166
- return self._injected or self._which(auditor.executable) is not None
167
-
168
- def available_auditors(self) -> list[str]:
169
- """Names of auditors that are both runnable AND relevant to this repo.
170
-
171
- An auditor counts as runnable when its executable is on PATH (real
172
- installs) OR when a custom runner is injected (tests/offline).
173
- """
174
- seen: set[str] = set()
175
- ordered: list[str] = []
176
- for auditor in _AUDITORS:
177
- if self._is_runnable(auditor) and auditor.name not in seen:
178
- seen.add(auditor.name)
179
- ordered.append(auditor.name)
180
- return ordered
181
-
182
- # -- scanning ------------------------------------------------------------
183
-
184
- def scan(self) -> list[DependencyRisk]:
185
- """Run every available auditor and merge their findings. Never raises."""
186
- risks: list[DependencyRisk] = []
187
- seen: set[tuple[str, str, str]] = set()
188
- for auditor in _AUDITORS:
189
- if not self._is_runnable(auditor):
190
- continue
191
- try:
192
- result = self._runner(self._argv_for(auditor), self.project_root)
193
- parsed = self._parse(auditor, result)
194
- except Exception:
195
- # Best-effort contract: a broken auditor/parse must not propagate.
196
- continue
197
- for risk in parsed:
198
- key = (risk.package, risk.installed_version, risk.advisory_id)
199
- if key in seen:
200
- continue
201
- seen.add(key)
202
- risks.append(risk)
203
- return risks
204
-
205
- @staticmethod
206
- def _argv_for(auditor: _Auditor) -> list[str]:
207
- if auditor.name == "pip-audit":
208
- return ["pip-audit", "--format", "json"]
209
- if auditor.name == "npm":
210
- return ["npm", "audit", "--json"]
211
- if auditor.name == "osv-scanner":
212
- return ["osv-scanner", "--format", "json", "."]
213
- return [auditor.executable]
214
-
215
- def _parse(self, auditor: _Auditor, result: AuditorResult) -> list[DependencyRisk]:
216
- if result.returncode < 0 or not result.stdout.strip():
217
- return []
218
- try:
219
- data = json.loads(result.stdout)
220
- except (json.JSONDecodeError, ValueError):
221
- return []
222
- if auditor.name == "pip-audit":
223
- return _parse_pip_audit(data)
224
- if auditor.name == "npm":
225
- return _parse_npm_audit(data)
226
- if auditor.name == "osv-scanner":
227
- return _parse_osv_scanner(data)
228
- return []
229
-
230
-
231
- # ----------------------------------------------------------------------------
232
- # Per-auditor parsers. Each is defensive: unknown shapes yield no risks.
233
- # ----------------------------------------------------------------------------
234
-
235
-
236
- def _coerce_str(value: object, default: str = "") -> str:
237
- if value is None:
238
- return default
239
- if isinstance(value, str):
240
- return value
241
- return str(value)
242
-
243
-
244
- def _parse_pip_audit(data: object) -> list[DependencyRisk]:
245
- """pip-audit ``--format json`` -> dependencies[].vulns[]."""
246
- risks: list[DependencyRisk] = []
247
- deps: object
248
- if isinstance(data, dict):
249
- deps = data.get("dependencies", [])
250
- elif isinstance(data, list):
251
- deps = data # older pip-audit emitted a bare list
252
- else:
253
- return []
254
- if not isinstance(deps, list):
255
- return []
256
- for dep in deps:
257
- if not isinstance(dep, dict):
258
- continue
259
- name = _coerce_str(dep.get("name"))
260
- version = _coerce_str(dep.get("version"))
261
- vulns = dep.get("vulns") or []
262
- if not isinstance(vulns, list):
263
- continue
264
- for vuln in vulns:
265
- if not isinstance(vuln, dict):
266
- continue
267
- risks.append(
268
- DependencyRisk(
269
- package=name,
270
- installed_version=version,
271
- severity=_coerce_str(vuln.get("severity"), "unknown") or "unknown",
272
- advisory_id=_coerce_str(vuln.get("id"), "UNKNOWN") or "UNKNOWN",
273
- summary=_coerce_str(vuln.get("description") or vuln.get("summary")),
274
- )
275
- )
276
- return risks
277
-
278
-
279
- def _parse_npm_audit(data: object) -> list[DependencyRisk]:
280
- """npm audit ``--json`` (npm v7+) -> vulnerabilities{ name: {...} }."""
281
- if not isinstance(data, dict):
282
- return []
283
- vulnerabilities = data.get("vulnerabilities")
284
- if not isinstance(vulnerabilities, dict):
285
- return []
286
- risks: list[DependencyRisk] = []
287
- for name, info in vulnerabilities.items():
288
- if not isinstance(info, dict):
289
- continue
290
- severity = _coerce_str(info.get("severity"), "unknown") or "unknown"
291
- version = _coerce_str(info.get("range"))
292
- via = info.get("via") or []
293
- advisory_id = "UNKNOWN"
294
- summary = ""
295
- if isinstance(via, list):
296
- for entry in via:
297
- if isinstance(entry, dict):
298
- source = entry.get("source") or entry.get("url")
299
- advisory_id = _coerce_str(source, "UNKNOWN") or "UNKNOWN"
300
- summary = _coerce_str(entry.get("title"))
301
- break
302
- risks.append(
303
- DependencyRisk(
304
- package=_coerce_str(name),
305
- installed_version=version,
306
- severity=severity,
307
- advisory_id=advisory_id,
308
- summary=summary,
309
- )
310
- )
311
- return risks
312
-
313
-
314
- def _parse_osv_scanner(data: object) -> list[DependencyRisk]:
315
- """osv-scanner ``--format json`` -> results[].packages[].vulnerabilities[]."""
316
- if not isinstance(data, dict):
317
- return []
318
- results = data.get("results")
319
- if not isinstance(results, list):
320
- return []
321
- risks: list[DependencyRisk] = []
322
- for result in results:
323
- if not isinstance(result, dict):
324
- continue
325
- packages = result.get("packages") or []
326
- if not isinstance(packages, list):
327
- continue
328
- for package in packages:
329
- if not isinstance(package, dict):
330
- continue
331
- pkg_info = package.get("package") or {}
332
- name = _coerce_str(pkg_info.get("name")) if isinstance(pkg_info, dict) else ""
333
- version = _coerce_str(pkg_info.get("version")) if isinstance(pkg_info, dict) else ""
334
- vulns = package.get("vulnerabilities") or []
335
- if not isinstance(vulns, list):
336
- continue
337
- for vuln in vulns:
338
- if not isinstance(vuln, dict):
339
- continue
340
- risks.append(
341
- DependencyRisk(
342
- package=name,
343
- installed_version=version,
344
- severity=_osv_severity(vuln),
345
- advisory_id=_coerce_str(vuln.get("id"), "UNKNOWN") or "UNKNOWN",
346
- summary=_coerce_str(vuln.get("summary") or vuln.get("details")),
347
- )
348
- )
349
- return risks
350
-
351
-
352
- def _osv_severity(vuln: dict) -> str:
353
- severity = vuln.get("severity")
354
- if isinstance(severity, list) and severity:
355
- first = severity[0]
356
- if isinstance(first, dict):
357
- return _coerce_str(first.get("type") or first.get("score"), "unknown") or "unknown"
358
- if isinstance(severity, str) and severity:
359
- return severity
360
- return "unknown"
361
-
362
-
363
- def scan_dependency_risks(
364
- project_root: Path,
365
- *,
366
- auditor_runner: AuditorRunner | None = None,
367
- timeout: int = DEFAULT_TIMEOUT,
368
- ) -> list[dict[str, str]]:
369
- """Convenience entry point: returns dependency risks as plain dicts. Never raises."""
370
- try:
371
- scanner = ScaScanner(project_root, auditor_runner=auditor_runner, timeout=timeout)
372
- return [risk.as_dict() for risk in scanner.scan()]
373
- except Exception:
374
- return []
File without changes
@@ -1,32 +0,0 @@
1
- from devcouncil.artifacts.graph import ArtifactGraph
2
-
3
- class GitHubCheckGenerator:
4
- """Generates GitHub Checks API payloads."""
5
-
6
- @staticmethod
7
- def generate(graph: ArtifactGraph) -> dict:
8
- summary = graph.coverage_summary()
9
- blocking_gaps = graph.blocking_gaps()
10
-
11
- status = "completed"
12
- conclusion = "failure" if summary["blocking_gaps"] > 0 else "success"
13
-
14
- text = f"**Requirements**: {summary['total_requirements']} | "
15
- text += f"**Tasks**: {summary['total_tasks']} | "
16
- text += f"**Gaps**: {summary['blocking_gaps']} blocking\n\n"
17
-
18
- if blocking_gaps:
19
- text += "### Blocking Gaps\n"
20
- for gap in blocking_gaps:
21
- text += f"- **{gap.id}**: {gap.description}\n"
22
-
23
- return {
24
- "name": "DevCouncil Verification",
25
- "status": status,
26
- "conclusion": conclusion,
27
- "output": {
28
- "title": f"DevCouncil: {conclusion.capitalize()}",
29
- "summary": f"Found {summary['blocking_gaps']} blocking gaps.",
30
- "text": text
31
- }
32
- }
@@ -1,30 +0,0 @@
1
- import json
2
- from devcouncil.artifacts.graph import ArtifactGraph
3
-
4
- class JsonReportGenerator:
5
- """Generates a JSON evidence report."""
6
-
7
- @staticmethod
8
- def generate(graph: ArtifactGraph, live_review: dict | None = None) -> str:
9
- summary = graph.coverage_summary()
10
- live_blockers = len((live_review or {}).get("blocking_cards", []))
11
-
12
- # Three honest states (see markdown_report for rationale):
13
- # blocked - positive evidence of a problem.
14
- # incomplete - nothing failing, but not every AC has passing evidence.
15
- # passed - no blocking gaps and every AC proven.
16
- if summary["blocking_gaps"] > 0 or live_blockers > 0:
17
- verdict = "blocked"
18
- elif summary["ac_without_evidence"] > 0:
19
- verdict = "incomplete"
20
- else:
21
- verdict = "passed"
22
- report = {
23
- "verdict": verdict,
24
- "coverage_summary": summary,
25
- "blocking_gaps": [g.model_dump() for g in graph.blocking_gaps()]
26
- }
27
- if live_review is not None:
28
- report["live_review"] = live_review
29
-
30
- return json.dumps(report, indent=2)
@@ -1,83 +0,0 @@
1
- from devcouncil.artifacts.graph import ArtifactGraph
2
-
3
- class MarkdownReportGenerator:
4
- """Generates a Markdown evidence report."""
5
-
6
- MAX_INLINE_GAPS = 25
7
-
8
- @staticmethod
9
- def generate(graph: ArtifactGraph, live_review: dict | None = None) -> str:
10
- summary = graph.coverage_summary()
11
- live_blockers = (live_review or {}).get("blocking_cards", [])
12
-
13
- unverified_ac = summary["ac_without_evidence"]
14
-
15
- md_output = "# DevCouncil Report\n\n"
16
- md_output += "## Verdict\n"
17
- # Three honest states:
18
- # Blocked - positive evidence of a problem (blocking gaps / live blockers).
19
- # Incomplete - nothing is failing, but not every acceptance criterion has
20
- # passing evidence yet (un-run, or could not be verified). NOT
21
- # a failure — distinguishing this from Blocked is what keeps the
22
- # "blocked" signal trustworthy (no false negatives on correct work).
23
- # Passed - no blocking gaps and every acceptance criterion is proven.
24
- if summary["blocking_gaps"] > 0 or live_blockers:
25
- parts = []
26
- if summary["blocking_gaps"] > 0:
27
- parts.append(f"{summary['blocking_gaps']} high-severity gap(s)")
28
- if live_blockers:
29
- parts.append(f"{len(live_blockers)} live-review blocker(s)")
30
- md_output += f"**Blocked**: {', '.join(parts)} remain.\n\n"
31
- elif unverified_ac > 0:
32
- md_output += (
33
- f"**Incomplete**: nothing is failing, but {unverified_ac} acceptance "
34
- "criterion(s) lack passing evidence (un-run or unverifiable). Not ready "
35
- "for release.\n\n"
36
- )
37
- else:
38
- md_output += "**Passed**: Ready for release.\n\n"
39
-
40
- md_output += "## Coverage Summary\n"
41
- md_output += f"- **Requirements**: {summary['total_requirements']} ({summary['requirements_without_tasks']} unmapped)\n"
42
- md_output += f"- **Tasks**: {summary['total_tasks']} ({summary['tasks_without_requirements']} orphaned)\n"
43
- md_output += f"- **Evidence**: {summary['total_ac'] - summary['ac_without_evidence']}/{summary['total_ac']} AC verified\n\n"
44
-
45
- md_output += "## Requirements Coverage Table\n"
46
- md_output += "| Requirement | Task Mapping | Status |\n"
47
- md_output += "|---|---|---|\n"
48
-
49
- for req in graph.requirements.values():
50
- linked_tasks = [t for t in graph.tasks.values() if req.id in t.requirement_ids]
51
- task_str = ", ".join([t.id for t in linked_tasks]) if linked_tasks else "*None*"
52
- status_str = "Covered" if linked_tasks else "**Unmapped**"
53
- md_output += f"| {req.id} {req.title} | {task_str} | {status_str} |\n"
54
-
55
- md_output += "\n## Blocking Gaps\n"
56
- blocking_gaps = graph.blocking_gaps()
57
- if not blocking_gaps:
58
- md_output += "None.\n"
59
- else:
60
- for gap in blocking_gaps[:MarkdownReportGenerator.MAX_INLINE_GAPS]:
61
- md_output += f"### {gap.id}: {gap.description}\n"
62
- md_output += f"**Recommended fix**: {gap.recommended_fix}\n\n"
63
- if len(blocking_gaps) > MarkdownReportGenerator.MAX_INLINE_GAPS:
64
- remaining = len(blocking_gaps) - MarkdownReportGenerator.MAX_INLINE_GAPS
65
- md_output += f"_Omitted {remaining} additional blocking gap(s). Use JSON output for the full list._\n"
66
-
67
- if live_review is not None:
68
- md_output += "\n## Live Review\n"
69
- cards = live_review.get("cards", {})
70
- md_output += f"- **Pending signals**: {live_review.get('pending_signals', 0)}\n"
71
- md_output += f"- **Open cards**: {cards.get('open', 0)}\n"
72
- md_output += f"- **Open critical cards**: {cards.get('critical_open', 0)}\n"
73
- if not live_blockers:
74
- md_output += "- **Blocking cards in scope**: None.\n"
75
- else:
76
- md_output += "\n### Blocking Live-Review Cards\n"
77
- for card in live_blockers[:MarkdownReportGenerator.MAX_INLINE_GAPS]:
78
- md_output += f"- **{card['id']}**"
79
- if card.get("task_id"):
80
- md_output += f" (`{card['task_id']}`)"
81
- md_output += f": {card['summary']}\n"
82
-
83
- return md_output
@@ -1,14 +0,0 @@
1
- from devcouncil.artifacts.graph import ArtifactGraph
2
- from devcouncil.reporting.markdown_report import MarkdownReportGenerator
3
- from devcouncil.reporting.json_report import JsonReportGenerator
4
-
5
- class ReportBuilder:
6
- """Builds reports in various formats from the artifact graph."""
7
-
8
- @staticmethod
9
- def build_markdown(graph: ArtifactGraph, live_review: dict | None = None) -> str:
10
- return MarkdownReportGenerator.generate(graph, live_review=live_review)
11
-
12
- @staticmethod
13
- def build_json(graph: ArtifactGraph, live_review: dict | None = None) -> str:
14
- return JsonReportGenerator.generate(graph, live_review=live_review)
@@ -1,19 +0,0 @@
1
- """DevCouncil skills library."""
2
-
3
- from devcouncil.skills.registry import (
4
- Skill,
5
- get_skill,
6
- load_skills,
7
- render_preamble,
8
- scaffold_skills,
9
- select_skills,
10
- )
11
-
12
- __all__ = [
13
- "Skill",
14
- "get_skill",
15
- "load_skills",
16
- "render_preamble",
17
- "scaffold_skills",
18
- "select_skills",
19
- ]
@@ -1,46 +0,0 @@
1
- # DevCouncil skills library
2
-
3
- Each `*.md` file here (except this README) is a **skill**: reusable guidance that
4
- DevCouncil selects for a goal/repo, embeds into `dev prompt` output, and scaffolds
5
- into a target repo's `.claude/skills/<name>/SKILL.md`.
6
-
7
- A skill is a markdown file with YAML frontmatter:
8
-
9
- ```markdown
10
- ---
11
- name: my-domain # required — unique slug; files without a name are ignored
12
- title: My Domain Intake # optional human title
13
- description: One-line directive shown in `dev skills` and embedded into prompts.
14
- always: false # true = always selected (only core-engineering uses this)
15
- triggers: # how this skill is auto-selected (omit for always-on)
16
- keywords: [foo, bar] # matched against the goal text (case-insensitive substring)
17
- globs: ["*.foo", "build.bar"] # matched against repo file basenames
18
- ---
19
-
20
- # Body
21
-
22
- The full guidance the agent reads. For domain skills, write a "senior-dev intake":
23
- what current versions, deprecations, recommended libraries, official guidelines, and
24
- CLI/build tools to confirm *before* writing code.
25
- ```
26
-
27
- ## Adding a skill
28
-
29
- 1. Create `<name>.md` with the frontmatter above.
30
- 2. Choose triggers: `keywords` (goal text) and/or `globs` (repo files). Both are ORed.
31
- 3. Verify: `dev skills` lists it; `dev skills show <name>` prints it.
32
-
33
- Selection is keyword-based inside `dev prompt` (fast, no repo walk) and
34
- keyword-plus-file-based for `dev skills` / `dev skills scaffold` / init scaffolding.
35
- Keep `description` actionable — it is what the agent sees inline in the task prompt.
36
-
37
- ## Repo-local skills
38
-
39
- You don't have to edit this packaged library to add a skill. Drop your own
40
- `SKILL.md` (same frontmatter) into a project's **`.claude/skills/<name>/SKILL.md`**
41
- (or `.devcouncil/skills/*.md`) and DevCouncil discovers it automatically — it shows
42
- up in `dev skills` (source `repo`), participates in selection, and is folded into the
43
- codebase-aware prompt enhancer and the task prompts. A repo-local skill **overrides a
44
- packaged skill with the same name**, so a team can keep its own house rules for
45
- `android`/`web`/etc. Give it `triggers` (or `always: true`) if you want it
46
- auto-selected; without triggers it stays available but dormant.
@@ -1,50 +0,0 @@
1
- ---
2
- name: ai-training
3
- title: AI / ML Model Training Intake
4
- description: Before writing model-training code, retrieve current framework versions, hardware/driver constraints, deprecations, dataset/eval practices, and the right tooling — like a senior ML engineer.
5
- triggers:
6
- keywords: [train, training, model, pytorch, torch, tensorflow, keras, jax, huggingface, transformers, finetune, "fine-tune", dataset, llm, ml, cuda, embedding]
7
- globs: ["*.ipynb", "train.py", "trainer.py", "requirements.txt", "environment.yml", "pyproject.toml", "accelerate*.yaml", "deepspeed*.json"]
8
- ---
9
-
10
- # AI / ML Model Training Intake
11
-
12
- Do this **before** writing or changing model-training code. Frameworks, hardware
13
- drivers, and recommended recipes change frequently, and silent version mismatches
14
- waste expensive compute. Confirm against the framework docs and the project's
15
- environment files.
16
-
17
- ## Establish current state first
18
-
19
- 1. **Framework & environment** — read `requirements.txt` / `environment.yml` /
20
- `pyproject.toml`: the framework (PyTorch, TensorFlow/Keras, JAX, Hugging Face
21
- `transformers`/`accelerate`/`peft`), its version, Python version, and the
22
- CUDA/cuDNN/ROCm requirement. Match the existing stack.
23
- 2. **Hardware & precision** — available accelerators (GPU/TPU), memory budget, and
24
- the precision strategy (fp32/fp16/bf16, mixed precision, quantization). Confirm the
25
- training approach fits the hardware (single-GPU vs distributed: DDP/FSDP/DeepSpeed).
26
- 3. **Deprecations & API shifts** — note moved/renamed APIs (e.g. `transformers`
27
- trainer/argument changes, optimizer/scheduler APIs, dataset loaders) and avoid
28
- patterns deprecated in the installed version.
29
- 4. **Recommended practices** — reproducibility (seed, deterministic flags, pinned
30
- versions), data pipeline (streaming vs in-memory, tokenization), checkpointing and
31
- resume, and evaluation (held-out split, the metric that actually matters, no leakage
32
- between train/val/test).
33
- 5. **Cost & safety** — estimate compute/time before launching long runs; log metrics
34
- (Weights & Biases / TensorBoard) and checkpoint so a crash isn't a total loss.
35
-
36
- ## Tools
37
-
38
- - `python`/`accelerate launch`/`torchrun` for training entry points.
39
- - Experiment tracking (W&B, MLflow, TensorBoard) and `nvidia-smi` for GPU monitoring.
40
- - `datasets`/`dvc` for data, and a pinned environment (`uv`/`conda`/`pip-tools`).
41
-
42
- ## What to record before coding
43
-
44
- - Framework + version, Python + CUDA versions, and the hardware/precision plan.
45
- - Deprecated APIs to avoid and their current replacements.
46
- - How the run is validated: the eval split, the metric, the checkpoint cadence, and a
47
- short smoke run (a few steps) before the full training job.
48
-
49
- Start with a tiny smoke run to validate the pipeline end-to-end before committing to a
50
- long, expensive training run.