claude-brain 0.17.12 → 0.17.14

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.12
1
+ 0.17.14
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-brain",
3
- "version": "0.17.12",
3
+ "version": "0.17.14",
4
4
  "description": "Local development assistant bridging Obsidian vaults with Claude Code via MCP",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -52,7 +52,10 @@ export async function main(): Promise<void> {
52
52
  return
53
53
  }
54
54
 
55
- const port = parseInt(process.env.CLAUDE_BRAIN_PORT || '3000', 10)
55
+ // BUG-006: Read port from --port arg (cross-platform) or env var fallback
56
+ const portIdx = process.argv.indexOf('--port')
57
+ const portArg = portIdx >= 0 ? process.argv[portIdx + 1] : undefined
58
+ const port = parseInt(portArg || process.env.CLAUDE_BRAIN_PORT || '3000', 10)
56
59
 
57
60
  // For Stop events: trigger session-end summarization regardless of classification
58
61
  if (input.hook_event_name === 'Stop' && input.session_id) {
@@ -85,7 +85,10 @@ async function main(): Promise<void> {
85
85
  return
86
86
  }
87
87
 
88
- const port = parseInt(process.env.CLAUDE_BRAIN_PORT || '3000', 10)
88
+ // BUG-006: Read port from --port arg (cross-platform) or env var fallback
89
+ const portIdx = process.argv.indexOf('--port')
90
+ const portArg = portIdx >= 0 ? process.argv[portIdx + 1] : undefined
91
+ const port = parseInt(portArg || process.env.CLAUDE_BRAIN_PORT || '3000', 10)
89
92
  const baseUrl = `http://localhost:${port}`
90
93
 
91
94
  let brainContext = ''
@@ -44,11 +44,14 @@ function writeSettings(settings: Record<string, any>): void {
44
44
  renameSync(tmpPath, CLAUDE_SETTINGS_PATH)
45
45
  }
46
46
 
47
- /** Build the hook command string, embedding the port so hooks work regardless of env */
47
+ /** Build the hook command string, embedding the port so hooks work regardless of env.
48
+ * BUG-006: settings.json syncs across platforms. We use --port arg instead of
49
+ * platform-specific env var syntax (VAR=val on Unix, set VAR=val on Windows). */
48
50
  function buildHookCommand(event: string, script: 'brain-hook' | 'context-hook' = 'brain-hook'): string {
49
51
  const scriptPath = script === 'context-hook' ? getContextHookScriptPath() : getHookScriptPath()
50
52
  const port = process.env.PORT || process.env.CLAUDE_BRAIN_PORT || '3000'
51
- return `CLAUDE_BRAIN_PORT=${port} bun "${scriptPath}" --event ${event} # ${HOOK_MARKER}`
53
+
54
+ return `bun "${scriptPath}" --event ${event} --port ${port} # ${HOOK_MARKER}`
52
55
  }
53
56
 
54
57
  /**
@@ -798,6 +798,37 @@ export class BrainRouter {
798
798
  const memory = getMemoryService()
799
799
  const topic = entities.topic || message
800
800
  const hasSpecificContent = this.hasDescriptiveContent(message)
801
+ const lower = message.toLowerCase()
802
+
803
+ // BUG-006 T6: Bulk delete — "delete all memories for project X"
804
+ if (lower.includes('delete all') || lower.includes('remove all') || lower.includes('clear all')) {
805
+ const bulkProject = entities.project || project || this.lastStoredProject
806
+ if (bulkProject) {
807
+ try {
808
+ const decisions = await memory.chroma.store.getDecisionsByProject(bulkProject)
809
+ if (decisions.length === 0) {
810
+ return {
811
+ action: 'none',
812
+ summary: 'No memories found',
813
+ content: `No memories found for project "${bulkProject}".`,
814
+ relevantItems: 0
815
+ }
816
+ }
817
+ for (const d of decisions) {
818
+ await memory.deleteDecision(d.id)
819
+ }
820
+ this.searchEngine.invalidateCache(bulkProject)
821
+ return {
822
+ action: 'stored',
823
+ summary: `Bulk deleted ${decisions.length} memories`,
824
+ content: `Deleted all ${decisions.length} memories for project "${bulkProject}".`,
825
+ relevantItems: 0
826
+ }
827
+ } catch {
828
+ // Bulk delete failed, fall through to single-item delete
829
+ }
830
+ }
831
+ }
801
832
 
802
833
  if (hasSpecificContent) {
803
834
  try {
@@ -967,8 +998,19 @@ export class BrainRouter {
967
998
  // C11: Add graph results as "Related Concepts"
968
999
  // When a project filter is active, cap graph results lower (0.25) so they don't
969
1000
  // outrank project-scoped ChromaDB results. Without a project filter, cap at 0.4.
1001
+ // BUG-006 T4: Also require keyword overlap with query to prevent noise
970
1002
  const graphScoreCap = searchProject ? 0.25 : 0.4
971
- const relevantGraphResults = graphResults.filter(g => g.score >= 0.6)
1003
+ const queryWordsForGraph = new Set(query.toLowerCase().split(/\s+/).filter(w => w.length > 2))
1004
+ const relevantGraphResults = graphResults.filter(g => {
1005
+ if (g.score < 0.6) return false
1006
+ // Require at least one query keyword to appear in graph content
1007
+ if (queryWordsForGraph.size > 0) {
1008
+ const contentLower = g.content.toLowerCase()
1009
+ const hasOverlap = [...queryWordsForGraph].some(w => contentLower.includes(w))
1010
+ if (!hasOverlap) return false
1011
+ }
1012
+ return true
1013
+ })
972
1014
  if (relevantGraphResults.length > 0) {
973
1015
  tiers.push({
974
1016
  label: 'Related Concepts',
@@ -1132,12 +1174,21 @@ export class BrainRouter {
1132
1174
  }
1133
1175
 
1134
1176
  // Always include graph exploration — cap scores when project filter is active
1177
+ // BUG-006 T4: Also require keyword overlap with query to prevent noise
1135
1178
  const explorationGraphCap = searchProject ? 0.25 : 0.5
1179
+ const explorationQueryWords = new Set(query.toLowerCase().split(/\s+/).filter(w => w.length > 2))
1136
1180
  const graphResults = await this.searchEngine.searchGraph(query, 10)
1137
- if (graphResults.length > 0) {
1181
+ const filteredGraphResults = graphResults.filter(g => {
1182
+ if (explorationQueryWords.size > 0) {
1183
+ const contentLower = g.content.toLowerCase()
1184
+ return [...explorationQueryWords].some(w => contentLower.includes(w))
1185
+ }
1186
+ return true
1187
+ })
1188
+ if (filteredGraphResults.length > 0) {
1138
1189
  tiers.push({
1139
1190
  label: 'Knowledge Graph',
1140
- results: graphResults.map(g => ({
1191
+ results: filteredGraphResults.map(g => ({
1141
1192
  content: g.content,
1142
1193
  score: Math.min(g.score, explorationGraphCap),
1143
1194
  source: 'Knowledge Graph',