claude-brain 0.30.2 → 0.30.3
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/README.md +241 -191
- package/VERSION +1 -1
- package/assets/CLAUDE-unified.md +11 -11
- package/assets/CLAUDE.md +29 -29
- package/package.json +7 -3
- package/packs/backend/node.json +173 -173
- package/packs/core/javascript.json +176 -176
- package/packs/core/typescript.json +222 -222
- package/packs/frontend/react.json +254 -254
- package/packs/meta/testing.json +172 -172
- package/scripts/postinstall.mjs +531 -531
- package/src/automation/decision-detector.ts +452 -452
- package/src/automation/phase12-manager.ts +456 -456
- package/src/automation/proactive-recall.ts +373 -373
- package/src/automation/project-detector.ts +310 -310
- package/src/automation/repo-scanner.ts +210 -205
- package/src/cli/auto-setup.ts +75 -75
- package/src/cli/auto-start.ts +266 -266
- package/src/cli/bin.ts +264 -264
- package/src/cli/commands/autostart.ts +90 -90
- package/src/cli/commands/chroma.ts +578 -577
- package/src/cli/commands/export-training.ts +70 -70
- package/src/cli/commands/export.ts +130 -130
- package/src/cli/commands/git-hook.ts +183 -183
- package/src/cli/commands/hooks.ts +217 -217
- package/src/cli/commands/init.ts +123 -123
- package/src/cli/commands/install-mcp.ts +122 -111
- package/src/cli/commands/models.ts +979 -979
- package/src/cli/commands/pack.ts +200 -200
- package/src/cli/commands/refresh.ts +344 -339
- package/src/cli/commands/reindex.ts +120 -120
- package/src/cli/commands/serve.ts +466 -463
- package/src/cli/commands/start.ts +44 -44
- package/src/cli/commands/status.ts +220 -203
- package/src/cli/commands/uninstall-mcp.ts +45 -41
- package/src/cli/commands/update.ts +130 -124
- package/src/cli/migrate-chroma.ts +106 -106
- package/src/cli/ui/animations.ts +80 -80
- package/src/cli/ui/components.ts +82 -82
- package/src/cli/ui/index.ts +4 -4
- package/src/cli/ui/logo.ts +36 -36
- package/src/cli/ui/theme.ts +55 -55
- package/src/code-intelligence/indexer.ts +352 -352
- package/src/code-intelligence/linker.ts +178 -178
- package/src/code-intelligence/parser.ts +484 -484
- package/src/code-intelligence/query.ts +291 -291
- package/src/code-intelligence/schema.ts +83 -83
- package/src/code-intelligence/types.ts +95 -95
- package/src/config/defaults.ts +52 -52
- package/src/config/home.ts +56 -56
- package/src/config/index.ts +5 -5
- package/src/config/loader.ts +192 -192
- package/src/config/schema.ts +446 -415
- package/src/config/validator.ts +182 -182
- package/src/context/assembler.ts +407 -400
- package/src/context/index.ts +79 -79
- package/src/context/progress-tracker.ts +174 -174
- package/src/context/standards-manager.ts +287 -287
- package/src/context/validator.ts +58 -58
- package/src/diagnostics/index.ts +122 -121
- package/src/health/index.ts +233 -232
- package/src/hooks/brain-hook.ts +134 -131
- package/src/hooks/capture.ts +168 -168
- package/src/hooks/claude-code-mastery.md +112 -112
- package/src/hooks/context-hook.ts +260 -245
- package/src/hooks/deduplicator.ts +72 -72
- package/src/hooks/git-capture.ts +109 -109
- package/src/hooks/git-hook-installer.ts +211 -207
- package/src/hooks/index.ts +20 -20
- package/src/hooks/installer.ts +306 -288
- package/src/hooks/interceptor-hook.ts +204 -201
- package/src/hooks/passive-classifier.ts +397 -397
- package/src/hooks/queue.ts +160 -129
- package/src/hooks/session-tracker.ts +312 -312
- package/src/hooks/types.ts +52 -52
- package/src/index.ts +7 -7
- package/src/intelligence/cross-project/generalizer.ts +283 -283
- package/src/intelligence/cross-project/index.ts +7 -7
- package/src/intelligence/hf-downloader.ts +222 -222
- package/src/intelligence/hf-manifest.json +78 -78
- package/src/intelligence/index.ts +24 -24
- package/src/intelligence/inference-router.ts +762 -762
- package/src/intelligence/model-manager.ts +263 -245
- package/src/intelligence/optimization/index.ts +10 -10
- package/src/intelligence/optimization/precompute.ts +202 -202
- package/src/intelligence/optimization/semantic-cache.ts +213 -207
- package/src/intelligence/prediction/index.ts +7 -7
- package/src/intelligence/prediction/recommender.ts +276 -268
- package/src/intelligence/reasoning/chain-retrieval.ts +243 -247
- package/src/intelligence/reasoning/index.ts +7 -7
- package/src/intelligence/temporal/evolution.ts +193 -197
- package/src/intelligence/temporal/index.ts +16 -16
- package/src/intelligence/temporal/query-processor.ts +190 -190
- package/src/intelligence/temporal/timeline.ts +272 -259
- package/src/intelligence/temporal/trends.ts +263 -263
- package/src/intelligence/tokenizer.ts +118 -118
- package/src/knowledge/entity-extractor.ts +447 -443
- package/src/knowledge/graph/builder.ts +185 -185
- package/src/knowledge/graph/linker.ts +201 -201
- package/src/knowledge/graph/memory-graph.ts +359 -359
- package/src/knowledge/graph/schema.ts +99 -99
- package/src/knowledge/graph/search.ts +166 -166
- package/src/knowledge/relationship-extractor.ts +108 -108
- package/src/memory/chroma/client.ts +211 -192
- package/src/memory/chroma/collection-manager.ts +92 -92
- package/src/memory/chroma/config.ts +57 -57
- package/src/memory/chroma/embeddings.ts +177 -175
- package/src/memory/chroma/index.ts +82 -82
- package/src/memory/chroma/migration.ts +270 -270
- package/src/memory/chroma/schemas.ts +69 -69
- package/src/memory/chroma/search.ts +319 -315
- package/src/memory/chroma/store.ts +755 -747
- package/src/memory/compression.ts +121 -121
- package/src/memory/consolidation/archiver.ts +162 -165
- package/src/memory/consolidation/merger.ts +182 -186
- package/src/memory/consolidation/scorer.ts +136 -136
- package/src/memory/database.ts +9 -0
- package/src/memory/dual-write.ts +145 -0
- package/src/memory/embeddings.ts +226 -226
- package/src/memory/episodic/detector.ts +108 -108
- package/src/memory/episodic/manager.ts +347 -351
- package/src/memory/episodic/summarizer.ts +179 -179
- package/src/memory/episodic/types.ts +52 -52
- package/src/memory/fts5-search.ts +692 -633
- package/src/memory/index.ts +943 -1060
- package/src/memory/migrations/add-fts5.ts +118 -108
- package/src/memory/patterns.ts +438 -438
- package/src/memory/pruning.ts +60 -60
- package/src/memory/schema.ts +88 -88
- package/src/memory/store.ts +911 -787
- package/src/orchestrator/handlers/decision-handler.ts +204 -204
- package/src/packs/index.ts +9 -9
- package/src/packs/loader.ts +134 -134
- package/src/packs/manager.ts +204 -204
- package/src/packs/ranker.ts +78 -78
- package/src/packs/types.ts +81 -81
- package/src/phase12/index.ts +5 -5
- package/src/retrieval/bm25/index.ts +300 -297
- package/src/retrieval/bm25/tokenizer.ts +184 -184
- package/src/retrieval/feedback/adaptive.ts +221 -221
- package/src/retrieval/feedback/index.ts +16 -16
- package/src/retrieval/feedback/metrics.ts +221 -221
- package/src/retrieval/feedback/store.ts +283 -283
- package/src/retrieval/fusion/index.ts +194 -194
- package/src/retrieval/fusion/rrf.ts +165 -165
- package/src/retrieval/index.ts +12 -12
- package/src/retrieval/pipeline.ts +375 -375
- package/src/retrieval/query/expander.ts +203 -203
- package/src/retrieval/query/index.ts +27 -27
- package/src/retrieval/query/intent-classifier.ts +252 -252
- package/src/retrieval/query/temporal-parser.ts +295 -295
- package/src/retrieval/reranker/index.ts +189 -188
- package/src/retrieval/reranker/model.ts +99 -95
- package/src/retrieval/service.ts +125 -125
- package/src/retrieval/types.ts +162 -162
- package/src/routing/entity-extractor.ts +454 -454
- package/src/routing/handlers/exploration-handler.ts +369 -0
- package/src/routing/handlers/index.ts +19 -0
- package/src/routing/handlers/memory-handler.ts +273 -0
- package/src/routing/handlers/mutation-handler.ts +241 -0
- package/src/routing/handlers/recall-handler.ts +642 -0
- package/src/routing/handlers/shared.ts +515 -0
- package/src/routing/handlers/types.ts +48 -0
- package/src/routing/intent-classifier.ts +552 -552
- package/src/routing/response-filter.ts +399 -391
- package/src/routing/router.ts +245 -2193
- package/src/routing/search-engine.ts +521 -514
- package/src/routing/types.ts +104 -94
- package/src/scripts/health-check.ts +118 -118
- package/src/scripts/setup.ts +122 -122
- package/src/server/auto-updater.ts +283 -276
- package/src/server/handlers/call-tool.ts +159 -159
- package/src/server/handlers/list-tools.ts +35 -35
- package/src/server/handlers/tools/auto-remember.ts +165 -165
- package/src/server/handlers/tools/brain.ts +86 -86
- package/src/server/handlers/tools/create-project.ts +135 -135
- package/src/server/handlers/tools/get-code-standards.ts +123 -123
- package/src/server/handlers/tools/get-corrections.ts +152 -152
- package/src/server/handlers/tools/get-patterns.ts +156 -156
- package/src/server/handlers/tools/get-project-context.ts +75 -75
- package/src/server/handlers/tools/index.ts +30 -30
- package/src/server/handlers/tools/init-project.ts +756 -756
- package/src/server/handlers/tools/list-projects.ts +126 -126
- package/src/server/handlers/tools/recall-similar.ts +87 -87
- package/src/server/handlers/tools/recognize-pattern.ts +132 -132
- package/src/server/handlers/tools/record-correction.ts +131 -131
- package/src/server/handlers/tools/remember-decision.ts +168 -168
- package/src/server/handlers/tools/schemas.ts +179 -179
- package/src/server/handlers/tools/search-code.ts +122 -122
- package/src/server/handlers/tools/smart-context.ts +146 -146
- package/src/server/handlers/tools/update-progress.ts +131 -131
- package/src/server/http-api.ts +215 -1229
- package/src/server/mcp-proxy.ts +85 -84
- package/src/server/mcp-server.ts +285 -284
- package/src/server/middleware/auth.ts +39 -0
- package/src/server/middleware/error-handler.ts +37 -0
- package/src/server/middleware/rate-limit.ts +53 -0
- package/src/server/middleware/validate.ts +42 -0
- package/src/server/pid-manager.ts +137 -136
- package/src/server/providers/resources.ts +581 -581
- package/src/server/routes/code.ts +228 -0
- package/src/server/routes/context.ts +26 -0
- package/src/server/routes/health.ts +19 -0
- package/src/server/routes/helpers.ts +100 -0
- package/src/server/routes/hooks.ts +197 -0
- package/src/server/routes/mcp.ts +47 -0
- package/src/server/routes/memory.ts +397 -0
- package/src/server/routes/models.ts +96 -0
- package/src/server/routes/projects.ts +89 -0
- package/src/server/routes/types.ts +21 -0
- package/src/server/schemas/api-schemas.ts +202 -0
- package/src/server/services.ts +720 -720
- package/src/server/utils/memory-indicator.ts +84 -84
- package/src/server/utils/response-formatter.ts +129 -129
- package/src/server/web-viewer.ts +1145 -1115
- package/src/setup/index.ts +38 -38
- package/src/tools/registry.ts +115 -115
- package/src/tools/schemas.ts +666 -666
- package/src/tools/types.ts +412 -412
- package/src/training/data-store.ts +320 -298
- package/src/training/retrain-pipeline.ts +399 -394
- package/src/utils/error-handler.ts +136 -136
- package/src/utils/index.ts +58 -58
- package/src/utils/kill-port.ts +55 -53
- package/src/utils/phase12-helper.ts +56 -56
- package/src/utils/safe-path.ts +43 -0
- package/src/utils/timing.ts +47 -47
- package/src/utils/transaction.ts +63 -63
- package/src/vault/index.ts +4 -3
- package/src/vault/paths.ts +106 -106
- package/src/vault/query.ts +4 -1
- package/src/vault/reader.ts +44 -1
- package/src/vault/watcher.ts +24 -1
- package/src/vault/writer.ts +487 -413
- package/skills/persistent-memory/SKILL.md +0 -148
- package/skills/persistent-memory/references/tool-reference.md +0 -90
|
@@ -1,108 +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
|
-
}
|
|
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
|
+
}
|
|
@@ -1,192 +1,211 @@
|
|
|
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
|
-
private pendingCollections: Map<string, Promise<Collection>> = new Map()
|
|
12
|
-
|
|
13
|
-
constructor(logger: Logger, config: ChromaConfig) {
|
|
14
|
-
this.logger = logger.child({ component: 'chroma-client' })
|
|
15
|
-
this.config = config
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
async initialize(): Promise<void> {
|
|
19
|
-
this.logger.info({ mode: this.config.mode }, 'Initializing ChromaDB client')
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
return
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
async
|
|
167
|
-
if (!this.client)
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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
|
+
private pendingCollections: Map<string, Promise<Collection>> = new Map()
|
|
12
|
+
|
|
13
|
+
constructor(logger: Logger, config: ChromaConfig) {
|
|
14
|
+
this.logger = logger.child({ component: 'chroma-client' })
|
|
15
|
+
this.config = config
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async initialize(): Promise<void> {
|
|
19
|
+
this.logger.info({ mode: this.config.mode }, 'Initializing ChromaDB client')
|
|
20
|
+
|
|
21
|
+
// Enforce HTTPS when API key/auth token is configured
|
|
22
|
+
if (this.config.host && this.config.chromaAuthToken) {
|
|
23
|
+
try {
|
|
24
|
+
const hostUrl = this.config.host.startsWith('http')
|
|
25
|
+
? this.config.host
|
|
26
|
+
: `https://${this.config.host}`
|
|
27
|
+
const url = new URL(hostUrl)
|
|
28
|
+
if (url.protocol !== 'https:') {
|
|
29
|
+
throw new Error(
|
|
30
|
+
`ChromaDB host must use HTTPS when API key is configured. Got: ${url.protocol} ` +
|
|
31
|
+
`Change CHROMA_HOST to use https://`
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
} catch (e) {
|
|
35
|
+
if ((e as Error).message.includes('HTTPS')) throw e
|
|
36
|
+
// URL parsing failed — will be caught downstream
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
const authConfig = this.config.chromaAuthToken ? {
|
|
42
|
+
headers: {
|
|
43
|
+
'Authorization': `Bearer ${this.config.chromaAuthToken}`,
|
|
44
|
+
'X-Chroma-Token': this.config.chromaAuthToken
|
|
45
|
+
},
|
|
46
|
+
...(this.config.chromaTenant ? { tenant: this.config.chromaTenant } : {}),
|
|
47
|
+
...(this.config.chromaDatabase ? { database: this.config.chromaDatabase } : {})
|
|
48
|
+
} : {}
|
|
49
|
+
|
|
50
|
+
switch (this.config.mode) {
|
|
51
|
+
case 'persistent':
|
|
52
|
+
// ChromaDB v3.x no longer supports embedded persistent mode via 'path'.
|
|
53
|
+
// Fall through to client-server connecting to localhost.
|
|
54
|
+
this.logger.info('Persistent mode: connecting to ChromaDB server on localhost (v3.x requires a running server)')
|
|
55
|
+
this.client = new ChromaClient({
|
|
56
|
+
host: this.config.host || 'localhost',
|
|
57
|
+
port: this.config.port || 8000,
|
|
58
|
+
...authConfig
|
|
59
|
+
})
|
|
60
|
+
break
|
|
61
|
+
|
|
62
|
+
case 'client-server':
|
|
63
|
+
if (!this.config.host) {
|
|
64
|
+
throw new Error('Host required for client-server mode')
|
|
65
|
+
}
|
|
66
|
+
this.client = new ChromaClient({
|
|
67
|
+
host: this.config.host,
|
|
68
|
+
port: this.config.port || 8000,
|
|
69
|
+
...authConfig
|
|
70
|
+
})
|
|
71
|
+
break
|
|
72
|
+
|
|
73
|
+
case 'cloud':
|
|
74
|
+
if (!this.config.host) {
|
|
75
|
+
throw new Error('Host URL required for cloud mode')
|
|
76
|
+
}
|
|
77
|
+
const hostWithProtocol = this.config.host.startsWith('http')
|
|
78
|
+
? this.config.host
|
|
79
|
+
: `https://${this.config.host}`
|
|
80
|
+
const url = new URL(hostWithProtocol)
|
|
81
|
+
this.client = new ChromaClient({
|
|
82
|
+
host: url.hostname,
|
|
83
|
+
port: url.port ? parseInt(url.port) : (url.protocol === 'https:' ? 443 : 80),
|
|
84
|
+
ssl: url.protocol === 'https:',
|
|
85
|
+
...authConfig
|
|
86
|
+
})
|
|
87
|
+
break
|
|
88
|
+
|
|
89
|
+
case 'ephemeral':
|
|
90
|
+
try {
|
|
91
|
+
this.client = new ChromaClient()
|
|
92
|
+
} catch (err) {
|
|
93
|
+
this.logger.warn({ error: err }, 'Ephemeral mode failed, attempting client-server mode')
|
|
94
|
+
this.client = new ChromaClient({
|
|
95
|
+
host: 'localhost',
|
|
96
|
+
port: 8000,
|
|
97
|
+
ssl: false,
|
|
98
|
+
...authConfig
|
|
99
|
+
})
|
|
100
|
+
}
|
|
101
|
+
break
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
try {
|
|
105
|
+
await this.client.heartbeat()
|
|
106
|
+
this.isConnected = true
|
|
107
|
+
this.logger.info('ChromaDB client initialized successfully')
|
|
108
|
+
} catch (heartbeatError) {
|
|
109
|
+
this.isConnected = false
|
|
110
|
+
this.logger.warn({ error: heartbeatError }, 'ChromaDB heartbeat failed')
|
|
111
|
+
throw new Error(`ChromaDB server unreachable: ${heartbeatError instanceof Error ? heartbeatError.message : String(heartbeatError)}`)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
} catch (error) {
|
|
115
|
+
this.logger.warn({ error }, 'ChromaDB not available, will use SQLite fallback')
|
|
116
|
+
throw error
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async getCollection(name: string): Promise<Collection> {
|
|
121
|
+
if (!this.client) {
|
|
122
|
+
throw new Error('ChromaDB client not initialized')
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (this.collections.has(name)) {
|
|
126
|
+
return this.collections.get(name)!
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Deduplicate concurrent calls: if a getOrCreateCollection is already
|
|
130
|
+
// in-flight for this name, reuse the same promise instead of racing.
|
|
131
|
+
if (this.pendingCollections.has(name)) {
|
|
132
|
+
return this.pendingCollections.get(name)!
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const pending = this.fetchAndCacheCollection(name)
|
|
136
|
+
this.pendingCollections.set(name, pending)
|
|
137
|
+
|
|
138
|
+
try {
|
|
139
|
+
return await pending
|
|
140
|
+
} finally {
|
|
141
|
+
this.pendingCollections.delete(name)
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
private async fetchAndCacheCollection(name: string): Promise<Collection> {
|
|
146
|
+
try {
|
|
147
|
+
const collection = await this.client!.getOrCreateCollection({
|
|
148
|
+
name,
|
|
149
|
+
metadata: {
|
|
150
|
+
'hnsw:space': 'cosine',
|
|
151
|
+
'created_at': new Date().toISOString()
|
|
152
|
+
}
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
this.collections.set(name, collection)
|
|
156
|
+
this.logger.info({ collection: name }, 'Collection retrieved/created')
|
|
157
|
+
|
|
158
|
+
return collection
|
|
159
|
+
|
|
160
|
+
} catch (error) {
|
|
161
|
+
this.logger.error({ error, collection: name }, 'Failed to get collection')
|
|
162
|
+
throw error
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async listCollections(): Promise<string[]> {
|
|
167
|
+
if (!this.client) {
|
|
168
|
+
throw new Error('ChromaDB client not initialized')
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const collections = await this.client.listCollections()
|
|
172
|
+
return collections.map(c => c.name)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
async deleteCollection(name: string): Promise<void> {
|
|
176
|
+
if (!this.client) {
|
|
177
|
+
throw new Error('ChromaDB client not initialized')
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
await this.client.deleteCollection({ name })
|
|
181
|
+
this.collections.delete(name)
|
|
182
|
+
this.logger.info({ collection: name }, 'Collection deleted')
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
async healthCheck(): Promise<boolean> {
|
|
186
|
+
if (!this.client) return false
|
|
187
|
+
|
|
188
|
+
try {
|
|
189
|
+
await this.client.heartbeat()
|
|
190
|
+
return true
|
|
191
|
+
} catch {
|
|
192
|
+
this.isConnected = false
|
|
193
|
+
return false
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
getStatus(): { connected: boolean; mode: string; collections: number } {
|
|
198
|
+
return {
|
|
199
|
+
connected: this.isConnected,
|
|
200
|
+
mode: this.config.mode,
|
|
201
|
+
collections: this.collections.size
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
async close(): Promise<void> {
|
|
206
|
+
this.collections.clear()
|
|
207
|
+
this.client = null
|
|
208
|
+
this.isConnected = false
|
|
209
|
+
this.logger.info('ChromaDB client closed')
|
|
210
|
+
}
|
|
211
|
+
}
|