<!-- Generated by community/.ai/render-guides.mjs; edit community/.agents/skills/duplicate-and-dead-code/SKILL.md -->
Duplicate and dead code
The exact copy-paste gate finds a copied text only. This skill finds one responsibility in two pieces of different
code, and dead code. Each tool gives candidates, not a proof. Read each candidate before you act on it.
The tools
| Tool | What it finds | Use |
|---|
jscpd exact, --exit-code 1 | a copied text (Type-1) | the gate of an area |
jscpd --ignore-identifiers --kind renamed | a copy with renamed names (Type-2) | burn-down |
jscpd --similarity 0.9 --kind ast | two functions with one syntax tree (Type-3) | the copy lint |
jscpd --semantic --kind semantic | two functions that do one thing (Type-4) | the copy lint |
jscpd --compare <a>/ <b>/ | the functions of <a> with no counterpart in <b> | a port or two implementations |
accio locate '<question>' --mode semantic, accio context '<task>' | the code of a behaviour or a task | the owner check |
accio trace <Name> --direction callers, accio references <Name> | the callers and every use of a name | the owner check, dead code |
accio unused --scope <dir>/ | declarations, bundle keys and image variants used nowhere | dead code |
Without accio on the PATH, run ./community/tools/accio.cmd. Run jscpd only through
./community/tools/jscpd.cmd -c <config>. <config> is the .jscpd.json of the area, for example
plugins/air/.jscpd.json. An area without a .jscpd.json runs the commands without -c and has no exact gate. Write
each JSON report to a temporary directory of your session. The jscpd_test target of the area runs the gate too, for
example //plugins/air:air-duplicate-code_test.
Before a plan: the owner check
Do this for each responsibility that the plan adds: a mapping, a policy, a parser, a cache, a lifecycle step.
accio locate '<a full question about the behaviour>' --mode semantic --scope <area>/. On a miss, ask again in
other words.
accio context '<the task in words>' --scope <area>/. Name the task by the nouns of the domain, not by a role word
such as "mapper".
accio references <ResultType> --scope <area>/ --no-tests --files. A private function that builds the type is an
owner too.
- For a V1 or V2 change, find the other version:
accio files '<Prefix>V1*', accio files '<Prefix>V2*'.
accio trace <Name> --direction callers for each candidate, then read it. A call site marked ? (:57?) fits
more than one overload: read it.
The plan then has an Existing owners line. It names each owner and the decision: extend it, move it, or write new
code with the reason why the owner does not fit. When the check finds nothing, the line names the queries.
Before a review: the copy lint
The copy lint is a report, not a gate. Run it after the exact gate of the area passes, when the area has one. Give
the touched directories and the directories of their owners: the scan compares only the files under the paths it gets.
./community/tools/jscpd.cmd -c <config> --similarity 0.9 --kind ast --absolute \
--reporters json --output <temp-dir>/ast <touched-dir>...
./community/tools/jscpd.cmd -c <config> --semantic --semantic-scope same --kind semantic \
--semantic-same-threshold 0.9 --ignore '**/testSrc/**,**/test/**,**/tests/**,**/testFramework/**' \
--absolute --reporters json --output <temp-dir>/semantic <touched-dir>...
Then keep the pairs that touch a file of the change. Run this from the repository root:
{ git diff --name-only <base>; git ls-files --others --exclude-standard; } | sed "s|^|$PWD/|" \
| jq -R . | jq -s . > <temp-dir>/changed.json
jq -r --slurpfile c <temp-dir>/changed.json '.duplicates[]
| select(.firstFile.name as $a | .secondFile.name as $b | $c[0] | index($a) or index($b))
| "\(.similarity) \(.firstFile.name):\(.firstFile.start)-\(.firstFile.end) ~ \(.secondFile.name):\(.secondFile.start)-\(.secondFile.end)"' \
<temp-dir>/ast/jscpd-report.json <temp-dir>/semantic/jscpd-report.json
Filter the report by file, not by kind. An --kind ast pair has "kind": "similar".
- Do not use
--changed or --baseline-from-ref. Each makes a git worktree of the base revision with 1.4 million
files.
- The first
--semantic run on a machine needs --semantic-download (a model of 548 MB). The first run on a
directory takes minutes. A later run takes below 1 s.
- Read each pair at 0.90 or more. Below 0.80, most pairs are two functions of one shape.
- A pair in one file whose two parts are one line apart is a table, not a copy.
- A pair that the change did not touch is a lead. Report it and do not fix it.
- A pair that you keep needs a reason in the review: two sides that no shared module can hold, or a wire DTO and its
model.
Dead code
accio unused --scope <dir>/ --limit 1000 gives the candidates. Each candidate is a lead. The note of the answer
explains each mark, and accio unused --help says what the scan leaves out and cannot see.
- A
(generated) icon field: delete its SVG and generate the icon class again with the icon-resources skill. First
search the SVG file name over the whole repository. A SKILL.md can name it as an icon, and then the fix is an
icon-robots.txt skip.
- A
(published) candidate is a published API. Keep it unless its owner decides otherwise.
- An area can keep a guide of the candidates that stay, for example
plugins/air/docs/guides/dead-code-guide.md. Read
it before the triage, and add each candidate that you keep.
To delete a candidate, prove it with accio locate '<name>' without --scope. Then delete it, the tests that test
only it, the imports that only it needed, and the KDoc links to it. Delete a dead key from the English bundle only.
The translations under localization/ belong to the localization team. Run the tests of the area, and run
accio unused again.