Skip to content

Expand run-sampler --compact: results register, vanished-output grouping, scope tier, visual-only tier - #265

Open
michieldegezelle wants to merge 11 commits into
mainfrom
sampler-compact-diff-v2
Open

Expand run-sampler --compact: results register, vanished-output grouping, scope tier, visual-only tier#265
michieldegezelle wants to merge 11 commits into
mainfrom
sampler-compact-diff-v2

Conversation

@michieldegezelle

@michieldegezelle michieldegezelle commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Summary

DO NOT MERGE UNTIL NL AND LU PILOT PHASE HAS FINISHED

Follow-up to #236/#261's --compact mode, driven by real feedback while reviewing PR #894's sampler run against nl_market: the named_results-only diff missed real signal and got unreadable fast (78KB/~19.7K tokens on a 202-entry NL run). This PR:

  • Diffs the results register alongside named_results - 0/1 flag arrays render as a triggered-indicator count (e.g. 1/1 triggered), other shapes (raw numeric values) fall back to a plain value diff.
  • Groups entries whose named_results/results collapsed entirely (≥3 keys lost at once, all to undefined) into a single "output vanished" finding per template - previously this showed as dozens of individual "value → undefined" lines and could dominate the whole summary.
  • Adds a scope/dependency tier (dependencies/rollforward_params/required_keys_missing), kept separate from the data diff so a dependency change is never mistaken for a data regression. dependencies renders as one sub-line per category (ledgers/handles/account ranges/company.attributes) instead of a semicolon-packed single line.
  • Adds a visual-only tier: entries where view.html changed but the data diff found nothing to explain it - a rendering-only regression the old diff couldn't see at all. Described field-by-field via Silverfin's data-name attribute where one anchors the change, with an honest "compare the files directly" fallback where it doesn't.
  • Truncates long values (accounting-policy paragraphs, notes) and caps per-template/per-entry change lists, always disclosing what was elided rather than silently dropping it.
  • Every finding now links to a concrete follow-up: the entry's live app URL when known, else its path inside the results directory.
  • Adds run-sampler --from-zip <path>: build the compact diff from an already-downloaded results.zip, with no sampler run or network call - lets a reviewer re-analyze a real result (or iterate on the compact-diff format itself) without a 30-60 min re-run against the shared staging backend.

Net effect measured on the real PR #894 zip (202 entries, 6 templates changed + 21 collapsed + 9 visual-only): output went from 78KB/~19.7K tokens (old format) to ~31.7KB while covering strictly more signal.

Why keeping this open for now

This is currently pointed at from nl_market's and lu_market's run_sampler.yml (temporarily, on this branch) so we can gather real feedback across their approved PRs before merging - see the follow-up in each of those repos.

Test plan

  • npm test - 640/640 passing

  • npm run lint - clean

  • Validated against a real downloaded results.zip from nl_market PR #894 via --from-zip, output reviewed in full

  • Real end-to-end validation via a live download URL (only the local-zip path has been exercised against real data so far; the live-download path is covered by mocked tests)

  • Feedback gathered from nl_market/lu_market usage before merge

  • Skip bumping the CLI version

🤖 Generated with Claude Code

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Code review skipped — your organization's overage spend limit has been reached.

Code review is billed via overage credits. To resume reviews, an organization admin can raise the monthly limit at claude.ai/admin-settings/claude-code.

Once credits are available, reopen this pull request to trigger a review.

@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • ✅ Review completed - (🔄 Check again to review again)

Walkthrough

The compact sampler diff now includes results, scope/dependency changes, vanished output, and visual-only view.html changes. Output is capped and grouped. run-sampler --from-zip generates the diff from a local archive without network calls.

Changes

Compact sampler diff

Layer / File(s) Summary
Diff signal helpers
lib/liquidSamplerCompact.js, tests/lib/liquidSamplerCompact.test.js
Register parsing, result vectors, scope/dependency comparisons, missing-key handling, truncation, and field-level HTML change detection were added and tested.
Tiered extraction and Markdown output
lib/liquidSamplerCompact.js, tests/lib/liquidSamplerCompact.test.js
Compact extraction and formatting now group data, vanished output, scope, and visual-only changes with caps, disclosures, example links, and expanded coverage.
Live and local zip execution
lib/liquidSamplerRunner.js, bin/cli.js, tests/lib/liquidSamplerRunner.test.js, tests/bin/cli.test.js
Runner extraction is shared across live and buffer workflows, --from-zip performs offline processing, and CLI partner validation depends on the selected path.
Release metadata
CHANGELOG.md, package.json
Release metadata documents version 1.58.0 and the compact-diff changes.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly summarizes the primary compact-diff enhancements and is concise enough for a pull request title.
Description check ✅ Passed The description provides a detailed change summary, testing results, rollout constraints, and checklist status, but omits the separate Reviewer Checklist.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch sampler-compact-diff-v2

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@bin/cli.js`:
- Around line 554-556: Update the CLI flow around options.fromZip and the
-p/--partner validation so the from-zip offline path does not require a partner
id. Preserve partner validation for all non-zip execution paths, and continue
passing options.partner to LiquidSamplerRunner when available.

In `@lib/liquidSamplerCompact.js`:
- Around line 734-736: The headline construction using summary.templatesChanged
and summary.entriesChanged misrepresents runs whose findings exist only in
collapsed, scope, or visual tiers. Update the summary-count logic and the
headline generation around summary.templatesChanged, summary.entriesChanged, and
the nothingChanged guard so non-data findings are reflected in the counts, or
explicitly label the headline as data-diff-specific and suppress the
contradictory zero-count line when other tiers contain findings.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 51b15e33-e97d-4eea-8a7b-b93f47d281b7

📥 Commits

Reviewing files that changed from the base of the PR and between 3bfdeb7 and fad591d.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (7)
  • CHANGELOG.md
  • bin/cli.js
  • lib/liquidSamplerCompact.js
  • lib/liquidSamplerRunner.js
  • package.json
  • tests/lib/liquidSamplerCompact.test.js
  • tests/lib/liquidSamplerRunner.test.js

Comment thread bin/cli.js
Comment thread lib/liquidSamplerCompact.js Outdated
Comment thread lib/liquidSamplerCompact.js Outdated
Comment thread lib/liquidSamplerCompact.js
Comment thread lib/liquidSamplerCompact.js Outdated
Comment thread lib/liquidSamplerCompact.js Outdated
Comment thread lib/liquidSamplerCompact.js
@michieldegezelle

Copy link
Copy Markdown
Contributor Author

Addressed all outstanding review comments in 3aa9484:

  • -p/--partner required even for --from-zip (CodeRabbit): -p/--partner is now optional at the Commander level; printCompactDiffFromZip never touches the partner/sampler API, so that path no longer needs it. Every other path now gets a manual consola.error check instead (matches the rest of the CLI's convention of .option + manual validation rather than .requiredOption).
  • Headline misreports "0 template(s) changed" when findings are only in the collapsed/scope/visual tiers (CodeRabbit): the headline is now worded as data-diff (named_results/results) specific and only prints the "N template(s) changed" count when that tier has findings; otherwise it says so plainly and points to the other tiers below instead of printing a contradictory "0 ... 0" line.
  • dependencyLedgers/.account_ranges throws on a malformed (non-array) register value: diffStringSet and dependencyLedgers now treat a non-array value as empty instead of throwing, so one bad entry can't abort the whole run's diff.
  • exampleRef interpolates an unvalidated url into Markdown: now validated as http(s) before use, falling back to the output/... path reference otherwise - relevant now that --from-zip's sample_entry_ids.yml isn't guaranteed to come from Silverfin's own backend.
  • <input> field values weren't entity-decoded, unlike <textarea>/<select>: fixed for consistency.
  • results: null vs. a genuinely absent register both rendered as "undefined": formatResultsValue now distinguishes them, so a legitimate explicit null no longer gets miscounted into the "output vanished" collapse heuristic.
  • Dedup key collision on truncated long values: truncated values now include a short content hash alongside the length, so two different long values sharing the same truncated prefix and total length no longer collide under the same cross-entry dedup key.

Added/updated tests for all of the above; npm test (647/647) and npm run lint both clean.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@lib/liquidSamplerCompact.js`:
- Around line 700-712: The exampleRef function currently validates only the URL
scheme, allowing Markdown link syntax injection through destinations containing
delimiters. Parse the URL, require an http(s) protocol, and reject or escape
Markdown-sensitive characters before interpolation; apply the same sanitizer to
any visual-only link rendering.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 05e1bb82-e9ba-4c30-a8c8-426dbbd6142f

📥 Commits

Reviewing files that changed from the base of the PR and between fad591d and 3aa9484.

📒 Files selected for processing (4)
  • bin/cli.js
  • lib/liquidSamplerCompact.js
  • tests/bin/cli.test.js
  • tests/lib/liquidSamplerCompact.test.js
🚧 Files skipped from review as they are similar to previous changes (2)
  • bin/cli.js
  • tests/lib/liquidSamplerCompact.test.js

Comment thread lib/liquidSamplerCompact.js

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
tests/lib/liquidSamplerRunner.test.js (1)

326-351: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Cover non-data diff tiers in this regression test.

This fixture only flags entry 1 through named_results. Add scope, vanished-output, and visual-only fixtures/assertions so a regression in diffEntryKeys cannot silently exclude those entries from diffs/.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/lib/liquidSamplerRunner.test.js` around lines 326 - 351, The regression
test around “adds view.html before/after…” currently covers only a named_results
diff; extend its ZIP fixture with separate entries representing scope,
vanished-output, and visual-only differences, then assert each flagged entry
receives both before and after view.html files under diffs/. Keep an unchanged
control entry and retain the assertion that original ZIP contents remain intact,
updating the expected success count to include all covered entries.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@tests/lib/liquidSamplerRunner.test.js`:
- Around line 326-351: The regression test around “adds view.html before/after…”
currently covers only a named_results diff; extend its ZIP fixture with separate
entries representing scope, vanished-output, and visual-only differences, then
assert each flagged entry receives both before and after view.html files under
diffs/. Keep an unchanged control entry and retain the assertion that original
ZIP contents remain intact, updating the expected success count to include all
covered entries.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6b02cfc3-f2f1-48ac-83ab-3dc25e40449d

📥 Commits

Reviewing files that changed from the base of the PR and between cb345b6 and 07534f9.

📒 Files selected for processing (6)
  • bin/cli.js
  • lib/liquidSamplerCompact.js
  • lib/liquidSamplerRunner.js
  • tests/bin/cli.test.js
  • tests/lib/liquidSamplerCompact.test.js
  • tests/lib/liquidSamplerRunner.test.js
🚧 Files skipped from review as they are similar to previous changes (3)
  • bin/cli.js
  • lib/liquidSamplerCompact.js
  • tests/lib/liquidSamplerCompact.test.js

Comment thread lib/liquidSamplerRunner.js Outdated
Comment thread lib/liquidSamplerRunner.js

@michieldegezelle michieldegezelle left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 MinorCHANGELOG.md [1.58.0] documents the --compact expansions and --from-zip, but the new --add-diffs-folder option from 07534f9 is missing. Worth a sentence on the 1.58.0 entry so the release notes match the user-facing surface (hidden command, but this repo still changelogs those).

@michieldegezelle
michieldegezelle force-pushed the sampler-compact-diff-v2 branch from 5accd6b to 0bf8f15 Compare July 30, 2026 08:29
Comment thread CHANGELOG.md
@michieldegezelle michieldegezelle self-assigned this Jul 30, 2026
@BenjaminLangenakenSF
BenjaminLangenakenSF self-requested a review July 30, 2026 12:48

@BenjaminLangenakenSF BenjaminLangenakenSF left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found some issues with Claude, and asked for pre-made prompts that you can easily verify with your AI tool of choice 😉


1. Flag-array reorders render as an identical no-op line — and dedupe into each other

In lib/liquidSamplerCompact.js, formatResultsValue (line 203) reduces a flag-shaped results vector to a triggered count. So a genuine change where the same number of indicators fire but different ones do — ["1.0","0.0"] → ["0.0","1.0"] — renders as:

- `results`: `1/2 triggered` → `1/2 triggered`

diffResultsRegister correctly detects it as a change (it compares the raw JSON), but the rendered line tells a reviewer nothing, and reads like a bug in the diff tool itself.

It's worse than cosmetic: that rendered string doubles as the cross-entry dedup key in changeDedupKey (line 565). Two entries of the same template with opposite reorders collapse into one [2×] results: 1/2 triggered → 1/2 triggered line — two different real changes reported as one repeated change. Verified both by running extractCompact/formatCompact over a two-entry fixture with mirrored flag vectors.

This is exactly the failure mode the #<hash> fingerprint in renderValue was added to prevent for truncated long values (see that function's own comment about dedup keys) — the same guard is just missing on the flag-array path. Either name the flipped indices when the counts match but the vectors differ, or append a short hash of the vector the way renderValue does.


2. The --add-diffs-folder success message overstates what landed in the zip

In lib/liquidSamplerRunner.js:426, the success line is built from data.diffEntryKeys.length — every entry the compact diff flagged — not from the entries that actually got a view.html written. filesAdded is tracked correctly right above it (lines 405-412) but only used for the count of files.

On a run where 9 entries were flagged but only 2 of them had a view.html on disk, it printed:

✔ Added diffs/ folder to results.zip: 4 view.html file(s) across 9 entries.

diffs/ contained 2 entries. That's the normal case, not an edge case — the data and scope tiers flag plenty of entries whose view.html was never rendered, and a reviewer who opens diffs/ expecting 9 finds 2 and assumes the folder is broken. Count the distinct entries that contributed a file instead (track a Set alongside filesAdded), and keep the entry/entries pluralisation keyed off that number.


3. --add-diffs-folder is missing from the CHANGELOG

The 1.58.0 entry in CHANGELOG.md documents run-sampler --from-zip but says nothing about --add-diffs-folder, which arrived in the three commits after that entry was written (a78b80a, 2f2f2f4, 0bf8f15). The CHANGELOG is surfaced to users by the CLI itself (lib/cli/changelogReader.js), so a new user-facing flag that isn't in it is effectively undocumented. Add a sentence covering it — worth mentioning that it only adds before/after pairs for the entries the compact diff flagged, since that's the whole point versus extracting the full archive.


4. Two small ones

Two non-blocking notes on the new code:

  1. extractNamedFields in lib/liquidSamplerCompact.js keys fields by data-name into a Map, so a repeated data-name (a radio group: several <input>s sharing one name) is last-wins, and the checked attribute isn't read at all — only value. A change in which radio is selected therefore produces no field-level note and falls through to the generic "layout/markup changed with no anchored field explaining it" line. That fallback is honest, so this isn't a correctness bug, but if radio groups are common in the sampled templates it's a blind spot worth either fixing (read checked, key by name + value) or naming in the function's doc comment alongside the existing note about fields without a data-name.

  2. --from-zip --add-diffs-folder extracts the zip and runs extractCompact over it twice — once in printCompactDiffFromZip (line 357) and again in addDiffsFolderToZip (line 384), each with its own mkdtemp/rmSync cycle. Correct, just wasteful; diffEntryKeys from the first pass could be handed to the second.

michieldegezelle added a commit that referenced this pull request Jul 31, 2026
…, radio groups

- Distinguish same-triggered-count flag-array reorders by naming the
  flipped indices, so distinct results reorders no longer render (and
  cross-entry dedupe) as an identical no-op line.
- Count only entries that actually got a view.html written when
  reporting --add-diffs-folder success, instead of every entry the
  compact diff flagged.
- Track the checked option in a radio group by its value so a changed
  selection produces a field-level visual-diff note instead of falling
  through to the generic layout-changed fallback.
- Document --add-diffs-folder in the 1.58.0 CHANGELOG entry.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@michieldegezelle

Copy link
Copy Markdown
Contributor Author

Thanks for the thorough pass, Benji — fixed 1, 2, and 3 in b148ffa, and took your suggestion for 4.1 (the radio-group blind spot). Left 4.2 (double extractCompact call in --from-zip --add-diffs-folder) as-is for now — it's a real inefficiency but not a bug, and threading state across the two call paths felt like more risk than the win justifies right now.

  1. Flag-array reorder dedup — fixed. formatResultsValue now takes the paired before/after value and, when the triggered count matches but the vector differs, appends (indices N,M flipped) to both sides. That both makes the line informative and (since changeDedupKey hashes the rendered string) stops opposite reorders from collapsing into one deduped change. Added tests for both the naming and the no-longer-collapsing dedup case.

  2. --add-diffs-folder success message — fixed. Now tracks a Set of entry keys that actually contributed a file and uses its size for the count/pluralization, instead of data.diffEntryKeys.length. Added a regression test with a flagged-but-no-view.html entry to catch this from recurring.

  3. CHANGELOG — added a clause to the 1.58.0 entry for --add-diffs-folder, noting it only covers the flagged entries.

4.1. Radio-group blind spot — fixed. extractNamedFields now reads checked for type="radio" inputs and stores the checked option's value per data-name, so a changed selection produces a field \x` value: ... → ...` note instead of falling through to the generic layout-changed line. Added a test.

4.2. Double extraction — skipping for now, per above.

@BenjaminLangenakenSF
BenjaminLangenakenSF self-requested a review July 31, 2026 11:26

@BenjaminLangenakenSF BenjaminLangenakenSF left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small pushback:

1.. The mirrored-reorder collapse survives. flipped is the set of differing indices, which is the same computed from either direction, so both sides render 1/2 triggered (indices 0,1 flipped) — identical strings, and changeDedupKey derives from them. Two entries with opposite reorders still dedupe into one [2×] line. The new test asserts the different-index-set case, which is why it passes. Naming which indices went 0→1 vs 1→0 fixes the rendered line and the key together.

Also: the suffix is uncapped, and the flag-array branch never reaches renderValue, so neither MAX_VALUE_CHARS nor the # guard applies. A long vector with many flips renders a 200+ char line.

Human readable 😅:
The fix that works: say which way each switch went — "0 turned off, 1 turned on" — so the two directions print differently and stop being mistaken for each other.

4.1. /\bchecked\b/ matches aria-checked and data-checked — \b matches after a hyphen. Once every radio in a group looks checked, last-wins returns and the selection change is invisible again. Anchor on preceding whitespace. Same weakness in getAttr(m[1], "type") vs data-type="radio". I couldn't tell from the repo whether the sampled markup emits aria-checked, so this may be latent rather than live.

Human readable 😅:
If every button in a group carries aria-checked, they all look selected, the code falls back to just keeping the last one, and a changed selection goes unnoticed — back to the original bug the fix was meant to solve.
The fix is to only accept checked when there's a space in front of it, not a hyphen.

Everything else ok 😉

@michieldegezelle

Copy link
Copy Markdown
Contributor Author

Good pushback, both landed — fixed in 4598087.

  1. Mirrored-reorder collapse — right, position-only wasn't enough. describeFlagFlipSuffix now names direction per index ("0 off, 1 on"), computed once from the actual before→after and reused on both sides, so opposite reorders on the same index set render (and dedup-key) differently. Also capped the detail list at MAX_SET_ITEMS_SHOWN with a sha1 fingerprint over the elided flips when it overflows, same pattern as renderValue's truncation - added a test with two 20-element vectors whose first 3 flips (and thus a plain "+N more") would've collapsed, confirming they don't.

  2. \b matching after a hyphen — confirmed as a real bug, not latent: getAttr(attrs, "type") would match inside data-type="radio" (which the radio-group markup in this repo does emit alongside plain type="radio"), and the checked check would match inside aria-checked. Anchored both on (?:^|\s) instead of \b. Added a test with data-type/aria-checked both present on every radio to catch a regression back to "every radio looks checked."

664/664 tests pass, lint clean.

@BenjaminLangenakenSF
BenjaminLangenakenSF self-requested a review July 31, 2026 11:50

@BenjaminLangenakenSF BenjaminLangenakenSF left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved!

Two non-blocking nitpicks:

  • The first has a suggestion you can apply directly
  • Test file, expect(diffA.before.length).toBeLessThan(100): Only bites if you apply the suggestion above: once before drops the suffix this just measures "10/20 triggered" and stops testing the cap. diffA.after keeps it meaningful.

Comment thread lib/liquidSamplerCompact.js Outdated
@michieldegezelle

Copy link
Copy Markdown
Contributor Author

Applied both — fixed in 8b05852. Dropped the suffix from before (now just formatResultsValue(before), suffix only on after), updated the doc comment to say so, and fixed the two tests: the reorder test now asserts before: "1/2 triggered" / after: "1/2 triggered (0 off, 1 on)", and the cap test now measures diffA.after.length instead of before. 664/664 tests pass, lint clean. Thanks for approving!

michieldegezelle and others added 2 commits August 5, 2026 09:42
…ing, scope tier, visual-only tier

- Diff the `results` register alongside `named_results` (0/1 flag arrays render
  as a triggered-indicator count, other shapes fall back to a plain value diff).
- Group entries whose named_results/results collapsed entirely (>=3 keys lost
  at once) into a single "output vanished" finding per template, instead of one
  line per lost key.
- Add a scope/dependency tier (`dependencies`/`rollforward_params`/
  `required_keys_missing`), separate from the data diff, with `dependencies`
  rendered as one sub-line per category rather than a semicolon-packed line.
- Add a visual-only tier: entries where `view.html` changed but the data diff
  found nothing, described field-by-field via Silverfin's `data-name` anchor
  where possible, with an honest fallback note otherwise.
- Truncate long values and cap per-template/per-entry change lists, always
  disclosing what was elided.
- Every finding now links to a concrete follow-up: the entry's live app URL
  when known, else its path inside the results directory.
- Add `run-sampler --from-zip <path>` to build the compact diff from an
  already-downloaded results.zip, with no sampler run or network call.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Make -p/--partner optional for --from-zip, which never touches the
  partner/sampler API; still required (with a clear error) for every
  other run-sampler path.
- Fix the compact-diff headline misreporting '0 template(s) changed'
  when findings exist only in the collapsed/scope/visual tiers - it's
  now worded as data-diff (named_results/results) specific and no
  longer contradicts the sections printed below it.
- Guard dependencyLedgers/diffStringSet against malformed (non-array)
  dependencies.ledgers/account_ranges register values so one bad entry
  can't throw and abort the whole run's diff.
- Validate exampleRef's url as http(s) before interpolating it into
  Markdown link syntax, since with --from-zip that url no longer
  necessarily comes from Silverfin's own sampler backend.
- Decode HTML entities in <input> field values, consistent with the
  <textarea>/<select> branches.
- Distinguish an explicit results: null from a genuinely absent
  register in formatResultsValue/diffResultsRegister, so a legitimate
  null doesn't get miscounted into the 'output vanished' heuristic.
- Give truncated long values a short content hash alongside the
  length, so two different values that share the same truncated prefix
  and total length no longer collide under the same cross-entry dedup
  key.

🤖 Generated with Claude Code

Co-authored-by: Cursor <cursoragent@cursor.com>
michieldegezelle and others added 9 commits August 5, 2026 09:42
CodeRabbit follow-up: an http(s) scheme check alone isn't enough - a
value like 'https://trusted.example/a) [injected](https://attacker)'
still passes it but closes the generated (...) link early and injects
arbitrary Markdown. Add a shared sanitizeUrl() that requires a
parseable http(s) URL and rejects parentheses/whitespace/angle
brackets/control characters outright, and use it for both exampleRef
and the visual-only section's 'open in app' link (which previously
interpolated entry.url with no validation at all).

🤖 Generated with Claude Code

Co-authored-by: Cursor <cursoragent@cursor.com>
Reviewers currently have to dig through the full results.zip (often ~150 MB)
to find the handful of entries the compact diff actually flagged. extractCompact
now returns diffEntryKeys - every entry mentioned across the data/scope/
vanished-output/visual-only tiers - and run-sampler --from-zip --add-diffs-folder
uses it to add a diffs/<kind>/<entryId>/{before,after}/view.html folder to an
existing results.zip, in place, with no extra network call.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
zip.writeZip(zipPath) truncated and rewrote the input zip in place with
no temp file or rename, so a crash mid-write (OOM, timeout, kill) could
leave a corrupted results.zip that CI would still upload as the
reviewer-facing artifact. Flagged independently across nl_market#906,
lu_market#774, and bso_github_actions#35's reviews of the CI workflows
that call this path.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
addDiffsFolderToZip still rewrote the zip and reported success even when
every flagged entry lacked a view.html (filesAdded stays 0), matching the
existing empty-diffEntryKeys early return instead. Also trims the CHANGELOG
entry for 1.58.0.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Large sampler runs (many templates x many firms) can legitimately
take longer than an hour on the backend; the CLI's client-side
timeout was giving up and reporting failure while the run was still
in progress.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…, radio groups

- Distinguish same-triggered-count flag-array reorders by naming the
  flipped indices, so distinct results reorders no longer render (and
  cross-entry dedupe) as an identical no-op line.
- Count only entries that actually got a view.html written when
  reporting --add-diffs-folder success, instead of every entry the
  compact diff flagged.
- Track the checked option in a radio group by its value so a changed
  selection produces a field-level visual-diff note instead of falling
  through to the generic layout-changed fallback.
- Document --add-diffs-folder in the 1.58.0 CHANGELOG entry.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Name the direction of each flag-array flip (on/off), not just its
  index. Two entries with opposite-direction reorders touching the
  same indices previously rendered identically and still collapsed
  into one deduped change - naming direction fixes the rendered line
  and the dedup key together. Cap the detail list and fingerprint the
  elided flips (like renderValue's truncation) so two long vectors
  can't collapse either.
- Anchor getAttr and the radio checked-attribute check on a preceding
  whitespace instead of \b, which also matches after a hyphen -
  "type" was matching inside data-type="radio", and "checked" was
  matching inside aria-checked/data-checked, making every radio in a
  group look checked and silently reverting to last-wins.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Putting the direction suffix on both before/after still rendered the
line as an identical no-op string on either side, despite naming the
flip direction in the text. Keeping it on after only ("N/M triggered"
-> "N/M triggered (0 off, 1 on)") makes the change visible while the
mirrored dedup keys stay distinct.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
--add-diffs-folder copied before/after view.html for every entry the
compact diff flagged, including entries flagged for something the RENDER
doesn't show (a named_results/results value, a dependencies/scope change).
Those pairs are byte-identical, so the reviewer opens diffs/ and finds two
files with nothing to compare.

Seen live on lu_market run 30920292551: all 3 flagged entries were
timestamp-only named_results changes, so the entire diffs/ folder was 6
identical files — the one thing the folder exists to avoid.

Only add a pair when the two renders actually differ, and say how many
entries were skipped for being identical (in the success line, or as the
reason nothing was added) so an absent diffs/ folder is never ambiguous
between "nothing rendered differently" and "no render was captured".

Verified against that run's real results.zip: before, 6 files across 3
entries; after, no diffs/ folder and an explicit message.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@michieldegezelle
michieldegezelle force-pushed the sampler-compact-diff-v2 branch from 1ba5a0f to 813d64f Compare August 5, 2026 07:44
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.

2 participants