refactorai-cli 0.2.20__tar.gz → 0.2.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.
Files changed (32) hide show
  1. {refactorai_cli-0.2.20 → refactorai_cli-0.2.22}/PKG-INFO +2 -2
  2. {refactorai_cli-0.2.20 → refactorai_cli-0.2.22}/README.md +1 -1
  3. {refactorai_cli-0.2.20 → refactorai_cli-0.2.22}/pyproject.toml +1 -1
  4. {refactorai_cli-0.2.20 → refactorai_cli-0.2.22}/refactorai_cli/__init__.py +1 -1
  5. {refactorai_cli-0.2.20 → refactorai_cli-0.2.22}/refactorai_cli/commands/run_cmds.py +164 -4
  6. {refactorai_cli-0.2.20 → refactorai_cli-0.2.22}/refactorai_cli.egg-info/PKG-INFO +2 -2
  7. {refactorai_cli-0.2.20 → refactorai_cli-0.2.22}/refactorai_cli/auth.py +0 -0
  8. {refactorai_cli-0.2.20 → refactorai_cli-0.2.22}/refactorai_cli/client.py +0 -0
  9. {refactorai_cli-0.2.20 → refactorai_cli-0.2.22}/refactorai_cli/commands/__init__.py +0 -0
  10. {refactorai_cli-0.2.20 → refactorai_cli-0.2.22}/refactorai_cli/commands/auth_cmds.py +0 -0
  11. {refactorai_cli-0.2.20 → refactorai_cli-0.2.22}/refactorai_cli/commands/engine_cmds.py +0 -0
  12. {refactorai_cli-0.2.20 → refactorai_cli-0.2.22}/refactorai_cli/commands/model_cmds.py +0 -0
  13. {refactorai_cli-0.2.20 → refactorai_cli-0.2.22}/refactorai_cli/commands/rules_cmds.py +0 -0
  14. {refactorai_cli-0.2.20 → refactorai_cli-0.2.22}/refactorai_cli/commands/runtime_cmds.py +0 -0
  15. {refactorai_cli-0.2.20 → refactorai_cli-0.2.22}/refactorai_cli/commands/runtime_proxy_cmds.py +0 -0
  16. {refactorai_cli-0.2.20 → refactorai_cli-0.2.22}/refactorai_cli/commands/setup_cmds.py +0 -0
  17. {refactorai_cli-0.2.20 → refactorai_cli-0.2.22}/refactorai_cli/control_plane.py +0 -0
  18. {refactorai_cli-0.2.20 → refactorai_cli-0.2.22}/refactorai_cli/credentials.py +0 -0
  19. {refactorai_cli-0.2.20 → refactorai_cli-0.2.22}/refactorai_cli/local_constitution.py +0 -0
  20. {refactorai_cli-0.2.20 → refactorai_cli-0.2.22}/refactorai_cli/local_engine_runtime.py +0 -0
  21. {refactorai_cli-0.2.20 → refactorai_cli-0.2.22}/refactorai_cli/local_paths.py +0 -0
  22. {refactorai_cli-0.2.20 → refactorai_cli-0.2.22}/refactorai_cli/main.py +0 -0
  23. {refactorai_cli-0.2.20 → refactorai_cli-0.2.22}/refactorai_cli/model_policy.py +0 -0
  24. {refactorai_cli-0.2.20 → refactorai_cli-0.2.22}/refactorai_cli/runtime_manager.py +0 -0
  25. {refactorai_cli-0.2.20 → refactorai_cli-0.2.22}/refactorai_cli/settings.py +0 -0
  26. {refactorai_cli-0.2.20 → refactorai_cli-0.2.22}/refactorai_cli/setup_flow.py +0 -0
  27. {refactorai_cli-0.2.20 → refactorai_cli-0.2.22}/refactorai_cli.egg-info/SOURCES.txt +0 -0
  28. {refactorai_cli-0.2.20 → refactorai_cli-0.2.22}/refactorai_cli.egg-info/dependency_links.txt +0 -0
  29. {refactorai_cli-0.2.20 → refactorai_cli-0.2.22}/refactorai_cli.egg-info/entry_points.txt +0 -0
  30. {refactorai_cli-0.2.20 → refactorai_cli-0.2.22}/refactorai_cli.egg-info/requires.txt +0 -0
  31. {refactorai_cli-0.2.20 → refactorai_cli-0.2.22}/refactorai_cli.egg-info/top_level.txt +0 -0
  32. {refactorai_cli-0.2.20 → refactorai_cli-0.2.22}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: refactorai-cli
3
- Version: 0.2.20
3
+ Version: 0.2.22
4
4
  Summary: Local-first CLI for the refactor platform
5
5
  Requires-Python: >=3.11
6
6
  Description-Content-Type: text/markdown
@@ -53,6 +53,6 @@ python -m twine upload ./refactorai-cli/dist/*
53
53
  ## Install test (local)
54
54
 
55
55
  ```bash
56
- python -m pip install ./refactorai-cli/dist/refactorai_cli-0.2.12-py3-none-any.whl
56
+ python -m pip install ./refactorai-cli/dist/refactorai_cli-0.2.22-py3-none-any.whl
57
57
  refactor --version
58
58
  ```
@@ -42,6 +42,6 @@ python -m twine upload ./refactorai-cli/dist/*
42
42
  ## Install test (local)
43
43
 
44
44
  ```bash
45
- python -m pip install ./refactorai-cli/dist/refactorai_cli-0.2.12-py3-none-any.whl
45
+ python -m pip install ./refactorai-cli/dist/refactorai_cli-0.2.22-py3-none-any.whl
46
46
  refactor --version
47
47
  ```
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "refactorai-cli"
3
- version = "0.2.20"
3
+ version = "0.2.22"
4
4
  description = "Local-first CLI for the refactor platform"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -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.2.20"
8
+ __version__ = "0.2.22"
@@ -72,6 +72,9 @@ from refactor_core.models import Finding, GeneratedTest, PatchGroup, RunArtifact
72
72
  from refactor_core.pipeline import build_refactor_prompt, build_review_prompt
73
73
  from refactor_core.providers import get_provider, resolve_provider_name, supported_providers
74
74
  from refactor_core.rules import annotate_documents_with_rules, filter_documents_by_rules
75
+ from refactor_core.security import merge_refactor_findings, suppress_findings_for_rr
76
+ from refactor_core.security.semgrep_runner import EMBEDDED_SEMGREP_SENTINEL
77
+ from refactor_core.security.suppression import SecuritySuppression
75
78
  from refactor_core.sandbox_runtime import (
76
79
  DEFAULT_IMAGE as SANDBOX_DEFAULT_IMAGE,
77
80
  DEFAULT_SHELL as SANDBOX_DEFAULT_SHELL,
@@ -110,6 +113,7 @@ from refactor_core.testcmd import resolve_test_command
110
113
 
111
114
  from refactorai_cli.auth import AuthError, ensure_authenticated
112
115
  from refactorai_cli.local_engine_runtime import DEFAULT_ENGINE_PORT, read_engine_state
116
+ from refactorai_cli.runtime_manager import runtime_status, runtime_version_dir
113
117
  from refactorai_cli.settings import platform_url
114
118
  from refactorai_cli.setup_flow import (
115
119
  BACKEND_BYOK,
@@ -689,6 +693,82 @@ def _warn_raw_key(constitution, config) -> None:
689
693
  )
690
694
 
691
695
 
696
+ def _resolve_runtime_semgrep_binary() -> str:
697
+ try:
698
+ status = runtime_status()
699
+ except Exception:
700
+ return ""
701
+ active = str(status.get("active_version") or "").strip()
702
+ if not active:
703
+ return ""
704
+ try:
705
+ base = runtime_version_dir(active)
706
+ except Exception:
707
+ return ""
708
+ candidates = (
709
+ base / "semgrep",
710
+ base / "semgrep.bin",
711
+ base / "bin" / "semgrep",
712
+ base / "tools" / "semgrep",
713
+ )
714
+ for path in candidates:
715
+ if path.is_file():
716
+ return str(path)
717
+ return ""
718
+
719
+
720
+ def _active_runtime_version() -> str:
721
+ try:
722
+ status = runtime_status()
723
+ except Exception:
724
+ return ""
725
+ return str(status.get("active_version") or "").strip()
726
+
727
+
728
+ def _inject_runtime_scanner_settings(constitution) -> None:
729
+ settings = dict(getattr(constitution, "settings", {}) or {})
730
+ if not bool(settings.get("security_scanner_enabled", False)):
731
+ constitution.settings = settings
732
+ return
733
+ active_version = _active_runtime_version()
734
+ scanner_path = _resolve_runtime_semgrep_binary()
735
+ settings["security_scan_require_runtime_binary"] = True
736
+ if scanner_path:
737
+ settings["security_scan_binary"] = scanner_path
738
+ elif active_version:
739
+ # Runtime artifact can include semgrep as an embedded python module.
740
+ settings["security_scan_binary"] = EMBEDDED_SEMGREP_SENTINEL
741
+ else:
742
+ settings["security_scan_binary"] = ""
743
+ constitution.settings = settings
744
+
745
+
746
+ def _report_runtime_scanner_status() -> None:
747
+ scanner_path = _resolve_runtime_semgrep_binary()
748
+ try:
749
+ status = runtime_status()
750
+ except Exception as exc:
751
+ console.print(f"[yellow]security scanner[/yellow]: status unavailable ({exc})")
752
+ return
753
+ active = str(status.get("active_version") or "").strip()
754
+ if scanner_path:
755
+ console.print(
756
+ f"[green]security scanner[/green]: ready "
757
+ f"[dim](runtime={active or 'unknown'}, path={scanner_path})[/dim]"
758
+ )
759
+ return
760
+ if active:
761
+ console.print(
762
+ "[green]security scanner[/green]: ready "
763
+ f"[dim](runtime={active}, embedded in runtime artifact)[/dim]"
764
+ )
765
+ return
766
+ console.print(
767
+ "[yellow]security scanner[/yellow]: unavailable "
768
+ "[dim](no active runtime; run `refactor runtime update`)[/dim]"
769
+ )
770
+
771
+
692
772
  def _resolve_compliance_context(
693
773
  *,
694
774
  project_config,
@@ -1562,6 +1642,7 @@ def review(
1562
1642
  """Run review (findings only) and store the result. Requires a valid key."""
1563
1643
  _require_auth()
1564
1644
  project_root, constitution, project_config = _load_project()
1645
+ _inject_runtime_scanner_settings(constitution)
1565
1646
  try:
1566
1647
  (
1567
1648
  compliance_frameworks,
@@ -1665,13 +1746,16 @@ def review(
1665
1746
  run_store = create_run(project_root, run_id=artifact.run_id)
1666
1747
  run_store.write_artifact(artifact, target=target, status="completed")
1667
1748
 
1668
- _print_findings(artifact.findings)
1749
+ rr_findings, security_suppressions = _prepare_rr_findings(artifact)
1750
+ _print_findings(rr_findings)
1751
+ _report_security_suppressions(security_suppressions, artifact=artifact)
1752
+ _print_security_overview(artifact)
1669
1753
 
1670
1754
  # A fresh review supersedes prior refactor requests: replace them with one
1671
1755
  # RR per actionable finding (central-only; nothing is written into project tree).
1672
1756
  requests = _replace_requests_with_framework_guardrails(
1673
1757
  project_root=project_root,
1674
- findings=artifact.findings,
1758
+ findings=rr_findings,
1675
1759
  run_id=artifact.run_id,
1676
1760
  compliance_frameworks=compliance_frameworks,
1677
1761
  compliance_level=resolved_compliance_level,
@@ -1781,6 +1865,7 @@ def code(
1781
1865
  """
1782
1866
  _require_auth()
1783
1867
  project_root, constitution, project_config = _load_project()
1868
+ _inject_runtime_scanner_settings(constitution)
1784
1869
  try:
1785
1870
  (
1786
1871
  compliance_frameworks,
@@ -1870,10 +1955,13 @@ def code(
1870
1955
  )
1871
1956
  review_store = create_run(project_root, run_id=review_artifact.run_id)
1872
1957
  review_store.write_artifact(review_artifact, target=target, status="completed")
1873
- _print_findings(review_artifact.findings)
1958
+ rr_findings, security_suppressions = _prepare_rr_findings(review_artifact)
1959
+ _print_findings(rr_findings)
1960
+ _report_security_suppressions(security_suppressions, artifact=review_artifact)
1961
+ _print_security_overview(review_artifact)
1874
1962
  requests = _replace_requests_with_framework_guardrails(
1875
1963
  project_root=project_root,
1876
- findings=review_artifact.findings,
1964
+ findings=rr_findings,
1877
1965
  run_id=review_artifact.run_id,
1878
1966
  compliance_frameworks=compliance_frameworks,
1879
1967
  compliance_level=resolved_compliance_level,
@@ -2294,6 +2382,37 @@ def _format_operation_scope(rr) -> str:
2294
2382
  return "-"
2295
2383
 
2296
2384
 
2385
+ def _security_findings_from_artifact(artifact: RunArtifact) -> list[Finding]:
2386
+ summary = dict(getattr(artifact, "security_summary", {}) or {})
2387
+ raw = summary.get("normalized_findings", [])
2388
+ if not isinstance(raw, list):
2389
+ return []
2390
+ out: list[Finding] = []
2391
+ for item in raw:
2392
+ try:
2393
+ out.append(Finding.model_validate(item))
2394
+ except Exception:
2395
+ continue
2396
+ return out
2397
+
2398
+
2399
+ def _prepare_rr_findings(artifact: RunArtifact) -> tuple[list[Finding], list[SecuritySuppression]]:
2400
+ scanner_findings = _security_findings_from_artifact(artifact)
2401
+ merged, _stats = merge_refactor_findings(list(artifact.findings or []), scanner_findings)
2402
+ actionable, suppressed = suppress_findings_for_rr(merged)
2403
+ summary = dict(getattr(artifact, "security_summary", {}) or {})
2404
+ summary["rr_prefilter_input_count"] = len(merged)
2405
+ summary["rr_prefilter_actionable_count"] = len(actionable)
2406
+ summary["rr_prefilter_suppressed_count"] = len(suppressed)
2407
+ reasons: dict[str, int] = {}
2408
+ for item in suppressed:
2409
+ code = str(item.reason_code or "unknown")
2410
+ reasons[code] = int(reasons.get(code, 0) or 0) + 1
2411
+ summary["rr_prefilter_reason_counts"] = reasons
2412
+ artifact.security_summary = summary
2413
+ return actionable, suppressed
2414
+
2415
+
2297
2416
  def _replace_requests_with_framework_guardrails(
2298
2417
  *,
2299
2418
  project_root: Path,
@@ -2348,6 +2467,46 @@ def _replace_requests_with_framework_guardrails(
2348
2467
  return requests
2349
2468
 
2350
2469
 
2470
+ def _report_security_suppressions(
2471
+ suppressions: list[SecuritySuppression], *, artifact: RunArtifact | None = None
2472
+ ) -> None:
2473
+ if not suppressions:
2474
+ return
2475
+ console.print(
2476
+ f"[yellow]warning:[/yellow] suppressed {len(suppressions)} finding(s) "
2477
+ "before RR creation (security prefilter)."
2478
+ )
2479
+ for item in suppressions:
2480
+ console.print(f"[yellow]-[/yellow] {item.location}: {item.message} [dim]({item.reason_code})[/dim]")
2481
+ if artifact is not None:
2482
+ artifact.notes.append(item.note())
2483
+
2484
+
2485
+ def _print_security_overview(artifact: RunArtifact) -> None:
2486
+ summary = dict(getattr(artifact, "security_summary", {}) or {})
2487
+ if not summary:
2488
+ return
2489
+ enabled = bool(summary.get("enabled", False))
2490
+ status = str(summary.get("status", "unknown") or "unknown")
2491
+ raw_count = int(summary.get("raw_count", 0) or 0)
2492
+ normalized = int(summary.get("normalized_count", 0) or 0)
2493
+ actionable = int(summary.get("rr_prefilter_actionable_count", len(getattr(artifact, "findings", []) or [])) or 0)
2494
+ suppressed = int(summary.get("rr_prefilter_suppressed_count", 0) or 0)
2495
+ duplicates = int(summary.get("duplicates_with_base", 0) or 0)
2496
+ scanner_version = str(summary.get("scanner_version", "") or "")
2497
+ version_note = f" v{scanner_version}" if scanner_version else ""
2498
+ enabled_note = "enabled" if enabled else "disabled"
2499
+ console.print(
2500
+ "[dim]security baseline: "
2501
+ f"{enabled_note} status={status}{version_note} raw={raw_count} "
2502
+ f"normalized={normalized} duplicates={duplicates} "
2503
+ f"actionable={actionable} suppressed={suppressed}[/dim]"
2504
+ )
2505
+ errors = summary.get("errors", [])
2506
+ if isinstance(errors, list) and errors:
2507
+ console.print("[dim]security notes:[/dim] " + ", ".join(str(item) for item in errors))
2508
+
2509
+
2351
2510
  def _apply_compliance_gate_to_artifact(artifact: RunArtifact) -> None:
2352
2511
  """Promote compliance blocking decisions into gate outcome (R18 Phase 7)."""
2353
2512
  summary = dict(getattr(artifact, "compliance_summary", {}) or {})
@@ -2801,6 +2960,7 @@ def doctor(
2801
2960
  console.print(f"[green]developer key[/green]: OK account={auth_ctx.account_id}{cache_note}")
2802
2961
  except AuthError as exc:
2803
2962
  console.print(f"[yellow]developer key[/yellow]: {exc}")
2963
+ _report_runtime_scanner_status()
2804
2964
 
2805
2965
  missing = _missing_requirements(provider_name)
2806
2966
  if missing:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: refactorai-cli
3
- Version: 0.2.20
3
+ Version: 0.2.22
4
4
  Summary: Local-first CLI for the refactor platform
5
5
  Requires-Python: >=3.11
6
6
  Description-Content-Type: text/markdown
@@ -53,6 +53,6 @@ python -m twine upload ./refactorai-cli/dist/*
53
53
  ## Install test (local)
54
54
 
55
55
  ```bash
56
- python -m pip install ./refactorai-cli/dist/refactorai_cli-0.2.12-py3-none-any.whl
56
+ python -m pip install ./refactorai-cli/dist/refactorai_cli-0.2.22-py3-none-any.whl
57
57
  refactor --version
58
58
  ```