refactorai-cli 0.7.9__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.9 → refactorai_cli-0.7.11}/PKG-INFO +47 -3
  2. refactorai_cli-0.7.11/README.md +130 -0
  3. {refactorai_cli-0.7.9 → refactorai_cli-0.7.11}/pyproject.toml +2 -2
  4. {refactorai_cli-0.7.9 → refactorai_cli-0.7.11}/refactorai_cli/__init__.py +1 -1
  5. {refactorai_cli-0.7.9 → refactorai_cli-0.7.11}/refactorai_cli/cloud_rr.py +114 -28
  6. {refactorai_cli-0.7.9 → refactorai_cli-0.7.11}/refactorai_cli/commands/run_cmds.py +194 -61
  7. {refactorai_cli-0.7.9 → refactorai_cli-0.7.11}/refactorai_cli/commands/runtime_proxy_cmds.py +64 -25
  8. {refactorai_cli-0.7.9 → refactorai_cli-0.7.11}/refactorai_cli/commands/setup_cmds.py +14 -1
  9. {refactorai_cli-0.7.9 → refactorai_cli-0.7.11}/refactorai_cli/setup_flow.py +187 -1
  10. {refactorai_cli-0.7.9 → refactorai_cli-0.7.11}/refactorai_cli.egg-info/PKG-INFO +47 -3
  11. {refactorai_cli-0.7.9 → refactorai_cli-0.7.11}/refactorai_cli.egg-info/requires.txt +1 -1
  12. refactorai_cli-0.7.9/README.md +0 -86
  13. {refactorai_cli-0.7.9 → refactorai_cli-0.7.11}/refactorai_cli/auth.py +0 -0
  14. {refactorai_cli-0.7.9 → refactorai_cli-0.7.11}/refactorai_cli/client.py +0 -0
  15. {refactorai_cli-0.7.9 → refactorai_cli-0.7.11}/refactorai_cli/commands/__init__.py +0 -0
  16. {refactorai_cli-0.7.9 → refactorai_cli-0.7.11}/refactorai_cli/commands/account_cmds.py +0 -0
  17. {refactorai_cli-0.7.9 → refactorai_cli-0.7.11}/refactorai_cli/commands/auth_cmds.py +0 -0
  18. {refactorai_cli-0.7.9 → refactorai_cli-0.7.11}/refactorai_cli/commands/branch_cmds.py +0 -0
  19. {refactorai_cli-0.7.9 → refactorai_cli-0.7.11}/refactorai_cli/commands/cloud_cmds.py +0 -0
  20. {refactorai_cli-0.7.9 → refactorai_cli-0.7.11}/refactorai_cli/commands/engine_cmds.py +0 -0
  21. {refactorai_cli-0.7.9 → refactorai_cli-0.7.11}/refactorai_cli/commands/hook_cmds.py +0 -0
  22. {refactorai_cli-0.7.9 → refactorai_cli-0.7.11}/refactorai_cli/commands/model_cmds.py +0 -0
  23. {refactorai_cli-0.7.9 → refactorai_cli-0.7.11}/refactorai_cli/commands/policy_cmds.py +0 -0
  24. {refactorai_cli-0.7.9 → refactorai_cli-0.7.11}/refactorai_cli/commands/pre_push_cmds.py +0 -0
  25. {refactorai_cli-0.7.9 → refactorai_cli-0.7.11}/refactorai_cli/commands/request_cmds.py +0 -0
  26. {refactorai_cli-0.7.9 → refactorai_cli-0.7.11}/refactorai_cli/commands/rules_cmds.py +0 -0
  27. {refactorai_cli-0.7.9 → refactorai_cli-0.7.11}/refactorai_cli/commands/runtime_cmds.py +0 -0
  28. {refactorai_cli-0.7.9 → refactorai_cli-0.7.11}/refactorai_cli/commands/toolchains_cmds.py +0 -0
  29. {refactorai_cli-0.7.9 → refactorai_cli-0.7.11}/refactorai_cli/commands/watch_cmds.py +0 -0
  30. {refactorai_cli-0.7.9 → refactorai_cli-0.7.11}/refactorai_cli/commands/workspace_cmds.py +0 -0
  31. {refactorai_cli-0.7.9 → refactorai_cli-0.7.11}/refactorai_cli/commit_queue.py +0 -0
  32. {refactorai_cli-0.7.9 → refactorai_cli-0.7.11}/refactorai_cli/commit_telemetry.py +0 -0
  33. {refactorai_cli-0.7.9 → refactorai_cli-0.7.11}/refactorai_cli/control_plane.py +0 -0
  34. {refactorai_cli-0.7.9 → refactorai_cli-0.7.11}/refactorai_cli/credentials.py +0 -0
  35. {refactorai_cli-0.7.9 → refactorai_cli-0.7.11}/refactorai_cli/dotenv_loader.py +0 -0
  36. {refactorai_cli-0.7.9 → refactorai_cli-0.7.11}/refactorai_cli/git_scope.py +0 -0
  37. {refactorai_cli-0.7.9 → refactorai_cli-0.7.11}/refactorai_cli/local_constitution.py +0 -0
  38. {refactorai_cli-0.7.9 → refactorai_cli-0.7.11}/refactorai_cli/local_engine_runtime.py +0 -0
  39. {refactorai_cli-0.7.9 → refactorai_cli-0.7.11}/refactorai_cli/local_paths.py +0 -0
  40. {refactorai_cli-0.7.9 → refactorai_cli-0.7.11}/refactorai_cli/main.py +0 -0
  41. {refactorai_cli-0.7.9 → refactorai_cli-0.7.11}/refactorai_cli/model_policy.py +0 -0
  42. {refactorai_cli-0.7.9 → refactorai_cli-0.7.11}/refactorai_cli/pre_push_gate.py +0 -0
  43. {refactorai_cli-0.7.9 → refactorai_cli-0.7.11}/refactorai_cli/refactor_branch_store.py +0 -0
  44. {refactorai_cli-0.7.9 → refactorai_cli-0.7.11}/refactorai_cli/review_runner.py +0 -0
  45. {refactorai_cli-0.7.9 → refactorai_cli-0.7.11}/refactorai_cli/runtime_manager.py +0 -0
  46. {refactorai_cli-0.7.9 → refactorai_cli-0.7.11}/refactorai_cli/settings.py +0 -0
  47. {refactorai_cli-0.7.9 → refactorai_cli-0.7.11}/refactorai_cli/watch_ledger.py +0 -0
  48. {refactorai_cli-0.7.9 → refactorai_cli-0.7.11}/refactorai_cli/watch_state.py +0 -0
  49. {refactorai_cli-0.7.9 → refactorai_cli-0.7.11}/refactorai_cli/watch_supervisor.py +0 -0
  50. {refactorai_cli-0.7.9 → refactorai_cli-0.7.11}/refactorai_cli.egg-info/SOURCES.txt +0 -0
  51. {refactorai_cli-0.7.9 → refactorai_cli-0.7.11}/refactorai_cli.egg-info/dependency_links.txt +0 -0
  52. {refactorai_cli-0.7.9 → refactorai_cli-0.7.11}/refactorai_cli.egg-info/entry_points.txt +0 -0
  53. {refactorai_cli-0.7.9 → refactorai_cli-0.7.11}/refactorai_cli.egg-info/top_level.txt +0 -0
  54. {refactorai_cli-0.7.9 → 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.9
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.20
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.9"
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.20",
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,
@@ -1505,7 +1542,13 @@ def run_cloud_refactor_requests(
1505
1542
  progress_cb = progress_cb or (lambda e: None)
1506
1543
  approval_cb = approval_cb or (lambda r: False)
1507
1544
 
1508
- execution_mode = str(constitution.get_setting("mode", "cloud_managed") or "cloud_managed")
1545
+ from refactor_core.execution_variant import (
1546
+ platform_inference_mode,
1547
+ resolve_execution_variant,
1548
+ )
1549
+
1550
+ variant = resolve_execution_variant(getattr(constitution, "settings", {}) or {})
1551
+ execution_mode = platform_inference_mode(variant) or "cloud_managed"
1509
1552
  auth_ctx = ensure_authenticated(project_root)
1510
1553
  test_command, testcmd_note = _resolve_cloud_test_command(
1511
1554
  project_root=project_root,
@@ -1540,7 +1583,31 @@ def run_cloud_refactor_requests(
1540
1583
  test_command=test_command or "",
1541
1584
  execution_env="cloud",
1542
1585
  )
1543
- 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)
1544
1611
  status_before = _git_status_porcelain(project_root)
1545
1612
 
1546
1613
  # Managed host toolchain: when the resolved command's runtime is missing from
@@ -1557,17 +1624,22 @@ def run_cloud_refactor_requests(
1557
1624
  apply_byok_payload=apply_byok_payload,
1558
1625
  )
1559
1626
 
1560
- toolchain_env, toolchain_note = _provision_toolchain(
1561
- execution_root,
1562
- test_command,
1563
- config,
1564
- consent_cb=toolchain_consent_cb,
1565
- progress_cb=progress_cb,
1566
- dep_locator_cb=_dep_locator,
1567
- )
1568
- 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)
1569
1641
  status_after_toolchain = _git_status_porcelain(project_root)
1570
- if status_after_toolchain != status_before:
1642
+ if verify_runner is None and status_after_toolchain != status_before:
1571
1643
  reason = (
1572
1644
  "managed workspace invariant violated: toolchain/dependency setup changed the project "
1573
1645
  "working tree before any RR patch was applied; aborting run."
@@ -1601,7 +1673,7 @@ def run_cloud_refactor_requests(
1601
1673
  # we did NOT provision a managed runtime (managed provisioning already
1602
1674
  # supplies a `python`). Prevents a `python: not found` baseline failure and
1603
1675
  # the endless-defer loop it caused.
1604
- if toolchain_env is None and test_command:
1676
+ if verify_runner is None and toolchain_env is None and test_command:
1605
1677
  normalized = _normalize_command_interpreters(test_command)
1606
1678
  if normalized and normalized != test_command:
1607
1679
  _emit(
@@ -1620,23 +1692,25 @@ def run_cloud_refactor_requests(
1620
1692
  # Run-level dependency preflight: always attempt dependency restoration for
1621
1693
  # runtimes implied by the configured test command, even when binaries are
1622
1694
  # already present on PATH. This prevents import-time baseline failures from
1623
- # deferring requests one-by-one later in the loop.
1624
- for runtime in _command_runtimes(test_command):
1625
- _restore_dependencies(
1626
- execution_root,
1627
- runtime,
1628
- toolchain_env,
1629
- progress_cb,
1630
- config=config,
1631
- dep_locator_cb=_dep_locator,
1632
- )
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
+ )
1633
1707
 
1634
1708
  # Fail-fast: if a test command is configured but STILL not runnable after
1635
1709
  # provisioning + interpreter normalization, stop up front with an actionable
1636
1710
  # message instead of opening a session and deferring every RR behind an
1637
1711
  # opaque `python: not found`. (A run with NO test command keeps prior
1638
1712
  # behavior: the server decides per-RR whether it can proceed.)
1639
- if test_command and not _client_preflight_command(
1713
+ if verify_runner is None and test_command and not _client_preflight_command(
1640
1714
  project_root, test_command, env=toolchain_env
1641
1715
  ):
1642
1716
  missing = _missing_binaries(test_command, env=toolchain_env)
@@ -1663,7 +1737,10 @@ def run_cloud_refactor_requests(
1663
1737
  # Run-level baseline characterization: execute ONCE and reuse for all RR
1664
1738
  # steps. If baseline is red, stop the run before opening the RR session so
1665
1739
  # requests are not deferred one-by-one for dependency/env failures.
1666
- 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)
1667
1744
  _emit(
1668
1745
  progress_cb,
1669
1746
  phase="rr",
@@ -1671,7 +1748,7 @@ def run_cloud_refactor_requests(
1671
1748
  rr_id="",
1672
1749
  passed=baseline_ok,
1673
1750
  detail=baseline_detail,
1674
- 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"),
1675
1752
  )
1676
1753
  if not baseline_ok:
1677
1754
  reason = (
@@ -1778,7 +1855,7 @@ def run_cloud_refactor_requests(
1778
1855
  passed, detail = cached_baseline
1779
1856
  _emit(progress_cb, phase="rr", event="baseline_test", rr_id=current_rr_id,
1780
1857
  passed=passed, detail=detail,
1781
- 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"))
1782
1859
  action = session.step(
1783
1860
  _step_payload(token, current_rr_id, "baseline_result", by_id, project_root,
1784
1861
  baseline_passed=passed, baseline_detail=detail)
@@ -1858,6 +1935,7 @@ def run_cloud_refactor_requests(
1858
1935
  diff_text,
1859
1936
  test_command,
1860
1937
  env=toolchain_env,
1938
+ verify_cmd=verify_runner,
1861
1939
  )
1862
1940
  pending.applied = True
1863
1941
  if passed:
@@ -1964,6 +2042,7 @@ def _apply_and_verify(
1964
2042
  test_command,
1965
2043
  *,
1966
2044
  env: dict | None = None,
2045
+ verify_cmd: Callable[[str | None], tuple[bool, str]] | None = None,
1967
2046
  ) -> tuple[bool, str, list[str]]:
1968
2047
  """Apply the diff (with backup), run the test; revert on red.
1969
2048
 
@@ -1971,6 +2050,10 @@ def _apply_and_verify(
1971
2050
  from the diff BEFORE applying (the tree is mutated in place afterward, so it
1972
2051
  cannot be recomputed against the modified files). ``env`` optionally supplies
1973
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.
1974
2057
  """
1975
2058
  if not diff_text:
1976
2059
  return False, "empty diff", []
@@ -1987,7 +2070,10 @@ def _apply_and_verify(
1987
2070
  apply_changes(execution_root, verify_backup_dir, changes)
1988
2071
  except Exception as exc:
1989
2072
  return False, f"apply failed: {exc}", changed_files
1990
- 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)
1991
2077
  if not passed:
1992
2078
  try:
1993
2079
  revert_from_backup(execution_root, verify_backup_dir)