refactorai-cli 0.2.15__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.15 → refactorai_cli-0.2.17}/PKG-INFO +1 -1
- {refactorai_cli-0.2.15 → refactorai_cli-0.2.17}/pyproject.toml +1 -1
- {refactorai_cli-0.2.15 → refactorai_cli-0.2.17}/refactorai_cli/__init__.py +1 -1
- {refactorai_cli-0.2.15 → refactorai_cli-0.2.17}/refactorai_cli/commands/run_cmds.py +550 -6
- {refactorai_cli-0.2.15 → refactorai_cli-0.2.17}/refactorai_cli/commands/runtime_cmds.py +18 -1
- {refactorai_cli-0.2.15 → refactorai_cli-0.2.17}/refactorai_cli/commands/runtime_proxy_cmds.py +5 -0
- {refactorai_cli-0.2.15 → refactorai_cli-0.2.17}/refactorai_cli/main.py +1 -0
- {refactorai_cli-0.2.15 → refactorai_cli-0.2.17}/refactorai_cli/runtime_manager.py +38 -5
- {refactorai_cli-0.2.15 → refactorai_cli-0.2.17}/refactorai_cli/setup_flow.py +21 -1
- {refactorai_cli-0.2.15 → refactorai_cli-0.2.17}/refactorai_cli.egg-info/PKG-INFO +1 -1
- {refactorai_cli-0.2.15 → refactorai_cli-0.2.17}/README.md +0 -0
- {refactorai_cli-0.2.15 → refactorai_cli-0.2.17}/refactorai_cli/auth.py +0 -0
- {refactorai_cli-0.2.15 → refactorai_cli-0.2.17}/refactorai_cli/client.py +0 -0
- {refactorai_cli-0.2.15 → refactorai_cli-0.2.17}/refactorai_cli/commands/__init__.py +0 -0
- {refactorai_cli-0.2.15 → refactorai_cli-0.2.17}/refactorai_cli/commands/auth_cmds.py +0 -0
- {refactorai_cli-0.2.15 → refactorai_cli-0.2.17}/refactorai_cli/commands/engine_cmds.py +0 -0
- {refactorai_cli-0.2.15 → refactorai_cli-0.2.17}/refactorai_cli/commands/model_cmds.py +0 -0
- {refactorai_cli-0.2.15 → refactorai_cli-0.2.17}/refactorai_cli/commands/rules_cmds.py +0 -0
- {refactorai_cli-0.2.15 → refactorai_cli-0.2.17}/refactorai_cli/commands/setup_cmds.py +0 -0
- {refactorai_cli-0.2.15 → refactorai_cli-0.2.17}/refactorai_cli/control_plane.py +0 -0
- {refactorai_cli-0.2.15 → refactorai_cli-0.2.17}/refactorai_cli/credentials.py +0 -0
- {refactorai_cli-0.2.15 → refactorai_cli-0.2.17}/refactorai_cli/local_constitution.py +0 -0
- {refactorai_cli-0.2.15 → refactorai_cli-0.2.17}/refactorai_cli/local_engine_runtime.py +0 -0
- {refactorai_cli-0.2.15 → refactorai_cli-0.2.17}/refactorai_cli/local_paths.py +0 -0
- {refactorai_cli-0.2.15 → refactorai_cli-0.2.17}/refactorai_cli/model_policy.py +0 -0
- {refactorai_cli-0.2.15 → refactorai_cli-0.2.17}/refactorai_cli/settings.py +0 -0
- {refactorai_cli-0.2.15 → refactorai_cli-0.2.17}/refactorai_cli.egg-info/SOURCES.txt +0 -0
- {refactorai_cli-0.2.15 → refactorai_cli-0.2.17}/refactorai_cli.egg-info/dependency_links.txt +0 -0
- {refactorai_cli-0.2.15 → refactorai_cli-0.2.17}/refactorai_cli.egg-info/entry_points.txt +0 -0
- {refactorai_cli-0.2.15 → refactorai_cli-0.2.17}/refactorai_cli.egg-info/requires.txt +0 -0
- {refactorai_cli-0.2.15 → refactorai_cli-0.2.17}/refactorai_cli.egg-info/top_level.txt +0 -0
- {refactorai_cli-0.2.15 → 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,
|
|
@@ -50,6 +54,8 @@ from refactor_core.execution.structural_ops import structural_compatibility_matr
|
|
|
50
54
|
from refactor_core.gate import evaluate_gate
|
|
51
55
|
from refactor_core.refactor_requests import (
|
|
52
56
|
DEFERRED,
|
|
57
|
+
RequestSuppression,
|
|
58
|
+
RequestWarning,
|
|
53
59
|
clear_requests,
|
|
54
60
|
create_requests,
|
|
55
61
|
list_requests,
|
|
@@ -659,6 +665,35 @@ def _warn_raw_key(constitution, config) -> None:
|
|
|
659
665
|
)
|
|
660
666
|
|
|
661
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
|
+
|
|
662
697
|
def init(
|
|
663
698
|
force: bool = typer.Option(
|
|
664
699
|
False,
|
|
@@ -1479,10 +1514,61 @@ def review(
|
|
|
1479
1514
|
"--force",
|
|
1480
1515
|
help="Run review even if unapplied patches exist from a prior code run.",
|
|
1481
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
|
+
),
|
|
1482
1537
|
) -> None:
|
|
1483
1538
|
"""Run review (findings only) and store the result. Requires a valid key."""
|
|
1484
1539
|
_require_auth()
|
|
1485
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}")
|
|
1486
1572
|
register_project(project_root, constitution_hash=constitution.content_hash)
|
|
1487
1573
|
_maybe_build_graph(project_root, project_config)
|
|
1488
1574
|
sandbox_ctx = None
|
|
@@ -1555,9 +1641,39 @@ def review(
|
|
|
1555
1641
|
_print_findings(artifact.findings)
|
|
1556
1642
|
|
|
1557
1643
|
# A fresh review supersedes prior refactor requests: replace them with one
|
|
1558
|
-
# RR per finding (central-only; nothing is written into
|
|
1559
|
-
|
|
1560
|
-
|
|
1644
|
+
# RR per actionable finding (central-only; nothing is written into project tree).
|
|
1645
|
+
requests = _replace_requests_with_framework_guardrails(
|
|
1646
|
+
project_root=project_root,
|
|
1647
|
+
findings=artifact.findings,
|
|
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,
|
|
1654
|
+
artifact=artifact,
|
|
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}")
|
|
1561
1677
|
if requests:
|
|
1562
1678
|
console.print(
|
|
1563
1679
|
f"\n[bold]Created {len(requests)} refactor request(s).[/bold] "
|
|
@@ -1602,6 +1718,26 @@ def code(
|
|
|
1602
1718
|
"--equivalence",
|
|
1603
1719
|
help="Equivalence policy: strict | balanced.",
|
|
1604
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
|
+
),
|
|
1605
1741
|
) -> None:
|
|
1606
1742
|
"""Work through refactor requests one by one, applying & verifying each.
|
|
1607
1743
|
|
|
@@ -1610,6 +1746,37 @@ def code(
|
|
|
1610
1746
|
"""
|
|
1611
1747
|
_require_auth()
|
|
1612
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}")
|
|
1613
1780
|
register_project(project_root, constitution_hash=constitution.content_hash)
|
|
1614
1781
|
_maybe_build_graph(project_root, project_config)
|
|
1615
1782
|
sandbox_ctx = None
|
|
@@ -1666,10 +1833,38 @@ def code(
|
|
|
1666
1833
|
review_store = create_run(project_root, run_id=review_artifact.run_id)
|
|
1667
1834
|
review_store.write_artifact(review_artifact, target=target, status="completed")
|
|
1668
1835
|
_print_findings(review_artifact.findings)
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1836
|
+
requests = _replace_requests_with_framework_guardrails(
|
|
1837
|
+
project_root=project_root,
|
|
1838
|
+
findings=review_artifact.findings,
|
|
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,
|
|
1845
|
+
artifact=review_artifact,
|
|
1672
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")
|
|
1673
1868
|
console.print(f"[bold]Created {len(requests)} refactor request(s).[/bold]")
|
|
1674
1869
|
|
|
1675
1870
|
if not requests:
|
|
@@ -1818,6 +2013,204 @@ def requests(
|
|
|
1818
2013
|
console.print(table)
|
|
1819
2014
|
|
|
1820
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
|
+
|
|
1821
2214
|
def _format_operation_scope(rr) -> str:
|
|
1822
2215
|
scope = getattr(rr, "scope", None)
|
|
1823
2216
|
if scope is None:
|
|
@@ -1848,6 +2241,131 @@ def _format_operation_scope(rr) -> str:
|
|
|
1848
2241
|
return "-"
|
|
1849
2242
|
|
|
1850
2243
|
|
|
2244
|
+
def _replace_requests_with_framework_guardrails(
|
|
2245
|
+
*,
|
|
2246
|
+
project_root: Path,
|
|
2247
|
+
findings: list[Finding],
|
|
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,
|
|
2254
|
+
artifact: RunArtifact | None = None,
|
|
2255
|
+
) -> list:
|
|
2256
|
+
clear_requests(project_root)
|
|
2257
|
+
requests, suppressions, warnings = create_requests(
|
|
2258
|
+
project_root,
|
|
2259
|
+
findings,
|
|
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,
|
|
2265
|
+
return_diagnostics=True,
|
|
2266
|
+
)
|
|
2267
|
+
_report_framework_suppressions(suppressions, artifact=artifact)
|
|
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
|
+
}
|
|
2295
|
+
return requests
|
|
2296
|
+
|
|
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
|
+
|
|
2314
|
+
def _report_framework_suppressions(
|
|
2315
|
+
suppressions: list[RequestSuppression], *, artifact: RunArtifact | None = None
|
|
2316
|
+
) -> None:
|
|
2317
|
+
if not suppressions:
|
|
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"
|
|
2332
|
+
console.print(
|
|
2333
|
+
f"[yellow]warning:[/yellow] suppressed {len(suppressions)} finding(s) ({detail})."
|
|
2334
|
+
)
|
|
2335
|
+
for item in suppressions:
|
|
2336
|
+
console.print(
|
|
2337
|
+
f"[yellow]-[/yellow] {item.location}: {item.message} "
|
|
2338
|
+
f"[dim](framework={item.framework or 'unknown'}, rule={item.rule_id or 'unspecified'})[/dim]"
|
|
2339
|
+
)
|
|
2340
|
+
if artifact is not None:
|
|
2341
|
+
artifact.notes.append(item.note())
|
|
2342
|
+
|
|
2343
|
+
|
|
2344
|
+
def _report_framework_warnings(
|
|
2345
|
+
warnings: list[RequestWarning], *, artifact: RunArtifact | None = None
|
|
2346
|
+
) -> None:
|
|
2347
|
+
if not warnings:
|
|
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"
|
|
2357
|
+
console.print(
|
|
2358
|
+
f"[yellow]warning:[/yellow] flagged {len(warnings)} finding(s) with caution ({detail})."
|
|
2359
|
+
)
|
|
2360
|
+
for item in warnings:
|
|
2361
|
+
console.print(
|
|
2362
|
+
f"[yellow]-[/yellow] {item.location}: {item.message} "
|
|
2363
|
+
f"[dim](framework={item.framework or 'unknown'}, rule={item.rule_id or 'unspecified'})[/dim]"
|
|
2364
|
+
)
|
|
2365
|
+
if artifact is not None:
|
|
2366
|
+
artifact.notes.append(item.note())
|
|
2367
|
+
|
|
2368
|
+
|
|
1851
2369
|
def apply(
|
|
1852
2370
|
force: bool = typer.Option(False, "--force", help="Apply even if the quality gate blocks."),
|
|
1853
2371
|
) -> None:
|
|
@@ -2105,6 +2623,11 @@ def gc(
|
|
|
2105
2623
|
|
|
2106
2624
|
def check(
|
|
2107
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
|
+
),
|
|
2108
2631
|
) -> None:
|
|
2109
2632
|
"""Check the constitution for committed secrets (developer/provider keys)."""
|
|
2110
2633
|
files = find_project_files()
|
|
@@ -2115,6 +2638,25 @@ def check(
|
|
|
2115
2638
|
constitution = load_constitution(consti_path)
|
|
2116
2639
|
config = load_config(config_path)
|
|
2117
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")
|
|
2118
2660
|
if findings:
|
|
2119
2661
|
kinds = ", ".join(sorted({kind for kind, _ in findings}))
|
|
2120
2662
|
console.print(
|
|
@@ -2122,6 +2664,8 @@ def check(
|
|
|
2122
2664
|
f"{consti_path} or {config_path}. Replace secrets with `${{ENV_VAR}}` references."
|
|
2123
2665
|
)
|
|
2124
2666
|
raise typer.Exit(code=1 if strict else 0)
|
|
2667
|
+
if compliance_failed:
|
|
2668
|
+
raise typer.Exit(code=1 if strict else 0)
|
|
2125
2669
|
console.print("[green]OK[/green]: no committed secret-like literals detected.")
|
|
2126
2670
|
|
|
2127
2671
|
|
|
@@ -4,6 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import typer
|
|
6
6
|
from rich.console import Console
|
|
7
|
+
from rich.progress import BarColumn, DownloadColumn, Progress, TaskID, TextColumn, TimeRemainingColumn, TransferSpeedColumn
|
|
7
8
|
|
|
8
9
|
from refactorai_cli.auth import AuthError, ensure_authenticated
|
|
9
10
|
from refactorai_cli.control_plane import ensure_lease, heartbeat, read_cached_lease, resolve_policy
|
|
@@ -62,7 +63,23 @@ def update(
|
|
|
62
63
|
f"[bold]resolved manifest[/bold]: version={manifest.runtime_version} "
|
|
63
64
|
f"channel={channel}"
|
|
64
65
|
)
|
|
65
|
-
|
|
66
|
+
progress_columns = (
|
|
67
|
+
TextColumn("[progress.description]{task.description}"),
|
|
68
|
+
BarColumn(),
|
|
69
|
+
DownloadColumn(),
|
|
70
|
+
TransferSpeedColumn(),
|
|
71
|
+
TimeRemainingColumn(),
|
|
72
|
+
)
|
|
73
|
+
with Progress(*progress_columns, console=console, transient=True) as progress:
|
|
74
|
+
task_id: TaskID = progress.add_task("Downloading runtime artifact", total=None)
|
|
75
|
+
|
|
76
|
+
def _on_progress(downloaded: int, total: int | None) -> None:
|
|
77
|
+
update_kwargs = {"completed": int(max(downloaded, 0))}
|
|
78
|
+
if total is not None and total > 0:
|
|
79
|
+
update_kwargs["total"] = total
|
|
80
|
+
progress.update(task_id, **update_kwargs)
|
|
81
|
+
|
|
82
|
+
artifact = download_artifact(manifest.artifact_url, progress_cb=_on_progress)
|
|
66
83
|
console.print(f"[bold]downloaded artifact[/bold]: {len(artifact)} bytes")
|
|
67
84
|
if dry_run:
|
|
68
85
|
# Verification happens in activate_runtime; dry-run still checks
|
{refactorai_cli-0.2.15 → 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
|
|
|
@@ -14,6 +14,7 @@ import platform
|
|
|
14
14
|
from dataclasses import dataclass
|
|
15
15
|
from datetime import datetime, timezone
|
|
16
16
|
from pathlib import Path
|
|
17
|
+
from typing import Callable
|
|
17
18
|
from urllib.parse import urlparse
|
|
18
19
|
|
|
19
20
|
import httpx
|
|
@@ -153,17 +154,49 @@ def resolve_runtime_manifest(*, channel: str = "stable", timeout: float = 20.0)
|
|
|
153
154
|
return manifest
|
|
154
155
|
|
|
155
156
|
|
|
156
|
-
def download_artifact(
|
|
157
|
+
def download_artifact(
|
|
158
|
+
url: str,
|
|
159
|
+
*,
|
|
160
|
+
timeout: float = 60.0,
|
|
161
|
+
progress_cb: Callable[[int, int | None], None] | None = None,
|
|
162
|
+
) -> bytes:
|
|
157
163
|
headers: dict[str, str] = {}
|
|
158
164
|
if _should_send_runtime_auth(url):
|
|
159
165
|
headers["Authorization"] = _runtime_auth_header()
|
|
166
|
+
resolved_timeout = max(float(timeout), 300.0)
|
|
167
|
+
if progress_cb is None:
|
|
168
|
+
try:
|
|
169
|
+
response = httpx.get(url, headers=headers, timeout=resolved_timeout)
|
|
170
|
+
except httpx.HTTPError as exc:
|
|
171
|
+
raise RuntimeError(f"Could not download runtime artifact: {exc}") from exc
|
|
172
|
+
if response.status_code >= 400:
|
|
173
|
+
raise RuntimeError(f"Runtime artifact download failed ({response.status_code})")
|
|
174
|
+
return response.content
|
|
160
175
|
try:
|
|
161
|
-
|
|
176
|
+
with httpx.stream("GET", url, headers=headers, timeout=resolved_timeout) as response:
|
|
177
|
+
if response.status_code >= 400:
|
|
178
|
+
raise RuntimeError(f"Runtime artifact download failed ({response.status_code})")
|
|
179
|
+
total: int | None = None
|
|
180
|
+
total_header = str(response.headers.get("Content-Length") or "").strip()
|
|
181
|
+
if total_header:
|
|
182
|
+
try:
|
|
183
|
+
parsed_total = int(total_header)
|
|
184
|
+
except ValueError:
|
|
185
|
+
parsed_total = -1
|
|
186
|
+
if parsed_total > 0:
|
|
187
|
+
total = parsed_total
|
|
188
|
+
downloaded = 0
|
|
189
|
+
chunks: list[bytes] = []
|
|
190
|
+
progress_cb(downloaded, total)
|
|
191
|
+
for chunk in response.iter_bytes(chunk_size=64 * 1024):
|
|
192
|
+
if not chunk:
|
|
193
|
+
continue
|
|
194
|
+
chunks.append(chunk)
|
|
195
|
+
downloaded += len(chunk)
|
|
196
|
+
progress_cb(downloaded, total)
|
|
162
197
|
except httpx.HTTPError as exc:
|
|
163
198
|
raise RuntimeError(f"Could not download runtime artifact: {exc}") from exc
|
|
164
|
-
|
|
165
|
-
raise RuntimeError(f"Runtime artifact download failed ({response.status_code})")
|
|
166
|
-
return response.content
|
|
199
|
+
return b"".join(chunks)
|
|
167
200
|
|
|
168
201
|
|
|
169
202
|
def _should_send_runtime_auth(url: str) -> bool:
|
|
@@ -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
|
{refactorai_cli-0.2.15 → 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
|