claude-brain 0.5.0 → 0.8.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/assets/CLAUDE-unified.md +11 -0
  3. package/package.json +2 -1
  4. package/packs/backend/node.json +173 -0
  5. package/packs/core/javascript.json +176 -0
  6. package/packs/core/typescript.json +222 -0
  7. package/packs/frontend/react.json +254 -0
  8. package/packs/meta/testing.json +172 -0
  9. package/src/cli/bin.ts +14 -0
  10. package/src/cli/commands/chroma.ts +53 -17
  11. package/src/cli/commands/hooks.ts +214 -0
  12. package/src/cli/commands/pack.ts +197 -0
  13. package/src/cli/commands/serve.ts +34 -0
  14. package/src/config/defaults.ts +1 -1
  15. package/src/config/schema.ts +85 -2
  16. package/src/hooks/brain-hook.ts +110 -0
  17. package/src/hooks/capture.ts +161 -0
  18. package/src/hooks/deduplicator.ts +72 -0
  19. package/src/hooks/index.ts +19 -0
  20. package/src/hooks/installer.ts +181 -0
  21. package/src/hooks/passive-classifier.ts +366 -0
  22. package/src/hooks/queue.ts +122 -0
  23. package/src/hooks/session-tracker.ts +199 -0
  24. package/src/hooks/types.ts +47 -0
  25. package/src/memory/chroma/client.ts +1 -1
  26. package/src/memory/chroma/index.ts +1 -1
  27. package/src/memory/chroma/store.ts +29 -9
  28. package/src/memory/index.ts +1 -0
  29. package/src/memory/store.ts +1 -0
  30. package/src/packs/index.ts +9 -0
  31. package/src/packs/loader.ts +134 -0
  32. package/src/packs/manager.ts +204 -0
  33. package/src/packs/ranker.ts +78 -0
  34. package/src/packs/types.ts +81 -0
  35. package/src/routing/entity-extractor.ts +410 -0
  36. package/src/routing/intent-classifier.ts +229 -0
  37. package/src/routing/response-filter.ts +221 -0
  38. package/src/routing/router.ts +671 -0
  39. package/src/server/handlers/call-tool.ts +7 -0
  40. package/src/server/handlers/list-tools.ts +22 -5
  41. package/src/server/handlers/tools/brain.ts +85 -0
  42. package/src/server/handlers/tools/init-project.ts +47 -0
  43. package/src/server/handlers/tools/schemas.ts +12 -0
  44. package/src/server/http-api.ts +188 -0
  45. package/src/tools/registry.ts +9 -0
  46. package/src/tools/schemas.ts +33 -1
@@ -1,18 +1,35 @@
1
1
  /**
2
2
  * List Tools Handler
3
3
  * Handles the MCP tools/list request
4
+ *
5
+ * In unified tool mode (Phase 16), only the brain() tool is exposed.
6
+ * In legacy mode, all 25 tools + brain are exposed.
4
7
  */
5
8
 
6
9
  import { ToolRegistry } from '@/tools/registry'
10
+ import { getServices, isServicesInitialized } from '@/server/services'
7
11
 
8
12
  /**
9
13
  * Handle tools/list request
10
- * Returns all available tools in MCP format
14
+ * Returns available tools in MCP format, filtered by config
11
15
  */
12
16
  export async function handleListTools() {
13
- const tools = ToolRegistry.getToolsForListResponse()
14
-
15
- return {
16
- tools
17
+ // Check if unified tool mode is enabled
18
+ if (isServicesInitialized()) {
19
+ try {
20
+ const { config } = getServices()
21
+ if (config.unifiedToolMode) {
22
+ const brainTool = ToolRegistry.getToolByName('brain')
23
+ return {
24
+ tools: brainTool ? [brainTool] : []
25
+ }
26
+ }
27
+ } catch {
28
+ // Services not ready, fall through to default
29
+ }
17
30
  }
31
+
32
+ // Default: return all tools (legacy + brain)
33
+ const tools = ToolRegistry.getToolsForListResponse()
34
+ return { tools }
18
35
  }
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Brain Tool Handler
3
+ * Phase 16: Unified single tool that replaces all 25 MCP tools
4
+ *
5
+ * Accepts natural language, classifies intent server-side,
6
+ * routes internally, and returns a unified response.
7
+ */
8
+
9
+ import type { Logger } from 'pino'
10
+ import type { ToolResponse } from '@/tools/types'
11
+ import { ToolValidator } from '@/server/utils/validators'
12
+ import { ResponseFormatter } from '@/server/utils/response-formatter'
13
+ import { withMemoryIndicator } from '@/server/utils/memory-indicator'
14
+ import { ErrorHandler } from '@/server/utils/error-handler'
15
+ import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js'
16
+ import { BrainSchema } from './schemas'
17
+ import { getBrainRouter } from '@/routing/router'
18
+
19
+ export async function handleBrain(
20
+ args: unknown,
21
+ logger: Logger
22
+ ): Promise<ToolResponse> {
23
+ try {
24
+ const input = ToolValidator.validate(args, BrainSchema)
25
+ const { message, project } = input
26
+
27
+ logger.debug(
28
+ {
29
+ messageLength: message.length,
30
+ project,
31
+ messagePreview: message.slice(0, 80)
32
+ },
33
+ 'Brain tool called'
34
+ )
35
+
36
+ const router = getBrainRouter(logger)
37
+ const response = await router.route({ message, project })
38
+
39
+ logger.info(
40
+ {
41
+ action: response.action,
42
+ summary: response.summary,
43
+ relevantItems: response.relevantItems
44
+ },
45
+ 'Brain tool response'
46
+ )
47
+
48
+ // Format response
49
+ if (response.action === 'none' && !response.content) {
50
+ return ResponseFormatter.text(response.summary || 'No action needed.')
51
+ }
52
+
53
+ const parts: string[] = []
54
+
55
+ if (response.summary) {
56
+ parts.push(`**${response.summary}**`)
57
+ parts.push('')
58
+ }
59
+
60
+ if (response.content) {
61
+ parts.push(response.content)
62
+ }
63
+
64
+ const content = parts.join('\n')
65
+
66
+ // Add memory indicator if we found relevant items
67
+ if (response.relevantItems > 0 && response.action === 'retrieved') {
68
+ return ResponseFormatter.text(withMemoryIndicator(content, response.relevantItems))
69
+ }
70
+
71
+ return ResponseFormatter.text(content)
72
+
73
+ } catch (error) {
74
+ ErrorHandler.logError(logger, error, { tool: 'brain', args })
75
+
76
+ if (error instanceof McpError) {
77
+ throw error
78
+ }
79
+
80
+ throw new McpError(
81
+ ErrorCode.InternalError,
82
+ `Brain tool failed: ${error instanceof Error ? error.message : 'Unknown error'}`
83
+ )
84
+ }
85
+ }
@@ -11,8 +11,11 @@ import { ToolValidator } from '@/server/utils/validators'
11
11
  import { ResponseFormatter } from '@/server/utils/response-formatter'
12
12
  import { ErrorHandler } from '@/server/utils/error-handler'
13
13
  import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js'
14
+ import { PackManager, PackLoader, type PackLoadResult } from '@/packs/index'
14
15
  import fs from 'fs/promises'
15
16
  import path from 'path'
17
+ import { fileURLToPath } from 'node:url'
18
+ import { dirname, resolve } from 'node:path'
16
19
 
17
20
  interface InitProjectInput {
18
21
  project_path?: string
@@ -176,6 +179,36 @@ Has TypeScript: ${analysis.hasTypeScript}
176
179
  logger.info({ normalizedName }, 'Project saved to memory')
177
180
  }
178
181
 
182
+ // Phase 18: Load knowledge packs based on detected tech stack
183
+ let packResult: PackLoadResult | null = null
184
+ try {
185
+ const initFile = fileURLToPath(import.meta.url)
186
+ const initDir = dirname(initFile)
187
+ const PACKAGE_ROOT = resolve(initDir, '..', '..', '..', '..')
188
+ const dataDir = path.join(process.env.CLAUDE_BRAIN_HOME || path.join(process.env.HOME || '~', '.claude-brain'), 'data')
189
+
190
+ // Default packs config
191
+ const packsConfig = {
192
+ enabled: true,
193
+ packsDir: 'packs',
194
+ alwaysLoadCore: true,
195
+ alwaysLoadMeta: true,
196
+ communityConfidenceMultiplier: 0.8,
197
+ personalBoost: 1.2,
198
+ projectBoost: 1.15
199
+ }
200
+
201
+ if (packsConfig.enabled) {
202
+ const memory = getMemoryService()
203
+ const packManager = new PackManager(logger, packsConfig, PACKAGE_ROOT, dataDir)
204
+ const packLoader = new PackLoader(logger, memory, packManager, packsConfig)
205
+ packResult = await packLoader.loadPacksForProject(normalizedName, analysis.techStack)
206
+ logger.info({ packResult }, 'Knowledge packs loaded')
207
+ }
208
+ } catch (packError) {
209
+ logger.warn({ error: packError }, 'Pack loading failed (non-blocking)')
210
+ }
211
+
179
212
  // Build response
180
213
  const parts: string[] = []
181
214
  parts.push(`## ${projectExists ? '🔄 Project Updated' : '✅ Project Initialized'}: ${normalizedName}\n`)
@@ -226,6 +259,20 @@ Has TypeScript: ${analysis.hasTypeScript}
226
259
  parts.push(`Project context saved to semantic memory for future recall.`)
227
260
  }
228
261
 
262
+ if (packResult && packResult.packsLoaded > 0) {
263
+ parts.push(`\n### Knowledge Packs`)
264
+ parts.push(`Loaded **${packResult.packsLoaded}** pack(s) with **${packResult.entriesLoaded}** entries:`)
265
+ for (const detail of packResult.packDetails) {
266
+ parts.push(`- **${detail.name}** (${detail.entriesLoaded} entries)`)
267
+ }
268
+ if (packResult.skipped.length > 0) {
269
+ parts.push(`\nSkipped: ${packResult.skipped.map(s => `${s.packId} (${s.reason})`).join(', ')}`)
270
+ }
271
+ } else if (packResult && packResult.skipped.length > 0) {
272
+ parts.push(`\n### Knowledge Packs`)
273
+ parts.push(`All packs already loaded (${packResult.skipped.length} skipped).`)
274
+ }
275
+
229
276
  parts.push(`\n### Next Steps`)
230
277
  parts.push(`1. Use \`get_project_context("${normalizedName}")\` to view full context`)
231
278
  parts.push(`2. Use \`remember_decision\` to save architectural decisions`)
@@ -209,6 +209,15 @@ export const FindCrossProjectPatternsSchema = z.object({
209
209
  query: z.string().optional()
210
210
  })
211
211
 
212
+ // ============================================================================
213
+ // Phase 16 - Unified Brain Tool
214
+ // ============================================================================
215
+
216
+ export const BrainSchema = z.object({
217
+ message: z.string().min(1, 'message is required'),
218
+ project: z.string().optional()
219
+ })
220
+
212
221
  // ============================================================================
213
222
  // Type exports for validated inputs
214
223
  // ============================================================================
@@ -239,3 +248,6 @@ export type ValidatedDetectTrends = z.infer<typeof DetectTrendsSchema>
239
248
  export type ValidatedWhatIfAnalysis = z.infer<typeof WhatIfAnalysisSchema>
240
249
  export type ValidatedGetRecommendations = z.infer<typeof GetRecommendationsSchema>
241
250
  export type ValidatedFindCrossProjectPatterns = z.infer<typeof FindCrossProjectPatternsSchema>
251
+
252
+ // Phase 16
253
+ export type ValidatedBrain = z.infer<typeof BrainSchema>
@@ -7,12 +7,24 @@ import { Hono } from 'hono'
7
7
  import type { Logger } from 'pino'
8
8
  import type { Config } from '@/config'
9
9
  import { getMemoryService, getVaultService, isServicesInitialized } from '@/server/services'
10
+ import type { MemoryManager } from '@/memory'
11
+ import type { CapturedKnowledge, HookStats } from '@/hooks/types'
12
+ import { SmartDeduplicator } from '@/hooks/deduplicator'
13
+ import type { HookSessionTracker } from '@/hooks/session-tracker'
10
14
 
11
15
  export class HttpApiServer {
12
16
  private app: Hono
13
17
  private logger: Logger
14
18
  private config: Config
15
19
  private server?: any
20
+ private sessionTracker?: HookSessionTracker
21
+ private hookStats: HookStats = {
22
+ totalCaptured: 0,
23
+ totalSkipped: 0,
24
+ totalMerged: 0,
25
+ sessionsTracked: 0,
26
+ queueSize: 0,
27
+ }
16
28
 
17
29
  constructor(config: Config, logger: Logger) {
18
30
  this.config = config
@@ -32,6 +44,11 @@ export class HttpApiServer {
32
44
  })
33
45
  }
34
46
 
47
+ /** Set the session tracker (called from serve.ts after initialization) */
48
+ setSessionTracker(tracker: HookSessionTracker): void {
49
+ this.sessionTracker = tracker
50
+ }
51
+
35
52
  private setupRoutes(): void {
36
53
  // Health check
37
54
  this.app.get('/api/health', (c) => {
@@ -59,6 +76,11 @@ export class HttpApiServer {
59
76
  this.app.post('/api/claude-brain/recognize-pattern', (c) => this.handleRecognizePattern(c))
60
77
  this.app.post('/api/claude-brain/record-correction', (c) => this.handleRecordCorrection(c))
61
78
  this.app.post('/api/claude-brain/update-progress', (c) => this.handleUpdateProgress(c))
79
+
80
+ // Phase 17: Hook passive learning endpoints
81
+ this.app.post('/api/hooks/ingest', (c) => this.handleHookIngest(c))
82
+ this.app.post('/api/hooks/session-end', (c) => this.handleHookSessionEnd(c))
83
+ this.app.get('/api/hooks/status', () => this.handleHookStatus())
62
84
  }
63
85
 
64
86
  private async handleListProjects(): Promise<Response> {
@@ -436,6 +458,172 @@ export class HttpApiServer {
436
458
  }
437
459
  }
438
460
 
461
+ // ─── Phase 17: Hook Endpoints ────────────────────────────
462
+
463
+ private async handleHookIngest(c: any): Promise<Response> {
464
+ try {
465
+ const body = await c.req.json()
466
+ const items: CapturedKnowledge[] = body.knowledge || []
467
+ const sessionId: string | undefined = body.sessionId
468
+
469
+ if (items.length === 0) {
470
+ return Response.json({ success: true, data: { stored: 0, skipped: 0, merged: 0 } })
471
+ }
472
+
473
+ const memoryService = getMemoryService()
474
+ if (!memoryService || !memoryService.isInitialized()) {
475
+ return Response.json(
476
+ { success: false, error: 'Memory service not initialized' },
477
+ { status: 503 }
478
+ )
479
+ }
480
+
481
+ const deduplicator = new SmartDeduplicator(this.config.hooks?.deduplication)
482
+ let stored = 0, skipped = 0, merged = 0
483
+
484
+ for (const knowledge of items) {
485
+ const action = await deduplicator.beforeStore(knowledge, memoryService)
486
+
487
+ switch (action.action) {
488
+ case 'skip':
489
+ skipped++
490
+ this.hookStats.totalSkipped++
491
+ break
492
+
493
+ case 'merge':
494
+ merged++
495
+ this.hookStats.totalMerged++
496
+ // For merge, we store as update — treat as new store with merged content
497
+ await this.storeKnowledge({ ...knowledge, content: action.mergedContent }, memoryService)
498
+ stored++
499
+ break
500
+
501
+ case 'store_new':
502
+ await this.storeKnowledge(knowledge, memoryService)
503
+ stored++
504
+ this.hookStats.totalCaptured++
505
+ break
506
+ }
507
+
508
+ // Track in session if sessionId provided
509
+ if (sessionId && this.sessionTracker) {
510
+ await this.sessionTracker.track(sessionId, knowledge)
511
+ }
512
+ }
513
+
514
+ this.hookStats.lastCaptureAt = new Date().toISOString()
515
+
516
+ this.logger.debug(
517
+ { stored, skipped, merged, sessionId },
518
+ 'Hook ingest processed'
519
+ )
520
+
521
+ return Response.json({ success: true, data: { stored, skipped, merged } })
522
+ } catch (error) {
523
+ this.logger.error({ error }, 'Failed to process hook ingest')
524
+ return Response.json(
525
+ { success: false, error: 'Failed to ingest hook data' },
526
+ { status: 500 }
527
+ )
528
+ }
529
+ }
530
+
531
+ private async handleHookSessionEnd(c: any): Promise<Response> {
532
+ try {
533
+ const body = await c.req.json()
534
+ const sessionId: string | undefined = body.sessionId
535
+
536
+ if (!sessionId) {
537
+ return Response.json(
538
+ { success: false, error: 'sessionId required' },
539
+ { status: 400 }
540
+ )
541
+ }
542
+
543
+ if (this.sessionTracker) {
544
+ await this.sessionTracker.endSession(sessionId)
545
+ this.hookStats.sessionsTracked++
546
+ }
547
+
548
+ return Response.json({ success: true, data: { message: 'Session ended' } })
549
+ } catch (error) {
550
+ this.logger.error({ error }, 'Failed to end hook session')
551
+ return Response.json(
552
+ { success: false, error: 'Failed to end session' },
553
+ { status: 500 }
554
+ )
555
+ }
556
+ }
557
+
558
+ private handleHookStatus(): Response {
559
+ const sessionStats = this.sessionTracker?.getStats()
560
+
561
+ return Response.json({
562
+ success: true,
563
+ data: {
564
+ ...this.hookStats,
565
+ activeSessions: sessionStats?.activeSessions ?? 0,
566
+ sessionItems: sessionStats?.totalItems ?? 0,
567
+ hooksEnabled: this.config.hooks?.enabled ?? false,
568
+ },
569
+ })
570
+ }
571
+
572
+ /** Route captured knowledge to the appropriate store method */
573
+ private async storeKnowledge(
574
+ knowledge: CapturedKnowledge,
575
+ memoryService: MemoryManager
576
+ ): Promise<void> {
577
+ const project = knowledge.project || 'unknown'
578
+
579
+ switch (knowledge.type) {
580
+ case 'decision':
581
+ await memoryService.rememberDecision(
582
+ project,
583
+ `Captured via hook: ${knowledge.metadata.action || 'tool-use'}`,
584
+ knowledge.content,
585
+ `Auto-captured from ${knowledge.source}`,
586
+ { tags: knowledge.technologies }
587
+ )
588
+ break
589
+
590
+ case 'pattern':
591
+ await memoryService.storePattern({
592
+ project,
593
+ pattern_type: 'solution',
594
+ description: knowledge.content,
595
+ context: `Captured via hook: ${knowledge.metadata.action || 'tool-use'}`,
596
+ confidence: knowledge.confidence,
597
+ })
598
+ break
599
+
600
+ case 'correction':
601
+ await memoryService.storeCorrection({
602
+ project,
603
+ original: knowledge.metadata.command || 'Unknown',
604
+ correction: knowledge.content,
605
+ reasoning: 'Auto-captured from hook passive learning',
606
+ context: `Captured via hook: ${knowledge.metadata.action || 'tool-use'}`,
607
+ confidence: knowledge.confidence,
608
+ })
609
+ break
610
+
611
+ case 'progress': {
612
+ const vaultService = getVaultService()
613
+ if (vaultService) {
614
+ try {
615
+ await vaultService.updateProgress(project, {
616
+ status: `Hook: ${knowledge.content.slice(0, 200)}`,
617
+ })
618
+ } catch {
619
+ // Vault might not have this project
620
+ }
621
+ }
622
+ break
623
+ }
624
+ }
625
+ }
626
+
439
627
  async start(): Promise<void> {
440
628
  const port = this.config.port || 3000
441
629
 
@@ -69,6 +69,15 @@ export class ToolRegistry {
69
69
  return Object.values(TOOLS)
70
70
  }
71
71
 
72
+ /**
73
+ * Get only the unified brain tool (Phase 16)
74
+ * @returns Array containing only the brain tool
75
+ */
76
+ static getUnifiedTools(): ToolDefinition[] {
77
+ const brain = this.getToolByName('brain')
78
+ return brain ? [brain] : []
79
+ }
80
+
72
81
  /**
73
82
  * Validate that required parameters are present
74
83
  * @param toolName - Name of the tool
@@ -9,7 +9,7 @@
9
9
  */
10
10
 
11
11
  /** Tool definition type */
12
- interface ToolDefinition {
12
+ export interface ToolDefinition {
13
13
  name: string
14
14
  description: string
15
15
  inputSchema: {
@@ -895,6 +895,38 @@ export const TOOLS = {
895
895
  }
896
896
  }
897
897
  }
898
+ },
899
+ // =========================================================================
900
+ // Phase 16 - Unified Brain Tool
901
+ // =========================================================================
902
+
903
+ /**
904
+ * BRAIN
905
+ * Unified intelligent tool that replaces all 25 tools
906
+ *
907
+ * Use this when:
908
+ * - You want a single tool for all Claude Brain interactions
909
+ * - Enabled via unifiedToolMode config flag
910
+ *
911
+ * The server classifies intent and routes internally
912
+ */
913
+ BRAIN: {
914
+ name: 'brain',
915
+ description: 'Your intelligent memory. Tell it what you are doing in natural language. The server classifies intent and routes internally — no need to pick the right tool.',
916
+ inputSchema: {
917
+ type: 'object' as const,
918
+ properties: {
919
+ message: {
920
+ type: 'string',
921
+ description: 'What you are doing, decided, learned, or need. Natural language.'
922
+ },
923
+ project: {
924
+ type: 'string',
925
+ description: 'Project name (optional — auto-detected from message if omitted)'
926
+ }
927
+ },
928
+ required: ['message']
929
+ }
898
930
  }
899
931
  } as const satisfies Record<string, ToolDefinition>
900
932