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,178 +0,0 @@
1
- """Verification sandbox abstraction."""
2
-
3
- from __future__ import annotations
4
-
5
- import hashlib
6
- import platform
7
- import subprocess
8
- import sys
9
- from pathlib import Path
10
- from typing import Literal
11
-
12
- from pydantic import BaseModel
13
-
14
- from devcouncil.app.config import DevCouncilConfig, load_config
15
- from devcouncil.domain.task import Task
16
- from devcouncil.storage.db import get_db
17
- from devcouncil.storage.native import VerificationRunRepository
18
- from devcouncil.verification.verifier import Verifier
19
-
20
-
21
- class SandboxResult(BaseModel):
22
- sandbox: str
23
- status: Literal["passed", "failed", "unsupported"]
24
- environment: dict[str, str]
25
- commands: list[dict]
26
-
27
-
28
- class VerificationSandbox:
29
- def run(self, task: Task, commands: list[str], requirements: list) -> SandboxResult:
30
- raise NotImplementedError
31
-
32
-
33
- def _save_run(
34
- project_root: Path,
35
- task: Task,
36
- sandbox: str,
37
- environment: dict[str, str],
38
- commands: list[dict],
39
- status: Literal["passed", "failed", "unsupported"],
40
- ) -> None:
41
- db = get_db(project_root)
42
- if not db:
43
- return
44
- with db.get_session() as session:
45
- VerificationRunRepository(session).save(
46
- task.id,
47
- sandbox,
48
- environment,
49
- commands,
50
- status,
51
- )
52
-
53
-
54
- class LocalSandbox(VerificationSandbox):
55
- def __init__(self, project_root: Path):
56
- self.project_root = project_root
57
-
58
- def run(self, task: Task, commands: list[str], requirements: list) -> SandboxResult:
59
- import asyncio
60
-
61
- gaps, _ = asyncio.run(Verifier(self.project_root).verify_task(task, requirements))
62
- status: Literal["passed", "failed", "unsupported"] = (
63
- "failed" if any(g.blocking for g in gaps) else "passed"
64
- )
65
- env = _environment_metadata(self.project_root)
66
- command_results = [{"command": cmd, "status": status} for cmd in commands]
67
- _save_run(self.project_root, task, "local", env, command_results, status)
68
- return SandboxResult(sandbox="local", status=status, environment=env, commands=command_results)
69
-
70
-
71
- class DockerSandbox(VerificationSandbox):
72
- def __init__(self, project_root: Path, config: DevCouncilConfig):
73
- self.project_root = project_root
74
- self.config = config
75
-
76
- def run(self, task: Task, commands: list[str], requirements: list) -> SandboxResult:
77
- if not shutil_which("docker"):
78
- result = SandboxResult(
79
- sandbox="docker",
80
- status="unsupported",
81
- environment={},
82
- commands=[{"reason": "docker not available"}],
83
- )
84
- _save_run(self.project_root, task, "docker", result.environment, result.commands, result.status)
85
- return result
86
- image = self.config.verification.sandbox.docker_image
87
- setup = self.config.verification.sandbox.docker_setup_commands
88
- results: list[dict] = []
89
- for setup_cmd in setup:
90
- proc = subprocess.run(
91
- ["docker", "run", "--rm", "-v", f"{self.project_root}:/work", "-w", "/work", image, "sh", "-c", setup_cmd],
92
- capture_output=True,
93
- text=True,
94
- )
95
- results.append({"command": setup_cmd, "exit_code": proc.returncode})
96
- if proc.returncode != 0:
97
- result = SandboxResult(sandbox="docker", status="failed", environment={"image": image}, commands=results)
98
- _save_run(self.project_root, task, "docker", result.environment, result.commands, result.status)
99
- return result
100
- for cmd in commands:
101
- proc = subprocess.run(
102
- ["docker", "run", "--rm", "-v", f"{self.project_root}:/work", "-w", "/work", image, "sh", "-c", cmd],
103
- capture_output=True,
104
- text=True,
105
- )
106
- results.append({"command": cmd, "exit_code": proc.returncode})
107
- if proc.returncode != 0:
108
- result = SandboxResult(sandbox="docker", status="failed", environment={"image": image}, commands=results)
109
- _save_run(self.project_root, task, "docker", result.environment, result.commands, result.status)
110
- return result
111
- result = SandboxResult(sandbox="docker", status="passed", environment={"image": image}, commands=results)
112
- _save_run(self.project_root, task, "docker", result.environment, result.commands, result.status)
113
- return result
114
-
115
-
116
- class NixSandbox(VerificationSandbox):
117
- def __init__(self, project_root: Path, config: DevCouncilConfig):
118
- self.project_root = project_root
119
- self.config = config
120
-
121
- def run(self, task: Task, commands: list[str], requirements: list) -> SandboxResult:
122
- if not (self.project_root / "flake.nix").exists() or not shutil_which("nix"):
123
- result = SandboxResult(
124
- sandbox="nix",
125
- status="unsupported",
126
- environment={},
127
- commands=[{"reason": "nix or flake.nix unavailable"}],
128
- )
129
- _save_run(self.project_root, task, "nix", result.environment, result.commands, result.status)
130
- return result
131
- attr = self.config.verification.sandbox.nix_flake_attr or "devShells.default"
132
- results: list[dict] = []
133
- for cmd in commands:
134
- proc = subprocess.run(
135
- ["nix", "develop", f".#{attr}", "-c", "sh", "-c", cmd],
136
- cwd=self.project_root,
137
- capture_output=True,
138
- text=True,
139
- )
140
- results.append({"command": cmd, "exit_code": proc.returncode})
141
- if proc.returncode != 0:
142
- result = SandboxResult(sandbox="nix", status="failed", environment={"attr": attr}, commands=results)
143
- _save_run(self.project_root, task, "nix", result.environment, result.commands, result.status)
144
- return result
145
- result = SandboxResult(sandbox="nix", status="passed", environment={"attr": attr}, commands=results)
146
- _save_run(self.project_root, task, "nix", result.environment, result.commands, result.status)
147
- return result
148
-
149
-
150
- def shutil_which(name: str) -> str | None:
151
- import shutil
152
-
153
- return shutil.which(name)
154
-
155
-
156
- def _environment_metadata(project_root: Path) -> dict[str, str]:
157
- env = {
158
- "python": sys.version.split()[0],
159
- "platform": platform.platform(),
160
- }
161
- try:
162
- uv = subprocess.check_output(["uv", "--version"], text=True).strip()
163
- env["uv"] = uv
164
- except (FileNotFoundError, subprocess.CalledProcessError, OSError):
165
- pass
166
- lock = project_root / "uv.lock"
167
- if lock.exists():
168
- env["uv_lock_hash"] = hashlib.sha256(lock.read_bytes()).hexdigest()[:16]
169
- return env
170
-
171
-
172
- def get_sandbox(name: str, project_root: Path) -> VerificationSandbox:
173
- config = load_config(project_root)
174
- if name == "docker":
175
- return DockerSandbox(project_root, config)
176
- if name == "nix":
177
- return NixSandbox(project_root, config)
178
- return LocalSandbox(project_root)
@@ -1,91 +0,0 @@
1
- """Evidence suggestion from changed files."""
2
-
3
- from __future__ import annotations
4
-
5
- from pathlib import Path
6
- from typing import Literal
7
-
8
- from pydantic import BaseModel
9
-
10
- from devcouncil.domain.task import Task
11
-
12
-
13
- class EvidenceSuggestion(BaseModel):
14
- command: str
15
- confidence: Literal["high", "medium", "low"]
16
- reason: str
17
- paths: list[str] = []
18
-
19
-
20
- class TestResolver:
21
- __test__ = False
22
-
23
- def __init__(self, project_root: Path):
24
- self.project_root = project_root.resolve()
25
-
26
- def suggest_for_task(
27
- self,
28
- task: Task,
29
- changed_files: list[str] | None = None,
30
- ) -> list[EvidenceSuggestion]:
31
- files = changed_files or [pf.path for pf in task.planned_files]
32
- suggestions: list[EvidenceSuggestion] = []
33
- seen: set[str] = set()
34
-
35
- for path in files:
36
- normalized = path.replace("\\", "/")
37
- for candidate in self._candidates_for_path(normalized):
38
- if candidate in seen:
39
- continue
40
- seen.add(candidate)
41
- confidence, reason = self._confidence_for(normalized, candidate)
42
- suggestions.append(
43
- EvidenceSuggestion(
44
- command=candidate,
45
- confidence=confidence,
46
- reason=reason,
47
- paths=[normalized],
48
- )
49
- )
50
- return suggestions
51
-
52
- def _candidates_for_path(self, path: str) -> list[str]:
53
- candidates: list[str] = []
54
- if path.startswith("src/") and path.endswith(".py"):
55
- module = path.removeprefix("src/").removesuffix(".py")
56
- parts = module.split("/")
57
- if len(parts) >= 2:
58
- nested = self.project_root / "tests" / parts[0] / f"test_{parts[-1]}.py"
59
- if nested.exists():
60
- candidates.append(f"pytest {nested.relative_to(self.project_root).as_posix()}")
61
- flat = self.project_root / "tests" / f"test_{parts[-1]}.py"
62
- if flat.exists():
63
- candidates.append(f"pytest {flat.relative_to(self.project_root).as_posix()}")
64
- if "cli/commands/" in path:
65
- unit = self.project_root / "tests/unit/test_cli_commands.py"
66
- if unit.exists():
67
- candidates.append(f"pytest {unit.relative_to(self.project_root).as_posix()}")
68
- if path.endswith("policy_engine.py"):
69
- unit = self.project_root / "tests/unit/test_task_policy_engine.py"
70
- if unit.exists():
71
- candidates.append(f"pytest {unit.relative_to(self.project_root).as_posix()}")
72
- if path == "src/auth.py":
73
- auth_test = self.project_root / "tests/test_auth.py"
74
- if auth_test.exists():
75
- candidates.append("pytest tests/test_auth.py")
76
- if not candidates:
77
- candidates.append("pytest tests/unit")
78
- return candidates
79
-
80
- def _confidence_for(self, path: str, command: str) -> tuple[Literal["high", "medium", "low"], str]:
81
- if "test_auth.py" in command and path.endswith("auth.py"):
82
- return "high", "Direct auth module mapping"
83
- if path.endswith("policy_engine.py") and "test_task_policy_engine.py" in command:
84
- return "high", "Policy engine unit test exists"
85
- if "cli/commands" in path and "test_cli_commands.py" in command:
86
- return "high", "CLI command module maps to cli command tests"
87
- if command.startswith("pytest tests/") and command != "pytest tests/unit":
88
- return "high", "Nested module test path exists"
89
- if "test_" in command:
90
- return "medium", "Related test file match"
91
- return "low", "Package-level fallback"