Pre-computed context
Current branch: !git branch --show-current 2>/dev/null || echo "unknown"
Purpose
Duplication is the measure most likely to report noise, because some replication is deliberate:
a repository that vendors one helper into seventeen plugins on purpose has seventeen copies and
zero debt. This skill reports the clone groups a detector found, then subtracts the groups the
target repository has already declared about itself, and stops there: no pass or fail, no
severity, no finding. What to do about a surviving clone is the reader's call.
Collectors, tried in ladder order per lane:
| Collector |
Lanes |
Values per clone group |
When |
jscpd |
every lane |
lines, tokens |
jscpd resolves on PATH or in ./node_modules/.bin |
dupl |
Go only |
lines, tokens null |
jscpd did not resolve and dupl did |
cpd (PMD) |
TypeScript/JavaScript, Python, Go, C# |
lines, tokens |
jscpd did not resolve and pmd did (never for Bash: CPD has no shell language) |
No collector resolves means the lane's row says so with the tool's install hint and the run
continues. This plugin never installs, downloads, or npx-fetches a detector.
Run it
"${CLAUDE_SKILL_DIR}/scripts/audit-duplication.sh" # the change: diff from the merge-base plus uncommitted files
"${CLAUDE_SKILL_DIR}/scripts/audit-duplication.sh" src/ lib/ # explicit paths (a missing one is a usage error)
"${CLAUDE_SKILL_DIR}/scripts/audit-duplication.sh" --all # every tracked or untracked-but-not-ignored file
"${CLAUDE_SKILL_DIR}/scripts/audit-duplication.sh" --registry scripts/cross-plugin-source-registry.txt --all
"${CLAUDE_SKILL_DIR}/scripts/audit-duplication.sh" --json --all src/ # the code-metrics/v1 document instead of markdown
Present the markdown report as printed. It opens with the scope and a "Coverage of this run"
table (lane, collector, status, reason), then one row per clone group listing every instance as
file:start-end, then the summary line with the duplicated-line total and how many groups a
registry excluded. Keep the --json document when the numbers feed a comparison:
/verification:measure metrics consumes it when the verification plugin is installed (treat a
report whose status is empty on either side as INCONCLUSIVE); otherwise keep the JSON beside
your notes and compare by hand.
Reading the numbers
- A clone group is reported beside no reference. There is no configured bar for duplication in
this plugin and no standard sets one, so nothing is ever counted as
over_reference.
summary.duplicated_lines counts each group once, using the length of the group, not the sum
over its instances: two copies of a 41-line block are 41 duplicated lines, not 82. The count is
what survived the registries.
- The registry is an exclusion, not a suppression: it is derived from the target repository's
own declaration that those copies are deliberate, so no suppression record is involved and the
excluded groups stay in the document under
excluded[] with the registry path, the 1-based line
number, and the line's text. A group is excluded only when one registry line accounts for every
instance and each instance sits under a different carrying directory; two copies inside one
directory are ordinary duplication and stay.
- A value the detector did not produce is
null, never 0: dupl reports no token count, so
its rows carry tokens: null.
status is complete when every lane in scope was measured, partial when one was not, and
empty when nothing was; a run that measured nothing prints "Measured nothing" and states no
duplication figure at all, which is not the same as zero duplication.
- Exit 0 whenever a report was produced, including an
empty one; exit 2 for a usage error such
as a named registry or scope path that does not exist; exit 3 when a detector resolved but
produced nothing parseable, with its stderr in the run table. A detector's own non-zero exit is
not a failure when it produced a report, which is how jscpd and PMD CPD signal "clones found".
Configuration
Everything tunable resolves through .claude/code-metrics.yaml (user-global, team, local
overlay; per-key override; keys in ${CLAUDE_PLUGIN_ROOT}/reference/config.md):
duplication.min_tokens (default 50), duplication.min_lines (default 5),
duplication.ignore (globs handed to the detector's own ignore option), and
duplication.registries (sanctioned-replication registries, each path relative to the repository
root, and each also nameable on the command line with --registry). /code-metrics:setup writes
the team file and probes the collectors.
This script exports the three tunables to the collector adapters as
CODE_METRICS_DUP_MIN_TOKENS, CODE_METRICS_DUP_MIN_LINES, and CODE_METRICS_DUP_IGNORE, which
is the only channel an adapter reads them through. jscpd passes all three to the tool;
dupl and cpd have no minimum-lines or ignore-glob option, so their adapters apply the
minimum after parsing and report the ignore globs as unused.
cpd (PMD) sits after jscpd on ${CLAUDE_PLUGIN_ROOT}/scripts/collector-ladder.tsv for every
lane but Bash, so it runs only when jscpd does not resolve and pmd does. A repository that
already runs PMD puts it first with lanes.<lane>.collectors.duplication: [cpd, jscpd]; collector
overrides are validated against the ladder file and an unknown name is dropped with a warning.
What this skill does not do
- It does not run tests, edit files, remove a clone, or install a detector; when none resolves the
run table says so and prints the install hint.
- It does not judge. No duplication figure here is a bar, and no
check gate exists in this
version.
- It does not find cross-language clones. Every detector here is token-based within one language
(
jscpd groups JavaScript with TypeScript and nothing wider), so a C# method reimplemented in
Python is invisible to it.
- It does not measure size, complexity, coverage, or type debt; those are the sibling
audit-*
skills in this plugin, and /code-metrics:principles explains what each number can and cannot
tell you.
Next
- The numbers feed a before-and-after comparison:
/verification:measure metrics.
- A surviving clone is about to be called debt:
/code-metrics:principles.
Gotchas
- Change scope needs a merge-base with the default branch; outside a git repository, or on a
branch with no default-branch ancestor, pass paths or
--all (the usage error says which).
- A registry named on the command line or in
duplication.registries that does not exist is a
usage error, not a silent no-op: a stale registry path would otherwise turn every exclusion off
without saying so.
- Clone detection compares the files in scope with each other. A default-scope run sees only the
changed files, so a block copied from a file the change did not touch is not found; use
--all
or name both paths when that is the question.
jscpd reports pairs, so seventeen identical copies arrive as sixteen two-instance groups
rather than one seventeen-instance group; the registry excludes each of them on the same line.
- Lowering
duplication.min_tokens finds more and smaller clones, most of them boilerplate the
language forces; the defaults are the detector's own conservative pair.
1---2name: audit-duplication3description: Measure duplicated code as clone groups over the changed files, a path, or the whole tree: each group's duplicated lines and tokens with every instance's file and line range, per lane (TypeScript/JavaScript, Python, Bash, Go, C#) from whichever clone detector already resolves. Replication the target repository declares about itself, a file vendored into several plugins and listed by path-within-plugin in a sanctioned-replication registry, is subtracted from the total and reported as an exclusion naming the registry line rather than as debt, and the report emits no finding, no severity, and no exit-code gate. Use when: 'is this duplicated', 'find copy-paste code', 'clone detection', 'duplication report', 'how much of this change is copied', 'DRY check', 'redundant code', 'duplicated lines in the diff'; for lines per file use /code-metrics:audit-size, and for what a duplication number can and cannot support use /code-metrics:principles.4---56## Pre-computed context78Current branch: !`git branch --show-current 2>/dev/null || echo "unknown"`910## Purpose1112Duplication is the measure most likely to report noise, because some replication is deliberate:13a repository that vendors one helper into seventeen plugins on purpose has seventeen copies and14zero debt. This skill reports the clone groups a detector found, then subtracts the groups the15target repository has already declared about itself, and stops there: no pass or fail, no16severity, no finding. What to do about a surviving clone is the reader's call.1718Collectors, tried in ladder order per lane:1920| Collector | Lanes | Values per clone group | When |21|---|---|---|---|22| `jscpd` | every lane | `lines`, `tokens` | `jscpd` resolves on `PATH` or in `./node_modules/.bin` |23| `dupl` | Go only | `lines`, `tokens` null | `jscpd` did not resolve and `dupl` did |24| `cpd` (PMD) | TypeScript/JavaScript, Python, Go, C# | `lines`, `tokens` | `jscpd` did not resolve and `pmd` did (never for Bash: CPD has no shell language) |2526No collector resolves means the lane's row says so with the tool's install hint and the run27continues. This plugin never installs, downloads, or `npx`-fetches a detector.2829## Run it3031```bash32"${CLAUDE_SKILL_DIR}/scripts/audit-duplication.sh" # the change: diff from the merge-base plus uncommitted files33"${CLAUDE_SKILL_DIR}/scripts/audit-duplication.sh" src/ lib/ # explicit paths (a missing one is a usage error)34"${CLAUDE_SKILL_DIR}/scripts/audit-duplication.sh" --all # every tracked or untracked-but-not-ignored file35"${CLAUDE_SKILL_DIR}/scripts/audit-duplication.sh" --registry scripts/cross-plugin-source-registry.txt --all36"${CLAUDE_SKILL_DIR}/scripts/audit-duplication.sh" --json --all src/ # the code-metrics/v1 document instead of markdown37```3839Present the markdown report as printed. It opens with the scope and a "Coverage of this run"40table (lane, collector, status, reason), then one row per clone group listing every instance as41`file:start-end`, then the summary line with the duplicated-line total and how many groups a42registry excluded. Keep the `--json` document when the numbers feed a comparison:43`/verification:measure metrics` consumes it when the `verification` plugin is installed (treat a44report whose `status` is `empty` on either side as INCONCLUSIVE); otherwise keep the JSON beside45your notes and compare by hand.4647## Reading the numbers4849- A clone group is reported beside no reference. There is no configured bar for duplication in50 this plugin and no standard sets one, so nothing is ever counted as `over_reference`.51- `summary.duplicated_lines` counts each group once, using the length of the group, not the sum52 over its instances: two copies of a 41-line block are 41 duplicated lines, not 82. The count is53 what survived the registries.54- The registry is an **exclusion**, not a suppression: it is derived from the target repository's55 own declaration that those copies are deliberate, so no suppression record is involved and the56 excluded groups stay in the document under `excluded[]` with the registry path, the 1-based line57 number, and the line's text. A group is excluded only when one registry line accounts for every58 instance and each instance sits under a different carrying directory; two copies inside one59 directory are ordinary duplication and stay.60- A value the detector did not produce is `null`, never `0`: `dupl` reports no token count, so61 its rows carry `tokens: null`.62- `status` is `complete` when every lane in scope was measured, `partial` when one was not, and63 `empty` when nothing was; a run that measured nothing prints "Measured nothing" and states no64 duplication figure at all, which is not the same as zero duplication.65- Exit 0 whenever a report was produced, including an `empty` one; exit 2 for a usage error such66 as a named registry or scope path that does not exist; exit 3 when a detector resolved but67 produced nothing parseable, with its stderr in the run table. A detector's own non-zero exit is68 not a failure when it produced a report, which is how `jscpd` and PMD CPD signal "clones found".6970## Configuration7172Everything tunable resolves through `.claude/code-metrics.yaml` (user-global, team, local73overlay; per-key override; keys in `${CLAUDE_PLUGIN_ROOT}/reference/config.md`):74`duplication.min_tokens` (default 50), `duplication.min_lines` (default 5),75`duplication.ignore` (globs handed to the detector's own ignore option), and76`duplication.registries` (sanctioned-replication registries, each path relative to the repository77root, and each also nameable on the command line with `--registry`). `/code-metrics:setup` writes78the team file and probes the collectors.7980This script exports the three tunables to the collector adapters as81`CODE_METRICS_DUP_MIN_TOKENS`, `CODE_METRICS_DUP_MIN_LINES`, and `CODE_METRICS_DUP_IGNORE`, which82is the only channel an adapter reads them through. `jscpd` passes all three to the tool;83`dupl` and `cpd` have no minimum-lines or ignore-glob option, so their adapters apply the84minimum after parsing and report the ignore globs as unused.8586`cpd` (PMD) sits after `jscpd` on `${CLAUDE_PLUGIN_ROOT}/scripts/collector-ladder.tsv` for every87lane but Bash, so it runs only when `jscpd` does not resolve and `pmd` does. A repository that88already runs PMD puts it first with `lanes.<lane>.collectors.duplication: [cpd, jscpd]`; collector89overrides are validated against the ladder file and an unknown name is dropped with a warning.9091## What this skill does not do9293- It does not run tests, edit files, remove a clone, or install a detector; when none resolves the94 run table says so and prints the install hint.95- It does not judge. No duplication figure here is a bar, and no `check` gate exists in this96 version.97- It does not find cross-language clones. Every detector here is token-based within one language98 (`jscpd` groups JavaScript with TypeScript and nothing wider), so a C# method reimplemented in99 Python is invisible to it.100- It does not measure size, complexity, coverage, or type debt; those are the sibling `audit-*`101 skills in this plugin, and `/code-metrics:principles` explains what each number can and cannot102 tell you.103104## Next105106- The numbers feed a before-and-after comparison: `/verification:measure metrics`.107- A surviving clone is about to be called debt: `/code-metrics:principles`.108109## Gotchas110111- Change scope needs a merge-base with the default branch; outside a git repository, or on a112 branch with no default-branch ancestor, pass paths or `--all` (the usage error says which).113- A registry named on the command line or in `duplication.registries` that does not exist is a114 usage error, not a silent no-op: a stale registry path would otherwise turn every exclusion off115 without saying so.116- Clone detection compares the files in scope with each other. A default-scope run sees only the117 changed files, so a block copied from a file the change did not touch is not found; use `--all`118 or name both paths when that is the question.119- `jscpd` reports pairs, so seventeen identical copies arrive as sixteen two-instance groups120 rather than one seventeen-instance group; the registry excludes each of them on the same line.121- Lowering `duplication.min_tokens` finds more and smaller clones, most of them boilerplate the122 language forces; the defaults are the detector's own conservative pair.