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.
- {refactorai_cli-0.2.19 → refactorai_cli-0.2.21}/PKG-INFO +2 -2
- {refactorai_cli-0.2.19 → refactorai_cli-0.2.21}/README.md +1 -1
- {refactorai_cli-0.2.19 → refactorai_cli-0.2.21}/pyproject.toml +1 -1
- {refactorai_cli-0.2.19 → refactorai_cli-0.2.21}/refactorai_cli/__init__.py +1 -1
- {refactorai_cli-0.2.19 → refactorai_cli-0.2.21}/refactorai_cli/commands/run_cmds.py +163 -4
- {refactorai_cli-0.2.19 → refactorai_cli-0.2.21}/refactorai_cli.egg-info/PKG-INFO +2 -2
- {refactorai_cli-0.2.19 → refactorai_cli-0.2.21}/refactorai_cli/auth.py +0 -0
- {refactorai_cli-0.2.19 → refactorai_cli-0.2.21}/refactorai_cli/client.py +0 -0
- {refactorai_cli-0.2.19 → refactorai_cli-0.2.21}/refactorai_cli/commands/__init__.py +0 -0
- {refactorai_cli-0.2.19 → refactorai_cli-0.2.21}/refactorai_cli/commands/auth_cmds.py +0 -0
- {refactorai_cli-0.2.19 → refactorai_cli-0.2.21}/refactorai_cli/commands/engine_cmds.py +0 -0
- {refactorai_cli-0.2.19 → refactorai_cli-0.2.21}/refactorai_cli/commands/model_cmds.py +0 -0
- {refactorai_cli-0.2.19 → refactorai_cli-0.2.21}/refactorai_cli/commands/rules_cmds.py +0 -0
- {refactorai_cli-0.2.19 → refactorai_cli-0.2.21}/refactorai_cli/commands/runtime_cmds.py +0 -0
- {refactorai_cli-0.2.19 → refactorai_cli-0.2.21}/refactorai_cli/commands/runtime_proxy_cmds.py +0 -0
- {refactorai_cli-0.2.19 → refactorai_cli-0.2.21}/refactorai_cli/commands/setup_cmds.py +0 -0
- {refactorai_cli-0.2.19 → refactorai_cli-0.2.21}/refactorai_cli/control_plane.py +0 -0
- {refactorai_cli-0.2.19 → refactorai_cli-0.2.21}/refactorai_cli/credentials.py +0 -0
- {refactorai_cli-0.2.19 → refactorai_cli-0.2.21}/refactorai_cli/local_constitution.py +0 -0
- {refactorai_cli-0.2.19 → refactorai_cli-0.2.21}/refactorai_cli/local_engine_runtime.py +0 -0
- {refactorai_cli-0.2.19 → refactorai_cli-0.2.21}/refactorai_cli/local_paths.py +0 -0
- {refactorai_cli-0.2.19 → refactorai_cli-0.2.21}/refactorai_cli/main.py +0 -0
- {refactorai_cli-0.2.19 → refactorai_cli-0.2.21}/refactorai_cli/model_policy.py +0 -0
- {refactorai_cli-0.2.19 → refactorai_cli-0.2.21}/refactorai_cli/runtime_manager.py +0 -0
- {refactorai_cli-0.2.19 → refactorai_cli-0.2.21}/refactorai_cli/settings.py +0 -0
- {refactorai_cli-0.2.19 → refactorai_cli-0.2.21}/refactorai_cli/setup_flow.py +0 -0
- {refactorai_cli-0.2.19 → refactorai_cli-0.2.21}/refactorai_cli.egg-info/SOURCES.txt +0 -0
- {refactorai_cli-0.2.19 → refactorai_cli-0.2.21}/refactorai_cli.egg-info/dependency_links.txt +0 -0
- {refactorai_cli-0.2.19 → refactorai_cli-0.2.21}/refactorai_cli.egg-info/entry_points.txt +0 -0
- {refactorai_cli-0.2.19 → refactorai_cli-0.2.21}/refactorai_cli.egg-info/requires.txt +0 -0
- {refactorai_cli-0.2.19 → refactorai_cli-0.2.21}/refactorai_cli.egg-info/top_level.txt +0 -0
- {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.
|
|
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.
|
|
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.
|
|
45
|
+
python -m pip install ./refactorai-cli/dist/refactorai_cli-0.2.21-py3-none-any.whl
|
|
46
46
|
refactor --version
|
|
47
47
|
```
|
|
@@ -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
|
-
|
|
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=
|
|
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
|
-
|
|
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=
|
|
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.
|
|
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.
|
|
56
|
+
python -m pip install ./refactorai-cli/dist/refactorai_cli-0.2.21-py3-none-any.whl
|
|
57
57
|
refactor --version
|
|
58
58
|
```
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{refactorai_cli-0.2.19 → refactorai_cli-0.2.21}/refactorai_cli/commands/runtime_proxy_cmds.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{refactorai_cli-0.2.19 → refactorai_cli-0.2.21}/refactorai_cli.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|