Skip to content

fix: address automated feedback from PR #217 - #219

Merged
don-petry merged 1 commit into
mainfrom
fix/address-pr217-feedback
May 17, 2026
Merged

fix: address automated feedback from PR #217#219
don-petry merged 1 commit into
mainfrom
fix/address-pr217-feedback

Conversation

@don-petry

Copy link
Copy Markdown
Collaborator

This addresses missed feedback from CodeRabbit (removing syntax errors in YAML) and gemini-code-assist (improving string concatenation in validate-engines.sh), and adds assertion output checks to tests.

This addresses missed feedback from CodeRabbit (removing syntax errors in YAML) and gemini-code-assist (improving string concatenation in validate-engines.sh), and adds assertion output checks to tests.
Copilot AI review requested due to automatic review settings May 17, 2026 15:50
@coderabbitai

coderabbitai Bot commented May 17, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@don-petry has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 25 minutes and 2 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 1e88b11a-1fba-4f07-a978-dff8a1d9cc29

📥 Commits

Reviewing files that changed from the base of the PR and between b0073a7 and d852faf.

📒 Files selected for processing (3)
  • .github/workflows/dev-lead.yml
  • scripts/validate-engines.sh
  • tests/test_batch_fallback.bats
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/address-pr217-feedback

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@don-petry

Copy link
Copy Markdown
Collaborator Author

No description provided.

@sonarqubecloud

Copy link
Copy Markdown

@don-petry
don-petry merged commit b2178f9 into main May 17, 2026
19 of 20 checks passed
@don-petry
don-petry deleted the fix/address-pr217-feedback branch May 17, 2026 15:51

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request refactors the Gemini engine validation logic in scripts/validate-engines.sh by introducing a helper function for string concatenation and adds new assertions to tests/test_batch_fallback.bats to verify fallback behavior. Feedback suggests replacing the nested bash function with ${parameter:+word} expansion to prevent global namespace pollution and simplify the logic.

Comment on lines +34 to 50
append_gemini_reason() {
if [ -n "$gemini_reasons" ]; then
gemini_reasons="$gemini_reasons; GOOGLE_API_KEY secret not set"
gemini_reasons="$gemini_reasons; $1"
else
gemini_reasons="GOOGLE_API_KEY secret not set"
gemini_reasons="$1"
fi
}

if ! command -v gemini >/dev/null 2>&1; then
append_gemini_reason "Gemini CLI not installed (fix: npm install -g @google/gemini-cli)"
fi
if [ -z "${GOOGLE_API_KEY:-}" ]; then
append_gemini_reason "GOOGLE_API_KEY secret not set"
fi
if [ "${GEMINI_CLI_TRUST_WORKSPACE:-false}" != "true" ]; then
if [ -n "$gemini_reasons" ]; then
gemini_reasons="$gemini_reasons; GEMINI_CLI_TRUST_WORKSPACE is not true"
else
gemini_reasons="GEMINI_CLI_TRUST_WORKSPACE is not true (fix: set in env or pass --skip-trust)"
fi
append_gemini_reason "GEMINI_CLI_TRUST_WORKSPACE is not true (fix: set in env or pass --skip-trust)"
fi

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Defining a nested function in Bash pollutes the global namespace once the outer function is executed, which can lead to unexpected behavior if the script is sourced multiple times or by other scripts. A more idiomatic and concise way to handle string concatenation with a separator is using the ${parameter:+word} expansion. This avoids the need for a helper function entirely and keeps the logic local to the variable's scope.

Suggested change
append_gemini_reason() {
if [ -n "$gemini_reasons" ]; then
gemini_reasons="$gemini_reasons; GOOGLE_API_KEY secret not set"
gemini_reasons="$gemini_reasons; $1"
else
gemini_reasons="GOOGLE_API_KEY secret not set"
gemini_reasons="$1"
fi
}
if ! command -v gemini >/dev/null 2>&1; then
append_gemini_reason "Gemini CLI not installed (fix: npm install -g @google/gemini-cli)"
fi
if [ -z "${GOOGLE_API_KEY:-}" ]; then
append_gemini_reason "GOOGLE_API_KEY secret not set"
fi
if [ "${GEMINI_CLI_TRUST_WORKSPACE:-false}" != "true" ]; then
if [ -n "$gemini_reasons" ]; then
gemini_reasons="$gemini_reasons; GEMINI_CLI_TRUST_WORKSPACE is not true"
else
gemini_reasons="GEMINI_CLI_TRUST_WORKSPACE is not true (fix: set in env or pass --skip-trust)"
fi
append_gemini_reason "GEMINI_CLI_TRUST_WORKSPACE is not true (fix: set in env or pass --skip-trust)"
fi
if ! command -v gemini >/dev/null 2>&1; then
gemini_reasons="${gemini_reasons:+$gemini_reasons; }Gemini CLI not installed (fix: npm install -g @google/gemini-cli)"
fi
if [ -z "${GOOGLE_API_KEY:-}" ]; then
gemini_reasons="${gemini_reasons:+$gemini_reasons; }GOOGLE_API_KEY secret not set"
fi
if [ "${GEMINI_CLI_TRUST_WORKSPACE:-false}" != "true" ]; then
gemini_reasons="${gemini_reasons:+$gemini_reasons; }GEMINI_CLI_TRUST_WORKSPACE is not true (fix: set in env or pass --skip-trust)"
fi

@don-petry

Copy link
Copy Markdown
Collaborator Author

No description provided.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

don-petry added a commit that referenced this pull request Jun 7, 2026
This addresses missed feedback from CodeRabbit (removing syntax errors in YAML) and gemini-code-assist (improving string concatenation in validate-engines.sh), and adds assertion output checks to tests.

Co-authored-by: Gemini CLI <gemini-cli@example.com>
don-petry added a commit that referenced this pull request Jun 7, 2026
This addresses missed feedback from CodeRabbit (removing syntax errors in YAML) and gemini-code-assist (improving string concatenation in validate-engines.sh), and adds assertion output checks to tests.

Co-authored-by: Gemini CLI <gemini-cli@example.com>
don-petry added a commit that referenced this pull request Jun 7, 2026
This addresses missed feedback from CodeRabbit (removing syntax errors in YAML) and gemini-code-assist (improving string concatenation in validate-engines.sh), and adds assertion output checks to tests.

Co-authored-by: Gemini CLI <gemini-cli@example.com>
don-petry added a commit that referenced this pull request Jun 8, 2026
This addresses missed feedback from CodeRabbit (removing syntax errors in YAML) and gemini-code-assist (improving string concatenation in validate-engines.sh), and adds assertion output checks to tests.

Co-authored-by: Gemini CLI <gemini-cli@example.com>
don-petry added a commit that referenced this pull request Jun 8, 2026
This addresses missed feedback from CodeRabbit (removing syntax errors in YAML) and gemini-code-assist (improving string concatenation in validate-engines.sh), and adds assertion output checks to tests.

Co-authored-by: Gemini CLI <gemini-cli@example.com>
don-petry added a commit that referenced this pull request Jun 8, 2026
This addresses missed feedback from CodeRabbit (removing syntax errors in YAML) and gemini-code-assist (improving string concatenation in validate-engines.sh), and adds assertion output checks to tests.

Co-authored-by: Gemini CLI <gemini-cli@example.com>
don-petry added a commit that referenced this pull request Jun 12, 2026
This addresses missed feedback from CodeRabbit (removing syntax errors in YAML) and gemini-code-assist (improving string concatenation in validate-engines.sh), and adds assertion output checks to tests.

Co-authored-by: Gemini CLI <gemini-cli@example.com>
don-petry added a commit that referenced this pull request Jun 13, 2026
This addresses missed feedback from CodeRabbit (removing syntax errors in YAML) and gemini-code-assist (improving string concatenation in validate-engines.sh), and adds assertion output checks to tests.

Co-authored-by: Gemini CLI <gemini-cli@example.com>
don-petry added a commit that referenced this pull request Jun 13, 2026
This addresses missed feedback from CodeRabbit (removing syntax errors in YAML) and gemini-code-assist (improving string concatenation in validate-engines.sh), and adds assertion output checks to tests.

Co-authored-by: Gemini CLI <gemini-cli@example.com>
don-petry added a commit that referenced this pull request Jun 14, 2026
This addresses missed feedback from CodeRabbit (removing syntax errors in YAML) and gemini-code-assist (improving string concatenation in validate-engines.sh), and adds assertion output checks to tests.

Co-authored-by: Gemini CLI <gemini-cli@example.com>
don-petry added a commit that referenced this pull request Jun 15, 2026
This addresses missed feedback from CodeRabbit (removing syntax errors in YAML) and gemini-code-assist (improving string concatenation in validate-engines.sh), and adds assertion output checks to tests.

Co-authored-by: Gemini CLI <gemini-cli@example.com>
don-petry added a commit that referenced this pull request Jun 18, 2026
This addresses missed feedback from CodeRabbit (removing syntax errors in YAML) and gemini-code-assist (improving string concatenation in validate-engines.sh), and adds assertion output checks to tests.

Co-authored-by: Gemini CLI <gemini-cli@example.com>
don-petry added a commit that referenced this pull request Jun 21, 2026
This addresses missed feedback from CodeRabbit (removing syntax errors in YAML) and gemini-code-assist (improving string concatenation in validate-engines.sh), and adds assertion output checks to tests.

Co-authored-by: Gemini CLI <gemini-cli@example.com>
don-petry added a commit that referenced this pull request Aug 3, 2026
This addresses missed feedback from CodeRabbit (removing syntax errors in YAML) and gemini-code-assist (improving string concatenation in validate-engines.sh), and adds assertion output checks to tests.

Co-authored-by: Gemini CLI <gemini-cli@example.com>
don-petry added a commit that referenced this pull request Aug 3, 2026
… trigger for the Class-2 timer changes, before narrowing them (#1420)

* add: workflow to fix stuck PRs using bot account token

Allows running the cleanup script via workflow_dispatch with access to
GH_PAT (bot account token) from repo secrets.

* fix: pass GH_PAT to checkout action so workflow uses bot account

* debug: simplify account check, add authentication debug output

* docs: add comprehensive petry-review-bot setup instructions

* docs: add GitHub App setup guide (recommended approach)

GitHub App is the secure, recommended way to automate PR reviews:
- Fine-grained permissions
- JWT tokens that auto-expire
- No human account needed
- Better audit trail
- GitHub's recommended approach

Replaces the bot user account approach with a more secure alternative.

* docs: add comprehensive GitHub App setup documentation

- SETUP.md: Quick reference guide with configuration and troubleshooting
- IMPLEMENTATION.md: Technical deep dive on architecture and design decisions
- DOCUMENTATION.md: Index of all documentation files
- Updated README.md with status and quick links
- Updated GITHUB_APP_SETUP.md with implementation notes and actual app ID
- Workflows use GitHub App token generation instead of static PATs

Covers the transition from bot user account to GitHub App authentication for improved security and maintainability.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: silence auth check in fix-stuck-prs when using GitHub App token

GitHub App tokens don't have user scope, so 'gh api user' returns 403.
This is fine - the script still works for PR operations. Suppress the error
so the script completes successfully with app-token fallback label.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: use explicit author instead of @me in stuck PR search

GitHub App tokens don't have user identity, so @me search doesn't work.
Use explicit 'don-petry' author instead to find PRs to fix.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: avoid subshell in while loop to preserve variable state

Using pipe in while loop created subshell where PROBLEM_PRS and FIXED_PRS
counters were incremented but changes didn't persist to parent shell.
Fixed by using process substitution for input instead of pipe.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* docs: add comprehensive status report for GitHub App setup

- Overview of completed work
- GitHub App authentication details
- 24 stuck PRs successfully fixed with approval reviews
- Architectural decisions and rationale
- Configuration guide and usage examples
- Known limitations and troubleshooting

System is now fully operational with GitHub App token authentication,
comprehensive documentation, and all infrastructure in place.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* docs: document stuck PR cleanup status and script fixes

Added documentation for:
- GitHub App token compatibility fixes in fix-stuck-prs.sh
  * Changed @me to explicit author (app tokens have no identity)
  * Fixed subshell variable scope (process substitution vs pipe)
  * Silenced expected 403 auth check error
- Current status: 24 PRs have approvals but remain OPEN
  * Auto-merge failed due to missing GitHub App permission
  * Approvals satisfy branch protection requirement
  * Next: expand permissions or manually merge

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: update list-prs.sh to search all repos by owner instead of @me

GitHub App tokens don't have user identity, so @me/@review-requested searches
fail. Changed to enumerate all repos in don-petry and petry-projects, then
search for open PRs within each repo. This covers the full scope:
- All open PRs in personal don-petry repos
- All open PRs in petry-projects org repos

Resolves 6 consecutive workflow failures due to zero PRs being enumerated.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* docs: add investigation report for PR review agent workflow failures

* fix: move env section before steps in workflow to fix YAML structure

GitHub Actions requires env section to come before steps in job definition.
Moving env definition up and setting GH_TOKEN only in steps that need it.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* script: add backfill-approvals to retroactively apply real approvals

Converts PRs with agent approval comments (but no real GitHub approvals)
to have actual APPROVED reviews. Needed for PRs reviewed before the
gh pr review --approve fix was applied.

Usage:
  scripts/backfill-approvals.sh          # dry-run (preview changes)
  scripts/backfill-approvals.sh false    # apply real approvals

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* feat: add backfill-approvals workflow to run as GitHub App bot

Runs backfill-approvals.sh via GitHub Actions so the approvals are posted
by the bot identity, not the PR author — GitHub rejects self-approvals.

Also fixes subshell counter bug in the script (piped while loop lost variable
state; switched to process substitution).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: convert inner while loop to process substitution to preserve counters

Both the outer repo loop and inner PR loop were piped subshells, causing
all counter increments (approved/skipped/failed) to be lost. The summary
always showed 0. Fixed by using < <(...) process substitution for both loops.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* car-hunt: add VIN-deep-dive flow, printable checklists, location-tier ranking

- Step 5.5 pre-test-drive flow: NHTSA VIN decode + per-VIN unrepaired-recall
  automation via Chrome MCP (Honda LWC owner portal documented as not
  automatable; NHTSA web form works).
- §F printable test-drive checklist generator (reportlab/Platypus PDF) with
  cold-start, model-specific red flags, negotiation table, walk-away rules.
- §G fraud / paperwork verification checklist (vehicle-agnostic, reusable):
  walk-away triggers, VIN three-location match, curbstoning, odometer fraud,
  bill of sale, title transfer, payment protection, stolen-vehicle, title-
  jumping, after-purchase steps.
- Step 5.25 head-to-head comparison template with information-asymmetry rule.
- Step 3 Location Quality Tier scoring (Birmingham AL metro table A/B/C/D)
  with adj-CPM modifier; sheet schema gains Adj.CPM + Tier columns.
- Hard-disqualification rules added: non-running engine keywords, mileage
  inconsistency, mandatory description scrape.
- Drive MCP overwrite limitation documented; sheet ID now read from memory
  rather than hardcoded.
- FB Marketplace operational facts: Birmingham AL city ID 107739635926718,
  /search? vs /vehicles? query handling, React-controlled inline composer,
  send-button selector, seller-name extraction regex.
- scheduled-tasks/used-car-search-{morning,afternoon}: tier A/B priority
  flag, sheet ID read from memory.
- Reference PDF generator scripts checked in for reuse.

* fix: re-approve PRs where approval predates last commit (stale approval)

Instead of skipping PRs that already have any APPROVED review, skip only
those where reviewDecision is not REVIEW_REQUIRED. This handles PRs where
a bot approval was posted before a new commit was pushed — GitHub's ruleset
engine treats those approvals as stale even with dismiss_stale_reviews=false.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: robustly extract JSON verdict from mixed claude --print output (#17)

Claude's --print mode can prefix the JSON verdict with conversational
preamble text, causing jq to fail with parse errors. Add
extract_verdict_json to engine.sh and wire all three cascade action
call sites in review-one-pr.sh to use it.

Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: extract_verdict_json — check dest file first (agent Bash-write style)

The cascade-action agent writes its verdict JSON to \$OUTPUT_FILE via a
Bash tool call, then prints a text summary to stdout. The previous fix
only scanned stdout (the .raw file), which contained no JSON. The agent-
written file at \$dest (= \$OUTPUT_FILE) was already correct — just not
checked. Now check \$dest first before falling back to stdout scan.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Enforce MAX_REVIEW_CYCLES cap before running the cascade (#18)

* fix: enforce MAX_REVIEW_CYCLES cap with human-escalation marker

Previously MAX_REVIEW_CYCLES only gated AI delegation in
post-pr-review.sh — the cascade itself ran on every cron tick
regardless of how many cycles had accumulated. Real-world result:
a PR could rack up 9+ review cycles (we observed exactly that on
ContentTwin#100) before any cap took effect.

Add a pre-cascade check in review-one-pr.sh: when the count of
existing `<!-- pr-review-agent v1 sha=... -->` markers is at or
above MAX_REVIEW_CYCLES (default 3), post one escalation comment
marked `<!-- pr-review-agent escalation -->`, label
needs-human-review, request don-petry, and exit 100 (skip
sentinel — doesn't burn the MAX_PRS budget). The escalation
marker doubles as the no-spam guard: subsequent runs detect it
and exit 100 immediately.

Also reuse a single `gh pr view` for both the cycle count and the
escalation-marker check.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* review feedback: surface escalation-comment failures, drop dead unset

- Drop `2>/dev/null || true` on `gh pr comment` so a failed escalation
  post is visible in the workflow log instead of silently leaving the
  PR with no marker (which would re-trigger the cap path next tick).
- Reword the cycle-count comment from "AI delegation loops" to "review
  loops" — the cap also catches cascade-only loops where every cycle
  approves and a new commit lands before merge.
- Remove the redundant `unset PR_BODIES` — never exported, no leakage.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>

* Reliability hardening: session circuit breaker, timeouts, retry, dedup (#20)

* feat: session circuit breaker, per-tier timeouts, retry, and triage hard-fail

Reliability hardening for the PR review agent.

1. Session circuit breaker (.github/workflows/pr-review.yml): on any
   non-zero, non-100 exit from review-one-pr.sh (general failure or rate
   limit on the fallback engine), break the per-PR loop, log a clear
   error annotation naming the failing PR and reason, and exit the step
   with code 1 so the run shows red. Prevents one systemic problem from
   silently burning the entire candidate pool.

2. Per-tier timeouts (scripts/engine.sh): triage/deep/audit/action/duck
   each get their own bounded timeout (180/600/600/300/300s defaults,
   env-overridable). Previously only the duck had a timeout — a hung
   tier could burn the whole 60min job budget.

3. Retry-with-backoff on transient errors (scripts/engine.sh): triage
   retries once on 124/137/143 (timeout / signal kill) since its caller
   captures stdout via $(...) so retries are safe. Deliberately NOT
   applied to run_agentic/run_duck where stdout is redirected to a file
   — a retry there would corrupt the partial first-attempt output.

4. Triage non-JSON now hard-fails (scripts/review-one-pr.sh): replaces
   the silent fallback that synthesized a fake "escalate=MEDIUM" verdict
   and proceeded to deep review. With the new circuit breaker, loud
   failure is the right call — masking a broken triage was burning
   tokens on every PR while the workflow looked healthy.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix: stop stacking duplicate agent reviews on the same PR

Two bugs were causing the agent to leave multiple comments on the same PR.
Together they produced 10 stacked APPROVED reviews on petry-projects/ContentTwin#100.

Bug A — idempotency check is order-dependent (review-one-pr.sh):
The previous marker-discovery code did:
  ((.reviews // []) + (.comments // [])) | .[].body | grep marker | tail -1
This relies on the array concatenation order, not chronological order. When
old agent comments existed alongside newer agent reviews, tail -1 picked
the comment-array marker (older) over the review-array marker (newer),
causing the script to think the head SHA hadn't been reviewed and re-run.
Replaced with a single jq pipeline that tags each item with submittedAt /
createdAt, sorts by timestamp, and takes the actual most-recent marker.

Bug B — no cleanup of prior agent items (post-pr-review.sh):
After successfully posting a new review/comment, prior agent items were
left in place, accumulating forever. Added mark_prior_agent_items_obsolete
which, after a successful post:
  - dismisses prior APPROVED/COMMENTED/CHANGES_REQUESTED agent reviews via
    the GitHub dismissal API (UI shows them struck-through as Dismissed)
  - edits prior agent comments to wrap their body in a collapsed <details>
    block with a "Superseded by re-review at <SHA>" summary, plus a
    `<!-- pr-review-agent superseded -->` sentinel for idempotency

All cleanup API calls are best-effort — failures don't break the workflow.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(cleanup): warn on API failures, preserve global newest, file-stage JSON

Three fixes to mark_prior_agent_items_obsolete from the review of PR #20:

1. ::warning:: annotations on every cleanup API failure (review/comment
   list-fetch, individual review dismissal, individual comment fetch+edit).
   Previously these were silenced with `|| true`, so a permissions change
   on the dismissal endpoint would let duplicates stack indefinitely with
   no signal in the Actions UI. Cleanup is still non-fatal — the new post
   has already landed — but failures are now visible.

2. Preserve the globally-latest agent item across BOTH categories, not the
   newest of each category separately. The earlier code split reviews and
   comments and applied `[:-1]` to each, which left a stale fix-request
   comment in place when the new post was a review (or vice versa). The
   one-off cleanup of ContentTwin#100 hit exactly this case: 12 stacked
   reviews collapsed to 1, but a stale comment from 2026-04-25 (SHA
   cd9132d6) was preserved as "newest comment" even though the latest
   review at SHA 3af8c8ee was newer overall. Now: compute the max
   timestamp across both feeds, exclude items at that timestamp.

3. Stage API responses to disk (`mktemp` + `jq <file>`) instead of routing
   through `--argjson "$var"`. The old approach broke on rare unescaped
   control chars in user-authored comment bodies (jq refused to parse the
   resulting shell-vared JSON). File-based input sidesteps the shell
   pipeline entirely.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>

* Remove car-hunt skill and scheduled tasks (moved to don-petry/don-petry) (#21)

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: apply Copilot fallback hardening and reuse GH_PAT for Copilot auth

- Use secrets.GH_PAT (existing personal account token with Copilot
  subscription) instead of a separate COPILOT_GITHUB_TOKEN secret
- Pin actions/create-github-app-token to SHA (v3.1.1) for supply chain safety
- Add post-install verification for gh-copilot extension with warning on failure
- Guard Copilot fallback path: skip PR gracefully if extension is not installed

Addresses issue #24 workflow fixes (1-3) that could not land via PR #25
due to GitHub App lacking workflows permission.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: switch Copilot engine to gh built-in and fix app-id deprecation

- Remove gh extension install (github/gh-copilot conflicts with built-in alias)
- Replace with gh copilot --version check using COPILOT_GITHUB_TOKEN (GH_PAT)
- Update all engine.sh copilot invocations from bare `copilot` binary to
  `gh copilot suggest --target shell` with GH_TOKEN overridden to GH_PAT
  so the user token (with Copilot subscription) is used, not the App token
- Fix actions/create-github-app-token: app-id → client-id (same secret value,
  just the renamed input in v3.x)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: harden Copilot fallback path and remove stale worktrees (#25)

- gh-copilot install: add --force flag and post-install verification
  warning so silent failures due to built-in alias conflict are visible
  in workflow logs instead of being silently swallowed by `|| true`
- fallback pre-flight check: verify `gh extension list | grep copilot`
  before switching to Copilot engine; if unavailable, skip the PR and
  continue the batch rather than session-aborting and dropping all
  remaining candidates (fixes the 27-PR drop on run #503)
- pin actions/create-github-app-token to SHA for v3.1.1 (Node.js 24
  compatible) to prevent hard break on 2026-06-02 when GitHub forces
  Node.js 24 as default runtime
- remove 6 stale Claude Code worktrees from git tracking; add
  .gitignore entry to prevent future worktrees from being committed
  (eliminates exit-128 warning on every actions/checkout post-job sweep)

Closes #24

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: petry-projects-pr-review-agent[bot] <petry-projects-pr-review-agent[bot]@users.noreply.github.com>

* ci: add pre-flight dedup check to prevent duplicate claude-issue PRs (#26)

Before invoking Claude on a labeled issue, a new shell step queries
for any open PR with a branch matching claude/issue-NNN-* (or a body
containing "Closes #NNN"). If one is found it posts a comment on the
issue pointing to the existing PR and skips the Claude step entirely.
A secondary prompt instruction tells Claude to check first and push to
the existing branch rather than opening a new PR.

Motivated by issue petry-projects/google-app-scripts#171, where the
claude label was re-applied three times and each run created a fresh
PR unaware of the prior attempts.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor: migrate to machine user PAT auth (closes #27) (#28)

* refactor: migrate from GitHub App to machine user PAT auth

Closes #27

GitHub Apps cannot be listed in CODEOWNERS, blocking PRs in repos with
require_code_owner_review: true. Switch all workflows to use a machine
user account's fine-grained PAT (DON_PETRY_BOT_GH_PAT secret), which can
join an org team listed in CODEOWNERS.

Workflows: removed actions/create-github-app-token steps in pr-review,
fix-stuck-prs, backfill-approvals, and daily-pr-review-health. All now
use secrets.DON_PETRY_BOT_GH_PAT directly.

Docs: renamed GITHUB_APP_SETUP.md to MACHINE_USER_SETUP.md with full
rewrite covering account creation, CODEOWNERS config, PAT generation,
and rotation. Updated auth sections in IMPLEMENTATION.md, SETUP.md,
STATUS.md, DOCUMENTATION.md, README.md.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* refactor: switch to org-scoped PAT secret DON_PETRY_BOT_PETRY_PROJECT_PAT

Replace DON_PETRY_BOT_GH_PAT with DON_PETRY_BOT_PETRY_PROJECT_PAT — the
new fine-grained PAT scoped to the petry-projects org (resource owner =
petry-projects). The previous PAT was scoped to the donpetry-bot personal
namespace and had no repository access.

The old DON_PETRY_BOT_GH_PAT secret is retained for any future use against
don-petry's personal repos.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* chore: ignore .claude/scheduled_tasks.lock state file

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>

* refactor: merge approval repair scripts into single automation

- Consolidate backfill-approvals.sh and fix-stuck-prs.sh into repair-pr-approvals.sh
- Iterate all repos in both orgs instead of assuming author
- Verify no existing APPROVED review before posting
- Copy original agent comment as review body
- Enable auto-merge when posting approval if needed
- Single workflow with 30-min timeout

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: add @mention trigger for on-demand PR reviews (#30)

Adds repository_dispatch support so commenting @petry-review-bot on any
PR fires an immediate review without waiting for the hourly schedule.

- pr-review.yml: new `repository_dispatch` trigger (type: pr-review-mention),
  per-PR concurrency group for mention runs, FORCE_REVIEW and DRY_RUN
  env vars that handle both workflow_dispatch and repository_dispatch paths
- review-one-pr.sh: FORCE_REVIEW=true bypasses idempotency so a mention
  always runs a fresh cascade even if the head SHA hasn't changed
- templates/mention-listener.yml: deploy to petry-projects/.github;
  listens for @petry-review-bot, validates commenter trust, posts ack,
  sends repository_dispatch (requires Contents:write, not Actions:write)

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: harden rebase, review dismissal, and health log diagnostics (#40)

Addresses 4 code-actionable recommendations from health check report #33:

- [CRITICAL] Make rebase/branch-update non-fatal — 403/504 during rebase
  emits ::warning:: instead of aborting the batch session (#34)
- [MEDIUM] Add retry with exponential backoff (3 attempts) for transient
  5xx on update-branch API; break immediately on 4xx (#37)
- [MEDIUM] Guard review dismissal with state re-check before calling
  dismiss API, preventing 422s from race conditions (#38)
- [LOW] Surface missing log warnings in health check script (#39)
- Fix: skip auto-merge when branch is still BEHIND after failed rebase

Closes #34, closes #37, closes #38, closes #39

* refactor: migrate to org-wide .github-private convention (#41)

* refactor: parametrize hardcoded identity values for org migration

Replace all hardcoded references to don-petry, petry-review-bot, and
don-petry/pr-review-agent with environment variables that default to
the current values. This allows the agent to be configured for different
orgs/users via repo variables.

Changes:
- Scripts use $REVIEWER_USER, $TARGET_ORG, $BOT_USER, $AGENT_REPO
- Prompts no longer reference specific GitHub usernames
- Workflows use org-level GH_PAT_WORKFLOWS secret instead of
  repo-level DON_PETRY_BOT_PETRY_PROJECT_PAT
- Health check uses context.repo.owner/repo for issue creation
- Variables are set in workflow env block with defaults

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add Copilot custom agent profiles for org-wide use

Create /agents/ directory with three agent profiles:
- pr-reviewer: Multi-tier cascading PR review
- feature-ideator: Feature idea generation and prioritization
- compliance-auditor: Org standards compliance checking

These are Copilot custom agent profiles that become available
org-wide in the .github-private repo convention.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Squashed 'frameworks/bmad-method/' content from commit e36f219c

git-subtree-dir: frameworks/bmad-method
git-subtree-split: e36f219c81b6010d4aae423ba12f49edb5b6e31a

* Squashed 'frameworks/spec-kit/' content from commit 11f49ebf

git-subtree-dir: frameworks/spec-kit
git-subtree-split: 11f49ebfb2f6af55345cb4bd9a7906acd211e56f

* Squashed 'frameworks/gsd/' content from commit 304c1a13

git-subtree-dir: frameworks/gsd
git-subtree-split: 304c1a1302564c45af252bbba4bcc5350e7dac3a

* docs: update README for .github-private org infrastructure role

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* ci: add dependabot-automerge.yml workflow (#73)

Adds the required dependabot-automerge.yml workflow from the org
standard template (petry-projects/.github/standards/workflows/).
This is a thin caller stub that delegates to the org-level reusable
workflow.

Closes #48

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Don Petry <don-petry@users.noreply.github.com>

* fix(pr-review): unblock queue starvation from self-authored PRs (#96) (#97)

* fix(pr-review): unblock queue starvation from self-authored PRs (#96)

A self-authored PR sorted first in the candidate list and triggered
"Can not approve your own pull request" — which the session-fatal
abort treated like an engine error, skipping all 28 remaining PRs
on every run.

- list-prs.sh: filter out PRs authored by REVIEWER_USER at enumeration
- post-pr-review.sh: catch the GraphQL self-approval error and exit 100
  (no-op) instead of 1, so a stray self-PR can no longer abort the batch
- engine.sh: gh copilot renamed --target to --agent; fix the rubber-duck
  invocations so tier-2 cross-engine review works again
- review-one-pr.sh: stop appending a duplicate "0" to REVIEW_CYCLE when
  grep -c finds no markers under set -o pipefail (was breaking the
  cycle-cap integer comparison)

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

* fix(pr-review): authenticate as bot, harden enumeration, address review

- Workflow now runs as AGENT_USER (default don-petry-bot), distinct from
  REVIEWER_USER (the human, don-petry). The self-approval rejection that
  caused #96 came from the agent and the human sharing one identity.
- list-prs.sh filters self-authored PRs against AGENT_USER, and validates
  AGENT_USER against the GitHub username charset before interpolating into
  the jq filter (Copilot review feedback).
- review-one-pr.sh uses printf '%s\n' instead of echo for PR_BODIES, since
  PR body content is user-authored and could begin with -n/-e or contain
  backslash escapes (Copilot review feedback).
- AGENT.md guideline updated to reflect that self-authored PRs are
  intentionally excluded (CodeRabbit review feedback).

Operator note: rotate the GH_PAT_WORKFLOWS secret to a token owned by
don-petry-bot (with read:org added) for the bot-account behavior to take
effect at runtime.

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

* refactor(pr-review): collapse to single BOT_USER, route escalations via CODEOWNERS

Drop AGENT_USER and REVIEWER_USER. The workflow now has one identity:
BOT_USER (default don-petry-bot), which both owns the repos to scan and
gets filtered out as the self-approval blocker. Human escalation no
longer hard-codes a single reviewer; instead, scripts/request-codeowners-
review.sh parses CODEOWNERS in the PR's repo and requests review from
every @user / @org/team mention.

- list-prs.sh: BOT_USER for both gh-repo-list and self-author filter
- review-one-pr.sh, post-pr-review.sh: replace --user "$REVIEWER_USER"
  with the CODEOWNERS helper
- repair-pr-approvals.sh: same gh-repo-list switch
- pr-review.yml: drop AGENT_USER/REVIEWER_USER, set BOT_USER default
  to don-petry-bot
- AGENT.md: updated guideline to describe single-identity model

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

* refactor(pr-review): extract batch loop, hoist env, drop redundant step

Workflow YAML drops from 231 to 111 lines by moving the per-PR review
loop into scripts/review-batch.sh. The script can be syntax-checked and
exercised locally; the workflow now just wires env vars and dispatches.

- Hoist GH_TOKEN, MAX_PRS, CANDIDATE_LIMIT to job-level env (were
  repeated on individual steps).
- Inline `gh auth status` into the install step; remove the standalone
  Verify auth step (its only output was a one-line auth dump).
- Drop the `if: steps.list.outputs.count != '0'` guard and the step's
  `id`/output: review-batch.sh handles empty input as a no-op.
- Collapse the duplicated summary-string branch in the review loop into
  a single template with a conditional fragment.

No behavior change.

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

* perf(pr-review): cache claude-code CLI install across runs

`npm install -g @anthropic-ai/claude-code` ran on every workflow start
(~30s). Switch to a per-user npm prefix (~/.npm-global) and cache that
directory via actions/cache, keyed on CLAUDE_CODE_VERSION + runner OS.

A `command -v claude` guard makes the install a no-op on cache hit, so
the only cost on subsequent runs is the cache restore.

CLAUDE_CODE_VERSION defaults to 'latest' (cache persists until manually
flushed); set the repo variable to pin a specific version for fully
reproducible caching.

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

---------

Co-authored-by: Claude <noreply@anthropic.com>

* chore: rename GH_PAT_WORKFLOWS secret to DON_PETRY_BOT_GH_PAT (#99)

The previous name was generic and didn't tell you which account the PAT
belonged to. The new name makes the binding explicit: this secret is the
PAT owned by don-petry-bot, used as BOT_USER throughout the PR-review
workflows.

Operator follow-up before this can be merged:
- Add a new repo secret DON_PETRY_BOT_GH_PAT containing the bot's PAT
  (with repo, workflow, and read:org scopes).
- After merge, the old GH_PAT_WORKFLOWS secret can be deleted.

Affected workflows:
- pr-review.yml (1 use)
- claude.yml (4 uses, with || github.token fallback)
- daily-pr-review-health.yml (1 use)
- repair-pr-approvals.yml (1 use)

scripts/pr_review_health.sh's error message is updated to point at the
new name as well.

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

Co-authored-by: Claude <noreply@anthropic.com>

* chore: remove frameworks directory (#101)

Co-authored-by: Gemini CLI <gemini-cli@example.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore all erroneously deleted files (items 4-34) (#104)

* revert: restore .github/workflows/daily-pr-review-health.yml

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore .github/workflows/repair-pr-approvals.yml

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore agents/pr-reviewer.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/cascade-action.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/deep-review.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/rubber-duck.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/security-audit.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/shared.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/single-review.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/synthesize-duck.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/synthesize.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/triage.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/engine.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/list-prs.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/post-pr-review.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/pr_review_health.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/repair-pr-approvals.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/request-codeowners-review.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/review-batch.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/review-one-pr.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore templates/mention-listener.yml

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore AGENT.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore BOT_SETUP.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore DOCUMENTATION.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore IMPLEMENTATION.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore MACHINE_USER_SETUP.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore PR_REVIEW_AGENT_FAILURE_REPORT.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore PR_REVIEW_FAILURE_INVESTIGATION.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore SETUP.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore STATUS.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore README.md to pre-change state

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore .github/workflows/daily-pr-review-health.yml

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat: add Gemini Pro support and optimize PR review fallback chain (#102)

* Merge main and resolve conflicts

* Enhance pr-review workflow: add Gemini support and refactor fallback logic

* Address Copilot review comments: tighten regex, fix fallback summary, and align docs

* docs: move agent documentation to docs/ folder and rename files

* docs: align secret names and fix casing in index

* docs: organize pr-review-agent documentation under dedicated folder

* Address final PR comments: tighten regex, add preflight checks, fix bot names, and cleanup docs

---------

Co-authored-by: Gemini CLI <gemini-cli@example.com>

* fix(pr-review): gate rate-limit detection on non-zero exit code

Broad patterns like `plan.*limit` and `claude.*usage` could match content
in a successful triage summary, triggering a false-positive engine fallback.
Guard the check with TRIAGE_RC != 0 so it only fires when the provider
command actually failed.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(claude): sync inlined workflow with org standard (#109)

* fix(claude): sync inlined workflow with org standard

Brings the inlined claude.yml up to parity with
petry-projects/.github/.github/workflows/claude-code-reusable.yml.

Changes:
1. Bot allow list for pull_request_review_comment
   Add coderabbitai[bot], Copilot, copilot-pull-request-reviewer[bot],
   and gemini-code-assist[bot] alongside the existing OWNER/MEMBER/
   COLLABORATOR check. These bots have author_association 'NONE' so
   their review comments were always skipped.

2. check_run trigger + claude-ci-fix job
   Port the CI failure auto-fix feature from the reusable. When a check
   fails on a PR, Claude diagnoses and fixes it automatically.
   Adapted to use DON_PETRY_BOT_GH_PAT (this repo's secret name).

3. claude-code-action SHA bump: v1.0.89 → v1.0.119
   Update both the claude and claude-issue jobs.

4. paths-ignore on pull_request trigger
   Prevents the workflow from firing on PRs that only change
   claude.yml itself, avoiding the Anthropic OIDC validation
   failure (workflow file must match default branch at token exchange).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(claude): guard bot allow list against fork PRs

Same fix as petry-projects/.github PR #238 — add same-repo guard
for bot-triggered pull_request_review_comment runs.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(claude): address ci-fix self-loop and fork PR security

- Replace Claude Code name prefix check with explicit job name list to
  correctly prevent self-loops (check_run names for inlined workflows
  are bare job names, not workflow-prefixed)
- Add fork PR trust gate in Resolve PR number step: verify head repo
  matches target repo before running Claude with privileged credentials

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* ci: replace inlined claude.yml with standard thin-caller stub

The inlined version embedded all three jobs (claude, claude-ci-fix,
claude-issue) directly and threaded DON_PETRY_BOT_GH_PAT through
checkout tokens and github_token, causing the bot's PAT to author
PRs and comments instead of github-actions[bot].

Replace with the standard thin caller that delegates to the org-level
reusable workflow (claude-code-reusable.yml@v1) via secrets: inherit,
which is the correct pattern per:
petry-projects/.github/standards/workflows/claude.yml

* fix: replace gh copilot suggest with GitHub Models REST API (#151)

* fix: replace gh copilot suggest with GitHub Models REST API (#147)

The `gh copilot suggest -p "$(cat <file>)"` invocation failed with
"Invalid command format" because:
1. The `-p` flag is not valid syntax in modern `gh copilot` built-in versions.
2. `gh copilot suggest` is a shell-command suggestion tool; it does not
   accept large PR prompts or return structured JSON responses.
3. Passing a multi-thousand-line prompt via `$(cat ...)` can hit ARG_MAX.

The non-zero exit was then misclassified by the rate-limit detector, which
aborted the entire session and skipped all remaining PRs in the queue.

Fix: replace all three copilot invocations (run_triage, run_agentic,
run_duck) with a new `copilot_chat` helper that calls the GitHub Models
REST API directly via curl. The API is OpenAI-compatible, versioned via
`X-GitHub-Api-Version`, stable against gh CLI version changes, and accepts
arbitrary prompt sizes using python3 for safe JSON encoding.

Additional changes:
- Add `COPILOT_API_MODEL` var (default `openai/o4-mini`) overrideable via env.
- run_agentic/run_duck copilot cases now also write output to $OUTPUT_FILE
  so callers that check that path directly (deep review, audit) find the JSON.
- Rate-limit responses (HTTP 429) are echoed to stdout so the existing
  `is_rate_limited()` detector fires correctly for engine fallback.
- Add pre-flight smoke test in review-batch.sh: tests GitHub Models API
  connectivity with a one-liner prompt before processing any PRs, so auth
  or model errors surface immediately as a clear setup failure.
- Add tests/test_copilot_chat.sh: unit tests for the JSON payload builder
  with edge-case prompts (quotes, newlines, # headings, large diffs, Unicode).

Closes #147

Co-authored-by: Don Petry <don-petry@users.noreply.github.com>

* fix: scope COPILOT_API_MODEL export to copilot engine only

Move the `export COPILOT_API_MODEL` from the common exports block into the
`copilot)` case where the variable is set, so it is not exported as an
empty/unset variable when the engine is `claude` or `gemini`. The
`copilot_chat` fallback `${COPILOT_API_MODEL:-openai/o4-mini}` still works
correctly when `DUCK_ENGINE=copilot` under a non-copilot primary engine.

Co-authored-by: Don Petry <don-petry@users.noreply.github.com>

* fix: address review comments — temp file for payload, fail-fast source, streaming output

- copilot_chat: write JSON body to mktemp file, pass to curl as @file to
  avoid ARG_MAX for large PR diffs (was --data-binary "$body")
- copilot_chat: add :? guard on COPILOT_GITHUB_TOKEN for a clear error
  instead of generic "unbound variable" under set -u
- run_agentic / run_duck copilot paths: stream directly to stdout (and
  tee to OUTPUT_FILE when set) rather than buffering the full response
  into a shell variable, which forced large outputs into memory and
  stripped trailing newlines
- review-batch.sh pre-flight: fail fast if source engine.sh fails
  (was silently ignored with || true)
- review-batch.sh pre-flight: build smoke-test JSON payload via python3
  + temp file instead of shell string interpolation to avoid JSON
  injection if COPILOT_API_MODEL contains special characters
- Clarify that openai/o4-mini is the correct April-2025 o4-generation
  model name, not a typo for o1-mini or gpt-4o-mini

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Don Petry <don-petry@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* feat: prioritise .github/.github-private PRs, oldest-first within tier (#155)

* feat: prioritise .github/.github-private PRs, oldest-first within tier

- list-prs.sh: add createdAt to JSON fetch; emit priority|createdAt|url
  lines (priority 0 for .github/.github-private, 1 for everything else)
- Replace final sort -u with a two-pass sort: deduplicate by URL, then
  sort by priority asc then createdAt asc; strip sort keys with cut
- tests/test_list_prs_sort.sh: 16 tests covering priority classification,
  oldest-first ordering, mixed scenarios and deduplication
- .github/workflows/test.yml: run both unit-test files on every PR / push

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* test: add dedup edge case for same URL with conflicting priorities

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: remove temperature from copilot_chat payload (o4-mini incompatible)

The GitHub Models API rejects temperature=0 for reasoning models like
o4-mini with HTTP 400:
  'Unsupported value: temperature does not support 0 with this model.
   Only the default (1) value is supported.'

This caused the rubber duck (DUCK_ENGINE=copilot / DUCK_MODEL=o4-mini)
to fail with HTTP 400 on every claude-engine run, silently degrading
every review to deep-only with no cross-engine sanity check.

Fix: remove temperature from the copilot_chat JSON payload entirely —
the API defaults to 1, which is the only supported value for o4-mini.

Also:
- Update tests/test_copilot_chat.sh: sync build_payload to match and
  flip Test 9 to assert temperature is ABSENT (not 0).
- Add .github/workflows/test.yml: run unit tests on every PR and push
  to main so regressions are caught before merge.

Reproducer: job/75690347409 — duck failure logged as:
  copilot_chat: HTTP 400 from GitHub Models API
  {"error":{"message":"Unsupported value: 'temperature' does not
   support 0 with this model..."}}

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: address PR review comments

- list-prs.sh: tighten priority regex to /[.]github(-private)?/pull/
  so path-boundary anchor prevents false positives (e.g. foo.github)
- list-prs.sh: replace printf pipeline with here-string to avoid ARG_MAX
  limits on large PR sets
- test.yml: add permissions: contents: read (least-privilege, CodeQL fix)
- test.yml: add timeout-minutes: 5 to prevent hung runs
- test_list_prs_sort.sh: sync sort_entries helper (here-string + || true)
  and classify regex to match updated list-prs.sh exactly

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: don-petry <don@petry.dev>

* fix: restore pr-review.yml content (accidentally emptied in revert)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: restore pr-review.yml (file was accidentally emptied)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore(deps): bump actions/cache from 4 to 5 (#165)

Bumps [actions/cache](https://github.com/actions/cache) from 4 to 5.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): bump petry-projects/.github/.github/workflows/claude-code-reusable.yml (#164)

Bumps [petry-projects/.github/.github/workflows/claude-code-reusable.yml](https://github.com/petry-projects/.github) from 1 to 2.
- [Commits](https://github.com/petry-projects/.github/compare/v1...v2)

---
updated-dependencies:
- dependency-name: petry-projects/.github/.github/workflows/claude-code-reusable.yml
  dependency-version: '2'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* fix(list-prs): search DELEGATION_ORGS and drop --checks success pre-filter

- Add iteration over all orgs in DELEGATION_ORGS (e.g. don-petry) so PRs
  in those orgs enter the candidate pool. Previously only BOT_USER and
  TARGET_ORG were searched.
- Remove --checks success from org repo searches. GitHub excludes PRs from
  repos with no CI configured when this flag is used, silently dropping all
  PRs from repos like .github, .github-private, google-app-scripts, and
  TalkTerm. review-one-pr.sh already enforces CI gating per-PR and treats
  empty statusCheckRollup as passing, so the pre-filter is redundant and
  harmful.
- Add --limit 200 to gh repo list calls to handle org growth beyond gh's
  default 30-repo cap.

* feat: add code-quality ruleset (compliance fix #60) (#86)

Creates the required `code-quality` repository ruleset enforcing required
status checks on the default branch, as mandated by the org standard:
standards/github-settings.md#code-quality--required-checks-ruleset-all-repositories

Required checks:
- SonarCloud (code quality analysis)
- CodeQL (SAST)
- agent-shield / AgentShield (agent security scan)
- dependency-audit / Detect ecosystems (dependency vulnerability scan)

Bypass actors:
- OrganizationAdmin (always) — emergency override
- dependabot-automerge-petry Integration (always) — Dependabot auto-merge

The ruleset was applied directly via GitHub API. This file documents the
configuration as code for auditability and future reapplication.

Closes #60

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Don Petry <don-petry@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* feat(prompts/dev-lead): add human prompt template

* feat(prompts/dev-lead): add human-pr prompt template

* feat(dev-lead): implement dev-lead agent Phases 0-6

* fix: per-PR isolation and single-review retry (closes #132)

- review-batch.sh: non-rate-limit per-PR failures (exit code 1) no
  longer abort the session. SESSION ABORTED EARLY is now reserved for
  the rate-limit-on-fallback-engine case (exit code 2) only. All other
  failures are counted and logged; remaining candidates continue.

- review-one-pr.sh: single-review step retries up to
  SINGLE_REVIEW_MAX_RETRIES (default 2) times with a
  SINGLE_REVIEW_RETRY_DELAY_SEC (default 15s) gap before giving up.
  On exhaustion, the PR is flagged needs-human-review and the script
  exits with code 1, which the updated batch treats as a non-fatal
  per-PR failure. Raw model output and stderr are logged on each failed
  attempt for post-mortem visibility.

Root cause of run #25707852006: claude-opus-4-7 returned a verbose
non-JSON response for PR #129; the old code treated that as fatal and
skipped 35 remaining candidates.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix: rate-limit detection and per-attempt stderr logs in single-review retry

Address inline review comments on PR #133:

- Rate-limit check: after each run_agentic call, inspect both stdout
  (VERDICT_JSON.raw) and stderr (SINGLE_LOG) with is_rate_limited before
  retrying. A rate-limit match exits immediately with code 2 so
  review-batch.sh can trigger engine fallback — consistent with triage and
  deep-review tiers. Previously a rate-limited single-review would burn all
  retries and exit 1 (per-PR failure), silently leaving the batch on the
  same rate-limited engine for all remaining PRs.

- Per-attempt log files: stderr is now written to
  single-review-attempt-N.log rather than a single overwritten file, so
  no earlier-attempt errors are lost. Each attempt logs its own stderr
  inline on failure; the fallback path cats all attempt logs for
  post-mortem visibility.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* feat(dev-lead): Phase 0 test infrastructure and Phase 1 intent stub

Phase 0: full test harness for the dev-lead agent —
  26 event fixtures (all valid JSON with _test_expected_intent),
  stub claude/gemini engines, mock gh binary, CI failure log sample,
  bats helpers (stub-engine, mock-gh, assert-env, prompt-vars),
  7 prompt templates with VARIABLES declarations, preflight script,
  prompt coverage integration test, and test-dev-lead.yml CI workflow.

Phase 1: dev-lead.yml trigger workflow (all 7 event types, dispatch + ci-relay
  jobs) and dev-lead-intent.sh stub (anti-loop guard live; all other events
  emit skip/not-implemented).  14/14 bats unit tests pass.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): add permissions blocks to test workflow jobs (CodeQL)

* fix(ci): auto-fix for lint / eslint [skip ci-relay]

* feat(dev-lead): Phases 2-6 — CI fix, review fix, issue, engine fallback

Phase 2: run_writer/run_writer_with_fallback in engine.sh, full intent
routing in dev-lead-intent.sh, dev-lead-fix-ci.sh handler, workflow wiring.

Phase 3: dev-lead-fix-reviews.sh handles fix-reviews, fix-bot-comment,
human, human-pr, rebase intents. Full review routing in intent classifier.

Phase 5: dev-lead-fix-issue.sh handles the issue intent with dedup guard
and branch/PR creation.

Phase 6: run_writer_with_fallback with claude→gemini→copilot fallback chain.

Tests: 77 unit tests across 8 new .bats files, all passing. Updated Phase 1
stub tests to reflect actual Phase 2+ routing behavior.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): address review comments — dispatch JSON, dead code, portable stub gh

* fix(dev-lead): pre-flight after intent, fork URL check, review-batch exit code, retry comment

* fix(dev-lead): install bats-core from GitHub to avoid root requirement

* fix(security): move event values to env vars to prevent script injection (SonarCloud)

* fix(security): move CLAUDE_CODE_VERSION to env block (SonarCloud script injection)

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* feat(dev-lead): Phase 1.5 — reusable workflow for cross-repo adoption + Phase 7 shadow period

* feat(dev-lead): add reusable workflow (Phase 1.5) and begin shadow period (Phase 7)

- Create .github/workflows/dev-lead-reusable.yml: workflow_call entry
  point for other repos. Checks out .github-private scripts/prompts into
  .dev-lead/, then runs the same intent-classify + handler pipeline as
  dev-lead.yml with PROMPTS_DIR=.dev-lead/prompts/dev-lead.
- Add PROMPTS_DIR env-var support to dev-lead-fix-ci.sh,
  dev-lead-fix-reviews.sh, and dev-lead-fix-issue.sh so the reusable
  workflow can point scripts at the sparse-checkout path without changing
  CWD. Defaults to prompts/dev-lead (backwards-compatible for dev-lead.yml).
- Annotate dev-lead.yml with Phase 7 shadow-period window (2026-05-15
  through ~2026-05-29): claude.yml and dev-lead.yml run in parallel until
  regressions clear, then claude.yml is removed.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* chore(dev-lead): update shadow-period tracking issue ref to #180

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): document PAT requirement and pin ref:main on private checkout

* fix(dev-lead): P1 review fixes — ci-relay in reusable, .dev-lead gitignore, export PROMPTS_DIR, trim header

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): rate-limit detection, separate status, and retry cron (#192) (#196)

* chore(dev-lead): decommission claude.yml, bump ACTION_TIMEOUT_SEC to 600s

- Delete .github/workflows/claude.yml — replaced by dev-lead.yml (shadow
  period complete, tracking issue #180).
- scripts/engine.sh: raise ACTION_TIMEOUT_SEC default 300→600s to reduce
  timeout failures on large-repo fix-ci runs (PR #80 had 3 timeouts).
- AGENTS.md: remove claude.yml immutability exemption; note dev-lead.yml
  as the active AI automation workflow and its edit-via-reusable pattern.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* refactor: replace Claude analysis with pure gh/jq telemetry in health check

* refactor: drop Node/Claude steps from daily-pr-review-health workflow

* rename: daily-pr-review-health → actions-fleet-monitor

* rename: daily-pr-review-health → actions-fleet-monitor

* fix(agents-md): clarify dev-lead.yml vs dev-lead-reusable.yml scope

dev-lead.yml in .github-private runs inline steps (not a caller stub).
Behavior changes for this repo go to dev-lead.yml directly; changes that
affect all org repos via the cross-repo reusable go to dev-lead-reusable.yml.

Addresses Copilot review on PR #194.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): rate-limit detection, separate status, and retry cron

Closes #192. Implements all phases from the revised plan.

**Phase 0 — fix run_writer stdout capture (prerequisite)**
- engine.sh: capture stdout via `tee` to a tempfile so `is_rate_limited`
  can inspect the output; old code read `/tmp/dev-lead-writer-stderr`
  which was never written (claude --print outputs to stdout, not stderr).
  Fallback engines were never tried in practice.
- engine.sh: add `parse_reset_time` to extract ISO timestamp from
  `resets H:MMpm (UTC)` in engine output and write it to
  `/tmp/dev-lead-rate-limit-reset` for callers to embed in markers.

**Phase 1 — separate status=rate-limited from status=failed**
- fix-ci.sh: detect engine exit 2 → post `status=rate-limited` (not
  `status=failed`); embed parsed reset time in marker body; exit 2.
- fix-ci.sh: fix `check_idempotency` to treat `status=rate-limited`
  as retriable — only block on terminal statuses (applied, failed,
  no-changes). Previously, rate-limited markers blocked all retries.
- fix-ci.sh: `count_recent_failures` already filtered to `status=failed`;
  add explicit comment confirming rate-limited markers are excluded.
- fix-ci.sh: add `has_rate_limited_marker` dedup check so the same SHA
  never accumulates more than one rate-limited comment.
- fix-reviews.sh: on engine exit 2 for all five intent types
  (fix-reviews, fix-bot-comment, human, human-pr, rebase), post a
  `status=rate-limited` marker with embedded reset time and exit 2.
- fix-reviews.sh: for `human` and `human-pr` intents, also post a
  user-visible acknowledgment comment so users know their request was
  received and will be retried.

**Phase 2 — scheduled retry cron for fix-ci**
- dev-lead-retry.sh: new script scans all open PRs across TARGET_ORG
  for `status=rate-limited` markers on current HEAD SHA; skips PRs
  whose reset time is still in the future; staggered dispatches (30s
  between repos) to prevent cascading org-level rate-limit hits.
- dev-lead-retry.yml: new workflow — scheduled every 2 hours (dual
  offsets to work around GitHub scheduler skew), plus workflow_dispatch.

**Phase 3 — retry for fix-reviews intents (same cron)**
- dev-lead-retry.sh already handles all five fix-reviews intent types
  (fix-reviews, fix-bot-comment, human, human-pr, rebase) via
  `dev-lead-reviews-retry` dispatch events.
- dev-lead-intent.sh: add `dev-lead-reviews-retry` dispatch type routing
  to the intent classifier — maps intent_type from payload to the correct
  existing fix-reviews step.
- dev-lead.yml: add `dev-lead-reviews-retry` to repository_dispatch types.
- dev-lead.yml: unify concurrency group for all dispatch types to
  `dev-lead-pr-{pr_number}` to avoid per-type slot fragmentation.

**Tests**
- test_engine_writer.bats: 8 new tests covering rate-limit stdout detection,
  exit-2 mapping, fallback exhaustion, reset time parsing.
- test_fix_ci.bats: 5 new tests covering rate-limited status, exhaustion
  exclusion, idempotency pass-through, and dedup.
- test_fix_reviews.bats: 5 new tests covering all intent types + human ack.
- test_intent_ci.bats: 4 new tests for dev-lead-reviews-retry dispatch routing.
- e2e/scenarios/07-rate-limit-retry.sh: new E2E scenario covering all parts.
- 3 new event fixtures for repository_dispatch_reviews_retry_* payloads.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): address review comments on #196

Addresses all 18 review threads from Copilot, Codex, and Gemini.

**P1 bugs fixed:**
- dev-lead-retry.sh: all log echo calls in scan_pr_for_rate_limits and
  dispatch_* functions now write to stderr; only the final count is on
  stdout. Previously, log lines polluted the command substitution,
  causing arithmetic expansion to fail and aborting the scan on the
  first retry candidate.
- dev-lead-retry.yml: permissions: contents was read-only; creating
  repository_dispatch events requires contents: write.

**Correctness fixes:**
- dev-lead-retry.sh: restrict automated retries to intents whose context
  can be reconstructed at runtime (fix-reviews, human-pr, rebase). human
  and fix-bot-comment require USER_INSTRUCTION/COMMENT_BODY from the
  original event which cannot be re-fetched; they are explicitly excluded.
- dev-lead-fix-reviews.sh: resolve HEAD_SHA from the PR API when not
  provided by the triggering event (issue_comment intents carry no SHA).
  Without this, rate-limited markers had no sha= field and were
  invisible to the retry scanner.
- dev-lead-fix-reviews.sh: write terminal status=applied marker after
  successful fix-reviews, human-pr, rebase runs. Prevents the retry
  cron from re-dispatching the same intent on every subsequent tick when
  the SHA hasn't changed.
- dev-lead-retry.sh: check for reviews terminal marker before dispatching
  a retry (mirrors the existing fix-ci terminal-marker check).
- dev-lead-retry.sh: look up current check-run details (details_url, id)
  at dispatch time via the commits check-runs API, so retried fix-ci runs
  have full failure logs and annotations rather than empty fields.
- dev-lead-fix-ci.sh: embed check= field in the rate-limited marker so
  the retry cron knows which check run to look up.
- dev-lead-fix-reviews.sh: human intent now posts a "please re-mention
  @dev-lead" ack instead of "I'll retry automatically" since it will NOT
  be retried automatically. human-pr keeps the auto-retry ack (correct).

**Pagination (Copilot + Gemini):**
- dev-lead-fix-ci.sh: add --paginate to check_idempotency, count_recent_
  failures, has_rate_limited_marker.
- dev-lead-fix-reviews.sh: add --paginate to has_reviews_rate_limited_marker.
- dev-lead-retry.sh: add --paginate to PR comments and PR list fetches.

**Other improvements:**
- dev-lead-retry.yml: simplify to single cron schedule (15 */2 * * *);
  remove BOT_USER env var (unused in the script).
- dev-lead-retry.sh: raise repo list limit 200 → 1000; add hard-error
  when list_repos_for_org returns empty (surfaces token permission issues
  rather than silently scanning 0 repos).
- dev-lead-intent.sh: use jq -nc (compact, null-input) for context JSON
  construction — safe against values with special characters.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(pr-review): handle Gemini trust-mode failure and properly route engine fallbacks (#217)

* fix(pr-review): handle Gemini trust-mode failure and properly route engine fallbacks

Closes #208

* fix(ci): install gemini CLI and set trust mode for dev-lead agents

---------

Co-authored-by: Gemini CLI <gemini-cli@example.com>

* fix: resolve YAML syntax error in dev-lead-reusable workflow

* fix: address automated feedback from PR 217 (#219)

This addresses missed feedback from CodeRabbit (removing syntax errors in YAML) and gemini-code-assist (improving string concatenation in validate-engines.sh), and adds assertion output checks to tests.

Co-authored-by: Gemini CLI <gemini-cli@example.com>

* chore: remove Phase 7 shadow period comment from dev-lead.yml

* feat: skip PRs with CHANGES_REQUESTED review (+ FORCE_REVIEW and stale-review guards)

* feat: skip PRs with CHANGES_REQUESTED review decision

Adds an early-exit check alongside the existing CI guards so the cascade
skips any PR where a human reviewer has already requested changes.
Reviewing those PRs before the author responds wastes tokens and creates
confusing parallel feedback threads.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix: honor FORCE_REVIEW and skip only stale-free CHANGES_REQUESTED reviews

Two issues addressed from Codex P2 feedback:

1. FORCE_REVIEW bypass: mention-triggered and force_review=true runs now
   bypass the CHANGES_REQUESTED skip, so authors can explicitly request a
   re-review after addressing feedback.

2. Stale review guard: only skip when a CHANGES_REQUESTED review targets
   the current head SHA. On repos without automatic stale-review dismissal,
   reviewDecision stays CHANGES_REQUESTED after new commits — the cascade
   now re-engages when the author has pushed new work.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): resolve context loss, missing checkout, and push logic (#223)

* feat: skip PRs with CHANGES_REQUESTED review decision

Adds an early-exit check alongside the existing CI guards so the cascade
skips any PR where a human reviewer has already requested changes.
Reviewing those PRs before the author responds wastes tokens and creates
confusing parallel feedback threads.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix: honor FORCE_REVIEW and skip only stale-free CHANGES_REQUESTED reviews

Two issues addressed from Codex P2 feedback:

1. FORCE_REVIEW bypass: mention-triggered and force_review=true runs now
   bypass the CHANGES_REQUESTED skip, so authors can explicitly request a
   re-review after addressing feedback.

2. Stale review guard: only skip when a CHANGES_REQUESTED review targets
   the current head SHA. On repos without automatic stale-review dismissal,
   reviewDecision stays CHANGES_REQUESTED after new commits — the cascade
   now re-engages when the author has pushed new work.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): resolve context loss, missing checkout, and push logic

- dev-lead-intent.sh: include actor and body in INTENT_CONTEXT for comments
- dev-lead.yml: parse and pass INTENT_ACTOR and INTENT_COMMENT_BODY to agent
- dev-lead-fix-reviews.sh: perform PR checkout, git push, and post summary comments

* fix(dev-lead): exit with 0 on no-changes in fix-reviews and human-pr

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* test: bypass CI check

* fix(pr-review): remove CI bypass hack

* fix(dev-lead): use heredoc for multiline environment variables (#224)

* fix(dev-lead): use heredoc for multiline environment variables

* fix(dev-lead): ensure all engines are installed and context is consistent

* fix(dev-lead): resolve stale model name in engine fallbacks

* fix(dev-lead): improve fallback reliability and script quality

- engine.sh: resolve stale model names in fallback loop; use -latest Gemini aliases
- dev-lead-fix-reviews.sh: remove unused code and fix PR_URL export

* fix(dev-lead): resolve Gemini model names and shell lint warnings

* security(dev-lead): use random heredoc delimiter to prevent inject…
don-petry added a commit that referenced this pull request Aug 3, 2026
This addresses missed feedback from CodeRabbit (removing syntax errors in YAML) and gemini-code-assist (improving string concatenation in validate-engines.sh), and adds assertion output checks to tests.

Co-authored-by: Gemini CLI <gemini-cli@example.com>
don-petry added a commit that referenced this pull request Aug 3, 2026
… trigger for the Class-2 timer changes, before narrowing them (#1420)

* add: workflow to fix stuck PRs using bot account token

Allows running the cleanup script via workflow_dispatch with access to
GH_PAT (bot account token) from repo secrets.

* fix: pass GH_PAT to checkout action so workflow uses bot account

* debug: simplify account check, add authentication debug output

* docs: add comprehensive petry-review-bot setup instructions

* docs: add GitHub App setup guide (recommended approach)

GitHub App is the secure, recommended way to automate PR reviews:
- Fine-grained permissions
- JWT tokens that auto-expire
- No human account needed
- Better audit trail
- GitHub's recommended approach

Replaces the bot user account approach with a more secure alternative.

* docs: add comprehensive GitHub App setup documentation

- SETUP.md: Quick reference guide with configuration and troubleshooting
- IMPLEMENTATION.md: Technical deep dive on architecture and design decisions
- DOCUMENTATION.md: Index of all documentation files
- Updated README.md with status and quick links
- Updated GITHUB_APP_SETUP.md with implementation notes and actual app ID
- Workflows use GitHub App token generation instead of static PATs

Covers the transition from bot user account to GitHub App authentication for improved security and maintainability.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: silence auth check in fix-stuck-prs when using GitHub App token

GitHub App tokens don't have user scope, so 'gh api user' returns 403.
This is fine - the script still works for PR operations. Suppress the error
so the script completes successfully with app-token fallback label.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: use explicit author instead of @me in stuck PR search

GitHub App tokens don't have user identity, so @me search doesn't work.
Use explicit 'don-petry' author instead to find PRs to fix.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: avoid subshell in while loop to preserve variable state

Using pipe in while loop created subshell where PROBLEM_PRS and FIXED_PRS
counters were incremented but changes didn't persist to parent shell.
Fixed by using process substitution for input instead of pipe.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* docs: add comprehensive status report for GitHub App setup

- Overview of completed work
- GitHub App authentication details
- 24 stuck PRs successfully fixed with approval reviews
- Architectural decisions and rationale
- Configuration guide and usage examples
- Known limitations and troubleshooting

System is now fully operational with GitHub App token authentication,
comprehensive documentation, and all infrastructure in place.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* docs: document stuck PR cleanup status and script fixes

Added documentation for:
- GitHub App token compatibility fixes in fix-stuck-prs.sh
  * Changed @me to explicit author (app tokens have no identity)
  * Fixed subshell variable scope (process substitution vs pipe)
  * Silenced expected 403 auth check error
- Current status: 24 PRs have approvals but remain OPEN
  * Auto-merge failed due to missing GitHub App permission
  * Approvals satisfy branch protection requirement
  * Next: expand permissions or manually merge

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: update list-prs.sh to search all repos by owner instead of @me

GitHub App tokens don't have user identity, so @me/@review-requested searches
fail. Changed to enumerate all repos in don-petry and petry-projects, then
search for open PRs within each repo. This covers the full scope:
- All open PRs in personal don-petry repos
- All open PRs in petry-projects org repos

Resolves 6 consecutive workflow failures due to zero PRs being enumerated.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* docs: add investigation report for PR review agent workflow failures

* fix: move env section before steps in workflow to fix YAML structure

GitHub Actions requires env section to come before steps in job definition.
Moving env definition up and setting GH_TOKEN only in steps that need it.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* script: add backfill-approvals to retroactively apply real approvals

Converts PRs with agent approval comments (but no real GitHub approvals)
to have actual APPROVED reviews. Needed for PRs reviewed before the
gh pr review --approve fix was applied.

Usage:
  scripts/backfill-approvals.sh          # dry-run (preview changes)
  scripts/backfill-approvals.sh false    # apply real approvals

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* feat: add backfill-approvals workflow to run as GitHub App bot

Runs backfill-approvals.sh via GitHub Actions so the approvals are posted
by the bot identity, not the PR author — GitHub rejects self-approvals.

Also fixes subshell counter bug in the script (piped while loop lost variable
state; switched to process substitution).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: convert inner while loop to process substitution to preserve counters

Both the outer repo loop and inner PR loop were piped subshells, causing
all counter increments (approved/skipped/failed) to be lost. The summary
always showed 0. Fixed by using < <(...) process substitution for both loops.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* car-hunt: add VIN-deep-dive flow, printable checklists, location-tier ranking

- Step 5.5 pre-test-drive flow: NHTSA VIN decode + per-VIN unrepaired-recall
  automation via Chrome MCP (Honda LWC owner portal documented as not
  automatable; NHTSA web form works).
- §F printable test-drive checklist generator (reportlab/Platypus PDF) with
  cold-start, model-specific red flags, negotiation table, walk-away rules.
- §G fraud / paperwork verification checklist (vehicle-agnostic, reusable):
  walk-away triggers, VIN three-location match, curbstoning, odometer fraud,
  bill of sale, title transfer, payment protection, stolen-vehicle, title-
  jumping, after-purchase steps.
- Step 5.25 head-to-head comparison template with information-asymmetry rule.
- Step 3 Location Quality Tier scoring (Birmingham AL metro table A/B/C/D)
  with adj-CPM modifier; sheet schema gains Adj.CPM + Tier columns.
- Hard-disqualification rules added: non-running engine keywords, mileage
  inconsistency, mandatory description scrape.
- Drive MCP overwrite limitation documented; sheet ID now read from memory
  rather than hardcoded.
- FB Marketplace operational facts: Birmingham AL city ID 107739635926718,
  /search? vs /vehicles? query handling, React-controlled inline composer,
  send-button selector, seller-name extraction regex.
- scheduled-tasks/used-car-search-{morning,afternoon}: tier A/B priority
  flag, sheet ID read from memory.
- Reference PDF generator scripts checked in for reuse.

* fix: re-approve PRs where approval predates last commit (stale approval)

Instead of skipping PRs that already have any APPROVED review, skip only
those where reviewDecision is not REVIEW_REQUIRED. This handles PRs where
a bot approval was posted before a new commit was pushed — GitHub's ruleset
engine treats those approvals as stale even with dismiss_stale_reviews=false.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: robustly extract JSON verdict from mixed claude --print output (#17)

Claude's --print mode can prefix the JSON verdict with conversational
preamble text, causing jq to fail with parse errors. Add
extract_verdict_json to engine.sh and wire all three cascade action
call sites in review-one-pr.sh to use it.

Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: extract_verdict_json — check dest file first (agent Bash-write style)

The cascade-action agent writes its verdict JSON to \$OUTPUT_FILE via a
Bash tool call, then prints a text summary to stdout. The previous fix
only scanned stdout (the .raw file), which contained no JSON. The agent-
written file at \$dest (= \$OUTPUT_FILE) was already correct — just not
checked. Now check \$dest first before falling back to stdout scan.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Enforce MAX_REVIEW_CYCLES cap before running the cascade (#18)

* fix: enforce MAX_REVIEW_CYCLES cap with human-escalation marker

Previously MAX_REVIEW_CYCLES only gated AI delegation in
post-pr-review.sh — the cascade itself ran on every cron tick
regardless of how many cycles had accumulated. Real-world result:
a PR could rack up 9+ review cycles (we observed exactly that on
ContentTwin#100) before any cap took effect.

Add a pre-cascade check in review-one-pr.sh: when the count of
existing `<!-- pr-review-agent v1 sha=... -->` markers is at or
above MAX_REVIEW_CYCLES (default 3), post one escalation comment
marked `<!-- pr-review-agent escalation -->`, label
needs-human-review, request don-petry, and exit 100 (skip
sentinel — doesn't burn the MAX_PRS budget). The escalation
marker doubles as the no-spam guard: subsequent runs detect it
and exit 100 immediately.

Also reuse a single `gh pr view` for both the cycle count and the
escalation-marker check.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* review feedback: surface escalation-comment failures, drop dead unset

- Drop `2>/dev/null || true` on `gh pr comment` so a failed escalation
  post is visible in the workflow log instead of silently leaving the
  PR with no marker (which would re-trigger the cap path next tick).
- Reword the cycle-count comment from "AI delegation loops" to "review
  loops" — the cap also catches cascade-only loops where every cycle
  approves and a new commit lands before merge.
- Remove the redundant `unset PR_BODIES` — never exported, no leakage.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>

* Reliability hardening: session circuit breaker, timeouts, retry, dedup (#20)

* feat: session circuit breaker, per-tier timeouts, retry, and triage hard-fail

Reliability hardening for the PR review agent.

1. Session circuit breaker (.github/workflows/pr-review.yml): on any
   non-zero, non-100 exit from review-one-pr.sh (general failure or rate
   limit on the fallback engine), break the per-PR loop, log a clear
   error annotation naming the failing PR and reason, and exit the step
   with code 1 so the run shows red. Prevents one systemic problem from
   silently burning the entire candidate pool.

2. Per-tier timeouts (scripts/engine.sh): triage/deep/audit/action/duck
   each get their own bounded timeout (180/600/600/300/300s defaults,
   env-overridable). Previously only the duck had a timeout — a hung
   tier could burn the whole 60min job budget.

3. Retry-with-backoff on transient errors (scripts/engine.sh): triage
   retries once on 124/137/143 (timeout / signal kill) since its caller
   captures stdout via $(...) so retries are safe. Deliberately NOT
   applied to run_agentic/run_duck where stdout is redirected to a file
   — a retry there would corrupt the partial first-attempt output.

4. Triage non-JSON now hard-fails (scripts/review-one-pr.sh): replaces
   the silent fallback that synthesized a fake "escalate=MEDIUM" verdict
   and proceeded to deep review. With the new circuit breaker, loud
   failure is the right call — masking a broken triage was burning
   tokens on every PR while the workflow looked healthy.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix: stop stacking duplicate agent reviews on the same PR

Two bugs were causing the agent to leave multiple comments on the same PR.
Together they produced 10 stacked APPROVED reviews on petry-projects/ContentTwin#100.

Bug A — idempotency check is order-dependent (review-one-pr.sh):
The previous marker-discovery code did:
  ((.reviews // []) + (.comments // [])) | .[].body | grep marker | tail -1
This relies on the array concatenation order, not chronological order. When
old agent comments existed alongside newer agent reviews, tail -1 picked
the comment-array marker (older) over the review-array marker (newer),
causing the script to think the head SHA hadn't been reviewed and re-run.
Replaced with a single jq pipeline that tags each item with submittedAt /
createdAt, sorts by timestamp, and takes the actual most-recent marker.

Bug B — no cleanup of prior agent items (post-pr-review.sh):
After successfully posting a new review/comment, prior agent items were
left in place, accumulating forever. Added mark_prior_agent_items_obsolete
which, after a successful post:
  - dismisses prior APPROVED/COMMENTED/CHANGES_REQUESTED agent reviews via
    the GitHub dismissal API (UI shows them struck-through as Dismissed)
  - edits prior agent comments to wrap their body in a collapsed <details>
    block with a "Superseded by re-review at <SHA>" summary, plus a
    `<!-- pr-review-agent superseded -->` sentinel for idempotency

All cleanup API calls are best-effort — failures don't break the workflow.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(cleanup): warn on API failures, preserve global newest, file-stage JSON

Three fixes to mark_prior_agent_items_obsolete from the review of PR #20:

1. ::warning:: annotations on every cleanup API failure (review/comment
   list-fetch, individual review dismissal, individual comment fetch+edit).
   Previously these were silenced with `|| true`, so a permissions change
   on the dismissal endpoint would let duplicates stack indefinitely with
   no signal in the Actions UI. Cleanup is still non-fatal — the new post
   has already landed — but failures are now visible.

2. Preserve the globally-latest agent item across BOTH categories, not the
   newest of each category separately. The earlier code split reviews and
   comments and applied `[:-1]` to each, which left a stale fix-request
   comment in place when the new post was a review (or vice versa). The
   one-off cleanup of ContentTwin#100 hit exactly this case: 12 stacked
   reviews collapsed to 1, but a stale comment from 2026-04-25 (SHA
   cd9132d6) was preserved as "newest comment" even though the latest
   review at SHA 3af8c8ee was newer overall. Now: compute the max
   timestamp across both feeds, exclude items at that timestamp.

3. Stage API responses to disk (`mktemp` + `jq <file>`) instead of routing
   through `--argjson "$var"`. The old approach broke on rare unescaped
   control chars in user-authored comment bodies (jq refused to parse the
   resulting shell-vared JSON). File-based input sidesteps the shell
   pipeline entirely.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>

* Remove car-hunt skill and scheduled tasks (moved to don-petry/don-petry) (#21)

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: apply Copilot fallback hardening and reuse GH_PAT for Copilot auth

- Use secrets.GH_PAT (existing personal account token with Copilot
  subscription) instead of a separate COPILOT_GITHUB_TOKEN secret
- Pin actions/create-github-app-token to SHA (v3.1.1) for supply chain safety
- Add post-install verification for gh-copilot extension with warning on failure
- Guard Copilot fallback path: skip PR gracefully if extension is not installed

Addresses issue #24 workflow fixes (1-3) that could not land via PR #25
due to GitHub App lacking workflows permission.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: switch Copilot engine to gh built-in and fix app-id deprecation

- Remove gh extension install (github/gh-copilot conflicts with built-in alias)
- Replace with gh copilot --version check using COPILOT_GITHUB_TOKEN (GH_PAT)
- Update all engine.sh copilot invocations from bare `copilot` binary to
  `gh copilot suggest --target shell` with GH_TOKEN overridden to GH_PAT
  so the user token (with Copilot subscription) is used, not the App token
- Fix actions/create-github-app-token: app-id → client-id (same secret value,
  just the renamed input in v3.x)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: harden Copilot fallback path and remove stale worktrees (#25)

- gh-copilot install: add --force flag and post-install verification
  warning so silent failures due to built-in alias conflict are visible
  in workflow logs instead of being silently swallowed by `|| true`
- fallback pre-flight check: verify `gh extension list | grep copilot`
  before switching to Copilot engine; if unavailable, skip the PR and
  continue the batch rather than session-aborting and dropping all
  remaining candidates (fixes the 27-PR drop on run #503)
- pin actions/create-github-app-token to SHA for v3.1.1 (Node.js 24
  compatible) to prevent hard break on 2026-06-02 when GitHub forces
  Node.js 24 as default runtime
- remove 6 stale Claude Code worktrees from git tracking; add
  .gitignore entry to prevent future worktrees from being committed
  (eliminates exit-128 warning on every actions/checkout post-job sweep)

Closes #24

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: petry-projects-pr-review-agent[bot] <petry-projects-pr-review-agent[bot]@users.noreply.github.com>

* ci: add pre-flight dedup check to prevent duplicate claude-issue PRs (#26)

Before invoking Claude on a labeled issue, a new shell step queries
for any open PR with a branch matching claude/issue-NNN-* (or a body
containing "Closes #NNN"). If one is found it posts a comment on the
issue pointing to the existing PR and skips the Claude step entirely.
A secondary prompt instruction tells Claude to check first and push to
the existing branch rather than opening a new PR.

Motivated by issue petry-projects/google-app-scripts#171, where the
claude label was re-applied three times and each run created a fresh
PR unaware of the prior attempts.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor: migrate to machine user PAT auth (closes #27) (#28)

* refactor: migrate from GitHub App to machine user PAT auth

Closes #27

GitHub Apps cannot be listed in CODEOWNERS, blocking PRs in repos with
require_code_owner_review: true. Switch all workflows to use a machine
user account's fine-grained PAT (DON_PETRY_BOT_GH_PAT secret), which can
join an org team listed in CODEOWNERS.

Workflows: removed actions/create-github-app-token steps in pr-review,
fix-stuck-prs, backfill-approvals, and daily-pr-review-health. All now
use secrets.DON_PETRY_BOT_GH_PAT directly.

Docs: renamed GITHUB_APP_SETUP.md to MACHINE_USER_SETUP.md with full
rewrite covering account creation, CODEOWNERS config, PAT generation,
and rotation. Updated auth sections in IMPLEMENTATION.md, SETUP.md,
STATUS.md, DOCUMENTATION.md, README.md.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* refactor: switch to org-scoped PAT secret DON_PETRY_BOT_PETRY_PROJECT_PAT

Replace DON_PETRY_BOT_GH_PAT with DON_PETRY_BOT_PETRY_PROJECT_PAT — the
new fine-grained PAT scoped to the petry-projects org (resource owner =
petry-projects). The previous PAT was scoped to the donpetry-bot personal
namespace and had no repository access.

The old DON_PETRY_BOT_GH_PAT secret is retained for any future use against
don-petry's personal repos.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* chore: ignore .claude/scheduled_tasks.lock state file

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>

* refactor: merge approval repair scripts into single automation

- Consolidate backfill-approvals.sh and fix-stuck-prs.sh into repair-pr-approvals.sh
- Iterate all repos in both orgs instead of assuming author
- Verify no existing APPROVED review before posting
- Copy original agent comment as review body
- Enable auto-merge when posting approval if needed
- Single workflow with 30-min timeout

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: add @mention trigger for on-demand PR reviews (#30)

Adds repository_dispatch support so commenting @petry-review-bot on any
PR fires an immediate review without waiting for the hourly schedule.

- pr-review.yml: new `repository_dispatch` trigger (type: pr-review-mention),
  per-PR concurrency group for mention runs, FORCE_REVIEW and DRY_RUN
  env vars that handle both workflow_dispatch and repository_dispatch paths
- review-one-pr.sh: FORCE_REVIEW=true bypasses idempotency so a mention
  always runs a fresh cascade even if the head SHA hasn't changed
- templates/mention-listener.yml: deploy to petry-projects/.github;
  listens for @petry-review-bot, validates commenter trust, posts ack,
  sends repository_dispatch (requires Contents:write, not Actions:write)

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: harden rebase, review dismissal, and health log diagnostics (#40)

Addresses 4 code-actionable recommendations from health check report #33:

- [CRITICAL] Make rebase/branch-update non-fatal — 403/504 during rebase
  emits ::warning:: instead of aborting the batch session (#34)
- [MEDIUM] Add retry with exponential backoff (3 attempts) for transient
  5xx on update-branch API; break immediately on 4xx (#37)
- [MEDIUM] Guard review dismissal with state re-check before calling
  dismiss API, preventing 422s from race conditions (#38)
- [LOW] Surface missing log warnings in health check script (#39)
- Fix: skip auto-merge when branch is still BEHIND after failed rebase

Closes #34, closes #37, closes #38, closes #39

* refactor: migrate to org-wide .github-private convention (#41)

* refactor: parametrize hardcoded identity values for org migration

Replace all hardcoded references to don-petry, petry-review-bot, and
don-petry/pr-review-agent with environment variables that default to
the current values. This allows the agent to be configured for different
orgs/users via repo variables.

Changes:
- Scripts use $REVIEWER_USER, $TARGET_ORG, $BOT_USER, $AGENT_REPO
- Prompts no longer reference specific GitHub usernames
- Workflows use org-level GH_PAT_WORKFLOWS secret instead of
  repo-level DON_PETRY_BOT_PETRY_PROJECT_PAT
- Health check uses context.repo.owner/repo for issue creation
- Variables are set in workflow env block with defaults

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add Copilot custom agent profiles for org-wide use

Create /agents/ directory with three agent profiles:
- pr-reviewer: Multi-tier cascading PR review
- feature-ideator: Feature idea generation and prioritization
- compliance-auditor: Org standards compliance checking

These are Copilot custom agent profiles that become available
org-wide in the .github-private repo convention.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Squashed 'frameworks/bmad-method/' content from commit e36f219c

git-subtree-dir: frameworks/bmad-method
git-subtree-split: e36f219c81b6010d4aae423ba12f49edb5b6e31a

* Squashed 'frameworks/spec-kit/' content from commit 11f49ebf

git-subtree-dir: frameworks/spec-kit
git-subtree-split: 11f49ebfb2f6af55345cb4bd9a7906acd211e56f

* Squashed 'frameworks/gsd/' content from commit 304c1a13

git-subtree-dir: frameworks/gsd
git-subtree-split: 304c1a1302564c45af252bbba4bcc5350e7dac3a

* docs: update README for .github-private org infrastructure role

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* ci: add dependabot-automerge.yml workflow (#73)

Adds the required dependabot-automerge.yml workflow from the org
standard template (petry-projects/.github/standards/workflows/).
This is a thin caller stub that delegates to the org-level reusable
workflow.

Closes #48

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Don Petry <don-petry@users.noreply.github.com>

* fix(pr-review): unblock queue starvation from self-authored PRs (#96) (#97)

* fix(pr-review): unblock queue starvation from self-authored PRs (#96)

A self-authored PR sorted first in the candidate list and triggered
"Can not approve your own pull request" — which the session-fatal
abort treated like an engine error, skipping all 28 remaining PRs
on every run.

- list-prs.sh: filter out PRs authored by REVIEWER_USER at enumeration
- post-pr-review.sh: catch the GraphQL self-approval error and exit 100
  (no-op) instead of 1, so a stray self-PR can no longer abort the batch
- engine.sh: gh copilot renamed --target to --agent; fix the rubber-duck
  invocations so tier-2 cross-engine review works again
- review-one-pr.sh: stop appending a duplicate "0" to REVIEW_CYCLE when
  grep -c finds no markers under set -o pipefail (was breaking the
  cycle-cap integer comparison)

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

* fix(pr-review): authenticate as bot, harden enumeration, address review

- Workflow now runs as AGENT_USER (default don-petry-bot), distinct from
  REVIEWER_USER (the human, don-petry). The self-approval rejection that
  caused #96 came from the agent and the human sharing one identity.
- list-prs.sh filters self-authored PRs against AGENT_USER, and validates
  AGENT_USER against the GitHub username charset before interpolating into
  the jq filter (Copilot review feedback).
- review-one-pr.sh uses printf '%s\n' instead of echo for PR_BODIES, since
  PR body content is user-authored and could begin with -n/-e or contain
  backslash escapes (Copilot review feedback).
- AGENT.md guideline updated to reflect that self-authored PRs are
  intentionally excluded (CodeRabbit review feedback).

Operator note: rotate the GH_PAT_WORKFLOWS secret to a token owned by
don-petry-bot (with read:org added) for the bot-account behavior to take
effect at runtime.

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

* refactor(pr-review): collapse to single BOT_USER, route escalations via CODEOWNERS

Drop AGENT_USER and REVIEWER_USER. The workflow now has one identity:
BOT_USER (default don-petry-bot), which both owns the repos to scan and
gets filtered out as the self-approval blocker. Human escalation no
longer hard-codes a single reviewer; instead, scripts/request-codeowners-
review.sh parses CODEOWNERS in the PR's repo and requests review from
every @user / @org/team mention.

- list-prs.sh: BOT_USER for both gh-repo-list and self-author filter
- review-one-pr.sh, post-pr-review.sh: replace --user "$REVIEWER_USER"
  with the CODEOWNERS helper
- repair-pr-approvals.sh: same gh-repo-list switch
- pr-review.yml: drop AGENT_USER/REVIEWER_USER, set BOT_USER default
  to don-petry-bot
- AGENT.md: updated guideline to describe single-identity model

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

* refactor(pr-review): extract batch loop, hoist env, drop redundant step

Workflow YAML drops from 231 to 111 lines by moving the per-PR review
loop into scripts/review-batch.sh. The script can be syntax-checked and
exercised locally; the workflow now just wires env vars and dispatches.

- Hoist GH_TOKEN, MAX_PRS, CANDIDATE_LIMIT to job-level env (were
  repeated on individual steps).
- Inline `gh auth status` into the install step; remove the standalone
  Verify auth step (its only output was a one-line auth dump).
- Drop the `if: steps.list.outputs.count != '0'` guard and the step's
  `id`/output: review-batch.sh handles empty input as a no-op.
- Collapse the duplicated summary-string branch in the review loop into
  a single template with a conditional fragment.

No behavior change.

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

* perf(pr-review): cache claude-code CLI install across runs

`npm install -g @anthropic-ai/claude-code` ran on every workflow start
(~30s). Switch to a per-user npm prefix (~/.npm-global) and cache that
directory via actions/cache, keyed on CLAUDE_CODE_VERSION + runner OS.

A `command -v claude` guard makes the install a no-op on cache hit, so
the only cost on subsequent runs is the cache restore.

CLAUDE_CODE_VERSION defaults to 'latest' (cache persists until manually
flushed); set the repo variable to pin a specific version for fully
reproducible caching.

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

---------

Co-authored-by: Claude <noreply@anthropic.com>

* chore: rename GH_PAT_WORKFLOWS secret to DON_PETRY_BOT_GH_PAT (#99)

The previous name was generic and didn't tell you which account the PAT
belonged to. The new name makes the binding explicit: this secret is the
PAT owned by don-petry-bot, used as BOT_USER throughout the PR-review
workflows.

Operator follow-up before this can be merged:
- Add a new repo secret DON_PETRY_BOT_GH_PAT containing the bot's PAT
  (with repo, workflow, and read:org scopes).
- After merge, the old GH_PAT_WORKFLOWS secret can be deleted.

Affected workflows:
- pr-review.yml (1 use)
- claude.yml (4 uses, with || github.token fallback)
- daily-pr-review-health.yml (1 use)
- repair-pr-approvals.yml (1 use)

scripts/pr_review_health.sh's error message is updated to point at the
new name as well.

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

Co-authored-by: Claude <noreply@anthropic.com>

* chore: remove frameworks directory (#101)

Co-authored-by: Gemini CLI <gemini-cli@example.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore all erroneously deleted files (items 4-34) (#104)

* revert: restore .github/workflows/daily-pr-review-health.yml

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore .github/workflows/repair-pr-approvals.yml

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore agents/pr-reviewer.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/cascade-action.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/deep-review.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/rubber-duck.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/security-audit.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/shared.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/single-review.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/synthesize-duck.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/synthesize.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/triage.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/engine.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/list-prs.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/post-pr-review.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/pr_review_health.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/repair-pr-approvals.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/request-codeowners-review.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/review-batch.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/review-one-pr.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore templates/mention-listener.yml

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore AGENT.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore BOT_SETUP.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore DOCUMENTATION.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore IMPLEMENTATION.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore MACHINE_USER_SETUP.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore PR_REVIEW_AGENT_FAILURE_REPORT.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore PR_REVIEW_FAILURE_INVESTIGATION.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore SETUP.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore STATUS.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore README.md to pre-change state

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore .github/workflows/daily-pr-review-health.yml

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat: add Gemini Pro support and optimize PR review fallback chain (#102)

* Merge main and resolve conflicts

* Enhance pr-review workflow: add Gemini support and refactor fallback logic

* Address Copilot review comments: tighten regex, fix fallback summary, and align docs

* docs: move agent documentation to docs/ folder and rename files

* docs: align secret names and fix casing in index

* docs: organize pr-review-agent documentation under dedicated folder

* Address final PR comments: tighten regex, add preflight checks, fix bot names, and cleanup docs

---------

Co-authored-by: Gemini CLI <gemini-cli@example.com>

* fix(pr-review): gate rate-limit detection on non-zero exit code

Broad patterns like `plan.*limit` and `claude.*usage` could match content
in a successful triage summary, triggering a false-positive engine fallback.
Guard the check with TRIAGE_RC != 0 so it only fires when the provider
command actually failed.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(claude): sync inlined workflow with org standard (#109)

* fix(claude): sync inlined workflow with org standard

Brings the inlined claude.yml up to parity with
petry-projects/.github/.github/workflows/claude-code-reusable.yml.

Changes:
1. Bot allow list for pull_request_review_comment
   Add coderabbitai[bot], Copilot, copilot-pull-request-reviewer[bot],
   and gemini-code-assist[bot] alongside the existing OWNER/MEMBER/
   COLLABORATOR check. These bots have author_association 'NONE' so
   their review comments were always skipped.

2. check_run trigger + claude-ci-fix job
   Port the CI failure auto-fix feature from the reusable. When a check
   fails on a PR, Claude diagnoses and fixes it automatically.
   Adapted to use DON_PETRY_BOT_GH_PAT (this repo's secret name).

3. claude-code-action SHA bump: v1.0.89 → v1.0.119
   Update both the claude and claude-issue jobs.

4. paths-ignore on pull_request trigger
   Prevents the workflow from firing on PRs that only change
   claude.yml itself, avoiding the Anthropic OIDC validation
   failure (workflow file must match default branch at token exchange).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(claude): guard bot allow list against fork PRs

Same fix as petry-projects/.github PR #238 — add same-repo guard
for bot-triggered pull_request_review_comment runs.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(claude): address ci-fix self-loop and fork PR security

- Replace Claude Code name prefix check with explicit job name list to
  correctly prevent self-loops (check_run names for inlined workflows
  are bare job names, not workflow-prefixed)
- Add fork PR trust gate in Resolve PR number step: verify head repo
  matches target repo before running Claude with privileged credentials

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* ci: replace inlined claude.yml with standard thin-caller stub

The inlined version embedded all three jobs (claude, claude-ci-fix,
claude-issue) directly and threaded DON_PETRY_BOT_GH_PAT through
checkout tokens and github_token, causing the bot's PAT to author
PRs and comments instead of github-actions[bot].

Replace with the standard thin caller that delegates to the org-level
reusable workflow (claude-code-reusable.yml@v1) via secrets: inherit,
which is the correct pattern per:
petry-projects/.github/standards/workflows/claude.yml

* fix: replace gh copilot suggest with GitHub Models REST API (#151)

* fix: replace gh copilot suggest with GitHub Models REST API (#147)

The `gh copilot suggest -p "$(cat <file>)"` invocation failed with
"Invalid command format" because:
1. The `-p` flag is not valid syntax in modern `gh copilot` built-in versions.
2. `gh copilot suggest` is a shell-command suggestion tool; it does not
   accept large PR prompts or return structured JSON responses.
3. Passing a multi-thousand-line prompt via `$(cat ...)` can hit ARG_MAX.

The non-zero exit was then misclassified by the rate-limit detector, which
aborted the entire session and skipped all remaining PRs in the queue.

Fix: replace all three copilot invocations (run_triage, run_agentic,
run_duck) with a new `copilot_chat` helper that calls the GitHub Models
REST API directly via curl. The API is OpenAI-compatible, versioned via
`X-GitHub-Api-Version`, stable against gh CLI version changes, and accepts
arbitrary prompt sizes using python3 for safe JSON encoding.

Additional changes:
- Add `COPILOT_API_MODEL` var (default `openai/o4-mini`) overrideable via env.
- run_agentic/run_duck copilot cases now also write output to $OUTPUT_FILE
  so callers that check that path directly (deep review, audit) find the JSON.
- Rate-limit responses (HTTP 429) are echoed to stdout so the existing
  `is_rate_limited()` detector fires correctly for engine fallback.
- Add pre-flight smoke test in review-batch.sh: tests GitHub Models API
  connectivity with a one-liner prompt before processing any PRs, so auth
  or model errors surface immediately as a clear setup failure.
- Add tests/test_copilot_chat.sh: unit tests for the JSON payload builder
  with edge-case prompts (quotes, newlines, # headings, large diffs, Unicode).

Closes #147

Co-authored-by: Don Petry <don-petry@users.noreply.github.com>

* fix: scope COPILOT_API_MODEL export to copilot engine only

Move the `export COPILOT_API_MODEL` from the common exports block into the
`copilot)` case where the variable is set, so it is not exported as an
empty/unset variable when the engine is `claude` or `gemini`. The
`copilot_chat` fallback `${COPILOT_API_MODEL:-openai/o4-mini}` still works
correctly when `DUCK_ENGINE=copilot` under a non-copilot primary engine.

Co-authored-by: Don Petry <don-petry@users.noreply.github.com>

* fix: address review comments — temp file for payload, fail-fast source, streaming output

- copilot_chat: write JSON body to mktemp file, pass to curl as @file to
  avoid ARG_MAX for large PR diffs (was --data-binary "$body")
- copilot_chat: add :? guard on COPILOT_GITHUB_TOKEN for a clear error
  instead of generic "unbound variable" under set -u
- run_agentic / run_duck copilot paths: stream directly to stdout (and
  tee to OUTPUT_FILE when set) rather than buffering the full response
  into a shell variable, which forced large outputs into memory and
  stripped trailing newlines
- review-batch.sh pre-flight: fail fast if source engine.sh fails
  (was silently ignored with || true)
- review-batch.sh pre-flight: build smoke-test JSON payload via python3
  + temp file instead of shell string interpolation to avoid JSON
  injection if COPILOT_API_MODEL contains special characters
- Clarify that openai/o4-mini is the correct April-2025 o4-generation
  model name, not a typo for o1-mini or gpt-4o-mini

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Don Petry <don-petry@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* feat: prioritise .github/.github-private PRs, oldest-first within tier (#155)

* feat: prioritise .github/.github-private PRs, oldest-first within tier

- list-prs.sh: add createdAt to JSON fetch; emit priority|createdAt|url
  lines (priority 0 for .github/.github-private, 1 for everything else)
- Replace final sort -u with a two-pass sort: deduplicate by URL, then
  sort by priority asc then createdAt asc; strip sort keys with cut
- tests/test_list_prs_sort.sh: 16 tests covering priority classification,
  oldest-first ordering, mixed scenarios and deduplication
- .github/workflows/test.yml: run both unit-test files on every PR / push

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* test: add dedup edge case for same URL with conflicting priorities

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: remove temperature from copilot_chat payload (o4-mini incompatible)

The GitHub Models API rejects temperature=0 for reasoning models like
o4-mini with HTTP 400:
  'Unsupported value: temperature does not support 0 with this model.
   Only the default (1) value is supported.'

This caused the rubber duck (DUCK_ENGINE=copilot / DUCK_MODEL=o4-mini)
to fail with HTTP 400 on every claude-engine run, silently degrading
every review to deep-only with no cross-engine sanity check.

Fix: remove temperature from the copilot_chat JSON payload entirely —
the API defaults to 1, which is the only supported value for o4-mini.

Also:
- Update tests/test_copilot_chat.sh: sync build_payload to match and
  flip Test 9 to assert temperature is ABSENT (not 0).
- Add .github/workflows/test.yml: run unit tests on every PR and push
  to main so regressions are caught before merge.

Reproducer: job/75690347409 — duck failure logged as:
  copilot_chat: HTTP 400 from GitHub Models API
  {"error":{"message":"Unsupported value: 'temperature' does not
   support 0 with this model..."}}

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: address PR review comments

- list-prs.sh: tighten priority regex to /[.]github(-private)?/pull/
  so path-boundary anchor prevents false positives (e.g. foo.github)
- list-prs.sh: replace printf pipeline with here-string to avoid ARG_MAX
  limits on large PR sets
- test.yml: add permissions: contents: read (least-privilege, CodeQL fix)
- test.yml: add timeout-minutes: 5 to prevent hung runs
- test_list_prs_sort.sh: sync sort_entries helper (here-string + || true)
  and classify regex to match updated list-prs.sh exactly

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: don-petry <don@petry.dev>

* fix: restore pr-review.yml content (accidentally emptied in revert)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: restore pr-review.yml (file was accidentally emptied)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore(deps): bump actions/cache from 4 to 5 (#165)

Bumps [actions/cache](https://github.com/actions/cache) from 4 to 5.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): bump petry-projects/.github/.github/workflows/claude-code-reusable.yml (#164)

Bumps [petry-projects/.github/.github/workflows/claude-code-reusable.yml](https://github.com/petry-projects/.github) from 1 to 2.
- [Commits](https://github.com/petry-projects/.github/compare/v1...v2)

---
updated-dependencies:
- dependency-name: petry-projects/.github/.github/workflows/claude-code-reusable.yml
  dependency-version: '2'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* fix(list-prs): search DELEGATION_ORGS and drop --checks success pre-filter

- Add iteration over all orgs in DELEGATION_ORGS (e.g. don-petry) so PRs
  in those orgs enter the candidate pool. Previously only BOT_USER and
  TARGET_ORG were searched.
- Remove --checks success from org repo searches. GitHub excludes PRs from
  repos with no CI configured when this flag is used, silently dropping all
  PRs from repos like .github, .github-private, google-app-scripts, and
  TalkTerm. review-one-pr.sh already enforces CI gating per-PR and treats
  empty statusCheckRollup as passing, so the pre-filter is redundant and
  harmful.
- Add --limit 200 to gh repo list calls to handle org growth beyond gh's
  default 30-repo cap.

* feat: add code-quality ruleset (compliance fix #60) (#86)

Creates the required `code-quality` repository ruleset enforcing required
status checks on the default branch, as mandated by the org standard:
standards/github-settings.md#code-quality--required-checks-ruleset-all-repositories

Required checks:
- SonarCloud (code quality analysis)
- CodeQL (SAST)
- agent-shield / AgentShield (agent security scan)
- dependency-audit / Detect ecosystems (dependency vulnerability scan)

Bypass actors:
- OrganizationAdmin (always) — emergency override
- dependabot-automerge-petry Integration (always) — Dependabot auto-merge

The ruleset was applied directly via GitHub API. This file documents the
configuration as code for auditability and future reapplication.

Closes #60

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Don Petry <don-petry@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* feat(prompts/dev-lead): add human prompt template

* feat(prompts/dev-lead): add human-pr prompt template

* feat(dev-lead): implement dev-lead agent Phases 0-6

* fix: per-PR isolation and single-review retry (closes #132)

- review-batch.sh: non-rate-limit per-PR failures (exit code 1) no
  longer abort the session. SESSION ABORTED EARLY is now reserved for
  the rate-limit-on-fallback-engine case (exit code 2) only. All other
  failures are counted and logged; remaining candidates continue.

- review-one-pr.sh: single-review step retries up to
  SINGLE_REVIEW_MAX_RETRIES (default 2) times with a
  SINGLE_REVIEW_RETRY_DELAY_SEC (default 15s) gap before giving up.
  On exhaustion, the PR is flagged needs-human-review and the script
  exits with code 1, which the updated batch treats as a non-fatal
  per-PR failure. Raw model output and stderr are logged on each failed
  attempt for post-mortem visibility.

Root cause of run #25707852006: claude-opus-4-7 returned a verbose
non-JSON response for PR #129; the old code treated that as fatal and
skipped 35 remaining candidates.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix: rate-limit detection and per-attempt stderr logs in single-review retry

Address inline review comments on PR #133:

- Rate-limit check: after each run_agentic call, inspect both stdout
  (VERDICT_JSON.raw) and stderr (SINGLE_LOG) with is_rate_limited before
  retrying. A rate-limit match exits immediately with code 2 so
  review-batch.sh can trigger engine fallback — consistent with triage and
  deep-review tiers. Previously a rate-limited single-review would burn all
  retries and exit 1 (per-PR failure), silently leaving the batch on the
  same rate-limited engine for all remaining PRs.

- Per-attempt log files: stderr is now written to
  single-review-attempt-N.log rather than a single overwritten file, so
  no earlier-attempt errors are lost. Each attempt logs its own stderr
  inline on failure; the fallback path cats all attempt logs for
  post-mortem visibility.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* feat(dev-lead): Phase 0 test infrastructure and Phase 1 intent stub

Phase 0: full test harness for the dev-lead agent —
  26 event fixtures (all valid JSON with _test_expected_intent),
  stub claude/gemini engines, mock gh binary, CI failure log sample,
  bats helpers (stub-engine, mock-gh, assert-env, prompt-vars),
  7 prompt templates with VARIABLES declarations, preflight script,
  prompt coverage integration test, and test-dev-lead.yml CI workflow.

Phase 1: dev-lead.yml trigger workflow (all 7 event types, dispatch + ci-relay
  jobs) and dev-lead-intent.sh stub (anti-loop guard live; all other events
  emit skip/not-implemented).  14/14 bats unit tests pass.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): add permissions blocks to test workflow jobs (CodeQL)

* fix(ci): auto-fix for lint / eslint [skip ci-relay]

* feat(dev-lead): Phases 2-6 — CI fix, review fix, issue, engine fallback

Phase 2: run_writer/run_writer_with_fallback in engine.sh, full intent
routing in dev-lead-intent.sh, dev-lead-fix-ci.sh handler, workflow wiring.

Phase 3: dev-lead-fix-reviews.sh handles fix-reviews, fix-bot-comment,
human, human-pr, rebase intents. Full review routing in intent classifier.

Phase 5: dev-lead-fix-issue.sh handles the issue intent with dedup guard
and branch/PR creation.

Phase 6: run_writer_with_fallback with claude→gemini→copilot fallback chain.

Tests: 77 unit tests across 8 new .bats files, all passing. Updated Phase 1
stub tests to reflect actual Phase 2+ routing behavior.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): address review comments — dispatch JSON, dead code, portable stub gh

* fix(dev-lead): pre-flight after intent, fork URL check, review-batch exit code, retry comment

* fix(dev-lead): install bats-core from GitHub to avoid root requirement

* fix(security): move event values to env vars to prevent script injection (SonarCloud)

* fix(security): move CLAUDE_CODE_VERSION to env block (SonarCloud script injection)

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* feat(dev-lead): Phase 1.5 — reusable workflow for cross-repo adoption + Phase 7 shadow period

* feat(dev-lead): add reusable workflow (Phase 1.5) and begin shadow period (Phase 7)

- Create .github/workflows/dev-lead-reusable.yml: workflow_call entry
  point for other repos. Checks out .github-private scripts/prompts into
  .dev-lead/, then runs the same intent-classify + handler pipeline as
  dev-lead.yml with PROMPTS_DIR=.dev-lead/prompts/dev-lead.
- Add PROMPTS_DIR env-var support to dev-lead-fix-ci.sh,
  dev-lead-fix-reviews.sh, and dev-lead-fix-issue.sh so the reusable
  workflow can point scripts at the sparse-checkout path without changing
  CWD. Defaults to prompts/dev-lead (backwards-compatible for dev-lead.yml).
- Annotate dev-lead.yml with Phase 7 shadow-period window (2026-05-15
  through ~2026-05-29): claude.yml and dev-lead.yml run in parallel until
  regressions clear, then claude.yml is removed.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* chore(dev-lead): update shadow-period tracking issue ref to #180

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): document PAT requirement and pin ref:main on private checkout

* fix(dev-lead): P1 review fixes — ci-relay in reusable, .dev-lead gitignore, export PROMPTS_DIR, trim header

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): rate-limit detection, separate status, and retry cron (#192) (#196)

* chore(dev-lead): decommission claude.yml, bump ACTION_TIMEOUT_SEC to 600s

- Delete .github/workflows/claude.yml — replaced by dev-lead.yml (shadow
  period complete, tracking issue #180).
- scripts/engine.sh: raise ACTION_TIMEOUT_SEC default 300→600s to reduce
  timeout failures on large-repo fix-ci runs (PR #80 had 3 timeouts).
- AGENTS.md: remove claude.yml immutability exemption; note dev-lead.yml
  as the active AI automation workflow and its edit-via-reusable pattern.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* refactor: replace Claude analysis with pure gh/jq telemetry in health check

* refactor: drop Node/Claude steps from daily-pr-review-health workflow

* rename: daily-pr-review-health → actions-fleet-monitor

* rename: daily-pr-review-health → actions-fleet-monitor

* fix(agents-md): clarify dev-lead.yml vs dev-lead-reusable.yml scope

dev-lead.yml in .github-private runs inline steps (not a caller stub).
Behavior changes for this repo go to dev-lead.yml directly; changes that
affect all org repos via the cross-repo reusable go to dev-lead-reusable.yml.

Addresses Copilot review on PR #194.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): rate-limit detection, separate status, and retry cron

Closes #192. Implements all phases from the revised plan.

**Phase 0 — fix run_writer stdout capture (prerequisite)**
- engine.sh: capture stdout via `tee` to a tempfile so `is_rate_limited`
  can inspect the output; old code read `/tmp/dev-lead-writer-stderr`
  which was never written (claude --print outputs to stdout, not stderr).
  Fallback engines were never tried in practice.
- engine.sh: add `parse_reset_time` to extract ISO timestamp from
  `resets H:MMpm (UTC)` in engine output and write it to
  `/tmp/dev-lead-rate-limit-reset` for callers to embed in markers.

**Phase 1 — separate status=rate-limited from status=failed**
- fix-ci.sh: detect engine exit 2 → post `status=rate-limited` (not
  `status=failed`); embed parsed reset time in marker body; exit 2.
- fix-ci.sh: fix `check_idempotency` to treat `status=rate-limited`
  as retriable — only block on terminal statuses (applied, failed,
  no-changes). Previously, rate-limited markers blocked all retries.
- fix-ci.sh: `count_recent_failures` already filtered to `status=failed`;
  add explicit comment confirming rate-limited markers are excluded.
- fix-ci.sh: add `has_rate_limited_marker` dedup check so the same SHA
  never accumulates more than one rate-limited comment.
- fix-reviews.sh: on engine exit 2 for all five intent types
  (fix-reviews, fix-bot-comment, human, human-pr, rebase), post a
  `status=rate-limited` marker with embedded reset time and exit 2.
- fix-reviews.sh: for `human` and `human-pr` intents, also post a
  user-visible acknowledgment comment so users know their request was
  received and will be retried.

**Phase 2 — scheduled retry cron for fix-ci**
- dev-lead-retry.sh: new script scans all open PRs across TARGET_ORG
  for `status=rate-limited` markers on current HEAD SHA; skips PRs
  whose reset time is still in the future; staggered dispatches (30s
  between repos) to prevent cascading org-level rate-limit hits.
- dev-lead-retry.yml: new workflow — scheduled every 2 hours (dual
  offsets to work around GitHub scheduler skew), plus workflow_dispatch.

**Phase 3 — retry for fix-reviews intents (same cron)**
- dev-lead-retry.sh already handles all five fix-reviews intent types
  (fix-reviews, fix-bot-comment, human, human-pr, rebase) via
  `dev-lead-reviews-retry` dispatch events.
- dev-lead-intent.sh: add `dev-lead-reviews-retry` dispatch type routing
  to the intent classifier — maps intent_type from payload to the correct
  existing fix-reviews step.
- dev-lead.yml: add `dev-lead-reviews-retry` to repository_dispatch types.
- dev-lead.yml: unify concurrency group for all dispatch types to
  `dev-lead-pr-{pr_number}` to avoid per-type slot fragmentation.

**Tests**
- test_engine_writer.bats: 8 new tests covering rate-limit stdout detection,
  exit-2 mapping, fallback exhaustion, reset time parsing.
- test_fix_ci.bats: 5 new tests covering rate-limited status, exhaustion
  exclusion, idempotency pass-through, and dedup.
- test_fix_reviews.bats: 5 new tests covering all intent types + human ack.
- test_intent_ci.bats: 4 new tests for dev-lead-reviews-retry dispatch routing.
- e2e/scenarios/07-rate-limit-retry.sh: new E2E scenario covering all parts.
- 3 new event fixtures for repository_dispatch_reviews_retry_* payloads.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): address review comments on #196

Addresses all 18 review threads from Copilot, Codex, and Gemini.

**P1 bugs fixed:**
- dev-lead-retry.sh: all log echo calls in scan_pr_for_rate_limits and
  dispatch_* functions now write to stderr; only the final count is on
  stdout. Previously, log lines polluted the command substitution,
  causing arithmetic expansion to fail and aborting the scan on the
  first retry candidate.
- dev-lead-retry.yml: permissions: contents was read-only; creating
  repository_dispatch events requires contents: write.

**Correctness fixes:**
- dev-lead-retry.sh: restrict automated retries to intents whose context
  can be reconstructed at runtime (fix-reviews, human-pr, rebase). human
  and fix-bot-comment require USER_INSTRUCTION/COMMENT_BODY from the
  original event which cannot be re-fetched; they are explicitly excluded.
- dev-lead-fix-reviews.sh: resolve HEAD_SHA from the PR API when not
  provided by the triggering event (issue_comment intents carry no SHA).
  Without this, rate-limited markers had no sha= field and were
  invisible to the retry scanner.
- dev-lead-fix-reviews.sh: write terminal status=applied marker after
  successful fix-reviews, human-pr, rebase runs. Prevents the retry
  cron from re-dispatching the same intent on every subsequent tick when
  the SHA hasn't changed.
- dev-lead-retry.sh: check for reviews terminal marker before dispatching
  a retry (mirrors the existing fix-ci terminal-marker check).
- dev-lead-retry.sh: look up current check-run details (details_url, id)
  at dispatch time via the commits check-runs API, so retried fix-ci runs
  have full failure logs and annotations rather than empty fields.
- dev-lead-fix-ci.sh: embed check= field in the rate-limited marker so
  the retry cron knows which check run to look up.
- dev-lead-fix-reviews.sh: human intent now posts a "please re-mention
  @dev-lead" ack instead of "I'll retry automatically" since it will NOT
  be retried automatically. human-pr keeps the auto-retry ack (correct).

**Pagination (Copilot + Gemini):**
- dev-lead-fix-ci.sh: add --paginate to check_idempotency, count_recent_
  failures, has_rate_limited_marker.
- dev-lead-fix-reviews.sh: add --paginate to has_reviews_rate_limited_marker.
- dev-lead-retry.sh: add --paginate to PR comments and PR list fetches.

**Other improvements:**
- dev-lead-retry.yml: simplify to single cron schedule (15 */2 * * *);
  remove BOT_USER env var (unused in the script).
- dev-lead-retry.sh: raise repo list limit 200 → 1000; add hard-error
  when list_repos_for_org returns empty (surfaces token permission issues
  rather than silently scanning 0 repos).
- dev-lead-intent.sh: use jq -nc (compact, null-input) for context JSON
  construction — safe against values with special characters.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(pr-review): handle Gemini trust-mode failure and properly route engine fallbacks (#217)

* fix(pr-review): handle Gemini trust-mode failure and properly route engine fallbacks

Closes #208

* fix(ci): install gemini CLI and set trust mode for dev-lead agents

---------

Co-authored-by: Gemini CLI <gemini-cli@example.com>

* fix: resolve YAML syntax error in dev-lead-reusable workflow

* fix: address automated feedback from PR 217 (#219)

This addresses missed feedback from CodeRabbit (removing syntax errors in YAML) and gemini-code-assist (improving string concatenation in validate-engines.sh), and adds assertion output checks to tests.

Co-authored-by: Gemini CLI <gemini-cli@example.com>

* chore: remove Phase 7 shadow period comment from dev-lead.yml

* feat: skip PRs with CHANGES_REQUESTED review (+ FORCE_REVIEW and stale-review guards)

* feat: skip PRs with CHANGES_REQUESTED review decision

Adds an early-exit check alongside the existing CI guards so the cascade
skips any PR where a human reviewer has already requested changes.
Reviewing those PRs before the author responds wastes tokens and creates
confusing parallel feedback threads.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix: honor FORCE_REVIEW and skip only stale-free CHANGES_REQUESTED reviews

Two issues addressed from Codex P2 feedback:

1. FORCE_REVIEW bypass: mention-triggered and force_review=true runs now
   bypass the CHANGES_REQUESTED skip, so authors can explicitly request a
   re-review after addressing feedback.

2. Stale review guard: only skip when a CHANGES_REQUESTED review targets
   the current head SHA. On repos without automatic stale-review dismissal,
   reviewDecision stays CHANGES_REQUESTED after new commits — the cascade
   now re-engages when the author has pushed new work.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): resolve context loss, missing checkout, and push logic (#223)

* feat: skip PRs with CHANGES_REQUESTED review decision

Adds an early-exit check alongside the existing CI guards so the cascade
skips any PR where a human reviewer has already requested changes.
Reviewing those PRs before the author responds wastes tokens and creates
confusing parallel feedback threads.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix: honor FORCE_REVIEW and skip only stale-free CHANGES_REQUESTED reviews

Two issues addressed from Codex P2 feedback:

1. FORCE_REVIEW bypass: mention-triggered and force_review=true runs now
   bypass the CHANGES_REQUESTED skip, so authors can explicitly request a
   re-review after addressing feedback.

2. Stale review guard: only skip when a CHANGES_REQUESTED review targets
   the current head SHA. On repos without automatic stale-review dismissal,
   reviewDecision stays CHANGES_REQUESTED after new commits — the cascade
   now re-engages when the author has pushed new work.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): resolve context loss, missing checkout, and push logic

- dev-lead-intent.sh: include actor and body in INTENT_CONTEXT for comments
- dev-lead.yml: parse and pass INTENT_ACTOR and INTENT_COMMENT_BODY to agent
- dev-lead-fix-reviews.sh: perform PR checkout, git push, and post summary comments

* fix(dev-lead): exit with 0 on no-changes in fix-reviews and human-pr

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* test: bypass CI check

* fix(pr-review): remove CI bypass hack

* fix(dev-lead): use heredoc for multiline environment variables (#224)

* fix(dev-lead): use heredoc for multiline environment variables

* fix(dev-lead): ensure all engines are installed and context is consistent

* fix(dev-lead): resolve stale model name in engine fallbacks

* fix(dev-lead): improve fallback reliability and script quality

- engine.sh: resolve stale model names in fallback loop; use -latest Gemini aliases
- dev-lead-fix-reviews.sh: remove unused code and fix PR_URL export

* fix(dev-lead): resolve Gemini model names and shell lint warnings

* security(dev-lead): use random heredoc delimiter to prevent inject…
don-petry added a commit that referenced this pull request Aug 3, 2026
… trigger for the Class-2 timer changes, before narrowing them (#1420)

* add: workflow to fix stuck PRs using bot account token

Allows running the cleanup script via workflow_dispatch with access to
GH_PAT (bot account token) from repo secrets.

* fix: pass GH_PAT to checkout action so workflow uses bot account

* debug: simplify account check, add authentication debug output

* docs: add comprehensive petry-review-bot setup instructions

* docs: add GitHub App setup guide (recommended approach)

GitHub App is the secure, recommended way to automate PR reviews:
- Fine-grained permissions
- JWT tokens that auto-expire
- No human account needed
- Better audit trail
- GitHub's recommended approach

Replaces the bot user account approach with a more secure alternative.

* docs: add comprehensive GitHub App setup documentation

- SETUP.md: Quick reference guide with configuration and troubleshooting
- IMPLEMENTATION.md: Technical deep dive on architecture and design decisions
- DOCUMENTATION.md: Index of all documentation files
- Updated README.md with status and quick links
- Updated GITHUB_APP_SETUP.md with implementation notes and actual app ID
- Workflows use GitHub App token generation instead of static PATs

Covers the transition from bot user account to GitHub App authentication for improved security and maintainability.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: silence auth check in fix-stuck-prs when using GitHub App token

GitHub App tokens don't have user scope, so 'gh api user' returns 403.
This is fine - the script still works for PR operations. Suppress the error
so the script completes successfully with app-token fallback label.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: use explicit author instead of @me in stuck PR search

GitHub App tokens don't have user identity, so @me search doesn't work.
Use explicit 'don-petry' author instead to find PRs to fix.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: avoid subshell in while loop to preserve variable state

Using pipe in while loop created subshell where PROBLEM_PRS and FIXED_PRS
counters were incremented but changes didn't persist to parent shell.
Fixed by using process substitution for input instead of pipe.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* docs: add comprehensive status report for GitHub App setup

- Overview of completed work
- GitHub App authentication details
- 24 stuck PRs successfully fixed with approval reviews
- Architectural decisions and rationale
- Configuration guide and usage examples
- Known limitations and troubleshooting

System is now fully operational with GitHub App token authentication,
comprehensive documentation, and all infrastructure in place.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* docs: document stuck PR cleanup status and script fixes

Added documentation for:
- GitHub App token compatibility fixes in fix-stuck-prs.sh
  * Changed @me to explicit author (app tokens have no identity)
  * Fixed subshell variable scope (process substitution vs pipe)
  * Silenced expected 403 auth check error
- Current status: 24 PRs have approvals but remain OPEN
  * Auto-merge failed due to missing GitHub App permission
  * Approvals satisfy branch protection requirement
  * Next: expand permissions or manually merge

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: update list-prs.sh to search all repos by owner instead of @me

GitHub App tokens don't have user identity, so @me/@review-requested searches
fail. Changed to enumerate all repos in don-petry and petry-projects, then
search for open PRs within each repo. This covers the full scope:
- All open PRs in personal don-petry repos
- All open PRs in petry-projects org repos

Resolves 6 consecutive workflow failures due to zero PRs being enumerated.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* docs: add investigation report for PR review agent workflow failures

* fix: move env section before steps in workflow to fix YAML structure

GitHub Actions requires env section to come before steps in job definition.
Moving env definition up and setting GH_TOKEN only in steps that need it.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* script: add backfill-approvals to retroactively apply real approvals

Converts PRs with agent approval comments (but no real GitHub approvals)
to have actual APPROVED reviews. Needed for PRs reviewed before the
gh pr review --approve fix was applied.

Usage:
  scripts/backfill-approvals.sh          # dry-run (preview changes)
  scripts/backfill-approvals.sh false    # apply real approvals

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* feat: add backfill-approvals workflow to run as GitHub App bot

Runs backfill-approvals.sh via GitHub Actions so the approvals are posted
by the bot identity, not the PR author — GitHub rejects self-approvals.

Also fixes subshell counter bug in the script (piped while loop lost variable
state; switched to process substitution).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: convert inner while loop to process substitution to preserve counters

Both the outer repo loop and inner PR loop were piped subshells, causing
all counter increments (approved/skipped/failed) to be lost. The summary
always showed 0. Fixed by using < <(...) process substitution for both loops.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* car-hunt: add VIN-deep-dive flow, printable checklists, location-tier ranking

- Step 5.5 pre-test-drive flow: NHTSA VIN decode + per-VIN unrepaired-recall
  automation via Chrome MCP (Honda LWC owner portal documented as not
  automatable; NHTSA web form works).
- §F printable test-drive checklist generator (reportlab/Platypus PDF) with
  cold-start, model-specific red flags, negotiation table, walk-away rules.
- §G fraud / paperwork verification checklist (vehicle-agnostic, reusable):
  walk-away triggers, VIN three-location match, curbstoning, odometer fraud,
  bill of sale, title transfer, payment protection, stolen-vehicle, title-
  jumping, after-purchase steps.
- Step 5.25 head-to-head comparison template with information-asymmetry rule.
- Step 3 Location Quality Tier scoring (Birmingham AL metro table A/B/C/D)
  with adj-CPM modifier; sheet schema gains Adj.CPM + Tier columns.
- Hard-disqualification rules added: non-running engine keywords, mileage
  inconsistency, mandatory description scrape.
- Drive MCP overwrite limitation documented; sheet ID now read from memory
  rather than hardcoded.
- FB Marketplace operational facts: Birmingham AL city ID 107739635926718,
  /search? vs /vehicles? query handling, React-controlled inline composer,
  send-button selector, seller-name extraction regex.
- scheduled-tasks/used-car-search-{morning,afternoon}: tier A/B priority
  flag, sheet ID read from memory.
- Reference PDF generator scripts checked in for reuse.

* fix: re-approve PRs where approval predates last commit (stale approval)

Instead of skipping PRs that already have any APPROVED review, skip only
those where reviewDecision is not REVIEW_REQUIRED. This handles PRs where
a bot approval was posted before a new commit was pushed — GitHub's ruleset
engine treats those approvals as stale even with dismiss_stale_reviews=false.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: robustly extract JSON verdict from mixed claude --print output (#17)

Claude's --print mode can prefix the JSON verdict with conversational
preamble text, causing jq to fail with parse errors. Add
extract_verdict_json to engine.sh and wire all three cascade action
call sites in review-one-pr.sh to use it.

Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: extract_verdict_json — check dest file first (agent Bash-write style)

The cascade-action agent writes its verdict JSON to \$OUTPUT_FILE via a
Bash tool call, then prints a text summary to stdout. The previous fix
only scanned stdout (the .raw file), which contained no JSON. The agent-
written file at \$dest (= \$OUTPUT_FILE) was already correct — just not
checked. Now check \$dest first before falling back to stdout scan.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Enforce MAX_REVIEW_CYCLES cap before running the cascade (#18)

* fix: enforce MAX_REVIEW_CYCLES cap with human-escalation marker

Previously MAX_REVIEW_CYCLES only gated AI delegation in
post-pr-review.sh — the cascade itself ran on every cron tick
regardless of how many cycles had accumulated. Real-world result:
a PR could rack up 9+ review cycles (we observed exactly that on
ContentTwin#100) before any cap took effect.

Add a pre-cascade check in review-one-pr.sh: when the count of
existing `<!-- pr-review-agent v1 sha=... -->` markers is at or
above MAX_REVIEW_CYCLES (default 3), post one escalation comment
marked `<!-- pr-review-agent escalation -->`, label
needs-human-review, request don-petry, and exit 100 (skip
sentinel — doesn't burn the MAX_PRS budget). The escalation
marker doubles as the no-spam guard: subsequent runs detect it
and exit 100 immediately.

Also reuse a single `gh pr view` for both the cycle count and the
escalation-marker check.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* review feedback: surface escalation-comment failures, drop dead unset

- Drop `2>/dev/null || true` on `gh pr comment` so a failed escalation
  post is visible in the workflow log instead of silently leaving the
  PR with no marker (which would re-trigger the cap path next tick).
- Reword the cycle-count comment from "AI delegation loops" to "review
  loops" — the cap also catches cascade-only loops where every cycle
  approves and a new commit lands before merge.
- Remove the redundant `unset PR_BODIES` — never exported, no leakage.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>

* Reliability hardening: session circuit breaker, timeouts, retry, dedup (#20)

* feat: session circuit breaker, per-tier timeouts, retry, and triage hard-fail

Reliability hardening for the PR review agent.

1. Session circuit breaker (.github/workflows/pr-review.yml): on any
   non-zero, non-100 exit from review-one-pr.sh (general failure or rate
   limit on the fallback engine), break the per-PR loop, log a clear
   error annotation naming the failing PR and reason, and exit the step
   with code 1 so the run shows red. Prevents one systemic problem from
   silently burning the entire candidate pool.

2. Per-tier timeouts (scripts/engine.sh): triage/deep/audit/action/duck
   each get their own bounded timeout (180/600/600/300/300s defaults,
   env-overridable). Previously only the duck had a timeout — a hung
   tier could burn the whole 60min job budget.

3. Retry-with-backoff on transient errors (scripts/engine.sh): triage
   retries once on 124/137/143 (timeout / signal kill) since its caller
   captures stdout via $(...) so retries are safe. Deliberately NOT
   applied to run_agentic/run_duck where stdout is redirected to a file
   — a retry there would corrupt the partial first-attempt output.

4. Triage non-JSON now hard-fails (scripts/review-one-pr.sh): replaces
   the silent fallback that synthesized a fake "escalate=MEDIUM" verdict
   and proceeded to deep review. With the new circuit breaker, loud
   failure is the right call — masking a broken triage was burning
   tokens on every PR while the workflow looked healthy.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix: stop stacking duplicate agent reviews on the same PR

Two bugs were causing the agent to leave multiple comments on the same PR.
Together they produced 10 stacked APPROVED reviews on petry-projects/ContentTwin#100.

Bug A — idempotency check is order-dependent (review-one-pr.sh):
The previous marker-discovery code did:
  ((.reviews // []) + (.comments // [])) | .[].body | grep marker | tail -1
This relies on the array concatenation order, not chronological order. When
old agent comments existed alongside newer agent reviews, tail -1 picked
the comment-array marker (older) over the review-array marker (newer),
causing the script to think the head SHA hadn't been reviewed and re-run.
Replaced with a single jq pipeline that tags each item with submittedAt /
createdAt, sorts by timestamp, and takes the actual most-recent marker.

Bug B — no cleanup of prior agent items (post-pr-review.sh):
After successfully posting a new review/comment, prior agent items were
left in place, accumulating forever. Added mark_prior_agent_items_obsolete
which, after a successful post:
  - dismisses prior APPROVED/COMMENTED/CHANGES_REQUESTED agent reviews via
    the GitHub dismissal API (UI shows them struck-through as Dismissed)
  - edits prior agent comments to wrap their body in a collapsed <details>
    block with a "Superseded by re-review at <SHA>" summary, plus a
    `<!-- pr-review-agent superseded -->` sentinel for idempotency

All cleanup API calls are best-effort — failures don't break the workflow.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(cleanup): warn on API failures, preserve global newest, file-stage JSON

Three fixes to mark_prior_agent_items_obsolete from the review of PR #20:

1. ::warning:: annotations on every cleanup API failure (review/comment
   list-fetch, individual review dismissal, individual comment fetch+edit).
   Previously these were silenced with `|| true`, so a permissions change
   on the dismissal endpoint would let duplicates stack indefinitely with
   no signal in the Actions UI. Cleanup is still non-fatal — the new post
   has already landed — but failures are now visible.

2. Preserve the globally-latest agent item across BOTH categories, not the
   newest of each category separately. The earlier code split reviews and
   comments and applied `[:-1]` to each, which left a stale fix-request
   comment in place when the new post was a review (or vice versa). The
   one-off cleanup of ContentTwin#100 hit exactly this case: 12 stacked
   reviews collapsed to 1, but a stale comment from 2026-04-25 (SHA
   cd9132d6) was preserved as "newest comment" even though the latest
   review at SHA 3af8c8ee was newer overall. Now: compute the max
   timestamp across both feeds, exclude items at that timestamp.

3. Stage API responses to disk (`mktemp` + `jq <file>`) instead of routing
   through `--argjson "$var"`. The old approach broke on rare unescaped
   control chars in user-authored comment bodies (jq refused to parse the
   resulting shell-vared JSON). File-based input sidesteps the shell
   pipeline entirely.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>

* Remove car-hunt skill and scheduled tasks (moved to don-petry/don-petry) (#21)

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: apply Copilot fallback hardening and reuse GH_PAT for Copilot auth

- Use secrets.GH_PAT (existing personal account token with Copilot
  subscription) instead of a separate COPILOT_GITHUB_TOKEN secret
- Pin actions/create-github-app-token to SHA (v3.1.1) for supply chain safety
- Add post-install verification for gh-copilot extension with warning on failure
- Guard Copilot fallback path: skip PR gracefully if extension is not installed

Addresses issue #24 workflow fixes (1-3) that could not land via PR #25
due to GitHub App lacking workflows permission.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: switch Copilot engine to gh built-in and fix app-id deprecation

- Remove gh extension install (github/gh-copilot conflicts with built-in alias)
- Replace with gh copilot --version check using COPILOT_GITHUB_TOKEN (GH_PAT)
- Update all engine.sh copilot invocations from bare `copilot` binary to
  `gh copilot suggest --target shell` with GH_TOKEN overridden to GH_PAT
  so the user token (with Copilot subscription) is used, not the App token
- Fix actions/create-github-app-token: app-id → client-id (same secret value,
  just the renamed input in v3.x)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: harden Copilot fallback path and remove stale worktrees (#25)

- gh-copilot install: add --force flag and post-install verification
  warning so silent failures due to built-in alias conflict are visible
  in workflow logs instead of being silently swallowed by `|| true`
- fallback pre-flight check: verify `gh extension list | grep copilot`
  before switching to Copilot engine; if unavailable, skip the PR and
  continue the batch rather than session-aborting and dropping all
  remaining candidates (fixes the 27-PR drop on run #503)
- pin actions/create-github-app-token to SHA for v3.1.1 (Node.js 24
  compatible) to prevent hard break on 2026-06-02 when GitHub forces
  Node.js 24 as default runtime
- remove 6 stale Claude Code worktrees from git tracking; add
  .gitignore entry to prevent future worktrees from being committed
  (eliminates exit-128 warning on every actions/checkout post-job sweep)

Closes #24

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: petry-projects-pr-review-agent[bot] <petry-projects-pr-review-agent[bot]@users.noreply.github.com>

* ci: add pre-flight dedup check to prevent duplicate claude-issue PRs (#26)

Before invoking Claude on a labeled issue, a new shell step queries
for any open PR with a branch matching claude/issue-NNN-* (or a body
containing "Closes #NNN"). If one is found it posts a comment on the
issue pointing to the existing PR and skips the Claude step entirely.
A secondary prompt instruction tells Claude to check first and push to
the existing branch rather than opening a new PR.

Motivated by issue petry-projects/google-app-scripts#171, where the
claude label was re-applied three times and each run created a fresh
PR unaware of the prior attempts.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor: migrate to machine user PAT auth (closes #27) (#28)

* refactor: migrate from GitHub App to machine user PAT auth

Closes #27

GitHub Apps cannot be listed in CODEOWNERS, blocking PRs in repos with
require_code_owner_review: true. Switch all workflows to use a machine
user account's fine-grained PAT (DON_PETRY_BOT_GH_PAT secret), which can
join an org team listed in CODEOWNERS.

Workflows: removed actions/create-github-app-token steps in pr-review,
fix-stuck-prs, backfill-approvals, and daily-pr-review-health. All now
use secrets.DON_PETRY_BOT_GH_PAT directly.

Docs: renamed GITHUB_APP_SETUP.md to MACHINE_USER_SETUP.md with full
rewrite covering account creation, CODEOWNERS config, PAT generation,
and rotation. Updated auth sections in IMPLEMENTATION.md, SETUP.md,
STATUS.md, DOCUMENTATION.md, README.md.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* refactor: switch to org-scoped PAT secret DON_PETRY_BOT_PETRY_PROJECT_PAT

Replace DON_PETRY_BOT_GH_PAT with DON_PETRY_BOT_PETRY_PROJECT_PAT — the
new fine-grained PAT scoped to the petry-projects org (resource owner =
petry-projects). The previous PAT was scoped to the donpetry-bot personal
namespace and had no repository access.

The old DON_PETRY_BOT_GH_PAT secret is retained for any future use against
don-petry's personal repos.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* chore: ignore .claude/scheduled_tasks.lock state file

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>

* refactor: merge approval repair scripts into single automation

- Consolidate backfill-approvals.sh and fix-stuck-prs.sh into repair-pr-approvals.sh
- Iterate all repos in both orgs instead of assuming author
- Verify no existing APPROVED review before posting
- Copy original agent comment as review body
- Enable auto-merge when posting approval if needed
- Single workflow with 30-min timeout

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: add @mention trigger for on-demand PR reviews (#30)

Adds repository_dispatch support so commenting @petry-review-bot on any
PR fires an immediate review without waiting for the hourly schedule.

- pr-review.yml: new `repository_dispatch` trigger (type: pr-review-mention),
  per-PR concurrency group for mention runs, FORCE_REVIEW and DRY_RUN
  env vars that handle both workflow_dispatch and repository_dispatch paths
- review-one-pr.sh: FORCE_REVIEW=true bypasses idempotency so a mention
  always runs a fresh cascade even if the head SHA hasn't changed
- templates/mention-listener.yml: deploy to petry-projects/.github;
  listens for @petry-review-bot, validates commenter trust, posts ack,
  sends repository_dispatch (requires Contents:write, not Actions:write)

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: harden rebase, review dismissal, and health log diagnostics (#40)

Addresses 4 code-actionable recommendations from health check report #33:

- [CRITICAL] Make rebase/branch-update non-fatal — 403/504 during rebase
  emits ::warning:: instead of aborting the batch session (#34)
- [MEDIUM] Add retry with exponential backoff (3 attempts) for transient
  5xx on update-branch API; break immediately on 4xx (#37)
- [MEDIUM] Guard review dismissal with state re-check before calling
  dismiss API, preventing 422s from race conditions (#38)
- [LOW] Surface missing log warnings in health check script (#39)
- Fix: skip auto-merge when branch is still BEHIND after failed rebase

Closes #34, closes #37, closes #38, closes #39

* refactor: migrate to org-wide .github-private convention (#41)

* refactor: parametrize hardcoded identity values for org migration

Replace all hardcoded references to don-petry, petry-review-bot, and
don-petry/pr-review-agent with environment variables that default to
the current values. This allows the agent to be configured for different
orgs/users via repo variables.

Changes:
- Scripts use $REVIEWER_USER, $TARGET_ORG, $BOT_USER, $AGENT_REPO
- Prompts no longer reference specific GitHub usernames
- Workflows use org-level GH_PAT_WORKFLOWS secret instead of
  repo-level DON_PETRY_BOT_PETRY_PROJECT_PAT
- Health check uses context.repo.owner/repo for issue creation
- Variables are set in workflow env block with defaults

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add Copilot custom agent profiles for org-wide use

Create /agents/ directory with three agent profiles:
- pr-reviewer: Multi-tier cascading PR review
- feature-ideator: Feature idea generation and prioritization
- compliance-auditor: Org standards compliance checking

These are Copilot custom agent profiles that become available
org-wide in the .github-private repo convention.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Squashed 'frameworks/bmad-method/' content from commit e36f219c

git-subtree-dir: frameworks/bmad-method
git-subtree-split: e36f219c81b6010d4aae423ba12f49edb5b6e31a

* Squashed 'frameworks/spec-kit/' content from commit 11f49ebf

git-subtree-dir: frameworks/spec-kit
git-subtree-split: 11f49ebfb2f6af55345cb4bd9a7906acd211e56f

* Squashed 'frameworks/gsd/' content from commit 304c1a13

git-subtree-dir: frameworks/gsd
git-subtree-split: 304c1a1302564c45af252bbba4bcc5350e7dac3a

* docs: update README for .github-private org infrastructure role

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* ci: add dependabot-automerge.yml workflow (#73)

Adds the required dependabot-automerge.yml workflow from the org
standard template (petry-projects/.github/standards/workflows/).
This is a thin caller stub that delegates to the org-level reusable
workflow.

Closes #48

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Don Petry <don-petry@users.noreply.github.com>

* fix(pr-review): unblock queue starvation from self-authored PRs (#96) (#97)

* fix(pr-review): unblock queue starvation from self-authored PRs (#96)

A self-authored PR sorted first in the candidate list and triggered
"Can not approve your own pull request" — which the session-fatal
abort treated like an engine error, skipping all 28 remaining PRs
on every run.

- list-prs.sh: filter out PRs authored by REVIEWER_USER at enumeration
- post-pr-review.sh: catch the GraphQL self-approval error and exit 100
  (no-op) instead of 1, so a stray self-PR can no longer abort the batch
- engine.sh: gh copilot renamed --target to --agent; fix the rubber-duck
  invocations so tier-2 cross-engine review works again
- review-one-pr.sh: stop appending a duplicate "0" to REVIEW_CYCLE when
  grep -c finds no markers under set -o pipefail (was breaking the
  cycle-cap integer comparison)

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

* fix(pr-review): authenticate as bot, harden enumeration, address review

- Workflow now runs as AGENT_USER (default don-petry-bot), distinct from
  REVIEWER_USER (the human, don-petry). The self-approval rejection that
  caused #96 came from the agent and the human sharing one identity.
- list-prs.sh filters self-authored PRs against AGENT_USER, and validates
  AGENT_USER against the GitHub username charset before interpolating into
  the jq filter (Copilot review feedback).
- review-one-pr.sh uses printf '%s\n' instead of echo for PR_BODIES, since
  PR body content is user-authored and could begin with -n/-e or contain
  backslash escapes (Copilot review feedback).
- AGENT.md guideline updated to reflect that self-authored PRs are
  intentionally excluded (CodeRabbit review feedback).

Operator note: rotate the GH_PAT_WORKFLOWS secret to a token owned by
don-petry-bot (with read:org added) for the bot-account behavior to take
effect at runtime.

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

* refactor(pr-review): collapse to single BOT_USER, route escalations via CODEOWNERS

Drop AGENT_USER and REVIEWER_USER. The workflow now has one identity:
BOT_USER (default don-petry-bot), which both owns the repos to scan and
gets filtered out as the self-approval blocker. Human escalation no
longer hard-codes a single reviewer; instead, scripts/request-codeowners-
review.sh parses CODEOWNERS in the PR's repo and requests review from
every @user / @org/team mention.

- list-prs.sh: BOT_USER for both gh-repo-list and self-author filter
- review-one-pr.sh, post-pr-review.sh: replace --user "$REVIEWER_USER"
  with the CODEOWNERS helper
- repair-pr-approvals.sh: same gh-repo-list switch
- pr-review.yml: drop AGENT_USER/REVIEWER_USER, set BOT_USER default
  to don-petry-bot
- AGENT.md: updated guideline to describe single-identity model

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

* refactor(pr-review): extract batch loop, hoist env, drop redundant step

Workflow YAML drops from 231 to 111 lines by moving the per-PR review
loop into scripts/review-batch.sh. The script can be syntax-checked and
exercised locally; the workflow now just wires env vars and dispatches.

- Hoist GH_TOKEN, MAX_PRS, CANDIDATE_LIMIT to job-level env (were
  repeated on individual steps).
- Inline `gh auth status` into the install step; remove the standalone
  Verify auth step (its only output was a one-line auth dump).
- Drop the `if: steps.list.outputs.count != '0'` guard and the step's
  `id`/output: review-batch.sh handles empty input as a no-op.
- Collapse the duplicated summary-string branch in the review loop into
  a single template with a conditional fragment.

No behavior change.

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

* perf(pr-review): cache claude-code CLI install across runs

`npm install -g @anthropic-ai/claude-code` ran on every workflow start
(~30s). Switch to a per-user npm prefix (~/.npm-global) and cache that
directory via actions/cache, keyed on CLAUDE_CODE_VERSION + runner OS.

A `command -v claude` guard makes the install a no-op on cache hit, so
the only cost on subsequent runs is the cache restore.

CLAUDE_CODE_VERSION defaults to 'latest' (cache persists until manually
flushed); set the repo variable to pin a specific version for fully
reproducible caching.

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

---------

Co-authored-by: Claude <noreply@anthropic.com>

* chore: rename GH_PAT_WORKFLOWS secret to DON_PETRY_BOT_GH_PAT (#99)

The previous name was generic and didn't tell you which account the PAT
belonged to. The new name makes the binding explicit: this secret is the
PAT owned by don-petry-bot, used as BOT_USER throughout the PR-review
workflows.

Operator follow-up before this can be merged:
- Add a new repo secret DON_PETRY_BOT_GH_PAT containing the bot's PAT
  (with repo, workflow, and read:org scopes).
- After merge, the old GH_PAT_WORKFLOWS secret can be deleted.

Affected workflows:
- pr-review.yml (1 use)
- claude.yml (4 uses, with || github.token fallback)
- daily-pr-review-health.yml (1 use)
- repair-pr-approvals.yml (1 use)

scripts/pr_review_health.sh's error message is updated to point at the
new name as well.

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

Co-authored-by: Claude <noreply@anthropic.com>

* chore: remove frameworks directory (#101)

Co-authored-by: Gemini CLI <gemini-cli@example.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore all erroneously deleted files (items 4-34) (#104)

* revert: restore .github/workflows/daily-pr-review-health.yml

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore .github/workflows/repair-pr-approvals.yml

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore agents/pr-reviewer.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/cascade-action.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/deep-review.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/rubber-duck.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/security-audit.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/shared.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/single-review.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/synthesize-duck.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/synthesize.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/triage.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/engine.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/list-prs.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/post-pr-review.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/pr_review_health.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/repair-pr-approvals.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/request-codeowners-review.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/review-batch.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/review-one-pr.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore templates/mention-listener.yml

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore AGENT.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore BOT_SETUP.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore DOCUMENTATION.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore IMPLEMENTATION.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore MACHINE_USER_SETUP.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore PR_REVIEW_AGENT_FAILURE_REPORT.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore PR_REVIEW_FAILURE_INVESTIGATION.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore SETUP.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore STATUS.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore README.md to pre-change state

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore .github/workflows/daily-pr-review-health.yml

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat: add Gemini Pro support and optimize PR review fallback chain (#102)

* Merge main and resolve conflicts

* Enhance pr-review workflow: add Gemini support and refactor fallback logic

* Address Copilot review comments: tighten regex, fix fallback summary, and align docs

* docs: move agent documentation to docs/ folder and rename files

* docs: align secret names and fix casing in index

* docs: organize pr-review-agent documentation under dedicated folder

* Address final PR comments: tighten regex, add preflight checks, fix bot names, and cleanup docs

---------

Co-authored-by: Gemini CLI <gemini-cli@example.com>

* fix(pr-review): gate rate-limit detection on non-zero exit code

Broad patterns like `plan.*limit` and `claude.*usage` could match content
in a successful triage summary, triggering a false-positive engine fallback.
Guard the check with TRIAGE_RC != 0 so it only fires when the provider
command actually failed.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(claude): sync inlined workflow with org standard (#109)

* fix(claude): sync inlined workflow with org standard

Brings the inlined claude.yml up to parity with
petry-projects/.github/.github/workflows/claude-code-reusable.yml.

Changes:
1. Bot allow list for pull_request_review_comment
   Add coderabbitai[bot], Copilot, copilot-pull-request-reviewer[bot],
   and gemini-code-assist[bot] alongside the existing OWNER/MEMBER/
   COLLABORATOR check. These bots have author_association 'NONE' so
   their review comments were always skipped.

2. check_run trigger + claude-ci-fix job
   Port the CI failure auto-fix feature from the reusable. When a check
   fails on a PR, Claude diagnoses and fixes it automatically.
   Adapted to use DON_PETRY_BOT_GH_PAT (this repo's secret name).

3. claude-code-action SHA bump: v1.0.89 → v1.0.119
   Update both the claude and claude-issue jobs.

4. paths-ignore on pull_request trigger
   Prevents the workflow from firing on PRs that only change
   claude.yml itself, avoiding the Anthropic OIDC validation
   failure (workflow file must match default branch at token exchange).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(claude): guard bot allow list against fork PRs

Same fix as petry-projects/.github PR #238 — add same-repo guard
for bot-triggered pull_request_review_comment runs.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(claude): address ci-fix self-loop and fork PR security

- Replace Claude Code name prefix check with explicit job name list to
  correctly prevent self-loops (check_run names for inlined workflows
  are bare job names, not workflow-prefixed)
- Add fork PR trust gate in Resolve PR number step: verify head repo
  matches target repo before running Claude with privileged credentials

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* ci: replace inlined claude.yml with standard thin-caller stub

The inlined version embedded all three jobs (claude, claude-ci-fix,
claude-issue) directly and threaded DON_PETRY_BOT_GH_PAT through
checkout tokens and github_token, causing the bot's PAT to author
PRs and comments instead of github-actions[bot].

Replace with the standard thin caller that delegates to the org-level
reusable workflow (claude-code-reusable.yml@v1) via secrets: inherit,
which is the correct pattern per:
petry-projects/.github/standards/workflows/claude.yml

* fix: replace gh copilot suggest with GitHub Models REST API (#151)

* fix: replace gh copilot suggest with GitHub Models REST API (#147)

The `gh copilot suggest -p "$(cat <file>)"` invocation failed with
"Invalid command format" because:
1. The `-p` flag is not valid syntax in modern `gh copilot` built-in versions.
2. `gh copilot suggest` is a shell-command suggestion tool; it does not
   accept large PR prompts or return structured JSON responses.
3. Passing a multi-thousand-line prompt via `$(cat ...)` can hit ARG_MAX.

The non-zero exit was then misclassified by the rate-limit detector, which
aborted the entire session and skipped all remaining PRs in the queue.

Fix: replace all three copilot invocations (run_triage, run_agentic,
run_duck) with a new `copilot_chat` helper that calls the GitHub Models
REST API directly via curl. The API is OpenAI-compatible, versioned via
`X-GitHub-Api-Version`, stable against gh CLI version changes, and accepts
arbitrary prompt sizes using python3 for safe JSON encoding.

Additional changes:
- Add `COPILOT_API_MODEL` var (default `openai/o4-mini`) overrideable via env.
- run_agentic/run_duck copilot cases now also write output to $OUTPUT_FILE
  so callers that check that path directly (deep review, audit) find the JSON.
- Rate-limit responses (HTTP 429) are echoed to stdout so the existing
  `is_rate_limited()` detector fires correctly for engine fallback.
- Add pre-flight smoke test in review-batch.sh: tests GitHub Models API
  connectivity with a one-liner prompt before processing any PRs, so auth
  or model errors surface immediately as a clear setup failure.
- Add tests/test_copilot_chat.sh: unit tests for the JSON payload builder
  with edge-case prompts (quotes, newlines, # headings, large diffs, Unicode).

Closes #147

Co-authored-by: Don Petry <don-petry@users.noreply.github.com>

* fix: scope COPILOT_API_MODEL export to copilot engine only

Move the `export COPILOT_API_MODEL` from the common exports block into the
`copilot)` case where the variable is set, so it is not exported as an
empty/unset variable when the engine is `claude` or `gemini`. The
`copilot_chat` fallback `${COPILOT_API_MODEL:-openai/o4-mini}` still works
correctly when `DUCK_ENGINE=copilot` under a non-copilot primary engine.

Co-authored-by: Don Petry <don-petry@users.noreply.github.com>

* fix: address review comments — temp file for payload, fail-fast source, streaming output

- copilot_chat: write JSON body to mktemp file, pass to curl as @file to
  avoid ARG_MAX for large PR diffs (was --data-binary "$body")
- copilot_chat: add :? guard on COPILOT_GITHUB_TOKEN for a clear error
  instead of generic "unbound variable" under set -u
- run_agentic / run_duck copilot paths: stream directly to stdout (and
  tee to OUTPUT_FILE when set) rather than buffering the full response
  into a shell variable, which forced large outputs into memory and
  stripped trailing newlines
- review-batch.sh pre-flight: fail fast if source engine.sh fails
  (was silently ignored with || true)
- review-batch.sh pre-flight: build smoke-test JSON payload via python3
  + temp file instead of shell string interpolation to avoid JSON
  injection if COPILOT_API_MODEL contains special characters
- Clarify that openai/o4-mini is the correct April-2025 o4-generation
  model name, not a typo for o1-mini or gpt-4o-mini

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Don Petry <don-petry@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* feat: prioritise .github/.github-private PRs, oldest-first within tier (#155)

* feat: prioritise .github/.github-private PRs, oldest-first within tier

- list-prs.sh: add createdAt to JSON fetch; emit priority|createdAt|url
  lines (priority 0 for .github/.github-private, 1 for everything else)
- Replace final sort -u with a two-pass sort: deduplicate by URL, then
  sort by priority asc then createdAt asc; strip sort keys with cut
- tests/test_list_prs_sort.sh: 16 tests covering priority classification,
  oldest-first ordering, mixed scenarios and deduplication
- .github/workflows/test.yml: run both unit-test files on every PR / push

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* test: add dedup edge case for same URL with conflicting priorities

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: remove temperature from copilot_chat payload (o4-mini incompatible)

The GitHub Models API rejects temperature=0 for reasoning models like
o4-mini with HTTP 400:
  'Unsupported value: temperature does not support 0 with this model.
   Only the default (1) value is supported.'

This caused the rubber duck (DUCK_ENGINE=copilot / DUCK_MODEL=o4-mini)
to fail with HTTP 400 on every claude-engine run, silently degrading
every review to deep-only with no cross-engine sanity check.

Fix: remove temperature from the copilot_chat JSON payload entirely —
the API defaults to 1, which is the only supported value for o4-mini.

Also:
- Update tests/test_copilot_chat.sh: sync build_payload to match and
  flip Test 9 to assert temperature is ABSENT (not 0).
- Add .github/workflows/test.yml: run unit tests on every PR and push
  to main so regressions are caught before merge.

Reproducer: job/75690347409 — duck failure logged as:
  copilot_chat: HTTP 400 from GitHub Models API
  {"error":{"message":"Unsupported value: 'temperature' does not
   support 0 with this model..."}}

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: address PR review comments

- list-prs.sh: tighten priority regex to /[.]github(-private)?/pull/
  so path-boundary anchor prevents false positives (e.g. foo.github)
- list-prs.sh: replace printf pipeline with here-string to avoid ARG_MAX
  limits on large PR sets
- test.yml: add permissions: contents: read (least-privilege, CodeQL fix)
- test.yml: add timeout-minutes: 5 to prevent hung runs
- test_list_prs_sort.sh: sync sort_entries helper (here-string + || true)
  and classify regex to match updated list-prs.sh exactly

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: don-petry <don@petry.dev>

* fix: restore pr-review.yml content (accidentally emptied in revert)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: restore pr-review.yml (file was accidentally emptied)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore(deps): bump actions/cache from 4 to 5 (#165)

Bumps [actions/cache](https://github.com/actions/cache) from 4 to 5.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): bump petry-projects/.github/.github/workflows/claude-code-reusable.yml (#164)

Bumps [petry-projects/.github/.github/workflows/claude-code-reusable.yml](https://github.com/petry-projects/.github) from 1 to 2.
- [Commits](https://github.com/petry-projects/.github/compare/v1...v2)

---
updated-dependencies:
- dependency-name: petry-projects/.github/.github/workflows/claude-code-reusable.yml
  dependency-version: '2'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* fix(list-prs): search DELEGATION_ORGS and drop --checks success pre-filter

- Add iteration over all orgs in DELEGATION_ORGS (e.g. don-petry) so PRs
  in those orgs enter the candidate pool. Previously only BOT_USER and
  TARGET_ORG were searched.
- Remove --checks success from org repo searches. GitHub excludes PRs from
  repos with no CI configured when this flag is used, silently dropping all
  PRs from repos like .github, .github-private, google-app-scripts, and
  TalkTerm. review-one-pr.sh already enforces CI gating per-PR and treats
  empty statusCheckRollup as passing, so the pre-filter is redundant and
  harmful.
- Add --limit 200 to gh repo list calls to handle org growth beyond gh's
  default 30-repo cap.

* feat: add code-quality ruleset (compliance fix #60) (#86)

Creates the required `code-quality` repository ruleset enforcing required
status checks on the default branch, as mandated by the org standard:
standards/github-settings.md#code-quality--required-checks-ruleset-all-repositories

Required checks:
- SonarCloud (code quality analysis)
- CodeQL (SAST)
- agent-shield / AgentShield (agent security scan)
- dependency-audit / Detect ecosystems (dependency vulnerability scan)

Bypass actors:
- OrganizationAdmin (always) — emergency override
- dependabot-automerge-petry Integration (always) — Dependabot auto-merge

The ruleset was applied directly via GitHub API. This file documents the
configuration as code for auditability and future reapplication.

Closes #60

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Don Petry <don-petry@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* feat(prompts/dev-lead): add human prompt template

* feat(prompts/dev-lead): add human-pr prompt template

* feat(dev-lead): implement dev-lead agent Phases 0-6

* fix: per-PR isolation and single-review retry (closes #132)

- review-batch.sh: non-rate-limit per-PR failures (exit code 1) no
  longer abort the session. SESSION ABORTED EARLY is now reserved for
  the rate-limit-on-fallback-engine case (exit code 2) only. All other
  failures are counted and logged; remaining candidates continue.

- review-one-pr.sh: single-review step retries up to
  SINGLE_REVIEW_MAX_RETRIES (default 2) times with a
  SINGLE_REVIEW_RETRY_DELAY_SEC (default 15s) gap before giving up.
  On exhaustion, the PR is flagged needs-human-review and the script
  exits with code 1, which the updated batch treats as a non-fatal
  per-PR failure. Raw model output and stderr are logged on each failed
  attempt for post-mortem visibility.

Root cause of run #25707852006: claude-opus-4-7 returned a verbose
non-JSON response for PR #129; the old code treated that as fatal and
skipped 35 remaining candidates.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix: rate-limit detection and per-attempt stderr logs in single-review retry

Address inline review comments on PR #133:

- Rate-limit check: after each run_agentic call, inspect both stdout
  (VERDICT_JSON.raw) and stderr (SINGLE_LOG) with is_rate_limited before
  retrying. A rate-limit match exits immediately with code 2 so
  review-batch.sh can trigger engine fallback — consistent with triage and
  deep-review tiers. Previously a rate-limited single-review would burn all
  retries and exit 1 (per-PR failure), silently leaving the batch on the
  same rate-limited engine for all remaining PRs.

- Per-attempt log files: stderr is now written to
  single-review-attempt-N.log rather than a single overwritten file, so
  no earlier-attempt errors are lost. Each attempt logs its own stderr
  inline on failure; the fallback path cats all attempt logs for
  post-mortem visibility.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* feat(dev-lead): Phase 0 test infrastructure and Phase 1 intent stub

Phase 0: full test harness for the dev-lead agent —
  26 event fixtures (all valid JSON with _test_expected_intent),
  stub claude/gemini engines, mock gh binary, CI failure log sample,
  bats helpers (stub-engine, mock-gh, assert-env, prompt-vars),
  7 prompt templates with VARIABLES declarations, preflight script,
  prompt coverage integration test, and test-dev-lead.yml CI workflow.

Phase 1: dev-lead.yml trigger workflow (all 7 event types, dispatch + ci-relay
  jobs) and dev-lead-intent.sh stub (anti-loop guard live; all other events
  emit skip/not-implemented).  14/14 bats unit tests pass.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): add permissions blocks to test workflow jobs (CodeQL)

* fix(ci): auto-fix for lint / eslint [skip ci-relay]

* feat(dev-lead): Phases 2-6 — CI fix, review fix, issue, engine fallback

Phase 2: run_writer/run_writer_with_fallback in engine.sh, full intent
routing in dev-lead-intent.sh, dev-lead-fix-ci.sh handler, workflow wiring.

Phase 3: dev-lead-fix-reviews.sh handles fix-reviews, fix-bot-comment,
human, human-pr, rebase intents. Full review routing in intent classifier.

Phase 5: dev-lead-fix-issue.sh handles the issue intent with dedup guard
and branch/PR creation.

Phase 6: run_writer_with_fallback with claude→gemini→copilot fallback chain.

Tests: 77 unit tests across 8 new .bats files, all passing. Updated Phase 1
stub tests to reflect actual Phase 2+ routing behavior.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): address review comments — dispatch JSON, dead code, portable stub gh

* fix(dev-lead): pre-flight after intent, fork URL check, review-batch exit code, retry comment

* fix(dev-lead): install bats-core from GitHub to avoid root requirement

* fix(security): move event values to env vars to prevent script injection (SonarCloud)

* fix(security): move CLAUDE_CODE_VERSION to env block (SonarCloud script injection)

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* feat(dev-lead): Phase 1.5 — reusable workflow for cross-repo adoption + Phase 7 shadow period

* feat(dev-lead): add reusable workflow (Phase 1.5) and begin shadow period (Phase 7)

- Create .github/workflows/dev-lead-reusable.yml: workflow_call entry
  point for other repos. Checks out .github-private scripts/prompts into
  .dev-lead/, then runs the same intent-classify + handler pipeline as
  dev-lead.yml with PROMPTS_DIR=.dev-lead/prompts/dev-lead.
- Add PROMPTS_DIR env-var support to dev-lead-fix-ci.sh,
  dev-lead-fix-reviews.sh, and dev-lead-fix-issue.sh so the reusable
  workflow can point scripts at the sparse-checkout path without changing
  CWD. Defaults to prompts/dev-lead (backwards-compatible for dev-lead.yml).
- Annotate dev-lead.yml with Phase 7 shadow-period window (2026-05-15
  through ~2026-05-29): claude.yml and dev-lead.yml run in parallel until
  regressions clear, then claude.yml is removed.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* chore(dev-lead): update shadow-period tracking issue ref to #180

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): document PAT requirement and pin ref:main on private checkout

* fix(dev-lead): P1 review fixes — ci-relay in reusable, .dev-lead gitignore, export PROMPTS_DIR, trim header

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): rate-limit detection, separate status, and retry cron (#192) (#196)

* chore(dev-lead): decommission claude.yml, bump ACTION_TIMEOUT_SEC to 600s

- Delete .github/workflows/claude.yml — replaced by dev-lead.yml (shadow
  period complete, tracking issue #180).
- scripts/engine.sh: raise ACTION_TIMEOUT_SEC default 300→600s to reduce
  timeout failures on large-repo fix-ci runs (PR #80 had 3 timeouts).
- AGENTS.md: remove claude.yml immutability exemption; note dev-lead.yml
  as the active AI automation workflow and its edit-via-reusable pattern.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* refactor: replace Claude analysis with pure gh/jq telemetry in health check

* refactor: drop Node/Claude steps from daily-pr-review-health workflow

* rename: daily-pr-review-health → actions-fleet-monitor

* rename: daily-pr-review-health → actions-fleet-monitor

* fix(agents-md): clarify dev-lead.yml vs dev-lead-reusable.yml scope

dev-lead.yml in .github-private runs inline steps (not a caller stub).
Behavior changes for this repo go to dev-lead.yml directly; changes that
affect all org repos via the cross-repo reusable go to dev-lead-reusable.yml.

Addresses Copilot review on PR #194.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): rate-limit detection, separate status, and retry cron

Closes #192. Implements all phases from the revised plan.

**Phase 0 — fix run_writer stdout capture (prerequisite)**
- engine.sh: capture stdout via `tee` to a tempfile so `is_rate_limited`
  can inspect the output; old code read `/tmp/dev-lead-writer-stderr`
  which was never written (claude --print outputs to stdout, not stderr).
  Fallback engines were never tried in practice.
- engine.sh: add `parse_reset_time` to extract ISO timestamp from
  `resets H:MMpm (UTC)` in engine output and write it to
  `/tmp/dev-lead-rate-limit-reset` for callers to embed in markers.

**Phase 1 — separate status=rate-limited from status=failed**
- fix-ci.sh: detect engine exit 2 → post `status=rate-limited` (not
  `status=failed`); embed parsed reset time in marker body; exit 2.
- fix-ci.sh: fix `check_idempotency` to treat `status=rate-limited`
  as retriable — only block on terminal statuses (applied, failed,
  no-changes). Previously, rate-limited markers blocked all retries.
- fix-ci.sh: `count_recent_failures` already filtered to `status=failed`;
  add explicit comment confirming rate-limited markers are excluded.
- fix-ci.sh: add `has_rate_limited_marker` dedup check so the same SHA
  never accumulates more than one rate-limited comment.
- fix-reviews.sh: on engine exit 2 for all five intent types
  (fix-reviews, fix-bot-comment, human, human-pr, rebase), post a
  `status=rate-limited` marker with embedded reset time and exit 2.
- fix-reviews.sh: for `human` and `human-pr` intents, also post a
  user-visible acknowledgment comment so users know their request was
  received and will be retried.

**Phase 2 — scheduled retry cron for fix-ci**
- dev-lead-retry.sh: new script scans all open PRs across TARGET_ORG
  for `status=rate-limited` markers on current HEAD SHA; skips PRs
  whose reset time is still in the future; staggered dispatches (30s
  between repos) to prevent cascading org-level rate-limit hits.
- dev-lead-retry.yml: new workflow — scheduled every 2 hours (dual
  offsets to work around GitHub scheduler skew), plus workflow_dispatch.

**Phase 3 — retry for fix-reviews intents (same cron)**
- dev-lead-retry.sh already handles all five fix-reviews intent types
  (fix-reviews, fix-bot-comment, human, human-pr, rebase) via
  `dev-lead-reviews-retry` dispatch events.
- dev-lead-intent.sh: add `dev-lead-reviews-retry` dispatch type routing
  to the intent classifier — maps intent_type from payload to the correct
  existing fix-reviews step.
- dev-lead.yml: add `dev-lead-reviews-retry` to repository_dispatch types.
- dev-lead.yml: unify concurrency group for all dispatch types to
  `dev-lead-pr-{pr_number}` to avoid per-type slot fragmentation.

**Tests**
- test_engine_writer.bats: 8 new tests covering rate-limit stdout detection,
  exit-2 mapping, fallback exhaustion, reset time parsing.
- test_fix_ci.bats: 5 new tests covering rate-limited status, exhaustion
  exclusion, idempotency pass-through, and dedup.
- test_fix_reviews.bats: 5 new tests covering all intent types + human ack.
- test_intent_ci.bats: 4 new tests for dev-lead-reviews-retry dispatch routing.
- e2e/scenarios/07-rate-limit-retry.sh: new E2E scenario covering all parts.
- 3 new event fixtures for repository_dispatch_reviews_retry_* payloads.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): address review comments on #196

Addresses all 18 review threads from Copilot, Codex, and Gemini.

**P1 bugs fixed:**
- dev-lead-retry.sh: all log echo calls in scan_pr_for_rate_limits and
  dispatch_* functions now write to stderr; only the final count is on
  stdout. Previously, log lines polluted the command substitution,
  causing arithmetic expansion to fail and aborting the scan on the
  first retry candidate.
- dev-lead-retry.yml: permissions: contents was read-only; creating
  repository_dispatch events requires contents: write.

**Correctness fixes:**
- dev-lead-retry.sh: restrict automated retries to intents whose context
  can be reconstructed at runtime (fix-reviews, human-pr, rebase). human
  and fix-bot-comment require USER_INSTRUCTION/COMMENT_BODY from the
  original event which cannot be re-fetched; they are explicitly excluded.
- dev-lead-fix-reviews.sh: resolve HEAD_SHA from the PR API when not
  provided by the triggering event (issue_comment intents carry no SHA).
  Without this, rate-limited markers had no sha= field and were
  invisible to the retry scanner.
- dev-lead-fix-reviews.sh: write terminal status=applied marker after
  successful fix-reviews, human-pr, rebase runs. Prevents the retry
  cron from re-dispatching the same intent on every subsequent tick when
  the SHA hasn't changed.
- dev-lead-retry.sh: check for reviews terminal marker before dispatching
  a retry (mirrors the existing fix-ci terminal-marker check).
- dev-lead-retry.sh: look up current check-run details (details_url, id)
  at dispatch time via the commits check-runs API, so retried fix-ci runs
  have full failure logs and annotations rather than empty fields.
- dev-lead-fix-ci.sh: embed check= field in the rate-limited marker so
  the retry cron knows which check run to look up.
- dev-lead-fix-reviews.sh: human intent now posts a "please re-mention
  @dev-lead" ack instead of "I'll retry automatically" since it will NOT
  be retried automatically. human-pr keeps the auto-retry ack (correct).

**Pagination (Copilot + Gemini):**
- dev-lead-fix-ci.sh: add --paginate to check_idempotency, count_recent_
  failures, has_rate_limited_marker.
- dev-lead-fix-reviews.sh: add --paginate to has_reviews_rate_limited_marker.
- dev-lead-retry.sh: add --paginate to PR comments and PR list fetches.

**Other improvements:**
- dev-lead-retry.yml: simplify to single cron schedule (15 */2 * * *);
  remove BOT_USER env var (unused in the script).
- dev-lead-retry.sh: raise repo list limit 200 → 1000; add hard-error
  when list_repos_for_org returns empty (surfaces token permission issues
  rather than silently scanning 0 repos).
- dev-lead-intent.sh: use jq -nc (compact, null-input) for context JSON
  construction — safe against values with special characters.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(pr-review): handle Gemini trust-mode failure and properly route engine fallbacks (#217)

* fix(pr-review): handle Gemini trust-mode failure and properly route engine fallbacks

Closes #208

* fix(ci): install gemini CLI and set trust mode for dev-lead agents

---------

Co-authored-by: Gemini CLI <gemini-cli@example.com>

* fix: resolve YAML syntax error in dev-lead-reusable workflow

* fix: address automated feedback from PR 217 (#219)

This addresses missed feedback from CodeRabbit (removing syntax errors in YAML) and gemini-code-assist (improving string concatenation in validate-engines.sh), and adds assertion output checks to tests.

Co-authored-by: Gemini CLI <gemini-cli@example.com>

* chore: remove Phase 7 shadow period comment from dev-lead.yml

* feat: skip PRs with CHANGES_REQUESTED review (+ FORCE_REVIEW and stale-review guards)

* feat: skip PRs with CHANGES_REQUESTED review decision

Adds an early-exit check alongside the existing CI guards so the cascade
skips any PR where a human reviewer has already requested changes.
Reviewing those PRs before the author responds wastes tokens and creates
confusing parallel feedback threads.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix: honor FORCE_REVIEW and skip only stale-free CHANGES_REQUESTED reviews

Two issues addressed from Codex P2 feedback:

1. FORCE_REVIEW bypass: mention-triggered and force_review=true runs now
   bypass the CHANGES_REQUESTED skip, so authors can explicitly request a
   re-review after addressing feedback.

2. Stale review guard: only skip when a CHANGES_REQUESTED review targets
   the current head SHA. On repos without automatic stale-review dismissal,
   reviewDecision stays CHANGES_REQUESTED after new commits — the cascade
   now re-engages when the author has pushed new work.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): resolve context loss, missing checkout, and push logic (#223)

* feat: skip PRs with CHANGES_REQUESTED review decision

Adds an early-exit check alongside the existing CI guards so the cascade
skips any PR where a human reviewer has already requested changes.
Reviewing those PRs before the author responds wastes tokens and creates
confusing parallel feedback threads.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix: honor FORCE_REVIEW and skip only stale-free CHANGES_REQUESTED reviews

Two issues addressed from Codex P2 feedback:

1. FORCE_REVIEW bypass: mention-triggered and force_review=true runs now
   bypass the CHANGES_REQUESTED skip, so authors can explicitly request a
   re-review after addressing feedback.

2. Stale review guard: only skip when a CHANGES_REQUESTED review targets
   the current head SHA. On repos without automatic stale-review dismissal,
   reviewDecision stays CHANGES_REQUESTED after new commits — the cascade
   now re-engages when the author has pushed new work.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): resolve context loss, missing checkout, and push logic

- dev-lead-intent.sh: include actor and body in INTENT_CONTEXT for comments
- dev-lead.yml: parse and pass INTENT_ACTOR and INTENT_COMMENT_BODY to agent
- dev-lead-fix-reviews.sh: perform PR checkout, git push, and post summary comments

* fix(dev-lead): exit with 0 on no-changes in fix-reviews and human-pr

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* test: bypass CI check

* fix(pr-review): remove CI bypass hack

* fix(dev-lead): use heredoc for multiline environment variables (#224)

* fix(dev-lead): use heredoc for multiline environment variables

* fix(dev-lead): ensure all engines are installed and context is consistent

* fix(dev-lead): resolve stale model name in engine fallbacks

* fix(dev-lead): improve fallback reliability and script quality

- engine.sh: resolve stale model names in fallback loop; use -latest Gemini aliases
- dev-lead-fix-reviews.sh: remove unused code and fix PR_URL export

* fix(dev-lead): resolve Gemini model names and shell lint warnings

* security(dev-lead): use random heredoc delimiter to prevent inject…
don-petry added a commit that referenced this pull request Aug 7, 2026
…-stale-bot-reviews.yml (#1292)

* fix: silence auth check in fix-stuck-prs when using GitHub App token

GitHub App tokens don't have user scope, so 'gh api user' returns 403.
This is fine - the script still works for PR operations. Suppress the error
so the script completes successfully with app-token fallback label.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: use explicit author instead of @me in stuck PR search

GitHub App tokens don't have user identity, so @me search doesn't work.
Use explicit 'don-petry' author instead to find PRs to fix.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: avoid subshell in while loop to preserve variable state

Using pipe in while loop created subshell where PROBLEM_PRS and FIXED_PRS
counters were incremented but changes didn't persist to parent shell.
Fixed by using process substitution for input instead of pipe.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* docs: add comprehensive status report for GitHub App setup

- Overview of completed work
- GitHub App authentication details
- 24 stuck PRs successfully fixed with approval reviews
- Architectural decisions and rationale
- Configuration guide and usage examples
- Known limitations and troubleshooting

System is now fully operational with GitHub App token authentication,
comprehensive documentation, and all infrastructure in place.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* docs: document stuck PR cleanup status and script fixes

Added documentation for:
- GitHub App token compatibility fixes in fix-stuck-prs.sh
  * Changed @me to explicit author (app tokens have no identity)
  * Fixed subshell variable scope (process substitution vs pipe)
  * Silenced expected 403 auth check error
- Current status: 24 PRs have approvals but remain OPEN
  * Auto-merge failed due to missing GitHub App permission
  * Approvals satisfy branch protection requirement
  * Next: expand permissions or manually merge

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: update list-prs.sh to search all repos by owner instead of @me

GitHub App tokens don't have user identity, so @me/@review-requested searches
fail. Changed to enumerate all repos in don-petry and petry-projects, then
search for open PRs within each repo. This covers the full scope:
- All open PRs in personal don-petry repos
- All open PRs in petry-projects org repos

Resolves 6 consecutive workflow failures due to zero PRs being enumerated.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* docs: add investigation report for PR review agent workflow failures

* fix: move env section before steps in workflow to fix YAML structure

GitHub Actions requires env section to come before steps in job definition.
Moving env definition up and setting GH_TOKEN only in steps that need it.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* script: add backfill-approvals to retroactively apply real approvals

Converts PRs with agent approval comments (but no real GitHub approvals)
to have actual APPROVED reviews. Needed for PRs reviewed before the
gh pr review --approve fix was applied.

Usage:
  scripts/backfill-approvals.sh          # dry-run (preview changes)
  scripts/backfill-approvals.sh false    # apply real approvals

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* feat: add backfill-approvals workflow to run as GitHub App bot

Runs backfill-approvals.sh via GitHub Actions so the approvals are posted
by the bot identity, not the PR author — GitHub rejects self-approvals.

Also fixes subshell counter bug in the script (piped while loop lost variable
state; switched to process substitution).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: convert inner while loop to process substitution to preserve counters

Both the outer repo loop and inner PR loop were piped subshells, causing
all counter increments (approved/skipped/failed) to be lost. The summary
always showed 0. Fixed by using < <(...) process substitution for both loops.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* car-hunt: add VIN-deep-dive flow, printable checklists, location-tier ranking

- Step 5.5 pre-test-drive flow: NHTSA VIN decode + per-VIN unrepaired-recall
  automation via Chrome MCP (Honda LWC owner portal documented as not
  automatable; NHTSA web form works).
- §F printable test-drive checklist generator (reportlab/Platypus PDF) with
  cold-start, model-specific red flags, negotiation table, walk-away rules.
- §G fraud / paperwork verification checklist (vehicle-agnostic, reusable):
  walk-away triggers, VIN three-location match, curbstoning, odometer fraud,
  bill of sale, title transfer, payment protection, stolen-vehicle, title-
  jumping, after-purchase steps.
- Step 5.25 head-to-head comparison template with information-asymmetry rule.
- Step 3 Location Quality Tier scoring (Birmingham AL metro table A/B/C/D)
  with adj-CPM modifier; sheet schema gains Adj.CPM + Tier columns.
- Hard-disqualification rules added: non-running engine keywords, mileage
  inconsistency, mandatory description scrape.
- Drive MCP overwrite limitation documented; sheet ID now read from memory
  rather than hardcoded.
- FB Marketplace operational facts: Birmingham AL city ID 107739635926718,
  /search? vs /vehicles? query handling, React-controlled inline composer,
  send-button selector, seller-name extraction regex.
- scheduled-tasks/used-car-search-{morning,afternoon}: tier A/B priority
  flag, sheet ID read from memory.
- Reference PDF generator scripts checked in for reuse.

* fix: re-approve PRs where approval predates last commit (stale approval)

Instead of skipping PRs that already have any APPROVED review, skip only
those where reviewDecision is not REVIEW_REQUIRED. This handles PRs where
a bot approval was posted before a new commit was pushed — GitHub's ruleset
engine treats those approvals as stale even with dismiss_stale_reviews=false.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: robustly extract JSON verdict from mixed claude --print output (#17)

Claude's --print mode can prefix the JSON verdict with conversational
preamble text, causing jq to fail with parse errors. Add
extract_verdict_json to engine.sh and wire all three cascade action
call sites in review-one-pr.sh to use it.

Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: extract_verdict_json — check dest file first (agent Bash-write style)

The cascade-action agent writes its verdict JSON to \$OUTPUT_FILE via a
Bash tool call, then prints a text summary to stdout. The previous fix
only scanned stdout (the .raw file), which contained no JSON. The agent-
written file at \$dest (= \$OUTPUT_FILE) was already correct — just not
checked. Now check \$dest first before falling back to stdout scan.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Enforce MAX_REVIEW_CYCLES cap before running the cascade (#18)

* fix: enforce MAX_REVIEW_CYCLES cap with human-escalation marker

Previously MAX_REVIEW_CYCLES only gated AI delegation in
post-pr-review.sh — the cascade itself ran on every cron tick
regardless of how many cycles had accumulated. Real-world result:
a PR could rack up 9+ review cycles (we observed exactly that on
ContentTwin#100) before any cap took effect.

Add a pre-cascade check in review-one-pr.sh: when the count of
existing `<!-- pr-review-agent v1 sha=... -->` markers is at or
above MAX_REVIEW_CYCLES (default 3), post one escalation comment
marked `<!-- pr-review-agent escalation -->`, label
needs-human-review, request don-petry, and exit 100 (skip
sentinel — doesn't burn the MAX_PRS budget). The escalation
marker doubles as the no-spam guard: subsequent runs detect it
and exit 100 immediately.

Also reuse a single `gh pr view` for both the cycle count and the
escalation-marker check.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* review feedback: surface escalation-comment failures, drop dead unset

- Drop `2>/dev/null || true` on `gh pr comment` so a failed escalation
  post is visible in the workflow log instead of silently leaving the
  PR with no marker (which would re-trigger the cap path next tick).
- Reword the cycle-count comment from "AI delegation loops" to "review
  loops" — the cap also catches cascade-only loops where every cycle
  approves and a new commit lands before merge.
- Remove the redundant `unset PR_BODIES` — never exported, no leakage.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>

* Reliability hardening: session circuit breaker, timeouts, retry, dedup (#20)

* feat: session circuit breaker, per-tier timeouts, retry, and triage hard-fail

Reliability hardening for the PR review agent.

1. Session circuit breaker (.github/workflows/pr-review.yml): on any
   non-zero, non-100 exit from review-one-pr.sh (general failure or rate
   limit on the fallback engine), break the per-PR loop, log a clear
   error annotation naming the failing PR and reason, and exit the step
   with code 1 so the run shows red. Prevents one systemic problem from
   silently burning the entire candidate pool.

2. Per-tier timeouts (scripts/engine.sh): triage/deep/audit/action/duck
   each get their own bounded timeout (180/600/600/300/300s defaults,
   env-overridable). Previously only the duck had a timeout — a hung
   tier could burn the whole 60min job budget.

3. Retry-with-backoff on transient errors (scripts/engine.sh): triage
   retries once on 124/137/143 (timeout / signal kill) since its caller
   captures stdout via $(...) so retries are safe. Deliberately NOT
   applied to run_agentic/run_duck where stdout is redirected to a file
   — a retry there would corrupt the partial first-attempt output.

4. Triage non-JSON now hard-fails (scripts/review-one-pr.sh): replaces
   the silent fallback that synthesized a fake "escalate=MEDIUM" verdict
   and proceeded to deep review. With the new circuit breaker, loud
   failure is the right call — masking a broken triage was burning
   tokens on every PR while the workflow looked healthy.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix: stop stacking duplicate agent reviews on the same PR

Two bugs were causing the agent to leave multiple comments on the same PR.
Together they produced 10 stacked APPROVED reviews on petry-projects/ContentTwin#100.

Bug A — idempotency check is order-dependent (review-one-pr.sh):
The previous marker-discovery code did:
  ((.reviews // []) + (.comments // [])) | .[].body | grep marker | tail -1
This relies on the array concatenation order, not chronological order. When
old agent comments existed alongside newer agent reviews, tail -1 picked
the comment-array marker (older) over the review-array marker (newer),
causing the script to think the head SHA hadn't been reviewed and re-run.
Replaced with a single jq pipeline that tags each item with submittedAt /
createdAt, sorts by timestamp, and takes the actual most-recent marker.

Bug B — no cleanup of prior agent items (post-pr-review.sh):
After successfully posting a new review/comment, prior agent items were
left in place, accumulating forever. Added mark_prior_agent_items_obsolete
which, after a successful post:
  - dismisses prior APPROVED/COMMENTED/CHANGES_REQUESTED agent reviews via
    the GitHub dismissal API (UI shows them struck-through as Dismissed)
  - edits prior agent comments to wrap their body in a collapsed <details>
    block with a "Superseded by re-review at <SHA>" summary, plus a
    `<!-- pr-review-agent superseded -->` sentinel for idempotency

All cleanup API calls are best-effort — failures don't break the workflow.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(cleanup): warn on API failures, preserve global newest, file-stage JSON

Three fixes to mark_prior_agent_items_obsolete from the review of PR #20:

1. ::warning:: annotations on every cleanup API failure (review/comment
   list-fetch, individual review dismissal, individual comment fetch+edit).
   Previously these were silenced with `|| true`, so a permissions change
   on the dismissal endpoint would let duplicates stack indefinitely with
   no signal in the Actions UI. Cleanup is still non-fatal — the new post
   has already landed — but failures are now visible.

2. Preserve the globally-latest agent item across BOTH categories, not the
   newest of each category separately. The earlier code split reviews and
   comments and applied `[:-1]` to each, which left a stale fix-request
   comment in place when the new post was a review (or vice versa). The
   one-off cleanup of ContentTwin#100 hit exactly this case: 12 stacked
   reviews collapsed to 1, but a stale comment from 2026-04-25 (SHA
   cd9132d6) was preserved as "newest comment" even though the latest
   review at SHA 3af8c8ee was newer overall. Now: compute the max
   timestamp across both feeds, exclude items at that timestamp.

3. Stage API responses to disk (`mktemp` + `jq <file>`) instead of routing
   through `--argjson "$var"`. The old approach broke on rare unescaped
   control chars in user-authored comment bodies (jq refused to parse the
   resulting shell-vared JSON). File-based input sidesteps the shell
   pipeline entirely.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>

* Remove car-hunt skill and scheduled tasks (moved to don-petry/don-petry) (#21)

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: apply Copilot fallback hardening and reuse GH_PAT for Copilot auth

- Use secrets.GH_PAT (existing personal account token with Copilot
  subscription) instead of a separate COPILOT_GITHUB_TOKEN secret
- Pin actions/create-github-app-token to SHA (v3.1.1) for supply chain safety
- Add post-install verification for gh-copilot extension with warning on failure
- Guard Copilot fallback path: skip PR gracefully if extension is not installed

Addresses issue #24 workflow fixes (1-3) that could not land via PR #25
due to GitHub App lacking workflows permission.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: switch Copilot engine to gh built-in and fix app-id deprecation

- Remove gh extension install (github/gh-copilot conflicts with built-in alias)
- Replace with gh copilot --version check using COPILOT_GITHUB_TOKEN (GH_PAT)
- Update all engine.sh copilot invocations from bare `copilot` binary to
  `gh copilot suggest --target shell` with GH_TOKEN overridden to GH_PAT
  so the user token (with Copilot subscription) is used, not the App token
- Fix actions/create-github-app-token: app-id → client-id (same secret value,
  just the renamed input in v3.x)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: harden Copilot fallback path and remove stale worktrees (#25)

- gh-copilot install: add --force flag and post-install verification
  warning so silent failures due to built-in alias conflict are visible
  in workflow logs instead of being silently swallowed by `|| true`
- fallback pre-flight check: verify `gh extension list | grep copilot`
  before switching to Copilot engine; if unavailable, skip the PR and
  continue the batch rather than session-aborting and dropping all
  remaining candidates (fixes the 27-PR drop on run #503)
- pin actions/create-github-app-token to SHA for v3.1.1 (Node.js 24
  compatible) to prevent hard break on 2026-06-02 when GitHub forces
  Node.js 24 as default runtime
- remove 6 stale Claude Code worktrees from git tracking; add
  .gitignore entry to prevent future worktrees from being committed
  (eliminates exit-128 warning on every actions/checkout post-job sweep)

Closes #24

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: petry-projects-pr-review-agent[bot] <petry-projects-pr-review-agent[bot]@users.noreply.github.com>

* ci: add pre-flight dedup check to prevent duplicate claude-issue PRs (#26)

Before invoking Claude on a labeled issue, a new shell step queries
for any open PR with a branch matching claude/issue-NNN-* (or a body
containing "Closes #NNN"). If one is found it posts a comment on the
issue pointing to the existing PR and skips the Claude step entirely.
A secondary prompt instruction tells Claude to check first and push to
the existing branch rather than opening a new PR.

Motivated by issue petry-projects/google-app-scripts#171, where the
claude label was re-applied three times and each run created a fresh
PR unaware of the prior attempts.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor: migrate to machine user PAT auth (closes #27) (#28)

* refactor: migrate from GitHub App to machine user PAT auth

Closes #27

GitHub Apps cannot be listed in CODEOWNERS, blocking PRs in repos with
require_code_owner_review: true. Switch all workflows to use a machine
user account's fine-grained PAT (DON_PETRY_BOT_GH_PAT secret), which can
join an org team listed in CODEOWNERS.

Workflows: removed actions/create-github-app-token steps in pr-review,
fix-stuck-prs, backfill-approvals, and daily-pr-review-health. All now
use secrets.DON_PETRY_BOT_GH_PAT directly.

Docs: renamed GITHUB_APP_SETUP.md to MACHINE_USER_SETUP.md with full
rewrite covering account creation, CODEOWNERS config, PAT generation,
and rotation. Updated auth sections in IMPLEMENTATION.md, SETUP.md,
STATUS.md, DOCUMENTATION.md, README.md.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* refactor: switch to org-scoped PAT secret DON_PETRY_BOT_PETRY_PROJECT_PAT

Replace DON_PETRY_BOT_GH_PAT with DON_PETRY_BOT_PETRY_PROJECT_PAT — the
new fine-grained PAT scoped to the petry-projects org (resource owner =
petry-projects). The previous PAT was scoped to the donpetry-bot personal
namespace and had no repository access.

The old DON_PETRY_BOT_GH_PAT secret is retained for any future use against
don-petry's personal repos.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* chore: ignore .claude/scheduled_tasks.lock state file

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>

* refactor: merge approval repair scripts into single automation

- Consolidate backfill-approvals.sh and fix-stuck-prs.sh into repair-pr-approvals.sh
- Iterate all repos in both orgs instead of assuming author
- Verify no existing APPROVED review before posting
- Copy original agent comment as review body
- Enable auto-merge when posting approval if needed
- Single workflow with 30-min timeout

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: add @mention trigger for on-demand PR reviews (#30)

Adds repository_dispatch support so commenting @petry-review-bot on any
PR fires an immediate review without waiting for the hourly schedule.

- pr-review.yml: new `repository_dispatch` trigger (type: pr-review-mention),
  per-PR concurrency group for mention runs, FORCE_REVIEW and DRY_RUN
  env vars that handle both workflow_dispatch and repository_dispatch paths
- review-one-pr.sh: FORCE_REVIEW=true bypasses idempotency so a mention
  always runs a fresh cascade even if the head SHA hasn't changed
- templates/mention-listener.yml: deploy to petry-projects/.github;
  listens for @petry-review-bot, validates commenter trust, posts ack,
  sends repository_dispatch (requires Contents:write, not Actions:write)

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: harden rebase, review dismissal, and health log diagnostics (#40)

Addresses 4 code-actionable recommendations from health check report #33:

- [CRITICAL] Make rebase/branch-update non-fatal — 403/504 during rebase
  emits ::warning:: instead of aborting the batch session (#34)
- [MEDIUM] Add retry with exponential backoff (3 attempts) for transient
  5xx on update-branch API; break immediately on 4xx (#37)
- [MEDIUM] Guard review dismissal with state re-check before calling
  dismiss API, preventing 422s from race conditions (#38)
- [LOW] Surface missing log warnings in health check script (#39)
- Fix: skip auto-merge when branch is still BEHIND after failed rebase

Closes #34, closes #37, closes #38, closes #39

* refactor: migrate to org-wide .github-private convention (#41)

* refactor: parametrize hardcoded identity values for org migration

Replace all hardcoded references to don-petry, petry-review-bot, and
don-petry/pr-review-agent with environment variables that default to
the current values. This allows the agent to be configured for different
orgs/users via repo variables.

Changes:
- Scripts use $REVIEWER_USER, $TARGET_ORG, $BOT_USER, $AGENT_REPO
- Prompts no longer reference specific GitHub usernames
- Workflows use org-level GH_PAT_WORKFLOWS secret instead of
  repo-level DON_PETRY_BOT_PETRY_PROJECT_PAT
- Health check uses context.repo.owner/repo for issue creation
- Variables are set in workflow env block with defaults

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add Copilot custom agent profiles for org-wide use

Create /agents/ directory with three agent profiles:
- pr-reviewer: Multi-tier cascading PR review
- feature-ideator: Feature idea generation and prioritization
- compliance-auditor: Org standards compliance checking

These are Copilot custom agent profiles that become available
org-wide in the .github-private repo convention.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Squashed 'frameworks/bmad-method/' content from commit e36f219c

git-subtree-dir: frameworks/bmad-method
git-subtree-split: e36f219c81b6010d4aae423ba12f49edb5b6e31a

* Squashed 'frameworks/spec-kit/' content from commit 11f49ebf

git-subtree-dir: frameworks/spec-kit
git-subtree-split: 11f49ebfb2f6af55345cb4bd9a7906acd211e56f

* Squashed 'frameworks/gsd/' content from commit 304c1a13

git-subtree-dir: frameworks/gsd
git-subtree-split: 304c1a1302564c45af252bbba4bcc5350e7dac3a

* docs: update README for .github-private org infrastructure role

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* ci: add dependabot-automerge.yml workflow (#73)

Adds the required dependabot-automerge.yml workflow from the org
standard template (petry-projects/.github/standards/workflows/).
This is a thin caller stub that delegates to the org-level reusable
workflow.

Closes #48

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Don Petry <don-petry@users.noreply.github.com>

* fix(pr-review): unblock queue starvation from self-authored PRs (#96) (#97)

* fix(pr-review): unblock queue starvation from self-authored PRs (#96)

A self-authored PR sorted first in the candidate list and triggered
"Can not approve your own pull request" — which the session-fatal
abort treated like an engine error, skipping all 28 remaining PRs
on every run.

- list-prs.sh: filter out PRs authored by REVIEWER_USER at enumeration
- post-pr-review.sh: catch the GraphQL self-approval error and exit 100
  (no-op) instead of 1, so a stray self-PR can no longer abort the batch
- engine.sh: gh copilot renamed --target to --agent; fix the rubber-duck
  invocations so tier-2 cross-engine review works again
- review-one-pr.sh: stop appending a duplicate "0" to REVIEW_CYCLE when
  grep -c finds no markers under set -o pipefail (was breaking the
  cycle-cap integer comparison)

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

* fix(pr-review): authenticate as bot, harden enumeration, address review

- Workflow now runs as AGENT_USER (default don-petry-bot), distinct from
  REVIEWER_USER (the human, don-petry). The self-approval rejection that
  caused #96 came from the agent and the human sharing one identity.
- list-prs.sh filters self-authored PRs against AGENT_USER, and validates
  AGENT_USER against the GitHub username charset before interpolating into
  the jq filter (Copilot review feedback).
- review-one-pr.sh uses printf '%s\n' instead of echo for PR_BODIES, since
  PR body content is user-authored and could begin with -n/-e or contain
  backslash escapes (Copilot review feedback).
- AGENT.md guideline updated to reflect that self-authored PRs are
  intentionally excluded (CodeRabbit review feedback).

Operator note: rotate the GH_PAT_WORKFLOWS secret to a token owned by
don-petry-bot (with read:org added) for the bot-account behavior to take
effect at runtime.

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

* refactor(pr-review): collapse to single BOT_USER, route escalations via CODEOWNERS

Drop AGENT_USER and REVIEWER_USER. The workflow now has one identity:
BOT_USER (default don-petry-bot), which both owns the repos to scan and
gets filtered out as the self-approval blocker. Human escalation no
longer hard-codes a single reviewer; instead, scripts/request-codeowners-
review.sh parses CODEOWNERS in the PR's repo and requests review from
every @user / @org/team mention.

- list-prs.sh: BOT_USER for both gh-repo-list and self-author filter
- review-one-pr.sh, post-pr-review.sh: replace --user "$REVIEWER_USER"
  with the CODEOWNERS helper
- repair-pr-approvals.sh: same gh-repo-list switch
- pr-review.yml: drop AGENT_USER/REVIEWER_USER, set BOT_USER default
  to don-petry-bot
- AGENT.md: updated guideline to describe single-identity model

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

* refactor(pr-review): extract batch loop, hoist env, drop redundant step

Workflow YAML drops from 231 to 111 lines by moving the per-PR review
loop into scripts/review-batch.sh. The script can be syntax-checked and
exercised locally; the workflow now just wires env vars and dispatches.

- Hoist GH_TOKEN, MAX_PRS, CANDIDATE_LIMIT to job-level env (were
  repeated on individual steps).
- Inline `gh auth status` into the install step; remove the standalone
  Verify auth step (its only output was a one-line auth dump).
- Drop the `if: steps.list.outputs.count != '0'` guard and the step's
  `id`/output: review-batch.sh handles empty input as a no-op.
- Collapse the duplicated summary-string branch in the review loop into
  a single template with a conditional fragment.

No behavior change.

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

* perf(pr-review): cache claude-code CLI install across runs

`npm install -g @anthropic-ai/claude-code` ran on every workflow start
(~30s). Switch to a per-user npm prefix (~/.npm-global) and cache that
directory via actions/cache, keyed on CLAUDE_CODE_VERSION + runner OS.

A `command -v claude` guard makes the install a no-op on cache hit, so
the only cost on subsequent runs is the cache restore.

CLAUDE_CODE_VERSION defaults to 'latest' (cache persists until manually
flushed); set the repo variable to pin a specific version for fully
reproducible caching.

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

---------

Co-authored-by: Claude <noreply@anthropic.com>

* chore: rename GH_PAT_WORKFLOWS secret to DON_PETRY_BOT_GH_PAT (#99)

The previous name was generic and didn't tell you which account the PAT
belonged to. The new name makes the binding explicit: this secret is the
PAT owned by don-petry-bot, used as BOT_USER throughout the PR-review
workflows.

Operator follow-up before this can be merged:
- Add a new repo secret DON_PETRY_BOT_GH_PAT containing the bot's PAT
  (with repo, workflow, and read:org scopes).
- After merge, the old GH_PAT_WORKFLOWS secret can be deleted.

Affected workflows:
- pr-review.yml (1 use)
- claude.yml (4 uses, with || github.token fallback)
- daily-pr-review-health.yml (1 use)
- repair-pr-approvals.yml (1 use)

scripts/pr_review_health.sh's error message is updated to point at the
new name as well.

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

Co-authored-by: Claude <noreply@anthropic.com>

* chore: remove frameworks directory (#101)

Co-authored-by: Gemini CLI <gemini-cli@example.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore all erroneously deleted files (items 4-34) (#104)

* revert: restore .github/workflows/daily-pr-review-health.yml

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore .github/workflows/repair-pr-approvals.yml

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore agents/pr-reviewer.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/cascade-action.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/deep-review.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/rubber-duck.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/security-audit.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/shared.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/single-review.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/synthesize-duck.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/synthesize.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/triage.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/engine.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/list-prs.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/post-pr-review.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/pr_review_health.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/repair-pr-approvals.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/request-codeowners-review.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/review-batch.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/review-one-pr.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore templates/mention-listener.yml

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore AGENT.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore BOT_SETUP.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore DOCUMENTATION.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore IMPLEMENTATION.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore MACHINE_USER_SETUP.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore PR_REVIEW_AGENT_FAILURE_REPORT.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore PR_REVIEW_FAILURE_INVESTIGATION.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore SETUP.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore STATUS.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore README.md to pre-change state

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore .github/workflows/daily-pr-review-health.yml

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat: add Gemini Pro support and optimize PR review fallback chain (#102)

* Merge main and resolve conflicts

* Enhance pr-review workflow: add Gemini support and refactor fallback logic

* Address Copilot review comments: tighten regex, fix fallback summary, and align docs

* docs: move agent documentation to docs/ folder and rename files

* docs: align secret names and fix casing in index

* docs: organize pr-review-agent documentation under dedicated folder

* Address final PR comments: tighten regex, add preflight checks, fix bot names, and cleanup docs

---------

Co-authored-by: Gemini CLI <gemini-cli@example.com>

* fix(pr-review): gate rate-limit detection on non-zero exit code

Broad patterns like `plan.*limit` and `claude.*usage` could match content
in a successful triage summary, triggering a false-positive engine fallback.
Guard the check with TRIAGE_RC != 0 so it only fires when the provider
command actually failed.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(claude): sync inlined workflow with org standard (#109)

* fix(claude): sync inlined workflow with org standard

Brings the inlined claude.yml up to parity with
petry-projects/.github/.github/workflows/claude-code-reusable.yml.

Changes:
1. Bot allow list for pull_request_review_comment
   Add coderabbitai[bot], Copilot, copilot-pull-request-reviewer[bot],
   and gemini-code-assist[bot] alongside the existing OWNER/MEMBER/
   COLLABORATOR check. These bots have author_association 'NONE' so
   their review comments were always skipped.

2. check_run trigger + claude-ci-fix job
   Port the CI failure auto-fix feature from the reusable. When a check
   fails on a PR, Claude diagnoses and fixes it automatically.
   Adapted to use DON_PETRY_BOT_GH_PAT (this repo's secret name).

3. claude-code-action SHA bump: v1.0.89 → v1.0.119
   Update both the claude and claude-issue jobs.

4. paths-ignore on pull_request trigger
   Prevents the workflow from firing on PRs that only change
   claude.yml itself, avoiding the Anthropic OIDC validation
   failure (workflow file must match default branch at token exchange).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(claude): guard bot allow list against fork PRs

Same fix as petry-projects/.github PR #238 — add same-repo guard
for bot-triggered pull_request_review_comment runs.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(claude): address ci-fix self-loop and fork PR security

- Replace Claude Code name prefix check with explicit job name list to
  correctly prevent self-loops (check_run names for inlined workflows
  are bare job names, not workflow-prefixed)
- Add fork PR trust gate in Resolve PR number step: verify head repo
  matches target repo before running Claude with privileged credentials

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* ci: replace inlined claude.yml with standard thin-caller stub

The inlined version embedded all three jobs (claude, claude-ci-fix,
claude-issue) directly and threaded DON_PETRY_BOT_GH_PAT through
checkout tokens and github_token, causing the bot's PAT to author
PRs and comments instead of github-actions[bot].

Replace with the standard thin caller that delegates to the org-level
reusable workflow (claude-code-reusable.yml@v1) via secrets: inherit,
which is the correct pattern per:
petry-projects/.github/standards/workflows/claude.yml

* fix: replace gh copilot suggest with GitHub Models REST API (#151)

* fix: replace gh copilot suggest with GitHub Models REST API (#147)

The `gh copilot suggest -p "$(cat <file>)"` invocation failed with
"Invalid command format" because:
1. The `-p` flag is not valid syntax in modern `gh copilot` built-in versions.
2. `gh copilot suggest` is a shell-command suggestion tool; it does not
   accept large PR prompts or return structured JSON responses.
3. Passing a multi-thousand-line prompt via `$(cat ...)` can hit ARG_MAX.

The non-zero exit was then misclassified by the rate-limit detector, which
aborted the entire session and skipped all remaining PRs in the queue.

Fix: replace all three copilot invocations (run_triage, run_agentic,
run_duck) with a new `copilot_chat` helper that calls the GitHub Models
REST API directly via curl. The API is OpenAI-compatible, versioned via
`X-GitHub-Api-Version`, stable against gh CLI version changes, and accepts
arbitrary prompt sizes using python3 for safe JSON encoding.

Additional changes:
- Add `COPILOT_API_MODEL` var (default `openai/o4-mini`) overrideable via env.
- run_agentic/run_duck copilot cases now also write output to $OUTPUT_FILE
  so callers that check that path directly (deep review, audit) find the JSON.
- Rate-limit responses (HTTP 429) are echoed to stdout so the existing
  `is_rate_limited()` detector fires correctly for engine fallback.
- Add pre-flight smoke test in review-batch.sh: tests GitHub Models API
  connectivity with a one-liner prompt before processing any PRs, so auth
  or model errors surface immediately as a clear setup failure.
- Add tests/test_copilot_chat.sh: unit tests for the JSON payload builder
  with edge-case prompts (quotes, newlines, # headings, large diffs, Unicode).

Closes #147

Co-authored-by: Don Petry <don-petry@users.noreply.github.com>

* fix: scope COPILOT_API_MODEL export to copilot engine only

Move the `export COPILOT_API_MODEL` from the common exports block into the
`copilot)` case where the variable is set, so it is not exported as an
empty/unset variable when the engine is `claude` or `gemini`. The
`copilot_chat` fallback `${COPILOT_API_MODEL:-openai/o4-mini}` still works
correctly when `DUCK_ENGINE=copilot` under a non-copilot primary engine.

Co-authored-by: Don Petry <don-petry@users.noreply.github.com>

* fix: address review comments — temp file for payload, fail-fast source, streaming output

- copilot_chat: write JSON body to mktemp file, pass to curl as @file to
  avoid ARG_MAX for large PR diffs (was --data-binary "$body")
- copilot_chat: add :? guard on COPILOT_GITHUB_TOKEN for a clear error
  instead of generic "unbound variable" under set -u
- run_agentic / run_duck copilot paths: stream directly to stdout (and
  tee to OUTPUT_FILE when set) rather than buffering the full response
  into a shell variable, which forced large outputs into memory and
  stripped trailing newlines
- review-batch.sh pre-flight: fail fast if source engine.sh fails
  (was silently ignored with || true)
- review-batch.sh pre-flight: build smoke-test JSON payload via python3
  + temp file instead of shell string interpolation to avoid JSON
  injection if COPILOT_API_MODEL contains special characters
- Clarify that openai/o4-mini is the correct April-2025 o4-generation
  model name, not a typo for o1-mini or gpt-4o-mini

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Don Petry <don-petry@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* feat: prioritise .github/.github-private PRs, oldest-first within tier (#155)

* feat: prioritise .github/.github-private PRs, oldest-first within tier

- list-prs.sh: add createdAt to JSON fetch; emit priority|createdAt|url
  lines (priority 0 for .github/.github-private, 1 for everything else)
- Replace final sort -u with a two-pass sort: deduplicate by URL, then
  sort by priority asc then createdAt asc; strip sort keys with cut
- tests/test_list_prs_sort.sh: 16 tests covering priority classification,
  oldest-first ordering, mixed scenarios and deduplication
- .github/workflows/test.yml: run both unit-test files on every PR / push

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* test: add dedup edge case for same URL with conflicting priorities

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: remove temperature from copilot_chat payload (o4-mini incompatible)

The GitHub Models API rejects temperature=0 for reasoning models like
o4-mini with HTTP 400:
  'Unsupported value: temperature does not support 0 with this model.
   Only the default (1) value is supported.'

This caused the rubber duck (DUCK_ENGINE=copilot / DUCK_MODEL=o4-mini)
to fail with HTTP 400 on every claude-engine run, silently degrading
every review to deep-only with no cross-engine sanity check.

Fix: remove temperature from the copilot_chat JSON payload entirely —
the API defaults to 1, which is the only supported value for o4-mini.

Also:
- Update tests/test_copilot_chat.sh: sync build_payload to match and
  flip Test 9 to assert temperature is ABSENT (not 0).
- Add .github/workflows/test.yml: run unit tests on every PR and push
  to main so regressions are caught before merge.

Reproducer: job/75690347409 — duck failure logged as:
  copilot_chat: HTTP 400 from GitHub Models API
  {"error":{"message":"Unsupported value: 'temperature' does not
   support 0 with this model..."}}

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: address PR review comments

- list-prs.sh: tighten priority regex to /[.]github(-private)?/pull/
  so path-boundary anchor prevents false positives (e.g. foo.github)
- list-prs.sh: replace printf pipeline with here-string to avoid ARG_MAX
  limits on large PR sets
- test.yml: add permissions: contents: read (least-privilege, CodeQL fix)
- test.yml: add timeout-minutes: 5 to prevent hung runs
- test_list_prs_sort.sh: sync sort_entries helper (here-string + || true)
  and classify regex to match updated list-prs.sh exactly

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: don-petry <don@petry.dev>

* fix: restore pr-review.yml content (accidentally emptied in revert)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: restore pr-review.yml (file was accidentally emptied)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore(deps): bump actions/cache from 4 to 5 (#165)

Bumps [actions/cache](https://github.com/actions/cache) from 4 to 5.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): bump petry-projects/.github/.github/workflows/claude-code-reusable.yml (#164)

Bumps [petry-projects/.github/.github/workflows/claude-code-reusable.yml](https://github.com/petry-projects/.github) from 1 to 2.
- [Commits](https://github.com/petry-projects/.github/compare/v1...v2)

---
updated-dependencies:
- dependency-name: petry-projects/.github/.github/workflows/claude-code-reusable.yml
  dependency-version: '2'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* fix(list-prs): search DELEGATION_ORGS and drop --checks success pre-filter

- Add iteration over all orgs in DELEGATION_ORGS (e.g. don-petry) so PRs
  in those orgs enter the candidate pool. Previously only BOT_USER and
  TARGET_ORG were searched.
- Remove --checks success from org repo searches. GitHub excludes PRs from
  repos with no CI configured when this flag is used, silently dropping all
  PRs from repos like .github, .github-private, google-app-scripts, and
  TalkTerm. review-one-pr.sh already enforces CI gating per-PR and treats
  empty statusCheckRollup as passing, so the pre-filter is redundant and
  harmful.
- Add --limit 200 to gh repo list calls to handle org growth beyond gh's
  default 30-repo cap.

* feat: add code-quality ruleset (compliance fix #60) (#86)

Creates the required `code-quality` repository ruleset enforcing required
status checks on the default branch, as mandated by the org standard:
standards/github-settings.md#code-quality--required-checks-ruleset-all-repositories

Required checks:
- SonarCloud (code quality analysis)
- CodeQL (SAST)
- agent-shield / AgentShield (agent security scan)
- dependency-audit / Detect ecosystems (dependency vulnerability scan)

Bypass actors:
- OrganizationAdmin (always) — emergency override
- dependabot-automerge-petry Integration (always) — Dependabot auto-merge

The ruleset was applied directly via GitHub API. This file documents the
configuration as code for auditability and future reapplication.

Closes #60

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Don Petry <don-petry@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* feat(prompts/dev-lead): add human prompt template

* feat(prompts/dev-lead): add human-pr prompt template

* feat(dev-lead): implement dev-lead agent Phases 0-6

* fix: per-PR isolation and single-review retry (closes #132)

- review-batch.sh: non-rate-limit per-PR failures (exit code 1) no
  longer abort the session. SESSION ABORTED EARLY is now reserved for
  the rate-limit-on-fallback-engine case (exit code 2) only. All other
  failures are counted and logged; remaining candidates continue.

- review-one-pr.sh: single-review step retries up to
  SINGLE_REVIEW_MAX_RETRIES (default 2) times with a
  SINGLE_REVIEW_RETRY_DELAY_SEC (default 15s) gap before giving up.
  On exhaustion, the PR is flagged needs-human-review and the script
  exits with code 1, which the updated batch treats as a non-fatal
  per-PR failure. Raw model output and stderr are logged on each failed
  attempt for post-mortem visibility.

Root cause of run #25707852006: claude-opus-4-7 returned a verbose
non-JSON response for PR #129; the old code treated that as fatal and
skipped 35 remaining candidates.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix: rate-limit detection and per-attempt stderr logs in single-review retry

Address inline review comments on PR #133:

- Rate-limit check: after each run_agentic call, inspect both stdout
  (VERDICT_JSON.raw) and stderr (SINGLE_LOG) with is_rate_limited before
  retrying. A rate-limit match exits immediately with code 2 so
  review-batch.sh can trigger engine fallback — consistent with triage and
  deep-review tiers. Previously a rate-limited single-review would burn all
  retries and exit 1 (per-PR failure), silently leaving the batch on the
  same rate-limited engine for all remaining PRs.

- Per-attempt log files: stderr is now written to
  single-review-attempt-N.log rather than a single overwritten file, so
  no earlier-attempt errors are lost. Each attempt logs its own stderr
  inline on failure; the fallback path cats all attempt logs for
  post-mortem visibility.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* feat(dev-lead): Phase 0 test infrastructure and Phase 1 intent stub

Phase 0: full test harness for the dev-lead agent —
  26 event fixtures (all valid JSON with _test_expected_intent),
  stub claude/gemini engines, mock gh binary, CI failure log sample,
  bats helpers (stub-engine, mock-gh, assert-env, prompt-vars),
  7 prompt templates with VARIABLES declarations, preflight script,
  prompt coverage integration test, and test-dev-lead.yml CI workflow.

Phase 1: dev-lead.yml trigger workflow (all 7 event types, dispatch + ci-relay
  jobs) and dev-lead-intent.sh stub (anti-loop guard live; all other events
  emit skip/not-implemented).  14/14 bats unit tests pass.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): add permissions blocks to test workflow jobs (CodeQL)

* fix(ci): auto-fix for lint / eslint [skip ci-relay]

* feat(dev-lead): Phases 2-6 — CI fix, review fix, issue, engine fallback

Phase 2: run_writer/run_writer_with_fallback in engine.sh, full intent
routing in dev-lead-intent.sh, dev-lead-fix-ci.sh handler, workflow wiring.

Phase 3: dev-lead-fix-reviews.sh handles fix-reviews, fix-bot-comment,
human, human-pr, rebase intents. Full review routing in intent classifier.

Phase 5: dev-lead-fix-issue.sh handles the issue intent with dedup guard
and branch/PR creation.

Phase 6: run_writer_with_fallback with claude→gemini→copilot fallback chain.

Tests: 77 unit tests across 8 new .bats files, all passing. Updated Phase 1
stub tests to reflect actual Phase 2+ routing behavior.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): address review comments — dispatch JSON, dead code, portable stub gh

* fix(dev-lead): pre-flight after intent, fork URL check, review-batch exit code, retry comment

* fix(dev-lead): install bats-core from GitHub to avoid root requirement

* fix(security): move event values to env vars to prevent script injection (SonarCloud)

* fix(security): move CLAUDE_CODE_VERSION to env block (SonarCloud script injection)

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* feat(dev-lead): Phase 1.5 — reusable workflow for cross-repo adoption + Phase 7 shadow period

* feat(dev-lead): add reusable workflow (Phase 1.5) and begin shadow period (Phase 7)

- Create .github/workflows/dev-lead-reusable.yml: workflow_call entry
  point for other repos. Checks out .github-private scripts/prompts into
  .dev-lead/, then runs the same intent-classify + handler pipeline as
  dev-lead.yml with PROMPTS_DIR=.dev-lead/prompts/dev-lead.
- Add PROMPTS_DIR env-var support to dev-lead-fix-ci.sh,
  dev-lead-fix-reviews.sh, and dev-lead-fix-issue.sh so the reusable
  workflow can point scripts at the sparse-checkout path without changing
  CWD. Defaults to prompts/dev-lead (backwards-compatible for dev-lead.yml).
- Annotate dev-lead.yml with Phase 7 shadow-period window (2026-05-15
  through ~2026-05-29): claude.yml and dev-lead.yml run in parallel until
  regressions clear, then claude.yml is removed.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* chore(dev-lead): update shadow-period tracking issue ref to #180

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): document PAT requirement and pin ref:main on private checkout

* fix(dev-lead): P1 review fixes — ci-relay in reusable, .dev-lead gitignore, export PROMPTS_DIR, trim header

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): rate-limit detection, separate status, and retry cron (#192) (#196)

* chore(dev-lead): decommission claude.yml, bump ACTION_TIMEOUT_SEC to 600s

- Delete .github/workflows/claude.yml — replaced by dev-lead.yml (shadow
  period complete, tracking issue #180).
- scripts/engine.sh: raise ACTION_TIMEOUT_SEC default 300→600s to reduce
  timeout failures on large-repo fix-ci runs (PR #80 had 3 timeouts).
- AGENTS.md: remove claude.yml immutability exemption; note dev-lead.yml
  as the active AI automation workflow and its edit-via-reusable pattern.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* refactor: replace Claude analysis with pure gh/jq telemetry in health check

* refactor: drop Node/Claude steps from daily-pr-review-health workflow

* rename: daily-pr-review-health → actions-fleet-monitor

* rename: daily-pr-review-health → actions-fleet-monitor

* fix(agents-md): clarify dev-lead.yml vs dev-lead-reusable.yml scope

dev-lead.yml in .github-private runs inline steps (not a caller stub).
Behavior changes for this repo go to dev-lead.yml directly; changes that
affect all org repos via the cross-repo reusable go to dev-lead-reusable.yml.

Addresses Copilot review on PR #194.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): rate-limit detection, separate status, and retry cron

Closes #192. Implements all phases from the revised plan.

**Phase 0 — fix run_writer stdout capture (prerequisite)**
- engine.sh: capture stdout via `tee` to a tempfile so `is_rate_limited`
  can inspect the output; old code read `/tmp/dev-lead-writer-stderr`
  which was never written (claude --print outputs to stdout, not stderr).
  Fallback engines were never tried in practice.
- engine.sh: add `parse_reset_time` to extract ISO timestamp from
  `resets H:MMpm (UTC)` in engine output and write it to
  `/tmp/dev-lead-rate-limit-reset` for callers to embed in markers.

**Phase 1 — separate status=rate-limited from status=failed**
- fix-ci.sh: detect engine exit 2 → post `status=rate-limited` (not
  `status=failed`); embed parsed reset time in marker body; exit 2.
- fix-ci.sh: fix `check_idempotency` to treat `status=rate-limited`
  as retriable — only block on terminal statuses (applied, failed,
  no-changes). Previously, rate-limited markers blocked all retries.
- fix-ci.sh: `count_recent_failures` already filtered to `status=failed`;
  add explicit comment confirming rate-limited markers are excluded.
- fix-ci.sh: add `has_rate_limited_marker` dedup check so the same SHA
  never accumulates more than one rate-limited comment.
- fix-reviews.sh: on engine exit 2 for all five intent types
  (fix-reviews, fix-bot-comment, human, human-pr, rebase), post a
  `status=rate-limited` marker with embedded reset time and exit 2.
- fix-reviews.sh: for `human` and `human-pr` intents, also post a
  user-visible acknowledgment comment so users know their request was
  received and will be retried.

**Phase 2 — scheduled retry cron for fix-ci**
- dev-lead-retry.sh: new script scans all open PRs across TARGET_ORG
  for `status=rate-limited` markers on current HEAD SHA; skips PRs
  whose reset time is still in the future; staggered dispatches (30s
  between repos) to prevent cascading org-level rate-limit hits.
- dev-lead-retry.yml: new workflow — scheduled every 2 hours (dual
  offsets to work around GitHub scheduler skew), plus workflow_dispatch.

**Phase 3 — retry for fix-reviews intents (same cron)**
- dev-lead-retry.sh already handles all five fix-reviews intent types
  (fix-reviews, fix-bot-comment, human, human-pr, rebase) via
  `dev-lead-reviews-retry` dispatch events.
- dev-lead-intent.sh: add `dev-lead-reviews-retry` dispatch type routing
  to the intent classifier — maps intent_type from payload to the correct
  existing fix-reviews step.
- dev-lead.yml: add `dev-lead-reviews-retry` to repository_dispatch types.
- dev-lead.yml: unify concurrency group for all dispatch types to
  `dev-lead-pr-{pr_number}` to avoid per-type slot fragmentation.

**Tests**
- test_engine_writer.bats: 8 new tests covering rate-limit stdout detection,
  exit-2 mapping, fallback exhaustion, reset time parsing.
- test_fix_ci.bats: 5 new tests covering rate-limited status, exhaustion
  exclusion, idempotency pass-through, and dedup.
- test_fix_reviews.bats: 5 new tests covering all intent types + human ack.
- test_intent_ci.bats: 4 new tests for dev-lead-reviews-retry dispatch routing.
- e2e/scenarios/07-rate-limit-retry.sh: new E2E scenario covering all parts.
- 3 new event fixtures for repository_dispatch_reviews_retry_* payloads.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): address review comments on #196

Addresses all 18 review threads from Copilot, Codex, and Gemini.

**P1 bugs fixed:**
- dev-lead-retry.sh: all log echo calls in scan_pr_for_rate_limits and
  dispatch_* functions now write to stderr; only the final count is on
  stdout. Previously, log lines polluted the command substitution,
  causing arithmetic expansion to fail and aborting the scan on the
  first retry candidate.
- dev-lead-retry.yml: permissions: contents was read-only; creating
  repository_dispatch events requires contents: write.

**Correctness fixes:**
- dev-lead-retry.sh: restrict automated retries to intents whose context
  can be reconstructed at runtime (fix-reviews, human-pr, rebase). human
  and fix-bot-comment require USER_INSTRUCTION/COMMENT_BODY from the
  original event which cannot be re-fetched; they are explicitly excluded.
- dev-lead-fix-reviews.sh: resolve HEAD_SHA from the PR API when not
  provided by the triggering event (issue_comment intents carry no SHA).
  Without this, rate-limited markers had no sha= field and were
  invisible to the retry scanner.
- dev-lead-fix-reviews.sh: write terminal status=applied marker after
  successful fix-reviews, human-pr, rebase runs. Prevents the retry
  cron from re-dispatching the same intent on every subsequent tick when
  the SHA hasn't changed.
- dev-lead-retry.sh: check for reviews terminal marker before dispatching
  a retry (mirrors the existing fix-ci terminal-marker check).
- dev-lead-retry.sh: look up current check-run details (details_url, id)
  at dispatch time via the commits check-runs API, so retried fix-ci runs
  have full failure logs and annotations rather than empty fields.
- dev-lead-fix-ci.sh: embed check= field in the rate-limited marker so
  the retry cron knows which check run to look up.
- dev-lead-fix-reviews.sh: human intent now posts a "please re-mention
  @dev-lead" ack instead of "I'll retry automatically" since it will NOT
  be retried automatically. human-pr keeps the auto-retry ack (correct).

**Pagination (Copilot + Gemini):**
- dev-lead-fix-ci.sh: add --paginate to check_idempotency, count_recent_
  failures, has_rate_limited_marker.
- dev-lead-fix-reviews.sh: add --paginate to has_reviews_rate_limited_marker.
- dev-lead-retry.sh: add --paginate to PR comments and PR list fetches.

**Other improvements:**
- dev-lead-retry.yml: simplify to single cron schedule (15 */2 * * *);
  remove BOT_USER env var (unused in the script).
- dev-lead-retry.sh: raise repo list limit 200 → 1000; add hard-error
  when list_repos_for_org returns empty (surfaces token permission issues
  rather than silently scanning 0 repos).
- dev-lead-intent.sh: use jq -nc (compact, null-input) for context JSON
  construction — safe against values with special characters.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(pr-review): handle Gemini trust-mode failure and properly route engine fallbacks (#217)

* fix(pr-review): handle Gemini trust-mode failure and properly route engine fallbacks

Closes #208

* fix(ci): install gemini CLI and set trust mode for dev-lead agents

---------

Co-authored-by: Gemini CLI <gemini-cli@example.com>

* fix: resolve YAML syntax error in dev-lead-reusable workflow

* fix: address automated feedback from PR 217 (#219)

This addresses missed feedback from CodeRabbit (removing syntax errors in YAML) and gemini-code-assist (improving string concatenation in validate-engines.sh), and adds assertion output checks to tests.

Co-authored-by: Gemini CLI <gemini-cli@example.com>

* chore: remove Phase 7 shadow period comment from dev-lead.yml

* feat: skip PRs with CHANGES_REQUESTED review (+ FORCE_REVIEW and stale-review guards)

* feat: skip PRs with CHANGES_REQUESTED review decision

Adds an early-exit check alongside the existing CI guards so the cascade
skips any PR where a human reviewer has already requested changes.
Reviewing those PRs before the author responds wastes tokens and creates
confusing parallel feedback threads.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix: honor FORCE_REVIEW and skip only stale-free CHANGES_REQUESTED reviews

Two issues addressed from Codex P2 feedback:

1. FORCE_REVIEW bypass: mention-triggered and force_review=true runs now
   bypass the CHANGES_REQUESTED skip, so authors can explicitly request a
   re-review after addressing feedback.

2. Stale review guard: only skip when a CHANGES_REQUESTED review targets
   the current head SHA. On repos without automatic stale-review dismissal,
   reviewDecision stays CHANGES_REQUESTED after new commits — the cascade
   now re-engages when the author has pushed new work.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): resolve context loss, missing checkout, and push logic (#223)

* feat: skip PRs with CHANGES_REQUESTED review decision

Adds an early-exit check alongside the existing CI guards so the cascade
skips any PR where a human reviewer has already requested changes.
Reviewing those PRs before the author responds wastes tokens and creates
confusing parallel feedback threads.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix: honor FORCE_REVIEW and skip only stale-free CHANGES_REQUESTED reviews

Two issues addressed from Codex P2 feedback:

1. FORCE_REVIEW bypass: mention-triggered and force_review=true runs now
   bypass the CHANGES_REQUESTED skip, so authors can explicitly request a
   re-review after addressing feedback.

2. Stale review guard: only skip when a CHANGES_REQUESTED review targets
   the current head SHA. On repos without automatic stale-review dismissal,
   reviewDecision stays CHANGES_REQUESTED after new commits — the cascade
   now re-engages when the author has pushed new work.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): resolve context loss, missing checkout, and push logic

- dev-lead-intent.sh: include actor and body in INTENT_CONTEXT for comments
- dev-lead.yml: parse and pass INTENT_ACTOR and INTENT_COMMENT_BODY to agent
- dev-lead-fix-reviews.sh: perform PR checkout, git push, and post summary comments

* fix(dev-lead): exit with 0 on no-changes in fix-reviews and human-pr

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* test: bypass CI check

* fix(pr-review): remove CI bypass hack

* fix(dev-lead): use heredoc for multiline environment variables (#224)

* fix(dev-lead): use heredoc for multiline environment variables

* fix(dev-lead): ensure all engines are installed and context is consistent

* fix(dev-lead): resolve stale model name in engine fallbacks

* fix(dev-lead): improve fallback reliability and script quality

- engine.sh: resolve stale model names in fallback loop; use -latest Gemini aliases
- dev-lead-fix-reviews.sh: remove unused code and fix PR_URL export

* fix(dev-lead): resolve Gemini model names and shell lint warnings

* security(dev-lead): use random heredoc delimiter to prevent injection

Also updates Gemini models to 3.1 family (pro/flash) for May 2026 compatibility.

* fix(dev-lead): update Gemini models to 2.5 stable family

* fix(dev-lead): use auto model selection for Gemini

* security(dev-lead): harden env var parsing and use high-quota Gemini fallback

* fix(dev-lead): use auto model for Gemini

* fix(dev-lead): detect engine-committed changes and remove commit instructions from prompts

commit_and_push only checked git diff (uncommitted changes), so when the engine
followed prompt instructions to run `git commit` via Bash, the working tree was
clean and commit_and_push concluded no-changes — dropping the engine's commits
when the ephemeral runner exited.

Fix 1 (commit_and_push): also detect unpushed commits via `git log @{u}..HEAD`.
If the engine already committed, skip the add+commit step and go straight to push.

Fix 2 (prompts): remove "Commit the changes with..." steps from human.md,
human-pr.md, fix-bot-comment.md, and fix-reviews.md. Replace with an explicit
"Do not commit or push" constraint so the engine leaves git operations to the
script in all cases.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): address PR review findings — untracked fil…
don-petry added a commit that referenced this pull request Aug 7, 2026
This addresses missed feedback from CodeRabbit (removing syntax errors in YAML) and gemini-code-assist (improving string concatenation in validate-engines.sh), and adds assertion output checks to tests.

Co-authored-by: Gemini CLI <gemini-cli@example.com>
don-petry added a commit that referenced this pull request Aug 7, 2026
… trigger for the Class-2 timer changes, before narrowing them (#1420)

* add: workflow to fix stuck PRs using bot account token

Allows running the cleanup script via workflow_dispatch with access to
GH_PAT (bot account token) from repo secrets.

* fix: pass GH_PAT to checkout action so workflow uses bot account

* debug: simplify account check, add authentication debug output

* docs: add comprehensive petry-review-bot setup instructions

* docs: add GitHub App setup guide (recommended approach)

GitHub App is the secure, recommended way to automate PR reviews:
- Fine-grained permissions
- JWT tokens that auto-expire
- No human account needed
- Better audit trail
- GitHub's recommended approach

Replaces the bot user account approach with a more secure alternative.

* docs: add comprehensive GitHub App setup documentation

- SETUP.md: Quick reference guide with configuration and troubleshooting
- IMPLEMENTATION.md: Technical deep dive on architecture and design decisions
- DOCUMENTATION.md: Index of all documentation files
- Updated README.md with status and quick links
- Updated GITHUB_APP_SETUP.md with implementation notes and actual app ID
- Workflows use GitHub App token generation instead of static PATs

Covers the transition from bot user account to GitHub App authentication for improved security and maintainability.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: silence auth check in fix-stuck-prs when using GitHub App token

GitHub App tokens don't have user scope, so 'gh api user' returns 403.
This is fine - the script still works for PR operations. Suppress the error
so the script completes successfully with app-token fallback label.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: use explicit author instead of @me in stuck PR search

GitHub App tokens don't have user identity, so @me search doesn't work.
Use explicit 'don-petry' author instead to find PRs to fix.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: avoid subshell in while loop to preserve variable state

Using pipe in while loop created subshell where PROBLEM_PRS and FIXED_PRS
counters were incremented but changes didn't persist to parent shell.
Fixed by using process substitution for input instead of pipe.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* docs: add comprehensive status report for GitHub App setup

- Overview of completed work
- GitHub App authentication details
- 24 stuck PRs successfully fixed with approval reviews
- Architectural decisions and rationale
- Configuration guide and usage examples
- Known limitations and troubleshooting

System is now fully operational with GitHub App token authentication,
comprehensive documentation, and all infrastructure in place.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* docs: document stuck PR cleanup status and script fixes

Added documentation for:
- GitHub App token compatibility fixes in fix-stuck-prs.sh
  * Changed @me to explicit author (app tokens have no identity)
  * Fixed subshell variable scope (process substitution vs pipe)
  * Silenced expected 403 auth check error
- Current status: 24 PRs have approvals but remain OPEN
  * Auto-merge failed due to missing GitHub App permission
  * Approvals satisfy branch protection requirement
  * Next: expand permissions or manually merge

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: update list-prs.sh to search all repos by owner instead of @me

GitHub App tokens don't have user identity, so @me/@review-requested searches
fail. Changed to enumerate all repos in don-petry and petry-projects, then
search for open PRs within each repo. This covers the full scope:
- All open PRs in personal don-petry repos
- All open PRs in petry-projects org repos

Resolves 6 consecutive workflow failures due to zero PRs being enumerated.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* docs: add investigation report for PR review agent workflow failures

* fix: move env section before steps in workflow to fix YAML structure

GitHub Actions requires env section to come before steps in job definition.
Moving env definition up and setting GH_TOKEN only in steps that need it.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* script: add backfill-approvals to retroactively apply real approvals

Converts PRs with agent approval comments (but no real GitHub approvals)
to have actual APPROVED reviews. Needed for PRs reviewed before the
gh pr review --approve fix was applied.

Usage:
  scripts/backfill-approvals.sh          # dry-run (preview changes)
  scripts/backfill-approvals.sh false    # apply real approvals

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* feat: add backfill-approvals workflow to run as GitHub App bot

Runs backfill-approvals.sh via GitHub Actions so the approvals are posted
by the bot identity, not the PR author — GitHub rejects self-approvals.

Also fixes subshell counter bug in the script (piped while loop lost variable
state; switched to process substitution).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: convert inner while loop to process substitution to preserve counters

Both the outer repo loop and inner PR loop were piped subshells, causing
all counter increments (approved/skipped/failed) to be lost. The summary
always showed 0. Fixed by using < <(...) process substitution for both loops.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* car-hunt: add VIN-deep-dive flow, printable checklists, location-tier ranking

- Step 5.5 pre-test-drive flow: NHTSA VIN decode + per-VIN unrepaired-recall
  automation via Chrome MCP (Honda LWC owner portal documented as not
  automatable; NHTSA web form works).
- §F printable test-drive checklist generator (reportlab/Platypus PDF) with
  cold-start, model-specific red flags, negotiation table, walk-away rules.
- §G fraud / paperwork verification checklist (vehicle-agnostic, reusable):
  walk-away triggers, VIN three-location match, curbstoning, odometer fraud,
  bill of sale, title transfer, payment protection, stolen-vehicle, title-
  jumping, after-purchase steps.
- Step 5.25 head-to-head comparison template with information-asymmetry rule.
- Step 3 Location Quality Tier scoring (Birmingham AL metro table A/B/C/D)
  with adj-CPM modifier; sheet schema gains Adj.CPM + Tier columns.
- Hard-disqualification rules added: non-running engine keywords, mileage
  inconsistency, mandatory description scrape.
- Drive MCP overwrite limitation documented; sheet ID now read from memory
  rather than hardcoded.
- FB Marketplace operational facts: Birmingham AL city ID 107739635926718,
  /search? vs /vehicles? query handling, React-controlled inline composer,
  send-button selector, seller-name extraction regex.
- scheduled-tasks/used-car-search-{morning,afternoon}: tier A/B priority
  flag, sheet ID read from memory.
- Reference PDF generator scripts checked in for reuse.

* fix: re-approve PRs where approval predates last commit (stale approval)

Instead of skipping PRs that already have any APPROVED review, skip only
those where reviewDecision is not REVIEW_REQUIRED. This handles PRs where
a bot approval was posted before a new commit was pushed — GitHub's ruleset
engine treats those approvals as stale even with dismiss_stale_reviews=false.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: robustly extract JSON verdict from mixed claude --print output (#17)

Claude's --print mode can prefix the JSON verdict with conversational
preamble text, causing jq to fail with parse errors. Add
extract_verdict_json to engine.sh and wire all three cascade action
call sites in review-one-pr.sh to use it.

Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: extract_verdict_json — check dest file first (agent Bash-write style)

The cascade-action agent writes its verdict JSON to \$OUTPUT_FILE via a
Bash tool call, then prints a text summary to stdout. The previous fix
only scanned stdout (the .raw file), which contained no JSON. The agent-
written file at \$dest (= \$OUTPUT_FILE) was already correct — just not
checked. Now check \$dest first before falling back to stdout scan.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Enforce MAX_REVIEW_CYCLES cap before running the cascade (#18)

* fix: enforce MAX_REVIEW_CYCLES cap with human-escalation marker

Previously MAX_REVIEW_CYCLES only gated AI delegation in
post-pr-review.sh — the cascade itself ran on every cron tick
regardless of how many cycles had accumulated. Real-world result:
a PR could rack up 9+ review cycles (we observed exactly that on
ContentTwin#100) before any cap took effect.

Add a pre-cascade check in review-one-pr.sh: when the count of
existing `<!-- pr-review-agent v1 sha=... -->` markers is at or
above MAX_REVIEW_CYCLES (default 3), post one escalation comment
marked `<!-- pr-review-agent escalation -->`, label
needs-human-review, request don-petry, and exit 100 (skip
sentinel — doesn't burn the MAX_PRS budget). The escalation
marker doubles as the no-spam guard: subsequent runs detect it
and exit 100 immediately.

Also reuse a single `gh pr view` for both the cycle count and the
escalation-marker check.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* review feedback: surface escalation-comment failures, drop dead unset

- Drop `2>/dev/null || true` on `gh pr comment` so a failed escalation
  post is visible in the workflow log instead of silently leaving the
  PR with no marker (which would re-trigger the cap path next tick).
- Reword the cycle-count comment from "AI delegation loops" to "review
  loops" — the cap also catches cascade-only loops where every cycle
  approves and a new commit lands before merge.
- Remove the redundant `unset PR_BODIES` — never exported, no leakage.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>

* Reliability hardening: session circuit breaker, timeouts, retry, dedup (#20)

* feat: session circuit breaker, per-tier timeouts, retry, and triage hard-fail

Reliability hardening for the PR review agent.

1. Session circuit breaker (.github/workflows/pr-review.yml): on any
   non-zero, non-100 exit from review-one-pr.sh (general failure or rate
   limit on the fallback engine), break the per-PR loop, log a clear
   error annotation naming the failing PR and reason, and exit the step
   with code 1 so the run shows red. Prevents one systemic problem from
   silently burning the entire candidate pool.

2. Per-tier timeouts (scripts/engine.sh): triage/deep/audit/action/duck
   each get their own bounded timeout (180/600/600/300/300s defaults,
   env-overridable). Previously only the duck had a timeout — a hung
   tier could burn the whole 60min job budget.

3. Retry-with-backoff on transient errors (scripts/engine.sh): triage
   retries once on 124/137/143 (timeout / signal kill) since its caller
   captures stdout via $(...) so retries are safe. Deliberately NOT
   applied to run_agentic/run_duck where stdout is redirected to a file
   — a retry there would corrupt the partial first-attempt output.

4. Triage non-JSON now hard-fails (scripts/review-one-pr.sh): replaces
   the silent fallback that synthesized a fake "escalate=MEDIUM" verdict
   and proceeded to deep review. With the new circuit breaker, loud
   failure is the right call — masking a broken triage was burning
   tokens on every PR while the workflow looked healthy.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix: stop stacking duplicate agent reviews on the same PR

Two bugs were causing the agent to leave multiple comments on the same PR.
Together they produced 10 stacked APPROVED reviews on petry-projects/ContentTwin#100.

Bug A — idempotency check is order-dependent (review-one-pr.sh):
The previous marker-discovery code did:
  ((.reviews // []) + (.comments // [])) | .[].body | grep marker | tail -1
This relies on the array concatenation order, not chronological order. When
old agent comments existed alongside newer agent reviews, tail -1 picked
the comment-array marker (older) over the review-array marker (newer),
causing the script to think the head SHA hadn't been reviewed and re-run.
Replaced with a single jq pipeline that tags each item with submittedAt /
createdAt, sorts by timestamp, and takes the actual most-recent marker.

Bug B — no cleanup of prior agent items (post-pr-review.sh):
After successfully posting a new review/comment, prior agent items were
left in place, accumulating forever. Added mark_prior_agent_items_obsolete
which, after a successful post:
  - dismisses prior APPROVED/COMMENTED/CHANGES_REQUESTED agent reviews via
    the GitHub dismissal API (UI shows them struck-through as Dismissed)
  - edits prior agent comments to wrap their body in a collapsed <details>
    block with a "Superseded by re-review at <SHA>" summary, plus a
    `<!-- pr-review-agent superseded -->` sentinel for idempotency

All cleanup API calls are best-effort — failures don't break the workflow.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(cleanup): warn on API failures, preserve global newest, file-stage JSON

Three fixes to mark_prior_agent_items_obsolete from the review of PR #20:

1. ::warning:: annotations on every cleanup API failure (review/comment
   list-fetch, individual review dismissal, individual comment fetch+edit).
   Previously these were silenced with `|| true`, so a permissions change
   on the dismissal endpoint would let duplicates stack indefinitely with
   no signal in the Actions UI. Cleanup is still non-fatal — the new post
   has already landed — but failures are now visible.

2. Preserve the globally-latest agent item across BOTH categories, not the
   newest of each category separately. The earlier code split reviews and
   comments and applied `[:-1]` to each, which left a stale fix-request
   comment in place when the new post was a review (or vice versa). The
   one-off cleanup of ContentTwin#100 hit exactly this case: 12 stacked
   reviews collapsed to 1, but a stale comment from 2026-04-25 (SHA
   cd9132d6) was preserved as "newest comment" even though the latest
   review at SHA 3af8c8ee was newer overall. Now: compute the max
   timestamp across both feeds, exclude items at that timestamp.

3. Stage API responses to disk (`mktemp` + `jq <file>`) instead of routing
   through `--argjson "$var"`. The old approach broke on rare unescaped
   control chars in user-authored comment bodies (jq refused to parse the
   resulting shell-vared JSON). File-based input sidesteps the shell
   pipeline entirely.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>

* Remove car-hunt skill and scheduled tasks (moved to don-petry/don-petry) (#21)

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: apply Copilot fallback hardening and reuse GH_PAT for Copilot auth

- Use secrets.GH_PAT (existing personal account token with Copilot
  subscription) instead of a separate COPILOT_GITHUB_TOKEN secret
- Pin actions/create-github-app-token to SHA (v3.1.1) for supply chain safety
- Add post-install verification for gh-copilot extension with warning on failure
- Guard Copilot fallback path: skip PR gracefully if extension is not installed

Addresses issue #24 workflow fixes (1-3) that could not land via PR #25
due to GitHub App lacking workflows permission.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: switch Copilot engine to gh built-in and fix app-id deprecation

- Remove gh extension install (github/gh-copilot conflicts with built-in alias)
- Replace with gh copilot --version check using COPILOT_GITHUB_TOKEN (GH_PAT)
- Update all engine.sh copilot invocations from bare `copilot` binary to
  `gh copilot suggest --target shell` with GH_TOKEN overridden to GH_PAT
  so the user token (with Copilot subscription) is used, not the App token
- Fix actions/create-github-app-token: app-id → client-id (same secret value,
  just the renamed input in v3.x)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: harden Copilot fallback path and remove stale worktrees (#25)

- gh-copilot install: add --force flag and post-install verification
  warning so silent failures due to built-in alias conflict are visible
  in workflow logs instead of being silently swallowed by `|| true`
- fallback pre-flight check: verify `gh extension list | grep copilot`
  before switching to Copilot engine; if unavailable, skip the PR and
  continue the batch rather than session-aborting and dropping all
  remaining candidates (fixes the 27-PR drop on run #503)
- pin actions/create-github-app-token to SHA for v3.1.1 (Node.js 24
  compatible) to prevent hard break on 2026-06-02 when GitHub forces
  Node.js 24 as default runtime
- remove 6 stale Claude Code worktrees from git tracking; add
  .gitignore entry to prevent future worktrees from being committed
  (eliminates exit-128 warning on every actions/checkout post-job sweep)

Closes #24

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: petry-projects-pr-review-agent[bot] <petry-projects-pr-review-agent[bot]@users.noreply.github.com>

* ci: add pre-flight dedup check to prevent duplicate claude-issue PRs (#26)

Before invoking Claude on a labeled issue, a new shell step queries
for any open PR with a branch matching claude/issue-NNN-* (or a body
containing "Closes #NNN"). If one is found it posts a comment on the
issue pointing to the existing PR and skips the Claude step entirely.
A secondary prompt instruction tells Claude to check first and push to
the existing branch rather than opening a new PR.

Motivated by issue petry-projects/google-app-scripts#171, where the
claude label was re-applied three times and each run created a fresh
PR unaware of the prior attempts.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor: migrate to machine user PAT auth (closes #27) (#28)

* refactor: migrate from GitHub App to machine user PAT auth

Closes #27

GitHub Apps cannot be listed in CODEOWNERS, blocking PRs in repos with
require_code_owner_review: true. Switch all workflows to use a machine
user account's fine-grained PAT (DON_PETRY_BOT_GH_PAT secret), which can
join an org team listed in CODEOWNERS.

Workflows: removed actions/create-github-app-token steps in pr-review,
fix-stuck-prs, backfill-approvals, and daily-pr-review-health. All now
use secrets.DON_PETRY_BOT_GH_PAT directly.

Docs: renamed GITHUB_APP_SETUP.md to MACHINE_USER_SETUP.md with full
rewrite covering account creation, CODEOWNERS config, PAT generation,
and rotation. Updated auth sections in IMPLEMENTATION.md, SETUP.md,
STATUS.md, DOCUMENTATION.md, README.md.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* refactor: switch to org-scoped PAT secret DON_PETRY_BOT_PETRY_PROJECT_PAT

Replace DON_PETRY_BOT_GH_PAT with DON_PETRY_BOT_PETRY_PROJECT_PAT — the
new fine-grained PAT scoped to the petry-projects org (resource owner =
petry-projects). The previous PAT was scoped to the donpetry-bot personal
namespace and had no repository access.

The old DON_PETRY_BOT_GH_PAT secret is retained for any future use against
don-petry's personal repos.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* chore: ignore .claude/scheduled_tasks.lock state file

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>

* refactor: merge approval repair scripts into single automation

- Consolidate backfill-approvals.sh and fix-stuck-prs.sh into repair-pr-approvals.sh
- Iterate all repos in both orgs instead of assuming author
- Verify no existing APPROVED review before posting
- Copy original agent comment as review body
- Enable auto-merge when posting approval if needed
- Single workflow with 30-min timeout

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: add @mention trigger for on-demand PR reviews (#30)

Adds repository_dispatch support so commenting @petry-review-bot on any
PR fires an immediate review without waiting for the hourly schedule.

- pr-review.yml: new `repository_dispatch` trigger (type: pr-review-mention),
  per-PR concurrency group for mention runs, FORCE_REVIEW and DRY_RUN
  env vars that handle both workflow_dispatch and repository_dispatch paths
- review-one-pr.sh: FORCE_REVIEW=true bypasses idempotency so a mention
  always runs a fresh cascade even if the head SHA hasn't changed
- templates/mention-listener.yml: deploy to petry-projects/.github;
  listens for @petry-review-bot, validates commenter trust, posts ack,
  sends repository_dispatch (requires Contents:write, not Actions:write)

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: harden rebase, review dismissal, and health log diagnostics (#40)

Addresses 4 code-actionable recommendations from health check report #33:

- [CRITICAL] Make rebase/branch-update non-fatal — 403/504 during rebase
  emits ::warning:: instead of aborting the batch session (#34)
- [MEDIUM] Add retry with exponential backoff (3 attempts) for transient
  5xx on update-branch API; break immediately on 4xx (#37)
- [MEDIUM] Guard review dismissal with state re-check before calling
  dismiss API, preventing 422s from race conditions (#38)
- [LOW] Surface missing log warnings in health check script (#39)
- Fix: skip auto-merge when branch is still BEHIND after failed rebase

Closes #34, closes #37, closes #38, closes #39

* refactor: migrate to org-wide .github-private convention (#41)

* refactor: parametrize hardcoded identity values for org migration

Replace all hardcoded references to don-petry, petry-review-bot, and
don-petry/pr-review-agent with environment variables that default to
the current values. This allows the agent to be configured for different
orgs/users via repo variables.

Changes:
- Scripts use $REVIEWER_USER, $TARGET_ORG, $BOT_USER, $AGENT_REPO
- Prompts no longer reference specific GitHub usernames
- Workflows use org-level GH_PAT_WORKFLOWS secret instead of
  repo-level DON_PETRY_BOT_PETRY_PROJECT_PAT
- Health check uses context.repo.owner/repo for issue creation
- Variables are set in workflow env block with defaults

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add Copilot custom agent profiles for org-wide use

Create /agents/ directory with three agent profiles:
- pr-reviewer: Multi-tier cascading PR review
- feature-ideator: Feature idea generation and prioritization
- compliance-auditor: Org standards compliance checking

These are Copilot custom agent profiles that become available
org-wide in the .github-private repo convention.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Squashed 'frameworks/bmad-method/' content from commit e36f219c

git-subtree-dir: frameworks/bmad-method
git-subtree-split: e36f219c81b6010d4aae423ba12f49edb5b6e31a

* Squashed 'frameworks/spec-kit/' content from commit 11f49ebf

git-subtree-dir: frameworks/spec-kit
git-subtree-split: 11f49ebfb2f6af55345cb4bd9a7906acd211e56f

* Squashed 'frameworks/gsd/' content from commit 304c1a13

git-subtree-dir: frameworks/gsd
git-subtree-split: 304c1a1302564c45af252bbba4bcc5350e7dac3a

* docs: update README for .github-private org infrastructure role

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* ci: add dependabot-automerge.yml workflow (#73)

Adds the required dependabot-automerge.yml workflow from the org
standard template (petry-projects/.github/standards/workflows/).
This is a thin caller stub that delegates to the org-level reusable
workflow.

Closes #48

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Don Petry <don-petry@users.noreply.github.com>

* fix(pr-review): unblock queue starvation from self-authored PRs (#96) (#97)

* fix(pr-review): unblock queue starvation from self-authored PRs (#96)

A self-authored PR sorted first in the candidate list and triggered
"Can not approve your own pull request" — which the session-fatal
abort treated like an engine error, skipping all 28 remaining PRs
on every run.

- list-prs.sh: filter out PRs authored by REVIEWER_USER at enumeration
- post-pr-review.sh: catch the GraphQL self-approval error and exit 100
  (no-op) instead of 1, so a stray self-PR can no longer abort the batch
- engine.sh: gh copilot renamed --target to --agent; fix the rubber-duck
  invocations so tier-2 cross-engine review works again
- review-one-pr.sh: stop appending a duplicate "0" to REVIEW_CYCLE when
  grep -c finds no markers under set -o pipefail (was breaking the
  cycle-cap integer comparison)

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

* fix(pr-review): authenticate as bot, harden enumeration, address review

- Workflow now runs as AGENT_USER (default don-petry-bot), distinct from
  REVIEWER_USER (the human, don-petry). The self-approval rejection that
  caused #96 came from the agent and the human sharing one identity.
- list-prs.sh filters self-authored PRs against AGENT_USER, and validates
  AGENT_USER against the GitHub username charset before interpolating into
  the jq filter (Copilot review feedback).
- review-one-pr.sh uses printf '%s\n' instead of echo for PR_BODIES, since
  PR body content is user-authored and could begin with -n/-e or contain
  backslash escapes (Copilot review feedback).
- AGENT.md guideline updated to reflect that self-authored PRs are
  intentionally excluded (CodeRabbit review feedback).

Operator note: rotate the GH_PAT_WORKFLOWS secret to a token owned by
don-petry-bot (with read:org added) for the bot-account behavior to take
effect at runtime.

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

* refactor(pr-review): collapse to single BOT_USER, route escalations via CODEOWNERS

Drop AGENT_USER and REVIEWER_USER. The workflow now has one identity:
BOT_USER (default don-petry-bot), which both owns the repos to scan and
gets filtered out as the self-approval blocker. Human escalation no
longer hard-codes a single reviewer; instead, scripts/request-codeowners-
review.sh parses CODEOWNERS in the PR's repo and requests review from
every @user / @org/team mention.

- list-prs.sh: BOT_USER for both gh-repo-list and self-author filter
- review-one-pr.sh, post-pr-review.sh: replace --user "$REVIEWER_USER"
  with the CODEOWNERS helper
- repair-pr-approvals.sh: same gh-repo-list switch
- pr-review.yml: drop AGENT_USER/REVIEWER_USER, set BOT_USER default
  to don-petry-bot
- AGENT.md: updated guideline to describe single-identity model

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

* refactor(pr-review): extract batch loop, hoist env, drop redundant step

Workflow YAML drops from 231 to 111 lines by moving the per-PR review
loop into scripts/review-batch.sh. The script can be syntax-checked and
exercised locally; the workflow now just wires env vars and dispatches.

- Hoist GH_TOKEN, MAX_PRS, CANDIDATE_LIMIT to job-level env (were
  repeated on individual steps).
- Inline `gh auth status` into the install step; remove the standalone
  Verify auth step (its only output was a one-line auth dump).
- Drop the `if: steps.list.outputs.count != '0'` guard and the step's
  `id`/output: review-batch.sh handles empty input as a no-op.
- Collapse the duplicated summary-string branch in the review loop into
  a single template with a conditional fragment.

No behavior change.

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

* perf(pr-review): cache claude-code CLI install across runs

`npm install -g @anthropic-ai/claude-code` ran on every workflow start
(~30s). Switch to a per-user npm prefix (~/.npm-global) and cache that
directory via actions/cache, keyed on CLAUDE_CODE_VERSION + runner OS.

A `command -v claude` guard makes the install a no-op on cache hit, so
the only cost on subsequent runs is the cache restore.

CLAUDE_CODE_VERSION defaults to 'latest' (cache persists until manually
flushed); set the repo variable to pin a specific version for fully
reproducible caching.

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

---------

Co-authored-by: Claude <noreply@anthropic.com>

* chore: rename GH_PAT_WORKFLOWS secret to DON_PETRY_BOT_GH_PAT (#99)

The previous name was generic and didn't tell you which account the PAT
belonged to. The new name makes the binding explicit: this secret is the
PAT owned by don-petry-bot, used as BOT_USER throughout the PR-review
workflows.

Operator follow-up before this can be merged:
- Add a new repo secret DON_PETRY_BOT_GH_PAT containing the bot's PAT
  (with repo, workflow, and read:org scopes).
- After merge, the old GH_PAT_WORKFLOWS secret can be deleted.

Affected workflows:
- pr-review.yml (1 use)
- claude.yml (4 uses, with || github.token fallback)
- daily-pr-review-health.yml (1 use)
- repair-pr-approvals.yml (1 use)

scripts/pr_review_health.sh's error message is updated to point at the
new name as well.

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

Co-authored-by: Claude <noreply@anthropic.com>

* chore: remove frameworks directory (#101)

Co-authored-by: Gemini CLI <gemini-cli@example.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore all erroneously deleted files (items 4-34) (#104)

* revert: restore .github/workflows/daily-pr-review-health.yml

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore .github/workflows/repair-pr-approvals.yml

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore agents/pr-reviewer.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/cascade-action.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/deep-review.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/rubber-duck.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/security-audit.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/shared.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/single-review.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/synthesize-duck.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/synthesize.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/triage.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/engine.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/list-prs.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/post-pr-review.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/pr_review_health.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/repair-pr-approvals.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/request-codeowners-review.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/review-batch.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/review-one-pr.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore templates/mention-listener.yml

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore AGENT.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore BOT_SETUP.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore DOCUMENTATION.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore IMPLEMENTATION.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore MACHINE_USER_SETUP.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore PR_REVIEW_AGENT_FAILURE_REPORT.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore PR_REVIEW_FAILURE_INVESTIGATION.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore SETUP.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore STATUS.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore README.md to pre-change state

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore .github/workflows/daily-pr-review-health.yml

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat: add Gemini Pro support and optimize PR review fallback chain (#102)

* Merge main and resolve conflicts

* Enhance pr-review workflow: add Gemini support and refactor fallback logic

* Address Copilot review comments: tighten regex, fix fallback summary, and align docs

* docs: move agent documentation to docs/ folder and rename files

* docs: align secret names and fix casing in index

* docs: organize pr-review-agent documentation under dedicated folder

* Address final PR comments: tighten regex, add preflight checks, fix bot names, and cleanup docs

---------

Co-authored-by: Gemini CLI <gemini-cli@example.com>

* fix(pr-review): gate rate-limit detection on non-zero exit code

Broad patterns like `plan.*limit` and `claude.*usage` could match content
in a successful triage summary, triggering a false-positive engine fallback.
Guard the check with TRIAGE_RC != 0 so it only fires when the provider
command actually failed.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(claude): sync inlined workflow with org standard (#109)

* fix(claude): sync inlined workflow with org standard

Brings the inlined claude.yml up to parity with
petry-projects/.github/.github/workflows/claude-code-reusable.yml.

Changes:
1. Bot allow list for pull_request_review_comment
   Add coderabbitai[bot], Copilot, copilot-pull-request-reviewer[bot],
   and gemini-code-assist[bot] alongside the existing OWNER/MEMBER/
   COLLABORATOR check. These bots have author_association 'NONE' so
   their review comments were always skipped.

2. check_run trigger + claude-ci-fix job
   Port the CI failure auto-fix feature from the reusable. When a check
   fails on a PR, Claude diagnoses and fixes it automatically.
   Adapted to use DON_PETRY_BOT_GH_PAT (this repo's secret name).

3. claude-code-action SHA bump: v1.0.89 → v1.0.119
   Update both the claude and claude-issue jobs.

4. paths-ignore on pull_request trigger
   Prevents the workflow from firing on PRs that only change
   claude.yml itself, avoiding the Anthropic OIDC validation
   failure (workflow file must match default branch at token exchange).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(claude): guard bot allow list against fork PRs

Same fix as petry-projects/.github PR #238 — add same-repo guard
for bot-triggered pull_request_review_comment runs.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(claude): address ci-fix self-loop and fork PR security

- Replace Claude Code name prefix check with explicit job name list to
  correctly prevent self-loops (check_run names for inlined workflows
  are bare job names, not workflow-prefixed)
- Add fork PR trust gate in Resolve PR number step: verify head repo
  matches target repo before running Claude with privileged credentials

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* ci: replace inlined claude.yml with standard thin-caller stub

The inlined version embedded all three jobs (claude, claude-ci-fix,
claude-issue) directly and threaded DON_PETRY_BOT_GH_PAT through
checkout tokens and github_token, causing the bot's PAT to author
PRs and comments instead of github-actions[bot].

Replace with the standard thin caller that delegates to the org-level
reusable workflow (claude-code-reusable.yml@v1) via secrets: inherit,
which is the correct pattern per:
petry-projects/.github/standards/workflows/claude.yml

* fix: replace gh copilot suggest with GitHub Models REST API (#151)

* fix: replace gh copilot suggest with GitHub Models REST API (#147)

The `gh copilot suggest -p "$(cat <file>)"` invocation failed with
"Invalid command format" because:
1. The `-p` flag is not valid syntax in modern `gh copilot` built-in versions.
2. `gh copilot suggest` is a shell-command suggestion tool; it does not
   accept large PR prompts or return structured JSON responses.
3. Passing a multi-thousand-line prompt via `$(cat ...)` can hit ARG_MAX.

The non-zero exit was then misclassified by the rate-limit detector, which
aborted the entire session and skipped all remaining PRs in the queue.

Fix: replace all three copilot invocations (run_triage, run_agentic,
run_duck) with a new `copilot_chat` helper that calls the GitHub Models
REST API directly via curl. The API is OpenAI-compatible, versioned via
`X-GitHub-Api-Version`, stable against gh CLI version changes, and accepts
arbitrary prompt sizes using python3 for safe JSON encoding.

Additional changes:
- Add `COPILOT_API_MODEL` var (default `openai/o4-mini`) overrideable via env.
- run_agentic/run_duck copilot cases now also write output to $OUTPUT_FILE
  so callers that check that path directly (deep review, audit) find the JSON.
- Rate-limit responses (HTTP 429) are echoed to stdout so the existing
  `is_rate_limited()` detector fires correctly for engine fallback.
- Add pre-flight smoke test in review-batch.sh: tests GitHub Models API
  connectivity with a one-liner prompt before processing any PRs, so auth
  or model errors surface immediately as a clear setup failure.
- Add tests/test_copilot_chat.sh: unit tests for the JSON payload builder
  with edge-case prompts (quotes, newlines, # headings, large diffs, Unicode).

Closes #147

Co-authored-by: Don Petry <don-petry@users.noreply.github.com>

* fix: scope COPILOT_API_MODEL export to copilot engine only

Move the `export COPILOT_API_MODEL` from the common exports block into the
`copilot)` case where the variable is set, so it is not exported as an
empty/unset variable when the engine is `claude` or `gemini`. The
`copilot_chat` fallback `${COPILOT_API_MODEL:-openai/o4-mini}` still works
correctly when `DUCK_ENGINE=copilot` under a non-copilot primary engine.

Co-authored-by: Don Petry <don-petry@users.noreply.github.com>

* fix: address review comments — temp file for payload, fail-fast source, streaming output

- copilot_chat: write JSON body to mktemp file, pass to curl as @file to
  avoid ARG_MAX for large PR diffs (was --data-binary "$body")
- copilot_chat: add :? guard on COPILOT_GITHUB_TOKEN for a clear error
  instead of generic "unbound variable" under set -u
- run_agentic / run_duck copilot paths: stream directly to stdout (and
  tee to OUTPUT_FILE when set) rather than buffering the full response
  into a shell variable, which forced large outputs into memory and
  stripped trailing newlines
- review-batch.sh pre-flight: fail fast if source engine.sh fails
  (was silently ignored with || true)
- review-batch.sh pre-flight: build smoke-test JSON payload via python3
  + temp file instead of shell string interpolation to avoid JSON
  injection if COPILOT_API_MODEL contains special characters
- Clarify that openai/o4-mini is the correct April-2025 o4-generation
  model name, not a typo for o1-mini or gpt-4o-mini

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Don Petry <don-petry@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* feat: prioritise .github/.github-private PRs, oldest-first within tier (#155)

* feat: prioritise .github/.github-private PRs, oldest-first within tier

- list-prs.sh: add createdAt to JSON fetch; emit priority|createdAt|url
  lines (priority 0 for .github/.github-private, 1 for everything else)
- Replace final sort -u with a two-pass sort: deduplicate by URL, then
  sort by priority asc then createdAt asc; strip sort keys with cut
- tests/test_list_prs_sort.sh: 16 tests covering priority classification,
  oldest-first ordering, mixed scenarios and deduplication
- .github/workflows/test.yml: run both unit-test files on every PR / push

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* test: add dedup edge case for same URL with conflicting priorities

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: remove temperature from copilot_chat payload (o4-mini incompatible)

The GitHub Models API rejects temperature=0 for reasoning models like
o4-mini with HTTP 400:
  'Unsupported value: temperature does not support 0 with this model.
   Only the default (1) value is supported.'

This caused the rubber duck (DUCK_ENGINE=copilot / DUCK_MODEL=o4-mini)
to fail with HTTP 400 on every claude-engine run, silently degrading
every review to deep-only with no cross-engine sanity check.

Fix: remove temperature from the copilot_chat JSON payload entirely —
the API defaults to 1, which is the only supported value for o4-mini.

Also:
- Update tests/test_copilot_chat.sh: sync build_payload to match and
  flip Test 9 to assert temperature is ABSENT (not 0).
- Add .github/workflows/test.yml: run unit tests on every PR and push
  to main so regressions are caught before merge.

Reproducer: job/75690347409 — duck failure logged as:
  copilot_chat: HTTP 400 from GitHub Models API
  {"error":{"message":"Unsupported value: 'temperature' does not
   support 0 with this model..."}}

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: address PR review comments

- list-prs.sh: tighten priority regex to /[.]github(-private)?/pull/
  so path-boundary anchor prevents false positives (e.g. foo.github)
- list-prs.sh: replace printf pipeline with here-string to avoid ARG_MAX
  limits on large PR sets
- test.yml: add permissions: contents: read (least-privilege, CodeQL fix)
- test.yml: add timeout-minutes: 5 to prevent hung runs
- test_list_prs_sort.sh: sync sort_entries helper (here-string + || true)
  and classify regex to match updated list-prs.sh exactly

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: don-petry <don@petry.dev>

* fix: restore pr-review.yml content (accidentally emptied in revert)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: restore pr-review.yml (file was accidentally emptied)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore(deps): bump actions/cache from 4 to 5 (#165)

Bumps [actions/cache](https://github.com/actions/cache) from 4 to 5.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): bump petry-projects/.github/.github/workflows/claude-code-reusable.yml (#164)

Bumps [petry-projects/.github/.github/workflows/claude-code-reusable.yml](https://github.com/petry-projects/.github) from 1 to 2.
- [Commits](https://github.com/petry-projects/.github/compare/v1...v2)

---
updated-dependencies:
- dependency-name: petry-projects/.github/.github/workflows/claude-code-reusable.yml
  dependency-version: '2'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* fix(list-prs): search DELEGATION_ORGS and drop --checks success pre-filter

- Add iteration over all orgs in DELEGATION_ORGS (e.g. don-petry) so PRs
  in those orgs enter the candidate pool. Previously only BOT_USER and
  TARGET_ORG were searched.
- Remove --checks success from org repo searches. GitHub excludes PRs from
  repos with no CI configured when this flag is used, silently dropping all
  PRs from repos like .github, .github-private, google-app-scripts, and
  TalkTerm. review-one-pr.sh already enforces CI gating per-PR and treats
  empty statusCheckRollup as passing, so the pre-filter is redundant and
  harmful.
- Add --limit 200 to gh repo list calls to handle org growth beyond gh's
  default 30-repo cap.

* feat: add code-quality ruleset (compliance fix #60) (#86)

Creates the required `code-quality` repository ruleset enforcing required
status checks on the default branch, as mandated by the org standard:
standards/github-settings.md#code-quality--required-checks-ruleset-all-repositories

Required checks:
- SonarCloud (code quality analysis)
- CodeQL (SAST)
- agent-shield / AgentShield (agent security scan)
- dependency-audit / Detect ecosystems (dependency vulnerability scan)

Bypass actors:
- OrganizationAdmin (always) — emergency override
- dependabot-automerge-petry Integration (always) — Dependabot auto-merge

The ruleset was applied directly via GitHub API. This file documents the
configuration as code for auditability and future reapplication.

Closes #60

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Don Petry <don-petry@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* feat(prompts/dev-lead): add human prompt template

* feat(prompts/dev-lead): add human-pr prompt template

* feat(dev-lead): implement dev-lead agent Phases 0-6

* fix: per-PR isolation and single-review retry (closes #132)

- review-batch.sh: non-rate-limit per-PR failures (exit code 1) no
  longer abort the session. SESSION ABORTED EARLY is now reserved for
  the rate-limit-on-fallback-engine case (exit code 2) only. All other
  failures are counted and logged; remaining candidates continue.

- review-one-pr.sh: single-review step retries up to
  SINGLE_REVIEW_MAX_RETRIES (default 2) times with a
  SINGLE_REVIEW_RETRY_DELAY_SEC (default 15s) gap before giving up.
  On exhaustion, the PR is flagged needs-human-review and the script
  exits with code 1, which the updated batch treats as a non-fatal
  per-PR failure. Raw model output and stderr are logged on each failed
  attempt for post-mortem visibility.

Root cause of run #25707852006: claude-opus-4-7 returned a verbose
non-JSON response for PR #129; the old code treated that as fatal and
skipped 35 remaining candidates.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix: rate-limit detection and per-attempt stderr logs in single-review retry

Address inline review comments on PR #133:

- Rate-limit check: after each run_agentic call, inspect both stdout
  (VERDICT_JSON.raw) and stderr (SINGLE_LOG) with is_rate_limited before
  retrying. A rate-limit match exits immediately with code 2 so
  review-batch.sh can trigger engine fallback — consistent with triage and
  deep-review tiers. Previously a rate-limited single-review would burn all
  retries and exit 1 (per-PR failure), silently leaving the batch on the
  same rate-limited engine for all remaining PRs.

- Per-attempt log files: stderr is now written to
  single-review-attempt-N.log rather than a single overwritten file, so
  no earlier-attempt errors are lost. Each attempt logs its own stderr
  inline on failure; the fallback path cats all attempt logs for
  post-mortem visibility.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* feat(dev-lead): Phase 0 test infrastructure and Phase 1 intent stub

Phase 0: full test harness for the dev-lead agent —
  26 event fixtures (all valid JSON with _test_expected_intent),
  stub claude/gemini engines, mock gh binary, CI failure log sample,
  bats helpers (stub-engine, mock-gh, assert-env, prompt-vars),
  7 prompt templates with VARIABLES declarations, preflight script,
  prompt coverage integration test, and test-dev-lead.yml CI workflow.

Phase 1: dev-lead.yml trigger workflow (all 7 event types, dispatch + ci-relay
  jobs) and dev-lead-intent.sh stub (anti-loop guard live; all other events
  emit skip/not-implemented).  14/14 bats unit tests pass.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): add permissions blocks to test workflow jobs (CodeQL)

* fix(ci): auto-fix for lint / eslint [skip ci-relay]

* feat(dev-lead): Phases 2-6 — CI fix, review fix, issue, engine fallback

Phase 2: run_writer/run_writer_with_fallback in engine.sh, full intent
routing in dev-lead-intent.sh, dev-lead-fix-ci.sh handler, workflow wiring.

Phase 3: dev-lead-fix-reviews.sh handles fix-reviews, fix-bot-comment,
human, human-pr, rebase intents. Full review routing in intent classifier.

Phase 5: dev-lead-fix-issue.sh handles the issue intent with dedup guard
and branch/PR creation.

Phase 6: run_writer_with_fallback with claude→gemini→copilot fallback chain.

Tests: 77 unit tests across 8 new .bats files, all passing. Updated Phase 1
stub tests to reflect actual Phase 2+ routing behavior.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): address review comments — dispatch JSON, dead code, portable stub gh

* fix(dev-lead): pre-flight after intent, fork URL check, review-batch exit code, retry comment

* fix(dev-lead): install bats-core from GitHub to avoid root requirement

* fix(security): move event values to env vars to prevent script injection (SonarCloud)

* fix(security): move CLAUDE_CODE_VERSION to env block (SonarCloud script injection)

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* feat(dev-lead): Phase 1.5 — reusable workflow for cross-repo adoption + Phase 7 shadow period

* feat(dev-lead): add reusable workflow (Phase 1.5) and begin shadow period (Phase 7)

- Create .github/workflows/dev-lead-reusable.yml: workflow_call entry
  point for other repos. Checks out .github-private scripts/prompts into
  .dev-lead/, then runs the same intent-classify + handler pipeline as
  dev-lead.yml with PROMPTS_DIR=.dev-lead/prompts/dev-lead.
- Add PROMPTS_DIR env-var support to dev-lead-fix-ci.sh,
  dev-lead-fix-reviews.sh, and dev-lead-fix-issue.sh so the reusable
  workflow can point scripts at the sparse-checkout path without changing
  CWD. Defaults to prompts/dev-lead (backwards-compatible for dev-lead.yml).
- Annotate dev-lead.yml with Phase 7 shadow-period window (2026-05-15
  through ~2026-05-29): claude.yml and dev-lead.yml run in parallel until
  regressions clear, then claude.yml is removed.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* chore(dev-lead): update shadow-period tracking issue ref to #180

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): document PAT requirement and pin ref:main on private checkout

* fix(dev-lead): P1 review fixes — ci-relay in reusable, .dev-lead gitignore, export PROMPTS_DIR, trim header

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): rate-limit detection, separate status, and retry cron (#192) (#196)

* chore(dev-lead): decommission claude.yml, bump ACTION_TIMEOUT_SEC to 600s

- Delete .github/workflows/claude.yml — replaced by dev-lead.yml (shadow
  period complete, tracking issue #180).
- scripts/engine.sh: raise ACTION_TIMEOUT_SEC default 300→600s to reduce
  timeout failures on large-repo fix-ci runs (PR #80 had 3 timeouts).
- AGENTS.md: remove claude.yml immutability exemption; note dev-lead.yml
  as the active AI automation workflow and its edit-via-reusable pattern.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* refactor: replace Claude analysis with pure gh/jq telemetry in health check

* refactor: drop Node/Claude steps from daily-pr-review-health workflow

* rename: daily-pr-review-health → actions-fleet-monitor

* rename: daily-pr-review-health → actions-fleet-monitor

* fix(agents-md): clarify dev-lead.yml vs dev-lead-reusable.yml scope

dev-lead.yml in .github-private runs inline steps (not a caller stub).
Behavior changes for this repo go to dev-lead.yml directly; changes that
affect all org repos via the cross-repo reusable go to dev-lead-reusable.yml.

Addresses Copilot review on PR #194.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): rate-limit detection, separate status, and retry cron

Closes #192. Implements all phases from the revised plan.

**Phase 0 — fix run_writer stdout capture (prerequisite)**
- engine.sh: capture stdout via `tee` to a tempfile so `is_rate_limited`
  can inspect the output; old code read `/tmp/dev-lead-writer-stderr`
  which was never written (claude --print outputs to stdout, not stderr).
  Fallback engines were never tried in practice.
- engine.sh: add `parse_reset_time` to extract ISO timestamp from
  `resets H:MMpm (UTC)` in engine output and write it to
  `/tmp/dev-lead-rate-limit-reset` for callers to embed in markers.

**Phase 1 — separate status=rate-limited from status=failed**
- fix-ci.sh: detect engine exit 2 → post `status=rate-limited` (not
  `status=failed`); embed parsed reset time in marker body; exit 2.
- fix-ci.sh: fix `check_idempotency` to treat `status=rate-limited`
  as retriable — only block on terminal statuses (applied, failed,
  no-changes). Previously, rate-limited markers blocked all retries.
- fix-ci.sh: `count_recent_failures` already filtered to `status=failed`;
  add explicit comment confirming rate-limited markers are excluded.
- fix-ci.sh: add `has_rate_limited_marker` dedup check so the same SHA
  never accumulates more than one rate-limited comment.
- fix-reviews.sh: on engine exit 2 for all five intent types
  (fix-reviews, fix-bot-comment, human, human-pr, rebase), post a
  `status=rate-limited` marker with embedded reset time and exit 2.
- fix-reviews.sh: for `human` and `human-pr` intents, also post a
  user-visible acknowledgment comment so users know their request was
  received and will be retried.

**Phase 2 — scheduled retry cron for fix-ci**
- dev-lead-retry.sh: new script scans all open PRs across TARGET_ORG
  for `status=rate-limited` markers on current HEAD SHA; skips PRs
  whose reset time is still in the future; staggered dispatches (30s
  between repos) to prevent cascading org-level rate-limit hits.
- dev-lead-retry.yml: new workflow — scheduled every 2 hours (dual
  offsets to work around GitHub scheduler skew), plus workflow_dispatch.

**Phase 3 — retry for fix-reviews intents (same cron)**
- dev-lead-retry.sh already handles all five fix-reviews intent types
  (fix-reviews, fix-bot-comment, human, human-pr, rebase) via
  `dev-lead-reviews-retry` dispatch events.
- dev-lead-intent.sh: add `dev-lead-reviews-retry` dispatch type routing
  to the intent classifier — maps intent_type from payload to the correct
  existing fix-reviews step.
- dev-lead.yml: add `dev-lead-reviews-retry` to repository_dispatch types.
- dev-lead.yml: unify concurrency group for all dispatch types to
  `dev-lead-pr-{pr_number}` to avoid per-type slot fragmentation.

**Tests**
- test_engine_writer.bats: 8 new tests covering rate-limit stdout detection,
  exit-2 mapping, fallback exhaustion, reset time parsing.
- test_fix_ci.bats: 5 new tests covering rate-limited status, exhaustion
  exclusion, idempotency pass-through, and dedup.
- test_fix_reviews.bats: 5 new tests covering all intent types + human ack.
- test_intent_ci.bats: 4 new tests for dev-lead-reviews-retry dispatch routing.
- e2e/scenarios/07-rate-limit-retry.sh: new E2E scenario covering all parts.
- 3 new event fixtures for repository_dispatch_reviews_retry_* payloads.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): address review comments on #196

Addresses all 18 review threads from Copilot, Codex, and Gemini.

**P1 bugs fixed:**
- dev-lead-retry.sh: all log echo calls in scan_pr_for_rate_limits and
  dispatch_* functions now write to stderr; only the final count is on
  stdout. Previously, log lines polluted the command substitution,
  causing arithmetic expansion to fail and aborting the scan on the
  first retry candidate.
- dev-lead-retry.yml: permissions: contents was read-only; creating
  repository_dispatch events requires contents: write.

**Correctness fixes:**
- dev-lead-retry.sh: restrict automated retries to intents whose context
  can be reconstructed at runtime (fix-reviews, human-pr, rebase). human
  and fix-bot-comment require USER_INSTRUCTION/COMMENT_BODY from the
  original event which cannot be re-fetched; they are explicitly excluded.
- dev-lead-fix-reviews.sh: resolve HEAD_SHA from the PR API when not
  provided by the triggering event (issue_comment intents carry no SHA).
  Without this, rate-limited markers had no sha= field and were
  invisible to the retry scanner.
- dev-lead-fix-reviews.sh: write terminal status=applied marker after
  successful fix-reviews, human-pr, rebase runs. Prevents the retry
  cron from re-dispatching the same intent on every subsequent tick when
  the SHA hasn't changed.
- dev-lead-retry.sh: check for reviews terminal marker before dispatching
  a retry (mirrors the existing fix-ci terminal-marker check).
- dev-lead-retry.sh: look up current check-run details (details_url, id)
  at dispatch time via the commits check-runs API, so retried fix-ci runs
  have full failure logs and annotations rather than empty fields.
- dev-lead-fix-ci.sh: embed check= field in the rate-limited marker so
  the retry cron knows which check run to look up.
- dev-lead-fix-reviews.sh: human intent now posts a "please re-mention
  @dev-lead" ack instead of "I'll retry automatically" since it will NOT
  be retried automatically. human-pr keeps the auto-retry ack (correct).

**Pagination (Copilot + Gemini):**
- dev-lead-fix-ci.sh: add --paginate to check_idempotency, count_recent_
  failures, has_rate_limited_marker.
- dev-lead-fix-reviews.sh: add --paginate to has_reviews_rate_limited_marker.
- dev-lead-retry.sh: add --paginate to PR comments and PR list fetches.

**Other improvements:**
- dev-lead-retry.yml: simplify to single cron schedule (15 */2 * * *);
  remove BOT_USER env var (unused in the script).
- dev-lead-retry.sh: raise repo list limit 200 → 1000; add hard-error
  when list_repos_for_org returns empty (surfaces token permission issues
  rather than silently scanning 0 repos).
- dev-lead-intent.sh: use jq -nc (compact, null-input) for context JSON
  construction — safe against values with special characters.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(pr-review): handle Gemini trust-mode failure and properly route engine fallbacks (#217)

* fix(pr-review): handle Gemini trust-mode failure and properly route engine fallbacks

Closes #208

* fix(ci): install gemini CLI and set trust mode for dev-lead agents

---------

Co-authored-by: Gemini CLI <gemini-cli@example.com>

* fix: resolve YAML syntax error in dev-lead-reusable workflow

* fix: address automated feedback from PR 217 (#219)

This addresses missed feedback from CodeRabbit (removing syntax errors in YAML) and gemini-code-assist (improving string concatenation in validate-engines.sh), and adds assertion output checks to tests.

Co-authored-by: Gemini CLI <gemini-cli@example.com>

* chore: remove Phase 7 shadow period comment from dev-lead.yml

* feat: skip PRs with CHANGES_REQUESTED review (+ FORCE_REVIEW and stale-review guards)

* feat: skip PRs with CHANGES_REQUESTED review decision

Adds an early-exit check alongside the existing CI guards so the cascade
skips any PR where a human reviewer has already requested changes.
Reviewing those PRs before the author responds wastes tokens and creates
confusing parallel feedback threads.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix: honor FORCE_REVIEW and skip only stale-free CHANGES_REQUESTED reviews

Two issues addressed from Codex P2 feedback:

1. FORCE_REVIEW bypass: mention-triggered and force_review=true runs now
   bypass the CHANGES_REQUESTED skip, so authors can explicitly request a
   re-review after addressing feedback.

2. Stale review guard: only skip when a CHANGES_REQUESTED review targets
   the current head SHA. On repos without automatic stale-review dismissal,
   reviewDecision stays CHANGES_REQUESTED after new commits — the cascade
   now re-engages when the author has pushed new work.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): resolve context loss, missing checkout, and push logic (#223)

* feat: skip PRs with CHANGES_REQUESTED review decision

Adds an early-exit check alongside the existing CI guards so the cascade
skips any PR where a human reviewer has already requested changes.
Reviewing those PRs before the author responds wastes tokens and creates
confusing parallel feedback threads.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix: honor FORCE_REVIEW and skip only stale-free CHANGES_REQUESTED reviews

Two issues addressed from Codex P2 feedback:

1. FORCE_REVIEW bypass: mention-triggered and force_review=true runs now
   bypass the CHANGES_REQUESTED skip, so authors can explicitly request a
   re-review after addressing feedback.

2. Stale review guard: only skip when a CHANGES_REQUESTED review targets
   the current head SHA. On repos without automatic stale-review dismissal,
   reviewDecision stays CHANGES_REQUESTED after new commits — the cascade
   now re-engages when the author has pushed new work.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): resolve context loss, missing checkout, and push logic

- dev-lead-intent.sh: include actor and body in INTENT_CONTEXT for comments
- dev-lead.yml: parse and pass INTENT_ACTOR and INTENT_COMMENT_BODY to agent
- dev-lead-fix-reviews.sh: perform PR checkout, git push, and post summary comments

* fix(dev-lead): exit with 0 on no-changes in fix-reviews and human-pr

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* test: bypass CI check

* fix(pr-review): remove CI bypass hack

* fix(dev-lead): use heredoc for multiline environment variables (#224)

* fix(dev-lead): use heredoc for multiline environment variables

* fix(dev-lead): ensure all engines are installed and context is consistent

* fix(dev-lead): resolve stale model name in engine fallbacks

* fix(dev-lead): improve fallback reliability and script quality

- engine.sh: resolve stale model names in fallback loop; use -latest Gemini aliases
- dev-lead-fix-reviews.sh: remove unused code and fix PR_URL export

* fix(dev-lead): resolve Gemini model names and shell lint warnings

* security(dev-lead): use random heredoc delimiter to prevent inject…
don-petry added a commit that referenced this pull request Aug 7, 2026
… trigger for the Class-2 timer changes, before narrowing them (#1420)

* add: workflow to fix stuck PRs using bot account token

Allows running the cleanup script via workflow_dispatch with access to
GH_PAT (bot account token) from repo secrets.

* fix: pass GH_PAT to checkout action so workflow uses bot account

* debug: simplify account check, add authentication debug output

* docs: add comprehensive petry-review-bot setup instructions

* docs: add GitHub App setup guide (recommended approach)

GitHub App is the secure, recommended way to automate PR reviews:
- Fine-grained permissions
- JWT tokens that auto-expire
- No human account needed
- Better audit trail
- GitHub's recommended approach

Replaces the bot user account approach with a more secure alternative.

* docs: add comprehensive GitHub App setup documentation

- SETUP.md: Quick reference guide with configuration and troubleshooting
- IMPLEMENTATION.md: Technical deep dive on architecture and design decisions
- DOCUMENTATION.md: Index of all documentation files
- Updated README.md with status and quick links
- Updated GITHUB_APP_SETUP.md with implementation notes and actual app ID
- Workflows use GitHub App token generation instead of static PATs

Covers the transition from bot user account to GitHub App authentication for improved security and maintainability.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: silence auth check in fix-stuck-prs when using GitHub App token

GitHub App tokens don't have user scope, so 'gh api user' returns 403.
This is fine - the script still works for PR operations. Suppress the error
so the script completes successfully with app-token fallback label.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: use explicit author instead of @me in stuck PR search

GitHub App tokens don't have user identity, so @me search doesn't work.
Use explicit 'don-petry' author instead to find PRs to fix.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: avoid subshell in while loop to preserve variable state

Using pipe in while loop created subshell where PROBLEM_PRS and FIXED_PRS
counters were incremented but changes didn't persist to parent shell.
Fixed by using process substitution for input instead of pipe.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* docs: add comprehensive status report for GitHub App setup

- Overview of completed work
- GitHub App authentication details
- 24 stuck PRs successfully fixed with approval reviews
- Architectural decisions and rationale
- Configuration guide and usage examples
- Known limitations and troubleshooting

System is now fully operational with GitHub App token authentication,
comprehensive documentation, and all infrastructure in place.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* docs: document stuck PR cleanup status and script fixes

Added documentation for:
- GitHub App token compatibility fixes in fix-stuck-prs.sh
  * Changed @me to explicit author (app tokens have no identity)
  * Fixed subshell variable scope (process substitution vs pipe)
  * Silenced expected 403 auth check error
- Current status: 24 PRs have approvals but remain OPEN
  * Auto-merge failed due to missing GitHub App permission
  * Approvals satisfy branch protection requirement
  * Next: expand permissions or manually merge

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: update list-prs.sh to search all repos by owner instead of @me

GitHub App tokens don't have user identity, so @me/@review-requested searches
fail. Changed to enumerate all repos in don-petry and petry-projects, then
search for open PRs within each repo. This covers the full scope:
- All open PRs in personal don-petry repos
- All open PRs in petry-projects org repos

Resolves 6 consecutive workflow failures due to zero PRs being enumerated.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* docs: add investigation report for PR review agent workflow failures

* fix: move env section before steps in workflow to fix YAML structure

GitHub Actions requires env section to come before steps in job definition.
Moving env definition up and setting GH_TOKEN only in steps that need it.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* script: add backfill-approvals to retroactively apply real approvals

Converts PRs with agent approval comments (but no real GitHub approvals)
to have actual APPROVED reviews. Needed for PRs reviewed before the
gh pr review --approve fix was applied.

Usage:
  scripts/backfill-approvals.sh          # dry-run (preview changes)
  scripts/backfill-approvals.sh false    # apply real approvals

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* feat: add backfill-approvals workflow to run as GitHub App bot

Runs backfill-approvals.sh via GitHub Actions so the approvals are posted
by the bot identity, not the PR author — GitHub rejects self-approvals.

Also fixes subshell counter bug in the script (piped while loop lost variable
state; switched to process substitution).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: convert inner while loop to process substitution to preserve counters

Both the outer repo loop and inner PR loop were piped subshells, causing
all counter increments (approved/skipped/failed) to be lost. The summary
always showed 0. Fixed by using < <(...) process substitution for both loops.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* car-hunt: add VIN-deep-dive flow, printable checklists, location-tier ranking

- Step 5.5 pre-test-drive flow: NHTSA VIN decode + per-VIN unrepaired-recall
  automation via Chrome MCP (Honda LWC owner portal documented as not
  automatable; NHTSA web form works).
- §F printable test-drive checklist generator (reportlab/Platypus PDF) with
  cold-start, model-specific red flags, negotiation table, walk-away rules.
- §G fraud / paperwork verification checklist (vehicle-agnostic, reusable):
  walk-away triggers, VIN three-location match, curbstoning, odometer fraud,
  bill of sale, title transfer, payment protection, stolen-vehicle, title-
  jumping, after-purchase steps.
- Step 5.25 head-to-head comparison template with information-asymmetry rule.
- Step 3 Location Quality Tier scoring (Birmingham AL metro table A/B/C/D)
  with adj-CPM modifier; sheet schema gains Adj.CPM + Tier columns.
- Hard-disqualification rules added: non-running engine keywords, mileage
  inconsistency, mandatory description scrape.
- Drive MCP overwrite limitation documented; sheet ID now read from memory
  rather than hardcoded.
- FB Marketplace operational facts: Birmingham AL city ID 107739635926718,
  /search? vs /vehicles? query handling, React-controlled inline composer,
  send-button selector, seller-name extraction regex.
- scheduled-tasks/used-car-search-{morning,afternoon}: tier A/B priority
  flag, sheet ID read from memory.
- Reference PDF generator scripts checked in for reuse.

* fix: re-approve PRs where approval predates last commit (stale approval)

Instead of skipping PRs that already have any APPROVED review, skip only
those where reviewDecision is not REVIEW_REQUIRED. This handles PRs where
a bot approval was posted before a new commit was pushed — GitHub's ruleset
engine treats those approvals as stale even with dismiss_stale_reviews=false.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: robustly extract JSON verdict from mixed claude --print output (#17)

Claude's --print mode can prefix the JSON verdict with conversational
preamble text, causing jq to fail with parse errors. Add
extract_verdict_json to engine.sh and wire all three cascade action
call sites in review-one-pr.sh to use it.

Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: extract_verdict_json — check dest file first (agent Bash-write style)

The cascade-action agent writes its verdict JSON to \$OUTPUT_FILE via a
Bash tool call, then prints a text summary to stdout. The previous fix
only scanned stdout (the .raw file), which contained no JSON. The agent-
written file at \$dest (= \$OUTPUT_FILE) was already correct — just not
checked. Now check \$dest first before falling back to stdout scan.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Enforce MAX_REVIEW_CYCLES cap before running the cascade (#18)

* fix: enforce MAX_REVIEW_CYCLES cap with human-escalation marker

Previously MAX_REVIEW_CYCLES only gated AI delegation in
post-pr-review.sh — the cascade itself ran on every cron tick
regardless of how many cycles had accumulated. Real-world result:
a PR could rack up 9+ review cycles (we observed exactly that on
ContentTwin#100) before any cap took effect.

Add a pre-cascade check in review-one-pr.sh: when the count of
existing `<!-- pr-review-agent v1 sha=... -->` markers is at or
above MAX_REVIEW_CYCLES (default 3), post one escalation comment
marked `<!-- pr-review-agent escalation -->`, label
needs-human-review, request don-petry, and exit 100 (skip
sentinel — doesn't burn the MAX_PRS budget). The escalation
marker doubles as the no-spam guard: subsequent runs detect it
and exit 100 immediately.

Also reuse a single `gh pr view` for both the cycle count and the
escalation-marker check.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* review feedback: surface escalation-comment failures, drop dead unset

- Drop `2>/dev/null || true` on `gh pr comment` so a failed escalation
  post is visible in the workflow log instead of silently leaving the
  PR with no marker (which would re-trigger the cap path next tick).
- Reword the cycle-count comment from "AI delegation loops" to "review
  loops" — the cap also catches cascade-only loops where every cycle
  approves and a new commit lands before merge.
- Remove the redundant `unset PR_BODIES` — never exported, no leakage.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>

* Reliability hardening: session circuit breaker, timeouts, retry, dedup (#20)

* feat: session circuit breaker, per-tier timeouts, retry, and triage hard-fail

Reliability hardening for the PR review agent.

1. Session circuit breaker (.github/workflows/pr-review.yml): on any
   non-zero, non-100 exit from review-one-pr.sh (general failure or rate
   limit on the fallback engine), break the per-PR loop, log a clear
   error annotation naming the failing PR and reason, and exit the step
   with code 1 so the run shows red. Prevents one systemic problem from
   silently burning the entire candidate pool.

2. Per-tier timeouts (scripts/engine.sh): triage/deep/audit/action/duck
   each get their own bounded timeout (180/600/600/300/300s defaults,
   env-overridable). Previously only the duck had a timeout — a hung
   tier could burn the whole 60min job budget.

3. Retry-with-backoff on transient errors (scripts/engine.sh): triage
   retries once on 124/137/143 (timeout / signal kill) since its caller
   captures stdout via $(...) so retries are safe. Deliberately NOT
   applied to run_agentic/run_duck where stdout is redirected to a file
   — a retry there would corrupt the partial first-attempt output.

4. Triage non-JSON now hard-fails (scripts/review-one-pr.sh): replaces
   the silent fallback that synthesized a fake "escalate=MEDIUM" verdict
   and proceeded to deep review. With the new circuit breaker, loud
   failure is the right call — masking a broken triage was burning
   tokens on every PR while the workflow looked healthy.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix: stop stacking duplicate agent reviews on the same PR

Two bugs were causing the agent to leave multiple comments on the same PR.
Together they produced 10 stacked APPROVED reviews on petry-projects/ContentTwin#100.

Bug A — idempotency check is order-dependent (review-one-pr.sh):
The previous marker-discovery code did:
  ((.reviews // []) + (.comments // [])) | .[].body | grep marker | tail -1
This relies on the array concatenation order, not chronological order. When
old agent comments existed alongside newer agent reviews, tail -1 picked
the comment-array marker (older) over the review-array marker (newer),
causing the script to think the head SHA hadn't been reviewed and re-run.
Replaced with a single jq pipeline that tags each item with submittedAt /
createdAt, sorts by timestamp, and takes the actual most-recent marker.

Bug B — no cleanup of prior agent items (post-pr-review.sh):
After successfully posting a new review/comment, prior agent items were
left in place, accumulating forever. Added mark_prior_agent_items_obsolete
which, after a successful post:
  - dismisses prior APPROVED/COMMENTED/CHANGES_REQUESTED agent reviews via
    the GitHub dismissal API (UI shows them struck-through as Dismissed)
  - edits prior agent comments to wrap their body in a collapsed <details>
    block with a "Superseded by re-review at <SHA>" summary, plus a
    `<!-- pr-review-agent superseded -->` sentinel for idempotency

All cleanup API calls are best-effort — failures don't break the workflow.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(cleanup): warn on API failures, preserve global newest, file-stage JSON

Three fixes to mark_prior_agent_items_obsolete from the review of PR #20:

1. ::warning:: annotations on every cleanup API failure (review/comment
   list-fetch, individual review dismissal, individual comment fetch+edit).
   Previously these were silenced with `|| true`, so a permissions change
   on the dismissal endpoint would let duplicates stack indefinitely with
   no signal in the Actions UI. Cleanup is still non-fatal — the new post
   has already landed — but failures are now visible.

2. Preserve the globally-latest agent item across BOTH categories, not the
   newest of each category separately. The earlier code split reviews and
   comments and applied `[:-1]` to each, which left a stale fix-request
   comment in place when the new post was a review (or vice versa). The
   one-off cleanup of ContentTwin#100 hit exactly this case: 12 stacked
   reviews collapsed to 1, but a stale comment from 2026-04-25 (SHA
   cd9132d6) was preserved as "newest comment" even though the latest
   review at SHA 3af8c8ee was newer overall. Now: compute the max
   timestamp across both feeds, exclude items at that timestamp.

3. Stage API responses to disk (`mktemp` + `jq <file>`) instead of routing
   through `--argjson "$var"`. The old approach broke on rare unescaped
   control chars in user-authored comment bodies (jq refused to parse the
   resulting shell-vared JSON). File-based input sidesteps the shell
   pipeline entirely.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>

* Remove car-hunt skill and scheduled tasks (moved to don-petry/don-petry) (#21)

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: apply Copilot fallback hardening and reuse GH_PAT for Copilot auth

- Use secrets.GH_PAT (existing personal account token with Copilot
  subscription) instead of a separate COPILOT_GITHUB_TOKEN secret
- Pin actions/create-github-app-token to SHA (v3.1.1) for supply chain safety
- Add post-install verification for gh-copilot extension with warning on failure
- Guard Copilot fallback path: skip PR gracefully if extension is not installed

Addresses issue #24 workflow fixes (1-3) that could not land via PR #25
due to GitHub App lacking workflows permission.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: switch Copilot engine to gh built-in and fix app-id deprecation

- Remove gh extension install (github/gh-copilot conflicts with built-in alias)
- Replace with gh copilot --version check using COPILOT_GITHUB_TOKEN (GH_PAT)
- Update all engine.sh copilot invocations from bare `copilot` binary to
  `gh copilot suggest --target shell` with GH_TOKEN overridden to GH_PAT
  so the user token (with Copilot subscription) is used, not the App token
- Fix actions/create-github-app-token: app-id → client-id (same secret value,
  just the renamed input in v3.x)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: harden Copilot fallback path and remove stale worktrees (#25)

- gh-copilot install: add --force flag and post-install verification
  warning so silent failures due to built-in alias conflict are visible
  in workflow logs instead of being silently swallowed by `|| true`
- fallback pre-flight check: verify `gh extension list | grep copilot`
  before switching to Copilot engine; if unavailable, skip the PR and
  continue the batch rather than session-aborting and dropping all
  remaining candidates (fixes the 27-PR drop on run #503)
- pin actions/create-github-app-token to SHA for v3.1.1 (Node.js 24
  compatible) to prevent hard break on 2026-06-02 when GitHub forces
  Node.js 24 as default runtime
- remove 6 stale Claude Code worktrees from git tracking; add
  .gitignore entry to prevent future worktrees from being committed
  (eliminates exit-128 warning on every actions/checkout post-job sweep)

Closes #24

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: petry-projects-pr-review-agent[bot] <petry-projects-pr-review-agent[bot]@users.noreply.github.com>

* ci: add pre-flight dedup check to prevent duplicate claude-issue PRs (#26)

Before invoking Claude on a labeled issue, a new shell step queries
for any open PR with a branch matching claude/issue-NNN-* (or a body
containing "Closes #NNN"). If one is found it posts a comment on the
issue pointing to the existing PR and skips the Claude step entirely.
A secondary prompt instruction tells Claude to check first and push to
the existing branch rather than opening a new PR.

Motivated by issue petry-projects/google-app-scripts#171, where the
claude label was re-applied three times and each run created a fresh
PR unaware of the prior attempts.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor: migrate to machine user PAT auth (closes #27) (#28)

* refactor: migrate from GitHub App to machine user PAT auth

Closes #27

GitHub Apps cannot be listed in CODEOWNERS, blocking PRs in repos with
require_code_owner_review: true. Switch all workflows to use a machine
user account's fine-grained PAT (DON_PETRY_BOT_GH_PAT secret), which can
join an org team listed in CODEOWNERS.

Workflows: removed actions/create-github-app-token steps in pr-review,
fix-stuck-prs, backfill-approvals, and daily-pr-review-health. All now
use secrets.DON_PETRY_BOT_GH_PAT directly.

Docs: renamed GITHUB_APP_SETUP.md to MACHINE_USER_SETUP.md with full
rewrite covering account creation, CODEOWNERS config, PAT generation,
and rotation. Updated auth sections in IMPLEMENTATION.md, SETUP.md,
STATUS.md, DOCUMENTATION.md, README.md.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* refactor: switch to org-scoped PAT secret DON_PETRY_BOT_PETRY_PROJECT_PAT

Replace DON_PETRY_BOT_GH_PAT with DON_PETRY_BOT_PETRY_PROJECT_PAT — the
new fine-grained PAT scoped to the petry-projects org (resource owner =
petry-projects). The previous PAT was scoped to the donpetry-bot personal
namespace and had no repository access.

The old DON_PETRY_BOT_GH_PAT secret is retained for any future use against
don-petry's personal repos.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* chore: ignore .claude/scheduled_tasks.lock state file

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>

* refactor: merge approval repair scripts into single automation

- Consolidate backfill-approvals.sh and fix-stuck-prs.sh into repair-pr-approvals.sh
- Iterate all repos in both orgs instead of assuming author
- Verify no existing APPROVED review before posting
- Copy original agent comment as review body
- Enable auto-merge when posting approval if needed
- Single workflow with 30-min timeout

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: add @mention trigger for on-demand PR reviews (#30)

Adds repository_dispatch support so commenting @petry-review-bot on any
PR fires an immediate review without waiting for the hourly schedule.

- pr-review.yml: new `repository_dispatch` trigger (type: pr-review-mention),
  per-PR concurrency group for mention runs, FORCE_REVIEW and DRY_RUN
  env vars that handle both workflow_dispatch and repository_dispatch paths
- review-one-pr.sh: FORCE_REVIEW=true bypasses idempotency so a mention
  always runs a fresh cascade even if the head SHA hasn't changed
- templates/mention-listener.yml: deploy to petry-projects/.github;
  listens for @petry-review-bot, validates commenter trust, posts ack,
  sends repository_dispatch (requires Contents:write, not Actions:write)

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: harden rebase, review dismissal, and health log diagnostics (#40)

Addresses 4 code-actionable recommendations from health check report #33:

- [CRITICAL] Make rebase/branch-update non-fatal — 403/504 during rebase
  emits ::warning:: instead of aborting the batch session (#34)
- [MEDIUM] Add retry with exponential backoff (3 attempts) for transient
  5xx on update-branch API; break immediately on 4xx (#37)
- [MEDIUM] Guard review dismissal with state re-check before calling
  dismiss API, preventing 422s from race conditions (#38)
- [LOW] Surface missing log warnings in health check script (#39)
- Fix: skip auto-merge when branch is still BEHIND after failed rebase

Closes #34, closes #37, closes #38, closes #39

* refactor: migrate to org-wide .github-private convention (#41)

* refactor: parametrize hardcoded identity values for org migration

Replace all hardcoded references to don-petry, petry-review-bot, and
don-petry/pr-review-agent with environment variables that default to
the current values. This allows the agent to be configured for different
orgs/users via repo variables.

Changes:
- Scripts use $REVIEWER_USER, $TARGET_ORG, $BOT_USER, $AGENT_REPO
- Prompts no longer reference specific GitHub usernames
- Workflows use org-level GH_PAT_WORKFLOWS secret instead of
  repo-level DON_PETRY_BOT_PETRY_PROJECT_PAT
- Health check uses context.repo.owner/repo for issue creation
- Variables are set in workflow env block with defaults

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add Copilot custom agent profiles for org-wide use

Create /agents/ directory with three agent profiles:
- pr-reviewer: Multi-tier cascading PR review
- feature-ideator: Feature idea generation and prioritization
- compliance-auditor: Org standards compliance checking

These are Copilot custom agent profiles that become available
org-wide in the .github-private repo convention.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Squashed 'frameworks/bmad-method/' content from commit e36f219c

git-subtree-dir: frameworks/bmad-method
git-subtree-split: e36f219c81b6010d4aae423ba12f49edb5b6e31a

* Squashed 'frameworks/spec-kit/' content from commit 11f49ebf

git-subtree-dir: frameworks/spec-kit
git-subtree-split: 11f49ebfb2f6af55345cb4bd9a7906acd211e56f

* Squashed 'frameworks/gsd/' content from commit 304c1a13

git-subtree-dir: frameworks/gsd
git-subtree-split: 304c1a1302564c45af252bbba4bcc5350e7dac3a

* docs: update README for .github-private org infrastructure role

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* ci: add dependabot-automerge.yml workflow (#73)

Adds the required dependabot-automerge.yml workflow from the org
standard template (petry-projects/.github/standards/workflows/).
This is a thin caller stub that delegates to the org-level reusable
workflow.

Closes #48

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Don Petry <don-petry@users.noreply.github.com>

* fix(pr-review): unblock queue starvation from self-authored PRs (#96) (#97)

* fix(pr-review): unblock queue starvation from self-authored PRs (#96)

A self-authored PR sorted first in the candidate list and triggered
"Can not approve your own pull request" — which the session-fatal
abort treated like an engine error, skipping all 28 remaining PRs
on every run.

- list-prs.sh: filter out PRs authored by REVIEWER_USER at enumeration
- post-pr-review.sh: catch the GraphQL self-approval error and exit 100
  (no-op) instead of 1, so a stray self-PR can no longer abort the batch
- engine.sh: gh copilot renamed --target to --agent; fix the rubber-duck
  invocations so tier-2 cross-engine review works again
- review-one-pr.sh: stop appending a duplicate "0" to REVIEW_CYCLE when
  grep -c finds no markers under set -o pipefail (was breaking the
  cycle-cap integer comparison)

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

* fix(pr-review): authenticate as bot, harden enumeration, address review

- Workflow now runs as AGENT_USER (default don-petry-bot), distinct from
  REVIEWER_USER (the human, don-petry). The self-approval rejection that
  caused #96 came from the agent and the human sharing one identity.
- list-prs.sh filters self-authored PRs against AGENT_USER, and validates
  AGENT_USER against the GitHub username charset before interpolating into
  the jq filter (Copilot review feedback).
- review-one-pr.sh uses printf '%s\n' instead of echo for PR_BODIES, since
  PR body content is user-authored and could begin with -n/-e or contain
  backslash escapes (Copilot review feedback).
- AGENT.md guideline updated to reflect that self-authored PRs are
  intentionally excluded (CodeRabbit review feedback).

Operator note: rotate the GH_PAT_WORKFLOWS secret to a token owned by
don-petry-bot (with read:org added) for the bot-account behavior to take
effect at runtime.

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

* refactor(pr-review): collapse to single BOT_USER, route escalations via CODEOWNERS

Drop AGENT_USER and REVIEWER_USER. The workflow now has one identity:
BOT_USER (default don-petry-bot), which both owns the repos to scan and
gets filtered out as the self-approval blocker. Human escalation no
longer hard-codes a single reviewer; instead, scripts/request-codeowners-
review.sh parses CODEOWNERS in the PR's repo and requests review from
every @user / @org/team mention.

- list-prs.sh: BOT_USER for both gh-repo-list and self-author filter
- review-one-pr.sh, post-pr-review.sh: replace --user "$REVIEWER_USER"
  with the CODEOWNERS helper
- repair-pr-approvals.sh: same gh-repo-list switch
- pr-review.yml: drop AGENT_USER/REVIEWER_USER, set BOT_USER default
  to don-petry-bot
- AGENT.md: updated guideline to describe single-identity model

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

* refactor(pr-review): extract batch loop, hoist env, drop redundant step

Workflow YAML drops from 231 to 111 lines by moving the per-PR review
loop into scripts/review-batch.sh. The script can be syntax-checked and
exercised locally; the workflow now just wires env vars and dispatches.

- Hoist GH_TOKEN, MAX_PRS, CANDIDATE_LIMIT to job-level env (were
  repeated on individual steps).
- Inline `gh auth status` into the install step; remove the standalone
  Verify auth step (its only output was a one-line auth dump).
- Drop the `if: steps.list.outputs.count != '0'` guard and the step's
  `id`/output: review-batch.sh handles empty input as a no-op.
- Collapse the duplicated summary-string branch in the review loop into
  a single template with a conditional fragment.

No behavior change.

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

* perf(pr-review): cache claude-code CLI install across runs

`npm install -g @anthropic-ai/claude-code` ran on every workflow start
(~30s). Switch to a per-user npm prefix (~/.npm-global) and cache that
directory via actions/cache, keyed on CLAUDE_CODE_VERSION + runner OS.

A `command -v claude` guard makes the install a no-op on cache hit, so
the only cost on subsequent runs is the cache restore.

CLAUDE_CODE_VERSION defaults to 'latest' (cache persists until manually
flushed); set the repo variable to pin a specific version for fully
reproducible caching.

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

---------

Co-authored-by: Claude <noreply@anthropic.com>

* chore: rename GH_PAT_WORKFLOWS secret to DON_PETRY_BOT_GH_PAT (#99)

The previous name was generic and didn't tell you which account the PAT
belonged to. The new name makes the binding explicit: this secret is the
PAT owned by don-petry-bot, used as BOT_USER throughout the PR-review
workflows.

Operator follow-up before this can be merged:
- Add a new repo secret DON_PETRY_BOT_GH_PAT containing the bot's PAT
  (with repo, workflow, and read:org scopes).
- After merge, the old GH_PAT_WORKFLOWS secret can be deleted.

Affected workflows:
- pr-review.yml (1 use)
- claude.yml (4 uses, with || github.token fallback)
- daily-pr-review-health.yml (1 use)
- repair-pr-approvals.yml (1 use)

scripts/pr_review_health.sh's error message is updated to point at the
new name as well.

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

Co-authored-by: Claude <noreply@anthropic.com>

* chore: remove frameworks directory (#101)

Co-authored-by: Gemini CLI <gemini-cli@example.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore all erroneously deleted files (items 4-34) (#104)

* revert: restore .github/workflows/daily-pr-review-health.yml

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore .github/workflows/repair-pr-approvals.yml

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore agents/pr-reviewer.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/cascade-action.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/deep-review.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/rubber-duck.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/security-audit.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/shared.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/single-review.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/synthesize-duck.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/synthesize.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/triage.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/engine.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/list-prs.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/post-pr-review.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/pr_review_health.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/repair-pr-approvals.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/request-codeowners-review.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/review-batch.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/review-one-pr.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore templates/mention-listener.yml

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore AGENT.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore BOT_SETUP.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore DOCUMENTATION.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore IMPLEMENTATION.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore MACHINE_USER_SETUP.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore PR_REVIEW_AGENT_FAILURE_REPORT.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore PR_REVIEW_FAILURE_INVESTIGATION.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore SETUP.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore STATUS.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore README.md to pre-change state

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore .github/workflows/daily-pr-review-health.yml

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat: add Gemini Pro support and optimize PR review fallback chain (#102)

* Merge main and resolve conflicts

* Enhance pr-review workflow: add Gemini support and refactor fallback logic

* Address Copilot review comments: tighten regex, fix fallback summary, and align docs

* docs: move agent documentation to docs/ folder and rename files

* docs: align secret names and fix casing in index

* docs: organize pr-review-agent documentation under dedicated folder

* Address final PR comments: tighten regex, add preflight checks, fix bot names, and cleanup docs

---------

Co-authored-by: Gemini CLI <gemini-cli@example.com>

* fix(pr-review): gate rate-limit detection on non-zero exit code

Broad patterns like `plan.*limit` and `claude.*usage` could match content
in a successful triage summary, triggering a false-positive engine fallback.
Guard the check with TRIAGE_RC != 0 so it only fires when the provider
command actually failed.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(claude): sync inlined workflow with org standard (#109)

* fix(claude): sync inlined workflow with org standard

Brings the inlined claude.yml up to parity with
petry-projects/.github/.github/workflows/claude-code-reusable.yml.

Changes:
1. Bot allow list for pull_request_review_comment
   Add coderabbitai[bot], Copilot, copilot-pull-request-reviewer[bot],
   and gemini-code-assist[bot] alongside the existing OWNER/MEMBER/
   COLLABORATOR check. These bots have author_association 'NONE' so
   their review comments were always skipped.

2. check_run trigger + claude-ci-fix job
   Port the CI failure auto-fix feature from the reusable. When a check
   fails on a PR, Claude diagnoses and fixes it automatically.
   Adapted to use DON_PETRY_BOT_GH_PAT (this repo's secret name).

3. claude-code-action SHA bump: v1.0.89 → v1.0.119
   Update both the claude and claude-issue jobs.

4. paths-ignore on pull_request trigger
   Prevents the workflow from firing on PRs that only change
   claude.yml itself, avoiding the Anthropic OIDC validation
   failure (workflow file must match default branch at token exchange).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(claude): guard bot allow list against fork PRs

Same fix as petry-projects/.github PR #238 — add same-repo guard
for bot-triggered pull_request_review_comment runs.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(claude): address ci-fix self-loop and fork PR security

- Replace Claude Code name prefix check with explicit job name list to
  correctly prevent self-loops (check_run names for inlined workflows
  are bare job names, not workflow-prefixed)
- Add fork PR trust gate in Resolve PR number step: verify head repo
  matches target repo before running Claude with privileged credentials

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* ci: replace inlined claude.yml with standard thin-caller stub

The inlined version embedded all three jobs (claude, claude-ci-fix,
claude-issue) directly and threaded DON_PETRY_BOT_GH_PAT through
checkout tokens and github_token, causing the bot's PAT to author
PRs and comments instead of github-actions[bot].

Replace with the standard thin caller that delegates to the org-level
reusable workflow (claude-code-reusable.yml@v1) via secrets: inherit,
which is the correct pattern per:
petry-projects/.github/standards/workflows/claude.yml

* fix: replace gh copilot suggest with GitHub Models REST API (#151)

* fix: replace gh copilot suggest with GitHub Models REST API (#147)

The `gh copilot suggest -p "$(cat <file>)"` invocation failed with
"Invalid command format" because:
1. The `-p` flag is not valid syntax in modern `gh copilot` built-in versions.
2. `gh copilot suggest` is a shell-command suggestion tool; it does not
   accept large PR prompts or return structured JSON responses.
3. Passing a multi-thousand-line prompt via `$(cat ...)` can hit ARG_MAX.

The non-zero exit was then misclassified by the rate-limit detector, which
aborted the entire session and skipped all remaining PRs in the queue.

Fix: replace all three copilot invocations (run_triage, run_agentic,
run_duck) with a new `copilot_chat` helper that calls the GitHub Models
REST API directly via curl. The API is OpenAI-compatible, versioned via
`X-GitHub-Api-Version`, stable against gh CLI version changes, and accepts
arbitrary prompt sizes using python3 for safe JSON encoding.

Additional changes:
- Add `COPILOT_API_MODEL` var (default `openai/o4-mini`) overrideable via env.
- run_agentic/run_duck copilot cases now also write output to $OUTPUT_FILE
  so callers that check that path directly (deep review, audit) find the JSON.
- Rate-limit responses (HTTP 429) are echoed to stdout so the existing
  `is_rate_limited()` detector fires correctly for engine fallback.
- Add pre-flight smoke test in review-batch.sh: tests GitHub Models API
  connectivity with a one-liner prompt before processing any PRs, so auth
  or model errors surface immediately as a clear setup failure.
- Add tests/test_copilot_chat.sh: unit tests for the JSON payload builder
  with edge-case prompts (quotes, newlines, # headings, large diffs, Unicode).

Closes #147

Co-authored-by: Don Petry <don-petry@users.noreply.github.com>

* fix: scope COPILOT_API_MODEL export to copilot engine only

Move the `export COPILOT_API_MODEL` from the common exports block into the
`copilot)` case where the variable is set, so it is not exported as an
empty/unset variable when the engine is `claude` or `gemini`. The
`copilot_chat` fallback `${COPILOT_API_MODEL:-openai/o4-mini}` still works
correctly when `DUCK_ENGINE=copilot` under a non-copilot primary engine.

Co-authored-by: Don Petry <don-petry@users.noreply.github.com>

* fix: address review comments — temp file for payload, fail-fast source, streaming output

- copilot_chat: write JSON body to mktemp file, pass to curl as @file to
  avoid ARG_MAX for large PR diffs (was --data-binary "$body")
- copilot_chat: add :? guard on COPILOT_GITHUB_TOKEN for a clear error
  instead of generic "unbound variable" under set -u
- run_agentic / run_duck copilot paths: stream directly to stdout (and
  tee to OUTPUT_FILE when set) rather than buffering the full response
  into a shell variable, which forced large outputs into memory and
  stripped trailing newlines
- review-batch.sh pre-flight: fail fast if source engine.sh fails
  (was silently ignored with || true)
- review-batch.sh pre-flight: build smoke-test JSON payload via python3
  + temp file instead of shell string interpolation to avoid JSON
  injection if COPILOT_API_MODEL contains special characters
- Clarify that openai/o4-mini is the correct April-2025 o4-generation
  model name, not a typo for o1-mini or gpt-4o-mini

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Don Petry <don-petry@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* feat: prioritise .github/.github-private PRs, oldest-first within tier (#155)

* feat: prioritise .github/.github-private PRs, oldest-first within tier

- list-prs.sh: add createdAt to JSON fetch; emit priority|createdAt|url
  lines (priority 0 for .github/.github-private, 1 for everything else)
- Replace final sort -u with a two-pass sort: deduplicate by URL, then
  sort by priority asc then createdAt asc; strip sort keys with cut
- tests/test_list_prs_sort.sh: 16 tests covering priority classification,
  oldest-first ordering, mixed scenarios and deduplication
- .github/workflows/test.yml: run both unit-test files on every PR / push

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* test: add dedup edge case for same URL with conflicting priorities

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: remove temperature from copilot_chat payload (o4-mini incompatible)

The GitHub Models API rejects temperature=0 for reasoning models like
o4-mini with HTTP 400:
  'Unsupported value: temperature does not support 0 with this model.
   Only the default (1) value is supported.'

This caused the rubber duck (DUCK_ENGINE=copilot / DUCK_MODEL=o4-mini)
to fail with HTTP 400 on every claude-engine run, silently degrading
every review to deep-only with no cross-engine sanity check.

Fix: remove temperature from the copilot_chat JSON payload entirely —
the API defaults to 1, which is the only supported value for o4-mini.

Also:
- Update tests/test_copilot_chat.sh: sync build_payload to match and
  flip Test 9 to assert temperature is ABSENT (not 0).
- Add .github/workflows/test.yml: run unit tests on every PR and push
  to main so regressions are caught before merge.

Reproducer: job/75690347409 — duck failure logged as:
  copilot_chat: HTTP 400 from GitHub Models API
  {"error":{"message":"Unsupported value: 'temperature' does not
   support 0 with this model..."}}

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: address PR review comments

- list-prs.sh: tighten priority regex to /[.]github(-private)?/pull/
  so path-boundary anchor prevents false positives (e.g. foo.github)
- list-prs.sh: replace printf pipeline with here-string to avoid ARG_MAX
  limits on large PR sets
- test.yml: add permissions: contents: read (least-privilege, CodeQL fix)
- test.yml: add timeout-minutes: 5 to prevent hung runs
- test_list_prs_sort.sh: sync sort_entries helper (here-string + || true)
  and classify regex to match updated list-prs.sh exactly

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: don-petry <don@petry.dev>

* fix: restore pr-review.yml content (accidentally emptied in revert)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: restore pr-review.yml (file was accidentally emptied)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore(deps): bump actions/cache from 4 to 5 (#165)

Bumps [actions/cache](https://github.com/actions/cache) from 4 to 5.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): bump petry-projects/.github/.github/workflows/claude-code-reusable.yml (#164)

Bumps [petry-projects/.github/.github/workflows/claude-code-reusable.yml](https://github.com/petry-projects/.github) from 1 to 2.
- [Commits](https://github.com/petry-projects/.github/compare/v1...v2)

---
updated-dependencies:
- dependency-name: petry-projects/.github/.github/workflows/claude-code-reusable.yml
  dependency-version: '2'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* fix(list-prs): search DELEGATION_ORGS and drop --checks success pre-filter

- Add iteration over all orgs in DELEGATION_ORGS (e.g. don-petry) so PRs
  in those orgs enter the candidate pool. Previously only BOT_USER and
  TARGET_ORG were searched.
- Remove --checks success from org repo searches. GitHub excludes PRs from
  repos with no CI configured when this flag is used, silently dropping all
  PRs from repos like .github, .github-private, google-app-scripts, and
  TalkTerm. review-one-pr.sh already enforces CI gating per-PR and treats
  empty statusCheckRollup as passing, so the pre-filter is redundant and
  harmful.
- Add --limit 200 to gh repo list calls to handle org growth beyond gh's
  default 30-repo cap.

* feat: add code-quality ruleset (compliance fix #60) (#86)

Creates the required `code-quality` repository ruleset enforcing required
status checks on the default branch, as mandated by the org standard:
standards/github-settings.md#code-quality--required-checks-ruleset-all-repositories

Required checks:
- SonarCloud (code quality analysis)
- CodeQL (SAST)
- agent-shield / AgentShield (agent security scan)
- dependency-audit / Detect ecosystems (dependency vulnerability scan)

Bypass actors:
- OrganizationAdmin (always) — emergency override
- dependabot-automerge-petry Integration (always) — Dependabot auto-merge

The ruleset was applied directly via GitHub API. This file documents the
configuration as code for auditability and future reapplication.

Closes #60

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Don Petry <don-petry@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* feat(prompts/dev-lead): add human prompt template

* feat(prompts/dev-lead): add human-pr prompt template

* feat(dev-lead): implement dev-lead agent Phases 0-6

* fix: per-PR isolation and single-review retry (closes #132)

- review-batch.sh: non-rate-limit per-PR failures (exit code 1) no
  longer abort the session. SESSION ABORTED EARLY is now reserved for
  the rate-limit-on-fallback-engine case (exit code 2) only. All other
  failures are counted and logged; remaining candidates continue.

- review-one-pr.sh: single-review step retries up to
  SINGLE_REVIEW_MAX_RETRIES (default 2) times with a
  SINGLE_REVIEW_RETRY_DELAY_SEC (default 15s) gap before giving up.
  On exhaustion, the PR is flagged needs-human-review and the script
  exits with code 1, which the updated batch treats as a non-fatal
  per-PR failure. Raw model output and stderr are logged on each failed
  attempt for post-mortem visibility.

Root cause of run #25707852006: claude-opus-4-7 returned a verbose
non-JSON response for PR #129; the old code treated that as fatal and
skipped 35 remaining candidates.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix: rate-limit detection and per-attempt stderr logs in single-review retry

Address inline review comments on PR #133:

- Rate-limit check: after each run_agentic call, inspect both stdout
  (VERDICT_JSON.raw) and stderr (SINGLE_LOG) with is_rate_limited before
  retrying. A rate-limit match exits immediately with code 2 so
  review-batch.sh can trigger engine fallback — consistent with triage and
  deep-review tiers. Previously a rate-limited single-review would burn all
  retries and exit 1 (per-PR failure), silently leaving the batch on the
  same rate-limited engine for all remaining PRs.

- Per-attempt log files: stderr is now written to
  single-review-attempt-N.log rather than a single overwritten file, so
  no earlier-attempt errors are lost. Each attempt logs its own stderr
  inline on failure; the fallback path cats all attempt logs for
  post-mortem visibility.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* feat(dev-lead): Phase 0 test infrastructure and Phase 1 intent stub

Phase 0: full test harness for the dev-lead agent —
  26 event fixtures (all valid JSON with _test_expected_intent),
  stub claude/gemini engines, mock gh binary, CI failure log sample,
  bats helpers (stub-engine, mock-gh, assert-env, prompt-vars),
  7 prompt templates with VARIABLES declarations, preflight script,
  prompt coverage integration test, and test-dev-lead.yml CI workflow.

Phase 1: dev-lead.yml trigger workflow (all 7 event types, dispatch + ci-relay
  jobs) and dev-lead-intent.sh stub (anti-loop guard live; all other events
  emit skip/not-implemented).  14/14 bats unit tests pass.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): add permissions blocks to test workflow jobs (CodeQL)

* fix(ci): auto-fix for lint / eslint [skip ci-relay]

* feat(dev-lead): Phases 2-6 — CI fix, review fix, issue, engine fallback

Phase 2: run_writer/run_writer_with_fallback in engine.sh, full intent
routing in dev-lead-intent.sh, dev-lead-fix-ci.sh handler, workflow wiring.

Phase 3: dev-lead-fix-reviews.sh handles fix-reviews, fix-bot-comment,
human, human-pr, rebase intents. Full review routing in intent classifier.

Phase 5: dev-lead-fix-issue.sh handles the issue intent with dedup guard
and branch/PR creation.

Phase 6: run_writer_with_fallback with claude→gemini→copilot fallback chain.

Tests: 77 unit tests across 8 new .bats files, all passing. Updated Phase 1
stub tests to reflect actual Phase 2+ routing behavior.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): address review comments — dispatch JSON, dead code, portable stub gh

* fix(dev-lead): pre-flight after intent, fork URL check, review-batch exit code, retry comment

* fix(dev-lead): install bats-core from GitHub to avoid root requirement

* fix(security): move event values to env vars to prevent script injection (SonarCloud)

* fix(security): move CLAUDE_CODE_VERSION to env block (SonarCloud script injection)

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* feat(dev-lead): Phase 1.5 — reusable workflow for cross-repo adoption + Phase 7 shadow period

* feat(dev-lead): add reusable workflow (Phase 1.5) and begin shadow period (Phase 7)

- Create .github/workflows/dev-lead-reusable.yml: workflow_call entry
  point for other repos. Checks out .github-private scripts/prompts into
  .dev-lead/, then runs the same intent-classify + handler pipeline as
  dev-lead.yml with PROMPTS_DIR=.dev-lead/prompts/dev-lead.
- Add PROMPTS_DIR env-var support to dev-lead-fix-ci.sh,
  dev-lead-fix-reviews.sh, and dev-lead-fix-issue.sh so the reusable
  workflow can point scripts at the sparse-checkout path without changing
  CWD. Defaults to prompts/dev-lead (backwards-compatible for dev-lead.yml).
- Annotate dev-lead.yml with Phase 7 shadow-period window (2026-05-15
  through ~2026-05-29): claude.yml and dev-lead.yml run in parallel until
  regressions clear, then claude.yml is removed.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* chore(dev-lead): update shadow-period tracking issue ref to #180

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): document PAT requirement and pin ref:main on private checkout

* fix(dev-lead): P1 review fixes — ci-relay in reusable, .dev-lead gitignore, export PROMPTS_DIR, trim header

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): rate-limit detection, separate status, and retry cron (#192) (#196)

* chore(dev-lead): decommission claude.yml, bump ACTION_TIMEOUT_SEC to 600s

- Delete .github/workflows/claude.yml — replaced by dev-lead.yml (shadow
  period complete, tracking issue #180).
- scripts/engine.sh: raise ACTION_TIMEOUT_SEC default 300→600s to reduce
  timeout failures on large-repo fix-ci runs (PR #80 had 3 timeouts).
- AGENTS.md: remove claude.yml immutability exemption; note dev-lead.yml
  as the active AI automation workflow and its edit-via-reusable pattern.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* refactor: replace Claude analysis with pure gh/jq telemetry in health check

* refactor: drop Node/Claude steps from daily-pr-review-health workflow

* rename: daily-pr-review-health → actions-fleet-monitor

* rename: daily-pr-review-health → actions-fleet-monitor

* fix(agents-md): clarify dev-lead.yml vs dev-lead-reusable.yml scope

dev-lead.yml in .github-private runs inline steps (not a caller stub).
Behavior changes for this repo go to dev-lead.yml directly; changes that
affect all org repos via the cross-repo reusable go to dev-lead-reusable.yml.

Addresses Copilot review on PR #194.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): rate-limit detection, separate status, and retry cron

Closes #192. Implements all phases from the revised plan.

**Phase 0 — fix run_writer stdout capture (prerequisite)**
- engine.sh: capture stdout via `tee` to a tempfile so `is_rate_limited`
  can inspect the output; old code read `/tmp/dev-lead-writer-stderr`
  which was never written (claude --print outputs to stdout, not stderr).
  Fallback engines were never tried in practice.
- engine.sh: add `parse_reset_time` to extract ISO timestamp from
  `resets H:MMpm (UTC)` in engine output and write it to
  `/tmp/dev-lead-rate-limit-reset` for callers to embed in markers.

**Phase 1 — separate status=rate-limited from status=failed**
- fix-ci.sh: detect engine exit 2 → post `status=rate-limited` (not
  `status=failed`); embed parsed reset time in marker body; exit 2.
- fix-ci.sh: fix `check_idempotency` to treat `status=rate-limited`
  as retriable — only block on terminal statuses (applied, failed,
  no-changes). Previously, rate-limited markers blocked all retries.
- fix-ci.sh: `count_recent_failures` already filtered to `status=failed`;
  add explicit comment confirming rate-limited markers are excluded.
- fix-ci.sh: add `has_rate_limited_marker` dedup check so the same SHA
  never accumulates more than one rate-limited comment.
- fix-reviews.sh: on engine exit 2 for all five intent types
  (fix-reviews, fix-bot-comment, human, human-pr, rebase), post a
  `status=rate-limited` marker with embedded reset time and exit 2.
- fix-reviews.sh: for `human` and `human-pr` intents, also post a
  user-visible acknowledgment comment so users know their request was
  received and will be retried.

**Phase 2 — scheduled retry cron for fix-ci**
- dev-lead-retry.sh: new script scans all open PRs across TARGET_ORG
  for `status=rate-limited` markers on current HEAD SHA; skips PRs
  whose reset time is still in the future; staggered dispatches (30s
  between repos) to prevent cascading org-level rate-limit hits.
- dev-lead-retry.yml: new workflow — scheduled every 2 hours (dual
  offsets to work around GitHub scheduler skew), plus workflow_dispatch.

**Phase 3 — retry for fix-reviews intents (same cron)**
- dev-lead-retry.sh already handles all five fix-reviews intent types
  (fix-reviews, fix-bot-comment, human, human-pr, rebase) via
  `dev-lead-reviews-retry` dispatch events.
- dev-lead-intent.sh: add `dev-lead-reviews-retry` dispatch type routing
  to the intent classifier — maps intent_type from payload to the correct
  existing fix-reviews step.
- dev-lead.yml: add `dev-lead-reviews-retry` to repository_dispatch types.
- dev-lead.yml: unify concurrency group for all dispatch types to
  `dev-lead-pr-{pr_number}` to avoid per-type slot fragmentation.

**Tests**
- test_engine_writer.bats: 8 new tests covering rate-limit stdout detection,
  exit-2 mapping, fallback exhaustion, reset time parsing.
- test_fix_ci.bats: 5 new tests covering rate-limited status, exhaustion
  exclusion, idempotency pass-through, and dedup.
- test_fix_reviews.bats: 5 new tests covering all intent types + human ack.
- test_intent_ci.bats: 4 new tests for dev-lead-reviews-retry dispatch routing.
- e2e/scenarios/07-rate-limit-retry.sh: new E2E scenario covering all parts.
- 3 new event fixtures for repository_dispatch_reviews_retry_* payloads.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): address review comments on #196

Addresses all 18 review threads from Copilot, Codex, and Gemini.

**P1 bugs fixed:**
- dev-lead-retry.sh: all log echo calls in scan_pr_for_rate_limits and
  dispatch_* functions now write to stderr; only the final count is on
  stdout. Previously, log lines polluted the command substitution,
  causing arithmetic expansion to fail and aborting the scan on the
  first retry candidate.
- dev-lead-retry.yml: permissions: contents was read-only; creating
  repository_dispatch events requires contents: write.

**Correctness fixes:**
- dev-lead-retry.sh: restrict automated retries to intents whose context
  can be reconstructed at runtime (fix-reviews, human-pr, rebase). human
  and fix-bot-comment require USER_INSTRUCTION/COMMENT_BODY from the
  original event which cannot be re-fetched; they are explicitly excluded.
- dev-lead-fix-reviews.sh: resolve HEAD_SHA from the PR API when not
  provided by the triggering event (issue_comment intents carry no SHA).
  Without this, rate-limited markers had no sha= field and were
  invisible to the retry scanner.
- dev-lead-fix-reviews.sh: write terminal status=applied marker after
  successful fix-reviews, human-pr, rebase runs. Prevents the retry
  cron from re-dispatching the same intent on every subsequent tick when
  the SHA hasn't changed.
- dev-lead-retry.sh: check for reviews terminal marker before dispatching
  a retry (mirrors the existing fix-ci terminal-marker check).
- dev-lead-retry.sh: look up current check-run details (details_url, id)
  at dispatch time via the commits check-runs API, so retried fix-ci runs
  have full failure logs and annotations rather than empty fields.
- dev-lead-fix-ci.sh: embed check= field in the rate-limited marker so
  the retry cron knows which check run to look up.
- dev-lead-fix-reviews.sh: human intent now posts a "please re-mention
  @dev-lead" ack instead of "I'll retry automatically" since it will NOT
  be retried automatically. human-pr keeps the auto-retry ack (correct).

**Pagination (Copilot + Gemini):**
- dev-lead-fix-ci.sh: add --paginate to check_idempotency, count_recent_
  failures, has_rate_limited_marker.
- dev-lead-fix-reviews.sh: add --paginate to has_reviews_rate_limited_marker.
- dev-lead-retry.sh: add --paginate to PR comments and PR list fetches.

**Other improvements:**
- dev-lead-retry.yml: simplify to single cron schedule (15 */2 * * *);
  remove BOT_USER env var (unused in the script).
- dev-lead-retry.sh: raise repo list limit 200 → 1000; add hard-error
  when list_repos_for_org returns empty (surfaces token permission issues
  rather than silently scanning 0 repos).
- dev-lead-intent.sh: use jq -nc (compact, null-input) for context JSON
  construction — safe against values with special characters.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(pr-review): handle Gemini trust-mode failure and properly route engine fallbacks (#217)

* fix(pr-review): handle Gemini trust-mode failure and properly route engine fallbacks

Closes #208

* fix(ci): install gemini CLI and set trust mode for dev-lead agents

---------

Co-authored-by: Gemini CLI <gemini-cli@example.com>

* fix: resolve YAML syntax error in dev-lead-reusable workflow

* fix: address automated feedback from PR 217 (#219)

This addresses missed feedback from CodeRabbit (removing syntax errors in YAML) and gemini-code-assist (improving string concatenation in validate-engines.sh), and adds assertion output checks to tests.

Co-authored-by: Gemini CLI <gemini-cli@example.com>

* chore: remove Phase 7 shadow period comment from dev-lead.yml

* feat: skip PRs with CHANGES_REQUESTED review (+ FORCE_REVIEW and stale-review guards)

* feat: skip PRs with CHANGES_REQUESTED review decision

Adds an early-exit check alongside the existing CI guards so the cascade
skips any PR where a human reviewer has already requested changes.
Reviewing those PRs before the author responds wastes tokens and creates
confusing parallel feedback threads.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix: honor FORCE_REVIEW and skip only stale-free CHANGES_REQUESTED reviews

Two issues addressed from Codex P2 feedback:

1. FORCE_REVIEW bypass: mention-triggered and force_review=true runs now
   bypass the CHANGES_REQUESTED skip, so authors can explicitly request a
   re-review after addressing feedback.

2. Stale review guard: only skip when a CHANGES_REQUESTED review targets
   the current head SHA. On repos without automatic stale-review dismissal,
   reviewDecision stays CHANGES_REQUESTED after new commits — the cascade
   now re-engages when the author has pushed new work.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): resolve context loss, missing checkout, and push logic (#223)

* feat: skip PRs with CHANGES_REQUESTED review decision

Adds an early-exit check alongside the existing CI guards so the cascade
skips any PR where a human reviewer has already requested changes.
Reviewing those PRs before the author responds wastes tokens and creates
confusing parallel feedback threads.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix: honor FORCE_REVIEW and skip only stale-free CHANGES_REQUESTED reviews

Two issues addressed from Codex P2 feedback:

1. FORCE_REVIEW bypass: mention-triggered and force_review=true runs now
   bypass the CHANGES_REQUESTED skip, so authors can explicitly request a
   re-review after addressing feedback.

2. Stale review guard: only skip when a CHANGES_REQUESTED review targets
   the current head SHA. On repos without automatic stale-review dismissal,
   reviewDecision stays CHANGES_REQUESTED after new commits — the cascade
   now re-engages when the author has pushed new work.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): resolve context loss, missing checkout, and push logic

- dev-lead-intent.sh: include actor and body in INTENT_CONTEXT for comments
- dev-lead.yml: parse and pass INTENT_ACTOR and INTENT_COMMENT_BODY to agent
- dev-lead-fix-reviews.sh: perform PR checkout, git push, and post summary comments

* fix(dev-lead): exit with 0 on no-changes in fix-reviews and human-pr

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* test: bypass CI check

* fix(pr-review): remove CI bypass hack

* fix(dev-lead): use heredoc for multiline environment variables (#224)

* fix(dev-lead): use heredoc for multiline environment variables

* fix(dev-lead): ensure all engines are installed and context is consistent

* fix(dev-lead): resolve stale model name in engine fallbacks

* fix(dev-lead): improve fallback reliability and script quality

- engine.sh: resolve stale model names in fallback loop; use -latest Gemini aliases
- dev-lead-fix-reviews.sh: remove unused code and fix PR_URL export

* fix(dev-lead): resolve Gemini model names and shell lint warnings

* security(dev-lead): use random heredoc delimiter to prevent inject…
don-petry added a commit that referenced this pull request Aug 7, 2026
This addresses missed feedback from CodeRabbit (removing syntax errors in YAML) and gemini-code-assist (improving string concatenation in validate-engines.sh), and adds assertion output checks to tests.

Co-authored-by: Gemini CLI <gemini-cli@example.com>
don-petry added a commit that referenced this pull request Aug 7, 2026
… trigger for the Class-2 timer changes, before narrowing them (#1420)

* add: workflow to fix stuck PRs using bot account token

Allows running the cleanup script via workflow_dispatch with access to
GH_PAT (bot account token) from repo secrets.

* fix: pass GH_PAT to checkout action so workflow uses bot account

* debug: simplify account check, add authentication debug output

* docs: add comprehensive petry-review-bot setup instructions

* docs: add GitHub App setup guide (recommended approach)

GitHub App is the secure, recommended way to automate PR reviews:
- Fine-grained permissions
- JWT tokens that auto-expire
- No human account needed
- Better audit trail
- GitHub's recommended approach

Replaces the bot user account approach with a more secure alternative.

* docs: add comprehensive GitHub App setup documentation

- SETUP.md: Quick reference guide with configuration and troubleshooting
- IMPLEMENTATION.md: Technical deep dive on architecture and design decisions
- DOCUMENTATION.md: Index of all documentation files
- Updated README.md with status and quick links
- Updated GITHUB_APP_SETUP.md with implementation notes and actual app ID
- Workflows use GitHub App token generation instead of static PATs

Covers the transition from bot user account to GitHub App authentication for improved security and maintainability.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: silence auth check in fix-stuck-prs when using GitHub App token

GitHub App tokens don't have user scope, so 'gh api user' returns 403.
This is fine - the script still works for PR operations. Suppress the error
so the script completes successfully with app-token fallback label.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: use explicit author instead of @me in stuck PR search

GitHub App tokens don't have user identity, so @me search doesn't work.
Use explicit 'don-petry' author instead to find PRs to fix.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: avoid subshell in while loop to preserve variable state

Using pipe in while loop created subshell where PROBLEM_PRS and FIXED_PRS
counters were incremented but changes didn't persist to parent shell.
Fixed by using process substitution for input instead of pipe.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* docs: add comprehensive status report for GitHub App setup

- Overview of completed work
- GitHub App authentication details
- 24 stuck PRs successfully fixed with approval reviews
- Architectural decisions and rationale
- Configuration guide and usage examples
- Known limitations and troubleshooting

System is now fully operational with GitHub App token authentication,
comprehensive documentation, and all infrastructure in place.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* docs: document stuck PR cleanup status and script fixes

Added documentation for:
- GitHub App token compatibility fixes in fix-stuck-prs.sh
  * Changed @me to explicit author (app tokens have no identity)
  * Fixed subshell variable scope (process substitution vs pipe)
  * Silenced expected 403 auth check error
- Current status: 24 PRs have approvals but remain OPEN
  * Auto-merge failed due to missing GitHub App permission
  * Approvals satisfy branch protection requirement
  * Next: expand permissions or manually merge

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: update list-prs.sh to search all repos by owner instead of @me

GitHub App tokens don't have user identity, so @me/@review-requested searches
fail. Changed to enumerate all repos in don-petry and petry-projects, then
search for open PRs within each repo. This covers the full scope:
- All open PRs in personal don-petry repos
- All open PRs in petry-projects org repos

Resolves 6 consecutive workflow failures due to zero PRs being enumerated.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* docs: add investigation report for PR review agent workflow failures

* fix: move env section before steps in workflow to fix YAML structure

GitHub Actions requires env section to come before steps in job definition.
Moving env definition up and setting GH_TOKEN only in steps that need it.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* script: add backfill-approvals to retroactively apply real approvals

Converts PRs with agent approval comments (but no real GitHub approvals)
to have actual APPROVED reviews. Needed for PRs reviewed before the
gh pr review --approve fix was applied.

Usage:
  scripts/backfill-approvals.sh          # dry-run (preview changes)
  scripts/backfill-approvals.sh false    # apply real approvals

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* feat: add backfill-approvals workflow to run as GitHub App bot

Runs backfill-approvals.sh via GitHub Actions so the approvals are posted
by the bot identity, not the PR author — GitHub rejects self-approvals.

Also fixes subshell counter bug in the script (piped while loop lost variable
state; switched to process substitution).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: convert inner while loop to process substitution to preserve counters

Both the outer repo loop and inner PR loop were piped subshells, causing
all counter increments (approved/skipped/failed) to be lost. The summary
always showed 0. Fixed by using < <(...) process substitution for both loops.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* car-hunt: add VIN-deep-dive flow, printable checklists, location-tier ranking

- Step 5.5 pre-test-drive flow: NHTSA VIN decode + per-VIN unrepaired-recall
  automation via Chrome MCP (Honda LWC owner portal documented as not
  automatable; NHTSA web form works).
- §F printable test-drive checklist generator (reportlab/Platypus PDF) with
  cold-start, model-specific red flags, negotiation table, walk-away rules.
- §G fraud / paperwork verification checklist (vehicle-agnostic, reusable):
  walk-away triggers, VIN three-location match, curbstoning, odometer fraud,
  bill of sale, title transfer, payment protection, stolen-vehicle, title-
  jumping, after-purchase steps.
- Step 5.25 head-to-head comparison template with information-asymmetry rule.
- Step 3 Location Quality Tier scoring (Birmingham AL metro table A/B/C/D)
  with adj-CPM modifier; sheet schema gains Adj.CPM + Tier columns.
- Hard-disqualification rules added: non-running engine keywords, mileage
  inconsistency, mandatory description scrape.
- Drive MCP overwrite limitation documented; sheet ID now read from memory
  rather than hardcoded.
- FB Marketplace operational facts: Birmingham AL city ID 107739635926718,
  /search? vs /vehicles? query handling, React-controlled inline composer,
  send-button selector, seller-name extraction regex.
- scheduled-tasks/used-car-search-{morning,afternoon}: tier A/B priority
  flag, sheet ID read from memory.
- Reference PDF generator scripts checked in for reuse.

* fix: re-approve PRs where approval predates last commit (stale approval)

Instead of skipping PRs that already have any APPROVED review, skip only
those where reviewDecision is not REVIEW_REQUIRED. This handles PRs where
a bot approval was posted before a new commit was pushed — GitHub's ruleset
engine treats those approvals as stale even with dismiss_stale_reviews=false.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: robustly extract JSON verdict from mixed claude --print output (#17)

Claude's --print mode can prefix the JSON verdict with conversational
preamble text, causing jq to fail with parse errors. Add
extract_verdict_json to engine.sh and wire all three cascade action
call sites in review-one-pr.sh to use it.

Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: extract_verdict_json — check dest file first (agent Bash-write style)

The cascade-action agent writes its verdict JSON to \$OUTPUT_FILE via a
Bash tool call, then prints a text summary to stdout. The previous fix
only scanned stdout (the .raw file), which contained no JSON. The agent-
written file at \$dest (= \$OUTPUT_FILE) was already correct — just not
checked. Now check \$dest first before falling back to stdout scan.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Enforce MAX_REVIEW_CYCLES cap before running the cascade (#18)

* fix: enforce MAX_REVIEW_CYCLES cap with human-escalation marker

Previously MAX_REVIEW_CYCLES only gated AI delegation in
post-pr-review.sh — the cascade itself ran on every cron tick
regardless of how many cycles had accumulated. Real-world result:
a PR could rack up 9+ review cycles (we observed exactly that on
ContentTwin#100) before any cap took effect.

Add a pre-cascade check in review-one-pr.sh: when the count of
existing `<!-- pr-review-agent v1 sha=... -->` markers is at or
above MAX_REVIEW_CYCLES (default 3), post one escalation comment
marked `<!-- pr-review-agent escalation -->`, label
needs-human-review, request don-petry, and exit 100 (skip
sentinel — doesn't burn the MAX_PRS budget). The escalation
marker doubles as the no-spam guard: subsequent runs detect it
and exit 100 immediately.

Also reuse a single `gh pr view` for both the cycle count and the
escalation-marker check.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* review feedback: surface escalation-comment failures, drop dead unset

- Drop `2>/dev/null || true` on `gh pr comment` so a failed escalation
  post is visible in the workflow log instead of silently leaving the
  PR with no marker (which would re-trigger the cap path next tick).
- Reword the cycle-count comment from "AI delegation loops" to "review
  loops" — the cap also catches cascade-only loops where every cycle
  approves and a new commit lands before merge.
- Remove the redundant `unset PR_BODIES` — never exported, no leakage.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>

* Reliability hardening: session circuit breaker, timeouts, retry, dedup (#20)

* feat: session circuit breaker, per-tier timeouts, retry, and triage hard-fail

Reliability hardening for the PR review agent.

1. Session circuit breaker (.github/workflows/pr-review.yml): on any
   non-zero, non-100 exit from review-one-pr.sh (general failure or rate
   limit on the fallback engine), break the per-PR loop, log a clear
   error annotation naming the failing PR and reason, and exit the step
   with code 1 so the run shows red. Prevents one systemic problem from
   silently burning the entire candidate pool.

2. Per-tier timeouts (scripts/engine.sh): triage/deep/audit/action/duck
   each get their own bounded timeout (180/600/600/300/300s defaults,
   env-overridable). Previously only the duck had a timeout — a hung
   tier could burn the whole 60min job budget.

3. Retry-with-backoff on transient errors (scripts/engine.sh): triage
   retries once on 124/137/143 (timeout / signal kill) since its caller
   captures stdout via $(...) so retries are safe. Deliberately NOT
   applied to run_agentic/run_duck where stdout is redirected to a file
   — a retry there would corrupt the partial first-attempt output.

4. Triage non-JSON now hard-fails (scripts/review-one-pr.sh): replaces
   the silent fallback that synthesized a fake "escalate=MEDIUM" verdict
   and proceeded to deep review. With the new circuit breaker, loud
   failure is the right call — masking a broken triage was burning
   tokens on every PR while the workflow looked healthy.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix: stop stacking duplicate agent reviews on the same PR

Two bugs were causing the agent to leave multiple comments on the same PR.
Together they produced 10 stacked APPROVED reviews on petry-projects/ContentTwin#100.

Bug A — idempotency check is order-dependent (review-one-pr.sh):
The previous marker-discovery code did:
  ((.reviews // []) + (.comments // [])) | .[].body | grep marker | tail -1
This relies on the array concatenation order, not chronological order. When
old agent comments existed alongside newer agent reviews, tail -1 picked
the comment-array marker (older) over the review-array marker (newer),
causing the script to think the head SHA hadn't been reviewed and re-run.
Replaced with a single jq pipeline that tags each item with submittedAt /
createdAt, sorts by timestamp, and takes the actual most-recent marker.

Bug B — no cleanup of prior agent items (post-pr-review.sh):
After successfully posting a new review/comment, prior agent items were
left in place, accumulating forever. Added mark_prior_agent_items_obsolete
which, after a successful post:
  - dismisses prior APPROVED/COMMENTED/CHANGES_REQUESTED agent reviews via
    the GitHub dismissal API (UI shows them struck-through as Dismissed)
  - edits prior agent comments to wrap their body in a collapsed <details>
    block with a "Superseded by re-review at <SHA>" summary, plus a
    `<!-- pr-review-agent superseded -->` sentinel for idempotency

All cleanup API calls are best-effort — failures don't break the workflow.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(cleanup): warn on API failures, preserve global newest, file-stage JSON

Three fixes to mark_prior_agent_items_obsolete from the review of PR #20:

1. ::warning:: annotations on every cleanup API failure (review/comment
   list-fetch, individual review dismissal, individual comment fetch+edit).
   Previously these were silenced with `|| true`, so a permissions change
   on the dismissal endpoint would let duplicates stack indefinitely with
   no signal in the Actions UI. Cleanup is still non-fatal — the new post
   has already landed — but failures are now visible.

2. Preserve the globally-latest agent item across BOTH categories, not the
   newest of each category separately. The earlier code split reviews and
   comments and applied `[:-1]` to each, which left a stale fix-request
   comment in place when the new post was a review (or vice versa). The
   one-off cleanup of ContentTwin#100 hit exactly this case: 12 stacked
   reviews collapsed to 1, but a stale comment from 2026-04-25 (SHA
   cd9132d6) was preserved as "newest comment" even though the latest
   review at SHA 3af8c8ee was newer overall. Now: compute the max
   timestamp across both feeds, exclude items at that timestamp.

3. Stage API responses to disk (`mktemp` + `jq <file>`) instead of routing
   through `--argjson "$var"`. The old approach broke on rare unescaped
   control chars in user-authored comment bodies (jq refused to parse the
   resulting shell-vared JSON). File-based input sidesteps the shell
   pipeline entirely.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>

* Remove car-hunt skill and scheduled tasks (moved to don-petry/don-petry) (#21)

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: apply Copilot fallback hardening and reuse GH_PAT for Copilot auth

- Use secrets.GH_PAT (existing personal account token with Copilot
  subscription) instead of a separate COPILOT_GITHUB_TOKEN secret
- Pin actions/create-github-app-token to SHA (v3.1.1) for supply chain safety
- Add post-install verification for gh-copilot extension with warning on failure
- Guard Copilot fallback path: skip PR gracefully if extension is not installed

Addresses issue #24 workflow fixes (1-3) that could not land via PR #25
due to GitHub App lacking workflows permission.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: switch Copilot engine to gh built-in and fix app-id deprecation

- Remove gh extension install (github/gh-copilot conflicts with built-in alias)
- Replace with gh copilot --version check using COPILOT_GITHUB_TOKEN (GH_PAT)
- Update all engine.sh copilot invocations from bare `copilot` binary to
  `gh copilot suggest --target shell` with GH_TOKEN overridden to GH_PAT
  so the user token (with Copilot subscription) is used, not the App token
- Fix actions/create-github-app-token: app-id → client-id (same secret value,
  just the renamed input in v3.x)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: harden Copilot fallback path and remove stale worktrees (#25)

- gh-copilot install: add --force flag and post-install verification
  warning so silent failures due to built-in alias conflict are visible
  in workflow logs instead of being silently swallowed by `|| true`
- fallback pre-flight check: verify `gh extension list | grep copilot`
  before switching to Copilot engine; if unavailable, skip the PR and
  continue the batch rather than session-aborting and dropping all
  remaining candidates (fixes the 27-PR drop on run #503)
- pin actions/create-github-app-token to SHA for v3.1.1 (Node.js 24
  compatible) to prevent hard break on 2026-06-02 when GitHub forces
  Node.js 24 as default runtime
- remove 6 stale Claude Code worktrees from git tracking; add
  .gitignore entry to prevent future worktrees from being committed
  (eliminates exit-128 warning on every actions/checkout post-job sweep)

Closes #24

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: petry-projects-pr-review-agent[bot] <petry-projects-pr-review-agent[bot]@users.noreply.github.com>

* ci: add pre-flight dedup check to prevent duplicate claude-issue PRs (#26)

Before invoking Claude on a labeled issue, a new shell step queries
for any open PR with a branch matching claude/issue-NNN-* (or a body
containing "Closes #NNN"). If one is found it posts a comment on the
issue pointing to the existing PR and skips the Claude step entirely.
A secondary prompt instruction tells Claude to check first and push to
the existing branch rather than opening a new PR.

Motivated by issue petry-projects/google-app-scripts#171, where the
claude label was re-applied three times and each run created a fresh
PR unaware of the prior attempts.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor: migrate to machine user PAT auth (closes #27) (#28)

* refactor: migrate from GitHub App to machine user PAT auth

Closes #27

GitHub Apps cannot be listed in CODEOWNERS, blocking PRs in repos with
require_code_owner_review: true. Switch all workflows to use a machine
user account's fine-grained PAT (DON_PETRY_BOT_GH_PAT secret), which can
join an org team listed in CODEOWNERS.

Workflows: removed actions/create-github-app-token steps in pr-review,
fix-stuck-prs, backfill-approvals, and daily-pr-review-health. All now
use secrets.DON_PETRY_BOT_GH_PAT directly.

Docs: renamed GITHUB_APP_SETUP.md to MACHINE_USER_SETUP.md with full
rewrite covering account creation, CODEOWNERS config, PAT generation,
and rotation. Updated auth sections in IMPLEMENTATION.md, SETUP.md,
STATUS.md, DOCUMENTATION.md, README.md.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* refactor: switch to org-scoped PAT secret DON_PETRY_BOT_PETRY_PROJECT_PAT

Replace DON_PETRY_BOT_GH_PAT with DON_PETRY_BOT_PETRY_PROJECT_PAT — the
new fine-grained PAT scoped to the petry-projects org (resource owner =
petry-projects). The previous PAT was scoped to the donpetry-bot personal
namespace and had no repository access.

The old DON_PETRY_BOT_GH_PAT secret is retained for any future use against
don-petry's personal repos.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* chore: ignore .claude/scheduled_tasks.lock state file

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>

* refactor: merge approval repair scripts into single automation

- Consolidate backfill-approvals.sh and fix-stuck-prs.sh into repair-pr-approvals.sh
- Iterate all repos in both orgs instead of assuming author
- Verify no existing APPROVED review before posting
- Copy original agent comment as review body
- Enable auto-merge when posting approval if needed
- Single workflow with 30-min timeout

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: add @mention trigger for on-demand PR reviews (#30)

Adds repository_dispatch support so commenting @petry-review-bot on any
PR fires an immediate review without waiting for the hourly schedule.

- pr-review.yml: new `repository_dispatch` trigger (type: pr-review-mention),
  per-PR concurrency group for mention runs, FORCE_REVIEW and DRY_RUN
  env vars that handle both workflow_dispatch and repository_dispatch paths
- review-one-pr.sh: FORCE_REVIEW=true bypasses idempotency so a mention
  always runs a fresh cascade even if the head SHA hasn't changed
- templates/mention-listener.yml: deploy to petry-projects/.github;
  listens for @petry-review-bot, validates commenter trust, posts ack,
  sends repository_dispatch (requires Contents:write, not Actions:write)

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: harden rebase, review dismissal, and health log diagnostics (#40)

Addresses 4 code-actionable recommendations from health check report #33:

- [CRITICAL] Make rebase/branch-update non-fatal — 403/504 during rebase
  emits ::warning:: instead of aborting the batch session (#34)
- [MEDIUM] Add retry with exponential backoff (3 attempts) for transient
  5xx on update-branch API; break immediately on 4xx (#37)
- [MEDIUM] Guard review dismissal with state re-check before calling
  dismiss API, preventing 422s from race conditions (#38)
- [LOW] Surface missing log warnings in health check script (#39)
- Fix: skip auto-merge when branch is still BEHIND after failed rebase

Closes #34, closes #37, closes #38, closes #39

* refactor: migrate to org-wide .github-private convention (#41)

* refactor: parametrize hardcoded identity values for org migration

Replace all hardcoded references to don-petry, petry-review-bot, and
don-petry/pr-review-agent with environment variables that default to
the current values. This allows the agent to be configured for different
orgs/users via repo variables.

Changes:
- Scripts use $REVIEWER_USER, $TARGET_ORG, $BOT_USER, $AGENT_REPO
- Prompts no longer reference specific GitHub usernames
- Workflows use org-level GH_PAT_WORKFLOWS secret instead of
  repo-level DON_PETRY_BOT_PETRY_PROJECT_PAT
- Health check uses context.repo.owner/repo for issue creation
- Variables are set in workflow env block with defaults

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add Copilot custom agent profiles for org-wide use

Create /agents/ directory with three agent profiles:
- pr-reviewer: Multi-tier cascading PR review
- feature-ideator: Feature idea generation and prioritization
- compliance-auditor: Org standards compliance checking

These are Copilot custom agent profiles that become available
org-wide in the .github-private repo convention.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Squashed 'frameworks/bmad-method/' content from commit e36f219c

git-subtree-dir: frameworks/bmad-method
git-subtree-split: e36f219c81b6010d4aae423ba12f49edb5b6e31a

* Squashed 'frameworks/spec-kit/' content from commit 11f49ebf

git-subtree-dir: frameworks/spec-kit
git-subtree-split: 11f49ebfb2f6af55345cb4bd9a7906acd211e56f

* Squashed 'frameworks/gsd/' content from commit 304c1a13

git-subtree-dir: frameworks/gsd
git-subtree-split: 304c1a1302564c45af252bbba4bcc5350e7dac3a

* docs: update README for .github-private org infrastructure role

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* ci: add dependabot-automerge.yml workflow (#73)

Adds the required dependabot-automerge.yml workflow from the org
standard template (petry-projects/.github/standards/workflows/).
This is a thin caller stub that delegates to the org-level reusable
workflow.

Closes #48

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Don Petry <don-petry@users.noreply.github.com>

* fix(pr-review): unblock queue starvation from self-authored PRs (#96) (#97)

* fix(pr-review): unblock queue starvation from self-authored PRs (#96)

A self-authored PR sorted first in the candidate list and triggered
"Can not approve your own pull request" — which the session-fatal
abort treated like an engine error, skipping all 28 remaining PRs
on every run.

- list-prs.sh: filter out PRs authored by REVIEWER_USER at enumeration
- post-pr-review.sh: catch the GraphQL self-approval error and exit 100
  (no-op) instead of 1, so a stray self-PR can no longer abort the batch
- engine.sh: gh copilot renamed --target to --agent; fix the rubber-duck
  invocations so tier-2 cross-engine review works again
- review-one-pr.sh: stop appending a duplicate "0" to REVIEW_CYCLE when
  grep -c finds no markers under set -o pipefail (was breaking the
  cycle-cap integer comparison)

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

* fix(pr-review): authenticate as bot, harden enumeration, address review

- Workflow now runs as AGENT_USER (default don-petry-bot), distinct from
  REVIEWER_USER (the human, don-petry). The self-approval rejection that
  caused #96 came from the agent and the human sharing one identity.
- list-prs.sh filters self-authored PRs against AGENT_USER, and validates
  AGENT_USER against the GitHub username charset before interpolating into
  the jq filter (Copilot review feedback).
- review-one-pr.sh uses printf '%s\n' instead of echo for PR_BODIES, since
  PR body content is user-authored and could begin with -n/-e or contain
  backslash escapes (Copilot review feedback).
- AGENT.md guideline updated to reflect that self-authored PRs are
  intentionally excluded (CodeRabbit review feedback).

Operator note: rotate the GH_PAT_WORKFLOWS secret to a token owned by
don-petry-bot (with read:org added) for the bot-account behavior to take
effect at runtime.

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

* refactor(pr-review): collapse to single BOT_USER, route escalations via CODEOWNERS

Drop AGENT_USER and REVIEWER_USER. The workflow now has one identity:
BOT_USER (default don-petry-bot), which both owns the repos to scan and
gets filtered out as the self-approval blocker. Human escalation no
longer hard-codes a single reviewer; instead, scripts/request-codeowners-
review.sh parses CODEOWNERS in the PR's repo and requests review from
every @user / @org/team mention.

- list-prs.sh: BOT_USER for both gh-repo-list and self-author filter
- review-one-pr.sh, post-pr-review.sh: replace --user "$REVIEWER_USER"
  with the CODEOWNERS helper
- repair-pr-approvals.sh: same gh-repo-list switch
- pr-review.yml: drop AGENT_USER/REVIEWER_USER, set BOT_USER default
  to don-petry-bot
- AGENT.md: updated guideline to describe single-identity model

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

* refactor(pr-review): extract batch loop, hoist env, drop redundant step

Workflow YAML drops from 231 to 111 lines by moving the per-PR review
loop into scripts/review-batch.sh. The script can be syntax-checked and
exercised locally; the workflow now just wires env vars and dispatches.

- Hoist GH_TOKEN, MAX_PRS, CANDIDATE_LIMIT to job-level env (were
  repeated on individual steps).
- Inline `gh auth status` into the install step; remove the standalone
  Verify auth step (its only output was a one-line auth dump).
- Drop the `if: steps.list.outputs.count != '0'` guard and the step's
  `id`/output: review-batch.sh handles empty input as a no-op.
- Collapse the duplicated summary-string branch in the review loop into
  a single template with a conditional fragment.

No behavior change.

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

* perf(pr-review): cache claude-code CLI install across runs

`npm install -g @anthropic-ai/claude-code` ran on every workflow start
(~30s). Switch to a per-user npm prefix (~/.npm-global) and cache that
directory via actions/cache, keyed on CLAUDE_CODE_VERSION + runner OS.

A `command -v claude` guard makes the install a no-op on cache hit, so
the only cost on subsequent runs is the cache restore.

CLAUDE_CODE_VERSION defaults to 'latest' (cache persists until manually
flushed); set the repo variable to pin a specific version for fully
reproducible caching.

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

---------

Co-authored-by: Claude <noreply@anthropic.com>

* chore: rename GH_PAT_WORKFLOWS secret to DON_PETRY_BOT_GH_PAT (#99)

The previous name was generic and didn't tell you which account the PAT
belonged to. The new name makes the binding explicit: this secret is the
PAT owned by don-petry-bot, used as BOT_USER throughout the PR-review
workflows.

Operator follow-up before this can be merged:
- Add a new repo secret DON_PETRY_BOT_GH_PAT containing the bot's PAT
  (with repo, workflow, and read:org scopes).
- After merge, the old GH_PAT_WORKFLOWS secret can be deleted.

Affected workflows:
- pr-review.yml (1 use)
- claude.yml (4 uses, with || github.token fallback)
- daily-pr-review-health.yml (1 use)
- repair-pr-approvals.yml (1 use)

scripts/pr_review_health.sh's error message is updated to point at the
new name as well.

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

Co-authored-by: Claude <noreply@anthropic.com>

* chore: remove frameworks directory (#101)

Co-authored-by: Gemini CLI <gemini-cli@example.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore all erroneously deleted files (items 4-34) (#104)

* revert: restore .github/workflows/daily-pr-review-health.yml

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore .github/workflows/repair-pr-approvals.yml

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore agents/pr-reviewer.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/cascade-action.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/deep-review.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/rubber-duck.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/security-audit.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/shared.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/single-review.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/synthesize-duck.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/synthesize.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/triage.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/engine.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/list-prs.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/post-pr-review.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/pr_review_health.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/repair-pr-approvals.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/request-codeowners-review.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/review-batch.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/review-one-pr.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore templates/mention-listener.yml

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore AGENT.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore BOT_SETUP.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore DOCUMENTATION.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore IMPLEMENTATION.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore MACHINE_USER_SETUP.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore PR_REVIEW_AGENT_FAILURE_REPORT.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore PR_REVIEW_FAILURE_INVESTIGATION.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore SETUP.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore STATUS.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore README.md to pre-change state

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore .github/workflows/daily-pr-review-health.yml

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat: add Gemini Pro support and optimize PR review fallback chain (#102)

* Merge main and resolve conflicts

* Enhance pr-review workflow: add Gemini support and refactor fallback logic

* Address Copilot review comments: tighten regex, fix fallback summary, and align docs

* docs: move agent documentation to docs/ folder and rename files

* docs: align secret names and fix casing in index

* docs: organize pr-review-agent documentation under dedicated folder

* Address final PR comments: tighten regex, add preflight checks, fix bot names, and cleanup docs

---------

Co-authored-by: Gemini CLI <gemini-cli@example.com>

* fix(pr-review): gate rate-limit detection on non-zero exit code

Broad patterns like `plan.*limit` and `claude.*usage` could match content
in a successful triage summary, triggering a false-positive engine fallback.
Guard the check with TRIAGE_RC != 0 so it only fires when the provider
command actually failed.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(claude): sync inlined workflow with org standard (#109)

* fix(claude): sync inlined workflow with org standard

Brings the inlined claude.yml up to parity with
petry-projects/.github/.github/workflows/claude-code-reusable.yml.

Changes:
1. Bot allow list for pull_request_review_comment
   Add coderabbitai[bot], Copilot, copilot-pull-request-reviewer[bot],
   and gemini-code-assist[bot] alongside the existing OWNER/MEMBER/
   COLLABORATOR check. These bots have author_association 'NONE' so
   their review comments were always skipped.

2. check_run trigger + claude-ci-fix job
   Port the CI failure auto-fix feature from the reusable. When a check
   fails on a PR, Claude diagnoses and fixes it automatically.
   Adapted to use DON_PETRY_BOT_GH_PAT (this repo's secret name).

3. claude-code-action SHA bump: v1.0.89 → v1.0.119
   Update both the claude and claude-issue jobs.

4. paths-ignore on pull_request trigger
   Prevents the workflow from firing on PRs that only change
   claude.yml itself, avoiding the Anthropic OIDC validation
   failure (workflow file must match default branch at token exchange).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(claude): guard bot allow list against fork PRs

Same fix as petry-projects/.github PR #238 — add same-repo guard
for bot-triggered pull_request_review_comment runs.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(claude): address ci-fix self-loop and fork PR security

- Replace Claude Code name prefix check with explicit job name list to
  correctly prevent self-loops (check_run names for inlined workflows
  are bare job names, not workflow-prefixed)
- Add fork PR trust gate in Resolve PR number step: verify head repo
  matches target repo before running Claude with privileged credentials

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* ci: replace inlined claude.yml with standard thin-caller stub

The inlined version embedded all three jobs (claude, claude-ci-fix,
claude-issue) directly and threaded DON_PETRY_BOT_GH_PAT through
checkout tokens and github_token, causing the bot's PAT to author
PRs and comments instead of github-actions[bot].

Replace with the standard thin caller that delegates to the org-level
reusable workflow (claude-code-reusable.yml@v1) via secrets: inherit,
which is the correct pattern per:
petry-projects/.github/standards/workflows/claude.yml

* fix: replace gh copilot suggest with GitHub Models REST API (#151)

* fix: replace gh copilot suggest with GitHub Models REST API (#147)

The `gh copilot suggest -p "$(cat <file>)"` invocation failed with
"Invalid command format" because:
1. The `-p` flag is not valid syntax in modern `gh copilot` built-in versions.
2. `gh copilot suggest` is a shell-command suggestion tool; it does not
   accept large PR prompts or return structured JSON responses.
3. Passing a multi-thousand-line prompt via `$(cat ...)` can hit ARG_MAX.

The non-zero exit was then misclassified by the rate-limit detector, which
aborted the entire session and skipped all remaining PRs in the queue.

Fix: replace all three copilot invocations (run_triage, run_agentic,
run_duck) with a new `copilot_chat` helper that calls the GitHub Models
REST API directly via curl. The API is OpenAI-compatible, versioned via
`X-GitHub-Api-Version`, stable against gh CLI version changes, and accepts
arbitrary prompt sizes using python3 for safe JSON encoding.

Additional changes:
- Add `COPILOT_API_MODEL` var (default `openai/o4-mini`) overrideable via env.
- run_agentic/run_duck copilot cases now also write output to $OUTPUT_FILE
  so callers that check that path directly (deep review, audit) find the JSON.
- Rate-limit responses (HTTP 429) are echoed to stdout so the existing
  `is_rate_limited()` detector fires correctly for engine fallback.
- Add pre-flight smoke test in review-batch.sh: tests GitHub Models API
  connectivity with a one-liner prompt before processing any PRs, so auth
  or model errors surface immediately as a clear setup failure.
- Add tests/test_copilot_chat.sh: unit tests for the JSON payload builder
  with edge-case prompts (quotes, newlines, # headings, large diffs, Unicode).

Closes #147

Co-authored-by: Don Petry <don-petry@users.noreply.github.com>

* fix: scope COPILOT_API_MODEL export to copilot engine only

Move the `export COPILOT_API_MODEL` from the common exports block into the
`copilot)` case where the variable is set, so it is not exported as an
empty/unset variable when the engine is `claude` or `gemini`. The
`copilot_chat` fallback `${COPILOT_API_MODEL:-openai/o4-mini}` still works
correctly when `DUCK_ENGINE=copilot` under a non-copilot primary engine.

Co-authored-by: Don Petry <don-petry@users.noreply.github.com>

* fix: address review comments — temp file for payload, fail-fast source, streaming output

- copilot_chat: write JSON body to mktemp file, pass to curl as @file to
  avoid ARG_MAX for large PR diffs (was --data-binary "$body")
- copilot_chat: add :? guard on COPILOT_GITHUB_TOKEN for a clear error
  instead of generic "unbound variable" under set -u
- run_agentic / run_duck copilot paths: stream directly to stdout (and
  tee to OUTPUT_FILE when set) rather than buffering the full response
  into a shell variable, which forced large outputs into memory and
  stripped trailing newlines
- review-batch.sh pre-flight: fail fast if source engine.sh fails
  (was silently ignored with || true)
- review-batch.sh pre-flight: build smoke-test JSON payload via python3
  + temp file instead of shell string interpolation to avoid JSON
  injection if COPILOT_API_MODEL contains special characters
- Clarify that openai/o4-mini is the correct April-2025 o4-generation
  model name, not a typo for o1-mini or gpt-4o-mini

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Don Petry <don-petry@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* feat: prioritise .github/.github-private PRs, oldest-first within tier (#155)

* feat: prioritise .github/.github-private PRs, oldest-first within tier

- list-prs.sh: add createdAt to JSON fetch; emit priority|createdAt|url
  lines (priority 0 for .github/.github-private, 1 for everything else)
- Replace final sort -u with a two-pass sort: deduplicate by URL, then
  sort by priority asc then createdAt asc; strip sort keys with cut
- tests/test_list_prs_sort.sh: 16 tests covering priority classification,
  oldest-first ordering, mixed scenarios and deduplication
- .github/workflows/test.yml: run both unit-test files on every PR / push

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* test: add dedup edge case for same URL with conflicting priorities

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: remove temperature from copilot_chat payload (o4-mini incompatible)

The GitHub Models API rejects temperature=0 for reasoning models like
o4-mini with HTTP 400:
  'Unsupported value: temperature does not support 0 with this model.
   Only the default (1) value is supported.'

This caused the rubber duck (DUCK_ENGINE=copilot / DUCK_MODEL=o4-mini)
to fail with HTTP 400 on every claude-engine run, silently degrading
every review to deep-only with no cross-engine sanity check.

Fix: remove temperature from the copilot_chat JSON payload entirely —
the API defaults to 1, which is the only supported value for o4-mini.

Also:
- Update tests/test_copilot_chat.sh: sync build_payload to match and
  flip Test 9 to assert temperature is ABSENT (not 0).
- Add .github/workflows/test.yml: run unit tests on every PR and push
  to main so regressions are caught before merge.

Reproducer: job/75690347409 — duck failure logged as:
  copilot_chat: HTTP 400 from GitHub Models API
  {"error":{"message":"Unsupported value: 'temperature' does not
   support 0 with this model..."}}

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: address PR review comments

- list-prs.sh: tighten priority regex to /[.]github(-private)?/pull/
  so path-boundary anchor prevents false positives (e.g. foo.github)
- list-prs.sh: replace printf pipeline with here-string to avoid ARG_MAX
  limits on large PR sets
- test.yml: add permissions: contents: read (least-privilege, CodeQL fix)
- test.yml: add timeout-minutes: 5 to prevent hung runs
- test_list_prs_sort.sh: sync sort_entries helper (here-string + || true)
  and classify regex to match updated list-prs.sh exactly

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: don-petry <don@petry.dev>

* fix: restore pr-review.yml content (accidentally emptied in revert)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: restore pr-review.yml (file was accidentally emptied)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore(deps): bump actions/cache from 4 to 5 (#165)

Bumps [actions/cache](https://github.com/actions/cache) from 4 to 5.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): bump petry-projects/.github/.github/workflows/claude-code-reusable.yml (#164)

Bumps [petry-projects/.github/.github/workflows/claude-code-reusable.yml](https://github.com/petry-projects/.github) from 1 to 2.
- [Commits](https://github.com/petry-projects/.github/compare/v1...v2)

---
updated-dependencies:
- dependency-name: petry-projects/.github/.github/workflows/claude-code-reusable.yml
  dependency-version: '2'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* fix(list-prs): search DELEGATION_ORGS and drop --checks success pre-filter

- Add iteration over all orgs in DELEGATION_ORGS (e.g. don-petry) so PRs
  in those orgs enter the candidate pool. Previously only BOT_USER and
  TARGET_ORG were searched.
- Remove --checks success from org repo searches. GitHub excludes PRs from
  repos with no CI configured when this flag is used, silently dropping all
  PRs from repos like .github, .github-private, google-app-scripts, and
  TalkTerm. review-one-pr.sh already enforces CI gating per-PR and treats
  empty statusCheckRollup as passing, so the pre-filter is redundant and
  harmful.
- Add --limit 200 to gh repo list calls to handle org growth beyond gh's
  default 30-repo cap.

* feat: add code-quality ruleset (compliance fix #60) (#86)

Creates the required `code-quality` repository ruleset enforcing required
status checks on the default branch, as mandated by the org standard:
standards/github-settings.md#code-quality--required-checks-ruleset-all-repositories

Required checks:
- SonarCloud (code quality analysis)
- CodeQL (SAST)
- agent-shield / AgentShield (agent security scan)
- dependency-audit / Detect ecosystems (dependency vulnerability scan)

Bypass actors:
- OrganizationAdmin (always) — emergency override
- dependabot-automerge-petry Integration (always) — Dependabot auto-merge

The ruleset was applied directly via GitHub API. This file documents the
configuration as code for auditability and future reapplication.

Closes #60

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Don Petry <don-petry@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* feat(prompts/dev-lead): add human prompt template

* feat(prompts/dev-lead): add human-pr prompt template

* feat(dev-lead): implement dev-lead agent Phases 0-6

* fix: per-PR isolation and single-review retry (closes #132)

- review-batch.sh: non-rate-limit per-PR failures (exit code 1) no
  longer abort the session. SESSION ABORTED EARLY is now reserved for
  the rate-limit-on-fallback-engine case (exit code 2) only. All other
  failures are counted and logged; remaining candidates continue.

- review-one-pr.sh: single-review step retries up to
  SINGLE_REVIEW_MAX_RETRIES (default 2) times with a
  SINGLE_REVIEW_RETRY_DELAY_SEC (default 15s) gap before giving up.
  On exhaustion, the PR is flagged needs-human-review and the script
  exits with code 1, which the updated batch treats as a non-fatal
  per-PR failure. Raw model output and stderr are logged on each failed
  attempt for post-mortem visibility.

Root cause of run #25707852006: claude-opus-4-7 returned a verbose
non-JSON response for PR #129; the old code treated that as fatal and
skipped 35 remaining candidates.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix: rate-limit detection and per-attempt stderr logs in single-review retry

Address inline review comments on PR #133:

- Rate-limit check: after each run_agentic call, inspect both stdout
  (VERDICT_JSON.raw) and stderr (SINGLE_LOG) with is_rate_limited before
  retrying. A rate-limit match exits immediately with code 2 so
  review-batch.sh can trigger engine fallback — consistent with triage and
  deep-review tiers. Previously a rate-limited single-review would burn all
  retries and exit 1 (per-PR failure), silently leaving the batch on the
  same rate-limited engine for all remaining PRs.

- Per-attempt log files: stderr is now written to
  single-review-attempt-N.log rather than a single overwritten file, so
  no earlier-attempt errors are lost. Each attempt logs its own stderr
  inline on failure; the fallback path cats all attempt logs for
  post-mortem visibility.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* feat(dev-lead): Phase 0 test infrastructure and Phase 1 intent stub

Phase 0: full test harness for the dev-lead agent —
  26 event fixtures (all valid JSON with _test_expected_intent),
  stub claude/gemini engines, mock gh binary, CI failure log sample,
  bats helpers (stub-engine, mock-gh, assert-env, prompt-vars),
  7 prompt templates with VARIABLES declarations, preflight script,
  prompt coverage integration test, and test-dev-lead.yml CI workflow.

Phase 1: dev-lead.yml trigger workflow (all 7 event types, dispatch + ci-relay
  jobs) and dev-lead-intent.sh stub (anti-loop guard live; all other events
  emit skip/not-implemented).  14/14 bats unit tests pass.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): add permissions blocks to test workflow jobs (CodeQL)

* fix(ci): auto-fix for lint / eslint [skip ci-relay]

* feat(dev-lead): Phases 2-6 — CI fix, review fix, issue, engine fallback

Phase 2: run_writer/run_writer_with_fallback in engine.sh, full intent
routing in dev-lead-intent.sh, dev-lead-fix-ci.sh handler, workflow wiring.

Phase 3: dev-lead-fix-reviews.sh handles fix-reviews, fix-bot-comment,
human, human-pr, rebase intents. Full review routing in intent classifier.

Phase 5: dev-lead-fix-issue.sh handles the issue intent with dedup guard
and branch/PR creation.

Phase 6: run_writer_with_fallback with claude→gemini→copilot fallback chain.

Tests: 77 unit tests across 8 new .bats files, all passing. Updated Phase 1
stub tests to reflect actual Phase 2+ routing behavior.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): address review comments — dispatch JSON, dead code, portable stub gh

* fix(dev-lead): pre-flight after intent, fork URL check, review-batch exit code, retry comment

* fix(dev-lead): install bats-core from GitHub to avoid root requirement

* fix(security): move event values to env vars to prevent script injection (SonarCloud)

* fix(security): move CLAUDE_CODE_VERSION to env block (SonarCloud script injection)

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* feat(dev-lead): Phase 1.5 — reusable workflow for cross-repo adoption + Phase 7 shadow period

* feat(dev-lead): add reusable workflow (Phase 1.5) and begin shadow period (Phase 7)

- Create .github/workflows/dev-lead-reusable.yml: workflow_call entry
  point for other repos. Checks out .github-private scripts/prompts into
  .dev-lead/, then runs the same intent-classify + handler pipeline as
  dev-lead.yml with PROMPTS_DIR=.dev-lead/prompts/dev-lead.
- Add PROMPTS_DIR env-var support to dev-lead-fix-ci.sh,
  dev-lead-fix-reviews.sh, and dev-lead-fix-issue.sh so the reusable
  workflow can point scripts at the sparse-checkout path without changing
  CWD. Defaults to prompts/dev-lead (backwards-compatible for dev-lead.yml).
- Annotate dev-lead.yml with Phase 7 shadow-period window (2026-05-15
  through ~2026-05-29): claude.yml and dev-lead.yml run in parallel until
  regressions clear, then claude.yml is removed.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* chore(dev-lead): update shadow-period tracking issue ref to #180

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): document PAT requirement and pin ref:main on private checkout

* fix(dev-lead): P1 review fixes — ci-relay in reusable, .dev-lead gitignore, export PROMPTS_DIR, trim header

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): rate-limit detection, separate status, and retry cron (#192) (#196)

* chore(dev-lead): decommission claude.yml, bump ACTION_TIMEOUT_SEC to 600s

- Delete .github/workflows/claude.yml — replaced by dev-lead.yml (shadow
  period complete, tracking issue #180).
- scripts/engine.sh: raise ACTION_TIMEOUT_SEC default 300→600s to reduce
  timeout failures on large-repo fix-ci runs (PR #80 had 3 timeouts).
- AGENTS.md: remove claude.yml immutability exemption; note dev-lead.yml
  as the active AI automation workflow and its edit-via-reusable pattern.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* refactor: replace Claude analysis with pure gh/jq telemetry in health check

* refactor: drop Node/Claude steps from daily-pr-review-health workflow

* rename: daily-pr-review-health → actions-fleet-monitor

* rename: daily-pr-review-health → actions-fleet-monitor

* fix(agents-md): clarify dev-lead.yml vs dev-lead-reusable.yml scope

dev-lead.yml in .github-private runs inline steps (not a caller stub).
Behavior changes for this repo go to dev-lead.yml directly; changes that
affect all org repos via the cross-repo reusable go to dev-lead-reusable.yml.

Addresses Copilot review on PR #194.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): rate-limit detection, separate status, and retry cron

Closes #192. Implements all phases from the revised plan.

**Phase 0 — fix run_writer stdout capture (prerequisite)**
- engine.sh: capture stdout via `tee` to a tempfile so `is_rate_limited`
  can inspect the output; old code read `/tmp/dev-lead-writer-stderr`
  which was never written (claude --print outputs to stdout, not stderr).
  Fallback engines were never tried in practice.
- engine.sh: add `parse_reset_time` to extract ISO timestamp from
  `resets H:MMpm (UTC)` in engine output and write it to
  `/tmp/dev-lead-rate-limit-reset` for callers to embed in markers.

**Phase 1 — separate status=rate-limited from status=failed**
- fix-ci.sh: detect engine exit 2 → post `status=rate-limited` (not
  `status=failed`); embed parsed reset time in marker body; exit 2.
- fix-ci.sh: fix `check_idempotency` to treat `status=rate-limited`
  as retriable — only block on terminal statuses (applied, failed,
  no-changes). Previously, rate-limited markers blocked all retries.
- fix-ci.sh: `count_recent_failures` already filtered to `status=failed`;
  add explicit comment confirming rate-limited markers are excluded.
- fix-ci.sh: add `has_rate_limited_marker` dedup check so the same SHA
  never accumulates more than one rate-limited comment.
- fix-reviews.sh: on engine exit 2 for all five intent types
  (fix-reviews, fix-bot-comment, human, human-pr, rebase), post a
  `status=rate-limited` marker with embedded reset time and exit 2.
- fix-reviews.sh: for `human` and `human-pr` intents, also post a
  user-visible acknowledgment comment so users know their request was
  received and will be retried.

**Phase 2 — scheduled retry cron for fix-ci**
- dev-lead-retry.sh: new script scans all open PRs across TARGET_ORG
  for `status=rate-limited` markers on current HEAD SHA; skips PRs
  whose reset time is still in the future; staggered dispatches (30s
  between repos) to prevent cascading org-level rate-limit hits.
- dev-lead-retry.yml: new workflow — scheduled every 2 hours (dual
  offsets to work around GitHub scheduler skew), plus workflow_dispatch.

**Phase 3 — retry for fix-reviews intents (same cron)**
- dev-lead-retry.sh already handles all five fix-reviews intent types
  (fix-reviews, fix-bot-comment, human, human-pr, rebase) via
  `dev-lead-reviews-retry` dispatch events.
- dev-lead-intent.sh: add `dev-lead-reviews-retry` dispatch type routing
  to the intent classifier — maps intent_type from payload to the correct
  existing fix-reviews step.
- dev-lead.yml: add `dev-lead-reviews-retry` to repository_dispatch types.
- dev-lead.yml: unify concurrency group for all dispatch types to
  `dev-lead-pr-{pr_number}` to avoid per-type slot fragmentation.

**Tests**
- test_engine_writer.bats: 8 new tests covering rate-limit stdout detection,
  exit-2 mapping, fallback exhaustion, reset time parsing.
- test_fix_ci.bats: 5 new tests covering rate-limited status, exhaustion
  exclusion, idempotency pass-through, and dedup.
- test_fix_reviews.bats: 5 new tests covering all intent types + human ack.
- test_intent_ci.bats: 4 new tests for dev-lead-reviews-retry dispatch routing.
- e2e/scenarios/07-rate-limit-retry.sh: new E2E scenario covering all parts.
- 3 new event fixtures for repository_dispatch_reviews_retry_* payloads.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): address review comments on #196

Addresses all 18 review threads from Copilot, Codex, and Gemini.

**P1 bugs fixed:**
- dev-lead-retry.sh: all log echo calls in scan_pr_for_rate_limits and
  dispatch_* functions now write to stderr; only the final count is on
  stdout. Previously, log lines polluted the command substitution,
  causing arithmetic expansion to fail and aborting the scan on the
  first retry candidate.
- dev-lead-retry.yml: permissions: contents was read-only; creating
  repository_dispatch events requires contents: write.

**Correctness fixes:**
- dev-lead-retry.sh: restrict automated retries to intents whose context
  can be reconstructed at runtime (fix-reviews, human-pr, rebase). human
  and fix-bot-comment require USER_INSTRUCTION/COMMENT_BODY from the
  original event which cannot be re-fetched; they are explicitly excluded.
- dev-lead-fix-reviews.sh: resolve HEAD_SHA from the PR API when not
  provided by the triggering event (issue_comment intents carry no SHA).
  Without this, rate-limited markers had no sha= field and were
  invisible to the retry scanner.
- dev-lead-fix-reviews.sh: write terminal status=applied marker after
  successful fix-reviews, human-pr, rebase runs. Prevents the retry
  cron from re-dispatching the same intent on every subsequent tick when
  the SHA hasn't changed.
- dev-lead-retry.sh: check for reviews terminal marker before dispatching
  a retry (mirrors the existing fix-ci terminal-marker check).
- dev-lead-retry.sh: look up current check-run details (details_url, id)
  at dispatch time via the commits check-runs API, so retried fix-ci runs
  have full failure logs and annotations rather than empty fields.
- dev-lead-fix-ci.sh: embed check= field in the rate-limited marker so
  the retry cron knows which check run to look up.
- dev-lead-fix-reviews.sh: human intent now posts a "please re-mention
  @dev-lead" ack instead of "I'll retry automatically" since it will NOT
  be retried automatically. human-pr keeps the auto-retry ack (correct).

**Pagination (Copilot + Gemini):**
- dev-lead-fix-ci.sh: add --paginate to check_idempotency, count_recent_
  failures, has_rate_limited_marker.
- dev-lead-fix-reviews.sh: add --paginate to has_reviews_rate_limited_marker.
- dev-lead-retry.sh: add --paginate to PR comments and PR list fetches.

**Other improvements:**
- dev-lead-retry.yml: simplify to single cron schedule (15 */2 * * *);
  remove BOT_USER env var (unused in the script).
- dev-lead-retry.sh: raise repo list limit 200 → 1000; add hard-error
  when list_repos_for_org returns empty (surfaces token permission issues
  rather than silently scanning 0 repos).
- dev-lead-intent.sh: use jq -nc (compact, null-input) for context JSON
  construction — safe against values with special characters.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(pr-review): handle Gemini trust-mode failure and properly route engine fallbacks (#217)

* fix(pr-review): handle Gemini trust-mode failure and properly route engine fallbacks

Closes #208

* fix(ci): install gemini CLI and set trust mode for dev-lead agents

---------

Co-authored-by: Gemini CLI <gemini-cli@example.com>

* fix: resolve YAML syntax error in dev-lead-reusable workflow

* fix: address automated feedback from PR 217 (#219)

This addresses missed feedback from CodeRabbit (removing syntax errors in YAML) and gemini-code-assist (improving string concatenation in validate-engines.sh), and adds assertion output checks to tests.

Co-authored-by: Gemini CLI <gemini-cli@example.com>

* chore: remove Phase 7 shadow period comment from dev-lead.yml

* feat: skip PRs with CHANGES_REQUESTED review (+ FORCE_REVIEW and stale-review guards)

* feat: skip PRs with CHANGES_REQUESTED review decision

Adds an early-exit check alongside the existing CI guards so the cascade
skips any PR where a human reviewer has already requested changes.
Reviewing those PRs before the author responds wastes tokens and creates
confusing parallel feedback threads.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix: honor FORCE_REVIEW and skip only stale-free CHANGES_REQUESTED reviews

Two issues addressed from Codex P2 feedback:

1. FORCE_REVIEW bypass: mention-triggered and force_review=true runs now
   bypass the CHANGES_REQUESTED skip, so authors can explicitly request a
   re-review after addressing feedback.

2. Stale review guard: only skip when a CHANGES_REQUESTED review targets
   the current head SHA. On repos without automatic stale-review dismissal,
   reviewDecision stays CHANGES_REQUESTED after new commits — the cascade
   now re-engages when the author has pushed new work.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): resolve context loss, missing checkout, and push logic (#223)

* feat: skip PRs with CHANGES_REQUESTED review decision

Adds an early-exit check alongside the existing CI guards so the cascade
skips any PR where a human reviewer has already requested changes.
Reviewing those PRs before the author responds wastes tokens and creates
confusing parallel feedback threads.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix: honor FORCE_REVIEW and skip only stale-free CHANGES_REQUESTED reviews

Two issues addressed from Codex P2 feedback:

1. FORCE_REVIEW bypass: mention-triggered and force_review=true runs now
   bypass the CHANGES_REQUESTED skip, so authors can explicitly request a
   re-review after addressing feedback.

2. Stale review guard: only skip when a CHANGES_REQUESTED review targets
   the current head SHA. On repos without automatic stale-review dismissal,
   reviewDecision stays CHANGES_REQUESTED after new commits — the cascade
   now re-engages when the author has pushed new work.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): resolve context loss, missing checkout, and push logic

- dev-lead-intent.sh: include actor and body in INTENT_CONTEXT for comments
- dev-lead.yml: parse and pass INTENT_ACTOR and INTENT_COMMENT_BODY to agent
- dev-lead-fix-reviews.sh: perform PR checkout, git push, and post summary comments

* fix(dev-lead): exit with 0 on no-changes in fix-reviews and human-pr

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* test: bypass CI check

* fix(pr-review): remove CI bypass hack

* fix(dev-lead): use heredoc for multiline environment variables (#224)

* fix(dev-lead): use heredoc for multiline environment variables

* fix(dev-lead): ensure all engines are installed and context is consistent

* fix(dev-lead): resolve stale model name in engine fallbacks

* fix(dev-lead): improve fallback reliability and script quality

- engine.sh: resolve stale model names in fallback loop; use -latest Gemini aliases
- dev-lead-fix-reviews.sh: remove unused code and fix PR_URL export

* fix(dev-lead): resolve Gemini model names and shell lint warnings

* security(dev-lead): use random heredoc delimiter to prevent inject…
don-petry added a commit that referenced this pull request Aug 7, 2026
… trigger for the Class-2 timer changes, before narrowing them (#1420)

* add: workflow to fix stuck PRs using bot account token

Allows running the cleanup script via workflow_dispatch with access to
GH_PAT (bot account token) from repo secrets.

* fix: pass GH_PAT to checkout action so workflow uses bot account

* debug: simplify account check, add authentication debug output

* docs: add comprehensive petry-review-bot setup instructions

* docs: add GitHub App setup guide (recommended approach)

GitHub App is the secure, recommended way to automate PR reviews:
- Fine-grained permissions
- JWT tokens that auto-expire
- No human account needed
- Better audit trail
- GitHub's recommended approach

Replaces the bot user account approach with a more secure alternative.

* docs: add comprehensive GitHub App setup documentation

- SETUP.md: Quick reference guide with configuration and troubleshooting
- IMPLEMENTATION.md: Technical deep dive on architecture and design decisions
- DOCUMENTATION.md: Index of all documentation files
- Updated README.md with status and quick links
- Updated GITHUB_APP_SETUP.md with implementation notes and actual app ID
- Workflows use GitHub App token generation instead of static PATs

Covers the transition from bot user account to GitHub App authentication for improved security and maintainability.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: silence auth check in fix-stuck-prs when using GitHub App token

GitHub App tokens don't have user scope, so 'gh api user' returns 403.
This is fine - the script still works for PR operations. Suppress the error
so the script completes successfully with app-token fallback label.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: use explicit author instead of @me in stuck PR search

GitHub App tokens don't have user identity, so @me search doesn't work.
Use explicit 'don-petry' author instead to find PRs to fix.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: avoid subshell in while loop to preserve variable state

Using pipe in while loop created subshell where PROBLEM_PRS and FIXED_PRS
counters were incremented but changes didn't persist to parent shell.
Fixed by using process substitution for input instead of pipe.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* docs: add comprehensive status report for GitHub App setup

- Overview of completed work
- GitHub App authentication details
- 24 stuck PRs successfully fixed with approval reviews
- Architectural decisions and rationale
- Configuration guide and usage examples
- Known limitations and troubleshooting

System is now fully operational with GitHub App token authentication,
comprehensive documentation, and all infrastructure in place.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* docs: document stuck PR cleanup status and script fixes

Added documentation for:
- GitHub App token compatibility fixes in fix-stuck-prs.sh
  * Changed @me to explicit author (app tokens have no identity)
  * Fixed subshell variable scope (process substitution vs pipe)
  * Silenced expected 403 auth check error
- Current status: 24 PRs have approvals but remain OPEN
  * Auto-merge failed due to missing GitHub App permission
  * Approvals satisfy branch protection requirement
  * Next: expand permissions or manually merge

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: update list-prs.sh to search all repos by owner instead of @me

GitHub App tokens don't have user identity, so @me/@review-requested searches
fail. Changed to enumerate all repos in don-petry and petry-projects, then
search for open PRs within each repo. This covers the full scope:
- All open PRs in personal don-petry repos
- All open PRs in petry-projects org repos

Resolves 6 consecutive workflow failures due to zero PRs being enumerated.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* docs: add investigation report for PR review agent workflow failures

* fix: move env section before steps in workflow to fix YAML structure

GitHub Actions requires env section to come before steps in job definition.
Moving env definition up and setting GH_TOKEN only in steps that need it.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* script: add backfill-approvals to retroactively apply real approvals

Converts PRs with agent approval comments (but no real GitHub approvals)
to have actual APPROVED reviews. Needed for PRs reviewed before the
gh pr review --approve fix was applied.

Usage:
  scripts/backfill-approvals.sh          # dry-run (preview changes)
  scripts/backfill-approvals.sh false    # apply real approvals

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* feat: add backfill-approvals workflow to run as GitHub App bot

Runs backfill-approvals.sh via GitHub Actions so the approvals are posted
by the bot identity, not the PR author — GitHub rejects self-approvals.

Also fixes subshell counter bug in the script (piped while loop lost variable
state; switched to process substitution).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: convert inner while loop to process substitution to preserve counters

Both the outer repo loop and inner PR loop were piped subshells, causing
all counter increments (approved/skipped/failed) to be lost. The summary
always showed 0. Fixed by using < <(...) process substitution for both loops.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* car-hunt: add VIN-deep-dive flow, printable checklists, location-tier ranking

- Step 5.5 pre-test-drive flow: NHTSA VIN decode + per-VIN unrepaired-recall
  automation via Chrome MCP (Honda LWC owner portal documented as not
  automatable; NHTSA web form works).
- §F printable test-drive checklist generator (reportlab/Platypus PDF) with
  cold-start, model-specific red flags, negotiation table, walk-away rules.
- §G fraud / paperwork verification checklist (vehicle-agnostic, reusable):
  walk-away triggers, VIN three-location match, curbstoning, odometer fraud,
  bill of sale, title transfer, payment protection, stolen-vehicle, title-
  jumping, after-purchase steps.
- Step 5.25 head-to-head comparison template with information-asymmetry rule.
- Step 3 Location Quality Tier scoring (Birmingham AL metro table A/B/C/D)
  with adj-CPM modifier; sheet schema gains Adj.CPM + Tier columns.
- Hard-disqualification rules added: non-running engine keywords, mileage
  inconsistency, mandatory description scrape.
- Drive MCP overwrite limitation documented; sheet ID now read from memory
  rather than hardcoded.
- FB Marketplace operational facts: Birmingham AL city ID 107739635926718,
  /search? vs /vehicles? query handling, React-controlled inline composer,
  send-button selector, seller-name extraction regex.
- scheduled-tasks/used-car-search-{morning,afternoon}: tier A/B priority
  flag, sheet ID read from memory.
- Reference PDF generator scripts checked in for reuse.

* fix: re-approve PRs where approval predates last commit (stale approval)

Instead of skipping PRs that already have any APPROVED review, skip only
those where reviewDecision is not REVIEW_REQUIRED. This handles PRs where
a bot approval was posted before a new commit was pushed — GitHub's ruleset
engine treats those approvals as stale even with dismiss_stale_reviews=false.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: robustly extract JSON verdict from mixed claude --print output (#17)

Claude's --print mode can prefix the JSON verdict with conversational
preamble text, causing jq to fail with parse errors. Add
extract_verdict_json to engine.sh and wire all three cascade action
call sites in review-one-pr.sh to use it.

Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: extract_verdict_json — check dest file first (agent Bash-write style)

The cascade-action agent writes its verdict JSON to \$OUTPUT_FILE via a
Bash tool call, then prints a text summary to stdout. The previous fix
only scanned stdout (the .raw file), which contained no JSON. The agent-
written file at \$dest (= \$OUTPUT_FILE) was already correct — just not
checked. Now check \$dest first before falling back to stdout scan.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Enforce MAX_REVIEW_CYCLES cap before running the cascade (#18)

* fix: enforce MAX_REVIEW_CYCLES cap with human-escalation marker

Previously MAX_REVIEW_CYCLES only gated AI delegation in
post-pr-review.sh — the cascade itself ran on every cron tick
regardless of how many cycles had accumulated. Real-world result:
a PR could rack up 9+ review cycles (we observed exactly that on
ContentTwin#100) before any cap took effect.

Add a pre-cascade check in review-one-pr.sh: when the count of
existing `<!-- pr-review-agent v1 sha=... -->` markers is at or
above MAX_REVIEW_CYCLES (default 3), post one escalation comment
marked `<!-- pr-review-agent escalation -->`, label
needs-human-review, request don-petry, and exit 100 (skip
sentinel — doesn't burn the MAX_PRS budget). The escalation
marker doubles as the no-spam guard: subsequent runs detect it
and exit 100 immediately.

Also reuse a single `gh pr view` for both the cycle count and the
escalation-marker check.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* review feedback: surface escalation-comment failures, drop dead unset

- Drop `2>/dev/null || true` on `gh pr comment` so a failed escalation
  post is visible in the workflow log instead of silently leaving the
  PR with no marker (which would re-trigger the cap path next tick).
- Reword the cycle-count comment from "AI delegation loops" to "review
  loops" — the cap also catches cascade-only loops where every cycle
  approves and a new commit lands before merge.
- Remove the redundant `unset PR_BODIES` — never exported, no leakage.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>

* Reliability hardening: session circuit breaker, timeouts, retry, dedup (#20)

* feat: session circuit breaker, per-tier timeouts, retry, and triage hard-fail

Reliability hardening for the PR review agent.

1. Session circuit breaker (.github/workflows/pr-review.yml): on any
   non-zero, non-100 exit from review-one-pr.sh (general failure or rate
   limit on the fallback engine), break the per-PR loop, log a clear
   error annotation naming the failing PR and reason, and exit the step
   with code 1 so the run shows red. Prevents one systemic problem from
   silently burning the entire candidate pool.

2. Per-tier timeouts (scripts/engine.sh): triage/deep/audit/action/duck
   each get their own bounded timeout (180/600/600/300/300s defaults,
   env-overridable). Previously only the duck had a timeout — a hung
   tier could burn the whole 60min job budget.

3. Retry-with-backoff on transient errors (scripts/engine.sh): triage
   retries once on 124/137/143 (timeout / signal kill) since its caller
   captures stdout via $(...) so retries are safe. Deliberately NOT
   applied to run_agentic/run_duck where stdout is redirected to a file
   — a retry there would corrupt the partial first-attempt output.

4. Triage non-JSON now hard-fails (scripts/review-one-pr.sh): replaces
   the silent fallback that synthesized a fake "escalate=MEDIUM" verdict
   and proceeded to deep review. With the new circuit breaker, loud
   failure is the right call — masking a broken triage was burning
   tokens on every PR while the workflow looked healthy.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix: stop stacking duplicate agent reviews on the same PR

Two bugs were causing the agent to leave multiple comments on the same PR.
Together they produced 10 stacked APPROVED reviews on petry-projects/ContentTwin#100.

Bug A — idempotency check is order-dependent (review-one-pr.sh):
The previous marker-discovery code did:
  ((.reviews // []) + (.comments // [])) | .[].body | grep marker | tail -1
This relies on the array concatenation order, not chronological order. When
old agent comments existed alongside newer agent reviews, tail -1 picked
the comment-array marker (older) over the review-array marker (newer),
causing the script to think the head SHA hadn't been reviewed and re-run.
Replaced with a single jq pipeline that tags each item with submittedAt /
createdAt, sorts by timestamp, and takes the actual most-recent marker.

Bug B — no cleanup of prior agent items (post-pr-review.sh):
After successfully posting a new review/comment, prior agent items were
left in place, accumulating forever. Added mark_prior_agent_items_obsolete
which, after a successful post:
  - dismisses prior APPROVED/COMMENTED/CHANGES_REQUESTED agent reviews via
    the GitHub dismissal API (UI shows them struck-through as Dismissed)
  - edits prior agent comments to wrap their body in a collapsed <details>
    block with a "Superseded by re-review at <SHA>" summary, plus a
    `<!-- pr-review-agent superseded -->` sentinel for idempotency

All cleanup API calls are best-effort — failures don't break the workflow.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(cleanup): warn on API failures, preserve global newest, file-stage JSON

Three fixes to mark_prior_agent_items_obsolete from the review of PR #20:

1. ::warning:: annotations on every cleanup API failure (review/comment
   list-fetch, individual review dismissal, individual comment fetch+edit).
   Previously these were silenced with `|| true`, so a permissions change
   on the dismissal endpoint would let duplicates stack indefinitely with
   no signal in the Actions UI. Cleanup is still non-fatal — the new post
   has already landed — but failures are now visible.

2. Preserve the globally-latest agent item across BOTH categories, not the
   newest of each category separately. The earlier code split reviews and
   comments and applied `[:-1]` to each, which left a stale fix-request
   comment in place when the new post was a review (or vice versa). The
   one-off cleanup of ContentTwin#100 hit exactly this case: 12 stacked
   reviews collapsed to 1, but a stale comment from 2026-04-25 (SHA
   cd9132d6) was preserved as "newest comment" even though the latest
   review at SHA 3af8c8ee was newer overall. Now: compute the max
   timestamp across both feeds, exclude items at that timestamp.

3. Stage API responses to disk (`mktemp` + `jq <file>`) instead of routing
   through `--argjson "$var"`. The old approach broke on rare unescaped
   control chars in user-authored comment bodies (jq refused to parse the
   resulting shell-vared JSON). File-based input sidesteps the shell
   pipeline entirely.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>

* Remove car-hunt skill and scheduled tasks (moved to don-petry/don-petry) (#21)

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: apply Copilot fallback hardening and reuse GH_PAT for Copilot auth

- Use secrets.GH_PAT (existing personal account token with Copilot
  subscription) instead of a separate COPILOT_GITHUB_TOKEN secret
- Pin actions/create-github-app-token to SHA (v3.1.1) for supply chain safety
- Add post-install verification for gh-copilot extension with warning on failure
- Guard Copilot fallback path: skip PR gracefully if extension is not installed

Addresses issue #24 workflow fixes (1-3) that could not land via PR #25
due to GitHub App lacking workflows permission.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: switch Copilot engine to gh built-in and fix app-id deprecation

- Remove gh extension install (github/gh-copilot conflicts with built-in alias)
- Replace with gh copilot --version check using COPILOT_GITHUB_TOKEN (GH_PAT)
- Update all engine.sh copilot invocations from bare `copilot` binary to
  `gh copilot suggest --target shell` with GH_TOKEN overridden to GH_PAT
  so the user token (with Copilot subscription) is used, not the App token
- Fix actions/create-github-app-token: app-id → client-id (same secret value,
  just the renamed input in v3.x)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: harden Copilot fallback path and remove stale worktrees (#25)

- gh-copilot install: add --force flag and post-install verification
  warning so silent failures due to built-in alias conflict are visible
  in workflow logs instead of being silently swallowed by `|| true`
- fallback pre-flight check: verify `gh extension list | grep copilot`
  before switching to Copilot engine; if unavailable, skip the PR and
  continue the batch rather than session-aborting and dropping all
  remaining candidates (fixes the 27-PR drop on run #503)
- pin actions/create-github-app-token to SHA for v3.1.1 (Node.js 24
  compatible) to prevent hard break on 2026-06-02 when GitHub forces
  Node.js 24 as default runtime
- remove 6 stale Claude Code worktrees from git tracking; add
  .gitignore entry to prevent future worktrees from being committed
  (eliminates exit-128 warning on every actions/checkout post-job sweep)

Closes #24

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: petry-projects-pr-review-agent[bot] <petry-projects-pr-review-agent[bot]@users.noreply.github.com>

* ci: add pre-flight dedup check to prevent duplicate claude-issue PRs (#26)

Before invoking Claude on a labeled issue, a new shell step queries
for any open PR with a branch matching claude/issue-NNN-* (or a body
containing "Closes #NNN"). If one is found it posts a comment on the
issue pointing to the existing PR and skips the Claude step entirely.
A secondary prompt instruction tells Claude to check first and push to
the existing branch rather than opening a new PR.

Motivated by issue petry-projects/google-app-scripts#171, where the
claude label was re-applied three times and each run created a fresh
PR unaware of the prior attempts.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor: migrate to machine user PAT auth (closes #27) (#28)

* refactor: migrate from GitHub App to machine user PAT auth

Closes #27

GitHub Apps cannot be listed in CODEOWNERS, blocking PRs in repos with
require_code_owner_review: true. Switch all workflows to use a machine
user account's fine-grained PAT (DON_PETRY_BOT_GH_PAT secret), which can
join an org team listed in CODEOWNERS.

Workflows: removed actions/create-github-app-token steps in pr-review,
fix-stuck-prs, backfill-approvals, and daily-pr-review-health. All now
use secrets.DON_PETRY_BOT_GH_PAT directly.

Docs: renamed GITHUB_APP_SETUP.md to MACHINE_USER_SETUP.md with full
rewrite covering account creation, CODEOWNERS config, PAT generation,
and rotation. Updated auth sections in IMPLEMENTATION.md, SETUP.md,
STATUS.md, DOCUMENTATION.md, README.md.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* refactor: switch to org-scoped PAT secret DON_PETRY_BOT_PETRY_PROJECT_PAT

Replace DON_PETRY_BOT_GH_PAT with DON_PETRY_BOT_PETRY_PROJECT_PAT — the
new fine-grained PAT scoped to the petry-projects org (resource owner =
petry-projects). The previous PAT was scoped to the donpetry-bot personal
namespace and had no repository access.

The old DON_PETRY_BOT_GH_PAT secret is retained for any future use against
don-petry's personal repos.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* chore: ignore .claude/scheduled_tasks.lock state file

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>

* refactor: merge approval repair scripts into single automation

- Consolidate backfill-approvals.sh and fix-stuck-prs.sh into repair-pr-approvals.sh
- Iterate all repos in both orgs instead of assuming author
- Verify no existing APPROVED review before posting
- Copy original agent comment as review body
- Enable auto-merge when posting approval if needed
- Single workflow with 30-min timeout

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: add @mention trigger for on-demand PR reviews (#30)

Adds repository_dispatch support so commenting @petry-review-bot on any
PR fires an immediate review without waiting for the hourly schedule.

- pr-review.yml: new `repository_dispatch` trigger (type: pr-review-mention),
  per-PR concurrency group for mention runs, FORCE_REVIEW and DRY_RUN
  env vars that handle both workflow_dispatch and repository_dispatch paths
- review-one-pr.sh: FORCE_REVIEW=true bypasses idempotency so a mention
  always runs a fresh cascade even if the head SHA hasn't changed
- templates/mention-listener.yml: deploy to petry-projects/.github;
  listens for @petry-review-bot, validates commenter trust, posts ack,
  sends repository_dispatch (requires Contents:write, not Actions:write)

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: harden rebase, review dismissal, and health log diagnostics (#40)

Addresses 4 code-actionable recommendations from health check report #33:

- [CRITICAL] Make rebase/branch-update non-fatal — 403/504 during rebase
  emits ::warning:: instead of aborting the batch session (#34)
- [MEDIUM] Add retry with exponential backoff (3 attempts) for transient
  5xx on update-branch API; break immediately on 4xx (#37)
- [MEDIUM] Guard review dismissal with state re-check before calling
  dismiss API, preventing 422s from race conditions (#38)
- [LOW] Surface missing log warnings in health check script (#39)
- Fix: skip auto-merge when branch is still BEHIND after failed rebase

Closes #34, closes #37, closes #38, closes #39

* refactor: migrate to org-wide .github-private convention (#41)

* refactor: parametrize hardcoded identity values for org migration

Replace all hardcoded references to don-petry, petry-review-bot, and
don-petry/pr-review-agent with environment variables that default to
the current values. This allows the agent to be configured for different
orgs/users via repo variables.

Changes:
- Scripts use $REVIEWER_USER, $TARGET_ORG, $BOT_USER, $AGENT_REPO
- Prompts no longer reference specific GitHub usernames
- Workflows use org-level GH_PAT_WORKFLOWS secret instead of
  repo-level DON_PETRY_BOT_PETRY_PROJECT_PAT
- Health check uses context.repo.owner/repo for issue creation
- Variables are set in workflow env block with defaults

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add Copilot custom agent profiles for org-wide use

Create /agents/ directory with three agent profiles:
- pr-reviewer: Multi-tier cascading PR review
- feature-ideator: Feature idea generation and prioritization
- compliance-auditor: Org standards compliance checking

These are Copilot custom agent profiles that become available
org-wide in the .github-private repo convention.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Squashed 'frameworks/bmad-method/' content from commit e36f219c

git-subtree-dir: frameworks/bmad-method
git-subtree-split: e36f219c81b6010d4aae423ba12f49edb5b6e31a

* Squashed 'frameworks/spec-kit/' content from commit 11f49ebf

git-subtree-dir: frameworks/spec-kit
git-subtree-split: 11f49ebfb2f6af55345cb4bd9a7906acd211e56f

* Squashed 'frameworks/gsd/' content from commit 304c1a13

git-subtree-dir: frameworks/gsd
git-subtree-split: 304c1a1302564c45af252bbba4bcc5350e7dac3a

* docs: update README for .github-private org infrastructure role

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* ci: add dependabot-automerge.yml workflow (#73)

Adds the required dependabot-automerge.yml workflow from the org
standard template (petry-projects/.github/standards/workflows/).
This is a thin caller stub that delegates to the org-level reusable
workflow.

Closes #48

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Don Petry <don-petry@users.noreply.github.com>

* fix(pr-review): unblock queue starvation from self-authored PRs (#96) (#97)

* fix(pr-review): unblock queue starvation from self-authored PRs (#96)

A self-authored PR sorted first in the candidate list and triggered
"Can not approve your own pull request" — which the session-fatal
abort treated like an engine error, skipping all 28 remaining PRs
on every run.

- list-prs.sh: filter out PRs authored by REVIEWER_USER at enumeration
- post-pr-review.sh: catch the GraphQL self-approval error and exit 100
  (no-op) instead of 1, so a stray self-PR can no longer abort the batch
- engine.sh: gh copilot renamed --target to --agent; fix the rubber-duck
  invocations so tier-2 cross-engine review works again
- review-one-pr.sh: stop appending a duplicate "0" to REVIEW_CYCLE when
  grep -c finds no markers under set -o pipefail (was breaking the
  cycle-cap integer comparison)

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

* fix(pr-review): authenticate as bot, harden enumeration, address review

- Workflow now runs as AGENT_USER (default don-petry-bot), distinct from
  REVIEWER_USER (the human, don-petry). The self-approval rejection that
  caused #96 came from the agent and the human sharing one identity.
- list-prs.sh filters self-authored PRs against AGENT_USER, and validates
  AGENT_USER against the GitHub username charset before interpolating into
  the jq filter (Copilot review feedback).
- review-one-pr.sh uses printf '%s\n' instead of echo for PR_BODIES, since
  PR body content is user-authored and could begin with -n/-e or contain
  backslash escapes (Copilot review feedback).
- AGENT.md guideline updated to reflect that self-authored PRs are
  intentionally excluded (CodeRabbit review feedback).

Operator note: rotate the GH_PAT_WORKFLOWS secret to a token owned by
don-petry-bot (with read:org added) for the bot-account behavior to take
effect at runtime.

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

* refactor(pr-review): collapse to single BOT_USER, route escalations via CODEOWNERS

Drop AGENT_USER and REVIEWER_USER. The workflow now has one identity:
BOT_USER (default don-petry-bot), which both owns the repos to scan and
gets filtered out as the self-approval blocker. Human escalation no
longer hard-codes a single reviewer; instead, scripts/request-codeowners-
review.sh parses CODEOWNERS in the PR's repo and requests review from
every @user / @org/team mention.

- list-prs.sh: BOT_USER for both gh-repo-list and self-author filter
- review-one-pr.sh, post-pr-review.sh: replace --user "$REVIEWER_USER"
  with the CODEOWNERS helper
- repair-pr-approvals.sh: same gh-repo-list switch
- pr-review.yml: drop AGENT_USER/REVIEWER_USER, set BOT_USER default
  to don-petry-bot
- AGENT.md: updated guideline to describe single-identity model

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

* refactor(pr-review): extract batch loop, hoist env, drop redundant step

Workflow YAML drops from 231 to 111 lines by moving the per-PR review
loop into scripts/review-batch.sh. The script can be syntax-checked and
exercised locally; the workflow now just wires env vars and dispatches.

- Hoist GH_TOKEN, MAX_PRS, CANDIDATE_LIMIT to job-level env (were
  repeated on individual steps).
- Inline `gh auth status` into the install step; remove the standalone
  Verify auth step (its only output was a one-line auth dump).
- Drop the `if: steps.list.outputs.count != '0'` guard and the step's
  `id`/output: review-batch.sh handles empty input as a no-op.
- Collapse the duplicated summary-string branch in the review loop into
  a single template with a conditional fragment.

No behavior change.

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

* perf(pr-review): cache claude-code CLI install across runs

`npm install -g @anthropic-ai/claude-code` ran on every workflow start
(~30s). Switch to a per-user npm prefix (~/.npm-global) and cache that
directory via actions/cache, keyed on CLAUDE_CODE_VERSION + runner OS.

A `command -v claude` guard makes the install a no-op on cache hit, so
the only cost on subsequent runs is the cache restore.

CLAUDE_CODE_VERSION defaults to 'latest' (cache persists until manually
flushed); set the repo variable to pin a specific version for fully
reproducible caching.

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

---------

Co-authored-by: Claude <noreply@anthropic.com>

* chore: rename GH_PAT_WORKFLOWS secret to DON_PETRY_BOT_GH_PAT (#99)

The previous name was generic and didn't tell you which account the PAT
belonged to. The new name makes the binding explicit: this secret is the
PAT owned by don-petry-bot, used as BOT_USER throughout the PR-review
workflows.

Operator follow-up before this can be merged:
- Add a new repo secret DON_PETRY_BOT_GH_PAT containing the bot's PAT
  (with repo, workflow, and read:org scopes).
- After merge, the old GH_PAT_WORKFLOWS secret can be deleted.

Affected workflows:
- pr-review.yml (1 use)
- claude.yml (4 uses, with || github.token fallback)
- daily-pr-review-health.yml (1 use)
- repair-pr-approvals.yml (1 use)

scripts/pr_review_health.sh's error message is updated to point at the
new name as well.

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

Co-authored-by: Claude <noreply@anthropic.com>

* chore: remove frameworks directory (#101)

Co-authored-by: Gemini CLI <gemini-cli@example.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore all erroneously deleted files (items 4-34) (#104)

* revert: restore .github/workflows/daily-pr-review-health.yml

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore .github/workflows/repair-pr-approvals.yml

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore agents/pr-reviewer.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/cascade-action.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/deep-review.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/rubber-duck.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/security-audit.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/shared.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/single-review.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/synthesize-duck.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/synthesize.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/triage.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/engine.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/list-prs.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/post-pr-review.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/pr_review_health.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/repair-pr-approvals.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/request-codeowners-review.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/review-batch.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/review-one-pr.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore templates/mention-listener.yml

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore AGENT.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore BOT_SETUP.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore DOCUMENTATION.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore IMPLEMENTATION.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore MACHINE_USER_SETUP.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore PR_REVIEW_AGENT_FAILURE_REPORT.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore PR_REVIEW_FAILURE_INVESTIGATION.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore SETUP.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore STATUS.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore README.md to pre-change state

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore .github/workflows/daily-pr-review-health.yml

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat: add Gemini Pro support and optimize PR review fallback chain (#102)

* Merge main and resolve conflicts

* Enhance pr-review workflow: add Gemini support and refactor fallback logic

* Address Copilot review comments: tighten regex, fix fallback summary, and align docs

* docs: move agent documentation to docs/ folder and rename files

* docs: align secret names and fix casing in index

* docs: organize pr-review-agent documentation under dedicated folder

* Address final PR comments: tighten regex, add preflight checks, fix bot names, and cleanup docs

---------

Co-authored-by: Gemini CLI <gemini-cli@example.com>

* fix(pr-review): gate rate-limit detection on non-zero exit code

Broad patterns like `plan.*limit` and `claude.*usage` could match content
in a successful triage summary, triggering a false-positive engine fallback.
Guard the check with TRIAGE_RC != 0 so it only fires when the provider
command actually failed.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(claude): sync inlined workflow with org standard (#109)

* fix(claude): sync inlined workflow with org standard

Brings the inlined claude.yml up to parity with
petry-projects/.github/.github/workflows/claude-code-reusable.yml.

Changes:
1. Bot allow list for pull_request_review_comment
   Add coderabbitai[bot], Copilot, copilot-pull-request-reviewer[bot],
   and gemini-code-assist[bot] alongside the existing OWNER/MEMBER/
   COLLABORATOR check. These bots have author_association 'NONE' so
   their review comments were always skipped.

2. check_run trigger + claude-ci-fix job
   Port the CI failure auto-fix feature from the reusable. When a check
   fails on a PR, Claude diagnoses and fixes it automatically.
   Adapted to use DON_PETRY_BOT_GH_PAT (this repo's secret name).

3. claude-code-action SHA bump: v1.0.89 → v1.0.119
   Update both the claude and claude-issue jobs.

4. paths-ignore on pull_request trigger
   Prevents the workflow from firing on PRs that only change
   claude.yml itself, avoiding the Anthropic OIDC validation
   failure (workflow file must match default branch at token exchange).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(claude): guard bot allow list against fork PRs

Same fix as petry-projects/.github PR #238 — add same-repo guard
for bot-triggered pull_request_review_comment runs.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(claude): address ci-fix self-loop and fork PR security

- Replace Claude Code name prefix check with explicit job name list to
  correctly prevent self-loops (check_run names for inlined workflows
  are bare job names, not workflow-prefixed)
- Add fork PR trust gate in Resolve PR number step: verify head repo
  matches target repo before running Claude with privileged credentials

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* ci: replace inlined claude.yml with standard thin-caller stub

The inlined version embedded all three jobs (claude, claude-ci-fix,
claude-issue) directly and threaded DON_PETRY_BOT_GH_PAT through
checkout tokens and github_token, causing the bot's PAT to author
PRs and comments instead of github-actions[bot].

Replace with the standard thin caller that delegates to the org-level
reusable workflow (claude-code-reusable.yml@v1) via secrets: inherit,
which is the correct pattern per:
petry-projects/.github/standards/workflows/claude.yml

* fix: replace gh copilot suggest with GitHub Models REST API (#151)

* fix: replace gh copilot suggest with GitHub Models REST API (#147)

The `gh copilot suggest -p "$(cat <file>)"` invocation failed with
"Invalid command format" because:
1. The `-p` flag is not valid syntax in modern `gh copilot` built-in versions.
2. `gh copilot suggest` is a shell-command suggestion tool; it does not
   accept large PR prompts or return structured JSON responses.
3. Passing a multi-thousand-line prompt via `$(cat ...)` can hit ARG_MAX.

The non-zero exit was then misclassified by the rate-limit detector, which
aborted the entire session and skipped all remaining PRs in the queue.

Fix: replace all three copilot invocations (run_triage, run_agentic,
run_duck) with a new `copilot_chat` helper that calls the GitHub Models
REST API directly via curl. The API is OpenAI-compatible, versioned via
`X-GitHub-Api-Version`, stable against gh CLI version changes, and accepts
arbitrary prompt sizes using python3 for safe JSON encoding.

Additional changes:
- Add `COPILOT_API_MODEL` var (default `openai/o4-mini`) overrideable via env.
- run_agentic/run_duck copilot cases now also write output to $OUTPUT_FILE
  so callers that check that path directly (deep review, audit) find the JSON.
- Rate-limit responses (HTTP 429) are echoed to stdout so the existing
  `is_rate_limited()` detector fires correctly for engine fallback.
- Add pre-flight smoke test in review-batch.sh: tests GitHub Models API
  connectivity with a one-liner prompt before processing any PRs, so auth
  or model errors surface immediately as a clear setup failure.
- Add tests/test_copilot_chat.sh: unit tests for the JSON payload builder
  with edge-case prompts (quotes, newlines, # headings, large diffs, Unicode).

Closes #147

Co-authored-by: Don Petry <don-petry@users.noreply.github.com>

* fix: scope COPILOT_API_MODEL export to copilot engine only

Move the `export COPILOT_API_MODEL` from the common exports block into the
`copilot)` case where the variable is set, so it is not exported as an
empty/unset variable when the engine is `claude` or `gemini`. The
`copilot_chat` fallback `${COPILOT_API_MODEL:-openai/o4-mini}` still works
correctly when `DUCK_ENGINE=copilot` under a non-copilot primary engine.

Co-authored-by: Don Petry <don-petry@users.noreply.github.com>

* fix: address review comments — temp file for payload, fail-fast source, streaming output

- copilot_chat: write JSON body to mktemp file, pass to curl as @file to
  avoid ARG_MAX for large PR diffs (was --data-binary "$body")
- copilot_chat: add :? guard on COPILOT_GITHUB_TOKEN for a clear error
  instead of generic "unbound variable" under set -u
- run_agentic / run_duck copilot paths: stream directly to stdout (and
  tee to OUTPUT_FILE when set) rather than buffering the full response
  into a shell variable, which forced large outputs into memory and
  stripped trailing newlines
- review-batch.sh pre-flight: fail fast if source engine.sh fails
  (was silently ignored with || true)
- review-batch.sh pre-flight: build smoke-test JSON payload via python3
  + temp file instead of shell string interpolation to avoid JSON
  injection if COPILOT_API_MODEL contains special characters
- Clarify that openai/o4-mini is the correct April-2025 o4-generation
  model name, not a typo for o1-mini or gpt-4o-mini

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Don Petry <don-petry@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* feat: prioritise .github/.github-private PRs, oldest-first within tier (#155)

* feat: prioritise .github/.github-private PRs, oldest-first within tier

- list-prs.sh: add createdAt to JSON fetch; emit priority|createdAt|url
  lines (priority 0 for .github/.github-private, 1 for everything else)
- Replace final sort -u with a two-pass sort: deduplicate by URL, then
  sort by priority asc then createdAt asc; strip sort keys with cut
- tests/test_list_prs_sort.sh: 16 tests covering priority classification,
  oldest-first ordering, mixed scenarios and deduplication
- .github/workflows/test.yml: run both unit-test files on every PR / push

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* test: add dedup edge case for same URL with conflicting priorities

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: remove temperature from copilot_chat payload (o4-mini incompatible)

The GitHub Models API rejects temperature=0 for reasoning models like
o4-mini with HTTP 400:
  'Unsupported value: temperature does not support 0 with this model.
   Only the default (1) value is supported.'

This caused the rubber duck (DUCK_ENGINE=copilot / DUCK_MODEL=o4-mini)
to fail with HTTP 400 on every claude-engine run, silently degrading
every review to deep-only with no cross-engine sanity check.

Fix: remove temperature from the copilot_chat JSON payload entirely —
the API defaults to 1, which is the only supported value for o4-mini.

Also:
- Update tests/test_copilot_chat.sh: sync build_payload to match and
  flip Test 9 to assert temperature is ABSENT (not 0).
- Add .github/workflows/test.yml: run unit tests on every PR and push
  to main so regressions are caught before merge.

Reproducer: job/75690347409 — duck failure logged as:
  copilot_chat: HTTP 400 from GitHub Models API
  {"error":{"message":"Unsupported value: 'temperature' does not
   support 0 with this model..."}}

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: address PR review comments

- list-prs.sh: tighten priority regex to /[.]github(-private)?/pull/
  so path-boundary anchor prevents false positives (e.g. foo.github)
- list-prs.sh: replace printf pipeline with here-string to avoid ARG_MAX
  limits on large PR sets
- test.yml: add permissions: contents: read (least-privilege, CodeQL fix)
- test.yml: add timeout-minutes: 5 to prevent hung runs
- test_list_prs_sort.sh: sync sort_entries helper (here-string + || true)
  and classify regex to match updated list-prs.sh exactly

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: don-petry <don@petry.dev>

* fix: restore pr-review.yml content (accidentally emptied in revert)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: restore pr-review.yml (file was accidentally emptied)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore(deps): bump actions/cache from 4 to 5 (#165)

Bumps [actions/cache](https://github.com/actions/cache) from 4 to 5.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): bump petry-projects/.github/.github/workflows/claude-code-reusable.yml (#164)

Bumps [petry-projects/.github/.github/workflows/claude-code-reusable.yml](https://github.com/petry-projects/.github) from 1 to 2.
- [Commits](https://github.com/petry-projects/.github/compare/v1...v2)

---
updated-dependencies:
- dependency-name: petry-projects/.github/.github/workflows/claude-code-reusable.yml
  dependency-version: '2'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* fix(list-prs): search DELEGATION_ORGS and drop --checks success pre-filter

- Add iteration over all orgs in DELEGATION_ORGS (e.g. don-petry) so PRs
  in those orgs enter the candidate pool. Previously only BOT_USER and
  TARGET_ORG were searched.
- Remove --checks success from org repo searches. GitHub excludes PRs from
  repos with no CI configured when this flag is used, silently dropping all
  PRs from repos like .github, .github-private, google-app-scripts, and
  TalkTerm. review-one-pr.sh already enforces CI gating per-PR and treats
  empty statusCheckRollup as passing, so the pre-filter is redundant and
  harmful.
- Add --limit 200 to gh repo list calls to handle org growth beyond gh's
  default 30-repo cap.

* feat: add code-quality ruleset (compliance fix #60) (#86)

Creates the required `code-quality` repository ruleset enforcing required
status checks on the default branch, as mandated by the org standard:
standards/github-settings.md#code-quality--required-checks-ruleset-all-repositories

Required checks:
- SonarCloud (code quality analysis)
- CodeQL (SAST)
- agent-shield / AgentShield (agent security scan)
- dependency-audit / Detect ecosystems (dependency vulnerability scan)

Bypass actors:
- OrganizationAdmin (always) — emergency override
- dependabot-automerge-petry Integration (always) — Dependabot auto-merge

The ruleset was applied directly via GitHub API. This file documents the
configuration as code for auditability and future reapplication.

Closes #60

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Don Petry <don-petry@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* feat(prompts/dev-lead): add human prompt template

* feat(prompts/dev-lead): add human-pr prompt template

* feat(dev-lead): implement dev-lead agent Phases 0-6

* fix: per-PR isolation and single-review retry (closes #132)

- review-batch.sh: non-rate-limit per-PR failures (exit code 1) no
  longer abort the session. SESSION ABORTED EARLY is now reserved for
  the rate-limit-on-fallback-engine case (exit code 2) only. All other
  failures are counted and logged; remaining candidates continue.

- review-one-pr.sh: single-review step retries up to
  SINGLE_REVIEW_MAX_RETRIES (default 2) times with a
  SINGLE_REVIEW_RETRY_DELAY_SEC (default 15s) gap before giving up.
  On exhaustion, the PR is flagged needs-human-review and the script
  exits with code 1, which the updated batch treats as a non-fatal
  per-PR failure. Raw model output and stderr are logged on each failed
  attempt for post-mortem visibility.

Root cause of run #25707852006: claude-opus-4-7 returned a verbose
non-JSON response for PR #129; the old code treated that as fatal and
skipped 35 remaining candidates.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix: rate-limit detection and per-attempt stderr logs in single-review retry

Address inline review comments on PR #133:

- Rate-limit check: after each run_agentic call, inspect both stdout
  (VERDICT_JSON.raw) and stderr (SINGLE_LOG) with is_rate_limited before
  retrying. A rate-limit match exits immediately with code 2 so
  review-batch.sh can trigger engine fallback — consistent with triage and
  deep-review tiers. Previously a rate-limited single-review would burn all
  retries and exit 1 (per-PR failure), silently leaving the batch on the
  same rate-limited engine for all remaining PRs.

- Per-attempt log files: stderr is now written to
  single-review-attempt-N.log rather than a single overwritten file, so
  no earlier-attempt errors are lost. Each attempt logs its own stderr
  inline on failure; the fallback path cats all attempt logs for
  post-mortem visibility.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* feat(dev-lead): Phase 0 test infrastructure and Phase 1 intent stub

Phase 0: full test harness for the dev-lead agent —
  26 event fixtures (all valid JSON with _test_expected_intent),
  stub claude/gemini engines, mock gh binary, CI failure log sample,
  bats helpers (stub-engine, mock-gh, assert-env, prompt-vars),
  7 prompt templates with VARIABLES declarations, preflight script,
  prompt coverage integration test, and test-dev-lead.yml CI workflow.

Phase 1: dev-lead.yml trigger workflow (all 7 event types, dispatch + ci-relay
  jobs) and dev-lead-intent.sh stub (anti-loop guard live; all other events
  emit skip/not-implemented).  14/14 bats unit tests pass.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): add permissions blocks to test workflow jobs (CodeQL)

* fix(ci): auto-fix for lint / eslint [skip ci-relay]

* feat(dev-lead): Phases 2-6 — CI fix, review fix, issue, engine fallback

Phase 2: run_writer/run_writer_with_fallback in engine.sh, full intent
routing in dev-lead-intent.sh, dev-lead-fix-ci.sh handler, workflow wiring.

Phase 3: dev-lead-fix-reviews.sh handles fix-reviews, fix-bot-comment,
human, human-pr, rebase intents. Full review routing in intent classifier.

Phase 5: dev-lead-fix-issue.sh handles the issue intent with dedup guard
and branch/PR creation.

Phase 6: run_writer_with_fallback with claude→gemini→copilot fallback chain.

Tests: 77 unit tests across 8 new .bats files, all passing. Updated Phase 1
stub tests to reflect actual Phase 2+ routing behavior.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): address review comments — dispatch JSON, dead code, portable stub gh

* fix(dev-lead): pre-flight after intent, fork URL check, review-batch exit code, retry comment

* fix(dev-lead): install bats-core from GitHub to avoid root requirement

* fix(security): move event values to env vars to prevent script injection (SonarCloud)

* fix(security): move CLAUDE_CODE_VERSION to env block (SonarCloud script injection)

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* feat(dev-lead): Phase 1.5 — reusable workflow for cross-repo adoption + Phase 7 shadow period

* feat(dev-lead): add reusable workflow (Phase 1.5) and begin shadow period (Phase 7)

- Create .github/workflows/dev-lead-reusable.yml: workflow_call entry
  point for other repos. Checks out .github-private scripts/prompts into
  .dev-lead/, then runs the same intent-classify + handler pipeline as
  dev-lead.yml with PROMPTS_DIR=.dev-lead/prompts/dev-lead.
- Add PROMPTS_DIR env-var support to dev-lead-fix-ci.sh,
  dev-lead-fix-reviews.sh, and dev-lead-fix-issue.sh so the reusable
  workflow can point scripts at the sparse-checkout path without changing
  CWD. Defaults to prompts/dev-lead (backwards-compatible for dev-lead.yml).
- Annotate dev-lead.yml with Phase 7 shadow-period window (2026-05-15
  through ~2026-05-29): claude.yml and dev-lead.yml run in parallel until
  regressions clear, then claude.yml is removed.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* chore(dev-lead): update shadow-period tracking issue ref to #180

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): document PAT requirement and pin ref:main on private checkout

* fix(dev-lead): P1 review fixes — ci-relay in reusable, .dev-lead gitignore, export PROMPTS_DIR, trim header

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): rate-limit detection, separate status, and retry cron (#192) (#196)

* chore(dev-lead): decommission claude.yml, bump ACTION_TIMEOUT_SEC to 600s

- Delete .github/workflows/claude.yml — replaced by dev-lead.yml (shadow
  period complete, tracking issue #180).
- scripts/engine.sh: raise ACTION_TIMEOUT_SEC default 300→600s to reduce
  timeout failures on large-repo fix-ci runs (PR #80 had 3 timeouts).
- AGENTS.md: remove claude.yml immutability exemption; note dev-lead.yml
  as the active AI automation workflow and its edit-via-reusable pattern.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* refactor: replace Claude analysis with pure gh/jq telemetry in health check

* refactor: drop Node/Claude steps from daily-pr-review-health workflow

* rename: daily-pr-review-health → actions-fleet-monitor

* rename: daily-pr-review-health → actions-fleet-monitor

* fix(agents-md): clarify dev-lead.yml vs dev-lead-reusable.yml scope

dev-lead.yml in .github-private runs inline steps (not a caller stub).
Behavior changes for this repo go to dev-lead.yml directly; changes that
affect all org repos via the cross-repo reusable go to dev-lead-reusable.yml.

Addresses Copilot review on PR #194.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): rate-limit detection, separate status, and retry cron

Closes #192. Implements all phases from the revised plan.

**Phase 0 — fix run_writer stdout capture (prerequisite)**
- engine.sh: capture stdout via `tee` to a tempfile so `is_rate_limited`
  can inspect the output; old code read `/tmp/dev-lead-writer-stderr`
  which was never written (claude --print outputs to stdout, not stderr).
  Fallback engines were never tried in practice.
- engine.sh: add `parse_reset_time` to extract ISO timestamp from
  `resets H:MMpm (UTC)` in engine output and write it to
  `/tmp/dev-lead-rate-limit-reset` for callers to embed in markers.

**Phase 1 — separate status=rate-limited from status=failed**
- fix-ci.sh: detect engine exit 2 → post `status=rate-limited` (not
  `status=failed`); embed parsed reset time in marker body; exit 2.
- fix-ci.sh: fix `check_idempotency` to treat `status=rate-limited`
  as retriable — only block on terminal statuses (applied, failed,
  no-changes). Previously, rate-limited markers blocked all retries.
- fix-ci.sh: `count_recent_failures` already filtered to `status=failed`;
  add explicit comment confirming rate-limited markers are excluded.
- fix-ci.sh: add `has_rate_limited_marker` dedup check so the same SHA
  never accumulates more than one rate-limited comment.
- fix-reviews.sh: on engine exit 2 for all five intent types
  (fix-reviews, fix-bot-comment, human, human-pr, rebase), post a
  `status=rate-limited` marker with embedded reset time and exit 2.
- fix-reviews.sh: for `human` and `human-pr` intents, also post a
  user-visible acknowledgment comment so users know their request was
  received and will be retried.

**Phase 2 — scheduled retry cron for fix-ci**
- dev-lead-retry.sh: new script scans all open PRs across TARGET_ORG
  for `status=rate-limited` markers on current HEAD SHA; skips PRs
  whose reset time is still in the future; staggered dispatches (30s
  between repos) to prevent cascading org-level rate-limit hits.
- dev-lead-retry.yml: new workflow — scheduled every 2 hours (dual
  offsets to work around GitHub scheduler skew), plus workflow_dispatch.

**Phase 3 — retry for fix-reviews intents (same cron)**
- dev-lead-retry.sh already handles all five fix-reviews intent types
  (fix-reviews, fix-bot-comment, human, human-pr, rebase) via
  `dev-lead-reviews-retry` dispatch events.
- dev-lead-intent.sh: add `dev-lead-reviews-retry` dispatch type routing
  to the intent classifier — maps intent_type from payload to the correct
  existing fix-reviews step.
- dev-lead.yml: add `dev-lead-reviews-retry` to repository_dispatch types.
- dev-lead.yml: unify concurrency group for all dispatch types to
  `dev-lead-pr-{pr_number}` to avoid per-type slot fragmentation.

**Tests**
- test_engine_writer.bats: 8 new tests covering rate-limit stdout detection,
  exit-2 mapping, fallback exhaustion, reset time parsing.
- test_fix_ci.bats: 5 new tests covering rate-limited status, exhaustion
  exclusion, idempotency pass-through, and dedup.
- test_fix_reviews.bats: 5 new tests covering all intent types + human ack.
- test_intent_ci.bats: 4 new tests for dev-lead-reviews-retry dispatch routing.
- e2e/scenarios/07-rate-limit-retry.sh: new E2E scenario covering all parts.
- 3 new event fixtures for repository_dispatch_reviews_retry_* payloads.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): address review comments on #196

Addresses all 18 review threads from Copilot, Codex, and Gemini.

**P1 bugs fixed:**
- dev-lead-retry.sh: all log echo calls in scan_pr_for_rate_limits and
  dispatch_* functions now write to stderr; only the final count is on
  stdout. Previously, log lines polluted the command substitution,
  causing arithmetic expansion to fail and aborting the scan on the
  first retry candidate.
- dev-lead-retry.yml: permissions: contents was read-only; creating
  repository_dispatch events requires contents: write.

**Correctness fixes:**
- dev-lead-retry.sh: restrict automated retries to intents whose context
  can be reconstructed at runtime (fix-reviews, human-pr, rebase). human
  and fix-bot-comment require USER_INSTRUCTION/COMMENT_BODY from the
  original event which cannot be re-fetched; they are explicitly excluded.
- dev-lead-fix-reviews.sh: resolve HEAD_SHA from the PR API when not
  provided by the triggering event (issue_comment intents carry no SHA).
  Without this, rate-limited markers had no sha= field and were
  invisible to the retry scanner.
- dev-lead-fix-reviews.sh: write terminal status=applied marker after
  successful fix-reviews, human-pr, rebase runs. Prevents the retry
  cron from re-dispatching the same intent on every subsequent tick when
  the SHA hasn't changed.
- dev-lead-retry.sh: check for reviews terminal marker before dispatching
  a retry (mirrors the existing fix-ci terminal-marker check).
- dev-lead-retry.sh: look up current check-run details (details_url, id)
  at dispatch time via the commits check-runs API, so retried fix-ci runs
  have full failure logs and annotations rather than empty fields.
- dev-lead-fix-ci.sh: embed check= field in the rate-limited marker so
  the retry cron knows which check run to look up.
- dev-lead-fix-reviews.sh: human intent now posts a "please re-mention
  @dev-lead" ack instead of "I'll retry automatically" since it will NOT
  be retried automatically. human-pr keeps the auto-retry ack (correct).

**Pagination (Copilot + Gemini):**
- dev-lead-fix-ci.sh: add --paginate to check_idempotency, count_recent_
  failures, has_rate_limited_marker.
- dev-lead-fix-reviews.sh: add --paginate to has_reviews_rate_limited_marker.
- dev-lead-retry.sh: add --paginate to PR comments and PR list fetches.

**Other improvements:**
- dev-lead-retry.yml: simplify to single cron schedule (15 */2 * * *);
  remove BOT_USER env var (unused in the script).
- dev-lead-retry.sh: raise repo list limit 200 → 1000; add hard-error
  when list_repos_for_org returns empty (surfaces token permission issues
  rather than silently scanning 0 repos).
- dev-lead-intent.sh: use jq -nc (compact, null-input) for context JSON
  construction — safe against values with special characters.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(pr-review): handle Gemini trust-mode failure and properly route engine fallbacks (#217)

* fix(pr-review): handle Gemini trust-mode failure and properly route engine fallbacks

Closes #208

* fix(ci): install gemini CLI and set trust mode for dev-lead agents

---------

Co-authored-by: Gemini CLI <gemini-cli@example.com>

* fix: resolve YAML syntax error in dev-lead-reusable workflow

* fix: address automated feedback from PR 217 (#219)

This addresses missed feedback from CodeRabbit (removing syntax errors in YAML) and gemini-code-assist (improving string concatenation in validate-engines.sh), and adds assertion output checks to tests.

Co-authored-by: Gemini CLI <gemini-cli@example.com>

* chore: remove Phase 7 shadow period comment from dev-lead.yml

* feat: skip PRs with CHANGES_REQUESTED review (+ FORCE_REVIEW and stale-review guards)

* feat: skip PRs with CHANGES_REQUESTED review decision

Adds an early-exit check alongside the existing CI guards so the cascade
skips any PR where a human reviewer has already requested changes.
Reviewing those PRs before the author responds wastes tokens and creates
confusing parallel feedback threads.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix: honor FORCE_REVIEW and skip only stale-free CHANGES_REQUESTED reviews

Two issues addressed from Codex P2 feedback:

1. FORCE_REVIEW bypass: mention-triggered and force_review=true runs now
   bypass the CHANGES_REQUESTED skip, so authors can explicitly request a
   re-review after addressing feedback.

2. Stale review guard: only skip when a CHANGES_REQUESTED review targets
   the current head SHA. On repos without automatic stale-review dismissal,
   reviewDecision stays CHANGES_REQUESTED after new commits — the cascade
   now re-engages when the author has pushed new work.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): resolve context loss, missing checkout, and push logic (#223)

* feat: skip PRs with CHANGES_REQUESTED review decision

Adds an early-exit check alongside the existing CI guards so the cascade
skips any PR where a human reviewer has already requested changes.
Reviewing those PRs before the author responds wastes tokens and creates
confusing parallel feedback threads.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix: honor FORCE_REVIEW and skip only stale-free CHANGES_REQUESTED reviews

Two issues addressed from Codex P2 feedback:

1. FORCE_REVIEW bypass: mention-triggered and force_review=true runs now
   bypass the CHANGES_REQUESTED skip, so authors can explicitly request a
   re-review after addressing feedback.

2. Stale review guard: only skip when a CHANGES_REQUESTED review targets
   the current head SHA. On repos without automatic stale-review dismissal,
   reviewDecision stays CHANGES_REQUESTED after new commits — the cascade
   now re-engages when the author has pushed new work.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): resolve context loss, missing checkout, and push logic

- dev-lead-intent.sh: include actor and body in INTENT_CONTEXT for comments
- dev-lead.yml: parse and pass INTENT_ACTOR and INTENT_COMMENT_BODY to agent
- dev-lead-fix-reviews.sh: perform PR checkout, git push, and post summary comments

* fix(dev-lead): exit with 0 on no-changes in fix-reviews and human-pr

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* test: bypass CI check

* fix(pr-review): remove CI bypass hack

* fix(dev-lead): use heredoc for multiline environment variables (#224)

* fix(dev-lead): use heredoc for multiline environment variables

* fix(dev-lead): ensure all engines are installed and context is consistent

* fix(dev-lead): resolve stale model name in engine fallbacks

* fix(dev-lead): improve fallback reliability and script quality

- engine.sh: resolve stale model names in fallback loop; use -latest Gemini aliases
- dev-lead-fix-reviews.sh: remove unused code and fix PR_URL export

* fix(dev-lead): resolve Gemini model names and shell lint warnings

* security(dev-lead): use random heredoc delimiter to prevent inject…
don-petry added a commit that referenced this pull request Aug 7, 2026
This addresses missed feedback from CodeRabbit (removing syntax errors in YAML) and gemini-code-assist (improving string concatenation in validate-engines.sh), and adds assertion output checks to tests.

Co-authored-by: Gemini CLI <gemini-cli@example.com>
don-petry added a commit that referenced this pull request Aug 7, 2026
… trigger for the Class-2 timer changes, before narrowing them (#1420)

* add: workflow to fix stuck PRs using bot account token

Allows running the cleanup script via workflow_dispatch with access to
GH_PAT (bot account token) from repo secrets.

* fix: pass GH_PAT to checkout action so workflow uses bot account

* debug: simplify account check, add authentication debug output

* docs: add comprehensive petry-review-bot setup instructions

* docs: add GitHub App setup guide (recommended approach)

GitHub App is the secure, recommended way to automate PR reviews:
- Fine-grained permissions
- JWT tokens that auto-expire
- No human account needed
- Better audit trail
- GitHub's recommended approach

Replaces the bot user account approach with a more secure alternative.

* docs: add comprehensive GitHub App setup documentation

- SETUP.md: Quick reference guide with configuration and troubleshooting
- IMPLEMENTATION.md: Technical deep dive on architecture and design decisions
- DOCUMENTATION.md: Index of all documentation files
- Updated README.md with status and quick links
- Updated GITHUB_APP_SETUP.md with implementation notes and actual app ID
- Workflows use GitHub App token generation instead of static PATs

Covers the transition from bot user account to GitHub App authentication for improved security and maintainability.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: silence auth check in fix-stuck-prs when using GitHub App token

GitHub App tokens don't have user scope, so 'gh api user' returns 403.
This is fine - the script still works for PR operations. Suppress the error
so the script completes successfully with app-token fallback label.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: use explicit author instead of @me in stuck PR search

GitHub App tokens don't have user identity, so @me search doesn't work.
Use explicit 'don-petry' author instead to find PRs to fix.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: avoid subshell in while loop to preserve variable state

Using pipe in while loop created subshell where PROBLEM_PRS and FIXED_PRS
counters were incremented but changes didn't persist to parent shell.
Fixed by using process substitution for input instead of pipe.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* docs: add comprehensive status report for GitHub App setup

- Overview of completed work
- GitHub App authentication details
- 24 stuck PRs successfully fixed with approval reviews
- Architectural decisions and rationale
- Configuration guide and usage examples
- Known limitations and troubleshooting

System is now fully operational with GitHub App token authentication,
comprehensive documentation, and all infrastructure in place.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* docs: document stuck PR cleanup status and script fixes

Added documentation for:
- GitHub App token compatibility fixes in fix-stuck-prs.sh
  * Changed @me to explicit author (app tokens have no identity)
  * Fixed subshell variable scope (process substitution vs pipe)
  * Silenced expected 403 auth check error
- Current status: 24 PRs have approvals but remain OPEN
  * Auto-merge failed due to missing GitHub App permission
  * Approvals satisfy branch protection requirement
  * Next: expand permissions or manually merge

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: update list-prs.sh to search all repos by owner instead of @me

GitHub App tokens don't have user identity, so @me/@review-requested searches
fail. Changed to enumerate all repos in don-petry and petry-projects, then
search for open PRs within each repo. This covers the full scope:
- All open PRs in personal don-petry repos
- All open PRs in petry-projects org repos

Resolves 6 consecutive workflow failures due to zero PRs being enumerated.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* docs: add investigation report for PR review agent workflow failures

* fix: move env section before steps in workflow to fix YAML structure

GitHub Actions requires env section to come before steps in job definition.
Moving env definition up and setting GH_TOKEN only in steps that need it.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* script: add backfill-approvals to retroactively apply real approvals

Converts PRs with agent approval comments (but no real GitHub approvals)
to have actual APPROVED reviews. Needed for PRs reviewed before the
gh pr review --approve fix was applied.

Usage:
  scripts/backfill-approvals.sh          # dry-run (preview changes)
  scripts/backfill-approvals.sh false    # apply real approvals

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* feat: add backfill-approvals workflow to run as GitHub App bot

Runs backfill-approvals.sh via GitHub Actions so the approvals are posted
by the bot identity, not the PR author — GitHub rejects self-approvals.

Also fixes subshell counter bug in the script (piped while loop lost variable
state; switched to process substitution).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: convert inner while loop to process substitution to preserve counters

Both the outer repo loop and inner PR loop were piped subshells, causing
all counter increments (approved/skipped/failed) to be lost. The summary
always showed 0. Fixed by using < <(...) process substitution for both loops.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* car-hunt: add VIN-deep-dive flow, printable checklists, location-tier ranking

- Step 5.5 pre-test-drive flow: NHTSA VIN decode + per-VIN unrepaired-recall
  automation via Chrome MCP (Honda LWC owner portal documented as not
  automatable; NHTSA web form works).
- §F printable test-drive checklist generator (reportlab/Platypus PDF) with
  cold-start, model-specific red flags, negotiation table, walk-away rules.
- §G fraud / paperwork verification checklist (vehicle-agnostic, reusable):
  walk-away triggers, VIN three-location match, curbstoning, odometer fraud,
  bill of sale, title transfer, payment protection, stolen-vehicle, title-
  jumping, after-purchase steps.
- Step 5.25 head-to-head comparison template with information-asymmetry rule.
- Step 3 Location Quality Tier scoring (Birmingham AL metro table A/B/C/D)
  with adj-CPM modifier; sheet schema gains Adj.CPM + Tier columns.
- Hard-disqualification rules added: non-running engine keywords, mileage
  inconsistency, mandatory description scrape.
- Drive MCP overwrite limitation documented; sheet ID now read from memory
  rather than hardcoded.
- FB Marketplace operational facts: Birmingham AL city ID 107739635926718,
  /search? vs /vehicles? query handling, React-controlled inline composer,
  send-button selector, seller-name extraction regex.
- scheduled-tasks/used-car-search-{morning,afternoon}: tier A/B priority
  flag, sheet ID read from memory.
- Reference PDF generator scripts checked in for reuse.

* fix: re-approve PRs where approval predates last commit (stale approval)

Instead of skipping PRs that already have any APPROVED review, skip only
those where reviewDecision is not REVIEW_REQUIRED. This handles PRs where
a bot approval was posted before a new commit was pushed — GitHub's ruleset
engine treats those approvals as stale even with dismiss_stale_reviews=false.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: robustly extract JSON verdict from mixed claude --print output (#17)

Claude's --print mode can prefix the JSON verdict with conversational
preamble text, causing jq to fail with parse errors. Add
extract_verdict_json to engine.sh and wire all three cascade action
call sites in review-one-pr.sh to use it.

Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: extract_verdict_json — check dest file first (agent Bash-write style)

The cascade-action agent writes its verdict JSON to \$OUTPUT_FILE via a
Bash tool call, then prints a text summary to stdout. The previous fix
only scanned stdout (the .raw file), which contained no JSON. The agent-
written file at \$dest (= \$OUTPUT_FILE) was already correct — just not
checked. Now check \$dest first before falling back to stdout scan.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Enforce MAX_REVIEW_CYCLES cap before running the cascade (#18)

* fix: enforce MAX_REVIEW_CYCLES cap with human-escalation marker

Previously MAX_REVIEW_CYCLES only gated AI delegation in
post-pr-review.sh — the cascade itself ran on every cron tick
regardless of how many cycles had accumulated. Real-world result:
a PR could rack up 9+ review cycles (we observed exactly that on
ContentTwin#100) before any cap took effect.

Add a pre-cascade check in review-one-pr.sh: when the count of
existing `<!-- pr-review-agent v1 sha=... -->` markers is at or
above MAX_REVIEW_CYCLES (default 3), post one escalation comment
marked `<!-- pr-review-agent escalation -->`, label
needs-human-review, request don-petry, and exit 100 (skip
sentinel — doesn't burn the MAX_PRS budget). The escalation
marker doubles as the no-spam guard: subsequent runs detect it
and exit 100 immediately.

Also reuse a single `gh pr view` for both the cycle count and the
escalation-marker check.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* review feedback: surface escalation-comment failures, drop dead unset

- Drop `2>/dev/null || true` on `gh pr comment` so a failed escalation
  post is visible in the workflow log instead of silently leaving the
  PR with no marker (which would re-trigger the cap path next tick).
- Reword the cycle-count comment from "AI delegation loops" to "review
  loops" — the cap also catches cascade-only loops where every cycle
  approves and a new commit lands before merge.
- Remove the redundant `unset PR_BODIES` — never exported, no leakage.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>

* Reliability hardening: session circuit breaker, timeouts, retry, dedup (#20)

* feat: session circuit breaker, per-tier timeouts, retry, and triage hard-fail

Reliability hardening for the PR review agent.

1. Session circuit breaker (.github/workflows/pr-review.yml): on any
   non-zero, non-100 exit from review-one-pr.sh (general failure or rate
   limit on the fallback engine), break the per-PR loop, log a clear
   error annotation naming the failing PR and reason, and exit the step
   with code 1 so the run shows red. Prevents one systemic problem from
   silently burning the entire candidate pool.

2. Per-tier timeouts (scripts/engine.sh): triage/deep/audit/action/duck
   each get their own bounded timeout (180/600/600/300/300s defaults,
   env-overridable). Previously only the duck had a timeout — a hung
   tier could burn the whole 60min job budget.

3. Retry-with-backoff on transient errors (scripts/engine.sh): triage
   retries once on 124/137/143 (timeout / signal kill) since its caller
   captures stdout via $(...) so retries are safe. Deliberately NOT
   applied to run_agentic/run_duck where stdout is redirected to a file
   — a retry there would corrupt the partial first-attempt output.

4. Triage non-JSON now hard-fails (scripts/review-one-pr.sh): replaces
   the silent fallback that synthesized a fake "escalate=MEDIUM" verdict
   and proceeded to deep review. With the new circuit breaker, loud
   failure is the right call — masking a broken triage was burning
   tokens on every PR while the workflow looked healthy.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix: stop stacking duplicate agent reviews on the same PR

Two bugs were causing the agent to leave multiple comments on the same PR.
Together they produced 10 stacked APPROVED reviews on petry-projects/ContentTwin#100.

Bug A — idempotency check is order-dependent (review-one-pr.sh):
The previous marker-discovery code did:
  ((.reviews // []) + (.comments // [])) | .[].body | grep marker | tail -1
This relies on the array concatenation order, not chronological order. When
old agent comments existed alongside newer agent reviews, tail -1 picked
the comment-array marker (older) over the review-array marker (newer),
causing the script to think the head SHA hadn't been reviewed and re-run.
Replaced with a single jq pipeline that tags each item with submittedAt /
createdAt, sorts by timestamp, and takes the actual most-recent marker.

Bug B — no cleanup of prior agent items (post-pr-review.sh):
After successfully posting a new review/comment, prior agent items were
left in place, accumulating forever. Added mark_prior_agent_items_obsolete
which, after a successful post:
  - dismisses prior APPROVED/COMMENTED/CHANGES_REQUESTED agent reviews via
    the GitHub dismissal API (UI shows them struck-through as Dismissed)
  - edits prior agent comments to wrap their body in a collapsed <details>
    block with a "Superseded by re-review at <SHA>" summary, plus a
    `<!-- pr-review-agent superseded -->` sentinel for idempotency

All cleanup API calls are best-effort — failures don't break the workflow.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(cleanup): warn on API failures, preserve global newest, file-stage JSON

Three fixes to mark_prior_agent_items_obsolete from the review of PR #20:

1. ::warning:: annotations on every cleanup API failure (review/comment
   list-fetch, individual review dismissal, individual comment fetch+edit).
   Previously these were silenced with `|| true`, so a permissions change
   on the dismissal endpoint would let duplicates stack indefinitely with
   no signal in the Actions UI. Cleanup is still non-fatal — the new post
   has already landed — but failures are now visible.

2. Preserve the globally-latest agent item across BOTH categories, not the
   newest of each category separately. The earlier code split reviews and
   comments and applied `[:-1]` to each, which left a stale fix-request
   comment in place when the new post was a review (or vice versa). The
   one-off cleanup of ContentTwin#100 hit exactly this case: 12 stacked
   reviews collapsed to 1, but a stale comment from 2026-04-25 (SHA
   cd9132d6) was preserved as "newest comment" even though the latest
   review at SHA 3af8c8ee was newer overall. Now: compute the max
   timestamp across both feeds, exclude items at that timestamp.

3. Stage API responses to disk (`mktemp` + `jq <file>`) instead of routing
   through `--argjson "$var"`. The old approach broke on rare unescaped
   control chars in user-authored comment bodies (jq refused to parse the
   resulting shell-vared JSON). File-based input sidesteps the shell
   pipeline entirely.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>

* Remove car-hunt skill and scheduled tasks (moved to don-petry/don-petry) (#21)

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: apply Copilot fallback hardening and reuse GH_PAT for Copilot auth

- Use secrets.GH_PAT (existing personal account token with Copilot
  subscription) instead of a separate COPILOT_GITHUB_TOKEN secret
- Pin actions/create-github-app-token to SHA (v3.1.1) for supply chain safety
- Add post-install verification for gh-copilot extension with warning on failure
- Guard Copilot fallback path: skip PR gracefully if extension is not installed

Addresses issue #24 workflow fixes (1-3) that could not land via PR #25
due to GitHub App lacking workflows permission.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: switch Copilot engine to gh built-in and fix app-id deprecation

- Remove gh extension install (github/gh-copilot conflicts with built-in alias)
- Replace with gh copilot --version check using COPILOT_GITHUB_TOKEN (GH_PAT)
- Update all engine.sh copilot invocations from bare `copilot` binary to
  `gh copilot suggest --target shell` with GH_TOKEN overridden to GH_PAT
  so the user token (with Copilot subscription) is used, not the App token
- Fix actions/create-github-app-token: app-id → client-id (same secret value,
  just the renamed input in v3.x)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: harden Copilot fallback path and remove stale worktrees (#25)

- gh-copilot install: add --force flag and post-install verification
  warning so silent failures due to built-in alias conflict are visible
  in workflow logs instead of being silently swallowed by `|| true`
- fallback pre-flight check: verify `gh extension list | grep copilot`
  before switching to Copilot engine; if unavailable, skip the PR and
  continue the batch rather than session-aborting and dropping all
  remaining candidates (fixes the 27-PR drop on run #503)
- pin actions/create-github-app-token to SHA for v3.1.1 (Node.js 24
  compatible) to prevent hard break on 2026-06-02 when GitHub forces
  Node.js 24 as default runtime
- remove 6 stale Claude Code worktrees from git tracking; add
  .gitignore entry to prevent future worktrees from being committed
  (eliminates exit-128 warning on every actions/checkout post-job sweep)

Closes #24

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: petry-projects-pr-review-agent[bot] <petry-projects-pr-review-agent[bot]@users.noreply.github.com>

* ci: add pre-flight dedup check to prevent duplicate claude-issue PRs (#26)

Before invoking Claude on a labeled issue, a new shell step queries
for any open PR with a branch matching claude/issue-NNN-* (or a body
containing "Closes #NNN"). If one is found it posts a comment on the
issue pointing to the existing PR and skips the Claude step entirely.
A secondary prompt instruction tells Claude to check first and push to
the existing branch rather than opening a new PR.

Motivated by issue petry-projects/google-app-scripts#171, where the
claude label was re-applied three times and each run created a fresh
PR unaware of the prior attempts.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor: migrate to machine user PAT auth (closes #27) (#28)

* refactor: migrate from GitHub App to machine user PAT auth

Closes #27

GitHub Apps cannot be listed in CODEOWNERS, blocking PRs in repos with
require_code_owner_review: true. Switch all workflows to use a machine
user account's fine-grained PAT (DON_PETRY_BOT_GH_PAT secret), which can
join an org team listed in CODEOWNERS.

Workflows: removed actions/create-github-app-token steps in pr-review,
fix-stuck-prs, backfill-approvals, and daily-pr-review-health. All now
use secrets.DON_PETRY_BOT_GH_PAT directly.

Docs: renamed GITHUB_APP_SETUP.md to MACHINE_USER_SETUP.md with full
rewrite covering account creation, CODEOWNERS config, PAT generation,
and rotation. Updated auth sections in IMPLEMENTATION.md, SETUP.md,
STATUS.md, DOCUMENTATION.md, README.md.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* refactor: switch to org-scoped PAT secret DON_PETRY_BOT_PETRY_PROJECT_PAT

Replace DON_PETRY_BOT_GH_PAT with DON_PETRY_BOT_PETRY_PROJECT_PAT — the
new fine-grained PAT scoped to the petry-projects org (resource owner =
petry-projects). The previous PAT was scoped to the donpetry-bot personal
namespace and had no repository access.

The old DON_PETRY_BOT_GH_PAT secret is retained for any future use against
don-petry's personal repos.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* chore: ignore .claude/scheduled_tasks.lock state file

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>

* refactor: merge approval repair scripts into single automation

- Consolidate backfill-approvals.sh and fix-stuck-prs.sh into repair-pr-approvals.sh
- Iterate all repos in both orgs instead of assuming author
- Verify no existing APPROVED review before posting
- Copy original agent comment as review body
- Enable auto-merge when posting approval if needed
- Single workflow with 30-min timeout

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: add @mention trigger for on-demand PR reviews (#30)

Adds repository_dispatch support so commenting @petry-review-bot on any
PR fires an immediate review without waiting for the hourly schedule.

- pr-review.yml: new `repository_dispatch` trigger (type: pr-review-mention),
  per-PR concurrency group for mention runs, FORCE_REVIEW and DRY_RUN
  env vars that handle both workflow_dispatch and repository_dispatch paths
- review-one-pr.sh: FORCE_REVIEW=true bypasses idempotency so a mention
  always runs a fresh cascade even if the head SHA hasn't changed
- templates/mention-listener.yml: deploy to petry-projects/.github;
  listens for @petry-review-bot, validates commenter trust, posts ack,
  sends repository_dispatch (requires Contents:write, not Actions:write)

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: harden rebase, review dismissal, and health log diagnostics (#40)

Addresses 4 code-actionable recommendations from health check report #33:

- [CRITICAL] Make rebase/branch-update non-fatal — 403/504 during rebase
  emits ::warning:: instead of aborting the batch session (#34)
- [MEDIUM] Add retry with exponential backoff (3 attempts) for transient
  5xx on update-branch API; break immediately on 4xx (#37)
- [MEDIUM] Guard review dismissal with state re-check before calling
  dismiss API, preventing 422s from race conditions (#38)
- [LOW] Surface missing log warnings in health check script (#39)
- Fix: skip auto-merge when branch is still BEHIND after failed rebase

Closes #34, closes #37, closes #38, closes #39

* refactor: migrate to org-wide .github-private convention (#41)

* refactor: parametrize hardcoded identity values for org migration

Replace all hardcoded references to don-petry, petry-review-bot, and
don-petry/pr-review-agent with environment variables that default to
the current values. This allows the agent to be configured for different
orgs/users via repo variables.

Changes:
- Scripts use $REVIEWER_USER, $TARGET_ORG, $BOT_USER, $AGENT_REPO
- Prompts no longer reference specific GitHub usernames
- Workflows use org-level GH_PAT_WORKFLOWS secret instead of
  repo-level DON_PETRY_BOT_PETRY_PROJECT_PAT
- Health check uses context.repo.owner/repo for issue creation
- Variables are set in workflow env block with defaults

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add Copilot custom agent profiles for org-wide use

Create /agents/ directory with three agent profiles:
- pr-reviewer: Multi-tier cascading PR review
- feature-ideator: Feature idea generation and prioritization
- compliance-auditor: Org standards compliance checking

These are Copilot custom agent profiles that become available
org-wide in the .github-private repo convention.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Squashed 'frameworks/bmad-method/' content from commit e36f219c

git-subtree-dir: frameworks/bmad-method
git-subtree-split: e36f219c81b6010d4aae423ba12f49edb5b6e31a

* Squashed 'frameworks/spec-kit/' content from commit 11f49ebf

git-subtree-dir: frameworks/spec-kit
git-subtree-split: 11f49ebfb2f6af55345cb4bd9a7906acd211e56f

* Squashed 'frameworks/gsd/' content from commit 304c1a13

git-subtree-dir: frameworks/gsd
git-subtree-split: 304c1a1302564c45af252bbba4bcc5350e7dac3a

* docs: update README for .github-private org infrastructure role

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* ci: add dependabot-automerge.yml workflow (#73)

Adds the required dependabot-automerge.yml workflow from the org
standard template (petry-projects/.github/standards/workflows/).
This is a thin caller stub that delegates to the org-level reusable
workflow.

Closes #48

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Don Petry <don-petry@users.noreply.github.com>

* fix(pr-review): unblock queue starvation from self-authored PRs (#96) (#97)

* fix(pr-review): unblock queue starvation from self-authored PRs (#96)

A self-authored PR sorted first in the candidate list and triggered
"Can not approve your own pull request" — which the session-fatal
abort treated like an engine error, skipping all 28 remaining PRs
on every run.

- list-prs.sh: filter out PRs authored by REVIEWER_USER at enumeration
- post-pr-review.sh: catch the GraphQL self-approval error and exit 100
  (no-op) instead of 1, so a stray self-PR can no longer abort the batch
- engine.sh: gh copilot renamed --target to --agent; fix the rubber-duck
  invocations so tier-2 cross-engine review works again
- review-one-pr.sh: stop appending a duplicate "0" to REVIEW_CYCLE when
  grep -c finds no markers under set -o pipefail (was breaking the
  cycle-cap integer comparison)

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

* fix(pr-review): authenticate as bot, harden enumeration, address review

- Workflow now runs as AGENT_USER (default don-petry-bot), distinct from
  REVIEWER_USER (the human, don-petry). The self-approval rejection that
  caused #96 came from the agent and the human sharing one identity.
- list-prs.sh filters self-authored PRs against AGENT_USER, and validates
  AGENT_USER against the GitHub username charset before interpolating into
  the jq filter (Copilot review feedback).
- review-one-pr.sh uses printf '%s\n' instead of echo for PR_BODIES, since
  PR body content is user-authored and could begin with -n/-e or contain
  backslash escapes (Copilot review feedback).
- AGENT.md guideline updated to reflect that self-authored PRs are
  intentionally excluded (CodeRabbit review feedback).

Operator note: rotate the GH_PAT_WORKFLOWS secret to a token owned by
don-petry-bot (with read:org added) for the bot-account behavior to take
effect at runtime.

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

* refactor(pr-review): collapse to single BOT_USER, route escalations via CODEOWNERS

Drop AGENT_USER and REVIEWER_USER. The workflow now has one identity:
BOT_USER (default don-petry-bot), which both owns the repos to scan and
gets filtered out as the self-approval blocker. Human escalation no
longer hard-codes a single reviewer; instead, scripts/request-codeowners-
review.sh parses CODEOWNERS in the PR's repo and requests review from
every @user / @org/team mention.

- list-prs.sh: BOT_USER for both gh-repo-list and self-author filter
- review-one-pr.sh, post-pr-review.sh: replace --user "$REVIEWER_USER"
  with the CODEOWNERS helper
- repair-pr-approvals.sh: same gh-repo-list switch
- pr-review.yml: drop AGENT_USER/REVIEWER_USER, set BOT_USER default
  to don-petry-bot
- AGENT.md: updated guideline to describe single-identity model

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

* refactor(pr-review): extract batch loop, hoist env, drop redundant step

Workflow YAML drops from 231 to 111 lines by moving the per-PR review
loop into scripts/review-batch.sh. The script can be syntax-checked and
exercised locally; the workflow now just wires env vars and dispatches.

- Hoist GH_TOKEN, MAX_PRS, CANDIDATE_LIMIT to job-level env (were
  repeated on individual steps).
- Inline `gh auth status` into the install step; remove the standalone
  Verify auth step (its only output was a one-line auth dump).
- Drop the `if: steps.list.outputs.count != '0'` guard and the step's
  `id`/output: review-batch.sh handles empty input as a no-op.
- Collapse the duplicated summary-string branch in the review loop into
  a single template with a conditional fragment.

No behavior change.

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

* perf(pr-review): cache claude-code CLI install across runs

`npm install -g @anthropic-ai/claude-code` ran on every workflow start
(~30s). Switch to a per-user npm prefix (~/.npm-global) and cache that
directory via actions/cache, keyed on CLAUDE_CODE_VERSION + runner OS.

A `command -v claude` guard makes the install a no-op on cache hit, so
the only cost on subsequent runs is the cache restore.

CLAUDE_CODE_VERSION defaults to 'latest' (cache persists until manually
flushed); set the repo variable to pin a specific version for fully
reproducible caching.

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

---------

Co-authored-by: Claude <noreply@anthropic.com>

* chore: rename GH_PAT_WORKFLOWS secret to DON_PETRY_BOT_GH_PAT (#99)

The previous name was generic and didn't tell you which account the PAT
belonged to. The new name makes the binding explicit: this secret is the
PAT owned by don-petry-bot, used as BOT_USER throughout the PR-review
workflows.

Operator follow-up before this can be merged:
- Add a new repo secret DON_PETRY_BOT_GH_PAT containing the bot's PAT
  (with repo, workflow, and read:org scopes).
- After merge, the old GH_PAT_WORKFLOWS secret can be deleted.

Affected workflows:
- pr-review.yml (1 use)
- claude.yml (4 uses, with || github.token fallback)
- daily-pr-review-health.yml (1 use)
- repair-pr-approvals.yml (1 use)

scripts/pr_review_health.sh's error message is updated to point at the
new name as well.

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

Co-authored-by: Claude <noreply@anthropic.com>

* chore: remove frameworks directory (#101)

Co-authored-by: Gemini CLI <gemini-cli@example.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore all erroneously deleted files (items 4-34) (#104)

* revert: restore .github/workflows/daily-pr-review-health.yml

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore .github/workflows/repair-pr-approvals.yml

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore agents/pr-reviewer.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/cascade-action.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/deep-review.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/rubber-duck.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/security-audit.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/shared.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/single-review.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/synthesize-duck.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/synthesize.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/triage.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/engine.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/list-prs.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/post-pr-review.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/pr_review_health.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/repair-pr-approvals.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/request-codeowners-review.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/review-batch.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/review-one-pr.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore templates/mention-listener.yml

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore AGENT.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore BOT_SETUP.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore DOCUMENTATION.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore IMPLEMENTATION.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore MACHINE_USER_SETUP.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore PR_REVIEW_AGENT_FAILURE_REPORT.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore PR_REVIEW_FAILURE_INVESTIGATION.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore SETUP.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore STATUS.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore README.md to pre-change state

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore .github/workflows/daily-pr-review-health.yml

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat: add Gemini Pro support and optimize PR review fallback chain (#102)

* Merge main and resolve conflicts

* Enhance pr-review workflow: add Gemini support and refactor fallback logic

* Address Copilot review comments: tighten regex, fix fallback summary, and align docs

* docs: move agent documentation to docs/ folder and rename files

* docs: align secret names and fix casing in index

* docs: organize pr-review-agent documentation under dedicated folder

* Address final PR comments: tighten regex, add preflight checks, fix bot names, and cleanup docs

---------

Co-authored-by: Gemini CLI <gemini-cli@example.com>

* fix(pr-review): gate rate-limit detection on non-zero exit code

Broad patterns like `plan.*limit` and `claude.*usage` could match content
in a successful triage summary, triggering a false-positive engine fallback.
Guard the check with TRIAGE_RC != 0 so it only fires when the provider
command actually failed.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(claude): sync inlined workflow with org standard (#109)

* fix(claude): sync inlined workflow with org standard

Brings the inlined claude.yml up to parity with
petry-projects/.github/.github/workflows/claude-code-reusable.yml.

Changes:
1. Bot allow list for pull_request_review_comment
   Add coderabbitai[bot], Copilot, copilot-pull-request-reviewer[bot],
   and gemini-code-assist[bot] alongside the existing OWNER/MEMBER/
   COLLABORATOR check. These bots have author_association 'NONE' so
   their review comments were always skipped.

2. check_run trigger + claude-ci-fix job
   Port the CI failure auto-fix feature from the reusable. When a check
   fails on a PR, Claude diagnoses and fixes it automatically.
   Adapted to use DON_PETRY_BOT_GH_PAT (this repo's secret name).

3. claude-code-action SHA bump: v1.0.89 → v1.0.119
   Update both the claude and claude-issue jobs.

4. paths-ignore on pull_request trigger
   Prevents the workflow from firing on PRs that only change
   claude.yml itself, avoiding the Anthropic OIDC validation
   failure (workflow file must match default branch at token exchange).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(claude): guard bot allow list against fork PRs

Same fix as petry-projects/.github PR #238 — add same-repo guard
for bot-triggered pull_request_review_comment runs.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(claude): address ci-fix self-loop and fork PR security

- Replace Claude Code name prefix check with explicit job name list to
  correctly prevent self-loops (check_run names for inlined workflows
  are bare job names, not workflow-prefixed)
- Add fork PR trust gate in Resolve PR number step: verify head repo
  matches target repo before running Claude with privileged credentials

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* ci: replace inlined claude.yml with standard thin-caller stub

The inlined version embedded all three jobs (claude, claude-ci-fix,
claude-issue) directly and threaded DON_PETRY_BOT_GH_PAT through
checkout tokens and github_token, causing the bot's PAT to author
PRs and comments instead of github-actions[bot].

Replace with the standard thin caller that delegates to the org-level
reusable workflow (claude-code-reusable.yml@v1) via secrets: inherit,
which is the correct pattern per:
petry-projects/.github/standards/workflows/claude.yml

* fix: replace gh copilot suggest with GitHub Models REST API (#151)

* fix: replace gh copilot suggest with GitHub Models REST API (#147)

The `gh copilot suggest -p "$(cat <file>)"` invocation failed with
"Invalid command format" because:
1. The `-p` flag is not valid syntax in modern `gh copilot` built-in versions.
2. `gh copilot suggest` is a shell-command suggestion tool; it does not
   accept large PR prompts or return structured JSON responses.
3. Passing a multi-thousand-line prompt via `$(cat ...)` can hit ARG_MAX.

The non-zero exit was then misclassified by the rate-limit detector, which
aborted the entire session and skipped all remaining PRs in the queue.

Fix: replace all three copilot invocations (run_triage, run_agentic,
run_duck) with a new `copilot_chat` helper that calls the GitHub Models
REST API directly via curl. The API is OpenAI-compatible, versioned via
`X-GitHub-Api-Version`, stable against gh CLI version changes, and accepts
arbitrary prompt sizes using python3 for safe JSON encoding.

Additional changes:
- Add `COPILOT_API_MODEL` var (default `openai/o4-mini`) overrideable via env.
- run_agentic/run_duck copilot cases now also write output to $OUTPUT_FILE
  so callers that check that path directly (deep review, audit) find the JSON.
- Rate-limit responses (HTTP 429) are echoed to stdout so the existing
  `is_rate_limited()` detector fires correctly for engine fallback.
- Add pre-flight smoke test in review-batch.sh: tests GitHub Models API
  connectivity with a one-liner prompt before processing any PRs, so auth
  or model errors surface immediately as a clear setup failure.
- Add tests/test_copilot_chat.sh: unit tests for the JSON payload builder
  with edge-case prompts (quotes, newlines, # headings, large diffs, Unicode).

Closes #147

Co-authored-by: Don Petry <don-petry@users.noreply.github.com>

* fix: scope COPILOT_API_MODEL export to copilot engine only

Move the `export COPILOT_API_MODEL` from the common exports block into the
`copilot)` case where the variable is set, so it is not exported as an
empty/unset variable when the engine is `claude` or `gemini`. The
`copilot_chat` fallback `${COPILOT_API_MODEL:-openai/o4-mini}` still works
correctly when `DUCK_ENGINE=copilot` under a non-copilot primary engine.

Co-authored-by: Don Petry <don-petry@users.noreply.github.com>

* fix: address review comments — temp file for payload, fail-fast source, streaming output

- copilot_chat: write JSON body to mktemp file, pass to curl as @file to
  avoid ARG_MAX for large PR diffs (was --data-binary "$body")
- copilot_chat: add :? guard on COPILOT_GITHUB_TOKEN for a clear error
  instead of generic "unbound variable" under set -u
- run_agentic / run_duck copilot paths: stream directly to stdout (and
  tee to OUTPUT_FILE when set) rather than buffering the full response
  into a shell variable, which forced large outputs into memory and
  stripped trailing newlines
- review-batch.sh pre-flight: fail fast if source engine.sh fails
  (was silently ignored with || true)
- review-batch.sh pre-flight: build smoke-test JSON payload via python3
  + temp file instead of shell string interpolation to avoid JSON
  injection if COPILOT_API_MODEL contains special characters
- Clarify that openai/o4-mini is the correct April-2025 o4-generation
  model name, not a typo for o1-mini or gpt-4o-mini

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Don Petry <don-petry@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* feat: prioritise .github/.github-private PRs, oldest-first within tier (#155)

* feat: prioritise .github/.github-private PRs, oldest-first within tier

- list-prs.sh: add createdAt to JSON fetch; emit priority|createdAt|url
  lines (priority 0 for .github/.github-private, 1 for everything else)
- Replace final sort -u with a two-pass sort: deduplicate by URL, then
  sort by priority asc then createdAt asc; strip sort keys with cut
- tests/test_list_prs_sort.sh: 16 tests covering priority classification,
  oldest-first ordering, mixed scenarios and deduplication
- .github/workflows/test.yml: run both unit-test files on every PR / push

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* test: add dedup edge case for same URL with conflicting priorities

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: remove temperature from copilot_chat payload (o4-mini incompatible)

The GitHub Models API rejects temperature=0 for reasoning models like
o4-mini with HTTP 400:
  'Unsupported value: temperature does not support 0 with this model.
   Only the default (1) value is supported.'

This caused the rubber duck (DUCK_ENGINE=copilot / DUCK_MODEL=o4-mini)
to fail with HTTP 400 on every claude-engine run, silently degrading
every review to deep-only with no cross-engine sanity check.

Fix: remove temperature from the copilot_chat JSON payload entirely —
the API defaults to 1, which is the only supported value for o4-mini.

Also:
- Update tests/test_copilot_chat.sh: sync build_payload to match and
  flip Test 9 to assert temperature is ABSENT (not 0).
- Add .github/workflows/test.yml: run unit tests on every PR and push
  to main so regressions are caught before merge.

Reproducer: job/75690347409 — duck failure logged as:
  copilot_chat: HTTP 400 from GitHub Models API
  {"error":{"message":"Unsupported value: 'temperature' does not
   support 0 with this model..."}}

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: address PR review comments

- list-prs.sh: tighten priority regex to /[.]github(-private)?/pull/
  so path-boundary anchor prevents false positives (e.g. foo.github)
- list-prs.sh: replace printf pipeline with here-string to avoid ARG_MAX
  limits on large PR sets
- test.yml: add permissions: contents: read (least-privilege, CodeQL fix)
- test.yml: add timeout-minutes: 5 to prevent hung runs
- test_list_prs_sort.sh: sync sort_entries helper (here-string + || true)
  and classify regex to match updated list-prs.sh exactly

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: don-petry <don@petry.dev>

* fix: restore pr-review.yml content (accidentally emptied in revert)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: restore pr-review.yml (file was accidentally emptied)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore(deps): bump actions/cache from 4 to 5 (#165)

Bumps [actions/cache](https://github.com/actions/cache) from 4 to 5.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): bump petry-projects/.github/.github/workflows/claude-code-reusable.yml (#164)

Bumps [petry-projects/.github/.github/workflows/claude-code-reusable.yml](https://github.com/petry-projects/.github) from 1 to 2.
- [Commits](https://github.com/petry-projects/.github/compare/v1...v2)

---
updated-dependencies:
- dependency-name: petry-projects/.github/.github/workflows/claude-code-reusable.yml
  dependency-version: '2'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* fix(list-prs): search DELEGATION_ORGS and drop --checks success pre-filter

- Add iteration over all orgs in DELEGATION_ORGS (e.g. don-petry) so PRs
  in those orgs enter the candidate pool. Previously only BOT_USER and
  TARGET_ORG were searched.
- Remove --checks success from org repo searches. GitHub excludes PRs from
  repos with no CI configured when this flag is used, silently dropping all
  PRs from repos like .github, .github-private, google-app-scripts, and
  TalkTerm. review-one-pr.sh already enforces CI gating per-PR and treats
  empty statusCheckRollup as passing, so the pre-filter is redundant and
  harmful.
- Add --limit 200 to gh repo list calls to handle org growth beyond gh's
  default 30-repo cap.

* feat: add code-quality ruleset (compliance fix #60) (#86)

Creates the required `code-quality` repository ruleset enforcing required
status checks on the default branch, as mandated by the org standard:
standards/github-settings.md#code-quality--required-checks-ruleset-all-repositories

Required checks:
- SonarCloud (code quality analysis)
- CodeQL (SAST)
- agent-shield / AgentShield (agent security scan)
- dependency-audit / Detect ecosystems (dependency vulnerability scan)

Bypass actors:
- OrganizationAdmin (always) — emergency override
- dependabot-automerge-petry Integration (always) — Dependabot auto-merge

The ruleset was applied directly via GitHub API. This file documents the
configuration as code for auditability and future reapplication.

Closes #60

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Don Petry <don-petry@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* feat(prompts/dev-lead): add human prompt template

* feat(prompts/dev-lead): add human-pr prompt template

* feat(dev-lead): implement dev-lead agent Phases 0-6

* fix: per-PR isolation and single-review retry (closes #132)

- review-batch.sh: non-rate-limit per-PR failures (exit code 1) no
  longer abort the session. SESSION ABORTED EARLY is now reserved for
  the rate-limit-on-fallback-engine case (exit code 2) only. All other
  failures are counted and logged; remaining candidates continue.

- review-one-pr.sh: single-review step retries up to
  SINGLE_REVIEW_MAX_RETRIES (default 2) times with a
  SINGLE_REVIEW_RETRY_DELAY_SEC (default 15s) gap before giving up.
  On exhaustion, the PR is flagged needs-human-review and the script
  exits with code 1, which the updated batch treats as a non-fatal
  per-PR failure. Raw model output and stderr are logged on each failed
  attempt for post-mortem visibility.

Root cause of run #25707852006: claude-opus-4-7 returned a verbose
non-JSON response for PR #129; the old code treated that as fatal and
skipped 35 remaining candidates.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix: rate-limit detection and per-attempt stderr logs in single-review retry

Address inline review comments on PR #133:

- Rate-limit check: after each run_agentic call, inspect both stdout
  (VERDICT_JSON.raw) and stderr (SINGLE_LOG) with is_rate_limited before
  retrying. A rate-limit match exits immediately with code 2 so
  review-batch.sh can trigger engine fallback — consistent with triage and
  deep-review tiers. Previously a rate-limited single-review would burn all
  retries and exit 1 (per-PR failure), silently leaving the batch on the
  same rate-limited engine for all remaining PRs.

- Per-attempt log files: stderr is now written to
  single-review-attempt-N.log rather than a single overwritten file, so
  no earlier-attempt errors are lost. Each attempt logs its own stderr
  inline on failure; the fallback path cats all attempt logs for
  post-mortem visibility.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* feat(dev-lead): Phase 0 test infrastructure and Phase 1 intent stub

Phase 0: full test harness for the dev-lead agent —
  26 event fixtures (all valid JSON with _test_expected_intent),
  stub claude/gemini engines, mock gh binary, CI failure log sample,
  bats helpers (stub-engine, mock-gh, assert-env, prompt-vars),
  7 prompt templates with VARIABLES declarations, preflight script,
  prompt coverage integration test, and test-dev-lead.yml CI workflow.

Phase 1: dev-lead.yml trigger workflow (all 7 event types, dispatch + ci-relay
  jobs) and dev-lead-intent.sh stub (anti-loop guard live; all other events
  emit skip/not-implemented).  14/14 bats unit tests pass.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): add permissions blocks to test workflow jobs (CodeQL)

* fix(ci): auto-fix for lint / eslint [skip ci-relay]

* feat(dev-lead): Phases 2-6 — CI fix, review fix, issue, engine fallback

Phase 2: run_writer/run_writer_with_fallback in engine.sh, full intent
routing in dev-lead-intent.sh, dev-lead-fix-ci.sh handler, workflow wiring.

Phase 3: dev-lead-fix-reviews.sh handles fix-reviews, fix-bot-comment,
human, human-pr, rebase intents. Full review routing in intent classifier.

Phase 5: dev-lead-fix-issue.sh handles the issue intent with dedup guard
and branch/PR creation.

Phase 6: run_writer_with_fallback with claude→gemini→copilot fallback chain.

Tests: 77 unit tests across 8 new .bats files, all passing. Updated Phase 1
stub tests to reflect actual Phase 2+ routing behavior.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): address review comments — dispatch JSON, dead code, portable stub gh

* fix(dev-lead): pre-flight after intent, fork URL check, review-batch exit code, retry comment

* fix(dev-lead): install bats-core from GitHub to avoid root requirement

* fix(security): move event values to env vars to prevent script injection (SonarCloud)

* fix(security): move CLAUDE_CODE_VERSION to env block (SonarCloud script injection)

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* feat(dev-lead): Phase 1.5 — reusable workflow for cross-repo adoption + Phase 7 shadow period

* feat(dev-lead): add reusable workflow (Phase 1.5) and begin shadow period (Phase 7)

- Create .github/workflows/dev-lead-reusable.yml: workflow_call entry
  point for other repos. Checks out .github-private scripts/prompts into
  .dev-lead/, then runs the same intent-classify + handler pipeline as
  dev-lead.yml with PROMPTS_DIR=.dev-lead/prompts/dev-lead.
- Add PROMPTS_DIR env-var support to dev-lead-fix-ci.sh,
  dev-lead-fix-reviews.sh, and dev-lead-fix-issue.sh so the reusable
  workflow can point scripts at the sparse-checkout path without changing
  CWD. Defaults to prompts/dev-lead (backwards-compatible for dev-lead.yml).
- Annotate dev-lead.yml with Phase 7 shadow-period window (2026-05-15
  through ~2026-05-29): claude.yml and dev-lead.yml run in parallel until
  regressions clear, then claude.yml is removed.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* chore(dev-lead): update shadow-period tracking issue ref to #180

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): document PAT requirement and pin ref:main on private checkout

* fix(dev-lead): P1 review fixes — ci-relay in reusable, .dev-lead gitignore, export PROMPTS_DIR, trim header

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): rate-limit detection, separate status, and retry cron (#192) (#196)

* chore(dev-lead): decommission claude.yml, bump ACTION_TIMEOUT_SEC to 600s

- Delete .github/workflows/claude.yml — replaced by dev-lead.yml (shadow
  period complete, tracking issue #180).
- scripts/engine.sh: raise ACTION_TIMEOUT_SEC default 300→600s to reduce
  timeout failures on large-repo fix-ci runs (PR #80 had 3 timeouts).
- AGENTS.md: remove claude.yml immutability exemption; note dev-lead.yml
  as the active AI automation workflow and its edit-via-reusable pattern.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* refactor: replace Claude analysis with pure gh/jq telemetry in health check

* refactor: drop Node/Claude steps from daily-pr-review-health workflow

* rename: daily-pr-review-health → actions-fleet-monitor

* rename: daily-pr-review-health → actions-fleet-monitor

* fix(agents-md): clarify dev-lead.yml vs dev-lead-reusable.yml scope

dev-lead.yml in .github-private runs inline steps (not a caller stub).
Behavior changes for this repo go to dev-lead.yml directly; changes that
affect all org repos via the cross-repo reusable go to dev-lead-reusable.yml.

Addresses Copilot review on PR #194.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): rate-limit detection, separate status, and retry cron

Closes #192. Implements all phases from the revised plan.

**Phase 0 — fix run_writer stdout capture (prerequisite)**
- engine.sh: capture stdout via `tee` to a tempfile so `is_rate_limited`
  can inspect the output; old code read `/tmp/dev-lead-writer-stderr`
  which was never written (claude --print outputs to stdout, not stderr).
  Fallback engines were never tried in practice.
- engine.sh: add `parse_reset_time` to extract ISO timestamp from
  `resets H:MMpm (UTC)` in engine output and write it to
  `/tmp/dev-lead-rate-limit-reset` for callers to embed in markers.

**Phase 1 — separate status=rate-limited from status=failed**
- fix-ci.sh: detect engine exit 2 → post `status=rate-limited` (not
  `status=failed`); embed parsed reset time in marker body; exit 2.
- fix-ci.sh: fix `check_idempotency` to treat `status=rate-limited`
  as retriable — only block on terminal statuses (applied, failed,
  no-changes). Previously, rate-limited markers blocked all retries.
- fix-ci.sh: `count_recent_failures` already filtered to `status=failed`;
  add explicit comment confirming rate-limited markers are excluded.
- fix-ci.sh: add `has_rate_limited_marker` dedup check so the same SHA
  never accumulates more than one rate-limited comment.
- fix-reviews.sh: on engine exit 2 for all five intent types
  (fix-reviews, fix-bot-comment, human, human-pr, rebase), post a
  `status=rate-limited` marker with embedded reset time and exit 2.
- fix-reviews.sh: for `human` and `human-pr` intents, also post a
  user-visible acknowledgment comment so users know their request was
  received and will be retried.

**Phase 2 — scheduled retry cron for fix-ci**
- dev-lead-retry.sh: new script scans all open PRs across TARGET_ORG
  for `status=rate-limited` markers on current HEAD SHA; skips PRs
  whose reset time is still in the future; staggered dispatches (30s
  between repos) to prevent cascading org-level rate-limit hits.
- dev-lead-retry.yml: new workflow — scheduled every 2 hours (dual
  offsets to work around GitHub scheduler skew), plus workflow_dispatch.

**Phase 3 — retry for fix-reviews intents (same cron)**
- dev-lead-retry.sh already handles all five fix-reviews intent types
  (fix-reviews, fix-bot-comment, human, human-pr, rebase) via
  `dev-lead-reviews-retry` dispatch events.
- dev-lead-intent.sh: add `dev-lead-reviews-retry` dispatch type routing
  to the intent classifier — maps intent_type from payload to the correct
  existing fix-reviews step.
- dev-lead.yml: add `dev-lead-reviews-retry` to repository_dispatch types.
- dev-lead.yml: unify concurrency group for all dispatch types to
  `dev-lead-pr-{pr_number}` to avoid per-type slot fragmentation.

**Tests**
- test_engine_writer.bats: 8 new tests covering rate-limit stdout detection,
  exit-2 mapping, fallback exhaustion, reset time parsing.
- test_fix_ci.bats: 5 new tests covering rate-limited status, exhaustion
  exclusion, idempotency pass-through, and dedup.
- test_fix_reviews.bats: 5 new tests covering all intent types + human ack.
- test_intent_ci.bats: 4 new tests for dev-lead-reviews-retry dispatch routing.
- e2e/scenarios/07-rate-limit-retry.sh: new E2E scenario covering all parts.
- 3 new event fixtures for repository_dispatch_reviews_retry_* payloads.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): address review comments on #196

Addresses all 18 review threads from Copilot, Codex, and Gemini.

**P1 bugs fixed:**
- dev-lead-retry.sh: all log echo calls in scan_pr_for_rate_limits and
  dispatch_* functions now write to stderr; only the final count is on
  stdout. Previously, log lines polluted the command substitution,
  causing arithmetic expansion to fail and aborting the scan on the
  first retry candidate.
- dev-lead-retry.yml: permissions: contents was read-only; creating
  repository_dispatch events requires contents: write.

**Correctness fixes:**
- dev-lead-retry.sh: restrict automated retries to intents whose context
  can be reconstructed at runtime (fix-reviews, human-pr, rebase). human
  and fix-bot-comment require USER_INSTRUCTION/COMMENT_BODY from the
  original event which cannot be re-fetched; they are explicitly excluded.
- dev-lead-fix-reviews.sh: resolve HEAD_SHA from the PR API when not
  provided by the triggering event (issue_comment intents carry no SHA).
  Without this, rate-limited markers had no sha= field and were
  invisible to the retry scanner.
- dev-lead-fix-reviews.sh: write terminal status=applied marker after
  successful fix-reviews, human-pr, rebase runs. Prevents the retry
  cron from re-dispatching the same intent on every subsequent tick when
  the SHA hasn't changed.
- dev-lead-retry.sh: check for reviews terminal marker before dispatching
  a retry (mirrors the existing fix-ci terminal-marker check).
- dev-lead-retry.sh: look up current check-run details (details_url, id)
  at dispatch time via the commits check-runs API, so retried fix-ci runs
  have full failure logs and annotations rather than empty fields.
- dev-lead-fix-ci.sh: embed check= field in the rate-limited marker so
  the retry cron knows which check run to look up.
- dev-lead-fix-reviews.sh: human intent now posts a "please re-mention
  @dev-lead" ack instead of "I'll retry automatically" since it will NOT
  be retried automatically. human-pr keeps the auto-retry ack (correct).

**Pagination (Copilot + Gemini):**
- dev-lead-fix-ci.sh: add --paginate to check_idempotency, count_recent_
  failures, has_rate_limited_marker.
- dev-lead-fix-reviews.sh: add --paginate to has_reviews_rate_limited_marker.
- dev-lead-retry.sh: add --paginate to PR comments and PR list fetches.

**Other improvements:**
- dev-lead-retry.yml: simplify to single cron schedule (15 */2 * * *);
  remove BOT_USER env var (unused in the script).
- dev-lead-retry.sh: raise repo list limit 200 → 1000; add hard-error
  when list_repos_for_org returns empty (surfaces token permission issues
  rather than silently scanning 0 repos).
- dev-lead-intent.sh: use jq -nc (compact, null-input) for context JSON
  construction — safe against values with special characters.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(pr-review): handle Gemini trust-mode failure and properly route engine fallbacks (#217)

* fix(pr-review): handle Gemini trust-mode failure and properly route engine fallbacks

Closes #208

* fix(ci): install gemini CLI and set trust mode for dev-lead agents

---------

Co-authored-by: Gemini CLI <gemini-cli@example.com>

* fix: resolve YAML syntax error in dev-lead-reusable workflow

* fix: address automated feedback from PR 217 (#219)

This addresses missed feedback from CodeRabbit (removing syntax errors in YAML) and gemini-code-assist (improving string concatenation in validate-engines.sh), and adds assertion output checks to tests.

Co-authored-by: Gemini CLI <gemini-cli@example.com>

* chore: remove Phase 7 shadow period comment from dev-lead.yml

* feat: skip PRs with CHANGES_REQUESTED review (+ FORCE_REVIEW and stale-review guards)

* feat: skip PRs with CHANGES_REQUESTED review decision

Adds an early-exit check alongside the existing CI guards so the cascade
skips any PR where a human reviewer has already requested changes.
Reviewing those PRs before the author responds wastes tokens and creates
confusing parallel feedback threads.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix: honor FORCE_REVIEW and skip only stale-free CHANGES_REQUESTED reviews

Two issues addressed from Codex P2 feedback:

1. FORCE_REVIEW bypass: mention-triggered and force_review=true runs now
   bypass the CHANGES_REQUESTED skip, so authors can explicitly request a
   re-review after addressing feedback.

2. Stale review guard: only skip when a CHANGES_REQUESTED review targets
   the current head SHA. On repos without automatic stale-review dismissal,
   reviewDecision stays CHANGES_REQUESTED after new commits — the cascade
   now re-engages when the author has pushed new work.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): resolve context loss, missing checkout, and push logic (#223)

* feat: skip PRs with CHANGES_REQUESTED review decision

Adds an early-exit check alongside the existing CI guards so the cascade
skips any PR where a human reviewer has already requested changes.
Reviewing those PRs before the author responds wastes tokens and creates
confusing parallel feedback threads.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix: honor FORCE_REVIEW and skip only stale-free CHANGES_REQUESTED reviews

Two issues addressed from Codex P2 feedback:

1. FORCE_REVIEW bypass: mention-triggered and force_review=true runs now
   bypass the CHANGES_REQUESTED skip, so authors can explicitly request a
   re-review after addressing feedback.

2. Stale review guard: only skip when a CHANGES_REQUESTED review targets
   the current head SHA. On repos without automatic stale-review dismissal,
   reviewDecision stays CHANGES_REQUESTED after new commits — the cascade
   now re-engages when the author has pushed new work.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): resolve context loss, missing checkout, and push logic

- dev-lead-intent.sh: include actor and body in INTENT_CONTEXT for comments
- dev-lead.yml: parse and pass INTENT_ACTOR and INTENT_COMMENT_BODY to agent
- dev-lead-fix-reviews.sh: perform PR checkout, git push, and post summary comments

* fix(dev-lead): exit with 0 on no-changes in fix-reviews and human-pr

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* test: bypass CI check

* fix(pr-review): remove CI bypass hack

* fix(dev-lead): use heredoc for multiline environment variables (#224)

* fix(dev-lead): use heredoc for multiline environment variables

* fix(dev-lead): ensure all engines are installed and context is consistent

* fix(dev-lead): resolve stale model name in engine fallbacks

* fix(dev-lead): improve fallback reliability and script quality

- engine.sh: resolve stale model names in fallback loop; use -latest Gemini aliases
- dev-lead-fix-reviews.sh: remove unused code and fix PR_URL export

* fix(dev-lead): resolve Gemini model names and shell lint warnings

* security(dev-lead): use random heredoc delimiter to prevent inject…
don-petry added a commit that referenced this pull request Aug 7, 2026
…-stale-bot-reviews.yml (#1292)

* fix: silence auth check in fix-stuck-prs when using GitHub App token

GitHub App tokens don't have user scope, so 'gh api user' returns 403.
This is fine - the script still works for PR operations. Suppress the error
so the script completes successfully with app-token fallback label.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: use explicit author instead of @me in stuck PR search

GitHub App tokens don't have user identity, so @me search doesn't work.
Use explicit 'don-petry' author instead to find PRs to fix.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: avoid subshell in while loop to preserve variable state

Using pipe in while loop created subshell where PROBLEM_PRS and FIXED_PRS
counters were incremented but changes didn't persist to parent shell.
Fixed by using process substitution for input instead of pipe.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* docs: add comprehensive status report for GitHub App setup

- Overview of completed work
- GitHub App authentication details
- 24 stuck PRs successfully fixed with approval reviews
- Architectural decisions and rationale
- Configuration guide and usage examples
- Known limitations and troubleshooting

System is now fully operational with GitHub App token authentication,
comprehensive documentation, and all infrastructure in place.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* docs: document stuck PR cleanup status and script fixes

Added documentation for:
- GitHub App token compatibility fixes in fix-stuck-prs.sh
  * Changed @me to explicit author (app tokens have no identity)
  * Fixed subshell variable scope (process substitution vs pipe)
  * Silenced expected 403 auth check error
- Current status: 24 PRs have approvals but remain OPEN
  * Auto-merge failed due to missing GitHub App permission
  * Approvals satisfy branch protection requirement
  * Next: expand permissions or manually merge

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: update list-prs.sh to search all repos by owner instead of @me

GitHub App tokens don't have user identity, so @me/@review-requested searches
fail. Changed to enumerate all repos in don-petry and petry-projects, then
search for open PRs within each repo. This covers the full scope:
- All open PRs in personal don-petry repos
- All open PRs in petry-projects org repos

Resolves 6 consecutive workflow failures due to zero PRs being enumerated.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* docs: add investigation report for PR review agent workflow failures

* fix: move env section before steps in workflow to fix YAML structure

GitHub Actions requires env section to come before steps in job definition.
Moving env definition up and setting GH_TOKEN only in steps that need it.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* script: add backfill-approvals to retroactively apply real approvals

Converts PRs with agent approval comments (but no real GitHub approvals)
to have actual APPROVED reviews. Needed for PRs reviewed before the
gh pr review --approve fix was applied.

Usage:
  scripts/backfill-approvals.sh          # dry-run (preview changes)
  scripts/backfill-approvals.sh false    # apply real approvals

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* feat: add backfill-approvals workflow to run as GitHub App bot

Runs backfill-approvals.sh via GitHub Actions so the approvals are posted
by the bot identity, not the PR author — GitHub rejects self-approvals.

Also fixes subshell counter bug in the script (piped while loop lost variable
state; switched to process substitution).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: convert inner while loop to process substitution to preserve counters

Both the outer repo loop and inner PR loop were piped subshells, causing
all counter increments (approved/skipped/failed) to be lost. The summary
always showed 0. Fixed by using < <(...) process substitution for both loops.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* car-hunt: add VIN-deep-dive flow, printable checklists, location-tier ranking

- Step 5.5 pre-test-drive flow: NHTSA VIN decode + per-VIN unrepaired-recall
  automation via Chrome MCP (Honda LWC owner portal documented as not
  automatable; NHTSA web form works).
- §F printable test-drive checklist generator (reportlab/Platypus PDF) with
  cold-start, model-specific red flags, negotiation table, walk-away rules.
- §G fraud / paperwork verification checklist (vehicle-agnostic, reusable):
  walk-away triggers, VIN three-location match, curbstoning, odometer fraud,
  bill of sale, title transfer, payment protection, stolen-vehicle, title-
  jumping, after-purchase steps.
- Step 5.25 head-to-head comparison template with information-asymmetry rule.
- Step 3 Location Quality Tier scoring (Birmingham AL metro table A/B/C/D)
  with adj-CPM modifier; sheet schema gains Adj.CPM + Tier columns.
- Hard-disqualification rules added: non-running engine keywords, mileage
  inconsistency, mandatory description scrape.
- Drive MCP overwrite limitation documented; sheet ID now read from memory
  rather than hardcoded.
- FB Marketplace operational facts: Birmingham AL city ID 107739635926718,
  /search? vs /vehicles? query handling, React-controlled inline composer,
  send-button selector, seller-name extraction regex.
- scheduled-tasks/used-car-search-{morning,afternoon}: tier A/B priority
  flag, sheet ID read from memory.
- Reference PDF generator scripts checked in for reuse.

* fix: re-approve PRs where approval predates last commit (stale approval)

Instead of skipping PRs that already have any APPROVED review, skip only
those where reviewDecision is not REVIEW_REQUIRED. This handles PRs where
a bot approval was posted before a new commit was pushed — GitHub's ruleset
engine treats those approvals as stale even with dismiss_stale_reviews=false.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: robustly extract JSON verdict from mixed claude --print output (#17)

Claude's --print mode can prefix the JSON verdict with conversational
preamble text, causing jq to fail with parse errors. Add
extract_verdict_json to engine.sh and wire all three cascade action
call sites in review-one-pr.sh to use it.

Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: extract_verdict_json — check dest file first (agent Bash-write style)

The cascade-action agent writes its verdict JSON to \$OUTPUT_FILE via a
Bash tool call, then prints a text summary to stdout. The previous fix
only scanned stdout (the .raw file), which contained no JSON. The agent-
written file at \$dest (= \$OUTPUT_FILE) was already correct — just not
checked. Now check \$dest first before falling back to stdout scan.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Enforce MAX_REVIEW_CYCLES cap before running the cascade (#18)

* fix: enforce MAX_REVIEW_CYCLES cap with human-escalation marker

Previously MAX_REVIEW_CYCLES only gated AI delegation in
post-pr-review.sh — the cascade itself ran on every cron tick
regardless of how many cycles had accumulated. Real-world result:
a PR could rack up 9+ review cycles (we observed exactly that on
ContentTwin#100) before any cap took effect.

Add a pre-cascade check in review-one-pr.sh: when the count of
existing `<!-- pr-review-agent v1 sha=... -->` markers is at or
above MAX_REVIEW_CYCLES (default 3), post one escalation comment
marked `<!-- pr-review-agent escalation -->`, label
needs-human-review, request don-petry, and exit 100 (skip
sentinel — doesn't burn the MAX_PRS budget). The escalation
marker doubles as the no-spam guard: subsequent runs detect it
and exit 100 immediately.

Also reuse a single `gh pr view` for both the cycle count and the
escalation-marker check.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* review feedback: surface escalation-comment failures, drop dead unset

- Drop `2>/dev/null || true` on `gh pr comment` so a failed escalation
  post is visible in the workflow log instead of silently leaving the
  PR with no marker (which would re-trigger the cap path next tick).
- Reword the cycle-count comment from "AI delegation loops" to "review
  loops" — the cap also catches cascade-only loops where every cycle
  approves and a new commit lands before merge.
- Remove the redundant `unset PR_BODIES` — never exported, no leakage.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>

* Reliability hardening: session circuit breaker, timeouts, retry, dedup (#20)

* feat: session circuit breaker, per-tier timeouts, retry, and triage hard-fail

Reliability hardening for the PR review agent.

1. Session circuit breaker (.github/workflows/pr-review.yml): on any
   non-zero, non-100 exit from review-one-pr.sh (general failure or rate
   limit on the fallback engine), break the per-PR loop, log a clear
   error annotation naming the failing PR and reason, and exit the step
   with code 1 so the run shows red. Prevents one systemic problem from
   silently burning the entire candidate pool.

2. Per-tier timeouts (scripts/engine.sh): triage/deep/audit/action/duck
   each get their own bounded timeout (180/600/600/300/300s defaults,
   env-overridable). Previously only the duck had a timeout — a hung
   tier could burn the whole 60min job budget.

3. Retry-with-backoff on transient errors (scripts/engine.sh): triage
   retries once on 124/137/143 (timeout / signal kill) since its caller
   captures stdout via $(...) so retries are safe. Deliberately NOT
   applied to run_agentic/run_duck where stdout is redirected to a file
   — a retry there would corrupt the partial first-attempt output.

4. Triage non-JSON now hard-fails (scripts/review-one-pr.sh): replaces
   the silent fallback that synthesized a fake "escalate=MEDIUM" verdict
   and proceeded to deep review. With the new circuit breaker, loud
   failure is the right call — masking a broken triage was burning
   tokens on every PR while the workflow looked healthy.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix: stop stacking duplicate agent reviews on the same PR

Two bugs were causing the agent to leave multiple comments on the same PR.
Together they produced 10 stacked APPROVED reviews on petry-projects/ContentTwin#100.

Bug A — idempotency check is order-dependent (review-one-pr.sh):
The previous marker-discovery code did:
  ((.reviews // []) + (.comments // [])) | .[].body | grep marker | tail -1
This relies on the array concatenation order, not chronological order. When
old agent comments existed alongside newer agent reviews, tail -1 picked
the comment-array marker (older) over the review-array marker (newer),
causing the script to think the head SHA hadn't been reviewed and re-run.
Replaced with a single jq pipeline that tags each item with submittedAt /
createdAt, sorts by timestamp, and takes the actual most-recent marker.

Bug B — no cleanup of prior agent items (post-pr-review.sh):
After successfully posting a new review/comment, prior agent items were
left in place, accumulating forever. Added mark_prior_agent_items_obsolete
which, after a successful post:
  - dismisses prior APPROVED/COMMENTED/CHANGES_REQUESTED agent reviews via
    the GitHub dismissal API (UI shows them struck-through as Dismissed)
  - edits prior agent comments to wrap their body in a collapsed <details>
    block with a "Superseded by re-review at <SHA>" summary, plus a
    `<!-- pr-review-agent superseded -->` sentinel for idempotency

All cleanup API calls are best-effort — failures don't break the workflow.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(cleanup): warn on API failures, preserve global newest, file-stage JSON

Three fixes to mark_prior_agent_items_obsolete from the review of PR #20:

1. ::warning:: annotations on every cleanup API failure (review/comment
   list-fetch, individual review dismissal, individual comment fetch+edit).
   Previously these were silenced with `|| true`, so a permissions change
   on the dismissal endpoint would let duplicates stack indefinitely with
   no signal in the Actions UI. Cleanup is still non-fatal — the new post
   has already landed — but failures are now visible.

2. Preserve the globally-latest agent item across BOTH categories, not the
   newest of each category separately. The earlier code split reviews and
   comments and applied `[:-1]` to each, which left a stale fix-request
   comment in place when the new post was a review (or vice versa). The
   one-off cleanup of ContentTwin#100 hit exactly this case: 12 stacked
   reviews collapsed to 1, but a stale comment from 2026-04-25 (SHA
   cd9132d6) was preserved as "newest comment" even though the latest
   review at SHA 3af8c8ee was newer overall. Now: compute the max
   timestamp across both feeds, exclude items at that timestamp.

3. Stage API responses to disk (`mktemp` + `jq <file>`) instead of routing
   through `--argjson "$var"`. The old approach broke on rare unescaped
   control chars in user-authored comment bodies (jq refused to parse the
   resulting shell-vared JSON). File-based input sidesteps the shell
   pipeline entirely.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>

* Remove car-hunt skill and scheduled tasks (moved to don-petry/don-petry) (#21)

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: apply Copilot fallback hardening and reuse GH_PAT for Copilot auth

- Use secrets.GH_PAT (existing personal account token with Copilot
  subscription) instead of a separate COPILOT_GITHUB_TOKEN secret
- Pin actions/create-github-app-token to SHA (v3.1.1) for supply chain safety
- Add post-install verification for gh-copilot extension with warning on failure
- Guard Copilot fallback path: skip PR gracefully if extension is not installed

Addresses issue #24 workflow fixes (1-3) that could not land via PR #25
due to GitHub App lacking workflows permission.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: switch Copilot engine to gh built-in and fix app-id deprecation

- Remove gh extension install (github/gh-copilot conflicts with built-in alias)
- Replace with gh copilot --version check using COPILOT_GITHUB_TOKEN (GH_PAT)
- Update all engine.sh copilot invocations from bare `copilot` binary to
  `gh copilot suggest --target shell` with GH_TOKEN overridden to GH_PAT
  so the user token (with Copilot subscription) is used, not the App token
- Fix actions/create-github-app-token: app-id → client-id (same secret value,
  just the renamed input in v3.x)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: harden Copilot fallback path and remove stale worktrees (#25)

- gh-copilot install: add --force flag and post-install verification
  warning so silent failures due to built-in alias conflict are visible
  in workflow logs instead of being silently swallowed by `|| true`
- fallback pre-flight check: verify `gh extension list | grep copilot`
  before switching to Copilot engine; if unavailable, skip the PR and
  continue the batch rather than session-aborting and dropping all
  remaining candidates (fixes the 27-PR drop on run #503)
- pin actions/create-github-app-token to SHA for v3.1.1 (Node.js 24
  compatible) to prevent hard break on 2026-06-02 when GitHub forces
  Node.js 24 as default runtime
- remove 6 stale Claude Code worktrees from git tracking; add
  .gitignore entry to prevent future worktrees from being committed
  (eliminates exit-128 warning on every actions/checkout post-job sweep)

Closes #24

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: petry-projects-pr-review-agent[bot] <petry-projects-pr-review-agent[bot]@users.noreply.github.com>

* ci: add pre-flight dedup check to prevent duplicate claude-issue PRs (#26)

Before invoking Claude on a labeled issue, a new shell step queries
for any open PR with a branch matching claude/issue-NNN-* (or a body
containing "Closes #NNN"). If one is found it posts a comment on the
issue pointing to the existing PR and skips the Claude step entirely.
A secondary prompt instruction tells Claude to check first and push to
the existing branch rather than opening a new PR.

Motivated by issue petry-projects/google-app-scripts#171, where the
claude label was re-applied three times and each run created a fresh
PR unaware of the prior attempts.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor: migrate to machine user PAT auth (closes #27) (#28)

* refactor: migrate from GitHub App to machine user PAT auth

Closes #27

GitHub Apps cannot be listed in CODEOWNERS, blocking PRs in repos with
require_code_owner_review: true. Switch all workflows to use a machine
user account's fine-grained PAT (DON_PETRY_BOT_GH_PAT secret), which can
join an org team listed in CODEOWNERS.

Workflows: removed actions/create-github-app-token steps in pr-review,
fix-stuck-prs, backfill-approvals, and daily-pr-review-health. All now
use secrets.DON_PETRY_BOT_GH_PAT directly.

Docs: renamed GITHUB_APP_SETUP.md to MACHINE_USER_SETUP.md with full
rewrite covering account creation, CODEOWNERS config, PAT generation,
and rotation. Updated auth sections in IMPLEMENTATION.md, SETUP.md,
STATUS.md, DOCUMENTATION.md, README.md.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* refactor: switch to org-scoped PAT secret DON_PETRY_BOT_PETRY_PROJECT_PAT

Replace DON_PETRY_BOT_GH_PAT with DON_PETRY_BOT_PETRY_PROJECT_PAT — the
new fine-grained PAT scoped to the petry-projects org (resource owner =
petry-projects). The previous PAT was scoped to the donpetry-bot personal
namespace and had no repository access.

The old DON_PETRY_BOT_GH_PAT secret is retained for any future use against
don-petry's personal repos.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* chore: ignore .claude/scheduled_tasks.lock state file

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>

* refactor: merge approval repair scripts into single automation

- Consolidate backfill-approvals.sh and fix-stuck-prs.sh into repair-pr-approvals.sh
- Iterate all repos in both orgs instead of assuming author
- Verify no existing APPROVED review before posting
- Copy original agent comment as review body
- Enable auto-merge when posting approval if needed
- Single workflow with 30-min timeout

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: add @mention trigger for on-demand PR reviews (#30)

Adds repository_dispatch support so commenting @petry-review-bot on any
PR fires an immediate review without waiting for the hourly schedule.

- pr-review.yml: new `repository_dispatch` trigger (type: pr-review-mention),
  per-PR concurrency group for mention runs, FORCE_REVIEW and DRY_RUN
  env vars that handle both workflow_dispatch and repository_dispatch paths
- review-one-pr.sh: FORCE_REVIEW=true bypasses idempotency so a mention
  always runs a fresh cascade even if the head SHA hasn't changed
- templates/mention-listener.yml: deploy to petry-projects/.github;
  listens for @petry-review-bot, validates commenter trust, posts ack,
  sends repository_dispatch (requires Contents:write, not Actions:write)

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: harden rebase, review dismissal, and health log diagnostics (#40)

Addresses 4 code-actionable recommendations from health check report #33:

- [CRITICAL] Make rebase/branch-update non-fatal — 403/504 during rebase
  emits ::warning:: instead of aborting the batch session (#34)
- [MEDIUM] Add retry with exponential backoff (3 attempts) for transient
  5xx on update-branch API; break immediately on 4xx (#37)
- [MEDIUM] Guard review dismissal with state re-check before calling
  dismiss API, preventing 422s from race conditions (#38)
- [LOW] Surface missing log warnings in health check script (#39)
- Fix: skip auto-merge when branch is still BEHIND after failed rebase

Closes #34, closes #37, closes #38, closes #39

* refactor: migrate to org-wide .github-private convention (#41)

* refactor: parametrize hardcoded identity values for org migration

Replace all hardcoded references to don-petry, petry-review-bot, and
don-petry/pr-review-agent with environment variables that default to
the current values. This allows the agent to be configured for different
orgs/users via repo variables.

Changes:
- Scripts use $REVIEWER_USER, $TARGET_ORG, $BOT_USER, $AGENT_REPO
- Prompts no longer reference specific GitHub usernames
- Workflows use org-level GH_PAT_WORKFLOWS secret instead of
  repo-level DON_PETRY_BOT_PETRY_PROJECT_PAT
- Health check uses context.repo.owner/repo for issue creation
- Variables are set in workflow env block with defaults

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add Copilot custom agent profiles for org-wide use

Create /agents/ directory with three agent profiles:
- pr-reviewer: Multi-tier cascading PR review
- feature-ideator: Feature idea generation and prioritization
- compliance-auditor: Org standards compliance checking

These are Copilot custom agent profiles that become available
org-wide in the .github-private repo convention.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Squashed 'frameworks/bmad-method/' content from commit e36f219c

git-subtree-dir: frameworks/bmad-method
git-subtree-split: e36f219c81b6010d4aae423ba12f49edb5b6e31a

* Squashed 'frameworks/spec-kit/' content from commit 11f49ebf

git-subtree-dir: frameworks/spec-kit
git-subtree-split: 11f49ebfb2f6af55345cb4bd9a7906acd211e56f

* Squashed 'frameworks/gsd/' content from commit 304c1a13

git-subtree-dir: frameworks/gsd
git-subtree-split: 304c1a1302564c45af252bbba4bcc5350e7dac3a

* docs: update README for .github-private org infrastructure role

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* ci: add dependabot-automerge.yml workflow (#73)

Adds the required dependabot-automerge.yml workflow from the org
standard template (petry-projects/.github/standards/workflows/).
This is a thin caller stub that delegates to the org-level reusable
workflow.

Closes #48

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Don Petry <don-petry@users.noreply.github.com>

* fix(pr-review): unblock queue starvation from self-authored PRs (#96) (#97)

* fix(pr-review): unblock queue starvation from self-authored PRs (#96)

A self-authored PR sorted first in the candidate list and triggered
"Can not approve your own pull request" — which the session-fatal
abort treated like an engine error, skipping all 28 remaining PRs
on every run.

- list-prs.sh: filter out PRs authored by REVIEWER_USER at enumeration
- post-pr-review.sh: catch the GraphQL self-approval error and exit 100
  (no-op) instead of 1, so a stray self-PR can no longer abort the batch
- engine.sh: gh copilot renamed --target to --agent; fix the rubber-duck
  invocations so tier-2 cross-engine review works again
- review-one-pr.sh: stop appending a duplicate "0" to REVIEW_CYCLE when
  grep -c finds no markers under set -o pipefail (was breaking the
  cycle-cap integer comparison)

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

* fix(pr-review): authenticate as bot, harden enumeration, address review

- Workflow now runs as AGENT_USER (default don-petry-bot), distinct from
  REVIEWER_USER (the human, don-petry). The self-approval rejection that
  caused #96 came from the agent and the human sharing one identity.
- list-prs.sh filters self-authored PRs against AGENT_USER, and validates
  AGENT_USER against the GitHub username charset before interpolating into
  the jq filter (Copilot review feedback).
- review-one-pr.sh uses printf '%s\n' instead of echo for PR_BODIES, since
  PR body content is user-authored and could begin with -n/-e or contain
  backslash escapes (Copilot review feedback).
- AGENT.md guideline updated to reflect that self-authored PRs are
  intentionally excluded (CodeRabbit review feedback).

Operator note: rotate the GH_PAT_WORKFLOWS secret to a token owned by
don-petry-bot (with read:org added) for the bot-account behavior to take
effect at runtime.

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

* refactor(pr-review): collapse to single BOT_USER, route escalations via CODEOWNERS

Drop AGENT_USER and REVIEWER_USER. The workflow now has one identity:
BOT_USER (default don-petry-bot), which both owns the repos to scan and
gets filtered out as the self-approval blocker. Human escalation no
longer hard-codes a single reviewer; instead, scripts/request-codeowners-
review.sh parses CODEOWNERS in the PR's repo and requests review from
every @user / @org/team mention.

- list-prs.sh: BOT_USER for both gh-repo-list and self-author filter
- review-one-pr.sh, post-pr-review.sh: replace --user "$REVIEWER_USER"
  with the CODEOWNERS helper
- repair-pr-approvals.sh: same gh-repo-list switch
- pr-review.yml: drop AGENT_USER/REVIEWER_USER, set BOT_USER default
  to don-petry-bot
- AGENT.md: updated guideline to describe single-identity model

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

* refactor(pr-review): extract batch loop, hoist env, drop redundant step

Workflow YAML drops from 231 to 111 lines by moving the per-PR review
loop into scripts/review-batch.sh. The script can be syntax-checked and
exercised locally; the workflow now just wires env vars and dispatches.

- Hoist GH_TOKEN, MAX_PRS, CANDIDATE_LIMIT to job-level env (were
  repeated on individual steps).
- Inline `gh auth status` into the install step; remove the standalone
  Verify auth step (its only output was a one-line auth dump).
- Drop the `if: steps.list.outputs.count != '0'` guard and the step's
  `id`/output: review-batch.sh handles empty input as a no-op.
- Collapse the duplicated summary-string branch in the review loop into
  a single template with a conditional fragment.

No behavior change.

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

* perf(pr-review): cache claude-code CLI install across runs

`npm install -g @anthropic-ai/claude-code` ran on every workflow start
(~30s). Switch to a per-user npm prefix (~/.npm-global) and cache that
directory via actions/cache, keyed on CLAUDE_CODE_VERSION + runner OS.

A `command -v claude` guard makes the install a no-op on cache hit, so
the only cost on subsequent runs is the cache restore.

CLAUDE_CODE_VERSION defaults to 'latest' (cache persists until manually
flushed); set the repo variable to pin a specific version for fully
reproducible caching.

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

---------

Co-authored-by: Claude <noreply@anthropic.com>

* chore: rename GH_PAT_WORKFLOWS secret to DON_PETRY_BOT_GH_PAT (#99)

The previous name was generic and didn't tell you which account the PAT
belonged to. The new name makes the binding explicit: this secret is the
PAT owned by don-petry-bot, used as BOT_USER throughout the PR-review
workflows.

Operator follow-up before this can be merged:
- Add a new repo secret DON_PETRY_BOT_GH_PAT containing the bot's PAT
  (with repo, workflow, and read:org scopes).
- After merge, the old GH_PAT_WORKFLOWS secret can be deleted.

Affected workflows:
- pr-review.yml (1 use)
- claude.yml (4 uses, with || github.token fallback)
- daily-pr-review-health.yml (1 use)
- repair-pr-approvals.yml (1 use)

scripts/pr_review_health.sh's error message is updated to point at the
new name as well.

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

Co-authored-by: Claude <noreply@anthropic.com>

* chore: remove frameworks directory (#101)

Co-authored-by: Gemini CLI <gemini-cli@example.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore all erroneously deleted files (items 4-34) (#104)

* revert: restore .github/workflows/daily-pr-review-health.yml

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore .github/workflows/repair-pr-approvals.yml

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore agents/pr-reviewer.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/cascade-action.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/deep-review.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/rubber-duck.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/security-audit.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/shared.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/single-review.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/synthesize-duck.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/synthesize.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/triage.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/engine.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/list-prs.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/post-pr-review.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/pr_review_health.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/repair-pr-approvals.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/request-codeowners-review.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/review-batch.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/review-one-pr.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore templates/mention-listener.yml

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore AGENT.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore BOT_SETUP.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore DOCUMENTATION.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore IMPLEMENTATION.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore MACHINE_USER_SETUP.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore PR_REVIEW_AGENT_FAILURE_REPORT.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore PR_REVIEW_FAILURE_INVESTIGATION.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore SETUP.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore STATUS.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore README.md to pre-change state

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore .github/workflows/daily-pr-review-health.yml

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat: add Gemini Pro support and optimize PR review fallback chain (#102)

* Merge main and resolve conflicts

* Enhance pr-review workflow: add Gemini support and refactor fallback logic

* Address Copilot review comments: tighten regex, fix fallback summary, and align docs

* docs: move agent documentation to docs/ folder and rename files

* docs: align secret names and fix casing in index

* docs: organize pr-review-agent documentation under dedicated folder

* Address final PR comments: tighten regex, add preflight checks, fix bot names, and cleanup docs

---------

Co-authored-by: Gemini CLI <gemini-cli@example.com>

* fix(pr-review): gate rate-limit detection on non-zero exit code

Broad patterns like `plan.*limit` and `claude.*usage` could match content
in a successful triage summary, triggering a false-positive engine fallback.
Guard the check with TRIAGE_RC != 0 so it only fires when the provider
command actually failed.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(claude): sync inlined workflow with org standard (#109)

* fix(claude): sync inlined workflow with org standard

Brings the inlined claude.yml up to parity with
petry-projects/.github/.github/workflows/claude-code-reusable.yml.

Changes:
1. Bot allow list for pull_request_review_comment
   Add coderabbitai[bot], Copilot, copilot-pull-request-reviewer[bot],
   and gemini-code-assist[bot] alongside the existing OWNER/MEMBER/
   COLLABORATOR check. These bots have author_association 'NONE' so
   their review comments were always skipped.

2. check_run trigger + claude-ci-fix job
   Port the CI failure auto-fix feature from the reusable. When a check
   fails on a PR, Claude diagnoses and fixes it automatically.
   Adapted to use DON_PETRY_BOT_GH_PAT (this repo's secret name).

3. claude-code-action SHA bump: v1.0.89 → v1.0.119
   Update both the claude and claude-issue jobs.

4. paths-ignore on pull_request trigger
   Prevents the workflow from firing on PRs that only change
   claude.yml itself, avoiding the Anthropic OIDC validation
   failure (workflow file must match default branch at token exchange).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(claude): guard bot allow list against fork PRs

Same fix as petry-projects/.github PR #238 — add same-repo guard
for bot-triggered pull_request_review_comment runs.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(claude): address ci-fix self-loop and fork PR security

- Replace Claude Code name prefix check with explicit job name list to
  correctly prevent self-loops (check_run names for inlined workflows
  are bare job names, not workflow-prefixed)
- Add fork PR trust gate in Resolve PR number step: verify head repo
  matches target repo before running Claude with privileged credentials

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* ci: replace inlined claude.yml with standard thin-caller stub

The inlined version embedded all three jobs (claude, claude-ci-fix,
claude-issue) directly and threaded DON_PETRY_BOT_GH_PAT through
checkout tokens and github_token, causing the bot's PAT to author
PRs and comments instead of github-actions[bot].

Replace with the standard thin caller that delegates to the org-level
reusable workflow (claude-code-reusable.yml@v1) via secrets: inherit,
which is the correct pattern per:
petry-projects/.github/standards/workflows/claude.yml

* fix: replace gh copilot suggest with GitHub Models REST API (#151)

* fix: replace gh copilot suggest with GitHub Models REST API (#147)

The `gh copilot suggest -p "$(cat <file>)"` invocation failed with
"Invalid command format" because:
1. The `-p` flag is not valid syntax in modern `gh copilot` built-in versions.
2. `gh copilot suggest` is a shell-command suggestion tool; it does not
   accept large PR prompts or return structured JSON responses.
3. Passing a multi-thousand-line prompt via `$(cat ...)` can hit ARG_MAX.

The non-zero exit was then misclassified by the rate-limit detector, which
aborted the entire session and skipped all remaining PRs in the queue.

Fix: replace all three copilot invocations (run_triage, run_agentic,
run_duck) with a new `copilot_chat` helper that calls the GitHub Models
REST API directly via curl. The API is OpenAI-compatible, versioned via
`X-GitHub-Api-Version`, stable against gh CLI version changes, and accepts
arbitrary prompt sizes using python3 for safe JSON encoding.

Additional changes:
- Add `COPILOT_API_MODEL` var (default `openai/o4-mini`) overrideable via env.
- run_agentic/run_duck copilot cases now also write output to $OUTPUT_FILE
  so callers that check that path directly (deep review, audit) find the JSON.
- Rate-limit responses (HTTP 429) are echoed to stdout so the existing
  `is_rate_limited()` detector fires correctly for engine fallback.
- Add pre-flight smoke test in review-batch.sh: tests GitHub Models API
  connectivity with a one-liner prompt before processing any PRs, so auth
  or model errors surface immediately as a clear setup failure.
- Add tests/test_copilot_chat.sh: unit tests for the JSON payload builder
  with edge-case prompts (quotes, newlines, # headings, large diffs, Unicode).

Closes #147

Co-authored-by: Don Petry <don-petry@users.noreply.github.com>

* fix: scope COPILOT_API_MODEL export to copilot engine only

Move the `export COPILOT_API_MODEL` from the common exports block into the
`copilot)` case where the variable is set, so it is not exported as an
empty/unset variable when the engine is `claude` or `gemini`. The
`copilot_chat` fallback `${COPILOT_API_MODEL:-openai/o4-mini}` still works
correctly when `DUCK_ENGINE=copilot` under a non-copilot primary engine.

Co-authored-by: Don Petry <don-petry@users.noreply.github.com>

* fix: address review comments — temp file for payload, fail-fast source, streaming output

- copilot_chat: write JSON body to mktemp file, pass to curl as @file to
  avoid ARG_MAX for large PR diffs (was --data-binary "$body")
- copilot_chat: add :? guard on COPILOT_GITHUB_TOKEN for a clear error
  instead of generic "unbound variable" under set -u
- run_agentic / run_duck copilot paths: stream directly to stdout (and
  tee to OUTPUT_FILE when set) rather than buffering the full response
  into a shell variable, which forced large outputs into memory and
  stripped trailing newlines
- review-batch.sh pre-flight: fail fast if source engine.sh fails
  (was silently ignored with || true)
- review-batch.sh pre-flight: build smoke-test JSON payload via python3
  + temp file instead of shell string interpolation to avoid JSON
  injection if COPILOT_API_MODEL contains special characters
- Clarify that openai/o4-mini is the correct April-2025 o4-generation
  model name, not a typo for o1-mini or gpt-4o-mini

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Don Petry <don-petry@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* feat: prioritise .github/.github-private PRs, oldest-first within tier (#155)

* feat: prioritise .github/.github-private PRs, oldest-first within tier

- list-prs.sh: add createdAt to JSON fetch; emit priority|createdAt|url
  lines (priority 0 for .github/.github-private, 1 for everything else)
- Replace final sort -u with a two-pass sort: deduplicate by URL, then
  sort by priority asc then createdAt asc; strip sort keys with cut
- tests/test_list_prs_sort.sh: 16 tests covering priority classification,
  oldest-first ordering, mixed scenarios and deduplication
- .github/workflows/test.yml: run both unit-test files on every PR / push

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* test: add dedup edge case for same URL with conflicting priorities

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: remove temperature from copilot_chat payload (o4-mini incompatible)

The GitHub Models API rejects temperature=0 for reasoning models like
o4-mini with HTTP 400:
  'Unsupported value: temperature does not support 0 with this model.
   Only the default (1) value is supported.'

This caused the rubber duck (DUCK_ENGINE=copilot / DUCK_MODEL=o4-mini)
to fail with HTTP 400 on every claude-engine run, silently degrading
every review to deep-only with no cross-engine sanity check.

Fix: remove temperature from the copilot_chat JSON payload entirely —
the API defaults to 1, which is the only supported value for o4-mini.

Also:
- Update tests/test_copilot_chat.sh: sync build_payload to match and
  flip Test 9 to assert temperature is ABSENT (not 0).
- Add .github/workflows/test.yml: run unit tests on every PR and push
  to main so regressions are caught before merge.

Reproducer: job/75690347409 — duck failure logged as:
  copilot_chat: HTTP 400 from GitHub Models API
  {"error":{"message":"Unsupported value: 'temperature' does not
   support 0 with this model..."}}

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: address PR review comments

- list-prs.sh: tighten priority regex to /[.]github(-private)?/pull/
  so path-boundary anchor prevents false positives (e.g. foo.github)
- list-prs.sh: replace printf pipeline with here-string to avoid ARG_MAX
  limits on large PR sets
- test.yml: add permissions: contents: read (least-privilege, CodeQL fix)
- test.yml: add timeout-minutes: 5 to prevent hung runs
- test_list_prs_sort.sh: sync sort_entries helper (here-string + || true)
  and classify regex to match updated list-prs.sh exactly

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: don-petry <don@petry.dev>

* fix: restore pr-review.yml content (accidentally emptied in revert)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: restore pr-review.yml (file was accidentally emptied)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore(deps): bump actions/cache from 4 to 5 (#165)

Bumps [actions/cache](https://github.com/actions/cache) from 4 to 5.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): bump petry-projects/.github/.github/workflows/claude-code-reusable.yml (#164)

Bumps [petry-projects/.github/.github/workflows/claude-code-reusable.yml](https://github.com/petry-projects/.github) from 1 to 2.
- [Commits](https://github.com/petry-projects/.github/compare/v1...v2)

---
updated-dependencies:
- dependency-name: petry-projects/.github/.github/workflows/claude-code-reusable.yml
  dependency-version: '2'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* fix(list-prs): search DELEGATION_ORGS and drop --checks success pre-filter

- Add iteration over all orgs in DELEGATION_ORGS (e.g. don-petry) so PRs
  in those orgs enter the candidate pool. Previously only BOT_USER and
  TARGET_ORG were searched.
- Remove --checks success from org repo searches. GitHub excludes PRs from
  repos with no CI configured when this flag is used, silently dropping all
  PRs from repos like .github, .github-private, google-app-scripts, and
  TalkTerm. review-one-pr.sh already enforces CI gating per-PR and treats
  empty statusCheckRollup as passing, so the pre-filter is redundant and
  harmful.
- Add --limit 200 to gh repo list calls to handle org growth beyond gh's
  default 30-repo cap.

* feat: add code-quality ruleset (compliance fix #60) (#86)

Creates the required `code-quality` repository ruleset enforcing required
status checks on the default branch, as mandated by the org standard:
standards/github-settings.md#code-quality--required-checks-ruleset-all-repositories

Required checks:
- SonarCloud (code quality analysis)
- CodeQL (SAST)
- agent-shield / AgentShield (agent security scan)
- dependency-audit / Detect ecosystems (dependency vulnerability scan)

Bypass actors:
- OrganizationAdmin (always) — emergency override
- dependabot-automerge-petry Integration (always) — Dependabot auto-merge

The ruleset was applied directly via GitHub API. This file documents the
configuration as code for auditability and future reapplication.

Closes #60

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Don Petry <don-petry@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* feat(prompts/dev-lead): add human prompt template

* feat(prompts/dev-lead): add human-pr prompt template

* feat(dev-lead): implement dev-lead agent Phases 0-6

* fix: per-PR isolation and single-review retry (closes #132)

- review-batch.sh: non-rate-limit per-PR failures (exit code 1) no
  longer abort the session. SESSION ABORTED EARLY is now reserved for
  the rate-limit-on-fallback-engine case (exit code 2) only. All other
  failures are counted and logged; remaining candidates continue.

- review-one-pr.sh: single-review step retries up to
  SINGLE_REVIEW_MAX_RETRIES (default 2) times with a
  SINGLE_REVIEW_RETRY_DELAY_SEC (default 15s) gap before giving up.
  On exhaustion, the PR is flagged needs-human-review and the script
  exits with code 1, which the updated batch treats as a non-fatal
  per-PR failure. Raw model output and stderr are logged on each failed
  attempt for post-mortem visibility.

Root cause of run #25707852006: claude-opus-4-7 returned a verbose
non-JSON response for PR #129; the old code treated that as fatal and
skipped 35 remaining candidates.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix: rate-limit detection and per-attempt stderr logs in single-review retry

Address inline review comments on PR #133:

- Rate-limit check: after each run_agentic call, inspect both stdout
  (VERDICT_JSON.raw) and stderr (SINGLE_LOG) with is_rate_limited before
  retrying. A rate-limit match exits immediately with code 2 so
  review-batch.sh can trigger engine fallback — consistent with triage and
  deep-review tiers. Previously a rate-limited single-review would burn all
  retries and exit 1 (per-PR failure), silently leaving the batch on the
  same rate-limited engine for all remaining PRs.

- Per-attempt log files: stderr is now written to
  single-review-attempt-N.log rather than a single overwritten file, so
  no earlier-attempt errors are lost. Each attempt logs its own stderr
  inline on failure; the fallback path cats all attempt logs for
  post-mortem visibility.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* feat(dev-lead): Phase 0 test infrastructure and Phase 1 intent stub

Phase 0: full test harness for the dev-lead agent —
  26 event fixtures (all valid JSON with _test_expected_intent),
  stub claude/gemini engines, mock gh binary, CI failure log sample,
  bats helpers (stub-engine, mock-gh, assert-env, prompt-vars),
  7 prompt templates with VARIABLES declarations, preflight script,
  prompt coverage integration test, and test-dev-lead.yml CI workflow.

Phase 1: dev-lead.yml trigger workflow (all 7 event types, dispatch + ci-relay
  jobs) and dev-lead-intent.sh stub (anti-loop guard live; all other events
  emit skip/not-implemented).  14/14 bats unit tests pass.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): add permissions blocks to test workflow jobs (CodeQL)

* fix(ci): auto-fix for lint / eslint [skip ci-relay]

* feat(dev-lead): Phases 2-6 — CI fix, review fix, issue, engine fallback

Phase 2: run_writer/run_writer_with_fallback in engine.sh, full intent
routing in dev-lead-intent.sh, dev-lead-fix-ci.sh handler, workflow wiring.

Phase 3: dev-lead-fix-reviews.sh handles fix-reviews, fix-bot-comment,
human, human-pr, rebase intents. Full review routing in intent classifier.

Phase 5: dev-lead-fix-issue.sh handles the issue intent with dedup guard
and branch/PR creation.

Phase 6: run_writer_with_fallback with claude→gemini→copilot fallback chain.

Tests: 77 unit tests across 8 new .bats files, all passing. Updated Phase 1
stub tests to reflect actual Phase 2+ routing behavior.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): address review comments — dispatch JSON, dead code, portable stub gh

* fix(dev-lead): pre-flight after intent, fork URL check, review-batch exit code, retry comment

* fix(dev-lead): install bats-core from GitHub to avoid root requirement

* fix(security): move event values to env vars to prevent script injection (SonarCloud)

* fix(security): move CLAUDE_CODE_VERSION to env block (SonarCloud script injection)

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* feat(dev-lead): Phase 1.5 — reusable workflow for cross-repo adoption + Phase 7 shadow period

* feat(dev-lead): add reusable workflow (Phase 1.5) and begin shadow period (Phase 7)

- Create .github/workflows/dev-lead-reusable.yml: workflow_call entry
  point for other repos. Checks out .github-private scripts/prompts into
  .dev-lead/, then runs the same intent-classify + handler pipeline as
  dev-lead.yml with PROMPTS_DIR=.dev-lead/prompts/dev-lead.
- Add PROMPTS_DIR env-var support to dev-lead-fix-ci.sh,
  dev-lead-fix-reviews.sh, and dev-lead-fix-issue.sh so the reusable
  workflow can point scripts at the sparse-checkout path without changing
  CWD. Defaults to prompts/dev-lead (backwards-compatible for dev-lead.yml).
- Annotate dev-lead.yml with Phase 7 shadow-period window (2026-05-15
  through ~2026-05-29): claude.yml and dev-lead.yml run in parallel until
  regressions clear, then claude.yml is removed.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* chore(dev-lead): update shadow-period tracking issue ref to #180

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): document PAT requirement and pin ref:main on private checkout

* fix(dev-lead): P1 review fixes — ci-relay in reusable, .dev-lead gitignore, export PROMPTS_DIR, trim header

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): rate-limit detection, separate status, and retry cron (#192) (#196)

* chore(dev-lead): decommission claude.yml, bump ACTION_TIMEOUT_SEC to 600s

- Delete .github/workflows/claude.yml — replaced by dev-lead.yml (shadow
  period complete, tracking issue #180).
- scripts/engine.sh: raise ACTION_TIMEOUT_SEC default 300→600s to reduce
  timeout failures on large-repo fix-ci runs (PR #80 had 3 timeouts).
- AGENTS.md: remove claude.yml immutability exemption; note dev-lead.yml
  as the active AI automation workflow and its edit-via-reusable pattern.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* refactor: replace Claude analysis with pure gh/jq telemetry in health check

* refactor: drop Node/Claude steps from daily-pr-review-health workflow

* rename: daily-pr-review-health → actions-fleet-monitor

* rename: daily-pr-review-health → actions-fleet-monitor

* fix(agents-md): clarify dev-lead.yml vs dev-lead-reusable.yml scope

dev-lead.yml in .github-private runs inline steps (not a caller stub).
Behavior changes for this repo go to dev-lead.yml directly; changes that
affect all org repos via the cross-repo reusable go to dev-lead-reusable.yml.

Addresses Copilot review on PR #194.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): rate-limit detection, separate status, and retry cron

Closes #192. Implements all phases from the revised plan.

**Phase 0 — fix run_writer stdout capture (prerequisite)**
- engine.sh: capture stdout via `tee` to a tempfile so `is_rate_limited`
  can inspect the output; old code read `/tmp/dev-lead-writer-stderr`
  which was never written (claude --print outputs to stdout, not stderr).
  Fallback engines were never tried in practice.
- engine.sh: add `parse_reset_time` to extract ISO timestamp from
  `resets H:MMpm (UTC)` in engine output and write it to
  `/tmp/dev-lead-rate-limit-reset` for callers to embed in markers.

**Phase 1 — separate status=rate-limited from status=failed**
- fix-ci.sh: detect engine exit 2 → post `status=rate-limited` (not
  `status=failed`); embed parsed reset time in marker body; exit 2.
- fix-ci.sh: fix `check_idempotency` to treat `status=rate-limited`
  as retriable — only block on terminal statuses (applied, failed,
  no-changes). Previously, rate-limited markers blocked all retries.
- fix-ci.sh: `count_recent_failures` already filtered to `status=failed`;
  add explicit comment confirming rate-limited markers are excluded.
- fix-ci.sh: add `has_rate_limited_marker` dedup check so the same SHA
  never accumulates more than one rate-limited comment.
- fix-reviews.sh: on engine exit 2 for all five intent types
  (fix-reviews, fix-bot-comment, human, human-pr, rebase), post a
  `status=rate-limited` marker with embedded reset time and exit 2.
- fix-reviews.sh: for `human` and `human-pr` intents, also post a
  user-visible acknowledgment comment so users know their request was
  received and will be retried.

**Phase 2 — scheduled retry cron for fix-ci**
- dev-lead-retry.sh: new script scans all open PRs across TARGET_ORG
  for `status=rate-limited` markers on current HEAD SHA; skips PRs
  whose reset time is still in the future; staggered dispatches (30s
  between repos) to prevent cascading org-level rate-limit hits.
- dev-lead-retry.yml: new workflow — scheduled every 2 hours (dual
  offsets to work around GitHub scheduler skew), plus workflow_dispatch.

**Phase 3 — retry for fix-reviews intents (same cron)**
- dev-lead-retry.sh already handles all five fix-reviews intent types
  (fix-reviews, fix-bot-comment, human, human-pr, rebase) via
  `dev-lead-reviews-retry` dispatch events.
- dev-lead-intent.sh: add `dev-lead-reviews-retry` dispatch type routing
  to the intent classifier — maps intent_type from payload to the correct
  existing fix-reviews step.
- dev-lead.yml: add `dev-lead-reviews-retry` to repository_dispatch types.
- dev-lead.yml: unify concurrency group for all dispatch types to
  `dev-lead-pr-{pr_number}` to avoid per-type slot fragmentation.

**Tests**
- test_engine_writer.bats: 8 new tests covering rate-limit stdout detection,
  exit-2 mapping, fallback exhaustion, reset time parsing.
- test_fix_ci.bats: 5 new tests covering rate-limited status, exhaustion
  exclusion, idempotency pass-through, and dedup.
- test_fix_reviews.bats: 5 new tests covering all intent types + human ack.
- test_intent_ci.bats: 4 new tests for dev-lead-reviews-retry dispatch routing.
- e2e/scenarios/07-rate-limit-retry.sh: new E2E scenario covering all parts.
- 3 new event fixtures for repository_dispatch_reviews_retry_* payloads.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): address review comments on #196

Addresses all 18 review threads from Copilot, Codex, and Gemini.

**P1 bugs fixed:**
- dev-lead-retry.sh: all log echo calls in scan_pr_for_rate_limits and
  dispatch_* functions now write to stderr; only the final count is on
  stdout. Previously, log lines polluted the command substitution,
  causing arithmetic expansion to fail and aborting the scan on the
  first retry candidate.
- dev-lead-retry.yml: permissions: contents was read-only; creating
  repository_dispatch events requires contents: write.

**Correctness fixes:**
- dev-lead-retry.sh: restrict automated retries to intents whose context
  can be reconstructed at runtime (fix-reviews, human-pr, rebase). human
  and fix-bot-comment require USER_INSTRUCTION/COMMENT_BODY from the
  original event which cannot be re-fetched; they are explicitly excluded.
- dev-lead-fix-reviews.sh: resolve HEAD_SHA from the PR API when not
  provided by the triggering event (issue_comment intents carry no SHA).
  Without this, rate-limited markers had no sha= field and were
  invisible to the retry scanner.
- dev-lead-fix-reviews.sh: write terminal status=applied marker after
  successful fix-reviews, human-pr, rebase runs. Prevents the retry
  cron from re-dispatching the same intent on every subsequent tick when
  the SHA hasn't changed.
- dev-lead-retry.sh: check for reviews terminal marker before dispatching
  a retry (mirrors the existing fix-ci terminal-marker check).
- dev-lead-retry.sh: look up current check-run details (details_url, id)
  at dispatch time via the commits check-runs API, so retried fix-ci runs
  have full failure logs and annotations rather than empty fields.
- dev-lead-fix-ci.sh: embed check= field in the rate-limited marker so
  the retry cron knows which check run to look up.
- dev-lead-fix-reviews.sh: human intent now posts a "please re-mention
  @dev-lead" ack instead of "I'll retry automatically" since it will NOT
  be retried automatically. human-pr keeps the auto-retry ack (correct).

**Pagination (Copilot + Gemini):**
- dev-lead-fix-ci.sh: add --paginate to check_idempotency, count_recent_
  failures, has_rate_limited_marker.
- dev-lead-fix-reviews.sh: add --paginate to has_reviews_rate_limited_marker.
- dev-lead-retry.sh: add --paginate to PR comments and PR list fetches.

**Other improvements:**
- dev-lead-retry.yml: simplify to single cron schedule (15 */2 * * *);
  remove BOT_USER env var (unused in the script).
- dev-lead-retry.sh: raise repo list limit 200 → 1000; add hard-error
  when list_repos_for_org returns empty (surfaces token permission issues
  rather than silently scanning 0 repos).
- dev-lead-intent.sh: use jq -nc (compact, null-input) for context JSON
  construction — safe against values with special characters.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(pr-review): handle Gemini trust-mode failure and properly route engine fallbacks (#217)

* fix(pr-review): handle Gemini trust-mode failure and properly route engine fallbacks

Closes #208

* fix(ci): install gemini CLI and set trust mode for dev-lead agents

---------

Co-authored-by: Gemini CLI <gemini-cli@example.com>

* fix: resolve YAML syntax error in dev-lead-reusable workflow

* fix: address automated feedback from PR 217 (#219)

This addresses missed feedback from CodeRabbit (removing syntax errors in YAML) and gemini-code-assist (improving string concatenation in validate-engines.sh), and adds assertion output checks to tests.

Co-authored-by: Gemini CLI <gemini-cli@example.com>

* chore: remove Phase 7 shadow period comment from dev-lead.yml

* feat: skip PRs with CHANGES_REQUESTED review (+ FORCE_REVIEW and stale-review guards)

* feat: skip PRs with CHANGES_REQUESTED review decision

Adds an early-exit check alongside the existing CI guards so the cascade
skips any PR where a human reviewer has already requested changes.
Reviewing those PRs before the author responds wastes tokens and creates
confusing parallel feedback threads.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix: honor FORCE_REVIEW and skip only stale-free CHANGES_REQUESTED reviews

Two issues addressed from Codex P2 feedback:

1. FORCE_REVIEW bypass: mention-triggered and force_review=true runs now
   bypass the CHANGES_REQUESTED skip, so authors can explicitly request a
   re-review after addressing feedback.

2. Stale review guard: only skip when a CHANGES_REQUESTED review targets
   the current head SHA. On repos without automatic stale-review dismissal,
   reviewDecision stays CHANGES_REQUESTED after new commits — the cascade
   now re-engages when the author has pushed new work.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): resolve context loss, missing checkout, and push logic (#223)

* feat: skip PRs with CHANGES_REQUESTED review decision

Adds an early-exit check alongside the existing CI guards so the cascade
skips any PR where a human reviewer has already requested changes.
Reviewing those PRs before the author responds wastes tokens and creates
confusing parallel feedback threads.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix: honor FORCE_REVIEW and skip only stale-free CHANGES_REQUESTED reviews

Two issues addressed from Codex P2 feedback:

1. FORCE_REVIEW bypass: mention-triggered and force_review=true runs now
   bypass the CHANGES_REQUESTED skip, so authors can explicitly request a
   re-review after addressing feedback.

2. Stale review guard: only skip when a CHANGES_REQUESTED review targets
   the current head SHA. On repos without automatic stale-review dismissal,
   reviewDecision stays CHANGES_REQUESTED after new commits — the cascade
   now re-engages when the author has pushed new work.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): resolve context loss, missing checkout, and push logic

- dev-lead-intent.sh: include actor and body in INTENT_CONTEXT for comments
- dev-lead.yml: parse and pass INTENT_ACTOR and INTENT_COMMENT_BODY to agent
- dev-lead-fix-reviews.sh: perform PR checkout, git push, and post summary comments

* fix(dev-lead): exit with 0 on no-changes in fix-reviews and human-pr

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* test: bypass CI check

* fix(pr-review): remove CI bypass hack

* fix(dev-lead): use heredoc for multiline environment variables (#224)

* fix(dev-lead): use heredoc for multiline environment variables

* fix(dev-lead): ensure all engines are installed and context is consistent

* fix(dev-lead): resolve stale model name in engine fallbacks

* fix(dev-lead): improve fallback reliability and script quality

- engine.sh: resolve stale model names in fallback loop; use -latest Gemini aliases
- dev-lead-fix-reviews.sh: remove unused code and fix PR_URL export

* fix(dev-lead): resolve Gemini model names and shell lint warnings

* security(dev-lead): use random heredoc delimiter to prevent injection

Also updates Gemini models to 3.1 family (pro/flash) for May 2026 compatibility.

* fix(dev-lead): update Gemini models to 2.5 stable family

* fix(dev-lead): use auto model selection for Gemini

* security(dev-lead): harden env var parsing and use high-quota Gemini fallback

* fix(dev-lead): use auto model for Gemini

* fix(dev-lead): detect engine-committed changes and remove commit instructions from prompts

commit_and_push only checked git diff (uncommitted changes), so when the engine
followed prompt instructions to run `git commit` via Bash, the working tree was
clean and commit_and_push concluded no-changes — dropping the engine's commits
when the ephemeral runner exited.

Fix 1 (commit_and_push): also detect unpushed commits via `git log @{u}..HEAD`.
If the engine already committed, skip the add+commit step and go straight to push.

Fix 2 (prompts): remove "Commit the changes with..." steps from human.md,
human-pr.md, fix-bot-comment.md, and fix-reviews.md. Replace with an explicit
"Do not commit or push" constraint so the engine leaves git operations to the
script in all cases.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): address PR review findings — untracked fil…
don-petry added a commit that referenced this pull request Aug 7, 2026
… trigger for the Class-2 timer changes, before narrowing them (#1420)

* add: workflow to fix stuck PRs using bot account token

Allows running the cleanup script via workflow_dispatch with access to
GH_PAT (bot account token) from repo secrets.

* fix: pass GH_PAT to checkout action so workflow uses bot account

* debug: simplify account check, add authentication debug output

* docs: add comprehensive petry-review-bot setup instructions

* docs: add GitHub App setup guide (recommended approach)

GitHub App is the secure, recommended way to automate PR reviews:
- Fine-grained permissions
- JWT tokens that auto-expire
- No human account needed
- Better audit trail
- GitHub's recommended approach

Replaces the bot user account approach with a more secure alternative.

* docs: add comprehensive GitHub App setup documentation

- SETUP.md: Quick reference guide with configuration and troubleshooting
- IMPLEMENTATION.md: Technical deep dive on architecture and design decisions
- DOCUMENTATION.md: Index of all documentation files
- Updated README.md with status and quick links
- Updated GITHUB_APP_SETUP.md with implementation notes and actual app ID
- Workflows use GitHub App token generation instead of static PATs

Covers the transition from bot user account to GitHub App authentication for improved security and maintainability.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: silence auth check in fix-stuck-prs when using GitHub App token

GitHub App tokens don't have user scope, so 'gh api user' returns 403.
This is fine - the script still works for PR operations. Suppress the error
so the script completes successfully with app-token fallback label.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: use explicit author instead of @me in stuck PR search

GitHub App tokens don't have user identity, so @me search doesn't work.
Use explicit 'don-petry' author instead to find PRs to fix.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: avoid subshell in while loop to preserve variable state

Using pipe in while loop created subshell where PROBLEM_PRS and FIXED_PRS
counters were incremented but changes didn't persist to parent shell.
Fixed by using process substitution for input instead of pipe.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* docs: add comprehensive status report for GitHub App setup

- Overview of completed work
- GitHub App authentication details
- 24 stuck PRs successfully fixed with approval reviews
- Architectural decisions and rationale
- Configuration guide and usage examples
- Known limitations and troubleshooting

System is now fully operational with GitHub App token authentication,
comprehensive documentation, and all infrastructure in place.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* docs: document stuck PR cleanup status and script fixes

Added documentation for:
- GitHub App token compatibility fixes in fix-stuck-prs.sh
  * Changed @me to explicit author (app tokens have no identity)
  * Fixed subshell variable scope (process substitution vs pipe)
  * Silenced expected 403 auth check error
- Current status: 24 PRs have approvals but remain OPEN
  * Auto-merge failed due to missing GitHub App permission
  * Approvals satisfy branch protection requirement
  * Next: expand permissions or manually merge

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: update list-prs.sh to search all repos by owner instead of @me

GitHub App tokens don't have user identity, so @me/@review-requested searches
fail. Changed to enumerate all repos in don-petry and petry-projects, then
search for open PRs within each repo. This covers the full scope:
- All open PRs in personal don-petry repos
- All open PRs in petry-projects org repos

Resolves 6 consecutive workflow failures due to zero PRs being enumerated.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* docs: add investigation report for PR review agent workflow failures

* fix: move env section before steps in workflow to fix YAML structure

GitHub Actions requires env section to come before steps in job definition.
Moving env definition up and setting GH_TOKEN only in steps that need it.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* script: add backfill-approvals to retroactively apply real approvals

Converts PRs with agent approval comments (but no real GitHub approvals)
to have actual APPROVED reviews. Needed for PRs reviewed before the
gh pr review --approve fix was applied.

Usage:
  scripts/backfill-approvals.sh          # dry-run (preview changes)
  scripts/backfill-approvals.sh false    # apply real approvals

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* feat: add backfill-approvals workflow to run as GitHub App bot

Runs backfill-approvals.sh via GitHub Actions so the approvals are posted
by the bot identity, not the PR author — GitHub rejects self-approvals.

Also fixes subshell counter bug in the script (piped while loop lost variable
state; switched to process substitution).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: convert inner while loop to process substitution to preserve counters

Both the outer repo loop and inner PR loop were piped subshells, causing
all counter increments (approved/skipped/failed) to be lost. The summary
always showed 0. Fixed by using < <(...) process substitution for both loops.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* car-hunt: add VIN-deep-dive flow, printable checklists, location-tier ranking

- Step 5.5 pre-test-drive flow: NHTSA VIN decode + per-VIN unrepaired-recall
  automation via Chrome MCP (Honda LWC owner portal documented as not
  automatable; NHTSA web form works).
- §F printable test-drive checklist generator (reportlab/Platypus PDF) with
  cold-start, model-specific red flags, negotiation table, walk-away rules.
- §G fraud / paperwork verification checklist (vehicle-agnostic, reusable):
  walk-away triggers, VIN three-location match, curbstoning, odometer fraud,
  bill of sale, title transfer, payment protection, stolen-vehicle, title-
  jumping, after-purchase steps.
- Step 5.25 head-to-head comparison template with information-asymmetry rule.
- Step 3 Location Quality Tier scoring (Birmingham AL metro table A/B/C/D)
  with adj-CPM modifier; sheet schema gains Adj.CPM + Tier columns.
- Hard-disqualification rules added: non-running engine keywords, mileage
  inconsistency, mandatory description scrape.
- Drive MCP overwrite limitation documented; sheet ID now read from memory
  rather than hardcoded.
- FB Marketplace operational facts: Birmingham AL city ID 107739635926718,
  /search? vs /vehicles? query handling, React-controlled inline composer,
  send-button selector, seller-name extraction regex.
- scheduled-tasks/used-car-search-{morning,afternoon}: tier A/B priority
  flag, sheet ID read from memory.
- Reference PDF generator scripts checked in for reuse.

* fix: re-approve PRs where approval predates last commit (stale approval)

Instead of skipping PRs that already have any APPROVED review, skip only
those where reviewDecision is not REVIEW_REQUIRED. This handles PRs where
a bot approval was posted before a new commit was pushed — GitHub's ruleset
engine treats those approvals as stale even with dismiss_stale_reviews=false.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: robustly extract JSON verdict from mixed claude --print output (#17)

Claude's --print mode can prefix the JSON verdict with conversational
preamble text, causing jq to fail with parse errors. Add
extract_verdict_json to engine.sh and wire all three cascade action
call sites in review-one-pr.sh to use it.

Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: extract_verdict_json — check dest file first (agent Bash-write style)

The cascade-action agent writes its verdict JSON to \$OUTPUT_FILE via a
Bash tool call, then prints a text summary to stdout. The previous fix
only scanned stdout (the .raw file), which contained no JSON. The agent-
written file at \$dest (= \$OUTPUT_FILE) was already correct — just not
checked. Now check \$dest first before falling back to stdout scan.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Enforce MAX_REVIEW_CYCLES cap before running the cascade (#18)

* fix: enforce MAX_REVIEW_CYCLES cap with human-escalation marker

Previously MAX_REVIEW_CYCLES only gated AI delegation in
post-pr-review.sh — the cascade itself ran on every cron tick
regardless of how many cycles had accumulated. Real-world result:
a PR could rack up 9+ review cycles (we observed exactly that on
ContentTwin#100) before any cap took effect.

Add a pre-cascade check in review-one-pr.sh: when the count of
existing `<!-- pr-review-agent v1 sha=... -->` markers is at or
above MAX_REVIEW_CYCLES (default 3), post one escalation comment
marked `<!-- pr-review-agent escalation -->`, label
needs-human-review, request don-petry, and exit 100 (skip
sentinel — doesn't burn the MAX_PRS budget). The escalation
marker doubles as the no-spam guard: subsequent runs detect it
and exit 100 immediately.

Also reuse a single `gh pr view` for both the cycle count and the
escalation-marker check.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* review feedback: surface escalation-comment failures, drop dead unset

- Drop `2>/dev/null || true` on `gh pr comment` so a failed escalation
  post is visible in the workflow log instead of silently leaving the
  PR with no marker (which would re-trigger the cap path next tick).
- Reword the cycle-count comment from "AI delegation loops" to "review
  loops" — the cap also catches cascade-only loops where every cycle
  approves and a new commit lands before merge.
- Remove the redundant `unset PR_BODIES` — never exported, no leakage.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>

* Reliability hardening: session circuit breaker, timeouts, retry, dedup (#20)

* feat: session circuit breaker, per-tier timeouts, retry, and triage hard-fail

Reliability hardening for the PR review agent.

1. Session circuit breaker (.github/workflows/pr-review.yml): on any
   non-zero, non-100 exit from review-one-pr.sh (general failure or rate
   limit on the fallback engine), break the per-PR loop, log a clear
   error annotation naming the failing PR and reason, and exit the step
   with code 1 so the run shows red. Prevents one systemic problem from
   silently burning the entire candidate pool.

2. Per-tier timeouts (scripts/engine.sh): triage/deep/audit/action/duck
   each get their own bounded timeout (180/600/600/300/300s defaults,
   env-overridable). Previously only the duck had a timeout — a hung
   tier could burn the whole 60min job budget.

3. Retry-with-backoff on transient errors (scripts/engine.sh): triage
   retries once on 124/137/143 (timeout / signal kill) since its caller
   captures stdout via $(...) so retries are safe. Deliberately NOT
   applied to run_agentic/run_duck where stdout is redirected to a file
   — a retry there would corrupt the partial first-attempt output.

4. Triage non-JSON now hard-fails (scripts/review-one-pr.sh): replaces
   the silent fallback that synthesized a fake "escalate=MEDIUM" verdict
   and proceeded to deep review. With the new circuit breaker, loud
   failure is the right call — masking a broken triage was burning
   tokens on every PR while the workflow looked healthy.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix: stop stacking duplicate agent reviews on the same PR

Two bugs were causing the agent to leave multiple comments on the same PR.
Together they produced 10 stacked APPROVED reviews on petry-projects/ContentTwin#100.

Bug A — idempotency check is order-dependent (review-one-pr.sh):
The previous marker-discovery code did:
  ((.reviews // []) + (.comments // [])) | .[].body | grep marker | tail -1
This relies on the array concatenation order, not chronological order. When
old agent comments existed alongside newer agent reviews, tail -1 picked
the comment-array marker (older) over the review-array marker (newer),
causing the script to think the head SHA hadn't been reviewed and re-run.
Replaced with a single jq pipeline that tags each item with submittedAt /
createdAt, sorts by timestamp, and takes the actual most-recent marker.

Bug B — no cleanup of prior agent items (post-pr-review.sh):
After successfully posting a new review/comment, prior agent items were
left in place, accumulating forever. Added mark_prior_agent_items_obsolete
which, after a successful post:
  - dismisses prior APPROVED/COMMENTED/CHANGES_REQUESTED agent reviews via
    the GitHub dismissal API (UI shows them struck-through as Dismissed)
  - edits prior agent comments to wrap their body in a collapsed <details>
    block with a "Superseded by re-review at <SHA>" summary, plus a
    `<!-- pr-review-agent superseded -->` sentinel for idempotency

All cleanup API calls are best-effort — failures don't break the workflow.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(cleanup): warn on API failures, preserve global newest, file-stage JSON

Three fixes to mark_prior_agent_items_obsolete from the review of PR #20:

1. ::warning:: annotations on every cleanup API failure (review/comment
   list-fetch, individual review dismissal, individual comment fetch+edit).
   Previously these were silenced with `|| true`, so a permissions change
   on the dismissal endpoint would let duplicates stack indefinitely with
   no signal in the Actions UI. Cleanup is still non-fatal — the new post
   has already landed — but failures are now visible.

2. Preserve the globally-latest agent item across BOTH categories, not the
   newest of each category separately. The earlier code split reviews and
   comments and applied `[:-1]` to each, which left a stale fix-request
   comment in place when the new post was a review (or vice versa). The
   one-off cleanup of ContentTwin#100 hit exactly this case: 12 stacked
   reviews collapsed to 1, but a stale comment from 2026-04-25 (SHA
   cd9132d6) was preserved as "newest comment" even though the latest
   review at SHA 3af8c8ee was newer overall. Now: compute the max
   timestamp across both feeds, exclude items at that timestamp.

3. Stage API responses to disk (`mktemp` + `jq <file>`) instead of routing
   through `--argjson "$var"`. The old approach broke on rare unescaped
   control chars in user-authored comment bodies (jq refused to parse the
   resulting shell-vared JSON). File-based input sidesteps the shell
   pipeline entirely.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>

* Remove car-hunt skill and scheduled tasks (moved to don-petry/don-petry) (#21)

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: apply Copilot fallback hardening and reuse GH_PAT for Copilot auth

- Use secrets.GH_PAT (existing personal account token with Copilot
  subscription) instead of a separate COPILOT_GITHUB_TOKEN secret
- Pin actions/create-github-app-token to SHA (v3.1.1) for supply chain safety
- Add post-install verification for gh-copilot extension with warning on failure
- Guard Copilot fallback path: skip PR gracefully if extension is not installed

Addresses issue #24 workflow fixes (1-3) that could not land via PR #25
due to GitHub App lacking workflows permission.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: switch Copilot engine to gh built-in and fix app-id deprecation

- Remove gh extension install (github/gh-copilot conflicts with built-in alias)
- Replace with gh copilot --version check using COPILOT_GITHUB_TOKEN (GH_PAT)
- Update all engine.sh copilot invocations from bare `copilot` binary to
  `gh copilot suggest --target shell` with GH_TOKEN overridden to GH_PAT
  so the user token (with Copilot subscription) is used, not the App token
- Fix actions/create-github-app-token: app-id → client-id (same secret value,
  just the renamed input in v3.x)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: harden Copilot fallback path and remove stale worktrees (#25)

- gh-copilot install: add --force flag and post-install verification
  warning so silent failures due to built-in alias conflict are visible
  in workflow logs instead of being silently swallowed by `|| true`
- fallback pre-flight check: verify `gh extension list | grep copilot`
  before switching to Copilot engine; if unavailable, skip the PR and
  continue the batch rather than session-aborting and dropping all
  remaining candidates (fixes the 27-PR drop on run #503)
- pin actions/create-github-app-token to SHA for v3.1.1 (Node.js 24
  compatible) to prevent hard break on 2026-06-02 when GitHub forces
  Node.js 24 as default runtime
- remove 6 stale Claude Code worktrees from git tracking; add
  .gitignore entry to prevent future worktrees from being committed
  (eliminates exit-128 warning on every actions/checkout post-job sweep)

Closes #24

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: petry-projects-pr-review-agent[bot] <petry-projects-pr-review-agent[bot]@users.noreply.github.com>

* ci: add pre-flight dedup check to prevent duplicate claude-issue PRs (#26)

Before invoking Claude on a labeled issue, a new shell step queries
for any open PR with a branch matching claude/issue-NNN-* (or a body
containing "Closes #NNN"). If one is found it posts a comment on the
issue pointing to the existing PR and skips the Claude step entirely.
A secondary prompt instruction tells Claude to check first and push to
the existing branch rather than opening a new PR.

Motivated by issue petry-projects/google-app-scripts#171, where the
claude label was re-applied three times and each run created a fresh
PR unaware of the prior attempts.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor: migrate to machine user PAT auth (closes #27) (#28)

* refactor: migrate from GitHub App to machine user PAT auth

Closes #27

GitHub Apps cannot be listed in CODEOWNERS, blocking PRs in repos with
require_code_owner_review: true. Switch all workflows to use a machine
user account's fine-grained PAT (DON_PETRY_BOT_GH_PAT secret), which can
join an org team listed in CODEOWNERS.

Workflows: removed actions/create-github-app-token steps in pr-review,
fix-stuck-prs, backfill-approvals, and daily-pr-review-health. All now
use secrets.DON_PETRY_BOT_GH_PAT directly.

Docs: renamed GITHUB_APP_SETUP.md to MACHINE_USER_SETUP.md with full
rewrite covering account creation, CODEOWNERS config, PAT generation,
and rotation. Updated auth sections in IMPLEMENTATION.md, SETUP.md,
STATUS.md, DOCUMENTATION.md, README.md.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* refactor: switch to org-scoped PAT secret DON_PETRY_BOT_PETRY_PROJECT_PAT

Replace DON_PETRY_BOT_GH_PAT with DON_PETRY_BOT_PETRY_PROJECT_PAT — the
new fine-grained PAT scoped to the petry-projects org (resource owner =
petry-projects). The previous PAT was scoped to the donpetry-bot personal
namespace and had no repository access.

The old DON_PETRY_BOT_GH_PAT secret is retained for any future use against
don-petry's personal repos.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* chore: ignore .claude/scheduled_tasks.lock state file

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>

* refactor: merge approval repair scripts into single automation

- Consolidate backfill-approvals.sh and fix-stuck-prs.sh into repair-pr-approvals.sh
- Iterate all repos in both orgs instead of assuming author
- Verify no existing APPROVED review before posting
- Copy original agent comment as review body
- Enable auto-merge when posting approval if needed
- Single workflow with 30-min timeout

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: add @mention trigger for on-demand PR reviews (#30)

Adds repository_dispatch support so commenting @petry-review-bot on any
PR fires an immediate review without waiting for the hourly schedule.

- pr-review.yml: new `repository_dispatch` trigger (type: pr-review-mention),
  per-PR concurrency group for mention runs, FORCE_REVIEW and DRY_RUN
  env vars that handle both workflow_dispatch and repository_dispatch paths
- review-one-pr.sh: FORCE_REVIEW=true bypasses idempotency so a mention
  always runs a fresh cascade even if the head SHA hasn't changed
- templates/mention-listener.yml: deploy to petry-projects/.github;
  listens for @petry-review-bot, validates commenter trust, posts ack,
  sends repository_dispatch (requires Contents:write, not Actions:write)

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: harden rebase, review dismissal, and health log diagnostics (#40)

Addresses 4 code-actionable recommendations from health check report #33:

- [CRITICAL] Make rebase/branch-update non-fatal — 403/504 during rebase
  emits ::warning:: instead of aborting the batch session (#34)
- [MEDIUM] Add retry with exponential backoff (3 attempts) for transient
  5xx on update-branch API; break immediately on 4xx (#37)
- [MEDIUM] Guard review dismissal with state re-check before calling
  dismiss API, preventing 422s from race conditions (#38)
- [LOW] Surface missing log warnings in health check script (#39)
- Fix: skip auto-merge when branch is still BEHIND after failed rebase

Closes #34, closes #37, closes #38, closes #39

* refactor: migrate to org-wide .github-private convention (#41)

* refactor: parametrize hardcoded identity values for org migration

Replace all hardcoded references to don-petry, petry-review-bot, and
don-petry/pr-review-agent with environment variables that default to
the current values. This allows the agent to be configured for different
orgs/users via repo variables.

Changes:
- Scripts use $REVIEWER_USER, $TARGET_ORG, $BOT_USER, $AGENT_REPO
- Prompts no longer reference specific GitHub usernames
- Workflows use org-level GH_PAT_WORKFLOWS secret instead of
  repo-level DON_PETRY_BOT_PETRY_PROJECT_PAT
- Health check uses context.repo.owner/repo for issue creation
- Variables are set in workflow env block with defaults

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add Copilot custom agent profiles for org-wide use

Create /agents/ directory with three agent profiles:
- pr-reviewer: Multi-tier cascading PR review
- feature-ideator: Feature idea generation and prioritization
- compliance-auditor: Org standards compliance checking

These are Copilot custom agent profiles that become available
org-wide in the .github-private repo convention.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Squashed 'frameworks/bmad-method/' content from commit e36f219c

git-subtree-dir: frameworks/bmad-method
git-subtree-split: e36f219c81b6010d4aae423ba12f49edb5b6e31a

* Squashed 'frameworks/spec-kit/' content from commit 11f49ebf

git-subtree-dir: frameworks/spec-kit
git-subtree-split: 11f49ebfb2f6af55345cb4bd9a7906acd211e56f

* Squashed 'frameworks/gsd/' content from commit 304c1a13

git-subtree-dir: frameworks/gsd
git-subtree-split: 304c1a1302564c45af252bbba4bcc5350e7dac3a

* docs: update README for .github-private org infrastructure role

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* ci: add dependabot-automerge.yml workflow (#73)

Adds the required dependabot-automerge.yml workflow from the org
standard template (petry-projects/.github/standards/workflows/).
This is a thin caller stub that delegates to the org-level reusable
workflow.

Closes #48

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Don Petry <don-petry@users.noreply.github.com>

* fix(pr-review): unblock queue starvation from self-authored PRs (#96) (#97)

* fix(pr-review): unblock queue starvation from self-authored PRs (#96)

A self-authored PR sorted first in the candidate list and triggered
"Can not approve your own pull request" — which the session-fatal
abort treated like an engine error, skipping all 28 remaining PRs
on every run.

- list-prs.sh: filter out PRs authored by REVIEWER_USER at enumeration
- post-pr-review.sh: catch the GraphQL self-approval error and exit 100
  (no-op) instead of 1, so a stray self-PR can no longer abort the batch
- engine.sh: gh copilot renamed --target to --agent; fix the rubber-duck
  invocations so tier-2 cross-engine review works again
- review-one-pr.sh: stop appending a duplicate "0" to REVIEW_CYCLE when
  grep -c finds no markers under set -o pipefail (was breaking the
  cycle-cap integer comparison)

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

* fix(pr-review): authenticate as bot, harden enumeration, address review

- Workflow now runs as AGENT_USER (default don-petry-bot), distinct from
  REVIEWER_USER (the human, don-petry). The self-approval rejection that
  caused #96 came from the agent and the human sharing one identity.
- list-prs.sh filters self-authored PRs against AGENT_USER, and validates
  AGENT_USER against the GitHub username charset before interpolating into
  the jq filter (Copilot review feedback).
- review-one-pr.sh uses printf '%s\n' instead of echo for PR_BODIES, since
  PR body content is user-authored and could begin with -n/-e or contain
  backslash escapes (Copilot review feedback).
- AGENT.md guideline updated to reflect that self-authored PRs are
  intentionally excluded (CodeRabbit review feedback).

Operator note: rotate the GH_PAT_WORKFLOWS secret to a token owned by
don-petry-bot (with read:org added) for the bot-account behavior to take
effect at runtime.

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

* refactor(pr-review): collapse to single BOT_USER, route escalations via CODEOWNERS

Drop AGENT_USER and REVIEWER_USER. The workflow now has one identity:
BOT_USER (default don-petry-bot), which both owns the repos to scan and
gets filtered out as the self-approval blocker. Human escalation no
longer hard-codes a single reviewer; instead, scripts/request-codeowners-
review.sh parses CODEOWNERS in the PR's repo and requests review from
every @user / @org/team mention.

- list-prs.sh: BOT_USER for both gh-repo-list and self-author filter
- review-one-pr.sh, post-pr-review.sh: replace --user "$REVIEWER_USER"
  with the CODEOWNERS helper
- repair-pr-approvals.sh: same gh-repo-list switch
- pr-review.yml: drop AGENT_USER/REVIEWER_USER, set BOT_USER default
  to don-petry-bot
- AGENT.md: updated guideline to describe single-identity model

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

* refactor(pr-review): extract batch loop, hoist env, drop redundant step

Workflow YAML drops from 231 to 111 lines by moving the per-PR review
loop into scripts/review-batch.sh. The script can be syntax-checked and
exercised locally; the workflow now just wires env vars and dispatches.

- Hoist GH_TOKEN, MAX_PRS, CANDIDATE_LIMIT to job-level env (were
  repeated on individual steps).
- Inline `gh auth status` into the install step; remove the standalone
  Verify auth step (its only output was a one-line auth dump).
- Drop the `if: steps.list.outputs.count != '0'` guard and the step's
  `id`/output: review-batch.sh handles empty input as a no-op.
- Collapse the duplicated summary-string branch in the review loop into
  a single template with a conditional fragment.

No behavior change.

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

* perf(pr-review): cache claude-code CLI install across runs

`npm install -g @anthropic-ai/claude-code` ran on every workflow start
(~30s). Switch to a per-user npm prefix (~/.npm-global) and cache that
directory via actions/cache, keyed on CLAUDE_CODE_VERSION + runner OS.

A `command -v claude` guard makes the install a no-op on cache hit, so
the only cost on subsequent runs is the cache restore.

CLAUDE_CODE_VERSION defaults to 'latest' (cache persists until manually
flushed); set the repo variable to pin a specific version for fully
reproducible caching.

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

---------

Co-authored-by: Claude <noreply@anthropic.com>

* chore: rename GH_PAT_WORKFLOWS secret to DON_PETRY_BOT_GH_PAT (#99)

The previous name was generic and didn't tell you which account the PAT
belonged to. The new name makes the binding explicit: this secret is the
PAT owned by don-petry-bot, used as BOT_USER throughout the PR-review
workflows.

Operator follow-up before this can be merged:
- Add a new repo secret DON_PETRY_BOT_GH_PAT containing the bot's PAT
  (with repo, workflow, and read:org scopes).
- After merge, the old GH_PAT_WORKFLOWS secret can be deleted.

Affected workflows:
- pr-review.yml (1 use)
- claude.yml (4 uses, with || github.token fallback)
- daily-pr-review-health.yml (1 use)
- repair-pr-approvals.yml (1 use)

scripts/pr_review_health.sh's error message is updated to point at the
new name as well.

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

Co-authored-by: Claude <noreply@anthropic.com>

* chore: remove frameworks directory (#101)

Co-authored-by: Gemini CLI <gemini-cli@example.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore all erroneously deleted files (items 4-34) (#104)

* revert: restore .github/workflows/daily-pr-review-health.yml

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore .github/workflows/repair-pr-approvals.yml

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore agents/pr-reviewer.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/cascade-action.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/deep-review.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/rubber-duck.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/security-audit.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/shared.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/single-review.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/synthesize-duck.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/synthesize.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/triage.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/engine.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/list-prs.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/post-pr-review.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/pr_review_health.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/repair-pr-approvals.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/request-codeowners-review.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/review-batch.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/review-one-pr.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore templates/mention-listener.yml

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore AGENT.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore BOT_SETUP.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore DOCUMENTATION.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore IMPLEMENTATION.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore MACHINE_USER_SETUP.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore PR_REVIEW_AGENT_FAILURE_REPORT.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore PR_REVIEW_FAILURE_INVESTIGATION.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore SETUP.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore STATUS.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore README.md to pre-change state

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore .github/workflows/daily-pr-review-health.yml

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat: add Gemini Pro support and optimize PR review fallback chain (#102)

* Merge main and resolve conflicts

* Enhance pr-review workflow: add Gemini support and refactor fallback logic

* Address Copilot review comments: tighten regex, fix fallback summary, and align docs

* docs: move agent documentation to docs/ folder and rename files

* docs: align secret names and fix casing in index

* docs: organize pr-review-agent documentation under dedicated folder

* Address final PR comments: tighten regex, add preflight checks, fix bot names, and cleanup docs

---------

Co-authored-by: Gemini CLI <gemini-cli@example.com>

* fix(pr-review): gate rate-limit detection on non-zero exit code

Broad patterns like `plan.*limit` and `claude.*usage` could match content
in a successful triage summary, triggering a false-positive engine fallback.
Guard the check with TRIAGE_RC != 0 so it only fires when the provider
command actually failed.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(claude): sync inlined workflow with org standard (#109)

* fix(claude): sync inlined workflow with org standard

Brings the inlined claude.yml up to parity with
petry-projects/.github/.github/workflows/claude-code-reusable.yml.

Changes:
1. Bot allow list for pull_request_review_comment
   Add coderabbitai[bot], Copilot, copilot-pull-request-reviewer[bot],
   and gemini-code-assist[bot] alongside the existing OWNER/MEMBER/
   COLLABORATOR check. These bots have author_association 'NONE' so
   their review comments were always skipped.

2. check_run trigger + claude-ci-fix job
   Port the CI failure auto-fix feature from the reusable. When a check
   fails on a PR, Claude diagnoses and fixes it automatically.
   Adapted to use DON_PETRY_BOT_GH_PAT (this repo's secret name).

3. claude-code-action SHA bump: v1.0.89 → v1.0.119
   Update both the claude and claude-issue jobs.

4. paths-ignore on pull_request trigger
   Prevents the workflow from firing on PRs that only change
   claude.yml itself, avoiding the Anthropic OIDC validation
   failure (workflow file must match default branch at token exchange).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(claude): guard bot allow list against fork PRs

Same fix as petry-projects/.github PR #238 — add same-repo guard
for bot-triggered pull_request_review_comment runs.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(claude): address ci-fix self-loop and fork PR security

- Replace Claude Code name prefix check with explicit job name list to
  correctly prevent self-loops (check_run names for inlined workflows
  are bare job names, not workflow-prefixed)
- Add fork PR trust gate in Resolve PR number step: verify head repo
  matches target repo before running Claude with privileged credentials

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* ci: replace inlined claude.yml with standard thin-caller stub

The inlined version embedded all three jobs (claude, claude-ci-fix,
claude-issue) directly and threaded DON_PETRY_BOT_GH_PAT through
checkout tokens and github_token, causing the bot's PAT to author
PRs and comments instead of github-actions[bot].

Replace with the standard thin caller that delegates to the org-level
reusable workflow (claude-code-reusable.yml@v1) via secrets: inherit,
which is the correct pattern per:
petry-projects/.github/standards/workflows/claude.yml

* fix: replace gh copilot suggest with GitHub Models REST API (#151)

* fix: replace gh copilot suggest with GitHub Models REST API (#147)

The `gh copilot suggest -p "$(cat <file>)"` invocation failed with
"Invalid command format" because:
1. The `-p` flag is not valid syntax in modern `gh copilot` built-in versions.
2. `gh copilot suggest` is a shell-command suggestion tool; it does not
   accept large PR prompts or return structured JSON responses.
3. Passing a multi-thousand-line prompt via `$(cat ...)` can hit ARG_MAX.

The non-zero exit was then misclassified by the rate-limit detector, which
aborted the entire session and skipped all remaining PRs in the queue.

Fix: replace all three copilot invocations (run_triage, run_agentic,
run_duck) with a new `copilot_chat` helper that calls the GitHub Models
REST API directly via curl. The API is OpenAI-compatible, versioned via
`X-GitHub-Api-Version`, stable against gh CLI version changes, and accepts
arbitrary prompt sizes using python3 for safe JSON encoding.

Additional changes:
- Add `COPILOT_API_MODEL` var (default `openai/o4-mini`) overrideable via env.
- run_agentic/run_duck copilot cases now also write output to $OUTPUT_FILE
  so callers that check that path directly (deep review, audit) find the JSON.
- Rate-limit responses (HTTP 429) are echoed to stdout so the existing
  `is_rate_limited()` detector fires correctly for engine fallback.
- Add pre-flight smoke test in review-batch.sh: tests GitHub Models API
  connectivity with a one-liner prompt before processing any PRs, so auth
  or model errors surface immediately as a clear setup failure.
- Add tests/test_copilot_chat.sh: unit tests for the JSON payload builder
  with edge-case prompts (quotes, newlines, # headings, large diffs, Unicode).

Closes #147

Co-authored-by: Don Petry <don-petry@users.noreply.github.com>

* fix: scope COPILOT_API_MODEL export to copilot engine only

Move the `export COPILOT_API_MODEL` from the common exports block into the
`copilot)` case where the variable is set, so it is not exported as an
empty/unset variable when the engine is `claude` or `gemini`. The
`copilot_chat` fallback `${COPILOT_API_MODEL:-openai/o4-mini}` still works
correctly when `DUCK_ENGINE=copilot` under a non-copilot primary engine.

Co-authored-by: Don Petry <don-petry@users.noreply.github.com>

* fix: address review comments — temp file for payload, fail-fast source, streaming output

- copilot_chat: write JSON body to mktemp file, pass to curl as @file to
  avoid ARG_MAX for large PR diffs (was --data-binary "$body")
- copilot_chat: add :? guard on COPILOT_GITHUB_TOKEN for a clear error
  instead of generic "unbound variable" under set -u
- run_agentic / run_duck copilot paths: stream directly to stdout (and
  tee to OUTPUT_FILE when set) rather than buffering the full response
  into a shell variable, which forced large outputs into memory and
  stripped trailing newlines
- review-batch.sh pre-flight: fail fast if source engine.sh fails
  (was silently ignored with || true)
- review-batch.sh pre-flight: build smoke-test JSON payload via python3
  + temp file instead of shell string interpolation to avoid JSON
  injection if COPILOT_API_MODEL contains special characters
- Clarify that openai/o4-mini is the correct April-2025 o4-generation
  model name, not a typo for o1-mini or gpt-4o-mini

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Don Petry <don-petry@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* feat: prioritise .github/.github-private PRs, oldest-first within tier (#155)

* feat: prioritise .github/.github-private PRs, oldest-first within tier

- list-prs.sh: add createdAt to JSON fetch; emit priority|createdAt|url
  lines (priority 0 for .github/.github-private, 1 for everything else)
- Replace final sort -u with a two-pass sort: deduplicate by URL, then
  sort by priority asc then createdAt asc; strip sort keys with cut
- tests/test_list_prs_sort.sh: 16 tests covering priority classification,
  oldest-first ordering, mixed scenarios and deduplication
- .github/workflows/test.yml: run both unit-test files on every PR / push

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* test: add dedup edge case for same URL with conflicting priorities

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: remove temperature from copilot_chat payload (o4-mini incompatible)

The GitHub Models API rejects temperature=0 for reasoning models like
o4-mini with HTTP 400:
  'Unsupported value: temperature does not support 0 with this model.
   Only the default (1) value is supported.'

This caused the rubber duck (DUCK_ENGINE=copilot / DUCK_MODEL=o4-mini)
to fail with HTTP 400 on every claude-engine run, silently degrading
every review to deep-only with no cross-engine sanity check.

Fix: remove temperature from the copilot_chat JSON payload entirely —
the API defaults to 1, which is the only supported value for o4-mini.

Also:
- Update tests/test_copilot_chat.sh: sync build_payload to match and
  flip Test 9 to assert temperature is ABSENT (not 0).
- Add .github/workflows/test.yml: run unit tests on every PR and push
  to main so regressions are caught before merge.

Reproducer: job/75690347409 — duck failure logged as:
  copilot_chat: HTTP 400 from GitHub Models API
  {"error":{"message":"Unsupported value: 'temperature' does not
   support 0 with this model..."}}

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: address PR review comments

- list-prs.sh: tighten priority regex to /[.]github(-private)?/pull/
  so path-boundary anchor prevents false positives (e.g. foo.github)
- list-prs.sh: replace printf pipeline with here-string to avoid ARG_MAX
  limits on large PR sets
- test.yml: add permissions: contents: read (least-privilege, CodeQL fix)
- test.yml: add timeout-minutes: 5 to prevent hung runs
- test_list_prs_sort.sh: sync sort_entries helper (here-string + || true)
  and classify regex to match updated list-prs.sh exactly

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: don-petry <don@petry.dev>

* fix: restore pr-review.yml content (accidentally emptied in revert)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: restore pr-review.yml (file was accidentally emptied)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore(deps): bump actions/cache from 4 to 5 (#165)

Bumps [actions/cache](https://github.com/actions/cache) from 4 to 5.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): bump petry-projects/.github/.github/workflows/claude-code-reusable.yml (#164)

Bumps [petry-projects/.github/.github/workflows/claude-code-reusable.yml](https://github.com/petry-projects/.github) from 1 to 2.
- [Commits](https://github.com/petry-projects/.github/compare/v1...v2)

---
updated-dependencies:
- dependency-name: petry-projects/.github/.github/workflows/claude-code-reusable.yml
  dependency-version: '2'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* fix(list-prs): search DELEGATION_ORGS and drop --checks success pre-filter

- Add iteration over all orgs in DELEGATION_ORGS (e.g. don-petry) so PRs
  in those orgs enter the candidate pool. Previously only BOT_USER and
  TARGET_ORG were searched.
- Remove --checks success from org repo searches. GitHub excludes PRs from
  repos with no CI configured when this flag is used, silently dropping all
  PRs from repos like .github, .github-private, google-app-scripts, and
  TalkTerm. review-one-pr.sh already enforces CI gating per-PR and treats
  empty statusCheckRollup as passing, so the pre-filter is redundant and
  harmful.
- Add --limit 200 to gh repo list calls to handle org growth beyond gh's
  default 30-repo cap.

* feat: add code-quality ruleset (compliance fix #60) (#86)

Creates the required `code-quality` repository ruleset enforcing required
status checks on the default branch, as mandated by the org standard:
standards/github-settings.md#code-quality--required-checks-ruleset-all-repositories

Required checks:
- SonarCloud (code quality analysis)
- CodeQL (SAST)
- agent-shield / AgentShield (agent security scan)
- dependency-audit / Detect ecosystems (dependency vulnerability scan)

Bypass actors:
- OrganizationAdmin (always) — emergency override
- dependabot-automerge-petry Integration (always) — Dependabot auto-merge

The ruleset was applied directly via GitHub API. This file documents the
configuration as code for auditability and future reapplication.

Closes #60

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Don Petry <don-petry@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* feat(prompts/dev-lead): add human prompt template

* feat(prompts/dev-lead): add human-pr prompt template

* feat(dev-lead): implement dev-lead agent Phases 0-6

* fix: per-PR isolation and single-review retry (closes #132)

- review-batch.sh: non-rate-limit per-PR failures (exit code 1) no
  longer abort the session. SESSION ABORTED EARLY is now reserved for
  the rate-limit-on-fallback-engine case (exit code 2) only. All other
  failures are counted and logged; remaining candidates continue.

- review-one-pr.sh: single-review step retries up to
  SINGLE_REVIEW_MAX_RETRIES (default 2) times with a
  SINGLE_REVIEW_RETRY_DELAY_SEC (default 15s) gap before giving up.
  On exhaustion, the PR is flagged needs-human-review and the script
  exits with code 1, which the updated batch treats as a non-fatal
  per-PR failure. Raw model output and stderr are logged on each failed
  attempt for post-mortem visibility.

Root cause of run #25707852006: claude-opus-4-7 returned a verbose
non-JSON response for PR #129; the old code treated that as fatal and
skipped 35 remaining candidates.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix: rate-limit detection and per-attempt stderr logs in single-review retry

Address inline review comments on PR #133:

- Rate-limit check: after each run_agentic call, inspect both stdout
  (VERDICT_JSON.raw) and stderr (SINGLE_LOG) with is_rate_limited before
  retrying. A rate-limit match exits immediately with code 2 so
  review-batch.sh can trigger engine fallback — consistent with triage and
  deep-review tiers. Previously a rate-limited single-review would burn all
  retries and exit 1 (per-PR failure), silently leaving the batch on the
  same rate-limited engine for all remaining PRs.

- Per-attempt log files: stderr is now written to
  single-review-attempt-N.log rather than a single overwritten file, so
  no earlier-attempt errors are lost. Each attempt logs its own stderr
  inline on failure; the fallback path cats all attempt logs for
  post-mortem visibility.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* feat(dev-lead): Phase 0 test infrastructure and Phase 1 intent stub

Phase 0: full test harness for the dev-lead agent —
  26 event fixtures (all valid JSON with _test_expected_intent),
  stub claude/gemini engines, mock gh binary, CI failure log sample,
  bats helpers (stub-engine, mock-gh, assert-env, prompt-vars),
  7 prompt templates with VARIABLES declarations, preflight script,
  prompt coverage integration test, and test-dev-lead.yml CI workflow.

Phase 1: dev-lead.yml trigger workflow (all 7 event types, dispatch + ci-relay
  jobs) and dev-lead-intent.sh stub (anti-loop guard live; all other events
  emit skip/not-implemented).  14/14 bats unit tests pass.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): add permissions blocks to test workflow jobs (CodeQL)

* fix(ci): auto-fix for lint / eslint [skip ci-relay]

* feat(dev-lead): Phases 2-6 — CI fix, review fix, issue, engine fallback

Phase 2: run_writer/run_writer_with_fallback in engine.sh, full intent
routing in dev-lead-intent.sh, dev-lead-fix-ci.sh handler, workflow wiring.

Phase 3: dev-lead-fix-reviews.sh handles fix-reviews, fix-bot-comment,
human, human-pr, rebase intents. Full review routing in intent classifier.

Phase 5: dev-lead-fix-issue.sh handles the issue intent with dedup guard
and branch/PR creation.

Phase 6: run_writer_with_fallback with claude→gemini→copilot fallback chain.

Tests: 77 unit tests across 8 new .bats files, all passing. Updated Phase 1
stub tests to reflect actual Phase 2+ routing behavior.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): address review comments — dispatch JSON, dead code, portable stub gh

* fix(dev-lead): pre-flight after intent, fork URL check, review-batch exit code, retry comment

* fix(dev-lead): install bats-core from GitHub to avoid root requirement

* fix(security): move event values to env vars to prevent script injection (SonarCloud)

* fix(security): move CLAUDE_CODE_VERSION to env block (SonarCloud script injection)

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* feat(dev-lead): Phase 1.5 — reusable workflow for cross-repo adoption + Phase 7 shadow period

* feat(dev-lead): add reusable workflow (Phase 1.5) and begin shadow period (Phase 7)

- Create .github/workflows/dev-lead-reusable.yml: workflow_call entry
  point for other repos. Checks out .github-private scripts/prompts into
  .dev-lead/, then runs the same intent-classify + handler pipeline as
  dev-lead.yml with PROMPTS_DIR=.dev-lead/prompts/dev-lead.
- Add PROMPTS_DIR env-var support to dev-lead-fix-ci.sh,
  dev-lead-fix-reviews.sh, and dev-lead-fix-issue.sh so the reusable
  workflow can point scripts at the sparse-checkout path without changing
  CWD. Defaults to prompts/dev-lead (backwards-compatible for dev-lead.yml).
- Annotate dev-lead.yml with Phase 7 shadow-period window (2026-05-15
  through ~2026-05-29): claude.yml and dev-lead.yml run in parallel until
  regressions clear, then claude.yml is removed.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* chore(dev-lead): update shadow-period tracking issue ref to #180

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): document PAT requirement and pin ref:main on private checkout

* fix(dev-lead): P1 review fixes — ci-relay in reusable, .dev-lead gitignore, export PROMPTS_DIR, trim header

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): rate-limit detection, separate status, and retry cron (#192) (#196)

* chore(dev-lead): decommission claude.yml, bump ACTION_TIMEOUT_SEC to 600s

- Delete .github/workflows/claude.yml — replaced by dev-lead.yml (shadow
  period complete, tracking issue #180).
- scripts/engine.sh: raise ACTION_TIMEOUT_SEC default 300→600s to reduce
  timeout failures on large-repo fix-ci runs (PR #80 had 3 timeouts).
- AGENTS.md: remove claude.yml immutability exemption; note dev-lead.yml
  as the active AI automation workflow and its edit-via-reusable pattern.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* refactor: replace Claude analysis with pure gh/jq telemetry in health check

* refactor: drop Node/Claude steps from daily-pr-review-health workflow

* rename: daily-pr-review-health → actions-fleet-monitor

* rename: daily-pr-review-health → actions-fleet-monitor

* fix(agents-md): clarify dev-lead.yml vs dev-lead-reusable.yml scope

dev-lead.yml in .github-private runs inline steps (not a caller stub).
Behavior changes for this repo go to dev-lead.yml directly; changes that
affect all org repos via the cross-repo reusable go to dev-lead-reusable.yml.

Addresses Copilot review on PR #194.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): rate-limit detection, separate status, and retry cron

Closes #192. Implements all phases from the revised plan.

**Phase 0 — fix run_writer stdout capture (prerequisite)**
- engine.sh: capture stdout via `tee` to a tempfile so `is_rate_limited`
  can inspect the output; old code read `/tmp/dev-lead-writer-stderr`
  which was never written (claude --print outputs to stdout, not stderr).
  Fallback engines were never tried in practice.
- engine.sh: add `parse_reset_time` to extract ISO timestamp from
  `resets H:MMpm (UTC)` in engine output and write it to
  `/tmp/dev-lead-rate-limit-reset` for callers to embed in markers.

**Phase 1 — separate status=rate-limited from status=failed**
- fix-ci.sh: detect engine exit 2 → post `status=rate-limited` (not
  `status=failed`); embed parsed reset time in marker body; exit 2.
- fix-ci.sh: fix `check_idempotency` to treat `status=rate-limited`
  as retriable — only block on terminal statuses (applied, failed,
  no-changes). Previously, rate-limited markers blocked all retries.
- fix-ci.sh: `count_recent_failures` already filtered to `status=failed`;
  add explicit comment confirming rate-limited markers are excluded.
- fix-ci.sh: add `has_rate_limited_marker` dedup check so the same SHA
  never accumulates more than one rate-limited comment.
- fix-reviews.sh: on engine exit 2 for all five intent types
  (fix-reviews, fix-bot-comment, human, human-pr, rebase), post a
  `status=rate-limited` marker with embedded reset time and exit 2.
- fix-reviews.sh: for `human` and `human-pr` intents, also post a
  user-visible acknowledgment comment so users know their request was
  received and will be retried.

**Phase 2 — scheduled retry cron for fix-ci**
- dev-lead-retry.sh: new script scans all open PRs across TARGET_ORG
  for `status=rate-limited` markers on current HEAD SHA; skips PRs
  whose reset time is still in the future; staggered dispatches (30s
  between repos) to prevent cascading org-level rate-limit hits.
- dev-lead-retry.yml: new workflow — scheduled every 2 hours (dual
  offsets to work around GitHub scheduler skew), plus workflow_dispatch.

**Phase 3 — retry for fix-reviews intents (same cron)**
- dev-lead-retry.sh already handles all five fix-reviews intent types
  (fix-reviews, fix-bot-comment, human, human-pr, rebase) via
  `dev-lead-reviews-retry` dispatch events.
- dev-lead-intent.sh: add `dev-lead-reviews-retry` dispatch type routing
  to the intent classifier — maps intent_type from payload to the correct
  existing fix-reviews step.
- dev-lead.yml: add `dev-lead-reviews-retry` to repository_dispatch types.
- dev-lead.yml: unify concurrency group for all dispatch types to
  `dev-lead-pr-{pr_number}` to avoid per-type slot fragmentation.

**Tests**
- test_engine_writer.bats: 8 new tests covering rate-limit stdout detection,
  exit-2 mapping, fallback exhaustion, reset time parsing.
- test_fix_ci.bats: 5 new tests covering rate-limited status, exhaustion
  exclusion, idempotency pass-through, and dedup.
- test_fix_reviews.bats: 5 new tests covering all intent types + human ack.
- test_intent_ci.bats: 4 new tests for dev-lead-reviews-retry dispatch routing.
- e2e/scenarios/07-rate-limit-retry.sh: new E2E scenario covering all parts.
- 3 new event fixtures for repository_dispatch_reviews_retry_* payloads.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): address review comments on #196

Addresses all 18 review threads from Copilot, Codex, and Gemini.

**P1 bugs fixed:**
- dev-lead-retry.sh: all log echo calls in scan_pr_for_rate_limits and
  dispatch_* functions now write to stderr; only the final count is on
  stdout. Previously, log lines polluted the command substitution,
  causing arithmetic expansion to fail and aborting the scan on the
  first retry candidate.
- dev-lead-retry.yml: permissions: contents was read-only; creating
  repository_dispatch events requires contents: write.

**Correctness fixes:**
- dev-lead-retry.sh: restrict automated retries to intents whose context
  can be reconstructed at runtime (fix-reviews, human-pr, rebase). human
  and fix-bot-comment require USER_INSTRUCTION/COMMENT_BODY from the
  original event which cannot be re-fetched; they are explicitly excluded.
- dev-lead-fix-reviews.sh: resolve HEAD_SHA from the PR API when not
  provided by the triggering event (issue_comment intents carry no SHA).
  Without this, rate-limited markers had no sha= field and were
  invisible to the retry scanner.
- dev-lead-fix-reviews.sh: write terminal status=applied marker after
  successful fix-reviews, human-pr, rebase runs. Prevents the retry
  cron from re-dispatching the same intent on every subsequent tick when
  the SHA hasn't changed.
- dev-lead-retry.sh: check for reviews terminal marker before dispatching
  a retry (mirrors the existing fix-ci terminal-marker check).
- dev-lead-retry.sh: look up current check-run details (details_url, id)
  at dispatch time via the commits check-runs API, so retried fix-ci runs
  have full failure logs and annotations rather than empty fields.
- dev-lead-fix-ci.sh: embed check= field in the rate-limited marker so
  the retry cron knows which check run to look up.
- dev-lead-fix-reviews.sh: human intent now posts a "please re-mention
  @dev-lead" ack instead of "I'll retry automatically" since it will NOT
  be retried automatically. human-pr keeps the auto-retry ack (correct).

**Pagination (Copilot + Gemini):**
- dev-lead-fix-ci.sh: add --paginate to check_idempotency, count_recent_
  failures, has_rate_limited_marker.
- dev-lead-fix-reviews.sh: add --paginate to has_reviews_rate_limited_marker.
- dev-lead-retry.sh: add --paginate to PR comments and PR list fetches.

**Other improvements:**
- dev-lead-retry.yml: simplify to single cron schedule (15 */2 * * *);
  remove BOT_USER env var (unused in the script).
- dev-lead-retry.sh: raise repo list limit 200 → 1000; add hard-error
  when list_repos_for_org returns empty (surfaces token permission issues
  rather than silently scanning 0 repos).
- dev-lead-intent.sh: use jq -nc (compact, null-input) for context JSON
  construction — safe against values with special characters.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(pr-review): handle Gemini trust-mode failure and properly route engine fallbacks (#217)

* fix(pr-review): handle Gemini trust-mode failure and properly route engine fallbacks

Closes #208

* fix(ci): install gemini CLI and set trust mode for dev-lead agents

---------

Co-authored-by: Gemini CLI <gemini-cli@example.com>

* fix: resolve YAML syntax error in dev-lead-reusable workflow

* fix: address automated feedback from PR 217 (#219)

This addresses missed feedback from CodeRabbit (removing syntax errors in YAML) and gemini-code-assist (improving string concatenation in validate-engines.sh), and adds assertion output checks to tests.

Co-authored-by: Gemini CLI <gemini-cli@example.com>

* chore: remove Phase 7 shadow period comment from dev-lead.yml

* feat: skip PRs with CHANGES_REQUESTED review (+ FORCE_REVIEW and stale-review guards)

* feat: skip PRs with CHANGES_REQUESTED review decision

Adds an early-exit check alongside the existing CI guards so the cascade
skips any PR where a human reviewer has already requested changes.
Reviewing those PRs before the author responds wastes tokens and creates
confusing parallel feedback threads.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix: honor FORCE_REVIEW and skip only stale-free CHANGES_REQUESTED reviews

Two issues addressed from Codex P2 feedback:

1. FORCE_REVIEW bypass: mention-triggered and force_review=true runs now
   bypass the CHANGES_REQUESTED skip, so authors can explicitly request a
   re-review after addressing feedback.

2. Stale review guard: only skip when a CHANGES_REQUESTED review targets
   the current head SHA. On repos without automatic stale-review dismissal,
   reviewDecision stays CHANGES_REQUESTED after new commits — the cascade
   now re-engages when the author has pushed new work.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): resolve context loss, missing checkout, and push logic (#223)

* feat: skip PRs with CHANGES_REQUESTED review decision

Adds an early-exit check alongside the existing CI guards so the cascade
skips any PR where a human reviewer has already requested changes.
Reviewing those PRs before the author responds wastes tokens and creates
confusing parallel feedback threads.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix: honor FORCE_REVIEW and skip only stale-free CHANGES_REQUESTED reviews

Two issues addressed from Codex P2 feedback:

1. FORCE_REVIEW bypass: mention-triggered and force_review=true runs now
   bypass the CHANGES_REQUESTED skip, so authors can explicitly request a
   re-review after addressing feedback.

2. Stale review guard: only skip when a CHANGES_REQUESTED review targets
   the current head SHA. On repos without automatic stale-review dismissal,
   reviewDecision stays CHANGES_REQUESTED after new commits — the cascade
   now re-engages when the author has pushed new work.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): resolve context loss, missing checkout, and push logic

- dev-lead-intent.sh: include actor and body in INTENT_CONTEXT for comments
- dev-lead.yml: parse and pass INTENT_ACTOR and INTENT_COMMENT_BODY to agent
- dev-lead-fix-reviews.sh: perform PR checkout, git push, and post summary comments

* fix(dev-lead): exit with 0 on no-changes in fix-reviews and human-pr

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* test: bypass CI check

* fix(pr-review): remove CI bypass hack

* fix(dev-lead): use heredoc for multiline environment variables (#224)

* fix(dev-lead): use heredoc for multiline environment variables

* fix(dev-lead): ensure all engines are installed and context is consistent

* fix(dev-lead): resolve stale model name in engine fallbacks

* fix(dev-lead): improve fallback reliability and script quality

- engine.sh: resolve stale model names in fallback loop; use -latest Gemini aliases
- dev-lead-fix-reviews.sh: remove unused code and fix PR_URL export

* fix(dev-lead): resolve Gemini model names and shell lint warnings

* security(dev-lead): use random heredoc delimiter to prevent inject…
don-petry added a commit that referenced this pull request Aug 7, 2026
… trigger for the Class-2 timer changes, before narrowing them (#1420)

* add: workflow to fix stuck PRs using bot account token

Allows running the cleanup script via workflow_dispatch with access to
GH_PAT (bot account token) from repo secrets.

* fix: pass GH_PAT to checkout action so workflow uses bot account

* debug: simplify account check, add authentication debug output

* docs: add comprehensive petry-review-bot setup instructions

* docs: add GitHub App setup guide (recommended approach)

GitHub App is the secure, recommended way to automate PR reviews:
- Fine-grained permissions
- JWT tokens that auto-expire
- No human account needed
- Better audit trail
- GitHub's recommended approach

Replaces the bot user account approach with a more secure alternative.

* docs: add comprehensive GitHub App setup documentation

- SETUP.md: Quick reference guide with configuration and troubleshooting
- IMPLEMENTATION.md: Technical deep dive on architecture and design decisions
- DOCUMENTATION.md: Index of all documentation files
- Updated README.md with status and quick links
- Updated GITHUB_APP_SETUP.md with implementation notes and actual app ID
- Workflows use GitHub App token generation instead of static PATs

Covers the transition from bot user account to GitHub App authentication for improved security and maintainability.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: silence auth check in fix-stuck-prs when using GitHub App token

GitHub App tokens don't have user scope, so 'gh api user' returns 403.
This is fine - the script still works for PR operations. Suppress the error
so the script completes successfully with app-token fallback label.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: use explicit author instead of @me in stuck PR search

GitHub App tokens don't have user identity, so @me search doesn't work.
Use explicit 'don-petry' author instead to find PRs to fix.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: avoid subshell in while loop to preserve variable state

Using pipe in while loop created subshell where PROBLEM_PRS and FIXED_PRS
counters were incremented but changes didn't persist to parent shell.
Fixed by using process substitution for input instead of pipe.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* docs: add comprehensive status report for GitHub App setup

- Overview of completed work
- GitHub App authentication details
- 24 stuck PRs successfully fixed with approval reviews
- Architectural decisions and rationale
- Configuration guide and usage examples
- Known limitations and troubleshooting

System is now fully operational with GitHub App token authentication,
comprehensive documentation, and all infrastructure in place.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* docs: document stuck PR cleanup status and script fixes

Added documentation for:
- GitHub App token compatibility fixes in fix-stuck-prs.sh
  * Changed @me to explicit author (app tokens have no identity)
  * Fixed subshell variable scope (process substitution vs pipe)
  * Silenced expected 403 auth check error
- Current status: 24 PRs have approvals but remain OPEN
  * Auto-merge failed due to missing GitHub App permission
  * Approvals satisfy branch protection requirement
  * Next: expand permissions or manually merge

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: update list-prs.sh to search all repos by owner instead of @me

GitHub App tokens don't have user identity, so @me/@review-requested searches
fail. Changed to enumerate all repos in don-petry and petry-projects, then
search for open PRs within each repo. This covers the full scope:
- All open PRs in personal don-petry repos
- All open PRs in petry-projects org repos

Resolves 6 consecutive workflow failures due to zero PRs being enumerated.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* docs: add investigation report for PR review agent workflow failures

* fix: move env section before steps in workflow to fix YAML structure

GitHub Actions requires env section to come before steps in job definition.
Moving env definition up and setting GH_TOKEN only in steps that need it.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* script: add backfill-approvals to retroactively apply real approvals

Converts PRs with agent approval comments (but no real GitHub approvals)
to have actual APPROVED reviews. Needed for PRs reviewed before the
gh pr review --approve fix was applied.

Usage:
  scripts/backfill-approvals.sh          # dry-run (preview changes)
  scripts/backfill-approvals.sh false    # apply real approvals

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* feat: add backfill-approvals workflow to run as GitHub App bot

Runs backfill-approvals.sh via GitHub Actions so the approvals are posted
by the bot identity, not the PR author — GitHub rejects self-approvals.

Also fixes subshell counter bug in the script (piped while loop lost variable
state; switched to process substitution).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: convert inner while loop to process substitution to preserve counters

Both the outer repo loop and inner PR loop were piped subshells, causing
all counter increments (approved/skipped/failed) to be lost. The summary
always showed 0. Fixed by using < <(...) process substitution for both loops.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* car-hunt: add VIN-deep-dive flow, printable checklists, location-tier ranking

- Step 5.5 pre-test-drive flow: NHTSA VIN decode + per-VIN unrepaired-recall
  automation via Chrome MCP (Honda LWC owner portal documented as not
  automatable; NHTSA web form works).
- §F printable test-drive checklist generator (reportlab/Platypus PDF) with
  cold-start, model-specific red flags, negotiation table, walk-away rules.
- §G fraud / paperwork verification checklist (vehicle-agnostic, reusable):
  walk-away triggers, VIN three-location match, curbstoning, odometer fraud,
  bill of sale, title transfer, payment protection, stolen-vehicle, title-
  jumping, after-purchase steps.
- Step 5.25 head-to-head comparison template with information-asymmetry rule.
- Step 3 Location Quality Tier scoring (Birmingham AL metro table A/B/C/D)
  with adj-CPM modifier; sheet schema gains Adj.CPM + Tier columns.
- Hard-disqualification rules added: non-running engine keywords, mileage
  inconsistency, mandatory description scrape.
- Drive MCP overwrite limitation documented; sheet ID now read from memory
  rather than hardcoded.
- FB Marketplace operational facts: Birmingham AL city ID 107739635926718,
  /search? vs /vehicles? query handling, React-controlled inline composer,
  send-button selector, seller-name extraction regex.
- scheduled-tasks/used-car-search-{morning,afternoon}: tier A/B priority
  flag, sheet ID read from memory.
- Reference PDF generator scripts checked in for reuse.

* fix: re-approve PRs where approval predates last commit (stale approval)

Instead of skipping PRs that already have any APPROVED review, skip only
those where reviewDecision is not REVIEW_REQUIRED. This handles PRs where
a bot approval was posted before a new commit was pushed — GitHub's ruleset
engine treats those approvals as stale even with dismiss_stale_reviews=false.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: robustly extract JSON verdict from mixed claude --print output (#17)

Claude's --print mode can prefix the JSON verdict with conversational
preamble text, causing jq to fail with parse errors. Add
extract_verdict_json to engine.sh and wire all three cascade action
call sites in review-one-pr.sh to use it.

Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: extract_verdict_json — check dest file first (agent Bash-write style)

The cascade-action agent writes its verdict JSON to \$OUTPUT_FILE via a
Bash tool call, then prints a text summary to stdout. The previous fix
only scanned stdout (the .raw file), which contained no JSON. The agent-
written file at \$dest (= \$OUTPUT_FILE) was already correct — just not
checked. Now check \$dest first before falling back to stdout scan.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Enforce MAX_REVIEW_CYCLES cap before running the cascade (#18)

* fix: enforce MAX_REVIEW_CYCLES cap with human-escalation marker

Previously MAX_REVIEW_CYCLES only gated AI delegation in
post-pr-review.sh — the cascade itself ran on every cron tick
regardless of how many cycles had accumulated. Real-world result:
a PR could rack up 9+ review cycles (we observed exactly that on
ContentTwin#100) before any cap took effect.

Add a pre-cascade check in review-one-pr.sh: when the count of
existing `<!-- pr-review-agent v1 sha=... -->` markers is at or
above MAX_REVIEW_CYCLES (default 3), post one escalation comment
marked `<!-- pr-review-agent escalation -->`, label
needs-human-review, request don-petry, and exit 100 (skip
sentinel — doesn't burn the MAX_PRS budget). The escalation
marker doubles as the no-spam guard: subsequent runs detect it
and exit 100 immediately.

Also reuse a single `gh pr view` for both the cycle count and the
escalation-marker check.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* review feedback: surface escalation-comment failures, drop dead unset

- Drop `2>/dev/null || true` on `gh pr comment` so a failed escalation
  post is visible in the workflow log instead of silently leaving the
  PR with no marker (which would re-trigger the cap path next tick).
- Reword the cycle-count comment from "AI delegation loops" to "review
  loops" — the cap also catches cascade-only loops where every cycle
  approves and a new commit lands before merge.
- Remove the redundant `unset PR_BODIES` — never exported, no leakage.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>

* Reliability hardening: session circuit breaker, timeouts, retry, dedup (#20)

* feat: session circuit breaker, per-tier timeouts, retry, and triage hard-fail

Reliability hardening for the PR review agent.

1. Session circuit breaker (.github/workflows/pr-review.yml): on any
   non-zero, non-100 exit from review-one-pr.sh (general failure or rate
   limit on the fallback engine), break the per-PR loop, log a clear
   error annotation naming the failing PR and reason, and exit the step
   with code 1 so the run shows red. Prevents one systemic problem from
   silently burning the entire candidate pool.

2. Per-tier timeouts (scripts/engine.sh): triage/deep/audit/action/duck
   each get their own bounded timeout (180/600/600/300/300s defaults,
   env-overridable). Previously only the duck had a timeout — a hung
   tier could burn the whole 60min job budget.

3. Retry-with-backoff on transient errors (scripts/engine.sh): triage
   retries once on 124/137/143 (timeout / signal kill) since its caller
   captures stdout via $(...) so retries are safe. Deliberately NOT
   applied to run_agentic/run_duck where stdout is redirected to a file
   — a retry there would corrupt the partial first-attempt output.

4. Triage non-JSON now hard-fails (scripts/review-one-pr.sh): replaces
   the silent fallback that synthesized a fake "escalate=MEDIUM" verdict
   and proceeded to deep review. With the new circuit breaker, loud
   failure is the right call — masking a broken triage was burning
   tokens on every PR while the workflow looked healthy.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix: stop stacking duplicate agent reviews on the same PR

Two bugs were causing the agent to leave multiple comments on the same PR.
Together they produced 10 stacked APPROVED reviews on petry-projects/ContentTwin#100.

Bug A — idempotency check is order-dependent (review-one-pr.sh):
The previous marker-discovery code did:
  ((.reviews // []) + (.comments // [])) | .[].body | grep marker | tail -1
This relies on the array concatenation order, not chronological order. When
old agent comments existed alongside newer agent reviews, tail -1 picked
the comment-array marker (older) over the review-array marker (newer),
causing the script to think the head SHA hadn't been reviewed and re-run.
Replaced with a single jq pipeline that tags each item with submittedAt /
createdAt, sorts by timestamp, and takes the actual most-recent marker.

Bug B — no cleanup of prior agent items (post-pr-review.sh):
After successfully posting a new review/comment, prior agent items were
left in place, accumulating forever. Added mark_prior_agent_items_obsolete
which, after a successful post:
  - dismisses prior APPROVED/COMMENTED/CHANGES_REQUESTED agent reviews via
    the GitHub dismissal API (UI shows them struck-through as Dismissed)
  - edits prior agent comments to wrap their body in a collapsed <details>
    block with a "Superseded by re-review at <SHA>" summary, plus a
    `<!-- pr-review-agent superseded -->` sentinel for idempotency

All cleanup API calls are best-effort — failures don't break the workflow.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(cleanup): warn on API failures, preserve global newest, file-stage JSON

Three fixes to mark_prior_agent_items_obsolete from the review of PR #20:

1. ::warning:: annotations on every cleanup API failure (review/comment
   list-fetch, individual review dismissal, individual comment fetch+edit).
   Previously these were silenced with `|| true`, so a permissions change
   on the dismissal endpoint would let duplicates stack indefinitely with
   no signal in the Actions UI. Cleanup is still non-fatal — the new post
   has already landed — but failures are now visible.

2. Preserve the globally-latest agent item across BOTH categories, not the
   newest of each category separately. The earlier code split reviews and
   comments and applied `[:-1]` to each, which left a stale fix-request
   comment in place when the new post was a review (or vice versa). The
   one-off cleanup of ContentTwin#100 hit exactly this case: 12 stacked
   reviews collapsed to 1, but a stale comment from 2026-04-25 (SHA
   cd9132d6) was preserved as "newest comment" even though the latest
   review at SHA 3af8c8ee was newer overall. Now: compute the max
   timestamp across both feeds, exclude items at that timestamp.

3. Stage API responses to disk (`mktemp` + `jq <file>`) instead of routing
   through `--argjson "$var"`. The old approach broke on rare unescaped
   control chars in user-authored comment bodies (jq refused to parse the
   resulting shell-vared JSON). File-based input sidesteps the shell
   pipeline entirely.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>

* Remove car-hunt skill and scheduled tasks (moved to don-petry/don-petry) (#21)

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: apply Copilot fallback hardening and reuse GH_PAT for Copilot auth

- Use secrets.GH_PAT (existing personal account token with Copilot
  subscription) instead of a separate COPILOT_GITHUB_TOKEN secret
- Pin actions/create-github-app-token to SHA (v3.1.1) for supply chain safety
- Add post-install verification for gh-copilot extension with warning on failure
- Guard Copilot fallback path: skip PR gracefully if extension is not installed

Addresses issue #24 workflow fixes (1-3) that could not land via PR #25
due to GitHub App lacking workflows permission.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: switch Copilot engine to gh built-in and fix app-id deprecation

- Remove gh extension install (github/gh-copilot conflicts with built-in alias)
- Replace with gh copilot --version check using COPILOT_GITHUB_TOKEN (GH_PAT)
- Update all engine.sh copilot invocations from bare `copilot` binary to
  `gh copilot suggest --target shell` with GH_TOKEN overridden to GH_PAT
  so the user token (with Copilot subscription) is used, not the App token
- Fix actions/create-github-app-token: app-id → client-id (same secret value,
  just the renamed input in v3.x)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: harden Copilot fallback path and remove stale worktrees (#25)

- gh-copilot install: add --force flag and post-install verification
  warning so silent failures due to built-in alias conflict are visible
  in workflow logs instead of being silently swallowed by `|| true`
- fallback pre-flight check: verify `gh extension list | grep copilot`
  before switching to Copilot engine; if unavailable, skip the PR and
  continue the batch rather than session-aborting and dropping all
  remaining candidates (fixes the 27-PR drop on run #503)
- pin actions/create-github-app-token to SHA for v3.1.1 (Node.js 24
  compatible) to prevent hard break on 2026-06-02 when GitHub forces
  Node.js 24 as default runtime
- remove 6 stale Claude Code worktrees from git tracking; add
  .gitignore entry to prevent future worktrees from being committed
  (eliminates exit-128 warning on every actions/checkout post-job sweep)

Closes #24

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: petry-projects-pr-review-agent[bot] <petry-projects-pr-review-agent[bot]@users.noreply.github.com>

* ci: add pre-flight dedup check to prevent duplicate claude-issue PRs (#26)

Before invoking Claude on a labeled issue, a new shell step queries
for any open PR with a branch matching claude/issue-NNN-* (or a body
containing "Closes #NNN"). If one is found it posts a comment on the
issue pointing to the existing PR and skips the Claude step entirely.
A secondary prompt instruction tells Claude to check first and push to
the existing branch rather than opening a new PR.

Motivated by issue petry-projects/google-app-scripts#171, where the
claude label was re-applied three times and each run created a fresh
PR unaware of the prior attempts.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor: migrate to machine user PAT auth (closes #27) (#28)

* refactor: migrate from GitHub App to machine user PAT auth

Closes #27

GitHub Apps cannot be listed in CODEOWNERS, blocking PRs in repos with
require_code_owner_review: true. Switch all workflows to use a machine
user account's fine-grained PAT (DON_PETRY_BOT_GH_PAT secret), which can
join an org team listed in CODEOWNERS.

Workflows: removed actions/create-github-app-token steps in pr-review,
fix-stuck-prs, backfill-approvals, and daily-pr-review-health. All now
use secrets.DON_PETRY_BOT_GH_PAT directly.

Docs: renamed GITHUB_APP_SETUP.md to MACHINE_USER_SETUP.md with full
rewrite covering account creation, CODEOWNERS config, PAT generation,
and rotation. Updated auth sections in IMPLEMENTATION.md, SETUP.md,
STATUS.md, DOCUMENTATION.md, README.md.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* refactor: switch to org-scoped PAT secret DON_PETRY_BOT_PETRY_PROJECT_PAT

Replace DON_PETRY_BOT_GH_PAT with DON_PETRY_BOT_PETRY_PROJECT_PAT — the
new fine-grained PAT scoped to the petry-projects org (resource owner =
petry-projects). The previous PAT was scoped to the donpetry-bot personal
namespace and had no repository access.

The old DON_PETRY_BOT_GH_PAT secret is retained for any future use against
don-petry's personal repos.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* chore: ignore .claude/scheduled_tasks.lock state file

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>

* refactor: merge approval repair scripts into single automation

- Consolidate backfill-approvals.sh and fix-stuck-prs.sh into repair-pr-approvals.sh
- Iterate all repos in both orgs instead of assuming author
- Verify no existing APPROVED review before posting
- Copy original agent comment as review body
- Enable auto-merge when posting approval if needed
- Single workflow with 30-min timeout

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: add @mention trigger for on-demand PR reviews (#30)

Adds repository_dispatch support so commenting @petry-review-bot on any
PR fires an immediate review without waiting for the hourly schedule.

- pr-review.yml: new `repository_dispatch` trigger (type: pr-review-mention),
  per-PR concurrency group for mention runs, FORCE_REVIEW and DRY_RUN
  env vars that handle both workflow_dispatch and repository_dispatch paths
- review-one-pr.sh: FORCE_REVIEW=true bypasses idempotency so a mention
  always runs a fresh cascade even if the head SHA hasn't changed
- templates/mention-listener.yml: deploy to petry-projects/.github;
  listens for @petry-review-bot, validates commenter trust, posts ack,
  sends repository_dispatch (requires Contents:write, not Actions:write)

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: harden rebase, review dismissal, and health log diagnostics (#40)

Addresses 4 code-actionable recommendations from health check report #33:

- [CRITICAL] Make rebase/branch-update non-fatal — 403/504 during rebase
  emits ::warning:: instead of aborting the batch session (#34)
- [MEDIUM] Add retry with exponential backoff (3 attempts) for transient
  5xx on update-branch API; break immediately on 4xx (#37)
- [MEDIUM] Guard review dismissal with state re-check before calling
  dismiss API, preventing 422s from race conditions (#38)
- [LOW] Surface missing log warnings in health check script (#39)
- Fix: skip auto-merge when branch is still BEHIND after failed rebase

Closes #34, closes #37, closes #38, closes #39

* refactor: migrate to org-wide .github-private convention (#41)

* refactor: parametrize hardcoded identity values for org migration

Replace all hardcoded references to don-petry, petry-review-bot, and
don-petry/pr-review-agent with environment variables that default to
the current values. This allows the agent to be configured for different
orgs/users via repo variables.

Changes:
- Scripts use $REVIEWER_USER, $TARGET_ORG, $BOT_USER, $AGENT_REPO
- Prompts no longer reference specific GitHub usernames
- Workflows use org-level GH_PAT_WORKFLOWS secret instead of
  repo-level DON_PETRY_BOT_PETRY_PROJECT_PAT
- Health check uses context.repo.owner/repo for issue creation
- Variables are set in workflow env block with defaults

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add Copilot custom agent profiles for org-wide use

Create /agents/ directory with three agent profiles:
- pr-reviewer: Multi-tier cascading PR review
- feature-ideator: Feature idea generation and prioritization
- compliance-auditor: Org standards compliance checking

These are Copilot custom agent profiles that become available
org-wide in the .github-private repo convention.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Squashed 'frameworks/bmad-method/' content from commit e36f219c

git-subtree-dir: frameworks/bmad-method
git-subtree-split: e36f219c81b6010d4aae423ba12f49edb5b6e31a

* Squashed 'frameworks/spec-kit/' content from commit 11f49ebf

git-subtree-dir: frameworks/spec-kit
git-subtree-split: 11f49ebfb2f6af55345cb4bd9a7906acd211e56f

* Squashed 'frameworks/gsd/' content from commit 304c1a13

git-subtree-dir: frameworks/gsd
git-subtree-split: 304c1a1302564c45af252bbba4bcc5350e7dac3a

* docs: update README for .github-private org infrastructure role

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* ci: add dependabot-automerge.yml workflow (#73)

Adds the required dependabot-automerge.yml workflow from the org
standard template (petry-projects/.github/standards/workflows/).
This is a thin caller stub that delegates to the org-level reusable
workflow.

Closes #48

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Don Petry <don-petry@users.noreply.github.com>

* fix(pr-review): unblock queue starvation from self-authored PRs (#96) (#97)

* fix(pr-review): unblock queue starvation from self-authored PRs (#96)

A self-authored PR sorted first in the candidate list and triggered
"Can not approve your own pull request" — which the session-fatal
abort treated like an engine error, skipping all 28 remaining PRs
on every run.

- list-prs.sh: filter out PRs authored by REVIEWER_USER at enumeration
- post-pr-review.sh: catch the GraphQL self-approval error and exit 100
  (no-op) instead of 1, so a stray self-PR can no longer abort the batch
- engine.sh: gh copilot renamed --target to --agent; fix the rubber-duck
  invocations so tier-2 cross-engine review works again
- review-one-pr.sh: stop appending a duplicate "0" to REVIEW_CYCLE when
  grep -c finds no markers under set -o pipefail (was breaking the
  cycle-cap integer comparison)

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

* fix(pr-review): authenticate as bot, harden enumeration, address review

- Workflow now runs as AGENT_USER (default don-petry-bot), distinct from
  REVIEWER_USER (the human, don-petry). The self-approval rejection that
  caused #96 came from the agent and the human sharing one identity.
- list-prs.sh filters self-authored PRs against AGENT_USER, and validates
  AGENT_USER against the GitHub username charset before interpolating into
  the jq filter (Copilot review feedback).
- review-one-pr.sh uses printf '%s\n' instead of echo for PR_BODIES, since
  PR body content is user-authored and could begin with -n/-e or contain
  backslash escapes (Copilot review feedback).
- AGENT.md guideline updated to reflect that self-authored PRs are
  intentionally excluded (CodeRabbit review feedback).

Operator note: rotate the GH_PAT_WORKFLOWS secret to a token owned by
don-petry-bot (with read:org added) for the bot-account behavior to take
effect at runtime.

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

* refactor(pr-review): collapse to single BOT_USER, route escalations via CODEOWNERS

Drop AGENT_USER and REVIEWER_USER. The workflow now has one identity:
BOT_USER (default don-petry-bot), which both owns the repos to scan and
gets filtered out as the self-approval blocker. Human escalation no
longer hard-codes a single reviewer; instead, scripts/request-codeowners-
review.sh parses CODEOWNERS in the PR's repo and requests review from
every @user / @org/team mention.

- list-prs.sh: BOT_USER for both gh-repo-list and self-author filter
- review-one-pr.sh, post-pr-review.sh: replace --user "$REVIEWER_USER"
  with the CODEOWNERS helper
- repair-pr-approvals.sh: same gh-repo-list switch
- pr-review.yml: drop AGENT_USER/REVIEWER_USER, set BOT_USER default
  to don-petry-bot
- AGENT.md: updated guideline to describe single-identity model

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

* refactor(pr-review): extract batch loop, hoist env, drop redundant step

Workflow YAML drops from 231 to 111 lines by moving the per-PR review
loop into scripts/review-batch.sh. The script can be syntax-checked and
exercised locally; the workflow now just wires env vars and dispatches.

- Hoist GH_TOKEN, MAX_PRS, CANDIDATE_LIMIT to job-level env (were
  repeated on individual steps).
- Inline `gh auth status` into the install step; remove the standalone
  Verify auth step (its only output was a one-line auth dump).
- Drop the `if: steps.list.outputs.count != '0'` guard and the step's
  `id`/output: review-batch.sh handles empty input as a no-op.
- Collapse the duplicated summary-string branch in the review loop into
  a single template with a conditional fragment.

No behavior change.

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

* perf(pr-review): cache claude-code CLI install across runs

`npm install -g @anthropic-ai/claude-code` ran on every workflow start
(~30s). Switch to a per-user npm prefix (~/.npm-global) and cache that
directory via actions/cache, keyed on CLAUDE_CODE_VERSION + runner OS.

A `command -v claude` guard makes the install a no-op on cache hit, so
the only cost on subsequent runs is the cache restore.

CLAUDE_CODE_VERSION defaults to 'latest' (cache persists until manually
flushed); set the repo variable to pin a specific version for fully
reproducible caching.

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

---------

Co-authored-by: Claude <noreply@anthropic.com>

* chore: rename GH_PAT_WORKFLOWS secret to DON_PETRY_BOT_GH_PAT (#99)

The previous name was generic and didn't tell you which account the PAT
belonged to. The new name makes the binding explicit: this secret is the
PAT owned by don-petry-bot, used as BOT_USER throughout the PR-review
workflows.

Operator follow-up before this can be merged:
- Add a new repo secret DON_PETRY_BOT_GH_PAT containing the bot's PAT
  (with repo, workflow, and read:org scopes).
- After merge, the old GH_PAT_WORKFLOWS secret can be deleted.

Affected workflows:
- pr-review.yml (1 use)
- claude.yml (4 uses, with || github.token fallback)
- daily-pr-review-health.yml (1 use)
- repair-pr-approvals.yml (1 use)

scripts/pr_review_health.sh's error message is updated to point at the
new name as well.

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

Co-authored-by: Claude <noreply@anthropic.com>

* chore: remove frameworks directory (#101)

Co-authored-by: Gemini CLI <gemini-cli@example.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore all erroneously deleted files (items 4-34) (#104)

* revert: restore .github/workflows/daily-pr-review-health.yml

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore .github/workflows/repair-pr-approvals.yml

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore agents/pr-reviewer.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/cascade-action.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/deep-review.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/rubber-duck.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/security-audit.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/shared.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/single-review.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/synthesize-duck.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/synthesize.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/triage.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/engine.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/list-prs.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/post-pr-review.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/pr_review_health.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/repair-pr-approvals.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/request-codeowners-review.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/review-batch.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/review-one-pr.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore templates/mention-listener.yml

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore AGENT.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore BOT_SETUP.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore DOCUMENTATION.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore IMPLEMENTATION.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore MACHINE_USER_SETUP.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore PR_REVIEW_AGENT_FAILURE_REPORT.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore PR_REVIEW_FAILURE_INVESTIGATION.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore SETUP.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore STATUS.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore README.md to pre-change state

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore .github/workflows/daily-pr-review-health.yml

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat: add Gemini Pro support and optimize PR review fallback chain (#102)

* Merge main and resolve conflicts

* Enhance pr-review workflow: add Gemini support and refactor fallback logic

* Address Copilot review comments: tighten regex, fix fallback summary, and align docs

* docs: move agent documentation to docs/ folder and rename files

* docs: align secret names and fix casing in index

* docs: organize pr-review-agent documentation under dedicated folder

* Address final PR comments: tighten regex, add preflight checks, fix bot names, and cleanup docs

---------

Co-authored-by: Gemini CLI <gemini-cli@example.com>

* fix(pr-review): gate rate-limit detection on non-zero exit code

Broad patterns like `plan.*limit` and `claude.*usage` could match content
in a successful triage summary, triggering a false-positive engine fallback.
Guard the check with TRIAGE_RC != 0 so it only fires when the provider
command actually failed.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(claude): sync inlined workflow with org standard (#109)

* fix(claude): sync inlined workflow with org standard

Brings the inlined claude.yml up to parity with
petry-projects/.github/.github/workflows/claude-code-reusable.yml.

Changes:
1. Bot allow list for pull_request_review_comment
   Add coderabbitai[bot], Copilot, copilot-pull-request-reviewer[bot],
   and gemini-code-assist[bot] alongside the existing OWNER/MEMBER/
   COLLABORATOR check. These bots have author_association 'NONE' so
   their review comments were always skipped.

2. check_run trigger + claude-ci-fix job
   Port the CI failure auto-fix feature from the reusable. When a check
   fails on a PR, Claude diagnoses and fixes it automatically.
   Adapted to use DON_PETRY_BOT_GH_PAT (this repo's secret name).

3. claude-code-action SHA bump: v1.0.89 → v1.0.119
   Update both the claude and claude-issue jobs.

4. paths-ignore on pull_request trigger
   Prevents the workflow from firing on PRs that only change
   claude.yml itself, avoiding the Anthropic OIDC validation
   failure (workflow file must match default branch at token exchange).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(claude): guard bot allow list against fork PRs

Same fix as petry-projects/.github PR #238 — add same-repo guard
for bot-triggered pull_request_review_comment runs.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(claude): address ci-fix self-loop and fork PR security

- Replace Claude Code name prefix check with explicit job name list to
  correctly prevent self-loops (check_run names for inlined workflows
  are bare job names, not workflow-prefixed)
- Add fork PR trust gate in Resolve PR number step: verify head repo
  matches target repo before running Claude with privileged credentials

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* ci: replace inlined claude.yml with standard thin-caller stub

The inlined version embedded all three jobs (claude, claude-ci-fix,
claude-issue) directly and threaded DON_PETRY_BOT_GH_PAT through
checkout tokens and github_token, causing the bot's PAT to author
PRs and comments instead of github-actions[bot].

Replace with the standard thin caller that delegates to the org-level
reusable workflow (claude-code-reusable.yml@v1) via secrets: inherit,
which is the correct pattern per:
petry-projects/.github/standards/workflows/claude.yml

* fix: replace gh copilot suggest with GitHub Models REST API (#151)

* fix: replace gh copilot suggest with GitHub Models REST API (#147)

The `gh copilot suggest -p "$(cat <file>)"` invocation failed with
"Invalid command format" because:
1. The `-p` flag is not valid syntax in modern `gh copilot` built-in versions.
2. `gh copilot suggest` is a shell-command suggestion tool; it does not
   accept large PR prompts or return structured JSON responses.
3. Passing a multi-thousand-line prompt via `$(cat ...)` can hit ARG_MAX.

The non-zero exit was then misclassified by the rate-limit detector, which
aborted the entire session and skipped all remaining PRs in the queue.

Fix: replace all three copilot invocations (run_triage, run_agentic,
run_duck) with a new `copilot_chat` helper that calls the GitHub Models
REST API directly via curl. The API is OpenAI-compatible, versioned via
`X-GitHub-Api-Version`, stable against gh CLI version changes, and accepts
arbitrary prompt sizes using python3 for safe JSON encoding.

Additional changes:
- Add `COPILOT_API_MODEL` var (default `openai/o4-mini`) overrideable via env.
- run_agentic/run_duck copilot cases now also write output to $OUTPUT_FILE
  so callers that check that path directly (deep review, audit) find the JSON.
- Rate-limit responses (HTTP 429) are echoed to stdout so the existing
  `is_rate_limited()` detector fires correctly for engine fallback.
- Add pre-flight smoke test in review-batch.sh: tests GitHub Models API
  connectivity with a one-liner prompt before processing any PRs, so auth
  or model errors surface immediately as a clear setup failure.
- Add tests/test_copilot_chat.sh: unit tests for the JSON payload builder
  with edge-case prompts (quotes, newlines, # headings, large diffs, Unicode).

Closes #147

Co-authored-by: Don Petry <don-petry@users.noreply.github.com>

* fix: scope COPILOT_API_MODEL export to copilot engine only

Move the `export COPILOT_API_MODEL` from the common exports block into the
`copilot)` case where the variable is set, so it is not exported as an
empty/unset variable when the engine is `claude` or `gemini`. The
`copilot_chat` fallback `${COPILOT_API_MODEL:-openai/o4-mini}` still works
correctly when `DUCK_ENGINE=copilot` under a non-copilot primary engine.

Co-authored-by: Don Petry <don-petry@users.noreply.github.com>

* fix: address review comments — temp file for payload, fail-fast source, streaming output

- copilot_chat: write JSON body to mktemp file, pass to curl as @file to
  avoid ARG_MAX for large PR diffs (was --data-binary "$body")
- copilot_chat: add :? guard on COPILOT_GITHUB_TOKEN for a clear error
  instead of generic "unbound variable" under set -u
- run_agentic / run_duck copilot paths: stream directly to stdout (and
  tee to OUTPUT_FILE when set) rather than buffering the full response
  into a shell variable, which forced large outputs into memory and
  stripped trailing newlines
- review-batch.sh pre-flight: fail fast if source engine.sh fails
  (was silently ignored with || true)
- review-batch.sh pre-flight: build smoke-test JSON payload via python3
  + temp file instead of shell string interpolation to avoid JSON
  injection if COPILOT_API_MODEL contains special characters
- Clarify that openai/o4-mini is the correct April-2025 o4-generation
  model name, not a typo for o1-mini or gpt-4o-mini

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Don Petry <don-petry@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* feat: prioritise .github/.github-private PRs, oldest-first within tier (#155)

* feat: prioritise .github/.github-private PRs, oldest-first within tier

- list-prs.sh: add createdAt to JSON fetch; emit priority|createdAt|url
  lines (priority 0 for .github/.github-private, 1 for everything else)
- Replace final sort -u with a two-pass sort: deduplicate by URL, then
  sort by priority asc then createdAt asc; strip sort keys with cut
- tests/test_list_prs_sort.sh: 16 tests covering priority classification,
  oldest-first ordering, mixed scenarios and deduplication
- .github/workflows/test.yml: run both unit-test files on every PR / push

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* test: add dedup edge case for same URL with conflicting priorities

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: remove temperature from copilot_chat payload (o4-mini incompatible)

The GitHub Models API rejects temperature=0 for reasoning models like
o4-mini with HTTP 400:
  'Unsupported value: temperature does not support 0 with this model.
   Only the default (1) value is supported.'

This caused the rubber duck (DUCK_ENGINE=copilot / DUCK_MODEL=o4-mini)
to fail with HTTP 400 on every claude-engine run, silently degrading
every review to deep-only with no cross-engine sanity check.

Fix: remove temperature from the copilot_chat JSON payload entirely —
the API defaults to 1, which is the only supported value for o4-mini.

Also:
- Update tests/test_copilot_chat.sh: sync build_payload to match and
  flip Test 9 to assert temperature is ABSENT (not 0).
- Add .github/workflows/test.yml: run unit tests on every PR and push
  to main so regressions are caught before merge.

Reproducer: job/75690347409 — duck failure logged as:
  copilot_chat: HTTP 400 from GitHub Models API
  {"error":{"message":"Unsupported value: 'temperature' does not
   support 0 with this model..."}}

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: address PR review comments

- list-prs.sh: tighten priority regex to /[.]github(-private)?/pull/
  so path-boundary anchor prevents false positives (e.g. foo.github)
- list-prs.sh: replace printf pipeline with here-string to avoid ARG_MAX
  limits on large PR sets
- test.yml: add permissions: contents: read (least-privilege, CodeQL fix)
- test.yml: add timeout-minutes: 5 to prevent hung runs
- test_list_prs_sort.sh: sync sort_entries helper (here-string + || true)
  and classify regex to match updated list-prs.sh exactly

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: don-petry <don@petry.dev>

* fix: restore pr-review.yml content (accidentally emptied in revert)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: restore pr-review.yml (file was accidentally emptied)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore(deps): bump actions/cache from 4 to 5 (#165)

Bumps [actions/cache](https://github.com/actions/cache) from 4 to 5.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): bump petry-projects/.github/.github/workflows/claude-code-reusable.yml (#164)

Bumps [petry-projects/.github/.github/workflows/claude-code-reusable.yml](https://github.com/petry-projects/.github) from 1 to 2.
- [Commits](https://github.com/petry-projects/.github/compare/v1...v2)

---
updated-dependencies:
- dependency-name: petry-projects/.github/.github/workflows/claude-code-reusable.yml
  dependency-version: '2'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* fix(list-prs): search DELEGATION_ORGS and drop --checks success pre-filter

- Add iteration over all orgs in DELEGATION_ORGS (e.g. don-petry) so PRs
  in those orgs enter the candidate pool. Previously only BOT_USER and
  TARGET_ORG were searched.
- Remove --checks success from org repo searches. GitHub excludes PRs from
  repos with no CI configured when this flag is used, silently dropping all
  PRs from repos like .github, .github-private, google-app-scripts, and
  TalkTerm. review-one-pr.sh already enforces CI gating per-PR and treats
  empty statusCheckRollup as passing, so the pre-filter is redundant and
  harmful.
- Add --limit 200 to gh repo list calls to handle org growth beyond gh's
  default 30-repo cap.

* feat: add code-quality ruleset (compliance fix #60) (#86)

Creates the required `code-quality` repository ruleset enforcing required
status checks on the default branch, as mandated by the org standard:
standards/github-settings.md#code-quality--required-checks-ruleset-all-repositories

Required checks:
- SonarCloud (code quality analysis)
- CodeQL (SAST)
- agent-shield / AgentShield (agent security scan)
- dependency-audit / Detect ecosystems (dependency vulnerability scan)

Bypass actors:
- OrganizationAdmin (always) — emergency override
- dependabot-automerge-petry Integration (always) — Dependabot auto-merge

The ruleset was applied directly via GitHub API. This file documents the
configuration as code for auditability and future reapplication.

Closes #60

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Don Petry <don-petry@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* feat(prompts/dev-lead): add human prompt template

* feat(prompts/dev-lead): add human-pr prompt template

* feat(dev-lead): implement dev-lead agent Phases 0-6

* fix: per-PR isolation and single-review retry (closes #132)

- review-batch.sh: non-rate-limit per-PR failures (exit code 1) no
  longer abort the session. SESSION ABORTED EARLY is now reserved for
  the rate-limit-on-fallback-engine case (exit code 2) only. All other
  failures are counted and logged; remaining candidates continue.

- review-one-pr.sh: single-review step retries up to
  SINGLE_REVIEW_MAX_RETRIES (default 2) times with a
  SINGLE_REVIEW_RETRY_DELAY_SEC (default 15s) gap before giving up.
  On exhaustion, the PR is flagged needs-human-review and the script
  exits with code 1, which the updated batch treats as a non-fatal
  per-PR failure. Raw model output and stderr are logged on each failed
  attempt for post-mortem visibility.

Root cause of run #25707852006: claude-opus-4-7 returned a verbose
non-JSON response for PR #129; the old code treated that as fatal and
skipped 35 remaining candidates.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix: rate-limit detection and per-attempt stderr logs in single-review retry

Address inline review comments on PR #133:

- Rate-limit check: after each run_agentic call, inspect both stdout
  (VERDICT_JSON.raw) and stderr (SINGLE_LOG) with is_rate_limited before
  retrying. A rate-limit match exits immediately with code 2 so
  review-batch.sh can trigger engine fallback — consistent with triage and
  deep-review tiers. Previously a rate-limited single-review would burn all
  retries and exit 1 (per-PR failure), silently leaving the batch on the
  same rate-limited engine for all remaining PRs.

- Per-attempt log files: stderr is now written to
  single-review-attempt-N.log rather than a single overwritten file, so
  no earlier-attempt errors are lost. Each attempt logs its own stderr
  inline on failure; the fallback path cats all attempt logs for
  post-mortem visibility.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* feat(dev-lead): Phase 0 test infrastructure and Phase 1 intent stub

Phase 0: full test harness for the dev-lead agent —
  26 event fixtures (all valid JSON with _test_expected_intent),
  stub claude/gemini engines, mock gh binary, CI failure log sample,
  bats helpers (stub-engine, mock-gh, assert-env, prompt-vars),
  7 prompt templates with VARIABLES declarations, preflight script,
  prompt coverage integration test, and test-dev-lead.yml CI workflow.

Phase 1: dev-lead.yml trigger workflow (all 7 event types, dispatch + ci-relay
  jobs) and dev-lead-intent.sh stub (anti-loop guard live; all other events
  emit skip/not-implemented).  14/14 bats unit tests pass.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): add permissions blocks to test workflow jobs (CodeQL)

* fix(ci): auto-fix for lint / eslint [skip ci-relay]

* feat(dev-lead): Phases 2-6 — CI fix, review fix, issue, engine fallback

Phase 2: run_writer/run_writer_with_fallback in engine.sh, full intent
routing in dev-lead-intent.sh, dev-lead-fix-ci.sh handler, workflow wiring.

Phase 3: dev-lead-fix-reviews.sh handles fix-reviews, fix-bot-comment,
human, human-pr, rebase intents. Full review routing in intent classifier.

Phase 5: dev-lead-fix-issue.sh handles the issue intent with dedup guard
and branch/PR creation.

Phase 6: run_writer_with_fallback with claude→gemini→copilot fallback chain.

Tests: 77 unit tests across 8 new .bats files, all passing. Updated Phase 1
stub tests to reflect actual Phase 2+ routing behavior.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): address review comments — dispatch JSON, dead code, portable stub gh

* fix(dev-lead): pre-flight after intent, fork URL check, review-batch exit code, retry comment

* fix(dev-lead): install bats-core from GitHub to avoid root requirement

* fix(security): move event values to env vars to prevent script injection (SonarCloud)

* fix(security): move CLAUDE_CODE_VERSION to env block (SonarCloud script injection)

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* feat(dev-lead): Phase 1.5 — reusable workflow for cross-repo adoption + Phase 7 shadow period

* feat(dev-lead): add reusable workflow (Phase 1.5) and begin shadow period (Phase 7)

- Create .github/workflows/dev-lead-reusable.yml: workflow_call entry
  point for other repos. Checks out .github-private scripts/prompts into
  .dev-lead/, then runs the same intent-classify + handler pipeline as
  dev-lead.yml with PROMPTS_DIR=.dev-lead/prompts/dev-lead.
- Add PROMPTS_DIR env-var support to dev-lead-fix-ci.sh,
  dev-lead-fix-reviews.sh, and dev-lead-fix-issue.sh so the reusable
  workflow can point scripts at the sparse-checkout path without changing
  CWD. Defaults to prompts/dev-lead (backwards-compatible for dev-lead.yml).
- Annotate dev-lead.yml with Phase 7 shadow-period window (2026-05-15
  through ~2026-05-29): claude.yml and dev-lead.yml run in parallel until
  regressions clear, then claude.yml is removed.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* chore(dev-lead): update shadow-period tracking issue ref to #180

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): document PAT requirement and pin ref:main on private checkout

* fix(dev-lead): P1 review fixes — ci-relay in reusable, .dev-lead gitignore, export PROMPTS_DIR, trim header

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): rate-limit detection, separate status, and retry cron (#192) (#196)

* chore(dev-lead): decommission claude.yml, bump ACTION_TIMEOUT_SEC to 600s

- Delete .github/workflows/claude.yml — replaced by dev-lead.yml (shadow
  period complete, tracking issue #180).
- scripts/engine.sh: raise ACTION_TIMEOUT_SEC default 300→600s to reduce
  timeout failures on large-repo fix-ci runs (PR #80 had 3 timeouts).
- AGENTS.md: remove claude.yml immutability exemption; note dev-lead.yml
  as the active AI automation workflow and its edit-via-reusable pattern.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* refactor: replace Claude analysis with pure gh/jq telemetry in health check

* refactor: drop Node/Claude steps from daily-pr-review-health workflow

* rename: daily-pr-review-health → actions-fleet-monitor

* rename: daily-pr-review-health → actions-fleet-monitor

* fix(agents-md): clarify dev-lead.yml vs dev-lead-reusable.yml scope

dev-lead.yml in .github-private runs inline steps (not a caller stub).
Behavior changes for this repo go to dev-lead.yml directly; changes that
affect all org repos via the cross-repo reusable go to dev-lead-reusable.yml.

Addresses Copilot review on PR #194.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): rate-limit detection, separate status, and retry cron

Closes #192. Implements all phases from the revised plan.

**Phase 0 — fix run_writer stdout capture (prerequisite)**
- engine.sh: capture stdout via `tee` to a tempfile so `is_rate_limited`
  can inspect the output; old code read `/tmp/dev-lead-writer-stderr`
  which was never written (claude --print outputs to stdout, not stderr).
  Fallback engines were never tried in practice.
- engine.sh: add `parse_reset_time` to extract ISO timestamp from
  `resets H:MMpm (UTC)` in engine output and write it to
  `/tmp/dev-lead-rate-limit-reset` for callers to embed in markers.

**Phase 1 — separate status=rate-limited from status=failed**
- fix-ci.sh: detect engine exit 2 → post `status=rate-limited` (not
  `status=failed`); embed parsed reset time in marker body; exit 2.
- fix-ci.sh: fix `check_idempotency` to treat `status=rate-limited`
  as retriable — only block on terminal statuses (applied, failed,
  no-changes). Previously, rate-limited markers blocked all retries.
- fix-ci.sh: `count_recent_failures` already filtered to `status=failed`;
  add explicit comment confirming rate-limited markers are excluded.
- fix-ci.sh: add `has_rate_limited_marker` dedup check so the same SHA
  never accumulates more than one rate-limited comment.
- fix-reviews.sh: on engine exit 2 for all five intent types
  (fix-reviews, fix-bot-comment, human, human-pr, rebase), post a
  `status=rate-limited` marker with embedded reset time and exit 2.
- fix-reviews.sh: for `human` and `human-pr` intents, also post a
  user-visible acknowledgment comment so users know their request was
  received and will be retried.

**Phase 2 — scheduled retry cron for fix-ci**
- dev-lead-retry.sh: new script scans all open PRs across TARGET_ORG
  for `status=rate-limited` markers on current HEAD SHA; skips PRs
  whose reset time is still in the future; staggered dispatches (30s
  between repos) to prevent cascading org-level rate-limit hits.
- dev-lead-retry.yml: new workflow — scheduled every 2 hours (dual
  offsets to work around GitHub scheduler skew), plus workflow_dispatch.

**Phase 3 — retry for fix-reviews intents (same cron)**
- dev-lead-retry.sh already handles all five fix-reviews intent types
  (fix-reviews, fix-bot-comment, human, human-pr, rebase) via
  `dev-lead-reviews-retry` dispatch events.
- dev-lead-intent.sh: add `dev-lead-reviews-retry` dispatch type routing
  to the intent classifier — maps intent_type from payload to the correct
  existing fix-reviews step.
- dev-lead.yml: add `dev-lead-reviews-retry` to repository_dispatch types.
- dev-lead.yml: unify concurrency group for all dispatch types to
  `dev-lead-pr-{pr_number}` to avoid per-type slot fragmentation.

**Tests**
- test_engine_writer.bats: 8 new tests covering rate-limit stdout detection,
  exit-2 mapping, fallback exhaustion, reset time parsing.
- test_fix_ci.bats: 5 new tests covering rate-limited status, exhaustion
  exclusion, idempotency pass-through, and dedup.
- test_fix_reviews.bats: 5 new tests covering all intent types + human ack.
- test_intent_ci.bats: 4 new tests for dev-lead-reviews-retry dispatch routing.
- e2e/scenarios/07-rate-limit-retry.sh: new E2E scenario covering all parts.
- 3 new event fixtures for repository_dispatch_reviews_retry_* payloads.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): address review comments on #196

Addresses all 18 review threads from Copilot, Codex, and Gemini.

**P1 bugs fixed:**
- dev-lead-retry.sh: all log echo calls in scan_pr_for_rate_limits and
  dispatch_* functions now write to stderr; only the final count is on
  stdout. Previously, log lines polluted the command substitution,
  causing arithmetic expansion to fail and aborting the scan on the
  first retry candidate.
- dev-lead-retry.yml: permissions: contents was read-only; creating
  repository_dispatch events requires contents: write.

**Correctness fixes:**
- dev-lead-retry.sh: restrict automated retries to intents whose context
  can be reconstructed at runtime (fix-reviews, human-pr, rebase). human
  and fix-bot-comment require USER_INSTRUCTION/COMMENT_BODY from the
  original event which cannot be re-fetched; they are explicitly excluded.
- dev-lead-fix-reviews.sh: resolve HEAD_SHA from the PR API when not
  provided by the triggering event (issue_comment intents carry no SHA).
  Without this, rate-limited markers had no sha= field and were
  invisible to the retry scanner.
- dev-lead-fix-reviews.sh: write terminal status=applied marker after
  successful fix-reviews, human-pr, rebase runs. Prevents the retry
  cron from re-dispatching the same intent on every subsequent tick when
  the SHA hasn't changed.
- dev-lead-retry.sh: check for reviews terminal marker before dispatching
  a retry (mirrors the existing fix-ci terminal-marker check).
- dev-lead-retry.sh: look up current check-run details (details_url, id)
  at dispatch time via the commits check-runs API, so retried fix-ci runs
  have full failure logs and annotations rather than empty fields.
- dev-lead-fix-ci.sh: embed check= field in the rate-limited marker so
  the retry cron knows which check run to look up.
- dev-lead-fix-reviews.sh: human intent now posts a "please re-mention
  @dev-lead" ack instead of "I'll retry automatically" since it will NOT
  be retried automatically. human-pr keeps the auto-retry ack (correct).

**Pagination (Copilot + Gemini):**
- dev-lead-fix-ci.sh: add --paginate to check_idempotency, count_recent_
  failures, has_rate_limited_marker.
- dev-lead-fix-reviews.sh: add --paginate to has_reviews_rate_limited_marker.
- dev-lead-retry.sh: add --paginate to PR comments and PR list fetches.

**Other improvements:**
- dev-lead-retry.yml: simplify to single cron schedule (15 */2 * * *);
  remove BOT_USER env var (unused in the script).
- dev-lead-retry.sh: raise repo list limit 200 → 1000; add hard-error
  when list_repos_for_org returns empty (surfaces token permission issues
  rather than silently scanning 0 repos).
- dev-lead-intent.sh: use jq -nc (compact, null-input) for context JSON
  construction — safe against values with special characters.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(pr-review): handle Gemini trust-mode failure and properly route engine fallbacks (#217)

* fix(pr-review): handle Gemini trust-mode failure and properly route engine fallbacks

Closes #208

* fix(ci): install gemini CLI and set trust mode for dev-lead agents

---------

Co-authored-by: Gemini CLI <gemini-cli@example.com>

* fix: resolve YAML syntax error in dev-lead-reusable workflow

* fix: address automated feedback from PR 217 (#219)

This addresses missed feedback from CodeRabbit (removing syntax errors in YAML) and gemini-code-assist (improving string concatenation in validate-engines.sh), and adds assertion output checks to tests.

Co-authored-by: Gemini CLI <gemini-cli@example.com>

* chore: remove Phase 7 shadow period comment from dev-lead.yml

* feat: skip PRs with CHANGES_REQUESTED review (+ FORCE_REVIEW and stale-review guards)

* feat: skip PRs with CHANGES_REQUESTED review decision

Adds an early-exit check alongside the existing CI guards so the cascade
skips any PR where a human reviewer has already requested changes.
Reviewing those PRs before the author responds wastes tokens and creates
confusing parallel feedback threads.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix: honor FORCE_REVIEW and skip only stale-free CHANGES_REQUESTED reviews

Two issues addressed from Codex P2 feedback:

1. FORCE_REVIEW bypass: mention-triggered and force_review=true runs now
   bypass the CHANGES_REQUESTED skip, so authors can explicitly request a
   re-review after addressing feedback.

2. Stale review guard: only skip when a CHANGES_REQUESTED review targets
   the current head SHA. On repos without automatic stale-review dismissal,
   reviewDecision stays CHANGES_REQUESTED after new commits — the cascade
   now re-engages when the author has pushed new work.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): resolve context loss, missing checkout, and push logic (#223)

* feat: skip PRs with CHANGES_REQUESTED review decision

Adds an early-exit check alongside the existing CI guards so the cascade
skips any PR where a human reviewer has already requested changes.
Reviewing those PRs before the author responds wastes tokens and creates
confusing parallel feedback threads.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix: honor FORCE_REVIEW and skip only stale-free CHANGES_REQUESTED reviews

Two issues addressed from Codex P2 feedback:

1. FORCE_REVIEW bypass: mention-triggered and force_review=true runs now
   bypass the CHANGES_REQUESTED skip, so authors can explicitly request a
   re-review after addressing feedback.

2. Stale review guard: only skip when a CHANGES_REQUESTED review targets
   the current head SHA. On repos without automatic stale-review dismissal,
   reviewDecision stays CHANGES_REQUESTED after new commits — the cascade
   now re-engages when the author has pushed new work.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): resolve context loss, missing checkout, and push logic

- dev-lead-intent.sh: include actor and body in INTENT_CONTEXT for comments
- dev-lead.yml: parse and pass INTENT_ACTOR and INTENT_COMMENT_BODY to agent
- dev-lead-fix-reviews.sh: perform PR checkout, git push, and post summary comments

* fix(dev-lead): exit with 0 on no-changes in fix-reviews and human-pr

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* test: bypass CI check

* fix(pr-review): remove CI bypass hack

* fix(dev-lead): use heredoc for multiline environment variables (#224)

* fix(dev-lead): use heredoc for multiline environment variables

* fix(dev-lead): ensure all engines are installed and context is consistent

* fix(dev-lead): resolve stale model name in engine fallbacks

* fix(dev-lead): improve fallback reliability and script quality

- engine.sh: resolve stale model names in fallback loop; use -latest Gemini aliases
- dev-lead-fix-reviews.sh: remove unused code and fix PR_URL export

* fix(dev-lead): resolve Gemini model names and shell lint warnings

* security(dev-lead): use random heredoc delimiter to prevent inject…
don-petry added a commit that referenced this pull request Aug 7, 2026
…-stale-bot-reviews.yml (#1292)

* fix: silence auth check in fix-stuck-prs when using GitHub App token

GitHub App tokens don't have user scope, so 'gh api user' returns 403.
This is fine - the script still works for PR operations. Suppress the error
so the script completes successfully with app-token fallback label.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: use explicit author instead of @me in stuck PR search

GitHub App tokens don't have user identity, so @me search doesn't work.
Use explicit 'don-petry' author instead to find PRs to fix.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: avoid subshell in while loop to preserve variable state

Using pipe in while loop created subshell where PROBLEM_PRS and FIXED_PRS
counters were incremented but changes didn't persist to parent shell.
Fixed by using process substitution for input instead of pipe.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* docs: add comprehensive status report for GitHub App setup

- Overview of completed work
- GitHub App authentication details
- 24 stuck PRs successfully fixed with approval reviews
- Architectural decisions and rationale
- Configuration guide and usage examples
- Known limitations and troubleshooting

System is now fully operational with GitHub App token authentication,
comprehensive documentation, and all infrastructure in place.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* docs: document stuck PR cleanup status and script fixes

Added documentation for:
- GitHub App token compatibility fixes in fix-stuck-prs.sh
  * Changed @me to explicit author (app tokens have no identity)
  * Fixed subshell variable scope (process substitution vs pipe)
  * Silenced expected 403 auth check error
- Current status: 24 PRs have approvals but remain OPEN
  * Auto-merge failed due to missing GitHub App permission
  * Approvals satisfy branch protection requirement
  * Next: expand permissions or manually merge

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: update list-prs.sh to search all repos by owner instead of @me

GitHub App tokens don't have user identity, so @me/@review-requested searches
fail. Changed to enumerate all repos in don-petry and petry-projects, then
search for open PRs within each repo. This covers the full scope:
- All open PRs in personal don-petry repos
- All open PRs in petry-projects org repos

Resolves 6 consecutive workflow failures due to zero PRs being enumerated.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* docs: add investigation report for PR review agent workflow failures

* fix: move env section before steps in workflow to fix YAML structure

GitHub Actions requires env section to come before steps in job definition.
Moving env definition up and setting GH_TOKEN only in steps that need it.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* script: add backfill-approvals to retroactively apply real approvals

Converts PRs with agent approval comments (but no real GitHub approvals)
to have actual APPROVED reviews. Needed for PRs reviewed before the
gh pr review --approve fix was applied.

Usage:
  scripts/backfill-approvals.sh          # dry-run (preview changes)
  scripts/backfill-approvals.sh false    # apply real approvals

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* feat: add backfill-approvals workflow to run as GitHub App bot

Runs backfill-approvals.sh via GitHub Actions so the approvals are posted
by the bot identity, not the PR author — GitHub rejects self-approvals.

Also fixes subshell counter bug in the script (piped while loop lost variable
state; switched to process substitution).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: convert inner while loop to process substitution to preserve counters

Both the outer repo loop and inner PR loop were piped subshells, causing
all counter increments (approved/skipped/failed) to be lost. The summary
always showed 0. Fixed by using < <(...) process substitution for both loops.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* car-hunt: add VIN-deep-dive flow, printable checklists, location-tier ranking

- Step 5.5 pre-test-drive flow: NHTSA VIN decode + per-VIN unrepaired-recall
  automation via Chrome MCP (Honda LWC owner portal documented as not
  automatable; NHTSA web form works).
- §F printable test-drive checklist generator (reportlab/Platypus PDF) with
  cold-start, model-specific red flags, negotiation table, walk-away rules.
- §G fraud / paperwork verification checklist (vehicle-agnostic, reusable):
  walk-away triggers, VIN three-location match, curbstoning, odometer fraud,
  bill of sale, title transfer, payment protection, stolen-vehicle, title-
  jumping, after-purchase steps.
- Step 5.25 head-to-head comparison template with information-asymmetry rule.
- Step 3 Location Quality Tier scoring (Birmingham AL metro table A/B/C/D)
  with adj-CPM modifier; sheet schema gains Adj.CPM + Tier columns.
- Hard-disqualification rules added: non-running engine keywords, mileage
  inconsistency, mandatory description scrape.
- Drive MCP overwrite limitation documented; sheet ID now read from memory
  rather than hardcoded.
- FB Marketplace operational facts: Birmingham AL city ID 107739635926718,
  /search? vs /vehicles? query handling, React-controlled inline composer,
  send-button selector, seller-name extraction regex.
- scheduled-tasks/used-car-search-{morning,afternoon}: tier A/B priority
  flag, sheet ID read from memory.
- Reference PDF generator scripts checked in for reuse.

* fix: re-approve PRs where approval predates last commit (stale approval)

Instead of skipping PRs that already have any APPROVED review, skip only
those where reviewDecision is not REVIEW_REQUIRED. This handles PRs where
a bot approval was posted before a new commit was pushed — GitHub's ruleset
engine treats those approvals as stale even with dismiss_stale_reviews=false.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: robustly extract JSON verdict from mixed claude --print output (#17)

Claude's --print mode can prefix the JSON verdict with conversational
preamble text, causing jq to fail with parse errors. Add
extract_verdict_json to engine.sh and wire all three cascade action
call sites in review-one-pr.sh to use it.

Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: extract_verdict_json — check dest file first (agent Bash-write style)

The cascade-action agent writes its verdict JSON to \$OUTPUT_FILE via a
Bash tool call, then prints a text summary to stdout. The previous fix
only scanned stdout (the .raw file), which contained no JSON. The agent-
written file at \$dest (= \$OUTPUT_FILE) was already correct — just not
checked. Now check \$dest first before falling back to stdout scan.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Enforce MAX_REVIEW_CYCLES cap before running the cascade (#18)

* fix: enforce MAX_REVIEW_CYCLES cap with human-escalation marker

Previously MAX_REVIEW_CYCLES only gated AI delegation in
post-pr-review.sh — the cascade itself ran on every cron tick
regardless of how many cycles had accumulated. Real-world result:
a PR could rack up 9+ review cycles (we observed exactly that on
ContentTwin#100) before any cap took effect.

Add a pre-cascade check in review-one-pr.sh: when the count of
existing `<!-- pr-review-agent v1 sha=... -->` markers is at or
above MAX_REVIEW_CYCLES (default 3), post one escalation comment
marked `<!-- pr-review-agent escalation -->`, label
needs-human-review, request don-petry, and exit 100 (skip
sentinel — doesn't burn the MAX_PRS budget). The escalation
marker doubles as the no-spam guard: subsequent runs detect it
and exit 100 immediately.

Also reuse a single `gh pr view` for both the cycle count and the
escalation-marker check.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* review feedback: surface escalation-comment failures, drop dead unset

- Drop `2>/dev/null || true` on `gh pr comment` so a failed escalation
  post is visible in the workflow log instead of silently leaving the
  PR with no marker (which would re-trigger the cap path next tick).
- Reword the cycle-count comment from "AI delegation loops" to "review
  loops" — the cap also catches cascade-only loops where every cycle
  approves and a new commit lands before merge.
- Remove the redundant `unset PR_BODIES` — never exported, no leakage.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>

* Reliability hardening: session circuit breaker, timeouts, retry, dedup (#20)

* feat: session circuit breaker, per-tier timeouts, retry, and triage hard-fail

Reliability hardening for the PR review agent.

1. Session circuit breaker (.github/workflows/pr-review.yml): on any
   non-zero, non-100 exit from review-one-pr.sh (general failure or rate
   limit on the fallback engine), break the per-PR loop, log a clear
   error annotation naming the failing PR and reason, and exit the step
   with code 1 so the run shows red. Prevents one systemic problem from
   silently burning the entire candidate pool.

2. Per-tier timeouts (scripts/engine.sh): triage/deep/audit/action/duck
   each get their own bounded timeout (180/600/600/300/300s defaults,
   env-overridable). Previously only the duck had a timeout — a hung
   tier could burn the whole 60min job budget.

3. Retry-with-backoff on transient errors (scripts/engine.sh): triage
   retries once on 124/137/143 (timeout / signal kill) since its caller
   captures stdout via $(...) so retries are safe. Deliberately NOT
   applied to run_agentic/run_duck where stdout is redirected to a file
   — a retry there would corrupt the partial first-attempt output.

4. Triage non-JSON now hard-fails (scripts/review-one-pr.sh): replaces
   the silent fallback that synthesized a fake "escalate=MEDIUM" verdict
   and proceeded to deep review. With the new circuit breaker, loud
   failure is the right call — masking a broken triage was burning
   tokens on every PR while the workflow looked healthy.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix: stop stacking duplicate agent reviews on the same PR

Two bugs were causing the agent to leave multiple comments on the same PR.
Together they produced 10 stacked APPROVED reviews on petry-projects/ContentTwin#100.

Bug A — idempotency check is order-dependent (review-one-pr.sh):
The previous marker-discovery code did:
  ((.reviews // []) + (.comments // [])) | .[].body | grep marker | tail -1
This relies on the array concatenation order, not chronological order. When
old agent comments existed alongside newer agent reviews, tail -1 picked
the comment-array marker (older) over the review-array marker (newer),
causing the script to think the head SHA hadn't been reviewed and re-run.
Replaced with a single jq pipeline that tags each item with submittedAt /
createdAt, sorts by timestamp, and takes the actual most-recent marker.

Bug B — no cleanup of prior agent items (post-pr-review.sh):
After successfully posting a new review/comment, prior agent items were
left in place, accumulating forever. Added mark_prior_agent_items_obsolete
which, after a successful post:
  - dismisses prior APPROVED/COMMENTED/CHANGES_REQUESTED agent reviews via
    the GitHub dismissal API (UI shows them struck-through as Dismissed)
  - edits prior agent comments to wrap their body in a collapsed <details>
    block with a "Superseded by re-review at <SHA>" summary, plus a
    `<!-- pr-review-agent superseded -->` sentinel for idempotency

All cleanup API calls are best-effort — failures don't break the workflow.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(cleanup): warn on API failures, preserve global newest, file-stage JSON

Three fixes to mark_prior_agent_items_obsolete from the review of PR #20:

1. ::warning:: annotations on every cleanup API failure (review/comment
   list-fetch, individual review dismissal, individual comment fetch+edit).
   Previously these were silenced with `|| true`, so a permissions change
   on the dismissal endpoint would let duplicates stack indefinitely with
   no signal in the Actions UI. Cleanup is still non-fatal — the new post
   has already landed — but failures are now visible.

2. Preserve the globally-latest agent item across BOTH categories, not the
   newest of each category separately. The earlier code split reviews and
   comments and applied `[:-1]` to each, which left a stale fix-request
   comment in place when the new post was a review (or vice versa). The
   one-off cleanup of ContentTwin#100 hit exactly this case: 12 stacked
   reviews collapsed to 1, but a stale comment from 2026-04-25 (SHA
   cd9132d6) was preserved as "newest comment" even though the latest
   review at SHA 3af8c8ee was newer overall. Now: compute the max
   timestamp across both feeds, exclude items at that timestamp.

3. Stage API responses to disk (`mktemp` + `jq <file>`) instead of routing
   through `--argjson "$var"`. The old approach broke on rare unescaped
   control chars in user-authored comment bodies (jq refused to parse the
   resulting shell-vared JSON). File-based input sidesteps the shell
   pipeline entirely.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>

* Remove car-hunt skill and scheduled tasks (moved to don-petry/don-petry) (#21)

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: apply Copilot fallback hardening and reuse GH_PAT for Copilot auth

- Use secrets.GH_PAT (existing personal account token with Copilot
  subscription) instead of a separate COPILOT_GITHUB_TOKEN secret
- Pin actions/create-github-app-token to SHA (v3.1.1) for supply chain safety
- Add post-install verification for gh-copilot extension with warning on failure
- Guard Copilot fallback path: skip PR gracefully if extension is not installed

Addresses issue #24 workflow fixes (1-3) that could not land via PR #25
due to GitHub App lacking workflows permission.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: switch Copilot engine to gh built-in and fix app-id deprecation

- Remove gh extension install (github/gh-copilot conflicts with built-in alias)
- Replace with gh copilot --version check using COPILOT_GITHUB_TOKEN (GH_PAT)
- Update all engine.sh copilot invocations from bare `copilot` binary to
  `gh copilot suggest --target shell` with GH_TOKEN overridden to GH_PAT
  so the user token (with Copilot subscription) is used, not the App token
- Fix actions/create-github-app-token: app-id → client-id (same secret value,
  just the renamed input in v3.x)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: harden Copilot fallback path and remove stale worktrees (#25)

- gh-copilot install: add --force flag and post-install verification
  warning so silent failures due to built-in alias conflict are visible
  in workflow logs instead of being silently swallowed by `|| true`
- fallback pre-flight check: verify `gh extension list | grep copilot`
  before switching to Copilot engine; if unavailable, skip the PR and
  continue the batch rather than session-aborting and dropping all
  remaining candidates (fixes the 27-PR drop on run #503)
- pin actions/create-github-app-token to SHA for v3.1.1 (Node.js 24
  compatible) to prevent hard break on 2026-06-02 when GitHub forces
  Node.js 24 as default runtime
- remove 6 stale Claude Code worktrees from git tracking; add
  .gitignore entry to prevent future worktrees from being committed
  (eliminates exit-128 warning on every actions/checkout post-job sweep)

Closes #24

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: petry-projects-pr-review-agent[bot] <petry-projects-pr-review-agent[bot]@users.noreply.github.com>

* ci: add pre-flight dedup check to prevent duplicate claude-issue PRs (#26)

Before invoking Claude on a labeled issue, a new shell step queries
for any open PR with a branch matching claude/issue-NNN-* (or a body
containing "Closes #NNN"). If one is found it posts a comment on the
issue pointing to the existing PR and skips the Claude step entirely.
A secondary prompt instruction tells Claude to check first and push to
the existing branch rather than opening a new PR.

Motivated by issue petry-projects/google-app-scripts#171, where the
claude label was re-applied three times and each run created a fresh
PR unaware of the prior attempts.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor: migrate to machine user PAT auth (closes #27) (#28)

* refactor: migrate from GitHub App to machine user PAT auth

Closes #27

GitHub Apps cannot be listed in CODEOWNERS, blocking PRs in repos with
require_code_owner_review: true. Switch all workflows to use a machine
user account's fine-grained PAT (DON_PETRY_BOT_GH_PAT secret), which can
join an org team listed in CODEOWNERS.

Workflows: removed actions/create-github-app-token steps in pr-review,
fix-stuck-prs, backfill-approvals, and daily-pr-review-health. All now
use secrets.DON_PETRY_BOT_GH_PAT directly.

Docs: renamed GITHUB_APP_SETUP.md to MACHINE_USER_SETUP.md with full
rewrite covering account creation, CODEOWNERS config, PAT generation,
and rotation. Updated auth sections in IMPLEMENTATION.md, SETUP.md,
STATUS.md, DOCUMENTATION.md, README.md.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* refactor: switch to org-scoped PAT secret DON_PETRY_BOT_PETRY_PROJECT_PAT

Replace DON_PETRY_BOT_GH_PAT with DON_PETRY_BOT_PETRY_PROJECT_PAT — the
new fine-grained PAT scoped to the petry-projects org (resource owner =
petry-projects). The previous PAT was scoped to the donpetry-bot personal
namespace and had no repository access.

The old DON_PETRY_BOT_GH_PAT secret is retained for any future use against
don-petry's personal repos.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* chore: ignore .claude/scheduled_tasks.lock state file

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>

* refactor: merge approval repair scripts into single automation

- Consolidate backfill-approvals.sh and fix-stuck-prs.sh into repair-pr-approvals.sh
- Iterate all repos in both orgs instead of assuming author
- Verify no existing APPROVED review before posting
- Copy original agent comment as review body
- Enable auto-merge when posting approval if needed
- Single workflow with 30-min timeout

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: add @mention trigger for on-demand PR reviews (#30)

Adds repository_dispatch support so commenting @petry-review-bot on any
PR fires an immediate review without waiting for the hourly schedule.

- pr-review.yml: new `repository_dispatch` trigger (type: pr-review-mention),
  per-PR concurrency group for mention runs, FORCE_REVIEW and DRY_RUN
  env vars that handle both workflow_dispatch and repository_dispatch paths
- review-one-pr.sh: FORCE_REVIEW=true bypasses idempotency so a mention
  always runs a fresh cascade even if the head SHA hasn't changed
- templates/mention-listener.yml: deploy to petry-projects/.github;
  listens for @petry-review-bot, validates commenter trust, posts ack,
  sends repository_dispatch (requires Contents:write, not Actions:write)

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: harden rebase, review dismissal, and health log diagnostics (#40)

Addresses 4 code-actionable recommendations from health check report #33:

- [CRITICAL] Make rebase/branch-update non-fatal — 403/504 during rebase
  emits ::warning:: instead of aborting the batch session (#34)
- [MEDIUM] Add retry with exponential backoff (3 attempts) for transient
  5xx on update-branch API; break immediately on 4xx (#37)
- [MEDIUM] Guard review dismissal with state re-check before calling
  dismiss API, preventing 422s from race conditions (#38)
- [LOW] Surface missing log warnings in health check script (#39)
- Fix: skip auto-merge when branch is still BEHIND after failed rebase

Closes #34, closes #37, closes #38, closes #39

* refactor: migrate to org-wide .github-private convention (#41)

* refactor: parametrize hardcoded identity values for org migration

Replace all hardcoded references to don-petry, petry-review-bot, and
don-petry/pr-review-agent with environment variables that default to
the current values. This allows the agent to be configured for different
orgs/users via repo variables.

Changes:
- Scripts use $REVIEWER_USER, $TARGET_ORG, $BOT_USER, $AGENT_REPO
- Prompts no longer reference specific GitHub usernames
- Workflows use org-level GH_PAT_WORKFLOWS secret instead of
  repo-level DON_PETRY_BOT_PETRY_PROJECT_PAT
- Health check uses context.repo.owner/repo for issue creation
- Variables are set in workflow env block with defaults

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add Copilot custom agent profiles for org-wide use

Create /agents/ directory with three agent profiles:
- pr-reviewer: Multi-tier cascading PR review
- feature-ideator: Feature idea generation and prioritization
- compliance-auditor: Org standards compliance checking

These are Copilot custom agent profiles that become available
org-wide in the .github-private repo convention.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Squashed 'frameworks/bmad-method/' content from commit e36f219c

git-subtree-dir: frameworks/bmad-method
git-subtree-split: e36f219c81b6010d4aae423ba12f49edb5b6e31a

* Squashed 'frameworks/spec-kit/' content from commit 11f49ebf

git-subtree-dir: frameworks/spec-kit
git-subtree-split: 11f49ebfb2f6af55345cb4bd9a7906acd211e56f

* Squashed 'frameworks/gsd/' content from commit 304c1a13

git-subtree-dir: frameworks/gsd
git-subtree-split: 304c1a1302564c45af252bbba4bcc5350e7dac3a

* docs: update README for .github-private org infrastructure role

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* ci: add dependabot-automerge.yml workflow (#73)

Adds the required dependabot-automerge.yml workflow from the org
standard template (petry-projects/.github/standards/workflows/).
This is a thin caller stub that delegates to the org-level reusable
workflow.

Closes #48

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Don Petry <don-petry@users.noreply.github.com>

* fix(pr-review): unblock queue starvation from self-authored PRs (#96) (#97)

* fix(pr-review): unblock queue starvation from self-authored PRs (#96)

A self-authored PR sorted first in the candidate list and triggered
"Can not approve your own pull request" — which the session-fatal
abort treated like an engine error, skipping all 28 remaining PRs
on every run.

- list-prs.sh: filter out PRs authored by REVIEWER_USER at enumeration
- post-pr-review.sh: catch the GraphQL self-approval error and exit 100
  (no-op) instead of 1, so a stray self-PR can no longer abort the batch
- engine.sh: gh copilot renamed --target to --agent; fix the rubber-duck
  invocations so tier-2 cross-engine review works again
- review-one-pr.sh: stop appending a duplicate "0" to REVIEW_CYCLE when
  grep -c finds no markers under set -o pipefail (was breaking the
  cycle-cap integer comparison)

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

* fix(pr-review): authenticate as bot, harden enumeration, address review

- Workflow now runs as AGENT_USER (default don-petry-bot), distinct from
  REVIEWER_USER (the human, don-petry). The self-approval rejection that
  caused #96 came from the agent and the human sharing one identity.
- list-prs.sh filters self-authored PRs against AGENT_USER, and validates
  AGENT_USER against the GitHub username charset before interpolating into
  the jq filter (Copilot review feedback).
- review-one-pr.sh uses printf '%s\n' instead of echo for PR_BODIES, since
  PR body content is user-authored and could begin with -n/-e or contain
  backslash escapes (Copilot review feedback).
- AGENT.md guideline updated to reflect that self-authored PRs are
  intentionally excluded (CodeRabbit review feedback).

Operator note: rotate the GH_PAT_WORKFLOWS secret to a token owned by
don-petry-bot (with read:org added) for the bot-account behavior to take
effect at runtime.

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

* refactor(pr-review): collapse to single BOT_USER, route escalations via CODEOWNERS

Drop AGENT_USER and REVIEWER_USER. The workflow now has one identity:
BOT_USER (default don-petry-bot), which both owns the repos to scan and
gets filtered out as the self-approval blocker. Human escalation no
longer hard-codes a single reviewer; instead, scripts/request-codeowners-
review.sh parses CODEOWNERS in the PR's repo and requests review from
every @user / @org/team mention.

- list-prs.sh: BOT_USER for both gh-repo-list and self-author filter
- review-one-pr.sh, post-pr-review.sh: replace --user "$REVIEWER_USER"
  with the CODEOWNERS helper
- repair-pr-approvals.sh: same gh-repo-list switch
- pr-review.yml: drop AGENT_USER/REVIEWER_USER, set BOT_USER default
  to don-petry-bot
- AGENT.md: updated guideline to describe single-identity model

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

* refactor(pr-review): extract batch loop, hoist env, drop redundant step

Workflow YAML drops from 231 to 111 lines by moving the per-PR review
loop into scripts/review-batch.sh. The script can be syntax-checked and
exercised locally; the workflow now just wires env vars and dispatches.

- Hoist GH_TOKEN, MAX_PRS, CANDIDATE_LIMIT to job-level env (were
  repeated on individual steps).
- Inline `gh auth status` into the install step; remove the standalone
  Verify auth step (its only output was a one-line auth dump).
- Drop the `if: steps.list.outputs.count != '0'` guard and the step's
  `id`/output: review-batch.sh handles empty input as a no-op.
- Collapse the duplicated summary-string branch in the review loop into
  a single template with a conditional fragment.

No behavior change.

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

* perf(pr-review): cache claude-code CLI install across runs

`npm install -g @anthropic-ai/claude-code` ran on every workflow start
(~30s). Switch to a per-user npm prefix (~/.npm-global) and cache that
directory via actions/cache, keyed on CLAUDE_CODE_VERSION + runner OS.

A `command -v claude` guard makes the install a no-op on cache hit, so
the only cost on subsequent runs is the cache restore.

CLAUDE_CODE_VERSION defaults to 'latest' (cache persists until manually
flushed); set the repo variable to pin a specific version for fully
reproducible caching.

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

---------

Co-authored-by: Claude <noreply@anthropic.com>

* chore: rename GH_PAT_WORKFLOWS secret to DON_PETRY_BOT_GH_PAT (#99)

The previous name was generic and didn't tell you which account the PAT
belonged to. The new name makes the binding explicit: this secret is the
PAT owned by don-petry-bot, used as BOT_USER throughout the PR-review
workflows.

Operator follow-up before this can be merged:
- Add a new repo secret DON_PETRY_BOT_GH_PAT containing the bot's PAT
  (with repo, workflow, and read:org scopes).
- After merge, the old GH_PAT_WORKFLOWS secret can be deleted.

Affected workflows:
- pr-review.yml (1 use)
- claude.yml (4 uses, with || github.token fallback)
- daily-pr-review-health.yml (1 use)
- repair-pr-approvals.yml (1 use)

scripts/pr_review_health.sh's error message is updated to point at the
new name as well.

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

Co-authored-by: Claude <noreply@anthropic.com>

* chore: remove frameworks directory (#101)

Co-authored-by: Gemini CLI <gemini-cli@example.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore all erroneously deleted files (items 4-34) (#104)

* revert: restore .github/workflows/daily-pr-review-health.yml

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore .github/workflows/repair-pr-approvals.yml

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore agents/pr-reviewer.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/cascade-action.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/deep-review.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/rubber-duck.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/security-audit.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/shared.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/single-review.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/synthesize-duck.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/synthesize.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/triage.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/engine.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/list-prs.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/post-pr-review.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/pr_review_health.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/repair-pr-approvals.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/request-codeowners-review.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/review-batch.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/review-one-pr.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore templates/mention-listener.yml

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore AGENT.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore BOT_SETUP.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore DOCUMENTATION.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore IMPLEMENTATION.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore MACHINE_USER_SETUP.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore PR_REVIEW_AGENT_FAILURE_REPORT.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore PR_REVIEW_FAILURE_INVESTIGATION.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore SETUP.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore STATUS.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore README.md to pre-change state

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore .github/workflows/daily-pr-review-health.yml

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat: add Gemini Pro support and optimize PR review fallback chain (#102)

* Merge main and resolve conflicts

* Enhance pr-review workflow: add Gemini support and refactor fallback logic

* Address Copilot review comments: tighten regex, fix fallback summary, and align docs

* docs: move agent documentation to docs/ folder and rename files

* docs: align secret names and fix casing in index

* docs: organize pr-review-agent documentation under dedicated folder

* Address final PR comments: tighten regex, add preflight checks, fix bot names, and cleanup docs

---------

Co-authored-by: Gemini CLI <gemini-cli@example.com>

* fix(pr-review): gate rate-limit detection on non-zero exit code

Broad patterns like `plan.*limit` and `claude.*usage` could match content
in a successful triage summary, triggering a false-positive engine fallback.
Guard the check with TRIAGE_RC != 0 so it only fires when the provider
command actually failed.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(claude): sync inlined workflow with org standard (#109)

* fix(claude): sync inlined workflow with org standard

Brings the inlined claude.yml up to parity with
petry-projects/.github/.github/workflows/claude-code-reusable.yml.

Changes:
1. Bot allow list for pull_request_review_comment
   Add coderabbitai[bot], Copilot, copilot-pull-request-reviewer[bot],
   and gemini-code-assist[bot] alongside the existing OWNER/MEMBER/
   COLLABORATOR check. These bots have author_association 'NONE' so
   their review comments were always skipped.

2. check_run trigger + claude-ci-fix job
   Port the CI failure auto-fix feature from the reusable. When a check
   fails on a PR, Claude diagnoses and fixes it automatically.
   Adapted to use DON_PETRY_BOT_GH_PAT (this repo's secret name).

3. claude-code-action SHA bump: v1.0.89 → v1.0.119
   Update both the claude and claude-issue jobs.

4. paths-ignore on pull_request trigger
   Prevents the workflow from firing on PRs that only change
   claude.yml itself, avoiding the Anthropic OIDC validation
   failure (workflow file must match default branch at token exchange).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(claude): guard bot allow list against fork PRs

Same fix as petry-projects/.github PR #238 — add same-repo guard
for bot-triggered pull_request_review_comment runs.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(claude): address ci-fix self-loop and fork PR security

- Replace Claude Code name prefix check with explicit job name list to
  correctly prevent self-loops (check_run names for inlined workflows
  are bare job names, not workflow-prefixed)
- Add fork PR trust gate in Resolve PR number step: verify head repo
  matches target repo before running Claude with privileged credentials

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* ci: replace inlined claude.yml with standard thin-caller stub

The inlined version embedded all three jobs (claude, claude-ci-fix,
claude-issue) directly and threaded DON_PETRY_BOT_GH_PAT through
checkout tokens and github_token, causing the bot's PAT to author
PRs and comments instead of github-actions[bot].

Replace with the standard thin caller that delegates to the org-level
reusable workflow (claude-code-reusable.yml@v1) via secrets: inherit,
which is the correct pattern per:
petry-projects/.github/standards/workflows/claude.yml

* fix: replace gh copilot suggest with GitHub Models REST API (#151)

* fix: replace gh copilot suggest with GitHub Models REST API (#147)

The `gh copilot suggest -p "$(cat <file>)"` invocation failed with
"Invalid command format" because:
1. The `-p` flag is not valid syntax in modern `gh copilot` built-in versions.
2. `gh copilot suggest` is a shell-command suggestion tool; it does not
   accept large PR prompts or return structured JSON responses.
3. Passing a multi-thousand-line prompt via `$(cat ...)` can hit ARG_MAX.

The non-zero exit was then misclassified by the rate-limit detector, which
aborted the entire session and skipped all remaining PRs in the queue.

Fix: replace all three copilot invocations (run_triage, run_agentic,
run_duck) with a new `copilot_chat` helper that calls the GitHub Models
REST API directly via curl. The API is OpenAI-compatible, versioned via
`X-GitHub-Api-Version`, stable against gh CLI version changes, and accepts
arbitrary prompt sizes using python3 for safe JSON encoding.

Additional changes:
- Add `COPILOT_API_MODEL` var (default `openai/o4-mini`) overrideable via env.
- run_agentic/run_duck copilot cases now also write output to $OUTPUT_FILE
  so callers that check that path directly (deep review, audit) find the JSON.
- Rate-limit responses (HTTP 429) are echoed to stdout so the existing
  `is_rate_limited()` detector fires correctly for engine fallback.
- Add pre-flight smoke test in review-batch.sh: tests GitHub Models API
  connectivity with a one-liner prompt before processing any PRs, so auth
  or model errors surface immediately as a clear setup failure.
- Add tests/test_copilot_chat.sh: unit tests for the JSON payload builder
  with edge-case prompts (quotes, newlines, # headings, large diffs, Unicode).

Closes #147

Co-authored-by: Don Petry <don-petry@users.noreply.github.com>

* fix: scope COPILOT_API_MODEL export to copilot engine only

Move the `export COPILOT_API_MODEL` from the common exports block into the
`copilot)` case where the variable is set, so it is not exported as an
empty/unset variable when the engine is `claude` or `gemini`. The
`copilot_chat` fallback `${COPILOT_API_MODEL:-openai/o4-mini}` still works
correctly when `DUCK_ENGINE=copilot` under a non-copilot primary engine.

Co-authored-by: Don Petry <don-petry@users.noreply.github.com>

* fix: address review comments — temp file for payload, fail-fast source, streaming output

- copilot_chat: write JSON body to mktemp file, pass to curl as @file to
  avoid ARG_MAX for large PR diffs (was --data-binary "$body")
- copilot_chat: add :? guard on COPILOT_GITHUB_TOKEN for a clear error
  instead of generic "unbound variable" under set -u
- run_agentic / run_duck copilot paths: stream directly to stdout (and
  tee to OUTPUT_FILE when set) rather than buffering the full response
  into a shell variable, which forced large outputs into memory and
  stripped trailing newlines
- review-batch.sh pre-flight: fail fast if source engine.sh fails
  (was silently ignored with || true)
- review-batch.sh pre-flight: build smoke-test JSON payload via python3
  + temp file instead of shell string interpolation to avoid JSON
  injection if COPILOT_API_MODEL contains special characters
- Clarify that openai/o4-mini is the correct April-2025 o4-generation
  model name, not a typo for o1-mini or gpt-4o-mini

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Don Petry <don-petry@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* feat: prioritise .github/.github-private PRs, oldest-first within tier (#155)

* feat: prioritise .github/.github-private PRs, oldest-first within tier

- list-prs.sh: add createdAt to JSON fetch; emit priority|createdAt|url
  lines (priority 0 for .github/.github-private, 1 for everything else)
- Replace final sort -u with a two-pass sort: deduplicate by URL, then
  sort by priority asc then createdAt asc; strip sort keys with cut
- tests/test_list_prs_sort.sh: 16 tests covering priority classification,
  oldest-first ordering, mixed scenarios and deduplication
- .github/workflows/test.yml: run both unit-test files on every PR / push

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* test: add dedup edge case for same URL with conflicting priorities

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: remove temperature from copilot_chat payload (o4-mini incompatible)

The GitHub Models API rejects temperature=0 for reasoning models like
o4-mini with HTTP 400:
  'Unsupported value: temperature does not support 0 with this model.
   Only the default (1) value is supported.'

This caused the rubber duck (DUCK_ENGINE=copilot / DUCK_MODEL=o4-mini)
to fail with HTTP 400 on every claude-engine run, silently degrading
every review to deep-only with no cross-engine sanity check.

Fix: remove temperature from the copilot_chat JSON payload entirely —
the API defaults to 1, which is the only supported value for o4-mini.

Also:
- Update tests/test_copilot_chat.sh: sync build_payload to match and
  flip Test 9 to assert temperature is ABSENT (not 0).
- Add .github/workflows/test.yml: run unit tests on every PR and push
  to main so regressions are caught before merge.

Reproducer: job/75690347409 — duck failure logged as:
  copilot_chat: HTTP 400 from GitHub Models API
  {"error":{"message":"Unsupported value: 'temperature' does not
   support 0 with this model..."}}

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: address PR review comments

- list-prs.sh: tighten priority regex to /[.]github(-private)?/pull/
  so path-boundary anchor prevents false positives (e.g. foo.github)
- list-prs.sh: replace printf pipeline with here-string to avoid ARG_MAX
  limits on large PR sets
- test.yml: add permissions: contents: read (least-privilege, CodeQL fix)
- test.yml: add timeout-minutes: 5 to prevent hung runs
- test_list_prs_sort.sh: sync sort_entries helper (here-string + || true)
  and classify regex to match updated list-prs.sh exactly

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: don-petry <don@petry.dev>

* fix: restore pr-review.yml content (accidentally emptied in revert)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: restore pr-review.yml (file was accidentally emptied)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore(deps): bump actions/cache from 4 to 5 (#165)

Bumps [actions/cache](https://github.com/actions/cache) from 4 to 5.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): bump petry-projects/.github/.github/workflows/claude-code-reusable.yml (#164)

Bumps [petry-projects/.github/.github/workflows/claude-code-reusable.yml](https://github.com/petry-projects/.github) from 1 to 2.
- [Commits](https://github.com/petry-projects/.github/compare/v1...v2)

---
updated-dependencies:
- dependency-name: petry-projects/.github/.github/workflows/claude-code-reusable.yml
  dependency-version: '2'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* fix(list-prs): search DELEGATION_ORGS and drop --checks success pre-filter

- Add iteration over all orgs in DELEGATION_ORGS (e.g. don-petry) so PRs
  in those orgs enter the candidate pool. Previously only BOT_USER and
  TARGET_ORG were searched.
- Remove --checks success from org repo searches. GitHub excludes PRs from
  repos with no CI configured when this flag is used, silently dropping all
  PRs from repos like .github, .github-private, google-app-scripts, and
  TalkTerm. review-one-pr.sh already enforces CI gating per-PR and treats
  empty statusCheckRollup as passing, so the pre-filter is redundant and
  harmful.
- Add --limit 200 to gh repo list calls to handle org growth beyond gh's
  default 30-repo cap.

* feat: add code-quality ruleset (compliance fix #60) (#86)

Creates the required `code-quality` repository ruleset enforcing required
status checks on the default branch, as mandated by the org standard:
standards/github-settings.md#code-quality--required-checks-ruleset-all-repositories

Required checks:
- SonarCloud (code quality analysis)
- CodeQL (SAST)
- agent-shield / AgentShield (agent security scan)
- dependency-audit / Detect ecosystems (dependency vulnerability scan)

Bypass actors:
- OrganizationAdmin (always) — emergency override
- dependabot-automerge-petry Integration (always) — Dependabot auto-merge

The ruleset was applied directly via GitHub API. This file documents the
configuration as code for auditability and future reapplication.

Closes #60

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Don Petry <don-petry@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* feat(prompts/dev-lead): add human prompt template

* feat(prompts/dev-lead): add human-pr prompt template

* feat(dev-lead): implement dev-lead agent Phases 0-6

* fix: per-PR isolation and single-review retry (closes #132)

- review-batch.sh: non-rate-limit per-PR failures (exit code 1) no
  longer abort the session. SESSION ABORTED EARLY is now reserved for
  the rate-limit-on-fallback-engine case (exit code 2) only. All other
  failures are counted and logged; remaining candidates continue.

- review-one-pr.sh: single-review step retries up to
  SINGLE_REVIEW_MAX_RETRIES (default 2) times with a
  SINGLE_REVIEW_RETRY_DELAY_SEC (default 15s) gap before giving up.
  On exhaustion, the PR is flagged needs-human-review and the script
  exits with code 1, which the updated batch treats as a non-fatal
  per-PR failure. Raw model output and stderr are logged on each failed
  attempt for post-mortem visibility.

Root cause of run #25707852006: claude-opus-4-7 returned a verbose
non-JSON response for PR #129; the old code treated that as fatal and
skipped 35 remaining candidates.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix: rate-limit detection and per-attempt stderr logs in single-review retry

Address inline review comments on PR #133:

- Rate-limit check: after each run_agentic call, inspect both stdout
  (VERDICT_JSON.raw) and stderr (SINGLE_LOG) with is_rate_limited before
  retrying. A rate-limit match exits immediately with code 2 so
  review-batch.sh can trigger engine fallback — consistent with triage and
  deep-review tiers. Previously a rate-limited single-review would burn all
  retries and exit 1 (per-PR failure), silently leaving the batch on the
  same rate-limited engine for all remaining PRs.

- Per-attempt log files: stderr is now written to
  single-review-attempt-N.log rather than a single overwritten file, so
  no earlier-attempt errors are lost. Each attempt logs its own stderr
  inline on failure; the fallback path cats all attempt logs for
  post-mortem visibility.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* feat(dev-lead): Phase 0 test infrastructure and Phase 1 intent stub

Phase 0: full test harness for the dev-lead agent —
  26 event fixtures (all valid JSON with _test_expected_intent),
  stub claude/gemini engines, mock gh binary, CI failure log sample,
  bats helpers (stub-engine, mock-gh, assert-env, prompt-vars),
  7 prompt templates with VARIABLES declarations, preflight script,
  prompt coverage integration test, and test-dev-lead.yml CI workflow.

Phase 1: dev-lead.yml trigger workflow (all 7 event types, dispatch + ci-relay
  jobs) and dev-lead-intent.sh stub (anti-loop guard live; all other events
  emit skip/not-implemented).  14/14 bats unit tests pass.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): add permissions blocks to test workflow jobs (CodeQL)

* fix(ci): auto-fix for lint / eslint [skip ci-relay]

* feat(dev-lead): Phases 2-6 — CI fix, review fix, issue, engine fallback

Phase 2: run_writer/run_writer_with_fallback in engine.sh, full intent
routing in dev-lead-intent.sh, dev-lead-fix-ci.sh handler, workflow wiring.

Phase 3: dev-lead-fix-reviews.sh handles fix-reviews, fix-bot-comment,
human, human-pr, rebase intents. Full review routing in intent classifier.

Phase 5: dev-lead-fix-issue.sh handles the issue intent with dedup guard
and branch/PR creation.

Phase 6: run_writer_with_fallback with claude→gemini→copilot fallback chain.

Tests: 77 unit tests across 8 new .bats files, all passing. Updated Phase 1
stub tests to reflect actual Phase 2+ routing behavior.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): address review comments — dispatch JSON, dead code, portable stub gh

* fix(dev-lead): pre-flight after intent, fork URL check, review-batch exit code, retry comment

* fix(dev-lead): install bats-core from GitHub to avoid root requirement

* fix(security): move event values to env vars to prevent script injection (SonarCloud)

* fix(security): move CLAUDE_CODE_VERSION to env block (SonarCloud script injection)

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* feat(dev-lead): Phase 1.5 — reusable workflow for cross-repo adoption + Phase 7 shadow period

* feat(dev-lead): add reusable workflow (Phase 1.5) and begin shadow period (Phase 7)

- Create .github/workflows/dev-lead-reusable.yml: workflow_call entry
  point for other repos. Checks out .github-private scripts/prompts into
  .dev-lead/, then runs the same intent-classify + handler pipeline as
  dev-lead.yml with PROMPTS_DIR=.dev-lead/prompts/dev-lead.
- Add PROMPTS_DIR env-var support to dev-lead-fix-ci.sh,
  dev-lead-fix-reviews.sh, and dev-lead-fix-issue.sh so the reusable
  workflow can point scripts at the sparse-checkout path without changing
  CWD. Defaults to prompts/dev-lead (backwards-compatible for dev-lead.yml).
- Annotate dev-lead.yml with Phase 7 shadow-period window (2026-05-15
  through ~2026-05-29): claude.yml and dev-lead.yml run in parallel until
  regressions clear, then claude.yml is removed.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* chore(dev-lead): update shadow-period tracking issue ref to #180

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): document PAT requirement and pin ref:main on private checkout

* fix(dev-lead): P1 review fixes — ci-relay in reusable, .dev-lead gitignore, export PROMPTS_DIR, trim header

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): rate-limit detection, separate status, and retry cron (#192) (#196)

* chore(dev-lead): decommission claude.yml, bump ACTION_TIMEOUT_SEC to 600s

- Delete .github/workflows/claude.yml — replaced by dev-lead.yml (shadow
  period complete, tracking issue #180).
- scripts/engine.sh: raise ACTION_TIMEOUT_SEC default 300→600s to reduce
  timeout failures on large-repo fix-ci runs (PR #80 had 3 timeouts).
- AGENTS.md: remove claude.yml immutability exemption; note dev-lead.yml
  as the active AI automation workflow and its edit-via-reusable pattern.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* refactor: replace Claude analysis with pure gh/jq telemetry in health check

* refactor: drop Node/Claude steps from daily-pr-review-health workflow

* rename: daily-pr-review-health → actions-fleet-monitor

* rename: daily-pr-review-health → actions-fleet-monitor

* fix(agents-md): clarify dev-lead.yml vs dev-lead-reusable.yml scope

dev-lead.yml in .github-private runs inline steps (not a caller stub).
Behavior changes for this repo go to dev-lead.yml directly; changes that
affect all org repos via the cross-repo reusable go to dev-lead-reusable.yml.

Addresses Copilot review on PR #194.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): rate-limit detection, separate status, and retry cron

Closes #192. Implements all phases from the revised plan.

**Phase 0 — fix run_writer stdout capture (prerequisite)**
- engine.sh: capture stdout via `tee` to a tempfile so `is_rate_limited`
  can inspect the output; old code read `/tmp/dev-lead-writer-stderr`
  which was never written (claude --print outputs to stdout, not stderr).
  Fallback engines were never tried in practice.
- engine.sh: add `parse_reset_time` to extract ISO timestamp from
  `resets H:MMpm (UTC)` in engine output and write it to
  `/tmp/dev-lead-rate-limit-reset` for callers to embed in markers.

**Phase 1 — separate status=rate-limited from status=failed**
- fix-ci.sh: detect engine exit 2 → post `status=rate-limited` (not
  `status=failed`); embed parsed reset time in marker body; exit 2.
- fix-ci.sh: fix `check_idempotency` to treat `status=rate-limited`
  as retriable — only block on terminal statuses (applied, failed,
  no-changes). Previously, rate-limited markers blocked all retries.
- fix-ci.sh: `count_recent_failures` already filtered to `status=failed`;
  add explicit comment confirming rate-limited markers are excluded.
- fix-ci.sh: add `has_rate_limited_marker` dedup check so the same SHA
  never accumulates more than one rate-limited comment.
- fix-reviews.sh: on engine exit 2 for all five intent types
  (fix-reviews, fix-bot-comment, human, human-pr, rebase), post a
  `status=rate-limited` marker with embedded reset time and exit 2.
- fix-reviews.sh: for `human` and `human-pr` intents, also post a
  user-visible acknowledgment comment so users know their request was
  received and will be retried.

**Phase 2 — scheduled retry cron for fix-ci**
- dev-lead-retry.sh: new script scans all open PRs across TARGET_ORG
  for `status=rate-limited` markers on current HEAD SHA; skips PRs
  whose reset time is still in the future; staggered dispatches (30s
  between repos) to prevent cascading org-level rate-limit hits.
- dev-lead-retry.yml: new workflow — scheduled every 2 hours (dual
  offsets to work around GitHub scheduler skew), plus workflow_dispatch.

**Phase 3 — retry for fix-reviews intents (same cron)**
- dev-lead-retry.sh already handles all five fix-reviews intent types
  (fix-reviews, fix-bot-comment, human, human-pr, rebase) via
  `dev-lead-reviews-retry` dispatch events.
- dev-lead-intent.sh: add `dev-lead-reviews-retry` dispatch type routing
  to the intent classifier — maps intent_type from payload to the correct
  existing fix-reviews step.
- dev-lead.yml: add `dev-lead-reviews-retry` to repository_dispatch types.
- dev-lead.yml: unify concurrency group for all dispatch types to
  `dev-lead-pr-{pr_number}` to avoid per-type slot fragmentation.

**Tests**
- test_engine_writer.bats: 8 new tests covering rate-limit stdout detection,
  exit-2 mapping, fallback exhaustion, reset time parsing.
- test_fix_ci.bats: 5 new tests covering rate-limited status, exhaustion
  exclusion, idempotency pass-through, and dedup.
- test_fix_reviews.bats: 5 new tests covering all intent types + human ack.
- test_intent_ci.bats: 4 new tests for dev-lead-reviews-retry dispatch routing.
- e2e/scenarios/07-rate-limit-retry.sh: new E2E scenario covering all parts.
- 3 new event fixtures for repository_dispatch_reviews_retry_* payloads.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): address review comments on #196

Addresses all 18 review threads from Copilot, Codex, and Gemini.

**P1 bugs fixed:**
- dev-lead-retry.sh: all log echo calls in scan_pr_for_rate_limits and
  dispatch_* functions now write to stderr; only the final count is on
  stdout. Previously, log lines polluted the command substitution,
  causing arithmetic expansion to fail and aborting the scan on the
  first retry candidate.
- dev-lead-retry.yml: permissions: contents was read-only; creating
  repository_dispatch events requires contents: write.

**Correctness fixes:**
- dev-lead-retry.sh: restrict automated retries to intents whose context
  can be reconstructed at runtime (fix-reviews, human-pr, rebase). human
  and fix-bot-comment require USER_INSTRUCTION/COMMENT_BODY from the
  original event which cannot be re-fetched; they are explicitly excluded.
- dev-lead-fix-reviews.sh: resolve HEAD_SHA from the PR API when not
  provided by the triggering event (issue_comment intents carry no SHA).
  Without this, rate-limited markers had no sha= field and were
  invisible to the retry scanner.
- dev-lead-fix-reviews.sh: write terminal status=applied marker after
  successful fix-reviews, human-pr, rebase runs. Prevents the retry
  cron from re-dispatching the same intent on every subsequent tick when
  the SHA hasn't changed.
- dev-lead-retry.sh: check for reviews terminal marker before dispatching
  a retry (mirrors the existing fix-ci terminal-marker check).
- dev-lead-retry.sh: look up current check-run details (details_url, id)
  at dispatch time via the commits check-runs API, so retried fix-ci runs
  have full failure logs and annotations rather than empty fields.
- dev-lead-fix-ci.sh: embed check= field in the rate-limited marker so
  the retry cron knows which check run to look up.
- dev-lead-fix-reviews.sh: human intent now posts a "please re-mention
  @dev-lead" ack instead of "I'll retry automatically" since it will NOT
  be retried automatically. human-pr keeps the auto-retry ack (correct).

**Pagination (Copilot + Gemini):**
- dev-lead-fix-ci.sh: add --paginate to check_idempotency, count_recent_
  failures, has_rate_limited_marker.
- dev-lead-fix-reviews.sh: add --paginate to has_reviews_rate_limited_marker.
- dev-lead-retry.sh: add --paginate to PR comments and PR list fetches.

**Other improvements:**
- dev-lead-retry.yml: simplify to single cron schedule (15 */2 * * *);
  remove BOT_USER env var (unused in the script).
- dev-lead-retry.sh: raise repo list limit 200 → 1000; add hard-error
  when list_repos_for_org returns empty (surfaces token permission issues
  rather than silently scanning 0 repos).
- dev-lead-intent.sh: use jq -nc (compact, null-input) for context JSON
  construction — safe against values with special characters.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(pr-review): handle Gemini trust-mode failure and properly route engine fallbacks (#217)

* fix(pr-review): handle Gemini trust-mode failure and properly route engine fallbacks

Closes #208

* fix(ci): install gemini CLI and set trust mode for dev-lead agents

---------

Co-authored-by: Gemini CLI <gemini-cli@example.com>

* fix: resolve YAML syntax error in dev-lead-reusable workflow

* fix: address automated feedback from PR 217 (#219)

This addresses missed feedback from CodeRabbit (removing syntax errors in YAML) and gemini-code-assist (improving string concatenation in validate-engines.sh), and adds assertion output checks to tests.

Co-authored-by: Gemini CLI <gemini-cli@example.com>

* chore: remove Phase 7 shadow period comment from dev-lead.yml

* feat: skip PRs with CHANGES_REQUESTED review (+ FORCE_REVIEW and stale-review guards)

* feat: skip PRs with CHANGES_REQUESTED review decision

Adds an early-exit check alongside the existing CI guards so the cascade
skips any PR where a human reviewer has already requested changes.
Reviewing those PRs before the author responds wastes tokens and creates
confusing parallel feedback threads.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix: honor FORCE_REVIEW and skip only stale-free CHANGES_REQUESTED reviews

Two issues addressed from Codex P2 feedback:

1. FORCE_REVIEW bypass: mention-triggered and force_review=true runs now
   bypass the CHANGES_REQUESTED skip, so authors can explicitly request a
   re-review after addressing feedback.

2. Stale review guard: only skip when a CHANGES_REQUESTED review targets
   the current head SHA. On repos without automatic stale-review dismissal,
   reviewDecision stays CHANGES_REQUESTED after new commits — the cascade
   now re-engages when the author has pushed new work.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): resolve context loss, missing checkout, and push logic (#223)

* feat: skip PRs with CHANGES_REQUESTED review decision

Adds an early-exit check alongside the existing CI guards so the cascade
skips any PR where a human reviewer has already requested changes.
Reviewing those PRs before the author responds wastes tokens and creates
confusing parallel feedback threads.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix: honor FORCE_REVIEW and skip only stale-free CHANGES_REQUESTED reviews

Two issues addressed from Codex P2 feedback:

1. FORCE_REVIEW bypass: mention-triggered and force_review=true runs now
   bypass the CHANGES_REQUESTED skip, so authors can explicitly request a
   re-review after addressing feedback.

2. Stale review guard: only skip when a CHANGES_REQUESTED review targets
   the current head SHA. On repos without automatic stale-review dismissal,
   reviewDecision stays CHANGES_REQUESTED after new commits — the cascade
   now re-engages when the author has pushed new work.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): resolve context loss, missing checkout, and push logic

- dev-lead-intent.sh: include actor and body in INTENT_CONTEXT for comments
- dev-lead.yml: parse and pass INTENT_ACTOR and INTENT_COMMENT_BODY to agent
- dev-lead-fix-reviews.sh: perform PR checkout, git push, and post summary comments

* fix(dev-lead): exit with 0 on no-changes in fix-reviews and human-pr

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* test: bypass CI check

* fix(pr-review): remove CI bypass hack

* fix(dev-lead): use heredoc for multiline environment variables (#224)

* fix(dev-lead): use heredoc for multiline environment variables

* fix(dev-lead): ensure all engines are installed and context is consistent

* fix(dev-lead): resolve stale model name in engine fallbacks

* fix(dev-lead): improve fallback reliability and script quality

- engine.sh: resolve stale model names in fallback loop; use -latest Gemini aliases
- dev-lead-fix-reviews.sh: remove unused code and fix PR_URL export

* fix(dev-lead): resolve Gemini model names and shell lint warnings

* security(dev-lead): use random heredoc delimiter to prevent injection

Also updates Gemini models to 3.1 family (pro/flash) for May 2026 compatibility.

* fix(dev-lead): update Gemini models to 2.5 stable family

* fix(dev-lead): use auto model selection for Gemini

* security(dev-lead): harden env var parsing and use high-quota Gemini fallback

* fix(dev-lead): use auto model for Gemini

* fix(dev-lead): detect engine-committed changes and remove commit instructions from prompts

commit_and_push only checked git diff (uncommitted changes), so when the engine
followed prompt instructions to run `git commit` via Bash, the working tree was
clean and commit_and_push concluded no-changes — dropping the engine's commits
when the ephemeral runner exited.

Fix 1 (commit_and_push): also detect unpushed commits via `git log @{u}..HEAD`.
If the engine already committed, skip the add+commit step and go straight to push.

Fix 2 (prompts): remove "Commit the changes with..." steps from human.md,
human-pr.md, fix-bot-comment.md, and fix-reviews.md. Replace with an explicit
"Do not commit or push" constraint so the engine leaves git operations to the
script in all cases.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): address PR review findings — untracked fil…
don-petry added a commit that referenced this pull request Aug 7, 2026
This addresses missed feedback from CodeRabbit (removing syntax errors in YAML) and gemini-code-assist (improving string concatenation in validate-engines.sh), and adds assertion output checks to tests.

Co-authored-by: Gemini CLI <gemini-cli@example.com>
don-petry added a commit that referenced this pull request Aug 7, 2026
… trigger for the Class-2 timer changes, before narrowing them (#1420)

* add: workflow to fix stuck PRs using bot account token

Allows running the cleanup script via workflow_dispatch with access to
GH_PAT (bot account token) from repo secrets.

* fix: pass GH_PAT to checkout action so workflow uses bot account

* debug: simplify account check, add authentication debug output

* docs: add comprehensive petry-review-bot setup instructions

* docs: add GitHub App setup guide (recommended approach)

GitHub App is the secure, recommended way to automate PR reviews:
- Fine-grained permissions
- JWT tokens that auto-expire
- No human account needed
- Better audit trail
- GitHub's recommended approach

Replaces the bot user account approach with a more secure alternative.

* docs: add comprehensive GitHub App setup documentation

- SETUP.md: Quick reference guide with configuration and troubleshooting
- IMPLEMENTATION.md: Technical deep dive on architecture and design decisions
- DOCUMENTATION.md: Index of all documentation files
- Updated README.md with status and quick links
- Updated GITHUB_APP_SETUP.md with implementation notes and actual app ID
- Workflows use GitHub App token generation instead of static PATs

Covers the transition from bot user account to GitHub App authentication for improved security and maintainability.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: silence auth check in fix-stuck-prs when using GitHub App token

GitHub App tokens don't have user scope, so 'gh api user' returns 403.
This is fine - the script still works for PR operations. Suppress the error
so the script completes successfully with app-token fallback label.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: use explicit author instead of @me in stuck PR search

GitHub App tokens don't have user identity, so @me search doesn't work.
Use explicit 'don-petry' author instead to find PRs to fix.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: avoid subshell in while loop to preserve variable state

Using pipe in while loop created subshell where PROBLEM_PRS and FIXED_PRS
counters were incremented but changes didn't persist to parent shell.
Fixed by using process substitution for input instead of pipe.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* docs: add comprehensive status report for GitHub App setup

- Overview of completed work
- GitHub App authentication details
- 24 stuck PRs successfully fixed with approval reviews
- Architectural decisions and rationale
- Configuration guide and usage examples
- Known limitations and troubleshooting

System is now fully operational with GitHub App token authentication,
comprehensive documentation, and all infrastructure in place.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* docs: document stuck PR cleanup status and script fixes

Added documentation for:
- GitHub App token compatibility fixes in fix-stuck-prs.sh
  * Changed @me to explicit author (app tokens have no identity)
  * Fixed subshell variable scope (process substitution vs pipe)
  * Silenced expected 403 auth check error
- Current status: 24 PRs have approvals but remain OPEN
  * Auto-merge failed due to missing GitHub App permission
  * Approvals satisfy branch protection requirement
  * Next: expand permissions or manually merge

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: update list-prs.sh to search all repos by owner instead of @me

GitHub App tokens don't have user identity, so @me/@review-requested searches
fail. Changed to enumerate all repos in don-petry and petry-projects, then
search for open PRs within each repo. This covers the full scope:
- All open PRs in personal don-petry repos
- All open PRs in petry-projects org repos

Resolves 6 consecutive workflow failures due to zero PRs being enumerated.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* docs: add investigation report for PR review agent workflow failures

* fix: move env section before steps in workflow to fix YAML structure

GitHub Actions requires env section to come before steps in job definition.
Moving env definition up and setting GH_TOKEN only in steps that need it.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* script: add backfill-approvals to retroactively apply real approvals

Converts PRs with agent approval comments (but no real GitHub approvals)
to have actual APPROVED reviews. Needed for PRs reviewed before the
gh pr review --approve fix was applied.

Usage:
  scripts/backfill-approvals.sh          # dry-run (preview changes)
  scripts/backfill-approvals.sh false    # apply real approvals

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* feat: add backfill-approvals workflow to run as GitHub App bot

Runs backfill-approvals.sh via GitHub Actions so the approvals are posted
by the bot identity, not the PR author — GitHub rejects self-approvals.

Also fixes subshell counter bug in the script (piped while loop lost variable
state; switched to process substitution).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: convert inner while loop to process substitution to preserve counters

Both the outer repo loop and inner PR loop were piped subshells, causing
all counter increments (approved/skipped/failed) to be lost. The summary
always showed 0. Fixed by using < <(...) process substitution for both loops.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* car-hunt: add VIN-deep-dive flow, printable checklists, location-tier ranking

- Step 5.5 pre-test-drive flow: NHTSA VIN decode + per-VIN unrepaired-recall
  automation via Chrome MCP (Honda LWC owner portal documented as not
  automatable; NHTSA web form works).
- §F printable test-drive checklist generator (reportlab/Platypus PDF) with
  cold-start, model-specific red flags, negotiation table, walk-away rules.
- §G fraud / paperwork verification checklist (vehicle-agnostic, reusable):
  walk-away triggers, VIN three-location match, curbstoning, odometer fraud,
  bill of sale, title transfer, payment protection, stolen-vehicle, title-
  jumping, after-purchase steps.
- Step 5.25 head-to-head comparison template with information-asymmetry rule.
- Step 3 Location Quality Tier scoring (Birmingham AL metro table A/B/C/D)
  with adj-CPM modifier; sheet schema gains Adj.CPM + Tier columns.
- Hard-disqualification rules added: non-running engine keywords, mileage
  inconsistency, mandatory description scrape.
- Drive MCP overwrite limitation documented; sheet ID now read from memory
  rather than hardcoded.
- FB Marketplace operational facts: Birmingham AL city ID 107739635926718,
  /search? vs /vehicles? query handling, React-controlled inline composer,
  send-button selector, seller-name extraction regex.
- scheduled-tasks/used-car-search-{morning,afternoon}: tier A/B priority
  flag, sheet ID read from memory.
- Reference PDF generator scripts checked in for reuse.

* fix: re-approve PRs where approval predates last commit (stale approval)

Instead of skipping PRs that already have any APPROVED review, skip only
those where reviewDecision is not REVIEW_REQUIRED. This handles PRs where
a bot approval was posted before a new commit was pushed — GitHub's ruleset
engine treats those approvals as stale even with dismiss_stale_reviews=false.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: robustly extract JSON verdict from mixed claude --print output (#17)

Claude's --print mode can prefix the JSON verdict with conversational
preamble text, causing jq to fail with parse errors. Add
extract_verdict_json to engine.sh and wire all three cascade action
call sites in review-one-pr.sh to use it.

Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: extract_verdict_json — check dest file first (agent Bash-write style)

The cascade-action agent writes its verdict JSON to \$OUTPUT_FILE via a
Bash tool call, then prints a text summary to stdout. The previous fix
only scanned stdout (the .raw file), which contained no JSON. The agent-
written file at \$dest (= \$OUTPUT_FILE) was already correct — just not
checked. Now check \$dest first before falling back to stdout scan.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Enforce MAX_REVIEW_CYCLES cap before running the cascade (#18)

* fix: enforce MAX_REVIEW_CYCLES cap with human-escalation marker

Previously MAX_REVIEW_CYCLES only gated AI delegation in
post-pr-review.sh — the cascade itself ran on every cron tick
regardless of how many cycles had accumulated. Real-world result:
a PR could rack up 9+ review cycles (we observed exactly that on
ContentTwin#100) before any cap took effect.

Add a pre-cascade check in review-one-pr.sh: when the count of
existing `<!-- pr-review-agent v1 sha=... -->` markers is at or
above MAX_REVIEW_CYCLES (default 3), post one escalation comment
marked `<!-- pr-review-agent escalation -->`, label
needs-human-review, request don-petry, and exit 100 (skip
sentinel — doesn't burn the MAX_PRS budget). The escalation
marker doubles as the no-spam guard: subsequent runs detect it
and exit 100 immediately.

Also reuse a single `gh pr view` for both the cycle count and the
escalation-marker check.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* review feedback: surface escalation-comment failures, drop dead unset

- Drop `2>/dev/null || true` on `gh pr comment` so a failed escalation
  post is visible in the workflow log instead of silently leaving the
  PR with no marker (which would re-trigger the cap path next tick).
- Reword the cycle-count comment from "AI delegation loops" to "review
  loops" — the cap also catches cascade-only loops where every cycle
  approves and a new commit lands before merge.
- Remove the redundant `unset PR_BODIES` — never exported, no leakage.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>

* Reliability hardening: session circuit breaker, timeouts, retry, dedup (#20)

* feat: session circuit breaker, per-tier timeouts, retry, and triage hard-fail

Reliability hardening for the PR review agent.

1. Session circuit breaker (.github/workflows/pr-review.yml): on any
   non-zero, non-100 exit from review-one-pr.sh (general failure or rate
   limit on the fallback engine), break the per-PR loop, log a clear
   error annotation naming the failing PR and reason, and exit the step
   with code 1 so the run shows red. Prevents one systemic problem from
   silently burning the entire candidate pool.

2. Per-tier timeouts (scripts/engine.sh): triage/deep/audit/action/duck
   each get their own bounded timeout (180/600/600/300/300s defaults,
   env-overridable). Previously only the duck had a timeout — a hung
   tier could burn the whole 60min job budget.

3. Retry-with-backoff on transient errors (scripts/engine.sh): triage
   retries once on 124/137/143 (timeout / signal kill) since its caller
   captures stdout via $(...) so retries are safe. Deliberately NOT
   applied to run_agentic/run_duck where stdout is redirected to a file
   — a retry there would corrupt the partial first-attempt output.

4. Triage non-JSON now hard-fails (scripts/review-one-pr.sh): replaces
   the silent fallback that synthesized a fake "escalate=MEDIUM" verdict
   and proceeded to deep review. With the new circuit breaker, loud
   failure is the right call — masking a broken triage was burning
   tokens on every PR while the workflow looked healthy.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix: stop stacking duplicate agent reviews on the same PR

Two bugs were causing the agent to leave multiple comments on the same PR.
Together they produced 10 stacked APPROVED reviews on petry-projects/ContentTwin#100.

Bug A — idempotency check is order-dependent (review-one-pr.sh):
The previous marker-discovery code did:
  ((.reviews // []) + (.comments // [])) | .[].body | grep marker | tail -1
This relies on the array concatenation order, not chronological order. When
old agent comments existed alongside newer agent reviews, tail -1 picked
the comment-array marker (older) over the review-array marker (newer),
causing the script to think the head SHA hadn't been reviewed and re-run.
Replaced with a single jq pipeline that tags each item with submittedAt /
createdAt, sorts by timestamp, and takes the actual most-recent marker.

Bug B — no cleanup of prior agent items (post-pr-review.sh):
After successfully posting a new review/comment, prior agent items were
left in place, accumulating forever. Added mark_prior_agent_items_obsolete
which, after a successful post:
  - dismisses prior APPROVED/COMMENTED/CHANGES_REQUESTED agent reviews via
    the GitHub dismissal API (UI shows them struck-through as Dismissed)
  - edits prior agent comments to wrap their body in a collapsed <details>
    block with a "Superseded by re-review at <SHA>" summary, plus a
    `<!-- pr-review-agent superseded -->` sentinel for idempotency

All cleanup API calls are best-effort — failures don't break the workflow.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(cleanup): warn on API failures, preserve global newest, file-stage JSON

Three fixes to mark_prior_agent_items_obsolete from the review of PR #20:

1. ::warning:: annotations on every cleanup API failure (review/comment
   list-fetch, individual review dismissal, individual comment fetch+edit).
   Previously these were silenced with `|| true`, so a permissions change
   on the dismissal endpoint would let duplicates stack indefinitely with
   no signal in the Actions UI. Cleanup is still non-fatal — the new post
   has already landed — but failures are now visible.

2. Preserve the globally-latest agent item across BOTH categories, not the
   newest of each category separately. The earlier code split reviews and
   comments and applied `[:-1]` to each, which left a stale fix-request
   comment in place when the new post was a review (or vice versa). The
   one-off cleanup of ContentTwin#100 hit exactly this case: 12 stacked
   reviews collapsed to 1, but a stale comment from 2026-04-25 (SHA
   cd9132d6) was preserved as "newest comment" even though the latest
   review at SHA 3af8c8ee was newer overall. Now: compute the max
   timestamp across both feeds, exclude items at that timestamp.

3. Stage API responses to disk (`mktemp` + `jq <file>`) instead of routing
   through `--argjson "$var"`. The old approach broke on rare unescaped
   control chars in user-authored comment bodies (jq refused to parse the
   resulting shell-vared JSON). File-based input sidesteps the shell
   pipeline entirely.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>

* Remove car-hunt skill and scheduled tasks (moved to don-petry/don-petry) (#21)

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: apply Copilot fallback hardening and reuse GH_PAT for Copilot auth

- Use secrets.GH_PAT (existing personal account token with Copilot
  subscription) instead of a separate COPILOT_GITHUB_TOKEN secret
- Pin actions/create-github-app-token to SHA (v3.1.1) for supply chain safety
- Add post-install verification for gh-copilot extension with warning on failure
- Guard Copilot fallback path: skip PR gracefully if extension is not installed

Addresses issue #24 workflow fixes (1-3) that could not land via PR #25
due to GitHub App lacking workflows permission.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: switch Copilot engine to gh built-in and fix app-id deprecation

- Remove gh extension install (github/gh-copilot conflicts with built-in alias)
- Replace with gh copilot --version check using COPILOT_GITHUB_TOKEN (GH_PAT)
- Update all engine.sh copilot invocations from bare `copilot` binary to
  `gh copilot suggest --target shell` with GH_TOKEN overridden to GH_PAT
  so the user token (with Copilot subscription) is used, not the App token
- Fix actions/create-github-app-token: app-id → client-id (same secret value,
  just the renamed input in v3.x)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: harden Copilot fallback path and remove stale worktrees (#25)

- gh-copilot install: add --force flag and post-install verification
  warning so silent failures due to built-in alias conflict are visible
  in workflow logs instead of being silently swallowed by `|| true`
- fallback pre-flight check: verify `gh extension list | grep copilot`
  before switching to Copilot engine; if unavailable, skip the PR and
  continue the batch rather than session-aborting and dropping all
  remaining candidates (fixes the 27-PR drop on run #503)
- pin actions/create-github-app-token to SHA for v3.1.1 (Node.js 24
  compatible) to prevent hard break on 2026-06-02 when GitHub forces
  Node.js 24 as default runtime
- remove 6 stale Claude Code worktrees from git tracking; add
  .gitignore entry to prevent future worktrees from being committed
  (eliminates exit-128 warning on every actions/checkout post-job sweep)

Closes #24

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: petry-projects-pr-review-agent[bot] <petry-projects-pr-review-agent[bot]@users.noreply.github.com>

* ci: add pre-flight dedup check to prevent duplicate claude-issue PRs (#26)

Before invoking Claude on a labeled issue, a new shell step queries
for any open PR with a branch matching claude/issue-NNN-* (or a body
containing "Closes #NNN"). If one is found it posts a comment on the
issue pointing to the existing PR and skips the Claude step entirely.
A secondary prompt instruction tells Claude to check first and push to
the existing branch rather than opening a new PR.

Motivated by issue petry-projects/google-app-scripts#171, where the
claude label was re-applied three times and each run created a fresh
PR unaware of the prior attempts.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor: migrate to machine user PAT auth (closes #27) (#28)

* refactor: migrate from GitHub App to machine user PAT auth

Closes #27

GitHub Apps cannot be listed in CODEOWNERS, blocking PRs in repos with
require_code_owner_review: true. Switch all workflows to use a machine
user account's fine-grained PAT (DON_PETRY_BOT_GH_PAT secret), which can
join an org team listed in CODEOWNERS.

Workflows: removed actions/create-github-app-token steps in pr-review,
fix-stuck-prs, backfill-approvals, and daily-pr-review-health. All now
use secrets.DON_PETRY_BOT_GH_PAT directly.

Docs: renamed GITHUB_APP_SETUP.md to MACHINE_USER_SETUP.md with full
rewrite covering account creation, CODEOWNERS config, PAT generation,
and rotation. Updated auth sections in IMPLEMENTATION.md, SETUP.md,
STATUS.md, DOCUMENTATION.md, README.md.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* refactor: switch to org-scoped PAT secret DON_PETRY_BOT_PETRY_PROJECT_PAT

Replace DON_PETRY_BOT_GH_PAT with DON_PETRY_BOT_PETRY_PROJECT_PAT — the
new fine-grained PAT scoped to the petry-projects org (resource owner =
petry-projects). The previous PAT was scoped to the donpetry-bot personal
namespace and had no repository access.

The old DON_PETRY_BOT_GH_PAT secret is retained for any future use against
don-petry's personal repos.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* chore: ignore .claude/scheduled_tasks.lock state file

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>

* refactor: merge approval repair scripts into single automation

- Consolidate backfill-approvals.sh and fix-stuck-prs.sh into repair-pr-approvals.sh
- Iterate all repos in both orgs instead of assuming author
- Verify no existing APPROVED review before posting
- Copy original agent comment as review body
- Enable auto-merge when posting approval if needed
- Single workflow with 30-min timeout

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: add @mention trigger for on-demand PR reviews (#30)

Adds repository_dispatch support so commenting @petry-review-bot on any
PR fires an immediate review without waiting for the hourly schedule.

- pr-review.yml: new `repository_dispatch` trigger (type: pr-review-mention),
  per-PR concurrency group for mention runs, FORCE_REVIEW and DRY_RUN
  env vars that handle both workflow_dispatch and repository_dispatch paths
- review-one-pr.sh: FORCE_REVIEW=true bypasses idempotency so a mention
  always runs a fresh cascade even if the head SHA hasn't changed
- templates/mention-listener.yml: deploy to petry-projects/.github;
  listens for @petry-review-bot, validates commenter trust, posts ack,
  sends repository_dispatch (requires Contents:write, not Actions:write)

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: harden rebase, review dismissal, and health log diagnostics (#40)

Addresses 4 code-actionable recommendations from health check report #33:

- [CRITICAL] Make rebase/branch-update non-fatal — 403/504 during rebase
  emits ::warning:: instead of aborting the batch session (#34)
- [MEDIUM] Add retry with exponential backoff (3 attempts) for transient
  5xx on update-branch API; break immediately on 4xx (#37)
- [MEDIUM] Guard review dismissal with state re-check before calling
  dismiss API, preventing 422s from race conditions (#38)
- [LOW] Surface missing log warnings in health check script (#39)
- Fix: skip auto-merge when branch is still BEHIND after failed rebase

Closes #34, closes #37, closes #38, closes #39

* refactor: migrate to org-wide .github-private convention (#41)

* refactor: parametrize hardcoded identity values for org migration

Replace all hardcoded references to don-petry, petry-review-bot, and
don-petry/pr-review-agent with environment variables that default to
the current values. This allows the agent to be configured for different
orgs/users via repo variables.

Changes:
- Scripts use $REVIEWER_USER, $TARGET_ORG, $BOT_USER, $AGENT_REPO
- Prompts no longer reference specific GitHub usernames
- Workflows use org-level GH_PAT_WORKFLOWS secret instead of
  repo-level DON_PETRY_BOT_PETRY_PROJECT_PAT
- Health check uses context.repo.owner/repo for issue creation
- Variables are set in workflow env block with defaults

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add Copilot custom agent profiles for org-wide use

Create /agents/ directory with three agent profiles:
- pr-reviewer: Multi-tier cascading PR review
- feature-ideator: Feature idea generation and prioritization
- compliance-auditor: Org standards compliance checking

These are Copilot custom agent profiles that become available
org-wide in the .github-private repo convention.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Squashed 'frameworks/bmad-method/' content from commit e36f219c

git-subtree-dir: frameworks/bmad-method
git-subtree-split: e36f219c81b6010d4aae423ba12f49edb5b6e31a

* Squashed 'frameworks/spec-kit/' content from commit 11f49ebf

git-subtree-dir: frameworks/spec-kit
git-subtree-split: 11f49ebfb2f6af55345cb4bd9a7906acd211e56f

* Squashed 'frameworks/gsd/' content from commit 304c1a13

git-subtree-dir: frameworks/gsd
git-subtree-split: 304c1a1302564c45af252bbba4bcc5350e7dac3a

* docs: update README for .github-private org infrastructure role

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* ci: add dependabot-automerge.yml workflow (#73)

Adds the required dependabot-automerge.yml workflow from the org
standard template (petry-projects/.github/standards/workflows/).
This is a thin caller stub that delegates to the org-level reusable
workflow.

Closes #48

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Don Petry <don-petry@users.noreply.github.com>

* fix(pr-review): unblock queue starvation from self-authored PRs (#96) (#97)

* fix(pr-review): unblock queue starvation from self-authored PRs (#96)

A self-authored PR sorted first in the candidate list and triggered
"Can not approve your own pull request" — which the session-fatal
abort treated like an engine error, skipping all 28 remaining PRs
on every run.

- list-prs.sh: filter out PRs authored by REVIEWER_USER at enumeration
- post-pr-review.sh: catch the GraphQL self-approval error and exit 100
  (no-op) instead of 1, so a stray self-PR can no longer abort the batch
- engine.sh: gh copilot renamed --target to --agent; fix the rubber-duck
  invocations so tier-2 cross-engine review works again
- review-one-pr.sh: stop appending a duplicate "0" to REVIEW_CYCLE when
  grep -c finds no markers under set -o pipefail (was breaking the
  cycle-cap integer comparison)

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

* fix(pr-review): authenticate as bot, harden enumeration, address review

- Workflow now runs as AGENT_USER (default don-petry-bot), distinct from
  REVIEWER_USER (the human, don-petry). The self-approval rejection that
  caused #96 came from the agent and the human sharing one identity.
- list-prs.sh filters self-authored PRs against AGENT_USER, and validates
  AGENT_USER against the GitHub username charset before interpolating into
  the jq filter (Copilot review feedback).
- review-one-pr.sh uses printf '%s\n' instead of echo for PR_BODIES, since
  PR body content is user-authored and could begin with -n/-e or contain
  backslash escapes (Copilot review feedback).
- AGENT.md guideline updated to reflect that self-authored PRs are
  intentionally excluded (CodeRabbit review feedback).

Operator note: rotate the GH_PAT_WORKFLOWS secret to a token owned by
don-petry-bot (with read:org added) for the bot-account behavior to take
effect at runtime.

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

* refactor(pr-review): collapse to single BOT_USER, route escalations via CODEOWNERS

Drop AGENT_USER and REVIEWER_USER. The workflow now has one identity:
BOT_USER (default don-petry-bot), which both owns the repos to scan and
gets filtered out as the self-approval blocker. Human escalation no
longer hard-codes a single reviewer; instead, scripts/request-codeowners-
review.sh parses CODEOWNERS in the PR's repo and requests review from
every @user / @org/team mention.

- list-prs.sh: BOT_USER for both gh-repo-list and self-author filter
- review-one-pr.sh, post-pr-review.sh: replace --user "$REVIEWER_USER"
  with the CODEOWNERS helper
- repair-pr-approvals.sh: same gh-repo-list switch
- pr-review.yml: drop AGENT_USER/REVIEWER_USER, set BOT_USER default
  to don-petry-bot
- AGENT.md: updated guideline to describe single-identity model

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

* refactor(pr-review): extract batch loop, hoist env, drop redundant step

Workflow YAML drops from 231 to 111 lines by moving the per-PR review
loop into scripts/review-batch.sh. The script can be syntax-checked and
exercised locally; the workflow now just wires env vars and dispatches.

- Hoist GH_TOKEN, MAX_PRS, CANDIDATE_LIMIT to job-level env (were
  repeated on individual steps).
- Inline `gh auth status` into the install step; remove the standalone
  Verify auth step (its only output was a one-line auth dump).
- Drop the `if: steps.list.outputs.count != '0'` guard and the step's
  `id`/output: review-batch.sh handles empty input as a no-op.
- Collapse the duplicated summary-string branch in the review loop into
  a single template with a conditional fragment.

No behavior change.

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

* perf(pr-review): cache claude-code CLI install across runs

`npm install -g @anthropic-ai/claude-code` ran on every workflow start
(~30s). Switch to a per-user npm prefix (~/.npm-global) and cache that
directory via actions/cache, keyed on CLAUDE_CODE_VERSION + runner OS.

A `command -v claude` guard makes the install a no-op on cache hit, so
the only cost on subsequent runs is the cache restore.

CLAUDE_CODE_VERSION defaults to 'latest' (cache persists until manually
flushed); set the repo variable to pin a specific version for fully
reproducible caching.

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

---------

Co-authored-by: Claude <noreply@anthropic.com>

* chore: rename GH_PAT_WORKFLOWS secret to DON_PETRY_BOT_GH_PAT (#99)

The previous name was generic and didn't tell you which account the PAT
belonged to. The new name makes the binding explicit: this secret is the
PAT owned by don-petry-bot, used as BOT_USER throughout the PR-review
workflows.

Operator follow-up before this can be merged:
- Add a new repo secret DON_PETRY_BOT_GH_PAT containing the bot's PAT
  (with repo, workflow, and read:org scopes).
- After merge, the old GH_PAT_WORKFLOWS secret can be deleted.

Affected workflows:
- pr-review.yml (1 use)
- claude.yml (4 uses, with || github.token fallback)
- daily-pr-review-health.yml (1 use)
- repair-pr-approvals.yml (1 use)

scripts/pr_review_health.sh's error message is updated to point at the
new name as well.

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

Co-authored-by: Claude <noreply@anthropic.com>

* chore: remove frameworks directory (#101)

Co-authored-by: Gemini CLI <gemini-cli@example.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore all erroneously deleted files (items 4-34) (#104)

* revert: restore .github/workflows/daily-pr-review-health.yml

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore .github/workflows/repair-pr-approvals.yml

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore agents/pr-reviewer.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/cascade-action.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/deep-review.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/rubber-duck.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/security-audit.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/shared.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/single-review.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/synthesize-duck.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/synthesize.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/triage.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/engine.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/list-prs.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/post-pr-review.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/pr_review_health.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/repair-pr-approvals.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/request-codeowners-review.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/review-batch.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/review-one-pr.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore templates/mention-listener.yml

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore AGENT.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore BOT_SETUP.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore DOCUMENTATION.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore IMPLEMENTATION.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore MACHINE_USER_SETUP.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore PR_REVIEW_AGENT_FAILURE_REPORT.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore PR_REVIEW_FAILURE_INVESTIGATION.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore SETUP.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore STATUS.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore README.md to pre-change state

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore .github/workflows/daily-pr-review-health.yml

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat: add Gemini Pro support and optimize PR review fallback chain (#102)

* Merge main and resolve conflicts

* Enhance pr-review workflow: add Gemini support and refactor fallback logic

* Address Copilot review comments: tighten regex, fix fallback summary, and align docs

* docs: move agent documentation to docs/ folder and rename files

* docs: align secret names and fix casing in index

* docs: organize pr-review-agent documentation under dedicated folder

* Address final PR comments: tighten regex, add preflight checks, fix bot names, and cleanup docs

---------

Co-authored-by: Gemini CLI <gemini-cli@example.com>

* fix(pr-review): gate rate-limit detection on non-zero exit code

Broad patterns like `plan.*limit` and `claude.*usage` could match content
in a successful triage summary, triggering a false-positive engine fallback.
Guard the check with TRIAGE_RC != 0 so it only fires when the provider
command actually failed.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(claude): sync inlined workflow with org standard (#109)

* fix(claude): sync inlined workflow with org standard

Brings the inlined claude.yml up to parity with
petry-projects/.github/.github/workflows/claude-code-reusable.yml.

Changes:
1. Bot allow list for pull_request_review_comment
   Add coderabbitai[bot], Copilot, copilot-pull-request-reviewer[bot],
   and gemini-code-assist[bot] alongside the existing OWNER/MEMBER/
   COLLABORATOR check. These bots have author_association 'NONE' so
   their review comments were always skipped.

2. check_run trigger + claude-ci-fix job
   Port the CI failure auto-fix feature from the reusable. When a check
   fails on a PR, Claude diagnoses and fixes it automatically.
   Adapted to use DON_PETRY_BOT_GH_PAT (this repo's secret name).

3. claude-code-action SHA bump: v1.0.89 → v1.0.119
   Update both the claude and claude-issue jobs.

4. paths-ignore on pull_request trigger
   Prevents the workflow from firing on PRs that only change
   claude.yml itself, avoiding the Anthropic OIDC validation
   failure (workflow file must match default branch at token exchange).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(claude): guard bot allow list against fork PRs

Same fix as petry-projects/.github PR #238 — add same-repo guard
for bot-triggered pull_request_review_comment runs.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(claude): address ci-fix self-loop and fork PR security

- Replace Claude Code name prefix check with explicit job name list to
  correctly prevent self-loops (check_run names for inlined workflows
  are bare job names, not workflow-prefixed)
- Add fork PR trust gate in Resolve PR number step: verify head repo
  matches target repo before running Claude with privileged credentials

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* ci: replace inlined claude.yml with standard thin-caller stub

The inlined version embedded all three jobs (claude, claude-ci-fix,
claude-issue) directly and threaded DON_PETRY_BOT_GH_PAT through
checkout tokens and github_token, causing the bot's PAT to author
PRs and comments instead of github-actions[bot].

Replace with the standard thin caller that delegates to the org-level
reusable workflow (claude-code-reusable.yml@v1) via secrets: inherit,
which is the correct pattern per:
petry-projects/.github/standards/workflows/claude.yml

* fix: replace gh copilot suggest with GitHub Models REST API (#151)

* fix: replace gh copilot suggest with GitHub Models REST API (#147)

The `gh copilot suggest -p "$(cat <file>)"` invocation failed with
"Invalid command format" because:
1. The `-p` flag is not valid syntax in modern `gh copilot` built-in versions.
2. `gh copilot suggest` is a shell-command suggestion tool; it does not
   accept large PR prompts or return structured JSON responses.
3. Passing a multi-thousand-line prompt via `$(cat ...)` can hit ARG_MAX.

The non-zero exit was then misclassified by the rate-limit detector, which
aborted the entire session and skipped all remaining PRs in the queue.

Fix: replace all three copilot invocations (run_triage, run_agentic,
run_duck) with a new `copilot_chat` helper that calls the GitHub Models
REST API directly via curl. The API is OpenAI-compatible, versioned via
`X-GitHub-Api-Version`, stable against gh CLI version changes, and accepts
arbitrary prompt sizes using python3 for safe JSON encoding.

Additional changes:
- Add `COPILOT_API_MODEL` var (default `openai/o4-mini`) overrideable via env.
- run_agentic/run_duck copilot cases now also write output to $OUTPUT_FILE
  so callers that check that path directly (deep review, audit) find the JSON.
- Rate-limit responses (HTTP 429) are echoed to stdout so the existing
  `is_rate_limited()` detector fires correctly for engine fallback.
- Add pre-flight smoke test in review-batch.sh: tests GitHub Models API
  connectivity with a one-liner prompt before processing any PRs, so auth
  or model errors surface immediately as a clear setup failure.
- Add tests/test_copilot_chat.sh: unit tests for the JSON payload builder
  with edge-case prompts (quotes, newlines, # headings, large diffs, Unicode).

Closes #147

Co-authored-by: Don Petry <don-petry@users.noreply.github.com>

* fix: scope COPILOT_API_MODEL export to copilot engine only

Move the `export COPILOT_API_MODEL` from the common exports block into the
`copilot)` case where the variable is set, so it is not exported as an
empty/unset variable when the engine is `claude` or `gemini`. The
`copilot_chat` fallback `${COPILOT_API_MODEL:-openai/o4-mini}` still works
correctly when `DUCK_ENGINE=copilot` under a non-copilot primary engine.

Co-authored-by: Don Petry <don-petry@users.noreply.github.com>

* fix: address review comments — temp file for payload, fail-fast source, streaming output

- copilot_chat: write JSON body to mktemp file, pass to curl as @file to
  avoid ARG_MAX for large PR diffs (was --data-binary "$body")
- copilot_chat: add :? guard on COPILOT_GITHUB_TOKEN for a clear error
  instead of generic "unbound variable" under set -u
- run_agentic / run_duck copilot paths: stream directly to stdout (and
  tee to OUTPUT_FILE when set) rather than buffering the full response
  into a shell variable, which forced large outputs into memory and
  stripped trailing newlines
- review-batch.sh pre-flight: fail fast if source engine.sh fails
  (was silently ignored with || true)
- review-batch.sh pre-flight: build smoke-test JSON payload via python3
  + temp file instead of shell string interpolation to avoid JSON
  injection if COPILOT_API_MODEL contains special characters
- Clarify that openai/o4-mini is the correct April-2025 o4-generation
  model name, not a typo for o1-mini or gpt-4o-mini

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Don Petry <don-petry@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* feat: prioritise .github/.github-private PRs, oldest-first within tier (#155)

* feat: prioritise .github/.github-private PRs, oldest-first within tier

- list-prs.sh: add createdAt to JSON fetch; emit priority|createdAt|url
  lines (priority 0 for .github/.github-private, 1 for everything else)
- Replace final sort -u with a two-pass sort: deduplicate by URL, then
  sort by priority asc then createdAt asc; strip sort keys with cut
- tests/test_list_prs_sort.sh: 16 tests covering priority classification,
  oldest-first ordering, mixed scenarios and deduplication
- .github/workflows/test.yml: run both unit-test files on every PR / push

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* test: add dedup edge case for same URL with conflicting priorities

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: remove temperature from copilot_chat payload (o4-mini incompatible)

The GitHub Models API rejects temperature=0 for reasoning models like
o4-mini with HTTP 400:
  'Unsupported value: temperature does not support 0 with this model.
   Only the default (1) value is supported.'

This caused the rubber duck (DUCK_ENGINE=copilot / DUCK_MODEL=o4-mini)
to fail with HTTP 400 on every claude-engine run, silently degrading
every review to deep-only with no cross-engine sanity check.

Fix: remove temperature from the copilot_chat JSON payload entirely —
the API defaults to 1, which is the only supported value for o4-mini.

Also:
- Update tests/test_copilot_chat.sh: sync build_payload to match and
  flip Test 9 to assert temperature is ABSENT (not 0).
- Add .github/workflows/test.yml: run unit tests on every PR and push
  to main so regressions are caught before merge.

Reproducer: job/75690347409 — duck failure logged as:
  copilot_chat: HTTP 400 from GitHub Models API
  {"error":{"message":"Unsupported value: 'temperature' does not
   support 0 with this model..."}}

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: address PR review comments

- list-prs.sh: tighten priority regex to /[.]github(-private)?/pull/
  so path-boundary anchor prevents false positives (e.g. foo.github)
- list-prs.sh: replace printf pipeline with here-string to avoid ARG_MAX
  limits on large PR sets
- test.yml: add permissions: contents: read (least-privilege, CodeQL fix)
- test.yml: add timeout-minutes: 5 to prevent hung runs
- test_list_prs_sort.sh: sync sort_entries helper (here-string + || true)
  and classify regex to match updated list-prs.sh exactly

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: don-petry <don@petry.dev>

* fix: restore pr-review.yml content (accidentally emptied in revert)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: restore pr-review.yml (file was accidentally emptied)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore(deps): bump actions/cache from 4 to 5 (#165)

Bumps [actions/cache](https://github.com/actions/cache) from 4 to 5.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): bump petry-projects/.github/.github/workflows/claude-code-reusable.yml (#164)

Bumps [petry-projects/.github/.github/workflows/claude-code-reusable.yml](https://github.com/petry-projects/.github) from 1 to 2.
- [Commits](https://github.com/petry-projects/.github/compare/v1...v2)

---
updated-dependencies:
- dependency-name: petry-projects/.github/.github/workflows/claude-code-reusable.yml
  dependency-version: '2'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* fix(list-prs): search DELEGATION_ORGS and drop --checks success pre-filter

- Add iteration over all orgs in DELEGATION_ORGS (e.g. don-petry) so PRs
  in those orgs enter the candidate pool. Previously only BOT_USER and
  TARGET_ORG were searched.
- Remove --checks success from org repo searches. GitHub excludes PRs from
  repos with no CI configured when this flag is used, silently dropping all
  PRs from repos like .github, .github-private, google-app-scripts, and
  TalkTerm. review-one-pr.sh already enforces CI gating per-PR and treats
  empty statusCheckRollup as passing, so the pre-filter is redundant and
  harmful.
- Add --limit 200 to gh repo list calls to handle org growth beyond gh's
  default 30-repo cap.

* feat: add code-quality ruleset (compliance fix #60) (#86)

Creates the required `code-quality` repository ruleset enforcing required
status checks on the default branch, as mandated by the org standard:
standards/github-settings.md#code-quality--required-checks-ruleset-all-repositories

Required checks:
- SonarCloud (code quality analysis)
- CodeQL (SAST)
- agent-shield / AgentShield (agent security scan)
- dependency-audit / Detect ecosystems (dependency vulnerability scan)

Bypass actors:
- OrganizationAdmin (always) — emergency override
- dependabot-automerge-petry Integration (always) — Dependabot auto-merge

The ruleset was applied directly via GitHub API. This file documents the
configuration as code for auditability and future reapplication.

Closes #60

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Don Petry <don-petry@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* feat(prompts/dev-lead): add human prompt template

* feat(prompts/dev-lead): add human-pr prompt template

* feat(dev-lead): implement dev-lead agent Phases 0-6

* fix: per-PR isolation and single-review retry (closes #132)

- review-batch.sh: non-rate-limit per-PR failures (exit code 1) no
  longer abort the session. SESSION ABORTED EARLY is now reserved for
  the rate-limit-on-fallback-engine case (exit code 2) only. All other
  failures are counted and logged; remaining candidates continue.

- review-one-pr.sh: single-review step retries up to
  SINGLE_REVIEW_MAX_RETRIES (default 2) times with a
  SINGLE_REVIEW_RETRY_DELAY_SEC (default 15s) gap before giving up.
  On exhaustion, the PR is flagged needs-human-review and the script
  exits with code 1, which the updated batch treats as a non-fatal
  per-PR failure. Raw model output and stderr are logged on each failed
  attempt for post-mortem visibility.

Root cause of run #25707852006: claude-opus-4-7 returned a verbose
non-JSON response for PR #129; the old code treated that as fatal and
skipped 35 remaining candidates.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix: rate-limit detection and per-attempt stderr logs in single-review retry

Address inline review comments on PR #133:

- Rate-limit check: after each run_agentic call, inspect both stdout
  (VERDICT_JSON.raw) and stderr (SINGLE_LOG) with is_rate_limited before
  retrying. A rate-limit match exits immediately with code 2 so
  review-batch.sh can trigger engine fallback — consistent with triage and
  deep-review tiers. Previously a rate-limited single-review would burn all
  retries and exit 1 (per-PR failure), silently leaving the batch on the
  same rate-limited engine for all remaining PRs.

- Per-attempt log files: stderr is now written to
  single-review-attempt-N.log rather than a single overwritten file, so
  no earlier-attempt errors are lost. Each attempt logs its own stderr
  inline on failure; the fallback path cats all attempt logs for
  post-mortem visibility.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* feat(dev-lead): Phase 0 test infrastructure and Phase 1 intent stub

Phase 0: full test harness for the dev-lead agent —
  26 event fixtures (all valid JSON with _test_expected_intent),
  stub claude/gemini engines, mock gh binary, CI failure log sample,
  bats helpers (stub-engine, mock-gh, assert-env, prompt-vars),
  7 prompt templates with VARIABLES declarations, preflight script,
  prompt coverage integration test, and test-dev-lead.yml CI workflow.

Phase 1: dev-lead.yml trigger workflow (all 7 event types, dispatch + ci-relay
  jobs) and dev-lead-intent.sh stub (anti-loop guard live; all other events
  emit skip/not-implemented).  14/14 bats unit tests pass.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): add permissions blocks to test workflow jobs (CodeQL)

* fix(ci): auto-fix for lint / eslint [skip ci-relay]

* feat(dev-lead): Phases 2-6 — CI fix, review fix, issue, engine fallback

Phase 2: run_writer/run_writer_with_fallback in engine.sh, full intent
routing in dev-lead-intent.sh, dev-lead-fix-ci.sh handler, workflow wiring.

Phase 3: dev-lead-fix-reviews.sh handles fix-reviews, fix-bot-comment,
human, human-pr, rebase intents. Full review routing in intent classifier.

Phase 5: dev-lead-fix-issue.sh handles the issue intent with dedup guard
and branch/PR creation.

Phase 6: run_writer_with_fallback with claude→gemini→copilot fallback chain.

Tests: 77 unit tests across 8 new .bats files, all passing. Updated Phase 1
stub tests to reflect actual Phase 2+ routing behavior.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): address review comments — dispatch JSON, dead code, portable stub gh

* fix(dev-lead): pre-flight after intent, fork URL check, review-batch exit code, retry comment

* fix(dev-lead): install bats-core from GitHub to avoid root requirement

* fix(security): move event values to env vars to prevent script injection (SonarCloud)

* fix(security): move CLAUDE_CODE_VERSION to env block (SonarCloud script injection)

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* feat(dev-lead): Phase 1.5 — reusable workflow for cross-repo adoption + Phase 7 shadow period

* feat(dev-lead): add reusable workflow (Phase 1.5) and begin shadow period (Phase 7)

- Create .github/workflows/dev-lead-reusable.yml: workflow_call entry
  point for other repos. Checks out .github-private scripts/prompts into
  .dev-lead/, then runs the same intent-classify + handler pipeline as
  dev-lead.yml with PROMPTS_DIR=.dev-lead/prompts/dev-lead.
- Add PROMPTS_DIR env-var support to dev-lead-fix-ci.sh,
  dev-lead-fix-reviews.sh, and dev-lead-fix-issue.sh so the reusable
  workflow can point scripts at the sparse-checkout path without changing
  CWD. Defaults to prompts/dev-lead (backwards-compatible for dev-lead.yml).
- Annotate dev-lead.yml with Phase 7 shadow-period window (2026-05-15
  through ~2026-05-29): claude.yml and dev-lead.yml run in parallel until
  regressions clear, then claude.yml is removed.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* chore(dev-lead): update shadow-period tracking issue ref to #180

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): document PAT requirement and pin ref:main on private checkout

* fix(dev-lead): P1 review fixes — ci-relay in reusable, .dev-lead gitignore, export PROMPTS_DIR, trim header

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): rate-limit detection, separate status, and retry cron (#192) (#196)

* chore(dev-lead): decommission claude.yml, bump ACTION_TIMEOUT_SEC to 600s

- Delete .github/workflows/claude.yml — replaced by dev-lead.yml (shadow
  period complete, tracking issue #180).
- scripts/engine.sh: raise ACTION_TIMEOUT_SEC default 300→600s to reduce
  timeout failures on large-repo fix-ci runs (PR #80 had 3 timeouts).
- AGENTS.md: remove claude.yml immutability exemption; note dev-lead.yml
  as the active AI automation workflow and its edit-via-reusable pattern.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* refactor: replace Claude analysis with pure gh/jq telemetry in health check

* refactor: drop Node/Claude steps from daily-pr-review-health workflow

* rename: daily-pr-review-health → actions-fleet-monitor

* rename: daily-pr-review-health → actions-fleet-monitor

* fix(agents-md): clarify dev-lead.yml vs dev-lead-reusable.yml scope

dev-lead.yml in .github-private runs inline steps (not a caller stub).
Behavior changes for this repo go to dev-lead.yml directly; changes that
affect all org repos via the cross-repo reusable go to dev-lead-reusable.yml.

Addresses Copilot review on PR #194.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): rate-limit detection, separate status, and retry cron

Closes #192. Implements all phases from the revised plan.

**Phase 0 — fix run_writer stdout capture (prerequisite)**
- engine.sh: capture stdout via `tee` to a tempfile so `is_rate_limited`
  can inspect the output; old code read `/tmp/dev-lead-writer-stderr`
  which was never written (claude --print outputs to stdout, not stderr).
  Fallback engines were never tried in practice.
- engine.sh: add `parse_reset_time` to extract ISO timestamp from
  `resets H:MMpm (UTC)` in engine output and write it to
  `/tmp/dev-lead-rate-limit-reset` for callers to embed in markers.

**Phase 1 — separate status=rate-limited from status=failed**
- fix-ci.sh: detect engine exit 2 → post `status=rate-limited` (not
  `status=failed`); embed parsed reset time in marker body; exit 2.
- fix-ci.sh: fix `check_idempotency` to treat `status=rate-limited`
  as retriable — only block on terminal statuses (applied, failed,
  no-changes). Previously, rate-limited markers blocked all retries.
- fix-ci.sh: `count_recent_failures` already filtered to `status=failed`;
  add explicit comment confirming rate-limited markers are excluded.
- fix-ci.sh: add `has_rate_limited_marker` dedup check so the same SHA
  never accumulates more than one rate-limited comment.
- fix-reviews.sh: on engine exit 2 for all five intent types
  (fix-reviews, fix-bot-comment, human, human-pr, rebase), post a
  `status=rate-limited` marker with embedded reset time and exit 2.
- fix-reviews.sh: for `human` and `human-pr` intents, also post a
  user-visible acknowledgment comment so users know their request was
  received and will be retried.

**Phase 2 — scheduled retry cron for fix-ci**
- dev-lead-retry.sh: new script scans all open PRs across TARGET_ORG
  for `status=rate-limited` markers on current HEAD SHA; skips PRs
  whose reset time is still in the future; staggered dispatches (30s
  between repos) to prevent cascading org-level rate-limit hits.
- dev-lead-retry.yml: new workflow — scheduled every 2 hours (dual
  offsets to work around GitHub scheduler skew), plus workflow_dispatch.

**Phase 3 — retry for fix-reviews intents (same cron)**
- dev-lead-retry.sh already handles all five fix-reviews intent types
  (fix-reviews, fix-bot-comment, human, human-pr, rebase) via
  `dev-lead-reviews-retry` dispatch events.
- dev-lead-intent.sh: add `dev-lead-reviews-retry` dispatch type routing
  to the intent classifier — maps intent_type from payload to the correct
  existing fix-reviews step.
- dev-lead.yml: add `dev-lead-reviews-retry` to repository_dispatch types.
- dev-lead.yml: unify concurrency group for all dispatch types to
  `dev-lead-pr-{pr_number}` to avoid per-type slot fragmentation.

**Tests**
- test_engine_writer.bats: 8 new tests covering rate-limit stdout detection,
  exit-2 mapping, fallback exhaustion, reset time parsing.
- test_fix_ci.bats: 5 new tests covering rate-limited status, exhaustion
  exclusion, idempotency pass-through, and dedup.
- test_fix_reviews.bats: 5 new tests covering all intent types + human ack.
- test_intent_ci.bats: 4 new tests for dev-lead-reviews-retry dispatch routing.
- e2e/scenarios/07-rate-limit-retry.sh: new E2E scenario covering all parts.
- 3 new event fixtures for repository_dispatch_reviews_retry_* payloads.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): address review comments on #196

Addresses all 18 review threads from Copilot, Codex, and Gemini.

**P1 bugs fixed:**
- dev-lead-retry.sh: all log echo calls in scan_pr_for_rate_limits and
  dispatch_* functions now write to stderr; only the final count is on
  stdout. Previously, log lines polluted the command substitution,
  causing arithmetic expansion to fail and aborting the scan on the
  first retry candidate.
- dev-lead-retry.yml: permissions: contents was read-only; creating
  repository_dispatch events requires contents: write.

**Correctness fixes:**
- dev-lead-retry.sh: restrict automated retries to intents whose context
  can be reconstructed at runtime (fix-reviews, human-pr, rebase). human
  and fix-bot-comment require USER_INSTRUCTION/COMMENT_BODY from the
  original event which cannot be re-fetched; they are explicitly excluded.
- dev-lead-fix-reviews.sh: resolve HEAD_SHA from the PR API when not
  provided by the triggering event (issue_comment intents carry no SHA).
  Without this, rate-limited markers had no sha= field and were
  invisible to the retry scanner.
- dev-lead-fix-reviews.sh: write terminal status=applied marker after
  successful fix-reviews, human-pr, rebase runs. Prevents the retry
  cron from re-dispatching the same intent on every subsequent tick when
  the SHA hasn't changed.
- dev-lead-retry.sh: check for reviews terminal marker before dispatching
  a retry (mirrors the existing fix-ci terminal-marker check).
- dev-lead-retry.sh: look up current check-run details (details_url, id)
  at dispatch time via the commits check-runs API, so retried fix-ci runs
  have full failure logs and annotations rather than empty fields.
- dev-lead-fix-ci.sh: embed check= field in the rate-limited marker so
  the retry cron knows which check run to look up.
- dev-lead-fix-reviews.sh: human intent now posts a "please re-mention
  @dev-lead" ack instead of "I'll retry automatically" since it will NOT
  be retried automatically. human-pr keeps the auto-retry ack (correct).

**Pagination (Copilot + Gemini):**
- dev-lead-fix-ci.sh: add --paginate to check_idempotency, count_recent_
  failures, has_rate_limited_marker.
- dev-lead-fix-reviews.sh: add --paginate to has_reviews_rate_limited_marker.
- dev-lead-retry.sh: add --paginate to PR comments and PR list fetches.

**Other improvements:**
- dev-lead-retry.yml: simplify to single cron schedule (15 */2 * * *);
  remove BOT_USER env var (unused in the script).
- dev-lead-retry.sh: raise repo list limit 200 → 1000; add hard-error
  when list_repos_for_org returns empty (surfaces token permission issues
  rather than silently scanning 0 repos).
- dev-lead-intent.sh: use jq -nc (compact, null-input) for context JSON
  construction — safe against values with special characters.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(pr-review): handle Gemini trust-mode failure and properly route engine fallbacks (#217)

* fix(pr-review): handle Gemini trust-mode failure and properly route engine fallbacks

Closes #208

* fix(ci): install gemini CLI and set trust mode for dev-lead agents

---------

Co-authored-by: Gemini CLI <gemini-cli@example.com>

* fix: resolve YAML syntax error in dev-lead-reusable workflow

* fix: address automated feedback from PR 217 (#219)

This addresses missed feedback from CodeRabbit (removing syntax errors in YAML) and gemini-code-assist (improving string concatenation in validate-engines.sh), and adds assertion output checks to tests.

Co-authored-by: Gemini CLI <gemini-cli@example.com>

* chore: remove Phase 7 shadow period comment from dev-lead.yml

* feat: skip PRs with CHANGES_REQUESTED review (+ FORCE_REVIEW and stale-review guards)

* feat: skip PRs with CHANGES_REQUESTED review decision

Adds an early-exit check alongside the existing CI guards so the cascade
skips any PR where a human reviewer has already requested changes.
Reviewing those PRs before the author responds wastes tokens and creates
confusing parallel feedback threads.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix: honor FORCE_REVIEW and skip only stale-free CHANGES_REQUESTED reviews

Two issues addressed from Codex P2 feedback:

1. FORCE_REVIEW bypass: mention-triggered and force_review=true runs now
   bypass the CHANGES_REQUESTED skip, so authors can explicitly request a
   re-review after addressing feedback.

2. Stale review guard: only skip when a CHANGES_REQUESTED review targets
   the current head SHA. On repos without automatic stale-review dismissal,
   reviewDecision stays CHANGES_REQUESTED after new commits — the cascade
   now re-engages when the author has pushed new work.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): resolve context loss, missing checkout, and push logic (#223)

* feat: skip PRs with CHANGES_REQUESTED review decision

Adds an early-exit check alongside the existing CI guards so the cascade
skips any PR where a human reviewer has already requested changes.
Reviewing those PRs before the author responds wastes tokens and creates
confusing parallel feedback threads.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix: honor FORCE_REVIEW and skip only stale-free CHANGES_REQUESTED reviews

Two issues addressed from Codex P2 feedback:

1. FORCE_REVIEW bypass: mention-triggered and force_review=true runs now
   bypass the CHANGES_REQUESTED skip, so authors can explicitly request a
   re-review after addressing feedback.

2. Stale review guard: only skip when a CHANGES_REQUESTED review targets
   the current head SHA. On repos without automatic stale-review dismissal,
   reviewDecision stays CHANGES_REQUESTED after new commits — the cascade
   now re-engages when the author has pushed new work.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): resolve context loss, missing checkout, and push logic

- dev-lead-intent.sh: include actor and body in INTENT_CONTEXT for comments
- dev-lead.yml: parse and pass INTENT_ACTOR and INTENT_COMMENT_BODY to agent
- dev-lead-fix-reviews.sh: perform PR checkout, git push, and post summary comments

* fix(dev-lead): exit with 0 on no-changes in fix-reviews and human-pr

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* test: bypass CI check

* fix(pr-review): remove CI bypass hack

* fix(dev-lead): use heredoc for multiline environment variables (#224)

* fix(dev-lead): use heredoc for multiline environment variables

* fix(dev-lead): ensure all engines are installed and context is consistent

* fix(dev-lead): resolve stale model name in engine fallbacks

* fix(dev-lead): improve fallback reliability and script quality

- engine.sh: resolve stale model names in fallback loop; use -latest Gemini aliases
- dev-lead-fix-reviews.sh: remove unused code and fix PR_URL export

* fix(dev-lead): resolve Gemini model names and shell lint warnings

* security(dev-lead): use random heredoc delimiter to prevent inject…
don-petry added a commit that referenced this pull request Aug 7, 2026
… trigger for the Class-2 timer changes, before narrowing them (#1420)

* add: workflow to fix stuck PRs using bot account token

Allows running the cleanup script via workflow_dispatch with access to
GH_PAT (bot account token) from repo secrets.

* fix: pass GH_PAT to checkout action so workflow uses bot account

* debug: simplify account check, add authentication debug output

* docs: add comprehensive petry-review-bot setup instructions

* docs: add GitHub App setup guide (recommended approach)

GitHub App is the secure, recommended way to automate PR reviews:
- Fine-grained permissions
- JWT tokens that auto-expire
- No human account needed
- Better audit trail
- GitHub's recommended approach

Replaces the bot user account approach with a more secure alternative.

* docs: add comprehensive GitHub App setup documentation

- SETUP.md: Quick reference guide with configuration and troubleshooting
- IMPLEMENTATION.md: Technical deep dive on architecture and design decisions
- DOCUMENTATION.md: Index of all documentation files
- Updated README.md with status and quick links
- Updated GITHUB_APP_SETUP.md with implementation notes and actual app ID
- Workflows use GitHub App token generation instead of static PATs

Covers the transition from bot user account to GitHub App authentication for improved security and maintainability.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: silence auth check in fix-stuck-prs when using GitHub App token

GitHub App tokens don't have user scope, so 'gh api user' returns 403.
This is fine - the script still works for PR operations. Suppress the error
so the script completes successfully with app-token fallback label.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: use explicit author instead of @me in stuck PR search

GitHub App tokens don't have user identity, so @me search doesn't work.
Use explicit 'don-petry' author instead to find PRs to fix.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: avoid subshell in while loop to preserve variable state

Using pipe in while loop created subshell where PROBLEM_PRS and FIXED_PRS
counters were incremented but changes didn't persist to parent shell.
Fixed by using process substitution for input instead of pipe.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* docs: add comprehensive status report for GitHub App setup

- Overview of completed work
- GitHub App authentication details
- 24 stuck PRs successfully fixed with approval reviews
- Architectural decisions and rationale
- Configuration guide and usage examples
- Known limitations and troubleshooting

System is now fully operational with GitHub App token authentication,
comprehensive documentation, and all infrastructure in place.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* docs: document stuck PR cleanup status and script fixes

Added documentation for:
- GitHub App token compatibility fixes in fix-stuck-prs.sh
  * Changed @me to explicit author (app tokens have no identity)
  * Fixed subshell variable scope (process substitution vs pipe)
  * Silenced expected 403 auth check error
- Current status: 24 PRs have approvals but remain OPEN
  * Auto-merge failed due to missing GitHub App permission
  * Approvals satisfy branch protection requirement
  * Next: expand permissions or manually merge

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: update list-prs.sh to search all repos by owner instead of @me

GitHub App tokens don't have user identity, so @me/@review-requested searches
fail. Changed to enumerate all repos in don-petry and petry-projects, then
search for open PRs within each repo. This covers the full scope:
- All open PRs in personal don-petry repos
- All open PRs in petry-projects org repos

Resolves 6 consecutive workflow failures due to zero PRs being enumerated.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* docs: add investigation report for PR review agent workflow failures

* fix: move env section before steps in workflow to fix YAML structure

GitHub Actions requires env section to come before steps in job definition.
Moving env definition up and setting GH_TOKEN only in steps that need it.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* script: add backfill-approvals to retroactively apply real approvals

Converts PRs with agent approval comments (but no real GitHub approvals)
to have actual APPROVED reviews. Needed for PRs reviewed before the
gh pr review --approve fix was applied.

Usage:
  scripts/backfill-approvals.sh          # dry-run (preview changes)
  scripts/backfill-approvals.sh false    # apply real approvals

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* feat: add backfill-approvals workflow to run as GitHub App bot

Runs backfill-approvals.sh via GitHub Actions so the approvals are posted
by the bot identity, not the PR author — GitHub rejects self-approvals.

Also fixes subshell counter bug in the script (piped while loop lost variable
state; switched to process substitution).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: convert inner while loop to process substitution to preserve counters

Both the outer repo loop and inner PR loop were piped subshells, causing
all counter increments (approved/skipped/failed) to be lost. The summary
always showed 0. Fixed by using < <(...) process substitution for both loops.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* car-hunt: add VIN-deep-dive flow, printable checklists, location-tier ranking

- Step 5.5 pre-test-drive flow: NHTSA VIN decode + per-VIN unrepaired-recall
  automation via Chrome MCP (Honda LWC owner portal documented as not
  automatable; NHTSA web form works).
- §F printable test-drive checklist generator (reportlab/Platypus PDF) with
  cold-start, model-specific red flags, negotiation table, walk-away rules.
- §G fraud / paperwork verification checklist (vehicle-agnostic, reusable):
  walk-away triggers, VIN three-location match, curbstoning, odometer fraud,
  bill of sale, title transfer, payment protection, stolen-vehicle, title-
  jumping, after-purchase steps.
- Step 5.25 head-to-head comparison template with information-asymmetry rule.
- Step 3 Location Quality Tier scoring (Birmingham AL metro table A/B/C/D)
  with adj-CPM modifier; sheet schema gains Adj.CPM + Tier columns.
- Hard-disqualification rules added: non-running engine keywords, mileage
  inconsistency, mandatory description scrape.
- Drive MCP overwrite limitation documented; sheet ID now read from memory
  rather than hardcoded.
- FB Marketplace operational facts: Birmingham AL city ID 107739635926718,
  /search? vs /vehicles? query handling, React-controlled inline composer,
  send-button selector, seller-name extraction regex.
- scheduled-tasks/used-car-search-{morning,afternoon}: tier A/B priority
  flag, sheet ID read from memory.
- Reference PDF generator scripts checked in for reuse.

* fix: re-approve PRs where approval predates last commit (stale approval)

Instead of skipping PRs that already have any APPROVED review, skip only
those where reviewDecision is not REVIEW_REQUIRED. This handles PRs where
a bot approval was posted before a new commit was pushed — GitHub's ruleset
engine treats those approvals as stale even with dismiss_stale_reviews=false.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: robustly extract JSON verdict from mixed claude --print output (#17)

Claude's --print mode can prefix the JSON verdict with conversational
preamble text, causing jq to fail with parse errors. Add
extract_verdict_json to engine.sh and wire all three cascade action
call sites in review-one-pr.sh to use it.

Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: extract_verdict_json — check dest file first (agent Bash-write style)

The cascade-action agent writes its verdict JSON to \$OUTPUT_FILE via a
Bash tool call, then prints a text summary to stdout. The previous fix
only scanned stdout (the .raw file), which contained no JSON. The agent-
written file at \$dest (= \$OUTPUT_FILE) was already correct — just not
checked. Now check \$dest first before falling back to stdout scan.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Enforce MAX_REVIEW_CYCLES cap before running the cascade (#18)

* fix: enforce MAX_REVIEW_CYCLES cap with human-escalation marker

Previously MAX_REVIEW_CYCLES only gated AI delegation in
post-pr-review.sh — the cascade itself ran on every cron tick
regardless of how many cycles had accumulated. Real-world result:
a PR could rack up 9+ review cycles (we observed exactly that on
ContentTwin#100) before any cap took effect.

Add a pre-cascade check in review-one-pr.sh: when the count of
existing `<!-- pr-review-agent v1 sha=... -->` markers is at or
above MAX_REVIEW_CYCLES (default 3), post one escalation comment
marked `<!-- pr-review-agent escalation -->`, label
needs-human-review, request don-petry, and exit 100 (skip
sentinel — doesn't burn the MAX_PRS budget). The escalation
marker doubles as the no-spam guard: subsequent runs detect it
and exit 100 immediately.

Also reuse a single `gh pr view` for both the cycle count and the
escalation-marker check.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* review feedback: surface escalation-comment failures, drop dead unset

- Drop `2>/dev/null || true` on `gh pr comment` so a failed escalation
  post is visible in the workflow log instead of silently leaving the
  PR with no marker (which would re-trigger the cap path next tick).
- Reword the cycle-count comment from "AI delegation loops" to "review
  loops" — the cap also catches cascade-only loops where every cycle
  approves and a new commit lands before merge.
- Remove the redundant `unset PR_BODIES` — never exported, no leakage.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>

* Reliability hardening: session circuit breaker, timeouts, retry, dedup (#20)

* feat: session circuit breaker, per-tier timeouts, retry, and triage hard-fail

Reliability hardening for the PR review agent.

1. Session circuit breaker (.github/workflows/pr-review.yml): on any
   non-zero, non-100 exit from review-one-pr.sh (general failure or rate
   limit on the fallback engine), break the per-PR loop, log a clear
   error annotation naming the failing PR and reason, and exit the step
   with code 1 so the run shows red. Prevents one systemic problem from
   silently burning the entire candidate pool.

2. Per-tier timeouts (scripts/engine.sh): triage/deep/audit/action/duck
   each get their own bounded timeout (180/600/600/300/300s defaults,
   env-overridable). Previously only the duck had a timeout — a hung
   tier could burn the whole 60min job budget.

3. Retry-with-backoff on transient errors (scripts/engine.sh): triage
   retries once on 124/137/143 (timeout / signal kill) since its caller
   captures stdout via $(...) so retries are safe. Deliberately NOT
   applied to run_agentic/run_duck where stdout is redirected to a file
   — a retry there would corrupt the partial first-attempt output.

4. Triage non-JSON now hard-fails (scripts/review-one-pr.sh): replaces
   the silent fallback that synthesized a fake "escalate=MEDIUM" verdict
   and proceeded to deep review. With the new circuit breaker, loud
   failure is the right call — masking a broken triage was burning
   tokens on every PR while the workflow looked healthy.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix: stop stacking duplicate agent reviews on the same PR

Two bugs were causing the agent to leave multiple comments on the same PR.
Together they produced 10 stacked APPROVED reviews on petry-projects/ContentTwin#100.

Bug A — idempotency check is order-dependent (review-one-pr.sh):
The previous marker-discovery code did:
  ((.reviews // []) + (.comments // [])) | .[].body | grep marker | tail -1
This relies on the array concatenation order, not chronological order. When
old agent comments existed alongside newer agent reviews, tail -1 picked
the comment-array marker (older) over the review-array marker (newer),
causing the script to think the head SHA hadn't been reviewed and re-run.
Replaced with a single jq pipeline that tags each item with submittedAt /
createdAt, sorts by timestamp, and takes the actual most-recent marker.

Bug B — no cleanup of prior agent items (post-pr-review.sh):
After successfully posting a new review/comment, prior agent items were
left in place, accumulating forever. Added mark_prior_agent_items_obsolete
which, after a successful post:
  - dismisses prior APPROVED/COMMENTED/CHANGES_REQUESTED agent reviews via
    the GitHub dismissal API (UI shows them struck-through as Dismissed)
  - edits prior agent comments to wrap their body in a collapsed <details>
    block with a "Superseded by re-review at <SHA>" summary, plus a
    `<!-- pr-review-agent superseded -->` sentinel for idempotency

All cleanup API calls are best-effort — failures don't break the workflow.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(cleanup): warn on API failures, preserve global newest, file-stage JSON

Three fixes to mark_prior_agent_items_obsolete from the review of PR #20:

1. ::warning:: annotations on every cleanup API failure (review/comment
   list-fetch, individual review dismissal, individual comment fetch+edit).
   Previously these were silenced with `|| true`, so a permissions change
   on the dismissal endpoint would let duplicates stack indefinitely with
   no signal in the Actions UI. Cleanup is still non-fatal — the new post
   has already landed — but failures are now visible.

2. Preserve the globally-latest agent item across BOTH categories, not the
   newest of each category separately. The earlier code split reviews and
   comments and applied `[:-1]` to each, which left a stale fix-request
   comment in place when the new post was a review (or vice versa). The
   one-off cleanup of ContentTwin#100 hit exactly this case: 12 stacked
   reviews collapsed to 1, but a stale comment from 2026-04-25 (SHA
   cd9132d6) was preserved as "newest comment" even though the latest
   review at SHA 3af8c8ee was newer overall. Now: compute the max
   timestamp across both feeds, exclude items at that timestamp.

3. Stage API responses to disk (`mktemp` + `jq <file>`) instead of routing
   through `--argjson "$var"`. The old approach broke on rare unescaped
   control chars in user-authored comment bodies (jq refused to parse the
   resulting shell-vared JSON). File-based input sidesteps the shell
   pipeline entirely.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>

* Remove car-hunt skill and scheduled tasks (moved to don-petry/don-petry) (#21)

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: apply Copilot fallback hardening and reuse GH_PAT for Copilot auth

- Use secrets.GH_PAT (existing personal account token with Copilot
  subscription) instead of a separate COPILOT_GITHUB_TOKEN secret
- Pin actions/create-github-app-token to SHA (v3.1.1) for supply chain safety
- Add post-install verification for gh-copilot extension with warning on failure
- Guard Copilot fallback path: skip PR gracefully if extension is not installed

Addresses issue #24 workflow fixes (1-3) that could not land via PR #25
due to GitHub App lacking workflows permission.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: switch Copilot engine to gh built-in and fix app-id deprecation

- Remove gh extension install (github/gh-copilot conflicts with built-in alias)
- Replace with gh copilot --version check using COPILOT_GITHUB_TOKEN (GH_PAT)
- Update all engine.sh copilot invocations from bare `copilot` binary to
  `gh copilot suggest --target shell` with GH_TOKEN overridden to GH_PAT
  so the user token (with Copilot subscription) is used, not the App token
- Fix actions/create-github-app-token: app-id → client-id (same secret value,
  just the renamed input in v3.x)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: harden Copilot fallback path and remove stale worktrees (#25)

- gh-copilot install: add --force flag and post-install verification
  warning so silent failures due to built-in alias conflict are visible
  in workflow logs instead of being silently swallowed by `|| true`
- fallback pre-flight check: verify `gh extension list | grep copilot`
  before switching to Copilot engine; if unavailable, skip the PR and
  continue the batch rather than session-aborting and dropping all
  remaining candidates (fixes the 27-PR drop on run #503)
- pin actions/create-github-app-token to SHA for v3.1.1 (Node.js 24
  compatible) to prevent hard break on 2026-06-02 when GitHub forces
  Node.js 24 as default runtime
- remove 6 stale Claude Code worktrees from git tracking; add
  .gitignore entry to prevent future worktrees from being committed
  (eliminates exit-128 warning on every actions/checkout post-job sweep)

Closes #24

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: petry-projects-pr-review-agent[bot] <petry-projects-pr-review-agent[bot]@users.noreply.github.com>

* ci: add pre-flight dedup check to prevent duplicate claude-issue PRs (#26)

Before invoking Claude on a labeled issue, a new shell step queries
for any open PR with a branch matching claude/issue-NNN-* (or a body
containing "Closes #NNN"). If one is found it posts a comment on the
issue pointing to the existing PR and skips the Claude step entirely.
A secondary prompt instruction tells Claude to check first and push to
the existing branch rather than opening a new PR.

Motivated by issue petry-projects/google-app-scripts#171, where the
claude label was re-applied three times and each run created a fresh
PR unaware of the prior attempts.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor: migrate to machine user PAT auth (closes #27) (#28)

* refactor: migrate from GitHub App to machine user PAT auth

Closes #27

GitHub Apps cannot be listed in CODEOWNERS, blocking PRs in repos with
require_code_owner_review: true. Switch all workflows to use a machine
user account's fine-grained PAT (DON_PETRY_BOT_GH_PAT secret), which can
join an org team listed in CODEOWNERS.

Workflows: removed actions/create-github-app-token steps in pr-review,
fix-stuck-prs, backfill-approvals, and daily-pr-review-health. All now
use secrets.DON_PETRY_BOT_GH_PAT directly.

Docs: renamed GITHUB_APP_SETUP.md to MACHINE_USER_SETUP.md with full
rewrite covering account creation, CODEOWNERS config, PAT generation,
and rotation. Updated auth sections in IMPLEMENTATION.md, SETUP.md,
STATUS.md, DOCUMENTATION.md, README.md.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* refactor: switch to org-scoped PAT secret DON_PETRY_BOT_PETRY_PROJECT_PAT

Replace DON_PETRY_BOT_GH_PAT with DON_PETRY_BOT_PETRY_PROJECT_PAT — the
new fine-grained PAT scoped to the petry-projects org (resource owner =
petry-projects). The previous PAT was scoped to the donpetry-bot personal
namespace and had no repository access.

The old DON_PETRY_BOT_GH_PAT secret is retained for any future use against
don-petry's personal repos.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* chore: ignore .claude/scheduled_tasks.lock state file

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>

* refactor: merge approval repair scripts into single automation

- Consolidate backfill-approvals.sh and fix-stuck-prs.sh into repair-pr-approvals.sh
- Iterate all repos in both orgs instead of assuming author
- Verify no existing APPROVED review before posting
- Copy original agent comment as review body
- Enable auto-merge when posting approval if needed
- Single workflow with 30-min timeout

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: add @mention trigger for on-demand PR reviews (#30)

Adds repository_dispatch support so commenting @petry-review-bot on any
PR fires an immediate review without waiting for the hourly schedule.

- pr-review.yml: new `repository_dispatch` trigger (type: pr-review-mention),
  per-PR concurrency group for mention runs, FORCE_REVIEW and DRY_RUN
  env vars that handle both workflow_dispatch and repository_dispatch paths
- review-one-pr.sh: FORCE_REVIEW=true bypasses idempotency so a mention
  always runs a fresh cascade even if the head SHA hasn't changed
- templates/mention-listener.yml: deploy to petry-projects/.github;
  listens for @petry-review-bot, validates commenter trust, posts ack,
  sends repository_dispatch (requires Contents:write, not Actions:write)

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: harden rebase, review dismissal, and health log diagnostics (#40)

Addresses 4 code-actionable recommendations from health check report #33:

- [CRITICAL] Make rebase/branch-update non-fatal — 403/504 during rebase
  emits ::warning:: instead of aborting the batch session (#34)
- [MEDIUM] Add retry with exponential backoff (3 attempts) for transient
  5xx on update-branch API; break immediately on 4xx (#37)
- [MEDIUM] Guard review dismissal with state re-check before calling
  dismiss API, preventing 422s from race conditions (#38)
- [LOW] Surface missing log warnings in health check script (#39)
- Fix: skip auto-merge when branch is still BEHIND after failed rebase

Closes #34, closes #37, closes #38, closes #39

* refactor: migrate to org-wide .github-private convention (#41)

* refactor: parametrize hardcoded identity values for org migration

Replace all hardcoded references to don-petry, petry-review-bot, and
don-petry/pr-review-agent with environment variables that default to
the current values. This allows the agent to be configured for different
orgs/users via repo variables.

Changes:
- Scripts use $REVIEWER_USER, $TARGET_ORG, $BOT_USER, $AGENT_REPO
- Prompts no longer reference specific GitHub usernames
- Workflows use org-level GH_PAT_WORKFLOWS secret instead of
  repo-level DON_PETRY_BOT_PETRY_PROJECT_PAT
- Health check uses context.repo.owner/repo for issue creation
- Variables are set in workflow env block with defaults

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add Copilot custom agent profiles for org-wide use

Create /agents/ directory with three agent profiles:
- pr-reviewer: Multi-tier cascading PR review
- feature-ideator: Feature idea generation and prioritization
- compliance-auditor: Org standards compliance checking

These are Copilot custom agent profiles that become available
org-wide in the .github-private repo convention.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Squashed 'frameworks/bmad-method/' content from commit e36f219c

git-subtree-dir: frameworks/bmad-method
git-subtree-split: e36f219c81b6010d4aae423ba12f49edb5b6e31a

* Squashed 'frameworks/spec-kit/' content from commit 11f49ebf

git-subtree-dir: frameworks/spec-kit
git-subtree-split: 11f49ebfb2f6af55345cb4bd9a7906acd211e56f

* Squashed 'frameworks/gsd/' content from commit 304c1a13

git-subtree-dir: frameworks/gsd
git-subtree-split: 304c1a1302564c45af252bbba4bcc5350e7dac3a

* docs: update README for .github-private org infrastructure role

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* ci: add dependabot-automerge.yml workflow (#73)

Adds the required dependabot-automerge.yml workflow from the org
standard template (petry-projects/.github/standards/workflows/).
This is a thin caller stub that delegates to the org-level reusable
workflow.

Closes #48

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Don Petry <don-petry@users.noreply.github.com>

* fix(pr-review): unblock queue starvation from self-authored PRs (#96) (#97)

* fix(pr-review): unblock queue starvation from self-authored PRs (#96)

A self-authored PR sorted first in the candidate list and triggered
"Can not approve your own pull request" — which the session-fatal
abort treated like an engine error, skipping all 28 remaining PRs
on every run.

- list-prs.sh: filter out PRs authored by REVIEWER_USER at enumeration
- post-pr-review.sh: catch the GraphQL self-approval error and exit 100
  (no-op) instead of 1, so a stray self-PR can no longer abort the batch
- engine.sh: gh copilot renamed --target to --agent; fix the rubber-duck
  invocations so tier-2 cross-engine review works again
- review-one-pr.sh: stop appending a duplicate "0" to REVIEW_CYCLE when
  grep -c finds no markers under set -o pipefail (was breaking the
  cycle-cap integer comparison)

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

* fix(pr-review): authenticate as bot, harden enumeration, address review

- Workflow now runs as AGENT_USER (default don-petry-bot), distinct from
  REVIEWER_USER (the human, don-petry). The self-approval rejection that
  caused #96 came from the agent and the human sharing one identity.
- list-prs.sh filters self-authored PRs against AGENT_USER, and validates
  AGENT_USER against the GitHub username charset before interpolating into
  the jq filter (Copilot review feedback).
- review-one-pr.sh uses printf '%s\n' instead of echo for PR_BODIES, since
  PR body content is user-authored and could begin with -n/-e or contain
  backslash escapes (Copilot review feedback).
- AGENT.md guideline updated to reflect that self-authored PRs are
  intentionally excluded (CodeRabbit review feedback).

Operator note: rotate the GH_PAT_WORKFLOWS secret to a token owned by
don-petry-bot (with read:org added) for the bot-account behavior to take
effect at runtime.

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

* refactor(pr-review): collapse to single BOT_USER, route escalations via CODEOWNERS

Drop AGENT_USER and REVIEWER_USER. The workflow now has one identity:
BOT_USER (default don-petry-bot), which both owns the repos to scan and
gets filtered out as the self-approval blocker. Human escalation no
longer hard-codes a single reviewer; instead, scripts/request-codeowners-
review.sh parses CODEOWNERS in the PR's repo and requests review from
every @user / @org/team mention.

- list-prs.sh: BOT_USER for both gh-repo-list and self-author filter
- review-one-pr.sh, post-pr-review.sh: replace --user "$REVIEWER_USER"
  with the CODEOWNERS helper
- repair-pr-approvals.sh: same gh-repo-list switch
- pr-review.yml: drop AGENT_USER/REVIEWER_USER, set BOT_USER default
  to don-petry-bot
- AGENT.md: updated guideline to describe single-identity model

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

* refactor(pr-review): extract batch loop, hoist env, drop redundant step

Workflow YAML drops from 231 to 111 lines by moving the per-PR review
loop into scripts/review-batch.sh. The script can be syntax-checked and
exercised locally; the workflow now just wires env vars and dispatches.

- Hoist GH_TOKEN, MAX_PRS, CANDIDATE_LIMIT to job-level env (were
  repeated on individual steps).
- Inline `gh auth status` into the install step; remove the standalone
  Verify auth step (its only output was a one-line auth dump).
- Drop the `if: steps.list.outputs.count != '0'` guard and the step's
  `id`/output: review-batch.sh handles empty input as a no-op.
- Collapse the duplicated summary-string branch in the review loop into
  a single template with a conditional fragment.

No behavior change.

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

* perf(pr-review): cache claude-code CLI install across runs

`npm install -g @anthropic-ai/claude-code` ran on every workflow start
(~30s). Switch to a per-user npm prefix (~/.npm-global) and cache that
directory via actions/cache, keyed on CLAUDE_CODE_VERSION + runner OS.

A `command -v claude` guard makes the install a no-op on cache hit, so
the only cost on subsequent runs is the cache restore.

CLAUDE_CODE_VERSION defaults to 'latest' (cache persists until manually
flushed); set the repo variable to pin a specific version for fully
reproducible caching.

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

---------

Co-authored-by: Claude <noreply@anthropic.com>

* chore: rename GH_PAT_WORKFLOWS secret to DON_PETRY_BOT_GH_PAT (#99)

The previous name was generic and didn't tell you which account the PAT
belonged to. The new name makes the binding explicit: this secret is the
PAT owned by don-petry-bot, used as BOT_USER throughout the PR-review
workflows.

Operator follow-up before this can be merged:
- Add a new repo secret DON_PETRY_BOT_GH_PAT containing the bot's PAT
  (with repo, workflow, and read:org scopes).
- After merge, the old GH_PAT_WORKFLOWS secret can be deleted.

Affected workflows:
- pr-review.yml (1 use)
- claude.yml (4 uses, with || github.token fallback)
- daily-pr-review-health.yml (1 use)
- repair-pr-approvals.yml (1 use)

scripts/pr_review_health.sh's error message is updated to point at the
new name as well.

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

Co-authored-by: Claude <noreply@anthropic.com>

* chore: remove frameworks directory (#101)

Co-authored-by: Gemini CLI <gemini-cli@example.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore all erroneously deleted files (items 4-34) (#104)

* revert: restore .github/workflows/daily-pr-review-health.yml

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore .github/workflows/repair-pr-approvals.yml

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore agents/pr-reviewer.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/cascade-action.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/deep-review.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/rubber-duck.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/security-audit.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/shared.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/single-review.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/synthesize-duck.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/synthesize.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/triage.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/engine.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/list-prs.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/post-pr-review.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/pr_review_health.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/repair-pr-approvals.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/request-codeowners-review.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/review-batch.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/review-one-pr.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore templates/mention-listener.yml

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore AGENT.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore BOT_SETUP.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore DOCUMENTATION.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore IMPLEMENTATION.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore MACHINE_USER_SETUP.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore PR_REVIEW_AGENT_FAILURE_REPORT.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore PR_REVIEW_FAILURE_INVESTIGATION.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore SETUP.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore STATUS.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore README.md to pre-change state

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore .github/workflows/daily-pr-review-health.yml

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat: add Gemini Pro support and optimize PR review fallback chain (#102)

* Merge main and resolve conflicts

* Enhance pr-review workflow: add Gemini support and refactor fallback logic

* Address Copilot review comments: tighten regex, fix fallback summary, and align docs

* docs: move agent documentation to docs/ folder and rename files

* docs: align secret names and fix casing in index

* docs: organize pr-review-agent documentation under dedicated folder

* Address final PR comments: tighten regex, add preflight checks, fix bot names, and cleanup docs

---------

Co-authored-by: Gemini CLI <gemini-cli@example.com>

* fix(pr-review): gate rate-limit detection on non-zero exit code

Broad patterns like `plan.*limit` and `claude.*usage` could match content
in a successful triage summary, triggering a false-positive engine fallback.
Guard the check with TRIAGE_RC != 0 so it only fires when the provider
command actually failed.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(claude): sync inlined workflow with org standard (#109)

* fix(claude): sync inlined workflow with org standard

Brings the inlined claude.yml up to parity with
petry-projects/.github/.github/workflows/claude-code-reusable.yml.

Changes:
1. Bot allow list for pull_request_review_comment
   Add coderabbitai[bot], Copilot, copilot-pull-request-reviewer[bot],
   and gemini-code-assist[bot] alongside the existing OWNER/MEMBER/
   COLLABORATOR check. These bots have author_association 'NONE' so
   their review comments were always skipped.

2. check_run trigger + claude-ci-fix job
   Port the CI failure auto-fix feature from the reusable. When a check
   fails on a PR, Claude diagnoses and fixes it automatically.
   Adapted to use DON_PETRY_BOT_GH_PAT (this repo's secret name).

3. claude-code-action SHA bump: v1.0.89 → v1.0.119
   Update both the claude and claude-issue jobs.

4. paths-ignore on pull_request trigger
   Prevents the workflow from firing on PRs that only change
   claude.yml itself, avoiding the Anthropic OIDC validation
   failure (workflow file must match default branch at token exchange).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(claude): guard bot allow list against fork PRs

Same fix as petry-projects/.github PR #238 — add same-repo guard
for bot-triggered pull_request_review_comment runs.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(claude): address ci-fix self-loop and fork PR security

- Replace Claude Code name prefix check with explicit job name list to
  correctly prevent self-loops (check_run names for inlined workflows
  are bare job names, not workflow-prefixed)
- Add fork PR trust gate in Resolve PR number step: verify head repo
  matches target repo before running Claude with privileged credentials

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* ci: replace inlined claude.yml with standard thin-caller stub

The inlined version embedded all three jobs (claude, claude-ci-fix,
claude-issue) directly and threaded DON_PETRY_BOT_GH_PAT through
checkout tokens and github_token, causing the bot's PAT to author
PRs and comments instead of github-actions[bot].

Replace with the standard thin caller that delegates to the org-level
reusable workflow (claude-code-reusable.yml@v1) via secrets: inherit,
which is the correct pattern per:
petry-projects/.github/standards/workflows/claude.yml

* fix: replace gh copilot suggest with GitHub Models REST API (#151)

* fix: replace gh copilot suggest with GitHub Models REST API (#147)

The `gh copilot suggest -p "$(cat <file>)"` invocation failed with
"Invalid command format" because:
1. The `-p` flag is not valid syntax in modern `gh copilot` built-in versions.
2. `gh copilot suggest` is a shell-command suggestion tool; it does not
   accept large PR prompts or return structured JSON responses.
3. Passing a multi-thousand-line prompt via `$(cat ...)` can hit ARG_MAX.

The non-zero exit was then misclassified by the rate-limit detector, which
aborted the entire session and skipped all remaining PRs in the queue.

Fix: replace all three copilot invocations (run_triage, run_agentic,
run_duck) with a new `copilot_chat` helper that calls the GitHub Models
REST API directly via curl. The API is OpenAI-compatible, versioned via
`X-GitHub-Api-Version`, stable against gh CLI version changes, and accepts
arbitrary prompt sizes using python3 for safe JSON encoding.

Additional changes:
- Add `COPILOT_API_MODEL` var (default `openai/o4-mini`) overrideable via env.
- run_agentic/run_duck copilot cases now also write output to $OUTPUT_FILE
  so callers that check that path directly (deep review, audit) find the JSON.
- Rate-limit responses (HTTP 429) are echoed to stdout so the existing
  `is_rate_limited()` detector fires correctly for engine fallback.
- Add pre-flight smoke test in review-batch.sh: tests GitHub Models API
  connectivity with a one-liner prompt before processing any PRs, so auth
  or model errors surface immediately as a clear setup failure.
- Add tests/test_copilot_chat.sh: unit tests for the JSON payload builder
  with edge-case prompts (quotes, newlines, # headings, large diffs, Unicode).

Closes #147

Co-authored-by: Don Petry <don-petry@users.noreply.github.com>

* fix: scope COPILOT_API_MODEL export to copilot engine only

Move the `export COPILOT_API_MODEL` from the common exports block into the
`copilot)` case where the variable is set, so it is not exported as an
empty/unset variable when the engine is `claude` or `gemini`. The
`copilot_chat` fallback `${COPILOT_API_MODEL:-openai/o4-mini}` still works
correctly when `DUCK_ENGINE=copilot` under a non-copilot primary engine.

Co-authored-by: Don Petry <don-petry@users.noreply.github.com>

* fix: address review comments — temp file for payload, fail-fast source, streaming output

- copilot_chat: write JSON body to mktemp file, pass to curl as @file to
  avoid ARG_MAX for large PR diffs (was --data-binary "$body")
- copilot_chat: add :? guard on COPILOT_GITHUB_TOKEN for a clear error
  instead of generic "unbound variable" under set -u
- run_agentic / run_duck copilot paths: stream directly to stdout (and
  tee to OUTPUT_FILE when set) rather than buffering the full response
  into a shell variable, which forced large outputs into memory and
  stripped trailing newlines
- review-batch.sh pre-flight: fail fast if source engine.sh fails
  (was silently ignored with || true)
- review-batch.sh pre-flight: build smoke-test JSON payload via python3
  + temp file instead of shell string interpolation to avoid JSON
  injection if COPILOT_API_MODEL contains special characters
- Clarify that openai/o4-mini is the correct April-2025 o4-generation
  model name, not a typo for o1-mini or gpt-4o-mini

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Don Petry <don-petry@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* feat: prioritise .github/.github-private PRs, oldest-first within tier (#155)

* feat: prioritise .github/.github-private PRs, oldest-first within tier

- list-prs.sh: add createdAt to JSON fetch; emit priority|createdAt|url
  lines (priority 0 for .github/.github-private, 1 for everything else)
- Replace final sort -u with a two-pass sort: deduplicate by URL, then
  sort by priority asc then createdAt asc; strip sort keys with cut
- tests/test_list_prs_sort.sh: 16 tests covering priority classification,
  oldest-first ordering, mixed scenarios and deduplication
- .github/workflows/test.yml: run both unit-test files on every PR / push

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* test: add dedup edge case for same URL with conflicting priorities

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: remove temperature from copilot_chat payload (o4-mini incompatible)

The GitHub Models API rejects temperature=0 for reasoning models like
o4-mini with HTTP 400:
  'Unsupported value: temperature does not support 0 with this model.
   Only the default (1) value is supported.'

This caused the rubber duck (DUCK_ENGINE=copilot / DUCK_MODEL=o4-mini)
to fail with HTTP 400 on every claude-engine run, silently degrading
every review to deep-only with no cross-engine sanity check.

Fix: remove temperature from the copilot_chat JSON payload entirely —
the API defaults to 1, which is the only supported value for o4-mini.

Also:
- Update tests/test_copilot_chat.sh: sync build_payload to match and
  flip Test 9 to assert temperature is ABSENT (not 0).
- Add .github/workflows/test.yml: run unit tests on every PR and push
  to main so regressions are caught before merge.

Reproducer: job/75690347409 — duck failure logged as:
  copilot_chat: HTTP 400 from GitHub Models API
  {"error":{"message":"Unsupported value: 'temperature' does not
   support 0 with this model..."}}

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: address PR review comments

- list-prs.sh: tighten priority regex to /[.]github(-private)?/pull/
  so path-boundary anchor prevents false positives (e.g. foo.github)
- list-prs.sh: replace printf pipeline with here-string to avoid ARG_MAX
  limits on large PR sets
- test.yml: add permissions: contents: read (least-privilege, CodeQL fix)
- test.yml: add timeout-minutes: 5 to prevent hung runs
- test_list_prs_sort.sh: sync sort_entries helper (here-string + || true)
  and classify regex to match updated list-prs.sh exactly

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: don-petry <don@petry.dev>

* fix: restore pr-review.yml content (accidentally emptied in revert)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: restore pr-review.yml (file was accidentally emptied)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore(deps): bump actions/cache from 4 to 5 (#165)

Bumps [actions/cache](https://github.com/actions/cache) from 4 to 5.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): bump petry-projects/.github/.github/workflows/claude-code-reusable.yml (#164)

Bumps [petry-projects/.github/.github/workflows/claude-code-reusable.yml](https://github.com/petry-projects/.github) from 1 to 2.
- [Commits](https://github.com/petry-projects/.github/compare/v1...v2)

---
updated-dependencies:
- dependency-name: petry-projects/.github/.github/workflows/claude-code-reusable.yml
  dependency-version: '2'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* fix(list-prs): search DELEGATION_ORGS and drop --checks success pre-filter

- Add iteration over all orgs in DELEGATION_ORGS (e.g. don-petry) so PRs
  in those orgs enter the candidate pool. Previously only BOT_USER and
  TARGET_ORG were searched.
- Remove --checks success from org repo searches. GitHub excludes PRs from
  repos with no CI configured when this flag is used, silently dropping all
  PRs from repos like .github, .github-private, google-app-scripts, and
  TalkTerm. review-one-pr.sh already enforces CI gating per-PR and treats
  empty statusCheckRollup as passing, so the pre-filter is redundant and
  harmful.
- Add --limit 200 to gh repo list calls to handle org growth beyond gh's
  default 30-repo cap.

* feat: add code-quality ruleset (compliance fix #60) (#86)

Creates the required `code-quality` repository ruleset enforcing required
status checks on the default branch, as mandated by the org standard:
standards/github-settings.md#code-quality--required-checks-ruleset-all-repositories

Required checks:
- SonarCloud (code quality analysis)
- CodeQL (SAST)
- agent-shield / AgentShield (agent security scan)
- dependency-audit / Detect ecosystems (dependency vulnerability scan)

Bypass actors:
- OrganizationAdmin (always) — emergency override
- dependabot-automerge-petry Integration (always) — Dependabot auto-merge

The ruleset was applied directly via GitHub API. This file documents the
configuration as code for auditability and future reapplication.

Closes #60

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Don Petry <don-petry@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* feat(prompts/dev-lead): add human prompt template

* feat(prompts/dev-lead): add human-pr prompt template

* feat(dev-lead): implement dev-lead agent Phases 0-6

* fix: per-PR isolation and single-review retry (closes #132)

- review-batch.sh: non-rate-limit per-PR failures (exit code 1) no
  longer abort the session. SESSION ABORTED EARLY is now reserved for
  the rate-limit-on-fallback-engine case (exit code 2) only. All other
  failures are counted and logged; remaining candidates continue.

- review-one-pr.sh: single-review step retries up to
  SINGLE_REVIEW_MAX_RETRIES (default 2) times with a
  SINGLE_REVIEW_RETRY_DELAY_SEC (default 15s) gap before giving up.
  On exhaustion, the PR is flagged needs-human-review and the script
  exits with code 1, which the updated batch treats as a non-fatal
  per-PR failure. Raw model output and stderr are logged on each failed
  attempt for post-mortem visibility.

Root cause of run #25707852006: claude-opus-4-7 returned a verbose
non-JSON response for PR #129; the old code treated that as fatal and
skipped 35 remaining candidates.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix: rate-limit detection and per-attempt stderr logs in single-review retry

Address inline review comments on PR #133:

- Rate-limit check: after each run_agentic call, inspect both stdout
  (VERDICT_JSON.raw) and stderr (SINGLE_LOG) with is_rate_limited before
  retrying. A rate-limit match exits immediately with code 2 so
  review-batch.sh can trigger engine fallback — consistent with triage and
  deep-review tiers. Previously a rate-limited single-review would burn all
  retries and exit 1 (per-PR failure), silently leaving the batch on the
  same rate-limited engine for all remaining PRs.

- Per-attempt log files: stderr is now written to
  single-review-attempt-N.log rather than a single overwritten file, so
  no earlier-attempt errors are lost. Each attempt logs its own stderr
  inline on failure; the fallback path cats all attempt logs for
  post-mortem visibility.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* feat(dev-lead): Phase 0 test infrastructure and Phase 1 intent stub

Phase 0: full test harness for the dev-lead agent —
  26 event fixtures (all valid JSON with _test_expected_intent),
  stub claude/gemini engines, mock gh binary, CI failure log sample,
  bats helpers (stub-engine, mock-gh, assert-env, prompt-vars),
  7 prompt templates with VARIABLES declarations, preflight script,
  prompt coverage integration test, and test-dev-lead.yml CI workflow.

Phase 1: dev-lead.yml trigger workflow (all 7 event types, dispatch + ci-relay
  jobs) and dev-lead-intent.sh stub (anti-loop guard live; all other events
  emit skip/not-implemented).  14/14 bats unit tests pass.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): add permissions blocks to test workflow jobs (CodeQL)

* fix(ci): auto-fix for lint / eslint [skip ci-relay]

* feat(dev-lead): Phases 2-6 — CI fix, review fix, issue, engine fallback

Phase 2: run_writer/run_writer_with_fallback in engine.sh, full intent
routing in dev-lead-intent.sh, dev-lead-fix-ci.sh handler, workflow wiring.

Phase 3: dev-lead-fix-reviews.sh handles fix-reviews, fix-bot-comment,
human, human-pr, rebase intents. Full review routing in intent classifier.

Phase 5: dev-lead-fix-issue.sh handles the issue intent with dedup guard
and branch/PR creation.

Phase 6: run_writer_with_fallback with claude→gemini→copilot fallback chain.

Tests: 77 unit tests across 8 new .bats files, all passing. Updated Phase 1
stub tests to reflect actual Phase 2+ routing behavior.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): address review comments — dispatch JSON, dead code, portable stub gh

* fix(dev-lead): pre-flight after intent, fork URL check, review-batch exit code, retry comment

* fix(dev-lead): install bats-core from GitHub to avoid root requirement

* fix(security): move event values to env vars to prevent script injection (SonarCloud)

* fix(security): move CLAUDE_CODE_VERSION to env block (SonarCloud script injection)

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* feat(dev-lead): Phase 1.5 — reusable workflow for cross-repo adoption + Phase 7 shadow period

* feat(dev-lead): add reusable workflow (Phase 1.5) and begin shadow period (Phase 7)

- Create .github/workflows/dev-lead-reusable.yml: workflow_call entry
  point for other repos. Checks out .github-private scripts/prompts into
  .dev-lead/, then runs the same intent-classify + handler pipeline as
  dev-lead.yml with PROMPTS_DIR=.dev-lead/prompts/dev-lead.
- Add PROMPTS_DIR env-var support to dev-lead-fix-ci.sh,
  dev-lead-fix-reviews.sh, and dev-lead-fix-issue.sh so the reusable
  workflow can point scripts at the sparse-checkout path without changing
  CWD. Defaults to prompts/dev-lead (backwards-compatible for dev-lead.yml).
- Annotate dev-lead.yml with Phase 7 shadow-period window (2026-05-15
  through ~2026-05-29): claude.yml and dev-lead.yml run in parallel until
  regressions clear, then claude.yml is removed.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* chore(dev-lead): update shadow-period tracking issue ref to #180

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): document PAT requirement and pin ref:main on private checkout

* fix(dev-lead): P1 review fixes — ci-relay in reusable, .dev-lead gitignore, export PROMPTS_DIR, trim header

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): rate-limit detection, separate status, and retry cron (#192) (#196)

* chore(dev-lead): decommission claude.yml, bump ACTION_TIMEOUT_SEC to 600s

- Delete .github/workflows/claude.yml — replaced by dev-lead.yml (shadow
  period complete, tracking issue #180).
- scripts/engine.sh: raise ACTION_TIMEOUT_SEC default 300→600s to reduce
  timeout failures on large-repo fix-ci runs (PR #80 had 3 timeouts).
- AGENTS.md: remove claude.yml immutability exemption; note dev-lead.yml
  as the active AI automation workflow and its edit-via-reusable pattern.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* refactor: replace Claude analysis with pure gh/jq telemetry in health check

* refactor: drop Node/Claude steps from daily-pr-review-health workflow

* rename: daily-pr-review-health → actions-fleet-monitor

* rename: daily-pr-review-health → actions-fleet-monitor

* fix(agents-md): clarify dev-lead.yml vs dev-lead-reusable.yml scope

dev-lead.yml in .github-private runs inline steps (not a caller stub).
Behavior changes for this repo go to dev-lead.yml directly; changes that
affect all org repos via the cross-repo reusable go to dev-lead-reusable.yml.

Addresses Copilot review on PR #194.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): rate-limit detection, separate status, and retry cron

Closes #192. Implements all phases from the revised plan.

**Phase 0 — fix run_writer stdout capture (prerequisite)**
- engine.sh: capture stdout via `tee` to a tempfile so `is_rate_limited`
  can inspect the output; old code read `/tmp/dev-lead-writer-stderr`
  which was never written (claude --print outputs to stdout, not stderr).
  Fallback engines were never tried in practice.
- engine.sh: add `parse_reset_time` to extract ISO timestamp from
  `resets H:MMpm (UTC)` in engine output and write it to
  `/tmp/dev-lead-rate-limit-reset` for callers to embed in markers.

**Phase 1 — separate status=rate-limited from status=failed**
- fix-ci.sh: detect engine exit 2 → post `status=rate-limited` (not
  `status=failed`); embed parsed reset time in marker body; exit 2.
- fix-ci.sh: fix `check_idempotency` to treat `status=rate-limited`
  as retriable — only block on terminal statuses (applied, failed,
  no-changes). Previously, rate-limited markers blocked all retries.
- fix-ci.sh: `count_recent_failures` already filtered to `status=failed`;
  add explicit comment confirming rate-limited markers are excluded.
- fix-ci.sh: add `has_rate_limited_marker` dedup check so the same SHA
  never accumulates more than one rate-limited comment.
- fix-reviews.sh: on engine exit 2 for all five intent types
  (fix-reviews, fix-bot-comment, human, human-pr, rebase), post a
  `status=rate-limited` marker with embedded reset time and exit 2.
- fix-reviews.sh: for `human` and `human-pr` intents, also post a
  user-visible acknowledgment comment so users know their request was
  received and will be retried.

**Phase 2 — scheduled retry cron for fix-ci**
- dev-lead-retry.sh: new script scans all open PRs across TARGET_ORG
  for `status=rate-limited` markers on current HEAD SHA; skips PRs
  whose reset time is still in the future; staggered dispatches (30s
  between repos) to prevent cascading org-level rate-limit hits.
- dev-lead-retry.yml: new workflow — scheduled every 2 hours (dual
  offsets to work around GitHub scheduler skew), plus workflow_dispatch.

**Phase 3 — retry for fix-reviews intents (same cron)**
- dev-lead-retry.sh already handles all five fix-reviews intent types
  (fix-reviews, fix-bot-comment, human, human-pr, rebase) via
  `dev-lead-reviews-retry` dispatch events.
- dev-lead-intent.sh: add `dev-lead-reviews-retry` dispatch type routing
  to the intent classifier — maps intent_type from payload to the correct
  existing fix-reviews step.
- dev-lead.yml: add `dev-lead-reviews-retry` to repository_dispatch types.
- dev-lead.yml: unify concurrency group for all dispatch types to
  `dev-lead-pr-{pr_number}` to avoid per-type slot fragmentation.

**Tests**
- test_engine_writer.bats: 8 new tests covering rate-limit stdout detection,
  exit-2 mapping, fallback exhaustion, reset time parsing.
- test_fix_ci.bats: 5 new tests covering rate-limited status, exhaustion
  exclusion, idempotency pass-through, and dedup.
- test_fix_reviews.bats: 5 new tests covering all intent types + human ack.
- test_intent_ci.bats: 4 new tests for dev-lead-reviews-retry dispatch routing.
- e2e/scenarios/07-rate-limit-retry.sh: new E2E scenario covering all parts.
- 3 new event fixtures for repository_dispatch_reviews_retry_* payloads.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): address review comments on #196

Addresses all 18 review threads from Copilot, Codex, and Gemini.

**P1 bugs fixed:**
- dev-lead-retry.sh: all log echo calls in scan_pr_for_rate_limits and
  dispatch_* functions now write to stderr; only the final count is on
  stdout. Previously, log lines polluted the command substitution,
  causing arithmetic expansion to fail and aborting the scan on the
  first retry candidate.
- dev-lead-retry.yml: permissions: contents was read-only; creating
  repository_dispatch events requires contents: write.

**Correctness fixes:**
- dev-lead-retry.sh: restrict automated retries to intents whose context
  can be reconstructed at runtime (fix-reviews, human-pr, rebase). human
  and fix-bot-comment require USER_INSTRUCTION/COMMENT_BODY from the
  original event which cannot be re-fetched; they are explicitly excluded.
- dev-lead-fix-reviews.sh: resolve HEAD_SHA from the PR API when not
  provided by the triggering event (issue_comment intents carry no SHA).
  Without this, rate-limited markers had no sha= field and were
  invisible to the retry scanner.
- dev-lead-fix-reviews.sh: write terminal status=applied marker after
  successful fix-reviews, human-pr, rebase runs. Prevents the retry
  cron from re-dispatching the same intent on every subsequent tick when
  the SHA hasn't changed.
- dev-lead-retry.sh: check for reviews terminal marker before dispatching
  a retry (mirrors the existing fix-ci terminal-marker check).
- dev-lead-retry.sh: look up current check-run details (details_url, id)
  at dispatch time via the commits check-runs API, so retried fix-ci runs
  have full failure logs and annotations rather than empty fields.
- dev-lead-fix-ci.sh: embed check= field in the rate-limited marker so
  the retry cron knows which check run to look up.
- dev-lead-fix-reviews.sh: human intent now posts a "please re-mention
  @dev-lead" ack instead of "I'll retry automatically" since it will NOT
  be retried automatically. human-pr keeps the auto-retry ack (correct).

**Pagination (Copilot + Gemini):**
- dev-lead-fix-ci.sh: add --paginate to check_idempotency, count_recent_
  failures, has_rate_limited_marker.
- dev-lead-fix-reviews.sh: add --paginate to has_reviews_rate_limited_marker.
- dev-lead-retry.sh: add --paginate to PR comments and PR list fetches.

**Other improvements:**
- dev-lead-retry.yml: simplify to single cron schedule (15 */2 * * *);
  remove BOT_USER env var (unused in the script).
- dev-lead-retry.sh: raise repo list limit 200 → 1000; add hard-error
  when list_repos_for_org returns empty (surfaces token permission issues
  rather than silently scanning 0 repos).
- dev-lead-intent.sh: use jq -nc (compact, null-input) for context JSON
  construction — safe against values with special characters.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(pr-review): handle Gemini trust-mode failure and properly route engine fallbacks (#217)

* fix(pr-review): handle Gemini trust-mode failure and properly route engine fallbacks

Closes #208

* fix(ci): install gemini CLI and set trust mode for dev-lead agents

---------

Co-authored-by: Gemini CLI <gemini-cli@example.com>

* fix: resolve YAML syntax error in dev-lead-reusable workflow

* fix: address automated feedback from PR 217 (#219)

This addresses missed feedback from CodeRabbit (removing syntax errors in YAML) and gemini-code-assist (improving string concatenation in validate-engines.sh), and adds assertion output checks to tests.

Co-authored-by: Gemini CLI <gemini-cli@example.com>

* chore: remove Phase 7 shadow period comment from dev-lead.yml

* feat: skip PRs with CHANGES_REQUESTED review (+ FORCE_REVIEW and stale-review guards)

* feat: skip PRs with CHANGES_REQUESTED review decision

Adds an early-exit check alongside the existing CI guards so the cascade
skips any PR where a human reviewer has already requested changes.
Reviewing those PRs before the author responds wastes tokens and creates
confusing parallel feedback threads.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix: honor FORCE_REVIEW and skip only stale-free CHANGES_REQUESTED reviews

Two issues addressed from Codex P2 feedback:

1. FORCE_REVIEW bypass: mention-triggered and force_review=true runs now
   bypass the CHANGES_REQUESTED skip, so authors can explicitly request a
   re-review after addressing feedback.

2. Stale review guard: only skip when a CHANGES_REQUESTED review targets
   the current head SHA. On repos without automatic stale-review dismissal,
   reviewDecision stays CHANGES_REQUESTED after new commits — the cascade
   now re-engages when the author has pushed new work.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): resolve context loss, missing checkout, and push logic (#223)

* feat: skip PRs with CHANGES_REQUESTED review decision

Adds an early-exit check alongside the existing CI guards so the cascade
skips any PR where a human reviewer has already requested changes.
Reviewing those PRs before the author responds wastes tokens and creates
confusing parallel feedback threads.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix: honor FORCE_REVIEW and skip only stale-free CHANGES_REQUESTED reviews

Two issues addressed from Codex P2 feedback:

1. FORCE_REVIEW bypass: mention-triggered and force_review=true runs now
   bypass the CHANGES_REQUESTED skip, so authors can explicitly request a
   re-review after addressing feedback.

2. Stale review guard: only skip when a CHANGES_REQUESTED review targets
   the current head SHA. On repos without automatic stale-review dismissal,
   reviewDecision stays CHANGES_REQUESTED after new commits — the cascade
   now re-engages when the author has pushed new work.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): resolve context loss, missing checkout, and push logic

- dev-lead-intent.sh: include actor and body in INTENT_CONTEXT for comments
- dev-lead.yml: parse and pass INTENT_ACTOR and INTENT_COMMENT_BODY to agent
- dev-lead-fix-reviews.sh: perform PR checkout, git push, and post summary comments

* fix(dev-lead): exit with 0 on no-changes in fix-reviews and human-pr

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* test: bypass CI check

* fix(pr-review): remove CI bypass hack

* fix(dev-lead): use heredoc for multiline environment variables (#224)

* fix(dev-lead): use heredoc for multiline environment variables

* fix(dev-lead): ensure all engines are installed and context is consistent

* fix(dev-lead): resolve stale model name in engine fallbacks

* fix(dev-lead): improve fallback reliability and script quality

- engine.sh: resolve stale model names in fallback loop; use -latest Gemini aliases
- dev-lead-fix-reviews.sh: remove unused code and fix PR_URL export

* fix(dev-lead): resolve Gemini model names and shell lint warnings

* security(dev-lead): use random heredoc delimiter to prevent inject…
don-petry added a commit that referenced this pull request Aug 7, 2026
… trigger for the Class-2 timer changes, before narrowing them (#1420)

* add: workflow to fix stuck PRs using bot account token

Allows running the cleanup script via workflow_dispatch with access to
GH_PAT (bot account token) from repo secrets.

* fix: pass GH_PAT to checkout action so workflow uses bot account

* debug: simplify account check, add authentication debug output

* docs: add comprehensive petry-review-bot setup instructions

* docs: add GitHub App setup guide (recommended approach)

GitHub App is the secure, recommended way to automate PR reviews:
- Fine-grained permissions
- JWT tokens that auto-expire
- No human account needed
- Better audit trail
- GitHub's recommended approach

Replaces the bot user account approach with a more secure alternative.

* docs: add comprehensive GitHub App setup documentation

- SETUP.md: Quick reference guide with configuration and troubleshooting
- IMPLEMENTATION.md: Technical deep dive on architecture and design decisions
- DOCUMENTATION.md: Index of all documentation files
- Updated README.md with status and quick links
- Updated GITHUB_APP_SETUP.md with implementation notes and actual app ID
- Workflows use GitHub App token generation instead of static PATs

Covers the transition from bot user account to GitHub App authentication for improved security and maintainability.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: silence auth check in fix-stuck-prs when using GitHub App token

GitHub App tokens don't have user scope, so 'gh api user' returns 403.
This is fine - the script still works for PR operations. Suppress the error
so the script completes successfully with app-token fallback label.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: use explicit author instead of @me in stuck PR search

GitHub App tokens don't have user identity, so @me search doesn't work.
Use explicit 'don-petry' author instead to find PRs to fix.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: avoid subshell in while loop to preserve variable state

Using pipe in while loop created subshell where PROBLEM_PRS and FIXED_PRS
counters were incremented but changes didn't persist to parent shell.
Fixed by using process substitution for input instead of pipe.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* docs: add comprehensive status report for GitHub App setup

- Overview of completed work
- GitHub App authentication details
- 24 stuck PRs successfully fixed with approval reviews
- Architectural decisions and rationale
- Configuration guide and usage examples
- Known limitations and troubleshooting

System is now fully operational with GitHub App token authentication,
comprehensive documentation, and all infrastructure in place.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* docs: document stuck PR cleanup status and script fixes

Added documentation for:
- GitHub App token compatibility fixes in fix-stuck-prs.sh
  * Changed @me to explicit author (app tokens have no identity)
  * Fixed subshell variable scope (process substitution vs pipe)
  * Silenced expected 403 auth check error
- Current status: 24 PRs have approvals but remain OPEN
  * Auto-merge failed due to missing GitHub App permission
  * Approvals satisfy branch protection requirement
  * Next: expand permissions or manually merge

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: update list-prs.sh to search all repos by owner instead of @me

GitHub App tokens don't have user identity, so @me/@review-requested searches
fail. Changed to enumerate all repos in don-petry and petry-projects, then
search for open PRs within each repo. This covers the full scope:
- All open PRs in personal don-petry repos
- All open PRs in petry-projects org repos

Resolves 6 consecutive workflow failures due to zero PRs being enumerated.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* docs: add investigation report for PR review agent workflow failures

* fix: move env section before steps in workflow to fix YAML structure

GitHub Actions requires env section to come before steps in job definition.
Moving env definition up and setting GH_TOKEN only in steps that need it.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* script: add backfill-approvals to retroactively apply real approvals

Converts PRs with agent approval comments (but no real GitHub approvals)
to have actual APPROVED reviews. Needed for PRs reviewed before the
gh pr review --approve fix was applied.

Usage:
  scripts/backfill-approvals.sh          # dry-run (preview changes)
  scripts/backfill-approvals.sh false    # apply real approvals

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* feat: add backfill-approvals workflow to run as GitHub App bot

Runs backfill-approvals.sh via GitHub Actions so the approvals are posted
by the bot identity, not the PR author — GitHub rejects self-approvals.

Also fixes subshell counter bug in the script (piped while loop lost variable
state; switched to process substitution).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: convert inner while loop to process substitution to preserve counters

Both the outer repo loop and inner PR loop were piped subshells, causing
all counter increments (approved/skipped/failed) to be lost. The summary
always showed 0. Fixed by using < <(...) process substitution for both loops.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* car-hunt: add VIN-deep-dive flow, printable checklists, location-tier ranking

- Step 5.5 pre-test-drive flow: NHTSA VIN decode + per-VIN unrepaired-recall
  automation via Chrome MCP (Honda LWC owner portal documented as not
  automatable; NHTSA web form works).
- §F printable test-drive checklist generator (reportlab/Platypus PDF) with
  cold-start, model-specific red flags, negotiation table, walk-away rules.
- §G fraud / paperwork verification checklist (vehicle-agnostic, reusable):
  walk-away triggers, VIN three-location match, curbstoning, odometer fraud,
  bill of sale, title transfer, payment protection, stolen-vehicle, title-
  jumping, after-purchase steps.
- Step 5.25 head-to-head comparison template with information-asymmetry rule.
- Step 3 Location Quality Tier scoring (Birmingham AL metro table A/B/C/D)
  with adj-CPM modifier; sheet schema gains Adj.CPM + Tier columns.
- Hard-disqualification rules added: non-running engine keywords, mileage
  inconsistency, mandatory description scrape.
- Drive MCP overwrite limitation documented; sheet ID now read from memory
  rather than hardcoded.
- FB Marketplace operational facts: Birmingham AL city ID 107739635926718,
  /search? vs /vehicles? query handling, React-controlled inline composer,
  send-button selector, seller-name extraction regex.
- scheduled-tasks/used-car-search-{morning,afternoon}: tier A/B priority
  flag, sheet ID read from memory.
- Reference PDF generator scripts checked in for reuse.

* fix: re-approve PRs where approval predates last commit (stale approval)

Instead of skipping PRs that already have any APPROVED review, skip only
those where reviewDecision is not REVIEW_REQUIRED. This handles PRs where
a bot approval was posted before a new commit was pushed — GitHub's ruleset
engine treats those approvals as stale even with dismiss_stale_reviews=false.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: robustly extract JSON verdict from mixed claude --print output (#17)

Claude's --print mode can prefix the JSON verdict with conversational
preamble text, causing jq to fail with parse errors. Add
extract_verdict_json to engine.sh and wire all three cascade action
call sites in review-one-pr.sh to use it.

Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: extract_verdict_json — check dest file first (agent Bash-write style)

The cascade-action agent writes its verdict JSON to \$OUTPUT_FILE via a
Bash tool call, then prints a text summary to stdout. The previous fix
only scanned stdout (the .raw file), which contained no JSON. The agent-
written file at \$dest (= \$OUTPUT_FILE) was already correct — just not
checked. Now check \$dest first before falling back to stdout scan.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Enforce MAX_REVIEW_CYCLES cap before running the cascade (#18)

* fix: enforce MAX_REVIEW_CYCLES cap with human-escalation marker

Previously MAX_REVIEW_CYCLES only gated AI delegation in
post-pr-review.sh — the cascade itself ran on every cron tick
regardless of how many cycles had accumulated. Real-world result:
a PR could rack up 9+ review cycles (we observed exactly that on
ContentTwin#100) before any cap took effect.

Add a pre-cascade check in review-one-pr.sh: when the count of
existing `<!-- pr-review-agent v1 sha=... -->` markers is at or
above MAX_REVIEW_CYCLES (default 3), post one escalation comment
marked `<!-- pr-review-agent escalation -->`, label
needs-human-review, request don-petry, and exit 100 (skip
sentinel — doesn't burn the MAX_PRS budget). The escalation
marker doubles as the no-spam guard: subsequent runs detect it
and exit 100 immediately.

Also reuse a single `gh pr view` for both the cycle count and the
escalation-marker check.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* review feedback: surface escalation-comment failures, drop dead unset

- Drop `2>/dev/null || true` on `gh pr comment` so a failed escalation
  post is visible in the workflow log instead of silently leaving the
  PR with no marker (which would re-trigger the cap path next tick).
- Reword the cycle-count comment from "AI delegation loops" to "review
  loops" — the cap also catches cascade-only loops where every cycle
  approves and a new commit lands before merge.
- Remove the redundant `unset PR_BODIES` — never exported, no leakage.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>

* Reliability hardening: session circuit breaker, timeouts, retry, dedup (#20)

* feat: session circuit breaker, per-tier timeouts, retry, and triage hard-fail

Reliability hardening for the PR review agent.

1. Session circuit breaker (.github/workflows/pr-review.yml): on any
   non-zero, non-100 exit from review-one-pr.sh (general failure or rate
   limit on the fallback engine), break the per-PR loop, log a clear
   error annotation naming the failing PR and reason, and exit the step
   with code 1 so the run shows red. Prevents one systemic problem from
   silently burning the entire candidate pool.

2. Per-tier timeouts (scripts/engine.sh): triage/deep/audit/action/duck
   each get their own bounded timeout (180/600/600/300/300s defaults,
   env-overridable). Previously only the duck had a timeout — a hung
   tier could burn the whole 60min job budget.

3. Retry-with-backoff on transient errors (scripts/engine.sh): triage
   retries once on 124/137/143 (timeout / signal kill) since its caller
   captures stdout via $(...) so retries are safe. Deliberately NOT
   applied to run_agentic/run_duck where stdout is redirected to a file
   — a retry there would corrupt the partial first-attempt output.

4. Triage non-JSON now hard-fails (scripts/review-one-pr.sh): replaces
   the silent fallback that synthesized a fake "escalate=MEDIUM" verdict
   and proceeded to deep review. With the new circuit breaker, loud
   failure is the right call — masking a broken triage was burning
   tokens on every PR while the workflow looked healthy.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix: stop stacking duplicate agent reviews on the same PR

Two bugs were causing the agent to leave multiple comments on the same PR.
Together they produced 10 stacked APPROVED reviews on petry-projects/ContentTwin#100.

Bug A — idempotency check is order-dependent (review-one-pr.sh):
The previous marker-discovery code did:
  ((.reviews // []) + (.comments // [])) | .[].body | grep marker | tail -1
This relies on the array concatenation order, not chronological order. When
old agent comments existed alongside newer agent reviews, tail -1 picked
the comment-array marker (older) over the review-array marker (newer),
causing the script to think the head SHA hadn't been reviewed and re-run.
Replaced with a single jq pipeline that tags each item with submittedAt /
createdAt, sorts by timestamp, and takes the actual most-recent marker.

Bug B — no cleanup of prior agent items (post-pr-review.sh):
After successfully posting a new review/comment, prior agent items were
left in place, accumulating forever. Added mark_prior_agent_items_obsolete
which, after a successful post:
  - dismisses prior APPROVED/COMMENTED/CHANGES_REQUESTED agent reviews via
    the GitHub dismissal API (UI shows them struck-through as Dismissed)
  - edits prior agent comments to wrap their body in a collapsed <details>
    block with a "Superseded by re-review at <SHA>" summary, plus a
    `<!-- pr-review-agent superseded -->` sentinel for idempotency

All cleanup API calls are best-effort — failures don't break the workflow.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(cleanup): warn on API failures, preserve global newest, file-stage JSON

Three fixes to mark_prior_agent_items_obsolete from the review of PR #20:

1. ::warning:: annotations on every cleanup API failure (review/comment
   list-fetch, individual review dismissal, individual comment fetch+edit).
   Previously these were silenced with `|| true`, so a permissions change
   on the dismissal endpoint would let duplicates stack indefinitely with
   no signal in the Actions UI. Cleanup is still non-fatal — the new post
   has already landed — but failures are now visible.

2. Preserve the globally-latest agent item across BOTH categories, not the
   newest of each category separately. The earlier code split reviews and
   comments and applied `[:-1]` to each, which left a stale fix-request
   comment in place when the new post was a review (or vice versa). The
   one-off cleanup of ContentTwin#100 hit exactly this case: 12 stacked
   reviews collapsed to 1, but a stale comment from 2026-04-25 (SHA
   cd9132d6) was preserved as "newest comment" even though the latest
   review at SHA 3af8c8ee was newer overall. Now: compute the max
   timestamp across both feeds, exclude items at that timestamp.

3. Stage API responses to disk (`mktemp` + `jq <file>`) instead of routing
   through `--argjson "$var"`. The old approach broke on rare unescaped
   control chars in user-authored comment bodies (jq refused to parse the
   resulting shell-vared JSON). File-based input sidesteps the shell
   pipeline entirely.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>

* Remove car-hunt skill and scheduled tasks (moved to don-petry/don-petry) (#21)

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: apply Copilot fallback hardening and reuse GH_PAT for Copilot auth

- Use secrets.GH_PAT (existing personal account token with Copilot
  subscription) instead of a separate COPILOT_GITHUB_TOKEN secret
- Pin actions/create-github-app-token to SHA (v3.1.1) for supply chain safety
- Add post-install verification for gh-copilot extension with warning on failure
- Guard Copilot fallback path: skip PR gracefully if extension is not installed

Addresses issue #24 workflow fixes (1-3) that could not land via PR #25
due to GitHub App lacking workflows permission.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: switch Copilot engine to gh built-in and fix app-id deprecation

- Remove gh extension install (github/gh-copilot conflicts with built-in alias)
- Replace with gh copilot --version check using COPILOT_GITHUB_TOKEN (GH_PAT)
- Update all engine.sh copilot invocations from bare `copilot` binary to
  `gh copilot suggest --target shell` with GH_TOKEN overridden to GH_PAT
  so the user token (with Copilot subscription) is used, not the App token
- Fix actions/create-github-app-token: app-id → client-id (same secret value,
  just the renamed input in v3.x)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: harden Copilot fallback path and remove stale worktrees (#25)

- gh-copilot install: add --force flag and post-install verification
  warning so silent failures due to built-in alias conflict are visible
  in workflow logs instead of being silently swallowed by `|| true`
- fallback pre-flight check: verify `gh extension list | grep copilot`
  before switching to Copilot engine; if unavailable, skip the PR and
  continue the batch rather than session-aborting and dropping all
  remaining candidates (fixes the 27-PR drop on run #503)
- pin actions/create-github-app-token to SHA for v3.1.1 (Node.js 24
  compatible) to prevent hard break on 2026-06-02 when GitHub forces
  Node.js 24 as default runtime
- remove 6 stale Claude Code worktrees from git tracking; add
  .gitignore entry to prevent future worktrees from being committed
  (eliminates exit-128 warning on every actions/checkout post-job sweep)

Closes #24

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: petry-projects-pr-review-agent[bot] <petry-projects-pr-review-agent[bot]@users.noreply.github.com>

* ci: add pre-flight dedup check to prevent duplicate claude-issue PRs (#26)

Before invoking Claude on a labeled issue, a new shell step queries
for any open PR with a branch matching claude/issue-NNN-* (or a body
containing "Closes #NNN"). If one is found it posts a comment on the
issue pointing to the existing PR and skips the Claude step entirely.
A secondary prompt instruction tells Claude to check first and push to
the existing branch rather than opening a new PR.

Motivated by issue petry-projects/google-app-scripts#171, where the
claude label was re-applied three times and each run created a fresh
PR unaware of the prior attempts.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor: migrate to machine user PAT auth (closes #27) (#28)

* refactor: migrate from GitHub App to machine user PAT auth

Closes #27

GitHub Apps cannot be listed in CODEOWNERS, blocking PRs in repos with
require_code_owner_review: true. Switch all workflows to use a machine
user account's fine-grained PAT (DON_PETRY_BOT_GH_PAT secret), which can
join an org team listed in CODEOWNERS.

Workflows: removed actions/create-github-app-token steps in pr-review,
fix-stuck-prs, backfill-approvals, and daily-pr-review-health. All now
use secrets.DON_PETRY_BOT_GH_PAT directly.

Docs: renamed GITHUB_APP_SETUP.md to MACHINE_USER_SETUP.md with full
rewrite covering account creation, CODEOWNERS config, PAT generation,
and rotation. Updated auth sections in IMPLEMENTATION.md, SETUP.md,
STATUS.md, DOCUMENTATION.md, README.md.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* refactor: switch to org-scoped PAT secret DON_PETRY_BOT_PETRY_PROJECT_PAT

Replace DON_PETRY_BOT_GH_PAT with DON_PETRY_BOT_PETRY_PROJECT_PAT — the
new fine-grained PAT scoped to the petry-projects org (resource owner =
petry-projects). The previous PAT was scoped to the donpetry-bot personal
namespace and had no repository access.

The old DON_PETRY_BOT_GH_PAT secret is retained for any future use against
don-petry's personal repos.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* chore: ignore .claude/scheduled_tasks.lock state file

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>

* refactor: merge approval repair scripts into single automation

- Consolidate backfill-approvals.sh and fix-stuck-prs.sh into repair-pr-approvals.sh
- Iterate all repos in both orgs instead of assuming author
- Verify no existing APPROVED review before posting
- Copy original agent comment as review body
- Enable auto-merge when posting approval if needed
- Single workflow with 30-min timeout

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: add @mention trigger for on-demand PR reviews (#30)

Adds repository_dispatch support so commenting @petry-review-bot on any
PR fires an immediate review without waiting for the hourly schedule.

- pr-review.yml: new `repository_dispatch` trigger (type: pr-review-mention),
  per-PR concurrency group for mention runs, FORCE_REVIEW and DRY_RUN
  env vars that handle both workflow_dispatch and repository_dispatch paths
- review-one-pr.sh: FORCE_REVIEW=true bypasses idempotency so a mention
  always runs a fresh cascade even if the head SHA hasn't changed
- templates/mention-listener.yml: deploy to petry-projects/.github;
  listens for @petry-review-bot, validates commenter trust, posts ack,
  sends repository_dispatch (requires Contents:write, not Actions:write)

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: harden rebase, review dismissal, and health log diagnostics (#40)

Addresses 4 code-actionable recommendations from health check report #33:

- [CRITICAL] Make rebase/branch-update non-fatal — 403/504 during rebase
  emits ::warning:: instead of aborting the batch session (#34)
- [MEDIUM] Add retry with exponential backoff (3 attempts) for transient
  5xx on update-branch API; break immediately on 4xx (#37)
- [MEDIUM] Guard review dismissal with state re-check before calling
  dismiss API, preventing 422s from race conditions (#38)
- [LOW] Surface missing log warnings in health check script (#39)
- Fix: skip auto-merge when branch is still BEHIND after failed rebase

Closes #34, closes #37, closes #38, closes #39

* refactor: migrate to org-wide .github-private convention (#41)

* refactor: parametrize hardcoded identity values for org migration

Replace all hardcoded references to don-petry, petry-review-bot, and
don-petry/pr-review-agent with environment variables that default to
the current values. This allows the agent to be configured for different
orgs/users via repo variables.

Changes:
- Scripts use $REVIEWER_USER, $TARGET_ORG, $BOT_USER, $AGENT_REPO
- Prompts no longer reference specific GitHub usernames
- Workflows use org-level GH_PAT_WORKFLOWS secret instead of
  repo-level DON_PETRY_BOT_PETRY_PROJECT_PAT
- Health check uses context.repo.owner/repo for issue creation
- Variables are set in workflow env block with defaults

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add Copilot custom agent profiles for org-wide use

Create /agents/ directory with three agent profiles:
- pr-reviewer: Multi-tier cascading PR review
- feature-ideator: Feature idea generation and prioritization
- compliance-auditor: Org standards compliance checking

These are Copilot custom agent profiles that become available
org-wide in the .github-private repo convention.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Squashed 'frameworks/bmad-method/' content from commit e36f219c

git-subtree-dir: frameworks/bmad-method
git-subtree-split: e36f219c81b6010d4aae423ba12f49edb5b6e31a

* Squashed 'frameworks/spec-kit/' content from commit 11f49ebf

git-subtree-dir: frameworks/spec-kit
git-subtree-split: 11f49ebfb2f6af55345cb4bd9a7906acd211e56f

* Squashed 'frameworks/gsd/' content from commit 304c1a13

git-subtree-dir: frameworks/gsd
git-subtree-split: 304c1a1302564c45af252bbba4bcc5350e7dac3a

* docs: update README for .github-private org infrastructure role

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* ci: add dependabot-automerge.yml workflow (#73)

Adds the required dependabot-automerge.yml workflow from the org
standard template (petry-projects/.github/standards/workflows/).
This is a thin caller stub that delegates to the org-level reusable
workflow.

Closes #48

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Don Petry <don-petry@users.noreply.github.com>

* fix(pr-review): unblock queue starvation from self-authored PRs (#96) (#97)

* fix(pr-review): unblock queue starvation from self-authored PRs (#96)

A self-authored PR sorted first in the candidate list and triggered
"Can not approve your own pull request" — which the session-fatal
abort treated like an engine error, skipping all 28 remaining PRs
on every run.

- list-prs.sh: filter out PRs authored by REVIEWER_USER at enumeration
- post-pr-review.sh: catch the GraphQL self-approval error and exit 100
  (no-op) instead of 1, so a stray self-PR can no longer abort the batch
- engine.sh: gh copilot renamed --target to --agent; fix the rubber-duck
  invocations so tier-2 cross-engine review works again
- review-one-pr.sh: stop appending a duplicate "0" to REVIEW_CYCLE when
  grep -c finds no markers under set -o pipefail (was breaking the
  cycle-cap integer comparison)

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

* fix(pr-review): authenticate as bot, harden enumeration, address review

- Workflow now runs as AGENT_USER (default don-petry-bot), distinct from
  REVIEWER_USER (the human, don-petry). The self-approval rejection that
  caused #96 came from the agent and the human sharing one identity.
- list-prs.sh filters self-authored PRs against AGENT_USER, and validates
  AGENT_USER against the GitHub username charset before interpolating into
  the jq filter (Copilot review feedback).
- review-one-pr.sh uses printf '%s\n' instead of echo for PR_BODIES, since
  PR body content is user-authored and could begin with -n/-e or contain
  backslash escapes (Copilot review feedback).
- AGENT.md guideline updated to reflect that self-authored PRs are
  intentionally excluded (CodeRabbit review feedback).

Operator note: rotate the GH_PAT_WORKFLOWS secret to a token owned by
don-petry-bot (with read:org added) for the bot-account behavior to take
effect at runtime.

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

* refactor(pr-review): collapse to single BOT_USER, route escalations via CODEOWNERS

Drop AGENT_USER and REVIEWER_USER. The workflow now has one identity:
BOT_USER (default don-petry-bot), which both owns the repos to scan and
gets filtered out as the self-approval blocker. Human escalation no
longer hard-codes a single reviewer; instead, scripts/request-codeowners-
review.sh parses CODEOWNERS in the PR's repo and requests review from
every @user / @org/team mention.

- list-prs.sh: BOT_USER for both gh-repo-list and self-author filter
- review-one-pr.sh, post-pr-review.sh: replace --user "$REVIEWER_USER"
  with the CODEOWNERS helper
- repair-pr-approvals.sh: same gh-repo-list switch
- pr-review.yml: drop AGENT_USER/REVIEWER_USER, set BOT_USER default
  to don-petry-bot
- AGENT.md: updated guideline to describe single-identity model

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

* refactor(pr-review): extract batch loop, hoist env, drop redundant step

Workflow YAML drops from 231 to 111 lines by moving the per-PR review
loop into scripts/review-batch.sh. The script can be syntax-checked and
exercised locally; the workflow now just wires env vars and dispatches.

- Hoist GH_TOKEN, MAX_PRS, CANDIDATE_LIMIT to job-level env (were
  repeated on individual steps).
- Inline `gh auth status` into the install step; remove the standalone
  Verify auth step (its only output was a one-line auth dump).
- Drop the `if: steps.list.outputs.count != '0'` guard and the step's
  `id`/output: review-batch.sh handles empty input as a no-op.
- Collapse the duplicated summary-string branch in the review loop into
  a single template with a conditional fragment.

No behavior change.

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

* perf(pr-review): cache claude-code CLI install across runs

`npm install -g @anthropic-ai/claude-code` ran on every workflow start
(~30s). Switch to a per-user npm prefix (~/.npm-global) and cache that
directory via actions/cache, keyed on CLAUDE_CODE_VERSION + runner OS.

A `command -v claude` guard makes the install a no-op on cache hit, so
the only cost on subsequent runs is the cache restore.

CLAUDE_CODE_VERSION defaults to 'latest' (cache persists until manually
flushed); set the repo variable to pin a specific version for fully
reproducible caching.

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

---------

Co-authored-by: Claude <noreply@anthropic.com>

* chore: rename GH_PAT_WORKFLOWS secret to DON_PETRY_BOT_GH_PAT (#99)

The previous name was generic and didn't tell you which account the PAT
belonged to. The new name makes the binding explicit: this secret is the
PAT owned by don-petry-bot, used as BOT_USER throughout the PR-review
workflows.

Operator follow-up before this can be merged:
- Add a new repo secret DON_PETRY_BOT_GH_PAT containing the bot's PAT
  (with repo, workflow, and read:org scopes).
- After merge, the old GH_PAT_WORKFLOWS secret can be deleted.

Affected workflows:
- pr-review.yml (1 use)
- claude.yml (4 uses, with || github.token fallback)
- daily-pr-review-health.yml (1 use)
- repair-pr-approvals.yml (1 use)

scripts/pr_review_health.sh's error message is updated to point at the
new name as well.

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

Co-authored-by: Claude <noreply@anthropic.com>

* chore: remove frameworks directory (#101)

Co-authored-by: Gemini CLI <gemini-cli@example.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore all erroneously deleted files (items 4-34) (#104)

* revert: restore .github/workflows/daily-pr-review-health.yml

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore .github/workflows/repair-pr-approvals.yml

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore agents/pr-reviewer.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/cascade-action.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/deep-review.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/rubber-duck.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/security-audit.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/shared.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/single-review.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/synthesize-duck.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/synthesize.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/triage.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/engine.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/list-prs.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/post-pr-review.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/pr_review_health.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/repair-pr-approvals.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/request-codeowners-review.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/review-batch.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/review-one-pr.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore templates/mention-listener.yml

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore AGENT.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore BOT_SETUP.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore DOCUMENTATION.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore IMPLEMENTATION.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore MACHINE_USER_SETUP.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore PR_REVIEW_AGENT_FAILURE_REPORT.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore PR_REVIEW_FAILURE_INVESTIGATION.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore SETUP.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore STATUS.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore README.md to pre-change state

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore .github/workflows/daily-pr-review-health.yml

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat: add Gemini Pro support and optimize PR review fallback chain (#102)

* Merge main and resolve conflicts

* Enhance pr-review workflow: add Gemini support and refactor fallback logic

* Address Copilot review comments: tighten regex, fix fallback summary, and align docs

* docs: move agent documentation to docs/ folder and rename files

* docs: align secret names and fix casing in index

* docs: organize pr-review-agent documentation under dedicated folder

* Address final PR comments: tighten regex, add preflight checks, fix bot names, and cleanup docs

---------

Co-authored-by: Gemini CLI <gemini-cli@example.com>

* fix(pr-review): gate rate-limit detection on non-zero exit code

Broad patterns like `plan.*limit` and `claude.*usage` could match content
in a successful triage summary, triggering a false-positive engine fallback.
Guard the check with TRIAGE_RC != 0 so it only fires when the provider
command actually failed.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(claude): sync inlined workflow with org standard (#109)

* fix(claude): sync inlined workflow with org standard

Brings the inlined claude.yml up to parity with
petry-projects/.github/.github/workflows/claude-code-reusable.yml.

Changes:
1. Bot allow list for pull_request_review_comment
   Add coderabbitai[bot], Copilot, copilot-pull-request-reviewer[bot],
   and gemini-code-assist[bot] alongside the existing OWNER/MEMBER/
   COLLABORATOR check. These bots have author_association 'NONE' so
   their review comments were always skipped.

2. check_run trigger + claude-ci-fix job
   Port the CI failure auto-fix feature from the reusable. When a check
   fails on a PR, Claude diagnoses and fixes it automatically.
   Adapted to use DON_PETRY_BOT_GH_PAT (this repo's secret name).

3. claude-code-action SHA bump: v1.0.89 → v1.0.119
   Update both the claude and claude-issue jobs.

4. paths-ignore on pull_request trigger
   Prevents the workflow from firing on PRs that only change
   claude.yml itself, avoiding the Anthropic OIDC validation
   failure (workflow file must match default branch at token exchange).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(claude): guard bot allow list against fork PRs

Same fix as petry-projects/.github PR #238 — add same-repo guard
for bot-triggered pull_request_review_comment runs.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(claude): address ci-fix self-loop and fork PR security

- Replace Claude Code name prefix check with explicit job name list to
  correctly prevent self-loops (check_run names for inlined workflows
  are bare job names, not workflow-prefixed)
- Add fork PR trust gate in Resolve PR number step: verify head repo
  matches target repo before running Claude with privileged credentials

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* ci: replace inlined claude.yml with standard thin-caller stub

The inlined version embedded all three jobs (claude, claude-ci-fix,
claude-issue) directly and threaded DON_PETRY_BOT_GH_PAT through
checkout tokens and github_token, causing the bot's PAT to author
PRs and comments instead of github-actions[bot].

Replace with the standard thin caller that delegates to the org-level
reusable workflow (claude-code-reusable.yml@v1) via secrets: inherit,
which is the correct pattern per:
petry-projects/.github/standards/workflows/claude.yml

* fix: replace gh copilot suggest with GitHub Models REST API (#151)

* fix: replace gh copilot suggest with GitHub Models REST API (#147)

The `gh copilot suggest -p "$(cat <file>)"` invocation failed with
"Invalid command format" because:
1. The `-p` flag is not valid syntax in modern `gh copilot` built-in versions.
2. `gh copilot suggest` is a shell-command suggestion tool; it does not
   accept large PR prompts or return structured JSON responses.
3. Passing a multi-thousand-line prompt via `$(cat ...)` can hit ARG_MAX.

The non-zero exit was then misclassified by the rate-limit detector, which
aborted the entire session and skipped all remaining PRs in the queue.

Fix: replace all three copilot invocations (run_triage, run_agentic,
run_duck) with a new `copilot_chat` helper that calls the GitHub Models
REST API directly via curl. The API is OpenAI-compatible, versioned via
`X-GitHub-Api-Version`, stable against gh CLI version changes, and accepts
arbitrary prompt sizes using python3 for safe JSON encoding.

Additional changes:
- Add `COPILOT_API_MODEL` var (default `openai/o4-mini`) overrideable via env.
- run_agentic/run_duck copilot cases now also write output to $OUTPUT_FILE
  so callers that check that path directly (deep review, audit) find the JSON.
- Rate-limit responses (HTTP 429) are echoed to stdout so the existing
  `is_rate_limited()` detector fires correctly for engine fallback.
- Add pre-flight smoke test in review-batch.sh: tests GitHub Models API
  connectivity with a one-liner prompt before processing any PRs, so auth
  or model errors surface immediately as a clear setup failure.
- Add tests/test_copilot_chat.sh: unit tests for the JSON payload builder
  with edge-case prompts (quotes, newlines, # headings, large diffs, Unicode).

Closes #147

Co-authored-by: Don Petry <don-petry@users.noreply.github.com>

* fix: scope COPILOT_API_MODEL export to copilot engine only

Move the `export COPILOT_API_MODEL` from the common exports block into the
`copilot)` case where the variable is set, so it is not exported as an
empty/unset variable when the engine is `claude` or `gemini`. The
`copilot_chat` fallback `${COPILOT_API_MODEL:-openai/o4-mini}` still works
correctly when `DUCK_ENGINE=copilot` under a non-copilot primary engine.

Co-authored-by: Don Petry <don-petry@users.noreply.github.com>

* fix: address review comments — temp file for payload, fail-fast source, streaming output

- copilot_chat: write JSON body to mktemp file, pass to curl as @file to
  avoid ARG_MAX for large PR diffs (was --data-binary "$body")
- copilot_chat: add :? guard on COPILOT_GITHUB_TOKEN for a clear error
  instead of generic "unbound variable" under set -u
- run_agentic / run_duck copilot paths: stream directly to stdout (and
  tee to OUTPUT_FILE when set) rather than buffering the full response
  into a shell variable, which forced large outputs into memory and
  stripped trailing newlines
- review-batch.sh pre-flight: fail fast if source engine.sh fails
  (was silently ignored with || true)
- review-batch.sh pre-flight: build smoke-test JSON payload via python3
  + temp file instead of shell string interpolation to avoid JSON
  injection if COPILOT_API_MODEL contains special characters
- Clarify that openai/o4-mini is the correct April-2025 o4-generation
  model name, not a typo for o1-mini or gpt-4o-mini

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Don Petry <don-petry@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* feat: prioritise .github/.github-private PRs, oldest-first within tier (#155)

* feat: prioritise .github/.github-private PRs, oldest-first within tier

- list-prs.sh: add createdAt to JSON fetch; emit priority|createdAt|url
  lines (priority 0 for .github/.github-private, 1 for everything else)
- Replace final sort -u with a two-pass sort: deduplicate by URL, then
  sort by priority asc then createdAt asc; strip sort keys with cut
- tests/test_list_prs_sort.sh: 16 tests covering priority classification,
  oldest-first ordering, mixed scenarios and deduplication
- .github/workflows/test.yml: run both unit-test files on every PR / push

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* test: add dedup edge case for same URL with conflicting priorities

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: remove temperature from copilot_chat payload (o4-mini incompatible)

The GitHub Models API rejects temperature=0 for reasoning models like
o4-mini with HTTP 400:
  'Unsupported value: temperature does not support 0 with this model.
   Only the default (1) value is supported.'

This caused the rubber duck (DUCK_ENGINE=copilot / DUCK_MODEL=o4-mini)
to fail with HTTP 400 on every claude-engine run, silently degrading
every review to deep-only with no cross-engine sanity check.

Fix: remove temperature from the copilot_chat JSON payload entirely —
the API defaults to 1, which is the only supported value for o4-mini.

Also:
- Update tests/test_copilot_chat.sh: sync build_payload to match and
  flip Test 9 to assert temperature is ABSENT (not 0).
- Add .github/workflows/test.yml: run unit tests on every PR and push
  to main so regressions are caught before merge.

Reproducer: job/75690347409 — duck failure logged as:
  copilot_chat: HTTP 400 from GitHub Models API
  {"error":{"message":"Unsupported value: 'temperature' does not
   support 0 with this model..."}}

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: address PR review comments

- list-prs.sh: tighten priority regex to /[.]github(-private)?/pull/
  so path-boundary anchor prevents false positives (e.g. foo.github)
- list-prs.sh: replace printf pipeline with here-string to avoid ARG_MAX
  limits on large PR sets
- test.yml: add permissions: contents: read (least-privilege, CodeQL fix)
- test.yml: add timeout-minutes: 5 to prevent hung runs
- test_list_prs_sort.sh: sync sort_entries helper (here-string + || true)
  and classify regex to match updated list-prs.sh exactly

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: don-petry <don@petry.dev>

* fix: restore pr-review.yml content (accidentally emptied in revert)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: restore pr-review.yml (file was accidentally emptied)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore(deps): bump actions/cache from 4 to 5 (#165)

Bumps [actions/cache](https://github.com/actions/cache) from 4 to 5.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): bump petry-projects/.github/.github/workflows/claude-code-reusable.yml (#164)

Bumps [petry-projects/.github/.github/workflows/claude-code-reusable.yml](https://github.com/petry-projects/.github) from 1 to 2.
- [Commits](https://github.com/petry-projects/.github/compare/v1...v2)

---
updated-dependencies:
- dependency-name: petry-projects/.github/.github/workflows/claude-code-reusable.yml
  dependency-version: '2'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* fix(list-prs): search DELEGATION_ORGS and drop --checks success pre-filter

- Add iteration over all orgs in DELEGATION_ORGS (e.g. don-petry) so PRs
  in those orgs enter the candidate pool. Previously only BOT_USER and
  TARGET_ORG were searched.
- Remove --checks success from org repo searches. GitHub excludes PRs from
  repos with no CI configured when this flag is used, silently dropping all
  PRs from repos like .github, .github-private, google-app-scripts, and
  TalkTerm. review-one-pr.sh already enforces CI gating per-PR and treats
  empty statusCheckRollup as passing, so the pre-filter is redundant and
  harmful.
- Add --limit 200 to gh repo list calls to handle org growth beyond gh's
  default 30-repo cap.

* feat: add code-quality ruleset (compliance fix #60) (#86)

Creates the required `code-quality` repository ruleset enforcing required
status checks on the default branch, as mandated by the org standard:
standards/github-settings.md#code-quality--required-checks-ruleset-all-repositories

Required checks:
- SonarCloud (code quality analysis)
- CodeQL (SAST)
- agent-shield / AgentShield (agent security scan)
- dependency-audit / Detect ecosystems (dependency vulnerability scan)

Bypass actors:
- OrganizationAdmin (always) — emergency override
- dependabot-automerge-petry Integration (always) — Dependabot auto-merge

The ruleset was applied directly via GitHub API. This file documents the
configuration as code for auditability and future reapplication.

Closes #60

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Don Petry <don-petry@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* feat(prompts/dev-lead): add human prompt template

* feat(prompts/dev-lead): add human-pr prompt template

* feat(dev-lead): implement dev-lead agent Phases 0-6

* fix: per-PR isolation and single-review retry (closes #132)

- review-batch.sh: non-rate-limit per-PR failures (exit code 1) no
  longer abort the session. SESSION ABORTED EARLY is now reserved for
  the rate-limit-on-fallback-engine case (exit code 2) only. All other
  failures are counted and logged; remaining candidates continue.

- review-one-pr.sh: single-review step retries up to
  SINGLE_REVIEW_MAX_RETRIES (default 2) times with a
  SINGLE_REVIEW_RETRY_DELAY_SEC (default 15s) gap before giving up.
  On exhaustion, the PR is flagged needs-human-review and the script
  exits with code 1, which the updated batch treats as a non-fatal
  per-PR failure. Raw model output and stderr are logged on each failed
  attempt for post-mortem visibility.

Root cause of run #25707852006: claude-opus-4-7 returned a verbose
non-JSON response for PR #129; the old code treated that as fatal and
skipped 35 remaining candidates.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix: rate-limit detection and per-attempt stderr logs in single-review retry

Address inline review comments on PR #133:

- Rate-limit check: after each run_agentic call, inspect both stdout
  (VERDICT_JSON.raw) and stderr (SINGLE_LOG) with is_rate_limited before
  retrying. A rate-limit match exits immediately with code 2 so
  review-batch.sh can trigger engine fallback — consistent with triage and
  deep-review tiers. Previously a rate-limited single-review would burn all
  retries and exit 1 (per-PR failure), silently leaving the batch on the
  same rate-limited engine for all remaining PRs.

- Per-attempt log files: stderr is now written to
  single-review-attempt-N.log rather than a single overwritten file, so
  no earlier-attempt errors are lost. Each attempt logs its own stderr
  inline on failure; the fallback path cats all attempt logs for
  post-mortem visibility.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* feat(dev-lead): Phase 0 test infrastructure and Phase 1 intent stub

Phase 0: full test harness for the dev-lead agent —
  26 event fixtures (all valid JSON with _test_expected_intent),
  stub claude/gemini engines, mock gh binary, CI failure log sample,
  bats helpers (stub-engine, mock-gh, assert-env, prompt-vars),
  7 prompt templates with VARIABLES declarations, preflight script,
  prompt coverage integration test, and test-dev-lead.yml CI workflow.

Phase 1: dev-lead.yml trigger workflow (all 7 event types, dispatch + ci-relay
  jobs) and dev-lead-intent.sh stub (anti-loop guard live; all other events
  emit skip/not-implemented).  14/14 bats unit tests pass.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): add permissions blocks to test workflow jobs (CodeQL)

* fix(ci): auto-fix for lint / eslint [skip ci-relay]

* feat(dev-lead): Phases 2-6 — CI fix, review fix, issue, engine fallback

Phase 2: run_writer/run_writer_with_fallback in engine.sh, full intent
routing in dev-lead-intent.sh, dev-lead-fix-ci.sh handler, workflow wiring.

Phase 3: dev-lead-fix-reviews.sh handles fix-reviews, fix-bot-comment,
human, human-pr, rebase intents. Full review routing in intent classifier.

Phase 5: dev-lead-fix-issue.sh handles the issue intent with dedup guard
and branch/PR creation.

Phase 6: run_writer_with_fallback with claude→gemini→copilot fallback chain.

Tests: 77 unit tests across 8 new .bats files, all passing. Updated Phase 1
stub tests to reflect actual Phase 2+ routing behavior.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): address review comments — dispatch JSON, dead code, portable stub gh

* fix(dev-lead): pre-flight after intent, fork URL check, review-batch exit code, retry comment

* fix(dev-lead): install bats-core from GitHub to avoid root requirement

* fix(security): move event values to env vars to prevent script injection (SonarCloud)

* fix(security): move CLAUDE_CODE_VERSION to env block (SonarCloud script injection)

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* feat(dev-lead): Phase 1.5 — reusable workflow for cross-repo adoption + Phase 7 shadow period

* feat(dev-lead): add reusable workflow (Phase 1.5) and begin shadow period (Phase 7)

- Create .github/workflows/dev-lead-reusable.yml: workflow_call entry
  point for other repos. Checks out .github-private scripts/prompts into
  .dev-lead/, then runs the same intent-classify + handler pipeline as
  dev-lead.yml with PROMPTS_DIR=.dev-lead/prompts/dev-lead.
- Add PROMPTS_DIR env-var support to dev-lead-fix-ci.sh,
  dev-lead-fix-reviews.sh, and dev-lead-fix-issue.sh so the reusable
  workflow can point scripts at the sparse-checkout path without changing
  CWD. Defaults to prompts/dev-lead (backwards-compatible for dev-lead.yml).
- Annotate dev-lead.yml with Phase 7 shadow-period window (2026-05-15
  through ~2026-05-29): claude.yml and dev-lead.yml run in parallel until
  regressions clear, then claude.yml is removed.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* chore(dev-lead): update shadow-period tracking issue ref to #180

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): document PAT requirement and pin ref:main on private checkout

* fix(dev-lead): P1 review fixes — ci-relay in reusable, .dev-lead gitignore, export PROMPTS_DIR, trim header

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): rate-limit detection, separate status, and retry cron (#192) (#196)

* chore(dev-lead): decommission claude.yml, bump ACTION_TIMEOUT_SEC to 600s

- Delete .github/workflows/claude.yml — replaced by dev-lead.yml (shadow
  period complete, tracking issue #180).
- scripts/engine.sh: raise ACTION_TIMEOUT_SEC default 300→600s to reduce
  timeout failures on large-repo fix-ci runs (PR #80 had 3 timeouts).
- AGENTS.md: remove claude.yml immutability exemption; note dev-lead.yml
  as the active AI automation workflow and its edit-via-reusable pattern.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* refactor: replace Claude analysis with pure gh/jq telemetry in health check

* refactor: drop Node/Claude steps from daily-pr-review-health workflow

* rename: daily-pr-review-health → actions-fleet-monitor

* rename: daily-pr-review-health → actions-fleet-monitor

* fix(agents-md): clarify dev-lead.yml vs dev-lead-reusable.yml scope

dev-lead.yml in .github-private runs inline steps (not a caller stub).
Behavior changes for this repo go to dev-lead.yml directly; changes that
affect all org repos via the cross-repo reusable go to dev-lead-reusable.yml.

Addresses Copilot review on PR #194.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): rate-limit detection, separate status, and retry cron

Closes #192. Implements all phases from the revised plan.

**Phase 0 — fix run_writer stdout capture (prerequisite)**
- engine.sh: capture stdout via `tee` to a tempfile so `is_rate_limited`
  can inspect the output; old code read `/tmp/dev-lead-writer-stderr`
  which was never written (claude --print outputs to stdout, not stderr).
  Fallback engines were never tried in practice.
- engine.sh: add `parse_reset_time` to extract ISO timestamp from
  `resets H:MMpm (UTC)` in engine output and write it to
  `/tmp/dev-lead-rate-limit-reset` for callers to embed in markers.

**Phase 1 — separate status=rate-limited from status=failed**
- fix-ci.sh: detect engine exit 2 → post `status=rate-limited` (not
  `status=failed`); embed parsed reset time in marker body; exit 2.
- fix-ci.sh: fix `check_idempotency` to treat `status=rate-limited`
  as retriable — only block on terminal statuses (applied, failed,
  no-changes). Previously, rate-limited markers blocked all retries.
- fix-ci.sh: `count_recent_failures` already filtered to `status=failed`;
  add explicit comment confirming rate-limited markers are excluded.
- fix-ci.sh: add `has_rate_limited_marker` dedup check so the same SHA
  never accumulates more than one rate-limited comment.
- fix-reviews.sh: on engine exit 2 for all five intent types
  (fix-reviews, fix-bot-comment, human, human-pr, rebase), post a
  `status=rate-limited` marker with embedded reset time and exit 2.
- fix-reviews.sh: for `human` and `human-pr` intents, also post a
  user-visible acknowledgment comment so users know their request was
  received and will be retried.

**Phase 2 — scheduled retry cron for fix-ci**
- dev-lead-retry.sh: new script scans all open PRs across TARGET_ORG
  for `status=rate-limited` markers on current HEAD SHA; skips PRs
  whose reset time is still in the future; staggered dispatches (30s
  between repos) to prevent cascading org-level rate-limit hits.
- dev-lead-retry.yml: new workflow — scheduled every 2 hours (dual
  offsets to work around GitHub scheduler skew), plus workflow_dispatch.

**Phase 3 — retry for fix-reviews intents (same cron)**
- dev-lead-retry.sh already handles all five fix-reviews intent types
  (fix-reviews, fix-bot-comment, human, human-pr, rebase) via
  `dev-lead-reviews-retry` dispatch events.
- dev-lead-intent.sh: add `dev-lead-reviews-retry` dispatch type routing
  to the intent classifier — maps intent_type from payload to the correct
  existing fix-reviews step.
- dev-lead.yml: add `dev-lead-reviews-retry` to repository_dispatch types.
- dev-lead.yml: unify concurrency group for all dispatch types to
  `dev-lead-pr-{pr_number}` to avoid per-type slot fragmentation.

**Tests**
- test_engine_writer.bats: 8 new tests covering rate-limit stdout detection,
  exit-2 mapping, fallback exhaustion, reset time parsing.
- test_fix_ci.bats: 5 new tests covering rate-limited status, exhaustion
  exclusion, idempotency pass-through, and dedup.
- test_fix_reviews.bats: 5 new tests covering all intent types + human ack.
- test_intent_ci.bats: 4 new tests for dev-lead-reviews-retry dispatch routing.
- e2e/scenarios/07-rate-limit-retry.sh: new E2E scenario covering all parts.
- 3 new event fixtures for repository_dispatch_reviews_retry_* payloads.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): address review comments on #196

Addresses all 18 review threads from Copilot, Codex, and Gemini.

**P1 bugs fixed:**
- dev-lead-retry.sh: all log echo calls in scan_pr_for_rate_limits and
  dispatch_* functions now write to stderr; only the final count is on
  stdout. Previously, log lines polluted the command substitution,
  causing arithmetic expansion to fail and aborting the scan on the
  first retry candidate.
- dev-lead-retry.yml: permissions: contents was read-only; creating
  repository_dispatch events requires contents: write.

**Correctness fixes:**
- dev-lead-retry.sh: restrict automated retries to intents whose context
  can be reconstructed at runtime (fix-reviews, human-pr, rebase). human
  and fix-bot-comment require USER_INSTRUCTION/COMMENT_BODY from the
  original event which cannot be re-fetched; they are explicitly excluded.
- dev-lead-fix-reviews.sh: resolve HEAD_SHA from the PR API when not
  provided by the triggering event (issue_comment intents carry no SHA).
  Without this, rate-limited markers had no sha= field and were
  invisible to the retry scanner.
- dev-lead-fix-reviews.sh: write terminal status=applied marker after
  successful fix-reviews, human-pr, rebase runs. Prevents the retry
  cron from re-dispatching the same intent on every subsequent tick when
  the SHA hasn't changed.
- dev-lead-retry.sh: check for reviews terminal marker before dispatching
  a retry (mirrors the existing fix-ci terminal-marker check).
- dev-lead-retry.sh: look up current check-run details (details_url, id)
  at dispatch time via the commits check-runs API, so retried fix-ci runs
  have full failure logs and annotations rather than empty fields.
- dev-lead-fix-ci.sh: embed check= field in the rate-limited marker so
  the retry cron knows which check run to look up.
- dev-lead-fix-reviews.sh: human intent now posts a "please re-mention
  @dev-lead" ack instead of "I'll retry automatically" since it will NOT
  be retried automatically. human-pr keeps the auto-retry ack (correct).

**Pagination (Copilot + Gemini):**
- dev-lead-fix-ci.sh: add --paginate to check_idempotency, count_recent_
  failures, has_rate_limited_marker.
- dev-lead-fix-reviews.sh: add --paginate to has_reviews_rate_limited_marker.
- dev-lead-retry.sh: add --paginate to PR comments and PR list fetches.

**Other improvements:**
- dev-lead-retry.yml: simplify to single cron schedule (15 */2 * * *);
  remove BOT_USER env var (unused in the script).
- dev-lead-retry.sh: raise repo list limit 200 → 1000; add hard-error
  when list_repos_for_org returns empty (surfaces token permission issues
  rather than silently scanning 0 repos).
- dev-lead-intent.sh: use jq -nc (compact, null-input) for context JSON
  construction — safe against values with special characters.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(pr-review): handle Gemini trust-mode failure and properly route engine fallbacks (#217)

* fix(pr-review): handle Gemini trust-mode failure and properly route engine fallbacks

Closes #208

* fix(ci): install gemini CLI and set trust mode for dev-lead agents

---------

Co-authored-by: Gemini CLI <gemini-cli@example.com>

* fix: resolve YAML syntax error in dev-lead-reusable workflow

* fix: address automated feedback from PR 217 (#219)

This addresses missed feedback from CodeRabbit (removing syntax errors in YAML) and gemini-code-assist (improving string concatenation in validate-engines.sh), and adds assertion output checks to tests.

Co-authored-by: Gemini CLI <gemini-cli@example.com>

* chore: remove Phase 7 shadow period comment from dev-lead.yml

* feat: skip PRs with CHANGES_REQUESTED review (+ FORCE_REVIEW and stale-review guards)

* feat: skip PRs with CHANGES_REQUESTED review decision

Adds an early-exit check alongside the existing CI guards so the cascade
skips any PR where a human reviewer has already requested changes.
Reviewing those PRs before the author responds wastes tokens and creates
confusing parallel feedback threads.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix: honor FORCE_REVIEW and skip only stale-free CHANGES_REQUESTED reviews

Two issues addressed from Codex P2 feedback:

1. FORCE_REVIEW bypass: mention-triggered and force_review=true runs now
   bypass the CHANGES_REQUESTED skip, so authors can explicitly request a
   re-review after addressing feedback.

2. Stale review guard: only skip when a CHANGES_REQUESTED review targets
   the current head SHA. On repos without automatic stale-review dismissal,
   reviewDecision stays CHANGES_REQUESTED after new commits — the cascade
   now re-engages when the author has pushed new work.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): resolve context loss, missing checkout, and push logic (#223)

* feat: skip PRs with CHANGES_REQUESTED review decision

Adds an early-exit check alongside the existing CI guards so the cascade
skips any PR where a human reviewer has already requested changes.
Reviewing those PRs before the author responds wastes tokens and creates
confusing parallel feedback threads.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix: honor FORCE_REVIEW and skip only stale-free CHANGES_REQUESTED reviews

Two issues addressed from Codex P2 feedback:

1. FORCE_REVIEW bypass: mention-triggered and force_review=true runs now
   bypass the CHANGES_REQUESTED skip, so authors can explicitly request a
   re-review after addressing feedback.

2. Stale review guard: only skip when a CHANGES_REQUESTED review targets
   the current head SHA. On repos without automatic stale-review dismissal,
   reviewDecision stays CHANGES_REQUESTED after new commits — the cascade
   now re-engages when the author has pushed new work.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): resolve context loss, missing checkout, and push logic

- dev-lead-intent.sh: include actor and body in INTENT_CONTEXT for comments
- dev-lead.yml: parse and pass INTENT_ACTOR and INTENT_COMMENT_BODY to agent
- dev-lead-fix-reviews.sh: perform PR checkout, git push, and post summary comments

* fix(dev-lead): exit with 0 on no-changes in fix-reviews and human-pr

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* test: bypass CI check

* fix(pr-review): remove CI bypass hack

* fix(dev-lead): use heredoc for multiline environment variables (#224)

* fix(dev-lead): use heredoc for multiline environment variables

* fix(dev-lead): ensure all engines are installed and context is consistent

* fix(dev-lead): resolve stale model name in engine fallbacks

* fix(dev-lead): improve fallback reliability and script quality

- engine.sh: resolve stale model names in fallback loop; use -latest Gemini aliases
- dev-lead-fix-reviews.sh: remove unused code and fix PR_URL export

* fix(dev-lead): resolve Gemini model names and shell lint warnings

* security(dev-lead): use random heredoc delimiter to prevent inject…
don-petry added a commit that referenced this pull request Aug 8, 2026
This addresses missed feedback from CodeRabbit (removing syntax errors in YAML) and gemini-code-assist (improving string concatenation in validate-engines.sh), and adds assertion output checks to tests.

Co-authored-by: Gemini CLI <gemini-cli@example.com>
don-petry added a commit that referenced this pull request Aug 8, 2026
… trigger for the Class-2 timer changes, before narrowing them (#1420)

* add: workflow to fix stuck PRs using bot account token

Allows running the cleanup script via workflow_dispatch with access to
GH_PAT (bot account token) from repo secrets.

* fix: pass GH_PAT to checkout action so workflow uses bot account

* debug: simplify account check, add authentication debug output

* docs: add comprehensive petry-review-bot setup instructions

* docs: add GitHub App setup guide (recommended approach)

GitHub App is the secure, recommended way to automate PR reviews:
- Fine-grained permissions
- JWT tokens that auto-expire
- No human account needed
- Better audit trail
- GitHub's recommended approach

Replaces the bot user account approach with a more secure alternative.

* docs: add comprehensive GitHub App setup documentation

- SETUP.md: Quick reference guide with configuration and troubleshooting
- IMPLEMENTATION.md: Technical deep dive on architecture and design decisions
- DOCUMENTATION.md: Index of all documentation files
- Updated README.md with status and quick links
- Updated GITHUB_APP_SETUP.md with implementation notes and actual app ID
- Workflows use GitHub App token generation instead of static PATs

Covers the transition from bot user account to GitHub App authentication for improved security and maintainability.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: silence auth check in fix-stuck-prs when using GitHub App token

GitHub App tokens don't have user scope, so 'gh api user' returns 403.
This is fine - the script still works for PR operations. Suppress the error
so the script completes successfully with app-token fallback label.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: use explicit author instead of @me in stuck PR search

GitHub App tokens don't have user identity, so @me search doesn't work.
Use explicit 'don-petry' author instead to find PRs to fix.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: avoid subshell in while loop to preserve variable state

Using pipe in while loop created subshell where PROBLEM_PRS and FIXED_PRS
counters were incremented but changes didn't persist to parent shell.
Fixed by using process substitution for input instead of pipe.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* docs: add comprehensive status report for GitHub App setup

- Overview of completed work
- GitHub App authentication details
- 24 stuck PRs successfully fixed with approval reviews
- Architectural decisions and rationale
- Configuration guide and usage examples
- Known limitations and troubleshooting

System is now fully operational with GitHub App token authentication,
comprehensive documentation, and all infrastructure in place.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* docs: document stuck PR cleanup status and script fixes

Added documentation for:
- GitHub App token compatibility fixes in fix-stuck-prs.sh
  * Changed @me to explicit author (app tokens have no identity)
  * Fixed subshell variable scope (process substitution vs pipe)
  * Silenced expected 403 auth check error
- Current status: 24 PRs have approvals but remain OPEN
  * Auto-merge failed due to missing GitHub App permission
  * Approvals satisfy branch protection requirement
  * Next: expand permissions or manually merge

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: update list-prs.sh to search all repos by owner instead of @me

GitHub App tokens don't have user identity, so @me/@review-requested searches
fail. Changed to enumerate all repos in don-petry and petry-projects, then
search for open PRs within each repo. This covers the full scope:
- All open PRs in personal don-petry repos
- All open PRs in petry-projects org repos

Resolves 6 consecutive workflow failures due to zero PRs being enumerated.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* docs: add investigation report for PR review agent workflow failures

* fix: move env section before steps in workflow to fix YAML structure

GitHub Actions requires env section to come before steps in job definition.
Moving env definition up and setting GH_TOKEN only in steps that need it.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* script: add backfill-approvals to retroactively apply real approvals

Converts PRs with agent approval comments (but no real GitHub approvals)
to have actual APPROVED reviews. Needed for PRs reviewed before the
gh pr review --approve fix was applied.

Usage:
  scripts/backfill-approvals.sh          # dry-run (preview changes)
  scripts/backfill-approvals.sh false    # apply real approvals

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* feat: add backfill-approvals workflow to run as GitHub App bot

Runs backfill-approvals.sh via GitHub Actions so the approvals are posted
by the bot identity, not the PR author — GitHub rejects self-approvals.

Also fixes subshell counter bug in the script (piped while loop lost variable
state; switched to process substitution).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: convert inner while loop to process substitution to preserve counters

Both the outer repo loop and inner PR loop were piped subshells, causing
all counter increments (approved/skipped/failed) to be lost. The summary
always showed 0. Fixed by using < <(...) process substitution for both loops.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* car-hunt: add VIN-deep-dive flow, printable checklists, location-tier ranking

- Step 5.5 pre-test-drive flow: NHTSA VIN decode + per-VIN unrepaired-recall
  automation via Chrome MCP (Honda LWC owner portal documented as not
  automatable; NHTSA web form works).
- §F printable test-drive checklist generator (reportlab/Platypus PDF) with
  cold-start, model-specific red flags, negotiation table, walk-away rules.
- §G fraud / paperwork verification checklist (vehicle-agnostic, reusable):
  walk-away triggers, VIN three-location match, curbstoning, odometer fraud,
  bill of sale, title transfer, payment protection, stolen-vehicle, title-
  jumping, after-purchase steps.
- Step 5.25 head-to-head comparison template with information-asymmetry rule.
- Step 3 Location Quality Tier scoring (Birmingham AL metro table A/B/C/D)
  with adj-CPM modifier; sheet schema gains Adj.CPM + Tier columns.
- Hard-disqualification rules added: non-running engine keywords, mileage
  inconsistency, mandatory description scrape.
- Drive MCP overwrite limitation documented; sheet ID now read from memory
  rather than hardcoded.
- FB Marketplace operational facts: Birmingham AL city ID 107739635926718,
  /search? vs /vehicles? query handling, React-controlled inline composer,
  send-button selector, seller-name extraction regex.
- scheduled-tasks/used-car-search-{morning,afternoon}: tier A/B priority
  flag, sheet ID read from memory.
- Reference PDF generator scripts checked in for reuse.

* fix: re-approve PRs where approval predates last commit (stale approval)

Instead of skipping PRs that already have any APPROVED review, skip only
those where reviewDecision is not REVIEW_REQUIRED. This handles PRs where
a bot approval was posted before a new commit was pushed — GitHub's ruleset
engine treats those approvals as stale even with dismiss_stale_reviews=false.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: robustly extract JSON verdict from mixed claude --print output (#17)

Claude's --print mode can prefix the JSON verdict with conversational
preamble text, causing jq to fail with parse errors. Add
extract_verdict_json to engine.sh and wire all three cascade action
call sites in review-one-pr.sh to use it.

Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: extract_verdict_json — check dest file first (agent Bash-write style)

The cascade-action agent writes its verdict JSON to \$OUTPUT_FILE via a
Bash tool call, then prints a text summary to stdout. The previous fix
only scanned stdout (the .raw file), which contained no JSON. The agent-
written file at \$dest (= \$OUTPUT_FILE) was already correct — just not
checked. Now check \$dest first before falling back to stdout scan.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Enforce MAX_REVIEW_CYCLES cap before running the cascade (#18)

* fix: enforce MAX_REVIEW_CYCLES cap with human-escalation marker

Previously MAX_REVIEW_CYCLES only gated AI delegation in
post-pr-review.sh — the cascade itself ran on every cron tick
regardless of how many cycles had accumulated. Real-world result:
a PR could rack up 9+ review cycles (we observed exactly that on
ContentTwin#100) before any cap took effect.

Add a pre-cascade check in review-one-pr.sh: when the count of
existing `<!-- pr-review-agent v1 sha=... -->` markers is at or
above MAX_REVIEW_CYCLES (default 3), post one escalation comment
marked `<!-- pr-review-agent escalation -->`, label
needs-human-review, request don-petry, and exit 100 (skip
sentinel — doesn't burn the MAX_PRS budget). The escalation
marker doubles as the no-spam guard: subsequent runs detect it
and exit 100 immediately.

Also reuse a single `gh pr view` for both the cycle count and the
escalation-marker check.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* review feedback: surface escalation-comment failures, drop dead unset

- Drop `2>/dev/null || true` on `gh pr comment` so a failed escalation
  post is visible in the workflow log instead of silently leaving the
  PR with no marker (which would re-trigger the cap path next tick).
- Reword the cycle-count comment from "AI delegation loops" to "review
  loops" — the cap also catches cascade-only loops where every cycle
  approves and a new commit lands before merge.
- Remove the redundant `unset PR_BODIES` — never exported, no leakage.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>

* Reliability hardening: session circuit breaker, timeouts, retry, dedup (#20)

* feat: session circuit breaker, per-tier timeouts, retry, and triage hard-fail

Reliability hardening for the PR review agent.

1. Session circuit breaker (.github/workflows/pr-review.yml): on any
   non-zero, non-100 exit from review-one-pr.sh (general failure or rate
   limit on the fallback engine), break the per-PR loop, log a clear
   error annotation naming the failing PR and reason, and exit the step
   with code 1 so the run shows red. Prevents one systemic problem from
   silently burning the entire candidate pool.

2. Per-tier timeouts (scripts/engine.sh): triage/deep/audit/action/duck
   each get their own bounded timeout (180/600/600/300/300s defaults,
   env-overridable). Previously only the duck had a timeout — a hung
   tier could burn the whole 60min job budget.

3. Retry-with-backoff on transient errors (scripts/engine.sh): triage
   retries once on 124/137/143 (timeout / signal kill) since its caller
   captures stdout via $(...) so retries are safe. Deliberately NOT
   applied to run_agentic/run_duck where stdout is redirected to a file
   — a retry there would corrupt the partial first-attempt output.

4. Triage non-JSON now hard-fails (scripts/review-one-pr.sh): replaces
   the silent fallback that synthesized a fake "escalate=MEDIUM" verdict
   and proceeded to deep review. With the new circuit breaker, loud
   failure is the right call — masking a broken triage was burning
   tokens on every PR while the workflow looked healthy.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix: stop stacking duplicate agent reviews on the same PR

Two bugs were causing the agent to leave multiple comments on the same PR.
Together they produced 10 stacked APPROVED reviews on petry-projects/ContentTwin#100.

Bug A — idempotency check is order-dependent (review-one-pr.sh):
The previous marker-discovery code did:
  ((.reviews // []) + (.comments // [])) | .[].body | grep marker | tail -1
This relies on the array concatenation order, not chronological order. When
old agent comments existed alongside newer agent reviews, tail -1 picked
the comment-array marker (older) over the review-array marker (newer),
causing the script to think the head SHA hadn't been reviewed and re-run.
Replaced with a single jq pipeline that tags each item with submittedAt /
createdAt, sorts by timestamp, and takes the actual most-recent marker.

Bug B — no cleanup of prior agent items (post-pr-review.sh):
After successfully posting a new review/comment, prior agent items were
left in place, accumulating forever. Added mark_prior_agent_items_obsolete
which, after a successful post:
  - dismisses prior APPROVED/COMMENTED/CHANGES_REQUESTED agent reviews via
    the GitHub dismissal API (UI shows them struck-through as Dismissed)
  - edits prior agent comments to wrap their body in a collapsed <details>
    block with a "Superseded by re-review at <SHA>" summary, plus a
    `<!-- pr-review-agent superseded -->` sentinel for idempotency

All cleanup API calls are best-effort — failures don't break the workflow.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(cleanup): warn on API failures, preserve global newest, file-stage JSON

Three fixes to mark_prior_agent_items_obsolete from the review of PR #20:

1. ::warning:: annotations on every cleanup API failure (review/comment
   list-fetch, individual review dismissal, individual comment fetch+edit).
   Previously these were silenced with `|| true`, so a permissions change
   on the dismissal endpoint would let duplicates stack indefinitely with
   no signal in the Actions UI. Cleanup is still non-fatal — the new post
   has already landed — but failures are now visible.

2. Preserve the globally-latest agent item across BOTH categories, not the
   newest of each category separately. The earlier code split reviews and
   comments and applied `[:-1]` to each, which left a stale fix-request
   comment in place when the new post was a review (or vice versa). The
   one-off cleanup of ContentTwin#100 hit exactly this case: 12 stacked
   reviews collapsed to 1, but a stale comment from 2026-04-25 (SHA
   cd9132d6) was preserved as "newest comment" even though the latest
   review at SHA 3af8c8ee was newer overall. Now: compute the max
   timestamp across both feeds, exclude items at that timestamp.

3. Stage API responses to disk (`mktemp` + `jq <file>`) instead of routing
   through `--argjson "$var"`. The old approach broke on rare unescaped
   control chars in user-authored comment bodies (jq refused to parse the
   resulting shell-vared JSON). File-based input sidesteps the shell
   pipeline entirely.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>

* Remove car-hunt skill and scheduled tasks (moved to don-petry/don-petry) (#21)

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: apply Copilot fallback hardening and reuse GH_PAT for Copilot auth

- Use secrets.GH_PAT (existing personal account token with Copilot
  subscription) instead of a separate COPILOT_GITHUB_TOKEN secret
- Pin actions/create-github-app-token to SHA (v3.1.1) for supply chain safety
- Add post-install verification for gh-copilot extension with warning on failure
- Guard Copilot fallback path: skip PR gracefully if extension is not installed

Addresses issue #24 workflow fixes (1-3) that could not land via PR #25
due to GitHub App lacking workflows permission.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: switch Copilot engine to gh built-in and fix app-id deprecation

- Remove gh extension install (github/gh-copilot conflicts with built-in alias)
- Replace with gh copilot --version check using COPILOT_GITHUB_TOKEN (GH_PAT)
- Update all engine.sh copilot invocations from bare `copilot` binary to
  `gh copilot suggest --target shell` with GH_TOKEN overridden to GH_PAT
  so the user token (with Copilot subscription) is used, not the App token
- Fix actions/create-github-app-token: app-id → client-id (same secret value,
  just the renamed input in v3.x)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: harden Copilot fallback path and remove stale worktrees (#25)

- gh-copilot install: add --force flag and post-install verification
  warning so silent failures due to built-in alias conflict are visible
  in workflow logs instead of being silently swallowed by `|| true`
- fallback pre-flight check: verify `gh extension list | grep copilot`
  before switching to Copilot engine; if unavailable, skip the PR and
  continue the batch rather than session-aborting and dropping all
  remaining candidates (fixes the 27-PR drop on run #503)
- pin actions/create-github-app-token to SHA for v3.1.1 (Node.js 24
  compatible) to prevent hard break on 2026-06-02 when GitHub forces
  Node.js 24 as default runtime
- remove 6 stale Claude Code worktrees from git tracking; add
  .gitignore entry to prevent future worktrees from being committed
  (eliminates exit-128 warning on every actions/checkout post-job sweep)

Closes #24

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: petry-projects-pr-review-agent[bot] <petry-projects-pr-review-agent[bot]@users.noreply.github.com>

* ci: add pre-flight dedup check to prevent duplicate claude-issue PRs (#26)

Before invoking Claude on a labeled issue, a new shell step queries
for any open PR with a branch matching claude/issue-NNN-* (or a body
containing "Closes #NNN"). If one is found it posts a comment on the
issue pointing to the existing PR and skips the Claude step entirely.
A secondary prompt instruction tells Claude to check first and push to
the existing branch rather than opening a new PR.

Motivated by issue petry-projects/google-app-scripts#171, where the
claude label was re-applied three times and each run created a fresh
PR unaware of the prior attempts.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor: migrate to machine user PAT auth (closes #27) (#28)

* refactor: migrate from GitHub App to machine user PAT auth

Closes #27

GitHub Apps cannot be listed in CODEOWNERS, blocking PRs in repos with
require_code_owner_review: true. Switch all workflows to use a machine
user account's fine-grained PAT (DON_PETRY_BOT_GH_PAT secret), which can
join an org team listed in CODEOWNERS.

Workflows: removed actions/create-github-app-token steps in pr-review,
fix-stuck-prs, backfill-approvals, and daily-pr-review-health. All now
use secrets.DON_PETRY_BOT_GH_PAT directly.

Docs: renamed GITHUB_APP_SETUP.md to MACHINE_USER_SETUP.md with full
rewrite covering account creation, CODEOWNERS config, PAT generation,
and rotation. Updated auth sections in IMPLEMENTATION.md, SETUP.md,
STATUS.md, DOCUMENTATION.md, README.md.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* refactor: switch to org-scoped PAT secret DON_PETRY_BOT_PETRY_PROJECT_PAT

Replace DON_PETRY_BOT_GH_PAT with DON_PETRY_BOT_PETRY_PROJECT_PAT — the
new fine-grained PAT scoped to the petry-projects org (resource owner =
petry-projects). The previous PAT was scoped to the donpetry-bot personal
namespace and had no repository access.

The old DON_PETRY_BOT_GH_PAT secret is retained for any future use against
don-petry's personal repos.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* chore: ignore .claude/scheduled_tasks.lock state file

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>

* refactor: merge approval repair scripts into single automation

- Consolidate backfill-approvals.sh and fix-stuck-prs.sh into repair-pr-approvals.sh
- Iterate all repos in both orgs instead of assuming author
- Verify no existing APPROVED review before posting
- Copy original agent comment as review body
- Enable auto-merge when posting approval if needed
- Single workflow with 30-min timeout

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: add @mention trigger for on-demand PR reviews (#30)

Adds repository_dispatch support so commenting @petry-review-bot on any
PR fires an immediate review without waiting for the hourly schedule.

- pr-review.yml: new `repository_dispatch` trigger (type: pr-review-mention),
  per-PR concurrency group for mention runs, FORCE_REVIEW and DRY_RUN
  env vars that handle both workflow_dispatch and repository_dispatch paths
- review-one-pr.sh: FORCE_REVIEW=true bypasses idempotency so a mention
  always runs a fresh cascade even if the head SHA hasn't changed
- templates/mention-listener.yml: deploy to petry-projects/.github;
  listens for @petry-review-bot, validates commenter trust, posts ack,
  sends repository_dispatch (requires Contents:write, not Actions:write)

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: harden rebase, review dismissal, and health log diagnostics (#40)

Addresses 4 code-actionable recommendations from health check report #33:

- [CRITICAL] Make rebase/branch-update non-fatal — 403/504 during rebase
  emits ::warning:: instead of aborting the batch session (#34)
- [MEDIUM] Add retry with exponential backoff (3 attempts) for transient
  5xx on update-branch API; break immediately on 4xx (#37)
- [MEDIUM] Guard review dismissal with state re-check before calling
  dismiss API, preventing 422s from race conditions (#38)
- [LOW] Surface missing log warnings in health check script (#39)
- Fix: skip auto-merge when branch is still BEHIND after failed rebase

Closes #34, closes #37, closes #38, closes #39

* refactor: migrate to org-wide .github-private convention (#41)

* refactor: parametrize hardcoded identity values for org migration

Replace all hardcoded references to don-petry, petry-review-bot, and
don-petry/pr-review-agent with environment variables that default to
the current values. This allows the agent to be configured for different
orgs/users via repo variables.

Changes:
- Scripts use $REVIEWER_USER, $TARGET_ORG, $BOT_USER, $AGENT_REPO
- Prompts no longer reference specific GitHub usernames
- Workflows use org-level GH_PAT_WORKFLOWS secret instead of
  repo-level DON_PETRY_BOT_PETRY_PROJECT_PAT
- Health check uses context.repo.owner/repo for issue creation
- Variables are set in workflow env block with defaults

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add Copilot custom agent profiles for org-wide use

Create /agents/ directory with three agent profiles:
- pr-reviewer: Multi-tier cascading PR review
- feature-ideator: Feature idea generation and prioritization
- compliance-auditor: Org standards compliance checking

These are Copilot custom agent profiles that become available
org-wide in the .github-private repo convention.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Squashed 'frameworks/bmad-method/' content from commit e36f219c

git-subtree-dir: frameworks/bmad-method
git-subtree-split: e36f219c81b6010d4aae423ba12f49edb5b6e31a

* Squashed 'frameworks/spec-kit/' content from commit 11f49ebf

git-subtree-dir: frameworks/spec-kit
git-subtree-split: 11f49ebfb2f6af55345cb4bd9a7906acd211e56f

* Squashed 'frameworks/gsd/' content from commit 304c1a13

git-subtree-dir: frameworks/gsd
git-subtree-split: 304c1a1302564c45af252bbba4bcc5350e7dac3a

* docs: update README for .github-private org infrastructure role

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* ci: add dependabot-automerge.yml workflow (#73)

Adds the required dependabot-automerge.yml workflow from the org
standard template (petry-projects/.github/standards/workflows/).
This is a thin caller stub that delegates to the org-level reusable
workflow.

Closes #48

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Don Petry <don-petry@users.noreply.github.com>

* fix(pr-review): unblock queue starvation from self-authored PRs (#96) (#97)

* fix(pr-review): unblock queue starvation from self-authored PRs (#96)

A self-authored PR sorted first in the candidate list and triggered
"Can not approve your own pull request" — which the session-fatal
abort treated like an engine error, skipping all 28 remaining PRs
on every run.

- list-prs.sh: filter out PRs authored by REVIEWER_USER at enumeration
- post-pr-review.sh: catch the GraphQL self-approval error and exit 100
  (no-op) instead of 1, so a stray self-PR can no longer abort the batch
- engine.sh: gh copilot renamed --target to --agent; fix the rubber-duck
  invocations so tier-2 cross-engine review works again
- review-one-pr.sh: stop appending a duplicate "0" to REVIEW_CYCLE when
  grep -c finds no markers under set -o pipefail (was breaking the
  cycle-cap integer comparison)

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

* fix(pr-review): authenticate as bot, harden enumeration, address review

- Workflow now runs as AGENT_USER (default don-petry-bot), distinct from
  REVIEWER_USER (the human, don-petry). The self-approval rejection that
  caused #96 came from the agent and the human sharing one identity.
- list-prs.sh filters self-authored PRs against AGENT_USER, and validates
  AGENT_USER against the GitHub username charset before interpolating into
  the jq filter (Copilot review feedback).
- review-one-pr.sh uses printf '%s\n' instead of echo for PR_BODIES, since
  PR body content is user-authored and could begin with -n/-e or contain
  backslash escapes (Copilot review feedback).
- AGENT.md guideline updated to reflect that self-authored PRs are
  intentionally excluded (CodeRabbit review feedback).

Operator note: rotate the GH_PAT_WORKFLOWS secret to a token owned by
don-petry-bot (with read:org added) for the bot-account behavior to take
effect at runtime.

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

* refactor(pr-review): collapse to single BOT_USER, route escalations via CODEOWNERS

Drop AGENT_USER and REVIEWER_USER. The workflow now has one identity:
BOT_USER (default don-petry-bot), which both owns the repos to scan and
gets filtered out as the self-approval blocker. Human escalation no
longer hard-codes a single reviewer; instead, scripts/request-codeowners-
review.sh parses CODEOWNERS in the PR's repo and requests review from
every @user / @org/team mention.

- list-prs.sh: BOT_USER for both gh-repo-list and self-author filter
- review-one-pr.sh, post-pr-review.sh: replace --user "$REVIEWER_USER"
  with the CODEOWNERS helper
- repair-pr-approvals.sh: same gh-repo-list switch
- pr-review.yml: drop AGENT_USER/REVIEWER_USER, set BOT_USER default
  to don-petry-bot
- AGENT.md: updated guideline to describe single-identity model

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

* refactor(pr-review): extract batch loop, hoist env, drop redundant step

Workflow YAML drops from 231 to 111 lines by moving the per-PR review
loop into scripts/review-batch.sh. The script can be syntax-checked and
exercised locally; the workflow now just wires env vars and dispatches.

- Hoist GH_TOKEN, MAX_PRS, CANDIDATE_LIMIT to job-level env (were
  repeated on individual steps).
- Inline `gh auth status` into the install step; remove the standalone
  Verify auth step (its only output was a one-line auth dump).
- Drop the `if: steps.list.outputs.count != '0'` guard and the step's
  `id`/output: review-batch.sh handles empty input as a no-op.
- Collapse the duplicated summary-string branch in the review loop into
  a single template with a conditional fragment.

No behavior change.

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

* perf(pr-review): cache claude-code CLI install across runs

`npm install -g @anthropic-ai/claude-code` ran on every workflow start
(~30s). Switch to a per-user npm prefix (~/.npm-global) and cache that
directory via actions/cache, keyed on CLAUDE_CODE_VERSION + runner OS.

A `command -v claude` guard makes the install a no-op on cache hit, so
the only cost on subsequent runs is the cache restore.

CLAUDE_CODE_VERSION defaults to 'latest' (cache persists until manually
flushed); set the repo variable to pin a specific version for fully
reproducible caching.

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

---------

Co-authored-by: Claude <noreply@anthropic.com>

* chore: rename GH_PAT_WORKFLOWS secret to DON_PETRY_BOT_GH_PAT (#99)

The previous name was generic and didn't tell you which account the PAT
belonged to. The new name makes the binding explicit: this secret is the
PAT owned by don-petry-bot, used as BOT_USER throughout the PR-review
workflows.

Operator follow-up before this can be merged:
- Add a new repo secret DON_PETRY_BOT_GH_PAT containing the bot's PAT
  (with repo, workflow, and read:org scopes).
- After merge, the old GH_PAT_WORKFLOWS secret can be deleted.

Affected workflows:
- pr-review.yml (1 use)
- claude.yml (4 uses, with || github.token fallback)
- daily-pr-review-health.yml (1 use)
- repair-pr-approvals.yml (1 use)

scripts/pr_review_health.sh's error message is updated to point at the
new name as well.

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

Co-authored-by: Claude <noreply@anthropic.com>

* chore: remove frameworks directory (#101)

Co-authored-by: Gemini CLI <gemini-cli@example.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore all erroneously deleted files (items 4-34) (#104)

* revert: restore .github/workflows/daily-pr-review-health.yml

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore .github/workflows/repair-pr-approvals.yml

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore agents/pr-reviewer.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/cascade-action.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/deep-review.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/rubber-duck.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/security-audit.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/shared.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/single-review.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/synthesize-duck.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/synthesize.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/triage.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/engine.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/list-prs.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/post-pr-review.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/pr_review_health.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/repair-pr-approvals.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/request-codeowners-review.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/review-batch.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/review-one-pr.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore templates/mention-listener.yml

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore AGENT.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore BOT_SETUP.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore DOCUMENTATION.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore IMPLEMENTATION.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore MACHINE_USER_SETUP.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore PR_REVIEW_AGENT_FAILURE_REPORT.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore PR_REVIEW_FAILURE_INVESTIGATION.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore SETUP.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore STATUS.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore README.md to pre-change state

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore .github/workflows/daily-pr-review-health.yml

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat: add Gemini Pro support and optimize PR review fallback chain (#102)

* Merge main and resolve conflicts

* Enhance pr-review workflow: add Gemini support and refactor fallback logic

* Address Copilot review comments: tighten regex, fix fallback summary, and align docs

* docs: move agent documentation to docs/ folder and rename files

* docs: align secret names and fix casing in index

* docs: organize pr-review-agent documentation under dedicated folder

* Address final PR comments: tighten regex, add preflight checks, fix bot names, and cleanup docs

---------

Co-authored-by: Gemini CLI <gemini-cli@example.com>

* fix(pr-review): gate rate-limit detection on non-zero exit code

Broad patterns like `plan.*limit` and `claude.*usage` could match content
in a successful triage summary, triggering a false-positive engine fallback.
Guard the check with TRIAGE_RC != 0 so it only fires when the provider
command actually failed.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(claude): sync inlined workflow with org standard (#109)

* fix(claude): sync inlined workflow with org standard

Brings the inlined claude.yml up to parity with
petry-projects/.github/.github/workflows/claude-code-reusable.yml.

Changes:
1. Bot allow list for pull_request_review_comment
   Add coderabbitai[bot], Copilot, copilot-pull-request-reviewer[bot],
   and gemini-code-assist[bot] alongside the existing OWNER/MEMBER/
   COLLABORATOR check. These bots have author_association 'NONE' so
   their review comments were always skipped.

2. check_run trigger + claude-ci-fix job
   Port the CI failure auto-fix feature from the reusable. When a check
   fails on a PR, Claude diagnoses and fixes it automatically.
   Adapted to use DON_PETRY_BOT_GH_PAT (this repo's secret name).

3. claude-code-action SHA bump: v1.0.89 → v1.0.119
   Update both the claude and claude-issue jobs.

4. paths-ignore on pull_request trigger
   Prevents the workflow from firing on PRs that only change
   claude.yml itself, avoiding the Anthropic OIDC validation
   failure (workflow file must match default branch at token exchange).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(claude): guard bot allow list against fork PRs

Same fix as petry-projects/.github PR #238 — add same-repo guard
for bot-triggered pull_request_review_comment runs.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(claude): address ci-fix self-loop and fork PR security

- Replace Claude Code name prefix check with explicit job name list to
  correctly prevent self-loops (check_run names for inlined workflows
  are bare job names, not workflow-prefixed)
- Add fork PR trust gate in Resolve PR number step: verify head repo
  matches target repo before running Claude with privileged credentials

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* ci: replace inlined claude.yml with standard thin-caller stub

The inlined version embedded all three jobs (claude, claude-ci-fix,
claude-issue) directly and threaded DON_PETRY_BOT_GH_PAT through
checkout tokens and github_token, causing the bot's PAT to author
PRs and comments instead of github-actions[bot].

Replace with the standard thin caller that delegates to the org-level
reusable workflow (claude-code-reusable.yml@v1) via secrets: inherit,
which is the correct pattern per:
petry-projects/.github/standards/workflows/claude.yml

* fix: replace gh copilot suggest with GitHub Models REST API (#151)

* fix: replace gh copilot suggest with GitHub Models REST API (#147)

The `gh copilot suggest -p "$(cat <file>)"` invocation failed with
"Invalid command format" because:
1. The `-p` flag is not valid syntax in modern `gh copilot` built-in versions.
2. `gh copilot suggest` is a shell-command suggestion tool; it does not
   accept large PR prompts or return structured JSON responses.
3. Passing a multi-thousand-line prompt via `$(cat ...)` can hit ARG_MAX.

The non-zero exit was then misclassified by the rate-limit detector, which
aborted the entire session and skipped all remaining PRs in the queue.

Fix: replace all three copilot invocations (run_triage, run_agentic,
run_duck) with a new `copilot_chat` helper that calls the GitHub Models
REST API directly via curl. The API is OpenAI-compatible, versioned via
`X-GitHub-Api-Version`, stable against gh CLI version changes, and accepts
arbitrary prompt sizes using python3 for safe JSON encoding.

Additional changes:
- Add `COPILOT_API_MODEL` var (default `openai/o4-mini`) overrideable via env.
- run_agentic/run_duck copilot cases now also write output to $OUTPUT_FILE
  so callers that check that path directly (deep review, audit) find the JSON.
- Rate-limit responses (HTTP 429) are echoed to stdout so the existing
  `is_rate_limited()` detector fires correctly for engine fallback.
- Add pre-flight smoke test in review-batch.sh: tests GitHub Models API
  connectivity with a one-liner prompt before processing any PRs, so auth
  or model errors surface immediately as a clear setup failure.
- Add tests/test_copilot_chat.sh: unit tests for the JSON payload builder
  with edge-case prompts (quotes, newlines, # headings, large diffs, Unicode).

Closes #147

Co-authored-by: Don Petry <don-petry@users.noreply.github.com>

* fix: scope COPILOT_API_MODEL export to copilot engine only

Move the `export COPILOT_API_MODEL` from the common exports block into the
`copilot)` case where the variable is set, so it is not exported as an
empty/unset variable when the engine is `claude` or `gemini`. The
`copilot_chat` fallback `${COPILOT_API_MODEL:-openai/o4-mini}` still works
correctly when `DUCK_ENGINE=copilot` under a non-copilot primary engine.

Co-authored-by: Don Petry <don-petry@users.noreply.github.com>

* fix: address review comments — temp file for payload, fail-fast source, streaming output

- copilot_chat: write JSON body to mktemp file, pass to curl as @file to
  avoid ARG_MAX for large PR diffs (was --data-binary "$body")
- copilot_chat: add :? guard on COPILOT_GITHUB_TOKEN for a clear error
  instead of generic "unbound variable" under set -u
- run_agentic / run_duck copilot paths: stream directly to stdout (and
  tee to OUTPUT_FILE when set) rather than buffering the full response
  into a shell variable, which forced large outputs into memory and
  stripped trailing newlines
- review-batch.sh pre-flight: fail fast if source engine.sh fails
  (was silently ignored with || true)
- review-batch.sh pre-flight: build smoke-test JSON payload via python3
  + temp file instead of shell string interpolation to avoid JSON
  injection if COPILOT_API_MODEL contains special characters
- Clarify that openai/o4-mini is the correct April-2025 o4-generation
  model name, not a typo for o1-mini or gpt-4o-mini

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Don Petry <don-petry@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* feat: prioritise .github/.github-private PRs, oldest-first within tier (#155)

* feat: prioritise .github/.github-private PRs, oldest-first within tier

- list-prs.sh: add createdAt to JSON fetch; emit priority|createdAt|url
  lines (priority 0 for .github/.github-private, 1 for everything else)
- Replace final sort -u with a two-pass sort: deduplicate by URL, then
  sort by priority asc then createdAt asc; strip sort keys with cut
- tests/test_list_prs_sort.sh: 16 tests covering priority classification,
  oldest-first ordering, mixed scenarios and deduplication
- .github/workflows/test.yml: run both unit-test files on every PR / push

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* test: add dedup edge case for same URL with conflicting priorities

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: remove temperature from copilot_chat payload (o4-mini incompatible)

The GitHub Models API rejects temperature=0 for reasoning models like
o4-mini with HTTP 400:
  'Unsupported value: temperature does not support 0 with this model.
   Only the default (1) value is supported.'

This caused the rubber duck (DUCK_ENGINE=copilot / DUCK_MODEL=o4-mini)
to fail with HTTP 400 on every claude-engine run, silently degrading
every review to deep-only with no cross-engine sanity check.

Fix: remove temperature from the copilot_chat JSON payload entirely —
the API defaults to 1, which is the only supported value for o4-mini.

Also:
- Update tests/test_copilot_chat.sh: sync build_payload to match and
  flip Test 9 to assert temperature is ABSENT (not 0).
- Add .github/workflows/test.yml: run unit tests on every PR and push
  to main so regressions are caught before merge.

Reproducer: job/75690347409 — duck failure logged as:
  copilot_chat: HTTP 400 from GitHub Models API
  {"error":{"message":"Unsupported value: 'temperature' does not
   support 0 with this model..."}}

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: address PR review comments

- list-prs.sh: tighten priority regex to /[.]github(-private)?/pull/
  so path-boundary anchor prevents false positives (e.g. foo.github)
- list-prs.sh: replace printf pipeline with here-string to avoid ARG_MAX
  limits on large PR sets
- test.yml: add permissions: contents: read (least-privilege, CodeQL fix)
- test.yml: add timeout-minutes: 5 to prevent hung runs
- test_list_prs_sort.sh: sync sort_entries helper (here-string + || true)
  and classify regex to match updated list-prs.sh exactly

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: don-petry <don@petry.dev>

* fix: restore pr-review.yml content (accidentally emptied in revert)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: restore pr-review.yml (file was accidentally emptied)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore(deps): bump actions/cache from 4 to 5 (#165)

Bumps [actions/cache](https://github.com/actions/cache) from 4 to 5.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): bump petry-projects/.github/.github/workflows/claude-code-reusable.yml (#164)

Bumps [petry-projects/.github/.github/workflows/claude-code-reusable.yml](https://github.com/petry-projects/.github) from 1 to 2.
- [Commits](https://github.com/petry-projects/.github/compare/v1...v2)

---
updated-dependencies:
- dependency-name: petry-projects/.github/.github/workflows/claude-code-reusable.yml
  dependency-version: '2'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* fix(list-prs): search DELEGATION_ORGS and drop --checks success pre-filter

- Add iteration over all orgs in DELEGATION_ORGS (e.g. don-petry) so PRs
  in those orgs enter the candidate pool. Previously only BOT_USER and
  TARGET_ORG were searched.
- Remove --checks success from org repo searches. GitHub excludes PRs from
  repos with no CI configured when this flag is used, silently dropping all
  PRs from repos like .github, .github-private, google-app-scripts, and
  TalkTerm. review-one-pr.sh already enforces CI gating per-PR and treats
  empty statusCheckRollup as passing, so the pre-filter is redundant and
  harmful.
- Add --limit 200 to gh repo list calls to handle org growth beyond gh's
  default 30-repo cap.

* feat: add code-quality ruleset (compliance fix #60) (#86)

Creates the required `code-quality` repository ruleset enforcing required
status checks on the default branch, as mandated by the org standard:
standards/github-settings.md#code-quality--required-checks-ruleset-all-repositories

Required checks:
- SonarCloud (code quality analysis)
- CodeQL (SAST)
- agent-shield / AgentShield (agent security scan)
- dependency-audit / Detect ecosystems (dependency vulnerability scan)

Bypass actors:
- OrganizationAdmin (always) — emergency override
- dependabot-automerge-petry Integration (always) — Dependabot auto-merge

The ruleset was applied directly via GitHub API. This file documents the
configuration as code for auditability and future reapplication.

Closes #60

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Don Petry <don-petry@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* feat(prompts/dev-lead): add human prompt template

* feat(prompts/dev-lead): add human-pr prompt template

* feat(dev-lead): implement dev-lead agent Phases 0-6

* fix: per-PR isolation and single-review retry (closes #132)

- review-batch.sh: non-rate-limit per-PR failures (exit code 1) no
  longer abort the session. SESSION ABORTED EARLY is now reserved for
  the rate-limit-on-fallback-engine case (exit code 2) only. All other
  failures are counted and logged; remaining candidates continue.

- review-one-pr.sh: single-review step retries up to
  SINGLE_REVIEW_MAX_RETRIES (default 2) times with a
  SINGLE_REVIEW_RETRY_DELAY_SEC (default 15s) gap before giving up.
  On exhaustion, the PR is flagged needs-human-review and the script
  exits with code 1, which the updated batch treats as a non-fatal
  per-PR failure. Raw model output and stderr are logged on each failed
  attempt for post-mortem visibility.

Root cause of run #25707852006: claude-opus-4-7 returned a verbose
non-JSON response for PR #129; the old code treated that as fatal and
skipped 35 remaining candidates.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix: rate-limit detection and per-attempt stderr logs in single-review retry

Address inline review comments on PR #133:

- Rate-limit check: after each run_agentic call, inspect both stdout
  (VERDICT_JSON.raw) and stderr (SINGLE_LOG) with is_rate_limited before
  retrying. A rate-limit match exits immediately with code 2 so
  review-batch.sh can trigger engine fallback — consistent with triage and
  deep-review tiers. Previously a rate-limited single-review would burn all
  retries and exit 1 (per-PR failure), silently leaving the batch on the
  same rate-limited engine for all remaining PRs.

- Per-attempt log files: stderr is now written to
  single-review-attempt-N.log rather than a single overwritten file, so
  no earlier-attempt errors are lost. Each attempt logs its own stderr
  inline on failure; the fallback path cats all attempt logs for
  post-mortem visibility.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* feat(dev-lead): Phase 0 test infrastructure and Phase 1 intent stub

Phase 0: full test harness for the dev-lead agent —
  26 event fixtures (all valid JSON with _test_expected_intent),
  stub claude/gemini engines, mock gh binary, CI failure log sample,
  bats helpers (stub-engine, mock-gh, assert-env, prompt-vars),
  7 prompt templates with VARIABLES declarations, preflight script,
  prompt coverage integration test, and test-dev-lead.yml CI workflow.

Phase 1: dev-lead.yml trigger workflow (all 7 event types, dispatch + ci-relay
  jobs) and dev-lead-intent.sh stub (anti-loop guard live; all other events
  emit skip/not-implemented).  14/14 bats unit tests pass.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): add permissions blocks to test workflow jobs (CodeQL)

* fix(ci): auto-fix for lint / eslint [skip ci-relay]

* feat(dev-lead): Phases 2-6 — CI fix, review fix, issue, engine fallback

Phase 2: run_writer/run_writer_with_fallback in engine.sh, full intent
routing in dev-lead-intent.sh, dev-lead-fix-ci.sh handler, workflow wiring.

Phase 3: dev-lead-fix-reviews.sh handles fix-reviews, fix-bot-comment,
human, human-pr, rebase intents. Full review routing in intent classifier.

Phase 5: dev-lead-fix-issue.sh handles the issue intent with dedup guard
and branch/PR creation.

Phase 6: run_writer_with_fallback with claude→gemini→copilot fallback chain.

Tests: 77 unit tests across 8 new .bats files, all passing. Updated Phase 1
stub tests to reflect actual Phase 2+ routing behavior.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): address review comments — dispatch JSON, dead code, portable stub gh

* fix(dev-lead): pre-flight after intent, fork URL check, review-batch exit code, retry comment

* fix(dev-lead): install bats-core from GitHub to avoid root requirement

* fix(security): move event values to env vars to prevent script injection (SonarCloud)

* fix(security): move CLAUDE_CODE_VERSION to env block (SonarCloud script injection)

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* feat(dev-lead): Phase 1.5 — reusable workflow for cross-repo adoption + Phase 7 shadow period

* feat(dev-lead): add reusable workflow (Phase 1.5) and begin shadow period (Phase 7)

- Create .github/workflows/dev-lead-reusable.yml: workflow_call entry
  point for other repos. Checks out .github-private scripts/prompts into
  .dev-lead/, then runs the same intent-classify + handler pipeline as
  dev-lead.yml with PROMPTS_DIR=.dev-lead/prompts/dev-lead.
- Add PROMPTS_DIR env-var support to dev-lead-fix-ci.sh,
  dev-lead-fix-reviews.sh, and dev-lead-fix-issue.sh so the reusable
  workflow can point scripts at the sparse-checkout path without changing
  CWD. Defaults to prompts/dev-lead (backwards-compatible for dev-lead.yml).
- Annotate dev-lead.yml with Phase 7 shadow-period window (2026-05-15
  through ~2026-05-29): claude.yml and dev-lead.yml run in parallel until
  regressions clear, then claude.yml is removed.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* chore(dev-lead): update shadow-period tracking issue ref to #180

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): document PAT requirement and pin ref:main on private checkout

* fix(dev-lead): P1 review fixes — ci-relay in reusable, .dev-lead gitignore, export PROMPTS_DIR, trim header

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): rate-limit detection, separate status, and retry cron (#192) (#196)

* chore(dev-lead): decommission claude.yml, bump ACTION_TIMEOUT_SEC to 600s

- Delete .github/workflows/claude.yml — replaced by dev-lead.yml (shadow
  period complete, tracking issue #180).
- scripts/engine.sh: raise ACTION_TIMEOUT_SEC default 300→600s to reduce
  timeout failures on large-repo fix-ci runs (PR #80 had 3 timeouts).
- AGENTS.md: remove claude.yml immutability exemption; note dev-lead.yml
  as the active AI automation workflow and its edit-via-reusable pattern.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* refactor: replace Claude analysis with pure gh/jq telemetry in health check

* refactor: drop Node/Claude steps from daily-pr-review-health workflow

* rename: daily-pr-review-health → actions-fleet-monitor

* rename: daily-pr-review-health → actions-fleet-monitor

* fix(agents-md): clarify dev-lead.yml vs dev-lead-reusable.yml scope

dev-lead.yml in .github-private runs inline steps (not a caller stub).
Behavior changes for this repo go to dev-lead.yml directly; changes that
affect all org repos via the cross-repo reusable go to dev-lead-reusable.yml.

Addresses Copilot review on PR #194.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): rate-limit detection, separate status, and retry cron

Closes #192. Implements all phases from the revised plan.

**Phase 0 — fix run_writer stdout capture (prerequisite)**
- engine.sh: capture stdout via `tee` to a tempfile so `is_rate_limited`
  can inspect the output; old code read `/tmp/dev-lead-writer-stderr`
  which was never written (claude --print outputs to stdout, not stderr).
  Fallback engines were never tried in practice.
- engine.sh: add `parse_reset_time` to extract ISO timestamp from
  `resets H:MMpm (UTC)` in engine output and write it to
  `/tmp/dev-lead-rate-limit-reset` for callers to embed in markers.

**Phase 1 — separate status=rate-limited from status=failed**
- fix-ci.sh: detect engine exit 2 → post `status=rate-limited` (not
  `status=failed`); embed parsed reset time in marker body; exit 2.
- fix-ci.sh: fix `check_idempotency` to treat `status=rate-limited`
  as retriable — only block on terminal statuses (applied, failed,
  no-changes). Previously, rate-limited markers blocked all retries.
- fix-ci.sh: `count_recent_failures` already filtered to `status=failed`;
  add explicit comment confirming rate-limited markers are excluded.
- fix-ci.sh: add `has_rate_limited_marker` dedup check so the same SHA
  never accumulates more than one rate-limited comment.
- fix-reviews.sh: on engine exit 2 for all five intent types
  (fix-reviews, fix-bot-comment, human, human-pr, rebase), post a
  `status=rate-limited` marker with embedded reset time and exit 2.
- fix-reviews.sh: for `human` and `human-pr` intents, also post a
  user-visible acknowledgment comment so users know their request was
  received and will be retried.

**Phase 2 — scheduled retry cron for fix-ci**
- dev-lead-retry.sh: new script scans all open PRs across TARGET_ORG
  for `status=rate-limited` markers on current HEAD SHA; skips PRs
  whose reset time is still in the future; staggered dispatches (30s
  between repos) to prevent cascading org-level rate-limit hits.
- dev-lead-retry.yml: new workflow — scheduled every 2 hours (dual
  offsets to work around GitHub scheduler skew), plus workflow_dispatch.

**Phase 3 — retry for fix-reviews intents (same cron)**
- dev-lead-retry.sh already handles all five fix-reviews intent types
  (fix-reviews, fix-bot-comment, human, human-pr, rebase) via
  `dev-lead-reviews-retry` dispatch events.
- dev-lead-intent.sh: add `dev-lead-reviews-retry` dispatch type routing
  to the intent classifier — maps intent_type from payload to the correct
  existing fix-reviews step.
- dev-lead.yml: add `dev-lead-reviews-retry` to repository_dispatch types.
- dev-lead.yml: unify concurrency group for all dispatch types to
  `dev-lead-pr-{pr_number}` to avoid per-type slot fragmentation.

**Tests**
- test_engine_writer.bats: 8 new tests covering rate-limit stdout detection,
  exit-2 mapping, fallback exhaustion, reset time parsing.
- test_fix_ci.bats: 5 new tests covering rate-limited status, exhaustion
  exclusion, idempotency pass-through, and dedup.
- test_fix_reviews.bats: 5 new tests covering all intent types + human ack.
- test_intent_ci.bats: 4 new tests for dev-lead-reviews-retry dispatch routing.
- e2e/scenarios/07-rate-limit-retry.sh: new E2E scenario covering all parts.
- 3 new event fixtures for repository_dispatch_reviews_retry_* payloads.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): address review comments on #196

Addresses all 18 review threads from Copilot, Codex, and Gemini.

**P1 bugs fixed:**
- dev-lead-retry.sh: all log echo calls in scan_pr_for_rate_limits and
  dispatch_* functions now write to stderr; only the final count is on
  stdout. Previously, log lines polluted the command substitution,
  causing arithmetic expansion to fail and aborting the scan on the
  first retry candidate.
- dev-lead-retry.yml: permissions: contents was read-only; creating
  repository_dispatch events requires contents: write.

**Correctness fixes:**
- dev-lead-retry.sh: restrict automated retries to intents whose context
  can be reconstructed at runtime (fix-reviews, human-pr, rebase). human
  and fix-bot-comment require USER_INSTRUCTION/COMMENT_BODY from the
  original event which cannot be re-fetched; they are explicitly excluded.
- dev-lead-fix-reviews.sh: resolve HEAD_SHA from the PR API when not
  provided by the triggering event (issue_comment intents carry no SHA).
  Without this, rate-limited markers had no sha= field and were
  invisible to the retry scanner.
- dev-lead-fix-reviews.sh: write terminal status=applied marker after
  successful fix-reviews, human-pr, rebase runs. Prevents the retry
  cron from re-dispatching the same intent on every subsequent tick when
  the SHA hasn't changed.
- dev-lead-retry.sh: check for reviews terminal marker before dispatching
  a retry (mirrors the existing fix-ci terminal-marker check).
- dev-lead-retry.sh: look up current check-run details (details_url, id)
  at dispatch time via the commits check-runs API, so retried fix-ci runs
  have full failure logs and annotations rather than empty fields.
- dev-lead-fix-ci.sh: embed check= field in the rate-limited marker so
  the retry cron knows which check run to look up.
- dev-lead-fix-reviews.sh: human intent now posts a "please re-mention
  @dev-lead" ack instead of "I'll retry automatically" since it will NOT
  be retried automatically. human-pr keeps the auto-retry ack (correct).

**Pagination (Copilot + Gemini):**
- dev-lead-fix-ci.sh: add --paginate to check_idempotency, count_recent_
  failures, has_rate_limited_marker.
- dev-lead-fix-reviews.sh: add --paginate to has_reviews_rate_limited_marker.
- dev-lead-retry.sh: add --paginate to PR comments and PR list fetches.

**Other improvements:**
- dev-lead-retry.yml: simplify to single cron schedule (15 */2 * * *);
  remove BOT_USER env var (unused in the script).
- dev-lead-retry.sh: raise repo list limit 200 → 1000; add hard-error
  when list_repos_for_org returns empty (surfaces token permission issues
  rather than silently scanning 0 repos).
- dev-lead-intent.sh: use jq -nc (compact, null-input) for context JSON
  construction — safe against values with special characters.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(pr-review): handle Gemini trust-mode failure and properly route engine fallbacks (#217)

* fix(pr-review): handle Gemini trust-mode failure and properly route engine fallbacks

Closes #208

* fix(ci): install gemini CLI and set trust mode for dev-lead agents

---------

Co-authored-by: Gemini CLI <gemini-cli@example.com>

* fix: resolve YAML syntax error in dev-lead-reusable workflow

* fix: address automated feedback from PR 217 (#219)

This addresses missed feedback from CodeRabbit (removing syntax errors in YAML) and gemini-code-assist (improving string concatenation in validate-engines.sh), and adds assertion output checks to tests.

Co-authored-by: Gemini CLI <gemini-cli@example.com>

* chore: remove Phase 7 shadow period comment from dev-lead.yml

* feat: skip PRs with CHANGES_REQUESTED review (+ FORCE_REVIEW and stale-review guards)

* feat: skip PRs with CHANGES_REQUESTED review decision

Adds an early-exit check alongside the existing CI guards so the cascade
skips any PR where a human reviewer has already requested changes.
Reviewing those PRs before the author responds wastes tokens and creates
confusing parallel feedback threads.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix: honor FORCE_REVIEW and skip only stale-free CHANGES_REQUESTED reviews

Two issues addressed from Codex P2 feedback:

1. FORCE_REVIEW bypass: mention-triggered and force_review=true runs now
   bypass the CHANGES_REQUESTED skip, so authors can explicitly request a
   re-review after addressing feedback.

2. Stale review guard: only skip when a CHANGES_REQUESTED review targets
   the current head SHA. On repos without automatic stale-review dismissal,
   reviewDecision stays CHANGES_REQUESTED after new commits — the cascade
   now re-engages when the author has pushed new work.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): resolve context loss, missing checkout, and push logic (#223)

* feat: skip PRs with CHANGES_REQUESTED review decision

Adds an early-exit check alongside the existing CI guards so the cascade
skips any PR where a human reviewer has already requested changes.
Reviewing those PRs before the author responds wastes tokens and creates
confusing parallel feedback threads.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix: honor FORCE_REVIEW and skip only stale-free CHANGES_REQUESTED reviews

Two issues addressed from Codex P2 feedback:

1. FORCE_REVIEW bypass: mention-triggered and force_review=true runs now
   bypass the CHANGES_REQUESTED skip, so authors can explicitly request a
   re-review after addressing feedback.

2. Stale review guard: only skip when a CHANGES_REQUESTED review targets
   the current head SHA. On repos without automatic stale-review dismissal,
   reviewDecision stays CHANGES_REQUESTED after new commits — the cascade
   now re-engages when the author has pushed new work.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): resolve context loss, missing checkout, and push logic

- dev-lead-intent.sh: include actor and body in INTENT_CONTEXT for comments
- dev-lead.yml: parse and pass INTENT_ACTOR and INTENT_COMMENT_BODY to agent
- dev-lead-fix-reviews.sh: perform PR checkout, git push, and post summary comments

* fix(dev-lead): exit with 0 on no-changes in fix-reviews and human-pr

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* test: bypass CI check

* fix(pr-review): remove CI bypass hack

* fix(dev-lead): use heredoc for multiline environment variables (#224)

* fix(dev-lead): use heredoc for multiline environment variables

* fix(dev-lead): ensure all engines are installed and context is consistent

* fix(dev-lead): resolve stale model name in engine fallbacks

* fix(dev-lead): improve fallback reliability and script quality

- engine.sh: resolve stale model names in fallback loop; use -latest Gemini aliases
- dev-lead-fix-reviews.sh: remove unused code and fix PR_URL export

* fix(dev-lead): resolve Gemini model names and shell lint warnings

* security(dev-lead): use random heredoc delimiter to prevent inject…
don-petry added a commit that referenced this pull request Aug 8, 2026
…-stale-bot-reviews.yml (#1292)

* fix: silence auth check in fix-stuck-prs when using GitHub App token

GitHub App tokens don't have user scope, so 'gh api user' returns 403.
This is fine - the script still works for PR operations. Suppress the error
so the script completes successfully with app-token fallback label.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: use explicit author instead of @me in stuck PR search

GitHub App tokens don't have user identity, so @me search doesn't work.
Use explicit 'don-petry' author instead to find PRs to fix.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: avoid subshell in while loop to preserve variable state

Using pipe in while loop created subshell where PROBLEM_PRS and FIXED_PRS
counters were incremented but changes didn't persist to parent shell.
Fixed by using process substitution for input instead of pipe.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* docs: add comprehensive status report for GitHub App setup

- Overview of completed work
- GitHub App authentication details
- 24 stuck PRs successfully fixed with approval reviews
- Architectural decisions and rationale
- Configuration guide and usage examples
- Known limitations and troubleshooting

System is now fully operational with GitHub App token authentication,
comprehensive documentation, and all infrastructure in place.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* docs: document stuck PR cleanup status and script fixes

Added documentation for:
- GitHub App token compatibility fixes in fix-stuck-prs.sh
  * Changed @me to explicit author (app tokens have no identity)
  * Fixed subshell variable scope (process substitution vs pipe)
  * Silenced expected 403 auth check error
- Current status: 24 PRs have approvals but remain OPEN
  * Auto-merge failed due to missing GitHub App permission
  * Approvals satisfy branch protection requirement
  * Next: expand permissions or manually merge

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: update list-prs.sh to search all repos by owner instead of @me

GitHub App tokens don't have user identity, so @me/@review-requested searches
fail. Changed to enumerate all repos in don-petry and petry-projects, then
search for open PRs within each repo. This covers the full scope:
- All open PRs in personal don-petry repos
- All open PRs in petry-projects org repos

Resolves 6 consecutive workflow failures due to zero PRs being enumerated.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* docs: add investigation report for PR review agent workflow failures

* fix: move env section before steps in workflow to fix YAML structure

GitHub Actions requires env section to come before steps in job definition.
Moving env definition up and setting GH_TOKEN only in steps that need it.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* script: add backfill-approvals to retroactively apply real approvals

Converts PRs with agent approval comments (but no real GitHub approvals)
to have actual APPROVED reviews. Needed for PRs reviewed before the
gh pr review --approve fix was applied.

Usage:
  scripts/backfill-approvals.sh          # dry-run (preview changes)
  scripts/backfill-approvals.sh false    # apply real approvals

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* feat: add backfill-approvals workflow to run as GitHub App bot

Runs backfill-approvals.sh via GitHub Actions so the approvals are posted
by the bot identity, not the PR author — GitHub rejects self-approvals.

Also fixes subshell counter bug in the script (piped while loop lost variable
state; switched to process substitution).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: convert inner while loop to process substitution to preserve counters

Both the outer repo loop and inner PR loop were piped subshells, causing
all counter increments (approved/skipped/failed) to be lost. The summary
always showed 0. Fixed by using < <(...) process substitution for both loops.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* car-hunt: add VIN-deep-dive flow, printable checklists, location-tier ranking

- Step 5.5 pre-test-drive flow: NHTSA VIN decode + per-VIN unrepaired-recall
  automation via Chrome MCP (Honda LWC owner portal documented as not
  automatable; NHTSA web form works).
- §F printable test-drive checklist generator (reportlab/Platypus PDF) with
  cold-start, model-specific red flags, negotiation table, walk-away rules.
- §G fraud / paperwork verification checklist (vehicle-agnostic, reusable):
  walk-away triggers, VIN three-location match, curbstoning, odometer fraud,
  bill of sale, title transfer, payment protection, stolen-vehicle, title-
  jumping, after-purchase steps.
- Step 5.25 head-to-head comparison template with information-asymmetry rule.
- Step 3 Location Quality Tier scoring (Birmingham AL metro table A/B/C/D)
  with adj-CPM modifier; sheet schema gains Adj.CPM + Tier columns.
- Hard-disqualification rules added: non-running engine keywords, mileage
  inconsistency, mandatory description scrape.
- Drive MCP overwrite limitation documented; sheet ID now read from memory
  rather than hardcoded.
- FB Marketplace operational facts: Birmingham AL city ID 107739635926718,
  /search? vs /vehicles? query handling, React-controlled inline composer,
  send-button selector, seller-name extraction regex.
- scheduled-tasks/used-car-search-{morning,afternoon}: tier A/B priority
  flag, sheet ID read from memory.
- Reference PDF generator scripts checked in for reuse.

* fix: re-approve PRs where approval predates last commit (stale approval)

Instead of skipping PRs that already have any APPROVED review, skip only
those where reviewDecision is not REVIEW_REQUIRED. This handles PRs where
a bot approval was posted before a new commit was pushed — GitHub's ruleset
engine treats those approvals as stale even with dismiss_stale_reviews=false.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: robustly extract JSON verdict from mixed claude --print output (#17)

Claude's --print mode can prefix the JSON verdict with conversational
preamble text, causing jq to fail with parse errors. Add
extract_verdict_json to engine.sh and wire all three cascade action
call sites in review-one-pr.sh to use it.

Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: extract_verdict_json — check dest file first (agent Bash-write style)

The cascade-action agent writes its verdict JSON to \$OUTPUT_FILE via a
Bash tool call, then prints a text summary to stdout. The previous fix
only scanned stdout (the .raw file), which contained no JSON. The agent-
written file at \$dest (= \$OUTPUT_FILE) was already correct — just not
checked. Now check \$dest first before falling back to stdout scan.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Enforce MAX_REVIEW_CYCLES cap before running the cascade (#18)

* fix: enforce MAX_REVIEW_CYCLES cap with human-escalation marker

Previously MAX_REVIEW_CYCLES only gated AI delegation in
post-pr-review.sh — the cascade itself ran on every cron tick
regardless of how many cycles had accumulated. Real-world result:
a PR could rack up 9+ review cycles (we observed exactly that on
ContentTwin#100) before any cap took effect.

Add a pre-cascade check in review-one-pr.sh: when the count of
existing `<!-- pr-review-agent v1 sha=... -->` markers is at or
above MAX_REVIEW_CYCLES (default 3), post one escalation comment
marked `<!-- pr-review-agent escalation -->`, label
needs-human-review, request don-petry, and exit 100 (skip
sentinel — doesn't burn the MAX_PRS budget). The escalation
marker doubles as the no-spam guard: subsequent runs detect it
and exit 100 immediately.

Also reuse a single `gh pr view` for both the cycle count and the
escalation-marker check.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* review feedback: surface escalation-comment failures, drop dead unset

- Drop `2>/dev/null || true` on `gh pr comment` so a failed escalation
  post is visible in the workflow log instead of silently leaving the
  PR with no marker (which would re-trigger the cap path next tick).
- Reword the cycle-count comment from "AI delegation loops" to "review
  loops" — the cap also catches cascade-only loops where every cycle
  approves and a new commit lands before merge.
- Remove the redundant `unset PR_BODIES` — never exported, no leakage.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>

* Reliability hardening: session circuit breaker, timeouts, retry, dedup (#20)

* feat: session circuit breaker, per-tier timeouts, retry, and triage hard-fail

Reliability hardening for the PR review agent.

1. Session circuit breaker (.github/workflows/pr-review.yml): on any
   non-zero, non-100 exit from review-one-pr.sh (general failure or rate
   limit on the fallback engine), break the per-PR loop, log a clear
   error annotation naming the failing PR and reason, and exit the step
   with code 1 so the run shows red. Prevents one systemic problem from
   silently burning the entire candidate pool.

2. Per-tier timeouts (scripts/engine.sh): triage/deep/audit/action/duck
   each get their own bounded timeout (180/600/600/300/300s defaults,
   env-overridable). Previously only the duck had a timeout — a hung
   tier could burn the whole 60min job budget.

3. Retry-with-backoff on transient errors (scripts/engine.sh): triage
   retries once on 124/137/143 (timeout / signal kill) since its caller
   captures stdout via $(...) so retries are safe. Deliberately NOT
   applied to run_agentic/run_duck where stdout is redirected to a file
   — a retry there would corrupt the partial first-attempt output.

4. Triage non-JSON now hard-fails (scripts/review-one-pr.sh): replaces
   the silent fallback that synthesized a fake "escalate=MEDIUM" verdict
   and proceeded to deep review. With the new circuit breaker, loud
   failure is the right call — masking a broken triage was burning
   tokens on every PR while the workflow looked healthy.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix: stop stacking duplicate agent reviews on the same PR

Two bugs were causing the agent to leave multiple comments on the same PR.
Together they produced 10 stacked APPROVED reviews on petry-projects/ContentTwin#100.

Bug A — idempotency check is order-dependent (review-one-pr.sh):
The previous marker-discovery code did:
  ((.reviews // []) + (.comments // [])) | .[].body | grep marker | tail -1
This relies on the array concatenation order, not chronological order. When
old agent comments existed alongside newer agent reviews, tail -1 picked
the comment-array marker (older) over the review-array marker (newer),
causing the script to think the head SHA hadn't been reviewed and re-run.
Replaced with a single jq pipeline that tags each item with submittedAt /
createdAt, sorts by timestamp, and takes the actual most-recent marker.

Bug B — no cleanup of prior agent items (post-pr-review.sh):
After successfully posting a new review/comment, prior agent items were
left in place, accumulating forever. Added mark_prior_agent_items_obsolete
which, after a successful post:
  - dismisses prior APPROVED/COMMENTED/CHANGES_REQUESTED agent reviews via
    the GitHub dismissal API (UI shows them struck-through as Dismissed)
  - edits prior agent comments to wrap their body in a collapsed <details>
    block with a "Superseded by re-review at <SHA>" summary, plus a
    `<!-- pr-review-agent superseded -->` sentinel for idempotency

All cleanup API calls are best-effort — failures don't break the workflow.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(cleanup): warn on API failures, preserve global newest, file-stage JSON

Three fixes to mark_prior_agent_items_obsolete from the review of PR #20:

1. ::warning:: annotations on every cleanup API failure (review/comment
   list-fetch, individual review dismissal, individual comment fetch+edit).
   Previously these were silenced with `|| true`, so a permissions change
   on the dismissal endpoint would let duplicates stack indefinitely with
   no signal in the Actions UI. Cleanup is still non-fatal — the new post
   has already landed — but failures are now visible.

2. Preserve the globally-latest agent item across BOTH categories, not the
   newest of each category separately. The earlier code split reviews and
   comments and applied `[:-1]` to each, which left a stale fix-request
   comment in place when the new post was a review (or vice versa). The
   one-off cleanup of ContentTwin#100 hit exactly this case: 12 stacked
   reviews collapsed to 1, but a stale comment from 2026-04-25 (SHA
   cd9132d6) was preserved as "newest comment" even though the latest
   review at SHA 3af8c8ee was newer overall. Now: compute the max
   timestamp across both feeds, exclude items at that timestamp.

3. Stage API responses to disk (`mktemp` + `jq <file>`) instead of routing
   through `--argjson "$var"`. The old approach broke on rare unescaped
   control chars in user-authored comment bodies (jq refused to parse the
   resulting shell-vared JSON). File-based input sidesteps the shell
   pipeline entirely.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>

* Remove car-hunt skill and scheduled tasks (moved to don-petry/don-petry) (#21)

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: apply Copilot fallback hardening and reuse GH_PAT for Copilot auth

- Use secrets.GH_PAT (existing personal account token with Copilot
  subscription) instead of a separate COPILOT_GITHUB_TOKEN secret
- Pin actions/create-github-app-token to SHA (v3.1.1) for supply chain safety
- Add post-install verification for gh-copilot extension with warning on failure
- Guard Copilot fallback path: skip PR gracefully if extension is not installed

Addresses issue #24 workflow fixes (1-3) that could not land via PR #25
due to GitHub App lacking workflows permission.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: switch Copilot engine to gh built-in and fix app-id deprecation

- Remove gh extension install (github/gh-copilot conflicts with built-in alias)
- Replace with gh copilot --version check using COPILOT_GITHUB_TOKEN (GH_PAT)
- Update all engine.sh copilot invocations from bare `copilot` binary to
  `gh copilot suggest --target shell` with GH_TOKEN overridden to GH_PAT
  so the user token (with Copilot subscription) is used, not the App token
- Fix actions/create-github-app-token: app-id → client-id (same secret value,
  just the renamed input in v3.x)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: harden Copilot fallback path and remove stale worktrees (#25)

- gh-copilot install: add --force flag and post-install verification
  warning so silent failures due to built-in alias conflict are visible
  in workflow logs instead of being silently swallowed by `|| true`
- fallback pre-flight check: verify `gh extension list | grep copilot`
  before switching to Copilot engine; if unavailable, skip the PR and
  continue the batch rather than session-aborting and dropping all
  remaining candidates (fixes the 27-PR drop on run #503)
- pin actions/create-github-app-token to SHA for v3.1.1 (Node.js 24
  compatible) to prevent hard break on 2026-06-02 when GitHub forces
  Node.js 24 as default runtime
- remove 6 stale Claude Code worktrees from git tracking; add
  .gitignore entry to prevent future worktrees from being committed
  (eliminates exit-128 warning on every actions/checkout post-job sweep)

Closes #24

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: petry-projects-pr-review-agent[bot] <petry-projects-pr-review-agent[bot]@users.noreply.github.com>

* ci: add pre-flight dedup check to prevent duplicate claude-issue PRs (#26)

Before invoking Claude on a labeled issue, a new shell step queries
for any open PR with a branch matching claude/issue-NNN-* (or a body
containing "Closes #NNN"). If one is found it posts a comment on the
issue pointing to the existing PR and skips the Claude step entirely.
A secondary prompt instruction tells Claude to check first and push to
the existing branch rather than opening a new PR.

Motivated by issue petry-projects/google-app-scripts#171, where the
claude label was re-applied three times and each run created a fresh
PR unaware of the prior attempts.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor: migrate to machine user PAT auth (closes #27) (#28)

* refactor: migrate from GitHub App to machine user PAT auth

Closes #27

GitHub Apps cannot be listed in CODEOWNERS, blocking PRs in repos with
require_code_owner_review: true. Switch all workflows to use a machine
user account's fine-grained PAT (DON_PETRY_BOT_GH_PAT secret), which can
join an org team listed in CODEOWNERS.

Workflows: removed actions/create-github-app-token steps in pr-review,
fix-stuck-prs, backfill-approvals, and daily-pr-review-health. All now
use secrets.DON_PETRY_BOT_GH_PAT directly.

Docs: renamed GITHUB_APP_SETUP.md to MACHINE_USER_SETUP.md with full
rewrite covering account creation, CODEOWNERS config, PAT generation,
and rotation. Updated auth sections in IMPLEMENTATION.md, SETUP.md,
STATUS.md, DOCUMENTATION.md, README.md.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* refactor: switch to org-scoped PAT secret DON_PETRY_BOT_PETRY_PROJECT_PAT

Replace DON_PETRY_BOT_GH_PAT with DON_PETRY_BOT_PETRY_PROJECT_PAT — the
new fine-grained PAT scoped to the petry-projects org (resource owner =
petry-projects). The previous PAT was scoped to the donpetry-bot personal
namespace and had no repository access.

The old DON_PETRY_BOT_GH_PAT secret is retained for any future use against
don-petry's personal repos.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* chore: ignore .claude/scheduled_tasks.lock state file

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>

* refactor: merge approval repair scripts into single automation

- Consolidate backfill-approvals.sh and fix-stuck-prs.sh into repair-pr-approvals.sh
- Iterate all repos in both orgs instead of assuming author
- Verify no existing APPROVED review before posting
- Copy original agent comment as review body
- Enable auto-merge when posting approval if needed
- Single workflow with 30-min timeout

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: add @mention trigger for on-demand PR reviews (#30)

Adds repository_dispatch support so commenting @petry-review-bot on any
PR fires an immediate review without waiting for the hourly schedule.

- pr-review.yml: new `repository_dispatch` trigger (type: pr-review-mention),
  per-PR concurrency group for mention runs, FORCE_REVIEW and DRY_RUN
  env vars that handle both workflow_dispatch and repository_dispatch paths
- review-one-pr.sh: FORCE_REVIEW=true bypasses idempotency so a mention
  always runs a fresh cascade even if the head SHA hasn't changed
- templates/mention-listener.yml: deploy to petry-projects/.github;
  listens for @petry-review-bot, validates commenter trust, posts ack,
  sends repository_dispatch (requires Contents:write, not Actions:write)

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: harden rebase, review dismissal, and health log diagnostics (#40)

Addresses 4 code-actionable recommendations from health check report #33:

- [CRITICAL] Make rebase/branch-update non-fatal — 403/504 during rebase
  emits ::warning:: instead of aborting the batch session (#34)
- [MEDIUM] Add retry with exponential backoff (3 attempts) for transient
  5xx on update-branch API; break immediately on 4xx (#37)
- [MEDIUM] Guard review dismissal with state re-check before calling
  dismiss API, preventing 422s from race conditions (#38)
- [LOW] Surface missing log warnings in health check script (#39)
- Fix: skip auto-merge when branch is still BEHIND after failed rebase

Closes #34, closes #37, closes #38, closes #39

* refactor: migrate to org-wide .github-private convention (#41)

* refactor: parametrize hardcoded identity values for org migration

Replace all hardcoded references to don-petry, petry-review-bot, and
don-petry/pr-review-agent with environment variables that default to
the current values. This allows the agent to be configured for different
orgs/users via repo variables.

Changes:
- Scripts use $REVIEWER_USER, $TARGET_ORG, $BOT_USER, $AGENT_REPO
- Prompts no longer reference specific GitHub usernames
- Workflows use org-level GH_PAT_WORKFLOWS secret instead of
  repo-level DON_PETRY_BOT_PETRY_PROJECT_PAT
- Health check uses context.repo.owner/repo for issue creation
- Variables are set in workflow env block with defaults

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add Copilot custom agent profiles for org-wide use

Create /agents/ directory with three agent profiles:
- pr-reviewer: Multi-tier cascading PR review
- feature-ideator: Feature idea generation and prioritization
- compliance-auditor: Org standards compliance checking

These are Copilot custom agent profiles that become available
org-wide in the .github-private repo convention.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Squashed 'frameworks/bmad-method/' content from commit e36f219c

git-subtree-dir: frameworks/bmad-method
git-subtree-split: e36f219c81b6010d4aae423ba12f49edb5b6e31a

* Squashed 'frameworks/spec-kit/' content from commit 11f49ebf

git-subtree-dir: frameworks/spec-kit
git-subtree-split: 11f49ebfb2f6af55345cb4bd9a7906acd211e56f

* Squashed 'frameworks/gsd/' content from commit 304c1a13

git-subtree-dir: frameworks/gsd
git-subtree-split: 304c1a1302564c45af252bbba4bcc5350e7dac3a

* docs: update README for .github-private org infrastructure role

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* ci: add dependabot-automerge.yml workflow (#73)

Adds the required dependabot-automerge.yml workflow from the org
standard template (petry-projects/.github/standards/workflows/).
This is a thin caller stub that delegates to the org-level reusable
workflow.

Closes #48

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Don Petry <don-petry@users.noreply.github.com>

* fix(pr-review): unblock queue starvation from self-authored PRs (#96) (#97)

* fix(pr-review): unblock queue starvation from self-authored PRs (#96)

A self-authored PR sorted first in the candidate list and triggered
"Can not approve your own pull request" — which the session-fatal
abort treated like an engine error, skipping all 28 remaining PRs
on every run.

- list-prs.sh: filter out PRs authored by REVIEWER_USER at enumeration
- post-pr-review.sh: catch the GraphQL self-approval error and exit 100
  (no-op) instead of 1, so a stray self-PR can no longer abort the batch
- engine.sh: gh copilot renamed --target to --agent; fix the rubber-duck
  invocations so tier-2 cross-engine review works again
- review-one-pr.sh: stop appending a duplicate "0" to REVIEW_CYCLE when
  grep -c finds no markers under set -o pipefail (was breaking the
  cycle-cap integer comparison)

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

* fix(pr-review): authenticate as bot, harden enumeration, address review

- Workflow now runs as AGENT_USER (default don-petry-bot), distinct from
  REVIEWER_USER (the human, don-petry). The self-approval rejection that
  caused #96 came from the agent and the human sharing one identity.
- list-prs.sh filters self-authored PRs against AGENT_USER, and validates
  AGENT_USER against the GitHub username charset before interpolating into
  the jq filter (Copilot review feedback).
- review-one-pr.sh uses printf '%s\n' instead of echo for PR_BODIES, since
  PR body content is user-authored and could begin with -n/-e or contain
  backslash escapes (Copilot review feedback).
- AGENT.md guideline updated to reflect that self-authored PRs are
  intentionally excluded (CodeRabbit review feedback).

Operator note: rotate the GH_PAT_WORKFLOWS secret to a token owned by
don-petry-bot (with read:org added) for the bot-account behavior to take
effect at runtime.

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

* refactor(pr-review): collapse to single BOT_USER, route escalations via CODEOWNERS

Drop AGENT_USER and REVIEWER_USER. The workflow now has one identity:
BOT_USER (default don-petry-bot), which both owns the repos to scan and
gets filtered out as the self-approval blocker. Human escalation no
longer hard-codes a single reviewer; instead, scripts/request-codeowners-
review.sh parses CODEOWNERS in the PR's repo and requests review from
every @user / @org/team mention.

- list-prs.sh: BOT_USER for both gh-repo-list and self-author filter
- review-one-pr.sh, post-pr-review.sh: replace --user "$REVIEWER_USER"
  with the CODEOWNERS helper
- repair-pr-approvals.sh: same gh-repo-list switch
- pr-review.yml: drop AGENT_USER/REVIEWER_USER, set BOT_USER default
  to don-petry-bot
- AGENT.md: updated guideline to describe single-identity model

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

* refactor(pr-review): extract batch loop, hoist env, drop redundant step

Workflow YAML drops from 231 to 111 lines by moving the per-PR review
loop into scripts/review-batch.sh. The script can be syntax-checked and
exercised locally; the workflow now just wires env vars and dispatches.

- Hoist GH_TOKEN, MAX_PRS, CANDIDATE_LIMIT to job-level env (were
  repeated on individual steps).
- Inline `gh auth status` into the install step; remove the standalone
  Verify auth step (its only output was a one-line auth dump).
- Drop the `if: steps.list.outputs.count != '0'` guard and the step's
  `id`/output: review-batch.sh handles empty input as a no-op.
- Collapse the duplicated summary-string branch in the review loop into
  a single template with a conditional fragment.

No behavior change.

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

* perf(pr-review): cache claude-code CLI install across runs

`npm install -g @anthropic-ai/claude-code` ran on every workflow start
(~30s). Switch to a per-user npm prefix (~/.npm-global) and cache that
directory via actions/cache, keyed on CLAUDE_CODE_VERSION + runner OS.

A `command -v claude` guard makes the install a no-op on cache hit, so
the only cost on subsequent runs is the cache restore.

CLAUDE_CODE_VERSION defaults to 'latest' (cache persists until manually
flushed); set the repo variable to pin a specific version for fully
reproducible caching.

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

---------

Co-authored-by: Claude <noreply@anthropic.com>

* chore: rename GH_PAT_WORKFLOWS secret to DON_PETRY_BOT_GH_PAT (#99)

The previous name was generic and didn't tell you which account the PAT
belonged to. The new name makes the binding explicit: this secret is the
PAT owned by don-petry-bot, used as BOT_USER throughout the PR-review
workflows.

Operator follow-up before this can be merged:
- Add a new repo secret DON_PETRY_BOT_GH_PAT containing the bot's PAT
  (with repo, workflow, and read:org scopes).
- After merge, the old GH_PAT_WORKFLOWS secret can be deleted.

Affected workflows:
- pr-review.yml (1 use)
- claude.yml (4 uses, with || github.token fallback)
- daily-pr-review-health.yml (1 use)
- repair-pr-approvals.yml (1 use)

scripts/pr_review_health.sh's error message is updated to point at the
new name as well.

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

Co-authored-by: Claude <noreply@anthropic.com>

* chore: remove frameworks directory (#101)

Co-authored-by: Gemini CLI <gemini-cli@example.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore all erroneously deleted files (items 4-34) (#104)

* revert: restore .github/workflows/daily-pr-review-health.yml

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore .github/workflows/repair-pr-approvals.yml

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore agents/pr-reviewer.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/cascade-action.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/deep-review.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/rubber-duck.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/security-audit.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/shared.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/single-review.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/synthesize-duck.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/synthesize.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/triage.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/engine.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/list-prs.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/post-pr-review.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/pr_review_health.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/repair-pr-approvals.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/request-codeowners-review.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/review-batch.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/review-one-pr.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore templates/mention-listener.yml

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore AGENT.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore BOT_SETUP.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore DOCUMENTATION.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore IMPLEMENTATION.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore MACHINE_USER_SETUP.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore PR_REVIEW_AGENT_FAILURE_REPORT.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore PR_REVIEW_FAILURE_INVESTIGATION.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore SETUP.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore STATUS.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore README.md to pre-change state

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore .github/workflows/daily-pr-review-health.yml

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat: add Gemini Pro support and optimize PR review fallback chain (#102)

* Merge main and resolve conflicts

* Enhance pr-review workflow: add Gemini support and refactor fallback logic

* Address Copilot review comments: tighten regex, fix fallback summary, and align docs

* docs: move agent documentation to docs/ folder and rename files

* docs: align secret names and fix casing in index

* docs: organize pr-review-agent documentation under dedicated folder

* Address final PR comments: tighten regex, add preflight checks, fix bot names, and cleanup docs

---------

Co-authored-by: Gemini CLI <gemini-cli@example.com>

* fix(pr-review): gate rate-limit detection on non-zero exit code

Broad patterns like `plan.*limit` and `claude.*usage` could match content
in a successful triage summary, triggering a false-positive engine fallback.
Guard the check with TRIAGE_RC != 0 so it only fires when the provider
command actually failed.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(claude): sync inlined workflow with org standard (#109)

* fix(claude): sync inlined workflow with org standard

Brings the inlined claude.yml up to parity with
petry-projects/.github/.github/workflows/claude-code-reusable.yml.

Changes:
1. Bot allow list for pull_request_review_comment
   Add coderabbitai[bot], Copilot, copilot-pull-request-reviewer[bot],
   and gemini-code-assist[bot] alongside the existing OWNER/MEMBER/
   COLLABORATOR check. These bots have author_association 'NONE' so
   their review comments were always skipped.

2. check_run trigger + claude-ci-fix job
   Port the CI failure auto-fix feature from the reusable. When a check
   fails on a PR, Claude diagnoses and fixes it automatically.
   Adapted to use DON_PETRY_BOT_GH_PAT (this repo's secret name).

3. claude-code-action SHA bump: v1.0.89 → v1.0.119
   Update both the claude and claude-issue jobs.

4. paths-ignore on pull_request trigger
   Prevents the workflow from firing on PRs that only change
   claude.yml itself, avoiding the Anthropic OIDC validation
   failure (workflow file must match default branch at token exchange).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(claude): guard bot allow list against fork PRs

Same fix as petry-projects/.github PR #238 — add same-repo guard
for bot-triggered pull_request_review_comment runs.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(claude): address ci-fix self-loop and fork PR security

- Replace Claude Code name prefix check with explicit job name list to
  correctly prevent self-loops (check_run names for inlined workflows
  are bare job names, not workflow-prefixed)
- Add fork PR trust gate in Resolve PR number step: verify head repo
  matches target repo before running Claude with privileged credentials

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* ci: replace inlined claude.yml with standard thin-caller stub

The inlined version embedded all three jobs (claude, claude-ci-fix,
claude-issue) directly and threaded DON_PETRY_BOT_GH_PAT through
checkout tokens and github_token, causing the bot's PAT to author
PRs and comments instead of github-actions[bot].

Replace with the standard thin caller that delegates to the org-level
reusable workflow (claude-code-reusable.yml@v1) via secrets: inherit,
which is the correct pattern per:
petry-projects/.github/standards/workflows/claude.yml

* fix: replace gh copilot suggest with GitHub Models REST API (#151)

* fix: replace gh copilot suggest with GitHub Models REST API (#147)

The `gh copilot suggest -p "$(cat <file>)"` invocation failed with
"Invalid command format" because:
1. The `-p` flag is not valid syntax in modern `gh copilot` built-in versions.
2. `gh copilot suggest` is a shell-command suggestion tool; it does not
   accept large PR prompts or return structured JSON responses.
3. Passing a multi-thousand-line prompt via `$(cat ...)` can hit ARG_MAX.

The non-zero exit was then misclassified by the rate-limit detector, which
aborted the entire session and skipped all remaining PRs in the queue.

Fix: replace all three copilot invocations (run_triage, run_agentic,
run_duck) with a new `copilot_chat` helper that calls the GitHub Models
REST API directly via curl. The API is OpenAI-compatible, versioned via
`X-GitHub-Api-Version`, stable against gh CLI version changes, and accepts
arbitrary prompt sizes using python3 for safe JSON encoding.

Additional changes:
- Add `COPILOT_API_MODEL` var (default `openai/o4-mini`) overrideable via env.
- run_agentic/run_duck copilot cases now also write output to $OUTPUT_FILE
  so callers that check that path directly (deep review, audit) find the JSON.
- Rate-limit responses (HTTP 429) are echoed to stdout so the existing
  `is_rate_limited()` detector fires correctly for engine fallback.
- Add pre-flight smoke test in review-batch.sh: tests GitHub Models API
  connectivity with a one-liner prompt before processing any PRs, so auth
  or model errors surface immediately as a clear setup failure.
- Add tests/test_copilot_chat.sh: unit tests for the JSON payload builder
  with edge-case prompts (quotes, newlines, # headings, large diffs, Unicode).

Closes #147

Co-authored-by: Don Petry <don-petry@users.noreply.github.com>

* fix: scope COPILOT_API_MODEL export to copilot engine only

Move the `export COPILOT_API_MODEL` from the common exports block into the
`copilot)` case where the variable is set, so it is not exported as an
empty/unset variable when the engine is `claude` or `gemini`. The
`copilot_chat` fallback `${COPILOT_API_MODEL:-openai/o4-mini}` still works
correctly when `DUCK_ENGINE=copilot` under a non-copilot primary engine.

Co-authored-by: Don Petry <don-petry@users.noreply.github.com>

* fix: address review comments — temp file for payload, fail-fast source, streaming output

- copilot_chat: write JSON body to mktemp file, pass to curl as @file to
  avoid ARG_MAX for large PR diffs (was --data-binary "$body")
- copilot_chat: add :? guard on COPILOT_GITHUB_TOKEN for a clear error
  instead of generic "unbound variable" under set -u
- run_agentic / run_duck copilot paths: stream directly to stdout (and
  tee to OUTPUT_FILE when set) rather than buffering the full response
  into a shell variable, which forced large outputs into memory and
  stripped trailing newlines
- review-batch.sh pre-flight: fail fast if source engine.sh fails
  (was silently ignored with || true)
- review-batch.sh pre-flight: build smoke-test JSON payload via python3
  + temp file instead of shell string interpolation to avoid JSON
  injection if COPILOT_API_MODEL contains special characters
- Clarify that openai/o4-mini is the correct April-2025 o4-generation
  model name, not a typo for o1-mini or gpt-4o-mini

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Don Petry <don-petry@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* feat: prioritise .github/.github-private PRs, oldest-first within tier (#155)

* feat: prioritise .github/.github-private PRs, oldest-first within tier

- list-prs.sh: add createdAt to JSON fetch; emit priority|createdAt|url
  lines (priority 0 for .github/.github-private, 1 for everything else)
- Replace final sort -u with a two-pass sort: deduplicate by URL, then
  sort by priority asc then createdAt asc; strip sort keys with cut
- tests/test_list_prs_sort.sh: 16 tests covering priority classification,
  oldest-first ordering, mixed scenarios and deduplication
- .github/workflows/test.yml: run both unit-test files on every PR / push

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* test: add dedup edge case for same URL with conflicting priorities

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: remove temperature from copilot_chat payload (o4-mini incompatible)

The GitHub Models API rejects temperature=0 for reasoning models like
o4-mini with HTTP 400:
  'Unsupported value: temperature does not support 0 with this model.
   Only the default (1) value is supported.'

This caused the rubber duck (DUCK_ENGINE=copilot / DUCK_MODEL=o4-mini)
to fail with HTTP 400 on every claude-engine run, silently degrading
every review to deep-only with no cross-engine sanity check.

Fix: remove temperature from the copilot_chat JSON payload entirely —
the API defaults to 1, which is the only supported value for o4-mini.

Also:
- Update tests/test_copilot_chat.sh: sync build_payload to match and
  flip Test 9 to assert temperature is ABSENT (not 0).
- Add .github/workflows/test.yml: run unit tests on every PR and push
  to main so regressions are caught before merge.

Reproducer: job/75690347409 — duck failure logged as:
  copilot_chat: HTTP 400 from GitHub Models API
  {"error":{"message":"Unsupported value: 'temperature' does not
   support 0 with this model..."}}

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: address PR review comments

- list-prs.sh: tighten priority regex to /[.]github(-private)?/pull/
  so path-boundary anchor prevents false positives (e.g. foo.github)
- list-prs.sh: replace printf pipeline with here-string to avoid ARG_MAX
  limits on large PR sets
- test.yml: add permissions: contents: read (least-privilege, CodeQL fix)
- test.yml: add timeout-minutes: 5 to prevent hung runs
- test_list_prs_sort.sh: sync sort_entries helper (here-string + || true)
  and classify regex to match updated list-prs.sh exactly

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: don-petry <don@petry.dev>

* fix: restore pr-review.yml content (accidentally emptied in revert)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: restore pr-review.yml (file was accidentally emptied)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore(deps): bump actions/cache from 4 to 5 (#165)

Bumps [actions/cache](https://github.com/actions/cache) from 4 to 5.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): bump petry-projects/.github/.github/workflows/claude-code-reusable.yml (#164)

Bumps [petry-projects/.github/.github/workflows/claude-code-reusable.yml](https://github.com/petry-projects/.github) from 1 to 2.
- [Commits](https://github.com/petry-projects/.github/compare/v1...v2)

---
updated-dependencies:
- dependency-name: petry-projects/.github/.github/workflows/claude-code-reusable.yml
  dependency-version: '2'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* fix(list-prs): search DELEGATION_ORGS and drop --checks success pre-filter

- Add iteration over all orgs in DELEGATION_ORGS (e.g. don-petry) so PRs
  in those orgs enter the candidate pool. Previously only BOT_USER and
  TARGET_ORG were searched.
- Remove --checks success from org repo searches. GitHub excludes PRs from
  repos with no CI configured when this flag is used, silently dropping all
  PRs from repos like .github, .github-private, google-app-scripts, and
  TalkTerm. review-one-pr.sh already enforces CI gating per-PR and treats
  empty statusCheckRollup as passing, so the pre-filter is redundant and
  harmful.
- Add --limit 200 to gh repo list calls to handle org growth beyond gh's
  default 30-repo cap.

* feat: add code-quality ruleset (compliance fix #60) (#86)

Creates the required `code-quality` repository ruleset enforcing required
status checks on the default branch, as mandated by the org standard:
standards/github-settings.md#code-quality--required-checks-ruleset-all-repositories

Required checks:
- SonarCloud (code quality analysis)
- CodeQL (SAST)
- agent-shield / AgentShield (agent security scan)
- dependency-audit / Detect ecosystems (dependency vulnerability scan)

Bypass actors:
- OrganizationAdmin (always) — emergency override
- dependabot-automerge-petry Integration (always) — Dependabot auto-merge

The ruleset was applied directly via GitHub API. This file documents the
configuration as code for auditability and future reapplication.

Closes #60

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Don Petry <don-petry@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* feat(prompts/dev-lead): add human prompt template

* feat(prompts/dev-lead): add human-pr prompt template

* feat(dev-lead): implement dev-lead agent Phases 0-6

* fix: per-PR isolation and single-review retry (closes #132)

- review-batch.sh: non-rate-limit per-PR failures (exit code 1) no
  longer abort the session. SESSION ABORTED EARLY is now reserved for
  the rate-limit-on-fallback-engine case (exit code 2) only. All other
  failures are counted and logged; remaining candidates continue.

- review-one-pr.sh: single-review step retries up to
  SINGLE_REVIEW_MAX_RETRIES (default 2) times with a
  SINGLE_REVIEW_RETRY_DELAY_SEC (default 15s) gap before giving up.
  On exhaustion, the PR is flagged needs-human-review and the script
  exits with code 1, which the updated batch treats as a non-fatal
  per-PR failure. Raw model output and stderr are logged on each failed
  attempt for post-mortem visibility.

Root cause of run #25707852006: claude-opus-4-7 returned a verbose
non-JSON response for PR #129; the old code treated that as fatal and
skipped 35 remaining candidates.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix: rate-limit detection and per-attempt stderr logs in single-review retry

Address inline review comments on PR #133:

- Rate-limit check: after each run_agentic call, inspect both stdout
  (VERDICT_JSON.raw) and stderr (SINGLE_LOG) with is_rate_limited before
  retrying. A rate-limit match exits immediately with code 2 so
  review-batch.sh can trigger engine fallback — consistent with triage and
  deep-review tiers. Previously a rate-limited single-review would burn all
  retries and exit 1 (per-PR failure), silently leaving the batch on the
  same rate-limited engine for all remaining PRs.

- Per-attempt log files: stderr is now written to
  single-review-attempt-N.log rather than a single overwritten file, so
  no earlier-attempt errors are lost. Each attempt logs its own stderr
  inline on failure; the fallback path cats all attempt logs for
  post-mortem visibility.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* feat(dev-lead): Phase 0 test infrastructure and Phase 1 intent stub

Phase 0: full test harness for the dev-lead agent —
  26 event fixtures (all valid JSON with _test_expected_intent),
  stub claude/gemini engines, mock gh binary, CI failure log sample,
  bats helpers (stub-engine, mock-gh, assert-env, prompt-vars),
  7 prompt templates with VARIABLES declarations, preflight script,
  prompt coverage integration test, and test-dev-lead.yml CI workflow.

Phase 1: dev-lead.yml trigger workflow (all 7 event types, dispatch + ci-relay
  jobs) and dev-lead-intent.sh stub (anti-loop guard live; all other events
  emit skip/not-implemented).  14/14 bats unit tests pass.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): add permissions blocks to test workflow jobs (CodeQL)

* fix(ci): auto-fix for lint / eslint [skip ci-relay]

* feat(dev-lead): Phases 2-6 — CI fix, review fix, issue, engine fallback

Phase 2: run_writer/run_writer_with_fallback in engine.sh, full intent
routing in dev-lead-intent.sh, dev-lead-fix-ci.sh handler, workflow wiring.

Phase 3: dev-lead-fix-reviews.sh handles fix-reviews, fix-bot-comment,
human, human-pr, rebase intents. Full review routing in intent classifier.

Phase 5: dev-lead-fix-issue.sh handles the issue intent with dedup guard
and branch/PR creation.

Phase 6: run_writer_with_fallback with claude→gemini→copilot fallback chain.

Tests: 77 unit tests across 8 new .bats files, all passing. Updated Phase 1
stub tests to reflect actual Phase 2+ routing behavior.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): address review comments — dispatch JSON, dead code, portable stub gh

* fix(dev-lead): pre-flight after intent, fork URL check, review-batch exit code, retry comment

* fix(dev-lead): install bats-core from GitHub to avoid root requirement

* fix(security): move event values to env vars to prevent script injection (SonarCloud)

* fix(security): move CLAUDE_CODE_VERSION to env block (SonarCloud script injection)

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* feat(dev-lead): Phase 1.5 — reusable workflow for cross-repo adoption + Phase 7 shadow period

* feat(dev-lead): add reusable workflow (Phase 1.5) and begin shadow period (Phase 7)

- Create .github/workflows/dev-lead-reusable.yml: workflow_call entry
  point for other repos. Checks out .github-private scripts/prompts into
  .dev-lead/, then runs the same intent-classify + handler pipeline as
  dev-lead.yml with PROMPTS_DIR=.dev-lead/prompts/dev-lead.
- Add PROMPTS_DIR env-var support to dev-lead-fix-ci.sh,
  dev-lead-fix-reviews.sh, and dev-lead-fix-issue.sh so the reusable
  workflow can point scripts at the sparse-checkout path without changing
  CWD. Defaults to prompts/dev-lead (backwards-compatible for dev-lead.yml).
- Annotate dev-lead.yml with Phase 7 shadow-period window (2026-05-15
  through ~2026-05-29): claude.yml and dev-lead.yml run in parallel until
  regressions clear, then claude.yml is removed.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* chore(dev-lead): update shadow-period tracking issue ref to #180

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): document PAT requirement and pin ref:main on private checkout

* fix(dev-lead): P1 review fixes — ci-relay in reusable, .dev-lead gitignore, export PROMPTS_DIR, trim header

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): rate-limit detection, separate status, and retry cron (#192) (#196)

* chore(dev-lead): decommission claude.yml, bump ACTION_TIMEOUT_SEC to 600s

- Delete .github/workflows/claude.yml — replaced by dev-lead.yml (shadow
  period complete, tracking issue #180).
- scripts/engine.sh: raise ACTION_TIMEOUT_SEC default 300→600s to reduce
  timeout failures on large-repo fix-ci runs (PR #80 had 3 timeouts).
- AGENTS.md: remove claude.yml immutability exemption; note dev-lead.yml
  as the active AI automation workflow and its edit-via-reusable pattern.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* refactor: replace Claude analysis with pure gh/jq telemetry in health check

* refactor: drop Node/Claude steps from daily-pr-review-health workflow

* rename: daily-pr-review-health → actions-fleet-monitor

* rename: daily-pr-review-health → actions-fleet-monitor

* fix(agents-md): clarify dev-lead.yml vs dev-lead-reusable.yml scope

dev-lead.yml in .github-private runs inline steps (not a caller stub).
Behavior changes for this repo go to dev-lead.yml directly; changes that
affect all org repos via the cross-repo reusable go to dev-lead-reusable.yml.

Addresses Copilot review on PR #194.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): rate-limit detection, separate status, and retry cron

Closes #192. Implements all phases from the revised plan.

**Phase 0 — fix run_writer stdout capture (prerequisite)**
- engine.sh: capture stdout via `tee` to a tempfile so `is_rate_limited`
  can inspect the output; old code read `/tmp/dev-lead-writer-stderr`
  which was never written (claude --print outputs to stdout, not stderr).
  Fallback engines were never tried in practice.
- engine.sh: add `parse_reset_time` to extract ISO timestamp from
  `resets H:MMpm (UTC)` in engine output and write it to
  `/tmp/dev-lead-rate-limit-reset` for callers to embed in markers.

**Phase 1 — separate status=rate-limited from status=failed**
- fix-ci.sh: detect engine exit 2 → post `status=rate-limited` (not
  `status=failed`); embed parsed reset time in marker body; exit 2.
- fix-ci.sh: fix `check_idempotency` to treat `status=rate-limited`
  as retriable — only block on terminal statuses (applied, failed,
  no-changes). Previously, rate-limited markers blocked all retries.
- fix-ci.sh: `count_recent_failures` already filtered to `status=failed`;
  add explicit comment confirming rate-limited markers are excluded.
- fix-ci.sh: add `has_rate_limited_marker` dedup check so the same SHA
  never accumulates more than one rate-limited comment.
- fix-reviews.sh: on engine exit 2 for all five intent types
  (fix-reviews, fix-bot-comment, human, human-pr, rebase), post a
  `status=rate-limited` marker with embedded reset time and exit 2.
- fix-reviews.sh: for `human` and `human-pr` intents, also post a
  user-visible acknowledgment comment so users know their request was
  received and will be retried.

**Phase 2 — scheduled retry cron for fix-ci**
- dev-lead-retry.sh: new script scans all open PRs across TARGET_ORG
  for `status=rate-limited` markers on current HEAD SHA; skips PRs
  whose reset time is still in the future; staggered dispatches (30s
  between repos) to prevent cascading org-level rate-limit hits.
- dev-lead-retry.yml: new workflow — scheduled every 2 hours (dual
  offsets to work around GitHub scheduler skew), plus workflow_dispatch.

**Phase 3 — retry for fix-reviews intents (same cron)**
- dev-lead-retry.sh already handles all five fix-reviews intent types
  (fix-reviews, fix-bot-comment, human, human-pr, rebase) via
  `dev-lead-reviews-retry` dispatch events.
- dev-lead-intent.sh: add `dev-lead-reviews-retry` dispatch type routing
  to the intent classifier — maps intent_type from payload to the correct
  existing fix-reviews step.
- dev-lead.yml: add `dev-lead-reviews-retry` to repository_dispatch types.
- dev-lead.yml: unify concurrency group for all dispatch types to
  `dev-lead-pr-{pr_number}` to avoid per-type slot fragmentation.

**Tests**
- test_engine_writer.bats: 8 new tests covering rate-limit stdout detection,
  exit-2 mapping, fallback exhaustion, reset time parsing.
- test_fix_ci.bats: 5 new tests covering rate-limited status, exhaustion
  exclusion, idempotency pass-through, and dedup.
- test_fix_reviews.bats: 5 new tests covering all intent types + human ack.
- test_intent_ci.bats: 4 new tests for dev-lead-reviews-retry dispatch routing.
- e2e/scenarios/07-rate-limit-retry.sh: new E2E scenario covering all parts.
- 3 new event fixtures for repository_dispatch_reviews_retry_* payloads.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): address review comments on #196

Addresses all 18 review threads from Copilot, Codex, and Gemini.

**P1 bugs fixed:**
- dev-lead-retry.sh: all log echo calls in scan_pr_for_rate_limits and
  dispatch_* functions now write to stderr; only the final count is on
  stdout. Previously, log lines polluted the command substitution,
  causing arithmetic expansion to fail and aborting the scan on the
  first retry candidate.
- dev-lead-retry.yml: permissions: contents was read-only; creating
  repository_dispatch events requires contents: write.

**Correctness fixes:**
- dev-lead-retry.sh: restrict automated retries to intents whose context
  can be reconstructed at runtime (fix-reviews, human-pr, rebase). human
  and fix-bot-comment require USER_INSTRUCTION/COMMENT_BODY from the
  original event which cannot be re-fetched; they are explicitly excluded.
- dev-lead-fix-reviews.sh: resolve HEAD_SHA from the PR API when not
  provided by the triggering event (issue_comment intents carry no SHA).
  Without this, rate-limited markers had no sha= field and were
  invisible to the retry scanner.
- dev-lead-fix-reviews.sh: write terminal status=applied marker after
  successful fix-reviews, human-pr, rebase runs. Prevents the retry
  cron from re-dispatching the same intent on every subsequent tick when
  the SHA hasn't changed.
- dev-lead-retry.sh: check for reviews terminal marker before dispatching
  a retry (mirrors the existing fix-ci terminal-marker check).
- dev-lead-retry.sh: look up current check-run details (details_url, id)
  at dispatch time via the commits check-runs API, so retried fix-ci runs
  have full failure logs and annotations rather than empty fields.
- dev-lead-fix-ci.sh: embed check= field in the rate-limited marker so
  the retry cron knows which check run to look up.
- dev-lead-fix-reviews.sh: human intent now posts a "please re-mention
  @dev-lead" ack instead of "I'll retry automatically" since it will NOT
  be retried automatically. human-pr keeps the auto-retry ack (correct).

**Pagination (Copilot + Gemini):**
- dev-lead-fix-ci.sh: add --paginate to check_idempotency, count_recent_
  failures, has_rate_limited_marker.
- dev-lead-fix-reviews.sh: add --paginate to has_reviews_rate_limited_marker.
- dev-lead-retry.sh: add --paginate to PR comments and PR list fetches.

**Other improvements:**
- dev-lead-retry.yml: simplify to single cron schedule (15 */2 * * *);
  remove BOT_USER env var (unused in the script).
- dev-lead-retry.sh: raise repo list limit 200 → 1000; add hard-error
  when list_repos_for_org returns empty (surfaces token permission issues
  rather than silently scanning 0 repos).
- dev-lead-intent.sh: use jq -nc (compact, null-input) for context JSON
  construction — safe against values with special characters.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(pr-review): handle Gemini trust-mode failure and properly route engine fallbacks (#217)

* fix(pr-review): handle Gemini trust-mode failure and properly route engine fallbacks

Closes #208

* fix(ci): install gemini CLI and set trust mode for dev-lead agents

---------

Co-authored-by: Gemini CLI <gemini-cli@example.com>

* fix: resolve YAML syntax error in dev-lead-reusable workflow

* fix: address automated feedback from PR 217 (#219)

This addresses missed feedback from CodeRabbit (removing syntax errors in YAML) and gemini-code-assist (improving string concatenation in validate-engines.sh), and adds assertion output checks to tests.

Co-authored-by: Gemini CLI <gemini-cli@example.com>

* chore: remove Phase 7 shadow period comment from dev-lead.yml

* feat: skip PRs with CHANGES_REQUESTED review (+ FORCE_REVIEW and stale-review guards)

* feat: skip PRs with CHANGES_REQUESTED review decision

Adds an early-exit check alongside the existing CI guards so the cascade
skips any PR where a human reviewer has already requested changes.
Reviewing those PRs before the author responds wastes tokens and creates
confusing parallel feedback threads.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix: honor FORCE_REVIEW and skip only stale-free CHANGES_REQUESTED reviews

Two issues addressed from Codex P2 feedback:

1. FORCE_REVIEW bypass: mention-triggered and force_review=true runs now
   bypass the CHANGES_REQUESTED skip, so authors can explicitly request a
   re-review after addressing feedback.

2. Stale review guard: only skip when a CHANGES_REQUESTED review targets
   the current head SHA. On repos without automatic stale-review dismissal,
   reviewDecision stays CHANGES_REQUESTED after new commits — the cascade
   now re-engages when the author has pushed new work.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): resolve context loss, missing checkout, and push logic (#223)

* feat: skip PRs with CHANGES_REQUESTED review decision

Adds an early-exit check alongside the existing CI guards so the cascade
skips any PR where a human reviewer has already requested changes.
Reviewing those PRs before the author responds wastes tokens and creates
confusing parallel feedback threads.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix: honor FORCE_REVIEW and skip only stale-free CHANGES_REQUESTED reviews

Two issues addressed from Codex P2 feedback:

1. FORCE_REVIEW bypass: mention-triggered and force_review=true runs now
   bypass the CHANGES_REQUESTED skip, so authors can explicitly request a
   re-review after addressing feedback.

2. Stale review guard: only skip when a CHANGES_REQUESTED review targets
   the current head SHA. On repos without automatic stale-review dismissal,
   reviewDecision stays CHANGES_REQUESTED after new commits — the cascade
   now re-engages when the author has pushed new work.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): resolve context loss, missing checkout, and push logic

- dev-lead-intent.sh: include actor and body in INTENT_CONTEXT for comments
- dev-lead.yml: parse and pass INTENT_ACTOR and INTENT_COMMENT_BODY to agent
- dev-lead-fix-reviews.sh: perform PR checkout, git push, and post summary comments

* fix(dev-lead): exit with 0 on no-changes in fix-reviews and human-pr

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* test: bypass CI check

* fix(pr-review): remove CI bypass hack

* fix(dev-lead): use heredoc for multiline environment variables (#224)

* fix(dev-lead): use heredoc for multiline environment variables

* fix(dev-lead): ensure all engines are installed and context is consistent

* fix(dev-lead): resolve stale model name in engine fallbacks

* fix(dev-lead): improve fallback reliability and script quality

- engine.sh: resolve stale model names in fallback loop; use -latest Gemini aliases
- dev-lead-fix-reviews.sh: remove unused code and fix PR_URL export

* fix(dev-lead): resolve Gemini model names and shell lint warnings

* security(dev-lead): use random heredoc delimiter to prevent injection

Also updates Gemini models to 3.1 family (pro/flash) for May 2026 compatibility.

* fix(dev-lead): update Gemini models to 2.5 stable family

* fix(dev-lead): use auto model selection for Gemini

* security(dev-lead): harden env var parsing and use high-quota Gemini fallback

* fix(dev-lead): use auto model for Gemini

* fix(dev-lead): detect engine-committed changes and remove commit instructions from prompts

commit_and_push only checked git diff (uncommitted changes), so when the engine
followed prompt instructions to run `git commit` via Bash, the working tree was
clean and commit_and_push concluded no-changes — dropping the engine's commits
when the ephemeral runner exited.

Fix 1 (commit_and_push): also detect unpushed commits via `git log @{u}..HEAD`.
If the engine already committed, skip the add+commit step and go straight to push.

Fix 2 (prompts): remove "Commit the changes with..." steps from human.md,
human-pr.md, fix-bot-comment.md, and fix-reviews.md. Replace with an explicit
"Do not commit or push" constraint so the engine leaves git operations to the
script in all cases.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): address PR review findings — untracked fil…
don-petry added a commit that referenced this pull request Aug 8, 2026
… trigger for the Class-2 timer changes, before narrowing them (#1420)

* add: workflow to fix stuck PRs using bot account token

Allows running the cleanup script via workflow_dispatch with access to
GH_PAT (bot account token) from repo secrets.

* fix: pass GH_PAT to checkout action so workflow uses bot account

* debug: simplify account check, add authentication debug output

* docs: add comprehensive petry-review-bot setup instructions

* docs: add GitHub App setup guide (recommended approach)

GitHub App is the secure, recommended way to automate PR reviews:
- Fine-grained permissions
- JWT tokens that auto-expire
- No human account needed
- Better audit trail
- GitHub's recommended approach

Replaces the bot user account approach with a more secure alternative.

* docs: add comprehensive GitHub App setup documentation

- SETUP.md: Quick reference guide with configuration and troubleshooting
- IMPLEMENTATION.md: Technical deep dive on architecture and design decisions
- DOCUMENTATION.md: Index of all documentation files
- Updated README.md with status and quick links
- Updated GITHUB_APP_SETUP.md with implementation notes and actual app ID
- Workflows use GitHub App token generation instead of static PATs

Covers the transition from bot user account to GitHub App authentication for improved security and maintainability.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: silence auth check in fix-stuck-prs when using GitHub App token

GitHub App tokens don't have user scope, so 'gh api user' returns 403.
This is fine - the script still works for PR operations. Suppress the error
so the script completes successfully with app-token fallback label.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: use explicit author instead of @me in stuck PR search

GitHub App tokens don't have user identity, so @me search doesn't work.
Use explicit 'don-petry' author instead to find PRs to fix.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: avoid subshell in while loop to preserve variable state

Using pipe in while loop created subshell where PROBLEM_PRS and FIXED_PRS
counters were incremented but changes didn't persist to parent shell.
Fixed by using process substitution for input instead of pipe.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* docs: add comprehensive status report for GitHub App setup

- Overview of completed work
- GitHub App authentication details
- 24 stuck PRs successfully fixed with approval reviews
- Architectural decisions and rationale
- Configuration guide and usage examples
- Known limitations and troubleshooting

System is now fully operational with GitHub App token authentication,
comprehensive documentation, and all infrastructure in place.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* docs: document stuck PR cleanup status and script fixes

Added documentation for:
- GitHub App token compatibility fixes in fix-stuck-prs.sh
  * Changed @me to explicit author (app tokens have no identity)
  * Fixed subshell variable scope (process substitution vs pipe)
  * Silenced expected 403 auth check error
- Current status: 24 PRs have approvals but remain OPEN
  * Auto-merge failed due to missing GitHub App permission
  * Approvals satisfy branch protection requirement
  * Next: expand permissions or manually merge

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: update list-prs.sh to search all repos by owner instead of @me

GitHub App tokens don't have user identity, so @me/@review-requested searches
fail. Changed to enumerate all repos in don-petry and petry-projects, then
search for open PRs within each repo. This covers the full scope:
- All open PRs in personal don-petry repos
- All open PRs in petry-projects org repos

Resolves 6 consecutive workflow failures due to zero PRs being enumerated.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* docs: add investigation report for PR review agent workflow failures

* fix: move env section before steps in workflow to fix YAML structure

GitHub Actions requires env section to come before steps in job definition.
Moving env definition up and setting GH_TOKEN only in steps that need it.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* script: add backfill-approvals to retroactively apply real approvals

Converts PRs with agent approval comments (but no real GitHub approvals)
to have actual APPROVED reviews. Needed for PRs reviewed before the
gh pr review --approve fix was applied.

Usage:
  scripts/backfill-approvals.sh          # dry-run (preview changes)
  scripts/backfill-approvals.sh false    # apply real approvals

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* feat: add backfill-approvals workflow to run as GitHub App bot

Runs backfill-approvals.sh via GitHub Actions so the approvals are posted
by the bot identity, not the PR author — GitHub rejects self-approvals.

Also fixes subshell counter bug in the script (piped while loop lost variable
state; switched to process substitution).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: convert inner while loop to process substitution to preserve counters

Both the outer repo loop and inner PR loop were piped subshells, causing
all counter increments (approved/skipped/failed) to be lost. The summary
always showed 0. Fixed by using < <(...) process substitution for both loops.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* car-hunt: add VIN-deep-dive flow, printable checklists, location-tier ranking

- Step 5.5 pre-test-drive flow: NHTSA VIN decode + per-VIN unrepaired-recall
  automation via Chrome MCP (Honda LWC owner portal documented as not
  automatable; NHTSA web form works).
- §F printable test-drive checklist generator (reportlab/Platypus PDF) with
  cold-start, model-specific red flags, negotiation table, walk-away rules.
- §G fraud / paperwork verification checklist (vehicle-agnostic, reusable):
  walk-away triggers, VIN three-location match, curbstoning, odometer fraud,
  bill of sale, title transfer, payment protection, stolen-vehicle, title-
  jumping, after-purchase steps.
- Step 5.25 head-to-head comparison template with information-asymmetry rule.
- Step 3 Location Quality Tier scoring (Birmingham AL metro table A/B/C/D)
  with adj-CPM modifier; sheet schema gains Adj.CPM + Tier columns.
- Hard-disqualification rules added: non-running engine keywords, mileage
  inconsistency, mandatory description scrape.
- Drive MCP overwrite limitation documented; sheet ID now read from memory
  rather than hardcoded.
- FB Marketplace operational facts: Birmingham AL city ID 107739635926718,
  /search? vs /vehicles? query handling, React-controlled inline composer,
  send-button selector, seller-name extraction regex.
- scheduled-tasks/used-car-search-{morning,afternoon}: tier A/B priority
  flag, sheet ID read from memory.
- Reference PDF generator scripts checked in for reuse.

* fix: re-approve PRs where approval predates last commit (stale approval)

Instead of skipping PRs that already have any APPROVED review, skip only
those where reviewDecision is not REVIEW_REQUIRED. This handles PRs where
a bot approval was posted before a new commit was pushed — GitHub's ruleset
engine treats those approvals as stale even with dismiss_stale_reviews=false.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: robustly extract JSON verdict from mixed claude --print output (#17)

Claude's --print mode can prefix the JSON verdict with conversational
preamble text, causing jq to fail with parse errors. Add
extract_verdict_json to engine.sh and wire all three cascade action
call sites in review-one-pr.sh to use it.

Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: extract_verdict_json — check dest file first (agent Bash-write style)

The cascade-action agent writes its verdict JSON to \$OUTPUT_FILE via a
Bash tool call, then prints a text summary to stdout. The previous fix
only scanned stdout (the .raw file), which contained no JSON. The agent-
written file at \$dest (= \$OUTPUT_FILE) was already correct — just not
checked. Now check \$dest first before falling back to stdout scan.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Enforce MAX_REVIEW_CYCLES cap before running the cascade (#18)

* fix: enforce MAX_REVIEW_CYCLES cap with human-escalation marker

Previously MAX_REVIEW_CYCLES only gated AI delegation in
post-pr-review.sh — the cascade itself ran on every cron tick
regardless of how many cycles had accumulated. Real-world result:
a PR could rack up 9+ review cycles (we observed exactly that on
ContentTwin#100) before any cap took effect.

Add a pre-cascade check in review-one-pr.sh: when the count of
existing `<!-- pr-review-agent v1 sha=... -->` markers is at or
above MAX_REVIEW_CYCLES (default 3), post one escalation comment
marked `<!-- pr-review-agent escalation -->`, label
needs-human-review, request don-petry, and exit 100 (skip
sentinel — doesn't burn the MAX_PRS budget). The escalation
marker doubles as the no-spam guard: subsequent runs detect it
and exit 100 immediately.

Also reuse a single `gh pr view` for both the cycle count and the
escalation-marker check.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* review feedback: surface escalation-comment failures, drop dead unset

- Drop `2>/dev/null || true` on `gh pr comment` so a failed escalation
  post is visible in the workflow log instead of silently leaving the
  PR with no marker (which would re-trigger the cap path next tick).
- Reword the cycle-count comment from "AI delegation loops" to "review
  loops" — the cap also catches cascade-only loops where every cycle
  approves and a new commit lands before merge.
- Remove the redundant `unset PR_BODIES` — never exported, no leakage.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>

* Reliability hardening: session circuit breaker, timeouts, retry, dedup (#20)

* feat: session circuit breaker, per-tier timeouts, retry, and triage hard-fail

Reliability hardening for the PR review agent.

1. Session circuit breaker (.github/workflows/pr-review.yml): on any
   non-zero, non-100 exit from review-one-pr.sh (general failure or rate
   limit on the fallback engine), break the per-PR loop, log a clear
   error annotation naming the failing PR and reason, and exit the step
   with code 1 so the run shows red. Prevents one systemic problem from
   silently burning the entire candidate pool.

2. Per-tier timeouts (scripts/engine.sh): triage/deep/audit/action/duck
   each get their own bounded timeout (180/600/600/300/300s defaults,
   env-overridable). Previously only the duck had a timeout — a hung
   tier could burn the whole 60min job budget.

3. Retry-with-backoff on transient errors (scripts/engine.sh): triage
   retries once on 124/137/143 (timeout / signal kill) since its caller
   captures stdout via $(...) so retries are safe. Deliberately NOT
   applied to run_agentic/run_duck where stdout is redirected to a file
   — a retry there would corrupt the partial first-attempt output.

4. Triage non-JSON now hard-fails (scripts/review-one-pr.sh): replaces
   the silent fallback that synthesized a fake "escalate=MEDIUM" verdict
   and proceeded to deep review. With the new circuit breaker, loud
   failure is the right call — masking a broken triage was burning
   tokens on every PR while the workflow looked healthy.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix: stop stacking duplicate agent reviews on the same PR

Two bugs were causing the agent to leave multiple comments on the same PR.
Together they produced 10 stacked APPROVED reviews on petry-projects/ContentTwin#100.

Bug A — idempotency check is order-dependent (review-one-pr.sh):
The previous marker-discovery code did:
  ((.reviews // []) + (.comments // [])) | .[].body | grep marker | tail -1
This relies on the array concatenation order, not chronological order. When
old agent comments existed alongside newer agent reviews, tail -1 picked
the comment-array marker (older) over the review-array marker (newer),
causing the script to think the head SHA hadn't been reviewed and re-run.
Replaced with a single jq pipeline that tags each item with submittedAt /
createdAt, sorts by timestamp, and takes the actual most-recent marker.

Bug B — no cleanup of prior agent items (post-pr-review.sh):
After successfully posting a new review/comment, prior agent items were
left in place, accumulating forever. Added mark_prior_agent_items_obsolete
which, after a successful post:
  - dismisses prior APPROVED/COMMENTED/CHANGES_REQUESTED agent reviews via
    the GitHub dismissal API (UI shows them struck-through as Dismissed)
  - edits prior agent comments to wrap their body in a collapsed <details>
    block with a "Superseded by re-review at <SHA>" summary, plus a
    `<!-- pr-review-agent superseded -->` sentinel for idempotency

All cleanup API calls are best-effort — failures don't break the workflow.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(cleanup): warn on API failures, preserve global newest, file-stage JSON

Three fixes to mark_prior_agent_items_obsolete from the review of PR #20:

1. ::warning:: annotations on every cleanup API failure (review/comment
   list-fetch, individual review dismissal, individual comment fetch+edit).
   Previously these were silenced with `|| true`, so a permissions change
   on the dismissal endpoint would let duplicates stack indefinitely with
   no signal in the Actions UI. Cleanup is still non-fatal — the new post
   has already landed — but failures are now visible.

2. Preserve the globally-latest agent item across BOTH categories, not the
   newest of each category separately. The earlier code split reviews and
   comments and applied `[:-1]` to each, which left a stale fix-request
   comment in place when the new post was a review (or vice versa). The
   one-off cleanup of ContentTwin#100 hit exactly this case: 12 stacked
   reviews collapsed to 1, but a stale comment from 2026-04-25 (SHA
   cd9132d6) was preserved as "newest comment" even though the latest
   review at SHA 3af8c8ee was newer overall. Now: compute the max
   timestamp across both feeds, exclude items at that timestamp.

3. Stage API responses to disk (`mktemp` + `jq <file>`) instead of routing
   through `--argjson "$var"`. The old approach broke on rare unescaped
   control chars in user-authored comment bodies (jq refused to parse the
   resulting shell-vared JSON). File-based input sidesteps the shell
   pipeline entirely.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>

* Remove car-hunt skill and scheduled tasks (moved to don-petry/don-petry) (#21)

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: apply Copilot fallback hardening and reuse GH_PAT for Copilot auth

- Use secrets.GH_PAT (existing personal account token with Copilot
  subscription) instead of a separate COPILOT_GITHUB_TOKEN secret
- Pin actions/create-github-app-token to SHA (v3.1.1) for supply chain safety
- Add post-install verification for gh-copilot extension with warning on failure
- Guard Copilot fallback path: skip PR gracefully if extension is not installed

Addresses issue #24 workflow fixes (1-3) that could not land via PR #25
due to GitHub App lacking workflows permission.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: switch Copilot engine to gh built-in and fix app-id deprecation

- Remove gh extension install (github/gh-copilot conflicts with built-in alias)
- Replace with gh copilot --version check using COPILOT_GITHUB_TOKEN (GH_PAT)
- Update all engine.sh copilot invocations from bare `copilot` binary to
  `gh copilot suggest --target shell` with GH_TOKEN overridden to GH_PAT
  so the user token (with Copilot subscription) is used, not the App token
- Fix actions/create-github-app-token: app-id → client-id (same secret value,
  just the renamed input in v3.x)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: harden Copilot fallback path and remove stale worktrees (#25)

- gh-copilot install: add --force flag and post-install verification
  warning so silent failures due to built-in alias conflict are visible
  in workflow logs instead of being silently swallowed by `|| true`
- fallback pre-flight check: verify `gh extension list | grep copilot`
  before switching to Copilot engine; if unavailable, skip the PR and
  continue the batch rather than session-aborting and dropping all
  remaining candidates (fixes the 27-PR drop on run #503)
- pin actions/create-github-app-token to SHA for v3.1.1 (Node.js 24
  compatible) to prevent hard break on 2026-06-02 when GitHub forces
  Node.js 24 as default runtime
- remove 6 stale Claude Code worktrees from git tracking; add
  .gitignore entry to prevent future worktrees from being committed
  (eliminates exit-128 warning on every actions/checkout post-job sweep)

Closes #24

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: petry-projects-pr-review-agent[bot] <petry-projects-pr-review-agent[bot]@users.noreply.github.com>

* ci: add pre-flight dedup check to prevent duplicate claude-issue PRs (#26)

Before invoking Claude on a labeled issue, a new shell step queries
for any open PR with a branch matching claude/issue-NNN-* (or a body
containing "Closes #NNN"). If one is found it posts a comment on the
issue pointing to the existing PR and skips the Claude step entirely.
A secondary prompt instruction tells Claude to check first and push to
the existing branch rather than opening a new PR.

Motivated by issue petry-projects/google-app-scripts#171, where the
claude label was re-applied three times and each run created a fresh
PR unaware of the prior attempts.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor: migrate to machine user PAT auth (closes #27) (#28)

* refactor: migrate from GitHub App to machine user PAT auth

Closes #27

GitHub Apps cannot be listed in CODEOWNERS, blocking PRs in repos with
require_code_owner_review: true. Switch all workflows to use a machine
user account's fine-grained PAT (DON_PETRY_BOT_GH_PAT secret), which can
join an org team listed in CODEOWNERS.

Workflows: removed actions/create-github-app-token steps in pr-review,
fix-stuck-prs, backfill-approvals, and daily-pr-review-health. All now
use secrets.DON_PETRY_BOT_GH_PAT directly.

Docs: renamed GITHUB_APP_SETUP.md to MACHINE_USER_SETUP.md with full
rewrite covering account creation, CODEOWNERS config, PAT generation,
and rotation. Updated auth sections in IMPLEMENTATION.md, SETUP.md,
STATUS.md, DOCUMENTATION.md, README.md.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* refactor: switch to org-scoped PAT secret DON_PETRY_BOT_PETRY_PROJECT_PAT

Replace DON_PETRY_BOT_GH_PAT with DON_PETRY_BOT_PETRY_PROJECT_PAT — the
new fine-grained PAT scoped to the petry-projects org (resource owner =
petry-projects). The previous PAT was scoped to the donpetry-bot personal
namespace and had no repository access.

The old DON_PETRY_BOT_GH_PAT secret is retained for any future use against
don-petry's personal repos.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* chore: ignore .claude/scheduled_tasks.lock state file

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>

* refactor: merge approval repair scripts into single automation

- Consolidate backfill-approvals.sh and fix-stuck-prs.sh into repair-pr-approvals.sh
- Iterate all repos in both orgs instead of assuming author
- Verify no existing APPROVED review before posting
- Copy original agent comment as review body
- Enable auto-merge when posting approval if needed
- Single workflow with 30-min timeout

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: add @mention trigger for on-demand PR reviews (#30)

Adds repository_dispatch support so commenting @petry-review-bot on any
PR fires an immediate review without waiting for the hourly schedule.

- pr-review.yml: new `repository_dispatch` trigger (type: pr-review-mention),
  per-PR concurrency group for mention runs, FORCE_REVIEW and DRY_RUN
  env vars that handle both workflow_dispatch and repository_dispatch paths
- review-one-pr.sh: FORCE_REVIEW=true bypasses idempotency so a mention
  always runs a fresh cascade even if the head SHA hasn't changed
- templates/mention-listener.yml: deploy to petry-projects/.github;
  listens for @petry-review-bot, validates commenter trust, posts ack,
  sends repository_dispatch (requires Contents:write, not Actions:write)

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: harden rebase, review dismissal, and health log diagnostics (#40)

Addresses 4 code-actionable recommendations from health check report #33:

- [CRITICAL] Make rebase/branch-update non-fatal — 403/504 during rebase
  emits ::warning:: instead of aborting the batch session (#34)
- [MEDIUM] Add retry with exponential backoff (3 attempts) for transient
  5xx on update-branch API; break immediately on 4xx (#37)
- [MEDIUM] Guard review dismissal with state re-check before calling
  dismiss API, preventing 422s from race conditions (#38)
- [LOW] Surface missing log warnings in health check script (#39)
- Fix: skip auto-merge when branch is still BEHIND after failed rebase

Closes #34, closes #37, closes #38, closes #39

* refactor: migrate to org-wide .github-private convention (#41)

* refactor: parametrize hardcoded identity values for org migration

Replace all hardcoded references to don-petry, petry-review-bot, and
don-petry/pr-review-agent with environment variables that default to
the current values. This allows the agent to be configured for different
orgs/users via repo variables.

Changes:
- Scripts use $REVIEWER_USER, $TARGET_ORG, $BOT_USER, $AGENT_REPO
- Prompts no longer reference specific GitHub usernames
- Workflows use org-level GH_PAT_WORKFLOWS secret instead of
  repo-level DON_PETRY_BOT_PETRY_PROJECT_PAT
- Health check uses context.repo.owner/repo for issue creation
- Variables are set in workflow env block with defaults

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add Copilot custom agent profiles for org-wide use

Create /agents/ directory with three agent profiles:
- pr-reviewer: Multi-tier cascading PR review
- feature-ideator: Feature idea generation and prioritization
- compliance-auditor: Org standards compliance checking

These are Copilot custom agent profiles that become available
org-wide in the .github-private repo convention.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Squashed 'frameworks/bmad-method/' content from commit e36f219c

git-subtree-dir: frameworks/bmad-method
git-subtree-split: e36f219c81b6010d4aae423ba12f49edb5b6e31a

* Squashed 'frameworks/spec-kit/' content from commit 11f49ebf

git-subtree-dir: frameworks/spec-kit
git-subtree-split: 11f49ebfb2f6af55345cb4bd9a7906acd211e56f

* Squashed 'frameworks/gsd/' content from commit 304c1a13

git-subtree-dir: frameworks/gsd
git-subtree-split: 304c1a1302564c45af252bbba4bcc5350e7dac3a

* docs: update README for .github-private org infrastructure role

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* ci: add dependabot-automerge.yml workflow (#73)

Adds the required dependabot-automerge.yml workflow from the org
standard template (petry-projects/.github/standards/workflows/).
This is a thin caller stub that delegates to the org-level reusable
workflow.

Closes #48

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Don Petry <don-petry@users.noreply.github.com>

* fix(pr-review): unblock queue starvation from self-authored PRs (#96) (#97)

* fix(pr-review): unblock queue starvation from self-authored PRs (#96)

A self-authored PR sorted first in the candidate list and triggered
"Can not approve your own pull request" — which the session-fatal
abort treated like an engine error, skipping all 28 remaining PRs
on every run.

- list-prs.sh: filter out PRs authored by REVIEWER_USER at enumeration
- post-pr-review.sh: catch the GraphQL self-approval error and exit 100
  (no-op) instead of 1, so a stray self-PR can no longer abort the batch
- engine.sh: gh copilot renamed --target to --agent; fix the rubber-duck
  invocations so tier-2 cross-engine review works again
- review-one-pr.sh: stop appending a duplicate "0" to REVIEW_CYCLE when
  grep -c finds no markers under set -o pipefail (was breaking the
  cycle-cap integer comparison)

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

* fix(pr-review): authenticate as bot, harden enumeration, address review

- Workflow now runs as AGENT_USER (default don-petry-bot), distinct from
  REVIEWER_USER (the human, don-petry). The self-approval rejection that
  caused #96 came from the agent and the human sharing one identity.
- list-prs.sh filters self-authored PRs against AGENT_USER, and validates
  AGENT_USER against the GitHub username charset before interpolating into
  the jq filter (Copilot review feedback).
- review-one-pr.sh uses printf '%s\n' instead of echo for PR_BODIES, since
  PR body content is user-authored and could begin with -n/-e or contain
  backslash escapes (Copilot review feedback).
- AGENT.md guideline updated to reflect that self-authored PRs are
  intentionally excluded (CodeRabbit review feedback).

Operator note: rotate the GH_PAT_WORKFLOWS secret to a token owned by
don-petry-bot (with read:org added) for the bot-account behavior to take
effect at runtime.

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

* refactor(pr-review): collapse to single BOT_USER, route escalations via CODEOWNERS

Drop AGENT_USER and REVIEWER_USER. The workflow now has one identity:
BOT_USER (default don-petry-bot), which both owns the repos to scan and
gets filtered out as the self-approval blocker. Human escalation no
longer hard-codes a single reviewer; instead, scripts/request-codeowners-
review.sh parses CODEOWNERS in the PR's repo and requests review from
every @user / @org/team mention.

- list-prs.sh: BOT_USER for both gh-repo-list and self-author filter
- review-one-pr.sh, post-pr-review.sh: replace --user "$REVIEWER_USER"
  with the CODEOWNERS helper
- repair-pr-approvals.sh: same gh-repo-list switch
- pr-review.yml: drop AGENT_USER/REVIEWER_USER, set BOT_USER default
  to don-petry-bot
- AGENT.md: updated guideline to describe single-identity model

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

* refactor(pr-review): extract batch loop, hoist env, drop redundant step

Workflow YAML drops from 231 to 111 lines by moving the per-PR review
loop into scripts/review-batch.sh. The script can be syntax-checked and
exercised locally; the workflow now just wires env vars and dispatches.

- Hoist GH_TOKEN, MAX_PRS, CANDIDATE_LIMIT to job-level env (were
  repeated on individual steps).
- Inline `gh auth status` into the install step; remove the standalone
  Verify auth step (its only output was a one-line auth dump).
- Drop the `if: steps.list.outputs.count != '0'` guard and the step's
  `id`/output: review-batch.sh handles empty input as a no-op.
- Collapse the duplicated summary-string branch in the review loop into
  a single template with a conditional fragment.

No behavior change.

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

* perf(pr-review): cache claude-code CLI install across runs

`npm install -g @anthropic-ai/claude-code` ran on every workflow start
(~30s). Switch to a per-user npm prefix (~/.npm-global) and cache that
directory via actions/cache, keyed on CLAUDE_CODE_VERSION + runner OS.

A `command -v claude` guard makes the install a no-op on cache hit, so
the only cost on subsequent runs is the cache restore.

CLAUDE_CODE_VERSION defaults to 'latest' (cache persists until manually
flushed); set the repo variable to pin a specific version for fully
reproducible caching.

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

---------

Co-authored-by: Claude <noreply@anthropic.com>

* chore: rename GH_PAT_WORKFLOWS secret to DON_PETRY_BOT_GH_PAT (#99)

The previous name was generic and didn't tell you which account the PAT
belonged to. The new name makes the binding explicit: this secret is the
PAT owned by don-petry-bot, used as BOT_USER throughout the PR-review
workflows.

Operator follow-up before this can be merged:
- Add a new repo secret DON_PETRY_BOT_GH_PAT containing the bot's PAT
  (with repo, workflow, and read:org scopes).
- After merge, the old GH_PAT_WORKFLOWS secret can be deleted.

Affected workflows:
- pr-review.yml (1 use)
- claude.yml (4 uses, with || github.token fallback)
- daily-pr-review-health.yml (1 use)
- repair-pr-approvals.yml (1 use)

scripts/pr_review_health.sh's error message is updated to point at the
new name as well.

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

Co-authored-by: Claude <noreply@anthropic.com>

* chore: remove frameworks directory (#101)

Co-authored-by: Gemini CLI <gemini-cli@example.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore all erroneously deleted files (items 4-34) (#104)

* revert: restore .github/workflows/daily-pr-review-health.yml

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore .github/workflows/repair-pr-approvals.yml

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore agents/pr-reviewer.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/cascade-action.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/deep-review.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/rubber-duck.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/security-audit.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/shared.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/single-review.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/synthesize-duck.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/synthesize.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/triage.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/engine.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/list-prs.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/post-pr-review.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/pr_review_health.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/repair-pr-approvals.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/request-codeowners-review.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/review-batch.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/review-one-pr.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore templates/mention-listener.yml

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore AGENT.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore BOT_SETUP.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore DOCUMENTATION.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore IMPLEMENTATION.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore MACHINE_USER_SETUP.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore PR_REVIEW_AGENT_FAILURE_REPORT.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore PR_REVIEW_FAILURE_INVESTIGATION.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore SETUP.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore STATUS.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore README.md to pre-change state

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore .github/workflows/daily-pr-review-health.yml

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat: add Gemini Pro support and optimize PR review fallback chain (#102)

* Merge main and resolve conflicts

* Enhance pr-review workflow: add Gemini support and refactor fallback logic

* Address Copilot review comments: tighten regex, fix fallback summary, and align docs

* docs: move agent documentation to docs/ folder and rename files

* docs: align secret names and fix casing in index

* docs: organize pr-review-agent documentation under dedicated folder

* Address final PR comments: tighten regex, add preflight checks, fix bot names, and cleanup docs

---------

Co-authored-by: Gemini CLI <gemini-cli@example.com>

* fix(pr-review): gate rate-limit detection on non-zero exit code

Broad patterns like `plan.*limit` and `claude.*usage` could match content
in a successful triage summary, triggering a false-positive engine fallback.
Guard the check with TRIAGE_RC != 0 so it only fires when the provider
command actually failed.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(claude): sync inlined workflow with org standard (#109)

* fix(claude): sync inlined workflow with org standard

Brings the inlined claude.yml up to parity with
petry-projects/.github/.github/workflows/claude-code-reusable.yml.

Changes:
1. Bot allow list for pull_request_review_comment
   Add coderabbitai[bot], Copilot, copilot-pull-request-reviewer[bot],
   and gemini-code-assist[bot] alongside the existing OWNER/MEMBER/
   COLLABORATOR check. These bots have author_association 'NONE' so
   their review comments were always skipped.

2. check_run trigger + claude-ci-fix job
   Port the CI failure auto-fix feature from the reusable. When a check
   fails on a PR, Claude diagnoses and fixes it automatically.
   Adapted to use DON_PETRY_BOT_GH_PAT (this repo's secret name).

3. claude-code-action SHA bump: v1.0.89 → v1.0.119
   Update both the claude and claude-issue jobs.

4. paths-ignore on pull_request trigger
   Prevents the workflow from firing on PRs that only change
   claude.yml itself, avoiding the Anthropic OIDC validation
   failure (workflow file must match default branch at token exchange).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(claude): guard bot allow list against fork PRs

Same fix as petry-projects/.github PR #238 — add same-repo guard
for bot-triggered pull_request_review_comment runs.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(claude): address ci-fix self-loop and fork PR security

- Replace Claude Code name prefix check with explicit job name list to
  correctly prevent self-loops (check_run names for inlined workflows
  are bare job names, not workflow-prefixed)
- Add fork PR trust gate in Resolve PR number step: verify head repo
  matches target repo before running Claude with privileged credentials

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* ci: replace inlined claude.yml with standard thin-caller stub

The inlined version embedded all three jobs (claude, claude-ci-fix,
claude-issue) directly and threaded DON_PETRY_BOT_GH_PAT through
checkout tokens and github_token, causing the bot's PAT to author
PRs and comments instead of github-actions[bot].

Replace with the standard thin caller that delegates to the org-level
reusable workflow (claude-code-reusable.yml@v1) via secrets: inherit,
which is the correct pattern per:
petry-projects/.github/standards/workflows/claude.yml

* fix: replace gh copilot suggest with GitHub Models REST API (#151)

* fix: replace gh copilot suggest with GitHub Models REST API (#147)

The `gh copilot suggest -p "$(cat <file>)"` invocation failed with
"Invalid command format" because:
1. The `-p` flag is not valid syntax in modern `gh copilot` built-in versions.
2. `gh copilot suggest` is a shell-command suggestion tool; it does not
   accept large PR prompts or return structured JSON responses.
3. Passing a multi-thousand-line prompt via `$(cat ...)` can hit ARG_MAX.

The non-zero exit was then misclassified by the rate-limit detector, which
aborted the entire session and skipped all remaining PRs in the queue.

Fix: replace all three copilot invocations (run_triage, run_agentic,
run_duck) with a new `copilot_chat` helper that calls the GitHub Models
REST API directly via curl. The API is OpenAI-compatible, versioned via
`X-GitHub-Api-Version`, stable against gh CLI version changes, and accepts
arbitrary prompt sizes using python3 for safe JSON encoding.

Additional changes:
- Add `COPILOT_API_MODEL` var (default `openai/o4-mini`) overrideable via env.
- run_agentic/run_duck copilot cases now also write output to $OUTPUT_FILE
  so callers that check that path directly (deep review, audit) find the JSON.
- Rate-limit responses (HTTP 429) are echoed to stdout so the existing
  `is_rate_limited()` detector fires correctly for engine fallback.
- Add pre-flight smoke test in review-batch.sh: tests GitHub Models API
  connectivity with a one-liner prompt before processing any PRs, so auth
  or model errors surface immediately as a clear setup failure.
- Add tests/test_copilot_chat.sh: unit tests for the JSON payload builder
  with edge-case prompts (quotes, newlines, # headings, large diffs, Unicode).

Closes #147

Co-authored-by: Don Petry <don-petry@users.noreply.github.com>

* fix: scope COPILOT_API_MODEL export to copilot engine only

Move the `export COPILOT_API_MODEL` from the common exports block into the
`copilot)` case where the variable is set, so it is not exported as an
empty/unset variable when the engine is `claude` or `gemini`. The
`copilot_chat` fallback `${COPILOT_API_MODEL:-openai/o4-mini}` still works
correctly when `DUCK_ENGINE=copilot` under a non-copilot primary engine.

Co-authored-by: Don Petry <don-petry@users.noreply.github.com>

* fix: address review comments — temp file for payload, fail-fast source, streaming output

- copilot_chat: write JSON body to mktemp file, pass to curl as @file to
  avoid ARG_MAX for large PR diffs (was --data-binary "$body")
- copilot_chat: add :? guard on COPILOT_GITHUB_TOKEN for a clear error
  instead of generic "unbound variable" under set -u
- run_agentic / run_duck copilot paths: stream directly to stdout (and
  tee to OUTPUT_FILE when set) rather than buffering the full response
  into a shell variable, which forced large outputs into memory and
  stripped trailing newlines
- review-batch.sh pre-flight: fail fast if source engine.sh fails
  (was silently ignored with || true)
- review-batch.sh pre-flight: build smoke-test JSON payload via python3
  + temp file instead of shell string interpolation to avoid JSON
  injection if COPILOT_API_MODEL contains special characters
- Clarify that openai/o4-mini is the correct April-2025 o4-generation
  model name, not a typo for o1-mini or gpt-4o-mini

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Don Petry <don-petry@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* feat: prioritise .github/.github-private PRs, oldest-first within tier (#155)

* feat: prioritise .github/.github-private PRs, oldest-first within tier

- list-prs.sh: add createdAt to JSON fetch; emit priority|createdAt|url
  lines (priority 0 for .github/.github-private, 1 for everything else)
- Replace final sort -u with a two-pass sort: deduplicate by URL, then
  sort by priority asc then createdAt asc; strip sort keys with cut
- tests/test_list_prs_sort.sh: 16 tests covering priority classification,
  oldest-first ordering, mixed scenarios and deduplication
- .github/workflows/test.yml: run both unit-test files on every PR / push

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* test: add dedup edge case for same URL with conflicting priorities

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: remove temperature from copilot_chat payload (o4-mini incompatible)

The GitHub Models API rejects temperature=0 for reasoning models like
o4-mini with HTTP 400:
  'Unsupported value: temperature does not support 0 with this model.
   Only the default (1) value is supported.'

This caused the rubber duck (DUCK_ENGINE=copilot / DUCK_MODEL=o4-mini)
to fail with HTTP 400 on every claude-engine run, silently degrading
every review to deep-only with no cross-engine sanity check.

Fix: remove temperature from the copilot_chat JSON payload entirely —
the API defaults to 1, which is the only supported value for o4-mini.

Also:
- Update tests/test_copilot_chat.sh: sync build_payload to match and
  flip Test 9 to assert temperature is ABSENT (not 0).
- Add .github/workflows/test.yml: run unit tests on every PR and push
  to main so regressions are caught before merge.

Reproducer: job/75690347409 — duck failure logged as:
  copilot_chat: HTTP 400 from GitHub Models API
  {"error":{"message":"Unsupported value: 'temperature' does not
   support 0 with this model..."}}

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: address PR review comments

- list-prs.sh: tighten priority regex to /[.]github(-private)?/pull/
  so path-boundary anchor prevents false positives (e.g. foo.github)
- list-prs.sh: replace printf pipeline with here-string to avoid ARG_MAX
  limits on large PR sets
- test.yml: add permissions: contents: read (least-privilege, CodeQL fix)
- test.yml: add timeout-minutes: 5 to prevent hung runs
- test_list_prs_sort.sh: sync sort_entries helper (here-string + || true)
  and classify regex to match updated list-prs.sh exactly

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: don-petry <don@petry.dev>

* fix: restore pr-review.yml content (accidentally emptied in revert)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: restore pr-review.yml (file was accidentally emptied)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore(deps): bump actions/cache from 4 to 5 (#165)

Bumps [actions/cache](https://github.com/actions/cache) from 4 to 5.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): bump petry-projects/.github/.github/workflows/claude-code-reusable.yml (#164)

Bumps [petry-projects/.github/.github/workflows/claude-code-reusable.yml](https://github.com/petry-projects/.github) from 1 to 2.
- [Commits](https://github.com/petry-projects/.github/compare/v1...v2)

---
updated-dependencies:
- dependency-name: petry-projects/.github/.github/workflows/claude-code-reusable.yml
  dependency-version: '2'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* fix(list-prs): search DELEGATION_ORGS and drop --checks success pre-filter

- Add iteration over all orgs in DELEGATION_ORGS (e.g. don-petry) so PRs
  in those orgs enter the candidate pool. Previously only BOT_USER and
  TARGET_ORG were searched.
- Remove --checks success from org repo searches. GitHub excludes PRs from
  repos with no CI configured when this flag is used, silently dropping all
  PRs from repos like .github, .github-private, google-app-scripts, and
  TalkTerm. review-one-pr.sh already enforces CI gating per-PR and treats
  empty statusCheckRollup as passing, so the pre-filter is redundant and
  harmful.
- Add --limit 200 to gh repo list calls to handle org growth beyond gh's
  default 30-repo cap.

* feat: add code-quality ruleset (compliance fix #60) (#86)

Creates the required `code-quality` repository ruleset enforcing required
status checks on the default branch, as mandated by the org standard:
standards/github-settings.md#code-quality--required-checks-ruleset-all-repositories

Required checks:
- SonarCloud (code quality analysis)
- CodeQL (SAST)
- agent-shield / AgentShield (agent security scan)
- dependency-audit / Detect ecosystems (dependency vulnerability scan)

Bypass actors:
- OrganizationAdmin (always) — emergency override
- dependabot-automerge-petry Integration (always) — Dependabot auto-merge

The ruleset was applied directly via GitHub API. This file documents the
configuration as code for auditability and future reapplication.

Closes #60

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Don Petry <don-petry@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* feat(prompts/dev-lead): add human prompt template

* feat(prompts/dev-lead): add human-pr prompt template

* feat(dev-lead): implement dev-lead agent Phases 0-6

* fix: per-PR isolation and single-review retry (closes #132)

- review-batch.sh: non-rate-limit per-PR failures (exit code 1) no
  longer abort the session. SESSION ABORTED EARLY is now reserved for
  the rate-limit-on-fallback-engine case (exit code 2) only. All other
  failures are counted and logged; remaining candidates continue.

- review-one-pr.sh: single-review step retries up to
  SINGLE_REVIEW_MAX_RETRIES (default 2) times with a
  SINGLE_REVIEW_RETRY_DELAY_SEC (default 15s) gap before giving up.
  On exhaustion, the PR is flagged needs-human-review and the script
  exits with code 1, which the updated batch treats as a non-fatal
  per-PR failure. Raw model output and stderr are logged on each failed
  attempt for post-mortem visibility.

Root cause of run #25707852006: claude-opus-4-7 returned a verbose
non-JSON response for PR #129; the old code treated that as fatal and
skipped 35 remaining candidates.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix: rate-limit detection and per-attempt stderr logs in single-review retry

Address inline review comments on PR #133:

- Rate-limit check: after each run_agentic call, inspect both stdout
  (VERDICT_JSON.raw) and stderr (SINGLE_LOG) with is_rate_limited before
  retrying. A rate-limit match exits immediately with code 2 so
  review-batch.sh can trigger engine fallback — consistent with triage and
  deep-review tiers. Previously a rate-limited single-review would burn all
  retries and exit 1 (per-PR failure), silently leaving the batch on the
  same rate-limited engine for all remaining PRs.

- Per-attempt log files: stderr is now written to
  single-review-attempt-N.log rather than a single overwritten file, so
  no earlier-attempt errors are lost. Each attempt logs its own stderr
  inline on failure; the fallback path cats all attempt logs for
  post-mortem visibility.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* feat(dev-lead): Phase 0 test infrastructure and Phase 1 intent stub

Phase 0: full test harness for the dev-lead agent —
  26 event fixtures (all valid JSON with _test_expected_intent),
  stub claude/gemini engines, mock gh binary, CI failure log sample,
  bats helpers (stub-engine, mock-gh, assert-env, prompt-vars),
  7 prompt templates with VARIABLES declarations, preflight script,
  prompt coverage integration test, and test-dev-lead.yml CI workflow.

Phase 1: dev-lead.yml trigger workflow (all 7 event types, dispatch + ci-relay
  jobs) and dev-lead-intent.sh stub (anti-loop guard live; all other events
  emit skip/not-implemented).  14/14 bats unit tests pass.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): add permissions blocks to test workflow jobs (CodeQL)

* fix(ci): auto-fix for lint / eslint [skip ci-relay]

* feat(dev-lead): Phases 2-6 — CI fix, review fix, issue, engine fallback

Phase 2: run_writer/run_writer_with_fallback in engine.sh, full intent
routing in dev-lead-intent.sh, dev-lead-fix-ci.sh handler, workflow wiring.

Phase 3: dev-lead-fix-reviews.sh handles fix-reviews, fix-bot-comment,
human, human-pr, rebase intents. Full review routing in intent classifier.

Phase 5: dev-lead-fix-issue.sh handles the issue intent with dedup guard
and branch/PR creation.

Phase 6: run_writer_with_fallback with claude→gemini→copilot fallback chain.

Tests: 77 unit tests across 8 new .bats files, all passing. Updated Phase 1
stub tests to reflect actual Phase 2+ routing behavior.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): address review comments — dispatch JSON, dead code, portable stub gh

* fix(dev-lead): pre-flight after intent, fork URL check, review-batch exit code, retry comment

* fix(dev-lead): install bats-core from GitHub to avoid root requirement

* fix(security): move event values to env vars to prevent script injection (SonarCloud)

* fix(security): move CLAUDE_CODE_VERSION to env block (SonarCloud script injection)

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* feat(dev-lead): Phase 1.5 — reusable workflow for cross-repo adoption + Phase 7 shadow period

* feat(dev-lead): add reusable workflow (Phase 1.5) and begin shadow period (Phase 7)

- Create .github/workflows/dev-lead-reusable.yml: workflow_call entry
  point for other repos. Checks out .github-private scripts/prompts into
  .dev-lead/, then runs the same intent-classify + handler pipeline as
  dev-lead.yml with PROMPTS_DIR=.dev-lead/prompts/dev-lead.
- Add PROMPTS_DIR env-var support to dev-lead-fix-ci.sh,
  dev-lead-fix-reviews.sh, and dev-lead-fix-issue.sh so the reusable
  workflow can point scripts at the sparse-checkout path without changing
  CWD. Defaults to prompts/dev-lead (backwards-compatible for dev-lead.yml).
- Annotate dev-lead.yml with Phase 7 shadow-period window (2026-05-15
  through ~2026-05-29): claude.yml and dev-lead.yml run in parallel until
  regressions clear, then claude.yml is removed.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* chore(dev-lead): update shadow-period tracking issue ref to #180

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): document PAT requirement and pin ref:main on private checkout

* fix(dev-lead): P1 review fixes — ci-relay in reusable, .dev-lead gitignore, export PROMPTS_DIR, trim header

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): rate-limit detection, separate status, and retry cron (#192) (#196)

* chore(dev-lead): decommission claude.yml, bump ACTION_TIMEOUT_SEC to 600s

- Delete .github/workflows/claude.yml — replaced by dev-lead.yml (shadow
  period complete, tracking issue #180).
- scripts/engine.sh: raise ACTION_TIMEOUT_SEC default 300→600s to reduce
  timeout failures on large-repo fix-ci runs (PR #80 had 3 timeouts).
- AGENTS.md: remove claude.yml immutability exemption; note dev-lead.yml
  as the active AI automation workflow and its edit-via-reusable pattern.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* refactor: replace Claude analysis with pure gh/jq telemetry in health check

* refactor: drop Node/Claude steps from daily-pr-review-health workflow

* rename: daily-pr-review-health → actions-fleet-monitor

* rename: daily-pr-review-health → actions-fleet-monitor

* fix(agents-md): clarify dev-lead.yml vs dev-lead-reusable.yml scope

dev-lead.yml in .github-private runs inline steps (not a caller stub).
Behavior changes for this repo go to dev-lead.yml directly; changes that
affect all org repos via the cross-repo reusable go to dev-lead-reusable.yml.

Addresses Copilot review on PR #194.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): rate-limit detection, separate status, and retry cron

Closes #192. Implements all phases from the revised plan.

**Phase 0 — fix run_writer stdout capture (prerequisite)**
- engine.sh: capture stdout via `tee` to a tempfile so `is_rate_limited`
  can inspect the output; old code read `/tmp/dev-lead-writer-stderr`
  which was never written (claude --print outputs to stdout, not stderr).
  Fallback engines were never tried in practice.
- engine.sh: add `parse_reset_time` to extract ISO timestamp from
  `resets H:MMpm (UTC)` in engine output and write it to
  `/tmp/dev-lead-rate-limit-reset` for callers to embed in markers.

**Phase 1 — separate status=rate-limited from status=failed**
- fix-ci.sh: detect engine exit 2 → post `status=rate-limited` (not
  `status=failed`); embed parsed reset time in marker body; exit 2.
- fix-ci.sh: fix `check_idempotency` to treat `status=rate-limited`
  as retriable — only block on terminal statuses (applied, failed,
  no-changes). Previously, rate-limited markers blocked all retries.
- fix-ci.sh: `count_recent_failures` already filtered to `status=failed`;
  add explicit comment confirming rate-limited markers are excluded.
- fix-ci.sh: add `has_rate_limited_marker` dedup check so the same SHA
  never accumulates more than one rate-limited comment.
- fix-reviews.sh: on engine exit 2 for all five intent types
  (fix-reviews, fix-bot-comment, human, human-pr, rebase), post a
  `status=rate-limited` marker with embedded reset time and exit 2.
- fix-reviews.sh: for `human` and `human-pr` intents, also post a
  user-visible acknowledgment comment so users know their request was
  received and will be retried.

**Phase 2 — scheduled retry cron for fix-ci**
- dev-lead-retry.sh: new script scans all open PRs across TARGET_ORG
  for `status=rate-limited` markers on current HEAD SHA; skips PRs
  whose reset time is still in the future; staggered dispatches (30s
  between repos) to prevent cascading org-level rate-limit hits.
- dev-lead-retry.yml: new workflow — scheduled every 2 hours (dual
  offsets to work around GitHub scheduler skew), plus workflow_dispatch.

**Phase 3 — retry for fix-reviews intents (same cron)**
- dev-lead-retry.sh already handles all five fix-reviews intent types
  (fix-reviews, fix-bot-comment, human, human-pr, rebase) via
  `dev-lead-reviews-retry` dispatch events.
- dev-lead-intent.sh: add `dev-lead-reviews-retry` dispatch type routing
  to the intent classifier — maps intent_type from payload to the correct
  existing fix-reviews step.
- dev-lead.yml: add `dev-lead-reviews-retry` to repository_dispatch types.
- dev-lead.yml: unify concurrency group for all dispatch types to
  `dev-lead-pr-{pr_number}` to avoid per-type slot fragmentation.

**Tests**
- test_engine_writer.bats: 8 new tests covering rate-limit stdout detection,
  exit-2 mapping, fallback exhaustion, reset time parsing.
- test_fix_ci.bats: 5 new tests covering rate-limited status, exhaustion
  exclusion, idempotency pass-through, and dedup.
- test_fix_reviews.bats: 5 new tests covering all intent types + human ack.
- test_intent_ci.bats: 4 new tests for dev-lead-reviews-retry dispatch routing.
- e2e/scenarios/07-rate-limit-retry.sh: new E2E scenario covering all parts.
- 3 new event fixtures for repository_dispatch_reviews_retry_* payloads.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): address review comments on #196

Addresses all 18 review threads from Copilot, Codex, and Gemini.

**P1 bugs fixed:**
- dev-lead-retry.sh: all log echo calls in scan_pr_for_rate_limits and
  dispatch_* functions now write to stderr; only the final count is on
  stdout. Previously, log lines polluted the command substitution,
  causing arithmetic expansion to fail and aborting the scan on the
  first retry candidate.
- dev-lead-retry.yml: permissions: contents was read-only; creating
  repository_dispatch events requires contents: write.

**Correctness fixes:**
- dev-lead-retry.sh: restrict automated retries to intents whose context
  can be reconstructed at runtime (fix-reviews, human-pr, rebase). human
  and fix-bot-comment require USER_INSTRUCTION/COMMENT_BODY from the
  original event which cannot be re-fetched; they are explicitly excluded.
- dev-lead-fix-reviews.sh: resolve HEAD_SHA from the PR API when not
  provided by the triggering event (issue_comment intents carry no SHA).
  Without this, rate-limited markers had no sha= field and were
  invisible to the retry scanner.
- dev-lead-fix-reviews.sh: write terminal status=applied marker after
  successful fix-reviews, human-pr, rebase runs. Prevents the retry
  cron from re-dispatching the same intent on every subsequent tick when
  the SHA hasn't changed.
- dev-lead-retry.sh: check for reviews terminal marker before dispatching
  a retry (mirrors the existing fix-ci terminal-marker check).
- dev-lead-retry.sh: look up current check-run details (details_url, id)
  at dispatch time via the commits check-runs API, so retried fix-ci runs
  have full failure logs and annotations rather than empty fields.
- dev-lead-fix-ci.sh: embed check= field in the rate-limited marker so
  the retry cron knows which check run to look up.
- dev-lead-fix-reviews.sh: human intent now posts a "please re-mention
  @dev-lead" ack instead of "I'll retry automatically" since it will NOT
  be retried automatically. human-pr keeps the auto-retry ack (correct).

**Pagination (Copilot + Gemini):**
- dev-lead-fix-ci.sh: add --paginate to check_idempotency, count_recent_
  failures, has_rate_limited_marker.
- dev-lead-fix-reviews.sh: add --paginate to has_reviews_rate_limited_marker.
- dev-lead-retry.sh: add --paginate to PR comments and PR list fetches.

**Other improvements:**
- dev-lead-retry.yml: simplify to single cron schedule (15 */2 * * *);
  remove BOT_USER env var (unused in the script).
- dev-lead-retry.sh: raise repo list limit 200 → 1000; add hard-error
  when list_repos_for_org returns empty (surfaces token permission issues
  rather than silently scanning 0 repos).
- dev-lead-intent.sh: use jq -nc (compact, null-input) for context JSON
  construction — safe against values with special characters.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(pr-review): handle Gemini trust-mode failure and properly route engine fallbacks (#217)

* fix(pr-review): handle Gemini trust-mode failure and properly route engine fallbacks

Closes #208

* fix(ci): install gemini CLI and set trust mode for dev-lead agents

---------

Co-authored-by: Gemini CLI <gemini-cli@example.com>

* fix: resolve YAML syntax error in dev-lead-reusable workflow

* fix: address automated feedback from PR 217 (#219)

This addresses missed feedback from CodeRabbit (removing syntax errors in YAML) and gemini-code-assist (improving string concatenation in validate-engines.sh), and adds assertion output checks to tests.

Co-authored-by: Gemini CLI <gemini-cli@example.com>

* chore: remove Phase 7 shadow period comment from dev-lead.yml

* feat: skip PRs with CHANGES_REQUESTED review (+ FORCE_REVIEW and stale-review guards)

* feat: skip PRs with CHANGES_REQUESTED review decision

Adds an early-exit check alongside the existing CI guards so the cascade
skips any PR where a human reviewer has already requested changes.
Reviewing those PRs before the author responds wastes tokens and creates
confusing parallel feedback threads.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix: honor FORCE_REVIEW and skip only stale-free CHANGES_REQUESTED reviews

Two issues addressed from Codex P2 feedback:

1. FORCE_REVIEW bypass: mention-triggered and force_review=true runs now
   bypass the CHANGES_REQUESTED skip, so authors can explicitly request a
   re-review after addressing feedback.

2. Stale review guard: only skip when a CHANGES_REQUESTED review targets
   the current head SHA. On repos without automatic stale-review dismissal,
   reviewDecision stays CHANGES_REQUESTED after new commits — the cascade
   now re-engages when the author has pushed new work.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): resolve context loss, missing checkout, and push logic (#223)

* feat: skip PRs with CHANGES_REQUESTED review decision

Adds an early-exit check alongside the existing CI guards so the cascade
skips any PR where a human reviewer has already requested changes.
Reviewing those PRs before the author responds wastes tokens and creates
confusing parallel feedback threads.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix: honor FORCE_REVIEW and skip only stale-free CHANGES_REQUESTED reviews

Two issues addressed from Codex P2 feedback:

1. FORCE_REVIEW bypass: mention-triggered and force_review=true runs now
   bypass the CHANGES_REQUESTED skip, so authors can explicitly request a
   re-review after addressing feedback.

2. Stale review guard: only skip when a CHANGES_REQUESTED review targets
   the current head SHA. On repos without automatic stale-review dismissal,
   reviewDecision stays CHANGES_REQUESTED after new commits — the cascade
   now re-engages when the author has pushed new work.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): resolve context loss, missing checkout, and push logic

- dev-lead-intent.sh: include actor and body in INTENT_CONTEXT for comments
- dev-lead.yml: parse and pass INTENT_ACTOR and INTENT_COMMENT_BODY to agent
- dev-lead-fix-reviews.sh: perform PR checkout, git push, and post summary comments

* fix(dev-lead): exit with 0 on no-changes in fix-reviews and human-pr

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* test: bypass CI check

* fix(pr-review): remove CI bypass hack

* fix(dev-lead): use heredoc for multiline environment variables (#224)

* fix(dev-lead): use heredoc for multiline environment variables

* fix(dev-lead): ensure all engines are installed and context is consistent

* fix(dev-lead): resolve stale model name in engine fallbacks

* fix(dev-lead): improve fallback reliability and script quality

- engine.sh: resolve stale model names in fallback loop; use -latest Gemini aliases
- dev-lead-fix-reviews.sh: remove unused code and fix PR_URL export

* fix(dev-lead): resolve Gemini model names and shell lint warnings

* security(dev-lead): use random heredoc delimiter to prevent inject…
don-petry added a commit that referenced this pull request Aug 8, 2026
…-stale-bot-reviews.yml (#1292)

* fix: silence auth check in fix-stuck-prs when using GitHub App token

GitHub App tokens don't have user scope, so 'gh api user' returns 403.
This is fine - the script still works for PR operations. Suppress the error
so the script completes successfully with app-token fallback label.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: use explicit author instead of @me in stuck PR search

GitHub App tokens don't have user identity, so @me search doesn't work.
Use explicit 'don-petry' author instead to find PRs to fix.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: avoid subshell in while loop to preserve variable state

Using pipe in while loop created subshell where PROBLEM_PRS and FIXED_PRS
counters were incremented but changes didn't persist to parent shell.
Fixed by using process substitution for input instead of pipe.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* docs: add comprehensive status report for GitHub App setup

- Overview of completed work
- GitHub App authentication details
- 24 stuck PRs successfully fixed with approval reviews
- Architectural decisions and rationale
- Configuration guide and usage examples
- Known limitations and troubleshooting

System is now fully operational with GitHub App token authentication,
comprehensive documentation, and all infrastructure in place.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* docs: document stuck PR cleanup status and script fixes

Added documentation for:
- GitHub App token compatibility fixes in fix-stuck-prs.sh
  * Changed @me to explicit author (app tokens have no identity)
  * Fixed subshell variable scope (process substitution vs pipe)
  * Silenced expected 403 auth check error
- Current status: 24 PRs have approvals but remain OPEN
  * Auto-merge failed due to missing GitHub App permission
  * Approvals satisfy branch protection requirement
  * Next: expand permissions or manually merge

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: update list-prs.sh to search all repos by owner instead of @me

GitHub App tokens don't have user identity, so @me/@review-requested searches
fail. Changed to enumerate all repos in don-petry and petry-projects, then
search for open PRs within each repo. This covers the full scope:
- All open PRs in personal don-petry repos
- All open PRs in petry-projects org repos

Resolves 6 consecutive workflow failures due to zero PRs being enumerated.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* docs: add investigation report for PR review agent workflow failures

* fix: move env section before steps in workflow to fix YAML structure

GitHub Actions requires env section to come before steps in job definition.
Moving env definition up and setting GH_TOKEN only in steps that need it.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* script: add backfill-approvals to retroactively apply real approvals

Converts PRs with agent approval comments (but no real GitHub approvals)
to have actual APPROVED reviews. Needed for PRs reviewed before the
gh pr review --approve fix was applied.

Usage:
  scripts/backfill-approvals.sh          # dry-run (preview changes)
  scripts/backfill-approvals.sh false    # apply real approvals

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* feat: add backfill-approvals workflow to run as GitHub App bot

Runs backfill-approvals.sh via GitHub Actions so the approvals are posted
by the bot identity, not the PR author — GitHub rejects self-approvals.

Also fixes subshell counter bug in the script (piped while loop lost variable
state; switched to process substitution).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: convert inner while loop to process substitution to preserve counters

Both the outer repo loop and inner PR loop were piped subshells, causing
all counter increments (approved/skipped/failed) to be lost. The summary
always showed 0. Fixed by using < <(...) process substitution for both loops.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* car-hunt: add VIN-deep-dive flow, printable checklists, location-tier ranking

- Step 5.5 pre-test-drive flow: NHTSA VIN decode + per-VIN unrepaired-recall
  automation via Chrome MCP (Honda LWC owner portal documented as not
  automatable; NHTSA web form works).
- §F printable test-drive checklist generator (reportlab/Platypus PDF) with
  cold-start, model-specific red flags, negotiation table, walk-away rules.
- §G fraud / paperwork verification checklist (vehicle-agnostic, reusable):
  walk-away triggers, VIN three-location match, curbstoning, odometer fraud,
  bill of sale, title transfer, payment protection, stolen-vehicle, title-
  jumping, after-purchase steps.
- Step 5.25 head-to-head comparison template with information-asymmetry rule.
- Step 3 Location Quality Tier scoring (Birmingham AL metro table A/B/C/D)
  with adj-CPM modifier; sheet schema gains Adj.CPM + Tier columns.
- Hard-disqualification rules added: non-running engine keywords, mileage
  inconsistency, mandatory description scrape.
- Drive MCP overwrite limitation documented; sheet ID now read from memory
  rather than hardcoded.
- FB Marketplace operational facts: Birmingham AL city ID 107739635926718,
  /search? vs /vehicles? query handling, React-controlled inline composer,
  send-button selector, seller-name extraction regex.
- scheduled-tasks/used-car-search-{morning,afternoon}: tier A/B priority
  flag, sheet ID read from memory.
- Reference PDF generator scripts checked in for reuse.

* fix: re-approve PRs where approval predates last commit (stale approval)

Instead of skipping PRs that already have any APPROVED review, skip only
those where reviewDecision is not REVIEW_REQUIRED. This handles PRs where
a bot approval was posted before a new commit was pushed — GitHub's ruleset
engine treats those approvals as stale even with dismiss_stale_reviews=false.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: robustly extract JSON verdict from mixed claude --print output (#17)

Claude's --print mode can prefix the JSON verdict with conversational
preamble text, causing jq to fail with parse errors. Add
extract_verdict_json to engine.sh and wire all three cascade action
call sites in review-one-pr.sh to use it.

Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: extract_verdict_json — check dest file first (agent Bash-write style)

The cascade-action agent writes its verdict JSON to \$OUTPUT_FILE via a
Bash tool call, then prints a text summary to stdout. The previous fix
only scanned stdout (the .raw file), which contained no JSON. The agent-
written file at \$dest (= \$OUTPUT_FILE) was already correct — just not
checked. Now check \$dest first before falling back to stdout scan.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Enforce MAX_REVIEW_CYCLES cap before running the cascade (#18)

* fix: enforce MAX_REVIEW_CYCLES cap with human-escalation marker

Previously MAX_REVIEW_CYCLES only gated AI delegation in
post-pr-review.sh — the cascade itself ran on every cron tick
regardless of how many cycles had accumulated. Real-world result:
a PR could rack up 9+ review cycles (we observed exactly that on
ContentTwin#100) before any cap took effect.

Add a pre-cascade check in review-one-pr.sh: when the count of
existing `<!-- pr-review-agent v1 sha=... -->` markers is at or
above MAX_REVIEW_CYCLES (default 3), post one escalation comment
marked `<!-- pr-review-agent escalation -->`, label
needs-human-review, request don-petry, and exit 100 (skip
sentinel — doesn't burn the MAX_PRS budget). The escalation
marker doubles as the no-spam guard: subsequent runs detect it
and exit 100 immediately.

Also reuse a single `gh pr view` for both the cycle count and the
escalation-marker check.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* review feedback: surface escalation-comment failures, drop dead unset

- Drop `2>/dev/null || true` on `gh pr comment` so a failed escalation
  post is visible in the workflow log instead of silently leaving the
  PR with no marker (which would re-trigger the cap path next tick).
- Reword the cycle-count comment from "AI delegation loops" to "review
  loops" — the cap also catches cascade-only loops where every cycle
  approves and a new commit lands before merge.
- Remove the redundant `unset PR_BODIES` — never exported, no leakage.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>

* Reliability hardening: session circuit breaker, timeouts, retry, dedup (#20)

* feat: session circuit breaker, per-tier timeouts, retry, and triage hard-fail

Reliability hardening for the PR review agent.

1. Session circuit breaker (.github/workflows/pr-review.yml): on any
   non-zero, non-100 exit from review-one-pr.sh (general failure or rate
   limit on the fallback engine), break the per-PR loop, log a clear
   error annotation naming the failing PR and reason, and exit the step
   with code 1 so the run shows red. Prevents one systemic problem from
   silently burning the entire candidate pool.

2. Per-tier timeouts (scripts/engine.sh): triage/deep/audit/action/duck
   each get their own bounded timeout (180/600/600/300/300s defaults,
   env-overridable). Previously only the duck had a timeout — a hung
   tier could burn the whole 60min job budget.

3. Retry-with-backoff on transient errors (scripts/engine.sh): triage
   retries once on 124/137/143 (timeout / signal kill) since its caller
   captures stdout via $(...) so retries are safe. Deliberately NOT
   applied to run_agentic/run_duck where stdout is redirected to a file
   — a retry there would corrupt the partial first-attempt output.

4. Triage non-JSON now hard-fails (scripts/review-one-pr.sh): replaces
   the silent fallback that synthesized a fake "escalate=MEDIUM" verdict
   and proceeded to deep review. With the new circuit breaker, loud
   failure is the right call — masking a broken triage was burning
   tokens on every PR while the workflow looked healthy.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix: stop stacking duplicate agent reviews on the same PR

Two bugs were causing the agent to leave multiple comments on the same PR.
Together they produced 10 stacked APPROVED reviews on petry-projects/ContentTwin#100.

Bug A — idempotency check is order-dependent (review-one-pr.sh):
The previous marker-discovery code did:
  ((.reviews // []) + (.comments // [])) | .[].body | grep marker | tail -1
This relies on the array concatenation order, not chronological order. When
old agent comments existed alongside newer agent reviews, tail -1 picked
the comment-array marker (older) over the review-array marker (newer),
causing the script to think the head SHA hadn't been reviewed and re-run.
Replaced with a single jq pipeline that tags each item with submittedAt /
createdAt, sorts by timestamp, and takes the actual most-recent marker.

Bug B — no cleanup of prior agent items (post-pr-review.sh):
After successfully posting a new review/comment, prior agent items were
left in place, accumulating forever. Added mark_prior_agent_items_obsolete
which, after a successful post:
  - dismisses prior APPROVED/COMMENTED/CHANGES_REQUESTED agent reviews via
    the GitHub dismissal API (UI shows them struck-through as Dismissed)
  - edits prior agent comments to wrap their body in a collapsed <details>
    block with a "Superseded by re-review at <SHA>" summary, plus a
    `<!-- pr-review-agent superseded -->` sentinel for idempotency

All cleanup API calls are best-effort — failures don't break the workflow.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(cleanup): warn on API failures, preserve global newest, file-stage JSON

Three fixes to mark_prior_agent_items_obsolete from the review of PR #20:

1. ::warning:: annotations on every cleanup API failure (review/comment
   list-fetch, individual review dismissal, individual comment fetch+edit).
   Previously these were silenced with `|| true`, so a permissions change
   on the dismissal endpoint would let duplicates stack indefinitely with
   no signal in the Actions UI. Cleanup is still non-fatal — the new post
   has already landed — but failures are now visible.

2. Preserve the globally-latest agent item across BOTH categories, not the
   newest of each category separately. The earlier code split reviews and
   comments and applied `[:-1]` to each, which left a stale fix-request
   comment in place when the new post was a review (or vice versa). The
   one-off cleanup of ContentTwin#100 hit exactly this case: 12 stacked
   reviews collapsed to 1, but a stale comment from 2026-04-25 (SHA
   cd9132d6) was preserved as "newest comment" even though the latest
   review at SHA 3af8c8ee was newer overall. Now: compute the max
   timestamp across both feeds, exclude items at that timestamp.

3. Stage API responses to disk (`mktemp` + `jq <file>`) instead of routing
   through `--argjson "$var"`. The old approach broke on rare unescaped
   control chars in user-authored comment bodies (jq refused to parse the
   resulting shell-vared JSON). File-based input sidesteps the shell
   pipeline entirely.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>

* Remove car-hunt skill and scheduled tasks (moved to don-petry/don-petry) (#21)

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: apply Copilot fallback hardening and reuse GH_PAT for Copilot auth

- Use secrets.GH_PAT (existing personal account token with Copilot
  subscription) instead of a separate COPILOT_GITHUB_TOKEN secret
- Pin actions/create-github-app-token to SHA (v3.1.1) for supply chain safety
- Add post-install verification for gh-copilot extension with warning on failure
- Guard Copilot fallback path: skip PR gracefully if extension is not installed

Addresses issue #24 workflow fixes (1-3) that could not land via PR #25
due to GitHub App lacking workflows permission.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: switch Copilot engine to gh built-in and fix app-id deprecation

- Remove gh extension install (github/gh-copilot conflicts with built-in alias)
- Replace with gh copilot --version check using COPILOT_GITHUB_TOKEN (GH_PAT)
- Update all engine.sh copilot invocations from bare `copilot` binary to
  `gh copilot suggest --target shell` with GH_TOKEN overridden to GH_PAT
  so the user token (with Copilot subscription) is used, not the App token
- Fix actions/create-github-app-token: app-id → client-id (same secret value,
  just the renamed input in v3.x)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: harden Copilot fallback path and remove stale worktrees (#25)

- gh-copilot install: add --force flag and post-install verification
  warning so silent failures due to built-in alias conflict are visible
  in workflow logs instead of being silently swallowed by `|| true`
- fallback pre-flight check: verify `gh extension list | grep copilot`
  before switching to Copilot engine; if unavailable, skip the PR and
  continue the batch rather than session-aborting and dropping all
  remaining candidates (fixes the 27-PR drop on run #503)
- pin actions/create-github-app-token to SHA for v3.1.1 (Node.js 24
  compatible) to prevent hard break on 2026-06-02 when GitHub forces
  Node.js 24 as default runtime
- remove 6 stale Claude Code worktrees from git tracking; add
  .gitignore entry to prevent future worktrees from being committed
  (eliminates exit-128 warning on every actions/checkout post-job sweep)

Closes #24

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: petry-projects-pr-review-agent[bot] <petry-projects-pr-review-agent[bot]@users.noreply.github.com>

* ci: add pre-flight dedup check to prevent duplicate claude-issue PRs (#26)

Before invoking Claude on a labeled issue, a new shell step queries
for any open PR with a branch matching claude/issue-NNN-* (or a body
containing "Closes #NNN"). If one is found it posts a comment on the
issue pointing to the existing PR and skips the Claude step entirely.
A secondary prompt instruction tells Claude to check first and push to
the existing branch rather than opening a new PR.

Motivated by issue petry-projects/google-app-scripts#171, where the
claude label was re-applied three times and each run created a fresh
PR unaware of the prior attempts.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor: migrate to machine user PAT auth (closes #27) (#28)

* refactor: migrate from GitHub App to machine user PAT auth

Closes #27

GitHub Apps cannot be listed in CODEOWNERS, blocking PRs in repos with
require_code_owner_review: true. Switch all workflows to use a machine
user account's fine-grained PAT (DON_PETRY_BOT_GH_PAT secret), which can
join an org team listed in CODEOWNERS.

Workflows: removed actions/create-github-app-token steps in pr-review,
fix-stuck-prs, backfill-approvals, and daily-pr-review-health. All now
use secrets.DON_PETRY_BOT_GH_PAT directly.

Docs: renamed GITHUB_APP_SETUP.md to MACHINE_USER_SETUP.md with full
rewrite covering account creation, CODEOWNERS config, PAT generation,
and rotation. Updated auth sections in IMPLEMENTATION.md, SETUP.md,
STATUS.md, DOCUMENTATION.md, README.md.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* refactor: switch to org-scoped PAT secret DON_PETRY_BOT_PETRY_PROJECT_PAT

Replace DON_PETRY_BOT_GH_PAT with DON_PETRY_BOT_PETRY_PROJECT_PAT — the
new fine-grained PAT scoped to the petry-projects org (resource owner =
petry-projects). The previous PAT was scoped to the donpetry-bot personal
namespace and had no repository access.

The old DON_PETRY_BOT_GH_PAT secret is retained for any future use against
don-petry's personal repos.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* chore: ignore .claude/scheduled_tasks.lock state file

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>

* refactor: merge approval repair scripts into single automation

- Consolidate backfill-approvals.sh and fix-stuck-prs.sh into repair-pr-approvals.sh
- Iterate all repos in both orgs instead of assuming author
- Verify no existing APPROVED review before posting
- Copy original agent comment as review body
- Enable auto-merge when posting approval if needed
- Single workflow with 30-min timeout

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: add @mention trigger for on-demand PR reviews (#30)

Adds repository_dispatch support so commenting @petry-review-bot on any
PR fires an immediate review without waiting for the hourly schedule.

- pr-review.yml: new `repository_dispatch` trigger (type: pr-review-mention),
  per-PR concurrency group for mention runs, FORCE_REVIEW and DRY_RUN
  env vars that handle both workflow_dispatch and repository_dispatch paths
- review-one-pr.sh: FORCE_REVIEW=true bypasses idempotency so a mention
  always runs a fresh cascade even if the head SHA hasn't changed
- templates/mention-listener.yml: deploy to petry-projects/.github;
  listens for @petry-review-bot, validates commenter trust, posts ack,
  sends repository_dispatch (requires Contents:write, not Actions:write)

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: harden rebase, review dismissal, and health log diagnostics (#40)

Addresses 4 code-actionable recommendations from health check report #33:

- [CRITICAL] Make rebase/branch-update non-fatal — 403/504 during rebase
  emits ::warning:: instead of aborting the batch session (#34)
- [MEDIUM] Add retry with exponential backoff (3 attempts) for transient
  5xx on update-branch API; break immediately on 4xx (#37)
- [MEDIUM] Guard review dismissal with state re-check before calling
  dismiss API, preventing 422s from race conditions (#38)
- [LOW] Surface missing log warnings in health check script (#39)
- Fix: skip auto-merge when branch is still BEHIND after failed rebase

Closes #34, closes #37, closes #38, closes #39

* refactor: migrate to org-wide .github-private convention (#41)

* refactor: parametrize hardcoded identity values for org migration

Replace all hardcoded references to don-petry, petry-review-bot, and
don-petry/pr-review-agent with environment variables that default to
the current values. This allows the agent to be configured for different
orgs/users via repo variables.

Changes:
- Scripts use $REVIEWER_USER, $TARGET_ORG, $BOT_USER, $AGENT_REPO
- Prompts no longer reference specific GitHub usernames
- Workflows use org-level GH_PAT_WORKFLOWS secret instead of
  repo-level DON_PETRY_BOT_PETRY_PROJECT_PAT
- Health check uses context.repo.owner/repo for issue creation
- Variables are set in workflow env block with defaults

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add Copilot custom agent profiles for org-wide use

Create /agents/ directory with three agent profiles:
- pr-reviewer: Multi-tier cascading PR review
- feature-ideator: Feature idea generation and prioritization
- compliance-auditor: Org standards compliance checking

These are Copilot custom agent profiles that become available
org-wide in the .github-private repo convention.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Squashed 'frameworks/bmad-method/' content from commit e36f219c

git-subtree-dir: frameworks/bmad-method
git-subtree-split: e36f219c81b6010d4aae423ba12f49edb5b6e31a

* Squashed 'frameworks/spec-kit/' content from commit 11f49ebf

git-subtree-dir: frameworks/spec-kit
git-subtree-split: 11f49ebfb2f6af55345cb4bd9a7906acd211e56f

* Squashed 'frameworks/gsd/' content from commit 304c1a13

git-subtree-dir: frameworks/gsd
git-subtree-split: 304c1a1302564c45af252bbba4bcc5350e7dac3a

* docs: update README for .github-private org infrastructure role

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* ci: add dependabot-automerge.yml workflow (#73)

Adds the required dependabot-automerge.yml workflow from the org
standard template (petry-projects/.github/standards/workflows/).
This is a thin caller stub that delegates to the org-level reusable
workflow.

Closes #48

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Don Petry <don-petry@users.noreply.github.com>

* fix(pr-review): unblock queue starvation from self-authored PRs (#96) (#97)

* fix(pr-review): unblock queue starvation from self-authored PRs (#96)

A self-authored PR sorted first in the candidate list and triggered
"Can not approve your own pull request" — which the session-fatal
abort treated like an engine error, skipping all 28 remaining PRs
on every run.

- list-prs.sh: filter out PRs authored by REVIEWER_USER at enumeration
- post-pr-review.sh: catch the GraphQL self-approval error and exit 100
  (no-op) instead of 1, so a stray self-PR can no longer abort the batch
- engine.sh: gh copilot renamed --target to --agent; fix the rubber-duck
  invocations so tier-2 cross-engine review works again
- review-one-pr.sh: stop appending a duplicate "0" to REVIEW_CYCLE when
  grep -c finds no markers under set -o pipefail (was breaking the
  cycle-cap integer comparison)

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

* fix(pr-review): authenticate as bot, harden enumeration, address review

- Workflow now runs as AGENT_USER (default don-petry-bot), distinct from
  REVIEWER_USER (the human, don-petry). The self-approval rejection that
  caused #96 came from the agent and the human sharing one identity.
- list-prs.sh filters self-authored PRs against AGENT_USER, and validates
  AGENT_USER against the GitHub username charset before interpolating into
  the jq filter (Copilot review feedback).
- review-one-pr.sh uses printf '%s\n' instead of echo for PR_BODIES, since
  PR body content is user-authored and could begin with -n/-e or contain
  backslash escapes (Copilot review feedback).
- AGENT.md guideline updated to reflect that self-authored PRs are
  intentionally excluded (CodeRabbit review feedback).

Operator note: rotate the GH_PAT_WORKFLOWS secret to a token owned by
don-petry-bot (with read:org added) for the bot-account behavior to take
effect at runtime.

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

* refactor(pr-review): collapse to single BOT_USER, route escalations via CODEOWNERS

Drop AGENT_USER and REVIEWER_USER. The workflow now has one identity:
BOT_USER (default don-petry-bot), which both owns the repos to scan and
gets filtered out as the self-approval blocker. Human escalation no
longer hard-codes a single reviewer; instead, scripts/request-codeowners-
review.sh parses CODEOWNERS in the PR's repo and requests review from
every @user / @org/team mention.

- list-prs.sh: BOT_USER for both gh-repo-list and self-author filter
- review-one-pr.sh, post-pr-review.sh: replace --user "$REVIEWER_USER"
  with the CODEOWNERS helper
- repair-pr-approvals.sh: same gh-repo-list switch
- pr-review.yml: drop AGENT_USER/REVIEWER_USER, set BOT_USER default
  to don-petry-bot
- AGENT.md: updated guideline to describe single-identity model

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

* refactor(pr-review): extract batch loop, hoist env, drop redundant step

Workflow YAML drops from 231 to 111 lines by moving the per-PR review
loop into scripts/review-batch.sh. The script can be syntax-checked and
exercised locally; the workflow now just wires env vars and dispatches.

- Hoist GH_TOKEN, MAX_PRS, CANDIDATE_LIMIT to job-level env (were
  repeated on individual steps).
- Inline `gh auth status` into the install step; remove the standalone
  Verify auth step (its only output was a one-line auth dump).
- Drop the `if: steps.list.outputs.count != '0'` guard and the step's
  `id`/output: review-batch.sh handles empty input as a no-op.
- Collapse the duplicated summary-string branch in the review loop into
  a single template with a conditional fragment.

No behavior change.

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

* perf(pr-review): cache claude-code CLI install across runs

`npm install -g @anthropic-ai/claude-code` ran on every workflow start
(~30s). Switch to a per-user npm prefix (~/.npm-global) and cache that
directory via actions/cache, keyed on CLAUDE_CODE_VERSION + runner OS.

A `command -v claude` guard makes the install a no-op on cache hit, so
the only cost on subsequent runs is the cache restore.

CLAUDE_CODE_VERSION defaults to 'latest' (cache persists until manually
flushed); set the repo variable to pin a specific version for fully
reproducible caching.

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

---------

Co-authored-by: Claude <noreply@anthropic.com>

* chore: rename GH_PAT_WORKFLOWS secret to DON_PETRY_BOT_GH_PAT (#99)

The previous name was generic and didn't tell you which account the PAT
belonged to. The new name makes the binding explicit: this secret is the
PAT owned by don-petry-bot, used as BOT_USER throughout the PR-review
workflows.

Operator follow-up before this can be merged:
- Add a new repo secret DON_PETRY_BOT_GH_PAT containing the bot's PAT
  (with repo, workflow, and read:org scopes).
- After merge, the old GH_PAT_WORKFLOWS secret can be deleted.

Affected workflows:
- pr-review.yml (1 use)
- claude.yml (4 uses, with || github.token fallback)
- daily-pr-review-health.yml (1 use)
- repair-pr-approvals.yml (1 use)

scripts/pr_review_health.sh's error message is updated to point at the
new name as well.

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

Co-authored-by: Claude <noreply@anthropic.com>

* chore: remove frameworks directory (#101)

Co-authored-by: Gemini CLI <gemini-cli@example.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore all erroneously deleted files (items 4-34) (#104)

* revert: restore .github/workflows/daily-pr-review-health.yml

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore .github/workflows/repair-pr-approvals.yml

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore agents/pr-reviewer.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/cascade-action.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/deep-review.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/rubber-duck.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/security-audit.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/shared.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/single-review.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/synthesize-duck.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/synthesize.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/triage.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/engine.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/list-prs.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/post-pr-review.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/pr_review_health.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/repair-pr-approvals.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/request-codeowners-review.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/review-batch.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/review-one-pr.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore templates/mention-listener.yml

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore AGENT.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore BOT_SETUP.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore DOCUMENTATION.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore IMPLEMENTATION.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore MACHINE_USER_SETUP.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore PR_REVIEW_AGENT_FAILURE_REPORT.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore PR_REVIEW_FAILURE_INVESTIGATION.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore SETUP.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore STATUS.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore README.md to pre-change state

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore .github/workflows/daily-pr-review-health.yml

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat: add Gemini Pro support and optimize PR review fallback chain (#102)

* Merge main and resolve conflicts

* Enhance pr-review workflow: add Gemini support and refactor fallback logic

* Address Copilot review comments: tighten regex, fix fallback summary, and align docs

* docs: move agent documentation to docs/ folder and rename files

* docs: align secret names and fix casing in index

* docs: organize pr-review-agent documentation under dedicated folder

* Address final PR comments: tighten regex, add preflight checks, fix bot names, and cleanup docs

---------

Co-authored-by: Gemini CLI <gemini-cli@example.com>

* fix(pr-review): gate rate-limit detection on non-zero exit code

Broad patterns like `plan.*limit` and `claude.*usage` could match content
in a successful triage summary, triggering a false-positive engine fallback.
Guard the check with TRIAGE_RC != 0 so it only fires when the provider
command actually failed.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(claude): sync inlined workflow with org standard (#109)

* fix(claude): sync inlined workflow with org standard

Brings the inlined claude.yml up to parity with
petry-projects/.github/.github/workflows/claude-code-reusable.yml.

Changes:
1. Bot allow list for pull_request_review_comment
   Add coderabbitai[bot], Copilot, copilot-pull-request-reviewer[bot],
   and gemini-code-assist[bot] alongside the existing OWNER/MEMBER/
   COLLABORATOR check. These bots have author_association 'NONE' so
   their review comments were always skipped.

2. check_run trigger + claude-ci-fix job
   Port the CI failure auto-fix feature from the reusable. When a check
   fails on a PR, Claude diagnoses and fixes it automatically.
   Adapted to use DON_PETRY_BOT_GH_PAT (this repo's secret name).

3. claude-code-action SHA bump: v1.0.89 → v1.0.119
   Update both the claude and claude-issue jobs.

4. paths-ignore on pull_request trigger
   Prevents the workflow from firing on PRs that only change
   claude.yml itself, avoiding the Anthropic OIDC validation
   failure (workflow file must match default branch at token exchange).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(claude): guard bot allow list against fork PRs

Same fix as petry-projects/.github PR #238 — add same-repo guard
for bot-triggered pull_request_review_comment runs.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(claude): address ci-fix self-loop and fork PR security

- Replace Claude Code name prefix check with explicit job name list to
  correctly prevent self-loops (check_run names for inlined workflows
  are bare job names, not workflow-prefixed)
- Add fork PR trust gate in Resolve PR number step: verify head repo
  matches target repo before running Claude with privileged credentials

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* ci: replace inlined claude.yml with standard thin-caller stub

The inlined version embedded all three jobs (claude, claude-ci-fix,
claude-issue) directly and threaded DON_PETRY_BOT_GH_PAT through
checkout tokens and github_token, causing the bot's PAT to author
PRs and comments instead of github-actions[bot].

Replace with the standard thin caller that delegates to the org-level
reusable workflow (claude-code-reusable.yml@v1) via secrets: inherit,
which is the correct pattern per:
petry-projects/.github/standards/workflows/claude.yml

* fix: replace gh copilot suggest with GitHub Models REST API (#151)

* fix: replace gh copilot suggest with GitHub Models REST API (#147)

The `gh copilot suggest -p "$(cat <file>)"` invocation failed with
"Invalid command format" because:
1. The `-p` flag is not valid syntax in modern `gh copilot` built-in versions.
2. `gh copilot suggest` is a shell-command suggestion tool; it does not
   accept large PR prompts or return structured JSON responses.
3. Passing a multi-thousand-line prompt via `$(cat ...)` can hit ARG_MAX.

The non-zero exit was then misclassified by the rate-limit detector, which
aborted the entire session and skipped all remaining PRs in the queue.

Fix: replace all three copilot invocations (run_triage, run_agentic,
run_duck) with a new `copilot_chat` helper that calls the GitHub Models
REST API directly via curl. The API is OpenAI-compatible, versioned via
`X-GitHub-Api-Version`, stable against gh CLI version changes, and accepts
arbitrary prompt sizes using python3 for safe JSON encoding.

Additional changes:
- Add `COPILOT_API_MODEL` var (default `openai/o4-mini`) overrideable via env.
- run_agentic/run_duck copilot cases now also write output to $OUTPUT_FILE
  so callers that check that path directly (deep review, audit) find the JSON.
- Rate-limit responses (HTTP 429) are echoed to stdout so the existing
  `is_rate_limited()` detector fires correctly for engine fallback.
- Add pre-flight smoke test in review-batch.sh: tests GitHub Models API
  connectivity with a one-liner prompt before processing any PRs, so auth
  or model errors surface immediately as a clear setup failure.
- Add tests/test_copilot_chat.sh: unit tests for the JSON payload builder
  with edge-case prompts (quotes, newlines, # headings, large diffs, Unicode).

Closes #147

Co-authored-by: Don Petry <don-petry@users.noreply.github.com>

* fix: scope COPILOT_API_MODEL export to copilot engine only

Move the `export COPILOT_API_MODEL` from the common exports block into the
`copilot)` case where the variable is set, so it is not exported as an
empty/unset variable when the engine is `claude` or `gemini`. The
`copilot_chat` fallback `${COPILOT_API_MODEL:-openai/o4-mini}` still works
correctly when `DUCK_ENGINE=copilot` under a non-copilot primary engine.

Co-authored-by: Don Petry <don-petry@users.noreply.github.com>

* fix: address review comments — temp file for payload, fail-fast source, streaming output

- copilot_chat: write JSON body to mktemp file, pass to curl as @file to
  avoid ARG_MAX for large PR diffs (was --data-binary "$body")
- copilot_chat: add :? guard on COPILOT_GITHUB_TOKEN for a clear error
  instead of generic "unbound variable" under set -u
- run_agentic / run_duck copilot paths: stream directly to stdout (and
  tee to OUTPUT_FILE when set) rather than buffering the full response
  into a shell variable, which forced large outputs into memory and
  stripped trailing newlines
- review-batch.sh pre-flight: fail fast if source engine.sh fails
  (was silently ignored with || true)
- review-batch.sh pre-flight: build smoke-test JSON payload via python3
  + temp file instead of shell string interpolation to avoid JSON
  injection if COPILOT_API_MODEL contains special characters
- Clarify that openai/o4-mini is the correct April-2025 o4-generation
  model name, not a typo for o1-mini or gpt-4o-mini

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Don Petry <don-petry@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* feat: prioritise .github/.github-private PRs, oldest-first within tier (#155)

* feat: prioritise .github/.github-private PRs, oldest-first within tier

- list-prs.sh: add createdAt to JSON fetch; emit priority|createdAt|url
  lines (priority 0 for .github/.github-private, 1 for everything else)
- Replace final sort -u with a two-pass sort: deduplicate by URL, then
  sort by priority asc then createdAt asc; strip sort keys with cut
- tests/test_list_prs_sort.sh: 16 tests covering priority classification,
  oldest-first ordering, mixed scenarios and deduplication
- .github/workflows/test.yml: run both unit-test files on every PR / push

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* test: add dedup edge case for same URL with conflicting priorities

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: remove temperature from copilot_chat payload (o4-mini incompatible)

The GitHub Models API rejects temperature=0 for reasoning models like
o4-mini with HTTP 400:
  'Unsupported value: temperature does not support 0 with this model.
   Only the default (1) value is supported.'

This caused the rubber duck (DUCK_ENGINE=copilot / DUCK_MODEL=o4-mini)
to fail with HTTP 400 on every claude-engine run, silently degrading
every review to deep-only with no cross-engine sanity check.

Fix: remove temperature from the copilot_chat JSON payload entirely —
the API defaults to 1, which is the only supported value for o4-mini.

Also:
- Update tests/test_copilot_chat.sh: sync build_payload to match and
  flip Test 9 to assert temperature is ABSENT (not 0).
- Add .github/workflows/test.yml: run unit tests on every PR and push
  to main so regressions are caught before merge.

Reproducer: job/75690347409 — duck failure logged as:
  copilot_chat: HTTP 400 from GitHub Models API
  {"error":{"message":"Unsupported value: 'temperature' does not
   support 0 with this model..."}}

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: address PR review comments

- list-prs.sh: tighten priority regex to /[.]github(-private)?/pull/
  so path-boundary anchor prevents false positives (e.g. foo.github)
- list-prs.sh: replace printf pipeline with here-string to avoid ARG_MAX
  limits on large PR sets
- test.yml: add permissions: contents: read (least-privilege, CodeQL fix)
- test.yml: add timeout-minutes: 5 to prevent hung runs
- test_list_prs_sort.sh: sync sort_entries helper (here-string + || true)
  and classify regex to match updated list-prs.sh exactly

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: don-petry <don@petry.dev>

* fix: restore pr-review.yml content (accidentally emptied in revert)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: restore pr-review.yml (file was accidentally emptied)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore(deps): bump actions/cache from 4 to 5 (#165)

Bumps [actions/cache](https://github.com/actions/cache) from 4 to 5.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): bump petry-projects/.github/.github/workflows/claude-code-reusable.yml (#164)

Bumps [petry-projects/.github/.github/workflows/claude-code-reusable.yml](https://github.com/petry-projects/.github) from 1 to 2.
- [Commits](https://github.com/petry-projects/.github/compare/v1...v2)

---
updated-dependencies:
- dependency-name: petry-projects/.github/.github/workflows/claude-code-reusable.yml
  dependency-version: '2'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* fix(list-prs): search DELEGATION_ORGS and drop --checks success pre-filter

- Add iteration over all orgs in DELEGATION_ORGS (e.g. don-petry) so PRs
  in those orgs enter the candidate pool. Previously only BOT_USER and
  TARGET_ORG were searched.
- Remove --checks success from org repo searches. GitHub excludes PRs from
  repos with no CI configured when this flag is used, silently dropping all
  PRs from repos like .github, .github-private, google-app-scripts, and
  TalkTerm. review-one-pr.sh already enforces CI gating per-PR and treats
  empty statusCheckRollup as passing, so the pre-filter is redundant and
  harmful.
- Add --limit 200 to gh repo list calls to handle org growth beyond gh's
  default 30-repo cap.

* feat: add code-quality ruleset (compliance fix #60) (#86)

Creates the required `code-quality` repository ruleset enforcing required
status checks on the default branch, as mandated by the org standard:
standards/github-settings.md#code-quality--required-checks-ruleset-all-repositories

Required checks:
- SonarCloud (code quality analysis)
- CodeQL (SAST)
- agent-shield / AgentShield (agent security scan)
- dependency-audit / Detect ecosystems (dependency vulnerability scan)

Bypass actors:
- OrganizationAdmin (always) — emergency override
- dependabot-automerge-petry Integration (always) — Dependabot auto-merge

The ruleset was applied directly via GitHub API. This file documents the
configuration as code for auditability and future reapplication.

Closes #60

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Don Petry <don-petry@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* feat(prompts/dev-lead): add human prompt template

* feat(prompts/dev-lead): add human-pr prompt template

* feat(dev-lead): implement dev-lead agent Phases 0-6

* fix: per-PR isolation and single-review retry (closes #132)

- review-batch.sh: non-rate-limit per-PR failures (exit code 1) no
  longer abort the session. SESSION ABORTED EARLY is now reserved for
  the rate-limit-on-fallback-engine case (exit code 2) only. All other
  failures are counted and logged; remaining candidates continue.

- review-one-pr.sh: single-review step retries up to
  SINGLE_REVIEW_MAX_RETRIES (default 2) times with a
  SINGLE_REVIEW_RETRY_DELAY_SEC (default 15s) gap before giving up.
  On exhaustion, the PR is flagged needs-human-review and the script
  exits with code 1, which the updated batch treats as a non-fatal
  per-PR failure. Raw model output and stderr are logged on each failed
  attempt for post-mortem visibility.

Root cause of run #25707852006: claude-opus-4-7 returned a verbose
non-JSON response for PR #129; the old code treated that as fatal and
skipped 35 remaining candidates.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix: rate-limit detection and per-attempt stderr logs in single-review retry

Address inline review comments on PR #133:

- Rate-limit check: after each run_agentic call, inspect both stdout
  (VERDICT_JSON.raw) and stderr (SINGLE_LOG) with is_rate_limited before
  retrying. A rate-limit match exits immediately with code 2 so
  review-batch.sh can trigger engine fallback — consistent with triage and
  deep-review tiers. Previously a rate-limited single-review would burn all
  retries and exit 1 (per-PR failure), silently leaving the batch on the
  same rate-limited engine for all remaining PRs.

- Per-attempt log files: stderr is now written to
  single-review-attempt-N.log rather than a single overwritten file, so
  no earlier-attempt errors are lost. Each attempt logs its own stderr
  inline on failure; the fallback path cats all attempt logs for
  post-mortem visibility.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* feat(dev-lead): Phase 0 test infrastructure and Phase 1 intent stub

Phase 0: full test harness for the dev-lead agent —
  26 event fixtures (all valid JSON with _test_expected_intent),
  stub claude/gemini engines, mock gh binary, CI failure log sample,
  bats helpers (stub-engine, mock-gh, assert-env, prompt-vars),
  7 prompt templates with VARIABLES declarations, preflight script,
  prompt coverage integration test, and test-dev-lead.yml CI workflow.

Phase 1: dev-lead.yml trigger workflow (all 7 event types, dispatch + ci-relay
  jobs) and dev-lead-intent.sh stub (anti-loop guard live; all other events
  emit skip/not-implemented).  14/14 bats unit tests pass.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): add permissions blocks to test workflow jobs (CodeQL)

* fix(ci): auto-fix for lint / eslint [skip ci-relay]

* feat(dev-lead): Phases 2-6 — CI fix, review fix, issue, engine fallback

Phase 2: run_writer/run_writer_with_fallback in engine.sh, full intent
routing in dev-lead-intent.sh, dev-lead-fix-ci.sh handler, workflow wiring.

Phase 3: dev-lead-fix-reviews.sh handles fix-reviews, fix-bot-comment,
human, human-pr, rebase intents. Full review routing in intent classifier.

Phase 5: dev-lead-fix-issue.sh handles the issue intent with dedup guard
and branch/PR creation.

Phase 6: run_writer_with_fallback with claude→gemini→copilot fallback chain.

Tests: 77 unit tests across 8 new .bats files, all passing. Updated Phase 1
stub tests to reflect actual Phase 2+ routing behavior.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): address review comments — dispatch JSON, dead code, portable stub gh

* fix(dev-lead): pre-flight after intent, fork URL check, review-batch exit code, retry comment

* fix(dev-lead): install bats-core from GitHub to avoid root requirement

* fix(security): move event values to env vars to prevent script injection (SonarCloud)

* fix(security): move CLAUDE_CODE_VERSION to env block (SonarCloud script injection)

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* feat(dev-lead): Phase 1.5 — reusable workflow for cross-repo adoption + Phase 7 shadow period

* feat(dev-lead): add reusable workflow (Phase 1.5) and begin shadow period (Phase 7)

- Create .github/workflows/dev-lead-reusable.yml: workflow_call entry
  point for other repos. Checks out .github-private scripts/prompts into
  .dev-lead/, then runs the same intent-classify + handler pipeline as
  dev-lead.yml with PROMPTS_DIR=.dev-lead/prompts/dev-lead.
- Add PROMPTS_DIR env-var support to dev-lead-fix-ci.sh,
  dev-lead-fix-reviews.sh, and dev-lead-fix-issue.sh so the reusable
  workflow can point scripts at the sparse-checkout path without changing
  CWD. Defaults to prompts/dev-lead (backwards-compatible for dev-lead.yml).
- Annotate dev-lead.yml with Phase 7 shadow-period window (2026-05-15
  through ~2026-05-29): claude.yml and dev-lead.yml run in parallel until
  regressions clear, then claude.yml is removed.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* chore(dev-lead): update shadow-period tracking issue ref to #180

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): document PAT requirement and pin ref:main on private checkout

* fix(dev-lead): P1 review fixes — ci-relay in reusable, .dev-lead gitignore, export PROMPTS_DIR, trim header

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): rate-limit detection, separate status, and retry cron (#192) (#196)

* chore(dev-lead): decommission claude.yml, bump ACTION_TIMEOUT_SEC to 600s

- Delete .github/workflows/claude.yml — replaced by dev-lead.yml (shadow
  period complete, tracking issue #180).
- scripts/engine.sh: raise ACTION_TIMEOUT_SEC default 300→600s to reduce
  timeout failures on large-repo fix-ci runs (PR #80 had 3 timeouts).
- AGENTS.md: remove claude.yml immutability exemption; note dev-lead.yml
  as the active AI automation workflow and its edit-via-reusable pattern.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* refactor: replace Claude analysis with pure gh/jq telemetry in health check

* refactor: drop Node/Claude steps from daily-pr-review-health workflow

* rename: daily-pr-review-health → actions-fleet-monitor

* rename: daily-pr-review-health → actions-fleet-monitor

* fix(agents-md): clarify dev-lead.yml vs dev-lead-reusable.yml scope

dev-lead.yml in .github-private runs inline steps (not a caller stub).
Behavior changes for this repo go to dev-lead.yml directly; changes that
affect all org repos via the cross-repo reusable go to dev-lead-reusable.yml.

Addresses Copilot review on PR #194.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): rate-limit detection, separate status, and retry cron

Closes #192. Implements all phases from the revised plan.

**Phase 0 — fix run_writer stdout capture (prerequisite)**
- engine.sh: capture stdout via `tee` to a tempfile so `is_rate_limited`
  can inspect the output; old code read `/tmp/dev-lead-writer-stderr`
  which was never written (claude --print outputs to stdout, not stderr).
  Fallback engines were never tried in practice.
- engine.sh: add `parse_reset_time` to extract ISO timestamp from
  `resets H:MMpm (UTC)` in engine output and write it to
  `/tmp/dev-lead-rate-limit-reset` for callers to embed in markers.

**Phase 1 — separate status=rate-limited from status=failed**
- fix-ci.sh: detect engine exit 2 → post `status=rate-limited` (not
  `status=failed`); embed parsed reset time in marker body; exit 2.
- fix-ci.sh: fix `check_idempotency` to treat `status=rate-limited`
  as retriable — only block on terminal statuses (applied, failed,
  no-changes). Previously, rate-limited markers blocked all retries.
- fix-ci.sh: `count_recent_failures` already filtered to `status=failed`;
  add explicit comment confirming rate-limited markers are excluded.
- fix-ci.sh: add `has_rate_limited_marker` dedup check so the same SHA
  never accumulates more than one rate-limited comment.
- fix-reviews.sh: on engine exit 2 for all five intent types
  (fix-reviews, fix-bot-comment, human, human-pr, rebase), post a
  `status=rate-limited` marker with embedded reset time and exit 2.
- fix-reviews.sh: for `human` and `human-pr` intents, also post a
  user-visible acknowledgment comment so users know their request was
  received and will be retried.

**Phase 2 — scheduled retry cron for fix-ci**
- dev-lead-retry.sh: new script scans all open PRs across TARGET_ORG
  for `status=rate-limited` markers on current HEAD SHA; skips PRs
  whose reset time is still in the future; staggered dispatches (30s
  between repos) to prevent cascading org-level rate-limit hits.
- dev-lead-retry.yml: new workflow — scheduled every 2 hours (dual
  offsets to work around GitHub scheduler skew), plus workflow_dispatch.

**Phase 3 — retry for fix-reviews intents (same cron)**
- dev-lead-retry.sh already handles all five fix-reviews intent types
  (fix-reviews, fix-bot-comment, human, human-pr, rebase) via
  `dev-lead-reviews-retry` dispatch events.
- dev-lead-intent.sh: add `dev-lead-reviews-retry` dispatch type routing
  to the intent classifier — maps intent_type from payload to the correct
  existing fix-reviews step.
- dev-lead.yml: add `dev-lead-reviews-retry` to repository_dispatch types.
- dev-lead.yml: unify concurrency group for all dispatch types to
  `dev-lead-pr-{pr_number}` to avoid per-type slot fragmentation.

**Tests**
- test_engine_writer.bats: 8 new tests covering rate-limit stdout detection,
  exit-2 mapping, fallback exhaustion, reset time parsing.
- test_fix_ci.bats: 5 new tests covering rate-limited status, exhaustion
  exclusion, idempotency pass-through, and dedup.
- test_fix_reviews.bats: 5 new tests covering all intent types + human ack.
- test_intent_ci.bats: 4 new tests for dev-lead-reviews-retry dispatch routing.
- e2e/scenarios/07-rate-limit-retry.sh: new E2E scenario covering all parts.
- 3 new event fixtures for repository_dispatch_reviews_retry_* payloads.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): address review comments on #196

Addresses all 18 review threads from Copilot, Codex, and Gemini.

**P1 bugs fixed:**
- dev-lead-retry.sh: all log echo calls in scan_pr_for_rate_limits and
  dispatch_* functions now write to stderr; only the final count is on
  stdout. Previously, log lines polluted the command substitution,
  causing arithmetic expansion to fail and aborting the scan on the
  first retry candidate.
- dev-lead-retry.yml: permissions: contents was read-only; creating
  repository_dispatch events requires contents: write.

**Correctness fixes:**
- dev-lead-retry.sh: restrict automated retries to intents whose context
  can be reconstructed at runtime (fix-reviews, human-pr, rebase). human
  and fix-bot-comment require USER_INSTRUCTION/COMMENT_BODY from the
  original event which cannot be re-fetched; they are explicitly excluded.
- dev-lead-fix-reviews.sh: resolve HEAD_SHA from the PR API when not
  provided by the triggering event (issue_comment intents carry no SHA).
  Without this, rate-limited markers had no sha= field and were
  invisible to the retry scanner.
- dev-lead-fix-reviews.sh: write terminal status=applied marker after
  successful fix-reviews, human-pr, rebase runs. Prevents the retry
  cron from re-dispatching the same intent on every subsequent tick when
  the SHA hasn't changed.
- dev-lead-retry.sh: check for reviews terminal marker before dispatching
  a retry (mirrors the existing fix-ci terminal-marker check).
- dev-lead-retry.sh: look up current check-run details (details_url, id)
  at dispatch time via the commits check-runs API, so retried fix-ci runs
  have full failure logs and annotations rather than empty fields.
- dev-lead-fix-ci.sh: embed check= field in the rate-limited marker so
  the retry cron knows which check run to look up.
- dev-lead-fix-reviews.sh: human intent now posts a "please re-mention
  @dev-lead" ack instead of "I'll retry automatically" since it will NOT
  be retried automatically. human-pr keeps the auto-retry ack (correct).

**Pagination (Copilot + Gemini):**
- dev-lead-fix-ci.sh: add --paginate to check_idempotency, count_recent_
  failures, has_rate_limited_marker.
- dev-lead-fix-reviews.sh: add --paginate to has_reviews_rate_limited_marker.
- dev-lead-retry.sh: add --paginate to PR comments and PR list fetches.

**Other improvements:**
- dev-lead-retry.yml: simplify to single cron schedule (15 */2 * * *);
  remove BOT_USER env var (unused in the script).
- dev-lead-retry.sh: raise repo list limit 200 → 1000; add hard-error
  when list_repos_for_org returns empty (surfaces token permission issues
  rather than silently scanning 0 repos).
- dev-lead-intent.sh: use jq -nc (compact, null-input) for context JSON
  construction — safe against values with special characters.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(pr-review): handle Gemini trust-mode failure and properly route engine fallbacks (#217)

* fix(pr-review): handle Gemini trust-mode failure and properly route engine fallbacks

Closes #208

* fix(ci): install gemini CLI and set trust mode for dev-lead agents

---------

Co-authored-by: Gemini CLI <gemini-cli@example.com>

* fix: resolve YAML syntax error in dev-lead-reusable workflow

* fix: address automated feedback from PR 217 (#219)

This addresses missed feedback from CodeRabbit (removing syntax errors in YAML) and gemini-code-assist (improving string concatenation in validate-engines.sh), and adds assertion output checks to tests.

Co-authored-by: Gemini CLI <gemini-cli@example.com>

* chore: remove Phase 7 shadow period comment from dev-lead.yml

* feat: skip PRs with CHANGES_REQUESTED review (+ FORCE_REVIEW and stale-review guards)

* feat: skip PRs with CHANGES_REQUESTED review decision

Adds an early-exit check alongside the existing CI guards so the cascade
skips any PR where a human reviewer has already requested changes.
Reviewing those PRs before the author responds wastes tokens and creates
confusing parallel feedback threads.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix: honor FORCE_REVIEW and skip only stale-free CHANGES_REQUESTED reviews

Two issues addressed from Codex P2 feedback:

1. FORCE_REVIEW bypass: mention-triggered and force_review=true runs now
   bypass the CHANGES_REQUESTED skip, so authors can explicitly request a
   re-review after addressing feedback.

2. Stale review guard: only skip when a CHANGES_REQUESTED review targets
   the current head SHA. On repos without automatic stale-review dismissal,
   reviewDecision stays CHANGES_REQUESTED after new commits — the cascade
   now re-engages when the author has pushed new work.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): resolve context loss, missing checkout, and push logic (#223)

* feat: skip PRs with CHANGES_REQUESTED review decision

Adds an early-exit check alongside the existing CI guards so the cascade
skips any PR where a human reviewer has already requested changes.
Reviewing those PRs before the author responds wastes tokens and creates
confusing parallel feedback threads.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix: honor FORCE_REVIEW and skip only stale-free CHANGES_REQUESTED reviews

Two issues addressed from Codex P2 feedback:

1. FORCE_REVIEW bypass: mention-triggered and force_review=true runs now
   bypass the CHANGES_REQUESTED skip, so authors can explicitly request a
   re-review after addressing feedback.

2. Stale review guard: only skip when a CHANGES_REQUESTED review targets
   the current head SHA. On repos without automatic stale-review dismissal,
   reviewDecision stays CHANGES_REQUESTED after new commits — the cascade
   now re-engages when the author has pushed new work.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): resolve context loss, missing checkout, and push logic

- dev-lead-intent.sh: include actor and body in INTENT_CONTEXT for comments
- dev-lead.yml: parse and pass INTENT_ACTOR and INTENT_COMMENT_BODY to agent
- dev-lead-fix-reviews.sh: perform PR checkout, git push, and post summary comments

* fix(dev-lead): exit with 0 on no-changes in fix-reviews and human-pr

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* test: bypass CI check

* fix(pr-review): remove CI bypass hack

* fix(dev-lead): use heredoc for multiline environment variables (#224)

* fix(dev-lead): use heredoc for multiline environment variables

* fix(dev-lead): ensure all engines are installed and context is consistent

* fix(dev-lead): resolve stale model name in engine fallbacks

* fix(dev-lead): improve fallback reliability and script quality

- engine.sh: resolve stale model names in fallback loop; use -latest Gemini aliases
- dev-lead-fix-reviews.sh: remove unused code and fix PR_URL export

* fix(dev-lead): resolve Gemini model names and shell lint warnings

* security(dev-lead): use random heredoc delimiter to prevent injection

Also updates Gemini models to 3.1 family (pro/flash) for May 2026 compatibility.

* fix(dev-lead): update Gemini models to 2.5 stable family

* fix(dev-lead): use auto model selection for Gemini

* security(dev-lead): harden env var parsing and use high-quota Gemini fallback

* fix(dev-lead): use auto model for Gemini

* fix(dev-lead): detect engine-committed changes and remove commit instructions from prompts

commit_and_push only checked git diff (uncommitted changes), so when the engine
followed prompt instructions to run `git commit` via Bash, the working tree was
clean and commit_and_push concluded no-changes — dropping the engine's commits
when the ephemeral runner exited.

Fix 1 (commit_and_push): also detect unpushed commits via `git log @{u}..HEAD`.
If the engine already committed, skip the add+commit step and go straight to push.

Fix 2 (prompts): remove "Commit the changes with..." steps from human.md,
human-pr.md, fix-bot-comment.md, and fix-reviews.md. Replace with an explicit
"Do not commit or push" constraint so the engine leaves git operations to the
script in all cases.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): address PR review findings — untracked fil…
don-petry added a commit that referenced this pull request Aug 8, 2026
… trigger for the Class-2 timer changes, before narrowing them (#1420)

* add: workflow to fix stuck PRs using bot account token

Allows running the cleanup script via workflow_dispatch with access to
GH_PAT (bot account token) from repo secrets.

* fix: pass GH_PAT to checkout action so workflow uses bot account

* debug: simplify account check, add authentication debug output

* docs: add comprehensive petry-review-bot setup instructions

* docs: add GitHub App setup guide (recommended approach)

GitHub App is the secure, recommended way to automate PR reviews:
- Fine-grained permissions
- JWT tokens that auto-expire
- No human account needed
- Better audit trail
- GitHub's recommended approach

Replaces the bot user account approach with a more secure alternative.

* docs: add comprehensive GitHub App setup documentation

- SETUP.md: Quick reference guide with configuration and troubleshooting
- IMPLEMENTATION.md: Technical deep dive on architecture and design decisions
- DOCUMENTATION.md: Index of all documentation files
- Updated README.md with status and quick links
- Updated GITHUB_APP_SETUP.md with implementation notes and actual app ID
- Workflows use GitHub App token generation instead of static PATs

Covers the transition from bot user account to GitHub App authentication for improved security and maintainability.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: silence auth check in fix-stuck-prs when using GitHub App token

GitHub App tokens don't have user scope, so 'gh api user' returns 403.
This is fine - the script still works for PR operations. Suppress the error
so the script completes successfully with app-token fallback label.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: use explicit author instead of @me in stuck PR search

GitHub App tokens don't have user identity, so @me search doesn't work.
Use explicit 'don-petry' author instead to find PRs to fix.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: avoid subshell in while loop to preserve variable state

Using pipe in while loop created subshell where PROBLEM_PRS and FIXED_PRS
counters were incremented but changes didn't persist to parent shell.
Fixed by using process substitution for input instead of pipe.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* docs: add comprehensive status report for GitHub App setup

- Overview of completed work
- GitHub App authentication details
- 24 stuck PRs successfully fixed with approval reviews
- Architectural decisions and rationale
- Configuration guide and usage examples
- Known limitations and troubleshooting

System is now fully operational with GitHub App token authentication,
comprehensive documentation, and all infrastructure in place.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* docs: document stuck PR cleanup status and script fixes

Added documentation for:
- GitHub App token compatibility fixes in fix-stuck-prs.sh
  * Changed @me to explicit author (app tokens have no identity)
  * Fixed subshell variable scope (process substitution vs pipe)
  * Silenced expected 403 auth check error
- Current status: 24 PRs have approvals but remain OPEN
  * Auto-merge failed due to missing GitHub App permission
  * Approvals satisfy branch protection requirement
  * Next: expand permissions or manually merge

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: update list-prs.sh to search all repos by owner instead of @me

GitHub App tokens don't have user identity, so @me/@review-requested searches
fail. Changed to enumerate all repos in don-petry and petry-projects, then
search for open PRs within each repo. This covers the full scope:
- All open PRs in personal don-petry repos
- All open PRs in petry-projects org repos

Resolves 6 consecutive workflow failures due to zero PRs being enumerated.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* docs: add investigation report for PR review agent workflow failures

* fix: move env section before steps in workflow to fix YAML structure

GitHub Actions requires env section to come before steps in job definition.
Moving env definition up and setting GH_TOKEN only in steps that need it.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* script: add backfill-approvals to retroactively apply real approvals

Converts PRs with agent approval comments (but no real GitHub approvals)
to have actual APPROVED reviews. Needed for PRs reviewed before the
gh pr review --approve fix was applied.

Usage:
  scripts/backfill-approvals.sh          # dry-run (preview changes)
  scripts/backfill-approvals.sh false    # apply real approvals

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* feat: add backfill-approvals workflow to run as GitHub App bot

Runs backfill-approvals.sh via GitHub Actions so the approvals are posted
by the bot identity, not the PR author — GitHub rejects self-approvals.

Also fixes subshell counter bug in the script (piped while loop lost variable
state; switched to process substitution).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: convert inner while loop to process substitution to preserve counters

Both the outer repo loop and inner PR loop were piped subshells, causing
all counter increments (approved/skipped/failed) to be lost. The summary
always showed 0. Fixed by using < <(...) process substitution for both loops.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* car-hunt: add VIN-deep-dive flow, printable checklists, location-tier ranking

- Step 5.5 pre-test-drive flow: NHTSA VIN decode + per-VIN unrepaired-recall
  automation via Chrome MCP (Honda LWC owner portal documented as not
  automatable; NHTSA web form works).
- §F printable test-drive checklist generator (reportlab/Platypus PDF) with
  cold-start, model-specific red flags, negotiation table, walk-away rules.
- §G fraud / paperwork verification checklist (vehicle-agnostic, reusable):
  walk-away triggers, VIN three-location match, curbstoning, odometer fraud,
  bill of sale, title transfer, payment protection, stolen-vehicle, title-
  jumping, after-purchase steps.
- Step 5.25 head-to-head comparison template with information-asymmetry rule.
- Step 3 Location Quality Tier scoring (Birmingham AL metro table A/B/C/D)
  with adj-CPM modifier; sheet schema gains Adj.CPM + Tier columns.
- Hard-disqualification rules added: non-running engine keywords, mileage
  inconsistency, mandatory description scrape.
- Drive MCP overwrite limitation documented; sheet ID now read from memory
  rather than hardcoded.
- FB Marketplace operational facts: Birmingham AL city ID 107739635926718,
  /search? vs /vehicles? query handling, React-controlled inline composer,
  send-button selector, seller-name extraction regex.
- scheduled-tasks/used-car-search-{morning,afternoon}: tier A/B priority
  flag, sheet ID read from memory.
- Reference PDF generator scripts checked in for reuse.

* fix: re-approve PRs where approval predates last commit (stale approval)

Instead of skipping PRs that already have any APPROVED review, skip only
those where reviewDecision is not REVIEW_REQUIRED. This handles PRs where
a bot approval was posted before a new commit was pushed — GitHub's ruleset
engine treats those approvals as stale even with dismiss_stale_reviews=false.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: robustly extract JSON verdict from mixed claude --print output (#17)

Claude's --print mode can prefix the JSON verdict with conversational
preamble text, causing jq to fail with parse errors. Add
extract_verdict_json to engine.sh and wire all three cascade action
call sites in review-one-pr.sh to use it.

Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: extract_verdict_json — check dest file first (agent Bash-write style)

The cascade-action agent writes its verdict JSON to \$OUTPUT_FILE via a
Bash tool call, then prints a text summary to stdout. The previous fix
only scanned stdout (the .raw file), which contained no JSON. The agent-
written file at \$dest (= \$OUTPUT_FILE) was already correct — just not
checked. Now check \$dest first before falling back to stdout scan.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Enforce MAX_REVIEW_CYCLES cap before running the cascade (#18)

* fix: enforce MAX_REVIEW_CYCLES cap with human-escalation marker

Previously MAX_REVIEW_CYCLES only gated AI delegation in
post-pr-review.sh — the cascade itself ran on every cron tick
regardless of how many cycles had accumulated. Real-world result:
a PR could rack up 9+ review cycles (we observed exactly that on
ContentTwin#100) before any cap took effect.

Add a pre-cascade check in review-one-pr.sh: when the count of
existing `<!-- pr-review-agent v1 sha=... -->` markers is at or
above MAX_REVIEW_CYCLES (default 3), post one escalation comment
marked `<!-- pr-review-agent escalation -->`, label
needs-human-review, request don-petry, and exit 100 (skip
sentinel — doesn't burn the MAX_PRS budget). The escalation
marker doubles as the no-spam guard: subsequent runs detect it
and exit 100 immediately.

Also reuse a single `gh pr view` for both the cycle count and the
escalation-marker check.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* review feedback: surface escalation-comment failures, drop dead unset

- Drop `2>/dev/null || true` on `gh pr comment` so a failed escalation
  post is visible in the workflow log instead of silently leaving the
  PR with no marker (which would re-trigger the cap path next tick).
- Reword the cycle-count comment from "AI delegation loops" to "review
  loops" — the cap also catches cascade-only loops where every cycle
  approves and a new commit lands before merge.
- Remove the redundant `unset PR_BODIES` — never exported, no leakage.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>

* Reliability hardening: session circuit breaker, timeouts, retry, dedup (#20)

* feat: session circuit breaker, per-tier timeouts, retry, and triage hard-fail

Reliability hardening for the PR review agent.

1. Session circuit breaker (.github/workflows/pr-review.yml): on any
   non-zero, non-100 exit from review-one-pr.sh (general failure or rate
   limit on the fallback engine), break the per-PR loop, log a clear
   error annotation naming the failing PR and reason, and exit the step
   with code 1 so the run shows red. Prevents one systemic problem from
   silently burning the entire candidate pool.

2. Per-tier timeouts (scripts/engine.sh): triage/deep/audit/action/duck
   each get their own bounded timeout (180/600/600/300/300s defaults,
   env-overridable). Previously only the duck had a timeout — a hung
   tier could burn the whole 60min job budget.

3. Retry-with-backoff on transient errors (scripts/engine.sh): triage
   retries once on 124/137/143 (timeout / signal kill) since its caller
   captures stdout via $(...) so retries are safe. Deliberately NOT
   applied to run_agentic/run_duck where stdout is redirected to a file
   — a retry there would corrupt the partial first-attempt output.

4. Triage non-JSON now hard-fails (scripts/review-one-pr.sh): replaces
   the silent fallback that synthesized a fake "escalate=MEDIUM" verdict
   and proceeded to deep review. With the new circuit breaker, loud
   failure is the right call — masking a broken triage was burning
   tokens on every PR while the workflow looked healthy.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix: stop stacking duplicate agent reviews on the same PR

Two bugs were causing the agent to leave multiple comments on the same PR.
Together they produced 10 stacked APPROVED reviews on petry-projects/ContentTwin#100.

Bug A — idempotency check is order-dependent (review-one-pr.sh):
The previous marker-discovery code did:
  ((.reviews // []) + (.comments // [])) | .[].body | grep marker | tail -1
This relies on the array concatenation order, not chronological order. When
old agent comments existed alongside newer agent reviews, tail -1 picked
the comment-array marker (older) over the review-array marker (newer),
causing the script to think the head SHA hadn't been reviewed and re-run.
Replaced with a single jq pipeline that tags each item with submittedAt /
createdAt, sorts by timestamp, and takes the actual most-recent marker.

Bug B — no cleanup of prior agent items (post-pr-review.sh):
After successfully posting a new review/comment, prior agent items were
left in place, accumulating forever. Added mark_prior_agent_items_obsolete
which, after a successful post:
  - dismisses prior APPROVED/COMMENTED/CHANGES_REQUESTED agent reviews via
    the GitHub dismissal API (UI shows them struck-through as Dismissed)
  - edits prior agent comments to wrap their body in a collapsed <details>
    block with a "Superseded by re-review at <SHA>" summary, plus a
    `<!-- pr-review-agent superseded -->` sentinel for idempotency

All cleanup API calls are best-effort — failures don't break the workflow.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(cleanup): warn on API failures, preserve global newest, file-stage JSON

Three fixes to mark_prior_agent_items_obsolete from the review of PR #20:

1. ::warning:: annotations on every cleanup API failure (review/comment
   list-fetch, individual review dismissal, individual comment fetch+edit).
   Previously these were silenced with `|| true`, so a permissions change
   on the dismissal endpoint would let duplicates stack indefinitely with
   no signal in the Actions UI. Cleanup is still non-fatal — the new post
   has already landed — but failures are now visible.

2. Preserve the globally-latest agent item across BOTH categories, not the
   newest of each category separately. The earlier code split reviews and
   comments and applied `[:-1]` to each, which left a stale fix-request
   comment in place when the new post was a review (or vice versa). The
   one-off cleanup of ContentTwin#100 hit exactly this case: 12 stacked
   reviews collapsed to 1, but a stale comment from 2026-04-25 (SHA
   cd9132d6) was preserved as "newest comment" even though the latest
   review at SHA 3af8c8ee was newer overall. Now: compute the max
   timestamp across both feeds, exclude items at that timestamp.

3. Stage API responses to disk (`mktemp` + `jq <file>`) instead of routing
   through `--argjson "$var"`. The old approach broke on rare unescaped
   control chars in user-authored comment bodies (jq refused to parse the
   resulting shell-vared JSON). File-based input sidesteps the shell
   pipeline entirely.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>

* Remove car-hunt skill and scheduled tasks (moved to don-petry/don-petry) (#21)

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: apply Copilot fallback hardening and reuse GH_PAT for Copilot auth

- Use secrets.GH_PAT (existing personal account token with Copilot
  subscription) instead of a separate COPILOT_GITHUB_TOKEN secret
- Pin actions/create-github-app-token to SHA (v3.1.1) for supply chain safety
- Add post-install verification for gh-copilot extension with warning on failure
- Guard Copilot fallback path: skip PR gracefully if extension is not installed

Addresses issue #24 workflow fixes (1-3) that could not land via PR #25
due to GitHub App lacking workflows permission.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: switch Copilot engine to gh built-in and fix app-id deprecation

- Remove gh extension install (github/gh-copilot conflicts with built-in alias)
- Replace with gh copilot --version check using COPILOT_GITHUB_TOKEN (GH_PAT)
- Update all engine.sh copilot invocations from bare `copilot` binary to
  `gh copilot suggest --target shell` with GH_TOKEN overridden to GH_PAT
  so the user token (with Copilot subscription) is used, not the App token
- Fix actions/create-github-app-token: app-id → client-id (same secret value,
  just the renamed input in v3.x)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: harden Copilot fallback path and remove stale worktrees (#25)

- gh-copilot install: add --force flag and post-install verification
  warning so silent failures due to built-in alias conflict are visible
  in workflow logs instead of being silently swallowed by `|| true`
- fallback pre-flight check: verify `gh extension list | grep copilot`
  before switching to Copilot engine; if unavailable, skip the PR and
  continue the batch rather than session-aborting and dropping all
  remaining candidates (fixes the 27-PR drop on run #503)
- pin actions/create-github-app-token to SHA for v3.1.1 (Node.js 24
  compatible) to prevent hard break on 2026-06-02 when GitHub forces
  Node.js 24 as default runtime
- remove 6 stale Claude Code worktrees from git tracking; add
  .gitignore entry to prevent future worktrees from being committed
  (eliminates exit-128 warning on every actions/checkout post-job sweep)

Closes #24

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: petry-projects-pr-review-agent[bot] <petry-projects-pr-review-agent[bot]@users.noreply.github.com>

* ci: add pre-flight dedup check to prevent duplicate claude-issue PRs (#26)

Before invoking Claude on a labeled issue, a new shell step queries
for any open PR with a branch matching claude/issue-NNN-* (or a body
containing "Closes #NNN"). If one is found it posts a comment on the
issue pointing to the existing PR and skips the Claude step entirely.
A secondary prompt instruction tells Claude to check first and push to
the existing branch rather than opening a new PR.

Motivated by issue petry-projects/google-app-scripts#171, where the
claude label was re-applied three times and each run created a fresh
PR unaware of the prior attempts.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor: migrate to machine user PAT auth (closes #27) (#28)

* refactor: migrate from GitHub App to machine user PAT auth

Closes #27

GitHub Apps cannot be listed in CODEOWNERS, blocking PRs in repos with
require_code_owner_review: true. Switch all workflows to use a machine
user account's fine-grained PAT (DON_PETRY_BOT_GH_PAT secret), which can
join an org team listed in CODEOWNERS.

Workflows: removed actions/create-github-app-token steps in pr-review,
fix-stuck-prs, backfill-approvals, and daily-pr-review-health. All now
use secrets.DON_PETRY_BOT_GH_PAT directly.

Docs: renamed GITHUB_APP_SETUP.md to MACHINE_USER_SETUP.md with full
rewrite covering account creation, CODEOWNERS config, PAT generation,
and rotation. Updated auth sections in IMPLEMENTATION.md, SETUP.md,
STATUS.md, DOCUMENTATION.md, README.md.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* refactor: switch to org-scoped PAT secret DON_PETRY_BOT_PETRY_PROJECT_PAT

Replace DON_PETRY_BOT_GH_PAT with DON_PETRY_BOT_PETRY_PROJECT_PAT — the
new fine-grained PAT scoped to the petry-projects org (resource owner =
petry-projects). The previous PAT was scoped to the donpetry-bot personal
namespace and had no repository access.

The old DON_PETRY_BOT_GH_PAT secret is retained for any future use against
don-petry's personal repos.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* chore: ignore .claude/scheduled_tasks.lock state file

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>

* refactor: merge approval repair scripts into single automation

- Consolidate backfill-approvals.sh and fix-stuck-prs.sh into repair-pr-approvals.sh
- Iterate all repos in both orgs instead of assuming author
- Verify no existing APPROVED review before posting
- Copy original agent comment as review body
- Enable auto-merge when posting approval if needed
- Single workflow with 30-min timeout

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: add @mention trigger for on-demand PR reviews (#30)

Adds repository_dispatch support so commenting @petry-review-bot on any
PR fires an immediate review without waiting for the hourly schedule.

- pr-review.yml: new `repository_dispatch` trigger (type: pr-review-mention),
  per-PR concurrency group for mention runs, FORCE_REVIEW and DRY_RUN
  env vars that handle both workflow_dispatch and repository_dispatch paths
- review-one-pr.sh: FORCE_REVIEW=true bypasses idempotency so a mention
  always runs a fresh cascade even if the head SHA hasn't changed
- templates/mention-listener.yml: deploy to petry-projects/.github;
  listens for @petry-review-bot, validates commenter trust, posts ack,
  sends repository_dispatch (requires Contents:write, not Actions:write)

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: harden rebase, review dismissal, and health log diagnostics (#40)

Addresses 4 code-actionable recommendations from health check report #33:

- [CRITICAL] Make rebase/branch-update non-fatal — 403/504 during rebase
  emits ::warning:: instead of aborting the batch session (#34)
- [MEDIUM] Add retry with exponential backoff (3 attempts) for transient
  5xx on update-branch API; break immediately on 4xx (#37)
- [MEDIUM] Guard review dismissal with state re-check before calling
  dismiss API, preventing 422s from race conditions (#38)
- [LOW] Surface missing log warnings in health check script (#39)
- Fix: skip auto-merge when branch is still BEHIND after failed rebase

Closes #34, closes #37, closes #38, closes #39

* refactor: migrate to org-wide .github-private convention (#41)

* refactor: parametrize hardcoded identity values for org migration

Replace all hardcoded references to don-petry, petry-review-bot, and
don-petry/pr-review-agent with environment variables that default to
the current values. This allows the agent to be configured for different
orgs/users via repo variables.

Changes:
- Scripts use $REVIEWER_USER, $TARGET_ORG, $BOT_USER, $AGENT_REPO
- Prompts no longer reference specific GitHub usernames
- Workflows use org-level GH_PAT_WORKFLOWS secret instead of
  repo-level DON_PETRY_BOT_PETRY_PROJECT_PAT
- Health check uses context.repo.owner/repo for issue creation
- Variables are set in workflow env block with defaults

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add Copilot custom agent profiles for org-wide use

Create /agents/ directory with three agent profiles:
- pr-reviewer: Multi-tier cascading PR review
- feature-ideator: Feature idea generation and prioritization
- compliance-auditor: Org standards compliance checking

These are Copilot custom agent profiles that become available
org-wide in the .github-private repo convention.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Squashed 'frameworks/bmad-method/' content from commit e36f219c

git-subtree-dir: frameworks/bmad-method
git-subtree-split: e36f219c81b6010d4aae423ba12f49edb5b6e31a

* Squashed 'frameworks/spec-kit/' content from commit 11f49ebf

git-subtree-dir: frameworks/spec-kit
git-subtree-split: 11f49ebfb2f6af55345cb4bd9a7906acd211e56f

* Squashed 'frameworks/gsd/' content from commit 304c1a13

git-subtree-dir: frameworks/gsd
git-subtree-split: 304c1a1302564c45af252bbba4bcc5350e7dac3a

* docs: update README for .github-private org infrastructure role

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* ci: add dependabot-automerge.yml workflow (#73)

Adds the required dependabot-automerge.yml workflow from the org
standard template (petry-projects/.github/standards/workflows/).
This is a thin caller stub that delegates to the org-level reusable
workflow.

Closes #48

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Don Petry <don-petry@users.noreply.github.com>

* fix(pr-review): unblock queue starvation from self-authored PRs (#96) (#97)

* fix(pr-review): unblock queue starvation from self-authored PRs (#96)

A self-authored PR sorted first in the candidate list and triggered
"Can not approve your own pull request" — which the session-fatal
abort treated like an engine error, skipping all 28 remaining PRs
on every run.

- list-prs.sh: filter out PRs authored by REVIEWER_USER at enumeration
- post-pr-review.sh: catch the GraphQL self-approval error and exit 100
  (no-op) instead of 1, so a stray self-PR can no longer abort the batch
- engine.sh: gh copilot renamed --target to --agent; fix the rubber-duck
  invocations so tier-2 cross-engine review works again
- review-one-pr.sh: stop appending a duplicate "0" to REVIEW_CYCLE when
  grep -c finds no markers under set -o pipefail (was breaking the
  cycle-cap integer comparison)

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

* fix(pr-review): authenticate as bot, harden enumeration, address review

- Workflow now runs as AGENT_USER (default don-petry-bot), distinct from
  REVIEWER_USER (the human, don-petry). The self-approval rejection that
  caused #96 came from the agent and the human sharing one identity.
- list-prs.sh filters self-authored PRs against AGENT_USER, and validates
  AGENT_USER against the GitHub username charset before interpolating into
  the jq filter (Copilot review feedback).
- review-one-pr.sh uses printf '%s\n' instead of echo for PR_BODIES, since
  PR body content is user-authored and could begin with -n/-e or contain
  backslash escapes (Copilot review feedback).
- AGENT.md guideline updated to reflect that self-authored PRs are
  intentionally excluded (CodeRabbit review feedback).

Operator note: rotate the GH_PAT_WORKFLOWS secret to a token owned by
don-petry-bot (with read:org added) for the bot-account behavior to take
effect at runtime.

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

* refactor(pr-review): collapse to single BOT_USER, route escalations via CODEOWNERS

Drop AGENT_USER and REVIEWER_USER. The workflow now has one identity:
BOT_USER (default don-petry-bot), which both owns the repos to scan and
gets filtered out as the self-approval blocker. Human escalation no
longer hard-codes a single reviewer; instead, scripts/request-codeowners-
review.sh parses CODEOWNERS in the PR's repo and requests review from
every @user / @org/team mention.

- list-prs.sh: BOT_USER for both gh-repo-list and self-author filter
- review-one-pr.sh, post-pr-review.sh: replace --user "$REVIEWER_USER"
  with the CODEOWNERS helper
- repair-pr-approvals.sh: same gh-repo-list switch
- pr-review.yml: drop AGENT_USER/REVIEWER_USER, set BOT_USER default
  to don-petry-bot
- AGENT.md: updated guideline to describe single-identity model

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

* refactor(pr-review): extract batch loop, hoist env, drop redundant step

Workflow YAML drops from 231 to 111 lines by moving the per-PR review
loop into scripts/review-batch.sh. The script can be syntax-checked and
exercised locally; the workflow now just wires env vars and dispatches.

- Hoist GH_TOKEN, MAX_PRS, CANDIDATE_LIMIT to job-level env (were
  repeated on individual steps).
- Inline `gh auth status` into the install step; remove the standalone
  Verify auth step (its only output was a one-line auth dump).
- Drop the `if: steps.list.outputs.count != '0'` guard and the step's
  `id`/output: review-batch.sh handles empty input as a no-op.
- Collapse the duplicated summary-string branch in the review loop into
  a single template with a conditional fragment.

No behavior change.

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

* perf(pr-review): cache claude-code CLI install across runs

`npm install -g @anthropic-ai/claude-code` ran on every workflow start
(~30s). Switch to a per-user npm prefix (~/.npm-global) and cache that
directory via actions/cache, keyed on CLAUDE_CODE_VERSION + runner OS.

A `command -v claude` guard makes the install a no-op on cache hit, so
the only cost on subsequent runs is the cache restore.

CLAUDE_CODE_VERSION defaults to 'latest' (cache persists until manually
flushed); set the repo variable to pin a specific version for fully
reproducible caching.

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

---------

Co-authored-by: Claude <noreply@anthropic.com>

* chore: rename GH_PAT_WORKFLOWS secret to DON_PETRY_BOT_GH_PAT (#99)

The previous name was generic and didn't tell you which account the PAT
belonged to. The new name makes the binding explicit: this secret is the
PAT owned by don-petry-bot, used as BOT_USER throughout the PR-review
workflows.

Operator follow-up before this can be merged:
- Add a new repo secret DON_PETRY_BOT_GH_PAT containing the bot's PAT
  (with repo, workflow, and read:org scopes).
- After merge, the old GH_PAT_WORKFLOWS secret can be deleted.

Affected workflows:
- pr-review.yml (1 use)
- claude.yml (4 uses, with || github.token fallback)
- daily-pr-review-health.yml (1 use)
- repair-pr-approvals.yml (1 use)

scripts/pr_review_health.sh's error message is updated to point at the
new name as well.

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

Co-authored-by: Claude <noreply@anthropic.com>

* chore: remove frameworks directory (#101)

Co-authored-by: Gemini CLI <gemini-cli@example.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore all erroneously deleted files (items 4-34) (#104)

* revert: restore .github/workflows/daily-pr-review-health.yml

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore .github/workflows/repair-pr-approvals.yml

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore agents/pr-reviewer.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/cascade-action.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/deep-review.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/rubber-duck.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/security-audit.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/shared.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/single-review.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/synthesize-duck.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/synthesize.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/triage.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/engine.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/list-prs.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/post-pr-review.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/pr_review_health.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/repair-pr-approvals.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/request-codeowners-review.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/review-batch.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/review-one-pr.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore templates/mention-listener.yml

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore AGENT.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore BOT_SETUP.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore DOCUMENTATION.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore IMPLEMENTATION.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore MACHINE_USER_SETUP.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore PR_REVIEW_AGENT_FAILURE_REPORT.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore PR_REVIEW_FAILURE_INVESTIGATION.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore SETUP.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore STATUS.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore README.md to pre-change state

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore .github/workflows/daily-pr-review-health.yml

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat: add Gemini Pro support and optimize PR review fallback chain (#102)

* Merge main and resolve conflicts

* Enhance pr-review workflow: add Gemini support and refactor fallback logic

* Address Copilot review comments: tighten regex, fix fallback summary, and align docs

* docs: move agent documentation to docs/ folder and rename files

* docs: align secret names and fix casing in index

* docs: organize pr-review-agent documentation under dedicated folder

* Address final PR comments: tighten regex, add preflight checks, fix bot names, and cleanup docs

---------

Co-authored-by: Gemini CLI <gemini-cli@example.com>

* fix(pr-review): gate rate-limit detection on non-zero exit code

Broad patterns like `plan.*limit` and `claude.*usage` could match content
in a successful triage summary, triggering a false-positive engine fallback.
Guard the check with TRIAGE_RC != 0 so it only fires when the provider
command actually failed.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(claude): sync inlined workflow with org standard (#109)

* fix(claude): sync inlined workflow with org standard

Brings the inlined claude.yml up to parity with
petry-projects/.github/.github/workflows/claude-code-reusable.yml.

Changes:
1. Bot allow list for pull_request_review_comment
   Add coderabbitai[bot], Copilot, copilot-pull-request-reviewer[bot],
   and gemini-code-assist[bot] alongside the existing OWNER/MEMBER/
   COLLABORATOR check. These bots have author_association 'NONE' so
   their review comments were always skipped.

2. check_run trigger + claude-ci-fix job
   Port the CI failure auto-fix feature from the reusable. When a check
   fails on a PR, Claude diagnoses and fixes it automatically.
   Adapted to use DON_PETRY_BOT_GH_PAT (this repo's secret name).

3. claude-code-action SHA bump: v1.0.89 → v1.0.119
   Update both the claude and claude-issue jobs.

4. paths-ignore on pull_request trigger
   Prevents the workflow from firing on PRs that only change
   claude.yml itself, avoiding the Anthropic OIDC validation
   failure (workflow file must match default branch at token exchange).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(claude): guard bot allow list against fork PRs

Same fix as petry-projects/.github PR #238 — add same-repo guard
for bot-triggered pull_request_review_comment runs.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(claude): address ci-fix self-loop and fork PR security

- Replace Claude Code name prefix check with explicit job name list to
  correctly prevent self-loops (check_run names for inlined workflows
  are bare job names, not workflow-prefixed)
- Add fork PR trust gate in Resolve PR number step: verify head repo
  matches target repo before running Claude with privileged credentials

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* ci: replace inlined claude.yml with standard thin-caller stub

The inlined version embedded all three jobs (claude, claude-ci-fix,
claude-issue) directly and threaded DON_PETRY_BOT_GH_PAT through
checkout tokens and github_token, causing the bot's PAT to author
PRs and comments instead of github-actions[bot].

Replace with the standard thin caller that delegates to the org-level
reusable workflow (claude-code-reusable.yml@v1) via secrets: inherit,
which is the correct pattern per:
petry-projects/.github/standards/workflows/claude.yml

* fix: replace gh copilot suggest with GitHub Models REST API (#151)

* fix: replace gh copilot suggest with GitHub Models REST API (#147)

The `gh copilot suggest -p "$(cat <file>)"` invocation failed with
"Invalid command format" because:
1. The `-p` flag is not valid syntax in modern `gh copilot` built-in versions.
2. `gh copilot suggest` is a shell-command suggestion tool; it does not
   accept large PR prompts or return structured JSON responses.
3. Passing a multi-thousand-line prompt via `$(cat ...)` can hit ARG_MAX.

The non-zero exit was then misclassified by the rate-limit detector, which
aborted the entire session and skipped all remaining PRs in the queue.

Fix: replace all three copilot invocations (run_triage, run_agentic,
run_duck) with a new `copilot_chat` helper that calls the GitHub Models
REST API directly via curl. The API is OpenAI-compatible, versioned via
`X-GitHub-Api-Version`, stable against gh CLI version changes, and accepts
arbitrary prompt sizes using python3 for safe JSON encoding.

Additional changes:
- Add `COPILOT_API_MODEL` var (default `openai/o4-mini`) overrideable via env.
- run_agentic/run_duck copilot cases now also write output to $OUTPUT_FILE
  so callers that check that path directly (deep review, audit) find the JSON.
- Rate-limit responses (HTTP 429) are echoed to stdout so the existing
  `is_rate_limited()` detector fires correctly for engine fallback.
- Add pre-flight smoke test in review-batch.sh: tests GitHub Models API
  connectivity with a one-liner prompt before processing any PRs, so auth
  or model errors surface immediately as a clear setup failure.
- Add tests/test_copilot_chat.sh: unit tests for the JSON payload builder
  with edge-case prompts (quotes, newlines, # headings, large diffs, Unicode).

Closes #147

Co-authored-by: Don Petry <don-petry@users.noreply.github.com>

* fix: scope COPILOT_API_MODEL export to copilot engine only

Move the `export COPILOT_API_MODEL` from the common exports block into the
`copilot)` case where the variable is set, so it is not exported as an
empty/unset variable when the engine is `claude` or `gemini`. The
`copilot_chat` fallback `${COPILOT_API_MODEL:-openai/o4-mini}` still works
correctly when `DUCK_ENGINE=copilot` under a non-copilot primary engine.

Co-authored-by: Don Petry <don-petry@users.noreply.github.com>

* fix: address review comments — temp file for payload, fail-fast source, streaming output

- copilot_chat: write JSON body to mktemp file, pass to curl as @file to
  avoid ARG_MAX for large PR diffs (was --data-binary "$body")
- copilot_chat: add :? guard on COPILOT_GITHUB_TOKEN for a clear error
  instead of generic "unbound variable" under set -u
- run_agentic / run_duck copilot paths: stream directly to stdout (and
  tee to OUTPUT_FILE when set) rather than buffering the full response
  into a shell variable, which forced large outputs into memory and
  stripped trailing newlines
- review-batch.sh pre-flight: fail fast if source engine.sh fails
  (was silently ignored with || true)
- review-batch.sh pre-flight: build smoke-test JSON payload via python3
  + temp file instead of shell string interpolation to avoid JSON
  injection if COPILOT_API_MODEL contains special characters
- Clarify that openai/o4-mini is the correct April-2025 o4-generation
  model name, not a typo for o1-mini or gpt-4o-mini

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Don Petry <don-petry@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* feat: prioritise .github/.github-private PRs, oldest-first within tier (#155)

* feat: prioritise .github/.github-private PRs, oldest-first within tier

- list-prs.sh: add createdAt to JSON fetch; emit priority|createdAt|url
  lines (priority 0 for .github/.github-private, 1 for everything else)
- Replace final sort -u with a two-pass sort: deduplicate by URL, then
  sort by priority asc then createdAt asc; strip sort keys with cut
- tests/test_list_prs_sort.sh: 16 tests covering priority classification,
  oldest-first ordering, mixed scenarios and deduplication
- .github/workflows/test.yml: run both unit-test files on every PR / push

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* test: add dedup edge case for same URL with conflicting priorities

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: remove temperature from copilot_chat payload (o4-mini incompatible)

The GitHub Models API rejects temperature=0 for reasoning models like
o4-mini with HTTP 400:
  'Unsupported value: temperature does not support 0 with this model.
   Only the default (1) value is supported.'

This caused the rubber duck (DUCK_ENGINE=copilot / DUCK_MODEL=o4-mini)
to fail with HTTP 400 on every claude-engine run, silently degrading
every review to deep-only with no cross-engine sanity check.

Fix: remove temperature from the copilot_chat JSON payload entirely —
the API defaults to 1, which is the only supported value for o4-mini.

Also:
- Update tests/test_copilot_chat.sh: sync build_payload to match and
  flip Test 9 to assert temperature is ABSENT (not 0).
- Add .github/workflows/test.yml: run unit tests on every PR and push
  to main so regressions are caught before merge.

Reproducer: job/75690347409 — duck failure logged as:
  copilot_chat: HTTP 400 from GitHub Models API
  {"error":{"message":"Unsupported value: 'temperature' does not
   support 0 with this model..."}}

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: address PR review comments

- list-prs.sh: tighten priority regex to /[.]github(-private)?/pull/
  so path-boundary anchor prevents false positives (e.g. foo.github)
- list-prs.sh: replace printf pipeline with here-string to avoid ARG_MAX
  limits on large PR sets
- test.yml: add permissions: contents: read (least-privilege, CodeQL fix)
- test.yml: add timeout-minutes: 5 to prevent hung runs
- test_list_prs_sort.sh: sync sort_entries helper (here-string + || true)
  and classify regex to match updated list-prs.sh exactly

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: don-petry <don@petry.dev>

* fix: restore pr-review.yml content (accidentally emptied in revert)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: restore pr-review.yml (file was accidentally emptied)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore(deps): bump actions/cache from 4 to 5 (#165)

Bumps [actions/cache](https://github.com/actions/cache) from 4 to 5.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): bump petry-projects/.github/.github/workflows/claude-code-reusable.yml (#164)

Bumps [petry-projects/.github/.github/workflows/claude-code-reusable.yml](https://github.com/petry-projects/.github) from 1 to 2.
- [Commits](https://github.com/petry-projects/.github/compare/v1...v2)

---
updated-dependencies:
- dependency-name: petry-projects/.github/.github/workflows/claude-code-reusable.yml
  dependency-version: '2'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* fix(list-prs): search DELEGATION_ORGS and drop --checks success pre-filter

- Add iteration over all orgs in DELEGATION_ORGS (e.g. don-petry) so PRs
  in those orgs enter the candidate pool. Previously only BOT_USER and
  TARGET_ORG were searched.
- Remove --checks success from org repo searches. GitHub excludes PRs from
  repos with no CI configured when this flag is used, silently dropping all
  PRs from repos like .github, .github-private, google-app-scripts, and
  TalkTerm. review-one-pr.sh already enforces CI gating per-PR and treats
  empty statusCheckRollup as passing, so the pre-filter is redundant and
  harmful.
- Add --limit 200 to gh repo list calls to handle org growth beyond gh's
  default 30-repo cap.

* feat: add code-quality ruleset (compliance fix #60) (#86)

Creates the required `code-quality` repository ruleset enforcing required
status checks on the default branch, as mandated by the org standard:
standards/github-settings.md#code-quality--required-checks-ruleset-all-repositories

Required checks:
- SonarCloud (code quality analysis)
- CodeQL (SAST)
- agent-shield / AgentShield (agent security scan)
- dependency-audit / Detect ecosystems (dependency vulnerability scan)

Bypass actors:
- OrganizationAdmin (always) — emergency override
- dependabot-automerge-petry Integration (always) — Dependabot auto-merge

The ruleset was applied directly via GitHub API. This file documents the
configuration as code for auditability and future reapplication.

Closes #60

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Don Petry <don-petry@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* feat(prompts/dev-lead): add human prompt template

* feat(prompts/dev-lead): add human-pr prompt template

* feat(dev-lead): implement dev-lead agent Phases 0-6

* fix: per-PR isolation and single-review retry (closes #132)

- review-batch.sh: non-rate-limit per-PR failures (exit code 1) no
  longer abort the session. SESSION ABORTED EARLY is now reserved for
  the rate-limit-on-fallback-engine case (exit code 2) only. All other
  failures are counted and logged; remaining candidates continue.

- review-one-pr.sh: single-review step retries up to
  SINGLE_REVIEW_MAX_RETRIES (default 2) times with a
  SINGLE_REVIEW_RETRY_DELAY_SEC (default 15s) gap before giving up.
  On exhaustion, the PR is flagged needs-human-review and the script
  exits with code 1, which the updated batch treats as a non-fatal
  per-PR failure. Raw model output and stderr are logged on each failed
  attempt for post-mortem visibility.

Root cause of run #25707852006: claude-opus-4-7 returned a verbose
non-JSON response for PR #129; the old code treated that as fatal and
skipped 35 remaining candidates.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix: rate-limit detection and per-attempt stderr logs in single-review retry

Address inline review comments on PR #133:

- Rate-limit check: after each run_agentic call, inspect both stdout
  (VERDICT_JSON.raw) and stderr (SINGLE_LOG) with is_rate_limited before
  retrying. A rate-limit match exits immediately with code 2 so
  review-batch.sh can trigger engine fallback — consistent with triage and
  deep-review tiers. Previously a rate-limited single-review would burn all
  retries and exit 1 (per-PR failure), silently leaving the batch on the
  same rate-limited engine for all remaining PRs.

- Per-attempt log files: stderr is now written to
  single-review-attempt-N.log rather than a single overwritten file, so
  no earlier-attempt errors are lost. Each attempt logs its own stderr
  inline on failure; the fallback path cats all attempt logs for
  post-mortem visibility.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* feat(dev-lead): Phase 0 test infrastructure and Phase 1 intent stub

Phase 0: full test harness for the dev-lead agent —
  26 event fixtures (all valid JSON with _test_expected_intent),
  stub claude/gemini engines, mock gh binary, CI failure log sample,
  bats helpers (stub-engine, mock-gh, assert-env, prompt-vars),
  7 prompt templates with VARIABLES declarations, preflight script,
  prompt coverage integration test, and test-dev-lead.yml CI workflow.

Phase 1: dev-lead.yml trigger workflow (all 7 event types, dispatch + ci-relay
  jobs) and dev-lead-intent.sh stub (anti-loop guard live; all other events
  emit skip/not-implemented).  14/14 bats unit tests pass.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): add permissions blocks to test workflow jobs (CodeQL)

* fix(ci): auto-fix for lint / eslint [skip ci-relay]

* feat(dev-lead): Phases 2-6 — CI fix, review fix, issue, engine fallback

Phase 2: run_writer/run_writer_with_fallback in engine.sh, full intent
routing in dev-lead-intent.sh, dev-lead-fix-ci.sh handler, workflow wiring.

Phase 3: dev-lead-fix-reviews.sh handles fix-reviews, fix-bot-comment,
human, human-pr, rebase intents. Full review routing in intent classifier.

Phase 5: dev-lead-fix-issue.sh handles the issue intent with dedup guard
and branch/PR creation.

Phase 6: run_writer_with_fallback with claude→gemini→copilot fallback chain.

Tests: 77 unit tests across 8 new .bats files, all passing. Updated Phase 1
stub tests to reflect actual Phase 2+ routing behavior.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): address review comments — dispatch JSON, dead code, portable stub gh

* fix(dev-lead): pre-flight after intent, fork URL check, review-batch exit code, retry comment

* fix(dev-lead): install bats-core from GitHub to avoid root requirement

* fix(security): move event values to env vars to prevent script injection (SonarCloud)

* fix(security): move CLAUDE_CODE_VERSION to env block (SonarCloud script injection)

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* feat(dev-lead): Phase 1.5 — reusable workflow for cross-repo adoption + Phase 7 shadow period

* feat(dev-lead): add reusable workflow (Phase 1.5) and begin shadow period (Phase 7)

- Create .github/workflows/dev-lead-reusable.yml: workflow_call entry
  point for other repos. Checks out .github-private scripts/prompts into
  .dev-lead/, then runs the same intent-classify + handler pipeline as
  dev-lead.yml with PROMPTS_DIR=.dev-lead/prompts/dev-lead.
- Add PROMPTS_DIR env-var support to dev-lead-fix-ci.sh,
  dev-lead-fix-reviews.sh, and dev-lead-fix-issue.sh so the reusable
  workflow can point scripts at the sparse-checkout path without changing
  CWD. Defaults to prompts/dev-lead (backwards-compatible for dev-lead.yml).
- Annotate dev-lead.yml with Phase 7 shadow-period window (2026-05-15
  through ~2026-05-29): claude.yml and dev-lead.yml run in parallel until
  regressions clear, then claude.yml is removed.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* chore(dev-lead): update shadow-period tracking issue ref to #180

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): document PAT requirement and pin ref:main on private checkout

* fix(dev-lead): P1 review fixes — ci-relay in reusable, .dev-lead gitignore, export PROMPTS_DIR, trim header

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): rate-limit detection, separate status, and retry cron (#192) (#196)

* chore(dev-lead): decommission claude.yml, bump ACTION_TIMEOUT_SEC to 600s

- Delete .github/workflows/claude.yml — replaced by dev-lead.yml (shadow
  period complete, tracking issue #180).
- scripts/engine.sh: raise ACTION_TIMEOUT_SEC default 300→600s to reduce
  timeout failures on large-repo fix-ci runs (PR #80 had 3 timeouts).
- AGENTS.md: remove claude.yml immutability exemption; note dev-lead.yml
  as the active AI automation workflow and its edit-via-reusable pattern.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* refactor: replace Claude analysis with pure gh/jq telemetry in health check

* refactor: drop Node/Claude steps from daily-pr-review-health workflow

* rename: daily-pr-review-health → actions-fleet-monitor

* rename: daily-pr-review-health → actions-fleet-monitor

* fix(agents-md): clarify dev-lead.yml vs dev-lead-reusable.yml scope

dev-lead.yml in .github-private runs inline steps (not a caller stub).
Behavior changes for this repo go to dev-lead.yml directly; changes that
affect all org repos via the cross-repo reusable go to dev-lead-reusable.yml.

Addresses Copilot review on PR #194.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): rate-limit detection, separate status, and retry cron

Closes #192. Implements all phases from the revised plan.

**Phase 0 — fix run_writer stdout capture (prerequisite)**
- engine.sh: capture stdout via `tee` to a tempfile so `is_rate_limited`
  can inspect the output; old code read `/tmp/dev-lead-writer-stderr`
  which was never written (claude --print outputs to stdout, not stderr).
  Fallback engines were never tried in practice.
- engine.sh: add `parse_reset_time` to extract ISO timestamp from
  `resets H:MMpm (UTC)` in engine output and write it to
  `/tmp/dev-lead-rate-limit-reset` for callers to embed in markers.

**Phase 1 — separate status=rate-limited from status=failed**
- fix-ci.sh: detect engine exit 2 → post `status=rate-limited` (not
  `status=failed`); embed parsed reset time in marker body; exit 2.
- fix-ci.sh: fix `check_idempotency` to treat `status=rate-limited`
  as retriable — only block on terminal statuses (applied, failed,
  no-changes). Previously, rate-limited markers blocked all retries.
- fix-ci.sh: `count_recent_failures` already filtered to `status=failed`;
  add explicit comment confirming rate-limited markers are excluded.
- fix-ci.sh: add `has_rate_limited_marker` dedup check so the same SHA
  never accumulates more than one rate-limited comment.
- fix-reviews.sh: on engine exit 2 for all five intent types
  (fix-reviews, fix-bot-comment, human, human-pr, rebase), post a
  `status=rate-limited` marker with embedded reset time and exit 2.
- fix-reviews.sh: for `human` and `human-pr` intents, also post a
  user-visible acknowledgment comment so users know their request was
  received and will be retried.

**Phase 2 — scheduled retry cron for fix-ci**
- dev-lead-retry.sh: new script scans all open PRs across TARGET_ORG
  for `status=rate-limited` markers on current HEAD SHA; skips PRs
  whose reset time is still in the future; staggered dispatches (30s
  between repos) to prevent cascading org-level rate-limit hits.
- dev-lead-retry.yml: new workflow — scheduled every 2 hours (dual
  offsets to work around GitHub scheduler skew), plus workflow_dispatch.

**Phase 3 — retry for fix-reviews intents (same cron)**
- dev-lead-retry.sh already handles all five fix-reviews intent types
  (fix-reviews, fix-bot-comment, human, human-pr, rebase) via
  `dev-lead-reviews-retry` dispatch events.
- dev-lead-intent.sh: add `dev-lead-reviews-retry` dispatch type routing
  to the intent classifier — maps intent_type from payload to the correct
  existing fix-reviews step.
- dev-lead.yml: add `dev-lead-reviews-retry` to repository_dispatch types.
- dev-lead.yml: unify concurrency group for all dispatch types to
  `dev-lead-pr-{pr_number}` to avoid per-type slot fragmentation.

**Tests**
- test_engine_writer.bats: 8 new tests covering rate-limit stdout detection,
  exit-2 mapping, fallback exhaustion, reset time parsing.
- test_fix_ci.bats: 5 new tests covering rate-limited status, exhaustion
  exclusion, idempotency pass-through, and dedup.
- test_fix_reviews.bats: 5 new tests covering all intent types + human ack.
- test_intent_ci.bats: 4 new tests for dev-lead-reviews-retry dispatch routing.
- e2e/scenarios/07-rate-limit-retry.sh: new E2E scenario covering all parts.
- 3 new event fixtures for repository_dispatch_reviews_retry_* payloads.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): address review comments on #196

Addresses all 18 review threads from Copilot, Codex, and Gemini.

**P1 bugs fixed:**
- dev-lead-retry.sh: all log echo calls in scan_pr_for_rate_limits and
  dispatch_* functions now write to stderr; only the final count is on
  stdout. Previously, log lines polluted the command substitution,
  causing arithmetic expansion to fail and aborting the scan on the
  first retry candidate.
- dev-lead-retry.yml: permissions: contents was read-only; creating
  repository_dispatch events requires contents: write.

**Correctness fixes:**
- dev-lead-retry.sh: restrict automated retries to intents whose context
  can be reconstructed at runtime (fix-reviews, human-pr, rebase). human
  and fix-bot-comment require USER_INSTRUCTION/COMMENT_BODY from the
  original event which cannot be re-fetched; they are explicitly excluded.
- dev-lead-fix-reviews.sh: resolve HEAD_SHA from the PR API when not
  provided by the triggering event (issue_comment intents carry no SHA).
  Without this, rate-limited markers had no sha= field and were
  invisible to the retry scanner.
- dev-lead-fix-reviews.sh: write terminal status=applied marker after
  successful fix-reviews, human-pr, rebase runs. Prevents the retry
  cron from re-dispatching the same intent on every subsequent tick when
  the SHA hasn't changed.
- dev-lead-retry.sh: check for reviews terminal marker before dispatching
  a retry (mirrors the existing fix-ci terminal-marker check).
- dev-lead-retry.sh: look up current check-run details (details_url, id)
  at dispatch time via the commits check-runs API, so retried fix-ci runs
  have full failure logs and annotations rather than empty fields.
- dev-lead-fix-ci.sh: embed check= field in the rate-limited marker so
  the retry cron knows which check run to look up.
- dev-lead-fix-reviews.sh: human intent now posts a "please re-mention
  @dev-lead" ack instead of "I'll retry automatically" since it will NOT
  be retried automatically. human-pr keeps the auto-retry ack (correct).

**Pagination (Copilot + Gemini):**
- dev-lead-fix-ci.sh: add --paginate to check_idempotency, count_recent_
  failures, has_rate_limited_marker.
- dev-lead-fix-reviews.sh: add --paginate to has_reviews_rate_limited_marker.
- dev-lead-retry.sh: add --paginate to PR comments and PR list fetches.

**Other improvements:**
- dev-lead-retry.yml: simplify to single cron schedule (15 */2 * * *);
  remove BOT_USER env var (unused in the script).
- dev-lead-retry.sh: raise repo list limit 200 → 1000; add hard-error
  when list_repos_for_org returns empty (surfaces token permission issues
  rather than silently scanning 0 repos).
- dev-lead-intent.sh: use jq -nc (compact, null-input) for context JSON
  construction — safe against values with special characters.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(pr-review): handle Gemini trust-mode failure and properly route engine fallbacks (#217)

* fix(pr-review): handle Gemini trust-mode failure and properly route engine fallbacks

Closes #208

* fix(ci): install gemini CLI and set trust mode for dev-lead agents

---------

Co-authored-by: Gemini CLI <gemini-cli@example.com>

* fix: resolve YAML syntax error in dev-lead-reusable workflow

* fix: address automated feedback from PR 217 (#219)

This addresses missed feedback from CodeRabbit (removing syntax errors in YAML) and gemini-code-assist (improving string concatenation in validate-engines.sh), and adds assertion output checks to tests.

Co-authored-by: Gemini CLI <gemini-cli@example.com>

* chore: remove Phase 7 shadow period comment from dev-lead.yml

* feat: skip PRs with CHANGES_REQUESTED review (+ FORCE_REVIEW and stale-review guards)

* feat: skip PRs with CHANGES_REQUESTED review decision

Adds an early-exit check alongside the existing CI guards so the cascade
skips any PR where a human reviewer has already requested changes.
Reviewing those PRs before the author responds wastes tokens and creates
confusing parallel feedback threads.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix: honor FORCE_REVIEW and skip only stale-free CHANGES_REQUESTED reviews

Two issues addressed from Codex P2 feedback:

1. FORCE_REVIEW bypass: mention-triggered and force_review=true runs now
   bypass the CHANGES_REQUESTED skip, so authors can explicitly request a
   re-review after addressing feedback.

2. Stale review guard: only skip when a CHANGES_REQUESTED review targets
   the current head SHA. On repos without automatic stale-review dismissal,
   reviewDecision stays CHANGES_REQUESTED after new commits — the cascade
   now re-engages when the author has pushed new work.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): resolve context loss, missing checkout, and push logic (#223)

* feat: skip PRs with CHANGES_REQUESTED review decision

Adds an early-exit check alongside the existing CI guards so the cascade
skips any PR where a human reviewer has already requested changes.
Reviewing those PRs before the author responds wastes tokens and creates
confusing parallel feedback threads.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix: honor FORCE_REVIEW and skip only stale-free CHANGES_REQUESTED reviews

Two issues addressed from Codex P2 feedback:

1. FORCE_REVIEW bypass: mention-triggered and force_review=true runs now
   bypass the CHANGES_REQUESTED skip, so authors can explicitly request a
   re-review after addressing feedback.

2. Stale review guard: only skip when a CHANGES_REQUESTED review targets
   the current head SHA. On repos without automatic stale-review dismissal,
   reviewDecision stays CHANGES_REQUESTED after new commits — the cascade
   now re-engages when the author has pushed new work.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): resolve context loss, missing checkout, and push logic

- dev-lead-intent.sh: include actor and body in INTENT_CONTEXT for comments
- dev-lead.yml: parse and pass INTENT_ACTOR and INTENT_COMMENT_BODY to agent
- dev-lead-fix-reviews.sh: perform PR checkout, git push, and post summary comments

* fix(dev-lead): exit with 0 on no-changes in fix-reviews and human-pr

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* test: bypass CI check

* fix(pr-review): remove CI bypass hack

* fix(dev-lead): use heredoc for multiline environment variables (#224)

* fix(dev-lead): use heredoc for multiline environment variables

* fix(dev-lead): ensure all engines are installed and context is consistent

* fix(dev-lead): resolve stale model name in engine fallbacks

* fix(dev-lead): improve fallback reliability and script quality

- engine.sh: resolve stale model names in fallback loop; use -latest Gemini aliases
- dev-lead-fix-reviews.sh: remove unused code and fix PR_URL export

* fix(dev-lead): resolve Gemini model names and shell lint warnings

* security(dev-lead): use random heredoc delimiter to prevent inject…
don-petry added a commit that referenced this pull request Aug 8, 2026
… trigger for the Class-2 timer changes, before narrowing them (#1420)

* add: workflow to fix stuck PRs using bot account token

Allows running the cleanup script via workflow_dispatch with access to
GH_PAT (bot account token) from repo secrets.

* fix: pass GH_PAT to checkout action so workflow uses bot account

* debug: simplify account check, add authentication debug output

* docs: add comprehensive petry-review-bot setup instructions

* docs: add GitHub App setup guide (recommended approach)

GitHub App is the secure, recommended way to automate PR reviews:
- Fine-grained permissions
- JWT tokens that auto-expire
- No human account needed
- Better audit trail
- GitHub's recommended approach

Replaces the bot user account approach with a more secure alternative.

* docs: add comprehensive GitHub App setup documentation

- SETUP.md: Quick reference guide with configuration and troubleshooting
- IMPLEMENTATION.md: Technical deep dive on architecture and design decisions
- DOCUMENTATION.md: Index of all documentation files
- Updated README.md with status and quick links
- Updated GITHUB_APP_SETUP.md with implementation notes and actual app ID
- Workflows use GitHub App token generation instead of static PATs

Covers the transition from bot user account to GitHub App authentication for improved security and maintainability.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: silence auth check in fix-stuck-prs when using GitHub App token

GitHub App tokens don't have user scope, so 'gh api user' returns 403.
This is fine - the script still works for PR operations. Suppress the error
so the script completes successfully with app-token fallback label.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: use explicit author instead of @me in stuck PR search

GitHub App tokens don't have user identity, so @me search doesn't work.
Use explicit 'don-petry' author instead to find PRs to fix.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: avoid subshell in while loop to preserve variable state

Using pipe in while loop created subshell where PROBLEM_PRS and FIXED_PRS
counters were incremented but changes didn't persist to parent shell.
Fixed by using process substitution for input instead of pipe.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* docs: add comprehensive status report for GitHub App setup

- Overview of completed work
- GitHub App authentication details
- 24 stuck PRs successfully fixed with approval reviews
- Architectural decisions and rationale
- Configuration guide and usage examples
- Known limitations and troubleshooting

System is now fully operational with GitHub App token authentication,
comprehensive documentation, and all infrastructure in place.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* docs: document stuck PR cleanup status and script fixes

Added documentation for:
- GitHub App token compatibility fixes in fix-stuck-prs.sh
  * Changed @me to explicit author (app tokens have no identity)
  * Fixed subshell variable scope (process substitution vs pipe)
  * Silenced expected 403 auth check error
- Current status: 24 PRs have approvals but remain OPEN
  * Auto-merge failed due to missing GitHub App permission
  * Approvals satisfy branch protection requirement
  * Next: expand permissions or manually merge

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: update list-prs.sh to search all repos by owner instead of @me

GitHub App tokens don't have user identity, so @me/@review-requested searches
fail. Changed to enumerate all repos in don-petry and petry-projects, then
search for open PRs within each repo. This covers the full scope:
- All open PRs in personal don-petry repos
- All open PRs in petry-projects org repos

Resolves 6 consecutive workflow failures due to zero PRs being enumerated.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* docs: add investigation report for PR review agent workflow failures

* fix: move env section before steps in workflow to fix YAML structure

GitHub Actions requires env section to come before steps in job definition.
Moving env definition up and setting GH_TOKEN only in steps that need it.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* script: add backfill-approvals to retroactively apply real approvals

Converts PRs with agent approval comments (but no real GitHub approvals)
to have actual APPROVED reviews. Needed for PRs reviewed before the
gh pr review --approve fix was applied.

Usage:
  scripts/backfill-approvals.sh          # dry-run (preview changes)
  scripts/backfill-approvals.sh false    # apply real approvals

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* feat: add backfill-approvals workflow to run as GitHub App bot

Runs backfill-approvals.sh via GitHub Actions so the approvals are posted
by the bot identity, not the PR author — GitHub rejects self-approvals.

Also fixes subshell counter bug in the script (piped while loop lost variable
state; switched to process substitution).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: convert inner while loop to process substitution to preserve counters

Both the outer repo loop and inner PR loop were piped subshells, causing
all counter increments (approved/skipped/failed) to be lost. The summary
always showed 0. Fixed by using < <(...) process substitution for both loops.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* car-hunt: add VIN-deep-dive flow, printable checklists, location-tier ranking

- Step 5.5 pre-test-drive flow: NHTSA VIN decode + per-VIN unrepaired-recall
  automation via Chrome MCP (Honda LWC owner portal documented as not
  automatable; NHTSA web form works).
- §F printable test-drive checklist generator (reportlab/Platypus PDF) with
  cold-start, model-specific red flags, negotiation table, walk-away rules.
- §G fraud / paperwork verification checklist (vehicle-agnostic, reusable):
  walk-away triggers, VIN three-location match, curbstoning, odometer fraud,
  bill of sale, title transfer, payment protection, stolen-vehicle, title-
  jumping, after-purchase steps.
- Step 5.25 head-to-head comparison template with information-asymmetry rule.
- Step 3 Location Quality Tier scoring (Birmingham AL metro table A/B/C/D)
  with adj-CPM modifier; sheet schema gains Adj.CPM + Tier columns.
- Hard-disqualification rules added: non-running engine keywords, mileage
  inconsistency, mandatory description scrape.
- Drive MCP overwrite limitation documented; sheet ID now read from memory
  rather than hardcoded.
- FB Marketplace operational facts: Birmingham AL city ID 107739635926718,
  /search? vs /vehicles? query handling, React-controlled inline composer,
  send-button selector, seller-name extraction regex.
- scheduled-tasks/used-car-search-{morning,afternoon}: tier A/B priority
  flag, sheet ID read from memory.
- Reference PDF generator scripts checked in for reuse.

* fix: re-approve PRs where approval predates last commit (stale approval)

Instead of skipping PRs that already have any APPROVED review, skip only
those where reviewDecision is not REVIEW_REQUIRED. This handles PRs where
a bot approval was posted before a new commit was pushed — GitHub's ruleset
engine treats those approvals as stale even with dismiss_stale_reviews=false.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: robustly extract JSON verdict from mixed claude --print output (#17)

Claude's --print mode can prefix the JSON verdict with conversational
preamble text, causing jq to fail with parse errors. Add
extract_verdict_json to engine.sh and wire all three cascade action
call sites in review-one-pr.sh to use it.

Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: extract_verdict_json — check dest file first (agent Bash-write style)

The cascade-action agent writes its verdict JSON to \$OUTPUT_FILE via a
Bash tool call, then prints a text summary to stdout. The previous fix
only scanned stdout (the .raw file), which contained no JSON. The agent-
written file at \$dest (= \$OUTPUT_FILE) was already correct — just not
checked. Now check \$dest first before falling back to stdout scan.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Enforce MAX_REVIEW_CYCLES cap before running the cascade (#18)

* fix: enforce MAX_REVIEW_CYCLES cap with human-escalation marker

Previously MAX_REVIEW_CYCLES only gated AI delegation in
post-pr-review.sh — the cascade itself ran on every cron tick
regardless of how many cycles had accumulated. Real-world result:
a PR could rack up 9+ review cycles (we observed exactly that on
ContentTwin#100) before any cap took effect.

Add a pre-cascade check in review-one-pr.sh: when the count of
existing `<!-- pr-review-agent v1 sha=... -->` markers is at or
above MAX_REVIEW_CYCLES (default 3), post one escalation comment
marked `<!-- pr-review-agent escalation -->`, label
needs-human-review, request don-petry, and exit 100 (skip
sentinel — doesn't burn the MAX_PRS budget). The escalation
marker doubles as the no-spam guard: subsequent runs detect it
and exit 100 immediately.

Also reuse a single `gh pr view` for both the cycle count and the
escalation-marker check.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* review feedback: surface escalation-comment failures, drop dead unset

- Drop `2>/dev/null || true` on `gh pr comment` so a failed escalation
  post is visible in the workflow log instead of silently leaving the
  PR with no marker (which would re-trigger the cap path next tick).
- Reword the cycle-count comment from "AI delegation loops" to "review
  loops" — the cap also catches cascade-only loops where every cycle
  approves and a new commit lands before merge.
- Remove the redundant `unset PR_BODIES` — never exported, no leakage.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>

* Reliability hardening: session circuit breaker, timeouts, retry, dedup (#20)

* feat: session circuit breaker, per-tier timeouts, retry, and triage hard-fail

Reliability hardening for the PR review agent.

1. Session circuit breaker (.github/workflows/pr-review.yml): on any
   non-zero, non-100 exit from review-one-pr.sh (general failure or rate
   limit on the fallback engine), break the per-PR loop, log a clear
   error annotation naming the failing PR and reason, and exit the step
   with code 1 so the run shows red. Prevents one systemic problem from
   silently burning the entire candidate pool.

2. Per-tier timeouts (scripts/engine.sh): triage/deep/audit/action/duck
   each get their own bounded timeout (180/600/600/300/300s defaults,
   env-overridable). Previously only the duck had a timeout — a hung
   tier could burn the whole 60min job budget.

3. Retry-with-backoff on transient errors (scripts/engine.sh): triage
   retries once on 124/137/143 (timeout / signal kill) since its caller
   captures stdout via $(...) so retries are safe. Deliberately NOT
   applied to run_agentic/run_duck where stdout is redirected to a file
   — a retry there would corrupt the partial first-attempt output.

4. Triage non-JSON now hard-fails (scripts/review-one-pr.sh): replaces
   the silent fallback that synthesized a fake "escalate=MEDIUM" verdict
   and proceeded to deep review. With the new circuit breaker, loud
   failure is the right call — masking a broken triage was burning
   tokens on every PR while the workflow looked healthy.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix: stop stacking duplicate agent reviews on the same PR

Two bugs were causing the agent to leave multiple comments on the same PR.
Together they produced 10 stacked APPROVED reviews on petry-projects/ContentTwin#100.

Bug A — idempotency check is order-dependent (review-one-pr.sh):
The previous marker-discovery code did:
  ((.reviews // []) + (.comments // [])) | .[].body | grep marker | tail -1
This relies on the array concatenation order, not chronological order. When
old agent comments existed alongside newer agent reviews, tail -1 picked
the comment-array marker (older) over the review-array marker (newer),
causing the script to think the head SHA hadn't been reviewed and re-run.
Replaced with a single jq pipeline that tags each item with submittedAt /
createdAt, sorts by timestamp, and takes the actual most-recent marker.

Bug B — no cleanup of prior agent items (post-pr-review.sh):
After successfully posting a new review/comment, prior agent items were
left in place, accumulating forever. Added mark_prior_agent_items_obsolete
which, after a successful post:
  - dismisses prior APPROVED/COMMENTED/CHANGES_REQUESTED agent reviews via
    the GitHub dismissal API (UI shows them struck-through as Dismissed)
  - edits prior agent comments to wrap their body in a collapsed <details>
    block with a "Superseded by re-review at <SHA>" summary, plus a
    `<!-- pr-review-agent superseded -->` sentinel for idempotency

All cleanup API calls are best-effort — failures don't break the workflow.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(cleanup): warn on API failures, preserve global newest, file-stage JSON

Three fixes to mark_prior_agent_items_obsolete from the review of PR #20:

1. ::warning:: annotations on every cleanup API failure (review/comment
   list-fetch, individual review dismissal, individual comment fetch+edit).
   Previously these were silenced with `|| true`, so a permissions change
   on the dismissal endpoint would let duplicates stack indefinitely with
   no signal in the Actions UI. Cleanup is still non-fatal — the new post
   has already landed — but failures are now visible.

2. Preserve the globally-latest agent item across BOTH categories, not the
   newest of each category separately. The earlier code split reviews and
   comments and applied `[:-1]` to each, which left a stale fix-request
   comment in place when the new post was a review (or vice versa). The
   one-off cleanup of ContentTwin#100 hit exactly this case: 12 stacked
   reviews collapsed to 1, but a stale comment from 2026-04-25 (SHA
   cd9132d6) was preserved as "newest comment" even though the latest
   review at SHA 3af8c8ee was newer overall. Now: compute the max
   timestamp across both feeds, exclude items at that timestamp.

3. Stage API responses to disk (`mktemp` + `jq <file>`) instead of routing
   through `--argjson "$var"`. The old approach broke on rare unescaped
   control chars in user-authored comment bodies (jq refused to parse the
   resulting shell-vared JSON). File-based input sidesteps the shell
   pipeline entirely.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>

* Remove car-hunt skill and scheduled tasks (moved to don-petry/don-petry) (#21)

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: apply Copilot fallback hardening and reuse GH_PAT for Copilot auth

- Use secrets.GH_PAT (existing personal account token with Copilot
  subscription) instead of a separate COPILOT_GITHUB_TOKEN secret
- Pin actions/create-github-app-token to SHA (v3.1.1) for supply chain safety
- Add post-install verification for gh-copilot extension with warning on failure
- Guard Copilot fallback path: skip PR gracefully if extension is not installed

Addresses issue #24 workflow fixes (1-3) that could not land via PR #25
due to GitHub App lacking workflows permission.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: switch Copilot engine to gh built-in and fix app-id deprecation

- Remove gh extension install (github/gh-copilot conflicts with built-in alias)
- Replace with gh copilot --version check using COPILOT_GITHUB_TOKEN (GH_PAT)
- Update all engine.sh copilot invocations from bare `copilot` binary to
  `gh copilot suggest --target shell` with GH_TOKEN overridden to GH_PAT
  so the user token (with Copilot subscription) is used, not the App token
- Fix actions/create-github-app-token: app-id → client-id (same secret value,
  just the renamed input in v3.x)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: harden Copilot fallback path and remove stale worktrees (#25)

- gh-copilot install: add --force flag and post-install verification
  warning so silent failures due to built-in alias conflict are visible
  in workflow logs instead of being silently swallowed by `|| true`
- fallback pre-flight check: verify `gh extension list | grep copilot`
  before switching to Copilot engine; if unavailable, skip the PR and
  continue the batch rather than session-aborting and dropping all
  remaining candidates (fixes the 27-PR drop on run #503)
- pin actions/create-github-app-token to SHA for v3.1.1 (Node.js 24
  compatible) to prevent hard break on 2026-06-02 when GitHub forces
  Node.js 24 as default runtime
- remove 6 stale Claude Code worktrees from git tracking; add
  .gitignore entry to prevent future worktrees from being committed
  (eliminates exit-128 warning on every actions/checkout post-job sweep)

Closes #24

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: petry-projects-pr-review-agent[bot] <petry-projects-pr-review-agent[bot]@users.noreply.github.com>

* ci: add pre-flight dedup check to prevent duplicate claude-issue PRs (#26)

Before invoking Claude on a labeled issue, a new shell step queries
for any open PR with a branch matching claude/issue-NNN-* (or a body
containing "Closes #NNN"). If one is found it posts a comment on the
issue pointing to the existing PR and skips the Claude step entirely.
A secondary prompt instruction tells Claude to check first and push to
the existing branch rather than opening a new PR.

Motivated by issue petry-projects/google-app-scripts#171, where the
claude label was re-applied three times and each run created a fresh
PR unaware of the prior attempts.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor: migrate to machine user PAT auth (closes #27) (#28)

* refactor: migrate from GitHub App to machine user PAT auth

Closes #27

GitHub Apps cannot be listed in CODEOWNERS, blocking PRs in repos with
require_code_owner_review: true. Switch all workflows to use a machine
user account's fine-grained PAT (DON_PETRY_BOT_GH_PAT secret), which can
join an org team listed in CODEOWNERS.

Workflows: removed actions/create-github-app-token steps in pr-review,
fix-stuck-prs, backfill-approvals, and daily-pr-review-health. All now
use secrets.DON_PETRY_BOT_GH_PAT directly.

Docs: renamed GITHUB_APP_SETUP.md to MACHINE_USER_SETUP.md with full
rewrite covering account creation, CODEOWNERS config, PAT generation,
and rotation. Updated auth sections in IMPLEMENTATION.md, SETUP.md,
STATUS.md, DOCUMENTATION.md, README.md.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* refactor: switch to org-scoped PAT secret DON_PETRY_BOT_PETRY_PROJECT_PAT

Replace DON_PETRY_BOT_GH_PAT with DON_PETRY_BOT_PETRY_PROJECT_PAT — the
new fine-grained PAT scoped to the petry-projects org (resource owner =
petry-projects). The previous PAT was scoped to the donpetry-bot personal
namespace and had no repository access.

The old DON_PETRY_BOT_GH_PAT secret is retained for any future use against
don-petry's personal repos.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* chore: ignore .claude/scheduled_tasks.lock state file

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>

* refactor: merge approval repair scripts into single automation

- Consolidate backfill-approvals.sh and fix-stuck-prs.sh into repair-pr-approvals.sh
- Iterate all repos in both orgs instead of assuming author
- Verify no existing APPROVED review before posting
- Copy original agent comment as review body
- Enable auto-merge when posting approval if needed
- Single workflow with 30-min timeout

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: add @mention trigger for on-demand PR reviews (#30)

Adds repository_dispatch support so commenting @petry-review-bot on any
PR fires an immediate review without waiting for the hourly schedule.

- pr-review.yml: new `repository_dispatch` trigger (type: pr-review-mention),
  per-PR concurrency group for mention runs, FORCE_REVIEW and DRY_RUN
  env vars that handle both workflow_dispatch and repository_dispatch paths
- review-one-pr.sh: FORCE_REVIEW=true bypasses idempotency so a mention
  always runs a fresh cascade even if the head SHA hasn't changed
- templates/mention-listener.yml: deploy to petry-projects/.github;
  listens for @petry-review-bot, validates commenter trust, posts ack,
  sends repository_dispatch (requires Contents:write, not Actions:write)

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: harden rebase, review dismissal, and health log diagnostics (#40)

Addresses 4 code-actionable recommendations from health check report #33:

- [CRITICAL] Make rebase/branch-update non-fatal — 403/504 during rebase
  emits ::warning:: instead of aborting the batch session (#34)
- [MEDIUM] Add retry with exponential backoff (3 attempts) for transient
  5xx on update-branch API; break immediately on 4xx (#37)
- [MEDIUM] Guard review dismissal with state re-check before calling
  dismiss API, preventing 422s from race conditions (#38)
- [LOW] Surface missing log warnings in health check script (#39)
- Fix: skip auto-merge when branch is still BEHIND after failed rebase

Closes #34, closes #37, closes #38, closes #39

* refactor: migrate to org-wide .github-private convention (#41)

* refactor: parametrize hardcoded identity values for org migration

Replace all hardcoded references to don-petry, petry-review-bot, and
don-petry/pr-review-agent with environment variables that default to
the current values. This allows the agent to be configured for different
orgs/users via repo variables.

Changes:
- Scripts use $REVIEWER_USER, $TARGET_ORG, $BOT_USER, $AGENT_REPO
- Prompts no longer reference specific GitHub usernames
- Workflows use org-level GH_PAT_WORKFLOWS secret instead of
  repo-level DON_PETRY_BOT_PETRY_PROJECT_PAT
- Health check uses context.repo.owner/repo for issue creation
- Variables are set in workflow env block with defaults

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add Copilot custom agent profiles for org-wide use

Create /agents/ directory with three agent profiles:
- pr-reviewer: Multi-tier cascading PR review
- feature-ideator: Feature idea generation and prioritization
- compliance-auditor: Org standards compliance checking

These are Copilot custom agent profiles that become available
org-wide in the .github-private repo convention.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Squashed 'frameworks/bmad-method/' content from commit e36f219c

git-subtree-dir: frameworks/bmad-method
git-subtree-split: e36f219c81b6010d4aae423ba12f49edb5b6e31a

* Squashed 'frameworks/spec-kit/' content from commit 11f49ebf

git-subtree-dir: frameworks/spec-kit
git-subtree-split: 11f49ebfb2f6af55345cb4bd9a7906acd211e56f

* Squashed 'frameworks/gsd/' content from commit 304c1a13

git-subtree-dir: frameworks/gsd
git-subtree-split: 304c1a1302564c45af252bbba4bcc5350e7dac3a

* docs: update README for .github-private org infrastructure role

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* ci: add dependabot-automerge.yml workflow (#73)

Adds the required dependabot-automerge.yml workflow from the org
standard template (petry-projects/.github/standards/workflows/).
This is a thin caller stub that delegates to the org-level reusable
workflow.

Closes #48

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Don Petry <don-petry@users.noreply.github.com>

* fix(pr-review): unblock queue starvation from self-authored PRs (#96) (#97)

* fix(pr-review): unblock queue starvation from self-authored PRs (#96)

A self-authored PR sorted first in the candidate list and triggered
"Can not approve your own pull request" — which the session-fatal
abort treated like an engine error, skipping all 28 remaining PRs
on every run.

- list-prs.sh: filter out PRs authored by REVIEWER_USER at enumeration
- post-pr-review.sh: catch the GraphQL self-approval error and exit 100
  (no-op) instead of 1, so a stray self-PR can no longer abort the batch
- engine.sh: gh copilot renamed --target to --agent; fix the rubber-duck
  invocations so tier-2 cross-engine review works again
- review-one-pr.sh: stop appending a duplicate "0" to REVIEW_CYCLE when
  grep -c finds no markers under set -o pipefail (was breaking the
  cycle-cap integer comparison)

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

* fix(pr-review): authenticate as bot, harden enumeration, address review

- Workflow now runs as AGENT_USER (default don-petry-bot), distinct from
  REVIEWER_USER (the human, don-petry). The self-approval rejection that
  caused #96 came from the agent and the human sharing one identity.
- list-prs.sh filters self-authored PRs against AGENT_USER, and validates
  AGENT_USER against the GitHub username charset before interpolating into
  the jq filter (Copilot review feedback).
- review-one-pr.sh uses printf '%s\n' instead of echo for PR_BODIES, since
  PR body content is user-authored and could begin with -n/-e or contain
  backslash escapes (Copilot review feedback).
- AGENT.md guideline updated to reflect that self-authored PRs are
  intentionally excluded (CodeRabbit review feedback).

Operator note: rotate the GH_PAT_WORKFLOWS secret to a token owned by
don-petry-bot (with read:org added) for the bot-account behavior to take
effect at runtime.

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

* refactor(pr-review): collapse to single BOT_USER, route escalations via CODEOWNERS

Drop AGENT_USER and REVIEWER_USER. The workflow now has one identity:
BOT_USER (default don-petry-bot), which both owns the repos to scan and
gets filtered out as the self-approval blocker. Human escalation no
longer hard-codes a single reviewer; instead, scripts/request-codeowners-
review.sh parses CODEOWNERS in the PR's repo and requests review from
every @user / @org/team mention.

- list-prs.sh: BOT_USER for both gh-repo-list and self-author filter
- review-one-pr.sh, post-pr-review.sh: replace --user "$REVIEWER_USER"
  with the CODEOWNERS helper
- repair-pr-approvals.sh: same gh-repo-list switch
- pr-review.yml: drop AGENT_USER/REVIEWER_USER, set BOT_USER default
  to don-petry-bot
- AGENT.md: updated guideline to describe single-identity model

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

* refactor(pr-review): extract batch loop, hoist env, drop redundant step

Workflow YAML drops from 231 to 111 lines by moving the per-PR review
loop into scripts/review-batch.sh. The script can be syntax-checked and
exercised locally; the workflow now just wires env vars and dispatches.

- Hoist GH_TOKEN, MAX_PRS, CANDIDATE_LIMIT to job-level env (were
  repeated on individual steps).
- Inline `gh auth status` into the install step; remove the standalone
  Verify auth step (its only output was a one-line auth dump).
- Drop the `if: steps.list.outputs.count != '0'` guard and the step's
  `id`/output: review-batch.sh handles empty input as a no-op.
- Collapse the duplicated summary-string branch in the review loop into
  a single template with a conditional fragment.

No behavior change.

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

* perf(pr-review): cache claude-code CLI install across runs

`npm install -g @anthropic-ai/claude-code` ran on every workflow start
(~30s). Switch to a per-user npm prefix (~/.npm-global) and cache that
directory via actions/cache, keyed on CLAUDE_CODE_VERSION + runner OS.

A `command -v claude` guard makes the install a no-op on cache hit, so
the only cost on subsequent runs is the cache restore.

CLAUDE_CODE_VERSION defaults to 'latest' (cache persists until manually
flushed); set the repo variable to pin a specific version for fully
reproducible caching.

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

---------

Co-authored-by: Claude <noreply@anthropic.com>

* chore: rename GH_PAT_WORKFLOWS secret to DON_PETRY_BOT_GH_PAT (#99)

The previous name was generic and didn't tell you which account the PAT
belonged to. The new name makes the binding explicit: this secret is the
PAT owned by don-petry-bot, used as BOT_USER throughout the PR-review
workflows.

Operator follow-up before this can be merged:
- Add a new repo secret DON_PETRY_BOT_GH_PAT containing the bot's PAT
  (with repo, workflow, and read:org scopes).
- After merge, the old GH_PAT_WORKFLOWS secret can be deleted.

Affected workflows:
- pr-review.yml (1 use)
- claude.yml (4 uses, with || github.token fallback)
- daily-pr-review-health.yml (1 use)
- repair-pr-approvals.yml (1 use)

scripts/pr_review_health.sh's error message is updated to point at the
new name as well.

https://claude.ai/code/session_01EacTxiHSUhR6kxppXpmxig

Co-authored-by: Claude <noreply@anthropic.com>

* chore: remove frameworks directory (#101)

Co-authored-by: Gemini CLI <gemini-cli@example.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: deprecate pr-review-agent — remove all traces

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore all erroneously deleted files (items 4-34) (#104)

* revert: restore .github/workflows/daily-pr-review-health.yml

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore .github/workflows/repair-pr-approvals.yml

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore agents/pr-reviewer.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/cascade-action.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/deep-review.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/rubber-duck.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/security-audit.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/shared.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/single-review.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/synthesize-duck.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/synthesize.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore prompts/triage.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/engine.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/list-prs.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/post-pr-review.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/pr_review_health.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/repair-pr-approvals.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/request-codeowners-review.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/review-batch.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore scripts/review-one-pr.sh

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore templates/mention-listener.yml

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore AGENT.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore BOT_SETUP.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore DOCUMENTATION.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore IMPLEMENTATION.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore MACHINE_USER_SETUP.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore PR_REVIEW_AGENT_FAILURE_REPORT.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore PR_REVIEW_FAILURE_INVESTIGATION.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore SETUP.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore STATUS.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore README.md to pre-change state

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert: restore .github/workflows/daily-pr-review-health.yml

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat: add Gemini Pro support and optimize PR review fallback chain (#102)

* Merge main and resolve conflicts

* Enhance pr-review workflow: add Gemini support and refactor fallback logic

* Address Copilot review comments: tighten regex, fix fallback summary, and align docs

* docs: move agent documentation to docs/ folder and rename files

* docs: align secret names and fix casing in index

* docs: organize pr-review-agent documentation under dedicated folder

* Address final PR comments: tighten regex, add preflight checks, fix bot names, and cleanup docs

---------

Co-authored-by: Gemini CLI <gemini-cli@example.com>

* fix(pr-review): gate rate-limit detection on non-zero exit code

Broad patterns like `plan.*limit` and `claude.*usage` could match content
in a successful triage summary, triggering a false-positive engine fallback.
Guard the check with TRIAGE_RC != 0 so it only fires when the provider
command actually failed.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(claude): sync inlined workflow with org standard (#109)

* fix(claude): sync inlined workflow with org standard

Brings the inlined claude.yml up to parity with
petry-projects/.github/.github/workflows/claude-code-reusable.yml.

Changes:
1. Bot allow list for pull_request_review_comment
   Add coderabbitai[bot], Copilot, copilot-pull-request-reviewer[bot],
   and gemini-code-assist[bot] alongside the existing OWNER/MEMBER/
   COLLABORATOR check. These bots have author_association 'NONE' so
   their review comments were always skipped.

2. check_run trigger + claude-ci-fix job
   Port the CI failure auto-fix feature from the reusable. When a check
   fails on a PR, Claude diagnoses and fixes it automatically.
   Adapted to use DON_PETRY_BOT_GH_PAT (this repo's secret name).

3. claude-code-action SHA bump: v1.0.89 → v1.0.119
   Update both the claude and claude-issue jobs.

4. paths-ignore on pull_request trigger
   Prevents the workflow from firing on PRs that only change
   claude.yml itself, avoiding the Anthropic OIDC validation
   failure (workflow file must match default branch at token exchange).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(claude): guard bot allow list against fork PRs

Same fix as petry-projects/.github PR #238 — add same-repo guard
for bot-triggered pull_request_review_comment runs.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(claude): address ci-fix self-loop and fork PR security

- Replace Claude Code name prefix check with explicit job name list to
  correctly prevent self-loops (check_run names for inlined workflows
  are bare job names, not workflow-prefixed)
- Add fork PR trust gate in Resolve PR number step: verify head repo
  matches target repo before running Claude with privileged credentials

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* ci: replace inlined claude.yml with standard thin-caller stub

The inlined version embedded all three jobs (claude, claude-ci-fix,
claude-issue) directly and threaded DON_PETRY_BOT_GH_PAT through
checkout tokens and github_token, causing the bot's PAT to author
PRs and comments instead of github-actions[bot].

Replace with the standard thin caller that delegates to the org-level
reusable workflow (claude-code-reusable.yml@v1) via secrets: inherit,
which is the correct pattern per:
petry-projects/.github/standards/workflows/claude.yml

* fix: replace gh copilot suggest with GitHub Models REST API (#151)

* fix: replace gh copilot suggest with GitHub Models REST API (#147)

The `gh copilot suggest -p "$(cat <file>)"` invocation failed with
"Invalid command format" because:
1. The `-p` flag is not valid syntax in modern `gh copilot` built-in versions.
2. `gh copilot suggest` is a shell-command suggestion tool; it does not
   accept large PR prompts or return structured JSON responses.
3. Passing a multi-thousand-line prompt via `$(cat ...)` can hit ARG_MAX.

The non-zero exit was then misclassified by the rate-limit detector, which
aborted the entire session and skipped all remaining PRs in the queue.

Fix: replace all three copilot invocations (run_triage, run_agentic,
run_duck) with a new `copilot_chat` helper that calls the GitHub Models
REST API directly via curl. The API is OpenAI-compatible, versioned via
`X-GitHub-Api-Version`, stable against gh CLI version changes, and accepts
arbitrary prompt sizes using python3 for safe JSON encoding.

Additional changes:
- Add `COPILOT_API_MODEL` var (default `openai/o4-mini`) overrideable via env.
- run_agentic/run_duck copilot cases now also write output to $OUTPUT_FILE
  so callers that check that path directly (deep review, audit) find the JSON.
- Rate-limit responses (HTTP 429) are echoed to stdout so the existing
  `is_rate_limited()` detector fires correctly for engine fallback.
- Add pre-flight smoke test in review-batch.sh: tests GitHub Models API
  connectivity with a one-liner prompt before processing any PRs, so auth
  or model errors surface immediately as a clear setup failure.
- Add tests/test_copilot_chat.sh: unit tests for the JSON payload builder
  with edge-case prompts (quotes, newlines, # headings, large diffs, Unicode).

Closes #147

Co-authored-by: Don Petry <don-petry@users.noreply.github.com>

* fix: scope COPILOT_API_MODEL export to copilot engine only

Move the `export COPILOT_API_MODEL` from the common exports block into the
`copilot)` case where the variable is set, so it is not exported as an
empty/unset variable when the engine is `claude` or `gemini`. The
`copilot_chat` fallback `${COPILOT_API_MODEL:-openai/o4-mini}` still works
correctly when `DUCK_ENGINE=copilot` under a non-copilot primary engine.

Co-authored-by: Don Petry <don-petry@users.noreply.github.com>

* fix: address review comments — temp file for payload, fail-fast source, streaming output

- copilot_chat: write JSON body to mktemp file, pass to curl as @file to
  avoid ARG_MAX for large PR diffs (was --data-binary "$body")
- copilot_chat: add :? guard on COPILOT_GITHUB_TOKEN for a clear error
  instead of generic "unbound variable" under set -u
- run_agentic / run_duck copilot paths: stream directly to stdout (and
  tee to OUTPUT_FILE when set) rather than buffering the full response
  into a shell variable, which forced large outputs into memory and
  stripped trailing newlines
- review-batch.sh pre-flight: fail fast if source engine.sh fails
  (was silently ignored with || true)
- review-batch.sh pre-flight: build smoke-test JSON payload via python3
  + temp file instead of shell string interpolation to avoid JSON
  injection if COPILOT_API_MODEL contains special characters
- Clarify that openai/o4-mini is the correct April-2025 o4-generation
  model name, not a typo for o1-mini or gpt-4o-mini

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Don Petry <don-petry@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* feat: prioritise .github/.github-private PRs, oldest-first within tier (#155)

* feat: prioritise .github/.github-private PRs, oldest-first within tier

- list-prs.sh: add createdAt to JSON fetch; emit priority|createdAt|url
  lines (priority 0 for .github/.github-private, 1 for everything else)
- Replace final sort -u with a two-pass sort: deduplicate by URL, then
  sort by priority asc then createdAt asc; strip sort keys with cut
- tests/test_list_prs_sort.sh: 16 tests covering priority classification,
  oldest-first ordering, mixed scenarios and deduplication
- .github/workflows/test.yml: run both unit-test files on every PR / push

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* test: add dedup edge case for same URL with conflicting priorities

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: remove temperature from copilot_chat payload (o4-mini incompatible)

The GitHub Models API rejects temperature=0 for reasoning models like
o4-mini with HTTP 400:
  'Unsupported value: temperature does not support 0 with this model.
   Only the default (1) value is supported.'

This caused the rubber duck (DUCK_ENGINE=copilot / DUCK_MODEL=o4-mini)
to fail with HTTP 400 on every claude-engine run, silently degrading
every review to deep-only with no cross-engine sanity check.

Fix: remove temperature from the copilot_chat JSON payload entirely —
the API defaults to 1, which is the only supported value for o4-mini.

Also:
- Update tests/test_copilot_chat.sh: sync build_payload to match and
  flip Test 9 to assert temperature is ABSENT (not 0).
- Add .github/workflows/test.yml: run unit tests on every PR and push
  to main so regressions are caught before merge.

Reproducer: job/75690347409 — duck failure logged as:
  copilot_chat: HTTP 400 from GitHub Models API
  {"error":{"message":"Unsupported value: 'temperature' does not
   support 0 with this model..."}}

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: address PR review comments

- list-prs.sh: tighten priority regex to /[.]github(-private)?/pull/
  so path-boundary anchor prevents false positives (e.g. foo.github)
- list-prs.sh: replace printf pipeline with here-string to avoid ARG_MAX
  limits on large PR sets
- test.yml: add permissions: contents: read (least-privilege, CodeQL fix)
- test.yml: add timeout-minutes: 5 to prevent hung runs
- test_list_prs_sort.sh: sync sort_entries helper (here-string + || true)
  and classify regex to match updated list-prs.sh exactly

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: don-petry <don@petry.dev>

* fix: restore pr-review.yml content (accidentally emptied in revert)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: restore pr-review.yml (file was accidentally emptied)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore(deps): bump actions/cache from 4 to 5 (#165)

Bumps [actions/cache](https://github.com/actions/cache) from 4 to 5.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): bump petry-projects/.github/.github/workflows/claude-code-reusable.yml (#164)

Bumps [petry-projects/.github/.github/workflows/claude-code-reusable.yml](https://github.com/petry-projects/.github) from 1 to 2.
- [Commits](https://github.com/petry-projects/.github/compare/v1...v2)

---
updated-dependencies:
- dependency-name: petry-projects/.github/.github/workflows/claude-code-reusable.yml
  dependency-version: '2'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* fix(list-prs): search DELEGATION_ORGS and drop --checks success pre-filter

- Add iteration over all orgs in DELEGATION_ORGS (e.g. don-petry) so PRs
  in those orgs enter the candidate pool. Previously only BOT_USER and
  TARGET_ORG were searched.
- Remove --checks success from org repo searches. GitHub excludes PRs from
  repos with no CI configured when this flag is used, silently dropping all
  PRs from repos like .github, .github-private, google-app-scripts, and
  TalkTerm. review-one-pr.sh already enforces CI gating per-PR and treats
  empty statusCheckRollup as passing, so the pre-filter is redundant and
  harmful.
- Add --limit 200 to gh repo list calls to handle org growth beyond gh's
  default 30-repo cap.

* feat: add code-quality ruleset (compliance fix #60) (#86)

Creates the required `code-quality` repository ruleset enforcing required
status checks on the default branch, as mandated by the org standard:
standards/github-settings.md#code-quality--required-checks-ruleset-all-repositories

Required checks:
- SonarCloud (code quality analysis)
- CodeQL (SAST)
- agent-shield / AgentShield (agent security scan)
- dependency-audit / Detect ecosystems (dependency vulnerability scan)

Bypass actors:
- OrganizationAdmin (always) — emergency override
- dependabot-automerge-petry Integration (always) — Dependabot auto-merge

The ruleset was applied directly via GitHub API. This file documents the
configuration as code for auditability and future reapplication.

Closes #60

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Don Petry <don-petry@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* feat(prompts/dev-lead): add human prompt template

* feat(prompts/dev-lead): add human-pr prompt template

* feat(dev-lead): implement dev-lead agent Phases 0-6

* fix: per-PR isolation and single-review retry (closes #132)

- review-batch.sh: non-rate-limit per-PR failures (exit code 1) no
  longer abort the session. SESSION ABORTED EARLY is now reserved for
  the rate-limit-on-fallback-engine case (exit code 2) only. All other
  failures are counted and logged; remaining candidates continue.

- review-one-pr.sh: single-review step retries up to
  SINGLE_REVIEW_MAX_RETRIES (default 2) times with a
  SINGLE_REVIEW_RETRY_DELAY_SEC (default 15s) gap before giving up.
  On exhaustion, the PR is flagged needs-human-review and the script
  exits with code 1, which the updated batch treats as a non-fatal
  per-PR failure. Raw model output and stderr are logged on each failed
  attempt for post-mortem visibility.

Root cause of run #25707852006: claude-opus-4-7 returned a verbose
non-JSON response for PR #129; the old code treated that as fatal and
skipped 35 remaining candidates.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix: rate-limit detection and per-attempt stderr logs in single-review retry

Address inline review comments on PR #133:

- Rate-limit check: after each run_agentic call, inspect both stdout
  (VERDICT_JSON.raw) and stderr (SINGLE_LOG) with is_rate_limited before
  retrying. A rate-limit match exits immediately with code 2 so
  review-batch.sh can trigger engine fallback — consistent with triage and
  deep-review tiers. Previously a rate-limited single-review would burn all
  retries and exit 1 (per-PR failure), silently leaving the batch on the
  same rate-limited engine for all remaining PRs.

- Per-attempt log files: stderr is now written to
  single-review-attempt-N.log rather than a single overwritten file, so
  no earlier-attempt errors are lost. Each attempt logs its own stderr
  inline on failure; the fallback path cats all attempt logs for
  post-mortem visibility.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* feat(dev-lead): Phase 0 test infrastructure and Phase 1 intent stub

Phase 0: full test harness for the dev-lead agent —
  26 event fixtures (all valid JSON with _test_expected_intent),
  stub claude/gemini engines, mock gh binary, CI failure log sample,
  bats helpers (stub-engine, mock-gh, assert-env, prompt-vars),
  7 prompt templates with VARIABLES declarations, preflight script,
  prompt coverage integration test, and test-dev-lead.yml CI workflow.

Phase 1: dev-lead.yml trigger workflow (all 7 event types, dispatch + ci-relay
  jobs) and dev-lead-intent.sh stub (anti-loop guard live; all other events
  emit skip/not-implemented).  14/14 bats unit tests pass.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): add permissions blocks to test workflow jobs (CodeQL)

* fix(ci): auto-fix for lint / eslint [skip ci-relay]

* feat(dev-lead): Phases 2-6 — CI fix, review fix, issue, engine fallback

Phase 2: run_writer/run_writer_with_fallback in engine.sh, full intent
routing in dev-lead-intent.sh, dev-lead-fix-ci.sh handler, workflow wiring.

Phase 3: dev-lead-fix-reviews.sh handles fix-reviews, fix-bot-comment,
human, human-pr, rebase intents. Full review routing in intent classifier.

Phase 5: dev-lead-fix-issue.sh handles the issue intent with dedup guard
and branch/PR creation.

Phase 6: run_writer_with_fallback with claude→gemini→copilot fallback chain.

Tests: 77 unit tests across 8 new .bats files, all passing. Updated Phase 1
stub tests to reflect actual Phase 2+ routing behavior.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): address review comments — dispatch JSON, dead code, portable stub gh

* fix(dev-lead): pre-flight after intent, fork URL check, review-batch exit code, retry comment

* fix(dev-lead): install bats-core from GitHub to avoid root requirement

* fix(security): move event values to env vars to prevent script injection (SonarCloud)

* fix(security): move CLAUDE_CODE_VERSION to env block (SonarCloud script injection)

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* feat(dev-lead): Phase 1.5 — reusable workflow for cross-repo adoption + Phase 7 shadow period

* feat(dev-lead): add reusable workflow (Phase 1.5) and begin shadow period (Phase 7)

- Create .github/workflows/dev-lead-reusable.yml: workflow_call entry
  point for other repos. Checks out .github-private scripts/prompts into
  .dev-lead/, then runs the same intent-classify + handler pipeline as
  dev-lead.yml with PROMPTS_DIR=.dev-lead/prompts/dev-lead.
- Add PROMPTS_DIR env-var support to dev-lead-fix-ci.sh,
  dev-lead-fix-reviews.sh, and dev-lead-fix-issue.sh so the reusable
  workflow can point scripts at the sparse-checkout path without changing
  CWD. Defaults to prompts/dev-lead (backwards-compatible for dev-lead.yml).
- Annotate dev-lead.yml with Phase 7 shadow-period window (2026-05-15
  through ~2026-05-29): claude.yml and dev-lead.yml run in parallel until
  regressions clear, then claude.yml is removed.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* chore(dev-lead): update shadow-period tracking issue ref to #180

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): document PAT requirement and pin ref:main on private checkout

* fix(dev-lead): P1 review fixes — ci-relay in reusable, .dev-lead gitignore, export PROMPTS_DIR, trim header

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): rate-limit detection, separate status, and retry cron (#192) (#196)

* chore(dev-lead): decommission claude.yml, bump ACTION_TIMEOUT_SEC to 600s

- Delete .github/workflows/claude.yml — replaced by dev-lead.yml (shadow
  period complete, tracking issue #180).
- scripts/engine.sh: raise ACTION_TIMEOUT_SEC default 300→600s to reduce
  timeout failures on large-repo fix-ci runs (PR #80 had 3 timeouts).
- AGENTS.md: remove claude.yml immutability exemption; note dev-lead.yml
  as the active AI automation workflow and its edit-via-reusable pattern.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* refactor: replace Claude analysis with pure gh/jq telemetry in health check

* refactor: drop Node/Claude steps from daily-pr-review-health workflow

* rename: daily-pr-review-health → actions-fleet-monitor

* rename: daily-pr-review-health → actions-fleet-monitor

* fix(agents-md): clarify dev-lead.yml vs dev-lead-reusable.yml scope

dev-lead.yml in .github-private runs inline steps (not a caller stub).
Behavior changes for this repo go to dev-lead.yml directly; changes that
affect all org repos via the cross-repo reusable go to dev-lead-reusable.yml.

Addresses Copilot review on PR #194.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): rate-limit detection, separate status, and retry cron

Closes #192. Implements all phases from the revised plan.

**Phase 0 — fix run_writer stdout capture (prerequisite)**
- engine.sh: capture stdout via `tee` to a tempfile so `is_rate_limited`
  can inspect the output; old code read `/tmp/dev-lead-writer-stderr`
  which was never written (claude --print outputs to stdout, not stderr).
  Fallback engines were never tried in practice.
- engine.sh: add `parse_reset_time` to extract ISO timestamp from
  `resets H:MMpm (UTC)` in engine output and write it to
  `/tmp/dev-lead-rate-limit-reset` for callers to embed in markers.

**Phase 1 — separate status=rate-limited from status=failed**
- fix-ci.sh: detect engine exit 2 → post `status=rate-limited` (not
  `status=failed`); embed parsed reset time in marker body; exit 2.
- fix-ci.sh: fix `check_idempotency` to treat `status=rate-limited`
  as retriable — only block on terminal statuses (applied, failed,
  no-changes). Previously, rate-limited markers blocked all retries.
- fix-ci.sh: `count_recent_failures` already filtered to `status=failed`;
  add explicit comment confirming rate-limited markers are excluded.
- fix-ci.sh: add `has_rate_limited_marker` dedup check so the same SHA
  never accumulates more than one rate-limited comment.
- fix-reviews.sh: on engine exit 2 for all five intent types
  (fix-reviews, fix-bot-comment, human, human-pr, rebase), post a
  `status=rate-limited` marker with embedded reset time and exit 2.
- fix-reviews.sh: for `human` and `human-pr` intents, also post a
  user-visible acknowledgment comment so users know their request was
  received and will be retried.

**Phase 2 — scheduled retry cron for fix-ci**
- dev-lead-retry.sh: new script scans all open PRs across TARGET_ORG
  for `status=rate-limited` markers on current HEAD SHA; skips PRs
  whose reset time is still in the future; staggered dispatches (30s
  between repos) to prevent cascading org-level rate-limit hits.
- dev-lead-retry.yml: new workflow — scheduled every 2 hours (dual
  offsets to work around GitHub scheduler skew), plus workflow_dispatch.

**Phase 3 — retry for fix-reviews intents (same cron)**
- dev-lead-retry.sh already handles all five fix-reviews intent types
  (fix-reviews, fix-bot-comment, human, human-pr, rebase) via
  `dev-lead-reviews-retry` dispatch events.
- dev-lead-intent.sh: add `dev-lead-reviews-retry` dispatch type routing
  to the intent classifier — maps intent_type from payload to the correct
  existing fix-reviews step.
- dev-lead.yml: add `dev-lead-reviews-retry` to repository_dispatch types.
- dev-lead.yml: unify concurrency group for all dispatch types to
  `dev-lead-pr-{pr_number}` to avoid per-type slot fragmentation.

**Tests**
- test_engine_writer.bats: 8 new tests covering rate-limit stdout detection,
  exit-2 mapping, fallback exhaustion, reset time parsing.
- test_fix_ci.bats: 5 new tests covering rate-limited status, exhaustion
  exclusion, idempotency pass-through, and dedup.
- test_fix_reviews.bats: 5 new tests covering all intent types + human ack.
- test_intent_ci.bats: 4 new tests for dev-lead-reviews-retry dispatch routing.
- e2e/scenarios/07-rate-limit-retry.sh: new E2E scenario covering all parts.
- 3 new event fixtures for repository_dispatch_reviews_retry_* payloads.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): address review comments on #196

Addresses all 18 review threads from Copilot, Codex, and Gemini.

**P1 bugs fixed:**
- dev-lead-retry.sh: all log echo calls in scan_pr_for_rate_limits and
  dispatch_* functions now write to stderr; only the final count is on
  stdout. Previously, log lines polluted the command substitution,
  causing arithmetic expansion to fail and aborting the scan on the
  first retry candidate.
- dev-lead-retry.yml: permissions: contents was read-only; creating
  repository_dispatch events requires contents: write.

**Correctness fixes:**
- dev-lead-retry.sh: restrict automated retries to intents whose context
  can be reconstructed at runtime (fix-reviews, human-pr, rebase). human
  and fix-bot-comment require USER_INSTRUCTION/COMMENT_BODY from the
  original event which cannot be re-fetched; they are explicitly excluded.
- dev-lead-fix-reviews.sh: resolve HEAD_SHA from the PR API when not
  provided by the triggering event (issue_comment intents carry no SHA).
  Without this, rate-limited markers had no sha= field and were
  invisible to the retry scanner.
- dev-lead-fix-reviews.sh: write terminal status=applied marker after
  successful fix-reviews, human-pr, rebase runs. Prevents the retry
  cron from re-dispatching the same intent on every subsequent tick when
  the SHA hasn't changed.
- dev-lead-retry.sh: check for reviews terminal marker before dispatching
  a retry (mirrors the existing fix-ci terminal-marker check).
- dev-lead-retry.sh: look up current check-run details (details_url, id)
  at dispatch time via the commits check-runs API, so retried fix-ci runs
  have full failure logs and annotations rather than empty fields.
- dev-lead-fix-ci.sh: embed check= field in the rate-limited marker so
  the retry cron knows which check run to look up.
- dev-lead-fix-reviews.sh: human intent now posts a "please re-mention
  @dev-lead" ack instead of "I'll retry automatically" since it will NOT
  be retried automatically. human-pr keeps the auto-retry ack (correct).

**Pagination (Copilot + Gemini):**
- dev-lead-fix-ci.sh: add --paginate to check_idempotency, count_recent_
  failures, has_rate_limited_marker.
- dev-lead-fix-reviews.sh: add --paginate to has_reviews_rate_limited_marker.
- dev-lead-retry.sh: add --paginate to PR comments and PR list fetches.

**Other improvements:**
- dev-lead-retry.yml: simplify to single cron schedule (15 */2 * * *);
  remove BOT_USER env var (unused in the script).
- dev-lead-retry.sh: raise repo list limit 200 → 1000; add hard-error
  when list_repos_for_org returns empty (surfaces token permission issues
  rather than silently scanning 0 repos).
- dev-lead-intent.sh: use jq -nc (compact, null-input) for context JSON
  construction — safe against values with special characters.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(pr-review): handle Gemini trust-mode failure and properly route engine fallbacks (#217)

* fix(pr-review): handle Gemini trust-mode failure and properly route engine fallbacks

Closes #208

* fix(ci): install gemini CLI and set trust mode for dev-lead agents

---------

Co-authored-by: Gemini CLI <gemini-cli@example.com>

* fix: resolve YAML syntax error in dev-lead-reusable workflow

* fix: address automated feedback from PR 217 (#219)

This addresses missed feedback from CodeRabbit (removing syntax errors in YAML) and gemini-code-assist (improving string concatenation in validate-engines.sh), and adds assertion output checks to tests.

Co-authored-by: Gemini CLI <gemini-cli@example.com>

* chore: remove Phase 7 shadow period comment from dev-lead.yml

* feat: skip PRs with CHANGES_REQUESTED review (+ FORCE_REVIEW and stale-review guards)

* feat: skip PRs with CHANGES_REQUESTED review decision

Adds an early-exit check alongside the existing CI guards so the cascade
skips any PR where a human reviewer has already requested changes.
Reviewing those PRs before the author responds wastes tokens and creates
confusing parallel feedback threads.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix: honor FORCE_REVIEW and skip only stale-free CHANGES_REQUESTED reviews

Two issues addressed from Codex P2 feedback:

1. FORCE_REVIEW bypass: mention-triggered and force_review=true runs now
   bypass the CHANGES_REQUESTED skip, so authors can explicitly request a
   re-review after addressing feedback.

2. Stale review guard: only skip when a CHANGES_REQUESTED review targets
   the current head SHA. On repos without automatic stale-review dismissal,
   reviewDecision stays CHANGES_REQUESTED after new commits — the cascade
   now re-engages when the author has pushed new work.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): resolve context loss, missing checkout, and push logic (#223)

* feat: skip PRs with CHANGES_REQUESTED review decision

Adds an early-exit check alongside the existing CI guards so the cascade
skips any PR where a human reviewer has already requested changes.
Reviewing those PRs before the author responds wastes tokens and creates
confusing parallel feedback threads.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix: honor FORCE_REVIEW and skip only stale-free CHANGES_REQUESTED reviews

Two issues addressed from Codex P2 feedback:

1. FORCE_REVIEW bypass: mention-triggered and force_review=true runs now
   bypass the CHANGES_REQUESTED skip, so authors can explicitly request a
   re-review after addressing feedback.

2. Stale review guard: only skip when a CHANGES_REQUESTED review targets
   the current head SHA. On repos without automatic stale-review dismissal,
   reviewDecision stays CHANGES_REQUESTED after new commits — the cascade
   now re-engages when the author has pushed new work.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* fix(dev-lead): resolve context loss, missing checkout, and push logic

- dev-lead-intent.sh: include actor and body in INTENT_CONTEXT for comments
- dev-lead.yml: parse and pass INTENT_ACTOR and INTENT_COMMENT_BODY to agent
- dev-lead-fix-reviews.sh: perform PR checkout, git push, and post summary comments

* fix(dev-lead): exit with 0 on no-changes in fix-reviews and human-pr

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* test: bypass CI check

* fix(pr-review): remove CI bypass hack

* fix(dev-lead): use heredoc for multiline environment variables (#224)

* fix(dev-lead): use heredoc for multiline environment variables

* fix(dev-lead): ensure all engines are installed and context is consistent

* fix(dev-lead): resolve stale model name in engine fallbacks

* fix(dev-lead): improve fallback reliability and script quality

- engine.sh: resolve stale model names in fallback loop; use -latest Gemini aliases
- dev-lead-fix-reviews.sh: remove unused code and fix PR_URL export

* fix(dev-lead): resolve Gemini model names and shell lint warnings

* security(dev-lead): use random heredoc delimiter to prevent inject…
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants