claude-brain 0.15.2 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +191 -191
- package/VERSION +1 -1
- package/assets/CLAUDE-unified.md +11 -11
- package/assets/CLAUDE.md +29 -11
- package/bunfig.toml +8 -8
- package/package.json +82 -82
- 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 +341 -341
- package/src/automation/auto-context.ts +240 -240
- package/src/automation/decision-detector.ts +452 -452
- package/src/automation/index.ts +11 -11
- 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 +205 -205
- package/src/cli/auto-setup.ts +82 -82
- package/src/cli/bin.ts +209 -202
- package/src/cli/commands/chroma.ts +573 -573
- package/src/cli/commands/git-hook.ts +189 -189
- package/src/cli/commands/hooks.ts +213 -213
- package/src/cli/commands/init.ts +122 -122
- package/src/cli/commands/install-mcp.ts +92 -92
- package/src/cli/commands/pack.ts +197 -197
- package/src/cli/commands/refresh.ts +323 -0
- package/src/cli/commands/serve.ts +167 -173
- package/src/cli/commands/start.ts +42 -42
- package/src/cli/commands/uninstall-mcp.ts +41 -41
- package/src/cli/commands/update.ts +124 -121
- package/src/cli/diagnose.ts +4 -4
- package/src/cli/health-check.ts +4 -4
- package/src/cli/migrate-chroma.ts +106 -106
- package/src/cli/setup.ts +4 -4
- 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/config/defaults.ts +50 -50
- package/src/config/home.ts +55 -55
- package/src/config/index.ts +7 -7
- package/src/config/loader.ts +166 -166
- package/src/config/migration.ts +76 -76
- package/src/config/schema.ts +360 -360
- package/src/config/validator.ts +184 -184
- package/src/config/watcher.ts +86 -86
- package/src/context/assembler.ts +398 -398
- package/src/context/cache-manager.ts +101 -101
- package/src/context/formatter.ts +84 -84
- package/src/context/hierarchy.ts +85 -85
- package/src/context/index.ts +83 -83
- package/src/context/progress-tracker.ts +174 -174
- package/src/context/standards-manager.ts +287 -287
- package/src/context/types.ts +252 -252
- package/src/context/validator.ts +58 -58
- package/src/diagnostics/index.ts +123 -123
- package/src/health/index.ts +229 -229
- package/src/hooks/brain-hook.ts +128 -112
- package/src/hooks/capture.ts +168 -205
- package/src/hooks/deduplicator.ts +72 -72
- package/src/hooks/git-capture.ts +109 -109
- package/src/hooks/git-hook-installer.ts +207 -207
- package/src/hooks/index.ts +20 -20
- package/src/hooks/installer.ts +194 -194
- package/src/hooks/passive-classifier.ts +404 -723
- package/src/hooks/queue.ts +129 -129
- package/src/hooks/session-tracker.ts +312 -275
- package/src/hooks/types.ts +47 -47
- package/src/index.ts +7 -7
- package/src/intelligence/cross-project/affinity.ts +162 -162
- package/src/intelligence/cross-project/generalizer.ts +283 -283
- package/src/intelligence/cross-project/index.ts +13 -13
- package/src/intelligence/cross-project/transfer.ts +201 -201
- package/src/intelligence/index.ts +24 -24
- package/src/intelligence/optimization/index.ts +10 -10
- package/src/intelligence/optimization/precompute.ts +202 -202
- package/src/intelligence/optimization/semantic-cache.ts +207 -207
- package/src/intelligence/prediction/context-anticipator.ts +198 -198
- package/src/intelligence/prediction/decision-predictor.ts +184 -184
- package/src/intelligence/prediction/index.ts +13 -13
- package/src/intelligence/prediction/recommender.ts +268 -268
- package/src/intelligence/reasoning/chain-retrieval.ts +247 -247
- package/src/intelligence/reasoning/counterfactual.ts +248 -248
- package/src/intelligence/reasoning/index.ts +13 -13
- package/src/intelligence/reasoning/synthesizer.ts +169 -169
- package/src/intelligence/temporal/evolution.ts +197 -197
- package/src/intelligence/temporal/index.ts +16 -16
- package/src/intelligence/temporal/query-processor.ts +190 -190
- package/src/intelligence/temporal/timeline.ts +259 -259
- package/src/intelligence/temporal/trends.ts +263 -263
- package/src/knowledge/entity-extractor.ts +416 -416
- 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 +168 -168
- package/src/knowledge/relationship-extractor.ts +108 -108
- package/src/memory/chroma/client.ts +174 -174
- package/src/memory/chroma/collection-manager.ts +94 -94
- package/src/memory/chroma/config.ts +57 -57
- package/src/memory/chroma/embeddings.ts +155 -155
- 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 +315 -315
- package/src/memory/chroma/store.ts +741 -741
- package/src/memory/consolidation/archiver.ts +164 -164
- package/src/memory/consolidation/merger.ts +186 -186
- package/src/memory/consolidation/scorer.ts +138 -138
- package/src/memory/context-builder.ts +236 -236
- package/src/memory/database.ts +169 -169
- package/src/memory/embedding-utils.ts +156 -156
- package/src/memory/embeddings.ts +226 -226
- package/src/memory/episodic/detector.ts +108 -108
- package/src/memory/episodic/manager.ts +351 -351
- package/src/memory/episodic/summarizer.ts +179 -179
- package/src/memory/episodic/types.ts +52 -52
- package/src/memory/index.ts +582 -582
- package/src/memory/knowledge-extractor.ts +455 -455
- package/src/memory/learning.ts +378 -378
- package/src/memory/patterns.ts +396 -396
- package/src/memory/schema.ts +88 -88
- package/src/memory/search.ts +309 -309
- package/src/memory/store.ts +787 -787
- package/src/memory/types.ts +121 -121
- package/src/orchestrator/coordinator.ts +272 -272
- package/src/orchestrator/decision-logger.ts +228 -228
- package/src/orchestrator/event-emitter.ts +198 -198
- package/src/orchestrator/event-queue.ts +184 -184
- package/src/orchestrator/handlers/base-handler.ts +70 -70
- package/src/orchestrator/handlers/context-handler.ts +73 -73
- package/src/orchestrator/handlers/decision-handler.ts +204 -204
- package/src/orchestrator/handlers/index.ts +10 -10
- package/src/orchestrator/handlers/status-handler.ts +131 -131
- package/src/orchestrator/handlers/task-handler.ts +171 -171
- package/src/orchestrator/index.ts +275 -275
- package/src/orchestrator/task-parser.ts +284 -284
- package/src/orchestrator/types.ts +98 -98
- 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 -300
- package/src/retrieval/bm25/tokenizer.ts +184 -184
- package/src/retrieval/feedback/adaptive.ts +223 -223
- package/src/retrieval/feedback/index.ts +16 -16
- package/src/retrieval/feedback/metrics.ts +223 -223
- package/src/retrieval/feedback/store.ts +283 -283
- package/src/retrieval/fusion/index.ts +194 -194
- package/src/retrieval/fusion/rrf.ts +163 -163
- package/src/retrieval/index.ts +12 -12
- package/src/retrieval/pipeline.ts +375 -375
- package/src/retrieval/query/expander.ts +198 -198
- package/src/retrieval/query/index.ts +27 -27
- package/src/retrieval/query/intent-classifier.ts +236 -236
- package/src/retrieval/query/temporal-parser.ts +295 -295
- package/src/retrieval/reranker/index.ts +188 -188
- package/src/retrieval/reranker/model.ts +95 -95
- package/src/retrieval/service.ts +125 -125
- package/src/retrieval/types.ts +162 -162
- package/src/routing/entity-extractor.ts +428 -428
- package/src/routing/intent-classifier.ts +450 -436
- package/src/routing/response-filter.ts +261 -258
- package/src/routing/router.ts +1441 -1322
- package/src/routing/search-engine.ts +515 -475
- package/src/routing/types.ts +94 -94
- package/src/scripts/health-check.ts +118 -118
- package/src/scripts/setup.ts +122 -122
- package/src/server/handlers/call-tool.ts +156 -156
- package/src/server/handlers/index.ts +9 -9
- package/src/server/handlers/list-tools.ts +35 -35
- package/src/server/handlers/tools/analyze-decision-evolution.ts +151 -151
- package/src/server/handlers/tools/auto-remember.ts +200 -200
- package/src/server/handlers/tools/brain.ts +85 -85
- package/src/server/handlers/tools/create-project.ts +135 -135
- package/src/server/handlers/tools/detect-trends.ts +144 -144
- package/src/server/handlers/tools/find-cross-project-patterns.ts +168 -168
- package/src/server/handlers/tools/get-activity-log.ts +194 -194
- package/src/server/handlers/tools/get-code-standards.ts +124 -124
- package/src/server/handlers/tools/get-corrections.ts +154 -154
- package/src/server/handlers/tools/get-decision-timeline.ts +172 -172
- package/src/server/handlers/tools/get-episode.ts +103 -103
- package/src/server/handlers/tools/get-patterns.ts +158 -158
- package/src/server/handlers/tools/get-phase12-status.ts +63 -63
- package/src/server/handlers/tools/get-project-context.ts +75 -75
- package/src/server/handlers/tools/get-recommendations.ts +145 -145
- package/src/server/handlers/tools/index.ts +31 -31
- package/src/server/handlers/tools/init-project.ts +757 -757
- package/src/server/handlers/tools/list-episodes.ts +90 -90
- package/src/server/handlers/tools/list-projects.ts +125 -125
- package/src/server/handlers/tools/rate-memory.ts +101 -101
- package/src/server/handlers/tools/recall-similar.ts +87 -87
- package/src/server/handlers/tools/recognize-pattern.ts +126 -126
- package/src/server/handlers/tools/record-correction.ts +125 -125
- package/src/server/handlers/tools/remember-decision.ts +153 -153
- package/src/server/handlers/tools/schemas.ts +253 -253
- package/src/server/handlers/tools/search-knowledge-graph.ts +102 -102
- package/src/server/handlers/tools/smart-context.ts +146 -146
- package/src/server/handlers/tools/update-progress.ts +131 -131
- package/src/server/handlers/tools/what-if-analysis.ts +135 -135
- package/src/server/http-api.ts +693 -693
- package/src/server/index.ts +40 -40
- package/src/server/mcp-server.ts +283 -283
- package/src/server/providers/index.ts +7 -7
- package/src/server/providers/prompts.ts +327 -327
- package/src/server/providers/resources.ts +622 -622
- package/src/server/services.ts +468 -468
- package/src/server/types.ts +39 -39
- package/src/server/utils/error-handler.ts +155 -155
- package/src/server/utils/index.ts +13 -13
- package/src/server/utils/memory-indicator.ts +83 -83
- package/src/server/utils/request-context.ts +122 -122
- package/src/server/utils/response-formatter.ts +129 -129
- package/src/server/utils/validators.ts +210 -210
- package/src/setup/index.ts +48 -48
- package/src/setup/wizard.ts +461 -461
- package/src/tools/index.ts +24 -24
- package/src/tools/registry.ts +115 -115
- package/src/tools/schemas.test.ts +30 -30
- package/src/tools/schemas.ts +617 -617
- package/src/tools/types.ts +412 -412
- package/src/utils/circuit-breaker.ts +130 -130
- package/src/utils/cleanup.ts +34 -34
- package/src/utils/error-handler.ts +132 -132
- package/src/utils/error-messages.ts +60 -60
- package/src/utils/fallback.ts +45 -45
- package/src/utils/index.ts +54 -54
- package/src/utils/logger-utils.ts +80 -80
- package/src/utils/logger.ts +88 -88
- package/src/utils/phase12-helper.ts +56 -56
- package/src/utils/retry.ts +94 -94
- package/src/utils/timing.ts +47 -47
- package/src/utils/transaction.ts +63 -63
- package/src/vault/frontmatter.ts +264 -264
- package/src/vault/index.ts +318 -318
- package/src/vault/paths.ts +106 -106
- package/src/vault/query.ts +422 -422
- package/src/vault/reader.ts +264 -264
- package/src/vault/templates.ts +186 -186
- package/src/vault/types.ts +73 -73
- package/src/vault/watcher.ts +277 -277
- package/src/vault/writer.ts +413 -413
- package/tsconfig.json +30 -30
- package/src/cli/auto-update.ts +0 -157
|
@@ -1,275 +1,312 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Phase 17: Session Tracker
|
|
3
|
-
* Server-side session tracking that maintains state across hook invocations.
|
|
4
|
-
* Buffers CapturedKnowledge items and generates session summaries on idle/stop.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import type { Logger } from 'pino'
|
|
8
|
-
import type { EpisodeManager } from '@/memory/episodic/manager'
|
|
9
|
-
import type { MemoryManager } from '@/memory'
|
|
10
|
-
import { ExtractiveSummarizer } from '@/memory/episodic/summarizer'
|
|
11
|
-
import type { CapturedKnowledge } from './types'
|
|
12
|
-
import type { HooksConfig } from '@/config/schema'
|
|
13
|
-
|
|
14
|
-
interface SessionState {
|
|
15
|
-
sessionId: string
|
|
16
|
-
project?: string
|
|
17
|
-
items: CapturedKnowledge[]
|
|
18
|
-
startedAt: string
|
|
19
|
-
lastActivityAt: string
|
|
20
|
-
episodeId?: string
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export class HookSessionTracker {
|
|
24
|
-
private logger: Logger
|
|
25
|
-
private episodeManager: EpisodeManager | null
|
|
26
|
-
private memoryManager: MemoryManager | null
|
|
27
|
-
private summarizer: ExtractiveSummarizer
|
|
28
|
-
private sessions: Map<string, SessionState> = new Map()
|
|
29
|
-
private idleTimers: Map<string, ReturnType<typeof setTimeout>> = new Map()
|
|
30
|
-
private idleTimeoutMs: number
|
|
31
|
-
private minEventsForSummary: number
|
|
32
|
-
|
|
33
|
-
constructor(
|
|
34
|
-
logger: Logger,
|
|
35
|
-
episodeManager: EpisodeManager | null,
|
|
36
|
-
config?: HooksConfig['sessions'],
|
|
37
|
-
memoryManager?: MemoryManager | null
|
|
38
|
-
) {
|
|
39
|
-
this.logger = logger.child({ component: 'hook-session-tracker' })
|
|
40
|
-
this.episodeManager = episodeManager
|
|
41
|
-
this.memoryManager = memoryManager ?? null
|
|
42
|
-
this.summarizer = new ExtractiveSummarizer()
|
|
43
|
-
this.idleTimeoutMs = (config?.idleTimeoutMinutes ?? 30) * 60 * 1000
|
|
44
|
-
this.minEventsForSummary = config?.minEventsForSummary ?? 3
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Track a captured knowledge item within a session.
|
|
49
|
-
*/
|
|
50
|
-
async track(sessionId: string, knowledge: CapturedKnowledge): Promise<void> {
|
|
51
|
-
let session = this.sessions.get(sessionId)
|
|
52
|
-
|
|
53
|
-
if (!session) {
|
|
54
|
-
session = {
|
|
55
|
-
sessionId,
|
|
56
|
-
project: knowledge.project,
|
|
57
|
-
items: [],
|
|
58
|
-
startedAt: new Date().toISOString(),
|
|
59
|
-
lastActivityAt: new Date().toISOString(),
|
|
60
|
-
}
|
|
61
|
-
this.sessions.set(sessionId, session)
|
|
62
|
-
|
|
63
|
-
// Start episode if manager available
|
|
64
|
-
if (this.episodeManager) {
|
|
65
|
-
try {
|
|
66
|
-
const episode = await this.episodeManager.startEpisode(knowledge.project)
|
|
67
|
-
session.episodeId = episode.id
|
|
68
|
-
} catch (err) {
|
|
69
|
-
this.logger.warn({ err }, 'Failed to start episode for session')
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
session.items.push(knowledge)
|
|
75
|
-
session.lastActivityAt = new Date().toISOString()
|
|
76
|
-
|
|
77
|
-
// Reset idle timer
|
|
78
|
-
this.resetIdleTimer(sessionId)
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* End a session immediately (triggered by Stop hook event).
|
|
83
|
-
*/
|
|
84
|
-
async endSession(sessionId: string): Promise<void> {
|
|
85
|
-
const session = this.sessions.get(sessionId)
|
|
86
|
-
if (!session) return
|
|
87
|
-
|
|
88
|
-
this.clearIdleTimer(sessionId)
|
|
89
|
-
await this.summarizeAndPersist(session)
|
|
90
|
-
this.sessions.delete(sessionId)
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* End all active sessions (for server shutdown).
|
|
95
|
-
*/
|
|
96
|
-
async endAllSessions(): Promise<void> {
|
|
97
|
-
const sessionIds = Array.from(this.sessions.keys())
|
|
98
|
-
for (const id of sessionIds) {
|
|
99
|
-
await this.endSession(id)
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Get stats about tracked sessions.
|
|
105
|
-
*/
|
|
106
|
-
getStats(): { activeSessions: number; totalItems: number } {
|
|
107
|
-
let totalItems = 0
|
|
108
|
-
for (const session of this.sessions.values()) {
|
|
109
|
-
totalItems += session.items.length
|
|
110
|
-
}
|
|
111
|
-
return { activeSessions: this.sessions.size, totalItems }
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
private resetIdleTimer(sessionId: string): void {
|
|
115
|
-
this.clearIdleTimer(sessionId)
|
|
116
|
-
const timer = setTimeout(() => {
|
|
117
|
-
this.onIdle(sessionId)
|
|
118
|
-
}, this.idleTimeoutMs)
|
|
119
|
-
this.idleTimers.set(sessionId, timer)
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
private clearIdleTimer(sessionId: string): void {
|
|
123
|
-
const existing = this.idleTimers.get(sessionId)
|
|
124
|
-
if (existing) {
|
|
125
|
-
clearTimeout(existing)
|
|
126
|
-
this.idleTimers.delete(sessionId)
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
private async onIdle(sessionId: string): Promise<void> {
|
|
131
|
-
this.logger.debug({ sessionId }, 'Session idle timeout, summarizing')
|
|
132
|
-
await this.endSession(sessionId)
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* Build a structured summary from session items.
|
|
137
|
-
*
|
|
138
|
-
*/
|
|
139
|
-
private buildStructuredSummary(session: SessionState): string {
|
|
140
|
-
const
|
|
141
|
-
const
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
if (item.
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
parts.push(`
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
)
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
{
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
.
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
)
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
this.
|
|
271
|
-
{
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Phase 17: Session Tracker
|
|
3
|
+
* Server-side session tracking that maintains state across hook invocations.
|
|
4
|
+
* Buffers CapturedKnowledge items and generates session summaries on idle/stop.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { Logger } from 'pino'
|
|
8
|
+
import type { EpisodeManager } from '@/memory/episodic/manager'
|
|
9
|
+
import type { MemoryManager } from '@/memory'
|
|
10
|
+
import { ExtractiveSummarizer } from '@/memory/episodic/summarizer'
|
|
11
|
+
import type { CapturedKnowledge } from './types'
|
|
12
|
+
import type { HooksConfig } from '@/config/schema'
|
|
13
|
+
|
|
14
|
+
interface SessionState {
|
|
15
|
+
sessionId: string
|
|
16
|
+
project?: string
|
|
17
|
+
items: CapturedKnowledge[]
|
|
18
|
+
startedAt: string
|
|
19
|
+
lastActivityAt: string
|
|
20
|
+
episodeId?: string
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export class HookSessionTracker {
|
|
24
|
+
private logger: Logger
|
|
25
|
+
private episodeManager: EpisodeManager | null
|
|
26
|
+
private memoryManager: MemoryManager | null
|
|
27
|
+
private summarizer: ExtractiveSummarizer
|
|
28
|
+
private sessions: Map<string, SessionState> = new Map()
|
|
29
|
+
private idleTimers: Map<string, ReturnType<typeof setTimeout>> = new Map()
|
|
30
|
+
private idleTimeoutMs: number
|
|
31
|
+
private minEventsForSummary: number
|
|
32
|
+
|
|
33
|
+
constructor(
|
|
34
|
+
logger: Logger,
|
|
35
|
+
episodeManager: EpisodeManager | null,
|
|
36
|
+
config?: HooksConfig['sessions'],
|
|
37
|
+
memoryManager?: MemoryManager | null
|
|
38
|
+
) {
|
|
39
|
+
this.logger = logger.child({ component: 'hook-session-tracker' })
|
|
40
|
+
this.episodeManager = episodeManager
|
|
41
|
+
this.memoryManager = memoryManager ?? null
|
|
42
|
+
this.summarizer = new ExtractiveSummarizer()
|
|
43
|
+
this.idleTimeoutMs = (config?.idleTimeoutMinutes ?? 30) * 60 * 1000
|
|
44
|
+
this.minEventsForSummary = config?.minEventsForSummary ?? 3
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Track a captured knowledge item within a session.
|
|
49
|
+
*/
|
|
50
|
+
async track(sessionId: string, knowledge: CapturedKnowledge): Promise<void> {
|
|
51
|
+
let session = this.sessions.get(sessionId)
|
|
52
|
+
|
|
53
|
+
if (!session) {
|
|
54
|
+
session = {
|
|
55
|
+
sessionId,
|
|
56
|
+
project: knowledge.project,
|
|
57
|
+
items: [],
|
|
58
|
+
startedAt: new Date().toISOString(),
|
|
59
|
+
lastActivityAt: new Date().toISOString(),
|
|
60
|
+
}
|
|
61
|
+
this.sessions.set(sessionId, session)
|
|
62
|
+
|
|
63
|
+
// Start episode if manager available
|
|
64
|
+
if (this.episodeManager) {
|
|
65
|
+
try {
|
|
66
|
+
const episode = await this.episodeManager.startEpisode(knowledge.project)
|
|
67
|
+
session.episodeId = episode.id
|
|
68
|
+
} catch (err) {
|
|
69
|
+
this.logger.warn({ err }, 'Failed to start episode for session')
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
session.items.push(knowledge)
|
|
75
|
+
session.lastActivityAt = new Date().toISOString()
|
|
76
|
+
|
|
77
|
+
// Reset idle timer
|
|
78
|
+
this.resetIdleTimer(sessionId)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* End a session immediately (triggered by Stop hook event).
|
|
83
|
+
*/
|
|
84
|
+
async endSession(sessionId: string): Promise<void> {
|
|
85
|
+
const session = this.sessions.get(sessionId)
|
|
86
|
+
if (!session) return
|
|
87
|
+
|
|
88
|
+
this.clearIdleTimer(sessionId)
|
|
89
|
+
await this.summarizeAndPersist(session)
|
|
90
|
+
this.sessions.delete(sessionId)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* End all active sessions (for server shutdown).
|
|
95
|
+
*/
|
|
96
|
+
async endAllSessions(): Promise<void> {
|
|
97
|
+
const sessionIds = Array.from(this.sessions.keys())
|
|
98
|
+
for (const id of sessionIds) {
|
|
99
|
+
await this.endSession(id)
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Get stats about tracked sessions.
|
|
105
|
+
*/
|
|
106
|
+
getStats(): { activeSessions: number; totalItems: number } {
|
|
107
|
+
let totalItems = 0
|
|
108
|
+
for (const session of this.sessions.values()) {
|
|
109
|
+
totalItems += session.items.length
|
|
110
|
+
}
|
|
111
|
+
return { activeSessions: this.sessions.size, totalItems }
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
private resetIdleTimer(sessionId: string): void {
|
|
115
|
+
this.clearIdleTimer(sessionId)
|
|
116
|
+
const timer = setTimeout(() => {
|
|
117
|
+
this.onIdle(sessionId)
|
|
118
|
+
}, this.idleTimeoutMs)
|
|
119
|
+
this.idleTimers.set(sessionId, timer)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
private clearIdleTimer(sessionId: string): void {
|
|
123
|
+
const existing = this.idleTimers.get(sessionId)
|
|
124
|
+
if (existing) {
|
|
125
|
+
clearTimeout(existing)
|
|
126
|
+
this.idleTimers.delete(sessionId)
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
private async onIdle(sessionId: string): Promise<void> {
|
|
131
|
+
this.logger.debug({ sessionId }, 'Session idle timeout, summarizing')
|
|
132
|
+
await this.endSession(sessionId)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Build a structured summary from session items.
|
|
137
|
+
* Phase 25: Natural sentence format with project context, deduplication, noise filtering.
|
|
138
|
+
*/
|
|
139
|
+
private buildStructuredSummary(session: SessionState): string {
|
|
140
|
+
const decisions = session.items.filter(i => i.type === 'decision')
|
|
141
|
+
const corrections = session.items.filter(i => i.type === 'correction')
|
|
142
|
+
const progress = session.items.filter(i => i.type === 'progress')
|
|
143
|
+
|
|
144
|
+
// Extract unique technologies across all items
|
|
145
|
+
const techs = new Set<string>()
|
|
146
|
+
for (const item of session.items) {
|
|
147
|
+
for (const t of item.technologies) techs.add(t)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Extract unique files touched
|
|
151
|
+
const files = new Set<string>()
|
|
152
|
+
for (const item of session.items) {
|
|
153
|
+
const filePath = item.metadata?.filePath || item.metadata?.file_path
|
|
154
|
+
if (filePath) files.add(filePath as string)
|
|
155
|
+
if (item.metadata?.files) {
|
|
156
|
+
for (const f of Array.isArray(item.metadata.files) ? item.metadata.files : [item.metadata.files]) {
|
|
157
|
+
files.add(f as string)
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const parts: string[] = []
|
|
163
|
+
|
|
164
|
+
// Project and tech context
|
|
165
|
+
const project = session.project || 'unknown project'
|
|
166
|
+
if (techs.size > 0) {
|
|
167
|
+
parts.push(`Worked on ${project} using ${Array.from(techs).slice(0, 5).join(', ')}`)
|
|
168
|
+
} else {
|
|
169
|
+
parts.push(`Worked on ${project}`)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Key decisions (deduplicated, max 3)
|
|
173
|
+
if (decisions.length > 0) {
|
|
174
|
+
const uniqueDecisions = this.deduplicateByPrefix(decisions.map(d => d.content), 3)
|
|
175
|
+
parts.push(`Decisions: ${uniqueDecisions.join('. ')}`)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// What was done (git commits and installs only — skip file creation noise)
|
|
179
|
+
const meaningfulProgress = progress.filter(p =>
|
|
180
|
+
p.metadata?.action === 'git' || p.metadata?.action === 'install' ||
|
|
181
|
+
(p.metadata?.action === 'build' && p.metadata?.failed)
|
|
182
|
+
)
|
|
183
|
+
if (meaningfulProgress.length > 0) {
|
|
184
|
+
const uniqueProgress = this.deduplicateByPrefix(meaningfulProgress.map(p => p.content), 3)
|
|
185
|
+
parts.push(`Progress: ${uniqueProgress.join('. ')}`)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Lessons learned (always valuable, keep all)
|
|
189
|
+
if (corrections.length > 0) {
|
|
190
|
+
parts.push(`Lessons: ${corrections.map(c => c.content).join('. ')}`)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// File count (not full list — just a signal of scope)
|
|
194
|
+
if (files.size > 0) {
|
|
195
|
+
parts.push(`Touched ${files.size} file${files.size > 1 ? 's' : ''}`)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return parts.join('. ') || 'Session with no significant events captured.'
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/** Deduplicate strings by checking if one starts with the first 30 chars of another */
|
|
202
|
+
private deduplicateByPrefix(items: string[], max: number): string[] {
|
|
203
|
+
const seen = new Set<string>()
|
|
204
|
+
const result: string[] = []
|
|
205
|
+
for (const item of items) {
|
|
206
|
+
const prefix = item.slice(0, 30).toLowerCase()
|
|
207
|
+
if (!seen.has(prefix)) {
|
|
208
|
+
seen.add(prefix)
|
|
209
|
+
result.push(item)
|
|
210
|
+
if (result.length >= max) break
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return result
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
private async summarizeAndPersist(session: SessionState): Promise<void> {
|
|
217
|
+
if (session.items.length < this.minEventsForSummary) {
|
|
218
|
+
this.logger.debug(
|
|
219
|
+
{ sessionId: session.sessionId, items: session.items.length },
|
|
220
|
+
'Too few items for summary, skipping'
|
|
221
|
+
)
|
|
222
|
+
// Still end the episode if started
|
|
223
|
+
if (session.episodeId && this.episodeManager) {
|
|
224
|
+
try {
|
|
225
|
+
await this.episodeManager.endEpisode(session.episodeId)
|
|
226
|
+
} catch {}
|
|
227
|
+
}
|
|
228
|
+
return
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
this.logger.info(
|
|
232
|
+
{ sessionId: session.sessionId, items: session.items.length, project: session.project },
|
|
233
|
+
'Summarizing session'
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
// Build a synthetic episode for the summarizer
|
|
237
|
+
const messages = session.items.map((item) => ({
|
|
238
|
+
role: 'assistant' as const,
|
|
239
|
+
content: `[${item.type}] ${item.content}`,
|
|
240
|
+
timestamp: item.timestamp,
|
|
241
|
+
token_estimate: Math.ceil(item.content.length / 4),
|
|
242
|
+
}))
|
|
243
|
+
|
|
244
|
+
const syntheticEpisode = {
|
|
245
|
+
id: session.sessionId,
|
|
246
|
+
project: session.project,
|
|
247
|
+
status: 'completed' as const,
|
|
248
|
+
started_at: session.startedAt,
|
|
249
|
+
ended_at: new Date().toISOString(),
|
|
250
|
+
messages,
|
|
251
|
+
related_decisions: session.items
|
|
252
|
+
.filter(i => i.type === 'decision')
|
|
253
|
+
.map((_, idx) => `hook-decision-${idx}`),
|
|
254
|
+
related_patterns: session.items
|
|
255
|
+
.filter(i => i.type === 'pattern')
|
|
256
|
+
.map((_, idx) => `hook-pattern-${idx}`),
|
|
257
|
+
related_corrections: session.items
|
|
258
|
+
.filter(i => i.type === 'correction')
|
|
259
|
+
.map((_, idx) => `hook-correction-${idx}`),
|
|
260
|
+
token_count: messages.reduce((sum, m) => sum + (m.token_estimate || 0), 0),
|
|
261
|
+
message_count: messages.length,
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const extractiveSummary = this.summarizer.summarize(syntheticEpisode)
|
|
265
|
+
|
|
266
|
+
// Phase 21: Build structured summary alongside extractive
|
|
267
|
+
const structuredSummary = this.buildStructuredSummary(session)
|
|
268
|
+
|
|
269
|
+
// End the episode with summary attached
|
|
270
|
+
if (session.episodeId && this.episodeManager) {
|
|
271
|
+
try {
|
|
272
|
+
// Add messages to episode before ending
|
|
273
|
+
for (const msg of messages) {
|
|
274
|
+
await this.episodeManager.addMessage(session.episodeId, msg)
|
|
275
|
+
}
|
|
276
|
+
await this.episodeManager.endEpisode(session.episodeId)
|
|
277
|
+
} catch (err) {
|
|
278
|
+
this.logger.warn({ err, sessionId: session.sessionId }, 'Failed to end episode')
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Phase 20+21: Store session summary as a searchable decision
|
|
283
|
+
// Uses structured summary for better auto-context readability
|
|
284
|
+
if (this.memoryManager && (extractiveSummary.brief || structuredSummary)) {
|
|
285
|
+
try {
|
|
286
|
+
const project = session.project || 'general'
|
|
287
|
+
const technologies = [...new Set(session.items.flatMap(i => i.technologies))]
|
|
288
|
+
const summaryContent = `Session summary: ${structuredSummary}${extractiveSummary.key_topics.length > 0 ? ` (Topics: ${extractiveSummary.key_topics.join(', ')})` : ''}`
|
|
289
|
+
|
|
290
|
+
await this.memoryManager.rememberDecision(
|
|
291
|
+
project,
|
|
292
|
+
'Auto-captured session summary',
|
|
293
|
+
summaryContent,
|
|
294
|
+
'Session ended — auto-summarized by passive hooks',
|
|
295
|
+
{ tags: ['session-summary', ...technologies] }
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
this.logger.info(
|
|
299
|
+
{ sessionId: session.sessionId, project },
|
|
300
|
+
'Session summary stored as searchable memory'
|
|
301
|
+
)
|
|
302
|
+
} catch (err) {
|
|
303
|
+
this.logger.warn({ err, sessionId: session.sessionId }, 'Failed to store session summary')
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
this.logger.info(
|
|
308
|
+
{ sessionId: session.sessionId, summary: structuredSummary },
|
|
309
|
+
'Session summarized'
|
|
310
|
+
)
|
|
311
|
+
}
|
|
312
|
+
}
|