refactorai-cli 0.3.3__tar.gz → 0.3.4__tar.gz
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.
- {refactorai_cli-0.3.3 → refactorai_cli-0.3.4}/PKG-INFO +3 -3
- {refactorai_cli-0.3.3 → refactorai_cli-0.3.4}/README.md +1 -1
- {refactorai_cli-0.3.3 → refactorai_cli-0.3.4}/pyproject.toml +2 -2
- {refactorai_cli-0.3.3 → refactorai_cli-0.3.4}/refactorai_cli/__init__.py +1 -1
- {refactorai_cli-0.3.3 → refactorai_cli-0.3.4}/refactorai_cli/cloud_rr.py +201 -2
- {refactorai_cli-0.3.3 → refactorai_cli-0.3.4}/refactorai_cli/commands/run_cmds.py +3 -3
- {refactorai_cli-0.3.3 → refactorai_cli-0.3.4}/refactorai_cli.egg-info/PKG-INFO +3 -3
- {refactorai_cli-0.3.3 → refactorai_cli-0.3.4}/refactorai_cli.egg-info/requires.txt +1 -1
- {refactorai_cli-0.3.3 → refactorai_cli-0.3.4}/refactorai_cli/auth.py +0 -0
- {refactorai_cli-0.3.3 → refactorai_cli-0.3.4}/refactorai_cli/client.py +0 -0
- {refactorai_cli-0.3.3 → refactorai_cli-0.3.4}/refactorai_cli/commands/__init__.py +0 -0
- {refactorai_cli-0.3.3 → refactorai_cli-0.3.4}/refactorai_cli/commands/auth_cmds.py +0 -0
- {refactorai_cli-0.3.3 → refactorai_cli-0.3.4}/refactorai_cli/commands/cloud_cmds.py +0 -0
- {refactorai_cli-0.3.3 → refactorai_cli-0.3.4}/refactorai_cli/commands/engine_cmds.py +0 -0
- {refactorai_cli-0.3.3 → refactorai_cli-0.3.4}/refactorai_cli/commands/model_cmds.py +0 -0
- {refactorai_cli-0.3.3 → refactorai_cli-0.3.4}/refactorai_cli/commands/rules_cmds.py +0 -0
- {refactorai_cli-0.3.3 → refactorai_cli-0.3.4}/refactorai_cli/commands/runtime_cmds.py +0 -0
- {refactorai_cli-0.3.3 → refactorai_cli-0.3.4}/refactorai_cli/commands/runtime_proxy_cmds.py +0 -0
- {refactorai_cli-0.3.3 → refactorai_cli-0.3.4}/refactorai_cli/commands/setup_cmds.py +0 -0
- {refactorai_cli-0.3.3 → refactorai_cli-0.3.4}/refactorai_cli/control_plane.py +0 -0
- {refactorai_cli-0.3.3 → refactorai_cli-0.3.4}/refactorai_cli/credentials.py +0 -0
- {refactorai_cli-0.3.3 → refactorai_cli-0.3.4}/refactorai_cli/local_constitution.py +0 -0
- {refactorai_cli-0.3.3 → refactorai_cli-0.3.4}/refactorai_cli/local_engine_runtime.py +0 -0
- {refactorai_cli-0.3.3 → refactorai_cli-0.3.4}/refactorai_cli/local_paths.py +0 -0
- {refactorai_cli-0.3.3 → refactorai_cli-0.3.4}/refactorai_cli/main.py +0 -0
- {refactorai_cli-0.3.3 → refactorai_cli-0.3.4}/refactorai_cli/model_policy.py +0 -0
- {refactorai_cli-0.3.3 → refactorai_cli-0.3.4}/refactorai_cli/runtime_manager.py +0 -0
- {refactorai_cli-0.3.3 → refactorai_cli-0.3.4}/refactorai_cli/settings.py +0 -0
- {refactorai_cli-0.3.3 → refactorai_cli-0.3.4}/refactorai_cli/setup_flow.py +0 -0
- {refactorai_cli-0.3.3 → refactorai_cli-0.3.4}/refactorai_cli.egg-info/SOURCES.txt +0 -0
- {refactorai_cli-0.3.3 → refactorai_cli-0.3.4}/refactorai_cli.egg-info/dependency_links.txt +0 -0
- {refactorai_cli-0.3.3 → refactorai_cli-0.3.4}/refactorai_cli.egg-info/entry_points.txt +0 -0
- {refactorai_cli-0.3.3 → refactorai_cli-0.3.4}/refactorai_cli.egg-info/top_level.txt +0 -0
- {refactorai_cli-0.3.3 → refactorai_cli-0.3.4}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: refactorai-cli
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.4
|
|
4
4
|
Summary: Local-first CLI for the refactor platform
|
|
5
5
|
Requires-Python: >=3.11
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -8,7 +8,7 @@ Requires-Dist: typer>=0.12.0
|
|
|
8
8
|
Requires-Dist: httpx>=0.27.0
|
|
9
9
|
Requires-Dist: rich>=13.7.0
|
|
10
10
|
Requires-Dist: PyYAML>=6.0.1
|
|
11
|
-
Requires-Dist: refactorai-core>=3.0.
|
|
11
|
+
Requires-Dist: refactorai-core>=3.0.4
|
|
12
12
|
|
|
13
13
|
# refactorai-cli
|
|
14
14
|
|
|
@@ -54,7 +54,7 @@ python -m twine upload ./refactorai-cli/dist/*
|
|
|
54
54
|
## Install test (local)
|
|
55
55
|
|
|
56
56
|
```bash
|
|
57
|
-
python -m pip install ./refactorai-cli/dist/refactorai_cli-0.3.
|
|
57
|
+
python -m pip install ./refactorai-cli/dist/refactorai_cli-0.3.4-py3-none-any.whl
|
|
58
58
|
refactor --version
|
|
59
59
|
```
|
|
60
60
|
|
|
@@ -42,7 +42,7 @@ python -m twine upload ./refactorai-cli/dist/*
|
|
|
42
42
|
## Install test (local)
|
|
43
43
|
|
|
44
44
|
```bash
|
|
45
|
-
python -m pip install ./refactorai-cli/dist/refactorai_cli-0.3.
|
|
45
|
+
python -m pip install ./refactorai-cli/dist/refactorai_cli-0.3.4-py3-none-any.whl
|
|
46
46
|
refactor --version
|
|
47
47
|
```
|
|
48
48
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "refactorai-cli"
|
|
3
|
-
version = "0.3.
|
|
3
|
+
version = "0.3.4"
|
|
4
4
|
description = "Local-first CLI for the refactor platform"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.11"
|
|
@@ -12,7 +12,7 @@ dependencies = [
|
|
|
12
12
|
"httpx>=0.27.0",
|
|
13
13
|
"rich>=13.7.0",
|
|
14
14
|
"PyYAML>=6.0.1",
|
|
15
|
-
"refactorai-core>=3.0.
|
|
15
|
+
"refactorai-core>=3.0.4",
|
|
16
16
|
]
|
|
17
17
|
|
|
18
18
|
[project.scripts]
|
|
@@ -23,6 +23,9 @@ this design fixes. ``tests/test_cloud_rr_client_boundary.py`` enforces this.
|
|
|
23
23
|
from __future__ import annotations
|
|
24
24
|
|
|
25
25
|
import hashlib
|
|
26
|
+
import re
|
|
27
|
+
import shlex
|
|
28
|
+
import shutil
|
|
26
29
|
import subprocess
|
|
27
30
|
from pathlib import Path
|
|
28
31
|
from typing import Callable
|
|
@@ -48,11 +51,33 @@ from refactor_core.refactor_requests import (
|
|
|
48
51
|
save_request,
|
|
49
52
|
)
|
|
50
53
|
from refactor_core.store import create_run
|
|
51
|
-
from refactor_core.testcmd import resolve_test_command
|
|
54
|
+
from refactor_core.testcmd import _SKIP_DIRS, discover_test_command, resolve_test_command
|
|
52
55
|
|
|
53
56
|
from refactorai_cli.auth import ensure_authenticated
|
|
54
57
|
from refactorai_cli.settings import platform_url
|
|
55
58
|
|
|
59
|
+
_ENV_ASSIGN_RE = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*=.*$")
|
|
60
|
+
|
|
61
|
+
# Manifest/config files that reveal how a project runs its tests. Sent (truncated)
|
|
62
|
+
# to the server for agentic test-command discovery when deterministic detection
|
|
63
|
+
# finds nothing.
|
|
64
|
+
_MANIFEST_NAMES = (
|
|
65
|
+
"package.json",
|
|
66
|
+
"pyproject.toml",
|
|
67
|
+
"setup.cfg",
|
|
68
|
+
"setup.py",
|
|
69
|
+
"pytest.ini",
|
|
70
|
+
"tox.ini",
|
|
71
|
+
"noxfile.py",
|
|
72
|
+
"requirements.txt",
|
|
73
|
+
"Makefile",
|
|
74
|
+
"makefile",
|
|
75
|
+
"go.mod",
|
|
76
|
+
"Cargo.toml",
|
|
77
|
+
"build.gradle",
|
|
78
|
+
"pom.xml",
|
|
79
|
+
)
|
|
80
|
+
|
|
56
81
|
|
|
57
82
|
class CloudRRError(RuntimeError):
|
|
58
83
|
"""Raised when the cloud RR session cannot proceed."""
|
|
@@ -98,6 +123,170 @@ def _run_test(project_root: Path, test_command: str | None) -> tuple[bool, str]:
|
|
|
98
123
|
return False, (proc.stdout or proc.stderr or "")[-500:]
|
|
99
124
|
|
|
100
125
|
|
|
126
|
+
def _client_preflight_command(project_root: Path, command: str | None) -> bool:
|
|
127
|
+
"""Return True when ``command`` looks runnable on this host.
|
|
128
|
+
|
|
129
|
+
Composite/shell commands (``&&``, subshells, pipes) run under ``shell=True``
|
|
130
|
+
and can't be reduced to one binary, so we trust them and let the real exit
|
|
131
|
+
code gate. Simple commands are validated by checking the leading binary is on
|
|
132
|
+
PATH — this guards against running (and charging for) a server-suggested
|
|
133
|
+
command whose toolchain isn't installed.
|
|
134
|
+
"""
|
|
135
|
+
if not command or not str(command).strip():
|
|
136
|
+
return False
|
|
137
|
+
if any(op in command for op in ("&&", "||", ";", "|", "(", ")")):
|
|
138
|
+
return True
|
|
139
|
+
try:
|
|
140
|
+
tokens = shlex.split(command, posix=True)
|
|
141
|
+
except ValueError:
|
|
142
|
+
return False
|
|
143
|
+
while tokens and _ENV_ASSIGN_RE.match(tokens[0]):
|
|
144
|
+
tokens.pop(0)
|
|
145
|
+
if not tokens:
|
|
146
|
+
return False
|
|
147
|
+
binary = tokens[0]
|
|
148
|
+
if "/" in binary:
|
|
149
|
+
return Path(binary).expanduser().exists()
|
|
150
|
+
return shutil.which(binary) is not None
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def _collect_test_manifests(
|
|
154
|
+
project_root: Path, *, max_files: int = 24, max_bytes: int = 4000
|
|
155
|
+
) -> list[dict]:
|
|
156
|
+
"""Gather manifest snippets (repo root + immediate subdirs) for discovery."""
|
|
157
|
+
root = Path(project_root)
|
|
158
|
+
docs: list[dict] = []
|
|
159
|
+
dirs: list[Path] = [root]
|
|
160
|
+
try:
|
|
161
|
+
for child in sorted(root.iterdir(), key=lambda p: p.name):
|
|
162
|
+
if child.is_dir() and child.name not in _SKIP_DIRS and not child.name.startswith("."):
|
|
163
|
+
dirs.append(child)
|
|
164
|
+
except OSError:
|
|
165
|
+
return docs
|
|
166
|
+
|
|
167
|
+
for directory in dirs:
|
|
168
|
+
for name in _MANIFEST_NAMES:
|
|
169
|
+
path = directory / name
|
|
170
|
+
if not path.is_file():
|
|
171
|
+
continue
|
|
172
|
+
try:
|
|
173
|
+
content = path.read_text(encoding="utf-8", errors="replace")[:max_bytes]
|
|
174
|
+
except OSError:
|
|
175
|
+
continue
|
|
176
|
+
rel = path.relative_to(root)
|
|
177
|
+
docs.append({"path": str(rel), "content": content})
|
|
178
|
+
if len(docs) >= max_files:
|
|
179
|
+
return docs
|
|
180
|
+
tests_dir = directory / "tests"
|
|
181
|
+
if tests_dir.is_dir():
|
|
182
|
+
try:
|
|
183
|
+
names = sorted(p.name for p in tests_dir.iterdir() if p.is_file())[:40]
|
|
184
|
+
except OSError:
|
|
185
|
+
names = []
|
|
186
|
+
rel = tests_dir.relative_to(root)
|
|
187
|
+
docs.append({"path": f"{rel}/ (listing)", "content": "\n".join(names)})
|
|
188
|
+
if len(docs) >= max_files:
|
|
189
|
+
return docs
|
|
190
|
+
return docs
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def _discover_test_command_via_cloud(
|
|
194
|
+
*,
|
|
195
|
+
project_root: Path,
|
|
196
|
+
constitution,
|
|
197
|
+
execution_mode: str,
|
|
198
|
+
auth_ctx,
|
|
199
|
+
model_hint: str | None,
|
|
200
|
+
apply_byok_payload: Callable[[dict, object], None] | None,
|
|
201
|
+
) -> str:
|
|
202
|
+
"""Ask the server to infer a test command from the project's manifests.
|
|
203
|
+
|
|
204
|
+
Best-effort: any transport/parse/auth issue returns ``""`` so the caller
|
|
205
|
+
falls back to the existing defer-when-unverifiable behavior.
|
|
206
|
+
"""
|
|
207
|
+
documents = _collect_test_manifests(project_root)
|
|
208
|
+
if not documents:
|
|
209
|
+
return ""
|
|
210
|
+
payload = {
|
|
211
|
+
"stage": "rr_discover_testcmd",
|
|
212
|
+
"constitution_hash": constitution.content_hash,
|
|
213
|
+
"constitution_body": getattr(constitution, "body", "") or "",
|
|
214
|
+
"model_hint": model_hint,
|
|
215
|
+
"documents": documents,
|
|
216
|
+
"execution_mode": execution_mode,
|
|
217
|
+
}
|
|
218
|
+
if execution_mode == "cloud_byok" and apply_byok_payload is not None:
|
|
219
|
+
apply_byok_payload(payload, constitution)
|
|
220
|
+
idem = hashlib.sha256(
|
|
221
|
+
"|".join(
|
|
222
|
+
[auth_ctx.account_id, execution_mode, constitution.content_hash, "rr_discover_testcmd"]
|
|
223
|
+
).encode("utf-8")
|
|
224
|
+
).hexdigest()
|
|
225
|
+
try:
|
|
226
|
+
resp = httpx.post(
|
|
227
|
+
f"{platform_url()}/v1/inference/refactor",
|
|
228
|
+
headers={
|
|
229
|
+
"Authorization": f"Bearer {auth_ctx.key.key}",
|
|
230
|
+
"Content-Type": "application/json",
|
|
231
|
+
"Idempotency-Key": idem,
|
|
232
|
+
},
|
|
233
|
+
json=payload,
|
|
234
|
+
timeout=45.0,
|
|
235
|
+
)
|
|
236
|
+
except httpx.HTTPError:
|
|
237
|
+
return ""
|
|
238
|
+
if resp.status_code != 200:
|
|
239
|
+
return ""
|
|
240
|
+
try:
|
|
241
|
+
data = resp.json() if resp.content else {}
|
|
242
|
+
except ValueError:
|
|
243
|
+
return ""
|
|
244
|
+
return str((data or {}).get("test_command", "") or "").strip()
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def _resolve_cloud_test_command(
|
|
248
|
+
*,
|
|
249
|
+
project_root: Path,
|
|
250
|
+
config: dict,
|
|
251
|
+
constitution,
|
|
252
|
+
execution_mode: str,
|
|
253
|
+
auth_ctx,
|
|
254
|
+
model_hint: str | None,
|
|
255
|
+
apply_byok_payload: Callable[[dict, object], None] | None,
|
|
256
|
+
) -> tuple[str | None, str]:
|
|
257
|
+
"""Resolve a test command for a cloud run; return ``(command, note)``.
|
|
258
|
+
|
|
259
|
+
Layered resolution so the developer usually needs no config:
|
|
260
|
+
1. configured ``verification.tests.command`` or root-marker detection,
|
|
261
|
+
2. deterministic monorepo discovery (subproject test setups),
|
|
262
|
+
3. agentic discovery (server infers from manifests), preflight-validated.
|
|
263
|
+
"""
|
|
264
|
+
configured = resolve_test_command(project_root, config)
|
|
265
|
+
if configured:
|
|
266
|
+
return configured, ""
|
|
267
|
+
|
|
268
|
+
discovered = discover_test_command(project_root)
|
|
269
|
+
if discovered:
|
|
270
|
+
return discovered, f"auto-detected test command: {discovered}"
|
|
271
|
+
|
|
272
|
+
suggested = _discover_test_command_via_cloud(
|
|
273
|
+
project_root=project_root,
|
|
274
|
+
constitution=constitution,
|
|
275
|
+
execution_mode=execution_mode,
|
|
276
|
+
auth_ctx=auth_ctx,
|
|
277
|
+
model_hint=model_hint,
|
|
278
|
+
apply_byok_payload=apply_byok_payload,
|
|
279
|
+
)
|
|
280
|
+
if suggested and _client_preflight_command(project_root, suggested):
|
|
281
|
+
return suggested, f"server-suggested test command: {suggested}"
|
|
282
|
+
if suggested:
|
|
283
|
+
return None, (
|
|
284
|
+
f"server suggested test command '{suggested}' but its toolchain was not "
|
|
285
|
+
"found on PATH; skipped."
|
|
286
|
+
)
|
|
287
|
+
return None, ""
|
|
288
|
+
|
|
289
|
+
|
|
101
290
|
class _Session:
|
|
102
291
|
"""Transport for the RR session endpoints with status-code error mapping."""
|
|
103
292
|
|
|
@@ -200,7 +389,15 @@ def run_cloud_refactor_requests(
|
|
|
200
389
|
|
|
201
390
|
execution_mode = str(constitution.get_setting("mode", "cloud_managed") or "cloud_managed")
|
|
202
391
|
auth_ctx = ensure_authenticated(project_root)
|
|
203
|
-
test_command =
|
|
392
|
+
test_command, testcmd_note = _resolve_cloud_test_command(
|
|
393
|
+
project_root=project_root,
|
|
394
|
+
config=config,
|
|
395
|
+
constitution=constitution,
|
|
396
|
+
execution_mode=execution_mode,
|
|
397
|
+
auth_ctx=auth_ctx,
|
|
398
|
+
model_hint=model_hint,
|
|
399
|
+
apply_byok_payload=apply_byok_payload,
|
|
400
|
+
)
|
|
204
401
|
|
|
205
402
|
if requests is None:
|
|
206
403
|
requests = pending_requests(project_root)
|
|
@@ -217,6 +414,8 @@ def run_cloud_refactor_requests(
|
|
|
217
414
|
outcomes: list[dict] = []
|
|
218
415
|
counts = {"applied": 0, "denied": 0, "failed": 0, "deferred": 0}
|
|
219
416
|
notes: list[str] = []
|
|
417
|
+
if testcmd_note:
|
|
418
|
+
notes.append(testcmd_note)
|
|
220
419
|
stopped = False
|
|
221
420
|
stop_reason = ""
|
|
222
421
|
model_id = resolved_model
|
|
@@ -1956,7 +1956,7 @@ def code(
|
|
|
1956
1956
|
help="Skip interactive issue approvals in agentic code mode.",
|
|
1957
1957
|
),
|
|
1958
1958
|
on_rr_failure: str = typer.Option(
|
|
1959
|
-
"
|
|
1959
|
+
"continue",
|
|
1960
1960
|
"--on-rr-failure",
|
|
1961
1961
|
help="RR failure behavior: stop | continue | defer.",
|
|
1962
1962
|
),
|
|
@@ -2227,7 +2227,7 @@ def requests(
|
|
|
2227
2227
|
help="Auto-approve retry-deferred proposals.",
|
|
2228
2228
|
),
|
|
2229
2229
|
on_rr_failure: str = typer.Option(
|
|
2230
|
-
"
|
|
2230
|
+
"continue",
|
|
2231
2231
|
"--on-rr-failure",
|
|
2232
2232
|
help="RR failure behavior for retry: stop | continue | defer.",
|
|
2233
2233
|
),
|
|
@@ -3484,7 +3484,7 @@ def _report_structural_readiness(project_root: Path, config) -> None:
|
|
|
3484
3484
|
if not isinstance(structural, dict):
|
|
3485
3485
|
structural = {}
|
|
3486
3486
|
eq_mode = str(structural.get("equivalence_mode", "strict") or "strict")
|
|
3487
|
-
rr_policy = str(structural.get("on_rr_failure", "
|
|
3487
|
+
rr_policy = str(structural.get("on_rr_failure", "continue") or "continue")
|
|
3488
3488
|
rewrite_mode = str(structural.get("reference_rewrite", "auto") or "auto")
|
|
3489
3489
|
langs = structural.get("languages", [])
|
|
3490
3490
|
rollout = str(structural.get("rollout", "default") or "default")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: refactorai-cli
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.4
|
|
4
4
|
Summary: Local-first CLI for the refactor platform
|
|
5
5
|
Requires-Python: >=3.11
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -8,7 +8,7 @@ Requires-Dist: typer>=0.12.0
|
|
|
8
8
|
Requires-Dist: httpx>=0.27.0
|
|
9
9
|
Requires-Dist: rich>=13.7.0
|
|
10
10
|
Requires-Dist: PyYAML>=6.0.1
|
|
11
|
-
Requires-Dist: refactorai-core>=3.0.
|
|
11
|
+
Requires-Dist: refactorai-core>=3.0.4
|
|
12
12
|
|
|
13
13
|
# refactorai-cli
|
|
14
14
|
|
|
@@ -54,7 +54,7 @@ python -m twine upload ./refactorai-cli/dist/*
|
|
|
54
54
|
## Install test (local)
|
|
55
55
|
|
|
56
56
|
```bash
|
|
57
|
-
python -m pip install ./refactorai-cli/dist/refactorai_cli-0.3.
|
|
57
|
+
python -m pip install ./refactorai-cli/dist/refactorai_cli-0.3.4-py3-none-any.whl
|
|
58
58
|
refactor --version
|
|
59
59
|
```
|
|
60
60
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|