refactorai-cli 0.2.16__tar.gz → 0.2.17__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.16 → refactorai_cli-0.2.17}/PKG-INFO +1 -1
- {refactorai_cli-0.2.16 → refactorai_cli-0.2.17}/pyproject.toml +1 -1
- {refactorai_cli-0.2.16 → refactorai_cli-0.2.17}/refactorai_cli/__init__.py +1 -1
- {refactorai_cli-0.2.16 → refactorai_cli-0.2.17}/refactorai_cli/commands/run_cmds.py +485 -2
- {refactorai_cli-0.2.16 → refactorai_cli-0.2.17}/refactorai_cli/commands/runtime_proxy_cmds.py +5 -0
- {refactorai_cli-0.2.16 → refactorai_cli-0.2.17}/refactorai_cli/main.py +1 -0
- {refactorai_cli-0.2.16 → refactorai_cli-0.2.17}/refactorai_cli/setup_flow.py +21 -1
- {refactorai_cli-0.2.16 → refactorai_cli-0.2.17}/refactorai_cli.egg-info/PKG-INFO +1 -1
- {refactorai_cli-0.2.16 → refactorai_cli-0.2.17}/README.md +0 -0
- {refactorai_cli-0.2.16 → refactorai_cli-0.2.17}/refactorai_cli/auth.py +0 -0
- {refactorai_cli-0.2.16 → refactorai_cli-0.2.17}/refactorai_cli/client.py +0 -0
- {refactorai_cli-0.2.16 → refactorai_cli-0.2.17}/refactorai_cli/commands/__init__.py +0 -0
- {refactorai_cli-0.2.16 → refactorai_cli-0.2.17}/refactorai_cli/commands/auth_cmds.py +0 -0
- {refactorai_cli-0.2.16 → refactorai_cli-0.2.17}/refactorai_cli/commands/engine_cmds.py +0 -0
- {refactorai_cli-0.2.16 → refactorai_cli-0.2.17}/refactorai_cli/commands/model_cmds.py +0 -0
- {refactorai_cli-0.2.16 → refactorai_cli-0.2.17}/refactorai_cli/commands/rules_cmds.py +0 -0
- {refactorai_cli-0.2.16 → refactorai_cli-0.2.17}/refactorai_cli/commands/runtime_cmds.py +0 -0
- {refactorai_cli-0.2.16 → refactorai_cli-0.2.17}/refactorai_cli/commands/setup_cmds.py +0 -0
- {refactorai_cli-0.2.16 → refactorai_cli-0.2.17}/refactorai_cli/control_plane.py +0 -0
- {refactorai_cli-0.2.16 → refactorai_cli-0.2.17}/refactorai_cli/credentials.py +0 -0
- {refactorai_cli-0.2.16 → refactorai_cli-0.2.17}/refactorai_cli/local_constitution.py +0 -0
- {refactorai_cli-0.2.16 → refactorai_cli-0.2.17}/refactorai_cli/local_engine_runtime.py +0 -0
- {refactorai_cli-0.2.16 → refactorai_cli-0.2.17}/refactorai_cli/local_paths.py +0 -0
- {refactorai_cli-0.2.16 → refactorai_cli-0.2.17}/refactorai_cli/model_policy.py +0 -0
- {refactorai_cli-0.2.16 → refactorai_cli-0.2.17}/refactorai_cli/runtime_manager.py +0 -0
- {refactorai_cli-0.2.16 → refactorai_cli-0.2.17}/refactorai_cli/settings.py +0 -0
- {refactorai_cli-0.2.16 → refactorai_cli-0.2.17}/refactorai_cli.egg-info/SOURCES.txt +0 -0
- {refactorai_cli-0.2.16 → refactorai_cli-0.2.17}/refactorai_cli.egg-info/dependency_links.txt +0 -0
- {refactorai_cli-0.2.16 → refactorai_cli-0.2.17}/refactorai_cli.egg-info/entry_points.txt +0 -0
- {refactorai_cli-0.2.16 → refactorai_cli-0.2.17}/refactorai_cli.egg-info/requires.txt +0 -0
- {refactorai_cli-0.2.16 → refactorai_cli-0.2.17}/refactorai_cli.egg-info/top_level.txt +0 -0
- {refactorai_cli-0.2.16 → refactorai_cli-0.2.17}/setup.cfg +0 -0
|
@@ -35,6 +35,10 @@ from refactor_core.constitution import (
|
|
|
35
35
|
raw_secret_matches,
|
|
36
36
|
raw_secret_matches_config,
|
|
37
37
|
)
|
|
38
|
+
from refactor_core.compliance_settings import resolve_compliance_selection
|
|
39
|
+
from refactor_core.compliance.loader import CompliancePackError, resolve_compliance_packs
|
|
40
|
+
from refactor_core.compliance.models import CompliancePackResolution
|
|
41
|
+
from refactor_core.compliance.evidence import build_compliance_evidence_bundle, evaluate_compliance_gate
|
|
38
42
|
from refactor_core.apply import (
|
|
39
43
|
ApplyError,
|
|
40
44
|
apply_changes,
|
|
@@ -661,6 +665,35 @@ def _warn_raw_key(constitution, config) -> None:
|
|
|
661
665
|
)
|
|
662
666
|
|
|
663
667
|
|
|
668
|
+
def _resolve_compliance_context(
|
|
669
|
+
*,
|
|
670
|
+
project_config,
|
|
671
|
+
cli_frameworks: list[str] | None = None,
|
|
672
|
+
cli_level: str | None = None,
|
|
673
|
+
cli_rollout: str | None = None,
|
|
674
|
+
cli_canary_percent: int | None = None,
|
|
675
|
+
) -> tuple[tuple[str, ...], str, CompliancePackResolution, str, int]:
|
|
676
|
+
"""Resolve compliance selection + managed pack set for this run."""
|
|
677
|
+
selection = resolve_compliance_selection(
|
|
678
|
+
dict(getattr(project_config, "settings", {}) or {}),
|
|
679
|
+
cli_frameworks=cli_frameworks,
|
|
680
|
+
cli_level=cli_level,
|
|
681
|
+
cli_rollout=cli_rollout,
|
|
682
|
+
cli_canary_percent=cli_canary_percent,
|
|
683
|
+
)
|
|
684
|
+
resolution = resolve_compliance_packs(
|
|
685
|
+
selection.frameworks,
|
|
686
|
+
level=selection.level,
|
|
687
|
+
)
|
|
688
|
+
return (
|
|
689
|
+
selection.frameworks,
|
|
690
|
+
selection.level,
|
|
691
|
+
resolution,
|
|
692
|
+
selection.rollout,
|
|
693
|
+
selection.canary_percent,
|
|
694
|
+
)
|
|
695
|
+
|
|
696
|
+
|
|
664
697
|
def init(
|
|
665
698
|
force: bool = typer.Option(
|
|
666
699
|
False,
|
|
@@ -1481,10 +1514,61 @@ def review(
|
|
|
1481
1514
|
"--force",
|
|
1482
1515
|
help="Run review even if unapplied patches exist from a prior code run.",
|
|
1483
1516
|
),
|
|
1517
|
+
compliance_framework: list[str] | None = typer.Option(
|
|
1518
|
+
None,
|
|
1519
|
+
"--compliance-framework",
|
|
1520
|
+
help="Compliance framework to enable for this run. Repeat for multiple values.",
|
|
1521
|
+
),
|
|
1522
|
+
compliance_level: str | None = typer.Option(
|
|
1523
|
+
None,
|
|
1524
|
+
"--compliance-level",
|
|
1525
|
+
help="Compliance level override: off | advisory | standard | strict.",
|
|
1526
|
+
),
|
|
1527
|
+
compliance_rollout: str | None = typer.Option(
|
|
1528
|
+
None,
|
|
1529
|
+
"--compliance-rollout",
|
|
1530
|
+
help="Compliance rollout mode: default | staged | canary | off | ga.",
|
|
1531
|
+
),
|
|
1532
|
+
compliance_canary_percent: int | None = typer.Option(
|
|
1533
|
+
None,
|
|
1534
|
+
"--compliance-canary-percent",
|
|
1535
|
+
help="Compliance canary percentage (0-100) when rollout=canary.",
|
|
1536
|
+
),
|
|
1484
1537
|
) -> None:
|
|
1485
1538
|
"""Run review (findings only) and store the result. Requires a valid key."""
|
|
1486
1539
|
_require_auth()
|
|
1487
1540
|
project_root, constitution, project_config = _load_project()
|
|
1541
|
+
try:
|
|
1542
|
+
(
|
|
1543
|
+
compliance_frameworks,
|
|
1544
|
+
resolved_compliance_level,
|
|
1545
|
+
compliance_resolution,
|
|
1546
|
+
compliance_rollout_mode,
|
|
1547
|
+
compliance_canary_pct,
|
|
1548
|
+
) = (
|
|
1549
|
+
_resolve_compliance_context(
|
|
1550
|
+
project_config=project_config,
|
|
1551
|
+
cli_frameworks=compliance_framework,
|
|
1552
|
+
cli_level=compliance_level,
|
|
1553
|
+
cli_rollout=compliance_rollout,
|
|
1554
|
+
cli_canary_percent=compliance_canary_percent,
|
|
1555
|
+
)
|
|
1556
|
+
)
|
|
1557
|
+
except CompliancePackError as exc:
|
|
1558
|
+
console.print(f"[red]review: compliance pack resolution failed:[/red] {exc}")
|
|
1559
|
+
raise typer.Exit(code=1) from exc
|
|
1560
|
+
if compliance_frameworks:
|
|
1561
|
+
pack_label = ", ".join(
|
|
1562
|
+
f"{pack.pack_id}@{pack.version}" for pack in compliance_resolution.packs
|
|
1563
|
+
) or "(none loaded)"
|
|
1564
|
+
console.print(
|
|
1565
|
+
"[dim]review: compliance selection "
|
|
1566
|
+
f"frameworks={list(compliance_frameworks)} level={resolved_compliance_level} "
|
|
1567
|
+
f"rollout={compliance_rollout_mode} canary_percent={compliance_canary_pct} "
|
|
1568
|
+
f"packs={pack_label}[/dim]"
|
|
1569
|
+
)
|
|
1570
|
+
for warning in compliance_resolution.warnings:
|
|
1571
|
+
console.print(f"[yellow]review: compliance warning:[/yellow] {warning}")
|
|
1488
1572
|
register_project(project_root, constitution_hash=constitution.content_hash)
|
|
1489
1573
|
_maybe_build_graph(project_root, project_config)
|
|
1490
1574
|
sandbox_ctx = None
|
|
@@ -1562,8 +1646,34 @@ def review(
|
|
|
1562
1646
|
project_root=project_root,
|
|
1563
1647
|
findings=artifact.findings,
|
|
1564
1648
|
run_id=artifact.run_id,
|
|
1649
|
+
compliance_frameworks=compliance_frameworks,
|
|
1650
|
+
compliance_level=resolved_compliance_level,
|
|
1651
|
+
compliance_rollout=compliance_rollout_mode,
|
|
1652
|
+
compliance_canary_percent=compliance_canary_pct,
|
|
1653
|
+
compliance_resolution=compliance_resolution,
|
|
1565
1654
|
artifact=artifact,
|
|
1566
1655
|
)
|
|
1656
|
+
_apply_compliance_gate_to_artifact(artifact)
|
|
1657
|
+
run_store.write_artifact(artifact, target=target, status="completed")
|
|
1658
|
+
if compliance_frameworks:
|
|
1659
|
+
artifact.notes.append(
|
|
1660
|
+
"Compliance selection: frameworks="
|
|
1661
|
+
+ ",".join(compliance_frameworks)
|
|
1662
|
+
+ (
|
|
1663
|
+
f" level={resolved_compliance_level} rollout={compliance_rollout_mode} "
|
|
1664
|
+
f"canary_percent={compliance_canary_pct}"
|
|
1665
|
+
)
|
|
1666
|
+
)
|
|
1667
|
+
if compliance_resolution.packs:
|
|
1668
|
+
artifact.notes.append(
|
|
1669
|
+
"Compliance packs: "
|
|
1670
|
+
+ ", ".join(
|
|
1671
|
+
f"{pack.pack_id}@{pack.version}"
|
|
1672
|
+
for pack in compliance_resolution.packs
|
|
1673
|
+
)
|
|
1674
|
+
)
|
|
1675
|
+
for warning in compliance_resolution.warnings:
|
|
1676
|
+
artifact.notes.append(f"Compliance pack warning: {warning}")
|
|
1567
1677
|
if requests:
|
|
1568
1678
|
console.print(
|
|
1569
1679
|
f"\n[bold]Created {len(requests)} refactor request(s).[/bold] "
|
|
@@ -1608,6 +1718,26 @@ def code(
|
|
|
1608
1718
|
"--equivalence",
|
|
1609
1719
|
help="Equivalence policy: strict | balanced.",
|
|
1610
1720
|
),
|
|
1721
|
+
compliance_framework: list[str] | None = typer.Option(
|
|
1722
|
+
None,
|
|
1723
|
+
"--compliance-framework",
|
|
1724
|
+
help="Compliance framework to enable for this run. Repeat for multiple values.",
|
|
1725
|
+
),
|
|
1726
|
+
compliance_level: str | None = typer.Option(
|
|
1727
|
+
None,
|
|
1728
|
+
"--compliance-level",
|
|
1729
|
+
help="Compliance level override: off | advisory | standard | strict.",
|
|
1730
|
+
),
|
|
1731
|
+
compliance_rollout: str | None = typer.Option(
|
|
1732
|
+
None,
|
|
1733
|
+
"--compliance-rollout",
|
|
1734
|
+
help="Compliance rollout mode: default | staged | canary | off | ga.",
|
|
1735
|
+
),
|
|
1736
|
+
compliance_canary_percent: int | None = typer.Option(
|
|
1737
|
+
None,
|
|
1738
|
+
"--compliance-canary-percent",
|
|
1739
|
+
help="Compliance canary percentage (0-100) when rollout=canary.",
|
|
1740
|
+
),
|
|
1611
1741
|
) -> None:
|
|
1612
1742
|
"""Work through refactor requests one by one, applying & verifying each.
|
|
1613
1743
|
|
|
@@ -1616,6 +1746,37 @@ def code(
|
|
|
1616
1746
|
"""
|
|
1617
1747
|
_require_auth()
|
|
1618
1748
|
project_root, constitution, project_config = _load_project()
|
|
1749
|
+
try:
|
|
1750
|
+
(
|
|
1751
|
+
compliance_frameworks,
|
|
1752
|
+
resolved_compliance_level,
|
|
1753
|
+
compliance_resolution,
|
|
1754
|
+
compliance_rollout_mode,
|
|
1755
|
+
compliance_canary_pct,
|
|
1756
|
+
) = (
|
|
1757
|
+
_resolve_compliance_context(
|
|
1758
|
+
project_config=project_config,
|
|
1759
|
+
cli_frameworks=compliance_framework,
|
|
1760
|
+
cli_level=compliance_level,
|
|
1761
|
+
cli_rollout=compliance_rollout,
|
|
1762
|
+
cli_canary_percent=compliance_canary_percent,
|
|
1763
|
+
)
|
|
1764
|
+
)
|
|
1765
|
+
except CompliancePackError as exc:
|
|
1766
|
+
console.print(f"[red]code: compliance pack resolution failed:[/red] {exc}")
|
|
1767
|
+
raise typer.Exit(code=1) from exc
|
|
1768
|
+
if compliance_frameworks:
|
|
1769
|
+
pack_label = ", ".join(
|
|
1770
|
+
f"{pack.pack_id}@{pack.version}" for pack in compliance_resolution.packs
|
|
1771
|
+
) or "(none loaded)"
|
|
1772
|
+
console.print(
|
|
1773
|
+
"[dim]code: compliance selection "
|
|
1774
|
+
f"frameworks={list(compliance_frameworks)} level={resolved_compliance_level} "
|
|
1775
|
+
f"rollout={compliance_rollout_mode} canary_percent={compliance_canary_pct} "
|
|
1776
|
+
f"packs={pack_label}[/dim]"
|
|
1777
|
+
)
|
|
1778
|
+
for warning in compliance_resolution.warnings:
|
|
1779
|
+
console.print(f"[yellow]code: compliance warning:[/yellow] {warning}")
|
|
1619
1780
|
register_project(project_root, constitution_hash=constitution.content_hash)
|
|
1620
1781
|
_maybe_build_graph(project_root, project_config)
|
|
1621
1782
|
sandbox_ctx = None
|
|
@@ -1676,8 +1837,34 @@ def code(
|
|
|
1676
1837
|
project_root=project_root,
|
|
1677
1838
|
findings=review_artifact.findings,
|
|
1678
1839
|
run_id=review_artifact.run_id,
|
|
1840
|
+
compliance_frameworks=compliance_frameworks,
|
|
1841
|
+
compliance_level=resolved_compliance_level,
|
|
1842
|
+
compliance_rollout=compliance_rollout_mode,
|
|
1843
|
+
compliance_canary_percent=compliance_canary_pct,
|
|
1844
|
+
compliance_resolution=compliance_resolution,
|
|
1679
1845
|
artifact=review_artifact,
|
|
1680
1846
|
)
|
|
1847
|
+
_apply_compliance_gate_to_artifact(review_artifact)
|
|
1848
|
+
if compliance_frameworks:
|
|
1849
|
+
review_artifact.notes.append(
|
|
1850
|
+
"Compliance selection: frameworks="
|
|
1851
|
+
+ ",".join(compliance_frameworks)
|
|
1852
|
+
+ (
|
|
1853
|
+
f" level={resolved_compliance_level} rollout={compliance_rollout_mode} "
|
|
1854
|
+
f"canary_percent={compliance_canary_pct}"
|
|
1855
|
+
)
|
|
1856
|
+
)
|
|
1857
|
+
if compliance_resolution.packs:
|
|
1858
|
+
review_artifact.notes.append(
|
|
1859
|
+
"Compliance packs: "
|
|
1860
|
+
+ ", ".join(
|
|
1861
|
+
f"{pack.pack_id}@{pack.version}"
|
|
1862
|
+
for pack in compliance_resolution.packs
|
|
1863
|
+
)
|
|
1864
|
+
)
|
|
1865
|
+
for warning in compliance_resolution.warnings:
|
|
1866
|
+
review_artifact.notes.append(f"Compliance pack warning: {warning}")
|
|
1867
|
+
review_store.write_artifact(review_artifact, target=target, status="completed")
|
|
1681
1868
|
console.print(f"[bold]Created {len(requests)} refactor request(s).[/bold]")
|
|
1682
1869
|
|
|
1683
1870
|
if not requests:
|
|
@@ -1826,6 +2013,204 @@ def requests(
|
|
|
1826
2013
|
console.print(table)
|
|
1827
2014
|
|
|
1828
2015
|
|
|
2016
|
+
def compliance(
|
|
2017
|
+
action: str = typer.Argument(
|
|
2018
|
+
"show",
|
|
2019
|
+
help="Compliance view action: status | show | coverage | explain | export",
|
|
2020
|
+
),
|
|
2021
|
+
subject: str = typer.Argument(
|
|
2022
|
+
"",
|
|
2023
|
+
help="Rule ID when action=explain.",
|
|
2024
|
+
),
|
|
2025
|
+
compliance_framework: list[str] | None = typer.Option(
|
|
2026
|
+
None,
|
|
2027
|
+
"--compliance-framework",
|
|
2028
|
+
help="Compliance framework selection override. Repeat for multiple values.",
|
|
2029
|
+
),
|
|
2030
|
+
compliance_level: str | None = typer.Option(
|
|
2031
|
+
None,
|
|
2032
|
+
"--compliance-level",
|
|
2033
|
+
help="Compliance level override: off | advisory | standard | strict.",
|
|
2034
|
+
),
|
|
2035
|
+
compliance_rollout: str | None = typer.Option(
|
|
2036
|
+
None,
|
|
2037
|
+
"--compliance-rollout",
|
|
2038
|
+
help="Compliance rollout mode override: default | staged | canary | off | ga.",
|
|
2039
|
+
),
|
|
2040
|
+
compliance_canary_percent: int | None = typer.Option(
|
|
2041
|
+
None,
|
|
2042
|
+
"--compliance-canary-percent",
|
|
2043
|
+
help="Compliance canary percentage override when rollout=canary.",
|
|
2044
|
+
),
|
|
2045
|
+
output: str = typer.Option(
|
|
2046
|
+
"",
|
|
2047
|
+
"--output",
|
|
2048
|
+
help="Output file path for action=export (defaults to latest run directory).",
|
|
2049
|
+
),
|
|
2050
|
+
) -> None:
|
|
2051
|
+
"""Show/export compliance packs, coverage, and rule details."""
|
|
2052
|
+
_require_auth()
|
|
2053
|
+
_project_root, _constitution, project_config = _load_project()
|
|
2054
|
+
frameworks, level, resolution, rollout_mode, canary_percent = _resolve_compliance_context(
|
|
2055
|
+
project_config=project_config,
|
|
2056
|
+
cli_frameworks=compliance_framework,
|
|
2057
|
+
cli_level=compliance_level,
|
|
2058
|
+
cli_rollout=compliance_rollout,
|
|
2059
|
+
cli_canary_percent=compliance_canary_percent,
|
|
2060
|
+
)
|
|
2061
|
+
mode = str(action or "show").strip().lower()
|
|
2062
|
+
if mode not in {"status", "show", "coverage", "explain", "export"}:
|
|
2063
|
+
console.print(f"[red]Unknown compliance action:[/red] {action}")
|
|
2064
|
+
raise typer.Exit(code=2)
|
|
2065
|
+
if not frameworks:
|
|
2066
|
+
console.print(
|
|
2067
|
+
"[dim]No compliance frameworks selected. "
|
|
2068
|
+
"Set `compliance.frameworks` in refactor.config or pass --compliance-framework.[/dim]"
|
|
2069
|
+
)
|
|
2070
|
+
return
|
|
2071
|
+
|
|
2072
|
+
if mode == "status":
|
|
2073
|
+
run_dir = head_run_dir(_project_root)
|
|
2074
|
+
report = read_report(run_dir) if run_dir else {}
|
|
2075
|
+
summary = (
|
|
2076
|
+
dict((report or {}).get("compliance_summary", {}) or {})
|
|
2077
|
+
if isinstance(report, dict)
|
|
2078
|
+
else {}
|
|
2079
|
+
)
|
|
2080
|
+
gate = dict((report or {}).get("gate", {}) or {}) if isinstance(report, dict) else {}
|
|
2081
|
+
console.print("[bold]Compliance status[/bold]")
|
|
2082
|
+
console.print(
|
|
2083
|
+
f" selection: frameworks={list(frameworks)} level={level} "
|
|
2084
|
+
f"rollout={rollout_mode} canary_percent={canary_percent}"
|
|
2085
|
+
)
|
|
2086
|
+
table = Table(title="Compliance packs", show_lines=False)
|
|
2087
|
+
table.add_column("pack_id", no_wrap=True)
|
|
2088
|
+
table.add_column("framework", no_wrap=True)
|
|
2089
|
+
table.add_column("version", no_wrap=True)
|
|
2090
|
+
table.add_column("rules", no_wrap=True)
|
|
2091
|
+
for pack in resolution.packs:
|
|
2092
|
+
table.add_row(pack.pack_id, pack.framework, pack.version, str(len(pack.rules)))
|
|
2093
|
+
console.print(table)
|
|
2094
|
+
if not run_dir:
|
|
2095
|
+
console.print(
|
|
2096
|
+
"[yellow]No runs yet.[/yellow] Run `refactor review` to produce compliance evidence."
|
|
2097
|
+
)
|
|
2098
|
+
return
|
|
2099
|
+
run_id = str((report or {}).get("run_id", run_dir.name))
|
|
2100
|
+
created = str((report or {}).get("created_at", ""))
|
|
2101
|
+
gate_status = str(gate.get("status", "unknown"))
|
|
2102
|
+
projected = int(summary.get("projected_count", 0) or 0)
|
|
2103
|
+
blocked = int(summary.get("blocked_count", 0) or 0)
|
|
2104
|
+
warned = int(summary.get("warned_count", 0) or 0)
|
|
2105
|
+
gate_color = "green" if gate_status == "passed" else "red"
|
|
2106
|
+
console.print(
|
|
2107
|
+
f" latest_run: {run_id}" + (f" ({created})" if created else "")
|
|
2108
|
+
)
|
|
2109
|
+
console.print(
|
|
2110
|
+
f" gate: [{gate_color}]{gate_status}[/{gate_color}] "
|
|
2111
|
+
f"projected={projected} blocked={blocked} warned={warned}"
|
|
2112
|
+
)
|
|
2113
|
+
if blocked > 0 and level in {"standard", "strict"}:
|
|
2114
|
+
console.print(
|
|
2115
|
+
"[yellow]next step:[/yellow] resolve blocked controls or run with "
|
|
2116
|
+
"`--compliance-rollout staged` during pilot."
|
|
2117
|
+
)
|
|
2118
|
+
elif projected == 0:
|
|
2119
|
+
console.print(
|
|
2120
|
+
"[dim]No compliance-projected controls were triggered in the latest run.[/dim]"
|
|
2121
|
+
)
|
|
2122
|
+
return
|
|
2123
|
+
|
|
2124
|
+
if mode == "show":
|
|
2125
|
+
console.print(
|
|
2126
|
+
"[bold]Compliance selection[/bold]: "
|
|
2127
|
+
f"frameworks={list(frameworks)} level={level} "
|
|
2128
|
+
f"rollout={rollout_mode} canary_percent={canary_percent}"
|
|
2129
|
+
)
|
|
2130
|
+
table = Table(title="Compliance packs", show_lines=False)
|
|
2131
|
+
table.add_column("pack_id", no_wrap=True)
|
|
2132
|
+
table.add_column("framework", no_wrap=True)
|
|
2133
|
+
table.add_column("version", no_wrap=True)
|
|
2134
|
+
table.add_column("rules", no_wrap=True)
|
|
2135
|
+
table.add_column("depends_on")
|
|
2136
|
+
for pack in resolution.packs:
|
|
2137
|
+
table.add_row(
|
|
2138
|
+
pack.pack_id,
|
|
2139
|
+
pack.framework,
|
|
2140
|
+
pack.version,
|
|
2141
|
+
str(len(pack.rules)),
|
|
2142
|
+
", ".join(pack.depends_on) if pack.depends_on else "-",
|
|
2143
|
+
)
|
|
2144
|
+
console.print(table)
|
|
2145
|
+
for warning in resolution.warnings:
|
|
2146
|
+
console.print(f"[yellow]warning:[/yellow] {warning}")
|
|
2147
|
+
return
|
|
2148
|
+
|
|
2149
|
+
if mode == "export":
|
|
2150
|
+
run_dir = head_run_dir(_project_root)
|
|
2151
|
+
if not run_dir:
|
|
2152
|
+
console.print("[yellow]No runs available to export compliance evidence.[/yellow]")
|
|
2153
|
+
raise typer.Exit(code=1)
|
|
2154
|
+
report = read_report(run_dir) or {}
|
|
2155
|
+
payload = build_compliance_evidence_bundle(report)
|
|
2156
|
+
destination = Path(output).expanduser() if output.strip() else (run_dir / "compliance-evidence.json")
|
|
2157
|
+
try:
|
|
2158
|
+
destination.parent.mkdir(parents=True, exist_ok=True)
|
|
2159
|
+
destination.write_text(json.dumps(payload, indent=2, sort_keys=True), encoding="utf-8")
|
|
2160
|
+
except OSError as exc:
|
|
2161
|
+
console.print(f"[red]Failed to write compliance evidence:[/red] {exc}")
|
|
2162
|
+
raise typer.Exit(code=1) from exc
|
|
2163
|
+
console.print(f"[green]Compliance evidence exported:[/green] {destination}")
|
|
2164
|
+
return
|
|
2165
|
+
|
|
2166
|
+
if mode == "coverage":
|
|
2167
|
+
table = Table(title="Compliance coverage", show_lines=False)
|
|
2168
|
+
table.add_column("framework", no_wrap=True)
|
|
2169
|
+
table.add_column("control", no_wrap=True)
|
|
2170
|
+
table.add_column("classification", no_wrap=True)
|
|
2171
|
+
rows = 0
|
|
2172
|
+
for pack in resolution.packs:
|
|
2173
|
+
if not pack.coverage:
|
|
2174
|
+
table.add_row(pack.framework, "-", "unknown")
|
|
2175
|
+
rows += 1
|
|
2176
|
+
continue
|
|
2177
|
+
for control, classification in sorted(pack.coverage.items()):
|
|
2178
|
+
table.add_row(pack.framework, control, classification)
|
|
2179
|
+
rows += 1
|
|
2180
|
+
if rows == 0:
|
|
2181
|
+
console.print("[dim]No coverage metadata available in selected packs.[/dim]")
|
|
2182
|
+
return
|
|
2183
|
+
console.print(table)
|
|
2184
|
+
return
|
|
2185
|
+
|
|
2186
|
+
# explain
|
|
2187
|
+
rule_id = (subject or "").strip()
|
|
2188
|
+
if not rule_id:
|
|
2189
|
+
console.print("[red]Rule id is required for `refactor compliance explain`.[/red]")
|
|
2190
|
+
raise typer.Exit(code=2)
|
|
2191
|
+
needle = rule_id.lower()
|
|
2192
|
+
matches = []
|
|
2193
|
+
for pack in resolution.packs:
|
|
2194
|
+
for rule in pack.rules:
|
|
2195
|
+
if rule.rule_id.lower() == needle or needle in rule.rule_id.lower():
|
|
2196
|
+
matches.append((pack, rule))
|
|
2197
|
+
if not matches:
|
|
2198
|
+
console.print(f"[yellow]No compliance rule found:[/yellow] {rule_id}")
|
|
2199
|
+
raise typer.Exit(code=1)
|
|
2200
|
+
for pack, rule in matches:
|
|
2201
|
+
console.print(
|
|
2202
|
+
f"[bold]{rule.rule_id}[/bold] "
|
|
2203
|
+
f"[dim](pack={pack.pack_id}@{pack.version}, framework={pack.framework})[/dim]"
|
|
2204
|
+
)
|
|
2205
|
+
if rule.source_standard:
|
|
2206
|
+
console.print(f" standard: {rule.source_standard}")
|
|
2207
|
+
if rule.control_objective:
|
|
2208
|
+
console.print(f" objective: {rule.control_objective}")
|
|
2209
|
+
console.print(f" operation_intent: {rule.operation_intent}")
|
|
2210
|
+
console.print(f" base_severity: {rule.base_severity}")
|
|
2211
|
+
console.print(f" priority: {rule.priority}")
|
|
2212
|
+
|
|
2213
|
+
|
|
1829
2214
|
def _format_operation_scope(rr) -> str:
|
|
1830
2215
|
scope = getattr(rr, "scope", None)
|
|
1831
2216
|
if scope is None:
|
|
@@ -1861,6 +2246,11 @@ def _replace_requests_with_framework_guardrails(
|
|
|
1861
2246
|
project_root: Path,
|
|
1862
2247
|
findings: list[Finding],
|
|
1863
2248
|
run_id: str,
|
|
2249
|
+
compliance_frameworks: tuple[str, ...] = (),
|
|
2250
|
+
compliance_level: str = "standard",
|
|
2251
|
+
compliance_rollout: str = "default",
|
|
2252
|
+
compliance_canary_percent: int = 20,
|
|
2253
|
+
compliance_resolution: CompliancePackResolution | None = None,
|
|
1864
2254
|
artifact: RunArtifact | None = None,
|
|
1865
2255
|
) -> list:
|
|
1866
2256
|
clear_requests(project_root)
|
|
@@ -1868,20 +2258,79 @@ def _replace_requests_with_framework_guardrails(
|
|
|
1868
2258
|
project_root,
|
|
1869
2259
|
findings,
|
|
1870
2260
|
run_id=run_id,
|
|
2261
|
+
compliance_frameworks=compliance_frameworks,
|
|
2262
|
+
compliance_level=compliance_level,
|
|
2263
|
+
compliance_rollout=compliance_rollout,
|
|
2264
|
+
compliance_canary_percent=compliance_canary_percent,
|
|
1871
2265
|
return_diagnostics=True,
|
|
1872
2266
|
)
|
|
1873
2267
|
_report_framework_suppressions(suppressions, artifact=artifact)
|
|
1874
2268
|
_report_framework_warnings(warnings, artifact=artifact)
|
|
2269
|
+
if artifact is not None and compliance_frameworks:
|
|
2270
|
+
compliance_suppressed = [
|
|
2271
|
+
item for item in suppressions if str(item.reason_code).startswith("RR_COMPLIANCE_")
|
|
2272
|
+
]
|
|
2273
|
+
compliance_warnings = [
|
|
2274
|
+
item for item in warnings if str(item.reason_code).startswith("RR_COMPLIANCE_")
|
|
2275
|
+
]
|
|
2276
|
+
artifact.compliance_summary = {
|
|
2277
|
+
"level": compliance_level,
|
|
2278
|
+
"frameworks": list(compliance_frameworks),
|
|
2279
|
+
"rollout": compliance_rollout,
|
|
2280
|
+
"canary_percent": int(compliance_canary_percent),
|
|
2281
|
+
"pack_versions": (
|
|
2282
|
+
compliance_resolution.pack_versions if compliance_resolution is not None else {}
|
|
2283
|
+
),
|
|
2284
|
+
"projected_count": len(compliance_suppressed) + len(compliance_warnings),
|
|
2285
|
+
"blocked_count": len(compliance_suppressed),
|
|
2286
|
+
"warned_count": len(compliance_warnings),
|
|
2287
|
+
"rules_triggered": sorted(
|
|
2288
|
+
{
|
|
2289
|
+
item.rule_id
|
|
2290
|
+
for item in [*compliance_suppressed, *compliance_warnings]
|
|
2291
|
+
if str(item.rule_id).strip()
|
|
2292
|
+
}
|
|
2293
|
+
),
|
|
2294
|
+
}
|
|
1875
2295
|
return requests
|
|
1876
2296
|
|
|
1877
2297
|
|
|
2298
|
+
def _apply_compliance_gate_to_artifact(artifact: RunArtifact) -> None:
|
|
2299
|
+
"""Promote compliance blocking decisions into gate outcome (R18 Phase 7)."""
|
|
2300
|
+
summary = dict(getattr(artifact, "compliance_summary", {}) or {})
|
|
2301
|
+
decision = evaluate_compliance_gate(
|
|
2302
|
+
summary,
|
|
2303
|
+
base_gate_status=str(getattr(artifact.gate, "status", "unknown") or "unknown"),
|
|
2304
|
+
)
|
|
2305
|
+
if decision.status == "blocked":
|
|
2306
|
+
artifact.gate.status = "blocked"
|
|
2307
|
+
reasons = list((artifact.gate.blocking_reasons or {}).get("reasons", []))
|
|
2308
|
+
for reason in decision.reasons:
|
|
2309
|
+
if reason not in reasons:
|
|
2310
|
+
reasons.append(reason)
|
|
2311
|
+
artifact.gate.blocking_reasons["reasons"] = reasons
|
|
2312
|
+
|
|
2313
|
+
|
|
1878
2314
|
def _report_framework_suppressions(
|
|
1879
2315
|
suppressions: list[RequestSuppression], *, artifact: RunArtifact | None = None
|
|
1880
2316
|
) -> None:
|
|
1881
2317
|
if not suppressions:
|
|
1882
2318
|
return
|
|
2319
|
+
feasibility = sum(1 for s in suppressions if s.rule_id == "operation_feasibility")
|
|
2320
|
+
compliance = sum(
|
|
2321
|
+
1 for s in suppressions if s.reason_code.startswith("RR_COMPLIANCE_")
|
|
2322
|
+
)
|
|
2323
|
+
framework = len(suppressions) - feasibility - compliance
|
|
2324
|
+
parts: list[str] = []
|
|
2325
|
+
if framework:
|
|
2326
|
+
parts.append(f"{framework} due to framework invariants")
|
|
2327
|
+
if compliance:
|
|
2328
|
+
parts.append(f"{compliance} due to compliance controls")
|
|
2329
|
+
if feasibility:
|
|
2330
|
+
parts.append(f"{feasibility} not executable as written")
|
|
2331
|
+
detail = "; ".join(parts) if parts else "review-stage checks"
|
|
1883
2332
|
console.print(
|
|
1884
|
-
f"[yellow]warning:[/yellow] suppressed {len(suppressions)} finding(s)
|
|
2333
|
+
f"[yellow]warning:[/yellow] suppressed {len(suppressions)} finding(s) ({detail})."
|
|
1885
2334
|
)
|
|
1886
2335
|
for item in suppressions:
|
|
1887
2336
|
console.print(
|
|
@@ -1897,8 +2346,16 @@ def _report_framework_warnings(
|
|
|
1897
2346
|
) -> None:
|
|
1898
2347
|
if not warnings:
|
|
1899
2348
|
return
|
|
2349
|
+
compliance = sum(1 for item in warnings if item.reason_code.startswith("RR_COMPLIANCE_"))
|
|
2350
|
+
framework = len(warnings) - compliance
|
|
2351
|
+
detail_parts: list[str] = []
|
|
2352
|
+
if framework:
|
|
2353
|
+
detail_parts.append(f"{framework} framework")
|
|
2354
|
+
if compliance:
|
|
2355
|
+
detail_parts.append(f"{compliance} compliance")
|
|
2356
|
+
detail = ", ".join(detail_parts) if detail_parts else "review"
|
|
1900
2357
|
console.print(
|
|
1901
|
-
f"[yellow]warning:[/yellow] flagged {len(warnings)} finding(s) with
|
|
2358
|
+
f"[yellow]warning:[/yellow] flagged {len(warnings)} finding(s) with caution ({detail})."
|
|
1902
2359
|
)
|
|
1903
2360
|
for item in warnings:
|
|
1904
2361
|
console.print(
|
|
@@ -2166,6 +2623,11 @@ def gc(
|
|
|
2166
2623
|
|
|
2167
2624
|
def check(
|
|
2168
2625
|
strict: bool = typer.Option(False, "--strict", help="Exit non-zero if issues are found (for CI)."),
|
|
2626
|
+
compliance_gate: bool = typer.Option(
|
|
2627
|
+
False,
|
|
2628
|
+
"--compliance-gate",
|
|
2629
|
+
help="Also enforce latest run compliance gate status (for CI/PR pipelines).",
|
|
2630
|
+
),
|
|
2169
2631
|
) -> None:
|
|
2170
2632
|
"""Check the constitution for committed secrets (developer/provider keys)."""
|
|
2171
2633
|
files = find_project_files()
|
|
@@ -2176,6 +2638,25 @@ def check(
|
|
|
2176
2638
|
constitution = load_constitution(consti_path)
|
|
2177
2639
|
config = load_config(config_path)
|
|
2178
2640
|
findings = raw_secret_matches(constitution) + raw_secret_matches_config(config)
|
|
2641
|
+
compliance_failed = False
|
|
2642
|
+
if compliance_gate:
|
|
2643
|
+
run_dir = head_run_dir(consti_path.parent)
|
|
2644
|
+
if run_dir is None:
|
|
2645
|
+
console.print("[yellow]Compliance gate check skipped:[/yellow] no runs yet.")
|
|
2646
|
+
else:
|
|
2647
|
+
report = read_report(run_dir) or {}
|
|
2648
|
+
decision = evaluate_compliance_gate(
|
|
2649
|
+
report.get("compliance_summary", {}),
|
|
2650
|
+
base_gate_status=str((report.get("gate") or {}).get("status", "unknown")),
|
|
2651
|
+
)
|
|
2652
|
+
if decision.status == "blocked":
|
|
2653
|
+
compliance_failed = True
|
|
2654
|
+
console.print(
|
|
2655
|
+
"[red]Compliance gate blocked[/red]: "
|
|
2656
|
+
+ ", ".join(decision.reasons or ("compliance_controls_blocked",))
|
|
2657
|
+
)
|
|
2658
|
+
else:
|
|
2659
|
+
console.print("[green]Compliance gate[/green]: passed")
|
|
2179
2660
|
if findings:
|
|
2180
2661
|
kinds = ", ".join(sorted({kind for kind, _ in findings}))
|
|
2181
2662
|
console.print(
|
|
@@ -2183,6 +2664,8 @@ def check(
|
|
|
2183
2664
|
f"{consti_path} or {config_path}. Replace secrets with `${{ENV_VAR}}` references."
|
|
2184
2665
|
)
|
|
2185
2666
|
raise typer.Exit(code=1 if strict else 0)
|
|
2667
|
+
if compliance_failed:
|
|
2668
|
+
raise typer.Exit(code=1 if strict else 0)
|
|
2186
2669
|
console.print("[green]OK[/green]: no committed secret-like literals detected.")
|
|
2187
2670
|
|
|
2188
2671
|
|
{refactorai_cli-0.2.16 → refactorai_cli-0.2.17}/refactorai_cli/commands/runtime_proxy_cmds.py
RENAMED
|
@@ -136,3 +136,8 @@ def doctor(ctx: typer.Context) -> None:
|
|
|
136
136
|
def config(ctx: typer.Context) -> None:
|
|
137
137
|
"""Manage config via proprietary runtime."""
|
|
138
138
|
_exec_runtime("config", list(ctx.args))
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def compliance(ctx: typer.Context) -> None:
|
|
142
|
+
"""Inspect compliance packs/coverage via proprietary runtime."""
|
|
143
|
+
_exec_runtime("compliance", list(ctx.args))
|
|
@@ -56,6 +56,7 @@ app.command(context_settings=_RUNTIME_CTX)(runtime_proxy_cmds.gc)
|
|
|
56
56
|
app.command(context_settings=_RUNTIME_CTX)(runtime_proxy_cmds.check)
|
|
57
57
|
app.command(context_settings=_RUNTIME_CTX)(runtime_proxy_cmds.doctor)
|
|
58
58
|
app.command(context_settings=_RUNTIME_CTX)(runtime_proxy_cmds.config)
|
|
59
|
+
app.command(context_settings=_RUNTIME_CTX)(runtime_proxy_cmds.compliance)
|
|
59
60
|
|
|
60
61
|
app.command()(setup_cmds.setup)
|
|
61
62
|
|
|
@@ -10,6 +10,9 @@ from pathlib import Path
|
|
|
10
10
|
from inspect import signature
|
|
11
11
|
from typing import Callable
|
|
12
12
|
|
|
13
|
+
from rich.console import Console
|
|
14
|
+
from rich.progress import BarColumn, DownloadColumn, Progress, TaskID, TextColumn, TimeRemainingColumn, TransferSpeedColumn
|
|
15
|
+
|
|
13
16
|
from refactorai_cli.local_engine_runtime import (
|
|
14
17
|
DEFAULT_ENGINE_CONTAINER,
|
|
15
18
|
DEFAULT_ENGINE_IMAGE,
|
|
@@ -39,6 +42,7 @@ STAGE_NAMES = {
|
|
|
39
42
|
"S5": "backend_bootstrap",
|
|
40
43
|
"S6": "validate",
|
|
41
44
|
}
|
|
45
|
+
_setup_console = Console()
|
|
42
46
|
|
|
43
47
|
|
|
44
48
|
class SetupError(RuntimeError):
|
|
@@ -180,7 +184,23 @@ def _stage_s3_runtime(ask_approval: Callable[[str], bool], emit: Callable[[str],
|
|
|
180
184
|
raise SetupError("Runtime installation was not approved.")
|
|
181
185
|
if emit:
|
|
182
186
|
emit(f"Downloading runtime artifact {manifest.runtime_version}...")
|
|
183
|
-
|
|
187
|
+
progress_columns = (
|
|
188
|
+
TextColumn("[progress.description]{task.description}"),
|
|
189
|
+
BarColumn(),
|
|
190
|
+
DownloadColumn(),
|
|
191
|
+
TransferSpeedColumn(),
|
|
192
|
+
TimeRemainingColumn(),
|
|
193
|
+
)
|
|
194
|
+
with Progress(*progress_columns, console=_setup_console, transient=True) as progress:
|
|
195
|
+
task_id: TaskID = progress.add_task("Downloading runtime artifact", total=None)
|
|
196
|
+
|
|
197
|
+
def _on_progress(downloaded: int, total: int | None) -> None:
|
|
198
|
+
update_kwargs = {"completed": int(max(downloaded, 0))}
|
|
199
|
+
if total is not None and total > 0:
|
|
200
|
+
update_kwargs["total"] = total
|
|
201
|
+
progress.update(task_id, **update_kwargs)
|
|
202
|
+
|
|
203
|
+
artifact = download_artifact(manifest.artifact_url, progress_cb=_on_progress)
|
|
184
204
|
if emit:
|
|
185
205
|
emit("Verifying and activating runtime artifact...")
|
|
186
206
|
path = activate_runtime(manifest, artifact, channel="stable")
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{refactorai_cli-0.2.16 → refactorai_cli-0.2.17}/refactorai_cli.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|