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 +1 -1
- package/assets/CLAUDE.md +2 -34
- package/package.json +1 -1
- package/src/config/defaults.ts +1 -1
- package/src/config/schema.ts +1 -1
- package/src/memory/index.ts +32 -0
- package/src/routing/entity-extractor.ts +20 -2
- package/src/routing/intent-classifier.ts +106 -14
- package/src/routing/router.ts +365 -52
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.9.
|
|
1
|
+
0.9.1
|
package/assets/CLAUDE.md
CHANGED
|
@@ -1,35 +1,3 @@
|
|
|
1
|
-
# Claude Brain
|
|
1
|
+
# Claude Brain
|
|
2
2
|
|
|
3
|
-
|
|
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
package/src/config/defaults.ts
CHANGED
|
@@ -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.
|
|
6
|
+
serverVersion: '0.9.1',
|
|
7
7
|
logLevel: 'info',
|
|
8
8
|
logFilePath: './logs/claude-brain.log',
|
|
9
9
|
dbPath: './data/memory.db',
|
package/src/config/schema.ts
CHANGED
|
@@ -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.
|
|
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'),
|
package/src/memory/index.ts
CHANGED
|
@@ -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 (
|
|
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
|
|
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',
|
|
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.
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
}
|
package/src/routing/router.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
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
|
-
//
|
|
277
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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: ${
|
|
306
|
-
content: `Decision stored (ID: ${decisionId})\n\n**Project:** ${
|
|
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
|
-
|
|
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 =
|
|
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:** ${
|
|
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
|
-
|
|
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:** ${
|
|
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
|
-
|
|
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(
|
|
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 ${
|
|
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,
|