refactorai-cli 0.7.10__tar.gz → 0.7.11__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.
Files changed (54) hide show
  1. {refactorai_cli-0.7.10 → refactorai_cli-0.7.11}/PKG-INFO +47 -3
  2. refactorai_cli-0.7.11/README.md +130 -0
  3. {refactorai_cli-0.7.10 → refactorai_cli-0.7.11}/pyproject.toml +2 -2
  4. {refactorai_cli-0.7.10 → refactorai_cli-0.7.11}/refactorai_cli/__init__.py +1 -1
  5. {refactorai_cli-0.7.10 → refactorai_cli-0.7.11}/refactorai_cli/cloud_rr.py +107 -27
  6. {refactorai_cli-0.7.10 → refactorai_cli-0.7.11}/refactorai_cli/commands/run_cmds.py +140 -43
  7. {refactorai_cli-0.7.10 → refactorai_cli-0.7.11}/refactorai_cli/commands/runtime_proxy_cmds.py +52 -23
  8. {refactorai_cli-0.7.10 → refactorai_cli-0.7.11}/refactorai_cli/commands/setup_cmds.py +14 -1
  9. {refactorai_cli-0.7.10 → refactorai_cli-0.7.11}/refactorai_cli/setup_flow.py +187 -1
  10. {refactorai_cli-0.7.10 → refactorai_cli-0.7.11}/refactorai_cli.egg-info/PKG-INFO +47 -3
  11. {refactorai_cli-0.7.10 → refactorai_cli-0.7.11}/refactorai_cli.egg-info/requires.txt +1 -1
  12. refactorai_cli-0.7.10/README.md +0 -86
  13. {refactorai_cli-0.7.10 → refactorai_cli-0.7.11}/refactorai_cli/auth.py +0 -0
  14. {refactorai_cli-0.7.10 → refactorai_cli-0.7.11}/refactorai_cli/client.py +0 -0
  15. {refactorai_cli-0.7.10 → refactorai_cli-0.7.11}/refactorai_cli/commands/__init__.py +0 -0
  16. {refactorai_cli-0.7.10 → refactorai_cli-0.7.11}/refactorai_cli/commands/account_cmds.py +0 -0
  17. {refactorai_cli-0.7.10 → refactorai_cli-0.7.11}/refactorai_cli/commands/auth_cmds.py +0 -0
  18. {refactorai_cli-0.7.10 → refactorai_cli-0.7.11}/refactorai_cli/commands/branch_cmds.py +0 -0
  19. {refactorai_cli-0.7.10 → refactorai_cli-0.7.11}/refactorai_cli/commands/cloud_cmds.py +0 -0
  20. {refactorai_cli-0.7.10 → refactorai_cli-0.7.11}/refactorai_cli/commands/engine_cmds.py +0 -0
  21. {refactorai_cli-0.7.10 → refactorai_cli-0.7.11}/refactorai_cli/commands/hook_cmds.py +0 -0
  22. {refactorai_cli-0.7.10 → refactorai_cli-0.7.11}/refactorai_cli/commands/model_cmds.py +0 -0
  23. {refactorai_cli-0.7.10 → refactorai_cli-0.7.11}/refactorai_cli/commands/policy_cmds.py +0 -0
  24. {refactorai_cli-0.7.10 → refactorai_cli-0.7.11}/refactorai_cli/commands/pre_push_cmds.py +0 -0
  25. {refactorai_cli-0.7.10 → refactorai_cli-0.7.11}/refactorai_cli/commands/request_cmds.py +0 -0
  26. {refactorai_cli-0.7.10 → refactorai_cli-0.7.11}/refactorai_cli/commands/rules_cmds.py +0 -0
  27. {refactorai_cli-0.7.10 → refactorai_cli-0.7.11}/refactorai_cli/commands/runtime_cmds.py +0 -0
  28. {refactorai_cli-0.7.10 → refactorai_cli-0.7.11}/refactorai_cli/commands/toolchains_cmds.py +0 -0
  29. {refactorai_cli-0.7.10 → refactorai_cli-0.7.11}/refactorai_cli/commands/watch_cmds.py +0 -0
  30. {refactorai_cli-0.7.10 → refactorai_cli-0.7.11}/refactorai_cli/commands/workspace_cmds.py +0 -0
  31. {refactorai_cli-0.7.10 → refactorai_cli-0.7.11}/refactorai_cli/commit_queue.py +0 -0
  32. {refactorai_cli-0.7.10 → refactorai_cli-0.7.11}/refactorai_cli/commit_telemetry.py +0 -0
  33. {refactorai_cli-0.7.10 → refactorai_cli-0.7.11}/refactorai_cli/control_plane.py +0 -0
  34. {refactorai_cli-0.7.10 → refactorai_cli-0.7.11}/refactorai_cli/credentials.py +0 -0
  35. {refactorai_cli-0.7.10 → refactorai_cli-0.7.11}/refactorai_cli/dotenv_loader.py +0 -0
  36. {refactorai_cli-0.7.10 → refactorai_cli-0.7.11}/refactorai_cli/git_scope.py +0 -0
  37. {refactorai_cli-0.7.10 → refactorai_cli-0.7.11}/refactorai_cli/local_constitution.py +0 -0
  38. {refactorai_cli-0.7.10 → refactorai_cli-0.7.11}/refactorai_cli/local_engine_runtime.py +0 -0
  39. {refactorai_cli-0.7.10 → refactorai_cli-0.7.11}/refactorai_cli/local_paths.py +0 -0
  40. {refactorai_cli-0.7.10 → refactorai_cli-0.7.11}/refactorai_cli/main.py +0 -0
  41. {refactorai_cli-0.7.10 → refactorai_cli-0.7.11}/refactorai_cli/model_policy.py +0 -0
  42. {refactorai_cli-0.7.10 → refactorai_cli-0.7.11}/refactorai_cli/pre_push_gate.py +0 -0
  43. {refactorai_cli-0.7.10 → refactorai_cli-0.7.11}/refactorai_cli/refactor_branch_store.py +0 -0
  44. {refactorai_cli-0.7.10 → refactorai_cli-0.7.11}/refactorai_cli/review_runner.py +0 -0
  45. {refactorai_cli-0.7.10 → refactorai_cli-0.7.11}/refactorai_cli/runtime_manager.py +0 -0
  46. {refactorai_cli-0.7.10 → refactorai_cli-0.7.11}/refactorai_cli/settings.py +0 -0
  47. {refactorai_cli-0.7.10 → refactorai_cli-0.7.11}/refactorai_cli/watch_ledger.py +0 -0
  48. {refactorai_cli-0.7.10 → refactorai_cli-0.7.11}/refactorai_cli/watch_state.py +0 -0
  49. {refactorai_cli-0.7.10 → refactorai_cli-0.7.11}/refactorai_cli/watch_supervisor.py +0 -0
  50. {refactorai_cli-0.7.10 → refactorai_cli-0.7.11}/refactorai_cli.egg-info/SOURCES.txt +0 -0
  51. {refactorai_cli-0.7.10 → refactorai_cli-0.7.11}/refactorai_cli.egg-info/dependency_links.txt +0 -0
  52. {refactorai_cli-0.7.10 → refactorai_cli-0.7.11}/refactorai_cli.egg-info/entry_points.txt +0 -0
  53. {refactorai_cli-0.7.10 → refactorai_cli-0.7.11}/refactorai_cli.egg-info/top_level.txt +0 -0
  54. {refactorai_cli-0.7.10 → refactorai_cli-0.7.11}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: refactorai-cli
3
- Version: 0.7.10
3
+ Version: 0.7.11
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.21
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
- # mode: cloud_byok
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.10"
3
+ version = "0.7.11"
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.21",
15
+ "refactorai-core>=3.2.22",
16
16
  ]
17
17
 
18
18
  [project.scripts]
@@ -5,4 +5,4 @@ the shared `refactor_core` pipeline from a project folder while staying
5
5
  authenticated to the hosted platform via a developer key.
6
6
  """
7
7
 
8
- __version__ = "0.7.9"
8
+ __version__ = "0.7.11"
@@ -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
- execution_root = _prepare_managed_workspace(project_root, progress_cb=progress_cb)
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
- toolchain_env, toolchain_note = _provision_toolchain(
1567
- execution_root,
1568
- test_command,
1569
- config,
1570
- consent_cb=toolchain_consent_cb,
1571
- progress_cb=progress_cb,
1572
- dep_locator_cb=_dep_locator,
1573
- )
1574
- toolchain_env = _workspace_env(project_root, toolchain_env)
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
- for runtime in _command_runtimes(test_command):
1631
- _restore_dependencies(
1632
- execution_root,
1633
- runtime,
1634
- toolchain_env,
1635
- progress_cb,
1636
- config=config,
1637
- dep_locator_cb=_dep_locator,
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
- baseline_ok, baseline_detail = _run_test(execution_root, test_command, env=toolchain_env)
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
- passed, detail = _run_test(execution_root, test_command, env=env)
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)