claude-brain 0.17.13 → 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 (46) 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/brain-hook.ts +4 -1
  21. package/src/hooks/context-hook.ts +69 -10
  22. package/src/hooks/installer.ts +4 -7
  23. package/src/intelligence/cross-project/index.ts +1 -7
  24. package/src/intelligence/prediction/index.ts +1 -7
  25. package/src/intelligence/reasoning/index.ts +1 -7
  26. package/src/memory/compression.ts +105 -0
  27. package/src/memory/fts5-search.ts +456 -0
  28. package/src/memory/index.ts +342 -38
  29. package/src/memory/migrations/add-fts5.ts +98 -0
  30. package/src/memory/pruning.ts +60 -0
  31. package/src/routing/intent-classifier.ts +58 -1
  32. package/src/routing/response-filter.ts +128 -0
  33. package/src/routing/router.ts +457 -54
  34. package/src/server/http-api.ts +319 -1
  35. package/src/server/providers/resources.ts +1 -42
  36. package/src/server/services.ts +113 -12
  37. package/src/server/web-viewer.ts +1115 -0
  38. package/src/setup/index.ts +12 -22
  39. package/src/tools/schemas.ts +1 -1
  40. package/src/intelligence/cross-project/affinity.ts +0 -159
  41. package/src/intelligence/cross-project/transfer.ts +0 -201
  42. package/src/intelligence/prediction/context-anticipator.ts +0 -198
  43. package/src/intelligence/prediction/decision-predictor.ts +0 -184
  44. package/src/intelligence/reasoning/counterfactual.ts +0 -248
  45. package/src/intelligence/reasoning/synthesizer.ts +0 -167
  46. package/src/setup/wizard.ts +0 -459
@@ -85,10 +85,15 @@ async function main(): Promise<void> {
85
85
  return
86
86
  }
87
87
 
88
- const port = parseInt(process.env.CLAUDE_BRAIN_PORT || '3000', 10)
88
+ // BUG-006: Read port from --port arg (cross-platform) or env var fallback
89
+ const portIdx = process.argv.indexOf('--port')
90
+ const portArg = portIdx >= 0 ? process.argv[portIdx + 1] : undefined
91
+ const port = parseInt(portArg || process.env.CLAUDE_BRAIN_PORT || '3000', 10)
89
92
  const baseUrl = `http://localhost:${port}`
90
93
 
91
94
  let brainContext = ''
95
+ let codeMapText = ''
96
+ let fileMemoryText = ''
92
97
 
93
98
  try {
94
99
  if (event === 'UserPromptSubmit' && input.prompt) {
@@ -99,27 +104,64 @@ async function main(): Promise<void> {
99
104
  })
100
105
  if (input.cwd) params.set('cwd', input.cwd)
101
106
 
102
- const res = await fetch(`${baseUrl}/api/hooks/context-query?${params}`, {
107
+ const contextPromise = fetch(`${baseUrl}/api/hooks/context-query?${params}`, {
103
108
  signal: AbortSignal.timeout(2000),
104
- })
109
+ }).catch(() => null)
110
+
111
+ // Phase 29: Detect file paths in the user's prompt and fetch linked memories
112
+ const projectName = input.cwd ? input.cwd.split('/').filter(Boolean).pop() : ''
113
+ const filePaths = extractFilePathsFromPrompt(input.prompt)
114
+ const fileMemoryPromises = filePaths.slice(0, 3).map(fp =>
115
+ fetch(`${baseUrl}/api/memory/for-file?file=${encodeURIComponent(fp)}&project=${encodeURIComponent(projectName || '')}`, {
116
+ signal: AbortSignal.timeout(1000),
117
+ }).then(r => r.ok ? r.json() : null).catch(() => null)
118
+ )
105
119
 
106
- if (res.ok) {
107
- const data = await res.json() as { success: boolean; context: string }
120
+ const [contextRes, ...fileMemories] = await Promise.all([contextPromise, ...fileMemoryPromises])
121
+
122
+ if (contextRes?.ok) {
123
+ const data = await contextRes.json() as { success: boolean; context: string }
108
124
  brainContext = data.context || ''
109
125
  }
126
+
127
+ // Phase 29: Aggregate file memory summaries
128
+ const relevant = fileMemories.filter((m: any) => m?.data?.summary)
129
+ if (relevant.length > 0) {
130
+ fileMemoryText = relevant.map((m: any) => m.data.summary).join('\n\n')
131
+ }
110
132
  } else if (event === 'SessionStart') {
111
133
  // Load broader project context on session start
112
134
  const params = new URLSearchParams({ type: 'session-start' })
113
135
  if (input.cwd) params.set('cwd', input.cwd)
114
136
 
115
- const res = await fetch(`${baseUrl}/api/hooks/context-query?${params}`, {
116
- signal: AbortSignal.timeout(2000),
117
- })
137
+ // Extract project name from cwd for code map
138
+ const projectName = input.cwd ? input.cwd.split('/').filter(Boolean).pop() : undefined
139
+
140
+ // Fetch brain context AND code map in parallel
141
+ const [contextRes, codeMapRes] = await Promise.all([
142
+ fetch(`${baseUrl}/api/hooks/context-query?${params}`, { signal: AbortSignal.timeout(2000) }).catch(() => null),
143
+ projectName
144
+ ? fetch(`${baseUrl}/api/code/file-map?project=${encodeURIComponent(projectName)}`, { signal: AbortSignal.timeout(2000) }).catch(() => null)
145
+ : Promise.resolve(null),
146
+ ])
118
147
 
119
- if (res.ok) {
120
- const data = await res.json() as { success: boolean; context: string }
148
+ // Process brain context (existing logic)
149
+ if (contextRes?.ok) {
150
+ const data = await contextRes.json() as { success: boolean; context: string }
121
151
  brainContext = data.context || ''
122
152
  }
153
+
154
+ // Process code map (Phase 28)
155
+ if (codeMapRes?.ok) {
156
+ try {
157
+ const data = await codeMapRes.json() as { success: boolean; data?: { map?: string } }
158
+ if (data.success && data.data?.map) {
159
+ codeMapText = data.data.map
160
+ }
161
+ } catch {
162
+ // Silently ignore malformed code map response
163
+ }
164
+ }
123
165
  }
124
166
  } catch {
125
167
  // Server unreachable or timeout — continue with static content only for SessionStart
@@ -145,6 +187,16 @@ async function main(): Promise<void> {
145
187
  parts.push(`[Brain Memory]\n${brainContext}`)
146
188
  }
147
189
 
190
+ // Phase 29: Append file-linked memories (UserPromptSubmit only)
191
+ if (fileMemoryText.trim()) {
192
+ parts.push(`[File Memories]\n${fileMemoryText}`)
193
+ }
194
+
195
+ // Append code map (SessionStart only, Phase 28)
196
+ if (codeMapText.trim()) {
197
+ parts.push(`[Code Map]\nProject file index (use Read directly, skip Glob):\n${codeMapText}`)
198
+ }
199
+
148
200
  // Output combined context
149
201
  if (parts.length > 0) {
150
202
  const output = {
@@ -159,6 +211,13 @@ async function main(): Promise<void> {
159
211
  process.exit(0)
160
212
  }
161
213
 
214
+ /** Phase 29: Extract file paths from user prompt text */
215
+ function extractFilePathsFromPrompt(prompt: string): string[] {
216
+ const pathPattern = /(?:^|\s)((?:src|lib|app|pages|components|utils|hooks|server|api|config|middleware|routes|models|services|types|scripts|tests|test)\/[\w\-./]+\.\w+)/g
217
+ const matches = [...prompt.matchAll(pathPattern)]
218
+ return [...new Set(matches.map(m => m[1]))]
219
+ }
220
+
162
221
  /** Read all of stdin as a string */
163
222
  function readStdin(): Promise<string> {
164
223
  return new Promise((resolve, reject) => {
@@ -44,17 +44,14 @@ function writeSettings(settings: Record<string, any>): void {
44
44
  renameSync(tmpPath, CLAUDE_SETTINGS_PATH)
45
45
  }
46
46
 
47
- /** Build the hook command string, embedding the port so hooks work regardless of env */
47
+ /** Build the hook command string, embedding the port so hooks work regardless of env.
48
+ * BUG-006: settings.json syncs across platforms. We use --port arg instead of
49
+ * platform-specific env var syntax (VAR=val on Unix, set VAR=val on Windows). */
48
50
  function buildHookCommand(event: string, script: 'brain-hook' | 'context-hook' = 'brain-hook'): string {
49
51
  const scriptPath = script === 'context-hook' ? getContextHookScriptPath() : getHookScriptPath()
50
52
  const port = process.env.PORT || process.env.CLAUDE_BRAIN_PORT || '3000'
51
53
 
52
- // Claude Code runs hooks via cmd.exe on Windows, which can't parse VAR=value syntax
53
- if (process.platform === 'win32') {
54
- return `set CLAUDE_BRAIN_PORT=${port}&& bun "${scriptPath}" --event ${event} # ${HOOK_MARKER}`
55
- }
56
-
57
- return `CLAUDE_BRAIN_PORT=${port} bun "${scriptPath}" --event ${event} # ${HOOK_MARKER}`
54
+ return `bun "${scriptPath}" --event ${event} --port ${port} # ${HOOK_MARKER}`
58
55
  }
59
56
 
60
57
  /**
@@ -1,13 +1,7 @@
1
1
  /**
2
2
  * Cross-Project Intelligence Module
3
- * Phase 15.4 - Pattern generalization, project affinity, knowledge transfer
3
+ * Phase 15.4 - Pattern generalization
4
4
  */
5
5
 
6
6
  export { PatternGeneralizer } from './generalizer'
7
7
  export type { GeneralizedPattern, GeneralizationResult } from './generalizer'
8
-
9
- export { AffinityCalculator } from './affinity'
10
- export type { ProjectAffinity } from './affinity'
11
-
12
- export { KnowledgeTransfer } from './transfer'
13
- export type { TransferableKnowledge, TransferItem } from './transfer'
@@ -1,13 +1,7 @@
1
1
  /**
2
2
  * Predictive Intelligence Module
3
- * Phase 15.3 - Decision prediction, context anticipation, recommendations
3
+ * Phase 15.3 - Recommendations
4
4
  */
5
5
 
6
- export { DecisionPredictor } from './decision-predictor'
7
- export type { PredictedDecision } from './decision-predictor'
8
-
9
- export { ContextAnticipator } from './context-anticipator'
10
- export type { AnticipatedContext } from './context-anticipator'
11
-
12
6
  export { Recommender } from './recommender'
13
7
  export type { Recommendation, RecommendationResult } from './recommender'
@@ -1,13 +1,7 @@
1
1
  /**
2
2
  * Multi-Hop Reasoning Module
3
- * Phase 15.2 - Chain retrieval, result synthesis, counterfactual analysis
3
+ * Phase 15.2 - Chain retrieval
4
4
  */
5
5
 
6
6
  export { ChainRetrieval } from './chain-retrieval'
7
7
  export type { ChainStep, ChainResult, ChainRetrievalResult } from './chain-retrieval'
8
-
9
- export { ResultSynthesizer } from './synthesizer'
10
- export type { SynthesizedResult } from './synthesizer'
11
-
12
- export { CounterfactualAnalyzer } from './counterfactual'
13
- export type { WhatIfScenario } from './counterfactual'
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Observation Compressor — Phase 30
3
+ * Optional LLM-based compression for long observations.
4
+ * Uses Anthropic API directly via fetch (no Agent SDK dependency).
5
+ * Graceful fallback: if compression fails, store original text.
6
+ */
7
+
8
+ import type { Logger } from 'pino'
9
+
10
+ export interface CompressionConfig {
11
+ enabled: boolean
12
+ provider: 'claude' | 'ollama'
13
+ model: string
14
+ apiKey?: string
15
+ minContentLength: number
16
+ }
17
+
18
+ export interface CompressedObservation {
19
+ summary: string
20
+ original?: string
21
+ compressed: boolean
22
+ }
23
+
24
+ export class ObservationCompressor {
25
+ private config: CompressionConfig
26
+ private logger: Logger
27
+
28
+ constructor(config: CompressionConfig, logger: Logger) {
29
+ this.config = config
30
+ this.logger = logger.child({ component: 'compressor' })
31
+ }
32
+
33
+ async compress(content: string, category: string): Promise<CompressedObservation> {
34
+ if (!this.config.enabled || content.length < this.config.minContentLength) {
35
+ return { summary: content, compressed: false }
36
+ }
37
+
38
+ const prompt = `Compress this ${category} observation into a concise summary (2-3 sentences max).
39
+ Preserve: the core decision/insight, reasoning, and any specific technologies mentioned.
40
+ Drop: filler words, repetition, context that's obvious from the category.
41
+
42
+ Observation: ${content}`
43
+
44
+ try {
45
+ const response = await this.callLLM(prompt)
46
+ return { summary: response, original: content, compressed: true }
47
+ } catch (error) {
48
+ this.logger.warn({ error }, 'LLM compression failed, storing original')
49
+ return { summary: content, compressed: false }
50
+ }
51
+ }
52
+
53
+ private async callLLM(prompt: string): Promise<string> {
54
+ const apiKey = this.config.apiKey || process.env.ANTHROPIC_API_KEY
55
+ if (!apiKey) {
56
+ throw new Error('No API key configured for compression')
57
+ }
58
+
59
+ if (this.config.provider === 'ollama') {
60
+ return this.callOllama(prompt)
61
+ }
62
+
63
+ const res = await fetch('https://api.anthropic.com/v1/messages', {
64
+ method: 'POST',
65
+ headers: {
66
+ 'Content-Type': 'application/json',
67
+ 'x-api-key': apiKey,
68
+ 'anthropic-version': '2023-06-01'
69
+ },
70
+ body: JSON.stringify({
71
+ model: this.config.model || 'claude-haiku-4-5-20251001',
72
+ max_tokens: 200,
73
+ messages: [{ role: 'user', content: prompt }]
74
+ }),
75
+ signal: AbortSignal.timeout(10000)
76
+ })
77
+
78
+ if (!res.ok) {
79
+ throw new Error(`Anthropic API error: ${res.status} ${res.statusText}`)
80
+ }
81
+
82
+ const data = await res.json() as { content: Array<{ text: string }> }
83
+ return data.content[0].text
84
+ }
85
+
86
+ private async callOllama(prompt: string): Promise<string> {
87
+ const res = await fetch('http://localhost:11434/api/generate', {
88
+ method: 'POST',
89
+ headers: { 'Content-Type': 'application/json' },
90
+ body: JSON.stringify({
91
+ model: this.config.model || 'llama3',
92
+ prompt,
93
+ stream: false
94
+ }),
95
+ signal: AbortSignal.timeout(10000)
96
+ })
97
+
98
+ if (!res.ok) {
99
+ throw new Error(`Ollama API error: ${res.status} ${res.statusText}`)
100
+ }
101
+
102
+ const data = await res.json() as { response: string }
103
+ return data.response
104
+ }
105
+ }