devcouncil 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +191 -253
- package/bin/devcouncil.js +130 -32
- package/package.json +6 -17
- package/pyproject.toml +0 -66
- package/src/devcouncil/__init__.py +0 -0
- package/src/devcouncil/__main__.py +0 -4
- package/src/devcouncil/app/__init__.py +0 -28
- package/src/devcouncil/app/config.py +0 -320
- package/src/devcouncil/app/errors.py +0 -23
- package/src/devcouncil/app/events.py +0 -44
- package/src/devcouncil/app/orchestrator.py +0 -92
- package/src/devcouncil/app/project_status.py +0 -29
- package/src/devcouncil/app/run_context.py +0 -39
- package/src/devcouncil/app/state_machine.py +0 -108
- package/src/devcouncil/artifacts/__init__.py +0 -1
- package/src/devcouncil/artifacts/coverage.py +0 -96
- package/src/devcouncil/artifacts/graph.py +0 -163
- package/src/devcouncil/artifacts/migrations.py +0 -20
- package/src/devcouncil/artifacts/schemas.py +0 -23
- package/src/devcouncil/artifacts/serializer.py +0 -21
- package/src/devcouncil/artifacts/validators.py +0 -27
- package/src/devcouncil/assets/__init__.py +0 -1
- package/src/devcouncil/assets/devcouncil-logo.svg +0 -60
- package/src/devcouncil/assets/devcouncil_logo_premium.png +0 -0
- package/src/devcouncil/cli/__init__.py +0 -0
- package/src/devcouncil/cli/commands/__init__.py +0 -0
- package/src/devcouncil/cli/commands/agents.py +0 -292
- package/src/devcouncil/cli/commands/artifacts.py +0 -54
- package/src/devcouncil/cli/commands/ast.py +0 -22
- package/src/devcouncil/cli/commands/baseline.py +0 -35
- package/src/devcouncil/cli/commands/check.py +0 -209
- package/src/devcouncil/cli/commands/config.py +0 -115
- package/src/devcouncil/cli/commands/cost.py +0 -57
- package/src/devcouncil/cli/commands/dashboard.py +0 -31
- package/src/devcouncil/cli/commands/doctor.py +0 -340
- package/src/devcouncil/cli/commands/evidence.py +0 -48
- package/src/devcouncil/cli/commands/go.py +0 -656
- package/src/devcouncil/cli/commands/handoff.py +0 -69
- package/src/devcouncil/cli/commands/hook.py +0 -237
- package/src/devcouncil/cli/commands/init.py +0 -289
- package/src/devcouncil/cli/commands/integrate.py +0 -1515
- package/src/devcouncil/cli/commands/lsp.py +0 -20
- package/src/devcouncil/cli/commands/map.py +0 -112
- package/src/devcouncil/cli/commands/mcp_server.py +0 -18
- package/src/devcouncil/cli/commands/plan.py +0 -488
- package/src/devcouncil/cli/commands/prompt.py +0 -61
- package/src/devcouncil/cli/commands/repair.py +0 -89
- package/src/devcouncil/cli/commands/report.py +0 -137
- package/src/devcouncil/cli/commands/reset_demo_state.py +0 -33
- package/src/devcouncil/cli/commands/rollback.py +0 -59
- package/src/devcouncil/cli/commands/run.py +0 -289
- package/src/devcouncil/cli/commands/runs.py +0 -223
- package/src/devcouncil/cli/commands/scaffold.py +0 -32
- package/src/devcouncil/cli/commands/semantic.py +0 -47
- package/src/devcouncil/cli/commands/setup.py +0 -362
- package/src/devcouncil/cli/commands/shell.py +0 -73
- package/src/devcouncil/cli/commands/show.py +0 -76
- package/src/devcouncil/cli/commands/skills.py +0 -88
- package/src/devcouncil/cli/commands/status.py +0 -141
- package/src/devcouncil/cli/commands/tasks.py +0 -55
- package/src/devcouncil/cli/commands/trace.py +0 -88
- package/src/devcouncil/cli/commands/verify.py +0 -328
- package/src/devcouncil/cli/commands/version.py +0 -20
- package/src/devcouncil/cli/commands/watch.py +0 -574
- package/src/devcouncil/cli/commands/watch_fs.py +0 -40
- package/src/devcouncil/cli/main.py +0 -137
- package/src/devcouncil/council/__init__.py +0 -0
- package/src/devcouncil/council/prompts/__init__.py +0 -0
- package/src/devcouncil/council/prompts/arbiter.md +0 -19
- package/src/devcouncil/council/prompts/critic_a.md +0 -10
- package/src/devcouncil/council/prompts/critic_b.md +0 -10
- package/src/devcouncil/council/prompts/implementation_reviewer.md +0 -16
- package/src/devcouncil/council/prompts/planner_a.md +0 -16
- package/src/devcouncil/council/prompts/planner_b.md +0 -16
- package/src/devcouncil/council/prompts/rebuttal.md +0 -10
- package/src/devcouncil/council/prompts/spec_writer.md +0 -12
- package/src/devcouncil/domain/__init__.py +0 -0
- package/src/devcouncil/domain/assumption.py +0 -17
- package/src/devcouncil/domain/critique.py +0 -32
- package/src/devcouncil/domain/evidence.py +0 -47
- package/src/devcouncil/domain/gap.py +0 -52
- package/src/devcouncil/domain/requirement.py +0 -22
- package/src/devcouncil/domain/task.py +0 -55
- package/src/devcouncil/execution/__init__.py +0 -1
- package/src/devcouncil/execution/checkpoints.py +0 -246
- package/src/devcouncil/execution/context_builder.py +0 -60
- package/src/devcouncil/execution/executor.py +0 -15
- package/src/devcouncil/execution/fs_watcher.py +0 -180
- package/src/devcouncil/execution/handoff.py +0 -102
- package/src/devcouncil/execution/hook_policy.py +0 -253
- package/src/devcouncil/execution/patch.py +0 -77
- package/src/devcouncil/execution/paths.py +0 -14
- package/src/devcouncil/execution/permissions.py +0 -85
- package/src/devcouncil/execution/policy_engine.py +0 -343
- package/src/devcouncil/execution/prompt_builder.py +0 -671
- package/src/devcouncil/execution/shell_session.py +0 -225
- package/src/devcouncil/execution/task_runner.py +0 -170
- package/src/devcouncil/executors/__init__.py +0 -1
- package/src/devcouncil/executors/agent_registry.py +0 -575
- package/src/devcouncil/executors/coding_cli.py +0 -736
- package/src/devcouncil/executors/mini_swe.py +0 -73
- package/src/devcouncil/executors/native/__init__.py +0 -0
- package/src/devcouncil/executors/native/agent.py +0 -208
- package/src/devcouncil/executors/openhands.py +0 -71
- package/src/devcouncil/gating/__init__.py +0 -1
- package/src/devcouncil/gating/checks/__init__.py +0 -0
- package/src/devcouncil/gating/checks/clean_git.py +0 -52
- package/src/devcouncil/gating/checks/planned_files_check.py +0 -32
- package/src/devcouncil/gating/checks/requirement_coverage.py +0 -26
- package/src/devcouncil/gating/checks/secret_scan_check.py +0 -53
- package/src/devcouncil/gating/policy.py +0 -338
- package/src/devcouncil/hardware.py +0 -184
- package/src/devcouncil/indexing/__init__.py +0 -1
- package/src/devcouncil/indexing/ast_matcher.py +0 -168
- package/src/devcouncil/indexing/graph_index.py +0 -48
- package/src/devcouncil/indexing/lsp.py +0 -161
- package/src/devcouncil/indexing/repo_mapper.py +0 -1455
- package/src/devcouncil/indexing/semantic_index.py +0 -205
- package/src/devcouncil/integrations/actions.py +0 -146
- package/src/devcouncil/integrations/check.py +0 -423
- package/src/devcouncil/integrations/code_review_graph.py +0 -163
- package/src/devcouncil/integrations/github.py +0 -39
- package/src/devcouncil/integrations/github_intent.py +0 -142
- package/src/devcouncil/integrations/gitnexus.py +0 -62
- package/src/devcouncil/integrations/graphify.py +0 -34
- package/src/devcouncil/integrations/mcp/__init__.py +0 -0
- package/src/devcouncil/integrations/mcp/server.py +0 -2122
- package/src/devcouncil/integrations/opencode_devcouncil_plugin.mjs +0 -24
- package/src/devcouncil/integrations/pr_comments.py +0 -62
- package/src/devcouncil/live/__init__.py +0 -2
- package/src/devcouncil/live/cards.py +0 -349
- package/src/devcouncil/live/models.py +0 -63
- package/src/devcouncil/live/repair_prompt.py +0 -83
- package/src/devcouncil/live/reviewer.py +0 -70
- package/src/devcouncil/live/signals.py +0 -135
- package/src/devcouncil/live/summary.py +0 -34
- package/src/devcouncil/live/tasks.py +0 -18
- package/src/devcouncil/live/transcripts.py +0 -141
- package/src/devcouncil/llm/__init__.py +0 -1
- package/src/devcouncil/llm/cache.py +0 -42
- package/src/devcouncil/llm/model_defaults.yaml +0 -44
- package/src/devcouncil/llm/provider.py +0 -627
- package/src/devcouncil/llm/router.py +0 -310
- package/src/devcouncil/optimization/__init__.py +0 -1
- package/src/devcouncil/optimization/gepa_agent.py +0 -318
- package/src/devcouncil/planning/__init__.py +0 -1
- package/src/devcouncil/planning/arbiter_service.py +0 -57
- package/src/devcouncil/planning/correction_manifest.py +0 -303
- package/src/devcouncil/planning/critique_service.py +0 -71
- package/src/devcouncil/planning/plan_service.py +0 -60
- package/src/devcouncil/planning/prompt_enhancer_service.py +0 -167
- package/src/devcouncil/planning/repair_service.py +0 -39
- package/src/devcouncil/planning/spec_service.py +0 -70
- package/src/devcouncil/repo/__init__.py +0 -0
- package/src/devcouncil/repo/ci_scaffold.py +0 -157
- package/src/devcouncil/repo/gitignore.py +0 -123
- package/src/devcouncil/repo/sca.py +0 -374
- package/src/devcouncil/reporting/__init__.py +0 -0
- package/src/devcouncil/reporting/github_check.py +0 -32
- package/src/devcouncil/reporting/json_report.py +0 -30
- package/src/devcouncil/reporting/markdown_report.py +0 -83
- package/src/devcouncil/reporting/report_builder.py +0 -14
- package/src/devcouncil/skills/__init__.py +0 -19
- package/src/devcouncil/skills/library/README.md +0 -46
- package/src/devcouncil/skills/library/ai-training.md +0 -50
- package/src/devcouncil/skills/library/android.md +0 -50
- package/src/devcouncil/skills/library/backend.md +0 -52
- package/src/devcouncil/skills/library/core-engineering.md +0 -95
- package/src/devcouncil/skills/library/data-engineering.md +0 -47
- package/src/devcouncil/skills/library/desktop.md +0 -46
- package/src/devcouncil/skills/library/devops.md +0 -48
- package/src/devcouncil/skills/library/game-dev.md +0 -46
- package/src/devcouncil/skills/library/ios.md +0 -48
- package/src/devcouncil/skills/library/mobile-cross-platform.md +0 -46
- package/src/devcouncil/skills/library/security.md +0 -48
- package/src/devcouncil/skills/library/systems.md +0 -48
- package/src/devcouncil/skills/library/web.md +0 -47
- package/src/devcouncil/skills/library/windows.md +0 -47
- package/src/devcouncil/skills/registry.py +0 -330
- package/src/devcouncil/storage/__init__.py +0 -0
- package/src/devcouncil/storage/db.py +0 -147
- package/src/devcouncil/storage/models.py +0 -204
- package/src/devcouncil/storage/native.py +0 -557
- package/src/devcouncil/storage/repositories.py +0 -485
- package/src/devcouncil/telemetry/__init__.py +0 -0
- package/src/devcouncil/telemetry/cost.py +0 -140
- package/src/devcouncil/telemetry/model_pricing.yaml +0 -48
- package/src/devcouncil/telemetry/pricing.py +0 -28
- package/src/devcouncil/telemetry/traces.py +0 -146
- package/src/devcouncil/telemetry/tracker.py +0 -52
- package/src/devcouncil/ui/__init__.py +0 -1
- package/src/devcouncil/ui/dashboard.py +0 -423
- package/src/devcouncil/utils/__init__.py +0 -1
- package/src/devcouncil/utils/redaction.py +0 -147
- package/src/devcouncil/utils/subprocess_env.py +0 -69
- package/src/devcouncil/verification/__init__.py +0 -1
- package/src/devcouncil/verification/acceptance_compiler.py +0 -125
- package/src/devcouncil/verification/ad_hoc_check.py +0 -129
- package/src/devcouncil/verification/diff_coverage.py +0 -353
- package/src/devcouncil/verification/implementation_reviewer.py +0 -55
- package/src/devcouncil/verification/next_actions.py +0 -189
- package/src/devcouncil/verification/sandbox.py +0 -178
- package/src/devcouncil/verification/test_resolver.py +0 -91
- package/src/devcouncil/verification/verifier.py +0 -1548
- package/uv.lock +0 -1226
|
@@ -1,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.
|