claude-brain 0.17.11 → 0.17.13
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/package.json +1 -1
- package/src/hooks/installer.ts +6 -0
- package/src/routing/intent-classifier.ts +13 -4
- package/src/routing/router.ts +29 -15
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.17.
|
|
1
|
+
0.17.13
|
package/package.json
CHANGED
package/src/hooks/installer.ts
CHANGED
|
@@ -48,6 +48,12 @@ function writeSettings(settings: Record<string, any>): void {
|
|
|
48
48
|
function buildHookCommand(event: string, script: 'brain-hook' | 'context-hook' = 'brain-hook'): string {
|
|
49
49
|
const scriptPath = script === 'context-hook' ? getContextHookScriptPath() : getHookScriptPath()
|
|
50
50
|
const port = process.env.PORT || process.env.CLAUDE_BRAIN_PORT || '3000'
|
|
51
|
+
|
|
52
|
+
// Claude Code runs hooks via cmd.exe on Windows, which can't parse VAR=value syntax
|
|
53
|
+
if (process.platform === 'win32') {
|
|
54
|
+
return `set CLAUDE_BRAIN_PORT=${port}&& bun "${scriptPath}" --event ${event} # ${HOOK_MARKER}`
|
|
55
|
+
}
|
|
56
|
+
|
|
51
57
|
return `CLAUDE_BRAIN_PORT=${port} bun "${scriptPath}" --event ${event} # ${HOOK_MARKER}`
|
|
52
58
|
}
|
|
53
59
|
|
|
@@ -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
|
-
|
|
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 {
|
package/src/routing/router.ts
CHANGED
|
@@ -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"
|
|
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,
|
|
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
|
|
1030
|
+
// Bug 4: Inject recently stored decisions using keyword overlap as score
|
|
1026
1031
|
const recentMatches = this.findRecentDecisions(query, searchProject)
|
|
1027
|
-
|
|
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:
|
|
1037
|
+
results: qualifiedRecent.map(d => ({
|
|
1031
1038
|
content: d.content,
|
|
1032
|
-
score: 0
|
|
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
|
-
|
|
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)
|
|
1412
|
-
const contentWords = d.content.toLowerCase().split(/\s+/).filter(w => w.length > 2)
|
|
1413
|
-
const overlap =
|
|
1414
|
-
|
|
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 {
|