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,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
|
+
"""
|