refactorai-cli 0.4.11__tar.gz → 0.4.14__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 (46) hide show
  1. {refactorai_cli-0.4.11 → refactorai_cli-0.4.14}/PKG-INFO +2 -2
  2. {refactorai_cli-0.4.11 → refactorai_cli-0.4.14}/pyproject.toml +2 -2
  3. {refactorai_cli-0.4.11 → refactorai_cli-0.4.14}/refactorai_cli/__init__.py +1 -1
  4. {refactorai_cli-0.4.11 → refactorai_cli-0.4.14}/refactorai_cli/cloud_rr.py +15 -2
  5. {refactorai_cli-0.4.11 → refactorai_cli-0.4.14}/refactorai_cli/commands/run_cmds.py +59 -9
  6. {refactorai_cli-0.4.11 → refactorai_cli-0.4.14}/refactorai_cli/commands/watch_cmds.py +17 -3
  7. {refactorai_cli-0.4.11 → refactorai_cli-0.4.14}/refactorai_cli.egg-info/PKG-INFO +2 -2
  8. {refactorai_cli-0.4.11 → refactorai_cli-0.4.14}/refactorai_cli.egg-info/requires.txt +1 -1
  9. {refactorai_cli-0.4.11 → refactorai_cli-0.4.14}/README.md +0 -0
  10. {refactorai_cli-0.4.11 → refactorai_cli-0.4.14}/refactorai_cli/auth.py +0 -0
  11. {refactorai_cli-0.4.11 → refactorai_cli-0.4.14}/refactorai_cli/client.py +0 -0
  12. {refactorai_cli-0.4.11 → refactorai_cli-0.4.14}/refactorai_cli/commands/__init__.py +0 -0
  13. {refactorai_cli-0.4.11 → refactorai_cli-0.4.14}/refactorai_cli/commands/account_cmds.py +0 -0
  14. {refactorai_cli-0.4.11 → refactorai_cli-0.4.14}/refactorai_cli/commands/auth_cmds.py +0 -0
  15. {refactorai_cli-0.4.11 → refactorai_cli-0.4.14}/refactorai_cli/commands/branch_cmds.py +0 -0
  16. {refactorai_cli-0.4.11 → refactorai_cli-0.4.14}/refactorai_cli/commands/cloud_cmds.py +0 -0
  17. {refactorai_cli-0.4.11 → refactorai_cli-0.4.14}/refactorai_cli/commands/engine_cmds.py +0 -0
  18. {refactorai_cli-0.4.11 → refactorai_cli-0.4.14}/refactorai_cli/commands/hook_cmds.py +0 -0
  19. {refactorai_cli-0.4.11 → refactorai_cli-0.4.14}/refactorai_cli/commands/model_cmds.py +0 -0
  20. {refactorai_cli-0.4.11 → refactorai_cli-0.4.14}/refactorai_cli/commands/request_cmds.py +0 -0
  21. {refactorai_cli-0.4.11 → refactorai_cli-0.4.14}/refactorai_cli/commands/rules_cmds.py +0 -0
  22. {refactorai_cli-0.4.11 → refactorai_cli-0.4.14}/refactorai_cli/commands/runtime_cmds.py +0 -0
  23. {refactorai_cli-0.4.11 → refactorai_cli-0.4.14}/refactorai_cli/commands/runtime_proxy_cmds.py +0 -0
  24. {refactorai_cli-0.4.11 → refactorai_cli-0.4.14}/refactorai_cli/commands/setup_cmds.py +0 -0
  25. {refactorai_cli-0.4.11 → refactorai_cli-0.4.14}/refactorai_cli/commands/toolchains_cmds.py +0 -0
  26. {refactorai_cli-0.4.11 → refactorai_cli-0.4.14}/refactorai_cli/commit_queue.py +0 -0
  27. {refactorai_cli-0.4.11 → refactorai_cli-0.4.14}/refactorai_cli/control_plane.py +0 -0
  28. {refactorai_cli-0.4.11 → refactorai_cli-0.4.14}/refactorai_cli/credentials.py +0 -0
  29. {refactorai_cli-0.4.11 → refactorai_cli-0.4.14}/refactorai_cli/dotenv_loader.py +0 -0
  30. {refactorai_cli-0.4.11 → refactorai_cli-0.4.14}/refactorai_cli/git_scope.py +0 -0
  31. {refactorai_cli-0.4.11 → refactorai_cli-0.4.14}/refactorai_cli/local_constitution.py +0 -0
  32. {refactorai_cli-0.4.11 → refactorai_cli-0.4.14}/refactorai_cli/local_engine_runtime.py +0 -0
  33. {refactorai_cli-0.4.11 → refactorai_cli-0.4.14}/refactorai_cli/local_paths.py +0 -0
  34. {refactorai_cli-0.4.11 → refactorai_cli-0.4.14}/refactorai_cli/main.py +0 -0
  35. {refactorai_cli-0.4.11 → refactorai_cli-0.4.14}/refactorai_cli/model_policy.py +0 -0
  36. {refactorai_cli-0.4.11 → refactorai_cli-0.4.14}/refactorai_cli/refactor_branch_store.py +0 -0
  37. {refactorai_cli-0.4.11 → refactorai_cli-0.4.14}/refactorai_cli/review_runner.py +0 -0
  38. {refactorai_cli-0.4.11 → refactorai_cli-0.4.14}/refactorai_cli/runtime_manager.py +0 -0
  39. {refactorai_cli-0.4.11 → refactorai_cli-0.4.14}/refactorai_cli/settings.py +0 -0
  40. {refactorai_cli-0.4.11 → refactorai_cli-0.4.14}/refactorai_cli/setup_flow.py +0 -0
  41. {refactorai_cli-0.4.11 → refactorai_cli-0.4.14}/refactorai_cli/watch_state.py +0 -0
  42. {refactorai_cli-0.4.11 → refactorai_cli-0.4.14}/refactorai_cli.egg-info/SOURCES.txt +0 -0
  43. {refactorai_cli-0.4.11 → refactorai_cli-0.4.14}/refactorai_cli.egg-info/dependency_links.txt +0 -0
  44. {refactorai_cli-0.4.11 → refactorai_cli-0.4.14}/refactorai_cli.egg-info/entry_points.txt +0 -0
  45. {refactorai_cli-0.4.11 → refactorai_cli-0.4.14}/refactorai_cli.egg-info/top_level.txt +0 -0
  46. {refactorai_cli-0.4.11 → refactorai_cli-0.4.14}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: refactorai-cli
3
- Version: 0.4.11
3
+ Version: 0.4.14
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.6
11
+ Requires-Dist: refactorai-core>=3.2.8
12
12
 
13
13
  # refactorai-cli
14
14
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "refactorai-cli"
3
- version = "0.4.11"
3
+ version = "0.4.14"
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.6",
15
+ "refactorai-core>=3.2.8",
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.4.11"
8
+ __version__ = "0.4.14"
@@ -651,6 +651,7 @@ def run_cloud_refactor_requests(
651
651
  apply_byok_payload: Callable[[dict, object], None] | None = None,
652
652
  toolchain_consent_cb: Callable[[dict], bool] | None = None,
653
653
  commit_cb: Callable[[object], None] | None = None,
654
+ on_rr_failure: str = "continue",
654
655
  ) -> RunArtifact:
655
656
  """Drive pending RRs through the cloud server-run loop and return a summary.
656
657
 
@@ -665,6 +666,12 @@ def run_cloud_refactor_requests(
665
666
  once per RR that reaches ``APPLIED`` so the CLI can record one git commit per
666
667
  resolved request on an isolated refactor branch. Best-effort; git failures
667
668
  never break the cloud RR loop.
669
+
670
+ ``on_rr_failure`` (``continue`` | ``defer`` | ``stop``) mirrors the local
671
+ executor policy and is forwarded to the server so a single failing RR no
672
+ longer halts the whole run by default (parity with local). The client stays a
673
+ thin executor: it stops only when the server returns a ``failed`` verdict with
674
+ no ``next_rr_id`` (older servers that always stop remain compatible).
668
675
  """
669
676
  project_root = Path(project_root)
670
677
  progress_cb = progress_cb or (lambda e: None)
@@ -741,6 +748,7 @@ def run_cloud_refactor_requests(
741
748
  "constitution_hash": constitution.content_hash,
742
749
  "constitution_body": getattr(constitution, "body", "") or "",
743
750
  "model_hint": resolved_model,
751
+ "on_rr_failure": str(on_rr_failure or "continue"),
744
752
  "rrs": [
745
753
  {
746
754
  "rr_id": rr.rr_id,
@@ -875,12 +883,17 @@ def run_cloud_refactor_requests(
875
883
  attempts_by_id.get(completed_id, 0 if verdict == "deferred" else 1),
876
884
  outcomes, counts, progress_cb, commit_cb=commit_cb,
877
885
  )
878
- if verdict == "failed":
886
+ next_id = str(action.get("next_rr_id", "") or "")
887
+ # With `continue`/`defer` (the default) the server advances after a
888
+ # failure, so the client resolves the remaining requests in one pass
889
+ # instead of stopping at the first failure (parity with local). Only a
890
+ # `stop` policy halts the run (no next RR named); a failed *last* RR
891
+ # under `continue` simply ends the queue, it is not a halt.
892
+ if verdict == "failed" and not next_id and str(on_rr_failure) == "stop":
879
893
  stopped = True
880
894
  stop_reason = str(action.get("message", "") or "refactor request failed")
881
895
  _emit(progress_cb, phase="rr", event="run_stopped", rr_id=completed_id, reason=stop_reason)
882
896
  break
883
- next_id = str(action.get("next_rr_id", "") or "")
884
897
  if not next_id:
885
898
  break
886
899
  current_rr_id = next_id
@@ -651,6 +651,26 @@ def _is_cloud_mode(constitution) -> bool:
651
651
  return bool(_cloud_execution_mode(constitution))
652
652
 
653
653
 
654
+ def _documents_digest(documents: list | None) -> str:
655
+ """Stable digest of the exact documents sent to the platform.
656
+
657
+ Folds each document's path + content so a commit-scoped review keys on the
658
+ code it actually reviews. Without this, two commits share the same
659
+ idempotency key (same account/stage/constitution/target) and the server
660
+ replays the FIRST commit's findings for the second commit -- re-suggesting
661
+ changes that were already applied/merged (docs/41 `R28`).
662
+ """
663
+ hasher = hashlib.sha256()
664
+ for doc in sorted(documents or [], key=lambda d: str((d or {}).get("path", ""))):
665
+ if not isinstance(doc, dict):
666
+ continue
667
+ hasher.update(str(doc.get("path", "")).encode("utf-8"))
668
+ hasher.update(b"\x00")
669
+ hasher.update(str(doc.get("content", "")).encode("utf-8"))
670
+ hasher.update(b"\x1f")
671
+ return hasher.hexdigest()
672
+
673
+
654
674
  def _stable_idempotency_key(
655
675
  *,
656
676
  account_id: str,
@@ -659,15 +679,29 @@ def _stable_idempotency_key(
659
679
  target: str,
660
680
  max_alerts: int,
661
681
  execution_mode: str,
682
+ scope_digest: str = "",
662
683
  ) -> str:
663
684
  """Deterministic idempotency key so retries replay instead of re-charging (C10).
664
685
 
665
- The key MUST change when inputs change (target/constitution/stage/mode) so
666
- distinct runs are not incorrectly deduplicated, and MUST be stable across
686
+ The key MUST change when inputs change (target/constitution/stage/mode/scope)
687
+ so distinct runs are not incorrectly deduplicated, and MUST be stable across
667
688
  retries of the same logical run so managed quota is not double-charged.
689
+
690
+ ``scope_digest`` folds the actual reviewed documents into the key so a
691
+ commit-scoped review of a different commit (or of changed code) produces a
692
+ distinct key and a fresh server run, instead of replaying an earlier commit's
693
+ cached findings.
668
694
  """
669
695
  seed = "|".join(
670
- [account_id, execution_mode, stage, constitution_hash, str(target), str(int(max_alerts))]
696
+ [
697
+ account_id,
698
+ execution_mode,
699
+ stage,
700
+ constitution_hash,
701
+ str(target),
702
+ str(int(max_alerts)),
703
+ scope_digest,
704
+ ]
671
705
  )
672
706
  return hashlib.sha256(seed.encode("utf-8")).hexdigest()
673
707
 
@@ -707,6 +741,10 @@ def _cloud_inference(
707
741
 
708
742
  execution_mode = _cloud_execution_mode(constitution) or "cloud_managed"
709
743
  auth_ctx = ensure_authenticated(project_root)
744
+ # Digest the exact documents so the idempotency key keys on the reviewed
745
+ # code, not just account/stage/target. (The backend independently recomputes
746
+ # a documents hash for its own replay guard, so this need not be sent.)
747
+ documents_hash = _documents_digest(documents)
710
748
  payload = {
711
749
  "stage": stage,
712
750
  "execution_mode": execution_mode,
@@ -728,6 +766,7 @@ def _cloud_inference(
728
766
  target=target,
729
767
  max_alerts=max_alerts,
730
768
  execution_mode=execution_mode,
769
+ scope_digest=documents_hash,
731
770
  )
732
771
  try:
733
772
  response = httpx.post(
@@ -2955,7 +2994,19 @@ def code(
2955
2994
  changed=changed,
2956
2995
  intent=intent,
2957
2996
  )
2958
- _confirm_full_project_scope(code_scope, command="code", assume_yes=yes_full_project)
2997
+ # Decide reuse-vs-generate up front. The full-project confirmation only guards
2998
+ # *creating* a large request set; when we are resolving an existing pending
2999
+ # queue (the common `[r]` resume case) there is nothing to confirm, so a root
3000
+ # commit no longer re-prompts every run (docs/42 `R29`, Part L).
3001
+ reused_requests = [] if (code_scope and refresh_requests) else pending_requests(project_root)
3002
+ will_generate_requests = bool(refresh_requests) or not reused_requests
3003
+ if will_generate_requests:
3004
+ _confirm_full_project_scope(code_scope, command="code", assume_yes=yes_full_project)
3005
+ elif code_scope and code_scope.get("full_project"):
3006
+ console.print(
3007
+ f"[dim]code: resuming {len(reused_requests)} pending request(s) for this "
3008
+ "commit (full-project review already ran).[/dim]"
3009
+ )
2959
3010
  code_scope_paths = code_scope.get("scope_paths") if code_scope else None
2960
3011
  code_changed_paths = code_scope.get("changed_paths") if code_scope else None
2961
3012
  if code_scope:
@@ -3043,11 +3094,9 @@ def code(
3043
3094
  # Reuse the existing pending requests by default (docs/42 `R29`, Phase 1):
3044
3095
  # `[r]`/`code --commit` resolves the queue already created for this commit
3045
3096
  # rather than regenerating it. `--refresh-requests` opts back into a fresh
3046
- # scoped review so the request set is rebuilt from scratch.
3047
- if code_scope and refresh_requests:
3048
- requests = []
3049
- else:
3050
- requests = pending_requests(project_root)
3097
+ # scoped review so the request set is rebuilt from scratch. Computed above
3098
+ # (``reused_requests``) so the full-project prompt could be skipped on reuse.
3099
+ requests = reused_requests
3051
3100
  if not requests:
3052
3101
  if code_scope and refresh_requests:
3053
3102
  console.print(
@@ -3169,6 +3218,7 @@ def code(
3169
3218
  apply_byok_payload=_apply_byok_payload,
3170
3219
  toolchain_consent_cb=_toolchain_consent_prompt(auto_approve=auto_approve),
3171
3220
  commit_cb=rr_commit_cb,
3221
+ on_rr_failure=on_rr_failure,
3172
3222
  )
3173
3223
  else:
3174
3224
  # Local mode is unchanged: dispatch to the local executor with the
@@ -415,6 +415,7 @@ def build_action_command(
415
415
  executable: str = "refactor",
416
416
  branch: str | None = None,
417
417
  rr_id: str | None = None,
418
+ yes_full_project: bool = False,
418
419
  ) -> list[str] | None:
419
420
  """Build the argv for a terminal action. Pure + testable (docs/42, docs/43).
420
421
 
@@ -430,10 +431,18 @@ def build_action_command(
430
431
  """
431
432
  if action == "code":
432
433
  # Commit-scoped so `[r]` resolves THIS commit's requests (docs/41 Phase 4).
433
- return [executable, "code", "--commit", sha, "--keep"]
434
+ cmd = [executable, "code", "--commit", sha, "--keep"]
435
+ if yes_full_project:
436
+ # Root/full-scope commits would otherwise pop a hidden confirmation in
437
+ # the suspended terminal; auto-confirm so `[r]` never appears to hang.
438
+ cmd.append("--yes-full-project")
439
+ return cmd
434
440
  if action == "refresh":
435
441
  # `[f]` regenerates the requests for this commit before resolving.
436
- return [executable, "code", "--commit", sha, "--refresh-requests", "--keep"]
442
+ cmd = [executable, "code", "--commit", sha, "--refresh-requests", "--keep"]
443
+ if yes_full_project:
444
+ cmd.append("--yes-full-project")
445
+ return cmd
437
446
  if action == "diff":
438
447
  # Shows the pending refactor-request patch diffs (not the git diff).
439
448
  return [executable, "diff"]
@@ -506,7 +515,12 @@ def _run_action(
506
515
  ) -> None:
507
516
  """Suspend the live graph, run an interactive action, then refresh the node."""
508
517
  branch = node.finalize.refactor_branch if getattr(node, "finalize", None) else None
509
- cmd = build_action_command(action, node.sha, branch=branch, rr_id=rr_id)
518
+ # Root/full-scope commits expand `code` to a full-project review; auto-confirm
519
+ # so `[r]`/`[f]` never stall on a hidden prompt in the suspended terminal.
520
+ yes_full_project = getattr(node, "scope_mode", "") == "full"
521
+ cmd = build_action_command(
522
+ action, node.sha, branch=branch, rr_id=rr_id, yes_full_project=yes_full_project
523
+ )
510
524
  if not cmd:
511
525
  return
512
526
  live.stop()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: refactorai-cli
3
- Version: 0.4.11
3
+ Version: 0.4.14
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.6
11
+ Requires-Dist: refactorai-core>=3.2.8
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.6
5
+ refactorai-core>=3.2.8