=== RUN 2026-06-24T17:15:10-05:00 === ===== monorepo pr-review-shared/lib ===== total 80 drwxrwxr-x 3 claude claude 4096 Jun 24 17:12 . drwxrwxr-x 4 claude claude 4096 Jun 23 05:39 .. -rw-rw-r-- 1 claude claude 5893 Jun 24 17:06 ask-ai.js -rw-rw-r-- 1 claude claude 248 Jun 1 01:50 build-diff.js -rw-rw-r-- 1 claude claude 800 Jun 1 01:50 build-prompt.js -rw-rw-r-- 1 claude claude 3608 Jun 1 01:50 build-prompt-v2.js -rw-rw-r-- 1 claude claude 2214 Jun 1 01:50 check-resolution.js -rw-rw-r-- 1 claude claude 1073 Jun 24 17:06 dismiss-stale-reviews.js -rw-rw-r-- 1 claude claude 472 Jun 24 17:06 filter-files.js -rw-rw-r-- 1 claude claude 481 Jun 24 17:06 list-files.js -rw-rw-r-- 1 claude claude 8034 Jun 24 17:12 parse-response.js -rw-rw-r-- 1 claude claude 2860 Jun 1 01:50 post-review.js -rw-rw-r-- 1 claude claude 2296 Jun 24 17:06 re-review.js -rw-rw-r-- 1 claude claude 8682 Jun 24 17:06 review-handler.js drwxrwxr-x 2 claude claude 4096 Jun 24 17:11 __tests__ -rw-rw-r-- 1 claude claude 894 Jun 24 17:06 validate-hunks.js ===== diff deploy vs monorepo parse-response.js ===== IDENTICAL ===== diff deploy vs monorepo review-handler.js ===== 1,11c1,16 < import { filterFiles } from './filter-files.js'; < import { buildDiff } from './build-diff.js'; < import { buildPromptV2, fetchCodebaseContext, extractPlanLink } from './build-prompt-v2.js'; < import { validateComments } from './validate-hunks.js'; < import { parseReviewWithRepair } from './parse-response.js'; < import { askAI } from './ask-ai.js'; < import { postStartedReview, postFullReview, updateStartedReviewOnError, updateStartedReviewNoFiles } from './post-review.js'; < import { listAllFiles } from './list-files.js'; < import { dismissOwnStaleReview } from './dismiss-stale-reviews.js'; < import { checkResolution } from './check-resolution.js'; < import { buildReReviewPrompt, fetchPriorReview } from './re-review.js'; --- > import { askAI } from "./ask-ai.js"; > import { buildDiff } from "./build-diff.js"; > import { buildPromptV2, fetchCodebaseContext, extractPlanLink } from "./build-prompt-v2.js"; > import { checkResolution } from "./check-resolution.js"; > import { dismissOwnStaleReview } from "./dismiss-stale-reviews.js"; > import { filterFiles } from "./filter-files.js"; > import { listAllFiles } from "./list-files.js"; > import { parseReviewWithRepair } from "./parse-response.js"; > import { > postStartedReview, > postFullReview, > updateStartedReviewOnError, > updateStartedReviewNoFiles, > } from "./post-review.js"; > import { buildReReviewPrompt, fetchPriorReview } from "./re-review.js"; > import { validateComments } from "./validate-hunks.js"; 31c36,38 < if (pr.state !== 'open') return; --- > if (pr.state !== "open") { > return; > } 33,34c40,44 < const { owner: { login: owner }, name: repo } = context.payload.repository; < const isSynchronize = context.payload.action === 'synchronize'; --- > const { > owner: { login: owner }, > name: repo, > } = context.payload.repository; > const isSynchronize = context.payload.action === "synchronize"; 37,40c47,50 < // Repo-context guard (#804): the repo under review is ALWAYS the one that < // fired the webhook (context.payload.repository), at the PR head SHA. < // Assert it agrees with the PR's base repo before doing anything, so we < // never review a diff against the wrong tree. Mismatch => log + abort. --- > // Repo-context guard: the repo under review is ALWAYS the one that fired > // the webhook (context.payload.repository), at the PR head SHA. Assert it > // agrees with the PR's base repo before doing anything, so we never review > // a diff against the wrong tree. Mismatch => log + abort (don't review). 51c61 < `Repo under review: ${webhookRepo} @ ${prHeadSha ?? 'unknown-sha'} (head: ${pr.head?.repo?.full_name ?? '?'})`, --- > `Repo under review: ${webhookRepo} @ ${prHeadSha ?? "unknown-sha"} (head: ${pr.head?.repo?.full_name ?? "?"})`, 60,61c70,76 < priorReviewData = await fetchPriorReview(context.octokit, { owner, repo, pull_number: pr.number, botName }); < if (priorReviewData?.state === 'CHANGES_REQUESTED') { --- > priorReviewData = await fetchPriorReview(context.octokit, { > owner, > repo, > pull_number: pr.number, > botName, > }); > if (priorReviewData?.state === "CHANGES_REQUESTED") { 65c80,85 < await dismissOwnStaleReview(context.octokit, { owner, repo, pull_number: pr.number, botName }); --- > await dismissOwnStaleReview(context.octokit, { > owner, > repo, > pull_number: pr.number, > botName, > }); 68c88,94 < startedReviewId = await postStartedReview(context.octokit, { owner, repo, pull_number: pr.number, botName, isReReview }); --- > startedReviewId = await postStartedReview(context.octokit, { > owner, > repo, > pull_number: pr.number, > botName, > isReReview, > }); 70c96,100 < const prFiles = await listAllFiles(context.octokit, { owner, repo, pull_number: pr.number }); --- > const prFiles = await listAllFiles(context.octokit, { > owner, > repo, > pull_number: pr.number, > }); 74c104,110 < await updateStartedReviewNoFiles(context.octokit, { owner, repo, pull_number: pr.number, reviewId: startedReviewId, botName }); --- > await updateStartedReviewNoFiles(context.octokit, { > owner, > repo, > pull_number: pr.number, > reviewId: startedReviewId, > botName, > }); 83,84c119,127 < prompt = buildReReviewPrompt({ prNumber: pr.number, prTitle: pr.title, diffText: diff, priorReview: priorReviewData }); < app.log.info(`Re-reviewing PR #${pr.number} against ${priorReviewData.comments.length} prior comments`); --- > prompt = buildReReviewPrompt({ > prNumber: pr.number, > prTitle: pr.title, > diffText: diff, > priorReview: priorReviewData, > }); > app.log.info( > `Re-reviewing PR #${pr.number} against ${priorReviewData.comments.length} prior comments`, > ); 91c134,141 < prompt = buildPromptV2({ prNumber: pr.number, prTitle: pr.title, prBody: pr.body, diffText: diff, codebaseContext, planLink }); --- > prompt = buildPromptV2({ > prNumber: pr.number, > prTitle: pr.title, > prBody: pr.body, > diffText: diff, > codebaseContext, > planLink, > }); 94,96c144,146 < // Empty-diff guard (#804): the prompt must contain the diff we fetched < // from THIS PR. If the diff is empty the CLI would be tempted to inspect < // its own working dir, so refuse rather than review the wrong tree. --- > // Final guard before spending a model call: the prompt must contain the > // diff we fetched from THIS PR. If the diff is empty the CLI would be > // tempted to inspect its own working dir, so refuse rather than review. 101c151,157 < await updateStartedReviewNoFiles(context.octokit, { owner, repo, pull_number: pr.number, reviewId: startedReviewId, botName }); --- > await updateStartedReviewNoFiles(context.octokit, { > owner, > repo, > pull_number: pr.number, > reviewId: startedReviewId, > botName, > }); 105c161,163 < app.log.info(`Invoking ${botName} CLI for PR #${pr.number} in ${webhookRepo} (${prompt.length} chars)...`); --- > app.log.info( > `Invoking ${botName} CLI for PR #${pr.number} in ${webhookRepo} (${prompt.length} chars)...`, > ); 114,117c172,180 < // Check thread resolution before posting < const resolution = await checkResolution(context.octokit, { owner, repo, pull_number: pr.number }); < let verdict = result.verdict || 'request_changes'; < let summaryAppendix = ''; --- > // Check only THIS bot's unresolved threads (not other bots') > const resolution = await checkResolution(context.octokit, { > owner, > repo, > pull_number: pr.number, > botName, > }); > let verdict = result.verdict || "request_changes"; > let summaryAppendix = ""; 119,120c182,183 < if (resolution.unresolved > 0 && verdict === 'approve') { < verdict = 'request_changes'; --- > if (resolution.unresolved > 0 && verdict === "approve") { > verdict = "request_changes"; 137c200,203 < owner, repo, pull_number: pr.number, botName, --- > owner, > repo, > pull_number: pr.number, > botName, 143c209,233 < app.log.info(`Review submitted for PR #${pr.number} (${validComments.length} inline comments, verdict: ${verdict})`); --- > app.log.info( > `Review submitted for PR #${pr.number} (${validComments.length} inline comments, verdict: ${verdict})`, > ); > > // Notify the PR author's agent via tmux (best-effort) > try { > const author = pr.user?.login ?? ""; > const agentPane = { > gbharg: "mbp:codex.1", > "gautam-claude": "mbp:claude.1", > "gautam-gemini": "mbp:gemini.1", > }[author]; > if (agentPane) { > const msg = `[${botName}] Review posted on PR #${pr.number}: ${verdict} (${validComments.length} comments)`; > const { execFile } = await import("node:child_process"); > execFile( > "tmux", > ["send-keys", "-t", agentPane, msg, "Enter"], > { timeout: 3000 }, > () => {}, > ); > } > } catch { > /* best-effort notification */ > } 146c236,243 < await updateStartedReviewOnError(context.octokit, { owner, repo, pull_number: pr.number, reviewId: startedReviewId, botName, error }); --- > await updateStartedReviewOnError(context.octokit, { > owner, > repo, > pull_number: pr.number, > reviewId: startedReviewId, > botName, > error, > }); DIFFERS ===== diff deploy vs monorepo build-prompt-v2.js ===== IDENTICAL ===== monorepo git status ===== ## feat/ai-codex-json-parse-robust ===== monorepo current branch ===== feat/ai-codex-json-parse-robust ===== monorepo recent log ===== 903c83b1 fix(pr-review): robustly extract JSON from codex CLI transcript output 68593f2b chore: sync iMac local utility files f1a1c0c5 chore: satisfy repository lint checks 81b5c1f8 chore: mark tmux helper scripts executable b117f44b chore(memory): trim daily snapshot EOF blanks 6afffe9d chore: ignore macOS trash directory 095ab91d chore(memory): add June dedup snapshots c881b551 chore: sync local OpenClaw operations state ===== tests dir ===== total 84 drwxrwxr-x 4 claude claude 4096 Jun 23 05:39 . drwxrwxr-x 67 claude claude 12288 Jun 24 17:06 .. drwxrwxr-x 3 claude claude 4096 Jun 24 17:12 lib drwxrwxr-x 47 claude claude 4096 Jun 23 05:39 node_modules -rw-rw-r-- 1 claude claude 253 Jun 1 01:50 package.json -rw-rw-r-- 1 claude claude 50993 Jun 23 05:39 package-lock.json -rw-rw-r-- 1 claude claude 141 Jun 1 01:50 vitest.config.js /home/claude/repos/gbharg-agents/pr-review-shared/lib/__tests__/build-prompt-v2.test.js /home/claude/repos/gbharg-agents/pr-review-shared/lib/__tests__/post-review.test.js /home/claude/repos/gbharg-agents/pr-review-shared/lib/__tests__/check-resolution.test.js /home/claude/repos/gbharg-agents/pr-review-shared/lib/__tests__/list-files.test.js /home/claude/repos/gbharg-agents/pr-review-shared/lib/__tests__/re-review.test.js /home/claude/repos/gbharg-agents/pr-review-shared/lib/__tests__/review-handler.test.js /home/claude/repos/gbharg-agents/pr-review-shared/lib/__tests__/ask-ai.test.js /home/claude/repos/gbharg-agents/pr-review-shared/lib/__tests__/parse-response.test.js /home/claude/repos/gbharg-agents/pr-review-shared/lib/__tests__/dismiss-stale-reviews.test.js /home/claude/repos/gbharg-agents/pr-review-shared/lib/__tests__/filter-files.test.js /home/claude/repos/gbharg-agents/pr-review-shared/lib/__tests__/build-diff.test.js /home/claude/repos/gbharg-agents/pr-review-shared/lib/__tests__/validate-hunks.test.js === EXIT 0 ===