Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 6 additions & 41 deletions .github/workflows/initiative-planner.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,44 +48,11 @@ concurrency:
cancel-in-progress: false

jobs:
# ── discussion [labeled] bridge ───────────────────────────────────────────────
# claude-code-action aborts on `discussion` event contexts ("Unsupported event
# type: discussion"), so we never plan inline on a discussion event. When a
# maintainer adds `idea:approved`, this job resolves the discussion number and
# re-invokes THIS workflow via workflow_dispatch — under which the action runs.
# The label stays the human gate; the re-dispatch is an invisible bridge.
redispatch:
if: github.event_name == 'discussion' && github.event.label.name == 'idea:approved'
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
contents: read # actions/checkout; the workflow_dispatch is fired with the PAT
steps:
- name: Checkout agent repo
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false

- name: Guard — PAT present
# PAT required: a workflow_dispatch fired with GITHUB_TOKEN is accepted but
# NEVER starts a run (loop prevention) — the planner would silently no-op.
env:
GH_TOKEN: ${{ secrets.GH_PAT_WORKFLOWS }}
run: |
if [ -z "${GH_TOKEN}" ]; then
echo "::error::GH_PAT_WORKFLOWS is required — a workflow_dispatch fired with GITHUB_TOKEN will not start a run."
exit 1
fi

- name: Re-dispatch under workflow_dispatch
env:
GH_TOKEN: ${{ secrets.GH_PAT_WORKFLOWS }}
REPO: ${{ github.repository }}
DISCUSSION_NUMBER: ${{ github.event.discussion.number }}
run: bash scripts/initiative-planner/redispatch.sh

plan:
if: github.event_name == 'workflow_dispatch'
# Skip discussion-label events unless the label added is exactly idea:approved.
if: >-
github.event_name == 'workflow_dispatch' ||
(github.event_name == 'discussion' && github.event.label.name == 'idea:approved')
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
Expand All @@ -94,11 +61,9 @@ jobs:
discussions: write # post the plan summary back to the idea
id-token: write # claude-code-action OIDC
env:
# This job only runs on workflow_dispatch (the discussion path re-dispatches
# here via the `redispatch` job), so inputs are always present.
REPO: ${{ github.repository }}
DISCUSSION_NUMBER: ${{ github.event.inputs.discussion }}
DRY_RUN: ${{ inputs.dry_run == true && '1' || '0' }}
DISCUSSION_NUMBER: ${{ github.event.inputs.discussion || github.event.discussion.number }}
DRY_RUN: ${{ (github.event_name == 'workflow_dispatch' && inputs.dry_run == true) && '1' || '0' }}
TOOLING_DIR: ${{ github.workspace }}/scripts/initiative-planner
steps:
- name: Checkout agent repo
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ jobs:
tests/test_cut_release.bats \
tests/test_gitleaks_config.bats \
tests/test_initiative_planner.bats \
tests/test_initiative_planner_redispatch.bats \
tests/test_idea_triage.bats
tests/test_idea_triage.bats \
tests/test_oversized_pr.bats

validate-agent-profiles:
runs-on: ubuntu-latest
Expand Down
14 changes: 14 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,20 @@ This is the `.github-private` org infrastructure repo for `petry-projects`. It c
- Scripts must be POSIX-compatible shell (`#!/usr/bin/env bash` with `set -euo pipefail`).
- No hardcoded tokens or secrets — use `$GITHUB_TOKEN` from the environment.

### Oversized PRs (> 300 changed files)

GitHub's unified-diff endpoint (`gh pr diff`) is hard-capped at 300 changed files and returns
HTTP 406 above that limit.

- **PR-size guideline:** PRs should stay under 300 changed files to receive a full automated review.
PRs exceeding 300 files are reviewed with a truncated diff and should be split where practical.
This guideline is advisory — no CI gate enforces it.
- **Agent behavior on 406:** When `gh pr diff` returns the 406 "diff exceeded the maximum number of
files" error, the cascade falls back to the per-file REST API (`/pulls/{n}/files`), assembles a
truncated diff up to the configured line limit, and continues the review with a note that the diff
is partial. The agent **never** exits with a fatal error (code 1) on the 406 — it always produces
an actionable review or comment.

### Cost reporting

- **All surfaced USD amounts are rounded to 2 decimals (cents).** Render every
Expand Down
10 changes: 10 additions & 0 deletions scripts/engine.sh
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,16 @@ is_cli_error() {
"(invalid command format|invalid (flag|argument|option|command)|unknown (flag|command|option|argument)|command not found|no such command|did you mean:|unrecognized (command|flag|argument|option)|bad (flag|argument|option))"
}

# is_diff_too_large <text>
# Returns 0 (true) if the text indicates that the PR diff exceeded GitHub's
# hard 300-file unified-diff cap (HTTP 406). Distinct from rate-limit (429/529)
# and CLI invocation errors — callers should fall back to the per-file REST API
# rather than exiting 1 (per-PR failure) or 2 (engine rate-limit).
is_diff_too_large() {
local text="$1"
printf '%s\n' "$text" | grep -qiE "(HTTP 406|exceeded the maximum number of files|diff exceeded the maximum)"
}

# is_transient_failure <exit_code>
# Returns 0 (true) for exit codes suggesting a flaky network/process state:
# 124 (GNU timeout) and 137/143 (signal kills). JSON parse failures and
Expand Down
3 changes: 1 addition & 2 deletions scripts/initiative-planner/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,13 @@ create-story template, which `plan.schema.json` mirrors field-for-field.

| File | Role |
|------|------|
| `redispatch.sh` | Bridge the `discussion [labeled]` trigger to `workflow_dispatch` (claude-code-action rejects `discussion` event contexts). Fired with a PAT so the dispatch actually starts a run. |
| `gather-context.sh` | Fetch the approved Discussion + repo context → `$CONTEXT_PATH`; export `DISCUSSION_NODE_ID`. |
| `plan.schema.json` | The plan contract Bob must emit (epic + stories + `blocked_by`). |
| `validate-plan.py` | Schema + semantic checks: unique ids, acyclic DAG, an entry point, no dangling edges. |
| `lib/mutations.sh` | DRY_RUN-aware GitHub helpers (create issue, link sub-issue, add `blocked_by`, comment on Discussion). |
| `apply-plan.sh` | Materialize a validated plan. Creates issues labelled `initiative` only — **never** `initiative:auto`. |

Tests: `tests/test_initiative_planner.bats`, `tests/test_initiative_planner_redispatch.bats` (run via `lint.yml`).
Tests: `tests/test_initiative_planner.bats` (run via `lint.yml`).

## Local dry run

Expand Down
58 changes: 0 additions & 58 deletions scripts/initiative-planner/redispatch.sh

This file was deleted.

34 changes: 30 additions & 4 deletions scripts/review-one-pr.sh
Original file line number Diff line number Diff line change
Expand Up @@ -429,10 +429,36 @@ else
echo "{\"pr\":\"$PR_URL\",\"sha\":\"$PR_HEAD_SHA\",\"decision\":\"skip\",\"reason\":\"gh-rate-limited\"}"
exit 100
fi
echo "::error::gh pr diff failed during prefetch for $PR_URL"
[ -n "$_gh_diff_err_content" ] && echo " $_gh_diff_err_content"
rm -f "$_gh_meta_err" "$_gh_diff_err" "$_gh_diff_tmp"
exit 1
if is_diff_too_large "$_gh_diff_err_content"; then
# GitHub's unified-diff endpoint is hard-capped at 300 changed files (HTTP 406).
# Fall back to the per-file REST API which works above the cap: each entry carries
# its own patch, so we assemble a truncated diff and continue the review normally.
_changed_files=$(echo "$PR_METADATA" | jq -r '.changedFiles // "unknown"' 2>/dev/null || echo "unknown")
echo " diff too large (${_changed_files} files exceeds GitHub 300-file unified-diff cap) — assembling diff from per-file REST API"
_owner_repo_fallback=$(echo "$PR_URL" | sed -E 's|https://github.com/([^/]+/[^/]+)/pull/.*|\1|')
_pr_num_fallback=${PR_URL##*/}
_fallback_tmp=$(mktemp 2>/dev/null || echo "/tmp/cascade/fallback-diff-$$.txt")
_fallback_err=$(mktemp 2>/dev/null || echo "/tmp/cascade/fallback-err-$$.txt")
{
printf '# NOTE: diff assembled from per-file REST API (%s changed files exceeded\n' "$_changed_files"
printf "# GitHub's 300-file unified-diff cap; patches may be partial or absent for large files).\n"
gh api "repos/$_owner_repo_fallback/pulls/$_pr_num_fallback/files" \
--paginate --slurp \
| jq -r 'add | .[]? | if .patch then "diff --git a/\(.filename) b/\(.filename)\n--- a/\(.filename)\n+++ b/\(.filename)\n\(.patch)" else "# \(.status): \(.filename) (no patch — binary or large file)" end' \
2>"$_fallback_err" || {
echo "::warning::REST API fallback also failed — review will have partial diff" >&2
cat "$_fallback_err" >&2 || true
}
} > "$_fallback_tmp"
PR_DIFF=$(head -"$_diff_limit" "$_fallback_tmp")
rm -f "$_fallback_tmp" "$_fallback_err"
unset _changed_files _owner_repo_fallback _pr_num_fallback _fallback_tmp _fallback_err
else
echo "::error::gh pr diff failed during prefetch for $PR_URL"
[ -n "$_gh_diff_err_content" ] && echo " $_gh_diff_err_content"
rm -f "$_gh_meta_err" "$_gh_diff_err" "$_gh_diff_tmp"
exit 1
fi
fi
rm -f "$_gh_meta_err" "$_gh_diff_err" "$_gh_diff_tmp"
unset _gh_meta_err _gh_diff_err _gh_diff_tmp _gh_meta_err_content _gh_diff_err_content
Expand Down
106 changes: 0 additions & 106 deletions tests/test_initiative_planner_redispatch.bats

This file was deleted.

Loading
Loading