claude-brain 0.3.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 (200) hide show
  1. package/README.md +157 -0
  2. package/VERSION +1 -0
  3. package/assets/CLAUDE.md +307 -0
  4. package/bunfig.toml +8 -0
  5. package/package.json +74 -0
  6. package/src/automation/auto-context.ts +240 -0
  7. package/src/automation/decision-detector.ts +452 -0
  8. package/src/automation/index.ts +11 -0
  9. package/src/automation/proactive-recall.ts +373 -0
  10. package/src/automation/project-detector.ts +297 -0
  11. package/src/cli/auto-setup.ts +74 -0
  12. package/src/cli/bin.ts +110 -0
  13. package/src/cli/commands/install-mcp.ts +50 -0
  14. package/src/cli/commands/serve.ts +129 -0
  15. package/src/cli/diagnose.ts +4 -0
  16. package/src/cli/health-check.ts +4 -0
  17. package/src/cli/migrate-chroma.ts +106 -0
  18. package/src/cli/setup.ts +4 -0
  19. package/src/config/defaults.ts +47 -0
  20. package/src/config/home.ts +55 -0
  21. package/src/config/index.ts +7 -0
  22. package/src/config/loader.ts +166 -0
  23. package/src/config/migration.ts +76 -0
  24. package/src/config/schema.ts +257 -0
  25. package/src/config/validator.ts +184 -0
  26. package/src/config/watcher.ts +86 -0
  27. package/src/context/assembler.ts +398 -0
  28. package/src/context/cache-manager.ts +101 -0
  29. package/src/context/formatter.ts +84 -0
  30. package/src/context/hierarchy.ts +85 -0
  31. package/src/context/index.ts +83 -0
  32. package/src/context/progress-tracker.ts +174 -0
  33. package/src/context/standards-manager.ts +267 -0
  34. package/src/context/types.ts +252 -0
  35. package/src/context/validator.ts +58 -0
  36. package/src/cross-project/affinity.ts +162 -0
  37. package/src/cross-project/generalizer.ts +283 -0
  38. package/src/cross-project/index.ts +13 -0
  39. package/src/cross-project/transfer.ts +201 -0
  40. package/src/diagnostics/index.ts +123 -0
  41. package/src/health/index.ts +229 -0
  42. package/src/index.ts +7 -0
  43. package/src/knowledge/entity-extractor.ts +416 -0
  44. package/src/knowledge/graph/builder.ts +159 -0
  45. package/src/knowledge/graph/linker.ts +201 -0
  46. package/src/knowledge/graph/memory-graph.ts +359 -0
  47. package/src/knowledge/graph/schema.ts +99 -0
  48. package/src/knowledge/graph/search.ts +168 -0
  49. package/src/knowledge/relationship-extractor.ts +108 -0
  50. package/src/memory/chroma/client.ts +169 -0
  51. package/src/memory/chroma/collection-manager.ts +94 -0
  52. package/src/memory/chroma/config.ts +46 -0
  53. package/src/memory/chroma/embeddings.ts +153 -0
  54. package/src/memory/chroma/index.ts +82 -0
  55. package/src/memory/chroma/migration.ts +270 -0
  56. package/src/memory/chroma/schemas.ts +69 -0
  57. package/src/memory/chroma/search.ts +315 -0
  58. package/src/memory/chroma/store.ts +694 -0
  59. package/src/memory/consolidation/archiver.ts +164 -0
  60. package/src/memory/consolidation/merger.ts +186 -0
  61. package/src/memory/consolidation/scorer.ts +138 -0
  62. package/src/memory/context-builder.ts +236 -0
  63. package/src/memory/database.ts +169 -0
  64. package/src/memory/embedding-utils.ts +156 -0
  65. package/src/memory/embeddings.ts +226 -0
  66. package/src/memory/episodic/detector.ts +108 -0
  67. package/src/memory/episodic/manager.ts +334 -0
  68. package/src/memory/episodic/summarizer.ts +179 -0
  69. package/src/memory/episodic/types.ts +52 -0
  70. package/src/memory/index.ts +395 -0
  71. package/src/memory/knowledge-extractor.ts +455 -0
  72. package/src/memory/learning.ts +378 -0
  73. package/src/memory/patterns.ts +396 -0
  74. package/src/memory/schema.ts +56 -0
  75. package/src/memory/search.ts +309 -0
  76. package/src/memory/store.ts +344 -0
  77. package/src/memory/types.ts +121 -0
  78. package/src/optimization/index.ts +10 -0
  79. package/src/optimization/precompute.ts +202 -0
  80. package/src/optimization/semantic-cache.ts +207 -0
  81. package/src/orchestrator/coordinator.ts +272 -0
  82. package/src/orchestrator/decision-logger.ts +228 -0
  83. package/src/orchestrator/event-emitter.ts +198 -0
  84. package/src/orchestrator/event-queue.ts +184 -0
  85. package/src/orchestrator/handlers/base-handler.ts +70 -0
  86. package/src/orchestrator/handlers/context-handler.ts +73 -0
  87. package/src/orchestrator/handlers/decision-handler.ts +204 -0
  88. package/src/orchestrator/handlers/index.ts +10 -0
  89. package/src/orchestrator/handlers/status-handler.ts +131 -0
  90. package/src/orchestrator/handlers/task-handler.ts +171 -0
  91. package/src/orchestrator/index.ts +275 -0
  92. package/src/orchestrator/task-parser.ts +284 -0
  93. package/src/orchestrator/types.ts +98 -0
  94. package/src/phase12/index.ts +456 -0
  95. package/src/prediction/context-anticipator.ts +198 -0
  96. package/src/prediction/decision-predictor.ts +184 -0
  97. package/src/prediction/index.ts +13 -0
  98. package/src/prediction/recommender.ts +268 -0
  99. package/src/reasoning/chain-retrieval.ts +247 -0
  100. package/src/reasoning/counterfactual.ts +248 -0
  101. package/src/reasoning/index.ts +13 -0
  102. package/src/reasoning/synthesizer.ts +169 -0
  103. package/src/retrieval/bm25/index.ts +300 -0
  104. package/src/retrieval/bm25/tokenizer.ts +184 -0
  105. package/src/retrieval/feedback/adaptive.ts +223 -0
  106. package/src/retrieval/feedback/index.ts +16 -0
  107. package/src/retrieval/feedback/metrics.ts +223 -0
  108. package/src/retrieval/feedback/store.ts +283 -0
  109. package/src/retrieval/fusion/index.ts +194 -0
  110. package/src/retrieval/fusion/rrf.ts +163 -0
  111. package/src/retrieval/index.ts +12 -0
  112. package/src/retrieval/pipeline.ts +375 -0
  113. package/src/retrieval/query/expander.ts +198 -0
  114. package/src/retrieval/query/index.ts +27 -0
  115. package/src/retrieval/query/intent-classifier.ts +236 -0
  116. package/src/retrieval/query/temporal-parser.ts +295 -0
  117. package/src/retrieval/reranker/index.ts +188 -0
  118. package/src/retrieval/reranker/model.ts +95 -0
  119. package/src/retrieval/service.ts +125 -0
  120. package/src/retrieval/types.ts +162 -0
  121. package/src/scripts/health-check.ts +118 -0
  122. package/src/scripts/setup.ts +122 -0
  123. package/src/server/handlers/call-tool.ts +194 -0
  124. package/src/server/handlers/index.ts +9 -0
  125. package/src/server/handlers/list-tools.ts +18 -0
  126. package/src/server/handlers/tools/analyze-decision-evolution.ts +71 -0
  127. package/src/server/handlers/tools/auto-remember.ts +200 -0
  128. package/src/server/handlers/tools/create-project.ts +135 -0
  129. package/src/server/handlers/tools/detect-trends.ts +80 -0
  130. package/src/server/handlers/tools/find-cross-project-patterns.ts +73 -0
  131. package/src/server/handlers/tools/get-activity-log.ts +194 -0
  132. package/src/server/handlers/tools/get-code-standards.ts +124 -0
  133. package/src/server/handlers/tools/get-corrections.ts +154 -0
  134. package/src/server/handlers/tools/get-decision-timeline.ts +86 -0
  135. package/src/server/handlers/tools/get-episode.ts +93 -0
  136. package/src/server/handlers/tools/get-patterns.ts +158 -0
  137. package/src/server/handlers/tools/get-phase12-status.ts +63 -0
  138. package/src/server/handlers/tools/get-project-context.ts +75 -0
  139. package/src/server/handlers/tools/get-recommendations.ts +65 -0
  140. package/src/server/handlers/tools/index.ts +33 -0
  141. package/src/server/handlers/tools/init-project.ts +710 -0
  142. package/src/server/handlers/tools/list-episodes.ts +80 -0
  143. package/src/server/handlers/tools/list-projects.ts +125 -0
  144. package/src/server/handlers/tools/rate-memory.ts +95 -0
  145. package/src/server/handlers/tools/recall-similar.ts +87 -0
  146. package/src/server/handlers/tools/recognize-pattern.ts +126 -0
  147. package/src/server/handlers/tools/record-correction.ts +125 -0
  148. package/src/server/handlers/tools/remember-decision.ts +153 -0
  149. package/src/server/handlers/tools/schemas.ts +241 -0
  150. package/src/server/handlers/tools/search-knowledge-graph.ts +89 -0
  151. package/src/server/handlers/tools/smart-context.ts +124 -0
  152. package/src/server/handlers/tools/update-progress.ts +114 -0
  153. package/src/server/handlers/tools/what-if-analysis.ts +73 -0
  154. package/src/server/http-api.ts +474 -0
  155. package/src/server/index.ts +40 -0
  156. package/src/server/mcp-server.ts +283 -0
  157. package/src/server/providers/index.ts +7 -0
  158. package/src/server/providers/prompts.ts +327 -0
  159. package/src/server/providers/resources.ts +427 -0
  160. package/src/server/services.ts +388 -0
  161. package/src/server/types.ts +39 -0
  162. package/src/server/utils/error-handler.ts +155 -0
  163. package/src/server/utils/index.ts +13 -0
  164. package/src/server/utils/memory-indicator.ts +83 -0
  165. package/src/server/utils/request-context.ts +122 -0
  166. package/src/server/utils/response-formatter.ts +124 -0
  167. package/src/server/utils/validators.ts +210 -0
  168. package/src/setup/index.ts +22 -0
  169. package/src/setup/wizard.ts +321 -0
  170. package/src/temporal/evolution.ts +197 -0
  171. package/src/temporal/index.ts +16 -0
  172. package/src/temporal/query-processor.ts +190 -0
  173. package/src/temporal/timeline.ts +259 -0
  174. package/src/temporal/trends.ts +263 -0
  175. package/src/tools/index.ts +24 -0
  176. package/src/tools/registry.ts +106 -0
  177. package/src/tools/schemas.test.ts +30 -0
  178. package/src/tools/schemas.ts +907 -0
  179. package/src/tools/types.ts +412 -0
  180. package/src/utils/circuit-breaker.ts +130 -0
  181. package/src/utils/cleanup.ts +34 -0
  182. package/src/utils/error-handler.ts +132 -0
  183. package/src/utils/error-messages.ts +60 -0
  184. package/src/utils/fallback.ts +45 -0
  185. package/src/utils/index.ts +54 -0
  186. package/src/utils/logger-utils.ts +80 -0
  187. package/src/utils/logger.ts +88 -0
  188. package/src/utils/phase12-helper.ts +56 -0
  189. package/src/utils/retry.ts +94 -0
  190. package/src/utils/transaction.ts +63 -0
  191. package/src/vault/frontmatter.ts +264 -0
  192. package/src/vault/index.ts +318 -0
  193. package/src/vault/paths.ts +106 -0
  194. package/src/vault/query.ts +422 -0
  195. package/src/vault/reader.ts +264 -0
  196. package/src/vault/templates.ts +186 -0
  197. package/src/vault/types.ts +73 -0
  198. package/src/vault/watcher.ts +277 -0
  199. package/src/vault/writer.ts +393 -0
  200. package/tsconfig.json +30 -0
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Knowledge Graph Schema
3
+ * Type definitions for the knowledge graph data structures
4
+ */
5
+
6
+ export type EntityType =
7
+ | 'project'
8
+ | 'technology'
9
+ | 'concept'
10
+ | 'person'
11
+ | 'file'
12
+ | 'decision'
13
+ | 'pattern'
14
+ | 'correction'
15
+ | 'date'
16
+
17
+ export type RelationshipType =
18
+ | 'uses'
19
+ | 'depends_on'
20
+ | 'replaces'
21
+ | 'contradicts'
22
+ | 'relates_to'
23
+ | 'part_of'
24
+ | 'created_by'
25
+ | 'decided_in'
26
+ | 'similar_to'
27
+ | 'precedes'
28
+ | 'follows'
29
+
30
+ export interface GraphNode {
31
+ id: string
32
+ name: string
33
+ type: EntityType
34
+ properties: Record<string, unknown>
35
+ created_at: string
36
+ updated_at: string
37
+ }
38
+
39
+ export interface GraphEdge {
40
+ id: string
41
+ source: string
42
+ target: string
43
+ relationship: RelationshipType
44
+ weight: number
45
+ properties: Record<string, unknown>
46
+ created_at: string
47
+ }
48
+
49
+ export interface TraversalOptions {
50
+ maxDepth?: number
51
+ nodeTypes?: EntityType[]
52
+ relationshipTypes?: RelationshipType[]
53
+ limit?: number
54
+ }
55
+
56
+ export interface GraphSearchOptions {
57
+ query?: string
58
+ nodeType?: EntityType
59
+ relationshipType?: RelationshipType
60
+ startNodeId?: string
61
+ hops?: number
62
+ limit?: number
63
+ alpha?: number
64
+ }
65
+
66
+ export interface GraphSearchResult {
67
+ nodes: GraphNode[]
68
+ edges: GraphEdge[]
69
+ paths: string[][]
70
+ scores: Map<string, number>
71
+ }
72
+
73
+ export interface KnowledgeGraph {
74
+ addNode(node: Omit<GraphNode, 'id' | 'created_at' | 'updated_at'>): GraphNode
75
+ getNode(id: string): GraphNode | undefined
76
+ updateNode(id: string, updates: Partial<Omit<GraphNode, 'id' | 'created_at'>>): GraphNode | undefined
77
+ deleteNode(id: string): boolean
78
+
79
+ addEdge(edge: Omit<GraphEdge, 'id' | 'created_at'>): GraphEdge
80
+ getEdges(nodeId: string, direction?: 'outgoing' | 'incoming' | 'both'): GraphEdge[]
81
+ deleteEdge(id: string): boolean
82
+
83
+ traverse(startId: string, options?: TraversalOptions): GraphNode[]
84
+ shortestPath(sourceId: string, targetId: string): string[] | null
85
+ findNodes(query: { type?: EntityType; name?: string; properties?: Record<string, unknown> }): GraphNode[]
86
+
87
+ getNodeCount(): number
88
+ getEdgeCount(): number
89
+
90
+ save(path: string): Promise<void>
91
+ load(path: string): Promise<void>
92
+ }
93
+
94
+ export interface SerializedGraph {
95
+ nodes: GraphNode[]
96
+ edges: GraphEdge[]
97
+ version: string
98
+ saved_at: string
99
+ }
@@ -0,0 +1,168 @@
1
+ /**
2
+ * Graph Search Engine
3
+ * Combines vector similarity with graph proximity for enhanced search
4
+ */
5
+
6
+ import type { Logger } from 'pino'
7
+ import type { InMemoryKnowledgeGraph } from './memory-graph'
8
+ import type { GraphNode, GraphEdge, GraphSearchOptions, GraphSearchResult } from './schema'
9
+
10
+ export class GraphSearchEngine {
11
+ private graph: InMemoryKnowledgeGraph
12
+ private logger: Logger
13
+
14
+ constructor(graph: InMemoryKnowledgeGraph, logger: Logger) {
15
+ this.graph = graph
16
+ this.logger = logger.child({ component: 'graph-search' })
17
+ }
18
+
19
+ search(options: GraphSearchOptions): GraphSearchResult {
20
+ const {
21
+ query,
22
+ nodeType,
23
+ startNodeId,
24
+ hops = 2,
25
+ limit = 20,
26
+ alpha = 0.6
27
+ } = options
28
+
29
+ const scores = new Map<string, number>()
30
+ const resultNodes: GraphNode[] = []
31
+ const resultEdges: GraphEdge[] = []
32
+ const paths: string[][] = []
33
+
34
+ if (startNodeId) {
35
+ // Graph expansion from a starting node
36
+ return this.expandFromNode(startNodeId, hops, limit, nodeType)
37
+ }
38
+
39
+ if (query) {
40
+ // Text-based search: find nodes matching query, then expand
41
+ const matchingNodes = this.findMatchingNodes(query, nodeType)
42
+
43
+ for (const node of matchingNodes) {
44
+ const nameScore = this.calculateNameScore(node.name, query)
45
+ scores.set(node.id, nameScore)
46
+
47
+ // Expand from each matching node
48
+ const neighbors = this.graph.traverse(node.id, {
49
+ maxDepth: hops,
50
+ nodeTypes: nodeType ? [nodeType] : undefined,
51
+ limit: Math.ceil(limit / Math.max(matchingNodes.length, 1))
52
+ })
53
+
54
+ for (const neighbor of neighbors) {
55
+ if (!scores.has(neighbor.id)) {
56
+ // Graph proximity score: decreases with distance
57
+ const path = this.graph.shortestPath(node.id, neighbor.id)
58
+ const distance = path ? path.length - 1 : hops + 1
59
+ const graphScore = 1 / (1 + distance)
60
+
61
+ // Combined score
62
+ const combinedScore = alpha * nameScore + (1 - alpha) * graphScore
63
+ scores.set(neighbor.id, Math.max(scores.get(neighbor.id) || 0, combinedScore))
64
+
65
+ if (path && path.length > 1) {
66
+ paths.push(path)
67
+ }
68
+ }
69
+ }
70
+ }
71
+
72
+ // Collect nodes sorted by score
73
+ const sortedEntries = Array.from(scores.entries())
74
+ .sort(([, a], [, b]) => b - a)
75
+ .slice(0, limit)
76
+
77
+ for (const [nodeId] of sortedEntries) {
78
+ const node = this.graph.getNode(nodeId)
79
+ if (node) resultNodes.push(node)
80
+ }
81
+
82
+ // Collect edges between result nodes
83
+ const resultNodeIds = new Set(resultNodes.map(n => n.id))
84
+ for (const node of resultNodes) {
85
+ const edges = this.graph.getEdges(node.id)
86
+ for (const edge of edges) {
87
+ if (resultNodeIds.has(edge.source) && resultNodeIds.has(edge.target)) {
88
+ if (!resultEdges.some(e => e.id === edge.id)) {
89
+ resultEdges.push(edge)
90
+ }
91
+ }
92
+ }
93
+ }
94
+ }
95
+
96
+ return { nodes: resultNodes, edges: resultEdges, paths, scores }
97
+ }
98
+
99
+ findRelated(nodeId: string, hops: number = 2): GraphSearchResult {
100
+ return this.expandFromNode(nodeId, hops, 50)
101
+ }
102
+
103
+ private expandFromNode(
104
+ nodeId: string,
105
+ hops: number,
106
+ limit: number,
107
+ nodeType?: string
108
+ ): GraphSearchResult {
109
+ const scores = new Map<string, number>()
110
+ const nodes = this.graph.traverse(nodeId, {
111
+ maxDepth: hops,
112
+ nodeTypes: nodeType ? [nodeType as any] : undefined,
113
+ limit
114
+ })
115
+
116
+ const resultEdges: GraphEdge[] = []
117
+ const paths: string[][] = []
118
+ const nodeIds = new Set(nodes.map(n => n.id))
119
+
120
+ for (const node of nodes) {
121
+ const path = this.graph.shortestPath(nodeId, node.id)
122
+ const distance = path ? path.length - 1 : 0
123
+ scores.set(node.id, 1 / (1 + distance))
124
+
125
+ if (path && path.length > 1) {
126
+ paths.push(path)
127
+ }
128
+
129
+ const edges = this.graph.getEdges(node.id)
130
+ for (const edge of edges) {
131
+ if (nodeIds.has(edge.source) && nodeIds.has(edge.target)) {
132
+ if (!resultEdges.some(e => e.id === edge.id)) {
133
+ resultEdges.push(edge)
134
+ }
135
+ }
136
+ }
137
+ }
138
+
139
+ return { nodes, edges: resultEdges, paths, scores }
140
+ }
141
+
142
+ private findMatchingNodes(query: string, nodeType?: string): GraphNode[] {
143
+ const lowerQuery = query.toLowerCase()
144
+ const queryTerms = lowerQuery.split(/\s+/).filter(t => t.length > 1)
145
+
146
+ return this.graph.findNodes({
147
+ type: nodeType as any,
148
+ name: undefined
149
+ }).filter(node => {
150
+ const lowerName = node.name.toLowerCase()
151
+ return queryTerms.some(term => lowerName.includes(term) || term.includes(lowerName))
152
+ })
153
+ }
154
+
155
+ private calculateNameScore(nodeName: string, query: string): number {
156
+ const lowerName = nodeName.toLowerCase()
157
+ const lowerQuery = query.toLowerCase()
158
+
159
+ if (lowerName === lowerQuery) return 1.0
160
+ if (lowerQuery.includes(lowerName)) return 0.9
161
+ if (lowerName.includes(lowerQuery)) return 0.8
162
+
163
+ // Partial term matching
164
+ const queryTerms = lowerQuery.split(/\s+/)
165
+ const matchCount = queryTerms.filter(t => lowerName.includes(t)).length
166
+ return matchCount > 0 ? 0.5 + (0.3 * matchCount / queryTerms.length) : 0.3
167
+ }
168
+ }
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Relationship Extractor
3
+ * Pattern-based extraction of relationships between entities
4
+ */
5
+
6
+ import type { RelationshipType } from './graph/schema'
7
+ import type { ExtractedEntity } from './entity-extractor'
8
+
9
+ export interface ExtractedRelationship {
10
+ sourceEntity: string
11
+ targetEntity: string
12
+ relationship: RelationshipType
13
+ confidence: number
14
+ evidence: string
15
+ }
16
+
17
+ interface RelationshipPattern {
18
+ regex: RegExp
19
+ relationship: RelationshipType
20
+ confidence: number
21
+ sourceGroup: number
22
+ targetGroup: number
23
+ }
24
+
25
+ const PATTERNS: RelationshipPattern[] = [
26
+ // "chose X for Y", "use X for Y", "selected X for Y"
27
+ { regex: /(?:chose|choose|use|using|selected|picked)\s+(\w[\w.-]*)\s+(?:for|in|with)\s+(\w[\w.-]*)/gi, relationship: 'uses', confidence: 0.85, sourceGroup: 2, targetGroup: 1 },
28
+ // "X uses Y", "X depends on Y"
29
+ { regex: /(\w[\w.-]*)\s+(?:uses|utilizes|leverages|employs)\s+(\w[\w.-]*)/gi, relationship: 'uses', confidence: 0.8, sourceGroup: 1, targetGroup: 2 },
30
+ // "X depends on Y", "X requires Y"
31
+ { regex: /(\w[\w.-]*)\s+(?:depends?\s+on|requires|needs)\s+(\w[\w.-]*)/gi, relationship: 'depends_on', confidence: 0.85, sourceGroup: 1, targetGroup: 2 },
32
+ // "replaced X with Y", "migrated from X to Y"
33
+ { regex: /(?:replaced|replace|swap(?:ped)?)\s+(\w[\w.-]*)\s+(?:with|for)\s+(\w[\w.-]*)/gi, relationship: 'replaces', confidence: 0.9, sourceGroup: 2, targetGroup: 1 },
34
+ { regex: /(?:migrated?|moved?|switched?)\s+(?:from\s+)?(\w[\w.-]*)\s+to\s+(\w[\w.-]*)/gi, relationship: 'replaces', confidence: 0.85, sourceGroup: 2, targetGroup: 1 },
35
+ // "X contradicts Y", "X conflicts with Y"
36
+ { regex: /(\w[\w.-]*)\s+(?:contradicts|conflicts?\s+with|incompatible\s+with)\s+(\w[\w.-]*)/gi, relationship: 'contradicts', confidence: 0.8, sourceGroup: 1, targetGroup: 2 },
37
+ // "X is part of Y", "X belongs to Y"
38
+ { regex: /(\w[\w.-]*)\s+(?:is\s+part\s+of|belongs?\s+to|included?\s+in)\s+(\w[\w.-]*)/gi, relationship: 'part_of', confidence: 0.8, sourceGroup: 1, targetGroup: 2 },
39
+ // "X relates to Y", "X is related to Y"
40
+ { regex: /(\w[\w.-]*)\s+(?:relates?\s+to|is\s+related\s+to|connects?\s+(?:to|with))\s+(\w[\w.-]*)/gi, relationship: 'relates_to', confidence: 0.7, sourceGroup: 1, targetGroup: 2 },
41
+ // "X is similar to Y", "X like Y"
42
+ { regex: /(\w[\w.-]*)\s+(?:is\s+similar\s+to|like|resembles|comparable\s+to)\s+(\w[\w.-]*)/gi, relationship: 'similar_to', confidence: 0.7, sourceGroup: 1, targetGroup: 2 },
43
+ // "after X, Y" / "X then Y" (temporal sequencing)
44
+ { regex: /(?:after|following)\s+(\w[\w.-]*)\s*,?\s*(?:we\s+)?(?:use|chose|moved\s+to|adopted)\s+(\w[\w.-]*)/gi, relationship: 'follows', confidence: 0.7, sourceGroup: 2, targetGroup: 1 },
45
+ ]
46
+
47
+ export class RelationshipExtractor {
48
+ extract(text: string, entities: ExtractedEntity[]): ExtractedRelationship[] {
49
+ const relationships: ExtractedRelationship[] = []
50
+ const entityNames = new Set(entities.map(e => e.normalizedName))
51
+
52
+ for (const pattern of PATTERNS) {
53
+ let match: RegExpExecArray | null
54
+ const regex = new RegExp(pattern.regex.source, pattern.regex.flags)
55
+
56
+ while ((match = regex.exec(text)) !== null) {
57
+ const rawSource = match[pattern.sourceGroup]?.toLowerCase()
58
+ const rawTarget = match[pattern.targetGroup]?.toLowerCase()
59
+
60
+ if (!rawSource || !rawTarget) continue
61
+ if (rawSource === rawTarget) continue
62
+
63
+ // Check if at least one entity is known
64
+ const sourceKnown = entityNames.has(rawSource) || this.findEntityMatch(rawSource, entities)
65
+ const targetKnown = entityNames.has(rawTarget) || this.findEntityMatch(rawTarget, entities)
66
+
67
+ if (!sourceKnown && !targetKnown) continue
68
+
69
+ const sourceName = this.resolveEntityName(rawSource, entities) || rawSource
70
+ const targetName = this.resolveEntityName(rawTarget, entities) || rawTarget
71
+
72
+ // Avoid duplicates
73
+ const exists = relationships.some(r =>
74
+ r.sourceEntity === sourceName &&
75
+ r.targetEntity === targetName &&
76
+ r.relationship === pattern.relationship
77
+ )
78
+
79
+ if (!exists) {
80
+ relationships.push({
81
+ sourceEntity: sourceName,
82
+ targetEntity: targetName,
83
+ relationship: pattern.relationship,
84
+ confidence: pattern.confidence * (sourceKnown && targetKnown ? 1.0 : 0.8),
85
+ evidence: match[0].trim()
86
+ })
87
+ }
88
+ }
89
+ }
90
+
91
+ return relationships
92
+ }
93
+
94
+ private findEntityMatch(name: string, entities: ExtractedEntity[]): boolean {
95
+ return entities.some(e =>
96
+ e.normalizedName === name ||
97
+ e.name.toLowerCase() === name
98
+ )
99
+ }
100
+
101
+ private resolveEntityName(name: string, entities: ExtractedEntity[]): string | undefined {
102
+ const entity = entities.find(e =>
103
+ e.normalizedName === name ||
104
+ e.name.toLowerCase() === name
105
+ )
106
+ return entity?.normalizedName
107
+ }
108
+ }
@@ -0,0 +1,169 @@
1
+ import { ChromaClient, Collection } from 'chromadb'
2
+ import type { Logger } from 'pino'
3
+ import type { ChromaConfig } from './config'
4
+
5
+ export class ChromaClientManager {
6
+ private client: ChromaClient | null = null
7
+ private logger: Logger
8
+ private config: ChromaConfig
9
+ private isConnected: boolean = false
10
+ private collections: Map<string, Collection> = new Map()
11
+
12
+ constructor(logger: Logger, config: ChromaConfig) {
13
+ this.logger = logger.child({ component: 'chroma-client' })
14
+ this.config = config
15
+ }
16
+
17
+ async initialize(): Promise<void> {
18
+ this.logger.info({ mode: this.config.mode }, 'Initializing ChromaDB client')
19
+
20
+ try {
21
+ const authConfig = this.config.chromaAuthToken ? {
22
+ headers: {
23
+ 'Authorization': `Bearer ${this.config.chromaAuthToken}`,
24
+ 'X-Chroma-Token': this.config.chromaAuthToken
25
+ },
26
+ ...(this.config.chromaTenant ? { tenant: this.config.chromaTenant } : {}),
27
+ ...(this.config.chromaDatabase ? { database: this.config.chromaDatabase } : {})
28
+ } : {}
29
+
30
+ switch (this.config.mode) {
31
+ case 'persistent':
32
+ this.client = new ChromaClient({
33
+ path: this.config.path,
34
+ ...authConfig
35
+ })
36
+ break
37
+
38
+ case 'client-server':
39
+ if (!this.config.host || !this.config.port) {
40
+ throw new Error('Host and port required for client-server mode')
41
+ }
42
+ this.client = new ChromaClient({
43
+ host: this.config.host,
44
+ port: this.config.port,
45
+ ...authConfig
46
+ })
47
+ break
48
+
49
+ case 'cloud':
50
+ if (!this.config.host) {
51
+ throw new Error('Host URL required for cloud mode')
52
+ }
53
+ const hostWithProtocol = this.config.host.startsWith('http')
54
+ ? this.config.host
55
+ : `https://${this.config.host}`
56
+ const url = new URL(hostWithProtocol)
57
+ this.client = new ChromaClient({
58
+ host: url.hostname,
59
+ port: url.port ? parseInt(url.port) : (url.protocol === 'https:' ? 443 : 80),
60
+ ssl: url.protocol === 'https:',
61
+ ...authConfig
62
+ })
63
+ break
64
+
65
+ case 'ephemeral':
66
+ try {
67
+ this.client = new ChromaClient()
68
+ } catch (err) {
69
+ this.logger.warn({ error: err }, 'Ephemeral mode failed, attempting client-server mode')
70
+ this.client = new ChromaClient({
71
+ host: 'localhost',
72
+ port: 8000,
73
+ ssl: false,
74
+ ...authConfig
75
+ })
76
+ }
77
+ break
78
+ }
79
+
80
+ try {
81
+ await this.client.heartbeat()
82
+ this.isConnected = true
83
+ this.logger.info('ChromaDB client initialized successfully')
84
+ } catch (heartbeatError) {
85
+ this.logger.warn({ error: heartbeatError }, 'ChromaDB heartbeat failed, connection may not be fully established')
86
+ this.isConnected = true
87
+ }
88
+
89
+ } catch (error) {
90
+ this.logger.error({ error }, 'Failed to initialize ChromaDB client')
91
+ throw error
92
+ }
93
+ }
94
+
95
+ async getCollection(name: string): Promise<Collection> {
96
+ if (!this.client) {
97
+ throw new Error('ChromaDB client not initialized')
98
+ }
99
+
100
+ if (this.collections.has(name)) {
101
+ return this.collections.get(name)!
102
+ }
103
+
104
+ try {
105
+ const collection = await this.client.getOrCreateCollection({
106
+ name,
107
+ metadata: {
108
+ 'hnsw:space': 'cosine',
109
+ 'created_at': new Date().toISOString()
110
+ }
111
+ })
112
+
113
+ this.collections.set(name, collection)
114
+ this.logger.info({ collection: name }, 'Collection retrieved/created')
115
+
116
+ return collection
117
+
118
+ } catch (error) {
119
+ this.logger.error({ error, collection: name }, 'Failed to get collection')
120
+ throw error
121
+ }
122
+ }
123
+
124
+ async listCollections(): Promise<string[]> {
125
+ if (!this.client) {
126
+ throw new Error('ChromaDB client not initialized')
127
+ }
128
+
129
+ const collections = await this.client.listCollections()
130
+ return collections.map(c => c.name)
131
+ }
132
+
133
+ async deleteCollection(name: string): Promise<void> {
134
+ if (!this.client) {
135
+ throw new Error('ChromaDB client not initialized')
136
+ }
137
+
138
+ await this.client.deleteCollection({ name })
139
+ this.collections.delete(name)
140
+ this.logger.info({ collection: name }, 'Collection deleted')
141
+ }
142
+
143
+ async healthCheck(): Promise<boolean> {
144
+ if (!this.client) return false
145
+
146
+ try {
147
+ await this.client.heartbeat()
148
+ return true
149
+ } catch {
150
+ this.isConnected = false
151
+ return false
152
+ }
153
+ }
154
+
155
+ getStatus(): { connected: boolean; mode: string; collections: number } {
156
+ return {
157
+ connected: this.isConnected,
158
+ mode: this.config.mode,
159
+ collections: this.collections.size
160
+ }
161
+ }
162
+
163
+ async close(): Promise<void> {
164
+ this.collections.clear()
165
+ this.client = null
166
+ this.isConnected = false
167
+ this.logger.info('ChromaDB client closed')
168
+ }
169
+ }
@@ -0,0 +1,94 @@
1
+ import type { Collection } from 'chromadb'
2
+ import type { Logger } from 'pino'
3
+ import type { ChromaClientManager } from './client'
4
+ import { COLLECTIONS, type CollectionName } from './schemas'
5
+
6
+ export class CollectionManager {
7
+ private logger: Logger
8
+ private client: ChromaClientManager
9
+ private initialized: boolean = false
10
+
11
+ constructor(logger: Logger, client: ChromaClientManager) {
12
+ this.logger = logger.child({ component: 'collection-manager' })
13
+ this.client = client
14
+ }
15
+
16
+ async initialize(): Promise<void> {
17
+ this.logger.info('Initializing collections')
18
+
19
+ try {
20
+ for (const collectionName of Object.values(COLLECTIONS)) {
21
+ await this.client.getCollection(collectionName)
22
+ }
23
+
24
+ this.initialized = true
25
+ this.logger.info('All collections initialized')
26
+
27
+ } catch (error) {
28
+ this.logger.error({ error }, 'Failed to initialize collections')
29
+ throw error
30
+ }
31
+ }
32
+
33
+ async getDecisions(): Promise<Collection> {
34
+ return this.client.getCollection(COLLECTIONS.DECISIONS)
35
+ }
36
+
37
+ async getMemories(): Promise<Collection> {
38
+ return this.client.getCollection(COLLECTIONS.MEMORIES)
39
+ }
40
+
41
+ async getPatterns(): Promise<Collection> {
42
+ return this.client.getCollection(COLLECTIONS.PATTERNS)
43
+ }
44
+
45
+ async getCorrections(): Promise<Collection> {
46
+ return this.client.getCollection(COLLECTIONS.CORRECTIONS)
47
+ }
48
+
49
+ async getFeedback(): Promise<Collection> {
50
+ return this.client.getCollection(COLLECTIONS.FEEDBACK)
51
+ }
52
+
53
+ async getEpisodes(): Promise<Collection> {
54
+ return this.client.getCollection(COLLECTIONS.EPISODES)
55
+ }
56
+
57
+ async getArchivedMemories(): Promise<Collection> {
58
+ return this.client.getCollection(COLLECTIONS.ARCHIVED_MEMORIES)
59
+ }
60
+
61
+ async getProjectCollection(projectName: string): Promise<Collection> {
62
+ const collectionName = `project_${projectName.toLowerCase().replace(/[^a-z0-9]/g, '_')}`
63
+ return this.client.getCollection(collectionName)
64
+ }
65
+
66
+ async getStats(): Promise<Record<string, number>> {
67
+ const stats: Record<string, number> = {}
68
+
69
+ for (const collectionName of Object.values(COLLECTIONS)) {
70
+ try {
71
+ const collection = await this.client.getCollection(collectionName)
72
+ stats[collectionName] = await collection.count()
73
+ } catch {
74
+ stats[collectionName] = 0
75
+ }
76
+ }
77
+
78
+ return stats
79
+ }
80
+
81
+ async clearAll(): Promise<void> {
82
+ this.logger.warn('Clearing all collections')
83
+
84
+ for (const collectionName of Object.values(COLLECTIONS)) {
85
+ try {
86
+ await this.client.deleteCollection(collectionName)
87
+ } catch (error) {
88
+ this.logger.error({ error, collection: collectionName }, 'Failed to delete collection')
89
+ }
90
+ }
91
+
92
+ await this.initialize()
93
+ }
94
+ }
@@ -0,0 +1,46 @@
1
+ import { z } from 'zod'
2
+
3
+ export const ChromaConfigSchema = z.object({
4
+ mode: z.enum(['persistent', 'client-server', 'ephemeral', 'cloud']).default('persistent'),
5
+ path: z.string().default('./data/chroma'),
6
+ host: z.string().optional(),
7
+ port: z.number().optional(),
8
+ embeddingProvider: z.enum(['default', 'transformers', 'openai']).default('default'),
9
+ embeddingModel: z.string().default('Xenova/all-MiniLM-L6-v2'),
10
+ defaultCollection: z.string().default('memories'),
11
+ batchSize: z.number().default(100),
12
+ openaiApiKey: z.string().optional(),
13
+ chromaAuthToken: z.string().optional(),
14
+ chromaTenant: z.string().optional(),
15
+ chromaDatabase: z.string().optional()
16
+ })
17
+
18
+ export type ChromaConfig = z.infer<typeof ChromaConfigSchema>
19
+
20
+ export const DEFAULT_CHROMA_CONFIG: ChromaConfig = {
21
+ mode: 'persistent',
22
+ path: './data/chroma',
23
+ embeddingProvider: 'default',
24
+ embeddingModel: 'Xenova/all-MiniLM-L6-v2',
25
+ defaultCollection: 'memories',
26
+ batchSize: 100
27
+ }
28
+
29
+ export function getChromaConfigFromEnv(): Partial<ChromaConfig> {
30
+ const config: Partial<ChromaConfig> = {
31
+ chromaAuthToken: process.env.CHROMA_API_KEY,
32
+ chromaTenant: process.env.CHROMA_TENANT,
33
+ chromaDatabase: process.env.CHROMA_DATABASE
34
+ }
35
+
36
+ if (process.env.CHROMA_HOST) {
37
+ config.host = process.env.CHROMA_HOST
38
+ config.mode = 'cloud'
39
+ }
40
+
41
+ if (process.env.CHROMA_EMBEDDING_PROVIDER) {
42
+ config.embeddingProvider = process.env.CHROMA_EMBEDDING_PROVIDER as any
43
+ }
44
+
45
+ return config
46
+ }