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,12 @@
|
|
|
1
|
+
# Package Runtime Lens
|
|
2
|
+
|
|
3
|
+
Use this lens when a PR changes packaging, bootstrap, doctor, provider catalog, CLI dispatch, or extracted-package imports.
|
|
4
|
+
|
|
5
|
+
Focus on:
|
|
6
|
+
|
|
7
|
+
- Repo execution and extracted package execution both work.
|
|
8
|
+
- Runtime choices are explicit: Python version, CLI command, environment variables, token requirements, and optional spend boundaries.
|
|
9
|
+
- Package manifests include every file needed at runtime, including templates, lane configs, adapters, and prompt lenses.
|
|
10
|
+
- Doctor checks reveal missing local CLIs or configuration instead of allowing silent fallback to random system tooling.
|
|
11
|
+
|
|
12
|
+
Block if the reference repo passes but the generated package would import, dispatch, or locate templates incorrectly.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Security Threat Model Lens
|
|
2
|
+
|
|
3
|
+
Use this lens for authentication, authorization, billing, entitlements, debug upload, secrets, storage, network APIs, and any change that crosses a trust boundary.
|
|
4
|
+
|
|
5
|
+
Stance:
|
|
6
|
+
|
|
7
|
+
- Reason from attacker capability, asset value, and trust boundaries before judging code shape.
|
|
8
|
+
- Prefer least privilege, explicit authorization, scoped tokens, auditable decisions, and fail-closed behavior.
|
|
9
|
+
- Treat logs, diagnostics, previews, artifacts, and exported benchmark bundles as possible disclosure surfaces.
|
|
10
|
+
- Consider STRIDE-style threats: spoofing, tampering, repudiation, information disclosure, denial of service, and elevation of privilege.
|
|
11
|
+
|
|
12
|
+
Review focus:
|
|
13
|
+
|
|
14
|
+
- Flag missing server-side authorization, quota, entitlement, ownership, or replay checks.
|
|
15
|
+
- Flag token, secret, account-state, debug payload, or customer-data exposure in logs, artifacts, comments, client state, or shareable reports.
|
|
16
|
+
- Flag client-side-only controls for protected actions.
|
|
17
|
+
- Flag unsafe defaults, broad permissions, ambiguous identity binding, or failure paths that grant access.
|
|
18
|
+
- Flag security-sensitive changes whose tests or diagnostics do not exercise the threat boundary.
|
|
19
|
+
|
|
20
|
+
Block only when the PR creates a plausible security, privacy, billing, entitlement, or data-exposure risk. Do not block on generic hardening suggestions without a concrete exploit path or sensitive asset.
|
|
21
|
+
|
|
22
|
+
When useful, phrase findings with: asset, actor, trust boundary, threat, control, failure mode, exploit path, and expected server-side invariant.
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
script_dir="$(CDPATH= cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
|
|
5
|
+
repo_root="$(CDPATH= cd -- "${script_dir}/.." && pwd -P)"
|
|
6
|
+
standalone_shadow="${script_dir}/code_mower_standalone_shadow.sh"
|
|
7
|
+
install_lock_acquired=0
|
|
8
|
+
install_lock_dir=""
|
|
9
|
+
|
|
10
|
+
python_is_supported() {
|
|
11
|
+
"$1" -c 'import sys; raise SystemExit(0 if sys.version_info >= (3, 11) else 1)' >/dev/null 2>&1
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
resolve_python_candidate() {
|
|
15
|
+
case "$1" in
|
|
16
|
+
*/*)
|
|
17
|
+
if [ -x "$1" ]; then
|
|
18
|
+
printf '%s\n' "$1"
|
|
19
|
+
fi
|
|
20
|
+
return 0
|
|
21
|
+
;;
|
|
22
|
+
*) command -v "$1" 2>/dev/null || true ;;
|
|
23
|
+
esac
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
find_bootstrap_python() {
|
|
27
|
+
local candidate resolved
|
|
28
|
+
if [ -n "${CODE_MOWER_BOOTSTRAP_PYTHON:-}" ]; then
|
|
29
|
+
printf '%s\n' "${CODE_MOWER_BOOTSTRAP_PYTHON}"
|
|
30
|
+
return
|
|
31
|
+
fi
|
|
32
|
+
if [ -n "${CODE_MOWER_PYTHON:-}" ]; then
|
|
33
|
+
printf '%s\n' "${CODE_MOWER_PYTHON}"
|
|
34
|
+
return
|
|
35
|
+
fi
|
|
36
|
+
|
|
37
|
+
for candidate in ${CODE_MOWER_BOOTSTRAP_PYTHON_CANDIDATES:-python3.13 python3.12 python3.11 /opt/homebrew/bin/python3 /usr/local/bin/python3 /opt/homebrew/bin/python3.13 /opt/homebrew/bin/python3.12 /opt/homebrew/bin/python3.11 /usr/local/bin/python3.13 /usr/local/bin/python3.12 /usr/local/bin/python3.11 python3 python}; do
|
|
38
|
+
resolved="$(resolve_python_candidate "${candidate}")"
|
|
39
|
+
if [ -n "${resolved}" ] && python_is_supported "${resolved}"; then
|
|
40
|
+
printf '%s\n' "${resolved}"
|
|
41
|
+
return
|
|
42
|
+
fi
|
|
43
|
+
done
|
|
44
|
+
|
|
45
|
+
resolved="$(command -v python3 2>/dev/null || command -v python 2>/dev/null || true)"
|
|
46
|
+
printf '%s\n' "${resolved:-python3}"
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
bootstrap_python="$(find_bootstrap_python)"
|
|
50
|
+
|
|
51
|
+
if [ "${CODE_MOWER_USE_LOCAL:-}" = "1" ] || [ "${CODE_MOWER_USE_STANDALONE:-1}" = "0" ]; then
|
|
52
|
+
unset CODE_MOWER_STANDALONE_COMMAND
|
|
53
|
+
unset CODE_MOWER_STANDALONE_PATH
|
|
54
|
+
fi
|
|
55
|
+
|
|
56
|
+
if [ -z "${CODE_MOWER_STANDALONE_COMMAND:-}" ] \
|
|
57
|
+
&& [ -z "${CODE_MOWER_STANDALONE_PATH:-}" ] \
|
|
58
|
+
&& [ -z "${CODE_MOWER_STANDALONE_DELEGATING:-}" ] \
|
|
59
|
+
&& [ "${CODE_MOWER_USE_LOCAL:-}" != "1" ] \
|
|
60
|
+
&& [ "${CODE_MOWER_USE_STANDALONE:-1}" != "0" ]; then
|
|
61
|
+
if [ -x "${standalone_shadow}" ]; then
|
|
62
|
+
export CODE_MOWER_STANDALONE_DELEGATING=1
|
|
63
|
+
exec "${standalone_shadow}" "$@"
|
|
64
|
+
fi
|
|
65
|
+
echo "warning: standalone delegation expected but ${standalone_shadow} is missing or not executable; running repo-local mirror" >&2
|
|
66
|
+
fi
|
|
67
|
+
|
|
68
|
+
release_install_lock() {
|
|
69
|
+
if [ "${install_lock_acquired}" = "1" ] && [ -n "${install_lock_dir}" ]; then
|
|
70
|
+
rm -rf "${install_lock_dir}"
|
|
71
|
+
fi
|
|
72
|
+
install_lock_acquired=0
|
|
73
|
+
install_lock_dir=""
|
|
74
|
+
}
|
|
75
|
+
trap release_install_lock EXIT
|
|
76
|
+
|
|
77
|
+
acquire_install_lock() {
|
|
78
|
+
local lock_age lock_mtime lock_pid now parent_dir stale_seconds started timeout_seconds
|
|
79
|
+
install_lock_dir="${venv_dir}.install.lock"
|
|
80
|
+
parent_dir="$(dirname -- "${install_lock_dir}")"
|
|
81
|
+
stale_seconds="${CODE_MOWER_STANDALONE_LOCK_STALE_SECONDS:-30}"
|
|
82
|
+
timeout_seconds="${CODE_MOWER_STANDALONE_LOCK_TIMEOUT_SECONDS:-120}"
|
|
83
|
+
started="$(date +%s)"
|
|
84
|
+
mkdir -p "${parent_dir}"
|
|
85
|
+
while ! mkdir "${install_lock_dir}" 2>/dev/null; do
|
|
86
|
+
now="$(date +%s)"
|
|
87
|
+
lock_pid=""
|
|
88
|
+
if [ -f "${install_lock_dir}/pid" ]; then
|
|
89
|
+
lock_pid="$(cat "${install_lock_dir}/pid" 2>/dev/null || true)"
|
|
90
|
+
fi
|
|
91
|
+
lock_mtime="$(stat -c %Y "${install_lock_dir}" 2>/dev/null || stat -f %m "${install_lock_dir}" 2>/dev/null || echo "${now}")"
|
|
92
|
+
case "${lock_mtime}" in
|
|
93
|
+
''|*[!0-9]*) lock_mtime="${now}" ;;
|
|
94
|
+
esac
|
|
95
|
+
lock_age=$((now - lock_mtime))
|
|
96
|
+
if [ -z "${lock_pid}" ] && [ "${lock_age}" -ge "${stale_seconds}" ]; then
|
|
97
|
+
rm -rf "${install_lock_dir}" 2>/dev/null || true
|
|
98
|
+
continue
|
|
99
|
+
fi
|
|
100
|
+
if [ -n "${lock_pid}" ] && ! kill -0 "${lock_pid}" 2>/dev/null; then
|
|
101
|
+
rm -rf "${install_lock_dir}" 2>/dev/null || true
|
|
102
|
+
continue
|
|
103
|
+
fi
|
|
104
|
+
if [ $((now - started)) -ge "${timeout_seconds}" ]; then
|
|
105
|
+
echo "error: timed out waiting for standalone install lock: ${install_lock_dir}" >&2
|
|
106
|
+
exit 1
|
|
107
|
+
fi
|
|
108
|
+
sleep 0.2
|
|
109
|
+
done
|
|
110
|
+
install_lock_acquired=1
|
|
111
|
+
printf '%s\n' "$$" > "${install_lock_dir}/pid"
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
check_standalone_python() {
|
|
115
|
+
if [ "${CODE_MOWER_SKIP_STANDALONE_PYTHON_CHECK:-}" = "1" ]; then
|
|
116
|
+
return
|
|
117
|
+
fi
|
|
118
|
+
if ! "${bootstrap_python}" -c 'import sys; raise SystemExit(0 if sys.version_info >= (3, 11) else 1)' >/dev/null 2>&1; then
|
|
119
|
+
echo "error: Code Mower standalone install requires Python >= 3.11; ${bootstrap_python} is unsupported. Set CODE_MOWER_BOOTSTRAP_PYTHON=/path/to/python3.11+ or install python3.11/python3.12." >&2
|
|
120
|
+
exit 1
|
|
121
|
+
fi
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if [ -n "${CODE_MOWER_STANDALONE_COMMAND:-}" ]; then
|
|
125
|
+
exec "${CODE_MOWER_STANDALONE_COMMAND}" "$@"
|
|
126
|
+
fi
|
|
127
|
+
|
|
128
|
+
if [ -n "${CODE_MOWER_STANDALONE_PATH:-}" ]; then
|
|
129
|
+
standalone_path="${CODE_MOWER_STANDALONE_PATH}"
|
|
130
|
+
if [ ! -f "${standalone_path}/pyproject.toml" ]; then
|
|
131
|
+
echo "error: CODE_MOWER_STANDALONE_PATH does not look like a Code Mower package: ${standalone_path}" >&2
|
|
132
|
+
exit 1
|
|
133
|
+
fi
|
|
134
|
+
standalone_path="$(CDPATH= cd -- "${standalone_path}" && pwd -P)"
|
|
135
|
+
|
|
136
|
+
venv_dir="${CODE_MOWER_STANDALONE_VENV:-${repo_root}/.code-mower-standalone-venv}"
|
|
137
|
+
case "${venv_dir}" in
|
|
138
|
+
/*) ;;
|
|
139
|
+
*) venv_dir="${repo_root}/${venv_dir}" ;;
|
|
140
|
+
esac
|
|
141
|
+
allowed_standalone_venv="${repo_root}/.code-mower-standalone-venv"
|
|
142
|
+
allowed_managed_venv_root="${repo_root}/.code-mower/standalone-venvs"
|
|
143
|
+
venv_parent="$(dirname -- "${venv_dir}")"
|
|
144
|
+
venv_name="$(basename -- "${venv_dir}")"
|
|
145
|
+
if [ -d "${venv_parent}" ]; then
|
|
146
|
+
venv_parent="$(CDPATH= cd -- "${venv_parent}" && pwd -P)"
|
|
147
|
+
venv_dir="${venv_parent}/${venv_name}"
|
|
148
|
+
fi
|
|
149
|
+
marker="${venv_dir}/.code-mower-standalone-path"
|
|
150
|
+
installed_path=""
|
|
151
|
+
if [ -f "${marker}" ]; then
|
|
152
|
+
installed_path="$(cat "${marker}")"
|
|
153
|
+
fi
|
|
154
|
+
if [ ! -x "${venv_dir}/bin/code-mower" ] || [ "${installed_path}" != "${standalone_path}" ] || [ "${CODE_MOWER_STANDALONE_REINSTALL:-}" = "1" ]; then
|
|
155
|
+
acquire_install_lock
|
|
156
|
+
installed_path=""
|
|
157
|
+
if [ -f "${marker}" ]; then
|
|
158
|
+
installed_path="$(cat "${marker}")"
|
|
159
|
+
fi
|
|
160
|
+
if [ ! -x "${venv_dir}/bin/code-mower" ] || [ "${installed_path}" != "${standalone_path}" ] || [ "${CODE_MOWER_STANDALONE_REINSTALL:-}" = "1" ]; then
|
|
161
|
+
if [ -d "${venv_dir}" ] && [ "${CODE_MOWER_STANDALONE_REINSTALL:-}" = "1" ]; then
|
|
162
|
+
case "${venv_dir}" in
|
|
163
|
+
"${allowed_standalone_venv}"|"${allowed_standalone_venv}"/*|"${allowed_managed_venv_root}"/*) rm -rf "${venv_dir}" ;;
|
|
164
|
+
*)
|
|
165
|
+
if [ ! -x "${venv_dir}/bin/python" ]; then
|
|
166
|
+
echo "error: refusing to recreate unsafe custom standalone venv without an existing Python: ${venv_dir}" >&2
|
|
167
|
+
echo "Choose a venv under ${allowed_standalone_venv} or ${allowed_managed_venv_root}, or remove/recreate the custom venv yourself." >&2
|
|
168
|
+
exit 1
|
|
169
|
+
fi
|
|
170
|
+
;;
|
|
171
|
+
esac
|
|
172
|
+
fi
|
|
173
|
+
if [ ! -x "${venv_dir}/bin/python" ]; then
|
|
174
|
+
check_standalone_python
|
|
175
|
+
"${bootstrap_python}" -m venv "${venv_dir}"
|
|
176
|
+
fi
|
|
177
|
+
"${venv_dir}/bin/python" -m pip install -e "${standalone_path}" 1>&2
|
|
178
|
+
printf '%s\n' "${standalone_path}" > "${marker}"
|
|
179
|
+
fi
|
|
180
|
+
release_install_lock
|
|
181
|
+
fi
|
|
182
|
+
release_install_lock
|
|
183
|
+
exec "${venv_dir}/bin/code-mower" "$@"
|
|
184
|
+
fi
|
|
185
|
+
|
|
186
|
+
if [ ! -f "${script_dir}/code_mower_bootstrap.py" ]; then
|
|
187
|
+
echo "error: repo-local Code Mower mirror is unavailable. Use the pinned standalone path via tools/code_mower_standalone_shadow.sh, or unset CODE_MOWER_USE_LOCAL/CODE_MOWER_USE_STANDALONE=0." >&2
|
|
188
|
+
exit 1
|
|
189
|
+
fi
|
|
190
|
+
|
|
191
|
+
case "${1:-}" in
|
|
192
|
+
bootstrap)
|
|
193
|
+
shift
|
|
194
|
+
exec "${bootstrap_python}" "${script_dir}/code_mower_bootstrap.py" "$@"
|
|
195
|
+
;;
|
|
196
|
+
trailer-comment-labeler)
|
|
197
|
+
shift
|
|
198
|
+
python_bin="$("${bootstrap_python}" "${script_dir}/code_mower_bootstrap.py" --repo-root "${repo_root}" --print-python)"
|
|
199
|
+
exec "${python_bin}" "${script_dir}/trailer_comment_labeler.py" "$@"
|
|
200
|
+
;;
|
|
201
|
+
saas-reviewer-labeler)
|
|
202
|
+
shift
|
|
203
|
+
python_bin="$("${bootstrap_python}" "${script_dir}/code_mower_bootstrap.py" --repo-root "${repo_root}" --print-python)"
|
|
204
|
+
exec "${python_bin}" "${script_dir}/saas_reviewer_labeler.py" "$@"
|
|
205
|
+
;;
|
|
206
|
+
clear-stale)
|
|
207
|
+
if [ -f "${script_dir}/clear_stale.py" ]; then
|
|
208
|
+
shift
|
|
209
|
+
python_bin="$("${bootstrap_python}" "${script_dir}/code_mower_bootstrap.py" --repo-root "${repo_root}" --print-python)"
|
|
210
|
+
exec "${python_bin}" "${script_dir}/clear_stale.py" "$@"
|
|
211
|
+
fi
|
|
212
|
+
;;
|
|
213
|
+
esac
|
|
214
|
+
|
|
215
|
+
python_bin="$("${bootstrap_python}" "${script_dir}/code_mower_bootstrap.py" --repo-root "${repo_root}" --print-python)"
|
|
216
|
+
exec "${python_bin}" "${script_dir}/code_mower_cli.py" "$@"
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Pinned standalone Code Mower source for product-repo wrapper mode.
|
|
2
|
+
#
|
|
3
|
+
# Replace OWNER/code-mower.git and the ref before copying into a live
|
|
4
|
+
# repository, or set CODE_MOWER_STANDALONE_REPO_URL / CODE_MOWER_STANDALONE_REF
|
|
5
|
+
# in the workflow environment.
|
|
6
|
+
CODE_MOWER_STANDALONE_REPO_URL="https://github.com/OWNER/code-mower.git"
|
|
7
|
+
CODE_MOWER_STANDALONE_REF="<pin-a-reviewed-code-mower-commit-or-tag>"
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
script_dir="$(CDPATH= cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
|
|
5
|
+
repo_root="$(CDPATH= cd -- "${script_dir}/.." && pwd -P)"
|
|
6
|
+
pin_file="${CODE_MOWER_STANDALONE_PIN_FILE:-${script_dir}/code_mower_standalone_pin.env}"
|
|
7
|
+
override_repo_url="${CODE_MOWER_STANDALONE_REPO_URL:-}"
|
|
8
|
+
override_ref="${CODE_MOWER_STANDALONE_REF:-}"
|
|
9
|
+
override_source_dir="${CODE_MOWER_STANDALONE_SOURCE_DIR:-}"
|
|
10
|
+
pin_file_missing=0
|
|
11
|
+
|
|
12
|
+
if [ -f "${pin_file}" ]; then
|
|
13
|
+
# shellcheck disable=SC1090
|
|
14
|
+
. "${pin_file}"
|
|
15
|
+
else
|
|
16
|
+
pin_file_missing=1
|
|
17
|
+
fi
|
|
18
|
+
if [ -n "${override_repo_url}" ]; then
|
|
19
|
+
CODE_MOWER_STANDALONE_REPO_URL="${override_repo_url}"
|
|
20
|
+
fi
|
|
21
|
+
if [ -n "${override_ref}" ]; then
|
|
22
|
+
CODE_MOWER_STANDALONE_REF="${override_ref}"
|
|
23
|
+
fi
|
|
24
|
+
if [ -n "${override_source_dir}" ]; then
|
|
25
|
+
CODE_MOWER_STANDALONE_SOURCE_DIR="${override_source_dir}"
|
|
26
|
+
fi
|
|
27
|
+
|
|
28
|
+
repo_url="${CODE_MOWER_STANDALONE_REPO_URL:-https://github.com/OWNER/code-mower.git}"
|
|
29
|
+
source_dir="${CODE_MOWER_STANDALONE_SOURCE_DIR:-}"
|
|
30
|
+
checkout_lock_acquired=0
|
|
31
|
+
checkout_lock_dir=""
|
|
32
|
+
|
|
33
|
+
release_checkout_lock() {
|
|
34
|
+
if [ "${checkout_lock_acquired}" = "1" ] && [ -n "${checkout_lock_dir}" ]; then
|
|
35
|
+
rm -rf "${checkout_lock_dir}"
|
|
36
|
+
fi
|
|
37
|
+
checkout_lock_acquired=0
|
|
38
|
+
checkout_lock_dir=""
|
|
39
|
+
}
|
|
40
|
+
trap release_checkout_lock EXIT
|
|
41
|
+
|
|
42
|
+
acquire_checkout_lock() {
|
|
43
|
+
local lock_age lock_mtime lock_pid now parent_dir stale_seconds started timeout_seconds
|
|
44
|
+
checkout_lock_dir="${source_dir}.checkout.lock"
|
|
45
|
+
parent_dir="$(dirname -- "${checkout_lock_dir}")"
|
|
46
|
+
stale_seconds="${CODE_MOWER_STANDALONE_CHECKOUT_LOCK_STALE_SECONDS:-30}"
|
|
47
|
+
timeout_seconds="${CODE_MOWER_STANDALONE_CHECKOUT_LOCK_TIMEOUT_SECONDS:-7200}"
|
|
48
|
+
started="$(date +%s)"
|
|
49
|
+
mkdir -p "${parent_dir}"
|
|
50
|
+
while ! mkdir "${checkout_lock_dir}" 2>/dev/null; do
|
|
51
|
+
now="$(date +%s)"
|
|
52
|
+
lock_pid=""
|
|
53
|
+
if [ -f "${checkout_lock_dir}/pid" ]; then
|
|
54
|
+
lock_pid="$(cat "${checkout_lock_dir}/pid" 2>/dev/null || true)"
|
|
55
|
+
fi
|
|
56
|
+
lock_mtime="$(stat -c %Y "${checkout_lock_dir}" 2>/dev/null || stat -f %m "${checkout_lock_dir}" 2>/dev/null || echo "${now}")"
|
|
57
|
+
case "${lock_mtime}" in
|
|
58
|
+
''|*[!0-9]*) lock_mtime="${now}" ;;
|
|
59
|
+
esac
|
|
60
|
+
lock_age=$((now - lock_mtime))
|
|
61
|
+
if [ -z "${lock_pid}" ] && [ "${lock_age}" -ge "${stale_seconds}" ]; then
|
|
62
|
+
rm -rf "${checkout_lock_dir}" 2>/dev/null || true
|
|
63
|
+
continue
|
|
64
|
+
fi
|
|
65
|
+
if [ -n "${lock_pid}" ] && ! kill -0 "${lock_pid}" 2>/dev/null; then
|
|
66
|
+
rm -rf "${checkout_lock_dir}" 2>/dev/null || true
|
|
67
|
+
continue
|
|
68
|
+
fi
|
|
69
|
+
if [ $((now - started)) -ge "${timeout_seconds}" ]; then
|
|
70
|
+
echo "error: timed out waiting for standalone checkout lock: ${checkout_lock_dir}" >&2
|
|
71
|
+
exit 1
|
|
72
|
+
fi
|
|
73
|
+
sleep 0.2
|
|
74
|
+
done
|
|
75
|
+
checkout_lock_acquired=1
|
|
76
|
+
printf '%s\n' "$$" > "${checkout_lock_dir}/pid"
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
hash_ref_name() {
|
|
80
|
+
if command -v sha256sum >/dev/null 2>&1; then
|
|
81
|
+
printf '%s' "$1" | sha256sum | cut -d ' ' -f1
|
|
82
|
+
return
|
|
83
|
+
fi
|
|
84
|
+
if command -v shasum >/dev/null 2>&1; then
|
|
85
|
+
printf '%s' "$1" | shasum -a 256 | cut -d ' ' -f1
|
|
86
|
+
return
|
|
87
|
+
fi
|
|
88
|
+
if command -v openssl >/dev/null 2>&1; then
|
|
89
|
+
printf '%s' "$1" | openssl dgst -sha256 -r | cut -d ' ' -f1
|
|
90
|
+
return
|
|
91
|
+
fi
|
|
92
|
+
printf '%s' "$1" | cksum | cut -d ' ' -f1
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if [ -n "${CODE_MOWER_STANDALONE_COMMAND:-}" ]; then
|
|
96
|
+
exec "${script_dir}/code_mower" "$@"
|
|
97
|
+
fi
|
|
98
|
+
|
|
99
|
+
if [ -z "${CODE_MOWER_STANDALONE_PATH:-}" ]; then
|
|
100
|
+
if [ -z "${CODE_MOWER_STANDALONE_REF:-}" ]; then
|
|
101
|
+
if [ "${pin_file_missing}" = "1" ]; then
|
|
102
|
+
echo "error: missing Code Mower standalone pin file: ${pin_file}" >&2
|
|
103
|
+
echo "Restore tools/code_mower_standalone_pin.env or set CODE_MOWER_STANDALONE_REF explicitly." >&2
|
|
104
|
+
else
|
|
105
|
+
echo "error: CODE_MOWER_STANDALONE_REF is required for standalone shadow mode." >&2
|
|
106
|
+
echo "Add CODE_MOWER_STANDALONE_REF to ${pin_file} or set it explicitly." >&2
|
|
107
|
+
fi
|
|
108
|
+
exit 1
|
|
109
|
+
fi
|
|
110
|
+
ref="${CODE_MOWER_STANDALONE_REF}"
|
|
111
|
+
if [ "${repo_url}" = "https://github.com/OWNER/code-mower.git" ] || [ "${ref}" = "<pin-a-reviewed-code-mower-commit-or-tag>" ]; then
|
|
112
|
+
echo "error: replace the placeholder Code Mower standalone repository URL and ref before using standalone shadow mode." >&2
|
|
113
|
+
echo "Update ${pin_file}, or set CODE_MOWER_STANDALONE_REPO_URL and CODE_MOWER_STANDALONE_REF explicitly." >&2
|
|
114
|
+
exit 1
|
|
115
|
+
fi
|
|
116
|
+
if [ -z "${source_dir}" ]; then
|
|
117
|
+
ref_slug="$(printf '%s' "${ref}" | tr -c 'A-Za-z0-9._-' '_' | cut -c1-48)"
|
|
118
|
+
ref_hash="$(hash_ref_name "${ref}" | cut -c1-12)"
|
|
119
|
+
source_dir="${repo_root}/.code-mower/standalone/code-mower-${ref_slug}-${ref_hash}"
|
|
120
|
+
if [ -z "${CODE_MOWER_STANDALONE_VENV:-}" ]; then
|
|
121
|
+
export CODE_MOWER_STANDALONE_VENV="${repo_root}/.code-mower/standalone-venvs/code-mower-${ref_slug}-${ref_hash}"
|
|
122
|
+
fi
|
|
123
|
+
fi
|
|
124
|
+
acquire_checkout_lock
|
|
125
|
+
if [ ! -d "${source_dir}/.git" ]; then
|
|
126
|
+
mkdir -p "$(dirname -- "${source_dir}")"
|
|
127
|
+
git clone --quiet "${repo_url}" "${source_dir}"
|
|
128
|
+
fi
|
|
129
|
+
repo_url_changed=""
|
|
130
|
+
existing_origin="$(git -C "${source_dir}" remote get-url origin 2>/dev/null || true)"
|
|
131
|
+
if [ "${existing_origin}" != "${repo_url}" ]; then
|
|
132
|
+
git -C "${source_dir}" remote set-url origin "${repo_url}"
|
|
133
|
+
repo_url_changed=1
|
|
134
|
+
fi
|
|
135
|
+
previous_head="$(git -C "${source_dir}" rev-parse --verify HEAD 2>/dev/null || true)"
|
|
136
|
+
if [ -n "${repo_url_changed}" ] || ! git -C "${source_dir}" cat-file -e "${ref}^{commit}" 2>/dev/null; then
|
|
137
|
+
git -C "${source_dir}" fetch --quiet --tags origin "${ref}" || git -C "${source_dir}" fetch --quiet --tags origin
|
|
138
|
+
fi
|
|
139
|
+
git -C "${source_dir}" checkout --quiet --detach "${ref}"
|
|
140
|
+
checked_out_head="$(git -C "${source_dir}" rev-parse --verify HEAD)"
|
|
141
|
+
if [ -n "${previous_head}" ] && [ "${previous_head}" != "${checked_out_head}" ] && [ -z "${CODE_MOWER_STANDALONE_REINSTALL:-}" ]; then
|
|
142
|
+
export CODE_MOWER_STANDALONE_REINSTALL=1
|
|
143
|
+
fi
|
|
144
|
+
export CODE_MOWER_STANDALONE_PATH="${source_dir}"
|
|
145
|
+
fi
|
|
146
|
+
|
|
147
|
+
set +e
|
|
148
|
+
"${script_dir}/code_mower" "$@"
|
|
149
|
+
rc=$?
|
|
150
|
+
set -e
|
|
151
|
+
exit "${rc}"
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
script_dir="$(CDPATH= cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
|
|
5
|
+
code_mower="${script_dir}/code_mower"
|
|
6
|
+
unset CODE_MOWER_USE_LOCAL
|
|
7
|
+
unset CODE_MOWER_USE_STANDALONE
|
|
8
|
+
|
|
9
|
+
token_from_stdin=""
|
|
10
|
+
filtered_args=()
|
|
11
|
+
for arg in "$@"; do
|
|
12
|
+
case "${arg}" in
|
|
13
|
+
--token-from-stdin|--read-token-from-stdin)
|
|
14
|
+
token_from_stdin=1
|
|
15
|
+
;;
|
|
16
|
+
*)
|
|
17
|
+
filtered_args+=("${arg}")
|
|
18
|
+
;;
|
|
19
|
+
esac
|
|
20
|
+
done
|
|
21
|
+
|
|
22
|
+
if [ -n "${token_from_stdin}" ]; then
|
|
23
|
+
exec env -u GITHUB_TOKEN -u GH_TOKEN "${code_mower}" claude-audit --read-token-from-stdin "${filtered_args[@]}"
|
|
24
|
+
fi
|
|
25
|
+
|
|
26
|
+
env_token="${GITHUB_TOKEN:-${GH_TOKEN:-}}"
|
|
27
|
+
if [ -n "${env_token}" ]; then
|
|
28
|
+
printf '%s\n' "${env_token}" | env -u GITHUB_TOKEN -u GH_TOKEN "${code_mower}" claude-audit --read-token-from-stdin "${filtered_args[@]}"
|
|
29
|
+
exit $?
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
exec env -u GITHUB_TOKEN -u GH_TOKEN "${code_mower}" claude-audit "${filtered_args[@]}"
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
script_dir="$(CDPATH= cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
|
|
5
|
+
code_mower="${script_dir}/code_mower"
|
|
6
|
+
unset CODE_MOWER_USE_LOCAL
|
|
7
|
+
unset CODE_MOWER_USE_STANDALONE
|
|
8
|
+
|
|
9
|
+
token_from_stdin=""
|
|
10
|
+
filtered_args=()
|
|
11
|
+
for arg in "$@"; do
|
|
12
|
+
case "${arg}" in
|
|
13
|
+
--token-from-stdin|--read-token-from-stdin)
|
|
14
|
+
token_from_stdin=1
|
|
15
|
+
;;
|
|
16
|
+
*)
|
|
17
|
+
filtered_args+=("${arg}")
|
|
18
|
+
;;
|
|
19
|
+
esac
|
|
20
|
+
done
|
|
21
|
+
|
|
22
|
+
if [ -n "${token_from_stdin}" ]; then
|
|
23
|
+
exec env -u GITHUB_TOKEN -u GH_TOKEN "${code_mower}" codex-audit --read-token-from-stdin "${filtered_args[@]}"
|
|
24
|
+
fi
|
|
25
|
+
|
|
26
|
+
env_token="${GITHUB_TOKEN:-${GH_TOKEN:-}}"
|
|
27
|
+
if [ -n "${env_token}" ]; then
|
|
28
|
+
printf '%s\n' "${env_token}" | env -u GITHUB_TOKEN -u GH_TOKEN "${code_mower}" codex-audit --read-token-from-stdin "${filtered_args[@]}"
|
|
29
|
+
exit $?
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
exec env -u GITHUB_TOKEN -u GH_TOKEN "${code_mower}" codex-audit "${filtered_args[@]}"
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Post or edit GitHub issue/PR comments without shell-interpreting Markdown.
|
|
3
|
+
|
|
4
|
+
Never pass Markdown through `gh ... --body "..."`: backticks and `$()` inside
|
|
5
|
+
the body are shell syntax before GitHub ever sees them. This helper reads the
|
|
6
|
+
body from a file or stdin, serializes it as JSON, and invokes `gh api` with
|
|
7
|
+
argument-list subprocess calls.
|
|
8
|
+
"""
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import argparse
|
|
12
|
+
import json
|
|
13
|
+
import subprocess
|
|
14
|
+
import sys
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import TYPE_CHECKING, Any, Callable, Optional, Sequence
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
RunFn = Callable[..., subprocess.CompletedProcess[str]]
|
|
20
|
+
else:
|
|
21
|
+
# Keep the generated helper importable under older ambient Python 3.x
|
|
22
|
+
# interpreters that cannot evaluate subprocess.CompletedProcess[str].
|
|
23
|
+
RunFn = Callable[..., Any]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _read_body(*, body_file: Optional[Path], stdin_text: Optional[str] = None) -> str:
|
|
27
|
+
if body_file is not None:
|
|
28
|
+
return body_file.read_text(encoding="utf-8")
|
|
29
|
+
if stdin_text is not None:
|
|
30
|
+
return stdin_text
|
|
31
|
+
return sys.stdin.read()
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _repo_from_gh(run: RunFn = subprocess.run) -> str:
|
|
35
|
+
result = run(
|
|
36
|
+
["gh", "repo", "view", "--json", "nameWithOwner", "--jq", ".nameWithOwner"],
|
|
37
|
+
check=True,
|
|
38
|
+
text=True,
|
|
39
|
+
capture_output=True,
|
|
40
|
+
)
|
|
41
|
+
return result.stdout.strip()
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _run_gh_api(args: Sequence[str], payload: "dict[str, str]", run: RunFn = subprocess.run) -> None:
|
|
45
|
+
run(
|
|
46
|
+
["gh", "api", *args, "--input", "-"],
|
|
47
|
+
input=json.dumps(payload),
|
|
48
|
+
check=True,
|
|
49
|
+
text=True,
|
|
50
|
+
capture_output=True,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def post_comment(repo: str, issue_or_pr: int, body: str, run: RunFn = subprocess.run) -> None:
|
|
55
|
+
_run_gh_api(
|
|
56
|
+
["-X", "POST", f"repos/{repo}/issues/{issue_or_pr}/comments"],
|
|
57
|
+
{"body": body},
|
|
58
|
+
run,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def edit_comment(repo: str, comment_id: int, body: str, run: RunFn = subprocess.run) -> None:
|
|
63
|
+
_run_gh_api(
|
|
64
|
+
["-X", "PATCH", f"repos/{repo}/issues/comments/{comment_id}"],
|
|
65
|
+
{"body": body},
|
|
66
|
+
run,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _parse_args(argv: Optional[Sequence[str]] = None) -> argparse.Namespace:
|
|
71
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
72
|
+
target = parser.add_mutually_exclusive_group(required=True)
|
|
73
|
+
target.add_argument("--issue", "--pr", dest="issue_or_pr", type=int)
|
|
74
|
+
target.add_argument("--edit-comment-id", type=int)
|
|
75
|
+
parser.add_argument("--repo", help="owner/repo. Defaults to `gh repo view`.")
|
|
76
|
+
parser.add_argument("--body-file", type=Path, help="Markdown file to post/edit. Defaults to stdin.")
|
|
77
|
+
return parser.parse_args(argv)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def main(argv: Optional[Sequence[str]] = None) -> int:
|
|
81
|
+
args = _parse_args(argv)
|
|
82
|
+
try:
|
|
83
|
+
repo = args.repo or _repo_from_gh()
|
|
84
|
+
body = _read_body(body_file=args.body_file)
|
|
85
|
+
if args.edit_comment_id is not None:
|
|
86
|
+
edit_comment(repo, args.edit_comment_id, body)
|
|
87
|
+
else:
|
|
88
|
+
post_comment(repo, args.issue_or_pr, body)
|
|
89
|
+
except (OSError, subprocess.CalledProcessError) as exc:
|
|
90
|
+
print(f"error: {exc}", file=sys.stderr)
|
|
91
|
+
return 1
|
|
92
|
+
return 0
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
if __name__ == "__main__":
|
|
96
|
+
raise SystemExit(main())
|