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
ai_dev_tools/config.py
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import tomllib
|
|
4
|
+
from dataclasses import dataclass, field
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
DEFAULT_IGNORES = {
|
|
9
|
+
".ai",
|
|
10
|
+
".ai/logs",
|
|
11
|
+
".ai/reports",
|
|
12
|
+
".git",
|
|
13
|
+
".mypy_cache",
|
|
14
|
+
".pytest_cache",
|
|
15
|
+
".ruff_cache",
|
|
16
|
+
".venv",
|
|
17
|
+
"__pycache__",
|
|
18
|
+
"build",
|
|
19
|
+
"dist",
|
|
20
|
+
"node_modules",
|
|
21
|
+
"venv",
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass(slots=True)
|
|
26
|
+
class BootstrapSettings:
|
|
27
|
+
create_env: bool = False
|
|
28
|
+
run_smoke_check: bool = True
|
|
29
|
+
timeout_seconds: int = 900
|
|
30
|
+
commands_before: list[list[str]] = field(default_factory=list)
|
|
31
|
+
commands_after: list[list[str]] = field(default_factory=list)
|
|
32
|
+
python_venv: str = ".venv"
|
|
33
|
+
node_frozen_lockfile: bool = True
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass(slots=True)
|
|
37
|
+
class Settings:
|
|
38
|
+
project_root: Path
|
|
39
|
+
reports_directory: Path
|
|
40
|
+
logs_directory: Path
|
|
41
|
+
commands: dict[str, str] = field(default_factory=dict)
|
|
42
|
+
ignore_paths: set[str] = field(default_factory=lambda: set(DEFAULT_IGNORES))
|
|
43
|
+
project_name: str | None = None
|
|
44
|
+
changed_tests: dict[str, list[str]] = field(default_factory=dict)
|
|
45
|
+
warnings: list[str] = field(default_factory=list)
|
|
46
|
+
bootstrap: BootstrapSettings = field(default_factory=BootstrapSettings)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def load_settings(project_root: Path) -> Settings:
|
|
50
|
+
root = project_root.resolve()
|
|
51
|
+
data: dict[str, Any] = {}
|
|
52
|
+
config_path = root / ".ai-dev-tools.toml"
|
|
53
|
+
if config_path.exists():
|
|
54
|
+
data = tomllib.loads(config_path.read_text(encoding="utf-8"))
|
|
55
|
+
|
|
56
|
+
reports = _section(data, "reports")
|
|
57
|
+
ignore = _section(data, "ignore")
|
|
58
|
+
commands = _section(data, "commands")
|
|
59
|
+
project = _section(data, "project")
|
|
60
|
+
bootstrap = _bootstrap_settings(data)
|
|
61
|
+
|
|
62
|
+
reports_dir = Path(_string_value(reports, "directory", ".ai/reports"))
|
|
63
|
+
logs_dir = Path(_string_value(reports, "logs_directory", ".ai/logs"))
|
|
64
|
+
ignore_paths = set(DEFAULT_IGNORES)
|
|
65
|
+
ignore_paths.update(_string_list(ignore, "paths"))
|
|
66
|
+
|
|
67
|
+
warnings = _config_warnings(data)
|
|
68
|
+
changed_tests = _changed_tests(data)
|
|
69
|
+
|
|
70
|
+
return Settings(
|
|
71
|
+
project_root=root,
|
|
72
|
+
reports_directory=(root / reports_dir).resolve(),
|
|
73
|
+
logs_directory=(root / logs_dir).resolve(),
|
|
74
|
+
commands={key: value for key, value in commands.items() if isinstance(value, str)},
|
|
75
|
+
ignore_paths=ignore_paths,
|
|
76
|
+
project_name=_optional_string(project, "name"),
|
|
77
|
+
changed_tests=changed_tests,
|
|
78
|
+
warnings=warnings,
|
|
79
|
+
bootstrap=bootstrap,
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _section(data: dict[str, Any], key: str) -> dict[str, Any]:
|
|
84
|
+
value = data.get(key, {})
|
|
85
|
+
return value if isinstance(value, dict) else {}
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _string_value(data: dict[str, Any], key: str, default: str) -> str:
|
|
89
|
+
value = data.get(key, default)
|
|
90
|
+
return value if isinstance(value, str) else default
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _optional_string(data: dict[str, Any], key: str) -> str | None:
|
|
94
|
+
value = data.get(key)
|
|
95
|
+
return value if isinstance(value, str) else None
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _string_list(data: dict[str, Any], key: str) -> list[str]:
|
|
99
|
+
value = data.get(key, [])
|
|
100
|
+
if not isinstance(value, list):
|
|
101
|
+
return []
|
|
102
|
+
return [item for item in value if isinstance(item, str)]
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _config_warnings(data: dict[str, Any]) -> list[str]:
|
|
106
|
+
known = {"project", "commands", "ignore", "reports", "changed_tests", "bootstrap"}
|
|
107
|
+
warnings = [f"Unknown top-level config key: {key}" for key in sorted(data) if key not in known]
|
|
108
|
+
for section in known & data.keys():
|
|
109
|
+
if not isinstance(data[section], dict):
|
|
110
|
+
warnings.append(f"Config section [{section}] must be a table")
|
|
111
|
+
warnings.extend(_table_string_warnings(data, "commands"))
|
|
112
|
+
warnings.extend(_table_string_warnings(data, "project", allowed_keys={"name"}))
|
|
113
|
+
warnings.extend(
|
|
114
|
+
_table_string_warnings(data, "reports", allowed_keys={"directory", "logs_directory"})
|
|
115
|
+
)
|
|
116
|
+
warnings.extend(_path_list_warning(data, "ignore", "paths"))
|
|
117
|
+
warnings.extend(_bootstrap_warnings(data))
|
|
118
|
+
return warnings
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _table_string_warnings(
|
|
122
|
+
data: dict[str, Any], section: str, allowed_keys: set[str] | None = None
|
|
123
|
+
) -> list[str]:
|
|
124
|
+
raw = data.get(section, {})
|
|
125
|
+
if not isinstance(raw, dict):
|
|
126
|
+
return []
|
|
127
|
+
warnings: list[str] = []
|
|
128
|
+
for key, value in raw.items():
|
|
129
|
+
if allowed_keys is not None and key not in allowed_keys:
|
|
130
|
+
warnings.append(f"Unknown config key: [{section}].{key}")
|
|
131
|
+
if not isinstance(value, str):
|
|
132
|
+
warnings.append(f"Config value [{section}].{key} must be a string")
|
|
133
|
+
return warnings
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _path_list_warning(data: dict[str, Any], section: str, key: str) -> list[str]:
|
|
137
|
+
raw = data.get(section, {})
|
|
138
|
+
if not isinstance(raw, dict) or key not in raw:
|
|
139
|
+
return []
|
|
140
|
+
value = raw[key]
|
|
141
|
+
if not isinstance(value, list) or not all(isinstance(item, str) for item in value):
|
|
142
|
+
return [f"Config value [{section}].{key} must be a list of strings"]
|
|
143
|
+
return []
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _changed_tests(data: dict[str, Any]) -> dict[str, list[str]]:
|
|
147
|
+
raw = data.get("changed_tests", {})
|
|
148
|
+
if not isinstance(raw, dict):
|
|
149
|
+
return {}
|
|
150
|
+
result: dict[str, list[str]] = {}
|
|
151
|
+
for key, value in raw.items():
|
|
152
|
+
if (
|
|
153
|
+
isinstance(key, str)
|
|
154
|
+
and isinstance(value, list)
|
|
155
|
+
and all(isinstance(item, str) for item in value)
|
|
156
|
+
):
|
|
157
|
+
result[key] = value
|
|
158
|
+
return result
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _bootstrap_settings(data: dict[str, Any]) -> BootstrapSettings:
|
|
162
|
+
bootstrap = _section(data, "bootstrap")
|
|
163
|
+
commands = _section(bootstrap, "commands")
|
|
164
|
+
python = _section(bootstrap, "python")
|
|
165
|
+
node = _section(bootstrap, "node")
|
|
166
|
+
return BootstrapSettings(
|
|
167
|
+
create_env=_bool_value(bootstrap, "create_env", False),
|
|
168
|
+
run_smoke_check=_bool_value(bootstrap, "run_smoke_check", True),
|
|
169
|
+
timeout_seconds=_int_value(bootstrap, "timeout_seconds", 900),
|
|
170
|
+
commands_before=_command_list(commands, "before"),
|
|
171
|
+
commands_after=_command_list(commands, "after"),
|
|
172
|
+
python_venv=_string_value(python, "venv", ".venv"),
|
|
173
|
+
node_frozen_lockfile=_bool_value(node, "frozen_lockfile", True),
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def _bool_value(data: dict[str, Any], key: str, default: bool) -> bool:
|
|
178
|
+
value = data.get(key, default)
|
|
179
|
+
return value if isinstance(value, bool) else default
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def _int_value(data: dict[str, Any], key: str, default: int) -> int:
|
|
183
|
+
value = data.get(key, default)
|
|
184
|
+
return value if isinstance(value, int) and value > 0 else default
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def _command_list(data: dict[str, Any], key: str) -> list[list[str]]:
|
|
188
|
+
value = data.get(key, [])
|
|
189
|
+
if not isinstance(value, list):
|
|
190
|
+
return []
|
|
191
|
+
commands: list[list[str]] = []
|
|
192
|
+
for item in value:
|
|
193
|
+
if isinstance(item, list) and item and all(isinstance(arg, str) for arg in item):
|
|
194
|
+
commands.append(item)
|
|
195
|
+
return commands
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def _bootstrap_warnings(data: dict[str, Any]) -> list[str]:
|
|
199
|
+
raw = data.get("bootstrap", {})
|
|
200
|
+
if not isinstance(raw, dict):
|
|
201
|
+
return []
|
|
202
|
+
allowed = {"create_env", "run_smoke_check", "timeout_seconds", "commands", "python", "node"}
|
|
203
|
+
warnings = [
|
|
204
|
+
f"Unknown config key: [bootstrap].{key}" for key in sorted(raw) if key not in allowed
|
|
205
|
+
]
|
|
206
|
+
commands = _section(raw, "commands")
|
|
207
|
+
for key in ("before", "after"):
|
|
208
|
+
value = commands.get(key, [])
|
|
209
|
+
invalid_commands = not isinstance(value, list) or not all(
|
|
210
|
+
isinstance(item, list) and item and all(isinstance(arg, str) for arg in item)
|
|
211
|
+
for item in value
|
|
212
|
+
)
|
|
213
|
+
if key in commands and invalid_commands:
|
|
214
|
+
warnings.append(
|
|
215
|
+
f"Config value [bootstrap.commands].{key} must be a list of argument lists"
|
|
216
|
+
)
|
|
217
|
+
python = _section(raw, "python")
|
|
218
|
+
if "venv" in python and not isinstance(python["venv"], str):
|
|
219
|
+
warnings.append("Config value [bootstrap.python].venv must be a string")
|
|
220
|
+
node = _section(raw, "node")
|
|
221
|
+
if "frozen_lockfile" in node and not isinstance(node["frozen_lockfile"], bool):
|
|
222
|
+
warnings.append("Config value [bootstrap.node].frozen_lockfile must be a boolean")
|
|
223
|
+
return warnings
|