claude-brain 0.22.3 → 0.22.4

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.3
1
+ 0.22.4
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-brain",
3
- "version": "0.22.3",
3
+ "version": "0.22.4",
4
4
  "description": "Local development assistant bridging Obsidian vaults with Claude Code via MCP",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -15,6 +15,7 @@ export interface FilterableResult {
15
15
  }
16
16
 
17
17
  export interface FilteredResult {
18
+ id?: string
18
19
  content: string
19
20
  score: number
20
21
  source: string
@@ -74,8 +75,9 @@ export class ResponseFilter {
74
75
  // 5. Limit to 5 results
75
76
  filtered = filtered.slice(0, 5)
76
77
 
77
- // 6. Add one-line relevance explanation per result
78
+ // 6. Add one-line relevance explanation per result (preserve id from metadata)
78
79
  return filtered.map(r => ({
80
+ id: (r.metadata as any)?.id || (r.metadata as any)?.decision_id,
79
81
  content: r.content,
80
82
  score: r.score,
81
83
  source: r.source,
@@ -39,6 +39,7 @@ const UPDATE_MIN_SIMILARITY = 0.3
39
39
  export interface BrainInput {
40
40
  message: string
41
41
  project?: string
42
+ action?: 'auto' | 'store' | 'recall' | 'update' | 'delete'
42
43
  }
43
44
 
44
45
  /** Recently stored decision for recency fast path (Bug 4 fix) */
@@ -84,17 +85,44 @@ export class BrainRouter {
84
85
  }
85
86
 
86
87
  async route(input: BrainInput): Promise<BrainResponse> {
87
- const { message, project: inputProject } = input
88
+ const { message, project: inputProject, action } = input
88
89
 
89
- // Classify intent
90
- const classification = this.classifier.classify(message)
91
- this.logger.debug({ intent: classification.primary, confidence: classification.confidence }, 'Intent classified')
92
-
93
- // Extract entities
90
+ // Extract entities (needed for all paths)
94
91
  const entities = await this.entityExtractor.extract(message, inputProject)
95
92
  const project = entities.project || inputProject
96
93
  this.logger.debug({ project, technologies: entities.technologies }, 'Entities extracted')
97
94
 
95
+ // P1: Explicit action override — bypass intent classifier entirely
96
+ if (action && action !== 'auto') {
97
+ this.logger.debug({ action }, 'Using explicit action override')
98
+ try {
99
+ switch (action) {
100
+ case 'store':
101
+ return this.handleStoreThis(message, project, entities)
102
+ case 'recall':
103
+ return this.handleContextNeeded(message, project, entities)
104
+ case 'update':
105
+ return this.handleUpdateMemory(message, project, entities)
106
+ case 'delete':
107
+ return this.handleDeleteMemory(message, project, entities)
108
+ default:
109
+ break
110
+ }
111
+ } catch (error) {
112
+ this.logger.error({ error, action }, 'Router action override error')
113
+ return {
114
+ action: 'none',
115
+ summary: `Error processing ${action} request`,
116
+ content: `Failed to process: ${error instanceof Error ? error.message : 'Unknown error'}`,
117
+ relevantItems: 0
118
+ }
119
+ }
120
+ }
121
+
122
+ // Classify intent
123
+ const classification = this.classifier.classify(message)
124
+ this.logger.debug({ intent: classification.primary, confidence: classification.confidence }, 'Intent classified')
125
+
98
126
  // Route to handler
99
127
  try {
100
128
  switch (classification.primary) {
@@ -383,7 +411,20 @@ export class BrainRouter {
383
411
  })
384
412
  }
385
413
 
386
- return this.responseFilter.synthesize(tiers, message, displayProject)
414
+ const response = this.responseFilter.synthesize(tiers, message, displayProject)
415
+
416
+ // P0: When no results found and message looks like a plain statement (not a question),
417
+ // add a hint that the user may have intended to store it
418
+ if (response.action === 'none' && response.relevantItems === 0) {
419
+ const isPlainStatement = message.length > 15 &&
420
+ !message.trim().endsWith('?') &&
421
+ message.split(/\s+/).length > 3
422
+ if (isPlainStatement) {
423
+ response.content += `\n\n**Tip:** If you meant to save this, try:\n- \`brain("Remember: ${message.slice(0, 60)}")\`\n- Or use \`action: "store"\` to force storage: \`brain({ message: "...", action: "store" })\``
424
+ }
425
+ }
426
+
427
+ return response
387
428
  }
388
429
 
389
430
  /**
@@ -22,19 +22,20 @@ export async function handleBrain(
22
22
  ): Promise<ToolResponse> {
23
23
  try {
24
24
  const input = ToolValidator.validate(args, BrainSchema)
25
- const { message, project } = input
25
+ const { message, project, action } = input
26
26
 
27
27
  logger.debug(
28
28
  {
29
29
  messageLength: message.length,
30
30
  project,
31
+ action,
31
32
  messagePreview: message.slice(0, 80)
32
33
  },
33
34
  'Brain tool called'
34
35
  )
35
36
 
36
37
  const router = getBrainRouter(logger)
37
- const response = await router.route({ message, project })
38
+ const response = await router.route({ message, project, action })
38
39
 
39
40
  logger.info(
40
41
  {
@@ -137,7 +137,8 @@ export const GetPhase12StatusSchema = z.object({})
137
137
 
138
138
  export const BrainSchema = z.object({
139
139
  message: z.string().min(1, 'message is required'),
140
- project: z.string().optional()
140
+ project: z.string().optional(),
141
+ action: z.enum(['auto', 'store', 'recall', 'update', 'delete']).optional()
141
142
  })
142
143
 
143
144
  // ============================================================================
@@ -814,7 +814,7 @@ export class HttpApiServer {
814
814
  memoryService.recallSimilar(projectName || 'recent work', {
815
815
  project: projectName,
816
816
  limit: 5,
817
- minSimilarity: 0.2,
817
+ minSimilarity: 0.65,
818
818
  }),
819
819
  memoryService.getPatterns(projectName, { limit: 5 }),
820
820
  ])
@@ -16,13 +16,14 @@ const ANSI = {
16
16
 
17
17
  /**
18
18
  * Memory indicator banner shown when Claude Brain provides helpful data
19
+ * Note: No ANSI codes — MCP tool responses should be plain text/markdown
19
20
  */
20
- export const MEMORY_HELPED_BANNER = `${ANSI.brightRed}${ANSI.bold}🧠 MEMORY ASSISTED${ANSI.reset}`
21
+ export const MEMORY_HELPED_BANNER = '🧠 MEMORY ASSISTED'
21
22
 
22
23
  /**
23
24
  * Alternative shorter indicator
24
25
  */
25
- export const MEMORY_HELPED_SHORT = `${ANSI.brightRed}[memory-assisted]${ANSI.reset}`
26
+ export const MEMORY_HELPED_SHORT = '[memory-assisted]'
26
27
 
27
28
  /**
28
29
  * Wrap content with the memory helped indicator
@@ -63,7 +64,7 @@ export function formatMemoryStats(stats: {
63
64
 
64
65
  if (parts.length === 0) return ''
65
66
 
66
- return `${ANSI.red}📊 Retrieved: ${parts.join(', ')}${ANSI.reset}`
67
+ return `📊 Retrieved: ${parts.join(', ')}`
67
68
  }
68
69
 
69
70
  /**
@@ -601,6 +601,11 @@ export const TOOLS = {
601
601
  project: {
602
602
  type: 'string',
603
603
  description: 'Project name (IMPORTANT: pass this to scope memories correctly, e.g. "my-app"). Auto-detected from message if omitted, defaults to "general".'
604
+ },
605
+ action: {
606
+ type: 'string',
607
+ enum: ['auto', 'store', 'recall', 'update', 'delete'],
608
+ description: 'Force a specific action instead of auto-detecting intent. "store" = always save, "recall" = always search, "update" = modify last stored, "delete" = remove. Default: auto (use intent classifier).'
604
609
  }
605
610
  },
606
611
  required: ['message']