# 识别「本次要录制交付的代码改动」

交付单里**没有代码仓库/分支字段**（详情只有 `jiraCode`、`projectCode`），平台侧推不出改动范围。
所以必须由用户给出代码来源，再由 diff 反推该配哪些页签。**禁止从需求描述臆测改动**——这是 skill 顶层「不猜」原则的一部分。

## 方式 A：本地 git 仓库（默认，最准）

第 0 步向用户要 3 个值：

| 变量 | 含义 | 默认 / 确认方式 |
|---|---|---|
| `REPO_PATH` | 服务代码仓库在本机的路径 | 必问。注意是**这次要录制的那个服务**的仓库，多服务单仓要问清模块目录 |
| `FEATURE_BRANCH` | 本次需求的开发分支 | 读当前分支并**回读确认**，见下方「⛔ 分支确认」 |
| `BASE_REF` | 对比基线 | **默认 `origin/master`**，见下方「⛔ 基线选择」。仓库无 `origin/master` 用 `origin/main`。只有用户明确指定了其它基线（发布分支/生产 tag/具体 commit）时才换，换了要回读确认 |

### ⛔ 分支确认（开工前第一件事）

```bash
cd "$REPO_PATH" && git rev-parse --abbrev-ref HEAD     # 当前分支
```

拿到当前分支后**必须问用户**：「当前在 `xxx` 分支，这是本次需求的开发分支吗？」

- **是** → 记为 `FEATURE_BRANCH`，继续。
- **不是**（比如停在 `master`、`develop`、上个需求的分支、或 detached HEAD）→ **让用户自己切到开发分支，然后停下来等**。
  话术：「当前在 `master`，不是本次需求的开发分支。请你切到本次需求的开发分支（`git checkout feature/xxx`）后告诉我，我再继续。」
  - ⛔ **不许我替用户执行 `git checkout` / `switch` / `pull` / `stash`**——用户工作区可能有未提交改动，切分支会丢东西。切分支是用户的动作。
  - ⛔ **不许绕过**：不许「那我直接 diff 用户口头说的那个分支名」就往下走，也不许在 `master` 上硬配。等用户切完、重新读一次当前分支确认，再继续。

### 基线：固定 `origin/master`

**一律和 `origin/master` 比，不用本地 `master`**（本地 master 常年不更新，比出来会混进别人早就合并的改动）。

```bash
cd "$REPO_PATH"
# 刷新远端引用。两个开关都必须带：
#   GIT_TERMINAL_PROMPT=0                  禁掉 git 自己的交互式账号密码输入
#   GIT_SSH_COMMAND='ssh -o BatchMode=yes' 禁掉 SSH 的一切交互（含 OpenSSH passphrase 弹窗），认证不过直接报错
GIT_TERMINAL_PROMPT=0 GIT_SSH_COMMAND='ssh -o BatchMode=yes' git fetch origin --prune
```

⛔ **不许省掉 BatchMode**：remote 是 SSH 形态（`git@…`）且 key 带 passphrase 时，少了它 Windows 会弹出一个 OpenSSH 输入窗口，用户根本不知道那窗口里该输什么。**本 skill 的任何步骤都不许弹出交互式输入窗口，要认证一律在对话里开口问**（见下节）。

仓库没有 `origin/master` → 退到 `origin/main`；都没有就问用户主干分支叫什么。

> `git diff origin/master...分支` **不走网络**，`origin/master` 是本地的远程跟踪引用；
> 只有上面这句 `fetch` 需要联网、才可能要凭据。

### ⛔ fetch 需要 GitLab 认证 → 在对话里直接问，说清原因，绝不弹窗口

`git fetch` 可能要 GitLab 凭据（私有仓库、凭据过期、要 2FA/token）。表现为报错含
`Authentication failed` / `could not read Username` / `terminal prompts disabled` / `Permission denied (publickey)` / `403` / `fatal: could not read Password`。

**遇到就立即停下，在对话里直接向用户开口，并把原因说清楚**——不要绕过、不要重试、不要拿旧的本地引用硬着头皮往下算，更不许：

- ⛔ **去掉上面两个开关重跑 fetch**，去触发 OpenSSH / Git Credential Manager 之类的交互窗口——用户不知道在窗口里该输什么。
- ⛔ **`ssh-add`、动用户的 key、改 remote 地址**，或替用户切换认证方式。

按 remote 形态（`git remote -v` 看）这样问（**原因必须说**：刷新 origin/master 基线）：

| remote 形态 | 怎么问 |
|---|---|
| **HTTPS** | 「为了算准本次改动，我需要先从 GitLab 刷新一次 `origin/master` 基线，避免把别人的改动算成本次交付内容。这需要你提供 **GitLab 账号 + 密码（或 access token）**，只在本次内存里用、不落盘；或者你自己在仓库目录执行一次 `git fetch origin` 完成认证后告诉我。」 |
| **SSH**（`git@…`） | 「SSH 认证走的是你本机的 SSH key，**没法用账号密码，我也不会弹 OpenSSH 窗口让你输东西**。请你自己在仓库目录执行一次 `git fetch origin`（passphrase 在你自己的终端里输），完成后告诉我，我再继续。」 |

- 用户给了 HTTPS 凭据 → 用它完成 fetch。**凭据只在本次内存里用**：不回显、不写进任何文件、不落日志（同交付平台密码的处理规则）。
- 用户自己 fetch 完成 → 直接继续。
- 用户说「先不拉」→ 按用户说的做，用本地已有的 `origin/master` 引用比，但**必须在最终摘要里注明「origin/master 未刷新，可能不是最新」**。

### ⛔ 算改动：以「本分支自己的提交」为准，不是裸 diff

`origin/master` 上会有别人不断合入的代码，开发分支也常把新 master 合回来。
**裸 diff 很容易把别人的改动算成本次交付内容**，配出一堆不该配的页签。所以按下面顺序做：

```bash
BASE=origin/master

# ① 本分支自己的提交 = 实际开发量（--not BASE 排除所有已在 master 上的提交，含合并进来的）
git log --no-merges --oneline "$FEATURE_BRANCH" --not "$BASE"
git log --no-merges --format='%h %an %ad %s' --date=short "$FEATURE_BRANCH" --not "$BASE"
git log --no-merges --shortstat "$FEATURE_BRANCH" --not "$BASE" | tail -3   # 增删行规模

# ② 【权威清单】只由这些提交触碰的文件
git log --no-merges --name-status --format='' "$FEATURE_BRANCH" --not "$BASE" | sort -u

# ③ 对照用的三点 diff（分叉点 → 分支尖端）
git merge-base "$BASE" "$FEATURE_BRANCH"
git diff --name-status "$BASE...$FEATURE_BRANCH"
git diff --stat "$BASE...$FEATURE_BRANCH"
```

**②「本分支提交实际触碰的文件」是配置页签的唯一依据**，③ 只用来交叉验证。

| ② 与 ③ 的关系 | 含义 | 动作 |
|---|---|---|
| 一致 | 分支很干净 | 正常往下走 |
| ③ 比 ② 多出文件 | 分支合并过新 master，或有回滚；多出的是**别人的改动** | **以 ② 为准**，把多出的文件列出来说明「这些是 master 上别人的改动，不计入本次交付」 |
| ② 比 ③ 多出文件 | 分支里改了又改回去（净变化为 0） | 该文件**不配**，但在摘要里提一句 |
| ② 为空 | 分支相对 master 没有任何自己的提交 | **停下来问用户**：分支给错了？代码没推？还是这次本来就没改代码 |

**先把「实际开发量」报给用户，再往下配**：

```
开发分支 feature/ZJJC-2452 相对 origin/master 的实际开发量：
  自己的提交 7 个（作者：张三 6 / 李四 1，2026-08-04 ~ 08-11）
  触碰文件 12 个，+318 / -47 行
  注：三点 diff 显示 96 个文件，多出的 84 个是 master 上别人合入的改动，已排除
```

分支上出现**别人作者的提交**、或提交信息明显不属于本次需求 → 提出来让用户确认是否计入。

> 全程**只读**：不许 checkout / pull / merge / stash / rebase，不许改用户工作区。
> 工作区有未提交改动时提醒用户「这些没进 diff」（`git status --short`）。

## 方式 B：粘贴 diff / 改动文件清单（无本地仓库时兜底）

让用户按此格式贴：

```
A  src/main/java/.../XxxController.java
M  src/main/resources/db/migration/V20260820__add_col.sql
M  src/main/java/.../XxxJob.java
```

或直接贴 `git diff` 全文。解析不出文件路径就停下来问，**不接受「我改了订单接口」这类口头描述**作为唯一输入。

## 改动 → 页签 映射规则

对每个改动文件按下表归类，**命中才配对应页签，未命中一律不配**（对应顶层「门禁：项目没引入就不配」）：

| 改动特征 | 页签 | 还要做什么 |
|---|---|---|
| `*.sql`，或 flyway/liquibase 迁移目录（`db/migration`、`db/changelog`）新增文件 | SQL `sqlFile` | 按 [../tabs/sql.md](../tabs/sql.md) 定库类型/地址 |
| **新增实体类 / Mapper**：`@TableName("t_xxx")`、`@Table(name=)`、`@Document()`、新 `XxxMapper.java`+`XxxMapper.xml`，或 XML 里出现旧代码没有过的表名 | SQL `sqlFile`（**建表语句**） | ⛔ 新增表**必须带 `CREATE TABLE`**：优先 `SHOW CREATE TABLE` 取测试环境真实结构（表已建时）；其次取仓库迁移脚本原文；都没有就**结合实体类 + Mapper XML + 同库其他表的既有 DDL 生成**，并标注「已自动生成，建议 DBA 复核」。见 [../tabs/sql.md](../tabs/sql.md) 第二节 |
| `bootstrap*.yml/properties`、`application*.yml` 新增或改动配置键；新增 `@Value` / `@ConfigurationProperties` 字段 | nacos | 去 Nacos 读该 key 的测试环境现值回填 |
| `@RocketMQMessageListener`、topic/tag 常量新增，新增 producer 发送点 | rocketmq `mq` | 按 [../tabs/rocketmq.md](../tabs/rocketmq.md) 取集群/topic |
| `@XxlJob("handler")` 新增或改名，任务类新增 | 定时任务 `cronJobs` | 去 XXL-Job 读该 handler 现值 |
| 新增/改动的**数据库、外部接口、中间件地址**（新库、新域名/IP、新端口） | 防火墙 `firewall` | 按 [../tabs/firewall.md](../tabs/firewall.md) 的触发判据判断；只改逻辑、复用老地址 → 不配 |
| `@RestController`/`@RequestMapping`/`@GetMapping` 等新增或改动的**接口路径** | 四个网关页签 | **默认不配**（见下）。用户明确要求时才按 [gateway-export-workflow.md](gateway-export-workflow.md) 检索导出，并先过「测试环境门禁」 |

### ⛔ 网关默认不配

**检测到接口改动 ≠ 要配网关。** 四个网关页签默认全部跳过，改动清单里注明一句：

```
检测到 3 个接口改动（/order/export 等），网关页签默认不配 —— 需要配请告诉我配哪几个网关。
```

只有用户明确说要配（「网关也配一下」「把外网网关配了」）才走网关流程；没点名是哪个网关就问清楚。

判不准归属（如某接口走内网还是外网、某配置是不是走 Nacos）→ **列出候选交用户定**，不自己拍板。

### ⛔ 网关门禁：测试环境没配，就不要配

代码里有新增接口 **≠** 就该配网关。判据是**测试环境网关平台上的现状**，不是代码：

1. 先去对应测试环境的网关平台，用**服务名 `uri`** 检索该服务的路由；
2. **该服务在这个网关平台上一条配置都没有** → 说明本服务根本不走这个网关，**该页签保持 `[]`，什么都不要配、不要上传文件**，在摘要里写明「测试环境 {env} 的 {平台} 无本服务配置，已跳过」；
3. 服务有配置、但**本次新增的接口在测试环境里查不到** → 说明这个接口不走网关，**直接跳过它，不要问用户、不要卡流程**；其余命中的接口照常导出。摘要里列一行「`/xxx` 在 {平台} 未查到，已跳过」即可。**绝不替它编一条塞进导出。**
4. 四个网关页签**各自独立判断**（内网/外网/文件流/中台），不要因为外网有配置就顺手把内网也配上。

一句话：**导出的内容必须是测试环境上真实存在的配置**。测试环境查不到的，一律不配、不编造。

## 产出：改动清单表，配置前先给用户确认

读完 diff、动手读现值之前，先输出这张表让用户核对：

```
本次改动 → 待配页签（请核对，多配少配都在这一步纠正）
┌────────────────────────────────────┬──────────────┬────────────────────┐
│ 改动文件                            │ 页签         │ 依据               │
├────────────────────────────────────┼──────────────┼────────────────────┤
│ V20260820__add_col.sql (A)          │ SQL          │ 新增迁移脚本        │
│ OrderController.java (M)            │ 外网网关      │ 新增 /order/export │
│ SyncJob.java (A)                    │ 定时任务      │ @XxlJob("syncJob") │
├────────────────────────────────────┼──────────────┼────────────────────┤
│ 未命中任何页签：README.md、单测 3 个  │ —            │ 不配置              │
└────────────────────────────────────┴──────────────┴────────────────────┘
```

用户确认后再进入「读现值 → 组装报文 → saveDeliver」。**用户说漏了/多了，以用户为准。**
