@dikolab/kbdb 0.2.1 → 0.3.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 (120) hide show
  1. package/README.md +59 -24
  2. package/dist/cli.cjs +462 -73
  3. package/dist/cli.cjs.map +4 -4
  4. package/dist/cli.mjs +454 -65
  5. package/dist/cli.mjs.map +4 -4
  6. package/dist/kbdb-worker.cjs +395 -60
  7. package/dist/kbdb-worker.cjs.map +4 -4
  8. package/dist/mod.cjs +18 -1
  9. package/dist/mod.cjs.map +2 -2
  10. package/dist/mod.mjs +18 -1
  11. package/dist/mod.mjs.map +2 -2
  12. package/dist/src/shared/cli/functions/dispatch-command.function.d.ts +3 -1
  13. package/dist/src/shared/cli/functions/install-default-skills.function.d.ts +12 -0
  14. package/dist/src/shared/cli/functions/run-check.function.d.ts +47 -5
  15. package/dist/src/shared/cli/functions/run-export.function.d.ts +34 -0
  16. package/dist/src/shared/cli/functions/run-gc.function.d.ts +6 -0
  17. package/dist/src/shared/cli/functions/run-learn.function.d.ts +35 -6
  18. package/dist/src/shared/cli/functions/run-recall.function.d.ts +45 -0
  19. package/dist/src/shared/cli/functions/run-search.function.d.ts +52 -15
  20. package/dist/src/shared/cli/index.d.ts +4 -0
  21. package/dist/src/shared/cli/typings/cli-options.interface.d.ts +13 -0
  22. package/dist/src/shared/cli/typings/learn-result.model.d.ts +19 -0
  23. package/dist/src/shared/cli/typings/search-display.model.d.ts +18 -0
  24. package/dist/src/shared/mcp/classes/mcp-server.class.d.ts +8 -0
  25. package/dist/src/shared/mcp/typings/mcp-client.interface.d.ts +43 -0
  26. package/dist/src/shared/version/constants/version.constant.d.ts +1 -1
  27. package/dist/src/shared/wasm-codec/functions/decode-add-section-result.function.d.ts +48 -0
  28. package/dist/src/shared/wasm-codec/functions/decode-scored-results.function.d.ts +58 -0
  29. package/dist/src/shared/wasm-codec/functions/decode-section-records.function.d.ts +33 -0
  30. package/dist/src/shared/wasm-codec/functions/encode-add-section-params.function.d.ts +52 -0
  31. package/dist/src/shared/wasm-codec/index.d.ts +2 -0
  32. package/dist/src/shared/worker-client/classes/worker-client.class.d.ts +16 -0
  33. package/dist/src/shared/worker-client/index.d.ts +1 -1
  34. package/dist/src/shared/worker-client/typings/add-section-result.model.d.ts +29 -15
  35. package/dist/src/shared/worker-client/typings/export-result.model.d.ts +14 -0
  36. package/dist/src/shared/worker-client/typings/gc-result.model.d.ts +6 -0
  37. package/dist/src/shared/worker-client/typings/search-result.model.d.ts +12 -0
  38. package/dist/src/shared/worker-client/typings/section.model.d.ts +16 -0
  39. package/dist/src/shared/worker-daemon/classes/worker-daemon.class.d.ts +35 -0
  40. package/dist/wasm/fs-database/kbdb_fs_database.d.ts +22 -1
  41. package/dist/wasm/fs-database/kbdb_fs_database.js +28 -1
  42. package/dist/wasm/fs-database/kbdb_fs_database_bg.wasm +0 -0
  43. package/dist/wasm/fs-database/kbdb_fs_database_bg.wasm.d.ts +1 -0
  44. package/dist/wasm/kb-worker/kbdb_worker.d.ts +48 -1
  45. package/dist/wasm/kb-worker/kbdb_worker.js +60 -1
  46. package/dist/wasm/kb-worker/kbdb_worker_bg.wasm +0 -0
  47. package/dist/wasm/kb-worker/kbdb_worker_bg.wasm.d.ts +2 -0
  48. package/dist/wasm/query-parser/kbdb_query_parser_bg.wasm +0 -0
  49. package/dist/worker.mjs +396 -60
  50. package/dist/worker.mjs.map +4 -4
  51. package/docs/details/cli.md +102 -15
  52. package/docs/details/library-api.md +55 -6
  53. package/docs/details/mcp-server.md +8 -4
  54. package/docs/details/search-and-ranking.md +63 -5
  55. package/docs/details/storage.md +2 -0
  56. package/docs/goals/cli.md +255 -14
  57. package/docs/goals/database.md +244 -17
  58. package/docs/goals/document.md +1 -1
  59. package/docs/goals/mcp.md +83 -14
  60. package/docs/goals/query-parser.md +98 -4
  61. package/docs/goals/query-result.md +177 -10
  62. package/docs/goals/skills.md +72 -0
  63. package/docs/goals/worker-daemon.md +54 -7
  64. package/docs/modules/rust/fs-database/functions/integrity.md +11 -0
  65. package/docs/modules/rust/fs-database/functions/section.md +23 -6
  66. package/docs/modules/rust/fs-database/module.md +1 -0
  67. package/docs/modules/rust/fs-database/types/section.md +27 -1
  68. package/docs/modules/rust/overview.md +2 -1
  69. package/docs/modules/rust/query-parser/functions/module.md +2 -2
  70. package/docs/modules/rust/query-parser/functions/pipeline.md +44 -6
  71. package/docs/modules/rust/query-parser/module.md +5 -4
  72. package/docs/modules/rust/query-parser/types/keyphrase.md +11 -0
  73. package/docs/modules/rust/query-parser/types/module.md +3 -3
  74. package/docs/modules/typescript/shared/cli/functions.md +68 -3
  75. package/package.json +1 -1
  76. package/src/shared/cli/functions/dispatch-command.function.ts +53 -1
  77. package/src/shared/cli/functions/format-output.function.ts +36 -12
  78. package/src/shared/cli/functions/generate-help.function.ts +56 -1
  79. package/src/shared/cli/functions/install-default-skills.function.ts +296 -0
  80. package/src/shared/cli/functions/parse-args.function.ts +13 -0
  81. package/src/shared/cli/functions/run-check.function.ts +87 -6
  82. package/src/shared/cli/functions/run-export.function.ts +40 -0
  83. package/src/shared/cli/functions/run-gc.function.ts +6 -0
  84. package/src/shared/cli/functions/run-init.function.ts +2 -0
  85. package/src/shared/cli/functions/run-learn.function.ts +68 -16
  86. package/src/shared/cli/functions/run-recall.function.ts +66 -0
  87. package/src/shared/cli/functions/run-search.function.ts +58 -15
  88. package/src/shared/cli/index.ts +4 -0
  89. package/src/shared/cli/typings/cli-options.interface.ts +13 -0
  90. package/src/shared/cli/typings/learn-result.model.ts +16 -0
  91. package/src/shared/cli/typings/search-display.model.ts +18 -0
  92. package/src/shared/mcp/classes/mcp-server.class.ts +30 -1
  93. package/src/shared/mcp/functions/create-tool-definitions.function.ts +46 -5
  94. package/src/shared/mcp/functions/handle-tool-call.function.ts +70 -2
  95. package/src/shared/mcp/typings/mcp-client.interface.ts +45 -0
  96. package/src/shared/version/constants/version.constant.ts +1 -1
  97. package/src/shared/wasm-codec/functions/decode-add-section-result.function.ts +112 -0
  98. package/src/shared/wasm-codec/functions/decode-scored-results.function.ts +86 -0
  99. package/src/shared/wasm-codec/functions/decode-section-records.function.ts +87 -0
  100. package/src/shared/wasm-codec/functions/encode-add-section-params.function.ts +68 -0
  101. package/src/shared/wasm-codec/index.ts +5 -0
  102. package/src/shared/worker-client/classes/worker-client.class.ts +19 -0
  103. package/src/shared/worker-client/index.ts +4 -1
  104. package/src/shared/worker-client/typings/add-section-result.model.ts +30 -15
  105. package/src/shared/worker-client/typings/export-result.model.ts +14 -0
  106. package/src/shared/worker-client/typings/gc-result.model.ts +6 -0
  107. package/src/shared/worker-client/typings/search-result.model.ts +12 -0
  108. package/src/shared/worker-client/typings/section.model.ts +16 -0
  109. package/src/shared/worker-daemon/classes/worker-daemon.class.ts +315 -46
  110. package/src/shared/worker-daemon/functions/create-host-imports.function.ts +19 -4
  111. package/src/shared/worker-daemon/functions/create-ipc-server.function.ts +5 -1
  112. package/src/wasm/fs-database/kbdb_fs_database.d.ts +22 -1
  113. package/src/wasm/fs-database/kbdb_fs_database.js +28 -1
  114. package/src/wasm/fs-database/kbdb_fs_database_bg.wasm +0 -0
  115. package/src/wasm/fs-database/kbdb_fs_database_bg.wasm.d.ts +1 -0
  116. package/src/wasm/kb-worker/kbdb_worker.d.ts +48 -1
  117. package/src/wasm/kb-worker/kbdb_worker.js +60 -1
  118. package/src/wasm/kb-worker/kbdb_worker_bg.wasm +0 -0
  119. package/src/wasm/kb-worker/kbdb_worker_bg.wasm.d.ts +2 -0
  120. package/src/wasm/query-parser/kbdb_query_parser_bg.wasm +0 -0
package/dist/cli.cjs CHANGED
@@ -146,6 +146,19 @@ function parseArgs(argv) {
146
146
  case "--skills":
147
147
  options.skills = (argv[++i] ?? "").split(",");
148
148
  break;
149
+ case "--tags":
150
+ options.tags = (argv[++i] ?? "").split(",");
151
+ break;
152
+ case "--replace":
153
+ options.replace = true;
154
+ break;
155
+ case "--depth":
156
+ case "-d":
157
+ options.depth = Math.min(
158
+ 3,
159
+ Math.max(0, parseInt(argv[++i] ?? "", 10) || 0)
160
+ );
161
+ break;
149
162
  case "--content":
150
163
  case "-c":
151
164
  options.content = true;
@@ -183,9 +196,11 @@ function formatText(paged) {
183
196
  const num = paged.offset + i + 1;
184
197
  const heading = r.heading ?? "(untitled)";
185
198
  const terms = r.matched_terms.join(", ");
186
- return `${String(num)}. [${r.kbid}] ${heading} (score: ${r.score.toFixed(4)})
199
+ const fields = r.matched_fields.join(", ");
200
+ const confidencePct = (r.confidence * 100).toFixed(0);
201
+ return `${String(num)}. [${r.kbid}] ${heading} (score: ${r.score.toFixed(4)}, confidence: ${confidencePct}%)
187
202
  ${r.snippet || r.preview || "(no snippet)"}
188
- type: ${r.type} terms: ${terms}`;
203
+ type: ${r.type} terms: ${terms}` + (fields ? ` fields: ${fields}` : "");
189
204
  }).join("\n\n");
190
205
  const footer = `
191
206
  Showing ${String(start)}-${String(end)} of ${String(paged.total)} results`;
@@ -194,12 +209,32 @@ Use --offset ${String(end)} to see more` : "";
194
209
  return lines + footer + hint;
195
210
  }
196
211
  function formatMcp(paged) {
197
- return JSON.stringify({
198
- type: "text",
199
- text: paged.items.map(
200
- (r) => `[${r.kbid}] ${r.heading ?? "(untitled)"} \u2014 ${r.snippet || r.preview}`
201
- ).join("\n")
202
- });
212
+ const mcpItems = paged.items.map((r) => ({
213
+ kbid: r.kbid,
214
+ heading: r.heading,
215
+ type: r.type,
216
+ docids: r.docids,
217
+ snippet: r.snippet,
218
+ matched_terms: r.matched_terms,
219
+ matched_fields: r.matched_fields,
220
+ created_at: r.created_at
221
+ }));
222
+ return JSON.stringify(
223
+ {
224
+ type: "text",
225
+ text: paged.items.map(
226
+ (r) => `[${r.kbid}] ${r.heading ?? "(untitled)"} \u2014 ${r.snippet || r.preview}`
227
+ ).join("\n"),
228
+ items: mcpItems,
229
+ total: paged.total,
230
+ offset: paged.offset,
231
+ limit: paged.limit,
232
+ has_more: paged.has_more,
233
+ relaxed: paged.relaxed
234
+ },
235
+ null,
236
+ 2
237
+ );
203
238
  }
204
239
 
205
240
  // src/shared/cli/functions/run-search.function.ts
@@ -211,7 +246,8 @@ async function runSearch(client, options) {
211
246
  total: 0,
212
247
  offset: 0,
213
248
  limit: options.limit,
214
- has_more: false
249
+ has_more: false,
250
+ relaxed: false
215
251
  };
216
252
  }
217
253
  const results = await client.search({
@@ -227,17 +263,43 @@ async function runSearch(client, options) {
227
263
  type: r.type,
228
264
  docids: r.docids,
229
265
  score: r.score,
266
+ confidence: r.confidence ?? 0,
230
267
  snippet: r.snippet,
231
268
  matched_terms: r.matched_terms,
269
+ matched_fields: r.matched_fields ?? [],
270
+ created_at: r.created_at ?? "",
232
271
  preview: ""
233
272
  })),
234
273
  total: results.total,
235
274
  offset: results.offset,
236
275
  limit: results.limit,
237
- has_more: results.has_more
276
+ has_more: results.has_more,
277
+ relaxed: results.relaxed ?? false
238
278
  };
239
279
  }
240
280
 
281
+ // src/shared/cli/functions/run-recall.function.ts
282
+ var MAX_RECALL_DEPTH = 3;
283
+ async function runRecall(client, options) {
284
+ if (options.args.length === 0) {
285
+ return { error: "no kbid provided" };
286
+ }
287
+ const depth = Math.min(
288
+ MAX_RECALL_DEPTH,
289
+ Math.max(0, options.depth ?? 0)
290
+ );
291
+ if (options.args.length === 1) {
292
+ return client.recall({ kbid: options.args[0], depth });
293
+ }
294
+ return client.recall({ kbids: options.args, depth });
295
+ }
296
+
297
+ // src/shared/cli/functions/run-export.function.ts
298
+ async function runExport(client, options) {
299
+ const path = options.args[0];
300
+ return client.export({ path });
301
+ }
302
+
241
303
  // src/shared/cli/functions/run-learn.function.ts
242
304
  var import_node_fs = require("node:fs");
243
305
  var import_node_path3 = require("node:path");
@@ -255,14 +317,11 @@ async function runLearn(client, options, io) {
255
317
  content,
256
318
  title: options.title,
257
319
  description: options.description,
258
- docid: options.docid
259
- });
260
- results.push({
261
- kbid: result.kbid,
262
- path: "-",
263
- size: content.length,
264
- skipped: false
320
+ docid: options.docid,
321
+ tags: options.tags,
322
+ replace: options.replace
265
323
  });
324
+ results.push(mapAddSectionResult(result, "-", content.length));
266
325
  continue;
267
326
  }
268
327
  const files = collectFiles(target);
@@ -272,18 +331,30 @@ async function runLearn(client, options, io) {
272
331
  sectionType: "text",
273
332
  content,
274
333
  title: options.title,
275
- docid: options.docid
276
- });
277
- results.push({
278
- kbid: result.kbid,
279
- path: file,
280
- size: content.length,
281
- skipped: false
334
+ description: options.description,
335
+ docid: options.docid,
336
+ tags: options.tags,
337
+ replace: options.replace,
338
+ sourcePath: file
282
339
  });
340
+ results.push(
341
+ mapAddSectionResult(result, file, content.length)
342
+ );
283
343
  }
284
344
  }
285
345
  return results;
286
346
  }
347
+ function mapAddSectionResult(result, path, size) {
348
+ return {
349
+ kbid: result.kbid,
350
+ path,
351
+ size,
352
+ skipped: false,
353
+ superseded: result.superseded ?? null,
354
+ warnings: result.warnings ?? [],
355
+ near_duplicates: result.near_duplicates ?? []
356
+ };
357
+ }
287
358
  function readProcessStdin() {
288
359
  return new Promise((resolve4) => {
289
360
  const chunks = [];
@@ -348,8 +419,26 @@ async function runContent(client, options) {
348
419
  }
349
420
 
350
421
  // src/shared/cli/functions/run-check.function.ts
422
+ function parseDivergentContent(encoded) {
423
+ if (!encoded) return [];
424
+ return encoded.split(";").filter((segment) => segment.includes(":")).map((segment) => {
425
+ const colonIdx = segment.indexOf(":");
426
+ const sourceKey = segment.slice(0, colonIdx);
427
+ const kbids = segment.slice(colonIdx + 1).split(",").filter((k) => k.length > 0);
428
+ return { sourceKey, kbids };
429
+ }).filter((entry) => entry.kbids.length > 0);
430
+ }
351
431
  async function runCheck(client, _options) {
352
- return client.integrityCheck();
432
+ const raw = await client.integrityCheck();
433
+ return {
434
+ ok: raw.ok,
435
+ errors: raw.errors,
436
+ sections_checked: raw.sections_checked,
437
+ documents_checked: raw.documents_checked,
438
+ references_checked: raw.references_checked,
439
+ elapsed_ms: raw.elapsed_ms,
440
+ divergent_content: parseDivergentContent(raw.divergent_content)
441
+ };
353
442
  }
354
443
 
355
444
  // src/shared/cli/functions/run-gc.function.ts
@@ -372,8 +461,109 @@ function findDb(dbPath) {
372
461
  }
373
462
 
374
463
  // src/shared/cli/functions/run-init.function.ts
464
+ var import_node_fs4 = require("node:fs");
465
+ var import_node_path6 = require("node:path");
466
+
467
+ // src/shared/cli/functions/install-default-skills.function.ts
468
+ var import_node_crypto = require("node:crypto");
375
469
  var import_node_fs3 = require("node:fs");
376
470
  var import_node_path5 = require("node:path");
471
+ var BASE32_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
472
+ function base32Encode(bytes) {
473
+ let result = "";
474
+ let bits = 0;
475
+ let value = 0;
476
+ for (const byte of bytes) {
477
+ value = value << 8 | byte;
478
+ bits += 8;
479
+ while (bits >= 5) {
480
+ result += BASE32_ALPHABET[value >>> bits - 5 & 31];
481
+ bits -= 5;
482
+ }
483
+ }
484
+ if (bits > 0) {
485
+ result += BASE32_ALPHABET[value << 5 - bits & 31];
486
+ }
487
+ return result.toLowerCase();
488
+ }
489
+ function computeSectionKbid(mimeType, content) {
490
+ const contentBytes = Buffer.from(content, "utf-8");
491
+ const header = `section ${mimeType} ${contentBytes.length.toString()}\0`;
492
+ const headerBytes = Buffer.from(header, "utf-8");
493
+ const input = Buffer.concat([headerBytes, contentBytes]);
494
+ const hash = (0, import_node_crypto.createHash)("sha256").update(input).digest();
495
+ const truncated = new Uint8Array(hash.buffer, 0, 16);
496
+ return base32Encode(truncated);
497
+ }
498
+ function escapeToml(s) {
499
+ return s.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
500
+ }
501
+ function serializeSkillSection(kbid, title, description, content) {
502
+ const size = Buffer.byteLength(content, "utf-8");
503
+ const header = [
504
+ `kbid = "${escapeToml(kbid)}"`,
505
+ `section_type = "text/markdown"`,
506
+ `type_name = "skill"`,
507
+ `title = "${escapeToml(title)}"`,
508
+ `description = "${escapeToml(description)}"`,
509
+ `size = ${size.toString()}`,
510
+ `token_count = 0`,
511
+ `heading_token_count = 0`,
512
+ `body_token_count = 0`,
513
+ `code_token_count = 0`,
514
+ `created_at = 0`,
515
+ `checksum = "${escapeToml(kbid)}"`,
516
+ ""
517
+ ].join("\n");
518
+ return `${header}
519
+ ---
520
+ ${content}`;
521
+ }
522
+ function installSkill(sectionsDir, skill) {
523
+ const content = JSON.stringify({
524
+ name: skill.name,
525
+ description: skill.description,
526
+ arguments: [],
527
+ body: skill.body
528
+ });
529
+ const kbid = computeSectionKbid("text/markdown", content);
530
+ const prefix = kbid.slice(0, 2);
531
+ const shardDir = (0, import_node_path5.join)(sectionsDir, prefix);
532
+ const filePath = (0, import_node_path5.join)(shardDir, `${kbid}.section`);
533
+ if ((0, import_node_fs3.existsSync)(filePath)) {
534
+ return;
535
+ }
536
+ (0, import_node_fs3.mkdirSync)(shardDir, { recursive: true });
537
+ const fileContent = serializeSkillSection(
538
+ kbid,
539
+ skill.name,
540
+ skill.description,
541
+ content
542
+ );
543
+ (0, import_node_fs3.writeFileSync)(filePath, fileContent, "utf-8");
544
+ }
545
+ var QUERY_GUIDANCE_BODY = 'This knowledge base uses lexical search (stemmed BM25F), not semantic/vector search. Follow these guidelines for effective retrieval:\n\n1. Issue MULTIPLE short keyword queries rather than one long natural-language question. Each query should target 2-3 specific terms.\n\n2. EXPAND SYNONYMS yourself. "login" will not match "sign-in" unless both terms appear in the content. Try alternative phrasings: search for "login", then "sign-in", then "authentication".\n\n3. USE BOOLEAN AND PHRASE SYNTAX:\n - Exact phrases: "error handling"\n - Boolean AND: auth AND token\n - Boolean OR: cache OR redis\n - Exclusion: auth -oauth\n\n4. LEAN ON HEADINGS. Headings carry 2x weight in ranking. Search for terms that are likely in section titles.\n\n5. STEMMING IS AUTOMATIC. "connections" matches "connect", "running" matches "run". You do not need to try inflected forms.\n\n6. USE RECALL AFTER SEARCH. After finding relevant sections with search, use recall with increasing depth (0-3) to progressively expand context: depth 0 = full content, depth 1 = parent documents + back-references, depth 2 = siblings + forward references, depth 3 = referenced sections.\n\n7. CODE IDENTIFIERS ARE SPLIT. camelCase and snake_case names are decomposed: getUserById becomes searchable as "get", "user", "by", "id".\n\n8. PAGINATION. Default limit is 20 results. Use offset for additional pages if total > limit.';
546
+ var MEMORY_PROTOCOL_BODY = "Follow these guidelines to keep the knowledge base accurate and useful:\n\n1. STORE DURABLE FACTS: decisions, architecture choices, configuration values, corrections to prior knowledge, API contracts, and resolved incidents. These are the facts that matter weeks later.\n\n2. DO NOT STORE: transient conversation context, debugging output, ephemeral task state, raw log dumps, or information that changes every session. The brain should contain knowledge, not activity logs.\n\n3. SEARCH BEFORE STORING. Always search the knowledge base before adding new content. If a section already covers the topic, use learn with --replace to update it rather than creating a duplicate.\n\n4. USE --replace FOR CORRECTIONS. When a fact changes (e.g., rate limit updated from 100 to 200 req/min), re-learn the corrected content with --replace so the old version is superseded. Without --replace, both the old and new versions coexist with equal ranking.\n\n5. ATTACH TAGS for scoping. Use tags like project name, domain, or topic (e.g., --tags auth,api,v2) so queries can be filtered by scope later.\n\n6. PREFER RECENT RESULTS. When search returns multiple sections covering the same topic, prefer the one with the most recent created_at timestamp -- it is more likely to reflect the current state.\n\n7. USE RECALL AFTER SEARCH. After finding relevant sections, use recall with increasing depth (0-3) to expand context progressively before making decisions.\n\n8. INCLUDE HEADINGS. Always provide a descriptive title when storing content. Headings carry 2x ranking weight -- omitting them significantly reduces searchability.";
547
+ var DEFAULT_SKILLS = [
548
+ {
549
+ name: "query-guidance",
550
+ description: "How to effectively search this knowledge base. Read this before issuing search queries.",
551
+ body: QUERY_GUIDANCE_BODY
552
+ },
553
+ {
554
+ name: "memory-protocol",
555
+ description: "Guidelines for what to store in this knowledge base and when. Read this before using the learn tool.",
556
+ body: MEMORY_PROTOCOL_BODY
557
+ }
558
+ ];
559
+ function installDefaultSkills(dbDir) {
560
+ const sectionsDir = (0, import_node_path5.join)(dbDir, "sections");
561
+ for (const skill of DEFAULT_SKILLS) {
562
+ installSkill(sectionsDir, skill);
563
+ }
564
+ }
565
+
566
+ // src/shared/cli/functions/run-init.function.ts
377
567
  var DB_FORMAT_VERSION = 1;
378
568
  var CATALOG_TOML = `version = ${DB_FORMAT_VERSION.toString()}
379
569
  section_count = 0
@@ -391,21 +581,21 @@ var GITIGNORE = [
391
581
  ""
392
582
  ].join("\n");
393
583
  function createScaffold(dbDir) {
394
- (0, import_node_fs3.mkdirSync)(dbDir, { recursive: true });
395
- (0, import_node_fs3.mkdirSync)((0, import_node_path5.join)(dbDir, "sections"));
396
- (0, import_node_fs3.mkdirSync)((0, import_node_path5.join)(dbDir, "documents"));
397
- (0, import_node_fs3.writeFileSync)((0, import_node_path5.join)(dbDir, "catalog.toml"), CATALOG_TOML);
398
- (0, import_node_fs3.writeFileSync)((0, import_node_path5.join)(dbDir, "corpus.bin"), "");
399
- (0, import_node_fs3.writeFileSync)((0, import_node_path5.join)(dbDir, "inverted.idx"), "");
400
- (0, import_node_fs3.writeFileSync)((0, import_node_path5.join)(dbDir, "positional.idx"), "");
401
- (0, import_node_fs3.writeFileSync)((0, import_node_path5.join)(dbDir, "worker.toml"), WORKER_TOML);
402
- (0, import_node_fs3.writeFileSync)((0, import_node_path5.join)(dbDir, ".gitignore"), GITIGNORE);
584
+ (0, import_node_fs4.mkdirSync)(dbDir, { recursive: true });
585
+ (0, import_node_fs4.mkdirSync)((0, import_node_path6.join)(dbDir, "sections"));
586
+ (0, import_node_fs4.mkdirSync)((0, import_node_path6.join)(dbDir, "documents"));
587
+ (0, import_node_fs4.writeFileSync)((0, import_node_path6.join)(dbDir, "catalog.toml"), CATALOG_TOML);
588
+ (0, import_node_fs4.writeFileSync)((0, import_node_path6.join)(dbDir, "corpus.bin"), "");
589
+ (0, import_node_fs4.writeFileSync)((0, import_node_path6.join)(dbDir, "inverted.idx"), "");
590
+ (0, import_node_fs4.writeFileSync)((0, import_node_path6.join)(dbDir, "positional.idx"), "");
591
+ (0, import_node_fs4.writeFileSync)((0, import_node_path6.join)(dbDir, "worker.toml"), WORKER_TOML);
592
+ (0, import_node_fs4.writeFileSync)((0, import_node_path6.join)(dbDir, ".gitignore"), GITIGNORE);
403
593
  }
404
594
  function runInit(options) {
405
- const basePath = options.dbPath ? (0, import_node_path5.resolve)(options.dbPath) : process.cwd();
406
- const dbDir = (0, import_node_path5.join)(basePath, ".kbdb");
595
+ const basePath = options.dbPath ? (0, import_node_path6.resolve)(options.dbPath) : process.cwd();
596
+ const dbDir = (0, import_node_path6.join)(basePath, ".kbdb");
407
597
  try {
408
- if ((0, import_node_fs3.existsSync)(dbDir)) {
598
+ if ((0, import_node_fs4.existsSync)(dbDir)) {
409
599
  return {
410
600
  ok: false,
411
601
  targetPath: dbDir,
@@ -413,6 +603,7 @@ function runInit(options) {
413
603
  };
414
604
  }
415
605
  createScaffold(dbDir);
606
+ installDefaultSkills(dbDir);
416
607
  return { ok: true, targetPath: dbDir };
417
608
  } catch (err) {
418
609
  const errorMessage = err instanceof Error ? err.message : String(err);
@@ -770,6 +961,8 @@ async function dispatchCommand(client, options, io) {
770
961
  return handleUnlearn(client, options);
771
962
  case "search":
772
963
  return handleSearch(client, options);
964
+ case "recall":
965
+ return handleRecall(client, options);
773
966
  case "content":
774
967
  return handleContent(client, options);
775
968
  case "check":
@@ -780,6 +973,8 @@ async function dispatchCommand(client, options, io) {
780
973
  return handleRebuild(client, options);
781
974
  case "status":
782
975
  return handleStatus(client, options);
976
+ case "export":
977
+ return handleExport(client, options);
783
978
  case "db-init":
784
979
  return handleDbInit(options);
785
980
  case "db-migrate":
@@ -868,6 +1063,40 @@ async function handleSearch(client, options) {
868
1063
  };
869
1064
  }
870
1065
  }
1066
+ async function handleRecall(client, options) {
1067
+ try {
1068
+ const result = await runRecall(client, options);
1069
+ if ("error" in result) {
1070
+ return {
1071
+ error: `error: ${result.error}`,
1072
+ exitCode: 1
1073
+ };
1074
+ }
1075
+ return {
1076
+ output: JSON.stringify(result, null, 2),
1077
+ exitCode: 0
1078
+ };
1079
+ } catch (err) {
1080
+ return {
1081
+ error: `recall failed: ${errorToMessage(err)}`,
1082
+ exitCode: 1
1083
+ };
1084
+ }
1085
+ }
1086
+ async function handleExport(client, options) {
1087
+ try {
1088
+ const result = await runExport(client, options);
1089
+ return {
1090
+ output: JSON.stringify(result, null, 2),
1091
+ exitCode: 0
1092
+ };
1093
+ } catch (err) {
1094
+ return {
1095
+ error: `export failed: ${errorToMessage(err)}`,
1096
+ exitCode: 1
1097
+ };
1098
+ }
1099
+ }
871
1100
  async function handleContent(client, options) {
872
1101
  try {
873
1102
  const result = await runContent(client, options);
@@ -968,18 +1197,20 @@ async function hydrateSearchPreviews(client, paged) {
968
1197
  }
969
1198
 
970
1199
  // src/shared/version/constants/version.constant.ts
971
- var VERSION = "0.2.1";
1200
+ var VERSION = "0.2.2";
972
1201
 
973
1202
  // src/shared/cli/functions/generate-help.function.ts
974
1203
  var KNOWN_COMMANDS = /* @__PURE__ */ new Set([
975
1204
  "learn",
976
1205
  "unlearn",
977
1206
  "search",
1207
+ "recall",
978
1208
  "content",
979
1209
  "check",
980
1210
  "gc",
981
1211
  "rebuild",
982
1212
  "status",
1213
+ "export",
983
1214
  "mcp",
984
1215
  "db",
985
1216
  "skill",
@@ -1042,6 +1273,7 @@ Knowledge Base:
1042
1273
  learn Import content from files/stdin
1043
1274
  unlearn Remove sections by kbid
1044
1275
  search Query the knowledge base
1276
+ recall Retrieve section context by kbid
1045
1277
  content Compose rendered content from ids
1046
1278
 
1047
1279
  Database:
@@ -1054,6 +1286,7 @@ Maintenance:
1054
1286
  sections
1055
1287
  rebuild Reconstruct all index files
1056
1288
  status Show database status
1289
+ export Snapshot database for backup
1057
1290
 
1058
1291
  Skills:
1059
1292
  skill learn Create a new skill definition
@@ -1126,14 +1359,18 @@ Options:
1126
1359
  --title <title> Section title
1127
1360
  --description <d> Section description
1128
1361
  --docid <docid> Document ID to group under
1362
+ --tags <t,...> Comma-separated tags
1363
+ --replace Supersede existing section by source key
1129
1364
  --db <path> Path to .kbdb directory
1130
1365
 
1131
1366
  Output:
1132
- JSON array of { kbid, path, size, skipped }
1367
+ JSON array of { kbid, path, size, skipped,
1368
+ superseded, warnings, near_duplicates }
1133
1369
 
1134
1370
  Examples:
1135
1371
  kbdb learn README.md
1136
1372
  kbdb learn ./docs/
1373
+ kbdb learn --tags auth,api --replace doc.md
1137
1374
  cat notes.txt | kbdb learn -
1138
1375
  `,
1139
1376
  unlearn: `kbdb unlearn - Remove sections by kbid
@@ -1174,6 +1411,25 @@ Examples:
1174
1411
  kbdb search "machine learning"
1175
1412
  kbdb search --limit 5 --format text query
1176
1413
  kbdb search --mode documents --content term
1414
+ `,
1415
+ recall: `kbdb recall - Retrieve section context by kbid
1416
+
1417
+ Usage: kbdb recall [options] <kbid>...
1418
+
1419
+ Arguments:
1420
+ <kbid> 26-character knowledge base identifier
1421
+
1422
+ Options:
1423
+ --depth <n>, -d Expansion depth (0-3, default: 0)
1424
+ --tags <t,...> Filter by tags
1425
+ --db <path> Path to .kbdb directory
1426
+
1427
+ Output:
1428
+ JSON RecallResult or RecallResult[]
1429
+
1430
+ Examples:
1431
+ kbdb recall abcdef1234567890abcdef1234
1432
+ kbdb recall kbid1 --depth 2
1177
1433
  `,
1178
1434
  content: `kbdb content - Compose rendered content from ids
1179
1435
 
@@ -1247,6 +1503,25 @@ Output:
1247
1503
  Examples:
1248
1504
  kbdb status
1249
1505
  kbdb status --db /path/to/project
1506
+ `,
1507
+ export: `kbdb export - Snapshot database for backup
1508
+
1509
+ Usage: kbdb export [options] [<path>]
1510
+
1511
+ Arguments:
1512
+ <path> Output directory path
1513
+ (default: ./kbdb-export/)
1514
+
1515
+ Options:
1516
+ --db <path> Path to .kbdb directory
1517
+
1518
+ Output:
1519
+ JSON { path, sections_exported, documents_exported }
1520
+
1521
+ Examples:
1522
+ kbdb export
1523
+ kbdb export ./my-backup
1524
+ kbdb export --db /path/to/project
1250
1525
  `,
1251
1526
  mcp: `kbdb mcp - Start MCP server mode (stdio)
1252
1527
 
@@ -1917,6 +2192,23 @@ var WorkerClient = class {
1917
2192
  docType
1918
2193
  });
1919
2194
  }
2195
+ /**
2196
+ * Exports the database to a target directory on disk.
2197
+ *
2198
+ * Copies section files, document manifests, and `catalog.toml`
2199
+ * to `params.path`. Index files are not exported because they
2200
+ * are derivable from the section and document data.
2201
+ *
2202
+ * @param params - optional export parameters
2203
+ * @param params.path - target directory path; defaults to
2204
+ * `./kbdb-export/` when not provided
2205
+ * @returns export report with the resolved path and file counts
2206
+ */
2207
+ async export(params) {
2208
+ return this.call("export", {
2209
+ targetPath: params?.path || void 0
2210
+ });
2211
+ }
1920
2212
  /**
1921
2213
  * Sends a JSON-RPC 2.0 request and awaits the response.
1922
2214
  *
@@ -1974,17 +2266,17 @@ var WorkerClient = class {
1974
2266
  };
1975
2267
 
1976
2268
  // src/shared/worker-client/functions/create-worker-client.function.ts
1977
- var import_node_fs7 = require("node:fs");
1978
- var import_node_path10 = require("node:path");
2269
+ var import_node_fs8 = require("node:fs");
2270
+ var import_node_path11 = require("node:path");
1979
2271
  var import_node_os4 = require("node:os");
1980
2272
 
1981
2273
  // src/shared/worker-client/functions/resolve-db-path.function.ts
1982
- var import_node_fs4 = require("node:fs");
1983
- var import_node_path6 = require("node:path");
2274
+ var import_node_fs5 = require("node:fs");
2275
+ var import_node_path7 = require("node:path");
1984
2276
  function resolveDbPath(targetDir) {
1985
- const base = targetDir ? (0, import_node_path6.resolve)(targetDir) : process.cwd();
1986
- const dbPath = (0, import_node_path6.join)(base, ".kbdb");
1987
- if (!(0, import_node_fs4.existsSync)(dbPath)) {
2277
+ const base = targetDir ? (0, import_node_path7.resolve)(targetDir) : process.cwd();
2278
+ const dbPath = (0, import_node_path7.join)(base, ".kbdb");
2279
+ if (!(0, import_node_fs5.existsSync)(dbPath)) {
1988
2280
  throw new Error(`database not found: ${dbPath}`);
1989
2281
  }
1990
2282
  return dbPath;
@@ -2005,8 +2297,8 @@ async function computeContextId(absolutePath) {
2005
2297
  }
2006
2298
 
2007
2299
  // src/shared/worker-client/functions/discover-daemon.function.ts
2008
- var import_node_fs5 = require("node:fs");
2009
- var import_node_path7 = require("node:path");
2300
+ var import_node_fs6 = require("node:fs");
2301
+ var import_node_path8 = require("node:path");
2010
2302
  var import_node_os = require("node:os");
2011
2303
 
2012
2304
  // src/shared/platform/functions/is-process-alive.function.ts
@@ -2021,21 +2313,21 @@ function isProcessAlive(pid) {
2021
2313
 
2022
2314
  // src/shared/worker-client/functions/discover-daemon.function.ts
2023
2315
  function discoverDaemon(contextId) {
2024
- const pidPath = (0, import_node_path7.join)((0, import_node_os.tmpdir)(), `kbdb-${contextId}.pid`);
2025
- if (!(0, import_node_fs5.existsSync)(pidPath)) return null;
2026
- const pidStr = (0, import_node_fs5.readFileSync)(pidPath, "utf-8").trim();
2316
+ const pidPath = (0, import_node_path8.join)((0, import_node_os.tmpdir)(), `kbdb-${contextId}.pid`);
2317
+ if (!(0, import_node_fs6.existsSync)(pidPath)) return null;
2318
+ const pidStr = (0, import_node_fs6.readFileSync)(pidPath, "utf-8").trim();
2027
2319
  const pid = parseInt(pidStr, 10);
2028
2320
  if (isNaN(pid) || !isProcessAlive(pid)) {
2029
2321
  try {
2030
- (0, import_node_fs5.unlinkSync)(pidPath);
2322
+ (0, import_node_fs6.unlinkSync)(pidPath);
2031
2323
  } catch {
2032
2324
  }
2033
2325
  return null;
2034
2326
  }
2035
- const socketPath = (0, import_node_path7.join)((0, import_node_os.tmpdir)(), `kbdb-${contextId}.sock`);
2036
- const crashPath = (0, import_node_path7.join)((0, import_node_os.tmpdir)(), `kbdb-${contextId}.crash`);
2327
+ const socketPath = (0, import_node_path8.join)((0, import_node_os.tmpdir)(), `kbdb-${contextId}.sock`);
2328
+ const crashPath = (0, import_node_path8.join)((0, import_node_os.tmpdir)(), `kbdb-${contextId}.crash`);
2037
2329
  try {
2038
- (0, import_node_fs5.unlinkSync)(crashPath);
2330
+ (0, import_node_fs6.unlinkSync)(crashPath);
2039
2331
  } catch {
2040
2332
  }
2041
2333
  return { pid, socketPath };
@@ -2059,23 +2351,23 @@ function spawnDaemon(contextPath, runtime2, workerScript2) {
2059
2351
  }
2060
2352
 
2061
2353
  // src/shared/worker-client/functions/wait-for-daemon.function.ts
2062
- var import_node_fs6 = require("node:fs");
2063
- var import_node_path8 = require("node:path");
2354
+ var import_node_fs7 = require("node:fs");
2355
+ var import_node_path9 = require("node:path");
2064
2356
  var import_node_os2 = require("node:os");
2065
2357
  var DEFAULT_TIMEOUT_MS = 1e4;
2066
2358
  var POLL_INTERVAL_MS = 100;
2067
2359
  async function waitForDaemon(contextId, timeoutMs = DEFAULT_TIMEOUT_MS) {
2068
- const pidPath = (0, import_node_path8.join)((0, import_node_os2.tmpdir)(), `kbdb-${contextId}.pid`);
2360
+ const pidPath = (0, import_node_path9.join)((0, import_node_os2.tmpdir)(), `kbdb-${contextId}.pid`);
2069
2361
  const start = Date.now();
2070
2362
  while (Date.now() - start < timeoutMs) {
2071
- if ((0, import_node_fs6.existsSync)(pidPath)) return true;
2363
+ if ((0, import_node_fs7.existsSync)(pidPath)) return true;
2072
2364
  await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
2073
2365
  }
2074
2366
  return false;
2075
2367
  }
2076
2368
 
2077
2369
  // src/shared/platform/functions/get-ipc-path.function.ts
2078
- var import_node_path9 = require("node:path");
2370
+ var import_node_path10 = require("node:path");
2079
2371
 
2080
2372
  // src/shared/platform/functions/get-tmpdir.function.ts
2081
2373
  var import_node_os3 = require("node:os");
@@ -2088,7 +2380,7 @@ function getIpcPath(ctx) {
2088
2380
  if (process.platform === "win32") {
2089
2381
  return `\\\\.\\pipe\\kbdb-${ctx}`;
2090
2382
  }
2091
- return (0, import_node_path9.join)(getTmpdir(), `kbdb-${ctx}.sock`);
2383
+ return (0, import_node_path10.join)(getTmpdir(), `kbdb-${ctx}.sock`);
2092
2384
  }
2093
2385
 
2094
2386
  // src/shared/worker-client/functions/create-worker-client.function.ts
@@ -2113,14 +2405,14 @@ async function createWorkerClient(options) {
2113
2405
  return client;
2114
2406
  }
2115
2407
  function readCrashReason(contextId) {
2116
- const crashPath = (0, import_node_path10.join)((0, import_node_os4.tmpdir)(), `kbdb-${contextId}.crash`);
2117
- if (!(0, import_node_fs7.existsSync)(crashPath)) {
2408
+ const crashPath = (0, import_node_path11.join)((0, import_node_os4.tmpdir)(), `kbdb-${contextId}.crash`);
2409
+ if (!(0, import_node_fs8.existsSync)(crashPath)) {
2118
2410
  return "daemon failed to start (no details available)";
2119
2411
  }
2120
2412
  try {
2121
- const raw = (0, import_node_fs7.readFileSync)(crashPath, "utf-8");
2413
+ const raw = (0, import_node_fs8.readFileSync)(crashPath, "utf-8");
2122
2414
  const crash = JSON.parse(raw);
2123
- (0, import_node_fs7.unlinkSync)(crashPath);
2415
+ (0, import_node_fs8.unlinkSync)(crashPath);
2124
2416
  return crash.error ?? "unknown error (crash log empty)";
2125
2417
  } catch {
2126
2418
  return "daemon failed to start (crash log unreadable)";
@@ -2135,7 +2427,7 @@ function createToolDefinitions() {
2135
2427
  return [
2136
2428
  {
2137
2429
  name: "search",
2138
- description: "Search the knowledge base. Returns ranked results with headings and snippets for relevance evaluation. Use `recall` to get full content of specific results.",
2430
+ description: "Query the knowledge base by keywords. Returns ranked results with heading, snippet, and matched terms. Use multiple short keyword queries for best coverage. Use recall to get full content.",
2139
2431
  inputSchema: {
2140
2432
  type: "object",
2141
2433
  properties: {
@@ -2169,7 +2461,7 @@ function createToolDefinitions() {
2169
2461
  },
2170
2462
  {
2171
2463
  name: "learn",
2172
- description: "Add a text section to the knowledge base.",
2464
+ description: "Import content into the knowledge base. Use for durable facts, decisions, and corrections. Use replace when updating existing knowledge. Attach tags for scoping.",
2173
2465
  inputSchema: {
2174
2466
  type: "object",
2175
2467
  properties: {
@@ -2192,6 +2484,15 @@ function createToolDefinitions() {
2192
2484
  docid: {
2193
2485
  type: "string",
2194
2486
  description: "Document ID to group under"
2487
+ },
2488
+ tags: {
2489
+ type: "array",
2490
+ items: { type: "string" },
2491
+ description: "Tags for scoping and filtering"
2492
+ },
2493
+ replace: {
2494
+ type: "boolean",
2495
+ description: "Replace existing section with same source key (supersession)"
2195
2496
  }
2196
2497
  },
2197
2498
  required: ["content"]
@@ -2360,6 +2661,26 @@ function createToolDefinitions() {
2360
2661
  openWorldHint: false
2361
2662
  }
2362
2663
  },
2664
+ {
2665
+ name: "export",
2666
+ description: "Export the database as a self-contained directory snapshot (sections, documents, catalog -- no indexes).",
2667
+ inputSchema: {
2668
+ type: "object",
2669
+ properties: {
2670
+ path: {
2671
+ type: "string",
2672
+ description: "Export destination path (default: ./kbdb-export/)"
2673
+ }
2674
+ }
2675
+ },
2676
+ annotations: {
2677
+ title: "Export Snapshot",
2678
+ readOnlyHint: false,
2679
+ destructiveHint: false,
2680
+ idempotentHint: true,
2681
+ openWorldHint: false
2682
+ }
2683
+ },
2363
2684
  {
2364
2685
  name: "skill-learn",
2365
2686
  description: "Create or update a skill definition. Skills are reusable AI agent capabilities with template placeholders.",
@@ -2739,7 +3060,7 @@ async function handleAgentDelete(client, args) {
2739
3060
  }
2740
3061
 
2741
3062
  // src/shared/mcp/functions/handle-recall.function.ts
2742
- async function handleRecall(client, args) {
3063
+ async function handleRecall2(client, args) {
2743
3064
  const kbid = typeof args["kbid"] === "string" ? args["kbid"] : void 0;
2744
3065
  const kbids = Array.isArray(args["kbids"]) && args["kbids"].every((item) => typeof item === "string") ? args["kbids"] : void 0;
2745
3066
  if (!kbid && !kbids) {
@@ -2769,6 +3090,15 @@ async function handleRecall(client, args) {
2769
3090
  function asString2(value, fallback) {
2770
3091
  return typeof value === "string" ? value : fallback;
2771
3092
  }
3093
+ function asBooleanOrUndefined(value) {
3094
+ return typeof value === "boolean" ? value : void 0;
3095
+ }
3096
+ function asStringArrayOrUndefined(value) {
3097
+ if (Array.isArray(value) && value.every((item) => typeof item === "string")) {
3098
+ return value;
3099
+ }
3100
+ return void 0;
3101
+ }
2772
3102
  function asStringArray2(value) {
2773
3103
  if (Array.isArray(value) && value.every((item) => typeof item === "string")) {
2774
3104
  return value;
@@ -2787,7 +3117,7 @@ async function handleToolCall(client, name, args) {
2787
3117
  case "retrieve":
2788
3118
  return await handleRetrieve(client, args);
2789
3119
  case "recall":
2790
- return await handleRecall(client, args);
3120
+ return await handleRecall2(client, args);
2791
3121
  case "status":
2792
3122
  return await handleStatus2(client);
2793
3123
  case "content":
@@ -2798,6 +3128,8 @@ async function handleToolCall(client, name, args) {
2798
3128
  return await handleGc2(client);
2799
3129
  case "rebuild":
2800
3130
  return await handleRebuild2(client);
3131
+ case "export":
3132
+ return await handleExport2(client, args);
2801
3133
  case "skill-learn":
2802
3134
  return await handleSkillLearn(client, args);
2803
3135
  case "skill-list":
@@ -2846,7 +3178,13 @@ async function handleSearch2(client, args) {
2846
3178
  });
2847
3179
  const stripped = {
2848
3180
  ...results,
2849
- items: results.items.map(({ score: _score, ...rest }) => rest)
3181
+ items: results.items.map(
3182
+ ({
3183
+ score: _score,
3184
+ confidence: _confidence,
3185
+ ...rest
3186
+ }) => rest
3187
+ )
2850
3188
  };
2851
3189
  return {
2852
3190
  content: [
@@ -2862,6 +3200,8 @@ async function handleLearn2(client, args) {
2862
3200
  const title = typeof args["title"] === "string" ? args["title"] : void 0;
2863
3201
  const description = typeof args["description"] === "string" ? args["description"] : void 0;
2864
3202
  const docid = typeof args["docid"] === "string" ? args["docid"] : void 0;
3203
+ const tags = asStringArrayOrUndefined(args["tags"]);
3204
+ const replace = asBooleanOrUndefined(args["replace"]);
2865
3205
  if (sectionType.startsWith("image/") && !title) {
2866
3206
  return {
2867
3207
  content: [
@@ -2878,13 +3218,26 @@ async function handleLearn2(client, args) {
2878
3218
  content: asString2(args["content"], ""),
2879
3219
  title,
2880
3220
  description,
2881
- docid
3221
+ docid,
3222
+ tags,
3223
+ replace
2882
3224
  });
3225
+ const normalized = {
3226
+ kbid: result.kbid,
3227
+ indexed_terms_count: result.indexed_terms_count ?? 0,
3228
+ elapsed_ms: result.elapsed_ms ?? 0,
3229
+ superseded: result.superseded ?? null,
3230
+ warnings: result.warnings ?? [],
3231
+ near_duplicates: (result.near_duplicates ?? []).map((d) => ({
3232
+ kbid: d.kbid,
3233
+ similarity: d.similarity
3234
+ }))
3235
+ };
2883
3236
  return {
2884
3237
  content: [
2885
3238
  {
2886
3239
  type: "text",
2887
- text: JSON.stringify(result)
3240
+ text: JSON.stringify(normalized)
2888
3241
  }
2889
3242
  ]
2890
3243
  };
@@ -3125,6 +3478,20 @@ async function handleSkillDelete(client, args) {
3125
3478
  ]
3126
3479
  };
3127
3480
  }
3481
+ async function handleExport2(client, args) {
3482
+ const path = typeof args["path"] === "string" ? args["path"] : void 0;
3483
+ const result = await client.export(
3484
+ path !== void 0 ? { path } : {}
3485
+ );
3486
+ return {
3487
+ content: [
3488
+ {
3489
+ type: "text",
3490
+ text: JSON.stringify(result)
3491
+ }
3492
+ ]
3493
+ };
3494
+ }
3128
3495
 
3129
3496
  // src/shared/mcp/functions/handle-resource-read.function.ts
3130
3497
  async function handleResourceRead(client, uri) {
@@ -3496,8 +3863,9 @@ var McpServer = class {
3496
3863
  const request = parsed;
3497
3864
  if (request.id === void 0) {
3498
3865
  if (request.method === "notifications/initialized" || request.method === "notifications/cancelled") {
3499
- if (request.method === "notifications/initialized") {
3866
+ if (request.method === "notifications/initialized" && !this.initialized) {
3500
3867
  this.initialized = true;
3868
+ void this.runStartupHealthCheck();
3501
3869
  }
3502
3870
  }
3503
3871
  return null;
@@ -3690,6 +4058,27 @@ var McpServer = class {
3690
4058
  const json = JSON.stringify(response);
3691
4059
  process.stdout.write(json + "\n");
3692
4060
  }
4061
+ /**
4062
+ * Runs an integrity check immediately after the MCP handshake
4063
+ * completes. Emits a `notifications/message` warning when the
4064
+ * check detects issues so the connected agent is aware before
4065
+ * making requests. The server continues operating regardless of
4066
+ * the result.
4067
+ */
4068
+ async runStartupHealthCheck() {
4069
+ try {
4070
+ const report = await this.client.integrityCheck();
4071
+ if (!report.ok) {
4072
+ const summary = report.errors.map((e) => `${e.type}: ${e.entity} -- ${e.detail}`).join("; ");
4073
+ this.notify("notifications/message", {
4074
+ level: "warning",
4075
+ logger: "kbdb",
4076
+ data: `Database integrity issues detected: ${summary}`
4077
+ });
4078
+ }
4079
+ } catch {
4080
+ }
4081
+ }
3693
4082
  };
3694
4083
 
3695
4084
  // src/shared/mcp/functions/create-mcp-server.function.ts