refactorai-cli 0.7.11__tar.gz → 0.7.12__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {refactorai_cli-0.7.11 → refactorai_cli-0.7.12}/PKG-INFO +1 -1
- {refactorai_cli-0.7.11 → refactorai_cli-0.7.12}/pyproject.toml +1 -1
- {refactorai_cli-0.7.11 → refactorai_cli-0.7.12}/refactorai_cli/__init__.py +1 -1
- {refactorai_cli-0.7.11 → refactorai_cli-0.7.12}/refactorai_cli/client.py +65 -0
- {refactorai_cli-0.7.11 → refactorai_cli-0.7.12}/refactorai_cli/commands/run_cmds.py +30 -77
- {refactorai_cli-0.7.11 → refactorai_cli-0.7.12}/refactorai_cli/commands/watch_cmds.py +10 -0
- refactorai_cli-0.7.12/refactorai_cli/mode_sync.py +140 -0
- {refactorai_cli-0.7.11 → refactorai_cli-0.7.12}/refactorai_cli/setup_flow.py +77 -5
- {refactorai_cli-0.7.11 → refactorai_cli-0.7.12}/refactorai_cli.egg-info/PKG-INFO +1 -1
- {refactorai_cli-0.7.11 → refactorai_cli-0.7.12}/refactorai_cli.egg-info/SOURCES.txt +1 -0
- {refactorai_cli-0.7.11 → refactorai_cli-0.7.12}/README.md +0 -0
- {refactorai_cli-0.7.11 → refactorai_cli-0.7.12}/refactorai_cli/auth.py +0 -0
- {refactorai_cli-0.7.11 → refactorai_cli-0.7.12}/refactorai_cli/cloud_rr.py +0 -0
- {refactorai_cli-0.7.11 → refactorai_cli-0.7.12}/refactorai_cli/commands/__init__.py +0 -0
- {refactorai_cli-0.7.11 → refactorai_cli-0.7.12}/refactorai_cli/commands/account_cmds.py +0 -0
- {refactorai_cli-0.7.11 → refactorai_cli-0.7.12}/refactorai_cli/commands/auth_cmds.py +0 -0
- {refactorai_cli-0.7.11 → refactorai_cli-0.7.12}/refactorai_cli/commands/branch_cmds.py +0 -0
- {refactorai_cli-0.7.11 → refactorai_cli-0.7.12}/refactorai_cli/commands/cloud_cmds.py +0 -0
- {refactorai_cli-0.7.11 → refactorai_cli-0.7.12}/refactorai_cli/commands/engine_cmds.py +0 -0
- {refactorai_cli-0.7.11 → refactorai_cli-0.7.12}/refactorai_cli/commands/hook_cmds.py +0 -0
- {refactorai_cli-0.7.11 → refactorai_cli-0.7.12}/refactorai_cli/commands/model_cmds.py +0 -0
- {refactorai_cli-0.7.11 → refactorai_cli-0.7.12}/refactorai_cli/commands/policy_cmds.py +0 -0
- {refactorai_cli-0.7.11 → refactorai_cli-0.7.12}/refactorai_cli/commands/pre_push_cmds.py +0 -0
- {refactorai_cli-0.7.11 → refactorai_cli-0.7.12}/refactorai_cli/commands/request_cmds.py +0 -0
- {refactorai_cli-0.7.11 → refactorai_cli-0.7.12}/refactorai_cli/commands/rules_cmds.py +0 -0
- {refactorai_cli-0.7.11 → refactorai_cli-0.7.12}/refactorai_cli/commands/runtime_cmds.py +0 -0
- {refactorai_cli-0.7.11 → refactorai_cli-0.7.12}/refactorai_cli/commands/runtime_proxy_cmds.py +0 -0
- {refactorai_cli-0.7.11 → refactorai_cli-0.7.12}/refactorai_cli/commands/setup_cmds.py +0 -0
- {refactorai_cli-0.7.11 → refactorai_cli-0.7.12}/refactorai_cli/commands/toolchains_cmds.py +0 -0
- {refactorai_cli-0.7.11 → refactorai_cli-0.7.12}/refactorai_cli/commands/workspace_cmds.py +0 -0
- {refactorai_cli-0.7.11 → refactorai_cli-0.7.12}/refactorai_cli/commit_queue.py +0 -0
- {refactorai_cli-0.7.11 → refactorai_cli-0.7.12}/refactorai_cli/commit_telemetry.py +0 -0
- {refactorai_cli-0.7.11 → refactorai_cli-0.7.12}/refactorai_cli/control_plane.py +0 -0
- {refactorai_cli-0.7.11 → refactorai_cli-0.7.12}/refactorai_cli/credentials.py +0 -0
- {refactorai_cli-0.7.11 → refactorai_cli-0.7.12}/refactorai_cli/dotenv_loader.py +0 -0
- {refactorai_cli-0.7.11 → refactorai_cli-0.7.12}/refactorai_cli/git_scope.py +0 -0
- {refactorai_cli-0.7.11 → refactorai_cli-0.7.12}/refactorai_cli/local_constitution.py +0 -0
- {refactorai_cli-0.7.11 → refactorai_cli-0.7.12}/refactorai_cli/local_engine_runtime.py +0 -0
- {refactorai_cli-0.7.11 → refactorai_cli-0.7.12}/refactorai_cli/local_paths.py +0 -0
- {refactorai_cli-0.7.11 → refactorai_cli-0.7.12}/refactorai_cli/main.py +0 -0
- {refactorai_cli-0.7.11 → refactorai_cli-0.7.12}/refactorai_cli/model_policy.py +0 -0
- {refactorai_cli-0.7.11 → refactorai_cli-0.7.12}/refactorai_cli/pre_push_gate.py +0 -0
- {refactorai_cli-0.7.11 → refactorai_cli-0.7.12}/refactorai_cli/refactor_branch_store.py +0 -0
- {refactorai_cli-0.7.11 → refactorai_cli-0.7.12}/refactorai_cli/review_runner.py +0 -0
- {refactorai_cli-0.7.11 → refactorai_cli-0.7.12}/refactorai_cli/runtime_manager.py +0 -0
- {refactorai_cli-0.7.11 → refactorai_cli-0.7.12}/refactorai_cli/settings.py +0 -0
- {refactorai_cli-0.7.11 → refactorai_cli-0.7.12}/refactorai_cli/watch_ledger.py +0 -0
- {refactorai_cli-0.7.11 → refactorai_cli-0.7.12}/refactorai_cli/watch_state.py +0 -0
- {refactorai_cli-0.7.11 → refactorai_cli-0.7.12}/refactorai_cli/watch_supervisor.py +0 -0
- {refactorai_cli-0.7.11 → refactorai_cli-0.7.12}/refactorai_cli.egg-info/dependency_links.txt +0 -0
- {refactorai_cli-0.7.11 → refactorai_cli-0.7.12}/refactorai_cli.egg-info/entry_points.txt +0 -0
- {refactorai_cli-0.7.11 → refactorai_cli-0.7.12}/refactorai_cli.egg-info/requires.txt +0 -0
- {refactorai_cli-0.7.11 → refactorai_cli-0.7.12}/refactorai_cli.egg-info/top_level.txt +0 -0
- {refactorai_cli-0.7.11 → refactorai_cli-0.7.12}/setup.cfg +0 -0
|
@@ -143,3 +143,68 @@ class PlatformClient:
|
|
|
143
143
|
)
|
|
144
144
|
payload = response.json()
|
|
145
145
|
return payload if isinstance(payload, dict) else {}
|
|
146
|
+
|
|
147
|
+
def get_project_execution_mode(self, developer_key: str, project_id: str) -> dict:
|
|
148
|
+
"""Read a project's engine execution settings via the developer key.
|
|
149
|
+
|
|
150
|
+
Used by the CLI (``setup`` / ``doctor`` / ``watch``) to sync the server's
|
|
151
|
+
``execution_mode`` (as chosen in the web UI) into the local
|
|
152
|
+
``refactor.config`` -- server-wins. Returns the project record dict.
|
|
153
|
+
"""
|
|
154
|
+
try:
|
|
155
|
+
response = httpx.get(
|
|
156
|
+
f"{self.base_url}/v1/projects/{project_id}/execution-mode",
|
|
157
|
+
headers={"Authorization": f"Bearer {developer_key}"},
|
|
158
|
+
timeout=self.timeout,
|
|
159
|
+
)
|
|
160
|
+
except httpx.HTTPError as exc:
|
|
161
|
+
raise PlatformError(f"Could not reach platform at {self.base_url}: {exc}") from exc
|
|
162
|
+
if response.status_code == 401:
|
|
163
|
+
raise PlatformError("Developer key is invalid or revoked", status_code=401)
|
|
164
|
+
if response.status_code == 404:
|
|
165
|
+
raise PlatformError("Project not found for this account", status_code=404)
|
|
166
|
+
if response.status_code >= 400:
|
|
167
|
+
raise PlatformError(
|
|
168
|
+
f"Could not read project execution mode ({response.status_code})",
|
|
169
|
+
status_code=response.status_code,
|
|
170
|
+
)
|
|
171
|
+
payload = response.json() if response.content else {}
|
|
172
|
+
return payload if isinstance(payload, dict) else {}
|
|
173
|
+
|
|
174
|
+
def set_project_execution_mode(
|
|
175
|
+
self, developer_key: str, project_id: str, execution_mode: str
|
|
176
|
+
) -> dict:
|
|
177
|
+
"""Push a chosen execution variant to the server project (developer key).
|
|
178
|
+
|
|
179
|
+
Lets ``refactor setup --mode`` keep the terminal and the web UI in sync.
|
|
180
|
+
Returns the updated project record dict.
|
|
181
|
+
"""
|
|
182
|
+
try:
|
|
183
|
+
response = httpx.patch(
|
|
184
|
+
f"{self.base_url}/v1/projects/{project_id}/execution-mode",
|
|
185
|
+
headers={
|
|
186
|
+
"Authorization": f"Bearer {developer_key}",
|
|
187
|
+
"Content-Type": "application/json",
|
|
188
|
+
},
|
|
189
|
+
json={"execution_mode": execution_mode},
|
|
190
|
+
timeout=self.timeout,
|
|
191
|
+
)
|
|
192
|
+
except httpx.HTTPError as exc:
|
|
193
|
+
raise PlatformError(f"Could not reach platform at {self.base_url}: {exc}") from exc
|
|
194
|
+
if response.status_code == 401:
|
|
195
|
+
raise PlatformError("Developer key is invalid or revoked", status_code=401)
|
|
196
|
+
if response.status_code == 404:
|
|
197
|
+
raise PlatformError("Project not found for this account", status_code=404)
|
|
198
|
+
if response.status_code >= 400:
|
|
199
|
+
detail = ""
|
|
200
|
+
try:
|
|
201
|
+
detail = str((response.json() or {}).get("detail", ""))
|
|
202
|
+
except Exception:
|
|
203
|
+
detail = response.text[:200]
|
|
204
|
+
suffix = f": {detail}" if detail else ""
|
|
205
|
+
raise PlatformError(
|
|
206
|
+
f"Could not update project execution mode ({response.status_code}){suffix}",
|
|
207
|
+
status_code=response.status_code,
|
|
208
|
+
)
|
|
209
|
+
payload = response.json() if response.content else {}
|
|
210
|
+
return payload if isinstance(payload, dict) else {}
|
|
@@ -126,15 +126,8 @@ from refactorai_cli.auth import AuthContext, AuthError, ensure_authenticated
|
|
|
126
126
|
from refactorai_cli.client import PlatformClient, PlatformError
|
|
127
127
|
from refactorai_cli.credentials import load_credentials, save_credentials
|
|
128
128
|
from refactorai_cli.dotenv_loader import DotenvResult, load_project_dotenv
|
|
129
|
-
from refactorai_cli.local_engine_runtime import DEFAULT_ENGINE_PORT, read_engine_state
|
|
130
129
|
from refactorai_cli.runtime_manager import runtime_status, runtime_version_dir
|
|
131
130
|
from refactorai_cli.settings import mask_key, platform_url
|
|
132
|
-
from refactorai_cli.setup_flow import (
|
|
133
|
-
BACKEND_BYOK,
|
|
134
|
-
BACKEND_LOCAL_SERVER,
|
|
135
|
-
read_setup_state,
|
|
136
|
-
stage_output_path,
|
|
137
|
-
)
|
|
138
131
|
|
|
139
132
|
console = Console()
|
|
140
133
|
|
|
@@ -165,66 +158,16 @@ def _sandbox_runtime():
|
|
|
165
158
|
return _sr
|
|
166
159
|
|
|
167
160
|
|
|
168
|
-
def _read_stage_output(stage_id: str) -> dict:
|
|
169
|
-
path = stage_output_path(stage_id)
|
|
170
|
-
if not path.is_file():
|
|
171
|
-
return {}
|
|
172
|
-
try:
|
|
173
|
-
payload = json.loads(path.read_text(encoding="utf-8"))
|
|
174
|
-
except (json.JSONDecodeError, OSError):
|
|
175
|
-
return {}
|
|
176
|
-
output = payload.get("output")
|
|
177
|
-
return output if isinstance(output, dict) else {}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
def _setup_recommended_model_id() -> str:
|
|
181
|
-
s5_output = _read_stage_output("S5")
|
|
182
|
-
model_id = str(s5_output.get("recommended_model_id") or "").strip()
|
|
183
|
-
return model_id or "qwen2.5-coder:1.5b-instruct"
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
def _setup_engine_base_url() -> str:
|
|
187
|
-
state = read_engine_state()
|
|
188
|
-
try:
|
|
189
|
-
port = int(state.get("port", DEFAULT_ENGINE_PORT))
|
|
190
|
-
except (TypeError, ValueError):
|
|
191
|
-
port = DEFAULT_ENGINE_PORT
|
|
192
|
-
return f"http://127.0.0.1:{port}/v1"
|
|
193
|
-
|
|
194
|
-
|
|
195
161
|
def _default_config_for_init() -> tuple[str, str]:
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
"provider: heuristic\n"
|
|
206
|
-
"model_id: heuristic-local-v1",
|
|
207
|
-
"# Ollama (local engine) - configured by `refactor init`\n"
|
|
208
|
-
"provider: ollama\n"
|
|
209
|
-
f"model_id: {model_id}\n"
|
|
210
|
-
f"base_url: {base_url}",
|
|
211
|
-
1,
|
|
212
|
-
)
|
|
213
|
-
config_text = config_text.replace(
|
|
214
|
-
"execution_variant: local_heuristic",
|
|
215
|
-
"execution_variant: local_model",
|
|
216
|
-
1,
|
|
217
|
-
)
|
|
218
|
-
return config_text, "local_server"
|
|
219
|
-
if backend == BACKEND_BYOK:
|
|
220
|
-
# BYOK only flips the execution variant. The developer chooses and
|
|
221
|
-
# uncomments a provider block in the Provider selection section.
|
|
222
|
-
config_text = DEFAULT_CONFIG.replace(
|
|
223
|
-
"execution_variant: local_heuristic",
|
|
224
|
-
"execution_variant: cloud_byok",
|
|
225
|
-
1,
|
|
226
|
-
)
|
|
227
|
-
return config_text, "byok"
|
|
162
|
+
"""Return the config `init` writes: always the ``local_heuristic`` default.
|
|
163
|
+
|
|
164
|
+
Mode selection is a *setup*-time concern (docs/46): after ``init`` the
|
|
165
|
+
developer picks the execution variant in the web UI or via ``refactor setup
|
|
166
|
+
--mode``, which persists it to ``refactor.config`` (and the server project).
|
|
167
|
+
``init`` therefore never derives the mode from prior setup-backend state --
|
|
168
|
+
that legacy coupling produced configs whose variant disagreed with the
|
|
169
|
+
server's default and is intentionally removed.
|
|
170
|
+
"""
|
|
228
171
|
return DEFAULT_CONFIG, "default"
|
|
229
172
|
|
|
230
173
|
_SEVERITY_STYLE = {
|
|
@@ -1479,7 +1422,7 @@ def init(
|
|
|
1479
1422
|
raise typer.Exit(code=1)
|
|
1480
1423
|
|
|
1481
1424
|
consti_target.write_text(DEFAULT_CONSTITUTION, encoding="utf-8")
|
|
1482
|
-
config_text,
|
|
1425
|
+
config_text, _config_profile = _default_config_for_init()
|
|
1483
1426
|
config_text = _inject_project_id(config_text, project_id)
|
|
1484
1427
|
config_target.write_text(config_text, encoding="utf-8")
|
|
1485
1428
|
gitignore_updated = _ensure_refactor_gitignore(project_root)
|
|
@@ -1534,16 +1477,11 @@ def init(
|
|
|
1534
1477
|
f"{account_label.title()}: [bold]{account_value}[/bold]. "
|
|
1535
1478
|
f"Project id saved in {CONFIG_FILENAME}."
|
|
1536
1479
|
)
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
elif config_profile == "byok":
|
|
1543
|
-
console.print(
|
|
1544
|
-
"[green]Configured[/green] refactor.config for BYOK setup "
|
|
1545
|
-
"(mode=cloud_byok, env-key-first via provider_key; credential_ref is optional fallback)."
|
|
1546
|
-
)
|
|
1480
|
+
console.print(
|
|
1481
|
+
"[green]Configured[/green] refactor.config with the default mode "
|
|
1482
|
+
"([bold]local_heuristic[/bold]). Choose your execution mode (and provider "
|
|
1483
|
+
"keys, if any) in the web UI or via `refactor setup --mode <variant>`."
|
|
1484
|
+
)
|
|
1547
1485
|
console.print(
|
|
1548
1486
|
f"Added {CONSTITUTION_FILENAME}. "
|
|
1549
1487
|
f"{CONFIG_FILENAME} is treated as local by default and added to .gitignore "
|
|
@@ -5360,6 +5298,21 @@ def doctor(
|
|
|
5360
5298
|
consti_path, _ = files
|
|
5361
5299
|
project_root = consti_path.parent
|
|
5362
5300
|
|
|
5301
|
+
# Server-wins: pull the project's execution mode (as chosen in the web UI)
|
|
5302
|
+
# into the local refactor.config before reading it, so doctor diagnoses the
|
|
5303
|
+
# mode that runs will actually use. Best-effort; a no-op when offline.
|
|
5304
|
+
try:
|
|
5305
|
+
from refactorai_cli.mode_sync import sync_execution_variant_from_server
|
|
5306
|
+
|
|
5307
|
+
synced = sync_execution_variant_from_server(
|
|
5308
|
+
project_root, emit=lambda m: console.print(f"[dim]{m}[/dim]")
|
|
5309
|
+
)
|
|
5310
|
+
if synced:
|
|
5311
|
+
files = find_project_files() or files
|
|
5312
|
+
consti_path, _ = files
|
|
5313
|
+
except Exception:
|
|
5314
|
+
pass
|
|
5315
|
+
|
|
5363
5316
|
# Load ``.env`` BEFORE parsing config so ``${VAR}`` interpolation (for
|
|
5364
5317
|
# example ``provider_key: ${OPENAI_API_KEY}``) resolves from it. The
|
|
5365
5318
|
# entrypoint already loads it for real runs; here we also surface the status
|
|
@@ -1098,6 +1098,16 @@ def watch(
|
|
|
1098
1098
|
_ensure_auth_in_window(json_output)
|
|
1099
1099
|
_report_heartbeat(project_root)
|
|
1100
1100
|
|
|
1101
|
+
# Server-wins: sync the project's execution mode (web UI choice) into the
|
|
1102
|
+
# local refactor.config before the loop reads it. Silent + best-effort so it
|
|
1103
|
+
# never pollutes the --json stream or blocks the terminal when offline.
|
|
1104
|
+
try:
|
|
1105
|
+
from refactorai_cli.mode_sync import sync_execution_variant_from_server
|
|
1106
|
+
|
|
1107
|
+
sync_execution_variant_from_server(project_root)
|
|
1108
|
+
except Exception:
|
|
1109
|
+
pass
|
|
1110
|
+
|
|
1101
1111
|
state = WatchState(repo_name=project_root.name)
|
|
1102
1112
|
try:
|
|
1103
1113
|
state.current_branch = git_scope.current_branch(project_root) or ""
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"""Execution-mode sync between the server project and local ``refactor.config``.
|
|
2
|
+
|
|
3
|
+
The web UI sets a project's ``execution_mode`` on the server (JWT). The CLI
|
|
4
|
+
authenticates with a developer key and treats the server as the source of truth
|
|
5
|
+
(server-wins): ``setup`` / ``doctor`` / ``watch`` pull the server mode and write
|
|
6
|
+
it into the local ``refactor.config`` so a UI change reflects in terminal runs.
|
|
7
|
+
``refactor setup --mode`` also pushes the chosen variant up so both agree.
|
|
8
|
+
|
|
9
|
+
All operations are best-effort: offline / unauthenticated / unbound projects are
|
|
10
|
+
a silent no-op so they never break the surrounding command.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import Callable
|
|
17
|
+
|
|
18
|
+
from refactor_core.constitution import CONFIG_FILENAME, find_project_files
|
|
19
|
+
from refactor_core.execution_variant import (
|
|
20
|
+
normalize_execution_variant,
|
|
21
|
+
resolve_execution_variant,
|
|
22
|
+
)
|
|
23
|
+
from refactor_core.store import resolve_project_id
|
|
24
|
+
|
|
25
|
+
from refactorai_cli.client import PlatformClient
|
|
26
|
+
from refactorai_cli.credentials import resolve_developer_key
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _local_config_path(project_root: Path) -> Path | None:
|
|
30
|
+
files = find_project_files(project_root)
|
|
31
|
+
if files:
|
|
32
|
+
return Path(files[1])
|
|
33
|
+
candidate = Path(project_root) / CONFIG_FILENAME
|
|
34
|
+
return candidate if candidate.is_file() else None
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _read_local_variant(project_root: Path) -> str:
|
|
38
|
+
files = find_project_files(project_root)
|
|
39
|
+
if not files:
|
|
40
|
+
return ""
|
|
41
|
+
try:
|
|
42
|
+
from refactor_core.constitution import load_constitution
|
|
43
|
+
|
|
44
|
+
constitution = load_constitution(files[0])
|
|
45
|
+
return resolve_execution_variant(getattr(constitution, "settings", {}) or {})
|
|
46
|
+
except Exception:
|
|
47
|
+
return ""
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _write_variant_to_config(config_path: Path, variant: str) -> bool:
|
|
51
|
+
"""Replace (or append) the ``execution_variant`` line in refactor.config."""
|
|
52
|
+
try:
|
|
53
|
+
text = config_path.read_text(encoding="utf-8")
|
|
54
|
+
except OSError:
|
|
55
|
+
return False
|
|
56
|
+
lines = text.splitlines()
|
|
57
|
+
new_line = f"execution_variant: {variant}"
|
|
58
|
+
replaced = False
|
|
59
|
+
for idx, line in enumerate(lines):
|
|
60
|
+
if line.strip().startswith("execution_variant:"):
|
|
61
|
+
if line.strip() == new_line:
|
|
62
|
+
return False
|
|
63
|
+
lines[idx] = new_line
|
|
64
|
+
replaced = True
|
|
65
|
+
break
|
|
66
|
+
if not replaced:
|
|
67
|
+
lines.append(new_line)
|
|
68
|
+
try:
|
|
69
|
+
config_path.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
|
70
|
+
except OSError:
|
|
71
|
+
return False
|
|
72
|
+
return True
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def sync_execution_variant_from_server(
|
|
76
|
+
project_root: Path,
|
|
77
|
+
*,
|
|
78
|
+
client: PlatformClient | None = None,
|
|
79
|
+
emit: Callable[[str], None] | None = None,
|
|
80
|
+
) -> str | None:
|
|
81
|
+
"""Pull the server project's execution mode into local config (server-wins).
|
|
82
|
+
|
|
83
|
+
Returns the newly applied variant when the local config was updated, else
|
|
84
|
+
``None`` (already in sync, offline, unauthenticated, or unbound project).
|
|
85
|
+
"""
|
|
86
|
+
try:
|
|
87
|
+
project_id = resolve_project_id(project_root)
|
|
88
|
+
if not project_id:
|
|
89
|
+
return None
|
|
90
|
+
resolved = resolve_developer_key(project_root)
|
|
91
|
+
if not resolved:
|
|
92
|
+
return None
|
|
93
|
+
client = client or PlatformClient()
|
|
94
|
+
record = client.get_project_execution_mode(resolved.key, project_id)
|
|
95
|
+
except Exception:
|
|
96
|
+
return None
|
|
97
|
+
|
|
98
|
+
server_variant = normalize_execution_variant((record or {}).get("execution_mode"))
|
|
99
|
+
if not server_variant:
|
|
100
|
+
return None
|
|
101
|
+
if server_variant == _read_local_variant(project_root):
|
|
102
|
+
return None
|
|
103
|
+
config_path = _local_config_path(project_root)
|
|
104
|
+
if config_path is None:
|
|
105
|
+
return None
|
|
106
|
+
if _write_variant_to_config(config_path, server_variant):
|
|
107
|
+
if emit:
|
|
108
|
+
emit(f"Synced execution_variant from server: {server_variant}")
|
|
109
|
+
return server_variant
|
|
110
|
+
return None
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def push_execution_variant_to_server(
|
|
114
|
+
project_root: Path,
|
|
115
|
+
variant: str,
|
|
116
|
+
*,
|
|
117
|
+
client: PlatformClient | None = None,
|
|
118
|
+
emit: Callable[[str], None] | None = None,
|
|
119
|
+
) -> bool:
|
|
120
|
+
"""Push a locally chosen execution variant to the server project.
|
|
121
|
+
|
|
122
|
+
Best-effort; returns ``True`` on a successful push, else ``False``.
|
|
123
|
+
"""
|
|
124
|
+
normalized = normalize_execution_variant(variant)
|
|
125
|
+
if not normalized:
|
|
126
|
+
return False
|
|
127
|
+
try:
|
|
128
|
+
project_id = resolve_project_id(project_root)
|
|
129
|
+
if not project_id:
|
|
130
|
+
return False
|
|
131
|
+
resolved = resolve_developer_key(project_root)
|
|
132
|
+
if not resolved:
|
|
133
|
+
return False
|
|
134
|
+
client = client or PlatformClient()
|
|
135
|
+
client.set_project_execution_mode(resolved.key, project_id, normalized)
|
|
136
|
+
except Exception:
|
|
137
|
+
return False
|
|
138
|
+
if emit:
|
|
139
|
+
emit(f"Pushed execution_variant to server: {normalized}")
|
|
140
|
+
return True
|
|
@@ -18,9 +18,11 @@ from rich.progress import BarColumn, DownloadColumn, Progress, TaskID, TextColum
|
|
|
18
18
|
from refactorai_cli.local_engine_runtime import (
|
|
19
19
|
DEFAULT_ENGINE_CONTAINER,
|
|
20
20
|
DEFAULT_ENGINE_IMAGE,
|
|
21
|
+
DEFAULT_ENGINE_PORT,
|
|
21
22
|
engine_status,
|
|
22
23
|
ensure_engine_up,
|
|
23
24
|
pull_model,
|
|
25
|
+
read_engine_state,
|
|
24
26
|
resolve_runtime as resolve_engine_runtime,
|
|
25
27
|
)
|
|
26
28
|
from refactorai_cli.local_paths import ensure_dir, refactor_home
|
|
@@ -151,6 +153,19 @@ def write_stage_output(stage_id: str, payload: dict) -> None:
|
|
|
151
153
|
tmp.replace(path)
|
|
152
154
|
|
|
153
155
|
|
|
156
|
+
def read_stage_output(stage_id: str) -> dict:
|
|
157
|
+
"""Return a stage's persisted ``output`` dict (or ``{}`` when absent)."""
|
|
158
|
+
path = stage_output_path(stage_id)
|
|
159
|
+
if not path.is_file():
|
|
160
|
+
return {}
|
|
161
|
+
try:
|
|
162
|
+
payload = json.loads(path.read_text(encoding="utf-8"))
|
|
163
|
+
except (json.JSONDecodeError, OSError):
|
|
164
|
+
return {}
|
|
165
|
+
output = payload.get("output")
|
|
166
|
+
return output if isinstance(output, dict) else {}
|
|
167
|
+
|
|
168
|
+
|
|
154
169
|
def get_setup_diagnostics() -> dict:
|
|
155
170
|
state = read_setup_state()
|
|
156
171
|
stages: dict[str, dict] = {}
|
|
@@ -562,12 +577,54 @@ def _resolve_project_variant() -> str:
|
|
|
562
577
|
return ""
|
|
563
578
|
|
|
564
579
|
|
|
580
|
+
def _local_model_provider_values() -> tuple[str, str]:
|
|
581
|
+
"""Resolve (model_id, base_url) for the local_model Ollama provider block.
|
|
582
|
+
|
|
583
|
+
``model_id`` prefers the S5 setup recommendation, falling back to a small
|
|
584
|
+
default; ``base_url`` targets the local engine port.
|
|
585
|
+
"""
|
|
586
|
+
model_id = ""
|
|
587
|
+
try:
|
|
588
|
+
s5 = read_stage_output("S5")
|
|
589
|
+
model_id = str(s5.get("recommended_model_id") or "").strip()
|
|
590
|
+
except Exception:
|
|
591
|
+
model_id = ""
|
|
592
|
+
if not model_id:
|
|
593
|
+
model_id = "qwen2.5-coder:1.5b-instruct"
|
|
594
|
+
try:
|
|
595
|
+
port = int(read_engine_state().get("port", DEFAULT_ENGINE_PORT))
|
|
596
|
+
except Exception:
|
|
597
|
+
port = DEFAULT_ENGINE_PORT
|
|
598
|
+
return model_id, f"http://127.0.0.1:{port}/v1"
|
|
599
|
+
|
|
600
|
+
|
|
601
|
+
def _apply_local_model_provider_block(text: str) -> str:
|
|
602
|
+
"""Swap the default heuristic provider block for an Ollama block (local_model).
|
|
603
|
+
|
|
604
|
+
Only rewrites when the untouched heuristic default is still present, so a
|
|
605
|
+
developer who already configured a provider is never overwritten.
|
|
606
|
+
"""
|
|
607
|
+
model_id, base_url = _local_model_provider_values()
|
|
608
|
+
return text.replace(
|
|
609
|
+
"# Heuristic (local, no LLM) - default for execution_variant: local_heuristic\n"
|
|
610
|
+
"provider: heuristic\n"
|
|
611
|
+
"model_id: heuristic-local-v1",
|
|
612
|
+
"# Ollama (local engine) - configured by `refactor setup --mode local_model`\n"
|
|
613
|
+
"provider: ollama\n"
|
|
614
|
+
f"model_id: {model_id}\n"
|
|
615
|
+
f"base_url: {base_url}",
|
|
616
|
+
1,
|
|
617
|
+
)
|
|
618
|
+
|
|
619
|
+
|
|
565
620
|
def _persist_variant_to_project_config(variant: str, emit: Callable[[str], None] | None = None) -> None:
|
|
566
621
|
"""Write the chosen ``execution_variant`` into the project's refactor.config.
|
|
567
622
|
|
|
568
623
|
Keeps a UI-selected or terminal-selected mode as the single source of truth
|
|
569
|
-
for subsequent runs (doctor/review/code read it from config).
|
|
570
|
-
|
|
624
|
+
for subsequent runs (doctor/review/code read it from config). For
|
|
625
|
+
``local_model`` it also swaps the default heuristic provider block for a local
|
|
626
|
+
Ollama block (docs/46: local_model provider config is a setup concern, not an
|
|
627
|
+
init concern). Best-effort: a missing config or write error never fails setup.
|
|
571
628
|
"""
|
|
572
629
|
if not variant:
|
|
573
630
|
return
|
|
@@ -580,14 +637,15 @@ def _persist_variant_to_project_config(variant: str, emit: Callable[[str], None]
|
|
|
580
637
|
_consti_path, config_path = files
|
|
581
638
|
config_path = Path(config_path)
|
|
582
639
|
text = config_path.read_text(encoding="utf-8")
|
|
640
|
+
if variant == LOCAL_MODEL:
|
|
641
|
+
text = _apply_local_model_provider_block(text)
|
|
583
642
|
lines = text.splitlines()
|
|
584
643
|
new_line = f"execution_variant: {variant}"
|
|
585
644
|
replaced = False
|
|
586
645
|
for idx, line in enumerate(lines):
|
|
587
646
|
if line.strip().startswith("execution_variant:"):
|
|
588
|
-
if line.strip()
|
|
589
|
-
|
|
590
|
-
lines[idx] = new_line
|
|
647
|
+
if line.strip() != new_line:
|
|
648
|
+
lines[idx] = new_line
|
|
591
649
|
replaced = True
|
|
592
650
|
break
|
|
593
651
|
if not replaced:
|
|
@@ -819,6 +877,20 @@ def run_setup(
|
|
|
819
877
|
resolved_variant = _resolve_project_variant()
|
|
820
878
|
if resolved_variant:
|
|
821
879
|
_persist_variant_to_project_config(resolved_variant, on_progress)
|
|
880
|
+
# Push the chosen variant to the server project so the web UI and the
|
|
881
|
+
# terminal agree (server-wins on later syncs). Best-effort; never blocks
|
|
882
|
+
# setup when offline or the project is not bound to a cloud record.
|
|
883
|
+
try:
|
|
884
|
+
from refactor_core.constitution import find_project_files
|
|
885
|
+
from refactorai_cli.mode_sync import push_execution_variant_to_server
|
|
886
|
+
|
|
887
|
+
files = find_project_files()
|
|
888
|
+
if files:
|
|
889
|
+
push_execution_variant_to_server(
|
|
890
|
+
Path(files[0]).parent, resolved_variant, emit=on_progress
|
|
891
|
+
)
|
|
892
|
+
except Exception:
|
|
893
|
+
pass
|
|
822
894
|
|
|
823
895
|
state = read_setup_state()
|
|
824
896
|
if not resume and not from_stage:
|
|
@@ -14,6 +14,7 @@ refactorai_cli/local_constitution.py
|
|
|
14
14
|
refactorai_cli/local_engine_runtime.py
|
|
15
15
|
refactorai_cli/local_paths.py
|
|
16
16
|
refactorai_cli/main.py
|
|
17
|
+
refactorai_cli/mode_sync.py
|
|
17
18
|
refactorai_cli/model_policy.py
|
|
18
19
|
refactorai_cli/pre_push_gate.py
|
|
19
20
|
refactorai_cli/refactor_branch_store.py
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{refactorai_cli-0.7.11 → refactorai_cli-0.7.12}/refactorai_cli/commands/runtime_proxy_cmds.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{refactorai_cli-0.7.11 → refactorai_cli-0.7.12}/refactorai_cli.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|