@dominikcz/greg 0.9.27

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 (183) hide show
  1. package/README.md +397 -0
  2. package/bin/greg.js +241 -0
  3. package/bin/init.js +351 -0
  4. package/bin/templates/docs/getting-started.md +47 -0
  5. package/bin/templates/docs/index.md +11 -0
  6. package/bin/templates/greg.config.js +39 -0
  7. package/bin/templates/greg.config.ts +38 -0
  8. package/bin/templates/index.html +16 -0
  9. package/bin/templates/src/App.svelte +5 -0
  10. package/bin/templates/src/app.css +20 -0
  11. package/bin/templates/src/main.js +9 -0
  12. package/bin/templates/svelte.config.js +1 -0
  13. package/bin/templates/tsconfig.json +21 -0
  14. package/bin/templates/vite.config.js +23 -0
  15. package/docs/__partials/markdown/examples/basic.md +4 -0
  16. package/docs/__partials/markdown/examples/diff.md +10 -0
  17. package/docs/__partials/markdown/examples/focus.md +5 -0
  18. package/docs/__partials/markdown/examples/language-title.md +3 -0
  19. package/docs/__partials/markdown/examples/line-highlighting.md +5 -0
  20. package/docs/__partials/markdown/examples/line-numbers.md +5 -0
  21. package/docs/__partials/note.md +4 -0
  22. package/docs/guide/__shared-warning.md +4 -0
  23. package/docs/guide/asset-handling.md +88 -0
  24. package/docs/guide/deploying.md +162 -0
  25. package/docs/guide/getting-started.md +334 -0
  26. package/docs/guide/index.md +23 -0
  27. package/docs/guide/localization.md +290 -0
  28. package/docs/guide/markdown/code.md +95 -0
  29. package/docs/guide/markdown/components-and-mermaid.md +43 -0
  30. package/docs/guide/markdown/containers.md +110 -0
  31. package/docs/guide/markdown/header-anchors.md +34 -0
  32. package/docs/guide/markdown/includes.md +84 -0
  33. package/docs/guide/markdown/index.md +20 -0
  34. package/docs/guide/markdown/inline-attributes.md +21 -0
  35. package/docs/guide/markdown/links-and-toc.md +64 -0
  36. package/docs/guide/markdown/math.md +54 -0
  37. package/docs/guide/markdown/syntax-highlighting.md +75 -0
  38. package/docs/guide/routing.md +150 -0
  39. package/docs/guide/using-svelte.md +88 -0
  40. package/docs/guide/versioning.md +281 -0
  41. package/docs/incompatibilities.md +48 -0
  42. package/docs/index.md +43 -0
  43. package/docs/reference/badge.md +100 -0
  44. package/docs/reference/carbon-ads.md +46 -0
  45. package/docs/reference/code-group.md +126 -0
  46. package/docs/reference/home-page.md +232 -0
  47. package/docs/reference/index.md +18 -0
  48. package/docs/reference/markdowndocs.md +275 -0
  49. package/docs/reference/outline.md +79 -0
  50. package/docs/reference/search.md +263 -0
  51. package/docs/reference/steps.md +200 -0
  52. package/docs/reference/team-page.md +189 -0
  53. package/docs/reference/theme.md +150 -0
  54. package/fakeDocsGenerator/generate_docs.js +310 -0
  55. package/package.json +92 -0
  56. package/scripts/build-versions.js +609 -0
  57. package/scripts/generate-static.js +79 -0
  58. package/scripts/render-markdown.js +420 -0
  59. package/src/lib/MarkdownDocs/AiChat.svelte +936 -0
  60. package/src/lib/MarkdownDocs/BackToTop.svelte +68 -0
  61. package/src/lib/MarkdownDocs/Breadcrumb.svelte +68 -0
  62. package/src/lib/MarkdownDocs/DocsNavigation.svelte +149 -0
  63. package/src/lib/MarkdownDocs/DocsSiteHeader.svelte +758 -0
  64. package/src/lib/MarkdownDocs/DocsVersionSwitcher.svelte +103 -0
  65. package/src/lib/MarkdownDocs/MarkdownDocs.svelte +2115 -0
  66. package/src/lib/MarkdownDocs/MarkdownRenderer.svelte +487 -0
  67. package/src/lib/MarkdownDocs/Outline.svelte +238 -0
  68. package/src/lib/MarkdownDocs/PrevNext.svelte +115 -0
  69. package/src/lib/MarkdownDocs/SearchModal.svelte +1241 -0
  70. package/src/lib/MarkdownDocs/TreeView.svelte +32 -0
  71. package/src/lib/MarkdownDocs/TreeViewItem.svelte +219 -0
  72. package/src/lib/MarkdownDocs/VersionOutdatedNotice.svelte +72 -0
  73. package/src/lib/MarkdownDocs/__tests__/codeDirectives.test.js +54 -0
  74. package/src/lib/MarkdownDocs/__tests__/common.test.js +41 -0
  75. package/src/lib/MarkdownDocs/__tests__/docsExamplesLint.test.js +77 -0
  76. package/src/lib/MarkdownDocs/__tests__/fixtures/docs/markdown/__partial-basic.md +3 -0
  77. package/src/lib/MarkdownDocs/__tests__/fixtures/docs/markdown/snippet.js +9 -0
  78. package/src/lib/MarkdownDocs/__tests__/fixtures/includes/part.md +11 -0
  79. package/src/lib/MarkdownDocs/__tests__/fixtures/includes/wrapper.md +5 -0
  80. package/src/lib/MarkdownDocs/__tests__/fixtures/snippets/sample.js +8 -0
  81. package/src/lib/MarkdownDocs/__tests__/fixtures/snippets/sample.md +5 -0
  82. package/src/lib/MarkdownDocs/__tests__/helpers.js +67 -0
  83. package/src/lib/MarkdownDocs/__tests__/localeUtils.test.js +204 -0
  84. package/src/lib/MarkdownDocs/__tests__/markdown.test.js +704 -0
  85. package/src/lib/MarkdownDocs/__tests__/markdownRendererRuntime.test.js +65 -0
  86. package/src/lib/MarkdownDocs/__tests__/searchIndexBuilder.test.js +117 -0
  87. package/src/lib/MarkdownDocs/__tests__/sqliteStore.test.js +202 -0
  88. package/src/lib/MarkdownDocs/__tests__/useRouter.test.js +16 -0
  89. package/src/lib/MarkdownDocs/ai/adapters/customAdapter.js +14 -0
  90. package/src/lib/MarkdownDocs/ai/adapters/customAdapter.ts +43 -0
  91. package/src/lib/MarkdownDocs/ai/adapters/ollamaAdapter.js +81 -0
  92. package/src/lib/MarkdownDocs/ai/adapters/ollamaAdapter.ts +116 -0
  93. package/src/lib/MarkdownDocs/ai/adapters/openaiAdapter.js +92 -0
  94. package/src/lib/MarkdownDocs/ai/adapters/openaiAdapter.ts +137 -0
  95. package/src/lib/MarkdownDocs/ai/aiProvider.ts +31 -0
  96. package/src/lib/MarkdownDocs/ai/characters.js +52 -0
  97. package/src/lib/MarkdownDocs/ai/characters.ts +69 -0
  98. package/src/lib/MarkdownDocs/ai/chunkStore.ts +25 -0
  99. package/src/lib/MarkdownDocs/ai/chunker.js +85 -0
  100. package/src/lib/MarkdownDocs/ai/chunker.ts +135 -0
  101. package/src/lib/MarkdownDocs/ai/docLinker.js +26 -0
  102. package/src/lib/MarkdownDocs/ai/docLinker.ts +36 -0
  103. package/src/lib/MarkdownDocs/ai/promptBuilder.js +33 -0
  104. package/src/lib/MarkdownDocs/ai/promptBuilder.ts +53 -0
  105. package/src/lib/MarkdownDocs/ai/ragPipeline.js +54 -0
  106. package/src/lib/MarkdownDocs/ai/ragPipeline.ts +106 -0
  107. package/src/lib/MarkdownDocs/ai/stores/memoryStore.js +88 -0
  108. package/src/lib/MarkdownDocs/ai/stores/memoryStore.ts +112 -0
  109. package/src/lib/MarkdownDocs/ai/stores/sqliteStore.ts +372 -0
  110. package/src/lib/MarkdownDocs/ai/types.ts +71 -0
  111. package/src/lib/MarkdownDocs/aiServer.js +288 -0
  112. package/src/lib/MarkdownDocs/codeDirectives.js +191 -0
  113. package/src/lib/MarkdownDocs/codeFenceInfo.js +45 -0
  114. package/src/lib/MarkdownDocs/codeGroup.ts +46 -0
  115. package/src/lib/MarkdownDocs/common.ts +47 -0
  116. package/src/lib/MarkdownDocs/docsUtils.js +281 -0
  117. package/src/lib/MarkdownDocs/index.plugins.js +22 -0
  118. package/src/lib/MarkdownDocs/layouts/LayoutDoc.svelte +8 -0
  119. package/src/lib/MarkdownDocs/layouts/LayoutHome.svelte +58 -0
  120. package/src/lib/MarkdownDocs/layouts/LayoutPage.svelte +9 -0
  121. package/src/lib/MarkdownDocs/loadGregConfig.js +82 -0
  122. package/src/lib/MarkdownDocs/localeUtils.ts +682 -0
  123. package/src/lib/MarkdownDocs/markdownRendererRuntime.ts +314 -0
  124. package/src/lib/MarkdownDocs/mermaidThemes.js +319 -0
  125. package/src/lib/MarkdownDocs/navigationUtils.js +22 -0
  126. package/src/lib/MarkdownDocs/rehypeCodeGroup.js +326 -0
  127. package/src/lib/MarkdownDocs/rehypeCodeTitle.js +96 -0
  128. package/src/lib/MarkdownDocs/rehypeToc.js +170 -0
  129. package/src/lib/MarkdownDocs/remarkCodeMeta.js +22 -0
  130. package/src/lib/MarkdownDocs/remarkContainers.js +329 -0
  131. package/src/lib/MarkdownDocs/remarkCustomAnchors.js +42 -0
  132. package/src/lib/MarkdownDocs/remarkEscapeSvelte.js +33 -0
  133. package/src/lib/MarkdownDocs/remarkGlobalComponents.js +65 -0
  134. package/src/lib/MarkdownDocs/remarkImports.js +461 -0
  135. package/src/lib/MarkdownDocs/remarkImportsBrowser.js +349 -0
  136. package/src/lib/MarkdownDocs/remarkInlineAttrs.js +95 -0
  137. package/src/lib/MarkdownDocs/remarkMathToHtml.js +138 -0
  138. package/src/lib/MarkdownDocs/searchIndexBuilder.js +497 -0
  139. package/src/lib/MarkdownDocs/searchServer.js +263 -0
  140. package/src/lib/MarkdownDocs/treeViewTypes.ts +11 -0
  141. package/src/lib/MarkdownDocs/useRouter.svelte.ts +114 -0
  142. package/src/lib/MarkdownDocs/useSplitter.svelte.ts +33 -0
  143. package/src/lib/MarkdownDocs/versioningDefaults.js +20 -0
  144. package/src/lib/MarkdownDocs/vitePluginAiServer.js +204 -0
  145. package/src/lib/MarkdownDocs/vitePluginCopyDocs.js +153 -0
  146. package/src/lib/MarkdownDocs/vitePluginFrontmatter.js +109 -0
  147. package/src/lib/MarkdownDocs/vitePluginGregConfig.js +108 -0
  148. package/src/lib/MarkdownDocs/vitePluginSearchIndex.js +57 -0
  149. package/src/lib/MarkdownDocs/vitePluginSearchServer.js +190 -0
  150. package/src/lib/components/Badge.svelte +59 -0
  151. package/src/lib/components/Button.svelte +138 -0
  152. package/src/lib/components/CarbonAds.svelte +99 -0
  153. package/src/lib/components/CodeGroup.svelte +102 -0
  154. package/src/lib/components/Feature.svelte +209 -0
  155. package/src/lib/components/Features.svelte +123 -0
  156. package/src/lib/components/Hero.svelte +399 -0
  157. package/src/lib/components/Image.svelte +128 -0
  158. package/src/lib/components/Link.svelte +105 -0
  159. package/src/lib/components/SocialLink.svelte +84 -0
  160. package/src/lib/components/SocialLinks.svelte +33 -0
  161. package/src/lib/components/Steps.svelte +143 -0
  162. package/src/lib/components/TeamMember.svelte +273 -0
  163. package/src/lib/components/TeamMembers.svelte +81 -0
  164. package/src/lib/components/TeamPage.svelte +65 -0
  165. package/src/lib/components/TeamPageSection.svelte +108 -0
  166. package/src/lib/components/TeamPageTitle.svelte +89 -0
  167. package/src/lib/components/index.js +24 -0
  168. package/src/lib/portal/context.js +12 -0
  169. package/src/lib/portal/index.js +3 -0
  170. package/src/lib/portal/portal.svelte +14 -0
  171. package/src/lib/portal/slot.svelte +8 -0
  172. package/src/lib/scss/__code.scss +128 -0
  173. package/src/lib/scss/__containers.scss +99 -0
  174. package/src/lib/scss/__markdown.scss +447 -0
  175. package/src/lib/scss/__scrollbar.scss +60 -0
  176. package/src/lib/scss/__steps.scss +100 -0
  177. package/src/lib/scss/__theme.scss +238 -0
  178. package/src/lib/scss/__toc.scss +55 -0
  179. package/src/lib/scss/__utilities.scss +7 -0
  180. package/src/lib/scss/greg.scss +9 -0
  181. package/src/lib/spinner/spinner.svelte +42 -0
  182. package/svelte.config.js +146 -0
  183. package/types/index.d.ts +456 -0
@@ -0,0 +1,372 @@
1
+ import type { ChunkStore } from '../chunkStore.js';
2
+ import type { AiProvider } from '../aiProvider.js';
3
+ import type { DocChunk, RetrievedChunk } from '../types.js';
4
+
5
+ /**
6
+ * SQLite-backed chunk store with hybrid BM25 + vector search.
7
+ *
8
+ * Uses:
9
+ * - FTS5 for full-text BM25 ranking
10
+ * - sqlite-vec for cosine-similarity vector search (when embeddings are available)
11
+ * - WAL mode for concurrent reads
12
+ *
13
+ * Dependencies: better-sqlite3, sqlite-vec (both optional peer deps).
14
+ *
15
+ * Usage:
16
+ * const store = new SqliteStore({ dbPath: 'docs.db' });
17
+ * // With vector search:
18
+ * const store = new SqliteStore({ dbPath: 'docs.db', provider, embeddingDimensions: 1536 });
19
+ */
20
+
21
+ export type SqliteStoreOptions = {
22
+ /** Path to the SQLite database file. Use ':memory:' for in-memory. Default: ':memory:' */
23
+ dbPath?: string;
24
+ /** Embedding dimensions (e.g. 1536 for OpenAI, 768 for nomic-embed-text). Required for vector search. */
25
+ embeddingDimensions?: number;
26
+ /**
27
+ * AI provider with embed() support — used to compute embeddings during index().
28
+ * Omit to use BM25-only mode.
29
+ */
30
+ provider?: AiProvider;
31
+ /**
32
+ * How many texts to embed in a single API call. Default: 64.
33
+ * Larger batches are faster but use more memory / may exceed API limits.
34
+ */
35
+ embeddingBatchSize?: number;
36
+ /**
37
+ * Weight for BM25 score in hybrid ranking (0–1). Default: 0.3.
38
+ * Vector score weight = 1 − bm25Weight.
39
+ * Only relevant when vector search is enabled.
40
+ */
41
+ bm25Weight?: number;
42
+ };
43
+
44
+ type Database = import('better-sqlite3').Database;
45
+
46
+ export class SqliteStore implements ChunkStore {
47
+ private db!: Database;
48
+ private readonly dbPath: string;
49
+ private readonly embDim: number;
50
+ private readonly provider?: AiProvider;
51
+ private readonly batchSize: number;
52
+ private readonly bm25Weight: number;
53
+ private chunkCount = 0;
54
+ private vectorEnabled = false;
55
+
56
+ constructor(options: SqliteStoreOptions = {}) {
57
+ this.dbPath = options.dbPath ?? ':memory:';
58
+ this.embDim = options.embeddingDimensions ?? 0;
59
+ this.provider = options.provider;
60
+ this.batchSize = options.embeddingBatchSize ?? 64;
61
+ this.bm25Weight = Math.max(0, Math.min(1, options.bm25Weight ?? 0.3));
62
+ }
63
+
64
+ // ── Lazy initialization ─────────────────────────────────────────────────
65
+
66
+ private async ensureDb(): Promise<Database> {
67
+ if (this.db) return this.db;
68
+
69
+ let BetterSqlite3: typeof import('better-sqlite3');
70
+ try {
71
+ BetterSqlite3 = (await import('better-sqlite3')).default;
72
+ } catch {
73
+ throw new Error(
74
+ '[greg-ai] SqliteStore requires "better-sqlite3" package. Install it with: npm install better-sqlite3',
75
+ );
76
+ }
77
+
78
+ this.db = new BetterSqlite3(this.dbPath);
79
+ this.db.pragma('journal_mode = WAL');
80
+ this.db.pragma('synchronous = NORMAL');
81
+
82
+ // Try loading sqlite-vec extension for vector search
83
+ if (this.embDim > 0) {
84
+ try {
85
+ const sqliteVec = await import('sqlite-vec');
86
+ sqliteVec.load(this.db);
87
+ this.vectorEnabled = true;
88
+ } catch {
89
+ console.warn(
90
+ '[greg-ai] sqlite-vec not available — falling back to BM25-only mode. ' +
91
+ 'Install with: npm install sqlite-vec',
92
+ );
93
+ }
94
+ }
95
+
96
+ this.createSchema();
97
+ return this.db;
98
+ }
99
+
100
+ private createSchema(): void {
101
+ this.db.exec(`
102
+ CREATE TABLE IF NOT EXISTS chunks (
103
+ id INTEGER PRIMARY KEY,
104
+ page_id TEXT NOT NULL,
105
+ page_title TEXT NOT NULL,
106
+ section_heading TEXT NOT NULL,
107
+ section_anchor TEXT NOT NULL,
108
+ content TEXT NOT NULL
109
+ );
110
+
111
+ CREATE VIRTUAL TABLE IF NOT EXISTS chunks_fts USING fts5(
112
+ page_title,
113
+ section_heading,
114
+ content,
115
+ content='chunks',
116
+ content_rowid='id',
117
+ tokenize='unicode61 remove_diacritics 2'
118
+ );
119
+ `);
120
+
121
+ if (this.vectorEnabled && this.embDim > 0) {
122
+ this.db.exec(`
123
+ CREATE VIRTUAL TABLE IF NOT EXISTS chunks_vec USING vec0(
124
+ embedding float[${this.embDim}]
125
+ );
126
+ `);
127
+ }
128
+ }
129
+
130
+ // ── ChunkStore interface ────────────────────────────────────────────────
131
+
132
+ async index(chunks: DocChunk[]): Promise<void> {
133
+ const db = await this.ensureDb();
134
+
135
+ // Clear existing data
136
+ db.exec('DELETE FROM chunks_fts');
137
+ db.exec('DELETE FROM chunks');
138
+ if (this.vectorEnabled) {
139
+ db.exec('DELETE FROM chunks_vec');
140
+ }
141
+
142
+ // Insert chunks with FTS
143
+ const insertChunk = db.prepare(`
144
+ INSERT INTO chunks (id, page_id, page_title, section_heading, section_anchor, content)
145
+ VALUES (?, ?, ?, ?, ?, ?)
146
+ `);
147
+ const insertFts = db.prepare(`
148
+ INSERT INTO chunks_fts (rowid, page_title, section_heading, content)
149
+ VALUES (?, ?, ?, ?)
150
+ `);
151
+
152
+ const insertAll = db.transaction((rows: DocChunk[]) => {
153
+ for (let i = 0; i < rows.length; i++) {
154
+ const c = rows[i];
155
+ const id = i + 1;
156
+ insertChunk.run(id, c.pageId, c.pageTitle, c.sectionHeading, c.sectionAnchor, c.content);
157
+ insertFts.run(id, c.pageTitle, c.sectionHeading, c.content);
158
+ }
159
+ });
160
+ insertAll(chunks);
161
+
162
+ // Compute and store embeddings if provider supports it
163
+ if (this.vectorEnabled && this.provider?.embed) {
164
+ const insertVec = db.prepare(`
165
+ INSERT INTO chunks_vec (rowid, embedding)
166
+ VALUES (?, ?)
167
+ `);
168
+
169
+ const texts = chunks.map(
170
+ c => `${c.pageTitle} — ${c.sectionHeading}\n${c.content}`,
171
+ );
172
+
173
+ for (let start = 0; start < texts.length; start += this.batchSize) {
174
+ const batch = texts.slice(start, start + this.batchSize);
175
+ const embeddings = await this.provider.embed(batch);
176
+
177
+ const insertBatch = db.transaction((embs: number[][], offset: number) => {
178
+ for (let j = 0; j < embs.length; j++) {
179
+ const id = offset + j + 1;
180
+ insertVec.run(id, new Float32Array(embs[j]));
181
+ }
182
+ });
183
+ insertBatch(embeddings, start);
184
+ }
185
+ }
186
+
187
+ this.chunkCount = chunks.length;
188
+ }
189
+
190
+ async search(query: string, limit = 8): Promise<RetrievedChunk[]> {
191
+ const db = await this.ensureDb();
192
+ if (this.chunkCount === 0) return [];
193
+
194
+ const ftsResults = this.searchBm25(db, query, limit);
195
+
196
+ if (!this.vectorEnabled || !this.provider?.embed) {
197
+ return ftsResults.slice(0, limit);
198
+ }
199
+
200
+ const vecResults = await this.searchVector(db, query, limit);
201
+
202
+ return this.mergeResults(ftsResults, vecResults, limit);
203
+ }
204
+
205
+ size(): number {
206
+ return this.chunkCount;
207
+ }
208
+
209
+ // ── BM25 full-text search ───────────────────────────────────────────────
210
+
211
+ private searchBm25(db: Database, query: string, limit: number): RetrievedChunk[] {
212
+ // FTS5 match query — escape double quotes and join terms
213
+ const sanitized = query.replace(/"/g, '""');
214
+ const ftsQuery = sanitized
215
+ .split(/\s+/)
216
+ .filter(t => t.length > 1)
217
+ .map(t => `"${t}"`)
218
+ .join(' OR ');
219
+
220
+ if (!ftsQuery) return [];
221
+
222
+ const stmt = db.prepare(`
223
+ SELECT
224
+ c.id,
225
+ c.page_id,
226
+ c.page_title,
227
+ c.section_heading,
228
+ c.section_anchor,
229
+ c.content,
230
+ rank * -1 AS score
231
+ FROM chunks_fts
232
+ JOIN chunks c ON c.id = chunks_fts.rowid
233
+ WHERE chunks_fts MATCH ?
234
+ ORDER BY rank
235
+ LIMIT ?
236
+ `);
237
+
238
+ const rows = stmt.all(ftsQuery, limit) as Array<{
239
+ id: number;
240
+ page_id: string;
241
+ page_title: string;
242
+ section_heading: string;
243
+ section_anchor: string;
244
+ content: string;
245
+ score: number;
246
+ }>;
247
+
248
+ if (rows.length === 0) return [];
249
+
250
+ // Normalize scores to 0–1
251
+ const maxScore = Math.max(...rows.map(r => r.score));
252
+ return rows.map(r => ({
253
+ pageId: r.page_id,
254
+ pageTitle: r.page_title,
255
+ sectionHeading: r.section_heading,
256
+ sectionAnchor: r.section_anchor,
257
+ content: r.content,
258
+ score: maxScore > 0 ? r.score / maxScore : 0,
259
+ }));
260
+ }
261
+
262
+ // ── Vector search ───────────────────────────────────────────────────────
263
+
264
+ private async searchVector(db: Database, query: string, limit: number): Promise<RetrievedChunk[]> {
265
+ if (!this.provider?.embed) return [];
266
+
267
+ const [queryEmbedding] = await this.provider.embed([query]);
268
+ if (!queryEmbedding || queryEmbedding.length === 0) return [];
269
+
270
+ const stmt = db.prepare(`
271
+ SELECT
272
+ v.rowid AS id,
273
+ v.distance,
274
+ c.page_id,
275
+ c.page_title,
276
+ c.section_heading,
277
+ c.section_anchor,
278
+ c.content
279
+ FROM chunks_vec v
280
+ JOIN chunks c ON c.id = v.rowid
281
+ WHERE v.embedding MATCH ?
282
+ AND k = ?
283
+ ORDER BY v.distance
284
+ `);
285
+
286
+ const rows = stmt.all(new Float32Array(queryEmbedding), limit) as Array<{
287
+ id: number;
288
+ distance: number;
289
+ page_id: string;
290
+ page_title: string;
291
+ section_heading: string;
292
+ section_anchor: string;
293
+ content: string;
294
+ }>;
295
+
296
+ if (rows.length === 0) return [];
297
+
298
+ // Convert cosine distance to similarity score (0–1)
299
+ return rows.map(r => ({
300
+ pageId: r.page_id,
301
+ pageTitle: r.page_title,
302
+ sectionHeading: r.section_heading,
303
+ sectionAnchor: r.section_anchor,
304
+ content: r.content,
305
+ score: Math.max(0, 1 - r.distance),
306
+ }));
307
+ }
308
+
309
+ // ── Hybrid merge ────────────────────────────────────────────────────────
310
+
311
+ /**
312
+ * Reciprocal Rank Fusion (RRF) merging of BM25 and vector results.
313
+ * Each result gets a combined score: bm25Weight * rrf(bm25_rank) + vecWeight * rrf(vec_rank).
314
+ */
315
+ private mergeResults(
316
+ bm25Results: RetrievedChunk[],
317
+ vecResults: RetrievedChunk[],
318
+ limit: number,
319
+ ): RetrievedChunk[] {
320
+ const K = 60; // RRF constant
321
+ const vecWeight = 1 - this.bm25Weight;
322
+
323
+ // Build a map of chunk key → merged chunk + score
324
+ const merged = new Map<string, { chunk: RetrievedChunk; score: number }>();
325
+
326
+ function chunkKey(c: RetrievedChunk): string {
327
+ return `${c.pageId}#${c.sectionAnchor}#${c.content.slice(0, 80)}`;
328
+ }
329
+
330
+ for (let rank = 0; rank < bm25Results.length; rank++) {
331
+ const c = bm25Results[rank];
332
+ const key = chunkKey(c);
333
+ const rrfScore = this.bm25Weight * (1 / (K + rank + 1));
334
+ const existing = merged.get(key);
335
+ if (existing) {
336
+ existing.score += rrfScore;
337
+ } else {
338
+ merged.set(key, { chunk: c, score: rrfScore });
339
+ }
340
+ }
341
+
342
+ for (let rank = 0; rank < vecResults.length; rank++) {
343
+ const c = vecResults[rank];
344
+ const key = chunkKey(c);
345
+ const rrfScore = vecWeight * (1 / (K + rank + 1));
346
+ const existing = merged.get(key);
347
+ if (existing) {
348
+ existing.score += rrfScore;
349
+ } else {
350
+ merged.set(key, { chunk: c, score: rrfScore });
351
+ }
352
+ }
353
+
354
+ // Sort by merged score, normalize, and return top-limit
355
+ const sorted = [...merged.values()].sort((a, b) => b.score - a.score);
356
+ const maxScore = sorted[0]?.score ?? 1;
357
+
358
+ return sorted.slice(0, limit).map(({ chunk, score }) => ({
359
+ ...chunk,
360
+ score: maxScore > 0 ? score / maxScore : 0,
361
+ }));
362
+ }
363
+
364
+ // ── Cleanup ─────────────────────────────────────────────────────────────
365
+
366
+ /** Close the database connection. Call when the server shuts down. */
367
+ close(): void {
368
+ if (this.db) {
369
+ this.db.close();
370
+ }
371
+ }
372
+ }
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Shared types for Greg AI knowledge base (RAG).
3
+ */
4
+
5
+ /** A chunk of documentation content extracted from the search index. */
6
+ export type DocChunk = {
7
+ /** Route path of the page, e.g. "/docs/guide/routing" */
8
+ pageId: string;
9
+ /** Page title */
10
+ pageTitle: string;
11
+ /** Section heading text (h2/h3) */
12
+ sectionHeading: string;
13
+ /** Section anchor ID used for URL linking */
14
+ sectionAnchor: string;
15
+ /** Plain-text content (~500 tokens max per chunk) */
16
+ content: string;
17
+ /** Optional: pre-computed embedding vector */
18
+ embedding?: number[];
19
+ };
20
+
21
+ /** A retrieved chunk with a relevance score (0–1, higher = more relevant). */
22
+ export type RetrievedChunk = DocChunk & {
23
+ score: number;
24
+ };
25
+
26
+ /** A source citation included in an AI response. */
27
+ export type AiSource = {
28
+ pageId: string;
29
+ pageTitle: string;
30
+ sectionHeading: string;
31
+ sectionAnchor: string;
32
+ };
33
+
34
+ /** Full response from the AI endpoint. */
35
+ export type AiResponse = {
36
+ /** Answer in markdown format */
37
+ answer: string;
38
+ /** Source citations used to generate the answer */
39
+ sources: AiSource[];
40
+ /** Character ID that was used */
41
+ character: string;
42
+ };
43
+
44
+ /** A chat message used in LLM API calls. */
45
+ export type ChatMessage = {
46
+ role: 'system' | 'user' | 'assistant';
47
+ content: string;
48
+ };
49
+
50
+ /** A predefined AI persona/character. */
51
+ export type AiCharacter = {
52
+ id: string;
53
+ /** Display name shown in the UI */
54
+ name: string;
55
+ /** Emoji, short string, or image URL shown in the character selector and chat messages. */
56
+ icon: string;
57
+ /** One-line description shown in character selector */
58
+ description?: string;
59
+ /** System prompt defining the response style */
60
+ systemPrompt: string;
61
+ };
62
+
63
+ /** Options for LLM generation (passed to an AiProvider.chat() call). */
64
+ export type AiProviderOptions = {
65
+ /** Override the model for this request */
66
+ model?: string;
67
+ /** Sampling temperature (0 = deterministic, 1 = creative) */
68
+ temperature?: number;
69
+ /** Maximum tokens to generate */
70
+ maxTokens?: number;
71
+ };