diff --git a/packages/coding-agent/src/core/tools/index.ts b/packages/coding-agent/src/core/tools/index.ts index 2dd96ef6c..9d5c31ca8 100644 --- a/packages/coding-agent/src/core/tools/index.ts +++ b/packages/coding-agent/src/core/tools/index.ts @@ -1,12 +1,12 @@ -export { type AskToolDetails, askTool, createAskTool } from "./ask.js"; -export { type BashToolDetails, bashTool, createBashTool } from "./bash.js"; -export { createEditTool, type EditToolOptions, editTool } from "./edit.js"; +export { type AskToolDetails, askTool, createAskTool } from "./ask"; +export { type BashToolDetails, bashTool, createBashTool } from "./bash"; +export { createEditTool, type EditToolOptions, editTool } from "./edit"; // Exa MCP tools (22 tools) -export { exaTools } from "./exa/index.js"; -export type { ExaRenderDetails, ExaSearchResponse, ExaSearchResult } from "./exa/types.js"; -export { createFindTool, type FindToolDetails, findTool } from "./find.js"; -export { createGrepTool, type GrepToolDetails, grepTool } from "./grep.js"; -export { createLsTool, type LsToolDetails, lsTool } from "./ls.js"; +export { exaTools } from "./exa/index"; +export type { ExaRenderDetails, ExaSearchResponse, ExaSearchResult } from "./exa/types"; +export { createFindTool, type FindToolDetails, findTool } from "./find"; +export { createGrepTool, type GrepToolDetails, grepTool } from "./grep"; +export { createLsTool, type LsToolDetails, lsTool } from "./ls"; export { createLspTool, type FileDiagnosticsResult, @@ -19,12 +19,13 @@ export { type LspWarmupResult, lspTool, warmupLspServers, -} from "./lsp/index.js"; -export { createNotebookTool, type NotebookToolDetails, notebookTool } from "./notebook.js"; -export { createReadTool, type ReadToolDetails, readTool } from "./read.js"; -export { BUNDLED_AGENTS, createTaskTool, taskTool } from "./task/index.js"; -export type { TruncationResult } from "./truncate.js"; -export { createWebFetchTool, type WebFetchToolDetails, webFetchCustomTool, webFetchTool } from "./web-fetch.js"; +} from "./lsp/index"; +export { createNotebookTool, type NotebookToolDetails, notebookTool } from "./notebook"; +export { createReadTool, type ReadToolDetails, readTool } from "./read"; +export { createReportFindingTool, createSubmitReviewTool, reportFindingTool, submitReviewTool } from "./review"; +export { BUNDLED_AGENTS, createTaskTool, taskTool } from "./task/index"; +export type { TruncationResult } from "./truncate"; +export { createWebFetchTool, type WebFetchToolDetails, webFetchCustomTool, webFetchTool } from "./web-fetch"; export { companyWebSearchTools, createWebSearchTool, @@ -42,24 +43,25 @@ export { webSearchDeepTool, webSearchLinkedinTool, webSearchTool, -} from "./web-search/index.js"; -export { createWriteTool, type WriteToolDetails, type WriteToolOptions, writeTool } from "./write.js"; +} from "./web-search/index"; +export { createWriteTool, type WriteToolDetails, type WriteToolOptions, writeTool } from "./write"; import type { AgentTool } from "@oh-my-pi/pi-agent-core"; -import { askTool, createAskTool } from "./ask.js"; -import { bashTool, createBashTool } from "./bash.js"; -import { checkBashInterception, checkSimpleLsInterception } from "./bash-interceptor.js"; -import { createEditTool, editTool } from "./edit.js"; -import { createFindTool, findTool } from "./find.js"; -import { createGrepTool, grepTool } from "./grep.js"; -import { createLsTool, lsTool } from "./ls.js"; -import { createLspTool, formatFile, getDiagnosticsForFile, lspTool } from "./lsp/index.js"; -import { createNotebookTool, notebookTool } from "./notebook.js"; -import { createReadTool, readTool } from "./read.js"; -import { createTaskTool, taskTool } from "./task/index.js"; -import { createWebFetchTool, webFetchTool } from "./web-fetch.js"; -import { createWebSearchTool, webSearchTool } from "./web-search/index.js"; -import { createWriteTool, writeTool } from "./write.js"; +import { askTool, createAskTool } from "./ask"; +import { bashTool, createBashTool } from "./bash"; +import { checkBashInterception, checkSimpleLsInterception } from "./bash-interceptor"; +import { createEditTool, editTool } from "./edit"; +import { createFindTool, findTool } from "./find"; +import { createGrepTool, grepTool } from "./grep"; +import { createLsTool, lsTool } from "./ls"; +import { createLspTool, formatFile, getDiagnosticsForFile, lspTool } from "./lsp/index"; +import { createNotebookTool, notebookTool } from "./notebook"; +import { createReadTool, readTool } from "./read"; +import { createReportFindingTool, createSubmitReviewTool, reportFindingTool, submitReviewTool } from "./review"; +import { createTaskTool, taskTool } from "./task/index"; +import { createWebFetchTool, webFetchTool } from "./web-fetch"; +import { createWebSearchTool, webSearchTool } from "./web-search/index"; +import { createWriteTool, writeTool } from "./write"; /** Tool type (AgentTool from pi-ai) */ export type Tool = AgentTool; @@ -118,6 +120,8 @@ const toolDefs: Record = { task: { tool: taskTool, create: (cwd, ctx) => createTaskTool(cwd, ctx) }, web_fetch: { tool: webFetchTool, create: createWebFetchTool }, web_search: { tool: webSearchTool, create: createWebSearchTool }, + report_finding: { tool: reportFindingTool, create: createReportFindingTool }, + submit_review: { tool: submitReviewTool, create: createSubmitReviewTool }, }; export type ToolName = keyof typeof toolDefs; diff --git a/packages/coding-agent/src/core/tools/review.ts b/packages/coding-agent/src/core/tools/review.ts new file mode 100644 index 000000000..45cee43ac --- /dev/null +++ b/packages/coding-agent/src/core/tools/review.ts @@ -0,0 +1,207 @@ +/** + * Review tools - report_finding and submit_review + * + * Used by the reviewer agent to report findings in a structured way. + * Both tools are hidden by default - only enabled when explicitly listed in agent's tools. + */ + +import type { AgentTool } from "@oh-my-pi/pi-agent-core"; +import type { Component } from "@oh-my-pi/pi-tui"; +import { Container, Spacer, Text } from "@oh-my-pi/pi-tui"; +import { Type } from "@sinclair/typebox"; +import type { Theme } from "../../modes/interactive/theme/theme"; + +const PRIORITY_LABELS: Record = { + 0: "P0", + 1: "P1", + 2: "P2", + 3: "P3", +}; + +const PRIORITY_DESCRIPTIONS: Record = { + 0: "Drop everything to fix. Blocking release, operations, or major usage.", + 1: "Urgent. Should be addressed in the next cycle.", + 2: "Normal. To be fixed eventually.", + 3: "Low. Nice to have.", +}; + +// report_finding schema +const ReportFindingParams = Type.Object({ + title: Type.String({ + description: "≤80 chars, imperative, prefixed with [P0-P3]. E.g., '[P1] Un-padding slices along wrong dimension'", + }), + body: Type.String({ + description: "Markdown explaining why this is a problem. One paragraph max.", + }), + priority: Type.Union([Type.Literal(0), Type.Literal(1), Type.Literal(2), Type.Literal(3)], { + description: "0=P0 (critical), 1=P1 (urgent), 2=P2 (normal), 3=P3 (low)", + }), + confidence: Type.Number({ + minimum: 0, + maximum: 1, + description: "Confidence score 0.0-1.0", + }), + file_path: Type.String({ description: "Absolute path to the file" }), + line_start: Type.Number({ description: "Start line of the issue" }), + line_end: Type.Number({ description: "End line of the issue" }), +}); + +interface ReportFindingDetails { + title: string; + body: string; + priority: number; + confidence: number; + file_path: string; + line_start: number; + line_end: number; +} + +export const reportFindingTool: AgentTool = { + name: "report_finding", + label: "Report Finding", + description: "Report a code review finding. Use this for each issue found. Call submit_review when done.", + parameters: ReportFindingParams, + hidden: true, + + async execute(_toolCallId, params, _signal, _onUpdate, _ctx) { + const { title, body, priority, confidence, file_path, line_start, line_end } = params; + const location = `${file_path}:${line_start}${line_end !== line_start ? `-${line_end}` : ""}`; + + return { + content: [ + { + type: "text", + text: `Finding recorded: ${PRIORITY_LABELS[priority]} ${title}\nLocation: ${location}\nConfidence: ${(confidence * 100).toFixed(0)}%`, + }, + ], + details: { title, body, priority, confidence, file_path, line_start, line_end }, + }; + }, + + renderCall(args, theme): Component { + const priority = PRIORITY_LABELS[args.priority as number] ?? "P?"; + const color = args.priority === 0 ? "error" : args.priority === 1 ? "warning" : "muted"; + const titleText = String(args.title).replace(/^\[P\d\]\s*/, ""); + return new Text( + `${theme.fg("toolTitle", theme.bold("report_finding "))}${theme.fg(color, `[${priority}]`)} ${theme.fg("dim", titleText)}`, + 0, + 0, + ); + }, + + renderResult(result, _options, theme): Component { + const { details } = result; + if (!details) { + const text = result.content[0]; + return new Text(text?.type === "text" ? text.text : "", 0, 0); + } + + const priority = PRIORITY_LABELS[details.priority] ?? "P?"; + const color = details.priority === 0 ? "error" : details.priority === 1 ? "warning" : "muted"; + const location = `${details.file_path}:${details.line_start}${details.line_end !== details.line_start ? `-${details.line_end}` : ""}`; + + return new Text( + `${theme.fg("success", "✓")} ${theme.fg(color, `[${priority}]`)} ${theme.fg("dim", location)}`, + 0, + 0, + ); + }, +}; + +// submit_review schema +const SubmitReviewParams = Type.Object({ + overall_correctness: Type.Union([Type.Literal("correct"), Type.Literal("incorrect")], { + description: "Whether the patch is correct (no bugs, tests won't break)", + }), + explanation: Type.String({ + description: "1-3 sentence explanation justifying the verdict", + }), + confidence: Type.Number({ + minimum: 0, + maximum: 1, + description: "Overall confidence score 0.0-1.0", + }), + findings_count: Type.Number({ + description: "Total number of findings reported", + }), +}); + +interface SubmitReviewDetails { + overall_correctness: "correct" | "incorrect"; + explanation: string; + confidence: number; + findings_count: number; +} + +export const submitReviewTool: AgentTool = { + name: "submit_review", + label: "Submit Review", + description: "Submit the final review verdict. Call this after all findings have been reported.", + parameters: SubmitReviewParams, + hidden: true, + + async execute(_toolCallId, params, _signal, _onUpdate, _ctx) { + const { overall_correctness, explanation, confidence, findings_count } = params; + + let summary = `## Review Summary\n\n`; + summary += `**Verdict:** ${overall_correctness === "correct" ? "✓ Patch is correct" : "✗ Patch is incorrect"}\n`; + summary += `**Confidence:** ${(confidence * 100).toFixed(0)}%\n`; + summary += `**Findings:** ${findings_count}\n\n`; + summary += explanation; + + return { + content: [{ type: "text", text: summary }], + details: { overall_correctness, explanation, confidence, findings_count }, + }; + }, + + renderCall(args, theme): Component { + const verdict = args.overall_correctness === "correct" ? "correct" : "incorrect"; + const color = args.overall_correctness === "correct" ? "success" : "error"; + return new Text( + `${theme.fg("toolTitle", theme.bold("submit_review "))}${theme.fg(color, verdict)} ${theme.fg("dim", `(${((args.confidence as number) * 100).toFixed(0)}%)`)}`, + 0, + 0, + ); + }, + + renderResult(result, { expanded }, theme): Component { + const { details } = result; + if (!details) { + const text = result.content[0]; + return new Text(text?.type === "text" ? text.text : "", 0, 0); + } + + const container = new Container(); + const verdictColor = details.overall_correctness === "correct" ? "success" : "error"; + const verdictIcon = details.overall_correctness === "correct" ? "✓" : "✗"; + + container.addChild( + new Text( + `${theme.fg(verdictColor, verdictIcon)} Patch is ${theme.fg(verdictColor, details.overall_correctness)} ${theme.fg("dim", `(${(details.confidence * 100).toFixed(0)}% confidence)`)}`, + 0, + 0, + ), + ); + + if (details.findings_count > 0) { + container.addChild(new Spacer(1)); + container.addChild(new Text(theme.fg("muted", `${details.findings_count} finding(s) reported`), 0, 0)); + } + + if (expanded) { + container.addChild(new Spacer(1)); + container.addChild(new Text(theme.fg("dim", details.explanation), 0, 0)); + } + + return container; + }, +}; + +export function createReportFindingTool(): AgentTool { + return reportFindingTool; +} + +export function createSubmitReviewTool(): AgentTool { + return submitReviewTool; +} diff --git a/packages/coding-agent/src/core/tools/task/bundled-commands/review.md b/packages/coding-agent/src/core/tools/task/bundled-commands/review.md new file mode 100644 index 000000000..2b65eedcb --- /dev/null +++ b/packages/coding-agent/src/core/tools/task/bundled-commands/review.md @@ -0,0 +1,9 @@ +--- +description: Launch code review with the reviewer agent +--- + +Use the Task tool to run the "reviewer" agent with this task: + +$@ + +The reviewer agent will analyze the code changes and report findings using the report_finding and submit_review tools.