refactorai-cli 0.3.13__tar.gz → 0.4.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.
- {refactorai_cli-0.3.13 → refactorai_cli-0.4.1}/PKG-INFO +2 -2
- {refactorai_cli-0.3.13 → refactorai_cli-0.4.1}/pyproject.toml +2 -2
- {refactorai_cli-0.3.13 → refactorai_cli-0.4.1}/refactorai_cli/__init__.py +1 -1
- refactorai_cli-0.4.1/refactorai_cli/commands/hook_cmds.py +229 -0
- {refactorai_cli-0.3.13 → refactorai_cli-0.4.1}/refactorai_cli/commands/run_cmds.py +332 -5
- refactorai_cli-0.4.1/refactorai_cli/commands/watch_cmds.py +321 -0
- refactorai_cli-0.4.1/refactorai_cli/commit_queue.py +138 -0
- refactorai_cli-0.4.1/refactorai_cli/dotenv_loader.py +112 -0
- refactorai_cli-0.4.1/refactorai_cli/git_scope.py +215 -0
- {refactorai_cli-0.3.13 → refactorai_cli-0.4.1}/refactorai_cli/main.py +22 -0
- refactorai_cli-0.4.1/refactorai_cli/review_runner.py +137 -0
- refactorai_cli-0.4.1/refactorai_cli/watch_state.py +318 -0
- {refactorai_cli-0.3.13 → refactorai_cli-0.4.1}/refactorai_cli.egg-info/PKG-INFO +2 -2
- {refactorai_cli-0.3.13 → refactorai_cli-0.4.1}/refactorai_cli.egg-info/SOURCES.txt +8 -1
- {refactorai_cli-0.3.13 → refactorai_cli-0.4.1}/refactorai_cli.egg-info/requires.txt +1 -1
- {refactorai_cli-0.3.13 → refactorai_cli-0.4.1}/README.md +0 -0
- {refactorai_cli-0.3.13 → refactorai_cli-0.4.1}/refactorai_cli/auth.py +0 -0
- {refactorai_cli-0.3.13 → refactorai_cli-0.4.1}/refactorai_cli/client.py +0 -0
- {refactorai_cli-0.3.13 → refactorai_cli-0.4.1}/refactorai_cli/cloud_rr.py +0 -0
- {refactorai_cli-0.3.13 → refactorai_cli-0.4.1}/refactorai_cli/commands/__init__.py +0 -0
- {refactorai_cli-0.3.13 → refactorai_cli-0.4.1}/refactorai_cli/commands/account_cmds.py +0 -0
- {refactorai_cli-0.3.13 → refactorai_cli-0.4.1}/refactorai_cli/commands/auth_cmds.py +0 -0
- {refactorai_cli-0.3.13 → refactorai_cli-0.4.1}/refactorai_cli/commands/cloud_cmds.py +0 -0
- {refactorai_cli-0.3.13 → refactorai_cli-0.4.1}/refactorai_cli/commands/engine_cmds.py +0 -0
- {refactorai_cli-0.3.13 → refactorai_cli-0.4.1}/refactorai_cli/commands/model_cmds.py +0 -0
- {refactorai_cli-0.3.13 → refactorai_cli-0.4.1}/refactorai_cli/commands/rules_cmds.py +0 -0
- {refactorai_cli-0.3.13 → refactorai_cli-0.4.1}/refactorai_cli/commands/runtime_cmds.py +0 -0
- {refactorai_cli-0.3.13 → refactorai_cli-0.4.1}/refactorai_cli/commands/runtime_proxy_cmds.py +0 -0
- {refactorai_cli-0.3.13 → refactorai_cli-0.4.1}/refactorai_cli/commands/setup_cmds.py +0 -0
- {refactorai_cli-0.3.13 → refactorai_cli-0.4.1}/refactorai_cli/commands/toolchains_cmds.py +0 -0
- {refactorai_cli-0.3.13 → refactorai_cli-0.4.1}/refactorai_cli/control_plane.py +0 -0
- {refactorai_cli-0.3.13 → refactorai_cli-0.4.1}/refactorai_cli/credentials.py +0 -0
- {refactorai_cli-0.3.13 → refactorai_cli-0.4.1}/refactorai_cli/local_constitution.py +0 -0
- {refactorai_cli-0.3.13 → refactorai_cli-0.4.1}/refactorai_cli/local_engine_runtime.py +0 -0
- {refactorai_cli-0.3.13 → refactorai_cli-0.4.1}/refactorai_cli/local_paths.py +0 -0
- {refactorai_cli-0.3.13 → refactorai_cli-0.4.1}/refactorai_cli/model_policy.py +0 -0
- {refactorai_cli-0.3.13 → refactorai_cli-0.4.1}/refactorai_cli/runtime_manager.py +0 -0
- {refactorai_cli-0.3.13 → refactorai_cli-0.4.1}/refactorai_cli/settings.py +0 -0
- {refactorai_cli-0.3.13 → refactorai_cli-0.4.1}/refactorai_cli/setup_flow.py +0 -0
- {refactorai_cli-0.3.13 → refactorai_cli-0.4.1}/refactorai_cli.egg-info/dependency_links.txt +0 -0
- {refactorai_cli-0.3.13 → refactorai_cli-0.4.1}/refactorai_cli.egg-info/entry_points.txt +0 -0
- {refactorai_cli-0.3.13 → refactorai_cli-0.4.1}/refactorai_cli.egg-info/top_level.txt +0 -0
- {refactorai_cli-0.3.13 → refactorai_cli-0.4.1}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: refactorai-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.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,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.
|
|
11
|
+
Requires-Dist: refactorai-core>=3.2.0
|
|
12
12
|
|
|
13
13
|
# refactorai-cli
|
|
14
14
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "refactorai-cli"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.4.1"
|
|
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.
|
|
15
|
+
"refactorai-core>=3.2.0",
|
|
16
16
|
]
|
|
17
17
|
|
|
18
18
|
[project.scripts]
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
"""`refactor hook` commands: local commit-trigger install + enqueue (docs/41 `R28`).
|
|
2
|
+
|
|
3
|
+
Phase 2 of the commit-triggered Refactor Terminal. ``install`` wires a
|
|
4
|
+
non-blocking ``post-commit`` git hook (via ``core.hooksPath``) that enqueues each
|
|
5
|
+
commit; ``enqueue`` is what that hook calls; ``status``/``uninstall`` manage it.
|
|
6
|
+
|
|
7
|
+
The hook and these commands live in the thin CLI (not the proprietary runtime)
|
|
8
|
+
so enqueuing is fast, offline, and never requires auth or a project config.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import os
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
import typer
|
|
17
|
+
from rich.console import Console
|
|
18
|
+
|
|
19
|
+
from refactorai_cli import commit_queue, git_scope
|
|
20
|
+
|
|
21
|
+
console = Console()
|
|
22
|
+
app = typer.Typer(
|
|
23
|
+
name="hook",
|
|
24
|
+
help="Manage the local commit-triggered review hook (docs/41).",
|
|
25
|
+
no_args_is_help=True,
|
|
26
|
+
add_completion=False,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
_HOOK_MARKER = "RefactorAI post-commit hook"
|
|
30
|
+
_DEFAULT_HOOKS_DIR = ".githooks"
|
|
31
|
+
|
|
32
|
+
# Non-blocking + fail-open: capture HEAD, then detach the enqueue so `git commit`
|
|
33
|
+
# returns immediately. Skip-detection (merge/rebase/opt-out) happens in `enqueue`.
|
|
34
|
+
_POST_COMMIT_SCRIPT = """#!/usr/bin/env bash
|
|
35
|
+
# RefactorAI post-commit hook (docs/41 R28, Phase 2).
|
|
36
|
+
# Non-blocking + fail-open: this hook must never delay or fail `git commit`.
|
|
37
|
+
if ! command -v refactor >/dev/null 2>&1; then
|
|
38
|
+
exit 0
|
|
39
|
+
fi
|
|
40
|
+
_refactor_sha="$(git rev-parse HEAD 2>/dev/null)"
|
|
41
|
+
if [ -z "$_refactor_sha" ]; then
|
|
42
|
+
exit 0
|
|
43
|
+
fi
|
|
44
|
+
# Detach so the commit returns instantly; the enqueue does its own skip checks.
|
|
45
|
+
( refactor hook enqueue --sha "$_refactor_sha" >/dev/null 2>&1 & ) >/dev/null 2>&1 || true
|
|
46
|
+
exit 0
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _require_repo(project_root: Path) -> str:
|
|
51
|
+
root = git_scope.repo_root(project_root)
|
|
52
|
+
if not root:
|
|
53
|
+
console.print("[red]hook:[/red] not inside a git repository.")
|
|
54
|
+
raise typer.Exit(code=1)
|
|
55
|
+
return root
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def perform_install(
|
|
59
|
+
root: Path, *, hooks_dir: str | None = None, force: bool = False
|
|
60
|
+
) -> dict:
|
|
61
|
+
"""Install the post-commit hook + wire ``core.hooksPath`` (no printing/exit).
|
|
62
|
+
|
|
63
|
+
Returns a result dict with ``status`` in ``{installed, exists_foreign, error}``
|
|
64
|
+
plus ``rel_dir``, ``hook_file`` and a human ``message``. Shared by the
|
|
65
|
+
``hook install`` command and ``refactor init`` so both stay in lockstep.
|
|
66
|
+
"""
|
|
67
|
+
existing_path = git_scope.get_config(root, "core.hooksPath")
|
|
68
|
+
if hooks_dir:
|
|
69
|
+
rel_dir = hooks_dir
|
|
70
|
+
elif existing_path:
|
|
71
|
+
rel_dir = existing_path
|
|
72
|
+
else:
|
|
73
|
+
rel_dir = _DEFAULT_HOOKS_DIR
|
|
74
|
+
|
|
75
|
+
hooks_path = (root / rel_dir).resolve()
|
|
76
|
+
hook_file = hooks_path / "post-commit"
|
|
77
|
+
|
|
78
|
+
if hook_file.exists() and _HOOK_MARKER not in _safe_read(hook_file) and not force:
|
|
79
|
+
return {
|
|
80
|
+
"status": "exists_foreign",
|
|
81
|
+
"rel_dir": rel_dir,
|
|
82
|
+
"hook_file": hook_file,
|
|
83
|
+
"message": f"a post-commit hook already exists at {hook_file}",
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
try:
|
|
87
|
+
hooks_path.mkdir(parents=True, exist_ok=True)
|
|
88
|
+
hook_file.write_text(_POST_COMMIT_SCRIPT, encoding="utf-8")
|
|
89
|
+
hook_file.chmod(hook_file.stat().st_mode | 0o755)
|
|
90
|
+
except OSError as exc:
|
|
91
|
+
return {"status": "error", "rel_dir": rel_dir, "hook_file": None, "message": str(exc)}
|
|
92
|
+
|
|
93
|
+
if not git_scope.set_config(root, "core.hooksPath", rel_dir):
|
|
94
|
+
return {
|
|
95
|
+
"status": "error",
|
|
96
|
+
"rel_dir": rel_dir,
|
|
97
|
+
"hook_file": hook_file,
|
|
98
|
+
"message": "could not set core.hooksPath",
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
"status": "installed",
|
|
103
|
+
"rel_dir": rel_dir,
|
|
104
|
+
"hook_file": hook_file,
|
|
105
|
+
"message": f"installed post-commit hook -> {hook_file}",
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
@app.command("install")
|
|
110
|
+
def install(
|
|
111
|
+
hooks_dir: str | None = typer.Option(
|
|
112
|
+
None,
|
|
113
|
+
"--hooks-dir",
|
|
114
|
+
help=f"Directory to hold git hooks (default: existing core.hooksPath or {_DEFAULT_HOOKS_DIR}).",
|
|
115
|
+
),
|
|
116
|
+
force: bool = typer.Option(
|
|
117
|
+
False,
|
|
118
|
+
"--force",
|
|
119
|
+
help="Overwrite an existing non-RefactorAI post-commit hook.",
|
|
120
|
+
),
|
|
121
|
+
) -> None:
|
|
122
|
+
"""Install the non-blocking post-commit hook and wire ``core.hooksPath``."""
|
|
123
|
+
root = Path(_require_repo(Path.cwd()))
|
|
124
|
+
result = perform_install(root, hooks_dir=hooks_dir, force=force)
|
|
125
|
+
|
|
126
|
+
if result["status"] == "exists_foreign":
|
|
127
|
+
console.print(
|
|
128
|
+
f"[yellow]hook:[/yellow] {result['message']}.\n"
|
|
129
|
+
"Re-run with [bold]--force[/bold] to overwrite, or point --hooks-dir elsewhere."
|
|
130
|
+
)
|
|
131
|
+
raise typer.Exit(code=1)
|
|
132
|
+
if result["status"] == "error":
|
|
133
|
+
console.print(f"[red]hook: could not install:[/red] {result['message']}")
|
|
134
|
+
raise typer.Exit(code=1)
|
|
135
|
+
|
|
136
|
+
console.print(
|
|
137
|
+
f"[green]Installed[/green] post-commit hook -> {result['hook_file']}\n"
|
|
138
|
+
f"[dim]core.hooksPath = {result['rel_dir']}[/dim]\n"
|
|
139
|
+
"Every commit now enqueues a local review event. "
|
|
140
|
+
"Open the Refactor Terminal with `refactor watch`."
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
@app.command("uninstall")
|
|
145
|
+
def uninstall(
|
|
146
|
+
reset_hooks_path: bool = typer.Option(
|
|
147
|
+
False,
|
|
148
|
+
"--reset-hooks-path",
|
|
149
|
+
help="Also unset core.hooksPath (only do this if RefactorAI set it).",
|
|
150
|
+
),
|
|
151
|
+
) -> None:
|
|
152
|
+
"""Remove the RefactorAI post-commit hook."""
|
|
153
|
+
root = Path(_require_repo(Path.cwd()))
|
|
154
|
+
rel_dir = git_scope.get_config(root, "core.hooksPath") or _DEFAULT_HOOKS_DIR
|
|
155
|
+
hook_file = (root / rel_dir / "post-commit").resolve()
|
|
156
|
+
|
|
157
|
+
removed = False
|
|
158
|
+
if hook_file.exists() and _HOOK_MARKER in _safe_read(hook_file):
|
|
159
|
+
try:
|
|
160
|
+
hook_file.unlink()
|
|
161
|
+
removed = True
|
|
162
|
+
except OSError as exc:
|
|
163
|
+
console.print(f"[red]hook: could not remove hook:[/red] {exc}")
|
|
164
|
+
raise typer.Exit(code=1) from exc
|
|
165
|
+
|
|
166
|
+
if reset_hooks_path:
|
|
167
|
+
git_scope.unset_config(root, "core.hooksPath")
|
|
168
|
+
|
|
169
|
+
if removed:
|
|
170
|
+
console.print("[green]Removed[/green] the RefactorAI post-commit hook.")
|
|
171
|
+
else:
|
|
172
|
+
console.print("[dim]hook: no RefactorAI post-commit hook found to remove.[/dim]")
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
@app.command("enqueue")
|
|
176
|
+
def enqueue(
|
|
177
|
+
sha: str = typer.Option("HEAD", "--sha", help="Commit SHA/ref to enqueue."),
|
|
178
|
+
verbose: bool = typer.Option(False, "--verbose", help="Print the enqueue result."),
|
|
179
|
+
) -> None:
|
|
180
|
+
"""Enqueue a commit event (invoked by the post-commit hook). Always exits 0."""
|
|
181
|
+
project_root = Path(os.environ.get("REFACTOR_PROJECT_ROOT") or Path.cwd())
|
|
182
|
+
try:
|
|
183
|
+
enqueued, reason, event = commit_queue.enqueue_commit_event(project_root, sha)
|
|
184
|
+
except Exception as exc: # fail-open: never let enqueue affect the commit flow
|
|
185
|
+
if verbose:
|
|
186
|
+
console.print(f"[yellow]hook enqueue error (ignored):[/yellow] {exc}")
|
|
187
|
+
return
|
|
188
|
+
if verbose:
|
|
189
|
+
if enqueued and event:
|
|
190
|
+
console.print(
|
|
191
|
+
f"[green]enqueued[/green] {event['sha'][:10]} "
|
|
192
|
+
f"({len(event['changed_files'])} changed file(s)) -> "
|
|
193
|
+
f"{commit_queue.queue_path(project_root)}"
|
|
194
|
+
)
|
|
195
|
+
else:
|
|
196
|
+
console.print(f"[dim]hook: not enqueued ({reason}).[/dim]")
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
@app.command("status")
|
|
200
|
+
def status() -> None:
|
|
201
|
+
"""Show hook installation state and the pending commit-event count."""
|
|
202
|
+
root_str = git_scope.repo_root(Path.cwd())
|
|
203
|
+
if not root_str:
|
|
204
|
+
console.print("[yellow]hook:[/yellow] not inside a git repository.")
|
|
205
|
+
raise typer.Exit(code=1)
|
|
206
|
+
root = Path(root_str)
|
|
207
|
+
|
|
208
|
+
rel_dir = git_scope.get_config(root, "core.hooksPath")
|
|
209
|
+
hook_file = (root / (rel_dir or _DEFAULT_HOOKS_DIR) / "post-commit").resolve()
|
|
210
|
+
is_ours = hook_file.exists() and _HOOK_MARKER in _safe_read(hook_file)
|
|
211
|
+
q_path = commit_queue.queue_path(root)
|
|
212
|
+
|
|
213
|
+
console.print(f"Repository: [bold]{root}[/bold]")
|
|
214
|
+
console.print(f"core.hooksPath: {rel_dir or '[dim](unset)[/dim]'}")
|
|
215
|
+
if is_ours:
|
|
216
|
+
console.print(f"post-commit hook: [green]installed[/green] ({hook_file})")
|
|
217
|
+
elif hook_file.exists():
|
|
218
|
+
console.print(f"post-commit hook: [yellow]present but not RefactorAI[/yellow] ({hook_file})")
|
|
219
|
+
else:
|
|
220
|
+
console.print("post-commit hook: [red]not installed[/red] (run `refactor hook install`)")
|
|
221
|
+
console.print(f"Queue: {q_path}")
|
|
222
|
+
console.print(f"Pending events: [bold]{commit_queue.pending_count(root)}[/bold]")
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def _safe_read(path: Path) -> str:
|
|
226
|
+
try:
|
|
227
|
+
return path.read_text(encoding="utf-8")
|
|
228
|
+
except OSError:
|
|
229
|
+
return ""
|