Repository navigation
Detection quality: SQL and JS/JSX support, optional embeddings, fragments/find/explain, eval harness - #10
Merged
Conversation
…pport, OpenAI-compatible embedding auth Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…tatements, literals Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…el comparison Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…nchmark Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… + literals); prefilter keeps all same-family candidates when structural weight is 0 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…nents Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…n file units Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…n file units Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…n file units Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…o function-level scoring keeps name/callee signals Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…re-implementations, renormalize weights over applicable features Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…pies profile scores exactly as before Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…edding server Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…alization; skip embed when a vector is missing Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…red tpr_at_fpr, dedup stats printing Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…rms, SQL name helper) and eval scripts Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…h an evaluation Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…nchmark Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…tween similar units Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…rver loads the requested model Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… FPR; MiniLM end-to-end result Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…in README Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…cific GPU script Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This was referenced Oct 7, 2026
Open
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
dominikpeter
added a commit
that referenced
this pull request
Oct 7, 2026
Fix the findings of the code review of #10 (security, correctness, performance, docs)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Refs #3 (Test Embeddings): this PR adds optional code embeddings and the measurements behind them (embedder bake-off, end-to-end results, find-by-description); see
eval/REPORT.md.Related issues: #1 (the benchmark added here was used to measure the comprehension-normalization question), #2 (the harness discovers new models by directory), #4 (ships through the existing auto-release flow).
Summary
Makes the detector measurably better at finding independently written re-implementations, adds SQL and
JavaScript/JSX support, adds three new commands (
fragments,find,scan --explain), and adds optionalcode embeddings (off by default, any provider). Everything is backed by a new labeled benchmark
(
duplicatecode eval-groups) with separate dev and holdout sets. The defaultcopiesprofile is not retuned.4 commits (engine, CLI, eval, docs), about +3,400 / -100 lines. The original chronological experiment log was
squashed into these; what was tried, what failed and why is recorded in
eval/REPORT.md.What we built
Detection quality (no embeddings)
Weightsgainsfile,sql,renormalize): SQL is scored on loosen-grams, referenced tables/columns and literals; whole-file units get weights without a name term; function
units keep the name-aware weights. Features absent on both sides (no literals/calls) are renormalized away in
the
reimplprofile, so identical structure without literals is not penalized.Idf,weighted_jaccard) and a newcontainmentfeature in the vector.dropped, a once-called
main()wrapper is inlined,print/consoleoutput is kept (it is the program'sresult in a script),
sys.setrecursionlimitandinput = sys.stdin.readlinelines are ignored.;and block braces no longer change tokens.copieskeeps 0.6 / 0.4 / 0.45 (scan/diff/review);reimplnow defaults to 0.35 / 0.28 / 0.30 (+0.07 with embeddings), chosen from measured false-positive rates.
New languages
tree-sitter-sequel: one unit per statement, clause-level statements, keywords case-folded,aliases/CTE names abstracted, table/column/function names kept.
.js .mjs .cjs .jsx) handled by the existing TypeScript/TSX grammars.New commands and options
duplicatecode fragments <paths>: copied blocks (4+ identical normalized statements,--min-stmts,--min-tokens) inside different functions; nested units and constructors are excluded.duplicatecode scan --pairs --explain: per pair, the literals/calls only one side has and the source lineswith no counterpart.
duplicatecode find "<description>" <paths>: ranks existing units by embedding similarity to a plain-languagedescription ("does this already exist?").
--embed <preset>(minilm,qwen3,potion,openai,cohere),--embed-code,--embed-weight,--embed-max-chars.Optional embeddings (off unless requested)
(native
/v2/embed). Vectors cached per unit text and model; cache key is a stable FNV-1a hash.eval/embed_server.py: small local OpenAI-compatible server aroundembed-anything(CPU), loads whichevermodel a request names, so cached vectors cannot mix models.
Evaluation harness (
eval/, seeeval/README.mdandeval/REPORT.md)eval-groups: AUC, TPR at 1%/5% FPR, top-1 and score-at-fixed-FPR for labeled clone groups, dev and holdout.sqlglotrewrites/edits), React(real TSX components + mutations), function-level LLM re-implementations (the repo's
dataset/, split bytask parity). Fetch/build scripts and an embedder bake-off (
embed_bench.py) are included.What we tested, and the results
Metric: mean of
(AUC + TPR@1%FPR) / 2for the combined score; each set has dev (tuned on) and holdout(disjoint problems/queries/components/tasks, except CodeNet: its dev and holdout were only separated by a
random seed and overlap, 13 of 60 Python and 4 of 60 JavaScript problems; see the note below the table).
"Before" for the function-level sets is the original weights on the current normalizer (the original build
cannot run the new benchmark), so the true baseline is somewhat lower. SQL "before" is the new parser with the
old weights. SQL and React are mutation-based and saturate; they are guards, not evidence of independent
re-implementation. More than 90% of positives in the CodeNet and function-level sets are independent solutions
(under 50% 4-gram overlap), so those numbers are not inflated by near-copies.
Optional embeddings (MiniLM,
--embed minilm, weight 0.35), through the Rust path: function-level Python0.698 / 0.706 to 0.811 / 0.791, TypeScript 0.823 / 0.728 to 0.836 / 0.901. Embedder bake-off on the
clean function-level and SQL data (
eval/REPORT.md): all-MiniLM-L6-v2 +0.083 mean gain, Qwen3-Embedding-0.6B+0.081 (about 5x slower on CPU), bge-small +0.052, bge-base +0.042, jina-v2-base +0.018, potion-base-8M +0.013.
Several models (ModernBERT, bge-m3, jina code models) do not load in
embed-anything. CodeNet embedding scoresare not quoted: likely training-set contamination.
New commands
fragments: injection benchmark (renamed blocks of real functions pasted into others). Recall at the default4 statements / 30 tokens: blocks of 6: Python 0.93, TypeScript 0.975; blocks of 8: 0.97 / 0.97; blocks of 4:
0.80 / 0.84 (the old 5/40 setting found only 0.43 / 0.33). Sampled reports on a real repo were genuine
duplicates. A bug found by reading the output (a one-line comprehension counted as a 4-statement block) is
fixed and covered by a test.
find: 50 task descriptions over 168 implementation units, right unit ranked first 94% (MiniLM) / 96%(Qwen3), recall@3 100%; with 1,379 unrelated real units added: 82% first, 96% in top 3, 100% in top 10
(MiniLM).
0.34; with MiniLM 0.33 / 0.42).
Engineering checks
main()inlining, script noise, cache/embeddingpipelines incl. Cohere request/response shapes, credential and key selection, cache recovery, fragments,
explain, prefilter agreement with brute force for every profile, deeply nested input).
cargo fmt --check,cargo test --workspace --locked, and the CI wheel smoke test (maturin build, install,scan dataset) passlocally on Linux.
bench(function-level LLM benchmark) compared against a build ofmain: combined top-1 improveson most rows (Python strict 94% -> 99%, strict
hard 79% -> 83%; TypeScript strict 95% -> 97%, stricthard 85% ->88%); a few hard rows are 2-3 points lower (Python hard
hard 70% -> 67%, loosehard 70% -> 67%; TypeScripthard~hard 85% -> 83%).
bench.rsand the cache loader are untouched).mainvs this branch: default scan 4.7 s -> 4.0 s,scan --min-name 013.3 s -> 6.9 s,--profile reimpl --min-name 04.0 s -> 12.2 s (about 3x slower: at itsweights the k-gram prefilter cannot prune),
fragments2.2 s.and JavaScript 0.497 (unchanged).
Review and fixes
The branch went through a full BMS code review (architecture gate, security, two correctness reviewers, agent
docs, performance, Python scripts, duplication) with a verification pass on every blocking/should-fix candidate;
refuted candidates were dropped. Fixed in this PR (each with a regression test where code is involved):
endpoint gets no credential); the API key is chosen from the endpoint's own provider (an OpenAI key can no
longer reach a Cohere endpoint); no redirects are followed (an
api-keyheader cannot be replayed to anotherhost); the key is redacted from
Debug.request (with a warning for plain http to a remote host); units that cannot match (
--min-tokens,--skip-tests, boilerplate) are not embedded; the embedding cache is owner-only and keyed by a 128-bit hash.unreadable, so the same texts were re-fetched on every run); empty or non-numeric embeddings are rejected
instead of being stored as hits; oversized entries no longer desync the file.
--embed-codeno longer changes thecopiesrenormalization; the embedding term is droppedwhenever a unit has no vector; the k-gram prefilter no longer loses pairs that share no 4-gram but score above
the threshold;
scancomputes only the features with weight, which made the default scan faster thanmain.query, which made any twoqueries look alike by name in the default profile);
.js/.mjs/.cjsuse the TSX grammar so React components inplain JS files are parsed.
main()inlining keeps an
elsebranch and multi-line strings; compound statements report their header lines only(
--explain,fragments); extraction runs on a large stack so deeply nested input cannot abort a scan.fragmentsandfindwith several roots keep the root in file names;--explainpairs units byfull place instead of
file:line.fragment generator, path filters, unsafe paths from remote data, local server validation and model allowlist,
pinned versions); the repo's own agent skill,
--helptext and crate descriptions now describe SQL/JS, the newcommands and the embedding privacy implications.
Not fixed here (listed in
eval/REPORT.md):--profile reimplis slower thanmain; SQL scoring compares everysame-family unit; per-language rules are still inlined in
units.rsand whole-file pruning rewrites source textinstead of the token stream;
Unit.textcopies source for every unit.Behavior changes to review
scan/diff/reviewnow also read.js .mjs .cjs .jsxand.sqlfiles. More units, so morefindings, which matters for
--fail-on-foundCI users. There is no language filter yet.copiesscoring is unchanged for Python: on a real internal Python repository a scan is identicalto
main(209 pairs, 0 score differences). C# token rules are untouched and its tests pass, but it was notre-checked against real C# code.
;and block braces no longer count as tokens: on areal TSX frontend, 316 pairs are common to both builds, 31 of them change score (largest change 0.118), 6
pairs from
maindrop out and 2 appear. Net effect on the function-level benchmark is positive (a no-;/braceablation scored 0.8064 / 0.7099 vs 0.8225 / 0.7282), but it is a change.
Authorizationheader(previously an Azure token could be attached); local
--embedpresets refuse a non-loopbackDUPLICATECODE_EMBED_ENDPOINT;--embed*prints where the text is sent before sending it. Plain SQL queriesare named after their file, so SQL pairs now need the same file name to get a name match.
Weightshas new fields,N_FEATURESis 12 (was 10),Scoreshas new fields andUnitcarriestext,vecand statement data. Embedding is opt-in;Unit.textcosts one copy of each unit'ssource in memory even when embeddings are off.
tree-sitter-sequel(SQL grammar). No other runtime crates; the Python scripts ineval/use inlineuvdependencies and are not part of the wheel.Known limitations
are directional, and the embedding gain varies by set (TypeScript dev is flat). The embedded text includes
docstrings, so part of the embedding gain is intent matching.
fragmentsbenchmark coversPython and TypeScript only. There are no labeled real-repository clone pairs; real-repo checks were manual
samples.
runs here were CPU-only. Hosted models (OpenAI, Cohere) are wired in but were not benchmarked or exercised
against a live endpoint (no key available): Cohere is covered by request/response unit tests only.
cfg(unix)).evaluation docs was wrong;
eval/fetch_codenet.py --exclude-frombuilds a disjoint holdout for future runs.embedextra, a.duplicatecode.toml,--embed-optionaland--allow-uploadare proposals, notbuilt (listed in the README).
Decisions for the owner
maintags and publishes automatically, so merge only when a release is wanted.
eval/(scripts, report, about 700 lines of Python) belongs in this repo or a separate one.reimplshould become the default profile forfind-style use, or stay opt-in.Reviewing
Suggested order:
eval/REPORT.md(what was measured and what failed) ->index.rs(scoring) ->units.rs(normalization, SQL, pruning) ->
embed.rs->fragments.rs/explain.rs->main.rs/groups.rs.Reproduce:
eval/README.md. The experiment journal (kept and discarded ideas, with numbers) is ineval/REPORT.md.🤖 Generated with Claude Code