Commit Graph

16 Commits

Author SHA1 Message Date
apk adbf5ecf3d feat(cli): --history, duplication trend over git history
Closes #1002 (the --history half; --ratchet is deliberately not
implemented, the report shows the headroom instead).

`jscpd src --history v5.0.0..HEAD` (or `--history-since 2026-01-01`)
lists the commits with `git log`, checks each one out into a temporary
detached worktree (the --baseline-from-ref machinery, now shared through
baseline_ref::map_scan_paths), scans it with the run's own configuration
and keeps the totals; the current run is the series' last point.
`--history-every N` keeps every Nth commit counted from the newest,
`--history-limit N` (default 30) samples a long series evenly keeping
both ends. Nothing runs unless one of the flags is given.

Output: the console reporter appends a sparkline of the duplication
percentage, a table (commit, date, files, lines, clones, dup lines,
dup%, change, subject) with the change column red when duplication rose
and green when it fell, the overall trend line, and, when --threshold is
set and the latest value is under it, the headroom with the value the
threshold could be tightened to. The json reporter adds a `history`
object; the ai reporter prints one compact line per point. Other
reporters are unchanged.

New cpd_core::history (model, sparkline, change/headroom helpers with
unit tests), cpd::history (git log parsing, sampling, worktree scans),
cpd_reporter::history_render. Config keys history / historySince /
historyEvery / historyLimit; action input `history`. Integration tests
build a four-commit repo with fixed dates and check the JSON series,
the console block, every/limit thinning and the error paths. 969
workspace tests pass, clippy clean.

Docs: History section and option rows in docs/rust.md, README bullet,
action input row, fixtures/history-demo/README.md with a script that
builds the demo repository and the expected output.

Claude-Session: https://claude.ai/code/session_01WztdFKZAwW9b51iJe9zhtV
2026-09-12 07:50:18 +02:00
apk bc3ec21f2d feat(cli): non-zero exit for unknown format, missing path, failed reporter; add --fail-on-empty
Closes #1047.

Four situations used to write an empty report and exit 0, so a CI job
that checks only the exit code saw a green run:

- `--format` naming a format that is not supported (a warning since #964)
  is now `Error: --format: 'x' is not a supported format`, exit 1. Names
  introduced through --formats-exts or --formats-names stay accepted.
- A scan path that does not exist is `Error: path does not exist: <p>`,
  exit 1, before anything runs.
- A reporter that fails to write its output still prints the existing
  `Reporter '<name>' error: ...` line; the run now ends with
  `ERROR: a reporter failed to write its output` and exit 1, after the
  other reporters have run.
- A scan that analyzes no files (paths exist, but nothing matched the
  --format/--ignore/--pattern filters or every file was below
  --min-tokens) prints `Warning: jscpd analyzed no files` and still
  exits 0, so an intentionally empty tree passes. The new
  `--fail-on-empty` flag (config key `failOnEmpty`, action input
  `fail-on-empty`) makes it exit 1. Reports are written before the check.

Docs: an "Exit codes" section in docs/rust.md, option rows in
docs/rust.md and docs/ci-and-hooks.md, a README feature bullet.
Fixtures: fixtures/fail-on-empty-demo with a `code/` clone pair, a
`nothing/` comment-only file, and a README quoting every command's
output and exit code. Tests: the #964 warning test becomes an error
test, plus integration tests for each new exit path and for the config
key; 531 tests pass, clippy clean.

Claude-Session: https://claude.ai/code/session_01WztdFKZAwW9b51iJe9zhtV
2026-09-11 08:45:09 +02:00
apk 89f62eb46a feat(similarity): default threshold 1 = exact matches only; the pass runs only below 1 2026-09-06 18:01:38 +02:00
apk 69bb0e36bf fix(mcp): cache the similarity index per scan; document --similarity range as (0, 1] 2026-09-06 16:51:48 +02:00
apk 57036eb75a feat: function-level similarity for JS/TS with --similarity (Type-3, stage 2 of #999)
Off by default. `--similarity RATIO` (config `similarity`, Action input
`similarity`, MCP `check_duplication` argument) summarizes every function,
method and arrow function of a JavaScript/TypeScript/JSX/TSX file by the
bag of 4-grams over the pre-order sequence of its AST node types (oxc),
indexes the bags with MinHash + LSH banding, and reports pairs whose
weighted Jaccard index reaches RATIO as `similar` clones with a
`similarity` value. Names and literal values are not part of the summary:
a renamed copy scores 1.0, one inserted line ~0.9, two inserted statements
plus renames ~0.75.

Functions must clear --min-tokens/--min-lines, nested functions are never
paired with their parent, and pairs already covered by an exact, renamed
or merged clone are skipped. Values outside (0, 1] warn and disable the
option. Reporting reuses the Type-3 vocabulary (console `similar ~0.75`,
JSON kind/similarity, SARIF jscpd/near-miss-code).

New: cpd-core::similarity (FunctionSig, SimilarityIndex, bag_jaccard,
minhash), cpd-tokenizer::functions (oxc visitor extractor), MCP function
index built lazily per scan. fixtures/type3-demo/similar-functions with
README numbers, docs, unit + integration + MCP stdio tests. Also adds the
missing max_gap_lines/similarity fields to the --debug config output.
2026-09-06 12:27:03 +02:00
apk 6d88ee6b35 ci(action): max-gap-lines input defaults to 0, matching the CLI 2026-09-06 11:54:48 +02:00
apk 448eb9526c feat: near-miss clone merging with --max-gap-lines (Type-3, stage 1 of #999)
Off by default (0). When set to N, clones of the same file pair whose
fragments follow each other in both files with at most N unmatched lines
between them are merged into one clone of kind `similar`, with `tokens` =
matched tokens and `similarity` = matched tokens over the tokens of the
longer merged span. Adjacent exact windows that share a boundary token are
counted once; chains merge transitively; overlapping or reordered fragments
never merge. The pass runs after every other filter and returns its input
untouched at 0, so default output is unchanged.

Reporting: console `Clone found (fmt, similar ~0.91)`, ai `[~0.91]`, JSON
"kind"/"similarity" (rounded to three decimals), SARIF rule
jscpd/near-miss-code with a similarity property (declared only when
referenced), Code Climate check_name.

Config key maxGapLines (kebab alias), GitHub Action input max-gap-lines,
docs section, fixtures/type3-demo with a README of runnable commands, unit
tests for the merge pass and reporters, and a binary-level integration test.
2026-09-06 08:55:12 +02:00
apk 9fef600ad1 feat: Type-2 clone detection (--ignore-identifiers, --ignore-literals, --ignore-annotations)
Closes #998.

Three opt-in flags (config keys ignoreIdentifiers, ignoreLiterals,
ignoreAnnotations) normalize token classes before hashing so that blocks
which differ only in names, values or annotations are detected:

- identifiers hash as `$id`; keywords are kept (the JS/TS tokenizer
  classifies them, other languages use a built-in common-keyword list)
- string literals hash as `$str`, numeric literals as `$num`
- `@Name`, `@a.b.Name` and `@Name(...)` runs are dropped in Java, Kotlin,
  Scala, Groovy, Python, Dart, Swift, JavaScript and TypeScript

Each clone now carries `kind: exact | renamed`. DetectionToken gains a
raw_hash (equal to hash unless normalization rewrote the token); the
PreparedSource materializes raw hashes only when a token was normalized,
and the detector compares raw hashes over the matched run to classify a
pair. A dropped annotation salts the raw hash of the token before it, so a
pair differing only in annotations inside the matched run is `renamed`.

Reporters: console prints `Clone found (fmt, renamed)`, ai appends
`(renamed)`, json adds "kind", sarif uses rule jscpd/similar-code (declared
only when referenced), codeclimate the same check_name. Default runs are
unchanged apart from the additive "kind": "exact" JSON field.

Also: GitHub Action inputs, docs section, fixtures/type2-demo with a README
of runnable commands, unit tests in tokenizer/core/reporter, CLI flag
tests, and a binary-level integration test.
2026-09-05 11:06:23 +02:00
Andrey Kucherenko e62bedb0fb Merge pull request #945 from kucherenko/feat/422-openmetrics-reporter
feat(rust): clone baseline — gate CI on new duplication only
2026-08-21 07:45:15 +02:00
dependabot[bot] bd9f6cb089 chore(deps): bump github/codeql-action from 3 to 4
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3 to 4.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/v3...v4)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: '4'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-21 03:07:15 +00:00
apk 4a72a70791 feat(rust): add --baseline-from-ref for stateless new-clone gating
Implements phase 2 of #944: an ephemeral baseline built from a git
ref's tree, for PR gates without a committed baseline file.

cpd --baseline-from-ref origin/main --fail-on-new-clones .

- checks the base ref out into a temporary detached git worktree
  (removed afterwards; shells out to git like blame does), scans it
  with the same detection configuration, and compares the current run
  against that in-memory fingerprint set via the phase 1 machinery
  (new baseline::apply_in_memory)
- scan paths are remapped into the base tree; paths absent there are
  simply new code
- a missing ref fails with a clear shallow-checkout hint (git fetch /
  fetch-depth: 0); conflicts with --baseline and --update-baseline,
  and --fail-on-new-clones now accepts either baseline source
- config key baselineFromRef; GitHub Action input baseline-from-ref

Refs #944
2026-08-19 12:19:53 +02:00
apk 251544b1dd feat(rust): add clone baseline to gate CI on new duplication only
Implements phase 1 of #944: a committed baseline of clone fingerprints
so CI tolerates recorded duplication and fails only on regressions.

- --baseline <file>: content-hash fingerprints (same value as the SARIF
  partialFingerprints jscpdCloneHash/v1) with per-fingerprint
  multiplicity counts; clones absent from the file are marked new
- --update-baseline: rewrite the file from the current run (creating it
  if missing) and print added/removed fingerprint counts
- --fail-on-new-clones[=N]: exit 1 when more than N (default 0) new
  clones are found, independent of --threshold
- config keys baseline / failOnNewClones, with the baseline path
  resolved relative to the config file
- reporters: [NEW] markers and "(N new)" found count in console and
  console-full, per-clone isNew plus newClones/newDuplicatedLines
  statistics in json, level "error" for new clones in sarif, and
  jscpd_new_clones / jscpd_new_duplicated_lines gauges in openmetrics
- baseline file is versioned and sorted one fingerprint per line for
  reviewable diffs and trivial merges
- GitHub Action: baseline, update-baseline, fail-on-new-clones inputs

Refs #944
2026-08-19 10:55:09 +02:00
dependabot[bot] 1d9314e6b5 chore(deps): bump actions/upload-artifact from 4 to 7
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 7.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v4...v7)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-14 09:04:57 +00:00
apk 89730b56ad fix(ci): resilient action install + release binary assets
- action.yml: retry install script 3x with 10s delay between attempts
- action.yml: fallback to npm install when install script fails
- action.yml: Windows install retries with jscpd then cpd fallback
- action.yml: add skip-install input for pre-installed binaries
- action.yml: add exit-code output from jscpd run step
- action.yml: parse-output step uses always() instead of success() || failure()
- action.yml: Run step captures exit code instead of failing workflow
- release.yml: download binary artifacts and upload as release assets
- release.yml: replace awk/sed with node for changelog extraction (exit 127 fix)
- release-rust.yml: same binary asset + node changelog fixes
- npm-rust-publish.yml: archive and upload platform binaries as artifacts
2026-06-13 08:48:01 +02:00
apk c62f176e9c fix: rename action to jscpd-copy-paste-detector for Marketplace uniqueness 2026-06-13 07:36:28 +02:00
apk 119a27bea1 feat: add GitHub Action for jscpd (Rust v5)
Composite action with curl install + npm fallback, SARIF upload,
artifact upload, and 28 configurable inputs mapping to cpd CLI flags.
2026-06-13 07:27:50 +02:00