@avantf/dsh-mem 0.7.0 → 0.8.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.
- package/README.md +4 -0
- package/lib/client.js +548 -1195
- package/lib/dsh-build.json +1 -0
- package/lib/engine/graph-recall/in-memory.d.ts +93 -0
- package/lib/engine/graph-recall/index.d.ts +22 -0
- package/lib/engine/graph-recall/traverse.d.ts +119 -0
- package/lib/engine/graph-recall/types.d.ts +188 -0
- package/lib/engine/mem/config/config_files.d.ts +1 -1
- package/lib/engine/mem/config/loader.d.ts +2 -3
- package/lib/engine/mem/config/paths.d.ts +2 -13
- package/lib/engine/mem/db/dao/contradictions.d.ts +4 -22
- package/lib/engine/mem/db/dao/entities.d.ts +80 -7
- package/lib/engine/mem/db/dao/fact_sources.d.ts +3 -13
- package/lib/engine/mem/db/dao/facts.d.ts +311 -56
- package/lib/engine/mem/db/dao/triples.d.ts +1 -26
- package/lib/engine/mem/db/graph.d.ts +249 -0
- package/lib/engine/mem/db/journal_notes.d.ts +35 -0
- package/lib/engine/mem/db/migrations.d.ts +9 -0
- package/lib/engine/mem/db/port.d.ts +22 -3
- package/lib/engine/mem/db/schema.d.ts +119 -0
- package/lib/engine/mem/db/tokenizer.d.ts +9 -0
- package/lib/engine/mem/dbdrivers/fake/driver.d.ts +75 -0
- package/lib/engine/mem/dbdrivers/fake/index.d.ts +7 -0
- package/lib/engine/mem/dbdrivers/fake/surface.d.ts +34 -0
- package/lib/engine/mem/dbdrivers/fake/transport.d.ts +61 -0
- package/lib/engine/mem/dbdrivers/sqlite/connection.d.ts +14 -0
- package/lib/engine/mem/dbdrivers/sqlite/contradictions.d.ts +35 -0
- package/lib/engine/mem/dbdrivers/sqlite/dao/index.d.ts +16 -0
- package/lib/engine/mem/dbdrivers/sqlite/driver.d.ts +54 -0
- package/lib/engine/mem/dbdrivers/sqlite/facts.d.ts +117 -0
- package/lib/engine/mem/dbdrivers/sqlite/index.d.ts +6 -0
- package/lib/engine/mem/dbdrivers/sqlite/migrations.d.ts +15 -0
- package/lib/engine/mem/dbdrivers/sqlite/relations.d.ts +62 -0
- package/lib/engine/mem/dbdrivers/sqlite/search.d.ts +56 -0
- package/lib/engine/mem/dbdrivers/sqlite/sources.d.ts +37 -0
- package/lib/engine/mem/defaults.d.ts +3 -4
- package/lib/engine/mem/dispatch.d.ts +7 -32
- package/lib/engine/mem/entities/extract.d.ts +44 -47
- package/lib/engine/mem/eval/derived_runner.d.ts +6 -1
- package/lib/engine/mem/eval/relation_gold.d.ts +251 -0
- package/lib/engine/mem/eval/write_metrics.d.ts +1 -1
- package/lib/engine/mem/facts_io.d.ts +50 -10
- package/lib/engine/mem/index.d.ts +6 -2
- package/lib/engine/mem/interfaces/capabilities.d.ts +30 -0
- package/lib/engine/mem/interfaces/common.d.ts +11 -0
- package/lib/engine/mem/interfaces/errors.d.ts +65 -0
- package/lib/engine/mem/interfaces/index.d.ts +18 -0
- package/lib/engine/mem/interfaces/migrations.d.ts +21 -0
- package/lib/engine/mem/interfaces/repositories.d.ts +471 -0
- package/lib/engine/mem/interfaces/search_index.d.ts +113 -0
- package/lib/engine/mem/interfaces/storage_backend.d.ts +84 -0
- package/lib/engine/mem/interfaces/storage_driver.d.ts +43 -0
- package/lib/engine/mem/interfaces/version.d.ts +34 -0
- package/lib/engine/mem/lifecycle/contradiction.d.ts +2 -2
- package/lib/engine/mem/modelBootstrap.d.ts +1 -1
- package/lib/engine/mem/relations/confidence.d.ts +39 -0
- package/lib/engine/mem/relations/extract.d.ts +128 -0
- package/lib/engine/mem/relations/index.d.ts +32 -0
- package/lib/engine/mem/relations/name-quality.d.ts +78 -0
- package/lib/engine/mem/relations/normalize.d.ts +275 -0
- package/lib/engine/mem/relations/patterns.d.ts +67 -0
- package/lib/engine/mem/relations/statement.d.ts +88 -0
- package/lib/engine/mem/relations/types.d.ts +79 -0
- package/lib/engine/mem/runtime.d.ts +61 -88
- package/lib/engine/mem/storage/create.d.ts +42 -0
- package/lib/engine/mem/storage/errors.d.ts +65 -0
- package/lib/engine/mem/storage/index.d.ts +15 -0
- package/lib/engine/mem/storage/ports.d.ts +472 -0
- package/lib/engine/mem/storage/registry.d.ts +37 -0
- package/lib/engine/mem/storage/sqlite.d.ts +28 -0
- package/lib/engine/mem/store/common.d.ts +1 -1
- package/lib/engine/mem/store/entity_leg.d.ts +59 -2
- package/lib/engine/mem/store/entity_sweep.d.ts +4 -3
- package/lib/engine/mem/store/explicit_relations.d.ts +52 -0
- package/lib/engine/mem/store/facts_restore.d.ts +10 -2
- package/lib/engine/mem/store/floors.d.ts +4 -1
- package/lib/engine/mem/store/graph_reader.d.ts +146 -0
- package/lib/engine/mem/store/hybrid.d.ts +50 -13
- package/lib/engine/mem/store/kinds.d.ts +47 -0
- package/lib/engine/mem/store/legs.d.ts +24 -29
- package/lib/engine/mem/store/lexical.d.ts +36 -45
- package/lib/engine/mem/store/memory.d.ts +471 -83
- package/lib/engine/mem/store/recall_policy.d.ts +49 -0
- package/lib/engine/mem/store/relation_backfill.d.ts +210 -0
- package/lib/engine/mem/store/relation_planner.d.ts +191 -0
- package/lib/engine/mem/store/secret_guard.d.ts +0 -1
- package/lib/engine/mem/store/self_query.d.ts +4 -2
- package/lib/engine/mem/store/time_filter.d.ts +121 -0
- package/lib/engine/mem/store/titles.d.ts +52 -0
- package/lib/engine/mem/store/vector_repair.d.ts +44 -54
- package/lib/engine/mem-contract/config.d.ts +44 -38
- package/lib/engine/mem-contract/text.d.ts +1 -1
- package/lib/engine/mem-contract/tools.d.ts +145 -132
- package/lib/engine/mem-contract/types.d.ts +364 -288
- package/lib/index.js +12048 -11337
- package/lib/interface-version.json +1 -1
- package/lib/types/client/index.d.ts +2 -2
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/paging.d.ts +3 -12
- package/lib/types/client/paging.d.ts.map +1 -1
- package/lib/types/client/summarize.d.ts.map +1 -1
- package/lib/types/envinit-bootstrap.d.ts +1 -1
- package/lib/types/envinit.d.ts +2 -6
- package/lib/types/envinit.d.ts.map +1 -1
- package/lib/types/hints.d.ts +28 -24
- package/lib/types/hints.d.ts.map +1 -1
- package/lib/types/index.d.ts +21 -40
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/inject.d.ts +3 -2
- package/lib/types/inject.d.ts.map +1 -1
- package/lib/types/interface_gate.d.ts +1 -1
- package/lib/types/prompt.d.ts +17 -41
- package/lib/types/prompt.d.ts.map +1 -1
- package/lib/types/provision.d.ts +3 -0
- package/lib/types/provision.d.ts.map +1 -1
- package/lib/types/remote.d.ts +14 -7
- package/lib/types/remote.d.ts.map +1 -1
- package/lib/types/tool_schema.d.ts +4 -0
- package/lib/types/tool_schema.d.ts.map +1 -1
- package/lib/types/vectorMigration.d.ts +5 -7
- package/lib/types/vectorMigration.d.ts.map +1 -1
- package/package.json +15 -14
- package/lib/engine/mem/config/domains.d.ts +0 -20
- package/lib/engine/mem/db/dao/chunks.d.ts +0 -231
- package/lib/engine/mem/db/dao/documents.d.ts +0 -51
- package/lib/engine/mem/db/hash.d.ts +0 -2
- package/lib/engine/mem/db/knowledge.d.ts +0 -17
- package/lib/engine/mem/eval/index.d.ts +0 -4
- package/lib/engine/mem/git.d.ts +0 -73
- package/lib/engine/mem/router.d.ts +0 -51
- package/lib/engine/mem/store/doc_files.d.ts +0 -125
- package/lib/engine/mem/store/document_text.d.ts +0 -47
- package/lib/engine/mem/store/ingest_guard.d.ts +0 -25
- package/lib/engine/mem/store/knowledge.d.ts +0 -598
- package/lib/engine/mem/store/source_picker.d.ts +0 -43
- package/lib/engine/mem-retrieval/adapters/local_reranker.d.ts +0 -52
- package/lib/engine/mem-retrieval/adapters/none_reranker.d.ts +0 -10
- package/lib/engine/mem-retrieval/rerank.d.ts +0 -13
- package/lib/types/client/domains.d.ts +0 -53
- package/lib/types/client/domains.d.ts.map +0 -1
- package/lib/types/client/ingest.d.ts +0 -24
- package/lib/types/client/ingest.d.ts.map +0 -1
- package/lib/types/open.d.ts +0 -77
- package/lib/types/open.d.ts.map +0 -1
- package/lib/types/reconcile.d.ts +0 -64
- package/lib/types/reconcile.d.ts.map +0 -1
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
/** What the first bytes say a document is. */
|
|
2
|
-
type DocumentFormat = 'pdf' | 'binary' | 'text';
|
|
3
|
-
/** How text bytes were decoded. Reported so a guess is visible instead of silent. */
|
|
4
|
-
type DocumentEncoding = 'utf-8' | 'utf-8-bom' | 'utf-16le' | 'utf-16be' | 'gb18030';
|
|
5
|
-
/** One document's indexable text, and where it came from. */
|
|
6
|
-
export interface DocumentText {
|
|
7
|
-
text: string;
|
|
8
|
-
/**
|
|
9
|
-
* `pdf` = extracted text layer; `converter` = a document format turned into Markdown (see
|
|
10
|
-
* {@link DocumentText.converter}); `text` = decoded bytes (see {@link DocumentText.encoding}).
|
|
11
|
-
*/
|
|
12
|
-
via: 'text' | 'pdf' | 'converter';
|
|
13
|
-
encoding?: DocumentEncoding;
|
|
14
|
-
/** Which `@avantf/mem-convert` converter produced the body (only when `via === 'converter'`). */
|
|
15
|
-
converter?: string;
|
|
16
|
-
/** What that conversion could not carry over (dropped media, a truncated sheet, …). */
|
|
17
|
-
warnings?: string[];
|
|
18
|
-
}
|
|
19
|
-
/** Classify one document by its leading bytes. */
|
|
20
|
-
export declare function sniffFormat(bytes: Uint8Array): {
|
|
21
|
-
format: DocumentFormat;
|
|
22
|
-
kind?: string;
|
|
23
|
-
};
|
|
24
|
-
/**
|
|
25
|
-
* Restore the ideographs pdfjs sometimes reports as compatibility codepoints.
|
|
26
|
-
*
|
|
27
|
-
* Some CJK PDFs extract `⽹站` (KANGXI RADICAL NET + 站) instead of `网站` — visually identical,
|
|
28
|
-
* different codepoints, so a search for the real word misses. Only those three compatibility ranges
|
|
29
|
-
* are normalized: NFKC over the whole text would also rewrite full-width punctuation (,。!?),
|
|
30
|
-
* which is real content and must survive verbatim.
|
|
31
|
-
*/
|
|
32
|
-
export declare function normalizeCjkCompatibility(text: string): string;
|
|
33
|
-
/**
|
|
34
|
-
* The text of one document, whatever its bytes are.
|
|
35
|
-
*
|
|
36
|
-
* CONVERSION RUNS FIRST, before the magic-number sniff and therefore before the binary rejection.
|
|
37
|
-
* docx/xlsx/html/csv are converted to Markdown here, and the order is not cosmetic: docx and xlsx
|
|
38
|
-
* are ZIPs, so a sniff-first pipeline would file them under "known binary" and refuse them. A
|
|
39
|
-
* document no converter claims falls through to the text/PDF/binary paths unchanged.
|
|
40
|
-
*
|
|
41
|
-
* @param bytes - the whole document (already bounded by the byte cap).
|
|
42
|
-
* @param label - the path or URL, used verbatim in error messages and as the sniff's extension hint.
|
|
43
|
-
* @throws when the document is a binary we cannot read, is not decodable, or is a PDF without a
|
|
44
|
-
* text layer.
|
|
45
|
-
*/
|
|
46
|
-
export declare function documentText(bytes: Uint8Array, label: string): Promise<DocumentText>;
|
|
47
|
-
export {};
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
/** The boundary knobs, as resolved from `knowledge.ingest`. */
|
|
2
|
-
export interface IngestLimits {
|
|
3
|
-
/** Roots a local `source_uri` may be read from; empty ⇒ the process workspace. */
|
|
4
|
-
local_roots: readonly string[];
|
|
5
|
-
allow_outside_workspace: boolean;
|
|
6
|
-
allow_private_network: boolean;
|
|
7
|
-
}
|
|
8
|
-
/**
|
|
9
|
-
* Whether a URL hostname addresses loopback / private / link-local / other
|
|
10
|
-
* non-public space. Names are matched by suffix too (`*.local` mDNS, `*.internal`,
|
|
11
|
-
* `*.home.arpa`), since those resolve inside the LAN by construction.
|
|
12
|
-
*/
|
|
13
|
-
export declare function isPrivateHostname(hostname: string): boolean;
|
|
14
|
-
/** Validate one `http(s)` source URL, refusing non-public targets unless opted in. */
|
|
15
|
-
export declare function assertFetchableUrl(uri: string, limits: Pick<IngestLimits, 'allow_private_network'>): URL;
|
|
16
|
-
/** The roots a local `source_uri` may be read from (realpath'd), defaulting to the workspace. */
|
|
17
|
-
export declare function allowedLocalRoots(limits: Pick<IngestLimits, 'local_roots'>): string[];
|
|
18
|
-
/**
|
|
19
|
-
* Resolve a local `source_uri` to the real absolute path it names, refusing one
|
|
20
|
-
* that lands outside the allowed roots.
|
|
21
|
-
*
|
|
22
|
-
* @returns the `realpath` — which is what the caller should BOTH read and store, so
|
|
23
|
-
* the recorded `source_uri` is exactly the file that was read.
|
|
24
|
-
*/
|
|
25
|
-
export declare function resolveLocalSource(raw: string, limits: IngestLimits): string;
|
|
@@ -1,598 +0,0 @@
|
|
|
1
|
-
import { type AvantfLogger, type Config, type DocumentDetail, type DocumentSummary, type DomainCatalog, type FloorProfile, type ImportResult, type IngestResult, type KbConflictReport, type KbIngestConflict, type KbSyncReport, type KnowledgeConfig, type RecallHit, type ReindexReport, type VectorMigrationOutcome, type VectorMigrationProgress, type VectorSpaceHealth, type VectorsFixStoreReport } from '../../mem-contract/index.js';
|
|
2
|
-
import type { SemanticBackend, VectorStore } from '../../mem-retrieval/index.js';
|
|
3
|
-
import { type LexicalProbe } from './lexical.js';
|
|
4
|
-
import { type FloorLeg } from './floors.js';
|
|
5
|
-
import { type HybridResult } from './hybrid.js';
|
|
6
|
-
import { type VectorRepairTarget } from './vector_repair.js';
|
|
7
|
-
/** Options for {@link KnowledgeStore.search}. */
|
|
8
|
-
export interface KnowledgeSearchOptions {
|
|
9
|
-
domain?: string;
|
|
10
|
-
source?: string;
|
|
11
|
-
limit?: number;
|
|
12
|
-
/** Overrides the config-derived fusion pool size; never smaller than `limit`. */
|
|
13
|
-
overFetch?: number;
|
|
14
|
-
/**
|
|
15
|
-
* Pre-encoded query vector, when the caller already has one — the cross-store router encodes a
|
|
16
|
-
* query ONCE and hands the same vector to both stores. Not verified, so it must come from the
|
|
17
|
-
* same backend.
|
|
18
|
-
*/
|
|
19
|
-
queryVector?: Float32Array;
|
|
20
|
-
/** Per-call output token budget; `0` = unlimited, omitted = `retriever.max_output_tokens`. */
|
|
21
|
-
maxTokens?: number;
|
|
22
|
-
/**
|
|
23
|
-
* Relevance-floor profile: omitted = the default policy (configured floors, plus one relaxed pass
|
|
24
|
-
* if they empty the result — see `MemoryStore`'s `SearchInput.floors`), `'strict'` = no fallback,
|
|
25
|
-
* `'loose'` = the relaxed floors outright. The cross-store router passes a profile explicitly for
|
|
26
|
-
* each pass, because only the MERGED result can decide whether relaxing is warranted.
|
|
27
|
-
*/
|
|
28
|
-
floors?: FloorProfile;
|
|
29
|
-
/**
|
|
30
|
-
* Which legs a `'loose'` pass may lower (see `store/floors.ts`; same contract as
|
|
31
|
-
* `SearchInput.relaxLegs`). Internal: the cross-store router pins the MERGED strict pass's
|
|
32
|
-
* dropping legs on its second call.
|
|
33
|
-
*/
|
|
34
|
-
relaxLegs?: readonly FloorLeg[];
|
|
35
|
-
/** See `SearchInput.rewriteQuery` (方案 A test seam); default = the intent table. */
|
|
36
|
-
rewriteQuery?: (query: string) => string | undefined;
|
|
37
|
-
/** See `SearchInput.recordStats`: the cross-store router records the merged query once. */
|
|
38
|
-
recordStats?: boolean;
|
|
39
|
-
/**
|
|
40
|
-
* Receives the full `HybridResult` (floors, per-leg floor drops, used tokens) after the search.
|
|
41
|
-
*
|
|
42
|
-
* `search` answers with the hits only, because that is what its callers want; the cross-store
|
|
43
|
-
* router still has to fold THIS store's floor drops into the one merged `kb_query` result, and a
|
|
44
|
-
* store-level "last result" field would race two concurrent queries.
|
|
45
|
-
*/
|
|
46
|
-
onResult?: (result: HybridResult<RecallHit>) => void;
|
|
47
|
-
/** P-01: attach per-leg evidence (`scores` / `final`) to every hit. Default false. */
|
|
48
|
-
includeScores?: boolean;
|
|
49
|
-
}
|
|
50
|
-
/**
|
|
51
|
-
* One identity a write will land on: the title it will use, the resolved local path it would read
|
|
52
|
-
* (absent for text/URL), and — when the identity is taken — the document already sitting on it.
|
|
53
|
-
*/
|
|
54
|
-
export interface IngestTarget {
|
|
55
|
-
title: string;
|
|
56
|
-
path?: string;
|
|
57
|
-
existing?: KbIngestConflict;
|
|
58
|
-
}
|
|
59
|
-
/**
|
|
60
|
-
* What the plan stage resolved, with NO file content read and NO network touched (see
|
|
61
|
-
* {@link KnowledgeStore.ingestRequest}). A directory is expanded to its ingestable FILES here, so
|
|
62
|
-
* the `existing` test uses the basename identity `importPaths` actually writes.
|
|
63
|
-
*/
|
|
64
|
-
export interface IngestPlan {
|
|
65
|
-
kind: 'text' | 'url' | 'file' | 'paths';
|
|
66
|
-
/** The pasted body (kind `text`). */
|
|
67
|
-
text?: string;
|
|
68
|
-
/** The URL, or the `realpath` of the local file (kinds `url`/`file`). */
|
|
69
|
-
uri?: string;
|
|
70
|
-
/** The caller's paths (kind `paths`), for the replace dispatch through `importPaths`. */
|
|
71
|
-
paths?: string[];
|
|
72
|
-
/** Every ingestable file the request expands to. */
|
|
73
|
-
files: string[];
|
|
74
|
-
/** Files a directory walk refused by format (reported by a batch import). */
|
|
75
|
-
skipped: string[];
|
|
76
|
-
targets: IngestTarget[];
|
|
77
|
-
}
|
|
78
|
-
/** One ingestion request, whichever field it came from. `overwrite` only matters in replace mode. */
|
|
79
|
-
export interface IngestRequest {
|
|
80
|
-
text?: string;
|
|
81
|
-
source_uri?: string;
|
|
82
|
-
paths?: string[];
|
|
83
|
-
domain: string;
|
|
84
|
-
source?: string;
|
|
85
|
-
title?: string;
|
|
86
|
-
overwrite?: boolean;
|
|
87
|
-
}
|
|
88
|
-
/** The two runtime faces of {@link KnowledgeStore.ingestRequest}. */
|
|
89
|
-
export type IngestMode = 'add' | 'replace';
|
|
90
|
-
export declare class KnowledgeStore {
|
|
91
|
-
private readonly semantic;
|
|
92
|
-
private readonly vstore;
|
|
93
|
-
/** The store config file `addDomain` writes the allowlist back to (`knowledgeConfigPath(home)`). */
|
|
94
|
-
private readonly domainConfigPath;
|
|
95
|
-
private readonly logger?;
|
|
96
|
-
private readonly db;
|
|
97
|
-
/**
|
|
98
|
-
* The tokenizer `doc_chunks_fts` was ACTUALLY built with, read from the database at open.
|
|
99
|
-
*
|
|
100
|
-
* Not `resolveFtsTokenizer()`: `CREATE VIRTUAL TABLE IF NOT EXISTS` never rebuilds an existing
|
|
101
|
-
* table, so a database created under another SQLite build keeps the tokenizer it was born with,
|
|
102
|
-
* and building MATCH expressions for the wrong one is silent zero recall (see `db/tokenizer.ts`).
|
|
103
|
-
*/
|
|
104
|
-
private readonly ftsTokenizer;
|
|
105
|
-
private readonly config;
|
|
106
|
-
private readonly kbConfig;
|
|
107
|
-
/** All SQL for this store lives behind these (DESIGN §19). */
|
|
108
|
-
private readonly docs;
|
|
109
|
-
private readonly chunks;
|
|
110
|
-
/** The editable `.md` copy of every document (`knowledge.docs.dir`). */
|
|
111
|
-
private readonly docFiles;
|
|
112
|
-
/** > 0 while a batch (directory import) is writing: commits and baseline refreshes wait. */
|
|
113
|
-
private deferCorpusWrites;
|
|
114
|
-
/**
|
|
115
|
-
* Identity object for the shared entity sweep's in-flight guard (`store/entity_sweep.ts`).
|
|
116
|
-
*
|
|
117
|
-
* The guard itself lives in that module; this is only the per-store identity it keys on. It exists
|
|
118
|
-
* as a field — rather than the adapter object, which is built per call — because the guard must
|
|
119
|
-
* survive between two overlapping calls: `reindex` can be reached from a tool call and the
|
|
120
|
-
* settings-page button at once, and both would select the same stale chunks.
|
|
121
|
-
*/
|
|
122
|
-
private readonly entitySweepGuard;
|
|
123
|
-
/**
|
|
124
|
-
* Per-file stamps (`path → mtimeMs:size`) and the file-set they were taken from, so a reconcile
|
|
125
|
-
* can ask "what changed?" for the price of one `stat` per document instead of reading and
|
|
126
|
-
* hashing the whole corpus. `null` = no baseline yet.
|
|
127
|
-
*/
|
|
128
|
-
private corpusStamps;
|
|
129
|
-
private corpusFileSet;
|
|
130
|
-
/** Version control for the corpus (`knowledge.git`); a failure here is never fatal. */
|
|
131
|
-
private readonly corpusGit;
|
|
132
|
-
/**
|
|
133
|
-
* The LIVE domain allowlist — seeded at boot from `knowledge.domains` ∪ the library's existing
|
|
134
|
-
* domains, then kept in step with the store config by {@link addDomain}. The config itself is
|
|
135
|
-
* read once at startup (layer ③ of `loader.ts`), so a freshly added domain must land HERE or the
|
|
136
|
-
* very next write would refuse it until a restart.
|
|
137
|
-
*/
|
|
138
|
-
private readonly allowedDomains;
|
|
139
|
-
/** Whether the config allowlist is active (non-empty); `false` = `domains: []` = no restriction. */
|
|
140
|
-
private readonly domainsRestricted;
|
|
141
|
-
constructor(dbPath: string, config: Config, kbConfig: KnowledgeConfig, semantic: SemanticBackend, vstore: VectorStore,
|
|
142
|
-
/** The store config file `addDomain` writes the allowlist back to (`knowledgeConfigPath(home)`). */
|
|
143
|
-
domainConfigPath: string, logger?: AvantfLogger | undefined);
|
|
144
|
-
/**
|
|
145
|
-
* Rebuild the in-memory vstore from persisted chunk vectors, and say loudly what belongs to
|
|
146
|
-
* another vector space.
|
|
147
|
-
*
|
|
148
|
-
* Also detects a VECTOR-SPACE change, which the reload itself cannot see: the reload checks the
|
|
149
|
-
* width, and a different model at the same width produces dim-valid vectors that are then ranked
|
|
150
|
-
* against the current query's — two spaces mixed in one index, with no error anywhere.
|
|
151
|
-
*
|
|
152
|
-
* The reload, the count and the warning are the SHARED open-time step (`store/vector_repair.ts`),
|
|
153
|
-
* which is what turned the knowledge store from "warns about a swapped model but never repairs it"
|
|
154
|
-
* into the same automatic path memory has always had; this store's half is the row read. The
|
|
155
|
-
* manual entry stays `vectors --fix` (the vector-only repair); `kb_reindex` remains the full
|
|
156
|
-
* FTS + entities + vectors re-entry.
|
|
157
|
-
*/
|
|
158
|
-
private reloadIndex;
|
|
159
|
-
/**
|
|
160
|
-
* The write-side domain allowlist AND the 知识页 picker's data, in one place: the configured
|
|
161
|
-
* `knowledge.domains` allowlist ∪ the domains already present in `documents` ∪ the domains added
|
|
162
|
-
* through {@link addDomain} this session.
|
|
163
|
-
*
|
|
164
|
-
* `restricted: false` means the allowlist is EXPLICITLY empty (`domains: []`), the documented
|
|
165
|
-
* "no restriction" value. The union half is not a compatibility layer: without it, narrowing
|
|
166
|
-
* the allowlist would make every existing document un-re-ingestable, and the picker would offer
|
|
167
|
-
* a name the store then refuses.
|
|
168
|
-
*/
|
|
169
|
-
domainCatalog(): DomainCatalog;
|
|
170
|
-
/** Refuse a `domain` outside the allowlist, naming every value that IS allowed. */
|
|
171
|
-
private assertDomainAllowed;
|
|
172
|
-
/**
|
|
173
|
-
* Add one domain to the live allowlist AND to the store config's `domains` list.
|
|
174
|
-
*
|
|
175
|
-
* The UI-only path (`kbAddDomain`) — deliberately NOT an agent tool: the asymmetry is the point,
|
|
176
|
-
* because an autonomous writer is exactly where domain sprawl would come from. The store owns the
|
|
177
|
-
* rules (trim / reject path separators / treat an existing name as a selection) so both halves
|
|
178
|
-
* agree, and it REFUSES while the allowlist is `[]` ("no restriction"): appending to an unfettered
|
|
179
|
-
* list would silently turn it into a restricted one.
|
|
180
|
-
*/
|
|
181
|
-
addDomain(raw: string): DomainCatalog;
|
|
182
|
-
/**
|
|
183
|
-
* The shared ingestion entry: **① plan → ② classify → ③ dispatch**, in that fixed order.
|
|
184
|
-
*
|
|
185
|
-
* Stage ① resolves the domain allowlist, insists on one of `text`/`source_uri`/`paths`, derives
|
|
186
|
-
* the identity every target will be written under, and looks each one up — using the SAME
|
|
187
|
-
* {@link classifySource} the source picker uses, so the type (text / URL / local file / directory)
|
|
188
|
-
* is decided once. Stage ① MUST NOT read a file or touch the network: identity only needs
|
|
189
|
-
* `realpath`/`stat`/`readdir` plus the body of a pasted `text`, which the caller already holds. That
|
|
190
|
-
* is what makes "conflict before read" observable (a colliding `source_uri` answers with the
|
|
191
|
-
* collision even when the URL is unreachable or the file is a binary the reader would refuse).
|
|
192
|
-
*
|
|
193
|
-
* Stage ③ dispatches on the plan: `ingest` / `ingestUri` / `importPaths`. Those keep their
|
|
194
|
-
* replace-by-identity semantics; this entry only puts the plan in front of them.
|
|
195
|
-
*
|
|
196
|
-
* The two modes are the two public faces:
|
|
197
|
-
* - `add` (`rt.kbAdd`, the model's `kb_add`): never replaces. A single collision throws an
|
|
198
|
-
* actionable Chinese error; a batch collision is per-file (see `importNew`), because one
|
|
199
|
-
* existing file in a 100-file import must not fail the other 99.
|
|
200
|
-
* - `replace` (`rt.kb`, the UI/CLI): a collision comes back as a {@link KbConflictReport} and
|
|
201
|
-
* writes NOTHING unless the caller confirmed with `overwrite: true`; then it re-runs the whole
|
|
202
|
-
* request. All-or-nothing, so a partial write can never sit behind a confirmation dialog.
|
|
203
|
-
*/
|
|
204
|
-
ingestRequest(req: IngestRequest, mode: IngestMode): Promise<IngestResult | ImportResult | KbConflictReport>;
|
|
205
|
-
/**
|
|
206
|
-
* Stage ①/②: resolve the request to its targets without reading anything.
|
|
207
|
-
*
|
|
208
|
-
* `paths` wins over `text`, which wins over `source_uri` — the same precedence the old
|
|
209
|
-
* `kb_add`/`rt.kb` split applied, kept so a call that sends two fields means the same thing.
|
|
210
|
-
*/
|
|
211
|
-
private planIngest;
|
|
212
|
-
/** One target's identity plus whatever document already owns it. */
|
|
213
|
-
private target;
|
|
214
|
-
private planText;
|
|
215
|
-
private planUri;
|
|
216
|
-
/**
|
|
217
|
-
* A batch request: each caller path is classified (file or directory) and a directory is WALKED
|
|
218
|
-
* for its ingestable files. Only the walk filters by extension — an explicitly named file is taken
|
|
219
|
-
* as given, exactly like `importPaths`. Target identity is each FILE's `basename`, which is the
|
|
220
|
-
* identity `importPaths` actually writes (never the directory name).
|
|
221
|
-
*/
|
|
222
|
-
private planPaths;
|
|
223
|
-
/** Stage ③, add mode: refuse a single collision, and split a batch's collisions into `failed`. */
|
|
224
|
-
private runAdd;
|
|
225
|
-
/**
|
|
226
|
-
* Batch add: the plan already knows which targets exist, so those are reported in `failed` (with
|
|
227
|
-
* the actionable text) and only the NEW files are handed to `importPaths`. Passing the expanded
|
|
228
|
-
* file list — not the caller's directories — is what keeps a walk from re-writing a colliding
|
|
229
|
-
* file; the walk's own `skipped` list is carried over because explicit files are never filtered.
|
|
230
|
-
*/
|
|
231
|
-
private importNew;
|
|
232
|
-
/** Stage ③, replace mode (the caller confirmed, or nothing collided). */
|
|
233
|
-
private runReplace;
|
|
234
|
-
/** The machine-readable "nothing was written; confirm to overwrite" answer (UI + CLI). */
|
|
235
|
-
private conflictReport;
|
|
236
|
-
/**
|
|
237
|
-
* Add one document from TEXT, and never replace an existing one.
|
|
238
|
-
*
|
|
239
|
-
* This is the PASTE entry point of the add-only face, and the distinction is the whole point:
|
|
240
|
-
* `ingest` is replace-by-identity (what `ingestUri`, `importPaths`, `sync`'s re-ingest and `adopt`
|
|
241
|
-
* need), while a paste that lands on an existing identity is an ADD mistake — the user meant to
|
|
242
|
-
* save a new note, not to overwrite the one already filed. So this refuses instead of writing
|
|
243
|
-
* anything, naming the document it collided with (its `doc_id`, title and managed file) so the
|
|
244
|
-
* caller can choose a different title or edit that file.
|
|
245
|
-
*/
|
|
246
|
-
ingestNew(text: string, domain: string, source?: string, title?: string): Promise<IngestResult>;
|
|
247
|
-
/**
|
|
248
|
-
* Ingest one document: chunk (paragraph/heading aware), persist chunks + entities,
|
|
249
|
-
* then encode vectors. Fully awaited — when ingest resolves, the document is
|
|
250
|
-
* searchable through every path (FTS, entity, semantic).
|
|
251
|
-
*/
|
|
252
|
-
ingest(text: string, domain: string, source?: string, title?: string, sourceUri?: string, encoding?: string,
|
|
253
|
-
/** What the automatic git commit should say this was (`adopt` = recovered from an overwrite). */
|
|
254
|
-
gitAction?: 'ingest' | 'sync' | 'adopt',
|
|
255
|
-
/** The converter that produced `text` (absent for text/PDF and for a body re-read from disk). */
|
|
256
|
-
converter?: string,
|
|
257
|
-
/** What that conversion could not carry over. */
|
|
258
|
-
warnings?: string[]): Promise<IngestResult>;
|
|
259
|
-
/** Materialize one document's editable copy; returns the `file` / `file_error` result fields. */
|
|
260
|
-
private writeManagedFile;
|
|
261
|
-
/** The resolved ingestion boundary (see `ingest_guard.ts`). */
|
|
262
|
-
private get ingestLimits();
|
|
263
|
-
/**
|
|
264
|
-
* Ingest from a source URI (DESIGN D5): local file paths (workspace files are the
|
|
265
|
-
* priority source) are read from disk; `http(s)` URIs are fetched. The URI itself
|
|
266
|
-
* is NEVER stored as the document body — that was the old broken behavior.
|
|
267
|
-
*
|
|
268
|
-
* Both paths are bounded BEFORE materializing the content (the `MAX_DOC_CHARS`
|
|
269
|
-
* guard inside `ingest` can only run once the whole body is already in memory),
|
|
270
|
-
* and both are checked against the ingestion boundary: a local path must resolve
|
|
271
|
-
* inside the allowed roots and a URL must not address private space (see
|
|
272
|
-
* `ingest_guard.ts`).
|
|
273
|
-
*
|
|
274
|
-
* The URL-vs-path decision goes through the store's ONE classifier ({@link classifySource}, the
|
|
275
|
-
* same one the source picker and the plan stage use) — a second `^https?://` test here would be a
|
|
276
|
-
* second answer to "what is this string".
|
|
277
|
-
*/
|
|
278
|
-
ingestUri(uri: string, domain: string, source?: string, title?: string): Promise<IngestResult>;
|
|
279
|
-
/**
|
|
280
|
-
* Extract entities per chunk (persisted) and encode/persist chunk vectors.
|
|
281
|
-
*
|
|
282
|
-
* Returns how many chunks did NOT get a vector, so the caller can put it in the tool result. A
|
|
283
|
-
* missing model counts every chunk that had no reusable vector: the document is still searchable by
|
|
284
|
-
* text and entity, but not semantically, and "入库成功 N 段" must not imply otherwise.
|
|
285
|
-
*/
|
|
286
|
-
private indexChunks;
|
|
287
|
-
/**
|
|
288
|
-
* The doc's stored vectors that still APPLY, keyed by content hash (see {@link vectorReusable}).
|
|
289
|
-
*
|
|
290
|
-
* Called inside the ingest transaction, before the replace: two chunks with the same text are one
|
|
291
|
-
* hash, and the first row wins — the reuse is only a cache lookup, so a duplicate entry cannot be
|
|
292
|
-
* distinguished from a hit and need not be.
|
|
293
|
-
*/
|
|
294
|
-
private reusableByHash;
|
|
295
|
-
/**
|
|
296
|
-
* Write back the vectors a re-ingest can reuse, and report which rows still need the model.
|
|
297
|
-
*
|
|
298
|
-
* A row is only reused when the lookup misses nothing: any row whose write fails, or whose text has
|
|
299
|
-
* no reusable vector, comes back in `remaining` and is encoded as before — so the reuse path cannot
|
|
300
|
-
* leave a chunk unindexed that the plain path would have indexed.
|
|
301
|
-
*/
|
|
302
|
-
private writeReusedVectors;
|
|
303
|
-
/**
|
|
304
|
-
* This store's half of the shared encode-and-persist flow (`store/vector_repair.ts`): the backend
|
|
305
|
-
* call, the live index and the `WRITE_BATCH` transaction this store measured its way into.
|
|
306
|
-
*
|
|
307
|
-
* The model call is deliberately NOT batched, and that is a MEASURED decision, not an omission:
|
|
308
|
-
* a batched forward pass pads each item to the longest in its group, and real chunks span 53–550
|
|
309
|
-
* characters (median 303). On 299 real chunks: serial 31.2 ms/text, batched in document order
|
|
310
|
-
* 55.7 ms/text (1.79x SLOWER), batched after sorting by length 31.9 ms/text (a wash). Batching
|
|
311
|
-
* only pays for SHORT homogeneous texts, which chunks are not — so the review's "batch the
|
|
312
|
-
* encode" suggestion was tested and rejected. `LocalBgeBackend.encodeBatch` remains correct (and
|
|
313
|
-
* length-sorted) for callers whose texts suit it.
|
|
314
|
-
*
|
|
315
|
-
* What DID pay off is the write side: one commit per `WRITE_BATCH` chunks instead of one per
|
|
316
|
-
* chunk (measured 14.2 µs/row vs 2.2 µs/row), and the transaction never spans an `await` — that
|
|
317
|
-
* is the `writeBatch` below.
|
|
318
|
-
*/
|
|
319
|
-
private encodeWriteTarget;
|
|
320
|
-
/**
|
|
321
|
-
* Encode texts one at a time and persist them in `WRITE_BATCH` transactions.
|
|
322
|
-
*
|
|
323
|
-
* A failed chunk is skipped, not thrown: the rows are already committed and their old vectors
|
|
324
|
-
* evicted by the ingest path, so a throw here would leave the caller with a failed ingest and NO
|
|
325
|
-
* COUNT of what was skipped. The shortfall travels back to the tool result
|
|
326
|
-
* (`IngestResult.vectors_failed`) instead of living only in a log line.
|
|
327
|
-
*/
|
|
328
|
-
private encodeAndStore;
|
|
329
|
-
/**
|
|
330
|
-
* Identity of the vector space this store currently writes into (DESIGN §20): the representation
|
|
331
|
-
* fingerprint — backend, model, width AND the backend's declared pooling/normalization/input
|
|
332
|
-
* window/model revision — so any representation change makes stored chunk vectors stale instead
|
|
333
|
-
* of silently mixing two spaces. Public so diagnostics and tests can name the space.
|
|
334
|
-
*/
|
|
335
|
-
vectorSpace(): string;
|
|
336
|
-
/**
|
|
337
|
-
* Cheapest possible DETECTION of "the embedding space changed under this store": counts of
|
|
338
|
-
* persisted chunk vectors whose width or recorded space id no longer matches the one this store
|
|
339
|
-
* writes into. Reads blob LENGTHS only — never decodes — so the startup warning and the background
|
|
340
|
-
* drive can afford it at any corpus size.
|
|
341
|
-
*/
|
|
342
|
-
vectorSpaceHealth(): VectorSpaceHealth;
|
|
343
|
-
/** {@link vectorSpaceHealth} plus `missing`, the third term of the repair flow's `remaining`. */
|
|
344
|
-
private vectorSpaceCounts;
|
|
345
|
-
/** Async bootstrap the semantic backend (the shared flow's explicit-fix warmup). */
|
|
346
|
-
warmupSemantic(): Promise<boolean>;
|
|
347
|
-
/**
|
|
348
|
-
* This store's half of the shared repair flow (`store/vector_repair.ts`): the primitives the flow
|
|
349
|
-
* steps. The flow itself — batch size, event-loop yield, stop / resume / no-progress rules,
|
|
350
|
-
* `semantic.auto_migrate`, the dry-run split — lives there once, for both stores.
|
|
351
|
-
*/
|
|
352
|
-
vectorRepairTarget(): VectorRepairTarget;
|
|
353
|
-
/**
|
|
354
|
-
* The chunks the semantic leg cannot use (no vector / wrong width / another space). One SQL
|
|
355
|
-
* aggregate over blob lengths; `dropIds` is empty because knowledge overwrites in place.
|
|
356
|
-
*/
|
|
357
|
-
private classifyRepairVectors;
|
|
358
|
-
/**
|
|
359
|
-
* Encode + persist up to `limit` chunks the semantic leg cannot use, bounded and ordered by id.
|
|
360
|
-
*
|
|
361
|
-
* It does NOT re-extract entities or rebuild FTS: that is `kb_reindex`'s job, and the whole point
|
|
362
|
-
* of the automatic repair is that the vector half can run on its own — which is exactly the
|
|
363
|
-
* objection the "known boundary" in DESIGN §20 used to raise against a background run.
|
|
364
|
-
*/
|
|
365
|
-
private reencodeStaleChunks;
|
|
366
|
-
/**
|
|
367
|
-
* The explicit, unbounded repair behind `mem_admin vectors_fix` / `avantf-mem vectors --fix`.
|
|
368
|
-
* `dry_run` previews every count without writing — and without loading the model, which is why
|
|
369
|
-
* the preview also reports `would_warm` (see the contract's `VectorsFixStoreReport`).
|
|
370
|
-
*/
|
|
371
|
-
vectorsFix(dryRun?: boolean): Promise<VectorsFixStoreReport>;
|
|
372
|
-
/**
|
|
373
|
-
* ONE bounded slice of the vector-space migration: at most `batchSize` chunks are re-encoded, so
|
|
374
|
-
* the caller keeps control of the event loop between slices. This is the unit the background
|
|
375
|
-
* migration ({@link migrateVectors}) and its tests drive.
|
|
376
|
-
*/
|
|
377
|
-
migrateVectorsBatch(batchSize?: number): Promise<VectorMigrationProgress>;
|
|
378
|
-
/**
|
|
379
|
-
* Drive the bounded background migration for this store until it is current, the model is
|
|
380
|
-
* unavailable, or `shouldStop()` turns true (plugin unmount / process shutdown). The loop, the
|
|
381
|
-
* `semantic.auto_migrate` switch, the no-progress rule and the store-named logging all live in the
|
|
382
|
-
* shared flow (`store/vector_repair.ts`); this is the one-store entry.
|
|
383
|
-
*/
|
|
384
|
-
migrateVectors(opts?: {
|
|
385
|
-
batchSize?: number;
|
|
386
|
-
shouldStop?: () => boolean;
|
|
387
|
-
onProgress?: (progress: VectorMigrationOutcome) => void;
|
|
388
|
-
}): Promise<VectorMigrationOutcome>;
|
|
389
|
-
/**
|
|
390
|
-
* A stored vector is reusable when it exists, matches the space, and its text is unchanged.
|
|
391
|
-
*
|
|
392
|
-
* The three checks answer different questions, and the hash is deliberately the RAREST of them
|
|
393
|
-
* to fire (see `db/hash.ts`): `ingest` replaces chunks rather than editing their text, so a
|
|
394
|
-
* re-ingested change is caught by the missing vector, and a rule change by `entities_version`.
|
|
395
|
-
* The hash is what catches a text mutation that skipped the ingest path entirely — without it
|
|
396
|
-
* the store would keep serving a vector for text it no longer holds.
|
|
397
|
-
*/
|
|
398
|
-
private vectorReusable;
|
|
399
|
-
/**
|
|
400
|
-
* Extract one chunk's entity names, bounded by `MAX_ENTITIES_PER_CHUNK`.
|
|
401
|
-
*
|
|
402
|
-
* The bound is here, not in either caller: it is a property of what `chunk_entities` may hold for
|
|
403
|
-
* one chunk, and the ingest path and the sweep must not disagree about it.
|
|
404
|
-
*/
|
|
405
|
-
private extractRowEntities;
|
|
406
|
-
/** One transaction replacing the entity rows of the given chunks (the ingest path). */
|
|
407
|
-
private replaceChunkEntities;
|
|
408
|
-
/**
|
|
409
|
-
* The per-row primitive of the shared entity sweep (`store/entity_sweep.ts`).
|
|
410
|
-
*
|
|
411
|
-
* Replaces ONE chunk's entity rows and stamps that same row with `ENTITY_EXTRACTOR_VERSION`, in one
|
|
412
|
-
* transaction. Knowledge's domain difference from memory is exactly this: memory's transaction also
|
|
413
|
-
* moves triples, the HRR bundle and the conflict queue, while a chunk has none of those — its
|
|
414
|
-
* derived state is the entity names alone.
|
|
415
|
-
*
|
|
416
|
-
* Per row, not per batch, because the sweep's contract is that a failure leaves THAT row stale and
|
|
417
|
-
* retryable: inside a batched transaction one bad extraction would roll back the whole batch, and
|
|
418
|
-
* rows it never reached would look stamped.
|
|
419
|
-
*/
|
|
420
|
-
private replaceRowEntities;
|
|
421
|
-
/**
|
|
422
|
-
* Ingest local files/directories. Every path goes through the SAME boundary as
|
|
423
|
-
* `ingestUri` — a bare `kb_import` on `~/.ssh` would otherwise be the exact
|
|
424
|
-
* exfiltration hole the `source_uri` guard closes.
|
|
425
|
-
*
|
|
426
|
-
* Failure split: a caller-supplied path that is missing or out of bounds THROWS
|
|
427
|
-
* (validating the arguments up front, and the operator needs to know), while a file
|
|
428
|
-
* reached by walking a directory is reported in `failed` — the previous `catch {}`
|
|
429
|
-
* dropped those silently, so "imported 60 of 100 files" was indistinguishable from
|
|
430
|
-
* a complete import.
|
|
431
|
-
*/
|
|
432
|
-
importPaths(paths: string[], domain: string, source?: string): Promise<ImportResult>;
|
|
433
|
-
/**
|
|
434
|
-
* One corpus commit + one baseline refresh, unless a batch is in flight.
|
|
435
|
-
*
|
|
436
|
-
* Every owned write calls this; `deferCorpusWrites` is what turns N writes into 1 commit during
|
|
437
|
-
* an import. Never throws (`GitRepo` swallows), and the baseline refresh is a no-op until one
|
|
438
|
-
* exists.
|
|
439
|
-
*/
|
|
440
|
-
private commitCorpus;
|
|
441
|
-
list(domain?: string, source?: string, limit?: number, offset?: number): DocumentSummary[];
|
|
442
|
-
detail(docId: number): DocumentDetail | null;
|
|
443
|
-
remove(docId: number): boolean;
|
|
444
|
-
/**
|
|
445
|
-
* The managed path for a document ROW the caller already holds — skips the `docs.get` that
|
|
446
|
-
* `docFilePath` does, which matters because `kb_list` needs a path for every row it renders.
|
|
447
|
-
*/
|
|
448
|
-
docFilePathOf(doc: {
|
|
449
|
-
doc_id: number;
|
|
450
|
-
domain: string;
|
|
451
|
-
source: string;
|
|
452
|
-
title: string;
|
|
453
|
-
}): string;
|
|
454
|
-
/** The managed file this document owns (whether or not it exists yet), or `null` for a missing doc. */
|
|
455
|
-
docFilePath(docId: number): string | null;
|
|
456
|
-
/**
|
|
457
|
-
* Reconcile the managed files with what the KB indexed (DESIGN: knowledge maintenance).
|
|
458
|
-
*
|
|
459
|
-
* `dryRun` answers "what changed?" without touching the index — that is what the 知识 tab's list
|
|
460
|
-
* uses to mark documents whose file was edited. Otherwise every STALE document is re-ingested
|
|
461
|
-
* from its file body, which replaces its chunks and re-encodes its vectors (the document keeps
|
|
462
|
-
* its `doc_id`: identity is `domain → source → title`, and the file records the same one).
|
|
463
|
-
*
|
|
464
|
-
* `missing` documents are reported, never invented: a file that was deleted by hand leaves the
|
|
465
|
-
* indexed copy alone until the user deletes the document itself. `orphans` are managed files no
|
|
466
|
-
* document claims — leftovers from a row deleted outside `remove` — and are only reported.
|
|
467
|
-
*/
|
|
468
|
-
/**
|
|
469
|
-
* Stat-only reconcile check: which managed files look changed since the last call, and whether
|
|
470
|
-
* the file SET changed (a new or vanished `.md` — an orphan or a restore).
|
|
471
|
-
*
|
|
472
|
-
* Nothing here reads a file, so the common answer ("nothing changed") costs one `stat` per
|
|
473
|
-
* document. A stamp is a TRIGGER, not a judgement: the caller re-ingests through `sync`, which
|
|
474
|
-
* hashes the body and decides. The first call only seeds the baseline and reports nothing —
|
|
475
|
-
* use `sync` once at startup if a process may have been down while the corpus changed.
|
|
476
|
-
*
|
|
477
|
-
* ASYNC because one `stat` per document is exactly the cost that has to leave the event loop to
|
|
478
|
-
* something else once in a while: at 10k documents the walk blocked the host for >0.1 s per trip
|
|
479
|
-
* (measured — performance review §7.5 / P7), and this runs after every tool result behind a 2 s
|
|
480
|
-
* throttle. The baseline is still taken ATOMICALLY at the end, so a caller sees the same
|
|
481
|
-
* all-or-nothing update as before.
|
|
482
|
-
*/
|
|
483
|
-
corpusDrift(): Promise<{
|
|
484
|
-
changed: number[];
|
|
485
|
-
missing: number[];
|
|
486
|
-
fileSetChanged: boolean;
|
|
487
|
-
}>;
|
|
488
|
-
/**
|
|
489
|
-
* Re-take the baseline after a write this store made ITSELF.
|
|
490
|
-
*
|
|
491
|
-
* Without this, every `ingest` would look like an external edit on the next drift check and cost
|
|
492
|
-
* a redundant (if harmless) sync; with it, only edits from OUTSIDE the store are reported. Cheap:
|
|
493
|
-
* one `stat` per document, no reads. A no-op until a baseline exists.
|
|
494
|
-
*/
|
|
495
|
-
private noteCorpusWrite;
|
|
496
|
-
/**
|
|
497
|
-
* Take back a file that lost its frontmatter, when the caller explicitly asks for it.
|
|
498
|
-
*
|
|
499
|
-
* Cheap refusals, all of them deliberate: a file that CLAIMS another document is a collision and
|
|
500
|
-
* is left alone; an EMPTY body is refused because `ingest` has no lower bound (`chunkText('')`
|
|
501
|
-
* returns no chunks), so adopting one would silently replace a good document with nothing.
|
|
502
|
-
*/
|
|
503
|
-
private adoptUnclaimed;
|
|
504
|
-
sync(opts?: {
|
|
505
|
-
docId?: number;
|
|
506
|
-
dryRun?: boolean;
|
|
507
|
-
adopt?: boolean;
|
|
508
|
-
}): Promise<KbSyncReport>;
|
|
509
|
-
/**
|
|
510
|
-
* Rebuild the read model: FTS, per-chunk entities, and vectors — incrementally.
|
|
511
|
-
*
|
|
512
|
-
* `domain` scopes the rebuild to one knowledge domain. One corpus SELECT feeds both legs, and
|
|
513
|
-
* each row is skipped when its derivation is provably still valid (same text digest, same
|
|
514
|
-
* vector space, same extractor version). Without that, the only way to know whether a chunk
|
|
515
|
-
* needed mission was to redo the mission: an ONNX forward pass plus a jieba pass per chunk over the
|
|
516
|
-
* whole corpus, every time.
|
|
517
|
-
*
|
|
518
|
-
* `dryRun` answers "how much would this cost?" without writing anything — including without
|
|
519
|
-
* the FTS rebuild, which is a write.
|
|
520
|
-
*/
|
|
521
|
-
reindex(domain?: string, opts?: {
|
|
522
|
-
dryRun?: boolean;
|
|
523
|
-
}): Promise<ReindexReport>;
|
|
524
|
-
close(): void;
|
|
525
|
-
count(): {
|
|
526
|
-
documents: number;
|
|
527
|
-
chunks: number;
|
|
528
|
-
};
|
|
529
|
-
/** Chunk search over the knowledge corpus (semantic + FTS + entity, joint fusion). */
|
|
530
|
-
search(query: string, opts?: KnowledgeSearchOptions): Promise<RecallHit[]>;
|
|
531
|
-
/**
|
|
532
|
-
* This store's half of the shared orchestration (`store/hybrid.ts`): the legs, the chunk-row load
|
|
533
|
-
* and the chunk→hit mapping.
|
|
534
|
-
*
|
|
535
|
-
* The limit normalization, the over-fetch factor, the leg cap, the capped-leg counter, the
|
|
536
|
-
* degraded weights and the output budget all live there now. They used to be copied
|
|
537
|
-
* here, and every fix landed on the memory side only — a `NaN` limit reached this store's SQL and
|
|
538
|
-
* took the whole cross-store query down with it, `retriever.over_fetch_factor` meant something
|
|
539
|
-
* different per store, and a capped knowledge leg was invisible to the health counters.
|
|
540
|
-
*/
|
|
541
|
-
private hybridDeps;
|
|
542
|
-
/**
|
|
543
|
-
* The knowledge legs: semantic, FTS, and entity overlap against the entities extracted AT INGEST
|
|
544
|
-
* time (`chunk_entities`) — no corpus scan.
|
|
545
|
-
*
|
|
546
|
-
* Same cap as the memory store: fusion keeps `overFetch` entries, so a leg that returns the whole
|
|
547
|
-
* matching corpus only makes `fuse` normalize and sort it (see `MemoryStore.searchLegs`).
|
|
548
|
-
*/
|
|
549
|
-
private searchLegs;
|
|
550
|
-
/** The texts of a candidate set, for the per-row FTS floor (one batched query). */
|
|
551
|
-
private chunkTexts;
|
|
552
|
-
/**
|
|
553
|
-
* Semantic leg. `queryVector` short-circuits the encode when the caller already
|
|
554
|
-
* encoded this exact query (the cross-store router does, for both stores at once); `onVector`
|
|
555
|
-
* publishes the vector this leg actually used, so the relaxed retry pass can reuse it instead of
|
|
556
|
-
* re-encoding (performance review §7.7 / P8).
|
|
557
|
-
*/
|
|
558
|
-
private semanticPath;
|
|
559
|
-
/**
|
|
560
|
-
* The semantic leg's scope filter, as a `filterTopk` primitive: `undefined` when NO domain/source
|
|
561
|
-
* was asked means "keep the whole pool" — the short-circuit is preserved, so no `chunks.meta`
|
|
562
|
-
* lookup runs at all. Otherwise one batched lookup, and rows whose domain/source do not match (or
|
|
563
|
-
* whose chunk is gone) are dropped.
|
|
564
|
-
*/
|
|
565
|
-
private scopeFilter;
|
|
566
|
-
/**
|
|
567
|
-
* Sync relevance probe for the plugin's conditional hint (DESIGN §12). Same contract as
|
|
568
|
-
* `MemoryStore.lexicalProbe` — see `store/lexical.ts`.
|
|
569
|
-
*/
|
|
570
|
-
lexicalProbe(text: string, stopAt?: number): LexicalProbe;
|
|
571
|
-
private ftsPath;
|
|
572
|
-
/**
|
|
573
|
-
* Entity overlap against entities extracted AT INGEST time (chunk_entities) — no corpus scan.
|
|
574
|
-
*
|
|
575
|
-
* Same metric as the memory store's sibling (`store/entity_leg.ts` carries the measurement and
|
|
576
|
-
* the reasoning): anchored Jaccard with a saturating union over the query's rare entities, so a
|
|
577
|
-
* wide chunk cannot dilute the denominator and a corpus-wide word cannot drag the whole knowledge
|
|
578
|
-
* base in.
|
|
579
|
-
*/
|
|
580
|
-
private jaccardPath;
|
|
581
|
-
}
|
|
582
|
-
export interface Chunk {
|
|
583
|
-
text: string;
|
|
584
|
-
headingsPath: string;
|
|
585
|
-
/** Inclusive start offset in the source text. */
|
|
586
|
-
start: number;
|
|
587
|
-
/** Exclusive end offset in the source text. */
|
|
588
|
-
end: number;
|
|
589
|
-
}
|
|
590
|
-
/**
|
|
591
|
-
* Paragraph + heading aware chunking (DESIGN §8): markdown headings open a new
|
|
592
|
-
* block and maintain a headings stack; blank lines separate blocks; blocks are
|
|
593
|
-
* packed greedily up to `chunkSize`; oversized blocks are hard-split with
|
|
594
|
-
* `overlap`; consecutive chunks finally share `overlap` characters of context.
|
|
595
|
-
* Offsets always refer to the ORIGINAL text so `source_ref`/char positions stay
|
|
596
|
-
* faithful for provenance.
|
|
597
|
-
*/
|
|
598
|
-
export declare function chunkText(text: string, chunkSize?: number, overlap?: number): Chunk[];
|