claude-brain 0.27.1 → 0.27.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 CHANGED
@@ -1 +1 @@
1
- 0.27.1
1
+ 0.27.2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-brain",
3
- "version": "0.27.1",
3
+ "version": "0.27.2",
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,10 +52,10 @@ export async function main(): Promise<void> {
52
52
  return
53
53
  }
54
54
 
55
- // BUG-006: Read port from --port arg (cross-platform) or env var fallback
55
+ // BUG-006: Read port from --port arg (cross-platform). Env var fallback removed — breaks Windows cmd.exe.
56
56
  const portIdx = process.argv.indexOf('--port')
57
57
  const portArg = portIdx >= 0 ? process.argv[portIdx + 1] : undefined
58
- const port = parseInt(portArg || process.env.CLAUDE_BRAIN_PORT || '3000', 10)
58
+ const port = parseInt(portArg || '3000', 10)
59
59
 
60
60
  // For Stop events: trigger session-end summarization regardless of classification
61
61
  if (input.hook_event_name === 'Stop' && input.session_id) {
@@ -85,10 +85,10 @@ async function main(): Promise<void> {
85
85
  return
86
86
  }
87
87
 
88
- // BUG-006: Read port from --port arg (cross-platform) or env var fallback
88
+ // BUG-006: Read port from --port arg (cross-platform). Env var fallback removed — breaks Windows cmd.exe.
89
89
  const portIdx = process.argv.indexOf('--port')
90
90
  const portArg = portIdx >= 0 ? process.argv[portIdx + 1] : undefined
91
- const port = parseInt(portArg || process.env.CLAUDE_BRAIN_PORT || '3000', 10)
91
+ const port = parseInt(portArg || '3000', 10)
92
92
  const baseUrl = `http://localhost:${port}`
93
93
 
94
94
  let brainContext = ''
@@ -51,10 +51,10 @@ async function main(): Promise<void> {
51
51
  const project = detectProject(input.cwd)
52
52
  if (!project) { process.exit(0); return }
53
53
 
54
- // Read port from --port arg or env
54
+ // BUG-006: Read port from --port arg (cross-platform). Env var fallback removed — breaks Windows cmd.exe.
55
55
  const portIdx = process.argv.indexOf('--port')
56
56
  const portArg = portIdx >= 0 ? process.argv[portIdx + 1] : undefined
57
- const port = parseInt(portArg || process.env.CLAUDE_BRAIN_PORT || '3000', 10)
57
+ const port = parseInt(portArg || '3000', 10)
58
58
 
59
59
  try {
60
60
  const params = new URLSearchParams({
@@ -34,6 +34,9 @@ import { handleInitProject } from './tools/init-project'
34
34
  // Phase 16 Unified Brain Tool
35
35
  import { handleBrain } from './tools/brain'
36
36
 
37
+ // Code Intelligence
38
+ import { handleSearchCode } from './tools/search-code'
39
+
37
40
  /**
38
41
  * Handle tools/call request
39
42
  * Validates that tool exists and routes to the appropriate handler
@@ -122,6 +125,10 @@ export async function handleCallTool(
122
125
  case 'brain':
123
126
  return await handleBrain(args, logger)
124
127
 
128
+ // Code Intelligence
129
+ case 'search_code':
130
+ return await handleSearchCode(args, logger)
131
+
125
132
  default:
126
133
  // This should never happen if validateToolExists works correctly
127
134
  throw new McpError(
@@ -25,3 +25,6 @@ export { handleCreateProject } from './create-project'
25
25
  export { handleInitProject } from './init-project'
26
26
 
27
27
  // Phase 19: Redundant tools absorbed into brain(). Handler files removed.
28
+
29
+ // Code Intelligence
30
+ export { handleSearchCode } from './search-code'
@@ -141,6 +141,18 @@ export const BrainSchema = z.object({
141
141
  action: z.enum(['auto', 'store', 'recall', 'update', 'delete']).optional()
142
142
  })
143
143
 
144
+ // ============================================================================
145
+ // Code Intelligence
146
+ // ============================================================================
147
+
148
+ export const SearchCodeSchema = z.object({
149
+ query: z.string().min(1, 'query is required'),
150
+ project: z.string().min(1, 'project is required'),
151
+ type: z.enum(['symbols', 'files', 'dependencies']).optional().default('symbols'),
152
+ file_path: z.string().optional(),
153
+ limit: z.number().int().min(1).max(100).optional().default(20)
154
+ })
155
+
144
156
  // ============================================================================
145
157
  // Type exports for validated inputs
146
158
  // ============================================================================
@@ -162,3 +174,6 @@ export type ValidatedGetCorrections = z.infer<typeof GetCorrectionsSchema>
162
174
 
163
175
  // Phase 16
164
176
  export type ValidatedBrain = z.infer<typeof BrainSchema>
177
+
178
+ // Code Intelligence
179
+ export type ValidatedSearchCode = z.infer<typeof SearchCodeSchema>
@@ -0,0 +1,122 @@
1
+ /**
2
+ * Search Code Handler
3
+ * Exposes code intelligence (symbol/file/dependency search) as a direct MCP tool
4
+ */
5
+
6
+ import type { Logger } from 'pino'
7
+ import type { ToolResponse } from '@/tools/types'
8
+ import { getCodeQuery, isServicesInitialized } from '@/server/services'
9
+ import { ToolValidator } from '@/server/utils/validators'
10
+ import { ResponseFormatter } from '@/server/utils/response-formatter'
11
+ import { ErrorHandler } from '@/server/utils/error-handler'
12
+ import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js'
13
+ import { SearchCodeSchema } from './schemas'
14
+
15
+ export async function handleSearchCode(
16
+ args: unknown,
17
+ logger: Logger
18
+ ): Promise<ToolResponse> {
19
+ try {
20
+ const input = ToolValidator.validate(args, SearchCodeSchema)
21
+ const { query, project, type, file_path, limit } = input
22
+
23
+ logger.debug({ query, project, type, limit }, 'Searching code index')
24
+
25
+ if (!isServicesInitialized()) {
26
+ throw new McpError(
27
+ ErrorCode.InternalError,
28
+ 'Services not initialized. The server may still be starting up.'
29
+ )
30
+ }
31
+
32
+ const codeQuery = getCodeQuery()
33
+ if (!codeQuery) {
34
+ return ResponseFormatter.text(
35
+ 'Code intelligence is not available. Run `claude-brain reindex` to index your project first.'
36
+ )
37
+ }
38
+
39
+ if (type === 'files') {
40
+ const results = codeQuery.findFiles(query, project)
41
+ if (results.length === 0) {
42
+ return ResponseFormatter.text(
43
+ `No files found matching "${query}" in project "${project}". Try \`claude-brain reindex\` if the project hasn't been indexed.`
44
+ )
45
+ }
46
+
47
+ const lines = results.map(r => {
48
+ const parts = [r.filePath]
49
+ if (r.language) parts.push(`(${r.language})`)
50
+ if (r.summary) parts.push(`— ${r.summary}`)
51
+ parts.push(`[${r.symbolCount} symbols, ${r.lineCount} lines]`)
52
+ return parts.join(' ')
53
+ })
54
+
55
+ return ResponseFormatter.text(
56
+ `Found ${results.length} file${results.length === 1 ? '' : 's'} matching "${query}":\n\n${lines.join('\n')}`
57
+ )
58
+ }
59
+
60
+ if (type === 'dependencies') {
61
+ const targetPath = file_path || query
62
+ const deps = codeQuery.getDependencies(targetPath, project)
63
+
64
+ const parts: string[] = [`Dependencies for ${deps.file}:\n`]
65
+
66
+ if (deps.imports.length > 0) {
67
+ parts.push('**Imports:**')
68
+ for (const imp of deps.imports) {
69
+ const names = imp.names.length > 0 ? ` { ${imp.names.join(', ')} }` : ''
70
+ parts.push(` → ${imp.file}${names}`)
71
+ }
72
+ } else {
73
+ parts.push('**Imports:** none')
74
+ }
75
+
76
+ parts.push('')
77
+
78
+ if (deps.importedBy.length > 0) {
79
+ parts.push('**Imported by:**')
80
+ for (const dep of deps.importedBy) {
81
+ const names = dep.names.length > 0 ? ` { ${dep.names.join(', ')} }` : ''
82
+ parts.push(` ← ${dep.file}${names}`)
83
+ }
84
+ } else {
85
+ parts.push('**Imported by:** none')
86
+ }
87
+
88
+ return ResponseFormatter.text(parts.join('\n'))
89
+ }
90
+
91
+ // Default: symbols
92
+ const results = codeQuery.findSymbols(query, project, limit)
93
+ if (results.length === 0) {
94
+ return ResponseFormatter.text(
95
+ `No symbols found matching "${query}" in project "${project}". Try \`claude-brain reindex\` if the project hasn't been indexed.`
96
+ )
97
+ }
98
+
99
+ const lines = results.map(r => {
100
+ const sig = r.signature ? ` ${r.signature}` : ''
101
+ const lineRange = r.lineEnd ? `${r.lineStart}-${r.lineEnd}` : `${r.lineStart}`
102
+ const conf = r.confidence < 1.0 ? ` (${(r.confidence * 100).toFixed(0)}%)` : ''
103
+ return `${r.symbol} (${r.type})${sig} → ${r.filePath}:${lineRange}${conf}`
104
+ })
105
+
106
+ return ResponseFormatter.text(
107
+ `Found ${results.length} symbol${results.length === 1 ? '' : 's'} matching "${query}":\n\n${lines.join('\n')}`
108
+ )
109
+
110
+ } catch (error) {
111
+ ErrorHandler.logError(logger, error, { tool: 'search_code', args })
112
+
113
+ if (error instanceof McpError) {
114
+ throw error
115
+ }
116
+
117
+ throw new McpError(
118
+ ErrorCode.InternalError,
119
+ `Failed to search code: ${error instanceof Error ? error.message : 'Unknown error'}`
120
+ )
121
+ }
122
+ }
@@ -610,6 +610,50 @@ export const TOOLS = {
610
610
  },
611
611
  required: ['message']
612
612
  }
613
+ },
614
+
615
+ /**
616
+ * SEARCH_CODE
617
+ * Search indexed code symbols, files, and dependencies
618
+ *
619
+ * Use this when:
620
+ * - Looking for a function, class, or type definition
621
+ * - Need to find which file contains a symbol
622
+ * - Want to see imports/dependencies of a file
623
+ * - Faster than grep for known symbol names
624
+ */
625
+ SEARCH_CODE: {
626
+ name: 'search_code',
627
+ description: 'Search indexed code for symbols (functions, classes, types), files, or dependencies. Faster than grep when the project is indexed. Run `claude-brain reindex` first if no results.',
628
+ inputSchema: {
629
+ type: 'object' as const,
630
+ properties: {
631
+ query: {
632
+ type: 'string',
633
+ description: 'Symbol name, file name, or search term'
634
+ },
635
+ project: {
636
+ type: 'string',
637
+ description: 'Project name (usually the directory name, e.g. "claude-brain")'
638
+ },
639
+ type: {
640
+ type: 'string',
641
+ enum: ['symbols', 'files', 'dependencies'],
642
+ description: 'What to search for: symbols (functions/classes/types), files, or dependencies of a file',
643
+ default: 'symbols'
644
+ },
645
+ file_path: {
646
+ type: 'string',
647
+ description: 'File path (required when type is "dependencies")'
648
+ },
649
+ limit: {
650
+ type: 'number',
651
+ description: 'Maximum results to return',
652
+ default: 20
653
+ }
654
+ },
655
+ required: ['query', 'project']
656
+ }
613
657
  }
614
658
  } as const satisfies Record<string, ToolDefinition>
615
659