Claude
Skills
Sign in
Back

pm-dedup

Included with Lifetime
$97 forever

Use this skill when the user asks to "find duplicate issues", "clean up duplicates", "what dupes are in the backlog", "/pm-dedup", or after a /pm-status briefing flags likely-dup clusters. Runs the `duplicates` lens against the team cache, has the agent judge each cluster, then offers merge / relate / close per cluster. Powered by the shared dedup engine in `hooks/lib/dedup.js`.

AI Agents

What this skill does


# PM Dedup

Surface likely-duplicate issue clusters in the tracker and help the operator clean them up. The skill is a thin orchestrator over the `duplicates` lens — the lens does the lexical heavy lifting; the agent does the per-cluster judgment; the operator chooses the action.

## When to run this

- The user asks "what duplicates are in the backlog" or runs `/pm-dedup`.
- A `/pm-status` triage briefing surfaced the **Likely duplicates** bucket and the operator wants to act on it.
- After a long backlog has built up and the operator wants a quick cleanup pass.

**Not when:** the goal is "is THIS draft a duplicate" before filing a new issue — that's the creation-guard path inside `pm-issues` (Phase 2), not this skill.

## Workflow

### Step 1: Run the duplicates lens

```bash
node "$CLAUDE_PLUGIN_ROOT/hooks/bin/pm-cache.js" lens --slug <slug> --lens duplicates
```

Returns a JSON array of clusters. Each cluster: `{ members: [id...], pairs: [{a,b,score,matchedTerms}] }`. The lens already excludes pairs where either member carries the `duplicate` label — resolved stays resolved across runs.

If the output is `[]`, tell the operator: *"No likely-duplicate clusters in the current corpus."* Stop.

### Step 2: Fetch the body of each cluster member

For each cluster, fetch the issue body so you can judge intelligently (the lens only carries id, score, and matched terms — not full text):

```bash
# Linear
linear issue view <ID> --json

# GitHub
gh issue view <NUM> --repo <org/repo> --json title,body
```

### Step 3: Judge each cluster

For each cluster, read the members' titles + bodies + matched terms and decide:

1. **True duplicates?** Identify which members are actually the same work item. Drop false positives the lexical pre-filter let through. The lexical stage owns *recall*; you own *precision*.
2. **Pick the canonical issue** — typically the oldest, most-detailed, or most-progressed one. State your reasoning to the operator. They can override.

If after judgment a cluster collapses to 1 member, drop it.

### Step 4: Present each cluster + recommended action

For each surviving cluster, print:

```
Cluster: NTH-12, NTH-47, NTH-58
  Matched on: token, refresh, safari
  Canonical: NTH-12 (oldest, most detail in body)
  Suggested action: close NTH-47 and NTH-58 as duplicates of NTH-12

  [m]erge — copy NTH-47 + NTH-58 body content as comments on NTH-12, then close them
  [r]elate — link NTH-47 and NTH-58 as duplicate-of NTH-12, leave all open
  [c]lose — close NTH-47 and NTH-58 as duplicate of NTH-12 (no body copy)
  [s]kip — leave this cluster alone
```

### Step 5: Apply the chosen action

- **merge** — for each non-canonical member, run the three commands in sequence:

  ```bash
  # 1. Copy the member's body into the canonical as a comment
  #    (Linear)
  linear issue comment create <canonical-id> --body "From <member-id>:\n\n<member-body-text>"
  #    (GitHub)
  gh issue comment <canonical-num> --repo <org/repo> --body "From #<member-num>:

  <member-body-text>"

  # 2. Mark the duplicate relationship
  node "$CLAUDE_PLUGIN_ROOT/hooks/bin/pm-issue.js" relate --slug <slug> \
    --from <member-id> --to <canonical-id> --type duplicate

  # 3. Close the member (state name varies per workspace — "Duplicate" / "Done" / "Cancelled")
  linear issue update <member-id> --status Done
  #    (GitHub)
  gh issue close <member-num> --repo <org/repo>
  ```
- **relate** — `pm-issue.js relate --slug <slug> --from <member> --to <canonical> --type duplicate` for each non-canonical; leave statuses alone.
- **close** — `pm-issue.js relate --slug <slug> --from <member> --to <canonical> --type duplicate` + close the member.
- **skip** — record nothing.

`relate` is the single new write verb (Phase 1). All other steps compose existing comment/close paths in `pm-issues`.

### Step 6: Summarize

After processing all clusters, print a one-line per cluster summary: what was done, what was skipped. The operator can re-run `/pm-dedup` later; the resolved-pair exclusion ensures already-handled clusters do not re-surface.

## What this skill does NOT do

- **Does not check a draft issue against the corpus** — that's the creation-guard inside `pm-issues` (Phase 2). This skill only sweeps existing issues.
- **Does not change cluster membership computation** — the `duplicates` lens owns that; this skill consumes the output.
- **Does not auto-act without operator confirmation** — every action is offered, not applied.

## Pipelining with /pm-triage

`/pm-triage` surfaces the same lens output as a 4th bucket ("Likely duplicates") and defers the action vocabulary to this skill. If the operator is already inside `/pm-triage` and reaches the duplicates bucket, they can invoke `/pm-dedup` to act on the same clusters.

Related in AI Agents