refactorai-cli 0.3.1__tar.gz → 0.3.3__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.1 → refactorai_cli-0.3.3}/PKG-INFO +3 -3
- {refactorai_cli-0.3.1 → refactorai_cli-0.3.3}/README.md +1 -1
- {refactorai_cli-0.3.1 → refactorai_cli-0.3.3}/pyproject.toml +2 -2
- {refactorai_cli-0.3.1 → refactorai_cli-0.3.3}/refactorai_cli/__init__.py +1 -1
- refactorai_cli-0.3.3/refactorai_cli/cloud_rr.py +531 -0
- {refactorai_cli-0.3.1 → refactorai_cli-0.3.3}/refactorai_cli/commands/run_cmds.py +71 -94
- {refactorai_cli-0.3.1 → refactorai_cli-0.3.3}/refactorai_cli.egg-info/PKG-INFO +3 -3
- {refactorai_cli-0.3.1 → refactorai_cli-0.3.3}/refactorai_cli.egg-info/SOURCES.txt +1 -0
- {refactorai_cli-0.3.1 → refactorai_cli-0.3.3}/refactorai_cli.egg-info/requires.txt +1 -1
- {refactorai_cli-0.3.1 → refactorai_cli-0.3.3}/refactorai_cli/auth.py +0 -0
- {refactorai_cli-0.3.1 → refactorai_cli-0.3.3}/refactorai_cli/client.py +0 -0
- {refactorai_cli-0.3.1 → refactorai_cli-0.3.3}/refactorai_cli/commands/__init__.py +0 -0
- {refactorai_cli-0.3.1 → refactorai_cli-0.3.3}/refactorai_cli/commands/auth_cmds.py +0 -0
- {refactorai_cli-0.3.1 → refactorai_cli-0.3.3}/refactorai_cli/commands/cloud_cmds.py +0 -0
- {refactorai_cli-0.3.1 → refactorai_cli-0.3.3}/refactorai_cli/commands/engine_cmds.py +0 -0
- {refactorai_cli-0.3.1 → refactorai_cli-0.3.3}/refactorai_cli/commands/model_cmds.py +0 -0
- {refactorai_cli-0.3.1 → refactorai_cli-0.3.3}/refactorai_cli/commands/rules_cmds.py +0 -0
- {refactorai_cli-0.3.1 → refactorai_cli-0.3.3}/refactorai_cli/commands/runtime_cmds.py +0 -0
- {refactorai_cli-0.3.1 → refactorai_cli-0.3.3}/refactorai_cli/commands/runtime_proxy_cmds.py +0 -0
- {refactorai_cli-0.3.1 → refactorai_cli-0.3.3}/refactorai_cli/commands/setup_cmds.py +0 -0
- {refactorai_cli-0.3.1 → refactorai_cli-0.3.3}/refactorai_cli/control_plane.py +0 -0
- {refactorai_cli-0.3.1 → refactorai_cli-0.3.3}/refactorai_cli/credentials.py +0 -0
- {refactorai_cli-0.3.1 → refactorai_cli-0.3.3}/refactorai_cli/local_constitution.py +0 -0
- {refactorai_cli-0.3.1 → refactorai_cli-0.3.3}/refactorai_cli/local_engine_runtime.py +0 -0
- {refactorai_cli-0.3.1 → refactorai_cli-0.3.3}/refactorai_cli/local_paths.py +0 -0
- {refactorai_cli-0.3.1 → refactorai_cli-0.3.3}/refactorai_cli/main.py +0 -0
- {refactorai_cli-0.3.1 → refactorai_cli-0.3.3}/refactorai_cli/model_policy.py +0 -0
- {refactorai_cli-0.3.1 → refactorai_cli-0.3.3}/refactorai_cli/runtime_manager.py +0 -0
- {refactorai_cli-0.3.1 → refactorai_cli-0.3.3}/refactorai_cli/settings.py +0 -0
- {refactorai_cli-0.3.1 → refactorai_cli-0.3.3}/refactorai_cli/setup_flow.py +0 -0
- {refactorai_cli-0.3.1 → refactorai_cli-0.3.3}/refactorai_cli.egg-info/dependency_links.txt +0 -0
- {refactorai_cli-0.3.1 → refactorai_cli-0.3.3}/refactorai_cli.egg-info/entry_points.txt +0 -0
- {refactorai_cli-0.3.1 → refactorai_cli-0.3.3}/refactorai_cli.egg-info/top_level.txt +0 -0
- {refactorai_cli-0.3.1 → refactorai_cli-0.3.3}/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.3
|
|
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.3
|
|
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.3-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.3-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.3"
|
|
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.3",
|
|
16
16
|
]
|
|
17
17
|
|
|
18
18
|
[project.scripts]
|
|
@@ -0,0 +1,531 @@
|
|
|
1
|
+
"""Client-side orchestrator for the cloud server-run per-RR loop (R23; doc 35).
|
|
2
|
+
|
|
3
|
+
This module is the thin "hand" in the Cursor-style split: the proprietary
|
|
4
|
+
per-RR decision loop runs on the platform (see ``backend/app/api/v1/rr_session``),
|
|
5
|
+
and this client executes only generic primitives and reports results:
|
|
6
|
+
|
|
7
|
+
- open a session with the pending RRs' context,
|
|
8
|
+
- write the server-generated characterization test and run it (baseline),
|
|
9
|
+
- render the server proposal and collect approval (per RR, mid-run),
|
|
10
|
+
- apply the approved diff, run the test again, report the result,
|
|
11
|
+
- honor the server verdict (applied / retry+revert / denied / deferred / stop),
|
|
12
|
+
- persist RR status + build the run summary.
|
|
13
|
+
|
|
14
|
+
IMPORTANT (IP + packaging boundary): this module MUST import only client-safe
|
|
15
|
+
``refactor_core`` modules (``apply``, ``refactor_requests``, ``models``,
|
|
16
|
+
``constitution``, ``store``, ``testcmd``). It MUST NOT import any engine-only
|
|
17
|
+
subpackage (``execution``, ``codeintel``, ``enginecore``, ``providers``); doing
|
|
18
|
+
so would both leak proprietary code to pip-only installs and re-introduce the
|
|
19
|
+
``ModuleNotFoundError: No module named 'refactor_core.execution'`` crash that
|
|
20
|
+
this design fixes. ``tests/test_cloud_rr_client_boundary.py`` enforces this.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
import hashlib
|
|
26
|
+
import subprocess
|
|
27
|
+
from pathlib import Path
|
|
28
|
+
from typing import Callable
|
|
29
|
+
|
|
30
|
+
import httpx
|
|
31
|
+
|
|
32
|
+
# Client-safe core modules only (see module docstring).
|
|
33
|
+
from refactor_core.apply import (
|
|
34
|
+
apply_changes,
|
|
35
|
+
compute_file_changes,
|
|
36
|
+
revert_from_backup,
|
|
37
|
+
validate_diff,
|
|
38
|
+
)
|
|
39
|
+
from refactor_core.models import GateResult, RunArtifact
|
|
40
|
+
from refactor_core.refactor_requests import (
|
|
41
|
+
APPLIED,
|
|
42
|
+
DEFERRED,
|
|
43
|
+
DENIED,
|
|
44
|
+
FAILED,
|
|
45
|
+
IN_PROGRESS,
|
|
46
|
+
pending_requests,
|
|
47
|
+
rr_test_dir,
|
|
48
|
+
save_request,
|
|
49
|
+
)
|
|
50
|
+
from refactor_core.store import create_run
|
|
51
|
+
from refactor_core.testcmd import resolve_test_command
|
|
52
|
+
|
|
53
|
+
from refactorai_cli.auth import ensure_authenticated
|
|
54
|
+
from refactorai_cli.settings import platform_url
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class CloudRRError(RuntimeError):
|
|
58
|
+
"""Raised when the cloud RR session cannot proceed."""
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _read_source(project_root: Path, rel: str) -> str:
|
|
62
|
+
path = project_root / rel
|
|
63
|
+
if not path.is_file():
|
|
64
|
+
return ""
|
|
65
|
+
try:
|
|
66
|
+
return path.read_text(encoding="utf-8")
|
|
67
|
+
except OSError:
|
|
68
|
+
return ""
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _run_test(project_root: Path, test_command: str | None) -> tuple[bool, str]:
|
|
72
|
+
"""Run the configured test command on the host; (passed, detail).
|
|
73
|
+
|
|
74
|
+
Cloud runs execute tests client-side because the developer's code + deps live
|
|
75
|
+
here. Behavior preservation (FR3/FR3a) requires a runnable test: if NO test
|
|
76
|
+
command is configured we return ``(False, ...)`` so the server defers the RR
|
|
77
|
+
rather than applying an unverifiable change. This differs from local's
|
|
78
|
+
permissive skip on purpose -- cloud never applies an unverified change.
|
|
79
|
+
"""
|
|
80
|
+
if not test_command or not str(test_command).strip():
|
|
81
|
+
return False, "no test command configured; cannot verify behavior preservation"
|
|
82
|
+
command = str(test_command).strip()
|
|
83
|
+
if command == "npm test":
|
|
84
|
+
command = "CI=true npm test -- --watch=false"
|
|
85
|
+
try:
|
|
86
|
+
proc = subprocess.run(
|
|
87
|
+
command,
|
|
88
|
+
shell=True,
|
|
89
|
+
cwd=str(project_root),
|
|
90
|
+
capture_output=True,
|
|
91
|
+
text=True,
|
|
92
|
+
timeout=600,
|
|
93
|
+
)
|
|
94
|
+
except (subprocess.SubprocessError, OSError) as exc:
|
|
95
|
+
return False, f"test command error: {exc}"
|
|
96
|
+
if proc.returncode == 0:
|
|
97
|
+
return True, "tests passed"
|
|
98
|
+
return False, (proc.stdout or proc.stderr or "")[-500:]
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class _Session:
|
|
102
|
+
"""Transport for the RR session endpoints with status-code error mapping."""
|
|
103
|
+
|
|
104
|
+
def __init__(self, *, auth_ctx, execution_mode: str, constitution_hash: str) -> None:
|
|
105
|
+
self._auth = auth_ctx
|
|
106
|
+
self._mode = execution_mode
|
|
107
|
+
self._consti = constitution_hash
|
|
108
|
+
self._base = f"{platform_url()}/v1/rr"
|
|
109
|
+
|
|
110
|
+
def _idempotency_key(self, path: str, payload: dict) -> str:
|
|
111
|
+
"""Deterministic key per (account, mode, consti, rr, step, attempt).
|
|
112
|
+
|
|
113
|
+
Makes a step POST safe to retry without duplicating work or double
|
|
114
|
+
charging (FR6). Distinct RRs/attempts/steps get distinct keys so unrelated
|
|
115
|
+
attempts never collapse into one replayed result.
|
|
116
|
+
"""
|
|
117
|
+
seed = "|".join(
|
|
118
|
+
[
|
|
119
|
+
self._auth.account_id,
|
|
120
|
+
self._mode,
|
|
121
|
+
self._consti,
|
|
122
|
+
path,
|
|
123
|
+
str(payload.get("rr_id", "")),
|
|
124
|
+
str(payload.get("step", "")),
|
|
125
|
+
str(payload.get("attempt", "")),
|
|
126
|
+
]
|
|
127
|
+
)
|
|
128
|
+
return hashlib.sha256(seed.encode("utf-8")).hexdigest()
|
|
129
|
+
|
|
130
|
+
def _post(self, path: str, payload: dict) -> dict:
|
|
131
|
+
try:
|
|
132
|
+
resp = httpx.post(
|
|
133
|
+
f"{self._base}{path}",
|
|
134
|
+
headers={
|
|
135
|
+
"Authorization": f"Bearer {self._auth.key.key}",
|
|
136
|
+
"Content-Type": "application/json",
|
|
137
|
+
"Idempotency-Key": self._idempotency_key(path, payload),
|
|
138
|
+
},
|
|
139
|
+
json=payload,
|
|
140
|
+
timeout=45.0,
|
|
141
|
+
)
|
|
142
|
+
except httpx.HTTPError as exc:
|
|
143
|
+
raise CloudRRError(f"Could not reach cloud RR service: {exc}") from exc
|
|
144
|
+
|
|
145
|
+
if resp.status_code == 401:
|
|
146
|
+
raise CloudRRError("Cloud RR unauthorized (developer key invalid or revoked).")
|
|
147
|
+
if resp.status_code == 402:
|
|
148
|
+
if self._mode == "cloud_byok":
|
|
149
|
+
raise CloudRRError(
|
|
150
|
+
"Cloud BYOK is not enabled for this account (missing `run:byok`). "
|
|
151
|
+
"Contact your admin to enable BYOK cloud execution."
|
|
152
|
+
)
|
|
153
|
+
raise CloudRRError("Cloud RR requires paid tier (`mode: cloud_managed`).")
|
|
154
|
+
if resp.status_code == 404 and self._mode == "cloud_byok":
|
|
155
|
+
raise CloudRRError(
|
|
156
|
+
"BYOK credential not found or not owned by this account. "
|
|
157
|
+
"Check `refactor cloud credentials list` and your `credential_ref`."
|
|
158
|
+
)
|
|
159
|
+
if resp.status_code in (400, 403, 409):
|
|
160
|
+
detail = ""
|
|
161
|
+
try:
|
|
162
|
+
detail = str((resp.json() or {}).get("detail", ""))
|
|
163
|
+
except Exception:
|
|
164
|
+
detail = resp.text[:300]
|
|
165
|
+
raise CloudRRError(detail or f"Cloud RR error ({resp.status_code}).")
|
|
166
|
+
if resp.status_code == 429:
|
|
167
|
+
raise CloudRRError("Cloud RR quota exhausted or rate-limited.")
|
|
168
|
+
if resp.status_code >= 400:
|
|
169
|
+
raise CloudRRError(f"Cloud RR failed ({resp.status_code}): {resp.text[:300]}")
|
|
170
|
+
return resp.json() if resp.content else {}
|
|
171
|
+
|
|
172
|
+
def open(self, payload: dict) -> dict:
|
|
173
|
+
return self._post("/session", payload)
|
|
174
|
+
|
|
175
|
+
def step(self, payload: dict) -> dict:
|
|
176
|
+
return self._post("/session/step", payload)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def run_cloud_refactor_requests(
|
|
180
|
+
*,
|
|
181
|
+
project_root: Path,
|
|
182
|
+
constitution,
|
|
183
|
+
config: dict,
|
|
184
|
+
requests=None,
|
|
185
|
+
model_hint: str | None = None,
|
|
186
|
+
progress_cb: Callable[[dict], None] | None = None,
|
|
187
|
+
approval_cb: Callable[[dict], bool] | None = None,
|
|
188
|
+
apply_byok_payload: Callable[[dict, object], None] | None = None,
|
|
189
|
+
) -> RunArtifact:
|
|
190
|
+
"""Drive pending RRs through the cloud server-run loop and return a summary.
|
|
191
|
+
|
|
192
|
+
``progress_cb`` / ``approval_cb`` are the SAME callbacks local uses
|
|
193
|
+
(``_rr_progress_logger`` / ``_rr_approval_prompt``), so the run streams the
|
|
194
|
+
same events and prompts per RR mid-run (FR8). ``apply_byok_payload`` is the
|
|
195
|
+
shared BYOK resolver injected by the caller to avoid a circular import.
|
|
196
|
+
"""
|
|
197
|
+
project_root = Path(project_root)
|
|
198
|
+
progress_cb = progress_cb or (lambda e: None)
|
|
199
|
+
approval_cb = approval_cb or (lambda r: False)
|
|
200
|
+
|
|
201
|
+
execution_mode = str(constitution.get_setting("mode", "cloud_managed") or "cloud_managed")
|
|
202
|
+
auth_ctx = ensure_authenticated(project_root)
|
|
203
|
+
test_command = resolve_test_command(project_root, config)
|
|
204
|
+
|
|
205
|
+
if requests is None:
|
|
206
|
+
requests = pending_requests(project_root)
|
|
207
|
+
|
|
208
|
+
run_store = create_run(project_root)
|
|
209
|
+
run_id = run_store.run_id
|
|
210
|
+
resolved_model = model_hint or str(
|
|
211
|
+
constitution.get_setting("model_id", "managed-default") or "managed-default"
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
_emit(progress_cb, phase="rr", event="run_start", total_requests=len(requests),
|
|
215
|
+
test_command=test_command or "", execution_env="cloud")
|
|
216
|
+
|
|
217
|
+
outcomes: list[dict] = []
|
|
218
|
+
counts = {"applied": 0, "denied": 0, "failed": 0, "deferred": 0}
|
|
219
|
+
notes: list[str] = []
|
|
220
|
+
stopped = False
|
|
221
|
+
stop_reason = ""
|
|
222
|
+
model_id = resolved_model
|
|
223
|
+
|
|
224
|
+
if not requests:
|
|
225
|
+
return _build_artifact(
|
|
226
|
+
run_store, run_id, constitution, model_id, outcomes, counts,
|
|
227
|
+
notes, stopped=False, stop_reason="", test_command=test_command,
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
session = _Session(
|
|
231
|
+
auth_ctx=auth_ctx,
|
|
232
|
+
execution_mode=execution_mode,
|
|
233
|
+
constitution_hash=constitution.content_hash,
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
# --- open the session ---------------------------------------------------
|
|
237
|
+
open_payload = {
|
|
238
|
+
"execution_mode": execution_mode,
|
|
239
|
+
"constitution_hash": constitution.content_hash,
|
|
240
|
+
"constitution_body": getattr(constitution, "body", "") or "",
|
|
241
|
+
"model_hint": resolved_model,
|
|
242
|
+
"rrs": [
|
|
243
|
+
{
|
|
244
|
+
"rr_id": rr.rr_id,
|
|
245
|
+
"rr_context": {
|
|
246
|
+
"location": rr.location,
|
|
247
|
+
"file_path": rr.file_path,
|
|
248
|
+
"category": rr.category,
|
|
249
|
+
"severity": rr.severity,
|
|
250
|
+
"recommendation": rr.recommendation,
|
|
251
|
+
"operation_type": rr.operation_type,
|
|
252
|
+
},
|
|
253
|
+
}
|
|
254
|
+
for rr in requests
|
|
255
|
+
],
|
|
256
|
+
}
|
|
257
|
+
if execution_mode == "cloud_byok" and apply_byok_payload is not None:
|
|
258
|
+
apply_byok_payload(open_payload, constitution)
|
|
259
|
+
|
|
260
|
+
action = session.open(open_payload)
|
|
261
|
+
token = action.get("session_token", "")
|
|
262
|
+
|
|
263
|
+
# Map rr_id -> RefactorRequest for status updates.
|
|
264
|
+
by_id = {rr.rr_id: rr for rr in requests}
|
|
265
|
+
attempts_by_id: dict[str, int] = {}
|
|
266
|
+
started: set[str] = set()
|
|
267
|
+
|
|
268
|
+
def _start_rr(rr_id: str):
|
|
269
|
+
"""Emit rr_start (once) and send the `start` step for ``rr_id``."""
|
|
270
|
+
rr = by_id.get(rr_id)
|
|
271
|
+
if rr is not None and rr_id not in started:
|
|
272
|
+
started.add(rr_id)
|
|
273
|
+
rr.status = IN_PROGRESS
|
|
274
|
+
rr.resolved_in_run = run_id
|
|
275
|
+
save_request(project_root, rr)
|
|
276
|
+
_emit(progress_cb, phase="rr", event="rr_start", rr_id=rr_id, location=rr.location)
|
|
277
|
+
return session.step(_step_payload(token, rr_id, "start", by_id, project_root))
|
|
278
|
+
|
|
279
|
+
# --- drive the loop -----------------------------------------------------
|
|
280
|
+
# Uniform contract: the server returns the next action; the client executes a
|
|
281
|
+
# primitive and posts the result. Terminal per-RR verdicts carry `next_rr_id`
|
|
282
|
+
# (empty => finished). We always send the current file source so the server
|
|
283
|
+
# can (re)build prompts. A generous iteration cap guards against runaway.
|
|
284
|
+
current_rr_id = action.get("rr_id", "")
|
|
285
|
+
if str(action.get("action", "")) == "next_rr" and current_rr_id:
|
|
286
|
+
action = _start_rr(current_rr_id)
|
|
287
|
+
token = action.get("session_token", token)
|
|
288
|
+
|
|
289
|
+
guard = 0
|
|
290
|
+
max_iters = max(50, len(requests) * 25)
|
|
291
|
+
while guard < max_iters:
|
|
292
|
+
guard += 1
|
|
293
|
+
act = str(action.get("action", ""))
|
|
294
|
+
model_id = action.get("model_id", model_id) or model_id
|
|
295
|
+
|
|
296
|
+
if act == "done":
|
|
297
|
+
break
|
|
298
|
+
|
|
299
|
+
if act == "run_baseline_test":
|
|
300
|
+
current_rr_id = action.get("rr_id", "") or current_rr_id
|
|
301
|
+
_emit(progress_cb, phase="rr", event="test_generating", rr_id=current_rr_id)
|
|
302
|
+
_write_generated_test(project_root, current_rr_id, action, by_id)
|
|
303
|
+
passed, detail = _run_test(project_root, test_command)
|
|
304
|
+
_emit(progress_cb, phase="rr", event="baseline_test", rr_id=current_rr_id,
|
|
305
|
+
passed=passed, detail=detail, execution_env="host")
|
|
306
|
+
action = session.step(
|
|
307
|
+
_step_payload(token, current_rr_id, "baseline_result", by_id, project_root,
|
|
308
|
+
baseline_passed=passed, baseline_detail=detail)
|
|
309
|
+
)
|
|
310
|
+
token = action.get("session_token", token)
|
|
311
|
+
continue
|
|
312
|
+
|
|
313
|
+
if act == "show_proposal":
|
|
314
|
+
current_rr_id = action.get("rr_id", "") or current_rr_id
|
|
315
|
+
rr = by_id.get(current_rr_id)
|
|
316
|
+
diff_text = str(action.get("diff_text", "") or "")
|
|
317
|
+
attempt = int(action.get("attempt", 1) or 1)
|
|
318
|
+
attempts_by_id[current_rr_id] = attempt
|
|
319
|
+
ok, why = validate_diff(project_root, diff_text)
|
|
320
|
+
if not ok:
|
|
321
|
+
_emit(progress_cb, phase="rr", event="proposal_invalid", attempt=attempt, reason=why)
|
|
322
|
+
action = session.step(
|
|
323
|
+
_step_payload(token, current_rr_id, "verify_result", by_id, project_root,
|
|
324
|
+
attempt=attempt,
|
|
325
|
+
verify_passed=False, verify_detail=f"invalid diff: {why}")
|
|
326
|
+
)
|
|
327
|
+
token = action.get("session_token", token)
|
|
328
|
+
continue
|
|
329
|
+
_emit(progress_cb, phase="rr", event="approval_requested", rr_id=current_rr_id)
|
|
330
|
+
approved = bool(approval_cb({
|
|
331
|
+
"rr_id": current_rr_id,
|
|
332
|
+
"location": getattr(rr, "location", current_rr_id),
|
|
333
|
+
"attempt": attempt,
|
|
334
|
+
"summary": action.get("summary", ""),
|
|
335
|
+
"risk": action.get("risk", "medium"),
|
|
336
|
+
"diff_text": diff_text,
|
|
337
|
+
}))
|
|
338
|
+
_emit(progress_cb, phase="rr", event="approval_decision", rr_id=current_rr_id, approved=approved)
|
|
339
|
+
_PENDING_DIFF[current_rr_id] = diff_text
|
|
340
|
+
action = session.step(
|
|
341
|
+
_step_payload(token, current_rr_id, "approval", by_id, project_root,
|
|
342
|
+
attempt=attempt, approved=approved)
|
|
343
|
+
)
|
|
344
|
+
token = action.get("session_token", token)
|
|
345
|
+
continue
|
|
346
|
+
|
|
347
|
+
if act in ("apply_result", "deferred"):
|
|
348
|
+
verdict = str(action.get("verdict", "") or "")
|
|
349
|
+
if verdict == "":
|
|
350
|
+
# Server approved: client applies + verifies, then posts result.
|
|
351
|
+
current_rr_id = action.get("rr_id", "") or current_rr_id
|
|
352
|
+
diff_text = _PENDING_DIFF.get(current_rr_id, "")
|
|
353
|
+
passed, detail, files = _apply_and_verify(
|
|
354
|
+
project_root, run_store.run_dir, diff_text, test_command
|
|
355
|
+
)
|
|
356
|
+
if passed:
|
|
357
|
+
_emit(progress_cb, phase="rr", event="rr_applied", rr_id=current_rr_id, files=files)
|
|
358
|
+
else:
|
|
359
|
+
_emit(progress_cb, phase="rr", event="rr_reverted", rr_id=current_rr_id,
|
|
360
|
+
attempt=attempts_by_id.get(current_rr_id, 1), detail=detail)
|
|
361
|
+
action = session.step(
|
|
362
|
+
_step_payload(token, current_rr_id, "verify_result", by_id, project_root,
|
|
363
|
+
attempt=attempts_by_id.get(current_rr_id, 1),
|
|
364
|
+
verify_passed=passed, verify_detail=detail)
|
|
365
|
+
)
|
|
366
|
+
token = action.get("session_token", token)
|
|
367
|
+
continue
|
|
368
|
+
# Terminal verdict for the completed RR.
|
|
369
|
+
completed_id = action.get("rr_id", "") or current_rr_id
|
|
370
|
+
_record_outcome(
|
|
371
|
+
project_root, by_id, completed_id, verdict, action.get("message", ""),
|
|
372
|
+
attempts_by_id.get(completed_id, 0 if verdict == "deferred" else 1),
|
|
373
|
+
outcomes, counts, progress_cb,
|
|
374
|
+
)
|
|
375
|
+
if verdict == "failed":
|
|
376
|
+
stopped = True
|
|
377
|
+
stop_reason = str(action.get("message", "") or "refactor request failed")
|
|
378
|
+
_emit(progress_cb, phase="rr", event="run_stopped", rr_id=completed_id, reason=stop_reason)
|
|
379
|
+
break
|
|
380
|
+
next_id = str(action.get("next_rr_id", "") or "")
|
|
381
|
+
if not next_id:
|
|
382
|
+
break
|
|
383
|
+
current_rr_id = next_id
|
|
384
|
+
action = _start_rr(next_id)
|
|
385
|
+
token = action.get("session_token", token)
|
|
386
|
+
continue
|
|
387
|
+
|
|
388
|
+
notes.append(f"unexpected cloud action '{act}'")
|
|
389
|
+
break
|
|
390
|
+
|
|
391
|
+
return _build_artifact(
|
|
392
|
+
run_store, run_id, constitution, model_id, outcomes, counts,
|
|
393
|
+
notes, stopped=stopped, stop_reason=stop_reason, test_command=test_command,
|
|
394
|
+
)
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
# Diffs approved but not yet applied, keyed by rr_id (client-local, per run).
|
|
398
|
+
_PENDING_DIFF: dict[str, str] = {}
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
def _step_payload(token, rr_id, step, by_id, project_root, *, attempt: int = 0, **kw) -> dict:
|
|
402
|
+
rr = by_id.get(rr_id) if rr_id else None
|
|
403
|
+
source = _read_source(project_root, rr.file_path) if rr is not None else ""
|
|
404
|
+
payload = {
|
|
405
|
+
"session_token": token,
|
|
406
|
+
"rr_id": rr_id,
|
|
407
|
+
"step": step,
|
|
408
|
+
"source": source,
|
|
409
|
+
"attempt": int(attempt),
|
|
410
|
+
}
|
|
411
|
+
payload.update(kw)
|
|
412
|
+
return payload
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
def _write_generated_test(project_root: Path, rr_id: str, action: dict, by_id: dict) -> None:
|
|
416
|
+
content = str(action.get("test_content", "") or "")
|
|
417
|
+
if not content:
|
|
418
|
+
return
|
|
419
|
+
test_path = str(action.get("test_path", "") or "")
|
|
420
|
+
name = Path(test_path).name if test_path else f"{rr_id}.test.txt"
|
|
421
|
+
test_dir = rr_test_dir(project_root, rr_id)
|
|
422
|
+
(test_dir / name).write_text(content, encoding="utf-8")
|
|
423
|
+
rr = by_id.get(rr_id)
|
|
424
|
+
if rr is not None:
|
|
425
|
+
rr.rrtest_path = str(test_dir / name)
|
|
426
|
+
save_request(project_root, rr)
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
def _apply_and_verify(
|
|
430
|
+
project_root: Path, run_dir: Path, diff_text: str, test_command
|
|
431
|
+
) -> tuple[bool, str, list[str]]:
|
|
432
|
+
"""Apply the diff (with backup), run the test; revert on red.
|
|
433
|
+
|
|
434
|
+
Returns ``(passed, detail, changed_files)``. ``changed_files`` is computed
|
|
435
|
+
from the diff BEFORE applying (the tree is mutated in place afterward, so it
|
|
436
|
+
cannot be recomputed against the modified files).
|
|
437
|
+
"""
|
|
438
|
+
if not diff_text:
|
|
439
|
+
return False, "empty diff", []
|
|
440
|
+
try:
|
|
441
|
+
changes = compute_file_changes(project_root, [diff_text])
|
|
442
|
+
except Exception as exc:
|
|
443
|
+
return False, f"could not compute changes: {exc}", []
|
|
444
|
+
if not changes:
|
|
445
|
+
return False, "diff produced no file changes", []
|
|
446
|
+
changed_files = list(changes.keys())
|
|
447
|
+
try:
|
|
448
|
+
apply_changes(project_root, run_dir, changes)
|
|
449
|
+
except Exception as exc:
|
|
450
|
+
return False, f"apply failed: {exc}", changed_files
|
|
451
|
+
passed, detail = _run_test(project_root, test_command)
|
|
452
|
+
if not passed:
|
|
453
|
+
try:
|
|
454
|
+
revert_from_backup(project_root, run_dir)
|
|
455
|
+
except Exception as exc: # keep going; report both
|
|
456
|
+
detail = f"{detail}; revert error: {exc}"
|
|
457
|
+
return passed, detail, changed_files
|
|
458
|
+
|
|
459
|
+
|
|
460
|
+
def _record_outcome(project_root, by_id, rr_id, verdict, message, attempts,
|
|
461
|
+
outcomes: list, counts: dict, progress_cb) -> None:
|
|
462
|
+
status_map = {
|
|
463
|
+
"applied": APPLIED,
|
|
464
|
+
"denied": DENIED,
|
|
465
|
+
"failed": FAILED,
|
|
466
|
+
"deferred": DEFERRED,
|
|
467
|
+
}
|
|
468
|
+
rr = by_id.get(rr_id)
|
|
469
|
+
if rr is not None:
|
|
470
|
+
rr.status = status_map.get(verdict, rr.status)
|
|
471
|
+
rr.attempt_count = max(int(getattr(rr, "attempt_count", 0) or 0), int(attempts or 0))
|
|
472
|
+
save_request(project_root, rr)
|
|
473
|
+
outcomes.append({
|
|
474
|
+
"location": getattr(rr, "location", rr_id) if rr is not None else rr_id,
|
|
475
|
+
"operation_type": getattr(rr, "operation_type", "content_patch") if rr is not None else "content_patch",
|
|
476
|
+
"outcome": verdict,
|
|
477
|
+
"attempts": int(attempts or 0),
|
|
478
|
+
})
|
|
479
|
+
if verdict in counts:
|
|
480
|
+
counts[verdict] += 1
|
|
481
|
+
ev = {
|
|
482
|
+
"applied": "rr_applied",
|
|
483
|
+
"denied": None,
|
|
484
|
+
"failed": "rr_failed",
|
|
485
|
+
"deferred": "rr_deferred",
|
|
486
|
+
}.get(verdict)
|
|
487
|
+
if ev == "rr_deferred":
|
|
488
|
+
_emit(progress_cb, phase="rr", event="rr_deferred", rr_id=rr_id, reason=message)
|
|
489
|
+
elif ev == "rr_failed":
|
|
490
|
+
_emit(progress_cb, phase="rr", event="rr_failed", rr_id=rr_id, reason=message)
|
|
491
|
+
|
|
492
|
+
|
|
493
|
+
def _build_artifact(run_store, run_id, constitution, model_id, outcomes, counts,
|
|
494
|
+
notes, *, stopped, stop_reason, test_command) -> RunArtifact:
|
|
495
|
+
gate = GateResult(status="blocked" if stopped else "passed")
|
|
496
|
+
applied = counts["applied"]
|
|
497
|
+
summary = (
|
|
498
|
+
f"Cloud refactor requests: applied={applied} denied={counts['denied']} "
|
|
499
|
+
f"failed={counts['failed']} deferred={counts['deferred']}"
|
|
500
|
+
)
|
|
501
|
+
artifact = RunArtifact(
|
|
502
|
+
run_id=run_id,
|
|
503
|
+
findings=[],
|
|
504
|
+
patches=[],
|
|
505
|
+
generated_tests=[],
|
|
506
|
+
gate=gate,
|
|
507
|
+
summary=summary,
|
|
508
|
+
constitution_hash=constitution.content_hash,
|
|
509
|
+
model_id=model_id,
|
|
510
|
+
notes=notes,
|
|
511
|
+
execution_mode="rr_cloud",
|
|
512
|
+
context_stats={"mode": "rr_cloud", "test_command": test_command or ""},
|
|
513
|
+
agent_trace_summary={
|
|
514
|
+
"outcomes": outcomes,
|
|
515
|
+
"applied": counts["applied"],
|
|
516
|
+
"denied": counts["denied"],
|
|
517
|
+
"failed": counts["failed"],
|
|
518
|
+
"deferred": counts["deferred"],
|
|
519
|
+
"stopped": stopped,
|
|
520
|
+
"stop_reason": stop_reason,
|
|
521
|
+
},
|
|
522
|
+
)
|
|
523
|
+
run_store.write_artifact(artifact, target=".", status="completed")
|
|
524
|
+
return artifact
|
|
525
|
+
|
|
526
|
+
|
|
527
|
+
def _emit(progress_cb, **event) -> None:
|
|
528
|
+
try:
|
|
529
|
+
progress_cb(event)
|
|
530
|
+
except Exception:
|
|
531
|
+
pass
|
|
@@ -665,29 +665,7 @@ def _cloud_inference(
|
|
|
665
665
|
}
|
|
666
666
|
|
|
667
667
|
if execution_mode == "cloud_byok":
|
|
668
|
-
|
|
669
|
-
provider_key = str(constitution.get_setting("provider_key", "") or "").strip()
|
|
670
|
-
credential_ref = str(constitution.get_setting("credential_ref", "") or "").strip()
|
|
671
|
-
|
|
672
|
-
# Env-key-first BYOK path: when provider_key is configured (typically via
|
|
673
|
-
# ${ENV_VAR} interpolation), prefer it over credential_ref.
|
|
674
|
-
if provider_key:
|
|
675
|
-
if not provider_name or provider_name.lower() == "cloud":
|
|
676
|
-
raise RuntimeError(
|
|
677
|
-
"cloud_byok with provider_key requires an explicit provider "
|
|
678
|
-
"(e.g. provider: openai)."
|
|
679
|
-
)
|
|
680
|
-
payload["provider_key"] = provider_key
|
|
681
|
-
payload["provider"] = provider_name
|
|
682
|
-
elif credential_ref:
|
|
683
|
-
payload["credential_ref"] = credential_ref
|
|
684
|
-
if provider_name and provider_name.lower() != "cloud":
|
|
685
|
-
payload["provider"] = provider_name
|
|
686
|
-
else:
|
|
687
|
-
raise RuntimeError(
|
|
688
|
-
"cloud_byok requires either `provider_key` (preferred env-key path) "
|
|
689
|
-
"or `credential_ref` (platform-stored credential path) in refactor.config."
|
|
690
|
-
)
|
|
668
|
+
_apply_byok_payload(payload, constitution)
|
|
691
669
|
|
|
692
670
|
idempotency_key = _stable_idempotency_key(
|
|
693
671
|
account_id=auth_ctx.account_id,
|
|
@@ -764,6 +742,36 @@ def _cloud_inference(
|
|
|
764
742
|
)
|
|
765
743
|
|
|
766
744
|
|
|
745
|
+
def _apply_byok_payload(payload: dict, constitution) -> None:
|
|
746
|
+
"""Populate BYOK provider fields on a cloud inference ``payload`` in place.
|
|
747
|
+
|
|
748
|
+
Env-key-first: prefer ``provider_key`` (typically ``${ENV_VAR}``) over a
|
|
749
|
+
platform-stored ``credential_ref``. Shared by the review/refactor path and
|
|
750
|
+
the per-RR proposal path so BYOK resolution stays consistent.
|
|
751
|
+
"""
|
|
752
|
+
provider_name = str(constitution.get_setting("provider", "") or "").strip()
|
|
753
|
+
provider_key = str(constitution.get_setting("provider_key", "") or "").strip()
|
|
754
|
+
credential_ref = str(constitution.get_setting("credential_ref", "") or "").strip()
|
|
755
|
+
|
|
756
|
+
if provider_key:
|
|
757
|
+
if not provider_name or provider_name.lower() == "cloud":
|
|
758
|
+
raise RuntimeError(
|
|
759
|
+
"cloud_byok with provider_key requires an explicit provider "
|
|
760
|
+
"(e.g. provider: openai)."
|
|
761
|
+
)
|
|
762
|
+
payload["provider_key"] = provider_key
|
|
763
|
+
payload["provider"] = provider_name
|
|
764
|
+
elif credential_ref:
|
|
765
|
+
payload["credential_ref"] = credential_ref
|
|
766
|
+
if provider_name and provider_name.lower() != "cloud":
|
|
767
|
+
payload["provider"] = provider_name
|
|
768
|
+
else:
|
|
769
|
+
raise RuntimeError(
|
|
770
|
+
"cloud_byok requires either `provider_key` (preferred env-key path) "
|
|
771
|
+
"or `credential_ref` (platform-stored credential path) in refactor.config."
|
|
772
|
+
)
|
|
773
|
+
|
|
774
|
+
|
|
767
775
|
def _load_project() -> tuple[Path, object, object]:
|
|
768
776
|
files = find_project_files()
|
|
769
777
|
if not files:
|
|
@@ -1802,7 +1810,8 @@ def review(
|
|
|
1802
1810
|
if _sandbox_enabled(project_config):
|
|
1803
1811
|
if cloud:
|
|
1804
1812
|
console.print(
|
|
1805
|
-
"[dim]review: sandbox.enabled is ignored in
|
|
1813
|
+
f"[dim]review: sandbox.enabled is ignored in "
|
|
1814
|
+
f"{_cloud_execution_mode(constitution)} mode.[/dim]"
|
|
1806
1815
|
)
|
|
1807
1816
|
else:
|
|
1808
1817
|
sandbox_ctx = _ensure_sandbox_ready(
|
|
@@ -2029,7 +2038,8 @@ def code(
|
|
|
2029
2038
|
if _sandbox_enabled(project_config):
|
|
2030
2039
|
if cloud:
|
|
2031
2040
|
console.print(
|
|
2032
|
-
"[dim]code: sandbox.enabled is ignored in
|
|
2041
|
+
f"[dim]code: sandbox.enabled is ignored in "
|
|
2042
|
+
f"{_cloud_execution_mode(constitution)} mode.[/dim]"
|
|
2033
2043
|
)
|
|
2034
2044
|
else:
|
|
2035
2045
|
sandbox_ctx = _ensure_sandbox_ready(
|
|
@@ -2133,82 +2143,49 @@ def code(
|
|
|
2133
2143
|
console.print("[green]No issues to refactor.[/green]")
|
|
2134
2144
|
return
|
|
2135
2145
|
|
|
2146
|
+
rr_config = (
|
|
2147
|
+
dict(getattr(project_config, "settings", {}) or {})
|
|
2148
|
+
| ({"sandbox": {"enabled": True}} if sandbox_on else {})
|
|
2149
|
+
| ({"_sandbox_context": sandbox_ctx} if sandbox_ctx else {})
|
|
2150
|
+
) or dict(getattr(constitution, "settings", {}) or {})
|
|
2151
|
+
|
|
2136
2152
|
if cloud:
|
|
2137
|
-
# Cloud modes
|
|
2138
|
-
#
|
|
2139
|
-
#
|
|
2140
|
-
#
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
]
|
|
2148
|
-
except Exception:
|
|
2149
|
-
cloud_review_findings = []
|
|
2150
|
-
with console.status("[cyan]code[/cyan] generating patches (cloud)...", spinner="dots"):
|
|
2151
|
-
artifact = _cloud_inference(
|
|
2152
|
-
stage="refactor",
|
|
2153
|
+
# Cloud modes: the proprietary per-RR loop runs SERVER-SIDE (R23; doc
|
|
2154
|
+
# 35). The client is a thin executor speaking the RR session protocol
|
|
2155
|
+
# and uses ONLY client-safe modules — it never imports the engine-only
|
|
2156
|
+
# ``rr_executor``. It reuses the SAME progress logger + approval prompt
|
|
2157
|
+
# as local so the run streams events and asks for approval per RR,
|
|
2158
|
+
# mid-run (identical UX), with tests guarding every change.
|
|
2159
|
+
from refactorai_cli.cloud_rr import run_cloud_refactor_requests
|
|
2160
|
+
|
|
2161
|
+
with console.status("[cyan]code[/cyan] running (cloud)...", spinner="dots") as status:
|
|
2162
|
+
artifact = run_cloud_refactor_requests(
|
|
2153
2163
|
project_root=project_root,
|
|
2154
2164
|
constitution=constitution,
|
|
2155
|
-
|
|
2165
|
+
config=rr_config,
|
|
2166
|
+
requests=requests,
|
|
2156
2167
|
model_hint=provider,
|
|
2157
|
-
|
|
2168
|
+
progress_cb=_rr_progress_logger(status=status),
|
|
2169
|
+
approval_cb=_rr_approval_prompt(auto_approve=auto_approve),
|
|
2170
|
+
apply_byok_payload=_apply_byok_payload,
|
|
2158
2171
|
)
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
_print_patches(artifact.patches)
|
|
2164
|
-
gate_color = "green" if artifact.gate.status == "passed" else "red"
|
|
2165
|
-
console.print(
|
|
2166
|
-
f"\n[bold]result[/bold]: [{gate_color}]{artifact.gate.status}[/{gate_color}] "
|
|
2167
|
-
f"[dim]model={artifact.model_id} mode={_cloud_execution_mode(constitution)} "
|
|
2168
|
-
f"run={artifact.run_id}[/dim]"
|
|
2169
|
-
)
|
|
2172
|
+
else:
|
|
2173
|
+
# Local mode is unchanged: dispatch to the local executor with the
|
|
2174
|
+
# local provider and no cloud hooks.
|
|
2175
|
+
from refactor_core.execution.rr_executor import run_refactor_requests
|
|
2170
2176
|
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
gate_mode,
|
|
2183
|
-
force,
|
|
2184
|
-
project_config,
|
|
2185
|
-
)
|
|
2186
|
-
elif artifact.patches:
|
|
2187
|
-
console.print(
|
|
2188
|
-
"[dim]Review patches with `refactor diff`, then apply with "
|
|
2189
|
-
"`refactor code --apply` or `refactor apply`.[/dim]"
|
|
2177
|
+
with console.status("[cyan]code[/cyan] running...", spinner="dots") as status:
|
|
2178
|
+
artifact = run_refactor_requests(
|
|
2179
|
+
project_root=project_root,
|
|
2180
|
+
constitution=constitution,
|
|
2181
|
+
provider=selected_provider,
|
|
2182
|
+
requests=requests,
|
|
2183
|
+
config=rr_config,
|
|
2184
|
+
progress_cb=_rr_progress_logger(status=status),
|
|
2185
|
+
approval_cb=_rr_approval_prompt(auto_approve=auto_approve),
|
|
2186
|
+
on_rr_failure=on_rr_failure,
|
|
2187
|
+
equivalence_mode=equivalence,
|
|
2190
2188
|
)
|
|
2191
|
-
return
|
|
2192
|
-
|
|
2193
|
-
from refactor_core.execution.rr_executor import run_refactor_requests
|
|
2194
|
-
|
|
2195
|
-
with console.status("[cyan]code[/cyan] running...", spinner="dots") as status:
|
|
2196
|
-
rr_config = (
|
|
2197
|
-
dict(getattr(project_config, "settings", {}) or {})
|
|
2198
|
-
| ({"sandbox": {"enabled": True}} if sandbox_on else {})
|
|
2199
|
-
| ({"_sandbox_context": sandbox_ctx} if sandbox_ctx else {})
|
|
2200
|
-
) or dict(getattr(constitution, "settings", {}) or {})
|
|
2201
|
-
artifact = run_refactor_requests(
|
|
2202
|
-
project_root=project_root,
|
|
2203
|
-
constitution=constitution,
|
|
2204
|
-
provider=selected_provider,
|
|
2205
|
-
requests=requests,
|
|
2206
|
-
config=rr_config,
|
|
2207
|
-
progress_cb=_rr_progress_logger(status=status),
|
|
2208
|
-
approval_cb=_rr_approval_prompt(auto_approve=auto_approve),
|
|
2209
|
-
on_rr_failure=on_rr_failure,
|
|
2210
|
-
equivalence_mode=equivalence,
|
|
2211
|
-
)
|
|
2212
2189
|
except typer.Exit:
|
|
2213
2190
|
# Intentional exits (e.g. gate-blocked apply) keep their own code/message.
|
|
2214
2191
|
raise
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: refactorai-cli
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.3
|
|
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.3
|
|
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.3-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
|