feat(code-metrics): clone classes, explicit caps, registry cluster lines, and rollups for audit-duplication - #4111
Draft
kyle-sexton wants to merge 17 commits into
Draft
Conversation
Interview contract for the six audit-duplication gaps a whole-tree run exposed: size-cap skip detection and defaults, pair-to-class merging, a registry grammar for canonical copies outside a plugin, report sort and rollups with additive JSON fields, the duplication summary line, and the no-detector experience. Brief only; the Plan section is filled next. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QShQwS5yDYYToBmofdEY5N
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QShQwS5yDYYToBmofdEY5N
Five phases under the locked Brief (caps end to end, pair-to-class merge, registry cluster lines with the drift-checker skip, report rollups and summary line, docs and release), plus the Tier B design early-exit with the type sketch the phases build to. Draft pending the fresh-context plan review and the stress-test; approval comes after both. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QShQwS5yDYYToBmofdEY5N
…d stress-test Fold in the verified findings from the fresh-context plan review and the devil's-advocate pass: eighteen hook-utils instances, the 1.45mb minified bundle that the 1mb cap skips by design, the dropped telemetry-sink line (the pair differs and has no sync script), a `->` marker for cluster lines so a registered path with a space keeps working, dirname-based carrying directories, root-relative instance paths, a `partial` zero floor, an additive run-row `hint`, path-sorted merged instances, fixtures outside the scoped tree, and the affected-tests exit-3 contract. Adds blast radius, stress-test summary, execution shape, and handoff sections. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QShQwS5yDYYToBmofdEY5N
Phase 1 of the duplication audit plan (docs/topics/code-metrics-duplication-audit/PLAN.md). - Add duplication.max_lines (null), duplication.max_size (1mb), and duplication.rollup_depth (2) to the bundled defaults, the config reference, and the setup template. - audit-duplication.sh exports CODE_METRICS_DUP_MAX_LINES and CODE_METRICS_DUP_MAX_SIZE beside the existing tunables; a null or 0 cap exports empty. - The jscpd adapter applies both caps itself before jscpd runs (stat size, binary newline count only when a line cap is set) and passes jscpd one above its own bound, because jscpd 4.x and 5.x disagree on the --max-size/--max-lines defaults and on what 0 means, and neither names a skipped file. Every skip is one line to CODE_METRICS_PARTIAL_REASON_FILE, or stderr when unset; zero files left is a zero measurement, not a failure. Both majors translate. - dispatch.sh gives each collect a partial-reason file and writes the run row as `partial` with that line when it is non-empty; every run row gains an additive `hint` field carrying the first install hint a failed probe produced. - registry-filter.py --zero-floor counts a `partial` duplication row as measured. - collectors.md pins jscpd 5.2.0 and records the 4.3.0 maintenance line and the caps semantics; report-schema.md documents `hint`. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QShQwS5yDYYToBmofdEY5N
Phase 2 of the duplication audit plan. jscpd (both majors) and PMD CPD report a clone as a pair, so N copies of one fragment arrive as N-1 rows that all name the first copy, and the summary counted the fragment's lines N-1 times. cluster-clones.py runs between the dispatcher and the registry filter and joins pair rows that share an instance with an identical (file, start_line, end_line) and equal lines, keeping the first pair's values, the union of instances sorted by path, and a `clustered` label. Overlap without an identical range stays separate, so a class is never widened past what the detector said was identical. Fixtures under scripts/fixtures/clone-classes (three byte-identical copies; two full copies plus one partial copy) with captures from a real jscpd 5.2.0 run rewritten to repo-relative names. jscpd emits a star for N copies and names the first copy with the same range in every pair, so the plan's offset example is corrected to the partial-copy shape. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QShQwS5yDYYToBmofdEY5N
…classes Phase 3 of the duplication audit plan, committed alone because the registry edit fans CI's test selection out. - registry-filter.py reads a second line shape, `<canonical> -> <member>...`: a root-relative canonical copy and the plugin paths or gitignore-style globs that carry it. A clone group is excluded when every instance is the canonical or matches a member and the instances' directories are pairwise distinct. Instance paths are compared root-relative, so a run from a subdirectory matches the same lines, and the first matching line in file order wins. A plain line is still one path taken whole. - check-cross-plugin-source-drift.sh skips ` -> ` lines, which key on a root path this check never sees; its test covers the skip. - scripts/cross-plugin-source-registry.txt gains five annotated cluster lines for the sync-declared root lib/ canonicals: hook-utils, rewrite-guard, index-regen, resolve-convention-pattern, and parse-concern-value. The whole-tree audit now excludes each class (18, 7, 2, 2, and 4 instances) from the root and from a subdirectory. - config.md and the claude-config exclusion-set reference describe the grammar; the fixture registry carries a commented example. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QShQwS5yDYYToBmofdEY5N
…summary Phase 4 of the duplication audit plan. - report.py summarize adds `summary.by_lane` and `summary.by_directory` (every ancestor of each group's first instance, cumulative, made relative to --root) beside duplicated_lines and clone_groups; by_directory["."] and the per-lane sum both restate the totals. The zero floor states both as empty maps on a clone-free run. - assemble reads a `partial` run row with no measure rows as `partial`, not `empty`: a lane that skipped every file said so in its row. - render lists a duplication document's groups largest first, adds a `## Rollup` section (per lane, and per directory to --rollup-depth, default 2 from duplication.rollup_depth), summarizes as `Files with clones: N.`, states an empty excluded list with its reason, adds a `Partial:` line, and prints one headline with the install hint when no clone detector ran in any lane. Every other skill's document renders as before. - report-schema.md documents the rollups, the ignore-unknown-keys rule, the partial status for a skipped lane, and intentional clones. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QShQwS5yDYYToBmofdEY5N
…ase 0.1.9 Phase 5 of the duplication audit plan. - audit-duplication SKILL.md: the three new keys and the 0-means-null rule, both registry line shapes, the five exported tunables and why the jscpd adapter applies the caps itself, the no-detector headline and the offer-never-perform install rule, clone classes and the identity-not-overlap merge, the rollups and their root identity, the partial reading, and the gotchas for drifted copies and cross-major comparison. - README row and known-gaps list; CHANGELOG [0.1.9]; plugin.json 0.1.9. - The two-copy jscpd capture is regenerated under jscpd 5.2.0 with repo-relative names so no stale version string remains; the plan records the whole-tree dogfood numbers under both majors. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QShQwS5yDYYToBmofdEY5N
Resolves the code-metrics 0.2.1 line against the duplication audit work: the plugin becomes 0.2.2; run rows keep the additive `hint` field on the slot-based parallel dispatcher and the partial-reason file rides with `launch_collect`; `report.py render` takes both `--document` and `--rollup-depth`, sorts duplication rows largest first and every other document by the upstream order; `scope.registries` is the key the skill reads through the resolver, with the cluster-line grammar documented on it; the drift checker drops cluster lines from the read-list entries so the stale-baseline pass never sees them, and replica-collapse.py skips them for the same reason with a test. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QShQwS5yDYYToBmofdEY5N
The sanctioned-replication registry is a contract three readers parse, so the arrow line that declares a root canonical plus its copies is recorded with the alternatives it displaced. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QShQwS5yDYYToBmofdEY5N
Review finding: with no line cap configured the adapter passed jscpd --max-lines 1000000, a number a generated file can reach, so such a file would survive the adapter's pre-filter and then be skipped by jscpd's own gate with nothing named, the exact failure the pre-filter exists to close. The sentinel is now the signed 32-bit maximum, which both majors accept (verified against 4.3.0 and 5.2.0); tests and the collectors reference follow. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QShQwS5yDYYToBmofdEY5N
…ctory Verification finding: cluster-clones.py sorted a class's instances by the cwd-relative path the detector gave them, so a run from lib/ put `../plugins/...` ahead of `hook-utils.sh` and the directory rollup attributed the class elsewhere than a run from the root did. The merge now takes --root and sorts by the root-relative path (the instances keep their paths); the skill passes it. The root-relative helper the summarizer and the registry filter each carried moves into pathglob.py and all three share it. The plan's acceptance criteria are amended to the facts the verifier established: the two majors can name a byte-identical class with different line ranges (5.x drops a leading comment block), the typescript partial row is observable only with the repository's default dist exclusion lifted, and the version is the next patch above the default branch. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QShQwS5yDYYToBmofdEY5N
Close-out of docs/topics/code-metrics-duplication-audit: the Brief and the dogfood numbers are published in the pull request body, the five-phase plan and the design sketch stay in the branch history (beb6169:docs/topics/code-metrics-duplication-audit/), the registry grammar is ADR 0033, and the user-facing outcomes are the code-metrics CHANGELOG [0.2.2] entry. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QShQwS5yDYYToBmofdEY5N
Main shipped code-metrics 0.2.2 (the principles skill fix) while this pull request was open, so this change moves to 0.2.3 with its changelog entry above that one. The claude-config reference edit that describes the registry cluster-line grammar now carries its own patch bump and entry, which the version-reuse gate requires for any shipped plugin file. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QShQwS5yDYYToBmofdEY5N
CI's lint lane on the pull request head reported three hygiene failures: the two new Python scripts carried a shebang without the exec bit, the three regenerated jscpd captures ended without a newline, and the audit-duplication description had grown past the Agent Skills field maximum of 1024 codepoints. The description now states the same triggers and scope in 963. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QShQwS5yDYYToBmofdEY5N
Main bumped code-metrics to 0.2.3 and claude-config to 0.42.3 (the description-prose sweep) while this pull request was open, the same numbers this branch had taken, so the branch moves to 0.2.4 and 0.42.4 with its changelog entries above the ones main shipped. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QShQwS5yDYYToBmofdEY5N
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.
Closes #4068
Summary
/code-metrics:audit-duplicationreported detector pairs as groups, so eighteen byte-identical copies ofhook-utils.sharrived as seventeen rows and the fragment's lines counted seventeen times; a 1.4 MB minified bundle was silently left out of the scan by jscpd's own size guard; the root canonical of a vendored file could not be declared in the sanctioned-replication registry; and the markdown read like a size report (Files. Functions. Over reference.) with no way to see which lane or directory carried the duplication. This PR fixes all of that in the code-metrics plugin (0.2.3 to 0.2.4), adds the registry cluster-line grammar this repository needed, and records the five clusters.Fix
duplication.max_size(default1mb, binary units) andduplication.max_lines(defaultnull) are applied by the jscpd adapter before the tool runs, because jscpd 4.x and 5.x disagree on their--max-size/--max-linesdefaults and on what0means, and neither names a skipped file. A skip makes the lane's run rowpartial(count and largest file), the documentpartial, and the summary carries aPartial:line. Both majors are translated;reference/collectors.mdpins 5.2.0 and records 4.3.0.cluster-clones.pymerges pair rows that share an identical(file, start_line, end_line)and equal lines into one row per class (union-find, instances sorted by root-relative path,clusteredlabel); lines count once. Overlap without identity stays separate, so a class is never wider than what the detector called identical.<canonical> -> <member>...names a root-relative canonical copy and the plugin paths or gitignore-style globs that carry it.registry-filter.pymatches root-relative (a run from a subdirectory matches the same lines), first matching line wins; the drift checker andreplica-collapse.pyskip such lines. Five annotated cluster lines are added for the sync-declared rootlib/canonicals (ADR 0033).summary.by_laneandsummary.by_directory(additivev1fields, cumulative ancestors of each class's first instance, computed after exclusion); duplication rows listed largest first; a## Rollupsection toduplication.rollup_depth(default 2);Files with clones: N.; an empty exclusion list is stated with its reason; one headline with the install hint when no clone detector ran, and the skill offers the install without performing it; run rows gain an additivehintfield.[0.2.4],reference/config.md,reference/report-schema.md, setup template and bundled defaults; the claude-configaudit-passexclusion-set reference describes both registry line shapes (claude-config 0.42.4, wording only).Two plan corrections came from live runs and are recorded in the plan: jscpd emits a star for N copies (each later copy paired with the first, same range every time), so three full copies at different offsets are one class and the "offset" fixture is a partial third copy; and the surviving root
lib/groups after exclusion are alllib/*.test.shper-suite harness boilerplate that no sync script declares, so they are a finding, not sanctioned replication.Verification
scripts/affected-tests.sh --runover the branch: every shell suite green exceptplugins/claude-ops/skills/plugins/scripts/cache-content-check.test.sh, whose process-budget PS4 trace probe fails identically on an untouched tree in this sandbox (the pid-stamped PS4 did not reach the traced shell); theNOT RUNPython and Node suites pass in their own lanes (932 pytest cases across the twenty selected suites; the autonomy fixtures.mjs, 544 checks) exceptplugins/session-flow/scripts/tests/test_save_point.py::test_new_origin_falls_back_to_directory_name, which fails identically on a detachedorigin/mainworktree and touches no file this branch changes.dispatch.test.sh79 cases,audit-duplication.test.sh27 cases,test_report.py42,test_registry_filter.py20,test_cluster_clones.py13,test_jscpd.py15,test_replica_collapse.py8,test_pathglob.pywith the sharedroot_relativecases;check-cross-plugin-source-drift.sh --checkexit 0 and its test 15 cases;check-code-metrics-config-reference.py28 keys;check-changelog-parity.sh --check-bump origin/main,--check, and--check-orderexit 0;check-changed-skills.sh origin/mainand the catalog check pass.d91a5971;ci-lanesreported all lanes passed on that head.--all --registry scripts/cross-plugin-source-registry.txt): before, 919 pair rows and 75267 duplicated lines with the miro bundle silently skipped; after, 722 classes and 11364 lines under 5.2.0, each of the five cluster lines excluding its class (18, 7, 4, 2, 2 instances),by_laneandby_directoryidentical from the repository root and fromlib/, and thepartialtypescript row namingplugins/miro/dist/index.min.jswhen the default**/dist/**exclusion is lifted.Related
docs/adr/0033-declare-a-canonical-plus-copies-cluster-as-one-arrow-line.mdrecords the registry grammar.[0.2.4]carries the user-facing entries; main shipped[0.2.2](fix(code-metrics): ground the principles skill in its primaries and add a skill-quality Next-section check #4098) and[0.2.3](fix(playbooks,skills): keep first and second person out of description prose across 36 skills #4108) while this PR was open, and the branch is merged with both.Approved plan, Brief and dogfood (the full PLAN.md with the five phases is in the branch history at `beb6169d:docs/topics/code-metrics-duplication-audit/PLAN.md`; the contract slice is pruned at close-out)
Brief
Scope-change note (2026-09-11, before approval, from the fresh-context plan review and the
stress-test, each finding verified against the tree): four criteria below were corrected. The
hook-utils.shclass has eighteen instances (root plus seventeen plugins), not seventeen. Theshipped 1mb cap skips one tracked file here,
plugins/miro/dist/index.min.js(1.45mb), so thetypescript lane reads
partialby design. Thehook-telemetry-sink.shpair differs at line 51and has no sync script, so it is not sanctioned replication and its registry line is dropped
(five lines, not six). Pair-to-class merging joins byte-aligned copies; a copy embedded at a
different offset with different surrounding lines stays its own group. Cluster lines carry an
explicit
->marker because a registered path may contain a space.TLDR
audit-duplicationreports skipped files instead of hiding them: the jscpd adapter pre-filtersby byte size and line count, passes both caps explicitly on 4.x and 5.x, and marks the lane
partialwhen anything was skipped; defaults are no line cap and a 1mb byte cap.count once.
(
<canonical path> -> <copy path or glob>...) so a canonical file outside any plugin can declareits copies; this repo's drift checker skips marked lines and five such lines are added here.
(cumulative, counts beside share, computed after exclusion, listed to depth 2), and the JSON gains
summary.by_lane/summary.by_directoryas additivev1fields with an explicit ignore-unknownrule in the schema reference.
one consolidated install headline and the skill offers, never performs, the install. Version 0.1.9.
Goal
A whole-tree or change-scoped duplication audit on any repository, this one included, produces
numbers a reader can act on without re-aggregating: no file is silently dropped by a size cap,
byte-aligned copies of a fragment form one group counted once, replication the repository declares
about itself (including copies of a root-level canonical file) is excluded and shown as an
exclusion, and the report says where the surviving duplication sits by lane and by directory. On
this repository the audit reads clean apart from genuine duplication.
Constraints
npx-fetches a detector; SKILL.md may instruct Claude tooffer the install command and run it only on the user's confirmation.
code-metrics/v1schema string is unchanged; every JSON change is additive (new optionalfields), and
reference/report-schema.mdstates that readers ignore unknown keys.the drift checker (
scripts/check-cross-plugin-source-drift.sh) keeps its behavior for them andskips only lines carrying the
->marker.latest-4= 4.3.0) and 5.x (5.2.0) stay supported by the adapter; the adapteralways passes explicit
--max-linesand--max-sizeon both majors, treats a configured0asnull(jscpd reads0as "default" for 4.x lines and "skip all" everywhere else), and neveremits a
0cap.null, never0; a run that measured nothing keeps "Measured nothing".scripts/affected-tests.sh --run; every changed file maps to at least one suite.lib/hook-utils.shor other cross-plugin synced source is edited; every cluster line mirrorsthe
src=and copy list itsscripts/sync-*.shalready declares.Closes #<issue>and carries the four requiredsections; CHANGELOG entry under
[0.1.9].Acceptance criteria
audit-duplication.sh --json --allon this repository with jscpd 4.3.0 and again with5.2.0 yields, for every byte-identical whole-file class, the same set of instance files per
group;
tokens, instance order, and the exact line range are excluded from the comparisonbecause the two majors tokenize differently and hub on different copies (5.x drops a leading
comment block from the clone, so a file that opens with one is reported from its first code line
under 5.x and from line 1 under 4.x; the verifier found one such class,
resolve-hook.mjsin twoknowledgeskills). The bash row readsokunder both; with therepository's default
**/dist/**scope exclusion lifted, the typescript row readspartialunder both, naming
plugins/miro/dist/index.min.jsas the one file over the 1mb cap; with theexclusion in force (this repository's shipped team file) the bundle never reaches the cap and the
row reads
ok.duplication.max_linesdefaults tonull(no cap) andduplication.max_sizeto1mb; both aredocumented in
reference/config.md(gated againstconfig-defaults.json) and exported to theadapter, and the adapter's tests cover the explicit-cap argv on both majors, the
0-means-nullrule, and that no
0cap is ever passed.partialwith thereason, no collector is invoked for it, and the script never exits 3 for that cause.
hook-utils.sh(rootlib/plus the seventeen plugincopies
scripts/sync-hook-utils.sh --print-manifestlists) produce exactly one clone group witheighteen instances, and with the registry line
lib/hook-utils.sh -> plugins/*/hooks/hook-utils.shthat group appears once underexcluded[]with
duplicated_linescounted once.hook-telemetry-sink.shshape, and a third copy that carries only part of a fragment two full copies share, so the
detector names the first copy with two different ranges) stay separate groups after the merge.
Three full copies at different line offsets are one class: jscpd pairs each later copy with the
first and names that first copy with the same range in every pair (verified against 5.2.0).
canonical path or one of the members (literal or glob) and the instances' directories are all
distinct; two copies inside one directory still count as duplication; a single-token line behaves
exactly as before, a registered path containing a space included; when a single-token line and a
cluster line both match, the first matching line in file order wins.
scripts/check-cross-plugin-source-drift.sh --checkpasses on this repository with the five newcluster lines present, each under its own annotation block, and its tests cover a marked line
being skipped.
audit-duplication.sh --json --registry scripts/cross-plugin-source-registry.txt --allon this repository reports zero surviving groups whose instances include a root
lib/file thata sync script declares as its canonical copy (
scripts/sync-*.sh --print-manifestsrc), fromthe repository root and from a subdirectory alike. Groups over the per-suite test-harness
boilerplate that
lib/*.test.shfiles share with plugin test files survive: no sync scriptdeclares them and the shell-test-helpers convention keeps those helpers per file, so they are
the audit's finding, not sanctioned replication.
excluded[]is empty and thesummary states that no registry was configured.
duplicated lines, and the report carries a
## Rollupsection with a per-lane table and aper-directory table (rows to depth 2 by default,
duplication.rollup_depthconfigurable) whosenumbers are cumulative up the tree and carry
groupsandduplicated_lines; the per-lanevalues sum to
summary.duplicated_lines, and the root row ofby_directoryequals it.summarycarriesby_laneandby_directorywith the same numbers (empty maps when aduplication collector ran and found nothing);
schemais stillcode-metrics/v1;reference/report-schema.mddocuments both fields, the run row's additivehintfield, andstates that readers ignore unknown keys;
verification:measure, the one marketplace consumer,reads only
statusand is unaffected.Files with clones: N.followed by the duplicated-lines andexclusion lines, with no "Functions" or "Over reference" text; the sibling skills' summary lines are
byte-identical to today's.
command and
/code-metrics:setup, taken from the run row'shintfield rather than parsed out ofits reason, and SKILL.md instructs Claude to offer the install and run it only on confirmation.
reference/collectors.mdstate that 4.x and 5.x are bothtranslated, pin 5.2.0, note the
kindfield, and record that the two majors tokenize differently;the schema reference names "intentional clones" beside "sanctioned replication".
plugin.jsoncarries the next patch version above the one on the default branch at merge time(0.2.4 over the 0.2.3 main shipped while the PR was open; the Brief was drafted against 0.1.8) and
CHANGELOG.mdcarries the matching entry covering every item above;scripts/affected-tests.sh --runexits 0, or exits 3 with only Python and Node suites listed as not run, each of which thenpasses in its own lane, or exits 1 only for a suite that fails identically on a detached
origin/mainworktree and touches no file this change edits (named in the PR body).Captured assumptions
(0.1.7 was a
feat). Revisit if the marketplace's release convention says a new registry grammaror JSON fields require a minor bump.
duplicated lines are totalled, and jscpd v5 is the one tool that sums per pair. Revisit if a
standard sets a duplicated-lines definition.
lines, so onlybyte-aligned copies join; jscpd 5.x hubs every later copy on the first and names it with the same
range, so full copies at different offsets are one class, while 4.x hubs on the last input and
extends into shared flanking lines, so under 4.x the same copies can stay two groups (a live
probe of three offset copies gave one class under 5.2.0 and two groups under 4.3.0). Closure is exact only for
type-1/type-2 clones, which is all the adapter receives because it passes no
--max-gap-lines.Revisit if
similarclones are ever enabled.cluster-clones.py --root) so the firstinstance, and therefore
by_directoryattribution, is the same under 4.x (which hubs on the lastinput) and 5.x (which hubs on the first), and the same whichever directory the run started from.
rollup_depthdefault 2 is the plugin's choice; no upstream sets a depth (SonarQube and Codacyroll up every directory). Revisit if a consuming repository's layout makes depth 2 meaningless.
rule, and means 1,048,576 bytes, the value jscpd 5.2.0 reports for
1mb; no line cap by defaultaligns with jscpd 5, PMD CPD, SonarQube, and Linguist. Revisit if jscpd changes its default or
multiplier in a later major.
that file as the shape; this is a repository-convention choice with no external authority.
Revisit if the drift checker grows a second consumer of the file.
->marker is the cluster-line signal because a registered single-token path may contain aspace and the drift checker's tests protect that case. Revisit if a consuming repository has a
path containing
->..claude/hooks/hook-telemetry-sink.shandplugins/claude-ops/hooks/hook-telemetry-sink.shpair differs at line 51 and has no sync script, so the audit keeps reporting its overlap as
duplication; a future sync script earns it a cluster line. Revisit when that script exists.
(Cordy 2008) is named beside it. No upstream tool models the canonical-plus-copies relation, so
no vocabulary conflict exists.
docs/rust.md"no limit" for--max-size, "per block"help text for
--max-lines, stale jscpd.dev v5 defaults) is reported separately, not here.Out-of-scope
npxfallback.duplorcpdadapters beyond passing their rows through the new merge and rollupsteps unchanged.
plugins/miro/dist/from this repo's own audits; thepartialrow is the designed reading and a config is the consuming repo's choice.
Deferred questions
Dogfood
plugins/miro/dist/index.min.js)Before this change the same 5.2.0 run reported 919 pair rows and 75267 duplicated lines with no
skipped file named. After the merge with the 0.2.1 line of the plugin (default
scope.excludeglobs, the
otherlane,scope.registriesin this repository's team file) the 5.2.0 run readscompletewith 722 classes, 11364 duplicated lines, 492 files with clones, and 16 exclusions: themiro bundle now falls under the default
**/dist/**exclusion before the cap is reached, so nolane is
partial; pass--allon a tree without that exclusion, or setscope.exclude: [], tosee the cap and the
partialrow. The two majors tokenize differently, so their counts are not comparable witheach other. The largest surviving classes are per-suite test-harness boilerplate (
pass/failhelpers shared by
lib/*.test.shand plugin test files), which no sync script declares and theshell-test-helpers convention keeps per file: a finding for the operator, not sanctioned
replication.
Review and outcome verification
Outcome verification (
/verification:confirm, a fresh-context verifier auditing the acceptance criteria against the diff with live jscpd 4.3.0 and 5.2.0 runs): thirteen of fifteen criteria passed on direct evidence. The two failures were criteria whose wording the tree contradicted, not the code: the plugin version (the Brief named 0.1.9; main was already at 0.2.1 and shipped 0.2.2 and 0.2.3 while this PR was open, so the change ships as 0.2.4), thetypescriptpartialrow being observable only with the repository's own default**/dist/**exclusion lifted, one byte-identical class that jscpd 5.x names from its first code line where 4.x names it from line 1, and the two pre-existing suite failures listed above. The criteria are amended to those facts in the plan. One observation outside the criteria was a real defect and is fixed on the branch: the class merge sorted instances by cwd-relative path, sosummary.by_directoryattributed a class differently fromlib/than from the root; the merge now takes--root, sorts root-relative, and the rollups are identical from both.Code review (
/review:quality-gate code, fresh-context reviewer over the merge-base diff): no CRITICAL finding. One IMPORTANT finding, fixed on the branch: with no line cap configured the jscpd adapter passed--max-lines 1000000, a number a generated file can reach, so such a file would have been skipped by jscpd's own gate with nothing named; the sentinel is now the signed 32-bit maximum, which both majors accept. Of two SUGGESTION items, one was taken (the root-relative path helper now lives inpathglob.pyand is shared by the summarizer, the registry filter, and the class merge) and one left as it is (the skill script's integer reader falls back silently on a float or quoted number forrollup_depth).🤖 Generated with Claude Code
https://claude.ai/code/session_01QShQwS5yDYYToBmofdEY5N