@noesis-brain/mcp-server 2.0.0 → 2.0.1

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.
package/README.md CHANGED
@@ -1,10 +1,10 @@
1
- # Noesis MCP Server
1
+ # Noesis — Markdown Notes Manager for Claude Code (MCP Server)
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/@noesis-brain/mcp-server.svg)](https://www.npmjs.com/package/@noesis-brain/mcp-server)
4
4
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
5
  [![MCP](https://img.shields.io/badge/MCP-Model%20Context%20Protocol-blue)](https://modelcontextprotocol.io/)
6
6
 
7
- Official [Model Context Protocol](https://modelcontextprotocol.io/) server for [Noesis](https://noesisbrain.com) — bring your personal knowledge base into Claude Code. Search, sync, and manage your markdown notes via 19 AI-powered tools.
7
+ Official [Model Context Protocol](https://modelcontextprotocol.io/) server for [Noesis](https://noesisbrain.com), the markdown notes manager for Claude Code. Search, sync, and manage your `.md` knowledge base via 19 AI-powered tools — turn your notes into a first-class context source for every prompt.
8
8
 
9
9
  ## What This Is
10
10
 
@@ -44,21 +44,21 @@ export class PostgresAdapter {
44
44
  }
45
45
  // Use PostgreSQL full-text search with ts_rank
46
46
  const userFilter = this.buildUserFilter(1);
47
- let sql = `
48
- SELECT
49
- n.id,
50
- n.title,
51
- n.content,
52
- n.file_path,
53
- n.description,
54
- n.points,
55
- n.is_favorite,
56
- n.modified_at,
57
- ts_rank(n.fts_vector, plainto_tsquery('english', $${userFilter.nextParamIndex})) as relevance_score
58
- FROM notes n
59
- WHERE n.fts_vector @@ plainto_tsquery('english', $${userFilter.nextParamIndex})
60
- AND COALESCE(n.is_trashed, FALSE) = FALSE
61
- AND ${userFilter.condition}
47
+ let sql = `
48
+ SELECT
49
+ n.id,
50
+ n.title,
51
+ n.content,
52
+ n.file_path,
53
+ n.description,
54
+ n.points,
55
+ n.is_favorite,
56
+ n.modified_at,
57
+ ts_rank(n.fts_vector, plainto_tsquery('english', $${userFilter.nextParamIndex})) as relevance_score
58
+ FROM notes n
59
+ WHERE n.fts_vector @@ plainto_tsquery('english', $${userFilter.nextParamIndex})
60
+ AND COALESCE(n.is_trashed, FALSE) = FALSE
61
+ AND ${userFilter.condition}
62
62
  `;
63
63
  const params = [...userFilter.params, searchTerms];
64
64
  let paramIndex = userFilter.nextParamIndex + 1;
@@ -92,20 +92,20 @@ export class PostgresAdapter {
92
92
  const { limit = 10, root } = options;
93
93
  const likePattern = `%${query}%`;
94
94
  const userFilter = this.buildUserFilter(1);
95
- let sql = `
96
- SELECT
97
- id,
98
- title,
99
- content,
100
- file_path,
101
- description,
102
- points,
103
- is_favorite,
104
- modified_at
105
- FROM notes
106
- WHERE (title ILIKE $${userFilter.nextParamIndex} OR description ILIKE $${userFilter.nextParamIndex} OR content ILIKE $${userFilter.nextParamIndex})
107
- AND COALESCE(is_trashed, FALSE) = FALSE
108
- AND ${userFilter.condition}
95
+ let sql = `
96
+ SELECT
97
+ id,
98
+ title,
99
+ content,
100
+ file_path,
101
+ description,
102
+ points,
103
+ is_favorite,
104
+ modified_at
105
+ FROM notes
106
+ WHERE (title ILIKE $${userFilter.nextParamIndex} OR description ILIKE $${userFilter.nextParamIndex} OR content ILIKE $${userFilter.nextParamIndex})
107
+ AND COALESCE(is_trashed, FALSE) = FALSE
108
+ AND ${userFilter.condition}
109
109
  `;
110
110
  const params = [...userFilter.params, likePattern];
111
111
  let paramIndex = userFilter.nextParamIndex + 1;
@@ -149,10 +149,10 @@ export class PostgresAdapter {
149
149
  async listNotes(options = {}) {
150
150
  const { limit = 50, offset = 0, root } = options;
151
151
  const userFilter = this.buildUserFilter(1);
152
- let sql = `
153
- SELECT * FROM notes
154
- WHERE COALESCE(is_trashed, FALSE) = FALSE
155
- AND ${userFilter.condition}
152
+ let sql = `
153
+ SELECT * FROM notes
154
+ WHERE COALESCE(is_trashed, FALSE) = FALSE
155
+ AND ${userFilter.condition}
156
156
  `;
157
157
  const params = [...userFilter.params];
158
158
  let paramIndex = userFilter.nextParamIndex;
@@ -173,13 +173,13 @@ export class PostgresAdapter {
173
173
  const cutoffDate = new Date();
174
174
  cutoffDate.setDate(cutoffDate.getDate() - days);
175
175
  const userFilter = this.buildUserFilter(3);
176
- const result = await this.pool.query(`
177
- SELECT * FROM notes
178
- WHERE modified_at >= $1
179
- AND COALESCE(is_trashed, FALSE) = FALSE
180
- AND ${userFilter.condition}
181
- ORDER BY modified_at DESC
182
- LIMIT $2
176
+ const result = await this.pool.query(`
177
+ SELECT * FROM notes
178
+ WHERE modified_at >= $1
179
+ AND COALESCE(is_trashed, FALSE) = FALSE
180
+ AND ${userFilter.condition}
181
+ ORDER BY modified_at DESC
182
+ LIMIT $2
183
183
  `, [cutoffDate.toISOString(), limit, ...userFilter.params]);
184
184
  return result.rows;
185
185
  }
@@ -188,11 +188,11 @@ export class PostgresAdapter {
188
188
  */
189
189
  async getRoots() {
190
190
  const userFilter = this.buildUserFilter(1);
191
- const result = await this.pool.query(`
192
- SELECT id, path, name, COALESCE(is_visible, TRUE) as is_visible
193
- FROM roots
194
- WHERE COALESCE(is_visible, TRUE) = TRUE
195
- AND ${userFilter.condition}
191
+ const result = await this.pool.query(`
192
+ SELECT id, path, name, COALESCE(is_visible, TRUE) as is_visible
193
+ FROM roots
194
+ WHERE COALESCE(is_visible, TRUE) = TRUE
195
+ AND ${userFilter.condition}
196
196
  `, userFilter.params);
197
197
  return result.rows;
198
198
  }
@@ -283,19 +283,19 @@ export class PostgresAdapter {
283
283
  */
284
284
  async getNotesForPull(options = {}) {
285
285
  const { root, rootId } = options;
286
- let sql = `
287
- SELECT
288
- n.id,
289
- n.relative_path,
290
- n.content,
291
- n.title,
292
- r.name as root_name,
293
- r.path as root_path,
294
- n.modified_at
295
- FROM notes n
296
- JOIN roots r ON n.root_id = r.id
297
- WHERE COALESCE(n.is_trashed, FALSE) = FALSE
298
- AND n.relative_path IS NOT NULL
286
+ let sql = `
287
+ SELECT
288
+ n.id,
289
+ n.relative_path,
290
+ n.content,
291
+ n.title,
292
+ r.name as root_name,
293
+ r.path as root_path,
294
+ n.modified_at
295
+ FROM notes n
296
+ JOIN roots r ON n.root_id = r.id
297
+ WHERE COALESCE(n.is_trashed, FALSE) = FALSE
298
+ AND n.relative_path IS NOT NULL
299
299
  `;
300
300
  const params = [];
301
301
  let paramIndex = 1;
@@ -336,20 +336,20 @@ export class PostgresAdapter {
336
336
  return 'skipped';
337
337
  }
338
338
  // Update existing note (also sets relative_path and project in case they were NULL)
339
- await this.pool.query(`
340
- UPDATE notes SET
341
- content = $1,
342
- hash = $2,
343
- title = $3,
344
- description = $4,
345
- keywords = $5::jsonb,
346
- file_path = $6,
347
- relative_path = $7,
348
- file_size = $8,
349
- project = $9,
350
- modified_at = NOW(),
351
- indexed_at = NOW()
352
- WHERE id = $10
339
+ await this.pool.query(`
340
+ UPDATE notes SET
341
+ content = $1,
342
+ hash = $2,
343
+ title = $3,
344
+ description = $4,
345
+ keywords = $5::jsonb,
346
+ file_path = $6,
347
+ relative_path = $7,
348
+ file_size = $8,
349
+ project = $9,
350
+ modified_at = NOW(),
351
+ indexed_at = NOW()
352
+ WHERE id = $10
353
353
  `, [
354
354
  file.content,
355
355
  file.hash,
@@ -365,11 +365,11 @@ export class PostgresAdapter {
365
365
  return 'updated';
366
366
  }
367
367
  // Insert new note (include user_id and project if configured)
368
- await this.pool.query(`
369
- INSERT INTO notes (
370
- root_id, file_path, relative_path, content, hash,
371
- title, description, keywords, file_size, project, modified_at, indexed_at, user_id
372
- ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8::jsonb, $9, $10, NOW(), NOW(), $11)
368
+ await this.pool.query(`
369
+ INSERT INTO notes (
370
+ root_id, file_path, relative_path, content, hash,
371
+ title, description, keywords, file_size, project, modified_at, indexed_at, user_id
372
+ ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8::jsonb, $9, $10, NOW(), NOW(), $11)
373
373
  `, [
374
374
  file.rootId,
375
375
  file.path,
@@ -390,11 +390,11 @@ export class PostgresAdapter {
390
390
  */
391
391
  async getRootsForSync() {
392
392
  const userFilter = this.buildUserFilter(1);
393
- const result = await this.pool.query(`
394
- SELECT id, name, path, last_scanned_at
395
- FROM roots
396
- WHERE COALESCE(is_visible, TRUE) = TRUE
397
- AND ${userFilter.condition}
393
+ const result = await this.pool.query(`
394
+ SELECT id, name, path, last_scanned_at
395
+ FROM roots
396
+ WHERE COALESCE(is_visible, TRUE) = TRUE
397
+ AND ${userFilter.condition}
398
398
  `, userFilter.params);
399
399
  return result.rows.map(row => ({
400
400
  id: row.id,
@@ -416,10 +416,10 @@ export class PostgresAdapter {
416
416
  */
417
417
  async createRoot(options) {
418
418
  const { name, path, type = 'folder' } = options;
419
- const result = await this.pool.query(`
420
- INSERT INTO roots (name, path, type, is_active, is_visible, user_id)
421
- VALUES ($1, $2, $3, FALSE, TRUE, $4)
422
- RETURNING id, name, path
419
+ const result = await this.pool.query(`
420
+ INSERT INTO roots (name, path, type, is_active, is_visible, user_id)
421
+ VALUES ($1, $2, $3, FALSE, TRUE, $4)
422
+ RETURNING id, name, path
423
423
  `, [name, path, type, this.userId]);
424
424
  return result.rows[0];
425
425
  }
@@ -427,9 +427,9 @@ export class PostgresAdapter {
427
427
  * Log a sync operation (for Dashboard sync activity display)
428
428
  */
429
429
  async logSyncOperation(options) {
430
- await this.pool.query(`
431
- INSERT INTO sync_logs (root_id, files_scanned, files_added, files_updated, files_deleted, source, machine_name, notes)
432
- VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
430
+ await this.pool.query(`
431
+ INSERT INTO sync_logs (root_id, files_scanned, files_added, files_updated, files_deleted, source, machine_name, notes)
432
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
433
433
  `, [
434
434
  options.rootId,
435
435
  options.filesScanned,
@@ -445,10 +445,10 @@ export class PostgresAdapter {
445
445
  * Get recent sync logs for a root
446
446
  */
447
447
  async getSyncLogs(rootId, limit = 10) {
448
- let sql = `
449
- SELECT id, root_id, synced_at, files_scanned, files_added,
450
- files_updated, files_deleted, source, machine_name, notes
451
- FROM sync_logs
448
+ let sql = `
449
+ SELECT id, root_id, synced_at, files_scanned, files_added,
450
+ files_updated, files_deleted, source, machine_name, notes
451
+ FROM sync_logs
452
452
  `;
453
453
  const params = [];
454
454
  let paramIndex = 1;
@@ -490,9 +490,9 @@ export class PostgresAdapter {
490
490
  * Set last sync time in settings
491
491
  */
492
492
  async setLastSyncTime(timestamp) {
493
- await this.pool.query(`
494
- INSERT INTO settings (key, value) VALUES ('last_sync_time', $1)
495
- ON CONFLICT (key) DO UPDATE SET value = $1
493
+ await this.pool.query(`
494
+ INSERT INTO settings (key, value) VALUES ('last_sync_time', $1)
495
+ ON CONFLICT (key) DO UPDATE SET value = $1
496
496
  `, [timestamp]);
497
497
  }
498
498
  /**
@@ -500,12 +500,12 @@ export class PostgresAdapter {
500
500
  */
501
501
  async getNoteCountByRoot() {
502
502
  const userFilter = this.buildUserFilter(1);
503
- const result = await this.pool.query(`
504
- SELECT root_id, COUNT(*) as count
505
- FROM notes
506
- WHERE COALESCE(is_trashed, FALSE) = FALSE
507
- AND ${userFilter.condition}
508
- GROUP BY root_id
503
+ const result = await this.pool.query(`
504
+ SELECT root_id, COUNT(*) as count
505
+ FROM notes
506
+ WHERE COALESCE(is_trashed, FALSE) = FALSE
507
+ AND ${userFilter.condition}
508
+ GROUP BY root_id
509
509
  `, userFilter.params);
510
510
  const counts = new Map();
511
511
  for (const row of result.rows) {
@@ -565,16 +565,16 @@ export class PostgresAdapter {
565
565
  */
566
566
  async getNoteForEnhancement(id) {
567
567
  const userFilter = this.buildUserFilter(2);
568
- const result = await this.pool.query(`
569
- SELECT
570
- n.id, n.title, n.description, n.keywords,
571
- COALESCE(n.aliases, '{}') as aliases,
572
- n.content, n.file_path, n.ai_enhanced_at,
573
- r.name as root_name
574
- FROM notes n
575
- LEFT JOIN roots r ON n.root_id = r.id
576
- WHERE n.id = $1 AND COALESCE(n.is_trashed, FALSE) = FALSE
577
- AND n.${userFilter.condition}
568
+ const result = await this.pool.query(`
569
+ SELECT
570
+ n.id, n.title, n.description, n.keywords,
571
+ COALESCE(n.aliases, '{}') as aliases,
572
+ n.content, n.file_path, n.ai_enhanced_at,
573
+ r.name as root_name
574
+ FROM notes n
575
+ LEFT JOIN roots r ON n.root_id = r.id
576
+ WHERE n.id = $1 AND COALESCE(n.is_trashed, FALSE) = FALSE
577
+ AND n.${userFilter.condition}
578
578
  `, [id, ...userFilter.params]);
579
579
  if (result.rows.length === 0)
580
580
  return null;
@@ -597,19 +597,19 @@ export class PostgresAdapter {
597
597
  async getNotesNeedingEnhancement(options = {}) {
598
598
  const { root, limit = 50, important_only = false } = options;
599
599
  const userFilter = this.buildUserFilter(1);
600
- let sql = `
601
- SELECT
602
- id, title, file_path,
603
- (description IS NOT NULL AND description != '') as has_description,
604
- (keywords IS NOT NULL AND jsonb_array_length(keywords) > 0) as has_keywords,
605
- COALESCE(is_favorite, FALSE) as is_favorite,
606
- (content LIKE '%⭐%') as has_stars,
607
- COALESCE(points, 0) as points,
608
- ai_enhanced_at
609
- FROM notes
610
- WHERE COALESCE(is_trashed, FALSE) = FALSE
611
- AND (description IS NULL OR description = '' OR keywords IS NULL OR jsonb_array_length(keywords) = 0)
612
- AND ${userFilter.condition}
600
+ let sql = `
601
+ SELECT
602
+ id, title, file_path,
603
+ (description IS NOT NULL AND description != '') as has_description,
604
+ (keywords IS NOT NULL AND jsonb_array_length(keywords) > 0) as has_keywords,
605
+ COALESCE(is_favorite, FALSE) as is_favorite,
606
+ (content LIKE '%⭐%') as has_stars,
607
+ COALESCE(points, 0) as points,
608
+ ai_enhanced_at
609
+ FROM notes
610
+ WHERE COALESCE(is_trashed, FALSE) = FALSE
611
+ AND (description IS NULL OR description = '' OR keywords IS NULL OR jsonb_array_length(keywords) = 0)
612
+ AND ${userFilter.condition}
613
613
  `;
614
614
  const params = [...userFilter.params];
615
615
  let paramIndex = userFilter.nextParamIndex;
@@ -674,16 +674,16 @@ export class PostgresAdapter {
674
674
  */
675
675
  async getNoteForScoring(id) {
676
676
  const userFilter = this.buildUserFilter(2);
677
- const result = await this.pool.query(`
678
- SELECT
679
- id, title, description, keywords, content, file_path,
680
- importance_score, quality_score,
681
- COALESCE(relations, '[]'::jsonb) as relations,
682
- COALESCE(is_favorite, FALSE) as is_favorite,
683
- COALESCE(points, 0) as points
684
- FROM notes
685
- WHERE id = $1 AND COALESCE(is_trashed, FALSE) = FALSE
686
- AND ${userFilter.condition}
677
+ const result = await this.pool.query(`
678
+ SELECT
679
+ id, title, description, keywords, content, file_path,
680
+ importance_score, quality_score,
681
+ COALESCE(relations, '[]'::jsonb) as relations,
682
+ COALESCE(is_favorite, FALSE) as is_favorite,
683
+ COALESCE(points, 0) as points
684
+ FROM notes
685
+ WHERE id = $1 AND COALESCE(is_trashed, FALSE) = FALSE
686
+ AND ${userFilter.condition}
687
687
  `, [id, ...userFilter.params]);
688
688
  if (result.rows.length === 0)
689
689
  return null;
@@ -770,11 +770,11 @@ export class PostgresAdapter {
770
770
  async getNotesForRelationAnalysis(excludeId, options = {}) {
771
771
  const { root, limit = 50 } = options;
772
772
  const userFilter = this.buildUserFilter(2);
773
- let sql = `
774
- SELECT id, title, description, keywords, file_path
775
- FROM notes
776
- WHERE id != $1 AND COALESCE(is_trashed, FALSE) = FALSE
777
- AND ${userFilter.condition}
773
+ let sql = `
774
+ SELECT id, title, description, keywords, file_path
775
+ FROM notes
776
+ WHERE id != $1 AND COALESCE(is_trashed, FALSE) = FALSE
777
+ AND ${userFilter.condition}
778
778
  `;
779
779
  const params = [excludeId, ...userFilter.params];
780
780
  let paramIndex = userFilter.nextParamIndex;
@@ -810,54 +810,54 @@ export class PostgresAdapter {
810
810
  paramIndex++;
811
811
  }
812
812
  // Get aggregate stats
813
- const statsResult = await this.pool.query(`
814
- SELECT
815
- COUNT(*) as total,
816
- COUNT(importance_score) as with_importance,
817
- COUNT(quality_score) as with_quality,
818
- COUNT(*) FILTER (WHERE jsonb_array_length(COALESCE(relations, '[]'::jsonb)) > 0) as with_relations,
819
- COUNT(*) FILTER (WHERE description IS NOT NULL AND description != '') as with_description,
820
- COUNT(*) FILTER (WHERE keywords IS NOT NULL AND jsonb_array_length(keywords) > 0) as with_keywords
821
- FROM notes
822
- WHERE ${whereClause}
813
+ const statsResult = await this.pool.query(`
814
+ SELECT
815
+ COUNT(*) as total,
816
+ COUNT(importance_score) as with_importance,
817
+ COUNT(quality_score) as with_quality,
818
+ COUNT(*) FILTER (WHERE jsonb_array_length(COALESCE(relations, '[]'::jsonb)) > 0) as with_relations,
819
+ COUNT(*) FILTER (WHERE description IS NOT NULL AND description != '') as with_description,
820
+ COUNT(*) FILTER (WHERE keywords IS NOT NULL AND jsonb_array_length(keywords) > 0) as with_keywords
821
+ FROM notes
822
+ WHERE ${whereClause}
823
823
  `, params);
824
824
  const stats = statsResult.rows[0];
825
825
  // Get low quality docs (score < 50 or NULL, prioritize NULL)
826
826
  const lowQualityParams = [...params, limit];
827
- const lowQualityResult = await this.pool.query(`
828
- SELECT id, title, quality_score, file_path
829
- FROM notes
830
- WHERE ${whereClause} AND (quality_score IS NULL OR quality_score < 50)
831
- ORDER BY quality_score NULLS FIRST, modified_at DESC
832
- LIMIT $${paramIndex}
827
+ const lowQualityResult = await this.pool.query(`
828
+ SELECT id, title, quality_score, file_path
829
+ FROM notes
830
+ WHERE ${whereClause} AND (quality_score IS NULL OR quality_score < 50)
831
+ ORDER BY quality_score NULLS FIRST, modified_at DESC
832
+ LIMIT $${paramIndex}
833
833
  `, lowQualityParams);
834
834
  // Get low importance docs (score < 30 or NULL)
835
- const lowImportanceResult = await this.pool.query(`
836
- SELECT id, title, importance_score, file_path
837
- FROM notes
838
- WHERE ${whereClause} AND (importance_score IS NULL OR importance_score < 30)
839
- ORDER BY importance_score NULLS FIRST, modified_at DESC
840
- LIMIT $${paramIndex}
835
+ const lowImportanceResult = await this.pool.query(`
836
+ SELECT id, title, importance_score, file_path
837
+ FROM notes
838
+ WHERE ${whereClause} AND (importance_score IS NULL OR importance_score < 30)
839
+ ORDER BY importance_score NULLS FIRST, modified_at DESC
840
+ LIMIT $${paramIndex}
841
841
  `, lowQualityParams);
842
842
  // Get orphan docs (no relations)
843
- const orphansResult = await this.pool.query(`
844
- SELECT id, title, file_path
845
- FROM notes
846
- WHERE ${whereClause} AND (relations IS NULL OR jsonb_array_length(relations) = 0)
847
- ORDER BY modified_at DESC
848
- LIMIT $${paramIndex}
843
+ const orphansResult = await this.pool.query(`
844
+ SELECT id, title, file_path
845
+ FROM notes
846
+ WHERE ${whereClause} AND (relations IS NULL OR jsonb_array_length(relations) = 0)
847
+ ORDER BY modified_at DESC
848
+ LIMIT $${paramIndex}
849
849
  `, lowQualityParams);
850
850
  // Get docs missing metadata
851
- const missingMetadataResult = await this.pool.query(`
852
- SELECT
853
- id, title, file_path,
854
- (description IS NULL OR description = '') as missing_desc,
855
- (keywords IS NULL OR jsonb_array_length(keywords) = 0) as missing_kw
856
- FROM notes
857
- WHERE ${whereClause}
858
- AND ((description IS NULL OR description = '') OR (keywords IS NULL OR jsonb_array_length(keywords) = 0))
859
- ORDER BY modified_at DESC
860
- LIMIT $${paramIndex}
851
+ const missingMetadataResult = await this.pool.query(`
852
+ SELECT
853
+ id, title, file_path,
854
+ (description IS NULL OR description = '') as missing_desc,
855
+ (keywords IS NULL OR jsonb_array_length(keywords) = 0) as missing_kw
856
+ FROM notes
857
+ WHERE ${whereClause}
858
+ AND ((description IS NULL OR description = '') OR (keywords IS NULL OR jsonb_array_length(keywords) = 0))
859
+ ORDER BY modified_at DESC
860
+ LIMIT $${paramIndex}
861
861
  `, lowQualityParams);
862
862
  return {
863
863
  total: parseInt(stats.total),
@@ -889,12 +889,12 @@ export class PostgresAdapter {
889
889
  async getNotesWithoutEmbeddings(options = {}) {
890
890
  const { limit = 50, root } = options;
891
891
  const userFilter = this.buildUserFilter(1);
892
- let sql = `
893
- SELECT id, title, content, file_path
894
- FROM notes
895
- WHERE embedding IS NULL
896
- AND COALESCE(is_trashed, FALSE) = FALSE
897
- AND ${userFilter.condition}
892
+ let sql = `
893
+ SELECT id, title, content, file_path
894
+ FROM notes
895
+ WHERE embedding IS NULL
896
+ AND COALESCE(is_trashed, FALSE) = FALSE
897
+ AND ${userFilter.condition}
898
898
  `;
899
899
  const params = [...userFilter.params];
900
900
  let paramIndex = userFilter.nextParamIndex;
@@ -925,14 +925,14 @@ export class PostgresAdapter {
925
925
  const { limit = 10, root } = options;
926
926
  const vectorString = `[${embedding.join(',')}]`;
927
927
  const userFilter = this.buildUserFilter(2);
928
- let sql = `
929
- SELECT
930
- id, title, description, file_path,
931
- 1 - (embedding <=> $1::vector) as similarity
932
- FROM notes
933
- WHERE embedding IS NOT NULL
934
- AND COALESCE(is_trashed, FALSE) = FALSE
935
- AND ${userFilter.condition}
928
+ let sql = `
929
+ SELECT
930
+ id, title, description, file_path,
931
+ 1 - (embedding <=> $1::vector) as similarity
932
+ FROM notes
933
+ WHERE embedding IS NOT NULL
934
+ AND COALESCE(is_trashed, FALSE) = FALSE
935
+ AND ${userFilter.condition}
936
936
  `;
937
937
  const params = [vectorString, ...userFilter.params];
938
938
  let paramIndex = userFilter.nextParamIndex;
@@ -957,11 +957,11 @@ export class PostgresAdapter {
957
957
  */
958
958
  async getNoteEmbedding(id) {
959
959
  const userFilter = this.buildUserFilter(2);
960
- const result = await this.pool.query(`
961
- SELECT id, title, embedding
962
- FROM notes
963
- WHERE id = $1 AND COALESCE(is_trashed, FALSE) = FALSE
964
- AND ${userFilter.condition}
960
+ const result = await this.pool.query(`
961
+ SELECT id, title, embedding
962
+ FROM notes
963
+ WHERE id = $1 AND COALESCE(is_trashed, FALSE) = FALSE
964
+ AND ${userFilter.condition}
965
965
  `, [id, ...userFilter.params]);
966
966
  if (result.rows.length === 0)
967
967
  return null;
@@ -992,17 +992,17 @@ export class PostgresAdapter {
992
992
  }
993
993
  // Find similar notes (excluding the source note)
994
994
  const similarUserFilter = this.buildUserFilter(4);
995
- const result = await this.pool.query(`
996
- SELECT
997
- id, title, description, file_path,
998
- 1 - (embedding <=> $1) as similarity
999
- FROM notes
1000
- WHERE id != $2
1001
- AND embedding IS NOT NULL
1002
- AND COALESCE(is_trashed, FALSE) = FALSE
1003
- AND ${similarUserFilter.condition}
1004
- ORDER BY embedding <=> $1
1005
- LIMIT $3
995
+ const result = await this.pool.query(`
996
+ SELECT
997
+ id, title, description, file_path,
998
+ 1 - (embedding <=> $1) as similarity
999
+ FROM notes
1000
+ WHERE id != $2
1001
+ AND embedding IS NOT NULL
1002
+ AND COALESCE(is_trashed, FALSE) = FALSE
1003
+ AND ${similarUserFilter.condition}
1004
+ ORDER BY embedding <=> $1
1005
+ LIMIT $3
1006
1006
  `, [noteResult.rows[0].embedding, noteId, limit, ...similarUserFilter.params]);
1007
1007
  return result.rows.map(row => ({
1008
1008
  id: row.id,
@@ -1017,14 +1017,14 @@ export class PostgresAdapter {
1017
1017
  */
1018
1018
  async getEmbeddingStats() {
1019
1019
  const userFilter = this.buildUserFilter(1);
1020
- const result = await this.pool.query(`
1021
- SELECT
1022
- COUNT(*) as total,
1023
- COUNT(embedding) as with_embeddings,
1024
- COUNT(*) - COUNT(embedding) as without_embeddings
1025
- FROM notes
1026
- WHERE COALESCE(is_trashed, FALSE) = FALSE
1027
- AND ${userFilter.condition}
1020
+ const result = await this.pool.query(`
1021
+ SELECT
1022
+ COUNT(*) as total,
1023
+ COUNT(embedding) as with_embeddings,
1024
+ COUNT(*) - COUNT(embedding) as without_embeddings
1025
+ FROM notes
1026
+ WHERE COALESCE(is_trashed, FALSE) = FALSE
1027
+ AND ${userFilter.condition}
1028
1028
  `, userFilter.params);
1029
1029
  const row = result.rows[0];
1030
1030
  return {
@@ -77,25 +77,25 @@ export async function generateMetadata(content) {
77
77
  if (contentToAnalyze.length > maxChars) {
78
78
  contentToAnalyze = contentToAnalyze.substring(0, maxChars) + '...';
79
79
  }
80
- const prompt = `Analyze this markdown document and generate metadata for it.
81
-
82
- Document content:
83
- ---
84
- ${contentToAnalyze}
85
- ---
86
-
87
- Generate the following metadata in JSON format:
88
- 1. title: A concise, descriptive title (max 80 chars). IMPORTANT: If the document starts with a # heading on the first line, use that FIRST heading as the title (clean it up if needed). Only generate a new title if there's no H1 heading at the start.
89
- 2. description: A brief summary of what this document is about (1-2 sentences, max 200 chars)
90
- 3. keywords: An array of 5-15 relevant keywords/tags including:
91
- - Main topics and concepts
92
- - Technical terms and technologies (Redis, SignalR, MongoDB, etc.)
93
- - Component/service/class names found in code (e.g., PrintController, BtXmlExecutor, DevicesInternalController)
94
- - Include BOTH CamelCase (PrintController) AND space-separated (Print Controller) versions of component names
95
- - API endpoint patterns or route names if mentioned
96
- Avoid generic words like "document", "file", "note", "example", "sample".
97
-
98
- Respond with ONLY valid JSON in this exact format, no markdown code blocks:
80
+ const prompt = `Analyze this markdown document and generate metadata for it.
81
+
82
+ Document content:
83
+ ---
84
+ ${contentToAnalyze}
85
+ ---
86
+
87
+ Generate the following metadata in JSON format:
88
+ 1. title: A concise, descriptive title (max 80 chars). IMPORTANT: If the document starts with a # heading on the first line, use that FIRST heading as the title (clean it up if needed). Only generate a new title if there's no H1 heading at the start.
89
+ 2. description: A brief summary of what this document is about (1-2 sentences, max 200 chars)
90
+ 3. keywords: An array of 5-15 relevant keywords/tags including:
91
+ - Main topics and concepts
92
+ - Technical terms and technologies (Redis, SignalR, MongoDB, etc.)
93
+ - Component/service/class names found in code (e.g., PrintController, BtXmlExecutor, DevicesInternalController)
94
+ - Include BOTH CamelCase (PrintController) AND space-separated (Print Controller) versions of component names
95
+ - API endpoint patterns or route names if mentioned
96
+ Avoid generic words like "document", "file", "note", "example", "sample".
97
+
98
+ Respond with ONLY valid JSON in this exact format, no markdown code blocks:
99
99
  {"title": "...", "description": "...", "keywords": ["...", "..."]}`;
100
100
  try {
101
101
  const result = await genAI.models.generateContent({