CLAUDE.md@.claude · git:20261005.f6556ce · 2026-10-05 · sha256 be4f952f6d43ba1a

CLAUDE.md@.claude git:20261005.f6556ceA

Immutable. This exact content never changes and is served at /api/v1/blob/be4f952f6d43ba1a.

<!-- hyalo:start -->
Use `hyalo` CLI (not Read/Grep/Glob) for all markdown knowledgebase operations.
Examples: `hyalo find --property status=planned`, `hyalo find "search text"`, `hyalo lint` (add `--strict` to fail on missing-type / undeclared-property warnings and HYALO003/004/006/007/008), `hyalo types list`.
Run `hyalo --help` for usage. Output format auto-detects (text on terminals, json when piped); pass `--format text`/`--format json` to override.
Use `hyalo config` to inspect the effective configuration (effective dir, config path, hints, format, site_prefix) — useful when debugging `.hyalo.toml` resolution. Add `--raw` to also print the file's text. When a config exists but does not parse, `results.malformed` is true and `results.parse_error` carries the diagnostic; every other value shown is a built-in default. `.hyalo.toml` is found in the working directory or in the nearest ancestor whose configured vault contains it, so running from inside the vault keeps the config in effect. `--dir` selects a vault, not a config: naming the configured vault keeps `.hyalo.toml` in effect, naming another tree switches to that tree's config (announced on stderr). Its JSON is the standard `results`/`hints` envelope, so `hyalo config --jq '.results.dir'` works.
`[scan] exclude = ["Templates/**"]` hides matching files from *every* command (reported as `results.scan.exclude`); naming an excluded file explicitly is refused with the glob. Every scan also honours `.gitignore`, `.ignore`, `.git/info/exclude` and global excludes the way Git does (DEC-342, amended iter-314) — `.gitignore` only inside a git repository (a `.git` directory at or above the vault), like ripgrep; `.ignore` applies either way: an ignored note is absent from unscoped reads and counted under `summary`'s `results.files.excluded` (recorded in the snapshot, so `--index` does no walk); a path you name (`--file`, positional, `--files-from`) is still returned under every `--fields`, and the graph is asymmetric — its own backlinks list its referrers while their links to it stay broken. A config that does not parse now exits 1 for `lint`, `find --strict` and `views run` — their exit code is a gate — while other reads keep answering with a `-q`-proof warning. An unloadable `[schema]` (TOML parses, but e.g. an `enum` has no `values`) joins the same refusal for all three gates, plain `lint` included (DEC-362, iter-314) — `hyalo config` already reported it as `malformed: true`; a plain write without `--validate` is unaffected.
Every link in `--fields links` carries a `kind`: `wikilink` | `embed` (`![[…]]`) | `markdown` | `frontmatter` (a `[[wikilink]]` in a YAML frontmatter value) | `external` (any `scheme:` URI — `https:`, `obsidian://`, `mailto:`, `file://`) | `attachment` (resolved to a non-`.md` vault file: an image, a PDF, an Obsidian `.base`). `external` and `attachment` links are never broken and are not graph edges, so list the genuinely broken ones with `hyalo find --broken-links --limit 0 --jq '.results[] as $f | $f.links[] | select(((.kind == "external" or .kind == "attachment") | not) and ((.path == null and (.out_of_vault | not)) or .broken_anchor)) | "\($f.file):\(.line) \(.target)#\(.fragment // "")"'`. Resolution folds case on every platform (DEC-267), and a broken `#anchor` that prefixes exactly one heading — the whole heading included — carries `suggested_fragment` with the full heading text (DEC-268).
Anchor **resolution** folds `-`, `_` and a space into one word separator (DEC-309), so `#Browser_compatibility` resolves to `## Browser compatibility` (MDN's broken anchors: 10 929 → 529); a nested heading path `[[note#H1#H2]]` resolves by walking the outline, so the second segment must sit under the first (DEC-311). A wikilink target is trimmed and `.` segments are dropped before resolution (DEC-310): `[[ a ]]`, `[[a #H]]` and `[[./a]]` all report `path: "a.md"` while `target` keeps what was written.
A bare `[[alias]]` naming a note's frontmatter `aliases:` is **broken** by default (DEC-308, amends DEC-296) — Obsidian does not resolve one; its suggester writes `[[Note|alias]]`. `[links] aliases` defaults to `false`; the link still carries `via: "alias"`, and `links fix` plans that exact rewrite in an `alias_fixes` bucket (strategy `Alias`, confidence 1.0, plain `--apply`, never fuzzy). `[links] aliases = true` restores iteration 272's resolution. In either mode an ambiguous stem is never tie-broken by an alias, and an alias claimed by two notes is ambiguous — `links fix` lists the `candidates` and HYALO006 says "ambiguous wikilink … matches 2 candidates".
`mv` lists a link it cannot attribute rather than guessing: a bare `[[stem]]` two files share lands in `skipped_ambiguous` with its `candidates`, for a frontmatter value (with `property`) as for body prose, in every directory layout, and for the moved file's own body self-links (`self: true`); `--allow-ambiguous` rewrites them all. Destinations resolve like sources — an absolute in-vault path is accepted, and `--to .` / `--to ./` / `--to <vault-dir>/` all mean the vault root. A batch dry run lists `collisions: [{source, destination}]` and plans the rest; `--apply` refuses.
A `[[wikilink]]` in **any** frontmatter value is a graph edge (DEC-269) — `categories: ["[[Books]]"]`, `type: "[[Author]]"`, a nested map — counting for `backlinks`, `--orphan`/`--dead-end`/`--broken-links`, `summary.links` and HYALO006, reported with `kind: "frontmatter"`, the `property` it came from and its frontmatter line, and rewritten in place by `mv`. Set `[links] frontmatter = false` in `.hyalo.toml` to narrow the scan back to `related`/`depends-on`/`supersedes`/`superseded-by` (`hyalo config --jq '.results.links'` reports the effective value).
`set K=<scalar>` on a property holding a list replaces it and says so on stderr, listing the files under `list_collapsed` (DEC-270) — use `hyalo append` to keep the list.
`title` is promoted in three steps — a scalar frontmatter `title`, else the first H1, else the **filename stem** (DEC-283) — and `title_source` (`property` | `h1` | `filename`) says which answered. `--title`, `--property 'title~=…'` and `--sort title` all read the promoted value; test the raw key with `--property title` / `--property '!title'`.
A path named with `--file`, positionally, or via `--files-from` is linted even when `[lint] ignore` matches it (DEC-284), so `git diff --name-only | hyalo lint --files-from -` checks what you touched; select paths with `--glob` when you want the ignore list applied.
`hyalo new --dry-run` prints the scaffold without writing. Its placeholders are deliberately un-fillable: a required `string` gets `TBD`, a required `number`/`date`/`datetime`/`boolean` with no schema default is written **empty** so `hyalo lint` reports it (DEC-285).
`links auto` holds back candidate titles that are common words, generic doc filenames or run-dominating, reporting them as `results.default_excluded_titles` (DEC-286); `[links.auto] exclude_titles` replaces that list and `--no-warn-common-titles` switches it off.
A bulk write phase — `lint --fix`, `mv`, `links fix --apply`, `set`/`append`/`remove --glob`, `properties rename`, `tags rename` — still replaces every file atomically but pays the durability fsync once per touched directory instead of once per file (DEC-317); the Obsidian Hub's `lint --fix` went from 48.1 s to 2.35 s. Past a few hundred files it reports progress on stderr; `-q` silences it. A phase of 8 files or fewer, and every single-file mutation, keep the full per-file guarantee.
`summary.orphans`/`summary.dead_ends` and `find --orphan`/`find --dead-end` share one edge predicate (DEC-318): an external URI, a same-file anchor and any target with an explicit non-`.md` extension are not note-graph edges — a *broken* `![[missing.png]]` included, which is still a broken link and still reported by `find --broken-links`.
`links fix` damps a contested fuzzy winner below the apply floor rather than writing it (DEC-319), reports `broken_anchors` as the count `find --broken-links` computes (never a hard-coded 0), carries `emitted_target` on every `fuzzy_fixes` entry including below-floor ones, and says "derived from the directory name" when the prefix that stripped nothing was never configured. MDN's `_colon_`/`_star_` slug encoding stays unresolved (DEC-320) and there is no basename fallback into `**/x/index.md` (DEC-321).
A near-neighbour stem is closed in the *basename* scorer, not by the margin (DEC-324): camelCase is a word boundary (`CatMuse` → `["cat","muse"]`, so `[[Cat]]` drops 0.867 → 0.481); a token pair clears the 0.85 floor on plain **Jaro**, so Jaro-Winkler's shared-prefix bonus can sharpen a match but never create one (`paulbricman` no longer matches `paultreanor`), unless one token is a prefix of the other (`get` in `getting`); and a token left entirely unmatched costs its share of the two names' characters (`obsidian-floating-toc-plugin` vs `obsidian-plugin-toc`: 0.857 → 0.694). That character charge is the basename's alone — applied to directories too it took 828 correct GitHub Docs relocations below the floor. A winner scored an **exact** 1.0 is exempt from DEC-319's contested-margin damping. Consequence: a basename that gains a whole word is now below the floor (`decision-log` → `decision-log-archive` = 0.607) — still reported, applied with `--min-confidence 0.5`.
The fuzzy **candidacy** gate — the cheap Jaro-Winkler prefilter that decides which files get scored at all — now compares stems in word form (DEC-325): split at separators AND camelCase boundaries, lowercased, rejoined with `-`. Before that it compared raw, case-sensitive stems, so a pair the scorer rates 1.0 never reached it (`my-long-note` / `MyLongNote` is 0.53 raw, `html-parser` / `HTMLParser` 0.45) and a prose-cased Obsidian vault could not be repaired from a slug-spelled link; `[[my-long-note]]` now fixes to `MyLongNote.md` at 1.0. A plain lowercase-hyphen slug is its own key byte for byte, so GitHub Docs and MDN produce byte-identical `links fix` output at unchanged wall time. That wider gate exposed DEC-324's `shares_dominant_prefix` exemption, closed by DEC-326.
The dominant-prefix exemption — the one thing that admits a token pair Jaro rates under the 0.85 floor — now requires the shared prefix to be **more than twice** what it leaves over of the shorter token, not merely half of it (DEC-326): `referen` (7) beats `ce` (2), `math` (4) does not beat `pad` (3), and `excali` (6) merely doubling `bur` (3) is a tie, not dominance. So the Obsidian Hub's `[[Mathjax]]` (no such note) no longer offers `mathpad.md` at 0.886 — its only above-floor proposal left is a perfect 1.0 — while every inflection the exemption exists for still passes at any word length (`create`/`creating`, `manage`/`managing`, and `reference`/`referential`, which a fixed one-character leftover would have thrown away along with GitHub Docs' `referential-content-type` rename at 0.973). MDN is byte-identical and GitHub Docs loses no above-floor proposal; the one honest cost is `saml-configuration-reference` (6 occurrences) falling 0.804 → 0.795 — a correct same-basename relocation that was 0.004 above the floor, still reported, applied with `--min-confidence 0.79`.
Hints thread `--site-prefix` when it came from the CLI, exactly as `--dir`, `--format` and `--index-file` are; `find --broken-links --format text` prints only each file's broken links (JSON keeps the full inventory); `<https://…>` / `<obsidian://…>` autolinks are inventoried as `external`; `K!=V` tests a value, so a file lacking K does not match — use `!K`; and `summary --index` reports the same `skipped` count as a disk scan (an older snapshot format is refused — rebuild the index; `hyalo config --jq .results.snapshot_format_version` names the current one).
Link-kind histogram: `hyalo find --fields links --limit 0 --jq '[.results[].links[].kind] | group_by(.) | map({kind: .[0], n: length})'`. Missing images: `hyalo find --fields links --limit 0 --jq '.results[] as $f | $f.links[] | select(.kind == "embed" and .path == null) | "\($f.file):\($f.line) \(.target)"'`.
Frontmatter closes only on a **column-0** `---` (trailing whitespace allowed): an indented `  ---` inside a block scalar is content, and a block that never closes at column 0 is reported as unclosed / `HYALO005` instead of being silently truncated (DEC-293). `set`/`append` also never emit a block scalar containing a `---`/`...` line — such a value is written double-quoted and round-trips. `properties rename --from ''`/`--to ''` exits 1.
`lint` does not fire a prose rule inside a fenced or indented code block or an HTML comment; MD010, MD031, MD040, MD046, MD047 and MD048 keep checking them on purpose, and MD031 stays quiet at the opener of an unterminated fence. Silence a region with markdownlint's own comments — `<!-- markdownlint-disable no-hard-tabs -->` … `<!-- markdownlint-enable … -->`, plus `-disable-line`, `-disable-next-line`, `-disable-file`, `-enable-file` — taking rule ids or aliases (DEC-294).
`links fix` produces no case-mismatch plan for a site-absolute link carrying the configured `site_prefix` (DEC-295) and never appends `/index` or `.md` to a link form that lacked it; `mv` applies its ambiguity guard to frontmatter links too, reporting each skip with the `property` it came from.
A path you *name* is a promise (DEC-301): `find --file`/positional on a note whose frontmatter will not parse exits 1 with the diagnostic (a `--files-from` list still counts it and exits 0); `find --index --file` reads a note the snapshot has never seen from disk; `--file`/`--glob` keep `broken_anchor` and `suggested_fragment`, so all four ways of selecting one file return identical link JSON; and `lint --rule X` reports rule X only — a parse error is HYALO005's finding, otherwise a counted skip.
The stale-index warning now names the file that drifted (DEC-302): when the directory-mtime probe is clean, each indexed file's mtime is compared against disk, so an in-place overwrite is no longer served silently (~0.03 s over MDN's 14,375 files; same-second edits remain invisible). A snapshot also records what `[scan] exclude` dropped when it was built, so `summary --index` and `summary` agree on `excluded` (DEC-303).
`mv` resolves the destination exactly like the source (DEC-304): with `dir = "kb"`, `hyalo mv kb/a.md kb/sub/a.md` from the project root lands at `kb/sub/a.md`, never `kb/kb/sub/a.md`, in all four destination forms. `--on-conflict` is `error` | `skip` and nothing else (DEC-305), honoured in single-file mode too; batch `mv` sweeps for split frontmatter links once per batch and reports `moves[].frontmatter_links_skipped` (DEC-306).
Normal exit codes are 0 / 1 / 2 (DEC-307), with the existing broken-pipe exception 141: 0 the command answered (a dry run and a zero-result query both answered — drift lives in the payload, e.g. `okf index`'s `results.changed`, not in the exit code); 1 every hyalo-own user error, always rendered through the JSON error envelope (bad `--sort`, unparseable `--glob`, unreadable `--files-from`, unknown `init --profile`, `create-index --output` into a missing directory, `find a b`, `deinit --dir <nonexistent>`); 2 clap usage errors and internal errors. Renderer/system failures also exit 2 and override domain findings; post-write failures retain observed effects. Broken pipe returns 141 without a panic and reports committed effects when any exist.
`--property` rejects an empty operand (`K=`, `K>=`, `K>`), a second `=` (`a=b=c`) and an empty name (`=b`) with exit 1 instead of matching nothing — use `K`, `K=null`, `!K`, or `K~=/a=b/` for a literal `=`. A zero-result `find` that filtered on a key the vault *does* carry now says so and lists the values with counts, rather than claiming no file has the property.
`hyalo lint --rule SCHEMA` / `--rule-prefix SCHEMA` runs the frontmatter/schema pass alone, and `lint-rules list`/`show` carry a non-configurable `SCHEMA` row; `lint --fix` JSON reports `rules_fixed: {rule: n}`. An empty required typed placeholder reports one error, not two. An empty `--files-from` list warns (`-q`-proof) because "no input" must not look like "no findings".
`--sort title` collates: case-folded, leading punctuation skipped, and an H1-derived title has its `<!-- … -->` comments stripped. Hints on an indexed run carry `--index` / `--index-file` wherever the hinted command accepts it. `hyalo config` reports `links.case_insensitive`; `false` disables hyalo's case-folding index only — the literal path probe is still the filesystem's, so exact-match resolution is guaranteed only on a case-sensitive volume.
`markdownlint-disable-next-line` protects the line *after* the comment and never its own — a trailing directive no longer makes a heading invisible to prose rules — and no autofix may insert a blank line between the directive and the line it guards; fenced blocks indented inside list items (including under `1.` and inside a blockquote) are code, so no prose rule fires there; an unknown rule id in a suppression comment warns (`-q`-proof); `--max-per-rule 0` means unlimited. MD010's `code_blocks` stays unexposed — use `<!-- markdownlint-disable no-hard-tabs -->` (DEC-316).
`check-jq-recipes` **fails** on a shipped mutating recipe without `--dry-run`, so every mutating example in `skill-hyalo.md`, `rule-knowledgebase.md`, `pi-package/` and this file is safe to paste. `--jq` computes **no** hints, so `.hints` under a filter is always `[]` (DEC-313) — read hints from plain `--format json`.
An unknown key under `[schema]`, `[schema.types.<t>]`, or the wrong nesting `[schema.<t>]`, is refused with the same "unknown field" diagnostic `[scan]` gives; `hyalo config` reports `malformed: true` plus `schema_error`, and every validating command refuses per DEC-290. `required = ["title"]` means present and not vacuous — a YAML null or `[]` fails, an empty string `""` passes — and **not** `string` (DEC-312): `title: 2024` passes — declare `type = "string"` to require one. `set` has no YAML-null form (DEC-314); `set --help` carries the full coercion table.
The snapshot header carries a format version: an index written by an older binary is refused with a warning naming both versions and the run falls back to disk. `hyalo config` reports `snapshot_format_version` (what this binary writes), `summary --index` reports the snapshot's `index_format_version`. `--index-file <unreadable>` exits 1 with an envelope — a named path is a promise — while a missing in-vault `.hyalo-index` under bare `--index` still falls back, with a `-q`-proof warning. The stale-index blind spot is up to ~2 s (whole-second mtimes plus a one-second tolerance).
A bare path is always vault-relative; when the CWD is inside the vault and a file of that name exists in both places, hyalo warns (`-q`-proof) naming both candidates and the one it used. `mv --to ../deep/` says "path contains `..`" like the source check. `[links] case_insensitive = "false"` means exact bytes and reports the link's own spelling, not the canonical one (DEC-315) — `hyalo config` says so on a case-folding filesystem.
`--- ` (trailing whitespace) opens frontmatter, matching the closer and YAML (DEC-293 amended), and both fences round-trip byte-identically through `set`/`append`/`remove`. A JSON-mode write against an unparsable file is one envelope carrying the YAML diagnostic in `cause`. `tags rename` keeps a flow-style `tags: [a, b]` flow. `1. [ ]`, `2) [x]` and `-  [ ]` are tasks. Bulk `set`/`append`/`remove` carry `skipped_detail` — `{file, reason}` with `unchanged` or `unparsable` — so a same-value write is distinguishable from a refusal.
Ranked `find PATTERN` has a grammar (DEC-333): implicit AND, and `OR` binds tighter — `a b OR c` is a AND (b OR c), where one `OR` used to make every term an alternative; `( … )` groups and nests, `-` negates a term, phrase or group, a parenthesis inside a word (`main()`) is literal, and an unbalanced `(`, an empty `()` or a bare `*` exits 1 with `invalid search query`. `conf*` expands over the **stemmed** dictionary (`config*` matches "configuration"), capped at the 256 most frequent terms with a `-q`-proof warning. Search terms are words, phrases, prefixes and their boolean combinations; structure is selected with flags (`--title`, `--section`, `--tag`, `--glob`) — there are no field terms, so `title:x`, `foo:bar`, URLs and `std::fs` are plain words (DEC-366). Each term is stemmed in every language present in the vault, so a `language: de` note matches its German inflection under an English default. A zero-result query names up to 3 close dictionary terms for words no document contains, hints the corrected query and `hyalo terms <prefix>`, and carries a top-level `suggestions: [{term, candidates: [{term, docs}]}]`; `hyalo terms [PREFIX]` lists `{term, docs}` sorted by docs (`--limit` 50, 0 = unlimited).
`find PATTERN --granularity section` returns one hit per flat section (`{file, section: {heading, level, line_start, line_end, path}, score, matches}`, preamble heading null) that holds the query's positive words by itself — negated terms are decided once per file (`-(-a)` ranks a), files qualify on their whole body even under `--section`, scores use the corpus IDF with section-length normalisation, `--limit` counts sections, `--sort`/`--reverse`/`--fields`/`-e` exit 1, and each hit hints `read --section '<heading>'` or `--lines A:B` (DEC-334). `--facet tags|property:K|type|dir` (repeatable) adds a top-level `facets: [{facet, buckets: [{value, count}], truncated}]` of FILE counts over the full match set before `--limit` — exact tags, property values resolved and case-folded like `--property K=V` (dot-paths included), missing/null/empty under `null`, 50 buckets max — and hints drill into the 3 largest buckets (DEC-335).
Snapshot format **v4** / tokenizer v4 (DEC-336): an older snapshot is refused and must be rebuilt with `create-index`; accents fold before stemming (`résumé` = `resume`), an identifier (`getUserName`, `get_user_name`, `get-user-name`) indexes its joined whole plus its parts so `user` finds it and a query identifier matches whole OR all parts, and `[search] code_blocks = "skip"` drops fenced blocks (the snapshot records the setting and is not used for search under the other one). Ranking is BM25F with one combined length (DEC-337): `[search.weights] title = 3, headings = 2, tags = 2` (tags plus `aliases`, so a tag-only term matches), `body = 1`, applied at query time; invalid values warn and keep the default. `"a b"~N` matches in order with at most N extra words (max 64), and with two or more required terms the top 200 candidates get ×(1 + `proximity_bonus`/(1 + w)), default 0.5, while snippets prefer the tightest line (DEC-338). `create-index` is incremental — unchanged size+mtime entries are reused, postings patched, `--force` rebuilds, `results` gains `reused`/`refreshed`/`skipped`/`removed`/`rebuilt`, and a racily clean file (mtime not older than the previous snapshot) is always re-scanned — and an `--index` read re-scans drifted files in memory with a `-q`-proof note naming the count and up to three files, drops a drifted file it cannot scan with a `-q`-proof warning as a disk scan would, and never writes the snapshot (DEC-339); `hyalo config` reports `results.search.{language, code_blocks, weights, proximity_bonus}`.
Frontmatter follows YAML 1.2 core (DEC-350): `01234` is the integer 1234, the plain words `NaN`/`Infinity` stay strings (only `.nan`/`.inf` keep their string fallback), and `set` keeps its old quoting except that `---`, `...` and `0X1F` are now quoted.
An anchor fix writes the fragment form the link syntax actually resolves (DEC-351): a wikilink gets the heading TEXT (`[[note#3. Deploy Steps]]`), a markdown link keeps the GFM slug (`note.md#3-deploy-steps`) — Obsidian only matches a wikilink fragment against heading text, never a slug. `summary.links.broken` now folds in `alias_fixes` (amends DEC-318), so it agrees with `find --broken-links` and HYALO006 on a bare `[[alias]]` link (Obsidian Hub: 154 → 162 everywhere). `mv` rewrites a bare relative attachment/embed link (`[img](img.png)`, `![embed](img.png)`, `[cfg](.gitignore)`) on a cross-directory move when it resolves to a real file, exactly like its `.md` sibling (DEC-352); the vault-wide bare-attachment read-side fallback is unchanged — that stays genuine Obsidian semantics, not the bug. A link fragment also resolves against an explicit HTML anchor in the target — `<a id="x">`, `<a name="x">`, `<h1 id="x">`…`<h6 id="x">` — matched byte-for-byte (case-sensitive) against the id/name value, independent of any ATX heading (DEC-353); `find --broken-links`, HYALO008 and `links fix` all take the combined check. The snapshot format bumps to **v5** for it — `IndexEntry.explicit_anchor_ids` would otherwise sit on a reused, un-rescanned v4 entry forever under incremental `create-index` (DEC-339), so a v4 snapshot is refused and rebuilt, like iteration 304's v4 bump (the format has since bumped again, to v6, for `IndexEntry.valid_utf8` — DEC-365, iter-314; `hyalo config --jq .results.snapshot_format_version` always names the current one). Wikilink resolution folds Unicode composition, not just case (DEC-354): `[[Café NFD]]` (precomposed) resolves a file named with decomposed accents and vice versa, matching Obsidian; `mv`/`links fix` propose no rewrite for a target that only differs in composition. A site-absolute bare `/` (or a target that is exactly the configured `site_prefix`) resolves to the vault-root `index.md`, consistent with `/dir` resolving to `dir/index.md` (BUG-15). `links fix`'s case-mismatch/relocation/fuzzy/certain text rendering shows the byte-truthful `emitted_target`, not the vault-relative `new_target` a wikilink write never puts on disk verbatim — a wikilink always drops `.md` (BUG-17). `--apply-fuzzy` (or `--min-confidence`) without `--apply` never claims a fix was written: `fuzzy_applied` is `false` on every dry run, and the text/JSON distinguish "never opted in — pass --apply-fuzzy" from "opted in but this is a dry run — pass --apply" (UX-3). `find --broken-links` and `links fix` share one anchor-suggestion chooser (`anchor::unique_heading_by_prefix`); a deferred anchor fix carries that `suggested_fragment`, and the `Fixable:` text line counts anchor fixes too, so it cannot read 0 next to an "Apply N fixes" hint (UX-7). A `[[<placeholder>]]` angle-bracket target joins the `templated` bucket with `{{…}}`/`{%…%}`/`${…}`, never offered as a fuzzy candidate.
`read --lines A:B` is file-absolute (DEC-355), the same numbering `find`'s section hits, lint and `task --line` print, so a range copied out of any of them pastes in unchanged, under `--section` included: the range is intersected with each matched section's own file-absolute span. A frontmatter line is a file line like any other — a range falling (even partially) inside the frontmatter block returns that raw text, fence included, rather than being refused — and the no-overlap warning is reserved for a window past the end of the file (or, under `--section`, outside every matched section). A leading-dash PATTERN clap reads as a short-flag cluster (`hyalo find '-snapshot'` becomes `--section napshot`) is detected from the real argv (a `-s…`/`-t…` token with a concatenated value, not merely "no PATTERN was given," which also caught a deliberate `--section Task`) and gets a hint naming `hyalo find -- '-term'` on the resulting `--tag` validation error or `--section` warning — including the common single-clean-match case (`-sqlite` → `--section qlite`) that previously warned about nothing at all (DEC-356). `prefix*` unions the typed prefix's own expansion with its stem's, so a one-document typo sharing the raw prefix (`configurationon`) no longer suppresses the stem fallback (`configuration*` now also finds `configur`, like `config*`) — a deliberate widening of recall, not a narrow fix (DEC-358 addendum). Did-you-mean candidates rank by Damerau-Levenshtein distance (transposition-aware) then document frequency, `corrected_query` fixes every suggested term in one query — including a term repeated more than once — rather than just the first, and a misspelled word inside a quoted phrase (`"stale indx"`) now gets a suggestion too (DEC-357); the "Try OR" hint only fires when at least one query word has any postings at all, checked independently of whether `suggest()` happened to propose a correction for it. `--sort property:K` and the zero-result `--property` diagnostic resolve a dot-path exactly like `--property K=V` and `--facet property:K` already did, and that diagnostic now covers `!=`/`~=`/the ordering operators too, not just `=` — a comparison whose values are all strings says so. `--facet property:a:b` is rejected like a bare `property:`, naming the four accepted forms. A pure-negative query (every term negated) says it needs a positive term instead of pointing at `properties summary`; `--granularity section`'s snippet lines print in line order in text mode; and the corrected-query hint keeps `--granularity section` when the original query ran in section mode. A dangling `OR` (`a OR`, `OR a`), an unterminated `"`, a misplaced `*` (`*foo`, `sn*p`) and a `~N` slop clamped above 64 all warn (`-q`-proof) — an operator-only query (`find OR`) gets exactly one warning, not this one plus the more specific "interpreted as a boolean operator" one; `"a b"~abc` exits 1 with `invalid search query` rather than silently treating `abc` as a new search word (DEC-358), and that slop check runs only right after an actual closing quote, so an ordinary phrase whose content starts with `~` (`"~home dir"`) is unaffected.
The old-format-snapshot refusal now goes through the same `-q`-proof warning facility as the missing-index fallback (previously `-q` silenced it, DEC-360). A snapshot built under a different `[search] code_blocks` than the current config is refused the same way — named by a `-q`-proof warning naming both settings, not a silent fallback (DEC-360); `summary --index` reports `index_format_version`, `code_blocks` and `source` ("index" or "disk") even when the snapshot was refused for either reason, instead of `index_format_version: null`. The setting lives on the snapshot header (`SnapshotHeader.skip_code_blocks`, format **v7**) so checking it never forces the BM25 section's lazy decode, which used to roughly double every `--index` read's cost for commands that otherwise never touch BM25 at all. A `create-index` rerun that changed nothing at all leaves the snapshot file untouched and reports `written: false` — "nothing changed" means every entry reused with the same count the previous snapshot held, the frontmatter-skip list unchanged, `scan_excluded`/`scan_exclude`/`gitignore_dropped` unchanged, attachments unchanged, *and* the cheap directory-mtime probe (DEC-280) clean; a dirty probe (even one that nets back to the same file set, e.g. `mkdir x; rmdir x`) forces a real write instead, so the snapshot's own mtime and `created_at` cannot fall permanently behind the vault (DEC-361). `--force` already never loads the previous snapshot before discarding it. Any command whose JSON `results` is a bare array (`find`, `properties`, `tags`, `terms`, `task toggle`/`task set`) carries `truncated: true` whenever `--limit` (or the default 50-item cap) cut it short of `total`, omitted — never `false` — otherwise; a command whose `results` is an object instead (`backlinks`, `lint`, `summary`) never carries it. `--jq` and `--count` already lift the default cap on their own (an explicit `--limit` still caps even under them), so `truncated` mainly matters under plain `--format json`. `summary` hints "refresh the stale index" instead of "create an index" when a `.hyalo-index` already exists and a cheap mtime probe (the index file's own mtime against the vault's newest directory mtime) suggests it is behind the vault; when it exists and looks current, the hint points at `--index` instead. `hyalo terms` and `prefix*` expansion continue to list/count an identifier's joined-whole token (DEC-336) alongside its parts — filtering it out of `terms` output and the 256-term prefix cap was considered and deliberately deferred (DEC-359): it needs per-term provenance that does not exist without another snapshot-format bump.
A literal backslash in a filename or in `--file`/`--files-from`/`--to`/`mv`/`links fix` selection text is preserved off Windows (DEC-363, iter-314) — `\` is a separator only on Windows; everywhere else it is an ordinary filename byte, and every form of file selection agrees on it. YAML 1.2 core's integer grammar is wider than a single decimal pattern (DEC-364, amends DEC-350): `0x1F`/`0o17` (lowercase prefix) are core and read as integers; `1_000`, `0X1F` (uppercase) and `0b101` are YAML 1.1 extensions and read as strings. `find`/`summary`/`lint` agree a note with invalid UTF-8 bytes is a problem, on disk and under `--index` alike (DEC-365): a scan that reads the body reports and counts the skip (silenced by `-q`, like HYALO005's), snapshot format v6 carries `IndexEntry.valid_utf8` so `--index` matches disk without rereading the file, and a bodyless `--fields file` projection deliberately never detects it (avoiding a false positive from a partial read).
<!-- hyalo:end -->
`find --help` opens with a 10-line FIND 101 block (grammar, section mode, facets, `terms`, "structure is selected with flags"), byte-identical in `skill-hyalo.md` and `rule-knowledgebase.md` (gate 3h of `check-help-drift`), and `hyalo --help` names the operators on its PATTERN line (DEC-367). `find` teaches at the moment of need: 3+ query words with 100+ file hits, or a PATTERN-less `--section` matching 10+ files, hints the same scope at `--granularity section` (withheld when `-e`/`--sort`/`--reverse`/`--fields` would make it exit 1); a PATTERN-less `--section` hint is withheld when the heading has no significant word, and its description says the `--section` scope is dropped. An unfiltered zero-result AND says no file holds all the words beside the OR rewrite only when hyalo-core finds that every compiled AND operand occurs (a prefix by its expansion, an identifier or a dotted word by its tokens). A plain AND of 2+ words matching 10+ files gets `"a b"~5`, ranked ahead of the generic drill-downs and withheld for `OR`, a negation, a group, a phrase or a `prefix*`. An error envelope whose `hint` says "see SECTION in" a command's `--help` also carries `hyalo <cmd> --help` in a `hints` array (text `-> hyalo find --help  # FACETS`). That array is empty under `--no-hints` or `--jq`, and gate 3g fails a pointer without that twin.
The directory-mtime staleness probe — `create-index`'s no-op guard (DEC-361), `--index`'s missing-files walk and the write path's "index older than vault" warning — is one helper, `index::tree_moved` (DEC-368): every probed directory compares against `created_at` + tolerance, except the directory holding the index file, whose bump is excused only when it coincides (within the tolerance) with the index file's own mtime, which the publish re-stamps right after its rename. So a slow serialize-fsync-rename tail no longer makes every rerun rewrite the snapshot, while `touch`ing, copying or future-dating `.hyalo-index` never hides a new note; the remaining blind spot is a note created or deleted directly in the index's own directory within the tolerance of a publish or a touch. The format stays v7 and the racily-clean rule (DEC-339) still uses `created_at`. A wikilink's authored `.md` suffix is still stripped from the reported `target` (`[[sub/note.md]]` → `target: "sub/note"`): the audit is in `backlog/wikilink-target-does-not-preserve-the-authored-md-suffix.md`.