---
name: gitlink-duplicate-detector
version: 1.0.0
description: "重复 Issue 检测与关联收敛：扫描仓库开放 Issue，按标题+正文做语义相似度聚类，识别重复簇、选出主 Issue，经确认后发布关联评论并（可选）关闭重复项。当用户需要收敛重复 Issue、清理看板、或在处理新 Issue 前查重时触发。"
metadata:
  requires:
    bins: ["gitlink-cli"]
  cliHelp: "gitlink-cli issue --help"
  requiresVersion: "需要 gitlink-cli 含 `issue +comments`、`label`、`issue +list --keyword` 的版本（本仓库源码已具备；npm 发布版 v0.1.13 暂缺，待 ≥ v0.2.0）"
---

# gitlink-duplicate-detector（重复 Issue 检测与关联收敛）

**CRITICAL — 开始前必须先阅读 [`../gitlink-shared/SKILL.md`](../gitlink-shared/SKILL.md)，其中包含认证、权限处理和 API 注意事项。**
**CRITICAL — 所有 Shortcuts 在执行写入/删除操作（评论、关闭、打标签）前，务必先确认用户意图；默认 dry-run，只读分析。**

## 说明

本 Skill 在**同一 `owner/repo` 内**扫描开放 Issue，对「标题（subject）+ 正文（description）」做语义相似度聚类，找出疑似重复簇，为每个簇选出「主 Issue」，输出结构化《重复 Issue 收敛报告》；经用户逐条确认后，在重复项上发布指向主 Issue 的关联评论，并可（可选）关闭重复项。目的是收敛 Issue 列表、避免重复劳动。

**与 `gitlink-issue-triage` 的边界**：triage 给单个 Issue **分类打标签/分派**；本 Skill 在 Issue **之间**找重复关系，**不分配类别标签**（至多复用项目已有的「重复/duplicate」标签作为幂等标记）。两者职责正交，可串联。

## 前置条件

- 已 `gitlink-cli auth login`（`auth status` 为已登录）。
- gitlink-cli 版本需支持 `issue +comments`、`label +list`、`issue +list --keyword`、`issue +update --label`（本仓库源码已具备；npm 发布版 v0.1.13 不具备，需用源码编译版或 ≥ v0.2.0）。检查方式：
  ```bash
  gitlink-cli issue +comments --help    # 存在即满足
  gitlink-cli label --help              # 存在即满足
  ```

## 依赖的 Shortcuts

| Shortcut | 关键参数 | 用途 |
|----------|----------|------|
| `issue +list` | `--state/-s`、`--keyword/-k`、`--label`(标签ID)、`-m`、`-a`、`-p`、`-l`、`--format json` | 批量拉取开放 Issue（含 `number`/`subject`/`comment_journals_count`/`tags`/`created_at`） |
| `issue +view` | `--number/-n`（**Web 编号**）、`--format json` | 取单条 `description` 正文 |
| `issue +comments` | `--number/-n`、`-p`、`-l` | 读已有评论（**幂等检查**：是否已存在关联标记） |
| `issue +comment` | `--number/-n`、`--body/-b` | 在重复项上发布「指向主 Issue」的关联评论 |
| `issue +close` | `--number/-n`、`--comment/-c` | 关闭重复项（可选，需确认） |
| `issue +update` | `--number/-n`、`--label`(标签ID) | 可选：给重复项打「重复」标签（幂等标记） |
| `label +list` | — | 确认是否存在「重复/duplicate」标签并取其 ID |

> ⚠️ `--number` 取的是 **Web URL 编号**（如 `#21`），来自 `issue +list` 返回的 `number` 字段，**不是** `database_id`。

## 工作流程

```
1. issue +list --state open --limit 100 --format json
   → 取全部开放 Issue（必要时 --page 翻页；大仓可用 --keyword 预筛某主题）
2. 取每条的 number / subject / comment_journals_count / tags / created_at
3. 粗筛候选对：相同标签、标题共享组件/模块词、或同作者连续提交
4. issue +view --number N（仅对候选）
   → 取 description 正文，与候选对做相似度比对
5. 聚类 + 主从判定 → 生成《重复 Issue 收敛报告》（见下）
   ----- 以上为只读分析，默认到此为止 -----
6. （用户确认后）幂等检查（主用评论幂等）：
   issue +comments --number N → 读 data.journals[]，看 is_journal_detail=false 的 notes
                                 是否已有「🔁 疑似重复 / duplicate-of」标记 → 有则跳过
   再查 status_name=关闭 → 已关闭则跳过
7. （用户逐条确认后）写动作：
   - issue +comment --number N --body "..."   → 关联评论（主幂等依据）
   - issue +close   --number N --comment "..." → 可选，关闭重复项
   - （标签标记 `issue +update --label` 实测不可靠，见「已知限制」，不建议依赖）
```

## 判定规则参考

### 相似度判定（0–1）

综合 **词面相似度（标题+正文关键词 Jaccard）** 与 **Agent 语义判断**，给出分数与一句话理由：

| 分数区间 | 判定 | 建议动作 |
|----------|------|----------|
| ≥ 0.85 | **高度疑似重复**（正文几乎一致/同作者同时间） | 关联评论 + 建议关闭 |
| 0.60–0.85 | **疑似重复**（主题相同，细节有出入） | 关联评论，由人决定是否关闭 |
| 0.40–0.60 | **相关但非重复**（同模块不同问题） | 仅在报告里「相关项」列出，**不**关联、**不**关闭 |
| < 0.40 | 不相关 | 不处理 |

> 强信号（任一命中即可显著加分）：正文逐字相同、同作者同一分钟内提交、标题仅序号/编号差异（如 "测试 issue 1" vs "测试 issue 2"）。
> 必须避免**过度合并**：症状不同（如「描述丢失」vs「状态框变红」vs「关闭时间缺失」）即使都关于 `+view/+update`，也应判为「相关但非重复」。

### 主 Issue 选择（每个重复簇选 1 个）

按优先级依次比较，取首个胜出者：
1. `comment_journals_count` 最大（讨论最充分）；
2. `created_at` 最早（先提出）；
3. 正文 `description` 较长/更完整。

### 幂等规则（避免重复评论/重复关闭）— 已实测验证

对每个拟处理的重复项 `N`，执行写动作前：

1. **评论幂等（主，已验证可靠）**：`issue +comments --number N`，读 `data.journals[]`，**仅看 `is_journal_detail=false` 的条目的 `notes`**；若含标记串（`🔁 疑似重复` / `duplicate-of` / `重复，指向 #`）→ 跳过评论。
   - 响应结构：`data` 是**对象**，评论在 `data.journals[]`；每条含 `is_journal_detail`（true=系统动态如「创建了疑修/将状态更改为关闭」，false=文字评论）、`notes`（评论正文）、`operate_content`（动态描述）。系统动态 `notes` 为空，须过滤。
2. **状态幂等（已验证可靠）**：`issue +list --state closed` 含该编号，或 `+view` 的 `status_name=关闭` → 跳过关闭。
3. **标签幂等（不推荐，见「已知限制」）**：`issue +update --label` 实测返回 `ok` 但标签未真正生效，**不可靠**，不要依赖它做幂等判定。

## 关联评论模板

在**重复项**上发布（`issue +comment --number <重复项> --body "..."`）：

```markdown
🔁 **疑似重复 Issue**

本 Issue 与 #<主Issue编号> 描述的问题高度相似，已标记为重复项。
- 主 Issue：<主Issue标题> → https://www.gitlink.org.cn/<owner>/<repo>/issues/<主Issue编号>
- 相似度：<分数>（<一句话理由>）

请到主 Issue 继续跟进；如确认重复，本 Issue 将被关闭。
（由 gitlink-duplicate-detector 辅助识别，最终以维护者判断为准）
```

关闭时留言（`issue +close --number <重复项> --comment "..."`）：

```markdown
关闭：与 #<主Issue编号> 重复，后续讨论请移步主 Issue。
```

## 使用示例

> 以下命令在目标仓库的 git 目录下可省略 `--owner/--repo`（自动从 `git remote origin` 解析）。AI 场景统一加 `--format json`。

```bash
# 0) 前置：确认版本能力 + 登录
gitlink-cli auth status
gitlink-cli issue +comments --help        # 存在 → 版本满足
gitlink-cli label +list --owner <o> --repo <r> --format json   # 看是否有「重复」标签及 ID

# 1) 拉取全部开放 Issue（含 number/subject/comment_journals_count/tags/created_at）
gitlink-cli issue +list --owner <o> --repo <r> --state open --limit 100 --format json

# 2) （可选）按主题预筛，缩小比对范围
gitlink-cli issue +list --owner <o> --repo <r> --keyword "登录" --state open --format json

# 3) 对候选对取正文做相似度比对
gitlink-cli issue +view --owner <o> --repo <r> --number 11 --format json
gitlink-cli issue +view --owner <o> --repo <r> --number 12 --format json

# 4) 幂等检查：读重复项已有评论，看是否已有关联标记
gitlink-cli issue +comments --owner <o> --repo <r> --number 12 --format json

# 5) 报告输出后，用户确认 → 发布关联评论（写操作，务必先确认）
gitlink-cli issue +comment --owner <o> --repo <r> --number 12 \
  --body "🔁 疑似重复 Issue：本 Issue 与 #11 高度相似（正文逐字一致，相似度 0.98）。主 Issue → https://www.gitlink.org.cn/<o>/<r>/issues/11"

# 6) ⚠️ 不推荐：打「重复」标签。实测 issue +update --label 返回 ok 但标签未生效，
#    不可靠。幂等请依赖步骤 4 的评论标记，而非标签。
# gitlink-cli issue +update --owner <o> --repo <r> --number 12 --label 298661

# 7) 可选：关闭重复项（破坏性，必须确认）
gitlink-cli issue +close --owner <o> --repo <r> --number 12 \
  --comment "关闭：与 #11 重复，后续讨论请移步主 Issue。"
```

## 输出：重复 Issue 收敛报告（Markdown）

````markdown
# 重复 Issue 收敛报告 — <owner>/<repo>

扫描范围：开放 Issue 共 N 条 | 生成时间：<时间> | 模式：dry-run（只读）

## 🔴 重复簇 1（高度疑似）
- **主 Issue**：#11 `[test] batch-close 测试 issue 1`（作者 wbtiger，2026-05-13，0 评论）
- **重复项**：
  - #12 `[test] batch-close 测试 issue 2` — 相似度 **0.98**（正文逐字相同、同作者同时间）
- **建议动作**：在 #12 发关联评论 → （确认后）关闭 #12

## 🟡 相关但非重复（不合并，仅提示）
- 主题「`+view`/`+update` 返回数据异常」：
  - #15 `issue +view 返回的数据与网页显示不一致`
  - #5  `认领/执行任务时描述信息消失`
  - #18 `+update 后状态框变红色`
  - #14 `pr +view 缺少 PR 关闭时间`
  → 症状各异（相似度 0.4–0.55），建议分别处理，不予合并。

## ✅ 待用户确认的写动作清单
| 重复项 | 动作 | 命令 |
|--------|------|------|
| #12 | 评论 + 关闭 | `issue +comment ...` → `issue +close --comment ...` |
````

> 真实数据跑通的完整报告见 [`examples/duplicate-detection-workflow.md`](examples/duplicate-detection-workflow.md)。

## 已知限制（实测，v0.2.0-dev / 本地源码版）

- **`issue +update --label` 不可靠**：实测在 `z2_cc/gitlink_help_center` 上 `--label <id>` 返回 `ok`，但 `+view`/`+list` 的 `tags` 均为空，标签未真正生效。**因此幂等一律以评论（`+comments` 的 `notes` 标记）为准，不要依赖标签。**
- **`+comments` 含系统动态**：返回的 `data.journals[]` 混有「创建了疑修 / 状态更改为关闭」等系统动态（`is_journal_detail=true`，`notes` 空）；判定文字评论须过滤 `is_journal_detail=false`。
- **`+list` 不含正文**：聚类前对每个候选 `+view` 取 `description`；大仓先用 `--keyword`/作者/时间窗粗筛降量。
- **`create`/`view` 不返回 Web 编号**：`issue +create` 与 `+view` 的响应里 `number` 可能为空；Web 编号以 `issue +list` 的 `number` 字段为准。

## 注意事项

- **默认 dry-run**：首次运行只产出报告，**不**评论/关闭/打标；所有写动作需用户逐条确认。
- **不碰他人仓库**：只在你有写权限、或用户明确指定的仓库执行写动作；在他人公开仓只做只读分析。
- **`--number` = Web 编号**（来自 `+list` 的 `number` 字段），勿用 `database_id`。
- **正文需 `+view` 补取**：`+list` 不含 `description`，聚类前对候选对逐个 `+view`；大仓库注意调用次数，先用 `--keyword`/标签/作者粗筛。
- **避免过度合并**：宁可漏报（列「相关项」）不可误并；维护者判断优先。
- **幂等**：写动作前用 `+comments` 查标记、用 `tags` 查标签，已处理则跳过。
- **不分配类别标签**：与 triage 划清边界；本 Skill 至多使用「重复」这一功能性标记标签。
