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.
- 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/brain-hook.ts +4 -1
- package/src/hooks/context-hook.ts +69 -10
- package/src/hooks/installer.ts +4 -7
- 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 +457 -54
- 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
|
@@ -85,10 +85,15 @@ async function main(): Promise<void> {
|
|
|
85
85
|
return
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
-
|
|
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
|
|
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
|
-
|
|
107
|
-
|
|
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
|
-
|
|
116
|
-
|
|
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
|
-
|
|
120
|
-
|
|
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) => {
|
package/src/hooks/installer.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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 -
|
|
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
|
|
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
|
+
}
|