@diegoaltoworks/chatter 2.0.0 → 2.1.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.
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Section-aware Markdown chunking.
3
+ *
4
+ * A pure line scanner (no Markdown parser dependency) that splits a document at
5
+ * every ATX heading, records the heading trail each chunk sat under, and keeps
6
+ * fenced code blocks, lists and tables whole. A section longer than the cap is
7
+ * split on blank-line paragraph boundaries; consecutive small sections are
8
+ * never merged.
9
+ */
10
+ /** One chunk of a document, in reading order. */
11
+ export type SectionChunk = {
12
+ /** The chunk's text. A section's first chunk starts with its heading line. */
13
+ text: string;
14
+ /** Heading trail from the H1 down; empty for text before the first heading. */
15
+ section: string[];
16
+ /** Zero-based index of the chunk within the document. */
17
+ position: number;
18
+ };
19
+ /**
20
+ * Split Markdown `text` into structure-aware chunks of at most `max` characters
21
+ * where the structure allows it.
22
+ *
23
+ * Every ATX heading starts a new chunk, with the heading line kept at its top.
24
+ * A section longer than `max` is split on paragraph boundaries, each piece
25
+ * keeping the section's trail. A fenced code block, list or table is atomic and
26
+ * is emitted whole even if it alone exceeds `max`. Empty chunks are never
27
+ * produced, and every non-blank input line appears in exactly one chunk, in
28
+ * order.
29
+ */
30
+ export declare function chunkSections(text: string, max?: number): SectionChunk[];
31
+ //# sourceMappingURL=chunking.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chunking.d.ts","sourceRoot":"","sources":["../../src/core/chunking.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,iDAAiD;AACjD,MAAM,MAAM,YAAY,GAAG;IACzB,8EAA8E;IAC9E,IAAI,EAAE,MAAM,CAAC;IACb,+EAA+E;IAC/E,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,yDAAyD;IACzD,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AA+GF;;;;;;;;;;GAUG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,SAAM,GAAG,YAAY,EAAE,CA6BrE"}
@@ -16,6 +16,13 @@ export declare const DEFAULT_KNOWLEDGE_DIR = "./config/knowledge";
16
16
  export interface Retriever {
17
17
  /** Retrieve up to `k` chunks across `allowedBuckets` for `query`, most relevant first. */
18
18
  query(query: string, k: number, allowedBuckets: string[]): Promise<string[]>;
19
+ /**
20
+ * Optional record-returning variant of `query`, for a host that needs to
21
+ * know where a passage came from (a search route, citations). `prepareChat`
22
+ * never calls it; implement it only if your own code wants provenance.
23
+ * Same scoping and ordering rules as `query`.
24
+ */
25
+ queryChunks?(query: string, k: number, allowedBuckets: string[]): Promise<RetrievedChunk[]>;
19
26
  /**
20
27
  * Optional one-time ingest/warm-up step, run once at server startup before
21
28
  * the store answers any query. Omit it for a retriever that is always
@@ -23,6 +30,18 @@ export interface Retriever {
23
30
  */
24
31
  build?(): Promise<void>;
25
32
  }
33
+ /** One retrieval result with its provenance. See {@link Retriever.queryChunks}. */
34
+ export interface RetrievedChunk {
35
+ /** The chunk text alone, without any embedding context line. */
36
+ text: string;
37
+ bucket: string;
38
+ /** Where the chunk came from; knowledgeDir-relative for rows ingested in `'sections'` mode. */
39
+ source: string;
40
+ /** Heading trail from the H1 down; empty when the row has none (every `'lines'` row). */
41
+ section: string[];
42
+ /** Cosine similarity between the query and the chunk embedding. */
43
+ score: number;
44
+ }
26
45
  /**
27
46
  * Embeds a batch of texts into vectors, in input order. Lets
28
47
  * {@link VectorStore} stay decoupled from any specific embeddings provider -
@@ -52,6 +71,8 @@ export declare function openLibsqlClient(database: {
52
71
  url: string;
53
72
  authToken: string;
54
73
  }): Promise<LibsqlClient>;
74
+ /** Chunking strategy for {@link VectorStoreOptions.chunking}. */
75
+ export type ChunkingMode = "lines" | "sections";
55
76
  /**
56
77
  * `VectorStore` always takes an already-open libsql client rather than
57
78
  * credentials to open its own - the same rule every other store in this
@@ -68,6 +89,15 @@ export interface VectorStoreOptions {
68
89
  databaseClient: LibsqlClient;
69
90
  /** Directory of markdown knowledge files. Default: `./config/knowledge` */
70
91
  knowledgeDir?: string;
92
+ /**
93
+ * How knowledge files are cut into chunks. `'lines'` (default) keeps the
94
+ * original fixed-size line chunks, so ids and embeddings are unchanged.
95
+ * `'sections'` cuts at Markdown headings (see
96
+ * [chunking.ts](./chunking.ts)), records each chunk's heading trail and
97
+ * position, and embeds it with a `<source> > <heading trail>` context line.
98
+ * Switching modes re-embeds every chunk once on the next build.
99
+ */
100
+ chunking?: ChunkingMode;
71
101
  /** Logger for build progress. Default: a console logger writing to stderr. */
72
102
  logger?: Logger;
73
103
  /**
@@ -92,6 +122,7 @@ export declare class VectorStore implements Retriever {
92
122
  */
93
123
  readonly db: LibsqlClient;
94
124
  private knowledgeDir;
125
+ private chunking;
95
126
  private logger;
96
127
  private buildLock;
97
128
  private instanceId;
@@ -113,6 +144,12 @@ export declare class VectorStore implements Retriever {
113
144
  private renewBuildLock;
114
145
  /** The ingest itself. Only ever runs with the build lock held. */
115
146
  private buildUnderLock;
147
+ /**
148
+ * Adds the nullable `section`/`position` columns to a `chunks` table that
149
+ * every released version created without them. Checked via table_info so it
150
+ * is a no-op once present; only `'sections'` mode ever calls it.
151
+ */
152
+ private ensureSectionColumns;
116
153
  private cleanupStaleChunks;
117
154
  private static cosine;
118
155
  /**
@@ -126,6 +163,8 @@ export declare class VectorStore implements Retriever {
126
163
  * interpolated. An empty list retrieves nothing, and short-circuits before
127
164
  * the embedding call.
128
165
  */
166
+ queryChunks(q: string, k?: number, allowed?: string[]): Promise<RetrievedChunk[]>;
167
+ /** Same results as {@link queryChunks}, as bare text. */
129
168
  query(q: string, k?: number, allowed?: string[]): Promise<string[]>;
130
169
  }
131
170
  //# sourceMappingURL=retrieval.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"retrieval.d.ts","sourceRoot":"","sources":["../../src/core/retrieval.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,IAAI,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC7D,OAAO,KAAK,MAAM,MAAM,QAAQ,CAAC;AACjC,OAAO,EAGL,KAAK,SAAS,EAEf,MAAM,aAAa,CAAC;AAErB,OAAO,EAAuB,KAAK,MAAM,EAAE,MAAM,UAAU,CAAC;AAI5D,kFAAkF;AAClF,eAAO,MAAM,qBAAqB,uBAAuB,CAAC;AAE1D;;;;;;;;GAQG;AACH,MAAM,WAAW,SAAS;IACxB,0FAA0F;IAC1F,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAC7E;;;;OAIG;IACH,KAAK,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACzB;AAED;;;;GAIG;AACH,MAAM,MAAM,QAAQ,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;AAEhE;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,QAAQ,CAK7D;AAED,2KAA2K;AAC3K,eAAO,MAAM,gCAAgC,QAG4B,CAAC;AAE1E,0MAA0M;AAC1M,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,CAO5D;AAED;;;;;GAKG;AACH,wBAAsB,gBAAgB,CAAC,QAAQ,EAAE;IAC/C,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;CACnB,GAAG,OAAO,CAAC,YAAY,CAAC,CAKxB;AAuBD;;;;;;;;;;GAUG;AACH,MAAM,WAAW,kBAAkB;IACjC,uEAAuE;IACvE,cAAc,EAAE,YAAY,CAAC;IAC7B,2EAA2E;IAC3E,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,8EAA8E;IAC9E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,6FAA6F;IAC7F,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,2HAA2H;IAC3H,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,qBAAa,WAAY,YAAW,SAAS;IAczC,OAAO,CAAC,KAAK;IAbf;;;;OAIG;IACH,QAAQ,CAAC,EAAE,EAAE,YAAY,CAAC;IAC1B,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,SAAS,CAAY;IAC7B,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,gBAAgB,CAAS;IAEjC,YACU,KAAK,EAAE,QAAQ,EACvB,OAAO,EAAE,kBAAkB,EAQ5B;IAED;;;;;;;;;;OAUG;IACG,KAAK,kBAyBV;IAED,0FAA0F;IAC1F,OAAO,CAAC,cAAc;IAStB,kEAAkE;YACpD,cAAc;YA8Gd,kBAAkB;IAqChC,OAAO,CAAC,MAAM,CAAC,MAAM;IAYrB;;;;;;;;;;OAUG;IACG,KAAK,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,SAAI,EAAE,OAAO,GAAE,MAAM,EAAa,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAuB7E;CACF"}
1
+ {"version":3,"file":"retrieval.d.ts","sourceRoot":"","sources":["../../src/core/retrieval.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -20,6 +20,8 @@ export type { BucketsFor, BucketsForContext } from "./core/buckets";
20
20
  export { defaultBuckets, resolveBuckets } from "./core/buckets";
21
21
  export type { BuildLock, BuildLockRow } from "./core/buildLock";
22
22
  export { BUILD_LOCK_KEY, BUILD_LOCK_STALE_MS, canAcquireBuildLock, createTursoBuildLock, } from "./core/buildLock";
23
+ export type { SectionChunk } from "./core/chunking";
24
+ export { chunkSections } from "./core/chunking";
23
25
  export { DEFAULT_REFUSAL, detectLeakage, guardOutput, scrubOutput } from "./core/guardrails";
24
26
  export { completeOnce, completeStream, DEFAULT_MODEL } from "./core/llm";
25
27
  export { loadKnowledge } from "./core/loaders";
@@ -30,7 +32,7 @@ export { normalizeChatBody, normalizeMessages } from "./core/messages";
30
32
  export type { FallbackContext, FallbackFn, PipelineMessage, PipelineMode, PreparedChat, RerankContext, RerankContextArgs, RewriteQuery, RewriteQueryArgs, } from "./core/pipeline";
31
33
  export { prepareChat } from "./core/pipeline";
32
34
  export { DEFAULT_PROMPTS_DIR, PromptLoader } from "./core/prompts";
33
- export type { Embedder, Retriever, VectorStoreOptions } from "./core/retrieval";
35
+ export type { ChunkingMode, Embedder, RetrievedChunk, Retriever, VectorStoreOptions, } from "./core/retrieval";
34
36
  export { createOpenAIEmbedder, DEFAULT_KNOWLEDGE_DIR, VectorStore } from "./core/retrieval";
35
37
  export { createSession, getActiveSessions, getSessionInfo, validateSession, } from "./core/session";
36
38
  export type { MCPServerOptions, MCPTransportMode } from "./mcp-server";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -94,6 +94,126 @@ var init_buildLock = __esm({
94
94
  }
95
95
  });
96
96
 
97
+ // src/core/chunking.ts
98
+ function headingOf(line) {
99
+ const match = HEADING.exec(line);
100
+ if (!match) return void 0;
101
+ const title = (match[2] ?? "").replace(/[ \t]+#+$/, "").replace(/^#+$/, "").trim();
102
+ return { level: match[1].length, title };
103
+ }
104
+ function startsTable(lines, i) {
105
+ return lines[i].includes("|") && i + 1 < lines.length && TABLE_DELIMITER.test(lines[i + 1]);
106
+ }
107
+ function fenceEnd(lines, start) {
108
+ const opener = FENCE.exec(lines[start]);
109
+ if (!opener) return start + 1;
110
+ const marker = opener[1];
111
+ const closer = new RegExp(`^ {0,3}${marker[0] === "`" ? "`" : "~"}{${marker.length},}[ \\t]*$`);
112
+ for (let i = start + 1; i < lines.length; i++) if (closer.test(lines[i])) return i + 1;
113
+ return lines.length;
114
+ }
115
+ function listEnd(lines, start) {
116
+ let end = start + 1;
117
+ for (let i = start + 1; i < lines.length; i++) {
118
+ const line = lines[i];
119
+ if (isBlank(line)) {
120
+ let next = i + 1;
121
+ while (next < lines.length && isBlank(lines[next])) next++;
122
+ if (next < lines.length && (LIST_ITEM.test(lines[next]) || isIndented(lines[next]))) {
123
+ i = next - 1;
124
+ continue;
125
+ }
126
+ break;
127
+ }
128
+ if (headingOf(line) && !isIndented(line)) break;
129
+ if (FENCE.test(line) && !isIndented(line)) break;
130
+ end = i + 1;
131
+ }
132
+ return end;
133
+ }
134
+ function tableEnd(lines, start) {
135
+ let end = start + 2;
136
+ while (end < lines.length && !isBlank(lines[end]) && lines[end].includes("|")) end++;
137
+ return end;
138
+ }
139
+ function paragraphEnd(lines, start) {
140
+ let end = start + 1;
141
+ while (end < lines.length && !isBlank(lines[end]) && !headingOf(lines[end]) && !FENCE.test(lines[end]) && !LIST_ITEM.test(lines[end])) {
142
+ end++;
143
+ }
144
+ return end;
145
+ }
146
+ function scan(text) {
147
+ const lines = text.replace(/\r\n?/g, "\n").split("\n");
148
+ const sections = [{ trail: [], blocks: [] }];
149
+ const stack = [];
150
+ let i = 0;
151
+ while (i < lines.length) {
152
+ const line = lines[i];
153
+ if (isBlank(line)) {
154
+ i++;
155
+ continue;
156
+ }
157
+ const heading = FENCE.test(line) ? void 0 : headingOf(line);
158
+ if (heading) {
159
+ while (stack.length && stack[stack.length - 1].level >= heading.level) stack.pop();
160
+ stack.push(heading);
161
+ sections.push({ trail: stack.map((h) => h.title), heading: line, blocks: [] });
162
+ i++;
163
+ continue;
164
+ }
165
+ let end;
166
+ if (FENCE.test(line)) end = fenceEnd(lines, i);
167
+ else if (LIST_ITEM.test(line)) end = listEnd(lines, i);
168
+ else if (startsTable(lines, i)) end = tableEnd(lines, i);
169
+ else end = paragraphEnd(lines, i);
170
+ sections[sections.length - 1].blocks.push({ lines: lines.slice(i, end) });
171
+ i = end;
172
+ }
173
+ return sections;
174
+ }
175
+ function chunkSections(text, max = 900) {
176
+ const cap = Math.max(1, max);
177
+ const out = [];
178
+ const emit = (parts, section) => {
179
+ const body = parts.join("\n\n");
180
+ if (body.trim() === "") return;
181
+ out.push({ text: body, section: [...section], position: out.length });
182
+ };
183
+ for (const section of scan(text)) {
184
+ let parts = section.heading ? [section.heading.trim()] : [];
185
+ let size = parts.join("\n\n").length;
186
+ let hasBlock = false;
187
+ for (const block of section.blocks) {
188
+ const piece = block.lines.join("\n");
189
+ const added = piece.length + (parts.length ? 2 : 0);
190
+ if (hasBlock && size + added > cap) {
191
+ emit(parts, section.trail);
192
+ parts = [];
193
+ size = 0;
194
+ hasBlock = false;
195
+ }
196
+ size += piece.length + (parts.length ? 2 : 0);
197
+ parts.push(piece);
198
+ hasBlock = true;
199
+ }
200
+ emit(parts, section.trail);
201
+ }
202
+ return out;
203
+ }
204
+ var FENCE, HEADING, LIST_ITEM, TABLE_DELIMITER, isBlank, isIndented;
205
+ var init_chunking = __esm({
206
+ "src/core/chunking.ts"() {
207
+ "use strict";
208
+ FENCE = /^ {0,3}(`{3,}|~{3,})/;
209
+ HEADING = /^ {0,3}(#{1,6})(?:[ \t]+(.*?))?[ \t]*$/;
210
+ LIST_ITEM = /^[ \t]*(?:[-*+]|\d{1,9}[.)])[ \t]+\S/;
211
+ TABLE_DELIMITER = /^[ \t]*\|?[ \t]*:?-+:?[ \t]*(?:\|[ \t]*:?-+:?[ \t]*)*\|?[ \t]*$/;
212
+ isBlank = (line) => line.trim() === "";
213
+ isIndented = (line) => /^(?: {2,}|\t)/.test(line);
214
+ }
215
+ });
216
+
97
217
  // src/core/loaders.ts
98
218
  function walk(dir) {
99
219
  const items = [];
@@ -199,11 +319,13 @@ async function sha256(input) {
199
319
  const hash = await crypto.subtle.digest("SHA-256", data);
200
320
  return Array.from(new Uint8Array(hash)).map((b) => b.toString(16).padStart(2, "0")).join("");
201
321
  }
202
- var EMB_MODEL, DEFAULT_KNOWLEDGE_DIR, DATABASE_CONFIG_REQUIRED_MESSAGE, VectorStore;
322
+ var import_node_path3, EMB_MODEL, DEFAULT_KNOWLEDGE_DIR, DATABASE_CONFIG_REQUIRED_MESSAGE, VectorStore;
203
323
  var init_retrieval = __esm({
204
324
  "src/core/retrieval.ts"() {
205
325
  "use strict";
326
+ import_node_path3 = require("node:path");
206
327
  init_buildLock();
328
+ init_chunking();
207
329
  init_loaders();
208
330
  init_logger();
209
331
  EMB_MODEL = "text-embedding-3-large";
@@ -214,6 +336,7 @@ var init_retrieval = __esm({
214
336
  this.embed = embed;
215
337
  this.db = options.databaseClient;
216
338
  this.knowledgeDir = options.knowledgeDir || DEFAULT_KNOWLEDGE_DIR;
339
+ this.chunking = options.chunking ?? "lines";
217
340
  this.logger = options.logger ?? createConsoleLogger();
218
341
  this.buildLock = options.buildLock ?? createTursoBuildLock(this.db);
219
342
  this.instanceId = options.instanceId ?? crypto.randomUUID();
@@ -227,6 +350,7 @@ var init_retrieval = __esm({
227
350
  */
228
351
  db;
229
352
  knowledgeDir;
353
+ chunking;
230
354
  logger;
231
355
  buildLock;
232
356
  instanceId;
@@ -287,11 +411,41 @@ var init_retrieval = __esm({
287
411
  this.logger.info("No knowledge documents and no existing chunks - nothing to build");
288
412
  return;
289
413
  }
414
+ this.logger.info(`Chunking mode: ${this.chunking}`);
415
+ if (this.chunking === "sections") await this.ensureSectionColumns();
290
416
  const rows = [];
291
417
  for (const d of docs) {
292
- for (const part of chunk(d.text)) {
293
- const id = await sha256(`${d.bucket}|${d.source}|${part}`);
294
- rows.push({ id, bucket: d.bucket, source: d.source, text: part });
418
+ if (this.chunking === "sections") {
419
+ const source = (0, import_node_path3.relative)(this.knowledgeDir, d.source).split(import_node_path3.sep).join("/");
420
+ for (const c of chunkSections(d.text)) {
421
+ const section = c.section.join(" > ");
422
+ const id = await sha256(`${d.bucket}|${source}|${section}|${c.text}`);
423
+ const context = section ? `${source} > ${section}` : source;
424
+ rows.push({
425
+ id,
426
+ bucket: d.bucket,
427
+ source,
428
+ text: c.text,
429
+ embedText: `${context}
430
+
431
+ ${c.text}`,
432
+ section,
433
+ position: c.position
434
+ });
435
+ }
436
+ } else {
437
+ for (const part of chunk(d.text)) {
438
+ const id = await sha256(`${d.bucket}|${d.source}|${part}`);
439
+ rows.push({
440
+ id,
441
+ bucket: d.bucket,
442
+ source: d.source,
443
+ text: part,
444
+ embedText: part,
445
+ section: null,
446
+ position: null
447
+ });
448
+ }
295
449
  }
296
450
  }
297
451
  this.logger.info(`Created ${rows.length} chunks from knowledge documents`);
@@ -300,11 +454,17 @@ var init_retrieval = __esm({
300
454
  for (let i = 0; i < rows.length; i += UPSERT_BATCH) {
301
455
  const batch = rows.slice(i, i + UPSERT_BATCH);
302
456
  await this.db.batch(
303
- batch.map((r) => ({
304
- sql: `INSERT INTO chunks(id,bucket,source,text) VALUES(?,?,?,?)
305
- ON CONFLICT(id) DO NOTHING`,
306
- args: [r.id, r.bucket, r.source, r.text]
307
- })),
457
+ batch.map(
458
+ (r) => this.chunking === "sections" ? {
459
+ sql: `INSERT INTO chunks(id,bucket,source,text,section,position) VALUES(?,?,?,?,?,?)
460
+ ON CONFLICT(id) DO NOTHING`,
461
+ args: [r.id, r.bucket, r.source, r.text, r.section, r.position]
462
+ } : {
463
+ sql: `INSERT INTO chunks(id,bucket,source,text) VALUES(?,?,?,?)
464
+ ON CONFLICT(id) DO NOTHING`,
465
+ args: [r.id, r.bucket, r.source, r.text]
466
+ }
467
+ ),
308
468
  "write"
309
469
  );
310
470
  }
@@ -325,7 +485,7 @@ var init_retrieval = __esm({
325
485
  return;
326
486
  }
327
487
  this.logger.info(`Embedding ${missing.length} new/updated chunks with ${EMB_MODEL}...`);
328
- const textById = new Map(rows.map((r) => [r.id, r.text]));
488
+ const textById = new Map(rows.map((r) => [r.id, r.embedText]));
329
489
  const BATCH = 96;
330
490
  for (let i = 0; i < missing.length; i += BATCH) {
331
491
  const batchIds = missing.slice(i, i + BATCH);
@@ -346,6 +506,18 @@ var init_retrieval = __esm({
346
506
  }
347
507
  this.logger.info(`Successfully embedded ${missing.length} new chunks`);
348
508
  }
509
+ /**
510
+ * Adds the nullable `section`/`position` columns to a `chunks` table that
511
+ * every released version created without them. Checked via table_info so it
512
+ * is a no-op once present; only `'sections'` mode ever calls it.
513
+ */
514
+ async ensureSectionColumns() {
515
+ const info = await this.db.execute("PRAGMA table_info(chunks)");
516
+ const have = new Set(info.rows.map((r) => String(r.name)));
517
+ if (!have.has("section")) await this.db.execute("ALTER TABLE chunks ADD COLUMN section TEXT");
518
+ if (!have.has("position"))
519
+ await this.db.execute("ALTER TABLE chunks ADD COLUMN position INTEGER");
520
+ }
349
521
  // Remove chunks from database that no longer exist in markdown files
350
522
  async cleanupStaleChunks(currentIds) {
351
523
  const result = await this.db.execute("SELECT id FROM chunks");
@@ -392,25 +564,34 @@ var init_retrieval = __esm({
392
564
  * interpolated. An empty list retrieves nothing, and short-circuits before
393
565
  * the embedding call.
394
566
  */
395
- async query(q, k = 6, allowed = ["base"]) {
567
+ async queryChunks(q, k = 6, allowed = ["base"]) {
396
568
  if (allowed.length === 0) return [];
397
569
  const [qv] = await this.embed([q]);
398
570
  const placeholders = allowed.map(() => "?").join(",");
399
571
  const res = await this.db.execute({
400
- sql: `SELECT c.id, c.text, e.embedding
572
+ sql: `SELECT c.*, e.embedding
401
573
  FROM chunks c
402
574
  JOIN embeddings e ON e.id = c.id
403
575
  WHERE c.bucket IN (${placeholders})`,
404
576
  args: allowed
405
577
  });
406
- const scored = [];
407
- for (const row of res.rows) {
578
+ const scored = res.rows.map((row) => {
408
579
  const emb = JSON.parse(String(row.embedding));
409
- const s = _VectorStore.cosine(qv, emb);
410
- scored.push({ s, text: String(row.text) });
411
- }
412
- scored.sort((a, b) => b.s - a.s);
413
- return scored.slice(0, k).map((r) => r.text);
580
+ const trail = row.section == null ? "" : String(row.section);
581
+ return {
582
+ text: String(row.text),
583
+ bucket: String(row.bucket ?? ""),
584
+ source: String(row.source ?? ""),
585
+ section: trail ? trail.split(" > ") : [],
586
+ score: _VectorStore.cosine(qv, emb)
587
+ };
588
+ });
589
+ scored.sort((a, b) => b.score - a.score);
590
+ return scored.slice(0, k);
591
+ }
592
+ /** Same results as {@link queryChunks}, as bare text. */
593
+ async query(q, k = 6, allowed = ["base"]) {
594
+ return (await this.queryChunks(q, k, allowed)).map((c) => c.text);
414
595
  }
415
596
  };
416
597
  }
@@ -546,6 +727,7 @@ __export(index_exports, {
546
727
  applyTransformReply: () => applyTransformReply,
547
728
  canAcquireBuildLock: () => canAcquireBuildLock,
548
729
  chatBodyLimit: () => chatBodyLimit,
730
+ chunkSections: () => chunkSections,
549
731
  completeOnce: () => completeOnce,
550
732
  completeStream: () => completeStream,
551
733
  cors: () => cors,
@@ -866,6 +1048,7 @@ async function resolveBuckets({
866
1048
 
867
1049
  // src/index.ts
868
1050
  init_buildLock();
1051
+ init_chunking();
869
1052
  init_loaders();
870
1053
  init_logger();
871
1054
 
@@ -1354,6 +1537,7 @@ async function createMCPServer(config) {
1354
1537
  store = new VectorStore2(createOpenAIEmbedder2(client), {
1355
1538
  databaseClient: db,
1356
1539
  knowledgeDir: config.knowledgeDir || DEFAULT_KNOWLEDGE_DIR2,
1540
+ chunking: config.chunking,
1357
1541
  logger: log
1358
1542
  });
1359
1543
  }
@@ -2401,7 +2585,7 @@ function publicRoutes(deps) {
2401
2585
  }
2402
2586
 
2403
2587
  // src/server.ts
2404
- var import_node_path4 = require("node:path");
2588
+ var import_node_path5 = require("node:path");
2405
2589
  var import_hono5 = require("hono");
2406
2590
  var import_openai2 = __toESM(require("openai"));
2407
2591
 
@@ -2505,7 +2689,7 @@ function loadServeStatic(runtime = detectRuntime()) {
2505
2689
 
2506
2690
  // src/core/widgets.ts
2507
2691
  var import_node_fs3 = require("node:fs");
2508
- var import_node_path3 = require("node:path");
2692
+ var import_node_path4 = require("node:path");
2509
2693
  var import_node_url = require("node:url");
2510
2694
  var import_meta = {};
2511
2695
  function resolveStatic(configStaticDir) {
@@ -2514,15 +2698,15 @@ function resolveStatic(configStaticDir) {
2514
2698
  }
2515
2699
  try {
2516
2700
  if (import_meta.url) {
2517
- const moduleDir = (0, import_node_path3.dirname)((0, import_node_url.fileURLToPath)(import_meta.url));
2518
- const staticDir = (0, import_node_path3.join)(moduleDir, "static");
2701
+ const moduleDir = (0, import_node_path4.dirname)((0, import_node_url.fileURLToPath)(import_meta.url));
2702
+ const staticDir = (0, import_node_path4.join)(moduleDir, "static");
2519
2703
  if ((0, import_node_fs3.existsSync)(staticDir)) return { staticDir };
2520
2704
  }
2521
2705
  } catch {
2522
2706
  }
2523
2707
  try {
2524
2708
  if (typeof __dirname !== "undefined") {
2525
- const staticDir = (0, import_node_path3.join)(__dirname, "static");
2709
+ const staticDir = (0, import_node_path4.join)(__dirname, "static");
2526
2710
  if ((0, import_node_fs3.existsSync)(staticDir)) return { staticDir };
2527
2711
  }
2528
2712
  } catch {
@@ -2561,6 +2745,7 @@ async function createServer(config) {
2561
2745
  store = new VectorStore2(createOpenAIEmbedder2(client), {
2562
2746
  databaseClient: db,
2563
2747
  knowledgeDir: config.knowledgeDir || DEFAULT_KNOWLEDGE_DIR2,
2748
+ chunking: config.chunking,
2564
2749
  logger
2565
2750
  });
2566
2751
  }
@@ -2611,7 +2796,7 @@ async function createServer(config) {
2611
2796
  }
2612
2797
  }
2613
2798
  if (serveStatic && staticDir) {
2614
- const relativePath = (0, import_node_path4.relative)(process.cwd(), staticDir);
2799
+ const relativePath = (0, import_node_path5.relative)(process.cwd(), staticDir);
2615
2800
  app.get("/chatter.js", serveStatic({ path: `${relativePath}/chatter.js` }));
2616
2801
  app.get("/chatter.css", serveStatic({ path: `${relativePath}/chatter.css` }));
2617
2802
  }
@@ -2773,6 +2958,7 @@ function createShortener(config, fetchImpl = fetch) {
2773
2958
  applyTransformReply,
2774
2959
  canAcquireBuildLock,
2775
2960
  chatBodyLimit,
2961
+ chunkSections,
2776
2962
  completeOnce,
2777
2963
  completeStream,
2778
2964
  cors,