pikelet-wasm 0.7.0 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/CHANGELOG.md +340 -4
  2. package/NOTICE +5 -0
  3. package/README.md +226 -475
  4. package/complete/builder.d.ts +4 -4
  5. package/complete/builder.mjs +26 -9
  6. package/complete/encoder-kernels/encoder.node.wasm +0 -0
  7. package/complete/encoder-kernels/encoder.wasm +0 -0
  8. package/complete/fusion.mjs +72 -0
  9. package/complete/index.d.ts +34 -7
  10. package/complete/index.mjs +258 -129
  11. package/complete/inline-transformer.mjs +119 -10
  12. package/complete/lexical.mjs +11 -11
  13. package/complete/retrieval-abstention.mjs +136 -31
  14. package/complete/sources.mjs +22 -1
  15. package/complete/wordpiece.mjs +22 -0
  16. package/dist/engine.js +1 -1
  17. package/dist/engine.scalar.js +1 -1
  18. package/dist/engine.scalar.wasm +0 -0
  19. package/dist/engine.wasm +0 -0
  20. package/package.json +47 -82
  21. package/{pancake-artifact-common.js → src/artifact/pikelet-artifact-common.js} +42 -42
  22. package/{pancake-artifact-range.js → src/artifact/pikelet-artifact-range.js} +43 -36
  23. package/{pancake-artifact-sketch.js → src/artifact/pikelet-artifact-sketch.js} +105 -68
  24. package/{pancake-artifact.d.ts → src/artifact/pikelet-artifact.d.ts} +11 -11
  25. package/{pancake-artifact.js → src/artifact/pikelet-artifact.js} +10 -10
  26. package/{pancake-core.js → src/core/pikelet-core.js} +141 -140
  27. package/{pancake-errors.js → src/errors/pikelet-errors.js} +9 -8
  28. package/{pancake.d.ts → src/pikelet.d.ts} +55 -55
  29. package/{pancake.js → src/pikelet.js} +47 -47
  30. package/{pancake.node.mjs → src/pikelet.node.mjs} +49 -49
  31. package/{pancake.web.d.ts → src/pikelet.web.d.ts} +13 -13
  32. package/{pancake.web.mjs → src/pikelet.web.mjs} +24 -24
  33. package/{pancake.workerd.mjs → src/pikelet.workerd.mjs} +22 -22
  34. /package/{pancake-loader.js → src/loader/pikelet-loader.js} +0 -0
package/CHANGELOG.md CHANGED
@@ -1,15 +1,350 @@
1
1
  # Changelog
2
2
 
3
3
  Release notes for the two published packages in this repository,
4
- `pikelet-wasm` (engine + artifact readers/builders) and
5
- `create-pancake-search` (scaffolder, `doctor`, Docusaurus plugin). Both are
6
- pre-1.0: a minor bump may carry breaking changes, and each entry lists them
4
+ `pikelet-wasm` (engine + artifact readers/builders) and `pikelet` (the
5
+ CLI: compile, mcp, doctor, scaffold; plus the Docusaurus plugin).
6
+ Through 0.6.0 these were published as `pancake-wasm` and
7
+ `create-pancake-search`; both old names are deprecated pointers. Both
8
+ packages are pre-1.0: a minor bump may carry breaking changes, and each entry lists them
7
9
  first.
8
10
 
11
+ ## 0.8.1 — `pikelet-wasm`
12
+
13
+ Security release, cut from `main` ahead of the rest of the unreleased
14
+ backlog below. `pikelet` is unchanged at 0.8.1 and picks this up through its
15
+ `^0.8.0` dependency range.
16
+
17
+ ### Security
18
+
19
+ - **A `.pikelet` can no longer overflow the encoder's WASM heap through an
20
+ out-of-range `maxTokens`.** `complete/inline-transformer.mjs` sized the
21
+ kernel's ids and hidden-state buffers from the artifact's own encoder
22
+ declaration, clamped with `Math.min(declaration.maxTokens || 512, 512)` —
23
+ an upper bound only. A pack declaring `maxTokens: 1` made the windowing
24
+ arithmetic (`windowLen = maxSeq - 2`) negative, so `interior.slice(w *
25
+ windowLen, (w + 1) * windowLen)` returned nearly the whole token sequence
26
+ and the forward pass wrote past both allocations: a 60-token query wrote
27
+ about 92 KB into a 1,536-byte buffer. `maxTokens` is now validated as an
28
+ integer in `[8, 512]` at parse, before anything is allocated, `maxSeq` is
29
+ clamped on both sides, and both call sites assert the window fits.
30
+
31
+ This fired at **mount**, before any query: the reader awaits the encoder
32
+ for a non-deferred query-interp segment (and prefetches it otherwise), and
33
+ the declaration's own `testVectors` are verified after the buffers exist,
34
+ so a long test vector was itself a trigger. Every integrity check passed,
35
+ because the attacker authored the file and its digests bind their bytes.
36
+
37
+ Affects anyone who mounts a `.pikelet` they did not compile — the format's
38
+ intended use, since packs are distributed and range-read from URLs. The
39
+ write is confined to the encoder's WASM linear memory and does not reach
40
+ host JavaScript objects, which is why it is rated high rather than
41
+ critical. Upgrade to get the bound; there is no workaround short of not
42
+ mounting third-party packs.
43
+
9
44
  ## Unreleased
10
45
 
11
46
  ### Breaking / compatibility
12
47
 
48
+ - **The `PANCAKE_ENCODER_WEIGHTS_URL`, `PANCAKE_SEARCH_EMBED_WORKERS`,
49
+ `PANCAKE_SEARCH_STUB_EMBEDDINGS`, and `PANCAKE_SEARCH_PYTHON`
50
+ environment variable fallbacks are removed.** 0.7.0 introduced
51
+ `PIKELET_ENCODER_WEIGHTS_URL` / `PIKELET_EMBED_WORKERS` while still
52
+ honoring the old names; those old names no longer work. Use
53
+ `PIKELET_ENCODER_WEIGHTS_URL`, `PIKELET_EMBED_WORKERS`,
54
+ `PIKELET_SEARCH_EMBED_WORKERS`, `PIKELET_SEARCH_STUB_EMBEDDINGS`, and
55
+ `PIKELET_SEARCH_PYTHON`. Pre-1.0; scripts/CI referencing the old
56
+ names need updating.
57
+ - **`pikelet-wasm` declares `engines.node >=18`** (was `>=16`).
58
+ `complete/sources.mjs` uses the global `fetch`, which Node 16 does not
59
+ have; CI tests 18, 20 and 22. The `pikelet` CLI is unchanged at
60
+ `>=20`.
61
+
62
+ ### Changed
63
+
64
+ - **The repository is an npm workspace with the two packages under
65
+ `packages/`.** `packages/pikelet-wasm/` holds the published library —
66
+ entrypoints and readers under `src/` (`src/core`, `src/artifact`,
67
+ `src/errors`, `src/loader`), the C++ engine under `engine/`
68
+ (formerly the root `src/`), `complete/`, `native/` and the four tracked
69
+ `dist/` build outputs; `packages/pikelet/` holds the CLI (formerly
70
+ `pikelet/`). The root `package.json` is a private workspace root
71
+ carrying the test ladder and dev tooling; `npm install` links
72
+ `node_modules/pikelet-wasm` and `node_modules/pikelet` to the checkouts,
73
+ so tests, benchmarks and examples import `pikelet-wasm` exactly as
74
+ consumers do, and the CLI's and Docusaurus plugin's "repo root is
75
+ pikelet-wasm" resolution fallbacks are gone. The published tarball's
76
+ file list is unchanged: `main`/`types`/`exports` point into `src/`, and
77
+ `CHANGELOG.md`, `LICENSE`, `NOTICE` and `README.md` are staged into the
78
+ package by an npm `prepack` script instead of being duplicated in the
79
+ tree. `run_tests.js` lives at `test/run_tests.js`; the engine benchmark
80
+ scripts, whose relative requires had been left pointing one directory
81
+ too high by the benchmarks move, resolve the package again.
82
+ - **Examples renamed, not renumbered**: `examples/04-static-wiki-pack` →
83
+ `examples/static-wiki-pack`, `examples/05-one-file-search` →
84
+ `examples/one-file-search`, `examples/06-mcp-knowledge-pack` →
85
+ `examples/mcp-knowledge-pack`; `npm run demo:veyra` and the committed
86
+ `veyra.mcp.json` follow. The raw browser demo page moves from `dist/`
87
+ to `examples/technical-demo.html` (loads `../dist/engine.js`), and
88
+ `docs/SYSTEM_DESIGN.md` is `docs/architecture.md`. The wiki playground's
89
+ built bundle and deployment are unchanged.
90
+ - **README trimmed to the pitch, quickstart, live demo, numbers and
91
+ links** (577 → ~340 lines). Its long-form sections moved verbatim to
92
+ `docs/how-a-query-runs.md` (what is inside the file, compression cost,
93
+ remote execution, integrity), `docs/veyra-ablation.md` and
94
+ `docs/why-a-file.md`, linked from a new "Deeper dives" section.
95
+ - **Benchmarks consolidated under `benchmarks/`**: `bench/beir` →
96
+ `benchmarks/beir`, `bench/range-proof` → `benchmarks/range-proof`, the
97
+ engine suite (formerly `benchmarks/`) → `benchmarks/engine`, and the
98
+ committed release runs (formerly `benchmark_results/release`) →
99
+ `benchmarks/results`, with raw runs under the ignored
100
+ `benchmarks/results/raw`. `npm run bench` runs `benchmarks/engine/run.js`.
101
+ Nothing published changes.
102
+ - **Abstention calibration is fit against a different negative pool.**
103
+ Hard negatives are now ablation pairs (a verified positive's own text,
104
+ scored with its source excluded), entity swaps (a proper noun replaced
105
+ by a corpus entity that never co-occurs with it), and held-out-document
106
+ questions (title questions scored with that document excluded). A fit
107
+ whose `d0` weight comes out non-negative ships `unscored` instead. The
108
+ calibration summary (`fitAuc`, `cvAuc`, `cvAucHard`, per-class counts)
109
+ is persisted in the evaluation segment and reported by `verify_pack`;
110
+ golden queries are sampled from retrieval-verified positives. Packs
111
+ rebuilt with this calibrator get a different fit and therefore a
112
+ different identity; existing packs are unaffected.
113
+ - **`query()` returns `grounding`** — `{ recordId, coverage, covered,
114
+ uncovered }`: which of the query's content words the best passage
115
+ restates and which no passage contains — and each result carries
116
+ `vectorRank`, `lexicalRank` and `fusedRank`. A verbatim match of three
117
+ or more consecutive query words is pinned to rank 1 ahead of semantic
118
+ near-misses. Phrase pinning and the coverage term now hydrate their
119
+ candidates in one batch.
120
+ - **MCP `search` ships results under a `"none"` verdict by default**,
121
+ with a note, instead of withholding them (`showAbstained: false`
122
+ restores withholding); packs without a fitted calibrator get an
123
+ `unscoredWarning`. Each section carries `grounding`. The tool
124
+ description and `server/discover` instructions describe `matchQuality`
125
+ as how directly the text restates the question, not whether the answer
126
+ is present, and tell the caller to treat a queried-for term in
127
+ `grounding.uncovered` as unestablished and to search again for the
128
+ second hop of a chained question.
129
+ - **The reader no longer reads `coverage.useMaxSim`**; the word-level
130
+ semantic grounding path is removed rather than left dormant (a pack
131
+ with the flag set was measured at 1.5–4.6 s per query).
132
+ - `scripts/calibration-harness.mjs` reports false-abstention and
133
+ false-answer rates on labeled question sets; the sets live under
134
+ `test/relevance/`.
135
+ - Public browser playground for the Wikipedia pack, with an ablation
136
+ tab.
137
+
138
+ - **`pikelet-wasm/complete` exports `fuseCandidates()` and
139
+ `FUSION_DEFAULTS`.** The hybrid rank-fusion rule (reciprocal-rank fusion
140
+ of the exact-reranked vector order with the BM25 order) is one function
141
+ the reader, the calibrator (`calibrate.mjs`, which fits coverage on the
142
+ fused top passage) and the BEIR ladder (`query-E.mjs`) all call, instead
143
+ of three copies of the arithmetic. It takes `{ rrfK, lexicalWeight,
144
+ guardMargin }`.
145
+ - **Hybrid fusion weights the lexical rank at 0.5 and keeps a confident
146
+ vector top-1 in place.** `FUSION_DEFAULTS` is now `{ rrfK: 60,
147
+ lexicalWeight: 0.5, guardMargin: 0.05 }`: the BM25 rank term counts half
148
+ as much as the vector rank term, and when the vector top-1 leads the
149
+ top-2 by at least 5% of its own distance it keeps rank 1 regardless of
150
+ fusion. Under equal weights any record present in both lists outranked a
151
+ vector-only rank 1 (`1/(60+v) + 1/(60+l) > 1/60` for every finite `v`),
152
+ so a query whose terms match many chunks — a character's name in a
153
+ novel — filled the top ranks with term matches and buried the passage
154
+ the encoder placed first. Measured with the evidence-rank report and the
155
+ fusion simulator (`test/relevance/README.md`): evidence in the top 3 on
156
+ Pride & Prejudice 36 → 42 of 157 direct questions, internal-docs
157
+ paraphrases 19 → 21 of 24, Veyra direct 19 → 20 of 20 and multihop
158
+ 17 → 18 of 20; BEIR E by fused rank scifact nDCG@10 0.694 → 0.699
159
+ (recall@10 0.827 → 0.853), nfcorpus 0.3345 → 0.3359, arguana unchanged.
160
+ Verdicts on packs calibrated under the old rule move the same way (false
161
+ answers: internal-docs 10 → 9, Pride & Prejudice 74 → 66, Veyra 13 → 13);
162
+ a Veyra pack recalibrated under the new rule fits at cvAUC 0.890 (was
163
+ 0.870). Packs need no rebuild to get the new ranking.
164
+ ### Fixed
165
+
166
+ - **Engine constructors validate their configuration.** `pikelet_init`
167
+ (WASM and the native addon) passed caller-supplied `dims`, `M` and
168
+ `max_elements` straight into the index constructors. `M = 1` made the
169
+ level multiplier `1/log(1) = +inf`, whose conversion to an integer on
170
+ the first insert is undefined behaviour, and `max_elements * dims` or
171
+ `max_elements * M0` could wrap a 32-bit `size_t` on wasm32 into an
172
+ undersized arena. Both backends now reject `dims` outside 1..65536, `M`
173
+ outside 2..128 (0 still selects the default), `max_elements = 0`, and
174
+ products that do not fit `size_t`; WASM returns `INVALID_HANDLE`, the
175
+ native addon throws a `RangeError` (a C++ exception used to escape the
176
+ N-API boundary and abort the process). `Pikelet.create()` mirrors the
177
+ `dim` ceiling with an `INVALID_ARGUMENT` error, and the native
178
+ `pikelet_import` rejects a non-buffer argument with a `TypeError`
179
+ instead of continuing with a null view while a JS exception is pending.
180
+ - **Artifacts with a NaN or infinite scale/offset are rejected.** A
181
+ producer signs whatever bytes it writes, so a non-finite affine pair in
182
+ a `.pikelet-range` record or a sketch artifact's affine table passed
183
+ every digest check and then made that row's distance non-finite, which
184
+ the result heap never evicts. The range reader rejects the record at
185
+ decode and the sketch reader rejects the table wherever it adopts it,
186
+ both with `SNAPSHOT_INVALID`.
187
+ - **`stripMarkdown` no longer backtracks quadratically on backtick runs.**
188
+ The fenced-block and inline-code regexes took seconds to minutes on a
189
+ line of 40k backticks (each candidate fence length re-scanned the rest
190
+ of the document; each suffix of an inline run was retried). Both are
191
+ replaced by linear scanners with CommonMark's rules: a fence closes on
192
+ the first later fence line of the same character at least as long, an
193
+ unclosed fence runs to the end of the document, and an inline span of N
194
+ backticks closes on the next run of exactly N on the same line, may
195
+ contain runs of other lengths (`` ``a ` b`` ``), and drops one padding
196
+ space from each end. Under the old regexes a shorter closing fence or
197
+ an unclosed one left the block as prose.
198
+ - **Folder ingestion reports what it skips and caps file size.** The walk
199
+ skipped symlinked files and directories silently (Dirents carry lstat
200
+ semantics); it now logs `warn: skipped symlink <path>` for each. Files
201
+ over 16 MiB are skipped with a warning naming the path and the limit
202
+ instead of being read whole into the build, matching the crawl path's
203
+ body cap.
204
+ - **`pikelet rebuild --verbose` parses.** `--verbose` was read as a
205
+ boolean (`verbose: !!flags.verbose`) but was not registered as one, so
206
+ as the last argument it failed with "Missing value for --verbose" and
207
+ before another flag it swallowed that flag as its value.
208
+ - **`verify_pack` surfaces a corrupt evaluation segment.** The MCP tool
209
+ turned any `evaluation()` failure into "no evaluation", reporting 0
210
+ golden queries and 0 probes, all passed, for a pack whose evaluation
211
+ segment failed its hash or did not parse. Those errors now propagate
212
+ as tool errors; a pack with no evaluation segment still reports zero.
213
+ - **Generated Workers no longer echo internal error messages on 5xx.**
214
+ The `pikelet` scaffold's `worker.js` / `worker.artifact.js` templates
215
+ returned `error.message` for every failure, including unexpected ones,
216
+ which can name bundled asset paths and load state. A 5xx now carries
217
+ `{ error: "Internal server error", code }` and the original error goes
218
+ to the Worker log (`wrangler tail`); 4xx and 503 (`EMBED_UNAVAILABLE`)
219
+ bodies still carry the message. The legacy reference Worker's
220
+ "Invalid JSON body" error no longer appends the parser's message.
221
+ Projects generated by earlier versions keep their old template.
222
+ - **References the 0.8.0 rename left broken are repaired.** The engine
223
+ benchmark scripts called `native.pancake_*` (the N-API addon exports
224
+ `pikelet_*`); the ANN-Benchmarks adapter imported `pancake_py` (the
225
+ pybind module is `pikelet_py`) and, after the benchmarks move, resolved
226
+ the repo root one level short; `dist/technical-demo.html` and
227
+ `test/ios-smoke.html` called `_pancake_*` WASM exports (the iOS page also
228
+ inlined a pre-rename engine build and used the pre-`ef_search` query
229
+ ABI — regenerated from the current `dist/`); the Docusaurus plugin's
230
+ webpack rule matched `pancake-(artifact|errors).js`, which no longer
231
+ exists, so it never applied; READMEs showed `openPancakeFile`,
232
+ `PancakeRangeArtifact`, `PancakeSketchArtifact` and `PancakeIndex`. The
233
+ scaffold template's UI title and the wiki example page's wordmark and
234
+ GitHub links now say Pikelet. `docs/history.md` records the rename and
235
+ lists what intentionally keeps the old name.
236
+ - **`compact()`'s rebuild path no longer leaves the index half-cleared**
237
+ when a reinsertion fails: the old graph is released only after the
238
+ rebuilt one is complete, so a failed rebuild returns with the index
239
+ unchanged. Previously the next `search`/`insert` read out of bounds.
240
+ - Coverage word rules: possessives (`darcy's`) match plain mentions,
241
+ eleven quantifier/hedge words are stopwords, and numerals are exempt
242
+ from the minimum word length (`Chamber 4` vs `Chamber 5`).
243
+ - Ingest: `**/dir/**` globs no longer substring-match sibling names,
244
+ minified HTML with unquoted attributes chunks correctly, a leading BOM
245
+ no longer breaks frontmatter, numeric entities decode, and
246
+ `stripMarkdown` no longer deletes hyphens, underscores and minus
247
+ signs; merging a section with a child heading keeps its provenance;
248
+ ingestion conformance runs on Windows.
249
+ - Browser verification without a WebCrypto backend fails with a clear
250
+ error instead of an obscure one.
251
+
252
+ ## pikelet 0.8.1 — 2026-09-18
253
+
254
+ - `pikelet` records `mcpName: "io.github.mcn92/pikelet"` and ships
255
+ `server.json`, the manifest for the official MCP registry entry
256
+ (`pikelet mcp <pack>`; one required positional argument, any
257
+ `.pikelet` file). No code change.
258
+
259
+ ## pikelet-wasm 0.8.0 / pikelet 0.8.0 — 2026-09-14
260
+
261
+ ### Breaking / compatibility
262
+
263
+ - **Manifest profile strings are renamed** `pancake-complete-v1/v2` →
264
+ `pikelet-complete-v1/v2`. New builds write the `pikelet-` string;
265
+ readers accept either for the same format version, so every pack
266
+ published before this release keeps opening with its identity
267
+ unchanged. Because the profile string is part of the manifest that
268
+ the identity hashes, **a 0.8.0 compile of the same inputs produces a
269
+ different identity than 0.7.0 did**. This reverses the 0.7.0 note that
270
+ the wire format would keep its pancake-era names.
271
+ - **Published API names are renamed** to their Pikelet forms:
272
+ `PancakeError`/`PANCAKE_ERROR_CODES` → `PikeletError`/
273
+ `PIKELET_ERROR_CODES`, `PancakeIndex` → `PikeletIndex`,
274
+ `PancakeRangeArtifact`/`PancakeSketchArtifact` → `PikeletRangeArtifact`/
275
+ `PikeletSketchArtifact`, `PancakeApi`/`NodePancakeApi` → `PikeletApi`/
276
+ `NodePikeletApi`, `openPancakeFile`/`assemblePancakeFile` →
277
+ `openPikeletFile`/`assemblePikeletFile`. The compatibility aliases are
278
+ dropped. The C ABI's `pancake_*` symbols are `pikelet_*`; the native
279
+ addon source is `native/pikelet_napi.cpp`. The `.pancake-range` and
280
+ `.pancake-sketch` extension conventions are `.pikelet-range` and
281
+ `.pikelet-sketch` (readers dispatch on magic bytes, not extension).
282
+ - **`export()` refuses while deleted vectors are resident.** The snapshot
283
+ format has no deleted flag, so a snapshot taken with ghosts present
284
+ resurrected them on import. C++ `serialize()` throws, the WASM
285
+ `pikelet_export` returns null, the native binding throws; call
286
+ `compact()` first. The JS wrapper already enforced this; the native
287
+ path did not.
288
+ - **`@xenova/transformers` is an optional dependency of `pikelet`.** It
289
+ is needed only by the scaffold (`create`) path; `npm install -g pikelet
290
+ --omit=optional` skips its ~140 MB for `compile`/`mcp`-only use.
291
+ - `pikelet` depends on `pikelet-wasm ^0.8.0`.
292
+
293
+ ### Added
294
+
295
+ - **Swappable inline encoder.** `compile --encoder-model
296
+ --encoder-weights --encoder-vocab --encoder-pooling mean|cls
297
+ --encoder-query-prefix --encoder-passage-prefix` records a different
298
+ BERT-shaped model (same hidden size, layers, heads and vocab size as
299
+ MiniLM-L6, e.g. Snowflake `arctic-embed-xs`) as artifact data; CLS
300
+ pooling is honored end to end, and `export_encoder_blob.py` checks the
301
+ architecture before exporting.
302
+ - **The inline-transformer kernel's window is 512 tokens** (was 128).
303
+ Chunks longer than 128 tokens are no longer silently truncated at
304
+ embed time; long chunks cost proportionally more to embed. Existing
305
+ artifacts declaring `maxTokens: 128` open and verify unchanged.
306
+ - **`query({ showAbstained: true })`** returns the retrieval under a
307
+ `"none"` verdict alongside the unchanged `matchQuality`/`confidence`.
308
+ - `examples/06-mcp-knowledge-pack`: compile a corpus, mount it over MCP,
309
+ answer with citations. Engine-reference examples move to
310
+ `examples/legacy/`.
311
+ - `bench/beir/` retrieval-quality ladder (dense, hybrid RRF, lexical-only,
312
+ Arctic-XS configs) and `bench/range-proof/` (dumb static server, MCP
313
+ and headless-LLM proofs of the static-hosting claim).
314
+ - A committed MCP config makes the Veyra demo packs queryable
315
+ interactively; the packs themselves are GitHub release assets fetched
316
+ by `web/veyra-corpus/fetch-veyra.mjs` rather than tracked files.
317
+ - Encoder-conformance fixtures live under `test/fixtures/` instead of
318
+ inside an example.
319
+
320
+ ### Fixed
321
+
322
+ - **The abstention verdict no longer depends on the caller's `k`.** The
323
+ scorer windows its inputs to the fixed top-10 the calibrator was fit
324
+ on; at `k=3`, `margin`, `mean10` and coverage were computed over
325
+ fewer candidates than at fit time and flipped verdicts between
326
+ `strong` and `weak` for identical retrieval.
327
+ - **Coverage is scored against the fused (vector + BM25) ranking**
328
+ in both the calibrator and the reader, with the lexical cutoff shared
329
+ between them. Calibration was fit on vector-only signals while the
330
+ reader served hybrid results; on a 2,841-record Wikipedia sample every
331
+ measured false abstention had the correct record at hybrid rank 1–6.
332
+ - `httpRangeSource` treats a `200` whose ETag differs from the pinned
333
+ one as `ARTIFACT_CHANGED` instead of a Range-unsupported host; the
334
+ sketch row-digest floor is 16 bytes (readers still accept 8–32);
335
+ `deserialize()` validates every header field before assigning it; the
336
+ L2 insert/search path rejects non-finite vectors (Cosine already did).
337
+ - `chunkDocs` no longer emits title-only stub records from leading
338
+ undersized sections or attributes a merged chunk to the wrong section.
339
+ - Calibration probes: phrasing diversity across question frames, a
340
+ saturation rail on the hard threshold, and a minimum weak band.
341
+ - `spec/COMPLETE_PROFILE.md` Draft 2 resolves the corpus-compression,
342
+ `sampleQueries` placement and browser-packaging questions.
343
+
344
+ ## pikelet-wasm 0.7.0 / pikelet 0.7.0 — 2026-09-02
345
+
346
+ ### Breaking / compatibility
347
+
13
348
  - **The project is renamed to Pikelet.** Packages: `pancake-wasm` →
14
349
  `pikelet-wasm`, `create-pancake-search` → `pikelet` (one CLI:
15
350
  `npx pikelet create|compile|doctor|mcp`; the old `npm create
@@ -392,7 +727,8 @@ first.
392
727
  - **The v1 spec no longer lists hybrid search as a non-goal**:
393
728
  docs/CREATE_PANCAKE_SEARCH_SPEC.md's non-goals entry for "Hybrid BM25 +
394
729
  vector scoring" is struck through with a pointer to the shipped lexical
395
- segment (spec §3.8) and the `query({ retrieval })` modes.
730
+ segment (spec §3.8) and the `query({ retrieval })` modes. (That planning
731
+ document was moved out of the repository on 2026-09-05.)
396
732
  - **Section-aware ingestion and chunking.** Markdown/MDX documents are
397
733
  parsed into their heading structure before chunking — heading lines are
398
734
  recognized only outside code fences, explicit `{#custom-id}` heading ids
package/NOTICE ADDED
@@ -0,0 +1,5 @@
1
+ Pikelet
2
+ Copyright 2026 Matthew Noonan
3
+
4
+ This product includes software and file-format specifications developed as
5
+ part of the Pikelet project (https://github.com/mcn92/pikelet).