refactorai-cli 0.7.10__tar.gz → 0.7.12__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.7.10 → refactorai_cli-0.7.12}/PKG-INFO +47 -3
- refactorai_cli-0.7.12/README.md +130 -0
- {refactorai_cli-0.7.10 → refactorai_cli-0.7.12}/pyproject.toml +2 -2
- {refactorai_cli-0.7.10 → refactorai_cli-0.7.12}/refactorai_cli/__init__.py +1 -1
- {refactorai_cli-0.7.10 → refactorai_cli-0.7.12}/refactorai_cli/client.py +65 -0
- {refactorai_cli-0.7.10 → refactorai_cli-0.7.12}/refactorai_cli/cloud_rr.py +107 -27
- {refactorai_cli-0.7.10 → refactorai_cli-0.7.12}/refactorai_cli/commands/run_cmds.py +170 -120
- {refactorai_cli-0.7.10 → refactorai_cli-0.7.12}/refactorai_cli/commands/runtime_proxy_cmds.py +52 -23
- {refactorai_cli-0.7.10 → refactorai_cli-0.7.12}/refactorai_cli/commands/setup_cmds.py +14 -1
- {refactorai_cli-0.7.10 → refactorai_cli-0.7.12}/refactorai_cli/commands/watch_cmds.py +10 -0
- refactorai_cli-0.7.12/refactorai_cli/mode_sync.py +140 -0
- {refactorai_cli-0.7.10 → refactorai_cli-0.7.12}/refactorai_cli/setup_flow.py +259 -1
- {refactorai_cli-0.7.10 → refactorai_cli-0.7.12}/refactorai_cli.egg-info/PKG-INFO +47 -3
- {refactorai_cli-0.7.10 → refactorai_cli-0.7.12}/refactorai_cli.egg-info/SOURCES.txt +1 -0
- {refactorai_cli-0.7.10 → refactorai_cli-0.7.12}/refactorai_cli.egg-info/requires.txt +1 -1
- refactorai_cli-0.7.10/README.md +0 -86
- {refactorai_cli-0.7.10 → refactorai_cli-0.7.12}/refactorai_cli/auth.py +0 -0
- {refactorai_cli-0.7.10 → refactorai_cli-0.7.12}/refactorai_cli/commands/__init__.py +0 -0
- {refactorai_cli-0.7.10 → refactorai_cli-0.7.12}/refactorai_cli/commands/account_cmds.py +0 -0
- {refactorai_cli-0.7.10 → refactorai_cli-0.7.12}/refactorai_cli/commands/auth_cmds.py +0 -0
- {refactorai_cli-0.7.10 → refactorai_cli-0.7.12}/refactorai_cli/commands/branch_cmds.py +0 -0
- {refactorai_cli-0.7.10 → refactorai_cli-0.7.12}/refactorai_cli/commands/cloud_cmds.py +0 -0
- {refactorai_cli-0.7.10 → refactorai_cli-0.7.12}/refactorai_cli/commands/engine_cmds.py +0 -0
- {refactorai_cli-0.7.10 → refactorai_cli-0.7.12}/refactorai_cli/commands/hook_cmds.py +0 -0
- {refactorai_cli-0.7.10 → refactorai_cli-0.7.12}/refactorai_cli/commands/model_cmds.py +0 -0
- {refactorai_cli-0.7.10 → refactorai_cli-0.7.12}/refactorai_cli/commands/policy_cmds.py +0 -0
- {refactorai_cli-0.7.10 → refactorai_cli-0.7.12}/refactorai_cli/commands/pre_push_cmds.py +0 -0
- {refactorai_cli-0.7.10 → refactorai_cli-0.7.12}/refactorai_cli/commands/request_cmds.py +0 -0
- {refactorai_cli-0.7.10 → refactorai_cli-0.7.12}/refactorai_cli/commands/rules_cmds.py +0 -0
- {refactorai_cli-0.7.10 → refactorai_cli-0.7.12}/refactorai_cli/commands/runtime_cmds.py +0 -0
- {refactorai_cli-0.7.10 → refactorai_cli-0.7.12}/refactorai_cli/commands/toolchains_cmds.py +0 -0
- {refactorai_cli-0.7.10 → refactorai_cli-0.7.12}/refactorai_cli/commands/workspace_cmds.py +0 -0
- {refactorai_cli-0.7.10 → refactorai_cli-0.7.12}/refactorai_cli/commit_queue.py +0 -0
- {refactorai_cli-0.7.10 → refactorai_cli-0.7.12}/refactorai_cli/commit_telemetry.py +0 -0
- {refactorai_cli-0.7.10 → refactorai_cli-0.7.12}/refactorai_cli/control_plane.py +0 -0
- {refactorai_cli-0.7.10 → refactorai_cli-0.7.12}/refactorai_cli/credentials.py +0 -0
- {refactorai_cli-0.7.10 → refactorai_cli-0.7.12}/refactorai_cli/dotenv_loader.py +0 -0
- {refactorai_cli-0.7.10 → refactorai_cli-0.7.12}/refactorai_cli/git_scope.py +0 -0
- {refactorai_cli-0.7.10 → refactorai_cli-0.7.12}/refactorai_cli/local_constitution.py +0 -0
- {refactorai_cli-0.7.10 → refactorai_cli-0.7.12}/refactorai_cli/local_engine_runtime.py +0 -0
- {refactorai_cli-0.7.10 → refactorai_cli-0.7.12}/refactorai_cli/local_paths.py +0 -0
- {refactorai_cli-0.7.10 → refactorai_cli-0.7.12}/refactorai_cli/main.py +0 -0
- {refactorai_cli-0.7.10 → refactorai_cli-0.7.12}/refactorai_cli/model_policy.py +0 -0
- {refactorai_cli-0.7.10 → refactorai_cli-0.7.12}/refactorai_cli/pre_push_gate.py +0 -0
- {refactorai_cli-0.7.10 → refactorai_cli-0.7.12}/refactorai_cli/refactor_branch_store.py +0 -0
- {refactorai_cli-0.7.10 → refactorai_cli-0.7.12}/refactorai_cli/review_runner.py +0 -0
- {refactorai_cli-0.7.10 → refactorai_cli-0.7.12}/refactorai_cli/runtime_manager.py +0 -0
- {refactorai_cli-0.7.10 → refactorai_cli-0.7.12}/refactorai_cli/settings.py +0 -0
- {refactorai_cli-0.7.10 → refactorai_cli-0.7.12}/refactorai_cli/watch_ledger.py +0 -0
- {refactorai_cli-0.7.10 → refactorai_cli-0.7.12}/refactorai_cli/watch_state.py +0 -0
- {refactorai_cli-0.7.10 → refactorai_cli-0.7.12}/refactorai_cli/watch_supervisor.py +0 -0
- {refactorai_cli-0.7.10 → refactorai_cli-0.7.12}/refactorai_cli.egg-info/dependency_links.txt +0 -0
- {refactorai_cli-0.7.10 → refactorai_cli-0.7.12}/refactorai_cli.egg-info/entry_points.txt +0 -0
- {refactorai_cli-0.7.10 → refactorai_cli-0.7.12}/refactorai_cli.egg-info/top_level.txt +0 -0
- {refactorai_cli-0.7.10 → refactorai_cli-0.7.12}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: refactorai-cli
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.12
|
|
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.2.
|
|
11
|
+
Requires-Dist: refactorai-core>=3.2.22
|
|
12
12
|
|
|
13
13
|
# refactorai-cli
|
|
14
14
|
|
|
@@ -22,6 +22,50 @@ By default, the CLI targets `https://api.refactorai.codes`.
|
|
|
22
22
|
Use `REFACTOR_PLATFORM_URL` only when you need to override the control-plane URL
|
|
23
23
|
(for self-hosted or local development environments).
|
|
24
24
|
|
|
25
|
+
## Developer flow (init → setup → doctor → watch)
|
|
26
|
+
|
|
27
|
+
The standard flow after creating an account and generating a developer key:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
# 1. Create the project (prompts for the developer key if needed) and register
|
|
31
|
+
# it with the platform. Writes refactor.consti + refactor.config.
|
|
32
|
+
refactor init
|
|
33
|
+
|
|
34
|
+
# 2. Choose the execution mode and provision only what that mode needs.
|
|
35
|
+
# Mode can be chosen in the web UI or here; the two stay in sync.
|
|
36
|
+
refactor setup --mode local_byok
|
|
37
|
+
|
|
38
|
+
# 3. Verify readiness for the resolved mode.
|
|
39
|
+
refactor doctor
|
|
40
|
+
|
|
41
|
+
# 4. Start the commit loop: commit in your editor, and each commit is reviewed,
|
|
42
|
+
# refactored, verified, and merged, then it waits for the next commit.
|
|
43
|
+
refactor watch
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Execution modes at a glance
|
|
47
|
+
|
|
48
|
+
Two independent axes: where the **engine** runs, and where **verification
|
|
49
|
+
(tests)** runs. See `docs/46-execution-variants-and-developer-flow.md`.
|
|
50
|
+
|
|
51
|
+
| variant | engine | verification (tests) | runtime artifact |
|
|
52
|
+
| --------------- | ----------------- | ------------------------------------ | ---------------- |
|
|
53
|
+
| local_model | local | local | **required** |
|
|
54
|
+
| local_byok | server | local (Podman sandbox / toolchain) | not required |
|
|
55
|
+
| local_managed | server | local (Podman sandbox / toolchain) | not required |
|
|
56
|
+
| cloud_byok | server | server | not required |
|
|
57
|
+
| cloud_managed | server | server | not required |
|
|
58
|
+
|
|
59
|
+
`refactor setup` is mode-aware: `local_byok` provisions the local verification
|
|
60
|
+
environment (Podman first) + auth only — it does **not** download the runtime
|
|
61
|
+
artifact or install a local model. Only `local_model` needs the runtime artifact.
|
|
62
|
+
|
|
63
|
+
The verification environment is chosen by a single OS/machine-aware seam
|
|
64
|
+
(`refactor_core.verification_env`): the Podman sandbox by default, falling back to
|
|
65
|
+
the managed host toolchain when no container runtime is available. `refactor
|
|
66
|
+
doctor` reports the selected environment (and `refactor doctor --sandbox` shows
|
|
67
|
+
its full state), so a run and diagnostics always agree.
|
|
68
|
+
|
|
25
69
|
## Local development install
|
|
26
70
|
|
|
27
71
|
From repository root:
|
|
@@ -71,7 +115,7 @@ refactor login
|
|
|
71
115
|
|
|
72
116
|
# 2A. Preferred: set BYOK directly from project env in refactor.config:
|
|
73
117
|
#
|
|
74
|
-
#
|
|
118
|
+
# execution_variant: cloud_byok
|
|
75
119
|
# provider: openai
|
|
76
120
|
# model_id: gpt-4.1-mini
|
|
77
121
|
# provider_key: ${OPENAI_API_KEY}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# refactorai-cli
|
|
2
|
+
|
|
3
|
+
Public CLI package for Refactor.
|
|
4
|
+
|
|
5
|
+
- PyPI package name: `refactorai-cli`
|
|
6
|
+
- Installed command: `refactor`
|
|
7
|
+
- Python module package: `refactorai_cli`
|
|
8
|
+
|
|
9
|
+
By default, the CLI targets `https://api.refactorai.codes`.
|
|
10
|
+
Use `REFACTOR_PLATFORM_URL` only when you need to override the control-plane URL
|
|
11
|
+
(for self-hosted or local development environments).
|
|
12
|
+
|
|
13
|
+
## Developer flow (init → setup → doctor → watch)
|
|
14
|
+
|
|
15
|
+
The standard flow after creating an account and generating a developer key:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
# 1. Create the project (prompts for the developer key if needed) and register
|
|
19
|
+
# it with the platform. Writes refactor.consti + refactor.config.
|
|
20
|
+
refactor init
|
|
21
|
+
|
|
22
|
+
# 2. Choose the execution mode and provision only what that mode needs.
|
|
23
|
+
# Mode can be chosen in the web UI or here; the two stay in sync.
|
|
24
|
+
refactor setup --mode local_byok
|
|
25
|
+
|
|
26
|
+
# 3. Verify readiness for the resolved mode.
|
|
27
|
+
refactor doctor
|
|
28
|
+
|
|
29
|
+
# 4. Start the commit loop: commit in your editor, and each commit is reviewed,
|
|
30
|
+
# refactored, verified, and merged, then it waits for the next commit.
|
|
31
|
+
refactor watch
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Execution modes at a glance
|
|
35
|
+
|
|
36
|
+
Two independent axes: where the **engine** runs, and where **verification
|
|
37
|
+
(tests)** runs. See `docs/46-execution-variants-and-developer-flow.md`.
|
|
38
|
+
|
|
39
|
+
| variant | engine | verification (tests) | runtime artifact |
|
|
40
|
+
| --------------- | ----------------- | ------------------------------------ | ---------------- |
|
|
41
|
+
| local_model | local | local | **required** |
|
|
42
|
+
| local_byok | server | local (Podman sandbox / toolchain) | not required |
|
|
43
|
+
| local_managed | server | local (Podman sandbox / toolchain) | not required |
|
|
44
|
+
| cloud_byok | server | server | not required |
|
|
45
|
+
| cloud_managed | server | server | not required |
|
|
46
|
+
|
|
47
|
+
`refactor setup` is mode-aware: `local_byok` provisions the local verification
|
|
48
|
+
environment (Podman first) + auth only — it does **not** download the runtime
|
|
49
|
+
artifact or install a local model. Only `local_model` needs the runtime artifact.
|
|
50
|
+
|
|
51
|
+
The verification environment is chosen by a single OS/machine-aware seam
|
|
52
|
+
(`refactor_core.verification_env`): the Podman sandbox by default, falling back to
|
|
53
|
+
the managed host toolchain when no container runtime is available. `refactor
|
|
54
|
+
doctor` reports the selected environment (and `refactor doctor --sandbox` shows
|
|
55
|
+
its full state), so a run and diagnostics always agree.
|
|
56
|
+
|
|
57
|
+
## Local development install
|
|
58
|
+
|
|
59
|
+
From repository root:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
pip install -e refactorai-core -e refactorai-cli
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Build
|
|
66
|
+
|
|
67
|
+
From repository root:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
python -m pip install --upgrade build twine
|
|
71
|
+
python -m build "./refactorai-cli"
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Artifacts are created in:
|
|
75
|
+
|
|
76
|
+
- `refactorai-cli/dist/*.whl`
|
|
77
|
+
- `refactorai-cli/dist/*.tar.gz`
|
|
78
|
+
|
|
79
|
+
## Publish
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
python -m twine check ./refactorai-cli/dist/*
|
|
83
|
+
python -m twine upload ./refactorai-cli/dist/*
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Install test (local)
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
python -m pip install ./refactorai-cli/dist/refactorai_cli-0.3.4-py3-none-any.whl
|
|
90
|
+
refactor --version
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Cloud BYOK setup (bring your own provider key)
|
|
94
|
+
|
|
95
|
+
Run cloud inference with your own provider credentials (no local runtime
|
|
96
|
+
required). Preferred path is env-key in project config (`provider_key:
|
|
97
|
+
${ENV_VAR}`). Platform-stored credentials (`credential_ref`) are optional
|
|
98
|
+
fallback.
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
# 1. Authenticate (developer key with the run:byok entitlement).
|
|
102
|
+
refactor login
|
|
103
|
+
|
|
104
|
+
# 2A. Preferred: set BYOK directly from project env in refactor.config:
|
|
105
|
+
#
|
|
106
|
+
# execution_variant: cloud_byok
|
|
107
|
+
# provider: openai
|
|
108
|
+
# model_id: gpt-4.1-mini
|
|
109
|
+
# provider_key: ${OPENAI_API_KEY}
|
|
110
|
+
#
|
|
111
|
+
# 2B. Optional fallback: register a platform-stored credential from env.
|
|
112
|
+
export OPENAI_API_KEY=sk-...
|
|
113
|
+
refactor cloud credentials set --provider openai --from-env OPENAI_API_KEY --label "my key"
|
|
114
|
+
# -> prints a credential_ref, e.g. cred_01J...
|
|
115
|
+
|
|
116
|
+
# 3. If you use platform-stored fallback, set credential_ref in config:
|
|
117
|
+
# credential_ref: cred_01J...
|
|
118
|
+
|
|
119
|
+
# 4. Verify readiness, then run.
|
|
120
|
+
refactor doctor
|
|
121
|
+
refactor review .
|
|
122
|
+
refactor code . --apply
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Manage credentials:
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
refactor cloud credentials list
|
|
129
|
+
refactor cloud credentials remove --credential-ref cred_01J...
|
|
130
|
+
```
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "refactorai-cli"
|
|
3
|
-
version = "0.7.
|
|
3
|
+
version = "0.7.12"
|
|
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.2.
|
|
15
|
+
"refactorai-core>=3.2.22",
|
|
16
16
|
]
|
|
17
17
|
|
|
18
18
|
[project.scripts]
|
|
@@ -143,3 +143,68 @@ class PlatformClient:
|
|
|
143
143
|
)
|
|
144
144
|
payload = response.json()
|
|
145
145
|
return payload if isinstance(payload, dict) else {}
|
|
146
|
+
|
|
147
|
+
def get_project_execution_mode(self, developer_key: str, project_id: str) -> dict:
|
|
148
|
+
"""Read a project's engine execution settings via the developer key.
|
|
149
|
+
|
|
150
|
+
Used by the CLI (``setup`` / ``doctor`` / ``watch``) to sync the server's
|
|
151
|
+
``execution_mode`` (as chosen in the web UI) into the local
|
|
152
|
+
``refactor.config`` -- server-wins. Returns the project record dict.
|
|
153
|
+
"""
|
|
154
|
+
try:
|
|
155
|
+
response = httpx.get(
|
|
156
|
+
f"{self.base_url}/v1/projects/{project_id}/execution-mode",
|
|
157
|
+
headers={"Authorization": f"Bearer {developer_key}"},
|
|
158
|
+
timeout=self.timeout,
|
|
159
|
+
)
|
|
160
|
+
except httpx.HTTPError as exc:
|
|
161
|
+
raise PlatformError(f"Could not reach platform at {self.base_url}: {exc}") from exc
|
|
162
|
+
if response.status_code == 401:
|
|
163
|
+
raise PlatformError("Developer key is invalid or revoked", status_code=401)
|
|
164
|
+
if response.status_code == 404:
|
|
165
|
+
raise PlatformError("Project not found for this account", status_code=404)
|
|
166
|
+
if response.status_code >= 400:
|
|
167
|
+
raise PlatformError(
|
|
168
|
+
f"Could not read project execution mode ({response.status_code})",
|
|
169
|
+
status_code=response.status_code,
|
|
170
|
+
)
|
|
171
|
+
payload = response.json() if response.content else {}
|
|
172
|
+
return payload if isinstance(payload, dict) else {}
|
|
173
|
+
|
|
174
|
+
def set_project_execution_mode(
|
|
175
|
+
self, developer_key: str, project_id: str, execution_mode: str
|
|
176
|
+
) -> dict:
|
|
177
|
+
"""Push a chosen execution variant to the server project (developer key).
|
|
178
|
+
|
|
179
|
+
Lets ``refactor setup --mode`` keep the terminal and the web UI in sync.
|
|
180
|
+
Returns the updated project record dict.
|
|
181
|
+
"""
|
|
182
|
+
try:
|
|
183
|
+
response = httpx.patch(
|
|
184
|
+
f"{self.base_url}/v1/projects/{project_id}/execution-mode",
|
|
185
|
+
headers={
|
|
186
|
+
"Authorization": f"Bearer {developer_key}",
|
|
187
|
+
"Content-Type": "application/json",
|
|
188
|
+
},
|
|
189
|
+
json={"execution_mode": execution_mode},
|
|
190
|
+
timeout=self.timeout,
|
|
191
|
+
)
|
|
192
|
+
except httpx.HTTPError as exc:
|
|
193
|
+
raise PlatformError(f"Could not reach platform at {self.base_url}: {exc}") from exc
|
|
194
|
+
if response.status_code == 401:
|
|
195
|
+
raise PlatformError("Developer key is invalid or revoked", status_code=401)
|
|
196
|
+
if response.status_code == 404:
|
|
197
|
+
raise PlatformError("Project not found for this account", status_code=404)
|
|
198
|
+
if response.status_code >= 400:
|
|
199
|
+
detail = ""
|
|
200
|
+
try:
|
|
201
|
+
detail = str((response.json() or {}).get("detail", ""))
|
|
202
|
+
except Exception:
|
|
203
|
+
detail = response.text[:200]
|
|
204
|
+
suffix = f": {detail}" if detail else ""
|
|
205
|
+
raise PlatformError(
|
|
206
|
+
f"Could not update project execution mode ({response.status_code}){suffix}",
|
|
207
|
+
status_code=response.status_code,
|
|
208
|
+
)
|
|
209
|
+
payload = response.json() if response.content else {}
|
|
210
|
+
return payload if isinstance(payload, dict) else {}
|
|
@@ -148,6 +148,43 @@ def _run_test(
|
|
|
148
148
|
return False, (proc.stdout or proc.stderr or "")[-500:]
|
|
149
149
|
|
|
150
150
|
|
|
151
|
+
def _make_sandbox_verifier(
|
|
152
|
+
sandbox_ctx: dict,
|
|
153
|
+
) -> tuple[Callable[[str | None], tuple[bool, str]] | None, str, str]:
|
|
154
|
+
"""Build a test runner that executes inside the project's sandbox container.
|
|
155
|
+
|
|
156
|
+
docs/46: ``local_byok`` / ``local_managed`` run the engine on the Refactor
|
|
157
|
+
server but verify on the developer's machine. The per-project sandbox
|
|
158
|
+
(created + preflighted by the CLI, mounting ``project_root`` at ``workdir``)
|
|
159
|
+
is reused here so applied diffs and the installed toolchain are both visible.
|
|
160
|
+
|
|
161
|
+
Returns ``(runner, container_name, error)``. ``runner`` is ``None`` when the
|
|
162
|
+
context is incomplete, with a human-readable ``error``.
|
|
163
|
+
"""
|
|
164
|
+
runtime = str(sandbox_ctx.get("runtime") or "podman")
|
|
165
|
+
container = str(sandbox_ctx.get("container_name") or "")
|
|
166
|
+
workdir = str(sandbox_ctx.get("workdir") or "/workspace")
|
|
167
|
+
if not container:
|
|
168
|
+
return None, "", "sandbox context is missing a container name"
|
|
169
|
+
|
|
170
|
+
from refactor_core import sandbox_runtime as _sr
|
|
171
|
+
|
|
172
|
+
def _runner(test_command: str | None) -> tuple[bool, str]:
|
|
173
|
+
if not test_command or not str(test_command).strip():
|
|
174
|
+
return False, "no test command configured; cannot verify behavior preservation"
|
|
175
|
+
command = str(test_command).strip()
|
|
176
|
+
if command == "npm test":
|
|
177
|
+
command = "CI=true npm test -- --watch=false"
|
|
178
|
+
return _sr.exec_in_container(
|
|
179
|
+
runtime=runtime,
|
|
180
|
+
container_name=container,
|
|
181
|
+
command=command,
|
|
182
|
+
workdir=workdir,
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
return _runner, container, ""
|
|
186
|
+
|
|
187
|
+
|
|
151
188
|
def _run_command_with_progress(
|
|
152
189
|
execution_root: Path,
|
|
153
190
|
command: str,
|
|
@@ -1546,7 +1583,31 @@ def run_cloud_refactor_requests(
|
|
|
1546
1583
|
test_command=test_command or "",
|
|
1547
1584
|
execution_env="cloud",
|
|
1548
1585
|
)
|
|
1549
|
-
|
|
1586
|
+
# docs/46: for local-verification variants (local_byok / local_managed) the
|
|
1587
|
+
# CLI passes the ready project sandbox via ``_sandbox_context``. When present,
|
|
1588
|
+
# verification runs INSIDE that container (which mounts project_root and has
|
|
1589
|
+
# the preflight-installed toolchain), so we verify against project_root
|
|
1590
|
+
# directly and skip host toolchain provisioning. _apply_and_verify still backs
|
|
1591
|
+
# up before applying and reverts on red, preserving the "never leave a red
|
|
1592
|
+
# tree" guarantee. Cloud_* variants keep the managed-workspace mirror + host
|
|
1593
|
+
# verification path unchanged.
|
|
1594
|
+
sandbox_ctx = config.get("_sandbox_context") if isinstance(config, dict) else None
|
|
1595
|
+
verify_runner: Callable[[str | None], tuple[bool, str]] | None = None
|
|
1596
|
+
if sandbox_ctx:
|
|
1597
|
+
verify_runner, _verify_container, verify_err = _make_sandbox_verifier(sandbox_ctx)
|
|
1598
|
+
if verify_runner is None:
|
|
1599
|
+
reason = f"sandbox verification unavailable: {verify_err}"
|
|
1600
|
+
_emit(progress_cb, phase="rr", event="preflight_failed", reason=reason)
|
|
1601
|
+
notes.append(reason)
|
|
1602
|
+
return _build_artifact(
|
|
1603
|
+
run_store, run_id, constitution, model_id, outcomes, counts,
|
|
1604
|
+
notes, stopped=True, stop_reason=reason, test_command=test_command,
|
|
1605
|
+
)
|
|
1606
|
+
|
|
1607
|
+
if verify_runner is not None:
|
|
1608
|
+
execution_root = project_root
|
|
1609
|
+
else:
|
|
1610
|
+
execution_root = _prepare_managed_workspace(project_root, progress_cb=progress_cb)
|
|
1550
1611
|
status_before = _git_status_porcelain(project_root)
|
|
1551
1612
|
|
|
1552
1613
|
# Managed host toolchain: when the resolved command's runtime is missing from
|
|
@@ -1563,17 +1624,22 @@ def run_cloud_refactor_requests(
|
|
|
1563
1624
|
apply_byok_payload=apply_byok_payload,
|
|
1564
1625
|
)
|
|
1565
1626
|
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1627
|
+
if verify_runner is not None:
|
|
1628
|
+
# Sandbox verification (docs/46): the container supplies the toolchain, so
|
|
1629
|
+
# no host runtime is provisioned and the host working tree is untouched.
|
|
1630
|
+
toolchain_env, toolchain_note = None, "verification runs in the project sandbox"
|
|
1631
|
+
else:
|
|
1632
|
+
toolchain_env, toolchain_note = _provision_toolchain(
|
|
1633
|
+
execution_root,
|
|
1634
|
+
test_command,
|
|
1635
|
+
config,
|
|
1636
|
+
consent_cb=toolchain_consent_cb,
|
|
1637
|
+
progress_cb=progress_cb,
|
|
1638
|
+
dep_locator_cb=_dep_locator,
|
|
1639
|
+
)
|
|
1640
|
+
toolchain_env = _workspace_env(project_root, toolchain_env)
|
|
1575
1641
|
status_after_toolchain = _git_status_porcelain(project_root)
|
|
1576
|
-
if status_after_toolchain != status_before:
|
|
1642
|
+
if verify_runner is None and status_after_toolchain != status_before:
|
|
1577
1643
|
reason = (
|
|
1578
1644
|
"managed workspace invariant violated: toolchain/dependency setup changed the project "
|
|
1579
1645
|
"working tree before any RR patch was applied; aborting run."
|
|
@@ -1607,7 +1673,7 @@ def run_cloud_refactor_requests(
|
|
|
1607
1673
|
# we did NOT provision a managed runtime (managed provisioning already
|
|
1608
1674
|
# supplies a `python`). Prevents a `python: not found` baseline failure and
|
|
1609
1675
|
# the endless-defer loop it caused.
|
|
1610
|
-
if toolchain_env is None and test_command:
|
|
1676
|
+
if verify_runner is None and toolchain_env is None and test_command:
|
|
1611
1677
|
normalized = _normalize_command_interpreters(test_command)
|
|
1612
1678
|
if normalized and normalized != test_command:
|
|
1613
1679
|
_emit(
|
|
@@ -1626,23 +1692,25 @@ def run_cloud_refactor_requests(
|
|
|
1626
1692
|
# Run-level dependency preflight: always attempt dependency restoration for
|
|
1627
1693
|
# runtimes implied by the configured test command, even when binaries are
|
|
1628
1694
|
# already present on PATH. This prevents import-time baseline failures from
|
|
1629
|
-
# deferring requests one-by-one later in the loop.
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1695
|
+
# deferring requests one-by-one later in the loop. Skipped for sandbox
|
|
1696
|
+
# verification (docs/46): the container's preflight already provisioned deps.
|
|
1697
|
+
if verify_runner is None:
|
|
1698
|
+
for runtime in _command_runtimes(test_command):
|
|
1699
|
+
_restore_dependencies(
|
|
1700
|
+
execution_root,
|
|
1701
|
+
runtime,
|
|
1702
|
+
toolchain_env,
|
|
1703
|
+
progress_cb,
|
|
1704
|
+
config=config,
|
|
1705
|
+
dep_locator_cb=_dep_locator,
|
|
1706
|
+
)
|
|
1639
1707
|
|
|
1640
1708
|
# Fail-fast: if a test command is configured but STILL not runnable after
|
|
1641
1709
|
# provisioning + interpreter normalization, stop up front with an actionable
|
|
1642
1710
|
# message instead of opening a session and deferring every RR behind an
|
|
1643
1711
|
# opaque `python: not found`. (A run with NO test command keeps prior
|
|
1644
1712
|
# behavior: the server decides per-RR whether it can proceed.)
|
|
1645
|
-
if test_command and not _client_preflight_command(
|
|
1713
|
+
if verify_runner is None and test_command and not _client_preflight_command(
|
|
1646
1714
|
project_root, test_command, env=toolchain_env
|
|
1647
1715
|
):
|
|
1648
1716
|
missing = _missing_binaries(test_command, env=toolchain_env)
|
|
@@ -1669,7 +1737,10 @@ def run_cloud_refactor_requests(
|
|
|
1669
1737
|
# Run-level baseline characterization: execute ONCE and reuse for all RR
|
|
1670
1738
|
# steps. If baseline is red, stop the run before opening the RR session so
|
|
1671
1739
|
# requests are not deferred one-by-one for dependency/env failures.
|
|
1672
|
-
|
|
1740
|
+
if verify_runner is not None:
|
|
1741
|
+
baseline_ok, baseline_detail = verify_runner(test_command)
|
|
1742
|
+
else:
|
|
1743
|
+
baseline_ok, baseline_detail = _run_test(execution_root, test_command, env=toolchain_env)
|
|
1673
1744
|
_emit(
|
|
1674
1745
|
progress_cb,
|
|
1675
1746
|
phase="rr",
|
|
@@ -1677,7 +1748,7 @@ def run_cloud_refactor_requests(
|
|
|
1677
1748
|
rr_id="",
|
|
1678
1749
|
passed=baseline_ok,
|
|
1679
1750
|
detail=baseline_detail,
|
|
1680
|
-
execution_env="host+toolchain" if toolchain_env else "host",
|
|
1751
|
+
execution_env="sandbox" if verify_runner is not None else ("host+toolchain" if toolchain_env else "host"),
|
|
1681
1752
|
)
|
|
1682
1753
|
if not baseline_ok:
|
|
1683
1754
|
reason = (
|
|
@@ -1784,7 +1855,7 @@ def run_cloud_refactor_requests(
|
|
|
1784
1855
|
passed, detail = cached_baseline
|
|
1785
1856
|
_emit(progress_cb, phase="rr", event="baseline_test", rr_id=current_rr_id,
|
|
1786
1857
|
passed=passed, detail=detail,
|
|
1787
|
-
execution_env="host+toolchain" if toolchain_env else "host")
|
|
1858
|
+
execution_env="sandbox" if verify_runner is not None else ("host+toolchain" if toolchain_env else "host"))
|
|
1788
1859
|
action = session.step(
|
|
1789
1860
|
_step_payload(token, current_rr_id, "baseline_result", by_id, project_root,
|
|
1790
1861
|
baseline_passed=passed, baseline_detail=detail)
|
|
@@ -1864,6 +1935,7 @@ def run_cloud_refactor_requests(
|
|
|
1864
1935
|
diff_text,
|
|
1865
1936
|
test_command,
|
|
1866
1937
|
env=toolchain_env,
|
|
1938
|
+
verify_cmd=verify_runner,
|
|
1867
1939
|
)
|
|
1868
1940
|
pending.applied = True
|
|
1869
1941
|
if passed:
|
|
@@ -1970,6 +2042,7 @@ def _apply_and_verify(
|
|
|
1970
2042
|
test_command,
|
|
1971
2043
|
*,
|
|
1972
2044
|
env: dict | None = None,
|
|
2045
|
+
verify_cmd: Callable[[str | None], tuple[bool, str]] | None = None,
|
|
1973
2046
|
) -> tuple[bool, str, list[str]]:
|
|
1974
2047
|
"""Apply the diff (with backup), run the test; revert on red.
|
|
1975
2048
|
|
|
@@ -1977,6 +2050,10 @@ def _apply_and_verify(
|
|
|
1977
2050
|
from the diff BEFORE applying (the tree is mutated in place afterward, so it
|
|
1978
2051
|
cannot be recomputed against the modified files). ``env`` optionally supplies
|
|
1979
2052
|
the managed-toolchain environment used to run the test.
|
|
2053
|
+
|
|
2054
|
+
``verify_cmd`` (docs/46) optionally overrides host execution: for
|
|
2055
|
+
``local_byok`` / ``local_managed`` it runs the test inside the project
|
|
2056
|
+
sandbox container so verification happens in the developer's environment.
|
|
1980
2057
|
"""
|
|
1981
2058
|
if not diff_text:
|
|
1982
2059
|
return False, "empty diff", []
|
|
@@ -1993,7 +2070,10 @@ def _apply_and_verify(
|
|
|
1993
2070
|
apply_changes(execution_root, verify_backup_dir, changes)
|
|
1994
2071
|
except Exception as exc:
|
|
1995
2072
|
return False, f"apply failed: {exc}", changed_files
|
|
1996
|
-
|
|
2073
|
+
if verify_cmd is not None:
|
|
2074
|
+
passed, detail = verify_cmd(test_command)
|
|
2075
|
+
else:
|
|
2076
|
+
passed, detail = _run_test(execution_root, test_command, env=env)
|
|
1997
2077
|
if not passed:
|
|
1998
2078
|
try:
|
|
1999
2079
|
revert_from_backup(execution_root, verify_backup_dir)
|