@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.mjs CHANGED
@@ -99,6 +99,19 @@ function parseArgs(argv) {
99
99
  case "--skills":
100
100
  options.skills = (argv[++i] ?? "").split(",");
101
101
  break;
102
+ case "--tags":
103
+ options.tags = (argv[++i] ?? "").split(",");
104
+ break;
105
+ case "--replace":
106
+ options.replace = true;
107
+ break;
108
+ case "--depth":
109
+ case "-d":
110
+ options.depth = Math.min(
111
+ 3,
112
+ Math.max(0, parseInt(argv[++i] ?? "", 10) || 0)
113
+ );
114
+ break;
102
115
  case "--content":
103
116
  case "-c":
104
117
  options.content = true;
@@ -136,9 +149,11 @@ function formatText(paged) {
136
149
  const num = paged.offset + i + 1;
137
150
  const heading = r.heading ?? "(untitled)";
138
151
  const terms = r.matched_terms.join(", ");
139
- return `${String(num)}. [${r.kbid}] ${heading} (score: ${r.score.toFixed(4)})
152
+ const fields = r.matched_fields.join(", ");
153
+ const confidencePct = (r.confidence * 100).toFixed(0);
154
+ return `${String(num)}. [${r.kbid}] ${heading} (score: ${r.score.toFixed(4)}, confidence: ${confidencePct}%)
140
155
  ${r.snippet || r.preview || "(no snippet)"}
141
- type: ${r.type} terms: ${terms}`;
156
+ type: ${r.type} terms: ${terms}` + (fields ? ` fields: ${fields}` : "");
142
157
  }).join("\n\n");
143
158
  const footer = `
144
159
  Showing ${String(start)}-${String(end)} of ${String(paged.total)} results`;
@@ -147,12 +162,32 @@ Use --offset ${String(end)} to see more` : "";
147
162
  return lines + footer + hint;
148
163
  }
149
164
  function formatMcp(paged) {
150
- return JSON.stringify({
151
- type: "text",
152
- text: paged.items.map(
153
- (r) => `[${r.kbid}] ${r.heading ?? "(untitled)"} \u2014 ${r.snippet || r.preview}`
154
- ).join("\n")
155
- });
165
+ const mcpItems = paged.items.map((r) => ({
166
+ kbid: r.kbid,
167
+ heading: r.heading,
168
+ type: r.type,
169
+ docids: r.docids,
170
+ snippet: r.snippet,
171
+ matched_terms: r.matched_terms,
172
+ matched_fields: r.matched_fields,
173
+ created_at: r.created_at
174
+ }));
175
+ return JSON.stringify(
176
+ {
177
+ type: "text",
178
+ text: paged.items.map(
179
+ (r) => `[${r.kbid}] ${r.heading ?? "(untitled)"} \u2014 ${r.snippet || r.preview}`
180
+ ).join("\n"),
181
+ items: mcpItems,
182
+ total: paged.total,
183
+ offset: paged.offset,
184
+ limit: paged.limit,
185
+ has_more: paged.has_more,
186
+ relaxed: paged.relaxed
187
+ },
188
+ null,
189
+ 2
190
+ );
156
191
  }
157
192
 
158
193
  // src/shared/cli/functions/run-search.function.ts
@@ -164,7 +199,8 @@ async function runSearch(client, options) {
164
199
  total: 0,
165
200
  offset: 0,
166
201
  limit: options.limit,
167
- has_more: false
202
+ has_more: false,
203
+ relaxed: false
168
204
  };
169
205
  }
170
206
  const results = await client.search({
@@ -180,17 +216,43 @@ async function runSearch(client, options) {
180
216
  type: r.type,
181
217
  docids: r.docids,
182
218
  score: r.score,
219
+ confidence: r.confidence ?? 0,
183
220
  snippet: r.snippet,
184
221
  matched_terms: r.matched_terms,
222
+ matched_fields: r.matched_fields ?? [],
223
+ created_at: r.created_at ?? "",
185
224
  preview: ""
186
225
  })),
187
226
  total: results.total,
188
227
  offset: results.offset,
189
228
  limit: results.limit,
190
- has_more: results.has_more
229
+ has_more: results.has_more,
230
+ relaxed: results.relaxed ?? false
191
231
  };
192
232
  }
193
233
 
234
+ // src/shared/cli/functions/run-recall.function.ts
235
+ var MAX_RECALL_DEPTH = 3;
236
+ async function runRecall(client, options) {
237
+ if (options.args.length === 0) {
238
+ return { error: "no kbid provided" };
239
+ }
240
+ const depth = Math.min(
241
+ MAX_RECALL_DEPTH,
242
+ Math.max(0, options.depth ?? 0)
243
+ );
244
+ if (options.args.length === 1) {
245
+ return client.recall({ kbid: options.args[0], depth });
246
+ }
247
+ return client.recall({ kbids: options.args, depth });
248
+ }
249
+
250
+ // src/shared/cli/functions/run-export.function.ts
251
+ async function runExport(client, options) {
252
+ const path = options.args[0];
253
+ return client.export({ path });
254
+ }
255
+
194
256
  // src/shared/cli/functions/run-learn.function.ts
195
257
  import { readFileSync, readdirSync, statSync } from "node:fs";
196
258
  import { basename, extname, join } from "node:path";
@@ -208,14 +270,11 @@ async function runLearn(client, options, io) {
208
270
  content,
209
271
  title: options.title,
210
272
  description: options.description,
211
- docid: options.docid
212
- });
213
- results.push({
214
- kbid: result.kbid,
215
- path: "-",
216
- size: content.length,
217
- skipped: false
273
+ docid: options.docid,
274
+ tags: options.tags,
275
+ replace: options.replace
218
276
  });
277
+ results.push(mapAddSectionResult(result, "-", content.length));
219
278
  continue;
220
279
  }
221
280
  const files = collectFiles(target);
@@ -225,18 +284,30 @@ async function runLearn(client, options, io) {
225
284
  sectionType: "text",
226
285
  content,
227
286
  title: options.title,
228
- docid: options.docid
229
- });
230
- results.push({
231
- kbid: result.kbid,
232
- path: file,
233
- size: content.length,
234
- skipped: false
287
+ description: options.description,
288
+ docid: options.docid,
289
+ tags: options.tags,
290
+ replace: options.replace,
291
+ sourcePath: file
235
292
  });
293
+ results.push(
294
+ mapAddSectionResult(result, file, content.length)
295
+ );
236
296
  }
237
297
  }
238
298
  return results;
239
299
  }
300
+ function mapAddSectionResult(result, path, size) {
301
+ return {
302
+ kbid: result.kbid,
303
+ path,
304
+ size,
305
+ skipped: false,
306
+ superseded: result.superseded ?? null,
307
+ warnings: result.warnings ?? [],
308
+ near_duplicates: result.near_duplicates ?? []
309
+ };
310
+ }
240
311
  function readProcessStdin() {
241
312
  return new Promise((resolve4) => {
242
313
  const chunks = [];
@@ -301,8 +372,26 @@ async function runContent(client, options) {
301
372
  }
302
373
 
303
374
  // src/shared/cli/functions/run-check.function.ts
375
+ function parseDivergentContent(encoded) {
376
+ if (!encoded) return [];
377
+ return encoded.split(";").filter((segment) => segment.includes(":")).map((segment) => {
378
+ const colonIdx = segment.indexOf(":");
379
+ const sourceKey = segment.slice(0, colonIdx);
380
+ const kbids = segment.slice(colonIdx + 1).split(",").filter((k) => k.length > 0);
381
+ return { sourceKey, kbids };
382
+ }).filter((entry) => entry.kbids.length > 0);
383
+ }
304
384
  async function runCheck(client, _options) {
305
- return client.integrityCheck();
385
+ const raw = await client.integrityCheck();
386
+ return {
387
+ ok: raw.ok,
388
+ errors: raw.errors,
389
+ sections_checked: raw.sections_checked,
390
+ documents_checked: raw.documents_checked,
391
+ references_checked: raw.references_checked,
392
+ elapsed_ms: raw.elapsed_ms,
393
+ divergent_content: parseDivergentContent(raw.divergent_content)
394
+ };
306
395
  }
307
396
 
308
397
  // src/shared/cli/functions/run-gc.function.ts
@@ -325,8 +414,109 @@ function findDb(dbPath) {
325
414
  }
326
415
 
327
416
  // src/shared/cli/functions/run-init.function.ts
417
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "node:fs";
418
+ import { join as join4, resolve as resolve2 } from "node:path";
419
+
420
+ // src/shared/cli/functions/install-default-skills.function.ts
421
+ import { createHash } from "node:crypto";
328
422
  import { existsSync as existsSync2, mkdirSync, writeFileSync } from "node:fs";
329
- import { join as join3, resolve as resolve2 } from "node:path";
423
+ import { join as join3 } from "node:path";
424
+ var BASE32_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
425
+ function base32Encode(bytes) {
426
+ let result = "";
427
+ let bits = 0;
428
+ let value = 0;
429
+ for (const byte of bytes) {
430
+ value = value << 8 | byte;
431
+ bits += 8;
432
+ while (bits >= 5) {
433
+ result += BASE32_ALPHABET[value >>> bits - 5 & 31];
434
+ bits -= 5;
435
+ }
436
+ }
437
+ if (bits > 0) {
438
+ result += BASE32_ALPHABET[value << 5 - bits & 31];
439
+ }
440
+ return result.toLowerCase();
441
+ }
442
+ function computeSectionKbid(mimeType, content) {
443
+ const contentBytes = Buffer.from(content, "utf-8");
444
+ const header = `section ${mimeType} ${contentBytes.length.toString()}\0`;
445
+ const headerBytes = Buffer.from(header, "utf-8");
446
+ const input = Buffer.concat([headerBytes, contentBytes]);
447
+ const hash = createHash("sha256").update(input).digest();
448
+ const truncated = new Uint8Array(hash.buffer, 0, 16);
449
+ return base32Encode(truncated);
450
+ }
451
+ function escapeToml(s) {
452
+ return s.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
453
+ }
454
+ function serializeSkillSection(kbid, title, description, content) {
455
+ const size = Buffer.byteLength(content, "utf-8");
456
+ const header = [
457
+ `kbid = "${escapeToml(kbid)}"`,
458
+ `section_type = "text/markdown"`,
459
+ `type_name = "skill"`,
460
+ `title = "${escapeToml(title)}"`,
461
+ `description = "${escapeToml(description)}"`,
462
+ `size = ${size.toString()}`,
463
+ `token_count = 0`,
464
+ `heading_token_count = 0`,
465
+ `body_token_count = 0`,
466
+ `code_token_count = 0`,
467
+ `created_at = 0`,
468
+ `checksum = "${escapeToml(kbid)}"`,
469
+ ""
470
+ ].join("\n");
471
+ return `${header}
472
+ ---
473
+ ${content}`;
474
+ }
475
+ function installSkill(sectionsDir, skill) {
476
+ const content = JSON.stringify({
477
+ name: skill.name,
478
+ description: skill.description,
479
+ arguments: [],
480
+ body: skill.body
481
+ });
482
+ const kbid = computeSectionKbid("text/markdown", content);
483
+ const prefix = kbid.slice(0, 2);
484
+ const shardDir = join3(sectionsDir, prefix);
485
+ const filePath = join3(shardDir, `${kbid}.section`);
486
+ if (existsSync2(filePath)) {
487
+ return;
488
+ }
489
+ mkdirSync(shardDir, { recursive: true });
490
+ const fileContent = serializeSkillSection(
491
+ kbid,
492
+ skill.name,
493
+ skill.description,
494
+ content
495
+ );
496
+ writeFileSync(filePath, fileContent, "utf-8");
497
+ }
498
+ 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.';
499
+ 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.";
500
+ var DEFAULT_SKILLS = [
501
+ {
502
+ name: "query-guidance",
503
+ description: "How to effectively search this knowledge base. Read this before issuing search queries.",
504
+ body: QUERY_GUIDANCE_BODY
505
+ },
506
+ {
507
+ name: "memory-protocol",
508
+ description: "Guidelines for what to store in this knowledge base and when. Read this before using the learn tool.",
509
+ body: MEMORY_PROTOCOL_BODY
510
+ }
511
+ ];
512
+ function installDefaultSkills(dbDir) {
513
+ const sectionsDir = join3(dbDir, "sections");
514
+ for (const skill of DEFAULT_SKILLS) {
515
+ installSkill(sectionsDir, skill);
516
+ }
517
+ }
518
+
519
+ // src/shared/cli/functions/run-init.function.ts
330
520
  var DB_FORMAT_VERSION = 1;
331
521
  var CATALOG_TOML = `version = ${DB_FORMAT_VERSION.toString()}
332
522
  section_count = 0
@@ -344,21 +534,21 @@ var GITIGNORE = [
344
534
  ""
345
535
  ].join("\n");
346
536
  function createScaffold(dbDir) {
347
- mkdirSync(dbDir, { recursive: true });
348
- mkdirSync(join3(dbDir, "sections"));
349
- mkdirSync(join3(dbDir, "documents"));
350
- writeFileSync(join3(dbDir, "catalog.toml"), CATALOG_TOML);
351
- writeFileSync(join3(dbDir, "corpus.bin"), "");
352
- writeFileSync(join3(dbDir, "inverted.idx"), "");
353
- writeFileSync(join3(dbDir, "positional.idx"), "");
354
- writeFileSync(join3(dbDir, "worker.toml"), WORKER_TOML);
355
- writeFileSync(join3(dbDir, ".gitignore"), GITIGNORE);
537
+ mkdirSync2(dbDir, { recursive: true });
538
+ mkdirSync2(join4(dbDir, "sections"));
539
+ mkdirSync2(join4(dbDir, "documents"));
540
+ writeFileSync2(join4(dbDir, "catalog.toml"), CATALOG_TOML);
541
+ writeFileSync2(join4(dbDir, "corpus.bin"), "");
542
+ writeFileSync2(join4(dbDir, "inverted.idx"), "");
543
+ writeFileSync2(join4(dbDir, "positional.idx"), "");
544
+ writeFileSync2(join4(dbDir, "worker.toml"), WORKER_TOML);
545
+ writeFileSync2(join4(dbDir, ".gitignore"), GITIGNORE);
356
546
  }
357
547
  function runInit(options) {
358
548
  const basePath = options.dbPath ? resolve2(options.dbPath) : process.cwd();
359
- const dbDir = join3(basePath, ".kbdb");
549
+ const dbDir = join4(basePath, ".kbdb");
360
550
  try {
361
- if (existsSync2(dbDir)) {
551
+ if (existsSync3(dbDir)) {
362
552
  return {
363
553
  ok: false,
364
554
  targetPath: dbDir,
@@ -366,6 +556,7 @@ function runInit(options) {
366
556
  };
367
557
  }
368
558
  createScaffold(dbDir);
559
+ installDefaultSkills(dbDir);
369
560
  return { ok: true, targetPath: dbDir };
370
561
  } catch (err) {
371
562
  const errorMessage = err instanceof Error ? err.message : String(err);
@@ -723,6 +914,8 @@ async function dispatchCommand(client, options, io) {
723
914
  return handleUnlearn(client, options);
724
915
  case "search":
725
916
  return handleSearch(client, options);
917
+ case "recall":
918
+ return handleRecall(client, options);
726
919
  case "content":
727
920
  return handleContent(client, options);
728
921
  case "check":
@@ -733,6 +926,8 @@ async function dispatchCommand(client, options, io) {
733
926
  return handleRebuild(client, options);
734
927
  case "status":
735
928
  return handleStatus(client, options);
929
+ case "export":
930
+ return handleExport(client, options);
736
931
  case "db-init":
737
932
  return handleDbInit(options);
738
933
  case "db-migrate":
@@ -821,6 +1016,40 @@ async function handleSearch(client, options) {
821
1016
  };
822
1017
  }
823
1018
  }
1019
+ async function handleRecall(client, options) {
1020
+ try {
1021
+ const result = await runRecall(client, options);
1022
+ if ("error" in result) {
1023
+ return {
1024
+ error: `error: ${result.error}`,
1025
+ exitCode: 1
1026
+ };
1027
+ }
1028
+ return {
1029
+ output: JSON.stringify(result, null, 2),
1030
+ exitCode: 0
1031
+ };
1032
+ } catch (err) {
1033
+ return {
1034
+ error: `recall failed: ${errorToMessage(err)}`,
1035
+ exitCode: 1
1036
+ };
1037
+ }
1038
+ }
1039
+ async function handleExport(client, options) {
1040
+ try {
1041
+ const result = await runExport(client, options);
1042
+ return {
1043
+ output: JSON.stringify(result, null, 2),
1044
+ exitCode: 0
1045
+ };
1046
+ } catch (err) {
1047
+ return {
1048
+ error: `export failed: ${errorToMessage(err)}`,
1049
+ exitCode: 1
1050
+ };
1051
+ }
1052
+ }
824
1053
  async function handleContent(client, options) {
825
1054
  try {
826
1055
  const result = await runContent(client, options);
@@ -921,18 +1150,20 @@ async function hydrateSearchPreviews(client, paged) {
921
1150
  }
922
1151
 
923
1152
  // src/shared/version/constants/version.constant.ts
924
- var VERSION = "0.2.1";
1153
+ var VERSION = "0.2.2";
925
1154
 
926
1155
  // src/shared/cli/functions/generate-help.function.ts
927
1156
  var KNOWN_COMMANDS = /* @__PURE__ */ new Set([
928
1157
  "learn",
929
1158
  "unlearn",
930
1159
  "search",
1160
+ "recall",
931
1161
  "content",
932
1162
  "check",
933
1163
  "gc",
934
1164
  "rebuild",
935
1165
  "status",
1166
+ "export",
936
1167
  "mcp",
937
1168
  "db",
938
1169
  "skill",
@@ -995,6 +1226,7 @@ Knowledge Base:
995
1226
  learn Import content from files/stdin
996
1227
  unlearn Remove sections by kbid
997
1228
  search Query the knowledge base
1229
+ recall Retrieve section context by kbid
998
1230
  content Compose rendered content from ids
999
1231
 
1000
1232
  Database:
@@ -1007,6 +1239,7 @@ Maintenance:
1007
1239
  sections
1008
1240
  rebuild Reconstruct all index files
1009
1241
  status Show database status
1242
+ export Snapshot database for backup
1010
1243
 
1011
1244
  Skills:
1012
1245
  skill learn Create a new skill definition
@@ -1079,14 +1312,18 @@ Options:
1079
1312
  --title <title> Section title
1080
1313
  --description <d> Section description
1081
1314
  --docid <docid> Document ID to group under
1315
+ --tags <t,...> Comma-separated tags
1316
+ --replace Supersede existing section by source key
1082
1317
  --db <path> Path to .kbdb directory
1083
1318
 
1084
1319
  Output:
1085
- JSON array of { kbid, path, size, skipped }
1320
+ JSON array of { kbid, path, size, skipped,
1321
+ superseded, warnings, near_duplicates }
1086
1322
 
1087
1323
  Examples:
1088
1324
  kbdb learn README.md
1089
1325
  kbdb learn ./docs/
1326
+ kbdb learn --tags auth,api --replace doc.md
1090
1327
  cat notes.txt | kbdb learn -
1091
1328
  `,
1092
1329
  unlearn: `kbdb unlearn - Remove sections by kbid
@@ -1127,6 +1364,25 @@ Examples:
1127
1364
  kbdb search "machine learning"
1128
1365
  kbdb search --limit 5 --format text query
1129
1366
  kbdb search --mode documents --content term
1367
+ `,
1368
+ recall: `kbdb recall - Retrieve section context by kbid
1369
+
1370
+ Usage: kbdb recall [options] <kbid>...
1371
+
1372
+ Arguments:
1373
+ <kbid> 26-character knowledge base identifier
1374
+
1375
+ Options:
1376
+ --depth <n>, -d Expansion depth (0-3, default: 0)
1377
+ --tags <t,...> Filter by tags
1378
+ --db <path> Path to .kbdb directory
1379
+
1380
+ Output:
1381
+ JSON RecallResult or RecallResult[]
1382
+
1383
+ Examples:
1384
+ kbdb recall abcdef1234567890abcdef1234
1385
+ kbdb recall kbid1 --depth 2
1130
1386
  `,
1131
1387
  content: `kbdb content - Compose rendered content from ids
1132
1388
 
@@ -1200,6 +1456,25 @@ Output:
1200
1456
  Examples:
1201
1457
  kbdb status
1202
1458
  kbdb status --db /path/to/project
1459
+ `,
1460
+ export: `kbdb export - Snapshot database for backup
1461
+
1462
+ Usage: kbdb export [options] [<path>]
1463
+
1464
+ Arguments:
1465
+ <path> Output directory path
1466
+ (default: ./kbdb-export/)
1467
+
1468
+ Options:
1469
+ --db <path> Path to .kbdb directory
1470
+
1471
+ Output:
1472
+ JSON { path, sections_exported, documents_exported }
1473
+
1474
+ Examples:
1475
+ kbdb export
1476
+ kbdb export ./my-backup
1477
+ kbdb export --db /path/to/project
1203
1478
  `,
1204
1479
  mcp: `kbdb mcp - Start MCP server mode (stdio)
1205
1480
 
@@ -1870,6 +2145,23 @@ var WorkerClient = class {
1870
2145
  docType
1871
2146
  });
1872
2147
  }
2148
+ /**
2149
+ * Exports the database to a target directory on disk.
2150
+ *
2151
+ * Copies section files, document manifests, and `catalog.toml`
2152
+ * to `params.path`. Index files are not exported because they
2153
+ * are derivable from the section and document data.
2154
+ *
2155
+ * @param params - optional export parameters
2156
+ * @param params.path - target directory path; defaults to
2157
+ * `./kbdb-export/` when not provided
2158
+ * @returns export report with the resolved path and file counts
2159
+ */
2160
+ async export(params) {
2161
+ return this.call("export", {
2162
+ targetPath: params?.path || void 0
2163
+ });
2164
+ }
1873
2165
  /**
1874
2166
  * Sends a JSON-RPC 2.0 request and awaits the response.
1875
2167
  *
@@ -1927,17 +2219,17 @@ var WorkerClient = class {
1927
2219
  };
1928
2220
 
1929
2221
  // src/shared/worker-client/functions/create-worker-client.function.ts
1930
- import { existsSync as existsSync6, readFileSync as readFileSync3, unlinkSync as unlinkSync2 } from "node:fs";
1931
- import { join as join7 } from "node:path";
2222
+ import { existsSync as existsSync7, readFileSync as readFileSync3, unlinkSync as unlinkSync2 } from "node:fs";
2223
+ import { join as join8 } from "node:path";
1932
2224
  import { tmpdir as tmpdir3 } from "node:os";
1933
2225
 
1934
2226
  // src/shared/worker-client/functions/resolve-db-path.function.ts
1935
- import { existsSync as existsSync3 } from "node:fs";
1936
- import { join as join4, resolve as resolve3 } from "node:path";
2227
+ import { existsSync as existsSync4 } from "node:fs";
2228
+ import { join as join5, resolve as resolve3 } from "node:path";
1937
2229
  function resolveDbPath(targetDir) {
1938
2230
  const base = targetDir ? resolve3(targetDir) : process.cwd();
1939
- const dbPath = join4(base, ".kbdb");
1940
- if (!existsSync3(dbPath)) {
2231
+ const dbPath = join5(base, ".kbdb");
2232
+ if (!existsSync4(dbPath)) {
1941
2233
  throw new Error(`database not found: ${dbPath}`);
1942
2234
  }
1943
2235
  return dbPath;
@@ -1950,8 +2242,8 @@ async function computeContextId(absolutePath) {
1950
2242
  }
1951
2243
 
1952
2244
  // src/shared/worker-client/functions/discover-daemon.function.ts
1953
- import { existsSync as existsSync4, readFileSync as readFileSync2, unlinkSync } from "node:fs";
1954
- import { join as join5 } from "node:path";
2245
+ import { existsSync as existsSync5, readFileSync as readFileSync2, unlinkSync } from "node:fs";
2246
+ import { join as join6 } from "node:path";
1955
2247
  import { tmpdir } from "node:os";
1956
2248
 
1957
2249
  // src/shared/platform/functions/is-process-alive.function.ts
@@ -1966,8 +2258,8 @@ function isProcessAlive(pid) {
1966
2258
 
1967
2259
  // src/shared/worker-client/functions/discover-daemon.function.ts
1968
2260
  function discoverDaemon(contextId) {
1969
- const pidPath = join5(tmpdir(), `kbdb-${contextId}.pid`);
1970
- if (!existsSync4(pidPath)) return null;
2261
+ const pidPath = join6(tmpdir(), `kbdb-${contextId}.pid`);
2262
+ if (!existsSync5(pidPath)) return null;
1971
2263
  const pidStr = readFileSync2(pidPath, "utf-8").trim();
1972
2264
  const pid = parseInt(pidStr, 10);
1973
2265
  if (isNaN(pid) || !isProcessAlive(pid)) {
@@ -1977,8 +2269,8 @@ function discoverDaemon(contextId) {
1977
2269
  }
1978
2270
  return null;
1979
2271
  }
1980
- const socketPath = join5(tmpdir(), `kbdb-${contextId}.sock`);
1981
- const crashPath = join5(tmpdir(), `kbdb-${contextId}.crash`);
2272
+ const socketPath = join6(tmpdir(), `kbdb-${contextId}.sock`);
2273
+ const crashPath = join6(tmpdir(), `kbdb-${contextId}.crash`);
1982
2274
  try {
1983
2275
  unlinkSync(crashPath);
1984
2276
  } catch {
@@ -2004,16 +2296,16 @@ function spawnDaemon(contextPath, runtime2, workerScript2) {
2004
2296
  }
2005
2297
 
2006
2298
  // src/shared/worker-client/functions/wait-for-daemon.function.ts
2007
- import { existsSync as existsSync5 } from "node:fs";
2008
- import { join as join6 } from "node:path";
2299
+ import { existsSync as existsSync6 } from "node:fs";
2300
+ import { join as join7 } from "node:path";
2009
2301
  import { tmpdir as tmpdir2 } from "node:os";
2010
2302
  var DEFAULT_TIMEOUT_MS = 1e4;
2011
2303
  var POLL_INTERVAL_MS = 100;
2012
2304
  async function waitForDaemon(contextId, timeoutMs = DEFAULT_TIMEOUT_MS) {
2013
- const pidPath = join6(tmpdir2(), `kbdb-${contextId}.pid`);
2305
+ const pidPath = join7(tmpdir2(), `kbdb-${contextId}.pid`);
2014
2306
  const start = Date.now();
2015
2307
  while (Date.now() - start < timeoutMs) {
2016
- if (existsSync5(pidPath)) return true;
2308
+ if (existsSync6(pidPath)) return true;
2017
2309
  await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
2018
2310
  }
2019
2311
  return false;
@@ -2041,8 +2333,8 @@ async function createWorkerClient(options) {
2041
2333
  return client;
2042
2334
  }
2043
2335
  function readCrashReason(contextId) {
2044
- const crashPath = join7(tmpdir3(), `kbdb-${contextId}.crash`);
2045
- if (!existsSync6(crashPath)) {
2336
+ const crashPath = join8(tmpdir3(), `kbdb-${contextId}.crash`);
2337
+ if (!existsSync7(crashPath)) {
2046
2338
  return "daemon failed to start (no details available)";
2047
2339
  }
2048
2340
  try {
@@ -2063,7 +2355,7 @@ function createToolDefinitions() {
2063
2355
  return [
2064
2356
  {
2065
2357
  name: "search",
2066
- description: "Search the knowledge base. Returns ranked results with headings and snippets for relevance evaluation. Use `recall` to get full content of specific results.",
2358
+ 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.",
2067
2359
  inputSchema: {
2068
2360
  type: "object",
2069
2361
  properties: {
@@ -2097,7 +2389,7 @@ function createToolDefinitions() {
2097
2389
  },
2098
2390
  {
2099
2391
  name: "learn",
2100
- description: "Add a text section to the knowledge base.",
2392
+ description: "Import content into the knowledge base. Use for durable facts, decisions, and corrections. Use replace when updating existing knowledge. Attach tags for scoping.",
2101
2393
  inputSchema: {
2102
2394
  type: "object",
2103
2395
  properties: {
@@ -2120,6 +2412,15 @@ function createToolDefinitions() {
2120
2412
  docid: {
2121
2413
  type: "string",
2122
2414
  description: "Document ID to group under"
2415
+ },
2416
+ tags: {
2417
+ type: "array",
2418
+ items: { type: "string" },
2419
+ description: "Tags for scoping and filtering"
2420
+ },
2421
+ replace: {
2422
+ type: "boolean",
2423
+ description: "Replace existing section with same source key (supersession)"
2123
2424
  }
2124
2425
  },
2125
2426
  required: ["content"]
@@ -2288,6 +2589,26 @@ function createToolDefinitions() {
2288
2589
  openWorldHint: false
2289
2590
  }
2290
2591
  },
2592
+ {
2593
+ name: "export",
2594
+ description: "Export the database as a self-contained directory snapshot (sections, documents, catalog -- no indexes).",
2595
+ inputSchema: {
2596
+ type: "object",
2597
+ properties: {
2598
+ path: {
2599
+ type: "string",
2600
+ description: "Export destination path (default: ./kbdb-export/)"
2601
+ }
2602
+ }
2603
+ },
2604
+ annotations: {
2605
+ title: "Export Snapshot",
2606
+ readOnlyHint: false,
2607
+ destructiveHint: false,
2608
+ idempotentHint: true,
2609
+ openWorldHint: false
2610
+ }
2611
+ },
2291
2612
  {
2292
2613
  name: "skill-learn",
2293
2614
  description: "Create or update a skill definition. Skills are reusable AI agent capabilities with template placeholders.",
@@ -2667,7 +2988,7 @@ async function handleAgentDelete(client, args) {
2667
2988
  }
2668
2989
 
2669
2990
  // src/shared/mcp/functions/handle-recall.function.ts
2670
- async function handleRecall(client, args) {
2991
+ async function handleRecall2(client, args) {
2671
2992
  const kbid = typeof args["kbid"] === "string" ? args["kbid"] : void 0;
2672
2993
  const kbids = Array.isArray(args["kbids"]) && args["kbids"].every((item) => typeof item === "string") ? args["kbids"] : void 0;
2673
2994
  if (!kbid && !kbids) {
@@ -2697,6 +3018,15 @@ async function handleRecall(client, args) {
2697
3018
  function asString2(value, fallback) {
2698
3019
  return typeof value === "string" ? value : fallback;
2699
3020
  }
3021
+ function asBooleanOrUndefined(value) {
3022
+ return typeof value === "boolean" ? value : void 0;
3023
+ }
3024
+ function asStringArrayOrUndefined(value) {
3025
+ if (Array.isArray(value) && value.every((item) => typeof item === "string")) {
3026
+ return value;
3027
+ }
3028
+ return void 0;
3029
+ }
2700
3030
  function asStringArray2(value) {
2701
3031
  if (Array.isArray(value) && value.every((item) => typeof item === "string")) {
2702
3032
  return value;
@@ -2715,7 +3045,7 @@ async function handleToolCall(client, name, args) {
2715
3045
  case "retrieve":
2716
3046
  return await handleRetrieve(client, args);
2717
3047
  case "recall":
2718
- return await handleRecall(client, args);
3048
+ return await handleRecall2(client, args);
2719
3049
  case "status":
2720
3050
  return await handleStatus2(client);
2721
3051
  case "content":
@@ -2726,6 +3056,8 @@ async function handleToolCall(client, name, args) {
2726
3056
  return await handleGc2(client);
2727
3057
  case "rebuild":
2728
3058
  return await handleRebuild2(client);
3059
+ case "export":
3060
+ return await handleExport2(client, args);
2729
3061
  case "skill-learn":
2730
3062
  return await handleSkillLearn(client, args);
2731
3063
  case "skill-list":
@@ -2774,7 +3106,13 @@ async function handleSearch2(client, args) {
2774
3106
  });
2775
3107
  const stripped = {
2776
3108
  ...results,
2777
- items: results.items.map(({ score: _score, ...rest }) => rest)
3109
+ items: results.items.map(
3110
+ ({
3111
+ score: _score,
3112
+ confidence: _confidence,
3113
+ ...rest
3114
+ }) => rest
3115
+ )
2778
3116
  };
2779
3117
  return {
2780
3118
  content: [
@@ -2790,6 +3128,8 @@ async function handleLearn2(client, args) {
2790
3128
  const title = typeof args["title"] === "string" ? args["title"] : void 0;
2791
3129
  const description = typeof args["description"] === "string" ? args["description"] : void 0;
2792
3130
  const docid = typeof args["docid"] === "string" ? args["docid"] : void 0;
3131
+ const tags = asStringArrayOrUndefined(args["tags"]);
3132
+ const replace = asBooleanOrUndefined(args["replace"]);
2793
3133
  if (sectionType.startsWith("image/") && !title) {
2794
3134
  return {
2795
3135
  content: [
@@ -2806,13 +3146,26 @@ async function handleLearn2(client, args) {
2806
3146
  content: asString2(args["content"], ""),
2807
3147
  title,
2808
3148
  description,
2809
- docid
3149
+ docid,
3150
+ tags,
3151
+ replace
2810
3152
  });
3153
+ const normalized = {
3154
+ kbid: result.kbid,
3155
+ indexed_terms_count: result.indexed_terms_count ?? 0,
3156
+ elapsed_ms: result.elapsed_ms ?? 0,
3157
+ superseded: result.superseded ?? null,
3158
+ warnings: result.warnings ?? [],
3159
+ near_duplicates: (result.near_duplicates ?? []).map((d) => ({
3160
+ kbid: d.kbid,
3161
+ similarity: d.similarity
3162
+ }))
3163
+ };
2811
3164
  return {
2812
3165
  content: [
2813
3166
  {
2814
3167
  type: "text",
2815
- text: JSON.stringify(result)
3168
+ text: JSON.stringify(normalized)
2816
3169
  }
2817
3170
  ]
2818
3171
  };
@@ -3053,6 +3406,20 @@ async function handleSkillDelete(client, args) {
3053
3406
  ]
3054
3407
  };
3055
3408
  }
3409
+ async function handleExport2(client, args) {
3410
+ const path = typeof args["path"] === "string" ? args["path"] : void 0;
3411
+ const result = await client.export(
3412
+ path !== void 0 ? { path } : {}
3413
+ );
3414
+ return {
3415
+ content: [
3416
+ {
3417
+ type: "text",
3418
+ text: JSON.stringify(result)
3419
+ }
3420
+ ]
3421
+ };
3422
+ }
3056
3423
 
3057
3424
  // src/shared/mcp/functions/handle-resource-read.function.ts
3058
3425
  async function handleResourceRead(client, uri) {
@@ -3424,8 +3791,9 @@ var McpServer = class {
3424
3791
  const request = parsed;
3425
3792
  if (request.id === void 0) {
3426
3793
  if (request.method === "notifications/initialized" || request.method === "notifications/cancelled") {
3427
- if (request.method === "notifications/initialized") {
3794
+ if (request.method === "notifications/initialized" && !this.initialized) {
3428
3795
  this.initialized = true;
3796
+ void this.runStartupHealthCheck();
3429
3797
  }
3430
3798
  }
3431
3799
  return null;
@@ -3618,6 +3986,27 @@ var McpServer = class {
3618
3986
  const json = JSON.stringify(response);
3619
3987
  process.stdout.write(json + "\n");
3620
3988
  }
3989
+ /**
3990
+ * Runs an integrity check immediately after the MCP handshake
3991
+ * completes. Emits a `notifications/message` warning when the
3992
+ * check detects issues so the connected agent is aware before
3993
+ * making requests. The server continues operating regardless of
3994
+ * the result.
3995
+ */
3996
+ async runStartupHealthCheck() {
3997
+ try {
3998
+ const report = await this.client.integrityCheck();
3999
+ if (!report.ok) {
4000
+ const summary = report.errors.map((e) => `${e.type}: ${e.entity} -- ${e.detail}`).join("; ");
4001
+ this.notify("notifications/message", {
4002
+ level: "warning",
4003
+ logger: "kbdb",
4004
+ data: `Database integrity issues detected: ${summary}`
4005
+ });
4006
+ }
4007
+ } catch {
4008
+ }
4009
+ }
3621
4010
  };
3622
4011
 
3623
4012
  // src/shared/mcp/functions/create-mcp-server.function.ts