refactorai-cli 0.7.8__tar.gz → 0.7.10__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 (53) hide show
  1. {refactorai_cli-0.7.8 → refactorai_cli-0.7.10}/PKG-INFO +2 -2
  2. {refactorai_cli-0.7.8 → refactorai_cli-0.7.10}/pyproject.toml +2 -2
  3. {refactorai_cli-0.7.8 → refactorai_cli-0.7.10}/refactorai_cli/__init__.py +1 -1
  4. {refactorai_cli-0.7.8 → refactorai_cli-0.7.10}/refactorai_cli/cloud_rr.py +81 -16
  5. {refactorai_cli-0.7.8 → refactorai_cli-0.7.10}/refactorai_cli/commands/run_cmds.py +58 -22
  6. {refactorai_cli-0.7.8 → refactorai_cli-0.7.10}/refactorai_cli/commands/runtime_proxy_cmds.py +22 -12
  7. {refactorai_cli-0.7.8 → refactorai_cli-0.7.10}/refactorai_cli.egg-info/PKG-INFO +2 -2
  8. {refactorai_cli-0.7.8 → refactorai_cli-0.7.10}/refactorai_cli.egg-info/requires.txt +1 -1
  9. {refactorai_cli-0.7.8 → refactorai_cli-0.7.10}/README.md +0 -0
  10. {refactorai_cli-0.7.8 → refactorai_cli-0.7.10}/refactorai_cli/auth.py +0 -0
  11. {refactorai_cli-0.7.8 → refactorai_cli-0.7.10}/refactorai_cli/client.py +0 -0
  12. {refactorai_cli-0.7.8 → refactorai_cli-0.7.10}/refactorai_cli/commands/__init__.py +0 -0
  13. {refactorai_cli-0.7.8 → refactorai_cli-0.7.10}/refactorai_cli/commands/account_cmds.py +0 -0
  14. {refactorai_cli-0.7.8 → refactorai_cli-0.7.10}/refactorai_cli/commands/auth_cmds.py +0 -0
  15. {refactorai_cli-0.7.8 → refactorai_cli-0.7.10}/refactorai_cli/commands/branch_cmds.py +0 -0
  16. {refactorai_cli-0.7.8 → refactorai_cli-0.7.10}/refactorai_cli/commands/cloud_cmds.py +0 -0
  17. {refactorai_cli-0.7.8 → refactorai_cli-0.7.10}/refactorai_cli/commands/engine_cmds.py +0 -0
  18. {refactorai_cli-0.7.8 → refactorai_cli-0.7.10}/refactorai_cli/commands/hook_cmds.py +0 -0
  19. {refactorai_cli-0.7.8 → refactorai_cli-0.7.10}/refactorai_cli/commands/model_cmds.py +0 -0
  20. {refactorai_cli-0.7.8 → refactorai_cli-0.7.10}/refactorai_cli/commands/policy_cmds.py +0 -0
  21. {refactorai_cli-0.7.8 → refactorai_cli-0.7.10}/refactorai_cli/commands/pre_push_cmds.py +0 -0
  22. {refactorai_cli-0.7.8 → refactorai_cli-0.7.10}/refactorai_cli/commands/request_cmds.py +0 -0
  23. {refactorai_cli-0.7.8 → refactorai_cli-0.7.10}/refactorai_cli/commands/rules_cmds.py +0 -0
  24. {refactorai_cli-0.7.8 → refactorai_cli-0.7.10}/refactorai_cli/commands/runtime_cmds.py +0 -0
  25. {refactorai_cli-0.7.8 → refactorai_cli-0.7.10}/refactorai_cli/commands/setup_cmds.py +0 -0
  26. {refactorai_cli-0.7.8 → refactorai_cli-0.7.10}/refactorai_cli/commands/toolchains_cmds.py +0 -0
  27. {refactorai_cli-0.7.8 → refactorai_cli-0.7.10}/refactorai_cli/commands/watch_cmds.py +0 -0
  28. {refactorai_cli-0.7.8 → refactorai_cli-0.7.10}/refactorai_cli/commands/workspace_cmds.py +0 -0
  29. {refactorai_cli-0.7.8 → refactorai_cli-0.7.10}/refactorai_cli/commit_queue.py +0 -0
  30. {refactorai_cli-0.7.8 → refactorai_cli-0.7.10}/refactorai_cli/commit_telemetry.py +0 -0
  31. {refactorai_cli-0.7.8 → refactorai_cli-0.7.10}/refactorai_cli/control_plane.py +0 -0
  32. {refactorai_cli-0.7.8 → refactorai_cli-0.7.10}/refactorai_cli/credentials.py +0 -0
  33. {refactorai_cli-0.7.8 → refactorai_cli-0.7.10}/refactorai_cli/dotenv_loader.py +0 -0
  34. {refactorai_cli-0.7.8 → refactorai_cli-0.7.10}/refactorai_cli/git_scope.py +0 -0
  35. {refactorai_cli-0.7.8 → refactorai_cli-0.7.10}/refactorai_cli/local_constitution.py +0 -0
  36. {refactorai_cli-0.7.8 → refactorai_cli-0.7.10}/refactorai_cli/local_engine_runtime.py +0 -0
  37. {refactorai_cli-0.7.8 → refactorai_cli-0.7.10}/refactorai_cli/local_paths.py +0 -0
  38. {refactorai_cli-0.7.8 → refactorai_cli-0.7.10}/refactorai_cli/main.py +0 -0
  39. {refactorai_cli-0.7.8 → refactorai_cli-0.7.10}/refactorai_cli/model_policy.py +0 -0
  40. {refactorai_cli-0.7.8 → refactorai_cli-0.7.10}/refactorai_cli/pre_push_gate.py +0 -0
  41. {refactorai_cli-0.7.8 → refactorai_cli-0.7.10}/refactorai_cli/refactor_branch_store.py +0 -0
  42. {refactorai_cli-0.7.8 → refactorai_cli-0.7.10}/refactorai_cli/review_runner.py +0 -0
  43. {refactorai_cli-0.7.8 → refactorai_cli-0.7.10}/refactorai_cli/runtime_manager.py +0 -0
  44. {refactorai_cli-0.7.8 → refactorai_cli-0.7.10}/refactorai_cli/settings.py +0 -0
  45. {refactorai_cli-0.7.8 → refactorai_cli-0.7.10}/refactorai_cli/setup_flow.py +0 -0
  46. {refactorai_cli-0.7.8 → refactorai_cli-0.7.10}/refactorai_cli/watch_ledger.py +0 -0
  47. {refactorai_cli-0.7.8 → refactorai_cli-0.7.10}/refactorai_cli/watch_state.py +0 -0
  48. {refactorai_cli-0.7.8 → refactorai_cli-0.7.10}/refactorai_cli/watch_supervisor.py +0 -0
  49. {refactorai_cli-0.7.8 → refactorai_cli-0.7.10}/refactorai_cli.egg-info/SOURCES.txt +0 -0
  50. {refactorai_cli-0.7.8 → refactorai_cli-0.7.10}/refactorai_cli.egg-info/dependency_links.txt +0 -0
  51. {refactorai_cli-0.7.8 → refactorai_cli-0.7.10}/refactorai_cli.egg-info/entry_points.txt +0 -0
  52. {refactorai_cli-0.7.8 → refactorai_cli-0.7.10}/refactorai_cli.egg-info/top_level.txt +0 -0
  53. {refactorai_cli-0.7.8 → refactorai_cli-0.7.10}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: refactorai-cli
3
- Version: 0.7.8
3
+ Version: 0.7.10
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.19
11
+ Requires-Dist: refactorai-core>=3.2.21
12
12
 
13
13
  # refactorai-cli
14
14
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "refactorai-cli"
3
- version = "0.7.8"
3
+ version = "0.7.10"
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.19",
15
+ "refactorai-core>=3.2.21",
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.8"
8
+ __version__ = "0.7.9"
@@ -328,6 +328,20 @@ def _missing_binaries(command: str | None, *, env: dict | None = None) -> list[s
328
328
  return [b for b in _required_binaries(command) if not _binary_on_path(b, env=env)]
329
329
 
330
330
 
331
+ def _command_runtimes(command: str | None) -> list[str]:
332
+ """Managed runtime ids implied by the command's required binaries."""
333
+ if not command:
334
+ return []
335
+ from refactor_core.host_toolchain import binary_to_runtime
336
+
337
+ out: list[str] = []
338
+ for binary in _required_binaries(command):
339
+ runtime = binary_to_runtime(binary)
340
+ if runtime and runtime not in out:
341
+ out.append(runtime)
342
+ return out
343
+
344
+
331
345
  def _normalize_command_interpreters(command: str | None, *, env: dict | None = None) -> str | None:
332
346
  """Rewrite a bare ``python`` command token to ``python3`` when needed.
333
347
 
@@ -521,12 +535,12 @@ def _provision_toolchain(
521
535
  ) -> tuple[dict | None, str]:
522
536
  """Provision managed runtimes for every missing binary in ``test_command``.
523
537
 
524
- Returns ``(env, note)``. ``env`` (with the toolchain(s) on ``PATH``) is
525
- returned only when at least one runtime was actually provisioned; otherwise
526
- ``None`` (the caller runs with the inherited environment). Composite commands
527
- are handled per segment so a monorepo runner like ``cd client && npm test &&
528
- cd server && pytest`` provisions BOTH node and python. No-ops when the command
529
- is already runnable, has no managed runtime, or provisioning is disabled.
538
+ Returns ``(env, note)``. ``env`` (with provisioned toolchain(s) on ``PATH``)
539
+ is returned only when at least one runtime was provisioned; otherwise
540
+ ``None`` (caller uses inherited env). Composite commands are handled per
541
+ segment so e.g. ``cd client && npm test && cd server && pytest`` can
542
+ provision BOTH node and python. Dependency restore is run-level and handled
543
+ by the caller after provisioning.
530
544
  """
531
545
  if not test_command or not _managed_toolchain_enabled(config):
532
546
  return None, ""
@@ -570,10 +584,6 @@ def _provision_toolchain(
570
584
  continue
571
585
  _emit(progress_cb, phase="rr", event="toolchain_ready", runtime=runtime)
572
586
  merged_env = _merge_toolchain_env(merged_env, env)
573
- _restore_dependencies(
574
- execution_root, runtime, merged_env, progress_cb,
575
- config=config, dep_locator_cb=dep_locator_cb,
576
- )
577
587
 
578
588
  return merged_env, " ".join(n for n in notes if n).strip()
579
589
 
@@ -1495,7 +1505,13 @@ def run_cloud_refactor_requests(
1495
1505
  progress_cb = progress_cb or (lambda e: None)
1496
1506
  approval_cb = approval_cb or (lambda r: False)
1497
1507
 
1498
- execution_mode = str(constitution.get_setting("mode", "cloud_managed") or "cloud_managed")
1508
+ from refactor_core.execution_variant import (
1509
+ platform_inference_mode,
1510
+ resolve_execution_variant,
1511
+ )
1512
+
1513
+ variant = resolve_execution_variant(getattr(constitution, "settings", {}) or {})
1514
+ execution_mode = platform_inference_mode(variant) or "cloud_managed"
1499
1515
  auth_ctx = ensure_authenticated(project_root)
1500
1516
  test_command, testcmd_note = _resolve_cloud_test_command(
1501
1517
  project_root=project_root,
@@ -1607,10 +1623,18 @@ def run_cloud_refactor_requests(
1607
1623
  if toolchain_note:
1608
1624
  notes.append(toolchain_note)
1609
1625
 
1610
- if not requests:
1611
- return _build_artifact(
1612
- run_store, run_id, constitution, model_id, outcomes, counts,
1613
- notes, stopped=False, stop_reason="", test_command=test_command,
1626
+ # Run-level dependency preflight: always attempt dependency restoration for
1627
+ # runtimes implied by the configured test command, even when binaries are
1628
+ # 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,
1614
1638
  )
1615
1639
 
1616
1640
  # Fail-fast: if a test command is configured but STILL not runnable after
@@ -1642,6 +1666,43 @@ def run_cloud_refactor_requests(
1642
1666
  notes, stopped=True, stop_reason=reason, test_command=test_command,
1643
1667
  )
1644
1668
 
1669
+ # Run-level baseline characterization: execute ONCE and reuse for all RR
1670
+ # steps. If baseline is red, stop the run before opening the RR session so
1671
+ # 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)
1673
+ _emit(
1674
+ progress_cb,
1675
+ phase="rr",
1676
+ event="baseline_test",
1677
+ rr_id="",
1678
+ passed=baseline_ok,
1679
+ detail=baseline_detail,
1680
+ execution_env="host+toolchain" if toolchain_env else "host",
1681
+ )
1682
+ if not baseline_ok:
1683
+ reason = (
1684
+ "baseline characterization test is not green. "
1685
+ f"Fix test/dependency issues, then retry. Detail: {baseline_detail}"
1686
+ )
1687
+ _emit(
1688
+ progress_cb,
1689
+ phase="rr",
1690
+ event="preflight_failed",
1691
+ reason=reason,
1692
+ test_command=test_command or "",
1693
+ )
1694
+ notes.append(reason)
1695
+ return _build_artifact(
1696
+ run_store, run_id, constitution, model_id, outcomes, counts,
1697
+ notes, stopped=True, stop_reason=reason, test_command=test_command,
1698
+ )
1699
+
1700
+ if not requests:
1701
+ return _build_artifact(
1702
+ run_store, run_id, constitution, model_id, outcomes, counts,
1703
+ notes, stopped=False, stop_reason="", test_command=test_command,
1704
+ )
1705
+
1645
1706
  session = _Session(
1646
1707
  auth_ctx=auth_ctx,
1647
1708
  execution_mode=execution_mode,
@@ -1704,6 +1765,10 @@ def run_cloud_refactor_requests(
1704
1765
 
1705
1766
  guard = 0
1706
1767
  max_iters = max(50, len(requests) * 25)
1768
+ # Cache the single run-level baseline result and reuse it whenever the
1769
+ # server asks for `run_baseline_test` for a specific RR.
1770
+ cached_baseline: tuple[bool, str] = (baseline_ok, baseline_detail)
1771
+
1707
1772
  while guard < max_iters:
1708
1773
  guard += 1
1709
1774
  act = str(action.get("action", ""))
@@ -1716,7 +1781,7 @@ def run_cloud_refactor_requests(
1716
1781
  current_rr_id = action.get("rr_id", "") or current_rr_id
1717
1782
  _emit(progress_cb, phase="rr", event="test_generating", rr_id=current_rr_id)
1718
1783
  _write_generated_test(project_root, current_rr_id, action, by_id)
1719
- passed, detail = _run_test(execution_root, test_command, env=toolchain_env)
1784
+ passed, detail = cached_baseline
1720
1785
  _emit(progress_cb, phase="rr", event="baseline_test", rr_id=current_rr_id,
1721
1786
  passed=passed, detail=detail,
1722
1787
  execution_env="host+toolchain" if toolchain_env else "host")
@@ -113,6 +113,12 @@ from refactor_core.store import (
113
113
  write_sandbox_preflight,
114
114
  )
115
115
  from refactor_core.testcmd import resolve_test_command
116
+ from refactor_core.execution_variant import (
117
+ is_cloud_execution,
118
+ platform_inference_mode,
119
+ resolve_execution_variant,
120
+ uses_platform_inference,
121
+ )
116
122
 
117
123
  from refactorai_cli.auth import AuthContext, AuthError, ensure_authenticated
118
124
  from refactorai_cli.client import PlatformClient, PlatformError
@@ -185,10 +191,10 @@ def _default_config_for_init() -> tuple[str, str]:
185
191
  if backend == BACKEND_LOCAL_SERVER:
186
192
  model_id = _setup_recommended_model_id()
187
193
  base_url = _setup_engine_base_url()
188
- # local_server stays in `mode: local` but swaps the default heuristic
194
+ # local_model runs tests on the host and swaps the default heuristic
189
195
  # provider block for an Ollama block pointing at the local engine.
190
196
  config_text = DEFAULT_CONFIG.replace(
191
- "# Heuristic (local, no LLM) - default for mode: local\n"
197
+ "# Heuristic (local, no LLM) - default for execution_variant: local_heuristic\n"
192
198
  "provider: heuristic\n"
193
199
  "model_id: heuristic-local-v1",
194
200
  "# Ollama (local engine) - configured by `refactor init`\n"
@@ -197,13 +203,18 @@ def _default_config_for_init() -> tuple[str, str]:
197
203
  f"base_url: {base_url}",
198
204
  1,
199
205
  )
206
+ config_text = config_text.replace(
207
+ "execution_variant: local_heuristic",
208
+ "execution_variant: local_model",
209
+ 1,
210
+ )
200
211
  return config_text, "local_server"
201
212
  if backend == BACKEND_BYOK:
202
- # BYOK only flips the execution mode. The developer chooses and
213
+ # BYOK only flips the execution variant. The developer chooses and
203
214
  # uncomments a provider block in the Provider selection section.
204
215
  config_text = DEFAULT_CONFIG.replace(
205
- "mode: local\n# mode: cloud_byok",
206
- "# mode: local\nmode: cloud_byok",
216
+ "execution_variant: local_heuristic",
217
+ "execution_variant: cloud_byok",
207
218
  1,
208
219
  )
209
220
  return config_text, "byok"
@@ -652,19 +663,36 @@ def _require_auth() -> None:
652
663
  raise typer.Exit(code=1) from exc
653
664
 
654
665
 
655
- def _cloud_execution_mode(constitution) -> str:
656
- """Return the cloud execution mode for the project, or "" for local mode.
666
+ def _platform_inference_mode(constitution) -> str:
667
+ """Platform inference ``execution_mode`` (``cloud_byok``/``cloud_managed``/"")."""
668
+ variant = resolve_execution_variant(getattr(constitution, "settings", {}) or {})
669
+ return platform_inference_mode(variant)
657
670
 
658
- Recognizes both cloud modes (R20):
659
- - ``cloud_managed``: platform-managed provider keys.
660
- - ``cloud_byok``: developer-supplied provider credential (via credential_ref).
661
- """
662
- mode = str(constitution.get_setting("mode", "local") or "local").strip().lower()
663
- return mode if mode in {"cloud_managed", "cloud_byok"} else ""
671
+
672
+ def _execution_variant(constitution) -> str:
673
+ return resolve_execution_variant(getattr(constitution, "settings", {}) or {})
664
674
 
665
675
 
666
676
  def _is_cloud_mode(constitution) -> bool:
667
- return bool(_cloud_execution_mode(constitution))
677
+ """True only for cloud-execution variants (loop + tests run server-side)."""
678
+ return is_cloud_execution(_execution_variant(constitution))
679
+
680
+
681
+ def _guard_local_inference_ready(variant: str) -> None:
682
+ """Fail clearly for host-verified variants whose inference is not wired yet.
683
+
684
+ ``local_managed`` (host tests + Refactor-managed inference) needs a managed
685
+ on-host inference path that is not implemented yet; without this guard the
686
+ local provider factory would silently fall back to a default provider.
687
+ """
688
+ from refactor_core.execution_variant import LOCAL_MANAGED
689
+
690
+ if variant == LOCAL_MANAGED:
691
+ raise RuntimeError(
692
+ "local_managed (host tests + Refactor-managed inference) is not "
693
+ "available yet. Use local_heuristic, local_model, or local_byok for "
694
+ "host runs, or cloud_managed for managed inference in the cloud."
695
+ )
668
696
 
669
697
 
670
698
  def _documents_digest(documents: list | None) -> str:
@@ -871,7 +899,8 @@ def _cloud_inference(
871
899
  f.model_dump() if hasattr(f, "model_dump") else dict(f) for f in (findings or [])
872
900
  ]
873
901
 
874
- execution_mode = _cloud_execution_mode(constitution) or "cloud_managed"
902
+ variant = _execution_variant(constitution)
903
+ execution_mode = platform_inference_mode(variant) or "cloud_managed"
875
904
  auth_ctx = ensure_authenticated(project_root)
876
905
  model_hint_value = model_hint or str(
877
906
  constitution.get_setting("model_id", "managed-default") or "managed-default"
@@ -3253,9 +3282,10 @@ def review(
3253
3282
  cloud = _is_cloud_mode(constitution)
3254
3283
  if _sandbox_enabled(project_config):
3255
3284
  if cloud:
3285
+ variant = _execution_variant(constitution)
3256
3286
  console.print(
3257
3287
  f"[dim]review: sandbox.enabled is ignored in "
3258
- f"{_cloud_execution_mode(constitution)} mode.[/dim]"
3288
+ f"{variant} variant.[/dim]"
3259
3289
  )
3260
3290
  else:
3261
3291
  sandbox_ctx = _ensure_sandbox_ready(
@@ -3281,6 +3311,7 @@ def review(
3281
3311
  from refactor_core.execution.orchestrator import run_with_mode
3282
3312
  from refactor_core.providers import get_provider
3283
3313
 
3314
+ _guard_local_inference_ready(_execution_variant(constitution))
3284
3315
  selected_provider = get_provider(
3285
3316
  constitution, project_root=project_root, provider_name=provider
3286
3317
  )
@@ -3622,9 +3653,10 @@ def code(
3622
3653
  sandbox_on = _sandbox_enabled(project_config)
3623
3654
  if _sandbox_enabled(project_config):
3624
3655
  if cloud:
3656
+ variant = _execution_variant(constitution)
3625
3657
  console.print(
3626
3658
  f"[dim]code: sandbox.enabled is ignored in "
3627
- f"{_cloud_execution_mode(constitution)} mode.[/dim]"
3659
+ f"{variant} variant.[/dim]"
3628
3660
  )
3629
3661
  else:
3630
3662
  sandbox_ctx = _ensure_sandbox_ready(
@@ -3638,6 +3670,7 @@ def code(
3638
3670
  if not cloud:
3639
3671
  from refactor_core.providers import get_provider
3640
3672
 
3673
+ _guard_local_inference_ready(_execution_variant(constitution))
3641
3674
  selected_provider = get_provider(
3642
3675
  constitution, project_root=project_root, provider_name=provider
3643
3676
  )
@@ -5036,6 +5069,7 @@ def _cloud_doctor(
5036
5069
  project_root: Path,
5037
5070
  constitution,
5038
5071
  execution_mode: str,
5072
+ execution_variant: str,
5039
5073
  skip_ping: bool,
5040
5074
  dotenv: DotenvResult | None = None,
5041
5075
  ) -> None:
@@ -5047,6 +5081,7 @@ def _cloud_doctor(
5047
5081
  """
5048
5082
  console.print(f"[bold]project[/bold]: {project_root}")
5049
5083
  console.print(f"[bold]mode[/bold]: {execution_mode}")
5084
+ console.print(f"[bold]execution variant[/bold]: {execution_variant}")
5050
5085
  _report_store_binding(project_root)
5051
5086
 
5052
5087
  try:
@@ -5253,12 +5288,13 @@ def doctor(
5253
5288
 
5254
5289
  _report_dotenv(dotenv, project_root)
5255
5290
 
5256
- execution_mode = _cloud_execution_mode(constitution)
5257
- if execution_mode:
5291
+ execution_variant = _execution_variant(constitution)
5292
+ if uses_platform_inference(execution_variant):
5258
5293
  _cloud_doctor(
5259
5294
  project_root=project_root,
5260
5295
  constitution=constitution,
5261
- execution_mode=execution_mode,
5296
+ execution_mode=platform_inference_mode(execution_variant),
5297
+ execution_variant=execution_variant,
5262
5298
  skip_ping=skip_ping,
5263
5299
  dotenv=dotenv,
5264
5300
  )
@@ -5274,11 +5310,11 @@ def doctor(
5274
5310
  supported_providers,
5275
5311
  )
5276
5312
 
5277
- mode = str(constitution.get_setting("mode", "local") or "local")
5313
+ variant = _execution_variant(constitution)
5278
5314
  provider_name = resolve_provider_name(constitution, provider_name=provider)
5279
5315
 
5280
5316
  console.print(f"[bold]project[/bold]: {project_root}")
5281
- console.print(f"[bold]mode[/bold]: {mode}")
5317
+ console.print(f"[bold]execution variant[/bold]: {variant}")
5282
5318
  console.print(f"[bold]provider[/bold]: {provider_name}")
5283
5319
  console.print(f"[bold]supported[/bold]: {', '.join(supported_providers())}")
5284
5320
  _report_store_binding(project_root)
@@ -3,11 +3,12 @@
3
3
  The public CLI package is intentionally thin; operational commands are executed
4
4
  by the activated runtime artifact under ``~/.refactor/runtime``.
5
5
 
6
- Cloud modes (``cloud_managed`` / ``cloud_byok``) do not require a downloaded
7
- runtime: those commands are handled locally by ``run_cmds`` which offloads only
8
- the model call to the platform inference API. Each dual-path command below
9
- detects the project's execution mode and delegates to ``run_cmds`` for cloud
10
- modes, falling back to the proprietary runtime for local mode (R20, C1).
6
+ Cloud-execution variants (``cloud_byok`` / ``cloud_managed``) run the whole
7
+ loop server-side and do not require a downloaded runtime: those commands are
8
+ handled by ``run_cmds`` speaking the cloud session protocol. Every ``local_*``
9
+ variant verifies on the host via the proprietary runtime. Each dual-path
10
+ command below resolves the project's execution variant and routes on
11
+ verification location (cloud vs local).
11
12
  """
12
13
 
13
14
  from __future__ import annotations
@@ -24,29 +25,38 @@ console = Console()
24
25
 
25
26
 
26
27
  def _project_execution_mode() -> str:
27
- """Return the configured execution mode for the current project.
28
+ """Return the configured execution variant for the current project.
28
29
 
29
- Reads ``refactor.config`` without requiring a runtime. Returns ``"local"``
30
+ Reads ``refactor.config`` without requiring a runtime. Returns
31
+ ``"local_heuristic"``
30
32
  when no project files are present or mode is unset, so local behavior (and
31
33
  the existing "run `refactor init`" guidance downstream) is preserved.
32
34
  """
33
35
  try:
34
36
  from refactor_core.constitution import find_project_files, load_constitution
37
+ from refactor_core.execution_variant import resolve_execution_variant
35
38
 
36
39
  files = find_project_files()
37
40
  if not files:
38
- return "local"
41
+ return "local_heuristic"
39
42
  consti_path, _config_path = files
40
43
  constitution = load_constitution(consti_path)
41
- mode = str(constitution.get_setting("mode", "local") or "local").strip().lower()
42
- return mode or "local"
44
+ return resolve_execution_variant(constitution.settings)
43
45
  except Exception:
44
46
  # Never let mode detection break command dispatch; default to local.
45
- return "local"
47
+ return "local_heuristic"
46
48
 
47
49
 
48
50
  def _is_cloud_project() -> bool:
49
- return _project_execution_mode() in {"cloud_managed", "cloud_byok"}
51
+ """True when the loop + tests run server-side (``cloud_byok`` / ``cloud_managed``).
52
+
53
+ Only the two ``cloud_*`` variants execute in the cloud; every ``local_*``
54
+ variant (including ``local_byok`` and ``local_managed``, which use remote
55
+ inference but verify on the host) runs through the local runtime.
56
+ """
57
+ from refactor_core.execution_variant import is_cloud_execution
58
+
59
+ return is_cloud_execution(_project_execution_mode())
50
60
 
51
61
 
52
62
  def _active_runtime_artifact() -> Path:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: refactorai-cli
3
- Version: 0.7.8
3
+ Version: 0.7.10
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.19
11
+ Requires-Dist: refactorai-core>=3.2.21
12
12
 
13
13
  # refactorai-cli
14
14
 
@@ -2,4 +2,4 @@ typer>=0.12.0
2
2
  httpx>=0.27.0
3
3
  rich>=13.7.0
4
4
  PyYAML>=6.0.1
5
- refactorai-core>=3.2.19
5
+ refactorai-core>=3.2.21