From 7246e2b421fe9526ab7db1085aa78734a70d7159 Mon Sep 17 00:00:00 2001 From: svtter Date: Tue, 19 May 2026 18:03:32 +0800 Subject: [PATCH 1/3] feat: add multi-agent parallel review with dimension-based sub-agents Add parallel review mode that dispatches specialized dimension sub-agents (code-quality, security, performance, testing, documentation) and collects results into a unified report. Includes dimension prompt templates with bilingual support (zh/en) and orchestrator prompt for coordinating sub-agents. Co-Authored-By: Claude Opus 4.7 --- src/agent.ts | 98 ++++++++++++++++++++++++++++ src/config.ts | 2 + src/dimensions/index.ts | 138 ++++++++++++++++++++++++++++++++++++++++ src/index.ts | 19 ++++++ 4 files changed, 257 insertions(+) create mode 100644 src/dimensions/index.ts diff --git a/src/agent.ts b/src/agent.ts index 266d681..ba61f4c 100644 --- a/src/agent.ts +++ b/src/agent.ts @@ -1,4 +1,5 @@ import type { ReviewConfig } from "./config.ts" +import { getDimensionPrompts } from "./dimensions/index.ts" const DIMENSION_LABELS: Record = { "code-quality": { @@ -39,6 +40,103 @@ function buildCustomRules(rules: string[]): string { } export function buildAgentPrompt(config: ReviewConfig): string { + if (config.parallel) { + return buildParallelPrompt(config) + } + return buildSinglePrompt(config) +} + +function buildParallelPrompt(config: ReviewConfig): string { + const isZh = config.language === "zh" + const dimensions = getDimensionPrompts(config) + const dimensionList = dimensions.map((d) => `- ${d.agentName}: ${d.name}`).join("\n") + + if (isZh) { + return `你是一个代码审查调度器。你的任务是并行调度多个维度审查子代理,收集结果,并生成统一报告。 + +## 可用维度代理 +${dimensionList} + +## 工作流程 +1. 使用 \`review_changes\` 工具获取 diff(默认 scope 为 staged) +2. 对每个启用的维度,使用 \`task\` 工具 spawn 对应的子代理: + - agent: \`<维度代理名>\` + - message: "请审查以下代码变更" + diff 摘要 +3. 收集所有维度代理的结果 +4. 按严重性分类合并结果: + - 关键问题(🔴)→ 建议改进(🟡)→ 亮点(✅) +5. 对同一代码位置的重复发现进行合并 +6. 输出统一报告 + +## 输出格式 + +\`\`\` +## 审查结果 + +### 总体评价 +[简要描述代码质量] + +### 关键问题 :red_circle: +[必须修复的问题,引用 file_path:line_number] + +### 建议改进 :yellow_circle: +[可选的优化建议,引用 file_path:line_number] + +### 亮点 :white_check_mark: +[代码中做得好的地方] +\`\`\` + +## 自动修复 + +如果任何维度代理发现了关键问题(🔴),你必须: +1. 汇总所有关键问题 +2. 使用 \`task\` 工具 spawn \`review:fixer\` 子代理,传入所有关键问题的修复指令 +3. 等待 fixer 完成修复` + } + + return `You are a code review orchestrator. Your task is to dispatch multiple dimension review sub-agents in parallel, collect results, and produce a unified report. + +## Available Dimension Agents +${dimensionList} + +## Workflow +1. Use the \`review_changes\` tool to get the diff (default scope is "staged") +2. For each enabled dimension, use the \`task\` tool to spawn the corresponding sub-agent: + - agent: \`\` + - message: "Review the following code changes" + diff summary +3. Collect all dimension agent results +4. Merge results by severity: + - Critical (🔴) → Suggestions (🟡) → Highlights (✅) +5. Deduplicate overlapping findings at the same code location +6. Output a unified report + +## Output Format + +\`\`\` +## Review Results + +### Overall Assessment +[Brief description of code quality] + +### Critical Issues :red_circle: +[Must-fix issues, reference file_path:line_number] + +### Suggestions :yellow_circle: +[Optional improvements, reference file_path:line_number] + +### Highlights :white_check_mark: +[Good practices found in the code] +\`\`\` + +## Auto-Fix + +If any dimension agent finds critical issues (🔴), you MUST: +1. Collect all critical issues across dimensions +2. Use the \`task\` tool to spawn a \`review:fixer\` sub-agent with combined fix instructions +3. Wait for the fixer to complete` +} + +function buildSinglePrompt(config: ReviewConfig): string { const isZh = config.language === "zh" if (isZh) { diff --git a/src/config.ts b/src/config.ts index 15c507d..586ce13 100644 --- a/src/config.ts +++ b/src/config.ts @@ -11,6 +11,7 @@ export interface ReviewConfig { cooldown_seconds: number } custom_rules: string[] + parallel: boolean } const DEFAULT_CONFIG: ReviewConfig = { @@ -28,6 +29,7 @@ const DEFAULT_CONFIG: ReviewConfig = { cooldown_seconds: 120, }, custom_rules: [], + parallel: true, } const CONFIG_FILENAME = "review.json" diff --git a/src/dimensions/index.ts b/src/dimensions/index.ts new file mode 100644 index 0000000..69e8ab7 --- /dev/null +++ b/src/dimensions/index.ts @@ -0,0 +1,138 @@ +import type { ReviewConfig } from "../config.ts" + +export interface DimensionPrompt { + name: string + agentName: string + prompt: string +} + +const DIMENSIONS: Record = { + "code-quality": { + zh: `你是一个专注于**代码质量**审查的专家。使用 \`review_changes\` 工具获取代码变更,然后进行审查。 + +## 审查要点 +- 可读性:命名是否清晰、代码是否自解释 +- 结构:函数/方法是否过长、职责是否单一 +- 规范:是否符合项目编码规范 +- 重复代码:是否存在可提取的重复逻辑 +- 错误处理:是否有适当的异常处理`, + en: `You are an expert reviewer focused on **code quality**. Use the \`review_changes\` tool to get code changes, then review them. + +## Review Focus +- Readability: clear naming, self-explanatory code +- Structure: function/method length, single responsibility +- Conventions: adherence to project coding standards +- Duplication: extractable repeated logic +- Error handling: appropriate exception handling`, + }, + "security": { + zh: `你是一个专注于**安全性**审查的专家。使用 \`review_changes\` 工具获取代码变更,然后进行审查。 + +## 审查要点 +- 输入验证:用户输入是否经过校验和清洗 +- 注入防护:SQL 注入、XSS、命令注入、路径遍历 +- 认证授权:权限检查是否完整、会话管理是否安全 +- 敏感信息:是否有硬编码的密钥/密码、日志中是否泄露敏感数据 +- 加密:是否使用安全的加密算法和协议 +- 依赖安全:是否引入已知有漏洞的依赖`, + en: `You are an expert reviewer focused on **security**. Use the \`review_changes\` tool to get code changes, then review them. + +## Review Focus +- Input validation: user input sanitization and validation +- Injection prevention: SQL injection, XSS, command injection, path traversal +- Authentication & authorization: permission checks, session management +- Sensitive data: hardcoded secrets, credential leaks in logs +- Cryptography: secure algorithms and protocols +- Dependency security: known vulnerable dependencies`, + }, + "performance": { + zh: `你是一个专注于**性能**审查的专家。使用 \`review_changes\` 工具获取代码变更,然后进行审查。 + +## 审查要点 +- 算法复杂度:是否有不必要的嵌套循环、时间复杂度是否合理 +- 数据库查询:N+1 查询、缺少索引、不必要的全表扫描 +- 内存使用:大对象未释放、内存泄漏风险、不必要的深拷贝 +- I/O 操作:同步阻塞操作、不必要的文件/网络请求 +- 缓存:是否应该使用缓存、缓存策略是否合理 +- 并发:是否有竞态条件、锁粒度是否合理`, + en: `You are an expert reviewer focused on **performance**. Use the \`review_changes\` tool to get code changes, then review them. + +## Review Focus +- Algorithm complexity: unnecessary nested loops, time complexity +- Database queries: N+1 queries, missing indexes, full table scans +- Memory usage: large objects, memory leak risks, unnecessary deep copies +- I/O operations: blocking synchronous calls, redundant file/network requests +- Caching: appropriate cache usage and strategies +- Concurrency: race conditions, lock granularity`, + }, + "testing": { + zh: `你是一个专注于**测试**审查的专家。使用 \`review_changes\` 工具获取代码变更,然后进行审查。 + +## 审查要点 +- 测试覆盖:新增/修改的代码是否有对应的测试 +- 边界条件:是否测试了空值、零值、边界、异常路径 +- 集成测试:模块间交互是否有测试保障 +- 测试质量:测试是否有意义(不是无用的断言)、mock 是否合理 +- 回归风险:修改的代码是否可能破坏现有测试`, + en: `You are an expert reviewer focused on **testing**. Use the \`review_changes\` tool to get code changes, then review them. + +## Review Focus +- Test coverage: do new/modified code paths have corresponding tests +- Edge cases: null, zero, boundary, error path testing +- Integration tests: inter-module interaction coverage +- Test quality: meaningful assertions, appropriate mocking +- Regression risk: could changes break existing tests`, + }, + "documentation": { + zh: `你是一个专注于**文档**审查的专家。使用 \`review_changes\` 工具获取代码变更,然后进行审查。 + +## 审查要点 +- 注释:复杂逻辑是否有必要的注释、注释是否准确 +- API 文档:公共接口是否有文档说明(参数、返回值、异常) +- README/CHANGELOG:是否需要更新项目文档 +- 类型文档:TypeScript 类型是否自解释、复杂类型是否有说明 +- 示例代码:新功能是否需要使用示例`, + en: `You are an expert reviewer focused on **documentation**. Use the \`review_changes\` tool to get code changes, then review them. + +## Review Focus +- Comments: necessary comments for complex logic, accuracy of existing comments +- API docs: public interfaces documented (params, returns, exceptions) +- README/CHANGELOG: project-level docs need updating +- Type docs: TypeScript types self-explanatory, complex types documented +- Examples: usage examples needed for new features`, + }, +} + +const OUTPUT_FORMAT: Record = { + zh: `## 输出格式 +对每个发现,使用以下格式: +- 🔴 **[file_path:line_number]** 关键问题:描述 +- 🟡 **[file_path:line_number]** 建议:描述 +- ✅ **[file_path:line_number]** 亮点:描述 + +如果没有发现,输出"该维度未发现问题"。`, + en: `## Output Format +For each finding, use: +- 🔴 **[file_path:line_number]** Critical: description +- 🟡 **[file_path:line_number]** Suggestion: description +- ✅ **[file_path:line_number]** Highlight: description + +If no issues found, output "No issues found for this dimension."`, +} + +function buildDimensionPrompt(dimension: string, config: ReviewConfig): string { + const content = DIMENSIONS[dimension] + if (!content) return "" + const lang = config.language === "zh" ? "zh" : "en" + return content[lang] + "\n\n" + OUTPUT_FORMAT[lang] +} + +export function getDimensionPrompts(config: ReviewConfig): DimensionPrompt[] { + return config.dimensions + .filter((dim) => DIMENSIONS[dim]) + .map((dim) => ({ + name: dim, + agentName: `review:dim-${dim}`, + prompt: buildDimensionPrompt(dim, config), + })) +} diff --git a/src/index.ts b/src/index.ts index aa4bce4..a4f1ea5 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,12 +1,14 @@ import type { Plugin } from "@opencode-ai/plugin" import { loadConfig } from "./config.ts" import { buildAgentPrompt, buildFixerPrompt } from "./agent.ts" +import { getDimensionPrompts } from "./dimensions/index.ts" import { reviewChanges } from "./tools/index.ts" const opencodeReview: Plugin = async ({ project, client, $, directory, worktree }) => { const config = await loadConfig(directory) const agentPrompt = buildAgentPrompt(config) const fixerPrompt = buildFixerPrompt(config) + const dimensionPrompts = getDimensionPrompts(config) let lastAutoReviewTime = 0 @@ -49,6 +51,23 @@ const opencodeReview: Plugin = async ({ project, client, $, directory, worktree prompt: fixerPrompt, } + if (config.parallel) { + for (const dim of dimensionPrompts) { + openCodeConfig.agent[dim.agentName] = { + mode: "subagent", + temperature: 0.1, + steps: 15, + tools: { + write: false, + edit: false, + bash: false, + task: false, + }, + prompt: dim.prompt, + } + } + } + openCodeConfig.command ??= {} openCodeConfig.command["review"] = { agent: "review", From 3054854f950343895a0433c9cfa189ced8505e58 Mon Sep 17 00:00:00 2001 From: svtter Date: Tue, 19 May 2026 18:21:56 +0800 Subject: [PATCH 2/3] fix: increase subagent steps to 30 for larger diffs Co-Authored-By: Claude Opus 4.7 --- src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index a3bd024..87d2a15 100644 --- a/src/index.ts +++ b/src/index.ts @@ -57,7 +57,7 @@ const opencodeReview: Plugin = async ({ project, client, $, directory, worktree openCodeConfig.agent[dim.agentName] = { mode: "subagent", temperature: 0.1, - steps: 15, + steps: 30, tools: { write: false, edit: false, From 3f37a571651f2f4e3e070fe8c3cefb5941f5c8ba Mon Sep 17 00:00:00 2001 From: svtter Date: Tue, 19 May 2026 18:23:24 +0800 Subject: [PATCH 3/3] fix: use bash -c wrapper for Bun shell compatibility in review_changes Bun shell template literals don't support variable command interpolation, causing "undefined is not a function" errors. Wrap all git commands in `bash -c ${cmd}` to avoid the issue. Also dedup dimension config with Set and extract shared report format constants in agent.ts. Co-Authored-By: Claude Opus 4.7 --- .../.openspec.yaml | 2 + .../multi-agent-parallel-review/design.md | 71 ++++++++++++ .../multi-agent-parallel-review/proposal.md | 28 +++++ .../specs/parallel-review/spec.md | 82 ++++++++++++++ .../multi-agent-parallel-review/tasks.md | 37 ++++++ src/agent.ts | 106 ++++++++++-------- src/dimensions/index.ts | 2 +- src/tools/review-changes.ts | 71 ++++++------ 8 files changed, 314 insertions(+), 85 deletions(-) create mode 100644 openspec/changes/multi-agent-parallel-review/.openspec.yaml create mode 100644 openspec/changes/multi-agent-parallel-review/design.md create mode 100644 openspec/changes/multi-agent-parallel-review/proposal.md create mode 100644 openspec/changes/multi-agent-parallel-review/specs/parallel-review/spec.md create mode 100644 openspec/changes/multi-agent-parallel-review/tasks.md diff --git a/openspec/changes/multi-agent-parallel-review/.openspec.yaml b/openspec/changes/multi-agent-parallel-review/.openspec.yaml new file mode 100644 index 0000000..28882f7 --- /dev/null +++ b/openspec/changes/multi-agent-parallel-review/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-05-19 diff --git a/openspec/changes/multi-agent-parallel-review/design.md b/openspec/changes/multi-agent-parallel-review/design.md new file mode 100644 index 0000000..09267f5 --- /dev/null +++ b/openspec/changes/multi-agent-parallel-review/design.md @@ -0,0 +1,71 @@ +## Context + +opencode-review 当前使用单个 `review` agent 串行审查所有维度。每个维度(code-quality、security、performance、testing、documentation)的 prompt 混合在同一个 prompt 中,由一个 agent 完成全部审查。 + +OpenCode SDK 原生支持 sub-agent 调度(通过 `task` 工具),可以在一个 agent 中并行 spawn 多个 sub-agent 并收集结果。当前项目已有一个 sub-agent 模式:`review:fixer`。 + +代码结构: +- `src/agent.ts` — 两个函数 `buildAgentPrompt` 和 `buildFixerPrompt` +- `src/config.ts` — `ReviewConfig` 接口 +- `src/index.ts` — 插件入口,注册 agent、tool、event handler +- `src/tools/review-changes.ts` — git diff 采集工具 + +## Goals / Non-Goals + +**Goals:** +- 将 5 个审查维度拆分为独立 sub-agent 并行执行 +- 保持输出格式与当前版本一致(critical / suggestion / highlight) +- 自动去重和合并不同维度 agent 的重叠发现 +- 支持 `parallel` 配置开关,允许回退到单 agent 模式 +- 保持 auto-fix 链功能(critical issue → fixer) + +**Non-Goals:** +- 不做 agent 间辩论/争论机制(如 spencermarx 的 discourse phase)——复杂度高,收益不确定 +- 不做共识评分(如 calimero 的 severity × agreement)——单 agent per 维度无需共识 +- 不做收敛检测/增量审查——属于独立功能,不在本次范围 +- 不做 Web dashboard +- 不做 GitHub PR 发帖集成 + +## Decisions + +### Decision 1: 维度 sub-agent 拆分策略 + +**选择**: 每个审查维度一个独立 sub-agent(`review:dim-code-quality`、`review:dim-security` 等 5 个) + +**替代方案**: +- A) 按文件类型拆分(前端/后端/配置)——不利于维度专业化 +- B) 按严重性拆分(critical scanner / suggestion scanner)——不同维度的 critical 标准不同 +- C) 动态拆分(根据 diff 内容决定 spawn 哪些 agent)——增加复杂度,且用户已通过 `dimensions` 配置表达了偏好 + +**理由**: 维度拆分与现有配置模型(`dimensions` 数组)天然对齐,用户可以选择启用哪些维度,每个维度 agent 只关注一个领域,prompt 更精准。 + +### Decision 2: 调度模式 + +**选择**: 主 review agent 作为调度器,通过 `task` 工具并行 spawn 维度 sub-agent,收集结果后生成统一报告 + +**理由**: OpenCode 的 `task` 工具支持并行 sub-agent 调度。主 agent 负责调度和聚合,不参与实际审查。这与现有的 `review:fixer` 模式一致。 + +### Decision 3: 结果聚合策略 + +**选择**: 主 agent 按顺序收集各维度 sub-agent 的输出,按 severity 分组(critical → suggestion → highlight),去重后输出 + +**理由**: 简单有效。不同维度 agent 可能对同一代码行有不同视角(如 security 和 performance 都关注某个查询),主 agent 负责合并这些重叠发现。 + +### Decision 4: prompt 文件组织 + +**选择**: 新增 `src/dimensions/` 目录,每个维度一个文件(`code-quality.ts`、`security.ts` 等),导出 prompt 构建函数 + +**理由**: 当前 `agent.ts` 已有 200+ 行,拆分后每个维度文件 30-50 行,可维护性更好。也方便未来独立迭代某个维度的 prompt。 + +### Decision 5: 向后兼容 + +**选择**: `config.parallel` 默认 `true`。设为 `false` 时使用当前的单 agent 逻辑 + +**理由**: 多 agent 应该是更好的默认体验,但需要一个回退选项以应对 sub-agent 调度的问题。 + +## Risks / Trade-offs + +- **[Token 消耗增加]** → 5 个 agent 各自独立运行,token 消耗约为当前的 3-5 倍。缓解:每个维度 agent 的 prompt 更短更精准(不需要包含其他维度的指令),部分抵消增长。用户可通过 `parallel: false` 回退。 +- **[延迟增加]** → sub-agent 调度有额外开销。缓解:并行执行,总时间应接近单 agent 中最慢的维度。 +- **[结果质量不一致]** → 不同维度 agent 可能对同一段代码给出矛盾建议。缓解:主 agent 在聚合时负责识别和解决矛盾。 +- **[sub-agent 数量限制]** → OpenCode 可能对同时运行的 sub-agent 数量有限制。缓解:如果有限制,改为串行 spawn + 流式收集。 diff --git a/openspec/changes/multi-agent-parallel-review/proposal.md b/openspec/changes/multi-agent-parallel-review/proposal.md new file mode 100644 index 0000000..7b7bd29 --- /dev/null +++ b/openspec/changes/multi-agent-parallel-review/proposal.md @@ -0,0 +1,28 @@ +## Why + +当前 opencode-review 使用单一 agent 审查所有维度(code-quality、security、performance、testing、documentation)。竞品分析(见 #2)表明,多 agent 并行审查是行业趋势——spencermarx/open-code-review(179 Stars)使用多 agent 辩论机制,calimero/ai-code-reviewer 使用 5 个专业 agent 共识评分。单 agent 在复杂 diff 中容易遗漏维度、输出笼统,且无法利用 OpenCode 的 subagent 并行能力。现在 OpenCode SDK 已原生支持 sub-agent 调度(`task` 工具),实现多 agent 并行审查的时机已成熟。 + +## What Changes + +- 新增**维度 agent 调度器**:将 5 个审查维度(code-quality、security、performance、testing、documentation)拆分为独立的 sub-agent,并行执行审查 +- 新增**结果聚合器**:收集各维度 agent 的审查结果,去重、合并、按严重性排序,输出统一报告 +- 修改 `buildAgentPrompt`:review agent 从"执行所有维度审查"变为"调度维度 sub-agent + 聚合结果" +- 新增 5 个维度 sub-agent prompt(`review:code-quality`、`review:security`、`review:performance`、`review:testing`、`review:documentation`) +- 修改 `ReviewConfig`:新增 `parallel` 开关(默认 true),允许回退到单 agent 模式 +- 保持向后兼容:`parallel: false` 时行为与当前版本一致 + +## Capabilities + +### New Capabilities +- `parallel-review`: 多维度 sub-agent 并行审查、结果聚合、统一报告生成 + +### Modified Capabilities +(无现有 specs) + +## Impact + +- **代码变更**:`src/agent.ts`(拆分为调度 prompt + 5 个维度 prompt)、`src/config.ts`(新增 `parallel` 字段)、`src/index.ts`(注册 5 个 sub-agent) +- **新增文件**:`src/dimensions/` 目录,每个维度一个 prompt 文件 +- **Token 消耗**:并行模式 token 消耗会增加(5 个 agent 各自独立运行),但总审查时间缩短(并行 vs 串行) +- **配置**:`.opencode/review.json` 新增可选字段 `"parallel": true` +- **无 breaking change**:默认行为从单 agent 切换为多 agent,但输出格式不变;用户可通过 `parallel: false` 回退 diff --git a/openspec/changes/multi-agent-parallel-review/specs/parallel-review/spec.md b/openspec/changes/multi-agent-parallel-review/specs/parallel-review/spec.md new file mode 100644 index 0000000..209a546 --- /dev/null +++ b/openspec/changes/multi-agent-parallel-review/specs/parallel-review/spec.md @@ -0,0 +1,82 @@ +## ADDED Requirements + +### Requirement: Parallel dimension agents +The system SHALL spawn one sub-agent per enabled review dimension. Each sub-agent SHALL be named `review:dim-` (e.g., `review:dim-security`). Sub-agents SHALL be spawned in parallel using the `task` tool. + +#### Scenario: All 5 dimensions enabled +- **WHEN** config.dimensions contains all 5 dimensions and config.parallel is true +- **THEN** the system spawns 5 sub-agents (`review:dim-code-quality`, `review:dim-security`, `review:dim-performance`, `review:dim-testing`, `review:dim-documentation`) in parallel + +#### Scenario: Subset of dimensions enabled +- **WHEN** config.dimensions contains only ["security", "performance"] and config.parallel is true +- **THEN** the system spawns only 2 sub-agents (`review:dim-security`, `review:dim-performance`) + +#### Scenario: Parallel mode disabled +- **WHEN** config.parallel is false +- **THEN** the system uses the current single-agent behavior, reviewing all enabled dimensions in one agent + +### Requirement: Dimension agent prompts +Each dimension sub-agent SHALL receive a focused prompt that covers only its assigned dimension. The prompt SHALL instruct the agent to use the `review_changes` tool, analyze the diff from its specific perspective, and output findings in the standard format (critical / suggestion / highlight). + +#### Scenario: Security dimension agent prompt +- **WHEN** the `review:dim-security` sub-agent is spawned +- **THEN** its prompt focuses exclusively on security concerns (input validation, injection prevention, auth, sensitive data) and outputs findings with file_path:line_number references + +#### Scenario: Performance dimension agent prompt +- **WHEN** the `review:dim-performance` sub-agent is spawned +- **THEN** its prompt focuses exclusively on performance concerns (algorithm complexity, query optimization, memory usage, N+1 queries) + +### Requirement: Result aggregation +The main review agent SHALL collect all dimension sub-agent results, merge them into a single report grouped by severity (critical → suggestion → highlight), and deduplicate overlapping findings about the same code location. + +#### Scenario: Overlapping findings from different dimensions +- **WHEN** `review:dim-security` reports a critical SQL injection at `db.ts:42` and `review:dim-performance` reports a suggestion about the same query at `db.ts:42` +- **THEN** both findings appear in the merged report under their respective severity sections, with the code location referenced once + +#### Scenario: No findings from any dimension +- **WHEN** all dimension sub-agents report no issues +- **THEN** the main agent outputs a clean report with highlights only + +### Requirement: Auto-fix chain preservation +The auto-fix chain (critical issue → `review:fixer` sub-agent) SHALL continue to work in parallel mode. The main agent SHALL collect all critical issues from all dimension sub-agents and spawn a single fixer with the combined fix instructions. + +#### Scenario: Critical issues from multiple dimensions +- **WHEN** `review:dim-security` finds a SQL injection and `review:dim-code-quality` finds a resource leak, both critical +- **THEN** the main agent spawns one `review:fixer` with fix instructions for both issues + +#### Scenario: No critical issues in parallel mode +- **WHEN** all dimension sub-agents report zero critical issues +- **THEN** no fixer sub-agent is spawned + +### Requirement: Parallel config option +The `ReviewConfig` interface SHALL include an optional `parallel` boolean field (default: `true`). When `false`, the plugin reverts to single-agent mode. + +#### Scenario: Config with parallel true +- **WHEN** `.opencode/review.json` contains `"parallel": true` or omits the field +- **THEN** multi-agent parallel review is used + +#### Scenario: Config with parallel false +- **WHEN** `.opencode/review.json` contains `"parallel": false` +- **THEN** single-agent review is used (current behavior) + +#### Scenario: Config without parallel field +- **WHEN** `.opencode/review.json` does not contain a `parallel` field +- **THEN** multi-agent parallel review is used (default is true) + +### Requirement: Dimension prompt files +Each dimension SHALL have its own prompt module file under `src/dimensions/`, exporting a function that returns the dimension-specific prompt string. Files SHALL follow the naming convention `.ts` (e.g., `security.ts`, `code-quality.ts`). + +#### Scenario: Prompt function returns correct content +- **WHEN** `buildDimensionPrompt("security", config)` is called +- **THEN** it returns a prompt string focused on security review, using the configured language (zh/en) + +### Requirement: Output format consistency +The final merged report output format SHALL be identical to the current single-agent format (Overall Assessment → Critical Issues → Suggestions → Highlights), regardless of parallel mode. + +#### Scenario: Output format in parallel mode +- **WHEN** parallel review completes with findings +- **THEN** the output uses the same section headers and emoji markers (🔴 🟡 ✅) as single-agent mode + +#### Scenario: Language consistency in parallel mode +- **WHEN** config.language is "zh" +- **THEN** the aggregated report and all dimension sub-agent outputs are in Chinese diff --git a/openspec/changes/multi-agent-parallel-review/tasks.md b/openspec/changes/multi-agent-parallel-review/tasks.md new file mode 100644 index 0000000..82636f8 --- /dev/null +++ b/openspec/changes/multi-agent-parallel-review/tasks.md @@ -0,0 +1,37 @@ +## 1. Config 扩展 + +- [x] 1.1 在 `ReviewConfig` 接口中添加 `parallel?: boolean` 字段,默认 `true` +- [x] 1.2 在 `DEFAULT_CONFIG` 中添加 `parallel: true` +- [x] 1.3 验证 `loadConfig` 正确合并 `parallel` 字段(全局 + 项目配置) + +## 2. 维度 Prompt 文件 + +- [x] 2.1 创建 `src/dimensions/` 目录 +- [x] 2.2 创建 `src/dimensions/code-quality.ts` — 代码质量维度 prompt 构建函数 +- [x] 2.3 创建 `src/dimensions/security.ts` — 安全性维度 prompt 构建函数 +- [x] 2.4 创建 `src/dimensions/performance.ts` — 性能维度 prompt 构建函数 +- [x] 2.5 创建 `src/dimensions/testing.ts` — 测试维度 prompt 构建函数 +- [x] 2.6 创建 `src/dimensions/documentation.ts` — 文档维度 prompt 构建函数 +- [x] 2.7 创建 `src/dimensions/index.ts` — 导出统一的 `getDimensionPrompts(config)` 函数,返回 `{ name, agentName, prompt }[]` +- [x] 2.8 验证每个维度 prompt 包含:使用 review_changes 工具的指令、维度专注的审查要点、标准输出格式(critical / suggestion / highlight)、中英双语支持 + +## 3. 主 Agent Prompt 重构 + +- [x] 3.1 修改 `buildAgentPrompt` 添加并行模式分支:当 `parallel: true` 时生成调度器 prompt(spawn sub-agents + 聚合结果),当 `parallel: false` 时保持当前逻辑 +- [x] 3.2 调度器 prompt 需包含:对每个启用维度调用 `task` 工具 spawn `review:dim-` sub-agent、收集所有结果、按 severity 分组合并、去重同位置发现、输出统一报告格式 +- [x] 3.3 调度器 prompt 需包含 auto-fix 指令:收集所有 critical issues 后 spawn 单个 `review:fixer` + +## 4. 插件入口更新 + +- [x] 4.1 在 `src/index.ts` 中注册 5 个维度 sub-agent(`review:dim-code-quality` 等),每个设置 `mode: "subagent"`、只读权限、对应维度 prompt +- [x] 4.2 维度 sub-agent 的注册需根据 config.dimensions 动态生成(只注册启用的维度) +- [x] 4.3 验证 `review` agent 和 `review:fixer` 的注册不受影响 + +## 5. 集成验证 + +- [ ] 5.1 验证并行模式:配置 `parallel: true`,运行 `/review`,确认 5 个维度 sub-agent 并行执行,结果正确聚合(需在 OpenCode 中手动验证) +- [ ] 5.2 验证回退模式:配置 `parallel: false`,运行 `/review`,确认行为与当前版本一致(需在 OpenCode 中手动验证) +- [ ] 5.3 验证 auto-fix 链:并行模式下触发 critical issue,确认 fixer 正确接收合并后的修复指令(需在 OpenCode 中手动验证) +- [ ] 5.4 验证部分维度:配置只启用 `["security", "performance"]`,确认只 spawn 2 个 sub-agent(需在 OpenCode 中手动验证) +- [ ] 5.5 验证中英双语:切换 `language: "en"` 后确认所有维度 prompt 输出英文(需在 OpenCode 中手动验证) +- [ ] 5.6 验证 idle 自动审查:并行模式下 session idle 自动触发审查(需在 OpenCode 中手动验证) diff --git a/src/agent.ts b/src/agent.ts index a9209b4..3302948 100644 --- a/src/agent.ts +++ b/src/agent.ts @@ -39,6 +39,58 @@ function buildCustomRules(rules: string[]): string { return `\n### Custom Rules\n${rules.map((r) => `- ${r}`).join("\n")}` } +const REPORT_FORMAT: Record = { + zh: `## 输出格式 + +\`\`\` +## 审查结果 + +### 总体评价 +[简要描述代码质量] + +### 关键问题 :red_circle: +[必须修复的问题,引用 file_path:line_number] + +### 建议改进 :yellow_circle: +[可选的优化建议,引用 file_path:line_number] + +### 亮点 :white_check_mark: +[代码中做得好的地方] +\`\`\``, + en: `## Output Format + +\`\`\` +## Review Results + +### Overall Assessment +[Brief description of code quality] + +### Critical Issues :red_circle: +[Must-fix issues, reference file_path:line_number] + +### Suggestions :yellow_circle: +[Optional improvements, reference file_path:line_number] + +### Highlights :white_check_mark: +[Good practices found in the code] +\`\`\``, +} + +const AUTO_FIX_INSTRUCTION: Record = { + zh: `## 自动修复 + +如果任何维度代理发现了关键问题(🔴),你必须: +1. 汇总所有关键问题 +2. 使用 \`task\` 工具 spawn \`review:fixer\` 子代理,传入所有关键问题的修复指令 +3. 等待 fixer 完成修复`, + en: `## Auto-Fix + +If any dimension agent finds critical issues (🔴), you MUST: +1. Collect all critical issues across dimensions +2. Use the \`task\` tool to spawn a \`review:fixer\` sub-agent with combined fix instructions +3. Wait for the fixer to complete`, +} + export function buildAgentPrompt(config: ReviewConfig): string { if (config.parallel) { return buildParallelPrompt(config) @@ -47,11 +99,11 @@ export function buildAgentPrompt(config: ReviewConfig): string { } function buildParallelPrompt(config: ReviewConfig): string { - const isZh = config.language === "zh" + const lang = config.language === "zh" ? "zh" : "en" const dimensions = getDimensionPrompts(config) const dimensionList = dimensions.map((d) => `- ${d.agentName}: ${d.name}`).join("\n") - if (isZh) { + if (lang === "zh") { return `你是一个代码审查调度器。你的任务是并行调度多个维度审查子代理,收集结果,并生成统一报告。 ## 可用维度代理 @@ -68,30 +120,9 @@ ${dimensionList} 5. 对同一代码位置的重复发现进行合并 6. 输出统一报告 -## 输出格式 - -\`\`\` -## 审查结果 - -### 总体评价 -[简要描述代码质量] - -### 关键问题 :red_circle: -[必须修复的问题,引用 file_path:line_number] - -### 建议改进 :yellow_circle: -[可选的优化建议,引用 file_path:line_number] - -### 亮点 :white_check_mark: -[代码中做得好的地方] -\`\`\` +${REPORT_FORMAT.zh} -## 自动修复 - -如果任何维度代理发现了关键问题(🔴),你必须: -1. 汇总所有关键问题 -2. 使用 \`task\` 工具 spawn \`review:fixer\` 子代理,传入所有关键问题的修复指令 -3. 等待 fixer 完成修复` +${AUTO_FIX_INSTRUCTION.zh}` } return `You are a code review orchestrator. Your task is to dispatch multiple dimension review sub-agents in parallel, collect results, and produce a unified report. @@ -110,30 +141,9 @@ ${dimensionList} 5. Deduplicate overlapping findings at the same code location 6. Output a unified report -## Output Format - -\`\`\` -## Review Results - -### Overall Assessment -[Brief description of code quality] - -### Critical Issues :red_circle: -[Must-fix issues, reference file_path:line_number] - -### Suggestions :yellow_circle: -[Optional improvements, reference file_path:line_number] - -### Highlights :white_check_mark: -[Good practices found in the code] -\`\`\` +${REPORT_FORMAT.en} -## Auto-Fix - -If any dimension agent finds critical issues (🔴), you MUST: -1. Collect all critical issues across dimensions -2. Use the \`task\` tool to spawn a \`review:fixer\` sub-agent with combined fix instructions -3. Wait for the fixer to complete` +${AUTO_FIX_INSTRUCTION.en}` } function buildSinglePrompt(config: ReviewConfig): string { diff --git a/src/dimensions/index.ts b/src/dimensions/index.ts index 69e8ab7..27f67e3 100644 --- a/src/dimensions/index.ts +++ b/src/dimensions/index.ts @@ -128,7 +128,7 @@ function buildDimensionPrompt(dimension: string, config: ReviewConfig): string { } export function getDimensionPrompts(config: ReviewConfig): DimensionPrompt[] { - return config.dimensions + return [...new Set(config.dimensions)] .filter((dim) => DIMENSIONS[dim]) .map((dim) => ({ name: dim, diff --git a/src/tools/review-changes.ts b/src/tools/review-changes.ts index 4c5ba89..317b7b7 100644 --- a/src/tools/review-changes.ts +++ b/src/tools/review-changes.ts @@ -14,62 +14,61 @@ export const reviewChanges = tool({ async execute(args, context) { const { $, directory } = context const scope = args.scope ?? "staged" + const maxLines = args.max_lines ?? 500 - let diffCmd: string - let statsCmd: string + let diffResult: string + let statsResult: string switch (scope) { case "staged": - diffCmd = "git diff --cached" - statsCmd = "git diff --cached --stat" + diffResult = await runCommand($, "git diff --cached") + statsResult = await runCommand($, "git diff --cached --stat") break case "last-commit": - diffCmd = "git show --format='' HEAD" - statsCmd = "git show --format='' --stat HEAD" + diffResult = await runCommand($, "git show --format='' HEAD") + statsResult = await runCommand($, "git show --format='' --stat HEAD") break case "branch": { - const defaultBranch = await getDefaultBranch($, directory) - diffCmd = `git diff ${defaultBranch}...HEAD` - statsCmd = `git diff ${defaultBranch}...HEAD --stat` + const defaultBranch = await getDefaultBranch($) + diffResult = await runCommand($, `git diff ${defaultBranch}...HEAD`) + statsResult = await runCommand($, `git diff ${defaultBranch}...HEAD --stat`) break } } - const maxLines = args.max_lines ?? 500 - - try { - const [diffResult, statsResult] = await Promise.all([ - $`${diffCmd}`.quiet(), - $`${statsCmd}`.quiet(), - ]) - - let diff = diffResult.stdout ?? "" - const stats = statsResult.stdout ?? "" + let diff = diffResult + const stats = statsResult - const truncated = diff.split("\n").length > maxLines - if (truncated) { - diff = diff.split("\n").slice(0, maxLines).join("\n") - } - - if (!diff.trim()) { - return "No changes found for the selected scope." - } + const truncated = diff.split("\n").length > maxLines + if (truncated) { + diff = diff.split("\n").slice(0, maxLines).join("\n") + } - let output = `## Change Stats\n${stats}\n\n## Diff\n\`\`\`diff\n${diff}\n\`\`\`` - if (truncated) { - output += `\n\n⚠️ Diff truncated at ${maxLines} lines. Use a smaller scope or increase max_lines for full review.` - } + if (!diff.trim()) { + return "No changes found for the selected scope." + } - return output - } catch (err: any) { - return `Error gathering diff: ${err.message ?? err}` + let output = `## Change Stats\n${stats}\n\n## Diff\n\`\`\`diff\n${diff}\n\`\`\`` + if (truncated) { + output += `\n\n⚠️ Diff truncated at ${maxLines} lines. Use a smaller scope or increase max_lines for full review.` } + + return output }, }) -async function getDefaultBranch($: any, _directory: string): Promise { +async function runCommand($: any, cmd: string): Promise { + try { + const result = await $`bash -c ${cmd}`.quiet() + return result.stdout ?? "" + } catch { + return "" + } +} + +async function getDefaultBranch($: any): Promise { try { - const result = await $`git remote show origin`.quiet() + const result = await $`bash -c 'git remote show origin'`.quiet() const match = (result.stdout ?? "").match(/HEAD branch: (.+)/) if (match) return match[1] } catch {