Skip to content

Roll back the post-#485/#518 regressions: canonical and two-row compare with the engine's verdict, whole-number mass, clone-kind titles and colours, foreign-build IPC refusal, detector false-cluster fixes, spec ids (#520-#526) - #529

Merged
MelbourneDeveloper merged 11 commits into
mainfrom
fix/regression-rollbacks
Sep 9, 2026

Conversation

@MelbourneDeveloper

Copy link
Copy Markdown
Collaborator

TLDR

Rolls back the regressions #520 to #526 that landed with #485 and #518: one-click canonical compare and two-row compare are back in the cluster panel with the engine's verdict in the diff title, mass prints as a whole number, every surface titles and colours a cluster by its clone kind, the MCP tools refuse a foreign build's reply, the detector stops welding unrelated member calls and Rust type annotations into false clusters, and every spec id the audit reported resolves.

Details

Compare (#524). Each non-canonical occurrence row carries a one-click Compare that opens VS Code's diff with the canonical range on the left and the clicked range on the right; the occurrence and cluster context menus expose the same command. Tapping one row and then another compares those two, first tap on the left. No per-row select button and no gated compare button exist. The diff title names both files and repeats the engine's verdict on exactly that pair: Identical bytes, Differs only by indentation, or the pair's clone kind. The engine reports that verdict itself: PairEvidence.text_identity is byte_identical, indentation_only, or different, measured once from the raw bytes beside the existing axes. The extension calculates nothing.

Mass (#525). Mass is an integer in the engine and prints as one on every extension surface.

Clone kinds (#521, #522). Every cluster carries its folded clone kind on the wire: the weakest pair classification between its canonical occurrence and any other member. The HTML report, terminal summary, LSP diagnostics, tree, hover bubble, and cluster panel title and colour the cluster by that kind from one label registry; the contradictory colour tables are gone.

IPC (#523). Every MCP tool refuses a reply whose build name differs from its own instead of decoding a partial page as an empty result.

Detector (#520). A changed member-call selector on a receiver the matched region does not declare is a content contradiction, so page.click() against page.fill() no longer proves a rename, while a copied collaborator property that renames consistently still does. Rust type annotations keep their full hashes and node counts inside the code that uses them, so iter().map().collect() idioms over unrelated types no longer cluster. The 18-occurrence false cluster on this repository is gone and the real two-occurrence clone stays at the top.

Spec ids (#526). The 36 reported spec ids plus the two new rules resolve to definitions; the cross-reference audit is mechanical (scripts/repository/spec-crossrefs.py) and its report is checked in.

Housekeeping. commands/register.ts is split under the 500-line limit: compare commands in commands/compare.ts, shared command-target guards in commands/deps.ts.

How Do The Automated Tests Prove It Works?

  • crates/deslop-core/tests/pair_comparison.rs: an_indentation_only_copy_reports_indentation_as_its_whole_difference writes one Rust function and a re-indented copy, runs the live engine, and asserts the cluster folds to nearly_identical, the pair's text_identity is indentation_only, structural overlap and content agreement are exactly 1.0, and the pair is admitted. The byte-identical and content-rejected tests assert byte_identical and different.
  • clients/vscode/src/test/suite/clusters.e2e.test.ts (real LSP, real VS Code host): one deslop.compareWithCanonical call on the Alpha.cs/Beta.cs cluster opens a diff whose left URI is the canonical range and right URI is the clicked range, whose title equals the title built from the engine's own deslop/pairCompare answer, names both files, ends with the kind title, and never claims an indentation-only difference. The Gamma.cs/Delta.cs pair is titled Differs only by indentation and its two diff documents differ as bytes but are equal line by line once indentation is removed.
  • clients/vscode/scripts/playwright-webview-smoke.spec.ts taps two occurrence rows in the built webview bundle and asserts the host receives compare/pair with exactly those two endpoints in tap order, the picked row is marked and released after the second tap, tapping the picked row again releases it without posting, and no "Select for comparison" or "Compare selected occurrences" control renders.
  • clients/vscode/src/test/unit/webview-cluster.unit.test.ts parses the TSX and asserts the row itself takes the tap, the pick is one store signal, the second tap posts through the store, and the retired copy is absent. compare-title.unit.test.ts pins the verdict for every classification and the engine-less title.
  • crates/deslop/tests/python_inherited_contract_boundary.rs runs both directions in one scan: InvoiceWorker.synchronise/UserWorker.synchronise, a copy with every local, parameter and collaborator renamed under a base that declares neither, must publish with its real files and ranges; the two LedgerSink implementations that an ABC forces into one signature must stay hidden. rust_issue_147_iter_collect_idiom.rs asserts the iterator idiom over unrelated types forms no cluster while the rust-small control clone still reports as one nearly_identical cluster of two occurrences.
  • crates/deslop/tests/cli/bucket_groups.rs, csharp_type1_type2_byte_truth.rs, and the extension kind.unit.test.ts pin the kind titles, taxonomy names, and colours on every surface; crates/deslop-mcp/src/backend/wire/tests.rs pins the foreign-build refusal on every tool.

Gates run on the final tree: cargo fmt --check clean; make lint exit 0; cargo clippy --workspace --all-targets --features deslop-core/live,deslop-lsp/profiling -- -D warnings clean; cargo test -p deslop --test suite 486 passed, 0 failed; cargo test -p deslop-core --features live --test suite pair_comparison and report_api green; extension npm run typecheck, tsc --noEmit, npm run lint clean; npm run test:playwright 8 passed; an isolated VS Code host run of the compare, kind, mass and projection unit suites plus clusters.e2e on freshly staged release binaries, 71 passed. The full cargo test --workspace and the exact make ci are running on this tree; their results follow as a comment on this PR.

For AI

Attribution: reports/regression-attribution-2026-09-07.md; restoration evidence: reports/regression-restoration-2026-09-07.md; spec cross-references: reports/spec-crossrefs-2026-09-07.md. Wire: PairEvidence.text_identity: PairTextIdentity in docs/models/live-ipc.td (generated into wire_generated.rs and wire-generated.ts, never committed) replaces a fourth boolean that would trip clippy::struct_excessive_bools; ReportCluster.kind: ClusterKind is the [CLONE-KIND-FOLD] of pair classifications against the canonical. Spec ids: [VSIX-PAIR-COMPARE], [VSIX-PAIR-EVIDENCE], [FUSED-PAIR-SIGNALS], [FUSED-CONTENT-GATE-CALL-TARGET], [PIPELINE-FINGERPRINT-MERKLE-TYPE-REFERENCE], [CLONE-KIND-FOLD], [CLONE-KIND-LABELS], [MCP-TOOL-COMPARE-PAIR], [REPORT-CONTEXT-PAIR]. Extension: compare/title.ts (pairVerdict, compareTitle, measurePair over deslop/pairCompare), commands/compare.ts, commands/deps.ts, store.ts (pickedOccurrence, tapOccurrenceRow), cluster/OccurrenceList.tsx.

Related issues: #520, #521, #522, #523, #524, #525, #526. Supersedes #528.

🤖 Generated with Claude Code

MelbourneDeveloper and others added 6 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>
@MelbourneDeveloper
MelbourneDeveloper marked this pull request as draft September 7, 2026 13:14
MelbourneDeveloper and others added 5 commits September 8, 2026 09:32
…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 marked this pull request as ready for review September 9, 2026 09:25
@MelbourneDeveloper
MelbourneDeveloper merged commit 978002e into main Sep 9, 2026
17 checks passed
@MelbourneDeveloper
MelbourneDeveloper deleted the fix/regression-rollbacks branch September 9, 2026 09:25
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