Skip to content

Fingerprints change when a measurement changes and collide for repeated findings — fingerprint v2 #97

Closed

Description

@serge-ivo

Problem

fingerprintIssue (src/issue-fingerprint.ts:16-19) hashes check + rule + normalised path + lower-cased message (:42-48).

  1. Unstable across commits. Many messages embed a measurement: "AdminLayout: 65 lines (max 60)" (runners/complexity.ts:43), "~4620 tokens (>4000)" (context), "329 lines — consider splitting" (standards large-file), "imports 12 modules" (architecture), "Barrel file with 5 re-exports" (performance). When the number moves, trend.ts reports one fixed + one new. On a 1,501-issue report (CLI 0.54.4) 600 messages contain digits.
  2. Duplication identity includes line numbers. runners/duplication.ts:82 sets file to `${fileA}:${lineA} ↔ ${fileB}:${lineB}` and :81 puts the clone length in the message; any edit above either clone re-identifies it.
  3. Not unique within a report. Repeated findings in one file share a fingerprint (on that report: 156 groups covering 529 issues; mostly error-handling, performance, accessibility, type-safety). delta.ts:57-73 already counts them as a multiset, but trend.ts:55-60 collapses them in a Map, so fixing one of two identical findings reports nothing.

What to do (cheapest first)

  1. trend.ts multiset — count per fingerprint like delta.ts. Ships alone, no fingerprint change.
  2. Per-runner identity (v2):
    • File-scoped measured rules (standards large-file, context high-token-count, architecture high-fan-out, performance barrel-import): key = check + rule + file; message excluded.
    • complexity: key = check + rule + file + function name (f.name).
    • duplication: key = sorted pair of paths without :line + hash of the normalised snippet.
    • Everything else: current key.
      Implemented as an optional per-runner subject (emitted as Issue.subject once schema 0.6.0 declares it); never a global digit regex.
  3. Disambiguate repeats with a content anchor: for issues with file + line, add a hash of the whitespace-normalised source line (read from the raw file via FileInventory, not SFC-extracted content); only among identical line contents append an occurrence ordinal. Issues without a line: ordinal by emission order.
  4. Versioning: meta.fingerprintVersion = 2. In trend.ts / delta.ts, when the two reports' versions differ (absent = 1), recompute v1 on both sides from check/rule/file/message (available on IssueSnapshot too) so the boundary scan reports no churn. Do not rely on readIssueFingerprint's stored-value preference (:37-40) — that is what would cause the churn.

Rejected

  • Global "replace digits with #": dependencies issues have no file/rule and say "95 high vulnerabilities" (runners/dependencies.ts:83-93); 95 → 96 would keep identity and hide a new vulnerability; also collapses version strings and CWE-nn.
  • Pure line-order ordinal: inserting an identical finding above existing ones shifts every later ordinal, so the "new" finding reported is the wrong one and any per-fingerprint state attaches to the wrong finding. ~⅓ of issues have no line, so line order is not even total.
  • Symbol for type-safety: it is a line regex (runners/type-safety.ts:147) with no AST; its messages carry no measurement anyway. The content anchor covers it.

Acceptance criteria

  • Each measured rule above: same finding with a changed number keeps its fingerprint (test per runner).
  • Duplication: inserting lines above both clones keeps the fingerprint.
  • Two identical findings on different lines get distinct fingerprints; inserting a third identical line above them leaves the first two unchanged; fixing one reports exactly that one fixed.
  • dependencies count change still reports a change.
  • A trend/delta between a v1 report and a v2 report of unchanged code reports 0 new / 0 fixed.
  • CHANGELOG notes the fingerprint version change.

Open question

Project-level aggregate findings ("14 unused files detected by Knip", "Mixed styling approaches: tailwind (1031)…", "Only 20% of exports…") are metrics phrased as findings. Keep the message in their key (churn on every change, today's behaviour) or key by rule (identity stable, count change invisible to identity)? Recommend: keep message for now; per-advisory dependency findings would be the real fix.

Constraints

Related: #45 (closed, introduced fingerprints), app#27 (fingerprints unread in the monitor).


🤖 Filed with Claude Code

Activity

  1. serge-ivo commented on Oct 5, 2026

    @serge-ivo
    ContributorAuthor

    Delivered in two stacked PRs:

    Both: pnpm build is clean and pnpm lint has 0 errors (62 warnings, same as main). Aggregate findings keep their message in the key, as recommended. Schema is not bumped: subject and fingerprintVersion are typed locally until schema#6 publishes.

    mcp's report-cli-0.55.0.json fixture changes at the next CLI_SPEC bump that includes this, so note it in that pin issue. This stays open until both PRs merge.

  2. serge-ivo commented on Oct 5, 2026

    @serge-ivo
    ContributorAuthor

    Documented limit (from review of #116): the acceptance criterion "inserting a third identical line above them leaves the first two unchanged" is met for counts but not for naming.

    • Counts: inserting a third identical finding above two reports exactly 1 new / 0 fixed, in delta and trend.
    • Naming: the finding reported as new is the last occurrence by line order, not the inserted one.
    • Why it can't be done: for textually identical lines, a report with them at L1 and L3 is the same whichever one is new. The only disambiguator, hashing neighbouring lines, would re-identify anchored findings on every adjacent edit.
    • This is pinned by the test "identical source lines: counts are right, naming is by line order" in src/fingerprint-v2.test.ts and stated in the CHANGELOG.

    Vue/Svelte findings are not content-anchored (mixed line provenance across runners) and use line-order ordinals instead.

  3. added a commit that references this issue on Oct 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions