feat(coding-agent): added structured code review with hidden tools and explicit opt-in

- Added hidden property for custom tools to exclude them from default tool list unless explicitly requested.
- Added explicitTools option to createAgentSession for enabling hidden tools by name.
- Added example review tools (report_finding, submit_review) with structured findings accumulation and verdict rendering.
- Added /review command for interactive code review with branch comparison, uncommitted changes, and commit review modes.
- Updated bundled reviewer agent to use structured review tools with priority-based findings (P0-P3) and formal verdict submission.
This commit is contained in:
can1357
2026-01-03 05:08:20 +01:00
parent 83809771d9
commit c70303ade9
12 changed files with 729 additions and 138 deletions
+2
View File
@@ -174,6 +174,8 @@ export interface AgentTool<TParameters extends TSchema = TSchema, TDetails = any
extends Tool<TParameters> {
// A human-readable label for the tool to be displayed in UI
label: string;
/** If true, tool is excluded unless explicitly listed in --tools or agent's tools field */
hidden?: boolean;
execute: (
toolCallId: string,
params: Static<TParameters>,
+9
View File
@@ -1,13 +1,22 @@
# Changelog
## [Unreleased]
### Added
- Added `hidden` property for custom tools to exclude them from default tool list unless explicitly requested
- Added `explicitTools` option to `createAgentSession` for enabling hidden tools by name
- Added example review tools (`report_finding`, `submit_review`) with structured findings accumulation and verdict rendering
- Added `/review` example command for interactive code review with branch comparison, uncommitted changes, and commit review modes
- Custom TypeScript slash commands: Create programmable commands at `~/.pi/agent/commands/[name]/index.ts` or `.pi/commands/[name]/index.ts`. Commands export a factory returning `{ name, description, execute(args, ctx) }`. Return a string to send as LLM prompt, or void for fire-and-forget actions. Full access to `HookCommandContext` for UI dialogs, session control, and shell execution.
- Claude command directories: Markdown slash commands now also load from `~/.claude/commands/` and `.claude/commands/` (parallel to existing `.pi/commands/` support)
- `commands.enableClaudeUser` and `commands.enableClaudeProject` settings to disable Claude command directory loading
- `/export --copy` option to copy entire session as formatted text to clipboard
### Changed
- Updated bundled `reviewer` agent to use structured review tools with priority-based findings (P0-P3) and formal verdict submission
## [1.341.0] - 2026-01-03
### Added
@@ -0,0 +1,79 @@
# Review Tools
Structured code review tools with findings accumulation and verdict rendering.
## Components
- **`report_finding`** - Report individual findings with priority (P0-P3), location, confidence
- **`submit_review`** - Submit final verdict with grouped findings summary
- **`/review`** - Interactive command to launch code review
Both tools have `hidden: true` - they only appear when explicitly listed in an agent's tools.
## Installation
From the repository root:
```bash
# Install review tools
mkdir -p ~/.pi/agent/tools/review
ln -sf "$(pwd)/packages/coding-agent/examples/custom-tools/review/index.ts" ~/.pi/agent/tools/review/index.ts
# Install /review command
mkdir -p ~/.pi/agent/commands/review
ln -sf "$(pwd)/packages/coding-agent/examples/custom-tools/review/commands/review/index.ts" ~/.pi/agent/commands/review/index.ts
```
## Usage with Subagent
The `reviewer` agent in the subagent example uses these tools. Make sure both subagent and review are installed:
```bash
# Also install subagent tools if not already done
# See: examples/custom-tools/subagent/README.md
```
Then use `/review`:
```
/review
```
This opens an interactive menu:
1. Review against a base branch (PR style)
2. Review uncommitted changes
3. Review a specific commit
4. Custom review instructions
## Tool Schemas
### report_finding
```typescript
{
title: string; // ≤80 chars, prefixed with [P0-P3]
body: string; // Markdown explanation
priority: 0 | 1 | 2 | 3;
confidence: number; // 0.0-1.0
file_path: string;
line_start: number;
line_end: number;
}
```
### submit_review
```typescript
{
overall_correctness: "correct" | "incorrect";
explanation: string; // 1-3 sentences
confidence: number; // 0.0-1.0
}
```
## Priority Levels
- **P0**: Drop everything. Blocking release/operations.
- **P1**: Urgent. Address in next cycle.
- **P2**: Normal. Fix eventually.
- **P3**: Low. Nice to have.
@@ -0,0 +1,160 @@
/**
* /review command - Interactive code review launcher
*
* Provides a menu to select review mode:
* 1. Review against a base branch (PR style)
* 2. Review uncommitted changes
* 3. Review a specific commit
* 4. Custom review instructions
*/
import type { CustomCommandAPI, CustomCommandFactory } from "@oh-my-pi/pi-coding-agent";
const factory: CustomCommandFactory = (api: CustomCommandAPI) => {
return {
name: "review",
description: "Launch interactive code review",
async execute(_args, ctx) {
if (!ctx.hasUI) {
return "Review command requires interactive mode. Run `pi` without --print flag.";
}
// Main menu
const mode = await ctx.ui.select("Review Mode", [
"1. Review against a base branch (PR Style)",
"2. Review uncommitted changes",
"3. Review a specific commit",
"4. Custom review instructions",
]);
if (!mode) return;
const modeNum = parseInt(mode[0]);
switch (modeNum) {
case 1: {
// PR-style review against base branch
const branches = await getGitBranches(api);
if (branches.length === 0) {
ctx.ui.notify("No git branches found", "error");
return;
}
const baseBranch = await ctx.ui.select("Select base branch to compare against", branches);
if (!baseBranch) return;
const currentBranch = await getCurrentBranch(api);
return `Use the subagent tool to run the "reviewer" agent with this task:
Review the changes between "${baseBranch}" and "${currentBranch}".
Run \`git diff ${baseBranch}...${currentBranch}\` to see the changes, then analyze the modified files.`;
}
case 2: {
// Uncommitted changes
const status = await getGitStatus(api);
if (!status.trim()) {
ctx.ui.notify("No uncommitted changes found", "warning");
return;
}
return `Use the subagent tool to run the "reviewer" agent with this task:
Review all uncommitted changes in the working directory.
Run \`git diff\` for unstaged changes and \`git diff --cached\` for staged changes.`;
}
case 3: {
// Specific commit
const commits = await getRecentCommits(api, 20);
if (commits.length === 0) {
ctx.ui.notify("No commits found", "error");
return;
}
const selected = await ctx.ui.select("Select commit to review", commits);
if (!selected) return;
// Extract commit hash from selection (format: "abc1234 - message")
const hash = selected.split(" ")[0];
return `Use the subagent tool to run the "reviewer" agent with this task:
Review commit ${hash}.
Run \`git show ${hash}\` to see the changes introduced by this commit.`;
}
case 4: {
// Custom instructions
const instructions = await ctx.ui.editor(
"Enter custom review instructions",
"Review the following:\n\n",
);
if (!instructions?.trim()) return;
return `Use the subagent tool to run the "reviewer" agent with this task:
${instructions}`;
}
default:
return;
}
},
};
};
async function getGitBranches(api: CustomCommandAPI): Promise<string[]> {
try {
const result = await api.exec("git", ["branch", "-a", "--format=%(refname:short)"]);
if (result.code !== 0) return [];
return result.stdout
.split("\n")
.map((b: string) => b.trim())
.filter(Boolean);
} catch {
return [];
}
}
async function getCurrentBranch(api: CustomCommandAPI): Promise<string> {
try {
const result = await api.exec("git", ["branch", "--show-current"]);
return result.stdout.trim() || "HEAD";
} catch {
return "HEAD";
}
}
async function getGitStatus(api: CustomCommandAPI): Promise<string> {
try {
const result = await api.exec("git", ["status", "--porcelain"]);
return result.stdout;
} catch {
return "";
}
}
async function getRecentCommits(api: CustomCommandAPI, count: number): Promise<string[]> {
try {
const result = await api.exec("git", [
"log",
`-${count}`,
"--oneline",
"--no-decorate",
]);
if (result.code !== 0) return [];
return result.stdout
.split("\n")
.map((c: string) => c.trim())
.filter(Boolean);
} catch {
return [];
}
}
export default factory;
@@ -0,0 +1,291 @@
/**
* Review Tools - report_finding and submit_review
*
* Used by code review agents to report findings in a structured way.
* Both tools are hidden by default - only enabled when explicitly listed in agent's tools.
*
* - report_finding: Accumulates findings in session state
* - submit_review: Collects all findings and renders final verdict
*/
import type {
CustomTool,
CustomToolContext,
CustomToolFactory,
CustomToolSessionEvent,
} from "@oh-my-pi/pi-coding-agent";
interface Finding {
title: string;
body: string;
priority: 0 | 1 | 2 | 3;
confidence: number;
file_path: string;
line_start: number;
line_end: number;
}
interface FindingDetails {
finding: Finding;
findings: Finding[];
}
interface SubmitDetails {
overall_correctness: "correct" | "incorrect";
explanation: string;
confidence: number;
findings: Finding[];
}
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.",
};
const factory: CustomToolFactory = (pi) => {
const { Type } = pi.typebox;
const { Text, Container, Spacer, Markdown, StringEnum } = pi.pi;
// In-memory state (reconstructed from session on load)
let findings: Finding[] = [];
const reconstructState = (_event: CustomToolSessionEvent, ctx: CustomToolContext) => {
findings = [];
for (const entry of ctx.sessionManager.getBranch()) {
if (entry.type !== "message") continue;
const msg = entry.message;
if (msg.role !== "toolResult") continue;
if (msg.toolName === "report_finding") {
const details = msg.details as FindingDetails | undefined;
if (details?.findings) {
findings = details.findings;
}
} else if (msg.toolName === "submit_review") {
// After submit_review, findings are cleared for next review
findings = [];
}
}
};
// report_finding tool
const FindingParams = 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" }),
});
const reportFinding: CustomTool<typeof FindingParams, FindingDetails> = {
name: "report_finding",
label: "Report Finding",
description:
"Report a code review finding. Use this for each issue found. Call submit_review when done reviewing.",
parameters: FindingParams,
hidden: true,
onSession: reconstructState,
async execute(_toolCallId, params, _onUpdate, _ctx, _signal) {
const finding: Finding = {
title: params.title,
body: params.body,
priority: params.priority,
confidence: params.confidence,
file_path: params.file_path,
line_start: params.line_start,
line_end: params.line_end,
};
findings.push(finding);
return {
content: [
{
type: "text",
text: `Finding recorded: ${finding.title} (${findings.length} total)`,
},
],
details: { finding, findings: [...findings] },
};
},
renderCall(args, theme) {
const priority = PRIORITY_LABELS[args.priority] ?? "P?";
const color = args.priority === 0 ? "error" : args.priority === 1 ? "warning" : "muted";
return new Text(
`${theme.fg("toolTitle", theme.bold("report_finding "))}${theme.fg(color, `[${priority}]`)} ${theme.fg("dim", args.title.replace(/^\[P\d\]\s*/, ""))}`,
0,
0,
);
},
renderResult(result, _options, theme) {
const { details } = result;
if (!details) {
const text = result.content[0];
return new Text(text?.type === "text" ? text.text : "", 0, 0);
}
const { finding } = details;
const priority = PRIORITY_LABELS[finding.priority] ?? "P?";
const color = finding.priority === 0 ? "error" : finding.priority === 1 ? "warning" : "muted";
const location = `${finding.file_path}:${finding.line_start}${finding.line_end !== finding.line_start ? `-${finding.line_end}` : ""}`;
return new Text(
`${theme.fg("success", "✓")} ${theme.fg(color, `[${priority}]`)} ${theme.fg("dim", location)}`,
0,
0,
);
},
};
// submit_review tool
const SubmitParams = Type.Object({
overall_correctness: StringEnum(["correct", "incorrect"] as const, {
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",
}),
});
const submitReview: CustomTool<typeof SubmitParams, SubmitDetails> = {
name: "submit_review",
label: "Submit Review",
description:
"Submit the final review verdict. Call this after all findings have been reported. Summarizes all findings and provides overall assessment.",
parameters: SubmitParams,
hidden: true,
onSession: reconstructState,
async execute(_toolCallId, params, _onUpdate, _ctx, _signal) {
const result: SubmitDetails = {
overall_correctness: params.overall_correctness as "correct" | "incorrect",
explanation: params.explanation,
confidence: params.confidence,
findings: [...findings],
};
// Group findings by priority
const byPriority = findings.reduce(
(acc, f) => {
acc[f.priority] = acc[f.priority] || [];
acc[f.priority].push(f);
return acc;
},
{} as Record<number, Finding[]>,
);
let summary = `## Review Summary\n\n`;
summary += `**Verdict:** ${params.overall_correctness === "correct" ? "✓ Patch is correct" : "✗ Patch is incorrect"}\n`;
summary += `**Confidence:** ${(params.confidence * 100).toFixed(0)}%\n\n`;
summary += `${params.explanation}\n\n`;
if (findings.length > 0) {
summary += `### Findings (${findings.length})\n\n`;
for (const priority of [0, 1, 2, 3]) {
const group = byPriority[priority];
if (!group || group.length === 0) continue;
summary += `#### ${PRIORITY_LABELS[priority]} - ${PRIORITY_DESCRIPTIONS[priority]}\n\n`;
for (const f of group) {
const location = `${f.file_path}:${f.line_start}${f.line_end !== f.line_start ? `-${f.line_end}` : ""}`;
summary += `- **${f.title}** (${location})\n ${f.body}\n\n`;
}
}
} else {
summary += `No findings reported.\n`;
}
// Clear findings for next review
const savedFindings = [...findings];
findings = [];
return {
content: [{ type: "text", text: summary }],
details: { ...result, findings: savedFindings },
};
},
renderCall(args, theme) {
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 * 100).toFixed(0)}%)`)}`,
0,
0,
);
},
renderResult(result, { expanded }, theme) {
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.length > 0) {
container.addChild(new Spacer(1));
container.addChild(
new Text(theme.fg("muted", `${details.findings.length} finding(s) reported`), 0, 0),
);
}
if (expanded && details.findings.length > 0) {
container.addChild(new Spacer(1));
for (const f of details.findings) {
const priority = PRIORITY_LABELS[f.priority] ?? "P?";
const color = f.priority === 0 ? "error" : f.priority === 1 ? "warning" : "dim";
const location = `${f.file_path}:${f.line_start}`;
container.addChild(
new Text(` ${theme.fg(color, `[${priority}]`)} ${theme.fg("dim", location)}`, 0, 0),
);
}
}
return container;
},
};
return [reportFinding, submitReview];
};
export default factory;
@@ -1,35 +1,60 @@
---
name: reviewer
description: Code review specialist for quality and security analysis
tools: read, grep, find, ls, bash
tools: read, grep, find, ls, bash, report_finding, submit_review
model: claude-sonnet-4-5
---
You are a senior code reviewer. Analyze code for quality, security, and maintainability.
You are acting as a reviewer for a proposed code change made by another engineer.
Bash is for read-only commands only: `git diff`, `git log`, `git show`. Do NOT modify files or run builds.
Assume tool permissions are not perfectly enforceable; keep all bash usage strictly read-only.
Strategy:
1. Run `git diff` to see recent changes (if applicable)
2. Read the modified files
3. Check for bugs, security issues, code smells
# Review Strategy
Output format:
1. Run `git diff` to see the changes being reviewed
2. Read the modified files for full context
3. Analyze for bugs, security issues, and code quality problems
4. Use `report_finding` for each issue found
5. Use `submit_review` to provide final verdict
## Files Reviewed
- `path/to/file.ts` (lines X-Y)
# What to Flag
## Critical (must fix)
- `file.ts:42` - Issue description
Only flag issues where ALL of these apply:
## Warnings (should fix)
- `file.ts:100` - Issue description
1. It meaningfully impacts the accuracy, performance, security, or maintainability of the code
2. The bug is discrete and actionable (not a general issue or combination of multiple issues)
3. Fixing it doesn't demand rigor not present elsewhere in the codebase
4. The bug was introduced in this commit (don't flag pre-existing bugs)
5. The author would likely fix the issue if made aware of it
6. The bug doesn't rely on unstated assumptions about the codebase or author's intent
7. You can identify specific code that is provably affected (speculation is not enough)
8. The issue is clearly not an intentional change by the author
## Suggestions (consider)
- `file.ts:150` - Improvement idea
# Priority Levels
## Summary
Overall assessment in 2-3 sentences.
- **P0**: Drop everything to fix. Blocking release, operations, or major usage. Only use for universal issues that do not depend on assumptions about inputs.
- **P1**: Urgent. Should be addressed in the next cycle.
- **P2**: Normal. To be fixed eventually.
- **P3**: Low. Nice to have.
Be specific with file paths and line numbers.
# Comment Guidelines
1. Be clear about WHY the issue is a bug
2. Communicate severity appropriately - don't overstate
3. Keep body to one paragraph max
4. Code snippets should be ≤3 lines, wrapped in markdown code tags
5. Clearly state what conditions are necessary for the bug to arise
6. Tone: matter-of-fact, not accusatory or overly positive
7. Write so the author can immediately grasp the idea without close reading
8. Avoid flattery and phrases like "Great job...", "Thanks for..."
# Output
- Use `report_finding` for each issue. Continue until you've listed every qualifying finding.
- If there is no finding that a person would definitely want to fix, prefer outputting no findings.
- Ignore trivial style unless it obscures meaning or violates documented standards.
- Use `submit_review` at the end with your overall verdict:
- **correct**: Existing code and tests will not break, patch is free of bugs and blocking issues
- **incorrect**: Has bugs or blocking issues that must be addressed
Ignore non-blocking issues (style, formatting, typos, documentation, nits) when determining correctness.
@@ -9,11 +9,11 @@ import type { AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agen
import type { Model } from "@oh-my-pi/pi-ai";
import type { Component } from "@oh-my-pi/pi-tui";
import type { Static, TSchema } from "@sinclair/typebox";
import type { Theme } from "../../modes/interactive/theme/theme.js";
import type { ExecOptions, ExecResult } from "../exec.js";
import type { HookUIContext } from "../hooks/types.js";
import type { ModelRegistry } from "../model-registry.js";
import type { ReadonlySessionManager } from "../session-manager.js";
import type { Theme } from "../../modes/interactive/theme/theme";
import type { ExecOptions, ExecResult } from "../exec";
import type { HookUIContext } from "../hooks/types";
import type { ModelRegistry } from "../model-registry";
import type { ReadonlySessionManager } from "../session-manager";
/** Alias for clarity */
export type CustomToolUIContext = HookUIContext;
@@ -22,7 +22,7 @@ export type CustomToolUIContext = HookUIContext;
export type { AgentToolResult, AgentToolUpdateCallback };
// Re-export for backward compatibility
export type { ExecOptions, ExecResult } from "../exec.js";
export type { ExecOptions, ExecResult } from "../exec";
/** API passed to custom tool factory (stable across session changes) */
export interface CustomToolAPI {
@@ -119,6 +119,8 @@ export interface CustomTool<TParams extends TSchema = TSchema, TDetails = any> {
description: string;
/** Parameter schema (TypeBox) */
parameters: TParams;
/** If true, tool is excluded unless explicitly listed in --tools or agent's tools field */
hidden?: boolean;
/**
* Execute the tool.
@@ -3,7 +3,7 @@
*/
import type { AgentTool } from "@oh-my-pi/pi-agent-core";
import type { CustomTool, CustomToolContext, LoadedCustomTool } from "./types.js";
import type { CustomTool, CustomToolContext, LoadedCustomTool } from "./types";
/**
* Wrap a CustomTool into an AgentTool.
@@ -15,6 +15,7 @@ export function wrapCustomTool(tool: CustomTool, getContext: () => CustomToolCon
label: tool.label,
description: tool.description,
parameters: tool.parameters,
hidden: tool.hidden,
execute: (toolCallId, params, signal, onUpdate, context) =>
tool.execute(toolCallId, params, onUpdate, context ?? getContext(), signal),
};
+39 -28
View File
@@ -29,38 +29,38 @@
* ```
*/
import { join } from "node:path";
import { Agent, type ThinkingLevel } from "@oh-my-pi/pi-agent-core";
import type { Model } from "@oh-my-pi/pi-ai";
import { join } from "path";
import { getAgentDir } from "../config.js";
import { AgentSession } from "./agent-session.js";
import { AuthStorage } from "./auth-storage.js";
import { getAgentDir } from "../config";
import { AgentSession } from "./agent-session";
import { AuthStorage } from "./auth-storage";
import {
type CustomCommandsLoadResult,
loadCustomCommands as loadCustomCommandsInternal,
} from "./custom-commands/index.js";
} from "./custom-commands/index";
import {
type CustomToolsLoadResult,
discoverAndLoadCustomTools,
type LoadedCustomTool,
wrapCustomTools,
} from "./custom-tools/index.js";
import type { CustomTool } from "./custom-tools/types.js";
import { discoverAndLoadHooks, HookRunner, type LoadedHook, wrapToolsWithHooks } from "./hooks/index.js";
import type { HookFactory } from "./hooks/types.js";
import { discoverAndLoadMCPTools, type MCPManager, type MCPToolsLoadResult } from "./mcp/index.js";
import { convertToLlm } from "./messages.js";
import { ModelRegistry } from "./model-registry.js";
import { SessionManager } from "./session-manager.js";
import { type CommandsSettings, type Settings, SettingsManager, type SkillsSettings } from "./settings-manager.js";
import { loadSkills as loadSkillsInternal, type Skill } from "./skills.js";
import { type FileSlashCommand, loadSlashCommands as loadSlashCommandsInternal } from "./slash-commands.js";
} from "./custom-tools/index";
import type { CustomTool } from "./custom-tools/types";
import { discoverAndLoadHooks, HookRunner, type LoadedHook, wrapToolsWithHooks } from "./hooks/index";
import type { HookFactory } from "./hooks/types";
import { discoverAndLoadMCPTools, type MCPManager, type MCPToolsLoadResult } from "./mcp/index";
import { convertToLlm } from "./messages";
import { ModelRegistry } from "./model-registry";
import { SessionManager } from "./session-manager";
import { type CommandsSettings, type Settings, SettingsManager, type SkillsSettings } from "./settings-manager";
import { loadSkills as loadSkillsInternal, type Skill } from "./skills";
import { type FileSlashCommand, loadSlashCommands as loadSlashCommandsInternal } from "./slash-commands";
import {
buildSystemPrompt as buildSystemPromptInternal,
loadProjectContextFiles as loadContextFilesInternal,
} from "./system-prompt.js";
import { time } from "./timings.js";
import { createToolContextStore } from "./tools/context.js";
} from "./system-prompt";
import { time } from "./timings";
import { createToolContextStore } from "./tools/context";
import {
allTools,
applyBashInterception,
@@ -84,7 +84,7 @@ import {
type Tool,
warmupLspServers,
writeTool,
} from "./tools/index.js";
} from "./tools/index";
// Types
@@ -131,6 +131,9 @@ export interface CreateAgentSessionOptions {
/** Enable MCP server discovery from .mcp.json files. Default: true */
enableMCP?: boolean;
/** Tool names explicitly requested (enables disabled-by-default tools) */
explicitTools?: string[];
/** Session manager. Default: SessionManager.create(cwd) */
sessionManager?: SessionManager;
@@ -157,14 +160,14 @@ export interface CreateAgentSessionResult {
// Re-exports
export type { CustomCommand, CustomCommandFactory } from "./custom-commands/types.js";
export type { CustomTool } from "./custom-tools/types.js";
export type { HookAPI, HookCommandContext, HookContext, HookFactory } from "./hooks/types.js";
export type { MCPManager, MCPServerConfig, MCPServerConnection, MCPToolsLoadResult } from "./mcp/index.js";
export type { Settings, SkillsSettings } from "./settings-manager.js";
export type { Skill } from "./skills.js";
export type { FileSlashCommand } from "./slash-commands.js";
export type { Tool } from "./tools/index.js";
export type { CustomCommand, CustomCommandFactory } from "./custom-commands/types";
export type { CustomTool } from "./custom-tools/types";
export type { HookAPI, HookCommandContext, HookContext, HookFactory } from "./hooks/types";
export type { MCPManager, MCPServerConfig, MCPServerConnection, MCPToolsLoadResult } from "./mcp/index";
export type { Settings, SkillsSettings } from "./settings-manager";
export type { Skill } from "./skills";
export type { FileSlashCommand } from "./slash-commands";
export type { Tool } from "./tools/index";
export {
// Pre-built tools (use process.cwd())
@@ -719,6 +722,14 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
};
let allToolsArray: Tool[] = [...builtInTools, ...wrappedCustomTools];
// Filter out hidden tools unless explicitly requested
if (options.explicitTools) {
const explicitSet = new Set(options.explicitTools);
allToolsArray = allToolsArray.filter((tool) => !tool.hidden || explicitSet.has(tool.name));
} else {
allToolsArray = allToolsArray.filter((tool) => !tool.hidden);
}
time("combineTools");
// Apply bash interception to redirect common shell patterns to proper tools (if enabled)
@@ -1,59 +1,60 @@
---
name: reviewer
description: Expert code reviewer for PRs and implementation changes
tools: read, grep, glob, ls, bash
description: Code review specialist for quality and security analysis
tools: read, grep, glob, ls, bash, report_finding, submit_review
model: pi/slow, gpt-5.2-codex, gpt-5.2, codex, gpt
---
You are an expert code reviewer. Analyze code changes and provide thorough reviews.
You are acting as a reviewer for a proposed code change made by another engineer.
## For PR Reviews
Bash is for read-only commands only: `git diff`, `git log`, `git show`, `gh pr diff`. Do NOT modify files or run builds.
1. If no PR number provided, run `gh pr list` to show open PRs
2. If PR number provided:
- `gh pr view <number>` to get PR details
- `gh pr diff <number>` to get the diff
3. Analyze changes and provide review
# Review Strategy
## For Implementation Reviews
1. Run `git diff` (or `gh pr diff <number>`) to see the changes
2. Read the modified files for full context
3. Analyze for bugs, security issues, and code quality problems
4. Use `report_finding` for each issue found
5. Use `submit_review` to provide final verdict
When reviewing implementation output from another agent:
# What to Flag
1. Read the files that were changed
2. Understand the context and requirements
3. Analyze the implementation quality
Only flag issues where ALL of these apply:
## Review Focus
1. It meaningfully impacts the accuracy, performance, security, or maintainability of the code
2. The bug is discrete and actionable (not a general issue or combination of multiple issues)
3. Fixing it doesn't demand rigor not present elsewhere in the codebase
4. The bug was introduced in this commit (don't flag pre-existing bugs)
5. The author would likely fix the issue if made aware of it
6. The bug doesn't rely on unstated assumptions about the codebase or author's intent
7. You can identify specific code that is provably affected (speculation is not enough)
8. The issue is clearly not an intentional change by the author
- **Correctness**: Does the code do what it's supposed to?
- **Project Conventions**: Does it follow existing patterns?
- **Performance**: Any performance implications?
- **Test Coverage**: Are changes adequately tested?
- **Security**: Any security considerations?
- **Edge Cases**: Are edge cases handled?
# Priority Levels
## Output Format
- **P0**: Drop everything to fix. Blocking release, operations, or major usage. Only use for universal issues that do not depend on assumptions about inputs.
- **P1**: Urgent. Should be addressed in the next cycle.
- **P2**: Normal. To be fixed eventually.
- **P3**: Low. Nice to have.
### Overview
# Comment Guidelines
What the changes do.
1. Be clear about WHY the issue is a bug
2. Communicate severity appropriately - don't overstate
3. Keep body to one paragraph max
4. Code snippets should be ≤3 lines, wrapped in markdown code tags
5. Clearly state what conditions are necessary for the bug to arise
6. Tone: matter-of-fact, not accusatory or overly positive
7. Write so the author can immediately grasp the idea without close reading
8. Avoid flattery and phrases like "Great job...", "Thanks for..."
### Strengths
# Output
What's done well.
- Use `report_finding` for each issue. Continue until you've listed every qualifying finding.
- If there is no finding that a person would definitely want to fix, prefer outputting no findings.
- Ignore trivial style unless it obscures meaning or violates documented standards.
- Use `submit_review` at the end with your overall verdict:
- **correct**: Existing code and tests will not break, patch is free of bugs and blocking issues
- **incorrect**: Has bugs or blocking issues that must be addressed
### Issues
Problems that should be fixed (with file:line references).
### Suggestions
Improvements to consider (optional, not blocking).
### Verdict
- ✅ **Approve**: Ready to merge/complete
- 🔄 **Request Changes**: Issues must be addressed
- 💬 **Comment**: Minor suggestions, can proceed
Keep reviews concise but thorough. Focus on substance over style nitpicks.
Ignore non-blocking issues (style, formatting, typos, documentation, nits) when determining correctness.
+27 -18
View File
@@ -10,9 +10,9 @@ export {
type ModelCycleResult,
type PromptOptions,
type SessionStats,
} from "./core/agent-session.js";
} from "./core/agent-session";
// Auth and model registry
export { type ApiKeyCredential, type AuthCredential, AuthStorage, type OAuthCredential } from "./core/auth-storage.js";
export { type ApiKeyCredential, type AuthCredential, AuthStorage, type OAuthCredential } from "./core/auth-storage";
// Compaction
export {
type BranchPreparation,
@@ -35,7 +35,16 @@ export {
prepareBranchEntries,
serializeConversation,
shouldCompact,
} from "./core/compaction/index.js";
} from "./core/compaction/index";
// Custom commands
export type {
CustomCommand,
CustomCommandAPI,
CustomCommandFactory,
CustomCommandSource,
CustomCommandsLoadResult,
LoadedCustomCommand,
} from "./core/custom-commands/types";
// Custom tools
export type {
AgentToolUpdateCallback,
@@ -49,9 +58,9 @@ export type {
ExecResult,
LoadedCustomTool,
RenderResultOptions,
} from "./core/custom-tools/index.js";
export { discoverAndLoadCustomTools, loadCustomTools } from "./core/custom-tools/index.js";
export type * from "./core/hooks/index.js";
} from "./core/custom-tools/index";
export { discoverAndLoadCustomTools, loadCustomTools } from "./core/custom-tools/index";
export type * from "./core/hooks/index";
// Hook system types and type guards
export {
isBashToolResult,
@@ -61,9 +70,9 @@ export {
isLsToolResult,
isReadToolResult,
isWriteToolResult,
} from "./core/hooks/index.js";
export { convertToLlm } from "./core/messages.js";
export { ModelRegistry } from "./core/model-registry.js";
} from "./core/hooks/index";
export { convertToLlm } from "./core/messages";
export { ModelRegistry } from "./core/model-registry";
// SDK for programmatic usage
export {
type BuildSystemPromptOptions,
@@ -98,7 +107,7 @@ export {
loadSettings,
// Pre-built tools (use process.cwd())
readOnlyTools,
} from "./core/sdk.js";
} from "./core/sdk";
export {
type BranchSummaryEntry,
buildSessionContext,
@@ -120,7 +129,7 @@ export {
SessionManager,
type SessionMessageEntry,
type ThinkingLevelChangeEntry,
} from "./core/session-manager.js";
} from "./core/session-manager";
export {
type CompactionSettings,
type LspSettings,
@@ -128,7 +137,7 @@ export {
type Settings,
SettingsManager,
type SkillsSettings,
} from "./core/settings-manager.js";
} from "./core/settings-manager";
// Skills
export {
formatSkillsForPrompt,
@@ -139,7 +148,7 @@ export {
type Skill,
type SkillFrontmatter,
type SkillWarning,
} from "./core/skills.js";
} from "./core/skills";
// Tools
export {
type BashToolDetails,
@@ -159,14 +168,14 @@ export {
type WriteToolDetails,
type WriteToolOptions,
writeTool,
} from "./core/tools/index.js";
export type { FileDiagnosticsResult } from "./core/tools/lsp/index.js";
} from "./core/tools/index";
export type { FileDiagnosticsResult } from "./core/tools/lsp/index";
// Main entry point
export { main } from "./main.js";
export { main } from "./main";
// UI components for hooks and custom tools
export { BorderedLoader } from "./modes/interactive/components/bordered-loader.js";
export { BorderedLoader } from "./modes/interactive/components/bordered-loader";
// Theme utilities for custom tools
export { getMarkdownTheme } from "./modes/interactive/theme/theme.js";
export { getMarkdownTheme } from "./modes/interactive/theme/theme";
// TypeBox helper for string enums (convenience for custom tools)
import { type TSchema, Type } from "@sinclair/typebox";
+26 -25
View File
@@ -5,33 +5,33 @@
* createAgentSession() options. The SDK does the heavy lifting.
*/
import { existsSync } from "node:fs";
import { join } from "node:path";
import { type ImageContent, supportsXhigh } from "@oh-my-pi/pi-ai";
import chalk from "chalk";
import { existsSync } from "fs";
import { join } from "path";
import { type Args, parseArgs, printHelp } from "./cli/args.js";
import { processFileArguments } from "./cli/file-processor.js";
import { listModels } from "./cli/list-models.js";
import { parsePluginArgs, printPluginHelp, runPluginCommand } from "./cli/plugin-cli.js";
import { selectSession } from "./cli/session-picker.js";
import { CONFIG_DIR_NAME, getAgentDir, getModelsPath, VERSION } from "./config.js";
import type { AgentSession } from "./core/agent-session.js";
import type { LoadedCustomTool } from "./core/custom-tools/index.js";
import { exportFromFile } from "./core/export-html/index.js";
import type { HookUIContext } from "./core/index.js";
import type { ModelRegistry } from "./core/model-registry.js";
import { resolveModelScope, type ScopedModel } from "./core/model-resolver.js";
import { type CreateAgentSessionOptions, createAgentSession, discoverAuthStorage, discoverModels } from "./core/sdk.js";
import { SessionManager } from "./core/session-manager.js";
import { SettingsManager } from "./core/settings-manager.js";
import { resolvePromptInput } from "./core/system-prompt.js";
import { printTimings, time } from "./core/timings.js";
import { allTools } from "./core/tools/index.js";
import { runMigrations } from "./migrations.js";
import { InteractiveMode, installTerminalCrashHandlers, runPrintMode, runRpcMode } from "./modes/index.js";
import { initTheme, stopThemeWatcher } from "./modes/interactive/theme/theme.js";
import { getChangelogPath, getNewEntries, parseChangelog } from "./utils/changelog.js";
import { ensureTool } from "./utils/tools-manager.js";
import { type Args, parseArgs, printHelp } from "./cli/args";
import { processFileArguments } from "./cli/file-processor";
import { listModels } from "./cli/list-models";
import { parsePluginArgs, printPluginHelp, runPluginCommand } from "./cli/plugin-cli";
import { selectSession } from "./cli/session-picker";
import { CONFIG_DIR_NAME, getAgentDir, getModelsPath, VERSION } from "./config";
import type { AgentSession } from "./core/agent-session";
import type { LoadedCustomTool } from "./core/custom-tools/index";
import { exportFromFile } from "./core/export-html/index";
import type { HookUIContext } from "./core/index";
import type { ModelRegistry } from "./core/model-registry";
import { resolveModelScope, type ScopedModel } from "./core/model-resolver";
import { type CreateAgentSessionOptions, createAgentSession, discoverAuthStorage, discoverModels } from "./core/sdk";
import { SessionManager } from "./core/session-manager";
import { SettingsManager } from "./core/settings-manager";
import { resolvePromptInput } from "./core/system-prompt";
import { printTimings, time } from "./core/timings";
import { allTools } from "./core/tools/index";
import { runMigrations } from "./migrations";
import { InteractiveMode, installTerminalCrashHandlers, runPrintMode, runRpcMode } from "./modes/index";
import { initTheme, stopThemeWatcher } from "./modes/interactive/theme/theme";
import { getChangelogPath, getNewEntries, parseChangelog } from "./utils/changelog";
import { ensureTool } from "./utils/tools-manager";
async function checkForNewVersion(currentVersion: string): Promise<string | undefined> {
try {
@@ -269,6 +269,7 @@ function buildSessionOptions(
// Tools
if (parsed.tools) {
options.tools = parsed.tools.map((name) => allTools[name]);
options.explicitTools = parsed.tools;
}
// Skills