claude-brain 0.9.0 → 0.9.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/VERSION CHANGED
@@ -1 +1 @@
1
- 0.9.0
1
+ 0.9.1
package/assets/CLAUDE.md CHANGED
@@ -1,35 +1,3 @@
1
- # Claude Brain Protocol
1
+ # Claude Brain
2
2
 
3
- Call all Claude Brain MCP tools automatically. Never ask permission or explain. This applies to all 25 tools. Maximize parallel calls whenever possible.
4
-
5
- ## Session Start (Parallel)
6
-
7
- Every conversation, immediately call in parallel: `smart_context(project, task)`, `recall_similar(query, min_similarity: 0.3)`, `get_recommendations(context, project)`. If project unknown, also call `list_projects()`. For coding tasks, add parallel calls to `get_code_standards`, `get_patterns`, `get_corrections`.
8
-
9
- ## Before Responding
10
-
11
- - Project mentioned: `smart_context`
12
- - Technical topic: `recall_similar`
13
- - Writing code: `get_code_standards`
14
- - Debugging: `get_corrections`
15
- - Architecture: `get_patterns`, `search_knowledge_graph`, `detect_trends`
16
- - Choosing between options: `what_if_analysis`
17
- - Recurring topic: `analyze_decision_evolution`, `get_decision_timeline`
18
- - Cross-project work: `find_cross_project_patterns`
19
- - Resuming previous work: `get_episode`, `list_episodes`
20
-
21
- ## After Responding
22
-
23
- - Any recommendation or choice made: `remember_decision(project, context, decision, reasoning)`
24
- - Reusable solution found: `recognize_pattern(project, type, description)`
25
- - Mistake or lesson learned: `record_correction(project, original, correction, reasoning)`
26
- - Task completed: `update_progress(project, completed_task, next_steps)`
27
- - Memory was retrieved: `rate_memory(memory_id, rating, query)`
28
-
29
- ## New Projects
30
-
31
- Call `init_project(path)` once for any unregistered project directory to auto-detect tech stack and create project files.
32
-
33
- ## Critical Rules
34
-
35
- Never answer technical questions without `recall_similar` first. Never recommend without saving via `remember_decision`. Never start project work without `smart_context`. Always call `get_recommendations` before major implementation work. Always call `rate_memory` when memories are retrieved.
3
+ Claude Brain gives you persistent memory across sessions. Just use the `brain` tool naturally tell it what you're doing, what you decided, or what you need to recall. It handles everything automatically.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-brain",
3
- "version": "0.9.0",
3
+ "version": "0.9.1",
4
4
  "description": "Local development assistant bridging Obsidian vaults with Claude Code via MCP",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -3,7 +3,7 @@ import type { PartialConfig } from './schema'
3
3
  /** Default configuration values for Claude Brain */
4
4
  export const defaultConfig: PartialConfig = {
5
5
  serverName: 'claude-brain',
6
- serverVersion: '0.9.0',
6
+ serverVersion: '0.9.1',
7
7
  logLevel: 'info',
8
8
  logFilePath: './logs/claude-brain.log',
9
9
  dbPath: './data/memory.db',
@@ -270,7 +270,7 @@ export const ConfigSchema = z.object({
270
270
  serverName: z.string().default('claude-brain'),
271
271
 
272
272
  /** Server version in semver format */
273
- serverVersion: z.string().regex(/^\d+\.\d+\.\d+$/, 'Version must be semver format').default('0.8.0'),
273
+ serverVersion: z.string().regex(/^\d+\.\d+\.\d+$/, 'Version must be semver format').default('0.9.1'),
274
274
 
275
275
  /** Logging level */
276
276
  logLevel: LogLevelSchema.default('info'),
@@ -507,6 +507,38 @@ export class MemoryManager {
507
507
  }
508
508
  return this.store.searchCorrections(query, options)
509
509
  }
510
+
511
+ /**
512
+ * Delete a decision by ID — routes to ChromaDB or SQLite
513
+ */
514
+ async deleteDecision(id: string): Promise<void> {
515
+ if (this.useChromaDB) {
516
+ await this.chroma.store.deleteDecision(id)
517
+ } else {
518
+ this.store.deleteMemory(id)
519
+ }
520
+ }
521
+
522
+ /**
523
+ * Update a decision by storing a new version and deleting the old one
524
+ */
525
+ async updateDecision(
526
+ oldId: string,
527
+ project: string,
528
+ context: string,
529
+ decision: string,
530
+ reasoning: string,
531
+ options?: { alternatives?: string; tags?: string[] }
532
+ ): Promise<string> {
533
+ // Delete old version
534
+ try {
535
+ await this.deleteDecision(oldId)
536
+ } catch {
537
+ // Old ID might not exist, continue with storing new version
538
+ }
539
+ // Store new version
540
+ return this.rememberDecision(project, context, decision, reasoning, options)
541
+ }
510
542
  }
511
543
 
512
544
  /**
@@ -84,6 +84,19 @@ const TECH_ALIASES: Record<string, string> = {
84
84
  'gql': 'graphql', 'golang': 'go',
85
85
  }
86
86
 
87
+ // Common English words that should never be treated as project names
88
+ const GENERIC_WORDS: Set<string> = new Set([
89
+ 'small', 'large', 'big', 'new', 'old', 'good', 'bad', 'best', 'worst',
90
+ 'simple', 'complex', 'easy', 'hard', 'fast', 'slow', 'quick', 'all',
91
+ 'this', 'that', 'these', 'those', 'some', 'any', 'every', 'each',
92
+ 'other', 'another', 'next', 'last', 'first', 'final', 'main', 'side',
93
+ 'personal', 'general', 'specific', 'local', 'global', 'internal', 'external',
94
+ 'current', 'future', 'past', 'recent', 'data', 'test', 'code', 'file',
95
+ 'the', 'for', 'with', 'from', 'into', 'about', 'like', 'just', 'more',
96
+ 'production', 'development', 'staging', 'projects', 'apps', 'tools',
97
+ 'frontend', 'backend', 'fullstack', 'mobile', 'web', 'desktop', 'cli'
98
+ ])
99
+
87
100
  export interface BrainExtractedEntities {
88
101
  project?: string
89
102
  technologies: string[]
@@ -159,8 +172,13 @@ export class BrainEntityExtractor {
159
172
  const match = message.match(pattern)
160
173
  if (match && match[1]) {
161
174
  const candidate = match[1].toLowerCase()
162
- // Filter out common false positives
163
- if (!COMMON_TECH.has(candidate) && candidate.length > 1 && candidate.length < 50) {
175
+ // Filter out common false positives: tech names, generic words, adjectives
176
+ if (
177
+ !COMMON_TECH.has(candidate) &&
178
+ !GENERIC_WORDS.has(candidate) &&
179
+ candidate.length > 2 &&
180
+ candidate.length < 50
181
+ ) {
164
182
  return candidate
165
183
  }
166
184
  }
@@ -10,12 +10,16 @@ export type Intent =
10
10
  | 'session_start'
11
11
  | 'context_needed'
12
12
  | 'decision_made'
13
+ | 'store_this'
13
14
  | 'pattern_found'
14
15
  | 'mistake_learned'
15
16
  | 'progress_update'
16
17
  | 'question'
17
18
  | 'comparison'
18
19
  | 'exploration'
20
+ | 'list_all'
21
+ | 'update_memory'
22
+ | 'delete_memory'
19
23
  | 'no_action'
20
24
 
21
25
  export interface ClassificationResult {
@@ -24,13 +28,30 @@ export interface ClassificationResult {
24
28
  secondary: Intent[]
25
29
  }
26
30
 
27
- // Decision-indicating phrases (from automation/decision-detector.ts)
31
+ // Decision-indicating phrases
28
32
  const DECISION_PHRASES = [
29
33
  'i recommend', 'you should use', 'the best approach',
30
34
  'i suggest', 'better to use', 'prefer using',
31
35
  'go with', 'decided to', "let's use", 'we will use',
32
36
  'the solution is', 'implement using', 'going with',
33
- 'switching to', 'adopting', 'we chose', 'the plan is to'
37
+ 'switching to', 'adopting', 'we chose', 'the plan is to',
38
+ 'i decided that', 'decided that', 'choosing', 'we picked',
39
+ 'settled on', 'committing to', 'opting for', 'sticking with'
40
+ ]
41
+
42
+ // Explicit "store this" phrases — the user wants to persist something
43
+ const STORE_PHRASES = [
44
+ 'remember:', 'remember this:', 'remember that',
45
+ 'note to self:', 'note to self', 'note:', 'save this:',
46
+ 'save this', 'store this', 'keep this', 'record this',
47
+ 'i prefer', 'my preference is', 'my convention is',
48
+ 'i like to', 'i always', 'always use', 'never use this',
49
+ 'from now on', 'going forward', 'the rule is',
50
+ 'my approach is', 'my standard is', 'the standard is',
51
+ 'we should always', 'we should never',
52
+ 'i want to remember', 'don\'t forget',
53
+ 'for future reference', 'for the record',
54
+ 'important:', 'key decision:', 'takeaway:'
34
55
  ]
35
56
 
36
57
  const REASONING_PHRASES = [
@@ -43,16 +64,20 @@ const REASONING_PHRASES = [
43
64
  const MISTAKE_PHRASES = [
44
65
  'bug was', 'the issue was', 'the problem was', 'mistake was',
45
66
  'should have', "shouldn't have", 'should not have',
46
- 'lesson learned', "don't use", 'avoid using', 'never use',
67
+ 'lesson learned', "don't use", 'avoid using',
47
68
  'the fix is', 'the fix was', 'fixed by', 'solved by',
48
- 'was wrong', 'was broken', 'was incorrect'
69
+ 'was wrong', 'was broken', 'was incorrect',
70
+ 'gotcha:', 'pitfall:', 'watch out for', 'be careful with',
71
+ 'i learned that', 'turns out that'
49
72
  ]
50
73
 
51
74
  // Progress indicators
52
75
  const PROGRESS_PHRASES = [
53
76
  'finished', 'completed', 'done with', 'implemented',
54
77
  'built', 'created', 'added', 'fixed', 'resolved',
55
- 'shipped', 'deployed', 'merged', 'released'
78
+ 'shipped', 'deployed', 'merged', 'released',
79
+ "i'm working on", 'currently working', 'making progress',
80
+ 'just did', 'just finished'
56
81
  ]
57
82
 
58
83
  // Comparison indicators
@@ -97,6 +122,32 @@ const NO_ACTION_PHRASES = [
97
122
  'understood', 'noted', 'alright', 'right'
98
123
  ]
99
124
 
125
+ // List/browse indicators
126
+ const LIST_PHRASES = [
127
+ 'list all', 'show all', 'what decisions',
128
+ 'what have i decided', 'all my decisions', 'all decisions',
129
+ 'everything about', 'what do i know', 'what do we know',
130
+ 'show me everything', 'list decisions', 'list memories',
131
+ 'browse', 'dump all', 'show my'
132
+ ]
133
+
134
+ // Update/correct existing memory
135
+ const UPDATE_PHRASES = [
136
+ 'actually,', 'actually ', 'correction:', 'update that',
137
+ 'i changed my mind', 'change that to', 'instead of what i said',
138
+ 'supersedes', 'override', 'revise that', 'amend that',
139
+ 'that should be', 'update the decision', 'modify that',
140
+ 'replace that with', 'no wait', 'scratch that, use'
141
+ ]
142
+
143
+ // Delete/forget memory
144
+ const DELETE_PHRASES = [
145
+ 'forget that', 'forget about', 'delete that', 'delete the',
146
+ 'remove that memory', 'remove that decision', 'that was wrong',
147
+ 'discard that', 'erase that', 'undo that decision',
148
+ 'remove the memory', 'clear that', 'drop that'
149
+ ]
150
+
100
151
  export class IntentClassifier {
101
152
  /**
102
153
  * Classify the intent of a message
@@ -112,54 +163,75 @@ export class IntentClassifier {
112
163
  return { primary: 'no_action', confidence: 0.95, secondary: [] }
113
164
  }
114
165
 
115
- // 2. decision_made: decision phrases + reasoning
166
+ // 2. delete_memory: "forget that", "delete", "remove"
167
+ if (this.isDeleteMemory(lower)) {
168
+ return { primary: 'delete_memory', confidence: 0.90, secondary }
169
+ }
170
+
171
+ // 3. update_memory: "actually", "correction:", "change that to"
172
+ if (this.isUpdateMemory(lower)) {
173
+ return { primary: 'update_memory', confidence: 0.85, secondary }
174
+ }
175
+
176
+ // 4. store_this: explicit "remember:", "save this:", "I prefer"
177
+ if (this.isStoreThis(lower)) {
178
+ if (this.hasDecisionSignal(lower)) secondary.push('decision_made')
179
+ return { primary: 'store_this', confidence: 0.90, secondary }
180
+ }
181
+
182
+ // 5. decision_made: decision phrases + reasoning
116
183
  if (this.isDecisionMade(lower)) {
117
184
  if (this.hasComparisonSignal(lower)) secondary.push('comparison')
118
185
  return { primary: 'decision_made', confidence: 0.85, secondary }
119
186
  }
120
187
 
121
- // 3. mistake_learned: correction/bug/lesson indicators
188
+ // 6. mistake_learned: correction/bug/lesson indicators
122
189
  if (this.isMistakeLearned(lower)) {
123
190
  return { primary: 'mistake_learned', confidence: 0.85, secondary }
124
191
  }
125
192
 
126
- // 4. progress_update: completed task indicators (NOT questions)
193
+ // 7. list_all: "list all", "what decisions", "show all"
194
+ if (this.isListAll(lower, message)) {
195
+ return { primary: 'list_all', confidence: 0.85, secondary }
196
+ }
197
+
198
+ // 8. progress_update: completed task indicators (NOT questions)
127
199
  if (this.isProgressUpdate(lower, message)) {
128
200
  if (this.hasSessionSignal(lower)) secondary.push('session_start')
129
201
  return { primary: 'progress_update', confidence: 0.85, secondary }
130
202
  }
131
203
 
132
- // 5. comparison: vs, which is better, etc.
204
+ // 9. comparison: vs, which is better, etc.
133
205
  if (this.isComparison(lower)) {
134
206
  if (this.isQuestion(lower, message)) secondary.push('question')
135
207
  return { primary: 'comparison', confidence: 0.85, secondary }
136
208
  }
137
209
 
138
- // 6. pattern_found: explicit pattern documentation
210
+ // 10. pattern_found: explicit pattern documentation
139
211
  if (this.isPatternFound(lower)) {
140
212
  return { primary: 'pattern_found', confidence: 0.80, secondary }
141
213
  }
142
214
 
143
- // 7. session_start: starting/resuming work
215
+ // 11. session_start: starting/resuming work
144
216
  if (this.isSessionStart(lower)) {
145
217
  secondary.push('context_needed')
146
218
  return { primary: 'session_start', confidence: 0.90, secondary }
147
219
  }
148
220
 
149
- // 8. exploration: timeline, trends, graph, history
221
+ // 12. exploration: timeline, trends, graph, history
150
222
  if (this.isExploration(lower)) {
151
223
  if (this.isQuestion(lower, message)) secondary.push('question')
152
224
  return { primary: 'exploration', confidence: 0.75, secondary }
153
225
  }
154
226
 
155
- // 9. question: starts with question word or ends with ?
227
+ // 13. question: starts with question word or ends with ?
156
228
  if (this.isQuestion(lower, message)) {
157
229
  if (this.hasComparisonSignal(lower)) secondary.push('comparison')
158
230
  if (this.hasExplorationSignal(lower)) secondary.push('exploration')
159
231
  return { primary: 'question', confidence: 0.80, secondary }
160
232
  }
161
233
 
162
- // 10. Default: context_needed
234
+ // 14. Default: context_needed
163
235
  return { primary: 'context_needed', confidence: 0.60, secondary }
164
236
  }
165
237
 
@@ -170,6 +242,10 @@ export class IntentClassifier {
170
242
  return NO_ACTION_PHRASES.some(p => lower === p || lower === p + '.' || lower === p + '!')
171
243
  }
172
244
 
245
+ private isStoreThis(lower: string): boolean {
246
+ return STORE_PHRASES.some(p => lower.includes(p))
247
+ }
248
+
173
249
  private isDecisionMade(lower: string): boolean {
174
250
  const hasDecision = DECISION_PHRASES.some(p => lower.includes(p))
175
251
  if (!hasDecision) return false
@@ -182,6 +258,10 @@ export class IntentClassifier {
182
258
  return MISTAKE_PHRASES.some(p => lower.includes(p))
183
259
  }
184
260
 
261
+ private isListAll(lower: string, _original: string): boolean {
262
+ return LIST_PHRASES.some(p => lower.includes(p))
263
+ }
264
+
185
265
  private isProgressUpdate(lower: string, original: string): boolean {
186
266
  // Must not be a question
187
267
  if (original.trim().endsWith('?')) return false
@@ -214,7 +294,19 @@ export class IntentClassifier {
214
294
  return QUESTION_WORDS.includes(firstWord)
215
295
  }
216
296
 
297
+ private isUpdateMemory(lower: string): boolean {
298
+ return UPDATE_PHRASES.some(p => lower.includes(p))
299
+ }
300
+
301
+ private isDeleteMemory(lower: string): boolean {
302
+ return DELETE_PHRASES.some(p => lower.includes(p))
303
+ }
304
+
217
305
  // Secondary signal checks
306
+ private hasDecisionSignal(lower: string): boolean {
307
+ return DECISION_PHRASES.some(p => lower.includes(p))
308
+ }
309
+
218
310
  private hasComparisonSignal(lower: string): boolean {
219
311
  return COMPARISON_PHRASES.some(p => lower.includes(p))
220
312
  }
@@ -19,6 +19,9 @@ import {
19
19
  isServicesInitialized
20
20
  } from '@/server/services'
21
21
 
22
+ /** Default project when none can be detected */
23
+ const DEFAULT_PROJECT = 'general'
24
+
22
25
  export interface BrainInput {
23
26
  message: string
24
27
  project?: string
@@ -30,6 +33,10 @@ export class BrainRouter {
30
33
  private responseFilter: ResponseFilter
31
34
  private logger: Logger
32
35
 
36
+ /** Track the most recently stored decision ID for update/delete operations */
37
+ private lastStoredId: string | null = null
38
+ private lastStoredProject: string | null = null
39
+
33
40
  constructor(logger: Logger) {
34
41
  this.classifier = new IntentClassifier()
35
42
  this.entityExtractor = new BrainEntityExtractor()
@@ -64,6 +71,9 @@ export class BrainRouter {
64
71
  case 'decision_made':
65
72
  return this.handleDecisionMade(message, project, entities)
66
73
 
74
+ case 'store_this':
75
+ return this.handleStoreThis(message, project, entities)
76
+
67
77
  case 'pattern_found':
68
78
  return this.handlePatternFound(message, project, entities)
69
79
 
@@ -82,6 +92,15 @@ export class BrainRouter {
82
92
  case 'exploration':
83
93
  return this.handleExploration(message, project, entities)
84
94
 
95
+ case 'list_all':
96
+ return this.handleListAll(message, project, entities)
97
+
98
+ case 'update_memory':
99
+ return this.handleUpdateMemory(message, project, entities)
100
+
101
+ case 'delete_memory':
102
+ return this.handleDeleteMemory(message, project, entities)
103
+
85
104
  default:
86
105
  return this.handleContextNeeded(message, project, entities)
87
106
  }
@@ -252,35 +271,78 @@ export class BrainRouter {
252
271
  return this.responseFilter.synthesize(tiers, message, project)
253
272
  }
254
273
 
255
- private async handleDecisionMade(
274
+ /**
275
+ * Handle explicit "store this" requests — always uses the FULL message as the decision text.
276
+ * This prevents content mangling from entity extraction.
277
+ */
278
+ private async handleStoreThis(
256
279
  message: string,
257
280
  project: string | undefined,
258
281
  entities: BrainExtractedEntities
259
282
  ): Promise<BrainResponse> {
260
- if (!project) {
261
- return {
262
- action: 'none',
263
- summary: 'Cannot store decision without a project',
264
- content: 'Please specify which project this decision relates to.',
265
- relevantItems: 0
283
+ // Use "general" as fallback when no project is detected
284
+ const effectiveProject = project || DEFAULT_PROJECT
285
+
286
+ if (!isServicesInitialized()) {
287
+ return this.servicesNotReady()
288
+ }
289
+
290
+ const memory = getMemoryService()
291
+
292
+ // ALWAYS use the full message as the decision — never the extracted fragment
293
+ const decision = message
294
+ const reasoning = entities.reasoning || ''
295
+ const context = entities.topic || message.slice(0, 200)
296
+
297
+ const decisionId = await memory.rememberDecision(
298
+ effectiveProject,
299
+ context,
300
+ decision,
301
+ reasoning,
302
+ {
303
+ alternatives: entities.alternatives,
304
+ tags: entities.technologies.length > 0 ? entities.technologies : ['user-stored']
266
305
  }
306
+ )
307
+
308
+ // Track for potential update/delete
309
+ this.lastStoredId = decisionId
310
+ this.lastStoredProject = effectiveProject
311
+
312
+ // Also write to vault
313
+ this.writeToVault(effectiveProject, decision, reasoning, context, entities.alternatives)
314
+
315
+ return {
316
+ action: 'stored',
317
+ summary: `Stored: ${message.slice(0, 60)}`,
318
+ content: `Memory stored (ID: ${decisionId})\n\n**Project:** ${effectiveProject}\n**Content:** ${decision}${reasoning ? `\n**Reasoning:** ${reasoning}` : ''}`,
319
+ relevantItems: 1
267
320
  }
321
+ }
322
+
323
+ private async handleDecisionMade(
324
+ message: string,
325
+ project: string | undefined,
326
+ entities: BrainExtractedEntities
327
+ ): Promise<BrainResponse> {
328
+ // Use "general" as fallback when no project is detected
329
+ const effectiveProject = project || DEFAULT_PROJECT
268
330
 
269
331
  if (!isServicesInitialized()) {
270
332
  return this.servicesNotReady()
271
333
  }
272
334
 
273
335
  const memory = getMemoryService()
274
- const vault = getVaultService()
275
336
 
276
- // Extract decision components (use entities or fall back to raw message)
277
- const decision = entities.decision || message
337
+ // ALWAYS use the full message as the decision text to prevent content mangling.
338
+ // The extracted entities are used only for structured metadata.
339
+ const decision = message
278
340
  const reasoning = entities.reasoning || ''
279
341
  const context = entities.topic || message.slice(0, 200)
280
342
  const alternatives = entities.alternatives
281
343
 
282
344
  const decisionId = await memory.rememberDecision(
283
- project,
345
+ effectiveProject,
284
346
  context,
285
347
  decision,
286
348
  reasoning,
@@ -290,20 +352,17 @@ export class BrainRouter {
290
352
  }
291
353
  )
292
354
 
355
+ // Track for potential update/delete
356
+ this.lastStoredId = decisionId
357
+ this.lastStoredProject = effectiveProject
358
+
293
359
  // Also write to vault
294
- try {
295
- const projectPaths = vault.getProjectPaths(project)
296
- const date = new Date().toISOString().split('T')[0]
297
- const entry = `### Decision: ${decision.slice(0, 100)}\n\n**Date:** ${date}\n**Context:** ${context}\n**Decision:** ${decision}\n**Reasoning:** ${reasoning}\n${alternatives ? `**Alternatives:** ${alternatives}\n` : ''}\n---\n\n`
298
- await vault.writer.appendContent(projectPaths.decisions, entry, '\n')
299
- } catch {
300
- // Vault write failed — memory storage still succeeded
301
- }
360
+ this.writeToVault(effectiveProject, decision, reasoning, context, alternatives)
302
361
 
303
362
  return {
304
363
  action: 'stored',
305
- summary: `Stored decision: ${decision.slice(0, 60)}`,
306
- content: `Decision stored (ID: ${decisionId})\n\n**Project:** ${project}\n**Decision:** ${decision}\n**Reasoning:** ${reasoning}`,
364
+ summary: `Stored decision: ${message.slice(0, 60)}`,
365
+ content: `Decision stored (ID: ${decisionId})\n\n**Project:** ${effectiveProject}\n**Decision:** ${decision}\n**Reasoning:** ${reasoning}`,
307
366
  relevantItems: 1
308
367
  }
309
368
  }
@@ -313,14 +372,7 @@ export class BrainRouter {
313
372
  project: string | undefined,
314
373
  entities: BrainExtractedEntities
315
374
  ): Promise<BrainResponse> {
316
- if (!project) {
317
- return {
318
- action: 'none',
319
- summary: 'Cannot store pattern without a project',
320
- content: 'Please specify which project this pattern relates to.',
321
- relevantItems: 0
322
- }
323
- }
375
+ const effectiveProject = project || DEFAULT_PROJECT
324
376
 
325
377
  if (!isServicesInitialized()) {
326
378
  return this.servicesNotReady()
@@ -328,10 +380,10 @@ export class BrainRouter {
328
380
 
329
381
  const memory = getMemoryService()
330
382
  const patternType = entities.patternType || 'solution'
331
- const description = entities.topic || message
383
+ const description = message // Use full message
332
384
 
333
385
  const patternId = await memory.storePattern({
334
- project,
386
+ project: effectiveProject,
335
387
  pattern_type: patternType,
336
388
  description,
337
389
  confidence: 0.8,
@@ -341,7 +393,7 @@ export class BrainRouter {
341
393
  return {
342
394
  action: 'stored',
343
395
  summary: `Stored ${patternType}: ${description.slice(0, 60)}`,
344
- content: `Pattern stored (ID: ${patternId})\n\n**Type:** ${patternType}\n**Project:** ${project}\n**Description:** ${description}`,
396
+ content: `Pattern stored (ID: ${patternId})\n\n**Type:** ${patternType}\n**Project:** ${effectiveProject}\n**Description:** ${description}`,
345
397
  relevantItems: 1
346
398
  }
347
399
  }
@@ -351,14 +403,7 @@ export class BrainRouter {
351
403
  project: string | undefined,
352
404
  entities: BrainExtractedEntities
353
405
  ): Promise<BrainResponse> {
354
- if (!project) {
355
- return {
356
- action: 'none',
357
- summary: 'Cannot store correction without a project',
358
- content: 'Please specify which project this lesson relates to.',
359
- relevantItems: 0
360
- }
361
- }
406
+ const effectiveProject = project || DEFAULT_PROJECT
362
407
 
363
408
  if (!isServicesInitialized()) {
364
409
  return this.servicesNotReady()
@@ -366,12 +411,13 @@ export class BrainRouter {
366
411
 
367
412
  const memory = getMemoryService()
368
413
 
414
+ // Use full message as the original content to prevent mangling
369
415
  const original = entities.original || message
370
416
  const correction = entities.correction || ''
371
417
  const reasoning = entities.reasoning || 'Lesson learned from experience'
372
418
 
373
419
  const correctionId = await memory.storeCorrection({
374
- project,
420
+ project: effectiveProject,
375
421
  original,
376
422
  correction: correction || message,
377
423
  reasoning,
@@ -382,7 +428,7 @@ export class BrainRouter {
382
428
  return {
383
429
  action: 'stored',
384
430
  summary: `Stored correction: ${original.slice(0, 60)}`,
385
- content: `Correction stored (ID: ${correctionId})\n\n**Project:** ${project}\n**Original:** ${original}\n**Correction:** ${correction || '(see original message)'}`,
431
+ content: `Correction stored (ID: ${correctionId})\n\n**Project:** ${effectiveProject}\n**Original:** ${original}\n**Correction:** ${correction || '(see original message)'}`,
386
432
  relevantItems: 1
387
433
  }
388
434
  }
@@ -392,26 +438,21 @@ export class BrainRouter {
392
438
  project: string | undefined,
393
439
  entities: BrainExtractedEntities
394
440
  ): Promise<BrainResponse> {
395
- if (!project) {
396
- return {
397
- action: 'none',
398
- summary: 'Cannot update progress without a project',
399
- content: 'Please specify which project to update progress for.',
400
- relevantItems: 0
401
- }
402
- }
441
+ const effectiveProject = project || DEFAULT_PROJECT
403
442
 
404
443
  if (!isServicesInitialized()) {
405
444
  return this.servicesNotReady()
406
445
  }
407
446
 
408
447
  const contextService = getContextService()
448
+ const memory = getMemoryService()
409
449
 
410
450
  const completedTask = entities.completedTask || message
411
451
  const nextSteps = entities.nextSteps || 'Continue development'
412
452
 
453
+ // Store in session context
413
454
  try {
414
- await contextService.progress.addCompletedTask(project, {
455
+ await contextService.progress.addCompletedTask(effectiveProject, {
415
456
  id: this.generateTaskId(completedTask),
416
457
  title: completedTask,
417
458
  status: 'done',
@@ -421,14 +462,268 @@ export class BrainRouter {
421
462
  // Progress update failed — still report what we found
422
463
  }
423
464
 
465
+ // Also store as a searchable memory in ChromaDB so it appears in search results
466
+ try {
467
+ await memory.rememberDecision(
468
+ effectiveProject,
469
+ `Progress update`,
470
+ `Progress: ${completedTask}${nextSteps !== 'Continue development' ? `. Next: ${nextSteps}` : ''}`,
471
+ 'Progress tracking',
472
+ { tags: ['progress', ...entities.technologies] }
473
+ )
474
+ } catch {
475
+ // Memory storage failed, context-only storage still succeeded
476
+ }
477
+
424
478
  return {
425
479
  action: 'stored',
426
480
  summary: `Progress: ${completedTask.slice(0, 60)}`,
427
- content: `Progress updated for ${project}\n\n**Completed:** ${completedTask}\n**Next:** ${nextSteps}`,
481
+ content: `Progress updated for ${effectiveProject}\n\n**Completed:** ${completedTask}\n**Next:** ${nextSteps}`,
482
+ relevantItems: 1
483
+ }
484
+ }
485
+
486
+ /**
487
+ * List all decisions for a project
488
+ */
489
+ private async handleListAll(
490
+ message: string,
491
+ project: string | undefined,
492
+ entities: BrainExtractedEntities
493
+ ): Promise<BrainResponse> {
494
+ if (!isServicesInitialized()) {
495
+ return this.servicesNotReady()
496
+ }
497
+
498
+ const memory = getMemoryService()
499
+ const effectiveProject = project // No fallback — list all if no project
500
+
501
+ try {
502
+ const decisions = await memory.fetchAllDecisions(effectiveProject)
503
+ const patterns = await memory.fetchAllPatterns(effectiveProject)
504
+ const corrections = await memory.fetchAllCorrections(effectiveProject)
505
+
506
+ const parts: string[] = []
507
+ const projectLabel = effectiveProject || 'all projects'
508
+
509
+ if (decisions.length > 0) {
510
+ parts.push(`## Decisions (${decisions.length})`)
511
+ for (const d of decisions.slice(0, 20)) {
512
+ const decision = d.decision || d.document || d.content || ''
513
+ const date = d.created_at ? ` (${String(d.created_at).split('T')[0]})` : ''
514
+ parts.push(`- ${decision.slice(0, 150)}${date}`)
515
+ }
516
+ if (decisions.length > 20) {
517
+ parts.push(`_...and ${decisions.length - 20} more_`)
518
+ }
519
+ parts.push('')
520
+ }
521
+
522
+ if (patterns.length > 0) {
523
+ parts.push(`## Patterns (${patterns.length})`)
524
+ for (const p of patterns.slice(0, 10)) {
525
+ const desc = p.description || p.document || p.content || ''
526
+ parts.push(`- **${p.pattern_type || 'solution'}**: ${desc.slice(0, 120)}`)
527
+ }
528
+ parts.push('')
529
+ }
530
+
531
+ if (corrections.length > 0) {
532
+ parts.push(`## Corrections (${corrections.length})`)
533
+ for (const c of corrections.slice(0, 10)) {
534
+ const orig = c.original || c.document || c.content || ''
535
+ parts.push(`- ${orig.slice(0, 120)}`)
536
+ }
537
+ parts.push('')
538
+ }
539
+
540
+ const total = decisions.length + patterns.length + corrections.length
541
+
542
+ if (total === 0) {
543
+ return {
544
+ action: 'none',
545
+ summary: `No memories found for ${projectLabel}`,
546
+ content: `No decisions, patterns, or corrections stored yet for ${projectLabel}.`,
547
+ relevantItems: 0
548
+ }
549
+ }
550
+
551
+ return {
552
+ action: 'retrieved',
553
+ summary: `${total} memories for ${projectLabel}`,
554
+ content: parts.join('\n'),
555
+ relevantItems: total
556
+ }
557
+ } catch (error) {
558
+ return {
559
+ action: 'none',
560
+ summary: 'Error listing memories',
561
+ content: `Failed to list memories: ${error instanceof Error ? error.message : 'Unknown error'}`,
562
+ relevantItems: 0
563
+ }
564
+ }
565
+ }
566
+
567
+ /**
568
+ * Update a previous decision — finds the most recent matching memory and replaces it
569
+ */
570
+ private async handleUpdateMemory(
571
+ message: string,
572
+ project: string | undefined,
573
+ entities: BrainExtractedEntities
574
+ ): Promise<BrainResponse> {
575
+ const effectiveProject = project || this.lastStoredProject || DEFAULT_PROJECT
576
+
577
+ if (!isServicesInitialized()) {
578
+ return this.servicesNotReady()
579
+ }
580
+
581
+ const memory = getMemoryService()
582
+
583
+ // Strategy: find the most recent similar decision, delete it, store the new one
584
+ // First try the lastStoredId shortcut
585
+ if (this.lastStoredId) {
586
+ const newId = await memory.updateDecision(
587
+ this.lastStoredId,
588
+ effectiveProject,
589
+ `Updated: ${entities.topic || message.slice(0, 200)}`,
590
+ message,
591
+ entities.reasoning || 'Updated via brain tool',
592
+ { tags: entities.technologies.length > 0 ? entities.technologies : ['updated'] }
593
+ )
594
+
595
+ this.lastStoredId = newId
596
+ this.lastStoredProject = effectiveProject
597
+
598
+ return {
599
+ action: 'stored',
600
+ summary: `Updated decision`,
601
+ content: `Previous decision replaced with:\n\n**Project:** ${effectiveProject}\n**New content:** ${message}`,
602
+ relevantItems: 1
603
+ }
604
+ }
605
+
606
+ // No recent ID — search for the most similar decision to update
607
+ try {
608
+ const results = await memory.searchRaw(entities.topic || message, {
609
+ project: effectiveProject,
610
+ limit: 1,
611
+ minSimilarity: 0.3
612
+ })
613
+
614
+ if (results.length > 0 && results[0].id) {
615
+ const oldId = results[0].id
616
+ const newId = await memory.updateDecision(
617
+ oldId,
618
+ effectiveProject,
619
+ `Updated: ${entities.topic || message.slice(0, 200)}`,
620
+ message,
621
+ entities.reasoning || 'Updated via brain tool',
622
+ { tags: entities.technologies.length > 0 ? entities.technologies : ['updated'] }
623
+ )
624
+
625
+ this.lastStoredId = newId
626
+ this.lastStoredProject = effectiveProject
627
+
628
+ const oldContent = results[0].decision?.decision || results[0].content?.slice(0, 100) || ''
629
+ return {
630
+ action: 'stored',
631
+ summary: `Updated decision`,
632
+ content: `Replaced: "${oldContent.slice(0, 80)}"\n\nWith:\n**New content:** ${message}`,
633
+ relevantItems: 1
634
+ }
635
+ }
636
+ } catch {
637
+ // Search failed
638
+ }
639
+
640
+ // Fallback: just store as a new decision
641
+ const decisionId = await memory.rememberDecision(
642
+ effectiveProject,
643
+ entities.topic || message.slice(0, 200),
644
+ message,
645
+ entities.reasoning || '',
646
+ { tags: entities.technologies.length > 0 ? entities.technologies : ['updated'] }
647
+ )
648
+
649
+ this.lastStoredId = decisionId
650
+ this.lastStoredProject = effectiveProject
651
+
652
+ return {
653
+ action: 'stored',
654
+ summary: `Stored as new (no matching decision found to update)`,
655
+ content: `Stored as new decision (ID: ${decisionId})\n\n**Project:** ${effectiveProject}\n**Content:** ${message}`,
428
656
  relevantItems: 1
429
657
  }
430
658
  }
431
659
 
660
+ /**
661
+ * Delete a memory — finds the most recent matching memory and removes it
662
+ */
663
+ private async handleDeleteMemory(
664
+ message: string,
665
+ project: string | undefined,
666
+ entities: BrainExtractedEntities
667
+ ): Promise<BrainResponse> {
668
+ const effectiveProject = project || this.lastStoredProject || DEFAULT_PROJECT
669
+
670
+ if (!isServicesInitialized()) {
671
+ return this.servicesNotReady()
672
+ }
673
+
674
+ const memory = getMemoryService()
675
+
676
+ // First try the lastStoredId shortcut
677
+ if (this.lastStoredId) {
678
+ try {
679
+ await memory.deleteDecision(this.lastStoredId)
680
+ const deletedId = this.lastStoredId
681
+ this.lastStoredId = null
682
+ return {
683
+ action: 'stored',
684
+ summary: `Deleted most recent memory`,
685
+ content: `Deleted memory (ID: ${deletedId})`,
686
+ relevantItems: 0
687
+ }
688
+ } catch {
689
+ // Deletion failed, try search approach
690
+ }
691
+ }
692
+
693
+ // Search for the most similar decision to delete
694
+ try {
695
+ const results = await memory.searchRaw(entities.topic || message, {
696
+ project: effectiveProject,
697
+ limit: 1,
698
+ minSimilarity: 0.3
699
+ })
700
+
701
+ if (results.length > 0 && results[0].id) {
702
+ const targetId = results[0].id
703
+ const content = results[0].decision?.decision || results[0].content?.slice(0, 100) || ''
704
+
705
+ await memory.deleteDecision(targetId)
706
+ this.lastStoredId = null
707
+
708
+ return {
709
+ action: 'stored',
710
+ summary: `Deleted memory`,
711
+ content: `Deleted: "${content.slice(0, 100)}" (ID: ${targetId})`,
712
+ relevantItems: 0
713
+ }
714
+ }
715
+ } catch {
716
+ // Search failed
717
+ }
718
+
719
+ return {
720
+ action: 'none',
721
+ summary: 'No matching memory found to delete',
722
+ content: 'Could not find a memory matching your request. Try being more specific about what to delete.',
723
+ relevantItems: 0
724
+ }
725
+ }
726
+
432
727
  private async handleQuestion(
433
728
  message: string,
434
729
  project: string | undefined,
@@ -612,6 +907,24 @@ export class BrainRouter {
612
907
 
613
908
  // ===== Helpers =====
614
909
 
910
+ private async writeToVault(
911
+ project: string,
912
+ decision: string,
913
+ reasoning: string,
914
+ context: string,
915
+ alternatives?: string
916
+ ): Promise<void> {
917
+ try {
918
+ const vault = getVaultService()
919
+ const projectPaths = vault.getProjectPaths(project)
920
+ const date = new Date().toISOString().split('T')[0]
921
+ const entry = `### Decision: ${decision.slice(0, 100)}\n\n**Date:** ${date}\n**Context:** ${context}\n**Decision:** ${decision}\n**Reasoning:** ${reasoning}\n${alternatives ? `**Alternatives:** ${alternatives}\n` : ''}\n---\n\n`
922
+ await vault.writer.appendContent(projectPaths.decisions, entry, '\n')
923
+ } catch {
924
+ // Vault write failed — memory storage still succeeded
925
+ }
926
+ }
927
+
615
928
  private async addExplorationResults(
616
929
  query: string,
617
930
  _project: string | undefined,