---
name: xiaoma-generate-rap-interface
description: >
  按本地代码改动把接口同步到 RAP 接口管理平台（创建/更新接口及入参出参）。只创建和更新，不删除任何接口。
  流程：先与用户确认 6 项信息「RAP 地址 / RAP 账号密码 / 代码仓库路径 / 开发分支 / 对比分支 / 取材基准」并等用户明确确认 → 登录 → 按 git 改动算出本次要同步哪些接口（优先工作区未提交改动，没有则让用户指定 commit）→ 从 Controller 与 DTO 提取字段 → 生成 review.md 草案 → 用户手选 RAP 仓库与模块 → 写入前回读完整清单再等用户确认 → 调脚本创建/更新。
  取材：一律以本地代码为准（优先工作区未提交改动，没有则让用户从本分支自己的提交里点名）；需求文档仅作辅助，不作为字段来源。
  硬约束：两道门禁（开工前 6 项确认卡片、写入前写入确认卡片）须用户回复「确认」；仓库/模块必须用户手选；git 全程只读；凭据只在内存用不落盘；写入只走 scripts/rap.mjs。
  ⚠️ 仅手动调用：只有当用户显式输入 /xiaoma-generate-rap-interface 或点名「用 xiaoma-generate-rap-interface 这个 skill」时才启用；
  日常对话中出现「同步接口到 RAP」「把改的接口传到 RAP」「生成接口草案」等字眼不要自动触发本 skill，先问用户是否要用它。
  Use when：仅当用户显式调用本 skill 时启用。
disable-model-invocation: true
---

# RAP 接口同步

按**本地代码改动**把接口写入 RAP 平台。

**人机分工**：AI 读代码、写草案、编排调用；**用户**提供账号密码、确认取材范围、选仓库与模块、授权写入。

本 skill 自带全部依赖，整个目录拷到任何机器都能用，不依赖 jiekou-sync 仓库或任何 MCP server。

## ⛔ 两道硬门禁

| 门禁 | 位置 | 拿到用户「确认」前不许做的事 |
|---|---|---|
| **A：开工确认卡片** | 第 0 步 | 登录、任何 RAP 调用、任何 git 读取之外的动作 |
| **B：写入确认卡片** | 步骤 6 前 | `sync`（创建/更新接口，不可逆） |

两处都必须**回读实际值**、等用户明确回复「确认」。用户只改其中一项 → **改完重新回读整张卡片**，再等确认。

## 脚本路径（先确定，后面每条命令都要用）

所有 RAP 操作都通过本 skill 目录下的 `scripts/rap.mjs`。

**本次会话加载本 skill 时给出的 base directory 就是 skill 目录**，脚本是 `<skill目录>/scripts/rap.mjs`。

下文命令里统一写作 `$RAP`，这是**占位符不是 shell 变量**——每条命令执行前都要替换成完整绝对路径并用引号包住（路径可能含空格）。别指望 `RAP=...` 能沿用到下一条命令，每次 Bash 调用的 shell 状态是独立的。

找不到时按这个顺序试：

```bash
ls ~/.claude/skills/xiaoma-generate-rap-interface/scripts/rap.mjs
ls .claude/skills/xiaoma-generate-rap-interface/scripts/rap.mjs
```

## 流程

复制此清单并逐步推进：

```
- [ ] 0.0 自检环境（换机器后第一次用必做）
- [ ] 0.1 ⛔ 门禁 A：6 项确认卡片 —— RAP 地址 / RAP 账密 / 代码仓库 / 开发分支 / 对比分支 / 取材基准
         拿到用户「确认」前不许登录、不许调任何 RAP 接口
- [ ] 1.  登录（凭据只在内存，不落盘）
- [ ] 2.  算本次改动 → 出「改动 → 接口」清单表给用户核对
- [ ] 3.  从 Controller 与 DTO 提取字段，写 review.md
- [ ] 4.  用户手选仓库 id 与模块 id；按 method + 路径判定每条是新增还是更新
- [ ] 5.  更新类接口拉平台现状比字段；差异列给用户
- [ ] 6.  ⛔ 门禁 B：写入确认卡片 —— 回读将要写入的每一条，等用户「确认」后才 sync
- [ ] 7.  回报每条的编辑器链接、成功失败数、警告
```

**取材总则**：**新增和更新一律以本地代码为准**——新增接口的 Controller / DTO 通常就躺在工作区里（多为未追踪的新文件），更新则是已有文件的改动。需求文档只用来圈定本次范围、补字段说明、核对语义，**不作为字段来源**。两者冲突时以代码为准，并把差异点在确认环节列给用户。

代码确实还没写（纯前置设计阶段）时才退回文档，且要在 review.md 里标明「来源：需求文档，代码未实现」。

## ⛔ 硬性约束

### 写入方式

创建/更新只能调 `scripts/rap.mjs`（HTTP 接口）。**禁止用浏览器 MCP 去 RAP 页面点保存/编辑**，禁止用数据库或其它 MCP 直连写库。浏览器仅可用于只读排查。

### 只增不删

本 skill 只**创建**和**更新**接口。**严禁删除任何 RAP 接口**——即使代码里这个 Controller 方法已经删掉，即使用户开口要求。删接口只能由用户本人在 RAP 页面上做。

字段层面同理：平台上有而代码里没有的字段，**先问用户**再决定保留还是移除，不静默丢弃（见步骤 5）。

### 凭据处理

- RAP 账号密码**只在本次运行内存里用**：不回显、不写进 review.md / plan.json / 任何文件、不落日志、不进 memory、不出现在最终摘要里（摘要写「已使用账号 xxx」即可）。
- **禁止**从仓库文件、`.mcp.json`、`.env`、历史记忆或其它项目里读取或猜测凭据。
- 登录失败 → **原样报错让用户重给**，不许重试别的账号、不许绕过登录。

### 不猜

分支、commit、仓库 id、模块 id、接口归属、字段是否该删——任何一项判不准时，**列出候选交给用户定，然后停在这里**。严禁从候选列表里挑一个「看着像的」，严禁设「未命中则默认取某某」这类兜底。

### 步骤 0.0：自检

```bash
node "$RAP" doctor
```

一次性确认 Node 版本、脚本路径、RAP 地址可达性、会话状态。

- `error: 本脚本需要 Node >= 20` → 让用户装/升级 Node 20+，`node -v` 确认后再继续。
- `baseUrlReachable: false` → 让用户确认已连公司网络或 VPN；地址不对就在步骤 1 里传正确的 `RAP_BASE_URL`。
- `session.loggedIn: true` 且 `ageMinutes` 不大 → 会话还在，可跳过步骤 1。

同一台机器上后续会话，直接 `node "$RAP" status` 就够，不必每次跑 doctor。

### ⛔ 步骤 0.1：门禁 A —— 6 项确认卡片

**这是硬门禁。** 下面 6 项逐项问到、拼成卡片回读给用户、**拿到用户明确的「确认」之后**，才允许登录和调用任何 RAP 接口。**任何一项都不许自行推断、从代码里猜、或沿用上次会话的值。**

| # | 信息 | 怎么取 / 校验 | 缺失或不确定时 |
|---|---|---|---|
| 1 | **RAP 地址** | 默认 `http://rap.corp.yljr.com:8080`（API 在 :8080）。**默认值也要列进卡片**让用户看见并有机会改 | 用默认，但必须显式列出 |
| 2 | **RAP 账号密码** | 用户本人的账号 + 密码。可从环境变量 `RAP_USERNAME` / `RAP_PASSWORD` 取 | 停下来问 |
| 3 | **代码仓库路径** | 接口代码所在仓库在本机的路径——**注意不是需求文档所在的仓库**。先确认是 git 仓库（`git -C <路径> rev-parse --git-dir`） | 停下来问；没有本地仓库 → 改让用户贴改动清单 |
| 4 | **开发分支** | 读当前分支（`git -C <路径> rev-parse --abbrev-ref HEAD`）回读确认「这是本次需求的开发分支吗」。**不是 → 让用户自己切过去，停下来等**；⛔ 不替用户 checkout | 停下来等用户切分支 |
| 5 | **对比分支** | **默认 `origin/master`**（无则 `origin/main`）。默认值也要列进卡片 | 主干分支名不明 → 问用户 |
| 6 | **取材基准** | 先跑 `git status --porcelain`：有接口相关的 Java 改动 → 填「工作区未提交改动（N 个文件）」；没有 → 列出**本分支自己的提交**让用户点名用哪次。详见 [change-detection.md](change-detection.md) | 两者都为空 → 停下来问用户 |

需求文档是**可选项**，有就在卡片里列出路径并注明「仅作辅助，不作为字段来源」。

#### 确认卡片（回读格式）

```
同步接口到 RAP 前请确认以下信息：

  1. RAP 地址   : http://rap.corp.yljr.com:8080          ← 默认值
  2. RAP 账号   : zhangsan　密码：已提供（环境变量 RAP_PASSWORD）
  3. 代码仓库   : D:\code\fc-payment
  4. 开发分支   : feature/quota                          ← 仓库当前分支
  5. 对比分支   : origin/master                          ← 默认值，已 fetch 刷新
  6. 取材基准   : 工作区未提交改动（3 个 Java 文件）
       ?? QuotaController.java
       ?? QuotaQueryDTO.java
        M CustInfoQueryDTO.java
  7. 需求文档   : docs\需求文档.md（仅作辅助，不作为字段来源）

  本次只会创建和更新接口，不会删除任何 RAP 接口。

以上确认无误请回复「确认」，我再登录并开始取材。
```

规则：

- 每项都要**显示实际值**，不许写「已确认」「同上」糊弄过去。
- 有默认值的（RAP 地址、对比分支）**必须显式列出**，让用户有机会否决。
- 用户只回复部分修改（如「对比分支换成 release/2026-08」）→ **改完重新回读整张卡片**，再等确认。
- ⛔ 用户没回「确认」（或等价表述）之前，**一个 RAP 动作都不许做**。用户直接说「开始吧」但卡片里还有「待提供」的项 → 先把那项补齐。

### 步骤 1：登录

会话不在时按卡片里确认的地址和账号登录：

```bash
RAP_BASE_URL='http://rap.corp.yljr.com:8080' RAP_USERNAME='<账号>' RAP_PASSWORD='<密码>' \
  node "$RAP" login
```

**密码含 `$` `!` `"` `'` `` ` `` 等字符时**，别用上面的内联写法（shell 会做转义/历史展开），改走管道：

```bash
printf '%s' '{"baseUrl":"http://rap.corp.yljr.com:8080","username":"<账号>","password":"<密码>"}' \
  | node "$RAP" login --stdin
```

PowerShell 用户：

```powershell
$env:RAP_BASE_URL='http://rap.corp.yljr.com:8080'; $env:RAP_USERNAME='<账号>'; $env:RAP_PASSWORD='<密码>'
node "$RAP" login
Remove-Item Env:RAP_PASSWORD
```

登录成功的标志是返回 `ok: true`——脚本会用一次真实查询验证会话确实可用，只拿到 cookie 不算成功。会话 cookie 写入系统临时目录（权限 0600），有效期约 8 小时，后续命令自动复用，不需要再问密码。

**若用户不希望密码出现在对话里**：告诉用户在输入框用 `!` 前缀自己执行上面的 `login` 命令，登录成功后你从步骤 2 继续。

用完可以 `node "$RAP" logout` 清掉会话，共用电脑时建议做。

### 步骤 2：算本次改动 → 出清单表

按门禁 A 里确认的取材基准，得出**权威文件清单**，再映射成「本次要同步哪些接口」。完整规则见 [change-detection.md](change-detection.md)，要点：

- **工作区未提交改动**：读工作区当前内容；`git diff` / `git diff --cached` 只用来看清改了什么。
- **用户指定的提交**：`git show <hash>:<path>` 读**那个提交时点**的内容，不是工作区。
- 候选提交列表用 `git log --no-merges "$FEATURE_BRANCH" --not "$BASE"`（本分支自己的提交），**不是裸 `git log -20`**——后者会把 master 上别人的提交摆进来让用户误选。
- 只改 Service / Mapper / 工具类而 Controller 签名没变 → **不同步**。DTO 改动 → 找出引用它的 Controller 方法，把那条接口纳入范围。

全程**只读**：不许 `checkout` / `pull` / `merge` / `stash` / `rebase`，不许改用户工作区。后续所有路径用**绝对路径**。

**产出改动清单表给用户核对**（格式见 [change-detection.md](change-detection.md#产出改动清单表取材后先给用户确认)），把不同步的文件也列出来说明理由。用户说漏了/多了，**以用户为准**。确认后再进步骤 3。

需求文档是**辅助**：用来核对语义、补 description。只有代码确实还没写（纯设计阶段）才退回文档取材，此时文档里没写的**留占位符**（`（方法待补）`/`（路径待补）`），不要编造。

### 步骤 3：从代码提取接口定义并写 review.md

对每个接口抽出：名称、HTTP 方法、路径、说明、入参、出参。

Controller 注解拼 path 与 method，`@RequestBody` 的 DTO 展开成入参，返回类型拆到最内层业务对象展开成出参，**父类字段要一起展开**。完整规则见 [code-extraction.md](code-extraction.md)。

出参按代码里统一响应壳的实际字段名归一（常见是 `{ msg, status, data }`，`data` 为对象或数组）；壳字段名不一样就以代码为准，别硬套。

写到 `<代码所在项目目录>/interface-docs/<yyyy-mm-dd-hh-mm-ss>/review.md`，格式见 [reference.md](reference.md#reviewmd-格式)。每条都要写明**代码来源**（文件路径:行号，以及「工作区未提交」或 commit hash），便于事后追溯。

变更类型这时可以先按 git 状态预填（未追踪 → 新增、已修改 → 更新），步骤 4 拿到平台数据后再定稿。

### 步骤 4：用户手选仓库与模块

```bash
node "$RAP" repos          # 列仓库
node "$RAP" repo <仓库id>   # 列该仓库的模块与已有接口
```

**硬性规则**：把候选列表展示给用户，等用户明确回复 id。**禁止**按名称相似度、文档关键词、列表顺序、"只有一个模块" 等理由自行择一——选错会把接口写进别人的仓库。

`repos` 返回空列表通常不是没权限，而是会话已失效，回步骤 1 重新登录。

#### 定稿变更类型

`repo <仓库id>` 的返回里带了所选模块下的全部接口。拿代码提取出的 **method + 路径**逐条去匹配：

| 匹配结果 | 变更类型 | 后续 |
|----------|----------|------|
| 匹配到 | 更新 | 记下 `interfaceId`，再跑 `itf <接口id>` 拿全量字段 |
| 没匹配到 | 新增 | 无需 `itf` |

git 状态的预判和平台匹配结果不一致时（比如文件是未追踪的新文件，但平台上早就有这个路径），**以平台匹配结果为准**，并在确认环节告诉用户。

git 状态的预判和平台匹配结果不一致时（比如文件是未追踪的新文件，但平台上早就有这个路径），**以平台匹配结果为准**，并在门禁 B 的卡片里告诉用户。

### 步骤 5：更新类接口比对字段

判定为更新的每条接口，都要拉平台现状比一遍：

```bash
node "$RAP" itf <接口id>
```

逐条比对，分成三类：

| 情况 | 处理 |
|------|------|
| 平台有、代码也有 | 带回平台的 `id` 与 `priority`，用代码的 type / required / description |
| 平台没有、代码有 | 新增字段，`id` 用 `memory-N` 或省略 |
| 平台有、代码没有 | ⛔ **停下来问用户**：是代码里已删除（RAP 也该删）还是你漏提取了。**不静默丢弃** |

第三类没得到用户答复之前，**不许带着走进门禁 B** —— 卡片里出现「待你回答」的项就不是一张可确认的卡片。

把比对结果同步回 review.md，用户可以直接改这个文件，改完以磁盘内容为准。

### ⛔ 步骤 6：门禁 B —— 写入确认卡片

`sync` 会真的在 RAP 上创建接口、覆盖字段，**不可逆**。执行前把将要写入的每一条回读给用户，等明确的「确认」。

#### 写入确认卡片（回读格式）

```
⛔ 即将写入 RAP，请确认：

  RAP 地址   : http://rap.corp.yljr.com:8080
  登录账号   : zhangsan
  目标仓库   : 108 供应链金融        ← 你选定的
  目标模块   : 772 额度中心          ← 你选定的
  取材基准   : 工作区未提交改动（分支 feature/quota）
  草案文件   : D:\code\fc-payment\...\interface-docs\2026-08-12-16-40-05\review.md

  本次将写入 3 个接口：

   1. [新增] 查询客户额度   POST /api/cust/quota/query
        入参 2 个 / 出参 4 个　　来源 QuotaController.java:9
   2. [新增] 冻结客户额度   POST /api/cust/quota/freeze
        入参 3 个 / 出参 3 个　　来源 QuotaController.java:14
   3. [更新] 查询客户信息   POST /api/cust/info  (interfaceId=34901)
        + 新增入参 withQuota (Boolean, 选填)
        = 保留 custNo / status
        - 平台上的 legacyFlag 代码里已无 → 按你的答复【保留】
        来源 CustController.java:18

  不在本次范围：/api/cust/export（代码有改动，你在步骤 2 排除了）

  本次只创建和更新，不会删除任何接口。

以上确认无误请回复「确认」，我再执行同步。
```

规则：

- 每条必须显示**实际值**：变更类型、method、路径、`interfaceId`、字段增删、代码来源。
- 更新类必须显式列出 `+` 新增 / `=` 保留 / `-` 平台有而代码没有 三类字段的处置。
- 卡片里**不许有「待定」「待你回答」的项**——有就先问清楚再重新回读整张卡片。
- 用户只改其中一项（换模块、去掉某条接口）→ **改完重新回读整张卡片**，再等确认。
- ⛔ 用户没回「确认」之前，**不许跑 `sync`**。用户说「传吧」但卡片还没回读过 → 先回读。

#### 确认后：生成 plan 并执行

更新类接口，步骤 5 拿到的平台现有字段这时要用上：**已有字段必须带回原 `id` 与 `priority`**，否则会被判为新增并丢失历史。

需要把 JSON 示例批量转成 RAP 扁平属性时（新增接口居多），可以用辅助命令，生成后再按代码补 `description` / `required`：

```bash
node "$RAP" props-from-json /tmp/example.json     # 或用 - 从 stdin 读
```

写 `plan.json`（放在 review.md 同目录，字段说明见 [reference.md](reference.md#planjson-格式)）：

```json
{
  "repositoryId": 123,
  "moduleId": 456,
  "userConfirmed": true,
  "interfaces": [
    {
      "changeType": "新增",
      "name": "查询客户额度",
      "method": "POST",
      "url": "/api/cust/quota/query",
      "description": "按客户号查询可用额度",
      "bodyOption": "JSON",
      "properties": [
        { "scope": "request", "name": "custNo", "type": "String", "parentId": -1, "required": true, "description": "客户号" },
        { "scope": "response", "name": "status", "type": "Number", "parentId": -1, "description": "状态码" }
      ]
    }
  ]
}
```

`userConfirmed: true` 是**门禁 B 的机器可读凭证**：表示用户已经看过写入确认卡片并回复了「确认」。用户没回「确认」就不许写这个字段，脚本会拒绝执行。

执行：

```bash
node "$RAP" sync <plan.json 绝对路径>
```

每个接口独立处理：新增走 create → unlock → update meta → properties；更新走 unlock → update meta → properties。单条失败不影响其他条，结果里 `ok: false` 的条目要如实报给用户。

完整走一遍的例子见 [examples.md](examples.md)。

### 步骤 7：回报结果

把 sync 返回的每条 `editorUrl`（`/repository/editor?id=&mod=&itf=`）给用户。**不要**自己拼 `/interface/detail/{id}`。

同时明确说明：成功几条、失败几条、失败原因、有没有 `warnings`（尤其是属性单侧提交的警告）。

## 硬性规则速查

1. **门禁 A**：6 项确认卡片没拿到用户「确认」，不许登录、不许调任何 RAP 接口。
2. **门禁 B**：写入确认卡片没拿到用户「确认」，不许跑 `sync`。卡片里有「待你回答」的项就不算数。
3. 凭据只从用户当次对话或环境变量获取，不从仓库文件读取，不写进 plan.json、review.md 或任何磁盘文件，不进最终摘要。
4. 仓库 id、模块 id 必须用户手选。
5. **新增和更新都以本地代码为准**：优先用工作区未提交改动；没有则让用户从**本分支自己的提交**里点名，绝不自己挑一个。需求文档只作参考，仅在代码尚未实现时才作为来源。
6. 平台有而代码没有的字段，先问用户，不静默删除。
7. **只增不删**：不删除任何 RAP 接口，用户要求也不行——删接口由用户自己在页面上做。
8. git 全程只读：不 checkout / pull / merge / stash / rebase，不改用户工作区。
9. 写入只走 `rap.mjs`，不用浏览器或其它 MCP 去页面上点保存。
10. 路径一律绝对路径。
11. 入参出参一次提交合并 request + response（脚本已固定 `posFilter: 1`）；只提交单侧会清空另一侧。
12. 代码或文档里没有的信息留占位，不编造。

## 故障排查

| 现象 | 处理 |
|------|------|
| `需要 Node >= 20` | 装 Node 20+；`doctor` 会显示当前版本 |
| `未检测到 RAP 登录会话` | 重跑步骤 1 的 `login` |
| 登录返回 `拿到了 cookie 但会话不可用` | 账号密码错，或地址指向了非 API 端口（API 通常在 `:8080`） |
| 登录四种形态都没拿到 cookie | 先 `doctor` 看地址是否可达；再设 `RAP_LOG_FILE=/tmp/rap.log` 重跑看响应 |
| 返回 HTML 而非 JSON | 会话失效或 baseUrl 端口错，重新 `login` |
| `repos` 返回空 | 多半是会话失效，重新 `login` |
| `变更类型为「更新」，但未匹配到接口` | 在 plan 里补 `interfaceId`，或核对 method + 路径与 RAP 一致 |
| `unlocked: false` | 接口被他人锁定，只有锁定人能解锁；联系对方或到网页处理 |
| 已有字段报「必须带平台下发的 priority」 | 先 `itf <id>` 取回全量属性，带上原 `id` 与 `priority` |
| 入参或出参被清空 | 两侧属性合并到一次 `properties` 里提交 |
| 项目根不是当前目录，git 命令报 not a repository | `git -C <项目根>` 指到代码所在仓库；需求文档和代码常不在同一个仓库 |
| `status --porcelain` 有输出但全是非 Java 文件 | 视为「没有未提交改动」，走问用户要 commit 那条路 |
| 提取出的入参出参是空的 | 多半只读了 Controller 没读 DTO，或漏了父类字段，见 [code-extraction.md](code-extraction.md) |
| 代码里的路径与 RAP 上的对不上 | 检查类级 `@RequestMapping` 前缀是否拼上了；对不上就是接口匹配失败的根因 |
| `git fetch` 需要 GitLab 认证 | 停下，**在对话里直接问用户**要 GitLab 账号密码/token（说明原因：刷新对比基线），或让用户自己 fetch 一次；⛔ fetch 必须带 `GIT_TERMINAL_PROMPT=0 GIT_SSH_COMMAND='ssh -o BatchMode=yes'`，绝不弹 OpenSSH/交互窗口把用户晾在那儿；不绕过、不用旧引用硬算，见 [change-detection.md](change-detection.md) |
| 当前分支不是开发分支 | 让用户自己 `git checkout`，停下来等；⛔ 不替用户切分支 |
| `log --not origin/master` 输出为空 | 分支相对基线没有自己的提交，停下来问用户：分支给错了？代码没推？ |
| 三点 diff 比本分支提交多出一堆文件 | 分支合并过新 master，多出的是别人的改动，以本分支提交为准并说明 |

- **算本次改动**（分支确认、取材基准、改动 → 接口映射、清单表）：[change-detection.md](change-detection.md)
- **提取字段**（Spring 注解、DTO → properties、类型映射）：[code-extraction.md](code-extraction.md)
- 格式与平台行为：[reference.md](reference.md)
- 完整走一遍的例子：[examples.md](examples.md)
- 给人看的安装说明：[README.md](README.md)
