@dikolab/kbdb 0.6.4 → 0.6.5

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 (67) hide show
  1. package/dist/cli.cjs +452 -144
  2. package/dist/cli.cjs.map +4 -4
  3. package/dist/cli.mjs +452 -144
  4. package/dist/cli.mjs.map +4 -4
  5. package/dist/kbdb-worker.cjs +49 -7
  6. package/dist/kbdb-worker.cjs.map +4 -4
  7. package/dist/mod.cjs +1 -1
  8. package/dist/mod.cjs.map +1 -1
  9. package/dist/mod.mjs +1 -1
  10. package/dist/mod.mjs.map +1 -1
  11. package/dist/src/shared/cli/constants/defaults.constant.d.ts +1 -1
  12. package/dist/src/shared/cli/functions/learn-structured-document.function.d.ts +10 -0
  13. package/dist/src/shared/cli/functions/learn-target-content.function.d.ts +5 -17
  14. package/dist/src/shared/cli/functions/split-markdown-sections.function.d.ts +4 -0
  15. package/dist/src/shared/cli/typings/learn-client.interface.d.ts +2 -0
  16. package/dist/src/shared/cli/typings/learn-result.model.d.ts +2 -0
  17. package/dist/src/shared/content-splitter/classes/heading-parent-tracker.class.d.ts +20 -0
  18. package/dist/src/shared/content-splitter/constants/content-type-maps.constant.d.ts +5 -0
  19. package/dist/src/shared/content-splitter/constants/split-strategy-map.constant.d.ts +4 -0
  20. package/dist/src/shared/content-splitter/functions/detect-content-type.function.d.ts +13 -0
  21. package/dist/src/shared/content-splitter/functions/flush-markdown-draft.function.d.ts +14 -0
  22. package/dist/src/shared/content-splitter/functions/flush-toml-draft.function.d.ts +15 -0
  23. package/dist/src/shared/content-splitter/functions/push-content-section.function.d.ts +16 -0
  24. package/dist/src/shared/content-splitter/functions/resolve-parent-ids.function.d.ts +14 -0
  25. package/dist/src/shared/content-splitter/functions/split-content.function.d.ts +11 -0
  26. package/dist/src/shared/content-splitter/functions/split-html.function.d.ts +12 -0
  27. package/dist/src/shared/content-splitter/functions/split-markdown.function.d.ts +13 -0
  28. package/dist/src/shared/content-splitter/functions/split-plain-text.function.d.ts +8 -0
  29. package/dist/src/shared/content-splitter/functions/split-toml.function.d.ts +12 -0
  30. package/dist/src/shared/content-splitter/index.d.ts +11 -0
  31. package/dist/src/shared/content-splitter/typings/content-format.type.d.ts +2 -0
  32. package/dist/src/shared/content-splitter/typings/content-section.interface.d.ts +17 -0
  33. package/dist/src/shared/content-splitter/typings/markdown-draft.interface.d.ts +12 -0
  34. package/dist/src/shared/content-splitter/typings/split-result.interface.d.ts +8 -0
  35. package/dist/src/shared/content-splitter/typings/split-strategy.interface.d.ts +3 -0
  36. package/dist/src/shared/content-splitter/typings/toml-draft.interface.d.ts +15 -0
  37. package/dist/src/shared/mcp/functions/build-learn-payload.function.d.ts +24 -0
  38. package/dist/src/shared/mcp/functions/determine-learn-split.function.d.ts +18 -0
  39. package/dist/src/shared/mcp/functions/handle-tool-learn.function.d.ts +3 -1
  40. package/dist/src/shared/mcp/functions/learn-with-splitting.function.d.ts +22 -0
  41. package/dist/src/shared/mcp/functions/parse-learn-args.function.d.ts +24 -0
  42. package/dist/src/shared/mcp/functions/store-split-learn-sections.function.d.ts +16 -0
  43. package/dist/src/shared/mcp/functions/unwrap-json-content.function.d.ts +9 -0
  44. package/dist/src/shared/mcp/typings/learn-section-params.interface.d.ts +20 -0
  45. package/dist/src/shared/mcp/typings/mcp-client.interface.d.ts +4 -0
  46. package/dist/src/shared/mcp/typings/mcp-worker-client.interface.d.ts +4 -0
  47. package/dist/src/shared/version/constants/version.constant.d.ts +1 -1
  48. package/dist/src/shared/wasm-codec/functions/decode-one-recall-result.function.d.ts +2 -1
  49. package/dist/src/shared/wasm-codec/functions/decode-recall-document.function.d.ts +2 -1
  50. package/dist/src/shared/wasm-codec/functions/decode-recall-result.function.d.ts +2 -0
  51. package/dist/src/shared/wasm-codec/functions/decode-recall-sibling.function.d.ts +2 -1
  52. package/dist/src/shared/wasm-codec/functions/decode-scored-results.function.d.ts +6 -0
  53. package/dist/src/shared/wasm-codec/functions/decode-section-match.function.d.ts +2 -1
  54. package/dist/src/shared/wasm-codec/functions/decode-section-records.function.d.ts +4 -0
  55. package/dist/src/shared/wasm-codec/functions/encode-add-section-params.function.d.ts +1 -0
  56. package/dist/src/shared/wasm-codec/typings/add-section-input.model.d.ts +2 -0
  57. package/dist/src/shared/wasm-codec/typings/section-match.model.d.ts +4 -0
  58. package/dist/src/shared/wasm-codec/typings/section-record-trailing.model.d.ts +2 -0
  59. package/dist/src/shared/wasm-codec/typings/section-record.model.d.ts +2 -0
  60. package/dist/src/shared/worker-client/typings/recall-result.model.d.ts +12 -0
  61. package/dist/src/shared/worker-client/typings/search-result.model.d.ts +4 -0
  62. package/dist/src/shared/worker-client/typings/section.model.d.ts +2 -0
  63. package/dist/wasm/fs-database/kbdb_fs_database_bg.wasm +0 -0
  64. package/dist/wasm/kb-worker/kbdb_worker_bg.wasm +0 -0
  65. package/dist/worker.mjs +49 -7
  66. package/dist/worker.mjs.map +4 -4
  67. package/package.json +1 -1
package/dist/cli.cjs CHANGED
@@ -31,7 +31,10 @@ var SUPPORTED_EXTENSIONS = [
31
31
  ".md",
32
32
  ".txt",
33
33
  ".rst",
34
- ".adoc"
34
+ ".adoc",
35
+ ".html",
36
+ ".htm",
37
+ ".toml"
35
38
  ];
36
39
 
37
40
  // src/shared/log/typings/log-level.type.ts
@@ -745,13 +748,102 @@ function computeDirectoryLevel(filePath, basePath) {
745
748
  return Math.min(depth + 1, MAX_LEVEL);
746
749
  }
747
750
 
748
- // src/shared/cli/functions/learn-markdown-document.function.ts
749
- var import_node_path3 = require("node:path");
751
+ // src/shared/content-splitter/constants/content-type-maps.constant.ts
752
+ var HINT_FORMAT_MAP = {
753
+ markdown: "markdown",
754
+ "text/markdown": "markdown",
755
+ md: "markdown",
756
+ html: "html",
757
+ "text/html": "html",
758
+ toml: "toml",
759
+ "application/toml": "toml",
760
+ "text/toml": "toml",
761
+ "text/x-toml": "toml"
762
+ };
763
+ var EXTENSION_FORMAT_MAP = {
764
+ md: "markdown",
765
+ markdown: "markdown",
766
+ html: "html",
767
+ htm: "html",
768
+ toml: "toml"
769
+ };
750
770
 
751
- // src/shared/cli/functions/split-markdown-sections.function.ts
771
+ // src/shared/content-splitter/functions/detect-content-type.function.ts
772
+ function detectContentType(content, typeHint, filePath) {
773
+ const hinted = typeHint && HINT_FORMAT_MAP[typeHint.toLowerCase()];
774
+ if (hinted) return hinted;
775
+ const extension = filePath?.split(".").pop()?.toLowerCase();
776
+ const extMatch = extension && EXTENSION_FORMAT_MAP[extension];
777
+ if (extMatch) return extMatch;
778
+ return sniffContentType(content);
779
+ }
780
+ function sniffContentType(content) {
781
+ const trimmed = content.trimStart();
782
+ if (/^<!DOCTYPE/i.test(trimmed) || /^<html/i.test(trimmed)) {
783
+ return "html";
784
+ }
785
+ if (trimmed.startsWith("[") && trimmed.includes("=")) {
786
+ return "toml";
787
+ }
788
+ return "plain-text";
789
+ }
790
+
791
+ // src/shared/content-splitter/classes/heading-parent-tracker.class.ts
792
+ var HeadingParentTracker = class {
793
+ stack = [];
794
+ /**
795
+ * Resolves the parent index for a heading at `level`
796
+ * appearing at output position `index`, then records the
797
+ * heading so subsequent, deeper headings can nest under it.
798
+ *
799
+ * @param level - heading depth, shallower is smaller
800
+ * @param index - position of this heading in the output
801
+ * sections array
802
+ * @returns the parent's output index, or `null` when the
803
+ * heading is at the root
804
+ */
805
+ resolve(level, index) {
806
+ while (this.stack.length > 0 && this.stack[this.stack.length - 1].level >= level) {
807
+ this.stack.pop();
808
+ }
809
+ const parentIndex = this.stack.length > 0 ? this.stack[this.stack.length - 1].index : null;
810
+ this.stack.push({ level, index });
811
+ return parentIndex;
812
+ }
813
+ };
814
+
815
+ // src/shared/content-splitter/functions/push-content-section.function.ts
816
+ function pushContentSection(sections, tracker, content, title, level) {
817
+ if (level === void 0) {
818
+ sections.push({ content, parentIndex: null });
819
+ return;
820
+ }
821
+ sections.push({
822
+ title,
823
+ level,
824
+ content,
825
+ parentIndex: tracker.resolve(level, sections.length)
826
+ });
827
+ }
828
+
829
+ // src/shared/content-splitter/functions/flush-markdown-draft.function.ts
830
+ function flushMarkdownDraft(draft, sections, tracker) {
831
+ const body = draft.lines.join("\n").trimEnd();
832
+ if (draft.title === void 0 && body.length === 0) return;
833
+ pushContentSection(
834
+ sections,
835
+ tracker,
836
+ body,
837
+ draft.title,
838
+ draft.level
839
+ );
840
+ }
841
+
842
+ // src/shared/content-splitter/functions/split-markdown.function.ts
752
843
  var HEADING_PATTERN = /^(#{1,6})\s+(.+)$/;
753
- function splitMarkdownSections(content) {
844
+ function splitMarkdown(content) {
754
845
  const sections = [];
846
+ const tracker = new HeadingParentTracker();
755
847
  let draft = { lines: [] };
756
848
  let inCodeBlock = false;
757
849
  for (const line of content.split("\n")) {
@@ -762,7 +854,7 @@ function splitMarkdownSections(content) {
762
854
  }
763
855
  const heading = inCodeBlock ? null : HEADING_PATTERN.exec(line);
764
856
  if (heading) {
765
- flushSection(draft, sections);
857
+ flushMarkdownDraft(draft, sections, tracker);
766
858
  draft = {
767
859
  title: heading[2].trim(),
768
860
  level: heading[1].length,
@@ -772,14 +864,113 @@ function splitMarkdownSections(content) {
772
864
  }
773
865
  draft.lines.push(line);
774
866
  }
775
- flushSection(draft, sections);
776
- return sections.length > 0 ? sections : [{ content: content.trimEnd() }];
867
+ flushMarkdownDraft(draft, sections, tracker);
868
+ return sections.length > 0 ? sections : [{ content: content.trimEnd(), parentIndex: null }];
869
+ }
870
+
871
+ // src/shared/content-splitter/functions/split-html.function.ts
872
+ var HEADING_PATTERN2 = /<h([1-6])[^>]*>(.*?)<\/h\1>/gi;
873
+ function splitHtml(content) {
874
+ const matches = [...content.matchAll(HEADING_PATTERN2)];
875
+ if (matches.length === 0) {
876
+ return [{ content: content.trimEnd(), parentIndex: null }];
877
+ }
878
+ const sections = [];
879
+ const tracker = new HeadingParentTracker();
880
+ const firstIndex = matches[0].index;
881
+ const preamble = content.slice(0, firstIndex).trimEnd();
882
+ if (preamble.length > 0) {
883
+ pushContentSection(sections, tracker, preamble);
884
+ }
885
+ matches.forEach((match, i) => {
886
+ const start = match.index + match[0].length;
887
+ const end = matches[i + 1]?.index ?? content.length;
888
+ pushContentSection(
889
+ sections,
890
+ tracker,
891
+ content.slice(start, end).trimEnd(),
892
+ match[2].trim(),
893
+ Number(match[1])
894
+ );
895
+ });
896
+ return sections;
777
897
  }
778
- function flushSection(draft, sections) {
898
+
899
+ // src/shared/content-splitter/functions/flush-toml-draft.function.ts
900
+ function flushTomlDraft(draft, sections, pathIndex) {
779
901
  const body = draft.lines.join("\n").trimEnd();
780
902
  if (draft.title === void 0 && body.length === 0) return;
781
- sections.push(
782
- draft.title === void 0 ? { content: body } : { title: draft.title, level: draft.level, content: body }
903
+ if (draft.title === void 0) {
904
+ sections.push({ content: body, parentIndex: null });
905
+ return;
906
+ }
907
+ const path = draft.path ?? draft.title;
908
+ sections.push({
909
+ title: draft.title,
910
+ content: body,
911
+ parentIndex: resolveParentIndex(path, pathIndex)
912
+ });
913
+ pathIndex.set(path, sections.length - 1);
914
+ }
915
+ function resolveParentIndex(path, pathIndex) {
916
+ if (/^["']/.test(path)) return null;
917
+ const segments = path.split(".");
918
+ segments.pop();
919
+ if (segments.length === 0) return null;
920
+ return pathIndex.get(segments.join(".")) ?? null;
921
+ }
922
+
923
+ // src/shared/content-splitter/functions/split-toml.function.ts
924
+ var HEADER_PATTERN = /^\[([^\]]+)\]\s*$/;
925
+ var QUOTED_PATTERN = /^(["'])(.*)\1$/;
926
+ function splitToml(content) {
927
+ const sections = [];
928
+ const pathIndex = /* @__PURE__ */ new Map();
929
+ let draft = { lines: [] };
930
+ for (const line of content.split("\n")) {
931
+ const header = HEADER_PATTERN.exec(line);
932
+ if (header) {
933
+ flushTomlDraft(draft, sections, pathIndex);
934
+ draft = { ...parseHeader(header[1]), lines: [] };
935
+ continue;
936
+ }
937
+ draft.lines.push(line);
938
+ }
939
+ flushTomlDraft(draft, sections, pathIndex);
940
+ return sections.length > 0 ? sections : [{ content: content.trimEnd(), parentIndex: null }];
941
+ }
942
+ function parseHeader(inner) {
943
+ const trimmed = inner.trim();
944
+ const quoted = QUOTED_PATTERN.exec(trimmed);
945
+ return quoted ? { title: quoted[2], path: trimmed } : { title: trimmed, path: trimmed };
946
+ }
947
+
948
+ // src/shared/content-splitter/functions/split-plain-text.function.ts
949
+ function splitPlainText(content) {
950
+ return [{ content, parentIndex: null }];
951
+ }
952
+
953
+ // src/shared/content-splitter/constants/split-strategy-map.constant.ts
954
+ var SPLIT_STRATEGY_MAP = {
955
+ markdown: splitMarkdown,
956
+ html: splitHtml,
957
+ toml: splitToml,
958
+ "plain-text": splitPlainText
959
+ };
960
+
961
+ // src/shared/content-splitter/functions/split-content.function.ts
962
+ function splitContent(content, typeHint, filePath) {
963
+ const format = detectContentType(content, typeHint, filePath);
964
+ return {
965
+ format,
966
+ sections: SPLIT_STRATEGY_MAP[format](content)
967
+ };
968
+ }
969
+
970
+ // src/shared/content-splitter/functions/resolve-parent-ids.function.ts
971
+ function resolveParentIds(sections, kbids) {
972
+ return sections.map(
973
+ (section) => section.parentIndex === null ? null : kbids[section.parentIndex]
783
974
  );
784
975
  }
785
976
 
@@ -798,15 +989,16 @@ function mapAddSectionResult(result, path, size, sectionType) {
798
989
  };
799
990
  }
800
991
 
801
- // src/shared/cli/functions/learn-markdown-document.function.ts
802
- async function learnMarkdownDocument(client, options, path, content, sourcePath) {
803
- const sections = splitMarkdownSections(content);
804
- const titled = sections.filter((s) => s.title !== void 0);
805
- if (sections.length < 2 || titled.length === 0) return void 0;
806
- const docTitle = titled[0]?.title ?? (0, import_node_path3.basename)(path);
992
+ // src/shared/cli/functions/learn-structured-document.function.ts
993
+ var import_node_path3 = require("node:path");
994
+ async function learnStructuredDocument(client, options, path, splitResult, sourcePath) {
995
+ const { sections } = splitResult;
996
+ const firstTitled = sections.find((s) => s.title !== void 0);
997
+ const docTitle = firstTitled?.title ?? (0, import_node_path3.basename)(path);
807
998
  const kbids = [];
808
999
  const results = [];
809
1000
  for (const section of sections) {
1001
+ const [parent] = resolveParentIds([section], kbids);
810
1002
  const added = await client.addSection({
811
1003
  sectionType: "text",
812
1004
  content: section.content,
@@ -816,7 +1008,8 @@ async function learnMarkdownDocument(client, options, path, content, sourcePath)
816
1008
  tags: options.tags,
817
1009
  replace: options.replace,
818
1010
  sourcePath,
819
- level: section.level
1011
+ level: section.level,
1012
+ parent: parent ?? void 0
820
1013
  });
821
1014
  kbids.push(added.items[0] ?? "");
822
1015
  results.push(
@@ -834,16 +1027,25 @@ async function learnMarkdownDocument(client, options, path, content, sourcePath)
834
1027
 
835
1028
  // src/shared/cli/functions/learn-target-content.function.ts
836
1029
  async function learnTargetContent(client, options, path, content, sourcePath, level) {
837
- const markdownEligible = (path === "-" || path.endsWith(".md")) && !options.title;
838
- if (markdownEligible) {
839
- const split = await learnMarkdownDocument(
840
- client,
841
- options,
842
- path,
1030
+ if (!options.title) {
1031
+ const isStdin = path === "-";
1032
+ const split = splitContent(
843
1033
  content,
844
- sourcePath
1034
+ isStdin ? "markdown" : void 0,
1035
+ isStdin ? void 0 : path
1036
+ );
1037
+ const hasTitled = split.sections.some(
1038
+ (s) => s.title !== void 0
845
1039
  );
846
- if (split) return split;
1040
+ if (split.sections.length >= 2 && hasTitled) {
1041
+ return learnStructuredDocument(
1042
+ client,
1043
+ options,
1044
+ path,
1045
+ split,
1046
+ sourcePath
1047
+ );
1048
+ }
847
1049
  }
848
1050
  const result = await client.addSection({
849
1051
  sectionType: "text",
@@ -1918,7 +2120,7 @@ async function handleTypedSearch(client, options, type) {
1918
2120
  }
1919
2121
 
1920
2122
  // src/shared/version/constants/version.constant.ts
1921
- var VERSION = "0.6.4";
2123
+ var VERSION = "0.6.5";
1922
2124
 
1923
2125
  // src/shared/cli/functions/generate-help.function.ts
1924
2126
  var KNOWN_COMMANDS = /* @__PURE__ */ new Set([
@@ -1997,7 +2199,7 @@ Usage: kbdb [options]
1997
2199
  kbdb <command> <subcommand> [options] ...[params]
1998
2200
 
1999
2201
  Knowledge Base:
2000
- learn Import content (splits .md on headings)
2202
+ learn Import content (auto-splits structured formats)
2001
2203
  unlearn Remove sections by kbid
2002
2204
  search Query the knowledge base (sibling boost)
2003
2205
  recall Retrieve section context by kbid
@@ -2021,25 +2223,31 @@ Skills:
2021
2223
  skill list List skills (filter by keyword)
2022
2224
  skill get Retrieve a skill by name or kbid
2023
2225
  skill delete Remove a skill
2226
+ skill search Search skills by keyword
2024
2227
 
2025
2228
  Agents:
2026
2229
  agent create Create an agent with skill refs
2027
2230
  agent list List agents (filter by keyword)
2028
2231
  agent get Retrieve an agent by docid
2029
2232
  agent delete Remove an agent
2030
-
2031
- Worker:
2032
- worker stop Stop the worker daemon
2033
- worker restart Restart the worker daemon
2233
+ agent search Search agents by keyword
2034
2234
 
2035
2235
  Server:
2036
2236
  mcp Start MCP server mode (stdio)
2037
2237
 
2038
2238
  Global Options:
2039
2239
  -h, --help Show this help message
2040
- -v, --version Show version information
2240
+ --version Show version information
2241
+ -v, --verbose Log warnings (-vv info, -vvv debug)
2041
2242
  --db <path> Path to .kbdb directory
2042
- --format <fmt> Output format (json|text|mcp)
2243
+ --format <fmt> Output format (rec|json|text|mcp)
2244
+ --timeout-ms <ms> Request timeout (default 30000)
2245
+ --retry <n> Retries on timeout (default 1)
2246
+ --non-interactive Suppress prompts
2247
+
2248
+ Worker:
2249
+ worker stop Stop background daemon
2250
+ worker restart Restart background daemon
2043
2251
 
2044
2252
  Run "kbdb <command> --help" for detailed information.
2045
2253
  `;
@@ -2096,14 +2304,19 @@ Options:
2096
2304
  --replace Supersede existing section by source key
2097
2305
  --db <path> Path to .kbdb directory
2098
2306
 
2099
- Markdown splitting:
2100
- .md files are automatically split on ATX
2101
- headings (# through ######) into independent
2102
- sections. Each section uses its heading as
2103
- the title and shares a docid for document
2104
- grouping. Providing --title imports the file
2105
- as a single section (skips splitting).
2106
- Non-.md files are never split.
2307
+ Content splitting:
2308
+ Structured formats are automatically split
2309
+ into independent sections that share a docid.
2310
+ Each section carries a level (heading depth)
2311
+ and parent (kbid of enclosing section).
2312
+ Providing --title imports as a single section
2313
+ (skips splitting).
2314
+
2315
+ Supported formats:
2316
+ Markdown .md, text/markdown ATX headings
2317
+ HTML .html, text/html <h1>-<h6> tags
2318
+ TOML .toml, application/toml [section]
2319
+ Plain .txt, text/plain no splitting
2107
2320
 
2108
2321
  Output:
2109
2322
  { items, total, offset } envelope
@@ -2141,7 +2354,7 @@ Arguments:
2141
2354
  Options:
2142
2355
  --mode <mode> Result granularity (sections|documents|stats)
2143
2356
  --limit <n> Max results (default: 20)
2144
- --format <fmt> Output format (json|text|mcp)
2357
+ --format <fmt> Output format (rec|json|text|mcp)
2145
2358
  --content, -c Hydrate results with section content
2146
2359
  --db <path> Path to .kbdb directory
2147
2360
 
@@ -2151,6 +2364,11 @@ Scoring:
2151
2364
  sibling score boost when multiple sections
2152
2365
  match the query.
2153
2366
 
2367
+ Fields:
2368
+ Each result includes level (heading depth,
2369
+ 1-6 or null) and parent (kbid of enclosing
2370
+ section or null).
2371
+
2154
2372
  Output:
2155
2373
  { items, total, offset } envelope
2156
2374
 
@@ -2172,7 +2390,8 @@ Options:
2172
2390
  --db <path> Path to .kbdb directory
2173
2391
 
2174
2392
  Output:
2175
- JSON RecallResult or RecallResult[]
2393
+ JSON RecallResult or RecallResult[] with
2394
+ level, parent, siblings, and references
2176
2395
 
2177
2396
  Examples:
2178
2397
  kbdb recall abcdef1234567890abcdef1234
@@ -2181,11 +2400,14 @@ Examples:
2181
2400
  content: `kbdb content - Compose rendered content from ids
2182
2401
 
2183
2402
  Usage: kbdb content [options] <id>...
2403
+ kbdb content --search <query>...
2184
2404
 
2185
2405
  Arguments:
2186
2406
  <id> kbid or docid identifier
2407
+ <query> Search terms (with --search)
2187
2408
 
2188
2409
  Options:
2410
+ --search, -s Search for ids by keyword
2189
2411
  --db <path> Path to .kbdb directory
2190
2412
 
2191
2413
  Output:
@@ -2194,6 +2416,7 @@ Output:
2194
2416
  Examples:
2195
2417
  kbdb content abcdef1234567890abcdef1234
2196
2418
  kbdb content id1 id2
2419
+ kbdb content --search machine learning
2197
2420
  `,
2198
2421
  check: `kbdb check - Verify knowledge base integrity
2199
2422
 
@@ -3784,7 +4007,7 @@ function createToolDefinitions() {
3784
4007
  return [
3785
4008
  {
3786
4009
  name: "search",
3787
- description: "Query the knowledge base by keywords. Returns ranked results with heading, snippet, and matched terms. Sections from the same document receive a sibling score boost when multiple sections match. Use multiple short keyword queries for best coverage. Use recall for full content.",
4010
+ description: "Query the knowledge base by keywords. Returns ranked results with heading, snippet, matched terms, level, and parent. Sections from the same document receive a sibling score boost when multiple sections match. Use multiple short keyword queries for best coverage. Use recall for full content.",
3788
4011
  inputSchema: {
3789
4012
  type: "object",
3790
4013
  properties: {
@@ -3827,7 +4050,7 @@ function createToolDefinitions() {
3827
4050
  },
3828
4051
  {
3829
4052
  name: "learn",
3830
- description: "Import content into the knowledge base. Markdown content is automatically split on ATX headings into independent sections that share a docid. Providing title imports as a single section (skips splitting). Use replace when updating existing knowledge. Attach tags for scoping.",
4053
+ description: "Import content into the knowledge base. Structured content (Markdown, HTML, TOML) is automatically split into sections that share a docid. Each section carries a level (heading depth) and parent reference. Providing title imports as a single section (skips splitting). Use replace when updating existing knowledge. Attach tags for scoping.",
3831
4054
  inputSchema: {
3832
4055
  type: "object",
3833
4056
  properties: {
@@ -3837,11 +4060,11 @@ function createToolDefinitions() {
3837
4060
  },
3838
4061
  type: {
3839
4062
  type: "string",
3840
- description: "MIME type (default: text/markdown). Use image/* for images."
4063
+ description: "MIME type or format hint. Supported: text/markdown, text/html, application/toml (also text/toml, text/x-toml), text/plain, image/*. Default: text/markdown."
3841
4064
  },
3842
4065
  title: {
3843
4066
  type: "string",
3844
- description: "Section title. Skips markdown splitting when set. Required for image/* types."
4067
+ description: "Section title. Skips content splitting when set. Required for image/* types."
3845
4068
  },
3846
4069
  description: {
3847
4070
  type: "string",
@@ -3849,7 +4072,7 @@ function createToolDefinitions() {
3849
4072
  },
3850
4073
  docid: {
3851
4074
  type: "string",
3852
- description: "Document ID to group sections under. Set automatically when markdown splitting is active."
4075
+ description: "Document ID to group sections under. Set automatically when content splitting is active."
3853
4076
  },
3854
4077
  tags: {
3855
4078
  type: "array",
@@ -3918,7 +4141,7 @@ function createToolDefinitions() {
3918
4141
  },
3919
4142
  {
3920
4143
  name: "recall",
3921
- description: "Retrieve full content and context for sections by identifier. Use after search to read relevant results. Returns section text, metadata, references, and back-references.",
4144
+ description: "Retrieve full content and context for sections by identifier. Use after search to read relevant results. Returns section text, level, parent, metadata, references, and back-references.",
3922
4145
  inputSchema: {
3923
4146
  type: "object",
3924
4147
  properties: {
@@ -4049,7 +4272,7 @@ function createToolDefinitions() {
4049
4272
  },
4050
4273
  {
4051
4274
  name: "skill-learn",
4052
- description: "Create or update a skill definition. Skills are reusable AI agent capabilities with template placeholders.",
4275
+ description: "Create or update a skill definition. Skills are reusable AI agent capabilities with template placeholders. Stored as atomic JSON (not split into sections).",
4053
4276
  inputSchema: {
4054
4277
  type: "object",
4055
4278
  properties: {
@@ -4143,7 +4366,7 @@ function createToolDefinitions() {
4143
4366
  },
4144
4367
  {
4145
4368
  name: "agent-create",
4146
- description: "Create an AI agent with a persona and optional skill references.",
4369
+ description: "Create an AI agent with a persona and optional skill references. Stored as atomic JSON (not split into sections).",
4147
4370
  inputSchema: {
4148
4371
  type: "object",
4149
4372
  properties: {
@@ -4304,7 +4527,7 @@ function createToolDefinitions() {
4304
4527
  title: "Auto-Capture Review",
4305
4528
  readOnlyHint: false,
4306
4529
  destructiveHint: false,
4307
- idempotentHint: true,
4530
+ idempotentHint: false,
4308
4531
  openWorldHint: false
4309
4532
  }
4310
4533
  }
@@ -4693,25 +4916,38 @@ async function handleAgentSearch(client, args) {
4693
4916
  return { content: [{ type: "text", text }] };
4694
4917
  }
4695
4918
 
4696
- // src/shared/mcp/functions/handle-tool-call.function.ts
4919
+ // src/shared/mcp/functions/as-string.function.ts
4697
4920
  function asString2(value, fallback) {
4698
4921
  return typeof value === "string" ? value : fallback;
4699
4922
  }
4700
- function asBooleanOrUndefined(value) {
4701
- return typeof value === "boolean" ? value : void 0;
4702
- }
4923
+
4924
+ // src/shared/mcp/functions/as-string-array-or-undefined.function.ts
4703
4925
  function asStringArrayOrUndefined(value) {
4704
4926
  if (Array.isArray(value) && value.every((item) => typeof item === "string")) {
4705
4927
  return value;
4706
4928
  }
4707
4929
  return void 0;
4708
4930
  }
4709
- function asStringArray2(value) {
4710
- if (Array.isArray(value) && value.every((item) => typeof item === "string")) {
4711
- return value;
4712
- }
4713
- return [];
4931
+
4932
+ // src/shared/mcp/functions/as-boolean-or-undefined.function.ts
4933
+ function asBooleanOrUndefined(value) {
4934
+ return typeof value === "boolean" ? value : void 0;
4935
+ }
4936
+
4937
+ // src/shared/mcp/functions/parse-learn-args.function.ts
4938
+ function parseLearnArgs(args) {
4939
+ return {
4940
+ sectionType: asString2(args["type"], "text/markdown"),
4941
+ title: typeof args["title"] === "string" ? args["title"] : void 0,
4942
+ description: typeof args["description"] === "string" ? args["description"] : void 0,
4943
+ docid: typeof args["docid"] === "string" ? args["docid"] : void 0,
4944
+ tags: asStringArrayOrUndefined(args["tags"]),
4945
+ replace: asBooleanOrUndefined(args["replace"]),
4946
+ level: typeof args["level"] === "number" ? args["level"] : void 0
4947
+ };
4714
4948
  }
4949
+
4950
+ // src/shared/mcp/functions/unwrap-json-content.function.ts
4715
4951
  function unwrapJsonContent(raw) {
4716
4952
  const trimmed = raw.trim();
4717
4953
  if (!trimmed.startsWith("{")) return raw;
@@ -4724,6 +4960,158 @@ function unwrapJsonContent(raw) {
4724
4960
  }
4725
4961
  return raw;
4726
4962
  }
4963
+
4964
+ // src/shared/mcp/functions/determine-learn-split.function.ts
4965
+ function determineLearnSplit(content, sectionType, title) {
4966
+ const splittable = sectionType.startsWith("text/") || sectionType === "markdown" || sectionType === "application/toml";
4967
+ if (title !== void 0 || !splittable) return void 0;
4968
+ const result = splitContent(content, sectionType);
4969
+ const hasTitledSection = result.sections.some(
4970
+ (section) => section.title !== void 0
4971
+ );
4972
+ return result.sections.length >= 2 && hasTitledSection ? result : void 0;
4973
+ }
4974
+
4975
+ // src/shared/mcp/functions/learn-with-splitting.function.ts
4976
+ async function learnWithSplitting(client, splitResult, params) {
4977
+ const { sections } = splitResult;
4978
+ const firstTitled = sections.find((s) => s.title !== void 0);
4979
+ const docTitle = firstTitled?.title ?? params.docid ?? "Untitled";
4980
+ const docid = params.docid ?? firstTitled?.title;
4981
+ const kbids = [];
4982
+ for (const section of sections) {
4983
+ const [parent] = resolveParentIds([section], kbids);
4984
+ const result = await client.addSection({
4985
+ sectionType: params.sectionType,
4986
+ content: section.content,
4987
+ title: section.title,
4988
+ description: params.description,
4989
+ docid,
4990
+ tags: params.tags,
4991
+ replace: params.replace,
4992
+ level: section.level,
4993
+ parent: parent ?? void 0
4994
+ });
4995
+ kbids.push(result.items[0] ?? "");
4996
+ }
4997
+ return { kbids, docTitle };
4998
+ }
4999
+
5000
+ // src/shared/mcp/functions/store-split-learn-sections.function.ts
5001
+ async function storeSplitLearnSections(client, splitResult, params) {
5002
+ const { kbids, docTitle } = await learnWithSplitting(
5003
+ client,
5004
+ splitResult,
5005
+ params
5006
+ );
5007
+ await client.groupSections(docTitle, kbids);
5008
+ return {
5009
+ items: kbids,
5010
+ total: kbids.length,
5011
+ offset: 0,
5012
+ superseded: null,
5013
+ warnings: [],
5014
+ near_duplicates: []
5015
+ };
5016
+ }
5017
+
5018
+ // src/shared/mcp/functions/build-learn-payload.function.ts
5019
+ function buildLearnPayload(result) {
5020
+ return {
5021
+ items: result.items,
5022
+ total: result.total,
5023
+ offset: result.offset,
5024
+ superseded: result.superseded ?? null,
5025
+ warnings: result.warnings ?? [],
5026
+ near_duplicates: result.near_duplicates ?? []
5027
+ };
5028
+ }
5029
+
5030
+ // src/shared/mcp/functions/handle-tool-learn.function.ts
5031
+ async function handleToolLearn(client, args) {
5032
+ const learnArgs = parseLearnArgs(args);
5033
+ const { sectionType, title, level, ...shared } = learnArgs;
5034
+ if (sectionType.startsWith("image/") && !title) {
5035
+ return {
5036
+ content: [
5037
+ {
5038
+ type: "text",
5039
+ text: "title is required for image/* types"
5040
+ }
5041
+ ],
5042
+ isError: true
5043
+ };
5044
+ }
5045
+ const content = unwrapJsonContent(asString2(args["content"], ""));
5046
+ const splitResult = determineLearnSplit(content, sectionType, title);
5047
+ const result = splitResult ? await storeSplitLearnSections(client, splitResult, {
5048
+ sectionType,
5049
+ ...shared
5050
+ }) : await client.addSection({
5051
+ sectionType,
5052
+ content,
5053
+ title,
5054
+ level,
5055
+ ...shared
5056
+ });
5057
+ return {
5058
+ content: [
5059
+ {
5060
+ type: "text",
5061
+ text: JSON.stringify(buildLearnPayload(result))
5062
+ }
5063
+ ]
5064
+ };
5065
+ }
5066
+
5067
+ // src/shared/mcp/functions/handle-tool-skill-learn.function.ts
5068
+ async function handleToolSkillLearn(client, args) {
5069
+ const name = asString2(args["name"], "");
5070
+ if (!name) {
5071
+ return {
5072
+ content: [{ type: "text", text: "name is required" }],
5073
+ isError: true
5074
+ };
5075
+ }
5076
+ const body = asString2(args["body"], "");
5077
+ if (!body) {
5078
+ return {
5079
+ content: [{ type: "text", text: "body is required" }],
5080
+ isError: true
5081
+ };
5082
+ }
5083
+ const description = typeof args["description"] === "string" ? args["description"] : void 0;
5084
+ const skillArgs = extractSkillArguments(body);
5085
+ const skill = {
5086
+ name,
5087
+ description,
5088
+ arguments: skillArgs.length > 0 ? skillArgs : void 0,
5089
+ body
5090
+ };
5091
+ const json = serializeSkillContent(skill);
5092
+ const result = await client.addSection({
5093
+ sectionType: "skill",
5094
+ content: json,
5095
+ title: name,
5096
+ description
5097
+ });
5098
+ return {
5099
+ content: [
5100
+ {
5101
+ type: "text",
5102
+ text: `Created skill "${name}" (kbid: ${result.items[0]})`
5103
+ }
5104
+ ]
5105
+ };
5106
+ }
5107
+
5108
+ // src/shared/mcp/functions/handle-tool-call.function.ts
5109
+ function asStringArray2(value) {
5110
+ if (Array.isArray(value) && value.every((item) => typeof item === "string")) {
5111
+ return value;
5112
+ }
5113
+ return [];
5114
+ }
4727
5115
  async function handleToolCall(client, name, args) {
4728
5116
  log("tool call: " + name, 0 /* DEBUG */);
4729
5117
  try {
@@ -4731,7 +5119,7 @@ async function handleToolCall(client, name, args) {
4731
5119
  case "search":
4732
5120
  return await handleSearch2(client, args);
4733
5121
  case "learn":
4734
- return await handleLearn2(client, args);
5122
+ return await handleToolLearn(client, args);
4735
5123
  case "unlearn":
4736
5124
  return await handleUnlearn2(client, args);
4737
5125
  case "retrieve":
@@ -4751,7 +5139,7 @@ async function handleToolCall(client, name, args) {
4751
5139
  case "export":
4752
5140
  return await handleExport2(client, args);
4753
5141
  case "skill-learn":
4754
- return await handleSkillLearn(client, args);
5142
+ return await handleToolSkillLearn(client, args);
4755
5143
  case "skill-list":
4756
5144
  return await handleSkillList(client, args);
4757
5145
  case "skill-get":
@@ -4816,47 +5204,6 @@ async function handleSearch2(client, args) {
4816
5204
  ]
4817
5205
  };
4818
5206
  }
4819
- async function handleLearn2(client, args) {
4820
- const sectionType = asString2(args["type"], "text/markdown");
4821
- const title = typeof args["title"] === "string" ? args["title"] : void 0;
4822
- const description = typeof args["description"] === "string" ? args["description"] : void 0;
4823
- const docid = typeof args["docid"] === "string" ? args["docid"] : void 0;
4824
- const tags = asStringArrayOrUndefined(args["tags"]);
4825
- const replace = asBooleanOrUndefined(args["replace"]);
4826
- if (sectionType.startsWith("image/") && !title) {
4827
- return {
4828
- content: [
4829
- {
4830
- type: "text",
4831
- text: "title is required for image/* types"
4832
- }
4833
- ],
4834
- isError: true
4835
- };
4836
- }
4837
- const rawContent = asString2(args["content"], "");
4838
- const content = unwrapJsonContent(rawContent);
4839
- const result = await client.addSection({
4840
- sectionType,
4841
- content,
4842
- title,
4843
- description,
4844
- docid,
4845
- tags,
4846
- replace
4847
- });
4848
- const payload = {
4849
- items: result.items,
4850
- total: result.total,
4851
- offset: result.offset,
4852
- superseded: result.superseded ?? null,
4853
- warnings: result.warnings ?? [],
4854
- near_duplicates: result.near_duplicates ?? []
4855
- };
4856
- return {
4857
- content: [{ type: "text", text: JSON.stringify(payload) }]
4858
- };
4859
- }
4860
5207
  async function handleUnlearn2(client, args) {
4861
5208
  const kbid = asString2(args["kbid"], "");
4862
5209
  await client.removeSection(kbid);
@@ -4963,45 +5310,6 @@ async function handleRebuild2(client) {
4963
5310
  const text = `Rebuild complete: ${String(report.sections_reindexed)} sections reindexed, ${String(report.terms_indexed)} terms indexed.`;
4964
5311
  return { content: [{ type: "text", text }] };
4965
5312
  }
4966
- async function handleSkillLearn(client, args) {
4967
- const name = asString2(args["name"], "");
4968
- if (!name) {
4969
- return {
4970
- content: [{ type: "text", text: "name is required" }],
4971
- isError: true
4972
- };
4973
- }
4974
- const body = asString2(args["body"], "");
4975
- if (!body) {
4976
- return {
4977
- content: [{ type: "text", text: "body is required" }],
4978
- isError: true
4979
- };
4980
- }
4981
- const description = typeof args["description"] === "string" ? args["description"] : void 0;
4982
- const skillArgs = extractSkillArguments(body);
4983
- const skill = {
4984
- name,
4985
- description,
4986
- arguments: skillArgs.length > 0 ? skillArgs : void 0,
4987
- body
4988
- };
4989
- const json = serializeSkillContent(skill);
4990
- const result = await client.addSection({
4991
- sectionType: "skill",
4992
- content: json,
4993
- title: name,
4994
- description
4995
- });
4996
- return {
4997
- content: [
4998
- {
4999
- type: "text",
5000
- text: `Created skill "${name}" (kbid: ${result.items[0]})`
5001
- }
5002
- ]
5003
- };
5004
- }
5005
5313
  async function handleSkillList(client, args) {
5006
5314
  const records = await client.listByType("skill");
5007
5315
  const query = typeof args["query"] === "string" ? args["query"].toLowerCase() : "";