claude-brain 0.17.14 → 0.22.0

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.
Files changed (44) hide show
  1. package/VERSION +1 -1
  2. package/package.json +3 -1
  3. package/scripts/postinstall.mjs +80 -104
  4. package/src/cli/auto-setup.ts +1 -9
  5. package/src/cli/bin.ts +23 -2
  6. package/src/cli/commands/export.ts +130 -0
  7. package/src/cli/commands/reindex.ts +107 -0
  8. package/src/cli/commands/serve.ts +54 -0
  9. package/src/cli/commands/status.ts +158 -0
  10. package/src/code-intelligence/indexer.ts +315 -0
  11. package/src/code-intelligence/linker.ts +178 -0
  12. package/src/code-intelligence/parser.ts +484 -0
  13. package/src/code-intelligence/query.ts +291 -0
  14. package/src/code-intelligence/schema.ts +83 -0
  15. package/src/code-intelligence/types.ts +95 -0
  16. package/src/config/defaults.ts +3 -3
  17. package/src/config/loader.ts +6 -0
  18. package/src/config/schema.ts +28 -2
  19. package/src/health/index.ts +5 -2
  20. package/src/hooks/context-hook.ts +65 -9
  21. package/src/intelligence/cross-project/index.ts +1 -7
  22. package/src/intelligence/prediction/index.ts +1 -7
  23. package/src/intelligence/reasoning/index.ts +1 -7
  24. package/src/memory/compression.ts +105 -0
  25. package/src/memory/fts5-search.ts +456 -0
  26. package/src/memory/index.ts +342 -38
  27. package/src/memory/migrations/add-fts5.ts +98 -0
  28. package/src/memory/pruning.ts +60 -0
  29. package/src/routing/intent-classifier.ts +58 -1
  30. package/src/routing/response-filter.ts +128 -0
  31. package/src/routing/router.ts +403 -51
  32. package/src/server/http-api.ts +319 -1
  33. package/src/server/providers/resources.ts +1 -42
  34. package/src/server/services.ts +113 -12
  35. package/src/server/web-viewer.ts +1115 -0
  36. package/src/setup/index.ts +12 -22
  37. package/src/tools/schemas.ts +1 -1
  38. package/src/intelligence/cross-project/affinity.ts +0 -159
  39. package/src/intelligence/cross-project/transfer.ts +0 -201
  40. package/src/intelligence/prediction/context-anticipator.ts +0 -198
  41. package/src/intelligence/prediction/decision-predictor.ts +0 -184
  42. package/src/intelligence/reasoning/counterfactual.ts +0 -248
  43. package/src/intelligence/reasoning/synthesizer.ts +0 -167
  44. package/src/setup/wizard.ts +0 -459
package/VERSION CHANGED
@@ -1 +1 @@
1
- 0.17.14
1
+ 0.22.0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-brain",
3
- "version": "0.17.14",
3
+ "version": "0.22.0",
4
4
  "description": "Local development assistant bridging Obsidian vaults with Claude Code via MCP",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -77,6 +77,8 @@
77
77
  "pino": "^10.1.1",
78
78
  "pino-pretty": "^13.1.3",
79
79
  "prompts": "2.4.2",
80
+ "tree-sitter-wasms": "0.1.13",
81
+ "web-tree-sitter": "0.24.7",
80
82
  "zod": "^4.3.5"
81
83
  }
82
84
  }
@@ -5,6 +5,15 @@
5
5
  * Runs automatically after `npm install -g claude-brain` or `bun install -g claude-brain`.
6
6
  * Pure Node.js — no TypeScript, no path aliases, no Bun APIs.
7
7
  * Always exits 0 so it never blocks the install.
8
+ *
9
+ * Phase 30: Zero-config — does EVERYTHING automatically:
10
+ * 1. Create data directory: ~/.claude-brain/data/
11
+ * 2. Create default config if none exists: ~/.claude-brain/config.yml
12
+ * 3. Copy hook files to ~/.claude-brain/hooks/
13
+ * 4. Register hooks in ~/.claude/settings.json (if Claude Code installed)
14
+ * 5. Print success message with next steps
15
+ *
16
+ * No interactive prompts. No setup wizard. Sensible defaults.
8
17
  */
9
18
 
10
19
  import { mkdirSync, writeFileSync, readFileSync, existsSync, renameSync } from 'node:fs'
@@ -56,19 +65,13 @@ function shouldSkip() {
56
65
  return null
57
66
  }
58
67
 
59
- // ── Step 1: Create ~/.claude-brain/ ──────────────────────
68
+ // ── Step 1: Create ~/.claude-brain/ data directory ───────
60
69
 
61
70
  function setupHomeDirectory() {
62
- if (existsSync(join(HOME, 'data'))) {
63
- log('Home directory already initialized')
64
- return true
65
- }
66
-
67
- log(`Setting up ${HOME}/...`)
71
+ const isAlreadySetup = existsSync(join(HOME, 'data'))
68
72
 
69
73
  const dirs = [
70
74
  join(HOME, 'data'),
71
- join(HOME, 'data', 'chroma'),
72
75
  join(HOME, 'logs'),
73
76
  join(HOME, 'vault'),
74
77
  join(HOME, 'vault', 'Projects'),
@@ -80,20 +83,14 @@ function setupHomeDirectory() {
80
83
  mkdirSync(dir, { recursive: true })
81
84
  }
82
85
 
83
- // Write default .env if not present
86
+ // Write default .env if not present (for backward compat)
84
87
  const envPath = join(HOME, '.env')
85
88
  if (!existsSync(envPath)) {
86
89
  writeFileSync(envPath, `# Claude Brain Configuration
87
90
  # Generated by postinstall
88
91
  VAULT_PATH=${join(HOME, 'vault')}
89
- LOG_LEVEL=info
92
+ LOG_LEVEL=warn
90
93
  NODE_ENV=production
91
-
92
- # ChromaDB Configuration
93
- CHROMA_MODE=client-server
94
- CHROMA_HOST=localhost
95
- CHROMA_PORT=8000
96
- CHROMA_EMBEDDING_PROVIDER=transformers
97
94
  `, 'utf-8')
98
95
  }
99
96
 
@@ -119,96 +116,50 @@ last_updated: ${new Date().toISOString().split('T')[0]}
119
116
  `, 'utf-8')
120
117
  }
121
118
 
122
- log('Home directory initialized')
119
+ if (isAlreadySetup) {
120
+ log('Data directory already exists')
121
+ } else {
122
+ log('Data directory created')
123
+ }
124
+
123
125
  return true
124
126
  }
125
127
 
126
- // ── Step 2: Install ChromaDB via pip ─────────────────────
128
+ // ── Step 2: Create default config.yml ────────────────────
127
129
 
128
- function installChromaDB() {
129
- // Check if already installed
130
- try {
131
- execSync('chroma --version', { stdio: 'pipe', timeout: 5000 })
132
- log('ChromaDB already installed')
130
+ function createDefaultConfig() {
131
+ const configPath = join(HOME, 'config.yml')
132
+ if (existsSync(configPath)) {
133
+ log('Config already exists')
133
134
  return true
134
- } catch {}
135
-
136
- // Check for Python
137
- let pythonCmd = null
138
- for (const cmd of ['python3', 'python']) {
139
- try {
140
- const ver = execSync(`${cmd} --version`, { encoding: 'utf-8', stdio: 'pipe', timeout: 5000 })
141
- if (ver.includes('3.')) {
142
- pythonCmd = cmd
143
- break
144
- }
145
- } catch {}
146
135
  }
147
136
 
148
- if (!pythonCmd) {
149
- log('Python 3 not foundskipping ChromaDB install (SQLite fallback will be used)')
150
- return false
151
- }
152
-
153
- log('Installing ChromaDB...')
154
-
155
- const pipCommands = pythonCmd === 'python3'
156
- ? ['pip3 install chromadb', 'python3 -m pip install chromadb']
157
- : ['pip install chromadb', 'python -m pip install chromadb']
158
-
159
- for (const cmd of pipCommands) {
160
- try {
161
- execSync(cmd, { stdio: 'pipe', timeout: 300_000 })
162
- log('ChromaDB installed')
163
- return true
164
- } catch {}
165
- }
137
+ const config = `# Claude Brain Configuration
138
+ # Auto-generated on installedit as needed
139
+ # Docs: https://github.com/your-org/claude-brain
166
140
 
167
- log('ChromaDB install failed — SQLite fallback will be used')
168
- return false
169
- }
141
+ storage:
142
+ dataDir: ~/.claude-brain/data
170
143
 
171
- // ── Step 3: Register MCP server ──────────────────────────
144
+ # ChromaDB is optional. SQLite FTS5 is the default search backend.
145
+ # Enable for semantic/vector search (requires: pip install chromadb)
146
+ chromadb:
147
+ enabled: false
172
148
 
173
- function registerMcpServer() {
174
- // Check if claude CLI exists
175
- try {
176
- execSync('claude --version', { stdio: 'pipe', timeout: 5000 })
177
- } catch {
178
- log('Claude CLI not found — skipping MCP registration')
179
- log(' Run manually: claude mcp add claude-brain -- claude-brain serve')
180
- return false
181
- }
149
+ # Code intelligence indexes your project files for smarter context
150
+ codeIntelligence:
151
+ enabled: true
152
+ autoIndexOnSessionStart: true
182
153
 
183
- // Check if already configured
184
- try {
185
- const result = execSync('claude mcp list', { encoding: 'utf-8', stdio: 'pipe', timeout: 10000 })
186
- if (result.includes('claude-brain')) {
187
- log('MCP server already registered')
188
- return true
189
- }
190
- } catch {}
154
+ logLevel: warn
155
+ `
191
156
 
192
- // Register
193
- try {
194
- execSync('claude mcp add claude-brain -- claude-brain serve', {
195
- stdio: 'pipe',
196
- timeout: 15000,
197
- })
198
- log('Registered as MCP server in Claude Code')
199
- return true
200
- } catch (err) {
201
- const msg = err instanceof Error ? err.message : String(err)
202
- if (msg.includes('already') || msg.includes('exists')) {
203
- log('MCP server already registered')
204
- return true
205
- }
206
- log('MCP registration failed — run manually: claude mcp add claude-brain -- claude-brain serve')
207
- return false
208
- }
157
+ writeFileSync(configPath, config, 'utf-8')
158
+ log('Created default config.yml')
159
+ return true
209
160
  }
210
161
 
211
- // ── Step 4: Install hooks ────────────────────────────────
162
+ // ── Step 3: Copy hook files ──────────────────────────────
212
163
 
213
164
  /** Files to copy from package src/hooks/ to ~/.claude-brain/hooks/ */
214
165
  const HOOK_FILES = [
@@ -236,13 +187,13 @@ function copyHookFiles() {
236
187
  copied++
237
188
  }
238
189
  }
239
- log(`Copied ${copied}/${HOOK_FILES.length} hook files to ${destDir}`)
190
+ log(`Copied ${copied}/${HOOK_FILES.length} hook files`)
191
+ return copied > 0
240
192
  }
241
193
 
242
- function installHooks() {
243
- // Copy hook script files first
244
- copyHookFiles()
194
+ // ── Step 4: Register hooks in ~/.claude/settings.json ────
245
195
 
196
+ function installHooks() {
246
197
  // Read existing settings
247
198
  let settings = {}
248
199
  if (existsSync(CLAUDE_SETTINGS)) {
@@ -322,7 +273,7 @@ function installHooks() {
322
273
  writeFileSync(tmpPath, JSON.stringify(settings, null, 2) + '\n', 'utf-8')
323
274
  renameSync(tmpPath, CLAUDE_SETTINGS)
324
275
 
325
- log('Hooks installed')
276
+ log('Hooks registered in Claude Code')
326
277
  return true
327
278
  }
328
279
 
@@ -372,25 +323,50 @@ async function main() {
372
323
  return
373
324
  }
374
325
 
375
- log('Running postinstall setup...')
326
+ log('Running zero-config setup...')
376
327
  console.error('')
377
328
 
378
329
  const results = {
379
330
  home: false,
380
- chromadb: false,
381
- mcp: false,
331
+ config: false,
382
332
  hooks: false,
333
+ hookFiles: false,
383
334
  claudemd: false,
384
335
  }
385
336
 
337
+ // Step 1: Create data directory
386
338
  try { results.home = setupHomeDirectory() } catch (e) { log(`Home setup failed: ${e.message}`) }
387
- try { results.chromadb = installChromaDB() } catch (e) { log(`ChromaDB install failed: ${e.message}`) }
388
- try { results.mcp = registerMcpServer() } catch (e) { log(`MCP registration failed: ${e.message}`) }
389
- try { results.hooks = installHooks() } catch (e) { log(`Hook install failed: ${e.message}`) }
339
+
340
+ // Step 2: Create default config.yml
341
+ try { results.config = createDefaultConfig() } catch (e) { log(`Config creation failed: ${e.message}`) }
342
+
343
+ // Step 3: Copy hook files
344
+ try { results.hookFiles = copyHookFiles() } catch (e) { log(`Hook file copy failed: ${e.message}`) }
345
+
346
+ // Step 4: Register hooks in Claude Code settings
347
+ try { results.hooks = installHooks() } catch (e) { log(`Hook registration failed: ${e.message}`) }
348
+
349
+ // Step 5: Install CLAUDE.md
390
350
  try { results.claudemd = installClaudeMd() } catch (e) { log(`CLAUDE.md install failed: ${e.message}`) }
391
351
 
352
+ // Print success summary
353
+ console.error('')
354
+ console.error(`${PREFIX} ────────────────────────────────────────`)
355
+ console.error(`${PREFIX}`)
356
+ console.error(`${PREFIX} Claude Brain installed successfully!`)
357
+ console.error(`${PREFIX}`)
358
+ console.error(`${PREFIX} Next steps:`)
359
+ console.error(`${PREFIX} 1. Start the server: claude-brain start`)
360
+ console.error(`${PREFIX} 2. Restart Claude Code to activate hooks`)
361
+ console.error(`${PREFIX}`)
362
+ console.error(`${PREFIX} Data: ${HOME}/data/`)
363
+ console.error(`${PREFIX} Config: ${HOME}/config.yml`)
364
+ console.error(`${PREFIX} Hooks: ${HOME}/hooks/`)
365
+ console.error(`${PREFIX}`)
366
+ console.error(`${PREFIX} No setup wizard needed — everything works out of the box.`)
367
+ console.error(`${PREFIX} ChromaDB is optional (disabled by default, SQLite FTS5 is used).`)
368
+ console.error(`${PREFIX} ────────────────────────────────────────`)
392
369
  console.error('')
393
- log('Setup complete! Restart Claude Code to activate.')
394
370
  }
395
371
 
396
372
  main().catch(err => {
@@ -6,16 +6,8 @@ function getDefaultEnv(vaultPath: string): string {
6
6
  return `# Claude Brain Configuration
7
7
  # Generated by auto-setup on first run
8
8
  VAULT_PATH=${vaultPath}
9
- LOG_LEVEL=info
9
+ LOG_LEVEL=warn
10
10
  NODE_ENV=production
11
-
12
- # ChromaDB Configuration
13
- # Start ChromaDB server: claude-brain chroma start
14
- # Install ChromaDB: claude-brain chroma install
15
- CHROMA_MODE=client-server
16
- CHROMA_HOST=localhost
17
- CHROMA_PORT=8000
18
- CHROMA_EMBEDDING_PROVIDER=transformers
19
11
  `
20
12
  }
21
13
 
package/src/cli/bin.ts CHANGED
@@ -29,7 +29,7 @@ function printHelp() {
29
29
  const commands = [
30
30
  ['start', 'Start ChromaDB + MCP server'],
31
31
  ['serve', 'Start the MCP server (default)'],
32
- ['setup', 'Run interactive setup wizard'],
32
+ ['setup', 'Ensure home directory and config exist'],
33
33
  ['install', 'Register as MCP server in Claude Code'],
34
34
  ['uninstall', 'Remove MCP server from Claude Code'],
35
35
  ['update', 'Update package and refresh CLAUDE.md'],
@@ -40,6 +40,9 @@ function printHelp() {
40
40
  ['init', 'Scan a repo and store initial project context'],
41
41
  ['capture', 'Capture a hook event (used by Claude Code hooks)'],
42
42
  ['pack', 'Manage knowledge packs (list/status/reload)'],
43
+ ['status', 'Show system overview'],
44
+ ['reindex', 'Rebuild code intelligence index'],
45
+ ['export', 'Export observations (--project, --format md|json)'],
43
46
  ['health', 'Run health checks'],
44
47
  ['diagnose', 'Run diagnostics'],
45
48
  ['version', 'Show version'],
@@ -65,7 +68,7 @@ function printHelp() {
65
68
  theme.bold('Examples:'),
66
69
  ` ${dimText('claude-brain start')} ${dimText('Start ChromaDB + MCP server')}`,
67
70
  ` ${dimText('claude-brain start --chroma-only')} ${dimText('Start only ChromaDB')}`,
68
- ` ${dimText('claude-brain setup')} ${dimText('Configure Claude Brain')}`,
71
+ ` ${dimText('claude-brain setup')} ${dimText('Initialize data directory')}`,
69
72
  ` ${dimText('claude-brain install')} ${dimText('Register with Claude Code')}`,
70
73
  ` ${dimText('claude-brain health')} ${dimText('Check system health')}`,
71
74
  '',
@@ -184,6 +187,24 @@ async function main() {
184
187
  break
185
188
  }
186
189
 
190
+ case 'status': {
191
+ const { runStatus } = await import('./commands/status')
192
+ await runStatus()
193
+ break
194
+ }
195
+
196
+ case 'reindex': {
197
+ const { runReindex } = await import('./commands/reindex')
198
+ await runReindex()
199
+ break
200
+ }
201
+
202
+ case 'export': {
203
+ const { runExport } = await import('./commands/export')
204
+ await runExport()
205
+ break
206
+ }
207
+
187
208
  case 'health': {
188
209
  const { runHealthCheck } = await import('@/health')
189
210
  await runHealthCheck()
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Export Command — Phase 30
3
+ * Export observations for a project in markdown or JSON format.
4
+ *
5
+ * Usage:
6
+ * claude-brain export --project expense-tracker --format md > decisions.md
7
+ * claude-brain export --project expense-tracker --format json > decisions.json
8
+ * claude-brain export --project expense-tracker --category decision
9
+ */
10
+
11
+ import { join } from 'node:path'
12
+ import { existsSync } from 'node:fs'
13
+ import { parseArgs } from 'citty'
14
+ import { getClaudeBrainHome } from '@/config/home'
15
+
16
+ export async function runExport() {
17
+ const args = parseArgs(process.argv.slice(3), {
18
+ project: { type: 'string', description: 'Project name to export' },
19
+ format: { type: 'string', default: 'md', description: 'Output format: md or json' },
20
+ category: { type: 'string', description: 'Filter by category: decision, pattern, correction' },
21
+ limit: { type: 'string', default: '1000', description: 'Max observations to export' },
22
+ })
23
+
24
+ const project = args.project
25
+ const format = args.format || 'md'
26
+ const category = args.category
27
+ const limit = parseInt(args.limit || '1000', 10)
28
+
29
+ if (!project) {
30
+ console.error('Error: --project is required')
31
+ console.error('Usage: claude-brain export --project <name> [--format md|json] [--category decision|pattern|correction]')
32
+ process.exit(1)
33
+ }
34
+
35
+ const dbPath = join(getClaudeBrainHome(), 'data', 'memory.db')
36
+ if (!existsSync(dbPath)) {
37
+ console.error('Error: No database found. Run claude-brain first to initialize.')
38
+ process.exit(1)
39
+ }
40
+
41
+ const { Database } = await import('bun:sqlite')
42
+ const db = new Database(dbPath, { readonly: true })
43
+
44
+ let sql = 'SELECT * FROM observations WHERE project = ? AND archived = 0'
45
+ const params: any[] = [project]
46
+
47
+ if (category) {
48
+ sql += ' AND category = ?'
49
+ params.push(category)
50
+ }
51
+
52
+ sql += ' ORDER BY created_at DESC LIMIT ?'
53
+ params.push(limit)
54
+
55
+ const rows = db.prepare(sql).all(...params) as any[]
56
+ db.close()
57
+
58
+ if (rows.length === 0) {
59
+ console.error(`No observations found for project "${project}"${category ? ` with category "${category}"` : ''}.`)
60
+ process.exit(0)
61
+ }
62
+
63
+ if (format === 'json') {
64
+ const output = rows.map(row => ({
65
+ id: row.id,
66
+ project: row.project,
67
+ category: row.category,
68
+ content: row.content,
69
+ reasoning: row.reasoning || null,
70
+ context: row.context || null,
71
+ confidence: row.confidence,
72
+ source: row.source,
73
+ tags: parseTags(row.tags),
74
+ created_at: row.created_at,
75
+ updated_at: row.updated_at,
76
+ }))
77
+ console.log(JSON.stringify(output, null, 2))
78
+ } else {
79
+ // Markdown format
80
+ const lines: string[] = []
81
+ lines.push(`# ${project} — Observations Export`)
82
+ lines.push('')
83
+ lines.push(`> Exported ${rows.length} observation(s) on ${new Date().toISOString().split('T')[0]}`)
84
+ lines.push('')
85
+
86
+ // Group by category
87
+ const grouped: Record<string, any[]> = {}
88
+ for (const row of rows) {
89
+ const cat = row.category || 'other'
90
+ if (!grouped[cat]) grouped[cat] = []
91
+ grouped[cat].push(row)
92
+ }
93
+
94
+ for (const [cat, items] of Object.entries(grouped)) {
95
+ lines.push(`## ${capitalize(cat)}s (${items.length})`)
96
+ lines.push('')
97
+
98
+ for (const item of items) {
99
+ const date = item.created_at ? item.created_at.split('T')[0] : 'unknown'
100
+ lines.push(`### ${(item.content as string).slice(0, 80)}`)
101
+ lines.push('')
102
+ lines.push(`- **Date:** ${date}`)
103
+ lines.push(`- **Content:** ${item.content}`)
104
+ if (item.reasoning) lines.push(`- **Reasoning:** ${item.reasoning}`)
105
+ if (item.context) lines.push(`- **Context:** ${item.context}`)
106
+ const tags = parseTags(item.tags)
107
+ if (tags.length > 0) lines.push(`- **Tags:** ${tags.join(', ')}`)
108
+ lines.push('')
109
+ lines.push('---')
110
+ lines.push('')
111
+ }
112
+ }
113
+
114
+ console.log(lines.join('\n'))
115
+ }
116
+ }
117
+
118
+ function parseTags(value: string | null): string[] {
119
+ if (!value) return []
120
+ try {
121
+ const parsed = JSON.parse(value)
122
+ return Array.isArray(parsed) ? parsed : []
123
+ } catch {
124
+ return value.split(',').map(s => s.trim()).filter(Boolean)
125
+ }
126
+ }
127
+
128
+ function capitalize(s: string): string {
129
+ return s.charAt(0).toUpperCase() + s.slice(1)
130
+ }
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Reindex Command — Phase 30
3
+ * Triggers a code intelligence reindex for a project.
4
+ *
5
+ * Usage:
6
+ * claude-brain reindex # reindex current directory
7
+ * claude-brain reindex /path/to/project # reindex specific project
8
+ * claude-brain reindex --force # ignore hashes, reparse everything
9
+ */
10
+
11
+ import path from 'path'
12
+ import { parseArgs } from 'citty'
13
+ import { renderLogo, theme, heading, successText, warningText, dimText, box } from '@/cli/ui/index.js'
14
+
15
+ export async function runReindex() {
16
+ console.log()
17
+ console.log(renderLogo())
18
+ console.log()
19
+ console.log(heading('Reindex Project'))
20
+ console.log()
21
+
22
+ const args = parseArgs(process.argv.slice(3), {
23
+ path: { type: 'positional', required: false, description: 'Path to project directory' },
24
+ force: { type: 'boolean', default: false, description: 'Ignore hashes, reparse everything' },
25
+ })
26
+
27
+ const projectPath = path.resolve(args.path || process.cwd())
28
+ const projectName = path.basename(projectPath)
29
+ const force = args.force
30
+
31
+ console.log(` ${theme.bold('Project:')} ${dimText(projectName)}`)
32
+ console.log(` ${theme.bold('Path:')} ${dimText(projectPath)}`)
33
+ if (force) {
34
+ console.log(` ${theme.bold('Mode:')} ${dimText('Force reindex (ignore hashes)')}`)
35
+ }
36
+ console.log()
37
+
38
+ // Try via HTTP API first (server may be running)
39
+ const port = process.env.CLAUDE_BRAIN_PORT || '3000'
40
+ const url = `http://localhost:${port}/api/code/index`
41
+
42
+ try {
43
+ const res = await fetch(url, {
44
+ method: 'POST',
45
+ headers: { 'Content-Type': 'application/json' },
46
+ body: JSON.stringify({ projectPath, projectName, force }),
47
+ signal: AbortSignal.timeout(30000)
48
+ })
49
+
50
+ if (res.ok) {
51
+ const data = await res.json() as { data?: { filesIndexed?: number; symbolsFound?: number } }
52
+ const files = data.data?.filesIndexed ?? 0
53
+ const symbols = data.data?.symbolsFound ?? 0
54
+ console.log(successText(` Reindex complete: ${files} files, ${symbols} symbols`))
55
+ console.log()
56
+ return
57
+ }
58
+
59
+ console.log(warningText(` Server returned ${res.status}, trying direct indexing...`))
60
+ } catch {
61
+ console.log(dimText(' Server not reachable, indexing directly...'))
62
+ }
63
+
64
+ // Direct indexing (no server)
65
+ try {
66
+ const { Database } = await import('bun:sqlite')
67
+ const { resolve, join } = await import('node:path')
68
+ const { mkdirSync, existsSync } = await import('node:fs')
69
+ const { getClaudeBrainHome } = await import('@/config/home')
70
+
71
+ const codeDbDir = resolve(getClaudeBrainHome(), 'data')
72
+ if (!existsSync(codeDbDir)) {
73
+ mkdirSync(codeDbDir, { recursive: true })
74
+ }
75
+ const codeDbPath = join(codeDbDir, 'code.db')
76
+ const codeDb = new Database(codeDbPath)
77
+ codeDb.exec('PRAGMA journal_mode=WAL')
78
+
79
+ const { CodeParser } = await import('@/code-intelligence/parser')
80
+ const { CodeIndexer } = await import('@/code-intelligence/indexer')
81
+ const pino = await import('pino')
82
+ const logger = pino.default({ level: 'warn' })
83
+
84
+ const parser = new CodeParser()
85
+ await parser.initialize()
86
+
87
+ const indexer = new CodeIndexer(codeDb, parser, logger)
88
+ await indexer.initialize()
89
+
90
+ if (force) {
91
+ // Clear existing index for this project
92
+ codeDb.run('DELETE FROM code_files WHERE project = ?', projectName)
93
+ codeDb.run('DELETE FROM code_symbols WHERE project = ?', projectName)
94
+ }
95
+
96
+ const result = await indexer.indexProject(projectPath, projectName)
97
+ console.log(successText(` Reindex complete: ${result.filesIndexed ?? 0} files, ${result.symbolsFound ?? 0} symbols`))
98
+ codeDb.close()
99
+ } catch (error) {
100
+ console.log(box(
101
+ warningText(`Failed to reindex: ${error instanceof Error ? error.message : String(error)}`),
102
+ 'Error'
103
+ ))
104
+ }
105
+
106
+ console.log()
107
+ }
@@ -131,6 +131,60 @@ export async function runServe() {
131
131
  }
132
132
  }
133
133
 
134
+ // Phase 28: Wire code intelligence to HTTP server
135
+ {
136
+ const { getCodeIndexer, getCodeQuery } = await import('@/server/services')
137
+ const codeIndexer = getCodeIndexer()
138
+ const codeQuery = getCodeQuery()
139
+ if (codeIndexer && codeQuery) {
140
+ httpServer.setCodeIntelligence(codeIndexer, codeQuery)
141
+ mainLogger.info('Code intelligence wired to HTTP server')
142
+ }
143
+ }
144
+
145
+ // Phase 29: Wire code linker to HTTP server
146
+ {
147
+ const { getCodeLinker } = await import('@/server/services')
148
+ const codeLinker = getCodeLinker()
149
+ if (codeLinker) {
150
+ httpServer.setCodeLinker(codeLinker)
151
+ mainLogger.info('Code linker wired to HTTP server')
152
+ }
153
+ }
154
+
155
+ // Phase 30: Activity log pruning on startup + periodic cleanup
156
+ {
157
+ const memory = getMemoryService()
158
+ if (memory?.isInitialized()) {
159
+ try {
160
+ const { startPeriodicPruning } = await import('@/memory/pruning')
161
+ const db = memory.database.getDb()
162
+ const stopPruning = startPeriodicPruning(db, logger, 30)
163
+ cleanup.register(async () => {
164
+ stopPruning()
165
+ mainLogger.info('Activity log pruning stopped')
166
+ })
167
+ mainLogger.info('Activity log pruning initialized')
168
+ } catch (error) {
169
+ mainLogger.warn({ error }, 'Failed to initialize activity log pruning')
170
+ }
171
+ }
172
+ }
173
+
174
+ // Phase 30: Optional LLM compression
175
+ if (config.compression?.enabled) {
176
+ try {
177
+ const { ObservationCompressor } = await import('@/memory/compression')
178
+ const { getBrainRouter } = await import('@/routing/router')
179
+ const compressor = new ObservationCompressor(config.compression, logger)
180
+ const router = getBrainRouter(logger)
181
+ router.setCompressor(compressor)
182
+ mainLogger.info({ provider: config.compression.provider, model: config.compression.model }, 'LLM compression enabled')
183
+ } catch (error) {
184
+ mainLogger.warn({ error }, 'Failed to initialize LLM compression')
185
+ }
186
+ }
187
+
134
188
  // Start HTTP server after MCP server is ready
135
189
  setTimeout(async () => {
136
190
  try {