refactorai-cli 0.3.1__tar.gz → 0.3.2__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.3.1 → refactorai_cli-0.3.2}/PKG-INFO +3 -3
  2. {refactorai_cli-0.3.1 → refactorai_cli-0.3.2}/README.md +1 -1
  3. {refactorai_cli-0.3.1 → refactorai_cli-0.3.2}/pyproject.toml +2 -2
  4. {refactorai_cli-0.3.1 → refactorai_cli-0.3.2}/refactorai_cli/__init__.py +1 -1
  5. {refactorai_cli-0.3.1 → refactorai_cli-0.3.2}/refactorai_cli/commands/run_cmds.py +202 -82
  6. {refactorai_cli-0.3.1 → refactorai_cli-0.3.2}/refactorai_cli.egg-info/PKG-INFO +3 -3
  7. {refactorai_cli-0.3.1 → refactorai_cli-0.3.2}/refactorai_cli.egg-info/requires.txt +1 -1
  8. {refactorai_cli-0.3.1 → refactorai_cli-0.3.2}/refactorai_cli/auth.py +0 -0
  9. {refactorai_cli-0.3.1 → refactorai_cli-0.3.2}/refactorai_cli/client.py +0 -0
  10. {refactorai_cli-0.3.1 → refactorai_cli-0.3.2}/refactorai_cli/commands/__init__.py +0 -0
  11. {refactorai_cli-0.3.1 → refactorai_cli-0.3.2}/refactorai_cli/commands/auth_cmds.py +0 -0
  12. {refactorai_cli-0.3.1 → refactorai_cli-0.3.2}/refactorai_cli/commands/cloud_cmds.py +0 -0
  13. {refactorai_cli-0.3.1 → refactorai_cli-0.3.2}/refactorai_cli/commands/engine_cmds.py +0 -0
  14. {refactorai_cli-0.3.1 → refactorai_cli-0.3.2}/refactorai_cli/commands/model_cmds.py +0 -0
  15. {refactorai_cli-0.3.1 → refactorai_cli-0.3.2}/refactorai_cli/commands/rules_cmds.py +0 -0
  16. {refactorai_cli-0.3.1 → refactorai_cli-0.3.2}/refactorai_cli/commands/runtime_cmds.py +0 -0
  17. {refactorai_cli-0.3.1 → refactorai_cli-0.3.2}/refactorai_cli/commands/runtime_proxy_cmds.py +0 -0
  18. {refactorai_cli-0.3.1 → refactorai_cli-0.3.2}/refactorai_cli/commands/setup_cmds.py +0 -0
  19. {refactorai_cli-0.3.1 → refactorai_cli-0.3.2}/refactorai_cli/control_plane.py +0 -0
  20. {refactorai_cli-0.3.1 → refactorai_cli-0.3.2}/refactorai_cli/credentials.py +0 -0
  21. {refactorai_cli-0.3.1 → refactorai_cli-0.3.2}/refactorai_cli/local_constitution.py +0 -0
  22. {refactorai_cli-0.3.1 → refactorai_cli-0.3.2}/refactorai_cli/local_engine_runtime.py +0 -0
  23. {refactorai_cli-0.3.1 → refactorai_cli-0.3.2}/refactorai_cli/local_paths.py +0 -0
  24. {refactorai_cli-0.3.1 → refactorai_cli-0.3.2}/refactorai_cli/main.py +0 -0
  25. {refactorai_cli-0.3.1 → refactorai_cli-0.3.2}/refactorai_cli/model_policy.py +0 -0
  26. {refactorai_cli-0.3.1 → refactorai_cli-0.3.2}/refactorai_cli/runtime_manager.py +0 -0
  27. {refactorai_cli-0.3.1 → refactorai_cli-0.3.2}/refactorai_cli/settings.py +0 -0
  28. {refactorai_cli-0.3.1 → refactorai_cli-0.3.2}/refactorai_cli/setup_flow.py +0 -0
  29. {refactorai_cli-0.3.1 → refactorai_cli-0.3.2}/refactorai_cli.egg-info/SOURCES.txt +0 -0
  30. {refactorai_cli-0.3.1 → refactorai_cli-0.3.2}/refactorai_cli.egg-info/dependency_links.txt +0 -0
  31. {refactorai_cli-0.3.1 → refactorai_cli-0.3.2}/refactorai_cli.egg-info/entry_points.txt +0 -0
  32. {refactorai_cli-0.3.1 → refactorai_cli-0.3.2}/refactorai_cli.egg-info/top_level.txt +0 -0
  33. {refactorai_cli-0.3.1 → refactorai_cli-0.3.2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: refactorai-cli
3
- Version: 0.3.1
3
+ Version: 0.3.2
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.0.1
11
+ Requires-Dist: refactorai-core>=3.0.2
12
12
 
13
13
  # refactorai-cli
14
14
 
@@ -54,7 +54,7 @@ python -m twine upload ./refactorai-cli/dist/*
54
54
  ## Install test (local)
55
55
 
56
56
  ```bash
57
- python -m pip install ./refactorai-cli/dist/refactorai_cli-0.3.1-py3-none-any.whl
57
+ python -m pip install ./refactorai-cli/dist/refactorai_cli-0.3.2-py3-none-any.whl
58
58
  refactor --version
59
59
  ```
60
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.3.1-py3-none-any.whl
45
+ python -m pip install ./refactorai-cli/dist/refactorai_cli-0.3.2-py3-none-any.whl
46
46
  refactor --version
47
47
  ```
48
48
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "refactorai-cli"
3
- version = "0.3.1"
3
+ version = "0.3.2"
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.0.1",
15
+ "refactorai-core>=3.0.2",
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.3.1"
8
+ __version__ = "0.3.2"
@@ -665,29 +665,7 @@ def _cloud_inference(
665
665
  }
666
666
 
667
667
  if execution_mode == "cloud_byok":
668
- provider_name = str(constitution.get_setting("provider", "") or "").strip()
669
- provider_key = str(constitution.get_setting("provider_key", "") or "").strip()
670
- credential_ref = str(constitution.get_setting("credential_ref", "") or "").strip()
671
-
672
- # Env-key-first BYOK path: when provider_key is configured (typically via
673
- # ${ENV_VAR} interpolation), prefer it over credential_ref.
674
- if provider_key:
675
- if not provider_name or provider_name.lower() == "cloud":
676
- raise RuntimeError(
677
- "cloud_byok with provider_key requires an explicit provider "
678
- "(e.g. provider: openai)."
679
- )
680
- payload["provider_key"] = provider_key
681
- payload["provider"] = provider_name
682
- elif credential_ref:
683
- payload["credential_ref"] = credential_ref
684
- if provider_name and provider_name.lower() != "cloud":
685
- payload["provider"] = provider_name
686
- else:
687
- raise RuntimeError(
688
- "cloud_byok requires either `provider_key` (preferred env-key path) "
689
- "or `credential_ref` (platform-stored credential path) in refactor.config."
690
- )
668
+ _apply_byok_payload(payload, constitution)
691
669
 
692
670
  idempotency_key = _stable_idempotency_key(
693
671
  account_id=auth_ctx.account_id,
@@ -764,6 +742,185 @@ def _cloud_inference(
764
742
  )
765
743
 
766
744
 
745
+ def _apply_byok_payload(payload: dict, constitution) -> None:
746
+ """Populate BYOK provider fields on a cloud inference ``payload`` in place.
747
+
748
+ Env-key-first: prefer ``provider_key`` (typically ``${ENV_VAR}``) over a
749
+ platform-stored ``credential_ref``. Shared by the review/refactor path and
750
+ the per-RR proposal path so BYOK resolution stays consistent.
751
+ """
752
+ provider_name = str(constitution.get_setting("provider", "") or "").strip()
753
+ provider_key = str(constitution.get_setting("provider_key", "") or "").strip()
754
+ credential_ref = str(constitution.get_setting("credential_ref", "") or "").strip()
755
+
756
+ if provider_key:
757
+ if not provider_name or provider_name.lower() == "cloud":
758
+ raise RuntimeError(
759
+ "cloud_byok with provider_key requires an explicit provider "
760
+ "(e.g. provider: openai)."
761
+ )
762
+ payload["provider_key"] = provider_key
763
+ payload["provider"] = provider_name
764
+ elif credential_ref:
765
+ payload["credential_ref"] = credential_ref
766
+ if provider_name and provider_name.lower() != "cloud":
767
+ payload["provider"] = provider_name
768
+ else:
769
+ raise RuntimeError(
770
+ "cloud_byok requires either `provider_key` (preferred env-key path) "
771
+ "or `credential_ref` (platform-stored credential path) in refactor.config."
772
+ )
773
+
774
+
775
+ def _post_cloud_inference(*, payload: dict, idempotency_key: str, auth_ctx, execution_mode: str) -> dict:
776
+ """POST a cloud inference request and return the parsed JSON body.
777
+
778
+ Centralizes transport + status-code error mapping so the per-RR proposal
779
+ path surfaces the same remediation messages as review/refactor.
780
+ """
781
+ try:
782
+ response = httpx.post(
783
+ f"{platform_url()}/v1/inference/refactor",
784
+ headers={
785
+ "Authorization": f"Bearer {auth_ctx.key.key}",
786
+ "Content-Type": "application/json",
787
+ "Idempotency-Key": idempotency_key,
788
+ },
789
+ json=payload,
790
+ timeout=45.0,
791
+ )
792
+ except httpx.HTTPError as exc:
793
+ raise RuntimeError(f"Could not reach cloud inference service: {exc}") from exc
794
+
795
+ if response.status_code == 401:
796
+ raise RuntimeError("Cloud inference unauthorized (developer key invalid or revoked).")
797
+ if response.status_code == 402:
798
+ if execution_mode == "cloud_byok":
799
+ raise RuntimeError(
800
+ "Cloud BYOK is not enabled for this account (missing `run:byok`). "
801
+ "Contact your admin to enable BYOK cloud execution."
802
+ )
803
+ raise RuntimeError("Cloud inference requires paid tier (`mode: cloud_managed`).")
804
+ if response.status_code == 404 and execution_mode == "cloud_byok":
805
+ raise RuntimeError(
806
+ "BYOK credential not found or not owned by this account. "
807
+ "Check `refactor cloud credentials list` and your `credential_ref`."
808
+ )
809
+ if response.status_code == 429:
810
+ raise RuntimeError("Cloud inference quota exhausted or rate-limited.")
811
+ if response.status_code >= 400:
812
+ raise RuntimeError(f"Cloud inference failed ({response.status_code}): {response.text[:500]}")
813
+
814
+ return response.json() if response.content else {}
815
+
816
+
817
+ class _CloudRRProvider:
818
+ """Lightweight provider stub for cloud RR execution (R22).
819
+
820
+ The executor reads ``provider.model_id`` for the artifact and would call
821
+ ``provider.complete(...)`` for proposals/tests. In cloud mode the proposal
822
+ and test-gen hooks are supplied, so ``complete`` is never invoked; it raises
823
+ to make any accidental local-prompt path explicit rather than silent.
824
+ """
825
+
826
+ def __init__(self, *, model_id: str) -> None:
827
+ self.model_id = model_id
828
+ self.provider_name = "cloud"
829
+ self.supports_refactor = True
830
+
831
+ def complete(self, system: str, user: str) -> str: # pragma: no cover - guard
832
+ raise RuntimeError(
833
+ "cloud RR execution must use server-side proposal hooks; "
834
+ "provider.complete should not be called."
835
+ )
836
+
837
+
838
+ def _cloud_rr_hooks(
839
+ *,
840
+ project_root: Path,
841
+ constitution,
842
+ model_hint: str | None,
843
+ ) -> tuple[Callable, Callable, str]:
844
+ """Build (propose_fn, gen_test_fn, model_id) for cloud per-RR execution (R22).
845
+
846
+ The returned callables match the executor's hook contract. They send the
847
+ single RR's context + current source to the platform, which builds the
848
+ proprietary prompts server-side and returns a proposal / characterization
849
+ test. Idempotency keys include the RR id and attempt so retries of distinct
850
+ RRs/attempts never collapse into a replayed result.
851
+ """
852
+ execution_mode = _cloud_execution_mode(constitution) or "cloud_managed"
853
+ auth_ctx = ensure_authenticated(project_root)
854
+ constitution_body = getattr(constitution, "body", "") or ""
855
+ resolved_model = model_hint or str(
856
+ constitution.get_setting("model_id", "managed-default") or "managed-default"
857
+ )
858
+
859
+ def _rr_context(rr) -> dict:
860
+ return {
861
+ "location": getattr(rr, "location", ""),
862
+ "file_path": getattr(rr, "file_path", ""),
863
+ "category": getattr(rr, "category", ""),
864
+ "severity": getattr(rr, "severity", ""),
865
+ "recommendation": getattr(rr, "recommendation", ""),
866
+ }
867
+
868
+ def _base_payload(stage: str, rr, source: str) -> dict:
869
+ payload = {
870
+ "stage": stage,
871
+ "execution_mode": execution_mode,
872
+ "constitution_hash": constitution.content_hash,
873
+ "constitution_body": constitution_body,
874
+ "rr_context": _rr_context(rr),
875
+ "source": source,
876
+ "max_alerts": int(constitution.get_setting("max_alerts_per_run", 5) or 5),
877
+ "model_hint": resolved_model,
878
+ }
879
+ if execution_mode == "cloud_byok":
880
+ _apply_byok_payload(payload, constitution)
881
+ return payload
882
+
883
+ def _idempotency(stage: str, rr, attempt: int) -> str:
884
+ seed = "|".join(
885
+ [
886
+ auth_ctx.account_id,
887
+ execution_mode,
888
+ stage,
889
+ constitution.content_hash,
890
+ str(getattr(rr, "rr_id", "")),
891
+ str(int(attempt)),
892
+ ]
893
+ )
894
+ return hashlib.sha256(seed.encode("utf-8")).hexdigest()
895
+
896
+ def propose_fn(rr, source: str, prior_failures: list[str]) -> dict:
897
+ # Attempt number is derived from prior failures so each retry gets a
898
+ # distinct idempotency key (1-based).
899
+ attempt = len(prior_failures or []) + 1
900
+ payload = _base_payload("rr_propose", rr, source)
901
+ payload["prior_failures"] = list(prior_failures or [])
902
+ data = _post_cloud_inference(
903
+ payload=payload,
904
+ idempotency_key=_idempotency("rr_propose", rr, attempt),
905
+ auth_ctx=auth_ctx,
906
+ execution_mode=execution_mode,
907
+ )
908
+ proposal = data.get("proposal") or {}
909
+ if not isinstance(proposal, dict):
910
+ return {}
911
+ return proposal
912
+
913
+ def gen_test_fn(rr, source: str) -> dict:
914
+ # Server-side characterization-test generation is not part of Phase 1.
915
+ # Returning no test makes the executor fall back to the configured test
916
+ # command for behavioral verification (parity with a local run that has
917
+ # no generated test). This keeps cloud RR execution safe today and lets
918
+ # a future phase add a dedicated server test-gen stage.
919
+ return {}
920
+
921
+ return propose_fn, gen_test_fn, resolved_model
922
+
923
+
767
924
  def _load_project() -> tuple[Path, object, object]:
768
925
  files = find_project_files()
769
926
  if not files:
@@ -1802,7 +1959,8 @@ def review(
1802
1959
  if _sandbox_enabled(project_config):
1803
1960
  if cloud:
1804
1961
  console.print(
1805
- "[dim]review: sandbox.enabled is ignored in cloud_managed mode.[/dim]"
1962
+ f"[dim]review: sandbox.enabled is ignored in "
1963
+ f"{_cloud_execution_mode(constitution)} mode.[/dim]"
1806
1964
  )
1807
1965
  else:
1808
1966
  sandbox_ctx = _ensure_sandbox_ready(
@@ -2029,7 +2187,8 @@ def code(
2029
2187
  if _sandbox_enabled(project_config):
2030
2188
  if cloud:
2031
2189
  console.print(
2032
- "[dim]code: sandbox.enabled is ignored in cloud_managed mode.[/dim]"
2190
+ f"[dim]code: sandbox.enabled is ignored in "
2191
+ f"{_cloud_execution_mode(constitution)} mode.[/dim]"
2033
2192
  )
2034
2193
  else:
2035
2194
  sandbox_ctx = _ensure_sandbox_ready(
@@ -2133,64 +2292,23 @@ def code(
2133
2292
  console.print("[green]No issues to refactor.[/green]")
2134
2293
  return
2135
2294
 
2136
- if cloud:
2137
- # Cloud modes generate patches via the platform inference API rather
2138
- # than the local RR interactive loop. Reuse prior review findings as
2139
- # prompt context; fall back to the latest stored review findings when
2140
- # requests were carried over from an earlier run.
2141
- if not cloud_review_findings:
2142
- existing_dir = head_run_dir(project_root)
2143
- if existing_dir is not None:
2144
- try:
2145
- cloud_review_findings = [
2146
- Finding.model_validate(item) for item in (read_findings(existing_dir) or [])
2147
- ]
2148
- except Exception:
2149
- cloud_review_findings = []
2150
- with console.status("[cyan]code[/cyan] generating patches (cloud)...", spinner="dots"):
2151
- artifact = _cloud_inference(
2152
- stage="refactor",
2153
- project_root=project_root,
2154
- constitution=constitution,
2155
- target=target,
2156
- model_hint=provider,
2157
- findings=cloud_review_findings,
2158
- )
2159
- cloud_store = create_run(project_root, run_id=artifact.run_id)
2160
- cloud_store.write_artifact(artifact, target=target, status="completed")
2295
+ from refactor_core.execution.rr_executor import run_refactor_requests
2161
2296
 
2162
- _print_rr_summary(artifact)
2163
- _print_patches(artifact.patches)
2164
- gate_color = "green" if artifact.gate.status == "passed" else "red"
2165
- console.print(
2166
- f"\n[bold]result[/bold]: [{gate_color}]{artifact.gate.status}[/{gate_color}] "
2167
- f"[dim]model={artifact.model_id} mode={_cloud_execution_mode(constitution)} "
2168
- f"run={artifact.run_id}[/dim]"
2297
+ # Cloud and local both drive the SAME per-RR interactive loop (R22 cloud
2298
+ # RR parity). The only difference is where proposals come from: cloud
2299
+ # passes server-side proposal hooks; local uses the local provider and
2300
+ # client-side prompt builders. This guarantees identical UX (proposal
2301
+ # diff + approval prompt), status transitions, and outcomes table.
2302
+ cloud_propose_fn = None
2303
+ cloud_gen_test_fn = None
2304
+ rr_provider = selected_provider
2305
+ if cloud:
2306
+ cloud_propose_fn, cloud_gen_test_fn, cloud_model_id = _cloud_rr_hooks(
2307
+ project_root=project_root,
2308
+ constitution=constitution,
2309
+ model_hint=provider,
2169
2310
  )
2170
-
2171
- if apply:
2172
- gate_mode = str(constitution.get_setting("gate_mode", "strict") or "strict")
2173
- diffs = [p.diff_text for p in (artifact.patches or [])]
2174
- if not diffs:
2175
- console.print("[dim]No patches to apply.[/dim]")
2176
- else:
2177
- _apply_diffs(
2178
- project_root,
2179
- cloud_store.run_dir,
2180
- diffs,
2181
- artifact.gate.status,
2182
- gate_mode,
2183
- force,
2184
- project_config,
2185
- )
2186
- elif artifact.patches:
2187
- console.print(
2188
- "[dim]Review patches with `refactor diff`, then apply with "
2189
- "`refactor code --apply` or `refactor apply`.[/dim]"
2190
- )
2191
- return
2192
-
2193
- from refactor_core.execution.rr_executor import run_refactor_requests
2311
+ rr_provider = _CloudRRProvider(model_id=cloud_model_id)
2194
2312
 
2195
2313
  with console.status("[cyan]code[/cyan] running...", spinner="dots") as status:
2196
2314
  rr_config = (
@@ -2201,13 +2319,15 @@ def code(
2201
2319
  artifact = run_refactor_requests(
2202
2320
  project_root=project_root,
2203
2321
  constitution=constitution,
2204
- provider=selected_provider,
2322
+ provider=rr_provider,
2205
2323
  requests=requests,
2206
2324
  config=rr_config,
2207
2325
  progress_cb=_rr_progress_logger(status=status),
2208
2326
  approval_cb=_rr_approval_prompt(auto_approve=auto_approve),
2209
2327
  on_rr_failure=on_rr_failure,
2210
2328
  equivalence_mode=equivalence,
2329
+ propose_fn=cloud_propose_fn,
2330
+ gen_test_fn=cloud_gen_test_fn,
2211
2331
  )
2212
2332
  except typer.Exit:
2213
2333
  # Intentional exits (e.g. gate-blocked apply) keep their own code/message.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: refactorai-cli
3
- Version: 0.3.1
3
+ Version: 0.3.2
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.0.1
11
+ Requires-Dist: refactorai-core>=3.0.2
12
12
 
13
13
  # refactorai-cli
14
14
 
@@ -54,7 +54,7 @@ python -m twine upload ./refactorai-cli/dist/*
54
54
  ## Install test (local)
55
55
 
56
56
  ```bash
57
- python -m pip install ./refactorai-cli/dist/refactorai_cli-0.3.1-py3-none-any.whl
57
+ python -m pip install ./refactorai-cli/dist/refactorai_cli-0.3.2-py3-none-any.whl
58
58
  refactor --version
59
59
  ```
60
60
 
@@ -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.0.1
5
+ refactorai-core>=3.0.2
File without changes