claude-brain 0.14.2 → 0.14.4
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 +11 -11
- package/bunfig.toml +8 -8
- package/package.json +80 -80
- 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/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 +202 -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/serve.ts +167 -167
- package/src/cli/commands/start.ts +42 -42
- package/src/cli/commands/uninstall-mcp.ts +41 -41
- package/src/cli/commands/update.ts +121 -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 +112 -112
- package/src/hooks/capture.ts +168 -168
- 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 +191 -194
- package/src/hooks/passive-classifier.ts +366 -366
- package/src/hooks/queue.ts +129 -129
- package/src/hooks/session-tracker.ts +275 -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 +153 -153
- 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 +436 -436
- package/src/routing/response-filter.ts +258 -254
- package/src/routing/router.ts +1322 -1314
- package/src/routing/search-engine.ts +475 -475
- package/src/routing/types.ts +94 -84
- 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 -124
- 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
|
@@ -1,275 +1,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
|
-
* Format: "Did: ...; Decided: ...; Files: ..."
|
|
138
|
-
*/
|
|
139
|
-
private buildStructuredSummary(session: SessionState): string {
|
|
140
|
-
const actions: string[] = []
|
|
141
|
-
const decisions: string[] = []
|
|
142
|
-
const files = new Set<string>()
|
|
143
|
-
|
|
144
|
-
for (const item of session.items) {
|
|
145
|
-
// Extract file paths from metadata
|
|
146
|
-
if (item.metadata?.file_path) {
|
|
147
|
-
files.add(item.metadata.file_path)
|
|
148
|
-
}
|
|
149
|
-
if (item.metadata?.files) {
|
|
150
|
-
for (const f of Array.isArray(item.metadata.files) ? item.metadata.files : [item.metadata.files]) {
|
|
151
|
-
files.add(f)
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
if (item.type === 'decision') {
|
|
156
|
-
decisions.push(item.content.slice(0, 80))
|
|
157
|
-
} else if (item.type === 'progress') {
|
|
158
|
-
actions.push(item.content.slice(0, 80))
|
|
159
|
-
} else if (item.type === 'pattern' || item.type === 'correction') {
|
|
160
|
-
actions.push(`[${item.type}] ${item.content.slice(0, 60)}`)
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
const parts: string[] = []
|
|
165
|
-
if (actions.length > 0) {
|
|
166
|
-
parts.push(`Did: ${actions.slice(0, 5).join('; ')}`)
|
|
167
|
-
}
|
|
168
|
-
if (decisions.length > 0) {
|
|
169
|
-
parts.push(`Decided: ${decisions.slice(0, 3).join('; ')}`)
|
|
170
|
-
}
|
|
171
|
-
if (files.size > 0) {
|
|
172
|
-
const fileList = [...files].slice(0, 8)
|
|
173
|
-
parts.push(`Files: ${fileList.join(', ')}`)
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
return parts.join('. ') || `Session with ${session.items.length} events`
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
private async summarizeAndPersist(session: SessionState): Promise<void> {
|
|
180
|
-
if (session.items.length < this.minEventsForSummary) {
|
|
181
|
-
this.logger.debug(
|
|
182
|
-
{ sessionId: session.sessionId, items: session.items.length },
|
|
183
|
-
'Too few items for summary, skipping'
|
|
184
|
-
)
|
|
185
|
-
// Still end the episode if started
|
|
186
|
-
if (session.episodeId && this.episodeManager) {
|
|
187
|
-
try {
|
|
188
|
-
await this.episodeManager.endEpisode(session.episodeId)
|
|
189
|
-
} catch {}
|
|
190
|
-
}
|
|
191
|
-
return
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
this.logger.info(
|
|
195
|
-
{ sessionId: session.sessionId, items: session.items.length, project: session.project },
|
|
196
|
-
'Summarizing session'
|
|
197
|
-
)
|
|
198
|
-
|
|
199
|
-
// Build a synthetic episode for the summarizer
|
|
200
|
-
const messages = session.items.map((item) => ({
|
|
201
|
-
role: 'assistant' as const,
|
|
202
|
-
content: `[${item.type}] ${item.content}`,
|
|
203
|
-
timestamp: item.timestamp,
|
|
204
|
-
token_estimate: Math.ceil(item.content.length / 4),
|
|
205
|
-
}))
|
|
206
|
-
|
|
207
|
-
const syntheticEpisode = {
|
|
208
|
-
id: session.sessionId,
|
|
209
|
-
project: session.project,
|
|
210
|
-
status: 'completed' as const,
|
|
211
|
-
started_at: session.startedAt,
|
|
212
|
-
ended_at: new Date().toISOString(),
|
|
213
|
-
messages,
|
|
214
|
-
related_decisions: session.items
|
|
215
|
-
.filter(i => i.type === 'decision')
|
|
216
|
-
.map((_, idx) => `hook-decision-${idx}`),
|
|
217
|
-
related_patterns: session.items
|
|
218
|
-
.filter(i => i.type === 'pattern')
|
|
219
|
-
.map((_, idx) => `hook-pattern-${idx}`),
|
|
220
|
-
related_corrections: session.items
|
|
221
|
-
.filter(i => i.type === 'correction')
|
|
222
|
-
.map((_, idx) => `hook-correction-${idx}`),
|
|
223
|
-
token_count: messages.reduce((sum, m) => sum + (m.token_estimate || 0), 0),
|
|
224
|
-
message_count: messages.length,
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
const extractiveSummary = this.summarizer.summarize(syntheticEpisode)
|
|
228
|
-
|
|
229
|
-
// Phase 21: Build structured summary alongside extractive
|
|
230
|
-
const structuredSummary = this.buildStructuredSummary(session)
|
|
231
|
-
|
|
232
|
-
// End the episode with summary attached
|
|
233
|
-
if (session.episodeId && this.episodeManager) {
|
|
234
|
-
try {
|
|
235
|
-
// Add messages to episode before ending
|
|
236
|
-
for (const msg of messages) {
|
|
237
|
-
await this.episodeManager.addMessage(session.episodeId, msg)
|
|
238
|
-
}
|
|
239
|
-
await this.episodeManager.endEpisode(session.episodeId)
|
|
240
|
-
} catch (err) {
|
|
241
|
-
this.logger.warn({ err, sessionId: session.sessionId }, 'Failed to end episode')
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
// Phase 20+21: Store session summary as a searchable decision
|
|
246
|
-
// Uses structured summary for better auto-context readability
|
|
247
|
-
if (this.memoryManager && (extractiveSummary.brief || structuredSummary)) {
|
|
248
|
-
try {
|
|
249
|
-
const project = session.project || 'general'
|
|
250
|
-
const technologies = [...new Set(session.items.flatMap(i => i.technologies))]
|
|
251
|
-
const summaryContent = `Session summary: ${structuredSummary}${extractiveSummary.key_topics.length > 0 ? ` (Topics: ${extractiveSummary.key_topics.join(', ')})` : ''}`
|
|
252
|
-
|
|
253
|
-
await this.memoryManager.rememberDecision(
|
|
254
|
-
project,
|
|
255
|
-
'Auto-captured session summary',
|
|
256
|
-
summaryContent,
|
|
257
|
-
'Session ended — auto-summarized by passive hooks',
|
|
258
|
-
{ tags: ['session-summary', ...technologies] }
|
|
259
|
-
)
|
|
260
|
-
|
|
261
|
-
this.logger.info(
|
|
262
|
-
{ sessionId: session.sessionId, project },
|
|
263
|
-
'Session summary stored as searchable memory'
|
|
264
|
-
)
|
|
265
|
-
} catch (err) {
|
|
266
|
-
this.logger.warn({ err, sessionId: session.sessionId }, 'Failed to store session summary')
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
this.logger.info(
|
|
271
|
-
{ sessionId: session.sessionId, summary: structuredSummary },
|
|
272
|
-
'Session summarized'
|
|
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
|
+
* Format: "Did: ...; Decided: ...; Files: ..."
|
|
138
|
+
*/
|
|
139
|
+
private buildStructuredSummary(session: SessionState): string {
|
|
140
|
+
const actions: string[] = []
|
|
141
|
+
const decisions: string[] = []
|
|
142
|
+
const files = new Set<string>()
|
|
143
|
+
|
|
144
|
+
for (const item of session.items) {
|
|
145
|
+
// Extract file paths from metadata
|
|
146
|
+
if (item.metadata?.file_path) {
|
|
147
|
+
files.add(item.metadata.file_path)
|
|
148
|
+
}
|
|
149
|
+
if (item.metadata?.files) {
|
|
150
|
+
for (const f of Array.isArray(item.metadata.files) ? item.metadata.files : [item.metadata.files]) {
|
|
151
|
+
files.add(f)
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (item.type === 'decision') {
|
|
156
|
+
decisions.push(item.content.slice(0, 80))
|
|
157
|
+
} else if (item.type === 'progress') {
|
|
158
|
+
actions.push(item.content.slice(0, 80))
|
|
159
|
+
} else if (item.type === 'pattern' || item.type === 'correction') {
|
|
160
|
+
actions.push(`[${item.type}] ${item.content.slice(0, 60)}`)
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const parts: string[] = []
|
|
165
|
+
if (actions.length > 0) {
|
|
166
|
+
parts.push(`Did: ${actions.slice(0, 5).join('; ')}`)
|
|
167
|
+
}
|
|
168
|
+
if (decisions.length > 0) {
|
|
169
|
+
parts.push(`Decided: ${decisions.slice(0, 3).join('; ')}`)
|
|
170
|
+
}
|
|
171
|
+
if (files.size > 0) {
|
|
172
|
+
const fileList = [...files].slice(0, 8)
|
|
173
|
+
parts.push(`Files: ${fileList.join(', ')}`)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return parts.join('. ') || `Session with ${session.items.length} events`
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
private async summarizeAndPersist(session: SessionState): Promise<void> {
|
|
180
|
+
if (session.items.length < this.minEventsForSummary) {
|
|
181
|
+
this.logger.debug(
|
|
182
|
+
{ sessionId: session.sessionId, items: session.items.length },
|
|
183
|
+
'Too few items for summary, skipping'
|
|
184
|
+
)
|
|
185
|
+
// Still end the episode if started
|
|
186
|
+
if (session.episodeId && this.episodeManager) {
|
|
187
|
+
try {
|
|
188
|
+
await this.episodeManager.endEpisode(session.episodeId)
|
|
189
|
+
} catch {}
|
|
190
|
+
}
|
|
191
|
+
return
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
this.logger.info(
|
|
195
|
+
{ sessionId: session.sessionId, items: session.items.length, project: session.project },
|
|
196
|
+
'Summarizing session'
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
// Build a synthetic episode for the summarizer
|
|
200
|
+
const messages = session.items.map((item) => ({
|
|
201
|
+
role: 'assistant' as const,
|
|
202
|
+
content: `[${item.type}] ${item.content}`,
|
|
203
|
+
timestamp: item.timestamp,
|
|
204
|
+
token_estimate: Math.ceil(item.content.length / 4),
|
|
205
|
+
}))
|
|
206
|
+
|
|
207
|
+
const syntheticEpisode = {
|
|
208
|
+
id: session.sessionId,
|
|
209
|
+
project: session.project,
|
|
210
|
+
status: 'completed' as const,
|
|
211
|
+
started_at: session.startedAt,
|
|
212
|
+
ended_at: new Date().toISOString(),
|
|
213
|
+
messages,
|
|
214
|
+
related_decisions: session.items
|
|
215
|
+
.filter(i => i.type === 'decision')
|
|
216
|
+
.map((_, idx) => `hook-decision-${idx}`),
|
|
217
|
+
related_patterns: session.items
|
|
218
|
+
.filter(i => i.type === 'pattern')
|
|
219
|
+
.map((_, idx) => `hook-pattern-${idx}`),
|
|
220
|
+
related_corrections: session.items
|
|
221
|
+
.filter(i => i.type === 'correction')
|
|
222
|
+
.map((_, idx) => `hook-correction-${idx}`),
|
|
223
|
+
token_count: messages.reduce((sum, m) => sum + (m.token_estimate || 0), 0),
|
|
224
|
+
message_count: messages.length,
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const extractiveSummary = this.summarizer.summarize(syntheticEpisode)
|
|
228
|
+
|
|
229
|
+
// Phase 21: Build structured summary alongside extractive
|
|
230
|
+
const structuredSummary = this.buildStructuredSummary(session)
|
|
231
|
+
|
|
232
|
+
// End the episode with summary attached
|
|
233
|
+
if (session.episodeId && this.episodeManager) {
|
|
234
|
+
try {
|
|
235
|
+
// Add messages to episode before ending
|
|
236
|
+
for (const msg of messages) {
|
|
237
|
+
await this.episodeManager.addMessage(session.episodeId, msg)
|
|
238
|
+
}
|
|
239
|
+
await this.episodeManager.endEpisode(session.episodeId)
|
|
240
|
+
} catch (err) {
|
|
241
|
+
this.logger.warn({ err, sessionId: session.sessionId }, 'Failed to end episode')
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Phase 20+21: Store session summary as a searchable decision
|
|
246
|
+
// Uses structured summary for better auto-context readability
|
|
247
|
+
if (this.memoryManager && (extractiveSummary.brief || structuredSummary)) {
|
|
248
|
+
try {
|
|
249
|
+
const project = session.project || 'general'
|
|
250
|
+
const technologies = [...new Set(session.items.flatMap(i => i.technologies))]
|
|
251
|
+
const summaryContent = `Session summary: ${structuredSummary}${extractiveSummary.key_topics.length > 0 ? ` (Topics: ${extractiveSummary.key_topics.join(', ')})` : ''}`
|
|
252
|
+
|
|
253
|
+
await this.memoryManager.rememberDecision(
|
|
254
|
+
project,
|
|
255
|
+
'Auto-captured session summary',
|
|
256
|
+
summaryContent,
|
|
257
|
+
'Session ended — auto-summarized by passive hooks',
|
|
258
|
+
{ tags: ['session-summary', ...technologies] }
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
this.logger.info(
|
|
262
|
+
{ sessionId: session.sessionId, project },
|
|
263
|
+
'Session summary stored as searchable memory'
|
|
264
|
+
)
|
|
265
|
+
} catch (err) {
|
|
266
|
+
this.logger.warn({ err, sessionId: session.sessionId }, 'Failed to store session summary')
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
this.logger.info(
|
|
271
|
+
{ sessionId: session.sessionId, summary: structuredSummary },
|
|
272
|
+
'Session summarized'
|
|
273
|
+
)
|
|
274
|
+
}
|
|
275
|
+
}
|
package/src/hooks/types.ts
CHANGED
|
@@ -1,47 +1,47 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Phase 17: Passive Learning via Hooks — Shared Types
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
/** Claude Code hook stdin JSON format */
|
|
6
|
-
export interface HookInput {
|
|
7
|
-
session_id: string
|
|
8
|
-
hook_event_name: 'PostToolUse' | 'Stop' | 'PreToolUse' | 'GitCommit'
|
|
9
|
-
cwd: string
|
|
10
|
-
tool_name?: string
|
|
11
|
-
tool_input?: Record<string, any>
|
|
12
|
-
tool_response?: {
|
|
13
|
-
content?: string | Array<{ type: string; text?: string }>
|
|
14
|
-
[key: string]: any
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/** Knowledge type classifications */
|
|
19
|
-
export type KnowledgeType = 'decision' | 'pattern' | 'correction' | 'progress'
|
|
20
|
-
|
|
21
|
-
/** A piece of knowledge captured from a hook event */
|
|
22
|
-
export interface CapturedKnowledge {
|
|
23
|
-
type: KnowledgeType
|
|
24
|
-
confidence: number
|
|
25
|
-
content: string
|
|
26
|
-
project?: string
|
|
27
|
-
technologies: string[]
|
|
28
|
-
metadata: Record<string, any>
|
|
29
|
-
source: 'hook-passive'
|
|
30
|
-
timestamp: string
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/** What to do with captured knowledge before storage */
|
|
34
|
-
export type StoreAction =
|
|
35
|
-
| { action: 'store_new' }
|
|
36
|
-
| { action: 'merge'; existingId: string; mergedContent: string }
|
|
37
|
-
| { action: 'skip'; reason: string }
|
|
38
|
-
|
|
39
|
-
/** Hook event stats for status reporting */
|
|
40
|
-
export interface HookStats {
|
|
41
|
-
totalCaptured: number
|
|
42
|
-
totalSkipped: number
|
|
43
|
-
totalMerged: number
|
|
44
|
-
sessionsTracked: number
|
|
45
|
-
lastCaptureAt?: string
|
|
46
|
-
queueSize: number
|
|
47
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Phase 17: Passive Learning via Hooks — Shared Types
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/** Claude Code hook stdin JSON format */
|
|
6
|
+
export interface HookInput {
|
|
7
|
+
session_id: string
|
|
8
|
+
hook_event_name: 'PostToolUse' | 'Stop' | 'PreToolUse' | 'GitCommit'
|
|
9
|
+
cwd: string
|
|
10
|
+
tool_name?: string
|
|
11
|
+
tool_input?: Record<string, any>
|
|
12
|
+
tool_response?: {
|
|
13
|
+
content?: string | Array<{ type: string; text?: string }>
|
|
14
|
+
[key: string]: any
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Knowledge type classifications */
|
|
19
|
+
export type KnowledgeType = 'decision' | 'pattern' | 'correction' | 'progress'
|
|
20
|
+
|
|
21
|
+
/** A piece of knowledge captured from a hook event */
|
|
22
|
+
export interface CapturedKnowledge {
|
|
23
|
+
type: KnowledgeType
|
|
24
|
+
confidence: number
|
|
25
|
+
content: string
|
|
26
|
+
project?: string
|
|
27
|
+
technologies: string[]
|
|
28
|
+
metadata: Record<string, any>
|
|
29
|
+
source: 'hook-passive'
|
|
30
|
+
timestamp: string
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** What to do with captured knowledge before storage */
|
|
34
|
+
export type StoreAction =
|
|
35
|
+
| { action: 'store_new' }
|
|
36
|
+
| { action: 'merge'; existingId: string; mergedContent: string }
|
|
37
|
+
| { action: 'skip'; reason: string }
|
|
38
|
+
|
|
39
|
+
/** Hook event stats for status reporting */
|
|
40
|
+
export interface HookStats {
|
|
41
|
+
totalCaptured: number
|
|
42
|
+
totalSkipped: number
|
|
43
|
+
totalMerged: number
|
|
44
|
+
sessionsTracked: number
|
|
45
|
+
lastCaptureAt?: string
|
|
46
|
+
queueSize: number
|
|
47
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
#!/usr/bin/env bun
|
|
2
|
-
import { runServe } from './cli/commands/serve'
|
|
3
|
-
|
|
4
|
-
runServe().catch((error) => {
|
|
5
|
-
console.error('Fatal error:', error)
|
|
6
|
-
process.exit(1)
|
|
7
|
-
})
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
import { runServe } from './cli/commands/serve'
|
|
3
|
+
|
|
4
|
+
runServe().catch((error) => {
|
|
5
|
+
console.error('Fatal error:', error)
|
|
6
|
+
process.exit(1)
|
|
7
|
+
})
|