devcouncil 0.1.0 → 0.2.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/LICENSE +201 -201
- package/README.md +197 -494
- package/package.json +9 -2
- package/pyproject.toml +62 -27
- package/src/devcouncil/__main__.py +4 -4
- package/src/devcouncil/app/__init__.py +28 -28
- package/src/devcouncil/app/config.py +297 -108
- package/src/devcouncil/app/errors.py +23 -23
- package/src/devcouncil/app/events.py +44 -44
- package/src/devcouncil/app/orchestrator.py +67 -67
- package/src/devcouncil/app/project_status.py +29 -0
- package/src/devcouncil/app/run_context.py +39 -39
- package/src/devcouncil/app/state_machine.py +108 -108
- package/src/devcouncil/artifacts/__init__.py +1 -1
- package/src/devcouncil/artifacts/coverage.py +96 -96
- package/src/devcouncil/artifacts/graph.py +163 -143
- package/src/devcouncil/artifacts/migrations.py +20 -20
- package/src/devcouncil/artifacts/schemas.py +23 -23
- package/src/devcouncil/artifacts/serializer.py +21 -21
- package/src/devcouncil/artifacts/validators.py +27 -27
- 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 +54 -48
- package/src/devcouncil/cli/commands/ast.py +22 -0
- package/src/devcouncil/cli/commands/baseline.py +35 -32
- package/src/devcouncil/cli/commands/check.py +209 -0
- package/src/devcouncil/cli/commands/config.py +115 -54
- package/src/devcouncil/cli/commands/cost.py +57 -0
- package/src/devcouncil/cli/commands/dashboard.py +31 -0
- package/src/devcouncil/cli/commands/doctor.py +291 -47
- package/src/devcouncil/cli/commands/evidence.py +48 -0
- package/src/devcouncil/cli/commands/go.py +656 -0
- package/src/devcouncil/cli/commands/handoff.py +69 -0
- package/src/devcouncil/cli/commands/hook.py +209 -33
- package/src/devcouncil/cli/commands/init.py +204 -57
- package/src/devcouncil/cli/commands/integrate.py +1171 -76
- package/src/devcouncil/cli/commands/lsp.py +20 -0
- package/src/devcouncil/cli/commands/map.py +96 -22
- package/src/devcouncil/cli/commands/plan.py +422 -210
- package/src/devcouncil/cli/commands/prompt.py +48 -34
- package/src/devcouncil/cli/commands/repair.py +89 -69
- package/src/devcouncil/cli/commands/report.py +120 -54
- package/src/devcouncil/cli/commands/reset_demo_state.py +33 -28
- package/src/devcouncil/cli/commands/rollback.py +55 -54
- package/src/devcouncil/cli/commands/run.py +285 -220
- 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 +300 -20
- package/src/devcouncil/cli/commands/shell.py +73 -0
- package/src/devcouncil/cli/commands/show.py +76 -57
- package/src/devcouncil/cli/commands/skills.py +88 -0
- package/src/devcouncil/cli/commands/status.py +141 -105
- package/src/devcouncil/cli/commands/tasks.py +55 -41
- package/src/devcouncil/cli/commands/trace.py +49 -4
- package/src/devcouncil/cli/commands/verify.py +293 -128
- package/src/devcouncil/cli/commands/version.py +20 -20
- package/src/devcouncil/cli/commands/watch.py +574 -0
- package/src/devcouncil/cli/commands/watch_fs.py +40 -0
- package/src/devcouncil/cli/main.py +92 -25
- package/src/devcouncil/council/prompts/arbiter.md +19 -19
- package/src/devcouncil/council/prompts/critic_a.md +10 -10
- package/src/devcouncil/council/prompts/critic_b.md +10 -10
- package/src/devcouncil/council/prompts/implementation_reviewer.md +16 -16
- package/src/devcouncil/council/prompts/planner_a.md +16 -16
- package/src/devcouncil/council/prompts/planner_b.md +16 -16
- package/src/devcouncil/council/prompts/rebuttal.md +10 -10
- package/src/devcouncil/council/prompts/spec_writer.md +12 -12
- package/src/devcouncil/domain/assumption.py +17 -17
- package/src/devcouncil/domain/critique.py +32 -32
- package/src/devcouncil/domain/evidence.py +47 -27
- package/src/devcouncil/domain/gap.py +52 -26
- package/src/devcouncil/domain/requirement.py +22 -22
- package/src/devcouncil/domain/task.py +55 -26
- package/src/devcouncil/execution/__init__.py +1 -1
- package/src/devcouncil/execution/checkpoints.py +246 -0
- package/src/devcouncil/execution/context_builder.py +54 -54
- package/src/devcouncil/execution/executor.py +15 -15
- package/src/devcouncil/execution/fs_watcher.py +180 -0
- package/src/devcouncil/execution/handoff.py +102 -0
- package/src/devcouncil/execution/hook_policy.py +186 -77
- package/src/devcouncil/execution/patch.py +77 -28
- package/src/devcouncil/execution/permissions.py +52 -59
- package/src/devcouncil/execution/policy_engine.py +343 -0
- package/src/devcouncil/execution/prompt_builder.py +650 -38
- package/src/devcouncil/execution/shell_session.py +225 -0
- package/src/devcouncil/execution/task_runner.py +68 -64
- package/src/devcouncil/executors/__init__.py +1 -1
- package/src/devcouncil/executors/agent_registry.py +575 -0
- package/src/devcouncil/executors/coding_cli.py +736 -0
- package/src/devcouncil/executors/mini_swe.py +63 -63
- package/src/devcouncil/executors/native/agent.py +186 -85
- package/src/devcouncil/executors/openhands.py +56 -56
- package/src/devcouncil/gating/__init__.py +1 -1
- package/src/devcouncil/gating/checks/clean_git.py +52 -45
- package/src/devcouncil/gating/checks/planned_files_check.py +32 -32
- package/src/devcouncil/gating/checks/requirement_coverage.py +26 -26
- package/src/devcouncil/gating/checks/secret_scan_check.py +53 -34
- package/src/devcouncil/gating/policy.py +315 -167
- package/src/devcouncil/hardware.py +184 -0
- package/src/devcouncil/indexing/__init__.py +1 -1
- package/src/devcouncil/indexing/ast_matcher.py +168 -0
- package/src/devcouncil/indexing/graph_index.py +48 -48
- package/src/devcouncil/indexing/lsp.py +161 -0
- package/src/devcouncil/indexing/repo_mapper.py +1455 -204
- package/src/devcouncil/indexing/semantic_index.py +205 -0
- package/src/devcouncil/integrations/actions.py +146 -0
- package/src/devcouncil/integrations/check.py +423 -0
- package/src/devcouncil/integrations/github.py +35 -35
- package/src/devcouncil/integrations/github_intent.py +142 -0
- package/src/devcouncil/integrations/gitnexus.py +62 -27
- package/src/devcouncil/integrations/graphify.py +34 -34
- package/src/devcouncil/integrations/mcp/server.py +2072 -96
- package/src/devcouncil/integrations/opencode_devcouncil_plugin.mjs +24 -0
- package/src/devcouncil/integrations/pr_comments.py +62 -0
- package/src/devcouncil/live/__init__.py +2 -0
- package/src/devcouncil/live/cards.py +349 -0
- package/src/devcouncil/live/models.py +63 -0
- package/src/devcouncil/live/repair_prompt.py +83 -0
- package/src/devcouncil/live/reviewer.py +70 -0
- package/src/devcouncil/live/signals.py +135 -0
- package/src/devcouncil/live/summary.py +34 -0
- package/src/devcouncil/live/tasks.py +18 -0
- package/src/devcouncil/live/transcripts.py +141 -0
- package/src/devcouncil/llm/__init__.py +1 -1
- package/src/devcouncil/llm/cache.py +42 -38
- package/src/devcouncil/llm/model_defaults.yaml +44 -0
- package/src/devcouncil/llm/provider.py +627 -125
- package/src/devcouncil/llm/router.py +303 -118
- package/src/devcouncil/optimization/__init__.py +1 -0
- package/src/devcouncil/optimization/gepa_agent.py +318 -0
- package/src/devcouncil/planning/__init__.py +1 -1
- package/src/devcouncil/planning/arbiter_service.py +57 -57
- package/src/devcouncil/planning/correction_manifest.py +303 -0
- package/src/devcouncil/planning/critique_service.py +71 -66
- package/src/devcouncil/planning/plan_service.py +60 -46
- package/src/devcouncil/planning/prompt_enhancer_service.py +167 -0
- package/src/devcouncil/planning/repair_service.py +39 -39
- package/src/devcouncil/planning/spec_service.py +70 -44
- package/src/devcouncil/repo/ci_scaffold.py +157 -0
- package/src/devcouncil/repo/gitignore.py +123 -0
- package/src/devcouncil/repo/sca.py +374 -0
- package/src/devcouncil/reporting/github_check.py +32 -32
- package/src/devcouncil/reporting/json_report.py +30 -17
- package/src/devcouncil/reporting/markdown_report.py +83 -46
- package/src/devcouncil/reporting/report_builder.py +14 -14
- 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 +330 -0
- package/src/devcouncil/storage/db.py +147 -66
- package/src/devcouncil/storage/models.py +204 -83
- package/src/devcouncil/storage/native.py +557 -0
- package/src/devcouncil/storage/repositories.py +388 -249
- package/src/devcouncil/telemetry/cost.py +140 -34
- package/src/devcouncil/telemetry/model_pricing.yaml +48 -0
- package/src/devcouncil/telemetry/pricing.py +28 -0
- package/src/devcouncil/telemetry/traces.py +62 -7
- package/src/devcouncil/telemetry/tracker.py +52 -49
- package/src/devcouncil/ui/__init__.py +1 -0
- package/src/devcouncil/ui/dashboard.py +423 -0
- package/src/devcouncil/utils/__init__.py +1 -1
- package/src/devcouncil/utils/redaction.py +147 -141
- package/src/devcouncil/utils/subprocess_env.py +69 -0
- package/src/devcouncil/verification/__init__.py +1 -1
- package/src/devcouncil/verification/acceptance_compiler.py +125 -0
- package/src/devcouncil/verification/ad_hoc_check.py +129 -0
- package/src/devcouncil/verification/diff_coverage.py +353 -0
- package/src/devcouncil/verification/implementation_reviewer.py +55 -55
- package/src/devcouncil/verification/next_actions.py +189 -0
- package/src/devcouncil/verification/sandbox.py +178 -0
- package/src/devcouncil/verification/test_resolver.py +91 -0
- package/src/devcouncil/verification/verifier.py +1342 -307
- package/uv.lock +205 -64
- package/src/devcouncil/indexing/symbol_index.py +0 -0
|
@@ -0,0 +1,178 @@
|
|
|
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)
|
|
@@ -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"
|