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,369 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Exploration Handler
|
|
3
|
+
* Task 3.1: Exploration intents — handleExploration, handleComparison,
|
|
4
|
+
* handleListAll, handleTimeline, handleCategoryQuery
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { HandlerContext, BrainExtractedEntities, BrainResponse, TierResults } from './types'
|
|
8
|
+
import { DEFAULT_PROJECT } from './types'
|
|
9
|
+
import { ResponseFilter, formatCompactResponse, formatTimeline, groupByDay } from '../response-filter'
|
|
10
|
+
import {
|
|
11
|
+
servicesNotReady,
|
|
12
|
+
queryCodeIntelligence,
|
|
13
|
+
fetchRecentObservations
|
|
14
|
+
} from './shared'
|
|
15
|
+
import { getMemoryService, isServicesInitialized } from '@/server/services'
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Phase 19 C6: Enhanced exploration handler
|
|
19
|
+
* - Timeline for "timeline"/"chronological" queries
|
|
20
|
+
* - DecisionEvolutionTracker for "how has X changed" queries
|
|
21
|
+
* - TrendDetector for "trends"/"emerging" queries
|
|
22
|
+
*/
|
|
23
|
+
export async function handleExploration(
|
|
24
|
+
ctx: HandlerContext,
|
|
25
|
+
message: string,
|
|
26
|
+
project: string | undefined,
|
|
27
|
+
entities: BrainExtractedEntities
|
|
28
|
+
): Promise<BrainResponse> {
|
|
29
|
+
if (!isServicesInitialized()) {
|
|
30
|
+
return servicesNotReady()
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Phase 25: Use undefined for search when no project detected
|
|
34
|
+
const searchProject = project || undefined
|
|
35
|
+
const displayProject = project || DEFAULT_PROJECT
|
|
36
|
+
const query = entities.topic || message
|
|
37
|
+
const lower = message.toLowerCase()
|
|
38
|
+
const tiers: TierResults[] = []
|
|
39
|
+
|
|
40
|
+
// C6: Timeline queries
|
|
41
|
+
if (lower.includes('timeline') || lower.includes('chronological') || lower.includes('history of')) {
|
|
42
|
+
const timeline = await ctx.searchEngine.buildTimeline({
|
|
43
|
+
project: searchProject,
|
|
44
|
+
topic: query,
|
|
45
|
+
limit: 20
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
if (timeline?.entries?.length) {
|
|
49
|
+
tiers.push({
|
|
50
|
+
label: 'Timeline',
|
|
51
|
+
results: timeline.entries.map((e: Record<string, unknown>) => ({
|
|
52
|
+
content: `**${String(e.date || '')}** — ${String(e.content || '')}`,
|
|
53
|
+
score: 0.8,
|
|
54
|
+
source: `Timeline (${String(e.type || 'event')})`,
|
|
55
|
+
metadata: (e.metadata || {}) as Record<string, unknown>
|
|
56
|
+
}))
|
|
57
|
+
})
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// C6: Evolution queries
|
|
62
|
+
if (lower.includes('evolution') || lower.includes('how has') || lower.includes('changed') || lower.includes('evolved')) {
|
|
63
|
+
const evolution = await ctx.searchEngine.analyzeEvolution(query, { project: searchProject })
|
|
64
|
+
if (evolution?.timeline?.length) {
|
|
65
|
+
const parts = []
|
|
66
|
+
parts.push(`**Stability:** ${evolution.stability || 'unknown'}`)
|
|
67
|
+
if (evolution.currentState) parts.push(`**Current:** ${evolution.currentState}`)
|
|
68
|
+
for (const change of (evolution.changes || []).slice(0, 5)) {
|
|
69
|
+
parts.push(`- ${change.description || change}`)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
tiers.push({
|
|
73
|
+
label: 'Decision Evolution',
|
|
74
|
+
results: [{
|
|
75
|
+
content: parts.join('\n'),
|
|
76
|
+
score: 0.8,
|
|
77
|
+
source: 'Evolution Analysis',
|
|
78
|
+
metadata: {}
|
|
79
|
+
}]
|
|
80
|
+
})
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// C6: Trend queries
|
|
85
|
+
if (lower.includes('trend') || lower.includes('emerging') || lower.includes('declining')) {
|
|
86
|
+
const trends = await ctx.searchEngine.detectTrends({ project: searchProject })
|
|
87
|
+
if (trends?.topTrends?.length) {
|
|
88
|
+
tiers.push({
|
|
89
|
+
label: 'Trends',
|
|
90
|
+
results: trends.topTrends.slice(0, 5).map((t: Record<string, unknown>) => ({
|
|
91
|
+
content: `**${String(t.term)}** — ${String(t.trend)} (${t.occurrences} occurrences, momentum: ${String(t.momentum || 'unknown')})`,
|
|
92
|
+
score: (t.occurrences as number) > 5 ? 0.9 : 0.7,
|
|
93
|
+
source: `Trend (${String(t.trend)})`,
|
|
94
|
+
metadata: {}
|
|
95
|
+
}))
|
|
96
|
+
})
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Always include graph exploration — cap scores when project filter is active
|
|
101
|
+
// BUG-006 T4: Also require keyword overlap with query to prevent noise
|
|
102
|
+
const explorationGraphCap = searchProject ? 0.45 : 0.6
|
|
103
|
+
const explorationQueryWords = new Set(query.toLowerCase().split(/\s+/).filter(w => w.length > 2))
|
|
104
|
+
const graphResults = await ctx.searchEngine.searchGraph(query, 10)
|
|
105
|
+
const filteredGraphResults = graphResults.filter(g => {
|
|
106
|
+
if (explorationQueryWords.size > 0) {
|
|
107
|
+
const contentLower = g.content.toLowerCase()
|
|
108
|
+
return [...explorationQueryWords].some(w => contentLower.includes(w))
|
|
109
|
+
}
|
|
110
|
+
return true
|
|
111
|
+
})
|
|
112
|
+
if (filteredGraphResults.length > 0) {
|
|
113
|
+
tiers.push({
|
|
114
|
+
label: 'Knowledge Graph',
|
|
115
|
+
results: filteredGraphResults.map(g => ({
|
|
116
|
+
content: g.content,
|
|
117
|
+
score: Math.min(g.score, explorationGraphCap),
|
|
118
|
+
source: 'Knowledge Graph',
|
|
119
|
+
metadata: g.metadata as Record<string, unknown>
|
|
120
|
+
}))
|
|
121
|
+
})
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Issue 9: Cross-project patterns for exploration queries
|
|
125
|
+
if (!project) {
|
|
126
|
+
try {
|
|
127
|
+
const crossProject = await ctx.searchEngine.findCrossProjectPatterns({
|
|
128
|
+
query,
|
|
129
|
+
limit: 3
|
|
130
|
+
})
|
|
131
|
+
if (crossProject?.patterns?.length) {
|
|
132
|
+
tiers.push({
|
|
133
|
+
label: 'Cross-Project Patterns',
|
|
134
|
+
results: crossProject.patterns.map((p: Record<string, unknown>) => ({
|
|
135
|
+
content: `${String(p.description || '')} (${Array.isArray(p.projects) ? p.projects.join(', ') : 'multiple projects'})`,
|
|
136
|
+
score: (p.confidence as number) || 0.5,
|
|
137
|
+
source: 'Cross-Project',
|
|
138
|
+
metadata: {}
|
|
139
|
+
}))
|
|
140
|
+
})
|
|
141
|
+
}
|
|
142
|
+
} catch {
|
|
143
|
+
// Cross-project not available
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Also do a basic memory search as fallback
|
|
148
|
+
const searchResults = await ctx.searchEngine.enhancedSearch(query, {
|
|
149
|
+
project: searchProject,
|
|
150
|
+
limit: 5,
|
|
151
|
+
minSimilarity: 0.2
|
|
152
|
+
})
|
|
153
|
+
if (searchResults.length > 0) {
|
|
154
|
+
tiers.push({
|
|
155
|
+
label: 'Memories',
|
|
156
|
+
results: searchResults.map(r => ({
|
|
157
|
+
content: r.content,
|
|
158
|
+
score: r.score,
|
|
159
|
+
source: r.source === 'decision' ? 'Past Decision' : r.source,
|
|
160
|
+
metadata: r.metadata as Record<string, unknown>
|
|
161
|
+
}))
|
|
162
|
+
})
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Phase 32: Code intelligence for exploration queries
|
|
166
|
+
const codeTier = queryCodeIntelligence(ctx, query, searchProject)
|
|
167
|
+
if (codeTier) {
|
|
168
|
+
tiers.push(codeTier)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const responseFilter = new ResponseFilter()
|
|
172
|
+
return responseFilter.synthesize(tiers, message, displayProject, 'analyzed')
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export async function handleComparison(
|
|
176
|
+
ctx: HandlerContext,
|
|
177
|
+
message: string,
|
|
178
|
+
project: string | undefined,
|
|
179
|
+
entities: BrainExtractedEntities
|
|
180
|
+
): Promise<BrainResponse> {
|
|
181
|
+
if (!isServicesInitialized()) {
|
|
182
|
+
return servicesNotReady()
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Phase 25: Use undefined for search when no project detected
|
|
186
|
+
const searchProject = project || undefined
|
|
187
|
+
const displayProject = project || DEFAULT_PROJECT
|
|
188
|
+
const query = entities.topic || message
|
|
189
|
+
const tiers: TierResults[] = []
|
|
190
|
+
|
|
191
|
+
// Search for related decisions
|
|
192
|
+
const searchResults = await ctx.searchEngine.enhancedSearch(query, {
|
|
193
|
+
project: searchProject,
|
|
194
|
+
limit: 5,
|
|
195
|
+
minSimilarity: 0.2
|
|
196
|
+
})
|
|
197
|
+
if (searchResults.length > 0) {
|
|
198
|
+
tiers.push({
|
|
199
|
+
label: 'Related Decisions',
|
|
200
|
+
results: searchResults.map(r => ({
|
|
201
|
+
content: r.content,
|
|
202
|
+
score: r.score,
|
|
203
|
+
source: 'Past Decision',
|
|
204
|
+
metadata: r.metadata as Record<string, unknown>
|
|
205
|
+
}))
|
|
206
|
+
})
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Graph search for comparison context
|
|
210
|
+
const graphResults = await ctx.searchEngine.searchGraph(query, 5)
|
|
211
|
+
if (graphResults.length > 0) {
|
|
212
|
+
tiers.push({
|
|
213
|
+
label: 'Knowledge Graph',
|
|
214
|
+
results: graphResults.map(g => ({
|
|
215
|
+
content: g.content,
|
|
216
|
+
score: g.score,
|
|
217
|
+
source: 'Knowledge Graph',
|
|
218
|
+
metadata: g.metadata as Record<string, unknown>
|
|
219
|
+
}))
|
|
220
|
+
})
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const responseFilter = new ResponseFilter()
|
|
224
|
+
return responseFilter.synthesize(tiers, message, displayProject, 'analyzed')
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* List all decisions for a project
|
|
229
|
+
*/
|
|
230
|
+
export async function handleListAll(
|
|
231
|
+
_ctx: HandlerContext,
|
|
232
|
+
_message: string,
|
|
233
|
+
project: string | undefined,
|
|
234
|
+
_entities: BrainExtractedEntities
|
|
235
|
+
): Promise<BrainResponse> {
|
|
236
|
+
if (!isServicesInitialized()) {
|
|
237
|
+
return servicesNotReady()
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const memory = getMemoryService()
|
|
241
|
+
const effectiveProject = project
|
|
242
|
+
|
|
243
|
+
try {
|
|
244
|
+
const decisions = await memory.fetchAllDecisions(effectiveProject)
|
|
245
|
+
const patterns = await memory.fetchAllPatterns(effectiveProject)
|
|
246
|
+
const corrections = await memory.fetchAllCorrections(effectiveProject)
|
|
247
|
+
|
|
248
|
+
const projectLabel = effectiveProject || 'all projects'
|
|
249
|
+
const total = decisions.length + patterns.length + corrections.length
|
|
250
|
+
|
|
251
|
+
if (total === 0) {
|
|
252
|
+
return {
|
|
253
|
+
action: 'none',
|
|
254
|
+
summary: `No memories found for ${projectLabel}`,
|
|
255
|
+
content: `No decisions, patterns, or corrections stored yet for ${projectLabel}.`,
|
|
256
|
+
relevantItems: 0
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Phase 27: Use compact format for list_all
|
|
261
|
+
const allItems: Record<string, unknown>[] = [
|
|
262
|
+
...decisions.map((d: Record<string, unknown>) => ({
|
|
263
|
+
id: d.id || d.decision_id,
|
|
264
|
+
content: typeof (d.decision || d.document || d.content) === 'string'
|
|
265
|
+
? (d.decision || d.document || d.content)
|
|
266
|
+
: JSON.stringify(d.decision || d.document || d.content || ''),
|
|
267
|
+
category: 'decision',
|
|
268
|
+
project: d.project || effectiveProject || 'general',
|
|
269
|
+
created_at: d.created_at || d.date || '',
|
|
270
|
+
})),
|
|
271
|
+
...patterns.map((p: Record<string, unknown>) => ({
|
|
272
|
+
id: p.id,
|
|
273
|
+
content: typeof (p.description || p.document || p.content) === 'string'
|
|
274
|
+
? (p.description || p.document || p.content)
|
|
275
|
+
: JSON.stringify(p.description || p.document || p.content || ''),
|
|
276
|
+
category: p.pattern_type || 'pattern',
|
|
277
|
+
project: p.project || effectiveProject || 'general',
|
|
278
|
+
created_at: p.created_at || '',
|
|
279
|
+
})),
|
|
280
|
+
...corrections.map((c: Record<string, unknown>) => ({
|
|
281
|
+
id: c.id,
|
|
282
|
+
content: typeof (c.original || c.document || c.content) === 'string'
|
|
283
|
+
? (c.original || c.document || c.content)
|
|
284
|
+
: JSON.stringify(c.original || c.document || c.content || ''),
|
|
285
|
+
category: 'correction',
|
|
286
|
+
project: c.project || effectiveProject || 'general',
|
|
287
|
+
created_at: c.created_at || '',
|
|
288
|
+
})),
|
|
289
|
+
]
|
|
290
|
+
|
|
291
|
+
const compactContent = formatCompactResponse(allItems, `all memories for ${projectLabel}`)
|
|
292
|
+
return {
|
|
293
|
+
action: 'retrieved',
|
|
294
|
+
summary: `${total} memories for ${projectLabel}`,
|
|
295
|
+
content: compactContent,
|
|
296
|
+
relevantItems: total
|
|
297
|
+
}
|
|
298
|
+
} catch (error) {
|
|
299
|
+
return {
|
|
300
|
+
action: 'none',
|
|
301
|
+
summary: 'Error listing memories',
|
|
302
|
+
content: `Failed to list memories: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
303
|
+
relevantItems: 0
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Phase 27: Handle timeline requests — "timeline for project", "recent activity"
|
|
310
|
+
* Fetches observations in a time range and groups by day.
|
|
311
|
+
*/
|
|
312
|
+
export async function handleTimeline(
|
|
313
|
+
_ctx: HandlerContext,
|
|
314
|
+
message: string,
|
|
315
|
+
project: string | undefined,
|
|
316
|
+
entities: BrainExtractedEntities
|
|
317
|
+
): Promise<BrainResponse> {
|
|
318
|
+
if (!isServicesInitialized()) {
|
|
319
|
+
return servicesNotReady()
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const effectiveProject = entities.project || project
|
|
323
|
+
const displayProject = effectiveProject || DEFAULT_PROJECT
|
|
324
|
+
|
|
325
|
+
// Parse time range from message (default: last 7 days)
|
|
326
|
+
const now = new Date()
|
|
327
|
+
let start: Date
|
|
328
|
+
let daysBack = 7
|
|
329
|
+
const lower = message.toLowerCase()
|
|
330
|
+
if (lower.includes('yesterday')) {
|
|
331
|
+
daysBack = 1
|
|
332
|
+
start = new Date(now.getTime() - 1 * 24 * 60 * 60 * 1000)
|
|
333
|
+
} else if (lower.includes('today')) {
|
|
334
|
+
daysBack = 0
|
|
335
|
+
// Start of today, not "now"
|
|
336
|
+
start = new Date(now.getFullYear(), now.getMonth(), now.getDate())
|
|
337
|
+
} else if (lower.includes('last month') || lower.includes('past month')) {
|
|
338
|
+
daysBack = 30
|
|
339
|
+
start = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000)
|
|
340
|
+
} else if (lower.includes('last week') || lower.includes('past week')) {
|
|
341
|
+
daysBack = 7
|
|
342
|
+
start = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000)
|
|
343
|
+
} else if (lower.includes('last 3 days') || lower.includes('past 3 days')) {
|
|
344
|
+
daysBack = 3
|
|
345
|
+
start = new Date(now.getTime() - 3 * 24 * 60 * 60 * 1000)
|
|
346
|
+
} else {
|
|
347
|
+
start = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000)
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const observations = await fetchRecentObservations(effectiveProject, start, now)
|
|
351
|
+
if (observations.length === 0) {
|
|
352
|
+
return {
|
|
353
|
+
action: 'none',
|
|
354
|
+
summary: `No activity found for ${displayProject}`,
|
|
355
|
+
content: `No observations found in the last ${daysBack === 0 ? 'day' : `${daysBack} day(s)`} for ${displayProject}.`,
|
|
356
|
+
relevantItems: 0
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const grouped = groupByDay(observations)
|
|
361
|
+
const timelineContent = formatTimeline(grouped, displayProject)
|
|
362
|
+
|
|
363
|
+
return {
|
|
364
|
+
action: 'retrieved',
|
|
365
|
+
summary: `Timeline for ${displayProject} (${observations.length} items)`,
|
|
366
|
+
content: timelineContent,
|
|
367
|
+
relevantItems: observations.length
|
|
368
|
+
}
|
|
369
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Handler Index
|
|
3
|
+
* Task 3.1: Re-exports all domain handler functions and shared types
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export type { HandlerContext, RecentDecision, IntentHandler } from './types'
|
|
7
|
+
export { DEFAULT_PROJECT, DELETE_MIN_SIMILARITY, UPDATE_MIN_SIMILARITY, RECENT_DECISION_WINDOW_MS } from './types'
|
|
8
|
+
|
|
9
|
+
// Memory (store) handlers
|
|
10
|
+
export { handleStoreThis, handleDecisionMade, handlePatternFound, handleMistakeLearned, handleProgressUpdate } from './memory-handler'
|
|
11
|
+
|
|
12
|
+
// Recall handlers
|
|
13
|
+
export { handleSessionStart, handleContextNeeded, handleQuestion, handleDetailRequest } from './recall-handler'
|
|
14
|
+
|
|
15
|
+
// Mutation handlers
|
|
16
|
+
export { handleUpdateMemory, handleDeleteMemory } from './mutation-handler'
|
|
17
|
+
|
|
18
|
+
// Exploration handlers
|
|
19
|
+
export { handleExploration, handleComparison, handleListAll, handleTimeline } from './exploration-handler'
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memory Handler
|
|
3
|
+
* Task 3.1: Store intents — handleStoreThis, handleDecisionMade,
|
|
4
|
+
* handlePatternFound, handleMistakeLearned, handleProgressUpdate
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { HandlerContext, BrainExtractedEntities, BrainResponse } from './types'
|
|
8
|
+
import { DEFAULT_PROJECT } from './types'
|
|
9
|
+
import {
|
|
10
|
+
servicesNotReady,
|
|
11
|
+
writeToVault,
|
|
12
|
+
linkToActiveEpisode,
|
|
13
|
+
linkToCodeFiles,
|
|
14
|
+
registerEpisodeMessage,
|
|
15
|
+
generateTaskId,
|
|
16
|
+
maybeCompress
|
|
17
|
+
} from './shared'
|
|
18
|
+
import {
|
|
19
|
+
getMemoryService,
|
|
20
|
+
getContextService,
|
|
21
|
+
isServicesInitialized
|
|
22
|
+
} from '@/server/services'
|
|
23
|
+
import { timed } from '@/utils/timing'
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Handle explicit "store this" requests — always uses the FULL message as the decision text.
|
|
27
|
+
*/
|
|
28
|
+
export async function handleStoreThis(
|
|
29
|
+
ctx: HandlerContext,
|
|
30
|
+
message: string,
|
|
31
|
+
project: string | undefined,
|
|
32
|
+
entities: BrainExtractedEntities
|
|
33
|
+
): Promise<BrainResponse> {
|
|
34
|
+
const effectiveProject = project || DEFAULT_PROJECT
|
|
35
|
+
|
|
36
|
+
if (!isServicesInitialized()) {
|
|
37
|
+
return servicesNotReady()
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const memory = getMemoryService()
|
|
41
|
+
const reasoning = entities.reasoning || ''
|
|
42
|
+
const context = entities.topic || message.slice(0, 200)
|
|
43
|
+
|
|
44
|
+
// Phase 30: Optional LLM compression
|
|
45
|
+
const { content: decision, rawContent } = await maybeCompress(ctx, message, 'store')
|
|
46
|
+
|
|
47
|
+
const decisionId = await timed('brain:store', () => memory.rememberDecision(
|
|
48
|
+
effectiveProject,
|
|
49
|
+
context,
|
|
50
|
+
decision,
|
|
51
|
+
reasoning,
|
|
52
|
+
{
|
|
53
|
+
alternatives: entities.alternatives,
|
|
54
|
+
tags: entities.technologies.length > 0 ? entities.technologies : ['user-stored']
|
|
55
|
+
}
|
|
56
|
+
))
|
|
57
|
+
|
|
58
|
+
// Track for potential update/delete
|
|
59
|
+
ctx.setLastStored(decisionId, effectiveProject)
|
|
60
|
+
|
|
61
|
+
// Bug 4: Track for recency fast path
|
|
62
|
+
ctx.trackRecentDecision(decision, effectiveProject)
|
|
63
|
+
|
|
64
|
+
// Invalidate cache for this project
|
|
65
|
+
ctx.searchEngine.invalidateCache(effectiveProject)
|
|
66
|
+
|
|
67
|
+
// Background: vault write + episode linking + code linkage (non-critical)
|
|
68
|
+
setImmediate(() => {
|
|
69
|
+
writeToVault(effectiveProject, decision, reasoning, context, entities.alternatives)
|
|
70
|
+
linkToActiveEpisode(effectiveProject, decisionId, 'decision')
|
|
71
|
+
linkToCodeFiles(ctx, decisionId, decision, effectiveProject)
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
const compressedNote = rawContent ? '\n*(compressed)*' : ''
|
|
75
|
+
return {
|
|
76
|
+
action: 'stored',
|
|
77
|
+
summary: `Stored: ${message.slice(0, 60)}`,
|
|
78
|
+
content: `Memory stored (ID: ${decisionId})\n\n**Project:** ${effectiveProject}\n**Content:** ${decision}${reasoning ? `\n**Reasoning:** ${reasoning}` : ''}${compressedNote}`,
|
|
79
|
+
relevantItems: 1
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export async function handleDecisionMade(
|
|
84
|
+
ctx: HandlerContext,
|
|
85
|
+
message: string,
|
|
86
|
+
project: string | undefined,
|
|
87
|
+
entities: BrainExtractedEntities
|
|
88
|
+
): Promise<BrainResponse> {
|
|
89
|
+
const effectiveProject = project || DEFAULT_PROJECT
|
|
90
|
+
|
|
91
|
+
if (!isServicesInitialized()) {
|
|
92
|
+
return servicesNotReady()
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const memory = getMemoryService()
|
|
96
|
+
const reasoning = entities.reasoning || ''
|
|
97
|
+
const context = entities.topic || message.slice(0, 200)
|
|
98
|
+
const alternatives = entities.alternatives
|
|
99
|
+
|
|
100
|
+
// Phase 30: Optional LLM compression
|
|
101
|
+
const { content: decision, rawContent } = await maybeCompress(ctx, message, 'decision')
|
|
102
|
+
|
|
103
|
+
const decisionId = await timed('brain:store', () => memory.rememberDecision(
|
|
104
|
+
effectiveProject,
|
|
105
|
+
context,
|
|
106
|
+
decision,
|
|
107
|
+
reasoning,
|
|
108
|
+
{
|
|
109
|
+
alternatives,
|
|
110
|
+
tags: entities.technologies.length > 0 ? entities.technologies : ['auto-detected']
|
|
111
|
+
}
|
|
112
|
+
))
|
|
113
|
+
|
|
114
|
+
ctx.setLastStored(decisionId, effectiveProject)
|
|
115
|
+
|
|
116
|
+
// Bug 4: Track for recency fast path
|
|
117
|
+
ctx.trackRecentDecision(decision, effectiveProject)
|
|
118
|
+
|
|
119
|
+
// Invalidate cache
|
|
120
|
+
ctx.searchEngine.invalidateCache(effectiveProject)
|
|
121
|
+
|
|
122
|
+
// Background: vault write + episode linking + code linkage (non-critical)
|
|
123
|
+
setImmediate(() => {
|
|
124
|
+
writeToVault(effectiveProject, decision, reasoning, context, alternatives)
|
|
125
|
+
linkToActiveEpisode(effectiveProject, decisionId, 'decision')
|
|
126
|
+
linkToCodeFiles(ctx, decisionId, decision, effectiveProject)
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
const compressedNote = rawContent ? '\n*(compressed)*' : ''
|
|
130
|
+
return {
|
|
131
|
+
action: 'stored',
|
|
132
|
+
summary: `Stored decision: ${message.slice(0, 60)}`,
|
|
133
|
+
content: `Decision stored (ID: ${decisionId})\n\n**Project:** ${effectiveProject}\n**Decision:** ${decision}\n**Reasoning:** ${reasoning}${compressedNote}`,
|
|
134
|
+
relevantItems: 1
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export async function handlePatternFound(
|
|
139
|
+
ctx: HandlerContext,
|
|
140
|
+
message: string,
|
|
141
|
+
project: string | undefined,
|
|
142
|
+
entities: BrainExtractedEntities
|
|
143
|
+
): Promise<BrainResponse> {
|
|
144
|
+
const effectiveProject = project || DEFAULT_PROJECT
|
|
145
|
+
|
|
146
|
+
if (!isServicesInitialized()) {
|
|
147
|
+
return servicesNotReady()
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const memory = getMemoryService()
|
|
151
|
+
const patternType = entities.patternType || 'solution'
|
|
152
|
+
const description = message
|
|
153
|
+
|
|
154
|
+
const patternId = await memory.storePattern({
|
|
155
|
+
project: effectiveProject,
|
|
156
|
+
pattern_type: patternType,
|
|
157
|
+
description,
|
|
158
|
+
confidence: 0.8,
|
|
159
|
+
context: message.slice(0, 300)
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
// Invalidate cache
|
|
163
|
+
ctx.searchEngine.invalidateCache(effectiveProject)
|
|
164
|
+
|
|
165
|
+
// Link to active episode + code files
|
|
166
|
+
linkToActiveEpisode(effectiveProject, patternId, 'pattern')
|
|
167
|
+
linkToCodeFiles(ctx, patternId, description, effectiveProject)
|
|
168
|
+
|
|
169
|
+
return {
|
|
170
|
+
action: 'stored',
|
|
171
|
+
summary: `Stored ${patternType}: ${description.slice(0, 60)}`,
|
|
172
|
+
content: `Pattern stored (ID: ${patternId})\n\n**Type:** ${patternType}\n**Project:** ${effectiveProject}\n**Description:** ${description}`,
|
|
173
|
+
relevantItems: 1
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export async function handleMistakeLearned(
|
|
178
|
+
ctx: HandlerContext,
|
|
179
|
+
message: string,
|
|
180
|
+
project: string | undefined,
|
|
181
|
+
entities: BrainExtractedEntities
|
|
182
|
+
): Promise<BrainResponse> {
|
|
183
|
+
const effectiveProject = project || DEFAULT_PROJECT
|
|
184
|
+
|
|
185
|
+
if (!isServicesInitialized()) {
|
|
186
|
+
return servicesNotReady()
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const memory = getMemoryService()
|
|
190
|
+
const original = entities.original || message
|
|
191
|
+
const correction = entities.correction || ''
|
|
192
|
+
const reasoning = entities.reasoning || 'Lesson learned from experience'
|
|
193
|
+
|
|
194
|
+
const correctionId = await memory.storeCorrection({
|
|
195
|
+
project: effectiveProject,
|
|
196
|
+
original,
|
|
197
|
+
correction: correction || message,
|
|
198
|
+
reasoning,
|
|
199
|
+
context: entities.topic || '',
|
|
200
|
+
confidence: 0.9
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
// Invalidate cache
|
|
204
|
+
ctx.searchEngine.invalidateCache(effectiveProject)
|
|
205
|
+
|
|
206
|
+
// Link to active episode + code files
|
|
207
|
+
linkToActiveEpisode(effectiveProject, correctionId, 'correction')
|
|
208
|
+
linkToCodeFiles(ctx, correctionId, original, effectiveProject)
|
|
209
|
+
|
|
210
|
+
return {
|
|
211
|
+
action: 'stored',
|
|
212
|
+
summary: `Stored correction: ${original.slice(0, 60)}`,
|
|
213
|
+
content: `Correction stored (ID: ${correctionId})\n\n**Project:** ${effectiveProject}\n**Original:** ${original}\n**Correction:** ${correction || '(see original message)'}`,
|
|
214
|
+
relevantItems: 1
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export async function handleProgressUpdate(
|
|
219
|
+
ctx: HandlerContext,
|
|
220
|
+
message: string,
|
|
221
|
+
project: string | undefined,
|
|
222
|
+
entities: BrainExtractedEntities
|
|
223
|
+
): Promise<BrainResponse> {
|
|
224
|
+
const effectiveProject = project || DEFAULT_PROJECT
|
|
225
|
+
|
|
226
|
+
if (!isServicesInitialized()) {
|
|
227
|
+
return servicesNotReady()
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const contextService = getContextService()
|
|
231
|
+
const memory = getMemoryService()
|
|
232
|
+
|
|
233
|
+
const completedTask = entities.completedTask || message
|
|
234
|
+
const nextSteps = entities.nextSteps || 'Continue development'
|
|
235
|
+
|
|
236
|
+
// Store in session context
|
|
237
|
+
try {
|
|
238
|
+
await contextService.progress.addCompletedTask(effectiveProject, {
|
|
239
|
+
id: generateTaskId(completedTask),
|
|
240
|
+
title: completedTask,
|
|
241
|
+
status: 'done',
|
|
242
|
+
completedAt: new Date()
|
|
243
|
+
})
|
|
244
|
+
} catch {
|
|
245
|
+
// Progress update failed
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Also store as a searchable memory
|
|
249
|
+
try {
|
|
250
|
+
await memory.rememberDecision(
|
|
251
|
+
effectiveProject,
|
|
252
|
+
`Progress update`,
|
|
253
|
+
`Progress: ${completedTask}${nextSteps !== 'Continue development' ? `. Next: ${nextSteps}` : ''}`,
|
|
254
|
+
'Progress tracking',
|
|
255
|
+
{ tags: ['progress', ...entities.technologies] }
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
// Invalidate cache
|
|
259
|
+
ctx.searchEngine.invalidateCache(effectiveProject)
|
|
260
|
+
} catch {
|
|
261
|
+
// Memory storage failed
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Register with episode
|
|
265
|
+
registerEpisodeMessage(ctx, message, effectiveProject, 'progress_update')
|
|
266
|
+
|
|
267
|
+
return {
|
|
268
|
+
action: 'stored',
|
|
269
|
+
summary: `Progress: ${completedTask.slice(0, 60)}`,
|
|
270
|
+
content: `Progress updated for ${effectiveProject}\n\n**Completed:** ${completedTask}\n**Next:** ${nextSteps}`,
|
|
271
|
+
relevantItems: 1
|
|
272
|
+
}
|
|
273
|
+
}
|