Skip to content

Consolidate duplicate code and repair clone matching and canonical navigation - #541

Merged
MelbourneDeveloper merged 20 commits into
mainfrom
fix/regression-rollbacks
Sep 10, 2026
Merged

MelbourneDeveloper merged 20 commits into
mainfrom
fix/regression-rollbacks

Conversation

@MelbourneDeveloper

Copy link
Copy Markdown
Collaborator

TLDR

Consolidate duplicate parsing, rendering, and test code while repairing shared-subtree matching, wrapper-call filtering, Python fixture detection, and canonical navigation.

Details

  • deslop-core indexes shared-subtree fingerprints by node identity, so a statement and its enclosing body cannot overwrite each other when their byte ranges coincide ([FUSED-SHARED-SUBTREE-CORE]). The existing fingerprint walk remains the single implementation.
  • Call-scaffolding detection recognizes literal collections and wrappers while preserving the wrapper callee, body guards, and string-payload requirement ([CLONE-NOISE-LITERAL-VARIATION-CALLS]). Python fixture decorators are resolved from the AST, including calls, multiline arguments, and class methods ([CLONE-NOISE-PY-PYTEST-FIXTURE]).
  • Cluster panels retain every cluster's original canonical occurrence through dirty-file projection and subsequent navigation ([VSIX-PAIR-COMPARE]). Comparison endpoints reuse the generated wire type; file/folder tooltips and tree settings share implementations.
  • Consolidate language normalization, node searches, counters, fingerprint traversal, and fixtures/assertions across the Rust crates and extension tests. Remove redundant implementations while retaining their behavioral contracts.
  • Re-enable both accessor-pair contracts, preserving measured evidence and exact positive controls; update the skip registry and plan together. Restore the original Python node floor alongside the stricter floor and restore the unknown rank-band assertion. Update specs, MCP/configuration documentation, repository indexes, and mechanically generated validation reports. Add local report-generation utilities.

No coverage thresholds are lowered. The report wire changes already on main are not introduced again by this PR. Related to #532; issues remain open.

How Do The Automated Tests Prove It Works?

  • Shared-core tests reproduce the equal-range node collision and then verify fingerprint identity, credited statements, and public scan/comparison parity with both copied and unrelated controls.
  • a_differing_wrapper_callee_is_not_literal_variation fails when differing constructors are treated as interchangeable payload. The call-filter suite covers callback bodies, literal variation, and invariant authored logic.
  • Python fixture tests exercise decorated functions and class methods, multiline decorator calls, and unrelated decorator arguments; the inherited-contract CLI test runs both original and stricter node floors.
  • Both active accessor-pair tests reject the unrelated whole functions and original short spans while retaining pair evidence. The only published control must have rank 1, mass 32, exactly two occurrences, and the expected report metrics.
  • Extension and Playwright tests exercise dirty-file projection followed by navigation and canonical comparison. Existing assertions and moved fixture declarations are retained.
  • Full make ci passed on unchanged sources. Rust, extension-host, and webview coverage gates passed with unchanged thresholds; the command evidence is in reports/pr-validation.md.

For AI

The diff against main is the complete scope. The main tree is identical to ancestor bd920260; the branch reconciles that squash ancestry without changing its validated source tree. Review and attribution use baseline f92300e5e1004ef6c53a94174a0d7e842232ec80. Preexisting registry output and embedding defects are not claimed as newly introduced regressions. reports/README.md indexes the local measurements; GitHub records subsequent CI and merge state.

MelbourneDeveloper and others added 20 commits September 7, 2026 19:40
)

PR #485 made ReportCluster.mass an integer count (canonical nodes × additional
visible occurrences, [RANK-MASS-SUM]) but left every render path on the
two-decimal helper written for the old fractional weight, so the panel header,
its body sentence, the stat row, the tree row tooltip, the file and folder row
descriptions, the cluster document and the copy-for-AI payload all printed
"527.00" for a count that can never carry a fraction — while the CLI text
report printed "mass=527" for the same cluster.

One formatter, `formatMass`, now renders a mass with no decimal point, and it
is the only way a mass reaches text: the four-decimal `formatScorePrecise`
had no remaining caller and is gone, and the two AI payloads that quoted mass
at different precisions now print the same string.

[RANK-MASS-SUM] in docs/specs/pipeline.md states the rule: mass is a whole
number, every surface prints it as one, and the extension and the CLI print
the identical string.

Tests:
- mass-format.unit.test.ts pins formatMass against the score formatter, pins
  that a measured pair signal keeps its two decimals, walks the parsed source
  of every file that prints a mass (extension host and webview) and fails if
  any rendered mass does not pass through formatMass, and scans the fixture
  with the staged CLI to assert the extension prints the exact string the
  text report prints for every cluster.
- tree.topOffenders.unit.test.ts asserts the cluster tooltip, file row and
  folder row print "527", never "527.00".
- cluster-document and command-impls tests now assert the exact whole-number
  line instead of the presence of the word "mass".
- The playwright smoke fixture no longer lies about the wire: its masses are
  integers.

The shared TS-AST walkers (descendants, hasDescendant) and parser move into
webview-source.helpers.ts; the two test files that each carried a private
copy now import them, which removes three duplicate clusters from
clients/vscode/src.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ool (gh #523)

The MCP decodes every document the LSP serves it over IPC, and the two
binaries can come from two builds: an extension bundle installed on 31 August
beside an engine built after PR #485 renamed ReportCluster.weight to mass on
2 September. That drift surfaced as serde's raw field error — "ipc transport
failure: ipc report parse: missing field `weight`" — on every agent-facing
tool, naming neither binary, neither version, nor a remedy. PR #144 wired the
IPC read path without any version negotiation, even though every report
stamps its producer in tool_version and the fingerprint cache already
partitions on it.

Every IPC decode now passes through one guard, backend/wire.rs::decode
([MCP-IPC-WIRE-MISMATCH]): a reply stamped with another producer version is
refused before it is decoded, and a reply that does not decode at the same
version — the development-build case, where both binaries say 0.0.0-dev — is
refused the same way. The refusal is a named BackendError::WireMismatch that
carries both versions, the IPC method, the endpoint, what was found and the
remedy (reinstall the VSIX so both binaries ship from one bundle, then
re-run the analysis). The ten decode sites — report/get, report/forFile,
report/forRange, session/config, duplicates/findSimilar, cluster/byId,
pair/compare, merge/plan, embedding/listModels, embedding/setModel — no
longer each carry their own "ipc <thing> parse" catch-all.

docs/specs/mcp.md gains [MCP-IPC-WIRE-MISMATCH] beside [MCP-IPC-CLIENT], and
[MCP-TESTING] names the refusal.

Tests:
- backend/wire/tests.rs pins the guard on a report another release wrote
  (tests/fixtures/report-from-another-release.json — the adversarial input,
  refused, never decoded): a foreign stamp is refused before decoding and no
  field error appears; a same-version drift names the moved field; an
  unstamped page says it carried no version; a report from this version
  decodes.
- tests/ipc_wire_mismatch.rs drives the real deslop-mcp binary against a stub
  LSP serving that report: `duplicates` is refused on the stamp, cluster-by-id
  — whose page carries no stamp — is refused on the moved field, both name
  the MCP version, the engine version, the socket path and the remedy, and
  neither returns a page an agent could read as "no duplicates".
- The stub LSP the #148 suite carried privately moves to
  tests/common/stub_lsp.rs with a scripted responder, and #148 now uses it.

The in-bundle gate the issue asks for already exists: scripts/verify-vsix-package.mjs
probes every bundled binary's --version against shipwright.json, so one bundle
cannot ship binaries from two builds; this change covers the cross-install case
that gate cannot see.

Completes the guard that 9e284bd captured mid-flight: the remaining six decode
sites, the fixture and constants renamed to say what they are (a report from
another release, refused, never decoded), and the two mismatch suites
collapsed onto shared helpers.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… by that kind (gh #521, #522)

#485 replaced the clone taxonomy on cluster surfaces with the neutral title
"Duplicate code" and re-keyed every colour to the mass rank band, so the
tree, bubble, underline, webview and HTML report could no longer say what
relation a cluster holds, and rank-1 shape-only scaffolding painted crimson.
This restores the taxonomy honestly instead of reviving the pre-#485
cluster-averaged signals.

Engine ([CLONE-KIND-FOLD]): every cluster carries a `kind` — the weakest
pair classification between its canonical occurrence and any other member,
measured through the same pair/compare path an explicit comparison uses,
reading the embedding pass's cosines rather than re-embedding. A member the
direct comparison does not admit folds to `loosely_similar`. `ClusterKind`
is generated from live-ipc.td; `buckets.rs` is the one label registry
([CLONE-KIND-LABELS]), with no advice sentences.

Surfaces: JSON/text/markdown carry the wire label; HTML groups cards into
one expander per kind and declares `--kind-*` colours; the terminal summary,
LSP diagnostic message and data, and MCP cluster summaries name the kind.

Extension ([CLONE-KIND-COLOR]): one paint table in design.ts, contributed
as `deslop.kind.*` theme colours; the webview imports it instead of
mirroring it. Colour follows the kind on the tree icon, bubble, underline
and badges; the rank band keeps only its glyph density. Severity grouping
becomes kind grouping; `severity.ts` and its test are deleted.

Tests: every retired "Duplicate code" assertion now pins the kind title;
bucket_groups pins identical-vs-nearly-identical in rank order; the
report goldens are re-blessed and their contracts assert `identical` over
the Type-1 corpora; the C# byte-truth suite pins that a wrapper-renamed
window folds to `nearly_identical`; live-delta coverage walks the enum
leaf; `kind.unit.test.ts` holds the extension's titles, taxonomy names and
colours to what the bundled CLI renders and package.json contributes.

Specs: taxonomy.md gains [CLONE-KIND], [CLONE-KIND-FOLD], [CLONE-KIND-LABELS],
[CLONE-KIND-COLOR], [CLONE-KIND-TESTING]; severity.md becomes the
two-channel model; SPEC, vsix, facets, fused, lsp, mcp, pipeline,
REPORTING-CONTEXT and webview-runtime follow.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ff with the engine's verdict on that pair (gh #524)

The cluster panel's compare flow, plus the detector and spec repairs the same audit surfaced:

- Engine: `PairEvidence.text_identity` (`byte_identical`, `indentation_only`, `different`) is read once from the pair's raw bytes next to the byte-identity verdict, so a copy that differs only by how its lines are indented says so on the wire ([FUSED-PAIR-SIGNALS]). Admission and classification are unchanged; the field is evidence only.
- Extension: the native diff is titled `<left> vs <right>: <verdict>`, the verdict read from `deslop/pairCompare` (Identical bytes, Differs only by indentation, or the pair's clone kind). Tapping one occurrence row and then another compares exactly those two; the pick lives in the store; no per-row "Select for comparison" button and no gated "Compare selected occurrences" button exist ([VSIX-PAIR-COMPARE]).
- Detector (gh #520): Rust type references no longer earn standalone fingerprints ([PIPELINE-FINGERPRINT-MERKLE-TYPE-REFERENCE]); a copied collaborator property may rename the operation called through it, so a renamed collaborator no longer defeats a genuine copy. Pinned by the Python inherited-boundary and Rust iterator-idiom suites.
- Specs (gh #526): admission.md and pipeline.md carry the new contracts; cross-reference and restoration reports under reports/ are regenerated mechanically.

Tests: pair_comparison (indentation-only copy), report_api, compare-title.unit, webview-cluster.unit, the Playwright smoke (two-row tap), clusters.e2e (one-click canonical compare on Alpha/Beta; indentation-only Gamma/Delta).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…n one is left broken

`_vsix-install-code` deletes the unpacked extension directory before it
installs, so a higher Marketplace version cannot keep winning. That one
directory is what every profile points at, and `code --install-extension`
registers the replacement in the default profile alone — so every other
profile was left naming a directory the delete had just removed, showing
"Unable to read file '.../package.json'" with no working extension behind
it, after a build that reported success. Four of the developer's profiles
were dead at once.

The install now names every profile that already had Deslop.live, and no
profile that did not, then proves no profile is left dangling.
[DEPLOY-VSIX-INSTALL-PROFILES]

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… scan would report

A cluster id names one finding again (gh #531). The digest plus the member
paths could not separate two copies of one shape inside one file, which feed
it the same shape and the same path, so the id now also carries the cluster's
position rank among the clusters sharing both. The first cluster of a source
is still named by the source alone, so every id published before this is
unchanged and a finding keeps its id when another copy of its shape appears
after it. Derivation moves to cluster/identity.rs; [PIPELINE-DETERMINISM]
states the rule.

The shared-subtree rescue now requires the code two endpoints share to be a
clone the scan would report on its own: at least min_nodes, checked before the
content axes run ([FUSED-SHARED-SUBTREE-CORE]). Three site/tests scenarios open
with the two Playwright lines every browser test starts with, and the one that
shares nothing else rode the rescue onto the other two on a core of twenty
nodes measuring exactly the content floor. The floor is the scan's own rather
than the rescue's endpoint floor, because a near-miss is by construction
smaller than its endpoints: python-type3's cores are 22 to 25 nodes and are
real clones at --min-nodes 8. Both readers of a core share one judge_core.

The two content_gate_signal_honesty accessor pins assert that the gh #460
false positive is reported. The fixture describes that pair as unrelated and
the aligned core refuses it on an inconsistent rename, so they are ignored
under [SKIP-UNFINISHED] citing gh #532, assertions untouched, with rows in
CURATED_SKIPS and a plan section that says what closes them. The pair is held
CLEARLY OUT in a new self-register, corpus/register/deslop.json, pinned at the
merge-base where the original blocks live, so the score gate fails the moment
anything reports it again. Its fifteen CLEARLY IN entries are byte-identical
pairs verified by diff at that commit; the register is not blinded and says so.

site/tests at floor 30 goes from 4 mixed-span clusters to 3 and 12.50% to
12.14%. Corpus score gate: 100% on click, cobra, axios and Deslop, no false
negatives or positives. E2E 490 passed, core lib 215, core integration 228 and
193, LSP 62 and 80 under the Makefile feature matrix, MCP 10 and 126.
@MelbourneDeveloper
MelbourneDeveloper merged commit 47947dd into main Sep 10, 2026
17 checks passed
@MelbourneDeveloper
MelbourneDeveloper deleted the fix/regression-rollbacks branch September 10, 2026 12:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant