refactorai-cli 0.2.19__tar.gz → 0.2.21__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.19 → refactorai_cli-0.2.21}/PKG-INFO +2 -2
  2. {refactorai_cli-0.2.19 → refactorai_cli-0.2.21}/README.md +1 -1
  3. {refactorai_cli-0.2.19 → refactorai_cli-0.2.21}/pyproject.toml +1 -1
  4. {refactorai_cli-0.2.19 → refactorai_cli-0.2.21}/refactorai_cli/__init__.py +1 -1
  5. {refactorai_cli-0.2.19 → refactorai_cli-0.2.21}/refactorai_cli/commands/run_cmds.py +163 -4
  6. {refactorai_cli-0.2.19 → refactorai_cli-0.2.21}/refactorai_cli.egg-info/PKG-INFO +2 -2
  7. {refactorai_cli-0.2.19 → refactorai_cli-0.2.21}/refactorai_cli/auth.py +0 -0
  8. {refactorai_cli-0.2.19 → refactorai_cli-0.2.21}/refactorai_cli/client.py +0 -0
  9. {refactorai_cli-0.2.19 → refactorai_cli-0.2.21}/refactorai_cli/commands/__init__.py +0 -0
  10. {refactorai_cli-0.2.19 → refactorai_cli-0.2.21}/refactorai_cli/commands/auth_cmds.py +0 -0
  11. {refactorai_cli-0.2.19 → refactorai_cli-0.2.21}/refactorai_cli/commands/engine_cmds.py +0 -0
  12. {refactorai_cli-0.2.19 → refactorai_cli-0.2.21}/refactorai_cli/commands/model_cmds.py +0 -0
  13. {refactorai_cli-0.2.19 → refactorai_cli-0.2.21}/refactorai_cli/commands/rules_cmds.py +0 -0
  14. {refactorai_cli-0.2.19 → refactorai_cli-0.2.21}/refactorai_cli/commands/runtime_cmds.py +0 -0
  15. {refactorai_cli-0.2.19 → refactorai_cli-0.2.21}/refactorai_cli/commands/runtime_proxy_cmds.py +0 -0
  16. {refactorai_cli-0.2.19 → refactorai_cli-0.2.21}/refactorai_cli/commands/setup_cmds.py +0 -0
  17. {refactorai_cli-0.2.19 → refactorai_cli-0.2.21}/refactorai_cli/control_plane.py +0 -0
  18. {refactorai_cli-0.2.19 → refactorai_cli-0.2.21}/refactorai_cli/credentials.py +0 -0
  19. {refactorai_cli-0.2.19 → refactorai_cli-0.2.21}/refactorai_cli/local_constitution.py +0 -0
  20. {refactorai_cli-0.2.19 → refactorai_cli-0.2.21}/refactorai_cli/local_engine_runtime.py +0 -0
  21. {refactorai_cli-0.2.19 → refactorai_cli-0.2.21}/refactorai_cli/local_paths.py +0 -0
  22. {refactorai_cli-0.2.19 → refactorai_cli-0.2.21}/refactorai_cli/main.py +0 -0
  23. {refactorai_cli-0.2.19 → refactorai_cli-0.2.21}/refactorai_cli/model_policy.py +0 -0
  24. {refactorai_cli-0.2.19 → refactorai_cli-0.2.21}/refactorai_cli/runtime_manager.py +0 -0
  25. {refactorai_cli-0.2.19 → refactorai_cli-0.2.21}/refactorai_cli/settings.py +0 -0
  26. {refactorai_cli-0.2.19 → refactorai_cli-0.2.21}/refactorai_cli/setup_flow.py +0 -0
  27. {refactorai_cli-0.2.19 → refactorai_cli-0.2.21}/refactorai_cli.egg-info/SOURCES.txt +0 -0
  28. {refactorai_cli-0.2.19 → refactorai_cli-0.2.21}/refactorai_cli.egg-info/dependency_links.txt +0 -0
  29. {refactorai_cli-0.2.19 → refactorai_cli-0.2.21}/refactorai_cli.egg-info/entry_points.txt +0 -0
  30. {refactorai_cli-0.2.19 → refactorai_cli-0.2.21}/refactorai_cli.egg-info/requires.txt +0 -0
  31. {refactorai_cli-0.2.19 → refactorai_cli-0.2.21}/refactorai_cli.egg-info/top_level.txt +0 -0
  32. {refactorai_cli-0.2.19 → refactorai_cli-0.2.21}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: refactorai-cli
3
- Version: 0.2.19
3
+ Version: 0.2.21
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.21-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.21-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.19"
3
+ version = "0.2.21"
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.19"
8
+ __version__ = "0.2.21"
@@ -72,6 +72,8 @@ 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.suppression import SecuritySuppression
75
77
  from refactor_core.sandbox_runtime import (
76
78
  DEFAULT_IMAGE as SANDBOX_DEFAULT_IMAGE,
77
79
  DEFAULT_SHELL as SANDBOX_DEFAULT_SHELL,
@@ -110,6 +112,7 @@ from refactor_core.testcmd import resolve_test_command
110
112
 
111
113
  from refactorai_cli.auth import AuthError, ensure_authenticated
112
114
  from refactorai_cli.local_engine_runtime import DEFAULT_ENGINE_PORT, read_engine_state
115
+ from refactorai_cli.runtime_manager import runtime_status, runtime_version_dir
113
116
  from refactorai_cli.settings import platform_url
114
117
  from refactorai_cli.setup_flow import (
115
118
  BACKEND_BYOK,
@@ -689,6 +692,66 @@ def _warn_raw_key(constitution, config) -> None:
689
692
  )
690
693
 
691
694
 
695
+ def _resolve_runtime_semgrep_binary() -> str:
696
+ try:
697
+ status = runtime_status()
698
+ except Exception:
699
+ return ""
700
+ active = str(status.get("active_version") or "").strip()
701
+ if not active:
702
+ return ""
703
+ try:
704
+ base = runtime_version_dir(active)
705
+ except Exception:
706
+ return ""
707
+ candidates = (
708
+ base / "semgrep",
709
+ base / "semgrep.bin",
710
+ base / "bin" / "semgrep",
711
+ base / "tools" / "semgrep",
712
+ )
713
+ for path in candidates:
714
+ if path.is_file():
715
+ return str(path)
716
+ return ""
717
+
718
+
719
+ def _inject_runtime_scanner_settings(constitution) -> None:
720
+ settings = dict(getattr(constitution, "settings", {}) or {})
721
+ if not bool(settings.get("security_scanner_enabled", False)):
722
+ constitution.settings = settings
723
+ return
724
+ settings["security_scan_require_runtime_binary"] = True
725
+ settings["security_scan_binary"] = _resolve_runtime_semgrep_binary()
726
+ constitution.settings = settings
727
+
728
+
729
+ def _report_runtime_scanner_status() -> None:
730
+ scanner_path = _resolve_runtime_semgrep_binary()
731
+ try:
732
+ status = runtime_status()
733
+ except Exception as exc:
734
+ console.print(f"[yellow]security scanner[/yellow]: status unavailable ({exc})")
735
+ return
736
+ active = str(status.get("active_version") or "").strip()
737
+ if scanner_path:
738
+ console.print(
739
+ f"[green]security scanner[/green]: ready "
740
+ f"[dim](runtime={active or 'unknown'}, path={scanner_path})[/dim]"
741
+ )
742
+ return
743
+ if active:
744
+ console.print(
745
+ "[yellow]security scanner[/yellow]: runtime active but scanner binary missing "
746
+ f"[dim](runtime={active})[/dim]"
747
+ )
748
+ return
749
+ console.print(
750
+ "[yellow]security scanner[/yellow]: unavailable "
751
+ "[dim](no active runtime; run `refactor runtime update`)[/dim]"
752
+ )
753
+
754
+
692
755
  def _resolve_compliance_context(
693
756
  *,
694
757
  project_config,
@@ -1562,6 +1625,7 @@ def review(
1562
1625
  """Run review (findings only) and store the result. Requires a valid key."""
1563
1626
  _require_auth()
1564
1627
  project_root, constitution, project_config = _load_project()
1628
+ _inject_runtime_scanner_settings(constitution)
1565
1629
  try:
1566
1630
  (
1567
1631
  compliance_frameworks,
@@ -1665,13 +1729,16 @@ def review(
1665
1729
  run_store = create_run(project_root, run_id=artifact.run_id)
1666
1730
  run_store.write_artifact(artifact, target=target, status="completed")
1667
1731
 
1668
- _print_findings(artifact.findings)
1732
+ rr_findings, security_suppressions = _prepare_rr_findings(artifact)
1733
+ _print_findings(rr_findings)
1734
+ _report_security_suppressions(security_suppressions, artifact=artifact)
1735
+ _print_security_overview(artifact)
1669
1736
 
1670
1737
  # A fresh review supersedes prior refactor requests: replace them with one
1671
1738
  # RR per actionable finding (central-only; nothing is written into project tree).
1672
1739
  requests = _replace_requests_with_framework_guardrails(
1673
1740
  project_root=project_root,
1674
- findings=artifact.findings,
1741
+ findings=rr_findings,
1675
1742
  run_id=artifact.run_id,
1676
1743
  compliance_frameworks=compliance_frameworks,
1677
1744
  compliance_level=resolved_compliance_level,
@@ -1682,6 +1749,14 @@ def review(
1682
1749
  )
1683
1750
  _apply_compliance_gate_to_artifact(artifact)
1684
1751
  run_store.write_artifact(artifact, target=target, status="completed")
1752
+ if compliance_frameworks:
1753
+ summary = dict(getattr(artifact, "compliance_summary", {}) or {})
1754
+ console.print(
1755
+ "[dim]review: compliance projection "
1756
+ f"projected={int(summary.get('projected_count', 0) or 0)} "
1757
+ f"blocked={int(summary.get('blocked_count', 0) or 0)} "
1758
+ f"warned={int(summary.get('warned_count', 0) or 0)}[/dim]"
1759
+ )
1685
1760
  if compliance_frameworks:
1686
1761
  artifact.notes.append(
1687
1762
  "Compliance selection: frameworks="
@@ -1773,6 +1848,7 @@ def code(
1773
1848
  """
1774
1849
  _require_auth()
1775
1850
  project_root, constitution, project_config = _load_project()
1851
+ _inject_runtime_scanner_settings(constitution)
1776
1852
  try:
1777
1853
  (
1778
1854
  compliance_frameworks,
@@ -1862,10 +1938,13 @@ def code(
1862
1938
  )
1863
1939
  review_store = create_run(project_root, run_id=review_artifact.run_id)
1864
1940
  review_store.write_artifact(review_artifact, target=target, status="completed")
1865
- _print_findings(review_artifact.findings)
1941
+ rr_findings, security_suppressions = _prepare_rr_findings(review_artifact)
1942
+ _print_findings(rr_findings)
1943
+ _report_security_suppressions(security_suppressions, artifact=review_artifact)
1944
+ _print_security_overview(review_artifact)
1866
1945
  requests = _replace_requests_with_framework_guardrails(
1867
1946
  project_root=project_root,
1868
- findings=review_artifact.findings,
1947
+ findings=rr_findings,
1869
1948
  run_id=review_artifact.run_id,
1870
1949
  compliance_frameworks=compliance_frameworks,
1871
1950
  compliance_level=resolved_compliance_level,
@@ -1875,6 +1954,14 @@ def code(
1875
1954
  artifact=review_artifact,
1876
1955
  )
1877
1956
  _apply_compliance_gate_to_artifact(review_artifact)
1957
+ if compliance_frameworks:
1958
+ summary = dict(getattr(review_artifact, "compliance_summary", {}) or {})
1959
+ console.print(
1960
+ "[dim]review: compliance projection "
1961
+ f"projected={int(summary.get('projected_count', 0) or 0)} "
1962
+ f"blocked={int(summary.get('blocked_count', 0) or 0)} "
1963
+ f"warned={int(summary.get('warned_count', 0) or 0)}[/dim]"
1964
+ )
1878
1965
  if compliance_frameworks:
1879
1966
  review_artifact.notes.append(
1880
1967
  "Compliance selection: frameworks="
@@ -2278,6 +2365,37 @@ def _format_operation_scope(rr) -> str:
2278
2365
  return "-"
2279
2366
 
2280
2367
 
2368
+ def _security_findings_from_artifact(artifact: RunArtifact) -> list[Finding]:
2369
+ summary = dict(getattr(artifact, "security_summary", {}) or {})
2370
+ raw = summary.get("normalized_findings", [])
2371
+ if not isinstance(raw, list):
2372
+ return []
2373
+ out: list[Finding] = []
2374
+ for item in raw:
2375
+ try:
2376
+ out.append(Finding.model_validate(item))
2377
+ except Exception:
2378
+ continue
2379
+ return out
2380
+
2381
+
2382
+ def _prepare_rr_findings(artifact: RunArtifact) -> tuple[list[Finding], list[SecuritySuppression]]:
2383
+ scanner_findings = _security_findings_from_artifact(artifact)
2384
+ merged, _stats = merge_refactor_findings(list(artifact.findings or []), scanner_findings)
2385
+ actionable, suppressed = suppress_findings_for_rr(merged)
2386
+ summary = dict(getattr(artifact, "security_summary", {}) or {})
2387
+ summary["rr_prefilter_input_count"] = len(merged)
2388
+ summary["rr_prefilter_actionable_count"] = len(actionable)
2389
+ summary["rr_prefilter_suppressed_count"] = len(suppressed)
2390
+ reasons: dict[str, int] = {}
2391
+ for item in suppressed:
2392
+ code = str(item.reason_code or "unknown")
2393
+ reasons[code] = int(reasons.get(code, 0) or 0) + 1
2394
+ summary["rr_prefilter_reason_counts"] = reasons
2395
+ artifact.security_summary = summary
2396
+ return actionable, suppressed
2397
+
2398
+
2281
2399
  def _replace_requests_with_framework_guardrails(
2282
2400
  *,
2283
2401
  project_root: Path,
@@ -2332,6 +2450,46 @@ def _replace_requests_with_framework_guardrails(
2332
2450
  return requests
2333
2451
 
2334
2452
 
2453
+ def _report_security_suppressions(
2454
+ suppressions: list[SecuritySuppression], *, artifact: RunArtifact | None = None
2455
+ ) -> None:
2456
+ if not suppressions:
2457
+ return
2458
+ console.print(
2459
+ f"[yellow]warning:[/yellow] suppressed {len(suppressions)} finding(s) "
2460
+ "before RR creation (security prefilter)."
2461
+ )
2462
+ for item in suppressions:
2463
+ console.print(f"[yellow]-[/yellow] {item.location}: {item.message} [dim]({item.reason_code})[/dim]")
2464
+ if artifact is not None:
2465
+ artifact.notes.append(item.note())
2466
+
2467
+
2468
+ def _print_security_overview(artifact: RunArtifact) -> None:
2469
+ summary = dict(getattr(artifact, "security_summary", {}) or {})
2470
+ if not summary:
2471
+ return
2472
+ enabled = bool(summary.get("enabled", False))
2473
+ status = str(summary.get("status", "unknown") or "unknown")
2474
+ raw_count = int(summary.get("raw_count", 0) or 0)
2475
+ normalized = int(summary.get("normalized_count", 0) or 0)
2476
+ actionable = int(summary.get("rr_prefilter_actionable_count", len(getattr(artifact, "findings", []) or [])) or 0)
2477
+ suppressed = int(summary.get("rr_prefilter_suppressed_count", 0) or 0)
2478
+ duplicates = int(summary.get("duplicates_with_base", 0) or 0)
2479
+ scanner_version = str(summary.get("scanner_version", "") or "")
2480
+ version_note = f" v{scanner_version}" if scanner_version else ""
2481
+ enabled_note = "enabled" if enabled else "disabled"
2482
+ console.print(
2483
+ "[dim]security baseline: "
2484
+ f"{enabled_note} status={status}{version_note} raw={raw_count} "
2485
+ f"normalized={normalized} duplicates={duplicates} "
2486
+ f"actionable={actionable} suppressed={suppressed}[/dim]"
2487
+ )
2488
+ errors = summary.get("errors", [])
2489
+ if isinstance(errors, list) and errors:
2490
+ console.print("[dim]security notes:[/dim] " + ", ".join(str(item) for item in errors))
2491
+
2492
+
2335
2493
  def _apply_compliance_gate_to_artifact(artifact: RunArtifact) -> None:
2336
2494
  """Promote compliance blocking decisions into gate outcome (R18 Phase 7)."""
2337
2495
  summary = dict(getattr(artifact, "compliance_summary", {}) or {})
@@ -2785,6 +2943,7 @@ def doctor(
2785
2943
  console.print(f"[green]developer key[/green]: OK account={auth_ctx.account_id}{cache_note}")
2786
2944
  except AuthError as exc:
2787
2945
  console.print(f"[yellow]developer key[/yellow]: {exc}")
2946
+ _report_runtime_scanner_status()
2788
2947
 
2789
2948
  missing = _missing_requirements(provider_name)
2790
2949
  if missing:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: refactorai-cli
3
- Version: 0.2.19
3
+ Version: 0.2.21
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.21-py3-none-any.whl
57
57
  refactor --version
58
58
  ```