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
@@ -1,184 +0,0 @@
1
- /**
2
- * Decision Predictor
3
- * Predicts likely next decisions based on historical patterns
4
- */
5
-
6
- import type { Logger } from 'pino'
7
- import type { CollectionManager } from '@/memory/chroma/collection-manager'
8
- import type { EmbeddingProvider } from '@/memory/chroma/embeddings'
9
-
10
- export interface PredictedDecision {
11
- prediction: string
12
- confidence: number
13
- basedOn: Array<{
14
- id: string
15
- decision: string
16
- similarity: number
17
- }>
18
- reasoning: string
19
- }
20
-
21
- export class DecisionPredictor {
22
- private logger: Logger
23
- private collections: CollectionManager
24
- private embeddings?: EmbeddingProvider
25
-
26
- constructor(logger: Logger, collections: CollectionManager, embeddings?: EmbeddingProvider) {
27
- this.logger = logger.child({ component: 'decision-predictor' })
28
- this.collections = collections
29
- this.embeddings = embeddings
30
- }
31
-
32
- /**
33
- * Predict likely decisions for a given context
34
- */
35
- async predict(context: string, options: {
36
- project?: string
37
- limit?: number
38
- } = {}): Promise<PredictedDecision[]> {
39
- const { project, limit = 5 } = options
40
-
41
- // Find similar past contexts and their resulting decisions
42
- const pastDecisions = await this.findSimilarContextDecisions(context, project, limit * 3)
43
-
44
- if (pastDecisions.length === 0) {
45
- return []
46
- }
47
-
48
- // Group by similar decisions
49
- const grouped = this.groupSimilarDecisions(pastDecisions)
50
-
51
- // Rank by frequency and similarity
52
- const predictions = grouped
53
- .map(group => this.buildPrediction(group, context))
54
- .sort((a, b) => b.confidence - a.confidence)
55
- .slice(0, limit)
56
-
57
- return predictions
58
- }
59
-
60
- private async findSimilarContextDecisions(
61
- context: string,
62
- project?: string,
63
- limit: number = 15
64
- ): Promise<Array<{ id: string; decision: string; context: string; similarity: number }>> {
65
- try {
66
- const collection = await this.collections.getDecisions()
67
-
68
- const where: any = project ? { project: { $eq: project } } : undefined
69
-
70
- let results: any
71
-
72
- if (this.embeddings) {
73
- const embedding = await this.embeddings.generate(context)
74
- results = await collection.query({
75
- queryEmbeddings: [embedding],
76
- nResults: limit,
77
- where,
78
- include: ['documents', 'metadatas', 'distances']
79
- })
80
- } else {
81
- results = await collection.query({
82
- queryTexts: [context],
83
- nResults: limit,
84
- where,
85
- include: ['documents', 'metadatas', 'distances']
86
- })
87
- }
88
-
89
- if (!results.ids || !results.ids[0]) return []
90
-
91
- const processed: Array<{ id: string; decision: string; context: string; similarity: number }> = []
92
- const ids = results.ids[0]
93
- const documents = results.documents?.[0] || []
94
- const metadatas = results.metadatas?.[0] || []
95
- const distances = results.distances?.[0] || []
96
-
97
- for (let i = 0; i < ids.length; i++) {
98
- const similarity = 1 - (distances[i] || 0)
99
- if (similarity < 0.3) continue
100
-
101
- processed.push({
102
- id: ids[i],
103
- decision: documents[i] || '',
104
- context: (metadatas[i] as any)?.context || '',
105
- similarity
106
- })
107
- }
108
-
109
- return processed
110
- } catch (error) {
111
- this.logger.warn({ error }, 'Failed to find similar contexts for prediction')
112
- return []
113
- }
114
- }
115
-
116
- private groupSimilarDecisions(
117
- decisions: Array<{ id: string; decision: string; context: string; similarity: number }>
118
- ): Array<Array<{ id: string; decision: string; context: string; similarity: number }>> {
119
- const groups: Array<Array<{ id: string; decision: string; context: string; similarity: number }>> = []
120
-
121
- for (const decision of decisions) {
122
- let addedToGroup = false
123
-
124
- for (const group of groups) {
125
- // Check if this decision is similar to the group leader
126
- const leader = group[0]!
127
- const jaccardSim = this.jaccardSimilarity(
128
- leader.decision.toLowerCase(),
129
- decision.decision.toLowerCase()
130
- )
131
-
132
- if (jaccardSim > 0.4) {
133
- group.push(decision)
134
- addedToGroup = true
135
- break
136
- }
137
- }
138
-
139
- if (!addedToGroup) {
140
- groups.push([decision])
141
- }
142
- }
143
-
144
- return groups.sort((a, b) => b.length - a.length)
145
- }
146
-
147
- private buildPrediction(
148
- group: Array<{ id: string; decision: string; context: string; similarity: number }>,
149
- _queryContext: string
150
- ): PredictedDecision {
151
- // Use the most similar decision as the prediction
152
- const best = group.reduce((a, b) => a.similarity > b.similarity ? a : b)
153
-
154
- // Confidence based on group size + similarity
155
- const groupSizeFactor = Math.min(group.length / 3, 1) // Saturates at 3
156
- const similarityFactor = best.similarity
157
- const confidence = (groupSizeFactor * 0.4) + (similarityFactor * 0.6)
158
-
159
- const basedOn = group.slice(0, 3).map(d => ({
160
- id: d.id,
161
- decision: d.decision.slice(0, 150),
162
- similarity: d.similarity
163
- }))
164
-
165
- const reasoning = group.length > 1
166
- ? `Based on ${group.length} similar past decisions with avg ${Math.round(group.reduce((s, d) => s + d.similarity, 0) / group.length * 100)}% match`
167
- : `Based on a similar past decision with ${Math.round(best.similarity * 100)}% match`
168
-
169
- return {
170
- prediction: best.decision,
171
- confidence,
172
- basedOn,
173
- reasoning
174
- }
175
- }
176
-
177
- private jaccardSimilarity(a: string, b: string): number {
178
- const setA = new Set(a.split(/\s+/))
179
- const setB = new Set(b.split(/\s+/))
180
- const intersection = new Set([...setA].filter(x => setB.has(x)))
181
- const union = new Set([...setA, ...setB])
182
- return union.size > 0 ? intersection.size / union.size : 0
183
- }
184
- }
@@ -1,248 +0,0 @@
1
- /**
2
- * Counterfactual Analysis
3
- * What-if analysis using knowledge graph and decision history
4
- */
5
-
6
- import type { Logger } from 'pino'
7
- import type { InMemoryKnowledgeGraph } from '@/knowledge/graph/memory-graph'
8
- import type { CollectionManager } from '@/memory/chroma/collection-manager'
9
- import type { EmbeddingProvider } from '@/memory/chroma/embeddings'
10
-
11
- export interface WhatIfScenario {
12
- change: string
13
- affectedDecisions: Array<{
14
- id: string
15
- decision: string
16
- impact: 'high' | 'medium' | 'low'
17
- reason: string
18
- }>
19
- affectedTechnologies: string[]
20
- riskLevel: 'high' | 'medium' | 'low'
21
- summary: string
22
- }
23
-
24
- export class CounterfactualAnalyzer {
25
- private logger: Logger
26
- private graph: InMemoryKnowledgeGraph | null
27
- private collections: CollectionManager
28
- private embeddings?: EmbeddingProvider
29
-
30
- constructor(
31
- logger: Logger,
32
- graph: InMemoryKnowledgeGraph | null,
33
- collections: CollectionManager,
34
- embeddings?: EmbeddingProvider
35
- ) {
36
- this.logger = logger.child({ component: 'counterfactual' })
37
- this.graph = graph
38
- this.collections = collections
39
- this.embeddings = embeddings
40
- }
41
-
42
- /**
43
- * Analyze what would happen if a technology/decision changed
44
- */
45
- async whatIf(change: string, options: {
46
- project?: string
47
- maxResults?: number
48
- } = {}): Promise<WhatIfScenario> {
49
- const { project, maxResults = 20 } = options
50
-
51
- // Find decisions related to the change
52
- const relatedDecisions = await this.findRelatedDecisions(change, project, maxResults)
53
-
54
- // Find affected technologies via knowledge graph
55
- const affectedTechnologies = this.findAffectedTechnologies(change)
56
-
57
- // Assess impact on each decision
58
- const affectedDecisions = relatedDecisions.map(d => ({
59
- id: d.id,
60
- decision: d.content.slice(0, 200),
61
- impact: this.assessImpact(d, change),
62
- reason: this.explainImpact(d, change)
63
- }))
64
-
65
- // Determine overall risk
66
- const riskLevel = this.calculateRisk(affectedDecisions)
67
-
68
- // Build summary
69
- const summary = this.buildSummary(change, affectedDecisions, affectedTechnologies, riskLevel)
70
-
71
- return {
72
- change,
73
- affectedDecisions,
74
- affectedTechnologies,
75
- riskLevel,
76
- summary
77
- }
78
- }
79
-
80
- private async findRelatedDecisions(
81
- change: string,
82
- project?: string,
83
- limit: number = 20
84
- ): Promise<Array<{ id: string; content: string; metadata: Record<string, any>; similarity: number }>> {
85
- try {
86
- const collection = await this.collections.getDecisions()
87
-
88
- const where: any = project ? { project: { $eq: project } } : undefined
89
-
90
- let results: any
91
-
92
- if (this.embeddings) {
93
- const embedding = await this.embeddings.generate(change)
94
- results = await collection.query({
95
- queryEmbeddings: [embedding],
96
- nResults: limit,
97
- where,
98
- include: ['documents', 'metadatas', 'distances']
99
- })
100
- } else {
101
- results = await collection.query({
102
- queryTexts: [change],
103
- nResults: limit,
104
- where,
105
- include: ['documents', 'metadatas', 'distances']
106
- })
107
- }
108
-
109
- if (!results.ids || !results.ids[0]) return []
110
-
111
- const processed: Array<{ id: string; content: string; metadata: Record<string, any>; similarity: number }> = []
112
- const ids = results.ids[0]
113
- const documents = results.documents?.[0] || []
114
- const metadatas = results.metadatas?.[0] || []
115
- const distances = results.distances?.[0] || []
116
-
117
- for (let i = 0; i < ids.length; i++) {
118
- const similarity = 1 - (distances[i] || 0)
119
- if (similarity < 0.2) continue
120
-
121
- processed.push({
122
- id: ids[i],
123
- content: documents[i] || '',
124
- metadata: metadatas[i] || {},
125
- similarity
126
- })
127
- }
128
-
129
- return processed
130
- } catch (error) {
131
- this.logger.warn({ error, change }, 'Failed to find related decisions for what-if')
132
- return []
133
- }
134
- }
135
-
136
- private findAffectedTechnologies(change: string): string[] {
137
- if (!this.graph) return []
138
-
139
- const technologies: string[] = []
140
-
141
- try {
142
- // Find nodes matching the change description
143
- const changeWords = change.toLowerCase().split(/\s+/)
144
-
145
- const matchingNodes = this.graph.findNodes({ type: 'technology' })
146
- .filter(node => {
147
- const name = node.name.toLowerCase()
148
- return changeWords.some(w => name.includes(w) || w.includes(name))
149
- })
150
-
151
- for (const node of matchingNodes) {
152
- technologies.push(node.name)
153
-
154
- // Traverse to find connected technologies
155
- const connected = this.graph.traverse(node.id, {
156
- maxDepth: 2,
157
- nodeTypes: ['technology']
158
- })
159
-
160
- for (const connNode of connected) {
161
- if (connNode.id !== node.id && !technologies.includes(connNode.name)) {
162
- technologies.push(connNode.name)
163
- }
164
- }
165
- }
166
- } catch (error) {
167
- this.logger.debug({ error }, 'Failed to traverse knowledge graph')
168
- }
169
-
170
- return technologies.slice(0, 20)
171
- }
172
-
173
- private assessImpact(
174
- decision: { content: string; metadata: Record<string, any>; similarity: number },
175
- change: string
176
- ): 'high' | 'medium' | 'low' {
177
- // High similarity = high impact
178
- if (decision.similarity > 0.7) return 'high'
179
- if (decision.similarity > 0.5) return 'medium'
180
-
181
- // Check if the decision directly references the change topic
182
- const changeTerms = change.toLowerCase().split(/\s+/)
183
- const content = decision.content.toLowerCase()
184
- const directMentions = changeTerms.filter(t => t.length > 3 && content.includes(t)).length
185
-
186
- if (directMentions >= 3) return 'high'
187
- if (directMentions >= 1) return 'medium'
188
-
189
- return 'low'
190
- }
191
-
192
- private explainImpact(
193
- decision: { content: string; metadata: Record<string, any> },
194
- _change: string
195
- ): string {
196
- const reasoning = decision.metadata.reasoning || ''
197
- const context = decision.metadata.context || ''
198
-
199
- if (reasoning) {
200
- return `This decision was based on: "${reasoning.slice(0, 100)}..." which may need revision.`
201
- }
202
-
203
- if (context) {
204
- return `Made in context: "${context.slice(0, 100)}..." which would be affected.`
205
- }
206
-
207
- return 'This decision may need to be reconsidered.'
208
- }
209
-
210
- private calculateRisk(
211
- affectedDecisions: Array<{ impact: 'high' | 'medium' | 'low' }>
212
- ): 'high' | 'medium' | 'low' {
213
- const highCount = affectedDecisions.filter(d => d.impact === 'high').length
214
- const medCount = affectedDecisions.filter(d => d.impact === 'medium').length
215
-
216
- if (highCount >= 3) return 'high'
217
- if (highCount >= 1 || medCount >= 5) return 'medium'
218
- return 'low'
219
- }
220
-
221
- private buildSummary(
222
- change: string,
223
- affectedDecisions: Array<{ impact: string }>,
224
- affectedTechnologies: string[],
225
- riskLevel: string
226
- ): string {
227
- const parts: string[] = []
228
-
229
- parts.push(`## What-If Analysis: "${change}"`)
230
- parts.push('')
231
- parts.push(`**Risk Level:** ${riskLevel.toUpperCase()}`)
232
- parts.push(`**Affected Decisions:** ${affectedDecisions.length}`)
233
-
234
- const highImpact = affectedDecisions.filter(d => d.impact === 'high').length
235
- const medImpact = affectedDecisions.filter(d => d.impact === 'medium').length
236
- const lowImpact = affectedDecisions.filter(d => d.impact === 'low').length
237
-
238
- parts.push(` - High impact: ${highImpact}`)
239
- parts.push(` - Medium impact: ${medImpact}`)
240
- parts.push(` - Low impact: ${lowImpact}`)
241
-
242
- if (affectedTechnologies.length > 0) {
243
- parts.push(`\n**Affected Technologies:** ${affectedTechnologies.join(', ')}`)
244
- }
245
-
246
- return parts.join('\n')
247
- }
248
- }
@@ -1,167 +0,0 @@
1
- /**
2
- * Result Synthesizer
3
- * Combines multiple retrieved sources into coherent answers using heuristics
4
- */
5
-
6
- import type { Logger } from 'pino'
7
- import type { ChainResult } from './chain-retrieval'
8
-
9
- export interface SynthesizedResult {
10
- summary: string
11
- sources: Array<{
12
- id: string
13
- relevance: number
14
- excerpt: string
15
- }>
16
- confidence: number
17
- themes: string[]
18
- }
19
-
20
- export class ResultSynthesizer {
21
- constructor(_logger: Logger) {
22
- // Logger available for future use
23
- }
24
-
25
- /**
26
- * Synthesize multiple sources into a coherent answer
27
- */
28
- synthesize(query: string, results: ChainResult[]): SynthesizedResult {
29
- if (results.length === 0) {
30
- return {
31
- summary: 'No relevant information found.',
32
- sources: [],
33
- confidence: 0,
34
- themes: []
35
- }
36
- }
37
-
38
- // Sort by relevance
39
- const sorted = [...results].sort((a, b) => b.similarity - a.similarity)
40
-
41
- // Extract themes
42
- const themes = this.extractThemes(sorted)
43
-
44
- // Build summary from top results
45
- const summary = this.buildSummary(query, sorted, themes)
46
-
47
- // Calculate confidence based on result quality
48
- const confidence = this.calculateConfidence(sorted)
49
-
50
- // Build source list
51
- const sources = sorted.slice(0, 10).map(r => ({
52
- id: r.id,
53
- relevance: r.similarity,
54
- excerpt: r.content.slice(0, 200)
55
- }))
56
-
57
- return {
58
- summary,
59
- sources,
60
- confidence,
61
- themes
62
- }
63
- }
64
-
65
- private extractThemes(results: ChainResult[]): string[] {
66
- const termFreq = new Map<string, number>()
67
-
68
- for (const result of results) {
69
- const words = result.content
70
- .toLowerCase()
71
- .replace(/[^a-z0-9\s]/g, ' ')
72
- .split(/\s+/)
73
- .filter(w => w.length > 3 && !this.isCommonWord(w))
74
-
75
- const seen = new Set<string>()
76
- for (const word of words) {
77
- if (seen.has(word)) continue
78
- seen.add(word)
79
- termFreq.set(word, (termFreq.get(word) || 0) + 1)
80
- }
81
- }
82
-
83
- // Return terms appearing in multiple results
84
- return Array.from(termFreq.entries())
85
- .filter(([_, count]) => count >= Math.max(2, results.length * 0.3))
86
- .sort((a, b) => b[1] - a[1])
87
- .slice(0, 10)
88
- .map(([term]) => term)
89
- }
90
-
91
- private buildSummary(_query: string, results: ChainResult[], themes: string[]): string {
92
- const parts: string[] = []
93
-
94
- // Group by type
95
- const decisions = results.filter(r => r.metadata.type === 'decision' || r.metadata.source === 'remember_decision')
96
- const patterns = results.filter(r => r.metadata.type === 'pattern' || r.metadata.source === 'recognize_pattern')
97
- const corrections = results.filter(r => r.metadata.type === 'correction' || r.metadata.source === 'record_correction')
98
- const other = results.filter(r =>
99
- !decisions.includes(r) && !patterns.includes(r) && !corrections.includes(r)
100
- )
101
-
102
- if (decisions.length > 0) {
103
- parts.push(`**Decisions (${decisions.length}):**`)
104
- for (const d of decisions.slice(0, 3)) {
105
- parts.push(`- ${d.content.slice(0, 150)}`)
106
- }
107
- }
108
-
109
- if (patterns.length > 0) {
110
- parts.push(`\n**Patterns (${patterns.length}):**`)
111
- for (const p of patterns.slice(0, 3)) {
112
- parts.push(`- ${p.content.slice(0, 150)}`)
113
- }
114
- }
115
-
116
- if (corrections.length > 0) {
117
- parts.push(`\n**Corrections (${corrections.length}):**`)
118
- for (const c of corrections.slice(0, 3)) {
119
- parts.push(`- ${c.content.slice(0, 150)}`)
120
- }
121
- }
122
-
123
- if (other.length > 0) {
124
- parts.push(`\n**Other (${other.length}):**`)
125
- for (const o of other.slice(0, 3)) {
126
- parts.push(`- ${o.content.slice(0, 150)}`)
127
- }
128
- }
129
-
130
- if (themes.length > 0) {
131
- parts.push(`\n**Key themes:** ${themes.join(', ')}`)
132
- }
133
-
134
- return parts.join('\n')
135
- }
136
-
137
- private calculateConfidence(results: ChainResult[]): number {
138
- if (results.length === 0) return 0
139
-
140
- // Average similarity of top results
141
- const top = results.slice(0, 5)
142
- const avgSimilarity = top.reduce((sum, r) => sum + r.similarity, 0) / top.length
143
-
144
- // Factor in number of results
145
- const countFactor = Math.min(results.length / 5, 1) // More results = higher confidence, up to 5
146
-
147
- return Math.min(avgSimilarity * countFactor, 1)
148
- }
149
-
150
- private isCommonWord(word: string): boolean {
151
- const common = new Set([
152
- 'the', 'and', 'for', 'are', 'but', 'not', 'you', 'all', 'can',
153
- 'was', 'one', 'our', 'out', 'has', 'had', 'been', 'have', 'with',
154
- 'this', 'that', 'from', 'they', 'will', 'would', 'there', 'their',
155
- 'what', 'about', 'which', 'when', 'make', 'like', 'time', 'just',
156
- 'know', 'take', 'into', 'year', 'your', 'good', 'some', 'could',
157
- 'them', 'than', 'other', 'then', 'also', 'back', 'after', 'work',
158
- 'first', 'well', 'even', 'most', 'find', 'here', 'thing', 'many',
159
- 'should', 'because', 'does', 'each', 'much', 'before', 'must',
160
- 'through', 'being', 'using', 'used', 'decision', 'decided',
161
- 'recommend', 'instead', 'project', 'context', 'reasoning', 'pattern',
162
- 'correction', 'original', 'still', 'between', 'think', 'over',
163
- 'come', 'only', 'give', 'look', 'people'
164
- ])
165
- return common.has(word)
166
- }
167
- }