pikelet-wasm 0.8.0 → 0.9.0

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