بنقرة واحدة
detect-duplicates
// Scans all feature requests for a WooCommerce Marketplace product and surfaces duplicates. Use when asked to find or clean up duplicate feature requests on WooCommerce.com.
// Scans all feature requests for a WooCommerce Marketplace product and surfaces duplicates. Use when asked to find or clean up duplicate feature requests on WooCommerce.com.
| name | detect-duplicates |
| description | Scans all feature requests for a WooCommerce Marketplace product and surfaces duplicates. Use when asked to find or clean up duplicate feature requests on WooCommerce.com. |
You are helping a WooCommerce Marketplace team member identify and act on duplicate feature requests for a specific product.
Read first:
.claude/skills/shared/RULES.md.claude/skills/shared/DISPLAY.md.claude/skills/shared/PHASE_LOOP.mdFollow .claude/skills/shared/RESOLVE_PRODUCT.md.
Skip this step if invoked by the orchestrator. The orchestrator passes the FRs in
input_path(a JSONL file already on disk). Use it directly as the input to the detection script in Step 3 and skip writing/tmp/frs-<id>.jsonl.
Call wccom-feature-requests-list with per_page=100 and
status: "publish". Paginate. Collect id, title, description,
status, votes, date, url.
If the first page returns 0 results, stop and report: "No open feature requests found for this product."
Skip this step if invoked by the orchestrator. The orchestrator passes
comparison_mode(fastorin-depth).
Ask:
How thorough should the duplicate scan be?
[F] Fast — title-based matching only (script + false-positive pruning).
Good for quick cleanup. Misses duplicates with different titles
but similar descriptions.
[D] In-depth — title + full description comparison. Claude reads every
description to catch hidden duplicates.
More thorough, takes longer.
Reply F or D.
Store as comparison_mode (fast or in-depth). Default to fast.
Skip this step if invoked by the orchestrator. Use the orchestrator's
input_pathdirectly.
Use Write to create $TMPDIR/frs-<product_id>.jsonl. One JSON object per
line: id, title, description, votes, date, url.
(Orchestrator subagent: begin reading from this step. Use <input_path>
wherever the script command shows $TMPDIR/frs-<product_id>.jsonl.)
Choose the command based on comparison_mode:
python3 scripts/detect_duplicates.py $TMPDIR/frs-<product_id>.jsonl --json.venv/bin/python3 scripts/detect_duplicates.py $TMPDIR/frs-<product_id>.jsonl --semantic --rerank --jsonThe --semantic --rerank flags require sentence-transformers in .venv/.
If import error, fall back to fast and note it.
Parse the JSON output:
{
"groups": [
{
"confidence": "high" | "low",
"max_similarity": 0.75,
"primary": {"id": 123, "title": "...", "votes": 5, "date": "...", "url": "..."},
"duplicates": [{"id": 456, "title": "...", "votes": 2, "date": "...", "url": "..."}]
}
],
"borderline_pairs": [
{"similarity": 0.58, "fr_a": {...}, "fr_b": {...}}
]
}
Normalise script confidence (high/low lowercase) to High/Low per
RULES.md. Store groups as script groups and borderline_pairs for
Step 3b-ii. If the script fails entirely, fall back to Step 3b and note it.
For each group, read full titles and descriptions of every member. If a pair is clearly NOT the same ask (same keyword but different intent), remove the false-positive member. If a group reduces to one member, discard it.
Freed FRs go to a re-evaluation pool — they may be true duplicates of
a different FR that the script missed.
Do not discard a group for being support/spam/off-topic. Duplicates are duplicates regardless of quality. Surface them here.
Upgrade or downgrade confidence:
Skip entirely if comparison_mode = fast. Proceed to 3b-iii.
If in-depth:
Review two inputs:
borderline_pairs from the script — pairs near the threshold.re-evaluation pool from 3b-i.For each borderline pair, read both title and description in full.
Decide: same core ask? If yes, add as a new group. Be conservative.
For pool FRs not covered by any pair, pay attention to vague short titles ("Booking", "Hi :)") — read full descriptions before comparing.
Assign confidence (High / Low). Primary = most votes, then oldest if tied. Add as new groups.
Combine script groups (after pruning) with new semantic groups. Sort: High first, then by primary vote count descending.
Pagination — grouped phase, see DISPLAY.md. Decode HTML entities.
Translate non-English excerpts per RULES.md.
For each group:
## Group [N]: [short description of the shared ask]
Confidence: High / Low
Reason: [one sentence — what makes these the same ask]
✅ Keep: ID [id] — "[title]" ([votes] votes · [status] · [date])
[url]
"[description — first 2–3 sentences, trimmed if long]"
❌ Close: ID [id] — "[title]" ([votes] votes · [status] · [date])
[url]
"[description — first 2–3 sentences, trimmed if long]"
End with: X requests scanned · Y duplicate groups · Z requests could be closed.
One record per duplicate-to-close:
{ "id": <int>, "title": "<str>", "url": "<str>",
"reason": "<one sentence>", "confidence": "High" | "Low",
"excerpt": "<first 1–3 sentences>",
"votes": <int>, "date": "<YYYY-MM-DD>",
"group_id": <int>,
"primary": { "id": <int>, "title": "<str>", "url": "<str>",
"status": "<str>", "votes": <int>, "date": "<YYYY-MM-DD>",
"excerpt": "<first 1–3 sentences>" } }
List every closeable duplicate grouped by group number. Within each group, sort High → Low. Assign sequential numbers across all groups (stable for pagination).
Which requests should I close?
Group 1: [short group label]
[1] ID [id] — "[title]" [High]
[2] ID [id] — "[title]" [High]
[3] ID [id] — "[title]" [Low]
Group 2: [short group label]
[4] ID [id] — "[title]" [High]
...
[A] All of the above
[H] High confidence only ← omit if no High items (see RULES.md)
[N] None / skip
Reply with numbers (e.g. "1 3"), A for all, H for High only, or N to skip.
Wait for the user's reply. Then group approved requests by their comment template (same primary + same primary status = same template). For each distinct template, show the preview once with the list of FRs:
📝 Comment preview — applies to [N] request(s):
• ID [id] — "[title]"
• ID [id] — "[title]"
---
[full comment text, with [primary_url] filled in]
---
After all previews: "Post these comments and close the requests? (Y / N)" If N, abort.
Then for each approved duplicate, look up its group's primary and process in this order:
Post closing comment via wccom-feature-requests-comment. Template
depends on primary status:
Primary open with more votes:
Hi there,
Thank you for your suggestion!
We noticed that there is a similar request about this, which currently has more votes: [primary_url]. To better prioritize requests, we aim to keep 1 request open per feature. That's why I will close this one. I encourage you to upvote the above request instead.
Primary open with equal votes (kept because older):
Hi there,
Thank you for your suggestion!
We noticed that there is a similar request about this that has been open longer: [primary_url]. To better prioritize requests, we aim to keep 1 request open per feature. That's why I will close this one. I encourage you to upvote the above request instead.
Primary planned or in-progress:
Hi there,
Thank you for your suggestion!
We noticed that this feature is already planned and being tracked here: [primary_url]. That's why I will close this duplicate. You can follow progress on that request instead.
Primary completed:
Hi there,
Thank you for your suggestion!
Great news — this feature has already been implemented! You can find more details here: [primary_url]. That's why I will close this request.
Update status via wccom-feature-requests-update-status → "closed".
Write failure handling — on failure, do not record as actioned; report in summary; continue. If comment posted but status update failed, note the half-completed state.
After closing approved duplicates, for each group with at least one closure, compare full descriptions of every closed request against the primary's. Look for:
If unique insights are found, draft a comment for the primary. Plain
text only (see RULES.md). Example:
Hi there,
We recently consolidated a few related requests into this one. In doing so,
we noticed some additional use cases and perspectives worth capturing here:
- [insight 1]
- [insight 2]
...
We've noted these for the team when reviewing this request.
Preview:
📝 Draft comment for primary — ID [primary_id] — "[primary_title]"
[primary_url]
---
[full draft comment]
---
Post this comment to the primary request? (Y / N)
If Y: call wccom-feature-requests-comment on the primary's ID. Do not
update the primary's status.
If no unique insights across any closed duplicate: "No additional insights found — primary requests are already comprehensive." Skip the step.
Done.
❌ Closed: ID [id] — "[title]"
[closed_url]
✅ Primary: ID [id] — "[title]" ([status])
[primary_url]
⚠️ Failed: ID [id] — "[title]" — [error]
Then follow PHASE_LOOP.md. Additionally, the standalone re-scan prompt
accepts a group-number reference:
Need more context on a group? Type the group number (e.g. "Group 3").
If the user specifies a group number: show full title, description,
votes, date, url for every request in that group (primary + all
closeable duplicates), then re-ask the loop question.
Scans open feature requests for a WooCommerce Marketplace product against its changelog and flags requests whose feature has already shipped. Recommends marking them as completed with a reply pointing to the version. Use when asked to find or close fulfilled/shipped feature requests on WooCommerce.com.
Scans open feature requests for a WooCommerce Marketplace product and flags ones that look like spam (promotional links, gibberish, off-topic, copy-pasted SEO content). Lets the user select which to mark as spam, then silently sets the status to spam — no comment is posted. Use when asked to find or clean up spam feature requests on WooCommerce.com.
Scans open feature requests for a WooCommerce Marketplace product and flags stale ones (opened more than 3 years ago with 2 or fewer votes). Groups them semantically, proposes tailored closing comments per group (with workarounds/plugins where known), then closes approved ones. Use when asked to find or clean up stale/old/low-vote feature requests on WooCommerce.com.
Scans open feature requests for a WooCommerce Marketplace product and flags ones that are actually support questions (troubleshooting, setup help, bug reports framed as features). Lets the user select which to close, then posts a closing comment redirecting them to woocommerce.com/contact-us/. Use when asked to find or clean up support questions misfiled as feature requests.
Full triage orchestrator for a WooCommerce Marketplace product's feature requests. Fetches all requests once to disk, then delegates each phase (completed → duplicates → spam → support → stale) to a subagent to keep the main context light. State is persisted under `.triage-state/<product_id>/` so the run is resumable per-FR. Ends with an optional free-form analysis pass. Use when asked to run a full triage or cleanup on a product's feature requests.