Changelog
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-codereads Rust dead code from the compiler. jscpd does not parse Rust and does not need to: everycargo checkalready prints lines likefunction `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--dashboardand--health, so a Rust project gets its health badge from the check it already runs, and the config file takes the file asrustDiagnostics. See Project Health Score and Project Dashboard. (#1089)- Findings from a Rust test target are treated as test code, reported only with
--include-testsand 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
tsblock 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 thetypescriptrow 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 printed10 linesfor 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
- The dead-code engine in this release is basta 0.3.0, which is also published on its own:
bastaon npm, crates.io and GitHub.
v5.3.1
Released 2026-09-21
New Features
--dead-codenow 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 thepackage.jsondependencies, or from its section inpackage.json, separately for each package of a monorepo. The console report names what it found, for exampleFrameworks: next (apps/web), vitest. (#1087)- Names a framework reads are never reported. Next calls a page's
getServerSideProps, Remix a route'sloader, Angular a component'sngOnInit. 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.jsoncan hold dead-code settings in a section of their own. ThedeadCodekey (alsodead-codeorbasta) 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 standalonebastabinary reads the same section. See Configuration. (#1087)--dashboardlists 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-codewas 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
ifor a media query'sandwere counted as branches, so an HTML page could top the "Most complex files" list. (#1081)
Other
- The dead-code engine in this release is basta 0.2.0, which is also published on its own:
bastaon npm, crates.io and GitHub.
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 labeledn/arather 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 onlyexact,renamedorsimilarmatches, or narrowsimilarfurther 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-codeand also ships as its own standalonebastacommand. A dedicated guide is coming; for now see the full write-up in the changelog. (#1064)- Markdown and HTML reports for
--dashboardand--health— alongside the existingconsole,jsonandbadgeoutput, so either one can be posted as a PR comment or published as a static page. - More accurate complexity counting —
--summary --summary-by complexitynow counts branches the way each language actually spells them (Rustmatcharms, Swiftguard, Goselect, 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.jsonfields are now checked one at a time, so one wrong type (say, a string whereentrywants an array) drops just that field instead of the entire file. - Hardened
--dashboard/--healthoutput 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/--healthno longer fail outright when--formatrules 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-emptynow writes its reports before failing, matching every other mode.--min-confidenceabove 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'soxcparser crates bumped to 0.150.
Thank You ❤️
- @Dev-next-gen for fixing how plain text, log and CSV files handle comments (#1065)
v5.2.1
Released 2026-09-15
New Features
--history: duplication trend over git history —jscpd src --history v5.0.0..HEADscans 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--thresholdcould be tightened.--history-since,--history-everyand--history-limitnarrow the range, the JSON report carries ahistorykey, and the Action takes ahistoryinput. 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-emptyfails 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, sopip install jscpdanduvx jscpdget 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-symlinksrenamed and double-counted linked files — a file reached through a link was reported by its resolved real path, so the report and--ignoredisagreed 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--noSymlinkswas set. True in every 5.x release but undocumented, and it silently drops a corpus mounted through a symlink. CITATION.cffand 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
--summaryand 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-literalsand--ignore-annotations(config keysignoreIdentifiers,ignoreLiterals,ignoreAnnotations, Action inputs of the same names) find blocks that differ only in names, literal values or annotations. Every clone now carries akind,exactorrenamed; 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 mostNunmatched lines in both files are merged into one clone of kindsimilarwith asimilarityscore, so a copy with a line inserted in the middle is reported once instead of as two halves. Default0keeps 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 assimilarclones; names and literals do not take part, so a renamed copy scores1.0and a couple of inserted statements about0.75. The default1means exact matches only. The MCPcheck_duplicationtool takes the same argument. See MCP Server. (#999, #1021) - Clone kinds in every reporter — console,
ai, JSON, XML, HTML, Xcode, SARIF (rulesjscpd/renamed-codeandjscpd/similar-code) and CodeClimate all show whether a clone is exact, renamed or similar, and how a similar one was found (gaporast). (#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_TIPSjoinsCIas an environment switch and--no-tipsstays. (#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
ignorePatternentries 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 ❤️
- @7487 for skipping the tips on a non-terminal stdout (#1029)
- @zero-stroke for the Vue template clone ranges (#1031)
- @kwesolowski for the Markdown comment-style fix (#1026)
- @w3lld1 for documenting ignore patterns and inline markers (#996)
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-muslpackage, 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, taggedlatest,5,5.1and the exact version, with SLSA provenance and an SBOM. See Using Docker. (#988) cargo-binstallmetadata — the crate now pointscargo binstall jscpdat 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 --versionand--helpnow sayjscpd— the command name was the literalcpd, sojscpd --versionprintedcpd 5.1.1. The name now follows the invoked executable. (#988)- Windows: drive-anchored
--patternvalues are treated as absolute — patterns likeC:\src\**\*.tswere 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 tocargo install jscpd. (#988)
Other
- Repository split:
masteris v5-only — the TypeScript v4 engine moved to themaster-v4branch and releases from there under thelatest-4npm dist-tag. See jscpd v4. (#989, #990) - Floating
v5tag for the GitHub Action —uses: kucherenko/jscpd@v5follows 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
jscpdon npm installed the 5.0.16 engine instead of 5.1.0 — thejscpdwrapper package published itsoptionalDependenciespinned to the5.0.16platform binaries, sonpm i jscpd@5.1.0resolved a native binary one release behind andjscpd --versionreportedcpd 5.0.16; everything 5.1.0 fixed was therefore absent forjscpdusers, including the Windows--baseline-from-reffix. Thecpdpackage 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 (theoxcparser 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 CIartifacts:reports:metrics. See OpenMetrics Reporter. (#422) - CodeClimate / GitLab Code Quality reporter (
--reporters codeclimate, aliasgitlab) —gl-code-quality-report.jsonfor GitLab'sartifacts: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-msvcbinary on Windows ARM64. (#963) - Config discovery in
.config/— jscpd also finds its config at.config/jscpd.jsonper the dot-config convention; a root.jscpd.jsonstill wins. (#979)
Bug Fixes
- Unknown
--formatvalues 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-refno 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 ❤️
- @luchsamapparat for contributing Windows on ARM support (#963)
- @dmromanov for proposing the OpenMetrics reporter (#422)
- @beanaroo for proposing the GitLab / CodeClimate Code Quality report format (#958)
- @MRDGH2821 for proposing config discovery from the
.config/subfolder (#979) - @zbcoding for reporting the silent unknown-
--formatbehavior (#964) - @eaves-dropper for reporting the Nix build failure (#976)
v5.0.16
New Features
- MCP server over stdio (
--mcp) —jscpd --mcp /path/to/projectserves 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|complexitypicks the metric. Renders inconsole/console-full, compactly in theaireporter, and as an additivesummarykey 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.mdwith private vulnerability reporting, protectedmasterbranch
Thank You ❤️
- @hanzhangyu for proposing isolated folder groups for monorepos and contributing the original
skipIsolatedimplementation (#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 levelerrorwhile smaller ones staywarning; when duplication exceeds--threshold, all results becomeerror. (#908) - SARIF: clone fingerprints — each result carries a
partialFingerprintsentry (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-coderule 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-formatsdefines groups of related formats sharing one comparison pool, e.g.--cross-formats "javascript,typescript"or thejs-tspreset (javascript,jsx,typescript,tsx). When a group mixes TypeScript with JavaScript, TS files are compared with erasable type syntax stripped, sofunction f(a: number): voidmatchesfunction f(a). Also configurable ascrossFormatsin.jscpd.json/package.json. See Configuration → Cross-Format Groups. (#810)
Bug Fixes
- Prose-only Markdown files are now analyzed —
.mdfiles 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-localnow 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
--patternmatching with absolute scan roots - v5.0.7 — fixed stack overflow on deeply-nested JS/TS;
--max-sizedefaults to1mb - v5.0.6 — v4 config backward compatibility (
path,pattern,ignore,ignorePattern);jscpdnpm 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 Ncontrols thread count. Detection runs in parallel across format groups. - CLI alignment — new
--absolute,--ignore-case,--formats-exts,--formats-namesflags; fixed--threshold; improved--max-size. - Side-by-side blame — console-full reporter shows side-by-side blame comparison with
--blame. - Dual npm package —
jscpd@5andcpdboth 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
fullreporter is nowconsole-fullin v5. - Output filenames — HTML reporter outputs
jscpd-report.htmlat 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/tokenizernow 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 --listto 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. --noTipsflag — 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.