@leu2m/semantic-search 0.2.0-beta.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 (67) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +146 -0
  3. package/dist/adapters/file-storage.d.ts +8 -0
  4. package/dist/adapters/file-storage.js +55 -0
  5. package/dist/adapters/file-storage.js.map +1 -0
  6. package/dist/adapters/filesystem.d.ts +23 -0
  7. package/dist/adapters/filesystem.js +119 -0
  8. package/dist/adapters/filesystem.js.map +1 -0
  9. package/dist/adapters/minilm.d.ts +50 -0
  10. package/dist/adapters/minilm.js +141 -0
  11. package/dist/adapters/minilm.js.map +1 -0
  12. package/dist/catalog.d.ts +3 -0
  13. package/dist/catalog.js +26 -0
  14. package/dist/catalog.js.map +1 -0
  15. package/dist/chunking.d.ts +9 -0
  16. package/dist/chunking.js +54 -0
  17. package/dist/chunking.js.map +1 -0
  18. package/dist/contracts.d.ts +163 -0
  19. package/dist/contracts.js +4 -0
  20. package/dist/contracts.js.map +1 -0
  21. package/dist/embedding-input.d.ts +4 -0
  22. package/dist/embedding-input.js +13 -0
  23. package/dist/embedding-input.js.map +1 -0
  24. package/dist/engine.d.ts +63 -0
  25. package/dist/engine.js +257 -0
  26. package/dist/engine.js.map +1 -0
  27. package/dist/file-types/registry.d.ts +13 -0
  28. package/dist/file-types/registry.js +47 -0
  29. package/dist/file-types/registry.js.map +1 -0
  30. package/dist/file-types/types.d.ts +11 -0
  31. package/dist/file-types/types.js +2 -0
  32. package/dist/file-types/types.js.map +1 -0
  33. package/dist/index-state.d.ts +17 -0
  34. package/dist/index-state.js +91 -0
  35. package/dist/index-state.js.map +1 -0
  36. package/dist/index.d.ts +11 -0
  37. package/dist/index.js +8 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/parsers/index.d.ts +28 -0
  40. package/dist/parsers/index.js +113 -0
  41. package/dist/parsers/index.js.map +1 -0
  42. package/dist/retrieval.d.ts +20 -0
  43. package/dist/retrieval.js +108 -0
  44. package/dist/retrieval.js.map +1 -0
  45. package/docs/alpha5-hardening.md +86 -0
  46. package/docs/alpha6-answerability.md +131 -0
  47. package/docs/alpha6-c-validation.md +490 -0
  48. package/docs/alpha6-evidence-traces.md +486 -0
  49. package/docs/api.md +43 -0
  50. package/docs/architecture.md +121 -0
  51. package/docs/benchmarks/alpha5-retrieval.json +5233 -0
  52. package/docs/benchmarks/alpha5-scale.json +505 -0
  53. package/docs/benchmarks/alpha6-evidence.json +14185 -0
  54. package/docs/benchmarks/alpha6c-heldout-real.json +9389 -0
  55. package/docs/decisions/0001-minilm-loading.md +32 -0
  56. package/docs/decisions/0002-retrieval-modes.md +26 -0
  57. package/docs/decisions/0003-retrieval-evidence-boundary.md +19 -0
  58. package/docs/evaluation.md +315 -0
  59. package/docs/file-types.md +31 -0
  60. package/docs/integration.md +204 -0
  61. package/docs/next-slice.md +11 -0
  62. package/examples/README.md +38 -0
  63. package/examples/core.mjs +53 -0
  64. package/examples/evidence.mjs +20 -0
  65. package/examples/filesystem.mjs +9 -0
  66. package/examples/minilm.mjs +15 -0
  67. package/package.json +73 -0
@@ -0,0 +1,204 @@
1
+ # Host integration
2
+
3
+ ## Install as a versioned dependency
4
+
5
+ Use a pinned Git commit or the tarball produced by `npm pack` until a registry release exists. Git installs run `prepare` to build declarations and JavaScript. Do not import another checkout's `src/` files or use product-specific aliases inside this package. A future npm release uses the same package name and exports.
6
+
7
+ Both inspected consumers can use ESM: Vector bundles dependencies with esbuild, while Nexus's ESM packages/daemon import the core directly. This work does not modify either consumer. Current package-install checks exercise native ESM and installed declarations for all three public exports; core also executes in a browser-like realm and, in Alpha.5, an actual Chrome page/module worker from the installed tarball. The initial foundation separately verified an esbuild CommonJS consumer bundle.
8
+
9
+ ## Obsidian vault (host-owned adapter)
10
+
11
+ The following is a conceptual host-side adapter. The host must apply its own hidden/protected-path policy on **both** list and read, and resolve the active editor versus saved content according to product semantics.
12
+
13
+ ```ts
14
+ import type { SearchSource } from '@leu2m/semantic-search';
15
+ import { SemanticSearchEngine, isSupportedFile } from '@leu2m/semantic-search';
16
+
17
+ const vaultSource: SearchSource = {
18
+ id: configuredVaultId,
19
+ type: 'obsidian',
20
+ async list() {
21
+ return app.vault.getFiles()
22
+ .filter(file => isAllowed(file.path) && isSupportedFile(file.path))
23
+ .map(file => ({
24
+ id: file.path, sourceId: configuredVaultId,
25
+ uri: makeObsidianUri(file.path), path: file.path, name: file.name,
26
+ modifiedAt: file.stat.mtime,
27
+ metadata: { tags: hostTagsFor(file) },
28
+ }));
29
+ },
30
+ async read(id) {
31
+ if (!isAllowed(id)) throw new Error('Source access denied');
32
+ const file = app.vault.getFileByPath(id);
33
+ if (!file) throw new Error('Document unavailable');
34
+ return { text: await app.vault.read(file) };
35
+ },
36
+ };
37
+ const engine = new SemanticSearchEngine();
38
+ engine.addSource(vaultSource);
39
+ await engine.initialize();
40
+ ```
41
+
42
+ On vault create/modify/rename/delete events, debounce and serialize `refreshSource(configuredVaultId)`. Dispose host listeners when the plugin unloads. This foundation rescans; event-level incremental scheduling is a later slice. A failed source scan retains previous results; a host that cannot permit stale evidence should remove that source or refuse searches until refresh succeeds. `SearchResult` has enough line/path/URI information for a host to re-read exact evidence before risky edits.
43
+
44
+ ## Filesystem folders (desktop or daemon)
45
+
46
+ ```ts
47
+ import { FileSystemFolderSource } from '@leu2m/semantic-search/filesystem';
48
+ engine.addSource(new FileSystemFolderSource({
49
+ id: 'approved-project', root: configuration.approvedAbsoluteRoot,
50
+ maxFileBytes: 512 * 1024,
51
+ }));
52
+ await engine.refreshSource('approved-project');
53
+ const evidence = await engine.search({ query: 'validateSession', sources: ['approved-project'] });
54
+ ```
55
+
56
+ The root must be explicitly approved/configured by the host. Do not turn an agent's arbitrary path argument directly into a registered source. Symlinks are not followed. No filesystem watcher is created and no external folder is inferred. Mobile/browser hosts should use their own source APIs and not import the Node adapter.
57
+
58
+ ## Custom sources and parsers
59
+
60
+ Implement `SearchSource` for any explicitly configured document collection. IDs are unique within a source and every listed document must carry that source's ID. Add a `DocumentParser` to a `ParserRegistry`; for extra text formats, pass an extended `FileTypeRegistry` to both the parser registry and the filesystem adapter. Use `new SemanticSearchEngine({ parsers })`. The shared registry provides parser selection and admission policy rather than competing extension arrays. Unknown files are rejected unless explicitly registered.
61
+
62
+ ## Optional storage and embedding
63
+
64
+ ```ts
65
+ import { SemanticSearchEngine, type Embedder, type IndexStorage } from '@leu2m/semantic-search';
66
+
67
+ // Host-provided implementations; alternatively use the optional MiniLMEmbedder below.
68
+ const embedder: Embedder = myWorkerEmbedder;
69
+ const storage: IndexStorage = myTransactionalStorage;
70
+ const engine = new SemanticSearchEngine({ embedder, storage });
71
+ engine.addSource(mySource);
72
+ await engine.initialize({ signal: abortController.signal });
73
+ ```
74
+
75
+ A browser app can supply a browser source and IndexedDB-backed storage; a daemon can supply filesystem sources and its own store. The core imports neither. Omit either option independently. Storage alone persists lexical records. An embedder alone reuses vectors in the current engine. Both enable compatible restart reuse. With an embedder, omitted mode now means hybrid retrieval; without one it means lexical. Storage is independent of that default. Explicit lexical mode requires no query inference and remains usable after a model has been disposed, using the last good index. Semantic/hybrid require a working embedder and fail on model errors rather than silently falling back.
76
+
77
+ ```ts
78
+ const exact = await engine.search({ query: 'validateSession', mode: 'lexical' });
79
+ const conceptual = await engine.search({ query: 'verify the caller identity', mode: 'semantic', sources: ['approved-project'] });
80
+ const combined = await engine.search({ query: 'verify the caller identity', mode: 'hybrid', filters: { extensions: ['ts'] } });
81
+ ```
82
+
83
+ This is a deliberate Alpha.3-to-Alpha.4 default change. Set `mode: 'lexical'` where existing consumers require lexical results or zero query-model latency. Keep the embedder alive through semantic/hybrid searches; the engine never disposes a shared host adapter. Any finite Float32 embedder dimensionality is supported, but query and candidate vectors must have nonzero norm for cosine. All candidate vectors must exist and match the engine configuration; inconsistency surfaces as an error.
84
+
85
+ Source/filter restrictions apply before both channels, and no eligible chunks means no inference. Results retain full provenance and one-based ranks, not confidence. RRF can retain noisy lexical matches; use evaluation to choose a mode for a workload. Exact search scales linearly with vector count/dimensions plus sorting; curate sources and filter scope before assuming a large-corpus latency budget.
86
+
87
+ Use `createSearchSession(engine)` per run to cache completed equivalent searches. It resolves default mode and includes mode/filters/sources/limit in keys. Semantic query text is case/whitespace-sensitive in the cache. Calls overlapping refresh see their captured old index and are not cached under the new revision. Equivalent unordered source/tag/extension sets share cache entries. `clear()` prevents earlier pending calls from repopulating the cache without cancelling their callers. Re-read cited material before mutations when current source content matters.
88
+
89
+ For Node/CLI/desktop consumers, a concrete atomic store is available from the existing Node-only export:
90
+
91
+ ```ts
92
+ import { FileIndexStorage, FileSystemFolderSource } from '@leu2m/semantic-search/filesystem';
93
+ const engine = new SemanticSearchEngine({
94
+ storage: new FileIndexStorage(configuration.absoluteIndexFilename),
95
+ // embedder: myEmbedder, // optional, including MiniLMEmbedder from /minilm
96
+ });
97
+ engine.addSource(new FileSystemFolderSource({ id: 'folder', root: configuration.approvedAbsoluteRoot }));
98
+ await engine.initialize();
99
+ ```
100
+
101
+ The host creates the index parent directory and chooses an index location outside its indexed source roots. Use one writer per store. Save failures surface and preserve the last good live source state. Missing files mean an empty store; corrupt JSON/compatible snapshots produce errors. Hosts choose recovery by repairing/removing that store or selecting a fresh one. The package never chooses a product-specific cache path.
102
+
103
+ Loaded snapshots are reconciled with current sources before becoming searchable. Unchanged exact content—including renames—reuses document vectors. Changed/new content alone requires embedding. Deletes leave live and persisted state on successful refresh. Initialization is per-source atomic; an early successful save during multi-source startup includes only reconciled live sources. A later failure keeps earlier successes and may leave other sources needing re-indexing after restart.
104
+
105
+ Removing a source clears it from memory immediately; the next successful source refresh saves that removal. This synchronous API does not promise an immediate durable write. To persist a document deletion, refresh its still-registered source, including when its listing is empty.
106
+
107
+ ### Version custom parsers and embedding configurations
108
+
109
+ ```ts
110
+ import { ParserRegistry } from '@leu2m/semantic-search';
111
+ const parsers = new ParserRegistry(myParsers, myFileTypes, 'my-parser-behavior-v1');
112
+ const engine = new SemanticSearchEngine({ parsers, storage, embedder });
113
+ ```
114
+
115
+ The version describes all custom parser behavior. Increment it when that behavior changes. Without an explicit version, custom registries still work, but persisted reuse is disabled. Registry definitions, chunker version and effective chunking options are also checked automatically. Incompatible snapshots rebuild wholesale. Keep parser and embedder configurations immutable; a changed embedder identity/dimension requires a new engine. Its ID must include model revision and vector-affecting settings, not just a friendly model name.
116
+
117
+ `IndexStorage.save` must atomically publish and reject failure/abort before publication. Once it resolves successfully, late cancellation cannot prevent the matching live commit. See [architecture](architecture.md) for the exact failure and cancellation policy. MiniLM is an optional adapter; hosts control its execution and caches.
118
+
119
+ ## Optional MiniLM: online or cached Hub loading
120
+
121
+ Install `@huggingface/transformers@4.2.0` alongside the package only when using `/minilm`. Built tarball installs for core-only usage omit the optional peer. Git/source builds install development dependencies for compilation, including Transformers.js.
122
+
123
+ ```ts
124
+ import { MiniLMEmbedder } from '@leu2m/semantic-search/minilm';
125
+ const embedder = new MiniLMEmbedder({
126
+ device: 'cpu', dtype: 'fp32', batchSize: 4,
127
+ cacheDir: configuration.modelCacheDirectory,
128
+ onProgress: event => showModelLoadProgress(event),
129
+ });
130
+ const engine = new SemanticSearchEngine({ embedder, storage });
131
+ engine.addSource(mySource);
132
+ await engine.initialize();
133
+ // Later, after every engine/consumer has finished with this adapter:
134
+ await embedder.dispose();
135
+ ```
136
+
137
+ No runtime/model load occurs on import or construction. The first nonempty call loads once; subsequent calls reuse the pipeline. The host controls the batch size and execution location. Empty document batches return no vectors without loading. Disposal is terminal; later calls reject. Concurrent calls serialize; cancellation is checked around expensive stages but does not interrupt native work already running.
138
+
139
+ The default model is `Xenova/all-MiniLM-L6-v2` at `751bff37182d3f1213fa05d7196b954e230abad9`, using 384-dimensional normalized mean-pooled embeddings. Custom model IDs/paths require an explicit revision and compatible 384-dimensional output. Model/revision, dtype/device, runtime/adapter behavior, pooling/normalization and tokenizer policy participate in identity. Batch size, cache location and `localFilesOnly` do not. Mutable revisions such as `main` weaken reproducibility. The default tokenizer truncates beyond 512 tokens.
140
+
141
+ `cacheDir` is a cache location, not an offline promise. `localFilesOnly` forwards `local_files_only`; Transformers.js 4.2.0 tokenizer metadata discovery does not forward that option, revision or cache location on every request. Thus a Hub ID may still trigger remote metadata discovery even with populated cached files and `localFilesOnly: true`. Do not use that flag alone to promise zero network access.
142
+
143
+ ## Strict offline: host-staged assets
144
+
145
+ The host provisions model assets before entering offline mode and ensures their bytes match the declared immutable revision. For the default CPU/fp32 deployment, stage this layout beneath a host-selected base directory:
146
+
147
+ ```text
148
+ <localModelPath>/
149
+ └── Xenova/all-MiniLM-L6-v2/
150
+ ├── config.json
151
+ ├── tokenizer.json
152
+ ├── tokenizer_config.json
153
+ └── onnx/model.onnx
154
+ ```
155
+
156
+ Other dtype choices require their corresponding ONNX assets. The host may copy the verified pinned cache assets into this deployment layout as a provisioning step; the adapter never copies or downloads staged files itself. A populated revision-specific Hub cache has a different role and is not sufficient by itself for strict offline Hub-ID resolution in 4.2.0.
157
+
158
+ ```ts
159
+ // HOST bootstrap, before constructing/using any models in this runtime realm.
160
+ import { env } from '@huggingface/transformers';
161
+ import { MiniLMEmbedder, MINILM_DEFAULT_REVISION } from '@leu2m/semantic-search/minilm';
162
+
163
+ env.localModelPath = configuration.stagedModelBaseWithTrailingSlash;
164
+ env.allowLocalModels = true;
165
+ env.allowRemoteModels = false;
166
+
167
+ const embedder = new MiniLMEmbedder({
168
+ revision: MINILM_DEFAULT_REVISION,
169
+ device: 'cpu', dtype: 'fp32', localFilesOnly: true,
170
+ });
171
+ const engine = new SemanticSearchEngine({ embedder, storage });
172
+ engine.addSource(mySource);
173
+ await engine.initialize();
174
+ ```
175
+
176
+ These global settings belong to the host and affect other Transformers.js users in the same realm. The adapter reads the selected runtime but never changes its environment. Configure globals before concurrent users start; use separate host-managed workers/processes when different policies need isolation. Local asset resolution does not enforce the Hub revision: hosts must bind staged bytes to the declared revision and change identity when those bytes change. Keeping the same model/revision and settings with the exact same staged bytes preserves vector compatibility with Hub loading.
177
+
178
+ The CPU acceptance run disables model caches and remote loading and traps fetch calls; staged inference succeeds with zero network attempts. Missing staged assets must fail instead of downloading replacements.
179
+
180
+ ## Browser and Web Worker hosts
181
+
182
+ Inside a host-created worker, import only core and `/minilm`, install/bundle the optional runtime, and choose `device: 'wasm'` or supported `webgpu` explicitly. The default `cpu` device targets Node. The adapter creates no worker, worker thread or process. The host supplies sources and storage adapters and transfers results as appropriate.
183
+
184
+ For browser offline deployment, the host stages and serves/caches model files, the JavaScript runtime and required WASM/backend assets. Configure `env.localModelPath`, `env.allowRemoteModels`, and `env.backends.onnx.wasm.wasmPaths` as required by that deployment. Local browser URLs may still involve fetch and require a functioning local/offline serving or service-worker strategy. The Node zero-fetch acceptance does not certify a browser asset-serving arrangement; real browser/backend inference must be validated by the host. Core and the adapter source remain free of Node filesystem/process imports.
185
+
186
+ The accepted [loading ADR](decisions/0001-minilm-loading.md) and [evaluation matrix](evaluation.md) distinguish supported inference paths from the known Hub-cache-only limitation.
187
+
188
+ ## Tested standalone examples and deployment limits
189
+
190
+ Start with [maintained examples](../examples/README.md): `core.mjs` exercises lexical-only usage and custom source/storage/embedder wiring; `filesystem.mjs` uses an explicit approved root and store path; `minilm.mjs` owns model disposal. These are public-export consumer code, not imports of internals. [API responsibilities](api.md) documents failures, mutability, source registration, storage single-writer rules and worker placement for each contract.
191
+
192
+ Alpha.5's explicit browser harness bundles the installed core example into a Chrome page and module worker, with no Node globals or Transformers peer. Its storage is memory-backed to prove the contract; it is not a durable IndexedDB implementation. The separate MiniLM worker test uses `device: 'wasm'`, fp32, `env.backends.onnx.wasm.numThreads = 1`, host-served backend assets and local model URLs, with remote models disabled and external requests blocked. It validates embeddings, semantic retrieval and disposal. All these settings are host test bootstrap, never adapter/core side effects. No WebGPU or cross-browser certification is claimed.
193
+
194
+ The browser test serves assets over loopback HTTP. Zero external requests differs from zero browser fetches: local URLs still need a server or host-managed offline cache. Node staged-local acceptance retains its stronger zero-fetch observation. Hub cache-only offline remains unsupported under Transformers.js 4.2.0; `localFilesOnly` is not a guarantee.
195
+
196
+ Before broader deployment, choose a retrieval mode for the workload and treat candidates as unverified evidence. Alpha.5 found high-similarity near misses and noisy lexical interference; unchanged hybrid defaults are a compromise. Exact wide-scope search becomes expensive at tens of thousands of candidates on the measured laptop. Filters and host-controlled workers help scope cost; no threshold, ANN, mode router or new host adapter is included.
197
+
198
+ ## Consuming evidence without inventing an answerability claim
199
+
200
+ The Alpha.6 [host example](../examples/evidence.mjs) returns candidate retrieval observations with `answerability: 'not-assessed'`. Copy/adapt it as consumer code; it is not another package export. `retrieved` and `no-results` describe only the returned list. No-result cause is not inferred, and unavailable models, malformed vectors or aborts remain rejected operations rather than empty success. A request spanning refresh may return a coherent older view; the example reports revision overlap but cannot detect source edits that were never indexed.
201
+
202
+ Hosts deciding to act on a passage must establish whether it contains the requested fact/task evidence, whether multiple passages are required, and whether it is current and authoritative. A procedure for collecting a measurement cannot substitute for that measurement. High rank, similarity or lexical/semantic agreement is not an assessment. The library does not require a particular human/rules/model verification method or define an evaluator ABI. If a host adds assessment, failures/unavailable/partial/stale evaluation must remain distinct from a justified finding that the *assessed set* is insufficient. Do not store those decisions in the retrieval cache as though they were candidate results.
203
+
204
+ Runtime/API compatibility is unchanged. Keep current mode choices, generic embedder/storage/source adapters and optional MiniLM settings. See [the decision and evidence](alpha6-answerability.md); independent Alpha.6C will review this boundary before a release-gate recommendation.
@@ -0,0 +1,11 @@
1
+ # Next: independent Alpha.6C adversarial review
2
+
3
+ Alpha.6A+B is complete. It selects retrieval-only core, clarifies the public contract, adds a tested unassessed-evidence host example and development chunk/set evaluation. Production ranking/API remains unchanged and package version stays `0.1.0-alpha.5`. No Beta promotion is made.
4
+
5
+ The next step is **Alpha.6C by an independent model**, not further implementation by the current author. Start with [the design record](alpha6-answerability.md), [ADR 0003](decisions/0003-retrieval-evidence-boundary.md), [traces](alpha6-evidence-traces.md), [evaluation](evaluation.md) and the ignored `local/alpha6-c-handoff.md` in the working checkout. The handoff is intentionally not part of Git or the package.
6
+
7
+ C must challenge the relevance/sufficiency judgments and task interpretations, construct/review held-out adversarial cases, inspect the tiny B source/comment/example diff, and decide whether this boundary is useful to unrelated consumers. Do not trust labels just because they are documented. Prioritize topic-versus-fact near misses, dates/measurements, contradictory evidence, multi-chunk requirements, incomplete code, filters and stale sources. Explicitly revisit subjective locator/ambiguous interpretations and whether publication/backup requirements were added too broadly.
8
+
9
+ The development run preserves every frozen Alpha.5 metric. Sufficient-set hit@5 is .6286/.8857/.8857 (lexical/semantic/hybrid) on 35 supported tasks; ten unsupported queries still receive results, and five are underspecified. These figures validate neither calibrated answerability nor independent retrieval quality. RRF/defaults remain unchanged. C owns the independent release gate and may recommend keeping Alpha; Beta is not preauthorized by A+B.
10
+
11
+ Do not start ANN, reranking, automatic routing, a score/evaluator API, consumer integration, Vector/Nexus/Obsidian work or another campaign as part of C without a separate decision and authorization.
@@ -0,0 +1,38 @@
1
+ # Maintained examples
2
+
3
+ These files use public package exports. They are included in the tarball as readable examples, not new package subpath exports. Copy/adapt them into a consumer or run the checkout after `npm run build`.
4
+
5
+ ```sh
6
+ node --input-type=module -e "import { exerciseCore } from './examples/core.mjs'; console.log(await exerciseCore());"
7
+ ```
8
+
9
+ - `evidence.mjs`: `retrieveUnassessedEvidence(engine, request)` preserves candidate evidence and reports `retrieved`/`no-results`, always with `answerability: 'not-assessed'`. Errors/abort reject; `indexChangedDuringSearch` reports only observed index revision overlap, never source freshness. This envelope is an example-local choice, not a public package contract.
10
+ - `core.mjs`: lexical-only indexing; custom SearchSource, memory-only IndexStorage, toy injected Embedder; semantic/hybrid retrieval, cache and restart checks. The two-topic vectors demonstrate plumbing and are not a production semantic model.
11
+ - `filesystem.mjs`: `searchFolder(approvedRoot, indexFilename, query)` uses explicit host paths and atomic Node storage. Create the index parent first; keep it outside indexed roots.
12
+ - `minilm.mjs`: `searchWithMiniLM(source, storage, query, options)` manages one concrete model lifetime. First inference may download files. Strict local deployments configure Transformers.js globals in host bootstrap before using this function; see integration docs.
13
+
14
+ The installed-tarball Node/browser test executes `exerciseCore` and `searchFolder`. The ordinary package test loads the MiniLM example with an empty source without loading a runtime; real Node and browser MiniLM acceptance separately prove actual inference. The browser harness bundles the installed core example into a page and a module worker. Production worker topology remains host-owned.
15
+
16
+ Browser commands (explicit, separate from offline `npm test`):
17
+
18
+ ```sh
19
+ npx playwright install chromium
20
+ npm run test:browser
21
+ npm run test:browser:minilm
22
+ ```
23
+
24
+ `BROWSER_EXECUTABLE` optionally selects a host browser executable. Browser MiniLM requires provisioned pinned fp32 assets (`MINILM_LOCAL_MODEL_DIR` or the documented `.cache/minilm` fixture), serves them and backend files from a temporary loopback server, selects WASM with one thread, and forbids external requests. It downloads no model itself. A successful local-server smoke is not certification of every browser, WebGPU, service-worker cache or offline PWA deployment.
25
+
26
+ ## Evidence interpretation example
27
+
28
+ ```js
29
+ import { retrieveUnassessedEvidence } from './evidence.mjs'; // Copy/adapt the maintained host example.
30
+ const observation = await retrieveUnassessedEvidence(engine, {
31
+ query: 'What was the measured duration of the latest recovery drill?',
32
+ mode: 'hybrid',
33
+ });
34
+ // observation.candidates are ranked passages, possibly just a drill procedure.
35
+ // observation.answerability is always 'not-assessed', including when candidates is [].
36
+ ```
37
+
38
+ Do not convert `no-results` to “the corpus cannot answer”: lexical mismatches, empty queries or filters can also produce it. Do not convert nonempty results to “supported”: instructions to measure are not a measured value. A host choosing to verify support must separately define the task, eligible scope, evidence completeness, source currency/authority and treatment of contradictions. A failed assessment of top-K evidence does not prove absence elsewhere. There is no evaluator interface or assessment cache in this example. The core example and installed browser/worker harness exercise this helper; focused tests cover cancellation, model errors and concurrent refresh.
@@ -0,0 +1,53 @@
1
+ import { SemanticSearchEngine, createSearchSession } from '@leu2m/semantic-search';
2
+ import { retrieveUnassessedEvidence } from './evidence.mjs';
3
+ /** A minimal host source. Replace its read/list methods with your authorized content APIs. */
4
+ export function memorySource(files, id = 'example') {
5
+ return { id, type: 'memory',
6
+ async list(options = {}) { options.signal?.throwIfAborted(); return [...files.keys()].map(path => ({ id: path, sourceId: id, name: path, path, uri: `memory://${id}/${path}` })); },
7
+ async read(id, options = {}) { options.signal?.throwIfAborted(); if (!files.has(id))
8
+ throw new Error('Document unavailable'); return { text: files.get(id) }; },
9
+ };
10
+ }
11
+ /** Test/example storage only: no browser database or durable production adapter is implied. */
12
+ export function memoryStorage() {
13
+ let snapshot = null;
14
+ return { async load(options = {}) { options.signal?.throwIfAborted(); return structuredClone(snapshot); },
15
+ async save(candidate, options = {}) { options.signal?.throwIfAborted(); const next = structuredClone(candidate); snapshot = next; } };
16
+ }
17
+ /** Toy vectors demonstrate injection only. Substitute your real text embedder in applications. */
18
+ export function demonstrationEmbedder() {
19
+ const stats = { documents: 0, queries: 0 };
20
+ const encode = text => new Float32Array(/password|credential|login/i.test(text) ? [2, 0] : [0, 3]);
21
+ return { id: 'example-two-topics-v1', dimensions: 2, stats,
22
+ async embedDocuments(texts, options = {}) { options.signal?.throwIfAborted(); stats.documents += texts.length; return texts.map(encode); },
23
+ async embedQuery(text, options = {}) { options.signal?.throwIfAborted(); stats.queries++; return encode(text); } };
24
+ }
25
+ export async function exerciseCore() {
26
+ const source = memorySource(new Map([['account.txt', 'Recover a lost password'], ['garden.txt', 'Water plants at dawn']]));
27
+ const lexical = new SemanticSearchEngine();
28
+ lexical.addSource(source);
29
+ await lexical.initialize();
30
+ const evidence = await retrieveUnassessedEvidence(lexical, { query: 'password' });
31
+ if (evidence.answerability !== 'not-assessed' || evidence.retrieval !== 'retrieved' || evidence.candidates[0]?.path !== 'account.txt')
32
+ throw new Error('Lexical example failed');
33
+ const storage = memoryStorage();
34
+ const embedder = demonstrationEmbedder();
35
+ for (let restart = 0; restart < 2; restart++) {
36
+ const engine = new SemanticSearchEngine({ storage, embedder });
37
+ engine.addSource(source);
38
+ await engine.initialize();
39
+ for (const mode of ['semantic', 'hybrid']) {
40
+ const run = createSearchSession(engine);
41
+ const request = { query: 'credentials', mode };
42
+ const hit = (await run.search(request))[0];
43
+ if (hit?.path !== 'account.txt' || hit.semanticRank !== 1 || !hit.uri || !hit.chunkId)
44
+ throw new Error('Rank/provenance example failed');
45
+ await run.search(request);
46
+ if (run.stats.hits !== 1)
47
+ throw new Error('Cache example failed');
48
+ }
49
+ }
50
+ if (embedder.stats.documents !== 2 || embedder.stats.queries !== 4)
51
+ throw new Error('Restart/query reuse example failed');
52
+ return { lexical: true, semantic: true, hybrid: true, restart: true, ...embedder.stats };
53
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Host-side example, not a new package API or an answerability evaluator.
3
+ * Empty and nonempty searches both leave support for the task unassessed.
4
+ * @param {import('@leu2m/semantic-search').SemanticSearchEngine} engine
5
+ * @param {import('@leu2m/semantic-search').SearchRequest} request
6
+ */
7
+ export async function retrieveUnassessedEvidence(engine, request) {
8
+ request.signal?.throwIfAborted();
9
+ const startedRevision = engine.revision;
10
+ // Do not convert failure/cancellation into an apparently successful empty search.
11
+ const candidates = await engine.search(request);
12
+ request.signal?.throwIfAborted();
13
+ return {
14
+ retrieval: candidates.length ? 'retrieved' : 'no-results',
15
+ answerability: 'not-assessed',
16
+ candidates,
17
+ // False is NOT a freshness guarantee: sources can change without an index refresh.
18
+ indexChangedDuringSearch: engine.revision !== startedRevision,
19
+ };
20
+ }
@@ -0,0 +1,9 @@
1
+ import { SemanticSearchEngine } from '@leu2m/semantic-search';
2
+ import { FileSystemFolderSource, FileIndexStorage } from '@leu2m/semantic-search/filesystem';
3
+ /** Host chooses an approved absolute root and an index filename outside that root. */
4
+ export async function searchFolder(root, indexFilename, query) {
5
+ const engine = new SemanticSearchEngine({ storage: new FileIndexStorage(indexFilename) });
6
+ engine.addSource(new FileSystemFolderSource({ id: 'folder', root }));
7
+ await engine.initialize();
8
+ return engine.search({ query, mode: 'lexical' });
9
+ }
@@ -0,0 +1,15 @@
1
+ import { SemanticSearchEngine } from '@leu2m/semantic-search';
2
+ import { MiniLMEmbedder } from '@leu2m/semantic-search/minilm';
3
+ /** Run in the host's chosen process/worker. First inference may download assets. */
4
+ export async function searchWithMiniLM(source, storage, query, options = {}) {
5
+ const embedder = new MiniLMEmbedder(options);
6
+ try {
7
+ const engine = new SemanticSearchEngine({ embedder, storage });
8
+ engine.addSource(source);
9
+ await engine.initialize();
10
+ return await engine.search({ query, mode: 'hybrid' });
11
+ }
12
+ finally {
13
+ await embedder.dispose();
14
+ }
15
+ }
package/package.json ADDED
@@ -0,0 +1,73 @@
1
+ {
2
+ "name": "@leu2m/semantic-search",
3
+ "version": "0.2.0-beta.1",
4
+ "description": "Source-oriented retrieval infrastructure with parsing, chunking and provenance.",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ },
13
+ "./filesystem": {
14
+ "types": "./dist/adapters/filesystem.d.ts",
15
+ "import": "./dist/adapters/filesystem.js"
16
+ },
17
+ "./minilm": {
18
+ "types": "./dist/adapters/minilm.d.ts",
19
+ "import": "./dist/adapters/minilm.js"
20
+ }
21
+ },
22
+ "files": [
23
+ "dist",
24
+ "README.md",
25
+ "LICENSE",
26
+ "docs",
27
+ "examples"
28
+ ],
29
+ "sideEffects": false,
30
+ "engines": {
31
+ "node": ">=20"
32
+ },
33
+ "license": "MIT",
34
+ "author": "leu2m",
35
+ "repository": {
36
+ "type": "git",
37
+ "url": "git+ssh://git@github.com/leu2m/semantic-search.git"
38
+ },
39
+ "scripts": {
40
+ "clean": "node scripts/clean.mjs",
41
+ "build": "npm run clean && tsc -p tsconfig.json",
42
+ "typecheck": "tsc -p tsconfig.json --noEmit",
43
+ "test": "npm run build && node tests/run.mjs",
44
+ "docs:file-types": "npm run build && node scripts/file-types.mjs",
45
+ "benchmark": "npm run build && node scripts/benchmark.mjs && node scripts/lifecycle-benchmark.mjs && node scripts/retrieval-benchmark.mjs",
46
+ "prepack": "npm run build && node scripts/file-types.mjs --check",
47
+ "prepare": "npm run build",
48
+ "test:minilm": "npm run build && node tests/minilm.acceptance.mjs",
49
+ "benchmark:minilm": "npm run build && node scripts/minilm-benchmark.mjs",
50
+ "test:browser": "npm run build && node tests/browser.real.mjs",
51
+ "test:browser:minilm": "npm run build && node tests/browser.real.mjs --minilm",
52
+ "evaluate": "npm run build && node scripts/representative-evaluation.mjs",
53
+ "evaluate:minilm": "npm run build && node scripts/representative-evaluation.mjs --minilm",
54
+ "benchmark:scale": "npm run build && node scripts/scale-benchmark.mjs",
55
+ "evaluate:evidence": "npm run build && node scripts/evidence-evaluation.mjs",
56
+ "evaluate:evidence:minilm": "npm run build && node scripts/evidence-evaluation.mjs --minilm"
57
+ },
58
+ "devDependencies": {
59
+ "@huggingface/transformers": "4.2.0",
60
+ "@types/node": "^22.15.17",
61
+ "esbuild": "0.25.5",
62
+ "playwright": "1.62.1",
63
+ "typescript": "^5.8.3"
64
+ },
65
+ "peerDependencies": {
66
+ "@huggingface/transformers": "4.2.0"
67
+ },
68
+ "peerDependenciesMeta": {
69
+ "@huggingface/transformers": {
70
+ "optional": true
71
+ }
72
+ }
73
+ }