| name | pm-dedup |
| description | 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`. |
| version | 0.44.4 |
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
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):
linear issue view <ID> --json
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:
- 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.
- 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:
linear issue comment create <canonical-id> --body "From <member-id>:\n\n<member-body-text>"
gh issue comment <canonical-num> --repo <org/repo> --body "From #<member-num>:
<member-body-text>"
node "$CLAUDE_PLUGIN_ROOT/hooks/bin/pm-issue.js" relate --slug <slug> \
--from <member-id> --to <canonical-id> --type duplicate
linear issue update <member-id> --status Done
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.