# Review skill 与 CLI 对齐设计

日期：2026-08-14  
状态：已确认（brainstorming）  
父规格：[2026-08-10-host-agent-design.md](./2026-08-10-host-agent-design.md)  
参考 CLI：`/Users/nietao/VSCode-plugins/smart-commit-cli` @ `peerReference.cliVersion`（当前 0.1.21）  
相关：[2026-08-14-commit-message-cli-parity-design.md](./2026-08-14-commit-message-cli-parity-design.md)（该规格将 `review.skill` 列为非目标；本规格覆盖该项）

## 1. 背景与目标

host-agent 的 `review` 只有 `threshold` / `language` / `maxDiffChars`。CLI 的

```json
"review": {
  "skill": {
    "id": "code-review",
    "path": "",
    "promptTuning": "Ignore findings that only complain about the project's required logging wrapper."
  }
}
```

写进 host-agent 配置会被静默丢掉，也不会进入 review prompt。两边无法按同一套 review skill 规则更新。

本规格把 host-agent 的 **review skill 子系统** 对齐到 CLI 当前语义，并一并移植 CLI 单次审查 prompt 上与 skill 配套的三块：domain classifier、行号标注、review rules。不保留「忽略未知 `review.skill`」的旧行为，不做迁移别名，不做弃用期。

### 目标

1. 配置契约对齐 CLI：`review.skill.{id,path,promptTuning}`，以及 `pullRequestReview.skillPromptTuning`。
2. `path` 语义与 CLI / 本仓已有 `commitMessage.skill.path` 相同（见 §3.2）。
3. 从 CLI 拷贝 `code-review-skills/`，经现有 `promptContext.ts` 注入 review prompt。
4. staged 与 PR review 都注入 skill；PR 可用 `skillPromptTuning` 覆盖 `promptTuning`。
5. 移植 CLI 的 domain classifier，写入 `Detected diff domain` 段。
6. PR/MR review 开启 `inlineAnchoring`：标注新文件行号、citation rules、per-line findings rules；解析后用 CLI 的 `alignReviewResultWithDiff` 对齐到 raw diff。
7. staged `bridge` / `--review-only` 不开行号标注与 per-line rules（与 CLI 相同）。

### 非目标

- 不修改 `smart-commit-cli`。
- 不抽共享 npm 包。
- 不做 chunked / hybrid review。
- 不做 correction repair turn。turn 协议保持单次 `purpose: "code-review"`（或现有 PR 的 `code-review:<url>`）。
- 不新增 CLI 的 `--code-review-skill-*` / `SMART_COMMIT_REVIEW_*` 覆盖层。配置仍走 JSON 文件。
- 不把 `review.language` 收成 `OutputLanguage`。
- 不移植 `validateReviewContentLanguage`。
- 不写 passHistory（host-agent 目前不写该记录；因此 CLI 把自定义 `path` 记为 `codeReviewSkillId: "custom"` 不在本规格范围）。

## 2. 决策摘要

| 决策项 | 选择 |
|--------|------|
| 范围 | review.skill 配置 + 内置 skill 文件 + prompt 注入 + domain classifier + PR 行号标注/rules + detailLocator |
| 默认 `review.skill.id` | `code-review`（与 CLI 相同） |
| `path` 语义 | 与 CLI 相同：非空则自定义文件优先，相对 `repositoryPath` |
| PR 覆盖 | `pullRequestReview.skillPromptTuning` 非空覆盖本次 `promptTuning` |
| 行号标注 | 仅 `inlineAnchoring: true`（PR/MR review / batch-review） |
| staged review | 注入 skill + classifier，不标注行号 |
| repair / chunked | 不做 |
| 实现方式 | 外科移植：扩展现有 config / `promptContext` / `review/prompt` / `runHostAgentReview`，拷贝 CLI 文件，不整文件替换、不抽包 |
| CLI flag/env 覆盖 | 不加 |

## 3. 配置契约

### 3.1 形状与默认值

```ts
review: {
  threshold: number;
  language: string;
  maxDiffChars: number;
  skill: {
    id: string;
    path: string;
    promptTuning: string;
  };
}

pullRequestReview: {
  // 现有字段不变
  skillPromptTuning: string;
}
```

与 CLI `defaultCliConfig` 对齐的新增默认值：

| 字段 | 默认 |
|------|------|
| `review.skill.id` | `code-review` |
| `review.skill.path` | `""` |
| `review.skill.promptTuning` | `""` |
| `pullRequestReview.skillPromptTuning` | `""` |

`review.threshold` / `language` / `maxDiffChars` 保持现状（含 host-agent 的 `maxDiffChars` 默认 `200000`，不改为 CLI 的 `100000`）。

### 3.2 `review.skill.path`（与 CLI 完全相同）

解析：`parseString` 后 `trim()`。空字符串表示不使用自定义文件。

校验：

- `path` 为空：`id` 必须属于内置 `code-review` 集合。空 id 非法。
- `path` 非空：不校验 `id` 是否属于内置集合。

路径解析：

- 绝对路径：`path.normalize`
- 相对路径：相对 **`repositoryPath`**（当前被审查的仓库根），不是配置文件所在目录

加载（`buildPromptAugmentation`，`categoryLabel: "review skill"`）：

1. `path` 非空：读整个文件。读失败 → `Failed to read review skill skill file at <resolved>: …`。trim 后为空 → `review skill skill file must not be empty: <resolved>`。prompt 段为 `Custom review skill instructions:`。**不加载** bundled `code-review-skills/<id>`。
2. `path` 为空且 `id` 非空：加载内置 bundle（`Selected review skill profile` + `SKILL.md` 去 frontmatter + `references/*.md`）。
3. `promptTuning` 非空：无论是否自定义 path，都再追加 `Additional review skill tuning:`。

classifier：`path` 非空时 selected domain 固定为 `generic`，不做专项 domain 匹配。

`id` 在自定义 path 下仍保留在配置中，但该次 run 的 bundled skill 与专项 domain 都不使用它。

### 3.3 内置 id

`BuiltinSkillCategory` 扩展为 `"code-review" | "git-commit-message"`。`code-review` 集合与 CLI 相同：

- `code-review`
- `frontend-code-review`
- `mobile-code-review`
- `python-code-review`
- `golang-code-review`
- `java-code-review`
- `c-code-review`
- `cpp-code-review`
- `csharp-code-review`
- `rust-code-review`
- `php-code-review`

`path` 为空时 `validateHostAgentConfig` 调用 `resolveBuiltinSkillId(id, "code-review", "review.skill.id")`。

### 3.4 解析与 merge

`parseCanonicalHostAgentConfig` 的 `review` 段增加 `skill`，复用现有 `parseSkillConfig`（与 `commitMessage.skill` 同一函数）。

`pullRequestReview` 段增加 `skillPromptTuning`：`parseString` 后 `trim()`。

嵌套 merge：`review.skill` 做一层浅合并，避免只写 `id` 时冲掉默认 `path` / `promptTuning`。写法对齐现有 `commitMessage.skill` 与 CLI `merge.ts`。

未知字段仍静默忽略（现有解析风格）。写了 `review.skill` 之后会真正生效，不再丢掉。

### 3.5 PR 覆盖

对齐 CLI `openaiProvider` / `reviewWorkflow`：

```
effectivePromptTuning =
  (pullRequestReview.skillPromptTuning || undefined)
  ?? review.skill.promptTuning
```

空字符串视为未覆盖。staged review（`bridge` / `--review-only`）只用 `review.skill.promptTuning`，不读 `skillPromptTuning`。

## 4. Prompt 与执行

### 4.1 调用开关（与 CLI 相同）

| 入口 | skill 注入 | domain classifier | 行号标注 | per-line rules |
|------|------------|-------------------|----------|----------------|
| `bridge` / `--review-only` | `review.skill` | 有 | 关 | 关 |
| `pull-request review` / `batch-review` | 同上，可被 `skillPromptTuning` 覆盖 | 有 | 开（`inlineAnchoring: true`） | 开 |

### 4.2 `buildReviewMessages`

对齐 CLI 单次审查 `buildReviewMessages`，不移植 chunk / repair / summary 三个 builder。

- system：保持现有 senior-reviewer / JSON schema / threshold 文案（与 CLI 单次审查 system 一致）。
- user 顺序对齐 CLI：
  1. Repository / language / threshold / commit message / changed files
  2. `Review skill guidance:` + `promptAugmentation`
  3. `Detected diff domain:` 段（classifier）
  4. diff：若 `inlineAnchoring` 或 `annotateLineNumbers` 为真，则用 CLI 的 `annotateDiffWithLineNumbers`；否则原始 diff
  5. 若标注开启：citation rules；若 `inlineAnchoring`：再加 per-line findings rules

`promptAugmentation` 放在 **user** 消息（对齐 CLI review），不放 system（commit-message 那次是 system，review 不要照搬）。

`runHostAgentReview` 对齐 `resolveHostAgentCommitMessage`：增加 `config: HostAgentConfig` 参数，从 `config.review.skill` 构建 augmentation（`builtinCategory: "code-review"`），并用 `input.skillPromptTuning ?? config.review.skill.promptTuning`。`ReviewExecutionInput` 增加 CLI 同名可选字段：`skillPromptTuning?`、`annotateLineNumbers?`、`inlineAnchoring?`。

PR / batch-review 调用处设 `inlineAnchoring: true` 与 `skillPromptTuning: config.pullRequestReview.skillPromptTuning || undefined`。staged 调用处不设这两项。

### 4.3 Domain classifier

从 CLI 拷贝：

- `src/review/diffClassifier.ts`
- `src/skills/types.ts`（`ReviewSkill` / `ReviewSkillDomain`）

`buildReviewDiffDomainSection` 与 CLI 文案、字段一致。`path` 非空 → `reviewDomain: "generic"`。专项 `id` 映射与 CLI `resolveSelectedReviewSkillDomain` 相同。

不移植 CLI 的 chunked 路径；classifier 只服务单次 `buildReviewMessages`。

### 4.4 行号标注与 locator

从 CLI 拷贝 `src/review/detailLocator.ts`。

执行顺序对齐 CLI 单次审查的 parse 段（无 repair）：

1. 模型看到的是 **标注后** 的 diff（仅 PR）。
2. `parseReviewResponse` 解析 JSON。
3. `alignReviewResultWithDiff(parsed, rawDiff)` 使用 **未标注** 的原始 diff。
4. 现有 `validateReviewResult` + threshold 判定。

不改变 `applyPullRequestReviewActions` 的发布门槛：仍要求 `filePath` + `lineNumber`。本规格只提高模型 citation 与 locator 对齐后的锚定率。

### 4.5 内置 skill 文件

从 CLI 原样拷贝 `src/code-review-skills/`（11 个 id 的 `SKILL.md` + `references/*.md`）。

`promptContext.ts` 恢复 CLI 的双 category 查找：`git-commit-message-skills` 与 `code-review-skills`。查找候选路径与现有 commit-message 相同：`__dirname` 下、`../src/<root>/<id>`、`cwd/src/<root>/<id>`。

`package.json` `files` 增加 `src/code-review-skills`（tsc 不复制 markdown）。

## 5. 文档与示例

更新：

- `docs/configuration.md`：`review.skill.*` 表、内置 id 列表、自定义 `path` 与 bundled id 互斥、classifier fallback；`pullRequestReview.skillPromptTuning`；注明行号标注只用于 PR/MR review。
- `docs/parity-matrix.md`：`bridge` / `pull-request review` / `batch-review` 备注改为已对齐 skill + classifier + PR 行号；仍注明无 chunked / repair。
- `README.md` 与 `examples/config.host-agent.json`：可写与 CLI 示例相同的 `review.skill`（含示例 `promptTuning`）。
- `src/contracts.ts`：config schema 增加 `review.skill` 与 `pullRequestReview.skillPromptTuning`。

不写迁移指南。Plan 6 规格里「可不移植 `skillPromptTuning`」被本规格覆盖，不回改那份历史文档。

## 6. 测试

至少覆盖：

**配置**

- 默认：`review.skill` 为 `code-review` / `""` / `""`；`skillPromptTuning` 为 `""`。
- 嵌套 merge：只写 `review.skill.id: "java-code-review"` 时 `path` / `promptTuning` 仍为空。
- `path` 为空：非法 id 失败；合法专项 id 通过。
- `path` 非空：任意 id 通过。
- `promptTuning` / `skillPromptTuning` trim。

**prompt / skill**

- 默认注入 bundled `code-review`（profile 行 + `SKILL.md` + 至少一个 reference）。
- 专项 id（如 `c-code-review`）加载对应 bundle，不含「当成通用 code-review 文件」的误加载。
- `skill.path` 相对 `repositoryPath` 读自定义文件；空文件报错；缺文件报错。
- `promptTuning` 追加 tuning 段；自定义 path 时仍追加。

**classifier**

- 移植 CLI `diffClassifier` 主路径：匹配 / 不匹配时的 `shouldFallbackToGeneric`。
- `path` 非空 → selected domain `generic`。
- `buildReviewMessages` user 内容含 `Detected diff domain:`。

**行号 / rules / locator**

- 无 `inlineAnchoring`：diff 不带 `<lineNumber> |` 前缀，无 citation / per-line rules。
- `inlineAnchoring: true`：新增行带新文件行号前缀；含 citation 与 per-line findings 文案。
- `alignReviewResultWithDiff`：能根据 message snippet 把 `filePath` / `lineNumber` 对齐到 raw diff 新增行。

**调用链**

- `bridge` / `--review-only` 使用 `review.skill.promptTuning`，`inlineAnchoring` 未开。
- `pull-request review` / `batch-review`：`skillPromptTuning` 非空则覆盖；`inlineAnchoring: true`。

现有 `reviewPrompt` / `hostAgentReview` / `configResolve` / PR review 测试随默认注入 skill 更新，不再假设 prompt 里没有 skill / domain 段。

## 7. 破坏性变化（有意，无兼容层）

1. 配置里的 `review.skill` 从静默忽略变为解析、校验、注入。非法 `id`（且 `path` 为空）会在 `config resolve` 失败。
2. 默认每次审查 turn 都带 bundled `code-review` skill 正文与 references。审查结果可能与旧的「无 skill 固定 prompt」不同。
3. PR/MR review 的 diff 对模型变为行号标注版本；inline comment 的 `lineNumber` 经 locator 对齐，可能与旧的模型自报行号不同。

不改变：`review.maxDiffChars` 默认值、`review.language` 任意字符串、无 chunked、无 repair、passHistory 仍不写。
