claude-brain 0.17.13 → 0.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/VERSION +1 -1
  2. package/package.json +3 -1
  3. package/scripts/postinstall.mjs +80 -104
  4. package/src/cli/auto-setup.ts +1 -9
  5. package/src/cli/bin.ts +23 -2
  6. package/src/cli/commands/export.ts +130 -0
  7. package/src/cli/commands/reindex.ts +107 -0
  8. package/src/cli/commands/serve.ts +54 -0
  9. package/src/cli/commands/status.ts +158 -0
  10. package/src/code-intelligence/indexer.ts +315 -0
  11. package/src/code-intelligence/linker.ts +178 -0
  12. package/src/code-intelligence/parser.ts +484 -0
  13. package/src/code-intelligence/query.ts +291 -0
  14. package/src/code-intelligence/schema.ts +83 -0
  15. package/src/code-intelligence/types.ts +95 -0
  16. package/src/config/defaults.ts +3 -3
  17. package/src/config/loader.ts +6 -0
  18. package/src/config/schema.ts +28 -2
  19. package/src/health/index.ts +5 -2
  20. package/src/hooks/brain-hook.ts +4 -1
  21. package/src/hooks/context-hook.ts +69 -10
  22. package/src/hooks/installer.ts +4 -7
  23. package/src/intelligence/cross-project/index.ts +1 -7
  24. package/src/intelligence/prediction/index.ts +1 -7
  25. package/src/intelligence/reasoning/index.ts +1 -7
  26. package/src/memory/compression.ts +105 -0
  27. package/src/memory/fts5-search.ts +456 -0
  28. package/src/memory/index.ts +342 -38
  29. package/src/memory/migrations/add-fts5.ts +98 -0
  30. package/src/memory/pruning.ts +60 -0
  31. package/src/routing/intent-classifier.ts +58 -1
  32. package/src/routing/response-filter.ts +128 -0
  33. package/src/routing/router.ts +457 -54
  34. package/src/server/http-api.ts +319 -1
  35. package/src/server/providers/resources.ts +1 -42
  36. package/src/server/services.ts +113 -12
  37. package/src/server/web-viewer.ts +1115 -0
  38. package/src/setup/index.ts +12 -22
  39. package/src/tools/schemas.ts +1 -1
  40. package/src/intelligence/cross-project/affinity.ts +0 -159
  41. package/src/intelligence/cross-project/transfer.ts +0 -201
  42. package/src/intelligence/prediction/context-anticipator.ts +0 -198
  43. package/src/intelligence/prediction/decision-predictor.ts +0 -184
  44. package/src/intelligence/reasoning/counterfactual.ts +0 -248
  45. package/src/intelligence/reasoning/synthesizer.ts +0 -167
  46. package/src/setup/wizard.ts +0 -459
@@ -12,6 +12,10 @@ import type { MemoryManager } from '@/memory'
12
12
  import type { CapturedKnowledge, HookStats } from '@/hooks/types'
13
13
  import { SmartDeduplicator } from '@/hooks/deduplicator'
14
14
  import type { HookSessionTracker } from '@/hooks/session-tracker'
15
+ import type { CodeIndexer } from '@/code-intelligence/indexer'
16
+ import type { CodeQuery } from '@/code-intelligence/query'
17
+ import type { MemoryCodeLinker } from '@/code-intelligence/linker'
18
+ import { setupWebViewer, setWebViewerCodeQuery } from '@/server/web-viewer'
15
19
 
16
20
  export class HttpApiServer {
17
21
  private app: Hono
@@ -19,6 +23,9 @@ export class HttpApiServer {
19
23
  private config: Config
20
24
  private server?: any
21
25
  private sessionTracker?: HookSessionTracker
26
+ private codeIndexer?: CodeIndexer
27
+ private codeQuery?: CodeQuery
28
+ private codeLinker?: MemoryCodeLinker
22
29
  private hookStats: HookStats = {
23
30
  totalCaptured: 0,
24
31
  totalSkipped: 0,
@@ -32,6 +39,7 @@ export class HttpApiServer {
32
39
  this.logger = logger.child({ component: 'http-api' })
33
40
  this.app = new Hono()
34
41
  this.setupMiddleware()
42
+ setupWebViewer(this.app)
35
43
  this.setupRoutes()
36
44
  }
37
45
 
@@ -40,7 +48,10 @@ export class HttpApiServer {
40
48
  c.header('Access-Control-Allow-Origin', '*')
41
49
  c.header('Access-Control-Allow-Methods', 'GET, POST, PATCH, DELETE, OPTIONS')
42
50
  c.header('Access-Control-Allow-Headers', 'Content-Type, Authorization')
43
- c.header('Content-Type', 'application/json')
51
+ // Only set JSON content-type for API routes; web viewer sets its own
52
+ if (c.req.path.startsWith('/api/')) {
53
+ c.header('Content-Type', 'application/json')
54
+ }
44
55
  await next()
45
56
  })
46
57
  }
@@ -50,6 +61,31 @@ export class HttpApiServer {
50
61
  this.sessionTracker = tracker
51
62
  }
52
63
 
64
+ /** Set code intelligence services (called after initialization) */
65
+ setCodeIntelligence(indexer: CodeIndexer, query: CodeQuery): void {
66
+ this.codeIndexer = indexer
67
+ this.codeQuery = query
68
+
69
+ // Phase 30: Share code query with web viewer for stats
70
+ setWebViewerCodeQuery(query)
71
+
72
+ // Phase 29: Wire memory DB into code query for file-linked memory annotations
73
+ try {
74
+ const memoryService = getMemoryService()
75
+ if (memoryService?.isInitialized()) {
76
+ const memDb = memoryService.database.getDb()
77
+ this.codeQuery.setMemoryDb(memDb)
78
+ }
79
+ } catch {
80
+ // Memory service may not be ready yet — annotations will be empty
81
+ }
82
+ }
83
+
84
+ /** Set code linker (Phase 29) */
85
+ setCodeLinker(linker: MemoryCodeLinker): void {
86
+ this.codeLinker = linker
87
+ }
88
+
53
89
  private setupRoutes(): void {
54
90
  // Health check
55
91
  this.app.get('/api/health', (c) => {
@@ -86,6 +122,17 @@ export class HttpApiServer {
86
122
  // Phase 26: Context injection for UserPromptSubmit/SessionStart hooks
87
123
  this.app.get('/api/hooks/context-query', (c) => this.handleContextQuery(c))
88
124
 
125
+ // Phase 28: Code intelligence endpoints
126
+ this.app.post('/api/code/index', (c) => this.handleCodeIndex(c))
127
+ this.app.post('/api/code/reindex', (c) => this.handleCodeReindex(c))
128
+ this.app.get('/api/code/search', (c) => this.handleCodeSearch(c))
129
+ this.app.get('/api/code/file-map', (c) => this.handleCodeFileMap(c))
130
+ this.app.get('/api/code/deps', (c) => this.handleCodeDeps(c))
131
+ this.app.get('/api/code/status', (c) => this.handleCodeStatus(c))
132
+
133
+ // Phase 29: Memory-to-code linkage endpoint
134
+ this.app.get('/api/memory/for-file', (c) => this.handleMemoryForFile(c))
135
+
89
136
  // Phase 23b: Expose brain://context/auto via HTTP for testability
90
137
  this.app.get('/api/context/auto', () => this.handleContextAuto())
91
138
  }
@@ -447,6 +494,277 @@ export class HttpApiServer {
447
494
  }
448
495
  }
449
496
 
497
+ // ─── Phase 29: Memory-to-Code Linkage ─────────────────────
498
+
499
+ private handleMemoryForFile(c: any): Response {
500
+ try {
501
+ const file = c.req.query('file') || ''
502
+ const project = c.req.query('project') || ''
503
+
504
+ if (!file) {
505
+ return Response.json(
506
+ { success: false, error: 'file query parameter is required' },
507
+ { status: 400 }
508
+ )
509
+ }
510
+
511
+ if (!isServicesInitialized()) {
512
+ return Response.json({ success: true, data: { file, memoryCount: 0, summary: '' } })
513
+ }
514
+
515
+ const memoryService = getMemoryService()
516
+ if (!memoryService?.fts5) {
517
+ return Response.json({ success: true, data: { file, memoryCount: 0, summary: '' } })
518
+ }
519
+
520
+ const db = memoryService.database.getDb()
521
+ const likePattern = `%${file}%`
522
+
523
+ let rows: any[]
524
+ if (project) {
525
+ rows = db.prepare(`
526
+ SELECT id, category, content, reasoning, created_at
527
+ FROM observations
528
+ WHERE project = ? AND file_paths LIKE ? AND archived = 0
529
+ ORDER BY created_at DESC
530
+ LIMIT 5
531
+ `).all(project, likePattern)
532
+ } else {
533
+ rows = db.prepare(`
534
+ SELECT id, category, content, reasoning, created_at
535
+ FROM observations
536
+ WHERE file_paths LIKE ? AND archived = 0
537
+ ORDER BY created_at DESC
538
+ LIMIT 5
539
+ `).all(likePattern)
540
+ }
541
+
542
+ if (rows.length === 0) {
543
+ return Response.json({ success: true, data: { file, memoryCount: 0, summary: '' } })
544
+ }
545
+
546
+ const lines = rows.map((r: any) => {
547
+ const age = this.formatAge(r.created_at)
548
+ return `- [${r.category}] ${(r.content as string).slice(0, 120)} (${age})`
549
+ })
550
+
551
+ return Response.json({
552
+ success: true,
553
+ data: {
554
+ file,
555
+ memoryCount: rows.length,
556
+ summary: `Memories linked to ${file}:\n${lines.join('\n')}`
557
+ }
558
+ })
559
+ } catch (error) {
560
+ this.logger.error({ error }, 'Failed to get memories for file')
561
+ return Response.json(
562
+ { success: false, error: 'Failed to get memories for file' },
563
+ { status: 500 }
564
+ )
565
+ }
566
+ }
567
+
568
+ /** Format a date as a relative age string (e.g., "2 days ago") */
569
+ private formatAge(isoDate: string): string {
570
+ try {
571
+ const diff = Date.now() - new Date(isoDate).getTime()
572
+ const minutes = Math.floor(diff / 60000)
573
+ if (minutes < 1) return 'just now'
574
+ if (minutes < 60) return `${minutes}m ago`
575
+ const hours = Math.floor(minutes / 60)
576
+ if (hours < 24) return `${hours}h ago`
577
+ const days = Math.floor(hours / 24)
578
+ if (days === 1) return 'yesterday'
579
+ if (days < 30) return `${days} days ago`
580
+ return `${Math.floor(days / 30)} months ago`
581
+ } catch {
582
+ return ''
583
+ }
584
+ }
585
+
586
+ // ─── Phase 28: Code Intelligence Endpoints ─────────────────
587
+
588
+ private async handleCodeIndex(c: any): Promise<Response> {
589
+ try {
590
+ if (!this.codeIndexer) {
591
+ return Response.json(
592
+ { success: false, error: 'Code intelligence not initialized' },
593
+ { status: 503 }
594
+ )
595
+ }
596
+
597
+ const body = await c.req.json()
598
+ const { projectPath, projectName } = body
599
+
600
+ if (!projectPath || !projectName) {
601
+ return Response.json(
602
+ { success: false, error: 'projectPath and projectName are required' },
603
+ { status: 400 }
604
+ )
605
+ }
606
+
607
+ const result = await this.codeIndexer.indexProject(projectPath, projectName)
608
+ return Response.json({ success: true, data: result })
609
+ } catch (error) {
610
+ this.logger.error({ error }, 'Failed to index project')
611
+ return Response.json(
612
+ { success: false, error: 'Failed to index project' },
613
+ { status: 500 }
614
+ )
615
+ }
616
+ }
617
+
618
+ private async handleCodeReindex(c: any): Promise<Response> {
619
+ try {
620
+ if (!this.codeIndexer) {
621
+ return Response.json(
622
+ { success: false, error: 'Code intelligence not initialized' },
623
+ { status: 503 }
624
+ )
625
+ }
626
+
627
+ const body = await c.req.json()
628
+ const { filePath, project } = body
629
+
630
+ if (!filePath || !project) {
631
+ return Response.json(
632
+ { success: false, error: 'filePath and project are required' },
633
+ { status: 400 }
634
+ )
635
+ }
636
+
637
+ await this.codeIndexer.indexFile(filePath, project)
638
+ return Response.json({ success: true })
639
+ } catch (error) {
640
+ this.logger.error({ error }, 'Failed to reindex file')
641
+ return Response.json(
642
+ { success: false, error: 'Failed to reindex file' },
643
+ { status: 500 }
644
+ )
645
+ }
646
+ }
647
+
648
+ private handleCodeSearch(c: any): Response {
649
+ try {
650
+ if (!this.codeQuery) {
651
+ return Response.json(
652
+ { success: false, error: 'Code intelligence not initialized' },
653
+ { status: 503 }
654
+ )
655
+ }
656
+
657
+ const query = c.req.query('query') || ''
658
+ const project = c.req.query('project') || ''
659
+ const limit = parseInt(c.req.query('limit') || '20', 10)
660
+
661
+ if (!query || !project) {
662
+ return Response.json(
663
+ { success: false, error: 'query and project are required' },
664
+ { status: 400 }
665
+ )
666
+ }
667
+
668
+ const results = this.codeQuery.findSymbols(query, project, limit)
669
+ return Response.json({ success: true, data: results })
670
+ } catch (error) {
671
+ this.logger.error({ error }, 'Failed to search code')
672
+ return Response.json(
673
+ { success: false, error: 'Failed to search code' },
674
+ { status: 500 }
675
+ )
676
+ }
677
+ }
678
+
679
+ private handleCodeFileMap(c: any): Response {
680
+ try {
681
+ if (!this.codeQuery) {
682
+ return Response.json(
683
+ { success: false, error: 'Code intelligence not initialized' },
684
+ { status: 503 }
685
+ )
686
+ }
687
+
688
+ const project = c.req.query('project') || ''
689
+
690
+ if (!project) {
691
+ return Response.json(
692
+ { success: false, error: 'project is required' },
693
+ { status: 400 }
694
+ )
695
+ }
696
+
697
+ const entries = this.codeQuery.getFileMap(project)
698
+ const map = this.codeQuery.formatFileMap(entries)
699
+ return Response.json({ success: true, data: { map } })
700
+ } catch (error) {
701
+ this.logger.error({ error }, 'Failed to get file map')
702
+ return Response.json(
703
+ { success: false, error: 'Failed to get file map' },
704
+ { status: 500 }
705
+ )
706
+ }
707
+ }
708
+
709
+ private handleCodeDeps(c: any): Response {
710
+ try {
711
+ if (!this.codeQuery) {
712
+ return Response.json(
713
+ { success: false, error: 'Code intelligence not initialized' },
714
+ { status: 503 }
715
+ )
716
+ }
717
+
718
+ const file = c.req.query('file') || ''
719
+ const project = c.req.query('project') || ''
720
+
721
+ if (!file || !project) {
722
+ return Response.json(
723
+ { success: false, error: 'file and project are required' },
724
+ { status: 400 }
725
+ )
726
+ }
727
+
728
+ const result = this.codeQuery.getDependencies(file, project)
729
+ return Response.json({ success: true, data: result })
730
+ } catch (error) {
731
+ this.logger.error({ error }, 'Failed to get dependencies')
732
+ return Response.json(
733
+ { success: false, error: 'Failed to get dependencies' },
734
+ { status: 500 }
735
+ )
736
+ }
737
+ }
738
+
739
+ private handleCodeStatus(c: any): Response {
740
+ try {
741
+ if (!this.codeQuery) {
742
+ return Response.json(
743
+ { success: false, error: 'Code intelligence not initialized' },
744
+ { status: 503 }
745
+ )
746
+ }
747
+
748
+ const project = c.req.query('project') || ''
749
+
750
+ if (!project) {
751
+ return Response.json(
752
+ { success: false, error: 'project is required' },
753
+ { status: 400 }
754
+ )
755
+ }
756
+
757
+ const stats = this.codeQuery.getStats(project)
758
+ return Response.json({ success: true, data: stats })
759
+ } catch (error) {
760
+ this.logger.error({ error }, 'Failed to get code status')
761
+ return Response.json(
762
+ { success: false, error: 'Failed to get code status' },
763
+ { status: 500 }
764
+ )
765
+ }
766
+ }
767
+
450
768
  // ─── Phase 26: Context Query for Hook Injection ──────────
451
769
 
452
770
  private async handleContextQuery(c: any): Promise<Response> {
@@ -494,48 +494,7 @@ export class ResourceProvider {
494
494
  this.logger.debug({ err }, 'Failed to get corrections for auto-context')
495
495
  }
496
496
 
497
- // 5. Cross-project knowledge
498
- if (projectName) {
499
- try {
500
- const vault = getVaultService()
501
- const allProjects = await vault.reader.getProjectDirectories(vault.paths.projects)
502
- const otherProjects = allProjects.filter(p => p !== projectName)
503
-
504
- if (otherProjects.length > 0) {
505
- const memory = getMemoryService()
506
- if (memory.isChromaDBEnabled()) {
507
- const { KnowledgeTransfer } = await import('@/intelligence/cross-project/transfer')
508
- const transfer = new KnowledgeTransfer(
509
- this.logger,
510
- memory.chroma.collections,
511
- memory.chroma.embeddings
512
- )
513
-
514
- const transferItems: string[] = []
515
- // Check up to 3 other projects for transferable knowledge
516
- for (const other of otherProjects.slice(0, 3)) {
517
- try {
518
- const result = await transfer.findTransferable(other, projectName, { limit: 2, minRelevance: 0.5 })
519
- for (const item of result.items) {
520
- const truncated = item.content.length > 100 ? item.content.slice(0, 100) + '...' : item.content
521
- transferItems.push(`- [${item.type} from ${other}] ${truncated}`)
522
- }
523
- } catch {
524
- // Skip failed transfers
525
- }
526
- }
527
-
528
- if (transferItems.length > 0) {
529
- sections.push('## From Other Projects\n')
530
- sections.push(...transferItems)
531
- sections.push('')
532
- }
533
- }
534
- }
535
- } catch (err) {
536
- this.logger.debug({ err }, 'Failed to get cross-project knowledge for auto-context')
537
- }
538
- }
497
+ // 5. Cross-project knowledge (removed — KnowledgeTransfer deleted in Phase 26)
539
498
 
540
499
  // If no sections beyond the header, try auto-scan for first-session value
541
500
  if (sections.length <= 1) {
@@ -19,6 +19,9 @@ import { CrossReferenceLinker } from '@/knowledge/graph/linker'
19
19
  import { EpisodeManager } from '@/memory/episodic/manager'
20
20
  import { HookSessionTracker } from '@/hooks/session-tracker'
21
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'
22
25
  import { SemanticCache } from '@/intelligence/optimization/semantic-cache'
23
26
  import { PrecomputeEngine } from '@/intelligence/optimization/precompute'
24
27
  import { MemoryArchiver } from '@/memory/consolidation/archiver'
@@ -47,6 +50,9 @@ export interface Services {
47
50
  semanticCache: SemanticCache | null
48
51
  precompute: PrecomputeEngine | null
49
52
  archiver: MemoryArchiver | null
53
+ codeIndexer: CodeIndexer | null
54
+ codeQuery: CodeQuery | null
55
+ codeLinker: MemoryCodeLinker | null
50
56
  logger: Logger
51
57
  config: Config
52
58
  }
@@ -78,30 +84,41 @@ export async function initializeServices(config: Config, logger: Logger): Promis
78
84
  const serviceLogger = logger.child({ component: 'services' })
79
85
  serviceLogger.info('Initializing services...')
80
86
 
81
- // Validate vault path configuration
87
+ // Phase 30: Auto-create vault path if it doesn't exist (zero-config)
82
88
  if (!config.vaultPath) {
83
- throw new Error('Vault path not configured. Set VAULT_PATH environment variable.')
89
+ const { getHomePaths } = await import('@/config/home')
90
+ config.vaultPath = getHomePaths().vault
91
+ serviceLogger.info({ vaultPath: config.vaultPath }, 'Auto-detected vault path')
84
92
  }
85
93
 
86
94
  if (!existsSync(config.vaultPath)) {
87
- serviceLogger.warn(
88
- { vaultPath: config.vaultPath },
89
- 'Vault path does not exist - will be created on first use'
90
- )
95
+ try {
96
+ const { mkdirSync } = await import('node:fs')
97
+ mkdirSync(config.vaultPath, { recursive: true })
98
+ mkdirSync(path.join(config.vaultPath, 'Projects'), { recursive: true })
99
+ mkdirSync(path.join(config.vaultPath, 'Global'), { recursive: true })
100
+ serviceLogger.info({ vaultPath: config.vaultPath }, 'Created vault directory')
101
+ } catch (error) {
102
+ serviceLogger.warn({ error, vaultPath: config.vaultPath }, 'Failed to create vault directory, continuing without vault writes')
103
+ }
91
104
  } else {
92
105
  const projectsPath = path.join(config.vaultPath, 'Projects')
93
106
  if (!existsSync(projectsPath)) {
94
- serviceLogger.warn(
95
- { projectsPath },
96
- 'Projects directory missing in vault - will be created when first project is added'
97
- )
107
+ try {
108
+ const { mkdirSync } = await import('node:fs')
109
+ mkdirSync(projectsPath, { recursive: true })
110
+ } catch {
111
+ serviceLogger.warn({ projectsPath }, 'Failed to create Projects directory')
112
+ }
98
113
  }
99
114
  }
100
115
 
101
116
  // Initialize Memory Manager
102
- const memory = new MemoryManager(config.dbPath, logger)
117
+ // Phase 26: ChromaDB is now opt-in (default false), FTS5 is primary
118
+ const useChromaDB = config.chromadb?.enabled ?? false
119
+ const memory = new MemoryManager(config.dbPath, logger, useChromaDB)
103
120
  await memory.initialize()
104
- serviceLogger.info('Memory service initialized')
121
+ serviceLogger.info({ chromadb: useChromaDB }, 'Memory service initialized')
105
122
 
106
123
  // Initialize Vault Manager
107
124
  const vault = new VaultManager(config.vaultPath, logger, {
@@ -306,6 +323,54 @@ export async function initializeServices(config: Config, logger: Logger): Promis
306
323
  }
307
324
  }
308
325
 
326
+ // Initialize Code Intelligence (Phase 28)
327
+ let codeIndexer: CodeIndexer | null = null
328
+ let codeQuery: CodeQuery | null = null
329
+ let codeLinker: MemoryCodeLinker | null = null
330
+ let codeDbInstance: import('bun:sqlite').Database | null = null
331
+ if (config.codeIntelligence?.enabled !== false) {
332
+ try {
333
+ const { Database } = await import('bun:sqlite')
334
+ const { resolve, dirname } = await import('node:path')
335
+ const { mkdirSync, existsSync } = await import('node:fs')
336
+ const { getClaudeBrainHome } = await import('@/config/home')
337
+ const codeDbDir = resolve(getClaudeBrainHome(), 'data')
338
+ if (!existsSync(codeDbDir)) {
339
+ mkdirSync(codeDbDir, { recursive: true })
340
+ }
341
+ const codeDbPath = resolve(codeDbDir, 'code.db')
342
+ const codeDb = new Database(codeDbPath)
343
+ codeDb.exec('PRAGMA journal_mode=WAL')
344
+ codeDbInstance = codeDb
345
+
346
+ const { CodeParser } = await import('@/code-intelligence/parser')
347
+ const { CodeIndexer: CodeIndexerClass } = await import('@/code-intelligence/indexer')
348
+ const { CodeQuery: CodeQueryClass } = await import('@/code-intelligence/query')
349
+
350
+ const parser = new CodeParser()
351
+ await parser.initialize()
352
+
353
+ codeIndexer = new CodeIndexerClass(codeDb, parser, serviceLogger)
354
+ await codeIndexer.initialize()
355
+
356
+ codeQuery = new CodeQueryClass(codeDb, serviceLogger)
357
+
358
+ serviceLogger.info('Code intelligence service initialized')
359
+ } catch (error) {
360
+ serviceLogger.warn({ error }, 'Failed to initialize code intelligence, continuing without it')
361
+ }
362
+ }
363
+
364
+ // Initialize MemoryCodeLinker (Phase 29)
365
+ try {
366
+ const { MemoryCodeLinker: LinkerClass } = await import('@/code-intelligence/linker')
367
+ const memoryDb = memory.database.getDb()
368
+ codeLinker = new LinkerClass(memoryDb, codeDbInstance, serviceLogger)
369
+ serviceLogger.info('Code linker initialized')
370
+ } catch (error) {
371
+ serviceLogger.warn({ error }, 'Failed to initialize code linker, continuing without it')
372
+ }
373
+
309
374
  // Store services
310
375
  services = {
311
376
  memory,
@@ -320,6 +385,9 @@ export async function initializeServices(config: Config, logger: Logger): Promis
320
385
  semanticCache,
321
386
  precompute,
322
387
  archiver,
388
+ codeIndexer,
389
+ codeQuery,
390
+ codeLinker,
323
391
  logger,
324
392
  config
325
393
  }
@@ -434,6 +502,30 @@ export function getArchiverService(): MemoryArchiver | null {
434
502
  return getServices().archiver
435
503
  }
436
504
 
505
+ /**
506
+ * Get Code Indexer service (Phase 28)
507
+ * Returns null if code intelligence is not enabled
508
+ */
509
+ export function getCodeIndexer(): CodeIndexer | null {
510
+ return services?.codeIndexer ?? null
511
+ }
512
+
513
+ /**
514
+ * Get Code Query service (Phase 28)
515
+ * Returns null if code intelligence is not enabled
516
+ */
517
+ export function getCodeQuery(): CodeQuery | null {
518
+ return services?.codeQuery ?? null
519
+ }
520
+
521
+ /**
522
+ * Get Code Linker service (Phase 29)
523
+ * Returns null if not initialized
524
+ */
525
+ export function getCodeLinker(): MemoryCodeLinker | null {
526
+ return services?.codeLinker ?? null
527
+ }
528
+
437
529
  /**
438
530
  * Check if services are initialized
439
531
  */
@@ -516,6 +608,15 @@ export async function shutdownServices(): Promise<void> {
516
608
  serviceLogger.info('Memory archiver stopped')
517
609
  }
518
610
 
611
+ // Shut down code intelligence
612
+ if (services.codeIndexer) {
613
+ try {
614
+ serviceLogger.info('Code intelligence shut down')
615
+ } catch (error) {
616
+ serviceLogger.error({ error }, 'Failed to shut down code intelligence')
617
+ }
618
+ }
619
+
519
620
  // Cleanup Phase 12
520
621
  services.phase12.cleanup()
521
622