Detecting Duplicate Code with jscpd: From Rust Rewrite to AI Agents

Learn how jscpd detects copy-pasted code, what the Rust rewrite of jscpd 5 changes, how to catch renamed and similar clones, and how to plug it into CI, your editor and your AI assistants.

Copy-paste is probably the oldest form of technical debt, and AI-assisted coding has given it a second life. An agent that doesn’t know about an existing helper will happily write a new one, slightly different, three files away.

jscpd, the duplicate-code detector created by Andrey Kucherenko, has been around for more than ten years. But 2026 changed a lot for the project: a full Rust rewrite, detection of “near-miss” clones, an MCP server and an LSP mode.

In this article, we’ll see how jscpd works, how to configure it, and how to integrate it into your CI pipeline, your editor and your coding assistants.

How jscpd Detects Duplicates

jscpd doesn’t compare raw text, it compares tokens. Each file is split according to the grammar of its language (more than 220 formats are supported, including code embedded in HTML or Markdown). Comments and whitespace are ignored, then windows of tokens are hashed and compared.

A clone is reported as soon as a duplicated sequence exceeds a threshold: 50 tokens or 5 lines by default.

The easiest way to try it is to run it on your source folder:

npx jscpd src/

The console report lists each pair of duplicated fragments with their files and lines, followed by a statistics table per format showing the percentage of duplicated lines and tokens.

Configuring jscpd

The configuration can live in a .jscpd.json file, a .config/jscpd.json file, or a jscpd key in your package.json:

{
  "threshold": 3,
  "minTokens": 70,
  "ignore": ["**/*.spec.ts", "**/node_modules/**", "**/dist/**"],
  "reporters": ["console", "html"],
  "gitignore": true
}

A few options are worth knowing:

  • threshold makes the command fail if the overall duplication exceeds the given percentage. With --threshold 3, jscpd becomes a one-line CI guard.
  • minTokens defines the minimum size of a clone. With the default value of 50, jscpd flags import blocks and injection constructors that nobody cares about. Raising it to 70 or 100 removes most false positives.
  • reporters defines the output formats.

Several reporters are available depending on your needs:

  • html generates a report that is handy for a team review.
  • sarif surfaces clones in GitHub Code Scanning. Since 5.0.15, clones above a given size can be reported with an error severity.
  • xml (pmd-cpd format) plugs into SonarQube and similar tools.

jscpd 5: The Rust Rewrite

In June 2026, jscpd 5.0 was released as a ground-up rewrite in Rust. It is published under the same npm package name (jscpd@5) and keeps the same configuration file, the same algorithm and the same reporters. Every v4 CLI option is preserved, so it is a drop-in replacement.

JavaScript and TypeScript parsing relies on oxc, and the announced speedups range from 10× to 37× depending on the repository.

# native binary via npm
npm install -g jscpd@5

# or straight from Rust
cargo install jscpd

# or with Homebrew
brew install jscpd

The v4 branch (Node.js) is still maintained if you need the TypeScript programmatic API (detectClones()) or the LevelDB and Redis stores.

Fun fact: the binary used to be called cpd, until some antivirus software blocked it because of a name collision. Since 5.0.12, it is called jscpd everywhere.

The following 5.0.x releases added some useful options:

  • --cross-formats "javascript,typescript" compares files across formats. TypeScript types are erased before comparison, so function f(a: number) matches function f(a).
  • --skip-isolated ignores duplicates between monorepo folders owned by different teams.
  • --summary displays a ranking of the heaviest files by tokens, lines or estimated complexity.

If jscpd used to be too slow to run on your whole monorepo, version 5 removes that limitation: you can now run it on every pull request instead of in a weekly scheduled job.

Beyond Exact Matches: Renamed and Similar Clones

Until recently, jscpd only detected exact clones (also called type-1 clones). Renaming a single variable was enough to slip under the radar. Since version 5.2 (September 2026), two options extend the detection:

  • --max-gap-lines N merges a copy containing a few inserted or modified lines into a single “similar” clone, with a similarity score.
  • --similarity 0.85 compares whole functions based on the structure of their syntax tree, which catches renames and scattered edits. First available for JavaScript and TypeScript, this mode covers 15 languages since 5.4.1 (Python, Go, Rust, C#, Clojure, and more).
jscpd --similarity 0.85 --max-gap-lines 3 src/

Every reporter now indicates the kind of clone: exact, renamed or similar, including the SARIF reporter (jscpd/renamed-code, jscpd/similar-code rules).

By default, nothing changes: only exact clones are reported, so your existing thresholds won’t suddenly fail. I recommend enabling --similarity in an informational mode first. Renamed clones are often the most profitable ones to factor out, but they are also the ones that require the most judgement.

Integrating jscpd with Editors and AI Agents

2026 is also the year jscpd turned towards coding assistants and editors:

  • A compact ai reporter, designed to fit in an LLM context without wasting tokens:

    jscpd --reporters ai src/
  • An MCP server over stdio. The project is scanned once and kept in memory, and the check_duplication tool answers in milliseconds the question “does the snippet I’m about to write already exist?”. It comes with three other tools: get_file_clones, get_statistics and check_current_directory.

    jscpd --mcp /path/to/project
  • Installable skills to guide an agent through a refactoring:

    npx skills add kucherenko/jscpd --skill dry-refactoring
  • Since 5.4.0, jscpd --lsp turns jscpd into a language server, so clones show up directly in your editor.

  • --changed only reports clones involving modified files, which is ideal for a pull request.

  • 56 framework presets (Next.js, Nuxt, SvelteKit, Astro, Remix, and more) preconfigure the folders to ignore.

The idea behind these features is to plug the detector in before the duplicated code gets written, inside the agent loop, rather than discovering it afterwards in CI.

Setting Up jscpd in Your Project

Duplication isn’t always bad: two usages that diverge are better than a forced abstraction. What matters is to see it. Here is a simple approach to introduce jscpd in an existing project:

  1. Measure: run npx jscpd@5 --reporters console,html . and look at the overall percentage. Between 2% and 5% is common. Above 10%, you have some work ahead of you.
  2. Block regressions: set a threshold just above your current level, so new duplicates are caught without blocking the team.
  3. Improve progressively: lower the threshold by one point each quarter, and connect the MCP server to your coding assistants so new duplicates stop appearing.

Conclusion

jscpd has been a reliable tool for years, but the 2026 releases make it much more than a CI report. The Rust rewrite makes it fast enough to run on every pull request, the similarity mode finds the clones that exact matching used to miss, and the MCP and LSP integrations bring duplicate detection right where the code is written, whether by a developer or by an AI agent.

Start by measuring, set a threshold, and let it guide your refactorings over time.

Resources