code-mower 0.5.0b5__py3-none-any.whl
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.
- code_mower/__init__.py +3 -0
- code_mower/adapters/__init__.py +39 -0
- code_mower/adapters/_base.py +148 -0
- code_mower/adapters/cursor_bugbot.py +97 -0
- code_mower/adapters/gitar.py +111 -0
- code_mower/adapters/greptile.py +191 -0
- code_mower/adapters/qodo.py +140 -0
- code_mower/antigravity_cli_audit_pr.py +241 -0
- code_mower/audit_handoff_log.py +345 -0
- code_mower/audit_labeler_lib.py +311 -0
- code_mower/audit_progress.py +203 -0
- code_mower/blind_review_artifacts.py +562 -0
- code_mower/blind_review_coordinator.py +276 -0
- code_mower/bootstrap.py +524 -0
- code_mower/builder_experiment.py +539 -0
- code_mower/calibration/__init__.py +181 -0
- code_mower/calibration/arms.py +267 -0
- code_mower/calibration/auto_discovery.py +348 -0
- code_mower/calibration/commands.py +192 -0
- code_mower/calibration/context_inputs.py +199 -0
- code_mower/calibration/corpus.py +91 -0
- code_mower/calibration/evidence.py +20 -0
- code_mower/calibration/evidence_report.py +360 -0
- code_mower/calibration/identity.py +27 -0
- code_mower/calibration/metrics.py +18 -0
- code_mower/calibration/overlap.py +103 -0
- code_mower/calibration/planning.py +309 -0
- code_mower/calibration/policy.py +207 -0
- code_mower/calibration/results.py +315 -0
- code_mower/calibration/run_results.py +147 -0
- code_mower/calibration/run_status.py +64 -0
- code_mower/calibration/runner.py +360 -0
- code_mower/calibration/truth.py +188 -0
- code_mower/calibration/value_report.py +142 -0
- code_mower/checks.py +402 -0
- code_mower/claude_audit_pr.py +1126 -0
- code_mower/claude_cli_bounce.py +303 -0
- code_mower/claude_cli_environment.py +73 -0
- code_mower/clear_stale.py +374 -0
- code_mower/cli.py +537 -0
- code_mower/cloud.py +674 -0
- code_mower/cloud_client/__init__.py +173 -0
- code_mower/cloud_client/bundle.py +155 -0
- code_mower/cloud_client/doctor.py +206 -0
- code_mower/cloud_client/dogfood.py +78 -0
- code_mower/cloud_client/endpoints.py +114 -0
- code_mower/cloud_client/errors.py +7 -0
- code_mower/cloud_client/events.py +278 -0
- code_mower/cloud_client/export.py +272 -0
- code_mower/cloud_client/git_metadata.py +46 -0
- code_mower/cloud_client/manifest.py +39 -0
- code_mower/cloud_client/operations.py +448 -0
- code_mower/cloud_client/reports.py +45 -0
- code_mower/cloud_client/setup.py +205 -0
- code_mower/cloud_client/upload.py +97 -0
- code_mower/code_mower_calibration.py +598 -0
- code_mower/code_mower_context_packs.py +591 -0
- code_mower/code_mower_merge.py +227 -0
- code_mower/code_mower_telemetry.py +561 -0
- code_mower/coderabbit_cli_audit_pr.py +526 -0
- code_mower/codex_audit_env_preflight.py +220 -0
- code_mower/codex_audit_pr.py +1738 -0
- code_mower/codex_audit_schema_smoke.py +160 -0
- code_mower/codex_audit_verdict.schema.json +44 -0
- code_mower/config.py +655 -0
- code_mower/doctor.py +161 -0
- code_mower/doctor_checks/__init__.py +104 -0
- code_mower/doctor_checks/cloud.py +129 -0
- code_mower/doctor_checks/common.py +215 -0
- code_mower/doctor_checks/github.py +128 -0
- code_mower/doctor_checks/github_actions.py +11 -0
- code_mower/doctor_checks/github_actions_cost.py +99 -0
- code_mower/doctor_checks/github_actions_cost_summary.py +111 -0
- code_mower/doctor_checks/github_actions_failure_annotations.py +27 -0
- code_mower/doctor_checks/github_actions_failure_models.py +47 -0
- code_mower/doctor_checks/github_actions_failure_scan.py +200 -0
- code_mower/doctor_checks/github_actions_failure_selection.py +63 -0
- code_mower/doctor_checks/github_actions_failures.py +103 -0
- code_mower/doctor_checks/github_actions_permissions.py +55 -0
- code_mower/doctor_checks/github_api.py +79 -0
- code_mower/doctor_checks/github_branch.py +56 -0
- code_mower/doctor_checks/github_config.py +25 -0
- code_mower/doctor_checks/github_provider.py +61 -0
- code_mower/doctor_checks/github_repo.py +120 -0
- code_mower/doctor_checks/groups.py +36 -0
- code_mower/doctor_checks/models.py +96 -0
- code_mower/doctor_checks/output.py +86 -0
- code_mower/doctor_checks/presets.py +64 -0
- code_mower/doctor_checks/privacy.py +20 -0
- code_mower/doctor_checks/provider_api_model.py +138 -0
- code_mower/doctor_checks/provider_api_model_openai.py +29 -0
- code_mower/doctor_checks/provider_api_model_profiles.py +137 -0
- code_mower/doctor_checks/provider_env.py +113 -0
- code_mower/doctor_checks/provider_env_required.py +56 -0
- code_mower/doctor_checks/provider_env_tokens.py +100 -0
- code_mower/doctor_checks/provider_local_cli.py +162 -0
- code_mower/doctor_checks/provider_local_cli_commands.py +47 -0
- code_mower/doctor_checks/provider_local_cli_probe_config.py +70 -0
- code_mower/doctor_checks/provider_probe.py +20 -0
- code_mower/doctor_checks/provider_probe_auth.py +52 -0
- code_mower/doctor_checks/provider_probe_evaluation.py +109 -0
- code_mower/doctor_checks/provider_probe_json.py +45 -0
- code_mower/doctor_checks/provider_probe_remediation.py +39 -0
- code_mower/doctor_checks/providers.py +159 -0
- code_mower/doctor_checks/registry.py +69 -0
- code_mower/doctor_checks/runner.py +188 -0
- code_mower/doctor_checks/runtime.py +89 -0
- code_mower/doctor_checks/runtime_github_auth.py +148 -0
- code_mower/gemini_cli_audit_pr.py +897 -0
- code_mower/hermes_cli_audit_pr.py +436 -0
- code_mower/init.py +888 -0
- code_mower/lane_configs/__init__.py +37 -0
- code_mower/lane_configs/aider.py +32 -0
- code_mower/lane_configs/antigravity_cli.py +35 -0
- code_mower/lane_configs/claude.py +35 -0
- code_mower/lane_configs/codex.py +32 -0
- code_mower/lane_configs/devin.py +33 -0
- code_mower/lane_configs/gemini_cli.py +35 -0
- code_mower/lane_configs/hermes_cli.py +35 -0
- code_mower/lane_configs/local_llm.py +31 -0
- code_mower/local_llm_audit_pr.py +1364 -0
- code_mower/local_llm_bakeoff.py +458 -0
- code_mower/local_llm_calibration.py +441 -0
- code_mower/local_llm_profiles.py +66 -0
- code_mower/migration.py +508 -0
- code_mower/migration_install.py +292 -0
- code_mower/migration_mirror.py +392 -0
- code_mower/migration_readiness.py +237 -0
- code_mower/migration_rehearsal.py +718 -0
- code_mower/next_steps.py +441 -0
- code_mower/package.py +673 -0
- code_mower/package_content.py +444 -0
- code_mower/package_manifest.py +452 -0
- code_mower/package_paths.py +53 -0
- code_mower/package_rendering.py +90 -0
- code_mower/package_static.py +585 -0
- code_mower/prompts.py +267 -0
- code_mower/provider_registry.py +469 -0
- code_mower/provider_runners/__init__.py +60 -0
- code_mower/provider_runners/comments.py +31 -0
- code_mower/provider_runners/git.py +46 -0
- code_mower/provider_runners/github_auth.py +61 -0
- code_mower/provider_runners/github_pr.py +120 -0
- code_mower/provider_runners/process.py +58 -0
- code_mower/provider_runners/repo_paths.py +23 -0
- code_mower/provider_runners/text_schema.py +41 -0
- code_mower/provider_runners/verdict_artifacts.py +103 -0
- code_mower/provider_runners/workspace.py +57 -0
- code_mower/release_readiness.py +549 -0
- code_mower/reviewer_metrics.py +389 -0
- code_mower/saas_reviewer_labeler.py +809 -0
- code_mower/secrets.py +89 -0
- code_mower/templates/builder-experiment.example.json +55 -0
- code_mower/templates/calibration-corpus.example.json +129 -0
- code_mower/templates/calibration-corpus.json +129 -0
- code_mower/templates/code-mower.example.yml +423 -0
- code_mower/templates/context-packs.example.json +150 -0
- code_mower/templates/lane_prompts/base-audit.md +22 -0
- code_mower/templates/lane_prompts/calibration-policy.md +21 -0
- code_mower/templates/lane_prompts/context-driven-quality.md +21 -0
- code_mower/templates/lane_prompts/docs-design.md +12 -0
- code_mower/templates/lane_prompts/generic-programming.md +21 -0
- code_mower/templates/lane_prompts/operability.md +22 -0
- code_mower/templates/lane_prompts/package-runtime.md +12 -0
- code_mower/templates/lane_prompts/security-threat-model.md +22 -0
- code_mower/templates/product-support/code_mower +216 -0
- code_mower/templates/product-support/code_mower_standalone_pin.env +7 -0
- code_mower/templates/product-support/code_mower_standalone_shadow.sh +151 -0
- code_mower/templates/product-support/run_claude_audit_pr.sh +32 -0
- code_mower/templates/product-support/run_codex_audit_pr.sh +32 -0
- code_mower/templates/product-support/safe_gh_comment.py +96 -0
- code_mower/templates/providers.yml +454 -0
- code_mower/templates/reviewer-spend.example.json +28 -0
- code_mower/templates/reviewer-value-report.example.md +20 -0
- code_mower/templates/workflows/private-standalone-shadow.yml.j2 +106 -0
- code_mower/templates/workflows/review-clear-stale.yml.j2 +83 -0
- code_mower/trailer_comment_labeler.py +207 -0
- code_mower/versioning.py +32 -0
- code_mower-0.5.0b5.dist-info/METADATA +302 -0
- code_mower-0.5.0b5.dist-info/RECORD +185 -0
- code_mower-0.5.0b5.dist-info/WHEEL +5 -0
- code_mower-0.5.0b5.dist-info/entry_points.txt +2 -0
- code_mower-0.5.0b5.dist-info/licenses/LICENSE +202 -0
- code_mower-0.5.0b5.dist-info/licenses/NOTICE +10 -0
- code_mower-0.5.0b5.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Plan blind-review hold/release state from provider-neutral lane events."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import argparse
|
|
7
|
+
import json
|
|
8
|
+
import sys
|
|
9
|
+
from datetime import datetime, timezone
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Any, Mapping
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
DONE_STATES = {"done", "pass", "passed", "completed", "success"}
|
|
15
|
+
BLOCKED_STATES = {"blocked", "failed", "failure", "error"}
|
|
16
|
+
PENDING_STATES = {"needs", "pending", "queued", "running", "requested", "stale"}
|
|
17
|
+
EVENT_TIMESTAMP_FIELDS = ("timestamp", "updated_at", "submitted_at", "created_at")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _strip_prefix(value: str, prefix: str) -> str:
|
|
21
|
+
if value.startswith(prefix):
|
|
22
|
+
return value[len(prefix):]
|
|
23
|
+
return value
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def normalize_state(value: Any) -> str:
|
|
27
|
+
text = str(value or "pending").strip().lower().replace("_", "-")
|
|
28
|
+
text = _strip_prefix(_strip_prefix(text, "audit-"), "review-")
|
|
29
|
+
if text in DONE_STATES:
|
|
30
|
+
return "done"
|
|
31
|
+
if text in BLOCKED_STATES:
|
|
32
|
+
return "blocked"
|
|
33
|
+
if text in PENDING_STATES:
|
|
34
|
+
return "pending"
|
|
35
|
+
return "pending"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _load_manifest(path: Path) -> Mapping[str, Any]:
|
|
39
|
+
try:
|
|
40
|
+
payload = json.loads(path.read_text(encoding="utf-8"))
|
|
41
|
+
except (OSError, UnicodeDecodeError, json.JSONDecodeError) as exc:
|
|
42
|
+
raise ValueError(f"unable to read {path}: {exc}") from exc
|
|
43
|
+
if not isinstance(payload, Mapping):
|
|
44
|
+
raise ValueError("blind-review manifest must be a JSON object")
|
|
45
|
+
return payload
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _required_lanes(manifest: Mapping[str, Any]) -> list[str]:
|
|
49
|
+
raw_lanes = manifest.get("required_lanes", [])
|
|
50
|
+
if raw_lanes is None:
|
|
51
|
+
raw_lanes = []
|
|
52
|
+
if not isinstance(raw_lanes, list):
|
|
53
|
+
raise ValueError("required_lanes must be a JSON array of lane IDs")
|
|
54
|
+
|
|
55
|
+
lanes: list[str] = []
|
|
56
|
+
for index, lane in enumerate(raw_lanes):
|
|
57
|
+
if not isinstance(lane, str) or not lane.strip():
|
|
58
|
+
raise ValueError(
|
|
59
|
+
f"required_lanes[{index}] must be a non-empty string lane ID"
|
|
60
|
+
)
|
|
61
|
+
lanes.append(lane.strip())
|
|
62
|
+
return lanes
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _event_timestamp(event: Mapping[str, Any]) -> str:
|
|
66
|
+
for field in EVENT_TIMESTAMP_FIELDS:
|
|
67
|
+
value = event.get(field)
|
|
68
|
+
if isinstance(value, str) and value.strip():
|
|
69
|
+
return value.strip()
|
|
70
|
+
return ""
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _parse_event_timestamp(event: Mapping[str, Any]) -> datetime | None:
|
|
74
|
+
timestamp = _event_timestamp(event)
|
|
75
|
+
if not timestamp:
|
|
76
|
+
return None
|
|
77
|
+
normalized = timestamp
|
|
78
|
+
if normalized.endswith("Z"):
|
|
79
|
+
normalized = normalized[:-1] + "+00:00"
|
|
80
|
+
try:
|
|
81
|
+
parsed = datetime.fromisoformat(normalized)
|
|
82
|
+
except ValueError:
|
|
83
|
+
return None
|
|
84
|
+
if parsed.tzinfo is None:
|
|
85
|
+
parsed = parsed.replace(tzinfo=timezone.utc)
|
|
86
|
+
return parsed.astimezone(timezone.utc)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _latest_events(events: Any) -> dict[str, Mapping[str, Any]]:
|
|
90
|
+
latest: dict[str, Mapping[str, Any]] = {}
|
|
91
|
+
lane_events: dict[str, list[Mapping[str, Any]]] = {}
|
|
92
|
+
if not isinstance(events, list):
|
|
93
|
+
return latest
|
|
94
|
+
for event in events:
|
|
95
|
+
if not isinstance(event, Mapping):
|
|
96
|
+
continue
|
|
97
|
+
lane = str(event.get("lane") or "")
|
|
98
|
+
if not lane:
|
|
99
|
+
continue
|
|
100
|
+
lane_events.setdefault(lane, []).append(event)
|
|
101
|
+
for lane, items in lane_events.items():
|
|
102
|
+
timestamps = [_parse_event_timestamp(event) for event in items]
|
|
103
|
+
if all(timestamp is not None for timestamp in timestamps):
|
|
104
|
+
latest_index = max(
|
|
105
|
+
range(len(items)),
|
|
106
|
+
key=lambda index: (timestamps[index], index),
|
|
107
|
+
)
|
|
108
|
+
latest[lane] = items[latest_index]
|
|
109
|
+
else:
|
|
110
|
+
latest[lane] = items[-1]
|
|
111
|
+
return latest
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def build_blind_review_plan(manifest: Mapping[str, Any]) -> dict[str, Any]:
|
|
115
|
+
required_lanes = _required_lanes(manifest)
|
|
116
|
+
latest = _latest_events(manifest.get("events", []))
|
|
117
|
+
expected_head = str(manifest.get("head_sha") or "")
|
|
118
|
+
lane_states: dict[str, dict[str, Any]] = {}
|
|
119
|
+
warnings: list[str] = []
|
|
120
|
+
|
|
121
|
+
for lane in required_lanes:
|
|
122
|
+
event = latest.get(lane, {})
|
|
123
|
+
raw_state = event.get("state", "pending")
|
|
124
|
+
state = normalize_state(raw_state)
|
|
125
|
+
event_head = str(event.get("head_sha") or "")
|
|
126
|
+
if expected_head and not event_head:
|
|
127
|
+
warnings.append(
|
|
128
|
+
f"{lane}: event head is missing while expected head is {expected_head[:12]}; holding release."
|
|
129
|
+
)
|
|
130
|
+
state = "pending"
|
|
131
|
+
elif expected_head and event_head != expected_head:
|
|
132
|
+
warnings.append(
|
|
133
|
+
f"{lane}: event head {event_head[:12]} does not match expected head {expected_head[:12]}; holding release."
|
|
134
|
+
)
|
|
135
|
+
state = "pending"
|
|
136
|
+
lane_states[lane] = {
|
|
137
|
+
"state": state,
|
|
138
|
+
"raw_state": str(raw_state),
|
|
139
|
+
"artifact": str(event.get("artifact") or event.get("url") or ""),
|
|
140
|
+
"head_sha": event_head,
|
|
141
|
+
"detail": str(event.get("detail") or ""),
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
pending_lanes = [
|
|
145
|
+
lane for lane, state in lane_states.items()
|
|
146
|
+
if state["state"] == "pending"
|
|
147
|
+
]
|
|
148
|
+
blocked_lanes = [
|
|
149
|
+
lane for lane, state in lane_states.items()
|
|
150
|
+
if state["state"] == "blocked"
|
|
151
|
+
]
|
|
152
|
+
done_lanes = [
|
|
153
|
+
lane for lane, state in lane_states.items()
|
|
154
|
+
if state["state"] == "done"
|
|
155
|
+
]
|
|
156
|
+
ready_to_release = bool(required_lanes) and not pending_lanes and not blocked_lanes
|
|
157
|
+
release_order = done_lanes if ready_to_release else []
|
|
158
|
+
if not required_lanes:
|
|
159
|
+
warnings.append("no required_lanes configured; blind review cannot release")
|
|
160
|
+
|
|
161
|
+
return {
|
|
162
|
+
"mode": "blind-review-plan",
|
|
163
|
+
"repo": manifest.get("repo", ""),
|
|
164
|
+
"pr_number": manifest.get("pr_number", ""),
|
|
165
|
+
"head_sha": expected_head,
|
|
166
|
+
"required_lanes": required_lanes,
|
|
167
|
+
"lane_states": lane_states,
|
|
168
|
+
"done_lanes": done_lanes,
|
|
169
|
+
"pending_lanes": pending_lanes,
|
|
170
|
+
"blocked_lanes": blocked_lanes,
|
|
171
|
+
"ready_to_release": ready_to_release,
|
|
172
|
+
"release_order": release_order,
|
|
173
|
+
"public_mode": (
|
|
174
|
+
"release_all_artifacts"
|
|
175
|
+
if ready_to_release
|
|
176
|
+
else "hold_artifacts"
|
|
177
|
+
),
|
|
178
|
+
"warnings": warnings,
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def render_blind_review_text(plan: Mapping[str, Any]) -> str:
|
|
183
|
+
repo = plan.get("repo") or "unknown repo"
|
|
184
|
+
pr_number = plan.get("pr_number") or "?"
|
|
185
|
+
lines = [
|
|
186
|
+
f"Blind review plan for {repo}#{pr_number}",
|
|
187
|
+
f"ready_to_release: {str(plan.get('ready_to_release')).lower()}",
|
|
188
|
+
"",
|
|
189
|
+
"Lane states:",
|
|
190
|
+
]
|
|
191
|
+
lane_states = plan.get("lane_states", {})
|
|
192
|
+
if isinstance(lane_states, Mapping):
|
|
193
|
+
for lane, state in lane_states.items():
|
|
194
|
+
if not isinstance(state, Mapping):
|
|
195
|
+
continue
|
|
196
|
+
artifact = f" artifact={state.get('artifact')}" if state.get("artifact") else ""
|
|
197
|
+
lines.append(f"- {lane}: {state.get('state')}{artifact}")
|
|
198
|
+
if plan.get("pending_lanes"):
|
|
199
|
+
lines.extend(["", "Pending lanes:"])
|
|
200
|
+
lines.extend(f"- {lane}" for lane in plan.get("pending_lanes", []))
|
|
201
|
+
if plan.get("blocked_lanes"):
|
|
202
|
+
lines.extend(["", "Blocked lanes:"])
|
|
203
|
+
lines.extend(f"- {lane}" for lane in plan.get("blocked_lanes", []))
|
|
204
|
+
if plan.get("warnings"):
|
|
205
|
+
lines.extend(["", "Warnings:"])
|
|
206
|
+
lines.extend(f"- {warning}" for warning in plan.get("warnings", []))
|
|
207
|
+
return "\n".join(lines) + "\n"
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def main(argv: list[str] | None = None) -> int:
|
|
211
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
212
|
+
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
213
|
+
artifacts_parser = subparsers.add_parser("artifacts")
|
|
214
|
+
artifacts_parser.add_argument("manifest", type=Path)
|
|
215
|
+
artifacts_parser.add_argument("--output-dir", default=".code-mower/blind-review")
|
|
216
|
+
artifacts_parser.add_argument(
|
|
217
|
+
"--storage-backend",
|
|
218
|
+
choices=["github_actions_artifact"],
|
|
219
|
+
default="github_actions_artifact",
|
|
220
|
+
)
|
|
221
|
+
artifacts_parser.add_argument("--retention-days", type=int, default=14)
|
|
222
|
+
artifacts_parser.add_argument("--write", action="store_true")
|
|
223
|
+
artifacts_parser.add_argument("--force", action="store_true")
|
|
224
|
+
artifacts_parser.add_argument("--require-sources", action="store_true")
|
|
225
|
+
artifacts_parser.add_argument("--json", action="store_true")
|
|
226
|
+
plan_parser = subparsers.add_parser("plan")
|
|
227
|
+
plan_parser.add_argument("manifest", type=Path)
|
|
228
|
+
plan_parser.add_argument("--json", action="store_true")
|
|
229
|
+
args = parser.parse_args(argv)
|
|
230
|
+
|
|
231
|
+
if args.command == "artifacts":
|
|
232
|
+
if __package__ in {None, ""}:
|
|
233
|
+
module_dir = Path(__file__).resolve().parent
|
|
234
|
+
if module_dir.name == "code_mower": # pragma: no cover - extracted direct CLI.
|
|
235
|
+
sys.path.insert(0, str(module_dir.parent))
|
|
236
|
+
from code_mower import blind_review_artifacts
|
|
237
|
+
else:
|
|
238
|
+
sys.path.insert(0, str(module_dir.parent))
|
|
239
|
+
from tools import blind_review_artifacts
|
|
240
|
+
elif __package__ == "tools":
|
|
241
|
+
from tools import blind_review_artifacts
|
|
242
|
+
else: # pragma: no cover - exercised after package extraction.
|
|
243
|
+
from . import blind_review_artifacts
|
|
244
|
+
return blind_review_artifacts.main(
|
|
245
|
+
[
|
|
246
|
+
str(args.manifest),
|
|
247
|
+
"--output-dir",
|
|
248
|
+
args.output_dir,
|
|
249
|
+
"--storage-backend",
|
|
250
|
+
args.storage_backend,
|
|
251
|
+
"--retention-days",
|
|
252
|
+
str(args.retention_days),
|
|
253
|
+
*(["--write"] if args.write else []),
|
|
254
|
+
*(["--force"] if args.force else []),
|
|
255
|
+
*(["--require-sources"] if args.require_sources else []),
|
|
256
|
+
*(["--json"] if args.json else []),
|
|
257
|
+
]
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
if args.command == "plan":
|
|
261
|
+
try:
|
|
262
|
+
plan = build_blind_review_plan(_load_manifest(args.manifest))
|
|
263
|
+
except ValueError as exc:
|
|
264
|
+
print(f"error: {exc}", file=sys.stderr)
|
|
265
|
+
return 1
|
|
266
|
+
if args.json:
|
|
267
|
+
print(json.dumps(plan, indent=2, sort_keys=True))
|
|
268
|
+
else:
|
|
269
|
+
print(render_blind_review_text(plan), end="")
|
|
270
|
+
return 0
|
|
271
|
+
|
|
272
|
+
raise AssertionError(f"unhandled blind-review command: {args.command}")
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
if __name__ == "__main__":
|
|
276
|
+
raise SystemExit(main())
|