claude-brain 0.17.4 → 0.17.6

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.4
1
+ 0.17.6
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-brain",
3
- "version": "0.17.4",
3
+ "version": "0.17.6",
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.17.4',
6
+ serverVersion: '0.17.6',
7
7
  logLevel: 'info',
8
8
  logFilePath: './logs/claude-brain.log',
9
9
  dbPath: './data/memory.db',
@@ -284,7 +284,7 @@ export const ConfigSchema = z.object({
284
284
  serverName: z.string().default('claude-brain'),
285
285
 
286
286
  /** Server version in semver format */
287
- serverVersion: z.string().regex(/^\d+\.\d+\.\d+$/, 'Version must be semver format').default('0.17.4'),
287
+ serverVersion: z.string().regex(/^\d+\.\d+\.\d+$/, 'Version must be semver format').default('0.17.6'),
288
288
 
289
289
  /** Logging level */
290
290
  logLevel: LogLevelSchema.default('info'),
@@ -1,16 +1,23 @@
1
1
  #!/usr/bin/env bun
2
2
  /**
3
- * Phase 26: Context Injection Hook
3
+ * Phase 26+: Context Injection Hook
4
4
  * Runs on UserPromptSubmit and SessionStart to inject relevant memories
5
5
  * into Claude's context via additionalContext.
6
6
  *
7
+ * SessionStart also injects the static Claude Code Mastery guidelines
8
+ * (claude-code-mastery.md) to optimize Claude's behavior every session.
9
+ *
7
10
  * CRITICAL CONSTRAINTS:
8
11
  * - Must complete in <3s (blocks user prompt processing)
9
- * - No heavy imports — just fetch + JSON parse
12
+ * - No heavy imports — just fetch + JSON parse + fs.readFileSync
10
13
  * - All errors silently caught with process.exit(0)
11
14
  * - Outputs JSON to stdout: { hookSpecificOutput: { additionalContext: "..." } }
12
15
  */
13
16
 
17
+ import { readFileSync, existsSync } from 'node:fs'
18
+ import { join, dirname } from 'node:path'
19
+ import { fileURLToPath } from 'node:url'
20
+
14
21
  interface HookStdin {
15
22
  session_id: string
16
23
  hook_event_name: string
@@ -19,6 +26,30 @@ interface HookStdin {
19
26
  source?: string
20
27
  }
21
28
 
29
+ /** Cache the mastery doc in memory after first read */
30
+ let masteryCache: string | null = null
31
+
32
+ /** Read the static Claude Code Mastery guidelines from disk (once, then cached) */
33
+ function loadMasteryDoc(): string {
34
+ if (masteryCache !== null) return masteryCache
35
+
36
+ // Resolve relative to this script's location (works both in src/ and installed ~/.claude-brain/hooks/)
37
+ const scriptDir = (import.meta as any).dir ?? dirname(fileURLToPath(import.meta.url))
38
+ const masteryPath = join(scriptDir, 'claude-code-mastery.md')
39
+
40
+ try {
41
+ if (existsSync(masteryPath)) {
42
+ masteryCache = readFileSync(masteryPath, 'utf-8').trim()
43
+ } else {
44
+ masteryCache = ''
45
+ }
46
+ } catch {
47
+ masteryCache = ''
48
+ }
49
+
50
+ return masteryCache
51
+ }
52
+
22
53
  async function main(): Promise<void> {
23
54
  // Parse --event arg
24
55
  const eventIdx = process.argv.indexOf('--event')
@@ -57,7 +88,7 @@ async function main(): Promise<void> {
57
88
  const port = parseInt(process.env.CLAUDE_BRAIN_PORT || '3000', 10)
58
89
  const baseUrl = `http://localhost:${port}`
59
90
 
60
- let context = ''
91
+ let brainContext = ''
61
92
 
62
93
  try {
63
94
  if (event === 'UserPromptSubmit' && input.prompt) {
@@ -74,7 +105,7 @@ async function main(): Promise<void> {
74
105
 
75
106
  if (res.ok) {
76
107
  const data = await res.json() as { success: boolean; context: string }
77
- context = data.context || ''
108
+ brainContext = data.context || ''
78
109
  }
79
110
  } else if (event === 'SessionStart') {
80
111
  // Load broader project context on session start
@@ -87,21 +118,39 @@ async function main(): Promise<void> {
87
118
 
88
119
  if (res.ok) {
89
120
  const data = await res.json() as { success: boolean; context: string }
90
- context = data.context || ''
121
+ brainContext = data.context || ''
91
122
  }
92
123
  }
93
124
  } catch {
94
- // Server unreachable or timeout — exit silently
95
- process.exit(0)
96
- return
125
+ // Server unreachable or timeout — continue with static content only for SessionStart
126
+ if (event !== 'SessionStart') {
127
+ process.exit(0)
128
+ return
129
+ }
130
+ }
131
+
132
+ // Build final injection content
133
+ const parts: string[] = []
134
+
135
+ // SessionStart: prepend static Claude Code Mastery guidelines
136
+ if (event === 'SessionStart') {
137
+ const mastery = loadMasteryDoc()
138
+ if (mastery) {
139
+ parts.push(`[Claude Code Mastery]\n${mastery}`)
140
+ }
141
+ }
142
+
143
+ // Append dynamic brain memories (both events)
144
+ if (brainContext.trim()) {
145
+ parts.push(`[Brain Memory]\n${brainContext}`)
97
146
  }
98
147
 
99
- // Only output if we have context to inject
100
- if (context.trim()) {
148
+ // Output combined context
149
+ if (parts.length > 0) {
101
150
  const output = {
102
151
  hookSpecificOutput: {
103
152
  hookEventName: event,
104
- additionalContext: `[Brain Memory]\n${context}`,
153
+ additionalContext: parts.join('\n\n'),
105
154
  },
106
155
  }
107
156
  process.stdout.write(JSON.stringify(output))
@@ -210,6 +210,7 @@ const HOOK_FILES = [
210
210
  'queue.ts',
211
211
  'types.ts',
212
212
  'passive-classifier.ts',
213
+ 'claude-code-mastery.md',
213
214
  ]
214
215
 
215
216
  /** Copy the hook script and all its dependencies to the install location */
@@ -36,13 +36,35 @@ export class TransformersEmbeddingProvider implements EmbeddingProvider {
36
36
 
37
37
  private async getPipeline() {
38
38
  if (!this.pipeline) {
39
- const { pipeline } = await import('@xenova/transformers')
40
- this.pipeline = await pipeline('feature-extraction', this.modelName)
39
+ try {
40
+ const { pipeline } = await import('@xenova/transformers')
41
+ this.pipeline = await pipeline('feature-extraction', this.modelName)
42
+ } catch (error) {
43
+ this.logger.warn({ error }, 'Model load failed, clearing cache and retrying')
44
+ await this.clearModelCache()
45
+ const { pipeline } = await import('@xenova/transformers')
46
+ this.pipeline = await pipeline('feature-extraction', this.modelName)
47
+ }
41
48
  this.logger.info({ model: this.modelName }, 'Transformers pipeline initialized')
42
49
  }
43
50
  return this.pipeline
44
51
  }
45
52
 
53
+ private async clearModelCache(): Promise<void> {
54
+ const { rm } = await import('fs/promises')
55
+ const { join } = await import('path')
56
+ const cacheDir = join(
57
+ require.resolve('@xenova/transformers'),
58
+ '..', '.cache', ...this.modelName.split('/')
59
+ )
60
+ try {
61
+ await rm(cacheDir, { recursive: true, force: true })
62
+ this.logger.info({ cacheDir }, 'Cleared corrupted model cache')
63
+ } catch {
64
+ // Cache dir may not exist, continue
65
+ }
66
+ }
67
+
46
68
  async generate(text: string): Promise<number[]> {
47
69
  const pipe = await this.getPipeline()
48
70
 
@@ -174,7 +174,7 @@ const UPDATE_PHRASES = [
174
174
 
175
175
  // Delete/forget memory
176
176
  const DELETE_PHRASES = [
177
- 'forget that', 'forget about', 'delete that', 'delete the',
177
+ 'forget that', 'forget about', 'delete that', 'delete the memory', 'delete the decision',
178
178
  'remove that memory', 'remove that decision', 'that was wrong',
179
179
  'discard that', 'erase that', 'undo that decision',
180
180
  'remove the memory', 'clear that', 'drop that'
@@ -400,6 +400,7 @@ export class IntentClassifier {
400
400
  }
401
401
 
402
402
  private isDeleteMemory(lower: string): boolean {
403
+ if (lower.length > 150) return false
403
404
  return DELETE_PHRASES.some(p => lower.includes(p))
404
405
  }
405
406