CLI tool
kucherenko/jscpd avatar
kucherenko/jscpd

jscpd v5: A Rust Copy/Paste Detector With Clone Kinds and a Baseline Gate

Copy/paste detector for source code. 220+ languages, Rust engine, SARIF/HTML/badge reporters, GitHub Action, MCP server for AI agents.

6,216 stars264 forksRustMIT

At a glance

What is it?
jscpd finds duplicated code blocks across 224 formats using a Rabin-Karp engine, and v5 adds clone kinds (exact, renamed, similar), a baseline mode for gating CI on new duplication only, and an MCP server for AI agents. It is a good fit for polyglot repositories and monorepo CI, and a poor fit if you want duplication tracked as a hard zero-tolerance rule.
Who is it for?
Adopt jscpd if you maintain a polyglot repository or a monorepo where duplication accumulates faster than anyone can review it, and you want a CI gate that fails on new clones rather than on the entire legacy backlog. Skip it if you need duplication treated as a hard zero-tolerance rule, or if you require a language whose grammar is not among the 224 listed formats.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 1 day ago.
What is it written in?
Mainly Rust, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.

DEEP OPEN-SOURCE ANALYSIS

The duplication problem jscpd is built around

Copy-paste duplication is the kind of defect that no compiler complains about and no test suite catches. A block of code is copied into a second file, one copy is edited six months later, and the two drift apart. jscpd exists to find those blocks mechanically. The README describes it as a copy/paste detector for programming source code covering 220+ formats, and the supported-formats document lists 224 formats with their file extensions. The intended audience is broad: the topics list includes code-quality, linter, static-analysis and developer-tools, and the install surface spans npm, PyPI, crates.io, Homebrew, Nix, Docker and a GitHub Action. That spread suggests the project is aimed at teams that already run linters in CI and want duplication reported alongside them, rather than at researchers studying clone detection as a field. The practical framing matters: jscpd is a detector, not a refactoring tool. It tells you where duplication is, gives you a similarity score for near-misses, and lets you decide what to do about it.

Rabin-Karp, clone kinds, and what the engine actually compares

The README states that jscpd implements the Rabin-Karp algorithm to find duplicated code blocks across files. That is a rolling-hash string search: the engine hashes windows of tokens and compares hashes rather than doing pairwise block comparison, which is why the project can scan a whole repository in one pass instead of comparing every file against every other file. On top of that base, the README describes opt-in passes. Type-2 detection uses --ignore-identifiers, --ignore-literals and --ignore-annotations to find blocks that differ only in names, literal values or annotations, reported with the kind renamed. Type-3 detection uses --max-gap-lines N to merge a copy with a few inserted or changed lines into one similar clone with a similarity score, and --similarity 0.85 compares whole JavaScript or TypeScript functions by syntax-tree structure. The important design detail is that these passes are opt-in and the default run reports only exact clones, which the README says is unchanged from earlier behaviour. Every reporter carries the clone kind: console, JSON with kind, similarity and method fields, XML, HTML, Xcode, SARIF and Code Climate. The SARIF rule identifiers are jscpd/duplicate-code, jscpd/renamed-code and jscpd/similar-code. Cross-format detection covers Vue SFC, Svelte, Astro and Markdown, and --cross-formats groups let you match clones across JavaScript and TypeScript. That last point is the one that separates jscpd from a plain text diff: a copy of a function moved from a .js file to a .ts file is still a clone here.

Installing jscpd and the first commands to run

The README gives several install paths. On macOS or Linux, curl -fsSL https://jscpd.dev/install.sh | bash. On Windows PowerShell, irm https://jscpd.dev/install.ps1 | iex. Without installing anything, npx jscpd . runs it once through Node.js. The npm global install is npm install -g jscpd, and the same binary is exposed as cpd by npm install -g cpd. PyPI offers pip install jscpd, pipx install jscpd, uv tool install jscpd, or uvx jscpd . to run without installing. cargo install jscpd builds from crates.io and installs both jscpd and cpd. Homebrew and Nix are covered with brew install jscpd and nix run github:kucherenko/jscpd -- /path/to/code. Docker users can run docker run --rm -v "$PWD:/src" ghcr.io/kucherenko/jscpd . against a multi-arch image built from the release binaries. The README notes the binary is prebuilt for eight platforms: macOS arm64 and x64, Linux arm64 and x64 in both glibc and musl variants, and Windows arm64 and x64. After install, the basic scan is jscpd /path/to/code. Configuration is discovered from .jscpd.json, .config/jscpd.json, or a jscpd key in package.json. For CI, the GitHub Action is used as kucherenko/jscpd@v5 with a threshold input, and the README says it uploads SARIF results to GitHub Code Scanning by default. Two flags deserve attention before you run anything large: --workers controls parallelism for tokenization and detection and defaults to all CPU cores, and --skip-local reports only clones that cross the scan roots, so jscpd packages/api packages/web --skip-local drops pairs inside one tree and leaves api-to-web duplication. There is also --skip-isolated for monorepo folders owned by different teams, taking a pattern such as "packages/team-a|packages/team-b".

Baselines, reporters, and the CI gate that only fails on new clones

The most consequential feature for existing codebases is the baseline. The README describes --baseline .jscpd-baseline.json combined with --fail-on-new-clones[=N], which tolerates legacy clones and fails the build on regressions. The alternative form, --baseline-from-ref origin/main, does the same without a committed file. This is the difference between a tool you can turn on today and a tool that reports thousands of pre-existing clones and gets muted within a week. The reporter list has fifteen entries: console, console-full, json, xml, csv, html, markdown, badge, sarif, codeclimate, openmetrics, ai, xcode, threshold and silent. GitLab integration is covered by codeclimate, which writes gl-code-quality-report.json, and openmetrics, which writes jscpd-metrics.txt, both plugging into artifacts:reports. The threshold reporter is what the GitHub Action uses for its threshold input. There is also --summary, described as a codebase summary covering top files and folders by tokens, lines and size plus a complexity estimate, and --blame with --reporters console-full for git blame with side-by-side author comparison. The AI-facing pieces are --mcp, a built-in MCP server over stdio with described tools that let an assistant check snippets for duplication against your codebase or find structurally similar functions with a similarity argument, and the ai reporter, which the README describes as token-efficient output for LLM pipelines at roughly 79 percent fewer tokens than console. Treat that percentage as the project's own claim, not an independent measurement. The MCP server is the more interesting item: it turns jscpd from a batch scanner into something an agent can query mid-edit.

Where jscpd is the wrong tool

The default run reports only exact clones. That is a deliberate compatibility decision, and it means a first scan of a mature codebase can look clean while renamed and near-miss duplication sits untouched. You have to know to add --ignore-identifiers, --ignore-literals or --max-gap-lines, and each of those changes what counts as a clone. Type-3 detection in particular is a heuristic: --max-gap-lines merges a copy with a few inserted or changed lines into a single similar clone with a similarity score, and the score is a number you have to interpret rather than a yes or no. Teams that want duplication treated as a binary rule will find the similarity threshold awkward to defend in review. The structural comparison path, --similarity 0.85 comparing whole JavaScript or TypeScript functions by syntax-tree structure, is scoped to those two languages in the README, so the near-miss story is stronger for JS/TS than for the rest of the 224 formats. Language coverage is format coverage: the supported-formats document lists file extensions, and a language whose grammar is not among the 224 listed formats is simply not analysed. Nothing in the supplied material describes incremental scanning or a daemon mode, so every run tokenizes and detects from scratch, which is where --workers and --skip-local become the practical levers on a large repository. Finally, jscpd reports duplication. It does not judge whether a duplicated block is worth extracting, and some duplication (test fixtures, generated code, protocol tables) is intentional. The tool has no concept of acceptable duplication beyond the baseline and skip flags.

How jscpd differs from a general-purpose linter

The obvious alternative category is a linter with a duplication rule, such as a SonarQube-style quality gate or an ESLint plugin that flags repeated blocks. The difference in approach is scope and mechanism. A language-specific linter parses one language with that language's own parser and can therefore reason about types, scopes and imports; it also only sees files in that language. jscpd is format-driven and cross-format: the README lists cross-format detection for Vue SFC, Svelte, Astro and Markdown, and --cross-formats groups to match clones across JavaScript and TypeScript. That means it can find a block copied from a .js file into a .vue single-file component, which a JavaScript-only linter cannot do without extra configuration. The trade-off runs the other way for depth: a linter's duplication rule can be type-aware, while jscpd works on tokenized text with hashing, and its structural mode is limited to JS/TS. A second alternative is a standalone clone-detection research tool, which typically targets one or two languages and produces a report rather than a CI gate. jscpd's advantage there is packaging: prebuilt binaries for eight platforms, install through six package ecosystems, a GitHub Action, and the baseline mechanism that makes adoption on a legacy repository feasible. If your repository is single-language and you already run a linter that has a duplication rule you trust, adding jscpd buys you cross-file and cross-format coverage at the cost of a second tool in the pipeline and a second threshold to tune.

Maintenance cost, licence, and what to check before adopting

jscpd is MIT licensed, which permits commercial and closed-source use and modification, and the README displays OpenSSF Scorecard and OpenSSF Best Practices badges. That is a statement about project hygiene, not about fitness for your codebase, and it is not legal advice; if your organisation has licence review, the MIT text is the thing to read. The release cadence visible in the supplied material is tight: v5.1.1 on 2026-08-31, v5.1.2 on 2026-09-03, and v5.2.0 on 2026-09-08. Frequent patch releases are good for fixes and bad for pinned CI images, so pin a specific version rather than a floating tag if reproducibility matters, and note that the GitHub Action example uses kucherenko/jscpd@v5, a major-version tag. The upgrade surface is wider than a single binary because the project ships under two npm names, on PyPI, crates.io, Homebrew, Nix and Docker, plus the cpd-finder crate for the Rust API. If you consume the Rust API rather than the CLI, you are tracking a separate crate version. The baseline file is also a maintenance artifact: .jscpd-baseline.json has to be regenerated as tolerated clones are removed, or the gate gradually stops meaning anything. The --baseline-from-ref origin/main form avoids committing that file but ties the gate to a git ref being available in the CI checkout, which shallow clones can break. Verify that before choosing between the two baseline modes.

Editorial conclusion

Adopt jscpd if you maintain a polyglot repository or a monorepo where duplication accumulates faster than anyone can review it, and you want a CI gate that fails on new clones rather than on the entire legacy backlog. Skip it if you need duplication treated as a hard zero-tolerance rule, or if you require a language whose grammar is not among the 224 listed formats. Before wiring it into a pipeline, run jscpd on the repository once and inspect the console output: the default run reports only exact clones, so a clean first pass tells you nothing about renamed or near-miss duplication until you add --ignore-identifiers, --ignore-literals or --max-gap-lines. Then commit a baseline with --baseline .jscpd-baseline.json and confirm which clones it tolerates before enabling --fail-on-new-clones.

Official sources

  1. kucherenko/jscpd on GitHub
  2. License: MIT
  3. Project website
  4. README
  5. Releases
Community notes

Community notes