Getting Started

Changelog

Release history and what's new in jscpd.

Overview

For the full, up-to-date changelog, see the v5 (Rust) changelog in the jscpd GitHub repository. The v4 (TypeScript) history lives on the master-v4 branch.

v5.3.2 (Latest)

Released 2026-09-23

New Features

  • --dead-code reads Rust dead code from the compiler. jscpd does not parse Rust and does not need to: every cargo check already prints lines like function `reprint` is never used, with real name resolution, trait dispatch and macro expansion behind them. Pipe that output in — cargo check --all-targets --message-format=json | jscpd . --dead-code --rust-diagnostics - — and the findings appear next to every other language, at 100% confidence. The same flag works for --dashboard and --health, so a Rust project gets its health badge from the check it already runs, and the config file takes the file as rustDiagnostics. See Project Health Score and Project Dashboard. (#1089)
  • Findings from a Rust test target are treated as test code, reported only with --include-tests and then at 85%, the same way a test file is handled in any other language. Neither jscpd nor basta runs cargo itself, because that would execute the project's build scripts and procedural macros. (#1089)

Bug Fixes

  • Text around an embedded code block was counted as code. A ts block in Markdown or a <script> in a Vue, Svelte or Astro component keeps the line numbers of the file it lives in, and the statistics read those numbers as if the block ran the whole way — so prose and templates landed in the embedded language's line counts, in the total and in the duplicated lines both. On a repository with about 3600 Markdown files the typescript row said 51.30% of lines were duplicated where the same code, scanned with -f typescript, said 3.37%. An embedded source now counts the lines its blocks occupy, and a clone counts the block lines it covers. To tell whether an older report was affected, compare the two percentage columns: they measure the same duplication, so a line column sitting far below the token column was counting text that holds no code. See Embedded languages. (#1091, closes #1090)
  • Every clone was one line short. Duplicated lines came from end - start, so a clone covering lines 10 through 19 counted nine — while the console printed 10 lines for it. Line counts are inclusive now and both come from one place, so duplicated-line totals rise by about one line per clone, and percentages with them. (#1091)

Other

v5.3.1

Released 2026-09-21

New Features

  • --dead-code now detects which frameworks a project uses. A framework runs files that nothing imports: a router's pages, a runtime's plugin directories. basta, the dead-code engine, used to know three such cases. It now reads a built-in list of 56 frameworks (Next.js, Nuxt, WXT, Remix, SvelteKit, Astro, Angular, NestJS, Storybook, Vitest, Django and more) and keeps the files they load out of the report. A framework is detected from its config file, from the package.json dependencies, or from its section in package.json, separately for each package of a monorepo. The console report names what it found, for example Frameworks: next (apps/web), vitest. (#1087)
  • Names a framework reads are never reported. Next calls a page's getServerSideProps, Remix a route's loader, Angular a component's ngOnInit. A framework definition can list such names, limited to the files the framework reads them from. A declaration with one of these names counts as used, and so does everything it calls. (#1087)
  • .jscpd.json can hold dead-code settings in a section of their own. The deadCode key (also dead-code or basta) can now be an object: minConfidence, entry, ignore, threshold, your own framework definitions and more. It only supplies settings, so clone settings and dead-code settings can share one file. A flag still wins over the section. The standalone basta binary reads the same section. See Configuration. (#1087)
  • --dashboard lists the largest code files, by lines, next to the largest formats. Only code is ranked, so a lockfile or a long HTML page does not top the list. See Project Dashboard. (#1086)

Bug Fixes

We ran basta next to knip and fallow on 55 repositories (the GitHub trending lists for JavaScript, TypeScript, Vue, Svelte and Astro, plus the source code of Nuxt, Next.js, Svelte, Gatsby and Astro) and fixed what the run found (#1087):

  • --dead-code was very slow on projects with many path aliases. A monorepo with 150 packages took 198 s and Next.js took 83 s. They now take 3.3 s and 4.4 s, with identical findings.
  • A catch-all alias such as "*": ["./*"] made every package import slow. LibreChat went from 4.0 s to 0.86 s.
  • Vite's '@': '/src' alias was read as the root of the file system, so every @/ import was lost and live components were reported as unused.
  • require(`./x`) with backticks was ignored. Gatsby writes every string that way.
  • An arrow function in a Svelte or Astro attribute broke parsing, and everything the component imports was reported as unused.
  • Django migrations and management commands were reported as unused files. Django, Alembic and Scrapy are now in the framework list.
  • A file named only by a path string without an extension, the way a framework registers a file it loads itself, is no longer reported at full confidence. It gets a new reason, "its path appears in a string literal", which puts it under the default threshold.
  • A WXT browser extension was read as almost entirely dead, because its entrypoints/ directory and its generated @/~ aliases were not recognized. (#1082)
  • Markup, stylesheet and template files no longer get a complexity score. Words like if or a media query's and were counted as branches, so an HTML page could top the "Most complex files" list. (#1081)

Other

v5.3.0

Released 2026-09-18

New Features

  • --health: one score for the project — duplication, dead code and complexity, folded into a single 0-100 score with an A-E grade. It adjusts for project size, so a small project isn't sunk by one finding, and a dimension jscpd can't measure (say, no JavaScript/TypeScript/Python to check for dead code) is left out and labeled n/a rather than scored as if it were clean. Bring in your own metrics — coverage, security scans, anything you track — with --health-input. See Project Health Score. (#1064)
  • --dashboard: the whole picture on one screen — project size, duplication by clone kind and format, the most complex files, and dead code, all from a single run, with the health score on top. See Project Dashboard. (#1064)
  • --complexity: the complexity summary without a clone scan — for when all you want to know is which files are hardest to maintain. See Configuration. (#1064)
  • --kind: filter clones by how they were found — keep only exact, renamed or similar matches, or narrow similar further to a near-miss merge (gap) or a structural match (ast). An unknown kind is an error instead of quietly returning a clean report. See Types of Code Clones. (#1064)
  • --dead-code: find code nothing runs — a new engine, basta, builds a project's import graph from its real entry points and reports unused files, exports and imports across JavaScript, TypeScript, Vue, Svelte, Astro and Python, including monorepo package names, path aliases and framework routing conventions. Every finding carries a confidence score. It's built into jscpd as --dead-code and also ships as its own standalone basta command. A dedicated guide is coming; for now see the full write-up in the changelog. (#1064)
  • Markdown and HTML reports for --dashboard and --health — alongside the existing console, json and badge output, so either one can be posted as a PR comment or published as a static page.
  • More accurate complexity counting — --summary --summary-by complexity now counts branches the way each language actually spells them (Rust match arms, Swift guard, Go select, and so on), counts one path per function instead of per file, and reads short-circuit operators (&&, ||) correctly in every language, not only JavaScript. Checked against lizard across nine languages, agreement on file ranking rose from 0.83 to 0.92.

Bug Fixes

  • A single mistyped config field no longer discards the whole config — .jscpd.json fields are now checked one at a time, so one wrong type (say, a string where entry wants an array) drops just that field instead of the entire file.
  • Hardened --dashboard/--health output against crafted input — a hostile file path, format name or metric ID could break a Markdown table, inject content into the HTML report, or forge extra rows in the console output; every such value is now escaped before it's rendered.
  • --dashboard/--health no longer fail outright when --format rules out every dead-code-capable language — they drop just that section, the way a project with none of those files already does.
  • --complexity --fail-on-empty now writes its reports before failing, matching every other mode.
  • --min-confidence above 100 is clamped everywhere it's read, not only in standalone --dead-code.
  • Fixed a health-score bug where excluding duplicated markup (HTML, CSS, templates, …) from scoring barely moved the number on a real project — it's now excluded properly on both sides of the calculation.
  • Windows report paths now use forward slashes, matching every other platform.
  • basta's oxc parser crates bumped to 0.150.

Thank You ❤️

v5.2.1

Released 2026-09-15

New Features

  • --history: duplication trend over git history — jscpd src --history v5.0.0..HEAD scans every commit in the range and prints a bar chart, a per-commit table with the change between points, the overall trend and how far --threshold could be tightened. --history-since, --history-every and --history-limit narrow the range, the JSON report carries a history key, and the Action takes a history input. See History. (#1002, #1050)
  • Exit codes you can gate on, and --fail-on-empty — an unknown --format, a scan path that does not exist and a reporter that cannot write its file now exit 1 instead of passing with an empty report. --fail-on-empty fails a scan that analyzed no files, so a mistyped path or an over-broad ignore cannot look like a clean run. See Configuration. (#1047, #1049)
  • pip install jscpd — eight platform wheels built from the same prebuilt binaries as the npm packages, so pip install jscpd and uvx jscpd get the Rust engine with no Python code and no Node.js runtime involved. The repository-hosted pre-commit hook now installs from PyPI. See Installation. (#1037, #1039)

Bug Fixes

  • An open clone could be stretched past the file it started in — a third file sharing the same text but continuing differently could extend a fragment beyond its own file, so the clone was dropped or reported with mismatched ends. Growth now follows the clone's own anchor, and N-way copies no longer lose pairs. (#1033, #1034)
  • The XML report could be rejected by every parser — bytes XML 1.0 cannot represent were written verbatim, ]]> inside a fragment closed the CDATA section early and attributes were escaped twice. Such characters become U+FFFD, ]]> is split across two CDATA sections, and paths are escaped once. (#375, #1055)
  • --follow-symlinks renamed and double-counted linked files — a file reached through a link was reported by its resolved real path, so the report and --ignore disagreed about its name, a file reachable through two paths counted twice, and a file symlink next to its target was a clone of itself. Files now keep the path they were found at and each real file is scanned once. (#1059, #1060)

Other

  • Symbolic links are skipped unless --follow-symlinks — v4 followed them by default unless --noSymlinks was set. True in every 5.x release but undocumented, and it silently drops a corpus mounted through a symlink.
  • CITATION.cff and a Citation section — GitHub's "Cite this repository" button and a BibTeX entry for papers that use jscpd as their detector. See Research. (#1051)
  • Docs: jscpd is language-aware — detection runs on language tokens, with per-format comment and string syntax and the oxc parser for JavaScript and TypeScript, not on raw text. See How detection works. (#1048)
  • Agent skills know about clone kinds, the summary and their noise — the bundled skills document --summary and the Type-2 and Type-3 flags, and warn that normalized passes surface look-alike code, with conservative defaults and a triage step. (#1056, #1057)

Thank You ❤️

v5.2.0

Released 2026-09-08

New Features

  • Type-2 clone detection — --ignore-identifiers, --ignore-literals and --ignore-annotations (config keys ignoreIdentifiers, ignoreLiterals, ignoreAnnotations, Action inputs of the same names) find blocks that differ only in names, literal values or annotations. Every clone now carries a kind, exact or renamed; a run without the flags is unchanged apart from the additive "kind": "exact" JSON field. (#998, #1019)
  • Near-miss clone merging with --max-gap-lines N — clones of one file pair separated by at most N unmatched lines in both files are merged into one clone of kind similar with a similarity score, so a copy with a line inserted in the middle is reported once instead of as two halves. Default 0 keeps the old behaviour. (#999, #1020, #1030)
  • Function-level similarity for JavaScript and TypeScript with --similarity RATIO — compares whole functions by syntax-tree structure and reports pairs at or above the ratio as similar clones; names and literals do not take part, so a renamed copy scores 1.0 and a couple of inserted statements about 0.75. The default 1 means exact matches only. The MCP check_duplication tool takes the same argument. See MCP Server. (#999, #1021)
  • Clone kinds in every reporter — console, ai, JSON, XML, HTML, Xcode, SARIF (rules jscpd/renamed-code and jscpd/similar-code) and CodeClimate all show whether a clone is exact, renamed or similar, and how a similar one was found (gap or ast). (#1019, #1021, #1030)
  • Tips are skipped when stdout is not a terminal — pipes, files, CI logs and agent hooks no longer receive the tips and sponsor lines; JSCPD_NO_TIPS joins CI as an environment switch and --no-tips stays. (#1008, #1029)
  • MCP: fully described tool definitions — titles, read-only annotations, parameter examples and descriptions that say when to use each tool; names and schemas are unchanged. See MCP Server. (#1028)

Bug Fixes

  • Config-file ignorePattern entries without * or ? silently did nothing — they were treated as relative paths; they are now applied verbatim, and an invalid regex prints a warning. See Configuration. (#997)
  • JavaScript/TypeScript files with a recoverable parse error could not match clean files — any parser diagnostic sent the file to the fallback tokenizer; tokens now come from the lexer whenever the parser did not fail outright. (#1023, #1024)
  • Markdown inherited the C comment style — a /* or // in prose swallowed the rest of the file; Markdown now has no comment syntax. (#1026)
  • Vue template clones were reported with wrong ranges — the html token stream is now in source order and the wrapper tags are left out of it, so a template clone gets the template's own line range. (#1031)

Other

  • Runnable demos under fixtures/ — every feature and fix above ships a demo directory with a README listing each command and its expected output.
  • Docs: ignore patterns and inline markers are documented in the v5 reference. (#993, #996)

Thank You ❤️

v5.1.2

Released 2026-09-03

New Features

  • Linux ARM64 musl prebuilt binaries — npm installs on Alpine and other musl-based ARM64 Linux systems now get a native binary from the new jscpd-linux-arm64-musl package, bringing the prebuilt platform count to 8. See Platform Binaries. (#988)
  • Docker image ghcr.io/kucherenko/jscpd — a multi-arch (amd64/arm64) distroless image built from the release binaries ships with every release, tagged latest, 5, 5.1 and the exact version, with SLSA provenance and an SBOM. See Using Docker. (#988)
  • cargo-binstall metadata — the crate now points cargo binstall jscpd at the release tarballs. The 5.1.2 metadata still makes binstall fall back to compiling from source because the crate ships two binaries from one archive file; a follow-up release fixes the layout. (#988)

Bug Fixes

  • jscpd --version and --help now say jscpd — the command name was the literal cpd, so jscpd --version printed cpd 5.1.1. The name now follows the invoked executable. (#988)
  • Windows: drive-anchored --pattern values are treated as absolute — patterns like C:\src\**\*.ts were never recognized as absolute because of an impossible character check. (#988)
  • pre-commit hook passed v4-only flags — the hook definition still used --gitignore --exitCode, which the v5 CLI rejects; it now passes --exit-code 1. (#989)
  • Unsupported-platform error is actionable — the npm launchers now name the host (os/arch (libc)), list the supported platforms and point to cargo install jscpd. (#988)

Other

  • Repository split: master is v5-only — the TypeScript v4 engine moved to the master-v4 branch and releases from there under the latest-4 npm dist-tag. See jscpd v4. (#989, #990)
  • Floating v5 tag for the GitHub Action — uses: kucherenko/jscpd@v5 follows the latest 5.x release. (#988)
  • Signed release assets — every release archive carries a Sigstore keyless signature (<asset>.sigstore.json) in addition to SLSA provenance. (#988)
  • crates.io metadata and CI — all crates declare repository, docs, keywords and categories; Windows joined the PR build matrix; a smoke test runs the release binary against the fixtures corpus; nightly cargo audit / cargo deny. (#988)

v5.1.1

Released 2026-08-31

Bug Fixes

  • jscpd on npm installed the 5.0.16 engine instead of 5.1.0 — the jscpd wrapper package published its optionalDependencies pinned to the 5.0.16 platform binaries, so npm i jscpd@5.1.0 resolved a native binary one release behind and jscpd --version reported cpd 5.0.16; everything 5.1.0 fixed was therefore absent for jscpd users, including the Windows --baseline-from-ref fix. The cpd package and the platform packages were unaffected. The release script now updates the wrapper's version and its platform pins independently and fails the release if any pin disagrees with the release version.

Other

  • Declared MSRV corrected to 1.96 — the workspace advertised rust-version = "1.87" on crates.io, a floor the crate could not build on (the oxc parser crates require 1.96). CI now builds at exactly the declared MSRV so it cannot drift again.

v5.1.0

New Features

  • Clone baseline (--baseline, --update-baseline, --fail-on-new-clones) — gate CI on new duplication only: a committed baseline file records fingerprints of accepted clones, and only clones absent from it fail the build, so legacy duplication is tolerated while regressions are caught. See Gate on New Duplication. (#944)
  • Ephemeral baseline from a git ref (--baseline-from-ref) — stateless PR gating without a committed file: scans the base ref in a temporary worktree and reports clones that don't exist there. (#944)
  • OpenMetrics reporter (--reporters openmetrics) — duplication gauges in the OpenMetrics text format, ready for GitLab CI artifacts:reports:metrics. See OpenMetrics Reporter. (#422)
  • CodeClimate / GitLab Code Quality reporter (--reporters codeclimate, alias gitlab) — gl-code-quality-report.json for GitLab's artifacts:reports:codequality, so duplicates appear as code quality issues in merge requests. See CodeClimate Reporter. (#958)
  • Windows on ARM support — npm installs pick a native aarch64-pc-windows-msvc binary on Windows ARM64. (#963)
  • Config discovery in .config/ — jscpd also finds its config at .config/jscpd.json per the dot-config convention; a root .jscpd.json still wins. (#979)

Bug Fixes

  • Unknown --format values warn instead of silently scanning 0 files — --format cs (or any typo) no longer looks identical to a clean scan in CI. (#964)
  • Nix flake builds again — the Rust toolchain is pinned to an exact patch version, so the manifest hash can no longer drift. (#976)
  • Windows: --baseline-from-ref no longer reports every clone as new — Windows verbatim paths were truncated by the format-suffix stripper, silently breaking snippet reads behind the baseline fingerprints; fingerprints are also line-ending agnostic now.

Thank You ❤️

v5.0.16

New Features

  • MCP server over stdio (--mcp) — jscpd --mcp /path/to/project serves the Model Context Protocol on stdin/stdout, the transport MCP clients like Claude Desktop, Claude Code, and Cursor spawn and manage themselves — no port, no network policy. The project is scanned once at startup and kept in memory, so snippet checks answer in milliseconds. Tools: check_duplication, get_file_clones, get_statistics, check_current_directory. See MCP Server. (#891)
  • Codebase summary (--summary) — opt-in refactoring-hotspot overview appended to the run output: top files and folders ranked by tokens, lines, size, or a token-based complexity estimate, with each file's duplication share. --summary-top <n> sets the list length, --summary-by tokens|lines|size|complexity picks the metric. Renders in console/console-full, compactly in the ai reporter, and as an additive summary key in the JSON report. (#934)
  • Isolated folder groups (--skip-isolated) — skip duplication between monorepo folders owned by different teams: --skip-isolated "packages/team-a|packages/team-b" drops clones spanning two different folders of the same group, while duplication inside one folder or against shared code is still reported. Config file: "skipIsolated": [["packages/a", "packages/b"]]. (#628)

Security

  • Supply-chain hardening (OpenSSF Scorecard) — GitHub Actions pinned to commit SHAs, least-privilege workflow tokens, SECURITY.md with private vulnerability reporting, protected master branch

Thank You ❤️

  • @hanzhangyu for proposing isolated folder groups for monorepos and contributing the original skipIsolated implementation (#628)

v5.0.15

New Features

  • SARIF: size-based severity — new --sarif-error-tokens <N> flag: clones with at least N tokens are reported at level error while smaller ones stay warning; when duplication exceeds --threshold, all results become error. (#908)
  • SARIF: clone fingerprints — each result carries a partialFingerprints entry (jscpdCloneHash/v1) for cross-run result identity in GitHub code scanning. (#909)
  • SARIF: related-location messages & richer rule metadata — counterpart locations get linked messages, and the jscpd/duplicate-code rule has full metadata for SARIF viewers. (#911, #914)

Bug Fixes

  • Scan-root-relative report paths — report paths are relative to the scanned directory again (as in 4.x); fixes empty snippets and unresolvable paths when scanning from outside the target directory. (#872, #892)
  • Report version stamping — SARIF and HTML report versions now match --version. (#915)
  • Multi-root blame attribution and git root discovery fixes

Thank You ❤️

  • @chrisc-onaorg for the SARIF fingerprints, related-location messages, and rule metadata
  • @darronz for the scan-root-relative paths fix
  • @nvuillam for proposing size-based SARIF severity

v5.0.14

New Features

  • Cross-format clone detection — --cross-formats defines groups of related formats sharing one comparison pool, e.g. --cross-formats "javascript,typescript" or the js-ts preset (javascript,jsx,typescript,tsx). When a group mixes TypeScript with JavaScript, TS files are compared with erasable type syntax stripped, so function f(a: number): void matches function f(a). Also configurable as crossFormats in .jscpd.json / package.json. See Configuration → Cross-Format Groups. (#810)

Bug Fixes

  • Prose-only Markdown files are now analyzed — .md files without fenced code blocks were silently skipped; duplicated prose is now detected as clones, while embedded code fences keep being detected under their own sub-format pools. (#883)

v5.0.6 – v5.0.13 highlights

  • v5.0.11 — Razor (.razor) tokenizer support
  • v5.0.10 — scan-root-relative paths in all reporters; --skip-local now matches v4 semantics
  • v5.0.9 — GitHub Action for jscpd v5; fixed platform binary resolution for nested installs
  • v5.0.8 — fixed mmap exhaustion on very large repos; fixed --pattern matching with absolute scan roots
  • v5.0.7 — fixed stack overflow on deeply-nested JS/TS; --max-size defaults to 1mb
  • v5.0.6 — v4 config backward compatibility (path, pattern, ignore, ignorePattern); jscpd npm wrapper package; memory-mapped I/O and SIMD line counting
  • v5.0.12 / v5.0.13 — dependency updates and npm packaging fixes

Full details for every release are on GitHub Releases and in the Rust changelog.

v5.0.4

The Rust Rewrite

jscpd v5 is a complete Rust rewrite — a new Rust engine shipped as a self-contained native binary with zero Node.js dependency, bringing parallel detection, near-zero startup cost, and a large performance improvement over the TypeScript engine.

See the Migration Guide for what changed and how to upgrade.

New Features

  • Native binary — no Node.js runtime required. Install via npm install -g jscpd, cargo install jscpd, or direct download.
  • 13 reporters — console, console-full, json, xml, csv, html, markdown, sarif, ai, badge, xcode, threshold, silent.
  • OXC-based JS/TS tokenization — JavaScript/TypeScript/JSX/TSX tokenization now uses the OXC parser instead of PrismJS for better accuracy and speed.
  • Parallel detection — --workers N controls thread count. Detection runs in parallel across format groups.
  • CLI alignment — new --absolute, --ignore-case, --formats-exts, --formats-names flags; fixed --threshold; improved --max-size.
  • Side-by-side blame — console-full reporter shows side-by-side blame comparison with --blame.
  • Dual npm package — jscpd@5 and cpd both install the same Rust binary via platform-specific optional dependencies.

Breaking Changes from v4

  • No Node.js API — import { jscpd } from 'jscpd' is not available in v5. Use the CLI (with JSON output) or the Rust crates instead.
  • No --store leveldb — external store backends are not supported in v5 (detection is fast enough without caching).
  • Reporter name change — v4's full reporter is now console-full in v5.
  • Output filenames — HTML reporter outputs jscpd-report.html at the output directory root.
  • Token counts — may differ by 1-2% due to the Rust tokenizer using OXC for JS/TS.
  • Format count — 223 formats (vs. v4's 224).

v5.0.3

  • Full Rust CLI feature parity with TypeScript jscpd
  • Cross-platform binary distribution via npm platform packages
  • CLI short-form aliases matching TypeScript jscpd conventions
  • Time reporter for execution timing
  • ReportContext struct for extensible reporter signatures

v5.0.2

  • Fixed Vue SFC tokenization (each block dispatched 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

v5.0.1

  • 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
  • Shebang detection for extensionless scripts

v5.0.0

  • First stable Rust release — replaces the TypeScript CLI with a native binary
  • Reporter trait signature changed to use ReportContext instead of Statistics directly

v4.2.5

  • JSON reporter token count fix, gitignore parent-directory walk, Commander v15, Vitest 4.1.0

v4.2.3

  • fix(finder): resolve relative ignore patterns against scan dirs (#611)
  • Dependency bumps across all packages

v4.2.2

  • fix(tokenizer): resolve quadratic bash tokenization hang

v4.2.1

  • fix(tokenizer): fix tokenization issue for cross-format detection

v4.2.0

New Features

  • Custom tokenizer backend — @jscpd/tokenizer now uses a self-contained reprism-based 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 tokenized per-block/per-section, enabling duplicate detection between embedded blocks and standalone source files.
  • 223 supported formats — Apex, CFML/ColdFusion, GDScript, Svelte, Astro, and 70+ additional languages (up from 152). Run jscpd --list to see the full list.
  • Shebang detection — extensionless executable scripts are auto-detected via their #! shebang line.
  • --store-path — specify a custom directory for the LevelDB token cache, eliminating collisions when multiple jscpd processes run concurrently.
  • --skipComments — shorthand for --mode weak. Strips comments before tokenization.
  • --formats-names — map specific filenames (e.g. Makefile, Dockerfile) to a detection format.

Bug Fixes

  • Entire-file duplicates silently dropped (#728) — fixed RabinKarp end-of-file flush bug in @jscpd/core.
  • ReDoS hang on Lisp/Elisp files (#737) — replaced catastrophic backtracking with a linear regex in @jscpd/tokenizer.
  • Process crash on malformed package.json (#739) — now emits a warning and continues.
  • Vue SFC cross-file detection broken — blocks now use resolved sub-format namespace.
  • Vue SFC incorrect column numbers — fixed in @jscpd/tokenizer.
  • 50 dependency security vulnerabilities remediated.

v4.1.1

  • Update hash function, improve performance and keep browser support.

v4.1.0

  • AI reporter (--reporters ai) — compact, token-efficient output for LLMs.
  • --noTips flag — suppress usage-tip messages after detection.
  • Execution timer — timing summary shown after detection completes.
  • Tokenizer speed — lazy Prism grammar loading, O(1) extension lookup, native MD5 hashing.
  • New languages: Apex and CFML (ColdFusion).
  • Test coverage raised to 98%+.

Earlier releases (v1.x – v4.0.x)

See the root CHANGELOG on the master-v4 branch for the full history of jscpd versions 1.0.0 through 4.0.9, covering the tool's origins, the TypeScript rewrite, LevelDB store, pluggable reporters, monorepo extraction, and all the improvements made from 2018 through 2024.