@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/worker.mjs CHANGED
@@ -85,6 +85,7 @@ import { tmpdir as tmpdir2 } from "node:os";
85
85
  import {
86
86
  existsSync as existsSync2,
87
87
  readFileSync as readFileSync4,
88
+ readdirSync as readdirSync2,
88
89
  accessSync,
89
90
  writeFileSync as writeFileSync2,
90
91
  unlinkSync as unlinkSync2,
@@ -135,8 +136,9 @@ function createHostImports(contextPath, memoryRef = nullMemory, fs = nodeFsAdapt
135
136
  }
136
137
  function readPathArg(ptr, len) {
137
138
  const bytes = new Uint8Array(getBuffer(), ptr, len);
138
- const relative = new TextDecoder().decode(bytes);
139
- return join2(contextPath, relative);
139
+ const decoded = new TextDecoder().decode(bytes);
140
+ if (decoded.startsWith("/")) return decoded;
141
+ return join2(contextPath, decoded);
140
142
  }
141
143
  return {
142
144
  /**
@@ -156,14 +158,15 @@ function createHostImports(contextPath, memoryRef = nullMemory, fs = nodeFsAdapt
156
158
  const filePath = readPathArg(pathPtr, pathLen);
157
159
  const data = fs.readFileSync(filePath);
158
160
  const dataLen = data.byteLength;
159
- const dv = new DataView(getBuffer());
160
161
  if (dataLen === 0) {
161
- dv.setUint32(outPtr, 0, true);
162
- dv.setUint32(outLen, 0, true);
162
+ const dv2 = new DataView(getBuffer());
163
+ dv2.setUint32(outPtr, 0, true);
164
+ dv2.setUint32(outLen, 0, true);
163
165
  return 0;
164
166
  }
165
167
  const dataPtr = allocator.alloc(dataLen);
166
168
  if (dataPtr === 0) return -1;
169
+ const dv = new DataView(getBuffer());
167
170
  const memView = new Uint8Array(getBuffer());
168
171
  memView.set(
169
172
  new Uint8Array(
@@ -241,14 +244,15 @@ function createHostImports(contextPath, memoryRef = nullMemory, fs = nodeFsAdapt
241
244
  const entries = fs.readdirSync(dirPath);
242
245
  const text = entries.join("\n");
243
246
  const encoded = new TextEncoder().encode(text);
244
- const dv = new DataView(getBuffer());
245
247
  if (encoded.length === 0) {
246
- dv.setUint32(outPtr, 0, true);
247
- dv.setUint32(outLen, 0, true);
248
+ const dv2 = new DataView(getBuffer());
249
+ dv2.setUint32(outPtr, 0, true);
250
+ dv2.setUint32(outLen, 0, true);
248
251
  return 0;
249
252
  }
250
253
  const dataPtr = allocator.alloc(encoded.length);
251
254
  if (dataPtr === 0) return -1;
255
+ const dv = new DataView(getBuffer());
252
256
  new Uint8Array(getBuffer(), dataPtr, encoded.length).set(
253
257
  encoded
254
258
  );
@@ -352,13 +356,16 @@ import { createServer } from "node:net";
352
356
  function createIpcServer(socketPath, handler) {
353
357
  const server = createServer((socket) => {
354
358
  let buffer = "";
359
+ let processingQueue = Promise.resolve();
355
360
  socket.on("data", (chunk) => {
356
361
  buffer += chunk.toString();
357
362
  const lines = buffer.split("\n");
358
363
  buffer = lines.pop() ?? "";
359
364
  for (const line of lines) {
360
365
  if (!line.trim()) continue;
361
- void handleLine(line, handler, socket);
366
+ processingQueue = processingQueue.then(
367
+ () => handleLine(line, handler, socket)
368
+ );
362
369
  }
363
370
  });
364
371
  socket.on("error", (err) => {
@@ -439,13 +446,27 @@ function encodeSectionType(kind, mimeType) {
439
446
 
440
447
  // src/shared/wasm-codec/functions/encode-add-section-params.function.ts
441
448
  function encodeAddSectionParams(params) {
449
+ const tags = params.tags ?? [];
450
+ const tagParts = [];
451
+ for (const tag of tags) {
452
+ tagParts.push(encodeString(tag));
453
+ }
454
+ const tagsCountBytes = encodeU32(tags.length);
455
+ const replaceByte = new Uint8Array([
456
+ params.replace === true ? 1 : 0
457
+ ]);
442
458
  const parts = [
443
459
  encodeSectionType(params.sectionType, params.mimeType),
444
460
  encodeString(params.content),
445
461
  encodeOptionString(params.title),
446
462
  encodeOptionString(params.description),
447
463
  encodeOptionString(params.docid),
448
- encodeOptionString(params.typeName)
464
+ encodeOptionString(params.typeName),
465
+ encodeOptionString(params.sourcePath),
466
+ encodeOptionString(params.sourceMtime),
467
+ tagsCountBytes,
468
+ ...tagParts,
469
+ replaceByte
449
470
  ];
450
471
  const totalLen = parts.reduce((sum, p) => sum + p.length, 0);
451
472
  const result = new Uint8Array(totalLen);
@@ -602,6 +623,10 @@ function decodePagedSearchResult(bytes) {
602
623
  offset += limit.bytesRead;
603
624
  const hasMore = bytes[offset] === 1;
604
625
  offset += 1;
626
+ const relaxed = offset < bytes.length && bytes[offset] === 1;
627
+ if (offset < bytes.length) {
628
+ offset += 1;
629
+ }
605
630
  const itemCount = decodeU32(bytes, offset);
606
631
  offset += itemCount.bytesRead;
607
632
  const items = [];
@@ -622,6 +647,8 @@ function decodePagedSearchResult(bytes) {
622
647
  }
623
648
  const score = decodeF32(bytes, offset);
624
649
  offset += score.bytesRead;
650
+ const confidence = decodeF32(bytes, offset);
651
+ offset += confidence.bytesRead;
625
652
  const snippet = decodeString(bytes, offset);
626
653
  offset += snippet.bytesRead;
627
654
  const termCount = decodeU32(bytes, offset);
@@ -632,14 +659,27 @@ function decodePagedSearchResult(bytes) {
632
659
  offset += term.bytesRead;
633
660
  matched_terms.push(term.value);
634
661
  }
662
+ const fieldCount = decodeU32(bytes, offset);
663
+ offset += fieldCount.bytesRead;
664
+ const matched_fields = [];
665
+ for (let j = 0; j < fieldCount.value; j++) {
666
+ const field = decodeString(bytes, offset);
667
+ offset += field.bytesRead;
668
+ matched_fields.push(field.value);
669
+ }
670
+ const createdAt = decodeString(bytes, offset);
671
+ offset += createdAt.bytesRead;
635
672
  items.push({
636
673
  kbid: kbid.value,
637
674
  heading: heading.value,
638
675
  type: sectionType.value,
639
676
  docids,
640
677
  score: score.value,
678
+ confidence: confidence.value,
641
679
  snippet: snippet.value,
642
- matched_terms
680
+ matched_terms,
681
+ matched_fields,
682
+ created_at: createdAt.value
643
683
  });
644
684
  }
645
685
  return {
@@ -647,7 +687,8 @@ function decodePagedSearchResult(bytes) {
647
687
  total: total.value,
648
688
  offset: resultOffset.value,
649
689
  limit: limit.value,
650
- has_more: hasMore
690
+ has_more: hasMore,
691
+ relaxed
651
692
  };
652
693
  }
653
694
 
@@ -707,6 +748,47 @@ function decodeSectionRecords(bytes) {
707
748
  offset += codeTokenCount.bytesRead;
708
749
  const typeName = decodeString(bytes, offset);
709
750
  offset += typeName.bytesRead;
751
+ let sourceKey = null;
752
+ if (offset < bytes.length) {
753
+ const v = decodeOptionString2(bytes, offset);
754
+ offset += v.bytesRead;
755
+ sourceKey = v.value;
756
+ }
757
+ let sourcePath = null;
758
+ if (offset < bytes.length) {
759
+ const v = decodeOptionString2(bytes, offset);
760
+ offset += v.bytesRead;
761
+ sourcePath = v.value;
762
+ }
763
+ let sourceMtime = null;
764
+ if (offset < bytes.length) {
765
+ const v = decodeOptionString2(bytes, offset);
766
+ offset += v.bytesRead;
767
+ sourceMtime = v.value;
768
+ }
769
+ let updatedAt = null;
770
+ if (offset < bytes.length) {
771
+ const v = decodeOptionString2(bytes, offset);
772
+ offset += v.bytesRead;
773
+ updatedAt = v.value;
774
+ }
775
+ let tags = [];
776
+ if (offset < bytes.length) {
777
+ const tagCount = decodeU32(bytes, offset);
778
+ offset += tagCount.bytesRead;
779
+ tags = [];
780
+ for (let j = 0; j < tagCount.value; j++) {
781
+ const tag = decodeString(bytes, offset);
782
+ offset += tag.bytesRead;
783
+ tags.push(tag.value);
784
+ }
785
+ }
786
+ let supersedes = null;
787
+ if (offset < bytes.length) {
788
+ const v = decodeOptionString2(bytes, offset);
789
+ offset += v.bytesRead;
790
+ supersedes = v.value;
791
+ }
710
792
  const record = {
711
793
  kbid: kbid.value,
712
794
  docid: docid.value,
@@ -721,7 +803,13 @@ function decodeSectionRecords(bytes) {
721
803
  headingTokenCount: headingTokenCount.value,
722
804
  bodyTokenCount: bodyTokenCount.value,
723
805
  codeTokenCount: codeTokenCount.value,
724
- typeName: typeName.value
806
+ typeName: typeName.value,
807
+ sourceKey,
808
+ sourcePath,
809
+ sourceMtime,
810
+ updatedAt,
811
+ tags,
812
+ supersedes
725
813
  };
726
814
  if (st.mimeType !== void 0) {
727
815
  record.mimeType = st.mimeType;
@@ -1041,6 +1129,54 @@ function decodeOneResult(bytes, startOffset) {
1041
1129
  };
1042
1130
  }
1043
1131
 
1132
+ // src/shared/wasm-codec/functions/decode-add-section-result.function.ts
1133
+ function decodeAddSectionResult(bytes) {
1134
+ let offset = 0;
1135
+ const kbid = decodeString(bytes, offset);
1136
+ offset += kbid.bytesRead;
1137
+ const indexedTermsCount = decodeU32(bytes, offset);
1138
+ offset += indexedTermsCount.bytesRead;
1139
+ const elapsedMsBig = decodeU64(bytes, offset);
1140
+ offset += elapsedMsBig.bytesRead;
1141
+ const supersededTag = bytes[offset];
1142
+ offset += 1;
1143
+ let superseded = null;
1144
+ if (supersededTag === 1) {
1145
+ const supersededStr = decodeString(bytes, offset);
1146
+ offset += supersededStr.bytesRead;
1147
+ superseded = supersededStr.value;
1148
+ }
1149
+ const warningCount = decodeU32(bytes, offset);
1150
+ offset += warningCount.bytesRead;
1151
+ const warnings = [];
1152
+ for (let i = 0; i < warningCount.value; i++) {
1153
+ const warning = decodeString(bytes, offset);
1154
+ offset += warning.bytesRead;
1155
+ warnings.push(warning.value);
1156
+ }
1157
+ const dupCount = decodeU32(bytes, offset);
1158
+ offset += dupCount.bytesRead;
1159
+ const nearDuplicates = [];
1160
+ for (let i = 0; i < dupCount.value; i++) {
1161
+ const dupKbid = decodeString(bytes, offset);
1162
+ offset += dupKbid.bytesRead;
1163
+ const similarity = decodeF32(bytes, offset);
1164
+ offset += similarity.bytesRead;
1165
+ nearDuplicates.push({
1166
+ kbid: dupKbid.value,
1167
+ similarity: similarity.value
1168
+ });
1169
+ }
1170
+ return {
1171
+ kbid: kbid.value,
1172
+ indexedTermsCount: indexedTermsCount.value,
1173
+ elapsedMs: Number(elapsedMsBig.value),
1174
+ superseded,
1175
+ warnings,
1176
+ nearDuplicates
1177
+ };
1178
+ }
1179
+
1044
1180
  // src/shared/worker-daemon/classes/worker-daemon.class.ts
1045
1181
  var WASM_METHOD_MAP = {
1046
1182
  search: "worker_search",
@@ -1080,7 +1216,9 @@ var WASM_METHOD_MAP = {
1080
1216
  list_by_type: "worker_list_by_type",
1081
1217
  listDocumentsByType: "worker_list_docs_by_type",
1082
1218
  list_docs_by_type: "worker_list_docs_by_type",
1083
- recall: "worker_recall"
1219
+ recall: "worker_recall",
1220
+ export: "worker_export",
1221
+ exportDb: "worker_export"
1084
1222
  };
1085
1223
  var CONFIG_PREFIX = "config_";
1086
1224
  var CONFIG_KEY_MAP = {
@@ -1115,6 +1253,14 @@ var WorkerDaemon = class {
1115
1253
  _idleTimer = null;
1116
1254
  _server = null;
1117
1255
  _wasmDir;
1256
+ /** Queue of waiters for the single-writer lock. */
1257
+ _writeQueue = [];
1258
+ /** Whether the write lock is currently held. */
1259
+ _writeLocked = false;
1260
+ /** Whether the startup integrity check passed. */
1261
+ _integrityOk = true;
1262
+ /** Integrity errors found at startup, if any. */
1263
+ _integrityErrors = [];
1118
1264
  /**
1119
1265
  * @param ctx - context ID (16-hex characters), the first 16 of
1120
1266
  * the SHA-256 hash of `targetDir`
@@ -1176,6 +1322,7 @@ var WorkerDaemon = class {
1176
1322
  );
1177
1323
  this._wasmModules = wasmModules;
1178
1324
  this.initWorkerContext();
1325
+ this.runStartupIntegrityCheck();
1179
1326
  const pidPath = join3(tmpdir(), `kbdb-${this.ctx}.pid`);
1180
1327
  writeFileSync2(pidPath, process.pid.toString());
1181
1328
  const ipcPath = getIpcPath(this.ctx);
@@ -1364,7 +1511,11 @@ var WorkerDaemon = class {
1364
1511
  */
1365
1512
  dispatch(method, params) {
1366
1513
  if (method === "status") {
1367
- return this._connectionCount > 0 ? "busy" : "idle";
1514
+ return {
1515
+ status: this._connectionCount > 0 ? "busy" : "idle",
1516
+ integrityOk: this._integrityOk,
1517
+ integrityErrors: this._integrityErrors
1518
+ };
1368
1519
  }
1369
1520
  if (method === "idle_at") {
1370
1521
  return this._idleAt;
@@ -1381,67 +1532,31 @@ var WorkerDaemon = class {
1381
1532
  }
1382
1533
  throw new IpcError(-32601, `Method not found: ${method}`);
1383
1534
  }
1384
- dispatchWasm(method, wasmExport, params) {
1535
+ async dispatchWasm(method, wasmExport, params) {
1385
1536
  if (method === "search" || method === "query") {
1386
1537
  return this.handleSearch(params);
1387
1538
  }
1388
- if (method === "addSection" || method === "add_section") {
1389
- return this.handleAddSection(params);
1390
- }
1391
- if (method === "updateSection" || method === "update_section") {
1392
- return this.handleUpdateSection(params);
1393
- }
1394
- if (method === "removeSection" || method === "remove_section") {
1395
- return this.handleRemoveSection(params);
1396
- }
1397
1539
  if (method === "readSections" || method === "read_sections") {
1398
1540
  return this.handleReadSections(params);
1399
1541
  }
1400
1542
  if (method === "retrieveDocument" || method === "retrieve_doc") {
1401
1543
  return this.handleRetrieveDoc(params);
1402
1544
  }
1403
- if (method === "groupSections" || method === "group_sections") {
1404
- return this.handleGroupSections(params);
1405
- }
1406
- if (method === "removeGrouping" || method === "remove_grouping") {
1407
- return this.handleRemoveGrouping(params);
1408
- }
1409
1545
  if (method === "content" || method === "compose") {
1410
1546
  return this.handleCompose(params);
1411
1547
  }
1412
1548
  if (method === "integrityCheck" || method === "integrity_check") {
1413
1549
  return this.handleIntegrityCheck();
1414
1550
  }
1415
- if (method === "gc") {
1416
- return this.handleGc();
1417
- }
1418
- if (method === "rebuildIndexes" || method === "rebuild_indexes") {
1419
- return this.handleRebuild();
1420
- }
1421
1551
  if (method === "dbStatus" || method === "db_status") {
1422
1552
  return this.handleDbStatus();
1423
1553
  }
1424
- if (method === "initDirectory" || method === "init_directory") {
1425
- return this.handleInitDirectory(params);
1426
- }
1427
- if (method === "invalidate") {
1428
- return this.handleInvalidate(params);
1429
- }
1430
- if (method === "invalidateAll" || method === "invalidate_all") {
1431
- return this.handleInvalidateAll();
1432
- }
1433
1554
  if (method === "get_result_status") {
1434
1555
  return this.handleResultStatus(params);
1435
1556
  }
1436
1557
  if (method === "get_result") {
1437
1558
  return this.handleGetResult(params);
1438
1559
  }
1439
- if (method === "migrateDatabase") {
1440
- throw new IpcError(
1441
- -32601,
1442
- "migrateDatabase is not yet supported"
1443
- );
1444
- }
1445
1560
  if (method === "listByType" || method === "list_by_type") {
1446
1561
  return this.handleListByType(params);
1447
1562
  }
@@ -1451,7 +1566,51 @@ var WorkerDaemon = class {
1451
1566
  if (method === "recall") {
1452
1567
  return this.handleRecall(params);
1453
1568
  }
1454
- return this.invokeWasmRaw(wasmExport, new Uint8Array(0));
1569
+ if (method === "export" || method === "exportDb") {
1570
+ return this.handleExport(params);
1571
+ }
1572
+ await this.acquireWriteLock();
1573
+ try {
1574
+ if (method === "invalidate") {
1575
+ return this.handleInvalidate(params);
1576
+ }
1577
+ if (method === "invalidateAll" || method === "invalidate_all") {
1578
+ return this.handleInvalidateAll();
1579
+ }
1580
+ if (method === "addSection" || method === "add_section") {
1581
+ return this.handleAddSection(params);
1582
+ }
1583
+ if (method === "updateSection" || method === "update_section") {
1584
+ return this.handleUpdateSection(params);
1585
+ }
1586
+ if (method === "removeSection" || method === "remove_section") {
1587
+ return this.handleRemoveSection(params);
1588
+ }
1589
+ if (method === "groupSections" || method === "group_sections") {
1590
+ return this.handleGroupSections(params);
1591
+ }
1592
+ if (method === "removeGrouping" || method === "remove_grouping") {
1593
+ return this.handleRemoveGrouping(params);
1594
+ }
1595
+ if (method === "gc") {
1596
+ return this.handleGc();
1597
+ }
1598
+ if (method === "rebuildIndexes" || method === "rebuild_indexes") {
1599
+ return this.handleRebuild();
1600
+ }
1601
+ if (method === "initDirectory" || method === "init_directory") {
1602
+ return this.handleInitDirectory(params);
1603
+ }
1604
+ if (method === "migrateDatabase") {
1605
+ throw new IpcError(
1606
+ -32601,
1607
+ "migrateDatabase is not yet supported"
1608
+ );
1609
+ }
1610
+ return this.invokeWasmRaw(wasmExport, new Uint8Array(0));
1611
+ } finally {
1612
+ this.releaseWriteLock();
1613
+ }
1455
1614
  }
1456
1615
  // -- Search/query (token-based async) --
1457
1616
  handleSearch(params) {
@@ -1506,6 +1665,7 @@ var WorkerDaemon = class {
1506
1665
  const sectionType = params["sectionType"] ?? "text";
1507
1666
  const semanticType = sectionType === "text" || sectionType === "code" || sectionType === "image" ? sectionType : "text";
1508
1667
  const typeName = sectionType !== semanticType ? sectionType : null;
1668
+ const tags = Array.isArray(params["tags"]) ? params["tags"] : [];
1509
1669
  const input = {
1510
1670
  sectionType: semanticType,
1511
1671
  mimeType: params["mimeType"],
@@ -1513,12 +1673,24 @@ var WorkerDaemon = class {
1513
1673
  title: params["title"] ?? null,
1514
1674
  description: params["description"] ?? null,
1515
1675
  docid: params["docid"] ?? null,
1516
- typeName
1676
+ typeName,
1677
+ sourcePath: typeof params["sourcePath"] === "string" ? params["sourcePath"] : void 0,
1678
+ sourceMtime: typeof params["sourceMtime"] === "string" ? params["sourceMtime"] : void 0,
1679
+ tags,
1680
+ replace: params["replace"] === true
1517
1681
  };
1518
1682
  const encoded = encodeAddSectionParams(input);
1519
1683
  const bytes = this.callWasmSync("worker_add_section", encoded);
1520
- const kbid = new TextDecoder().decode(bytes);
1521
- return { kbid };
1684
+ const decoded = decodeAddSectionResult(bytes);
1685
+ const nearDups = decoded.nearDuplicates.length > 0 ? decoded.nearDuplicates : this.scanNearDuplicates(input.content, decoded.kbid);
1686
+ return {
1687
+ kbid: decoded.kbid,
1688
+ indexed_terms_count: decoded.indexedTermsCount,
1689
+ elapsed_ms: decoded.elapsedMs,
1690
+ superseded: decoded.superseded,
1691
+ warnings: decoded.warnings,
1692
+ near_duplicates: nearDups
1693
+ };
1522
1694
  }
1523
1695
  handleUpdateSection(params) {
1524
1696
  const oldKbid = params["kbid"] ?? "";
@@ -1592,7 +1764,18 @@ var WorkerDaemon = class {
1592
1764
  handleInvalidate(params) {
1593
1765
  const kbid = params["kbid"] ?? "";
1594
1766
  const encoded = new TextEncoder().encode(kbid);
1595
- this.callWasmSync("worker_invalidate", encoded);
1767
+ const fn = this.getExport("worker_invalidate");
1768
+ const ptr = this.wasmAlloc(encoded.length);
1769
+ this.wasmWrite(ptr, encoded);
1770
+ const rc = fn(ptr, encoded.length);
1771
+ this.wasmFree(ptr, encoded.length);
1772
+ if (rc === -1) {
1773
+ const errBytes = this.readReturnSlot();
1774
+ throw new IpcError(
1775
+ -32e3,
1776
+ new TextDecoder().decode(errBytes) || "invalidate failed"
1777
+ );
1778
+ }
1596
1779
  return { ok: true };
1597
1780
  }
1598
1781
  handleListByType(params) {
@@ -1636,20 +1819,173 @@ var WorkerDaemon = class {
1636
1819
  return decodeKeyValueText(text);
1637
1820
  }
1638
1821
  handleGc() {
1822
+ const start = Date.now();
1639
1823
  this.callWasmZeroArg("worker_garbage_collect");
1640
1824
  const bytes = this.readReturnSlot();
1641
1825
  const text = new TextDecoder().decode(bytes);
1642
- return decodeKeyValueText(text);
1826
+ const kv = decodeKeyValueText(text);
1827
+ const removedCount = Number(kv["removed"] ?? 0);
1828
+ const staleSources = Number(kv["stale_sources"] ?? 0);
1829
+ return {
1830
+ removed_sections: removedCount,
1831
+ removed_bytes: 0,
1832
+ elapsed_ms: Date.now() - start,
1833
+ stale_sources: staleSources
1834
+ };
1643
1835
  }
1644
1836
  handleRebuild() {
1645
- this.callWasmZeroArg("worker_rebuild_indexes");
1646
- return { ok: true };
1837
+ const fn = this.getExport("worker_rebuild_indexes");
1838
+ const rc = fn();
1839
+ if (rc === -1) {
1840
+ const errBytes = this.readReturnSlot();
1841
+ const errMsg = new TextDecoder().decode(errBytes);
1842
+ throw new IpcError(-32e3, errMsg || "rebuild failed");
1843
+ }
1844
+ const resultBytes = this.readReturnSlot();
1845
+ const reindexed = resultBytes.length >= 4 ? new DataView(
1846
+ resultBytes.buffer,
1847
+ resultBytes.byteOffset,
1848
+ resultBytes.byteLength
1849
+ ).getUint32(0, true) : 0;
1850
+ return { ok: true, sections_reindexed: reindexed };
1851
+ }
1852
+ // -- Export (read operation, no lock required) --
1853
+ handleExport(params) {
1854
+ const targetPath = params["targetPath"] ?? params["path"] ?? "./kbdb-export/";
1855
+ const encoded = new TextEncoder().encode(targetPath);
1856
+ const start = Date.now();
1857
+ const bytes = this.callWasmSync("worker_export", encoded);
1858
+ const elapsed = Date.now() - start;
1859
+ const text = new TextDecoder().decode(bytes);
1860
+ const kv = decodeKeyValueText(text);
1861
+ return {
1862
+ path: kv["path"] ?? targetPath,
1863
+ sections_exported: Number(kv["sections_exported"] ?? 0),
1864
+ documents_exported: Number(kv["documents_exported"] ?? 0),
1865
+ elapsed_ms: elapsed
1866
+ };
1867
+ }
1868
+ // -- Near-duplicate detection --
1869
+ scanNearDuplicates(content, newKbid) {
1870
+ const newTerms = this.extractTermSet(content);
1871
+ if (newTerms.size === 0) return [];
1872
+ const sectionsDir = join3(this.targetDir, "sections");
1873
+ let shards;
1874
+ try {
1875
+ shards = readdirSync2(sectionsDir);
1876
+ } catch {
1877
+ return [];
1878
+ }
1879
+ const results = [];
1880
+ for (const shard of shards) {
1881
+ let files;
1882
+ try {
1883
+ files = readdirSync2(join3(sectionsDir, shard));
1884
+ } catch {
1885
+ continue;
1886
+ }
1887
+ for (const file of files) {
1888
+ if (!file.endsWith(".section")) continue;
1889
+ const kbid = file.slice(0, -8);
1890
+ if (kbid === newKbid) continue;
1891
+ try {
1892
+ const data = readFileSync4(
1893
+ join3(sectionsDir, shard, file),
1894
+ "utf-8"
1895
+ );
1896
+ const sep = data.indexOf("\n---\n");
1897
+ const body = sep >= 0 ? data.slice(sep + 5) : data;
1898
+ const existing = this.extractTermSet(body);
1899
+ let intersection = 0;
1900
+ for (const t of newTerms) {
1901
+ if (existing.has(t)) intersection++;
1902
+ }
1903
+ const union = newTerms.size + existing.size - intersection;
1904
+ const sim = union > 0 ? intersection / union : 0;
1905
+ if (sim >= 0.85) {
1906
+ results.push({
1907
+ kbid,
1908
+ similarity: Math.round(sim * 100) / 100
1909
+ });
1910
+ }
1911
+ } catch {
1912
+ continue;
1913
+ }
1914
+ }
1915
+ }
1916
+ return results;
1917
+ }
1918
+ extractTermSet(text) {
1919
+ return new Set(
1920
+ text.toLowerCase().split(/\W+/).filter((w) => w.length > 2)
1921
+ );
1922
+ }
1923
+ // -- Single-writer lock --
1924
+ /**
1925
+ * Acquires the write lock.
1926
+ *
1927
+ * If the lock is free it is granted immediately. If it is held the
1928
+ * caller is queued and its promise resolves once the lock is
1929
+ * released by the current holder.
1930
+ */
1931
+ acquireWriteLock() {
1932
+ if (!this._writeLocked) {
1933
+ this._writeLocked = true;
1934
+ return Promise.resolve();
1935
+ }
1936
+ return new Promise((resolve2) => {
1937
+ this._writeQueue.push({ resolve: resolve2 });
1938
+ });
1939
+ }
1940
+ /**
1941
+ * Releases the write lock.
1942
+ *
1943
+ * If waiters are queued the next one is granted the lock
1944
+ * immediately. Otherwise the lock is marked free.
1945
+ */
1946
+ releaseWriteLock() {
1947
+ const next = this._writeQueue.shift();
1948
+ if (next !== void 0) {
1949
+ next.resolve();
1950
+ } else {
1951
+ this._writeLocked = false;
1952
+ }
1647
1953
  }
1648
1954
  // -- WASM lifecycle --
1649
1955
  initWorkerContext() {
1650
1956
  const pathBytes = new TextEncoder().encode(".");
1651
1957
  this.callWasmSync("worker_init", pathBytes);
1652
1958
  }
1959
+ /**
1960
+ * Runs a database integrity check immediately after context
1961
+ * initialisation.
1962
+ *
1963
+ * Stores the result in `_integrityOk` and `_integrityErrors`.
1964
+ * Logs a warning to stderr when issues are found so operators
1965
+ * are alerted without aborting the daemon.
1966
+ */
1967
+ runStartupIntegrityCheck() {
1968
+ try {
1969
+ this.callWasmZeroArg("worker_check_integrity");
1970
+ const bytes = this.readReturnSlot();
1971
+ const text = new TextDecoder().decode(bytes);
1972
+ const kv = decodeKeyValueText(text);
1973
+ this._integrityOk = kv["ok"] === "true";
1974
+ if (!this._integrityOk) {
1975
+ this._integrityErrors = Object.entries(kv).filter(([k]) => k !== "ok").map(([k, v]) => `${k}=${v}`);
1976
+ console.error(
1977
+ "[kbdb-worker] startup integrity check failed:",
1978
+ this._integrityErrors.join(", ")
1979
+ );
1980
+ }
1981
+ } catch {
1982
+ this._integrityOk = false;
1983
+ this._integrityErrors = ["integrity_check_failed"];
1984
+ console.error(
1985
+ "[kbdb-worker] startup integrity check threw an error"
1986
+ );
1987
+ }
1988
+ }
1653
1989
  // -- WASM bridge primitives --
1654
1990
  getExport(name) {
1655
1991
  if (this._wasmModules === null) {