| name | issue-dup-find |
| codex-short-description | Scan open Jira issues for likely duplicates; markdown report with probability + reason |
| description | Scan all open issues in a Jira project and identify likely duplicates using semantic comparison. Produces a markdown report listing each suspected duplicate pair with a probability score and reasoning. Read-only — never modifies, links, or transitions tickets. Default project key is read from memory (`reference_jira_default_project.md`); accepts an override project key. Use when user says "find duplicates", "duplicate check", "dedupe issues", "find duplicate tickets", or invokes /issue-dup-find with or without a project key.
|
| allowed-tools | ["Read","Write","Bash","AskUserQuestion","mcp__atlassian__jira_search","mcp__atlassian__jira_get_issue"] |
| related-skills | ["project-manager"] |
| loop-eligible | false |
| compatibility | claude-code codex opencode |
Issue Duplicate Finder
Read-only scan of open issues in a Jira project to surface likely duplicates. Output is a markdown report — no tickets are linked, transitioned, commented, or modified.
Input
- Project key — resolved from memory (
reference_jira_default_project.md → **Default Jira project key:**). If that memory entry is missing, prompt via AskUserQuestion for the key and save the answer back to that file before proceeding. Accept a per-invocation override if the user provides one (/issue-dup-find ABC or "find duplicates in PROJ") — the override always wins.
- Output destination — chat by default. If the user specifies a file path (e.g.
C:\temp\dups.md), also write the report there.
If the user message is ambiguous, confirm the project key and output destination once before proceeding.
Workflow
Duplicate scan progress:
- [ ] Phase 1: Fetch open issues (paginated)
- [ ] Phase 2: Cluster candidate pairs
- [ ] Phase 3: Semantic comparison
- [ ] Phase 4: Render report
- [ ] Phase 5: Optional file write
- [ ] Phase 6: Cleanup scratch folder
Phase 1 — Fetch open issues
JQL: project = {KEY} AND statusCategory != Done ORDER BY created DESC
Paginate jira_search until exhausted. Use max_results: 100 per call and increment until the API returns fewer than max_results (or zero). Do not impose an artificial ceiling.
For each issue capture: key, summary, description (first 1500 chars), issuetype, status, components, labels, priority, created, reporter, and any existing duplicates / is duplicated by issuelinks.
Create a per-run scratch folder at C:\temp\issue-dup-find-{PROJECT}-{YYYYMMDD-HHMM}\ and use it for all intermediate state. Tell the user the path once. Persist the harvested table to issues.json inside that folder as you fetch — protects the work if a later phase fails so you can resume without re-fetching.
If issues already have an existing is duplicated by / duplicates link, note them and exclude those pairs from the candidate set (they're already known).
Phase 2 — Cluster candidate pairs
Avoid O(n²) blowup. Group issues that share any of:
- the same component
- an overlapping label
- ≥3 shared significant tokens in the summary (ignore stopwords, common verbs, and the project key)
Form candidate pairs only within each group. De-duplicate pairs across groups. Surface the final pair count to the user as a progress checkpoint, then proceed — no budget cap.
Persist the candidate-pair list to pairs.json in the scratch folder so Phase 3 can resume mid-stream if interrupted.
Phase 3 — Semantic comparison
Process every candidate pair — no sampling. For very large pair sets, work in batches of ~25 and append scored results to scores.jsonl in the scratch folder after each batch. This keeps progress durable.
For each candidate pair, judge semantically using the issue summaries, descriptions, components, and labels. Assign one of:
- High (80–100%) — clearly the same defect / feature request, even if worded differently. Same observable symptom, same affected area, or one is plainly a re-report of the other.
- Medium (50–79%) — overlapping symptom or scope, but meaningful differences in cause, environment, or scope make it possible they're distinct.
- Low (25–49%) — superficial similarity (shared keywords or component) but the underlying issues look different.
- Below 25% — drop from the report.
For each kept pair, write one or two sentences of reasoning that cites specific evidence (shared error string, identical reproduction steps, same component + same symptom, etc.). Do not pad. Do not invent evidence not present in the tickets.
When uncertain between two bands, pick the lower. False positives waste reviewer time.
Phase 4 — Render report
Render in this exact format:
# Duplicate Issue Scan — {PROJECT_KEY}
**Scanned:** {N} open issues · **Candidate pairs evaluated:** {M} · **Reported:** {K}
**Generated:** {YYYY-MM-DD HH:MM} · **Read-only — no tickets modified**
## Suspected duplicates
### {ISSUE-KEY-A} — {short summary}
| Possible duplicate | Probability | Reason |
| --- | --- | --- |
| [{KEY-B}]({jira-base}/browse/{KEY-B}) — {short summary} | High (92%) | {1–2 sentence reason citing concrete evidence} |
| [{KEY-C}]({jira-base}/browse/{KEY-C}) — {short summary} | Medium (64%) | {reason} |
### {ISSUE-KEY-D} — {short summary}
| Possible duplicate | Probability | Reason |
| --- | --- | --- |
| [{KEY-E}]({jira-base}/browse/{KEY-E}) — {short summary} | High (87%) | {reason} |
---
## Notes
- Issues with existing `duplicates` / `is duplicated by` links were excluded.
- Pairs scoring below 25% were dropped.
- This is a read-only report. To act on a pair, link the tickets manually in Jira or run the appropriate update skill.
Group by the anchoring issue (lower issue number wins as anchor). Each pair appears once. Sort anchors by highest probability score within their group, descending.
If no duplicates clear the 25% threshold, render the header + a single line: _No suspected duplicates found above the 25% threshold._
Phase 5 — Optional file write
If the user supplied a file path in their original request, write the rendered markdown there with Write. Confirm with the absolute path written.
If they didn't specify, ask once via AskUserQuestion whether to save (offer "No — chat only" as the default, "Yes — save to {default path}", "Yes — custom path"). Do not nag; if they decline, move on.
Phase 6 — Cleanup
After the report is delivered (and any user-requested file write is confirmed complete), remove the scratch folder C:\temp\issue-dup-find-{PROJECT}-{YYYYMMDD-HHMM}\ and everything in it via Bash: rm -rf. Confirm cleanup with one line: "Scratch folder removed."
Skip cleanup if the run aborted with an error — leave the scratch folder intact so the user can inspect partial state or resume. Surface the path explicitly when an error occurs: "Partial state retained at {path} for inspection."
Constraints
- Read-only. No
jira_update_issue, jira_link_issues, jira_transition_issue, jira_add_comment, or any write tool. The only filesystem write is the optional report file in Phase 5.
- Do not infer duplicates from issue keys alone (e.g. consecutive numbers). Use ticket content.
- Do not include closed/resolved issues in the candidate set.
- Do not invent reasons. If the only signal is "same component", say so plainly and rate it Low.
- Cap reported pairs per anchor at 5. If more candidates score High, list the top 5 and add a footnote:
_+N additional medium/low matches suppressed._
- The default project key is read from memory (
reference_jira_default_project.md). Always confirm the resolved project key in the report header so the user can verify scope.
Quality Loop
Before returning the artifact, evaluate it and refine if it falls short.
- Generate the artifact via the workflow above.
- Self-evaluate against these criteria:
- Each suspected pair has a probability score and a one-line reason
- Pairs are ordered by descending probability
- Read-only respected — no tickets linked, transitioned, or edited
- Obvious non-duplicates are not padded into the report
- Loop — if two or more criteria fail, revise and re-check.
- Exit when all criteria pass, or after two refinement passes (then note which criteria still fall short).
Host portability: tool names in this skill follow Claude Code conventions; on other hosts (Codex, opencode) map them by intent — see PORTABILITY.md.
Self-Evolve Loop
This skill learns across invocations — the full contract is
SELF-EVOLVE.md. Start: read the learnings
journal — ~/.ink-and-agency/learnings/issue-dup-find.md and/or the workspace-local
.ink-and-agency/learnings/issue-dup-find.md — if present, and apply its guidance.
End: self-evaluate the results; optionally ask the user for feedback (never
block on it); append signal-bearing learnings to the journal (user-global when
the sandbox allows writing there, workspace-local otherwise); route
skill-improvement ideas per the contract's tiers — edit the canonical source
when one is present, never the plugin cache.