Ai Ready
AI-Ready Integrations
jscpd integrates into AI-powered development workflows through three complementary mechanisms: the AI reporter, agent skills, and an MCP server.
AI Reporter
The ai reporter produces compact, token-efficient output designed to be piped directly into an LLM prompt or agentic pipeline. It uses common-path-prefix compression and omits code fragments and colors — just the clone locations and a summary.
TypeScript (v4)
jscpd --reporters ai /path/to/sourceRust (v5)
cpd --reporters ai /path/to/sourceExample Output
src/utils/ auth.ts:10-25 ~ helpers.ts:40-55
src/utils/auth.ts 30-45 ~ 80-95
src/ utils/auth.ts:10-25 ~ api/routes.ts:5-20
---
23 clones · 4.2% duplicationToken Efficiency
Benchmarked on the fixtures/ directory (212 clones, 347 files):
| Reporter | Output size | Estimated tokens |
|----------|-------------|------------------|
| console (default) | ~21,800 chars | ~5,400 |
| ai | ~4,500 chars | ~1,100 |
~79% fewer tokens than the default console reporter.
Codebase Summary (v5)
Add --summary for a compact refactoring-hotspot overview — top files and folders by tokens, lines, size, and a complexity estimate. In the ai reporter each entry is one line with all metrics inline, so an agent gets the full picture for a handful of tokens:
Summary by tokens (321 files, 129 folders):
files (tokens/lines/size/cx/dup%):
src/core/files.ts 2052/363/11.4K/80/0.0%
...
folders (files/tokens/lines/size):
src/core 8/5264/843/26.5K
...cpd --reporters ai --summary --no-tips /path/to/sourceSee rust.md for the metric definitions and --summary-top / --summary-by options.
Agent Skills
jscpd ships two AI agent skills that teach coding assistants how to use jscpd and refactor detected duplications.
jscpd — Tool Reference Skill
Covers all CLI options, the AI reporter output format, and configuration file syntax.
npx skills add kucherenko/jscpd --skill jscpddry-refactoring — Refactoring Workflow Skill
A guided process for reading clone output, choosing the right extraction strategy, applying the refactor, and verifying the clone is eliminated.
npx skills add kucherenko/jscpd --skill dry-refactoringAfter installation, ask your agent to "find and fix code duplication" and it will invoke jscpd with the right options and act on the results.
MCP Server
jscpd-server implements the Model Context Protocol (MCP), exposing jscpd's detection capabilities as tools that AI assistants can call directly from the editor. Start the server once against your codebase, then let your AI assistant check any snippet for duplication on demand — no CLI invocation needed.
Installation
npm install jscpd-serverUsage
Start the server:
jscpd-server /path/to/projectOptions:
- --port — Port number (default: 3000)
- --host — Host to bind (default: 127.0.0.1)
- --allowed-origin — Extra Origin hostname accepted by the MCP and REST endpoints (repeatable)
- --allowed-host — Host hostname the MCP and REST endpoints answer on (repeatable)
- --store leveldb — Use LevelDB persistent storage
- Plus all standard jscpd detection options
MCP Configuration
Add to your MCP client config (e.g. Claude Desktop):
{
"mcpServers": {
"jscpd": {
"type": "streamable-http",
"url": "http://localhost:3000/mcp"
}
}
}The endpoint serves protocol revision 2026-07-28: requests are direct and stateless, carrying their protocol version and client capabilities in the per-request _meta envelope, so there is no initialize handshake and no Mcp-Session-Id. Clients discover the server with server/discover. 2025-era clients keep working through the SDK's stateless legacy fallback.
/mcp, POST /api/check, POST /api/recheck, and GET /api/stats validate the Origin and Host headers, as the transport specification requires. Loopback origins and hosts are allowed by default; add --allowed-origin for a browser client served under another name, and --allowed-host to pin extra hostnames a reachable deployment answers on. A concrete --host is always included in the Host allowlist.
REST API
| Method | Path | Description |
|--------|------|-------------|
| POST | /api/check | Check a code snippet for duplications. Body: {"code": "...", "format": "javascript"} |
| POST | /api/recheck | Trigger a re-scan of the directory |
| GET | /api/stats | Get overall project duplication statistics |
| GET | /api/health | Health check — returns { status, workingDirectory, lastScanTime } |
| GET | / | API info with endpoint listing |
MCP Tools
Available MCP tools exposed via the /mcp endpoint:
- check_duplication — Check a code snippet for duplications (inputs: code, format)
- get_statistics — Get project stats (no inputs)
- check_current_directory — Re-scan the working directory (no inputs)
Snippet checking uses an ephemeral in-memory store per request for isolation — no cross-request contamination, automatic cleanup, concurrent-request safe.
---
Api
Programming API
Both jscpd v4 (TypeScript) and v5 (Rust) provide programmatic APIs for integration into your own tools.
TypeScript (v4)
jscpd Function
The jscpd function accepts an argv-style array and returns a Promise<IClone[]>:
import { IClone } from '@jscpd/core';
import { jscpd } from 'jscpd';const clones: IClone[] = await jscpd([]);
Pass options as CLI-like arguments:
const clones: IClone[] = await jscpd([
'', '', __dirname + '/../fixtures',
'-m', 'weak',
'--silent',
]);detectClones Function
A higher-level API with an options object:
import { detectClones } from 'jscpd';const clones = await detectClones({
path: ['./src'],
silent: true,
format: ['javascript', 'typescript'],
minLines: 5,
minTokens: 50,
mode: 'mild',
});
Custom Store
Use detectClones with a custom store for incremental detection:
import { detectClones } from 'jscpd';
import { IMapFrame, MemoryStore } from '@jscpd/core';const store = new MemoryStore<IMapFrame>();
await detectClones({
path: ['./src'],
}, store);
// Re-use the store for incremental detection
await detectClones({
path: ['./src'],
silent: true,
}, store);
For large repositories, use the LevelDB store:
import { detectClones } from 'jscpd';
import { IMapFrame } from '@jscpd/core';
import { LevelDBStore } from '@jscpd/leveldb-store';const store = new LevelDBStore<IMapFrame>('/path/to/leveldb/dir');
await detectClones({
path: ['./src'],
}, store);
Building Custom Tools
Compose the lower-level packages for deep customization:
- @jscpd/core — Core detection algorithm (Rabin-Karp), event emitter interface. Single dependency on eventemitter3.
- @jscpd/tokenizer — Source code tokenization (224+ formats via reprism).
- @jscpd/finder — File walking, clone detection orchestration, built-in reporters, subscribers, validators.
- @jscpd/leveldb-store — LevelDB persistent store for large repositories.
- @jscpd/redis-store — Redis store for distributed/CI environments.
See Packages for details on each package.
Rust (v5)
The Rust engine is available as two npm packages — jscpd@5 (installs the jscpd command) and cpd (installs the cpd command). On crates.io it is published as jscpd, which installs both jscpd and cpd binaries.
For integration in Rust applications, use the cpd-finder crate:
use cpd_finder::orchestrate::{RunConfig, run};let config = RunConfig {
paths: vec!["./src".into()],
min_tokens: 50,
..Default::default()
};
let result = run(&config).unwrap();
println!("Found {} clones", result.clones.len());
println!("Analyzed {} files", result.statistics.total.sources);
Crate Architecture
| Crate | Description |
|-------|-------------|
| cpd-core | Core data models and hashing (Rabin-Karp rolling hash) |
| cpd-tokenizer | Source code tokenization (223+ formats, uses oxc_parser) |
| cpd-finder | File walking, orchestration, git blame (rayon + ignore + globset) |
| cpd-reporter | Output format rendering (13 reporters) |
There is no Node.js API for v5 — use v4's TypeScript API for Node.js integration, or v5's Rust API for Rust integration.
---
Ci And Hooks
CI & Pre-Commit Hooks
jscpd can enforce duplication thresholds in CI pipelines and as a local pre-commit hook — catching copy/pasted code before it reaches the main branch.
GitHub Action
The jscpd-copy-paste-detector GitHub Action runs jscpd in your CI workflow. It installs the Rust engine, runs detection, uploads SARIF to GitHub Code Scanning, and optionally uploads the report as an artifact.
Basic Usage
name: Duplication Checkon: [push, pull_request]
jobs:
jscpd:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: kucherenko/jscpd@master
This scans the entire repository with default settings and uploads SARIF results to GitHub Code Scanning.
Fail on Threshold
Set threshold to fail the build when duplication exceeds a percentage:
- uses: kucherenko/jscpd@master
with:
threshold: 5The workflow fails if more than 5% of the code is duplicated.
Action Inputs
| Input | Description | Default |
|-------|-------------|---------|
| path | Paths to scan (space-separated) | . |
| config | Path to .jscpd.json config file | — |
| min-tokens | Minimum tokens for a clone | 50 |
| min-lines | Minimum lines for a clone | 5 |
| max-lines | Maximum lines per block | — |
| mode | Detection mode: mild, weak, strict | mild |
| format | Comma-separated formats to check | — |
| ignore | Comma-separated glob patterns to ignore | — |
| ignore-pattern | Comma-separated regex patterns to skip | — |
| reporters | Comma-separated reporters | console |
| output | Output directory for file reporters | report |
| threshold | Max duplication % before exit 1 | — |
| blame | Enrich clones with git blame data | false |
| exit-code | Exit with code when duplicates found (true or integer) | — |
| pattern | Glob pattern for file search | — |
| max-size | Skip files larger than SIZE | — |
| skip-local | Skip clones in same directory | false |
| ignore-case | Ignore case of symbols (experimental) | false |
| follow-symlinks | Follow symbolic links | false |
| no-gitignore | Don't respect .gitignore files | false |
| absolute | Use absolute paths in reports | false |
| formats-exts | Custom format-to-extension mappings | — |
| formats-names | Custom format-to-filename mappings | — |
| version | jscpd version to install | latest |
| install-prefix | Installation directory for the binary | — |
| skip-install | Skip installation (binary already present) | false |
| extra-args | Additional arguments passed to jscpd | — |
| upload-report | Upload report directory as artifact | false |
| upload-sarif | Upload SARIF to GitHub Code Scanning | true |
Action Outputs
| Output | Description |
|--------|-------------|
| duplication-percentage | Percentage of duplicated code found |
| clones-found | Number of clone pairs found |
| duplicated-lines | Number of duplicated lines |
| total-lines | Total lines scanned |
| files-count | Number of source files scanned |
| report-path | Path to the output directory |
| sarif-path | Path to the SARIF report file |
| exit-code | Exit code from jscpd |
Examples
#### Scan specific directories with threshold
- uses: kucherenko/jscpd@master
with:
path: src/lib src/utils
threshold: 3
ignore: "/.test.,/.spec."#### Use a config file
- uses: kucherenko/jscpd@master
with:
config: .jscpd.json
upload-report: true#### Multi-reporter with artifact upload
- uses: kucherenko/jscpd@master
with:
reporters: console,json,html,sarif
output: jscpd-report
upload-report: true#### Pin a specific version
- uses: kucherenko/jscpd@master
with:
version: "5.0.9"#### Skip install (binary already in image)
- uses: kucherenko/jscpd@master
with:
skip-install: true#### Use outputs in subsequent steps
- uses: kucherenko/jscpd@master
id: jscpd- name: Check results
if: steps.jscpd.outputs.duplication-percentage > 5
run: |
echo "Duplication is ${{ steps.jscpd.outputs.duplication-percentage }}%"
echo "Found ${{ steps.jscpd.outputs.clones-found }} clones"
Pre-Commit Hook
Run jscpd before every commit to prevent duplicated code from entering the repository.
Using pre-commit framework
The pre-commit framework manages git hooks for you. After configuring the hook, it runs automatically on every git commit.
1. Install pre-commit (one time, any of these):
pip
pip install pre-commitbrew
brew install pre-commitnpm (wrapper around the Python tool)
npm install pre-commit2. Add the hook config to .pre-commit-config.yaml in your repo:
Option A: language: node — pre-commit installs jscpd automatically:
repos:
- repo: local
hooks:
- id: jscpd
name: jscpd - copy/paste detector
entry: jscpd
language: node
additional_dependencies: ['jscpd@5']
args: [--threshold, "5", --reporters, console,silent]
pass_filenames: false
always_run: trueOption B: language: system — jscpd must be pre-installed globally:
repos:
- repo: local
hooks:
- id: jscpd
name: jscpd - copy/paste detector
entry: jscpd
language: system
args: [--threshold, "5", --reporters, console,silent]
pass_filenames: false
always_run: trueIf using Option B, install jscpd globally first: npm install jscpd@5 or cargo install jscpd.
3. Install the hook into git:
pre-commit installThat's it — jscpd now runs on every git commit. If duplication exceeds the threshold, the commit is blocked.
To run manually without committing:
pre-commit run jscpd --all-filesUsing Husky
npm install -D husky
npx husky initAdd the hook:
echo 'npx jscpd@5 --threshold 5 --reporters console,silent .' > .husky/pre-commitManual git hook
No extra tools required — just a shell script in .git/hooks/.
1. Create .git/hooks/pre-commit:
#!/bin/sh
jscpd --threshold 5 --reporters console,silent .2. Make it executable:
chmod +x .git/hooks/pre-commitHooks in .git/hooks/ are not version-controlled. To share the hook with your team, store it in the repo and symlink or copy it:
Option A: Symlink from a versioned script
Store the hook logic in the repo (e.g. scripts/pre-commit), then symlink:
ln -s ../../scripts/pre-commit .git/hooks/pre-commitEach developer runs the symlink command once after cloning.
Option B: core.hooksPath (Git 2.9+)
Point Git at a versioned hooks directory:
git config core.hooksPath .githooksCreate .githooks/pre-commit:
#!/bin/sh
jscpd --threshold 5 --reporters console,silent .chmod +x .githooks/pre-commitCommit .githooks/ to the repo. New contributors run the git config command once after cloning. Add it to your onboarding docs or a scripts/setup.sh:
#!/bin/sh
git config core.hooksPath .githooksOption C: npm prepare script
Add to package.json:
{
"scripts": {
"prepare": "git config core.hooksPath .githooks"
}
}npm install (and npm ci) automatically run prepare, so the hooks path is set with no manual steps.
Option D: Makefile
.PHONY: hooks
hooks:
git config core.hooksPath .githooksContributors run make hooks after cloning.
Tips
- Use --reporters console,silent to show clone details without writing report files on every commit
- Use --threshold to set a failure threshold — the hook exits with code 1 if exceeded
- Use --ignore to exclude generated files, test fixtures, or vendor directories
- For large repos, use the Rust engine (jscpd@5 / cpd) — it runs 24-37x faster, keeping commit latency low
- Consider --format to limit detection to specific languages during the hook, with a full scan in CI
---
Packages
Packages
The jscpd monorepo contains two apps and several supporting packages.
Apps
jscpd
Path: apps/jscpd
npm: jscpd
Version: 4.2.5
Main package for jscpd — CLI and Node.js API for copy/paste detection. See TypeScript docs.
jscpd-server
Path: apps/jscpd-server
npm: jscpd-server
Version: 4.2.5
Standalone server application providing REST API and MCP server for on-demand code duplication detection. See AI-Ready docs for details.
Packages (TypeScript / Node.js)
@jscpd/core
Path: packages/core
npm: @jscpd/core
Version: 4.2.5
Core detection algorithm. Implements Rabin-Karp rolling hash for finding duplicate code blocks. Single dependency on eventemitter3. Provides IClone, IMapFrame, MemoryStore, and event interfaces.
@jscpd/finder
Path: packages/finder
npm: @jscpd/finder
Version: 4.2.5
Detector of duplications in files. Walks filesystem, runs clone detection, provides built-in reporters, subscribers, validators, and hooks.
@jscpd/tokenizer
Path: packages/tokenizer
npm: @jscpd/tokenizer
Version: 4.2.5
Tokenizer — converts source code into tokens for duplicate detection. Supports 224 languages/formats via reprism-based grammar engine with lazy loading. Cross-format tokenization for Vue SFC, Svelte, Astro, and Markdown.
@jscpd/html-reporter
Path: packages/html-reporter
npm: @jscpd/html-reporter
Version: 4.2.5
HTML reporter — generates interactive HTML report with per-format statistics, duplication graph, and syntax-highlighted clone diffs.
@jscpd/badge-reporter
Path: packages/badge-reporter
npm: @jscpd/badge-reporter
Version: 4.2.5
Badge reporter — generates SVG badges showing copy/paste level.
jscpd-sarif-reporter
Path: packages/sarif-reporter
npm: jscpd-sarif-reporter
Version: 4.2.5
SARIF reporter — generates Static Analysis Results Interchange Format output for GitHub Code Scanning. Emits warning-level results per clone, plus error if threshold exceeded.
@jscpd/leveldb-store
Path: packages/leveldb-store
npm: @jscpd/leveldb-store
Version: 4.2.5
LevelDB store — persistent disk-backed token store for large repositories. Slower than default in-memory store but can handle very large codebases.
@jscpd/redis-store
Path: packages/redis-store
npm: @jscpd/redis-store
Version: 4.2.5
Redis store — offloads in-memory hash map to Redis. Useful for large codebases or distributed/CI environments.
Crates (Rust / v5)
cpd (binary)
Path: rust/crates/cpd
npm: jscpd@5 (installs the jscpd command) | cpd (installs the cpd command)
crates.io: jscpd (installs both jscpd and cpd binaries)
Version: 5.0.4 (npm) / 0.1.4 (crates.io)
CLI binary, entry point. Published as jscpd@5 on npm (self-contained binary, installs the jscpd command, no Node.js runtime) and cpd on npm (installs the cpd command). See Rust docs.
cpd-core
Path: rust/crates/cpd-core
Version: 0.1.3
Core data models and Rabin-Karp rolling hash implementation.
cpd-tokenizer
Path: rust/crates/cpd-tokenizer
Version: 0.1.3
Source code tokenizer (223+ formats). Uses oxc_parser for Go, TypeScript/JSX tokenization.
cpd-finder
Path: rust/crates/cpd-finder
Version: 0.1.4
File walking, orchestration, and git blame. Uses rayon for parallelism, ignore + globset for file matching.
cpd-reporter
Path: rust/crates/cpd-reporter
Version: 0.1.4
Output format rendering for 13 reporters.
---
Performance Comparison
jscpd Performance Comparison: v4 (TypeScript) vs v5 (Rust)
Date: 2026-06-08
Runs per configuration: 10 (fixtures, svelte), 3 (CopilotKit)
Machine: macOS (Apple Silicon)
Versions
| Tool | Version | Runtime |
|------|---------|---------|
| jscpd v4 | 4.2.5 | Node.js |
| jscpd v5 (cpd) | 5.0.4 | Native binary (Rust) |
Benchmark Targets
| Target | Files | Size | Description |
|--------|-------|------|-------------|
| fixtures | 548 | 1.5 MB | Multi-language test fixtures (126+ formats) |
| svelte | 8,963 | 38 MB | Svelte framework source code |
| CopilotKit | 17,092 | 159 MB | Large real-world TypeScript/React project |
Execution Time Results
fixtures (548 files, 1.5 MB)
| Metric | jscpd v4 | jscpd v5 | Speedup |
|--------|----------|----------|---------|
| Mean real time | 1.030s | 0.030s | 34.3x |
| Std dev | 0.042s | 0.000s | |
| Min | 1.000s | 0.030s | |
| Max | 1.130s | 0.030s | |
| Mean user time | 1.174s | 0.085s | |
| Mean sys time | 0.074s | 0.050s | |
svelte (8,963 files, 38 MB)
| Metric | jscpd v4 | jscpd v5 | Speedup |
|--------|----------|----------|---------|
| Mean real time | 15.803s | 0.428s | 36.9x |
| Std dev | 1.010s | 0.021s | |
| Min | 14.740s | 0.390s | |
| Max | 17.790s | 0.450s | |
| Mean user time | 16.075s | 0.553s | |
| Mean sys time | 0.738s | 1.110s | |
CopilotKit (17,092 files, 159 MB)
| Metric | jscpd v4 | jscpd v5 | Speedup |
|--------|----------|----------|---------|
| Mean real time | 82.890s | 3.440s | 24.1x |
| Std dev | 4.086s | 0.699s | |
| Min | 79.560s | 2.900s | |
| Max | 87.450s | 4.230s | |
| Mean user time | 100.020s | 7.323s | |
| Mean sys time | 18.263s | 3.100s | |
Detection Results Comparison
fixtures
| Metric | jscpd v4 | jscpd v5 |
|--------|----------|----------|
| Files analyzed | 364 | 347 |
| Clones found | 211 | 212 |
| Duplicated lines | 9,969 (47.08%) | 9,133 (37.12%) |
| Duplicated tokens | 73,416 (47.64%) | 56,491 (43.30%) |
svelte
| Metric | jscpd v4 | jscpd v5 |
|--------|----------|----------|
| Files analyzed | 11,672 | 4,322 |
| Clones found | 903 | 1,055 |
| Duplicated lines | 18,246 (7.34%) | 21,821 (8.78%) |
CopilotKit
| Metric | jscpd v4 | jscpd v5 |
|--------|----------|----------|
| Files analyzed | 13,944 | 12,386 |
| Clones found | 12,272 | 22,487 |
Raw Timing Data
jscpd v4 — fixtures
| Run | Real (s) | User (s) | Sys (s) |
|-----|----------|----------|---------|
| 1 | 1.13 | 1.17 | 0.10 |
| 2 | 1.00 | 1.15 | 0.07 |
| 3 | 1.01 | 1.16 | 0.07 |
| 4 | 1.01 | 1.17 | 0.07 |
| 5 | 1.00 | 1.16 | 0.07 |
| 6 | 1.01 | 1.17 | 0.07 |
| 7 | 1.01 | 1.17 | 0.07 |
| 8 | 1.02 | 1.19 | 0.07 |
| 9 | 1.03 | 1.20 | 0.07 |
| 10 | 1.08 | 1.20 | 0.08 |
jscpd v5 — fixtures
| Run | Real (s) | User (s) | Sys (s) |
|-----|----------|----------|---------|
| 1 | 0.03 | 0.08 | 0.05 |
| 2 | 0.03 | 0.09 | 0.05 |
| 3 | 0.03 | 0.08 | 0.05 |
| 4 | 0.03 | 0.08 | 0.05 |
| 5 | 0.03 | 0.08 | 0.05 |
| 6 | 0.03 | 0.09 | 0.06 |
| 7 | 0.03 | 0.09 | 0.04 |
| 8 | 0.03 | 0.09 | 0.05 |
| 9 | 0.03 | 0.08 | 0.05 |
| 10 | 0.03 | 0.09 | 0.05 |
jscpd v4 — svelte
| Run | Real (s) | User (s) | Sys (s) |
|-----|----------|----------|---------|
| 1 | 15.98 | 16.06 | 0.70 |
| 2 | 15.06 | 15.55 | 0.59 |
| 3 | 14.74 | 15.35 | 0.56 |
| 4 | 17.37 | 16.03 | 1.04 |
| 5 | 17.79 | 17.54 | 1.38 |
| 6 | 15.86 | 16.00 | 0.72 |
| 7 | 15.11 | 15.67 | 0.63 |
| 8 | 15.28 | 15.89 | 0.60 |
| 9 | 15.47 | 15.99 | 0.59 |
| 10 | 15.37 | 16.67 | 0.57 |
jscpd v5 — svelte
| Run | Real (s) | User (s) | Sys (s) |
|-----|----------|----------|---------|
| 1 | 0.39 | 0.55 | 0.96 |
| 2 | 0.43 | 0.55 | 1.23 |
| 3 | 0.40 | 0.55 | 0.95 |
| 4 | 0.41 | 0.54 | 1.18 |
| 5 | 0.45 | 0.56 | 1.11 |
| 6 | 0.45 | 0.56 | 1.20 |
| 7 | 0.44 | 0.56 | 1.19 |
| 8 | 0.44 | 0.55 | 1.17 |
| 9 | 0.43 | 0.56 | 0.97 |
| 10 | 0.44 | 0.55 | 1.14 |
jscpd v4 — CopilotKit
| Run | Real (s) | User (s) | Sys (s) |
|-----|----------|----------|---------|
| 1 | 87.45 | 99.76 | 20.15 |
| 2 | 79.56 | 97.08 | 16.02 |
| 3 | 81.66 | 103.22 | 18.62 |
jscpd v5 — CopilotKit
| Run | Real (s) | User (s) | Sys (s) |
|-----|----------|----------|---------|
| 1 | 2.90 | 7.41 | 3.13 |
| 2 | 4.23 | 7.16 | 3.18 |
| 3 | 3.19 | 7.40 | 2.99 |
Analysis
v5 is dramatically faster across all targets
After correcting the benchmark methodology, v5 is consistently 24–37x faster than v4:
| Target | v4 (TypeScript) | v5 (Rust) | Speedup |
|--------|----------------|-----------|---------|
| fixtures (548 files, 1.5 MB) | 1.03s | 0.03s | 34.3x |
| svelte (9K files, 38 MB) | 15.80s | 0.43s | 36.9x |
| CopilotKit (17K files, 159 MB) | 82.89s | 3.44s | 24.1x |
Key observations
1. Startup overhead: v5's native binary has near-zero startup cost. v4's Node.js runtime adds ~1s even for tiny fixtures.
2. Scaling: v5 scales well from small to large codebases. CopilotKit (159 MB) takes only 3.4s. v4 takes 83s on the same target.
3. CPU utilization: v5's higher user time relative to real time (e.g., CopilotKit: 7.3s user vs 3.4s real) shows effective multi-threading. v4 is single-threaded (user ≈ real).
4. Consistency: v5 has tighter variance across all runs. On CopilotKit, v5's std dev is 0.7s (20% of mean) vs v4's 4.1s (5% of mean, but absolute variation is much larger).
5. File scanning differences: v4 with --no-gitignore analyzes more files than v5 on svelte (11,672 vs 4,322) because v5's gitignore handling differs. This means v5 is even more efficient per-file analyzed than the raw speedup numbers suggest.
6. Detection accuracy: v5 finds more clones on large codebases (1,055 vs 903 on svelte, 22,487 vs 12,272 on CopilotKit), likely due to different token counting and the maxSize default behavior.
---
Rust
jscpd v5 (Rust Engine)
The Rust engine is a ground-up rewrite of jscpd. It is a drop-in replacement for the Node.js CLI — same algorithm, same reporters, same .jscpd.json config — but 24-37x faster.
The Rust engine is distributed as two npm packages:
| Package | Installs commands | Notes |
|---------|-------------------|-------|
| jscpd@5 | jscpd | Same command name as v4; drop-in CLI replacement |
| cpd | cpd | Lighter package, shorter command only |
| jscpd (crates.io) | jscpd and cpd | Rust-native install; both binaries |
All three install the identical Rust binary and accept the same CLI options. Only the crates.io install exposes both command names from a single package.
Performance
Benchmarks on macOS (Apple Silicon), 10 runs per target (3 for CopilotKit). v4 ran with --no-gitignore -i "node_modules" to ensure comparable file scanning. See performance-comparison.md for full methodology.
| Codebase | Files | Size | jscpd v4 (Node.js) | cpd/jscpd v5 (Rust) | Speedup |
|----------|-------|------|----------------------|-------------------------|---------|
| Multi-format fixtures | 548 | 1.5 MB | 1.03s | 0.03s | 34.3x |
| Svelte source | 9K | 38 MB | 15.80s | 0.43s | 36.9x |
| CopilotKit | 17K | 159 MB | 82.89s | 3.44s | 24.1x |
Installation
npm — installs the jscpd command (same binary as v4 command name)
npm install jscpd@5
jscpd /path/to/codenpm — installs only the cpd command (lighter)
npm install cpd
cpd /path/to/codecrates.io — Rust-native install (exposes both jscpd and cpd commands)
cargo install jscpd
jscpd /path/to/code
cpd /path/to/codeNix — run without installing
nix run github:kucherenko/jscpd -- /path/to/codeNix — install permanently
nix profile install github:kucherenko/jscpdHomebrew (macOS/Linux)
brew install jscpdThe npm packages ship prebuilt binaries for 6 platforms: macOS arm64/x64, Linux arm64/x64 (glibc/musl), Windows x64. No Node.js runtime is required — the binary is self-contained.
CLI Usage
The jscpd command is available after installing jscpd@5; the cpd command is available after installing either cpd (npm) or jscpd (crates.io). Both commands accept the same options and are identical:
jscpd [OPTIONS] [PATH]...
cpd [OPTIONS] [PATH]...Options
| Option | Short | Description | Default |
|--------|-------|-------------|---------|
| --min-tokens | -k | Minimum tokens in a clone | 50 |
| --min-lines | -l | Minimum lines in a clone | 5 |
| --max-lines | -x | Maximum source file lines | — |
| --max-size | -z | Skip files larger than SIZE (e.g. 1kb, 1mb, 100kb) | no limit |
| --mode | -m | Detection mode: mild, weak, strict | mild |
| --workers | | Number of worker threads for parallel tokenization/detection | auto (all CPU cores) |
| --no-colors | | Disable ANSI color output | off |
| --absolute | -a | Use absolute paths in reports | off |
| --ignore-case | | Ignore case of symbols in code (experimental) | off |
| --formats-exts | | Custom format-to-extension mapping (e.g. javascript:es,es6;dart:dt) | — |
| --formats-names | | Custom format-to-filename mapping | — |
| --cross-formats | | Detect clones across formats: ;-separated groups of ,-separated formats (e.g. javascript,typescript). Preset js-ts = javascript,jsx,typescript,tsx | — |
| --list | | List all supported formats and exit | — |
| --skip-local | | Skip clones where both fragments are in the same directory | off |
| --sarif-error-tokens | | Report SARIF results as error for clones with at least this many tokens (smaller clones stay warning). When overall duplication exceeds --threshold, all SARIF results become error regardless of size. | — (all warning) |
| --min-duplicated-lines | | Minimum percentage of duplication to report (0-100) | 0 |
| --summary | | Print a codebase summary: top files and folders by tokens, lines, size, and a complexity estimate. See Summary | off |
| --summary-top | | Number of entries in each summary top list | 10 |
| --summary-by | | Summary sort metric: tokens, lines, size, complexity | tokens |
| --silent | -s | Suppress console output | off |
| --no-tips | | Suppress tips and promotional messages | off |
| --version | -V | Print version | — |
| --help | -h | Print help | — |
Reporters
13 built-in reporters:
| Reporter | Output |
|----------|--------|
| console | Clone list + statistics table (default) |
| console-full | Clone list with source snippets; with --blame shows side-by-side author comparison |
| json | report/jscpd-report.json |
| xml | report/jscpd-report.xml |
| csv | report/jscpd-report.csv |
| html | report/jscpd-report.html |
| markdown | report/jscpd-report.md |
| badge | report/jscpd-badge.svg + report/jscpd-lines-badge.svg |
| sarif | report/jscpd-report.sarif (GitHub Code Scanning) |
| ai | Token-efficient output for LLM pipelines |
| xcode | Xcode-compatible warnings |
| threshold | Exit 1 if duplication percentage exceeds --threshold |
| silent | No console output |
Output file names differ from v4: v5 uses jscpd-report.* prefix (e.g. jscpd-report.json, jscpd-report.sarif) while v4 uses jscpd-report.json, html/ directory, etc.
Summary
--summary appends a codebase summary to the run output — the statistics jscpd already collects while scanning, aggregated to answer "where should I refactor first":
Summary (by tokens; 321 files, 129 folders analyzed)
Top files:
TOKENS LINES SIZE CX DUP% PATH
2052 363 11.4K 80 0.0 files.ts
...
Top folders:
FILES TOKENS LINES SIZE CX PATH
8 5264 843 26.5K 15 src/core
...- Top files lists the top --summary-top files ranked by the --summary-by metric. Every row carries all metrics, so re-ranking by another lens is a --summary-by size (or lines, complexity) away.
- Top folders aggregates files into their direct parent directory (each file counted exactly once; no cumulative ancestor totals).
- CX is a language-agnostic cyclomatic-complexity estimate computed from the token stream: 1 + the number of decision-point tokens (if, elif/elsif/elseif, unless, for, foreach, while, until, case, cond, when, catch, rescue, except, and, or, andalso, orelse, &&, ||, ?, ??). Matching is case-insensitive, so uppercase-keyword languages (SQL, PL/SQL, Fortran, COBOL, BASIC) count too. For folders it is the per-file mean. Languages that branch without such keywords (Smalltalk ifTrue: messages, Prolog clauses) stay at 1 — treat CX as a ranking signal, not an exact metric.
- DUP% is the share of the file's lines covered by detected clone fragments (both fragments of a clone count toward their files; display is capped at 100%).
The summary is fully opt-in and computed after detection from data already in memory, so runs without --summary are unaffected. It integrates with:
- console / console-full — the block shown above
- ai — a compact, LLM-token-efficient variant (one line per file/folder)
- json — an additive summary key in jscpd-report.json (absent when the flag is off, so the schema is unchanged for existing consumers)
Config file equivalents: "summary": true, "summaryTop": 10, "summaryBy": "tokens".
Refactoring hotspots: biggest files by tokens plus duplication share
cpd ./src --summaryAgent-friendly: compact clone list + compact summary
cpd ./src --summary --reporters ai --no-tipsFocus on the most complex files, top 5 lists, machine-readable
cpd ./src --summary --summary-by complexity --summary-top 5 --reporters jsonBlame Output
With --blame --reporters console-full, clones are displayed with a side-by-side author comparison:
176 │ Andrii Kucherenko │ <= │ 196 │ Josh Soref │ ## TODO
177 │ Andrii Kucherenko │ <= │ 197 │ Josh Soref │
180 │ Andrii Kucherenko │ == │ 200 │ Andrii Kucherenko │ ## License== means both lines were written by the same author; <= means different authors (potential copy).
Examples
Drop-in replacement for jscpd v4
jscpd /path/to/source
or
cpd /path/to/sourceSame flags as v4
cpd /path/to/source --min-tokens 30 --min-lines 3 --reporters console,json,htmlGit blame with side-by-side author comparison
cpd /path/to/source --blame --reporters console-fullList supported formats
cpd --listUse multiple reporters with custom output
cpd ./src -r console,json,sarif -o ./reportsSkip clones within the same directory
cpd --skip-local /path/to/sourceConfig File
v5 reads the same .jscpd.json config file format as v4:
{
"path": ["./src"],
"reporters": ["console", "json"],
"minLines": 5,
"minTokens": 50,
"threshold": 0,
"format": ["javascript", "typescript"],
"ignore": ["/node_modules/"],
"gitignore": true,
"mode": "mild"
}Format Support
v5 supports 223 formats (verified via --list). Use cpd --list to see the full list.
Cross-Format Detection
Vue SFC (.vue), Svelte (.svelte), Astro (.astro), and Markdown (.md) files are tokenized per-block/per-section, enabling duplicate detection across file types — same as v4.
Cross-Format Groups (--cross-formats)
By default every format is compared in its own isolated pool, so a TypeScript file never matches a near-identical JavaScript file. --cross-formats declares format equivalence groups that share one comparison pool — useful for finding leftover .js copies during a TypeScript migration:
cpd --cross-formats "javascript,typescript" ./src
cpd --cross-formats js-ts ./src # preset: javascript,jsx,typescript,tsx
cpd --cross-formats "js-ts;css,scss" ./src # multiple groupsWhen a group mixes TypeScript (typescript/tsx) with JavaScript (javascript/jsx), TypeScript files are compared with erasable type syntax stripped from the detection token stream — type annotations, generics, interface/type declarations, as/satisfies, ?/! markers, access modifiers, implements clauses, type-only imports/exports, overload signatures, and declare statements. Reported clone positions always reference the original sources.
Config file equivalents (all three shapes are accepted):
{ "crossFormats": "javascript,typescript;css,scss" }
{ "crossFormats": ["javascript,typescript", "css,scss"] }
{ "crossFormats": [["javascript", "typescript"], ["css", "scss"]] }Notes:
- TypeScript syntax with runtime semantics is not erased and will not cross-match: enum, non-declare namespace, parameter properties (constructor(private x)), import x = require(), export =.
- A cross-format clone is attributed to one member format in the per-format statistics.
- Overlapping groups are merged; groups with fewer than two formats are ignored.
Differences from jscpd v4 (Node.js)
| Feature | jscpd v4 (Node.js) | cpd v5 (Rust) |
|---------|--------------------|-----------------|
| --blame | Calls git CLI for each file | Same output (==/<= markers), calls git blame --porcelain per file |
| --store (LevelDB/Redis) | Persistent store for large repos | Not supported. Use jscpd v4.x for external stores. |
| --formats-exts | Custom format-to-extension mapping | Same flag name, same behavior |
| --formats-names | Custom format-to-filename mapping | Same flag name, same behavior |
| Programming API | jscpd() Promise API, detectClones() | Rust API via cpd-finder crate; no Node.js API |
| Config file | .jscpd.json with camelCase keys | Same — .jscpd.json with camelCase keys |
| Cross-format detection | Vue SFC, Svelte, Astro, Markdown | Same — per-block tokenization |
| Token counts | Varies by tokenizer | May differ by 1-2% due to Rust tokenizer; clone detection matches |
| --reporters | All v4 reporters | All v4 reporters except full (use console-full) |
| --no-gitignore | Default respects .gitignore | Same behavior, same flag name |
| --workers | Not available | Available — control parallelism for file tokenization/detection |
| Output filenames | jscpd-report.json, html/ directory | jscpd-report.json, jscpd-report.html, jscpd-report.sarif, jscpd-report.csv, jscpd-report.md, jscpd-badge.svg, jscpd-lines-badge.svg |
Rust API
For integration in Rust applications:
use cpd_finder::orchestrate::{RunConfig, run};let config = RunConfig {
paths: vec!["./src".into()],
min_tokens: 50,
..Default::default()
};
let result = run(&config).unwrap();
println!("Found {} clones", result.clones.len());
println!("Analyzed {} files", result.statistics.total.sources);
Architecture
cpd (CLI binary)
├── cpd-core — Detection algorithm (Rabin-Karp rolling hash)
├── cpd-tokenizer — Language tokenization (223 formats)
├── cpd-finder — File walking, orchestration, git blame
└── cpd-reporter — Output formatting (13 reporters)---
Typescript
jscpd v4 (TypeScript / Node.js)
Copy/paste detector for programming source code. The TypeScript engine runs on Node.js and is published as jscpd on npm.
Installation
npm
npm install jscpdnpx (no install required)
npx jscpd /path/to/codeCLI Usage
jscpd [options] <path ...>Options
| Option | Short | Description | Default |
|--------|-------|-------------|---------|
| --min-lines | -l | Minimum lines in a clone | 5 |
| --min-tokens | -k | Minimum tokens in a clone | 50 |
| --max-lines | -x | Maximum source file lines | 1000 |
| --max-size | -z | Maximum source file size (e.g. 1kb, 1mb) | 100kb |
| --threshold | -t | Duplication percentage threshold (exit with error if exceeded) | — |
| --config | -c | Path to config file | .jscpd.json in path |
| --ignore | -i | Glob patterns to exclude | — |
| --ignore-pattern | | Regex patterns to ignore code blocks | — |
| --reporters | -r | Reporters (comma-separated) | time,console |
| --output | -o | Output directory for file reporters | ./report/ |
| --mode | -m | Detection mode: strict, mild, weak | mild |
| --format | -f | Formats to check (comma-separated) | all detected |
| --pattern | -p | Glob pattern for file search | — |
| --blame | -b | Enrich clones with git blame author data | off |
| --silent | -s | Suppress console output | off |
| --store | | Custom store (e.g. leveldb for large repos) | memory |
| --store-path | | Directory for store cache (parallel runs) | — |
| --absolute | -a | Use absolute paths in reports | off |
| --noSymlinks | -n | Don't follow symlinks | off |
| --ignoreCase | | Ignore case of symbols (experimental) | off |
| --gitignore | | Respect .gitignore files (default: enabled) | on |
| --no-gitignore | | Don't respect .gitignore files | — |
| --colors | | Force ANSI colors even when stdout is not a TTY | — |
| --no-colors | | Disable ANSI colors in console output | — |
| --formats-exts | | Custom format-to-extension mapping (e.g. javascript:es,es6;dart:dt) | — |
| --formats-names | | Custom format-to-filename mapping (e.g. makefile:Makefile;docker:Dockerfile) | — |
| --skipLocal | | Skip clones within the same directory | off |
| --skipComments | | Alias for --mode weak (ignore comments) | off |
| --noTips | | Suppress tips and promotional messages | off |
| --exitCode | | Exit code when clones detected | — |
| --debug | -d | Show debug info, don't run detection | off |
| --verbose | -v | Show full info during detection | off |
| --list | | List all supported formats and exit | — |
| --version | -V | Print version | — |
| --help | -h | Print help | — |
Color output
Console colors are auto-detected. When neither --colors nor --no-colors is
passed, jscpd emits ANSI colors only when stdout is a TTY, so piped or captured
output (CI logs, coding agents, jscpd . > out.txt) stays free of escape
sequences. Resolution order: --colors / --no-colors (or colors: true|false
in config) → FORCE_COLOR (force on, unless 0 or false) → NO_COLOR (force
off) → TTY detection.
FORCE_COLOR is checked before NO_COLOR so that opting back in still works in
images that export NO_COLOR globally, matching Node core, chalk, and the rest
of the ecosystem.
Reporters
| Reporter | Output |
|----------|--------|
| console | Clone list with per-format statistics table |
| consoleFull | Full source snippets for each clone |
| json | report/jscpd-report.json |
| xml | report/jscpd-report.xml |
| csv | report/jscpd-report.csv |
| html | Interactive HTML report (via @jscpd/html-reporter) |
| markdown | report/jscpd-report.md |
| badge | SVG badges (via @jscpd/badge-reporter) |
| sarif | SARIF output for GitHub Code Scanning (via jscpd-sarif-reporter) |
| ai | Token-efficient output for LLM pipelines |
| xcode | Xcode-compatible warnings |
| threshold | Exit 1 if duplication exceeds --threshold |
| silent | No console output |
You can also install third-party reporters as npm packages (e.g. jscpd-full-reporter).
Config File
Create .jscpd.json in the target directory:
{
"path": ["./src"],
"reporters": ["console", "json"],
"minLines": 5,
"minTokens": 50,
"maxLines": 1000,
"maxSize": "100kb",
"threshold": 0,
"format": ["javascript", "typescript"],
"ignore": ["/node_modules/"],
"gitignore": true,
"mode": "mild",
"absolute": false,
"skipLocal": false,
"skipComments": false
}Detection Modes
| Mode | Behavior |
|------|----------|
| strict | All tokens must match (including whitespace, newlines) |
| mild | Ignore empty and newline tokens |
| weak | Ignore comments, empty tokens, and newlines (--skipComments is an alias) |
Examples
Scan current directory
jscpd .Scan specific paths with options
jscpd --min-lines 10 --min-tokens 100 --reporters console,json,html ./srcScan only TypeScript files
jscpd --format typescript --pattern "/*.ts" ./srcIgnore directories
jscpd --ignore "/dist/,/node_modules/" .Skip clones within the same folder
jscpd --skipLocal .Use LevelDB store for large repos
jscpd --store leveldb /path/to/large/repoConfigure LevelDB cache directory for parallel runs
jscpd --store leveldb --store-path /tmp/jscpd-cache /path/to/repoProgramming API
jscpd Promise API
import { IClone } from '@jscpd/core';
import { jscpd } from 'jscpd';const clones: IClone[] = await jscpd([]);
jscpd with argv
import { IClone } from '@jscpd/core';
import { jscpd } from 'jscpd';const clones: IClone[] = await jscpd(['', '', './fixtures', '-m', 'weak', '--silent']);
detectClones API
import { detectClones } from 'jscpd';const clones = await detectClones({
path: ['./src'],
silent: true,
format: ['javascript', 'typescript'],
minLines: 5,
minTokens: 50,
mode: 'mild',
});
detectClones with custom store
import { detectClones } from 'jscpd';
import { IMapFrame, MemoryStore } from '@jscpd/core';const store = new MemoryStore<IMapFrame>();
await detectClones({
path: ['./src'],
}, store);
// Re-use the store for incremental detection
await detectClones({
path: ['./src'],
silent: true,
}, store);
Building custom tools
For deep customization, compose the lower-level packages:
- @jscpd/core — Core detection algorithm, event emitter interface
- @jscpd/tokenizer — Source code tokenization (224+ formats via reprism)
- @jscpd/finder — File walking, clone detection, built-in reporters
- @jscpd/leveldb-store — LevelDB persistent store for large repos
- @jscpd/redis-store — Redis store for distributed/CI environments
Format Support
v4 supports 224 formats (verified via --list). Use jscpd --list to see the full list.
Cross-Format Detection
Vue SFC (.vue), Svelte (.svelte), Astro (.astro), and Markdown (.md) files are tokenized per-block/per-section, enabling duplicate detection across file types (e.g., a <script> block in a .vue file matching a .ts file).
Shebang Detection
Extensionless executable scripts are auto-detected by their shebang line (supports bash, python, node, ruby, perl, php, lua, tcl, R, groovy, swift, kotlin).
Custom Format Mapping
Map extensions to formats
jscpd --formats-exts "javascript:es,es6;dart:dt" ./srcMap specific filenames to formats
jscpd --formats-names "makefile:Makefile,GNUmakefile;docker:Dockerfile" ./srcArchitecture
jscpd (CLI + API)
├── @jscpd/core — Detection algorithm (Rabin-Karp), event system
├── @jscpd/tokenizer — Source code tokenization (224+ formats via reprism)
├── @jscpd/finder — File walking, orchestration, built-in reporters
├── @jscpd/html-reporter — Interactive HTML report
├── @jscpd/badge-reporter — SVG badge generation
├── @jscpd/sarif-reporter — SARIF for GitHub Code Scanning
├── @jscpd/leveldb-store — LevelDB persistent store
└── @jscpd/redis-store — Redis distributed store---
CHANGELOG
Changelog
All notable changes to jscpd are documented here. Releases follow Semantic Versioning.
---
5.0.15
New Features
- SARIF: size-based severity — new --sarif-error-tokens <N> flag (also sarifErrorTokens in .jscpd.json): clones with at least N tokens are reported at level error while smaller ones stay warning. When overall duplication exceeds --threshold, all SARIF results are emitted as error. (#908)
- SARIF: clone fingerprints — each result carries token_count, a clone_hash, and a partialFingerprints entry (jscpdCloneHash/v1) for cross-run result identity in consumers like GitHub code scanning. (#909)
- SARIF: related-location messages — the duplicate's counterpart location now has a message linked from the primary message, so GitHub code scanning displays it. (#911)
- SARIF: richer rule metadata — display name, full description, default configuration, and quality tags on the jscpd/duplicate-code rule. (#914)
Bug Fixes
- Scan-root-relative report paths — report paths are relative to the scanned directory again (as in 4.x) while reporters can still resolve source files; fixes empty snippets and unresolvable paths when scanning from outside the target directory, including multi-root scans. (#872, #892)
- Report version stamping — SARIF tool.driver.version and the HTML report version now match --version. (#915)
- Multi-root blame attribution — git blame data is keyed by resolved path, so a second scan root no longer inherits the first root's authors
- Git root discovery — walking up from a relative scan path no longer terminates before reaching the repository root
Thank You ❤️
- @chrisc-onaorg for the SARIF fingerprints, related-location messages, and rule metadata (#910, #912, #914)
- @darronz for the scan-root-relative paths fix (#913)
- @nvuillam for proposing size-based SARIF severity (#908)
---
5.0.14
New Features
- --cross-formats — detect clones across related formats via format equivalence groups sharing one comparison pool, e.g. --cross-formats "javascript,typescript" or the js-ts preset. When a group mixes TypeScript with JavaScript, TS files are compared with erasable type syntax stripped. Also configurable as crossFormats in .jscpd.json / package.json. (#810)
Bug Fixes
- Prose-only Markdown files are now analyzed — .md files without code fences previously produced zero tokens and were silently skipped; prose is now tokenized, while embedded code fences keep their own sub-format pools. (#883)
---
5.0.13
npm-only release: republished the cpd package so its optionalDependencies point at the 5.0.12 platform binaries. No code changes.
---
5.0.12
Dependencies
- Rust dependency updates (askama 0.16.0, log 0.4.33, env_logger 0.11.11, rustc-hash 2.1.3)
---
5.0.11
New Features
- Razor (.razor) support — new tokenizer for Razor files in the Rust backend (thanks to @chrisc-onaorg in #829)
Dependencies
- cpd-core bumped to 0.1.6, cpd-tokenizer bumped to 0.1.7
---
5.0.10
Bug Fixes
- Emit scan-root-relative paths in all reporters when absolute: false. Fixes #827
- Fix --skip-local to match jscpd v4 TypeScript semantics
Refactoring
- DRY duplication in reporters: extract shared helpers into cpd-reporter/src/shared.rs
- Move blame enrichment from gitoxide to git blame --porcelain
---
5.0.9
New Features
- GitHub Action for jscpd (Rust v5) — jscpd-copy-paste-detector action for GitHub Actions Marketplace. Scan your repo for copy/paste in CI with uses: kucherenko/jscpd/.github/workflows/action.yml@v5
Bug Fixes
- Resolve platform binary resolution when cpd is installed as a nested dependency (e.g. in a project's node_modules via a parent package). The runner now correctly locates the platform-specific binary relative to the installed package rather than assuming a top-level install. Fixes #816
---
5.0.8
Bug Fixes
- Prevent mmap exhaustion crashes when scanning repositories with more files than vm.max_map_count (default 131 072 on Linux). The walker previously held a live Mmap per discovered file; each rayon worker now opens and drops its mapping within the processing closure, capping concurrent mappings to the thread-pool size (typically 8–32). Fixes #813
- Fix --pattern not matching relative paths when the scan root is absolute (e.g. CWD). Patterns like src//.ts now match correctly by comparing against both the relative path and the full absolute path, and bare patterns like .ts gain a / prefix to match at any depth. Fixes #811
- Fix trailing-newline off-by-one in line-count filter: files not ending with \n now count the final line correctly
---
5.0.7
Bug Fixes
- Prevent stack overflow when scanning directories containing deeply-nested JS/TS files (e.g. Bun's test/bundler with 320K+ nested for-loops). OXC's recursive-descent parser allocates one stack frame per AST nesting level; pathological inputs now exceed the default 8 MiB thread stack. Fixed by building a local rayon ThreadPool with 64 MiB stacks instead of using the global pool (which silently fails on re-init)
- Default --max-size to 1mb — files exceeding the limit are skipped at walk time, consistent with jscpd v4's maxSize behavior. This prevents OXC from ever seeing megabyte-scale generated files that would overflow the stack
- --workers N now correctly takes effect on every run() call (previously build_global() silently no-op'd after the first invocation)
---
5.0.6
New Features
- v4 config backward compatibility — .jscpd.json fields path, pattern, ignore, and ignorePattern are now read and applied, matching jscpd v4 behavior
- ignore and ignorePattern are now distinct: ignore matches file-level globs, ignorePattern matches code-level regex patterns (previously conflated)
- .jscpd.json path config support — reads scan directories from the path field, resolving relative paths against the config file's directory
- jscpd npm wrapper package — publishes the same Rust binary under the jscpd name on npm with v5.x versioning
- --exit-code now matches v4 behavior: accepts optional integer value (--exit-code exits 1, --exit-code 2 exits 2); --threshold and --exit-code are now independent
- Performance improvements: memory-mapped file I/O (via memmap2) eliminates heap copies of file contents; SIMD-accelerated line counting (via memchr); parallel detection pipeline uses flat_map to avoid intermediate allocations; JS tokenizer no longer clones source strings before parsing (thanks to @auterium, #808)
Bug Fixes
- Fixed --exit-code to match jscpd v4's --exitCode behavior (was boolean, now optional integer)
- Fixed unique temp dir generation in reporter tests (added PID to prevent race conditions under parallel test runners)
---
5.0.4
New Features
- CLI alignment with jscpd v4: new --absolute, --ignore-case, --formats-exts, --formats-names flags; fixed --threshold, improved --max-size
- Detection and statistics aligned with jscpd for consistent output across Rust and TypeScript versions
- Side-by-side blame comparison in console-full reporter
- Clone list display in console reporter
Bug Fixes
- HTML reporter now outputs jscpd-report.html at the output_dir root
- Resolved all clippy warnings across workspace
- Fixed unique temp dir generation in tests (use as_nanos() instead of subsec_nanos())
---
5.0.3
New Features
- Rust-based cpd CLI with full feature parity to TypeScript jscpd
- Cross-platform binary distribution via npm platform packages (linux-x64-gnu, linux-arm64-gnu, linux-x64-musl, darwin-arm64, darwin-x64, windows-x64-msvc)
- 13 reporters: json, console, xml, csv, html, markdown, sarif, ai, badge, xcode, threshold, silent, console-full
- Time reporter for execution timing
- CLI short-form aliases matching TypeScript jscpd conventions
- ReportContext data structure for extensible reporter signatures
- Trusted Publishing support for crates.io via OIDC
---
5.0.2
Bug Fixes
- Fixed Vue SFC tokenization to dispatch each block to its own sub-format
- Fixed entire-file duplicates silently dropped by RabinKarp store flush logic
- Fixed ReDoS hang on Lisp/Elisp files
- Fixed crash on malformed package.json when reading config
---
5.0.1
New Features
- Initial Rust workspace with cpd-core, cpd-tokenizer, cpd-finder, cpd-reporter, and jscpd crates
- Cross-format detection for Vue SFC, Svelte, Astro, and Markdown files
- Shebang detection for extensionless scripts
---
5.0.0
Breaking Changes
- First stable Rust release — replaces the TypeScript-based CLI with a native binary
- Reporter trait signature changed to use ReportContext instead of Statistics directly
---
4.3.0 — 2026-08-13
New Features
- Color auto-detection — ANSI colors are disabled automatically when stdout is not a TTY (piped or redirected output), with new --colors / --no-colors flags and a colors config key to override. Precedence: explicit flag/config → FORCE_COLOR → NO_COLOR → TTY detection. The statistics table is covered too. (#893, #899)
- jscpd-server: MCP protocol revision 2026-07-28 — migrated to the official MCP SDK v2 with the stateless 2026-07-28 revision and a legacy fallback for 2025-era clients, DNS-rebinding protection (Origin/Host allowlists with --allowed-origin/--allowed-host) on both /mcp and the REST API, a loopback default bind (127.0.0.1), and a hardened start/stop lifecycle. (#902)
Bug Fixes
- consoleFull no longer prints clones twice — the progress announcer is skipped for reporters that print every clone themselves (ai, consoleFull); jscpd-server shares the same wiring. (#900)
- Inclusive source line counts — fixes off-by-one line counts in statistics and reports. (#881)
- jscpd-server log colors — server output now respects NO_COLOR/FORCE_COLOR and TTY detection like the CLI.
Security
- Resolved all 27 open Dependabot alerts on transitive dependencies (fast-uri, hono, js-yaml, brace-expansion, nanoid, postcss, ip-address, body-parser and others) by restoring the pnpm override mechanism — overrides now live in pnpm-workspace.yaml, where pnpm 10 actually reads them. CI installs use --frozen-lockfile so lockfile drift fails loudly.
Thank You ❤️
This release was shaped by community contributions — huge thanks to:
- @suzunn for the color auto-detection (#899), the consoleFull double-print fix (#900), and the lockfile repair (#901)
- @anxkhn for the MCP 2026-07-28 server migration and its security hardening (#902)
- @9904099 for the inclusive line-count fix (#881) and repository metadata cleanups
- @kfstorm for reporting the non-TTY color issue (#893) and @maxpatiiuk for the consoleFull improvement request (#652)
---
4.2.5 — 2026-06-07
Bug Fixes
- JSON reporter duplicate token counts — tokens was always reported as 0 in JSON output; now computed from token positions (end.position - start.position) (#801).
- Gitignore parent-directory walk — .gitignore files in parent directories up to the repo root are now read and combined with scan-directory .gitignore files. Also reads .git/info/exclude and the global core.excludesFile for full parity with Git's ignore resolution (#741).
- Commander v15 migration — CLI option parsing migrated from direct property access (cli.minTokens, etc.) to the cli.opts() API required by Commander v8+. The --no-gitignore / --gitignore flag handling was rewritten to use Commander's native negation support instead of rawArgs inspection.
- Vitest 4.1.0 — bumped from 3.2.4 to address CVE-2026-47429.
- Commander v15 — bumped from v5 to v15, enabling modern Node.js compatibility.
- Pug 3.0.4, node-sarif-builder 4.1.0, nodemon 3.1.14 — dependency bumps for security and compatibility.
---
4.2.0 — 2026-05-14
Breaking Changes
- Vue SFC tokenization — .vue files are no longer tokenized as markup. Each block is now dispatched to its own sub-format: <script> → javascript, <script lang="ts"> → typescript, <template> → markup, <style> → css, <style lang="scss"> → scss, <style lang="less"> → less. Clone reports for .vue files now appear under these resolved sub-format names. Any tooling or configuration that relied on .vue clones being reported under markup must be updated.
- --formatsExts users — custom mappings that pointed .vue to markup (e.g. "formatsExts": { "markup": ["vue"] }) will no longer take effect because .vue is handled by the dedicated vue format processor. Remove or update such mappings.
New Features
- Custom tokenizer backend — replaced the prismjs npm package with a self-contained reprism-based grammar engine. ~11.5% faster tokenization on real projects (avg 1126 ms → 997 ms on a 548-file, 223-format scan).
- Cross-format detection — Vue SFC (.vue), Svelte (.svelte), Astro (.astro), and Markdown files are now tokenized per-block/per-section. A <script> block in a .vue file can match a .ts file; a fenced code block in Markdown can match a .py file.
- 223 supported formats — Apex, CFML/ColdFusion, GDScript, Svelte, Astro, and 70+ additional languages added (up from 152). See FORMATS.md.
- Shebang detection — extensionless executable scripts (e.g. /usr/bin/env python3) are auto-detected by their #! shebang line and tokenized in the correct language.
- --store-path — configure a custom directory for the LevelDB cache, eliminating collisions when multiple jscpd processes run in parallel on the same machine.
- --skipComments — shorthand flag for --mode weak, which strips comments before detection.
- --formats-names — map specific filenames (e.g. Makefile, Dockerfile) to a detection format.
Bug Fixes
- Entire-file duplicates silently dropped (@jscpd/core #728) — RabinKarp flushed the pending clone on a store hit at end-of-file instead of on a miss. Files that are complete copies of each other were undetected. Fixed.
- ReDoS hang on Lisp/Elisp files (@jscpd/tokenizer #737) — the Lisp string regex /"(?:[^"\\]|\\.)"/ could catastrophically backtrack (O(2ⁿ)) on unterminated strings. Replaced with a linear /"(?:[^"\\]|\\[\s\S])*"/ pattern.
- Process crash on malformed package.json (#739) — readJSONSync threw an unhandled SyntaxError when package.json contained invalid JSON, killing the process. Now emits a warning and continues with an empty config.
- Vue SFC cross-file detection broken — the detector used the file-level format (vue) as the store namespace for all SFC blocks, preventing a <script> block in one .vue file from ever matching a <script> block in another. The namespace now reflects each block's resolved sub-format.
- Vue SFC incorrect column numbers — tokens on the first line of a block carried block-relative column 1 instead of file-absolute column numbers. Fixed in @jscpd/tokenizer.
- 50 dependency security vulnerabilities remediated across the monorepo (Dependabot batches).
Known Limitations
- Malformed SFC blocks (e.g. unclosed tags, invalid attributes) are silently skipped and do not contribute tokens.
---
4.1.0 — 2026-05-09
New Features
- AI Reporter — new ai reporter that produces compact, token-efficient clone output specifically designed for feeding results into language models and AI tooling. Use --reporters ai to activate it.
- MCP Server enhancements — the Model Context Protocol server now exposes a jscpd://statistics resource and supports a recheck endpoint so AI agents can trigger a rescan without restarting the process.
- Apex & CFML language support — jscpd can now detect duplicate code in Salesforce Apex and ColdFusion Markup Language (CFML) files (closes #83, #619).
- GDScript support — detect copy-paste duplication in Godot Engine GDScript files.
- HTML reporter footer — the HTML report now displays a branded footer with the jscpd version and a sponsor link.
- --noTips flag — suppress the usage-tip messages that appear after a detection run.
- CI: Node.js 22.x / 24.x — continuous integration updated to test against the latest Node.js LTS and current releases.
Performance
- Tokenizer — grammars are now loaded lazily, hot paths are O(n), and the spark-md5 dependency has been removed in favour of a lighter built-in implementation. Startup time and memory usage are noticeably reduced on large codebases.
- Replaced the vendored reprism syntax library with the official prismjs npm package, shrinking the installed footprint.
Bug Fixes
- Restored the correct start.line expectation for weak-mode clone detection.
---
4.0.7 — 2026-01-11
New Features
- jscpd-server — a new jscpd-server package ships a RESTful HTTP API for code-duplication detection. Ideal for CI pipelines, IDE plugins, and services that need on-demand analysis without spinning up a CLI process.
- GitHub Actions example — an example_github_action.yml starter workflow is included in the repository.
Bug Fixes
- Ignore patterns defined in configuration files are now applied correctly (the path-matching bug in resolveIgnorePattern has been fixed).
- Importing jscpd as a Node.js module no longer auto-executes the CLI entry point.
- Fixed an invalid documentation link.
---
4.0.6 — 2026-01-11
Bug Fixes
- Dependency and lock-file updates to address security advisories.
---
4.0.5 — 2024-07-03
New Features
- SARIF reporter — jscpd now supports the SARIF output format (Static Analysis Results Interchange Format), making it easy to integrate reports with GitHub Code Scanning and other SARIF-aware tooling. Use --reporters sarif.
Bug Fixes
- Fixed TypeScript type-declaration generation for the jscpd app package.
- Fixed colors being a missing runtime dependency in the SARIF reporter.
---
4.0.0 — 2024-05-26
Breaking Changes
- Monorepo restructured — packages have been reorganised and renamed. If you import sub-packages directly (e.g. @jscpd/core, @jscpd/finder) please review the updated package names and paths.
- Build system replaced — switched from the old TypeScript compiler pipeline to tsup-node, which produces cleaner ESM/CJS dual-mode bundles.
- Test framework migrated — tests are now powered by Vitest instead of the previous runner.
Highlights
This is a major release that brings the entire jscpd ecosystem up to modern tooling standards. The public API remains largely compatible, but the internal architecture, package layout, and build artefacts have changed significantly.
---
3.5.10 — 2023-09-17
Maintenance
- Updated dependencies that had known issues.
- Added a dependabot.yml configuration to keep dependencies up to date automatically.
---
3.5.9 — 2023-05-02
Bug Fixes
- Fixed an issue where files that had not been published were incorrectly processed.
---
3.5.8 — 2023-05-01
Bug Fixes
- Fixed the HTML reporter build script that was producing broken output.
---
3.5.7 — 2023-05-01
Bug Fixes
- Fixed a crash that occurred when a path specified for HTML reporting did not exist.
---
3.5.6 — 2023-05-01
Bug Fixes
- Fixed a missing-dependency error in the HTML reporter.
---
3.5.5 — 2023-04-27
Maintenance
- Updated the blamer dependency to its latest version.
---
3.5.4 — 2023-03-24
New Features
- pre-commit hook support — a .pre-commit-hooks.yaml file is now included so jscpd can be used as a pre-commit hook with zero extra configuration.
---
3.5.3 — 2022-12-15
Maintenance
- Upgraded the Vue.js version used by the HTML report viewer.
---
3.5.2 — 2022-10-24
Bug Fixes
- Fixed incorrect HTML escaping in code snippets shown in reports.
---
3.5.1 — 2022-10-24
New Features
- Modern JS/TS module extensions — jscpd now detects duplicates in .mjs, .cjs, .mts, and .cts files out of the box.
Bug Fixes
- Ensure that ignore patterns specified in configuration files are respected even when not passed on the command line.
---
3.5.0 — 2022-10-01
New Features
- HTML reporter redesigned — the HTML report has been rebuilt as a standalone page, removing the Vue.js SPA dependency and making it simpler to open and share.
Bug Fixes
- Fixed symlink detection so symlinked files are correctly handled when --noSymlinks is set.
- Fixed HTML tag escaping in code blocks within the HTML report (rendering issues when code contained < / > characters).
- Dropped the unused constructor that was causing a minor overhead at startup.
---
3.4.5 — 2022-01-10
Bug Fixes
- Pinned colors to v1.4.0 to avoid the intentionally broken [email protected] release that caused console output corruption.
---
3.4.2 — 2021-11-06
Bug Fixes
- Fixed the exit callback not being invoked when duplicates were detected.
---
3.4.0 — 2021-11-06
New Features
- --exitCode option — you can now configure the exit code that jscpd returns when duplicates are found, making it easier to integrate into pipelines that use non-zero exit codes to signal failures.
- --ignore-pattern option — supply a glob or regex pattern to exclude matching code fragments from detection (closes #435).
---
3.3.26 — 2021-05-23
Bug Fixes
- Silent mode is now truly silent — no output is produced when --silent is used.
Security
- Bumped several transitive dependencies (hosted-git-info, handlebars, url-parse, ssri, y18n) to patched versions to close known vulnerabilities.
---
3.3.25 — 2021-03-04
Maintenance
- Bumped pug to v3.0.1 (security fix).
---
3.3.24 — 2021-02-27
Bug Fixes
- Fixed a tokenizer bug that caused incorrect source-location calculation.
- Fixed a crash when calculateLocation() received an empty array.
---
3.3.23 — 2020-12-13
Bug Fixes
- Added TAP format support so jscpd can now detect copy-paste in TAP (Test Anything Protocol) files.
- Fixed a crash that occurred when an unsupported language was encountered instead of silently skipping it.
---
3.3.22 — 2020-12-01
New Features
- Badge reporter — generates a jscpd shield badge (SVG/URL) showing your project's duplication percentage. Drop it straight into your README.
---
3.3.21 — 2020-11-20
Bug Fixes
- Fixed a crash that occurred when the clone list was empty.
---
3.3.20 — 2020-11-20
Bug Fixes
- Fixed a crash that occurred when the source list was empty.
---
3.3.19 — 2020-09-01
Bug Fixes
- Fixed the coverage report output.
- Removed cyclic package dependencies that caused intermittent build failures.
---
3.3.17 — 2020-08-30
New Features
- CSV and Markdown reporters — two new output formats for jscpd reports. Use --reporters csv or --reporters markdown to generate spreadsheet-friendly or documentation-ready output.
- Duplicated lines and tokens in HTML report — the HTML report now shows both the number of duplicated lines and the token count for each clone, giving you more context at a glance.
- Ability to persist the detection store — the store can now be saved between runs, enabling incremental analysis on large codebases.
- Redis store — an optional Redis-backed store (@jscpd/redis-store) is available for teams that want a shared, persistent store across multiple machines or CI agents.
- New programmatic API — detectClones() and related helpers are now properly exported, making it straightforward to embed jscpd in your own tooling.
- Xcode reporter — outputs results in the format Xcode's Issue Navigator understands, useful for Swift/Objective-C projects.
- File-search glob pattern — you can now pass a glob pattern to control which files are scanned.
Bug Fixes
- Fixed a bug with empty files being processed incorrectly.
- Fixed filenames being escaped incorrectly in XML output.
- Fixed an empty token-map payload in event hooks.
- Fixed wrong exit codes in some edge cases.
- Fixed a SQL grammar tokenization issue.
- Fixed the path option not being resolved correctly.
---
3.3.14 — 2020-08-20
New Features
- Improved HTML reporter — internal refactor to optimise language loading and tokeniser performance. The HTML report includes more detailed clone statistics.
---
3.3.1 — 2020-07-27
New Features
- Migrated the project to a monorepo structure, splitting functionality into focused packages (@jscpd/core, @jscpd/finder, @jscpd/html-reporter, etc.).
- Added Node.js 14 to the CI matrix.
Bug Fixes
- Fixed the HTML reporter producing broken output in some configurations.
---
3.2.1 — 2020-04-18
Bug Fixes
- Used fs-extra v8.0.0 for compatibility with Node.js v8 (closes #346, #345).
---
3.2.0 — 2020-04-08
New Features
- --skipLocal flag — skip duplicates that exist only within the same folder, reducing noise in reports for projects that intentionally have similar files in isolated directories (closes #326).
Bug Fixes
- Updated cli-table3 to v0.6.0.
- Updated fs-extra to v9.0.0.
---
3.1.0 — 2020-03-11
New Features
- Plain-text file support — jscpd can now detect duplicates in .txt files (closes #272).
---
3.0.1 — 2020-03-10
Bug Fixes
- Fixed incorrect usage of the blamer module (closes #238).
- Updated blamer to v1.0.1.
---
3.0.0 — 2020-03-08
Breaking Changes
- XML reporter — the CDATA format in the XML report has changed to fix a correctness issue (closes #331). Tools that parse the XML output may need updating.
Bug Fixes
- Updated commander to v4.0.1.
- Updated level to v6.0.0.
- Fixed CDATA handling in the XML reporter.
Changes
- Updated the CLI entry script for running jscpd.
---
2.0.16 — 2019-09-24
Bug Fixes
- Updated several dependencies to close known security vulnerabilities (commander, eventemitter3, fs-extra, rimraf, snyk).
- Fixed a typo and a broken screenshot URL in the README.
- Fixed failing test snapshots.
---
2.0.15 — 2019-04-24
Bug Fixes
- Updated level to v5.0.1.
---
2.0.14 — 2019-04-18
Bug Fixes
- Fixed a crash in the Prism tokenizer caused by a language entry with an empty name (closes #223).
---
2.0.13 — 2019-03-29
Bug Fixes
- Fixed empty-statistic display in the HTML reporter (closes #214).
---
2.0.4 — 2019-01-08
Bug Fixes
- Split C/C++ and C/C++ header formats so that header files (.h, .hpp) are now tokenised separately from source files. This prevents spurious matches across file types (closes #188).
---
2.0.3 — 2019-01-08
Bug Fixes
- Fixed a bug where duplicates within a single file were not detected correctly (closes #189).
---
2.0.2 — 2018-12-28
Bug Fixes
- Replaced GPL-licensed packages with MIT-licensed equivalents.
---
2.0.1 — 2018-12-28
Bug Fixes
- The --threshold option now accepts 0 as a valid value (closes #182).
---
2.0.0 — 2018-12-28
Breaking Changes
- Persistent store — jscpd now uses LevelDB as its default store to keep memory usage low on very large codebases. The in-memory store from v1.x is no longer the default (closes #66, #184).
---
1.2.3 — 2018-12-27
Bug Fixes
- Fixed a bug with files that use multiple format extensions (e.g. .html.erb).
---
1.2.1 — 2018-12-23
Bug Fixes
- Fixed an unhandled promise rejection in the blamer module (closes #185).
---
1.2.0 — 2018-12-14
New Features
- Graph view in HTML report — the HTML report now includes an interactive graph showing clone relationships between files.
Bug Fixes
- Fixed empty lines being rendered incorrectly in HTML code blocks.
---
1.1.0 — 2018-12-02
New Features
- Blamed lines in reports — the html and consoleFull reporters now show Git blame information alongside duplicate code, so you know who introduced each clone and when.
- Syntax highlighting in the HTML reporter.
- Custom mode — a new custom detection mode that lets you tune detection behaviour beyond the built-in strict and weak presets (closes #172).
---
1.0.3 — 2018-11-27
Bug Fixes
- Fixed the --path option not being applied correctly (closes #177).
---
1.0.2 — 2018-11-27
Bug Fixes
- Added support for locally installed reporters and modes (installed in the project's node_modules rather than globally).
---
1.0.1 — 2018-11-27
Bug Fixes
- Added support for trailing-slash patterns in .gitignore-style ignore files.
---
1.0.0 — 2018-11-21
First stable release of the fully rewritten jscpd. The tool was migrated from CoffeeScript to TypeScript, the tokenizer was redesigned from scratch, and a new pluggable reporter system was introduced.
---
Earlier Pre-releases (1.0.0-rc.x, 1.0.0-alpha.x) — 2018
These releases established the current architecture during active development:
- 1.0.0-rc.6 — HTML reporter added.
- 1.0.0-rc.4 — CLI supports multiple path arguments; hooks system introduced; reporter interface redesigned.
- 1.0.0-rc.1 — Execution-timer reporter added.
- 1.0.0-alpha.2 — Configuration file name finalised.
- 1.0.0-alpha.1 — CLI binary script added.
- 1.0.0-alpha.0 — Initial TypeScript rewrite: new tokenizer, XML/JSON/statistic/threshold/silent reporters, YAML language support, cache for detection results, and a --debug option.
---
README
jscpd
[](https://www.npmjs.com/package/jscpd)
[](https://crates.io/crates/jscpd)
[](https://github.com/kucherenko/jscpd/actions/workflows/nodejs.yml)
Copy/paste detector for programming source code. Supports 224+ formats. AI-ready with MCP server and token-efficient reporter. Now with a Rust-powered engine — 24-37x faster.
jscpd implements the Rabin-Karp algorithm to find duplicated code blocks across files.
Quick Start
Install (all platforms — installs the jscpd command)
curl -fsSL https://jscpd.dev/install.sh | bashTypeScript engine (Node.js, v4.x)
npm install jscpd@4
jscpd /path/to/code
or use without installing
npx jscpd@4 /path/to/codeRust engine (v5.x, 24-37x faster) — installs the jscpd command
npm install jscpd@5
jscpd /path/to/codeRust engine — cpd command only
npm install cpd
cpd /path/to/codeRust-native install (exposes both jscpd and cpd)
cargo install jscpdNix (installs both jscpd and cpd)
nix run github:kucherenko/jscpd -- /path/to/code
or install permanently
nix profile install github:kucherenko/jscpdHomebrew (macOS/Linux)
brew install jscpdDocumentation
| Document | Description |
|----------|-------------|
| TypeScript (v4.x) | Node.js engine — CLI, reporters, config, detection modes |
| Rust (v5.x) | Rust engine — installation, CLI, reporters, blame, Rust API |
| AI-Ready | AI reporter, agent skills, MCP server |
| Programming API | TypeScript and Rust programmatic APIs |
| CI & Pre-Commit Hooks | GitHub Action, pre-commit hooks |
| Packages | Monorepo package and crate overview |
Two Engines
| | TypeScript (v4) | Rust (v5) |
|---|---|---|
| npm package | jscpd@4 | jscpd@5 or cpd |
| CLI command | jscpd | jscpd (from jscpd@5) or cpd (from cpd) |
| Speed | Baseline | 24-37x faster |
| Formats | 224 | 223 |
| Node.js required | Yes | No (self-contained binary) |
| Programming API | TypeScript (jscpd(), detectClones()) | Rust (cpd-finder crate) |
| LevelDB store | Yes | No |
| Reporters | 13 | 13 |
jscpd@5 installs the jscpd command. The cpd npm package installs the cpd command. Both contain the same Rust binary. For both command names from a single install, use crates.io: cargo install jscpd.
What's New
v5.0.x — Rust Engine
jscpd v5 is a ground-up Rust rewrite that ships as jscpd@5 (installs the jscpd command) or cpd (installs the cpd command). Self-contained binary — no Node.js runtime required.
Same interface, 24-37x faster:
- All CLI options from v4 are preserved — drop-in replacement: jscpd → jscpd@5
- Same .jscpd.json config file, same detection algorithm, same reporters
- 223 language formats with cross-format detection (Vue SFC, Svelte, Astro, Markdown)
New in v5:
- 24-37x faster detection on real projects (see benchmark)
- Small codebases (548 files): 34x faster
- Medium codebases (9K files): 37x faster
- Large codebases (17K files, 900 MB): 24x faster
- Git blame with side-by-side author comparison (--blame --reporters console-full)
- --workers — control parallelism for file tokenization and detection (default: auto, uses all CPU cores; not available in v4)
- 13 reporters: console, console-full, json, xml, csv, html, markdown, badge, sarif, ai, xcode, threshold, silent
- AI reporter — token-efficient output for LLM pipelines (~79% fewer tokens than console)
- --summary — codebase summary: top files and folders by tokens, lines, size, and a complexity estimate — refactoring hotspots straight from the scan (see docs)
- Self-contained binary — prebuilt for 6 platforms (macOS arm64/x64, Linux arm64/x64, Windows x64)
Not yet in v5 (use v4 for these):
- LevelDB/Redis stores (--store leveldb)
- Node.js programming API (jscpd(), detectClones())
See Rust docs for the full CLI reference and differences from v4.
v4.2.x — TypeScript Engine
- Custom tokenizer backend — replaced prismjs with own backend built on reprism. ~11.5% faster tokenization on real projects
- Cross-format detection — Vue SFC, Svelte, Astro, and Markdown tokenized per-block, enabling detection across file types
- New formats: Apex, CFML/ColdFusion, GDScript, and 70+ additional formats (224 total, up from 152)
- Shebang detection — auto-detect language for extensionless scripts
- --store-path — configure LevelDB cache directory for parallel runs
- --skipComments — shorthand for --mode weak
- --formats-names — map filenames (e.g. Makefile, Dockerfile) to formats
- --noTips — suppress tip output in CI
- Bug fixes: entire-file duplicates silently dropped (#728), ReDoS on Lisp/Elisp files (#737), process crash on malformed package.json (#739), Vue SFC cross-file detection (#737), Vue SFC column numbers (#737), 50 dependency security vulnerabilities
See TypeScript docs for the full CLI reference.
Packages
| Package | Description |
|---------|-------------|
| jscpd | CLI and Node.js API (v4.x) |
| jscpd-server | REST API + MCP server |
| @jscpd/core | Core detection algorithm |
| @jscpd/finder | File detection, reporters |
| @jscpd/tokenizer | Source code tokenization |
| @jscpd/html-reporter | HTML report |
| @jscpd/badge-reporter | SVG badge |
| jscpd-sarif-reporter | SARIF (GitHub Code Scanning) |
| @jscpd/leveldb-store | LevelDB persistent store |
| @jscpd/redis-store | Redis distributed store |
| cpd (Rust engine) | Rust-powered engine (v5.x) — also available as jscpd@5 |
Who Uses jscpd
- GitHub Super Linter — official GitHub linter aggregator, bundles jscpd as its copy/paste detector
- Codacy — automated code analysis platform, jscpd powers the duplication engine
- MegaLinter — 100% open-source linter aggregator for CI, integrates jscpd
- OpenClaw — personal AI assistant for self-hosted devices
- Natural — NLP library for Node.js, uses jscpd for code quality
Performance
Benchmarked on macOS (Apple Silicon), 10 runs per target (3 for CopilotKit). v4 ran with --no-gitignore -i "node_modules" to ensure comparable file scanning.
| Target | Files | Size | jscpd v4 | jscpd v5 | Speedup |
|--------|-------|------|----------|----------|---------|
| fixtures | 548 | 1.5 MB | 1.03s | 0.03s | 34.3x |
| svelte | 9K | 38 MB | 15.80s | 0.43s | 36.9x |
| CopilotKit | 17K | 159 MB | 82.89s | 3.44s | 24.1x |
See performance-comparison.md for full methodology and raw data.
AI-Ready Features
jscpd integrates into AI-powered workflows through three mechanisms:
AI Reporter
Token-efficient output for LLM pipelines (~79% fewer tokens than the default console reporter):
jscpd --reporters ai /path/to/source # v4
cpd --reporters ai /path/to/source # v5
cpd --reporters ai --summary /path/to/source # v5: + compact codebase summaryAgent Skills
Two installable skills that teach AI coding assistants how to use jscpd and refactor detected duplications:
| Skill | Purpose | Install |
|-------|---------|---------|
| jscpd | Tool reference — CLI options, AI reporter format, config syntax | npx skills add kucherenko/jscpd --skill jscpd |
| dry-refactoring | Guided refactoring workflow — read clones, choose strategy, apply, verify | npx skills add kucherenko/jscpd --skill dry-refactoring |
After installation, ask your agent to "find and fix code duplication" and it will invoke jscpd with the right options and act on the results.
See AI-Ready docs for full details.
Contributing
1. Fork the repo kucherenko/jscpd
2. Clone forked version (git clone https://github.com/{your-id}/jscpd)
3. Install dependencies (pnpm install)
4. Run in dev mode: pnpm dev
5. Add your changes
6. Add tests and check: pnpm test
7. Build: pnpm build
8. Create PR
Backers
Thank you to all our backers! 🙏 [Become a backer]
<a href="https://opencollective.com/jscpd#backers" target="_blank"><img src="https://opencollective.com/jscpd/backers.svg?width=890"></a>
Sponsors
Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [Become a sponsor]
<a href="https://opencollective.com/jscpd/sponsor/0/website" target="_blank"><img src="https://opencollective.com/jscpd/sponsor/0/avatar.svg"></a>
<a href="https://opencollective.com/jscpd/sponsor/1/website" target="_blank"><img src="https://opencollective.com/jscpd/sponsor/1/avatar.svg"></a>
<a href="https://opencollective.com/jscpd/sponsor/2/website" target="_blank"><img src="https://opencollective.com/jscpd/sponsor/2/avatar.svg"></a>
<a href="https://opencollective.com/jscpd/sponsor/3/website" target="_blank"><img src="https://opencollective.com/jscpd/sponsor/3/avatar.svg"></a>
<a href="https://opencollective.com/jscpd/sponsor/4/website" target="_blank"><img src="https://opencollective.com/jscpd/sponsor/4/avatar.svg"></a>
<a href="https://opencollective.com/jscpd/sponsor/5/website" target="_blank"><img src="https://opencollective.com/jscpd/sponsor/5/avatar.svg"></a>
<a href="https://opencollective.com/jscpd/sponsor/6/website" target="_blank"><img src="https://opencollective.com/jscpd/sponsor/6/avatar.svg"></a>
<a href="https://opencollective.com/jscpd/sponsor/7/website" target="_blank"><img src="https://opencollective.com/jscpd/sponsor/7/avatar.svg"></a>
<a href="https://opencollective.com/jscpd/sponsor/8/website" target="_blank"><img src="https://opencollective.com/jscpd/sponsor/8/avatar.svg"></a>
<a href="https://opencollective.com/jscpd/sponsor/9/website" target="_blank"><img src="https://opencollective.com/jscpd/sponsor/9/avatar.svg"></a>
License
MIT © Andrey Kucherenko
---