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.
Files changed (60) hide show
  1. ai_dev_cli_tools-0.5.0a1.dist-info/METADATA +240 -0
  2. ai_dev_cli_tools-0.5.0a1.dist-info/RECORD +60 -0
  3. ai_dev_cli_tools-0.5.0a1.dist-info/WHEEL +4 -0
  4. ai_dev_cli_tools-0.5.0a1.dist-info/entry_points.txt +2 -0
  5. ai_dev_cli_tools-0.5.0a1.dist-info/licenses/LICENSE +21 -0
  6. ai_dev_tools/__init__.py +3 -0
  7. ai_dev_tools/cache/__init__.py +11 -0
  8. ai_dev_tools/cache/graph.py +136 -0
  9. ai_dev_tools/cache/repository.py +169 -0
  10. ai_dev_tools/cache/validation.py +154 -0
  11. ai_dev_tools/cli.py +387 -0
  12. ai_dev_tools/completion.py +72 -0
  13. ai_dev_tools/config.py +223 -0
  14. ai_dev_tools/context/__init__.py +5 -0
  15. ai_dev_tools/context/builder.py +506 -0
  16. ai_dev_tools/context/incremental.py +107 -0
  17. ai_dev_tools/context/models.py +59 -0
  18. ai_dev_tools/context/profiles.py +49 -0
  19. ai_dev_tools/context/selection.py +270 -0
  20. ai_dev_tools/context/symbols.py +178 -0
  21. ai_dev_tools/detectors/__init__.py +1 -0
  22. ai_dev_tools/detectors/environment.py +125 -0
  23. ai_dev_tools/detectors/project.py +189 -0
  24. ai_dev_tools/detectors/repository_map.py +129 -0
  25. ai_dev_tools/detectors/runtime.py +190 -0
  26. ai_dev_tools/detectors/workspaces.py +228 -0
  27. ai_dev_tools/git/__init__.py +1 -0
  28. ai_dev_tools/git/inspect.py +219 -0
  29. ai_dev_tools/models/__init__.py +1 -0
  30. ai_dev_tools/models/report.py +95 -0
  31. ai_dev_tools/models/workspace.py +48 -0
  32. ai_dev_tools/parsers/__init__.py +1 -0
  33. ai_dev_tools/parsers/logs.py +372 -0
  34. ai_dev_tools/parsers/registry.py +60 -0
  35. ai_dev_tools/reporters/__init__.py +1 -0
  36. ai_dev_tools/reporters/progressive.py +161 -0
  37. ai_dev_tools/reporters/writer.py +74 -0
  38. ai_dev_tools/runners/__init__.py +1 -0
  39. ai_dev_tools/runners/baseline.py +190 -0
  40. ai_dev_tools/runners/bootstrap.py +191 -0
  41. ai_dev_tools/runners/bootstrap_models.py +64 -0
  42. ai_dev_tools/runners/bootstrap_strategies.py +444 -0
  43. ai_dev_tools/runners/cache.py +23 -0
  44. ai_dev_tools/runners/check.py +509 -0
  45. ai_dev_tools/runners/check_checkpoint.py +50 -0
  46. ai_dev_tools/runners/check_models.py +51 -0
  47. ai_dev_tools/runners/check_scheduler.py +94 -0
  48. ai_dev_tools/runners/check_selection.py +267 -0
  49. ai_dev_tools/runners/diagnostics.py +96 -0
  50. ai_dev_tools/runners/feedback.py +193 -0
  51. ai_dev_tools/runners/finish.py +105 -0
  52. ai_dev_tools/runners/focused.py +37 -0
  53. ai_dev_tools/runners/index.py +44 -0
  54. ai_dev_tools/runtime/__init__.py +3 -0
  55. ai_dev_tools/runtime/runner.py +380 -0
  56. ai_dev_tools/runtime/supervisor.py +145 -0
  57. ai_dev_tools/security/__init__.py +1 -0
  58. ai_dev_tools/security/secrets.py +58 -0
  59. ai_dev_tools/utils/__init__.py +1 -0
  60. 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
@@ -0,0 +1,5 @@
1
+ from __future__ import annotations
2
+
3
+ from ai_dev_tools.context.builder import ContextOptions, build_context
4
+
5
+ __all__ = ["ContextOptions", "build_context"]