devcouncil 0.1.1 → 0.3.0
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 +201 -6
- package/package.json +9 -2
- package/pyproject.toml +34 -2
- package/src/devcouncil/app/config.py +348 -12
- package/src/devcouncil/app/orchestrator.py +10 -6
- package/src/devcouncil/app/state_machine.py +4 -0
- package/src/devcouncil/artifacts/graph.py +32 -5
- package/src/devcouncil/assets/__init__.py +1 -0
- package/src/devcouncil/assets/devcouncil-logo.svg +60 -0
- package/src/devcouncil/assets/devcouncil_logo_premium.png +0 -0
- package/src/devcouncil/cli/commands/agents.py +292 -0
- package/src/devcouncil/cli/commands/artifacts.py +6 -3
- package/src/devcouncil/cli/commands/check.py +220 -0
- package/src/devcouncil/cli/commands/config.py +43 -4
- package/src/devcouncil/cli/commands/cost.py +57 -0
- package/src/devcouncil/cli/commands/dashboard.py +6 -1
- package/src/devcouncil/cli/commands/design.py +186 -0
- package/src/devcouncil/cli/commands/doctor.py +379 -22
- package/src/devcouncil/cli/commands/evidence.py +48 -0
- package/src/devcouncil/cli/commands/go.py +532 -33
- package/src/devcouncil/cli/commands/handoff.py +69 -0
- package/src/devcouncil/cli/commands/hook.py +296 -15
- package/src/devcouncil/cli/commands/init.py +161 -20
- package/src/devcouncil/cli/commands/integrate.py +1371 -124
- package/src/devcouncil/cli/commands/logs.py +106 -0
- package/src/devcouncil/cli/commands/map.py +80 -10
- package/src/devcouncil/cli/commands/okf.py +245 -0
- package/src/devcouncil/cli/commands/plan.py +256 -55
- package/src/devcouncil/cli/commands/prompt.py +18 -7
- package/src/devcouncil/cli/commands/repair.py +50 -24
- package/src/devcouncil/cli/commands/report.py +8 -0
- package/src/devcouncil/cli/commands/reset_demo_state.py +4 -2
- package/src/devcouncil/cli/commands/rollback.py +27 -28
- package/src/devcouncil/cli/commands/run.py +195 -54
- package/src/devcouncil/cli/commands/runs.py +223 -0
- package/src/devcouncil/cli/commands/scaffold.py +32 -0
- package/src/devcouncil/cli/commands/semantic.py +47 -0
- package/src/devcouncil/cli/commands/setup.py +145 -6
- package/src/devcouncil/cli/commands/shell.py +73 -0
- package/src/devcouncil/cli/commands/skills.py +267 -0
- package/src/devcouncil/cli/commands/status.py +30 -15
- package/src/devcouncil/cli/commands/trace.py +47 -3
- package/src/devcouncil/cli/commands/verify.py +144 -3
- package/src/devcouncil/cli/commands/watch.py +32 -12
- package/src/devcouncil/cli/commands/watch_fs.py +40 -0
- package/src/devcouncil/cli/main.py +91 -7
- package/src/devcouncil/domain/evidence.py +29 -2
- package/src/devcouncil/domain/gap.py +27 -1
- package/src/devcouncil/domain/task.py +31 -2
- package/src/devcouncil/execution/checkpoints.py +256 -0
- package/src/devcouncil/execution/context_builder.py +1 -1
- package/src/devcouncil/execution/fs_watcher.py +205 -0
- package/src/devcouncil/execution/handoff.py +102 -0
- package/src/devcouncil/execution/hook_policy.py +162 -74
- package/src/devcouncil/execution/patch.py +65 -10
- package/src/devcouncil/execution/permissions.py +24 -24
- package/src/devcouncil/execution/policy_engine.py +350 -0
- package/src/devcouncil/execution/prompt_builder.py +751 -23
- package/src/devcouncil/execution/shell_session.py +231 -0
- package/src/devcouncil/execution/task_runner.py +24 -9
- package/src/devcouncil/executors/agent_registry.py +596 -0
- package/src/devcouncil/executors/coding_cli.py +791 -39
- package/src/devcouncil/executors/mini_swe.py +6 -0
- package/src/devcouncil/executors/native/agent.py +135 -19
- package/src/devcouncil/executors/openhands.py +6 -0
- package/src/devcouncil/gating/checks/clean_git.py +3 -1
- package/src/devcouncil/gating/checks/secret_scan_check.py +47 -21
- package/src/devcouncil/gating/policy.py +190 -11
- package/src/devcouncil/hardware.py +184 -0
- package/src/devcouncil/indexing/ast_matcher.py +17 -7
- package/src/devcouncil/indexing/lsp.py +45 -4
- package/src/devcouncil/indexing/repo_mapper.py +1284 -15
- package/src/devcouncil/indexing/semantic_index.py +221 -0
- package/src/devcouncil/integrations/actions.py +166 -0
- package/src/devcouncil/integrations/check.py +426 -0
- package/src/devcouncil/integrations/claude_assets.py +444 -0
- package/src/devcouncil/integrations/code_review_graph.py +13 -2
- package/src/devcouncil/integrations/github_intent.py +149 -0
- package/src/devcouncil/integrations/gitnexus.py +45 -2
- package/src/devcouncil/integrations/mcp/server.py +1944 -32
- package/src/devcouncil/integrations/opencode_devcouncil_plugin.mjs +24 -0
- package/src/devcouncil/integrations/pr_comments.py +9 -0
- package/src/devcouncil/knowledge/__init__.py +23 -0
- package/src/devcouncil/knowledge/design.py +374 -0
- package/src/devcouncil/knowledge/design_conformance.py +317 -0
- package/src/devcouncil/knowledge/fetch.py +223 -0
- package/src/devcouncil/knowledge/frontmatter.py +51 -0
- package/src/devcouncil/knowledge/okf.py +202 -0
- package/src/devcouncil/knowledge/skill_bridge.py +96 -0
- package/src/devcouncil/knowledge/sources.py +239 -0
- package/src/devcouncil/live/cards.py +181 -25
- package/src/devcouncil/live/repair_prompt.py +29 -6
- package/src/devcouncil/live/reviewer.py +72 -13
- package/src/devcouncil/live/signals.py +2 -2
- package/src/devcouncil/live/summary.py +18 -8
- package/src/devcouncil/live/transcripts.py +47 -11
- package/src/devcouncil/llm/cache.py +20 -8
- package/src/devcouncil/llm/model_defaults.yaml +44 -0
- package/src/devcouncil/llm/provider.py +617 -49
- package/src/devcouncil/llm/router.py +337 -53
- package/src/devcouncil/optimization/__init__.py +1 -0
- package/src/devcouncil/optimization/gepa_agent.py +318 -0
- package/src/devcouncil/optimization/skillopt.py +673 -0
- package/src/devcouncil/planning/arbiter_service.py +10 -2
- package/src/devcouncil/planning/correction_manifest.py +346 -0
- package/src/devcouncil/planning/critique_service.py +16 -4
- package/src/devcouncil/planning/plan_service.py +86 -6
- package/src/devcouncil/planning/prompt_enhancer_service.py +206 -1
- package/src/devcouncil/planning/repair_service.py +8 -2
- package/src/devcouncil/planning/spec_service.py +37 -3
- package/src/devcouncil/repo/ci_scaffold.py +165 -0
- package/src/devcouncil/repo/gitignore.py +123 -0
- package/src/devcouncil/repo/sca.py +384 -0
- package/src/devcouncil/reporting/json_report.py +22 -1
- package/src/devcouncil/reporting/markdown_report.py +29 -1
- package/src/devcouncil/reporting/okf_bundle_writer.py +364 -0
- package/src/devcouncil/reporting/okf_html.py +323 -0
- package/src/devcouncil/reporting/report_builder.py +18 -1
- package/src/devcouncil/skills/__init__.py +19 -0
- package/src/devcouncil/skills/library/README.md +46 -0
- package/src/devcouncil/skills/library/ai-training.md +50 -0
- package/src/devcouncil/skills/library/android.md +50 -0
- package/src/devcouncil/skills/library/backend.md +52 -0
- package/src/devcouncil/skills/library/core-engineering.md +95 -0
- package/src/devcouncil/skills/library/data-engineering.md +47 -0
- package/src/devcouncil/skills/library/desktop.md +46 -0
- package/src/devcouncil/skills/library/devops.md +48 -0
- package/src/devcouncil/skills/library/game-dev.md +46 -0
- package/src/devcouncil/skills/library/ios.md +48 -0
- package/src/devcouncil/skills/library/mobile-cross-platform.md +46 -0
- package/src/devcouncil/skills/library/security.md +48 -0
- package/src/devcouncil/skills/library/systems.md +48 -0
- package/src/devcouncil/skills/library/web.md +47 -0
- package/src/devcouncil/skills/library/windows.md +47 -0
- package/src/devcouncil/skills/registry.py +408 -0
- package/src/devcouncil/storage/db.py +140 -3
- package/src/devcouncil/storage/models.py +125 -0
- package/src/devcouncil/storage/native.py +559 -0
- package/src/devcouncil/storage/repositories.py +157 -78
- package/src/devcouncil/telemetry/cost.py +123 -17
- package/src/devcouncil/telemetry/logging_setup.py +244 -0
- package/src/devcouncil/telemetry/model_pricing.yaml +48 -0
- package/src/devcouncil/telemetry/pricing.py +28 -0
- package/src/devcouncil/telemetry/stages.py +141 -0
- package/src/devcouncil/telemetry/traces.py +62 -7
- package/src/devcouncil/telemetry/tracker.py +24 -10
- package/src/devcouncil/ui/dashboard.py +393 -28
- package/src/devcouncil/utils/redaction.py +9 -3
- package/src/devcouncil/utils/subprocess_env.py +69 -0
- package/src/devcouncil/verification/acceptance_compiler.py +253 -0
- package/src/devcouncil/verification/ad_hoc_check.py +135 -0
- package/src/devcouncil/verification/diff_coverage.py +353 -0
- package/src/devcouncil/verification/implementation_reviewer.py +11 -2
- package/src/devcouncil/verification/next_actions.py +189 -0
- package/src/devcouncil/verification/sandbox.py +181 -0
- package/src/devcouncil/verification/test_resolver.py +91 -0
- package/src/devcouncil/verification/verifier.py +1549 -143
- package/uv.lock +205 -64
- package/src/devcouncil/indexing/symbol_index.py +0 -0
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
"""Verification sandbox abstraction."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import hashlib
|
|
7
|
+
import logging
|
|
8
|
+
import platform
|
|
9
|
+
import shutil
|
|
10
|
+
import subprocess
|
|
11
|
+
import sys
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Literal
|
|
14
|
+
|
|
15
|
+
from pydantic import BaseModel
|
|
16
|
+
|
|
17
|
+
from devcouncil.app.config import DevCouncilConfig, load_config
|
|
18
|
+
from devcouncil.domain.task import Task
|
|
19
|
+
from devcouncil.storage.db import get_db
|
|
20
|
+
from devcouncil.storage.native import VerificationRunRepository
|
|
21
|
+
from devcouncil.verification.verifier import Verifier
|
|
22
|
+
|
|
23
|
+
logger = logging.getLogger(__name__)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class SandboxResult(BaseModel):
|
|
27
|
+
sandbox: str
|
|
28
|
+
status: Literal["passed", "failed", "unsupported"]
|
|
29
|
+
environment: dict[str, str]
|
|
30
|
+
commands: list[dict]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class VerificationSandbox:
|
|
34
|
+
def run(self, task: Task, commands: list[str], requirements: list) -> SandboxResult:
|
|
35
|
+
raise NotImplementedError
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _save_run(
|
|
39
|
+
project_root: Path,
|
|
40
|
+
task: Task,
|
|
41
|
+
sandbox: str,
|
|
42
|
+
environment: dict[str, str],
|
|
43
|
+
commands: list[dict],
|
|
44
|
+
status: Literal["passed", "failed", "unsupported"],
|
|
45
|
+
) -> None:
|
|
46
|
+
log = logger.info if status == "passed" else logger.warning
|
|
47
|
+
log("Sandbox %s for %s: %s (%d command(s))", sandbox, task.id, status, len(commands))
|
|
48
|
+
db = get_db(project_root)
|
|
49
|
+
if not db:
|
|
50
|
+
return
|
|
51
|
+
with db.get_session() as session:
|
|
52
|
+
VerificationRunRepository(session).save(
|
|
53
|
+
task.id,
|
|
54
|
+
sandbox,
|
|
55
|
+
environment,
|
|
56
|
+
commands,
|
|
57
|
+
status,
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class LocalSandbox(VerificationSandbox):
|
|
62
|
+
def __init__(self, project_root: Path):
|
|
63
|
+
self.project_root = project_root
|
|
64
|
+
|
|
65
|
+
def run(self, task: Task, commands: list[str], requirements: list) -> SandboxResult:
|
|
66
|
+
gaps, _ = asyncio.run(Verifier(self.project_root).verify_task(task, requirements))
|
|
67
|
+
status: Literal["passed", "failed", "unsupported"] = (
|
|
68
|
+
"failed" if any(g.blocking for g in gaps) else "passed"
|
|
69
|
+
)
|
|
70
|
+
env = _environment_metadata(self.project_root)
|
|
71
|
+
command_results = [{"command": cmd, "status": status} for cmd in commands]
|
|
72
|
+
_save_run(self.project_root, task, "local", env, command_results, status)
|
|
73
|
+
return SandboxResult(sandbox="local", status=status, environment=env, commands=command_results)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class DockerSandbox(VerificationSandbox):
|
|
77
|
+
def __init__(self, project_root: Path, config: DevCouncilConfig):
|
|
78
|
+
self.project_root = project_root
|
|
79
|
+
self.config = config
|
|
80
|
+
|
|
81
|
+
def run(self, task: Task, commands: list[str], requirements: list) -> SandboxResult:
|
|
82
|
+
if not shutil_which("docker"):
|
|
83
|
+
result = SandboxResult(
|
|
84
|
+
sandbox="docker",
|
|
85
|
+
status="unsupported",
|
|
86
|
+
environment={},
|
|
87
|
+
commands=[{"reason": "docker not available"}],
|
|
88
|
+
)
|
|
89
|
+
_save_run(self.project_root, task, "docker", result.environment, result.commands, result.status)
|
|
90
|
+
return result
|
|
91
|
+
image = self.config.verification.sandbox.docker_image
|
|
92
|
+
setup = self.config.verification.sandbox.docker_setup_commands
|
|
93
|
+
results: list[dict] = []
|
|
94
|
+
for setup_cmd in setup:
|
|
95
|
+
proc = subprocess.run(
|
|
96
|
+
["docker", "run", "--rm", "-v", f"{self.project_root}:/work", "-w", "/work", image, "sh", "-c", setup_cmd],
|
|
97
|
+
capture_output=True,
|
|
98
|
+
text=True,
|
|
99
|
+
)
|
|
100
|
+
results.append({"command": setup_cmd, "exit_code": proc.returncode})
|
|
101
|
+
if proc.returncode != 0:
|
|
102
|
+
result = SandboxResult(sandbox="docker", status="failed", environment={"image": image}, commands=results)
|
|
103
|
+
_save_run(self.project_root, task, "docker", result.environment, result.commands, result.status)
|
|
104
|
+
return result
|
|
105
|
+
for cmd in commands:
|
|
106
|
+
proc = subprocess.run(
|
|
107
|
+
["docker", "run", "--rm", "-v", f"{self.project_root}:/work", "-w", "/work", image, "sh", "-c", cmd],
|
|
108
|
+
capture_output=True,
|
|
109
|
+
text=True,
|
|
110
|
+
)
|
|
111
|
+
results.append({"command": cmd, "exit_code": proc.returncode})
|
|
112
|
+
if proc.returncode != 0:
|
|
113
|
+
result = SandboxResult(sandbox="docker", status="failed", environment={"image": image}, commands=results)
|
|
114
|
+
_save_run(self.project_root, task, "docker", result.environment, result.commands, result.status)
|
|
115
|
+
return result
|
|
116
|
+
result = SandboxResult(sandbox="docker", status="passed", environment={"image": image}, commands=results)
|
|
117
|
+
_save_run(self.project_root, task, "docker", result.environment, result.commands, result.status)
|
|
118
|
+
return result
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class NixSandbox(VerificationSandbox):
|
|
122
|
+
def __init__(self, project_root: Path, config: DevCouncilConfig):
|
|
123
|
+
self.project_root = project_root
|
|
124
|
+
self.config = config
|
|
125
|
+
|
|
126
|
+
def run(self, task: Task, commands: list[str], requirements: list) -> SandboxResult:
|
|
127
|
+
if not (self.project_root / "flake.nix").exists() or not shutil_which("nix"):
|
|
128
|
+
result = SandboxResult(
|
|
129
|
+
sandbox="nix",
|
|
130
|
+
status="unsupported",
|
|
131
|
+
environment={},
|
|
132
|
+
commands=[{"reason": "nix or flake.nix unavailable"}],
|
|
133
|
+
)
|
|
134
|
+
_save_run(self.project_root, task, "nix", result.environment, result.commands, result.status)
|
|
135
|
+
return result
|
|
136
|
+
attr = self.config.verification.sandbox.nix_flake_attr or "devShells.default"
|
|
137
|
+
results: list[dict] = []
|
|
138
|
+
for cmd in commands:
|
|
139
|
+
proc = subprocess.run(
|
|
140
|
+
["nix", "develop", f".#{attr}", "-c", "sh", "-c", cmd],
|
|
141
|
+
cwd=self.project_root,
|
|
142
|
+
capture_output=True,
|
|
143
|
+
text=True,
|
|
144
|
+
)
|
|
145
|
+
results.append({"command": cmd, "exit_code": proc.returncode})
|
|
146
|
+
if proc.returncode != 0:
|
|
147
|
+
result = SandboxResult(sandbox="nix", status="failed", environment={"attr": attr}, commands=results)
|
|
148
|
+
_save_run(self.project_root, task, "nix", result.environment, result.commands, result.status)
|
|
149
|
+
return result
|
|
150
|
+
result = SandboxResult(sandbox="nix", status="passed", environment={"attr": attr}, commands=results)
|
|
151
|
+
_save_run(self.project_root, task, "nix", result.environment, result.commands, result.status)
|
|
152
|
+
return result
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def shutil_which(name: str) -> str | None:
|
|
156
|
+
return shutil.which(name)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def _environment_metadata(project_root: Path) -> dict[str, str]:
|
|
160
|
+
env = {
|
|
161
|
+
"python": sys.version.split()[0],
|
|
162
|
+
"platform": platform.platform(),
|
|
163
|
+
}
|
|
164
|
+
try:
|
|
165
|
+
uv = subprocess.check_output(["uv", "--version"], text=True).strip()
|
|
166
|
+
env["uv"] = uv
|
|
167
|
+
except (FileNotFoundError, subprocess.CalledProcessError, OSError):
|
|
168
|
+
pass
|
|
169
|
+
lock = project_root / "uv.lock"
|
|
170
|
+
if lock.exists():
|
|
171
|
+
env["uv_lock_hash"] = hashlib.sha256(lock.read_bytes()).hexdigest()[:16]
|
|
172
|
+
return env
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def get_sandbox(name: str, project_root: Path) -> VerificationSandbox:
|
|
176
|
+
config = load_config(project_root)
|
|
177
|
+
if name == "docker":
|
|
178
|
+
return DockerSandbox(project_root, config)
|
|
179
|
+
if name == "nix":
|
|
180
|
+
return NixSandbox(project_root, config)
|
|
181
|
+
return LocalSandbox(project_root)
|
|
@@ -0,0 +1,91 @@
|
|
|
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"
|