jscpd

kucherenkoMIT活跃维护

用 Rust 引擎扫描源码里的复制粘贴和重复代码,支持 220 多种语言,能输出 SARIF、HTML 和徽章报告,还提供 GitHub Action 与给 AI 代理用的 MCP 服务。

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

v5.4.3发布于 2026-11-08 06:54 UTC6,533star +18 / 7 天274fork同步于 2026-11-10 17:34 UTC

怎么上手

由 README 提炼,执行前请确认
curl -fsSL https://jscpd.dev/install.sh | bash
jscpd /path/to/code
硬件要求按 README 说明

README 未说明;以自包含二进制分发,无需运行时,未提及 GPU 或显存要求。

支持 Apple Silicon有 Docker 镜像
下载:源码 zip·GitHub Releases(安装包)本站不镜像安装包

项目说明

由 README 自动翻译提炼

Rust 引擎的 220+ 语言重复代码检测器,还能查死代码、复杂度和健康趋势

用 Rust 引擎扫描源码里的复制粘贴和重复代码,支持 220 多种语言,能输出 SARIF、HTML 和徽章报告,还提供 GitHub Action 与给 AI 代理用的 MCP 服务。

核心能力

  • 按各语言注释与字符串规则分词,用 Rabin-Karp 滚动哈希找重复 token 序列
  • 支持 Type-2 改名克隆、Type-3 近似克隆和实验性 Type-4 语义克隆
  • 提供 15 种报告器,含 SARIF、HTML、badge、Code Climate 和 AI 报告
  • 用 baseline 和 --changed 只对新增重复或改动文件做 CI 门禁
  • 附带 --dead-code、--complexity、--history 和 --health 等扩展分析

容易踩的坑

语义克隆需先执行 jscpd --semantic-download 下载嵌入模型,或自行接入 OpenAI 兼容 API;默认只报告 exact 克隆,改名和近似克隆需显式开启对应参数;--dead-code 目前仅覆盖 JavaScript、TypeScript 和 Python。

内容由模型根据 README 生成,可能不完整或不准确,以官方文档为准。看原文 README

最近发版

5 个版本v5.4.3
  1. v5.4.3Release v5.4.3最新正式版2026-11-08 · 2026-11-08 06:54 UTC

    New Features

    • runJscpd() runs the jscpd binary from Node.js. require("jscpd") and import { runJscpd } from "jscpd" now export runJscpd(args, options). It finds the platform binary the way the jscpd command does and returns the result of spawnSync, so a script no longer starts a second Node.js process to run the command, and tools such as knip see that the script uses jscpd. Where the command prints an error and exits, the function throws an Error with the same message. The types are in index.d.ts. Detection still runs only in the Rust binary, and nothing parses or returns clones in JavaScript. By @abrahamguo. (#1238)

    • The HTML report shows both fragments of a clone and highlights the code. "Show code" used to open only the first fragment. Now it opens both side by side, or one above the other on a narrow screen, each under its file with line numbers in a gutter, and a long line scrolls inside its pane. The report highlights the code with Prism 1.30.0 again, as the v4 report did, and inlines only the languages it shows. A block inside another file, such as guide.md:typescript, keeps the lines that detection gives it. (#1263, #1265)

    Bug Fixes

    • The GitHub Action installed the newest jscpd, whatever ref you pinned. The version input defaulted to latest, so kucherenko/[email protected] installed 5.4.2 once it came out. Now an empty version installs the release that the action's own ref belongs to, so pinning the action pins jscpd, and a Dependabot bump of the action updates both. version: latest still installs the newest release. This works from v5.4.3 on: older tags still install the newest jscpd. Reported by @dannystaple, fixed by @sarankumar1325. (#1240, #1249)

    • A /* or // inside a JS or TS string ended a jscpd:ignore region. The tokenizer looked for the markers with a byte scan that took every /* and // for a comment, so 'a /* b' inside a marked region opened a comment that ran to the end marker, and the code after the string came back as clones. The markers are now found by their text, as in the other languages, so a region runs from jscpd:ignore-start to the next jscpd:ignore-end. Markers inside template literals, such as the CSS of a styled component, keep working. Reported by @bauchdj, fixed by @sarankumar1325. (#1257, #1258)

    • "Ignore this clone" in the language server broke JSX. It wrote // jscpd:ignore-start among JSX children, where the page shows it as text, and could put a marker inside a tag, such as < /* jscpd:ignore-end *//ul>, which TypeScript can't parse. Now it writes {/* jscpd:ignore-start */} among children and never puts a marker inside a tag. The start goes before the tag and the end after it, so the region still covers the clone. Each copy of a --cross-formats clone gets the comment of its own format, JSX in the <script lang="tsx"> of a Vue, Svelte or Astro component gets the JSX markers, and a marker after / gets a space so it doesn't turn into a line comment. (#1260, #1264)

    • --dead-code reported a file loaded with import(`./x`) or import('$lib/x') as unused. import() read only quoted specifiers and turned away any specifier with a $ in it, so a backtick import() with nothing interpolated and SvelteKit's $lib alias were never followed. The importing file also lost 30 points o

  2. v5.4.2Release v5.4.22026-10-20 · 2026-10-20 16:14 UTC
    查看更新说明

    The post What's new in jscpd 5.4.2 shows the new features with commands and real output.

    New Features

    • Boundaries keep the code of a module inside it. The new boundaries key of the config file lists rules. A rule names the code it protects (code: a folder, a file, a glob, or a method as path#name), where copies of it must not show up (notIn, or every file outside code when it is left out) and where they may after all (allow). Every clone run checks the rules, and a copy of protected code outside its boundary fails the run with exit code 1. Import linters miss such copies, because a copied function imports nothing.

      • jscpd looks for copies three ways, and a rule can set its own bars in thresholds. Token clones find copy-paste, the structure comparison of --similarity finds an edited copy, and the --semantic model finds the same logic written again, often in another language. disable turns kinds off by their --kind names. Without a downloaded model, jscpd prints a warning and checks the rules without the semantic search.
      • A mistake in a rule, such as an unknown key or a glob that doesn't compile, stops the run with an error that names the rule. A rule that checks nothing in a run, after a typo in a path or a method name for example, gets a warning.
      • The console, console-full, ai and json reporters show the violations, the json one under boundaries. Baselines and --changed don't apply to violations yet, and SARIF, the language server and the MCP server don't report them.
      • fixtures/boundaries-demo is a small shop whose web app copies the backend's pricing and loan code. (#1225, #1227)
    • --ignore-identifiers pairs a name with one name. Every name used to match every other name, so x = x + x * x was a clone of d = a + b * c, although its data flows another way. Now a name on one side has to stand for the same name on the other side every time, and two names can't stand for one, as in Baker's parameterized matching and NiCad's consistent renaming. On 24 projects in 11 languages, 6,942 of the 14,501 renamed clones that the old matching reported had no such mapping.

      • jscpd cuts a clone where a name starts to stand for two and still reports every part of --min-tokens tokens or more.
      • A name reached as a member, after . or ?., and after -> in C, C++, PHP and Perl, is another name than a local of the same text. A constructor that sets this.url = url still matches its copy that sets this.url = link (#1220).
      • --ignore-identifiers and --ignore-identifiers=consistent give the new matching, --ignore-identifiers=blind the old one. The config takes "ignoreIdentifiers": true, "consistent" or "blind", and the Action input takes consistent or blind. The value goes after = only, so --ignore-identifiers src/ still scans src/.
      • fixtures/type2-demo has the consistent and members examples. (#1217, #1218)
    • --similarity pairs a name with one name too. The fingerprint of a subtree now records how its names repeat, so x = x + x * x no longer scores 1.0 against d = a + b * c, and a renamed copy still does. Members, labels such as keyword arguments and object keys, and types each have names of their own, so self.url = url still matches self.url = link. A function's own name matches any name. Kotlin's true, false and null and Clojure's true, false and nil are literals now. On the same 24 projects, 15 of 23,530 pairs fell below 0.8, and runs take 2 to 9% longer. `--ignore-iden

  3. v5.4.1Release v5.4.12026-10-09 · 2026-10-09 12:39 UTC
    查看更新说明

    New Features

    • --similarity compares the structure of functions in 15 languages. jscpd parses every function and method and normalizes its syntax tree: the names of the functions it calls and its operators stay, local names, field names and literals become markers, and comments and the parentheses around one expression drop out. Every subtree of that tree is a fingerprint, and two functions score the Jaccard index of their two fingerprint sets, so a renamed copy scores 1 and an edited statement lowers the score.

      • It reads JavaScript, TypeScript, Python, Java, Kotlin, Scala, C#, Go, Rust, C, C++, PHP, Ruby, Swift and Clojure, and the code blocks of Markdown files and the scripts of Vue, Svelte and Astro components. 5.4.0 compared JavaScript and TypeScript only.
      • The search is exact: it finds every pair at or above the ratio. A function nested in another is part of it, and test files and Rust mod tests are left out. Code between jscpd:ignore-start and jscpd:ignore-end, or matched by --ignore-pattern, adds nothing to a function (#1144).
      • --similarity alone compares at 0.8. --min-nodes (config key minNodes, 20 by default) sets the smallest normalized tree that takes part, --min-lines applies as before and --min-tokens does not. The JSON report gives the node count of both functions as nodes.
      • While the method was built, a check on 100 open-source repositories against a brute-force implementation of it, which scores every pair, found the same 58,405 pairs with the same scores, and jscpd's runs took 64.8 seconds on one thread against 498.
      • fixtures/similarity-demo has a runnable example in every language. (#1129, #1132, #1134, #1136, #1139, #1147, #1148, #1149, #1150, #1152, #1155, #1138, #1141, #1142, #1143, #1145, #1146, #1156)
    • An edn reporter. -r edn writes jscpd-report.edn with {:candidates [...] :clones [...]}. The candidates are every pair --similarity found, most similar first, with their score, language, both locations and node counts, also the pairs a token clone already reports. The clones are the ones the other passes found, with their kind, method and score. (#1151, #1153, #1156)

    • --changed reports the clones of the files you are working on. The changed files are the ones git status lists: staged, unstaged and untracked files, and a renamed file under its new name. jscpd still scans every file and reports a clone when one of its fragments is in a changed file, so a new file that copies an unchanged one shows up.

      • The first run saves the clones of HEAD to .jscpd-baseline.json at the repository root, or to the --baseline file, with the commit and a hash of the scan paths and options. Later runs read it and mark the clones HEAD did not have as [NEW], so --fail-on-new-clones works with it. jscpd builds the file again
  4. v5.4.0Release v5.4.02026-09-30 · 2026-09-30 18:20 UTC
    查看更新说明

    New Features

    • A language server: jscpd --lsp. An editor starts jscpd for a workspace, and the files you edit get what jscpd finds as diagnostics that follow the text in the editor, saved or not. After 300 ms without typing, the server tokenizes the file again from the buffer and searches its detection pool again from the tokens it keeps for the rest of the project.

      • The server runs five analyses: clones (exact, renamed and near-miss copies), similar functions (--similarity), semantic clones (--semantic), dead code (--dead-code) and complexity, with a limit of 15 per function and the complex-file bar of the health score per file. Only clones are on unless you turn the others on, with --lsp-analyses, with the lsp section of .jscpd.json, or from the editor's settings, and each place wins over the one before it.
      • The code of each diagnostic is the id of its rule, the one the SARIF reporters write for clones and dead code. A clone is a warning, as in SARIF, and lsp.clones.warningTokens lowers the smaller ones to information. Dead code is a hint that editors fade.
      • Each .jscpd.json in the workspace makes its folder a project of its own, and clones are found within a project. A workspace with no config is one project across all its folders.
      • A clone comes with "Go to the other copy" and "Ignore this clone", which wraps the fragment in jscpd:ignore-start and jscpd:ignore-end comments. Dead code and semantic clones run in the background after a save, and the progress of each run ends with what it found, such as 94 files, 13 clones.
      • Editor clients can ask for the whole project's clones, semantic pairs, dead code, complexity and statistics with custom requests.
      • docs/editors.md has the setup for Neovim, Helix, Sublime Text, Emacs and JetBrains IDEs, and fixtures/lsp-demo is a project that shows each analysis. (#1120, #1121, #1122)
    • Comparing two codebases, experimental: --compare. jscpd --compare source target pairs the functions of two folders with the --semantic model and reports which functions of each side have a counterpart in the other. During a port to another language or platform, that is what is still to port; for two implementations of one app, it is what only one of them has.

      • A function pairs with its counterpart when the model finds them each other's closest match. A short function the model cannot place pairs by name when the names match once case and underscores are ignored and the code is similar enough. Every pair has its similarity and a level, high, medium or low, and the report lists the pairs under other names on their own.
      • Tests and code are measured apart, and a test pairs only with a test. jscpd tells a test by the conventions of its language, such as *_test.go, test_*.py, *.test.ts or Rust's #[cfg(test)], and JavaScript test cases take part under their titles.
      • The console, JSON and Markdown reporters print the totals per side and per file, the functions with no counterpart and the pairs. -r html writes a migration map: both sides as dependency graphs with the pairs bridging them, a table of the same pairs, and the functions ready to port, whose callees all have a counterpart already. The JSON report lists those as readyToPort.
      • On the Java and Python versions of nayuki/QR-Code-generator, it paired 30 of 41 Java functions with no wrong pair. fixtures/compare-demo is a runnable example. (#1115, #1118, #1119)
    • Agent skills for ports. compare-codebases explains how --compare pairs functions and how to check a comparison. `co

  5. v5.3.3Release v5.3.32026-09-28 · 2026-09-28 13:44 UTC
    查看更新说明

    New Features

    • Semantic clones, experimental: --semantic. The token passes match runs of tokens, so they miss two functions that do the same job with different code. Such a function may be renamed and restructured, or written in another language, like a validation rule that a Rust backend enforces and a Svelte frontend repeats (Type-4 clones). --semantic (config key semantic) embeds every function with a code embedding model and reports the pairs whose vectors point the same way as clones of kind semantic.
      • jscpd embeds the functions of JavaScript, TypeScript, JSX, TSX, Vue, Svelte, Astro, Python, Rust, Go, Java, Kotlin, C#, C, C++, PHP, Ruby, Scala and Swift files that clear --min-tokens and --min-lines. It embeds each function's code without comments, starting at the name the function is declared under.
      • Two functions pair only if they are in different files, neither calls the other, and the clones already found do not cover both. Each must be the other's closest match among the functions of its language; a function that is only close to the best match needs a higher similarity. The cosine must reach --semantic-threshold for a pair across languages, or --semantic-same-threshold for a pair within one language (0.4125 and 0.6375 with the default model), and it must stand at least 3 standard deviations above each function's background. --semantic-scope same keeps the pairs within one language, and --semantic-scope cross keeps the pairs across languages.
      • The default model, CodeRankEmbed (MIT), runs inside jscpd on the CPU once jscpd --semantic-download has fetched it (548 MB, pinned by revision and SHA-256), and a scan makes no network call. In a comparison of nine open models, it found more known clones than jina-embeddings-v2-base-code at the same precision, and reviewers judged more of its pairs to be duplicates. jscpd also runs jina-embeddings-v2-base-code, which is faster: jscpd --semantic-download jina-embeddings-v2-base-code fetches it (324 MB), and --semantic-model jina-embeddings-v2-base-code scans with it.
      • --semantic-url sends the functions to an OpenAI-compatible embeddings API instead, such as Ollama, llama.cpp, text-embeddings-inference or a hosted API. jscpd reads the API key only from JSCPD_SEMANTIC_API_KEY, and sends it only to a URL given on the command line or to a server on this machine. A config file can neither hold a key nor send code to another host on its own.
      • jscpd caches the vectors, so a repeat run embeds only the functions whose code changed. Each set of scanned paths has its own cache file. Once the vectors of changed and deleted functions make up more than a quarter of that file, the next run that embeds something rewrites it without them. --semantic-rebuild-cache embeds everything again and replaces the file at once.
      • Each model scores similarity on its own scale, so each model gets its own thresholds. jscpd --semantic-models lists the nine models jscpd has calibrated, with their thresholds, licenses and where they run. --semantic-model takes any of them by the name in that list (CodeRankEmbed), by Hugging Face id or by Ollama name, and the thresholds follow the model. The rule for groups of copies has two more settings, tuned with jina-embeddings-v2-base-code: a near-best margin of 0.05 and a floor of 0.8. jscpd scales both for every other model by the model's gap between its two thresholds, which gives 0.075 and 0.7125 for CodeRankEmbed. Qwen3-Embedding-0.6B and jina-code-embeddings-0.5b expect an instruction before the text, and jscpd puts the one they were calibrated with before every function; the config key prefix replaces it. A model that jscpd has not calibrated gets 0.6 and 0.75, and the run warns about it.
      • The console prints Clone found (rust, semantic ~0.78), -r ai prints [~0.78 semantic], JSON carries "kind": "semantic" and similarity, and SARIF uses the r

用户点评

登录用户的真实使用记录,可按版本和硬件筛选
共 0 条

    还没有人点评。第一个说出真实体验的人最有价值。

    README 原文

    未翻译

    jscpd

    npm version npm downloads skills.sh installs Crates.io Version NPM License jscpd CI Coverage Duplication in rust/ Socket Badge OpenSSF Scorecard OpenSSF Best Practices Follow on X

    Duplicate code detector for 220+ languages — plus dead code, complexity hotspots, duplication and health trends over git history, and one health score for the whole codebase. Rust engine, self-contained binary, AI-ready with an MCP server and a token-efficient reporter.

    Documentation: https://jscpd.dev

    jscpd tokenizes each of its 224 supported formats the way that language defines it — its own comment and string rules, not generic text — then finds duplicated token sequences across files with a rolling Rabin-Karp hash. Opt-in passes catch copies that differ only in names or values (Type-2) or that have a few edited lines (Type-3), and an experimental one uses a code embedding model for functions that do the same thing written differently (Type-4). See How detection works for the full mechanism, and Supported formats for the full list.

    Beyond duplicates, jscpd also finds dead code (--dead-code), ranks files by complexity (--complexity), tracks duplication and health over git history (--history), and rolls it all into one health score (--health) — see Features below.

    Quick Start

    # macOS / Linux
    curl -fsSL https://jscpd.dev/install.sh | bash
    
    # Windows (PowerShell)
    irm https://jscpd.dev/install.ps1 | iex
    
    # No install — run once with npx (Node.js)
    npx jscpd .
    

    Then scan a project:

    jscpd /path/to/code
    

    Other install methods

    MethodCommandNotes
    npmnpm install -g jscpdInstalls the jscpd command; prebuilt binary, no Node.js at runtime
    npm (cpd command)npm install -g cpdSame binary, exposed as cpd
    PyPIpip install jscpdPlatform wheels with both commands; also pipx install jscpd, uv tool install jscpd, or uvx jscpd . to run without installing
    Cargocargo install jscpdBuilds from crates.io; installs both jscpd and cpd
    Homebrewbrew install jscpdmacOS / Linux
    Nixnix run github:kucherenko/jscpd -- /path/to/codeOr nix profile install github:kucherenko/jscpd
    Dockerdocker run --rm -v "$PWD:/src" ghcr.io/kucherenko/jscpd .Multi-arch image built from the release binaries

    GitHub Action

    - uses: kucherenko/jscpd@v5
      with:
        threshold: 5
    

    Uploads SARIF results to GitHub Code Scanning by default. See CI & Pre-Commit Hooks for all inputs and outputs.

    Documentation

    DocumentDescription
    Rust engineInstallation, CLI reference, reporters, baseline, summary, complexity, dashboard, blame, config file
    AI-ReadyAI reporter, agent skills, MCP server
    Editorsjscpd --lsp, the language server: setup for Neovim, Helix, Sublime Text, Emacs and JetBrains IDEs
    Programming APIRust API (cpd-finder crate)
    CI & Pre-Commit HooksGitHub Action, Docker image, pre-commit hooks
    Packagesnpm packages and crates that make up a release
    Supported formatsAll 224 formats with their file extensions
    Runnable demosOne fixtures/<feature>-demo/ directory per feature, each README lists the commands with their expected output

    Features

    jscpd v5 is a Rust engine that ships as a self-contained binary — no runtime required — under two npm names (jscpd installs the jscpd command, cpd installs cpd), on PyPI, crates.io, Homebrew, Nix, Docker, and as a GitHub Action.

    Duplicate detection

    • Language-aware tokenization — per-format comment and string syntax for all 224 formats, the oxc parser for JavaScript/TypeScript/JSX/TSX, embedded-language extraction for Vue, Svelte, Astro, Markdown and Razor, and keyword/identifier/literal classification, so a clone is a repeated sequence of language tokens, never a repeated run of text (see How detection works)
    • 224 language formats, with cross-format detection (Vue SFC, Svelte, Astro, Markdown) and --cross-formats groups to match clones across JavaScript and TypeScript
    • Type-2 clones — --ignore-identifiers, --ignore-literals and --ignore-annotations find blocks that differ only in names, literal values or annotations, reported as renamed. A name must stand for one name on the other side, so x = x + x does not match d = a + b; --ignore-identifiers=blind matches any names, as 5.4 did (see docs)
    • Type-3 near-miss clones — --max-gap-lines N merges a copy with a few inserted or changed lines into one similar clone with a similarity score; --similarity compares whole functions of JavaScript, TypeScript, Python, Java, Kotlin, Scala, C#, Go, Rust, C, C++, PHP, Ruby, Swift and Clojure by the subtrees of their normalized syntax trees, in code blocks of Markdown and components as well: renamed locals (each standing for one name on the other side) and other literals match, other called functions or operators do not, and test files are left out; the edn reporter lists every pair with its score (see docs)
    • Type-4 semantic clones (experimental) — --semantic embeds the functions of JavaScript, TypeScript, Vue, Svelte, Astro, Python, Rust, Go, Java, Kotlin, C#, C, C++, PHP, Ruby, Scala and Swift files with a code embedding model, either one jscpd runs itself (after jscpd --semantic-download once) or any OpenAI-compatible API, and reports functions that do the same thing written differently: the same feature implemented twice in one language, or a rule your Rust backend enforces and your Svelte frontend repeats (see Semantic clones below)
    • Port progress and parity (experimental): jscpd --compare python/ typescript/ pairs the functions of two folders with the same model and lists which functions of each have a counterpart in the other: what is left to port to another language or platform, or what the Android version of an app has that the iOS one lacks (see Comparing two codebases below)
    • Boundaries: rules in .jscpd.json keep the code of a module, a folder, a file or a method inside its boundary, such as money that only the backend may calculate. A copy of it in a place a rule keeps it out of fails the run, whether someone copied it, edited the copy or wrote the logic again in another language (see Boundaries below)
    • Clone kinds everywhere — exact, renamed or similar in the console, JSON (kind, similarity, method), XML, HTML, Xcode, SARIF (jscpd/duplicate-code, jscpd/renamed-code, jscpd/similar-code, jscpd/similar-function, jscpd/semantic-code) and Code Climate output; default runs still report only exact clones
    • --kind — keep only the clone kinds you care about: --kind renamed, or --kind gap,ast for near-miss clones only. Statistics and --threshold follow the filter; a kind whose detector is off warns, an unknown kind errors (see docs)
    • 15 reporters: console, console-full, json, xml, csv, html, markdown, badge, sarif, codeclimate, openmetrics, ai, xcode, threshold, silent
    • Clone baseline — gate CI on new duplication only. --baseline .jscpd-baseline.json --fail-on-new-clones[=N] tolerates legacy clones and fails the build on regressions; --baseline-from-ref origin/main does the same without a committed file (see docs)
    • Only the files you changed — jscpd --changed . reports the clones of the files git status lists, matched against the whole project, and marks the ones HEAD didn't have as new; --changed-only compares the changed files only with one another. The baseline of HEAD is saved on the first run and built again after a commit (see docs)
    • Exit codes you can gate on — an unknown --format, a missing scan path or a reporter that can't write its file now exit 1 instead of passing with an empty report; --fail-on-empty fails a scan that analyzed no files (see Exit codes)
    • GitLab-ready reporters — codeclimate (gl-code-quality-report.json) and openmetrics (jscpd-metrics.txt) plug into artifacts:reports
    • Git blame with side-by-side author comparison (--blame --reporters console-full)
    • --skip-local — report only clones that cross the scan roots: jscpd packages/api packages/web --skip-local drops pairs inside either tree, keeping only api-to-web duplication
    • --skip-isolated — ignore duplication between monorepo folders owned by different teams (--skip-isolated "packages/team-a|packages/team-b")

    Beyond duplication

    • --history — duplication and health trend over git history: jscpd --history scans the current folder over the last 30 commits of the branch, jscpd src --history v5.0.0..HEAD the commits of a range, a merged branch as its merge commit. It prints bar charts of the duplication percentage and the health score, a per-commit table with the change between points, the trends with the score of each health dimension at both ends, and how far --threshold could be tightened; --health and --dashboard print the series too (see docs)
    • --dead-code — find code nothing runs, not just code written twice: unused files, exports, declarations and imports across JavaScript, TypeScript and Python. Builds the import graph from your entry points (package.json, pyproject.toml, framework conventions) and walks it, so dead code cascades — a helper whose only caller is dead gets reported too, each finding with a confidence score and, below 100, why it might be wrong. Also ships standalone as basta (see docs)
    • --summary — refactoring hotspots straight from the scan: top files and folders by tokens, lines, size, and a complexity estimate (see docs)
    • --complexity — the complexity ranking alone, without clone detection: most complex files and folders from one tokenizing pass, in the console, ai or json (see docs)
    • --health — one 0-100 score with a grade, from the share of code that's duplicated, dead, or concentrated in complex files; size-aware, calibrated on 42 open-source projects, extensible with coverage, test or security metrics via --health-input. Console badge, JSON, SVG badge (see docs)
    • --dashboard — the whole picture on one screen, under the health badge: project size with the largest code files, duplication by clone kind with the most duplicated files, the most complex files, and dead code by category for JavaScript, TypeScript and Python (see docs)

    AI and operations

    • --mcp — built-in MCP server over stdio with fully described tools: point your AI assistant at the binary and it can check a snippet against your codebase for all four types of clone (exact, renamed, near-miss and semantic), list a file's clones, and compare two folders function by function (see docs)
    • --lsp: a language server over stdio. Clones, similar functions, semantic clones, dead code and complexity show up as diagnostics in the files you edit and follow the text as you type, each analysis with a switch of its own (see Editors below)
    • AI reporter — token-efficient output for LLM pipelines (~79% fewer tokens than console)
    • Prebuilt for 8 platforms — macOS arm64/x64, Linux arm64/x64 (glibc and musl), Windows arm64/x64
    • --workers — control parallelism for file tokenization and detection (default: all CPU cores)
    • Config discovery — .jscpd.json, .config/jscpd.json, or the jscpd key in package.json
    • Symbolic links are skipped unless --follow-symlinks — v4 followed them by default. With the flag, a file reached through a link is reported by the path it was found at, and a file reachable through several paths is counted once
    • Quiet in pipelines — tips and sponsor lines print only on an interactive terminal; --no-tips, CI or JSCPD_NO_TIPS switch them off everywhere

    See the Rust docs for the full CLI reference and rust/CHANGELOG.md for release notes.

    Looking for v4?

    jscpd v4 (TypeScript engine, Node.js API, LevelDB/Redis stores) is maintained on the master-v4 branch and published as jscpd@4 / the latest-4 dist-tag. README-v4.md describes it in one page (install, CLI, API, packages, maintenance policy); the same content is at https://jscpd.dev/getting-started/v4.

    Packages

    PackageRegistryDescription
    jscpdnpmInstalls the jscpd command (prebuilt binary via platform packages)
    cpdnpmInstalls the cpd command (same binary)
    jscpd-<platform>npmPlatform binary packages pulled in as optional dependencies: jscpd-darwin-arm64, jscpd-darwin-x64, jscpd-linux-x64-gnu, jscpd-linux-arm64-gnu, jscpd-linux-x64-musl, jscpd-linux-arm64-musl, jscpd-windows-x64-msvc, jscpd-windows-arm64-msvc
    jscpdPyPIPlatform wheels repacked from the release binaries; installs both jscpd and cpd commands
    jscpdcrates.ioCLI crate; installs both jscpd and cpd binaries
    cpd-corecrates.ioDetection algorithm (Rabin-Karp rolling hash), data models
    cpd-tokenizercrates.ioSource code tokenization (224 formats)
    cpd-similaritycrates.ioStructural similarity (--similarity): functions from tree-sitter syntax trees and a Clojure reader, normalized trees and their subtree fingerprints, and the exact search for pairs
    cpd-findercrates.ioFile walking, orchestration, git blame — the library entry point
    cpd-reportercrates.ioOutput formatting (15 reporters, duplication and dead code)
    cpd-semanticcrates.ioSemantic clones (--semantic, experimental): function extraction, code embeddings from a model run in-process or an API, pairing
    bastanpm / crates.ioDead code detection — unused files, exports, symbols and imports for JavaScript, TypeScript and Python. Installs the basta command; the same engine backs jscpd --dead-code

    Who Uses jscpd

    The jscpd npm package is downloaded 10M+ times per month, and 6,000+ repositories declare it on GitHub's dependents graph.

    Bundled by analysis platforms:

    • GitHub Super Linter, the official GitHub linter aggregator, bundles jscpd as its copy/paste detector and runs it by default. 15,500+ workflow files on GitHub reference Super Linter (as of Sep 2026).
    • MegaLinter, an open-source linter aggregator for CI, ships jscpd in every flavor, ci_light included.
    • Codacy, an automated code analysis platform, uses jscpd for its duplication engine.

    Runs in the public repositories of these teams and projects. Each link opens the file that runs jscpd. jscpd.dev shows the same list with logos.

    ProjectHow it runs jscpd
    JetBrains IntelliJ IDEABazel pins jscpd 5.4.2 and runs it as the copy-paste test of several areas
    Microsoft MSTestA workflow scans the C# sources and has a model review the top findings
    AWS Toolkit for VS CodeEvery pull request runs jscpd and keeps the clones that touch the diff
    Google Cloud Document AI samplesSuper Linter with a jscpd config of its own
    IBM ContextForgeThe web lint workflow scans the gateway's static files and templates
    Salesforce Extensions for VS CodeA check:dupes script with the repository's .jscpd.json
    Elastic CLIMegaLinter with COPYPASTE_JSCPD enabled and a 5% threshold
    REAThe deep static checks job runs jscpd with a 4% threshold on every pull request
    OpenClawdup:check scripts on jscpd 5.4.2
    n8nA workflow scans the monorepo and uploads the report
    DeepSeek HarnessA duplication gate in CI over packages and scripts
    OmniRouteA duplication ratchet in CI fails when duplication goes up
    GitNexusThe weekly test scenario map looks for exact, renamed and near-miss copies in test fixtures
    MiniMax CodeA jscpd config with a 5% threshold in the local runtime package
    Kiro KiroCrewCopy/paste detection on the website code in CI
    A2ASuper Linter with VALIDATE_JSCPD=true
    HyperDXA duplicate code analysis workflow
    Sveltia CMScheck:duplicates in the pnpm check of its test workflow
    Deutsche Bahn DB UX Design Systemlint:jscpd exits with 1 when jscpd finds clones
    Callstack agent-deviceA baseline gate fails CI on new clones in the native code
    Evil Martians AgentPrismA jscpd job in CI
    Wire for WebA lint-duplication recipe
    MEGA web clientAn npm script over the client's JavaScript
    OVHcloud ManagerA duplication check in its static analysis kit
    Kitware VolViewA check script that reads jscpd's JSON report
    RudderStack JavaScript SDKcheck:duplicates in every package of the monorepo
    Prebid.jsA duplicated code check on every pull request
    Eclipse Temurin buildSuper Linter with a jscpd config of its own

    jscpd also runs in degit, RimSort, Drifty and Microsoft TypeAgent, and AppWorks embeds it as a library.

    Recommended aggregator settings

    Super Linter and MegaLinter both run jscpd with threshold: 0 by default, which fails a build on any duplication at all, including clones that were already there. That is the most common reason projects end up setting VALIDATE_JSCPD: false. Gate on new duplication instead, and leave the threshold out:

    jscpd . --baseline-from-ref origin/main --fail-on-new-clones 0
    

    --baseline-from-ref scans the base ref's tree with the same configuration and marks the clones that are absent from it as new, and --fail-on-new-clones 0 fails the run when there is one. Every clone is still reported, and --threshold still compares the duplication of the whole tree, so keep threshold: 0 out of the config, or the old clones fail the build as before. The base ref has to be in the checkout: with actions/checkout set fetch-depth: 0, or run git fetch origin main first, otherwise jscpd stops with an error. jscpd needs the whole tree to find clones, so VALIDATE_ALL_CODEBASE: false does not narrow the scan.

    Super Linter takes the same gate from .github/linters/.jscpd.json, on v9.0.0 or later: see Using jscpd with Super Linter.

    When several linters write into one reports directory, give jscpd its own file name so parallel runs cannot overwrite each other:

    jscpd . --reporters json --output reports --report-name megalinter-jscpd
    # -> reports/megalinter-jscpd.json
    

    --report-name sets the base name for the json, xml, csv, html, markdown, sarif and edn reports, and defaults to jscpd-report. It can also be set as reportName in .jscpd.json, or with the report-name input of the GitHub Action. The name is a plain file name, so a path, an extension or an empty name is an error. The badge, OpenMetrics and CodeClimate files (jscpd-badge.svg, jscpd-metrics.txt, gl-code-quality-report.json) keep their names, so the pipelines that read them keep working. When two runs write those too, give each run its own --output.

    Benchmark

    Compared against other copy/paste detectors on the fixtures/ corpus (547 files, 150+ formats), default thresholds, wall-clock time on Apple Silicon:

    ToolTimeFilesClonesDup Lines
    jscpd84ms3472129,133
    jscpd-rs111ms36022210,317
    Duplo162ms31951813,049
    Fallow dupes164ms34103,137
    Simian964ms54742415,351
    PMD CPD35.980s71562,267

    Methodology, cross-format detection and AI-token-efficiency comparisons: benchmark/BENCHMARK.md. Re-run with benchmark/benchmark.sh.

    Semantic clones (experimental)

    Token matching finds code that someone copied. It cannot find two functions that do the same job with different code, such as a validation rule that a Rust backend enforces and a Svelte frontend writes again, or two helpers that two people wrote for the same task. --semantic looks for these pairs with a code embedding model. jscpd turns every function into a vector and reports two functions as a semantic clone when each is the other's closest match and their vectors are similar enough.

    jscpd --semantic-download                                    # once: CodeRankEmbed, 548 MB, into the jscpd cache
    jscpd --semantic src/                                        # pairs within one language and across languages
    jscpd --semantic --semantic-scope cross backend/ frontend/   # only pairs across languages
    jscpd --semantic --kind semantic -r ai .                     # only semantic clones, one line each
    

    The model runs inside jscpd on the CPU, so a scan makes no network call. The default model is CodeRankEmbed (MIT), and jscpd can also run jina-embeddings-v2-base-code. jscpd --semantic-models lists nine models with the thresholds jscpd calibrated for each one, and --semantic-model picks one of them. To use a model that jscpd does not run itself, point --semantic-url at an OpenAI-compatible embeddings API such as Ollama, LM Studio or llama-server. jscpd reads an API key only from the JSCPD_SEMANTIC_API_KEY environment variable.

    jscpd compares the functions of JavaScript, TypeScript, JSX, TSX, Vue, Svelte, Astro, Python, Rust, Go, Java, Kotlin, C#, C, C++, PHP, Ruby, Scala and Swift files. A pair within one language needs a higher similarity than a pair across languages, because code in one language resembles other code in that language whatever it does. With CodeRankEmbed the two thresholds are 0.4125 and 0.6375, and --semantic-threshold and --semantic-same-threshold change them. jscpd caches the vectors, so a second run embeds only the functions whose code changed.

    The console, ai, JSON, SARIF and Code Climate reporters mark these clones as kind semantic with their similarity. The mode is experimental. Besides real duplicates it reports related code, such as a client function and the server endpoint it calls, so review a pair before you merge the two functions. The docs describe the rules, fixtures/semantic-demo is a runnable example with a Rust backend and a SvelteKit frontend, and Embedding Models compares the nine models.

    Comparing two codebases (experimental)

    --compare takes two folders and pairs their functions with the --semantic model. During a port, such as a library moving to another language or an iOS app moving to Android, it shows which functions of the source already have a version in the target and which are still to port. For two implementations of one app, such as the Android and the iOS one, it shows what both have and what only one of them has.

    jscpd --compare python typescript        # progress of a port: the source first, the target second
    jscpd --compare ios android -r console-full  # parity, with every pair and its similarity
    
    Code
     71% 5 of 7 functions in python have a counterpart in typescript
     80% 4 of 5 functions in typescript have a counterpart in python
    
    Paired under other names (1):
      python                        typescript              similarity
      billing.py:28 tax_for_region  billing.ts:27 salesTax  0.87 high
    
    Only in python (2):
      billing.py (1)
        46  due_date                6 lines
      shipping.py (1)
        18  estimate_delivery_days  8 lines
    
    Tests
     75% 3 of 4 tests in python have a counterpart in typescript
    100% 3 of 3 tests in typescript have a counterpart in python
    

    A function pairs with its counterpart when the model finds them each other's closest match. A short function the model cannot place pairs by name when the names match once case and underscores are ignored (encodeBinary, encode_binary) and the code is similar enough. jscpd measures tests and code in two blocks and pairs a test only with a test. It tells a test by the conventions of its language, such as *_test.go, test_*.py, *.test.ts, src/test/ or Rust's #[cfg(test)]. Every pair has its similarity and a level, high, medium or low, on the scale of the model, and the report lists the pairs under other names on their own, since nobody finds those by searching for a name. The console, JSON and Markdown reporters print the totals per side and per file with the mean similarity, the functions with no counterpart, and the pairs. -r html writes a migration map, a page that draws both sides as dependency graphs with the pairs bridging them and marks the functions ready to port, the ones whose callees all have a counterpart already. A second tab lists the same pairs as a table you can sort. The docs describe the rules and how they did on two real codebases, and fixtures/compare-demo is a runnable example.

    Boundaries

    Some code must stay where it is. In a bank or a shop only the backend calculates money, and a frontend that calculates it too shows one price and charges another as soon as the formula changes. A boundary rule names that code and the places its copies must stay out of:

    {
      "boundaries": [
        {
          "name": "money",
          "code": ["backend/billing", "backend/loans/Interest.kt#monthlyPayment"],
          "notIn": ["web/**", "mobile/**"],
          "allow": ["web/e2e/**"]
        }
      ]
    }
    

    code takes modules, folders, files or methods, by name (#monthlyPayment) or by a pattern of names (#(*Payments)). Without notIn, every place outside code counts. Every clone run checks the rules with every method jscpd has: token clones for copy-paste, --similarity for an edited copy, and the model of --semantic for the same logic written again in another language, such as the annuity formula of a Kotlin backend in a TypeScript widget. A copy fails the run with exit code 1:

    Boundaries:
    money: 1 copy outside the boundary
      web/src/loan/payment.ts:4-14 monthlyPayment
        looks like backend/loans/Interest.kt:8-15 monthlyPayment (semantic ~0.82)
    

    The logic written again in another language needs the embedding model, so run jscpd --semantic-download once, or point --semantic-url at an embeddings API. Without it jscpd prints a warning and still finds the copies and the edited copies. Each rule can set its own thresholds, and disable turns kinds of copies off: "disable": ["semantic"] for a rule that needs no model, or ["ast", "semantic"] for one that reports copy-paste only. The docs describe the rules, and fixtures/boundaries-demo is a runnable example.

    Editors

    jscpd --lsp runs jscpd as a language server. An editor starts it for a workspace, and the files you edit get what jscpd finds as diagnostics, updated as you type: clones by default, and similar functions, semantic clones, dead code and complexity when --lsp-analyses or the lsp section of .jscpd.json turns them on. A clone comes with "Go to the other copy" and "Ignore this clone" actions. In Neovim 0.11:

    vim.lsp.config('jscpd', { cmd = { 'jscpd', '--lsp' }, root_markers = { '.jscpd.json', '.git' } })
    vim.lsp.enable('jscpd')
    

    Editors has the setup for Helix, Sublime Text, Emacs and JetBrains IDEs too, and fixtures/lsp-demo is a project that shows each analysis. Clients for VS Code and Zed will follow in a repository of their own.

    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              # compact clone list
    jscpd --reporters ai --summary /path/to/source    # + compact codebase summary
    jscpd --reporters ai --complexity /path/to/source # most complex files, no clone detection
    

    Agent Skills

    Installable skills that teach AI coding assistants how to use jscpd, refactor detected duplications, and clean up a codebase more broadly:

    SkillPurposeInstall
    jscpdTool reference — CLI options, AI reporter format, config syntaxnpx skills add kucherenko/jscpd --skill jscpd
    dry-refactoringGuided refactoring workflow — read clones, choose strategy, apply, verifynpx skills add kucherenko/jscpd --skill dry-refactoring
    compare-codebasesHow --compare pairs the functions of two folders, and a step-by-step way to compare two folders and check the resultnpx skills add kucherenko/jscpd --skill compare-codebases
    code-migrationPort a codebase to another language or framework, tests first and code second, with a coverage map binding functions to their tests and --compare as the progress measure; or check two implementations for paritynpx skills add kucherenko/jscpd --skill code-migration
    codebase-refactoringBroader health pass — fix duplication, then remove/refactor dead code, then simplify the biggest/most complex files, prioritized from --healthnpx skills add kucherenko/jscpd --skill codebase-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 — or "clean up this codebase" for the broader pass, or "port this Python library to Rust" for a migration.

    MCP Server

    jscpd --mcp /path/to/project scans the project and serves the Model Context Protocol over stdio. An assistant can check a snippet against the codebase before writing it, list a file's clones, scan the working directory again and compare two folders function by function. The tools that find clones can look for exact (Type-1), renamed (Type-2), near-miss (Type-3) and semantic (Type-4) ones.

    See AI-Ready docs for full details.

    Citation

    If jscpd is part of your research, cite it via the repository's CITATION.cff (GitHub's "Cite this repository" button produces BibTeX and APA) or with:

    @software{jscpd,
      title        = {jscpd: copy/paste detector for programming source code},
      author       = {Kucherenko, Andrey},
      year         = {2026},
      version      = {5.3.0},
      license      = {MIT},
      url          = {https://github.com/kucherenko/jscpd},
    }
    

    Contributing

    See CONTRIBUTING.md for the development setup, test policy, and pull request requirements. In short:

    cd rust
    cargo nextest run --workspace
    cargo clippy --workspace --all-targets -- -D warnings
    cargo fmt --all --check
    

    Security issues go through the security policy, not public issues.

    Backers

    Thank you to all our backers! 🙏 [Become a backer]

    Sponsors

    Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [Become a sponsor]

    License

    MIT © Andrey Kucherenko