npm / jscpd
jscpd
What changed in jscpd 4 to 5, from its official release notes, upgrade guides and advisories. Sources checked 2026-10-18.
14 breaking6 security1 deprecations120 new APIs and options4 performance15 behavior and defaults8 tooling70 fixes and other
See which of these apply to your code, ranked, in your agent:
npx @unstale/cli initjscpd 5211 changes
Breaking changes 11
The minimum Rust version is now 1.97.
Compares function structure across 15 languages and supported code blocks using Jaccard similarity. It finds every pair meeting the ratio; scores differ from 5.4.0, and a ratio of 1 now reports identical structures.
Similarity pairs now use their own SARIF rule instead of
jscpd/similar-code, changing alert identity for GitHub code scanning and Code Climate check names.Ignore-pattern matching in files with CRLF endings now removes the intended tokens, which can change clone token counts.
Dead-code configuration precedence is flag over the deadCode section over older top-level keys; misspelled keys in the section are reported by name.
Unknown formats, nonexistent scan paths, and reporter file-write failures now print an error and exit 1 instead of passing with an empty report.
The declared minimum Rust version is corrected to 1.96, the floor required to build the crate.
The CLI binary and platform npm packages are now named jscpd / jscpd-* instead of cpd / cpd-* to avoid antivirus false positives.
When absolute is false or unset, all reporters now emit paths relative to the canonicalized scan root and strip leading ./ or .\ components.
Now filters clones whose fragments are under the same scan root, matching jscpd v4 TypeScript semantics rather than limiting the filter to the same parent directory.
File-level globs and code-level regex patterns are no longer conflated.
Security 4
An API key echoed by an embeddings API in an error is now redacted.
Dashboard and health reports, including markdown and HTML output, escape untrusted file paths, custom format names and external metric IDs before rendering.
Each release archive and
checksums.txtnow has a Sigstore keyless signature that can be verified with this command.The SARIF schema URI now uses HTTPS, avoiding "untrusted URI" errors in SARIF-consuming tools.
Deprecations 1
The ineffective flag is hidden from help and warns when passed, but remains accepted; use
--threshold,--min-lines, or--min-tokensfor the stated purposes.
New APIs and options 113
Selects the file used to save or read the baseline for
--changed.Reports clones involving files listed by
git status, including staged, unstaged and untracked files, while still scanning every file. A baseline marks clones absent from HEAD as[NEW].Scans only changed files and matches them only against one another.
The MCP server adds
compare_folders(left, right), which returns the JSON report of--comparefor two folders.The
-r ednreporter writesjscpd-report.edn, including similarity candidates with scores, languages, locations and node counts, plus clones found by other passes.MCP tools accept clone kinds to report, including exact, renamed, similar, semantic, or type1 through type4.
Sets the smallest normalized function tree considered by similarity matching; defaults to 20.
The JSON report now gives the node count of both functions in a similarity result.
Sets the base name for JSON, XML, CSV, HTML, Markdown, SARIF and EDN report files to avoid collisions in a shared reports folder.
An installable agent skill guides test-first codebase ports, binding tests to code by coverage and selecting functions to port from
--compare.An installable agent skill explains how
--comparepairs functions and how to check a comparison.Compares two codebases by pairing functions with the semantic model, reporting counterparts, unmatched functions, similarity levels, and functions ready to port.
Starts a language server that reports diagnostics for edited buffers, supports configurable analyses, and provides clone actions and project-wide custom requests.
Selects which language-server analyses to enable; clones are enabled by default, and editor settings can override this option.
Lowers smaller clone diagnostics from warnings to information in the language server.
Provides the API key for semantic embeddings requests; jscpd reads it only from this environment variable.
The
semanticvalue filters results to semantic clones.Replaces the instruction prefix jscpd adds before functions for models that expect one.
Downloads a semantic embedding model before scanning; the default model runs locally on the CPU.
Selects a semantic embedding model by its listed name, Hugging Face id, or Ollama name.
Lists the calibrated semantic models, including their thresholds, licenses, and execution locations.
Re-embeds all functions and replaces the semantic vector cache file at once.
Sets the minimum cosine similarity for a semantic clone pair within one language; thresholds vary by model.
Restricts semantic clone pairs to the same language or to different languages.
Sets the minimum cosine similarity for a semantic clone pair across languages; thresholds vary by model.
Sends functions to an OpenAI-compatible embeddings API; the API key is read only from
JSCPD_SEMANTIC_API_KEY.Experimental semantic clone detection embeds supported functions and reports sufficiently similar pairs as semantic clones.
The
semanticconfig key enables experimental semantic clone detection.The dead-code config section accepts a Rust diagnostics file, allowing Rust compiler diagnostics to be used for dead-code reporting.
jscpd accepts compiler JSON diagnostics via a file or pipe for dead-code, dashboard, and health runs, enabling Rust dead-code reporting without running cargo.
basta discovers framework definitions from basta.frameworks.yaml, basta.frameworks.yml or basta.frameworks.json in the working directory.
The standalone basta binary accepts -c, --config <file> to select its configuration file.
The .jscpd.json deadCode setting can be an object with enabled, categories, confidence and line thresholds, entry and ignore settings, reporting controls, and framework settings. The object enables the mode only when enabled is true; the existing boolean switch still works.
Treats a named framework as present, useful when a scan starts below its package.json.
Loads custom framework definitions from a YAML or JSON file; definitions can add frameworks or replace built-ins with the same name.
Framework definitions can declare names looked up in code; declarations with those names are treated as used, and the names-and-files form scopes them to selected files.
Prints the list of framework definitions.
Turns framework detection off.
The dashboard JSON adds project.largestFiles, containing the ranked list of largest code files.
Dead-code threshold can be set inside deadCode; if omitted there, a dead-code run still falls back to the top-level threshold.
The dead-code analysis engine is also available as a standalone command.
Reports complexity without running a clone scan, providing the complexity portion of
--summary.Adds a one-run report combining health information, project size, duplication by format, complex files and dead-code findings by category.
Finds unused files, exports and imports across JavaScript, TypeScript, Vue, Svelte, Astro and Python using the project's import graph and real entry points; findings include confidence scores.
Adds a 0–100 health score with an A–E grade based on duplication, dead code and complexity, adjusted for project size and excluding unmeasurable dimensions.
Lets you plug custom metrics, such as coverage or security scan results, into the health score.
Adds HTML output for both dashboard and health reports.
Filters clones by whether they were found as exact, renamed or similar matches; similar matches distinguish gap merges from structural matches, and typos are caught.
Adds markdown output for both dashboard and health reports.
Makes a run fail when no files were analyzed, so a mistyped path or over-broad ignore cannot look like a clean run.
Adds a GitHub Action input that makes a run fail when no files were analyzed.
Makes a run fail when no files were analyzed, so a mistyped path or over-broad ignore cannot look like a clean run.
Scans every commit in a Git range and prints duplication trends, per-commit changes, and how far --threshold could be tightened without failing the build.
Adds a flag to narrow the commit range scanned by --history.
Adds a flag to narrow the commit range scanned by --history.
Adds a flag to narrow the commit range scanned by --history.
The JSON reporter includes history points under a new history key.
The GitHub Action accepts a history input for duplication trend scans.
The MCP check_duplication tool accepts the similarity argument for function-level similarity detection.
Reporters distinguish renamed and similar clones; JSON and XML include clone metadata, statistics expose renamedClones and similarClones, and SARIF and Code Climate use dedicated rules.
Drops supported annotation runs before hashing so code blocks that differ only in annotations can be detected as renamed clones.
Normalizes identifiers before hashing so code blocks that differ only in names can be detected as renamed clones.
Normalizes literal values before hashing so code blocks that differ only in literals can be detected as renamed clones.
Drops supported annotation runs before hashing so code blocks that differ only in annotations can be detected as renamed clones.
Normalizes identifiers before hashing so code blocks that differ only in names can be detected as renamed clones.
Normalizes literal values before hashing so code blocks that differ only in literals can be detected as renamed clones.
Every clone carries a kind of exact or renamed; runs without the new normalization flags remain unchanged apart from the additive exact JSON field.
Merges nearby clone fragments from one file pair into a similar clone when at most N unmatched lines separate them in both files; defaults to off.
Merges nearby clone fragments from one file pair into a similar clone when at most N unmatched lines separate them in both files; defaults to off.
Compares JavaScript and TypeScript functions, methods, and arrow functions by syntax-tree 4-grams and reports pairs meeting the ratio as similar clones.
Compares JavaScript and TypeScript functions, methods, and arrow functions by syntax-tree 4-grams and reports pairs meeting the ratio as similar clones.
npm installs on musl-based ARM64 Linux systems now select a native prebuilt binary from this platform package.
Compare current clones with fingerprints scanned from a base Git ref using a temporary detached worktree, without a committed baseline file.
Use a committed baseline of accepted clone fingerprints so CI can distinguish new duplication from legacy duplication.
Configure the committed clone baseline file with the
baselineconfig key.Configure the Git ref used as an ephemeral clone baseline.
Add
.config/.jscpd.jsonto automatic config discovery.Add
.config/jscpd.jsonto automatic config discovery, after the root.jscpd.jsonand before thepackage.jsonjscpdkey.Exit 1 when new clones exceed the specified count, defaulting to zero, independently of
--threshold.Configure the maximum number of new clones allowed before the run fails.
npm installs on Windows ARM64 now select a native binary from this platform package.
Report new clones through console markers and counts, JSON fields and statistics, SARIF errors, and OpenMetrics gauges.
Select a CodeClimate/GitLab Code Quality reporter that writes
gl-code-quality-report.jsonwith clone issues and deterministic fingerprints.Add
gitlabas an alias for the CodeClimate/GitLab Code Quality reporter.Select a reporter that writes OpenMetrics text exposition to
jscpd-metrics.txt, including duplication and detection-duration gauges.Rewrite the baseline from the current run, creating it if missing, and print added and removed fingerprint counts.
The MCP server provides a tool that returns the clone list.
The MCP server provides snippet checks that accept format names or file extensions.
The MCP server provides clone lookup for one file, a capability described as new over the HTTP server.
The MCP server provides a statistics tool.
Starts an MCP server over stdio, scanning the project once and keeping detection-ready token hashes in memory for snippet checks. Standard detection options apply to the scan and snippet checks.
Accepts comma-separated isolation groups of pipe-separated folders and drops clones whose fragments fall under different folders of the same group. It also applies to MCP project scans.
Accepts a nested-array shape to declare isolated folder groups in the config file.
Opts into a refactoring-hotspot overview appended to run output, ranking top files and folders by a selected metric and showing each file's duplication share.
Selects whether the codebase summary ranks by tokens, lines, size, or a token-based cyclomatic-complexity estimate.
Sets the list length for the codebase summary.
The config-file setting enables the refactoring-hotspot summary.
When the summary flag is enabled, the JSON report adds a
summarykey; it is absent when the flag is off, preserving the schema for existing consumers.Selects the codebase summary ranking metric in the config file.
Sets the codebase summary list length in the config file.
SARIF results include an order-insensitive clone hash for cross-run identity, alongside token_count and clone_hash in the properties bag.
Reports clones with at least N tokens as SARIF errors and smaller clones as warnings; when overall duplication exceeds --threshold, all SARIF results are errors.
Sets the token count at which clones are reported as SARIF errors; when overall duplication exceeds --threshold, all SARIF results are errors.
Detect clones across related formats using comparison groups, including the js-ts preset; TypeScript is compared with erasable type syntax stripped when grouped with JavaScript.
Configure cross-format clone detection in
.jscpd.jsonorpackage.jsonwith a string, array of strings, or array of arrays.Adds a tokenizer for Razor files that parses HTML content and Razor keyword blocks.
The path config field reads scan directories and resolves relative paths against the config file's directory.
Adds the --absolute CLI flag as part of alignment with jscpd v4.
The console reporter displays a clone list.
The console-full reporter adds side-by-side blame comparison.
Adds the --formats-exts CLI flag as part of alignment with jscpd v4.
Adds the --formats-names CLI flag as part of alignment with jscpd v4.
Adds the --ignore-case CLI flag as part of alignment with jscpd v4.
Performance 3
The module index can quickly determine whether a path could name a module under any extension or as a directory index, avoiding expensive resolver misses for catch-all aliases.
basta checks whether an alias pattern matches an import before comparing paths, substantially reducing dead-code scan time on projects with many aliases without changing findings.
Memory-mapped file I/O, SIMD line counting, a parallel detection pipeline, and avoiding tokenizer source-string clones improve performance and reduce allocations.
Behavior and default changes 12
The MCP tool now finds every copy of a snippet instead of matching one of two.
The Project section ranks the largest code files by lines, with tokens and size; json, markdown and html reporters include the same list.
basta detects frameworks from built-in definitions and keeps files they load alive, checking each scanned-file directory independently.
basta reads the deadCode section from the same configuration file as jscpd and searches .jscpd.json, .config/jscpd.json or package.json by default.
The top-level minLines remains a clone-reporting setting and is not applied to dead-code runs; dead-code minLines is configured inside deadCode.
Nitro framework detection follows srcDir and treats tasks/ as framework-loaded.
The
--summary --summary-by complexityestimate more closely tracks cyclomatic complexity, accounting for short-circuit operators and language-specific branches and measuring complexity per function.Tips and sponsor lines print only on an interactive terminal, preventing them from appearing in piped output, files, CI logs, or agent hooks.
The counterpart location gets a message and the primary message links to it, enabling GitHub code scanning to display the relationship.
A CLI option now accepts an optional integer value, with distinct exit codes; two options are now independent.
Several previously unsupported v4 configuration fields are now read and applied, matching jscpd v4 behavior.
Improves the --max-size CLI flag.
Tooling 7
The release publishes platform wheels installable with pip or uvx, providing the Rust engine without Python code or a Node.js runtime.
The four MCP tools now include titles, read-only annotations, parameter descriptions with examples and defaults, and descriptions of use and return values; server instructions describe the workflow.
The crate now provides cargo-binstall metadata so this command downloads a prebuilt binary instead of compiling the oxc parser stack from source.
A multi-arch amd64/arm64 distroless Docker image built from release binaries is published with every release.
The SARIF rule now includes a display name, full description, default configuration, and quality tags for improved presentation in SARIF viewers and Azure DevOps.
A GitHub Action is available to scan repositories for copy/paste in CI.
An npm package publishes the same Rust binary with v5.x versioning.
Fixes and other changes 60
Comparison fixes include counting test helpers among call namesakes, recognizing .NET test projects wherever
Testsappears in a dotted name, and aligning console and HTML percentages; declarations without bodies no longer count as functions.Imports referenced in TSDoc links and JavaScript JSDoc types now count as used; documentation-only references do not make a function live.
Git subprocesses drop variables exported to hooks, refs or history ranges beginning with
-are refused, and worktree cleanup preserves the user's other worktrees.Ignored ranges now align with offsets in Markdown fences, component scripts and Razor blocks, so the intended tokens are excluded.
The MCP server no longer ignores
--semantic.C, C++ and C# scans no longer treat control-flow keywords around preprocessor directives as phantom functions or count every matching keyword call.
Svelte store reads using
$nameand template-literal placeholders in component markup now count as uses, avoiding false unused-symbol findings.Files under a scanned path that is nested inside another scanned path are read once instead of twice, preventing self-clones and duplicate semantic embeddings.
JSON report positions for files with CRLF endings now use true byte offsets.
Embedded sources now count only the lines occupied by their code blocks, and clones count the block lines they cover, preventing surrounding prose or templates from inflating line totals.
Duplicated-line counts now include both the start and end lines of each clone, so totals rise by about one line per clone and percentages rise with them.
Alembic is detected as a framework, preventing its loaded files from being reported unused.
Django is detected as a framework, so migrations, management commands, admin.py, apps.py, template tags and modules named by dotted settings paths are not incorrectly reported unused.
The markup block of a component file tokenized as html is excluded from the duplication share, matching css and scss blocks.
Markup, stylesheet and template files now have complexity 0 and are excluded from the health score's code count; component script blocks still count.
The JSON report can include path-appears-in-string as a reason when a file's extensionless path appears in a string literal; this reduces its confidence by 40 points.
Static require calls using a template literal with no interpolation are recognized.
Scrapy is detected as a framework, preventing its loaded files from being reported unused.
Arrow functions in component attributes no longer cause the tag parser to mistake the arrow's > for the end of the tag and fail parsing the script.
A leading slash in a Vite alias target is resolved from the project root rather than the filesystem root, preventing imported components from being reported unused.
A wxt.config.* marks entrypoints and auto-import directories as entries, honors srcDir, entrypointsDir and imports: false, and declares conventional aliases.
Plain text, log and CSV files now handle comments differently; the notes do not specify further details.
With
--fail-on-empty, complexity mode writes its reports before failing, matching other modes.Dashboard and health reports omit the dead-code section rather than failing when
--formatexcludes every analyzable language.Excluding markup duplication from scoring now properly excludes it on both sides of the calculation.
A mistyped field no longer invalidates the whole configuration; only the bad field is dropped and the rest still applies.
Values above 100 are clamped with a warning everywhere the option is read, not only in standalone dead-code mode.
Windows report paths now use forward slashes consistently with other platforms.
Clone growth now checks whether the clone's own anchor continues and starts a new clone otherwise, preventing N-way copies from losing pairs or producing mismatched ends.
Prints the --history block, matching the console reporter.
Files retain the path they were found at, and each real file is scanned once, avoiding renamed paths, duplicate sources, and self-clones through symlinks.
Replaces XML 1.0-invalid characters with U+FFFD, splits ]]> across CDATA sections, and escapes paths once so generated reports can be parsed.
Config-file entries are now applied verbatim instead of being treated as relative paths, so patterns without * or ? work; invalid regexes produce a warning.
Files with recoverable parser diagnostics now use lexer tokens unless parsing fails outright, allowing them to match clean files.
Markdown no longer inherits C comment syntax, preventing prose containing /* or // from swallowing the rest of a file during clone detection.
Vue template tokens are kept in source order and wrapper tags are excluded, correcting template clone line ranges and preventing script and style bodies from counting as duplicated HTML.
The pre-commit hook now passes the v5 CLI flag
--exit-code 1instead of rejected v4-only flags, so repository hooks no longer fail on every run.The version output and help usage line now use the invoked executable name, jscpd or cpd, instead of always saying cpd.
On Windows, drive-anchored pattern values such as
C:\src\**\*.tsare now treated as absolute instead of also receiving a relative**/variant.When no prebuilt binary matches, npm launchers name the host, list supported platform keys, and point to
cargo install jscpd.The floating GitHub Action tag
v5follows the latest 5.x release and is moved on every stable release.The npm wrapper now pins its platform binaries to the release version, so installing jscpd no longer resolves a native binary one release behind.
On Windows, ephemeral baselines now match clone fingerprints instead of reporting every clone as new.
Fingerprint hashing strips CR characters, so committed baselines survive CRLF/LF differences between platforms.
Unsupported format values now produce a stderr warning naming the value and pointing to
--listinstead of silently scanning zero files.Pinning the Rust toolchain to an exact patch version fixes flake builds that failed when the mutable Rust manifest changed.
Walking up from a relative scan path no longer stops before reaching the repository root.
Git blame data is keyed by resolved path when multiple scan roots contain the same relative path, preventing the second root from inheriting the first root's authors.
The SARIF tool driver and HTML report versions now match cpd --version instead of using a hardcoded SARIF version.
Report paths are relative to the scanned directory while reporters can still resolve and read source files, fixing empty snippets and unresolvable paths, including in multi-root scans.
Markdown prose is now tokenized, so prose-only files are analyzed and duplicated prose can be detected as clones; embedded code fences remain detected under their own sub-format pools.
Lisp, Clojure, Scheme, and Racket semicolon comments are recognized as comments, allowing --mode weak and --skip-comments to drop them.
When installed as a nested dependency, the runner locates the platform-specific binary relative to the installed package instead of assuming a top-level install.
The walker opens and drops each file mapping within the processing closure, capping concurrent mappings to the thread-pool size and preventing mmap exhaustion crashes.
Patterns match against both the relative path and the full absolute path when the scan root is absolute; bare patterns gain a prefix to match at any depth.
Scanning directories with deeply nested JS/TS files no longer risks stack overflow; parsing uses a local rayon pool with 64 MiB stacks.
Files exceeding the size limit are skipped during directory walking, preventing OXC from processing megabyte-scale generated files.
Detection and statistics are aligned with jscpd for consistent output across Rust and TypeScript versions.
The HTML reporter now outputs jscpd-report.html at the output_dir root.
Fixes the --threshold CLI flag.
jscpd 427 changes
Breaking changes 3
The server now binds to 127.0.0.1 by default.
Custom mappings that assign .vue to markup no longer take effect because .vue is handled by the dedicated vue format processor.
Vue SFC blocks are tokenized and reported under their resolved sub-format names instead of markup; tooling relying on markup reports must be updated.
Security 2
Adds a Host allowlist option for DNS-rebinding protection on both /mcp and the REST API.
Adds an Origin allowlist option for DNS-rebinding protection on both /mcp and the REST API.
New APIs and options 7
Adds a config key to override automatic ANSI color detection; explicit flag or config settings take precedence over FORCE_COLOR, NO_COLOR, and TTY detection.
Adds a flag to override automatic ANSI color detection.
Adds a flag to disable ANSI colors, overriding automatic detection.
Maps specific filenames, such as Makefile and Dockerfile, to a detection format.
Extensionless executable scripts are detected from their #! shebang line and tokenized in the corresponding language.
Adds a shorthand flag for --mode weak, which strips comments before detection.
Configures a custom directory for the LevelDB cache, avoiding collisions when multiple jscpd processes run in parallel on the same machine.
Performance 1
The new self-contained reprism-based grammar engine replaces prismjs and improves tokenization speed by about 11.5% on real projects.
Behavior and default changes 3
ANSI colors are disabled automatically when stdout is not a TTY, including for the statistics table.
jscpd-server migrates to the official MCP SDK v2 and stateless 2026-07-28 revision, with a legacy fallback for 2025-era clients.
Vue, Svelte, Astro, and Markdown files are tokenized per block or section, allowing their code to match files in the corresponding language.
Tooling 1
Support increased from 152 to 223 formats, including Apex, CFML/ColdFusion, GDScript, Svelte, and Astro.
Fixes and other changes 10
Skips the progress announcer for ai and consoleFull reporters that print every clone themselves, preventing duplicate output.
Server output now respects NO_COLOR and FORCE_COLOR and detects TTY status like the CLI.
Fixes off-by-one source line counts in statistics and reports.
Ignore files in parent directories up to the repository root are combined with scan-directory ignore files; Git info and global excludes are also read.
Duplicate token counts in JSON output are now computed from token positions.
The Commander upgrade enables modern Node.js compatibility.
Complete copies of files are detected instead of being silently dropped when RabinKarp flushes a pending clone.
Replaces a catastrophically backtracking Lisp string regex with a linear pattern, preventing hangs on unterminated strings.
Invalid JSON now produces a warning and an empty config instead of an unhandled SyntaxError that crashes the process.
Tokens on the first line of a Vue SFC block now use file-absolute rather than block-relative column numbers.