requesting-code-review
OBobra/superpowers299k2,751 new stars in the last 7 days
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.
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 skill; for the rest of jscpd, the jscpd skill.
jscpd 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.
Functions pair in two steps:
--min-tokens (30 with --compare) or --min-lines (5) stay out of this step, because a short function resembles too many others.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.Each pair gets a level on the scale of the model, because a cosine that is high for one model is low for another:
| Level | With CodeRankEmbed, across languages | Meaning |
|---|---|---|
high | 0.7125 and up | almost always the same function |
medium | 0.5625 to 0.7125 | usually the same function, restructured |
low | 0.4125 to 0.5625 | read both: related code pairs here too |
jscpd 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:
*_test.go, test_*.py, *.test.ts, *.spec.js, *Tests.swift or *_spec.rb;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;#[cfg(test)] module or under #[test];it('rounds cents', () => …).When neither side has a test that counts (see below), the report has one block and no headings.
Totals 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.
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.app/ and app/android/ is refused.ios/<module> and android/<module>). Modules steer the name step, so matching folder names help.--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.The first run needs the model (548 MB). Ask the user before downloading it:
npx jscpd --semantic-download
jscpd caches the vectors per pair of folders, so later runs embed only the functions whose code changed.
npx jscpd --compare billing-py/ billing-ts/
71% 5 of 7 functions in billing-py/ have a counterpart in billing-ts/
80% 4 of 5 functions in billing-ts/ have a counterpart in billing-py/
billing-py/
file paired similarity counterpart
billing.py 4 / 5 0.89 billing.ts
shipping.py 1 / 2 0.91 shipping.ts
billing-ts/
file paired similarity counterpart
billing.ts 3 / 4 0.89 billing.py
shipping.ts 1 / 1 0.91 shipping.py
Paired under other names (1):
billing-py/ billing-ts/ similarity
billing.py:28 tax_for_region billing.ts:27 salesTax 0.87 high
Only in billing-py/ (2):
billing.py (1)
46 due_date 6 lines
shipping.py (1)
18 estimate_delivery_days 8 lines
Only in billing-ts/ (1):
billing.ts (1)
39 toCurrency 8 lines
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.low pairs, as in 0.62, 1 low), and the file on the other side that holds most of its counterparts.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.<folder> has no functions yet. Check the path and the languages before concluding anything else.npx jscpd --compare billing-py/ billing-ts/ -r console-full
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:
high pairs, to confirm the pairing works on this code;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);by name, since a same-named function can do something else.When a sample pair is wrong, say which one and why.
A 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:
new, Kotlin's constructor, Swift's init) pair only when their code is similar enough;add_history for _finder_penalty_add_history);Functions 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.
For 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:
npx jscpd --compare billing-py/ billing-ts/ -r markdown -o .jscpd-compare
For 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.
--min-tokens and --min-lines set which functions count. Raise them to focus on substantial functions.--ignore and --format narrow the files, for example to leave out tests or build scripts.--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.--semantic-rebuild-cache embeds everything again, which is needed only when the cache is suspect.Keep 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.
high. Only tests and reading the code catch drift.OBobra/superpowers299k2,751 new stars in the last 7 days
OBobra/superpowers299k2,751 new stars in the last 7 days
OBobra/superpowers299k2,751 new stars in the last 7 days
MAmattpocock/skills291k10,101 new stars in the last 7 days
MAmattpocock/skills291k10,101 new stars in the last 7 days
MAmattpocock/skills291k10,101 new stars in the last 7 days