{"owner":"kucherenko","repo":"jscpd","hasSkills":true,"totalSkillsCount":7,"totalTokensCount":18525,"categories":["plugin-manifest","project-spec","anthropic-skill"],"hasMcp":false,"mcpConfig":null,"found":["benchmark/tools/README.md","docs/api.md","skills/code-migration/SKILL.md","skills/codebase-refactoring/SKILL.md","skills/compare-codebases/SKILL.md","skills/dry-refactoring/SKILL.md","skills/jscpd/SKILL.md"],"skills":{"benchmark/tools/README.md":"# Benchmark Tools\n\nThis directory contains all third-party tools required by the benchmark script. Install each tool by following the instructions below, then run `./benchmark.sh` from the parent directory.\n\n## Quick Install\n\n```bash\ncd tools/\nnpm install\n```\n\nThis installs **jscpd-rs** and **Fallow** (the only tools distributed via npm). The remaining tools must be installed manually.\n\n## Tools\n\n### jscpd@5 (local build)\n\nThe jscpd v5 Rust binary, built from the `rust/` directory in this repository.\n\n```bash\ncd ../../rust/\ncargo build --release\n```\n\nThe binary is expected at `../../rust/target/release/jscpd`.\n\n### jscpd-rs (npm)\n\nInstalled in `tools/node_modules/` via `npm install`. The npm package downloads a prebuilt Rust binary for the current platform.\n\n```bash\nnpm install\n```\n\nBinary: `node_modules/.bin/jscpd-rs`\n\n### Fallow dupes\n\nInstalled in `tools/node_modules/` via `npm install`. Fallow is a Rust-based code intelligence tool; the `dupes` subcommand performs structural clone detection for TypeScript/JavaScript projects.\n\n```bash\nnpm install\n```\n\nBinary: `node_modules/.bin/fallow`\n\n> **Note:** Fallow must run from inside the target directory (it has no path argument). The benchmark script `cd`s into the fixtures directory before invoking it.\n\n### PMD CPD\n\nInstall via Homebrew (macOS) or download from [pmd.github.io](https://pmd.github.io).\n\n```bash\nbrew install pmd\n```\n\nPMD requires a Java runtime (8+). The `pmd` command must be on `$PATH`.\n\n> **Note:** The benchmark runs PMD CPD for all 34 supported languages individually and sums the results. This takes ~30 seconds.\n\n### Duplo\n\nA prebuilt macOS arm64 binary is included at `tools/duplo`. To replace it:\n\n1. Download from [github.com/dlidstrom/Duplo/releases](https://github.com/dlidstrom/Duplo/releases)\n2. Place the binary at `tools/duplo`\n3. `chmod +x tools/duplo`\n\n```bash\n# Verify\n./duplo -help\n```\n\n### Simian\n\nA prebuilt JAR is included at `tools/simian.jar`. To replace it:\n\n1. Download from [simian.quandarypeak.com](https://simian.quandarypeak.com)\n2. Place the JAR at `tools/simian.jar`\n\nRequires a Java runtime:\n\n```bash\njava -jar tools/simian.jar -help\n```","docs/api.md":"# Programming API\n\njscpd is a Rust engine. The CLI ships as the `jscpd` crate on crates.io (installs the `jscpd` and `cpd` binaries) and as the `jscpd` / `cpd` npm packages (prebuilt binaries); the engine itself is a set of library crates you can depend on directly.\n\n## Rust\n\nUse the `cpd-finder` crate — it is the orchestration entry point and pulls in the tokenizer, detection core, and reporters:\n\n```toml\n[dependencies]\ncpd-finder = \"0.1\"\n```\n\n```rust\nuse cpd_finder::orchestrate::{RunConfig, run};\n\nlet config = RunConfig {\n    paths: vec![\"./src\".into()],\n    min_tokens: 50,\n    ..Default::default()\n};\n\nlet result = run(&config).unwrap();\nprintln!(\"Found {} clones\", result.clones.len());\nprintln!(\"Analyzed {} files\", result.statistics.total.sources);\n```\n\nA complete, runnable version is in [`examples/rust-cpd-finder`](../examples/rust-cpd-finder).\n\nSemantic clones come from the `cpd-semantic` crate, which adds a clone pass to the run:\n\n```rust\nuse cpd_finder::orchestrate::{RunConfig, run};\nuse cpd_semantic::{SemanticOptions, SemanticPass};\nuse std::sync::Arc;\n\n// The local model is downloaded once, e.g. with `jscpd --semantic-download`.\nlet options = SemanticOptions::default();\nlet paths = vec![\"./src\".into()];\nlet embedder = cpd_semantic::embedder(&options, &paths, true)?;\n// The thresholds of the rules, on the scale of the options' model.\nlet thresholds = options.thresholds();\nlet config = RunConfig {\n    paths,\n    passes: vec![Arc::new(SemanticPass::new(embedder, thresholds, options.scope))],\n    ..Default::default()\n};\nlet result = run(&config)?;\n```\n\nThe `jscpd` crate's own library target is **not a public API** — it exists to share helpers with the crate's tests and may change in any release. Depend on the engine crates instead.\n\n### Crate Architecture\n\n| Crate | Description |\n|-------|-------------|\n| [`cpd-core`](https://crates.io/crates/cpd-core) | Core data models and hashing (Rabin-Karp rolling hash) |\n| [`cpd-tokenizer`](https://crates.io/crates/cpd-tokenizer) | Source code tokenization (224 formats, uses `oxc_parser` for JavaScript/TypeScript) — pure, no I/O |\n| [`cpd-similarity`](https://crates.io/crates/cpd-similarity) | Structural similarity (`--similarity`): functions from tree-sitter syntax trees and a Clojure reader, the subtree fingerprints of their normalized trees and the exact search for pairs; pure, no I/O |\n| [`cpd-finder`](https://crates.io/crates/cpd-finder) | File walking, orchestration, git blame (`rayon` + `ignore` + `globset`) |\n| [`cpd-reporter`](https://crates.io/crates/cpd-reporter) | Output format rendering (15 reporters) |\n| [`cpd-semantic`](https://crates.io/crates/cpd-semantic) | Semantic clones (`--semantic`, experimental), as a clone pass for `cpd-finder` |\n\nSee [Packages](./packages.md) for versions and paths.\n\n## Other languages\n\nThere is no Node.js (or other language) binding for the v5 engine. From another language, run the binary and read the `json` reporter's output (`--reporters json --output <dir>` writes `<dir>/jscpd-report.json`), or talk to it over the [MCP stdio transport](./ai-ready.md#stdio-transport-rust-v5).\n\nThe TypeScript API of jscpd v4 (`jscpd()`, `detectClones()`, `@jscpd/core`, LevelDB/Redis stores) is maintained on the [`master-v4`](https://github.com/kucherenko/jscpd/tree/master-v4) branch and published as `jscpd@4`.\n","skills/code-migration/SKILL.md":"---\nname: code-migration\ndescription: Move code from one implementation to another function by function, tests first and code second, and check two implementations of one app for parity, with jscpd --compare as the progress measure and a coverage map binding each function to its tests. Use when porting a library or app to another language or framework (Java to Kotlin, JavaScript to Rust, a Python library to TypeScript, an iOS app to Android), when asked what is left to port, or when comparing the Android and iOS versions of an app.\n---\n\n# code-migration\n\n`jscpd --compare SOURCE TARGET` pairs every function of one folder with the function of the other folder that does the same job, in any pair of languages, and lists the functions that have no counterpart. This skill uses it to measure a port: what is ported, what is left, and whether the port you just wrote was recognized.\n\nA port runs in two phases: the tests first, then the code they check. A coverage report of the source's tests tells which tests exercise which function, so each function is ported together with the tests that prove it works. See [Plan: tests first, then code](#plan-tests-first-then-code).\n\nTwo words are used throughout:\n\n- **source**: the implementation you port from, such as the iOS app, the Python library, or the JavaScript package;\n- **target**: the implementation you port to, such as the Android app, the Rust crate, or the TypeScript rewrite.\n\nNeither has to be old or new. The source may stay in production and keep changing, and the target may already hold features the source lacks. For two implementations that both live on (`ios/` and `android/`), the same report shows parity; see [Parity](#parity-between-two-implementations).\n\njscpd finds functions in JavaScript, TypeScript, JSX, TSX, Vue, Svelte, Astro, Python, Rust, Go, Java, Kotlin, C#, C, C++, PHP, Ruby, Scala and Swift. The [compare-codebases](../compare-codebases/SKILL.md) skill explains how the comparison pairs functions and how to check its result; the [jscpd](../jscpd/SKILL.md) skill covers the rest of the tool.\n\n## Setup\n\n`--compare` pairs functions with a code embedding model that runs inside jscpd. Download it once (548 MB, CodeRankEmbed). Ask the user before you start the download:\n\n```bash\nnpx jscpd --semantic-download\n```\n\njscpd caches the vectors, so a repeat run embeds only the functions whose code changed and takes seconds.\n\nPick the two paths and keep them fixed for the whole port: the source first, the target second. Two paths are required, and they must not overlap (`app/` and `app/android/` is refused). The target may be empty at the start.\n\nOne run measures both phases: the report has a `Code` block and a `Tests` block (a `code` and a `tests` section in JSON), and a test pairs only with a test. jscpd tells a test by the conventions of its language: test files such as `*_test.go`, `test_*.py`, `*.test.ts` or `*Test.java`, folders such as `tests/`, `__tests__/` or `src/test/`, Rust tests in `#[cfg(test)]` modules, and JavaScript test cases such as `it('rounds cents', () => …)`. Phase 1 reads the `Tests` block, phase 2 the `Code` block.\n\n### Keep vendored and generated code out\n\n`--compare` counts and embeds every function in both paths. Vendored dependencies (`vendor/` from `cargo vendor` or `go mod vendor`, `third_party/`), installed packages (`node_modules/`, `.venv/`), build output (`target/`, `build/`, `dist/`) and generated code are not part of the port, yet a vendored crate tree alone holds thousands of functions. With them in a path, the first run embeds all of them and takes tens of minutes instead of seconds, and the percentages describe the dependencies instead of the port.\n\njscpd skips what `.gitignore` excludes, but only inside a git repository. Before the first run:\n\n1. Check both paths for such folders. A target you create for the port gets them as soon as you build it or vendor its dependencies, so check again after the first build.\n2. Suggest a `.gitignore` to the user that lists the folders the target's language and tools produce, plus the report folder. For a Rust addon built with napi-rs:\n\n   ```gitignore\n   /target/\n   /vendor/\n   node_modules/\n   *.node\n   .jscpd-compare/\n   ```\n\n   If the target is not in a git repository, tell the user that jscpd reads the `.gitignore` only after `git init`.\n3. Until the `.gitignore` works, pass the same folders with `--ignore`, and keep the globs the same for every run:\n\n   ```bash\n   npx jscpd --compare node-lib/ rust-lib/ --ignore \"**/vendor/**,**/target/**,**/node_modules/**\"\n   ```\n\nThe totals line shows when something slipped through. Suspect vendored or generated code in a path when the target has far more functions than the source, or when a run embeds hundreds of functions after a small change.\n\n## Measure\n\nRun the console report for yourself and for the user. Here a Python billing library is being ported to TypeScript:\n\n```bash\nnpx jscpd --compare billing-py/ billing-ts/\n```\n\n```text\n 71% 5 of 7 functions in billing-py/ have a counterpart in billing-ts/\n 80% 4 of 5 functions in billing-ts/ have a counterpart in billing-py/\n\nbilling-py/\n  file         paired  similarity  counterpart\n  billing.py   4 / 5   0.89        billing.ts\n  shipping.py  1 / 2   0.91        shipping.ts\n\nbilling-ts/\n  file         paired  similarity  counterpart\n  billing.ts   3 / 4   0.89        billing.py\n  shipping.ts  1 / 1   0.91        shipping.py\n\nPaired under other names (1):\n  billing-py/                   billing-ts/             similarity\n  billing.py:28 tax_for_region  billing.ts:27 salesTax  0.87 high\n\nOnly in billing-py/ (2):\n  billing.py (1)\n    46  due_date                6 lines\n  shipping.py (1)\n    18  estimate_delivery_days  8 lines\n\nOnly in billing-ts/ (1):\n  billing.ts (1)\n    39  toCurrency  8 lines\n```\n\n- The first line is the port's progress: the share of the source's functions that have a counterpart in the target. \"Only in\" the source is the work left.\n- The second line and \"Only in\" the target describe the target's own code: helpers the port needed, features the source never had, or a port jscpd did not recognize (see [Misses](#misses)).\n- Each file row gives its paired functions, the mean similarity of their pairs (with the number of `low` pairs, if any), and the counterpart file: the file on the other side that holds most of its counterparts. Use the counterpart to decide where a missing function goes.\n- \"Paired under other names\" lists the pairs whose names differ even once case and underscores are ignored: renamed ports, constructors, platform names. Each has its similarity and level (see [Reading pairs](#reading-pairs)). These are ported already, even though a search by name would not find them.\n- With an empty target the report is one line of totals plus `billing-ts/ has no functions yet`.\n\nFor work you plan and track, read the JSON report instead of the console:\n\n```bash\nnpx jscpd --compare billing-py/ billing-ts/ -r json -o .jscpd-compare --silent\n```\n\njscpd measures tests and code apart and pairs a test only with a test, so `.jscpd-compare/jscpd-compare.json` has a `code` and a `tests` section of the same shape. Each section has `sides[0]` (the source) and `sides[1]` (the target), each with `path`, `functions`, `matched`, `percentage`, `files` (`file`, `functions`, `matched`, `counterpart`, `similarity`, `lowPairs`) `unmatched` (`file`, `name`, `start`, `end`) and `readyToPort` (the unmatched functions whose callees all have a counterpart, with their number of `callers`, most called first), and `pairs`, each with `a` (source), `b` (target), `similarity`, `level` (`high`, `medium`, `low`), `renamed` (the names differ) and `matchedBy` (`code` or `name`). Paths are relative to each side's folder. Add `.jscpd-compare/` to `.gitignore` or write the report outside the repository.\n\n`-r console-full` also prints every pair with its similarity, `-r markdown` writes `jscpd-compare.md`, a table you can paste into a PR description or a tracking issue, and `-r html` writes `jscpd-compare.html`, a migration map the user can open to see the two dependency graphs side by side, or the same pairs as a table.\n\n## Plan: tests first, then code\n\nPort the tests before the code. Ported tests define, before any target code exists, what it means for each function to be ported: when the function lands, its tests pass or show what still differs. A port written before its tests can only be checked against your reading of the source.\n\n### Bind tests to functions with coverage\n\nA test's name does not say which functions it runs: a test of `checkout` also runs `apply_discount` and `tax_for_region`. Coverage does. Before porting anything, build a map from each source function to the tests that exercise it.\n\n1. Get a coverage report of the source's tests that says which test ran which lines: recorded per test, or per test file when the project's tooling has no per-test mode.\n2. Take the source's functions and their lines from the `code` section of the JSON report. Every counted function is either in the source's `unmatched` list or the `a` side of a pair, each with `file`, `name`, `start` and `end`. Functions under `--min-tokens` or `--min-lines` are not listed there; take those from the coverage report's own function list, which most formats have.\n3. A test covers a function when it runs a line from the function's `start` to its `end`. Write the map both ways, function to tests and test to functions, to a file next to the report (for example `.jscpd-compare/test-map.json`), and rebuild it when the source's tests change.\n\nUse the map for three things:\n\n- which tests to port with a function, and which functions a ported test needs before it can pass;\n- the source functions no test covers. Before porting such a function, write a test for it on the source side that records what it does today (a characterization test), and port that test with the others. A port with no test has nothing to check it;\n- the order of work: tests that need few functions come first, since they turn green soonest.\n\nCoverage says which code a test runs, not what it checks. A function that a test only passes through on the way to another is weakly bound to that test; prefer the tests whose names or assertions are about the function.\n\n### Phase 1: port the tests\n\n1. Take the source's unmatched tests from the `tests` section of the JSON report. A JavaScript or TypeScript test case written as a callback, `it('rounds cents', () => …)`, goes by its title, so it pairs with `test_rounds_cents` in pytest or `rounds_cents` in Rust like any named test.\n2. Port the tests file by file, into the target's test layout, against the API the target will have. Keep inputs and expected values exactly as they are. Where the target must behave differently (a platform limit, a language's number types), write the difference into the test with a comment and tell the user.\n3. A ported test calls functions that do not exist yet, so it fails. In a compiled language it stops the whole test build. Keep such tests out of the build until their functions land, for example a module declaration left commented out, a cfg feature or an excluded source set, and turn them on one by one in phase 2. Do not write empty functions to make the tests compile: a stub under a source name can pair by name and count as ported.\n4. Run the target's tests. The ported tests that pass already cover what the target has; the failing or disabled ones, read against the test map, are the work list for phase 2.\n\n### Phase 2: port the code\n\nPort the code one function at a time, with its tests already in place.\n\n1. Run the JSON report and take the source's `unmatched` list from its `code` section.\n2. Order the work. Start with functions whose tests are ported, and port a function after the functions it calls, since a caller ported before its helpers has nothing to call. The source's `readyToPort` list is that order's front: the functions whose callees all have a counterpart already, most called first. Take the next one from there, and rerun the report after each batch, since every port can make its callers ready. Within that order, finish one file before starting the next, so each target file fills up in one go.\n3. Before writing a port, make sure the target does not have it already. Pairs with `renamed: true` are ported functions under other names; they are not in `unmatched`, but check them when the user asks where a function went. Then search the target for an equivalent that jscpd missed: in the counterpart file from `files`, under other names (constructors, merged functions, a platform or library call that replaces the helper). If one exists, do not port the function again. Note the equivalent and move on.\n4. Write the port in the counterpart file, or in the file the target's layout puts it in. Keep the name recognizable in the target language's convention (`encodeBinary` becomes `encode_binary` in Rust or Python), because jscpd pairs short functions by name. Follow the style of pairs that are already done in the same file. `console-full` lists them.\n5. Turn on the function's ported tests, as the test map lists them, and run them. When they fail, fix the port, not the test, unless the user agrees that the target should behave differently. A pair in the report says the two functions look alike; the tests say whether they behave alike.\n6. Run the target's coverage for those tests and check that they run the new function. A ported test that never reaches it is bound to something else on the target (a helper, a mock, an old path), and it proves nothing about the port.\n7. Run the comparison again with the same paths and options. Check that the function has left `unmatched`, that its pair joins the right function in the target, and its level. A `high` pair is done. A `low` pair to your port usually means the port differs a lot from the source in structure; read both and make sure the behavior matches. A pair to a different function means the port is not recognized, or it resembles the wrong thing; read both before going on.\n8. Report progress to the user with both measures, as the reports print them: tests ported and passing, and functions ported (`functions 73% → 76%, 3 ported: …; tests 41 of 44 ported, 38 passing`), with the functions you decided not to port and why.\n\nRepeat until the source's `unmatched` list holds only functions you decided not to port. Typical reasons: dead code (check with `npx jscpd --dead-code` on the source where it supports the language), code the target platform or its libraries provide (a JSON parser, a retry helper), platform glue with no equivalent on the target (an iOS delegate callback, an Android notification channel), and features the user decided not to carry over. List these in a notes file or the PR, one line each, so the remaining percentage is explained.\n\nWhen the source keeps changing during the port, a function that was paired can come back as unmatched after a rewrite, and new source functions join the list. Rerun the report before each batch of work instead of reusing an old list.\n\n## Parity between two implementations\n\nWhen both implementations live on, as the iOS and the Android app do, `npx jscpd --compare ios/ android/` shows parity. Read both \"Only in\" lists:\n\n- a function on one side only is either platform code (Android notification channels, iOS delegate callbacks) or a feature the other side lacks. Report each missing feature to the user before implementing it;\n- to add a missing feature, treat the side that has it as the source for that feature: bind its tests with coverage, port the tests, then the code, as in [Plan: tests first, then code](#plan-tests-first-then-code).\n\njscpd pairs functions within modules that already match. A module is the folder right under the deepest folder each side's files share, so keep the two trees parallel (`ios/<plugin>` and `android/<plugin>`) for the best pairing.\n\n## Reading pairs\n\n- `level` rates the similarity on the scale of the model in use, so it means the same with every model. `high` (0.7125 and up with the default CodeRankEmbed, across languages) is almost always the same function. `medium` is usually the same function, restructured. `low` needs reading both functions, since related code pairs there too (a function counting UTF-8 bytes paired with one converting a string to them). On the Tauri plugins, every `low` pair joined two differently named functions, and one of them joined two different plugins.\n- `matchedBy: code` means the two functions are each other's closest match and the similarity stands out.\n- `matchedBy: name` means the names match once case and underscores are ignored, and the code is similar enough. It catches short ports. Read these pairs, because a same-named function can do something else.\n- The similarity does not see small differences in behavior. Two versions that drifted apart still pair. Only tests and reading the code catch drift.\n\n## Misses\n\njscpd compares functions only: types, constants, enums with data, SQL and UI markup (storyboards, Android layouts) are not in the report, so port and check them yourself. Known gaps in pairing:\n\n- constructors across languages (a Java constructor and Rust's `new`, Kotlin's `constructor`, Swift's `init`) pair only when their code is similar enough;\n- a short function renamed in the port (`add_history` for `_finder_penalty_add_history`);\n- one function split into several, or several merged into one: the report may pair only the closest part and list the rest as unmatched;\n- anonymous functions (callbacks, closures) take no part at all, except JavaScript and TypeScript test cases such as `it('rounds cents', () => …)`, which go by their titles.\n\nA function listed only in the target that you know is a port of a source function is one of these. Do not rename working code only to raise the number, and never add stubs or empty functions with source names: jscpd may pair a stub by name, and the progress would then report work that was not done.\n\n## Options\n\n- `--min-tokens` (30 with `--compare`) and `--min-lines` (5) decide which functions count toward the totals. Smaller functions still pair as partners. Raise them to focus on substantial functions, and keep them fixed across runs so percentages compare.\n- `--ignore` leaves files out, such as vendored dependencies, build output, generated code or fixtures (see [Keep vendored and generated code out](#keep-vendored-and-generated-code-out)); `--pattern` narrows the comparison to some files, such as the tests of one module. Keep the globs fixed across runs.\n- `--format` narrows the walk to some languages, for example when the source mixes the code being ported with build scripts.\n- `--semantic-model` and `--semantic-url` pick another embedding model or an OpenAI-compatible API (`npx jscpd --semantic-models` lists the models with calibrated thresholds). Keep the model fixed across runs, because each model scores on its own scale.\n- The exit code is 0 whatever the progress, so the command does not fail a build on its own.\n\n## Rules\n\n- Keep the two paths, their order, the options and the model the same from run to run, or the numbers stop being comparable.\n- Keep vendored dependencies, installed packages and build output out of both paths: through a `.gitignore` in a git repository, which you suggest to the user, or with `--ignore`.\n- Port the tests before the code, and a function only together with the tests the coverage map binds to it.\n- Treat the report as a map of what to read and what is left. It does not prove the port is correct; tests do.\n- Never game the percentage: no stubs, no renames made only for jscpd, no deleting source functions to shrink the denominator without the user's decision.\n- When the report and your reading of the code disagree, trust the code and tell the user which pair looked wrong.\n","skills/codebase-refactoring/SKILL.md":"---\nname: codebase-refactoring\ndescription: Three-part workflow to improve overall codebase health with jscpd — find and fix duplicated code, find and remove or refactor dead code, then find and simplify the largest/most complex files. Starts from --dashboard/--health to prioritize and ends by re-measuring the score.\n---\n\n# codebase-refactoring\n\nA guided pass over a codebase's three biggest maintenance costs — duplicated\ncode, dead code, and complexity — using jscpd to find each and a strategy to\nfix it. Use this skill when asked to \"clean up\", \"improve\", \"refactor\" or\n\"pay down tech debt in\" a codebase, rather than to fix one specific bug.\n\n## Start here: measure, then prioritize\n\n```bash\nnpx jscpd --health --reporters ai <path>\n```\n\n```\nhealth 74 B (duplication 75, dead-code 72, complexity 76; 93 code lines)\n```\n\nThe health score is a weighted mix of three sub-scores, each a share of the\ncode lines — high is good, 100 is clean. The lowest of the three names where\nthe codebase actually hurts most; if it isn't the order below, do that\ndimension first instead. `n/a` on a dimension (e.g. `dead code n/a`) means\njscpd could not measure it — dead-code detection needs JavaScript,\nTypeScript or Python to be a real share of the code — skip that step rather\nthan treating it as clean.\n\nFor the full picture behind that score — largest duplicated formats, most\ncomplex files, largest dead-code findings, all in one screen — run `npx\njscpd --dashboard` instead. Its `console` output (the default) is the only\nreporter with that per-file detail; `--reporters ai` on `--dashboard` prints\nthe same one-line summary shown above, not the breakdown, so don't reach for\nit expecting more than the score. Use `--reporters json` on `--dashboard` if\nyou need the detail in a parseable form.\n\nThe three passes below are otherwise independent and safe to run in any\norder; **duplication → dead code → complexity** is the default because\nfixing duplication first means step 2 isn't wasting effort tracing call\nsites that a later extract-function pass will move anyway, and clearing\ndead code before step 3 means the files ranked for simplification are ones\nactually worth simplifying, not ones about to be deleted.\n\n## Step 1 — Duplicated code\n\n```bash\nnpx jscpd --reporters ai --summary <path>\n```\n\nFind and read each clone, decide whether it is a real copy worth merging,\nand apply the matching refactoring strategy (extract function, parameterize,\nextract a shared module or constant, or a base class/template for\nstructural duplication). Full guided workflow, triage rules for\nrenamed/near-miss clones, and worked examples per clone kind:\n\n→ **[dry-refactoring](../dry-refactoring/SKILL.md)** — run this now, then\ncome back here for steps 2 and 3. Its own `--summary` hotspot ranking is the\nsame one the health score's duplication dimension reads from, so a file it\nflags as a duplication hotspot is the same file that dragged the score down.\n\n## Step 2 — Dead code\n\n```bash\nnpx jscpd --dead-code --reporters ai <path>\n```\n\n```\nbasta dead code report — 18 findings across 30 files (22.1% of 272 lines)\nConfidence is 0-100; anything below 90 has a listed reason it may be wrong. Verify a finding before deleting the code.\nunused-file typescript/src/legacy-export.ts:1:1 confidence=95 typescript/src/legacy-export.ts is never imported and is not an entry point\nunused-export typescript/src/invoice.ts:21:17 confidence=85 exported function `renderReceipt` is never imported\nunused-symbol typescript/src/invoice.ts:29:10 confidence=90 function `describeTotal` is never used in typescript/src/invoice.ts\nunused-import typescript/src/invoice.ts:2:10 confidence=100 `roundToCents` is imported but never used\n```\n\nThis is a graph traversal from the project's entry points (`package.json`\n`main`/`bin`/`exports`/`scripts`, `pyproject.toml` scripts, framework\nconventions, or `--entry <glob>`), not a reference count — a helper whose\nonly caller is itself dead is reported too, so deleting one finding can make\nanother true that wasn't reported yet. Covers JavaScript, TypeScript, JSX,\nTSX, Vue, Svelte, Astro and Python.\n\n### The four categories, and what to do with each\n\n| Category | Meaning | Usual fix |\n|----------|---------|-----------|\n| `unused-file` | Never imported anywhere, not an entry point | Delete the file |\n| `unused-export` | Exported, but no other file imports it | Delete it, or drop the `export` keyword if something in the same file still uses it |\n| `unused-symbol` | A module-private declaration nothing in its own file references | Delete it |\n| `unused-import` | Imported but never referenced in the file | Delete the import line |\n\n### Workflow\n\n1. Run with `--reporters ai` (add `--dead-code-categories` to focus on one\n   category, `--include-tests` if test-only usage shouldn't count as \"used\",\n   `--include-entry-exports` to also flag an entry file's own unused\n   exports, normally excluded since its whole surface is the public API)\n2. Sort by confidence, high to low. Everything is 0-100; below 90 the finding\n   carries a stated reason it might be wrong — `eval`/`getattr` calls,\n   decorators the analyzer doesn't recognize, a wildcard re-export, the name\n   showing up only inside a string literal, or a file elsewhere that failed\n   to parse (see `statistics.unparsedFiles` in the JSON report). Read the\n   reason before deleting; `--min-confidence` (default 60) only sets the\n   floor for what's reported, it doesn't make a low-confidence finding safe\n3. For each finding above your comfort threshold, confirm it by hand — grep\n   the symbol name across the repo (dynamic access, a string-based router, a\n   template file jscpd doesn't parse can all hide a real use) — then apply\n   the fix from the table above\n4. Run the project's own test suite and build after each batch of deletions,\n   not only jscpd's re-run: dead-code analysis proves nothing *reaches* the\n   code from an entry point, not that removing it can't break a build step\n5. Re-run `--dead-code` after a batch: a file whose only import was the one\n   you just deleted may now be dead itself — that cascade is expected, keep\n   going until a clean pass, not just once\n6. Report what you skipped and why (low confidence, a reason you couldn't\n   rule out, or intentionally-kept dead code such as a public library export)\n   separately from what you actually removed\n\n## Step 3 — Big and complex files\n\n```bash\nnpx jscpd --complexity --reporters ai --summary-top 10 <path>\n# or, ranked alongside duplication and size:\nnpx jscpd --reporters ai --summary --summary-by complexity <path>\n```\n\n```\nComplexity by complexity (6 files, 6 folders):\nfiles (tokens/lines/size/cx):\nc/checkout.c 176/32/710/13\nrust/status.rs 112/24/481/9\nswift/Profile.swift 88/21/446/8\n```\n\n`cx` is a language-aware cyclomatic-complexity estimate — roughly one path\nper function plus one per branch (`if`, loops, `&&`/`||`, `match`/`switch`\narms, the ternary and Elvis operators, each counted the way the language\nactually spells it) — read as a ranking signal, not an exact metric. Prose\nand data files (Markdown, JSON, YAML, lock files, …) always score `0`; a\nlarge README is not a refactoring target. jscpd's own health score treats a\nfile with complexity **50 or higher** as \"complex\" (the threshold its\n`dead code`-style half-life curve is calibrated against) — a reasonable\ndefault cutoff for \"worth reading\" if the summary doesn't make an obvious\none clear.\n\n### Workflow\n\n1. Take the top N files by complexity (and, from the `--summary` form, by\n   size — a huge low-complexity file, e.g. a big data table, is a different\n   problem than a small high-complexity one)\n2. Read each file and name the actual driver: deep nesting, a long function\n   doing several unrelated things, a large `switch`/`if`-chain over a type\n   or state, or a file that is really several modules glued together\n3. Pick the matching strategy:\n   - **Deep nesting** → guard clauses / early return, flatten the happy path\n   - **Long function, several responsibilities** → extract function per\n     responsibility, named for what it does\n   - **Branching over a fixed set of cases** (type, enum, status) →\n     replace with a lookup table/map, or polymorphism if each case already\n     has its own type\n   - **File doing too much** → split along its actual seams (one export\n     group per file), not an arbitrary line-count cut\n4. Refactor one file at a time; keep behavior identical — this is a\n   complexity pass, not a rewrite. If a duplication clone (step 1) or a dead\n   branch (step 2) turns up while reading, still handle it in its own step\n   so the reason for each change stays traceable\n5. Re-run `--complexity` on the touched files to confirm `cx` actually\n   dropped — a split that only moves code around without reducing branching\n   per function didn't fix anything, it just renamed the problem\n\n## Step 4 — Confirm the improvement\n\n```bash\nnpx jscpd --health --reporters ai <path>\n```\n\nCompare the score and grade to the Step 0 measurement. A dimension that got\nworse (duplication crept back in while extracting a shared complexity fix,\nsay) is a signal to revisit that step, not just note it — re-run the\nrelevant pass (`--summary`, `--dead-code`, `--complexity`) rather than only\ntrusting the one aggregate number.\n\n## Tips\n\n- Don't run all three passes on a codebase you've never seen before without\n  reading `--dashboard` first — on a project where only one dimension is\n  actually bad, spending equal effort on all three wastes time on the two\n  that are already fine\n- A `--health-input` file (coverage, security scan results) can be layered\n  onto the same score if the project already tracks those; it doesn't change\n  anything about this workflow, it just adds more dimensions to Step 0\n- Health scores from two runs are only comparable when built from the same\n  dimensions — if a dimension went from `n/a` to measured (or the reverse)\n  because the file mix changed, say so rather than reading the raw score\n  delta as improvement or regression\n- Commit each step separately (duplication fixes, dead-code removal,\n  complexity simplification) — a reviewer verifying \"did this actually\n  reduce X\" wants a diff scoped to X\n","skills/compare-codebases/SKILL.md":"---\nname: compare-codebases\ndescription: Compare two folders function by function with jscpd --compare, in the same language or across languages, and explain the result. Use when asked how two codebases relate, which functions one implementation has and the other lacks, how far a port has come, whether the iOS and Android versions of an app match, or which function in one folder corresponds to a function in the other.\n---\n\n# compare-codebases\n\n`jscpd --compare A B` pairs every function of folder `A` with the function of folder `B` that does the same job, and lists the functions of each folder that have no counterpart. The two folders may be in one language or in two (Python and TypeScript, Kotlin and Swift, Java and Rust). This skill explains how the comparison works and how to run one well. For porting code with the comparison as the progress measure, use the [code-migration](../code-migration/SKILL.md) skill; for the rest of jscpd, the [jscpd](../jscpd/SKILL.md) skill.\n\n## How the comparison works\n\njscpd finds the functions of JavaScript, TypeScript, JSX, TSX, Vue, Svelte, Astro, Python, Rust, Go, Java, Kotlin, C#, C, C++, PHP, Ruby, Scala and Swift files in both folders. It turns the code of each function into a vector with a code embedding model that runs inside jscpd (CodeRankEmbed by default), so two functions that do the same job point the same way even when their languages, names and structure differ. Functions of one folder are never compared with each other.\n\nFunctions pair in two steps:\n\n1. By code. Two functions pair when each is the other's closest match in the other folder, their cosine similarity reaches the model's threshold (0.4125 across languages and 0.6375 within one language, with CodeRankEmbed), and the similarity stands out from the function's other matches. A function close to the best one also pairs when it reaches a higher bar, so a feature written twice on one side gets two pairs. A pair stays only when it is the best match of at least one of its two functions, so a lookalike from another class does not join a function and its counterpart; a tie goes to the file of the same name. Functions shorter than `--min-tokens` (30 with `--compare`) or `--min-lines` (5) stay out of this step, because a short function resembles too many others.\n2. By name. A function left over pairs with a function of the other folder under the same name, once case, underscores, spaces and punctuation are ignored (`encodeBinary`, `encode_binary`, `_encode_binary`; the test title `rounds cents` and `rounds_cents`), when their similarity reaches the `medium` level (0.5625 across languages with CodeRankEmbed). A name pair skips the closest-match and stand-out checks of the first step, so it needs more than that step's threshold; otherwise every `load` and `init` of two codebases would pair. Size does not matter here, so a short port is found. A name pair has to stay within modules the first step linked. A module is the folder right under the deepest folder all files of a side share (`notification` in `android/notification/…`), and a file that sits higher than the rest, such as a build script, does not move that folder up. Two modules link when one holds the most of the other's pairs, counted for code and for tests separately. So `checkPermissions` of one plugin does not pair with its namesake in another.\n\nEach pair gets a level on the scale of the model, because a cosine that is high for one model is low for another:\n\n| Level | With CodeRankEmbed, across languages | Meaning |\n|---|---|---|\n| `high` | 0.7125 and up | almost always the same function |\n| `medium` | 0.5625 to 0.7125 | usually the same function, restructured |\n| `low` | 0.4125 to 0.5625 | read both: related code pairs here too |\n\njscpd measures tests and code apart, in two blocks of the report, and pairs a test only with a test. It tells a test by the conventions of its language:\n\n- a test file such as `*_test.go`, `test_*.py`, `*.test.ts`, `*.spec.js`, `*Tests.swift` or `*_spec.rb`;\n- a test folder such as `tests/`, `__tests__/`, `spec/`, `src/test/` (where Java, Kotlin and Scala keep their tests), `MyAppTests/` or `MyApp.Tests.Integration/`, the compared folder's own name included;\n- a Rust function in a `#[cfg(test)]` module or under `#[test]`;\n- a JavaScript or TypeScript test case such as `it('rounds cents', () => …)`.\n\nWhen neither side has a test that counts (see below), the report has one block and no headings.\n\nTotals count the functions of at least `--min-tokens` tokens and `--min-lines` lines; smaller ones appear only as partners. Declarations without a body (TypeScript overloads, the functions of a `.d.ts` file, interface and abstract methods) take no part. Anonymous functions (callbacks, closures) take no part either, except JavaScript and TypeScript test cases. A test case such as `it('rounds cents', () => …)` goes by its title, and so do those written with `test`, `specify`, `fit`, `xit`, `xtest` or `bench`, with `.only`, `.skip` or `.each(table)` after them. Suites and hooks stay anonymous. jscpd does not compare types, constants, SQL or UI markup.\n\n## A way to compare two folders\n\n### 1. Pick the folders\n\n- Point at the code, not the repositories: `app/src/main/java` and `ios/Sources`, not the two repository roots. Build output, vendored code, installed packages and generated files dilute the result, and a vendored dependency tree can turn a run of seconds into one of tens of minutes, since every function in it is embedded. Inside a git repository jscpd skips what `.gitignore` excludes; outside one, or for folders the `.gitignore` misses, pass them with `--ignore` (`--ignore \"**/vendor/**,**/target/**,**/node_modules/**\"`). When a side has no `.gitignore` entries for such folders, suggest them to the user.\n- Two folders are required, and they must not overlap: `app/` and `app/android/` is refused.\n- Keep parallel structures when you can (`ios/<module>` and `android/<module>`). Modules steer the name step, so matching folder names help.\n- For a port, put the source first and the target second, so the first line of the report is the port's progress. For two implementations that both live on, the order does not matter.\n- One run covers tests and code, since the report measures the tests in a block of their own. When the user asks about the code alone, leave the tests out with `--ignore \"**/__tests__/**,**/*.test.*,**/test/**\"`. When they ask about the tests alone, pass the two test folders as the paths, or pick the test files with `--pattern`.\n\n### 2. Get the model\n\nThe first run needs the model (548 MB). Ask the user before downloading it:\n\n```bash\nnpx jscpd --semantic-download\n```\n\njscpd caches the vectors per pair of folders, so later runs embed only the functions whose code changed.\n\n### 3. Read the overview\n\n```bash\nnpx jscpd --compare billing-py/ billing-ts/\n```\n\n```text\n 71% 5 of 7 functions in billing-py/ have a counterpart in billing-ts/\n 80% 4 of 5 functions in billing-ts/ have a counterpart in billing-py/\n\nbilling-py/\n  file         paired  similarity  counterpart\n  billing.py   4 / 5   0.89        billing.ts\n  shipping.py  1 / 2   0.91        shipping.ts\n\nbilling-ts/\n  file         paired  similarity  counterpart\n  billing.ts   3 / 4   0.89        billing.py\n  shipping.ts  1 / 1   0.91        shipping.py\n\nPaired under other names (1):\n  billing-py/                   billing-ts/             similarity\n  billing.py:28 tax_for_region  billing.ts:27 salesTax  0.87 high\n\nOnly in billing-py/ (2):\n  billing.py (1)\n    46  due_date                6 lines\n  shipping.py (1)\n    18  estimate_delivery_days  8 lines\n\nOnly in billing-ts/ (1):\n  billing.ts (1)\n    39  toCurrency  8 lines\n```\n\n- When both folders hold tests, the report has a `Code` block and a `Tests` block, each with everything below. The two top lines of a block give the share of each folder's functions (or tests) that have a counterpart in the other.\n- The file tables give each file's paired functions, the mean similarity of its pairs (with the number of `low` pairs, as in `0.62, 1 low`), and the file on the other side that holds most of its counterparts.\n- \"Paired under other names\" lists the pairs whose names differ even once case and underscores are ignored. A search by name never finds these.\n- \"Only in\" lists the functions with no counterpart, per folder, grouped by file with the number of each file's functions, then each function's first line, name and length.\n- In colour, levels are green (`high`), yellow (`medium`) and red (`low`), and the shares are green when complete, yellow when partial and red when nothing is paired. Pass `--no-colors` when you parse the console output, or read the JSON report instead.\n- If one folder has no functions, the report is one line of totals and `<folder> has no functions yet`. Check the path and the languages before concluding anything else.\n\n### 4. Check the pairs\n\n```bash\nnpx jscpd --compare billing-py/ billing-ts/ -r console-full\n```\n\n`console-full` adds every pair with its similarity and level, and marks the pairs found by name. Before reporting the comparison as reliable, read a sample:\n\n- two or three `high` pairs, to confirm the pairing works on this code;\n- every `low` pair, since related code pairs there too (a client call and the endpoint it calls, a function counting UTF-8 bytes and one converting a string to them);\n- the pairs marked `by name`, since a same-named function can do something else.\n\nWhen a sample pair is wrong, say which one and why.\n\n### 5. Check the functions with no counterpart\n\nA function in an \"Only in\" list is either missing on the other side, or its counterpart was not recognized. Before calling it missing, search the other folder: in its counterpart file, under other names, as part of a larger function, or replaced by a library or platform call. The known gaps in pairing:\n\n- constructors across languages (a Java constructor and Rust's `new`, Kotlin's `constructor`, Swift's `init`) pair only when their code is similar enough;\n- a short function renamed on the other side (`add_history` for `_finder_penalty_add_history`);\n- one function split into several, or several merged into one: only the closest part may pair.\n\nFunctions that exist on one side only by design are expected: platform glue (an iOS delegate callback, an Android notification channel), helpers a language needs and another does not, features one side dropped.\n\n### 6. Report\n\nFor the user, summarize in a few lines: the two percentages, the notable renamed pairs, the `low` pairs you checked and what you found, and the real gaps on each side, grouped by file or module. For a person who wants to explore the result, write the migration map, `-r html`: one offline page that draws both sides as dependency graphs with the pairs bridging them, and lists the same pairs as a table on a second tab. For a document or an issue, write the Markdown report and attach or paste it:\n\n```bash\nnpx jscpd --compare billing-py/ billing-ts/ -r markdown -o .jscpd-compare\n```\n\nFor your own processing, read the JSON report (`-r json`, written to `jscpd-compare.json`). It has a `code` and a `tests` section of the same shape. Each has `sides[0]` and `sides[1]`, each with `path`, `functions`, `matched`, `percentage`, `files` (`file`, `functions`, `matched`, `counterpart`, `similarity`, `lowPairs`) `unmatched` (`file`, `name`, `start`, `end`) and `readyToPort` (the unmatched functions whose callees all have a counterpart, each with its number of `callers`), and `pairs`, each with `a`, `b`, `similarity`, `level`, `renamed` and `matchedBy`. Paths are relative to each folder. Write reports outside the repository or add the folder to `.gitignore`.\n\n## Options that change the result\n\n- `--min-tokens` and `--min-lines` set which functions count. Raise them to focus on substantial functions.\n- `--ignore` and `--format` narrow the files, for example to leave out tests or build scripts.\n- `--semantic-model` picks another model, and `--semantic-url` an OpenAI-compatible embeddings API; `npx jscpd --semantic-models` lists the models with calibrated thresholds. `--semantic-threshold` and `--semantic-same-threshold` move the thresholds; change them only after reading pairs on both sides of the new value.\n- `--semantic-rebuild-cache` embeds everything again, which is needed only when the cache is suspect.\n\nKeep the folders, their order, the options and the model the same when you compare runs over time, or the numbers stop being comparable. The exit code is 0 whatever the result.\n\n## Limits\n\n- Similarity does not see small differences in behavior: two versions that drifted apart still pair, often at `high`. Only tests and reading the code catch drift.\n- The more one side is restructured, the fewer of its functions pair by code.\n- Only functions are compared.\n- The model runs on the CPU. The first run embedded about 25 functions a second on an Apple M1 (261 functions in 11 seconds) and can be several times slower on a small CI machine; later runs reuse the cache.\n","skills/dry-refactoring/SKILL.md":"---\nname: dry-refactoring\ndescription: Guided workflow to eliminate copy-paste duplication detected by jscpd. Refactor exact, renamed and near-miss clones using extract function, parameterize, module, constant, or base class strategies, starting from the hotspots the summary ranks.\n---\n\n# dry-refactoring\n\nGuided workflow to eliminate copy-paste duplication in source code. Use after running [jscpd](../jscpd/SKILL.md) to detect clones.\n\n## Prerequisites\n\nFirst, run jscpd to identify duplications:\n\n```bash\nnpx jscpd --reporters ai <path>\n```\n\nIn codebases that mix related formats (e.g. JavaScript and TypeScript), add `--cross-formats` so clones spanning both are detected too:\n\n```bash\nnpx jscpd --reporters ai --cross-formats \"js-ts\" <path>\n```\n\nOn larger codebases, add `--summary` to get a refactoring-hotspot overview alongside the clone list — top files and folders with a `dup%` column showing how much of each file is duplicated:\n\n```bash\nnpx jscpd --reporters ai --summary <path>\n```\n\nThe default scan reports only **exact** copies, and those are the ones to refactor first: an exact clone is almost always a real copy-paste. Two more passes find copies that were edited after pasting. They are **noisier**: they ignore names, values or a few statements on purpose, so they also surface blocks that merely look alike (models and DTOs, config tables, test setup, generated code, shared idioms). Run them only after the exact clones are dealt with, one family at a time, with tight settings, and treat what they report as leads to read rather than defects to fix:\n\n```bash\n# Type-2: renamed copies (other variable names, other constants), reported as \"(renamed)\".\n# Raise --min-tokens: with identifiers ignored, a short block is mostly placeholders.\nnpx jscpd --reporters ai --ignore-identifiers --min-tokens 70 <path>\n\n# Type-3: near-miss copies (one or two edited lines, or JS/TS/Python functions with the same structure),\n# reported as \"[~0.91 gap]\" and \"[~0.85 ast]\". Widen only if the tight run finds nothing.\nnpx jscpd --reporters ai --max-gap-lines 1 --similarity <path>\n```\n\nSee the **[jscpd](../jscpd/SKILL.md)** skill for full option reference, including cross-format group syntax, the clone-kind suffixes and how to read the summary.\n\n## Workflow\n\n1. Run jscpd with `--reporters ai` on the target path (add `--summary` on larger codebases to pick a starting point: files with high `dup%` and high token counts pay off most)\n2. Parse each clone line to identify the two duplicated locations (file + line range) and its kind: no suffix is an exact copy, `(renamed)` differs only in names or values, `[~N gap]` has a few edited lines in the middle, `[~N ast]` is a function pair with the same structure\n3. Read both code fragments from the source files\n4. Understand what the duplicated code does, and for renamed and similar clones list exactly what differs between the two sides\n5. **Triage renamed and similar clones before touching them.** Skip the pair, and say so, when any of these holds: the two sides do different things despite the same shape (a `switch` over different enums, two reducers with unrelated semantics); the sameness is intentional boilerplate (models, DTOs, config, route tables, test fixtures); the code is generated; a shared abstraction would need a vague name like `processData`; or the pair is under about 10 lines. Only a pair that would let you delete code and give the extraction a precise name goes on to the next step\n6. Design a refactoring: extract a shared function, class, module, or constant; the kind decides the strategy (below)\n7. Apply the refactoring — update both locations and all other usages\n8. Re-run jscpd **with the same flags** to confirm the clone is eliminated and the `dup%` of the touched files went down; a clone that was `(renamed)` will not show in a default run, so check with `--ignore-identifiers` again\n9. Repeat for remaining clones, highest-impact first: exact clones, then renamed, then similar. Report the skipped candidates separately from the refactored ones, with the reason, so nobody mistakes a normalized run's count for real duplication\n\n## Refactoring Strategies\n\n**Extract function** — when the duplicate is a block of logic:\n```ts\n// Before: same block in two places\n// After: shared function called from both places\n```\n\n**Extract module/utility** — when the duplicate spans multiple files in different domains:\n```ts\n// Move shared logic to a shared utility file and import it\n```\n\n**Extract constant or config** — when the duplicate is repeated data or configuration.\n\n**Template/base class** — when the duplicate is structural (e.g., repeated class shape).\n\n**Parameterize** — for `(renamed)` clones. The two sides are the same algorithm over different names or values, so the things that differ become parameters:\n```ts\n// Before: computeCartTotal(items) and computeBasketTotal(entries), same body, other names;\n//         limits-dev.js and limits-prod.js, same shape, other numbers\n// After: one function whose parameters are the identifiers that differed,\n//        or one function reading the values that differed from a config object\n```\nA renamed clone whose only difference is a literal is a missing constant or config entry, not a missing function.\n\n**Unify near-miss copies** — for `[~N gap]` clones. Read the unmatched lines: the gap is the one place the copies diverged, typically a guard, a log call or an extra field. Extract the common body and pass the divergence in:\n```ts\n// Before: saveUser and saveAccount, identical except one inserted validation line\n// After: one saveRecord(record, { validate }) with the inserted line behind the option,\n//        or the inserted line moved to the caller before the shared call\n```\nIf the gap changes the meaning rather than adding a step, keep two functions but extract the shared halves.\n\n**Merge similar functions** — for `[~N ast]` clones. The structure matches but names, literals and some statements do not. Diff the two functions first; the `ast` score tells how much is shared (`0.9` is a copy with one edit, `0.75` a copy with a couple of added statements plus renames). Extract the shared skeleton and inject what differs, as arguments, a strategy object, or a callback:\n```ts\n// Before: buildInvoice(order, customer, taxRate) and buildCreditNote(refund, account, vatRate):\n//         same loop, same rounding, one extra guard and one extra log call in the second\n// After: buildDocument(source, party, rate, { filter, onBuilt }) used by both\n```\nBelow about `0.8` the pair usually shares an idiom, not an implementation; leave those alone unless the summary shows the file is a hotspot anyway.\n\nAlways ensure:\n- All call sites are updated, not just the two reported by jscpd\n- Tests still pass after refactoring\n- The extracted abstraction has a clear, descriptive name\n- The re-run uses the same detection flags as the run that found the clone\n\n## Tips\n\n- Start with clones that have the highest line count — they have the most impact\n- Use the summary's `dup%` column to order the work: a large file with a high share of duplicated lines pays back first\n- A clone between test files may indicate a missing test helper\n- Clones across unrelated modules may signal a missing shared utility\n- A cross-format clone (same logic in a `.js` and a `.ts` file, found with `--cross-formats`) often means code was ported without deleting the original — consolidate into one implementation (usually the TypeScript one) and update imports, rather than extracting a third shared copy\n- Many `(renamed)` clones in one file usually mean one abstraction is missing, not many: look for the shared shape before extracting pair by pair. Many `(renamed)` clones across *test* files usually mean nothing: test cases are supposed to look alike\n- Do not gate CI (`--threshold`, `--fail-on-new-clones`) on the Type-2/Type-3 passes until the team has reviewed what they report on this codebase; gate on the exact run\n- `--similarity` covers JavaScript, TypeScript, Python, Java, Kotlin, Scala, C#, Go, Rust, C, C++, PHP, Ruby, Swift and Clojure, also inside Markdown code blocks and Vue, Svelte and Astro scripts; for other languages rely on the exact and `--max-gap-lines` passes\n- Use `--min-lines 10` to filter noise and focus on meaningful duplications\n- Keep a separate `--baseline` per set of detection flags when gating CI: renamed and similar runs fingerprint clones differently from exact runs\n","skills/jscpd/SKILL.md":"---\nname: jscpd\ndescription: Copy-paste detector for 220+ languages. Detect exact, renamed and near-miss duplicated code, measure duplication percentages, and find refactoring hotspots with the codebase summary.\n---\n\n# jscpd\n\nCopy-paste detector for programming source code, supports 220+ languages. Use this skill to run jscpd and understand its output.\n\n## Quick Start\n\n```bash\n# Run with ai reporter (compact output optimized for agents)\nnpx jscpd --reporters ai <path>\n\n# With ignore patterns\nnpx jscpd --reporters ai --ignore \"**/node_modules/**,**/dist/**\" <path>\n\n# Scope to specific formats\nnpx jscpd --reporters ai --format \"javascript,typescript\" <path>\n\n# Second pass, noisier: copies that only differ in names and values (Type-2, \"renamed\").\n# Review each hit before acting on it.\nnpx jscpd --reporters ai --ignore-identifiers --min-tokens 70 <path>\n\n# Third pass, noisier still: copies with a few edited lines or the same function\n# structure (Type-3, \"similar\"). Keep the settings tight.\nnpx jscpd --reporters ai --max-gap-lines 1 --similarity <path>\n\n# Where to refactor first: clone list plus a hotspot summary\nnpx jscpd --reporters ai --summary <path>\n\n# Most complex files only, without clone detection\nnpx jscpd --complexity --reporters ai <path>\n```\n\n## AI Reporter Output Format\n\nThe `ai` reporter produces compact, token-efficient output designed for agent consumption:\n\n```\nClones:\nsrc/ foo.ts:10-25 ~ bar.ts:42-57\nsrc/utils/helpers.ts:100-120 ~ src/utils/other.ts:5-25\nsrc/cart/ basket.js:1-9 ~ cart.js:1-9 (renamed)\nsrc/api/ save-account.js:1-12 ~ save-user.js:1-11 [~0.91 gap]\nsrc/billing/ orders.ts:1-14 ~ refunds.ts:1-15 [~0.85 ast]\n---\n5 clones · 4.2% duplication\n```\n\nEach line represents one clone pair:\n- **Same file**: `path/file.ts 10-25 ~ 45-60` (shared path shown once)\n- **Same directory**: `shared/prefix/ file-a.ts:10-25 ~ file-b.ts:42-57` (common prefix factored out)\n- **Different paths**: `path/a.ts:10-25 ~ path/b.ts:42-57`\n\nA suffix tells the **kind** of clone; no suffix means an exact copy:\n- `(renamed)`: the two blocks differ only in identifier names, literal values or annotations (Type-2). Only appears with `--ignore-identifiers`, `--ignore-literals` or `--ignore-annotations`.\n- `[~0.91 gap]`: two exact clones merged across up to `--max-gap-lines` unmatched lines (Type-3). The number is matched tokens over the merged span.\n- `[~0.85 ast]`: two functions whose normalized syntax trees share at least `--similarity` of their subtrees (Type-3). The number is that share. The names of called functions and methods and the operators count, local names and literal values do not, so a renamed copy with other constants scores 1 and a copy that calls other methods scores lower. A name still has to stand for one name on the other side: a copy that swaps the roles of two names scores lower.\n\n## Options\n\n| Option | Description |\n|--------|-------------|\n| `--reporters ai` | Use the AI-optimized reporter (compact clone list for agents) |\n| `--reporters html` | Generate HTML report |\n| `--reporters json` | Output JSON report |\n| `--min-tokens N` | Minimum tokens to consider a duplication (default: 50) |\n| `--min-lines N` | Minimum lines to consider a duplication (default: 5) |\n| `--threshold N` | Exit with error if duplication % exceeds N |\n| `--ignore \"glob\"` | Ignore patterns (comma-separated) |\n| `--format \"list\"` | Limit to specific languages (e.g. `typescript,javascript`) |\n| `--cross-formats \"groups\"` | Detect clones across related formats (e.g. `javascript,typescript` or the `js-ts` preset) |\n| `--ignore-identifiers` | Treat all identifiers as equal, so blocks that differ only in variable, function or type names match (Type-2, reported as `renamed`); a name must stand for one name on the other side, so look-alikes with another data flow stay out, while a field and a local of one text are two names, so `this.url = url` still matches `this.url = link` (`--ignore-identifiers=blind` matches any names) |\n| `--ignore-literals` | Treat all string literals as equal and all numeric literals as equal (Type-2) |\n| `--ignore-annotations` | Drop `@Name` / `@Name(...)` annotations and decorators before matching, in languages where `@` means one (Type-2) |\n| `--max-gap-lines N` | Merge clones of one file pair separated by at most N unmatched lines into one `similar` clone (Type-3, default: 0 = off) |\n| `--similarity [RATIO]` | Report pairs of functions whose normalized syntax trees share at least RATIO of their subtrees, in `(0, 1]`, as `similar` clones (Type-3, 0.8 without a value). JavaScript, TypeScript, Python, Java, Kotlin, Scala, C#, Go, Rust, C, C++, PHP, Ruby, Swift and Clojure; test files are left out |\n| `--min-nodes N` | The fewest nodes of a normalized syntax tree a function needs for `--similarity` (default: 20) |\n| `--summary` | Append a codebase summary: top files/folders by tokens, lines, size, complexity, with duplication share |\n| `--summary-top N` | Number of entries in each summary top list (default: 10) |\n| `--summary-by metric` | Summary ranking metric: `tokens`, `lines`, `size`, `complexity` (default: `tokens`) |\n| `--kind list` | Report only these clone kinds: `exact`, `renamed`, `similar`, `gap`, `ast` (never enables a detector) |\n| `--complexity` | Complexity tables only, without clone detection (reporters: console, ai, json) |\n| `--dead-code` | Find unused files, exports, module-private declarations and imports — JavaScript, TypeScript, JSX, TSX, Vue, Svelte, Astro, Python. Each finding carries a 0-100 confidence score |\n| `--dead-code-categories list` | Limit `--dead-code`/`--dashboard` to categories: `unused-file`, `unused-export`, `unused-symbol`, `unused-import` |\n| `--min-confidence N` | Floor for `--dead-code`/`--dashboard` findings, 0-100 (default: 60) |\n| `--entry \"glob\"` | Extra entry points for dead-code analysis, beyond what's inferred from `package.json`/`pyproject.toml`/framework conventions |\n| `--include-tests` | Count test-only usage as \"used\" for dead-code analysis (excluded by default) |\n| `--include-entry-exports` | Also flag an entry file's own unused exports (excluded by default: its exports are the public API) |\n| `--dashboard` | One screen: health badge, project size and largest code files, duplication, complexity, dead code (JS/TS/Python); `-r json` writes `jscpd-dashboard.json` |\n| `--health` | Project health badge only: 0-100 score and grade from duplication, dead code and complexity (`-r ai` one line, `-r json`, `-r badge`) |\n| `--health-input FILE` | Add metrics from other tools (coverage, tests, security) to the health score |\n| `--pattern \"glob\"` | Glob pattern to select files |\n| `--no-gitignore` | Do not respect `.gitignore` (it is respected by default) |\n| `--output \"path\"` | Directory to write reports to |\n| `--silent` | Suppress console output (useful with file reporters and `--output`) |\n| `--list` | List all supported formats and exit |\n| `--no-tips` | Disable tips in output (skipped automatically when stdout is not a TTY or `CI` or `JSCPD_NO_TIPS` is set) |\n| `--config \"path\"` | Path to .jscpd.json config file |\n\n## Clone Kinds: Exact, Renamed, Similar\n\nBy default jscpd reports **exact** clones only: the token sequences are identical (whitespace, layout and, depending on the mode, comments do not count). Two opt-in families widen the net. Run them as separate passes after the default scan, because they find more and longer clones and change what a \"clone\" means.\n\n**These passes are noisy by design.** An exact clone is almost always a real copy. A renamed or similar clone is a *candidate*: the flags deliberately ignore the very things (names, values, a statement or two) that often make two blocks different in meaning. Expect false positives from:\n\n- boilerplate that is supposed to look alike: DTOs and models, config tables, enum-like maps, route or handler registrations, builders\n- test files: `describe`/`it` blocks, fixtures and setup code repeat the same shape on purpose\n- generated code, migrations, serializers, protocol bindings\n- language idioms: two `reduce` loops or two `switch` statements that share structure but not logic\n- small blocks: with `--ignore-identifiers` a 50-token block is mostly placeholders, so raise `--min-tokens`\n\nRules that keep the noise manageable:\n\n- Run them **after** the exact clones are handled, one family at a time, so every hit is attributable to one flag.\n- Start conservative: `--ignore-identifiers` alone (add `--ignore-literals` only when constants are the known problem), `--max-gap-lines 1` or `2`, `--similarity 0.85` or higher, and `--min-tokens 70` or more for the identifier pass. Widen only when the tight run comes back empty.\n- Treat every `(renamed)` or `[~…]` line as a lead to read, not a defect to fix. Do not gate CI (`--threshold`, `--fail-on-new-clones`) on these passes unless the team has reviewed what they report on the codebase.\n- Never claim \"N duplicates found\" from a normalized run without saying which flags produced them.\n\n### Type-2: renamed clones (`--ignore-identifiers`, `--ignore-literals`, `--ignore-annotations`)\n\nCopies where someone renamed the variables or changed the constants:\n\n```bash\nnpx jscpd --reporters ai --ignore-identifiers <path>                      # function a(x) {…} matches function b(y) {…}\nnpx jscpd --reporters ai --ignore-identifiers --ignore-literals <path>    # …and 10 matches 25, 'dev' matches 'prod'\nnpx jscpd --reporters ai --ignore-annotations <path>                      # @Override / @Deprecated no longer split a clone\n```\n\n```\nClones:\nbasket.js:1-9 ~ cart.js:1-9 (renamed)\nlimits-dev.js:1-13 ~ limits-prod.js:1-13 (renamed)\n---\n```\n\n- Keywords keep their meaning (`return` never matches `retry`), so structure still has to match.\n- `--ignore-annotations` only acts in Java, Kotlin, Scala, Groovy, Python, Dart, Swift, JavaScript and TypeScript; in Ruby, Perl, T-SQL, Razor and CSS `@` means something else and is left alone.\n- Positions in the report still point at the original source.\n- Config keys: `ignoreIdentifiers`, `ignoreLiterals`, `ignoreAnnotations`.\n\n### Type-3: near-miss clones (`--max-gap-lines`, `--similarity`)\n\nCopies with a few edited lines, or functions rewritten with the same shape:\n\n```bash\nnpx jscpd --reporters ai --max-gap-lines 2 <path>      # a copy with up to 2 inserted/changed lines becomes one clone\nnpx jscpd --reporters ai --similarity <path>           # functions whose normalized syntax trees share ≥82% of their subtrees\n```\n\n```\nClones:\nsave-account.js:1-12 ~ save-user.js:1-11 [~0.91 gap]\norders.ts:1-14 ~ refunds.ts:1-15 [~0.85 ast]\n---\n```\n\n- `--max-gap-lines N` only joins clones the exact run already found, so it removes fragmentation rather than inventing matches; it works in every language. A merge is refused when the gap holds more tokens than the halves share (similarity would drop under `0.5`).\n- `--similarity` normalizes the syntax tree of every function (local names and literals become markers, each name standing for one name on the other side; called names and operators stay) and scores the share of subtrees two trees have in common, so a renamed copy scores `1.0`, one inserted statement in a function of ten about `0.85`, a copy that calls other methods less. It applies to JavaScript, TypeScript, Python, Java, Kotlin, Scala, C#, Go, Rust, C, C++, PHP, Ruby, Swift and Clojure, and to their code blocks in Markdown and in Vue, Svelte and Astro files; other formats are a silent no-op. A function nested in another is part of it, and test files are left out. Start at the default `0.8` and lower to `0.7` only when looking for leads; read both functions before believing a low score.\n- `similar` takes precedence over `renamed` when both apply (a merged clone is no longer identical even after normalization).\n- Config keys: `maxGapLines`, `similarity`, `minNodes`.\n\n### Where the kind shows up\n\n- `console`: `Clone found (javascript, renamed)`, `Clone found (javascript, similar (gap) ~0.91)`, `Clone found (javascript, similar (ast) ~0.75)`.\n- `json`: `\"kind\": \"exact\" | \"renamed\" | \"similar\"`, plus `\"similarity\"` and `\"method\": \"gap\" | \"ast\"` for similar clones, and the size of each normalized tree as `\"nodes\"` in `firstFile` and `secondFile` for ast ones.\n- `sarif`: rules `jscpd/duplicate-code`, `jscpd/renamed-code`, `jscpd/similar-code` (clones merged across a gap), `jscpd/similar-function` (functions and classes paired by `--similarity`) and `jscpd/semantic-code` (`--semantic`); Code Climate uses the same `check_name` values.\n- A default run reports only `exact` clones and its output is unchanged by these features.\n- Normalized runs produce different clone fingerprints than exact runs: keep a separate `--baseline` file per configuration.\n\n## Codebase Summary (`--summary`)\n\n`--summary` appends a refactoring-hotspot overview to the run output — use it to decide **where to refactor first** before diving into individual clones:\n\n```bash\n# Compact clone list + compact summary, optimized for agents\nnpx jscpd --reporters ai --summary --no-tips <path>\n\n# Rank by complexity instead of tokens, top 5 lists\nnpx jscpd --reporters ai --summary --summary-by complexity --summary-top 5 <path>\n```\n\nWith the `ai` reporter the summary is one line per entry:\n\n```\nSummary by tokens (321 files, 129 folders):\nfiles (tokens/lines/size/cx/dup%):\nsrc/files.ts 2052/363/11662/80/0.0%\nlong-line/theme-branded.js 498/15/2.5K/10/93.3%\n...\nfolders (files/tokens/lines/size):\nsrc/core 8/5264/843/27136\n...\n```\n\nHow to read it:\n- **Top files** are ranked by the `--summary-by` metric, but every row carries all metrics — `tokens/lines/size/cx/dup%`.\n- **cx** is a language-agnostic cyclomatic-complexity estimate from the token stream (1 + decision-point tokens like `if`/`while`/`&&`); treat it as a ranking signal, not an exact metric.\n- **dup%** is the share of the file's lines covered by detected clones — a large file with high `dup%` is the best refactoring target. It reflects the clones of the current run, so with the Type-2/Type-3 flags it rises accordingly.\n- **Folders** aggregate files into their direct parent directory (no cumulative ancestor totals).\n- In `console` reporters the summary renders as aligned tables; in the `json` report it appears as an additive `summary` key (absent when the flag is off).\n\nConfig file equivalents: `\"summary\": true`, `\"summaryTop\": 10`, `\"summaryBy\": \"tokens\"`.\n\n## Cross-Format Clone Detection\n\nBy default each format is compared only against itself. `--cross-formats` defines groups of related formats that share one comparison pool, so a block duplicated between a `.js` and a `.ts` file is reported as a clone:\n\n```bash\n# One group: compare JavaScript and TypeScript files together\nnpx jscpd --reporters ai --cross-formats \"javascript,typescript\" <path>\n\n# Preset covering javascript, jsx, typescript, tsx\nnpx jscpd --reporters ai --cross-formats \"js-ts\" <path>\n\n# Multiple groups are separated by \";\"\nnpx jscpd --reporters ai --cross-formats \"javascript,typescript;css,scss\" <path>\n```\n\nNotes:\n- 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)`. Reported positions still reference the original source.\n- Groups need at least two formats; groups sharing a format are merged into one pool.\n- In per-format statistics, a cross-format clone is attributed to one member format of the group.\n- In config files the key is `crossFormats` (or `cross-formats`) and accepts a string (`\"javascript,typescript;css,scss\"`), an array of strings (`[\"javascript,typescript\", \"css,scss\"]`), or an array of arrays (`[[\"javascript\",\"typescript\"],[\"css\",\"scss\"]]`).\n\n## Comparing Two Codebases (`--compare`)\n\n`--compare SOURCE TARGET` pairs the functions of two folders with the `--semantic` embedding model and lists which functions of each side have a counterpart in the other: the progress of a port to another language or framework, or the parity of two implementations of one app. It takes exactly two paths that do not overlap, needs the model once (`--semantic-download`), and runs no clone detection.\n\n```bash\nnpx jscpd --compare python-lib/ rust-lib/                                  # progress of a port and what is left\nnpx jscpd --compare ios/ android/ -r console-full                          # parity, with every pair and its similarity\nnpx jscpd --compare python-lib/ rust-lib/ -r json -o .jscpd-compare --silent  # jscpd-compare.json for an agent\n```\n\nFunctions pair by code first (each is the other's closest match), then by name when the names match once case and underscores are ignored and the code is similar enough. Every pair has its similarity and a level (`high`, `medium`, `low`) on the scale of the model, and the default report lists the pairs under other names on their own. jscpd measures tests and code in two blocks and pairs a test only with a test. `--min-tokens` defaults to 30 here. Reporters: `console`, `console-full`, `json`, `markdown`. See the **[compare-codebases](../compare-codebases/SKILL.md)** skill for how the comparison works and how to check it, and **[code-migration](../code-migration/SKILL.md)** for porting code with it.\n\n## Configuration File\n\nCreate a `.jscpd.json` in your project root:\n\n```json\n{\n  \"threshold\": 0,\n  \"reporters\": [\"ai\"],\n  \"ignore\": [\"**/node_modules/**\", \"**/dist/**\", \"**/*.min.*\"],\n  \"format\": [\"typescript\", \"javascript\"],\n  \"minLines\": 5,\n  \"minTokens\": 50,\n  \"ignoreIdentifiers\": false,\n  \"ignoreLiterals\": false,\n  \"maxGapLines\": 0,\n  \"similarity\": 1,\n  \"summary\": false,\n  \"output\": \"./reports/jscpd\"\n}\n```\n\n## Refactoring Duplicated Code\n\nOnce you've detected clones, use the **dry-refactoring** skill for a guided workflow to eliminate them, with a strategy per clone kind:\n\n→ **dry-refactoring** — step-by-step refactoring strategies and workflow for removing duplication. Install with:\n  ```bash\n  npx skills add https://github.com/kucherenko/jscpd --skill dry-refactoring\n  ```\n\n## Improving Overall Codebase Health\n\nFor a broader \"clean up this codebase\" pass — duplication, then dead code, then the largest/most complex files, prioritized from `--health`/`--dashboard` and re-measured at the end — use **codebase-refactoring**:\n\n  ```bash\n  npx skills add https://github.com/kucherenko/jscpd --skill codebase-refactoring\n  ```\n"},"files":{"benchmark/tools/README.md":"# Benchmark Tools\n\nThis directory contains all third-party tools required by the benchmark script. Install each tool by following the instructions below, then run `./benchmark.sh` from the parent directory.\n\n## Quick Install\n\n```bash\ncd tools/\nnpm install\n```\n\nThis installs **jscpd-rs** and **Fallow** (the only tools distributed via npm). The remaining tools must be installed manually.\n\n## Tools\n\n### jscpd@5 (local build)\n\nThe jscpd v5 Rust binary, built from the `rust/` directory in this repository.\n\n```bash\ncd ../../rust/\ncargo build --release\n```\n\nThe binary is expected at `../../rust/target/release/jscpd`.\n\n### jscpd-rs (npm)\n\nInstalled in `tools/node_modules/` via `npm install`. The npm package downloads a prebuilt Rust binary for the current platform.\n\n```bash\nnpm install\n```\n\nBinary: `node_modules/.bin/jscpd-rs`\n\n### Fallow dupes\n\nInstalled in `tools/node_modules/` via `npm install`. Fallow is a Rust-based code intelligence tool; the `dupes` subcommand performs structural clone detection for TypeScript/JavaScript projects.\n\n```bash\nnpm install\n```\n\nBinary: `node_modules/.bin/fallow`\n\n> **Note:** Fallow must run from inside the target directory (it has no path argument). The benchmark script `cd`s into the fixtures directory before invoking it.\n\n### PMD CPD\n\nInstall via Homebrew (macOS) or download from [pmd.github.io](https://pmd.github.io).\n\n```bash\nbrew install pmd\n```\n\nPMD requires a Java runtime (8+). The `pmd` command must be on `$PATH`.\n\n> **Note:** The benchmark runs PMD CPD for all 34 supported languages individually and sums the results. This takes ~30 seconds.\n\n### Duplo\n\nA prebuilt macOS arm64 binary is included at `tools/duplo`. To replace it:\n\n1. Download from [github.com/dlidstrom/Duplo/releases](https://github.com/dlidstrom/Duplo/releases)\n2. Place the binary at `tools/duplo`\n3. `chmod +x tools/duplo`\n\n```bash\n# Verify\n./duplo -help\n```\n\n### Simian\n\nA prebuilt JAR is included at `tools/simian.jar`. To replace it:\n\n1. Download from [simian.quandarypeak.com](https://simian.quandarypeak.com)\n2. Place the JAR at `tools/simian.jar`\n\nRequires a Java runtime:\n\n```bash\njava -jar tools/simian.jar -help\n```","docs/api.md":"# Programming API\n\njscpd is a Rust engine. The CLI ships as the `jscpd` crate on crates.io (installs the `jscpd` and `cpd` binaries) and as the `jscpd` / `cpd` npm packages (prebuilt binaries); the engine itself is a set of library crates you can depend on directly.\n\n## Rust\n\nUse the `cpd-finder` crate — it is the orchestration entry point and pulls in the tokenizer, detection core, and reporters:\n\n```toml\n[dependencies]\ncpd-finder = \"0.1\"\n```\n\n```rust\nuse cpd_finder::orchestrate::{RunConfig, run};\n\nlet config = RunConfig {\n    paths: vec![\"./src\".into()],\n    min_tokens: 50,\n    ..Default::default()\n};\n\nlet result = run(&config).unwrap();\nprintln!(\"Found {} clones\", result.clones.len());\nprintln!(\"Analyzed {} files\", result.statistics.total.sources);\n```\n\nA complete, runnable version is in [`examples/rust-cpd-finder`](../examples/rust-cpd-finder).\n\nSemantic clones come from the `cpd-semantic` crate, which adds a clone pass to the run:\n\n```rust\nuse cpd_finder::orchestrate::{RunConfig, run};\nuse cpd_semantic::{SemanticOptions, SemanticPass};\nuse std::sync::Arc;\n\n// The local model is downloaded once, e.g. with `jscpd --semantic-download`.\nlet options = SemanticOptions::default();\nlet paths = vec![\"./src\".into()];\nlet embedder = cpd_semantic::embedder(&options, &paths, true)?;\n// The thresholds of the rules, on the scale of the options' model.\nlet thresholds = options.thresholds();\nlet config = RunConfig {\n    paths,\n    passes: vec![Arc::new(SemanticPass::new(embedder, thresholds, options.scope))],\n    ..Default::default()\n};\nlet result = run(&config)?;\n```\n\nThe `jscpd` crate's own library target is **not a public API** — it exists to share helpers with the crate's tests and may change in any release. Depend on the engine crates instead.\n\n### Crate Architecture\n\n| Crate | Description |\n|-------|-------------|\n| [`cpd-core`](https://crates.io/crates/cpd-core) | Core data models and hashing (Rabin-Karp rolling hash) |\n| [`cpd-tokenizer`](https://crates.io/crates/cpd-tokenizer) | Source code tokenization (224 formats, uses `oxc_parser` for JavaScript/TypeScript) — pure, no I/O |\n| [`cpd-similarity`](https://crates.io/crates/cpd-similarity) | Structural similarity (`--similarity`): functions from tree-sitter syntax trees and a Clojure reader, the subtree fingerprints of their normalized trees and the exact search for pairs; pure, no I/O |\n| [`cpd-finder`](https://crates.io/crates/cpd-finder) | File walking, orchestration, git blame (`rayon` + `ignore` + `globset`) |\n| [`cpd-reporter`](https://crates.io/crates/cpd-reporter) | Output format rendering (15 reporters) |\n| [`cpd-semantic`](https://crates.io/crates/cpd-semantic) | Semantic clones (`--semantic`, experimental), as a clone pass for `cpd-finder` |\n\nSee [Packages](./packages.md) for versions and paths.\n\n## Other languages\n\nThere is no Node.js (or other language) binding for the v5 engine. From another language, run the binary and read the `json` reporter's output (`--reporters json --output <dir>` writes `<dir>/jscpd-report.json`), or talk to it over the [MCP stdio transport](./ai-ready.md#stdio-transport-rust-v5).\n\nThe TypeScript API of jscpd v4 (`jscpd()`, `detectClones()`, `@jscpd/core`, LevelDB/Redis stores) is maintained on the [`master-v4`](https://github.com/kucherenko/jscpd/tree/master-v4) branch and published as `jscpd@4`.\n","skills/code-migration/SKILL.md":"---\nname: code-migration\ndescription: Move code from one implementation to another function by function, tests first and code second, and check two implementations of one app for parity, with jscpd --compare as the progress measure and a coverage map binding each function to its tests. Use when porting a library or app to another language or framework (Java to Kotlin, JavaScript to Rust, a Python library to TypeScript, an iOS app to Android), when asked what is left to port, or when comparing the Android and iOS versions of an app.\n---\n\n# code-migration\n\n`jscpd --compare SOURCE TARGET` pairs every function of one folder with the function of the other folder that does the same job, in any pair of languages, and lists the functions that have no counterpart. This skill uses it to measure a port: what is ported, what is left, and whether the port you just wrote was recognized.\n\nA port runs in two phases: the tests first, then the code they check. A coverage report of the source's tests tells which tests exercise which function, so each function is ported together with the tests that prove it works. See [Plan: tests first, then code](#plan-tests-first-then-code).\n\nTwo words are used throughout:\n\n- **source**: the implementation you port from, such as the iOS app, the Python library, or the JavaScript package;\n- **target**: the implementation you port to, such as the Android app, the Rust crate, or the TypeScript rewrite.\n\nNeither has to be old or new. The source may stay in production and keep changing, and the target may already hold features the source lacks. For two implementations that both live on (`ios/` and `android/`), the same report shows parity; see [Parity](#parity-between-two-implementations).\n\njscpd finds functions in JavaScript, TypeScript, JSX, TSX, Vue, Svelte, Astro, Python, Rust, Go, Java, Kotlin, C#, C, C++, PHP, Ruby, Scala and Swift. The [compare-codebases](../compare-codebases/SKILL.md) skill explains how the comparison pairs functions and how to check its result; the [jscpd](../jscpd/SKILL.md) skill covers the rest of the tool.\n\n## Setup\n\n`--compare` pairs functions with a code embedding model that runs inside jscpd. Download it once (548 MB, CodeRankEmbed). Ask the user before you start the download:\n\n```bash\nnpx jscpd --semantic-download\n```\n\njscpd caches the vectors, so a repeat run embeds only the functions whose code changed and takes seconds.\n\nPick the two paths and keep them fixed for the whole port: the source first, the target second. Two paths are required, and they must not overlap (`app/` and `app/android/` is refused). The target may be empty at the start.\n\nOne run measures both phases: the report has a `Code` block and a `Tests` block (a `code` and a `tests` section in JSON), and a test pairs only with a test. jscpd tells a test by the conventions of its language: test files such as `*_test.go`, `test_*.py`, `*.test.ts` or `*Test.java`, folders such as `tests/`, `__tests__/` or `src/test/`, Rust tests in `#[cfg(test)]` modules, and JavaScript test cases such as `it('rounds cents', () => …)`. Phase 1 reads the `Tests` block, phase 2 the `Code` block.\n\n### Keep vendored and generated code out\n\n`--compare` counts and embeds every function in both paths. Vendored dependencies (`vendor/` from `cargo vendor` or `go mod vendor`, `third_party/`), installed packages (`node_modules/`, `.venv/`), build output (`target/`, `build/`, `dist/`) and generated code are not part of the port, yet a vendored crate tree alone holds thousands of functions. With them in a path, the first run embeds all of them and takes tens of minutes instead of seconds, and the percentages describe the dependencies instead of the port.\n\njscpd skips what `.gitignore` excludes, but only inside a git repository. Before the first run:\n\n1. Check both paths for such folders. A target you create for the port gets them as soon as you build it or vendor its dependencies, so check again after the first build.\n2. Suggest a `.gitignore` to the user that lists the folders the target's language and tools produce, plus the report folder. For a Rust addon built with napi-rs:\n\n   ```gitignore\n   /target/\n   /vendor/\n   node_modules/\n   *.node\n   .jscpd-compare/\n   ```\n\n   If the target is not in a git repository, tell the user that jscpd reads the `.gitignore` only after `git init`.\n3. Until the `.gitignore` works, pass the same folders with `--ignore`, and keep the globs the same for every run:\n\n   ```bash\n   npx jscpd --compare node-lib/ rust-lib/ --ignore \"**/vendor/**,**/target/**,**/node_modules/**\"\n   ```\n\nThe totals line shows when something slipped through. Suspect vendored or generated code in a path when the target has far more functions than the source, or when a run embeds hundreds of functions after a small change.\n\n## Measure\n\nRun the console report for yourself and for the user. Here a Python billing library is being ported to TypeScript:\n\n```bash\nnpx jscpd --compare billing-py/ billing-ts/\n```\n\n```text\n 71% 5 of 7 functions in billing-py/ have a counterpart in billing-ts/\n 80% 4 of 5 functions in billing-ts/ have a counterpart in billing-py/\n\nbilling-py/\n  file         paired  similarity  counterpart\n  billing.py   4 / 5   0.89        billing.ts\n  shipping.py  1 / 2   0.91        shipping.ts\n\nbilling-ts/\n  file         paired  similarity  counterpart\n  billing.ts   3 / 4   0.89        billing.py\n  shipping.ts  1 / 1   0.91        shipping.py\n\nPaired under other names (1):\n  billing-py/                   billing-ts/             similarity\n  billing.py:28 tax_for_region  billing.ts:27 salesTax  0.87 high\n\nOnly in billing-py/ (2):\n  billing.py (1)\n    46  due_date                6 lines\n  shipping.py (1)\n    18  estimate_delivery_days  8 lines\n\nOnly in billing-ts/ (1):\n  billing.ts (1)\n    39  toCurrency  8 lines\n```\n\n- The first line is the port's progress: the share of the source's functions that have a counterpart in the target. \"Only in\" the source is the work left.\n- The second line and \"Only in\" the target describe the target's own code: helpers the port needed, features the source never had, or a port jscpd did not recognize (see [Misses](#misses)).\n- Each file row gives its paired functions, the mean similarity of their pairs (with the number of `low` pairs, if any), and the counterpart file: the file on the other side that holds most of its counterparts. Use the counterpart to decide where a missing function goes.\n- \"Paired under other names\" lists the pairs whose names differ even once case and underscores are ignored: renamed ports, constructors, platform names. Each has its similarity and level (see [Reading pairs](#reading-pairs)). These are ported already, even though a search by name would not find them.\n- With an empty target the report is one line of totals plus `billing-ts/ has no functions yet`.\n\nFor work you plan and track, read the JSON report instead of the console:\n\n```bash\nnpx jscpd --compare billing-py/ billing-ts/ -r json -o .jscpd-compare --silent\n```\n\njscpd measures tests and code apart and pairs a test only with a test, so `.jscpd-compare/jscpd-compare.json` has a `code` and a `tests` section of the same shape. Each section has `sides[0]` (the source) and `sides[1]` (the target), each with `path`, `functions`, `matched`, `percentage`, `files` (`file`, `functions`, `matched`, `counterpart`, `similarity`, `lowPairs`) `unmatched` (`file`, `name`, `start`, `end`) and `readyToPort` (the unmatched functions whose callees all have a counterpart, with their number of `callers`, most called first), and `pairs`, each with `a` (source), `b` (target), `similarity`, `level` (`high`, `medium`, `low`), `renamed` (the names differ) and `matchedBy` (`code` or `name`). Paths are relative to each side's folder. Add `.jscpd-compare/` to `.gitignore` or write the report outside the repository.\n\n`-r console-full` also prints every pair with its similarity, `-r markdown` writes `jscpd-compare.md`, a table you can paste into a PR description or a tracking issue, and `-r html` writes `jscpd-compare.html`, a migration map the user can open to see the two dependency graphs side by side, or the same pairs as a table.\n\n## Plan: tests first, then code\n\nPort the tests before the code. Ported tests define, before any target code exists, what it means for each function to be ported: when the function lands, its tests pass or show what still differs. A port written before its tests can only be checked against your reading of the source.\n\n### Bind tests to functions with coverage\n\nA test's name does not say which functions it runs: a test of `checkout` also runs `apply_discount` and `tax_for_region`. Coverage does. Before porting anything, build a map from each source function to the tests that exercise it.\n\n1. Get a coverage report of the source's tests that says which test ran which lines: recorded per test, or per test file when the project's tooling has no per-test mode.\n2. Take the source's functions and their lines from the `code` section of the JSON report. Every counted function is either in the source's `unmatched` list or the `a` side of a pair, each with `file`, `name`, `start` and `end`. Functions under `--min-tokens` or `--min-lines` are not listed there; take those from the coverage report's own function list, which most formats have.\n3. A test covers a function when it runs a line from the function's `start` to its `end`. Write the map both ways, function to tests and test to functions, to a file next to the report (for example `.jscpd-compare/test-map.json`), and rebuild it when the source's tests change.\n\nUse the map for three things:\n\n- which tests to port with a function, and which functions a ported test needs before it can pass;\n- the source functions no test covers. Before porting such a function, write a test for it on the source side that records what it does today (a characterization test), and port that test with the others. A port with no test has nothing to check it;\n- the order of work: tests that need few functions come first, since they turn green soonest.\n\nCoverage says which code a test runs, not what it checks. A function that a test only passes through on the way to another is weakly bound to that test; prefer the tests whose names or assertions are about the function.\n\n### Phase 1: port the tests\n\n1. Take the source's unmatched tests from the `tests` section of the JSON report. A JavaScript or TypeScript test case written as a callback, `it('rounds cents', () => …)`, goes by its title, so it pairs with `test_rounds_cents` in pytest or `rounds_cents` in Rust like any named test.\n2. Port the tests file by file, into the target's test layout, against the API the target will have. Keep inputs and expected values exactly as they are. Where the target must behave differently (a platform limit, a language's number types), write the difference into the test with a comment and tell the user.\n3. A ported test calls functions that do not exist yet, so it fails. In a compiled language it stops the whole test build. Keep such tests out of the build until their functions land, for example a module declaration left commented out, a cfg feature or an excluded source set, and turn them on one by one in phase 2. Do not write empty functions to make the tests compile: a stub under a source name can pair by name and count as ported.\n4. Run the target's tests. The ported tests that pass already cover what the target has; the failing or disabled ones, read against the test map, are the work list for phase 2.\n\n### Phase 2: port the code\n\nPort the code one function at a time, with its tests already in place.\n\n1. Run the JSON report and take the source's `unmatched` list from its `code` section.\n2. Order the work. Start with functions whose tests are ported, and port a function after the functions it calls, since a caller ported before its helpers has nothing to call. The source's `readyToPort` list is that order's front: the functions whose callees all have a counterpart already, most called first. Take the next one from there, and rerun the report after each batch, since every port can make its callers ready. Within that order, finish one file before starting the next, so each target file fills up in one go.\n3. Before writing a port, make sure the target does not have it already. Pairs with `renamed: true` are ported functions under other names; they are not in `unmatched`, but check them when the user asks where a function went. Then search the target for an equivalent that jscpd missed: in the counterpart file from `files`, under other names (constructors, merged functions, a platform or library call that replaces the helper). If one exists, do not port the function again. Note the equivalent and move on.\n4. Write the port in the counterpart file, or in the file the target's layout puts it in. Keep the name recognizable in the target language's convention (`encodeBinary` becomes `encode_binary` in Rust or Python), because jscpd pairs short functions by name. Follow the style of pairs that are already done in the same file. `console-full` lists them.\n5. Turn on the function's ported tests, as the test map lists them, and run them. When they fail, fix the port, not the test, unless the user agrees that the target should behave differently. A pair in the report says the two functions look alike; the tests say whether they behave alike.\n6. Run the target's coverage for those tests and check that they run the new function. A ported test that never reaches it is bound to something else on the target (a helper, a mock, an old path), and it proves nothing about the port.\n7. Run the comparison again with the same paths and options. Check that the function has left `unmatched`, that its pair joins the right function in the target, and its level. A `high` pair is done. A `low` pair to your port usually means the port differs a lot from the source in structure; read both and make sure the behavior matches. A pair to a different function means the port is not recognized, or it resembles the wrong thing; read both before going on.\n8. Report progress to the user with both measures, as the reports print them: tests ported and passing, and functions ported (`functions 73% → 76%, 3 ported: …; tests 41 of 44 ported, 38 passing`), with the functions you decided not to port and why.\n\nRepeat until the source's `unmatched` list holds only functions you decided not to port. Typical reasons: dead code (check with `npx jscpd --dead-code` on the source where it supports the language), code the target platform or its libraries provide (a JSON parser, a retry helper), platform glue with no equivalent on the target (an iOS delegate callback, an Android notification channel), and features the user decided not to carry over. List these in a notes file or the PR, one line each, so the remaining percentage is explained.\n\nWhen the source keeps changing during the port, a function that was paired can come back as unmatched after a rewrite, and new source functions join the list. Rerun the report before each batch of work instead of reusing an old list.\n\n## Parity between two implementations\n\nWhen both implementations live on, as the iOS and the Android app do, `npx jscpd --compare ios/ android/` shows parity. Read both \"Only in\" lists:\n\n- a function on one side only is either platform code (Android notification channels, iOS delegate callbacks) or a feature the other side lacks. Report each missing feature to the user before implementing it;\n- to add a missing feature, treat the side that has it as the source for that feature: bind its tests with coverage, port the tests, then the code, as in [Plan: tests first, then code](#plan-tests-first-then-code).\n\njscpd pairs functions within modules that already match. A module is the folder right under the deepest folder each side's files share, so keep the two trees parallel (`ios/<plugin>` and `android/<plugin>`) for the best pairing.\n\n## Reading pairs\n\n- `level` rates the similarity on the scale of the model in use, so it means the same with every model. `high` (0.7125 and up with the default CodeRankEmbed, across languages) is almost always the same function. `medium` is usually the same function, restructured. `low` needs reading both functions, since related code pairs there too (a function counting UTF-8 bytes paired with one converting a string to them). On the Tauri plugins, every `low` pair joined two differently named functions, and one of them joined two different plugins.\n- `matchedBy: code` means the two functions are each other's closest match and the similarity stands out.\n- `matchedBy: name` means the names match once case and underscores are ignored, and the code is similar enough. It catches short ports. Read these pairs, because a same-named function can do something else.\n- The similarity does not see small differences in behavior. Two versions that drifted apart still pair. Only tests and reading the code catch drift.\n\n## Misses\n\njscpd compares functions only: types, constants, enums with data, SQL and UI markup (storyboards, Android layouts) are not in the report, so port and check them yourself. Known gaps in pairing:\n\n- constructors across languages (a Java constructor and Rust's `new`, Kotlin's `constructor`, Swift's `init`) pair only when their code is similar enough;\n- a short function renamed in the port (`add_history` for `_finder_penalty_add_history`);\n- one function split into several, or several merged into one: the report may pair only the closest part and list the rest as unmatched;\n- anonymous functions (callbacks, closures) take no part at all, except JavaScript and TypeScript test cases such as `it('rounds cents', () => …)`, which go by their titles.\n\nA function listed only in the target that you know is a port of a source function is one of these. Do not rename working code only to raise the number, and never add stubs or empty functions with source names: jscpd may pair a stub by name, and the progress would then report work that was not done.\n\n## Options\n\n- `--min-tokens` (30 with `--compare`) and `--min-lines` (5) decide which functions count toward the totals. Smaller functions still pair as partners. Raise them to focus on substantial functions, and keep them fixed across runs so percentages compare.\n- `--ignore` leaves files out, such as vendored dependencies, build output, generated code or fixtures (see [Keep vendored and generated code out](#keep-vendored-and-generated-code-out)); `--pattern` narrows the comparison to some files, such as the tests of one module. Keep the globs fixed across runs.\n- `--format` narrows the walk to some languages, for example when the source mixes the code being ported with build scripts.\n- `--semantic-model` and `--semantic-url` pick another embedding model or an OpenAI-compatible API (`npx jscpd --semantic-models` lists the models with calibrated thresholds). Keep the model fixed across runs, because each model scores on its own scale.\n- The exit code is 0 whatever the progress, so the command does not fail a build on its own.\n\n## Rules\n\n- Keep the two paths, their order, the options and the model the same from run to run, or the numbers stop being comparable.\n- Keep vendored dependencies, installed packages and build output out of both paths: through a `.gitignore` in a git repository, which you suggest to the user, or with `--ignore`.\n- Port the tests before the code, and a function only together with the tests the coverage map binds to it.\n- Treat the report as a map of what to read and what is left. It does not prove the port is correct; tests do.\n- Never game the percentage: no stubs, no renames made only for jscpd, no deleting source functions to shrink the denominator without the user's decision.\n- When the report and your reading of the code disagree, trust the code and tell the user which pair looked wrong.\n","skills/codebase-refactoring/SKILL.md":"---\nname: codebase-refactoring\ndescription: Three-part workflow to improve overall codebase health with jscpd — find and fix duplicated code, find and remove or refactor dead code, then find and simplify the largest/most complex files. Starts from --dashboard/--health to prioritize and ends by re-measuring the score.\n---\n\n# codebase-refactoring\n\nA guided pass over a codebase's three biggest maintenance costs — duplicated\ncode, dead code, and complexity — using jscpd to find each and a strategy to\nfix it. Use this skill when asked to \"clean up\", \"improve\", \"refactor\" or\n\"pay down tech debt in\" a codebase, rather than to fix one specific bug.\n\n## Start here: measure, then prioritize\n\n```bash\nnpx jscpd --health --reporters ai <path>\n```\n\n```\nhealth 74 B (duplication 75, dead-code 72, complexity 76; 93 code lines)\n```\n\nThe health score is a weighted mix of three sub-scores, each a share of the\ncode lines — high is good, 100 is clean. The lowest of the three names where\nthe codebase actually hurts most; if it isn't the order below, do that\ndimension first instead. `n/a` on a dimension (e.g. `dead code n/a`) means\njscpd could not measure it — dead-code detection needs JavaScript,\nTypeScript or Python to be a real share of the code — skip that step rather\nthan treating it as clean.\n\nFor the full picture behind that score — largest duplicated formats, most\ncomplex files, largest dead-code findings, all in one screen — run `npx\njscpd --dashboard` instead. Its `console` output (the default) is the only\nreporter with that per-file detail; `--reporters ai` on `--dashboard` prints\nthe same one-line summary shown above, not the breakdown, so don't reach for\nit expecting more than the score. Use `--reporters json` on `--dashboard` if\nyou need the detail in a parseable form.\n\nThe three passes below are otherwise independent and safe to run in any\norder; **duplication → dead code → complexity** is the default because\nfixing duplication first means step 2 isn't wasting effort tracing call\nsites that a later extract-function pass will move anyway, and clearing\ndead code before step 3 means the files ranked for simplification are ones\nactually worth simplifying, not ones about to be deleted.\n\n## Step 1 — Duplicated code\n\n```bash\nnpx jscpd --reporters ai --summary <path>\n```\n\nFind and read each clone, decide whether it is a real copy worth merging,\nand apply the matching refactoring strategy (extract function, parameterize,\nextract a shared module or constant, or a base class/template for\nstructural duplication). Full guided workflow, triage rules for\nrenamed/near-miss clones, and worked examples per clone kind:\n\n→ **[dry-refactoring](../dry-refactoring/SKILL.md)** — run this now, then\ncome back here for steps 2 and 3. Its own `--summary` hotspot ranking is the\nsame one the health score's duplication dimension reads from, so a file it\nflags as a duplication hotspot is the same file that dragged the score down.\n\n## Step 2 — Dead code\n\n```bash\nnpx jscpd --dead-code --reporters ai <path>\n```\n\n```\nbasta dead code report — 18 findings across 30 files (22.1% of 272 lines)\nConfidence is 0-100; anything below 90 has a listed reason it may be wrong. Verify a finding before deleting the code.\nunused-file typescript/src/legacy-export.ts:1:1 confidence=95 typescript/src/legacy-export.ts is never imported and is not an entry point\nunused-export typescript/src/invoice.ts:21:17 confidence=85 exported function `renderReceipt` is never imported\nunused-symbol typescript/src/invoice.ts:29:10 confidence=90 function `describeTotal` is never used in typescript/src/invoice.ts\nunused-import typescript/src/invoice.ts:2:10 confidence=100 `roundToCents` is imported but never used\n```\n\nThis is a graph traversal from the project's entry points (`package.json`\n`main`/`bin`/`exports`/`scripts`, `pyproject.toml` scripts, framework\nconventions, or `--entry <glob>`), not a reference count — a helper whose\nonly caller is itself dead is reported too, so deleting one finding can make\nanother true that wasn't reported yet. Covers JavaScript, TypeScript, JSX,\nTSX, Vue, Svelte, Astro and Python.\n\n### The four categories, and what to do with each\n\n| Category | Meaning | Usual fix |\n|----------|---------|-----------|\n| `unused-file` | Never imported anywhere, not an entry point | Delete the file |\n| `unused-export` | Exported, but no other file imports it | Delete it, or drop the `export` keyword if something in the same file still uses it |\n| `unused-symbol` | A module-private declaration nothing in its own file references | Delete it |\n| `unused-import` | Imported but never referenced in the file | Delete the import line |\n\n### Workflow\n\n1. Run with `--reporters ai` (add `--dead-code-categories` to focus on one\n   category, `--include-tests` if test-only usage shouldn't count as \"used\",\n   `--include-entry-exports` to also flag an entry file's own unused\n   exports, normally excluded since its whole surface is the public API)\n2. Sort by confidence, high to low. Everything is 0-100; below 90 the finding\n   carries a stated reason it might be wrong — `eval`/`getattr` calls,\n   decorators the analyzer doesn't recognize, a wildcard re-export, the name\n   showing up only inside a string literal, or a file elsewhere that failed\n   to parse (see `statistics.unparsedFiles` in the JSON report). Read the\n   reason before deleting; `--min-confidence` (default 60) only sets the\n   floor for what's reported, it doesn't make a low-confidence finding safe\n3. For each finding above your comfort threshold, confirm it by hand — grep\n   the symbol name across the repo (dynamic access, a string-based router, a\n   template file jscpd doesn't parse can all hide a real use) — then apply\n   the fix from the table above\n4. Run the project's own test suite and build after each batch of deletions,\n   not only jscpd's re-run: dead-code analysis proves nothing *reaches* the\n   code from an entry point, not that removing it can't break a build step\n5. Re-run `--dead-code` after a batch: a file whose only import was the one\n   you just deleted may now be dead itself — that cascade is expected, keep\n   going until a clean pass, not just once\n6. Report what you skipped and why (low confidence, a reason you couldn't\n   rule out, or intentionally-kept dead code such as a public library export)\n   separately from what you actually removed\n\n## Step 3 — Big and complex files\n\n```bash\nnpx jscpd --complexity --reporters ai --summary-top 10 <path>\n# or, ranked alongside duplication and size:\nnpx jscpd --reporters ai --summary --summary-by complexity <path>\n```\n\n```\nComplexity by complexity (6 files, 6 folders):\nfiles (tokens/lines/size/cx):\nc/checkout.c 176/32/710/13\nrust/status.rs 112/24/481/9\nswift/Profile.swift 88/21/446/8\n```\n\n`cx` is a language-aware cyclomatic-complexity estimate — roughly one path\nper function plus one per branch (`if`, loops, `&&`/`||`, `match`/`switch`\narms, the ternary and Elvis operators, each counted the way the language\nactually spells it) — read as a ranking signal, not an exact metric. Prose\nand data files (Markdown, JSON, YAML, lock files, …) always score `0`; a\nlarge README is not a refactoring target. jscpd's own health score treats a\nfile with complexity **50 or higher** as \"complex\" (the threshold its\n`dead code`-style half-life curve is calibrated against) — a reasonable\ndefault cutoff for \"worth reading\" if the summary doesn't make an obvious\none clear.\n\n### Workflow\n\n1. Take the top N files by complexity (and, from the `--summary` form, by\n   size — a huge low-complexity file, e.g. a big data table, is a different\n   problem than a small high-complexity one)\n2. Read each file and name the actual driver: deep nesting, a long function\n   doing several unrelated things, a large `switch`/`if`-chain over a type\n   or state, or a file that is really several modules glued together\n3. Pick the matching strategy:\n   - **Deep nesting** → guard clauses / early return, flatten the happy path\n   - **Long function, several responsibilities** → extract function per\n     responsibility, named for what it does\n   - **Branching over a fixed set of cases** (type, enum, status) →\n     replace with a lookup table/map, or polymorphism if each case already\n     has its own type\n   - **File doing too much** → split along its actual seams (one export\n     group per file), not an arbitrary line-count cut\n4. Refactor one file at a time; keep behavior identical — this is a\n   complexity pass, not a rewrite. If a duplication clone (step 1) or a dead\n   branch (step 2) turns up while reading, still handle it in its own step\n   so the reason for each change stays traceable\n5. Re-run `--complexity` on the touched files to confirm `cx` actually\n   dropped — a split that only moves code around without reducing branching\n   per function didn't fix anything, it just renamed the problem\n\n## Step 4 — Confirm the improvement\n\n```bash\nnpx jscpd --health --reporters ai <path>\n```\n\nCompare the score and grade to the Step 0 measurement. A dimension that got\nworse (duplication crept back in while extracting a shared complexity fix,\nsay) is a signal to revisit that step, not just note it — re-run the\nrelevant pass (`--summary`, `--dead-code`, `--complexity`) rather than only\ntrusting the one aggregate number.\n\n## Tips\n\n- Don't run all three passes on a codebase you've never seen before without\n  reading `--dashboard` first — on a project where only one dimension is\n  actually bad, spending equal effort on all three wastes time on the two\n  that are already fine\n- A `--health-input` file (coverage, security scan results) can be layered\n  onto the same score if the project already tracks those; it doesn't change\n  anything about this workflow, it just adds more dimensions to Step 0\n- Health scores from two runs are only comparable when built from the same\n  dimensions — if a dimension went from `n/a` to measured (or the reverse)\n  because the file mix changed, say so rather than reading the raw score\n  delta as improvement or regression\n- Commit each step separately (duplication fixes, dead-code removal,\n  complexity simplification) — a reviewer verifying \"did this actually\n  reduce X\" wants a diff scoped to X\n","skills/compare-codebases/SKILL.md":"---\nname: compare-codebases\ndescription: Compare two folders function by function with jscpd --compare, in the same language or across languages, and explain the result. Use when asked how two codebases relate, which functions one implementation has and the other lacks, how far a port has come, whether the iOS and Android versions of an app match, or which function in one folder corresponds to a function in the other.\n---\n\n# compare-codebases\n\n`jscpd --compare A B` pairs every function of folder `A` with the function of folder `B` that does the same job, and lists the functions of each folder that have no counterpart. The two folders may be in one language or in two (Python and TypeScript, Kotlin and Swift, Java and Rust). This skill explains how the comparison works and how to run one well. For porting code with the comparison as the progress measure, use the [code-migration](../code-migration/SKILL.md) skill; for the rest of jscpd, the [jscpd](../jscpd/SKILL.md) skill.\n\n## How the comparison works\n\njscpd finds the functions of JavaScript, TypeScript, JSX, TSX, Vue, Svelte, Astro, Python, Rust, Go, Java, Kotlin, C#, C, C++, PHP, Ruby, Scala and Swift files in both folders. It turns the code of each function into a vector with a code embedding model that runs inside jscpd (CodeRankEmbed by default), so two functions that do the same job point the same way even when their languages, names and structure differ. Functions of one folder are never compared with each other.\n\nFunctions pair in two steps:\n\n1. By code. Two functions pair when each is the other's closest match in the other folder, their cosine similarity reaches the model's threshold (0.4125 across languages and 0.6375 within one language, with CodeRankEmbed), and the similarity stands out from the function's other matches. A function close to the best one also pairs when it reaches a higher bar, so a feature written twice on one side gets two pairs. A pair stays only when it is the best match of at least one of its two functions, so a lookalike from another class does not join a function and its counterpart; a tie goes to the file of the same name. Functions shorter than `--min-tokens` (30 with `--compare`) or `--min-lines` (5) stay out of this step, because a short function resembles too many others.\n2. By name. A function left over pairs with a function of the other folder under the same name, once case, underscores, spaces and punctuation are ignored (`encodeBinary`, `encode_binary`, `_encode_binary`; the test title `rounds cents` and `rounds_cents`), when their similarity reaches the `medium` level (0.5625 across languages with CodeRankEmbed). A name pair skips the closest-match and stand-out checks of the first step, so it needs more than that step's threshold; otherwise every `load` and `init` of two codebases would pair. Size does not matter here, so a short port is found. A name pair has to stay within modules the first step linked. A module is the folder right under the deepest folder all files of a side share (`notification` in `android/notification/…`), and a file that sits higher than the rest, such as a build script, does not move that folder up. Two modules link when one holds the most of the other's pairs, counted for code and for tests separately. So `checkPermissions` of one plugin does not pair with its namesake in another.\n\nEach pair gets a level on the scale of the model, because a cosine that is high for one model is low for another:\n\n| Level | With CodeRankEmbed, across languages | Meaning |\n|---|---|---|\n| `high` | 0.7125 and up | almost always the same function |\n| `medium` | 0.5625 to 0.7125 | usually the same function, restructured |\n| `low` | 0.4125 to 0.5625 | read both: related code pairs here too |\n\njscpd measures tests and code apart, in two blocks of the report, and pairs a test only with a test. It tells a test by the conventions of its language:\n\n- a test file such as `*_test.go`, `test_*.py`, `*.test.ts`, `*.spec.js`, `*Tests.swift` or `*_spec.rb`;\n- a test folder such as `tests/`, `__tests__/`, `spec/`, `src/test/` (where Java, Kotlin and Scala keep their tests), `MyAppTests/` or `MyApp.Tests.Integration/`, the compared folder's own name included;\n- a Rust function in a `#[cfg(test)]` module or under `#[test]`;\n- a JavaScript or TypeScript test case such as `it('rounds cents', () => …)`.\n\nWhen neither side has a test that counts (see below), the report has one block and no headings.\n\nTotals count the functions of at least `--min-tokens` tokens and `--min-lines` lines; smaller ones appear only as partners. Declarations without a body (TypeScript overloads, the functions of a `.d.ts` file, interface and abstract methods) take no part. Anonymous functions (callbacks, closures) take no part either, except JavaScript and TypeScript test cases. A test case such as `it('rounds cents', () => …)` goes by its title, and so do those written with `test`, `specify`, `fit`, `xit`, `xtest` or `bench`, with `.only`, `.skip` or `.each(table)` after them. Suites and hooks stay anonymous. jscpd does not compare types, constants, SQL or UI markup.\n\n## A way to compare two folders\n\n### 1. Pick the folders\n\n- Point at the code, not the repositories: `app/src/main/java` and `ios/Sources`, not the two repository roots. Build output, vendored code, installed packages and generated files dilute the result, and a vendored dependency tree can turn a run of seconds into one of tens of minutes, since every function in it is embedded. Inside a git repository jscpd skips what `.gitignore` excludes; outside one, or for folders the `.gitignore` misses, pass them with `--ignore` (`--ignore \"**/vendor/**,**/target/**,**/node_modules/**\"`). When a side has no `.gitignore` entries for such folders, suggest them to the user.\n- Two folders are required, and they must not overlap: `app/` and `app/android/` is refused.\n- Keep parallel structures when you can (`ios/<module>` and `android/<module>`). Modules steer the name step, so matching folder names help.\n- For a port, put the source first and the target second, so the first line of the report is the port's progress. For two implementations that both live on, the order does not matter.\n- One run covers tests and code, since the report measures the tests in a block of their own. When the user asks about the code alone, leave the tests out with `--ignore \"**/__tests__/**,**/*.test.*,**/test/**\"`. When they ask about the tests alone, pass the two test folders as the paths, or pick the test files with `--pattern`.\n\n### 2. Get the model\n\nThe first run needs the model (548 MB). Ask the user before downloading it:\n\n```bash\nnpx jscpd --semantic-download\n```\n\njscpd caches the vectors per pair of folders, so later runs embed only the functions whose code changed.\n\n### 3. Read the overview\n\n```bash\nnpx jscpd --compare billing-py/ billing-ts/\n```\n\n```text\n 71% 5 of 7 functions in billing-py/ have a counterpart in billing-ts/\n 80% 4 of 5 functions in billing-ts/ have a counterpart in billing-py/\n\nbilling-py/\n  file         paired  similarity  counterpart\n  billing.py   4 / 5   0.89        billing.ts\n  shipping.py  1 / 2   0.91        shipping.ts\n\nbilling-ts/\n  file         paired  similarity  counterpart\n  billing.ts   3 / 4   0.89        billing.py\n  shipping.ts  1 / 1   0.91        shipping.py\n\nPaired under other names (1):\n  billing-py/                   billing-ts/             similarity\n  billing.py:28 tax_for_region  billing.ts:27 salesTax  0.87 high\n\nOnly in billing-py/ (2):\n  billing.py (1)\n    46  due_date                6 lines\n  shipping.py (1)\n    18  estimate_delivery_days  8 lines\n\nOnly in billing-ts/ (1):\n  billing.ts (1)\n    39  toCurrency  8 lines\n```\n\n- When both folders hold tests, the report has a `Code` block and a `Tests` block, each with everything below. The two top lines of a block give the share of each folder's functions (or tests) that have a counterpart in the other.\n- The file tables give each file's paired functions, the mean similarity of its pairs (with the number of `low` pairs, as in `0.62, 1 low`), and the file on the other side that holds most of its counterparts.\n- \"Paired under other names\" lists the pairs whose names differ even once case and underscores are ignored. A search by name never finds these.\n- \"Only in\" lists the functions with no counterpart, per folder, grouped by file with the number of each file's functions, then each function's first line, name and length.\n- In colour, levels are green (`high`), yellow (`medium`) and red (`low`), and the shares are green when complete, yellow when partial and red when nothing is paired. Pass `--no-colors` when you parse the console output, or read the JSON report instead.\n- If one folder has no functions, the report is one line of totals and `<folder> has no functions yet`. Check the path and the languages before concluding anything else.\n\n### 4. Check the pairs\n\n```bash\nnpx jscpd --compare billing-py/ billing-ts/ -r console-full\n```\n\n`console-full` adds every pair with its similarity and level, and marks the pairs found by name. Before reporting the comparison as reliable, read a sample:\n\n- two or three `high` pairs, to confirm the pairing works on this code;\n- every `low` pair, since related code pairs there too (a client call and the endpoint it calls, a function counting UTF-8 bytes and one converting a string to them);\n- the pairs marked `by name`, since a same-named function can do something else.\n\nWhen a sample pair is wrong, say which one and why.\n\n### 5. Check the functions with no counterpart\n\nA function in an \"Only in\" list is either missing on the other side, or its counterpart was not recognized. Before calling it missing, search the other folder: in its counterpart file, under other names, as part of a larger function, or replaced by a library or platform call. The known gaps in pairing:\n\n- constructors across languages (a Java constructor and Rust's `new`, Kotlin's `constructor`, Swift's `init`) pair only when their code is similar enough;\n- a short function renamed on the other side (`add_history` for `_finder_penalty_add_history`);\n- one function split into several, or several merged into one: only the closest part may pair.\n\nFunctions that exist on one side only by design are expected: platform glue (an iOS delegate callback, an Android notification channel), helpers a language needs and another does not, features one side dropped.\n\n### 6. Report\n\nFor the user, summarize in a few lines: the two percentages, the notable renamed pairs, the `low` pairs you checked and what you found, and the real gaps on each side, grouped by file or module. For a person who wants to explore the result, write the migration map, `-r html`: one offline page that draws both sides as dependency graphs with the pairs bridging them, and lists the same pairs as a table on a second tab. For a document or an issue, write the Markdown report and attach or paste it:\n\n```bash\nnpx jscpd --compare billing-py/ billing-ts/ -r markdown -o .jscpd-compare\n```\n\nFor your own processing, read the JSON report (`-r json`, written to `jscpd-compare.json`). It has a `code` and a `tests` section of the same shape. Each has `sides[0]` and `sides[1]`, each with `path`, `functions`, `matched`, `percentage`, `files` (`file`, `functions`, `matched`, `counterpart`, `similarity`, `lowPairs`) `unmatched` (`file`, `name`, `start`, `end`) and `readyToPort` (the unmatched functions whose callees all have a counterpart, each with its number of `callers`), and `pairs`, each with `a`, `b`, `similarity`, `level`, `renamed` and `matchedBy`. Paths are relative to each folder. Write reports outside the repository or add the folder to `.gitignore`.\n\n## Options that change the result\n\n- `--min-tokens` and `--min-lines` set which functions count. Raise them to focus on substantial functions.\n- `--ignore` and `--format` narrow the files, for example to leave out tests or build scripts.\n- `--semantic-model` picks another model, and `--semantic-url` an OpenAI-compatible embeddings API; `npx jscpd --semantic-models` lists the models with calibrated thresholds. `--semantic-threshold` and `--semantic-same-threshold` move the thresholds; change them only after reading pairs on both sides of the new value.\n- `--semantic-rebuild-cache` embeds everything again, which is needed only when the cache is suspect.\n\nKeep the folders, their order, the options and the model the same when you compare runs over time, or the numbers stop being comparable. The exit code is 0 whatever the result.\n\n## Limits\n\n- Similarity does not see small differences in behavior: two versions that drifted apart still pair, often at `high`. Only tests and reading the code catch drift.\n- The more one side is restructured, the fewer of its functions pair by code.\n- Only functions are compared.\n- The model runs on the CPU. The first run embedded about 25 functions a second on an Apple M1 (261 functions in 11 seconds) and can be several times slower on a small CI machine; later runs reuse the cache.\n","skills/dry-refactoring/SKILL.md":"---\nname: dry-refactoring\ndescription: Guided workflow to eliminate copy-paste duplication detected by jscpd. Refactor exact, renamed and near-miss clones using extract function, parameterize, module, constant, or base class strategies, starting from the hotspots the summary ranks.\n---\n\n# dry-refactoring\n\nGuided workflow to eliminate copy-paste duplication in source code. Use after running [jscpd](../jscpd/SKILL.md) to detect clones.\n\n## Prerequisites\n\nFirst, run jscpd to identify duplications:\n\n```bash\nnpx jscpd --reporters ai <path>\n```\n\nIn codebases that mix related formats (e.g. JavaScript and TypeScript), add `--cross-formats` so clones spanning both are detected too:\n\n```bash\nnpx jscpd --reporters ai --cross-formats \"js-ts\" <path>\n```\n\nOn larger codebases, add `--summary` to get a refactoring-hotspot overview alongside the clone list — top files and folders with a `dup%` column showing how much of each file is duplicated:\n\n```bash\nnpx jscpd --reporters ai --summary <path>\n```\n\nThe default scan reports only **exact** copies, and those are the ones to refactor first: an exact clone is almost always a real copy-paste. Two more passes find copies that were edited after pasting. They are **noisier**: they ignore names, values or a few statements on purpose, so they also surface blocks that merely look alike (models and DTOs, config tables, test setup, generated code, shared idioms). Run them only after the exact clones are dealt with, one family at a time, with tight settings, and treat what they report as leads to read rather than defects to fix:\n\n```bash\n# Type-2: renamed copies (other variable names, other constants), reported as \"(renamed)\".\n# Raise --min-tokens: with identifiers ignored, a short block is mostly placeholders.\nnpx jscpd --reporters ai --ignore-identifiers --min-tokens 70 <path>\n\n# Type-3: near-miss copies (one or two edited lines, or JS/TS/Python functions with the same structure),\n# reported as \"[~0.91 gap]\" and \"[~0.85 ast]\". Widen only if the tight run finds nothing.\nnpx jscpd --reporters ai --max-gap-lines 1 --similarity <path>\n```\n\nSee the **[jscpd](../jscpd/SKILL.md)** skill for full option reference, including cross-format group syntax, the clone-kind suffixes and how to read the summary.\n\n## Workflow\n\n1. Run jscpd with `--reporters ai` on the target path (add `--summary` on larger codebases to pick a starting point: files with high `dup%` and high token counts pay off most)\n2. Parse each clone line to identify the two duplicated locations (file + line range) and its kind: no suffix is an exact copy, `(renamed)` differs only in names or values, `[~N gap]` has a few edited lines in the middle, `[~N ast]` is a function pair with the same structure\n3. Read both code fragments from the source files\n4. Understand what the duplicated code does, and for renamed and similar clones list exactly what differs between the two sides\n5. **Triage renamed and similar clones before touching them.** Skip the pair, and say so, when any of these holds: the two sides do different things despite the same shape (a `switch` over different enums, two reducers with unrelated semantics); the sameness is intentional boilerplate (models, DTOs, config, route tables, test fixtures); the code is generated; a shared abstraction would need a vague name like `processData`; or the pair is under about 10 lines. Only a pair that would let you delete code and give the extraction a precise name goes on to the next step\n6. Design a refactoring: extract a shared function, class, module, or constant; the kind decides the strategy (below)\n7. Apply the refactoring — update both locations and all other usages\n8. Re-run jscpd **with the same flags** to confirm the clone is eliminated and the `dup%` of the touched files went down; a clone that was `(renamed)` will not show in a default run, so check with `--ignore-identifiers` again\n9. Repeat for remaining clones, highest-impact first: exact clones, then renamed, then similar. Report the skipped candidates separately from the refactored ones, with the reason, so nobody mistakes a normalized run's count for real duplication\n\n## Refactoring Strategies\n\n**Extract function** — when the duplicate is a block of logic:\n```ts\n// Before: same block in two places\n// After: shared function called from both places\n```\n\n**Extract module/utility** — when the duplicate spans multiple files in different domains:\n```ts\n// Move shared logic to a shared utility file and import it\n```\n\n**Extract constant or config** — when the duplicate is repeated data or configuration.\n\n**Template/base class** — when the duplicate is structural (e.g., repeated class shape).\n\n**Parameterize** — for `(renamed)` clones. The two sides are the same algorithm over different names or values, so the things that differ become parameters:\n```ts\n// Before: computeCartTotal(items) and computeBasketTotal(entries), same body, other names;\n//         limits-dev.js and limits-prod.js, same shape, other numbers\n// After: one function whose parameters are the identifiers that differed,\n//        or one function reading the values that differed from a config object\n```\nA renamed clone whose only difference is a literal is a missing constant or config entry, not a missing function.\n\n**Unify near-miss copies** — for `[~N gap]` clones. Read the unmatched lines: the gap is the one place the copies diverged, typically a guard, a log call or an extra field. Extract the common body and pass the divergence in:\n```ts\n// Before: saveUser and saveAccount, identical except one inserted validation line\n// After: one saveRecord(record, { validate }) with the inserted line behind the option,\n//        or the inserted line moved to the caller before the shared call\n```\nIf the gap changes the meaning rather than adding a step, keep two functions but extract the shared halves.\n\n**Merge similar functions** — for `[~N ast]` clones. The structure matches but names, literals and some statements do not. Diff the two functions first; the `ast` score tells how much is shared (`0.9` is a copy with one edit, `0.75` a copy with a couple of added statements plus renames). Extract the shared skeleton and inject what differs, as arguments, a strategy object, or a callback:\n```ts\n// Before: buildInvoice(order, customer, taxRate) and buildCreditNote(refund, account, vatRate):\n//         same loop, same rounding, one extra guard and one extra log call in the second\n// After: buildDocument(source, party, rate, { filter, onBuilt }) used by both\n```\nBelow about `0.8` the pair usually shares an idiom, not an implementation; leave those alone unless the summary shows the file is a hotspot anyway.\n\nAlways ensure:\n- All call sites are updated, not just the two reported by jscpd\n- Tests still pass after refactoring\n- The extracted abstraction has a clear, descriptive name\n- The re-run uses the same detection flags as the run that found the clone\n\n## Tips\n\n- Start with clones that have the highest line count — they have the most impact\n- Use the summary's `dup%` column to order the work: a large file with a high share of duplicated lines pays back first\n- A clone between test files may indicate a missing test helper\n- Clones across unrelated modules may signal a missing shared utility\n- A cross-format clone (same logic in a `.js` and a `.ts` file, found with `--cross-formats`) often means code was ported without deleting the original — consolidate into one implementation (usually the TypeScript one) and update imports, rather than extracting a third shared copy\n- Many `(renamed)` clones in one file usually mean one abstraction is missing, not many: look for the shared shape before extracting pair by pair. Many `(renamed)` clones across *test* files usually mean nothing: test cases are supposed to look alike\n- Do not gate CI (`--threshold`, `--fail-on-new-clones`) on the Type-2/Type-3 passes until the team has reviewed what they report on this codebase; gate on the exact run\n- `--similarity` covers JavaScript, TypeScript, Python, Java, Kotlin, Scala, C#, Go, Rust, C, C++, PHP, Ruby, Swift and Clojure, also inside Markdown code blocks and Vue, Svelte and Astro scripts; for other languages rely on the exact and `--max-gap-lines` passes\n- Use `--min-lines 10` to filter noise and focus on meaningful duplications\n- Keep a separate `--baseline` per set of detection flags when gating CI: renamed and similar runs fingerprint clones differently from exact runs\n","skills/jscpd/SKILL.md":"---\nname: jscpd\ndescription: Copy-paste detector for 220+ languages. Detect exact, renamed and near-miss duplicated code, measure duplication percentages, and find refactoring hotspots with the codebase summary.\n---\n\n# jscpd\n\nCopy-paste detector for programming source code, supports 220+ languages. Use this skill to run jscpd and understand its output.\n\n## Quick Start\n\n```bash\n# Run with ai reporter (compact output optimized for agents)\nnpx jscpd --reporters ai <path>\n\n# With ignore patterns\nnpx jscpd --reporters ai --ignore \"**/node_modules/**,**/dist/**\" <path>\n\n# Scope to specific formats\nnpx jscpd --reporters ai --format \"javascript,typescript\" <path>\n\n# Second pass, noisier: copies that only differ in names and values (Type-2, \"renamed\").\n# Review each hit before acting on it.\nnpx jscpd --reporters ai --ignore-identifiers --min-tokens 70 <path>\n\n# Third pass, noisier still: copies with a few edited lines or the same function\n# structure (Type-3, \"similar\"). Keep the settings tight.\nnpx jscpd --reporters ai --max-gap-lines 1 --similarity <path>\n\n# Where to refactor first: clone list plus a hotspot summary\nnpx jscpd --reporters ai --summary <path>\n\n# Most complex files only, without clone detection\nnpx jscpd --complexity --reporters ai <path>\n```\n\n## AI Reporter Output Format\n\nThe `ai` reporter produces compact, token-efficient output designed for agent consumption:\n\n```\nClones:\nsrc/ foo.ts:10-25 ~ bar.ts:42-57\nsrc/utils/helpers.ts:100-120 ~ src/utils/other.ts:5-25\nsrc/cart/ basket.js:1-9 ~ cart.js:1-9 (renamed)\nsrc/api/ save-account.js:1-12 ~ save-user.js:1-11 [~0.91 gap]\nsrc/billing/ orders.ts:1-14 ~ refunds.ts:1-15 [~0.85 ast]\n---\n5 clones · 4.2% duplication\n```\n\nEach line represents one clone pair:\n- **Same file**: `path/file.ts 10-25 ~ 45-60` (shared path shown once)\n- **Same directory**: `shared/prefix/ file-a.ts:10-25 ~ file-b.ts:42-57` (common prefix factored out)\n- **Different paths**: `path/a.ts:10-25 ~ path/b.ts:42-57`\n\nA suffix tells the **kind** of clone; no suffix means an exact copy:\n- `(renamed)`: the two blocks differ only in identifier names, literal values or annotations (Type-2). Only appears with `--ignore-identifiers`, `--ignore-literals` or `--ignore-annotations`.\n- `[~0.91 gap]`: two exact clones merged across up to `--max-gap-lines` unmatched lines (Type-3). The number is matched tokens over the merged span.\n- `[~0.85 ast]`: two functions whose normalized syntax trees share at least `--similarity` of their subtrees (Type-3). The number is that share. The names of called functions and methods and the operators count, local names and literal values do not, so a renamed copy with other constants scores 1 and a copy that calls other methods scores lower. A name still has to stand for one name on the other side: a copy that swaps the roles of two names scores lower.\n\n## Options\n\n| Option | Description |\n|--------|-------------|\n| `--reporters ai` | Use the AI-optimized reporter (compact clone list for agents) |\n| `--reporters html` | Generate HTML report |\n| `--reporters json` | Output JSON report |\n| `--min-tokens N` | Minimum tokens to consider a duplication (default: 50) |\n| `--min-lines N` | Minimum lines to consider a duplication (default: 5) |\n| `--threshold N` | Exit with error if duplication % exceeds N |\n| `--ignore \"glob\"` | Ignore patterns (comma-separated) |\n| `--format \"list\"` | Limit to specific languages (e.g. `typescript,javascript`) |\n| `--cross-formats \"groups\"` | Detect clones across related formats (e.g. `javascript,typescript` or the `js-ts` preset) |\n| `--ignore-identifiers` | Treat all identifiers as equal, so blocks that differ only in variable, function or type names match (Type-2, reported as `renamed`); a name must stand for one name on the other side, so look-alikes with another data flow stay out, while a field and a local of one text are two names, so `this.url = url` still matches `this.url = link` (`--ignore-identifiers=blind` matches any names) |\n| `--ignore-literals` | Treat all string literals as equal and all numeric literals as equal (Type-2) |\n| `--ignore-annotations` | Drop `@Name` / `@Name(...)` annotations and decorators before matching, in languages where `@` means one (Type-2) |\n| `--max-gap-lines N` | Merge clones of one file pair separated by at most N unmatched lines into one `similar` clone (Type-3, default: 0 = off) |\n| `--similarity [RATIO]` | Report pairs of functions whose normalized syntax trees share at least RATIO of their subtrees, in `(0, 1]`, as `similar` clones (Type-3, 0.8 without a value). JavaScript, TypeScript, Python, Java, Kotlin, Scala, C#, Go, Rust, C, C++, PHP, Ruby, Swift and Clojure; test files are left out |\n| `--min-nodes N` | The fewest nodes of a normalized syntax tree a function needs for `--similarity` (default: 20) |\n| `--summary` | Append a codebase summary: top files/folders by tokens, lines, size, complexity, with duplication share |\n| `--summary-top N` | Number of entries in each summary top list (default: 10) |\n| `--summary-by metric` | Summary ranking metric: `tokens`, `lines`, `size`, `complexity` (default: `tokens`) |\n| `--kind list` | Report only these clone kinds: `exact`, `renamed`, `similar`, `gap`, `ast` (never enables a detector) |\n| `--complexity` | Complexity tables only, without clone detection (reporters: console, ai, json) |\n| `--dead-code` | Find unused files, exports, module-private declarations and imports — JavaScript, TypeScript, JSX, TSX, Vue, Svelte, Astro, Python. Each finding carries a 0-100 confidence score |\n| `--dead-code-categories list` | Limit `--dead-code`/`--dashboard` to categories: `unused-file`, `unused-export`, `unused-symbol`, `unused-import` |\n| `--min-confidence N` | Floor for `--dead-code`/`--dashboard` findings, 0-100 (default: 60) |\n| `--entry \"glob\"` | Extra entry points for dead-code analysis, beyond what's inferred from `package.json`/`pyproject.toml`/framework conventions |\n| `--include-tests` | Count test-only usage as \"used\" for dead-code analysis (excluded by default) |\n| `--include-entry-exports` | Also flag an entry file's own unused exports (excluded by default: its exports are the public API) |\n| `--dashboard` | One screen: health badge, project size and largest code files, duplication, complexity, dead code (JS/TS/Python); `-r json` writes `jscpd-dashboard.json` |\n| `--health` | Project health badge only: 0-100 score and grade from duplication, dead code and complexity (`-r ai` one line, `-r json`, `-r badge`) |\n| `--health-input FILE` | Add metrics from other tools (coverage, tests, security) to the health score |\n| `--pattern \"glob\"` | Glob pattern to select files |\n| `--no-gitignore` | Do not respect `.gitignore` (it is respected by default) |\n| `--output \"path\"` | Directory to write reports to |\n| `--silent` | Suppress console output (useful with file reporters and `--output`) |\n| `--list` | List all supported formats and exit |\n| `--no-tips` | Disable tips in output (skipped automatically when stdout is not a TTY or `CI` or `JSCPD_NO_TIPS` is set) |\n| `--config \"path\"` | Path to .jscpd.json config file |\n\n## Clone Kinds: Exact, Renamed, Similar\n\nBy default jscpd reports **exact** clones only: the token sequences are identical (whitespace, layout and, depending on the mode, comments do not count). Two opt-in families widen the net. Run them as separate passes after the default scan, because they find more and longer clones and change what a \"clone\" means.\n\n**These passes are noisy by design.** An exact clone is almost always a real copy. A renamed or similar clone is a *candidate*: the flags deliberately ignore the very things (names, values, a statement or two) that often make two blocks different in meaning. Expect false positives from:\n\n- boilerplate that is supposed to look alike: DTOs and models, config tables, enum-like maps, route or handler registrations, builders\n- test files: `describe`/`it` blocks, fixtures and setup code repeat the same shape on purpose\n- generated code, migrations, serializers, protocol bindings\n- language idioms: two `reduce` loops or two `switch` statements that share structure but not logic\n- small blocks: with `--ignore-identifiers` a 50-token block is mostly placeholders, so raise `--min-tokens`\n\nRules that keep the noise manageable:\n\n- Run them **after** the exact clones are handled, one family at a time, so every hit is attributable to one flag.\n- Start conservative: `--ignore-identifiers` alone (add `--ignore-literals` only when constants are the known problem), `--max-gap-lines 1` or `2`, `--similarity 0.85` or higher, and `--min-tokens 70` or more for the identifier pass. Widen only when the tight run comes back empty.\n- Treat every `(renamed)` or `[~…]` line as a lead to read, not a defect to fix. Do not gate CI (`--threshold`, `--fail-on-new-clones`) on these passes unless the team has reviewed what they report on the codebase.\n- Never claim \"N duplicates found\" from a normalized run without saying which flags produced them.\n\n### Type-2: renamed clones (`--ignore-identifiers`, `--ignore-literals`, `--ignore-annotations`)\n\nCopies where someone renamed the variables or changed the constants:\n\n```bash\nnpx jscpd --reporters ai --ignore-identifiers <path>                      # function a(x) {…} matches function b(y) {…}\nnpx jscpd --reporters ai --ignore-identifiers --ignore-literals <path>    # …and 10 matches 25, 'dev' matches 'prod'\nnpx jscpd --reporters ai --ignore-annotations <path>                      # @Override / @Deprecated no longer split a clone\n```\n\n```\nClones:\nbasket.js:1-9 ~ cart.js:1-9 (renamed)\nlimits-dev.js:1-13 ~ limits-prod.js:1-13 (renamed)\n---\n```\n\n- Keywords keep their meaning (`return` never matches `retry`), so structure still has to match.\n- `--ignore-annotations` only acts in Java, Kotlin, Scala, Groovy, Python, Dart, Swift, JavaScript and TypeScript; in Ruby, Perl, T-SQL, Razor and CSS `@` means something else and is left alone.\n- Positions in the report still point at the original source.\n- Config keys: `ignoreIdentifiers`, `ignoreLiterals`, `ignoreAnnotations`.\n\n### Type-3: near-miss clones (`--max-gap-lines`, `--similarity`)\n\nCopies with a few edited lines, or functions rewritten with the same shape:\n\n```bash\nnpx jscpd --reporters ai --max-gap-lines 2 <path>      # a copy with up to 2 inserted/changed lines becomes one clone\nnpx jscpd --reporters ai --similarity <path>           # functions whose normalized syntax trees share ≥82% of their subtrees\n```\n\n```\nClones:\nsave-account.js:1-12 ~ save-user.js:1-11 [~0.91 gap]\norders.ts:1-14 ~ refunds.ts:1-15 [~0.85 ast]\n---\n```\n\n- `--max-gap-lines N` only joins clones the exact run already found, so it removes fragmentation rather than inventing matches; it works in every language. A merge is refused when the gap holds more tokens than the halves share (similarity would drop under `0.5`).\n- `--similarity` normalizes the syntax tree of every function (local names and literals become markers, each name standing for one name on the other side; called names and operators stay) and scores the share of subtrees two trees have in common, so a renamed copy scores `1.0`, one inserted statement in a function of ten about `0.85`, a copy that calls other methods less. It applies to JavaScript, TypeScript, Python, Java, Kotlin, Scala, C#, Go, Rust, C, C++, PHP, Ruby, Swift and Clojure, and to their code blocks in Markdown and in Vue, Svelte and Astro files; other formats are a silent no-op. A function nested in another is part of it, and test files are left out. Start at the default `0.8` and lower to `0.7` only when looking for leads; read both functions before believing a low score.\n- `similar` takes precedence over `renamed` when both apply (a merged clone is no longer identical even after normalization).\n- Config keys: `maxGapLines`, `similarity`, `minNodes`.\n\n### Where the kind shows up\n\n- `console`: `Clone found (javascript, renamed)`, `Clone found (javascript, similar (gap) ~0.91)`, `Clone found (javascript, similar (ast) ~0.75)`.\n- `json`: `\"kind\": \"exact\" | \"renamed\" | \"similar\"`, plus `\"similarity\"` and `\"method\": \"gap\" | \"ast\"` for similar clones, and the size of each normalized tree as `\"nodes\"` in `firstFile` and `secondFile` for ast ones.\n- `sarif`: rules `jscpd/duplicate-code`, `jscpd/renamed-code`, `jscpd/similar-code` (clones merged across a gap), `jscpd/similar-function` (functions and classes paired by `--similarity`) and `jscpd/semantic-code` (`--semantic`); Code Climate uses the same `check_name` values.\n- A default run reports only `exact` clones and its output is unchanged by these features.\n- Normalized runs produce different clone fingerprints than exact runs: keep a separate `--baseline` file per configuration.\n\n## Codebase Summary (`--summary`)\n\n`--summary` appends a refactoring-hotspot overview to the run output — use it to decide **where to refactor first** before diving into individual clones:\n\n```bash\n# Compact clone list + compact summary, optimized for agents\nnpx jscpd --reporters ai --summary --no-tips <path>\n\n# Rank by complexity instead of tokens, top 5 lists\nnpx jscpd --reporters ai --summary --summary-by complexity --summary-top 5 <path>\n```\n\nWith the `ai` reporter the summary is one line per entry:\n\n```\nSummary by tokens (321 files, 129 folders):\nfiles (tokens/lines/size/cx/dup%):\nsrc/files.ts 2052/363/11662/80/0.0%\nlong-line/theme-branded.js 498/15/2.5K/10/93.3%\n...\nfolders (files/tokens/lines/size):\nsrc/core 8/5264/843/27136\n...\n```\n\nHow to read it:\n- **Top files** are ranked by the `--summary-by` metric, but every row carries all metrics — `tokens/lines/size/cx/dup%`.\n- **cx** is a language-agnostic cyclomatic-complexity estimate from the token stream (1 + decision-point tokens like `if`/`while`/`&&`); treat it as a ranking signal, not an exact metric.\n- **dup%** is the share of the file's lines covered by detected clones — a large file with high `dup%` is the best refactoring target. It reflects the clones of the current run, so with the Type-2/Type-3 flags it rises accordingly.\n- **Folders** aggregate files into their direct parent directory (no cumulative ancestor totals).\n- In `console` reporters the summary renders as aligned tables; in the `json` report it appears as an additive `summary` key (absent when the flag is off).\n\nConfig file equivalents: `\"summary\": true`, `\"summaryTop\": 10`, `\"summaryBy\": \"tokens\"`.\n\n## Cross-Format Clone Detection\n\nBy default each format is compared only against itself. `--cross-formats` defines groups of related formats that share one comparison pool, so a block duplicated between a `.js` and a `.ts` file is reported as a clone:\n\n```bash\n# One group: compare JavaScript and TypeScript files together\nnpx jscpd --reporters ai --cross-formats \"javascript,typescript\" <path>\n\n# Preset covering javascript, jsx, typescript, tsx\nnpx jscpd --reporters ai --cross-formats \"js-ts\" <path>\n\n# Multiple groups are separated by \";\"\nnpx jscpd --reporters ai --cross-formats \"javascript,typescript;css,scss\" <path>\n```\n\nNotes:\n- 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)`. Reported positions still reference the original source.\n- Groups need at least two formats; groups sharing a format are merged into one pool.\n- In per-format statistics, a cross-format clone is attributed to one member format of the group.\n- In config files the key is `crossFormats` (or `cross-formats`) and accepts a string (`\"javascript,typescript;css,scss\"`), an array of strings (`[\"javascript,typescript\", \"css,scss\"]`), or an array of arrays (`[[\"javascript\",\"typescript\"],[\"css\",\"scss\"]]`).\n\n## Comparing Two Codebases (`--compare`)\n\n`--compare SOURCE TARGET` pairs the functions of two folders with the `--semantic` embedding model and lists which functions of each side have a counterpart in the other: the progress of a port to another language or framework, or the parity of two implementations of one app. It takes exactly two paths that do not overlap, needs the model once (`--semantic-download`), and runs no clone detection.\n\n```bash\nnpx jscpd --compare python-lib/ rust-lib/                                  # progress of a port and what is left\nnpx jscpd --compare ios/ android/ -r console-full                          # parity, with every pair and its similarity\nnpx jscpd --compare python-lib/ rust-lib/ -r json -o .jscpd-compare --silent  # jscpd-compare.json for an agent\n```\n\nFunctions pair by code first (each is the other's closest match), then by name when the names match once case and underscores are ignored and the code is similar enough. Every pair has its similarity and a level (`high`, `medium`, `low`) on the scale of the model, and the default report lists the pairs under other names on their own. jscpd measures tests and code in two blocks and pairs a test only with a test. `--min-tokens` defaults to 30 here. Reporters: `console`, `console-full`, `json`, `markdown`. See the **[compare-codebases](../compare-codebases/SKILL.md)** skill for how the comparison works and how to check it, and **[code-migration](../code-migration/SKILL.md)** for porting code with it.\n\n## Configuration File\n\nCreate a `.jscpd.json` in your project root:\n\n```json\n{\n  \"threshold\": 0,\n  \"reporters\": [\"ai\"],\n  \"ignore\": [\"**/node_modules/**\", \"**/dist/**\", \"**/*.min.*\"],\n  \"format\": [\"typescript\", \"javascript\"],\n  \"minLines\": 5,\n  \"minTokens\": 50,\n  \"ignoreIdentifiers\": false,\n  \"ignoreLiterals\": false,\n  \"maxGapLines\": 0,\n  \"similarity\": 1,\n  \"summary\": false,\n  \"output\": \"./reports/jscpd\"\n}\n```\n\n## Refactoring Duplicated Code\n\nOnce you've detected clones, use the **dry-refactoring** skill for a guided workflow to eliminate them, with a strategy per clone kind:\n\n→ **dry-refactoring** — step-by-step refactoring strategies and workflow for removing duplication. Install with:\n  ```bash\n  npx skills add https://github.com/kucherenko/jscpd --skill dry-refactoring\n  ```\n\n## Improving Overall Codebase Health\n\nFor a broader \"clean up this codebase\" pass — duplication, then dead code, then the largest/most complex files, prioritized from `--health`/`--dashboard` and re-measured at the end — use **codebase-refactoring**:\n\n  ```bash\n  npx skills add https://github.com/kucherenko/jscpd --skill codebase-refactoring\n  ```\n"},"items":[{"name":"SKILL.md","path":"skills/code-migration/SKILL.md","rawUrl":"https://raw.githubusercontent.com/kucherenko/jscpd/HEAD/skills/code-migration/SKILL.md","title":"code-migration","category":"anthropic-skill","format":"markdown","content":"---\nname: code-migration\ndescription: Move code from one implementation to another function by function, tests first and code second, and check two implementations of one app for parity, with jscpd --compare as the progress measure and a coverage map binding each function to its tests. Use when porting a library or app to another language or framework (Java to Kotlin, JavaScript to Rust, a Python library to TypeScript, an iOS app to Android), when asked what is left to port, or when comparing the Android and iOS versions of an app.\n---\n\n# code-migration\n\n`jscpd --compare SOURCE TARGET` pairs every function of one folder with the function of the other folder that does the same job, in any pair of languages, and lists the functions that have no counterpart. This skill uses it to measure a port: what is ported, what is left, and whether the port you just wrote was recognized.\n\nA port runs in two phases: the tests first, then the code they check. A coverage report of the source's tests tells which tests exercise which function, so each function is ported together with the tests that prove it works. See [Plan: tests first, then code](#plan-tests-first-then-code).\n\nTwo words are used throughout:\n\n- **source**: the implementation you port from, such as the iOS app, the Python library, or the JavaScript package;\n- **target**: the implementation you port to, such as the Android app, the Rust crate, or the TypeScript rewrite.\n\nNeither has to be old or new. The source may stay in production and keep changing, and the target may already hold features the source lacks. For two implementations that both live on (`ios/` and `android/`), the same report shows parity; see [Parity](#parity-between-two-implementations).\n\njscpd finds functions in JavaScript, TypeScript, JSX, TSX, Vue, Svelte, Astro, Python, Rust, Go, Java, Kotlin, C#, C, C++, PHP, Ruby, Scala and Swift. The [compare-codebases](../compare-codebases/SKILL.md) skill explains how the comparison pairs functions and how to check its result; the [jscpd](../jscpd/SKILL.md) skill covers the rest of the tool.\n\n## Setup\n\n`--compare` pairs functions with a code embedding model that runs inside jscpd. Download it once (548 MB, CodeRankEmbed). Ask the user before you start the download:\n\n```bash\nnpx jscpd --semantic-download\n```\n\njscpd caches the vectors, so a repeat run embeds only the functions whose code changed and takes seconds.\n\nPick the two paths and keep them fixed for the whole port: the source first, the target second. Two paths are required, and they must not overlap (`app/` and `app/android/` is refused). The target may be empty at the start.\n\nOne run measures both phases: the report has a `Code` block and a `Tests` block (a `code` and a `tests` section in JSON), and a test pairs only with a test. jscpd tells a test by the conventions of its language: test files such as `*_test.go`, `test_*.py`, `*.test.ts` or `*Test.java`, folders such as `tests/`, `__tests__/` or `src/test/`, Rust tests in `#[cfg(test)]` modules, and JavaScript test cases such as `it('rounds cents', () => …)`. Phase 1 reads the `Tests` block, phase 2 the `Code` block.\n\n### Keep vendored and generated code out\n\n`--compare` counts and embeds every function in both paths. Vendored dependencies (`vendor/` from `cargo vendor` or `go mod vendor`, `third_party/`), installed packages (`node_modules/`, `.venv/`), build output (`target/`, `build/`, `dist/`) and generated code are not part of the port, yet a vendored crate tree alone holds thousands of functions. With them in a path, the first run embeds all of them and takes tens of minutes instead of seconds, and the percentages describe the dependencies instead of the port.\n\njscpd skips what `.gitignore` excludes, but only inside a git repository. Before the first run:\n\n1. Check both paths for such folders. A target you create for the port gets them as soon as you build it or vendor its dependencies, so check again after the first build.\n2. Suggest a `.gitignore` to the user that lists the folders the target's language and tools produce, plus the report folder. For a Rust addon built with napi-rs:\n\n   ```gitignore\n   /target/\n   /vendor/\n   node_modules/\n   *.node\n   .jscpd-compare/\n   ```\n\n   If the target is not in a git repository, tell the user that jscpd reads the `.gitignore` only after `git init`.\n3. Until the `.gitignore` works, pass the same folders with `--ignore`, and keep the globs the same for every run:\n\n   ```bash\n   npx jscpd --compare node-lib/ rust-lib/ --ignore \"**/vendor/**,**/target/**,**/node_modules/**\"\n   ```\n\nThe totals line shows when something slipped through. Suspect vendored or generated code in a path when the target has far more functions than the source, or when a run embeds hundreds of functions after a small change.\n\n## Measure\n\nRun the console report for yourself and for the user. Here a Python billing library is being ported to TypeScript:\n\n```bash\nnpx jscpd --compare billing-py/ billing-ts/\n```\n\n```text\n 71% 5 of 7 functions in billing-py/ have a counterpart in billing-ts/\n 80% 4 of 5 functions in billing-ts/ have a counterpart in billing-py/\n\nbilling-py/\n  file         paired  similarity  counterpart\n  billing.py   4 / 5   0.89        billing.ts\n  shipping.py  1 / 2   0.91        shipping.ts\n\nbilling-ts/\n  file         paired  similarity  counterpart\n  billing.ts   3 / 4   0.89        billing.py\n  shipping.ts  1 / 1   0.91        shipping.py\n\nPaired under other names (1):\n  billing-py/                   billing-ts/             similarity\n  billing.py:28 tax_for_region  billing.ts:27 salesTax  0.87 high\n\nOnly in billing-py/ (2):\n  billing.py (1)\n    46  due_date                6 lines\n  shipping.py (1)\n    18  estimate_delivery_days  8 lines\n\nOnly in billing-ts/ (1):\n  billing.ts (1)\n    39  toCurrency  8 lines\n```\n\n- The first line is the port's progress: the share of the source's functions that have a counterpart in the target. \"Only in\" the source is the work left.\n- The second line and \"Only in\" the target describe the target's own code: helpers the port needed, features the source never had, or a port jscpd did not recognize (see [Misses](#misses)).\n- Each file row gives its paired functions, the mean similarity of their pairs (with the number of `low` pairs, if any), and the counterpart file: the file on the other side that holds most of its counterparts. Use the counterpart to decide where a missing function goes.\n- \"Paired under other names\" lists the pairs whose names differ even once case and underscores are ignored: renamed ports, constructors, platform names. Each has its similarity and level (see [Reading pairs](#reading-pairs)). These are ported already, even though a search by name would not find them.\n- With an empty target the report is one line of totals plus `billing-ts/ has no functions yet`.\n\nFor work you plan and track, read the JSON report instead of the console:\n\n```bash\nnpx jscpd --compare billing-py/ billing-ts/ -r json -o .jscpd-compare --silent\n```\n\njscpd measures tests and code apart and pairs a test only with a test, so `.jscpd-compare/jscpd-compare.json` has a `code` and a `tests` section of the same shape. Each section has `sides[0]` (the source) and `sides[1]` (the target), each with `path`, `functions`, `matched`, `percentage`, `files` (`file`, `functions`, `matched`, `counterpart`, `similarity`, `lowPairs`) `unmatched` (`file`, `name`, `start`, `end`) and `readyToPort` (the unmatched functions whose callees all have a counterpart, with their number of `callers`, most called first), and `pairs`, each with `a` (source), `b` (target), `similarity`, `level` (`high`, `medium`, `low`), `renamed` (the names differ) and `matchedBy` (`code` or `name`). Paths are relative to each side's folder. Add `.jscpd-compare/` to `.gitignore` or write the report outside the repository.\n\n`-r console-full` also prints every pair with its similarity, `-r markdown` writes `jscpd-compare.md`, a table you can paste into a PR description or a tracking issue, and `-r html` writes `jscpd-compare.html`, a migration map the user can open to see the two dependency graphs side by side, or the same pairs as a table.\n\n## Plan: tests first, then code\n\nPort the tests before the code. Ported tests define, before any target code exists, what it means for each function to be ported: when the function lands, its tests pass or show what still differs. A port written before its tests can only be checked against your reading of the source.\n\n### Bind tests to functions with coverage\n\nA test's name does not say which functions it runs: a test of `checkout` also runs `apply_discount` and `tax_for_region`. Coverage does. Before porting anything, build a map from each source function to the tests that exercise it.\n\n1. Get a coverage report of the source's tests that says which test ran which lines: recorded per test, or per test file when the project's tooling has no per-test mode.\n2. Take the source's functions and their lines from the `code` section of the JSON report. Every counted function is either in the source's `unmatched` list or the `a` side of a pair, each with `file`, `name`, `start` and `end`. Functions under `--min-tokens` or `--min-lines` are not listed there; take those from the coverage report's own function list, which most formats have.\n3. A test covers a function when it runs a line from the function's `start` to its `end`. Write the map both ways, function to tests and test to functions, to a file next to the report (for example `.jscpd-compare/test-map.json`), and rebuild it when the source's tests change.\n\nUse the map for three things:\n\n- which tests to port with a function, and which functions a ported test needs before it can pass;\n- the source functions no test covers. Before porting such a function, write a test for it on the source side that records what it does today (a characterization test), and port that test with the others. A port with no test has nothing to check it;\n- the order of work: tests that need few functions come first, since they turn green soonest.\n\nCoverage says which code a test runs, not what it checks. A function that a test only passes through on the way to another is weakly bound to that test; prefer the tests whose names or assertions are about the function.\n\n### Phase 1: port the tests\n\n1. Take the source's unmatched tests from the `tests` section of the JSON report. A JavaScript or TypeScript test case written as a callback, `it('rounds cents', () => …)`, goes by its title, so it pairs with `test_rounds_cents` in pytest or `rounds_cents` in Rust like any named test.\n2. Port the tests file by file, into the target's test layout, against the API the target will have. Keep inputs and expected values exactly as they are. Where the target must behave differently (a platform limit, a language's number types), write the difference into the test with a comment and tell the user.\n3. A ported test calls functions that do not exist yet, so it fails. In a compiled language it stops the whole test build. Keep such tests out of the build until their functions land, for example a module declaration left commented out, a cfg feature or an excluded source set, and turn them on one by one in phase 2. Do not write empty functions to make the tests compile: a stub under a source name can pair by name and count as ported.\n4. Run the target's tests. The ported tests that pass already cover what the target has; the failing or disabled ones, read against the test map, are the work list for phase 2.\n\n### Phase 2: port the code\n\nPort the code one function at a time, with its tests already in place.\n\n1. Run the JSON report and take the source's `unmatched` list from its `code` section.\n2. Order the work. Start with functions whose tests are ported, and port a function after the functions it calls, since a caller ported before its helpers has nothing to call. The source's `readyToPort` list is that order's front: the functions whose callees all have a counterpart already, most called first. Take the next one from there, and rerun the report after each batch, since every port can make its callers ready. Within that order, finish one file before starting the next, so each target file fills up in one go.\n3. Before writing a port, make sure the target does not have it already. Pairs with `renamed: true` are ported functions under other names; they are not in `unmatched`, but check them when the user asks where a function went. Then search the target for an equivalent that jscpd missed: in the counterpart file from `files`, under other names (constructors, merged functions, a platform or library call that replaces the helper). If one exists, do not port the function again. Note the equivalent and move on.\n4. Write the port in the counterpart file, or in the file the target's layout puts it in. Keep the name recognizable in the target language's convention (`encodeBinary` becomes `encode_binary` in Rust or Python), because jscpd pairs short functions by name. Follow the style of pairs that are already done in the same file. `console-full` lists them.\n5. Turn on the function's ported tests, as the test map lists them, and run them. When they fail, fix the port, not the test, unless the user agrees that the target should behave differently. A pair in the report says the two functions look alike; the tests say whether they behave alike.\n6. Run the target's coverage for those tests and check that they run the new function. A ported test that never reaches it is bound to something else on the target (a helper, a mock, an old path), and it proves nothing about the port.\n7. Run the comparison again with the same paths and options. Check that the function has left `unmatched`, that its pair joins the right function in the target, and its level. A `high` pair is done. A `low` pair to your port usually means the port differs a lot from the source in structure; read both and make sure the behavior matches. A pair to a different function means the port is not recognized, or it resembles the wrong thing; read both before going on.\n8. Report progress to the user with both measures, as the reports print them: tests ported and passing, and functions ported (`functions 73% → 76%, 3 ported: …; tests 41 of 44 ported, 38 passing`), with the functions you decided not to port and why.\n\nRepeat until the source's `unmatched` list holds only functions you decided not to port. Typical reasons: dead code (check with `npx jscpd --dead-code` on the source where it supports the language), code the target platform or its libraries provide (a JSON parser, a retry helper), platform glue with no equivalent on the target (an iOS delegate callback, an Android notification channel), and features the user decided not to carry over. List these in a notes file or the PR, one line each, so the remaining percentage is explained.\n\nWhen the source keeps changing during the port, a function that was paired can come back as unmatched after a rewrite, and new source functions join the list. Rerun the report before each batch of work instead of reusing an old list.\n\n## Parity between two implementations\n\nWhen both implementations live on, as the iOS and the Android app do, `npx jscpd --compare ios/ android/` shows parity. Read both \"Only in\" lists:\n\n- a function on one side only is either platform code (Android notification channels, iOS delegate callbacks) or a feature the other side lacks. Report each missing feature to the user before implementing it;\n- to add a missing feature, treat the side that has it as the source for that feature: bind its tests with coverage, port the tests, then the code, as in [Plan: tests first, then code](#plan-tests-first-then-code).\n\njscpd pairs functions within modules that already match. A module is the folder right under the deepest folder each side's files share, so keep the two trees parallel (`ios/<plugin>` and `android/<plugin>`) for the best pairing.\n\n## Reading pairs\n\n- `level` rates the similarity on the scale of the model in use, so it means the same with every model. `high` (0.7125 and up with the default CodeRankEmbed, across languages) is almost always the same function. `medium` is usually the same function, restructured. `low` needs reading both functions, since related code pairs there too (a function counting UTF-8 bytes paired with one converting a string to them). On the Tauri plugins, every `low` pair joined two differently named functions, and one of them joined two different plugins.\n- `matchedBy: code` means the two functions are each other's closest match and the similarity stands out.\n- `matchedBy: name` means the names match once case and underscores are ignored, and the code is similar enough. It catches short ports. Read these pairs, because a same-named function can do something else.\n- The similarity does not see small differences in behavior. Two versions that drifted apart still pair. Only tests and reading the code catch drift.\n\n## Misses\n\njscpd compares functions only: types, constants, enums with data, SQL and UI markup (storyboards, Android layouts) are not in the report, so port and check them yourself. Known gaps in pairing:\n\n- constructors across languages (a Java constructor and Rust's `new`, Kotlin's `constructor`, Swift's `init`) pair only when their code is similar enough;\n- a short function renamed in the port (`add_history` for `_finder_penalty_add_history`);\n- one function split into several, or several merged into one: the report may pair only the closest part and list the rest as unmatched;\n- anonymous functions (callbacks, closures) take no part at all, except JavaScript and TypeScript test cases such as `it('rounds cents', () => …)`, which go by their titles.\n\nA function listed only in the target that you know is a port of a source function is one of these. Do not rename working code only to raise the number, and never add stubs or empty functions with source names: jscpd may pair a stub by name, and the progress would then report work that was not done.\n\n## Options\n\n- `--min-tokens` (30 with `--compare`) and `--min-lines` (5) decide which functions count toward the totals. Smaller functions still pair as partners. Raise them to focus on substantial functions, and keep them fixed across runs so percentages compare.\n- `--ignore` leaves files out, such as vendored dependencies, build output, generated code or fixtures (see [Keep vendored and generated code out](#keep-vendored-and-generated-code-out)); `--pattern` narrows the comparison to some files, such as the tests of one module. Keep the globs fixed across runs.\n- `--format` narrows the walk to some languages, for example when the source mixes the code being ported with build scripts.\n- `--semantic-model` and `--semantic-url` pick another embedding model or an OpenAI-compatible API (`npx jscpd --semantic-models` lists the models with calibrated thresholds). Keep the model fixed across runs, because each model scores on its own scale.\n- The exit code is 0 whatever the progress, so the command does not fail a build on its own.\n\n## Rules\n\n- Keep the two paths, their order, the options and the model the same from run to run, or the numbers stop being comparable.\n- Keep vendored dependencies, installed packages and build output out of both paths: through a `.gitignore` in a git repository, which you suggest to the user, or with `--ignore`.\n- Port the tests before the code, and a function only together with the tests the coverage map binds to it.\n- Treat the report as a map of what to read and what is left. It does not prove the port is correct; tests do.\n- Never game the percentage: no stubs, no renames made only for jscpd, no deleting source functions to shrink the denominator without the user's decision.\n- When the report and your reading of the code disagree, trust the code and tell the user which pair looked wrong.\n","frontmatter":{"name":"code-migration","description":"Move code from one implementation to another function by function, tests first and code second, and check two implementations of one app for parity, with jscpd --compare as the progress measure and a coverage map binding each function to its tests. Use when porting a library or app to another language or framework (Java to Kotlin, JavaScript to Rust, a Python library to TypeScript, an iOS app to Android), when asked what is left to port, or when comparing the Android and iOS versions of an app."},"isInternal":false,"tokens":4675,"sizeBytes":19895},{"name":"SKILL.md","path":"skills/codebase-refactoring/SKILL.md","rawUrl":"https://raw.githubusercontent.com/kucherenko/jscpd/HEAD/skills/codebase-refactoring/SKILL.md","title":"codebase-refactoring","category":"anthropic-skill","format":"markdown","content":"---\nname: codebase-refactoring\ndescription: Three-part workflow to improve overall codebase health with jscpd — find and fix duplicated code, find and remove or refactor dead code, then find and simplify the largest/most complex files. Starts from --dashboard/--health to prioritize and ends by re-measuring the score.\n---\n\n# codebase-refactoring\n\nA guided pass over a codebase's three biggest maintenance costs — duplicated\ncode, dead code, and complexity — using jscpd to find each and a strategy to\nfix it. Use this skill when asked to \"clean up\", \"improve\", \"refactor\" or\n\"pay down tech debt in\" a codebase, rather than to fix one specific bug.\n\n## Start here: measure, then prioritize\n\n```bash\nnpx jscpd --health --reporters ai <path>\n```\n\n```\nhealth 74 B (duplication 75, dead-code 72, complexity 76; 93 code lines)\n```\n\nThe health score is a weighted mix of three sub-scores, each a share of the\ncode lines — high is good, 100 is clean. The lowest of the three names where\nthe codebase actually hurts most; if it isn't the order below, do that\ndimension first instead. `n/a` on a dimension (e.g. `dead code n/a`) means\njscpd could not measure it — dead-code detection needs JavaScript,\nTypeScript or Python to be a real share of the code — skip that step rather\nthan treating it as clean.\n\nFor the full picture behind that score — largest duplicated formats, most\ncomplex files, largest dead-code findings, all in one screen — run `npx\njscpd --dashboard` instead. Its `console` output (the default) is the only\nreporter with that per-file detail; `--reporters ai` on `--dashboard` prints\nthe same one-line summary shown above, not the breakdown, so don't reach for\nit expecting more than the score. Use `--reporters json` on `--dashboard` if\nyou need the detail in a parseable form.\n\nThe three passes below are otherwise independent and safe to run in any\norder; **duplication → dead code → complexity** is the default because\nfixing duplication first means step 2 isn't wasting effort tracing call\nsites that a later extract-function pass will move anyway, and clearing\ndead code before step 3 means the files ranked for simplification are ones\nactually worth simplifying, not ones about to be deleted.\n\n## Step 1 — Duplicated code\n\n```bash\nnpx jscpd --reporters ai --summary <path>\n```\n\nFind and read each clone, decide whether it is a real copy worth merging,\nand apply the matching refactoring strategy (extract function, parameterize,\nextract a shared module or constant, or a base class/template for\nstructural duplication). Full guided workflow, triage rules for\nrenamed/near-miss clones, and worked examples per clone kind:\n\n→ **[dry-refactoring](../dry-refactoring/SKILL.md)** — run this now, then\ncome back here for steps 2 and 3. Its own `--summary` hotspot ranking is the\nsame one the health score's duplication dimension reads from, so a file it\nflags as a duplication hotspot is the same file that dragged the score down.\n\n## Step 2 — Dead code\n\n```bash\nnpx jscpd --dead-code --reporters ai <path>\n```\n\n```\nbasta dead code report — 18 findings across 30 files (22.1% of 272 lines)\nConfidence is 0-100; anything below 90 has a listed reason it may be wrong. Verify a finding before deleting the code.\nunused-file typescript/src/legacy-export.ts:1:1 confidence=95 typescript/src/legacy-export.ts is never imported and is not an entry point\nunused-export typescript/src/invoice.ts:21:17 confidence=85 exported function `renderReceipt` is never imported\nunused-symbol typescript/src/invoice.ts:29:10 confidence=90 function `describeTotal` is never used in typescript/src/invoice.ts\nunused-import typescript/src/invoice.ts:2:10 confidence=100 `roundToCents` is imported but never used\n```\n\nThis is a graph traversal from the project's entry points (`package.json`\n`main`/`bin`/`exports`/`scripts`, `pyproject.toml` scripts, framework\nconventions, or `--entry <glob>`), not a reference count — a helper whose\nonly caller is itself dead is reported too, so deleting one finding can make\nanother true that wasn't reported yet. Covers JavaScript, TypeScript, JSX,\nTSX, Vue, Svelte, Astro and Python.\n\n### The four categories, and what to do with each\n\n| Category | Meaning | Usual fix |\n|----------|---------|-----------|\n| `unused-file` | Never imported anywhere, not an entry point | Delete the file |\n| `unused-export` | Exported, but no other file imports it | Delete it, or drop the `export` keyword if something in the same file still uses it |\n| `unused-symbol` | A module-private declaration nothing in its own file references | Delete it |\n| `unused-import` | Imported but never referenced in the file | Delete the import line |\n\n### Workflow\n\n1. Run with `--reporters ai` (add `--dead-code-categories` to focus on one\n   category, `--include-tests` if test-only usage shouldn't count as \"used\",\n   `--include-entry-exports` to also flag an entry file's own unused\n   exports, normally excluded since its whole surface is the public API)\n2. Sort by confidence, high to low. Everything is 0-100; below 90 the finding\n   carries a stated reason it might be wrong — `eval`/`getattr` calls,\n   decorators the analyzer doesn't recognize, a wildcard re-export, the name\n   showing up only inside a string literal, or a file elsewhere that failed\n   to parse (see `statistics.unparsedFiles` in the JSON report). Read the\n   reason before deleting; `--min-confidence` (default 60) only sets the\n   floor for what's reported, it doesn't make a low-confidence finding safe\n3. For each finding above your comfort threshold, confirm it by hand — grep\n   the symbol name across the repo (dynamic access, a string-based router, a\n   template file jscpd doesn't parse can all hide a real use) — then apply\n   the fix from the table above\n4. Run the project's own test suite and build after each batch of deletions,\n   not only jscpd's re-run: dead-code analysis proves nothing *reaches* the\n   code from an entry point, not that removing it can't break a build step\n5. Re-run `--dead-code` after a batch: a file whose only import was the one\n   you just deleted may now be dead itself — that cascade is expected, keep\n   going until a clean pass, not just once\n6. Report what you skipped and why (low confidence, a reason you couldn't\n   rule out, or intentionally-kept dead code such as a public library export)\n   separately from what you actually removed\n\n## Step 3 — Big and complex files\n\n```bash\nnpx jscpd --complexity --reporters ai --summary-top 10 <path>\n# or, ranked alongside duplication and size:\nnpx jscpd --reporters ai --summary --summary-by complexity <path>\n```\n\n```\nComplexity by complexity (6 files, 6 folders):\nfiles (tokens/lines/size/cx):\nc/checkout.c 176/32/710/13\nrust/status.rs 112/24/481/9\nswift/Profile.swift 88/21/446/8\n```\n\n`cx` is a language-aware cyclomatic-complexity estimate — roughly one path\nper function plus one per branch (`if`, loops, `&&`/`||`, `match`/`switch`\narms, the ternary and Elvis operators, each counted the way the language\nactually spells it) — read as a ranking signal, not an exact metric. Prose\nand data files (Markdown, JSON, YAML, lock files, …) always score `0`; a\nlarge README is not a refactoring target. jscpd's own health score treats a\nfile with complexity **50 or higher** as \"complex\" (the threshold its\n`dead code`-style half-life curve is calibrated against) — a reasonable\ndefault cutoff for \"worth reading\" if the summary doesn't make an obvious\none clear.\n\n### Workflow\n\n1. Take the top N files by complexity (and, from the `--summary` form, by\n   size — a huge low-complexity file, e.g. a big data table, is a different\n   problem than a small high-complexity one)\n2. Read each file and name the actual driver: deep nesting, a long function\n   doing several unrelated things, a large `switch`/`if`-chain over a type\n   or state, or a file that is really several modules glued together\n3. Pick the matching strategy:\n   - **Deep nesting** → guard clauses / early return, flatten the happy path\n   - **Long function, several responsibilities** → extract function per\n     responsibility, named for what it does\n   - **Branching over a fixed set of cases** (type, enum, status) →\n     replace with a lookup table/map, or polymorphism if each case already\n     has its own type\n   - **File doing too much** → split along its actual seams (one export\n     group per file), not an arbitrary line-count cut\n4. Refactor one file at a time; keep behavior identical — this is a\n   complexity pass, not a rewrite. If a duplication clone (step 1) or a dead\n   branch (step 2) turns up while reading, still handle it in its own step\n   so the reason for each change stays traceable\n5. Re-run `--complexity` on the touched files to confirm `cx` actually\n   dropped — a split that only moves code around without reducing branching\n   per function didn't fix anything, it just renamed the problem\n\n## Step 4 — Confirm the improvement\n\n```bash\nnpx jscpd --health --reporters ai <path>\n```\n\nCompare the score and grade to the Step 0 measurement. A dimension that got\nworse (duplication crept back in while extracting a shared complexity fix,\nsay) is a signal to revisit that step, not just note it — re-run the\nrelevant pass (`--summary`, `--dead-code`, `--complexity`) rather than only\ntrusting the one aggregate number.\n\n## Tips\n\n- Don't run all three passes on a codebase you've never seen before without\n  reading `--dashboard` first — on a project where only one dimension is\n  actually bad, spending equal effort on all three wastes time on the two\n  that are already fine\n- A `--health-input` file (coverage, security scan results) can be layered\n  onto the same score if the project already tracks those; it doesn't change\n  anything about this workflow, it just adds more dimensions to Step 0\n- Health scores from two runs are only comparable when built from the same\n  dimensions — if a dimension went from `n/a` to measured (or the reverse)\n  because the file mix changed, say so rather than reading the raw score\n  delta as improvement or regression\n- Commit each step separately (duplication fixes, dead-code removal,\n  complexity simplification) — a reviewer verifying \"did this actually\n  reduce X\" wants a diff scoped to X\n","frontmatter":{"name":"codebase-refactoring","description":"Three-part workflow to improve overall codebase health with jscpd — find and fix duplicated code, find and remove or refactor dead code, then find and simplify the largest/most complex files. Starts from --dashboard/--health to prioritize and ends by re-measuring the score."},"isInternal":false,"tokens":2516,"sizeBytes":10273},{"name":"SKILL.md","path":"skills/compare-codebases/SKILL.md","rawUrl":"https://raw.githubusercontent.com/kucherenko/jscpd/HEAD/skills/compare-codebases/SKILL.md","title":"compare-codebases","category":"anthropic-skill","format":"markdown","content":"---\nname: compare-codebases\ndescription: Compare two folders function by function with jscpd --compare, in the same language or across languages, and explain the result. Use when asked how two codebases relate, which functions one implementation has and the other lacks, how far a port has come, whether the iOS and Android versions of an app match, or which function in one folder corresponds to a function in the other.\n---\n\n# compare-codebases\n\n`jscpd --compare A B` pairs every function of folder `A` with the function of folder `B` that does the same job, and lists the functions of each folder that have no counterpart. The two folders may be in one language or in two (Python and TypeScript, Kotlin and Swift, Java and Rust). This skill explains how the comparison works and how to run one well. For porting code with the comparison as the progress measure, use the [code-migration](../code-migration/SKILL.md) skill; for the rest of jscpd, the [jscpd](../jscpd/SKILL.md) skill.\n\n## How the comparison works\n\njscpd finds the functions of JavaScript, TypeScript, JSX, TSX, Vue, Svelte, Astro, Python, Rust, Go, Java, Kotlin, C#, C, C++, PHP, Ruby, Scala and Swift files in both folders. It turns the code of each function into a vector with a code embedding model that runs inside jscpd (CodeRankEmbed by default), so two functions that do the same job point the same way even when their languages, names and structure differ. Functions of one folder are never compared with each other.\n\nFunctions pair in two steps:\n\n1. By code. Two functions pair when each is the other's closest match in the other folder, their cosine similarity reaches the model's threshold (0.4125 across languages and 0.6375 within one language, with CodeRankEmbed), and the similarity stands out from the function's other matches. A function close to the best one also pairs when it reaches a higher bar, so a feature written twice on one side gets two pairs. A pair stays only when it is the best match of at least one of its two functions, so a lookalike from another class does not join a function and its counterpart; a tie goes to the file of the same name. Functions shorter than `--min-tokens` (30 with `--compare`) or `--min-lines` (5) stay out of this step, because a short function resembles too many others.\n2. By name. A function left over pairs with a function of the other folder under the same name, once case, underscores, spaces and punctuation are ignored (`encodeBinary`, `encode_binary`, `_encode_binary`; the test title `rounds cents` and `rounds_cents`), when their similarity reaches the `medium` level (0.5625 across languages with CodeRankEmbed). A name pair skips the closest-match and stand-out checks of the first step, so it needs more than that step's threshold; otherwise every `load` and `init` of two codebases would pair. Size does not matter here, so a short port is found. A name pair has to stay within modules the first step linked. A module is the folder right under the deepest folder all files of a side share (`notification` in `android/notification/…`), and a file that sits higher than the rest, such as a build script, does not move that folder up. Two modules link when one holds the most of the other's pairs, counted for code and for tests separately. So `checkPermissions` of one plugin does not pair with its namesake in another.\n\nEach pair gets a level on the scale of the model, because a cosine that is high for one model is low for another:\n\n| Level | With CodeRankEmbed, across languages | Meaning |\n|---|---|---|\n| `high` | 0.7125 and up | almost always the same function |\n| `medium` | 0.5625 to 0.7125 | usually the same function, restructured |\n| `low` | 0.4125 to 0.5625 | read both: related code pairs here too |\n\njscpd measures tests and code apart, in two blocks of the report, and pairs a test only with a test. It tells a test by the conventions of its language:\n\n- a test file such as `*_test.go`, `test_*.py`, `*.test.ts`, `*.spec.js`, `*Tests.swift` or `*_spec.rb`;\n- a test folder such as `tests/`, `__tests__/`, `spec/`, `src/test/` (where Java, Kotlin and Scala keep their tests), `MyAppTests/` or `MyApp.Tests.Integration/`, the compared folder's own name included;\n- a Rust function in a `#[cfg(test)]` module or under `#[test]`;\n- a JavaScript or TypeScript test case such as `it('rounds cents', () => …)`.\n\nWhen neither side has a test that counts (see below), the report has one block and no headings.\n\nTotals count the functions of at least `--min-tokens` tokens and `--min-lines` lines; smaller ones appear only as partners. Declarations without a body (TypeScript overloads, the functions of a `.d.ts` file, interface and abstract methods) take no part. Anonymous functions (callbacks, closures) take no part either, except JavaScript and TypeScript test cases. A test case such as `it('rounds cents', () => …)` goes by its title, and so do those written with `test`, `specify`, `fit`, `xit`, `xtest` or `bench`, with `.only`, `.skip` or `.each(table)` after them. Suites and hooks stay anonymous. jscpd does not compare types, constants, SQL or UI markup.\n\n## A way to compare two folders\n\n### 1. Pick the folders\n\n- Point at the code, not the repositories: `app/src/main/java` and `ios/Sources`, not the two repository roots. Build output, vendored code, installed packages and generated files dilute the result, and a vendored dependency tree can turn a run of seconds into one of tens of minutes, since every function in it is embedded. Inside a git repository jscpd skips what `.gitignore` excludes; outside one, or for folders the `.gitignore` misses, pass them with `--ignore` (`--ignore \"**/vendor/**,**/target/**,**/node_modules/**\"`). When a side has no `.gitignore` entries for such folders, suggest them to the user.\n- Two folders are required, and they must not overlap: `app/` and `app/android/` is refused.\n- Keep parallel structures when you can (`ios/<module>` and `android/<module>`). Modules steer the name step, so matching folder names help.\n- For a port, put the source first and the target second, so the first line of the report is the port's progress. For two implementations that both live on, the order does not matter.\n- One run covers tests and code, since the report measures the tests in a block of their own. When the user asks about the code alone, leave the tests out with `--ignore \"**/__tests__/**,**/*.test.*,**/test/**\"`. When they ask about the tests alone, pass the two test folders as the paths, or pick the test files with `--pattern`.\n\n### 2. Get the model\n\nThe first run needs the model (548 MB). Ask the user before downloading it:\n\n```bash\nnpx jscpd --semantic-download\n```\n\njscpd caches the vectors per pair of folders, so later runs embed only the functions whose code changed.\n\n### 3. Read the overview\n\n```bash\nnpx jscpd --compare billing-py/ billing-ts/\n```\n\n```text\n 71% 5 of 7 functions in billing-py/ have a counterpart in billing-ts/\n 80% 4 of 5 functions in billing-ts/ have a counterpart in billing-py/\n\nbilling-py/\n  file         paired  similarity  counterpart\n  billing.py   4 / 5   0.89        billing.ts\n  shipping.py  1 / 2   0.91        shipping.ts\n\nbilling-ts/\n  file         paired  similarity  counterpart\n  billing.ts   3 / 4   0.89        billing.py\n  shipping.ts  1 / 1   0.91        shipping.py\n\nPaired under other names (1):\n  billing-py/                   billing-ts/             similarity\n  billing.py:28 tax_for_region  billing.ts:27 salesTax  0.87 high\n\nOnly in billing-py/ (2):\n  billing.py (1)\n    46  due_date                6 lines\n  shipping.py (1)\n    18  estimate_delivery_days  8 lines\n\nOnly in billing-ts/ (1):\n  billing.ts (1)\n    39  toCurrency  8 lines\n```\n\n- When both folders hold tests, the report has a `Code` block and a `Tests` block, each with everything below. The two top lines of a block give the share of each folder's functions (or tests) that have a counterpart in the other.\n- The file tables give each file's paired functions, the mean similarity of its pairs (with the number of `low` pairs, as in `0.62, 1 low`), and the file on the other side that holds most of its counterparts.\n- \"Paired under other names\" lists the pairs whose names differ even once case and underscores are ignored. A search by name never finds these.\n- \"Only in\" lists the functions with no counterpart, per folder, grouped by file with the number of each file's functions, then each function's first line, name and length.\n- In colour, levels are green (`high`), yellow (`medium`) and red (`low`), and the shares are green when complete, yellow when partial and red when nothing is paired. Pass `--no-colors` when you parse the console output, or read the JSON report instead.\n- If one folder has no functions, the report is one line of totals and `<folder> has no functions yet`. Check the path and the languages before concluding anything else.\n\n### 4. Check the pairs\n\n```bash\nnpx jscpd --compare billing-py/ billing-ts/ -r console-full\n```\n\n`console-full` adds every pair with its similarity and level, and marks the pairs found by name. Before reporting the comparison as reliable, read a sample:\n\n- two or three `high` pairs, to confirm the pairing works on this code;\n- every `low` pair, since related code pairs there too (a client call and the endpoint it calls, a function counting UTF-8 bytes and one converting a string to them);\n- the pairs marked `by name`, since a same-named function can do something else.\n\nWhen a sample pair is wrong, say which one and why.\n\n### 5. Check the functions with no counterpart\n\nA function in an \"Only in\" list is either missing on the other side, or its counterpart was not recognized. Before calling it missing, search the other folder: in its counterpart file, under other names, as part of a larger function, or replaced by a library or platform call. The known gaps in pairing:\n\n- constructors across languages (a Java constructor and Rust's `new`, Kotlin's `constructor`, Swift's `init`) pair only when their code is similar enough;\n- a short function renamed on the other side (`add_history` for `_finder_penalty_add_history`);\n- one function split into several, or several merged into one: only the closest part may pair.\n\nFunctions that exist on one side only by design are expected: platform glue (an iOS delegate callback, an Android notification channel), helpers a language needs and another does not, features one side dropped.\n\n### 6. Report\n\nFor the user, summarize in a few lines: the two percentages, the notable renamed pairs, the `low` pairs you checked and what you found, and the real gaps on each side, grouped by file or module. For a person who wants to explore the result, write the migration map, `-r html`: one offline page that draws both sides as dependency graphs with the pairs bridging them, and lists the same pairs as a table on a second tab. For a document or an issue, write the Markdown report and attach or paste it:\n\n```bash\nnpx jscpd --compare billing-py/ billing-ts/ -r markdown -o .jscpd-compare\n```\n\nFor your own processing, read the JSON report (`-r json`, written to `jscpd-compare.json`). It has a `code` and a `tests` section of the same shape. Each has `sides[0]` and `sides[1]`, each with `path`, `functions`, `matched`, `percentage`, `files` (`file`, `functions`, `matched`, `counterpart`, `similarity`, `lowPairs`) `unmatched` (`file`, `name`, `start`, `end`) and `readyToPort` (the unmatched functions whose callees all have a counterpart, each with its number of `callers`), and `pairs`, each with `a`, `b`, `similarity`, `level`, `renamed` and `matchedBy`. Paths are relative to each folder. Write reports outside the repository or add the folder to `.gitignore`.\n\n## Options that change the result\n\n- `--min-tokens` and `--min-lines` set which functions count. Raise them to focus on substantial functions.\n- `--ignore` and `--format` narrow the files, for example to leave out tests or build scripts.\n- `--semantic-model` picks another model, and `--semantic-url` an OpenAI-compatible embeddings API; `npx jscpd --semantic-models` lists the models with calibrated thresholds. `--semantic-threshold` and `--semantic-same-threshold` move the thresholds; change them only after reading pairs on both sides of the new value.\n- `--semantic-rebuild-cache` embeds everything again, which is needed only when the cache is suspect.\n\nKeep the folders, their order, the options and the model the same when you compare runs over time, or the numbers stop being comparable. The exit code is 0 whatever the result.\n\n## Limits\n\n- Similarity does not see small differences in behavior: two versions that drifted apart still pair, often at `high`. Only tests and reading the code catch drift.\n- The more one side is restructured, the fewer of its functions pair by code.\n- Only functions are compared.\n- The model runs on the CPU. The first run embedded about 25 functions a second on an Apple M1 (261 functions in 11 seconds) and can be several times slower on a small CI machine; later runs reuse the cache.\n","frontmatter":{"name":"compare-codebases","description":"Compare two folders function by function with jscpd --compare, in the same language or across languages, and explain the result. Use when asked how two codebases relate, which functions one implementation has and the other lacks, how far a port has come, whether the iOS and Android versions of an app match, or which function in one folder corresponds to a function in the other."},"isInternal":false,"tokens":3162,"sizeBytes":13043},{"name":"SKILL.md","path":"skills/dry-refactoring/SKILL.md","rawUrl":"https://raw.githubusercontent.com/kucherenko/jscpd/HEAD/skills/dry-refactoring/SKILL.md","title":"dry-refactoring","category":"anthropic-skill","format":"markdown","content":"---\nname: dry-refactoring\ndescription: Guided workflow to eliminate copy-paste duplication detected by jscpd. Refactor exact, renamed and near-miss clones using extract function, parameterize, module, constant, or base class strategies, starting from the hotspots the summary ranks.\n---\n\n# dry-refactoring\n\nGuided workflow to eliminate copy-paste duplication in source code. Use after running [jscpd](../jscpd/SKILL.md) to detect clones.\n\n## Prerequisites\n\nFirst, run jscpd to identify duplications:\n\n```bash\nnpx jscpd --reporters ai <path>\n```\n\nIn codebases that mix related formats (e.g. JavaScript and TypeScript), add `--cross-formats` so clones spanning both are detected too:\n\n```bash\nnpx jscpd --reporters ai --cross-formats \"js-ts\" <path>\n```\n\nOn larger codebases, add `--summary` to get a refactoring-hotspot overview alongside the clone list — top files and folders with a `dup%` column showing how much of each file is duplicated:\n\n```bash\nnpx jscpd --reporters ai --summary <path>\n```\n\nThe default scan reports only **exact** copies, and those are the ones to refactor first: an exact clone is almost always a real copy-paste. Two more passes find copies that were edited after pasting. They are **noisier**: they ignore names, values or a few statements on purpose, so they also surface blocks that merely look alike (models and DTOs, config tables, test setup, generated code, shared idioms). Run them only after the exact clones are dealt with, one family at a time, with tight settings, and treat what they report as leads to read rather than defects to fix:\n\n```bash\n# Type-2: renamed copies (other variable names, other constants), reported as \"(renamed)\".\n# Raise --min-tokens: with identifiers ignored, a short block is mostly placeholders.\nnpx jscpd --reporters ai --ignore-identifiers --min-tokens 70 <path>\n\n# Type-3: near-miss copies (one or two edited lines, or JS/TS/Python functions with the same structure),\n# reported as \"[~0.91 gap]\" and \"[~0.85 ast]\". Widen only if the tight run finds nothing.\nnpx jscpd --reporters ai --max-gap-lines 1 --similarity <path>\n```\n\nSee the **[jscpd](../jscpd/SKILL.md)** skill for full option reference, including cross-format group syntax, the clone-kind suffixes and how to read the summary.\n\n## Workflow\n\n1. Run jscpd with `--reporters ai` on the target path (add `--summary` on larger codebases to pick a starting point: files with high `dup%` and high token counts pay off most)\n2. Parse each clone line to identify the two duplicated locations (file + line range) and its kind: no suffix is an exact copy, `(renamed)` differs only in names or values, `[~N gap]` has a few edited lines in the middle, `[~N ast]` is a function pair with the same structure\n3. Read both code fragments from the source files\n4. Understand what the duplicated code does, and for renamed and similar clones list exactly what differs between the two sides\n5. **Triage renamed and similar clones before touching them.** Skip the pair, and say so, when any of these holds: the two sides do different things despite the same shape (a `switch` over different enums, two reducers with unrelated semantics); the sameness is intentional boilerplate (models, DTOs, config, route tables, test fixtures); the code is generated; a shared abstraction would need a vague name like `processData`; or the pair is under about 10 lines. Only a pair that would let you delete code and give the extraction a precise name goes on to the next step\n6. Design a refactoring: extract a shared function, class, module, or constant; the kind decides the strategy (below)\n7. Apply the refactoring — update both locations and all other usages\n8. Re-run jscpd **with the same flags** to confirm the clone is eliminated and the `dup%` of the touched files went down; a clone that was `(renamed)` will not show in a default run, so check with `--ignore-identifiers` again\n9. Repeat for remaining clones, highest-impact first: exact clones, then renamed, then similar. Report the skipped candidates separately from the refactored ones, with the reason, so nobody mistakes a normalized run's count for real duplication\n\n## Refactoring Strategies\n\n**Extract function** — when the duplicate is a block of logic:\n```ts\n// Before: same block in two places\n// After: shared function called from both places\n```\n\n**Extract module/utility** — when the duplicate spans multiple files in different domains:\n```ts\n// Move shared logic to a shared utility file and import it\n```\n\n**Extract constant or config** — when the duplicate is repeated data or configuration.\n\n**Template/base class** — when the duplicate is structural (e.g., repeated class shape).\n\n**Parameterize** — for `(renamed)` clones. The two sides are the same algorithm over different names or values, so the things that differ become parameters:\n```ts\n// Before: computeCartTotal(items) and computeBasketTotal(entries), same body, other names;\n//         limits-dev.js and limits-prod.js, same shape, other numbers\n// After: one function whose parameters are the identifiers that differed,\n//        or one function reading the values that differed from a config object\n```\nA renamed clone whose only difference is a literal is a missing constant or config entry, not a missing function.\n\n**Unify near-miss copies** — for `[~N gap]` clones. Read the unmatched lines: the gap is the one place the copies diverged, typically a guard, a log call or an extra field. Extract the common body and pass the divergence in:\n```ts\n// Before: saveUser and saveAccount, identical except one inserted validation line\n// After: one saveRecord(record, { validate }) with the inserted line behind the option,\n//        or the inserted line moved to the caller before the shared call\n```\nIf the gap changes the meaning rather than adding a step, keep two functions but extract the shared halves.\n\n**Merge similar functions** — for `[~N ast]` clones. The structure matches but names, literals and some statements do not. Diff the two functions first; the `ast` score tells how much is shared (`0.9` is a copy with one edit, `0.75` a copy with a couple of added statements plus renames). Extract the shared skeleton and inject what differs, as arguments, a strategy object, or a callback:\n```ts\n// Before: buildInvoice(order, customer, taxRate) and buildCreditNote(refund, account, vatRate):\n//         same loop, same rounding, one extra guard and one extra log call in the second\n// After: buildDocument(source, party, rate, { filter, onBuilt }) used by both\n```\nBelow about `0.8` the pair usually shares an idiom, not an implementation; leave those alone unless the summary shows the file is a hotspot anyway.\n\nAlways ensure:\n- All call sites are updated, not just the two reported by jscpd\n- Tests still pass after refactoring\n- The extracted abstraction has a clear, descriptive name\n- The re-run uses the same detection flags as the run that found the clone\n\n## Tips\n\n- Start with clones that have the highest line count — they have the most impact\n- Use the summary's `dup%` column to order the work: a large file with a high share of duplicated lines pays back first\n- A clone between test files may indicate a missing test helper\n- Clones across unrelated modules may signal a missing shared utility\n- A cross-format clone (same logic in a `.js` and a `.ts` file, found with `--cross-formats`) often means code was ported without deleting the original — consolidate into one implementation (usually the TypeScript one) and update imports, rather than extracting a third shared copy\n- Many `(renamed)` clones in one file usually mean one abstraction is missing, not many: look for the shared shape before extracting pair by pair. Many `(renamed)` clones across *test* files usually mean nothing: test cases are supposed to look alike\n- Do not gate CI (`--threshold`, `--fail-on-new-clones`) on the Type-2/Type-3 passes until the team has reviewed what they report on this codebase; gate on the exact run\n- `--similarity` covers JavaScript, TypeScript, Python, Java, Kotlin, Scala, C#, Go, Rust, C, C++, PHP, Ruby, Swift and Clojure, also inside Markdown code blocks and Vue, Svelte and Astro scripts; for other languages rely on the exact and `--max-gap-lines` passes\n- Use `--min-lines 10` to filter noise and focus on meaningful duplications\n- Keep a separate `--baseline` per set of detection flags when gating CI: renamed and similar runs fingerprint clones differently from exact runs\n","frontmatter":{"name":"dry-refactoring","description":"Guided workflow to eliminate copy-paste duplication detected by jscpd. Refactor exact, renamed and near-miss clones using extract function, parameterize, module, constant, or base class strategies, starting from the hotspots the summary ranks."},"isInternal":false,"tokens":1962,"sizeBytes":8469},{"name":"SKILL.md","path":"skills/jscpd/SKILL.md","rawUrl":"https://raw.githubusercontent.com/kucherenko/jscpd/HEAD/skills/jscpd/SKILL.md","title":"jscpd","category":"anthropic-skill","format":"markdown","content":"---\nname: jscpd\ndescription: Copy-paste detector for 220+ languages. Detect exact, renamed and near-miss duplicated code, measure duplication percentages, and find refactoring hotspots with the codebase summary.\n---\n\n# jscpd\n\nCopy-paste detector for programming source code, supports 220+ languages. Use this skill to run jscpd and understand its output.\n\n## Quick Start\n\n```bash\n# Run with ai reporter (compact output optimized for agents)\nnpx jscpd --reporters ai <path>\n\n# With ignore patterns\nnpx jscpd --reporters ai --ignore \"**/node_modules/**,**/dist/**\" <path>\n\n# Scope to specific formats\nnpx jscpd --reporters ai --format \"javascript,typescript\" <path>\n\n# Second pass, noisier: copies that only differ in names and values (Type-2, \"renamed\").\n# Review each hit before acting on it.\nnpx jscpd --reporters ai --ignore-identifiers --min-tokens 70 <path>\n\n# Third pass, noisier still: copies with a few edited lines or the same function\n# structure (Type-3, \"similar\"). Keep the settings tight.\nnpx jscpd --reporters ai --max-gap-lines 1 --similarity <path>\n\n# Where to refactor first: clone list plus a hotspot summary\nnpx jscpd --reporters ai --summary <path>\n\n# Most complex files only, without clone detection\nnpx jscpd --complexity --reporters ai <path>\n```\n\n## AI Reporter Output Format\n\nThe `ai` reporter produces compact, token-efficient output designed for agent consumption:\n\n```\nClones:\nsrc/ foo.ts:10-25 ~ bar.ts:42-57\nsrc/utils/helpers.ts:100-120 ~ src/utils/other.ts:5-25\nsrc/cart/ basket.js:1-9 ~ cart.js:1-9 (renamed)\nsrc/api/ save-account.js:1-12 ~ save-user.js:1-11 [~0.91 gap]\nsrc/billing/ orders.ts:1-14 ~ refunds.ts:1-15 [~0.85 ast]\n---\n5 clones · 4.2% duplication\n```\n\nEach line represents one clone pair:\n- **Same file**: `path/file.ts 10-25 ~ 45-60` (shared path shown once)\n- **Same directory**: `shared/prefix/ file-a.ts:10-25 ~ file-b.ts:42-57` (common prefix factored out)\n- **Different paths**: `path/a.ts:10-25 ~ path/b.ts:42-57`\n\nA suffix tells the **kind** of clone; no suffix means an exact copy:\n- `(renamed)`: the two blocks differ only in identifier names, literal values or annotations (Type-2). Only appears with `--ignore-identifiers`, `--ignore-literals` or `--ignore-annotations`.\n- `[~0.91 gap]`: two exact clones merged across up to `--max-gap-lines` unmatched lines (Type-3). The number is matched tokens over the merged span.\n- `[~0.85 ast]`: two functions whose normalized syntax trees share at least `--similarity` of their subtrees (Type-3). The number is that share. The names of called functions and methods and the operators count, local names and literal values do not, so a renamed copy with other constants scores 1 and a copy that calls other methods scores lower. A name still has to stand for one name on the other side: a copy that swaps the roles of two names scores lower.\n\n## Options\n\n| Option | Description |\n|--------|-------------|\n| `--reporters ai` | Use the AI-optimized reporter (compact clone list for agents) |\n| `--reporters html` | Generate HTML report |\n| `--reporters json` | Output JSON report |\n| `--min-tokens N` | Minimum tokens to consider a duplication (default: 50) |\n| `--min-lines N` | Minimum lines to consider a duplication (default: 5) |\n| `--threshold N` | Exit with error if duplication % exceeds N |\n| `--ignore \"glob\"` | Ignore patterns (comma-separated) |\n| `--format \"list\"` | Limit to specific languages (e.g. `typescript,javascript`) |\n| `--cross-formats \"groups\"` | Detect clones across related formats (e.g. `javascript,typescript` or the `js-ts` preset) |\n| `--ignore-identifiers` | Treat all identifiers as equal, so blocks that differ only in variable, function or type names match (Type-2, reported as `renamed`); a name must stand for one name on the other side, so look-alikes with another data flow stay out, while a field and a local of one text are two names, so `this.url = url` still matches `this.url = link` (`--ignore-identifiers=blind` matches any names) |\n| `--ignore-literals` | Treat all string literals as equal and all numeric literals as equal (Type-2) |\n| `--ignore-annotations` | Drop `@Name` / `@Name(...)` annotations and decorators before matching, in languages where `@` means one (Type-2) |\n| `--max-gap-lines N` | Merge clones of one file pair separated by at most N unmatched lines into one `similar` clone (Type-3, default: 0 = off) |\n| `--similarity [RATIO]` | Report pairs of functions whose normalized syntax trees share at least RATIO of their subtrees, in `(0, 1]`, as `similar` clones (Type-3, 0.8 without a value). JavaScript, TypeScript, Python, Java, Kotlin, Scala, C#, Go, Rust, C, C++, PHP, Ruby, Swift and Clojure; test files are left out |\n| `--min-nodes N` | The fewest nodes of a normalized syntax tree a function needs for `--similarity` (default: 20) |\n| `--summary` | Append a codebase summary: top files/folders by tokens, lines, size, complexity, with duplication share |\n| `--summary-top N` | Number of entries in each summary top list (default: 10) |\n| `--summary-by metric` | Summary ranking metric: `tokens`, `lines`, `size`, `complexity` (default: `tokens`) |\n| `--kind list` | Report only these clone kinds: `exact`, `renamed`, `similar`, `gap`, `ast` (never enables a detector) |\n| `--complexity` | Complexity tables only, without clone detection (reporters: console, ai, json) |\n| `--dead-code` | Find unused files, exports, module-private declarations and imports — JavaScript, TypeScript, JSX, TSX, Vue, Svelte, Astro, Python. Each finding carries a 0-100 confidence score |\n| `--dead-code-categories list` | Limit `--dead-code`/`--dashboard` to categories: `unused-file`, `unused-export`, `unused-symbol`, `unused-import` |\n| `--min-confidence N` | Floor for `--dead-code`/`--dashboard` findings, 0-100 (default: 60) |\n| `--entry \"glob\"` | Extra entry points for dead-code analysis, beyond what's inferred from `package.json`/`pyproject.toml`/framework conventions |\n| `--include-tests` | Count test-only usage as \"used\" for dead-code analysis (excluded by default) |\n| `--include-entry-exports` | Also flag an entry file's own unused exports (excluded by default: its exports are the public API) |\n| `--dashboard` | One screen: health badge, project size and largest code files, duplication, complexity, dead code (JS/TS/Python); `-r json` writes `jscpd-dashboard.json` |\n| `--health` | Project health badge only: 0-100 score and grade from duplication, dead code and complexity (`-r ai` one line, `-r json`, `-r badge`) |\n| `--health-input FILE` | Add metrics from other tools (coverage, tests, security) to the health score |\n| `--pattern \"glob\"` | Glob pattern to select files |\n| `--no-gitignore` | Do not respect `.gitignore` (it is respected by default) |\n| `--output \"path\"` | Directory to write reports to |\n| `--silent` | Suppress console output (useful with file reporters and `--output`) |\n| `--list` | List all supported formats and exit |\n| `--no-tips` | Disable tips in output (skipped automatically when stdout is not a TTY or `CI` or `JSCPD_NO_TIPS` is set) |\n| `--config \"path\"` | Path to .jscpd.json config file |\n\n## Clone Kinds: Exact, Renamed, Similar\n\nBy default jscpd reports **exact** clones only: the token sequences are identical (whitespace, layout and, depending on the mode, comments do not count). Two opt-in families widen the net. Run them as separate passes after the default scan, because they find more and longer clones and change what a \"clone\" means.\n\n**These passes are noisy by design.** An exact clone is almost always a real copy. A renamed or similar clone is a *candidate*: the flags deliberately ignore the very things (names, values, a statement or two) that often make two blocks different in meaning. Expect false positives from:\n\n- boilerplate that is supposed to look alike: DTOs and models, config tables, enum-like maps, route or handler registrations, builders\n- test files: `describe`/`it` blocks, fixtures and setup code repeat the same shape on purpose\n- generated code, migrations, serializers, protocol bindings\n- language idioms: two `reduce` loops or two `switch` statements that share structure but not logic\n- small blocks: with `--ignore-identifiers` a 50-token block is mostly placeholders, so raise `--min-tokens`\n\nRules that keep the noise manageable:\n\n- Run them **after** the exact clones are handled, one family at a time, so every hit is attributable to one flag.\n- Start conservative: `--ignore-identifiers` alone (add `--ignore-literals` only when constants are the known problem), `--max-gap-lines 1` or `2`, `--similarity 0.85` or higher, and `--min-tokens 70` or more for the identifier pass. Widen only when the tight run comes back empty.\n- Treat every `(renamed)` or `[~…]` line as a lead to read, not a defect to fix. Do not gate CI (`--threshold`, `--fail-on-new-clones`) on these passes unless the team has reviewed what they report on the codebase.\n- Never claim \"N duplicates found\" from a normalized run without saying which flags produced them.\n\n### Type-2: renamed clones (`--ignore-identifiers`, `--ignore-literals`, `--ignore-annotations`)\n\nCopies where someone renamed the variables or changed the constants:\n\n```bash\nnpx jscpd --reporters ai --ignore-identifiers <path>                      # function a(x) {…} matches function b(y) {…}\nnpx jscpd --reporters ai --ignore-identifiers --ignore-literals <path>    # …and 10 matches 25, 'dev' matches 'prod'\nnpx jscpd --reporters ai --ignore-annotations <path>                      # @Override / @Deprecated no longer split a clone\n```\n\n```\nClones:\nbasket.js:1-9 ~ cart.js:1-9 (renamed)\nlimits-dev.js:1-13 ~ limits-prod.js:1-13 (renamed)\n---\n```\n\n- Keywords keep their meaning (`return` never matches `retry`), so structure still has to match.\n- `--ignore-annotations` only acts in Java, Kotlin, Scala, Groovy, Python, Dart, Swift, JavaScript and TypeScript; in Ruby, Perl, T-SQL, Razor and CSS `@` means something else and is left alone.\n- Positions in the report still point at the original source.\n- Config keys: `ignoreIdentifiers`, `ignoreLiterals`, `ignoreAnnotations`.\n\n### Type-3: near-miss clones (`--max-gap-lines`, `--similarity`)\n\nCopies with a few edited lines, or functions rewritten with the same shape:\n\n```bash\nnpx jscpd --reporters ai --max-gap-lines 2 <path>      # a copy with up to 2 inserted/changed lines becomes one clone\nnpx jscpd --reporters ai --similarity <path>           # functions whose normalized syntax trees share ≥82% of their subtrees\n```\n\n```\nClones:\nsave-account.js:1-12 ~ save-user.js:1-11 [~0.91 gap]\norders.ts:1-14 ~ refunds.ts:1-15 [~0.85 ast]\n---\n```\n\n- `--max-gap-lines N` only joins clones the exact run already found, so it removes fragmentation rather than inventing matches; it works in every language. A merge is refused when the gap holds more tokens than the halves share (similarity would drop under `0.5`).\n- `--similarity` normalizes the syntax tree of every function (local names and literals become markers, each name standing for one name on the other side; called names and operators stay) and scores the share of subtrees two trees have in common, so a renamed copy scores `1.0`, one inserted statement in a function of ten about `0.85`, a copy that calls other methods less. It applies to JavaScript, TypeScript, Python, Java, Kotlin, Scala, C#, Go, Rust, C, C++, PHP, Ruby, Swift and Clojure, and to their code blocks in Markdown and in Vue, Svelte and Astro files; other formats are a silent no-op. A function nested in another is part of it, and test files are left out. Start at the default `0.8` and lower to `0.7` only when looking for leads; read both functions before believing a low score.\n- `similar` takes precedence over `renamed` when both apply (a merged clone is no longer identical even after normalization).\n- Config keys: `maxGapLines`, `similarity`, `minNodes`.\n\n### Where the kind shows up\n\n- `console`: `Clone found (javascript, renamed)`, `Clone found (javascript, similar (gap) ~0.91)`, `Clone found (javascript, similar (ast) ~0.75)`.\n- `json`: `\"kind\": \"exact\" | \"renamed\" | \"similar\"`, plus `\"similarity\"` and `\"method\": \"gap\" | \"ast\"` for similar clones, and the size of each normalized tree as `\"nodes\"` in `firstFile` and `secondFile` for ast ones.\n- `sarif`: rules `jscpd/duplicate-code`, `jscpd/renamed-code`, `jscpd/similar-code` (clones merged across a gap), `jscpd/similar-function` (functions and classes paired by `--similarity`) and `jscpd/semantic-code` (`--semantic`); Code Climate uses the same `check_name` values.\n- A default run reports only `exact` clones and its output is unchanged by these features.\n- Normalized runs produce different clone fingerprints than exact runs: keep a separate `--baseline` file per configuration.\n\n## Codebase Summary (`--summary`)\n\n`--summary` appends a refactoring-hotspot overview to the run output — use it to decide **where to refactor first** before diving into individual clones:\n\n```bash\n# Compact clone list + compact summary, optimized for agents\nnpx jscpd --reporters ai --summary --no-tips <path>\n\n# Rank by complexity instead of tokens, top 5 lists\nnpx jscpd --reporters ai --summary --summary-by complexity --summary-top 5 <path>\n```\n\nWith the `ai` reporter the summary is one line per entry:\n\n```\nSummary by tokens (321 files, 129 folders):\nfiles (tokens/lines/size/cx/dup%):\nsrc/files.ts 2052/363/11662/80/0.0%\nlong-line/theme-branded.js 498/15/2.5K/10/93.3%\n...\nfolders (files/tokens/lines/size):\nsrc/core 8/5264/843/27136\n...\n```\n\nHow to read it:\n- **Top files** are ranked by the `--summary-by` metric, but every row carries all metrics — `tokens/lines/size/cx/dup%`.\n- **cx** is a language-agnostic cyclomatic-complexity estimate from the token stream (1 + decision-point tokens like `if`/`while`/`&&`); treat it as a ranking signal, not an exact metric.\n- **dup%** is the share of the file's lines covered by detected clones — a large file with high `dup%` is the best refactoring target. It reflects the clones of the current run, so with the Type-2/Type-3 flags it rises accordingly.\n- **Folders** aggregate files into their direct parent directory (no cumulative ancestor totals).\n- In `console` reporters the summary renders as aligned tables; in the `json` report it appears as an additive `summary` key (absent when the flag is off).\n\nConfig file equivalents: `\"summary\": true`, `\"summaryTop\": 10`, `\"summaryBy\": \"tokens\"`.\n\n## Cross-Format Clone Detection\n\nBy default each format is compared only against itself. `--cross-formats` defines groups of related formats that share one comparison pool, so a block duplicated between a `.js` and a `.ts` file is reported as a clone:\n\n```bash\n# One group: compare JavaScript and TypeScript files together\nnpx jscpd --reporters ai --cross-formats \"javascript,typescript\" <path>\n\n# Preset covering javascript, jsx, typescript, tsx\nnpx jscpd --reporters ai --cross-formats \"js-ts\" <path>\n\n# Multiple groups are separated by \";\"\nnpx jscpd --reporters ai --cross-formats \"javascript,typescript;css,scss\" <path>\n```\n\nNotes:\n- 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)`. Reported positions still reference the original source.\n- Groups need at least two formats; groups sharing a format are merged into one pool.\n- In per-format statistics, a cross-format clone is attributed to one member format of the group.\n- In config files the key is `crossFormats` (or `cross-formats`) and accepts a string (`\"javascript,typescript;css,scss\"`), an array of strings (`[\"javascript,typescript\", \"css,scss\"]`), or an array of arrays (`[[\"javascript\",\"typescript\"],[\"css\",\"scss\"]]`).\n\n## Comparing Two Codebases (`--compare`)\n\n`--compare SOURCE TARGET` pairs the functions of two folders with the `--semantic` embedding model and lists which functions of each side have a counterpart in the other: the progress of a port to another language or framework, or the parity of two implementations of one app. It takes exactly two paths that do not overlap, needs the model once (`--semantic-download`), and runs no clone detection.\n\n```bash\nnpx jscpd --compare python-lib/ rust-lib/                                  # progress of a port and what is left\nnpx jscpd --compare ios/ android/ -r console-full                          # parity, with every pair and its similarity\nnpx jscpd --compare python-lib/ rust-lib/ -r json -o .jscpd-compare --silent  # jscpd-compare.json for an agent\n```\n\nFunctions pair by code first (each is the other's closest match), then by name when the names match once case and underscores are ignored and the code is similar enough. Every pair has its similarity and a level (`high`, `medium`, `low`) on the scale of the model, and the default report lists the pairs under other names on their own. jscpd measures tests and code in two blocks and pairs a test only with a test. `--min-tokens` defaults to 30 here. Reporters: `console`, `console-full`, `json`, `markdown`. See the **[compare-codebases](../compare-codebases/SKILL.md)** skill for how the comparison works and how to check it, and **[code-migration](../code-migration/SKILL.md)** for porting code with it.\n\n## Configuration File\n\nCreate a `.jscpd.json` in your project root:\n\n```json\n{\n  \"threshold\": 0,\n  \"reporters\": [\"ai\"],\n  \"ignore\": [\"**/node_modules/**\", \"**/dist/**\", \"**/*.min.*\"],\n  \"format\": [\"typescript\", \"javascript\"],\n  \"minLines\": 5,\n  \"minTokens\": 50,\n  \"ignoreIdentifiers\": false,\n  \"ignoreLiterals\": false,\n  \"maxGapLines\": 0,\n  \"similarity\": 1,\n  \"summary\": false,\n  \"output\": \"./reports/jscpd\"\n}\n```\n\n## Refactoring Duplicated Code\n\nOnce you've detected clones, use the **dry-refactoring** skill for a guided workflow to eliminate them, with a strategy per clone kind:\n\n→ **dry-refactoring** — step-by-step refactoring strategies and workflow for removing duplication. Install with:\n  ```bash\n  npx skills add https://github.com/kucherenko/jscpd --skill dry-refactoring\n  ```\n\n## Improving Overall Codebase Health\n\nFor a broader \"clean up this codebase\" pass — duplication, then dead code, then the largest/most complex files, prioritized from `--health`/`--dashboard` and re-measured at the end — use **codebase-refactoring**:\n\n  ```bash\n  npx skills add https://github.com/kucherenko/jscpd --skill codebase-refactoring\n  ```\n","frontmatter":{"name":"jscpd","description":"Copy-paste detector for 220+ languages. Detect exact, renamed and near-miss duplicated code, measure duplication percentages, and find refactoring hotspots with the codebase summary."},"isInternal":false,"tokens":4762,"sizeBytes":18368},{"name":"api.md","path":"docs/api.md","rawUrl":"https://raw.githubusercontent.com/kucherenko/jscpd/HEAD/docs/api.md","title":"docs - API Interface Contract & Specifications","category":"project-spec","format":"markdown","content":"# Programming API\n\njscpd is a Rust engine. The CLI ships as the `jscpd` crate on crates.io (installs the `jscpd` and `cpd` binaries) and as the `jscpd` / `cpd` npm packages (prebuilt binaries); the engine itself is a set of library crates you can depend on directly.\n\n## Rust\n\nUse the `cpd-finder` crate — it is the orchestration entry point and pulls in the tokenizer, detection core, and reporters:\n\n```toml\n[dependencies]\ncpd-finder = \"0.1\"\n```\n\n```rust\nuse cpd_finder::orchestrate::{RunConfig, run};\n\nlet config = RunConfig {\n    paths: vec![\"./src\".into()],\n    min_tokens: 50,\n    ..Default::default()\n};\n\nlet result = run(&config).unwrap();\nprintln!(\"Found {} clones\", result.clones.len());\nprintln!(\"Analyzed {} files\", result.statistics.total.sources);\n```\n\nA complete, runnable version is in [`examples/rust-cpd-finder`](../examples/rust-cpd-finder).\n\nSemantic clones come from the `cpd-semantic` crate, which adds a clone pass to the run:\n\n```rust\nuse cpd_finder::orchestrate::{RunConfig, run};\nuse cpd_semantic::{SemanticOptions, SemanticPass};\nuse std::sync::Arc;\n\n// The local model is downloaded once, e.g. with `jscpd --semantic-download`.\nlet options = SemanticOptions::default();\nlet paths = vec![\"./src\".into()];\nlet embedder = cpd_semantic::embedder(&options, &paths, true)?;\n// The thresholds of the rules, on the scale of the options' model.\nlet thresholds = options.thresholds();\nlet config = RunConfig {\n    paths,\n    passes: vec![Arc::new(SemanticPass::new(embedder, thresholds, options.scope))],\n    ..Default::default()\n};\nlet result = run(&config)?;\n```\n\nThe `jscpd` crate's own library target is **not a public API** — it exists to share helpers with the crate's tests and may change in any release. Depend on the engine crates instead.\n\n### Crate Architecture\n\n| Crate | Description |\n|-------|-------------|\n| [`cpd-core`](https://crates.io/crates/cpd-core) | Core data models and hashing (Rabin-Karp rolling hash) |\n| [`cpd-tokenizer`](https://crates.io/crates/cpd-tokenizer) | Source code tokenization (224 formats, uses `oxc_parser` for JavaScript/TypeScript) — pure, no I/O |\n| [`cpd-similarity`](https://crates.io/crates/cpd-similarity) | Structural similarity (`--similarity`): functions from tree-sitter syntax trees and a Clojure reader, the subtree fingerprints of their normalized trees and the exact search for pairs; pure, no I/O |\n| [`cpd-finder`](https://crates.io/crates/cpd-finder) | File walking, orchestration, git blame (`rayon` + `ignore` + `globset`) |\n| [`cpd-reporter`](https://crates.io/crates/cpd-reporter) | Output format rendering (15 reporters) |\n| [`cpd-semantic`](https://crates.io/crates/cpd-semantic) | Semantic clones (`--semantic`, experimental), as a clone pass for `cpd-finder` |\n\nSee [Packages](./packages.md) for versions and paths.\n\n## Other languages\n\nThere is no Node.js (or other language) binding for the v5 engine. From another language, run the binary and read the `json` reporter's output (`--reporters json --output <dir>` writes `<dir>/jscpd-report.json`), or talk to it over the [MCP stdio transport](./ai-ready.md#stdio-transport-rust-v5).\n\nThe TypeScript API of jscpd v4 (`jscpd()`, `detectClones()`, `@jscpd/core`, LevelDB/Redis stores) is maintained on the [`master-v4`](https://github.com/kucherenko/jscpd/tree/master-v4) branch and published as `jscpd@4`.\n","isInternal":false,"tokens":888,"sizeBytes":3349},{"name":"README.md","path":"benchmark/tools/README.md","rawUrl":"https://raw.githubusercontent.com/kucherenko/jscpd/HEAD/benchmark/tools/README.md","title":"tools Documentation","category":"plugin-manifest","format":"markdown","content":"# Benchmark Tools\n\nThis directory contains all third-party tools required by the benchmark script. Install each tool by following the instructions below, then run `./benchmark.sh` from the parent directory.\n\n## Quick Install\n\n```bash\ncd tools/\nnpm install\n```\n\nThis installs **jscpd-rs** and **Fallow** (the only tools distributed via npm). The remaining tools must be installed manually.\n\n## Tools\n\n### jscpd@5 (local build)\n\nThe jscpd v5 Rust binary, built from the `rust/` directory in this repository.\n\n```bash\ncd ../../rust/\ncargo build --release\n```\n\nThe binary is expected at `../../rust/target/release/jscpd`.\n\n### jscpd-rs (npm)\n\nInstalled in `tools/node_modules/` via `npm install`. The npm package downloads a prebuilt Rust binary for the current platform.\n\n```bash\nnpm install\n```\n\nBinary: `node_modules/.bin/jscpd-rs`\n\n### Fallow dupes\n\nInstalled in `tools/node_modules/` via `npm install`. Fallow is a Rust-based code intelligence tool; the `dupes` subcommand performs structural clone detection for TypeScript/JavaScript projects.\n\n```bash\nnpm install\n```\n\nBinary: `node_modules/.bin/fallow`\n\n> **Note:** Fallow must run from inside the target directory (it has no path argument). The benchmark script `cd`s into the fixtures directory before invoking it.\n\n### PMD CPD\n\nInstall via Homebrew (macOS) or download from [pmd.github.io](https://pmd.github.io).\n\n```bash\nbrew install pmd\n```\n\nPMD requires a Java runtime (8+). The `pmd` command must be on `$PATH`.\n\n> **Note:** The benchmark runs PMD CPD for all 34 supported languages individually and sums the results. This takes ~30 seconds.\n\n### Duplo\n\nA prebuilt macOS arm64 binary is included at `tools/duplo`. To replace it:\n\n1. Download from [github.com/dlidstrom/Duplo/releases](https://github.com/dlidstrom/Duplo/releases)\n2. Place the binary at `tools/duplo`\n3. `chmod +x tools/duplo`\n\n```bash\n# Verify\n./duplo -help\n```\n\n### Simian\n\nA prebuilt JAR is included at `tools/simian.jar`. To replace it:\n\n1. Download from [simian.quandarypeak.com](https://simian.quandarypeak.com)\n2. Place the JAR at `tools/simian.jar`\n\nRequires a Java runtime:\n\n```bash\njava -jar tools/simian.jar -help\n```","isInternal":false,"tokens":560,"sizeBytes":2156}],"systemPromptSnippet":"<yakaai_skills repo=\"kucherenko/jscpd\">\n<!-- File: skills/code-migration/SKILL.md (Tokens: ~4675 | Category: anthropic-skill) -->\n---\nname: code-migration\ndescription: Move code from one implementation to another function by function, tests first and code second, and check two implementations of one app for parity, with jscpd --compare as the progress measure and a coverage map binding each function to its tests. Use when porting a library or app to another language or framework (Java to Kotlin, JavaScript to Rust, a Python library to TypeScript, an iOS app to Android), when asked what is left to port, or when comparing the Android and iOS versions of an app.\n---\n\n# code-migration\n\n`jscpd --compare SOURCE TARGET` pairs every function of one folder with the function of the other folder that does the same job, in any pair of languages, and lists the functions that have no counterpart. This skill uses it to measure a port: what is ported, what is left, and whether the port you just wrote was recognized.\n\nA port runs in two phases: the tests first, then the code they check. A coverage report of the source's tests tells which tests exercise which function, so each function is ported together with the tests that prove it works. See [Plan: tests first, then code](#plan-tests-first-then-code).\n\nTwo words are used throughout:\n\n- **source**: the implementation you port from, such as the iOS app, the Python library, or the JavaScript package;\n- **target**: the implementation you port to, such as the Android app, the Rust crate, or the TypeScript rewrite.\n\nNeither has to be old or new. The source may stay in production and keep changing, and the target may already hold features the source lacks. For two implementations that both live on (`ios/` and `android/`), the same report shows parity; see [Parity](#parity-between-two-implementations).\n\njscpd finds functions in JavaScript, TypeScript, JSX, TSX, Vue, Svelte, Astro, Python, Rust, Go, Java, Kotlin, C#, C, C++, PHP, Ruby, Scala and Swift. The [compare-codebases](../compare-codebases/SKILL.md) skill explains how the comparison pairs functions and how to check its result; the [jscpd](../jscpd/SKILL.md) skill covers the rest of the tool.\n\n## Setup\n\n`--compare` pairs functions with a code embedding model that runs inside jscpd. Download it once (548 MB, CodeRankEmbed). Ask the user before you start the download:\n\n```bash\nnpx jscpd --semantic-download\n```\n\njscpd caches the vectors, so a repeat run embeds only the functions whose code changed and takes seconds.\n\nPick the two paths and keep them fixed for the whole port: the source first, the target second. Two paths are required, and they must not overlap (`app/` and `app/android/` is refused). The target may be empty at the start.\n\nOne run measures both phases: the report has a `Code` block and a `Tests` block (a `code` and a `tests` section in JSON), and a test pairs only with a test. jscpd tells a test by the conventions of its language: test files such as `*_test.go`, `test_*.py`, `*.test.ts` or `*Test.java`, folders such as `tests/`, `__tests__/` or `src/test/`, Rust tests in `#[cfg(test)]` modules, and JavaScript test cases such as `it('rounds cents', () => …)`. Phase 1 reads the `Tests` block, phase 2 the `Code` block.\n\n### Keep vendored and generated code out\n\n`--compare` counts and embeds every function in both paths. Vendored dependencies (`vendor/` from `cargo vendor` or `go mod vendor`, `third_party/`), installed packages (`node_modules/`, `.venv/`), build output (`target/`, `build/`, `dist/`) and generated code are not part of the port, yet a vendored crate tree alone holds thousands of functions. With them in a path, the first run embeds all of them and takes tens of minutes instead of seconds, and the percentages describe the dependencies instead of the port.\n\njscpd skips what `.gitignore` excludes, but only inside a git repository. Before the first run:\n\n1. Check both paths for such folders. A target you create for the port gets them as soon as you build it or vendor its dependencies, so check again after the first build.\n2. Suggest a `.gitignore` to the user that lists the folders the target's language and tools produce, plus the report folder. For a Rust addon built with napi-rs:\n\n   ```gitignore\n   /target/\n   /vendor/\n   node_modules/\n   *.node\n   .jscpd-compare/\n   ```\n\n   If the target is not in a git repository, tell the user that jscpd reads the `.gitignore` only after `git init`.\n3. Until the `.gitignore` works, pass the same folders with `--ignore`, and keep the globs the same for every run:\n\n   ```bash\n   npx jscpd --compare node-lib/ rust-lib/ --ignore \"**/vendor/**,**/target/**,**/node_modules/**\"\n   ```\n\nThe totals line shows when something slipped through. Suspect vendored or generated code in a path when the target has far more functions than the source, or when a run embeds hundreds of functions after a small change.\n\n## Measure\n\nRun the console report for yourself and for the user. Here a Python billing library is being ported to TypeScript:\n\n```bash\nnpx jscpd --compare billing-py/ billing-ts/\n```\n\n```text\n 71% 5 of 7 functions in billing-py/ have a counterpart in billing-ts/\n 80% 4 of 5 functions in billing-ts/ have a counterpart in billing-py/\n\nbilling-py/\n  file         paired  similarity  counterpart\n  billing.py   4 / 5   0.89        billing.ts\n  shipping.py  1 / 2   0.91        shipping.ts\n\nbilling-ts/\n  file         paired  similarity  counterpart\n  billing.ts   3 / 4   0.89        billing.py\n  shipping.ts  1 / 1   0.91        shipping.py\n\nPaired under other names (1):\n  billing-py/                   billing-ts/             similarity\n  billing.py:28 tax_for_region  billing.ts:27 salesTax  0.87 high\n\nOnly in billing-py/ (2):\n  billing.py (1)\n    46  due_date                6 lines\n  shipping.py (1)\n    18  estimate_delivery_days  8 lines\n\nOnly in billing-ts/ (1):\n  billing.ts (1)\n    39  toCurrency  8 lines\n```\n\n- The first line is the port's progress: the share of the source's functions that have a counterpart in the target. \"Only in\" the source is the work left.\n- The second line and \"Only in\" the target describe the target's own code: helpers the port needed, features the source never had, or a port jscpd did not recognize (see [Misses](#misses)).\n- Each file row gives its paired functions, the mean similarity of their pairs (with the number of `low` pairs, if any), and the counterpart file: the file on the other side that holds most of its counterparts. Use the counterpart to decide where a missing function goes.\n- \"Paired under other names\" lists the pairs whose names differ even once case and underscores are ignored: renamed ports, constructors, platform names. Each has its similarity and level (see [Reading pairs](#reading-pairs)). These are ported already, even though a search by name would not find them.\n- With an empty target the report is one line of totals plus `billing-ts/ has no functions yet`.\n\nFor work you plan and track, read the JSON report instead of the console:\n\n```bash\nnpx jscpd --compare billing-py/ billing-ts/ -r json -o .jscpd-compare --silent\n```\n\njscpd measures tests and code apart and pairs a test only with a test, so `.jscpd-compare/jscpd-compare.json` has a `code` and a `tests` section of the same shape. Each section has `sides[0]` (the source) and `sides[1]` (the target), each with `path`, `functions`, `matched`, `percentage`, `files` (`file`, `functions`, `matched`, `counterpart`, `similarity`, `lowPairs`) `unmatched` (`file`, `name`, `start`, `end`) and `readyToPort` (the unmatched functions whose callees all have a counterpart, with their number of `callers`, most called first), and `pairs`, each with `a` (source), `b` (target), `similarity`, `level` (`high`, `medium`, `low`), `renamed` (the names differ) and `matchedBy` (`code` or `name`). Paths are relative to each side's folder. Add `.jscpd-compare/` to `.gitignore` or write the report outside the repository.\n\n`-r console-full` also prints every pair with its similarity, `-r markdown` writes `jscpd-compare.md`, a table you can paste into a PR description or a tracking issue, and `-r html` writes `jscpd-compare.html`, a migration map the user can open to see the two dependency graphs side by side, or the same pairs as a table.\n\n## Plan: tests first, then code\n\nPort the tests before the code. Ported tests define, before any target code exists, what it means for each function to be ported: when the function lands, its tests pass or show what still differs. A port written before its tests can only be checked against your reading of the source.\n\n### Bind tests to functions with coverage\n\nA test's name does not say which functions it runs: a test of `checkout` also runs `apply_discount` and `tax_for_region`. Coverage does. Before porting anything, build a map from each source function to the tests that exercise it.\n\n1. Get a coverage report of the source's tests that says which test ran which lines: recorded per test, or per test file when the project's tooling has no per-test mode.\n2. Take the source's functions and their lines from the `code` section of the JSON report. Every counted function is either in the source's `unmatched` list or the `a` side of a pair, each with `file`, `name`, `start` and `end`. Functions under `--min-tokens` or `--min-lines` are not listed there; take those from the coverage report's own function list, which most formats have.\n3. A test covers a function when it runs a line from the function's `start` to its `end`. Write the map both ways, function to tests and test to functions, to a file next to the report (for example `.jscpd-compare/test-map.json`), and rebuild it when the source's tests change.\n\nUse the map for three things:\n\n- which tests to port with a function, and which functions a ported test needs before it can pass;\n- the source functions no test covers. Before porting such a function, write a test for it on the source side that records what it does today (a characterization test), and port that test with the others. A port with no test has nothing to check it;\n- the order of work: tests that need few functions come first, since they turn green soonest.\n\nCoverage says which code a test runs, not what it checks. A function that a test only passes through on the way to another is weakly bound to that test; prefer the tests whose names or assertions are about the function.\n\n### Phase 1: port the tests\n\n1. Take the source's unmatched tests from the `tests` section of the JSON report. A JavaScript or TypeScript test case written as a callback, `it('rounds cents', () => …)`, goes by its title, so it pairs with `test_rounds_cents` in pytest or `rounds_cents` in Rust like any named test.\n2. Port the tests file by file, into the target's test layout, against the API the target will have. Keep inputs and expected values exactly as they are. Where the target must behave differently (a platform limit, a language's number types), write the difference into the test with a comment and tell the user.\n3. A ported test calls functions that do not exist yet, so it fails. In a compiled language it stops the whole test build. Keep such tests out of the build until their functions land, for example a module declaration left commented out, a cfg feature or an excluded source set, and turn them on one by one in phase 2. Do not write empty functions to make the tests compile: a stub under a source name can pair by name and count as ported.\n4. Run the target's tests. The ported tests that pass already cover what the target has; the failing or disabled ones, read against the test map, are the work list for phase 2.\n\n### Phase 2: port the code\n\nPort the code one function at a time, with its tests already in place.\n\n1. Run the JSON report and take the source's `unmatched` list from its `code` section.\n2. Order the work. Start with functions whose tests are ported, and port a function after the functions it calls, since a caller ported before its helpers has nothing to call. The source's `readyToPort` list is that order's front: the functions whose callees all have a counterpart already, most called first. Take the next one from there, and rerun the report after each batch, since every port can make its callers ready. Within that order, finish one file before starting the next, so each target file fills up in one go.\n3. Before writing a port, make sure the target does not have it already. Pairs with `renamed: true` are ported functions under other names; they are not in `unmatched`, but check them when the user asks where a function went. Then search the target for an equivalent that jscpd missed: in the counterpart file from `files`, under other names (constructors, merged functions, a platform or library call that replaces the helper). If one exists, do not port the function again. Note the equivalent and move on.\n4. Write the port in the counterpart file, or in the file the target's layout puts it in. Keep the name recognizable in the target language's convention (`encodeBinary` becomes `encode_binary` in Rust or Python), because jscpd pairs short functions by name. Follow the style of pairs that are already done in the same file. `console-full` lists them.\n5. Turn on the function's ported tests, as the test map lists them, and run them. When they fail, fix the port, not the test, unless the user agrees that the target should behave differently. A pair in the report says the two functions look alike; the tests say whether they behave alike.\n6. Run the target's coverage for those tests and check that they run the new function. A ported test that never reaches it is bound to something else on the target (a helper, a mock, an old path), and it proves nothing about the port.\n7. Run the comparison again with the same paths and options. Check that the function has left `unmatched`, that its pair joins the right function in the target, and its level. A `high` pair is done. A `low` pair to your port usually means the port differs a lot from the source in structure; read both and make sure the behavior matches. A pair to a different function means the port is not recognized, or it resembles the wrong thing; read both before going on.\n8. Report progress to the user with both measures, as the reports print them: tests ported and passing, and functions ported (`functions 73% → 76%, 3 ported: …; tests 41 of 44 ported, 38 passing`), with the functions you decided not to port and why.\n\nRepeat until the source's `unmatched` list holds only functions you decided not to port. Typical reasons: dead code (check with `npx jscpd --dead-code` on the source where it supports the language), code the target platform or its libraries provide (a JSON parser, a retry helper), platform glue with no equivalent on the target (an iOS delegate callback, an Android notification channel), and features the user decided not to carry over. List these in a notes file or the PR, one line each, so the remaining percentage is explained.\n\nWhen the source keeps changing during the port, a function that was paired can come back as unmatched after a rewrite, and new source functions join the list. Rerun the report before each batch of work instead of reusing an old list.\n\n## Parity between two implementations\n\nWhen both implementations live on, as the iOS and the Android app do, `npx jscpd --compare ios/ android/` shows parity. Read both \"Only in\" lists:\n\n- a function on one side only is either platform code (Android notification channels, iOS delegate callbacks) or a feature the other side lacks. Report each missing feature to the user before implementing it;\n- to add a missing feature, treat the side that has it as the source for that feature: bind its tests with coverage, port the tests, then the code, as in [Plan: tests first, then code](#plan-tests-first-then-code).\n\njscpd pairs functions within modules that already match. A module is the folder right under the deepest folder each side's files share, so keep the two trees parallel (`ios/<plugin>` and `android/<plugin>`) for the best pairing.\n\n## Reading pairs\n\n- `level` rates the similarity on the scale of the model in use, so it means the same with every model. `high` (0.7125 and up with the default CodeRankEmbed, across languages) is almost always the same function. `medium` is usually the same function, restructured. `low` needs reading both functions, since related code pairs there too (a function counting UTF-8 bytes paired with one converting a string to them). On the Tauri plugins, every `low` pair joined two differently named functions, and one of them joined two different plugins.\n- `matchedBy: code` means the two functions are each other's closest match and the similarity stands out.\n- `matchedBy: name` means the names match once case and underscores are ignored, and the code is similar enough. It catches short ports. Read these pairs, because a same-named function can do something else.\n- The similarity does not see small differences in behavior. Two versions that drifted apart still pair. Only tests and reading the code catch drift.\n\n## Misses\n\njscpd compares functions only: types, constants, enums with data, SQL and UI markup (storyboards, Android layouts) are not in the report, so port and check them yourself. Known gaps in pairing:\n\n- constructors across languages (a Java constructor and Rust's `new`, Kotlin's `constructor`, Swift's `init`) pair only when their code is similar enough;\n- a short function renamed in the port (`add_history` for `_finder_penalty_add_history`);\n- one function split into several, or several merged into one: the report may pair only the closest part and list the rest as unmatched;\n- anonymous functions (callbacks, closures) take no part at all, except JavaScript and TypeScript test cases such as `it('rounds cents', () => …)`, which go by their titles.\n\nA function listed only in the target that you know is a port of a source function is one of these. Do not rename working code only to raise the number, and never add stubs or empty functions with source names: jscpd may pair a stub by name, and the progress would then report work that was not done.\n\n## Options\n\n- `--min-tokens` (30 with `--compare`) and `--min-lines` (5) decide which functions count toward the totals. Smaller functions still pair as partners. Raise them to focus on substantial functions, and keep them fixed across runs so percentages compare.\n- `--ignore` leaves files out, such as vendored dependencies, build output, generated code or fixtures (see [Keep vendored and generated code out](#keep-vendored-and-generated-code-out)); `--pattern` narrows the comparison to some files, such as the tests of one module. Keep the globs fixed across runs.\n- `--format` narrows the walk to some languages, for example when the source mixes the code being ported with build scripts.\n- `--semantic-model` and `--semantic-url` pick another embedding model or an OpenAI-compatible API (`npx jscpd --semantic-models` lists the models with calibrated thresholds). Keep the model fixed across runs, because each model scores on its own scale.\n- The exit code is 0 whatever the progress, so the command does not fail a build on its own.\n\n## Rules\n\n- Keep the two paths, their order, the options and the model the same from run to run, or the numbers stop being comparable.\n- Keep vendored dependencies, installed packages and build output out of both paths: through a `.gitignore` in a git repository, which you suggest to the user, or with `--ignore`.\n- Port the tests before the code, and a function only together with the tests the coverage map binds to it.\n- Treat the report as a map of what to read and what is left. It does not prove the port is correct; tests do.\n- Never game the percentage: no stubs, no renames made only for jscpd, no deleting source functions to shrink the denominator without the user's decision.\n- When the report and your reading of the code disagree, trust the code and tell the user which pair looked wrong.\n\n<!-- File: skills/codebase-refactoring/SKILL.md (Tokens: ~2516 | Category: anthropic-skill) -->\n---\nname: codebase-refactoring\ndescription: Three-part workflow to improve overall codebase health with jscpd — find and fix duplicated code, find and remove or refactor dead code, then find and simplify the largest/most complex files. Starts from --dashboard/--health to prioritize and ends by re-measuring the score.\n---\n\n# codebase-refactoring\n\nA guided pass over a codebase's three biggest maintenance costs — duplicated\ncode, dead code, and complexity — using jscpd to find each and a strategy to\nfix it. Use this skill when asked to \"clean up\", \"improve\", \"refactor\" or\n\"pay down tech debt in\" a codebase, rather than to fix one specific bug.\n\n## Start here: measure, then prioritize\n\n```bash\nnpx jscpd --health --reporters ai <path>\n```\n\n```\nhealth 74 B (duplication 75, dead-code 72, complexity 76; 93 code lines)\n```\n\nThe health score is a weighted mix of three sub-scores, each a share of the\ncode lines — high is good, 100 is clean. The lowest of the three names where\nthe codebase actually hurts most; if it isn't the order below, do that\ndimension first instead. `n/a` on a dimension (e.g. `dead code n/a`) means\njscpd could not measure it — dead-code detection needs JavaScript,\nTypeScript or Python to be a real share of the code — skip that step rather\nthan treating it as clean.\n\nFor the full picture behind that score — largest duplicated formats, most\ncomplex files, largest dead-code findings, all in one screen — run `npx\njscpd --dashboard` instead. Its `console` output (the default) is the only\nreporter with that per-file detail; `--reporters ai` on `--dashboard` prints\nthe same one-line summary shown above, not the breakdown, so don't reach for\nit expecting more than the score. Use `--reporters json` on `--dashboard` if\nyou need the detail in a parseable form.\n\nThe three passes below are otherwise independent and safe to run in any\norder; **duplication → dead code → complexity** is the default because\nfixing duplication first means step 2 isn't wasting effort tracing call\nsites that a later extract-function pass will move anyway, and clearing\ndead code before step 3 means the files ranked for simplification are ones\nactually worth simplifying, not ones about to be deleted.\n\n## Step 1 — Duplicated code\n\n```bash\nnpx jscpd --reporters ai --summary <path>\n```\n\nFind and read each clone, decide whether it is a real copy worth merging,\nand apply the matching refactoring strategy (extract function, parameterize,\nextract a shared module or constant, or a base class/template for\nstructural duplication). Full guided workflow, triage rules for\nrenamed/near-miss clones, and worked examples per clone kind:\n\n→ **[dry-refactoring](../dry-refactoring/SKILL.md)** — run this now, then\ncome back here for steps 2 and 3. Its own `--summary` hotspot ranking is the\nsame one the health score's duplication dimension reads from, so a file it\nflags as a duplication hotspot is the same file that dragged the score down.\n\n## Step 2 — Dead code\n\n```bash\nnpx jscpd --dead-code --reporters ai <path>\n```\n\n```\nbasta dead code report — 18 findings across 30 files (22.1% of 272 lines)\nConfidence is 0-100; anything below 90 has a listed reason it may be wrong. Verify a finding before deleting the code.\nunused-file typescript/src/legacy-export.ts:1:1 confidence=95 typescript/src/legacy-export.ts is never imported and is not an entry point\nunused-export typescript/src/invoice.ts:21:17 confidence=85 exported function `renderReceipt` is never imported\nunused-symbol typescript/src/invoice.ts:29:10 confidence=90 function `describeTotal` is never used in typescript/src/invoice.ts\nunused-import typescript/src/invoice.ts:2:10 confidence=100 `roundToCents` is imported but never used\n```\n\nThis is a graph traversal from the project's entry points (`package.json`\n`main`/`bin`/`exports`/`scripts`, `pyproject.toml` scripts, framework\nconventions, or `--entry <glob>`), not a reference count — a helper whose\nonly caller is itself dead is reported too, so deleting one finding can make\nanother true that wasn't reported yet. Covers JavaScript, TypeScript, JSX,\nTSX, Vue, Svelte, Astro and Python.\n\n### The four categories, and what to do with each\n\n| Category | Meaning | Usual fix |\n|----------|---------|-----------|\n| `unused-file` | Never imported anywhere, not an entry point | Delete the file |\n| `unused-export` | Exported, but no other file imports it | Delete it, or drop the `export` keyword if something in the same file still uses it |\n| `unused-symbol` | A module-private declaration nothing in its own file references | Delete it |\n| `unused-import` | Imported but never referenced in the file | Delete the import line |\n\n### Workflow\n\n1. Run with `--reporters ai` (add `--dead-code-categories` to focus on one\n   category, `--include-tests` if test-only usage shouldn't count as \"used\",\n   `--include-entry-exports` to also flag an entry file's own unused\n   exports, normally excluded since its whole surface is the public API)\n2. Sort by confidence, high to low. Everything is 0-100; below 90 the finding\n   carries a stated reason it might be wrong — `eval`/`getattr` calls,\n   decorators the analyzer doesn't recognize, a wildcard re-export, the name\n   showing up only inside a string literal, or a file elsewhere that failed\n   to parse (see `statistics.unparsedFiles` in the JSON report). Read the\n   reason before deleting; `--min-confidence` (default 60) only sets the\n   floor for what's reported, it doesn't make a low-confidence finding safe\n3. For each finding above your comfort threshold, confirm it by hand — grep\n   the symbol name across the repo (dynamic access, a string-based router, a\n   template file jscpd doesn't parse can all hide a real use) — then apply\n   the fix from the table above\n4. Run the project's own test suite and build after each batch of deletions,\n   not only jscpd's re-run: dead-code analysis proves nothing *reaches* the\n   code from an entry point, not that removing it can't break a build step\n5. Re-run `--dead-code` after a batch: a file whose only import was the one\n   you just deleted may now be dead itself — that cascade is expected, keep\n   going until a clean pass, not just once\n6. Report what you skipped and why (low confidence, a reason you couldn't\n   rule out, or intentionally-kept dead code such as a public library export)\n   separately from what you actually removed\n\n## Step 3 — Big and complex files\n\n```bash\nnpx jscpd --complexity --reporters ai --summary-top 10 <path>\n# or, ranked alongside duplication and size:\nnpx jscpd --reporters ai --summary --summary-by complexity <path>\n```\n\n```\nComplexity by complexity (6 files, 6 folders):\nfiles (tokens/lines/size/cx):\nc/checkout.c 176/32/710/13\nrust/status.rs 112/24/481/9\nswift/Profile.swift 88/21/446/8\n```\n\n`cx` is a language-aware cyclomatic-complexity estimate — roughly one path\nper function plus one per branch (`if`, loops, `&&`/`||`, `match`/`switch`\narms, the ternary and Elvis operators, each counted the way the language\nactually spells it) — read as a ranking signal, not an exact metric. Prose\nand data files (Markdown, JSON, YAML, lock files, …) always score `0`; a\nlarge README is not a refactoring target. jscpd's own health score treats a\nfile with complexity **50 or higher** as \"complex\" (the threshold its\n`dead code`-style half-life curve is calibrated against) — a reasonable\ndefault cutoff for \"worth reading\" if the summary doesn't make an obvious\none clear.\n\n### Workflow\n\n1. Take the top N files by complexity (and, from the `--summary` form, by\n   size — a huge low-complexity file, e.g. a big data table, is a different\n   problem than a small high-complexity one)\n2. Read each file and name the actual driver: deep nesting, a long function\n   doing several unrelated things, a large `switch`/`if`-chain over a type\n   or state, or a file that is really several modules glued together\n3. Pick the matching strategy:\n   - **Deep nesting** → guard clauses / early return, flatten the happy path\n   - **Long function, several responsibilities** → extract function per\n     responsibility, named for what it does\n   - **Branching over a fixed set of cases** (type, enum, status) →\n     replace with a lookup table/map, or polymorphism if each case already\n     has its own type\n   - **File doing too much** → split along its actual seams (one export\n     group per file), not an arbitrary line-count cut\n4. Refactor one file at a time; keep behavior identical — this is a\n   complexity pass, not a rewrite. If a duplication clone (step 1) or a dead\n   branch (step 2) turns up while reading, still handle it in its own step\n   so the reason for each change stays traceable\n5. Re-run `--complexity` on the touched files to confirm `cx` actually\n   dropped — a split that only moves code around without reducing branching\n   per function didn't fix anything, it just renamed the problem\n\n## Step 4 — Confirm the improvement\n\n```bash\nnpx jscpd --health --reporters ai <path>\n```\n\nCompare the score and grade to the Step 0 measurement. A dimension that got\nworse (duplication crept back in while extracting a shared complexity fix,\nsay) is a signal to revisit that step, not just note it — re-run the\nrelevant pass (`--summary`, `--dead-code`, `--complexity`) rather than only\ntrusting the one aggregate number.\n\n## Tips\n\n- Don't run all three passes on a codebase you've never seen before without\n  reading `--dashboard` first — on a project where only one dimension is\n  actually bad, spending equal effort on all three wastes time on the two\n  that are already fine\n- A `--health-input` file (coverage, security scan results) can be layered\n  onto the same score if the project already tracks those; it doesn't change\n  anything about this workflow, it just adds more dimensions to Step 0\n- Health scores from two runs are only comparable when built from the same\n  dimensions — if a dimension went from `n/a` to measured (or the reverse)\n  because the file mix changed, say so rather than reading the raw score\n  delta as improvement or regression\n- Commit each step separately (duplication fixes, dead-code removal,\n  complexity simplification) — a reviewer verifying \"did this actually\n  reduce X\" wants a diff scoped to X\n\n<!-- File: skills/compare-codebases/SKILL.md (Tokens: ~3162 | Category: anthropic-skill) -->\n---\nname: compare-codebases\ndescription: Compare two folders function by function with jscpd --compare, in the same language or across languages, and explain the result. Use when asked how two codebases relate, which functions one implementation has and the other lacks, how far a port has come, whether the iOS and Android versions of an app match, or which function in one folder corresponds to a function in the other.\n---\n\n# compare-codebases\n\n`jscpd --compare A B` pairs every function of folder `A` with the function of folder `B` that does the same job, and lists the functions of each folder that have no counterpart. The two folders may be in one language or in two (Python and TypeScript, Kotlin and Swift, Java and Rust). This skill explains how the comparison works and how to run one well. For porting code with the comparison as the progress measure, use the [code-migration](../code-migration/SKILL.md) skill; for the rest of jscpd, the [jscpd](../jscpd/SKILL.md) skill.\n\n## How the comparison works\n\njscpd finds the functions of JavaScript, TypeScript, JSX, TSX, Vue, Svelte, Astro, Python, Rust, Go, Java, Kotlin, C#, C, C++, PHP, Ruby, Scala and Swift files in both folders. It turns the code of each function into a vector with a code embedding model that runs inside jscpd (CodeRankEmbed by default), so two functions that do the same job point the same way even when their languages, names and structure differ. Functions of one folder are never compared with each other.\n\nFunctions pair in two steps:\n\n1. By code. Two functions pair when each is the other's closest match in the other folder, their cosine similarity reaches the model's threshold (0.4125 across languages and 0.6375 within one language, with CodeRankEmbed), and the similarity stands out from the function's other matches. A function close to the best one also pairs when it reaches a higher bar, so a feature written twice on one side gets two pairs. A pair stays only when it is the best match of at least one of its two functions, so a lookalike from another class does not join a function and its counterpart; a tie goes to the file of the same name. Functions shorter than `--min-tokens` (30 with `--compare`) or `--min-lines` (5) stay out of this step, because a short function resembles too many others.\n2. By name. A function left over pairs with a function of the other folder under the same name, once case, underscores, spaces and punctuation are ignored (`encodeBinary`, `encode_binary`, `_encode_binary`; the test title `rounds cents` and `rounds_cents`), when their similarity reaches the `medium` level (0.5625 across languages with CodeRankEmbed). A name pair skips the closest-match and stand-out checks of the first step, so it needs more than that step's threshold; otherwise every `load` and `init` of two codebases would pair. Size does not matter here, so a short port is found. A name pair has to stay within modules the first step linked. A module is the folder right under the deepest folder all files of a side share (`notification` in `android/notification/…`), and a file that sits higher than the rest, such as a build script, does not move that folder up. Two modules link when one holds the most of the other's pairs, counted for code and for tests separately. So `checkPermissions` of one plugin does not pair with its namesake in another.\n\nEach pair gets a level on the scale of the model, because a cosine that is high for one model is low for another:\n\n| Level | With CodeRankEmbed, across languages | Meaning |\n|---|---|---|\n| `high` | 0.7125 and up | almost always the same function |\n| `medium` | 0.5625 to 0.7125 | usually the same function, restructured |\n| `low` | 0.4125 to 0.5625 | read both: related code pairs here too |\n\njscpd measures tests and code apart, in two blocks of the report, and pairs a test only with a test. It tells a test by the conventions of its language:\n\n- a test file such as `*_test.go`, `test_*.py`, `*.test.ts`, `*.spec.js`, `*Tests.swift` or `*_spec.rb`;\n- a test folder such as `tests/`, `__tests__/`, `spec/`, `src/test/` (where Java, Kotlin and Scala keep their tests), `MyAppTests/` or `MyApp.Tests.Integration/`, the compared folder's own name included;\n- a Rust function in a `#[cfg(test)]` module or under `#[test]`;\n- a JavaScript or TypeScript test case such as `it('rounds cents', () => …)`.\n\nWhen neither side has a test that counts (see below), the report has one block and no headings.\n\nTotals count the functions of at least `--min-tokens` tokens and `--min-lines` lines; smaller ones appear only as partners. Declarations without a body (TypeScript overloads, the functions of a `.d.ts` file, interface and abstract methods) take no part. Anonymous functions (callbacks, closures) take no part either, except JavaScript and TypeScript test cases. A test case such as `it('rounds cents', () => …)` goes by its title, and so do those written with `test`, `specify`, `fit`, `xit`, `xtest` or `bench`, with `.only`, `.skip` or `.each(table)` after them. Suites and hooks stay anonymous. jscpd does not compare types, constants, SQL or UI markup.\n\n## A way to compare two folders\n\n### 1. Pick the folders\n\n- Point at the code, not the repositories: `app/src/main/java` and `ios/Sources`, not the two repository roots. Build output, vendored code, installed packages and generated files dilute the result, and a vendored dependency tree can turn a run of seconds into one of tens of minutes, since every function in it is embedded. Inside a git repository jscpd skips what `.gitignore` excludes; outside one, or for folders the `.gitignore` misses, pass them with `--ignore` (`--ignore \"**/vendor/**,**/target/**,**/node_modules/**\"`). When a side has no `.gitignore` entries for such folders, suggest them to the user.\n- Two folders are required, and they must not overlap: `app/` and `app/android/` is refused.\n- Keep parallel structures when you can (`ios/<module>` and `android/<module>`). Modules steer the name step, so matching folder names help.\n- For a port, put the source first and the target second, so the first line of the report is the port's progress. For two implementations that both live on, the order does not matter.\n- One run covers tests and code, since the report measures the tests in a block of their own. When the user asks about the code alone, leave the tests out with `--ignore \"**/__tests__/**,**/*.test.*,**/test/**\"`. When they ask about the tests alone, pass the two test folders as the paths, or pick the test files with `--pattern`.\n\n### 2. Get the model\n\nThe first run needs the model (548 MB). Ask the user before downloading it:\n\n```bash\nnpx jscpd --semantic-download\n```\n\njscpd caches the vectors per pair of folders, so later runs embed only the functions whose code changed.\n\n### 3. Read the overview\n\n```bash\nnpx jscpd --compare billing-py/ billing-ts/\n```\n\n```text\n 71% 5 of 7 functions in billing-py/ have a counterpart in billing-ts/\n 80% 4 of 5 functions in billing-ts/ have a counterpart in billing-py/\n\nbilling-py/\n  file         paired  similarity  counterpart\n  billing.py   4 / 5   0.89        billing.ts\n  shipping.py  1 / 2   0.91        shipping.ts\n\nbilling-ts/\n  file         paired  similarity  counterpart\n  billing.ts   3 / 4   0.89        billing.py\n  shipping.ts  1 / 1   0.91        shipping.py\n\nPaired under other names (1):\n  billing-py/                   billing-ts/             similarity\n  billing.py:28 tax_for_region  billing.ts:27 salesTax  0.87 high\n\nOnly in billing-py/ (2):\n  billing.py (1)\n    46  due_date                6 lines\n  shipping.py (1)\n    18  estimate_delivery_days  8 lines\n\nOnly in billing-ts/ (1):\n  billing.ts (1)\n    39  toCurrency  8 lines\n```\n\n- When both folders hold tests, the report has a `Code` block and a `Tests` block, each with everything below. The two top lines of a block give the share of each folder's functions (or tests) that have a counterpart in the other.\n- The file tables give each file's paired functions, the mean similarity of its pairs (with the number of `low` pairs, as in `0.62, 1 low`), and the file on the other side that holds most of its counterparts.\n- \"Paired under other names\" lists the pairs whose names differ even once case and underscores are ignored. A search by name never finds these.\n- \"Only in\" lists the functions with no counterpart, per folder, grouped by file with the number of each file's functions, then each function's first line, name and length.\n- In colour, levels are green (`high`), yellow (`medium`) and red (`low`), and the shares are green when complete, yellow when partial and red when nothing is paired. Pass `--no-colors` when you parse the console output, or read the JSON report instead.\n- If one folder has no functions, the report is one line of totals and `<folder> has no functions yet`. Check the path and the languages before concluding anything else.\n\n### 4. Check the pairs\n\n```bash\nnpx jscpd --compare billing-py/ billing-ts/ -r console-full\n```\n\n`console-full` adds every pair with its similarity and level, and marks the pairs found by name. Before reporting the comparison as reliable, read a sample:\n\n- two or three `high` pairs, to confirm the pairing works on this code;\n- every `low` pair, since related code pairs there too (a client call and the endpoint it calls, a function counting UTF-8 bytes and one converting a string to them);\n- the pairs marked `by name`, since a same-named function can do something else.\n\nWhen a sample pair is wrong, say which one and why.\n\n### 5. Check the functions with no counterpart\n\nA function in an \"Only in\" list is either missing on the other side, or its counterpart was not recognized. Before calling it missing, search the other folder: in its counterpart file, under other names, as part of a larger function, or replaced by a library or platform call. The known gaps in pairing:\n\n- constructors across languages (a Java constructor and Rust's `new`, Kotlin's `constructor`, Swift's `init`) pair only when their code is similar enough;\n- a short function renamed on the other side (`add_history` for `_finder_penalty_add_history`);\n- one function split into several, or several merged into one: only the closest part may pair.\n\nFunctions that exist on one side only by design are expected: platform glue (an iOS delegate callback, an Android notification channel), helpers a language needs and another does not, features one side dropped.\n\n### 6. Report\n\nFor the user, summarize in a few lines: the two percentages, the notable renamed pairs, the `low` pairs you checked and what you found, and the real gaps on each side, grouped by file or module. For a person who wants to explore the result, write the migration map, `-r html`: one offline page that draws both sides as dependency graphs with the pairs bridging them, and lists the same pairs as a table on a second tab. For a document or an issue, write the Markdown report and attach or paste it:\n\n```bash\nnpx jscpd --compare billing-py/ billing-ts/ -r markdown -o .jscpd-compare\n```\n\nFor your own processing, read the JSON report (`-r json`, written to `jscpd-compare.json`). It has a `code` and a `tests` section of the same shape. Each has `sides[0]` and `sides[1]`, each with `path`, `functions`, `matched`, `percentage`, `files` (`file`, `functions`, `matched`, `counterpart`, `similarity`, `lowPairs`) `unmatched` (`file`, `name`, `start`, `end`) and `readyToPort` (the unmatched functions whose callees all have a counterpart, each with its number of `callers`), and `pairs`, each with `a`, `b`, `similarity`, `level`, `renamed` and `matchedBy`. Paths are relative to each folder. Write reports outside the repository or add the folder to `.gitignore`.\n\n## Options that change the result\n\n- `--min-tokens` and `--min-lines` set which functions count. Raise them to focus on substantial functions.\n- `--ignore` and `--format` narrow the files, for example to leave out tests or build scripts.\n- `--semantic-model` picks another model, and `--semantic-url` an OpenAI-compatible embeddings API; `npx jscpd --semantic-models` lists the models with calibrated thresholds. `--semantic-threshold` and `--semantic-same-threshold` move the thresholds; change them only after reading pairs on both sides of the new value.\n- `--semantic-rebuild-cache` embeds everything again, which is needed only when the cache is suspect.\n\nKeep the folders, their order, the options and the model the same when you compare runs over time, or the numbers stop being comparable. The exit code is 0 whatever the result.\n\n## Limits\n\n- Similarity does not see small differences in behavior: two versions that drifted apart still pair, often at `high`. Only tests and reading the code catch drift.\n- The more one side is restructured, the fewer of its functions pair by code.\n- Only functions are compared.\n- The model runs on the CPU. The first run embedded about 25 functions a second on an Apple M1 (261 functions in 11 seconds) and can be several times slower on a small CI machine; later runs reuse the cache.\n\n<!-- File: skills/dry-refactoring/SKILL.md (Tokens: ~1962 | Category: anthropic-skill) -->\n---\nname: dry-refactoring\ndescription: Guided workflow to eliminate copy-paste duplication detected by jscpd. Refactor exact, renamed and near-miss clones using extract function, parameterize, module, constant, or base class strategies, starting from the hotspots the summary ranks.\n---\n\n# dry-refactoring\n\nGuided workflow to eliminate copy-paste duplication in source code. Use after running [jscpd](../jscpd/SKILL.md) to detect clones.\n\n## Prerequisites\n\nFirst, run jscpd to identify duplications:\n\n```bash\nnpx jscpd --reporters ai <path>\n```\n\nIn codebases that mix related formats (e.g. JavaScript and TypeScript), add `--cross-formats` so clones spanning both are detected too:\n\n```bash\nnpx jscpd --reporters ai --cross-formats \"js-ts\" <path>\n```\n\nOn larger codebases, add `--summary` to get a refactoring-hotspot overview alongside the clone list — top files and folders with a `dup%` column showing how much of each file is duplicated:\n\n```bash\nnpx jscpd --reporters ai --summary <path>\n```\n\nThe default scan reports only **exact** copies, and those are the ones to refactor first: an exact clone is almost always a real copy-paste. Two more passes find copies that were edited after pasting. They are **noisier**: they ignore names, values or a few statements on purpose, so they also surface blocks that merely look alike (models and DTOs, config tables, test setup, generated code, shared idioms). Run them only after the exact clones are dealt with, one family at a time, with tight settings, and treat what they report as leads to read rather than defects to fix:\n\n```bash\n# Type-2: renamed copies (other variable names, other constants), reported as \"(renamed)\".\n# Raise --min-tokens: with identifiers ignored, a short block is mostly placeholders.\nnpx jscpd --reporters ai --ignore-identifiers --min-tokens 70 <path>\n\n# Type-3: near-miss copies (one or two edited lines, or JS/TS/Python functions with the same structure),\n# reported as \"[~0.91 gap]\" and \"[~0.85 ast]\". Widen only if the tight run finds nothing.\nnpx jscpd --reporters ai --max-gap-lines 1 --similarity <path>\n```\n\nSee the **[jscpd](../jscpd/SKILL.md)** skill for full option reference, including cross-format group syntax, the clone-kind suffixes and how to read the summary.\n\n## Workflow\n\n1. Run jscpd with `--reporters ai` on the target path (add `--summary` on larger codebases to pick a starting point: files with high `dup%` and high token counts pay off most)\n2. Parse each clone line to identify the two duplicated locations (file + line range) and its kind: no suffix is an exact copy, `(renamed)` differs only in names or values, `[~N gap]` has a few edited lines in the middle, `[~N ast]` is a function pair with the same structure\n3. Read both code fragments from the source files\n4. Understand what the duplicated code does, and for renamed and similar clones list exactly what differs between the two sides\n5. **Triage renamed and similar clones before touching them.** Skip the pair, and say so, when any of these holds: the two sides do different things despite the same shape (a `switch` over different enums, two reducers with unrelated semantics); the sameness is intentional boilerplate (models, DTOs, config, route tables, test fixtures); the code is generated; a shared abstraction would need a vague name like `processData`; or the pair is under about 10 lines. Only a pair that would let you delete code and give the extraction a precise name goes on to the next step\n6. Design a refactoring: extract a shared function, class, module, or constant; the kind decides the strategy (below)\n7. Apply the refactoring — update both locations and all other usages\n8. Re-run jscpd **with the same flags** to confirm the clone is eliminated and the `dup%` of the touched files went down; a clone that was `(renamed)` will not show in a default run, so check with `--ignore-identifiers` again\n9. Repeat for remaining clones, highest-impact first: exact clones, then renamed, then similar. Report the skipped candidates separately from the refactored ones, with the reason, so nobody mistakes a normalized run's count for real duplication\n\n## Refactoring Strategies\n\n**Extract function** — when the duplicate is a block of logic:\n```ts\n// Before: same block in two places\n// After: shared function called from both places\n```\n\n**Extract module/utility** — when the duplicate spans multiple files in different domains:\n```ts\n// Move shared logic to a shared utility file and import it\n```\n\n**Extract constant or config** — when the duplicate is repeated data or configuration.\n\n**Template/base class** — when the duplicate is structural (e.g., repeated class shape).\n\n**Parameterize** — for `(renamed)` clones. The two sides are the same algorithm over different names or values, so the things that differ become parameters:\n```ts\n// Before: computeCartTotal(items) and computeBasketTotal(entries), same body, other names;\n//         limits-dev.js and limits-prod.js, same shape, other numbers\n// After: one function whose parameters are the identifiers that differed,\n//        or one function reading the values that differed from a config object\n```\nA renamed clone whose only difference is a literal is a missing constant or config entry, not a missing function.\n\n**Unify near-miss copies** — for `[~N gap]` clones. Read the unmatched lines: the gap is the one place the copies diverged, typically a guard, a log call or an extra field. Extract the common body and pass the divergence in:\n```ts\n// Before: saveUser and saveAccount, identical except one inserted validation line\n// After: one saveRecord(record, { validate }) with the inserted line behind the option,\n//        or the inserted line moved to the caller before the shared call\n```\nIf the gap changes the meaning rather than adding a step, keep two functions but extract the shared halves.\n\n**Merge similar functions** — for `[~N ast]` clones. The structure matches but names, literals and some statements do not. Diff the two functions first; the `ast` score tells how much is shared (`0.9` is a copy with one edit, `0.75` a copy with a couple of added statements plus renames). Extract the shared skeleton and inject what differs, as arguments, a strategy object, or a callback:\n```ts\n// Before: buildInvoice(order, customer, taxRate) and buildCreditNote(refund, account, vatRate):\n//         same loop, same rounding, one extra guard and one extra log call in the second\n// After: buildDocument(source, party, rate, { filter, onBuilt }) used by both\n```\nBelow about `0.8` the pair usually shares an idiom, not an implementation; leave those alone unless the summary shows the file is a hotspot anyway.\n\nAlways ensure:\n- All call sites are updated, not just the two reported by jscpd\n- Tests still pass after refactoring\n- The extracted abstraction has a clear, descriptive name\n- The re-run uses the same detection flags as the run that found the clone\n\n## Tips\n\n- Start with clones that have the highest line count — they have the most impact\n- Use the summary's `dup%` column to order the work: a large file with a high share of duplicated lines pays back first\n- A clone between test files may indicate a missing test helper\n- Clones across unrelated modules may signal a missing shared utility\n- A cross-format clone (same logic in a `.js` and a `.ts` file, found with `--cross-formats`) often means code was ported without deleting the original — consolidate into one implementation (usually the TypeScript one) and update imports, rather than extracting a third shared copy\n- Many `(renamed)` clones in one file usually mean one abstraction is missing, not many: look for the shared shape before extracting pair by pair. Many `(renamed)` clones across *test* files usually mean nothing: test cases are supposed to look alike\n- Do not gate CI (`--threshold`, `--fail-on-new-clones`) on the Type-2/Type-3 passes until the team has reviewed what they report on this codebase; gate on the exact run\n- `--similarity` covers JavaScript, TypeScript, Python, Java, Kotlin, Scala, C#, Go, Rust, C, C++, PHP, Ruby, Swift and Clojure, also inside Markdown code blocks and Vue, Svelte and Astro scripts; for other languages rely on the exact and `--max-gap-lines` passes\n- Use `--min-lines 10` to filter noise and focus on meaningful duplications\n- Keep a separate `--baseline` per set of detection flags when gating CI: renamed and similar runs fingerprint clones differently from exact runs\n\n<!-- File: skills/jscpd/SKILL.md (Tokens: ~4762 | Category: anthropic-skill) -->\n---\nname: jscpd\ndescription: Copy-paste detector for 220+ languages. Detect exact, renamed and near-miss duplicated code, measure duplication percentages, and find refactoring hotspots with the codebase summary.\n---\n\n# jscpd\n\nCopy-paste detector for programming source code, supports 220+ languages. Use this skill to run jscpd and understand its output.\n\n## Quick Start\n\n```bash\n# Run with ai reporter (compact output optimized for agents)\nnpx jscpd --reporters ai <path>\n\n# With ignore patterns\nnpx jscpd --reporters ai --ignore \"**/node_modules/**,**/dist/**\" <path>\n\n# Scope to specific formats\nnpx jscpd --reporters ai --format \"javascript,typescript\" <path>\n\n# Second pass, noisier: copies that only differ in names and values (Type-2, \"renamed\").\n# Review each hit before acting on it.\nnpx jscpd --reporters ai --ignore-identifiers --min-tokens 70 <path>\n\n# Third pass, noisier still: copies with a few edited lines or the same function\n# structure (Type-3, \"similar\"). Keep the settings tight.\nnpx jscpd --reporters ai --max-gap-lines 1 --similarity <path>\n\n# Where to refactor first: clone list plus a hotspot summary\nnpx jscpd --reporters ai --summary <path>\n\n# Most complex files only, without clone detection\nnpx jscpd --complexity --reporters ai <path>\n```\n\n## AI Reporter Output Format\n\nThe `ai` reporter produces compact, token-efficient output designed for agent consumption:\n\n```\nClones:\nsrc/ foo.ts:10-25 ~ bar.ts:42-57\nsrc/utils/helpers.ts:100-120 ~ src/utils/other.ts:5-25\nsrc/cart/ basket.js:1-9 ~ cart.js:1-9 (renamed)\nsrc/api/ save-account.js:1-12 ~ save-user.js:1-11 [~0.91 gap]\nsrc/billing/ orders.ts:1-14 ~ refunds.ts:1-15 [~0.85 ast]\n---\n5 clones · 4.2% duplication\n```\n\nEach line represents one clone pair:\n- **Same file**: `path/file.ts 10-25 ~ 45-60` (shared path shown once)\n- **Same directory**: `shared/prefix/ file-a.ts:10-25 ~ file-b.ts:42-57` (common prefix factored out)\n- **Different paths**: `path/a.ts:10-25 ~ path/b.ts:42-57`\n\nA suffix tells the **kind** of clone; no suffix means an exact copy:\n- `(renamed)`: the two blocks differ only in identifier names, literal values or annotations (Type-2). Only appears with `--ignore-identifiers`, `--ignore-literals` or `--ignore-annotations`.\n- `[~0.91 gap]`: two exact clones merged across up to `--max-gap-lines` unmatched lines (Type-3). The number is matched tokens over the merged span.\n- `[~0.85 ast]`: two functions whose normalized syntax trees share at least `--similarity` of their subtrees (Type-3). The number is that share. The names of called functions and methods and the operators count, local names and literal values do not, so a renamed copy with other constants scores 1 and a copy that calls other methods scores lower. A name still has to stand for one name on the other side: a copy that swaps the roles of two names scores lower.\n\n## Options\n\n| Option | Description |\n|--------|-------------|\n| `--reporters ai` | Use the AI-optimized reporter (compact clone list for agents) |\n| `--reporters html` | Generate HTML report |\n| `--reporters json` | Output JSON report |\n| `--min-tokens N` | Minimum tokens to consider a duplication (default: 50) |\n| `--min-lines N` | Minimum lines to consider a duplication (default: 5) |\n| `--threshold N` | Exit with error if duplication % exceeds N |\n| `--ignore \"glob\"` | Ignore patterns (comma-separated) |\n| `--format \"list\"` | Limit to specific languages (e.g. `typescript,javascript`) |\n| `--cross-formats \"groups\"` | Detect clones across related formats (e.g. `javascript,typescript` or the `js-ts` preset) |\n| `--ignore-identifiers` | Treat all identifiers as equal, so blocks that differ only in variable, function or type names match (Type-2, reported as `renamed`); a name must stand for one name on the other side, so look-alikes with another data flow stay out, while a field and a local of one text are two names, so `this.url = url` still matches `this.url = link` (`--ignore-identifiers=blind` matches any names) |\n| `--ignore-literals` | Treat all string literals as equal and all numeric literals as equal (Type-2) |\n| `--ignore-annotations` | Drop `@Name` / `@Name(...)` annotations and decorators before matching, in languages where `@` means one (Type-2) |\n| `--max-gap-lines N` | Merge clones of one file pair separated by at most N unmatched lines into one `similar` clone (Type-3, default: 0 = off) |\n| `--similarity [RATIO]` | Report pairs of functions whose normalized syntax trees share at least RATIO of their subtrees, in `(0, 1]`, as `similar` clones (Type-3, 0.8 without a value). JavaScript, TypeScript, Python, Java, Kotlin, Scala, C#, Go, Rust, C, C++, PHP, Ruby, Swift and Clojure; test files are left out |\n| `--min-nodes N` | The fewest nodes of a normalized syntax tree a function needs for `--similarity` (default: 20) |\n| `--summary` | Append a codebase summary: top files/folders by tokens, lines, size, complexity, with duplication share |\n| `--summary-top N` | Number of entries in each summary top list (default: 10) |\n| `--summary-by metric` | Summary ranking metric: `tokens`, `lines`, `size`, `complexity` (default: `tokens`) |\n| `--kind list` | Report only these clone kinds: `exact`, `renamed`, `similar`, `gap`, `ast` (never enables a detector) |\n| `--complexity` | Complexity tables only, without clone detection (reporters: console, ai, json) |\n| `--dead-code` | Find unused files, exports, module-private declarations and imports — JavaScript, TypeScript, JSX, TSX, Vue, Svelte, Astro, Python. Each finding carries a 0-100 confidence score |\n| `--dead-code-categories list` | Limit `--dead-code`/`--dashboard` to categories: `unused-file`, `unused-export`, `unused-symbol`, `unused-import` |\n| `--min-confidence N` | Floor for `--dead-code`/`--dashboard` findings, 0-100 (default: 60) |\n| `--entry \"glob\"` | Extra entry points for dead-code analysis, beyond what's inferred from `package.json`/`pyproject.toml`/framework conventions |\n| `--include-tests` | Count test-only usage as \"used\" for dead-code analysis (excluded by default) |\n| `--include-entry-exports` | Also flag an entry file's own unused exports (excluded by default: its exports are the public API) |\n| `--dashboard` | One screen: health badge, project size and largest code files, duplication, complexity, dead code (JS/TS/Python); `-r json` writes `jscpd-dashboard.json` |\n| `--health` | Project health badge only: 0-100 score and grade from duplication, dead code and complexity (`-r ai` one line, `-r json`, `-r badge`) |\n| `--health-input FILE` | Add metrics from other tools (coverage, tests, security) to the health score |\n| `--pattern \"glob\"` | Glob pattern to select files |\n| `--no-gitignore` | Do not respect `.gitignore` (it is respected by default) |\n| `--output \"path\"` | Directory to write reports to |\n| `--silent` | Suppress console output (useful with file reporters and `--output`) |\n| `--list` | List all supported formats and exit |\n| `--no-tips` | Disable tips in output (skipped automatically when stdout is not a TTY or `CI` or `JSCPD_NO_TIPS` is set) |\n| `--config \"path\"` | Path to .jscpd.json config file |\n\n## Clone Kinds: Exact, Renamed, Similar\n\nBy default jscpd reports **exact** clones only: the token sequences are identical (whitespace, layout and, depending on the mode, comments do not count). Two opt-in families widen the net. Run them as separate passes after the default scan, because they find more and longer clones and change what a \"clone\" means.\n\n**These passes are noisy by design.** An exact clone is almost always a real copy. A renamed or similar clone is a *candidate*: the flags deliberately ignore the very things (names, values, a statement or two) that often make two blocks different in meaning. Expect false positives from:\n\n- boilerplate that is supposed to look alike: DTOs and models, config tables, enum-like maps, route or handler registrations, builders\n- test files: `describe`/`it` blocks, fixtures and setup code repeat the same shape on purpose\n- generated code, migrations, serializers, protocol bindings\n- language idioms: two `reduce` loops or two `switch` statements that share structure but not logic\n- small blocks: with `--ignore-identifiers` a 50-token block is mostly placeholders, so raise `--min-tokens`\n\nRules that keep the noise manageable:\n\n- Run them **after** the exact clones are handled, one family at a time, so every hit is attributable to one flag.\n- Start conservative: `--ignore-identifiers` alone (add `--ignore-literals` only when constants are the known problem), `--max-gap-lines 1` or `2`, `--similarity 0.85` or higher, and `--min-tokens 70` or more for the identifier pass. Widen only when the tight run comes back empty.\n- Treat every `(renamed)` or `[~…]` line as a lead to read, not a defect to fix. Do not gate CI (`--threshold`, `--fail-on-new-clones`) on these passes unless the team has reviewed what they report on the codebase.\n- Never claim \"N duplicates found\" from a normalized run without saying which flags produced them.\n\n### Type-2: renamed clones (`--ignore-identifiers`, `--ignore-literals`, `--ignore-annotations`)\n\nCopies where someone renamed the variables or changed the constants:\n\n```bash\nnpx jscpd --reporters ai --ignore-identifiers <path>                      # function a(x) {…} matches function b(y) {…}\nnpx jscpd --reporters ai --ignore-identifiers --ignore-literals <path>    # …and 10 matches 25, 'dev' matches 'prod'\nnpx jscpd --reporters ai --ignore-annotations <path>                      # @Override / @Deprecated no longer split a clone\n```\n\n```\nClones:\nbasket.js:1-9 ~ cart.js:1-9 (renamed)\nlimits-dev.js:1-13 ~ limits-prod.js:1-13 (renamed)\n---\n```\n\n- Keywords keep their meaning (`return` never matches `retry`), so structure still has to match.\n- `--ignore-annotations` only acts in Java, Kotlin, Scala, Groovy, Python, Dart, Swift, JavaScript and TypeScript; in Ruby, Perl, T-SQL, Razor and CSS `@` means something else and is left alone.\n- Positions in the report still point at the original source.\n- Config keys: `ignoreIdentifiers`, `ignoreLiterals`, `ignoreAnnotations`.\n\n### Type-3: near-miss clones (`--max-gap-lines`, `--similarity`)\n\nCopies with a few edited lines, or functions rewritten with the same shape:\n\n```bash\nnpx jscpd --reporters ai --max-gap-lines 2 <path>      # a copy with up to 2 inserted/changed lines becomes one clone\nnpx jscpd --reporters ai --similarity <path>           # functions whose normalized syntax trees share ≥82% of their subtrees\n```\n\n```\nClones:\nsave-account.js:1-12 ~ save-user.js:1-11 [~0.91 gap]\norders.ts:1-14 ~ refunds.ts:1-15 [~0.85 ast]\n---\n```\n\n- `--max-gap-lines N` only joins clones the exact run already found, so it removes fragmentation rather than inventing matches; it works in every language. A merge is refused when the gap holds more tokens than the halves share (similarity would drop under `0.5`).\n- `--similarity` normalizes the syntax tree of every function (local names and literals become markers, each name standing for one name on the other side; called names and operators stay) and scores the share of subtrees two trees have in common, so a renamed copy scores `1.0`, one inserted statement in a function of ten about `0.85`, a copy that calls other methods less. It applies to JavaScript, TypeScript, Python, Java, Kotlin, Scala, C#, Go, Rust, C, C++, PHP, Ruby, Swift and Clojure, and to their code blocks in Markdown and in Vue, Svelte and Astro files; other formats are a silent no-op. A function nested in another is part of it, and test files are left out. Start at the default `0.8` and lower to `0.7` only when looking for leads; read both functions before believing a low score.\n- `similar` takes precedence over `renamed` when both apply (a merged clone is no longer identical even after normalization).\n- Config keys: `maxGapLines`, `similarity`, `minNodes`.\n\n### Where the kind shows up\n\n- `console`: `Clone found (javascript, renamed)`, `Clone found (javascript, similar (gap) ~0.91)`, `Clone found (javascript, similar (ast) ~0.75)`.\n- `json`: `\"kind\": \"exact\" | \"renamed\" | \"similar\"`, plus `\"similarity\"` and `\"method\": \"gap\" | \"ast\"` for similar clones, and the size of each normalized tree as `\"nodes\"` in `firstFile` and `secondFile` for ast ones.\n- `sarif`: rules `jscpd/duplicate-code`, `jscpd/renamed-code`, `jscpd/similar-code` (clones merged across a gap), `jscpd/similar-function` (functions and classes paired by `--similarity`) and `jscpd/semantic-code` (`--semantic`); Code Climate uses the same `check_name` values.\n- A default run reports only `exact` clones and its output is unchanged by these features.\n- Normalized runs produce different clone fingerprints than exact runs: keep a separate `--baseline` file per configuration.\n\n## Codebase Summary (`--summary`)\n\n`--summary` appends a refactoring-hotspot overview to the run output — use it to decide **where to refactor first** before diving into individual clones:\n\n```bash\n# Compact clone list + compact summary, optimized for agents\nnpx jscpd --reporters ai --summary --no-tips <path>\n\n# Rank by complexity instead of tokens, top 5 lists\nnpx jscpd --reporters ai --summary --summary-by complexity --summary-top 5 <path>\n```\n\nWith the `ai` reporter the summary is one line per entry:\n\n```\nSummary by tokens (321 files, 129 folders):\nfiles (tokens/lines/size/cx/dup%):\nsrc/files.ts 2052/363/11662/80/0.0%\nlong-line/theme-branded.js 498/15/2.5K/10/93.3%\n...\nfolders (files/tokens/lines/size):\nsrc/core 8/5264/843/27136\n...\n```\n\nHow to read it:\n- **Top files** are ranked by the `--summary-by` metric, but every row carries all metrics — `tokens/lines/size/cx/dup%`.\n- **cx** is a language-agnostic cyclomatic-complexity estimate from the token stream (1 + decision-point tokens like `if`/`while`/`&&`); treat it as a ranking signal, not an exact metric.\n- **dup%** is the share of the file's lines covered by detected clones — a large file with high `dup%` is the best refactoring target. It reflects the clones of the current run, so with the Type-2/Type-3 flags it rises accordingly.\n- **Folders** aggregate files into their direct parent directory (no cumulative ancestor totals).\n- In `console` reporters the summary renders as aligned tables; in the `json` report it appears as an additive `summary` key (absent when the flag is off).\n\nConfig file equivalents: `\"summary\": true`, `\"summaryTop\": 10`, `\"summaryBy\": \"tokens\"`.\n\n## Cross-Format Clone Detection\n\nBy default each format is compared only against itself. `--cross-formats` defines groups of related formats that share one comparison pool, so a block duplicated between a `.js` and a `.ts` file is reported as a clone:\n\n```bash\n# One group: compare JavaScript and TypeScript files together\nnpx jscpd --reporters ai --cross-formats \"javascript,typescript\" <path>\n\n# Preset covering javascript, jsx, typescript, tsx\nnpx jscpd --reporters ai --cross-formats \"js-ts\" <path>\n\n# Multiple groups are separated by \";\"\nnpx jscpd --reporters ai --cross-formats \"javascript,typescript;css,scss\" <path>\n```\n\nNotes:\n- 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)`. Reported positions still reference the original source.\n- Groups need at least two formats; groups sharing a format are merged into one pool.\n- In per-format statistics, a cross-format clone is attributed to one member format of the group.\n- In config files the key is `crossFormats` (or `cross-formats`) and accepts a string (`\"javascript,typescript;css,scss\"`), an array of strings (`[\"javascript,typescript\", \"css,scss\"]`), or an array of arrays (`[[\"javascript\",\"typescript\"],[\"css\",\"scss\"]]`).\n\n## Comparing Two Codebases (`--compare`)\n\n`--compare SOURCE TARGET` pairs the functions of two folders with the `--semantic` embedding model and lists which functions of each side have a counterpart in the other: the progress of a port to another language or framework, or the parity of two implementations of one app. It takes exactly two paths that do not overlap, needs the model once (`--semantic-download`), and runs no clone detection.\n\n```bash\nnpx jscpd --compare python-lib/ rust-lib/                                  # progress of a port and what is left\nnpx jscpd --compare ios/ android/ -r console-full                          # parity, with every pair and its similarity\nnpx jscpd --compare python-lib/ rust-lib/ -r json -o .jscpd-compare --silent  # jscpd-compare.json for an agent\n```\n\nFunctions pair by code first (each is the other's closest match), then by name when the names match once case and underscores are ignored and the code is similar enough. Every pair has its similarity and a level (`high`, `medium`, `low`) on the scale of the model, and the default report lists the pairs under other names on their own. jscpd measures tests and code in two blocks and pairs a test only with a test. `--min-tokens` defaults to 30 here. Reporters: `console`, `console-full`, `json`, `markdown`. See the **[compare-codebases](../compare-codebases/SKILL.md)** skill for how the comparison works and how to check it, and **[code-migration](../code-migration/SKILL.md)** for porting code with it.\n\n## Configuration File\n\nCreate a `.jscpd.json` in your project root:\n\n```json\n{\n  \"threshold\": 0,\n  \"reporters\": [\"ai\"],\n  \"ignore\": [\"**/node_modules/**\", \"**/dist/**\", \"**/*.min.*\"],\n  \"format\": [\"typescript\", \"javascript\"],\n  \"minLines\": 5,\n  \"minTokens\": 50,\n  \"ignoreIdentifiers\": false,\n  \"ignoreLiterals\": false,\n  \"maxGapLines\": 0,\n  \"similarity\": 1,\n  \"summary\": false,\n  \"output\": \"./reports/jscpd\"\n}\n```\n\n## Refactoring Duplicated Code\n\nOnce you've detected clones, use the **dry-refactoring** skill for a guided workflow to eliminate them, with a strategy per clone kind:\n\n→ **dry-refactoring** — step-by-step refactoring strategies and workflow for removing duplication. Install with:\n  ```bash\n  npx skills add https://github.com/kucherenko/jscpd --skill dry-refactoring\n  ```\n\n## Improving Overall Codebase Health\n\nFor a broader \"clean up this codebase\" pass — duplication, then dead code, then the largest/most complex files, prioritized from `--health`/`--dashboard` and re-measured at the end — use **codebase-refactoring**:\n\n  ```bash\n  npx skills add https://github.com/kucherenko/jscpd --skill codebase-refactoring\n  ```\n\n<!-- File: docs/api.md (Tokens: ~888 | Category: project-spec) -->\n# Programming API\n\njscpd is a Rust engine. The CLI ships as the `jscpd` crate on crates.io (installs the `jscpd` and `cpd` binaries) and as the `jscpd` / `cpd` npm packages (prebuilt binaries); the engine itself is a set of library crates you can depend on directly.\n\n## Rust\n\nUse the `cpd-finder` crate — it is the orchestration entry point and pulls in the tokenizer, detection core, and reporters:\n\n```toml\n[dependencies]\ncpd-finder = \"0.1\"\n```\n\n```rust\nuse cpd_finder::orchestrate::{RunConfig, run};\n\nlet config = RunConfig {\n    paths: vec![\"./src\".into()],\n    min_tokens: 50,\n    ..Default::default()\n};\n\nlet result = run(&config).unwrap();\nprintln!(\"Found {} clones\", result.clones.len());\nprintln!(\"Analyzed {} files\", result.statistics.total.sources);\n```\n\nA complete, runnable version is in [`examples/rust-cpd-finder`](../examples/rust-cpd-finder).\n\nSemantic clones come from the `cpd-semantic` crate, which adds a clone pass to the run:\n\n```rust\nuse cpd_finder::orchestrate::{RunConfig, run};\nuse cpd_semantic::{SemanticOptions, SemanticPass};\nuse std::sync::Arc;\n\n// The local model is downloaded once, e.g. with `jscpd --semantic-download`.\nlet options = SemanticOptions::default();\nlet paths = vec![\"./src\".into()];\nlet embedder = cpd_semantic::embedder(&options, &paths, true)?;\n// The thresholds of the rules, on the scale of the options' model.\nlet thresholds = options.thresholds();\nlet config = RunConfig {\n    paths,\n    passes: vec![Arc::new(SemanticPass::new(embedder, thresholds, options.scope))],\n    ..Default::default()\n};\nlet result = run(&config)?;\n```\n\nThe `jscpd` crate's own library target is **not a public API** — it exists to share helpers with the crate's tests and may change in any release. Depend on the engine crates instead.\n\n### Crate Architecture\n\n| Crate | Description |\n|-------|-------------|\n| [`cpd-core`](https://crates.io/crates/cpd-core) | Core data models and hashing (Rabin-Karp rolling hash) |\n| [`cpd-tokenizer`](https://crates.io/crates/cpd-tokenizer) | Source code tokenization (224 formats, uses `oxc_parser` for JavaScript/TypeScript) — pure, no I/O |\n| [`cpd-similarity`](https://crates.io/crates/cpd-similarity) | Structural similarity (`--similarity`): functions from tree-sitter syntax trees and a Clojure reader, the subtree fingerprints of their normalized trees and the exact search for pairs; pure, no I/O |\n| [`cpd-finder`](https://crates.io/crates/cpd-finder) | File walking, orchestration, git blame (`rayon` + `ignore` + `globset`) |\n| [`cpd-reporter`](https://crates.io/crates/cpd-reporter) | Output format rendering (15 reporters) |\n| [`cpd-semantic`](https://crates.io/crates/cpd-semantic) | Semantic clones (`--semantic`, experimental), as a clone pass for `cpd-finder` |\n\nSee [Packages](./packages.md) for versions and paths.\n\n## Other languages\n\nThere is no Node.js (or other language) binding for the v5 engine. From another language, run the binary and read the `json` reporter's output (`--reporters json --output <dir>` writes `<dir>/jscpd-report.json`), or talk to it over the [MCP stdio transport](./ai-ready.md#stdio-transport-rust-v5).\n\nThe TypeScript API of jscpd v4 (`jscpd()`, `detectClones()`, `@jscpd/core`, LevelDB/Redis stores) is maintained on the [`master-v4`](https://github.com/kucherenko/jscpd/tree/master-v4) branch and published as `jscpd@4`.\n\n<!-- File: benchmark/tools/README.md (Tokens: ~560 | Category: plugin-manifest) -->\n# Benchmark Tools\n\nThis directory contains all third-party tools required by the benchmark script. Install each tool by following the instructions below, then run `./benchmark.sh` from the parent directory.\n\n## Quick Install\n\n```bash\ncd tools/\nnpm install\n```\n\nThis installs **jscpd-rs** and **Fallow** (the only tools distributed via npm). The remaining tools must be installed manually.\n\n## Tools\n\n### jscpd@5 (local build)\n\nThe jscpd v5 Rust binary, built from the `rust/` directory in this repository.\n\n```bash\ncd ../../rust/\ncargo build --release\n```\n\nThe binary is expected at `../../rust/target/release/jscpd`.\n\n### jscpd-rs (npm)\n\nInstalled in `tools/node_modules/` via `npm install`. The npm package downloads a prebuilt Rust binary for the current platform.\n\n```bash\nnpm install\n```\n\nBinary: `node_modules/.bin/jscpd-rs`\n\n### Fallow dupes\n\nInstalled in `tools/node_modules/` via `npm install`. Fallow is a Rust-based code intelligence tool; the `dupes` subcommand performs structural clone detection for TypeScript/JavaScript projects.\n\n```bash\nnpm install\n```\n\nBinary: `node_modules/.bin/fallow`\n\n> **Note:** Fallow must run from inside the target directory (it has no path argument). The benchmark script `cd`s into the fixtures directory before invoking it.\n\n### PMD CPD\n\nInstall via Homebrew (macOS) or download from [pmd.github.io](https://pmd.github.io).\n\n```bash\nbrew install pmd\n```\n\nPMD requires a Java runtime (8+). The `pmd` command must be on `$PATH`.\n\n> **Note:** The benchmark runs PMD CPD for all 34 supported languages individually and sums the results. This takes ~30 seconds.\n\n### Duplo\n\nA prebuilt macOS arm64 binary is included at `tools/duplo`. To replace it:\n\n1. Download from [github.com/dlidstrom/Duplo/releases](https://github.com/dlidstrom/Duplo/releases)\n2. Place the binary at `tools/duplo`\n3. `chmod +x tools/duplo`\n\n```bash\n# Verify\n./duplo -help\n```\n\n### Simian\n\nA prebuilt JAR is included at `tools/simian.jar`. To replace it:\n\n1. Download from [simian.quandarypeak.com](https://simian.quandarypeak.com)\n2. Place the JAR at `tools/simian.jar`\n\nRequires a Java runtime:\n\n```bash\njava -jar tools/simian.jar -help\n```\n</yakaai_skills>"}