-
Notifications
You must be signed in to change notification settings - Fork 4
Feature/connected pipeline #147
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
9c8bf00
804bc4e
a831b5c
c86a227
98e7419
6ac0831
1959402
1e9a5f3
d75b3ad
da7fbed
499819b
ab81338
18908f1
640e0cd
8700632
4dda068
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -97,6 +97,13 @@ | |
| default=False, | ||
| help="Disable interactive mode — dump all issues (for CI/CD or piped output)", | ||
| ) | ||
| @click.option( | ||
| "--fix-on", | ||
| "fix_on", | ||
| type=click.Choice(["CRITICAL", "ERROR", "WARNING", "INFO"], case_sensitive=False), | ||
| default=None, | ||
| help="Auto-queue issues at this level and above for fixing after analysis.", | ||
| ) | ||
| @click.option( | ||
| "--format", | ||
| "output_format", | ||
|
|
@@ -123,6 +130,7 @@ def analyze( | |
| environment: Optional[str], | ||
| no_cache: bool, | ||
| no_interactive: bool, | ||
| fix_on: Optional[str] = None, | ||
| output_format: str = "text", | ||
| fail_on: Optional[str] = None, | ||
| ) -> None: | ||
|
|
@@ -180,8 +188,10 @@ def analyze( | |
| cfg.log_format = log_format | ||
| if metrics is not None: | ||
| cfg.enable_metrics = metrics | ||
| if no_cache: | ||
| cfg.enable_incremental_analysis = False | ||
| # Always disable incremental analysis in CLI — users expect `analyze` to | ||
| # always return all issues, not skip unchanged files silently. | ||
| # (Incremental filtering is an optimization for the programmatic API only.) | ||
| cfg.enable_incremental_analysis = False | ||
|
|
||
| if output_format != "json": | ||
| _print_file_count(target_path) | ||
|
|
@@ -258,6 +268,54 @@ def analyze( | |
| ) | ||
| console.print(f" Success rate: {metrics_summary.get('success_rate_percent', 0):.1f}%") | ||
|
|
||
| # Exit with error code if critical issues found | ||
| should_fail = summary["critical"] > 0 | ||
|
|
||
| # ── Pipeline session ────────────────────────────────────────────── | ||
| from datetime import datetime, timezone | ||
|
|
||
| from refactron.core.pipeline import RefactronPipeline | ||
| from refactron.core.pipeline_session import PipelineSession, SessionStore | ||
|
|
||
| _FIX_LEVEL_MAP = { | ||
| "CRITICAL": IssueLevel.CRITICAL, | ||
| "ERROR": IssueLevel.ERROR, | ||
| "WARNING": IssueLevel.WARNING, | ||
| "INFO": IssueLevel.INFO, | ||
| } | ||
|
|
||
| _target_path = Path(target) if target else Path.cwd() | ||
| _project_root = _target_path if _target_path.is_dir() else _target_path.parent | ||
| _pipeline = RefactronPipeline(project_root=_project_root) | ||
|
Comment on lines
+287
to
+289
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Persist the session under the project root, not For nested targets, Also applies to: 312-313 🤖 Prompt for AI Agents |
||
|
|
||
| _session_id = SessionStore.make_session_id() | ||
| _pipeline_session = PipelineSession( | ||
| session_id=_session_id, | ||
| target=str(_target_path), | ||
| created_at=datetime.now(timezone.utc).isoformat(), | ||
| total_files=summary.get("total_files", 0), | ||
| total_issues=summary.get("total_issues", 0), | ||
| issues_by_level={ | ||
| "CRITICAL": summary.get("critical", 0), | ||
| "ERROR": summary.get("errors", 0), | ||
| "WARNING": summary.get("warnings", 0), | ||
| "INFO": summary.get("info", 0), | ||
| }, | ||
| ) | ||
|
Comment on lines
+292
to
+304
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Set the lifecycle state explicitly when creating the session by hand.
🤖 Prompt for AI Agents |
||
|
|
||
| # Always queue all issues so `autofix` has a full picture. | ||
| # `autofix --fix-on` controls which level actually gets applied. | ||
| _all_issues = [i for fm in result.file_metrics for i in fm.issues] | ||
| _pipeline.queue_issues(_pipeline_session, _all_issues) | ||
|
|
||
| _pipeline.store.save(_pipeline_session) | ||
| _pipeline.store.set_current(_session_id) | ||
|
|
||
| _fixable = len([i for i in _pipeline_session.fix_queue if i.status.value == "pending"]) | ||
| console.print(f"\n[dim]Session: {_session_id}[/dim]") | ||
| if _fixable: | ||
| console.print(f"[dim]{_fixable} fixable issues queued → refactron autofix --dry-run[/dim]") | ||
|
|
||
| # Exit with error code: --fail-on sets threshold, default is CRITICAL | ||
| _LEVEL_RANK = {"INFO": 0, "WARNING": 1, "ERROR": 2, "CRITICAL": 3} | ||
| _SUMMARY_KEY = { | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -209,7 +209,7 @@ def refactor( | |||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| @click.command() | ||||||||||||||||||||||||||
| @click.argument("target", type=click.Path(exists=True)) | ||||||||||||||||||||||||||
| @click.argument("target", type=click.Path(exists=True), required=False) | ||||||||||||||||||||||||||
| @click.option( | ||||||||||||||||||||||||||
| "--config", | ||||||||||||||||||||||||||
| "-c", | ||||||||||||||||||||||||||
|
|
@@ -261,29 +261,161 @@ def refactor( | |||||||||||||||||||||||||
| default=False, | ||||||||||||||||||||||||||
| help="Run verification checks (syntax, imports, tests) before applying fixes", | ||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||
| @click.option( | ||||||||||||||||||||||||||
| "--session", | ||||||||||||||||||||||||||
| "session_id", | ||||||||||||||||||||||||||
| default=None, | ||||||||||||||||||||||||||
| help=( | ||||||||||||||||||||||||||
| "Override the active workspace session. If omitted, uses the " | ||||||||||||||||||||||||||
| "session set by the last 'refactron analyze' or 'refactron run'." | ||||||||||||||||||||||||||
| ), | ||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||
| @click.option( | ||||||||||||||||||||||||||
| "--fix-on", | ||||||||||||||||||||||||||
| "fix_on", | ||||||||||||||||||||||||||
| type=click.Choice(["CRITICAL", "ERROR", "WARNING", "INFO"], case_sensitive=False), | ||||||||||||||||||||||||||
| default="CRITICAL", | ||||||||||||||||||||||||||
| show_default=True, | ||||||||||||||||||||||||||
| help="Apply only issues at this severity level and above.", | ||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||
| def autofix( | ||||||||||||||||||||||||||
| target: str, | ||||||||||||||||||||||||||
| target: Optional[str], | ||||||||||||||||||||||||||
| config: Optional[str], | ||||||||||||||||||||||||||
| profile: Optional[str], | ||||||||||||||||||||||||||
| environment: Optional[str], | ||||||||||||||||||||||||||
| preview: bool, | ||||||||||||||||||||||||||
| dry_run: bool, | ||||||||||||||||||||||||||
| safety_level: str, | ||||||||||||||||||||||||||
| verify: bool, | ||||||||||||||||||||||||||
| session_id: Optional[str] = None, | ||||||||||||||||||||||||||
| fix_on: str = "CRITICAL", | ||||||||||||||||||||||||||
| ) -> None: | ||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||
| Automatically fix code issues (Phase 3 feature). | ||||||||||||||||||||||||||
| """Apply fixes from the active pipeline session. | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| TARGET: Path to file or directory to fix | ||||||||||||||||||||||||||
| Automatically reads the current workspace session created by | ||||||||||||||||||||||||||
| 'refactron analyze' or 'refactron run' — no session ID needed. | ||||||||||||||||||||||||||
| Use --session to target a specific session instead. | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| \b | ||||||||||||||||||||||||||
| Typical workflow: | ||||||||||||||||||||||||||
| refactron analyze src/ --fix-on CRITICAL # creates session | ||||||||||||||||||||||||||
| refactron autofix --dry-run # preview (uses active session) | ||||||||||||||||||||||||||
| refactron autofix # apply fixes | ||||||||||||||||||||||||||
| refactron rollback # undo if needed | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| \b | ||||||||||||||||||||||||||
| Examples: | ||||||||||||||||||||||||||
| refactron autofix myfile.py --preview | ||||||||||||||||||||||||||
| refactron autofix myproject/ --apply --safety-level moderate | ||||||||||||||||||||||||||
| refactron autofix --dry-run | ||||||||||||||||||||||||||
| refactron autofix --session sess_20260404_120000 | ||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||
| console.print() | ||||||||||||||||||||||||||
| _auth_banner("Auto-fix") | ||||||||||||||||||||||||||
| console.print() | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| # ── Session-aware pipeline ──────────────────────────────────────── | ||||||||||||||||||||||||||
| from refactron.core.pipeline import RefactronPipeline | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| _target_path = Path(target) if target else Path.cwd() | ||||||||||||||||||||||||||
| _project_root = _target_path if _target_path.is_dir() else _target_path.parent | ||||||||||||||||||||||||||
| _pipeline = RefactronPipeline(project_root=_project_root) | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
| _pipeline = RefactronPipeline(project_root=_project_root) | |
| _safety_level_map = { | |
| "conservative": FixRiskLevel.CONSERVATIVE, | |
| "moderate": FixRiskLevel.MODERATE, | |
| "aggressive": FixRiskLevel.AGGRESSIVE, | |
| } | |
| _selected_safety_level = _safety_level_map.get( | |
| str(safety_level).lower(), FixRiskLevel.MODERATE | |
| ) | |
| _pipeline = RefactronPipeline( | |
| project_root=_project_root, safety_level=_selected_safety_level | |
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new session-aware path bypasses the command's config and safety settings.
Because this branch returns at Line 378, the old _load_config() call and safety_level mapping are now unreachable. --config, --profile, --environment, and --safety-level no longer affect the new pipeline flow, so the command surface and runtime behavior are out of sync.
Also applies to: 378-404
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@refactron/cli/refactor.py` around lines 306 - 312, The new session-aware
branch constructs RefactronPipeline using _target_path/_project_root and returns
early, which bypasses _load_config(), the safety_level mapping, and flags like
--config, --profile, and --environment; update the flow so that before creating
or returning the RefactronPipeline instance (_pipeline) you call _load_config()
and apply the resolved configuration and safety_level (or pass them into
RefactronPipeline constructor) so the session-aware path honors --config,
--profile, --environment and --safety-level exactly like the original branch
did.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Resolve sessions/backups from the pipeline root, not the caller's location.
RefactronPipeline persists sessions under its project_root, but autofix --session derives that root from the current target/cwd and rollback --pipeline-session hard-codes Path.cwd() for both SessionStore and BackupManager. Resuming or rolling back the same session from a subdirectory or another shell location will miss the saved session/backups or target the wrong project. Reuse the same root-discovery logic in both flows, or derive it from PipelineSession.target.
Also applies to: 461-478
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@refactron/cli/refactor.py` around lines 300 - 307, The session/backup lookup
is using the caller's CWD instead of the pipeline's project root; update the
code that constructs SessionStore and BackupManager and the session-loading
logic to derive project_root the same way RefactronPipeline does (use the
_target_path/_project_root logic used when instantiating RefactronPipeline or
use PipelineSession.target when resuming), so SessionStore.load(session_id) and
BackupManager operate against the pipeline's project_root rather than
Path.cwd(); apply the same change to the other affected block referenced (around
the code handling rollback / lines 461-478) so all session and backup resolution
consistently uses RefactronPipeline.project_root or PipelineSession.target.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't persist --fix-on filtering into the stored queue.
This mutates queued PENDING items to SKIPPED before apply. After one CRITICAL-only run, a later WARNING run against the same session never sees those lower-severity items again.
🧰 Tools
🪛 GitHub Actions: Pre-commit
[error] black failed: files were modified by this hook (reformatted refactron/cli/refactor.py). Re-run pre-commit to apply changes.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@refactron/cli/refactor.py` around lines 354 - 364, The current loop mutates
stored session items (setting _item.status = _FixStatus.SKIPPED) which persists
the --fix-on filter; instead leave _pipeline_session.fix_queue unchanged and
apply the threshold only transiently. Replace the in-place mutation with logic
that computes a local view or predicates: use _LEVEL_RANK, _threshold and
_FixStatus to count pending items whose level meets the threshold (e.g., count
items where item.status == _FixStatus.PENDING and
_LEVEL_RANK.get(item.level.upper(),0) >= _threshold) and pass that
filtered/local view into the apply step or consumer; do not write back SKIPPED
into _pipeline_session.fix_queue. Ensure all references to _pending_count and
any downstream use operate on the transient/filtered view rather than mutated
stored items.
Copilot
AI
Apr 3, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
autofix currently ignores the --preview/--apply flag: it always calls _pipeline.apply(..., dry_run=dry_run) regardless of preview. Since preview defaults to True and dry_run defaults to False, running refactron autofix <target> would write changes by default. Please gate writes on preview (e.g., treat preview as dry-run) so the default behavior remains non-destructive unless --apply is explicitly provided.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
autofix now applies by default even though --preview is still the default.
This branch ignores preview and passes only dry_run into RefactronPipeline.apply(). With the current defaults (preview=True, dry_run=False), refactron autofix writes changes immediately instead of previewing them.
Proposed fix
- _pipeline.apply(
+ _effective_dry_run = dry_run or preview
+
+ _pipeline.apply(
_pipeline_session,
- dry_run=dry_run,
+ dry_run=_effective_dry_run,
verify=verify,
)
@@
- if dry_run:
+ if _effective_dry_run:
console.print("\n[bold]Dry-run complete[/bold]")Based on learnings, All refactoring must go through safety-first pipeline: preview → backup → apply → optional rollback.
Also applies to: 358-366
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@refactron/cli/refactor.py` around lines 348 - 352, The call to
_pipeline.apply(...) is missing the preview argument so the CLI's preview flag
is ignored (causing autofix to write changes); update the two places where
_pipeline.apply is invoked (the call using _pipeline_session and the similar
block further down) to pass preview=preview along with dry_run=dry_run and
verify=verify so the RefactronPipeline.apply(preview, dry_run, verify, ...)
safety-first flow (preview → backup → apply → optional rollback) is preserved.
Copilot
AI
Apr 3, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the non-dry-run summary, the rollback hint uses refactron rollback --session <id>, but the pipeline-session rollback option added below is --pipeline-session. As written, the printed command will not roll back the pipeline session. Please update the hint to match the actual option name for pipeline sessions.
| f"[dim]To undo: refactron rollback --session " | |
| f"[dim]To undo: refactron rollback --pipeline-session " |
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't clear/close the session unless rollback fully succeeded.
BackupManager.rollback_session() can return failed restores, but this branch still marks the session ROLLED_BACK and unconditionally deletes .refactron/current. That misreports partial rollbacks, and it also clears the active session even when the user explicitly rolled back some other session.
Also applies to: 509-513
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@refactron/cli/refactor.py` around lines 494 - 495, The current code
unconditionally marks a session ROLLED_BACK and removes the active session file
after calling BackupManager.rollback_session(), which can return partial
failures; change the logic in the block around _resolved_id (and the similar
block at lines ~509-513) to inspect the result of
BackupManager.rollback_session() and only (1) set the session state to
ROLLED_BACK and (2) delete .refactron/current when rollback_session reports all
restores succeeded; if rollback_session reports any failures, leave the session
state unchanged (or set an explicit PARTIAL_ROLLBACK status if available) and do
not remove .refactron/current unless _resolved_id matches the currently active
session and the rollback fully succeeded; reference functions/vars:
BackupManager.rollback_session(), _resolved_id, pipeline_session_id,
_store.get_current_id(), and the ROLLED_BACK state to locate and update the code
paths.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't auto-execute pipeline rollback before honoring the explicit rollback mode.
Because _resolved_id is handled before --list, --clear, --use-git, or the legacy positional session id are checked, any active current pipeline session makes this branch run immediately. refactron rollback --list can restore files instead of listing them, and the confirmation prompt is skipped entirely.
🧰 Tools
🪛 GitHub Actions: Pre-commit
[error] black failed: files were modified by this hook (reformatted refactron/cli/refactor.py). Re-run pre-commit to apply changes.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@refactron/cli/refactor.py` around lines 529 - 559, The rollback branch
currently triggers as soon as _resolved_id is set (via pipeline_session_id or
SessionStore.get_current_id()), which runs before handling explicit rollback
modes like --list, --clear, --use-git or legacy positional IDs; move or guard
this logic so it only executes when the user actually requested a rollback
action (e.g., when a specific rollback flag/mode is set), not merely when an
active session exists: modify the control flow around _resolved_id,
SessionStore, and the block that uses BackupManager.rollback_session so that
flag checks for list/clear/use-git/positional-id are evaluated first and only
when none apply and the explicit rollback mode is present do you load the
session, validate backup_session_id, prompt for confirmation, and call
BackupManager.rollback_session; ensure functions/classes referenced are
SessionStore, _resolved_id/pipeline_session_id, BackupManager.rollback_session,
and _pipeline_session are used in the guarded branch.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
--fix-onis exposed but never affects the queued session.The flag is parsed and
_FIX_LEVEL_MAPis built, but this path still callsqueue_issues(_pipeline_session, _all_issues)with nomin_level. The saved session therefore ignores the user's threshold even though the help text says otherwise.Also applies to: 280-309
🤖 Prompt for AI Agents