Skip to content

feat(links): broken-anchor detection in find --broken-links (iter-190, L-21) - #224

Merged
ractive merged 2 commits into
mainfrom
iter-190/link-anchors
Jul 19, 2026
Merged

feat(links): broken-anchor detection in find --broken-links (iter-190, L-21)#224
ractive merged 2 commits into
mainfrom
iter-190/link-anchors

Conversation

@ractive

@ractive ractive commented Jul 19, 2026

Copy link
Copy Markdown
Owner

Summary

Lands L-21 as one atomic unit: link #fragment anchors are now carried through parsing and resolution, and find --broken-links reports a broken anchor — a link whose target file exists but whose #heading does not — as a category distinct from a broken target.

  • Link.fragment added as a backward-compatible Option<String> (#[serde(default, skip_serializing_if)]). Parse side captures the fragment without #; the rewrite span still stops before #, so mv / links fix preserve fragments byte-exact. Under this repo's to_vec_named framing the field is additive — old .hyalo-index snapshots decode with fragment: None (fail-safe, no false anchor reports); rebuild with create-index to pick up anchor data. Recorded as a DEC-060 deviation from the plan's hard-break premise (verified empirically).
  • New hyalo-core/src/anchor.rs matcher: exact, case-insensitive (Obsidian [[Foo#tasks]]## Tasks), percent-decodes markdown fragments for matching only, skips ^block-id refs. Validates against already-indexed / already-scanned sectionszero extra file reads (A/B: within noise on both disk and --index paths).
  • find --broken-links: LinkInfo gains fragment + broken_anchor (skipped from JSON when absent); the two categories are never both set on one link (broken target skips the anchor check). Unified text filter renders (broken anchor) distinctly from (unresolved).
  • links fix unchanged: still target-only, so broken anchors never inflate broken/fixable or the "Apply N fixes" hint.
  • HYALO006 stays target-only (DEC-061) — anchors soak one release behind find before any lint/CI gate. DEC-060/DEC-061 recorded; CHANGELOG, README, HYALO006 description, and the rule-knowledgebase template updated; iter-188 + review L-21 dispositions flipped to resolved.

Test plan

  • cargo fmt --check, cargo clippy --workspace --all-targets -- -D warnings, cargo test --workspace -q all green

  • xtask gates: check-ac-fidelity (plan), check-feature-fanout, check-help-drift, check-bundled-skills all exit 0

  • anchor.rs: 12 unit tests (exact/trim/case/multiple/no-match/heading:None/unicode/percent-encoded/^block)

  • anchors.rs: 9 e2e — matrix disk + --index parity, links fix count guard, backlinks preservation, single + batch mv fragment preservation, rebuilt-index

  • links.rs: span/parse fragment round-trip + span-untouched invariant tests

  • A/B perf recorded in the iteration plan (disk 30.3→30.1 ms, index 29.7→30.2 ms — within noise)## Claims vs code
    <generated 2026-07-19T18:53:34Z by ralph-loop>

  • target-only → ❌ no match in diff

1 claim(s) had no matching evidence. Add the code, soften the
commit message, or annotate with // allow-claim-miss: <symbol> and a reason.

ractive and others added 2 commits July 19, 2026 20:52
…, L-21)

Carry link `#fragment` anchors through parsing and resolution and report a
distinct broken-anchor category in `find --broken-links` — a link whose target
file exists but whose `#heading` does not — never conflated with a broken
target and never inflating `links fix` counts.

- Add backward-compatible `fragment: Option<String>` to `Link` (serde default);
  parse side captures the fragment without `#`, rewrite span still stops before
  `#` so mv / links fix preserve fragments byte-exact.
- New `hyalo-core/src/anchor.rs` exact-heading matcher: case-insensitive
  Obsidian convention, percent-decodes markdown fragments for matching, skips
  `^block-id` refs. Validates against already-indexed/scanned sections — zero
  extra file reads (A/B: within noise on both disk and --index paths).
- `LinkInfo` gains `fragment` + `broken_anchor` (skipped from JSON when absent);
  unified text filter distinguishes broken-target `(unresolved)` from broken
  anchor `(broken anchor)`.
- HYALO006 stays target-only (DEC-061); anchors surface only in find for now.
- DEC-060 (match convention + shape backward-compat deviation) and DEC-061
  recorded; CHANGELOG / README / HYALO006 desc / rule-knowledgebase template
  updated; iter-188 + review L-21 disposition flipped to resolved.
- 9 e2e (anchors.rs): matrix disk+index parity, links-fix count guard,
  backlinks, single + batch mv fragment preservation, rebuilt-index; plus
  anchor.rs unit tests and links.rs span/parse round-trip tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… gate

Soften the task-4 perf AC to name the zero-file-read code path
(index.get(target_path).sections / fragment_matches_headings in
find/mod.rs) so ac-fidelity-check.sh can verify it against the diff
alongside the hyperfine A/B numbers, which aren't diff-verifiable.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@ractive
ractive merged commit 119101c into main Jul 19, 2026
8 checks passed
@ractive
ractive deleted the iter-190/link-anchors branch July 19, 2026 19:01
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