refactorai-cli 0.7.19__tar.gz → 0.7.22__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.19 → refactorai_cli-0.7.22}/PKG-INFO +1 -1
- {refactorai_cli-0.7.19 → refactorai_cli-0.7.22}/pyproject.toml +1 -1
- {refactorai_cli-0.7.19 → refactorai_cli-0.7.22}/refactorai_cli/__init__.py +1 -1
- {refactorai_cli-0.7.19 → refactorai_cli-0.7.22}/refactorai_cli/commands/run_cmds.py +9 -21
- {refactorai_cli-0.7.19 → refactorai_cli-0.7.22}/refactorai_cli/commands/watch_cmds.py +70 -5
- {refactorai_cli-0.7.19 → refactorai_cli-0.7.22}/refactorai_cli/review_runner.py +62 -10
- {refactorai_cli-0.7.19 → refactorai_cli-0.7.22}/refactorai_cli/watch_state.py +57 -5
- {refactorai_cli-0.7.19 → refactorai_cli-0.7.22}/refactorai_cli.egg-info/PKG-INFO +1 -1
- {refactorai_cli-0.7.19 → refactorai_cli-0.7.22}/README.md +0 -0
- {refactorai_cli-0.7.19 → refactorai_cli-0.7.22}/refactorai_cli/auth.py +0 -0
- {refactorai_cli-0.7.19 → refactorai_cli-0.7.22}/refactorai_cli/client.py +0 -0
- {refactorai_cli-0.7.19 → refactorai_cli-0.7.22}/refactorai_cli/cloud_rr.py +0 -0
- {refactorai_cli-0.7.19 → refactorai_cli-0.7.22}/refactorai_cli/commands/__init__.py +0 -0
- {refactorai_cli-0.7.19 → refactorai_cli-0.7.22}/refactorai_cli/commands/account_cmds.py +0 -0
- {refactorai_cli-0.7.19 → refactorai_cli-0.7.22}/refactorai_cli/commands/auth_cmds.py +0 -0
- {refactorai_cli-0.7.19 → refactorai_cli-0.7.22}/refactorai_cli/commands/branch_cmds.py +0 -0
- {refactorai_cli-0.7.19 → refactorai_cli-0.7.22}/refactorai_cli/commands/cloud_cmds.py +0 -0
- {refactorai_cli-0.7.19 → refactorai_cli-0.7.22}/refactorai_cli/commands/engine_cmds.py +0 -0
- {refactorai_cli-0.7.19 → refactorai_cli-0.7.22}/refactorai_cli/commands/hook_cmds.py +0 -0
- {refactorai_cli-0.7.19 → refactorai_cli-0.7.22}/refactorai_cli/commands/model_cmds.py +0 -0
- {refactorai_cli-0.7.19 → refactorai_cli-0.7.22}/refactorai_cli/commands/policy_cmds.py +0 -0
- {refactorai_cli-0.7.19 → refactorai_cli-0.7.22}/refactorai_cli/commands/pre_push_cmds.py +0 -0
- {refactorai_cli-0.7.19 → refactorai_cli-0.7.22}/refactorai_cli/commands/request_cmds.py +0 -0
- {refactorai_cli-0.7.19 → refactorai_cli-0.7.22}/refactorai_cli/commands/rules_cmds.py +0 -0
- {refactorai_cli-0.7.19 → refactorai_cli-0.7.22}/refactorai_cli/commands/runtime_cmds.py +0 -0
- {refactorai_cli-0.7.19 → refactorai_cli-0.7.22}/refactorai_cli/commands/runtime_proxy_cmds.py +0 -0
- {refactorai_cli-0.7.19 → refactorai_cli-0.7.22}/refactorai_cli/commands/setup_cmds.py +0 -0
- {refactorai_cli-0.7.19 → refactorai_cli-0.7.22}/refactorai_cli/commands/toolchains_cmds.py +0 -0
- {refactorai_cli-0.7.19 → refactorai_cli-0.7.22}/refactorai_cli/commands/workspace_cmds.py +0 -0
- {refactorai_cli-0.7.19 → refactorai_cli-0.7.22}/refactorai_cli/commit_queue.py +0 -0
- {refactorai_cli-0.7.19 → refactorai_cli-0.7.22}/refactorai_cli/commit_telemetry.py +0 -0
- {refactorai_cli-0.7.19 → refactorai_cli-0.7.22}/refactorai_cli/control_plane.py +0 -0
- {refactorai_cli-0.7.19 → refactorai_cli-0.7.22}/refactorai_cli/credentials.py +0 -0
- {refactorai_cli-0.7.19 → refactorai_cli-0.7.22}/refactorai_cli/dotenv_loader.py +0 -0
- {refactorai_cli-0.7.19 → refactorai_cli-0.7.22}/refactorai_cli/git_scope.py +0 -0
- {refactorai_cli-0.7.19 → refactorai_cli-0.7.22}/refactorai_cli/local_constitution.py +0 -0
- {refactorai_cli-0.7.19 → refactorai_cli-0.7.22}/refactorai_cli/local_engine_runtime.py +0 -0
- {refactorai_cli-0.7.19 → refactorai_cli-0.7.22}/refactorai_cli/local_paths.py +0 -0
- {refactorai_cli-0.7.19 → refactorai_cli-0.7.22}/refactorai_cli/main.py +0 -0
- {refactorai_cli-0.7.19 → refactorai_cli-0.7.22}/refactorai_cli/mode_sync.py +0 -0
- {refactorai_cli-0.7.19 → refactorai_cli-0.7.22}/refactorai_cli/model_policy.py +0 -0
- {refactorai_cli-0.7.19 → refactorai_cli-0.7.22}/refactorai_cli/pre_push_gate.py +0 -0
- {refactorai_cli-0.7.19 → refactorai_cli-0.7.22}/refactorai_cli/refactor_branch_store.py +0 -0
- {refactorai_cli-0.7.19 → refactorai_cli-0.7.22}/refactorai_cli/runtime_manager.py +0 -0
- {refactorai_cli-0.7.19 → refactorai_cli-0.7.22}/refactorai_cli/settings.py +0 -0
- {refactorai_cli-0.7.19 → refactorai_cli-0.7.22}/refactorai_cli/setup_flow.py +0 -0
- {refactorai_cli-0.7.19 → refactorai_cli-0.7.22}/refactorai_cli/watch_ledger.py +0 -0
- {refactorai_cli-0.7.19 → refactorai_cli-0.7.22}/refactorai_cli/watch_supervisor.py +0 -0
- {refactorai_cli-0.7.19 → refactorai_cli-0.7.22}/refactorai_cli.egg-info/SOURCES.txt +0 -0
- {refactorai_cli-0.7.19 → refactorai_cli-0.7.22}/refactorai_cli.egg-info/dependency_links.txt +0 -0
- {refactorai_cli-0.7.19 → refactorai_cli-0.7.22}/refactorai_cli.egg-info/entry_points.txt +0 -0
- {refactorai_cli-0.7.19 → refactorai_cli-0.7.22}/refactorai_cli.egg-info/requires.txt +0 -0
- {refactorai_cli-0.7.19 → refactorai_cli-0.7.22}/refactorai_cli.egg-info/top_level.txt +0 -0
- {refactorai_cli-0.7.19 → refactorai_cli-0.7.22}/setup.cfg +0 -0
|
@@ -3329,28 +3329,16 @@ def review(
|
|
|
3329
3329
|
sandbox_ctx = None
|
|
3330
3330
|
|
|
3331
3331
|
try:
|
|
3332
|
-
#
|
|
3333
|
-
#
|
|
3334
|
-
#
|
|
3335
|
-
# verification
|
|
3332
|
+
# Engine routing keys off engine location (docs/46). Review is *read-only*
|
|
3333
|
+
# analysis: it produces findings / refactor requests and never executes
|
|
3334
|
+
# verification (tests) locally. The container sandbox exists for that
|
|
3335
|
+
# verification during ``code`` / ``refactor``, so provisioning + preflighting
|
|
3336
|
+
# it here only added container start + ``exec`` probe latency (and could hang
|
|
3337
|
+
# on the container runtime) for zero effect on the review result — the old
|
|
3338
|
+
# ``sandbox_ctx`` was consumed solely to print a preflight warning. Sandbox
|
|
3339
|
+
# readiness is therefore enforced lazily, when code is actually applied; use
|
|
3340
|
+
# ``refactor sandbox start`` to validate the toolchain ahead of time.
|
|
3336
3341
|
engine_remote = _engine_is_remote(constitution)
|
|
3337
|
-
verify_local = _verify_local(constitution)
|
|
3338
|
-
if _sandbox_enabled(project_config):
|
|
3339
|
-
if verify_local:
|
|
3340
|
-
sandbox_ctx = _ensure_sandbox_ready(
|
|
3341
|
-
project_root=project_root,
|
|
3342
|
-
project_config=project_config,
|
|
3343
|
-
stage="review",
|
|
3344
|
-
auto_approve_install=False,
|
|
3345
|
-
no_install=False,
|
|
3346
|
-
continue_on_preflight_failure=True,
|
|
3347
|
-
)
|
|
3348
|
-
else:
|
|
3349
|
-
variant = _execution_variant(constitution)
|
|
3350
|
-
console.print(
|
|
3351
|
-
f"[dim]review: sandbox.enabled is ignored in "
|
|
3352
|
-
f"{variant} variant (verification runs in the cloud).[/dim]"
|
|
3353
|
-
)
|
|
3354
3342
|
if engine_remote:
|
|
3355
3343
|
artifact = _cloud_inference(
|
|
3356
3344
|
stage="review",
|
|
@@ -14,8 +14,10 @@ headless). The heavy lifting (state machine, rendering, review parsing) lives in
|
|
|
14
14
|
from __future__ import annotations
|
|
15
15
|
|
|
16
16
|
import contextlib
|
|
17
|
+
import inspect
|
|
17
18
|
import json
|
|
18
19
|
import os
|
|
20
|
+
import re
|
|
19
21
|
import select
|
|
20
22
|
import subprocess
|
|
21
23
|
import sys
|
|
@@ -461,6 +463,53 @@ def _report_refactor_requests(project_root: Path) -> None:
|
|
|
461
463
|
pass
|
|
462
464
|
|
|
463
465
|
|
|
466
|
+
_ANSI_RE = re.compile(r"\x1b\[[0-9;]*m")
|
|
467
|
+
|
|
468
|
+
# Map the review's stable stdout milestones to short, live sub-step labels shown
|
|
469
|
+
# on the reviewing node. Ordered: first match wins. Kept tolerant (substring /
|
|
470
|
+
# simple regex) so minor wording changes degrade to "no update" rather than
|
|
471
|
+
# crashing. Each entry is (compiled pattern, formatter(match) -> label).
|
|
472
|
+
_PROGRESS_PATTERNS: list[tuple[re.Pattern[str], Callable[[re.Match[str]], str]]] = [
|
|
473
|
+
(re.compile(r"batch\s+(\d+)\s*/\s*(\d+)", re.I), lambda m: f"reviewing batch {m.group(1)}/{m.group(2)}"),
|
|
474
|
+
(re.compile(r"split into\s+(\d+)\s+batch", re.I), lambda m: f"planning {m.group(1)} batch(es)"),
|
|
475
|
+
(re.compile(r"sandbox ready", re.I), lambda m: "sandbox ready"),
|
|
476
|
+
(re.compile(r"ensuring sandbox|sandbox:", re.I), lambda m: "preparing sandbox"),
|
|
477
|
+
(re.compile(r"review scope", re.I), lambda m: "resolving scope"),
|
|
478
|
+
(re.compile(r"security baseline", re.I), lambda m: "security scan"),
|
|
479
|
+
(re.compile(r"Created\s+\d+\s+refactor request", re.I), lambda m: "creating requests"),
|
|
480
|
+
(re.compile(r"\bgate:\s*", re.I), lambda m: "finalizing gate"),
|
|
481
|
+
(re.compile(r"\bindex", re.I), lambda m: "indexing files"),
|
|
482
|
+
]
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
def _summarize_progress(line: str) -> str:
|
|
486
|
+
"""Reduce one raw review stdout line to a short sub-step label, or ``""``.
|
|
487
|
+
|
|
488
|
+
Returns ``""`` for lines that carry no useful milestone so the caller can
|
|
489
|
+
skip a redundant repaint.
|
|
490
|
+
"""
|
|
491
|
+
text = _ANSI_RE.sub("", line or "").strip()
|
|
492
|
+
if not text:
|
|
493
|
+
return ""
|
|
494
|
+
for pattern, fmt in _PROGRESS_PATTERNS:
|
|
495
|
+
match = pattern.search(text)
|
|
496
|
+
if match:
|
|
497
|
+
return fmt(match)
|
|
498
|
+
return ""
|
|
499
|
+
|
|
500
|
+
|
|
501
|
+
def _runner_supports_on_progress(runner: ReviewRunner) -> bool:
|
|
502
|
+
"""True when ``runner.run`` accepts an ``on_progress`` keyword.
|
|
503
|
+
|
|
504
|
+
Lets streaming be opt-in per runner so simple/test runners with the older
|
|
505
|
+
signature keep working unchanged.
|
|
506
|
+
"""
|
|
507
|
+
try:
|
|
508
|
+
return "on_progress" in inspect.signature(runner.run).parameters
|
|
509
|
+
except (TypeError, ValueError):
|
|
510
|
+
return False
|
|
511
|
+
|
|
512
|
+
|
|
464
513
|
def process_pending(
|
|
465
514
|
project_root: Path,
|
|
466
515
|
state: WatchState,
|
|
@@ -500,11 +549,27 @@ def process_pending(
|
|
|
500
549
|
on_update(state)
|
|
501
550
|
_report_node(project_root, node)
|
|
502
551
|
intent = intent_for(node.sha) if intent_for else None
|
|
503
|
-
|
|
504
|
-
sha
|
|
505
|
-
intent
|
|
506
|
-
yes_full_project
|
|
507
|
-
|
|
552
|
+
run_kwargs: dict = {
|
|
553
|
+
"sha": node.sha,
|
|
554
|
+
"intent": intent,
|
|
555
|
+
"yes_full_project": (node.scope_mode == "full"),
|
|
556
|
+
}
|
|
557
|
+
# Stream live sub-steps into the reviewing node when the runner supports
|
|
558
|
+
# it, so a long review shows batch-by-batch progress instead of a static
|
|
559
|
+
# "analyzing..." spinner that looks hung.
|
|
560
|
+
if _runner_supports_on_progress(runner):
|
|
561
|
+
reviewing_sha = node.sha
|
|
562
|
+
|
|
563
|
+
def _on_progress(line: str, _sha: str = reviewing_sha) -> None:
|
|
564
|
+
label = _summarize_progress(line)
|
|
565
|
+
if not label:
|
|
566
|
+
return
|
|
567
|
+
state.set_substep(_sha, label)
|
|
568
|
+
if on_update:
|
|
569
|
+
on_update(state)
|
|
570
|
+
|
|
571
|
+
run_kwargs["on_progress"] = _on_progress
|
|
572
|
+
outcome = runner.run(**run_kwargs)
|
|
508
573
|
if outcome.run_id:
|
|
509
574
|
outcome.rr_preview = _preview_for_run(project_root, outcome.run_id)
|
|
510
575
|
state.apply_outcome(node.sha, outcome)
|
|
@@ -14,12 +14,15 @@ machine without spawning a subprocess or needing the engine.
|
|
|
14
14
|
|
|
15
15
|
from __future__ import annotations
|
|
16
16
|
|
|
17
|
+
import contextlib
|
|
17
18
|
import os
|
|
18
19
|
import re
|
|
20
|
+
import signal
|
|
19
21
|
import subprocess
|
|
22
|
+
import threading
|
|
20
23
|
from dataclasses import dataclass, field
|
|
21
24
|
from pathlib import Path
|
|
22
|
-
from typing import Protocol
|
|
25
|
+
from typing import Callable, Protocol
|
|
23
26
|
|
|
24
27
|
_ANSI = re.compile(r"\x1b\[[0-9;]*m")
|
|
25
28
|
_SCOPE_COUNTS = re.compile(r"(\d+)\s+changed\s*\+\s*(\d+)\s+associated")
|
|
@@ -58,6 +61,7 @@ class ReviewRunner(Protocol):
|
|
|
58
61
|
sha: str,
|
|
59
62
|
intent: str | None = None,
|
|
60
63
|
yes_full_project: bool = False,
|
|
64
|
+
on_progress: Callable[[str], None] | None = None,
|
|
61
65
|
) -> ReviewOutcome: ...
|
|
62
66
|
|
|
63
67
|
|
|
@@ -123,6 +127,7 @@ class SubprocessReviewRunner:
|
|
|
123
127
|
sha: str,
|
|
124
128
|
intent: str | None = None,
|
|
125
129
|
yes_full_project: bool = False,
|
|
130
|
+
on_progress: Callable[[str], None] | None = None,
|
|
126
131
|
) -> ReviewOutcome:
|
|
127
132
|
cmd = [self.executable, "review", "--commit", sha, *self.extra_args]
|
|
128
133
|
if intent and intent.strip():
|
|
@@ -132,23 +137,70 @@ class SubprocessReviewRunner:
|
|
|
132
137
|
# non-interactive confirmation so the loop never blocks on a hidden prompt.
|
|
133
138
|
cmd.append("--yes-full-project")
|
|
134
139
|
# Wide, non-interactive output so rich does not wrap the status lines the
|
|
135
|
-
# parser depends on.
|
|
136
|
-
|
|
140
|
+
# parser depends on. ``PYTHONUNBUFFERED`` forces line-buffered child output
|
|
141
|
+
# so progress lines reach us (and the terminal UI) live instead of only at
|
|
142
|
+
# process exit -- the difference between a spinner that looks hung and one
|
|
143
|
+
# that shows real sub-steps.
|
|
144
|
+
env = dict(os.environ, COLUMNS="200", NO_COLOR="1", PYTHONUNBUFFERED="1")
|
|
137
145
|
try:
|
|
138
|
-
proc = subprocess.
|
|
146
|
+
proc = subprocess.Popen( # noqa: S603 - fixed argv, no shell
|
|
139
147
|
cmd,
|
|
140
148
|
cwd=str(self.project_root),
|
|
141
|
-
|
|
149
|
+
stdout=subprocess.PIPE,
|
|
150
|
+
stderr=subprocess.STDOUT,
|
|
142
151
|
text=True,
|
|
143
|
-
|
|
152
|
+
bufsize=1,
|
|
144
153
|
env=env,
|
|
154
|
+
# New session/process group so the timeout can kill the whole tree
|
|
155
|
+
# (review spawns grandchildren, e.g. the sandbox runtime). Killing
|
|
156
|
+
# only the direct child would leave a grandchild holding the stdout
|
|
157
|
+
# pipe open, so the streaming read loop would block until it exits
|
|
158
|
+
# and the timeout would not take effect promptly.
|
|
159
|
+
start_new_session=True,
|
|
145
160
|
)
|
|
146
161
|
except FileNotFoundError:
|
|
147
162
|
return ReviewOutcome(sha=sha, error=f"'{self.executable}' not found on PATH")
|
|
148
|
-
except subprocess.TimeoutExpired:
|
|
149
|
-
return ReviewOutcome(sha=sha, error=f"review timed out after {int(self.timeout)}s")
|
|
150
163
|
except Exception as exc: # never let a review failure crash the terminal
|
|
151
164
|
return ReviewOutcome(sha=sha, error=f"review invocation failed: {exc}")
|
|
152
165
|
|
|
153
|
-
|
|
154
|
-
|
|
166
|
+
def _kill_tree() -> None:
|
|
167
|
+
"""Kill the child and all of its descendants (best-effort)."""
|
|
168
|
+
with contextlib.suppress(Exception):
|
|
169
|
+
os.killpg(os.getpgid(proc.pid), signal.SIGKILL)
|
|
170
|
+
with contextlib.suppress(Exception):
|
|
171
|
+
proc.kill()
|
|
172
|
+
|
|
173
|
+
# Enforce the overall timeout even while streaming: a watchdog kills the
|
|
174
|
+
# process group at the deadline (the streaming read loop then unblocks
|
|
175
|
+
# because every pipe write-end is closed).
|
|
176
|
+
done = threading.Event()
|
|
177
|
+
timed_out = threading.Event()
|
|
178
|
+
|
|
179
|
+
def _watchdog() -> None:
|
|
180
|
+
if not done.wait(self.timeout):
|
|
181
|
+
timed_out.set()
|
|
182
|
+
_kill_tree()
|
|
183
|
+
|
|
184
|
+
watchdog = threading.Thread(target=_watchdog, name="review-timeout", daemon=True)
|
|
185
|
+
watchdog.start()
|
|
186
|
+
|
|
187
|
+
lines: list[str] = []
|
|
188
|
+
try:
|
|
189
|
+
if proc.stdout is not None:
|
|
190
|
+
for raw in proc.stdout:
|
|
191
|
+
lines.append(raw)
|
|
192
|
+
if on_progress is not None:
|
|
193
|
+
# A progress-callback error must never abort the review.
|
|
194
|
+
with contextlib.suppress(Exception):
|
|
195
|
+
on_progress(raw.rstrip("\n"))
|
|
196
|
+
proc.wait()
|
|
197
|
+
except Exception as exc: # never let a streaming/read error crash the terminal
|
|
198
|
+
done.set()
|
|
199
|
+
_kill_tree()
|
|
200
|
+
return ReviewOutcome(sha=sha, error=f"review invocation failed: {exc}")
|
|
201
|
+
finally:
|
|
202
|
+
done.set()
|
|
203
|
+
|
|
204
|
+
if timed_out.is_set():
|
|
205
|
+
return ReviewOutcome(sha=sha, error=f"review timed out after {int(self.timeout)}s")
|
|
206
|
+
return parse_review_output(sha, "".join(lines), proc.returncode or 0)
|
|
@@ -43,6 +43,10 @@ PHASE_FINALIZE = "finalize"
|
|
|
43
43
|
PHASE_DONE = "done"
|
|
44
44
|
PHASE_NONE = "none"
|
|
45
45
|
|
|
46
|
+
# Cap the live sub-step trail so a long review never overflows the node block.
|
|
47
|
+
# Older steps scroll off the top (the checklist keeps the most recent progress).
|
|
48
|
+
_MAX_SUBSTEPS = 6
|
|
49
|
+
|
|
46
50
|
|
|
47
51
|
@dataclass
|
|
48
52
|
class RRCommit:
|
|
@@ -121,6 +125,13 @@ class CommitNode:
|
|
|
121
125
|
intent_summary: str = ""
|
|
122
126
|
run_id: str = ""
|
|
123
127
|
error: str = ""
|
|
128
|
+
# Live sub-step trail while REVIEWING, oldest first (e.g. "resolving scope",
|
|
129
|
+
# "reviewing batch 2/5", "security scan"). Rendered as a Cursor-style
|
|
130
|
+
# checklist: completed steps ticked, the last one spinning. Volatile UI-only
|
|
131
|
+
# detail; not part of the persisted node contract. ``substep`` mirrors the
|
|
132
|
+
# current (last) step for the compact single-line status fallback.
|
|
133
|
+
substep: str = ""
|
|
134
|
+
substeps: list[str] = field(default_factory=list)
|
|
124
135
|
finalize: "FinalizeInfo | None" = None
|
|
125
136
|
|
|
126
137
|
@property
|
|
@@ -271,6 +282,25 @@ class WatchState:
|
|
|
271
282
|
node = self._nodes.get(sha)
|
|
272
283
|
if node:
|
|
273
284
|
node.state = REVIEWING
|
|
285
|
+
node.substep = ""
|
|
286
|
+
node.substeps = []
|
|
287
|
+
|
|
288
|
+
def set_substep(self, sha: str, label: str) -> None:
|
|
289
|
+
"""Append a new live sub-step to a reviewing node's trail (UI-only).
|
|
290
|
+
|
|
291
|
+
Consecutive duplicates are collapsed and the trail is capped at
|
|
292
|
+
:data:`_MAX_SUBSTEPS` (oldest scroll off). No-op unless the node is
|
|
293
|
+
currently ``REVIEWING`` so a stale progress line can never mutate a
|
|
294
|
+
finished node.
|
|
295
|
+
"""
|
|
296
|
+
node = self._nodes.get(sha)
|
|
297
|
+
if not (node and node.state == REVIEWING):
|
|
298
|
+
return
|
|
299
|
+
if not node.substeps or node.substeps[-1] != label:
|
|
300
|
+
node.substeps.append(label)
|
|
301
|
+
if len(node.substeps) > _MAX_SUBSTEPS:
|
|
302
|
+
node.substeps = node.substeps[-_MAX_SUBSTEPS:]
|
|
303
|
+
node.substep = label
|
|
274
304
|
|
|
275
305
|
def requeue(self, sha: str) -> None:
|
|
276
306
|
"""Reset a node to ``queued`` so the next pass re-reviews it (post-resolve)."""
|
|
@@ -282,6 +312,8 @@ class WatchState:
|
|
|
282
312
|
node.rr_count = 0
|
|
283
313
|
node.rr_preview = []
|
|
284
314
|
node.error = ""
|
|
315
|
+
node.substep = ""
|
|
316
|
+
node.substeps = []
|
|
285
317
|
node.finalize = None
|
|
286
318
|
self.forced_review.discard(sha)
|
|
287
319
|
|
|
@@ -443,7 +475,8 @@ def _status_line(node: CommitNode) -> str:
|
|
|
443
475
|
if node.state == REVIEWING:
|
|
444
476
|
scope = f" ({node.changed} files)" if node.changed else ""
|
|
445
477
|
spinner = _spinner_frame()
|
|
446
|
-
|
|
478
|
+
detail = node.substep or "analyzing changed+associated"
|
|
479
|
+
return f"[cyan]{spinner}[/cyan] [dim]{detail}{scope}...[/dim]"
|
|
447
480
|
if node.state == ERROR:
|
|
448
481
|
return f"[red]review error:[/red] {node.error or 'unknown'}"
|
|
449
482
|
# reviewed
|
|
@@ -538,10 +571,29 @@ def _render_review_view(state: WatchState, *, now: datetime | None = None, width
|
|
|
538
571
|
row2 = Text.from_markup(f"[cyan]|[/cyan] [yellow]{node.short_sha}[/yellow] ")
|
|
539
572
|
row2.append(node.subject or "(no subject)")
|
|
540
573
|
node_lines.append(row2)
|
|
541
|
-
# Row 3:
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
574
|
+
# Row 3: while reviewing, render a Cursor-style checklist of sub-steps
|
|
575
|
+
# (completed steps ticked, the current one spinning) so progress reads as
|
|
576
|
+
# advancing work instead of a single overwritten line. Falls back to the
|
|
577
|
+
# compact status line before the first step arrives / for other states.
|
|
578
|
+
if node.state == REVIEWING and node.substeps:
|
|
579
|
+
spinner = _spinner_frame(now)
|
|
580
|
+
last = len(node.substeps) - 1
|
|
581
|
+
for idx, label in enumerate(node.substeps):
|
|
582
|
+
row = Text.from_markup("[cyan]|[/cyan] ")
|
|
583
|
+
if idx == last:
|
|
584
|
+
row.append_text(Text.from_markup(f"[cyan]{spinner}[/cyan] "))
|
|
585
|
+
row.append(label)
|
|
586
|
+
if node.changed:
|
|
587
|
+
row.append(f" ({node.changed} files)", style="dim")
|
|
588
|
+
row.append(" …", style="dim")
|
|
589
|
+
else:
|
|
590
|
+
row.append_text(Text.from_markup("[green]✓[/green] "))
|
|
591
|
+
row.append(label, style="dim")
|
|
592
|
+
node_lines.append(row)
|
|
593
|
+
else:
|
|
594
|
+
row3 = Text.from_markup("[cyan]|[/cyan] ")
|
|
595
|
+
row3.append_text(Text.from_markup(_status_line(node)))
|
|
596
|
+
node_lines.append(row3)
|
|
545
597
|
if node.state == REVIEWED and node.rr_preview:
|
|
546
598
|
for item in node.rr_preview:
|
|
547
599
|
req = Text.from_markup("[cyan]|[/cyan] ")
|
|
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.19 → refactorai_cli-0.7.22}/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
|
|
File without changes
|
{refactorai_cli-0.7.19 → refactorai_cli-0.7.22}/refactorai_cli.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|