← All reviews

Repo review · Tested August 28, 2026

jscpd is a copy editor for copied code

jscpd is a local copy/paste detector that points maintainers to repeated source-code blocks and can enforce a CI threshold. The findings are useful; a zero exit code still needs supervision.

A blue pigeon railway signal operator steps on an orange pedal as two tracks carry the same distinctive sequence of freight cars.
jscpd works the switchyard, flagging the same train of code when it turns up on another track.

Copy-paste is cheap when it happens and expensive when the copies drift apart. A fix lands in one branch of the logic, misses its sibling three directories away, and turns a harmless shortcut into a maintenance mess.

jscpd is a local copy/paste detector for that problem. It scans source code for repeated token sequences, reports both file-and-line locations, and can fail a build when duplication crosses a configured threshold. The percentage is the easy part. The useful question is whether the locations point to code worth discussing, and whether CI can tell a valid scan from an empty one.

Tested: commit 56b65069a22073505f6813dfdfbf302880066dfb on August 28, 2026. Workflow: scanned a pinned mixed TypeScript, JavaScript, and JSX production monorepo, repeated the accepted configuration, verified every reported range, and exercised reporters, thresholds, configuration failures, and MCP. Result: 67 clone groups across 378 analyzed sources, with 16 of the 20 highest-impact groups judged plausible consolidation candidates. Several invalid or failed workflows still exited zero. Verdict: try it for local duplicate-code review; verify the report around CI thresholds.

A red pencil for source code

The project dates to 2013 and is MIT-licensed. By August 28, 2026, its GitHub repository showed roughly 6,000 stars and 253 forks, while npm listed version 5.0.16 with 104 dependents. Those numbers establish that jscpd is neither a weekend novelty nor an obscure parser nobody has put near automation. They do not establish that its findings are good.

The tested v5 release is a self-contained Rust binary distributed through npm and other package channels. According to the README, it supports 223 formats and 13 reporters, including JSON, HTML, SARIF, Markdown, CSV, badges, compact AI output, and console views. It also exposes a stdio MCP server and a summary mode. The same documentation says v5 does not include the v4 Node.js API or LevelDB and Redis stores.

jscpd works like a copy editor for copy-paste. It marks the two passages and shows where each appears; it does not decide whether the repetition is accidental, clearer in place, or ready to become an abstraction. That judgment still belongs to the maintainer, who at least receives page numbers instead of a vague note that the manuscript feels familiar.

The report found duplicate code worth reviewing

We built the primary scan around production TypeScript, JavaScript, and JSX, with a 50-token and five-line minimum, mild matching, JavaScript-to-TypeScript cross-format comparison, one worker, and machine-readable reports. Before scanning, 427 candidate files were inventoried and hashed against the pinned revision.

jscpd analyzed 378 sources. The difference was post-filter accounting rather than a lost directory: 22 candidates sat outside the default five-to-1,000-line window, while the remainder did not produce an analyzed token map under the selected settings. The report contained 67 clone groups and 1.00% duplicated lines. A repeated run produced the same report after normalizing its timestamp.

$ jscpd --min-tokens 50 --min-lines 5 --mode mild \
    --format typescript,javascript,jsx,tsx --cross-formats js-ts \
    --reporters silent --no-colors --no-tips --workers 1 ./production-source
Duplications detection: Found 67 exact clones with 764(1.00%) duplicated lines in 378 (3 formats) files.

We independently resolved all 134 reported sides back to an inventoried file and valid line range. Then we read the 20 highest-impact groups and a deterministic 20-group sample, covering 34 distinct groups after overlap. Sixteen of the highest-impact groups were plausible consolidation candidates: repeated contracts, parser helpers, request handling, timestamp normalization, and async state flows. Four were contextual matches where a shared abstraction might make the code less clear. The sample added one low-value match involving generic presentation scaffolding.

That is the right shape for a duplicate code checker. It found real repetition, preserved enough context to review it, and left room for a human to decide whether two similar blocks should actually become one shared abstraction.

Scan scope changes the duplication percentage

The accepted production scope reported 1.00% duplicated lines. A broader repository scan included tests, fixtures, documentation, and other material, analyzed 1,686 sources, and reported 8.36%.

Neither percentage is the one true duplication rate. They answer different inventory questions. jscpd’s defaults also exclude files above 1,000 lines unless that maximum changes, so a team can produce a tidy number by leaving untidy files outside the room. Pin the format list, ignores, minimums, maximums, and paths in configuration. Then review the analyzed-file count beside the percentage.

The output side was broad and coherent in this run. JSON, HTML, XML, CSV, Markdown, badge, SARIF, AI, console, and summary surfaces completed; the SARIF parsed as version 2.1.0. A bounded MCP session initialized, advertised four tools, and completed six messages. Thresholds behaved as a build gate should: one below the observed percentage exited one, and one above it exited zero.

Zero did not always mean success

The invalid-input matrix produced the material limitation:

  • an unknown format silently wrote an empty report and exited zero;
  • a nonexistent input path silently wrote an empty report and exited zero;
  • an unwritable output destination printed a reporter I/O error and exited zero;
  • an invalid mode warned, fell back to mild, and exited zero;
  • a misspelled configuration key warned, continued with defaults, and exited zero.

Malformed JSON did exit one. A threshold violation also exited one. The problem is narrower than “jscpd never fails”: process success does not prove that the intended files, mode, configuration, or artifact destination were used.

For local use, the warning or empty report is visible. In unattended CI, a green step can discard the message and keep moving. A safe wrapper should verify that the expected artifact exists, parse it, confirm a nonzero and plausible analyzed-source count, and only then apply the duplication threshold.

Where jscpd fits

jscpd makes sense when broad format support, a standalone local binary, exact clone locations, and portable reports matter. It can sit in a pre-commit check, produce SARIF for code scanning, or create a maintenance shortlist without sending source to a hosted service.

PMD CPD is the stronger comparison when you want a mature detector with language-specific normalization and distinct exit statuses for invalid usage, recoverable errors, and duplication. SonarQube fits when duplication should live inside a larger code-quality system with dashboards and governance. A language-specific analyzer or narrow internal script may be better when one ecosystem needs deeper semantics than broad token matching.

Try it locally; guard it in CI

Try jscpd for local duplicate-code review. Its file-and-line evidence held up, its production findings produced a useful maintenance shortlist, and its report surfaces cover terminals, automation, code scanning, and agents.

But make the report artifact part of the gate. Verify that it exists, parses, and contains a plausible analyzed-source count before treating exit code zero as success. Choose PMD CPD when distinct usage and recoverable-error statuses matter more than jscpd’s broad reporter and MCP surface; choose a larger quality platform when duplication is only one policy among many.

For maintainer-led scans, jscpd does the job. In CI, verify the report and analyzed-source count before trusting a zero exit code.