claude-brain 0.25.0 → 0.25.2
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/scripts/postinstall.mjs +19 -2
- package/src/cli/commands/reindex.ts +14 -1
- package/src/code-intelligence/indexer.ts +39 -2
- package/src/hooks/context-hook.ts +2 -2
- package/src/hooks/git-capture.ts +1 -1
- package/src/hooks/installer.ts +4 -1
- package/src/hooks/interceptor-hook.ts +2 -1
- package/src/hooks/passive-classifier.ts +1 -1
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.25.
|
|
1
|
+
0.25.2
|
package/package.json
CHANGED
package/scripts/postinstall.mjs
CHANGED
|
@@ -165,6 +165,7 @@ logLevel: warn
|
|
|
165
165
|
const HOOK_FILES = [
|
|
166
166
|
'brain-hook.ts',
|
|
167
167
|
'context-hook.ts',
|
|
168
|
+
'interceptor-hook.ts',
|
|
168
169
|
'capture.ts',
|
|
169
170
|
'queue.ts',
|
|
170
171
|
'types.ts',
|
|
@@ -215,7 +216,8 @@ function installHooks() {
|
|
|
215
216
|
hasOurHooks(settings.hooks.PostToolUse) &&
|
|
216
217
|
hasOurHooks(settings.hooks.Stop) &&
|
|
217
218
|
hasOurHooks(settings.hooks.UserPromptSubmit) &&
|
|
218
|
-
hasOurHooks(settings.hooks.SessionStart)
|
|
219
|
+
hasOurHooks(settings.hooks.SessionStart) &&
|
|
220
|
+
hasOurHooks(settings.hooks.PreToolUse)) {
|
|
219
221
|
log('Hooks already installed')
|
|
220
222
|
return true
|
|
221
223
|
}
|
|
@@ -223,8 +225,10 @@ function installHooks() {
|
|
|
223
225
|
// Build hook command
|
|
224
226
|
const brainScriptPath = join(HOME, 'hooks', 'brain-hook.ts')
|
|
225
227
|
const contextScriptPath = join(HOME, 'hooks', 'context-hook.ts')
|
|
228
|
+
const interceptorScriptPath = join(HOME, 'hooks', 'interceptor-hook.ts')
|
|
229
|
+
const port = process.env.CLAUDE_BRAIN_PORT || process.env.PORT || '3000'
|
|
226
230
|
function buildCmd(event, scriptPath) {
|
|
227
|
-
return `bun "${scriptPath}" --event ${event} # ${HOOK_MARKER}`
|
|
231
|
+
return `bun "${scriptPath}" --event ${event} --port ${port} # ${HOOK_MARKER}`
|
|
228
232
|
}
|
|
229
233
|
|
|
230
234
|
if (!settings.hooks) settings.hooks = {}
|
|
@@ -265,6 +269,19 @@ function installHooks() {
|
|
|
265
269
|
})
|
|
266
270
|
}
|
|
267
271
|
|
|
272
|
+
// PreToolUse — code intelligence interceptor for Glob and Grep
|
|
273
|
+
if (!hasOurHooks(settings.hooks.PreToolUse)) {
|
|
274
|
+
if (!settings.hooks.PreToolUse) settings.hooks.PreToolUse = []
|
|
275
|
+
settings.hooks.PreToolUse.push({
|
|
276
|
+
matcher: 'Glob',
|
|
277
|
+
hooks: [{ type: 'command', command: buildCmd('PreToolUse', interceptorScriptPath) }],
|
|
278
|
+
})
|
|
279
|
+
settings.hooks.PreToolUse.push({
|
|
280
|
+
matcher: 'Grep',
|
|
281
|
+
hooks: [{ type: 'command', command: buildCmd('PreToolUse', interceptorScriptPath) }],
|
|
282
|
+
})
|
|
283
|
+
}
|
|
284
|
+
|
|
268
285
|
// Write atomically
|
|
269
286
|
if (!existsSync(CLAUDE_DIR)) {
|
|
270
287
|
mkdirSync(CLAUDE_DIR, { recursive: true })
|
|
@@ -83,6 +83,7 @@ export async function runReindex() {
|
|
|
83
83
|
|
|
84
84
|
const parser = new CodeParser()
|
|
85
85
|
await parser.initialize()
|
|
86
|
+
console.log(dimText(' Parser initialized'))
|
|
86
87
|
|
|
87
88
|
const indexer = new CodeIndexer(codeDb, parser, logger)
|
|
88
89
|
await indexer.initialize()
|
|
@@ -94,7 +95,19 @@ export async function runReindex() {
|
|
|
94
95
|
}
|
|
95
96
|
|
|
96
97
|
const result = await indexer.indexProject(projectPath, projectName)
|
|
97
|
-
|
|
98
|
+
const files = result.filesIndexed ?? 0
|
|
99
|
+
const symbols = result.symbolsFound ?? 0
|
|
100
|
+
const skipped = result.filesSkipped ?? 0
|
|
101
|
+
|
|
102
|
+
if (files === 0 && skipped === 0) {
|
|
103
|
+
console.log(warningText(` No supported files found in project.`))
|
|
104
|
+
console.log(dimText(` Supported: .ts .tsx .js .jsx .mjs .cjs .py .go .rs .vue .html .css .json .yaml .yml`))
|
|
105
|
+
if (result.errors && result.errors.length > 0) {
|
|
106
|
+
console.log(warningText(` Errors: ${result.errors.slice(0, 3).join('; ')}`))
|
|
107
|
+
}
|
|
108
|
+
} else {
|
|
109
|
+
console.log(successText(` Reindex complete: ${files} files, ${symbols} symbols` + (skipped > 0 ? ` (${skipped} unchanged)` : '')))
|
|
110
|
+
}
|
|
98
111
|
codeDb.close()
|
|
99
112
|
} catch (error) {
|
|
100
113
|
console.log(box(
|
|
@@ -190,6 +190,10 @@ export class CodeIndexer {
|
|
|
190
190
|
private async collectFiles(dirPath: string): Promise<string[]> {
|
|
191
191
|
const files: string[] = []
|
|
192
192
|
await this.walkDirectory(dirPath, files)
|
|
193
|
+
if (files.length === 0) {
|
|
194
|
+
this.logger.warn({ dirPath }, 'collectFiles returned 0 files — trying fallback without withFileTypes')
|
|
195
|
+
await this.walkDirectoryFallback(dirPath, files)
|
|
196
|
+
}
|
|
193
197
|
return files
|
|
194
198
|
}
|
|
195
199
|
|
|
@@ -197,8 +201,9 @@ export class CodeIndexer {
|
|
|
197
201
|
let entries
|
|
198
202
|
try {
|
|
199
203
|
entries = await readdir(dirPath, { withFileTypes: true })
|
|
200
|
-
} catch {
|
|
201
|
-
|
|
204
|
+
} catch (error) {
|
|
205
|
+
this.logger.debug({ dirPath, error: error instanceof Error ? error.message : String(error) }, 'Cannot read directory')
|
|
206
|
+
return
|
|
202
207
|
}
|
|
203
208
|
|
|
204
209
|
for (const entry of entries) {
|
|
@@ -215,6 +220,38 @@ export class CodeIndexer {
|
|
|
215
220
|
}
|
|
216
221
|
}
|
|
217
222
|
|
|
223
|
+
/** Fallback walker that uses readdir (names only) + stat — works on all platforms */
|
|
224
|
+
private async walkDirectoryFallback(dirPath: string, files: string[]): Promise<void> {
|
|
225
|
+
let names: string[]
|
|
226
|
+
try {
|
|
227
|
+
names = await readdir(dirPath)
|
|
228
|
+
} catch (error) {
|
|
229
|
+
this.logger.debug({ dirPath, error: error instanceof Error ? error.message : String(error) }, 'Fallback: cannot read directory')
|
|
230
|
+
return
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
for (const name of names) {
|
|
234
|
+
const fullPath = join(dirPath, name)
|
|
235
|
+
let fileStat
|
|
236
|
+
try {
|
|
237
|
+
fileStat = await stat(fullPath)
|
|
238
|
+
} catch {
|
|
239
|
+
continue
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (fileStat.isDirectory()) {
|
|
243
|
+
if (!SKIP_DIRS.has(name)) {
|
|
244
|
+
await this.walkDirectoryFallback(fullPath, files)
|
|
245
|
+
}
|
|
246
|
+
} else if (fileStat.isFile()) {
|
|
247
|
+
if (SKIP_FILES.has(name)) continue
|
|
248
|
+
const ext = extname(name).toLowerCase()
|
|
249
|
+
if (!SUPPORTED_EXTENSIONS.has(ext)) continue
|
|
250
|
+
files.push(fullPath)
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
218
255
|
private async indexSingleFile(
|
|
219
256
|
filePath: string,
|
|
220
257
|
projectPath: string,
|
|
@@ -109,7 +109,7 @@ async function main(): Promise<void> {
|
|
|
109
109
|
}).catch(() => null)
|
|
110
110
|
|
|
111
111
|
// Phase 29: Detect file paths in the user's prompt and fetch linked memories
|
|
112
|
-
const projectName = input.cwd ? input.cwd.split(
|
|
112
|
+
const projectName = input.cwd ? input.cwd.split(/[/\\]/).filter(Boolean).pop() : ''
|
|
113
113
|
const filePaths = extractFilePathsFromPrompt(input.prompt)
|
|
114
114
|
const fileMemoryPromises = filePaths.slice(0, 3).map(fp =>
|
|
115
115
|
fetch(`${baseUrl}/api/memory/for-file?file=${encodeURIComponent(fp)}&project=${encodeURIComponent(projectName || '')}`, {
|
|
@@ -135,7 +135,7 @@ async function main(): Promise<void> {
|
|
|
135
135
|
if (input.cwd) params.set('cwd', input.cwd)
|
|
136
136
|
|
|
137
137
|
// Extract project name from cwd for code map
|
|
138
|
-
const projectName = input.cwd ? input.cwd.split(
|
|
138
|
+
const projectName = input.cwd ? input.cwd.split(/[/\\]/).filter(Boolean).pop() : undefined
|
|
139
139
|
|
|
140
140
|
// Fetch brain context AND code map in parallel
|
|
141
141
|
const [contextRes, codeMapRes] = await Promise.all([
|
package/src/hooks/git-capture.ts
CHANGED
|
@@ -70,7 +70,7 @@ function resolveProjectName(raw: string): string {
|
|
|
70
70
|
let name = raw.trim()
|
|
71
71
|
// Strip npm scoped prefix: @scope/name → name
|
|
72
72
|
if (name.startsWith('@') && name.includes('/')) {
|
|
73
|
-
name = name.split(
|
|
73
|
+
name = name.split(/[/\\]/).pop() || name
|
|
74
74
|
}
|
|
75
75
|
return name || raw
|
|
76
76
|
}
|
package/src/hooks/installer.ts
CHANGED
|
@@ -230,7 +230,10 @@ export function isHooksInstalled(): boolean {
|
|
|
230
230
|
const hasUserPromptSubmit = Array.isArray(settings.hooks.UserPromptSubmit) &&
|
|
231
231
|
settings.hooks.UserPromptSubmit.some((entry: any) => isOurHookEntry(entry))
|
|
232
232
|
|
|
233
|
-
|
|
233
|
+
const hasPreToolUse = Array.isArray(settings.hooks.PreToolUse) &&
|
|
234
|
+
settings.hooks.PreToolUse.some((entry: any) => isOurHookEntry(entry))
|
|
235
|
+
|
|
236
|
+
return hasPostToolUse || hasStop || hasUserPromptSubmit || hasPreToolUse
|
|
234
237
|
}
|
|
235
238
|
|
|
236
239
|
/** Check if a hook entry belongs to us (by marker in command) */
|
|
@@ -133,7 +133,8 @@ function isComplexRegex(pattern: string): boolean {
|
|
|
133
133
|
|
|
134
134
|
function detectProject(cwd?: string): string | null {
|
|
135
135
|
if (!cwd) return null
|
|
136
|
-
|
|
136
|
+
// Split by both / and \ for cross-platform support (Windows uses backslashes)
|
|
137
|
+
const segments = cwd.split(/[/\\]/).filter(Boolean)
|
|
137
138
|
return segments[segments.length - 1] || null
|
|
138
139
|
}
|
|
139
140
|
|
|
@@ -305,7 +305,7 @@ export class PassiveClassifier {
|
|
|
305
305
|
}
|
|
306
306
|
|
|
307
307
|
// Check for Dockerfile without extension
|
|
308
|
-
const basename = filePath.split(
|
|
308
|
+
const basename = filePath.split(/[/\\]/).pop()?.toLowerCase() || ''
|
|
309
309
|
if (basename === 'dockerfile' || basename.startsWith('dockerfile.')) {
|
|
310
310
|
techs.push('docker')
|
|
311
311
|
}
|