claude-brain 0.30.2 → 0.31.1
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 +59 -56
- package/src/config/index.ts +5 -5
- package/src/config/loader.ts +195 -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
package/src/server/services.ts
CHANGED
|
@@ -1,720 +1,720 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared Services Module
|
|
3
|
-
* Provides access to initialized services for tool handlers
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import type { Logger } from 'pino'
|
|
7
|
-
import { existsSync } from 'fs'
|
|
8
|
-
import * as path from 'path'
|
|
9
|
-
import { MemoryManager } from '@/memory'
|
|
10
|
-
import { VaultManager } from '@/vault'
|
|
11
|
-
import { ContextManager } from '@/context'
|
|
12
|
-
import { Phase12Manager } from '@/automation/phase12-manager'
|
|
13
|
-
import { RetrievalService } from '@/retrieval/service'
|
|
14
|
-
import { RetrievalPipeline } from '@/retrieval/pipeline'
|
|
15
|
-
import { InMemoryKnowledgeGraph } from '@/knowledge/graph/memory-graph'
|
|
16
|
-
import { GraphSearchEngine } from '@/knowledge/graph/search'
|
|
17
|
-
import { KnowledgeGraphBuilder } from '@/knowledge/graph/builder'
|
|
18
|
-
import { CrossReferenceLinker } from '@/knowledge/graph/linker'
|
|
19
|
-
import { EpisodeManager } from '@/memory/episodic/manager'
|
|
20
|
-
import { HookSessionTracker } from '@/hooks/session-tracker'
|
|
21
|
-
import type { Config } from '@/config'
|
|
22
|
-
import type { CodeIndexer } from '@/code-intelligence/indexer'
|
|
23
|
-
import type { CodeQuery } from '@/code-intelligence/query'
|
|
24
|
-
import type { MemoryCodeLinker } from '@/code-intelligence/linker'
|
|
25
|
-
import { SemanticCache } from '@/intelligence/optimization/semantic-cache'
|
|
26
|
-
import { PrecomputeEngine } from '@/intelligence/optimization/precompute'
|
|
27
|
-
import { MemoryArchiver } from '@/memory/consolidation/archiver'
|
|
28
|
-
import { ImportanceScorer } from '@/memory/consolidation/scorer'
|
|
29
|
-
import { ModelManager } from '@/intelligence/model-manager'
|
|
30
|
-
import { InferenceRouter } from '@/intelligence/inference-router'
|
|
31
|
-
|
|
32
|
-
export interface KnowledgeGraphServiceContainer {
|
|
33
|
-
graph: InMemoryKnowledgeGraph
|
|
34
|
-
search: GraphSearchEngine
|
|
35
|
-
builder: KnowledgeGraphBuilder
|
|
36
|
-
linker: CrossReferenceLinker
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Container for all shared services
|
|
41
|
-
*/
|
|
42
|
-
export interface Services {
|
|
43
|
-
memory: MemoryManager
|
|
44
|
-
vault: VaultManager
|
|
45
|
-
context: ContextManager
|
|
46
|
-
phase12: Phase12Manager
|
|
47
|
-
retrieval: RetrievalService | null
|
|
48
|
-
retrievalPipeline: RetrievalPipeline | null
|
|
49
|
-
knowledgeGraph: KnowledgeGraphServiceContainer | null
|
|
50
|
-
episodeManager: EpisodeManager | null
|
|
51
|
-
sessionTracker: HookSessionTracker | null
|
|
52
|
-
semanticCache: SemanticCache | null
|
|
53
|
-
precompute: PrecomputeEngine | null
|
|
54
|
-
archiver: MemoryArchiver | null
|
|
55
|
-
codeIndexer: CodeIndexer | null
|
|
56
|
-
codeQuery: CodeQuery | null
|
|
57
|
-
codeLinker: MemoryCodeLinker | null
|
|
58
|
-
modelManager: ModelManager | null
|
|
59
|
-
inferenceRouter: InferenceRouter | null
|
|
60
|
-
logger: Logger
|
|
61
|
-
config: Config
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// Singleton services instance
|
|
65
|
-
let services: Services | null = null
|
|
66
|
-
let initializationPromise: Promise<void> | null = null
|
|
67
|
-
let archiveTimer: ReturnType<typeof setInterval> | null = null
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Initialize all services
|
|
71
|
-
* Should be called once during server startup
|
|
72
|
-
*/
|
|
73
|
-
export async function initializeServices(config: Config, logger: Logger): Promise<Services> {
|
|
74
|
-
// If already initialized, return existing services
|
|
75
|
-
if (services) {
|
|
76
|
-
logger.debug('Services already initialized')
|
|
77
|
-
return services
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// If initialization is in progress, wait for it
|
|
81
|
-
if (initializationPromise) {
|
|
82
|
-
await initializationPromise
|
|
83
|
-
return services!
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// Start initialization
|
|
87
|
-
initializationPromise = (async () => {
|
|
88
|
-
const serviceLogger = logger.child({ component: 'services' })
|
|
89
|
-
serviceLogger.info('Initializing services...')
|
|
90
|
-
|
|
91
|
-
// Phase 30: Auto-create vault path if it doesn't exist (zero-config)
|
|
92
|
-
if (!config.vaultPath) {
|
|
93
|
-
const { getHomePaths } = await import('@/config/home')
|
|
94
|
-
config.vaultPath = getHomePaths().vault
|
|
95
|
-
serviceLogger.info({ vaultPath: config.vaultPath }, 'Auto-detected vault path')
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
if (!existsSync(config.vaultPath)) {
|
|
99
|
-
try {
|
|
100
|
-
const { mkdirSync } = await import('node:fs')
|
|
101
|
-
mkdirSync(config.vaultPath, { recursive: true })
|
|
102
|
-
mkdirSync(path.join(config.vaultPath, 'Projects'), { recursive: true })
|
|
103
|
-
mkdirSync(path.join(config.vaultPath, 'Global'), { recursive: true })
|
|
104
|
-
serviceLogger.info({ vaultPath: config.vaultPath }, 'Created vault directory')
|
|
105
|
-
} catch (error) {
|
|
106
|
-
serviceLogger.warn({ error, vaultPath: config.vaultPath }, 'Failed to create vault directory, continuing without vault writes')
|
|
107
|
-
}
|
|
108
|
-
} else {
|
|
109
|
-
const projectsPath = path.join(config.vaultPath, 'Projects')
|
|
110
|
-
if (!existsSync(projectsPath)) {
|
|
111
|
-
try {
|
|
112
|
-
const { mkdirSync } = await import('node:fs')
|
|
113
|
-
mkdirSync(projectsPath, { recursive: true })
|
|
114
|
-
} catch {
|
|
115
|
-
serviceLogger.warn({ projectsPath }, 'Failed to create Projects directory')
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// Initialize Memory Manager
|
|
121
|
-
// Phase 26: ChromaDB is now opt-in (default false), FTS5 is primary
|
|
122
|
-
const useChromaDB = config.chromadb?.enabled ?? false
|
|
123
|
-
const memory = new MemoryManager(config.dbPath, logger, useChromaDB)
|
|
124
|
-
await memory.initialize()
|
|
125
|
-
serviceLogger.info({ chromadb: useChromaDB }, 'Memory service initialized')
|
|
126
|
-
|
|
127
|
-
// Initialize Vault Manager
|
|
128
|
-
const vault = new VaultManager(config.vaultPath, logger, {
|
|
129
|
-
cacheEnabled: config.cacheSize > 0,
|
|
130
|
-
watchEnabled: config.enableFileWatch
|
|
131
|
-
})
|
|
132
|
-
await vault.initialize()
|
|
133
|
-
serviceLogger.info('Vault service initialized')
|
|
134
|
-
|
|
135
|
-
// Initialize Context Manager
|
|
136
|
-
const context = new ContextManager(logger, vault, memory)
|
|
137
|
-
serviceLogger.info('Context service initialized')
|
|
138
|
-
|
|
139
|
-
// Initialize Phase 12 Manager
|
|
140
|
-
const phase12 = new Phase12Manager(logger, vault, memory, context)
|
|
141
|
-
await phase12.initialize()
|
|
142
|
-
serviceLogger.info('Phase 12 service initialized')
|
|
143
|
-
|
|
144
|
-
// Initialize Retrieval Service (Phase 13) — requires ChromaDB
|
|
145
|
-
let retrieval: RetrievalService | null = null
|
|
146
|
-
if ((config.retrieval?.feedback?.enabled || config.retrieval?.enabled) && memory.isChromaDBEnabled()) {
|
|
147
|
-
retrieval = new RetrievalService(
|
|
148
|
-
logger,
|
|
149
|
-
memory.chroma.collections,
|
|
150
|
-
memory.chroma.embeddings!,
|
|
151
|
-
config.retrieval
|
|
152
|
-
)
|
|
153
|
-
await retrieval.initialize()
|
|
154
|
-
serviceLogger.info('Retrieval service initialized')
|
|
155
|
-
} else if (config.retrieval?.enabled && !memory.isChromaDBEnabled()) {
|
|
156
|
-
serviceLogger.warn('Retrieval service requires ChromaDB, skipping initialization')
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
// Initialize Retrieval Pipeline (Phase 19) — hybrid search with BM25 + semantic + fusion
|
|
160
|
-
let retrievalPipeline: RetrievalPipeline | null = null
|
|
161
|
-
if (config.retrieval?.enabled && memory.isChromaDBEnabled()) {
|
|
162
|
-
try {
|
|
163
|
-
retrievalPipeline = new RetrievalPipeline(
|
|
164
|
-
logger,
|
|
165
|
-
memory.chroma.collections,
|
|
166
|
-
memory.chroma.embeddings!,
|
|
167
|
-
config.retrieval
|
|
168
|
-
)
|
|
169
|
-
await retrievalPipeline.initialize()
|
|
170
|
-
serviceLogger.info('Retrieval pipeline initialized (hybrid search)')
|
|
171
|
-
} catch (error) {
|
|
172
|
-
serviceLogger.warn({ error }, 'Failed to initialize retrieval pipeline, continuing without hybrid search')
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
// Initialize Knowledge Graph Service (Phase 14)
|
|
177
|
-
let knowledgeGraph: KnowledgeGraphServiceContainer | null = null
|
|
178
|
-
if (config.knowledge?.graph?.enabled !== false) {
|
|
179
|
-
try {
|
|
180
|
-
const graph = new InMemoryKnowledgeGraph(logger)
|
|
181
|
-
// Use absolute path from getHomePaths() if persistPath not configured
|
|
182
|
-
const { getHomePaths: getHomePathsKG } = await import('@/config/home')
|
|
183
|
-
const graphPersistPath = config.knowledge?.graph?.persistPath || getHomePathsKG().knowledgeGraph
|
|
184
|
-
|
|
185
|
-
// Load existing graph from disk
|
|
186
|
-
await graph.load(graphPersistPath)
|
|
187
|
-
|
|
188
|
-
const search = new GraphSearchEngine(graph, logger)
|
|
189
|
-
const builder = new KnowledgeGraphBuilder(graph, logger)
|
|
190
|
-
await builder.initialize()
|
|
191
|
-
const linker = new CrossReferenceLinker(graph, logger)
|
|
192
|
-
|
|
193
|
-
// Start auto-save
|
|
194
|
-
const autoSaveInterval = (config.knowledge?.graph?.autoSaveInterval || 60) * 1000
|
|
195
|
-
graph.startAutoSave(graphPersistPath, autoSaveInterval)
|
|
196
|
-
|
|
197
|
-
knowledgeGraph = { graph, search, builder, linker }
|
|
198
|
-
|
|
199
|
-
// Migrate existing decisions if graph is empty (requires ChromaDB)
|
|
200
|
-
if (graph.getNodeCount() === 0 && memory.isChromaDBEnabled()) {
|
|
201
|
-
serviceLogger.info('Empty graph detected, migrating existing decisions...')
|
|
202
|
-
const migrationResult = await builder.migrateExistingDecisions(memory.chroma.collections)
|
|
203
|
-
serviceLogger.info(
|
|
204
|
-
{ processed: migrationResult.processed, errors: migrationResult.errors },
|
|
205
|
-
'Initial graph migration complete'
|
|
206
|
-
)
|
|
207
|
-
} else if (graph.getNodeCount() === 0) {
|
|
208
|
-
serviceLogger.info('Empty graph detected, but ChromaDB unavailable — graph will populate as new decisions are stored')
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
// Hook builder into decision storage for real-time graph population
|
|
212
|
-
// Uses MemoryManager listener so it works for both ChromaDB and SQLite paths
|
|
213
|
-
memory.addDecisionStoredListener((input) => {
|
|
214
|
-
builder.processDecision(input)
|
|
215
|
-
})
|
|
216
|
-
|
|
217
|
-
// Hook builder into decision deletion for graph synchronization
|
|
218
|
-
memory.addDecisionDeletedListener((id) => {
|
|
219
|
-
builder.removeDecision(id)
|
|
220
|
-
|
|
221
|
-
// Phase 20: Also unlink from active episodes
|
|
222
|
-
if (episodeManager) {
|
|
223
|
-
episodeManager.unlinkDecision(id)
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
// Phase 20: Invalidate semantic cache for deleted decisions
|
|
227
|
-
if (semanticCache) {
|
|
228
|
-
semanticCache.clear()
|
|
229
|
-
}
|
|
230
|
-
})
|
|
231
|
-
|
|
232
|
-
serviceLogger.info(
|
|
233
|
-
{ nodes: graph.getNodeCount(), edges: graph.getEdgeCount() },
|
|
234
|
-
'Knowledge graph service initialized'
|
|
235
|
-
)
|
|
236
|
-
} catch (error) {
|
|
237
|
-
serviceLogger.warn({ error }, 'Failed to initialize knowledge graph, continuing without it')
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
// Initialize Episode Manager (Phase 14) — requires ChromaDB
|
|
242
|
-
let episodeManager: EpisodeManager | null = null
|
|
243
|
-
if (config.knowledge?.episodic?.enabled !== false && memory.isChromaDBEnabled()) {
|
|
244
|
-
try {
|
|
245
|
-
episodeManager = new EpisodeManager(
|
|
246
|
-
logger,
|
|
247
|
-
memory.chroma.collections,
|
|
248
|
-
memory.chroma.embeddings,
|
|
249
|
-
{ sessionGapMinutes: config.knowledge?.episodic?.sessionGapMinutes || 30 }
|
|
250
|
-
)
|
|
251
|
-
serviceLogger.info('Episode manager initialized')
|
|
252
|
-
} catch (error) {
|
|
253
|
-
serviceLogger.warn({ error }, 'Failed to initialize episode manager, continuing without it')
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
// Initialize Session Tracker (Phase 21) — promoted from serve.ts
|
|
258
|
-
let sessionTracker: HookSessionTracker | null = null
|
|
259
|
-
if (config.hooks?.enabled !== false) {
|
|
260
|
-
try {
|
|
261
|
-
sessionTracker = new HookSessionTracker(logger, episodeManager, config.hooks?.sessions, memory)
|
|
262
|
-
serviceLogger.info('Session tracker initialized')
|
|
263
|
-
} catch (error) {
|
|
264
|
-
serviceLogger.warn({ error }, 'Failed to initialize session tracker, continuing without it')
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
// Initialize Semantic Cache & Precompute (Phase 15) — requires ChromaDB
|
|
269
|
-
let semanticCache: SemanticCache | null = null
|
|
270
|
-
let precompute: PrecomputeEngine | null = null
|
|
271
|
-
if (config.advancedIntelligence?.enabled !== false && config.advancedIntelligence?.cache?.enabled !== false && memory.isChromaDBEnabled()) {
|
|
272
|
-
try {
|
|
273
|
-
const cacheConfig = config.advancedIntelligence?.cache || {}
|
|
274
|
-
semanticCache = new SemanticCache(logger, {
|
|
275
|
-
maxSize: cacheConfig.maxSize || 1000,
|
|
276
|
-
ttlMs: cacheConfig.ttlMs || 60 * 60 * 1000,
|
|
277
|
-
similarityThreshold: cacheConfig.similarityThreshold || 0.80,
|
|
278
|
-
embeddings: memory.chroma.embeddings
|
|
279
|
-
})
|
|
280
|
-
|
|
281
|
-
precompute = new PrecomputeEngine(logger, memory.chroma.collections, semanticCache)
|
|
282
|
-
await precompute.initialize()
|
|
283
|
-
precompute.startAutoRefresh(5 * 60 * 1000) // Refresh every 5 minutes
|
|
284
|
-
|
|
285
|
-
serviceLogger.info('Phase 15 semantic cache & precompute initialized')
|
|
286
|
-
} catch (error) {
|
|
287
|
-
serviceLogger.warn({ error }, 'Failed to initialize Phase 15 caching, continuing without it')
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
// Initialize Memory Archiver — periodic archival of low-importance memories
|
|
292
|
-
let archiver: MemoryArchiver | null = null
|
|
293
|
-
const consolidation = config.knowledge?.consolidation
|
|
294
|
-
if (consolidation?.enabled !== false && memory.isChromaDBEnabled()) {
|
|
295
|
-
try {
|
|
296
|
-
const scorer = new ImportanceScorer(logger, consolidation?.weights)
|
|
297
|
-
archiver = new MemoryArchiver(
|
|
298
|
-
logger,
|
|
299
|
-
memory.chroma.collections,
|
|
300
|
-
scorer,
|
|
301
|
-
memory.chroma.embeddings
|
|
302
|
-
)
|
|
303
|
-
|
|
304
|
-
const intervalMinutes = consolidation?.archiveIntervalMinutes ?? 60
|
|
305
|
-
if (intervalMinutes > 0) {
|
|
306
|
-
const threshold = consolidation?.archiveThreshold ?? 0.2
|
|
307
|
-
archiveTimer = setInterval(async () => {
|
|
308
|
-
try {
|
|
309
|
-
const result = await archiver!.archiveLowImportance(threshold)
|
|
310
|
-
if (result.archivedCount > 0) {
|
|
311
|
-
serviceLogger.info(
|
|
312
|
-
{ archived: result.archivedCount, threshold },
|
|
313
|
-
'Periodic archival sweep complete'
|
|
314
|
-
)
|
|
315
|
-
}
|
|
316
|
-
} catch (error) {
|
|
317
|
-
serviceLogger.warn({ error }, 'Periodic archival sweep failed')
|
|
318
|
-
}
|
|
319
|
-
}, intervalMinutes * 60 * 1000)
|
|
320
|
-
serviceLogger.info(
|
|
321
|
-
{ intervalMinutes, threshold: consolidation?.archiveThreshold ?? 0.2 },
|
|
322
|
-
'Memory archiver initialized with periodic sweep'
|
|
323
|
-
)
|
|
324
|
-
} else {
|
|
325
|
-
serviceLogger.info('Memory archiver initialized (periodic sweep disabled)')
|
|
326
|
-
}
|
|
327
|
-
} catch (error) {
|
|
328
|
-
serviceLogger.warn({ error }, 'Failed to initialize memory archiver, continuing without it')
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
// Initialize Code Intelligence (Phase 28)
|
|
333
|
-
let codeIndexer: CodeIndexer | null = null
|
|
334
|
-
let codeQuery: CodeQuery | null = null
|
|
335
|
-
let codeLinker: MemoryCodeLinker | null = null
|
|
336
|
-
let codeDbInstance: import('bun:sqlite').Database | null = null
|
|
337
|
-
if (config.codeIntelligence?.enabled !== false) {
|
|
338
|
-
try {
|
|
339
|
-
const { Database } = await import('bun:sqlite')
|
|
340
|
-
const { resolve, dirname } = await import('node:path')
|
|
341
|
-
const { mkdirSync, existsSync } = await import('node:fs')
|
|
342
|
-
const { getClaudeBrainHome } = await import('@/config/home')
|
|
343
|
-
const codeDbDir = resolve(getClaudeBrainHome(), 'data')
|
|
344
|
-
if (!existsSync(codeDbDir)) {
|
|
345
|
-
mkdirSync(codeDbDir, { recursive: true })
|
|
346
|
-
}
|
|
347
|
-
const codeDbPath = resolve(codeDbDir, 'code.db')
|
|
348
|
-
const codeDb = new Database(codeDbPath)
|
|
349
|
-
codeDb.exec('PRAGMA journal_mode=WAL')
|
|
350
|
-
codeDbInstance = codeDb
|
|
351
|
-
|
|
352
|
-
const { CodeParser } = await import('@/code-intelligence/parser')
|
|
353
|
-
const { CodeIndexer: CodeIndexerClass } = await import('@/code-intelligence/indexer')
|
|
354
|
-
const { CodeQuery: CodeQueryClass } = await import('@/code-intelligence/query')
|
|
355
|
-
|
|
356
|
-
const parser = new CodeParser()
|
|
357
|
-
await parser.initialize()
|
|
358
|
-
|
|
359
|
-
codeIndexer = new CodeIndexerClass(codeDb, parser, serviceLogger)
|
|
360
|
-
await codeIndexer.initialize()
|
|
361
|
-
|
|
362
|
-
codeQuery = new CodeQueryClass(codeDb, serviceLogger)
|
|
363
|
-
|
|
364
|
-
serviceLogger.info('Code intelligence service initialized')
|
|
365
|
-
} catch (error) {
|
|
366
|
-
serviceLogger.warn({ error }, 'Failed to initialize code intelligence, continuing without it')
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
// Initialize MemoryCodeLinker (Phase 29)
|
|
371
|
-
try {
|
|
372
|
-
const { MemoryCodeLinker: LinkerClass } = await import('@/code-intelligence/linker')
|
|
373
|
-
const memoryDb = memory.database.getDb()
|
|
374
|
-
codeLinker = new LinkerClass(memoryDb, codeDbInstance, serviceLogger)
|
|
375
|
-
serviceLogger.info('Code linker initialized')
|
|
376
|
-
} catch (error) {
|
|
377
|
-
serviceLogger.warn({ error }, 'Failed to initialize code linker, continuing without it')
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
// Initialize SLM Model Manager & Inference Router
|
|
381
|
-
let modelManager: ModelManager | null = null
|
|
382
|
-
let inferenceRouter: InferenceRouter | null = null
|
|
383
|
-
try {
|
|
384
|
-
const slmModelsDir = config.slm?.modelsDir?.replace(/^~/, require('os').homedir())
|
|
385
|
-
modelManager = new ModelManager(serviceLogger, slmModelsDir)
|
|
386
|
-
inferenceRouter = new InferenceRouter(serviceLogger, config, modelManager)
|
|
387
|
-
const slmEnabled = config.slm?.enabled ?? false
|
|
388
|
-
serviceLogger.info({ enabled: slmEnabled }, 'SLM inference router initialized')
|
|
389
|
-
} catch (error) {
|
|
390
|
-
serviceLogger.warn({ error }, 'Failed to initialize SLM inference, continuing with regex only')
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
// Wire SLM inference into PatternRecognizer (Phase 4C)
|
|
394
|
-
if (inferenceRouter && phase12) {
|
|
395
|
-
phase12.patterns.setInferenceRouter(inferenceRouter)
|
|
396
|
-
serviceLogger.info('SLM inference wired into PatternRecognizer')
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
// SLM startup detection: log model availability and configuration state
|
|
400
|
-
if (modelManager) {
|
|
401
|
-
const modelStatus = modelManager.getStatus()
|
|
402
|
-
const modelsDir = modelManager.getModelsDir()
|
|
403
|
-
const availableCount = Object.values(modelStatus).filter(s => s.available).length
|
|
404
|
-
const slmEnabled = config.slm?.enabled ?? false
|
|
405
|
-
|
|
406
|
-
if (availableCount > 0 && !slmEnabled) {
|
|
407
|
-
serviceLogger.info(
|
|
408
|
-
{ count: availableCount, modelsDir },
|
|
409
|
-
`Found ${availableCount} ONNX models in ${modelsDir}. SLM is disabled. Run "claude-brain models enable all" to activate local inference.`
|
|
410
|
-
)
|
|
411
|
-
} else if (availableCount > 0 && slmEnabled) {
|
|
412
|
-
const tasks = config.slm?.tasks ?? {}
|
|
413
|
-
const enabledTasks = Object.entries(tasks)
|
|
414
|
-
.filter(([_, mode]) => mode === 'model' || mode === 'both')
|
|
415
|
-
.map(([task]) => task)
|
|
416
|
-
serviceLogger.info(
|
|
417
|
-
{ count: availableCount, enabledTasks },
|
|
418
|
-
`SLM active: ${enabledTasks.length} tasks using local model inference`
|
|
419
|
-
)
|
|
420
|
-
|
|
421
|
-
// Check onnxruntime availability when SLM is enabled
|
|
422
|
-
const onnxAvailable = await modelManager.isOnnxAvailable()
|
|
423
|
-
if (!onnxAvailable) {
|
|
424
|
-
serviceLogger.warn('SLM enabled but onnxruntime-node not installed. Run: bun add onnxruntime-node')
|
|
425
|
-
}
|
|
426
|
-
} else if (availableCount === 0 && slmEnabled) {
|
|
427
|
-
serviceLogger.warn(
|
|
428
|
-
{ modelsDir },
|
|
429
|
-
`SLM enabled but no models found in ${modelsDir}. Run "claude-brain models download" to install models.`
|
|
430
|
-
)
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
// Store services
|
|
435
|
-
services = {
|
|
436
|
-
memory,
|
|
437
|
-
vault,
|
|
438
|
-
context,
|
|
439
|
-
phase12,
|
|
440
|
-
retrieval,
|
|
441
|
-
retrievalPipeline,
|
|
442
|
-
knowledgeGraph,
|
|
443
|
-
episodeManager,
|
|
444
|
-
sessionTracker,
|
|
445
|
-
semanticCache,
|
|
446
|
-
precompute,
|
|
447
|
-
archiver,
|
|
448
|
-
codeIndexer,
|
|
449
|
-
codeQuery,
|
|
450
|
-
codeLinker,
|
|
451
|
-
modelManager,
|
|
452
|
-
inferenceRouter,
|
|
453
|
-
logger,
|
|
454
|
-
config
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
serviceLogger.info('All services initialized successfully')
|
|
458
|
-
})()
|
|
459
|
-
|
|
460
|
-
await initializationPromise
|
|
461
|
-
return services!
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
/**
|
|
465
|
-
* Get initialized services
|
|
466
|
-
* Throws if services not initialized
|
|
467
|
-
*/
|
|
468
|
-
export function getServices(): Services {
|
|
469
|
-
if (!services) {
|
|
470
|
-
throw new Error('Services not initialized. Call initializeServices() first.')
|
|
471
|
-
}
|
|
472
|
-
return services
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
/**
|
|
476
|
-
* Get memory service
|
|
477
|
-
* Throws if not initialized
|
|
478
|
-
*/
|
|
479
|
-
export function getMemoryService(): MemoryManager {
|
|
480
|
-
return getServices().memory
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
/**
|
|
484
|
-
* Get vault service
|
|
485
|
-
* Throws if not initialized
|
|
486
|
-
*/
|
|
487
|
-
export function getVaultService(): VaultManager {
|
|
488
|
-
return getServices().vault
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
export function getContextService(): ContextManager {
|
|
492
|
-
return getServices().context
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
/**
|
|
496
|
-
* Get Phase 12 service
|
|
497
|
-
* Throws if not initialized
|
|
498
|
-
*/
|
|
499
|
-
export function getPhase12Service(): Phase12Manager {
|
|
500
|
-
return getServices().phase12
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
/**
|
|
504
|
-
* Get Retrieval service
|
|
505
|
-
* Returns null if retrieval is not enabled
|
|
506
|
-
*/
|
|
507
|
-
export function getRetrievalService(): RetrievalService | null {
|
|
508
|
-
return getServices().retrieval
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
/**
|
|
512
|
-
* Get Retrieval Pipeline (Phase 19)
|
|
513
|
-
* Returns null if hybrid search is not enabled
|
|
514
|
-
*/
|
|
515
|
-
export function getRetrievalPipeline(): RetrievalPipeline | null {
|
|
516
|
-
return getServices().retrievalPipeline
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
/**
|
|
520
|
-
* Get Knowledge Graph service
|
|
521
|
-
* Returns null if knowledge graph is not enabled
|
|
522
|
-
*/
|
|
523
|
-
export function getKnowledgeGraphService(): KnowledgeGraphServiceContainer | null {
|
|
524
|
-
return getServices().knowledgeGraph
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
/**
|
|
528
|
-
* Get Episode Manager service
|
|
529
|
-
* Returns null if episodic memory is not enabled
|
|
530
|
-
*/
|
|
531
|
-
export function getEpisodeService(): EpisodeManager | null {
|
|
532
|
-
return getServices().episodeManager
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
/**
|
|
536
|
-
* Get Session Tracker service
|
|
537
|
-
* Returns null if hooks are not enabled
|
|
538
|
-
*/
|
|
539
|
-
export function getSessionTracker(): HookSessionTracker | null {
|
|
540
|
-
return getServices().sessionTracker
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
/**
|
|
544
|
-
* Get Semantic Cache service
|
|
545
|
-
* Returns null if caching is not enabled
|
|
546
|
-
*/
|
|
547
|
-
export function getSemanticCacheService(): SemanticCache | null {
|
|
548
|
-
return getServices().semanticCache
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
/**
|
|
552
|
-
* Get Precompute Engine service
|
|
553
|
-
* Returns null if caching is not enabled
|
|
554
|
-
*/
|
|
555
|
-
export function getPrecomputeService(): PrecomputeEngine | null {
|
|
556
|
-
return getServices().precompute
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
/**
|
|
560
|
-
* Get Memory Archiver service
|
|
561
|
-
* Returns null if consolidation is not enabled
|
|
562
|
-
*/
|
|
563
|
-
export function getArchiverService(): MemoryArchiver | null {
|
|
564
|
-
return getServices().archiver
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
/**
|
|
568
|
-
* Get Code Indexer service (Phase 28)
|
|
569
|
-
* Returns null if code intelligence is not enabled
|
|
570
|
-
*/
|
|
571
|
-
export function getCodeIndexer(): CodeIndexer | null {
|
|
572
|
-
return services?.codeIndexer ?? null
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
/**
|
|
576
|
-
* Get Code Query service (Phase 28)
|
|
577
|
-
* Returns null if code intelligence is not enabled
|
|
578
|
-
*/
|
|
579
|
-
export function getCodeQuery(): CodeQuery | null {
|
|
580
|
-
return services?.codeQuery ?? null
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
/**
|
|
584
|
-
* Get Code Linker service (Phase 29)
|
|
585
|
-
* Returns null if not initialized
|
|
586
|
-
*/
|
|
587
|
-
export function getCodeLinker(): MemoryCodeLinker | null {
|
|
588
|
-
return services?.codeLinker ?? null
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
/**
|
|
592
|
-
* Get Model Manager (SLM Upgrade)
|
|
593
|
-
* Returns null if SLM is not initialized
|
|
594
|
-
*/
|
|
595
|
-
export function getModelManager(): ModelManager | null {
|
|
596
|
-
return services?.modelManager ?? null
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
/**
|
|
600
|
-
* Get Inference Router (SLM Upgrade)
|
|
601
|
-
* Returns null if SLM is not initialized
|
|
602
|
-
*/
|
|
603
|
-
export function getInferenceRouter(): InferenceRouter | null {
|
|
604
|
-
return services?.inferenceRouter ?? null
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
/**
|
|
608
|
-
* Check if services are initialized
|
|
609
|
-
*/
|
|
610
|
-
export function isServicesInitialized(): boolean {
|
|
611
|
-
return services !== null
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
/**
|
|
615
|
-
* Inject services for testing
|
|
616
|
-
* Allows tests to provide mock services without going through initializeServices
|
|
617
|
-
* @internal - Only use in tests
|
|
618
|
-
*/
|
|
619
|
-
export function _injectServicesForTesting(testServices: Services | null): void {
|
|
620
|
-
services = testServices
|
|
621
|
-
}
|
|
622
|
-
|
|
623
|
-
/**
|
|
624
|
-
* Reset services for testing
|
|
625
|
-
* Clears the services singleton without proper shutdown
|
|
626
|
-
* @internal - Only use in tests
|
|
627
|
-
*/
|
|
628
|
-
export function _resetServicesForTesting(): void {
|
|
629
|
-
services = null
|
|
630
|
-
initializationPromise = null
|
|
631
|
-
if (archiveTimer) {
|
|
632
|
-
clearInterval(archiveTimer)
|
|
633
|
-
archiveTimer = null
|
|
634
|
-
}
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
/**
|
|
638
|
-
* Shutdown all services gracefully
|
|
639
|
-
*/
|
|
640
|
-
export async function shutdownServices(): Promise<void> {
|
|
641
|
-
if (!services) {
|
|
642
|
-
return
|
|
643
|
-
}
|
|
644
|
-
|
|
645
|
-
const serviceLogger = services.logger.child({ component: 'services' })
|
|
646
|
-
serviceLogger.info('Shutting down services...')
|
|
647
|
-
|
|
648
|
-
// End all active sessions
|
|
649
|
-
if (services.sessionTracker) {
|
|
650
|
-
try {
|
|
651
|
-
await services.sessionTracker.endAllSessions()
|
|
652
|
-
serviceLogger.info('Session tracker shut down')
|
|
653
|
-
} catch (error) {
|
|
654
|
-
serviceLogger.error({ error }, 'Failed to end sessions on shutdown')
|
|
655
|
-
}
|
|
656
|
-
}
|
|
657
|
-
|
|
658
|
-
// Save and stop knowledge graph
|
|
659
|
-
if (services.knowledgeGraph) {
|
|
660
|
-
services.knowledgeGraph.graph.stopAutoSave()
|
|
661
|
-
// Use absolute path from getHomePaths() if persistPath not configured
|
|
662
|
-
const { getHomePaths } = await import('@/config/home')
|
|
663
|
-
const persistPath = services.config.knowledge?.graph?.persistPath || getHomePaths().knowledgeGraph
|
|
664
|
-
try {
|
|
665
|
-
await services.knowledgeGraph.graph.save(persistPath)
|
|
666
|
-
serviceLogger.info('Knowledge graph saved on shutdown')
|
|
667
|
-
} catch (error) {
|
|
668
|
-
serviceLogger.error({ error }, 'Failed to save knowledge graph on shutdown')
|
|
669
|
-
}
|
|
670
|
-
}
|
|
671
|
-
|
|
672
|
-
// Stop precompute auto-refresh
|
|
673
|
-
if (services.precompute) {
|
|
674
|
-
services.precompute.stopAutoRefresh()
|
|
675
|
-
serviceLogger.info('Precompute engine stopped')
|
|
676
|
-
}
|
|
677
|
-
|
|
678
|
-
// Clear semantic cache
|
|
679
|
-
if (services.semanticCache) {
|
|
680
|
-
services.semanticCache.clear()
|
|
681
|
-
serviceLogger.info('Semantic cache cleared')
|
|
682
|
-
}
|
|
683
|
-
|
|
684
|
-
// Stop archival timer
|
|
685
|
-
if (archiveTimer) {
|
|
686
|
-
clearInterval(archiveTimer)
|
|
687
|
-
archiveTimer = null
|
|
688
|
-
serviceLogger.info('Memory archiver stopped')
|
|
689
|
-
}
|
|
690
|
-
|
|
691
|
-
// Shut down code intelligence
|
|
692
|
-
if (services.codeIndexer) {
|
|
693
|
-
try {
|
|
694
|
-
serviceLogger.info('Code intelligence shut down')
|
|
695
|
-
} catch (error) {
|
|
696
|
-
serviceLogger.error({ error }, 'Failed to shut down code intelligence')
|
|
697
|
-
}
|
|
698
|
-
}
|
|
699
|
-
|
|
700
|
-
// Unload SLM models (releases ONNX sessions)
|
|
701
|
-
if (services.modelManager) {
|
|
702
|
-
await services.modelManager.unloadAll()
|
|
703
|
-
serviceLogger.info('SLM models unloaded')
|
|
704
|
-
}
|
|
705
|
-
|
|
706
|
-
// Cleanup Phase 12
|
|
707
|
-
services.phase12.cleanup()
|
|
708
|
-
|
|
709
|
-
// Stop vault watcher
|
|
710
|
-
services.vault.stopWatching()
|
|
711
|
-
|
|
712
|
-
// Close memory database
|
|
713
|
-
services.memory.close()
|
|
714
|
-
|
|
715
|
-
// Clear reference
|
|
716
|
-
services = null
|
|
717
|
-
initializationPromise = null
|
|
718
|
-
|
|
719
|
-
serviceLogger.info('All services shut down')
|
|
720
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Shared Services Module
|
|
3
|
+
* Provides access to initialized services for tool handlers
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { Logger } from 'pino'
|
|
7
|
+
import { existsSync } from 'fs'
|
|
8
|
+
import * as path from 'path'
|
|
9
|
+
import { MemoryManager } from '@/memory'
|
|
10
|
+
import { VaultManager } from '@/vault'
|
|
11
|
+
import { ContextManager } from '@/context'
|
|
12
|
+
import { Phase12Manager } from '@/automation/phase12-manager'
|
|
13
|
+
import { RetrievalService } from '@/retrieval/service'
|
|
14
|
+
import { RetrievalPipeline } from '@/retrieval/pipeline'
|
|
15
|
+
import { InMemoryKnowledgeGraph } from '@/knowledge/graph/memory-graph'
|
|
16
|
+
import { GraphSearchEngine } from '@/knowledge/graph/search'
|
|
17
|
+
import { KnowledgeGraphBuilder } from '@/knowledge/graph/builder'
|
|
18
|
+
import { CrossReferenceLinker } from '@/knowledge/graph/linker'
|
|
19
|
+
import { EpisodeManager } from '@/memory/episodic/manager'
|
|
20
|
+
import { HookSessionTracker } from '@/hooks/session-tracker'
|
|
21
|
+
import type { Config } from '@/config'
|
|
22
|
+
import type { CodeIndexer } from '@/code-intelligence/indexer'
|
|
23
|
+
import type { CodeQuery } from '@/code-intelligence/query'
|
|
24
|
+
import type { MemoryCodeLinker } from '@/code-intelligence/linker'
|
|
25
|
+
import { SemanticCache } from '@/intelligence/optimization/semantic-cache'
|
|
26
|
+
import { PrecomputeEngine } from '@/intelligence/optimization/precompute'
|
|
27
|
+
import { MemoryArchiver } from '@/memory/consolidation/archiver'
|
|
28
|
+
import { ImportanceScorer } from '@/memory/consolidation/scorer'
|
|
29
|
+
import { ModelManager } from '@/intelligence/model-manager'
|
|
30
|
+
import { InferenceRouter } from '@/intelligence/inference-router'
|
|
31
|
+
|
|
32
|
+
export interface KnowledgeGraphServiceContainer {
|
|
33
|
+
graph: InMemoryKnowledgeGraph
|
|
34
|
+
search: GraphSearchEngine
|
|
35
|
+
builder: KnowledgeGraphBuilder
|
|
36
|
+
linker: CrossReferenceLinker
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Container for all shared services
|
|
41
|
+
*/
|
|
42
|
+
export interface Services {
|
|
43
|
+
memory: MemoryManager
|
|
44
|
+
vault: VaultManager
|
|
45
|
+
context: ContextManager
|
|
46
|
+
phase12: Phase12Manager
|
|
47
|
+
retrieval: RetrievalService | null
|
|
48
|
+
retrievalPipeline: RetrievalPipeline | null
|
|
49
|
+
knowledgeGraph: KnowledgeGraphServiceContainer | null
|
|
50
|
+
episodeManager: EpisodeManager | null
|
|
51
|
+
sessionTracker: HookSessionTracker | null
|
|
52
|
+
semanticCache: SemanticCache | null
|
|
53
|
+
precompute: PrecomputeEngine | null
|
|
54
|
+
archiver: MemoryArchiver | null
|
|
55
|
+
codeIndexer: CodeIndexer | null
|
|
56
|
+
codeQuery: CodeQuery | null
|
|
57
|
+
codeLinker: MemoryCodeLinker | null
|
|
58
|
+
modelManager: ModelManager | null
|
|
59
|
+
inferenceRouter: InferenceRouter | null
|
|
60
|
+
logger: Logger
|
|
61
|
+
config: Config
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Singleton services instance
|
|
65
|
+
let services: Services | null = null
|
|
66
|
+
let initializationPromise: Promise<void> | null = null
|
|
67
|
+
let archiveTimer: ReturnType<typeof setInterval> | null = null
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Initialize all services
|
|
71
|
+
* Should be called once during server startup
|
|
72
|
+
*/
|
|
73
|
+
export async function initializeServices(config: Config, logger: Logger): Promise<Services> {
|
|
74
|
+
// If already initialized, return existing services
|
|
75
|
+
if (services) {
|
|
76
|
+
logger.debug('Services already initialized')
|
|
77
|
+
return services
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// If initialization is in progress, wait for it
|
|
81
|
+
if (initializationPromise) {
|
|
82
|
+
await initializationPromise
|
|
83
|
+
return services!
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Start initialization
|
|
87
|
+
initializationPromise = (async () => {
|
|
88
|
+
const serviceLogger = logger.child({ component: 'services' })
|
|
89
|
+
serviceLogger.info('Initializing services...')
|
|
90
|
+
|
|
91
|
+
// Phase 30: Auto-create vault path if it doesn't exist (zero-config)
|
|
92
|
+
if (!config.vaultPath) {
|
|
93
|
+
const { getHomePaths } = await import('@/config/home')
|
|
94
|
+
config.vaultPath = getHomePaths().vault
|
|
95
|
+
serviceLogger.info({ vaultPath: config.vaultPath }, 'Auto-detected vault path')
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (!existsSync(config.vaultPath)) {
|
|
99
|
+
try {
|
|
100
|
+
const { mkdirSync } = await import('node:fs')
|
|
101
|
+
mkdirSync(config.vaultPath, { recursive: true })
|
|
102
|
+
mkdirSync(path.join(config.vaultPath, 'Projects'), { recursive: true })
|
|
103
|
+
mkdirSync(path.join(config.vaultPath, 'Global'), { recursive: true })
|
|
104
|
+
serviceLogger.info({ vaultPath: config.vaultPath }, 'Created vault directory')
|
|
105
|
+
} catch (error) {
|
|
106
|
+
serviceLogger.warn({ error, vaultPath: config.vaultPath }, 'Failed to create vault directory, continuing without vault writes')
|
|
107
|
+
}
|
|
108
|
+
} else {
|
|
109
|
+
const projectsPath = path.join(config.vaultPath, 'Projects')
|
|
110
|
+
if (!existsSync(projectsPath)) {
|
|
111
|
+
try {
|
|
112
|
+
const { mkdirSync } = await import('node:fs')
|
|
113
|
+
mkdirSync(projectsPath, { recursive: true })
|
|
114
|
+
} catch {
|
|
115
|
+
serviceLogger.warn({ projectsPath }, 'Failed to create Projects directory')
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Initialize Memory Manager
|
|
121
|
+
// Phase 26: ChromaDB is now opt-in (default false), FTS5 is primary
|
|
122
|
+
const useChromaDB = config.chromadb?.enabled ?? false
|
|
123
|
+
const memory = new MemoryManager(config.dbPath, logger, useChromaDB)
|
|
124
|
+
await memory.initialize()
|
|
125
|
+
serviceLogger.info({ chromadb: useChromaDB }, 'Memory service initialized')
|
|
126
|
+
|
|
127
|
+
// Initialize Vault Manager
|
|
128
|
+
const vault = new VaultManager(config.vaultPath, logger, {
|
|
129
|
+
cacheEnabled: config.cacheSize > 0,
|
|
130
|
+
watchEnabled: config.enableFileWatch
|
|
131
|
+
})
|
|
132
|
+
await vault.initialize()
|
|
133
|
+
serviceLogger.info('Vault service initialized')
|
|
134
|
+
|
|
135
|
+
// Initialize Context Manager
|
|
136
|
+
const context = new ContextManager(logger, vault, memory)
|
|
137
|
+
serviceLogger.info('Context service initialized')
|
|
138
|
+
|
|
139
|
+
// Initialize Phase 12 Manager
|
|
140
|
+
const phase12 = new Phase12Manager(logger, vault, memory, context)
|
|
141
|
+
await phase12.initialize()
|
|
142
|
+
serviceLogger.info('Phase 12 service initialized')
|
|
143
|
+
|
|
144
|
+
// Initialize Retrieval Service (Phase 13) — requires ChromaDB
|
|
145
|
+
let retrieval: RetrievalService | null = null
|
|
146
|
+
if ((config.retrieval?.feedback?.enabled || config.retrieval?.enabled) && memory.isChromaDBEnabled()) {
|
|
147
|
+
retrieval = new RetrievalService(
|
|
148
|
+
logger,
|
|
149
|
+
memory.chroma.collections,
|
|
150
|
+
memory.chroma.embeddings!,
|
|
151
|
+
config.retrieval
|
|
152
|
+
)
|
|
153
|
+
await retrieval.initialize()
|
|
154
|
+
serviceLogger.info('Retrieval service initialized')
|
|
155
|
+
} else if (config.retrieval?.enabled && !memory.isChromaDBEnabled()) {
|
|
156
|
+
serviceLogger.warn('Retrieval service requires ChromaDB, skipping initialization')
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Initialize Retrieval Pipeline (Phase 19) — hybrid search with BM25 + semantic + fusion
|
|
160
|
+
let retrievalPipeline: RetrievalPipeline | null = null
|
|
161
|
+
if (config.retrieval?.enabled && memory.isChromaDBEnabled()) {
|
|
162
|
+
try {
|
|
163
|
+
retrievalPipeline = new RetrievalPipeline(
|
|
164
|
+
logger,
|
|
165
|
+
memory.chroma.collections,
|
|
166
|
+
memory.chroma.embeddings!,
|
|
167
|
+
config.retrieval
|
|
168
|
+
)
|
|
169
|
+
await retrievalPipeline.initialize()
|
|
170
|
+
serviceLogger.info('Retrieval pipeline initialized (hybrid search)')
|
|
171
|
+
} catch (error) {
|
|
172
|
+
serviceLogger.warn({ error }, 'Failed to initialize retrieval pipeline, continuing without hybrid search')
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Initialize Knowledge Graph Service (Phase 14)
|
|
177
|
+
let knowledgeGraph: KnowledgeGraphServiceContainer | null = null
|
|
178
|
+
if (config.knowledge?.graph?.enabled !== false) {
|
|
179
|
+
try {
|
|
180
|
+
const graph = new InMemoryKnowledgeGraph(logger)
|
|
181
|
+
// Use absolute path from getHomePaths() if persistPath not configured
|
|
182
|
+
const { getHomePaths: getHomePathsKG } = await import('@/config/home')
|
|
183
|
+
const graphPersistPath = config.knowledge?.graph?.persistPath || getHomePathsKG().knowledgeGraph
|
|
184
|
+
|
|
185
|
+
// Load existing graph from disk
|
|
186
|
+
await graph.load(graphPersistPath)
|
|
187
|
+
|
|
188
|
+
const search = new GraphSearchEngine(graph, logger)
|
|
189
|
+
const builder = new KnowledgeGraphBuilder(graph, logger)
|
|
190
|
+
await builder.initialize()
|
|
191
|
+
const linker = new CrossReferenceLinker(graph, logger)
|
|
192
|
+
|
|
193
|
+
// Start auto-save
|
|
194
|
+
const autoSaveInterval = (config.knowledge?.graph?.autoSaveInterval || 60) * 1000
|
|
195
|
+
graph.startAutoSave(graphPersistPath, autoSaveInterval)
|
|
196
|
+
|
|
197
|
+
knowledgeGraph = { graph, search, builder, linker }
|
|
198
|
+
|
|
199
|
+
// Migrate existing decisions if graph is empty (requires ChromaDB)
|
|
200
|
+
if (graph.getNodeCount() === 0 && memory.isChromaDBEnabled()) {
|
|
201
|
+
serviceLogger.info('Empty graph detected, migrating existing decisions...')
|
|
202
|
+
const migrationResult = await builder.migrateExistingDecisions(memory.chroma.collections)
|
|
203
|
+
serviceLogger.info(
|
|
204
|
+
{ processed: migrationResult.processed, errors: migrationResult.errors },
|
|
205
|
+
'Initial graph migration complete'
|
|
206
|
+
)
|
|
207
|
+
} else if (graph.getNodeCount() === 0) {
|
|
208
|
+
serviceLogger.info('Empty graph detected, but ChromaDB unavailable — graph will populate as new decisions are stored')
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Hook builder into decision storage for real-time graph population
|
|
212
|
+
// Uses MemoryManager listener so it works for both ChromaDB and SQLite paths
|
|
213
|
+
memory.addDecisionStoredListener((input) => {
|
|
214
|
+
builder.processDecision(input)
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
// Hook builder into decision deletion for graph synchronization
|
|
218
|
+
memory.addDecisionDeletedListener((id) => {
|
|
219
|
+
builder.removeDecision(id)
|
|
220
|
+
|
|
221
|
+
// Phase 20: Also unlink from active episodes
|
|
222
|
+
if (episodeManager) {
|
|
223
|
+
episodeManager.unlinkDecision(id)
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Phase 20: Invalidate semantic cache for deleted decisions
|
|
227
|
+
if (semanticCache) {
|
|
228
|
+
semanticCache.clear()
|
|
229
|
+
}
|
|
230
|
+
})
|
|
231
|
+
|
|
232
|
+
serviceLogger.info(
|
|
233
|
+
{ nodes: graph.getNodeCount(), edges: graph.getEdgeCount() },
|
|
234
|
+
'Knowledge graph service initialized'
|
|
235
|
+
)
|
|
236
|
+
} catch (error) {
|
|
237
|
+
serviceLogger.warn({ error }, 'Failed to initialize knowledge graph, continuing without it')
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Initialize Episode Manager (Phase 14) — requires ChromaDB
|
|
242
|
+
let episodeManager: EpisodeManager | null = null
|
|
243
|
+
if (config.knowledge?.episodic?.enabled !== false && memory.isChromaDBEnabled()) {
|
|
244
|
+
try {
|
|
245
|
+
episodeManager = new EpisodeManager(
|
|
246
|
+
logger,
|
|
247
|
+
memory.chroma.collections,
|
|
248
|
+
memory.chroma.embeddings,
|
|
249
|
+
{ sessionGapMinutes: config.knowledge?.episodic?.sessionGapMinutes || 30 }
|
|
250
|
+
)
|
|
251
|
+
serviceLogger.info('Episode manager initialized')
|
|
252
|
+
} catch (error) {
|
|
253
|
+
serviceLogger.warn({ error }, 'Failed to initialize episode manager, continuing without it')
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Initialize Session Tracker (Phase 21) — promoted from serve.ts
|
|
258
|
+
let sessionTracker: HookSessionTracker | null = null
|
|
259
|
+
if (config.hooks?.enabled !== false) {
|
|
260
|
+
try {
|
|
261
|
+
sessionTracker = new HookSessionTracker(logger, episodeManager, config.hooks?.sessions, memory)
|
|
262
|
+
serviceLogger.info('Session tracker initialized')
|
|
263
|
+
} catch (error) {
|
|
264
|
+
serviceLogger.warn({ error }, 'Failed to initialize session tracker, continuing without it')
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Initialize Semantic Cache & Precompute (Phase 15) — requires ChromaDB
|
|
269
|
+
let semanticCache: SemanticCache | null = null
|
|
270
|
+
let precompute: PrecomputeEngine | null = null
|
|
271
|
+
if (config.advancedIntelligence?.enabled !== false && config.advancedIntelligence?.cache?.enabled !== false && memory.isChromaDBEnabled()) {
|
|
272
|
+
try {
|
|
273
|
+
const cacheConfig = config.advancedIntelligence?.cache || {}
|
|
274
|
+
semanticCache = new SemanticCache(logger, {
|
|
275
|
+
maxSize: cacheConfig.maxSize || 1000,
|
|
276
|
+
ttlMs: cacheConfig.ttlMs || 60 * 60 * 1000,
|
|
277
|
+
similarityThreshold: cacheConfig.similarityThreshold || 0.80,
|
|
278
|
+
embeddings: memory.chroma.embeddings
|
|
279
|
+
})
|
|
280
|
+
|
|
281
|
+
precompute = new PrecomputeEngine(logger, memory.chroma.collections, semanticCache)
|
|
282
|
+
await precompute.initialize()
|
|
283
|
+
precompute.startAutoRefresh(5 * 60 * 1000) // Refresh every 5 minutes
|
|
284
|
+
|
|
285
|
+
serviceLogger.info('Phase 15 semantic cache & precompute initialized')
|
|
286
|
+
} catch (error) {
|
|
287
|
+
serviceLogger.warn({ error }, 'Failed to initialize Phase 15 caching, continuing without it')
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Initialize Memory Archiver — periodic archival of low-importance memories
|
|
292
|
+
let archiver: MemoryArchiver | null = null
|
|
293
|
+
const consolidation = config.knowledge?.consolidation
|
|
294
|
+
if (consolidation?.enabled !== false && memory.isChromaDBEnabled()) {
|
|
295
|
+
try {
|
|
296
|
+
const scorer = new ImportanceScorer(logger, consolidation?.weights)
|
|
297
|
+
archiver = new MemoryArchiver(
|
|
298
|
+
logger,
|
|
299
|
+
memory.chroma.collections,
|
|
300
|
+
scorer,
|
|
301
|
+
memory.chroma.embeddings
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
const intervalMinutes = consolidation?.archiveIntervalMinutes ?? 60
|
|
305
|
+
if (intervalMinutes > 0) {
|
|
306
|
+
const threshold = consolidation?.archiveThreshold ?? 0.2
|
|
307
|
+
archiveTimer = setInterval(async () => {
|
|
308
|
+
try {
|
|
309
|
+
const result = await archiver!.archiveLowImportance(threshold)
|
|
310
|
+
if (result.archivedCount > 0) {
|
|
311
|
+
serviceLogger.info(
|
|
312
|
+
{ archived: result.archivedCount, threshold },
|
|
313
|
+
'Periodic archival sweep complete'
|
|
314
|
+
)
|
|
315
|
+
}
|
|
316
|
+
} catch (error) {
|
|
317
|
+
serviceLogger.warn({ error }, 'Periodic archival sweep failed')
|
|
318
|
+
}
|
|
319
|
+
}, intervalMinutes * 60 * 1000)
|
|
320
|
+
serviceLogger.info(
|
|
321
|
+
{ intervalMinutes, threshold: consolidation?.archiveThreshold ?? 0.2 },
|
|
322
|
+
'Memory archiver initialized with periodic sweep'
|
|
323
|
+
)
|
|
324
|
+
} else {
|
|
325
|
+
serviceLogger.info('Memory archiver initialized (periodic sweep disabled)')
|
|
326
|
+
}
|
|
327
|
+
} catch (error) {
|
|
328
|
+
serviceLogger.warn({ error }, 'Failed to initialize memory archiver, continuing without it')
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Initialize Code Intelligence (Phase 28)
|
|
333
|
+
let codeIndexer: CodeIndexer | null = null
|
|
334
|
+
let codeQuery: CodeQuery | null = null
|
|
335
|
+
let codeLinker: MemoryCodeLinker | null = null
|
|
336
|
+
let codeDbInstance: import('bun:sqlite').Database | null = null
|
|
337
|
+
if (config.codeIntelligence?.enabled !== false) {
|
|
338
|
+
try {
|
|
339
|
+
const { Database } = await import('bun:sqlite')
|
|
340
|
+
const { resolve, dirname } = await import('node:path')
|
|
341
|
+
const { mkdirSync, existsSync } = await import('node:fs')
|
|
342
|
+
const { getClaudeBrainHome } = await import('@/config/home')
|
|
343
|
+
const codeDbDir = resolve(getClaudeBrainHome(), 'data')
|
|
344
|
+
if (!existsSync(codeDbDir)) {
|
|
345
|
+
mkdirSync(codeDbDir, { recursive: true })
|
|
346
|
+
}
|
|
347
|
+
const codeDbPath = resolve(codeDbDir, 'code.db')
|
|
348
|
+
const codeDb = new Database(codeDbPath)
|
|
349
|
+
codeDb.exec('PRAGMA journal_mode=WAL')
|
|
350
|
+
codeDbInstance = codeDb
|
|
351
|
+
|
|
352
|
+
const { CodeParser } = await import('@/code-intelligence/parser')
|
|
353
|
+
const { CodeIndexer: CodeIndexerClass } = await import('@/code-intelligence/indexer')
|
|
354
|
+
const { CodeQuery: CodeQueryClass } = await import('@/code-intelligence/query')
|
|
355
|
+
|
|
356
|
+
const parser = new CodeParser()
|
|
357
|
+
await parser.initialize()
|
|
358
|
+
|
|
359
|
+
codeIndexer = new CodeIndexerClass(codeDb, parser, serviceLogger)
|
|
360
|
+
await codeIndexer.initialize()
|
|
361
|
+
|
|
362
|
+
codeQuery = new CodeQueryClass(codeDb, serviceLogger)
|
|
363
|
+
|
|
364
|
+
serviceLogger.info('Code intelligence service initialized')
|
|
365
|
+
} catch (error) {
|
|
366
|
+
serviceLogger.warn({ error }, 'Failed to initialize code intelligence, continuing without it')
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// Initialize MemoryCodeLinker (Phase 29)
|
|
371
|
+
try {
|
|
372
|
+
const { MemoryCodeLinker: LinkerClass } = await import('@/code-intelligence/linker')
|
|
373
|
+
const memoryDb = memory.database.getDb()
|
|
374
|
+
codeLinker = new LinkerClass(memoryDb, codeDbInstance, serviceLogger)
|
|
375
|
+
serviceLogger.info('Code linker initialized')
|
|
376
|
+
} catch (error) {
|
|
377
|
+
serviceLogger.warn({ error }, 'Failed to initialize code linker, continuing without it')
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// Initialize SLM Model Manager & Inference Router
|
|
381
|
+
let modelManager: ModelManager | null = null
|
|
382
|
+
let inferenceRouter: InferenceRouter | null = null
|
|
383
|
+
try {
|
|
384
|
+
const slmModelsDir = config.slm?.modelsDir?.replace(/^~/, require('os').homedir())
|
|
385
|
+
modelManager = new ModelManager(serviceLogger, slmModelsDir)
|
|
386
|
+
inferenceRouter = new InferenceRouter(serviceLogger, config, modelManager)
|
|
387
|
+
const slmEnabled = config.slm?.enabled ?? false
|
|
388
|
+
serviceLogger.info({ enabled: slmEnabled }, 'SLM inference router initialized')
|
|
389
|
+
} catch (error) {
|
|
390
|
+
serviceLogger.warn({ error }, 'Failed to initialize SLM inference, continuing with regex only')
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// Wire SLM inference into PatternRecognizer (Phase 4C)
|
|
394
|
+
if (inferenceRouter && phase12) {
|
|
395
|
+
phase12.patterns.setInferenceRouter(inferenceRouter)
|
|
396
|
+
serviceLogger.info('SLM inference wired into PatternRecognizer')
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// SLM startup detection: log model availability and configuration state
|
|
400
|
+
if (modelManager) {
|
|
401
|
+
const modelStatus = modelManager.getStatus()
|
|
402
|
+
const modelsDir = modelManager.getModelsDir()
|
|
403
|
+
const availableCount = Object.values(modelStatus).filter(s => s.available).length
|
|
404
|
+
const slmEnabled = config.slm?.enabled ?? false
|
|
405
|
+
|
|
406
|
+
if (availableCount > 0 && !slmEnabled) {
|
|
407
|
+
serviceLogger.info(
|
|
408
|
+
{ count: availableCount, modelsDir },
|
|
409
|
+
`Found ${availableCount} ONNX models in ${modelsDir}. SLM is disabled. Run "claude-brain models enable all" to activate local inference.`
|
|
410
|
+
)
|
|
411
|
+
} else if (availableCount > 0 && slmEnabled) {
|
|
412
|
+
const tasks = config.slm?.tasks ?? {}
|
|
413
|
+
const enabledTasks = Object.entries(tasks)
|
|
414
|
+
.filter(([_, mode]) => mode === 'model' || mode === 'both')
|
|
415
|
+
.map(([task]) => task)
|
|
416
|
+
serviceLogger.info(
|
|
417
|
+
{ count: availableCount, enabledTasks },
|
|
418
|
+
`SLM active: ${enabledTasks.length} tasks using local model inference`
|
|
419
|
+
)
|
|
420
|
+
|
|
421
|
+
// Check onnxruntime availability when SLM is enabled
|
|
422
|
+
const onnxAvailable = await modelManager.isOnnxAvailable()
|
|
423
|
+
if (!onnxAvailable) {
|
|
424
|
+
serviceLogger.warn('SLM enabled but onnxruntime-node not installed. Run: bun add onnxruntime-node')
|
|
425
|
+
}
|
|
426
|
+
} else if (availableCount === 0 && slmEnabled) {
|
|
427
|
+
serviceLogger.warn(
|
|
428
|
+
{ modelsDir },
|
|
429
|
+
`SLM enabled but no models found in ${modelsDir}. Run "claude-brain models download" to install models.`
|
|
430
|
+
)
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// Store services
|
|
435
|
+
services = {
|
|
436
|
+
memory,
|
|
437
|
+
vault,
|
|
438
|
+
context,
|
|
439
|
+
phase12,
|
|
440
|
+
retrieval,
|
|
441
|
+
retrievalPipeline,
|
|
442
|
+
knowledgeGraph,
|
|
443
|
+
episodeManager,
|
|
444
|
+
sessionTracker,
|
|
445
|
+
semanticCache,
|
|
446
|
+
precompute,
|
|
447
|
+
archiver,
|
|
448
|
+
codeIndexer,
|
|
449
|
+
codeQuery,
|
|
450
|
+
codeLinker,
|
|
451
|
+
modelManager,
|
|
452
|
+
inferenceRouter,
|
|
453
|
+
logger,
|
|
454
|
+
config
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
serviceLogger.info('All services initialized successfully')
|
|
458
|
+
})()
|
|
459
|
+
|
|
460
|
+
await initializationPromise
|
|
461
|
+
return services!
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* Get initialized services
|
|
466
|
+
* Throws if services not initialized
|
|
467
|
+
*/
|
|
468
|
+
export function getServices(): Services {
|
|
469
|
+
if (!services) {
|
|
470
|
+
throw new Error('Services not initialized. Call initializeServices() first.')
|
|
471
|
+
}
|
|
472
|
+
return services
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* Get memory service
|
|
477
|
+
* Throws if not initialized
|
|
478
|
+
*/
|
|
479
|
+
export function getMemoryService(): MemoryManager {
|
|
480
|
+
return getServices().memory
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
/**
|
|
484
|
+
* Get vault service
|
|
485
|
+
* Throws if not initialized
|
|
486
|
+
*/
|
|
487
|
+
export function getVaultService(): VaultManager {
|
|
488
|
+
return getServices().vault
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
export function getContextService(): ContextManager {
|
|
492
|
+
return getServices().context
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/**
|
|
496
|
+
* Get Phase 12 service
|
|
497
|
+
* Throws if not initialized
|
|
498
|
+
*/
|
|
499
|
+
export function getPhase12Service(): Phase12Manager {
|
|
500
|
+
return getServices().phase12
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* Get Retrieval service
|
|
505
|
+
* Returns null if retrieval is not enabled
|
|
506
|
+
*/
|
|
507
|
+
export function getRetrievalService(): RetrievalService | null {
|
|
508
|
+
return getServices().retrieval
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/**
|
|
512
|
+
* Get Retrieval Pipeline (Phase 19)
|
|
513
|
+
* Returns null if hybrid search is not enabled
|
|
514
|
+
*/
|
|
515
|
+
export function getRetrievalPipeline(): RetrievalPipeline | null {
|
|
516
|
+
return getServices().retrievalPipeline
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
/**
|
|
520
|
+
* Get Knowledge Graph service
|
|
521
|
+
* Returns null if knowledge graph is not enabled
|
|
522
|
+
*/
|
|
523
|
+
export function getKnowledgeGraphService(): KnowledgeGraphServiceContainer | null {
|
|
524
|
+
return getServices().knowledgeGraph
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
/**
|
|
528
|
+
* Get Episode Manager service
|
|
529
|
+
* Returns null if episodic memory is not enabled
|
|
530
|
+
*/
|
|
531
|
+
export function getEpisodeService(): EpisodeManager | null {
|
|
532
|
+
return getServices().episodeManager
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
/**
|
|
536
|
+
* Get Session Tracker service
|
|
537
|
+
* Returns null if hooks are not enabled
|
|
538
|
+
*/
|
|
539
|
+
export function getSessionTracker(): HookSessionTracker | null {
|
|
540
|
+
return getServices().sessionTracker
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
/**
|
|
544
|
+
* Get Semantic Cache service
|
|
545
|
+
* Returns null if caching is not enabled
|
|
546
|
+
*/
|
|
547
|
+
export function getSemanticCacheService(): SemanticCache | null {
|
|
548
|
+
return getServices().semanticCache
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/**
|
|
552
|
+
* Get Precompute Engine service
|
|
553
|
+
* Returns null if caching is not enabled
|
|
554
|
+
*/
|
|
555
|
+
export function getPrecomputeService(): PrecomputeEngine | null {
|
|
556
|
+
return getServices().precompute
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
/**
|
|
560
|
+
* Get Memory Archiver service
|
|
561
|
+
* Returns null if consolidation is not enabled
|
|
562
|
+
*/
|
|
563
|
+
export function getArchiverService(): MemoryArchiver | null {
|
|
564
|
+
return getServices().archiver
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
/**
|
|
568
|
+
* Get Code Indexer service (Phase 28)
|
|
569
|
+
* Returns null if code intelligence is not enabled
|
|
570
|
+
*/
|
|
571
|
+
export function getCodeIndexer(): CodeIndexer | null {
|
|
572
|
+
return services?.codeIndexer ?? null
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
/**
|
|
576
|
+
* Get Code Query service (Phase 28)
|
|
577
|
+
* Returns null if code intelligence is not enabled
|
|
578
|
+
*/
|
|
579
|
+
export function getCodeQuery(): CodeQuery | null {
|
|
580
|
+
return services?.codeQuery ?? null
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
/**
|
|
584
|
+
* Get Code Linker service (Phase 29)
|
|
585
|
+
* Returns null if not initialized
|
|
586
|
+
*/
|
|
587
|
+
export function getCodeLinker(): MemoryCodeLinker | null {
|
|
588
|
+
return services?.codeLinker ?? null
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
/**
|
|
592
|
+
* Get Model Manager (SLM Upgrade)
|
|
593
|
+
* Returns null if SLM is not initialized
|
|
594
|
+
*/
|
|
595
|
+
export function getModelManager(): ModelManager | null {
|
|
596
|
+
return services?.modelManager ?? null
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
/**
|
|
600
|
+
* Get Inference Router (SLM Upgrade)
|
|
601
|
+
* Returns null if SLM is not initialized
|
|
602
|
+
*/
|
|
603
|
+
export function getInferenceRouter(): InferenceRouter | null {
|
|
604
|
+
return services?.inferenceRouter ?? null
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
/**
|
|
608
|
+
* Check if services are initialized
|
|
609
|
+
*/
|
|
610
|
+
export function isServicesInitialized(): boolean {
|
|
611
|
+
return services !== null
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
/**
|
|
615
|
+
* Inject services for testing
|
|
616
|
+
* Allows tests to provide mock services without going through initializeServices
|
|
617
|
+
* @internal - Only use in tests
|
|
618
|
+
*/
|
|
619
|
+
export function _injectServicesForTesting(testServices: Services | null): void {
|
|
620
|
+
services = testServices
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
/**
|
|
624
|
+
* Reset services for testing
|
|
625
|
+
* Clears the services singleton without proper shutdown
|
|
626
|
+
* @internal - Only use in tests
|
|
627
|
+
*/
|
|
628
|
+
export function _resetServicesForTesting(): void {
|
|
629
|
+
services = null
|
|
630
|
+
initializationPromise = null
|
|
631
|
+
if (archiveTimer) {
|
|
632
|
+
clearInterval(archiveTimer)
|
|
633
|
+
archiveTimer = null
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
/**
|
|
638
|
+
* Shutdown all services gracefully
|
|
639
|
+
*/
|
|
640
|
+
export async function shutdownServices(): Promise<void> {
|
|
641
|
+
if (!services) {
|
|
642
|
+
return
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
const serviceLogger = services.logger.child({ component: 'services' })
|
|
646
|
+
serviceLogger.info('Shutting down services...')
|
|
647
|
+
|
|
648
|
+
// End all active sessions
|
|
649
|
+
if (services.sessionTracker) {
|
|
650
|
+
try {
|
|
651
|
+
await services.sessionTracker.endAllSessions()
|
|
652
|
+
serviceLogger.info('Session tracker shut down')
|
|
653
|
+
} catch (error) {
|
|
654
|
+
serviceLogger.error({ error }, 'Failed to end sessions on shutdown')
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
// Save and stop knowledge graph
|
|
659
|
+
if (services.knowledgeGraph) {
|
|
660
|
+
services.knowledgeGraph.graph.stopAutoSave()
|
|
661
|
+
// Use absolute path from getHomePaths() if persistPath not configured
|
|
662
|
+
const { getHomePaths } = await import('@/config/home')
|
|
663
|
+
const persistPath = services.config.knowledge?.graph?.persistPath || getHomePaths().knowledgeGraph
|
|
664
|
+
try {
|
|
665
|
+
await services.knowledgeGraph.graph.save(persistPath)
|
|
666
|
+
serviceLogger.info('Knowledge graph saved on shutdown')
|
|
667
|
+
} catch (error) {
|
|
668
|
+
serviceLogger.error({ error }, 'Failed to save knowledge graph on shutdown')
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
// Stop precompute auto-refresh
|
|
673
|
+
if (services.precompute) {
|
|
674
|
+
services.precompute.stopAutoRefresh()
|
|
675
|
+
serviceLogger.info('Precompute engine stopped')
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
// Clear semantic cache
|
|
679
|
+
if (services.semanticCache) {
|
|
680
|
+
services.semanticCache.clear()
|
|
681
|
+
serviceLogger.info('Semantic cache cleared')
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
// Stop archival timer
|
|
685
|
+
if (archiveTimer) {
|
|
686
|
+
clearInterval(archiveTimer)
|
|
687
|
+
archiveTimer = null
|
|
688
|
+
serviceLogger.info('Memory archiver stopped')
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
// Shut down code intelligence
|
|
692
|
+
if (services.codeIndexer) {
|
|
693
|
+
try {
|
|
694
|
+
serviceLogger.info('Code intelligence shut down')
|
|
695
|
+
} catch (error) {
|
|
696
|
+
serviceLogger.error({ error }, 'Failed to shut down code intelligence')
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
// Unload SLM models (releases ONNX sessions)
|
|
701
|
+
if (services.modelManager) {
|
|
702
|
+
await services.modelManager.unloadAll()
|
|
703
|
+
serviceLogger.info('SLM models unloaded')
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
// Cleanup Phase 12
|
|
707
|
+
services.phase12.cleanup()
|
|
708
|
+
|
|
709
|
+
// Stop vault watcher
|
|
710
|
+
services.vault.stopWatching()
|
|
711
|
+
|
|
712
|
+
// Close memory database
|
|
713
|
+
services.memory.close()
|
|
714
|
+
|
|
715
|
+
// Clear reference
|
|
716
|
+
services = null
|
|
717
|
+
initializationPromise = null
|
|
718
|
+
|
|
719
|
+
serviceLogger.info('All services shut down')
|
|
720
|
+
}
|