Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
157 commits
Select commit Hold shift + click to select a range
ea7e898
Add scheduled PR review agent
Apr 8, 2026
e06c3b9
Use Claude Code OAuth token instead of API key
Apr 9, 2026
513bac1
Enforce single-PR scope and default to dry-run
Apr 9, 2026
be14f6e
Add 3-model council + commit-SHA-aware iterative review
Apr 9, 2026
36f3eeb
Upgrade maintainability lens from Haiku 4.5 to Sonnet 4.6
Apr 9, 2026
dbc8df5
Go-live improvements for PR review agent (#2)
don-petry Apr 9, 2026
a027e03
Add Claude delegation, auto-merge, and rebase handling (#3)
don-petry Apr 10, 2026
8707fe7
Optimize review: small-PR and incremental fast paths (#4)
don-petry Apr 10, 2026
1e5d464
Add Claude Code workflow (#8)
don-petry Apr 16, 2026
ee366d4
feat: add Copilot engine support via REVIEW_ENGINE toggle (#5)
don-petry Apr 16, 2026
0ce34fc
feat: inline Claude Code workflow to access repo secrets directly (#9)
don-petry Apr 16, 2026
8fc182a
feat: cross-engine adversarial rubber duck review (#11)
don-petry Apr 16, 2026
880fecb
docs: clarify PR review batching issue
Apr 16, 2026
039f97d
feat: CI gate + poll-loop rebase wait across all review prompts (#14)
don-petry Apr 18, 2026
97e8321
docs: update repo references from self to pr-review-agent
Apr 21, 2026
9f057c3
feat: add car-hunt skill for used vehicle research and ranking
Apr 25, 2026
6da777c
fix: fix FB Marketplace links, improve AutoTrader fetch, add Sheets e…
Apr 25, 2026
63fa71e
fix: add Craigslist as explicit source, enforce real-URL-only link rule
Apr 25, 2026
ef770ca
fix: split zip/radius questions, rename MAX_MILES to MAX_ODOMETER
Apr 25, 2026
126a305
fix: include self-authored PRs awaiting review in agent enumeration (…
don-petry Apr 25, 2026
9fa91db
feat: add doors/transmission fields, update-mode sheet, fix CarGurus/…
Apr 25, 2026
c93ebed
feat: add hard disqualification filters for finance-speak, title issu…
Apr 25, 2026
c3be113
feat: add Distance to Car field using Nominatim geocoding + Haversine…
Apr 25, 2026
0d00802
fix: address low result count — batch URL extraction, preview-filter …
Apr 25, 2026
1d19691
fix: make cascade-action prompt explicit about gh pr review --approve
Apr 25, 2026
9867085
add: cleanup script to fix PRs with comment approvals instead of revi…
Apr 25, 2026
b4155af
fix: cleanup script now requires bot account token
Apr 25, 2026
30fa3e1
add: workflow to fix stuck PRs using bot account token
Apr 25, 2026
82769cb
fix: pass GH_PAT to checkout action so workflow uses bot account
Apr 25, 2026
717e0fa
debug: simplify account check, add authentication debug output
Apr 25, 2026
fa95b48
docs: add comprehensive petry-review-bot setup instructions
Apr 25, 2026
14933df
docs: add GitHub App setup guide (recommended approach)
Apr 26, 2026
645028f
docs: add comprehensive GitHub App setup documentation
Apr 26, 2026
08d627c
fix: silence auth check in fix-stuck-prs when using GitHub App token
Apr 26, 2026
98abc68
fix: use explicit author instead of @me in stuck PR search
Apr 26, 2026
ce47e41
fix: avoid subshell in while loop to preserve variable state
Apr 26, 2026
e7df60d
docs: add comprehensive status report for GitHub App setup
Apr 27, 2026
086e761
docs: document stuck PR cleanup status and script fixes
Apr 28, 2026
b404fde
docs: add investigation report for PR review agent workflow failures
Apr 28, 2026
74b9421
script: add backfill-approvals to retroactively apply real approvals
Apr 28, 2026
2a7e2c6
feat: add backfill-approvals workflow to run as GitHub App bot
Apr 28, 2026
52e60be
fix: convert inner while loop to process substitution to preserve cou…
Apr 28, 2026
8c378ab
car-hunt: add VIN-deep-dive flow, printable checklists, location-tier…
Apr 28, 2026
7f17703
fix: re-approve PRs where approval predates last commit (stale approval)
Apr 28, 2026
177a413
fix: robustly extract JSON verdict from mixed claude --print output (…
don-petry Apr 28, 2026
a51514c
fix: extract_verdict_json — check dest file first (agent Bash-write s…
don-petry Apr 29, 2026
9735915
Enforce MAX_REVIEW_CYCLES cap before running the cascade (#18)
don-petry Apr 29, 2026
e4b9916
Remove car-hunt skill and scheduled tasks (moved to don-petry/don-pet…
don-petry Apr 30, 2026
f0d9618
fix: harden Copilot fallback path and remove stale worktrees (#25)
claude[bot] May 3, 2026
b19eaa4
ci: add pre-flight dedup check to prevent duplicate claude-issue PRs …
don-petry May 3, 2026
b439405
refactor: migrate to machine user PAT auth (closes #27) (#28)
don-petry May 5, 2026
471c769
refactor: merge approval repair scripts into single automation
don-petry May 5, 2026
8a8fecf
feat: add @mention trigger for on-demand PR reviews (#30)
don-petry May 6, 2026
aa3c8d5
refactor: migrate to org-wide .github-private convention (#41)
don-petry May 7, 2026
08ed723
ci: add dependabot-automerge.yml workflow (#73)
don-petry May 10, 2026
59014e2
fix(pr-review): unblock queue starvation from self-authored PRs (#96)…
don-petry May 10, 2026
306e9ae
chore: rename GH_PAT_WORKFLOWS secret to DON_PETRY_BOT_GH_PAT (#99)
don-petry May 10, 2026
84f2be5
chore: remove frameworks directory (#101)
don-petry May 10, 2026
085dccd
chore: deprecate pr-review-agent — remove all traces
don-petry May 11, 2026
41bcb86
chore: deprecate pr-review-agent — remove all traces
don-petry May 11, 2026
ae641b8
chore: deprecate pr-review-agent — remove all traces
don-petry May 11, 2026
bdcf731
chore: deprecate pr-review-agent — remove all traces
don-petry May 11, 2026
158b786
chore: deprecate pr-review-agent — remove all traces
don-petry May 11, 2026
5b4abf9
chore: deprecate pr-review-agent — remove all traces
don-petry May 11, 2026
c999598
chore: deprecate pr-review-agent — remove all traces
don-petry May 11, 2026
170febd
chore: deprecate pr-review-agent — remove all traces
don-petry May 11, 2026
795db28
chore: deprecate pr-review-agent — remove all traces
don-petry May 11, 2026
d295554
chore: deprecate pr-review-agent — remove all traces
don-petry May 11, 2026
8626d25
chore: deprecate pr-review-agent — remove all traces
don-petry May 11, 2026
dc36edf
chore: deprecate pr-review-agent — remove all traces
don-petry May 11, 2026
c64a3e5
chore: deprecate pr-review-agent — remove all traces
don-petry May 11, 2026
ea0e98f
chore: deprecate pr-review-agent — remove all traces
don-petry May 11, 2026
2f52653
chore: deprecate pr-review-agent — remove all traces
don-petry May 11, 2026
414f36f
chore: deprecate pr-review-agent — remove all traces
don-petry May 11, 2026
b1fdeb6
revert: restore all erroneously deleted files (items 4-34) (#104)
don-petry May 11, 2026
9f5737f
feat: add Gemini Pro support and optimize PR review fallback chain (#…
don-petry May 11, 2026
4f77d82
fix(claude): sync inlined workflow with org standard (#109)
don-petry May 11, 2026
927d75d
ci: replace inlined claude.yml with standard thin-caller stub
don-petry May 11, 2026
00989a9
fix: restore pr-review.yml content (accidentally emptied in revert)
don-petry May 13, 2026
49889e8
fix: restore pr-review.yml (file was accidentally emptied)
don-petry May 13, 2026
61a48fc
chore(deps): bump petry-projects/.github/.github/workflows/claude-cod…
dependabot[bot] May 13, 2026
b94aae0
fix(list-prs): search DELEGATION_ORGS and drop --checks success pre-f…
don-petry May 13, 2026
b63ca81
feat(prompts/dev-lead): add human prompt template
don-petry May 15, 2026
7a7ba1d
feat(prompts/dev-lead): add human-pr prompt template
don-petry May 15, 2026
408ae67
feat(dev-lead): implement dev-lead agent Phases 0-6
don-petry May 15, 2026
807578b
feat(dev-lead): Phase 1.5 — reusable workflow for cross-repo adoption…
don-petry May 15, 2026
a8caf25
fix(dev-lead): rate-limit detection, separate status, and retry cron …
don-petry May 16, 2026
5b0f3c3
fix(pr-review): handle Gemini trust-mode failure and properly route e…
don-petry May 17, 2026
6523856
fix: resolve YAML syntax error in dev-lead-reusable workflow
May 17, 2026
7b594a3
chore: remove Phase 7 shadow period comment from dev-lead.yml
don-petry May 17, 2026
e95fc48
test: bypass CI check
May 17, 2026
6f7872d
fix(pr-review): remove CI bypass hack
May 17, 2026
7d2db93
fix(dev-lead): detect engine-committed changes and remove commit inst…
May 18, 2026
8a513cc
fix(dev-lead): address PR review findings — untracked files, upstream…
May 18, 2026
71ead37
fix(dev-lead): handle SonarQube Quality Gate failures (issue #279) (#…
don-petry May 19, 2026
830e3f3
fix(dev-lead): configure git identity, surface commit/push failures, …
don-petry May 19, 2026
9ab6ff2
fix(dev-lead): resolve review threads after fixing + notify CodeRabbi…
don-petry May 19, 2026
7f25177
fix(dev-lead): post @coderabbitai resolve on no-changes + enable auto…
don-petry May 20, 2026
a0280ab
fix(dev-lead): relay check_run id, verify engine CLI, improve SonarCl…
don-petry May 20, 2026
74c64ff
ci: add required ci.yml workflow
don-petry May 20, 2026
2edb9f2
fix(dev-lead): configure git user identity in fix-issue script (#326)
don-petry May 20, 2026
18ba036
feat: implement issue #183 — Compliance: non-stub-claude.yml (#328)
don-petry May 20, 2026
521fab8
feat: implement issue #135 — Compliance: unpinned-actions-claude.yml …
don-petry May 21, 2026
1152a58
feat: implement issue #184 — Compliance: non-stub-dependency-audit.ym…
don-petry May 21, 2026
b1493c7
chore(deps): bump petry-projects/.github/.github/workflows/dependency…
dependabot[bot] May 21, 2026
c6a9f6c
feat(fleet-monitor): summarize token usage by workflow (#343)
don-petry May 21, 2026
b566b95
feat(dev-lead): harden agent instructions with Plan→TDD→Verify→Rubber…
don-petry May 23, 2026
f7f964c
add: force-deploy-pr-review workflow for branch-protection bypass
Jun 3, 2026
8d1ea47
add: pr-review-reusable.yml wrapper for workflow_call pattern
Jun 4, 2026
8f76697
fix: explicitly pass secrets in pr-review-reusable.yml workflow_call
Jun 4, 2026
4b50388
feat: implement issue #425 — dev-lead: Incomplete issue resolution - …
don-petry Jun 4, 2026
d8e3258
debug: pr-review enumeration logging (#410)
don-petry Jun 4, 2026
00501f3
chore(deps): bump actions/checkout from 4 to 6 (#433)
dependabot[bot] Jun 4, 2026
c9415c7
merge: PR #353 - PR Review Agent diagnostics and transient failure ha…
Jun 4, 2026
1c21a20
merge: PR #422 - Add deploy-pr-review.yml workflow for branch protect…
Jun 4, 2026
eab8fe0
fix(ci): strip trailing whitespace from deploy-pr-review workflows (#…
don-petry Jun 6, 2026
dbfac12
feat(token-report): org-wide weekly Token Cost Observatory report (#456)
don-petry Jun 7, 2026
d8e02e0
fix(engine): handle gemini quota exhaustion and capture stderr (#226)
don-petry Jun 8, 2026
128ff46
feat: implement issue #396 — Compliance: non-stub-dependency-audit.ym…
don-petry Jun 8, 2026
6402d4e
feat: implement issue #438 — Compliance: unpinned-actions-deploy-pr-r…
don-petry Jun 8, 2026
99d8d9b
feat: implement issue #404 — PR Review Agent — failures detected 2026…
don-petry Jun 8, 2026
ee7e18b
feat: implement issue #511 — [Fleet Monitor] petry-projects/.github-p…
don-petry Jun 10, 2026
30baa1c
feat: implement issue #512 — [Fleet Monitor] petry-projects/.github-p…
don-petry Jun 10, 2026
5629058
feat: implement issue #395 — Compliance: non-stub-dev-lead.yml (#488)
don-petry Jun 10, 2026
87d7544
chore: retire deploy-pr-review, force-deploy, and pr-review-reusable …
don-petry Jun 10, 2026
72ad6b2
feat: implement issue #545 — Compliance: non-stub-dependency-audit.ym…
don-petry Jun 10, 2026
f1de74d
chore(frameworks): vendor BMAD Method v6.8.0 + Test Architect v1.19.0…
don-petry Jun 11, 2026
b1a88b4
feat: implement issue #244 — [Fleet Monitor] petry-projects/.github-p…
don-petry Jun 11, 2026
44c56fa
feat: implement issue #574 — pr-review: PRs with >300 changed files h…
don-petry Jun 12, 2026
a34a2aa
feat: implement issue #143 — Compliance: secret_scanning_non_provider…
donpetry-bot Jun 9, 2026
94bc185
fix(reviews): address review comments [skip ci-relay]
donpetry-bot Jun 9, 2026
ce60e9d
fix: prevent strict shell options from polluting test runner context
donpetry-bot Jun 13, 2026
012927f
chore: apply manual instructions [skip ci-relay]
donpetry-bot Jun 13, 2026
b644598
chore(deps): bump the actions group across 1 directory with 8 updates…
dependabot[bot] Jun 13, 2026
afa4b06
fix(initiative-planner): restore discussion→workflow_dispatch redispa…
don-petry Jun 13, 2026
c9d39b3
chore: apply manual instructions [skip ci-relay]
donpetry-bot Jun 14, 2026
2d09e14
fix(test-apply-repo-settings): prevent gh mock from hanging on TTY stdin
donpetry-bot Jun 14, 2026
a2f8eb1
Merge branch 'main' into dev-lead/issue-143-20260609-2057
don-petry Jun 14, 2026
0521cba
fix(test-apply-repo-settings): add TTY check to inline gh stub
donpetry-bot Jun 14, 2026
d23e458
Merge branch 'main' into dev-lead/issue-143-20260609-2057
don-petry Jun 14, 2026
152cd90
Merge branch 'main' into dev-lead/issue-143-20260609-2057
don-petry Jun 14, 2026
b9bc3a1
Merge branch 'main' into dev-lead/issue-143-20260609-2057
don-petry Jun 15, 2026
855e04c
Merge branch 'main' into dev-lead/issue-143-20260609-2057
don-petry Jun 15, 2026
1465ec3
chore: apply manual instructions [skip ci-relay]
donpetry-bot Jun 15, 2026
741d6e7
Merge branch 'main' into dev-lead/issue-143-20260609-2057
don-petry Jun 15, 2026
1b2300a
Merge branch 'main' into dev-lead/issue-143-20260609-2057
don-petry Jun 15, 2026
3f8bba9
Merge branch 'main' into dev-lead/issue-143-20260609-2057
don-petry Jun 15, 2026
7bf98f1
Merge branch 'main' into dev-lead/issue-143-20260609-2057
don-petry Jun 15, 2026
4668943
Merge branch 'main' into dev-lead/issue-143-20260609-2057
don-petry Jun 15, 2026
74d8485
Merge branch 'main' into dev-lead/issue-143-20260609-2057
don-petry Jun 15, 2026
3655e3c
fix(bot): address bot feedback [skip ci-relay]
donpetry-bot Jun 15, 2026
b501c6b
Merge branch 'main' into dev-lead/issue-143-20260609-2057
don-petry Jun 15, 2026
338c05f
Merge branch 'main' into dev-lead/issue-143-20260609-2057
don-petry Jun 15, 2026
8d11985
Merge branch 'main' into dev-lead/issue-143-20260609-2057
don-petry Jun 15, 2026
682b333
chore: apply manual instructions [skip ci-relay]
donpetry-bot Jun 15, 2026
97529a5
Merge branch 'main' into dev-lead/issue-143-20260609-2057
don-petry Jun 15, 2026
963c07f
Merge branch 'main' into dev-lead/issue-143-20260609-2057
don-petry Jun 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 78 additions & 33 deletions evals/validate-cases.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,20 @@
#!/usr/bin/env python3
"""Validate held-out eval cases under evals/<skill>/{dev,holdout}/cases.jsonl.
"""Validate held-out eval cases.

This enforces the held-out-hygiene contract (#691, epic #581): the proposer only
ever sees the *dev* split, and the gate scores against the *holdout* split. For
that to mean anything, a case must never live in both splits — otherwise the
proposer could see (and overfit to) a case the gate later scores against.
Two modes, selected by the type of the argument:

Per skill directory under the eval root, this checks:
File mode: validate-cases.py <cases.jsonl> [schema.json]
Validates a single JSONL file against case.schema.json (JSON-Schema
2020-12). Checks per-case schema compliance and uniqueness of case ids
within the file. Outputs "cases OK: N case(s)...".

- both a dev/ and a holdout/ split exist (each with a cases.jsonl)
- every non-blank line is a JSON object
- every case has a non-empty string `id`
- ids are unique within each split
- ids are disjoint across the two splits (no case appears in both)
Directory mode: validate-cases.py <eval_root>
Validates the dev/holdout split hygiene for every skill directory under
eval_root. Per skill: both dev/ and holdout/ splits must exist, every
case must have a non-empty unique id, and ids must be disjoint across
splits. Outputs "OK: N skill(s)...".

This is data/structure/validation only — it never invokes a scorer or a model
(consistent with #582). The case payload schema beyond `id` is owned by the
scorer story (#583); this validator is deliberately agnostic to it.

Usage: validate-cases.py [eval_root]
eval_root defaults to the directory containing this script (the evals/ tree).
Exit 0 on success; non-zero with a message on failure.
Exit 0 on success; non-zero with a diagnostic on failure.
"""
import json
import sys
Expand All @@ -32,18 +26,57 @@


def fail(msg: str) -> NoReturn:
# Write to stderr so the reason is visible in workflow logs and capturable
# by tests; the ::error:: prefix still renders as a GitHub annotation.
print(f"::error::eval cases invalid: {msg}", file=sys.stderr)
sys.exit(1)


def load_split_ids(cases_path: Path) -> list[str]:
"""Parse a cases.jsonl file and return its ordered list of case ids.
# ── File mode: schema + id-uniqueness validation ──────────────────────────────

def validate_file(cases_path: Path, schema_path: Path) -> None:
try:
import jsonschema
except ImportError:
fail("jsonschema not installed (pip install 'jsonschema>=4')")

try:
schema = json.loads(schema_path.read_text(encoding="utf-8"))
except (OSError, json.JSONDecodeError) as exc:
fail(f"could not read/parse schema {schema_path}: {exc}")

try:
raw = cases_path.read_text(encoding="utf-8")
except OSError as exc:
fail(f"could not read {cases_path}: {exc}")

validator = jsonschema.Draft202012Validator(schema)
ids: dict[str, int] = {}
count = 0
for lineno, line in enumerate(raw.splitlines(), start=1):
if not line.strip():
continue
try:
case = json.loads(line)
except json.JSONDecodeError as exc:
fail(f"line {lineno}: not valid JSON: {exc}")
error = next(validator.iter_errors(case), None)
if error is not None:
loc = "/".join(str(p) for p in error.absolute_path) or "<root>"
fail(f"line {lineno}: schema violation at {loc}: {error.message}")
case_id = case["id"]
if case_id in ids:
fail(f"line {lineno}: duplicate case id '{case_id}' (first seen on line {ids[case_id]})")
ids[case_id] = lineno
count += 1

if count == 0:
fail(f"{cases_path} contains no cases")

Fails (exits non-zero) on malformed JSON, non-object lines, missing/empty
ids, or duplicate ids within this single file.
"""
print(f"cases OK: {count} case(s), unique ids, schema-valid.")


# ── Directory mode: dev/holdout split hygiene ─────────────────────────────────

def load_split_ids(cases_path: Path) -> list[str]:
try:
raw = cases_path.read_text(encoding="utf-8")
except OSError as exc:
Expand All @@ -53,7 +86,7 @@ def load_split_ids(cases_path: Path) -> list[str]:
seen: set[str] = set()
for lineno, line in enumerate(raw.splitlines(), start=1):
if not line.strip():
continue # blank lines are allowed as separators
continue
try:
case = json.loads(line)
except json.JSONDecodeError as exc:
Expand All @@ -73,7 +106,6 @@ def load_split_ids(cases_path: Path) -> list[str]:


def validate_skill(skill_dir: Path) -> int:
"""Validate one skill's dev/holdout split. Returns the total case count."""
split_ids: dict[str, list[str]] = {}
for split in SPLITS:
cases_path = skill_dir / split / CASES_FILENAME
Expand All @@ -92,7 +124,6 @@ def validate_skill(skill_dir: Path) -> int:


def discover_skills(eval_root: Path) -> list[Path]:
"""A skill is any direct subdirectory that has a dev/ or holdout/ split."""
skills = []
for child in sorted(eval_root.iterdir()):
if not child.is_dir():
Expand All @@ -102,11 +133,7 @@ def discover_skills(eval_root: Path) -> list[Path]:
return skills


def main() -> None:
eval_root = Path(sys.argv[1]) if len(sys.argv) > 1 else Path(__file__).parent
if not eval_root.is_dir():
fail(f"eval root is not a directory: {eval_root}")

def validate_directory(eval_root: Path) -> None:
skills = discover_skills(eval_root)
if not skills:
fail(f"no skills found under {eval_root} "
Expand All @@ -120,5 +147,23 @@ def main() -> None:
f"no cross-split id overlap.")


# ── Entry point ───────────────────────────────────────────────────────────────

def main() -> None:
if len(sys.argv) < 2:
validate_directory(Path(__file__).parent)
return

arg = Path(sys.argv[1])
if arg.is_file():
schema_path = (Path(sys.argv[2]) if len(sys.argv) > 2
else Path(__file__).with_name("case.schema.json"))
validate_file(arg, schema_path)
elif arg.is_dir():
validate_directory(arg)
else:
fail(f"argument is not a file or directory: {arg}")


if __name__ == "__main__":
main()
13 changes: 13 additions & 0 deletions prompts/dev-lead/fix-bot-comment.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,19 @@ If `${ACTOR}` is `sonarqubecloud[bot]` and the comment reports security hotspots
3. Fix each identified hotspot — for `curl | bash` patterns, replace with a safer alternative such as a pinned binary download with SHA verification, `gh extension install <owner>/<repo>`, or a package manager install
4. If no hotspot is found in changed files, read any newly introduced shell scripts or workflow YAML steps for the patterns above

## SonarQube / SonarCloud comments

If `${ACTOR}` is `sonarqubecloud[bot]` and the comment reports security hotspots or ratings **without referencing specific files or line numbers**, the SonarCloud dashboard link is not browsable — you must infer the hotspot from the PR's changed files:

1. Run `gh pr diff ${PR_NUMBER} --repo ${REPO}` to list all changed files and their diffs
2. Scan changed files for these known SonarQube hotspot patterns (in descending severity):
- **Script injection (S4830 / RSPEC-4830):** `curl … | bash`, `curl … | sh`, `wget … | bash` — replace with a pinned, checksum-verified install or `gh extension install`
- **Hardcoded credentials:** tokens, passwords, or API keys in source files
- **Dynamic code execution:** `eval`, `exec` with user-controlled input
- **Insecure download:** HTTP (non-HTTPS) URLs used to fetch scripts or packages
3. Fix each identified hotspot — for `curl | bash` patterns, replace with a safer alternative such as a pinned binary download with SHA verification, `gh extension install <owner>/<repo>`, or a package manager install
4. If no hotspot is found in changed files, read any newly introduced shell scripts or workflow YAML steps for the patterns above

## Constraints

- Only fix issues that are clearly actionable from the bot's output
Expand Down
21 changes: 21 additions & 0 deletions prompts/dev-lead/fix-ci.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,27 @@ Read every changed line as if you are a reviewer:
4. Ask: is there a simpler fix that achieves the same result with less risk?
5. Fix anything found, then re-run Phase 3

### External quality gate (SonarCloud, CodeQL, etc.)

If **Failure Logs** begins with `# External quality gate`, this check is not a GitHub Actions workflow — it is an external service that reported a quality gate failure. In this case:

- **Annotations** contains the specific lines and rule messages flagged by the gate — read it carefully
- If **Annotations** is empty, **Failure Logs** contains the PR diff to identify what the gate likely flagged
- For **SonarQube / SonarCloud**, note: `# NOSONAR` suppresses Bugs/Code Smells but **not Security Hotspots** — hotspots require code changes or UI acknowledgment in SonarCloud

Common SonarCloud Security Hotspot patterns to look for in changed files:

| Pattern | Typical fix |
|---|---|
| `curl … \| bash` / `wget … \| sh` | Replace with pinned download + verify checksum, or `gh extension install` |
| Hardcoded credentials / API keys | Move to secrets / env vars |
| `eval` / `exec` with dynamic input | Remove dynamic execution or sanitize input |
| HTTP (non-HTTPS) download URLs | Change to `https://` |
| `npm install` without `--ignore-scripts` | Add `--ignore-scripts` if install scripts are not required; otherwise, this may require manual acknowledgment in the SonarCloud UI |
| `npm install pkg@variable` or `@latest` | Pin to an exact version number (e.g. `pkg@1.2.3`), or exclude the file in `sonar-project.properties` if version is intentionally managed via a CI variable |

- Fix each identified issue.

## Constraints

- Fix only what is broken — do not refactor unrelated code
Expand Down
48 changes: 32 additions & 16 deletions scripts/apply-repo-settings.sh
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
#!/usr/bin/env bash
# apply-repo-settings.sh — apply standard GitHub repository settings.
#
# Disables check-suite auto-trigger for apps that queue suites on every push
# without completing them, which permanently blocks auto-merge.
# Applies security_and_analysis settings and disables check-suite auto-trigger
# for apps that queue suites on every push without completing them, which
# permanently blocks auto-merge.
#
# Usage:
# bash scripts/apply-repo-settings.sh <repo-name>
# bash scripts/apply-repo-settings.sh owner/repo
# REPO=owner/repo bash scripts/apply-repo-settings.sh
# bash scripts/apply-repo-settings.sh --all
#
# Environment:
Expand All @@ -14,13 +16,23 @@
# ORG — GitHub org (default: petry-projects)
# DEV_LEAD_DRY_RUN — if "true", print intent but make no API calls
#
# Standard:
# Standards:
# https://github.com/petry-projects/.github/blob/main/standards/push-protection.md
# https://github.com/petry-projects/.github/blob/main/standards/github-settings.md#check-suite-auto-trigger-preferences

set -euo pipefail

ORG="${ORG:-petry-projects}"

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

# Lazy-load push-protection.sh to avoid polluting test environments with its strict shell options
_ensure_push_protection_sourced() {
if [ -z "${_push_protection_sourced:-}" ]; then
# shellcheck source=scripts/lib/push-protection.sh
source "${SCRIPT_DIR}/lib/push-protection.sh"
_push_protection_sourced=1
fi
}

# Apps whose check-suite auto-trigger must be disabled.
# GitHub creates a queued suite on every push when auto-trigger is on; those
# suites are never completed by these apps, permanently blocking auto-merge.
Expand Down Expand Up @@ -83,8 +95,9 @@ rs_apply_repo() {
--input - <<<"$payload"
}

# rs_apply_all — apply settings to every repo in ORG.
# rs_apply_all — apply all settings to every repo in ORG.
rs_apply_all() {
_ensure_push_protection_sourced
local repos
if ! repos=$(gh repo list "${ORG}" --json name --jq '.[].name' --limit 1000); then
echo "[error] failed to list repositories for org ${ORG}" >&2
Expand All @@ -93,21 +106,24 @@ rs_apply_all() {

local repo_name
while IFS= read -r repo_name; do
[ -n "$repo_name" ] && rs_apply_repo "${ORG}/${repo_name}"
if [ -n "$repo_name" ]; then
REPO="${ORG}/${repo_name}" pp_apply_security_and_analysis
rs_apply_repo "${ORG}/${repo_name}"
fi
done <<< "$repos"
}

# Run main only when executed directly, so tests can source the helpers.
if [ "${BASH_SOURCE[0]:-$0}" = "$0" ]; then
if [ $# -eq 0 ]; then
echo "Usage: $0 <repo-name> | --all" >&2
echo " $0 .github-private" >&2
echo " $0 --all" >&2
exit 1
fi
if [ "$1" = "--all" ]; then
set -euo pipefail
_ensure_push_protection_sourced
if [ "${1:-}" = "--all" ]; then
rs_apply_all
else
rs_apply_repo "${ORG}/$1"
REPO="${1:-${REPO:-}}"
: "${REPO:?Usage: $0 owner/repo | --all OR REPO=owner/repo $0}"
export REPO
pp_apply_security_and_analysis
rs_apply_repo "$REPO"
fi
fi
2 changes: 1 addition & 1 deletion scripts/dev-lead-fix-reviews.sh
Original file line number Diff line number Diff line change
Expand Up @@ -994,7 +994,7 @@ case "$INTENT_TYPE" in
fi
exit "$rc"
;;
review-changes)
human-pr|review-changes)
export PR_NUMBER="${PR_NUMBER:-}"
export PR_URL="https://github.com/${REPO}/pull/${PR_NUMBER}"
# ACTOR is exported so resolve_actor_outdated_threads can scrub outdated
Expand Down
71 changes: 71 additions & 0 deletions scripts/engine.sh
Original file line number Diff line number Diff line change
Expand Up @@ -1047,6 +1047,40 @@ sys.exit(1)
" "$raw" > "$dest" 2>/dev/null
}

# extract_verdict_json <raw_file> <dest_file>
# Resolves the verdict JSON from an agentic run, handling two output styles:
# 1. Agent wrote JSON to $dest via Bash tool (dest already valid — use it as-is).
# 2. Agent printed JSON to stdout captured in raw_file (scan for first valid
# JSON object containing a 'decision' field, ignoring preamble text).
extract_verdict_json() {
local raw="$1" dest="$2"
# Style 1: agent wrote to $dest via Bash tool (our stdout redirect didn't clobber it).
if jq empty "$dest" 2>/dev/null; then
return 0
fi
# Style 2: agent printed JSON to stdout.
if jq empty "$raw" 2>/dev/null; then
cp "$raw" "$dest"
return 0
fi
python3 -c "
import sys, json
text = open(sys.argv[1]).read()
decoder = json.JSONDecoder()
pos = text.find('{')
while pos >= 0:
try:
obj, _ = decoder.raw_decode(text, pos)
if isinstance(obj, dict) and 'decision' in obj:
print(json.dumps(obj))
sys.exit(0)
except Exception:
pass
pos = text.find('{', pos + 1)
sys.exit(1)
" "$raw" > "$dest" 2>/dev/null
}

# run_duck <prompt_file> <model>
# Used by: review-one-pr.sh only (not the dev-lead writer pipeline).
# Cross-engine adversarial "rubber duck" review.
Expand Down Expand Up @@ -1394,3 +1428,40 @@ run_writer_with_fallback() {
[ "$any_rate_limited" -eq 1 ] && return 2
return 1
}

# parse_reset_time <text>
# Extracts the rate-limit reset time from engine output and writes an ISO-8601
# UTC timestamp to /tmp/dev-lead-rate-limit-reset for callers to embed in
# status=rate-limited markers. Pattern: "resets H:MMam/pm (UTC)" or
# "resets H:MM(am|pm) UTC".
# Writes empty string if no reset time is found (caller treats as unknown).
parse_reset_time() {
local text="$1"
# Match "resets 11:20pm (UTC)" or "resets 11:20pm UTC"
local time_str
time_str=$(printf '%s\n' "$text" | grep -oiE 'resets [0-9]{1,2}:[0-9]{2}(am|pm)' | head -1 || true)
if [ -z "$time_str" ]; then
printf '' > /tmp/dev-lead-rate-limit-reset
return 0
fi
# Extract H:MM(am|pm) part
local hhmm
hhmm=$(printf '%s' "$time_str" | grep -oiE '[0-9]{1,2}:[0-9]{2}(am|pm)$' || true)
if [ -z "$hhmm" ]; then
printf '' > /tmp/dev-lead-rate-limit-reset
return 0
fi
# Convert to ISO-8601 UTC using today's date (rate limits reset within 24h)
local today
today=$(date -u +%Y-%m-%d)
local iso
iso=$(date -u -d "${today} ${hhmm} UTC" +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || true)
# If reset time is in the past (already reset today), it means tomorrow
if [ -n "$iso" ] && [ "$(date -u +%s)" -gt "$(date -u -d "$iso" +%s 2>/dev/null || echo 0)" ]; then
local tomorrow
tomorrow=$(date -u -d "tomorrow" +%Y-%m-%d)
iso=$(date -u -d "${tomorrow} ${hhmm} UTC" +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || true)
fi
printf '%s' "${iso:-}" > /tmp/dev-lead-rate-limit-reset
}

Loading
Loading