claude-brain 0.22.2 → 0.22.3

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/VERSION CHANGED
@@ -1 +1 @@
1
- 0.22.2
1
+ 0.22.3
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-brain",
3
- "version": "0.22.2",
3
+ "version": "0.22.3",
4
4
  "description": "Local development assistant bridging Obsidian vaults with Claude Code via MCP",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -25,6 +25,7 @@ function sanitizeMetadata(metadata: Record<string, any>): Record<string, string
25
25
  }
26
26
 
27
27
  export interface StoreDecisionInput {
28
+ id?: string
28
29
  project: string
29
30
  context: string
30
31
  decision: string
@@ -141,7 +142,7 @@ export class ChromaMemoryStore {
141
142
  return firstDuplicate.id
142
143
  }
143
144
 
144
- const id = randomUUID()
145
+ const id = input.id || randomUUID()
145
146
  const now = new Date().toISOString()
146
147
 
147
148
  const metadata: Record<string, any> = {
@@ -225,6 +226,7 @@ export class ChromaMemoryStore {
225
226
  }
226
227
 
227
228
  async storePattern(input: {
229
+ id?: string
228
230
  project: string
229
231
  pattern_type: 'solution' | 'anti-pattern' | 'best-practice' | 'common-issue'
230
232
  description: string
@@ -233,7 +235,7 @@ export class ChromaMemoryStore {
233
235
  context?: string
234
236
  source?: string
235
237
  }): Promise<string> {
236
- const id = randomUUID()
238
+ const id = input.id || randomUUID()
237
239
  const now = new Date().toISOString()
238
240
 
239
241
  const metadata: Record<string, any> = {
@@ -300,6 +302,7 @@ export class ChromaMemoryStore {
300
302
  }
301
303
 
302
304
  async storeCorrection(input: {
305
+ id?: string
303
306
  project: string
304
307
  original: string
305
308
  correction: string
@@ -307,7 +310,7 @@ export class ChromaMemoryStore {
307
310
  context?: string
308
311
  confidence: number
309
312
  }): Promise<string> {
310
- const id = randomUUID()
313
+ const id = input.id || randomUUID()
311
314
  const now = new Date().toISOString()
312
315
 
313
316
  const metadata: Record<string, any> = {
@@ -7,6 +7,7 @@
7
7
  import { randomUUID } from 'crypto'
8
8
  import type { Database } from 'bun:sqlite'
9
9
  import type { Logger } from 'pino'
10
+ import { expandQuery } from '@/retrieval/query/expander'
10
11
 
11
12
  export type ObservationCategory = 'decision' | 'pattern' | 'correction' | 'insight' | 'preference'
12
13
 
@@ -174,8 +175,8 @@ export class FTS5Search {
174
175
  /**
175
176
  * Store a new observation. Returns the generated ID.
176
177
  */
177
- store(observation: NewObservation): string {
178
- const id = randomUUID()
178
+ store(observation: NewObservation, providedId?: string): string {
179
+ const id = providedId || randomUUID()
179
180
  const now = new Date().toISOString()
180
181
 
181
182
  const stmt = this.db.prepare(`
@@ -374,12 +375,13 @@ export class FTS5Search {
374
375
  const cleaned = query.replace(/[":*^~(){}[\]]/g, ' ').trim()
375
376
  if (!cleaned) return ''
376
377
 
377
- // Split into words, filter short/stop words
378
- const words = cleaned.split(/\s+/).filter(w => w.length >= 2)
379
- if (words.length === 0) return ''
378
+ // Expand query with synonyms (e.g., "database" → also search "storage", "persistence")
379
+ const expanded = expandQuery(cleaned, { useSynonyms: true, maxExpansions: 8 })
380
+ const allWords = expanded.combined.split(/\s+/).filter(w => w.length >= 2)
381
+ const unique = [...new Set(allWords)]
380
382
 
381
- // Join with OR for broad matching
382
- return words.map(w => `"${w}"`).join(' OR ')
383
+ if (unique.length === 0) return ''
384
+ return unique.map(w => `"${w}"`).join(' OR ')
383
385
  }
384
386
 
385
387
  /**
@@ -5,6 +5,7 @@
5
5
  * Unified memory system manager that combines all components
6
6
  */
7
7
 
8
+ import { randomUUID } from 'crypto'
8
9
  import type { Logger } from 'pino'
9
10
  import { MemoryDatabase } from './database'
10
11
  import { EmbeddingService } from './embeddings'
@@ -244,6 +245,9 @@ export class MemoryManager {
244
245
  reasoning: string,
245
246
  options?: { alternatives?: string; tags?: string[] }
246
247
  ): Promise<string> {
248
+ // BUG-001 fix: Generate a single shared ID for all backends
249
+ const sharedId = randomUUID()
250
+
247
251
  // Phase 26: Always store in FTS5 if available
248
252
  let fts5Id: string | undefined
249
253
  if (this._fts5) {
@@ -262,7 +266,7 @@ export class MemoryManager {
262
266
  reasoning,
263
267
  context,
264
268
  tags: options?.tags
265
- })
269
+ }, sharedId)
266
270
  } catch (error) {
267
271
  this.logger.warn({ error }, 'FTS5 store failed, continuing with other backends')
268
272
  }
@@ -272,6 +276,7 @@ export class MemoryManager {
272
276
  if (this.useChromaDB) {
273
277
  try {
274
278
  const chromaId = await this.chroma.store.storeDecision({
279
+ id: sharedId,
275
280
  project,
276
281
  context,
277
282
  decision,
@@ -456,6 +461,9 @@ export class MemoryManager {
456
461
  context?: string
457
462
  source?: string
458
463
  }): Promise<string> {
464
+ // BUG-001 fix: Generate a single shared ID for all backends
465
+ const sharedId = randomUUID()
466
+
459
467
  // Phase 26: Dual-write to FTS5
460
468
  let fts5Id: string | undefined
461
469
  if (this._fts5) {
@@ -467,7 +475,7 @@ export class MemoryManager {
467
475
  context: input.context,
468
476
  confidence: input.confidence,
469
477
  source: input.source
470
- })
478
+ }, sharedId)
471
479
  } catch (error) {
472
480
  this.logger.warn({ error }, 'FTS5 pattern store failed')
473
481
  }
@@ -475,7 +483,7 @@ export class MemoryManager {
475
483
 
476
484
  if (this.useChromaDB) {
477
485
  try {
478
- const chromaId = await this.chroma.store.storePattern(input)
486
+ const chromaId = await this.chroma.store.storePattern({ ...input, id: sharedId })
479
487
  return fts5Id || chromaId
480
488
  } catch (error) {
481
489
  this.logger.warn({ error }, 'ChromaDB pattern store failed')
@@ -500,6 +508,9 @@ export class MemoryManager {
500
508
  context?: string
501
509
  confidence: number
502
510
  }): Promise<string> {
511
+ // BUG-001 fix: Generate a single shared ID for all backends
512
+ const sharedId = randomUUID()
513
+
503
514
  // Phase 26: Dual-write to FTS5
504
515
  let fts5Id: string | undefined
505
516
  if (this._fts5) {
@@ -511,7 +522,7 @@ export class MemoryManager {
511
522
  reasoning: input.reasoning,
512
523
  context: input.context,
513
524
  confidence: input.confidence
514
- })
525
+ }, sharedId)
515
526
  } catch (error) {
516
527
  this.logger.warn({ error }, 'FTS5 correction store failed')
517
528
  }
@@ -519,7 +530,7 @@ export class MemoryManager {
519
530
 
520
531
  if (this.useChromaDB) {
521
532
  try {
522
- const chromaId = await this.chroma.store.storeCorrection(input)
533
+ const chromaId = await this.chroma.store.storeCorrection({ ...input, id: sharedId })
523
534
  return fts5Id || chromaId
524
535
  } catch (error) {
525
536
  this.logger.warn({ error }, 'ChromaDB correction store failed')
@@ -59,11 +59,16 @@ const SYNONYMS: Record<string, string[]> = {
59
59
  deploy: ['deployment', 'release', 'ship', 'publish'],
60
60
  build: ['compile', 'bundle', 'package'],
61
61
 
62
+ // Memory terms
63
+ memory: ['storage', 'store', 'cache', 'array'],
64
+
62
65
  // Common verbs
63
66
  create: ['add', 'new', 'generate', 'make'],
64
67
  update: ['modify', 'change', 'edit', 'patch'],
65
68
  delete: ['remove', 'destroy', 'drop'],
66
69
  get: ['fetch', 'retrieve', 'read', 'query'],
70
+ choose: ['select', 'pick', 'decide', 'opt', 'chose'],
71
+ chose: ['choose', 'select', 'pick', 'decide', 'opted'],
67
72
 
68
73
  // Framework terms
69
74
  react: ['reactjs', 'react.js', 'component'],
@@ -288,7 +288,7 @@ export class HttpApiServer {
288
288
 
289
289
  private async handleRecallSimilar(c: any): Promise<Response> {
290
290
  try {
291
- const query = c.req.query('query') || ''
291
+ const query = c.req.query('query') || c.req.query('q') || ''
292
292
 
293
293
  const memoryService = getMemoryService()
294
294
 
@@ -583,6 +583,16 @@ export class HttpApiServer {
583
583
  }
584
584
  }
585
585
 
586
+ /** Normalize project param — extract basename if full path provided */
587
+ private normalizeProject(raw: string): string {
588
+ if (!raw) return raw
589
+ // If it looks like a path (contains /), extract the last segment
590
+ if (raw.includes('/')) {
591
+ return raw.split('/').filter(Boolean).pop() || raw
592
+ }
593
+ return raw
594
+ }
595
+
586
596
  // ─── Phase 28: Code Intelligence Endpoints ─────────────────
587
597
 
588
598
  private async handleCodeIndex(c: any): Promise<Response> {
@@ -669,7 +679,7 @@ export class HttpApiServer {
669
679
  }
670
680
 
671
681
  const query = c.req.query('query') || c.req.query('q') || ''
672
- const project = c.req.query('project') || ''
682
+ const project = this.normalizeProject(c.req.query('project') || '')
673
683
  const limit = parseInt(c.req.query('limit') || '20', 10)
674
684
 
675
685
  if (!query || !project) {
@@ -699,11 +709,11 @@ export class HttpApiServer {
699
709
  )
700
710
  }
701
711
 
702
- const project = c.req.query('project') || ''
712
+ const project = this.normalizeProject(c.req.query('project') || '')
703
713
 
704
714
  if (!project) {
705
715
  return Response.json(
706
- { success: false, error: 'project is required' },
716
+ { success: false, error: 'project query parameter is required (e.g., ?project=my-app)' },
707
717
  { status: 400 }
708
718
  )
709
719
  }
@@ -730,11 +740,11 @@ export class HttpApiServer {
730
740
  }
731
741
 
732
742
  const file = c.req.query('file') || ''
733
- const project = c.req.query('project') || ''
743
+ const project = this.normalizeProject(c.req.query('project') || '')
734
744
 
735
745
  if (!file || !project) {
736
746
  return Response.json(
737
- { success: false, error: 'file and project are required' },
747
+ { success: false, error: 'file and project query parameters are required (e.g., ?file=src/index.ts&project=my-app)' },
738
748
  { status: 400 }
739
749
  )
740
750
  }
@@ -759,11 +769,11 @@ export class HttpApiServer {
759
769
  )
760
770
  }
761
771
 
762
- const project = c.req.query('project') || ''
772
+ const project = this.normalizeProject(c.req.query('project') || '')
763
773
 
764
774
  if (!project) {
765
775
  return Response.json(
766
- { success: false, error: 'project is required' },
776
+ { success: false, error: 'project query parameter is required (e.g., ?project=my-app)' },
767
777
  { status: 400 }
768
778
  )
769
779
  }
@@ -783,7 +793,7 @@ export class HttpApiServer {
783
793
 
784
794
  private async handleContextQuery(c: any): Promise<Response> {
785
795
  try {
786
- const query = c.req.query('query') || ''
796
+ const query = c.req.query('query') || c.req.query('q') || ''
787
797
  const type = c.req.query('type') || ''
788
798
  const cwd = c.req.query('cwd') || ''
789
799
  const limit = parseInt(c.req.query('limit') || '5', 10)
@@ -30,7 +30,7 @@ export function setupWebViewer(app: Hono): void {
30
30
  // Search endpoint — queries FTS5
31
31
  app.get('/api/memory/search', async (c) => {
32
32
  try {
33
- const query = c.req.query('q') || ''
33
+ const query = c.req.query('query') || c.req.query('q') || ''
34
34
  const project = c.req.query('project') || undefined
35
35
  const category = c.req.query('category') || undefined
36
36
  const limit = parseInt(c.req.query('limit') || '20', 10)