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
package/src/hooks/brain-hook.ts
CHANGED
|
@@ -1,131 +1,134 @@
|
|
|
1
|
-
#!/usr/bin/env bun
|
|
2
|
-
/**
|
|
3
|
-
* Phase 17: Brain Hook Worker Script
|
|
4
|
-
* Standalone entry point executed by Claude Code on every tool call.
|
|
5
|
-
*
|
|
6
|
-
* CRITICAL CONSTRAINTS:
|
|
7
|
-
* - Must complete in <200ms (no heavy imports like ChromaDB/embeddings)
|
|
8
|
-
* - NEVER write to stdout (would corrupt Claude Code JSON-RPC)
|
|
9
|
-
* - All errors silently caught with process.exit(0)
|
|
10
|
-
* - Reads stdin JSON from Claude Code, classifies, POSTs to HTTP API
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import { BrainCapture } from './capture'
|
|
14
|
-
import { appendToQueue } from './queue'
|
|
15
|
-
import type { HookInput } from './types'
|
|
16
|
-
|
|
17
|
-
export async function main(): Promise<void> {
|
|
18
|
-
// Parse --event arg
|
|
19
|
-
const eventIdx = process.argv.indexOf('--event')
|
|
20
|
-
const eventName = eventIdx >= 0 ? process.argv[eventIdx + 1] : undefined
|
|
21
|
-
|
|
22
|
-
// Read stdin JSON from Claude Code
|
|
23
|
-
let rawInput: string
|
|
24
|
-
try {
|
|
25
|
-
rawInput = await readStdin()
|
|
26
|
-
} catch {
|
|
27
|
-
process.exit(0)
|
|
28
|
-
return
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
if (!rawInput.trim()) {
|
|
32
|
-
process.exit(0)
|
|
33
|
-
return
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
let input: HookInput
|
|
37
|
-
try {
|
|
38
|
-
input = JSON.parse(rawInput)
|
|
39
|
-
} catch {
|
|
40
|
-
process.exit(0)
|
|
41
|
-
return
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// Override event name from arg if provided
|
|
45
|
-
if (eventName) {
|
|
46
|
-
input.hook_event_name = eventName as HookInput['hook_event_name']
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// Phase 20: Hooks enabled by default — only disabled by explicit env var
|
|
50
|
-
if (process.env.CLAUDE_BRAIN_HOOKS_ENABLED === 'false') {
|
|
51
|
-
process.exit(0)
|
|
52
|
-
return
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// BUG-006: Read port from --port arg (cross-platform). Env var fallback removed — breaks Windows cmd.exe.
|
|
56
|
-
const portIdx = process.argv.indexOf('--port')
|
|
57
|
-
const portArg = portIdx >= 0 ? process.argv[portIdx + 1] : undefined
|
|
58
|
-
const port = parseInt(portArg || '3000', 10)
|
|
59
|
-
|
|
60
|
-
// For Stop events: trigger session-end summarization regardless of classification
|
|
61
|
-
if (input.hook_event_name === 'Stop' && input.session_id) {
|
|
62
|
-
try {
|
|
63
|
-
await fetch(`http://localhost:${port}/api/hooks/session-end`, {
|
|
64
|
-
method: 'POST',
|
|
65
|
-
headers: { 'Content-Type': 'application/json' },
|
|
66
|
-
body: JSON.stringify({ sessionId: input.session_id }),
|
|
67
|
-
signal: AbortSignal.timeout(5000),
|
|
68
|
-
})
|
|
69
|
-
} catch {
|
|
70
|
-
// Server unreachable — session summary lost, acceptable tradeoff
|
|
71
|
-
}
|
|
72
|
-
process.exit(0)
|
|
73
|
-
return
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// Process the hook event (non-Stop events only)
|
|
77
|
-
const capture = new BrainCapture({ enabled: true })
|
|
78
|
-
const knowledge = capture.process(input)
|
|
79
|
-
|
|
80
|
-
if (!knowledge) {
|
|
81
|
-
process.exit(0)
|
|
82
|
-
return
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// POST to HTTP API server
|
|
86
|
-
try {
|
|
87
|
-
const res = await fetch(`http://localhost:${port}/api/hooks/ingest`, {
|
|
88
|
-
method: 'POST',
|
|
89
|
-
headers: { 'Content-Type': 'application/json' },
|
|
90
|
-
body: JSON.stringify({
|
|
91
|
-
knowledge: [knowledge],
|
|
92
|
-
sessionId: input.session_id,
|
|
93
|
-
}),
|
|
94
|
-
signal: AbortSignal.timeout(3000),
|
|
95
|
-
})
|
|
96
|
-
|
|
97
|
-
if (!res.ok) {
|
|
98
|
-
// Server returned error — queue for later
|
|
99
|
-
appendToQueue([knowledge])
|
|
100
|
-
}
|
|
101
|
-
} catch {
|
|
102
|
-
// Server unreachable — append to offline queue
|
|
103
|
-
appendToQueue([knowledge])
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
process.exit(0)
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/** Read all of stdin as a string */
|
|
110
|
-
function readStdin(): Promise<string> {
|
|
111
|
-
return new Promise((resolve, reject) => {
|
|
112
|
-
const chunks: Buffer[] = []
|
|
113
|
-
const stdin = process.stdin
|
|
114
|
-
|
|
115
|
-
stdin.on('data', (chunk: Buffer) => chunks.push(chunk))
|
|
116
|
-
stdin.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8')))
|
|
117
|
-
stdin.on('error', reject)
|
|
118
|
-
|
|
119
|
-
// Timeout after 2 seconds
|
|
120
|
-
setTimeout(() => {
|
|
121
|
-
stdin.destroy()
|
|
122
|
-
resolve(Buffer.concat(chunks).toString('utf-8'))
|
|
123
|
-
}, 2000)
|
|
124
|
-
})
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// Execute when run directly (not when imported by CLI)
|
|
128
|
-
const isDirectRun = process.argv[1]?.includes('brain-hook')
|
|
129
|
-
if (isDirectRun) {
|
|
130
|
-
main().catch(() =>
|
|
131
|
-
}
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* Phase 17: Brain Hook Worker Script
|
|
4
|
+
* Standalone entry point executed by Claude Code on every tool call.
|
|
5
|
+
*
|
|
6
|
+
* CRITICAL CONSTRAINTS:
|
|
7
|
+
* - Must complete in <200ms (no heavy imports like ChromaDB/embeddings)
|
|
8
|
+
* - NEVER write to stdout (would corrupt Claude Code JSON-RPC)
|
|
9
|
+
* - All errors silently caught with process.exit(0)
|
|
10
|
+
* - Reads stdin JSON from Claude Code, classifies, POSTs to HTTP API
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { BrainCapture } from './capture'
|
|
14
|
+
import { appendToQueue } from './queue'
|
|
15
|
+
import type { HookInput } from './types'
|
|
16
|
+
|
|
17
|
+
export async function main(): Promise<void> {
|
|
18
|
+
// Parse --event arg
|
|
19
|
+
const eventIdx = process.argv.indexOf('--event')
|
|
20
|
+
const eventName = eventIdx >= 0 ? process.argv[eventIdx + 1] : undefined
|
|
21
|
+
|
|
22
|
+
// Read stdin JSON from Claude Code
|
|
23
|
+
let rawInput: string
|
|
24
|
+
try {
|
|
25
|
+
rawInput = await readStdin()
|
|
26
|
+
} catch {
|
|
27
|
+
process.exit(0)
|
|
28
|
+
return
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (!rawInput.trim()) {
|
|
32
|
+
process.exit(0)
|
|
33
|
+
return
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
let input: HookInput
|
|
37
|
+
try {
|
|
38
|
+
input = JSON.parse(rawInput)
|
|
39
|
+
} catch {
|
|
40
|
+
process.exit(0)
|
|
41
|
+
return
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Override event name from arg if provided
|
|
45
|
+
if (eventName) {
|
|
46
|
+
input.hook_event_name = eventName as HookInput['hook_event_name']
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Phase 20: Hooks enabled by default — only disabled by explicit env var
|
|
50
|
+
if (process.env.CLAUDE_BRAIN_HOOKS_ENABLED === 'false') {
|
|
51
|
+
process.exit(0)
|
|
52
|
+
return
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// BUG-006: Read port from --port arg (cross-platform). Env var fallback removed — breaks Windows cmd.exe.
|
|
56
|
+
const portIdx = process.argv.indexOf('--port')
|
|
57
|
+
const portArg = portIdx >= 0 ? process.argv[portIdx + 1] : undefined
|
|
58
|
+
const port = parseInt(portArg || '3000', 10)
|
|
59
|
+
|
|
60
|
+
// For Stop events: trigger session-end summarization regardless of classification
|
|
61
|
+
if (input.hook_event_name === 'Stop' && input.session_id) {
|
|
62
|
+
try {
|
|
63
|
+
await fetch(`http://localhost:${port}/api/hooks/session-end`, {
|
|
64
|
+
method: 'POST',
|
|
65
|
+
headers: { 'Content-Type': 'application/json' },
|
|
66
|
+
body: JSON.stringify({ sessionId: input.session_id }),
|
|
67
|
+
signal: AbortSignal.timeout(5000),
|
|
68
|
+
})
|
|
69
|
+
} catch {
|
|
70
|
+
// Server unreachable — session summary lost, acceptable tradeoff
|
|
71
|
+
}
|
|
72
|
+
process.exit(0)
|
|
73
|
+
return
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Process the hook event (non-Stop events only)
|
|
77
|
+
const capture = new BrainCapture({ enabled: true })
|
|
78
|
+
const knowledge = capture.process(input)
|
|
79
|
+
|
|
80
|
+
if (!knowledge) {
|
|
81
|
+
process.exit(0)
|
|
82
|
+
return
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// POST to HTTP API server
|
|
86
|
+
try {
|
|
87
|
+
const res = await fetch(`http://localhost:${port}/api/hooks/ingest`, {
|
|
88
|
+
method: 'POST',
|
|
89
|
+
headers: { 'Content-Type': 'application/json' },
|
|
90
|
+
body: JSON.stringify({
|
|
91
|
+
knowledge: [knowledge],
|
|
92
|
+
sessionId: input.session_id,
|
|
93
|
+
}),
|
|
94
|
+
signal: AbortSignal.timeout(3000),
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
if (!res.ok) {
|
|
98
|
+
// Server returned error — queue for later
|
|
99
|
+
appendToQueue([knowledge])
|
|
100
|
+
}
|
|
101
|
+
} catch {
|
|
102
|
+
// Server unreachable — append to offline queue
|
|
103
|
+
appendToQueue([knowledge])
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
process.exit(0)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Read all of stdin as a string */
|
|
110
|
+
function readStdin(): Promise<string> {
|
|
111
|
+
return new Promise((resolve, reject) => {
|
|
112
|
+
const chunks: Buffer[] = []
|
|
113
|
+
const stdin = process.stdin
|
|
114
|
+
|
|
115
|
+
stdin.on('data', (chunk: Buffer) => chunks.push(chunk))
|
|
116
|
+
stdin.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8')))
|
|
117
|
+
stdin.on('error', reject)
|
|
118
|
+
|
|
119
|
+
// Timeout after 2 seconds
|
|
120
|
+
setTimeout(() => {
|
|
121
|
+
stdin.destroy()
|
|
122
|
+
resolve(Buffer.concat(chunks).toString('utf-8'))
|
|
123
|
+
}, 2000)
|
|
124
|
+
})
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Execute when run directly (not when imported by CLI)
|
|
128
|
+
const isDirectRun = process.argv[1]?.includes('brain-hook')
|
|
129
|
+
if (isDirectRun) {
|
|
130
|
+
main().catch((error) => {
|
|
131
|
+
process.stderr.write(`[brain-hook] Fatal error: ${error?.message || error}\n`)
|
|
132
|
+
process.exit(1)
|
|
133
|
+
})
|
|
134
|
+
}
|
package/src/hooks/capture.ts
CHANGED
|
@@ -1,168 +1,168 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Phase 17: Capture Engine
|
|
3
|
-
* Orchestrates passive classification + entity extraction with privacy filters.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import type { HookInput, CapturedKnowledge } from './types'
|
|
7
|
-
import type { HooksConfig } from '@/config/schema'
|
|
8
|
-
import { PassiveClassifier } from './passive-classifier'
|
|
9
|
-
|
|
10
|
-
// Reuse tech dictionaries from entity extractor for enrichment
|
|
11
|
-
const COMMON_TECH: Set<string> = new Set([
|
|
12
|
-
'typescript', 'javascript', 'python', 'rust', 'go', 'java', 'ruby', 'php', 'swift', 'kotlin',
|
|
13
|
-
'react', 'vue', 'angular', 'svelte', 'nextjs', 'nuxt', 'remix', 'astro', 'solid',
|
|
14
|
-
'express', 'fastify', 'hono', 'nestjs', 'django', 'flask', 'fastapi', 'rails', 'spring',
|
|
15
|
-
'mongodb', 'redis', 'postgresql', 'postgres', 'mysql', 'sqlite', 'dynamodb', 'firebase', 'supabase',
|
|
16
|
-
'prisma', 'drizzle', 'typeorm', 'sequelize', 'chromadb', 'pinecone',
|
|
17
|
-
'docker', 'kubernetes', 'aws', 'gcp', 'azure', 'vercel', 'netlify',
|
|
18
|
-
'webpack', 'vite', 'esbuild', 'bun', 'deno', 'node', 'npm', 'yarn', 'pnpm',
|
|
19
|
-
'jest', 'vitest', 'cypress', 'playwright',
|
|
20
|
-
'tailwind', 'bootstrap', 'zod', 'trpc', 'graphql', 'rest',
|
|
21
|
-
'jwt', 'oauth', 'openai', 'anthropic', 'langchain',
|
|
22
|
-
'git', 'github', 'gitlab', 'eslint', 'prettier',
|
|
23
|
-
'zustand', 'redux', 'pinia', 'mobx', 'jotai', 'recoil',
|
|
24
|
-
'storybook', 'turborepo', 'nx',
|
|
25
|
-
'microservices', 'serverless', 'monolith', 'ssr', 'ssg', 'spa', 'pwa', 'mcp', 'rag'
|
|
26
|
-
])
|
|
27
|
-
|
|
28
|
-
const TECH_ALIASES: Record<string, string> = {
|
|
29
|
-
'ts': 'typescript', 'js': 'javascript', 'py': 'python',
|
|
30
|
-
'react.js': 'react', 'reactjs': 'react', 'vue.js': 'vue', 'vuejs': 'vue',
|
|
31
|
-
'next.js': 'nextjs', 'nuxt.js': 'nuxt', 'nest.js': 'nestjs',
|
|
32
|
-
'express.js': 'express', 'node.js': 'node', 'nodejs': 'node',
|
|
33
|
-
'mongo': 'mongodb', 'pg': 'postgresql', 'k8s': 'kubernetes',
|
|
34
|
-
'tailwindcss': 'tailwind', 'tailwind-css': 'tailwind',
|
|
35
|
-
'gql': 'graphql', 'golang': 'go',
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/** Phase 20: Default privacy ignore paths for sensitive files */
|
|
39
|
-
const DEFAULT_IGNORE_PATHS = [
|
|
40
|
-
'node_modules/**', '.env*', '*.key', '*.pem', '*.p12', '*.pfx',
|
|
41
|
-
'.git/**', 'dist/**', 'build/**', '*.secret', 'credentials*',
|
|
42
|
-
'.aws/**', '.ssh/**'
|
|
43
|
-
]
|
|
44
|
-
|
|
45
|
-
export class BrainCapture {
|
|
46
|
-
private classifier: PassiveClassifier
|
|
47
|
-
private config: HooksConfig
|
|
48
|
-
|
|
49
|
-
constructor(config?: Partial<HooksConfig>) {
|
|
50
|
-
this.config = {
|
|
51
|
-
enabled: config?.enabled ?? true,
|
|
52
|
-
capture: {
|
|
53
|
-
toolUse: config?.capture?.toolUse ?? true,
|
|
54
|
-
fileEdits: config?.capture?.fileEdits ?? true,
|
|
55
|
-
bashCommands: config?.capture?.bashCommands ?? true,
|
|
56
|
-
userMessages: config?.capture?.userMessages ?? true,
|
|
57
|
-
},
|
|
58
|
-
privacy: {
|
|
59
|
-
ignorePaths: config?.privacy?.ignorePaths ?? DEFAULT_IGNORE_PATHS,
|
|
60
|
-
ignoreProjects: config?.privacy?.ignoreProjects ?? [],
|
|
61
|
-
minConfidence: config?.privacy?.minConfidence ?? 0.7,
|
|
62
|
-
},
|
|
63
|
-
sessions: {
|
|
64
|
-
enabled: config?.sessions?.enabled ?? true,
|
|
65
|
-
idleTimeoutMinutes: config?.sessions?.idleTimeoutMinutes ?? 30,
|
|
66
|
-
minEventsForSummary: config?.sessions?.minEventsForSummary ?? 3,
|
|
67
|
-
},
|
|
68
|
-
deduplication: {
|
|
69
|
-
skipThreshold: config?.deduplication?.skipThreshold ?? 0.95,
|
|
70
|
-
mergeThreshold: config?.deduplication?.mergeThreshold ?? 0.85,
|
|
71
|
-
},
|
|
72
|
-
}
|
|
73
|
-
this.classifier = new PassiveClassifier()
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Process a hook input event.
|
|
78
|
-
* Returns captured knowledge or null if nothing worth capturing.
|
|
79
|
-
*/
|
|
80
|
-
process(input: HookInput): CapturedKnowledge | null {
|
|
81
|
-
if (!this.config.enabled) return null
|
|
82
|
-
|
|
83
|
-
// Privacy: check ignored paths
|
|
84
|
-
if (this.isPathIgnored(input.cwd)) return null
|
|
85
|
-
|
|
86
|
-
// Check capture toggles by tool type
|
|
87
|
-
if (!this.shouldCapture(input)) return null
|
|
88
|
-
|
|
89
|
-
// Classify tool output
|
|
90
|
-
const knowledge = this.classifier.classify(input)
|
|
91
|
-
if (!knowledge) return null
|
|
92
|
-
|
|
93
|
-
// Privacy: check ignored projects
|
|
94
|
-
if (knowledge.project && this.config.privacy.ignoreProjects.includes(knowledge.project)) {
|
|
95
|
-
return null
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// Enrich with additional technology detection from content
|
|
99
|
-
knowledge.technologies = this.enrichTechnologies(knowledge)
|
|
100
|
-
|
|
101
|
-
// Filter by minimum confidence
|
|
102
|
-
if (knowledge.confidence < this.config.privacy.minConfidence) {
|
|
103
|
-
return null
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
return knowledge
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/** Check if a path matches any ignore pattern */
|
|
110
|
-
private isPathIgnored(cwd: string): boolean {
|
|
111
|
-
if (!cwd || this.config.privacy.ignorePaths.length === 0) return false
|
|
112
|
-
const lowerCwd = cwd.toLowerCase()
|
|
113
|
-
return this.config.privacy.ignorePaths.some(pattern => {
|
|
114
|
-
const lowerPattern = pattern.toLowerCase()
|
|
115
|
-
// Simple glob: just check if pattern appears in path
|
|
116
|
-
if (lowerPattern.includes('*')) {
|
|
117
|
-
const regex = new RegExp(lowerPattern.replace(/\*/g, '.*'))
|
|
118
|
-
return regex.test(lowerCwd)
|
|
119
|
-
}
|
|
120
|
-
return lowerCwd.includes(lowerPattern)
|
|
121
|
-
})
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/** Check if this tool type should be captured based on config */
|
|
125
|
-
private shouldCapture(input: HookInput): boolean {
|
|
126
|
-
const toolName = input.tool_name?.toLowerCase()
|
|
127
|
-
if (!toolName) return false
|
|
128
|
-
|
|
129
|
-
switch (toolName) {
|
|
130
|
-
case 'edit':
|
|
131
|
-
case 'write':
|
|
132
|
-
return this.config.capture.fileEdits
|
|
133
|
-
case 'bash':
|
|
134
|
-
return this.config.capture.bashCommands
|
|
135
|
-
default:
|
|
136
|
-
return this.config.capture.toolUse
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/** Enrich technologies by scanning content text for known tech names */
|
|
141
|
-
private enrichTechnologies(knowledge: CapturedKnowledge): string[] {
|
|
142
|
-
const existing = new Set(knowledge.technologies)
|
|
143
|
-
const lower = knowledge.content.toLowerCase()
|
|
144
|
-
const words = lower.split(/[\s,;:()[\]{}"'`|/\\]+/)
|
|
145
|
-
|
|
146
|
-
for (const word of words) {
|
|
147
|
-
const cleaned = word.replace(/^[^a-z0-9]+|[^a-z0-9]+$/g, '')
|
|
148
|
-
if (cleaned.length < 2) continue
|
|
149
|
-
|
|
150
|
-
if (COMMON_TECH.has(cleaned) && !existing.has(cleaned)) {
|
|
151
|
-
existing.add(cleaned)
|
|
152
|
-
}
|
|
153
|
-
const alias = TECH_ALIASES[cleaned]
|
|
154
|
-
if (alias && !existing.has(alias)) {
|
|
155
|
-
existing.add(alias)
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
// Check multi-word aliases
|
|
160
|
-
for (const [alias, normalized] of Object.entries(TECH_ALIASES)) {
|
|
161
|
-
if ((alias.includes('.') || alias.includes('-')) && lower.includes(alias) && !existing.has(normalized)) {
|
|
162
|
-
existing.add(normalized)
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
return Array.from(existing)
|
|
167
|
-
}
|
|
168
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Phase 17: Capture Engine
|
|
3
|
+
* Orchestrates passive classification + entity extraction with privacy filters.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { HookInput, CapturedKnowledge } from './types'
|
|
7
|
+
import type { HooksConfig } from '@/config/schema'
|
|
8
|
+
import { PassiveClassifier } from './passive-classifier'
|
|
9
|
+
|
|
10
|
+
// Reuse tech dictionaries from entity extractor for enrichment
|
|
11
|
+
const COMMON_TECH: Set<string> = new Set([
|
|
12
|
+
'typescript', 'javascript', 'python', 'rust', 'go', 'java', 'ruby', 'php', 'swift', 'kotlin',
|
|
13
|
+
'react', 'vue', 'angular', 'svelte', 'nextjs', 'nuxt', 'remix', 'astro', 'solid',
|
|
14
|
+
'express', 'fastify', 'hono', 'nestjs', 'django', 'flask', 'fastapi', 'rails', 'spring',
|
|
15
|
+
'mongodb', 'redis', 'postgresql', 'postgres', 'mysql', 'sqlite', 'dynamodb', 'firebase', 'supabase',
|
|
16
|
+
'prisma', 'drizzle', 'typeorm', 'sequelize', 'chromadb', 'pinecone',
|
|
17
|
+
'docker', 'kubernetes', 'aws', 'gcp', 'azure', 'vercel', 'netlify',
|
|
18
|
+
'webpack', 'vite', 'esbuild', 'bun', 'deno', 'node', 'npm', 'yarn', 'pnpm',
|
|
19
|
+
'jest', 'vitest', 'cypress', 'playwright',
|
|
20
|
+
'tailwind', 'bootstrap', 'zod', 'trpc', 'graphql', 'rest',
|
|
21
|
+
'jwt', 'oauth', 'openai', 'anthropic', 'langchain',
|
|
22
|
+
'git', 'github', 'gitlab', 'eslint', 'prettier',
|
|
23
|
+
'zustand', 'redux', 'pinia', 'mobx', 'jotai', 'recoil',
|
|
24
|
+
'storybook', 'turborepo', 'nx',
|
|
25
|
+
'microservices', 'serverless', 'monolith', 'ssr', 'ssg', 'spa', 'pwa', 'mcp', 'rag'
|
|
26
|
+
])
|
|
27
|
+
|
|
28
|
+
const TECH_ALIASES: Record<string, string> = {
|
|
29
|
+
'ts': 'typescript', 'js': 'javascript', 'py': 'python',
|
|
30
|
+
'react.js': 'react', 'reactjs': 'react', 'vue.js': 'vue', 'vuejs': 'vue',
|
|
31
|
+
'next.js': 'nextjs', 'nuxt.js': 'nuxt', 'nest.js': 'nestjs',
|
|
32
|
+
'express.js': 'express', 'node.js': 'node', 'nodejs': 'node',
|
|
33
|
+
'mongo': 'mongodb', 'pg': 'postgresql', 'k8s': 'kubernetes',
|
|
34
|
+
'tailwindcss': 'tailwind', 'tailwind-css': 'tailwind',
|
|
35
|
+
'gql': 'graphql', 'golang': 'go',
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Phase 20: Default privacy ignore paths for sensitive files */
|
|
39
|
+
const DEFAULT_IGNORE_PATHS = [
|
|
40
|
+
'node_modules/**', '.env*', '*.key', '*.pem', '*.p12', '*.pfx',
|
|
41
|
+
'.git/**', 'dist/**', 'build/**', '*.secret', 'credentials*',
|
|
42
|
+
'.aws/**', '.ssh/**'
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
export class BrainCapture {
|
|
46
|
+
private classifier: PassiveClassifier
|
|
47
|
+
private config: HooksConfig
|
|
48
|
+
|
|
49
|
+
constructor(config?: Partial<HooksConfig>) {
|
|
50
|
+
this.config = {
|
|
51
|
+
enabled: config?.enabled ?? true,
|
|
52
|
+
capture: {
|
|
53
|
+
toolUse: config?.capture?.toolUse ?? true,
|
|
54
|
+
fileEdits: config?.capture?.fileEdits ?? true,
|
|
55
|
+
bashCommands: config?.capture?.bashCommands ?? true,
|
|
56
|
+
userMessages: config?.capture?.userMessages ?? true,
|
|
57
|
+
},
|
|
58
|
+
privacy: {
|
|
59
|
+
ignorePaths: config?.privacy?.ignorePaths ?? DEFAULT_IGNORE_PATHS,
|
|
60
|
+
ignoreProjects: config?.privacy?.ignoreProjects ?? [],
|
|
61
|
+
minConfidence: config?.privacy?.minConfidence ?? 0.7,
|
|
62
|
+
},
|
|
63
|
+
sessions: {
|
|
64
|
+
enabled: config?.sessions?.enabled ?? true,
|
|
65
|
+
idleTimeoutMinutes: config?.sessions?.idleTimeoutMinutes ?? 30,
|
|
66
|
+
minEventsForSummary: config?.sessions?.minEventsForSummary ?? 3,
|
|
67
|
+
},
|
|
68
|
+
deduplication: {
|
|
69
|
+
skipThreshold: config?.deduplication?.skipThreshold ?? 0.95,
|
|
70
|
+
mergeThreshold: config?.deduplication?.mergeThreshold ?? 0.85,
|
|
71
|
+
},
|
|
72
|
+
}
|
|
73
|
+
this.classifier = new PassiveClassifier()
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Process a hook input event.
|
|
78
|
+
* Returns captured knowledge or null if nothing worth capturing.
|
|
79
|
+
*/
|
|
80
|
+
process(input: HookInput): CapturedKnowledge | null {
|
|
81
|
+
if (!this.config.enabled) return null
|
|
82
|
+
|
|
83
|
+
// Privacy: check ignored paths
|
|
84
|
+
if (this.isPathIgnored(input.cwd)) return null
|
|
85
|
+
|
|
86
|
+
// Check capture toggles by tool type
|
|
87
|
+
if (!this.shouldCapture(input)) return null
|
|
88
|
+
|
|
89
|
+
// Classify tool output
|
|
90
|
+
const knowledge = this.classifier.classify(input)
|
|
91
|
+
if (!knowledge) return null
|
|
92
|
+
|
|
93
|
+
// Privacy: check ignored projects
|
|
94
|
+
if (knowledge.project && this.config.privacy.ignoreProjects.includes(knowledge.project)) {
|
|
95
|
+
return null
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Enrich with additional technology detection from content
|
|
99
|
+
knowledge.technologies = this.enrichTechnologies(knowledge)
|
|
100
|
+
|
|
101
|
+
// Filter by minimum confidence
|
|
102
|
+
if (knowledge.confidence < this.config.privacy.minConfidence) {
|
|
103
|
+
return null
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return knowledge
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Check if a path matches any ignore pattern */
|
|
110
|
+
private isPathIgnored(cwd: string): boolean {
|
|
111
|
+
if (!cwd || this.config.privacy.ignorePaths.length === 0) return false
|
|
112
|
+
const lowerCwd = cwd.toLowerCase()
|
|
113
|
+
return this.config.privacy.ignorePaths.some(pattern => {
|
|
114
|
+
const lowerPattern = pattern.toLowerCase()
|
|
115
|
+
// Simple glob: just check if pattern appears in path
|
|
116
|
+
if (lowerPattern.includes('*')) {
|
|
117
|
+
const regex = new RegExp(lowerPattern.replace(/\*/g, '.*'))
|
|
118
|
+
return regex.test(lowerCwd)
|
|
119
|
+
}
|
|
120
|
+
return lowerCwd.includes(lowerPattern)
|
|
121
|
+
})
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** Check if this tool type should be captured based on config */
|
|
125
|
+
private shouldCapture(input: HookInput): boolean {
|
|
126
|
+
const toolName = input.tool_name?.toLowerCase()
|
|
127
|
+
if (!toolName) return false
|
|
128
|
+
|
|
129
|
+
switch (toolName) {
|
|
130
|
+
case 'edit':
|
|
131
|
+
case 'write':
|
|
132
|
+
return this.config.capture.fileEdits
|
|
133
|
+
case 'bash':
|
|
134
|
+
return this.config.capture.bashCommands
|
|
135
|
+
default:
|
|
136
|
+
return this.config.capture.toolUse
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** Enrich technologies by scanning content text for known tech names */
|
|
141
|
+
private enrichTechnologies(knowledge: CapturedKnowledge): string[] {
|
|
142
|
+
const existing = new Set(knowledge.technologies)
|
|
143
|
+
const lower = knowledge.content.toLowerCase()
|
|
144
|
+
const words = lower.split(/[\s,;:()[\]{}"'`|/\\]+/)
|
|
145
|
+
|
|
146
|
+
for (const word of words) {
|
|
147
|
+
const cleaned = word.replace(/^[^a-z0-9]+|[^a-z0-9]+$/g, '')
|
|
148
|
+
if (cleaned.length < 2) continue
|
|
149
|
+
|
|
150
|
+
if (COMMON_TECH.has(cleaned) && !existing.has(cleaned)) {
|
|
151
|
+
existing.add(cleaned)
|
|
152
|
+
}
|
|
153
|
+
const alias = TECH_ALIASES[cleaned]
|
|
154
|
+
if (alias && !existing.has(alias)) {
|
|
155
|
+
existing.add(alias)
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Check multi-word aliases
|
|
160
|
+
for (const [alias, normalized] of Object.entries(TECH_ALIASES)) {
|
|
161
|
+
if ((alias.includes('.') || alias.includes('-')) && lower.includes(alias) && !existing.has(normalized)) {
|
|
162
|
+
existing.add(normalized)
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return Array.from(existing)
|
|
167
|
+
}
|
|
168
|
+
}
|