| name | find-dupes |
| description | Find potential duplicate Shortcut stories, present pairs for review, execute decisions (archive, merge, link) |
| disable-model-invocation | true |
Find Dupes
Find potential duplicate stories in Shortcut, present pairs for review, and
execute decisions: archive the loser, merge content, add story links.
This is primarily a hygiene play, not a recurring one. Play 1 (/sync-ideas)
catches duplicates at intake via semantic matching against the existing backlog.
This skill is for periodic sweeps or after bulk imports that bypassed Play 1.
Also Read
Before starting, read these shared sections from box/shortcut-ops.md:
- Search Operators: Shortcut search syntax
- Workspace Constants: Shortcut IDs, workflow states
- General Constraints: mutation cap, rate limiting
Constraints
- Mutation gate: A PreToolUse hook blocks all Shortcut mutations through
Bash. Route through
agenterminal.execute_approved or present the command
for the user to run.
- Human-in-the-loop: Present one pair at a time with full content for
comparison. Wait for Paul's decision. Don't batch-execute without review.
- Before Shortcut API calls: check
reference/tooling-logistics.md for
tested recipes. Story link creation uses /api/v3/story-links, NOT a
nested endpoint under stories.
Steps
1. Load candidates
Pull active stories by state. Don't manually curl Shortcut for the card list:
python3 box/shortcut-cards.py --query '!is:archived !state:Released state:"In Definition"' --summary
Repeat for other active states (Need Requirements, Backlog, Build,
Test). Exclude archived and Released stories from analysis.
2. Identify duplicate pairs
Lead with semantic judgment. Read titles and descriptions. Use your
judgment to identify pairs that describe the same need from different angles.
The first run (2026-02-11) showed this catches nuances that keyword similarity
misses — e.g., SC-28 and SC-140 being the same feature from different angles.
Keyword/Jaccard similarity (>=35% shared terms) can pre-filter large candidate
sets but isn't the primary detection method.
3. Present and decide
Present one pair at a time with full content for comparison. Possible decisions:
- Keep one (archive the other)
- Combine into one (merge content)
- Keep both (distinct enough)
- Keep both + "relates to" link (related but not duplicates)
4. Execute decisions
For each approved decision, use the wrapper scripts via execute_approved.
Scripts include idempotency checks and read-back verification. Exit 0/1/2.
- Archive:
python3 box/shortcut-mutate.py archive STORY_ID. Carry over
the archived story's Slack permalink to the surviving story (preserves
provenance).
- Story links:
python3 box/shortcut-mutate.py story-link SUBJECT_ID OBJECT_ID "duplicates".
Use verb duplicates (loser to winner) or relates to.
- Product Area: May need reassignment during resolution. The surviving
card should reflect the real product area, not just the surface where the
idea was filed.
- Content merge: When combining, merge the loser's content into the
winner under a
## Merged from SC-{id} heading.
Idempotency
Before each mutation, check:
- Story link already exists before creating
archived status before archiving
## Merged from SC-{id} heading already present before merging content