Skip to content

Add Conley (1999) spatial HAC SE on DiD/TWFE/MultiPeriodDiD (Phase 1 of spillover-conley) #1595

Add Conley (1999) spatial HAC SE on DiD/TWFE/MultiPeriodDiD (Phase 1 of spillover-conley)

Add Conley (1999) spatial HAC SE on DiD/TWFE/MultiPeriodDiD (Phase 1 of spillover-conley) #1595

Workflow file for this run

name: AI PR Review
on:
pull_request:
types: [opened]
issue_comment:
types: [created]
pull_request_review_comment:
types: [created]
permissions:
contents: read
pull-requests: write
issues: write
concurrency:
group: ai-pr-review-${{ github.event.pull_request.number || github.event.issue.number || github.run_id }}
cancel-in-progress: true
jobs:
review:
runs-on: ubuntu-latest
# Run if:
# - PR opened, OR
# - Comment "/ai-review" on a PR by a collaborator/member/owner (issue or inline diff comment)
if: |
(github.event_name == 'pull_request') ||
(
github.event_name == 'issue_comment' &&
github.event.issue.pull_request != null &&
startsWith(github.event.comment.body, '/ai-review') &&
(
github.event.comment.author_association == 'OWNER' ||
github.event.comment.author_association == 'MEMBER' ||
github.event.comment.author_association == 'COLLABORATOR'
)
) ||
(
github.event_name == 'pull_request_review_comment' &&
startsWith(github.event.comment.body, '/ai-review') &&
(
github.event.comment.author_association == 'OWNER' ||
github.event.comment.author_association == 'MEMBER' ||
github.event.comment.author_association == 'COLLABORATOR'
)
)
steps:
- name: Resolve PR number + metadata
id: pr
uses: actions/github-script@v9
with:
script: |
const prNumber =
context.payload.pull_request?.number ??
context.payload.issue?.number ??
context.payload.pull_request_review?.pull_request?.number ??
(() => {
const url = context.payload.pull_request_url;
if (!url) return null;
const m = url.match(/\/pulls\/(\d+)$/);
return m ? Number(m[1]) : null;
})();
if (!prNumber) {
throw new Error("Could not determine PR number from event payload");
}
const { owner, repo } = context.repo;
const pr = await github.rest.pulls.get({ owner, repo, pull_number: prNumber });
core.setOutput("number", prNumber);
core.setOutput("title", pr.data.title || "");
core.setOutput("body", pr.data.body || "");
core.setOutput("base_sha", pr.data.base.sha);
core.setOutput("head_sha", pr.data.head.sha);
core.setOutput("base_ref", pr.data.base.ref);
core.setOutput("head_ref", pr.data.head.ref);
- uses: actions/checkout@v6
with:
ref: refs/pull/${{ steps.pr.outputs.number }}/merge
- name: Pre-fetch base and head refs
run: |
set -euo pipefail
git fetch --no-tags origin \
"${{ steps.pr.outputs.base_ref }}" \
+refs/pull/${{ steps.pr.outputs.number }}/head
- name: Fetch previous AI review (if any)
id: prev_review
uses: actions/github-script@v9
with:
script: |
const { owner, repo } = context.repo;
const issue_number = Number('${{ steps.pr.outputs.number }}');
const comments = await github.paginate(github.rest.issues.listComments, {
owner, repo, issue_number, per_page: 100,
});
const aiComments = comments.filter(c =>
(c.body || "").includes("<!-- ai-pr-review:codex:") &&
c.user?.login === "github-actions[bot]"
);
const last = aiComments.length > 0 ? aiComments[aiComments.length - 1] : null;
core.setOutput("body", last ? last.body : "");
core.setOutput("found", last ? "true" : "false");
- name: Build review inputs (diff + previous review)
env:
BASE_SHA: ${{ steps.pr.outputs.base_sha }}
HEAD_SHA: ${{ steps.pr.outputs.head_sha }}
IS_RERUN: ${{ github.event_name == 'issue_comment' || github.event_name == 'pull_request_review_comment' }}
PREV_REVIEW: ${{ steps.prev_review.outputs.body }}
PREV_REVIEW_FOUND: ${{ steps.prev_review.outputs.found }}
run: |
set -euo pipefail
# Exclude large generated/data files from the full diff to stay
# within the model's input limit. The --name-status output still
# lists them. Narrowed to real-data assets and notebook outputs.
git --no-pager diff --unified=5 "$BASE_SHA" "$HEAD_SHA" \
-- . ':!benchmarks/data/real/*.json' ':!benchmarks/data/real/*.csv' \
':!docs/tutorials/*.ipynb' \
> /tmp/pr-diff.patch
git --no-pager diff --name-status "$BASE_SHA" "$HEAD_SHA" \
> /tmp/pr-files.txt
if [ "$IS_RERUN" = "true" ] && [ "$PREV_REVIEW_FOUND" = "true" ]; then
printf '%s\n' "$PREV_REVIEW" > /tmp/previous-review.md
fi
- name: Run AI review (single-shot Responses API)
id: run_review
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
BRANCH: ${{ steps.pr.outputs.head_ref }}
PR_TITLE: ${{ steps.pr.outputs.title }}
PR_BODY: ${{ steps.pr.outputs.body }}
IS_RERUN: ${{ github.event_name == 'issue_comment' || github.event_name == 'pull_request_review_comment' }}
run: |
set -euo pipefail
# Pin --model gpt-5.5 explicitly so future bumps to the script's
# DEFAULT_MODEL don't silently ship to CI without review.
# Use --key=value form for untrusted PR title/body so argparse
# cannot misinterpret an option-looking value (e.g. a PR body
# starting with "--") as a separate flag and break the job.
ARGS=(--ci-mode --full-registry --context standard --model gpt-5.5
--review-criteria .github/codex/prompts/pr_review.md
--registry docs/methodology/REGISTRY.md
--diff /tmp/pr-diff.patch
--changed-files /tmp/pr-files.txt
--output /tmp/review-output.md
--branch-info "$BRANCH"
"--pr-title=$PR_TITLE"
"--pr-body=$PR_BODY"
--repo-root "$(pwd)")
if [ "$IS_RERUN" = "true" ] && [ -f /tmp/previous-review.md ]; then
ARGS+=(--previous-review /tmp/previous-review.md)
fi
python3 .claude/scripts/openai_review.py "${ARGS[@]}"
- name: Post PR comment (new on /ai-review, update on opened)
uses: actions/github-script@v9
env:
PR_NUMBER: ${{ steps.pr.outputs.number }}
HEAD_SHA: ${{ steps.pr.outputs.head_sha }}
with:
script: |
const fs = require('fs');
let msg = '';
try {
msg = fs.readFileSync('/tmp/review-output.md', 'utf8').trim();
} catch (e) {
core.setFailed(`Could not read review output: ${e.message}`);
return;
}
if (!msg) return;
const { owner, repo } = context.repo;
const issue_number = Number(process.env.PR_NUMBER);
// If this run was triggered by /ai-review (issue_comment or review_comment), create a NEW comment.
const isRerun =
context.eventName === "issue_comment" ||
context.eventName === "pull_request_review_comment";
// Marker for the "canonical" auto comment. Kept as :codex: for
// backward compatibility with historical PR comments — the marker
// is just an identifier for the canonical auto comment, not a
// declaration of the backend.
const marker = "<!-- ai-pr-review:codex:auto -->";
// For reruns, use a unique marker so nothing ever gets overwritten
const rerunMarker = `<!-- ai-pr-review:codex:rerun:${process.env.GITHUB_RUN_ID} -->`;
const header = isRerun
? `🔁 **AI review rerun** (requested by @${context.actor})\n\n**Head SHA:** \`${process.env.HEAD_SHA}\`\n\n---\n`
: "";
const body = `${isRerun ? rerunMarker : marker}\n\n${header}${msg}`.trim();
if (isRerun) {
await github.rest.issues.createComment({ owner, repo, issue_number, body });
return;
}
// Auto run: update existing canonical comment if present
const comments = await github.paginate(github.rest.issues.listComments, {
owner, repo, issue_number, per_page: 100,
});
const existing = comments.find(c => (c.body || "").includes(marker));
if (existing) {
await github.rest.issues.updateComment({ owner, repo, comment_id: existing.id, body });
} else {
await github.rest.issues.createComment({ owner, repo, issue_number, body });
}