WORK-591
ID:WORK-591Status:done

Normalization, the two hash levels, and snippet review --check

SPEC-131 answers am I quoting the right region. SPEC-134 answers the next one up: the region is right, and the prose around it no longer describes what is in it.

A field is removed, a default flips, a parameter is reordered — the anchor resolves perfectly, the code block renders real current code, and the paragraph above it quietly becomes false. That is the ordinary case, because editing a declaration is far more common than renaming or moving one.

Priority:highComplexity:moderateMilestone:v0.37.0Source:SPEC-134
claude/nifty-ptolemy-80epot View source

Criteria completion

Criteria completion: 14 of 14 (100%) checked; history from Sep 23 to Sep 240%25%50%75%100%Sep 23Sep 24
Branches 4
History 6
  1. ab57f06
    • ☑ `reviewed` is accepted on `snippet` and `file-ref`, and its absence leaves behaviour unchanged
    • ☑ `expand` does not accept `reviewed` — it resolves no slice, so there is nothing of the right shape to hash
    • ☑ The hashed form applies `reindent` first, then normalizes line endings, per-line trailing whitespace, and the trailing newline
    • ☑ Comments are included in the hashed form, covered by a test where only a doc comment changes and the marker fires
    • ☑ A nesting-only change (a function moved into a class, content otherwise identical) does not fire the marker
    • ☑ The stored hash is truncated to 8–12 hex characters
    • ☑ Both hash levels are computed and exposed, with the loose form additionally collapsing whitespace runs and dropping blank lines
    • ☑ A change that alters the strict hash but not the loose hash is classified formatting-only, covered by a reformatting test
    • ☑ A stale marker produces a `PipelineWarning` and renders nothing into the page
    • ☑ A refused anchor never evaluates `reviewed`
    • ☑ A `snippet` command group is registered in `packages/cli/src/commands/` — no such command exists today, and this item is the first to need it; {% ref "WORK-592" /%}'s `review` extends it rather than adding a second
    • ☑ `refrakt snippet review --check` reports every stale marker with file, line, anchor, and a summary of what changed
    • ☑ `--check` has its own exit code, independent of the pipeline diagnostic channel
    • ☑ {% ref "SPEC-134" /%}'s Approach is corrected — it still says the feature "would ship as a no-op with a CLI attached" until {% ref "WORK-573" /%}, which {% ref "WORK-575" /%} made untrue in v0.36.0 — and its first open question is marked answered by {% ref "WORK-580" /%}'s pre-merge job
    by bjornolofandersson
  2. 9762a80
    Content editedby bjornolofandersson
  3. 45c53a2
    Created (ready)by bjornolofandersson
  4. 81a138f
    Content editedby Claude
    docs(plan): renumber the masker item to WORK-597, resolving the collisio
  5. c680110
    Content editedby Claude
    docs(plan): correct the expand scope error and soften WORK-596's blocker
  6. d40fe65
    Content editedby Claude
    docs(plan): break the documentation-drift specs into v0.37.0

Normalization is the whole design

Hash raw bytes and the marker fires on every trailing space and every reformat — and a marker that cries wolf gets deleted. In order:

  1. Apply reindent first (WORK-589). Moving a function into a class shifts every line two columns without changing a character of content.
  2. Normalize line endings, strip per-line trailing whitespace, normalize the trailing newline.
  3. Stop. Comments stay in — D2.

Excluding comments would make markers quieter and the masker from WORK-597 could do it for free. It is still wrong: a doc comment is very often the exact text the surrounding prose paraphrases. If @param timeout's description changes meaning and the marker stays green, the feature has failed at the only job it has.

Two levels, so a reformat is not a review (D3)

A repo-wide formatter run would invalidate every marker at once, and an author re-stamping fifty of them learns nothing — the feature's worst failure mode and the strongest objection to it.

  • strict — the stored hash, per above.
  • loose — additionally collapsing whitespace runs and dropping blank lines.

Strict differs and loose matches → the change is provably formatting-only, re-stamp silently. Otherwise hold for review. A mass reformat becomes one mechanical commit, and the three slices that actually changed still stop someone.

This item defines and computes both. WORK-592 is what acts on them.

Where findings go (D6)

Into SPEC-132's diagnostics surface as PipelineWarnings, and no further. Nothing is rendered into the page.

This is deliberately different from SPEC-131 D6, and the difference is principled: SPEC-131 uses an error fence because the content could not be produced. Here the content was produced perfectly — the code is real, current, and correctly located. What is uncertain is the prose beside it, which the resolver cannot see. Replacing a correct code block with an error because a paragraph might be stale is a straightforward regression for every reader.

Acceptance Criteria

  • reviewed is accepted on snippet and file-ref, and its absence leaves behaviour unchanged
  • expand does not accept reviewed — it resolves no slice, so there is nothing of the right shape to hash
  • The hashed form applies reindent first, then normalizes line endings, per-line trailing whitespace, and the trailing newline
  • Comments are included in the hashed form, covered by a test where only a doc comment changes and the marker fires
  • A nesting-only change (a function moved into a class, content otherwise identical) does not fire the marker
  • The stored hash is truncated to 8–12 hex characters
  • Both hash levels are computed and exposed, with the loose form additionally collapsing whitespace runs and dropping blank lines
  • A change that alters the strict hash but not the loose hash is classified formatting-only, covered by a reformatting test
  • A stale marker produces a PipelineWarning and renders nothing into the page
  • A refused anchor never evaluates reviewed
  • A snippet command group is registered in packages/cli/src/commands/ — no such command exists today, and this item is the first to need it; WORK-592's review extends it rather than adding a second
  • refrakt snippet review --check reports every stale marker with file, line, anchor, and a summary of what changed
  • --check has its own exit code, independent of the pipeline diagnostic channel
  • SPEC-134's Approach is corrected — it still says the feature "would ship as a no-op with a CLI attached" until WORK-573, which WORK-575 made untrue in v0.36.0 — and its first open question is marked answered by WORK-580's pre-merge job

Approach

Build the checker before the writer. The feature is useful read-only the moment a marker can be placed, and building --check first keeps the normalization decisions honest — every normalization choice is immediately visible as a marker that does or does not fire.

Budget the normalization, not the hash. Hashing is a line. Getting normalization wrong produces a feature that is worse than not having it, because a noisy marker trains people to ignore a real one.

Blocked by

  • WORK-589 — reindent is normalization step 1

Notes

SPEC-134's stated prerequisite is stale and this item is not blocked on it. The spec's Approach says the feature would "ship as a no-op with a CLI attached" until WORK-573 lands, on the strength of WORK-554's finding that the adapter dev server prints no diagnostics at all.

WORK-575 has since shipped in v0.36.0 and fixed exactly that: one reporter at loadContent, printing in dev. So a review-marker diagnostic now is seen by someone editing documentation, which was the disqualifying row.

What WORK-573 still owns is whether an error-severity diagnostic fails a build — and D7 says a fired marker is a review prompt, not a failure, so this feature actively does not want that. No dependency in either direction.

D1 is worth respecting in the naming: reviewed, not pin. This freezes nothing — the snippet still tracks HEAD and re-resolves every build. pin= would steer an author toward "this is frozen, nothing to do here", the opposite of the intended response.

References

  • SPEC-134 — D1 (the name), D2 (comments in), D3 (proven formatting-only), D4 (inline and truncated, not a sidecar), D6 (diagnostic, never a fence), D9 (the anchor refuses first)
  • WORK-589 — reindent
  • WORK-575 — the reporter seam that makes the diagnostic visible in dev, resolving the spec's stated blocker
  • SPEC-132 — the diagnostics routing model
  • packages/content/src/site.ts — the diagnostics surface
  • packages/editor/src/community-tags-builder.ts — the 8-character truncated-hash precedent

Resolution

Completed: 2026-09-24

Branch: claude/v0-37-0-review-vqpl41 PR: refrakt-md/refrakt#649 (batched with WORK-592 and WORK-593)

What was done

  • packages/runes/src/lib/review-marker.ts (new) — normalizeStrict, normalizeLoose, hashSlice, compareMarker, formatMarker, parseMarker, canCarryMarker.
  • snippet-pipeline.ts — comparison after resolution, emitting a PipelineWarning and rendering nothing into the page.
  • reviewed attribute on snippet and file-ref; deliberately not expand.
  • refrakt snippet review --check (the command group lives in WORK-592's module, registered here as the first consumer).
  • SPEC-134's Approach corrected; both its open questions marked answered.
  • packages/runes/test/review-marker.test.ts — 18 tests.

Notes

  • Both hash levels are stored inline, colon-separated. This is a decision beyond what the spec states. D3 requires formatting-only to be proven, and with a single stored value it cannot be — so a one-value marker degrades to stale rather than guessing. That direction is deliberate: it asks for a review that may be trivial rather than skipping one that mattered.
  • The pipeline warning carries no diff, and cannot. At transform time the only record of the reviewed version is its hash. Recovering the old content needs git, which is WORK-592's job — this is why D5's "show content, never hashes" is the CLI's responsibility rather than the pipeline's.
  • D9's layering is load-bearing in the code, not just the spec. A refused anchor returns before the marker is evaluated, so one failure never produces two findings. Covered by a test that renames a symbol and asserts silence.
  • Comments stay in the hash (D2) even though SPEC-131's masker could strip them for free. The test that pins this changes only a doc comment and asserts the marker fires.

Verification

4903 tests pass. refrakt snippet review --check clean, exit 0.