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,154 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import os
5
+ import platform
6
+ import sys
7
+ from datetime import UTC, datetime
8
+ from pathlib import Path
9
+
10
+ from ai_dev_tools.cache.repository import repository_fingerprint
11
+ from ai_dev_tools.utils.subprocess import CommandResult
12
+
13
+ CACHE_SCHEMA_VERSION = "1"
14
+ DEFAULT_MAX_ENTRIES = 200
15
+ DEFAULT_MAX_BYTES = 100 * 1024 * 1024
16
+
17
+
18
+ def validation_cache_key(
19
+ entries: object,
20
+ command: list[str],
21
+ workspace: str,
22
+ ) -> str:
23
+ return repository_fingerprint(
24
+ entries,
25
+ workspace,
26
+ (
27
+ *command,
28
+ platform.system(),
29
+ platform.machine(),
30
+ sys.version,
31
+ ),
32
+ )
33
+
34
+
35
+ def load_validation_result(
36
+ root: Path,
37
+ key: str,
38
+ command: list[str],
39
+ ) -> CommandResult | None:
40
+ path = _cache_path(root, key)
41
+ try:
42
+ value = json.loads(path.read_text(encoding="utf-8"))
43
+ except (OSError, json.JSONDecodeError):
44
+ return None
45
+ if (
46
+ not isinstance(value, dict)
47
+ or value.get("schema_version") != CACHE_SCHEMA_VERSION
48
+ or value.get("key") != key
49
+ or value.get("command") != command
50
+ or value.get("exit_code") != 0
51
+ ):
52
+ return None
53
+ stdout = value.get("stdout")
54
+ stderr = value.get("stderr")
55
+ if not isinstance(stdout, str) or not isinstance(stderr, str):
56
+ return None
57
+ return CommandResult(
58
+ command=command,
59
+ exit_code=0,
60
+ stdout=stdout,
61
+ stderr=stderr,
62
+ duration_seconds=0.0,
63
+ timed_out=False,
64
+ cached=True,
65
+ )
66
+
67
+
68
+ def store_validation_result(root: Path, key: str, result: CommandResult) -> Path | None:
69
+ if result.exit_code != 0 or result.timed_out:
70
+ return None
71
+ path = _cache_path(root, key)
72
+ payload: dict[str, object] = {
73
+ "schema_version": CACHE_SCHEMA_VERSION,
74
+ "key": key,
75
+ "command": result.command,
76
+ "exit_code": result.exit_code,
77
+ "stdout": result.stdout,
78
+ "stderr": result.stderr,
79
+ "duration_seconds": result.duration_seconds,
80
+ "created_at": datetime.now(UTC).isoformat(),
81
+ }
82
+ path.parent.mkdir(parents=True, exist_ok=True)
83
+ temporary = path.with_suffix(".tmp")
84
+ temporary.write_text(json.dumps(payload, indent=2, sort_keys=True) + "\n", encoding="utf-8")
85
+ os.replace(temporary, path)
86
+ prune_validation_cache(root)
87
+ return path
88
+
89
+
90
+ def validation_cache_stats(root: Path) -> dict[str, int]:
91
+ directory = root.resolve() / ".ai" / "cache" / "checks"
92
+ files = list(directory.glob("*.json")) if directory.exists() else []
93
+ sizes = [_safe_size(path) for path in files]
94
+ return {"entries": len(files), "bytes": sum(sizes)}
95
+
96
+
97
+ def prune_validation_cache(
98
+ root: Path,
99
+ *,
100
+ max_entries: int = DEFAULT_MAX_ENTRIES,
101
+ max_bytes: int = DEFAULT_MAX_BYTES,
102
+ ) -> dict[str, int]:
103
+ directory = root.resolve() / ".ai" / "cache" / "checks"
104
+ files = sorted(
105
+ directory.glob("*.json") if directory.exists() else [],
106
+ key=_safe_mtime,
107
+ reverse=True,
108
+ )
109
+ kept = 0
110
+ kept_bytes = 0
111
+ removed = 0
112
+ removed_bytes = 0
113
+ for path in files:
114
+ size = _safe_size(path)
115
+ if kept < max(max_entries, 0) and kept_bytes + size <= max(max_bytes, 0):
116
+ kept += 1
117
+ kept_bytes += size
118
+ continue
119
+ try:
120
+ path.unlink()
121
+ except OSError:
122
+ continue
123
+ removed += 1
124
+ removed_bytes += size
125
+ return {
126
+ "entries": kept,
127
+ "bytes": kept_bytes,
128
+ "removed": removed,
129
+ "removed_bytes": removed_bytes,
130
+ "max_entries": max_entries,
131
+ "max_bytes": max_bytes,
132
+ }
133
+
134
+
135
+ def clear_validation_cache(root: Path) -> dict[str, int]:
136
+ return prune_validation_cache(root, max_entries=0, max_bytes=0)
137
+
138
+
139
+ def _safe_size(path: Path) -> int:
140
+ try:
141
+ return path.stat().st_size
142
+ except OSError:
143
+ return 0
144
+
145
+
146
+ def _safe_mtime(path: Path) -> int:
147
+ try:
148
+ return path.stat().st_mtime_ns
149
+ except OSError:
150
+ return 0
151
+
152
+
153
+ def _cache_path(root: Path, key: str) -> Path:
154
+ return root.resolve() / ".ai" / "cache" / "checks" / f"{key}.json"
ai_dev_tools/cli.py ADDED
@@ -0,0 +1,387 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ import json
5
+ import sys
6
+ from collections.abc import Callable
7
+ from pathlib import Path
8
+
9
+ from ai_dev_tools import __version__
10
+ from ai_dev_tools.models.report import Report
11
+
12
+ CommandHandler = Callable[[Path], Report]
13
+
14
+
15
+ EXIT_SUCCESS = 0
16
+ EXIT_FAILED = 1
17
+ EXIT_USAGE = 2
18
+
19
+
20
+ def build_parser() -> argparse.ArgumentParser:
21
+ parser = argparse.ArgumentParser(prog="ai-dev")
22
+ parser.add_argument("--version", action="version", version=f"ai-dev {__version__}")
23
+ parser.add_argument("--project", type=Path, default=Path.cwd(), help="Project directory")
24
+ parser.add_argument("--json", action="store_true", help="Print JSON instead of text")
25
+ parser.add_argument("--quiet", action="store_true", help="Only print errors and artifact paths")
26
+
27
+ sub = parser.add_subparsers(dest="command", required=True)
28
+ for name in ["doctor", "scan"]:
29
+ sub.add_parser(name)
30
+
31
+ bootstrap = sub.add_parser("bootstrap")
32
+ bootstrap.add_argument("--dry-run", action="store_true")
33
+ bootstrap.add_argument("--explain", action="store_true")
34
+ bootstrap.add_argument("--create-env", action="store_true")
35
+
36
+ run = sub.add_parser("run")
37
+ run.add_argument("--dry-run", action="store_true")
38
+ run.add_argument("--explain", action="store_true")
39
+ run.add_argument("--foreground", action="store_true")
40
+ run.add_argument("--timeout", type=int, default=300)
41
+ run.add_argument("--ready-http")
42
+ run.add_argument("--ready-tcp")
43
+ run.add_argument("--startup-timeout", type=int, default=10)
44
+ run.add_argument("--startup-log-lines", type=int, default=50)
45
+
46
+ stop = sub.add_parser("stop")
47
+ stop.add_argument("--explain", action="store_true")
48
+ stop.add_argument("--timeout", type=int, default=10)
49
+ map_parser = sub.add_parser("map")
50
+ map_parser.add_argument("--max-files", type=int, default=500)
51
+ map_parser.add_argument("--max-depth", type=int, default=6)
52
+
53
+ check = sub.add_parser("check")
54
+ check.add_argument("--mode", choices=["fast", "changed", "full"], default="fast")
55
+ check.add_argument("--jobs", type=int, default=1)
56
+ check.add_argument("--no-cache", action="store_true")
57
+ check.add_argument("--resume", action="store_true")
58
+ check.add_argument("--policy", choices=["complete", "feedback-first"], default="complete")
59
+ check.add_argument(
60
+ "--explain", action="store_true", help="Show selected checks without running them"
61
+ )
62
+
63
+ cache = sub.add_parser("cache")
64
+ cache_sub = cache.add_subparsers(dest="cache_command", required=True)
65
+ for action in ["status", "prune", "clear"]:
66
+ cache_sub.add_parser(action)
67
+
68
+ index = sub.add_parser("index")
69
+ index_sub = index.add_subparsers(dest="index_command", required=True)
70
+ for action in ["status", "update", "rebuild"]:
71
+ index_sub.add_parser(action)
72
+
73
+ test = sub.add_parser("test")
74
+ test_sub = test.add_subparsers(dest="test_command", required=True)
75
+ test_sub.add_parser("affected")
76
+
77
+ logs = sub.add_parser("logs")
78
+ logs_sub = logs.add_subparsers(dest="logs_command", required=True)
79
+ logs_summary = logs_sub.add_parser("summarize")
80
+ logs_summary.add_argument("log_path", nargs="?", type=Path)
81
+ logs_summary.add_argument("--tool", default="auto")
82
+
83
+ context = sub.add_parser("context")
84
+ context_sub = context.add_subparsers(dest="context_command", required=True)
85
+ context_build = context_sub.add_parser("build")
86
+ context_build.add_argument("--task", default="")
87
+ context_build.add_argument(
88
+ "--profile",
89
+ choices=["default", "minimal", "debug", "review", "full"],
90
+ default="default",
91
+ )
92
+ context_build.add_argument("--max-chars", type=int, default=50_000)
93
+ context_build.add_argument("--max-files", type=int, default=30)
94
+ context_build.add_argument("--max-file-chars", type=int, default=8_000)
95
+ context_build.add_argument("--max-diff-chars", type=int, default=15_000)
96
+ context_build.add_argument("--include", action="append", default=[])
97
+ context_build.add_argument("--exclude", action="append", default=[])
98
+ context_build.add_argument("--changed-only", action="store_true")
99
+ context_build.add_argument("--staged-only", action="store_true")
100
+ context_build.add_argument("--no-git", action="store_true")
101
+ context_build.add_argument("--format", choices=["markdown", "json", "both"], default="both")
102
+ context_build.add_argument("--output", type=Path)
103
+ context_build.add_argument("--explain", action="store_true")
104
+ context_build.add_argument("--incremental", action="store_true")
105
+
106
+ git = sub.add_parser("git")
107
+ git_sub = git.add_subparsers(dest="git_command", required=True)
108
+ git_sub.add_parser("status")
109
+ git_sub.add_parser("inspect")
110
+
111
+ completion = sub.add_parser("completion")
112
+ completion.add_argument("shell", choices=["bash", "zsh", "fish", "powershell"])
113
+
114
+ feedback = sub.add_parser("feedback")
115
+ feedback.add_argument("--task", default="")
116
+ feedback.add_argument("--explain", action="store_true")
117
+ feedback.add_argument("--jobs", type=int, default=4)
118
+
119
+ session = sub.add_parser("session")
120
+ session_sub = session.add_subparsers(dest="session_command", required=True)
121
+ session_sub.add_parser("status")
122
+
123
+ baseline = sub.add_parser("baseline")
124
+ baseline_sub = baseline.add_subparsers(dest="baseline_command", required=True)
125
+ baseline_sub.add_parser("list")
126
+ for action in ("create", "compare"):
127
+ baseline_action = baseline_sub.add_parser(action)
128
+ baseline_action.add_argument("name")
129
+
130
+ explain = sub.add_parser("explain")
131
+ explain.add_argument("reference")
132
+ explain.add_argument("--tail", type=int, default=100)
133
+
134
+ sub.add_parser("diagnostics")
135
+ sub.add_parser("capabilities")
136
+ sub.add_parser("finish")
137
+ return parser
138
+
139
+
140
+ def main(argv: list[str] | None = None) -> int:
141
+ parser = build_parser()
142
+ args = parser.parse_args(_normalize_global_flags(argv))
143
+ project_root = args.project.resolve()
144
+ if args.command == "completion":
145
+ from ai_dev_tools.completion import render_completion
146
+
147
+ print(render_completion(args.shell), end="")
148
+ return EXIT_SUCCESS
149
+ report = _dispatch(args, project_root).finish()
150
+ if args.json:
151
+ print(json.dumps(report.to_dict(), indent=2, sort_keys=True))
152
+ elif not args.quiet:
153
+ _print_text(report)
154
+ else:
155
+ for artifact in report.artifacts:
156
+ print(artifact.path)
157
+ return EXIT_SUCCESS if report.status in {"success", "warning", "partial"} else EXIT_FAILED
158
+
159
+
160
+ def _normalize_global_flags(argv: list[str] | None) -> list[str] | None:
161
+ if argv is None:
162
+ argv = sys.argv[1:]
163
+ normalized = list(argv)
164
+ moved: list[str] = []
165
+ for flag in ("--json", "--quiet"):
166
+ if flag in normalized:
167
+ normalized = [item for item in normalized if item != flag]
168
+ moved.append(flag)
169
+ if "--project" in normalized:
170
+ index = normalized.index("--project")
171
+ if index + 1 < len(normalized) and index != 0:
172
+ project_pair = normalized[index : index + 2]
173
+ del normalized[index : index + 2]
174
+ moved.extend(project_pair)
175
+ return [*moved, *normalized]
176
+
177
+
178
+ def _dispatch(args: argparse.Namespace, project_root: Path) -> Report:
179
+ command = args.command
180
+ if command == "run":
181
+ from ai_dev_tools.runtime import RunOptions, run_application
182
+
183
+ return run_application(
184
+ project_root,
185
+ RunOptions(
186
+ explain=args.explain,
187
+ dry_run=args.dry_run,
188
+ foreground=args.foreground,
189
+ timeout_seconds=args.timeout,
190
+ readiness_http=args.ready_http,
191
+ readiness_tcp=args.ready_tcp,
192
+ startup_timeout_seconds=args.startup_timeout,
193
+ startup_log_lines=args.startup_log_lines,
194
+ ),
195
+ )
196
+ if command == "stop":
197
+ from ai_dev_tools.runtime import stop_application
198
+
199
+ return stop_application(
200
+ project_root,
201
+ explain=args.explain,
202
+ timeout_seconds=args.timeout,
203
+ )
204
+ if command == "test" and args.test_command == "affected":
205
+ command = "check"
206
+ args.mode = "changed"
207
+ args.explain = False
208
+ args.jobs = 1
209
+ args.no_cache = False
210
+ args.resume = False
211
+ args.policy = "complete"
212
+ if command == "logs" and args.logs_command == "summarize":
213
+ from ai_dev_tools.parsers.logs import summarize_latest_log, summarize_log_file
214
+
215
+ if args.log_path is not None:
216
+ return summarize_log_file(project_root, args.log_path, tool=args.tool)
217
+ return summarize_latest_log(project_root)
218
+ if command == "context" and args.context_command == "build":
219
+ from ai_dev_tools.context import ContextOptions, build_context
220
+
221
+ return build_context(
222
+ project_root,
223
+ ContextOptions(
224
+ task=args.task,
225
+ profile=args.profile,
226
+ max_chars=args.max_chars,
227
+ max_files=args.max_files,
228
+ max_file_chars=args.max_file_chars,
229
+ max_diff_chars=args.max_diff_chars,
230
+ include=tuple(args.include),
231
+ exclude=tuple(args.exclude),
232
+ changed_only=args.changed_only,
233
+ staged_only=args.staged_only,
234
+ no_git=args.no_git,
235
+ output=args.output,
236
+ format=args.format,
237
+ explain=args.explain,
238
+ incremental=args.incremental,
239
+ ),
240
+ )
241
+ if command == "bootstrap":
242
+ from ai_dev_tools.runners.bootstrap import BootstrapOptions, run_bootstrap
243
+
244
+ return run_bootstrap(
245
+ project_root,
246
+ BootstrapOptions(
247
+ dry_run=args.dry_run,
248
+ explain=args.explain,
249
+ create_env=args.create_env,
250
+ ),
251
+ )
252
+ if command == "doctor":
253
+ from ai_dev_tools.detectors.environment import run_doctor
254
+
255
+ return run_doctor(project_root)
256
+ if command == "scan":
257
+ from ai_dev_tools.detectors.project import scan_project
258
+
259
+ return scan_project(project_root)
260
+ if command == "map":
261
+ from ai_dev_tools.detectors.repository_map import map_repository
262
+
263
+ return map_repository(project_root, max_files=args.max_files, max_depth=args.max_depth)
264
+ if command == "check":
265
+ from ai_dev_tools.runners.check import run_check
266
+
267
+ return run_check(
268
+ project_root,
269
+ mode=args.mode,
270
+ explain=args.explain,
271
+ jobs=args.jobs,
272
+ use_cache=not args.no_cache,
273
+ policy=args.policy,
274
+ resume=args.resume,
275
+ )
276
+ if command == "cache":
277
+ from ai_dev_tools.runners.cache import run_cache
278
+
279
+ return run_cache(project_root, args.cache_command)
280
+ if command == "index":
281
+ from ai_dev_tools.runners.index import run_index
282
+
283
+ return run_index(project_root, args.index_command)
284
+ if command == "feedback":
285
+ from ai_dev_tools.runners.feedback import FeedbackOptions, run_feedback
286
+
287
+ return run_feedback(
288
+ project_root,
289
+ FeedbackOptions(task=args.task, explain=args.explain, jobs=args.jobs),
290
+ )
291
+ if command == "session" and args.session_command == "status":
292
+ from ai_dev_tools.runners.feedback import run_session_status
293
+
294
+ return run_session_status(project_root)
295
+ if command == "baseline":
296
+ from ai_dev_tools.runners.baseline import run_baseline
297
+
298
+ return run_baseline(
299
+ project_root,
300
+ args.baseline_command,
301
+ getattr(args, "name", None),
302
+ )
303
+ if command == "explain":
304
+ from ai_dev_tools.reporters.progressive import run_explain
305
+
306
+ return run_explain(project_root, args.reference, args.tail)
307
+ if command == "diagnostics":
308
+ from ai_dev_tools.runners.diagnostics import run_diagnostics
309
+
310
+ return run_diagnostics(project_root)
311
+ if command == "capabilities":
312
+ return _capabilities_report(project_root)
313
+ if command == "git":
314
+ from ai_dev_tools.git.inspect import inspect_git
315
+
316
+ return inspect_git(project_root, detailed=args.git_command == "inspect")
317
+ if command == "finish":
318
+ from ai_dev_tools.runners.finish import run_finish
319
+
320
+ return run_finish(project_root)
321
+ raise SystemExit(EXIT_USAGE)
322
+
323
+
324
+ def _capabilities_report(project_root: Path) -> Report:
325
+ report = Report(command="capabilities", project_root=project_root)
326
+ implemented = [
327
+ "doctor",
328
+ "scan",
329
+ "map",
330
+ "check",
331
+ "test affected",
332
+ "cache status",
333
+ "cache prune",
334
+ "cache clear",
335
+ "index status",
336
+ "index update",
337
+ "index rebuild",
338
+ "logs summarize",
339
+ "context build",
340
+ "bootstrap",
341
+ "run",
342
+ "stop",
343
+ "git status",
344
+ "git inspect",
345
+ "finish",
346
+ "feedback",
347
+ "session status",
348
+ "baseline create",
349
+ "baseline compare",
350
+ "baseline list",
351
+ "explain",
352
+ "diagnostics",
353
+ "capabilities",
354
+ ]
355
+ planned: list[str] = []
356
+ report.summary = {
357
+ "implemented": implemented,
358
+ "planned": planned,
359
+ "deprecated": [],
360
+ "commands": {name: "implemented" for name in implemented}
361
+ | {name: "planned" for name in planned},
362
+ "quality": {
363
+ "implemented": implemented,
364
+ "unit_tested": implemented,
365
+ "integration_tested": ["scan", "git status", "git inspect", "context build"],
366
+ "cross_platform_ci_verified": [],
367
+ "ci_status": "BLOCKED_EXTERNAL",
368
+ },
369
+ }
370
+ return report
371
+
372
+
373
+ def _print_text(report: Report) -> None:
374
+ print(f"STATUS: {report.status.upper()}")
375
+ print(f"COMMAND: {report.command}")
376
+ print(f"DURATION: {report.duration_seconds}s")
377
+ for key, value in report.summary.items():
378
+ print(f"{key.upper()}: {value}")
379
+ if report.issues:
380
+ print("ISSUES:")
381
+ for issue in report.issues:
382
+ location = f" [{issue.location}]" if issue.location else ""
383
+ print(f"- {issue.severity}: {issue.message}{location}")
384
+
385
+
386
+ if __name__ == "__main__":
387
+ sys.exit(main())
@@ -0,0 +1,72 @@
1
+ from __future__ import annotations
2
+
3
+ TOP_LEVEL_COMMANDS = (
4
+ "doctor scan bootstrap run stop map check test logs context cache index "
5
+ "baseline explain feedback session diagnostics git "
6
+ "capabilities finish completion"
7
+ )
8
+ GLOBAL_FLAGS = "--project --json --quiet --help --version"
9
+
10
+
11
+ def render_completion(shell: str) -> str:
12
+ if shell == "bash":
13
+ return _bash()
14
+ if shell == "zsh":
15
+ return _zsh()
16
+ if shell == "fish":
17
+ return _fish()
18
+ if shell == "powershell":
19
+ return _powershell()
20
+ raise ValueError(f"Unsupported shell: {shell}")
21
+
22
+
23
+ def _bash() -> str:
24
+ return f'''_ai_dev_complete() {{
25
+ local current="${{COMP_WORDS[COMP_CWORD]}}"
26
+ COMPREPLY=($(compgen -W "{TOP_LEVEL_COMMANDS} {GLOBAL_FLAGS}" -- "$current"))
27
+ }}
28
+ complete -F _ai_dev_complete ai-dev
29
+ '''
30
+
31
+
32
+ def _zsh() -> str:
33
+ words = " ".join(TOP_LEVEL_COMMANDS.split() + GLOBAL_FLAGS.split())
34
+ return f"""#compdef ai-dev
35
+ _ai_dev() {{
36
+ local -a candidates
37
+ candidates=({words})
38
+ compadd -- $candidates
39
+ }}
40
+ compdef _ai_dev ai-dev
41
+ """
42
+
43
+
44
+ def _fish() -> str:
45
+ lines = ["complete -c ai-dev -f"]
46
+ lines.extend(
47
+ f"complete -c ai-dev -n '__fish_use_subcommand' -a '{item}'"
48
+ for item in TOP_LEVEL_COMMANDS.split()
49
+ )
50
+ lines.extend(
51
+ f"complete -c ai-dev -l '{item[2:]}'"
52
+ for item in GLOBAL_FLAGS.split()
53
+ if item.startswith("--")
54
+ )
55
+ return "\n".join(lines) + "\n"
56
+
57
+
58
+ def _powershell() -> str:
59
+ candidates = ", ".join(
60
+ f"'{item}'" for item in TOP_LEVEL_COMMANDS.split() + GLOBAL_FLAGS.split()
61
+ )
62
+ return f"""Register-ArgumentCompleter -Native -CommandName ai-dev -ScriptBlock {{
63
+ param($wordToComplete, $commandAst, $cursorPosition)
64
+ @({candidates}) |
65
+ Where-Object {{ $_ -like "$wordToComplete*" }} |
66
+ ForEach-Object {{
67
+ [System.Management.Automation.CompletionResult]::new(
68
+ $_, $_, 'ParameterValue', $_
69
+ )
70
+ }}
71
+ }}
72
+ """