@avantf/dsh-mem 0.6.4 → 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.
Files changed (150) hide show
  1. package/README.md +19 -10
  2. package/lib/client.js +1665 -3466
  3. package/lib/dsh-build.json +6 -5
  4. package/lib/engine/graph-recall/in-memory.d.ts +93 -0
  5. package/lib/engine/graph-recall/index.d.ts +22 -0
  6. package/lib/engine/graph-recall/traverse.d.ts +119 -0
  7. package/lib/engine/graph-recall/types.d.ts +188 -0
  8. package/lib/engine/mem/config/config_files.d.ts +1 -1
  9. package/lib/engine/mem/config/loader.d.ts +2 -3
  10. package/lib/engine/mem/config/paths.d.ts +2 -13
  11. package/lib/engine/mem/db/dao/contradictions.d.ts +4 -22
  12. package/lib/engine/mem/db/dao/entities.d.ts +80 -7
  13. package/lib/engine/mem/db/dao/fact_sources.d.ts +3 -13
  14. package/lib/engine/mem/db/dao/facts.d.ts +311 -56
  15. package/lib/engine/mem/db/dao/triples.d.ts +1 -26
  16. package/lib/engine/mem/db/graph.d.ts +249 -0
  17. package/lib/engine/mem/db/journal_notes.d.ts +35 -0
  18. package/lib/engine/mem/db/migrations.d.ts +9 -0
  19. package/lib/engine/mem/db/port.d.ts +22 -3
  20. package/lib/engine/mem/db/schema.d.ts +119 -0
  21. package/lib/engine/mem/db/tokenizer.d.ts +9 -0
  22. package/lib/engine/mem/dbdrivers/fake/driver.d.ts +75 -0
  23. package/lib/engine/mem/dbdrivers/fake/index.d.ts +7 -0
  24. package/lib/engine/mem/dbdrivers/fake/surface.d.ts +34 -0
  25. package/lib/engine/mem/dbdrivers/fake/transport.d.ts +61 -0
  26. package/lib/engine/mem/dbdrivers/sqlite/connection.d.ts +14 -0
  27. package/lib/engine/mem/dbdrivers/sqlite/contradictions.d.ts +35 -0
  28. package/lib/engine/mem/dbdrivers/sqlite/dao/index.d.ts +16 -0
  29. package/lib/engine/mem/dbdrivers/sqlite/driver.d.ts +54 -0
  30. package/lib/engine/mem/dbdrivers/sqlite/facts.d.ts +117 -0
  31. package/lib/engine/mem/dbdrivers/sqlite/index.d.ts +6 -0
  32. package/lib/engine/mem/dbdrivers/sqlite/migrations.d.ts +15 -0
  33. package/lib/engine/mem/dbdrivers/sqlite/relations.d.ts +62 -0
  34. package/lib/engine/mem/dbdrivers/sqlite/search.d.ts +56 -0
  35. package/lib/engine/mem/dbdrivers/sqlite/sources.d.ts +37 -0
  36. package/lib/engine/mem/defaults.d.ts +3 -4
  37. package/lib/engine/mem/dispatch.d.ts +7 -32
  38. package/lib/engine/mem/entities/extract.d.ts +44 -47
  39. package/lib/engine/mem/eval/derived_runner.d.ts +6 -1
  40. package/lib/engine/mem/eval/relation_gold.d.ts +251 -0
  41. package/lib/engine/mem/eval/write_metrics.d.ts +1 -1
  42. package/lib/engine/mem/facts_io.d.ts +328 -0
  43. package/lib/engine/mem/index.d.ts +10 -2
  44. package/lib/engine/mem/interfaces/capabilities.d.ts +30 -0
  45. package/lib/engine/mem/interfaces/common.d.ts +11 -0
  46. package/lib/engine/mem/interfaces/errors.d.ts +65 -0
  47. package/lib/engine/mem/interfaces/index.d.ts +18 -0
  48. package/lib/engine/mem/interfaces/migrations.d.ts +21 -0
  49. package/lib/engine/mem/interfaces/repositories.d.ts +471 -0
  50. package/lib/engine/mem/interfaces/search_index.d.ts +113 -0
  51. package/lib/engine/mem/interfaces/storage_backend.d.ts +84 -0
  52. package/lib/engine/mem/interfaces/storage_driver.d.ts +43 -0
  53. package/lib/engine/mem/interfaces/version.d.ts +34 -0
  54. package/lib/engine/mem/lifecycle/contradiction.d.ts +2 -2
  55. package/lib/engine/mem/maintenance_paths.d.ts +41 -0
  56. package/lib/engine/mem/modelBootstrap.d.ts +1 -1
  57. package/lib/engine/mem/relations/confidence.d.ts +39 -0
  58. package/lib/engine/mem/relations/extract.d.ts +128 -0
  59. package/lib/engine/mem/relations/index.d.ts +32 -0
  60. package/lib/engine/mem/relations/name-quality.d.ts +78 -0
  61. package/lib/engine/mem/relations/normalize.d.ts +275 -0
  62. package/lib/engine/mem/relations/patterns.d.ts +67 -0
  63. package/lib/engine/mem/relations/statement.d.ts +88 -0
  64. package/lib/engine/mem/relations/types.d.ts +79 -0
  65. package/lib/engine/mem/runtime.d.ts +117 -90
  66. package/lib/engine/mem/storage/create.d.ts +42 -0
  67. package/lib/engine/mem/storage/errors.d.ts +65 -0
  68. package/lib/engine/mem/storage/index.d.ts +15 -0
  69. package/lib/engine/mem/storage/ports.d.ts +472 -0
  70. package/lib/engine/mem/storage/registry.d.ts +37 -0
  71. package/lib/engine/mem/storage/sqlite.d.ts +28 -0
  72. package/lib/engine/mem/store/common.d.ts +1 -1
  73. package/lib/engine/mem/store/entity_leg.d.ts +59 -2
  74. package/lib/engine/mem/store/entity_sweep.d.ts +4 -3
  75. package/lib/engine/mem/store/explicit_relations.d.ts +52 -0
  76. package/lib/engine/mem/store/facts_restore.d.ts +124 -0
  77. package/lib/engine/mem/store/floors.d.ts +4 -1
  78. package/lib/engine/mem/store/graph_reader.d.ts +146 -0
  79. package/lib/engine/mem/store/hybrid.d.ts +50 -13
  80. package/lib/engine/mem/store/kinds.d.ts +47 -0
  81. package/lib/engine/mem/store/legs.d.ts +24 -29
  82. package/lib/engine/mem/store/lexical.d.ts +36 -45
  83. package/lib/engine/mem/store/memory.d.ts +486 -82
  84. package/lib/engine/mem/store/recall_policy.d.ts +49 -0
  85. package/lib/engine/mem/store/relation_backfill.d.ts +210 -0
  86. package/lib/engine/mem/store/relation_planner.d.ts +191 -0
  87. package/lib/engine/mem/store/secret_guard.d.ts +0 -1
  88. package/lib/engine/mem/store/self_query.d.ts +4 -2
  89. package/lib/engine/mem/store/time_filter.d.ts +121 -0
  90. package/lib/engine/mem/store/titles.d.ts +52 -0
  91. package/lib/engine/mem/store/vector_repair.d.ts +44 -54
  92. package/lib/engine/mem-contract/config.d.ts +44 -38
  93. package/lib/engine/mem-contract/text.d.ts +1 -1
  94. package/lib/engine/mem-contract/tools.d.ts +184 -117
  95. package/lib/engine/mem-contract/types.d.ts +364 -288
  96. package/lib/index.js +13898 -11500
  97. package/lib/interface-version.json +1 -1
  98. package/lib/types/client/index.d.ts +2 -2
  99. package/lib/types/client/index.d.ts.map +1 -1
  100. package/lib/types/client/paging.d.ts +3 -12
  101. package/lib/types/client/paging.d.ts.map +1 -1
  102. package/lib/types/client/summarize.d.ts.map +1 -1
  103. package/lib/types/command.d.ts +61 -0
  104. package/lib/types/command.d.ts.map +1 -0
  105. package/lib/types/envinit-bootstrap.d.ts +1 -1
  106. package/lib/types/envinit.d.ts +2 -6
  107. package/lib/types/envinit.d.ts.map +1 -1
  108. package/lib/types/hints.d.ts +28 -24
  109. package/lib/types/hints.d.ts.map +1 -1
  110. package/lib/types/index.d.ts +21 -40
  111. package/lib/types/index.d.ts.map +1 -1
  112. package/lib/types/inject.d.ts +3 -2
  113. package/lib/types/inject.d.ts.map +1 -1
  114. package/lib/types/interface_gate.d.ts +9 -9
  115. package/lib/types/prompt.d.ts +17 -41
  116. package/lib/types/prompt.d.ts.map +1 -1
  117. package/lib/types/provision.d.ts +3 -0
  118. package/lib/types/provision.d.ts.map +1 -1
  119. package/lib/types/remote.d.ts +23 -9
  120. package/lib/types/remote.d.ts.map +1 -1
  121. package/lib/types/render.d.ts +3 -3
  122. package/lib/types/tool_schema.d.ts +4 -0
  123. package/lib/types/tool_schema.d.ts.map +1 -1
  124. package/lib/types/vectorMigration.d.ts +5 -7
  125. package/lib/types/vectorMigration.d.ts.map +1 -1
  126. package/package.json +16 -15
  127. package/lib/engine/mem/config/domains.d.ts +0 -20
  128. package/lib/engine/mem/db/dao/chunks.d.ts +0 -231
  129. package/lib/engine/mem/db/dao/documents.d.ts +0 -51
  130. package/lib/engine/mem/db/hash.d.ts +0 -2
  131. package/lib/engine/mem/db/knowledge.d.ts +0 -17
  132. package/lib/engine/mem/eval/index.d.ts +0 -4
  133. package/lib/engine/mem/git.d.ts +0 -73
  134. package/lib/engine/mem/router.d.ts +0 -51
  135. package/lib/engine/mem/store/doc_files.d.ts +0 -125
  136. package/lib/engine/mem/store/document_text.d.ts +0 -47
  137. package/lib/engine/mem/store/ingest_guard.d.ts +0 -25
  138. package/lib/engine/mem/store/knowledge.d.ts +0 -598
  139. package/lib/engine/mem/store/source_picker.d.ts +0 -43
  140. package/lib/engine/mem-retrieval/adapters/local_reranker.d.ts +0 -52
  141. package/lib/engine/mem-retrieval/adapters/none_reranker.d.ts +0 -10
  142. package/lib/engine/mem-retrieval/rerank.d.ts +0 -13
  143. package/lib/types/client/domains.d.ts +0 -53
  144. package/lib/types/client/domains.d.ts.map +0 -1
  145. package/lib/types/client/ingest.d.ts +0 -24
  146. package/lib/types/client/ingest.d.ts.map +0 -1
  147. package/lib/types/open.d.ts +0 -77
  148. package/lib/types/open.d.ts.map +0 -1
  149. package/lib/types/reconcile.d.ts +0 -64
  150. package/lib/types/reconcile.d.ts.map +0 -1
@@ -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[];
@@ -1,43 +0,0 @@
1
- import type { BrowseEntry, BrowseListing, SourceClassification, SourceKind } from '../../mem-contract/index.js';
2
- import { type IngestLimits } from './ingest_guard.js';
3
- /**
4
- * The shapes come from the CONTRACT, and core re-exports them for its own callers.
5
- *
6
- * The engine owns the behaviour, but the browser half needs the types WITHOUT the engine, so the
7
- * contract owns them (see `SourceClassification` / `BrowseListing` there). No hand-mirror left to
8
- * drift, which is why the re-export below exists: `export type { X } from '…'` alone does NOT put `X`
9
- * in this module's scope, so the separate `import type` above is what the rest of this file uses.
10
- */
11
- export type { BrowseEntry, BrowseListing, SourceClassification, SourceKind };
12
- /** Split one source string the way `kb import` does: whitespace or comma separated. */
13
- export declare function splitSourceInput(raw: string): string[];
14
- /**
15
- * Walk one directory for ingestable files: text formats plus PDF.
16
- *
17
- * Everything else is reported as SKIPPED rather than dropped silently — a directory of 200 files
18
- * where 3 were ingested and 197 ignored used to look exactly like a directory of 3 files.
19
- */
20
- export declare function listTextFiles(dir: string): {
21
- files: string[];
22
- skipped: string[];
23
- };
24
- /** Would ingestion take this file name? */
25
- export declare function isIngestableFile(name: string): boolean;
26
- /**
27
- * Classify one source input without ingesting anything.
28
- *
29
- * The order matters: a URL wins outright, then anything that resolves on disk (several paths, a
30
- * directory, a file), then a path-SHAPED input that does not resolve is `missing` (a typo, or a
31
- * path outside the boundary — the reason says which), and only then is it pasted text. Long or
32
- * many-token inputs skip the filesystem probes entirely: prose is not a path list, and a paragraph
33
- * should not cost thousands of `stat` calls.
34
- */
35
- export declare function classifySource(raw: string, limits: IngestLimits): SourceClassification;
36
- /**
37
- * List one directory for the picker.
38
- *
39
- * `requested` empty ⇒ the first allowed root. Every requested path goes through
40
- * {@link resolveLocalSource}, so the picker can never show (or hand back) a directory that
41
- * ingestion would refuse; `parent` stops at a root for the same reason.
42
- */
43
- export declare function browseDirectory(requested: string | undefined, limits: IngestLimits): BrowseListing;