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,14 +1,9 @@
1
- import { createLogger } from '@/utils/logger'
2
- import { resolveHomePath } from '@/config/home'
3
1
  import { ensureHomeDirectory } from '@/cli/auto-setup'
4
- import { SetupWizard } from './wizard'
5
- import { renderBanner, typewrite, transition, box, errorText, theme } from '@/cli/ui/index.js'
2
+ import { renderBanner, theme } from '@/cli/ui/index.js'
6
3
  import { readFileSync } from 'node:fs'
7
4
  import { resolve, dirname } from 'node:path'
8
5
  import { fileURLToPath } from 'node:url'
9
6
 
10
- export * from './wizard'
11
-
12
7
  const __filename = fileURLToPath(import.meta.url)
13
8
  const __dirname = dirname(__filename)
14
9
  const PACKAGE_ROOT = resolve(__dirname, '..', '..')
@@ -22,27 +17,22 @@ function getVersion(): string {
22
17
  }
23
18
  }
24
19
 
20
+ /**
21
+ * Phase 30: Zero-config setup — no wizard, no prompts.
22
+ * Just ensures the home directory exists with sensible defaults.
23
+ */
25
24
  export async function runSetup() {
26
- ensureHomeDirectory()
27
-
28
25
  const version = getVersion()
29
26
  console.log()
30
27
  console.log(renderBanner(version))
31
28
  console.log()
32
29
 
33
- await typewrite(theme.dim('Welcome! Let\'s configure Claude Brain for your system.'))
34
- await transition(400)
35
-
36
- const logger = createLogger('info', resolveHomePath('./logs/setup.log'))
37
-
38
- try {
39
- const wizard = new SetupWizard(logger)
40
- const answers = await wizard.run()
41
- await wizard.applyConfiguration(answers)
30
+ ensureHomeDirectory()
42
31
 
43
- } catch (error) {
44
- console.log()
45
- console.log(box(errorText(`Setup failed: ${error instanceof Error ? error.message : String(error)}`), 'Error'))
46
- process.exit(1)
47
- }
32
+ console.log()
33
+ console.log(theme.bold('Setup complete! No configuration needed.'))
34
+ console.log()
35
+ console.log(` ${theme.primary('Next:')} ${theme.dim('claude-brain start')}`)
36
+ console.log(` ${theme.dim('ChromaDB is optional — SQLite FTS5 works out of the box.')}`)
37
+ console.log()
48
38
  }
@@ -590,7 +590,7 @@ export const TOOLS = {
590
590
  */
591
591
  BRAIN: {
592
592
  name: 'brain',
593
- description: 'Your persistent memory. Tell it decisions, ask it questions, or update/delete past notes. Most context is captured automatically — only call this for intentional storage or recall.',
593
+ description: 'Your persistent memory. Tell it decisions, ask it questions, or update/delete past notes. Most context is captured automatically — only call this for intentional storage or recall. Search returns compact summaries — use brain("details {ID}") for full context.',
594
594
  inputSchema: {
595
595
  type: 'object' as const,
596
596
  properties: {
@@ -1,159 +0,0 @@
1
- /**
2
- * Project Affinity
3
- * Computes similarity between projects based on decisions, patterns, and tech stack
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 ProjectAffinity {
11
- projectA: string
12
- projectB: string
13
- similarity: number
14
- sharedTopics: string[]
15
- sharedTechnologies: string[]
16
- decisionOverlap: number
17
- }
18
-
19
- export class AffinityCalculator {
20
- private logger: Logger
21
- private collections: CollectionManager
22
- constructor(logger: Logger, collections: CollectionManager, _embeddings?: EmbeddingProvider) {
23
- this.logger = logger.child({ component: 'affinity-calculator' })
24
- this.collections = collections
25
- }
26
-
27
- /**
28
- * Calculate affinity between all pairs of projects
29
- */
30
- async calculateAffinities(options: {
31
- minDecisions?: number
32
- } = {}): Promise<ProjectAffinity[]> {
33
- const { minDecisions = 2 } = options
34
-
35
- // Get all decisions grouped by project
36
- const byProject = await this.getDecisionsByProject()
37
-
38
- // Filter out projects with too few decisions
39
- const projects = Array.from(byProject.entries())
40
- .filter(([_, decisions]) => decisions.length >= minDecisions)
41
-
42
- const affinities: ProjectAffinity[] = []
43
-
44
- // Compare all pairs
45
- for (let i = 0; i < projects.length; i++) {
46
- for (let j = i + 1; j < projects.length; j++) {
47
- const [projectA, decisionsA] = projects[i]!
48
- const [projectB, decisionsB] = projects[j]!
49
-
50
- const affinity = this.compareProjects(projectA, decisionsA, projectB, decisionsB)
51
- if (affinity.similarity > 0.1) {
52
- affinities.push(affinity)
53
- }
54
- }
55
- }
56
-
57
- return affinities.sort((a, b) => b.similarity - a.similarity)
58
- }
59
-
60
- /**
61
- * Find most similar projects to a given project
62
- */
63
- async findSimilarProjects(project: string, limit: number = 5): Promise<ProjectAffinity[]> {
64
- const affinities = await this.calculateAffinities()
65
-
66
- return affinities
67
- .filter(a => a.projectA === project || a.projectB === project)
68
- .sort((a, b) => b.similarity - a.similarity)
69
- .slice(0, limit)
70
- }
71
-
72
- private async getDecisionsByProject(): Promise<Map<string, Array<{ id: string; content: string; tags: string[] }>>> {
73
- try {
74
- const collection = await this.collections.getDecisions()
75
-
76
- const results = await collection.get({
77
- include: ['documents', 'metadatas']
78
- })
79
-
80
- const byProject = new Map<string, Array<{ id: string; content: string; tags: string[] }>>()
81
-
82
- for (let i = 0; i < results.ids.length; i++) {
83
- const project = (results.metadatas![i] as any)?.project || ''
84
- if (!project) continue
85
-
86
- if (!byProject.has(project)) byProject.set(project, [])
87
-
88
- byProject.get(project)!.push({
89
- id: results.ids[i]!,
90
- content: results.documents![i] as string || '',
91
- tags: String((results.metadatas![i] as any)?.tags || '').split(',').filter(t => t.trim())
92
- })
93
- }
94
-
95
- return byProject
96
- } catch (error) {
97
- this.logger.warn({ error }, 'Failed to get decisions by project')
98
- return new Map()
99
- }
100
- }
101
-
102
- private compareProjects(
103
- projectA: string,
104
- decisionsA: Array<{ id: string; content: string; tags: string[] }>,
105
- projectB: string,
106
- decisionsB: Array<{ id: string; content: string; tags: string[] }>
107
- ): ProjectAffinity {
108
- // Term-based similarity
109
- const termsA = this.extractAllTerms(decisionsA)
110
- const termsB = this.extractAllTerms(decisionsB)
111
-
112
- const sharedTopics = Array.from(termsA).filter(t => termsB.has(t))
113
- const allTopics = new Set([...termsA, ...termsB])
114
-
115
- const topicSimilarity = allTopics.size > 0 ? sharedTopics.length / allTopics.size : 0
116
-
117
- // Tag-based similarity
118
- const tagsA = new Set(decisionsA.flatMap(d => d.tags))
119
- const tagsB = new Set(decisionsB.flatMap(d => d.tags))
120
-
121
- const sharedTags = Array.from(tagsA).filter(t => tagsB.has(t))
122
- const allTags = new Set([...tagsA, ...tagsB])
123
- const tagSimilarity = allTags.size > 0 ? sharedTags.length / allTags.size : 0
124
-
125
- // Combined similarity
126
- const similarity = topicSimilarity * 0.7 + tagSimilarity * 0.3
127
-
128
- return {
129
- projectA,
130
- projectB,
131
- similarity,
132
- sharedTopics: sharedTopics.slice(0, 20),
133
- sharedTechnologies: sharedTags.slice(0, 10),
134
- decisionOverlap: sharedTopics.length
135
- }
136
- }
137
-
138
- private extractAllTerms(decisions: Array<{ content: string }>): Set<string> {
139
- const terms = new Set<string>()
140
- const stopWords = new Set([
141
- 'the', 'and', 'for', 'are', 'but', 'not', 'all', 'can', 'was',
142
- 'has', 'had', 'been', 'have', 'with', 'this', 'that', 'from',
143
- 'use', 'using', 'used', 'will', 'would', 'should', 'also',
144
- 'decision', 'decided', 'recommend', 'instead', 'because',
145
- 'project', 'context', 'reasoning', 'about', 'which', 'when'
146
- ])
147
-
148
- for (const d of decisions) {
149
- const words = d.content.toLowerCase().replace(/[^a-z0-9\s]/g, ' ').split(/\s+/)
150
- for (const w of words) {
151
- if (w.length > 3 && !stopWords.has(w)) {
152
- terms.add(w)
153
- }
154
- }
155
- }
156
-
157
- return terms
158
- }
159
- }
@@ -1,201 +0,0 @@
1
- /**
2
- * Knowledge Transfer
3
- * Transfer knowledge from one project to another based on similarity
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 TransferableKnowledge {
11
- sourceProject: string
12
- targetProject: string
13
- items: TransferItem[]
14
- affinityScore: number
15
- }
16
-
17
- export interface TransferItem {
18
- type: 'decision' | 'pattern' | 'correction'
19
- id: string
20
- content: string
21
- relevance: number
22
- reasoning: string
23
- }
24
-
25
- export class KnowledgeTransfer {
26
- private logger: Logger
27
- private collections: CollectionManager
28
- private embeddings?: EmbeddingProvider
29
-
30
- constructor(logger: Logger, collections: CollectionManager, embeddings?: EmbeddingProvider) {
31
- this.logger = logger.child({ component: 'knowledge-transfer' })
32
- this.collections = collections
33
- this.embeddings = embeddings
34
- }
35
-
36
- /**
37
- * Find transferable knowledge from source project to apply to target project
38
- */
39
- async findTransferable(sourceProject: string, targetProject: string, options: {
40
- limit?: number
41
- minRelevance?: number
42
- } = {}): Promise<TransferableKnowledge> {
43
- const { limit = 10, minRelevance = 0.4 } = options
44
-
45
- // Get target project's decisions to understand its context
46
- const targetContext = await this.getProjectContext(targetProject)
47
-
48
- if (!targetContext) {
49
- return {
50
- sourceProject,
51
- targetProject,
52
- items: [],
53
- affinityScore: 0
54
- }
55
- }
56
-
57
- // Search source project for relevant knowledge
58
- const items: TransferItem[] = []
59
-
60
- // Decisions
61
- const decisions = await this.searchSourceProject(
62
- sourceProject,
63
- targetContext,
64
- 'decisions',
65
- limit,
66
- minRelevance
67
- )
68
- items.push(...decisions)
69
-
70
- // Patterns
71
- const patterns = await this.searchSourceProject(
72
- sourceProject,
73
- targetContext,
74
- 'patterns',
75
- limit,
76
- minRelevance
77
- )
78
- items.push(...patterns)
79
-
80
- // Corrections
81
- const corrections = await this.searchSourceProject(
82
- sourceProject,
83
- targetContext,
84
- 'corrections',
85
- limit,
86
- minRelevance
87
- )
88
- items.push(...corrections)
89
-
90
- // Sort by relevance
91
- items.sort((a, b) => b.relevance - a.relevance)
92
-
93
- // Calculate affinity
94
- const affinityScore = items.length > 0
95
- ? items.reduce((sum, i) => sum + i.relevance, 0) / items.length
96
- : 0
97
-
98
- return {
99
- sourceProject,
100
- targetProject,
101
- items: items.slice(0, limit),
102
- affinityScore
103
- }
104
- }
105
-
106
- private async getProjectContext(project: string): Promise<string | null> {
107
- try {
108
- const collection = await this.collections.getDecisions()
109
-
110
- const results = await collection.get({
111
- where: { project: { $eq: project } },
112
- include: ['documents'],
113
- limit: 20
114
- })
115
-
116
- if (results.ids.length === 0) return null
117
-
118
- // Combine recent decisions into a context string
119
- return results.documents!
120
- .slice(0, 10)
121
- .map(d => d as string)
122
- .join(' ')
123
- } catch (error) {
124
- this.logger.debug({ error, project }, 'Failed to get project context')
125
- return null
126
- }
127
- }
128
-
129
- private async searchSourceProject(
130
- sourceProject: string,
131
- targetContext: string,
132
- collectionType: 'decisions' | 'patterns' | 'corrections',
133
- limit: number,
134
- minRelevance: number
135
- ): Promise<TransferItem[]> {
136
- try {
137
- let collection
138
- let type: 'decision' | 'pattern' | 'correction'
139
-
140
- switch (collectionType) {
141
- case 'decisions':
142
- collection = await this.collections.getDecisions()
143
- type = 'decision'
144
- break
145
- case 'patterns':
146
- collection = await this.collections.getPatterns()
147
- type = 'pattern'
148
- break
149
- case 'corrections':
150
- collection = await this.collections.getCorrections()
151
- type = 'correction'
152
- break
153
- }
154
-
155
- const where: any = { project: { $eq: sourceProject } }
156
-
157
- let results: any
158
-
159
- if (this.embeddings) {
160
- // Use truncated target context for embedding
161
- const queryText = targetContext.slice(0, 500)
162
- const embedding = await this.embeddings.generate(queryText)
163
- results = await collection.query({
164
- queryEmbeddings: [embedding],
165
- nResults: limit,
166
- where,
167
- include: ['documents', 'metadatas', 'distances']
168
- })
169
- } else {
170
- results = await collection.query({
171
- queryTexts: [targetContext.slice(0, 500)],
172
- nResults: limit,
173
- where,
174
- include: ['documents', 'metadatas', 'distances']
175
- })
176
- }
177
-
178
- if (!results.ids || !results.ids[0]) return []
179
-
180
- const items: TransferItem[] = []
181
-
182
- for (let i = 0; i < results.ids[0].length; i++) {
183
- const similarity = 1 - (results.distances?.[0]?.[i] || 0)
184
- if (similarity < minRelevance) continue
185
-
186
- items.push({
187
- type,
188
- id: results.ids[0][i],
189
- content: results.documents?.[0]?.[i] || '',
190
- relevance: similarity,
191
- reasoning: `Relevant ${type} from ${sourceProject} (${Math.round(similarity * 100)}% match)`
192
- })
193
- }
194
-
195
- return items
196
- } catch (error) {
197
- this.logger.debug({ error, sourceProject, collectionType }, 'Failed to search source project')
198
- return []
199
- }
200
- }
201
- }
@@ -1,198 +0,0 @@
1
- /**
2
- * Context Anticipator
3
- * Anticipates what context will be needed based on current activity
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 AnticipatedContext {
11
- topic: string
12
- relevance: number
13
- relatedDecisions: number
14
- suggestedQueries: string[]
15
- }
16
-
17
- export class ContextAnticipator {
18
- private logger: Logger
19
- private collections: CollectionManager
20
- private embeddings?: EmbeddingProvider
21
- private recentTopics: Array<{ topic: string; timestamp: number }> = []
22
-
23
- constructor(logger: Logger, collections: CollectionManager, embeddings?: EmbeddingProvider) {
24
- this.logger = logger.child({ component: 'context-anticipator' })
25
- this.collections = collections
26
- this.embeddings = embeddings
27
- }
28
-
29
- /**
30
- * Record a topic being worked on for anticipation
31
- */
32
- recordActivity(topic: string): void {
33
- this.recentTopics.push({ topic, timestamp: Date.now() })
34
- // Keep last 50 topics
35
- if (this.recentTopics.length > 50) {
36
- this.recentTopics = this.recentTopics.slice(-50)
37
- }
38
- }
39
-
40
- /**
41
- * Anticipate what context might be needed next
42
- */
43
- async anticipate(currentTask: string, options: {
44
- project?: string
45
- limit?: number
46
- } = {}): Promise<AnticipatedContext[]> {
47
- const { project, limit = 5 } = options
48
-
49
- // Get patterns of what typically follows similar tasks
50
- const followUpTopics = await this.findFollowUpPatterns(currentTask, project)
51
-
52
- // Get related but not-yet-accessed topics
53
- const relatedTopics = await this.findRelatedTopics(currentTask, project)
54
-
55
- // Merge and rank
56
- const merged = new Map<string, AnticipatedContext>()
57
-
58
- for (const t of followUpTopics) {
59
- merged.set(t.topic, t)
60
- }
61
-
62
- for (const t of relatedTopics) {
63
- if (merged.has(t.topic)) {
64
- const existing = merged.get(t.topic)!
65
- existing.relevance = Math.max(existing.relevance, t.relevance)
66
- existing.relatedDecisions = Math.max(existing.relatedDecisions, t.relatedDecisions)
67
- } else {
68
- merged.set(t.topic, t)
69
- }
70
- }
71
-
72
- return Array.from(merged.values())
73
- .sort((a, b) => b.relevance - a.relevance)
74
- .slice(0, limit)
75
- }
76
-
77
- private async findFollowUpPatterns(
78
- currentTask: string,
79
- project?: string
80
- ): Promise<AnticipatedContext[]> {
81
- try {
82
- const collection = await this.collections.getDecisions()
83
-
84
- const where: any = project ? { project: { $eq: project } } : undefined
85
-
86
- let results: any
87
-
88
- if (this.embeddings) {
89
- const embedding = await this.embeddings.generate(currentTask)
90
- results = await collection.query({
91
- queryEmbeddings: [embedding],
92
- nResults: 10,
93
- where,
94
- include: ['documents', 'metadatas', 'distances']
95
- })
96
- } else {
97
- results = await collection.query({
98
- queryTexts: [currentTask],
99
- nResults: 10,
100
- where,
101
- include: ['documents', 'metadatas', 'distances']
102
- })
103
- }
104
-
105
- if (!results.ids || !results.ids[0]) return []
106
-
107
- // Extract unique topics from the results
108
- const topicCounts = new Map<string, { count: number; similarity: number }>()
109
- const metadatas = results.metadatas?.[0] || []
110
- const distances = results.distances?.[0] || []
111
-
112
- for (let i = 0; i < metadatas.length; i++) {
113
- const tags = String((metadatas[i] as any)?.tags || '').split(',').filter((t: string) => t.trim())
114
- const similarity = 1 - (distances[i] || 0)
115
-
116
- for (const tag of tags) {
117
- const trimmed = tag.trim().toLowerCase()
118
- if (!trimmed) continue
119
-
120
- const existing = topicCounts.get(trimmed)
121
- if (existing) {
122
- existing.count++
123
- existing.similarity = Math.max(existing.similarity, similarity)
124
- } else {
125
- topicCounts.set(trimmed, { count: 1, similarity })
126
- }
127
- }
128
- }
129
-
130
- return Array.from(topicCounts.entries()).map(([topic, data]) => ({
131
- topic,
132
- relevance: data.similarity * (1 + Math.log(data.count + 1) / 5),
133
- relatedDecisions: data.count,
134
- suggestedQueries: [`What decisions were made about ${topic}?`, `Show patterns for ${topic}`]
135
- }))
136
- } catch (error) {
137
- this.logger.debug({ error }, 'Failed to find follow-up patterns')
138
- return []
139
- }
140
- }
141
-
142
- private async findRelatedTopics(
143
- currentTask: string,
144
- project?: string
145
- ): Promise<AnticipatedContext[]> {
146
- try {
147
- const collection = await this.collections.getPatterns()
148
-
149
- const where: any = project ? { project: { $eq: project } } : undefined
150
-
151
- let results: any
152
-
153
- if (this.embeddings) {
154
- const embedding = await this.embeddings.generate(currentTask)
155
- results = await collection.query({
156
- queryEmbeddings: [embedding],
157
- nResults: 5,
158
- where,
159
- include: ['documents', 'metadatas', 'distances']
160
- })
161
- } else {
162
- results = await collection.query({
163
- queryTexts: [currentTask],
164
- nResults: 5,
165
- where,
166
- include: ['documents', 'metadatas', 'distances']
167
- })
168
- }
169
-
170
- if (!results.ids || !results.ids[0]) return []
171
-
172
- const topics: AnticipatedContext[] = []
173
- const documents = results.documents?.[0] || []
174
- const metadatas = results.metadatas?.[0] || []
175
- const distances = results.distances?.[0] || []
176
-
177
- for (let i = 0; i < documents.length; i++) {
178
- const similarity = 1 - (distances[i] || 0)
179
- if (similarity < 0.3) continue
180
-
181
- const content = (documents[i] || '').slice(0, 100)
182
- const patternType = (metadatas[i] as any)?.pattern_type || 'unknown'
183
-
184
- topics.push({
185
- topic: `${patternType}: ${content}`,
186
- relevance: similarity,
187
- relatedDecisions: 1,
188
- suggestedQueries: [`Show ${patternType} patterns related to this`]
189
- })
190
- }
191
-
192
- return topics
193
- } catch (error) {
194
- this.logger.debug({ error }, 'Failed to find related topics')
195
- return []
196
- }
197
- }
198
- }