ai-dev-cli-tools 0.5.0a1__py3-none-any.whl
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.
- ai_dev_cli_tools-0.5.0a1.dist-info/METADATA +240 -0
- ai_dev_cli_tools-0.5.0a1.dist-info/RECORD +60 -0
- ai_dev_cli_tools-0.5.0a1.dist-info/WHEEL +4 -0
- ai_dev_cli_tools-0.5.0a1.dist-info/entry_points.txt +2 -0
- ai_dev_cli_tools-0.5.0a1.dist-info/licenses/LICENSE +21 -0
- ai_dev_tools/__init__.py +3 -0
- ai_dev_tools/cache/__init__.py +11 -0
- ai_dev_tools/cache/graph.py +136 -0
- ai_dev_tools/cache/repository.py +169 -0
- ai_dev_tools/cache/validation.py +154 -0
- ai_dev_tools/cli.py +387 -0
- ai_dev_tools/completion.py +72 -0
- ai_dev_tools/config.py +223 -0
- ai_dev_tools/context/__init__.py +5 -0
- ai_dev_tools/context/builder.py +506 -0
- ai_dev_tools/context/incremental.py +107 -0
- ai_dev_tools/context/models.py +59 -0
- ai_dev_tools/context/profiles.py +49 -0
- ai_dev_tools/context/selection.py +270 -0
- ai_dev_tools/context/symbols.py +178 -0
- ai_dev_tools/detectors/__init__.py +1 -0
- ai_dev_tools/detectors/environment.py +125 -0
- ai_dev_tools/detectors/project.py +189 -0
- ai_dev_tools/detectors/repository_map.py +129 -0
- ai_dev_tools/detectors/runtime.py +190 -0
- ai_dev_tools/detectors/workspaces.py +228 -0
- ai_dev_tools/git/__init__.py +1 -0
- ai_dev_tools/git/inspect.py +219 -0
- ai_dev_tools/models/__init__.py +1 -0
- ai_dev_tools/models/report.py +95 -0
- ai_dev_tools/models/workspace.py +48 -0
- ai_dev_tools/parsers/__init__.py +1 -0
- ai_dev_tools/parsers/logs.py +372 -0
- ai_dev_tools/parsers/registry.py +60 -0
- ai_dev_tools/reporters/__init__.py +1 -0
- ai_dev_tools/reporters/progressive.py +161 -0
- ai_dev_tools/reporters/writer.py +74 -0
- ai_dev_tools/runners/__init__.py +1 -0
- ai_dev_tools/runners/baseline.py +190 -0
- ai_dev_tools/runners/bootstrap.py +191 -0
- ai_dev_tools/runners/bootstrap_models.py +64 -0
- ai_dev_tools/runners/bootstrap_strategies.py +444 -0
- ai_dev_tools/runners/cache.py +23 -0
- ai_dev_tools/runners/check.py +509 -0
- ai_dev_tools/runners/check_checkpoint.py +50 -0
- ai_dev_tools/runners/check_models.py +51 -0
- ai_dev_tools/runners/check_scheduler.py +94 -0
- ai_dev_tools/runners/check_selection.py +267 -0
- ai_dev_tools/runners/diagnostics.py +96 -0
- ai_dev_tools/runners/feedback.py +193 -0
- ai_dev_tools/runners/finish.py +105 -0
- ai_dev_tools/runners/focused.py +37 -0
- ai_dev_tools/runners/index.py +44 -0
- ai_dev_tools/runtime/__init__.py +3 -0
- ai_dev_tools/runtime/runner.py +380 -0
- ai_dev_tools/runtime/supervisor.py +145 -0
- ai_dev_tools/security/__init__.py +1 -0
- ai_dev_tools/security/secrets.py +58 -0
- ai_dev_tools/utils/__init__.py +1 -0
- ai_dev_tools/utils/subprocess.py +74 -0
|
@@ -0,0 +1,444 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
import tomllib
|
|
6
|
+
from dataclasses import replace
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from ai_dev_tools.config import BootstrapSettings, Settings, load_settings
|
|
10
|
+
from ai_dev_tools.detectors.workspaces import detect_workspaces
|
|
11
|
+
from ai_dev_tools.runners.bootstrap_models import BootstrapOptions, BootstrapPlan, BootstrapStep
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def build_bootstrap_plan(settings: Settings, options: BootstrapOptions) -> BootstrapPlan:
|
|
15
|
+
root = settings.project_root
|
|
16
|
+
configured_create_env = settings.bootstrap.create_env or options.create_env
|
|
17
|
+
before = _custom_steps(
|
|
18
|
+
settings.bootstrap.commands_before, "configured bootstrap before command"
|
|
19
|
+
)
|
|
20
|
+
after = _custom_steps(settings.bootstrap.commands_after, "configured bootstrap after command")
|
|
21
|
+
env_available = (root / ".env.example").exists()
|
|
22
|
+
env_will_create = configured_create_env and env_available and not (root / ".env").exists()
|
|
23
|
+
env_step = (
|
|
24
|
+
[
|
|
25
|
+
BootstrapStep(
|
|
26
|
+
"create .env from example",
|
|
27
|
+
["copy", ".env.example", ".env"],
|
|
28
|
+
"--create-env or [bootstrap].create_env enabled and .env is missing",
|
|
29
|
+
"detected",
|
|
30
|
+
modifies_project=True,
|
|
31
|
+
action="copy_env",
|
|
32
|
+
)
|
|
33
|
+
]
|
|
34
|
+
if env_will_create
|
|
35
|
+
else []
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
strategy = _strategy_for_root(settings)
|
|
39
|
+
monorepo = _detect_subprojects(root)
|
|
40
|
+
workspace_steps, workspace_smoke, workspace_tools = _workspace_bootstrap_steps(settings)
|
|
41
|
+
if strategy is None and not workspace_steps:
|
|
42
|
+
return BootstrapPlan(
|
|
43
|
+
"unknown",
|
|
44
|
+
None,
|
|
45
|
+
[*before, *env_step, *after],
|
|
46
|
+
[],
|
|
47
|
+
[],
|
|
48
|
+
env_available,
|
|
49
|
+
env_will_create,
|
|
50
|
+
monorepo,
|
|
51
|
+
)
|
|
52
|
+
if strategy is None:
|
|
53
|
+
project_type, package_manager = "monorepo", "multiple"
|
|
54
|
+
steps: list[BootstrapStep] = []
|
|
55
|
+
smoke: list[BootstrapStep] = []
|
|
56
|
+
tools: list[str] = []
|
|
57
|
+
else:
|
|
58
|
+
project_type, package_manager, steps, smoke, tools = strategy
|
|
59
|
+
if workspace_steps:
|
|
60
|
+
project_type = "monorepo"
|
|
61
|
+
package_manager = "multiple"
|
|
62
|
+
return BootstrapPlan(
|
|
63
|
+
project_type,
|
|
64
|
+
package_manager,
|
|
65
|
+
[*before, *env_step, *steps, *workspace_steps, *after],
|
|
66
|
+
[*smoke, *workspace_smoke],
|
|
67
|
+
sorted({*tools, *workspace_tools}),
|
|
68
|
+
env_available,
|
|
69
|
+
env_will_create,
|
|
70
|
+
monorepo,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _workspace_bootstrap_steps(
|
|
75
|
+
settings: Settings,
|
|
76
|
+
) -> tuple[list[BootstrapStep], list[BootstrapStep], list[str]]:
|
|
77
|
+
steps: list[BootstrapStep] = []
|
|
78
|
+
smoke_steps: list[BootstrapStep] = []
|
|
79
|
+
tools: list[str] = []
|
|
80
|
+
for workspace in detect_workspaces(settings.project_root):
|
|
81
|
+
if not workspace.root:
|
|
82
|
+
continue
|
|
83
|
+
child_settings = load_settings(settings.project_root / workspace.root)
|
|
84
|
+
strategy = _strategy_for_root(child_settings)
|
|
85
|
+
if strategy is None:
|
|
86
|
+
continue
|
|
87
|
+
_, _, child_steps, child_smoke, child_tools = strategy
|
|
88
|
+
steps.extend(
|
|
89
|
+
replace(
|
|
90
|
+
step,
|
|
91
|
+
name=f"{workspace.workspace_id}: {step.name}",
|
|
92
|
+
workspace=workspace.root,
|
|
93
|
+
)
|
|
94
|
+
for step in child_steps
|
|
95
|
+
)
|
|
96
|
+
smoke_steps.extend(
|
|
97
|
+
replace(
|
|
98
|
+
step,
|
|
99
|
+
name=f"{workspace.workspace_id}: {step.name}",
|
|
100
|
+
workspace=workspace.root,
|
|
101
|
+
)
|
|
102
|
+
for step in child_smoke
|
|
103
|
+
)
|
|
104
|
+
tools.extend(child_tools)
|
|
105
|
+
return steps, smoke_steps, sorted(set(tools))
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _strategy_for_root(
|
|
109
|
+
settings: Settings,
|
|
110
|
+
) -> tuple[str, str, list[BootstrapStep], list[BootstrapStep], list[str]] | None:
|
|
111
|
+
root = settings.project_root
|
|
112
|
+
if (root / "uv.lock").exists() and (root / "pyproject.toml").exists():
|
|
113
|
+
return _python_uv_strategy(root)
|
|
114
|
+
if _is_poetry_project(root):
|
|
115
|
+
return _python_poetry_strategy(root)
|
|
116
|
+
if (root / "pyproject.toml").exists():
|
|
117
|
+
return _python_pip_strategy(settings)
|
|
118
|
+
if (root / "requirements.txt").exists():
|
|
119
|
+
return _python_requirements_strategy(settings)
|
|
120
|
+
if (root / "package.json").exists():
|
|
121
|
+
return _node_strategy(root, settings.bootstrap)
|
|
122
|
+
if (root / "pom.xml").exists():
|
|
123
|
+
return _maven_strategy(root)
|
|
124
|
+
if (root / "build.gradle").exists() or (root / "build.gradle.kts").exists():
|
|
125
|
+
return _gradle_strategy(root)
|
|
126
|
+
if (root / "Cargo.toml").exists():
|
|
127
|
+
return _simple_strategy("rust", "cargo", "cargo fetch", ["cargo", "fetch"], "cargo")
|
|
128
|
+
if (root / "composer.json").exists():
|
|
129
|
+
return _simple_strategy(
|
|
130
|
+
"php",
|
|
131
|
+
"composer",
|
|
132
|
+
"composer install --no-interaction",
|
|
133
|
+
["composer", "install", "--no-interaction"],
|
|
134
|
+
"composer",
|
|
135
|
+
)
|
|
136
|
+
return None
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _python_uv_strategy(
|
|
140
|
+
root: Path,
|
|
141
|
+
) -> tuple[str, str, list[BootstrapStep], list[BootstrapStep], list[str]]:
|
|
142
|
+
return (
|
|
143
|
+
"python",
|
|
144
|
+
"uv",
|
|
145
|
+
[
|
|
146
|
+
BootstrapStep(
|
|
147
|
+
"uv sync",
|
|
148
|
+
["uv", "sync"],
|
|
149
|
+
"uv.lock and pyproject.toml detected",
|
|
150
|
+
"detected",
|
|
151
|
+
required_tool="uv",
|
|
152
|
+
)
|
|
153
|
+
],
|
|
154
|
+
_pytest_smoke(["uv", "run", "pytest", "--collect-only"], root, "uv"),
|
|
155
|
+
["uv"],
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def _python_poetry_strategy(
|
|
160
|
+
root: Path,
|
|
161
|
+
) -> tuple[str, str, list[BootstrapStep], list[BootstrapStep], list[str]]:
|
|
162
|
+
return (
|
|
163
|
+
"python",
|
|
164
|
+
"poetry",
|
|
165
|
+
[
|
|
166
|
+
BootstrapStep(
|
|
167
|
+
"poetry install",
|
|
168
|
+
["poetry", "install"],
|
|
169
|
+
"Poetry configuration detected",
|
|
170
|
+
"detected",
|
|
171
|
+
required_tool="poetry",
|
|
172
|
+
)
|
|
173
|
+
],
|
|
174
|
+
_pytest_smoke(["poetry", "run", "pytest", "--collect-only"], root, "poetry"),
|
|
175
|
+
["poetry"],
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def _python_pip_strategy(
|
|
180
|
+
settings: Settings,
|
|
181
|
+
) -> tuple[str, str, list[BootstrapStep], list[BootstrapStep], list[str]]:
|
|
182
|
+
root = settings.project_root
|
|
183
|
+
venv_dir = root / settings.bootstrap.python_venv
|
|
184
|
+
venv_python = _venv_python(settings.bootstrap.python_venv)
|
|
185
|
+
steps: list[BootstrapStep] = []
|
|
186
|
+
if not venv_dir.exists():
|
|
187
|
+
steps.append(
|
|
188
|
+
BootstrapStep(
|
|
189
|
+
"create virtual environment",
|
|
190
|
+
[sys.executable, "-m", "venv", settings.bootstrap.python_venv],
|
|
191
|
+
"pip bootstrap uses an isolated local virtual environment",
|
|
192
|
+
"detected",
|
|
193
|
+
action="create_venv",
|
|
194
|
+
)
|
|
195
|
+
)
|
|
196
|
+
steps.append(
|
|
197
|
+
BootstrapStep(
|
|
198
|
+
"upgrade pip",
|
|
199
|
+
[venv_python, "-m", "pip", "install", "--upgrade", "pip"],
|
|
200
|
+
"ensure local virtual environment has a current pip",
|
|
201
|
+
"detected",
|
|
202
|
+
)
|
|
203
|
+
)
|
|
204
|
+
if _is_installable_python_project(root):
|
|
205
|
+
steps.append(
|
|
206
|
+
BootstrapStep(
|
|
207
|
+
"install project editable",
|
|
208
|
+
[venv_python, "-m", "pip", "install", "-e", "."],
|
|
209
|
+
"installable Python project metadata detected",
|
|
210
|
+
"detected",
|
|
211
|
+
)
|
|
212
|
+
)
|
|
213
|
+
return (
|
|
214
|
+
"python",
|
|
215
|
+
"pip",
|
|
216
|
+
steps,
|
|
217
|
+
_pytest_smoke([venv_python, "-m", "pytest", "--collect-only"], root, None),
|
|
218
|
+
["python"],
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def _python_requirements_strategy(
|
|
223
|
+
settings: Settings,
|
|
224
|
+
) -> tuple[str, str, list[BootstrapStep], list[BootstrapStep], list[str]]:
|
|
225
|
+
project_type, package_manager, steps, smoke, tools = _python_pip_strategy(settings)
|
|
226
|
+
venv_python = _venv_python(settings.bootstrap.python_venv)
|
|
227
|
+
steps.append(
|
|
228
|
+
BootstrapStep(
|
|
229
|
+
"install requirements",
|
|
230
|
+
[venv_python, "-m", "pip", "install", "-r", "requirements.txt"],
|
|
231
|
+
"requirements.txt detected",
|
|
232
|
+
"detected",
|
|
233
|
+
)
|
|
234
|
+
)
|
|
235
|
+
return project_type, package_manager, steps, smoke, tools
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def _node_strategy(
|
|
239
|
+
root: Path, bootstrap: BootstrapSettings
|
|
240
|
+
) -> tuple[str, str, list[BootstrapStep], list[BootstrapStep], list[str]]:
|
|
241
|
+
package_text = (root / "package.json").read_text(encoding="utf-8", errors="replace")
|
|
242
|
+
package_manager = _node_package_manager(root, package_text)
|
|
243
|
+
command: list[str]
|
|
244
|
+
reason: str
|
|
245
|
+
if package_manager == "pnpm":
|
|
246
|
+
command = ["pnpm", "install"]
|
|
247
|
+
if bootstrap.node_frozen_lockfile and (root / "pnpm-lock.yaml").exists():
|
|
248
|
+
command.append("--frozen-lockfile")
|
|
249
|
+
reason = "pnpm lockfile or packageManager detected"
|
|
250
|
+
elif package_manager == "yarn":
|
|
251
|
+
command = ["yarn", "install"]
|
|
252
|
+
if bootstrap.node_frozen_lockfile and (root / "yarn.lock").exists():
|
|
253
|
+
command.append("--immutable")
|
|
254
|
+
reason = "yarn lockfile or packageManager detected"
|
|
255
|
+
else:
|
|
256
|
+
command = ["npm", "ci"] if (root / "package-lock.json").exists() else ["npm", "install"]
|
|
257
|
+
reason = "npm lockfile detected" if command[-1] == "ci" else "npm fallback without lockfile"
|
|
258
|
+
return (
|
|
259
|
+
"node",
|
|
260
|
+
package_manager,
|
|
261
|
+
[
|
|
262
|
+
BootstrapStep(
|
|
263
|
+
"install node dependencies",
|
|
264
|
+
command,
|
|
265
|
+
reason,
|
|
266
|
+
"detected",
|
|
267
|
+
required_tool=package_manager,
|
|
268
|
+
)
|
|
269
|
+
],
|
|
270
|
+
[],
|
|
271
|
+
[package_manager],
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def _maven_strategy(
|
|
276
|
+
root: Path,
|
|
277
|
+
) -> tuple[str, str, list[BootstrapStep], list[BootstrapStep], list[str]]:
|
|
278
|
+
wrapper = _wrapper(root, "mvnw", "mvnw.cmd")
|
|
279
|
+
command = [wrapper or "mvn", "dependency:go-offline"]
|
|
280
|
+
tool = None if wrapper else "maven"
|
|
281
|
+
return (
|
|
282
|
+
"java",
|
|
283
|
+
"maven",
|
|
284
|
+
[
|
|
285
|
+
BootstrapStep(
|
|
286
|
+
"maven go offline",
|
|
287
|
+
command,
|
|
288
|
+
"pom.xml detected; wrapper preferred when present",
|
|
289
|
+
"detected",
|
|
290
|
+
required_tool=tool,
|
|
291
|
+
)
|
|
292
|
+
],
|
|
293
|
+
[],
|
|
294
|
+
[] if wrapper else ["maven"],
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def _gradle_strategy(
|
|
299
|
+
root: Path,
|
|
300
|
+
) -> tuple[str, str, list[BootstrapStep], list[BootstrapStep], list[str]]:
|
|
301
|
+
wrapper = _wrapper(root, "gradlew", "gradlew.bat")
|
|
302
|
+
command = [wrapper or "gradle", "dependencies"]
|
|
303
|
+
tool = None if wrapper else "gradle"
|
|
304
|
+
return (
|
|
305
|
+
"java",
|
|
306
|
+
"gradle",
|
|
307
|
+
[
|
|
308
|
+
BootstrapStep(
|
|
309
|
+
"gradle dependencies",
|
|
310
|
+
command,
|
|
311
|
+
"Gradle build file detected; wrapper preferred when present",
|
|
312
|
+
"detected",
|
|
313
|
+
required_tool=tool,
|
|
314
|
+
)
|
|
315
|
+
],
|
|
316
|
+
[],
|
|
317
|
+
[] if wrapper else ["gradle"],
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def _simple_strategy(
|
|
322
|
+
project_type: str, package_manager: str, name: str, command: list[str], tool: str
|
|
323
|
+
) -> tuple[str, str, list[BootstrapStep], list[BootstrapStep], list[str]]:
|
|
324
|
+
return (
|
|
325
|
+
project_type,
|
|
326
|
+
package_manager,
|
|
327
|
+
[
|
|
328
|
+
BootstrapStep(
|
|
329
|
+
name,
|
|
330
|
+
command,
|
|
331
|
+
f"{package_manager} project file detected",
|
|
332
|
+
"detected",
|
|
333
|
+
required_tool=tool,
|
|
334
|
+
)
|
|
335
|
+
],
|
|
336
|
+
[],
|
|
337
|
+
[tool],
|
|
338
|
+
)
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
def _pytest_smoke(command: list[str], root: Path, tool: str | None) -> list[BootstrapStep]:
|
|
342
|
+
if (root / "tests").exists() or "pytest" in _pyproject_text(root):
|
|
343
|
+
return [
|
|
344
|
+
BootstrapStep(
|
|
345
|
+
"pytest collect only",
|
|
346
|
+
command,
|
|
347
|
+
"pytest configuration or tests directory detected",
|
|
348
|
+
"detected",
|
|
349
|
+
modifies_project=False,
|
|
350
|
+
required_tool=tool,
|
|
351
|
+
)
|
|
352
|
+
]
|
|
353
|
+
return []
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
def _custom_steps(commands: list[list[str]], reason: str) -> list[BootstrapStep]:
|
|
357
|
+
return [
|
|
358
|
+
BootstrapStep(
|
|
359
|
+
"configured command",
|
|
360
|
+
command,
|
|
361
|
+
reason,
|
|
362
|
+
"configured",
|
|
363
|
+
required_tool=command[0] if command else None,
|
|
364
|
+
)
|
|
365
|
+
for command in commands
|
|
366
|
+
]
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
def _is_poetry_project(root: Path) -> bool:
|
|
370
|
+
text = _pyproject_text(root)
|
|
371
|
+
return "[tool.poetry]" in text or "poetry-core" in text
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
def _is_installable_python_project(root: Path) -> bool:
|
|
375
|
+
if (root / "setup.py").exists() or (root / "setup.cfg").exists():
|
|
376
|
+
return True
|
|
377
|
+
try:
|
|
378
|
+
data = tomllib.loads(_pyproject_text(root))
|
|
379
|
+
except tomllib.TOMLDecodeError:
|
|
380
|
+
return False
|
|
381
|
+
return isinstance(data.get("project"), dict)
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
def _pyproject_text(root: Path) -> str:
|
|
385
|
+
path = root / "pyproject.toml"
|
|
386
|
+
return path.read_text(encoding="utf-8", errors="replace") if path.exists() else ""
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
def _node_package_manager(root: Path, package_text: str) -> str:
|
|
390
|
+
if (root / "pnpm-lock.yaml").exists():
|
|
391
|
+
return "pnpm"
|
|
392
|
+
if (root / "yarn.lock").exists():
|
|
393
|
+
return "yarn"
|
|
394
|
+
if (root / "package-lock.json").exists():
|
|
395
|
+
return "npm"
|
|
396
|
+
lowered = package_text.lower()
|
|
397
|
+
if '"packagemanager"' in lowered:
|
|
398
|
+
if "pnpm@" in lowered:
|
|
399
|
+
return "pnpm"
|
|
400
|
+
if "yarn@" in lowered:
|
|
401
|
+
return "yarn"
|
|
402
|
+
return "npm"
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
def _wrapper(root: Path, posix_name: str, windows_name: str) -> str | None:
|
|
406
|
+
windows = root / windows_name
|
|
407
|
+
posix = root / posix_name
|
|
408
|
+
if os.name == "nt" and windows.exists():
|
|
409
|
+
return windows_name
|
|
410
|
+
if posix.exists():
|
|
411
|
+
return f"./{posix_name}"
|
|
412
|
+
if windows.exists():
|
|
413
|
+
return windows_name
|
|
414
|
+
return None
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
def _venv_python(venv: str) -> str:
|
|
418
|
+
if os.name == "nt":
|
|
419
|
+
return str(Path(venv) / "Scripts" / "python.exe")
|
|
420
|
+
return str(Path(venv) / "bin" / "python")
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
def _detect_subprojects(root: Path) -> list[str]:
|
|
424
|
+
signals = {
|
|
425
|
+
"pyproject.toml",
|
|
426
|
+
"requirements.txt",
|
|
427
|
+
"package.json",
|
|
428
|
+
"pom.xml",
|
|
429
|
+
"build.gradle",
|
|
430
|
+
"build.gradle.kts",
|
|
431
|
+
"Cargo.toml",
|
|
432
|
+
"composer.json",
|
|
433
|
+
}
|
|
434
|
+
found: list[str] = []
|
|
435
|
+
for path in root.rglob("*"):
|
|
436
|
+
if not path.is_file() or path.name not in signals:
|
|
437
|
+
continue
|
|
438
|
+
rel = path.relative_to(root)
|
|
439
|
+
if len(rel.parts) <= 1 or any(
|
|
440
|
+
part in {".git", "node_modules", ".venv"} for part in rel.parts
|
|
441
|
+
):
|
|
442
|
+
continue
|
|
443
|
+
found.append(str(rel.parent.as_posix()))
|
|
444
|
+
return sorted(set(found))[:25]
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from ai_dev_tools.cache.validation import (
|
|
6
|
+
clear_validation_cache,
|
|
7
|
+
prune_validation_cache,
|
|
8
|
+
validation_cache_stats,
|
|
9
|
+
)
|
|
10
|
+
from ai_dev_tools.models.report import Report
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def run_cache(project_root: Path, action: str) -> Report:
|
|
14
|
+
root = project_root.resolve()
|
|
15
|
+
report = Report(command=f"cache {action}", project_root=root)
|
|
16
|
+
if action == "status":
|
|
17
|
+
result = validation_cache_stats(root)
|
|
18
|
+
elif action == "prune":
|
|
19
|
+
result = prune_validation_cache(root)
|
|
20
|
+
else:
|
|
21
|
+
result = clear_validation_cache(root)
|
|
22
|
+
report.summary = {"validation_cache": result}
|
|
23
|
+
return report
|