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.
- package/VERSION +1 -1
- package/package.json +3 -1
- package/scripts/postinstall.mjs +80 -104
- package/src/cli/auto-setup.ts +1 -9
- package/src/cli/bin.ts +23 -2
- package/src/cli/commands/export.ts +130 -0
- package/src/cli/commands/reindex.ts +107 -0
- package/src/cli/commands/serve.ts +54 -0
- package/src/cli/commands/status.ts +158 -0
- package/src/code-intelligence/indexer.ts +315 -0
- package/src/code-intelligence/linker.ts +178 -0
- package/src/code-intelligence/parser.ts +484 -0
- package/src/code-intelligence/query.ts +291 -0
- package/src/code-intelligence/schema.ts +83 -0
- package/src/code-intelligence/types.ts +95 -0
- package/src/config/defaults.ts +3 -3
- package/src/config/loader.ts +6 -0
- package/src/config/schema.ts +28 -2
- package/src/health/index.ts +5 -2
- package/src/hooks/context-hook.ts +65 -9
- package/src/intelligence/cross-project/index.ts +1 -7
- package/src/intelligence/prediction/index.ts +1 -7
- package/src/intelligence/reasoning/index.ts +1 -7
- package/src/memory/compression.ts +105 -0
- package/src/memory/fts5-search.ts +456 -0
- package/src/memory/index.ts +342 -38
- package/src/memory/migrations/add-fts5.ts +98 -0
- package/src/memory/pruning.ts +60 -0
- package/src/routing/intent-classifier.ts +58 -1
- package/src/routing/response-filter.ts +128 -0
- package/src/routing/router.ts +403 -51
- package/src/server/http-api.ts +319 -1
- package/src/server/providers/resources.ts +1 -42
- package/src/server/services.ts +113 -12
- package/src/server/web-viewer.ts +1115 -0
- package/src/setup/index.ts +12 -22
- package/src/tools/schemas.ts +1 -1
- package/src/intelligence/cross-project/affinity.ts +0 -159
- package/src/intelligence/cross-project/transfer.ts +0 -201
- package/src/intelligence/prediction/context-anticipator.ts +0 -198
- package/src/intelligence/prediction/decision-predictor.ts +0 -184
- package/src/intelligence/reasoning/counterfactual.ts +0 -248
- package/src/intelligence/reasoning/synthesizer.ts +0 -167
- package/src/setup/wizard.ts +0 -459
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.22.0
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-brain",
|
|
3
|
-
"version": "0.
|
|
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
|
}
|
package/scripts/postinstall.mjs
CHANGED
|
@@ -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
|
-
|
|
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=
|
|
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
|
-
|
|
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:
|
|
128
|
+
// ── Step 2: Create default config.yml ────────────────────
|
|
127
129
|
|
|
128
|
-
function
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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 install — edit as needed
|
|
139
|
+
# Docs: https://github.com/your-org/claude-brain
|
|
166
140
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
}
|
|
141
|
+
storage:
|
|
142
|
+
dataDir: ~/.claude-brain/data
|
|
170
143
|
|
|
171
|
-
|
|
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
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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
|
-
|
|
184
|
-
|
|
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
|
-
|
|
193
|
-
|
|
194
|
-
|
|
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
|
|
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
|
|
190
|
+
log(`Copied ${copied}/${HOOK_FILES.length} hook files`)
|
|
191
|
+
return copied > 0
|
|
240
192
|
}
|
|
241
193
|
|
|
242
|
-
|
|
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
|
|
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
|
|
326
|
+
log('Running zero-config setup...')
|
|
376
327
|
console.error('')
|
|
377
328
|
|
|
378
329
|
const results = {
|
|
379
330
|
home: false,
|
|
380
|
-
|
|
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
|
-
|
|
388
|
-
|
|
389
|
-
try { results.
|
|
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 => {
|
package/src/cli/auto-setup.ts
CHANGED
|
@@ -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=
|
|
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', '
|
|
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('
|
|
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 {
|