
Finding Duplicate Functions
- 553 installs
- 408 repo stars
- Updated June 1, 2026
- obra/superpowers-lab
finding-duplicate-functions is an agent skill that detects semantically duplicate functions in LLM-generated codebases using classical extraction plus LLM intent clustering beyond jscpd copy-paste detection.
About
finding-duplicate-functions is an obra superpowers-lab skill for auditing codebases where LLM agents created parallel implementations of the same intent under different names. Classical copy-paste detectors like jscpd catch syntactic duplicates but miss same-purpose, different-implementation functions common in agent-generated repos. The skill uses a two-phase pipeline: classical function extraction followed by LLM-powered intent clustering to surface consolidation candidates. Developers reach for finding-duplicate-functions after heavy AI-assisted coding sessions, before refactors, or when test suites balloon with overlapping helpers. Outputs guide deduplication PRs that shrink maintenance surface without breaking behaviorally distinct code paths.
- Two-phase approach: classical function extraction followed by LLM-powered intent clustering
- Finds semantic duplicates where functions do the same thing but have different names or implementations
- Especially effective on LLM-generated codebases that tend to create new functions instead of reusing existing ones
- 5-step automated workflow from extraction through final markdown report
- Runs after jscpd to catch duplicates that syntactic tools cannot detect
Finding Duplicate Functions by the numbers
- 553 all-time installs (skills.sh)
- +13 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #228 of 1,352 Code Review & Quality skills by installs in the Skillselion catalog
- Security screen: MEDIUM risk (skills.sh audit)
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/obra/superpowers-lab --skill finding-duplicate-functionsAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 553 |
|---|---|
| repo stars | ★ 408 |
| Security audit | 3 / 3 scanners passed |
| Last updated | June 1, 2026 |
| Repository | obra/superpowers-lab ↗ |
How do you find duplicate-intent functions in AI code?
Detect semantically duplicate functions in LLM-generated codebases that classical tools miss.
Who is it for?
Developers reviewing LLM-generated or rapidly iterated codebases accumulating overlapping utility functions.
Skip if: Greenfield projects with minimal code or teams needing only literal copy-paste detection via jscpd alone.
When should I use this skill?
The user audits an AI-generated codebase for duplicate helpers, overlapping utilities, or semantic duplication jscpd missed.
What you get
Intent-clustered duplicate function groups with consolidation recommendations for refactoring PRs.
- intent-clustered duplicate function report
- consolidation recommendations
By the numbers
- Uses a 2-phase pipeline: classical extraction plus LLM intent clustering
Files
Finding Duplicate-Intent Functions
Overview
LLM-generated codebases accumulate semantic duplicates: functions that serve the same purpose but were implemented independently. Classical copy-paste detectors (jscpd) find syntactic duplicates but miss "same intent, different implementation."
This skill uses a two-phase approach: classical extraction followed by LLM-powered intent clustering.
When to Use
- Codebase has grown organically with multiple contributors (human or LLM)
- You suspect utility functions have been reimplemented multiple times
- Before major refactoring to identify consolidation opportunities
- After jscpd has been run and syntactic duplicates are already handled
Quick Reference
| Phase | Tool | Model | Output |
|---|---|---|---|
| 1. Extract | scripts/extract-functions.sh | - | catalog.json |
| 2. Categorize | scripts/categorize-prompt.md | haiku | categorized.json |
| 3. Split | scripts/prepare-category-analysis.sh | - | categories/*.json |
| 4. Detect | scripts/find-duplicates-prompt.md | opus | duplicates/*.json |
| 5. Report | scripts/generate-report.sh | - | report.md |
Process
digraph duplicate_detection {
rankdir=TB;
node [shape=box];
extract [label="1. Extract function catalog\n./scripts/extract-functions.sh"];
categorize [label="2. Categorize by domain\n(haiku subagent)"];
split [label="3. Split into categories\n./scripts/prepare-category-analysis.sh"];
detect [label="4. Find duplicates per category\n(opus subagent per category)"];
report [label="5. Generate report\n./scripts/generate-report.sh"];
review [label="6. Human review & consolidate"];
extract -> categorize -> split -> detect -> report -> review;
}Phase 1: Extract Function Catalog
./scripts/extract-functions.sh src/ -o catalog.jsonOptions:
-o FILE: Output file (default: stdout)-c N: Lines of context to capture (default: 15)-t GLOB: File types (default:*.ts,*.tsx,*.js,*.jsx)--include-tests: Include test files (excluded by default)
Test files (*.test.*, *.spec.*, __tests__/**) are excluded by default since test utilities are less likely to be consolidation candidates.
Phase 2: Categorize by Domain
Dispatch a haiku subagent using the prompt in scripts/categorize-prompt.md.
Insert the contents of catalog.json where indicated in the prompt template. Save output as categorized.json.
Phase 3: Split into Categories
./scripts/prepare-category-analysis.sh categorized.json ./categoriesCreates one JSON file per category. Only categories with 3+ functions are worth analyzing.
Phase 4: Find Duplicates (Per Category)
For each category file in ./categories/, dispatch an opus subagent using the prompt in scripts/find-duplicates-prompt.md.
Save each output as ./duplicates/{category}.json.
Phase 5: Generate Report
./scripts/generate-report.sh ./duplicates ./duplicates-report.mdProduces a prioritized markdown report grouped by confidence level.
Phase 6: Human Review
Review the report. For HIGH confidence duplicates: 1. Verify the recommended survivor has tests 2. Update callers to use the survivor 3. Delete the duplicates 4. Run tests
High-Risk Duplicate Zones
Focus extraction on these areas first - they accumulate duplicates fastest:
| Zone | Common Duplicates |
|---|---|
utils/, helpers/, lib/ | General utilities reimplemented |
| Validation code | Same checks written multiple ways |
| Error formatting | Error-to-string conversions |
| Path manipulation | Joining, resolving, normalizing paths |
| String formatting | Case conversion, truncation, escaping |
| Date formatting | Same formats implemented repeatedly |
| API response shaping | Similar transformations for different endpoints |
Common Mistakes
Extracting too much: Focus on exported functions and public methods. Internal helpers are less likely to be duplicated across files.
Skipping the categorization step: Going straight to duplicate detection on the full catalog produces noise. Categories focus the comparison.
Using haiku for duplicate detection: Haiku is cost-effective for categorization but misses subtle semantic duplicates. Use Opus for the actual duplicate analysis.
Consolidating without tests: Before deleting duplicates, ensure the survivor has tests covering all use cases of the deleted functions.
Function Categorization Prompt
Use this prompt with a haiku subagent for cost-effective categorization.
Prompt Template
Read the function catalog at <CATALOG_PATH> and categorize each function.
Assign each function to exactly ONE category based on its primary purpose.
## Categories
- **file-ops**: Reading, writing, path manipulation, directory operations
- **string-utils**: Formatting, parsing, sanitization, case conversion, truncation
- **validation**: Input checking, schema validation, type guards, assertions
- **error-handling**: Error creation, wrapping, formatting, logging helpers
- **http-api**: Request building, response parsing, URL construction, headers
- **date-time**: Date formatting, parsing, comparison, timezone handling
- **data-transform**: Mapping, filtering, normalization, serialization
- **database**: Query building, connection management, migrations
- **logging**: Log formatting, debug helpers, telemetry
- **config**: Configuration loading, environment variables, settings
- **async-utils**: Promise helpers, retry logic, debounce, throttle
- **testing**: Test utilities, mocks, fixtures, assertions
- **ui-helpers**: DOM manipulation, event handling, component utilities
- **crypto**: Hashing, encryption, token generation
- **provider-impl**: AI provider interface implementations (createResponse, etc.)
- **tool-impl**: Tool interface implementations (executeValidated, etc.)
- **event-handling**: Event creation, emission, processing, subscription
- **session-management**: Session/thread/conversation lifecycle
- **compaction**: Message compaction, summarization, token management
- **other**: Doesn't fit above categories (note subcategory in purpose)
## Output Format
For each function, output:
{"file": "...", "name": "...", "line": N, "category": "...", "purpose": "one sentence"}
## Guidelines
1. Focus on WHAT the function does, not HOW it's implemented
2. If a function could fit multiple categories, choose the primary purpose
3. Constructors: categorize based on what the class does
4. Interface implementations: use provider-impl or tool-impl as appropriate
5. Keep purpose descriptions concise but specific
## IMPORTANT
Use the Write tool to save the complete JSON array to <OUTPUT_PATH>.
Do NOT truncate or summarize - write ALL entries.Usage
1. Run extraction: ./extract-functions.sh src/ -o catalog.json 2. Dispatch haiku subagent with the prompt above, replacing:
<CATALOG_PATH>with path to catalog.json<OUTPUT_PATH>with desired output path (e.g.,categorized.json)
3. Verify output file was created with all entries
Critical: The subagent must use the Write tool to save output. If it only returns a summary, re-prompt with explicit file write instructions.
#!/usr/bin/env bash
# ABOUTME: Extracts function/method definitions from TypeScript/JavaScript codebase
# Outputs JSON catalog for duplicate detection analysis
set -euo pipefail
usage() {
cat <<EOF
Usage: $(basename "$0") [OPTIONS] <source-directory>
Extract function catalog from TypeScript/JavaScript codebase.
OPTIONS:
-o, --output FILE Output file (default: stdout)
-c, --context N Lines of implementation to capture (default: 15)
-t, --types GLOB File types to scan (default: "*.ts,*.tsx,*.js,*.jsx")
--include-tests Include test files (excluded by default)
-h, --help Show this help
Test files excluded by default:
*.test.*, *.spec.*, __tests__/**, test/**, tests/**
EXAMPLES:
$(basename "$0") src/
$(basename "$0") -o catalog.json -c 3 packages/
$(basename "$0") --types "*.ts" src/
$(basename "$0") --include-tests src/ # Include test files
OUTPUT FORMAT:
JSON array of objects with: file, name, signature, context, exportType
EOF
exit 0
}
# Defaults
OUTPUT="/dev/stdout"
CONTEXT_LINES=15
FILE_TYPES="*.ts,*.tsx,*.js,*.jsx"
INCLUDE_TESTS=false
# Parse args
while [[ $# -gt 0 ]]; do
case $1 in
-o|--output) OUTPUT="$2"; shift 2 ;;
-c|--context) CONTEXT_LINES="$2"; shift 2 ;;
-t|--types) FILE_TYPES="$2"; shift 2 ;;
--include-tests) INCLUDE_TESTS=true; shift ;;
-h|--help) usage ;;
-*) echo "Unknown option: $1" >&2; exit 1 ;;
*) SRC_DIR="$1"; shift ;;
esac
done
if [[ -z "${SRC_DIR:-}" ]]; then
echo "Error: source directory required" >&2
usage
fi
if [[ ! -d "$SRC_DIR" ]]; then
echo "Error: directory not found: $SRC_DIR" >&2
exit 1
fi
# Build glob pattern for ripgrep (use array to avoid glob expansion)
GLOB_ARGS=()
IFS=',' read -ra TYPES <<< "$FILE_TYPES"
for type in "${TYPES[@]}"; do
GLOB_ARGS+=(--glob "$type")
done
# Exclude test files by default
if [[ "$INCLUDE_TESTS" == "false" ]]; then
GLOB_ARGS+=(--glob '!*.test.*' --glob '!*.spec.*')
GLOB_ARGS+=(--glob '!**/__tests__/**' --glob '!**/test/**' --glob '!**/tests/**')
fi
# Patterns to match function definitions
# Pattern 1: export function name(
# Pattern 2: export const name = (async)? (
# Pattern 3: export const name = (async)? function
# Pattern 4: export default function
# Pattern 5: class methods (public/private/protected/async)
# Pattern 6: standalone function declarations
extract_functions() {
local dir="$1"
local ctx="$2"
# Use ripgrep to find function definitions with context
# Note: Class method pattern requires visibility/async/static to avoid matching if/for/while
rg --json \
-e '^export (async )?function \w+' \
-e '^export const \w+ = (async )?\(' \
-e '^export const \w+ = (async )?function' \
-e '^export default (async )?function' \
-e '^ (public |private |protected )(async |static )*(get |set )?\w+\s*\(' \
-e '^ (async |static )(async |static )*(get |set )?\w+\s*\(' \
-e '^ (get |set )\w+\s*\(' \
-e '^ constructor\s*\(' \
-e '^(async )?function \w+\s*\(' \
"${GLOB_ARGS[@]}" \
-A "$ctx" \
"$dir" 2>/dev/null || true
}
# Process ripgrep JSON output into our catalog format
process_output() {
jq -s '
# Group by match (each match has type "begin", "match", "context", "end")
reduce .[] as $item (
{current: null, results: []};
if $item.type == "begin" then
.current = {file: $item.data.path.text, lines: []}
elif $item.type == "match" then
.current.lines += [{
line_number: $item.data.line_number,
text: $item.data.lines.text,
is_match: true
}]
elif $item.type == "context" then
.current.lines += [{
line_number: $item.data.line_number,
text: $item.data.lines.text,
is_match: false
}]
elif $item.type == "end" then
if .current.lines | length > 0 then
.results += [.current]
else . end
else . end
) | .results
# Transform into catalog entries - group each match with its following context
| map(
.file as $file |
.lines |
# Find indices of match lines
to_entries |
reduce .[] as $entry (
{matches: [], current_match: null, entries: []};
if $entry.value.is_match then
# Save previous match group if exists
(if .current_match then
.entries += [{
file: $file,
line: .current_match.line_number,
match_line: .current_match.text,
context_lines: .context
}]
else . end) |
# Start new match group
.current_match = $entry.value |
.context = []
else
# Add to current context if we have a match
if .current_match then
.context += [$entry.value.text]
else . end
end
) |
# Dont forget last match group
(if .current_match then
.entries += [{
file: $file,
line: .current_match.line_number,
match_line: .current_match.text,
context_lines: .context
}]
else . end) |
.entries |
map(. + {context: ((.match_line // "") + ((.context_lines // []) | join("")))})
) | flatten
# Extract function name and classify export type
| map(
. + {
name: (
.match_line |
capture("(?:export )?(?:async )?(?:function |const )(?<name>\\w+)") //
capture("(?:public |private |protected )?(?:async |static )*(?:get |set )?(?<name>\\w+)\\s*\\(") //
{name: "unknown"}
).name,
exportType: (
if .match_line | test("^export default") then "default"
elif .match_line | test("^export ") then "named"
elif .match_line | test("^ ") then "method"
else "internal"
end
)
}
)
# Filter out keywords, invalid entries, and common loop variables
| map(select(
.name != "unknown" and
.name != "if" and
.name != "else" and
.name != "for" and
.name != "while" and
.name != "switch" and
.name != "try" and
.name != "catch" and
.name != "return" and
.name != "throw" and
.name != "new" and
.name != "typeof" and
.name != "await" and
.name != "const" and
.name != "let" and
.name != "var" and
# Common loop variables that get false-positive matched
.name != "line" and
.name != "item" and
.name != "entry" and
.name != "element" and
.name != "key" and
.name != "value" and
.name != "i" and
.name != "j" and
.name != "k"
))
# Clean up and format output
| map({
file: .file,
name: .name,
line: .line,
exportType: .exportType,
context: (.context | gsub("\\n+$"; ""))
})
| sort_by(.file, .line)
'
}
# Main
extract_functions "$SRC_DIR" "$CONTEXT_LINES" | process_output > "$OUTPUT"
# Report stats to stderr
if [[ "$OUTPUT" != "/dev/stdout" ]]; then
count=$(jq 'length' "$OUTPUT")
echo "Extracted $count function definitions to $OUTPUT" >&2
fi
Duplicate Detection Prompt
Use this prompt with an opus subagent for thorough semantic analysis.
Run this prompt once per category that has 3+ functions.
Prompt Template
You are analyzing functions in the "{CATEGORY}" category for semantic duplicates.
Semantic duplicates are functions that serve the SAME PURPOSE even if:
- They have different names
- They use different implementations
- They have slightly different signatures
- One is more general than another
## Your Task
1. Compare all functions in this category
2. Identify groups of functions that do the same thing
3. For each duplicate group, assess confidence and recommend action
## Output Format
Return a JSON array of duplicate groups:
[ { "intent": "<what these functions all do>", "confidence": "HIGH|MEDIUM|LOW", "functions": [ { "file": "<file path>", "name": "<function name>", "line": <line number>, "notes": "<implementation specifics>" } ], "differences": "<how implementations differ, if at all>", "recommendation": { "action": "CONSOLIDATE|INVESTIGATE|KEEP_SEPARATE", "survivor": "<which function to keep, if CONSOLIDATE>", "reason": "<why this recommendation>" } } ]
## Confidence Levels
- **HIGH**: Definitely the same thing. Same input→output semantics.
Example: `formatDate(d)` and `dateToString(d)` both format dates identically
- **MEDIUM**: Likely the same thing with minor differences.
Example: `validateEmail(s)` uses regex, `checkEmail(s)` uses library, but same purpose
- **LOW**: Possibly related, worth investigating.
Example: `sanitizeInput(s)` and `escapeHtml(s)` - related but maybe distinct purposes
## Recommendations
- **CONSOLIDATE**: Functions are duplicates. Keep the one with better name/implementation/tests.
- **INVESTIGATE**: Need to read full implementations to decide. Flag for human review.
- **KEEP_SEPARATE**: Functions look similar but serve distinct purposes.
## Guidelines
1. Read the context/implementation snippets carefully
2. Consider edge case handling - two functions might differ in how they handle nulls
3. If functions are in test files, they're less likely to be true duplicates
4. Generic utilities (identity, noop, constant) are often intentionally duplicated
5. When in doubt, recommend INVESTIGATE rather than CONSOLIDATE
## Functions in "{CATEGORY}" Category
<INSERT_CATEGORY_FUNCTIONS_HERE>Usage
1. First run categorization (see categorize-prompt.md) 2. Filter categorized.json to get functions for one category:
jq '[.[] | select(.category == "validation")]' categorized.json > validation-functions.json3. Replace {CATEGORY} with the category name 4. Replace <INSERT_CATEGORY_FUNCTIONS_HERE> with the filtered JSON 5. Dispatch opus subagent with the prompt 6. Repeat for each category with 3+ functions 7. Combine outputs into final report
#!/usr/bin/env bash
# ABOUTME: Generates human-readable duplicate detection report from Opus analysis output
# Combines per-category duplicate findings into a prioritized markdown report
set -euo pipefail
usage() {
cat <<EOF
Usage: $(basename "$0") <duplicates-dir> [output-file]
Generate markdown report from duplicate detection results.
ARGUMENTS:
duplicates-dir Directory containing per-category duplicate JSON files
output-file Output markdown file (default: duplicates-report.md)
INPUT FORMAT:
Each JSON file should contain array of duplicate groups from Opus analysis.
EXAMPLE:
$(basename "$0") ./duplicates ./duplicates-report.md
EOF
exit 0
}
if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
usage
fi
if [[ -z "${1:-}" ]]; then
echo "Error: duplicates directory required" >&2
usage
fi
DUPLICATES_DIR="$1"
OUTPUT="${2:-duplicates-report.md}"
if [[ ! -d "$DUPLICATES_DIR" ]]; then
echo "Error: directory not found: $DUPLICATES_DIR" >&2
exit 1
fi
# Generate the report
{
echo "# Duplicate Functions Report"
echo ""
echo "Generated: $(date '+%Y-%m-%d %H:%M')"
echo ""
# Count totals
high_count=0
medium_count=0
low_count=0
for f in "$DUPLICATES_DIR"/*.json; do
[[ -f "$f" ]] || continue
h=$(jq '[.[] | select(.confidence == "HIGH")] | length' "$f")
m=$(jq '[.[] | select(.confidence == "MEDIUM")] | length' "$f")
l=$(jq '[.[] | select(.confidence == "LOW")] | length' "$f")
high_count=$((high_count + h))
medium_count=$((medium_count + m))
low_count=$((low_count + l))
done
echo "## Summary"
echo ""
echo "| Confidence | Count | Action |"
echo "|------------|-------|--------|"
echo "| HIGH | $high_count | Consolidate immediately |"
echo "| MEDIUM | $medium_count | Investigate further |"
echo "| LOW | $low_count | Review if time permits |"
echo ""
# HIGH confidence section
echo "---"
echo ""
echo "## HIGH Confidence Duplicates"
echo ""
echo "These functions are definitely duplicates. Consolidate them."
echo ""
for f in "$DUPLICATES_DIR"/*.json; do
[[ -f "$f" ]] || continue
category=$(basename "$f" .json)
jq -r --arg cat "$category" '
.[] | select(.confidence == "HIGH") |
"### \(.intent)\n\n" +
"**Category:** \($cat)\n\n" +
"**Functions:**\n" +
(.functions | map("- `\(.name)` in `\(.file):\(.line)`" + if .notes then " - \(.notes)" else "" end) | join("\n")) +
"\n\n" +
"**Differences:** \(.differences // "None - identical implementations")\n\n" +
"**Recommendation:** Keep `\(.recommendation.survivor)` - \(.recommendation.reason)\n\n" +
"---\n"
' "$f" 2>/dev/null || true
done
# MEDIUM confidence section
echo ""
echo "## MEDIUM Confidence Duplicates"
echo ""
echo "These functions likely do the same thing. Investigate before consolidating."
echo ""
for f in "$DUPLICATES_DIR"/*.json; do
[[ -f "$f" ]] || continue
category=$(basename "$f" .json)
jq -r --arg cat "$category" '
.[] | select(.confidence == "MEDIUM") |
"### \(.intent)\n\n" +
"**Category:** \($cat)\n\n" +
"**Functions:**\n" +
(.functions | map("- `\(.name)` in `\(.file):\(.line)`" + if .notes then " - \(.notes)" else "" end) | join("\n")) +
"\n\n" +
"**Differences:** \(.differences)\n\n" +
"**Recommendation:** \(.recommendation.action) - \(.recommendation.reason)\n\n" +
"---\n"
' "$f" 2>/dev/null || true
done
# LOW confidence section
echo ""
echo "## LOW Confidence (Possibly Related)"
echo ""
echo "These functions might be related. Review if time permits."
echo ""
for f in "$DUPLICATES_DIR"/*.json; do
[[ -f "$f" ]] || continue
category=$(basename "$f" .json)
jq -r --arg cat "$category" '
.[] | select(.confidence == "LOW") |
"### \(.intent)\n\n" +
"**Category:** \($cat)\n\n" +
"**Functions:**\n" +
(.functions | map("- `\(.name)` in `\(.file):\(.line)`") | join("\n")) +
"\n\n" +
"**Notes:** \(.differences)\n\n" +
"---\n"
' "$f" 2>/dev/null || true
done
} > "$OUTPUT"
echo "Report generated: $OUTPUT" >&2
echo " HIGH confidence: $high_count groups" >&2
echo " MEDIUM confidence: $medium_count groups" >&2
echo " LOW confidence: $low_count groups" >&2
#!/usr/bin/env bash
# ABOUTME: Prepares category-specific function lists for duplicate detection
# Takes categorized output and splits into per-category files for Opus analysis
set -euo pipefail
usage() {
cat <<EOF
Usage: $(basename "$0") <categorized.json> [output-dir]
Split categorized function catalog into per-category files for duplicate analysis.
ARGUMENTS:
categorized.json Output from categorization phase
output-dir Directory for category files (default: ./categories)
OUTPUT:
Creates one JSON file per category (e.g., validation.json, string-utils.json)
Only creates files for categories with 3+ functions (worth analyzing)
EXAMPLE:
$(basename "$0") categorized.json ./analysis
# Creates: ./analysis/validation.json, ./analysis/file-ops.json, etc.
EOF
exit 0
}
if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
usage
fi
if [[ -z "${1:-}" ]]; then
echo "Error: categorized.json required" >&2
usage
fi
CATEGORIZED="$1"
OUTPUT_DIR="${2:-./categories}"
if [[ ! -f "$CATEGORIZED" ]]; then
echo "Error: file not found: $CATEGORIZED" >&2
exit 1
fi
mkdir -p "$OUTPUT_DIR"
# Get category counts and filter to those with 3+ functions
echo "Analyzing categories..." >&2
jq -r '
group_by(.category) |
map({
category: .[0].category,
count: length,
functions: .
}) |
sort_by(-.count) |
.[] |
"\(.category)\t\(.count)"
' "$CATEGORIZED" | while IFS=$'\t' read -r category count; do
if [[ "$count" -ge 3 ]]; then
outfile="$OUTPUT_DIR/${category}.json"
jq --arg cat "$category" '[.[] | select(.category == $cat)]' "$CATEGORIZED" > "$outfile"
echo " $category: $count functions -> $outfile" >&2
else
echo " $category: $count functions (skipped, < 3)" >&2
fi
done
echo "" >&2
echo "Category files created in $OUTPUT_DIR" >&2
echo "Run Opus duplicate detection on each file with 3+ functions" >&2
Related skills
How it compares
Pick finding-duplicate-functions over jscpd alone when auditing agent-generated repos for same-purpose helpers with different implementations.
FAQ
How is finding-duplicate-functions different from jscpd?
finding-duplicate-functions catches semantic duplicates—functions with the same intent but different names or implementations. jscpd only finds syntactic copy-paste matches that classical detectors already cover.
When should finding-duplicate-functions run?
finding-duplicate-functions fits post-generation audits of LLM-assisted codebases, especially when new utility functions appear instead of reusing existing ones. Run it before large refactors or merge reviews.
Is Finding Duplicate Functions safe to install?
skills.sh reports 3 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.