Skip to content

Overview perf + caching, gray-matter fix, ferrosearch search engine - #21

Merged
Michaelliv merged 7 commits into
mainfrom
perf-overview-cache-ferrosearch
Aug 2, 2026
Merged

Overview perf + caching, gray-matter fix, ferrosearch search engine#21
Michaelliv merged 7 commits into
mainfrom
perf-overview-cache-ferrosearch

Conversation

@Michaelliv

Copy link
Copy Markdown
Owner

Four workstreams, each TDD'd with its contract pinned before the change:

1. gray-matter cache-poisoning fix (7834e8d)

A failed frontmatter parse left a poisoned cache entry: the next parse of byte-identical malformed content silently returned empty data — second identical broken note got no warning and leaked raw frontmatter into tags. safeMatter evicts on failure; malformed content now fails deterministically.

2. Overview performance (6fe8bad, e5c0502, fa5b163)

  • Golden characterization suite snapshotting the full pipeline across three option sets, captured before any refactor.
  • 3.7x faster cold scan (3.7s → 1.0s on 5,000 notes): single tokenize pass, guarded noise regexes, per-text count-then-merge. Verified against a verbatim copy of the original algorithm: 30,000+ fuzzed strings + 60 randomized vaults, byte-identical JSON including Map-order tie-breaks.
  • Fingerprint cache: warm napkin overview drops to ~25ms via .napkin/overview-cache.json (mtime fingerprint + options key).

3. ferrosearch swap (72c7b77)

Search now runs on @shift-labs/ferrosearch 0.1.1 — native, MiniSearch-compatible including the serialized index format. Existing search-cache.json blobs keep working (migration test asserts identical files and scores). Warm search ~36ms → ~14ms. minisearch stays as the test oracle.

4. build:bun fix (35b367d)

require("../package.json") broke every compiled binary at startup (pre-existing). Static JSON import fixes it; compiled binary verified standalone, native addon embeds.

Plus a docs pass (506b18b) bringing the keyword-extraction doc, configuration reference, CLAUDE.md, and README in line with reality.

Verification: 388 tests / 44,957 assertions, tsc clean, biome clean, compiled binary smoke-tested.

gray-matter caches the file object keyed by content BEFORE parsing, so a
failed parse leaves a poisoned entry: the next parse of a byte-identical
string silently returns empty data with no error. In a vault with two
identical malformed notes, only the first produced a warning — the second
was indexed with empty properties and leaked raw frontmatter text into
tags. All frontmatter operations now route through safeMatter, which
clears the cache on a parse failure so malformed content fails
deterministically.
One dense fixture vault exercising every code path — weighted TF sources,
heading dedup and corroboration, bigram suppression, folder-token
exclusion, all noise patterns, scaffold skipping, depth limits, malformed
frontmatter, and both collapse outcomes — snapshotted across three option
sets. Any behavior change in the pipeline must show up as a snapshot diff.
Three behavior-preserving changes to the term pipeline:
- terms are tokenized once per text; bigrams derive from the token array
  instead of re-running the noise-stripping regex chain
- each noise regex runs only when a cheap necessary condition holds
  (an email needs @, a URL needs http, ...)
- occurrences are counted into a small per-text map and merged into the
  folder accumulators by unique term, preserving first-occurrence
  insertion order exactly (keyword tie-breaking depends on it)

Equivalence is enforced, not assumed: the pre-refactor implementation is
kept verbatim as an oracle, and a differential suite fuzzes both across
30,000+ adversarial strings and 60 randomized vaults, asserting
byte-identical output including Map-order-dependent tie-breaks.

3.7s -> 1.0s on a 5,000-note vault.
Single-entry cache in .napkin/overview-cache.json keyed by the whole-vault
fingerprint (paths + mtimes, shared with the search cache) plus the
resolved options. A hit costs one stat pass instead of reading and
tokenizing every note: ~25ms vs ~1s on a 5,000-note vault. Any file add,
remove, or touch invalidates; corrupt cache files are ignored and rebuilt.
Tests observe caching strictly through getOverview behavior, including a
frozen-mtime rewrite that must be invisible.
…gine

@shift-labs/ferrosearch 0.1.1 replaces minisearch in the search core. The
serialized index format is interchangeable, so existing search-cache.json
blobs keep working — pinned by a migration test asserting identical files
and scores from a minisearch-written cache. The cache write moves to
toJsonString() (ferrosearch has no toJSON), and the index options are a
single shared constant because loadJson requires the exact options the
index was serialized with.

Warm search path (load cached index + query) drops from ~36ms to ~14ms on
a 5,000-note vault. minisearch remains a devDependency as the migration
test oracle.
require("../package.json") cannot resolve inside a bun-compiled binary's
$bunfs, which broke every build:bun executable at startup. A static JSON
import bundles the version at compile time and behaves identically under
node and tsc output.
The keyword-extraction doc catches up with the pipeline it describes:
frontmatter-value weighting, the full structured-noise list with the
necessary-condition guards, the homogeneous-sibling collapse step, keyword
corroboration filtering, and a new caching section. configuration.md gains
the overview.collapse key and the auto-managed cache files. CLAUDE.md and
the README stop claiming .napkin/ holds config only, and the README states
the native engine's platform support.
@Michaelliv
Michaelliv merged commit 2f3a667 into main Aug 2, 2026
3 checks passed
@Michaelliv
Michaelliv deleted the perf-overview-cache-ferrosearch branch August 2, 2026 06:42
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