Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 18 additions & 9 deletions refactron/cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,16 +89,18 @@ def main(ctx: click.Context) -> None:


# Register subcommands
# We import them here to ensure they are registered with the main group
# Using a try-except block to allow partial loading during refactoring
# We import them here to ensure they are registered with the main group.
# Each block is isolated so a single broken subcommand — or a broken optional
# dependency it pulls in (e.g. a failed native-library load raising OSError,
# not ImportError) — degrades gracefully instead of taking down the whole CLI.
try:
from refactron.cli.auth import auth, login, logout, telemetry

main.add_command(login)
main.add_command(logout)
main.add_command(auth)
main.add_command(telemetry)
except ImportError:
except Exception:
pass

try:
Expand All @@ -111,7 +113,7 @@ def main(ctx: click.Context) -> None:
main.add_command(metrics)
main.add_command(serve_metrics)
main.add_command(suggest)
except ImportError:
except Exception:
pass

try:
Expand All @@ -121,28 +123,35 @@ def main(ctx: click.Context) -> None:
main.add_command(autofix)
main.add_command(rollback)
main.add_command(document)
except ImportError:
except Exception:
pass

try:
from refactron.cli.verify import verify

main.add_command(verify)
except Exception:
pass

try:
from refactron.cli.patterns import patterns

main.add_command(patterns)
except ImportError:
except Exception:
pass

try:
from refactron.cli.repo import repo

main.add_command(repo)
except ImportError:
except Exception:
pass

try:
from refactron.cli.rag import rag

main.add_command(rag)
except ImportError:
except Exception:
pass

try:
Expand All @@ -151,5 +160,5 @@ def main(ctx: click.Context) -> None:
main.add_command(generate_cicd)
main.add_command(feedback)
main.add_command(init)
except ImportError:
except Exception:
pass
86 changes: 86 additions & 0 deletions refactron/cli/verify.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,89 @@
"""
Refactron CLI - Verification Module.
Command to run the Verification Engine on a code change and emit either a
human-readable report or a stable, machine-readable JSON object for CI gates.
"""

from pathlib import Path
from typing import Optional

import click

from refactron.cli.ui import _auth_banner, console
from refactron.verification.engine import VerificationEngine
from refactron.verification.report import (
format_verification_result,
format_verification_result_json,
)


@click.command()
@click.argument("target", type=click.Path(exists=True, dir_okay=False))
@click.option(
"--against",
"-a",
"candidate",
type=click.Path(exists=True, dir_okay=False),
default=None,
help=(
"Path to the proposed/modified version of TARGET. "
"If omitted, TARGET is verified against itself."
),
)
@click.option(
"--project-root",
type=click.Path(exists=True, file_okay=False),
default=None,
help="Project root used by the test-suite gate. Defaults to the current directory.",
)
@click.option(
"--all-checks",
is_flag=True,
default=False,
help=(
"Run every check even after one fails (no short-circuit), so all "
"failure categories surface in a single run."
),
)
@click.option(
"--json",
"as_json",
is_flag=True,
default=False,
help="Emit a stable, machine-readable JSON report instead of formatted text.",
)
def verify(
target: str,
candidate: Optional[str],
project_root: Optional[str],
all_checks: bool,
as_json: bool,
) -> None:
"""
Verify that a code change is safe to apply.

TARGET is the file as it currently lives in the project. With --against,
the proposed new content is checked against it (syntax, import integrity,
test suite). Exit code is 0 when safe to apply, 1 when blocked — and with
--json a versioned JSON object is printed for CI dashboards and bots.
"""
target_path = Path(target)
original = target_path.read_text(encoding="utf-8")
transformed = Path(candidate).read_text(encoding="utf-8") if candidate else original

root = Path(project_root) if project_root else Path.cwd()
engine = VerificationEngine(project_root=root)
result = engine.verify(
original, transformed, target_path, short_circuit=not all_checks
)

if as_json:
# JSON mode prints only the JSON object so consumers can parse stdout.
click.echo(format_verification_result_json(result))
else:
console.print()
_auth_banner("Verification")
format_verification_result(result, console)
"""refactron verify <file> --against <original> — standalone verification command."""

from pathlib import Path
Expand Down
Loading
Loading