refactorai-cli 0.2.30__tar.gz → 0.3.1__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 (33) hide show
  1. {refactorai_cli-0.2.30 → refactorai_cli-0.3.1}/PKG-INFO +3 -2
  2. {refactorai_cli-0.2.30 → refactorai_cli-0.3.1}/README.md +1 -1
  3. {refactorai_cli-0.2.30 → refactorai_cli-0.3.1}/pyproject.toml +5 -1
  4. {refactorai_cli-0.2.30 → refactorai_cli-0.3.1}/refactorai_cli/__init__.py +1 -1
  5. {refactorai_cli-0.2.30 → refactorai_cli-0.3.1}/refactorai_cli/commands/run_cmds.py +95 -44
  6. {refactorai_cli-0.2.30 → refactorai_cli-0.3.1}/refactorai_cli.egg-info/PKG-INFO +3 -2
  7. {refactorai_cli-0.2.30 → refactorai_cli-0.3.1}/refactorai_cli.egg-info/requires.txt +1 -0
  8. {refactorai_cli-0.2.30 → refactorai_cli-0.3.1}/refactorai_cli/auth.py +0 -0
  9. {refactorai_cli-0.2.30 → refactorai_cli-0.3.1}/refactorai_cli/client.py +0 -0
  10. {refactorai_cli-0.2.30 → refactorai_cli-0.3.1}/refactorai_cli/commands/__init__.py +0 -0
  11. {refactorai_cli-0.2.30 → refactorai_cli-0.3.1}/refactorai_cli/commands/auth_cmds.py +0 -0
  12. {refactorai_cli-0.2.30 → refactorai_cli-0.3.1}/refactorai_cli/commands/cloud_cmds.py +0 -0
  13. {refactorai_cli-0.2.30 → refactorai_cli-0.3.1}/refactorai_cli/commands/engine_cmds.py +0 -0
  14. {refactorai_cli-0.2.30 → refactorai_cli-0.3.1}/refactorai_cli/commands/model_cmds.py +0 -0
  15. {refactorai_cli-0.2.30 → refactorai_cli-0.3.1}/refactorai_cli/commands/rules_cmds.py +0 -0
  16. {refactorai_cli-0.2.30 → refactorai_cli-0.3.1}/refactorai_cli/commands/runtime_cmds.py +0 -0
  17. {refactorai_cli-0.2.30 → refactorai_cli-0.3.1}/refactorai_cli/commands/runtime_proxy_cmds.py +0 -0
  18. {refactorai_cli-0.2.30 → refactorai_cli-0.3.1}/refactorai_cli/commands/setup_cmds.py +0 -0
  19. {refactorai_cli-0.2.30 → refactorai_cli-0.3.1}/refactorai_cli/control_plane.py +0 -0
  20. {refactorai_cli-0.2.30 → refactorai_cli-0.3.1}/refactorai_cli/credentials.py +0 -0
  21. {refactorai_cli-0.2.30 → refactorai_cli-0.3.1}/refactorai_cli/local_constitution.py +0 -0
  22. {refactorai_cli-0.2.30 → refactorai_cli-0.3.1}/refactorai_cli/local_engine_runtime.py +0 -0
  23. {refactorai_cli-0.2.30 → refactorai_cli-0.3.1}/refactorai_cli/local_paths.py +0 -0
  24. {refactorai_cli-0.2.30 → refactorai_cli-0.3.1}/refactorai_cli/main.py +0 -0
  25. {refactorai_cli-0.2.30 → refactorai_cli-0.3.1}/refactorai_cli/model_policy.py +0 -0
  26. {refactorai_cli-0.2.30 → refactorai_cli-0.3.1}/refactorai_cli/runtime_manager.py +0 -0
  27. {refactorai_cli-0.2.30 → refactorai_cli-0.3.1}/refactorai_cli/settings.py +0 -0
  28. {refactorai_cli-0.2.30 → refactorai_cli-0.3.1}/refactorai_cli/setup_flow.py +0 -0
  29. {refactorai_cli-0.2.30 → refactorai_cli-0.3.1}/refactorai_cli.egg-info/SOURCES.txt +0 -0
  30. {refactorai_cli-0.2.30 → refactorai_cli-0.3.1}/refactorai_cli.egg-info/dependency_links.txt +0 -0
  31. {refactorai_cli-0.2.30 → refactorai_cli-0.3.1}/refactorai_cli.egg-info/entry_points.txt +0 -0
  32. {refactorai_cli-0.2.30 → refactorai_cli-0.3.1}/refactorai_cli.egg-info/top_level.txt +0 -0
  33. {refactorai_cli-0.2.30 → refactorai_cli-0.3.1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: refactorai-cli
3
- Version: 0.2.30
3
+ Version: 0.3.1
4
4
  Summary: Local-first CLI for the refactor platform
5
5
  Requires-Python: >=3.11
6
6
  Description-Content-Type: text/markdown
@@ -8,6 +8,7 @@ Requires-Dist: typer>=0.12.0
8
8
  Requires-Dist: httpx>=0.27.0
9
9
  Requires-Dist: rich>=13.7.0
10
10
  Requires-Dist: PyYAML>=6.0.1
11
+ Requires-Dist: refactorai-core>=3.0.1
11
12
 
12
13
  # refactorai-cli
13
14
 
@@ -53,7 +54,7 @@ python -m twine upload ./refactorai-cli/dist/*
53
54
  ## Install test (local)
54
55
 
55
56
  ```bash
56
- python -m pip install ./refactorai-cli/dist/refactorai_cli-0.2.30-py3-none-any.whl
57
+ python -m pip install ./refactorai-cli/dist/refactorai_cli-0.3.1-py3-none-any.whl
57
58
  refactor --version
58
59
  ```
59
60
 
@@ -42,7 +42,7 @@ python -m twine upload ./refactorai-cli/dist/*
42
42
  ## Install test (local)
43
43
 
44
44
  ```bash
45
- python -m pip install ./refactorai-cli/dist/refactorai_cli-0.2.30-py3-none-any.whl
45
+ python -m pip install ./refactorai-cli/dist/refactorai_cli-0.3.1-py3-none-any.whl
46
46
  refactor --version
47
47
  ```
48
48
 
@@ -1,14 +1,18 @@
1
1
  [project]
2
2
  name = "refactorai-cli"
3
- version = "0.2.30"
3
+ version = "0.3.1"
4
4
  description = "Local-first CLI for the refactor platform"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
7
+ # R21 core split: the CLI depends on the PUBLIC client core only. The proprietary
8
+ # engine (refactor-core-engine) is never installed on a developer's machine via
9
+ # pip; local mode runs the engine from the downloaded runtime binary instead.
7
10
  dependencies = [
8
11
  "typer>=0.12.0",
9
12
  "httpx>=0.27.0",
10
13
  "rich>=13.7.0",
11
14
  "PyYAML>=6.0.1",
15
+ "refactorai-core>=3.0.1",
12
16
  ]
13
17
 
14
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.2.30"
8
+ __version__ = "0.3.1"
@@ -52,9 +52,6 @@ from refactor_core.apply import (
52
52
  )
53
53
  from refactor_core.budgeting import compute_budget
54
54
  from refactor_core.capabilities import resolve_capabilities
55
- from refactor_core.execution import resolve_mode, run_with_mode
56
- from refactor_core.execution.rr_executor import run_refactor_requests
57
- from refactor_core.execution.structural_ops import structural_compatibility_matrix
58
55
  from refactor_core.gate import evaluate_gate
59
56
  from refactor_core.refactor_requests import (
60
57
  DEFERRED,
@@ -70,30 +67,10 @@ from refactor_core.refactor_requests import (
70
67
  from refactor_core.refactor_ops import remediation_hint
71
68
  from refactor_core.indexing import index_target
72
69
  from refactor_core.models import Finding, GeneratedTest, PatchGroup, RunArtifact
73
- from refactor_core.pipeline import build_refactor_prompt, build_review_prompt
74
- from refactor_core.providers import get_provider, resolve_provider_name, supported_providers
75
70
  from refactor_core.rules import annotate_documents_with_rules, filter_documents_by_rules
76
71
  from refactor_core.security import merge_refactor_findings, suppress_findings_for_rr
77
72
  from refactor_core.security.semgrep_runner import EMBEDDED_SEMGREP_SENTINEL
78
73
  from refactor_core.security.suppression import SecuritySuppression
79
- from refactor_core.sandbox_runtime import (
80
- DEFAULT_IMAGE as SANDBOX_DEFAULT_IMAGE,
81
- DEFAULT_SHELL as SANDBOX_DEFAULT_SHELL,
82
- attach_shell as attach_sandbox_shell,
83
- command_exists_in_container,
84
- command_exists_in_sandbox,
85
- detect_installer_in_container,
86
- detect_installer_in_sandbox,
87
- ensure_sandbox,
88
- exec_in_container,
89
- exec_in_container_stream,
90
- exec_in_sandbox,
91
- exec_in_sandbox_stream,
92
- remove_sandbox_container,
93
- sandbox_name,
94
- resolve_runtime,
95
- stop_sandbox,
96
- )
97
74
  from refactor_core.store import (
98
75
  append_sandbox_install_history,
99
76
  clear_sandbox_state,
@@ -125,6 +102,27 @@ from refactorai_cli.setup_flow import (
125
102
 
126
103
  console = Console()
127
104
 
105
+ # Local mirrors of engine-only sandbox defaults (R21 core split). These are used
106
+ # as Typer option defaults, which are evaluated at import time, so they cannot be
107
+ # lazy-imported from the engine-only ``refactor_core.enginecore.sandbox_runtime`` module.
108
+ # They mirror ``sandbox_runtime.DEFAULT_IMAGE`` / ``DEFAULT_SHELL`` and are only
109
+ # meaningful in local mode (where the engine is present via the runtime binary).
110
+ SANDBOX_DEFAULT_IMAGE = "docker.io/library/alpine:3.20"
111
+ SANDBOX_DEFAULT_SHELL = "/bin/sh"
112
+
113
+
114
+ def _sandbox_runtime():
115
+ """Lazily import the engine-only sandbox runtime module (local mode only).
116
+
117
+ Cloud modes never touch the sandbox; this import is therefore reached only on
118
+ the local execution path, where the engine is present (inside the runtime
119
+ binary). A client-only install that never runs local commands never imports
120
+ it, keeping engine code off the developer's disk (R21 core split).
121
+ """
122
+ from refactor_core.enginecore import sandbox_runtime as _sr
123
+
124
+ return _sr
125
+
128
126
 
129
127
  def _read_stage_output(stage_id: str) -> dict:
130
128
  path = stage_output_path(stage_id)
@@ -159,19 +157,25 @@ def _default_config_for_init() -> tuple[str, str]:
159
157
  if backend == BACKEND_LOCAL_SERVER:
160
158
  model_id = _setup_recommended_model_id()
161
159
  base_url = _setup_engine_base_url()
162
- config_text = DEFAULT_CONFIG.replace("provider: heuristic", "provider: ollama", 1)
163
- config_text = config_text.replace(
160
+ # local_server stays in `mode: local` but swaps the default heuristic
161
+ # provider block for an Ollama block pointing at the local engine.
162
+ config_text = DEFAULT_CONFIG.replace(
163
+ "# Heuristic (local, no LLM) - default for mode: local\n"
164
+ "provider: heuristic\n"
164
165
  "model_id: heuristic-local-v1",
165
- f"model_id: {model_id}\nbase_url: {base_url}",
166
+ "# Ollama (local engine) - configured by `refactor init`\n"
167
+ "provider: ollama\n"
168
+ f"model_id: {model_id}\n"
169
+ f"base_url: {base_url}",
166
170
  1,
167
171
  )
168
172
  return config_text, "local_server"
169
173
  if backend == BACKEND_BYOK:
170
- config_text = DEFAULT_CONFIG.replace("mode: local", "mode: cloud_byok", 1)
171
- config_text = config_text.replace("provider: heuristic", "provider: openai", 1)
172
- config_text = config_text.replace(
173
- "model_id: heuristic-local-v1",
174
- "model_id: gpt-4.1-mini\nprovider_key: ${OPENAI_API_KEY}\n# credential_ref: cred_01J...",
174
+ # BYOK only flips the execution mode. The developer chooses and
175
+ # uncomments a provider block in the Provider selection section.
176
+ config_text = DEFAULT_CONFIG.replace(
177
+ "mode: local\n# mode: cloud_byok",
178
+ "# mode: local\nmode: cloud_byok",
175
179
  1,
176
180
  )
177
181
  return config_text, "byok"
@@ -638,14 +642,14 @@ def _cloud_inference(
638
642
  documents, _scope_meta = filter_documents_by_rules(documents, project_root=Path(project_root))
639
643
  rule_meta = annotate_documents_with_rules(documents, project_root=Path(project_root))
640
644
 
641
- if stage == "review":
642
- system_prompt, user_prompt = build_review_prompt(constitution, documents, max_alerts)
643
- else:
644
- # Feed prior review findings into the refactor prompt so cloud `code`
645
- # generates patches targeting the same issues (parity with local RR).
646
- system_prompt, user_prompt = build_refactor_prompt(
647
- constitution, documents, list(findings or []), max_alerts
648
- )
645
+ # Server-side prompt building (R21): send raw indexed documents + the
646
+ # constitution body and let the platform build the prompts. The proprietary
647
+ # prompt engineering stays off the developer's machine. Prior review findings
648
+ # are forwarded for the refactor stage so cloud `code` targets the same
649
+ # issues (parity with local RR).
650
+ request_findings = [
651
+ f.model_dump() if hasattr(f, "model_dump") else dict(f) for f in (findings or [])
652
+ ]
649
653
 
650
654
  execution_mode = _cloud_execution_mode(constitution) or "cloud_managed"
651
655
  auth_ctx = ensure_authenticated(project_root)
@@ -653,8 +657,9 @@ def _cloud_inference(
653
657
  "stage": stage,
654
658
  "execution_mode": execution_mode,
655
659
  "constitution_hash": constitution.content_hash,
656
- "system_prompt": system_prompt,
657
- "user_prompt": user_prompt,
660
+ "documents": documents,
661
+ "constitution_body": getattr(constitution, "body", "") or "",
662
+ "findings": request_findings if stage != "review" else [],
658
663
  "max_alerts": max_alerts,
659
664
  "model_hint": model_hint or str(constitution.get_setting("model_id", "managed-default") or "managed-default"),
660
665
  }
@@ -1186,6 +1191,15 @@ def _sandbox_preflight(
1186
1191
  workdir: str | None = None,
1187
1192
  ) -> tuple[bool, str]:
1188
1193
  """Run sandbox preflight and optionally perform approved install plan."""
1194
+ _sr = _sandbox_runtime()
1195
+ command_exists_in_container = _sr.command_exists_in_container
1196
+ command_exists_in_sandbox = _sr.command_exists_in_sandbox
1197
+ exec_in_container = _sr.exec_in_container
1198
+ exec_in_container_stream = _sr.exec_in_container_stream
1199
+ exec_in_sandbox = _sr.exec_in_sandbox
1200
+ exec_in_sandbox_stream = _sr.exec_in_sandbox_stream
1201
+ detect_installer_in_container = _sr.detect_installer_in_container
1202
+ detect_installer_in_sandbox = _sr.detect_installer_in_sandbox
1189
1203
 
1190
1204
  has_container_ctx = bool(runtime and container_name)
1191
1205
  resolved_workdir = str(workdir or "/workspace")
@@ -1485,6 +1499,10 @@ def _ensure_sandbox_ready(
1485
1499
  continue_on_preflight_failure: bool = False,
1486
1500
  ) -> dict | None:
1487
1501
  """Ensure sandbox lifecycle + preflight for stage execution."""
1502
+ _sr = _sandbox_runtime()
1503
+ resolve_runtime = _sr.resolve_runtime
1504
+ sandbox_name = _sr.sandbox_name
1505
+ ensure_sandbox = _sr.ensure_sandbox
1488
1506
  sandbox_cfg = _sandbox_settings(project_config)
1489
1507
  lifecycle = _sandbox_lifecycle(project_config)
1490
1508
  ephemeral = lifecycle == "per_run" and stage in {"review", "code"}
@@ -1553,6 +1571,9 @@ def _teardown_sandbox_context(project_root: Path, ctx: dict | None) -> None:
1553
1571
  """Clean up ephemeral per-run sandboxes."""
1554
1572
  if not ctx or not ctx.get("ephemeral"):
1555
1573
  return
1574
+ _sr = _sandbox_runtime()
1575
+ remove_sandbox_container = _sr.remove_sandbox_container
1576
+ sandbox_name = _sr.sandbox_name
1556
1577
  runtime = str(ctx.get("runtime") or "")
1557
1578
  name = str(ctx.get("container_name") or "")
1558
1579
  ok, msg = remove_sandbox_container(runtime=runtime, container_name=name)
@@ -1587,6 +1608,10 @@ def start(
1587
1608
  project_root, _constitution, project_config = _load_project()
1588
1609
  register_project(project_root)
1589
1610
 
1611
+ _sr = _sandbox_runtime()
1612
+ resolve_runtime = _sr.resolve_runtime
1613
+ ensure_sandbox = _sr.ensure_sandbox
1614
+ attach_sandbox_shell = _sr.attach_shell
1590
1615
  sandbox_cfg = _sandbox_settings(project_config)
1591
1616
  preferred_runtime = str(runtime or sandbox_cfg.get("runtime", "podman") or "podman")
1592
1617
  resolved_runtime, reason = resolve_runtime(preferred_runtime)
@@ -1662,7 +1687,7 @@ def stop() -> None:
1662
1687
  "Switch to a valid directory and retry."
1663
1688
  )
1664
1689
  raise typer.Exit(code=1) from None
1665
- ok, message = stop_sandbox(project_root=project_root)
1690
+ ok, message = _sandbox_runtime().stop_sandbox(project_root=project_root)
1666
1691
  if ok:
1667
1692
  console.print(f"[green]{message}[/green]")
1668
1693
  return
@@ -1685,7 +1710,7 @@ def shell(
1685
1710
  if not sys.stdin.isatty():
1686
1711
  console.print("[yellow]Non-interactive terminal; cannot attach shell.[/yellow]")
1687
1712
  raise typer.Exit(code=1)
1688
- ok, message = attach_sandbox_shell(project_root=project_root, shell_path=shell_path)
1713
+ ok, message = _sandbox_runtime().attach_shell(project_root=project_root, shell_path=shell_path)
1689
1714
  if ok:
1690
1715
  return
1691
1716
  console.print(f"[red]Sandbox shell failed:[/red] {message}")
@@ -1797,6 +1822,9 @@ def review(
1797
1822
  model_hint=provider,
1798
1823
  )
1799
1824
  else:
1825
+ from refactor_core.execution.orchestrator import run_with_mode
1826
+ from refactor_core.providers import get_provider
1827
+
1800
1828
  selected_provider = get_provider(
1801
1829
  constitution, project_root=project_root, provider_name=provider
1802
1830
  )
@@ -2013,6 +2041,8 @@ def code(
2013
2041
  )
2014
2042
  selected_provider = None
2015
2043
  if not cloud:
2044
+ from refactor_core.providers import get_provider
2045
+
2016
2046
  selected_provider = get_provider(
2017
2047
  constitution, project_root=project_root, provider_name=provider
2018
2048
  )
@@ -2035,6 +2065,8 @@ def code(
2035
2065
  )
2036
2066
  cloud_review_findings = list(review_artifact.findings or [])
2037
2067
  else:
2068
+ from refactor_core.execution.orchestrator import run_with_mode
2069
+
2038
2070
  with console.status("[cyan]review[/cyan] analyzing...", spinner="dots") as status:
2039
2071
  review_progress = _agent_progress_logger(
2040
2072
  "review", status=status, verbose_steps=True
@@ -2158,6 +2190,8 @@ def code(
2158
2190
  )
2159
2191
  return
2160
2192
 
2193
+ from refactor_core.execution.rr_executor import run_refactor_requests
2194
+
2161
2195
  with console.status("[cyan]code[/cyan] running...", spinner="dots") as status:
2162
2196
  rr_config = (
2163
2197
  dict(getattr(project_config, "settings", {}) or {})
@@ -2234,6 +2268,9 @@ def requests(
2234
2268
  if not deferred_rrs:
2235
2269
  console.print("[dim]No deferred requests to retry.[/dim]")
2236
2270
  else:
2271
+ from refactor_core.execution.rr_executor import run_refactor_requests
2272
+ from refactor_core.providers import get_provider
2273
+
2237
2274
  selected_provider = get_provider(
2238
2275
  constitution, project_root=project_root, provider_name=provider
2239
2276
  )
@@ -3253,6 +3290,16 @@ def doctor(
3253
3290
  )
3254
3291
  return
3255
3292
 
3293
+ # Local-mode doctor uses engine-only provider/execution helpers. These are
3294
+ # lazily imported here so a client-only install (cloud modes) never requires
3295
+ # the engine on disk (R21 core split); local mode has the engine via binary.
3296
+ from refactor_core.execution.modes import resolve_mode
3297
+ from refactor_core.providers import (
3298
+ get_provider,
3299
+ resolve_provider_name,
3300
+ supported_providers,
3301
+ )
3302
+
3256
3303
  mode = str(constitution.get_setting("mode", "local") or "local")
3257
3304
  provider_name = resolve_provider_name(constitution, provider_name=provider)
3258
3305
 
@@ -3398,7 +3445,7 @@ def _report_sandbox(project_root: Path, config) -> None:
3398
3445
  enabled = True
3399
3446
  runtime_pref = str(sandbox_cfg.get("runtime", "podman") or "podman")
3400
3447
  image = str(sandbox_cfg.get("image") or SANDBOX_DEFAULT_IMAGE)
3401
- resolved_runtime, reason = resolve_runtime(runtime_pref)
3448
+ resolved_runtime, reason = _sandbox_runtime().resolve_runtime(runtime_pref)
3402
3449
 
3403
3450
  console.print(f"[bold]project[/bold]: {project_root}")
3404
3451
  console.print(f"[bold]sandbox enabled[/bold]: {enabled} [dim](mandatory for local runs)[/dim]")
@@ -3448,6 +3495,10 @@ def _report_sandbox(project_root: Path, config) -> None:
3448
3495
 
3449
3496
 
3450
3497
  def _report_structural_readiness(project_root: Path, config) -> None:
3498
+ # Engine-only structural ops (local doctor diagnostic); lazy so client-only
3499
+ # installs never require the engine on disk (R21 core split).
3500
+ from refactor_core.execution.structural_ops import structural_compatibility_matrix
3501
+
3451
3502
  settings = dict(getattr(config, "settings", {}) or {})
3452
3503
  refactor_cfg = settings.get("refactor", {})
3453
3504
  if not isinstance(refactor_cfg, dict):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: refactorai-cli
3
- Version: 0.2.30
3
+ Version: 0.3.1
4
4
  Summary: Local-first CLI for the refactor platform
5
5
  Requires-Python: >=3.11
6
6
  Description-Content-Type: text/markdown
@@ -8,6 +8,7 @@ Requires-Dist: typer>=0.12.0
8
8
  Requires-Dist: httpx>=0.27.0
9
9
  Requires-Dist: rich>=13.7.0
10
10
  Requires-Dist: PyYAML>=6.0.1
11
+ Requires-Dist: refactorai-core>=3.0.1
11
12
 
12
13
  # refactorai-cli
13
14
 
@@ -53,7 +54,7 @@ python -m twine upload ./refactorai-cli/dist/*
53
54
  ## Install test (local)
54
55
 
55
56
  ```bash
56
- python -m pip install ./refactorai-cli/dist/refactorai_cli-0.2.30-py3-none-any.whl
57
+ python -m pip install ./refactorai-cli/dist/refactorai_cli-0.3.1-py3-none-any.whl
57
58
  refactor --version
58
59
  ```
59
60
 
@@ -2,3 +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.0.1