@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.mjs CHANGED
@@ -23,7 +23,10 @@ var SUPPORTED_EXTENSIONS = [
23
23
  ".md",
24
24
  ".txt",
25
25
  ".rst",
26
- ".adoc"
26
+ ".adoc",
27
+ ".html",
28
+ ".htm",
29
+ ".toml"
27
30
  ];
28
31
 
29
32
  // src/shared/cli/functions/parse-args.function.ts
@@ -659,13 +662,102 @@ function computeDirectoryLevel(filePath, basePath) {
659
662
  return Math.min(depth + 1, MAX_LEVEL);
660
663
  }
661
664
 
662
- // src/shared/cli/functions/learn-markdown-document.function.ts
663
- import { basename as basename2 } from "node:path";
665
+ // src/shared/content-splitter/constants/content-type-maps.constant.ts
666
+ var HINT_FORMAT_MAP = {
667
+ markdown: "markdown",
668
+ "text/markdown": "markdown",
669
+ md: "markdown",
670
+ html: "html",
671
+ "text/html": "html",
672
+ toml: "toml",
673
+ "application/toml": "toml",
674
+ "text/toml": "toml",
675
+ "text/x-toml": "toml"
676
+ };
677
+ var EXTENSION_FORMAT_MAP = {
678
+ md: "markdown",
679
+ markdown: "markdown",
680
+ html: "html",
681
+ htm: "html",
682
+ toml: "toml"
683
+ };
664
684
 
665
- // src/shared/cli/functions/split-markdown-sections.function.ts
685
+ // src/shared/content-splitter/functions/detect-content-type.function.ts
686
+ function detectContentType(content, typeHint, filePath) {
687
+ const hinted = typeHint && HINT_FORMAT_MAP[typeHint.toLowerCase()];
688
+ if (hinted) return hinted;
689
+ const extension = filePath?.split(".").pop()?.toLowerCase();
690
+ const extMatch = extension && EXTENSION_FORMAT_MAP[extension];
691
+ if (extMatch) return extMatch;
692
+ return sniffContentType(content);
693
+ }
694
+ function sniffContentType(content) {
695
+ const trimmed = content.trimStart();
696
+ if (/^<!DOCTYPE/i.test(trimmed) || /^<html/i.test(trimmed)) {
697
+ return "html";
698
+ }
699
+ if (trimmed.startsWith("[") && trimmed.includes("=")) {
700
+ return "toml";
701
+ }
702
+ return "plain-text";
703
+ }
704
+
705
+ // src/shared/content-splitter/classes/heading-parent-tracker.class.ts
706
+ var HeadingParentTracker = class {
707
+ stack = [];
708
+ /**
709
+ * Resolves the parent index for a heading at `level`
710
+ * appearing at output position `index`, then records the
711
+ * heading so subsequent, deeper headings can nest under it.
712
+ *
713
+ * @param level - heading depth, shallower is smaller
714
+ * @param index - position of this heading in the output
715
+ * sections array
716
+ * @returns the parent's output index, or `null` when the
717
+ * heading is at the root
718
+ */
719
+ resolve(level, index) {
720
+ while (this.stack.length > 0 && this.stack[this.stack.length - 1].level >= level) {
721
+ this.stack.pop();
722
+ }
723
+ const parentIndex = this.stack.length > 0 ? this.stack[this.stack.length - 1].index : null;
724
+ this.stack.push({ level, index });
725
+ return parentIndex;
726
+ }
727
+ };
728
+
729
+ // src/shared/content-splitter/functions/push-content-section.function.ts
730
+ function pushContentSection(sections, tracker, content, title, level) {
731
+ if (level === void 0) {
732
+ sections.push({ content, parentIndex: null });
733
+ return;
734
+ }
735
+ sections.push({
736
+ title,
737
+ level,
738
+ content,
739
+ parentIndex: tracker.resolve(level, sections.length)
740
+ });
741
+ }
742
+
743
+ // src/shared/content-splitter/functions/flush-markdown-draft.function.ts
744
+ function flushMarkdownDraft(draft, sections, tracker) {
745
+ const body = draft.lines.join("\n").trimEnd();
746
+ if (draft.title === void 0 && body.length === 0) return;
747
+ pushContentSection(
748
+ sections,
749
+ tracker,
750
+ body,
751
+ draft.title,
752
+ draft.level
753
+ );
754
+ }
755
+
756
+ // src/shared/content-splitter/functions/split-markdown.function.ts
666
757
  var HEADING_PATTERN = /^(#{1,6})\s+(.+)$/;
667
- function splitMarkdownSections(content) {
758
+ function splitMarkdown(content) {
668
759
  const sections = [];
760
+ const tracker = new HeadingParentTracker();
669
761
  let draft = { lines: [] };
670
762
  let inCodeBlock = false;
671
763
  for (const line of content.split("\n")) {
@@ -676,7 +768,7 @@ function splitMarkdownSections(content) {
676
768
  }
677
769
  const heading = inCodeBlock ? null : HEADING_PATTERN.exec(line);
678
770
  if (heading) {
679
- flushSection(draft, sections);
771
+ flushMarkdownDraft(draft, sections, tracker);
680
772
  draft = {
681
773
  title: heading[2].trim(),
682
774
  level: heading[1].length,
@@ -686,14 +778,113 @@ function splitMarkdownSections(content) {
686
778
  }
687
779
  draft.lines.push(line);
688
780
  }
689
- flushSection(draft, sections);
690
- return sections.length > 0 ? sections : [{ content: content.trimEnd() }];
781
+ flushMarkdownDraft(draft, sections, tracker);
782
+ return sections.length > 0 ? sections : [{ content: content.trimEnd(), parentIndex: null }];
783
+ }
784
+
785
+ // src/shared/content-splitter/functions/split-html.function.ts
786
+ var HEADING_PATTERN2 = /<h([1-6])[^>]*>(.*?)<\/h\1>/gi;
787
+ function splitHtml(content) {
788
+ const matches = [...content.matchAll(HEADING_PATTERN2)];
789
+ if (matches.length === 0) {
790
+ return [{ content: content.trimEnd(), parentIndex: null }];
791
+ }
792
+ const sections = [];
793
+ const tracker = new HeadingParentTracker();
794
+ const firstIndex = matches[0].index;
795
+ const preamble = content.slice(0, firstIndex).trimEnd();
796
+ if (preamble.length > 0) {
797
+ pushContentSection(sections, tracker, preamble);
798
+ }
799
+ matches.forEach((match, i) => {
800
+ const start = match.index + match[0].length;
801
+ const end = matches[i + 1]?.index ?? content.length;
802
+ pushContentSection(
803
+ sections,
804
+ tracker,
805
+ content.slice(start, end).trimEnd(),
806
+ match[2].trim(),
807
+ Number(match[1])
808
+ );
809
+ });
810
+ return sections;
691
811
  }
692
- function flushSection(draft, sections) {
812
+
813
+ // src/shared/content-splitter/functions/flush-toml-draft.function.ts
814
+ function flushTomlDraft(draft, sections, pathIndex) {
693
815
  const body = draft.lines.join("\n").trimEnd();
694
816
  if (draft.title === void 0 && body.length === 0) return;
695
- sections.push(
696
- draft.title === void 0 ? { content: body } : { title: draft.title, level: draft.level, content: body }
817
+ if (draft.title === void 0) {
818
+ sections.push({ content: body, parentIndex: null });
819
+ return;
820
+ }
821
+ const path = draft.path ?? draft.title;
822
+ sections.push({
823
+ title: draft.title,
824
+ content: body,
825
+ parentIndex: resolveParentIndex(path, pathIndex)
826
+ });
827
+ pathIndex.set(path, sections.length - 1);
828
+ }
829
+ function resolveParentIndex(path, pathIndex) {
830
+ if (/^["']/.test(path)) return null;
831
+ const segments = path.split(".");
832
+ segments.pop();
833
+ if (segments.length === 0) return null;
834
+ return pathIndex.get(segments.join(".")) ?? null;
835
+ }
836
+
837
+ // src/shared/content-splitter/functions/split-toml.function.ts
838
+ var HEADER_PATTERN = /^\[([^\]]+)\]\s*$/;
839
+ var QUOTED_PATTERN = /^(["'])(.*)\1$/;
840
+ function splitToml(content) {
841
+ const sections = [];
842
+ const pathIndex = /* @__PURE__ */ new Map();
843
+ let draft = { lines: [] };
844
+ for (const line of content.split("\n")) {
845
+ const header = HEADER_PATTERN.exec(line);
846
+ if (header) {
847
+ flushTomlDraft(draft, sections, pathIndex);
848
+ draft = { ...parseHeader(header[1]), lines: [] };
849
+ continue;
850
+ }
851
+ draft.lines.push(line);
852
+ }
853
+ flushTomlDraft(draft, sections, pathIndex);
854
+ return sections.length > 0 ? sections : [{ content: content.trimEnd(), parentIndex: null }];
855
+ }
856
+ function parseHeader(inner) {
857
+ const trimmed = inner.trim();
858
+ const quoted = QUOTED_PATTERN.exec(trimmed);
859
+ return quoted ? { title: quoted[2], path: trimmed } : { title: trimmed, path: trimmed };
860
+ }
861
+
862
+ // src/shared/content-splitter/functions/split-plain-text.function.ts
863
+ function splitPlainText(content) {
864
+ return [{ content, parentIndex: null }];
865
+ }
866
+
867
+ // src/shared/content-splitter/constants/split-strategy-map.constant.ts
868
+ var SPLIT_STRATEGY_MAP = {
869
+ markdown: splitMarkdown,
870
+ html: splitHtml,
871
+ toml: splitToml,
872
+ "plain-text": splitPlainText
873
+ };
874
+
875
+ // src/shared/content-splitter/functions/split-content.function.ts
876
+ function splitContent(content, typeHint, filePath) {
877
+ const format = detectContentType(content, typeHint, filePath);
878
+ return {
879
+ format,
880
+ sections: SPLIT_STRATEGY_MAP[format](content)
881
+ };
882
+ }
883
+
884
+ // src/shared/content-splitter/functions/resolve-parent-ids.function.ts
885
+ function resolveParentIds(sections, kbids) {
886
+ return sections.map(
887
+ (section) => section.parentIndex === null ? null : kbids[section.parentIndex]
697
888
  );
698
889
  }
699
890
 
@@ -712,15 +903,16 @@ function mapAddSectionResult(result, path, size, sectionType) {
712
903
  };
713
904
  }
714
905
 
715
- // src/shared/cli/functions/learn-markdown-document.function.ts
716
- async function learnMarkdownDocument(client, options, path, content, sourcePath) {
717
- const sections = splitMarkdownSections(content);
718
- const titled = sections.filter((s) => s.title !== void 0);
719
- if (sections.length < 2 || titled.length === 0) return void 0;
720
- const docTitle = titled[0]?.title ?? basename2(path);
906
+ // src/shared/cli/functions/learn-structured-document.function.ts
907
+ import { basename as basename2 } from "node:path";
908
+ async function learnStructuredDocument(client, options, path, splitResult, sourcePath) {
909
+ const { sections } = splitResult;
910
+ const firstTitled = sections.find((s) => s.title !== void 0);
911
+ const docTitle = firstTitled?.title ?? basename2(path);
721
912
  const kbids = [];
722
913
  const results = [];
723
914
  for (const section of sections) {
915
+ const [parent] = resolveParentIds([section], kbids);
724
916
  const added = await client.addSection({
725
917
  sectionType: "text",
726
918
  content: section.content,
@@ -730,7 +922,8 @@ async function learnMarkdownDocument(client, options, path, content, sourcePath)
730
922
  tags: options.tags,
731
923
  replace: options.replace,
732
924
  sourcePath,
733
- level: section.level
925
+ level: section.level,
926
+ parent: parent ?? void 0
734
927
  });
735
928
  kbids.push(added.items[0] ?? "");
736
929
  results.push(
@@ -748,16 +941,25 @@ async function learnMarkdownDocument(client, options, path, content, sourcePath)
748
941
 
749
942
  // src/shared/cli/functions/learn-target-content.function.ts
750
943
  async function learnTargetContent(client, options, path, content, sourcePath, level) {
751
- const markdownEligible = (path === "-" || path.endsWith(".md")) && !options.title;
752
- if (markdownEligible) {
753
- const split = await learnMarkdownDocument(
754
- client,
755
- options,
756
- path,
944
+ if (!options.title) {
945
+ const isStdin = path === "-";
946
+ const split = splitContent(
757
947
  content,
758
- sourcePath
948
+ isStdin ? "markdown" : void 0,
949
+ isStdin ? void 0 : path
950
+ );
951
+ const hasTitled = split.sections.some(
952
+ (s) => s.title !== void 0
759
953
  );
760
- if (split) return split;
954
+ if (split.sections.length >= 2 && hasTitled) {
955
+ return learnStructuredDocument(
956
+ client,
957
+ options,
958
+ path,
959
+ split,
960
+ sourcePath
961
+ );
962
+ }
761
963
  }
762
964
  const result = await client.addSection({
763
965
  sectionType: "text",
@@ -1832,7 +2034,7 @@ async function handleTypedSearch(client, options, type) {
1832
2034
  }
1833
2035
 
1834
2036
  // src/shared/version/constants/version.constant.ts
1835
- var VERSION = "0.6.4";
2037
+ var VERSION = "0.6.5";
1836
2038
 
1837
2039
  // src/shared/cli/functions/generate-help.function.ts
1838
2040
  var KNOWN_COMMANDS = /* @__PURE__ */ new Set([
@@ -1911,7 +2113,7 @@ Usage: kbdb [options]
1911
2113
  kbdb <command> <subcommand> [options] ...[params]
1912
2114
 
1913
2115
  Knowledge Base:
1914
- learn Import content (splits .md on headings)
2116
+ learn Import content (auto-splits structured formats)
1915
2117
  unlearn Remove sections by kbid
1916
2118
  search Query the knowledge base (sibling boost)
1917
2119
  recall Retrieve section context by kbid
@@ -1935,25 +2137,31 @@ Skills:
1935
2137
  skill list List skills (filter by keyword)
1936
2138
  skill get Retrieve a skill by name or kbid
1937
2139
  skill delete Remove a skill
2140
+ skill search Search skills by keyword
1938
2141
 
1939
2142
  Agents:
1940
2143
  agent create Create an agent with skill refs
1941
2144
  agent list List agents (filter by keyword)
1942
2145
  agent get Retrieve an agent by docid
1943
2146
  agent delete Remove an agent
1944
-
1945
- Worker:
1946
- worker stop Stop the worker daemon
1947
- worker restart Restart the worker daemon
2147
+ agent search Search agents by keyword
1948
2148
 
1949
2149
  Server:
1950
2150
  mcp Start MCP server mode (stdio)
1951
2151
 
1952
2152
  Global Options:
1953
2153
  -h, --help Show this help message
1954
- -v, --version Show version information
2154
+ --version Show version information
2155
+ -v, --verbose Log warnings (-vv info, -vvv debug)
1955
2156
  --db <path> Path to .kbdb directory
1956
- --format <fmt> Output format (json|text|mcp)
2157
+ --format <fmt> Output format (rec|json|text|mcp)
2158
+ --timeout-ms <ms> Request timeout (default 30000)
2159
+ --retry <n> Retries on timeout (default 1)
2160
+ --non-interactive Suppress prompts
2161
+
2162
+ Worker:
2163
+ worker stop Stop background daemon
2164
+ worker restart Restart background daemon
1957
2165
 
1958
2166
  Run "kbdb <command> --help" for detailed information.
1959
2167
  `;
@@ -2010,14 +2218,19 @@ Options:
2010
2218
  --replace Supersede existing section by source key
2011
2219
  --db <path> Path to .kbdb directory
2012
2220
 
2013
- Markdown splitting:
2014
- .md files are automatically split on ATX
2015
- headings (# through ######) into independent
2016
- sections. Each section uses its heading as
2017
- the title and shares a docid for document
2018
- grouping. Providing --title imports the file
2019
- as a single section (skips splitting).
2020
- Non-.md files are never split.
2221
+ Content splitting:
2222
+ Structured formats are automatically split
2223
+ into independent sections that share a docid.
2224
+ Each section carries a level (heading depth)
2225
+ and parent (kbid of enclosing section).
2226
+ Providing --title imports as a single section
2227
+ (skips splitting).
2228
+
2229
+ Supported formats:
2230
+ Markdown .md, text/markdown ATX headings
2231
+ HTML .html, text/html <h1>-<h6> tags
2232
+ TOML .toml, application/toml [section]
2233
+ Plain .txt, text/plain no splitting
2021
2234
 
2022
2235
  Output:
2023
2236
  { items, total, offset } envelope
@@ -2055,7 +2268,7 @@ Arguments:
2055
2268
  Options:
2056
2269
  --mode <mode> Result granularity (sections|documents|stats)
2057
2270
  --limit <n> Max results (default: 20)
2058
- --format <fmt> Output format (json|text|mcp)
2271
+ --format <fmt> Output format (rec|json|text|mcp)
2059
2272
  --content, -c Hydrate results with section content
2060
2273
  --db <path> Path to .kbdb directory
2061
2274
 
@@ -2065,6 +2278,11 @@ Scoring:
2065
2278
  sibling score boost when multiple sections
2066
2279
  match the query.
2067
2280
 
2281
+ Fields:
2282
+ Each result includes level (heading depth,
2283
+ 1-6 or null) and parent (kbid of enclosing
2284
+ section or null).
2285
+
2068
2286
  Output:
2069
2287
  { items, total, offset } envelope
2070
2288
 
@@ -2086,7 +2304,8 @@ Options:
2086
2304
  --db <path> Path to .kbdb directory
2087
2305
 
2088
2306
  Output:
2089
- JSON RecallResult or RecallResult[]
2307
+ JSON RecallResult or RecallResult[] with
2308
+ level, parent, siblings, and references
2090
2309
 
2091
2310
  Examples:
2092
2311
  kbdb recall abcdef1234567890abcdef1234
@@ -2095,11 +2314,14 @@ Examples:
2095
2314
  content: `kbdb content - Compose rendered content from ids
2096
2315
 
2097
2316
  Usage: kbdb content [options] <id>...
2317
+ kbdb content --search <query>...
2098
2318
 
2099
2319
  Arguments:
2100
2320
  <id> kbid or docid identifier
2321
+ <query> Search terms (with --search)
2101
2322
 
2102
2323
  Options:
2324
+ --search, -s Search for ids by keyword
2103
2325
  --db <path> Path to .kbdb directory
2104
2326
 
2105
2327
  Output:
@@ -2108,6 +2330,7 @@ Output:
2108
2330
  Examples:
2109
2331
  kbdb content abcdef1234567890abcdef1234
2110
2332
  kbdb content id1 id2
2333
+ kbdb content --search machine learning
2111
2334
  `,
2112
2335
  check: `kbdb check - Verify knowledge base integrity
2113
2336
 
@@ -3617,7 +3840,7 @@ function createToolDefinitions() {
3617
3840
  return [
3618
3841
  {
3619
3842
  name: "search",
3620
- 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.",
3843
+ 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.",
3621
3844
  inputSchema: {
3622
3845
  type: "object",
3623
3846
  properties: {
@@ -3660,7 +3883,7 @@ function createToolDefinitions() {
3660
3883
  },
3661
3884
  {
3662
3885
  name: "learn",
3663
- 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.",
3886
+ 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.",
3664
3887
  inputSchema: {
3665
3888
  type: "object",
3666
3889
  properties: {
@@ -3670,11 +3893,11 @@ function createToolDefinitions() {
3670
3893
  },
3671
3894
  type: {
3672
3895
  type: "string",
3673
- description: "MIME type (default: text/markdown). Use image/* for images."
3896
+ 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."
3674
3897
  },
3675
3898
  title: {
3676
3899
  type: "string",
3677
- description: "Section title. Skips markdown splitting when set. Required for image/* types."
3900
+ description: "Section title. Skips content splitting when set. Required for image/* types."
3678
3901
  },
3679
3902
  description: {
3680
3903
  type: "string",
@@ -3682,7 +3905,7 @@ function createToolDefinitions() {
3682
3905
  },
3683
3906
  docid: {
3684
3907
  type: "string",
3685
- description: "Document ID to group sections under. Set automatically when markdown splitting is active."
3908
+ description: "Document ID to group sections under. Set automatically when content splitting is active."
3686
3909
  },
3687
3910
  tags: {
3688
3911
  type: "array",
@@ -3751,7 +3974,7 @@ function createToolDefinitions() {
3751
3974
  },
3752
3975
  {
3753
3976
  name: "recall",
3754
- 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.",
3977
+ 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.",
3755
3978
  inputSchema: {
3756
3979
  type: "object",
3757
3980
  properties: {
@@ -3882,7 +4105,7 @@ function createToolDefinitions() {
3882
4105
  },
3883
4106
  {
3884
4107
  name: "skill-learn",
3885
- description: "Create or update a skill definition. Skills are reusable AI agent capabilities with template placeholders.",
4108
+ description: "Create or update a skill definition. Skills are reusable AI agent capabilities with template placeholders. Stored as atomic JSON (not split into sections).",
3886
4109
  inputSchema: {
3887
4110
  type: "object",
3888
4111
  properties: {
@@ -3976,7 +4199,7 @@ function createToolDefinitions() {
3976
4199
  },
3977
4200
  {
3978
4201
  name: "agent-create",
3979
- description: "Create an AI agent with a persona and optional skill references.",
4202
+ description: "Create an AI agent with a persona and optional skill references. Stored as atomic JSON (not split into sections).",
3980
4203
  inputSchema: {
3981
4204
  type: "object",
3982
4205
  properties: {
@@ -4137,7 +4360,7 @@ function createToolDefinitions() {
4137
4360
  title: "Auto-Capture Review",
4138
4361
  readOnlyHint: false,
4139
4362
  destructiveHint: false,
4140
- idempotentHint: true,
4363
+ idempotentHint: false,
4141
4364
  openWorldHint: false
4142
4365
  }
4143
4366
  }
@@ -4526,25 +4749,38 @@ async function handleAgentSearch(client, args) {
4526
4749
  return { content: [{ type: "text", text }] };
4527
4750
  }
4528
4751
 
4529
- // src/shared/mcp/functions/handle-tool-call.function.ts
4752
+ // src/shared/mcp/functions/as-string.function.ts
4530
4753
  function asString2(value, fallback) {
4531
4754
  return typeof value === "string" ? value : fallback;
4532
4755
  }
4533
- function asBooleanOrUndefined(value) {
4534
- return typeof value === "boolean" ? value : void 0;
4535
- }
4756
+
4757
+ // src/shared/mcp/functions/as-string-array-or-undefined.function.ts
4536
4758
  function asStringArrayOrUndefined(value) {
4537
4759
  if (Array.isArray(value) && value.every((item) => typeof item === "string")) {
4538
4760
  return value;
4539
4761
  }
4540
4762
  return void 0;
4541
4763
  }
4542
- function asStringArray2(value) {
4543
- if (Array.isArray(value) && value.every((item) => typeof item === "string")) {
4544
- return value;
4545
- }
4546
- return [];
4764
+
4765
+ // src/shared/mcp/functions/as-boolean-or-undefined.function.ts
4766
+ function asBooleanOrUndefined(value) {
4767
+ return typeof value === "boolean" ? value : void 0;
4768
+ }
4769
+
4770
+ // src/shared/mcp/functions/parse-learn-args.function.ts
4771
+ function parseLearnArgs(args) {
4772
+ return {
4773
+ sectionType: asString2(args["type"], "text/markdown"),
4774
+ title: typeof args["title"] === "string" ? args["title"] : void 0,
4775
+ description: typeof args["description"] === "string" ? args["description"] : void 0,
4776
+ docid: typeof args["docid"] === "string" ? args["docid"] : void 0,
4777
+ tags: asStringArrayOrUndefined(args["tags"]),
4778
+ replace: asBooleanOrUndefined(args["replace"]),
4779
+ level: typeof args["level"] === "number" ? args["level"] : void 0
4780
+ };
4547
4781
  }
4782
+
4783
+ // src/shared/mcp/functions/unwrap-json-content.function.ts
4548
4784
  function unwrapJsonContent(raw) {
4549
4785
  const trimmed = raw.trim();
4550
4786
  if (!trimmed.startsWith("{")) return raw;
@@ -4557,6 +4793,158 @@ function unwrapJsonContent(raw) {
4557
4793
  }
4558
4794
  return raw;
4559
4795
  }
4796
+
4797
+ // src/shared/mcp/functions/determine-learn-split.function.ts
4798
+ function determineLearnSplit(content, sectionType, title) {
4799
+ const splittable = sectionType.startsWith("text/") || sectionType === "markdown" || sectionType === "application/toml";
4800
+ if (title !== void 0 || !splittable) return void 0;
4801
+ const result = splitContent(content, sectionType);
4802
+ const hasTitledSection = result.sections.some(
4803
+ (section) => section.title !== void 0
4804
+ );
4805
+ return result.sections.length >= 2 && hasTitledSection ? result : void 0;
4806
+ }
4807
+
4808
+ // src/shared/mcp/functions/learn-with-splitting.function.ts
4809
+ async function learnWithSplitting(client, splitResult, params) {
4810
+ const { sections } = splitResult;
4811
+ const firstTitled = sections.find((s) => s.title !== void 0);
4812
+ const docTitle = firstTitled?.title ?? params.docid ?? "Untitled";
4813
+ const docid = params.docid ?? firstTitled?.title;
4814
+ const kbids = [];
4815
+ for (const section of sections) {
4816
+ const [parent] = resolveParentIds([section], kbids);
4817
+ const result = await client.addSection({
4818
+ sectionType: params.sectionType,
4819
+ content: section.content,
4820
+ title: section.title,
4821
+ description: params.description,
4822
+ docid,
4823
+ tags: params.tags,
4824
+ replace: params.replace,
4825
+ level: section.level,
4826
+ parent: parent ?? void 0
4827
+ });
4828
+ kbids.push(result.items[0] ?? "");
4829
+ }
4830
+ return { kbids, docTitle };
4831
+ }
4832
+
4833
+ // src/shared/mcp/functions/store-split-learn-sections.function.ts
4834
+ async function storeSplitLearnSections(client, splitResult, params) {
4835
+ const { kbids, docTitle } = await learnWithSplitting(
4836
+ client,
4837
+ splitResult,
4838
+ params
4839
+ );
4840
+ await client.groupSections(docTitle, kbids);
4841
+ return {
4842
+ items: kbids,
4843
+ total: kbids.length,
4844
+ offset: 0,
4845
+ superseded: null,
4846
+ warnings: [],
4847
+ near_duplicates: []
4848
+ };
4849
+ }
4850
+
4851
+ // src/shared/mcp/functions/build-learn-payload.function.ts
4852
+ function buildLearnPayload(result) {
4853
+ return {
4854
+ items: result.items,
4855
+ total: result.total,
4856
+ offset: result.offset,
4857
+ superseded: result.superseded ?? null,
4858
+ warnings: result.warnings ?? [],
4859
+ near_duplicates: result.near_duplicates ?? []
4860
+ };
4861
+ }
4862
+
4863
+ // src/shared/mcp/functions/handle-tool-learn.function.ts
4864
+ async function handleToolLearn(client, args) {
4865
+ const learnArgs = parseLearnArgs(args);
4866
+ const { sectionType, title, level, ...shared } = learnArgs;
4867
+ if (sectionType.startsWith("image/") && !title) {
4868
+ return {
4869
+ content: [
4870
+ {
4871
+ type: "text",
4872
+ text: "title is required for image/* types"
4873
+ }
4874
+ ],
4875
+ isError: true
4876
+ };
4877
+ }
4878
+ const content = unwrapJsonContent(asString2(args["content"], ""));
4879
+ const splitResult = determineLearnSplit(content, sectionType, title);
4880
+ const result = splitResult ? await storeSplitLearnSections(client, splitResult, {
4881
+ sectionType,
4882
+ ...shared
4883
+ }) : await client.addSection({
4884
+ sectionType,
4885
+ content,
4886
+ title,
4887
+ level,
4888
+ ...shared
4889
+ });
4890
+ return {
4891
+ content: [
4892
+ {
4893
+ type: "text",
4894
+ text: JSON.stringify(buildLearnPayload(result))
4895
+ }
4896
+ ]
4897
+ };
4898
+ }
4899
+
4900
+ // src/shared/mcp/functions/handle-tool-skill-learn.function.ts
4901
+ async function handleToolSkillLearn(client, args) {
4902
+ const name = asString2(args["name"], "");
4903
+ if (!name) {
4904
+ return {
4905
+ content: [{ type: "text", text: "name is required" }],
4906
+ isError: true
4907
+ };
4908
+ }
4909
+ const body = asString2(args["body"], "");
4910
+ if (!body) {
4911
+ return {
4912
+ content: [{ type: "text", text: "body is required" }],
4913
+ isError: true
4914
+ };
4915
+ }
4916
+ const description = typeof args["description"] === "string" ? args["description"] : void 0;
4917
+ const skillArgs = extractSkillArguments(body);
4918
+ const skill = {
4919
+ name,
4920
+ description,
4921
+ arguments: skillArgs.length > 0 ? skillArgs : void 0,
4922
+ body
4923
+ };
4924
+ const json = serializeSkillContent(skill);
4925
+ const result = await client.addSection({
4926
+ sectionType: "skill",
4927
+ content: json,
4928
+ title: name,
4929
+ description
4930
+ });
4931
+ return {
4932
+ content: [
4933
+ {
4934
+ type: "text",
4935
+ text: `Created skill "${name}" (kbid: ${result.items[0]})`
4936
+ }
4937
+ ]
4938
+ };
4939
+ }
4940
+
4941
+ // src/shared/mcp/functions/handle-tool-call.function.ts
4942
+ function asStringArray2(value) {
4943
+ if (Array.isArray(value) && value.every((item) => typeof item === "string")) {
4944
+ return value;
4945
+ }
4946
+ return [];
4947
+ }
4560
4948
  async function handleToolCall(client, name, args) {
4561
4949
  log("tool call: " + name, 0 /* DEBUG */);
4562
4950
  try {
@@ -4564,7 +4952,7 @@ async function handleToolCall(client, name, args) {
4564
4952
  case "search":
4565
4953
  return await handleSearch2(client, args);
4566
4954
  case "learn":
4567
- return await handleLearn2(client, args);
4955
+ return await handleToolLearn(client, args);
4568
4956
  case "unlearn":
4569
4957
  return await handleUnlearn2(client, args);
4570
4958
  case "retrieve":
@@ -4584,7 +4972,7 @@ async function handleToolCall(client, name, args) {
4584
4972
  case "export":
4585
4973
  return await handleExport2(client, args);
4586
4974
  case "skill-learn":
4587
- return await handleSkillLearn(client, args);
4975
+ return await handleToolSkillLearn(client, args);
4588
4976
  case "skill-list":
4589
4977
  return await handleSkillList(client, args);
4590
4978
  case "skill-get":
@@ -4649,47 +5037,6 @@ async function handleSearch2(client, args) {
4649
5037
  ]
4650
5038
  };
4651
5039
  }
4652
- async function handleLearn2(client, args) {
4653
- const sectionType = asString2(args["type"], "text/markdown");
4654
- const title = typeof args["title"] === "string" ? args["title"] : void 0;
4655
- const description = typeof args["description"] === "string" ? args["description"] : void 0;
4656
- const docid = typeof args["docid"] === "string" ? args["docid"] : void 0;
4657
- const tags = asStringArrayOrUndefined(args["tags"]);
4658
- const replace = asBooleanOrUndefined(args["replace"]);
4659
- if (sectionType.startsWith("image/") && !title) {
4660
- return {
4661
- content: [
4662
- {
4663
- type: "text",
4664
- text: "title is required for image/* types"
4665
- }
4666
- ],
4667
- isError: true
4668
- };
4669
- }
4670
- const rawContent = asString2(args["content"], "");
4671
- const content = unwrapJsonContent(rawContent);
4672
- const result = await client.addSection({
4673
- sectionType,
4674
- content,
4675
- title,
4676
- description,
4677
- docid,
4678
- tags,
4679
- replace
4680
- });
4681
- const payload = {
4682
- items: result.items,
4683
- total: result.total,
4684
- offset: result.offset,
4685
- superseded: result.superseded ?? null,
4686
- warnings: result.warnings ?? [],
4687
- near_duplicates: result.near_duplicates ?? []
4688
- };
4689
- return {
4690
- content: [{ type: "text", text: JSON.stringify(payload) }]
4691
- };
4692
- }
4693
5040
  async function handleUnlearn2(client, args) {
4694
5041
  const kbid = asString2(args["kbid"], "");
4695
5042
  await client.removeSection(kbid);
@@ -4796,45 +5143,6 @@ async function handleRebuild2(client) {
4796
5143
  const text = `Rebuild complete: ${String(report.sections_reindexed)} sections reindexed, ${String(report.terms_indexed)} terms indexed.`;
4797
5144
  return { content: [{ type: "text", text }] };
4798
5145
  }
4799
- async function handleSkillLearn(client, args) {
4800
- const name = asString2(args["name"], "");
4801
- if (!name) {
4802
- return {
4803
- content: [{ type: "text", text: "name is required" }],
4804
- isError: true
4805
- };
4806
- }
4807
- const body = asString2(args["body"], "");
4808
- if (!body) {
4809
- return {
4810
- content: [{ type: "text", text: "body is required" }],
4811
- isError: true
4812
- };
4813
- }
4814
- const description = typeof args["description"] === "string" ? args["description"] : void 0;
4815
- const skillArgs = extractSkillArguments(body);
4816
- const skill = {
4817
- name,
4818
- description,
4819
- arguments: skillArgs.length > 0 ? skillArgs : void 0,
4820
- body
4821
- };
4822
- const json = serializeSkillContent(skill);
4823
- const result = await client.addSection({
4824
- sectionType: "skill",
4825
- content: json,
4826
- title: name,
4827
- description
4828
- });
4829
- return {
4830
- content: [
4831
- {
4832
- type: "text",
4833
- text: `Created skill "${name}" (kbid: ${result.items[0]})`
4834
- }
4835
- ]
4836
- };
4837
- }
4838
5146
  async function handleSkillList(client, args) {
4839
5147
  const records = await client.listByType("skill");
4840
5148
  const query = typeof args["query"] === "string" ? args["query"].toLowerCase() : "";