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.
- package/VERSION +1 -1
- package/package.json +3 -1
- package/scripts/postinstall.mjs +80 -104
- package/src/cli/auto-setup.ts +1 -9
- package/src/cli/bin.ts +23 -2
- package/src/cli/commands/export.ts +130 -0
- package/src/cli/commands/reindex.ts +107 -0
- package/src/cli/commands/serve.ts +54 -0
- package/src/cli/commands/status.ts +158 -0
- package/src/code-intelligence/indexer.ts +315 -0
- package/src/code-intelligence/linker.ts +178 -0
- package/src/code-intelligence/parser.ts +484 -0
- package/src/code-intelligence/query.ts +291 -0
- package/src/code-intelligence/schema.ts +83 -0
- package/src/code-intelligence/types.ts +95 -0
- package/src/config/defaults.ts +3 -3
- package/src/config/loader.ts +6 -0
- package/src/config/schema.ts +28 -2
- package/src/health/index.ts +5 -2
- package/src/hooks/brain-hook.ts +4 -1
- package/src/hooks/context-hook.ts +69 -10
- package/src/hooks/installer.ts +4 -7
- package/src/intelligence/cross-project/index.ts +1 -7
- package/src/intelligence/prediction/index.ts +1 -7
- package/src/intelligence/reasoning/index.ts +1 -7
- package/src/memory/compression.ts +105 -0
- package/src/memory/fts5-search.ts +456 -0
- package/src/memory/index.ts +342 -38
- package/src/memory/migrations/add-fts5.ts +98 -0
- package/src/memory/pruning.ts +60 -0
- package/src/routing/intent-classifier.ts +58 -1
- package/src/routing/response-filter.ts +128 -0
- package/src/routing/router.ts +457 -54
- package/src/server/http-api.ts +319 -1
- package/src/server/providers/resources.ts +1 -42
- package/src/server/services.ts +113 -12
- package/src/server/web-viewer.ts +1115 -0
- package/src/setup/index.ts +12 -22
- package/src/tools/schemas.ts +1 -1
- package/src/intelligence/cross-project/affinity.ts +0 -159
- package/src/intelligence/cross-project/transfer.ts +0 -201
- package/src/intelligence/prediction/context-anticipator.ts +0 -198
- package/src/intelligence/prediction/decision-predictor.ts +0 -184
- package/src/intelligence/reasoning/counterfactual.ts +0 -248
- package/src/intelligence/reasoning/synthesizer.ts +0 -167
- package/src/setup/wizard.ts +0 -459
package/src/server/http-api.ts
CHANGED
|
@@ -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
|
-
|
|
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) {
|
package/src/server/services.ts
CHANGED
|
@@ -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
|
-
//
|
|
87
|
+
// Phase 30: Auto-create vault path if it doesn't exist (zero-config)
|
|
82
88
|
if (!config.vaultPath) {
|
|
83
|
-
|
|
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
|
-
|
|
88
|
-
{
|
|
89
|
-
|
|
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
|
-
|
|
95
|
-
{
|
|
96
|
-
|
|
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
|
-
|
|
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
|
|