@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.
- package/LICENSE +21 -0
- package/README.md +146 -0
- package/dist/adapters/file-storage.d.ts +8 -0
- package/dist/adapters/file-storage.js +55 -0
- package/dist/adapters/file-storage.js.map +1 -0
- package/dist/adapters/filesystem.d.ts +23 -0
- package/dist/adapters/filesystem.js +119 -0
- package/dist/adapters/filesystem.js.map +1 -0
- package/dist/adapters/minilm.d.ts +50 -0
- package/dist/adapters/minilm.js +141 -0
- package/dist/adapters/minilm.js.map +1 -0
- package/dist/catalog.d.ts +3 -0
- package/dist/catalog.js +26 -0
- package/dist/catalog.js.map +1 -0
- package/dist/chunking.d.ts +9 -0
- package/dist/chunking.js +54 -0
- package/dist/chunking.js.map +1 -0
- package/dist/contracts.d.ts +163 -0
- package/dist/contracts.js +4 -0
- package/dist/contracts.js.map +1 -0
- package/dist/embedding-input.d.ts +4 -0
- package/dist/embedding-input.js +13 -0
- package/dist/embedding-input.js.map +1 -0
- package/dist/engine.d.ts +63 -0
- package/dist/engine.js +257 -0
- package/dist/engine.js.map +1 -0
- package/dist/file-types/registry.d.ts +13 -0
- package/dist/file-types/registry.js +47 -0
- package/dist/file-types/registry.js.map +1 -0
- package/dist/file-types/types.d.ts +11 -0
- package/dist/file-types/types.js +2 -0
- package/dist/file-types/types.js.map +1 -0
- package/dist/index-state.d.ts +17 -0
- package/dist/index-state.js +91 -0
- package/dist/index-state.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/parsers/index.d.ts +28 -0
- package/dist/parsers/index.js +113 -0
- package/dist/parsers/index.js.map +1 -0
- package/dist/retrieval.d.ts +20 -0
- package/dist/retrieval.js +108 -0
- package/dist/retrieval.js.map +1 -0
- package/docs/alpha5-hardening.md +86 -0
- package/docs/alpha6-answerability.md +131 -0
- package/docs/alpha6-c-validation.md +490 -0
- package/docs/alpha6-evidence-traces.md +486 -0
- package/docs/api.md +43 -0
- package/docs/architecture.md +121 -0
- package/docs/benchmarks/alpha5-retrieval.json +5233 -0
- package/docs/benchmarks/alpha5-scale.json +505 -0
- package/docs/benchmarks/alpha6-evidence.json +14185 -0
- package/docs/benchmarks/alpha6c-heldout-real.json +9389 -0
- package/docs/decisions/0001-minilm-loading.md +32 -0
- package/docs/decisions/0002-retrieval-modes.md +26 -0
- package/docs/decisions/0003-retrieval-evidence-boundary.md +19 -0
- package/docs/evaluation.md +315 -0
- package/docs/file-types.md +31 -0
- package/docs/integration.md +204 -0
- package/docs/next-slice.md +11 -0
- package/examples/README.md +38 -0
- package/examples/core.mjs +53 -0
- package/examples/evidence.mjs +20 -0
- package/examples/filesystem.mjs +9 -0
- package/examples/minilm.mjs +15 -0
- package/package.json +73 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"minilm.js","sourceRoot":"","sources":["../../src/adapters/minilm.ts"],"names":[],"mappings":"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","sourcesContent":["import type { FeatureExtractionPipeline, ProgressCallback } from '@huggingface/transformers';\nimport type { Embedder, OperationOptions } from '../contracts.js';\n\nexport const MINILM_DEFAULT_MODEL = 'Xenova/all-MiniLM-L6-v2';\nexport const MINILM_DEFAULT_REVISION = '751bff37182d3f1213fa05d7196b954e230abad9';\nconst RUNTIME_VERSION = '4.2.0';\nexport interface MiniLMEmbedderOptions {\n /** Custom/local model must produce 384-dimensional sentence embeddings; supply its revision explicitly. */\n model?: string;\n revision?: string;\n batchSize?: number;\n /** Node default is cpu. Browser/worker hosts select wasm or supported webgpu explicitly. */\n device?: 'cpu' | 'wasm' | 'webgpu';\n dtype?: 'fp32' | 'fp16' | 'q8';\n /** Forwarded to Transformers.js; Hub-ID metadata probes in 4.2.0 may still access the network.\n * Strict offline operation requires host-staged assets and host env.allowRemoteModels=false.\n */\n localFilesOnly?: boolean;\n /** Passed to the runtime; a populated Hub cache alone does not guarantee offline resolution. */\n cacheDir?: string;\n onProgress?: (event: { status: string; file?: string; progress?: number; loaded?: number; total?: number }) => void;\n}\n/** Optional local sentence embedder. Import/construction never load the runtime or model.\n * Hosts own placement and disposal. Calls are serialized; cancellation cannot preempt native inference.\n */\nexport class MiniLMEmbedder implements Embedder {\n readonly dimensions = 384;\n readonly id: string;\n private readonly options: Readonly<MiniLMEmbedderOptions>;\n private readonly model: string;\n private readonly revision: string;\n private readonly batchSize: number;\n private readonly device: 'cpu' | 'wasm' | 'webgpu';\n private readonly dtype: 'fp32' | 'fp16' | 'q8';\n private extractor: FeatureExtractionPipeline | undefined;\n private tail: Promise<void> = Promise.resolve();\n private disposed = false;\n private disposal: Promise<void> | undefined;\n constructor(options: MiniLMEmbedderOptions = {}) {\n this.options = { ...options };\n this.model = options.model ?? MINILM_DEFAULT_MODEL;\n this.revision = options.revision ?? MINILM_DEFAULT_REVISION;\n this.batchSize = options.batchSize ?? 4;\n this.device = options.device ?? 'cpu'; this.dtype = options.dtype ?? 'fp32';\n if (!this.model.trim() || !this.revision.trim() || (this.model !== MINILM_DEFAULT_MODEL && !options.revision)) throw new Error('A custom MiniLM model requires an explicit revision');\n if (!Number.isSafeInteger(this.batchSize) || this.batchSize < 1) throw new Error('Invalid MiniLM batch size');\n if (!['cpu', 'wasm', 'webgpu'].includes(this.device) || !['fp32', 'fp16', 'q8'].includes(this.dtype)) throw new Error('Unsupported MiniLM device or dtype');\n this.id = JSON.stringify({ adapter: 'minilm-v1', runtime: RUNTIME_VERSION, model: this.model, revision: this.revision,\n device: this.device, dtype: this.dtype, pooling: 'mean', normalize: true, truncation: 'pinned-tokenizer-default' });\n }\n private check(options: OperationOptions): void {\n options.signal?.throwIfAborted();\n if (this.disposed) throw new Error('MiniLMEmbedder is disposed');\n }\n private async load(options: OperationOptions): Promise<FeatureExtractionPipeline> {\n this.check(options);\n if (!this.extractor) {\n try {\n const runtime = await import('@huggingface/transformers');\n this.check(options);\n if (runtime.env.version !== RUNTIME_VERSION) throw new Error(`Expected @huggingface/transformers ${RUNTIME_VERSION}, received ${runtime.env.version}`);\n const loading = {\n revision: this.revision, device: this.device, dtype: this.dtype,\n ...(this.options.cacheDir !== undefined ? { cache_dir: this.options.cacheDir } : {}),\n ...(this.options.localFilesOnly !== undefined ? { local_files_only: this.options.localFilesOnly } : {}),\n ...(this.options.onProgress ? { progress_callback: (event: Parameters<ProgressCallback>[0]) => this.options.onProgress!({\n status: event.status,\n ...('file' in event ? { file: event.file } : {}),\n ...('progress' in event ? { progress: event.progress } : {}),\n ...('loaded' in event ? { loaded: event.loaded } : {}),\n ...('total' in event ? { total: event.total } : {}),\n }) } : {}),\n };\n // Avoid factory preflight; the 4.2.0 tokenizer still probes unversioned metadata.\n // Strict offline hosts stage local assets and configure the runtime globally (see integration docs).\n const tokenizer = await runtime.AutoTokenizer.from_pretrained(this.model, loading);\n this.check(options);\n const model = await runtime.AutoModel.from_pretrained(this.model, loading);\n try { this.extractor = new runtime.FeatureExtractionPipeline({ task: 'feature-extraction', tokenizer, model }); }\n catch (cause) { await model.dispose(); throw cause; }\n this.options.onProgress?.({ status: 'ready' });\n } catch (cause) {\n this.check(options);\n throw new Error(`MiniLM model load failed (${this.model}@${this.revision}). Install @huggingface/transformers@${RUNTIME_VERSION} and check model/cache availability.`, { cause });\n }\n }\n this.check(options); return this.extractor;\n }\n async embedDocuments(texts: string[], options: OperationOptions = {}): Promise<Float32Array[]> {\n this.check(options);\n if (!Array.isArray(texts) || texts.some(text => typeof text !== 'string')) throw new Error('MiniLM inputs must be strings');\n const inputs = [...texts]; const operationOptions = { ...options };\n const operation = this.tail.then(() => this.run(inputs, operationOptions));\n this.tail = operation.then(() => {}, () => {});\n return operation;\n }\n async embedQuery(text: string, options: OperationOptions = {}): Promise<Float32Array> {\n return (await this.embedDocuments([text], options))[0]!;\n }\n private async run(texts: string[], options: OperationOptions): Promise<Float32Array[]> {\n this.check(options); if (!texts.length) return [];\n const extractor = await this.load(options); const vectors: Float32Array[] = [];\n for (let start = 0; start < texts.length; start += this.batchSize) {\n this.check(options);\n const batch = texts.slice(start, start + this.batchSize);\n const output = await extractor(batch, { pooling: 'mean', normalize: true });\n try {\n this.check(options);\n const data = output.data;\n if (output.type !== 'float32' || !(data instanceof Float32Array) || output.dims.length !== 2 ||\n output.dims[0] !== batch.length || output.dims[1] !== this.dimensions || data.length !== batch.length * this.dimensions) throw new Error('Invalid MiniLM output shape/type');\n for (let row = 0; row < batch.length; row++) {\n const vector = data.slice(row * this.dimensions, (row + 1) * this.dimensions);\n const norm = Math.sqrt(vector.reduce((sum, value) => sum + value * value, 0));\n if (!vector.every(Number.isFinite) || Math.abs(norm - 1) > 0.001) throw new Error('Invalid MiniLM output: expected finite unit vectors');\n vectors.push(vector);\n }\n } finally { output.dispose(); }\n }\n this.check(options); return vectors;\n }\n /** Terminal and idempotent. Waits for active work, releases the pipeline; further use rejects. */\n dispose(): Promise<void> {\n if (!this.disposal) {\n this.disposed = true;\n this.disposal = this.tail.then(async () => {\n const extractor = this.extractor; this.extractor = undefined;\n if (extractor) await extractor.dispose();\n });\n }\n return this.disposal;\n }\n}\n"]}
|
package/dist/catalog.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { extensionOf } from './file-types/registry.js';
|
|
2
|
+
export function catalogEntry(parsed) {
|
|
3
|
+
const doc = parsed.document;
|
|
4
|
+
const tags = Array.isArray(doc.metadata?.['tags']) ? doc.metadata['tags'].filter((tag) => typeof tag === 'string') : [];
|
|
5
|
+
return { documentId: doc.id, sourceId: doc.sourceId, uri: doc.uri, title: parsed.title,
|
|
6
|
+
...(doc.path !== undefined ? { path: doc.path } : {}), fileType: extensionOf(doc.path ?? doc.name),
|
|
7
|
+
...(doc.createdAt !== undefined ? { createdAt: doc.createdAt } : {}), ...(doc.modifiedAt !== undefined ? { modifiedAt: doc.modifiedAt } : {}),
|
|
8
|
+
tags: [...tags], headings: [...parsed.headings], outgoingLinks: [...parsed.outgoingLinks] };
|
|
9
|
+
}
|
|
10
|
+
export function matchesFilters(entry, filters = {}) {
|
|
11
|
+
if (filters.pathPrefix !== undefined) {
|
|
12
|
+
const prefix = filters.pathPrefix.replace(/\\/g, '/').replace(/^\.\//, '').replace(/^\/+|\/+$/g, '');
|
|
13
|
+
if (prefix && prefix !== '.' && entry.path !== prefix && !entry.path?.startsWith(prefix + '/'))
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
if (filters.extensions && !filters.extensions.map(ext => ext.startsWith('.') ? ext.toLowerCase() : '.' + ext.toLowerCase()).includes(entry.fileType ?? ''))
|
|
17
|
+
return false;
|
|
18
|
+
if (filters.tags && !filters.tags.every(tag => entry.tags.includes(tag)))
|
|
19
|
+
return false;
|
|
20
|
+
if (filters.modifiedAfter !== undefined && (entry.modifiedAt === undefined || entry.modifiedAt < filters.modifiedAfter))
|
|
21
|
+
return false;
|
|
22
|
+
if (filters.createdAfter !== undefined && (entry.createdAt === undefined || entry.createdAt < filters.createdAfter))
|
|
23
|
+
return false;
|
|
24
|
+
return filters.linkedTo === undefined || entry.outgoingLinks.includes(filters.linkedTo);
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=catalog.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"catalog.js","sourceRoot":"","sources":["../src/catalog.ts"],"names":[],"mappings":"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","sourcesContent":["import type { CatalogEntry, ParsedDocument, SearchFilters } from './contracts.js';\nimport { extensionOf } from './file-types/registry.js';\nexport function catalogEntry(parsed: ParsedDocument): CatalogEntry {\n const doc = parsed.document;\n const tags = Array.isArray(doc.metadata?.['tags']) ? doc.metadata['tags'].filter((tag): tag is string => typeof tag === 'string') : [];\n return { documentId: doc.id, sourceId: doc.sourceId, uri: doc.uri, title: parsed.title,\n ...(doc.path !== undefined ? { path: doc.path } : {}), fileType: extensionOf(doc.path ?? doc.name),\n ...(doc.createdAt !== undefined ? { createdAt: doc.createdAt } : {}), ...(doc.modifiedAt !== undefined ? { modifiedAt: doc.modifiedAt } : {}),\n tags: [...tags], headings: [...parsed.headings], outgoingLinks: [...parsed.outgoingLinks] };\n}\nexport function matchesFilters(entry: CatalogEntry, filters: SearchFilters = {}): boolean {\n if (filters.pathPrefix !== undefined) {\n const prefix = filters.pathPrefix.replace(/\\\\/g, '/').replace(/^\\.\\//, '').replace(/^\\/+|\\/+$/g, '');\n if (prefix && prefix !== '.' && entry.path !== prefix && !entry.path?.startsWith(prefix + '/')) return false;\n }\n if (filters.extensions && !filters.extensions.map(ext => ext.startsWith('.') ? ext.toLowerCase() : '.' + ext.toLowerCase()).includes(entry.fileType ?? '')) return false;\n if (filters.tags && !filters.tags.every(tag => entry.tags.includes(tag))) return false;\n if (filters.modifiedAfter !== undefined && (entry.modifiedAt === undefined || entry.modifiedAt < filters.modifiedAfter)) return false;\n if (filters.createdAfter !== undefined && (entry.createdAt === undefined || entry.createdAt < filters.createdAfter)) return false;\n return filters.linkedTo === undefined || entry.outgoingLinks.includes(filters.linkedTo);\n}\n"]}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { IndexedChunk, ParsedDocument } from './contracts.js';
|
|
2
|
+
export declare const CHUNKER_VERSION = "1";
|
|
3
|
+
export interface ChunkOptions {
|
|
4
|
+
targetChars?: number;
|
|
5
|
+
overlapLines?: number;
|
|
6
|
+
}
|
|
7
|
+
/** Deterministic fingerprint, not a cryptographic/security hash. Index reuse also compares text. */
|
|
8
|
+
export declare function contentHash(text: string): string;
|
|
9
|
+
export declare function chunkDocument(parsed: ParsedDocument, options?: ChunkOptions): IndexedChunk[];
|
package/dist/chunking.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { documentKey } from './contracts.js';
|
|
2
|
+
export const CHUNKER_VERSION = '1';
|
|
3
|
+
/** Deterministic fingerprint, not a cryptographic/security hash. Index reuse also compares text. */
|
|
4
|
+
export function contentHash(text) {
|
|
5
|
+
let hash = 0xcbf29ce484222325n;
|
|
6
|
+
for (const byte of new TextEncoder().encode(text))
|
|
7
|
+
hash = BigInt.asUintN(64, (hash ^ BigInt(byte)) * 0x100000001b3n);
|
|
8
|
+
return hash.toString(16).padStart(16, '0');
|
|
9
|
+
}
|
|
10
|
+
export function chunkDocument(parsed, options = {}) {
|
|
11
|
+
const target = options.targetChars ?? 1600;
|
|
12
|
+
const overlap = options.overlapLines ?? 2;
|
|
13
|
+
if (!Number.isSafeInteger(target) || target < 64 || !Number.isSafeInteger(overlap) || overlap < 0)
|
|
14
|
+
throw new Error('Invalid chunk options');
|
|
15
|
+
const lines = parsed.text.split('\n');
|
|
16
|
+
const chunks = [];
|
|
17
|
+
const occurrences = new Map();
|
|
18
|
+
for (const section of parsed.sections) {
|
|
19
|
+
let start = section.startLine - 1;
|
|
20
|
+
while (start < section.endLine) {
|
|
21
|
+
let end = start;
|
|
22
|
+
let chars = 0;
|
|
23
|
+
let boundary = -1;
|
|
24
|
+
while (end < section.endLine) {
|
|
25
|
+
const size = lines[end].length + (end > start ? 1 : 0);
|
|
26
|
+
if (end > start && chars + size > target)
|
|
27
|
+
break;
|
|
28
|
+
chars += size;
|
|
29
|
+
end++;
|
|
30
|
+
// Prefer a nearby paragraph/function boundary, without discarding any lines.
|
|
31
|
+
if (chars >= target / 2 && (!lines[end - 1].trim() || /^\s*[}\]];?\s*$/.test(lines[end - 1])))
|
|
32
|
+
boundary = end;
|
|
33
|
+
}
|
|
34
|
+
if (end < section.endLine && boundary > start)
|
|
35
|
+
end = boundary;
|
|
36
|
+
const text = lines.slice(start, end).join('\n');
|
|
37
|
+
if (text.trim()) {
|
|
38
|
+
const hash = contentHash(text);
|
|
39
|
+
const occurrence = occurrences.get(hash) ?? 0;
|
|
40
|
+
occurrences.set(hash, occurrence + 1);
|
|
41
|
+
const doc = parsed.document;
|
|
42
|
+
chunks.push({ id: JSON.stringify([documentKey(doc.sourceId, doc.id), hash, occurrence]), sourceId: doc.sourceId,
|
|
43
|
+
documentId: doc.id, uri: doc.uri, ...(doc.path !== undefined ? { path: doc.path } : {}),
|
|
44
|
+
...(section.heading ? { heading: section.heading } : {}), ...(parsed.language ? { language: parsed.language } : {}),
|
|
45
|
+
text, contentHash: hash, startLine: start + 1, endLine: end });
|
|
46
|
+
}
|
|
47
|
+
if (end >= section.endLine)
|
|
48
|
+
break;
|
|
49
|
+
start = Math.max(start + 1, end - Math.min(overlap, end - start - 1));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return chunks;
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=chunking.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chunking.js","sourceRoot":"","sources":["../src/chunking.ts"],"names":[],"mappings":"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","sourcesContent":["import type { IndexedChunk, ParsedDocument } from './contracts.js';\nimport { documentKey } from './contracts.js';\nexport const CHUNKER_VERSION = '1';\nexport interface ChunkOptions { targetChars?: number; overlapLines?: number; }\n/** Deterministic fingerprint, not a cryptographic/security hash. Index reuse also compares text. */\nexport function contentHash(text: string): string {\n let hash = 0xcbf29ce484222325n;\n for (const byte of new TextEncoder().encode(text)) hash = BigInt.asUintN(64, (hash ^ BigInt(byte)) * 0x100000001b3n);\n return hash.toString(16).padStart(16, '0');\n}\nexport function chunkDocument(parsed: ParsedDocument, options: ChunkOptions = {}): IndexedChunk[] {\n const target = options.targetChars ?? 1600; const overlap = options.overlapLines ?? 2;\n if (!Number.isSafeInteger(target) || target < 64 || !Number.isSafeInteger(overlap) || overlap < 0) throw new Error('Invalid chunk options');\n const lines = parsed.text.split('\\n'); const chunks: IndexedChunk[] = []; const occurrences = new Map<string, number>();\n for (const section of parsed.sections) {\n let start = section.startLine - 1;\n while (start < section.endLine) {\n let end = start; let chars = 0; let boundary = -1;\n while (end < section.endLine) {\n const size = lines[end]!.length + (end > start ? 1 : 0);\n if (end > start && chars + size > target) break;\n chars += size; end++;\n // Prefer a nearby paragraph/function boundary, without discarding any lines.\n if (chars >= target / 2 && (!lines[end - 1]!.trim() || /^\\s*[}\\]];?\\s*$/.test(lines[end - 1]!))) boundary = end;\n }\n if (end < section.endLine && boundary > start) end = boundary;\n const text = lines.slice(start, end).join('\\n');\n if (text.trim()) {\n const hash = contentHash(text); const occurrence = occurrences.get(hash) ?? 0; occurrences.set(hash, occurrence + 1);\n const doc = parsed.document;\n chunks.push({ id: JSON.stringify([documentKey(doc.sourceId, doc.id), hash, occurrence]), sourceId: doc.sourceId,\n documentId: doc.id, uri: doc.uri, ...(doc.path !== undefined ? { path: doc.path } : {}),\n ...(section.heading ? { heading: section.heading } : {}), ...(parsed.language ? { language: parsed.language } : {}),\n text, contentHash: hash, startLine: start + 1, endLine: end });\n }\n if (end >= section.endLine) break;\n start = Math.max(start + 1, end - Math.min(overlap, end - start - 1));\n }\n }\n return chunks;\n}\n"]}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/** Identities are local to a source. URI is the host's reopenable origin. */
|
|
2
|
+
export interface SourceDocument {
|
|
3
|
+
id: string;
|
|
4
|
+
sourceId: string;
|
|
5
|
+
uri: string;
|
|
6
|
+
path?: string;
|
|
7
|
+
name: string;
|
|
8
|
+
extension?: string;
|
|
9
|
+
mimeType?: string;
|
|
10
|
+
createdAt?: number;
|
|
11
|
+
modifiedAt?: number;
|
|
12
|
+
metadata?: Record<string, unknown>;
|
|
13
|
+
}
|
|
14
|
+
export interface SourceContent {
|
|
15
|
+
text: string;
|
|
16
|
+
}
|
|
17
|
+
export interface Disposable {
|
|
18
|
+
dispose(): void;
|
|
19
|
+
}
|
|
20
|
+
export type SourceChangeEvent = {
|
|
21
|
+
type: 'create' | 'modify' | 'delete';
|
|
22
|
+
documentId: string;
|
|
23
|
+
} | {
|
|
24
|
+
type: 'rename';
|
|
25
|
+
documentId: string;
|
|
26
|
+
previousDocumentId: string;
|
|
27
|
+
};
|
|
28
|
+
export interface OperationOptions {
|
|
29
|
+
signal?: AbortSignal;
|
|
30
|
+
}
|
|
31
|
+
export interface SearchSource {
|
|
32
|
+
readonly id: string;
|
|
33
|
+
readonly type: string;
|
|
34
|
+
list(options?: OperationOptions): Promise<SourceDocument[]>;
|
|
35
|
+
read(documentId: string, options?: OperationOptions): Promise<SourceContent>;
|
|
36
|
+
watch?(callback: (event: SourceChangeEvent) => void): Disposable;
|
|
37
|
+
}
|
|
38
|
+
export interface DocumentSection {
|
|
39
|
+
startLine: number;
|
|
40
|
+
endLine: number;
|
|
41
|
+
heading?: string;
|
|
42
|
+
kind: 'text' | 'code';
|
|
43
|
+
}
|
|
44
|
+
export interface ParsedDocument {
|
|
45
|
+
document: SourceDocument;
|
|
46
|
+
text: string;
|
|
47
|
+
parserId: string;
|
|
48
|
+
title: string;
|
|
49
|
+
language?: string;
|
|
50
|
+
sections: DocumentSection[];
|
|
51
|
+
headings: string[];
|
|
52
|
+
outgoingLinks: string[];
|
|
53
|
+
/** Preserved verbatim; this foundation does not pretend to implement YAML. */
|
|
54
|
+
frontmatter?: string;
|
|
55
|
+
}
|
|
56
|
+
export interface DocumentParser {
|
|
57
|
+
readonly id: string;
|
|
58
|
+
supports(document: SourceDocument): boolean;
|
|
59
|
+
parse(document: SourceDocument, content: SourceContent): ParsedDocument | Promise<ParsedDocument>;
|
|
60
|
+
}
|
|
61
|
+
export interface IndexedChunk {
|
|
62
|
+
id: string;
|
|
63
|
+
sourceId: string;
|
|
64
|
+
documentId: string;
|
|
65
|
+
uri: string;
|
|
66
|
+
path?: string;
|
|
67
|
+
heading?: string;
|
|
68
|
+
language?: string;
|
|
69
|
+
text: string;
|
|
70
|
+
contentHash: string;
|
|
71
|
+
startLine: number;
|
|
72
|
+
endLine: number;
|
|
73
|
+
}
|
|
74
|
+
export interface Embedder {
|
|
75
|
+
/** Include model revision, pooling and normalization in this identity. */
|
|
76
|
+
readonly id: string;
|
|
77
|
+
readonly dimensions: number;
|
|
78
|
+
embedDocuments(texts: string[], options?: OperationOptions): Promise<Float32Array[]>;
|
|
79
|
+
embedQuery(text: string, options?: OperationOptions): Promise<Float32Array>;
|
|
80
|
+
}
|
|
81
|
+
export interface CatalogEntry {
|
|
82
|
+
documentId: string;
|
|
83
|
+
sourceId: string;
|
|
84
|
+
uri: string;
|
|
85
|
+
path?: string;
|
|
86
|
+
title: string;
|
|
87
|
+
fileType?: string;
|
|
88
|
+
createdAt?: number;
|
|
89
|
+
modifiedAt?: number;
|
|
90
|
+
tags: string[];
|
|
91
|
+
headings: string[];
|
|
92
|
+
outgoingLinks: string[];
|
|
93
|
+
}
|
|
94
|
+
export interface SearchFilters {
|
|
95
|
+
pathPrefix?: string;
|
|
96
|
+
extensions?: string[];
|
|
97
|
+
tags?: string[];
|
|
98
|
+
modifiedAfter?: number;
|
|
99
|
+
createdAfter?: number;
|
|
100
|
+
linkedTo?: string;
|
|
101
|
+
}
|
|
102
|
+
export type SearchMode = 'lexical' | 'semantic' | 'hybrid';
|
|
103
|
+
/** Retrieve eligible candidates, not an assessment that the requested fact/task is supported. */
|
|
104
|
+
export interface SearchRequest extends OperationOptions {
|
|
105
|
+
/** Defaults to hybrid with an embedder, lexical without one. */
|
|
106
|
+
mode?: SearchMode;
|
|
107
|
+
query: string;
|
|
108
|
+
sources?: string[];
|
|
109
|
+
filters?: SearchFilters;
|
|
110
|
+
limit?: number;
|
|
111
|
+
}
|
|
112
|
+
/** Ranked candidate evidence from one captured committed index view.
|
|
113
|
+
* Ranks and channel matches are retrieval positions/signals, not confidence or sufficiency.
|
|
114
|
+
* A nonempty list does not establish answerability; an empty list does not prove corpus absence.
|
|
115
|
+
* Source content may change after indexing; hosts verify current evidence when needed.
|
|
116
|
+
*/
|
|
117
|
+
export interface SearchResult {
|
|
118
|
+
sourceId: string;
|
|
119
|
+
documentId: string;
|
|
120
|
+
chunkId: string;
|
|
121
|
+
uri: string;
|
|
122
|
+
path?: string;
|
|
123
|
+
title: string;
|
|
124
|
+
heading?: string;
|
|
125
|
+
language?: string;
|
|
126
|
+
startLine: number;
|
|
127
|
+
endLine: number;
|
|
128
|
+
snippet: string;
|
|
129
|
+
lexicalRank?: number;
|
|
130
|
+
semanticRank?: number;
|
|
131
|
+
combinedRank: number;
|
|
132
|
+
matchReasons: string[];
|
|
133
|
+
}
|
|
134
|
+
/** Serializable index state. Engines validate compatibility and reconcile live sources before reuse. */
|
|
135
|
+
export interface IndexSnapshot {
|
|
136
|
+
schemaVersion: number;
|
|
137
|
+
parserVersion: string;
|
|
138
|
+
chunkerVersion: string;
|
|
139
|
+
/** Version of content/structure-based text supplied to the embedder. */
|
|
140
|
+
embeddingInputVersion?: string;
|
|
141
|
+
/** Effective boundary configuration; older snapshots without it are rebuilt. */
|
|
142
|
+
chunking?: {
|
|
143
|
+
targetChars: number;
|
|
144
|
+
overlapLines: number;
|
|
145
|
+
};
|
|
146
|
+
embedding?: {
|
|
147
|
+
id: string;
|
|
148
|
+
dimensions: number;
|
|
149
|
+
};
|
|
150
|
+
documents: CatalogEntry[];
|
|
151
|
+
chunks: Array<IndexedChunk & {
|
|
152
|
+
vector?: number[];
|
|
153
|
+
}>;
|
|
154
|
+
}
|
|
155
|
+
/** Host-owned atomic storage. One writer per store. Errors must surface.
|
|
156
|
+
* save must reject before publication on failure/abort and resolve after atomic publication.
|
|
157
|
+
* Cancellation arriving after publication does not roll back a successful save.
|
|
158
|
+
*/
|
|
159
|
+
export interface IndexStorage {
|
|
160
|
+
load(options?: OperationOptions): Promise<IndexSnapshot | null>;
|
|
161
|
+
save(snapshot: IndexSnapshot, options?: OperationOptions): Promise<void>;
|
|
162
|
+
}
|
|
163
|
+
export declare function documentKey(sourceId: string, documentId: string): string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contracts.js","sourceRoot":"","sources":["../src/contracts.ts"],"names":[],"mappings":"AA+IA,MAAM,UAAU,WAAW,CAAC,QAAgB,EAAE,UAAkB;IAC9D,OAAO,IAAI,CAAC,SAAS,CAAC,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC,CAAC;AAChD,CAAC","sourcesContent":["/** Identities are local to a source. URI is the host's reopenable origin. */\nexport interface SourceDocument {\n id: string;\n sourceId: string;\n uri: string;\n path?: string;\n name: string;\n extension?: string;\n mimeType?: string;\n createdAt?: number;\n modifiedAt?: number;\n metadata?: Record<string, unknown>;\n}\nexport interface SourceContent { text: string; }\nexport interface Disposable { dispose(): void; }\nexport type SourceChangeEvent =\n | { type: 'create' | 'modify' | 'delete'; documentId: string }\n | { type: 'rename'; documentId: string; previousDocumentId: string };\nexport interface OperationOptions { signal?: AbortSignal; }\nexport interface SearchSource {\n readonly id: string;\n readonly type: string;\n list(options?: OperationOptions): Promise<SourceDocument[]>;\n read(documentId: string, options?: OperationOptions): Promise<SourceContent>;\n watch?(callback: (event: SourceChangeEvent) => void): Disposable;\n}\nexport interface DocumentSection {\n startLine: number;\n endLine: number;\n heading?: string;\n kind: 'text' | 'code';\n}\nexport interface ParsedDocument {\n document: SourceDocument;\n text: string;\n parserId: string;\n title: string;\n language?: string;\n sections: DocumentSection[];\n headings: string[];\n outgoingLinks: string[];\n /** Preserved verbatim; this foundation does not pretend to implement YAML. */\n frontmatter?: string;\n}\nexport interface DocumentParser {\n readonly id: string;\n supports(document: SourceDocument): boolean;\n parse(document: SourceDocument, content: SourceContent): ParsedDocument | Promise<ParsedDocument>;\n}\nexport interface IndexedChunk {\n id: string;\n sourceId: string;\n documentId: string;\n uri: string;\n path?: string;\n heading?: string;\n language?: string;\n text: string;\n contentHash: string;\n startLine: number;\n endLine: number;\n}\nexport interface Embedder {\n /** Include model revision, pooling and normalization in this identity. */\n readonly id: string;\n readonly dimensions: number;\n embedDocuments(texts: string[], options?: OperationOptions): Promise<Float32Array[]>;\n embedQuery(text: string, options?: OperationOptions): Promise<Float32Array>;\n}\nexport interface CatalogEntry {\n documentId: string;\n sourceId: string;\n uri: string;\n path?: string;\n title: string;\n fileType?: string;\n createdAt?: number;\n modifiedAt?: number;\n tags: string[];\n headings: string[];\n outgoingLinks: string[];\n}\nexport interface SearchFilters {\n pathPrefix?: string;\n extensions?: string[];\n tags?: string[];\n modifiedAfter?: number;\n createdAfter?: number;\n linkedTo?: string;\n}\nexport type SearchMode = 'lexical' | 'semantic' | 'hybrid';\n/** Retrieve eligible candidates, not an assessment that the requested fact/task is supported. */\nexport interface SearchRequest extends OperationOptions {\n /** Defaults to hybrid with an embedder, lexical without one. */\n mode?: SearchMode;\n query: string;\n sources?: string[];\n filters?: SearchFilters;\n limit?: number;\n}\n/** Ranked candidate evidence from one captured committed index view.\n * Ranks and channel matches are retrieval positions/signals, not confidence or sufficiency.\n * A nonempty list does not establish answerability; an empty list does not prove corpus absence.\n * Source content may change after indexing; hosts verify current evidence when needed.\n */\nexport interface SearchResult {\n sourceId: string;\n documentId: string;\n chunkId: string;\n uri: string;\n path?: string;\n title: string;\n heading?: string;\n language?: string;\n startLine: number;\n endLine: number;\n snippet: string;\n lexicalRank?: number;\n semanticRank?: number;\n combinedRank: number;\n matchReasons: string[];\n}\n/** Serializable index state. Engines validate compatibility and reconcile live sources before reuse. */\nexport interface IndexSnapshot {\n schemaVersion: number;\n parserVersion: string;\n chunkerVersion: string;\n /** Version of content/structure-based text supplied to the embedder. */\n embeddingInputVersion?: string;\n /** Effective boundary configuration; older snapshots without it are rebuilt. */\n chunking?: { targetChars: number; overlapLines: number };\n embedding?: { id: string; dimensions: number };\n documents: CatalogEntry[];\n chunks: Array<IndexedChunk & { vector?: number[] }>;\n}\n/** Host-owned atomic storage. One writer per store. Errors must surface.\n * save must reject before publication on failure/abort and resolve after atomic publication.\n * Cancellation arriving after publication does not roll back a successful save.\n */\nexport interface IndexStorage {\n load(options?: OperationOptions): Promise<IndexSnapshot | null>;\n save(snapshot: IndexSnapshot, options?: OperationOptions): Promise<void>;\n}\nexport function documentKey(sourceId: string, documentId: string): string {\n return JSON.stringify([sourceId, documentId]);\n}\n"]}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { IndexedChunk } from './contracts.js';
|
|
2
|
+
/** Bump when changing the text presented to embedders. Paths never participate. */
|
|
3
|
+
export declare const EMBEDDING_INPUT_VERSION = "1";
|
|
4
|
+
export declare function embeddingTextForChunk(chunk: Pick<IndexedChunk, 'text' | 'heading'>): string;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** Bump when changing the text presented to embedders. Paths never participate. */
|
|
2
|
+
export const EMBEDDING_INPUT_VERSION = '1';
|
|
3
|
+
export function embeddingTextForChunk(chunk) {
|
|
4
|
+
const heading = chunk.heading?.trim();
|
|
5
|
+
if (!heading)
|
|
6
|
+
return chunk.text;
|
|
7
|
+
const lines = chunk.text.split('\n', 2);
|
|
8
|
+
const atx = /^ {0,3}#{1,6}\s+(.+?)\s*#*\s*$/.exec(lines[0] ?? '')?.[1];
|
|
9
|
+
const setext = lines[0]?.trim() === heading && /^ {0,3}(?:=+|-+)\s*$/.test(lines[1] ?? '');
|
|
10
|
+
// Initial Markdown section chunks already contain their heading; continuation chunks do not.
|
|
11
|
+
return atx === heading || setext ? chunk.text : `${heading}\n\n${chunk.text}`;
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=embedding-input.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"embedding-input.js","sourceRoot":"","sources":["../src/embedding-input.ts"],"names":[],"mappings":"AACA,mFAAmF;AACnF,MAAM,CAAC,MAAM,uBAAuB,GAAG,GAAG,CAAC;AAC3C,MAAM,UAAU,qBAAqB,CAAC,KAA6C;IACjF,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC;IACtC,IAAI,CAAC,OAAO;QAAE,OAAO,KAAK,CAAC,IAAI,CAAC;IAChC,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IACxC,MAAM,GAAG,GAAG,gCAAgC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IACvE,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,OAAO,IAAI,sBAAsB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;IAC3F,6FAA6F;IAC7F,OAAO,GAAG,KAAK,OAAO,IAAI,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,OAAO,OAAO,KAAK,CAAC,IAAI,EAAE,CAAC;AAChF,CAAC","sourcesContent":["import type { IndexedChunk } from './contracts.js';\n/** Bump when changing the text presented to embedders. Paths never participate. */\nexport const EMBEDDING_INPUT_VERSION = '1';\nexport function embeddingTextForChunk(chunk: Pick<IndexedChunk, 'text' | 'heading'>): string {\n const heading = chunk.heading?.trim();\n if (!heading) return chunk.text;\n const lines = chunk.text.split('\\n', 2);\n const atx = /^ {0,3}#{1,6}\\s+(.+?)\\s*#*\\s*$/.exec(lines[0] ?? '')?.[1];\n const setext = lines[0]?.trim() === heading && /^ {0,3}(?:=+|-+)\\s*$/.test(lines[1] ?? '');\n // Initial Markdown section chunks already contain their heading; continuation chunks do not.\n return atx === heading || setext ? chunk.text : `${heading}\\n\\n${chunk.text}`;\n}\n"]}
|
package/dist/engine.d.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { CatalogEntry, Embedder, IndexStorage, OperationOptions, SearchMode, SearchRequest, SearchResult, SearchSource } from './contracts.js';
|
|
2
|
+
import { ParserRegistry } from './parsers/index.js';
|
|
3
|
+
import { type ChunkOptions } from './chunking.js';
|
|
4
|
+
export interface RefreshReport {
|
|
5
|
+
sourceId: string;
|
|
6
|
+
documents: number;
|
|
7
|
+
chunks: number;
|
|
8
|
+
unchangedChunks: number;
|
|
9
|
+
embeddedTexts: number;
|
|
10
|
+
reusedVectors: number;
|
|
11
|
+
}
|
|
12
|
+
export interface EngineOptions {
|
|
13
|
+
parsers?: ParserRegistry;
|
|
14
|
+
chunking?: ChunkOptions;
|
|
15
|
+
embedder?: Embedder;
|
|
16
|
+
storage?: IndexStorage;
|
|
17
|
+
}
|
|
18
|
+
/** Host-independent indexing and lexical/semantic retrieval lifecycle. */
|
|
19
|
+
export declare class SemanticSearchEngine {
|
|
20
|
+
private readonly sources;
|
|
21
|
+
private readonly records;
|
|
22
|
+
private readonly parsers;
|
|
23
|
+
private readonly chunking;
|
|
24
|
+
private readonly embedder;
|
|
25
|
+
private readonly storage;
|
|
26
|
+
private readonly config;
|
|
27
|
+
private vectors;
|
|
28
|
+
private loaded;
|
|
29
|
+
private seed;
|
|
30
|
+
private busy;
|
|
31
|
+
private generation;
|
|
32
|
+
constructor(options?: EngineOptions);
|
|
33
|
+
/** Effective strategy when SearchRequest.mode is omitted. */
|
|
34
|
+
get defaultMode(): SearchMode;
|
|
35
|
+
get revision(): number;
|
|
36
|
+
addSource(source: SearchSource): void;
|
|
37
|
+
removeSource(sourceId: string): void;
|
|
38
|
+
initialize(options?: OperationOptions): Promise<RefreshReport[]>;
|
|
39
|
+
/** Atomic per-source refresh. A failed or cancelled scan preserves that source's previous index. */
|
|
40
|
+
refreshSource(sourceId: string, options?: OperationOptions): Promise<RefreshReport>;
|
|
41
|
+
private discardSeed;
|
|
42
|
+
getCatalog(): CatalogEntry[];
|
|
43
|
+
/** Return ranked candidates only. Empty results may reflect query/mode/filter restrictions,
|
|
44
|
+
* not absence of sufficient evidence elsewhere. No answerability evaluation is performed. */
|
|
45
|
+
search(request: SearchRequest): Promise<SearchResult[]>;
|
|
46
|
+
private checkEmbeddingConfiguration;
|
|
47
|
+
}
|
|
48
|
+
/** Host creates one candidate-result cache per run; bounded and invalidated by index revision.
|
|
49
|
+
* This caches retrieval only, not answerability, host decisions or current source verification.
|
|
50
|
+
*
|
|
51
|
+
* Cache key semantics:
|
|
52
|
+
* - Lexical queries normalize via trim() + toLowerCase().
|
|
53
|
+
* - Semantic/hybrid queries preserve the raw query string because arbitrary embedders may be case- or whitespace-sensitive.
|
|
54
|
+
* - Filters are canonicalized: source/tag/extension sets are sorted; extensions are lowercased with leading dot normalized. */
|
|
55
|
+
export declare function createSearchSession(engine: SemanticSearchEngine, maxEntries?: number): {
|
|
56
|
+
readonly stats: {
|
|
57
|
+
hits: number;
|
|
58
|
+
misses: number;
|
|
59
|
+
};
|
|
60
|
+
/** Cache candidate retrieval only; no host assessment is cached here. */
|
|
61
|
+
search(request: SearchRequest): Promise<SearchResult[]>;
|
|
62
|
+
clear(): void;
|
|
63
|
+
};
|