@sparkelf/dsh-knowledge-base 0.1.5 → 0.1.20

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 (58) hide show
  1. package/README.md +129 -2
  2. package/SKILL.md +86 -18
  3. package/lib/client.js +14766 -9663
  4. package/lib/client.js.map +1 -1
  5. package/lib/index.js +8082 -258
  6. package/lib/types/api.d.ts +36 -1
  7. package/lib/types/client/KnowledgeCreateDialog.d.ts +25 -0
  8. package/lib/types/client/KnowledgeFileTree.d.ts +143 -0
  9. package/lib/types/client/KnowledgeHome.d.ts +58 -0
  10. package/lib/types/client/KnowledgeListing.d.ts +7 -0
  11. package/lib/types/client/KnowledgeNewDocumentDialog.d.ts +32 -0
  12. package/lib/types/client/KnowledgeProgressPopover.d.ts +69 -0
  13. package/lib/types/client/KnowledgeSettings.d.ts +4 -3
  14. package/lib/types/client/KnowledgeTemplates.d.ts +4 -0
  15. package/lib/types/client/VectorSettingsGroup.d.ts +17 -0
  16. package/lib/types/client/WorkflowMenuIcon.d.ts +22 -0
  17. package/lib/types/client/extractionHistory.d.ts +43 -0
  18. package/lib/types/client/index.d.ts +32 -14
  19. package/lib/types/client/listingFilter.d.ts +58 -0
  20. package/lib/types/client/locales.d.ts +123 -0
  21. package/lib/types/client/openInSidebar.d.ts +2 -2
  22. package/lib/types/client/progressRules.d.ts +198 -17
  23. package/lib/types/client/sanitizeExplorerTabs.d.ts +20 -0
  24. package/lib/types/client/settingsGate.d.ts +65 -0
  25. package/lib/types/client/uploadTransport.d.ts +65 -0
  26. package/lib/types/client/useTreeSort.d.ts +72 -0
  27. package/lib/types/client/workflows/WorkflowPlaza.d.ts +146 -0
  28. package/lib/types/client/workflows/cron.d.ts +125 -0
  29. package/lib/types/client/workflows/dedupe.d.ts +70 -0
  30. package/lib/types/client/workflows/deps.d.ts +101 -0
  31. package/lib/types/client/workflows/modelChoice.d.ts +106 -0
  32. package/lib/types/client/workflows/published.d.ts +43 -0
  33. package/lib/types/client/workflows/registry.d.ts +98 -0
  34. package/lib/types/client/workflows/subscribe.d.ts +179 -0
  35. package/lib/types/embeddings.d.ts +62 -0
  36. package/lib/types/externalChange.d.ts +25 -0
  37. package/lib/types/extract.d.ts +32 -0
  38. package/lib/types/extractionQueue.d.ts +23 -2
  39. package/lib/types/ftsIndex.d.ts +497 -0
  40. package/lib/types/index.d.ts +266 -8
  41. package/lib/types/initialize.d.ts +105 -0
  42. package/lib/types/knowledgeTools.d.ts +239 -0
  43. package/lib/types/localStoragePath.d.ts +6 -0
  44. package/lib/types/localStorageSettings.d.ts +33 -0
  45. package/lib/types/managementTools.d.ts +184 -0
  46. package/lib/types/outlineGuard.d.ts +119 -0
  47. package/lib/types/outlineText.d.ts +134 -0
  48. package/lib/types/protocol.d.ts +34 -0
  49. package/lib/types/scopeAliases.d.ts +20 -0
  50. package/lib/types/vectorIndex.d.ts +113 -0
  51. package/lib/types/views.d.ts +33 -1
  52. package/lib/types/workflowPublish.d.ts +114 -0
  53. package/package.json +7 -7
  54. package/skills/kb-summarize/SKILL.md +158 -0
  55. package/skills/kb-summarize/i18n/zh.json +10 -0
  56. package/skills/kb-vectorize/SKILL.md +57 -0
  57. package//344/270/207/347/233/270AI/344/275/277/347/224/250/346/225/231/347/250/213.html +294 -0
  58. package/lib/types/client/KnowledgeProgressPanel.d.ts +0 -53
@@ -0,0 +1,497 @@
1
+ /**
2
+ * SQLite FTS5 index for Agentic RAG.
3
+ *
4
+ * The index is deliberately a sidecar of the authoritative tree, never inside the
5
+ * caller's view: a view is rebuilt from grants, so an index stored there would be
6
+ * deleted by the next sync. It also must never be reachable through `kb_file_*`.
7
+ *
8
+ * Chunks are tokenized with FTS5's `trigram` tokenizer, which is what makes Chinese
9
+ * text searchable without a word segmenter. Scope columns are applied in SQL before
10
+ * BM25 ordering; the caller's per-path grant check is applied after the top candidate
11
+ * window, so a hit can be dropped but never leak.
12
+ *
13
+ * @module @sparkelf/dsh-knowledge-base/ftsIndex
14
+ */
15
+ import { type ScopeLocale } from './scopeAliases.ts';
16
+ import { type OutlineSection, type OutlineTocEntry } from './outlineText.ts';
17
+ import { type OutlineSaveRejection, type OutlineSectionSubmission, type OutlineSummaryKind } from './outlineGuard.ts';
18
+ /** Prefix used for the sidecar directory when no explicit index dir is configured. */
19
+ export declare const DEFAULT_INDEX_DIR = "/nas/kb/index";
20
+ /** One search hit returned to the model. */
21
+ /** Result of one kb_search call, including whether it had to relax. */
22
+ export interface FtsSearchResult {
23
+ hits: FtsSearchHit[];
24
+ relaxed: boolean;
25
+ queries: string[];
26
+ /** Set when hybrid retrieval ran; absent in pure-FTS answers. */
27
+ retrieval?: 'fts' | 'vector' | 'hybrid';
28
+ /** Provider/vector failure that made the answer fall back to FTS. */
29
+ vectorWarning?: string;
30
+ /** How many vector-only slots the FTS floor reclaimed. */
31
+ floorReplacements?: number;
32
+ /**
33
+ * Summary chunks that matched the query, for navigation only.
34
+ *
35
+ * They deliberately carry no citation token: a summary is generated from the source and
36
+ * must never become the quoted evidence. Follow one with `kb_outline` or a chunk read.
37
+ */
38
+ relatedSections?: FtsRelatedSection[];
39
+ }
40
+ /** One summary chunk returned by the summarised half of the FTS index. */
41
+ export interface FtsRelatedSection {
42
+ docId: number;
43
+ kind: 'doc_summary' | 'section_summary';
44
+ sectionId?: string;
45
+ /** Original source path (the citable document). */
46
+ path: string;
47
+ displayPath?: string;
48
+ /** Chapter/section heading path. */
49
+ sectionPath: string;
50
+ summary: string;
51
+ /** Source chunk ids this summary was extracted from; navigation only. */
52
+ sourceChunkIds: number[];
53
+ score: number;
54
+ }
55
+ export interface FtsChunkBox {
56
+ page: number;
57
+ bbox: string;
58
+ blockIndex?: number;
59
+ occurrence?: number;
60
+ confidence?: number;
61
+ approximate?: boolean;
62
+ }
63
+ /**
64
+ * Collapse provenance boxes to exactly one union rectangle per page.
65
+ *
66
+ * `kb_chunk_boxes` intentionally stores one row per extracted block so provenance stays
67
+ * inspectable, but every reader (search hit, chunk endpoint, PDF viewer) wants the chunk's
68
+ * painted region: one rectangle per page. Unioning at the SQL boundary means a citation never
69
+ * receives a list of paragraph boxes that a client might paint one by one before merging.
70
+ */
71
+ export declare function unionBoxesByPage(boxes: readonly FtsChunkBox[]): FtsChunkBox[];
72
+ export interface FtsSearchHit {
73
+ /** Original document path (docx/xlsx/pdf/txt), never the derived Markdown sibling. */
74
+ path: string;
75
+ /** Localized virtual-root spelling for display; `path` stays canonical. */
76
+ displayPath?: string;
77
+ /** Derived text sibling used internally for snippets/reading. */
78
+ markdownPath: string;
79
+ heading?: string;
80
+ /** Chunk ordinal/paragraph anchor within the source. */
81
+ paragraph?: number;
82
+ /** Zero-based chunk ordinal in the document. */
83
+ chunkOrdinal?: number;
84
+ /** First prose paragraph anchor, for a viewer outline. */
85
+ paragraphText?: string;
86
+ /** Plain-text quote for findController (never Markdown/table syntax). */
87
+ quote?: string;
88
+ /** Query terms found in this chunk. */
89
+ matchedTerms?: string[];
90
+ /** Snippet/sentence containing the query terms. */
91
+ highlightText?: string;
92
+ /** Character ranges of matchedTerms inside highlightText (end-exclusive). */
93
+ highlightRanges?: Array<{
94
+ start: number;
95
+ end: number;
96
+ }>;
97
+ /** Spreadsheet anchors, when known. */
98
+ sheet?: string;
99
+ cell?: string;
100
+ /** Present only for text/Markdown sources; PDF/scan hits carry boxes instead. */
101
+ line?: number;
102
+ lineStart?: number;
103
+ lineEnd?: number;
104
+ snippet: string;
105
+ score: number;
106
+ docId: number;
107
+ chunkId: number;
108
+ /** Stable content-keyed citation id (survives a full reindex). */
109
+ chunkKey?: string;
110
+ /** Stable citation token: kb://doc/<docId>/chunk/<chunkKey or chunkId> */
111
+ citation: string;
112
+ /** All provenance boxes for this chunk, grouped by page by the viewer. */
113
+ boxes?: FtsChunkBox[];
114
+ /** First box's page, kept for convenience. */
115
+ page?: number;
116
+ /** First box bbox, kept for compatibility with earlier consumers. */
117
+ bbox?: string;
118
+ blockIndex?: number;
119
+ occurrence?: number;
120
+ /** True when any box came from a low-confidence OCR pass. */
121
+ approximate?: boolean;
122
+ /** Nearest outline section, when the document has one. */
123
+ sectionId?: string;
124
+ /** Full heading path of that section (`第三章 > 第二十二条`). */
125
+ sectionPath?: string;
126
+ /** Extractive summary of that section; navigation only, never a citation. */
127
+ sectionSummary?: string;
128
+ /** Depth of that section (1 = chapter). */
129
+ sectionLevel?: number;
130
+ /** Extractive document summary; navigation only, never a citation. */
131
+ docSummary?: string;
132
+ /** Which retrieval channel produced this hit in hybrid mode. */
133
+ retrieval?: 'fts' | 'vector' | 'hybrid';
134
+ /** One-based rank in the FTS candidate list. */
135
+ ftsRank?: number;
136
+ /** One-based rank in the vector candidate list. */
137
+ vectorRank?: number;
138
+ /** Weighted reciprocal-rank-fusion score. */
139
+ rrfScore?: number;
140
+ /** Raw BM25 score, when the FTS channel found this chunk. */
141
+ ftsScore?: number;
142
+ /** Raw cosine similarity, when the vector channel found this chunk. */
143
+ vectorScore?: number;
144
+ /** Set when the FTS floor put this hit back into the final window. */
145
+ retrievalReason?: 'fts-floor';
146
+ }
147
+ /** Query-side vector inputs for hybrid retrieval. */
148
+ export interface VectorSearchInput {
149
+ model: string;
150
+ queryVector: readonly number[];
151
+ candidateK: number;
152
+ rrfK: number;
153
+ ftsWeight: number;
154
+ vectorWeight: number;
155
+ /** Share of the corpus that has vectors; scales only the vector contribution. */
156
+ coverage: number;
157
+ }
158
+ /** Counts reported by an index sync/rebuild. */
159
+ export interface FtsIndexStats {
160
+ indexPath: string;
161
+ docs: number;
162
+ chunks: number;
163
+ indexed: number;
164
+ unchanged: number;
165
+ removed: number;
166
+ updatedAt: string;
167
+ }
168
+ /** A caller's scope for index filtering. */
169
+ export interface FtsCallerScope {
170
+ callerId: string;
171
+ /** Department codes the caller may see, including ancestors/descendants as resolved by the Host. */
172
+ departmentCodes: readonly string[];
173
+ /** Global administrators see every scope. */
174
+ global: boolean;
175
+ }
176
+ /** The index path for one deployment. */
177
+ export declare function indexPathFor(indexDir: string | undefined): string;
178
+ /**
179
+ * Refuse an index path inside the browsable/authoritative trees.
180
+ *
181
+ * The index contains unreviewed snippets; putting it under a view would expose it to
182
+ * file tools and would let the next sync delete it as an unlinked file.
183
+ */
184
+ export declare function assertIndexOutsideTrees(indexPath: string, trees: readonly string[]): void;
185
+ /** Derive the scope columns from a view-relative path. */
186
+ export declare function scopeForPath(relPath: string): {
187
+ scope: 'global' | 'department' | 'personal';
188
+ ownerId: string | null;
189
+ departmentCode: string | null;
190
+ };
191
+ /**
192
+ * Split text into structure-aware, line-addressed chunks for retrieval.
193
+ *
194
+ * Chapter/section headings, article headings, Markdown headings and table rows open a new
195
+ * chunk; a heading is never appended to the body that precedes it. Article bodies may split at
196
+ * sentence or list boundaries, but a chunk never crosses a chapter/section heading. The
197
+ * 350–500 CJK character target is a soft packing hint (min 150, max 800); structure wins when
198
+ * the two disagree. The 60-character overlap is added only inside one chapter and never in
199
+ * front of a heading, so a citation quote always begins with its own structure. The index
200
+ * records the first source line of each chunk, so text sources keep an honest `lineStart`.
201
+ */
202
+ export declare function chunkText(text: string, options?: number | {
203
+ target?: number;
204
+ min?: number;
205
+ max?: number;
206
+ overlap?: number;
207
+ }): Array<{
208
+ line: number;
209
+ content: string;
210
+ }>;
211
+ /** Stable citation key for one chunk: heading + normalized content hash. */
212
+ export declare function stableChunkKey(heading: string | null | undefined, content: string): string;
213
+ /**
214
+ * Resolve the original document for one indexed text file.
215
+ *
216
+ * A derived sibling is named `<source>.<ext>.md`; the source is that name with the
217
+ * trailing `.md` removed. A plain `.md`/`.txt` is its own source.
218
+ */
219
+ export declare function sourcePathFor(markdownPath: string): string;
220
+ /** Whether a source path is text that can expose a real line anchor. */
221
+ export declare function isTextSourcePath(sourcePath: string): boolean;
222
+ /** Collapse Markdown/table syntax into plain text suitable for a viewer search. */
223
+ export declare function plainQuoteText(content: string): string;
224
+ /**
225
+ * Pick the most distinctive prose sentence in a chunk. A title/heading is short and
226
+ * often repeated; a body sentence is the useful quote for findController when the
227
+ * caller has no query (`kb_chunk_get`).
228
+ */
229
+ export declare function quoteForContent(content: string, minChars?: number, maxChars?: number): string;
230
+ /** Strip FTS5 `[highlight]` markers/ellipses from a query-centered snippet. */
231
+ export declare function quoteFromSnippet(snippet: string, minChars?: number, maxChars?: number): string;
232
+ /** Split a chunk into plain-text paragraphs for viewer-side whole-chunk boxes. */
233
+ export declare function paragraphsFor(content: string): Array<{
234
+ ordinal: number;
235
+ text: string;
236
+ }>;
237
+ /** Query tokens used by the viewer to draw yellow/orange hit boxes. */
238
+ export declare function queryTerms(query: string): string[];
239
+ /** Locate all term occurrences inside the plain snippet; ranges index highlightText. */
240
+ export declare function highlightRangesFor(text: string, terms: readonly string[]): Array<{
241
+ start: number;
242
+ end: number;
243
+ }>;
244
+ /** Build the query-relevance fields a viewer uses for the second (yellow) layer. */
245
+ export declare function queryHighlights(query: string, snippet: string, content: string): {
246
+ matchedTerms: string[];
247
+ highlightText: string;
248
+ highlightRanges: Array<{
249
+ start: number;
250
+ end: number;
251
+ }>;
252
+ };
253
+ /** Append the query-highlight hints to a citation token for the viewer. */
254
+ export declare function citationWithHighlights(citation: string, highlights: {
255
+ matchedTerms: string[];
256
+ highlightText: string;
257
+ highlightRanges: Array<{
258
+ start: number;
259
+ end: number;
260
+ }>;
261
+ }): string;
262
+ /** Backwards-compatible simple quote for callers that only have content. */
263
+ export declare function quoteFor(content: string, maxChars?: number): string;
264
+ export declare const CHUNKER_VERSION = "structure-v3-keyed";
265
+ /** Rebuild/refresh the FTS index incrementally from the authoritative tree. */
266
+ export declare function syncIndex(options: {
267
+ actualRoot: string;
268
+ indexPath: string;
269
+ now?: () => Date;
270
+ /**
271
+ * Opt-in legacy extractive summaries. The production config default is false: upload
272
+ * and extraction create the TOC/section structure only, and a model-authored summary
273
+ * arrives later through `kb_outline_save`.
274
+ */
275
+ autoGenerateSummaries?: boolean;
276
+ }): Promise<FtsIndexStats>;
277
+ /** Read only the counts, without a rebuild. */
278
+ export declare function indexStats(indexPath: string): Promise<{
279
+ docs: number;
280
+ chunks: number;
281
+ indexPath: string;
282
+ }>;
283
+ /**
284
+ * Search FTS5 and apply the caller's scope in SQL before BM25 ordering.
285
+ *
286
+ * `canRead` is the final path-level grant check. The SQL window is `topK * 4`, so a
287
+ * few unreadable descendants cannot starve the result; the returned list is still
288
+ * capped at `topK`.
289
+ */
290
+ export declare function searchIndex(options: {
291
+ indexPath: string;
292
+ query: string;
293
+ topK: number;
294
+ scope?: string;
295
+ /** Locale for `displayPath`; the canonical `path` is unchanged. */
296
+ locale?: ScopeLocale;
297
+ caller: FtsCallerScope;
298
+ canRead: (relPath: string) => Promise<boolean>;
299
+ /** Present only when vector retrieval is enabled and the query embedded successfully. */
300
+ vector?: VectorSearchInput;
301
+ }): Promise<FtsSearchResult>;
302
+ /** One MinerU block, as extract.ts reports it. */
303
+ export interface ProvenanceBlock {
304
+ page: number;
305
+ bbox: readonly [number, number, number, number];
306
+ text: string;
307
+ type?: string;
308
+ blockIndex?: number;
309
+ occurrence?: number;
310
+ confidence?: number;
311
+ pageWidth?: number;
312
+ pageHeight?: number;
313
+ coordSpace?: 'normalized' | 'points';
314
+ }
315
+ /**
316
+ * Attach MinerU page/bbox provenance to the chunks already indexed for one document.
317
+ *
318
+ * The Markdown and FTS rows stay pure text; bbox lives only in `kb_chunk_boxes`.
319
+ */
320
+ export declare function attachProvenance(options: {
321
+ indexPath: string;
322
+ markdownPath: string;
323
+ sourcePath?: string;
324
+ blocks: readonly ProvenanceBlock[];
325
+ }): Promise<{
326
+ chunks: number;
327
+ boxes: number;
328
+ pages: number;
329
+ }>;
330
+ /** Read one chunk and all of its provenance boxes by stable id. */
331
+ /**
332
+ * Resolve a path (original or derived Markdown sibling) plus an optional 1-based
333
+ * line to the chunk that contains it. The viewer needs this for inline
334
+ * `.docx.md:73` links, whose label is derived text but whose citation must point at
335
+ * the original document's chunk.
336
+ */
337
+ export declare function findChunkForPath(options: {
338
+ indexPath: string;
339
+ path: string;
340
+ line?: number;
341
+ /** The text on the requested markdown line, used when stored line anchors are stale. */
342
+ lineText?: string;
343
+ }): Promise<{
344
+ docId: number;
345
+ chunkId: number;
346
+ } | undefined>;
347
+ /** Resolve a stable chunk_key (or an old numeric id) to the current numeric id. */
348
+ export declare function findChunkId(options: {
349
+ indexPath: string;
350
+ docId: number;
351
+ chunkId: number | string;
352
+ }): Promise<number | undefined>;
353
+ export declare function readChunkProvenance(options: {
354
+ indexPath: string;
355
+ docId: number;
356
+ chunkId: number;
357
+ query?: string;
358
+ }): ReturnType<typeof readChunkProvenanceUnlocked>;
359
+ declare function readChunkProvenanceUnlocked(options: {
360
+ indexPath: string;
361
+ docId: number;
362
+ chunkId: number;
363
+ query?: string;
364
+ }): Promise<{
365
+ docId: number;
366
+ chunkId: number;
367
+ chunkKey?: string;
368
+ path: string;
369
+ sourcePath: string;
370
+ markdownPath: string;
371
+ heading?: string;
372
+ paragraph?: number;
373
+ chunkOrdinal?: number;
374
+ paragraphText?: string;
375
+ quote?: string;
376
+ sheet?: string;
377
+ cell?: string;
378
+ chunkText: string;
379
+ paragraphs: Array<{
380
+ ordinal: number;
381
+ text: string;
382
+ }>;
383
+ snippet: string;
384
+ line?: number;
385
+ lineStart?: number;
386
+ lineEnd?: number;
387
+ boxes: FtsChunkBox[];
388
+ unionBoxes: Array<{
389
+ page: number;
390
+ bbox: string;
391
+ }>;
392
+ matchedTerms?: string[];
393
+ highlightText?: string;
394
+ highlightRanges?: Array<{
395
+ start: number;
396
+ end: number;
397
+ }>;
398
+ } | undefined>;
399
+ export declare function optimizeIndex(indexPath: string): Promise<void>;
400
+ /** Absolute path of one document's generated INDEX.md (outside the agent-visible tree). */
401
+ export declare function outlineFilePath(indexPath: string, docId: number): string;
402
+ /** Write one generated outline beside the SQLite index; never into the source tree. */
403
+ export declare function writeOutlineFile(indexPath: string, docId: number, markdown: string): Promise<string>;
404
+ /** One stored outline, as `kb_outline` returns it. */
405
+ export interface DocumentOutlineRecord {
406
+ docId: number;
407
+ /** Original source path (the citable document). */
408
+ path: string;
409
+ /** Derived Markdown path; internal routing detail. */
410
+ markdownPath: string;
411
+ title: string;
412
+ summary: string;
413
+ summaryVersion?: string;
414
+ /** How the current summary was produced. Missing summaries are `none`. */
415
+ summaryKind: OutlineSummaryKind;
416
+ /** `missing` until an explicit `kb_outline_save` writes a model-authored summary. */
417
+ summaryStatus: 'ready' | 'missing';
418
+ /** Source chunk ids behind the document summary. */
419
+ sourceChunkIds: number[];
420
+ toc: OutlineTocEntry[];
421
+ sections: OutlineSection[];
422
+ /** Absolute path of the generated INDEX.md. */
423
+ indexPath: string;
424
+ }
425
+ /** Read one stored outline without rebuilding it. */
426
+ export declare function readDocumentOutline(options: {
427
+ indexPath: string;
428
+ docId: number;
429
+ }): Promise<DocumentOutlineRecord | undefined>;
430
+ /**
431
+ * Rewrite one document's INDEX.md from the summaries already stored in SQLite.
432
+ *
433
+ * This is deliberately not a summary generator: it reads `kb_docs`/`kb_sections`
434
+ * and re-renders the file, so a renderer-only change (for example adding article
435
+ * headings) never regenerates or rewrites any summary text. Permission is decided
436
+ * by the caller on the source path before this function runs.
437
+ */
438
+ export declare function rewriteDocumentOutline(options: {
439
+ indexPath: string;
440
+ docId: number;
441
+ }): Promise<string | undefined>;
442
+ /**
443
+ * Ensure one document has an outline and an INDEX.md.
444
+ *
445
+ * Upload and extraction only guarantee the navigation structure: TOC rows and section
446
+ * records exist, but summaries stay empty unless `autoGenerate` explicitly asks for the
447
+ * legacy extractive text. A model-written (`llm`) summary is never regenerated here: it is
448
+ * preserved until the source hash changes or `kb_outline_save` replaces it.
449
+ */
450
+ export declare function ensureDocumentOutline(options: {
451
+ indexPath: string;
452
+ docId: number;
453
+ force?: boolean;
454
+ /** Opt-in legacy extractive summaries; production config defaults this to false. */
455
+ autoGenerate?: boolean;
456
+ }): Promise<DocumentOutlineRecord | undefined>;
457
+ /** Statistics for one successful outline save. */
458
+ export interface OutlineSaveStats {
459
+ /** Document summary written. */
460
+ docSummary: boolean;
461
+ /** Section rows replaced (the full heading tree, not only submitted rows). */
462
+ sections: number;
463
+ /** Sections whose non-empty summary was written. */
464
+ sectionSummaries: number;
465
+ /** Key facts written across all sections. */
466
+ keyFacts: number;
467
+ /** Rows inserted into `kb_summary_chunks` (and its FTS mirror). */
468
+ summaryChunks: number;
469
+ /** Generated INDEX.md path. */
470
+ indexPath: string;
471
+ }
472
+ /** Result of `kb_outline_save`: either the write statistics or the refused fields. */
473
+ export interface OutlineSaveResult {
474
+ ok: boolean;
475
+ docId: number;
476
+ summaryVersion?: string;
477
+ summaryKind: 'llm' | 'none';
478
+ written: OutlineSaveStats;
479
+ rejected: OutlineSaveRejection[];
480
+ }
481
+ /**
482
+ * Persist one model-authored summary set.
483
+ *
484
+ * Validation runs before the transaction, so a refused field writes nothing. The write
485
+ * itself replaces `kb_docs.summary`/`toc_json`/`summary_version`/`summary_kind`, every
486
+ * `kb_sections` row, the `kb_summary_chunks` rows and their FTS mirror, then rewrites the
487
+ * document's INDEX.md. Section structure (ids, heading paths, own chunks) is never
488
+ * invented: unknown ids and foreign chunk ids are refused rather than created.
489
+ */
490
+ export declare function saveDocumentOutline(options: {
491
+ indexPath: string;
492
+ docId: number;
493
+ summary: string;
494
+ sections: readonly OutlineSectionSubmission[];
495
+ summaryVersion?: string;
496
+ }): Promise<OutlineSaveResult | undefined>;
497
+ export {};