Skip to content

Commit 62229d9

Browse files
committed
fix(ci): address BM Bossbot PR feedback
Signed-off-by: phernandez <paul@basicmachines.co>
1 parent 03ba268 commit 62229d9

4 files changed

Lines changed: 110 additions & 16 deletions

File tree

.github/workflows/bm-bossbot.yml

Lines changed: 61 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ jobs:
3232
name: BM Bossbot Review
3333
if: github.event.pull_request.draft != true
3434
runs-on: ubuntu-latest
35+
outputs:
36+
pr_number: ${{ steps.pr.outputs.pr_number }}
37+
head_ref: ${{ steps.pr.outputs.head_ref }}
3538

3639
steps:
3740
- name: Checkout trusted base ref
@@ -83,13 +86,21 @@ jobs:
8386
metadata="${RUNNER_TEMP}/bm-bossbot-pr.json"
8487
diff_file="${RUNNER_TEMP}/bm-bossbot-pr.diff"
8588
prompt_file="${RUNNER_TEMP}/bm-bossbot-prompt.md"
89+
review_file="${RUNNER_TEMP}/bm-bossbot-review.json"
90+
max_diff_bytes=120000
8691
8792
gh pr view "${PR_NUMBER}" \
8893
--repo "${GITHUB_REPOSITORY}" \
8994
--json number,title,body,author,headRefName,headRefOid,baseRefName,labels,files,commits,reviewDecision,mergeStateStatus,isDraft \
9095
> "${metadata}"
9196
gh pr diff "${PR_NUMBER}" --repo "${GITHUB_REPOSITORY}" --patch > "${diff_file}"
9297
98+
diff_bytes="$(wc -c < "${diff_file}" | tr -d '[:space:]')"
99+
diff_truncated=false
100+
if [ "${diff_bytes}" -gt "${max_diff_bytes}" ]; then
101+
diff_truncated=true
102+
fi
103+
93104
cat .github/basic-memory/bm-bossbot-review.md > "${prompt_file}"
94105
{
95106
echo ""
@@ -103,16 +114,42 @@ jobs:
103114
echo "### Diff"
104115
echo ""
105116
echo '```diff'
106-
head -c 120000 "${diff_file}"
117+
if [ "${diff_truncated}" = "true" ]; then
118+
echo "[Diff omitted: ${diff_bytes} bytes exceeds BM Bossbot's ${max_diff_bytes} byte review limit.]"
119+
else
120+
cat "${diff_file}"
121+
fi
107122
echo ""
108123
echo '```'
109124
} >> "${prompt_file}"
110125
126+
if [ "${diff_truncated}" = "true" ]; then
127+
jq -n \
128+
--arg sha "${HEAD_SHA}" \
129+
--argjson bytes "${diff_bytes}" \
130+
--argjson max_bytes "${max_diff_bytes}" \
131+
'{
132+
reviewed_head_sha: $sha,
133+
review_complete: false,
134+
verdict: "needs_human",
135+
blocking_findings: [
136+
{
137+
title: "Diff exceeds BM Bossbot review limit",
138+
body: "The PR diff is \($bytes) bytes, exceeding the deterministic \($max_bytes) byte review limit. A human review is required or the PR must be split before BM Bossbot can approve."
139+
}
140+
],
141+
nonblocking_findings: [],
142+
summary: "BM Bossbot did not approve because the PR diff exceeded the deterministic review limit."
143+
}' > "${review_file}"
144+
fi
145+
111146
echo "prompt_file=${prompt_file}" >> "${GITHUB_OUTPUT}"
112-
echo "review_file=${RUNNER_TEMP}/bm-bossbot-review.json" >> "${GITHUB_OUTPUT}"
147+
echo "review_file=${review_file}" >> "${GITHUB_OUTPUT}"
148+
echo "diff_truncated=${diff_truncated}" >> "${GITHUB_OUTPUT}"
113149
114150
- name: Run BM Bossbot review with Codex
115151
id: codex
152+
if: steps.context.outputs.diff_truncated != 'true'
116153
uses: openai/codex-action@v1
117154
with:
118155
openai-api-key: ${{ secrets.OPENAI_API_KEY }}
@@ -133,13 +170,31 @@ jobs:
133170
--repo "${GITHUB_REPOSITORY}" \
134171
--run-url "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}"
135172
173+
assets:
174+
name: BM Bossbot Assets
175+
needs: review
176+
if: needs.review.result == 'success'
177+
runs-on: ubuntu-latest
178+
permissions:
179+
contents: write
180+
pull-requests: write
181+
182+
steps:
183+
- name: Checkout trusted base ref
184+
uses: actions/checkout@v6
185+
with:
186+
ref: ${{ github.event.pull_request.base.ref || github.ref }}
187+
fetch-depth: 1
188+
189+
- name: Set up uv
190+
uses: astral-sh/setup-uv@v3
191+
136192
- name: Generate non-gating PR infographic
137-
if: success()
138193
continue-on-error: true
139194
env:
140195
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
141196
GH_TOKEN: ${{ github.token }}
142-
PR_NUMBER: ${{ steps.pr.outputs.pr_number }}
197+
PR_NUMBER: ${{ needs.review.outputs.pr_number }}
143198
run: |
144199
set -euo pipefail
145200
gh pr view "${PR_NUMBER}" --repo "${GITHUB_REPOSITORY}" --json body --jq '.body // ""' > "${RUNNER_TEMP}/bm-bossbot-pr-body.md"
@@ -150,12 +205,11 @@ jobs:
150205
--output "docs/assets/infographics/pr-${PR_NUMBER}.webp"
151206
152207
- name: Publish non-gating PR infographic
153-
if: success()
154208
continue-on-error: true
155209
env:
156210
GH_TOKEN: ${{ github.token }}
157-
PR_NUMBER: ${{ steps.pr.outputs.pr_number }}
158-
HEAD_REF: ${{ steps.pr.outputs.head_ref }}
211+
PR_NUMBER: ${{ needs.review.outputs.pr_number }}
212+
HEAD_REF: ${{ needs.review.outputs.head_ref }}
159213
run: |
160214
set -euo pipefail
161215
asset_path="docs/assets/infographics/pr-${PR_NUMBER}.webp"

scripts/generate_infographic.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@ def validate_output_path(path: Path, *, repo_root: Path | None = None) -> Path:
4545
output = path.resolve()
4646
allowed_root = (root / "docs" / "assets" / "infographics").resolve()
4747
if not output.is_relative_to(allowed_root):
48-
raise ValueError(f"Output path must be under {allowed_root.relative_to(root)}")
48+
allowed_path = allowed_root.relative_to(root).as_posix()
49+
raise ValueError(f"Output path must be under {allowed_path}")
4950
if output.suffix != ".webp":
5051
raise ValueError("Output path must end with .webp")
5152
return output

tests/ci/test_bm_bossbot_workflow.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ def test_bm_bossbot_uses_safe_pull_request_target_gate() -> None:
2929
assert permissions["pull-requests"] == "write"
3030
assert permissions["statuses"] == "write"
3131

32+
asset_permissions = workflow["jobs"]["assets"]["permissions"]
33+
assert asset_permissions["contents"] == "write"
34+
assert asset_permissions["pull-requests"] == "write"
35+
3236

3337
def test_bm_bossbot_workflow_never_checks_out_untrusted_head() -> None:
3438
workflow = _workflow()
@@ -65,6 +69,24 @@ def test_bm_bossbot_workflow_has_deterministic_status_steps() -> None:
6569
assert "uv run --script scripts/bm_bossbot_status.py finalize" in WORKFLOW_PATH.read_text(
6670
encoding="utf-8"
6771
)
72+
73+
74+
def test_bm_bossbot_assets_are_non_gating_and_separate_from_review_job() -> None:
75+
workflow = _workflow()
76+
review_steps = workflow["jobs"]["review"]["steps"]
77+
asset_job = workflow["jobs"]["assets"]
78+
asset_steps = asset_job["steps"]
79+
80+
assert asset_job["needs"] == "review"
81+
assert asset_job["if"] == "needs.review.result == 'success'"
82+
assert not any(step["name"] == "Generate non-gating PR infographic" for step in review_steps)
83+
assert not any(step["name"] == "Publish non-gating PR infographic" for step in review_steps)
84+
85+
generate = next(step for step in asset_steps if step["name"] == "Generate non-gating PR infographic")
86+
publish = next(step for step in asset_steps if step["name"] == "Publish non-gating PR infographic")
87+
88+
assert generate["continue-on-error"] is True
89+
assert publish["continue-on-error"] is True
6890
assert "uv run --script scripts/generate_pr_infographic.py" in WORKFLOW_PATH.read_text(
6991
encoding="utf-8"
7092
)
@@ -73,6 +95,21 @@ def test_bm_bossbot_workflow_has_deterministic_status_steps() -> None:
7395
assert "BM_INFOGRAPHIC_PROVENANCE:end" in WORKFLOW_PATH.read_text(encoding="utf-8")
7496

7597

98+
def test_bm_bossbot_rejects_oversized_diffs_without_partial_approval() -> None:
99+
workflow_text = WORKFLOW_PATH.read_text(encoding="utf-8")
100+
workflow = _workflow()
101+
steps = workflow["jobs"]["review"]["steps"]
102+
run_codex = next(step for step in steps if step["name"] == "Run BM Bossbot review with Codex")
103+
104+
assert "max_diff_bytes=120000" in workflow_text
105+
assert "diff_truncated=true" in workflow_text
106+
assert "review_complete: false" in workflow_text
107+
assert 'verdict: "needs_human"' in workflow_text
108+
assert "Diff exceeds BM Bossbot review limit" in workflow_text
109+
assert run_codex["if"] == "steps.context.outputs.diff_truncated != 'true'"
110+
assert "head -c 120000" not in workflow_text
111+
112+
76113
def test_bm_bossbot_prompt_references_engineering_style_and_json_bullets() -> None:
77114
prompt = PROMPT_PATH.read_text(encoding="utf-8")
78115

tests/scripts/test_generate_pr_infographic.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from pathlib import Path
22

33
import pytest
4+
from click import unstyle
45
from typer.testing import CliRunner
56

67
from scripts import generate_infographic, generate_pr_infographic
@@ -20,16 +21,17 @@ def test_infographic_scripts_are_uv_typer_entrypoints() -> None:
2021

2122
def test_generate_pr_infographic_cli_help_exposes_useful_options() -> None:
2223
result = CliRunner().invoke(generate_pr_infographic.app, ["--help"])
24+
help_text = unstyle(result.output)
2325

2426
assert result.exit_code == 0
25-
assert "--pr-number" in result.output
26-
assert "--pr-body-file" in result.output
27-
assert "--output" in result.output
28-
assert "--theme" in result.output
29-
assert "--visual-format" in result.output
30-
assert "--provenance-output" in result.output
31-
assert "--print-prompt" in result.output
32-
assert "--dry-run" in result.output
27+
assert "--pr-number" in help_text
28+
assert "--pr-body-file" in help_text
29+
assert "--output" in help_text
30+
assert "--theme" in help_text
31+
assert "--visual-format" in help_text
32+
assert "--provenance-output" in help_text
33+
assert "--print-prompt" in help_text
34+
assert "--dry-run" in help_text
3335

3436

3537
def test_extract_bossbot_summary_from_pr_body() -> None:

0 commit comments

Comments
 (0)