claude-brain 0.17.11 → 0.17.12

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.17.11
1
+ 0.17.12
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-brain",
3
- "version": "0.17.11",
3
+ "version": "0.17.12",
4
4
  "description": "Local development assistant bridging Obsidian vaults with Claude Code via MCP",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -41,7 +41,9 @@ const DECISION_PHRASES = [
41
41
  'the solution is', 'implement using', 'going with',
42
42
  'switching to', 'adopting', 'we chose', 'the plan is to',
43
43
  'i decided that', 'decided that', 'choosing', 'we picked',
44
- 'settled on', 'committing to', 'opting for', 'sticking with'
44
+ 'settled on', 'committing to', 'opting for', 'sticking with',
45
+ // BUG-006: "Chose X because Y" — bare "chose" at start
46
+ 'chose ',
45
47
  ]
46
48
 
47
49
  // Explicit "store this" phrases — the user wants to persist something
@@ -81,7 +83,9 @@ const MISTAKE_PHRASES = [
81
83
  'the fix is', 'the fix was', 'fixed by', 'solved by',
82
84
  'was wrong', 'was broken', 'was incorrect',
83
85
  'gotcha:', 'pitfall:', 'watch out for', 'be careful with',
84
- 'turns out that'
86
+ 'turns out that',
87
+ // BUG-006: "Mistake:" prefix (colon form, e.g. "Mistake: I was using...")
88
+ 'mistake:',
85
89
  ]
86
90
 
87
91
  // Progress indicators
@@ -177,7 +181,9 @@ const DELETE_PHRASES = [
177
181
  'forget that', 'forget about', 'delete that', 'delete the memory', 'delete the decision',
178
182
  'remove that memory', 'remove that decision', 'that was wrong',
179
183
  'discard that', 'erase that', 'undo that decision',
180
- 'remove the memory', 'clear that', 'drop that'
184
+ 'remove the memory', 'clear that', 'drop that',
185
+ // BUG-006: Bulk delete support
186
+ 'delete all',
181
187
  ]
182
188
 
183
189
  // Phase 19 B3: Temporal signal phrases
@@ -307,7 +313,10 @@ export class IntentClassifier {
307
313
  if (QUESTION_WORDS.includes(firstWord)) return false
308
314
  // Phase 19 B4: Additional question guards
309
315
  if (this.startsWithQuestionPattern(lower)) return false
310
- return STORE_PHRASES.some(p => lower.includes(p))
316
+ if (STORE_PHRASES.some(p => lower.includes(p))) return true
317
+ // BUG-006: "Using X for Y" at start of message is a declarative preference
318
+ if (lower.startsWith('using ') && lower.includes(' for ')) return true
319
+ return false
311
320
  }
312
321
 
313
322
  private isDecisionMade(lower: string, original?: string): boolean {
@@ -44,6 +44,8 @@ interface RecentDecision {
44
44
  content: string
45
45
  project: string
46
46
  storedAt: number
47
+ /** Keyword overlap ratio with query (0-1), computed by findRecentDecisions */
48
+ overlapScore?: number
47
49
  }
48
50
 
49
51
  /** How long recent decisions stay eligible for fast-path recall (ms) */
@@ -962,14 +964,17 @@ export class BrainRouter {
962
964
  })
963
965
  }
964
966
 
965
- // C11: Add graph results as "Related Concepts" (Bug 3: filter low-score, cap to 0.4)
967
+ // C11: Add graph results as "Related Concepts"
968
+ // When a project filter is active, cap graph results lower (0.25) so they don't
969
+ // outrank project-scoped ChromaDB results. Without a project filter, cap at 0.4.
970
+ const graphScoreCap = searchProject ? 0.25 : 0.4
966
971
  const relevantGraphResults = graphResults.filter(g => g.score >= 0.6)
967
972
  if (relevantGraphResults.length > 0) {
968
973
  tiers.push({
969
974
  label: 'Related Concepts',
970
975
  results: relevantGraphResults.map(g => ({
971
976
  content: g.content,
972
- score: Math.min(g.score, 0.4),
977
+ score: Math.min(g.score, graphScoreCap),
973
978
  source: 'Knowledge Graph',
974
979
  metadata: g.metadata as Record<string, unknown>
975
980
  }))
@@ -1022,14 +1027,16 @@ export class BrainRouter {
1022
1027
  }
1023
1028
  }
1024
1029
 
1025
- // Bug 4: Inject recently stored decisions as high-score tier
1030
+ // Bug 4: Inject recently stored decisions using keyword overlap as score
1026
1031
  const recentMatches = this.findRecentDecisions(query, searchProject)
1027
- if (recentMatches.length > 0) {
1032
+ // Only include results whose overlap score meets the hard floor (0.35)
1033
+ const qualifiedRecent = recentMatches.filter(d => (d.overlapScore || 0) >= 0.35)
1034
+ if (qualifiedRecent.length > 0) {
1028
1035
  tiers.unshift({
1029
1036
  label: 'Recent Decisions',
1030
- results: recentMatches.map(d => ({
1037
+ results: qualifiedRecent.map(d => ({
1031
1038
  content: d.content,
1032
- score: 0.95,
1039
+ score: d.overlapScore || 0,
1033
1040
  source: 'Recent Decision',
1034
1041
  metadata: { storedAt: d.storedAt, project: d.project }
1035
1042
  }))
@@ -1124,14 +1131,15 @@ export class BrainRouter {
1124
1131
  }
1125
1132
  }
1126
1133
 
1127
- // Always include graph exploration
1134
+ // Always include graph exploration — cap scores when project filter is active
1135
+ const explorationGraphCap = searchProject ? 0.25 : 0.5
1128
1136
  const graphResults = await this.searchEngine.searchGraph(query, 10)
1129
1137
  if (graphResults.length > 0) {
1130
1138
  tiers.push({
1131
1139
  label: 'Knowledge Graph',
1132
1140
  results: graphResults.map(g => ({
1133
1141
  content: g.content,
1134
- score: g.score,
1142
+ score: Math.min(g.score, explorationGraphCap),
1135
1143
  source: 'Knowledge Graph',
1136
1144
  metadata: g.metadata as Record<string, unknown>
1137
1145
  }))
@@ -1392,7 +1400,8 @@ export class BrainRouter {
1392
1400
  }
1393
1401
 
1394
1402
  /**
1395
- * Bug 4: Find recent decisions matching a query by keyword overlap
1403
+ * Bug 4: Find recent decisions matching a query by keyword overlap.
1404
+ * Returns matches with their overlapScore (ratio of query keywords found in content).
1396
1405
  */
1397
1406
  private findRecentDecisions(query: string, project?: string): RecentDecision[] {
1398
1407
  const now = Date.now()
@@ -1406,13 +1415,18 @@ export class BrainRouter {
1406
1415
  )
1407
1416
  if (queryWords.size === 0) return []
1408
1417
 
1409
- return this.recentDecisions.filter(d => {
1418
+ const matches: RecentDecision[] = []
1419
+ for (const d of this.recentDecisions) {
1410
1420
  // Project filter: skip if searching a specific project and it doesn't match
1411
- if (project && d.project !== project) return false
1412
- const contentWords = d.content.toLowerCase().split(/\s+/).filter(w => w.length > 2)
1413
- const overlap = contentWords.filter(w => queryWords.has(w)).length
1414
- return overlap >= 1
1415
- })
1421
+ if (project && d.project !== project) continue
1422
+ const contentWords = new Set(d.content.toLowerCase().split(/\s+/).filter(w => w.length > 2))
1423
+ const overlap = [...queryWords].filter(w => contentWords.has(w)).length
1424
+ if (overlap >= 1) {
1425
+ const overlapScore = overlap / queryWords.size
1426
+ matches.push({ ...d, overlapScore })
1427
+ }
1428
+ }
1429
+ return matches
1416
1430
  }
1417
1431
 
1418
1432
  private generateTaskId(title: string): string {