claude-brain 0.14.2 → 0.14.3

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.14.2
1
+ 0.14.3
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-brain",
3
- "version": "0.14.2",
3
+ "version": "0.14.3",
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.14.2',
6
+ serverVersion: '0.14.3',
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.14.2'),
287
+ serverVersion: z.string().regex(/^\d+\.\d+\.\d+$/, 'Version must be semver format').default('0.14.3'),
288
288
 
289
289
  /** Logging level */
290
290
  logLevel: LogLevelSchema.default('info'),
@@ -111,6 +111,36 @@ export class PassiveClassifier {
111
111
 
112
112
  // Check for new file creation (Write tool)
113
113
  if (input.tool_name?.toLowerCase() === 'write') {
114
+ // Check file content for decision/correction language before defaulting to pattern
115
+ if (typeof content === 'string' && content.length > 50) {
116
+ const decisionInContent = this.detectDecisionLanguage(content)
117
+ if (decisionInContent) {
118
+ return {
119
+ type: 'decision',
120
+ confidence: 0.8,
121
+ content: decisionInContent,
122
+ project: this.extractProjectFromCwd(input.cwd),
123
+ technologies,
124
+ metadata: { filePath, role, action: 'create' },
125
+ source: 'hook-passive',
126
+ timestamp: new Date().toISOString(),
127
+ }
128
+ }
129
+ const correctionInContent = this.detectCorrectionLanguage(content)
130
+ if (correctionInContent) {
131
+ return {
132
+ type: 'correction',
133
+ confidence: 0.75,
134
+ content: correctionInContent,
135
+ project: this.extractProjectFromCwd(input.cwd),
136
+ technologies,
137
+ metadata: { filePath, role, action: 'create' },
138
+ source: 'hook-passive',
139
+ timestamp: new Date().toISOString(),
140
+ }
141
+ }
142
+ }
143
+
114
144
  return {
115
145
  type: 'pattern',
116
146
  confidence: 0.7,
@@ -161,10 +191,19 @@ export class PassiveClassifier {
161
191
  }
162
192
 
163
193
  private classifyBashCommand(input: HookInput): CapturedKnowledge | null {
164
- const command = (input.tool_input?.command || '') as string
165
- if (!command || command.length < 3) return null
194
+ const rawCommand = (input.tool_input?.command || '') as string
195
+ if (!rawCommand || rawCommand.length < 3) return null
196
+
197
+ // Split compound commands (cd "..." && bun add react) into sub-commands
198
+ const subCommands = rawCommand.split(/\s*(?:&&|\|\||;)\s*/).map(s => s.trim()).filter(Boolean)
199
+
200
+ // Find the first meaningful sub-command (skip cd, export, etc.)
201
+ const command = subCommands.find(sub => {
202
+ const firstWord = sub.split(/\s+/)[0]?.toLowerCase()
203
+ return !firstWord || !SKIP_COMMANDS.has(firstWord)
204
+ }) || rawCommand
166
205
 
167
- // Skip low-signal commands
206
+ // If all sub-commands are skip-worthy, bail
168
207
  const firstWord = command.trim().split(/\s+/)[0]?.toLowerCase()
169
208
  if (firstWord && SKIP_COMMANDS.has(firstWord)) return null
170
209
 
@@ -180,7 +219,7 @@ export class PassiveClassifier {
180
219
  content: `Installed package(s): ${packages}`,
181
220
  project: this.extractProjectFromCwd(input.cwd),
182
221
  technologies: this.extractTechFromPackages(packages),
183
- metadata: { command, action: 'install' },
222
+ metadata: { command: rawCommand, action: 'install' },
184
223
  source: 'hook-passive',
185
224
  timestamp: new Date().toISOString(),
186
225
  }
@@ -198,16 +237,17 @@ export class PassiveClassifier {
198
237
  content: `Git: ${command.trim().slice(0, 200)}`,
199
238
  project: this.extractProjectFromCwd(input.cwd),
200
239
  technologies: ['git'],
201
- metadata: { command, action: 'git' },
240
+ metadata: { command: rawCommand, action: 'git' },
202
241
  source: 'hook-passive',
203
242
  timestamp: new Date().toISOString(),
204
243
  }
205
244
  }
206
245
  }
207
246
 
208
- // Test/build runs
247
+ // Test/build runs — check all sub-commands since test may follow cd
248
+ const buildCommand = subCommands.find(sub => BUILD_PATTERNS.some(p => p.test(sub))) || command
209
249
  for (const pattern of BUILD_PATTERNS) {
210
- if (pattern.test(command)) {
250
+ if (pattern.test(buildCommand)) {
211
251
  const responseText = this.extractResponseText(input.tool_response)
212
252
  const failed = responseText?.toLowerCase().includes('fail') ||
213
253
  responseText?.toLowerCase().includes('error')
@@ -216,10 +256,10 @@ export class PassiveClassifier {
216
256
  return {
217
257
  type: 'correction',
218
258
  confidence: 0.75,
219
- content: `Build/test failure: ${command.trim().slice(0, 100)}`,
259
+ content: `Build/test failure: ${buildCommand.trim().slice(0, 100)}`,
220
260
  project: this.extractProjectFromCwd(input.cwd),
221
261
  technologies: [],
222
- metadata: { command, action: 'build', failed: true },
262
+ metadata: { command: rawCommand, action: 'build', failed: true },
223
263
  source: 'hook-passive',
224
264
  timestamp: new Date().toISOString(),
225
265
  }
@@ -228,10 +268,10 @@ export class PassiveClassifier {
228
268
  return {
229
269
  type: 'progress',
230
270
  confidence: 0.7,
231
- content: `Ran: ${command.trim().slice(0, 200)}`,
271
+ content: `Ran: ${buildCommand.trim().slice(0, 200)}`,
232
272
  project: this.extractProjectFromCwd(input.cwd),
233
273
  technologies: [],
234
- metadata: { command, action: 'build', failed: false },
274
+ metadata: { command: rawCommand, action: 'build', failed: false },
235
275
  source: 'hook-passive',
236
276
  timestamp: new Date().toISOString(),
237
277
  }
@@ -249,7 +289,7 @@ export class PassiveClassifier {
249
289
  content: correction,
250
290
  project: this.extractProjectFromCwd(input.cwd),
251
291
  technologies: [],
252
- metadata: { command, action: 'bash' },
292
+ metadata: { command: rawCommand, action: 'bash' },
253
293
  source: 'hook-passive',
254
294
  timestamp: new Date().toISOString(),
255
295
  }
@@ -281,7 +321,7 @@ export class PassiveClassifier {
281
321
 
282
322
  /** Extract file role from path segments */
283
323
  private extractRoleFromPath(filePath: string): string | undefined {
284
- const segments = filePath.toLowerCase().split('/')
324
+ const segments = filePath.toLowerCase().split(/[/\\]/)
285
325
  for (const segment of segments) {
286
326
  if (PATH_ROLE_MAP[segment]) return PATH_ROLE_MAP[segment]
287
327
  }
@@ -331,7 +371,8 @@ export class PassiveClassifier {
331
371
  /** Extract project name from cwd (last directory segment) */
332
372
  private extractProjectFromCwd(cwd: string): string | undefined {
333
373
  if (!cwd) return undefined
334
- const parts = cwd.split('/').filter(Boolean)
374
+ // Split on both / and \ for cross-platform support
375
+ const parts = cwd.split(/[/\\]/).filter(Boolean)
335
376
  const last = parts.pop()
336
377
  if (last && last.length > 1 && last.length < 50) {
337
378
  return last.replace(/\s+/g, '-').toLowerCase()
@@ -341,7 +382,7 @@ export class PassiveClassifier {
341
382
 
342
383
  /** Shorten a file path for display */
343
384
  private shortenPath(filePath: string): string {
344
- const parts = filePath.split('/')
385
+ const parts = filePath.split(/[/\\]/)
345
386
  if (parts.length <= 3) return filePath
346
387
  return `.../${parts.slice(-3).join('/')}`
347
388
  }