import type { FixPrConfig } from "./types.js"; import type { FixPrAssessmentBatch, FixPrWorkBatch } from "./contracts.js"; import { FixPrAssessmentBatchSchema } from "./contracts.js"; import { renderSchemaText } from "../ai/schema-text.js"; import { buildReceivingReviewInstructions } from "../discipline/receiving-review.js"; export interface FixPrPromptOptions { prNumber: number; repo: string; comments: string; sessionDir: string; scriptsDir: string; config: FixPrConfig; iteration: number; skillContent: string; taskModel: string; selectedTargetLabel: string; deferredCommentsSummary: string | null; assessment: FixPrAssessmentBatch; workBatches: FixPrWorkBatch[]; } function buildReplyInstructions(config: FixPrConfig): string { const { commentPolicy } = config; const replyCmd = `gh api repos/REPO/pulls/PR/comments/COMMENT_ID/replies -f body="..."`; switch (commentPolicy) { case "no-answer": return [ "### Comment Replies", "", "Policy: **Do not reply** to any comments. Focus only on fixing the code.", "Do not post any replies via gh api.", ].join("\n"); case "answer-all": return [ "### Comment Replies", "", "Policy: **Answer all** comments — both accepted and rejected.", "For each comment, post a reply explaining what was done or why it was rejected.", `Use: \`${replyCmd}\``, "Keep replies factual and technical. No performative agreement.", ].join("\n"); case "answer-selective": return [ "### Comment Replies", "", "Policy: **Answer selectively** — only reply to comments you reject or where clarification adds value.", "For ACCEPT: fix silently (the code change speaks for itself).", "For REJECT: explain why with technical reasoning.", `Use: \`${replyCmd}\``, "Keep replies factual. No performative agreement.", ].join("\n"); } } export function buildFixPrOrchestratorPrompt(options: FixPrPromptOptions): string { const { prNumber, repo, comments, sessionDir, scriptsDir, config, iteration, skillContent, taskModel, selectedTargetLabel, deferredCommentsSummary, assessment, workBatches, } = options; const { loop, reviewer } = config; const maxIter = loop.maxIterations; const delay = loop.delaySeconds; const sections: string[] = [ "# PR Review Fix Orchestration", "", `You are the orchestrator for fixing PR #${prNumber} on \`${repo}\`.`, "", "## Session Context", "", `- Session dir: \`${sessionDir}\``, `- Iteration: ${iteration} of ${maxIter}`, `- Selected target: ${selectedTargetLabel}`, `- Full PR diff: \`pr://${repo}/${prNumber}/diff/all\`; changed-file list: \`pr://${repo}/${prNumber}/diff\``, `- Comment reply policy: ${config.commentPolicy}`, `- Reviewer: ${reviewer.type}${reviewer.triggerMethod ? ` (trigger: ${reviewer.triggerMethod})` : ""}`, deferredCommentsSummary ? `- Deferred comments outside this target: ${deferredCommentsSummary}` : "- Deferred comments outside this target: none", "", "## Review Scope Rules", "", "- Process only the comments listed below for the selected target.", deferredCommentsSummary ? "- Comments outside the selected target were intentionally excluded for a separate run. Do not remediate them here." : "- There are no deferred comments outside this target in this snapshot.", "- After each wait-and-check cycle, keep enforcing the same target boundary. If new root or sibling-package comments appear, surface them explicitly and leave them for a separate run.", "", "## Review Comments to Process", "", "Each line is a JSON object with comment data:", "", "```jsonl", comments, "```", "", ]; if (skillContent) { sections.push( "## Assessment Methodology", "", skillContent, "", ); } sections.push( "## Review Discipline", "", buildReceivingReviewInstructions(), "", ); sections.push( "## Step 1: Validated Assessment Artifact", "", "The per-comment assessment for this run has already been validated against the `FixPrAssessmentBatchSchema` contract. Treat it as the source of truth: do not re-assess, do not change verdicts. Each entry has a verdict (`apply` / `reject` / `investigate`), rationale, affectedFiles, rippleEffects (downstream callers/tests/docs), and a verificationPlan.", "", "Schema:", "```ts", renderSchemaText(FixPrAssessmentBatchSchema), "```", "", "Validated artifact:", "```json", JSON.stringify(assessment, null, 2), "```", "", ); sections.push( "## Step 2: Work Batches (Parallel Execution Groups)", "", "Batches were derived deterministically from the artifact above by grouping `apply` assessments whose `affectedFiles` overlap. Independent batches may run in parallel; a batch's commentIds share at least one file and must execute together. `reject` and `investigate` verdicts produce no batch — handle those per the reply policy below.", "", "```json", JSON.stringify(workBatches, null, 2), "```", "", ); sections.push( "## Step 3: Plan Each Group", "", "For each group, create a fix plan:", "- What changes are needed and why", "- Which files to modify", "- Expected ripple effects and how to handle them", "- How to verify the fix (which tests to run)", "", ); sections.push( "## Step 4: Execute Fixes", "", "For each group:", "1. Make the code changes", "2. Run relevant tests to verify", "3. If tests fail, fix before moving on", "", ); sections.push(buildReplyInstructions(config), ""); sections.push( "## Step 6: Push, Monitor CI, and Check for New Comments", "", `1. Stage and commit: \`git add -A && git commit -m "fix: address PR review comments (iteration ${iteration})"\``, "2. Push: `git push`", ); if (reviewer.type !== "none" && reviewer.triggerMethod) { sections.push( `3. Trigger re-review: \`bun "${scriptsDir}/trigger-review.ts" "${repo}" ${prNumber} "${reviewer.type}" "${reviewer.triggerMethod}"\``, `4. While the reviewer runs, start the green pipeline: invoke OMP \`/green\` if available; otherwise run \`gh pr checks ${prNumber} --repo ${repo} --watch\`.`, " - If CI turns red, stop waiting for review comments and focus on CI.", " - Diagnose the failed check from its logs, fix the root cause, push again, then restart green monitoring.", "5. Run the wait-and-check runner:", ); } else { sections.push( `3. Start the green pipeline: invoke OMP \`/green\` if available; otherwise run \`gh pr checks ${prNumber} --repo ${repo} --watch\`.`, " - If CI turns red, focus on CI before considering the PR complete.", " - Diagnose the failed check from its logs, fix the root cause, push again, then restart green monitoring.", "4. Run the wait-and-check runner:", ); } sections.push( "```text", `bun "${scriptsDir}/wait-and-check.ts" "${sessionDir}" ${delay} ${iteration + 1} "${repo}" ${prNumber}`, "```", `${reviewer.type !== "none" ? "6" : "5"}. Read the last line of output:`, ` - If \`hasNewComments: true\` and iteration < ${maxIter}: process the new comments (go back to Step 1), then repeat push, green monitoring, and final validation`, ` - If \`hasNewComments: false\` or iteration >= ${maxIter}: continue only after CI is green`, `${reviewer.type !== "none" ? "7" : "6"}. Full validation is mandatory at the end: run \`bun ci\` locally after comments are handled and CI is green. Do not report done until both remote CI and local full validation are green.`, "", ); sections.push( "## Runner Paths", "", `- trigger-review.ts: \`${scriptsDir}/trigger-review.ts\``, `- wait-and-check.ts: \`${scriptsDir}/wait-and-check.ts\``, "", ); sections.push( "## Model Guidance", "", "- **Orchestrator** (this session): handles assessment & grouping", `- **Planner & Fixer** (sub-agents): use model \`${taskModel}\``, "", "Sub-agents inherit the task model for planning and code changes.", ); return sections.join("\n"); }