@gmickel/gno 0.3.5 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/README.md +74 -7
  2. package/package.json +30 -1
  3. package/src/cli/commands/ask.ts +12 -187
  4. package/src/cli/commands/embed.ts +10 -4
  5. package/src/cli/commands/models/pull.ts +9 -4
  6. package/src/cli/commands/serve.ts +19 -0
  7. package/src/cli/commands/vsearch.ts +5 -2
  8. package/src/cli/program.ts +28 -0
  9. package/src/config/types.ts +11 -6
  10. package/src/llm/registry.ts +3 -1
  11. package/src/mcp/tools/vsearch.ts +5 -2
  12. package/src/pipeline/answer.ts +224 -0
  13. package/src/pipeline/contextual.ts +57 -0
  14. package/src/pipeline/expansion.ts +49 -31
  15. package/src/pipeline/explain.ts +11 -3
  16. package/src/pipeline/fusion.ts +20 -9
  17. package/src/pipeline/hybrid.ts +57 -40
  18. package/src/pipeline/index.ts +7 -0
  19. package/src/pipeline/rerank.ts +55 -27
  20. package/src/pipeline/types.ts +0 -3
  21. package/src/pipeline/vsearch.ts +3 -2
  22. package/src/serve/CLAUDE.md +91 -0
  23. package/src/serve/bunfig.toml +2 -0
  24. package/src/serve/context.ts +181 -0
  25. package/src/serve/index.ts +7 -0
  26. package/src/serve/public/app.tsx +56 -0
  27. package/src/serve/public/components/ai-elements/code-block.tsx +176 -0
  28. package/src/serve/public/components/ai-elements/conversation.tsx +98 -0
  29. package/src/serve/public/components/ai-elements/inline-citation.tsx +285 -0
  30. package/src/serve/public/components/ai-elements/loader.tsx +96 -0
  31. package/src/serve/public/components/ai-elements/message.tsx +443 -0
  32. package/src/serve/public/components/ai-elements/prompt-input.tsx +1421 -0
  33. package/src/serve/public/components/ai-elements/sources.tsx +75 -0
  34. package/src/serve/public/components/ai-elements/suggestion.tsx +51 -0
  35. package/src/serve/public/components/preset-selector.tsx +403 -0
  36. package/src/serve/public/components/ui/badge.tsx +46 -0
  37. package/src/serve/public/components/ui/button-group.tsx +82 -0
  38. package/src/serve/public/components/ui/button.tsx +62 -0
  39. package/src/serve/public/components/ui/card.tsx +92 -0
  40. package/src/serve/public/components/ui/carousel.tsx +244 -0
  41. package/src/serve/public/components/ui/collapsible.tsx +31 -0
  42. package/src/serve/public/components/ui/command.tsx +181 -0
  43. package/src/serve/public/components/ui/dialog.tsx +141 -0
  44. package/src/serve/public/components/ui/dropdown-menu.tsx +255 -0
  45. package/src/serve/public/components/ui/hover-card.tsx +42 -0
  46. package/src/serve/public/components/ui/input-group.tsx +167 -0
  47. package/src/serve/public/components/ui/input.tsx +21 -0
  48. package/src/serve/public/components/ui/progress.tsx +28 -0
  49. package/src/serve/public/components/ui/scroll-area.tsx +56 -0
  50. package/src/serve/public/components/ui/select.tsx +188 -0
  51. package/src/serve/public/components/ui/separator.tsx +26 -0
  52. package/src/serve/public/components/ui/table.tsx +114 -0
  53. package/src/serve/public/components/ui/textarea.tsx +18 -0
  54. package/src/serve/public/components/ui/tooltip.tsx +59 -0
  55. package/src/serve/public/globals.css +226 -0
  56. package/src/serve/public/hooks/use-api.ts +112 -0
  57. package/src/serve/public/index.html +13 -0
  58. package/src/serve/public/pages/Ask.tsx +442 -0
  59. package/src/serve/public/pages/Browse.tsx +270 -0
  60. package/src/serve/public/pages/Dashboard.tsx +202 -0
  61. package/src/serve/public/pages/DocView.tsx +302 -0
  62. package/src/serve/public/pages/Search.tsx +335 -0
  63. package/src/serve/routes/api.ts +763 -0
  64. package/src/serve/server.ts +249 -0
  65. package/src/store/migrations/002-documents-fts.ts +40 -0
  66. package/src/store/migrations/index.ts +2 -1
  67. package/src/store/sqlite/adapter.ts +216 -33
  68. package/src/store/sqlite/fts5-snowball.ts +144 -0
  69. package/src/store/types.ts +33 -3
  70. package/src/store/vector/stats.ts +3 -0
  71. package/src/store/vector/types.ts +1 -0
@@ -0,0 +1,144 @@
1
+ /**
2
+ * fts5-snowball extension loader.
3
+ *
4
+ * Loads vendored fts5-snowball extension for multilingual FTS5 stemming.
5
+ * Pattern mirrors sqlite-vec loader.
6
+ *
7
+ * @module src/store/sqlite/fts5-snowball
8
+ */
9
+
10
+ import type { Database } from 'bun:sqlite';
11
+ // node:fs: existsSync for sync file checks at load time
12
+ import { existsSync } from 'node:fs';
13
+ // node:path: join for cross-platform paths
14
+ import { join } from 'node:path';
15
+ // node:process: arch/platform detection (no Bun equivalent)
16
+ import { arch, platform } from 'node:process';
17
+ import { fileURLToPath } from 'node:url';
18
+
19
+ // ─────────────────────────────────────────────────────────────────────────────
20
+ // Types
21
+ // ─────────────────────────────────────────────────────────────────────────────
22
+
23
+ /**
24
+ * Result of attempting to load fts5-snowball.
25
+ */
26
+ export interface Fts5SnowballLoadResult {
27
+ loaded: boolean;
28
+ error?: string;
29
+ path?: string;
30
+ }
31
+
32
+ // ─────────────────────────────────────────────────────────────────────────────
33
+ // Platform Detection
34
+ // ─────────────────────────────────────────────────────────────────────────────
35
+
36
+ function getPlatformDir(): string | null {
37
+ const os = platform === 'win32' ? 'windows' : platform;
38
+ const archName = arch === 'arm64' ? 'arm64' : 'x64';
39
+
40
+ if (os === 'darwin') {
41
+ return `darwin-${archName}`;
42
+ }
43
+ if (os === 'linux' && archName === 'x64') {
44
+ return 'linux-x64';
45
+ }
46
+ if (os === 'windows' && archName === 'x64') {
47
+ return 'windows-x64';
48
+ }
49
+
50
+ return null;
51
+ }
52
+
53
+ function getExtensionSuffix(): string {
54
+ if (platform === 'win32') {
55
+ return 'dll';
56
+ }
57
+ if (platform === 'darwin') {
58
+ return 'dylib';
59
+ }
60
+ return 'so';
61
+ }
62
+
63
+ // ─────────────────────────────────────────────────────────────────────────────
64
+ // Path Resolution
65
+ // ─────────────────────────────────────────────────────────────────────────────
66
+
67
+ /**
68
+ * Get path to vendored fts5-snowball extension.
69
+ * Returns null if not available for this platform.
70
+ */
71
+ export function getExtensionPath(): string | null {
72
+ const platformDir = getPlatformDir();
73
+ if (!platformDir) {
74
+ return null;
75
+ }
76
+
77
+ const suffix = getExtensionSuffix();
78
+ const filename = `fts5stemmer.${suffix}`;
79
+
80
+ // Resolve relative to this module (ESM-safe)
81
+ const thisDir = fileURLToPath(new URL('.', import.meta.url));
82
+ const vendorPath = join(
83
+ thisDir,
84
+ '..',
85
+ '..',
86
+ '..',
87
+ 'vendor',
88
+ 'fts5-snowball',
89
+ platformDir,
90
+ filename
91
+ );
92
+
93
+ if (existsSync(vendorPath)) {
94
+ return vendorPath;
95
+ }
96
+
97
+ return null;
98
+ }
99
+
100
+ // ─────────────────────────────────────────────────────────────────────────────
101
+ // Extension Loading
102
+ // ─────────────────────────────────────────────────────────────────────────────
103
+
104
+ /**
105
+ * Load fts5-snowball extension into database.
106
+ *
107
+ * Must be called after Database.setCustomSQLite() on macOS.
108
+ * Safe to call multiple times - extension load is idempotent.
109
+ *
110
+ * @param db - Open database connection
111
+ * @returns Load result with success/error info
112
+ */
113
+ export function loadFts5Snowball(db: Database): Fts5SnowballLoadResult {
114
+ const path = getExtensionPath();
115
+
116
+ if (!path) {
117
+ const platformDir = getPlatformDir();
118
+ return {
119
+ loaded: false,
120
+ error: platformDir
121
+ ? `fts5-snowball binary not found for ${platformDir}`
122
+ : `fts5-snowball not available for ${platform}-${arch}`,
123
+ };
124
+ }
125
+
126
+ try {
127
+ db.loadExtension(path);
128
+ return { loaded: true, path };
129
+ } catch (e) {
130
+ const message = e instanceof Error ? e.message : String(e);
131
+ return {
132
+ loaded: false,
133
+ error: message,
134
+ path,
135
+ };
136
+ }
137
+ }
138
+
139
+ /**
140
+ * Check if fts5-snowball is available for this platform.
141
+ */
142
+ export function isAvailable(): boolean {
143
+ return getExtensionPath() !== null;
144
+ }
@@ -18,6 +18,7 @@ export type StoreErrorCode =
18
18
  | 'CONSTRAINT_VIOLATION'
19
19
  | 'MIGRATION_FAILED'
20
20
  | 'CONNECTION_FAILED'
21
+ | 'EXTENSION_LOAD_FAILED'
21
22
  | 'QUERY_FAILED'
22
23
  | 'TRANSACTION_FAILED'
23
24
  | 'INVALID_INPUT'
@@ -195,7 +196,11 @@ export interface FtsSearchOptions {
195
196
  limit?: number;
196
197
  /** Filter by collection */
197
198
  collection?: string;
198
- /** Filter by language */
199
+ /**
200
+ * Language hint (reserved for future use).
201
+ * Note: FTS5 snowball tokenizer is language-aware at index time,
202
+ * so runtime language filtering is not currently implemented.
203
+ */
199
204
  language?: string;
200
205
  /** Include snippet with highlights */
201
206
  snippet?: boolean;
@@ -399,6 +404,16 @@ export interface StorePort {
399
404
  */
400
405
  listDocuments(collection?: string): Promise<StoreResult<DocumentRow[]>>;
401
406
 
407
+ /**
408
+ * List documents with pagination support.
409
+ * Returns documents and total count for efficient browsing.
410
+ */
411
+ listDocumentsPaginated(options: {
412
+ collection?: string;
413
+ limit: number;
414
+ offset: number;
415
+ }): Promise<StoreResult<{ documents: DocumentRow[]; total: number }>>;
416
+
402
417
  /**
403
418
  * Mark documents as inactive (soft delete).
404
419
  * Returns count of affected documents.
@@ -459,7 +474,7 @@ export interface StorePort {
459
474
  // ─────────────────────────────────────────────────────────────────────────
460
475
 
461
476
  /**
462
- * Search chunks using FTS5.
477
+ * Search documents using FTS5 (document-level).
463
478
  */
464
479
  searchFts(
465
480
  query: string,
@@ -467,8 +482,23 @@ export interface StorePort {
467
482
  ): Promise<StoreResult<FtsResult[]>>;
468
483
 
469
484
  /**
485
+ * Sync a document to documents_fts for full-text search.
486
+ * Must be called after document and content are both upserted.
487
+ */
488
+ syncDocumentFts(
489
+ collection: string,
490
+ relPath: string
491
+ ): Promise<StoreResult<void>>;
492
+
493
+ /**
494
+ * Rebuild entire documents_fts index from scratch.
495
+ * Use after migration or for recovery. Returns count of indexed docs.
496
+ */
497
+ rebuildAllDocumentsFts(): Promise<StoreResult<number>>;
498
+
499
+ /**
500
+ * @deprecated Use syncDocumentFts for document-level FTS.
470
501
  * Rebuild FTS index for a mirror hash.
471
- * Called after upserting chunks.
472
502
  */
473
503
  rebuildFtsForHash(mirrorHash: string): Promise<StoreResult<void>>;
474
504
 
@@ -78,9 +78,11 @@ export function createVectorStatsPort(db: Database): VectorStatsPort {
78
78
 
79
79
  // Seek pagination: use cursor to avoid skipping items as backlog shrinks
80
80
  // Query structure changes based on whether we have a cursor
81
+ // Include document title for contextual embedding
81
82
  const sql = after
82
83
  ? `
83
84
  SELECT c.mirror_hash as mirrorHash, c.seq, c.text,
85
+ (SELECT d.title FROM documents d WHERE d.mirror_hash = c.mirror_hash AND d.active = 1 LIMIT 1) as title,
84
86
  CASE
85
87
  WHEN NOT EXISTS (
86
88
  SELECT 1 FROM content_vectors v
@@ -108,6 +110,7 @@ export function createVectorStatsPort(db: Database): VectorStatsPort {
108
110
  `
109
111
  : `
110
112
  SELECT c.mirror_hash as mirrorHash, c.seq, c.text,
113
+ (SELECT d.title FROM documents d WHERE d.mirror_hash = c.mirror_hash AND d.active = 1 LIMIT 1) as title,
111
114
  CASE
112
115
  WHEN NOT EXISTS (
113
116
  SELECT 1 FROM content_vectors v
@@ -38,6 +38,7 @@ export interface BacklogItem {
38
38
  mirrorHash: string;
39
39
  seq: number;
40
40
  text: string;
41
+ title: string | null;
41
42
  reason: 'new' | 'changed' | 'force';
42
43
  }
43
44