feat(coding-agent): added review tools and /review command

- Added report_finding and submit_review built-in tools (hidden by default)
- Added hidden field to AgentTool interface for tools that require explicit opt-in
- Added /review bundled command to launch reviewer agent
- Updated bundled reviewer agent with comprehensive code review guidelines
This commit is contained in:
can1357
2026-01-03 05:10:53 +01:00
parent c70303ade9
commit 3d53aa181f
3 changed files with 250 additions and 30 deletions
+34 -30
View File
@@ -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<any, any, any>;
@@ -118,6 +120,8 @@ const toolDefs: Record<string, { tool: Tool; create: ToolFactory }> = {
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;
@@ -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<number, string> = {
0: "P0",
1: "P1",
2: "P2",
3: "P3",
};
const PRIORITY_DESCRIPTIONS: Record<number, string> = {
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<typeof ReportFindingParams, ReportFindingDetails, Theme> = {
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<typeof SubmitReviewParams, SubmitReviewDetails, Theme> = {
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<typeof ReportFindingParams, ReportFindingDetails, Theme> {
return reportFindingTool;
}
export function createSubmitReviewTool(): AgentTool<typeof SubmitReviewParams, SubmitReviewDetails, Theme> {
return submitReviewTool;
}
@@ -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.