import { execFile } from "node:child_process"; import { statSync } from "node:fs"; import { promisify } from "node:util"; import type { ExtensionAPI, ExtensionCommandContext, } from "@earendil-works/pi-coding-agent"; import { VETTE_BETA_TOPICS, VetteBetaCooldown, VetteBetaDiffError, averageTopicDuration, formatResolvedModelPool, forceLocalVetteBetaConfig, formatVetteBetaSynthesisPrompt, loadTopicTimings, loadVetteBetaConfig, resolveModelPool, runVetteBetaReview, type VetteBetaReviewMode, type VetteBetaReviewTarget, } from "./vette-beta.ts"; import { formatVetteReviewPrompt, loadVetteReviewSections, } from "./vette-review.ts"; const execFileAsync = promisify(execFile); type GhAuthor = { login?: string; name?: string; is_bot?: boolean; }; type GhActivity = { author?: { login?: string; type?: string; __typename?: string }; body?: string; url?: string; createdAt?: string; updatedAt?: string; submittedAt?: string; }; type GhCheckRollup = { name?: string; workflowName?: string; workflow?: string; state?: string; status?: string; conclusion?: string; bucket?: string; }; type GhPullRequest = { number: number; url: string; title?: string; body?: string; author?: GhAuthor; headRefName?: string; headRefOid?: string; baseRefName?: string; isDraft?: boolean; state?: string; mergedAt?: string | null; mergeStateStatus?: string; reviewDecision?: string; updatedAt?: string; comments?: GhActivity[]; reviews?: GhActivity[]; latestReviews?: GhActivity[]; statusCheckRollup?: GhCheckRollup[]; }; type PrContext = { selector: string; pr: GhPullRequest; localIdentity: string; ownership: "local" | "external"; isOwner: boolean; dirtyStatus: string; }; type DraftPrContext = { branch: string; baseBranch: string; localIdentity: string; dirtyStatus: string; remoteUrl: string; }; type PrCommandContext = | { kind: "existing"; prContext: PrContext } | { kind: "draft"; draftContext: DraftPrContext; resolveError: string }; type ScopeVetteContext = { target: string; branch: string; baseBranch: string; dirtyStatus: string; draftsDir: string; findingsPath: string; resolveError: string; }; type VetteCommandContext = | { kind: "pr"; prContext: PrContext } | { kind: "scope"; scopeContext: ScopeVetteContext }; type VetteBetaStatusContext = { targetLabel: string; reviewMode: VetteBetaReviewMode; queued: boolean; progress?: string; }; type CommandStatus = { command: "vette" | "pr"; target: string; mode: string; phase: "working" | "queued" | "idle" | "blocked" | "merged"; progress: string; nextCheckAt?: number; }; const GH_PR_FIELDS = [ "number", "url", "title", "body", "author", "headRefName", "headRefOid", "baseRefName", "isDraft", "state", "mergedAt", "mergeStateStatus", "reviewDecision", "updatedAt", "comments", "reviews", "latestReviews", "statusCheckRollup", ].join(","); function shellQuote(value: string): string { return `'${value.replace(/'/g, `'"'"'`)}'`; } function formatModelConnection(selector: string): string { const slash = selector.indexOf("/"); if (slash <= 0 || slash === selector.length - 1) { return `connection=${selector} model=${selector}`; } return `connection=${selector.slice(0, slash)} model=${selector.slice(slash + 1)}`; } function parseArgs(args: string): { selector: string; scopeTarget: string; wantsPosting: boolean; wantsScope: boolean; wantsWatch: boolean; noPost: boolean; forceLocal: boolean; raw: string; } { const tokens = args.trim().split(/\s+/).filter(Boolean); const flags = new Set(tokens.filter((token) => token.startsWith("--"))); const positional = tokens.filter((token) => !token.startsWith("--")); const selector = positional[0] ?? ""; const wantsScope = flags.has("--scope") || flags.has("--service"); const noPost = flags.has("--no-post") || flags.has("--dry-run"); const forceLocal = flags.has("--local") || flags.has("--force-local"); return { selector, scopeTarget: positional.join(" ") || (wantsScope ? "." : ""), wantsPosting: !noPost && (flags.has("--post-comments") || flags.has("--post") || flags.has("--submit-review")), wantsScope, wantsWatch: !flags.has("--no-watch"), noPost, forceLocal, raw: args.trim(), }; } async function run( command: string, args: string[], cwd: string, ): Promise { try { const { stdout } = await execFileAsync(command, args, { cwd, maxBuffer: 10 * 1024 * 1024, }); return String(stdout).trim(); } catch (error) { const message = error instanceof Error ? error.message : String(error); const stderr = typeof (error as { stderr?: unknown }).stderr === "string" ? String((error as { stderr: string }).stderr).trim() : ""; throw new Error(stderr ? `${message}\n${stderr}` : message); } } async function getDirtyStatus(cwd: string): Promise { try { return await run("git", ["status", "--short"], cwd); } catch { return ""; } } async function getLocalGitIdentity(cwd: string): Promise<{ name?: string; email?: string; label: string; }> { const [name, email] = await Promise.all([ run("git", ["config", "user.name"], cwd).catch(() => ""), run("git", ["config", "user.email"], cwd).catch(() => ""), ]); const trimmedName = name.trim(); const trimmedEmail = email.trim(); const label = trimmedName && trimmedEmail ? `${trimmedName} <${trimmedEmail}>` : trimmedEmail || trimmedName || ""; return { ...(trimmedName ? { name: trimmedName } : {}), ...(trimmedEmail ? { email: trimmedEmail } : {}), label, }; } async function getCurrentBranch(cwd: string): Promise { const branch = await run("git", ["branch", "--show-current"], cwd); if (!branch) { throw new Error( "This workflow must run from a named git branch, not detached HEAD.", ); } return branch; } async function getDefaultBaseBranch(cwd: string): Promise { try { const originHead = await run( "git", ["symbolic-ref", "refs/remotes/origin/HEAD", "--short"], cwd, ); return originHead.replace(/^origin\//, "") || "main"; } catch { return "main"; } } async function getOriginRemoteUrl(cwd: string): Promise { try { return await run("git", ["remote", "get-url", "origin"], cwd); } catch { return ""; } } async function resolveDraftPrContext(cwd: string): Promise { const [branch, baseBranch, identity, dirtyStatus, remoteUrl] = await Promise.all([ getCurrentBranch(cwd), getDefaultBaseBranch(cwd), getLocalGitIdentity(cwd), getDirtyStatus(cwd), getOriginRemoteUrl(cwd), ]); return { branch, baseBranch, localIdentity: identity.label, dirtyStatus, remoteUrl, }; } async function resolvePrCommandContext( selector: string, cwd: string, ): Promise { try { return { kind: "existing", prContext: await resolvePrContext(selector, cwd), }; } catch (error) { if (selector) throw error; return { kind: "draft", draftContext: await resolveDraftPrContext(cwd), resolveError: error instanceof Error ? error.message : String(error), }; } } function isLikelyPrSelector(selector: string): boolean { return /^#?\d+$/.test(selector) || /^https?:\/\//i.test(selector); } async function resolveScopeVetteContext( target: string, resolveError: string, cwd: string, ): Promise { const [branch, baseBranch, dirtyStatus] = await Promise.all([ getCurrentBranch(cwd), getDefaultBaseBranch(cwd), getDirtyStatus(cwd), ]); const slug = slugifyBranch(target, "scope"); const draftsDir = `/tmp/pi-vette-bug-drafts/${slug}`; return { target, branch, baseBranch, dirtyStatus, draftsDir, findingsPath: `${draftsDir}/findings.md`, resolveError, }; } async function resolveVetteCommandContext( parsed: ReturnType, cwd: string, ): Promise { if (parsed.wantsScope) { return { kind: "scope", scopeContext: await resolveScopeVetteContext( parsed.scopeTarget, "Scope mode explicitly requested.", cwd, ), }; } try { return { kind: "pr", prContext: await resolvePrContext(parsed.selector, cwd), }; } catch (error) { if (!parsed.scopeTarget || isLikelyPrSelector(parsed.selector)) { throw error; } return { kind: "scope", scopeContext: await resolveScopeVetteContext( parsed.scopeTarget, error instanceof Error ? error.message : String(error), cwd, ), }; } } type LocalCommitEvidence = { authorEmail?: string; authorName?: string; message?: string; parents?: string[]; }; export function inferLocalOwnership(input: { localUserEmail?: string; localUserName?: string; commits: LocalCommitEvidence[]; }): { isOwner: boolean; ownership: "local" | "external" } { const localEmail = input.localUserEmail?.trim().toLowerCase(); // Name-only evidence is too weak to claim ownership (names collide easily); // without a configured local email, treat the branch as external. const isOwner = Boolean( localEmail && input.commits.some((commit) => { if ( (commit.parents?.length ?? 0) > 1 || commit.message?.startsWith("Merge ") ) { return false; } return commit.authorEmail?.trim().toLowerCase() === localEmail; }), ); return isOwner ? { isOwner: true, ownership: "local" } : { isOwner: false, ownership: "external" }; } async function localBranchExists( cwd: string, branch: string, ): Promise { try { await run("git", ["rev-parse", "--verify", `${branch}^{commit}`], cwd); return true; } catch { return false; } } async function mergeBaseForBranch( cwd: string, branch: string, baseBranch: string | undefined, ): Promise { const candidates = baseBranch ? [`origin/${baseBranch}`, baseBranch] : []; for (const candidate of candidates) { try { return await run("git", ["merge-base", candidate, branch], cwd); } catch { // Try the next local base candidate. } } return undefined; } function parseCommitEvidence(output: string): LocalCommitEvidence[] { return output.split("\x1e").flatMap((rawEntry) => { const entry = rawEntry.trim(); if (!entry) return []; const [, authorName, authorEmail, message, parents] = entry.split("\x00"); return [ { ...(authorName ? { authorName } : {}), ...(authorEmail ? { authorEmail } : {}), ...(message ? { message } : {}), ...(parents ? { parents: parents.split(" ").filter(Boolean) } : {}), }, ]; }); } async function getLocalCommitEvidence( cwd: string, branch: string | undefined, baseBranch: string | undefined, ): Promise { if (!branch || !(await localBranchExists(cwd, branch))) return []; const mergeBase = await mergeBaseForBranch(cwd, branch, baseBranch); if (!mergeBase) return []; const output = await run( "git", [ "log", "--format=%H%x00%an%x00%ae%x00%s%x00%P%x1e", `${mergeBase}..${branch}`, ], cwd, ); return parseCommitEvidence(output); } async function resolveLocalOwnership( cwd: string, pr: GhPullRequest, ): Promise<{ localIdentity: string; isOwner: boolean; ownership: "local" | "external"; }> { const identity = await getLocalGitIdentity(cwd); const commits = await getLocalCommitEvidence( cwd, pr.headRefName, pr.baseRefName, ); const ownership = inferLocalOwnership({ localUserEmail: identity.email, localUserName: identity.name, commits, }); return { localIdentity: identity.label, ...ownership }; } async function resolvePrContext( selector: string, cwd: string, ): Promise { const prArgs = ["pr", "view"]; if (selector) prArgs.push(selector); prArgs.push("--json", GH_PR_FIELDS); let pr: GhPullRequest; try { pr = JSON.parse(await run("gh", prArgs, cwd)) as GhPullRequest; } catch (error) { const hint = selector ? `Could not resolve PR selector ${shellQuote(selector)}. Use a PR number, branch, or URL.` : "Could not resolve a PR for the current branch. Pass a PR number, branch, or URL."; throw new Error( `${hint}\n\n${error instanceof Error ? error.message : String(error)}`, ); } const [ownership, dirtyStatus] = await Promise.all([ resolveLocalOwnership(cwd, pr), getDirtyStatus(cwd), ]); return { selector, pr, localIdentity: ownership.localIdentity, ownership: ownership.ownership, isOwner: ownership.isOwner, dirtyStatus, }; } function slugifyBranch(value: string, fallback: string): string { const slug = value .replace(/[^a-zA-Z0-9._-]+/g, "-") .replace(/\.{2,}/g, ".") .replace(/^[-.]+|[-.]+$/g, ""); return slug || fallback; } function branchSlug(ctx: PrContext): string { const raw = ctx.pr.headRefName || ctx.selector || `pr-${ctx.pr.number}`; return slugifyBranch(raw, `pr-${ctx.pr.number}`); } function findingsArtifactPath(ctx: PrContext): string { return `/tmp/pi-vette-findings/${branchSlug(ctx)}/pr-${ctx.pr.number}-findings.md`; } function isMergedPullRequest(pr: GhPullRequest): boolean { return pr.state?.toUpperCase() === "MERGED" || Boolean(pr.mergedAt); } function draftFindingsArtifactPath(ctx: DraftPrContext): string { return `/tmp/pi-vette-findings/${slugifyBranch(ctx.branch, "draft-pr")}/draft-pr-findings.md`; } function prSnapshotSummary(pr: GhPullRequest): string { const checks = pr.statusCheckRollup ?? []; const failedChecks = checks.filter((check) => /failure|timed_out|action_required|fail|error/i.test( `${check.conclusion ?? check.bucket ?? check.state ?? check.status ?? ""}`, ), ).length; const pendingChecks = checks.filter((check) => /pending|queued|in_progress|waiting/i.test( `${check.state ?? check.status ?? check.bucket ?? ""}`, ), ).length; const activityCount = (pr.comments?.length ?? 0) + (pr.reviews?.length ?? 0) + (pr.latestReviews?.length ?? 0); return `${checks.length} checks (${failedChecks} failing, ${pendingChecks} pending); ${activityCount} comments/reviews`; } function prSummary(ctx: PrContext): string { return [ `PR: ${ctx.pr.url} (#${ctx.pr.number})`, `Title: ${ctx.pr.title ?? ""}`, `Author: ${ctx.pr.author?.login ?? ""}`, `Local git identity: ${ctx.localIdentity}`, `Ownership mode: ${ctx.isOwner ? "owner repair" : "external review"}`, `Ownership evidence: ${ctx.ownership === "local" ? "matching local non-merge commit" : "no matching local non-merge commit"}`, `Head branch: ${ctx.pr.headRefName ?? ""}`, `Head SHA: ${ctx.pr.headRefOid ?? ""}`, `Base branch: ${ctx.pr.baseRefName ?? ""}`, `PR snapshot: ${prSnapshotSummary(ctx.pr)}`, `Findings artifact: ${findingsArtifactPath(ctx)}`, `Draft: ${String(ctx.pr.isDraft ?? false)}`, `PR state: ${ctx.pr.state ?? ""}`, `Merged at: ${ctx.pr.mergedAt ?? ""}`, `Merge state: ${ctx.pr.mergeStateStatus ?? ""}`, `Review decision: ${ctx.pr.reviewDecision ?? ""}`, ctx.dirtyStatus ? `Dirty worktree before command:\n${ctx.dirtyStatus}` : "Dirty worktree before command: clean or unavailable", ].join("\n"); } function draftPrSummary(ctx: DraftPrContext, resolveError: string): string { return [ "PR: ", `Current branch: ${ctx.branch}`, `Proposed base branch: ${ctx.baseBranch}`, `Origin remote: ${ctx.remoteUrl}`, `Local git identity: ${ctx.localIdentity}`, `Findings artifact: ${draftFindingsArtifactPath(ctx)}`, `Existing PR lookup: ${resolveError}`, ctx.dirtyStatus ? `Dirty worktree before command:\n${ctx.dirtyStatus}` : "Dirty worktree before command: clean or unavailable", ].join("\n"); } function scopeVetteSummary(ctx: ScopeVetteContext): string { return [ `Target scope: ${ctx.target}`, `Current branch: ${ctx.branch}`, `Reference base branch: ${ctx.baseBranch}`, `Bug ticket drafts directory: ${ctx.draftsDir}`, `Findings artifact: ${ctx.findingsPath}`, `PR lookup fallback reason: ${ctx.resolveError}`, ctx.dirtyStatus ? `Dirty worktree before command:\n${ctx.dirtyStatus}` : "Dirty worktree before command: clean or unavailable", ].join("\n"); } function subagentContract(): string { return `Required focused-agent contract: - Use isolated focused agents for non-trivial work. Do not let two agents write overlapping paths or share mutable ports, databases, caches, fixtures, or browser profiles. - Red test agent: may edit only tests/fixtures needed for one behavior; must prove the new test fails for the intended reason. - Green implementation agent: may edit only production code required to pass the staged red test; must not edit tests. - Reviewer/verifier agent: read-only by default; verifies behavior, minimality, test honesty, and no unrelated edits. - CI failure investigator: classifies failures as related, unrelated, or uncertain; uncertain is treated as related until proven otherwise. - Merge conflict resolver: resolves conflicts minimally, preserves both sides when safe, removes all conflict markers, and runs focused verification. - Commit/push only after parent review of agent output and passing verification. Never force push.`; } function localModelContract(forceLocal: boolean): string { if (!forceLocal) return ""; return `Local model mode (--local): - Prefer local model execution for every spawned review, repair, investigation, or verification agent. - When a command/tool supports smart-model-run local selection, pass its local-only option so it ranks local providers only. - Use local providers such as ollama, lmstudio, or local, with fallback from stronger code/review models to smaller 7B/8B models when larger models are unavailable. - Do not use remote/cloud model fallbacks unless the user explicitly authorizes leaving local mode.`; } function fallowAuditContract(): string { return `Required Fallow audit leg: - Run \`pnpx fallow audit --base origin/main --gate new-only\` after initial code/PR context is gathered and before final synthesis. If origin/main is unavailable, use the PR/review base branch shown in the command context. - Run the Fallow command once per vette pass. Fallow may exit with status 1 when it successfully found audit items. Treat exit 1 with usable findings/output as a completed audit result, not as a failed run; do not rerun it solely because the exit code is 1 or because advisory findings were reported. Only rerun or mark failed when the command produces no usable output or shows an execution/configuration error. - Treat Fallow output as advisory candidates, not verified findings. Deduplicate it against other lanes and changed files. - For every Fallow item considered useful, verify it with the same evidence gate as other findings before fixing, posting, or reporting it. - For noisy, duplicate, pre-existing, or out-of-scope Fallow items, summarize why they were rejected so this run can evaluate whether the audit leg was useful.`; } function parallelSuggestionContract(): string { return `Required parallel suggestion lanes: - Run these read-only lanes in parallel before choosing fixes or comments: vette risk review, naming/test-name check, and thermo-nuclear-code-quality-review. - The vette lane looks for correctness, security, reliability, data, UX, and test gaps in changed behavior. - The naming/test-name lane checks PR title/body wording, identifiers, branch/ticket wording when relevant, and especially behavior-first test names. - The thermo-nuclear lane runs an extremely strict maintainability review for abstraction quality, code judo opportunities, giant files, spaghetti conditionals, type/boundary cleanliness, and simpler structural alternatives. - Merge the three lane outputs into one deduplicated suggestion set before deciding what to repair or comment on. - Preserve lane provenance on every suggestion: [vette], [name-check], [thermo-nuclear], or a combined tag when multiple lanes agree. - Do not serialize these lanes unless a repo constraint prevents parallelism; if serialization is forced, explain why. - Suggestions become repairs/comments only after parent verification confirms scope, impact, and evidence. - Name-check suggestions and questions: when the [name-check] lane produces a substantive test-name or identifier/variable naming suggestion (a proposed alternative name, a question about intent, or a recommendation beyond a trivial wording tweak), that suggestion must be posted as a review comment anchored to the exact changed line in the diff. Use a GitHub \`\`\`suggest block with the full replacement line first so the author can apply it directly in the PR UI. Minor mechanical tweaks (typos, casing, punctuation) that the agent can silently fix in owner mode do not require a comment, but any suggestion that questions intent, proposes a meaningfully different name, or asks the author a question must be an inline comment, not bundled into a general PR comment.`; } function findingsArtifactContractForPath(path: string): string { return `Findings artifact contract: - Maintain a local Markdown findings artifact at ${path} for this branch/PR. - Create or update the artifact before posting or repairing anything, and keep it current as verification progresses. - The artifact must include every candidate finding from every lane, whether verified, rejected, duplicate, out-of-scope, test-reproduced, verified-but-untestable, or still blocked. - For each item, record: stable finding id, title, source lanes, status, severity/disposition, file/line when known, evidence, verification command/result, repro test path/code when applicable, posted comment URL/status when applicable, and rejection/blocker reason when applicable. - Use the artifact as the source of truth for final counts and for resuming the review if the session is interrupted. - Do not commit the artifact unless the user explicitly asks; it is a local temporary reference file.`; } function findingsArtifactContract(ctx: PrContext): string { return findingsArtifactContractForPath(findingsArtifactPath(ctx)); } function draftFindingsArtifactContract(ctx: DraftPrContext): string { return findingsArtifactContractForPath(draftFindingsArtifactPath(ctx)); } function bugDraftContract(ctx: ScopeVetteContext): string { return `Bug ticket draft contract: - Create the local directory ${ctx.draftsDir} if it does not exist. - Write ${ctx.draftsDir}/index.md summarizing every verified, rejected, duplicate, blocked, and unverified candidate. - Write one Markdown draft per verified bug as ${ctx.draftsDir}/bug-.md. - Do not create tracker tickets, GitHub issues, or PR comments in scope mode. - Each bug draft must include: behavior-first title, target scope, severity, user/system impact, affected files/symbols, evidence, focused verification command and result, exact repro test code when practical, why no focused test was practical when omitted, suggested acceptance criteria, and smallest safe fix boundary. - Unverified suspicions stay only in the findings artifact and index; do not promote them to standalone bug drafts.`; } function reviewCommentTestContract(): string { return `Review comment reproducibility contract: - At the end of external-review synthesis, inspect every actionable finding for whether it can be reproduced with a focused unit or regression test. - For every actionable finding, especially blockers, make a good-faith attempt to build the smallest temporary validating test or repro command that demonstrates the behavior. - For each reproducible finding, build the smallest temporary test that demonstrates the behavior, run the focused test command, and verify it fails for the expected reason on the PR branch. - If the focused test command fails after the exact-command retry, run one second dependency install attempt, then run the repository build/rebuild command, then rerun the focused test before preparing or posting any comments. - Clean up temporary test files unless the user explicitly asked to commit tests; keep the exact test code and failing command output in the review evidence. - Put the relevant test code directly in the associated GitHub review comment body, along with the command that proved it failed as expected, before posting the verified comment. - If a verified finding cannot be practically reproduced with a unit/regression test, classify it as untestable and preserve the best available evidence plus the reason no focused failing test is practical.`; } function reviewCommentPostingContract(): string { return `Review comment posting contract: - Do not post comments while still gathering, testing, retry-installing, rebuilding, rerunning checks, or cleaning up evidence. After all verification and cleanup is complete, post the verified items in one posting pass. - Every prepared or posted comment must start with exactly one scan label line before any details block or suggestion fence: \`🔴 **Blocker**\`, \`🟡 **Recommended**\`, or \`🔵 **Note**\`. Use Blocker for merge-blocking defects, Recommended for non-blocking fixes the author should strongly consider, and Note for contextual/low-risk observations. - Every substantive verified issue comment must put the developer-facing finding in the \`\`: one plain sentence that says what breaks and why. Do not overload the summary with verification metadata, lane names, counts, model names, or command output. - For each verified finding with a concrete file target, including verified-but-untestable findings, post the associated review comment at the most precise location available: prefer file + exact diff line; if no reliable line exists, use the file-level location when GitHub supports it; if the file is not a good/valid review-comment target, post it as a general PR comment with the file/line context in the body. - For [name-check] test-name or identifier/variable naming suggestions and questions: post each substantive naming suggestion as a review comment anchored to the exact changed line in the diff. Use the minimal naming-suggestion comment style from the template contract: a GitHub \`\`\`suggest block with the full replacement line first, then brief reasoning. Do not attach or reference screenshots, clipboard paths, or local image paths for naming suggestions. These are not bundled into grouped untestable-items comments; they are per-line inline comments even when no repro test applies. - Build a final grouped PR comment only for verified-but-untestable findings that cannot be anchored to a specific changed file or useful file-level target. Start it with a short non-scary sentence that states what kind of risk was found, then put each finding in its own \`
\` block with a one-sentence \`\` that names the broken behavior and why it matters. - Post any grouped untestable-items comment at the end of the posting pass, after all line/file-specific verified comments have been posted. - If GitHub rejects a line/file comment location, fall back to the next less-specific location and record that fallback in the final report.`; } export function reviewCommentTemplateContract(): string { return `Review comment templates: - Use the templates below for posted comments. Keep headings and labels stable so the PR thread is scannable. - Summary text must be one sentence, behavior-first, and plainly explain what was found and why it is a bug. Keep verification details inside the expanded panel. - GitHub rendering rule: always leave one blank line after the closing \`\` tag before hidden Markdown content starts, especially before lists, headings, or fenced code blocks. - Put long logs and repro/test code inside fenced code blocks within the expanded details body. - For line/file-level test-reproduced findings, post one comment per finding with this body: 🔴 **Blocker** | 🟡 **Recommended** | 🔵 **Note**
Verified issue: **Location:** **Source lanes:** <[vette] [name-check] [thermo-nuclear]> **Impact:** **Evidence:** - - Verification command: - Result: fails as expected because **Failing repro test:** ~~~~ ~~~~ **Fix boundary:**
- For [name-check] test-name-only or identifier/variable naming comments, do not use the verified issue template above. Use this minimal body exactly: 🟡 **Recommended** \`\`\`suggest \`\`\` - For general PR-comment fallbacks of test-reproduced findings, use the same \`
\` template and keep **Location** as the first expanded field with the best available file/line context. - For file/line-level verified-but-untestable findings, post one comment per finding using the verified issue details template without the failing repro test section. Keep **Location** as the first expanded field and include **Why no focused test:** before **Fix boundary:**. - For a final grouped verified-but-untestable PR comment covering only items that cannot be anchored to a specific changed file, use this body: 🔴 **Blocker** | 🟡 **Recommended** | 🔵 **Note** Verified findings without focused repro tests: . These PR-wide items were verified but were not practical to demonstrate with focused unit/regression tests or anchor to a useful changed file. They are grouped here to keep the PR thread focused.
- **Location:** - **Source lanes:** <[vette] [name-check] [thermo-nuclear]> - **Impact:** - **Evidence:** - **Why no focused test:** - **Fix boundary:**
- Repeat one \`
\` block per verified-but-untestable finding. - If every verified-but-untestable finding was posted as a file/line-level comment, do not post a grouped untestable-items comment; record "none" for grouped untestable items in the final report.`; } function vettePrompt( ctx: PrContext, rawArgs: string, options: { wantsPosting: boolean; noPost?: boolean; forceLocal?: boolean; }, ): string { const commentPolicy = options.noPost ? "DRY RUN (--no-post): do not post any GitHub comments, reviews, or other externally visible output. Prepare comment-ready markdown for verified findings and present it in the final report only." : options.wantsPosting ? "The user explicitly allowed posting comments, but posting is already automatic for verified external-review findings." : "Post externally visible GitHub review comments automatically for verified external-review findings. Do not ask for additional posting approval after verification passes."; const localModels = localModelContract(options.forceLocal === true); const fallowAudit = fallowAuditContract(); const visibleStatusContract = `Visible status requirements: - Maintain an explicit status/todo sequence and update it immediately as phases change: 1. Resolve PR context 2. Run parallel review lanes 3. Synthesize findings 4. Verify/repair or prepare verified comments when applicable 5. Post verified comments when applicable 6. Complete - While active, state the current phase in plain text, e.g. "working on (2/6): running parallel review lanes". - When review lanes finish, immediately move to "working on (3/6): synthesizing findings". - When synthesis is done, move to the posting phase before completion when external-review comments are applicable. - When posting is done, explicitly state "Vette complete" with counts: suggestions, repairs, comments prepared, comments posted, and untestable items grouped. - Do not leave the final phase in progress after returning the final report. End with "status: idle — vette complete".`; if (ctx.isOwner) { return `Run /vette owner repair mode for this pull request.\n\n${prSummary(ctx)}\n\nOriginal /vette args: ${rawArgs || ""}\n\n${visibleStatusContract}\n\n${localModels ? `${localModels}\n\n` : ""}Mandatory behavior:\n- Local non-merge commit evidence indicates this PR branch is owned here, so do NOT draft or post PR review comments for findings.\n- Use evidence-first vette/pr-review techniques to find confirmed, user-impacting defects, weak tests, merge conflicts, failed checks, and review/bot comments that require action.\n- For each confirmed related finding, repair it through strict TDD: red test, red verification, green implementation, reviewer/verifier, refactor gate.\n- Spawn focused subagents according to the contract below for every non-trivial failure/finding.\n- Verify locally with focused commands, then broader checks when appropriate.\n- Commit and push focused fixes when verification passes and the repository state is safe.\n- If a finding is real but out of scope, document it in the final report instead of bloating this PR.\n- If the worktree was dirty before this command, protect pre-existing changes and report how they were handled before any repair action. ${fallowAudit} Use these existing skills/instructions by prompt routing as relevant: vette, pr-review, tdd, loop-on-ci, fix-merge-conflicts, naming, test-name, thermo-nuclear-code-quality-review. \n${parallelSuggestionContract()}\n\n${findingsArtifactContract(ctx)}\n\n${subagentContract()}\n\nFinish with PR URL, fixes made, commits pushed, findings artifact path, exact verification commands/results, and any blockers.\n\nComment policy: owner PR mode must not draft or post PR review comments.`; } return `Run /vette external PR review mode for this pull request.\n\n${prSummary(ctx)}\n\nOriginal /vette args: ${rawArgs || ""}\n\n${visibleStatusContract}\n\n${localModels ? `${localModels}\n\n` : ""}Mandatory behavior:\n- Local non-merge commit evidence does not show this PR branch is owned here, so perform an evidence-backed PR review/comment workflow.\n- Review source branch against base branch using merge-base diff, PR title/body, linked requirements, changed files, contracts, and tests.\n- Run vette risk lanes only for changed behavior; do not expand into a whole-repo audit unless necessary for evidence.\n- Verify every actionable finding locally through static proof, focused command, or a temporary failing test. Clean up temporary artifacts.\n- Before finalizing comments, look for findings that can be reproduced with focused unit/regression tests; build those tests, run them, and verify they fail for the expected reason. If the test command still fails after the exact-command retry, run one second dependency install attempt, then run the repository build/rebuild command, then rerun the focused test before preparing or posting any comments. - Prepare GitHub review comments that follow the repo comment contract: exact file/line when available, user impact, local evidence, fix boundary, and suggested tests when appropriate. For every substantive finding, put a one-sentence bug reason in the \`\` and keep evidence/verification inside the expanded \`
\` body. For test-reproducible findings, include the exact failing test code in the associated comment body. - After all verification and cleanup is complete, post verified comments in one posting pass. Prefer file/line comments, fall back to file-level comments when line placement is not possible, and fall back to a general PR comment when the file is not a good comment target. - Split verified-but-untestable findings into specific file/line review comments whenever possible, using file-level comments when exact line placement is not reliable, so each affected file can be resolved separately. Build a grouped final PR comment only for verified-but-untestable items that cannot be anchored to a useful changed file; each grouped finding must be its own \`
\` block with a concise summary. - Post only findings that passed the verification gate; reject or report unverified suggestions without posting them.\n- ${commentPolicy}\n- Do not implement repairs on someone else's PR unless the user explicitly asks after seeing the review.\n\n${fallowAudit}\n\nUse these existing skills/instructions by prompt routing as relevant: pr-review, vette, naming, test-name, and thermo-nuclear-code-quality-review. \n${parallelSuggestionContract()} \n${findingsArtifactContract(ctx)}\n\n${reviewCommentTestContract()}\n\n${reviewCommentPostingContract()}\n\n${reviewCommentTemplateContract()}\n\nFinish with review disposition, commands/results, findings artifact path, comments prepared and posted, rejected findings, untestable-items comment URL/status, and cleanup status.`; } function scopeVettePrompt( ctx: ScopeVetteContext, rawArgs: string, options: { forceLocal?: boolean } = {}, ): string { const localModels = localModelContract(options.forceLocal === true); return `Run /vette scope bug-discovery mode. This is not a PR review: audit the requested service/module/scope, validate likely bugs, build focused repro tests where practical, and draft local bug tickets only.\n\n${scopeVetteSummary(ctx)}\n\nOriginal /vette args: ${rawArgs || ""}\n\n${localModels ? `${localModels}\n\n` : ""}Visible status requirements:\n- Maintain an explicit status/todo sequence and update it immediately as phases change:\n 1. Resolve and map target scope\n 2. Run parallel risk lanes\n 3. Synthesize candidate bugs\n 4. Verify candidates with evidence and repro tests where practical\n 5. Write local bug-ticket drafts\n 6. Complete\n- While active, state the current phase in plain text, e.g. "working on (2/6): running parallel risk lanes".\n- End with "status: idle — scope vette complete" and counts for candidates, verified bugs, bug drafts written, rejected items, blocked items, and test-backed drafts.\n\nMandatory behavior:\n- Treat ${ctx.target} as the audit boundary. It may be a full service, module, package, directory, route group, job, or subsystem. First identify its entry points, dependencies, data stores, side effects, tests, and owner-facing behavior.\n- Run read-only risk lanes in parallel before deciding what deserves verification: vette risk review, naming/test-name check, and thermo-nuclear-code-quality-review. For broad service scopes, add focused lanes for API/contract boundaries, data consistency, async/job behavior, error handling, and observability where relevant.\n- Promote only verified, user-impacting defects to bug-ticket drafts. Verification can be static proof, a focused command, a runtime observation, or a temporary focused failing test.\n- For each candidate where a focused unit/regression/integration test is practical, build the smallest repro test, run the focused command, and prove it fails for the expected reason. Clean up temporary test files unless the user explicitly asks to keep them, but preserve exact test code and failing output in the draft.\n- Do not edit production code or implement fixes in scope mode unless the user explicitly asks after reading the drafts.\n- Do not create GitHub issues, Linear tickets, PR comments, or commits. Write local Markdown drafts only.\n\nUse these existing skills/instructions by prompt routing as relevant: vette, tdd, pr-review, naming, test-name, and thermo-nuclear-code-quality-review.\n\n${parallelSuggestionContract()}\n\n${findingsArtifactContractForPath(ctx.findingsPath)}\n\n${bugDraftContract(ctx)}\n\n${subagentContract()}\n\nFinish with target scope, drafts directory, findings artifact path, verification commands/results, repro test summary, draft filenames, rejected findings, blocked findings, and cleanup status.`; } function prPrompt( ctx: PrContext, rawArgs: string, options: { wantsPosting: boolean; wantsWatch: boolean; noPost?: boolean; forceLocal?: boolean; }, ): string { const localModels = localModelContract(options.forceLocal === true); const fallowAudit = fallowAuditContract(); return `Run /pr preparation, vette, repair, and monitoring mode for this pull request.\n\n${prSummary(ctx)}\n\nOriginal /pr args: ${rawArgs || ""}\n\n${localModels ? `${localModels}\n\n` : ""}Visible status and timing requirements:\n- Check immediately, then use a 15-minute cadence while watching.\n- Before every wait, state the current PR status, what was checked, whether you are working or idle, progress like "working on (1/1)", and the next check time.\n- On every watch check, inspect the PR lifecycle with \`gh pr view ${ctx.pr.number} --json state,mergedAt,mergeStateStatus\`. If \`state\` is \`MERGED\` or \`mergedAt\` is present, close down the watch item immediately: do not run more checks, post comments, repair code, or schedule another wait. End with exactly "status: merged — PR #${ctx.pr.number} is merged; watch closed".\n- When no actionable issue/comment/check failure is present, state "idle until