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
@@ -0,0 +1,240 @@
1
+ Metadata-Version: 2.4
2
+ Name: ai-dev-cli-tools
3
+ Version: 0.5.0a1
4
+ Summary: Cross-platform CLI helpers that give AI coding agents concise, deterministic development reports.
5
+ Project-URL: Homepage, https://github.com/MatthiasLew/ai-dev-cli-tools
6
+ Project-URL: Repository, https://github.com/MatthiasLew/ai-dev-cli-tools
7
+ Project-URL: Issues, https://github.com/MatthiasLew/ai-dev-cli-tools/issues
8
+ Project-URL: Changelog, https://github.com/MatthiasLew/ai-dev-cli-tools/blob/main/CHANGELOG.md
9
+ Author: Mateusz Lewandowski
10
+ License: MIT
11
+ License-File: LICENSE
12
+ Keywords: agents,ai,automation,cli,developer-tools
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Environment :: Console
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Topic :: Software Development :: Quality Assurance
21
+ Requires-Python: >=3.11
22
+ Provides-Extra: dev
23
+ Requires-Dist: build>=1.2; extra == 'dev'
24
+ Requires-Dist: coverage[toml]>=7.6; extra == 'dev'
25
+ Requires-Dist: mypy>=1.11; extra == 'dev'
26
+ Requires-Dist: pytest>=8.3; extra == 'dev'
27
+ Requires-Dist: ruff>=0.6; extra == 'dev'
28
+ Description-Content-Type: text/markdown
29
+
30
+ # AI Dev CLI Tools
31
+
32
+ Cross-platform Python CLI helpers for AI coding agents and humans who want concise, deterministic development reports instead of huge logs.
33
+
34
+ `ai-dev` runs repeatable project checks locally, stores full logs under `.ai/logs/`, and returns compact Markdown/JSON summaries under `.ai/reports/`.
35
+
36
+ ## Problem
37
+
38
+ AI coding agents often spend tokens reading full test output, repository trees, dependency noise, repeated warnings, and raw Git diffs. This project follows one rule:
39
+
40
+ ```text
41
+ Scripts do the work and collect data.
42
+ AI reads a short report and decides what to do next.
43
+ Full logs stay on disk until needed.
44
+
45
+ Generated state under `.ai/logs/`, `.ai/reports/`, `.ai/context/`, `.ai/cache/`,
46
+ `.ai/runtime/`, and `.ai/tmp/` is local and ignored by Git. Logs, reports, and context
47
+ artifacts may be removed when no longer needed; cache, runtime, and temporary state must always
48
+ be safe for `ai-dev` to recreate.
49
+ ```
50
+
51
+ ## Install
52
+
53
+ ```bash
54
+ python -m pip install --upgrade pipx
55
+ pipx install ai-dev-cli-tools==0.5.0a1
56
+ ai-dev --help
57
+ ```
58
+
59
+ For a source checkout before the public alpha is published, use `pipx install .`. See
60
+ `docs/DISTRIBUTION.md` for the Trusted Publishing and upgrade policy.
61
+
62
+ For development:
63
+
64
+ ```bash
65
+ python -m pip install -e ".[dev]"
66
+ ```
67
+
68
+ ## Windows
69
+
70
+ ```powershell
71
+ .\ai.ps1 doctor
72
+ ai-dev scan --project "C:\path with spaces\project"
73
+ ```
74
+
75
+ ## Linux and macOS
76
+
77
+ ```bash
78
+ ./ai.sh doctor
79
+ ai-dev check --mode fast --project "/path/with spaces/project"
80
+ ```
81
+
82
+ ## Commands
83
+
84
+ ```bash
85
+ ai-dev doctor
86
+ ai-dev scan
87
+ ai-dev map --max-files 500 --max-depth 6
88
+ ai-dev check --mode fast
89
+ ai-dev check --mode changed # reports changed files and falls back safely when test mapping is uncertain
90
+ ai-dev check --mode full --jobs 4
91
+ ai-dev check --mode changed --policy feedback-first --resume
92
+ ai-dev index update
93
+ ai-dev cache status
94
+ ai-dev baseline create main
95
+ ai-dev baseline compare main
96
+ ai-dev explain issue:<id> --tail 100
97
+ ai-dev feedback --task "fix authentication timeout"
98
+ ai-dev session status
99
+ ai-dev diagnostics
100
+ ai-dev completion bash
101
+ ai-dev test affected
102
+ ai-dev logs summarize
103
+ ai-dev context build
104
+ ai-dev git status
105
+ ai-dev git inspect
106
+ ai-dev finish
107
+ ```
108
+
109
+ All commands support `--project`, `--json`, `--quiet`, `--help`, and `--version` at the top level.
110
+
111
+
112
+ ## Bootstrap
113
+
114
+ `ai-dev bootstrap` prepares a detected project with conservative, project-local commands. Use `--explain` to see the plan without modifications, `--dry-run` to validate planning without executing modifying commands, and `--create-env` to allow copying `.env.example` to `.env` only when `.env` is missing.
115
+
116
+ Supported strategies include Python uv, Poetry, pip with `pyproject.toml`, pip with `requirements.txt`, Node npm/pnpm/Yarn, Maven wrapper or system Maven, Gradle wrapper or system Gradle, Cargo, and Composer.
117
+
118
+ See `docs/BOOTSTRAP.md` for safety rules and configuration.
119
+
120
+ ## Managed application runtime
121
+
122
+ `ai-dev run` supports explain, dry-run, foreground, and supervised background modes.
123
+ `ai-dev stop` sends a token-authenticated request to the matching local supervisor and never
124
+ kills an arbitrary PID read from stale state. See `docs/RUNTIME.md`.
125
+
126
+ ## Context Builder
127
+
128
+ `ai-dev context build` creates a bounded local context package for coding agents without calling any LLM, embedding API, or cloud service.
129
+
130
+ ```bash
131
+ ai-dev context build --task "fix auth tests"
132
+ ai-dev context build --changed-only --max-chars 50000
133
+ ai-dev context build --incremental # emits only candidates changed since the last pack
134
+ ai-dev context build --profile review
135
+ ai-dev context build --include "src/**/*.py" --exclude "tests/fixtures/**"
136
+ ai-dev context build --explain --json
137
+ ```
138
+
139
+ Artifacts are written to `.ai/context/context-latest.md` and `.ai/context/context-latest.json` by default. The builder includes detected technologies, git state, changed files, related tests, validation plan, recent commits, selected snippets, limited diffs, latest check errors, masked secret findings, and budget/truncation metadata. Large Python files use AST-aware symbol snippets with line ranges instead of blindly returning only the beginning of the file.
140
+
141
+ Incremental mode stores a schema-versioned manifest under `.ai/cache/` and reports changed versus reused files. Default limits are `--max-chars 50000`, `--max-files 30`, `--max-file-chars 8000`, and `--max-diff-chars 15000`. Secret-bearing and generated paths such as `.env`, private keys, caches, build output, `.ai/logs`, and `.ai/reports` are excluded from snippets.
142
+ ## Reports and Logs
143
+
144
+ Validation results are cached by default using repository, command, workspace, runtime, and platform fingerprints; use `check --no-cache` to force execution. `check --resume` reuses only exact successful checkpoint fingerprints. `--policy feedback-first` runs cheaper waves first and cancels later expensive waves after a required failure; `complete` retains comprehensive execution. `index status/update/rebuild` manages the reusable repository index, while `cache status/prune/clear` provides bounded local cache maintenance. See `docs/CACHE_AND_INDEX.md`.
145
+
146
+ Short reports are written to `.ai/reports/` as Markdown and JSON. Full command output is written to `.ai/logs/` and ignored by Git. Check summaries include exit codes, durations, first failure hints, grouped repeated messages, test counts, and full log paths.
147
+
148
+ JSON reports use schema `1.1` with `schema_version`, `tool_version`, `command`, `status`, `exit_code`, timestamps, `project_root`, `summary`, `issues`, `artifacts`, and `metadata`.
149
+
150
+ `ai-dev feedback` combines Git changes, changed validation, incremental context, focused rerun hints, stage timings, and local session state into one compact agent protocol report.
151
+
152
+ Every expandable issue, check, file, snippet, diff, workspace, and artifact receives a stable local `evidence_id`. The report metadata lists references; `ai-dev explain <evidence-id> --tail 100` retrieves only that evidence. `ai-dev baseline create <name>` stores a compact local snapshot under `.ai/cache/baselines/`, and `baseline compare <name>` leads with new/resolved failures, issue codes, and status regressions.
153
+
154
+ ## Auto Detection
155
+
156
+ `scan` detects Python, Node.js, Java, Rust, PHP, Docker, Make, CI workflows, package managers, scripts, entrypoints, tests, lint, formatters, type checkers, and `.env.example` variables.
157
+
158
+ Supported examples:
159
+
160
+ - Python: `pyproject.toml`, `requirements.txt`, `pytest`, `ruff`, `black`, `mypy`, `coverage`.
161
+ - Node.js: `package.json`, npm, pnpm, yarn, Jest, Vitest, ESLint, Prettier, TypeScript.
162
+ - Java: Maven, Gradle, tests, Checkstyle, SpotBugs, JaCoCo when configured.
163
+ - Rust: Cargo test, fmt, clippy.
164
+ - PHP: Composer, PHPUnit, PHPStan, PHP-CS-Fixer when configured.
165
+
166
+ ## Configuration
167
+
168
+ Optional `.ai-dev-tools.toml`:
169
+
170
+ ```toml
171
+ [project]
172
+ name = "example-project"
173
+
174
+ [commands]
175
+ test = "pytest"
176
+ lint = "ruff check ."
177
+ typecheck = "mypy src"
178
+
179
+ [ignore]
180
+ paths = ["node_modules", ".venv", "dist", "build"]
181
+
182
+ [reports]
183
+ directory = ".ai/reports"
184
+ logs_directory = ".ai/logs"
185
+ ```
186
+
187
+ Configuration takes precedence over auto detection. Invalid or unknown configuration is reported through `config_warnings` instead of crashing normal scans.
188
+ ## Local Validation
189
+
190
+ Before pushing a larger stage, run:
191
+
192
+ ```bash
193
+ python -m ruff check .
194
+ python -m mypy src tests scripts
195
+ python -m coverage run -m pytest
196
+ python -m coverage report --fail-under=90
197
+ python -m build
198
+ python scripts/validate_ci.py
199
+ python scripts/test_installed_package.py
200
+ git diff --check
201
+ ```
202
+
203
+ `python scripts/test_installed_package.py` rebuilds the wheel, installs only that wheel into a clean virtual environment in a path containing spaces, and verifies the installed `ai-dev` entrypoint without editable install or `PYTHONPATH`.
204
+
205
+ ## Command Status
206
+
207
+ | Command | Status |
208
+ | --- | --- |
209
+ | doctor | implemented |
210
+ | scan | implemented |
211
+ | map | implemented |
212
+ | check | implemented |
213
+ | check --explain | implemented |
214
+ | test affected | implemented |
215
+ | index status/update/rebuild | implemented |
216
+ | cache status/prune/clear | implemented |
217
+ | logs summarize | implemented |
218
+ | context build | implemented |
219
+ | diagnostics | implemented |
220
+ | capabilities | implemented |
221
+ | git status | implemented |
222
+ | git inspect | implemented |
223
+ | finish | implemented |
224
+ | bootstrap | implemented |
225
+ | run | implemented |
226
+ | stop | implemented |
227
+
228
+ ## Scope Notes
229
+
230
+ - Monorepo/workspace detection and per-subproject command routing: implemented.
231
+ - Per-subproject check and bootstrap working directories: implemented.
232
+ - Runtime version validation: partial.
233
+ - `context build` is implemented as a bounded local context pack builder.
234
+ - `bootstrap` is implemented as a conservative local setup planner/executor.
235
+ - Auto-commit, auto-push, and GUI remain out of scope or planned.
236
+
237
+ ## Intentional Limits
238
+ Version 0.5.0a1 does not reset, clean, commit, push, merge, clone organizations, synchronize repositories, delete containers, publish releases, or remove user files.
239
+
240
+ Shell completion scripts are generated with `ai-dev completion bash|zsh|fish|powershell` and can be sourced or installed using the normal mechanism for the selected shell.
@@ -0,0 +1,60 @@
1
+ ai_dev_tools/__init__.py,sha256=gcZy3Dv5W7LjZwt6A24kOUR1D9TCRyDsf_LFbf7unoA,49
2
+ ai_dev_tools/cli.py,sha256=ilgDBx9zc8V9mgwRe_08wzobrKvSoJQh1hFNLQ8pIwQ,14246
3
+ ai_dev_tools/completion.py,sha256=pZwxo4KoueUeDIK1C8013o_T0517nhf1hrzuSnw12DI,1929
4
+ ai_dev_tools/config.py,sha256=uZsEn7xe2XHHnknYmRg1qhg5FNgwBZm3L8zKPVPDezM,7998
5
+ ai_dev_tools/cache/__init__.py,sha256=cQ0ONFQ2MRhwnabuooeiiR9iKgujoWpxFTtgrgnOUn0,235
6
+ ai_dev_tools/cache/graph.py,sha256=WOpm_TLLgUOK4SCxhkt7llEvCKUx12joZgstTJzayVk,5296
7
+ ai_dev_tools/cache/repository.py,sha256=2t9_5vu5U0zaJ1UDhYh35joQPSiv2I8Yw0A9rzNEeoU,5506
8
+ ai_dev_tools/cache/validation.py,sha256=j8Vl9dAsgMc660zzs0yw5qG5j3DTQt6IgUJ2EsStSjA,4141
9
+ ai_dev_tools/context/__init__.py,sha256=QUXob-3eD2d0evw6eTUpy6yaaNrhiGO1-Ynx_y-D4l8,154
10
+ ai_dev_tools/context/builder.py,sha256=6Vb0EhEtifhWAetE2UN9yPkuRy7KOy_fCh0Lbbo6za8,18713
11
+ ai_dev_tools/context/incremental.py,sha256=VNhjdnQxvRKkJgSc-PT7zlBOGLHhYttS85gpft9aIpc,3520
12
+ ai_dev_tools/context/models.py,sha256=tfYnZWvAsUNQ9RsU1etzRz3gX5MAGZn0lxRDruxQj2Y,1452
13
+ ai_dev_tools/context/profiles.py,sha256=VteAM-YNqGHUpOQG3Aq2T60WuR_Ik2-2oOYSrrRACMA,1218
14
+ ai_dev_tools/context/selection.py,sha256=spu7CjZWJBR3VXQs3YBxPOxQit1H145IDz4BKmrQFJQ,9957
15
+ ai_dev_tools/context/symbols.py,sha256=NwEJ3_lBUPc7nZRSMLzfmbvG25gKm8P0IBy1KfeCJNE,5339
16
+ ai_dev_tools/detectors/__init__.py,sha256=FHCytKbvq-UnxBTHvMD3abeEVyAzG-OYRelp84GFfkU,41
17
+ ai_dev_tools/detectors/environment.py,sha256=98Wc7OUog-CKfyrxFr6yZlazeryhsL28tWM2_koLZos,4622
18
+ ai_dev_tools/detectors/project.py,sha256=iS3aykw0GoRItjLR1pdGBC-UuRe-blGviETf4fo0yC0,7213
19
+ ai_dev_tools/detectors/repository_map.py,sha256=4a7p1_6UcleFEDRWnc5cQiJuMqp3e1088sgEneKIwS8,4026
20
+ ai_dev_tools/detectors/runtime.py,sha256=QzVucMF_OSc8EGLUhpAh8HghC3UAtPT7NCn6U4gRRrY,6827
21
+ ai_dev_tools/detectors/workspaces.py,sha256=GnGMw7k9ST8sJRvc3PAs39cJtiMxTYKxFvOKA1DSjio,8404
22
+ ai_dev_tools/git/__init__.py,sha256=72rVU6ALOpCNljDnkRrbyCs4zfWwoSAaF4sRnQKWXPk,30
23
+ ai_dev_tools/git/inspect.py,sha256=bUsUMosmSM6p-CmbVkazDe0eX_oyr6qhT0yTcEP4F2E,7777
24
+ ai_dev_tools/models/__init__.py,sha256=zCWnVsKHBP8Myr5K4p1O3mh5u0h0Ee2cqNvGnHyFXJg,26
25
+ ai_dev_tools/models/report.py,sha256=evyUrZIN98Rv6rLEbS61t_nn5S7BjIF6fIVBRT4vMRg,2848
26
+ ai_dev_tools/models/workspace.py,sha256=KBHhn4FDH08OPm8fzqJbbW431B3gq21fmUrhH1FQ1Fk,1461
27
+ ai_dev_tools/parsers/__init__.py,sha256=CTeRzVPM4WA4KgU9q9fFxeKYruQsbCTkou9YOsQamxM,22
28
+ ai_dev_tools/parsers/logs.py,sha256=W4ieWdz9ixrGRjIVgnlEGzI5EuQxJMqS2qTvzqYwK1o,14121
29
+ ai_dev_tools/parsers/registry.py,sha256=Eye1NLCy2W6TF2OZ7tIOaFPj_tvPm7MyJRy_pyltuAg,2050
30
+ ai_dev_tools/reporters/__init__.py,sha256=JWR2WoPfAOilOoKesREl7dqZofYHmfn1Oask7pLUhbQ,22
31
+ ai_dev_tools/reporters/progressive.py,sha256=Z40kwEH5v4puyjT6AnU1a_fg0eChWnfBPodI08GtQe4,5599
32
+ ai_dev_tools/reporters/writer.py,sha256=yf6v-NSR9mxI3zSnlxUFl6N7cUK0vBnNWkg3gE5x-bM,2517
33
+ ai_dev_tools/runners/__init__.py,sha256=EMkZFuAQJdiVCAS8qsJmIj25XgzHvBoACqoKdKa9LG8,23
34
+ ai_dev_tools/runners/baseline.py,sha256=gNVcP1nWYTAh_6kCIJ2Fk8s9Ca_Q8oc1xhiMGpm6zyo,7285
35
+ ai_dev_tools/runners/bootstrap.py,sha256=xo7Xl9OFw-bnWdKYTA1i2UyBWwc5FoYuVuHZOvsFjuk,7762
36
+ ai_dev_tools/runners/bootstrap_models.py,sha256=ABdgCF9qUsp8lM2gWO1UUSNXgbd28stV4qQICZ0MW8U,1960
37
+ ai_dev_tools/runners/bootstrap_strategies.py,sha256=8tpNfiT_KRtZFxKbIQYJNU0fvRB3rJTHbSNLzE_9ET8,14115
38
+ ai_dev_tools/runners/cache.py,sha256=U_MBMBfJ6cSRPEl4GZZakXknONAWdd9rSgqNlnrnUhI,669
39
+ ai_dev_tools/runners/check.py,sha256=1sZlsPxY5E4iKpWwGJIj2I_2y8XT6M5GgAYQexYR5kQ,19316
40
+ ai_dev_tools/runners/check_checkpoint.py,sha256=7h7XzRjE10C6NqKePGYNDZWqB64ftLExzPW_1JcuVg0,1545
41
+ ai_dev_tools/runners/check_models.py,sha256=BrHTxq2yVSzpTxRuZ6jB7zOmbXFvVuVU7q0hwrPvy2g,1312
42
+ ai_dev_tools/runners/check_scheduler.py,sha256=tjMzKyqEFptgNK-CJjb7jtNXTvEjhjGBt3rrAE8I6sY,3165
43
+ ai_dev_tools/runners/check_selection.py,sha256=LlWmj4kK5bQUuZBl1LeUs8HGicSD7tInA2s3YCbOgJs,9650
44
+ ai_dev_tools/runners/diagnostics.py,sha256=7M1Zula_nUoBv3a8n9ioBsnHgM-qkoYVXydU8P4VjUY,3623
45
+ ai_dev_tools/runners/feedback.py,sha256=5Xo4OFaFMGf2UZ9SdHBOcCOdrztCJ1VPsEQZRSqRFQc,6642
46
+ ai_dev_tools/runners/finish.py,sha256=Za3SkW5o54NRQr_bo1WSKhrVfImv7TWVzszsDlFIceA,4102
47
+ ai_dev_tools/runners/focused.py,sha256=RierjyU6WChD4sSq53GsvFLcpQGrpsEZ9mOsaTzrSms,1347
48
+ ai_dev_tools/runners/index.py,sha256=GU581MlHrUtcYZu7P4Hy9Re24UfhJinQDYD0bFGF0dM,1467
49
+ ai_dev_tools/runtime/__init__.py,sha256=Pb-djv_Kcze8cKc5tiYh_3YeQzCENrBhKG00uYpCpWs,151
50
+ ai_dev_tools/runtime/runner.py,sha256=PbBIXfOQFR5hK1TgrD6dlQMiS9ZDcJDCyOQswqKYvN4,13689
51
+ ai_dev_tools/runtime/supervisor.py,sha256=ym7iQ92jF8hYUt4dSUo5sjeMwLAUDoxb6X0Xz70Aqj0,4713
52
+ ai_dev_tools/security/__init__.py,sha256=e6X0XtAGkfFSBGRxHZOHm7-ZMg8Pnypq3Z3IONu7Fho,25
53
+ ai_dev_tools/security/secrets.py,sha256=N97uz0qAGybvhzXSTf_D3hT5i_VmmesMavXXobME7sk,2151
54
+ ai_dev_tools/utils/__init__.py,sha256=vTzpzAhwZwhp9xdzSN95SWmf02qgw7Asc0ULXG4aEok,23
55
+ ai_dev_tools/utils/subprocess.py,sha256=Jjt2ezr8RM3fmbdrTCw13Qi8KYxgix-d7rXQkzwDHZ8,2266
56
+ ai_dev_cli_tools-0.5.0a1.dist-info/METADATA,sha256=d7f71_XPlQxJykInHceUIGD4eVgBX5phkVUAxTlToqM,10363
57
+ ai_dev_cli_tools-0.5.0a1.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
58
+ ai_dev_cli_tools-0.5.0a1.dist-info/entry_points.txt,sha256=xKNwzY-s7fmCWs65waRZ5gzMpYlXl03S_P1btlxj3E4,49
59
+ ai_dev_cli_tools-0.5.0a1.dist-info/licenses/LICENSE,sha256=Nc0_ePANo7Sy4FWWgVlFx6mNbKnByDAcQA7jGAYJX0w,1076
60
+ ai_dev_cli_tools-0.5.0a1.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.31.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ ai-dev = ai_dev_tools.cli:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Mateusz Lewandowski
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,3 @@
1
+ """AI Dev CLI Tools."""
2
+
3
+ __version__ = "0.5.0a1"
@@ -0,0 +1,11 @@
1
+ from ai_dev_tools.cache.repository import (
2
+ read_repository_index,
3
+ repository_fingerprint,
4
+ update_repository_index,
5
+ )
6
+
7
+ __all__ = [
8
+ "read_repository_index",
9
+ "repository_fingerprint",
10
+ "update_repository_index",
11
+ ]
@@ -0,0 +1,136 @@
1
+ from __future__ import annotations
2
+
3
+ import posixpath
4
+ import re
5
+ from pathlib import Path
6
+
7
+
8
+ def build_impact_graph(
9
+ root: Path,
10
+ paths: set[str],
11
+ *,
12
+ reused_paths: set[str] | None = None,
13
+ previous_edges: object = None,
14
+ ) -> list[dict[str, str]]:
15
+ reused_paths = reused_paths or set()
16
+ old_by_source = _edges_by_source(previous_edges)
17
+ edges: set[tuple[str, str, str]] = set()
18
+ for relative in sorted(paths):
19
+ if relative in reused_paths and relative in old_by_source:
20
+ edges.update(old_by_source[relative])
21
+ continue
22
+ source = root / relative
23
+ for target, kind in _references(source, relative, paths):
24
+ edges.add((relative, target, kind))
25
+ for target in _candidate_tests(relative, paths):
26
+ edges.add((relative, target, "test"))
27
+ return [{"from": source, "to": target, "kind": kind} for source, target, kind in sorted(edges)]
28
+
29
+
30
+ def related_tests(graph: object, changed_files: list[str]) -> list[str]:
31
+ changed = {path.replace("\\", "/") for path in changed_files}
32
+ selected: set[str] = set()
33
+ if not isinstance(graph, list):
34
+ return []
35
+ for edge in graph:
36
+ if (
37
+ isinstance(edge, dict)
38
+ and edge.get("kind") == "test"
39
+ and edge.get("from") in changed
40
+ and isinstance(edge.get("to"), str)
41
+ ):
42
+ selected.add(str(edge["to"]))
43
+ return sorted(selected)
44
+
45
+
46
+ def _references(source: Path, relative: str, paths: set[str]) -> list[tuple[str, str]]:
47
+ if source.suffix.lower() not in {".py", ".js", ".jsx", ".ts", ".tsx", ".rs"}:
48
+ return []
49
+ try:
50
+ text = source.read_text(encoding="utf-8", errors="replace")
51
+ except OSError:
52
+ return []
53
+ if source.suffix.lower() == ".py":
54
+ return _python_references(relative, text, paths)
55
+ if source.suffix.lower() in {".js", ".jsx", ".ts", ".tsx"}:
56
+ return _js_references(relative, text, paths)
57
+ return _rust_references(relative, text, paths)
58
+
59
+
60
+ def _python_references(relative: str, text: str, paths: set[str]) -> list[tuple[str, str]]:
61
+ result: list[tuple[str, str]] = []
62
+ for match in re.finditer(
63
+ r"^\s*(?:from\s+([A-Za-z_][\w.]*)\s+import|import\s+([A-Za-z_][\w.]*))",
64
+ text,
65
+ re.MULTILINE,
66
+ ):
67
+ module = match.group(1) or match.group(2)
68
+ module_path = module.replace(".", "/")
69
+ candidates = [
70
+ f"{module_path}.py",
71
+ f"src/{module_path}.py",
72
+ f"{module_path}/__init__.py",
73
+ f"src/{module_path}/__init__.py",
74
+ ]
75
+ result.extend((candidate, "import") for candidate in candidates if candidate in paths)
76
+ return result
77
+
78
+
79
+ def _js_references(relative: str, text: str, paths: set[str]) -> list[tuple[str, str]]:
80
+ result: list[tuple[str, str]] = []
81
+ directory = posixpath.dirname(relative)
82
+ for match in re.finditer(r"(?:from\s+|require\()(['\"])(\.{1,2}/[^'\"]+)\1", text):
83
+ base = posixpath.normpath(posixpath.join(directory, match.group(2)))
84
+ candidates = [
85
+ base,
86
+ *[f"{base}{suffix}" for suffix in (".ts", ".tsx", ".js", ".jsx")],
87
+ *[f"{base}/index{suffix}" for suffix in (".ts", ".tsx", ".js", ".jsx")],
88
+ ]
89
+ result.extend((candidate, "import") for candidate in candidates if candidate in paths)
90
+ return result
91
+
92
+
93
+ def _rust_references(relative: str, text: str, paths: set[str]) -> list[tuple[str, str]]:
94
+ directory = posixpath.dirname(relative)
95
+ result: list[tuple[str, str]] = []
96
+ for match in re.finditer(r"^\s*mod\s+([A-Za-z_][\w]*)\s*;", text, re.MULTILINE):
97
+ name = match.group(1)
98
+ for candidate in (f"{directory}/{name}.rs", f"{directory}/{name}/mod.rs"):
99
+ normalized = candidate.lstrip("/")
100
+ if normalized in paths:
101
+ result.append((normalized, "import"))
102
+ return result
103
+
104
+
105
+ def _candidate_tests(relative: str, paths: set[str]) -> list[str]:
106
+ path = Path(relative)
107
+ stem = path.stem
108
+ candidates: list[str] = []
109
+ if path.suffix == ".py":
110
+ parts = list(path.parts)
111
+ source_parts = parts[1:] if parts and parts[0] in {"src", "lib"} else parts
112
+ if source_parts:
113
+ source_parts[-1] = f"test_{stem}.py"
114
+ candidates.append(Path("tests", *source_parts).as_posix())
115
+ candidates.append(Path("tests", f"test_{stem}.py").as_posix())
116
+ elif path.suffix.lower() in {".js", ".jsx", ".ts", ".tsx"}:
117
+ candidates.extend(
118
+ [
119
+ path.with_name(f"{stem}.test{path.suffix}").as_posix(),
120
+ path.with_name(f"{stem}.spec{path.suffix}").as_posix(),
121
+ ]
122
+ )
123
+ return sorted({candidate for candidate in candidates if candidate in paths})
124
+
125
+
126
+ def _edges_by_source(value: object) -> dict[str, set[tuple[str, str, str]]]:
127
+ result: dict[str, set[tuple[str, str, str]]] = {}
128
+ if not isinstance(value, list):
129
+ return result
130
+ for edge in value:
131
+ if not isinstance(edge, dict):
132
+ continue
133
+ source, target, kind = edge.get("from"), edge.get("to"), edge.get("kind")
134
+ if all(isinstance(item, str) for item in (source, target, kind)):
135
+ result.setdefault(str(source), set()).add((str(source), str(target), str(kind)))
136
+ return result
@@ -0,0 +1,169 @@
1
+ from __future__ import annotations
2
+
3
+ import hashlib
4
+ import json
5
+ import os
6
+ from datetime import UTC, datetime
7
+ from pathlib import Path
8
+ from typing import TypedDict
9
+
10
+ from ai_dev_tools.cache.graph import build_impact_graph
11
+ from ai_dev_tools.config import DEFAULT_IGNORES
12
+
13
+ INDEX_SCHEMA_VERSION = "1"
14
+ INDEX_RELATIVE_PATH = Path(".ai/cache/repository-index.json")
15
+
16
+
17
+ class IndexEntry(TypedDict):
18
+ path: str
19
+ size: int
20
+ mtime_ns: int
21
+ sha256: str
22
+
23
+
24
+ def update_repository_index(root: Path, *, rebuild: bool = False) -> dict[str, object]:
25
+ resolved_root = root.resolve()
26
+ index_path = resolved_root / INDEX_RELATIVE_PATH
27
+ previous = {} if rebuild else read_repository_index(resolved_root)
28
+ previous_entries = _entry_map(previous.get("entries"))
29
+ entries: list[IndexEntry] = []
30
+ reused = 0
31
+ hashed = 0
32
+ reused_paths: set[str] = set()
33
+
34
+ for path in _project_files(resolved_root):
35
+ relative = path.relative_to(resolved_root).as_posix()
36
+ stat = path.stat()
37
+ old = previous_entries.get(relative)
38
+ if old is not None and old["size"] == stat.st_size and old["mtime_ns"] == stat.st_mtime_ns:
39
+ digest = old["sha256"]
40
+ reused += 1
41
+ reused_paths.add(relative)
42
+ else:
43
+ digest = _sha256(path)
44
+ hashed += 1
45
+ entries.append(
46
+ {
47
+ "path": relative,
48
+ "size": stat.st_size,
49
+ "mtime_ns": stat.st_mtime_ns,
50
+ "sha256": digest,
51
+ }
52
+ )
53
+
54
+ graph = build_impact_graph(
55
+ resolved_root,
56
+ {item["path"] for item in entries},
57
+ reused_paths=reused_paths,
58
+ previous_edges=previous.get("graph"),
59
+ )
60
+
61
+ payload: dict[str, object] = {
62
+ "schema_version": INDEX_SCHEMA_VERSION,
63
+ "project_root": str(resolved_root),
64
+ "generated_at": datetime.now(UTC).isoformat(),
65
+ "entries": entries,
66
+ "graph": graph,
67
+ "summary": {
68
+ "files": len(entries),
69
+ "hashed": hashed,
70
+ "reused": reused,
71
+ "removed": len(set(previous_entries) - {item["path"] for item in entries}),
72
+ "graph_edges": len(graph),
73
+ },
74
+ }
75
+ _write_json(index_path, payload)
76
+ return payload
77
+
78
+
79
+ def read_repository_index(root: Path) -> dict[str, object]:
80
+ path = root.resolve() / INDEX_RELATIVE_PATH
81
+ try:
82
+ value = json.loads(path.read_text(encoding="utf-8"))
83
+ except (OSError, json.JSONDecodeError):
84
+ return {}
85
+ if not isinstance(value, dict) or value.get("schema_version") != INDEX_SCHEMA_VERSION:
86
+ return {}
87
+ return value
88
+
89
+
90
+ def repository_fingerprint(
91
+ entries: object, workspace: str = "", extra: tuple[str, ...] = ()
92
+ ) -> str:
93
+ prefix = workspace.replace(chr(92), "/").strip("/")
94
+ selected = []
95
+ for entry in _entry_list(entries):
96
+ path = entry["path"]
97
+ if not prefix or path == prefix or path.startswith(prefix + "/"):
98
+ selected.append((path, entry["sha256"]))
99
+ payload = json.dumps(
100
+ {"workspace": prefix, "files": selected, "extra": extra},
101
+ sort_keys=True,
102
+ separators=(",", ":"),
103
+ )
104
+ return hashlib.sha256(payload.encode("utf-8")).hexdigest()
105
+
106
+
107
+ def _project_files(root: Path) -> list[Path]:
108
+ ignored = {item.replace(chr(92), "/").strip("/") for item in DEFAULT_IGNORES}
109
+ ignored_names = {Path(item).name for item in ignored}
110
+ files: list[Path] = []
111
+ for current, directories, names in os.walk(root, followlinks=False):
112
+ current_path = Path(current)
113
+ directories[:] = sorted(
114
+ name
115
+ for name in directories
116
+ if name not in ignored_names
117
+ and not (current_path.name in {"test", "tests"} and name == "fixtures")
118
+ )
119
+ for name in sorted(names):
120
+ path = current_path / name
121
+ if path.is_symlink() or not path.is_file():
122
+ continue
123
+ relative = path.relative_to(root).as_posix()
124
+ if any(relative == item or relative.startswith(item + "/") for item in ignored):
125
+ continue
126
+ files.append(path)
127
+ return files
128
+
129
+
130
+ def _entry_map(value: object) -> dict[str, IndexEntry]:
131
+ return {entry["path"]: entry for entry in _entry_list(value)}
132
+
133
+
134
+ def _entry_list(value: object) -> list[IndexEntry]:
135
+ if not isinstance(value, list):
136
+ return []
137
+ result: list[IndexEntry] = []
138
+ for item in value:
139
+ if (
140
+ isinstance(item, dict)
141
+ and isinstance(item.get("path"), str)
142
+ and isinstance(item.get("size"), int)
143
+ and isinstance(item.get("mtime_ns"), int)
144
+ and isinstance(item.get("sha256"), str)
145
+ ):
146
+ result.append(
147
+ {
148
+ "path": item["path"],
149
+ "size": item["size"],
150
+ "mtime_ns": item["mtime_ns"],
151
+ "sha256": item["sha256"],
152
+ }
153
+ )
154
+ return result
155
+
156
+
157
+ def _sha256(path: Path) -> str:
158
+ digest = hashlib.sha256()
159
+ with path.open("rb") as handle:
160
+ for chunk in iter(lambda: handle.read(1024 * 1024), b""):
161
+ digest.update(chunk)
162
+ return digest.hexdigest()
163
+
164
+
165
+ def _write_json(path: Path, value: dict[str, object]) -> None:
166
+ path.parent.mkdir(parents=True, exist_ok=True)
167
+ temporary = path.with_suffix(path.suffix + ".tmp")
168
+ temporary.write_text(json.dumps(value, indent=2, sort_keys=True) + "\n", encoding="utf-8")
169
+ os.replace(temporary, path)