# PR/MR 创建配置与 CLI 对齐设计

日期：2026-08-15  
状态：已确认（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)、[2026-08-14-review-skill-cli-parity-design.md](./2026-08-14-review-skill-cli-parity-design.md)

## 1. 背景与目标

host-agent 的 `pullRequestCreation` 只有 `autoCreateAfterPush` / `targetBranch` / `maxDiffChars` / `assignees` / `labels` / `removeSourceBranch` / `skipBranches`。CLI 的

```json
"pullRequestCreation": {
  "configFilePath": ".smart-commit-pr.creation.json",
  "titlePrompt": "Keep the title short.",
  "descriptionPrompt": "Summarize risks.",
  "reviewers": ["alice"],
  "milestone": "42",
  "draft": true
}
```

写进 host-agent 会被静默丢掉。`createApi` 虽有 `draft` 参数，但 `bridge` / `pull-request create` 写死 `false`。Plan 4 有意不移植 `titlePrompt` / `descriptionPrompt`；Plan 6 把 `configFilePath` 与 `skillPromptTuning` 捆在一起标成可不移植（后者已由 review skill 规格补上；前者是本地 overlay，不是 skill 路径）。

两边无法按同一套 creation 配置更新。本规格把 host-agent 的 **PR/MR 创建配置、overlay 加载、pr-content prompt、创建 API** 对齐到 CLI 当前语义。不保留旧写法，不做迁移别名，不做弃用期。

### 目标

1. `pullRequestCreation` 补齐：`configFilePath`、`titlePrompt`、`descriptionPrompt`、`reviewers`、`milestone`、`draft`。
2. `pullRequestReview.configFilePath` 使用同一套 overlay loader。
3. `titlePrompt` / `descriptionPrompt` 注入 pr-content turn；`titlePrompt` 非空时不再用单条 commit subject 当 title。
4. `reviewers` / `milestone` / `draft` 按 CLI 发给 GitHub / GitLab。
5. overlay：逗号分隔路径、相对 `--repo`（否则 `cwd`）、取第一个存在的文件、只加载对应段。
6. 主配置与 overlay 顶层都只认 `smartCommitHostAgent`。

### 非目标

- 不修改 `smart-commit-cli`。
- 不抽共享 npm 包。
- 不新增 CLI 的 `--pull-request-creation-*` / `SMART_COMMIT_PULL_REQUEST_CREATION_*` 覆盖层。配置仍走 JSON 文件 + 已有 `--title` / `--description` / `--config` / `--repo`。
- 不改 `autoCreateAfterPush`、`removeSourceBranch` 的 host-agent 默认值（CLI 为 `false`，host-agent 保持 `true`）。
- 不移植 CLI 的 PR content repair turn。
- 不移植 CLI `contentPrompt` 的 language rules 段。
- 不把主配置未知**内部**字段改成硬失败（canonical 对象内未知键仍静默忽略，与现有解析风格相同）。
- 不写迁移指南。

## 2. 决策摘要

| 决策项 | 选择 |
|--------|------|
| 范围 | creation 六字段 + review overlay + prompt/title + 创建 API + 根键收紧 |
| 实现方式 | 外科移植：扩现有 config / contentPrompt / createApi / bridge / pull-request create；creation 与 review 共用 overlay helper |
| overlay 与主配置根键 | 只允许 `smartCommitHostAgent`；`smartCommitCli` 失败 |
| CLI flag/env 覆盖 | 不加 |
| repair turn | 不做 |
| host-agent 默认值 | `autoCreateAfterPush` / `removeSourceBranch` 保持 `true`；新字段与 CLI 相同 |
| 旧写法 | 无别名、无弃用期、无静默忽略这六个字段 |

## 3. 配置契约

### 3.1 形状与默认值

```ts
pullRequestCreation: {
  autoCreateAfterPush: boolean;
  configFilePath: string;
  targetBranch: string;
  titlePrompt: string;
  descriptionPrompt: string;
  maxDiffChars: number;
  assignees: string[];
  reviewers: string[];
  labels: string[];
  milestone: string;
  draft: boolean;
  removeSourceBranch: boolean;
  skipBranches: string[];
}

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

新增默认值（与 CLI `defaultCliConfig` 相同）：

| 字段 | 默认 |
|------|------|
| `pullRequestCreation.configFilePath` | `""` |
| `pullRequestCreation.titlePrompt` | `""` |
| `pullRequestCreation.descriptionPrompt` | `""` |
| `pullRequestCreation.reviewers` | `[]` |
| `pullRequestCreation.milestone` | `""` |
| `pullRequestCreation.draft` | `false` |
| `pullRequestReview.configFilePath` | `""` |

保持现状、不对齐 CLI 的默认值：`pullRequestCreation.autoCreateAfterPush`（host-agent `true`）、`pullRequestCreation.removeSourceBranch`（host-agent `true`）。

### 3.2 解析

`parseCanonicalHostAgentConfig` 的 `pullRequestCreation` 段增加上述字段；`pullRequestReview` 段增加 `configFilePath`。

- 字符串（`configFilePath` / `titlePrompt` / `descriptionPrompt` / `milestone`）：`parseString` 后 `trim()`。空字符串合法，表示未设置。
- `reviewers`：现有 `parseStringArray`（trim，丢掉空项）。
- `draft`：`parseBoolean`。
- `configFilePath` 只存原始逗号分隔字符串，不在 parse 阶段拆路径。

这六个字段没有额外取值约束。`maxDiffChars >= 1000` 不变。

canonical 对象内未知键仍静默忽略。但这几个键一旦写出就会被解析并生效，不再丢掉。

### 3.3 merge

`mergeHostAgentConfig` 中 `reviewers` 与 `assignees` / `labels` / `skipBranches` 相同：override 有则整表替换，没有则保留 base。其余新字段走浅合并。

## 4. 根键

主配置与 overlay 使用同一条顶层规则，降低心智负担：

- 顶层只允许 `smartCommitHostAgent`。
- 只有 `smartCommitCli`、两个都有、或其它顶层键：直接失败。报错明确写改用 `smartCommitHostAgent`，不再接受 `smartCommitCli`。
- 主配置 canonical 对象内若仍带 `connection`：继续剥掉（host-agent 不用 LLM connection）。这不是根键兼容。

有意破坏：原先把 CLI JSON 原样丢给 host-agent 的用法会失败，必须改根键。`examples/config.host-agent.json` 已经是 `smartCommitHostAgent`。

## 5. Overlay 加载

`configFilePath` 是第二层本地覆盖，加载时机与路径语义对齐 CLI；根键按 §4。

### 5.1 何时加载

`resolveHostAgentConfig` 两阶段，`config resolve` 也会套上 overlay：

1. 默认值 + 主配置文件，做 `env:` 解析 → 得到 `*.configFilePath`。
2. 按该路径加载 overlay，盖在主配置上（overlay 赢）。
3. 再做一次 `env:` 解析，然后 `validate`。

`configFilePath` 为空：跳过，不当错误。

`baseDirectory`：argv 里的 `--repo`（若有）否则 `process.cwd()`。相对路径相对这个目录，不是主配置文件所在目录。`config resolve` 未传 `--repo` 时用 `cwd`。

### 5.2 路径列表

与 CLI `parseConfigFilePathList` / `resolveFirstExistingConfigFilePath` 相同：

- 逗号分隔，trim，丢掉空段。
- 相对路径相对 `baseDirectory`；绝对路径 `path.normalize`。
- 取**第一个存在且是文件**的路径。
- 列表非空但全部不存在：失败，错误列出尝试过的路径，字段名用 `pullRequestCreation.configFilePath` 或 `pullRequestReview.configFilePath`。

### 5.3 本地文件形状

顶层规则同 §4：只允许 `smartCommitHostAgent`。出现 `smartCommitCli` 或其它顶层键直接失败，不静默跳过。文件为 `{}`：视为没有 overlay 内容（与 CLI 对空对象相同）。

canonical 对象里：

| overlay | 只允许 | 禁止 |
|---------|--------|------|
| creation | `pullRequestCreation` | `autoCreateAfterPush`、`configFilePath` |
| review | `pullRequestReview` | `configFilePath` |

禁止项报错文案对齐 CLI：

- `…autoCreateAfterPush is not supported inside a pull request creation config file.`
- `…configFilePath is not supported inside a pull request creation config file.`
- `…configFilePath is not supported inside a pull request review config file.`
- canonical 里出现其它段：`only supports pullRequestCreation settings` / `only supports pullRequestReview settings`。

creation 和 review 共用一个 loader helper，section 名与禁止字段不同。

## 6. Prompt 与 title 解析

`--title` 与 `--description` 都已提供时仍跳过 pr-content turn，不改。两者只提供一个仍报错（现有行为）。

### 6.1 注入

`buildPullRequestContentMessages` 增加 `titlePrompt`、`descriptionPrompt`。非空则插在 changed files 之前，文案与 CLI 相同：

```
Title prompt tuning:
<trimmed titlePrompt>

Description prompt tuning:
<trimmed descriptionPrompt>
```

空字符串不插段。不移植 CLI 的 language rules 段，也不做 PR content repair turn。

`bridge` 与 `pull-request create` 把配置里的 prompt 传进 `resolveHostAgentPullRequestContent`。

### 6.2 title 回退

`resolvePullRequestTitle(commitsText, generatedTitle, titlePrompt)` 对齐 CLI：

1. `titlePrompt.trim()` 非空 → 用模型生成的 title。
2. 否则且分支范围**恰好一条** commit → 用该 commit subject。
3. 否则 → 用生成 title。

删除「host-agent 没有 titlePrompt 字段」的注释和旧两参数签名。

## 7. 创建 API

`PullRequestCreationApiConfig` 增加 `reviewers: string[]`、`milestone: string`、`draft: boolean`。`bridge` 与 `pull-request create` 从 `config.pullRequestCreation` 传入，不再写死 `draft: false`。

行为从 CLI `src/pullRequest/api.ts` 原样搬，平台差异保留。

### 7.1 GitHub

- 创建 POST 带 `draft`。
- 创建成功后，与 assignees/labels 一样用 `collectWarning`：
  - `reviewers`：含 `/` 的当 team（取最后一段进 `team_reviewers`），其余进 `reviewers`，POST `…/pulls/{n}/requested_reviewers`。
  - `milestone`：空则跳过；纯数字当 id；否则在 open milestones 里按 title 找，再 PATCH issue。
- 设置失败记入 `warnings`，不让整次创建失败。

### 7.2 GitLab

- 创建 POST 一次带上：`draft`、`reviewer_ids`、`milestone_id`（以及现有的 assignees/labels/`remove_source_branch`）。
- username / milestone title 解析失败：**抛错**（与 CLI 相同，不是 warning）。
- milestone 空 → 不传 `milestone_id`；纯数字当 id；否则按 title 在 active milestones 里找。

空 `reviewers` / 空 `milestone` 都是 no-op。`--dry-run` 仍不调写 API。

## 8. 文档与示例

更新：

- `docs/configuration.md`：`pullRequestCreation` 六个字段表、`pullRequestReview.configFilePath`；删掉 `smartCommitCli` 迁移说明；写明 overlay 只认 `smartCommitHostAgent`、相对 `--repo`/`cwd`、禁止嵌套自引用。
- `docs/parity-matrix.md`：`bridge` / `pull-request create` 注明 creation 配置与 overlay 已对齐；仍注明无 PR content repair。
- `README.md`：根键只写 `smartCommitHostAgent`；能力表/示例可带 `titlePrompt` / `reviewers` 等。
- `examples/config.host-agent.json`：可写与 CLI 示例同语义的 creation 字段（根键仍是 `smartCommitHostAgent`）。
- `src/contracts.ts`：config schema 增加新字段。
- `CHANGELOG.md`：记录破坏性根键收紧与新字段生效。

Plan 4「不移植 titlePrompt/descriptionPrompt」与 Plan 6「可不移植 configFilePath」被本规格覆盖，不回改那些历史文档。

不写迁移指南。

## 9. 测试

至少覆盖：

**配置**

- 默认：六个新字段为 `""` / `[]` / `false`；`pullRequestReview.configFilePath` 为 `""`。
- 字符串 trim；`reviewers` 整表替换 merge。
- 主配置出现 `smartCommitCli`、两个根键、或其它顶层键失败。
- overlay 出现 `smartCommitCli` 或其它顶层键失败。
- `configFilePath` 空则跳过。
- 逗号列表取第一个存在的文件；相对 `--repo`；缺文件列出 tried paths。
- creation overlay 禁止 `configFilePath` / `autoCreateAfterPush`；review overlay 禁止 `configFilePath`。
- overlay 覆盖主配置同名字段（overlay 赢）。

**prompt / title**

- 非空 `titlePrompt` / `descriptionPrompt` 出现在 user 消息对应 tuning 段；空则不出现。
- `titlePrompt` 非空时即使单条 commit 也用生成 title。
- 空 `titlePrompt` + 单条 commit 仍复用 subject。
- `--title` 与 `--description` 都提供时不发 pr-content turn。

**API / 调用链**

- GitHub：POST 含 `draft`；随后 `requested_reviewers`；milestone PATCH；设置失败进 `warnings`。
- GitLab：POST 含 `draft` / `reviewer_ids` / `milestone_id`；找不到 user 或 milestone 抛错。
- 空 reviewers / milestone 不发对应请求（GitHub）或不传对应字段（GitLab）。
- `bridge` / `pull-request create` 传入配置里的 `draft`，不再写死 `false`。

现有 `configResolve` 中「加载 smartCommitCli」「两个根键时 host-agent 优先」的测试改为断言失败。

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

1. 根键不再接受 `smartCommitCli`。只有该键、与 `smartCommitHostAgent` 同时存在、或其它顶层键都会失败。
2. 配置里已写的 `reviewers` / `titlePrompt` / `descriptionPrompt` / `milestone` / `draft` / `configFilePath` 从静默忽略变为解析并生效。
3. `draft` 跟配置走；默认仍是 `false`，但写了 `true` 会真正建草稿。
4. `titlePrompt` 非空时，单条 commit 不再覆盖生成 title。

不改变：`autoCreateAfterPush` / `removeSourceBranch` 默认值、无 chunked、无 repair、无 flag/env 覆盖层。
