Pi Coding Agent Extensions - Debug, Guardrails, Review, Todos, Emacs Bridge, Web Browser Pi Coding Agent has released a set of extensions including a code review extension that provides `/review`, `/post-review`, `/resume-review`, and `/end-review` commands. The extension integrates with GitHub via the `gh` CLI, supports project-specific guidelines from REVIEW_GUIDELINES.md, and allows filtering by priority and type. It also includes debug, guardrails, todos, an Emacs bridge, and a web browser extension. | / | | Code Review Extension | | | | Provides a /review command focused on: | | 1. Validating code correctness | | 2. Surfacing important design decisions | | | | Usage: | | - /review - show interactive selector | | - /review uncommitted - review uncommitted changes | | - /review branch main - review against main branch | | - /review commit abc123 - review specific commit | | - /review pr 123 - review PR 123 checks out locally | | - /review file src/foo.ts - review specific files snapshot | | - /review custom "focus on error handling" - custom focus | | - /post-review - post findings to GitHub PR interactive selection | | - /post-review 123 - post findings to specific PR | | - /post-review P1 - only post P1 priority findings | | - /post-review P0 P1 - only post P0 and P1 findings | | - /post-review design - only post design findings | | - /post-review 123 P1 correctness - combine PR number and filters | | - /resume-review - restore review session after /reload | | - /end-review - complete review and return to original position | | | | Project-specific guidelines: | | - If REVIEW GUIDELINES.md exists in project root, it's included in the prompt. | | | | GitHub Integration: | | - Uses gh CLI for PR operations checkout, post comments | | - /post-review parses review findings and posts as PR review with inline comments | | / | | | | import type { ExtensionAPI, ExtensionContext, ExtensionCommandContext } from "@mariozechner/pi-coding-agent"; | | import { Text } from "@mariozechner/pi-tui"; | | import path from "node:path"; | | import { promises as fs } from "node:fs"; | | | | // State for fresh session review tracking | | let reviewOriginId: string | undefined = undefined; | | let currentPrNumber: number | undefined = undefined; | | let currentPrBaseBranch: string | undefined = undefined; | | const REVIEW STATE TYPE = "review-session"; | | | | type ReviewSessionState = { | | active: boolean; | | originId?: string; | | prNumber?: number; | | prBaseBranch?: string; | | }; | | | | // Types for PR review comments | | type ReviewFinding = { | | type: "correctness" | "design"; | | priority?: "P0" | "P1" | "P2" | "P3"; | | file: string; | | line?: number; | | issue: string; | | details: string; | | suggestion?: string; | | }; | | | | type ParsedReview = { | | findings: ReviewFinding ; | | summary?: string; | | }; | | | | function setReviewWidget ctx: ExtensionContext, active: boolean { | | if ctx.hasUI return; | | if active { | | ctx.ui.setWidget "review", undefined ; | | return; | | } | | | | ctx.ui.setWidget "review", tui, theme = { | | const text = new Text theme.fg "warning", "Review session active — use /end-review to return" , 0, 0 ; | | return { | | render width: number { return text.render width ; }, | | invalidate { text.invalidate ; }, | | }; | | } ; | | } | | | | function getReviewState ctx: ExtensionContext : ReviewSessionState | undefined { | | let state: ReviewSessionState | undefined; | | for const entry of ctx.sessionManager.getBranch { | | if entry.type === "custom" && entry.customType === REVIEW STATE TYPE { | | state = entry.data as ReviewSessionState | undefined; | | } | | } | | return state; | | } | | | | function applyReviewState ctx: ExtensionContext { | | const state = getReviewState ctx ; | | if state?.active && state.originId { | | reviewOriginId = state.originId; | | currentPrNumber = state.prNumber; | | currentPrBaseBranch = state.prBaseBranch; | | setReviewWidget ctx, true ; | | return; | | } | | reviewOriginId = undefined; | | currentPrNumber = undefined; | | currentPrBaseBranch = undefined; | | setReviewWidget ctx, false ; | | } | | | | // Parse review findings from assistant messages | | function parseReviewFindings messages: string : ParsedReview { | | const findings: ReviewFinding = ; | | let summary: string | undefined; | | | | const combinedText = messages.join "\n\n" ; | | | | // Extract findings using regex patterns matching the review output format | | // Format: CORRECTNESS or DESIGN followed by File: path:line | | const findingPattern = /\ \ \ CORRECTNESS|DESIGN \ \ \ ?:\s \ \ \ P 0-3 \ \ \ ?\s \n+\ \ File:\ \ \s ^ + \s \n+\ \ Issue:\ \ \s ^\n + \s \n+\ \ Details:\ \ \s \s\S ? ?=\ \ Suggestion:\ \ |\ \ \ ?:CORRECTNESS|DESIGN \ \ \ | Summary|$ \s ?:\ \ Suggestion:\ \ \s \s\S ? ? ?=\ \ \ ?:CORRECTNESS|DESIGN \ \ \ | Summary|$ /gi; | | | | let match; | | while match = findingPattern.exec combinedText == null { | | const , type, priority, filePath, issue, details, suggestion = match; | | | | // Parse file:line format | | const fileMatch = filePath.match /^ .+? ?:: \d+ ?$/ ; | | if fileMatch { | | findings.push { | | type: type.toLowerCase as "correctness" | "design", | | priority: priority as "P0" | "P1" | "P2" | "P3" | undefined, | | file: fileMatch 1 .trim , | | line: fileMatch 2 ? parseInt fileMatch 2 , 10 : undefined, | | issue: issue.trim , | | details: details.trim , | | suggestion: suggestion?.trim , | | } ; | | } | | } | | | | // Extract summary section | | const summaryMatch = combinedText.match / Summary\s \s\S ? ?= Key Questions|$ /i ; | | if summaryMatch { | | summary = summaryMatch 1 .trim ; | | } | | | | return { findings, summary }; | | } | | | | // Get the latest assistant messages from the session for parsing | | function getAssistantMessages ctx: ExtensionContext, searchAll = false : string { | | const messages: string = ; | | const allAssistantMessages: string = ; | | const entries = ctx.sessionManager.getBranch ; | | | | // Get messages from current review session after the review prompt | | let foundReviewPrompt = false; | | for const entry of entries { | | if entry.type === "message" { | | const msg = entry.message; | | | | // Collect all assistant messages as fallback | | if msg.role === "assistant" { | | const extractText = content: any : string = { | | if Array.isArray content { | | return content.filter p = p.type === "text" .map p = p.text ; | | } else if typeof content === "string" { | | return content ; | | } | | return ; | | }; | | allAssistantMessages.push ...extractText msg.content ; | | } | | | | if msg.role === "user" { | | const content = typeof msg.content === "string" ? msg.content : | | Array.isArray msg.content ? msg.content.filter p = p.type === "text" .map p = p.text .join "\n" : ""; | | if content.includes " Code Review Guidelines" || content.includes "Review the" && content.includes "changes" { | | foundReviewPrompt = true; | | continue; | | } | | } | | if foundReviewPrompt && msg.role === "assistant" { | | if Array.isArray msg.content { | | for const part of msg.content { | | if part.type === "text" { | | messages.push part.text ; | | } | | } | | } else if typeof msg.content === "string" { | | messages.push msg.content ; | | } | | } | | } | | } | | | | // If we didn't find a review prompt or searchAll is true, return all assistant messages | | // that contain review-like content | | if messages.length === 0 || searchAll { | | // Filter to only messages that look like they contain review findings | | const reviewMessages = allAssistantMessages.filter m = | | m.includes " CORRECTNESS " || | | m.includes " DESIGN " || | | m.includes " File: " || | | m.includes " Summary" | | ; | | if reviewMessages.length 0 { | | return reviewMessages; | | } | | // Last resort: return all assistant messages | | return allAssistantMessages; | | } | | | | return messages; | | } | | | | // Format a finding as a GitHub review comment body | | function formatFindingAsComment finding: ReviewFinding : string { | | let body = ""; | | | | // Header with type and priority | | const typeEmoji = finding.type === "correctness" ? "🐛" : "💡"; | | const priorityBadge = finding.priority ? ${finding.priority} : ""; | | body += ${typeEmoji} ${finding.type.toUpperCase } ${priorityBadge}\n\n ; | | | | // Issue | | body += Issue: ${finding.issue}\n\n ; | | | | // Details | | body += ${finding.details}\n ; | | | | // Suggestion | | if finding.suggestion { | | body += \n Suggestion: ${finding.suggestion} ; | | } | | | | return body; | | } | | | | // Get the diff position for a file:line in the PR diff | | async function getDiffPosition | | pi: ExtensionAPI, | | prNumber: number, | | file: string, | | line: number | | : Promise