claude-brain 0.30.2 → 0.30.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +241 -191
- package/VERSION +1 -1
- package/assets/CLAUDE-unified.md +11 -11
- package/assets/CLAUDE.md +29 -29
- package/package.json +7 -3
- package/packs/backend/node.json +173 -173
- package/packs/core/javascript.json +176 -176
- package/packs/core/typescript.json +222 -222
- package/packs/frontend/react.json +254 -254
- package/packs/meta/testing.json +172 -172
- package/scripts/postinstall.mjs +531 -531
- package/src/automation/decision-detector.ts +452 -452
- package/src/automation/phase12-manager.ts +456 -456
- package/src/automation/proactive-recall.ts +373 -373
- package/src/automation/project-detector.ts +310 -310
- package/src/automation/repo-scanner.ts +210 -205
- package/src/cli/auto-setup.ts +75 -75
- package/src/cli/auto-start.ts +266 -266
- package/src/cli/bin.ts +264 -264
- package/src/cli/commands/autostart.ts +90 -90
- package/src/cli/commands/chroma.ts +578 -577
- package/src/cli/commands/export-training.ts +70 -70
- package/src/cli/commands/export.ts +130 -130
- package/src/cli/commands/git-hook.ts +183 -183
- package/src/cli/commands/hooks.ts +217 -217
- package/src/cli/commands/init.ts +123 -123
- package/src/cli/commands/install-mcp.ts +122 -111
- package/src/cli/commands/models.ts +979 -979
- package/src/cli/commands/pack.ts +200 -200
- package/src/cli/commands/refresh.ts +344 -339
- package/src/cli/commands/reindex.ts +120 -120
- package/src/cli/commands/serve.ts +466 -463
- package/src/cli/commands/start.ts +44 -44
- package/src/cli/commands/status.ts +220 -203
- package/src/cli/commands/uninstall-mcp.ts +45 -41
- package/src/cli/commands/update.ts +130 -124
- package/src/cli/migrate-chroma.ts +106 -106
- package/src/cli/ui/animations.ts +80 -80
- package/src/cli/ui/components.ts +82 -82
- package/src/cli/ui/index.ts +4 -4
- package/src/cli/ui/logo.ts +36 -36
- package/src/cli/ui/theme.ts +55 -55
- package/src/code-intelligence/indexer.ts +352 -352
- package/src/code-intelligence/linker.ts +178 -178
- package/src/code-intelligence/parser.ts +484 -484
- package/src/code-intelligence/query.ts +291 -291
- package/src/code-intelligence/schema.ts +83 -83
- package/src/code-intelligence/types.ts +95 -95
- package/src/config/defaults.ts +52 -52
- package/src/config/home.ts +56 -56
- package/src/config/index.ts +5 -5
- package/src/config/loader.ts +192 -192
- package/src/config/schema.ts +446 -415
- package/src/config/validator.ts +182 -182
- package/src/context/assembler.ts +407 -400
- package/src/context/index.ts +79 -79
- package/src/context/progress-tracker.ts +174 -174
- package/src/context/standards-manager.ts +287 -287
- package/src/context/validator.ts +58 -58
- package/src/diagnostics/index.ts +122 -121
- package/src/health/index.ts +233 -232
- package/src/hooks/brain-hook.ts +134 -131
- package/src/hooks/capture.ts +168 -168
- package/src/hooks/claude-code-mastery.md +112 -112
- package/src/hooks/context-hook.ts +260 -245
- package/src/hooks/deduplicator.ts +72 -72
- package/src/hooks/git-capture.ts +109 -109
- package/src/hooks/git-hook-installer.ts +211 -207
- package/src/hooks/index.ts +20 -20
- package/src/hooks/installer.ts +306 -288
- package/src/hooks/interceptor-hook.ts +204 -201
- package/src/hooks/passive-classifier.ts +397 -397
- package/src/hooks/queue.ts +160 -129
- package/src/hooks/session-tracker.ts +312 -312
- package/src/hooks/types.ts +52 -52
- package/src/index.ts +7 -7
- package/src/intelligence/cross-project/generalizer.ts +283 -283
- package/src/intelligence/cross-project/index.ts +7 -7
- package/src/intelligence/hf-downloader.ts +222 -222
- package/src/intelligence/hf-manifest.json +78 -78
- package/src/intelligence/index.ts +24 -24
- package/src/intelligence/inference-router.ts +762 -762
- package/src/intelligence/model-manager.ts +263 -245
- package/src/intelligence/optimization/index.ts +10 -10
- package/src/intelligence/optimization/precompute.ts +202 -202
- package/src/intelligence/optimization/semantic-cache.ts +213 -207
- package/src/intelligence/prediction/index.ts +7 -7
- package/src/intelligence/prediction/recommender.ts +276 -268
- package/src/intelligence/reasoning/chain-retrieval.ts +243 -247
- package/src/intelligence/reasoning/index.ts +7 -7
- package/src/intelligence/temporal/evolution.ts +193 -197
- package/src/intelligence/temporal/index.ts +16 -16
- package/src/intelligence/temporal/query-processor.ts +190 -190
- package/src/intelligence/temporal/timeline.ts +272 -259
- package/src/intelligence/temporal/trends.ts +263 -263
- package/src/intelligence/tokenizer.ts +118 -118
- package/src/knowledge/entity-extractor.ts +447 -443
- package/src/knowledge/graph/builder.ts +185 -185
- package/src/knowledge/graph/linker.ts +201 -201
- package/src/knowledge/graph/memory-graph.ts +359 -359
- package/src/knowledge/graph/schema.ts +99 -99
- package/src/knowledge/graph/search.ts +166 -166
- package/src/knowledge/relationship-extractor.ts +108 -108
- package/src/memory/chroma/client.ts +211 -192
- package/src/memory/chroma/collection-manager.ts +92 -92
- package/src/memory/chroma/config.ts +57 -57
- package/src/memory/chroma/embeddings.ts +177 -175
- package/src/memory/chroma/index.ts +82 -82
- package/src/memory/chroma/migration.ts +270 -270
- package/src/memory/chroma/schemas.ts +69 -69
- package/src/memory/chroma/search.ts +319 -315
- package/src/memory/chroma/store.ts +755 -747
- package/src/memory/compression.ts +121 -121
- package/src/memory/consolidation/archiver.ts +162 -165
- package/src/memory/consolidation/merger.ts +182 -186
- package/src/memory/consolidation/scorer.ts +136 -136
- package/src/memory/database.ts +9 -0
- package/src/memory/dual-write.ts +145 -0
- package/src/memory/embeddings.ts +226 -226
- package/src/memory/episodic/detector.ts +108 -108
- package/src/memory/episodic/manager.ts +347 -351
- package/src/memory/episodic/summarizer.ts +179 -179
- package/src/memory/episodic/types.ts +52 -52
- package/src/memory/fts5-search.ts +692 -633
- package/src/memory/index.ts +943 -1060
- package/src/memory/migrations/add-fts5.ts +118 -108
- package/src/memory/patterns.ts +438 -438
- package/src/memory/pruning.ts +60 -60
- package/src/memory/schema.ts +88 -88
- package/src/memory/store.ts +911 -787
- package/src/orchestrator/handlers/decision-handler.ts +204 -204
- package/src/packs/index.ts +9 -9
- package/src/packs/loader.ts +134 -134
- package/src/packs/manager.ts +204 -204
- package/src/packs/ranker.ts +78 -78
- package/src/packs/types.ts +81 -81
- package/src/phase12/index.ts +5 -5
- package/src/retrieval/bm25/index.ts +300 -297
- package/src/retrieval/bm25/tokenizer.ts +184 -184
- package/src/retrieval/feedback/adaptive.ts +221 -221
- package/src/retrieval/feedback/index.ts +16 -16
- package/src/retrieval/feedback/metrics.ts +221 -221
- package/src/retrieval/feedback/store.ts +283 -283
- package/src/retrieval/fusion/index.ts +194 -194
- package/src/retrieval/fusion/rrf.ts +165 -165
- package/src/retrieval/index.ts +12 -12
- package/src/retrieval/pipeline.ts +375 -375
- package/src/retrieval/query/expander.ts +203 -203
- package/src/retrieval/query/index.ts +27 -27
- package/src/retrieval/query/intent-classifier.ts +252 -252
- package/src/retrieval/query/temporal-parser.ts +295 -295
- package/src/retrieval/reranker/index.ts +189 -188
- package/src/retrieval/reranker/model.ts +99 -95
- package/src/retrieval/service.ts +125 -125
- package/src/retrieval/types.ts +162 -162
- package/src/routing/entity-extractor.ts +454 -454
- package/src/routing/handlers/exploration-handler.ts +369 -0
- package/src/routing/handlers/index.ts +19 -0
- package/src/routing/handlers/memory-handler.ts +273 -0
- package/src/routing/handlers/mutation-handler.ts +241 -0
- package/src/routing/handlers/recall-handler.ts +642 -0
- package/src/routing/handlers/shared.ts +515 -0
- package/src/routing/handlers/types.ts +48 -0
- package/src/routing/intent-classifier.ts +552 -552
- package/src/routing/response-filter.ts +399 -391
- package/src/routing/router.ts +245 -2193
- package/src/routing/search-engine.ts +521 -514
- package/src/routing/types.ts +104 -94
- package/src/scripts/health-check.ts +118 -118
- package/src/scripts/setup.ts +122 -122
- package/src/server/auto-updater.ts +283 -276
- package/src/server/handlers/call-tool.ts +159 -159
- package/src/server/handlers/list-tools.ts +35 -35
- package/src/server/handlers/tools/auto-remember.ts +165 -165
- package/src/server/handlers/tools/brain.ts +86 -86
- package/src/server/handlers/tools/create-project.ts +135 -135
- package/src/server/handlers/tools/get-code-standards.ts +123 -123
- package/src/server/handlers/tools/get-corrections.ts +152 -152
- package/src/server/handlers/tools/get-patterns.ts +156 -156
- package/src/server/handlers/tools/get-project-context.ts +75 -75
- package/src/server/handlers/tools/index.ts +30 -30
- package/src/server/handlers/tools/init-project.ts +756 -756
- package/src/server/handlers/tools/list-projects.ts +126 -126
- package/src/server/handlers/tools/recall-similar.ts +87 -87
- package/src/server/handlers/tools/recognize-pattern.ts +132 -132
- package/src/server/handlers/tools/record-correction.ts +131 -131
- package/src/server/handlers/tools/remember-decision.ts +168 -168
- package/src/server/handlers/tools/schemas.ts +179 -179
- package/src/server/handlers/tools/search-code.ts +122 -122
- package/src/server/handlers/tools/smart-context.ts +146 -146
- package/src/server/handlers/tools/update-progress.ts +131 -131
- package/src/server/http-api.ts +215 -1229
- package/src/server/mcp-proxy.ts +85 -84
- package/src/server/mcp-server.ts +285 -284
- package/src/server/middleware/auth.ts +39 -0
- package/src/server/middleware/error-handler.ts +37 -0
- package/src/server/middleware/rate-limit.ts +53 -0
- package/src/server/middleware/validate.ts +42 -0
- package/src/server/pid-manager.ts +137 -136
- package/src/server/providers/resources.ts +581 -581
- package/src/server/routes/code.ts +228 -0
- package/src/server/routes/context.ts +26 -0
- package/src/server/routes/health.ts +19 -0
- package/src/server/routes/helpers.ts +100 -0
- package/src/server/routes/hooks.ts +197 -0
- package/src/server/routes/mcp.ts +47 -0
- package/src/server/routes/memory.ts +397 -0
- package/src/server/routes/models.ts +96 -0
- package/src/server/routes/projects.ts +89 -0
- package/src/server/routes/types.ts +21 -0
- package/src/server/schemas/api-schemas.ts +202 -0
- package/src/server/services.ts +720 -720
- package/src/server/utils/memory-indicator.ts +84 -84
- package/src/server/utils/response-formatter.ts +129 -129
- package/src/server/web-viewer.ts +1145 -1115
- package/src/setup/index.ts +38 -38
- package/src/tools/registry.ts +115 -115
- package/src/tools/schemas.ts +666 -666
- package/src/tools/types.ts +412 -412
- package/src/training/data-store.ts +320 -298
- package/src/training/retrain-pipeline.ts +399 -394
- package/src/utils/error-handler.ts +136 -136
- package/src/utils/index.ts +58 -58
- package/src/utils/kill-port.ts +55 -53
- package/src/utils/phase12-helper.ts +56 -56
- package/src/utils/safe-path.ts +43 -0
- package/src/utils/timing.ts +47 -47
- package/src/utils/transaction.ts +63 -63
- package/src/vault/index.ts +4 -3
- package/src/vault/paths.ts +106 -106
- package/src/vault/query.ts +4 -1
- package/src/vault/reader.ts +44 -1
- package/src/vault/watcher.ts +24 -1
- package/src/vault/writer.ts +487 -413
- package/skills/persistent-memory/SKILL.md +0 -148
- package/skills/persistent-memory/references/tool-reference.md +0 -90
|
@@ -0,0 +1,642 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recall Handler
|
|
3
|
+
* Task 3.1: Recall intents — handleSessionStart, handleContextNeeded,
|
|
4
|
+
* handleQuestion, handleDetailRequest
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { HandlerContext, BrainExtractedEntities, BrainResponse, ClassificationResult, TierResults } from './types'
|
|
8
|
+
import { DEFAULT_PROJECT } from './types'
|
|
9
|
+
import type { NormalizedResult } from '../types'
|
|
10
|
+
import { ResponseFilter, type FilterableResult, formatCompactResponse, formatDetailResponse } from '../response-filter'
|
|
11
|
+
import {
|
|
12
|
+
servicesNotReady,
|
|
13
|
+
registerEpisodeMessage,
|
|
14
|
+
queryCodeIntelligence,
|
|
15
|
+
isSessionRecallQuery,
|
|
16
|
+
isComplexQuestion,
|
|
17
|
+
detectCategoryIntent,
|
|
18
|
+
findRecentDecisions,
|
|
19
|
+
lookupById,
|
|
20
|
+
incrementAccess
|
|
21
|
+
} from './shared'
|
|
22
|
+
import {
|
|
23
|
+
getMemoryService,
|
|
24
|
+
getContextService,
|
|
25
|
+
getPhase12Service,
|
|
26
|
+
getSessionTracker,
|
|
27
|
+
getCodeQuery,
|
|
28
|
+
isServicesInitialized
|
|
29
|
+
} from '@/server/services'
|
|
30
|
+
|
|
31
|
+
export async function handleSessionStart(
|
|
32
|
+
ctx: HandlerContext,
|
|
33
|
+
message: string,
|
|
34
|
+
project: string | undefined,
|
|
35
|
+
entities: BrainExtractedEntities
|
|
36
|
+
): Promise<BrainResponse> {
|
|
37
|
+
if (!project) {
|
|
38
|
+
return {
|
|
39
|
+
action: 'none',
|
|
40
|
+
summary: 'No project detected',
|
|
41
|
+
content: 'Could not determine project. Please specify which project you are working on.',
|
|
42
|
+
relevantItems: 0
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (!isServicesInitialized()) {
|
|
47
|
+
return servicesNotReady()
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const contextService = getContextService()
|
|
51
|
+
const phase12 = getPhase12Service()
|
|
52
|
+
|
|
53
|
+
// Get project context
|
|
54
|
+
const context = await contextService.getContext(project, {
|
|
55
|
+
includeMemories: false,
|
|
56
|
+
includeProgress: true,
|
|
57
|
+
includeStandards: true,
|
|
58
|
+
maxTokens: 6000,
|
|
59
|
+
relevanceThreshold: 0.5
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
const formattedContext = contextService.formatter.format(context)
|
|
63
|
+
|
|
64
|
+
// Process with Phase 12 for proactive recall
|
|
65
|
+
const phase12Result = await phase12.processMessage(
|
|
66
|
+
entities.topic || message,
|
|
67
|
+
project
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
// Build response
|
|
71
|
+
const parts: string[] = [formattedContext]
|
|
72
|
+
|
|
73
|
+
if (phase12Result.recalledMemories?.memories.length) {
|
|
74
|
+
parts.push('\n---\n## Relevant Past Decisions\n')
|
|
75
|
+
for (const mem of phase12Result.recalledMemories.memories) {
|
|
76
|
+
const similarity = Math.round((mem.similarity || 0) * 100)
|
|
77
|
+
const decisionText = mem.decision?.decision || mem.memory?.content || ''
|
|
78
|
+
const safeText = typeof decisionText === 'string' ? decisionText : JSON.stringify(decisionText)
|
|
79
|
+
parts.push(`**[${similarity}%]** ${safeText.slice(0, 200)}`)
|
|
80
|
+
if (mem.decision?.reasoning) {
|
|
81
|
+
const reasoning = typeof mem.decision.reasoning === 'string' ? mem.decision.reasoning : JSON.stringify(mem.decision.reasoning)
|
|
82
|
+
parts.push(` _${reasoning}_`)
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// If Phase 12 found nothing, do a direct search fallback via SearchEngine
|
|
88
|
+
if (!phase12Result.recalledMemories?.memories.length) {
|
|
89
|
+
try {
|
|
90
|
+
const directResults = await ctx.searchEngine.enhancedSearch(entities.topic || message, {
|
|
91
|
+
project,
|
|
92
|
+
limit: 5,
|
|
93
|
+
minSimilarity: 0.3
|
|
94
|
+
})
|
|
95
|
+
if (directResults.length > 0) {
|
|
96
|
+
parts.push('\n---\n## Related Memories\n')
|
|
97
|
+
for (const r of directResults) {
|
|
98
|
+
const similarity = Math.round((r.score || 0) * 100)
|
|
99
|
+
const safeContent = typeof r.content === 'string' ? r.content : JSON.stringify(r.content ?? '')
|
|
100
|
+
parts.push(`**[${similarity}%]** ${safeContent.slice(0, 200)}`)
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
} catch {
|
|
104
|
+
// Direct search failed, continue without
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Phase 32: Include code file map in session context if code index is available
|
|
109
|
+
try {
|
|
110
|
+
const codeQuery = getCodeQuery()
|
|
111
|
+
if (codeQuery && project) {
|
|
112
|
+
const fileMap = codeQuery.getFileMap(project, 50)
|
|
113
|
+
if (fileMap.length > 0) {
|
|
114
|
+
const formatted = codeQuery.formatFileMap(fileMap)
|
|
115
|
+
parts.push('\n---\n## Code Structure\n')
|
|
116
|
+
parts.push(formatted)
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
} catch {
|
|
120
|
+
// Code intelligence not available
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// C8: Register with episode manager
|
|
124
|
+
registerEpisodeMessage(ctx, message, project, 'session_start')
|
|
125
|
+
|
|
126
|
+
// C9: Append proactive recommendations
|
|
127
|
+
try {
|
|
128
|
+
const recommendations = await ctx.searchEngine.getRecommendations(
|
|
129
|
+
entities.topic || message,
|
|
130
|
+
{ project, limit: 3 }
|
|
131
|
+
)
|
|
132
|
+
if (recommendations?.recommendations?.length) {
|
|
133
|
+
parts.push('\n---\n## Proactive Recommendations\n')
|
|
134
|
+
for (const rec of recommendations.recommendations) {
|
|
135
|
+
parts.push(`- **${rec.type}**: ${rec.content?.slice(0, 150) || ''}`)
|
|
136
|
+
if (rec.reasoning) parts.push(` _${rec.reasoning}_`)
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
} catch {
|
|
140
|
+
// Recommendations not available
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Phase 25: Query past session summaries for this project
|
|
144
|
+
try {
|
|
145
|
+
let sessionSummaries = await ctx.searchEngine.plainSearch(
|
|
146
|
+
'session summary',
|
|
147
|
+
{ project: project || undefined, limit: 5, minSimilarity: 0.3 }
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
// Fallback: if plainSearch returns 0, try enhancedSearch with broader query
|
|
151
|
+
if (sessionSummaries.length === 0) {
|
|
152
|
+
sessionSummaries = await ctx.searchEngine.enhancedSearch(
|
|
153
|
+
`session summary ${project || ''}`.trim(),
|
|
154
|
+
{ project: project || undefined, limit: 5, minSimilarity: 0.2 }
|
|
155
|
+
)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
let summaryItems = sessionSummaries.filter(r =>
|
|
159
|
+
(r.metadata as Record<string, unknown>)?.tags &&
|
|
160
|
+
Array.isArray((r.metadata as Record<string, unknown>).tags) &&
|
|
161
|
+
((r.metadata as Record<string, unknown>).tags as string[]).includes('session-summary') ||
|
|
162
|
+
r.content.toLowerCase().startsWith('session summary:') ||
|
|
163
|
+
r.content.toLowerCase().includes('worked on')
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
// Sort by date descending (most recent first)
|
|
167
|
+
summaryItems.sort((a, b) => {
|
|
168
|
+
const dateA = a.date || (a.metadata as Record<string, unknown>)?.created_at as string || ''
|
|
169
|
+
const dateB = b.date || (b.metadata as Record<string, unknown>)?.created_at as string || ''
|
|
170
|
+
return dateB.localeCompare(dateA)
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
if (summaryItems.length > 0) {
|
|
174
|
+
parts.push('\n---\n## Past Sessions\n')
|
|
175
|
+
for (const s of summaryItems.slice(0, 3)) {
|
|
176
|
+
const safeContent = typeof s.content === 'string' ? s.content : JSON.stringify(s.content ?? '')
|
|
177
|
+
parts.push(`- ${safeContent.slice(0, 300)}`)
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
} catch {
|
|
181
|
+
// Non-critical — skip if search fails
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const totalRecalled = phase12Result.recalledMemories?.memories.length || 0
|
|
185
|
+
return {
|
|
186
|
+
action: 'retrieved',
|
|
187
|
+
summary: `Session context for ${project}${totalRecalled ? ` (${totalRecalled} memories)` : ''}`,
|
|
188
|
+
content: parts.join('\n'),
|
|
189
|
+
relevantItems: totalRecalled
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export async function handleContextNeeded(
|
|
194
|
+
ctx: HandlerContext,
|
|
195
|
+
message: string,
|
|
196
|
+
project: string | undefined,
|
|
197
|
+
entities: BrainExtractedEntities,
|
|
198
|
+
classification?: ClassificationResult
|
|
199
|
+
): Promise<BrainResponse> {
|
|
200
|
+
if (!isServicesInitialized()) {
|
|
201
|
+
return servicesNotReady()
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Phase 25: Use undefined for search when no project detected (search all projects)
|
|
205
|
+
const searchProject = project || undefined
|
|
206
|
+
const displayProject = project || DEFAULT_PROJECT
|
|
207
|
+
const query = entities.topic || message
|
|
208
|
+
const tiers: TierResults[] = []
|
|
209
|
+
const hasTemporal = classification?.secondary.includes('exploration') ||
|
|
210
|
+
ctx.classifier.hasTemporalSignal(message.toLowerCase())
|
|
211
|
+
|
|
212
|
+
// Use temporal search if temporal signals detected
|
|
213
|
+
if (hasTemporal) {
|
|
214
|
+
const { results } = await ctx.searchEngine.temporalSearch(query, { project: searchProject, limit: 5 })
|
|
215
|
+
if (results.length > 0) {
|
|
216
|
+
tiers.push({
|
|
217
|
+
label: 'Memories',
|
|
218
|
+
results: results.map(r => ({
|
|
219
|
+
content: r.content,
|
|
220
|
+
score: r.score,
|
|
221
|
+
source: r.source === 'decision' ? 'Past Decision' : r.source,
|
|
222
|
+
metadata: r.metadata as Record<string, unknown>
|
|
223
|
+
}))
|
|
224
|
+
})
|
|
225
|
+
}
|
|
226
|
+
} else {
|
|
227
|
+
// Standard enhanced search
|
|
228
|
+
const searchResults = await ctx.searchEngine.enhancedSearch(query, {
|
|
229
|
+
project: searchProject,
|
|
230
|
+
limit: 5,
|
|
231
|
+
minSimilarity: 0.3
|
|
232
|
+
})
|
|
233
|
+
if (searchResults.length > 0) {
|
|
234
|
+
tiers.push({
|
|
235
|
+
label: 'Memories',
|
|
236
|
+
results: searchResults.map(r => ({
|
|
237
|
+
content: r.content,
|
|
238
|
+
score: r.score,
|
|
239
|
+
source: r.source === 'decision' ? 'Past Decision' : r.source,
|
|
240
|
+
metadata: r.metadata as Record<string, unknown>
|
|
241
|
+
}))
|
|
242
|
+
})
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Also search patterns
|
|
247
|
+
const patternResults = await ctx.searchEngine.searchPatterns(query, { project: searchProject, limit: 3 })
|
|
248
|
+
if (patternResults.length > 0) {
|
|
249
|
+
tiers.push({
|
|
250
|
+
label: 'Patterns',
|
|
251
|
+
results: patternResults.map(p => ({
|
|
252
|
+
content: p.content,
|
|
253
|
+
score: p.score,
|
|
254
|
+
source: `Pattern`,
|
|
255
|
+
metadata: p.metadata as Record<string, unknown>
|
|
256
|
+
}))
|
|
257
|
+
})
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Phase 32: Code intelligence — query code index for matching symbols/files
|
|
261
|
+
const codeTier = queryCodeIntelligence(ctx, query, searchProject)
|
|
262
|
+
if (codeTier) {
|
|
263
|
+
tiers.push(codeTier)
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const responseFilter = new ResponseFilter()
|
|
267
|
+
const response = responseFilter.synthesize(tiers, message, displayProject)
|
|
268
|
+
|
|
269
|
+
// P0: When no results found and message looks like a plain statement (not a question),
|
|
270
|
+
// add a hint that the user may have intended to store it
|
|
271
|
+
if (response.action === 'none' && response.relevantItems === 0) {
|
|
272
|
+
const isPlainStatement = message.length > 15 &&
|
|
273
|
+
!message.trim().endsWith('?') &&
|
|
274
|
+
message.split(/\s+/).length > 3
|
|
275
|
+
if (isPlainStatement) {
|
|
276
|
+
response.content += `\n\n**Tip:** If you meant to save this, try:\n- \`brain("Remember: ${message.slice(0, 60)}")\`\n- Or use \`action: "store"\` to force storage: \`brain({ message: "...", action: "store" })\``
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return response
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Phase 19 C7+C10+C11: Enhanced question handler
|
|
285
|
+
* - Uses SearchEngine for all searches (hybrid, cached)
|
|
286
|
+
* - Temporal search when temporal signals detected
|
|
287
|
+
* - ChainRetrieval for complex multi-part questions
|
|
288
|
+
* - CrossProject patterns for general/unspecified project
|
|
289
|
+
* - KnowledgeGraph enrichment
|
|
290
|
+
*/
|
|
291
|
+
export async function handleQuestion(
|
|
292
|
+
ctx: HandlerContext,
|
|
293
|
+
message: string,
|
|
294
|
+
project: string | undefined,
|
|
295
|
+
entities: BrainExtractedEntities,
|
|
296
|
+
classification: ClassificationResult
|
|
297
|
+
): Promise<BrainResponse> {
|
|
298
|
+
if (!isServicesInitialized()) {
|
|
299
|
+
return servicesNotReady()
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// BUG-002: Detect category-based queries and route to fetchAll
|
|
303
|
+
const categoryIntent = detectCategoryIntent(message)
|
|
304
|
+
if (categoryIntent) {
|
|
305
|
+
return handleCategoryQuery(message, project, categoryIntent)
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Phase 25: Use undefined for search (no project filter = search all) when no project detected
|
|
309
|
+
const searchProject = project || undefined
|
|
310
|
+
const displayProject = project || DEFAULT_PROJECT
|
|
311
|
+
const query = entities.topic || message
|
|
312
|
+
const tiers: TierResults[] = []
|
|
313
|
+
const hasTemporal = classification.secondary.includes('exploration') ||
|
|
314
|
+
ctx.classifier.hasTemporalSignal(message.toLowerCase())
|
|
315
|
+
|
|
316
|
+
// C7: Detect multi-part questions for chain retrieval
|
|
317
|
+
const complex = isComplexQuestion(message)
|
|
318
|
+
|
|
319
|
+
// Main search — temporal or standard
|
|
320
|
+
let searchResults: NormalizedResult[]
|
|
321
|
+
if (hasTemporal) {
|
|
322
|
+
const { results } = await ctx.searchEngine.temporalSearch(query, { project: searchProject, limit: 5 })
|
|
323
|
+
searchResults = results
|
|
324
|
+
} else if (complex) {
|
|
325
|
+
// C7: Try multi-hop chain retrieval
|
|
326
|
+
const chainResult = await ctx.searchEngine.chainSearch(query, { project: searchProject })
|
|
327
|
+
if (chainResult?.allResults?.length) {
|
|
328
|
+
searchResults = chainResult.allResults.map((r: Record<string, unknown>) => {
|
|
329
|
+
const meta = (r.metadata || {}) as Record<string, unknown>
|
|
330
|
+
return {
|
|
331
|
+
id: String(r.id || ''),
|
|
332
|
+
content: String(r.content || ''),
|
|
333
|
+
score: (r.similarity as number) || 0,
|
|
334
|
+
source: 'decision' as const,
|
|
335
|
+
project: String(meta.project || displayProject || ''),
|
|
336
|
+
date: String(meta.created_at || ''),
|
|
337
|
+
metadata: meta
|
|
338
|
+
}
|
|
339
|
+
})
|
|
340
|
+
} else {
|
|
341
|
+
searchResults = await ctx.searchEngine.enhancedSearch(query, { project: searchProject, limit: 5 })
|
|
342
|
+
}
|
|
343
|
+
} else {
|
|
344
|
+
searchResults = await ctx.searchEngine.enhancedSearch(query, { project: searchProject, limit: 5 })
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
if (searchResults.length > 0) {
|
|
348
|
+
tiers.push({
|
|
349
|
+
label: 'Memories',
|
|
350
|
+
results: searchResults.map(r => ({
|
|
351
|
+
content: r.content,
|
|
352
|
+
score: r.score,
|
|
353
|
+
source: r.source === 'decision' ? 'Past Decision' : r.source,
|
|
354
|
+
metadata: {
|
|
355
|
+
...(r.metadata as Record<string, unknown>),
|
|
356
|
+
id: r.id,
|
|
357
|
+
project: r.project,
|
|
358
|
+
created_at: r.date
|
|
359
|
+
}
|
|
360
|
+
}))
|
|
361
|
+
})
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// Phase 32: Code intelligence — synchronous SQLite query, runs alongside async searches
|
|
365
|
+
const codeTier = queryCodeIntelligence(ctx, query, searchProject)
|
|
366
|
+
if (codeTier) {
|
|
367
|
+
tiers.push(codeTier)
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// Parallel: patterns + corrections + graph
|
|
371
|
+
const [patternResults, correctionResults, graphResults] = await Promise.all([
|
|
372
|
+
ctx.searchEngine.searchPatterns(query, { project: searchProject, limit: 3 }),
|
|
373
|
+
ctx.searchEngine.searchCorrections(query, { project: searchProject, limit: 3 }),
|
|
374
|
+
// C11: KnowledgeGraph enrichment for all questions
|
|
375
|
+
ctx.searchEngine.searchGraph(query, 5)
|
|
376
|
+
])
|
|
377
|
+
|
|
378
|
+
if (patternResults.length > 0) {
|
|
379
|
+
tiers.push({
|
|
380
|
+
label: 'Patterns',
|
|
381
|
+
results: patternResults.map(p => ({
|
|
382
|
+
content: p.content,
|
|
383
|
+
score: p.score,
|
|
384
|
+
source: `Pattern`,
|
|
385
|
+
metadata: {
|
|
386
|
+
...(p.metadata as Record<string, unknown>),
|
|
387
|
+
id: p.id,
|
|
388
|
+
project: p.project,
|
|
389
|
+
created_at: p.date,
|
|
390
|
+
category: 'pattern'
|
|
391
|
+
}
|
|
392
|
+
}))
|
|
393
|
+
})
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
if (correctionResults.length > 0) {
|
|
397
|
+
tiers.push({
|
|
398
|
+
label: 'Corrections',
|
|
399
|
+
results: correctionResults.map(c => ({
|
|
400
|
+
content: c.content,
|
|
401
|
+
score: c.score,
|
|
402
|
+
source: 'Lesson Learned',
|
|
403
|
+
metadata: {
|
|
404
|
+
...(c.metadata as Record<string, unknown>),
|
|
405
|
+
id: c.id,
|
|
406
|
+
project: c.project,
|
|
407
|
+
created_at: c.date,
|
|
408
|
+
category: 'correction'
|
|
409
|
+
}
|
|
410
|
+
}))
|
|
411
|
+
})
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// C11: Add graph results as "Related Concepts"
|
|
415
|
+
// When a project filter is active, cap graph results at 0.45 so they can compete
|
|
416
|
+
// fairly with project-scoped ChromaDB results. Without a project filter, cap at 0.6.
|
|
417
|
+
// BUG-006 T4: Also require keyword overlap with query to prevent noise
|
|
418
|
+
const graphScoreCap = searchProject ? 0.45 : 0.6
|
|
419
|
+
const queryWordsForGraph = new Set(query.toLowerCase().split(/\s+/).filter(w => w.length > 2))
|
|
420
|
+
const relevantGraphResults = graphResults.filter(g => {
|
|
421
|
+
if (g.score < 0.6) return false
|
|
422
|
+
// Require at least one query keyword to appear in graph content
|
|
423
|
+
if (queryWordsForGraph.size > 0) {
|
|
424
|
+
const contentLower = g.content.toLowerCase()
|
|
425
|
+
const hasOverlap = [...queryWordsForGraph].some(w => contentLower.includes(w))
|
|
426
|
+
if (!hasOverlap) return false
|
|
427
|
+
}
|
|
428
|
+
return true
|
|
429
|
+
})
|
|
430
|
+
if (relevantGraphResults.length > 0) {
|
|
431
|
+
tiers.push({
|
|
432
|
+
label: 'Related Concepts',
|
|
433
|
+
results: relevantGraphResults.map(g => ({
|
|
434
|
+
content: g.content,
|
|
435
|
+
score: Math.min(g.score, graphScoreCap),
|
|
436
|
+
source: 'Knowledge Graph',
|
|
437
|
+
metadata: g.metadata as Record<string, unknown>
|
|
438
|
+
}))
|
|
439
|
+
})
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// C10: CrossProject patterns for general/unspecified project queries (Bug 2: cap to 0.3)
|
|
443
|
+
if (!project) {
|
|
444
|
+
try {
|
|
445
|
+
const crossProject = await ctx.searchEngine.findCrossProjectPatterns({
|
|
446
|
+
query,
|
|
447
|
+
limit: 3
|
|
448
|
+
})
|
|
449
|
+
if (crossProject?.patterns?.length) {
|
|
450
|
+
tiers.push({
|
|
451
|
+
label: 'Cross-Project Patterns',
|
|
452
|
+
results: crossProject.patterns.map((p: Record<string, unknown>) => ({
|
|
453
|
+
content: `${String(p.description || '')} (${Array.isArray(p.projects) ? p.projects.join(', ') : 'multiple projects'})`,
|
|
454
|
+
score: Math.min((p.confidence as number) || 0.5, 0.3),
|
|
455
|
+
source: 'Cross-Project',
|
|
456
|
+
metadata: {}
|
|
457
|
+
}))
|
|
458
|
+
})
|
|
459
|
+
}
|
|
460
|
+
} catch {
|
|
461
|
+
// Cross-project not available
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// Phase 23b Fix 5: Session recall — "what have we discussed" queries session tracker
|
|
466
|
+
if (isSessionRecallQuery(message)) {
|
|
467
|
+
try {
|
|
468
|
+
const tracker = getSessionTracker()
|
|
469
|
+
if (tracker) {
|
|
470
|
+
const stats = tracker.getStats()
|
|
471
|
+
if (stats.totalItems > 0) {
|
|
472
|
+
tiers.push({
|
|
473
|
+
label: 'Current Session',
|
|
474
|
+
results: [{
|
|
475
|
+
content: `Active sessions: ${stats.activeSessions}, items tracked: ${stats.totalItems}`,
|
|
476
|
+
score: 0.95,
|
|
477
|
+
source: 'Session Tracker',
|
|
478
|
+
metadata: {}
|
|
479
|
+
}]
|
|
480
|
+
})
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
} catch {
|
|
484
|
+
// Session tracker not available
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// Bug 4: Inject recently stored decisions using keyword overlap as score
|
|
489
|
+
const recentMatches = findRecentDecisions(ctx, query, searchProject)
|
|
490
|
+
// Only include results whose overlap score meets the hard floor (0.35)
|
|
491
|
+
const qualifiedRecent = recentMatches.filter(d => (d.overlapScore || 0) >= 0.35)
|
|
492
|
+
if (qualifiedRecent.length > 0) {
|
|
493
|
+
tiers.unshift({
|
|
494
|
+
label: 'Recent Decisions',
|
|
495
|
+
results: qualifiedRecent.map(d => ({
|
|
496
|
+
content: d.content,
|
|
497
|
+
score: d.overlapScore || 0,
|
|
498
|
+
source: 'Recent Decision',
|
|
499
|
+
metadata: { storedAt: d.storedAt, project: d.project }
|
|
500
|
+
}))
|
|
501
|
+
})
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// Register with episode
|
|
505
|
+
registerEpisodeMessage(ctx, message, displayProject, 'question')
|
|
506
|
+
|
|
507
|
+
// Phase 27: Use compact format for progressive disclosure
|
|
508
|
+
// Combine all tier results, filter, then format compactly
|
|
509
|
+
const allResults: FilterableResult[] = []
|
|
510
|
+
for (const tier of tiers) {
|
|
511
|
+
allResults.push(...tier.results)
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
if (allResults.length === 0) {
|
|
515
|
+
return {
|
|
516
|
+
action: 'none',
|
|
517
|
+
summary: 'No relevant information found',
|
|
518
|
+
content: `No results found for: "${message.slice(0, 100)}"`,
|
|
519
|
+
relevantItems: 0
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
const responseFilter = new ResponseFilter()
|
|
524
|
+
const filtered = responseFilter.filter(allResults, message, displayProject)
|
|
525
|
+
if (filtered.length === 0) {
|
|
526
|
+
return {
|
|
527
|
+
action: 'none',
|
|
528
|
+
summary: 'Results filtered out as noise or irrelevant',
|
|
529
|
+
content: `No relevant results after filtering for: "${message.slice(0, 100)}"`,
|
|
530
|
+
relevantItems: 0
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
const compactContent = formatCompactResponse(filtered, message)
|
|
535
|
+
return {
|
|
536
|
+
action: 'retrieved',
|
|
537
|
+
summary: `Found ${filtered.length} result${filtered.length === 1 ? '' : 's'}`,
|
|
538
|
+
content: compactContent,
|
|
539
|
+
relevantItems: filtered.length
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
/**
|
|
544
|
+
* Phase 27: Handle detail requests — "details obs_abc123", "expand <id>"
|
|
545
|
+
* Looks up a single observation by ID and returns full detail view.
|
|
546
|
+
*/
|
|
547
|
+
export async function handleDetailRequest(
|
|
548
|
+
_ctx: HandlerContext,
|
|
549
|
+
message: string,
|
|
550
|
+
_project: string | undefined,
|
|
551
|
+
_entities: BrainExtractedEntities
|
|
552
|
+
): Promise<BrainResponse> {
|
|
553
|
+
if (!isServicesInitialized()) {
|
|
554
|
+
return servicesNotReady()
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
// Extract ID from message using regex
|
|
558
|
+
const idMatch = message.match(/\b(obs_\w+|[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}|[a-f0-9]{8,})\b/i)
|
|
559
|
+
if (!idMatch) {
|
|
560
|
+
return {
|
|
561
|
+
action: 'none',
|
|
562
|
+
summary: 'No observation ID provided',
|
|
563
|
+
content: 'Please provide an observation ID. Use brain("details {ID}") with an ID from search results.',
|
|
564
|
+
relevantItems: 0
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
const id = idMatch[1]!
|
|
569
|
+
const observation = await lookupById(id)
|
|
570
|
+
|
|
571
|
+
if (!observation) {
|
|
572
|
+
return {
|
|
573
|
+
action: 'none',
|
|
574
|
+
summary: `No observation found: ${id}`,
|
|
575
|
+
content: `No observation found with ID: ${id}`,
|
|
576
|
+
relevantItems: 0
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
// Increment access count
|
|
581
|
+
incrementAccess(id)
|
|
582
|
+
|
|
583
|
+
return {
|
|
584
|
+
action: 'retrieved',
|
|
585
|
+
summary: `Details for ${id.slice(0, 12)}...`,
|
|
586
|
+
content: formatDetailResponse(observation),
|
|
587
|
+
relevantItems: 1
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
/**
|
|
592
|
+
* BUG-002: Handle category-scoped queries by fetching all items of that category.
|
|
593
|
+
*/
|
|
594
|
+
async function handleCategoryQuery(
|
|
595
|
+
_message: string,
|
|
596
|
+
project: string | undefined,
|
|
597
|
+
category: 'decision' | 'pattern' | 'correction'
|
|
598
|
+
): Promise<BrainResponse> {
|
|
599
|
+
const memory = getMemoryService()
|
|
600
|
+
const projectLabel = project || 'all projects'
|
|
601
|
+
|
|
602
|
+
let items: Record<string, unknown>[]
|
|
603
|
+
switch (category) {
|
|
604
|
+
case 'decision':
|
|
605
|
+
items = await memory.fetchAllDecisions(project) as Record<string, unknown>[]
|
|
606
|
+
break
|
|
607
|
+
case 'pattern':
|
|
608
|
+
items = await memory.fetchAllPatterns(project) as Record<string, unknown>[]
|
|
609
|
+
break
|
|
610
|
+
case 'correction':
|
|
611
|
+
items = await memory.fetchAllCorrections(project) as Record<string, unknown>[]
|
|
612
|
+
break
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
if (items.length === 0) {
|
|
616
|
+
return {
|
|
617
|
+
action: 'none',
|
|
618
|
+
summary: `No ${category}s found`,
|
|
619
|
+
content: `No ${category}s found for ${projectLabel}.`,
|
|
620
|
+
relevantItems: 0
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
// Format as compact items
|
|
625
|
+
const allItems = items.map(item => ({
|
|
626
|
+
id: item.id || item.decision_id,
|
|
627
|
+
content: typeof (item.decision || item.description || item.correction || item.original || item.document || item.content) === 'string'
|
|
628
|
+
? (item.decision || item.description || item.correction || item.original || item.document || item.content)
|
|
629
|
+
: JSON.stringify(item.decision || item.description || item.correction || item.original || item.document || item.content || ''),
|
|
630
|
+
category,
|
|
631
|
+
project: item.project || project || 'general',
|
|
632
|
+
created_at: item.created_at || item.date || '',
|
|
633
|
+
}))
|
|
634
|
+
|
|
635
|
+
const compactContent = formatCompactResponse(allItems, `${category}s for ${projectLabel}`)
|
|
636
|
+
return {
|
|
637
|
+
action: 'retrieved',
|
|
638
|
+
summary: `${items.length} ${category}s for ${projectLabel}`,
|
|
639
|
+
content: compactContent,
|
|
640
|
+
relevantItems: items.length
|
|
641
|
+
}
|
|
642
|
+
}
|