jscpd
Copy-paste detector for programming source code, supports 220+ languages. Use this skill to run jscpd and understand its output.
Quick Start
# Run with ai reporter (compact output optimized for agents)
npx jscpd --reporters ai <path>
# With ignore patterns
npx jscpd --reporters ai --ignore "**/node_modules/**,**/dist/**" <path>
# Scope to specific formats
npx jscpd --reporters ai --format "javascript,typescript" <path>
# Second pass, noisier: copies that only differ in names and values (Type-2, "renamed").
# Review each hit before acting on it.
npx jscpd --reporters ai --ignore-identifiers --min-tokens 70 <path>
# Third pass, noisier still: copies with a few edited lines or the same function
# structure (Type-3, "similar"). Keep the settings tight.
npx jscpd --reporters ai --max-gap-lines 1 --similarity 0.85 <path>
# Where to refactor first: clone list plus a hotspot summary
npx jscpd --reporters ai --summary <path>
# Most complex files only, without clone detection
npx jscpd --complexity --reporters ai <path>
AI Reporter Output Format
The ai reporter produces compact, token-efficient output designed for agent consumption:
Clones:
src/ foo.ts:10-25 ~ bar.ts:42-57
src/utils/helpers.ts:100-120 ~ src/utils/other.ts:5-25
src/cart/ basket.js:1-9 ~ cart.js:1-9 (renamed)
src/api/ save-account.js:1-12 ~ save-user.js:1-11 [~0.91 gap]
src/billing/ credit-note.js:1-19 ~ invoice.js:1-17 [~0.75 ast]
---
5 clones · 4.2% duplication
Each line represents one clone pair:
- Same file:
path/file.ts 10-25 ~ 45-60(shared path shown once) - Same directory:
shared/prefix/ file-a.ts:10-25 ~ file-b.ts:42-57(common prefix factored out) - Different paths:
path/a.ts:10-25 ~ path/b.ts:42-57
A suffix tells the kind of clone; no suffix means an exact copy:
(renamed): the two blocks differ only in identifier names, literal values or annotations (Type-2). Only appears with--ignore-identifiers,--ignore-literalsor--ignore-annotations.[~0.91 gap]: two exact clones merged across up to--max-gap-linesunmatched lines (Type-3). The number is matched tokens over the merged span.[~0.75 ast]: two functions whose syntax-tree structure overlaps at least--similarity(Type-3). The number is the structural similarity, names and literal values do not count.
Options
| Option | Description |
|---|---|
--reporters ai | Use the AI-optimized reporter (compact clone list for agents) |
--reporters html | Generate HTML report |
--reporters json | Output JSON report |
--min-tokens N | Minimum tokens to consider a duplication (default: 50) |
--min-lines N | Minimum lines to consider a duplication (default: 5) |
--threshold N | Exit with error if duplication % exceeds N |
--ignore "glob" | Ignore patterns (comma-separated) |
--format "list" | Limit to specific languages (e.g. typescript,javascript) |
--cross-formats "groups" | Detect clones across related formats (e.g. javascript,typescript or the js-ts preset) |
--ignore-identifiers | Treat all identifiers as equal, so blocks that differ only in variable, function or type names match (Type-2, reported as renamed) |
--ignore-literals | Treat all string literals as equal and all numeric literals as equal (Type-2) |
--ignore-annotations | Drop @Name / @Name(...) annotations and decorators before matching, in languages where @ means one (Type-2) |
--max-gap-lines N | Merge clones of one file pair separated by at most N unmatched lines into one similar clone (Type-3, default: 0 = off) |
--similarity RATIO | Report JavaScript/TypeScript function pairs whose syntax-tree similarity reaches RATIO, in (0, 1], as similar clones (Type-3, default: 1 = exact only) |
--summary | Append a codebase summary: top files/folders by tokens, lines, size, complexity, with duplication share |
--summary-top N | Number of entries in each summary top list (default: 10) |
--summary-by metric | Summary ranking metric: tokens, lines, size, complexity (default: tokens) |
--kind list | Report only these clone kinds: exact, renamed, similar, gap, ast (never enables a detector) |
--complexity | Complexity tables only, without clone detection (reporters: console, ai, json) |
--dead-code | Find unused files, exports, module-private declarations and imports — JavaScript, TypeScript, JSX, TSX, Vue, Svelte, Astro, Python. Each finding carries a 0-100 confidence score |
--dead-code-categories list | Limit --dead-code/--dashboard to categories: unused-file, unused-export, unused-symbol, unused-import |
--min-confidence N | Floor for --dead-code/--dashboard findings, 0-100 (default: 60) |
--entry "glob" | Extra entry points for dead-code analysis, beyond what's inferred from package.json/pyproject.toml/framework conventions |
--include-tests | Count test-only usage as "used" for dead-code analysis (excluded by default) |
--include-entry-exports | Also flag an entry file's own unused exports (excluded by default: its exports are the public API) |
--dashboard | One screen: health badge, project size and largest code files, duplication, complexity, dead code (JS/TS/Python); -r json writes jscpd-dashboard.json |
--health | Project health badge only: 0-100 score and grade from duplication, dead code and complexity (-r ai one line, -r json, -r badge) |
--health-input FILE | Add metrics from other tools (coverage, tests, security) to the health score |
--pattern "glob" | Glob pattern to select files |
--no-gitignore | Do not respect .gitignore (it is respected by default) |
--output "path" | Directory to write reports to |
--silent | Suppress console output (useful with file reporters and --output) |
--list | List all supported formats and exit |
--no-tips | Disable tips in output (skipped automatically when stdout is not a TTY or CI or JSCPD_NO_TIPS is set) |
--config "path" | Path to .jscpd.json config file |
Clone Kinds: Exact, Renamed, Similar
By default jscpd reports exact clones only: the token sequences are identical (whitespace, layout and, depending on the mode, comments do not count). Two opt-in families widen the net. Run them as separate passes after the default scan, because they find more and longer clones and change what a "clone" means.
These passes are noisy by design. An exact clone is almost always a real copy. A renamed or similar clone is a candidate: the flags deliberately ignore the very things (names, values, a statement or two) that often make two blocks different in meaning. Expect false positives from:
- boilerplate that is supposed to look alike: DTOs and models, config tables, enum-like maps, route or handler registrations, builders
- test files:
describe/itblocks, fixtures and setup code repeat the same shape on purpose - generated code, migrations, serializers, protocol bindings
- language idioms: two
reduceloops or twoswitchstatements that share structure but not logic - small blocks: with
--ignore-identifiersa 50-token block is mostly placeholders, so raise--min-tokens
Rules that keep the noise manageable:
- Run them after the exact clones are handled, one family at a time, so every hit is attributable to one flag.
- Start conservative:
--ignore-identifiersalone (add--ignore-literalsonly when constants are the known problem),--max-gap-lines 1or2,--similarity 0.85or higher, and--min-tokens 70or more for the identifier pass. Widen only when the tight run comes back empty. - Treat every
(renamed)or[~…]line as a lead to read, not a defect to fix. Do not gate CI (--threshold,--fail-on-new-clones) on these passes unless the team has reviewed what they report on the codebase. - Never claim "N duplicates found" from a normalized run without saying which flags produced them.
Type-2: renamed clones (--ignore-identifiers, --ignore-literals, --ignore-annotations)
Copies where someone renamed the variables or changed the constants:
npx jscpd --reporters ai --ignore-identifiers <path> # function a(x) {…} matches function b(y) {…}
npx jscpd --reporters ai --ignore-identifiers --ignore-literals <path> # …and 10 matches 25, 'dev' matches 'prod'
npx jscpd --reporters ai --ignore-annotations <path> # @Override / @Deprecated no longer split a clone
Clones:
basket.js:1-9 ~ cart.js:1-9 (renamed)
limits-dev.js:1-13 ~ limits-prod.js:1-13 (renamed)
---
- Keywords keep their meaning (
returnnever matchesretry), so structure still has to match. --ignore-annotationsonly acts in Java, Kotlin, Scala, Groovy, Python, Dart, Swift, JavaScript and TypeScript; in Ruby, Perl, T-SQL, Razor and CSS@means something else and is left alone.- Positions in the report still point at the original source.
- Config keys:
ignoreIdentifiers,ignoreLiterals,ignoreAnnotations.
Type-3: near-miss clones (--max-gap-lines, --similarity)
Copies with a few edited lines, or functions rewritten with the same shape:
npx jscpd --reporters ai --max-gap-lines 2 <path> # a copy with up to 2 inserted/changed lines becomes one clone
npx jscpd --reporters ai --similarity 0.8 <path> # JS/TS functions with ≥80% shared syntax-tree structure
Clones:
save-account.js:1-12 ~ save-user.js:1-11 [~0.91 gap]
credit-note.js:1-19 ~ invoice.js:1-17 [~0.75 ast]
---
--max-gap-lines Nonly joins clones the exact run already found, so it removes fragmentation rather than inventing matches; it works in every language. A merge is refused when the gap holds more tokens than the halves share (similarity would drop under0.5).--similarity RATIOcompares whole functions by the bag of 4-grams over their syntax-tree node types, so a renamed copy scores1.0, one inserted line about0.9, two added statements plus renames about0.75. Today it applies to JavaScript, TypeScript, JSX and TSX only; other formats are a silent no-op. Start at0.85for near-identical structure and lower to0.7only when looking for leads; below0.8a large share of pairs merely share an idiom, so read both functions before believing the score.similartakes precedence overrenamedwhen both apply (a merged clone is no longer identical even after normalization).- Config keys:
maxGapLines,similarity.
Where the kind shows up
console:Clone found (javascript, renamed),Clone found (javascript, similar (gap) ~0.91),Clone found (javascript, similar (ast) ~0.75).json:"kind": "exact" | "renamed" | "similar", plus"similarity"and"method": "gap" | "ast"for similar clones.sarif: rulesjscpd/duplicate-code,jscpd/renamed-code,jscpd/similar-code; Code Climate uses the same threecheck_namevalues.- A default run reports only
exactclones and its output is unchanged by these features. - Normalized runs produce different clone fingerprints than exact runs: keep a separate
--baselinefile per configuration.
Codebase Summary (--summary)
--summary appends a refactoring-hotspot overview to the run output — use it to decide where to refactor first before diving into individual clones:
# Compact clone list + compact summary, optimized for agents
npx jscpd --reporters ai --summary --no-tips <path>
# Rank by complexity instead of tokens, top 5 lists
npx jscpd --reporters ai --summary --summary-by complexity --summary-top 5 <path>
With the ai reporter the summary is one line per entry:
Summary by tokens (321 files, 129 folders):
files (tokens/lines/size/cx/dup%):
src/files.ts 2052/363/11662/80/0.0%
long-line/theme-branded.js 498/15/2.5K/10/93.3%
...
folders (files/tokens/lines/size):
src/core 8/5264/843/27136
...
How to read it:
- Top files are ranked by the
--summary-bymetric, but every row carries all metrics —tokens/lines/size/cx/dup%. - cx is a language-agnostic cyclomatic-complexity estimate from the token stream (1 + decision-point tokens like
if/while/&&); treat it as a ranking signal, not an exact metric. - dup% is the share of the file's lines covered by detected clones — a large file with high
dup%is the best refactoring target. It reflects the clones of the current run, so with the Type-2/Type-3 flags it rises accordingly. - Folders aggregate files into their direct parent directory (no cumulative ancestor totals).
- In
consolereporters the summary renders as aligned tables; in thejsonreport it appears as an additivesummarykey (absent when the flag is off).
Config file equivalents: "summary": true, "summaryTop": 10, "summaryBy": "tokens".
Cross-Format Clone Detection
By default each format is compared only against itself. --cross-formats defines groups of related formats that share one comparison pool, so a block duplicated between a .js and a .ts file is reported as a clone:
# One group: compare JavaScript and TypeScript files together
npx jscpd --reporters ai --cross-formats "javascript,typescript" <path>
# Preset covering javascript, jsx, typescript, tsx
npx jscpd --reporters ai --cross-formats "js-ts" <path>
# Multiple groups are separated by ";"
npx jscpd --reporters ai --cross-formats "javascript,typescript;css,scss" <path>
Notes:
- When a group mixes TypeScript with JavaScript, TS files are compared with erasable type syntax stripped, so
function f(a: number): voidmatchesfunction f(a). Reported positions still reference the original source. - Groups need at least two formats; groups sharing a format are merged into one pool.
- In per-format statistics, a cross-format clone is attributed to one member format of the group.
- In config files the key is
crossFormats(orcross-formats) and accepts a string ("javascript,typescript;css,scss"), an array of strings (["javascript,typescript", "css,scss"]), or an array of arrays ([["javascript","typescript"],["css","scss"]]).
Configuration File
Create a .jscpd.json in your project root:
{
"threshold": 0,
"reporters": ["ai"],
"ignore": ["**/node_modules/**", "**/dist/**", "**/*.min.*"],
"format": ["typescript", "javascript"],
"minLines": 5,
"minTokens": 50,
"ignoreIdentifiers": false,
"ignoreLiterals": false,
"maxGapLines": 0,
"similarity": 1,
"summary": false,
"output": "./reports/jscpd"
}
Refactoring Duplicated Code
Once you've detected clones, use the dry-refactoring skill for a guided workflow to eliminate them, with a strategy per clone kind:
→ dry-refactoring — step-by-step refactoring strategies and workflow for removing duplication. Install with:
npx skills add https://github.com/kucherenko/jscpd --skill dry-refactoring
Improving Overall Codebase Health
For a broader "clean up this codebase" pass — duplication, then dead code, then the largest/most complex files, prioritized from --health/--dashboard and re-measured at the end — use codebase-refactoring:
npx skills add https://github.com/kucherenko/jscpd --skill codebase-refactoring