@timmeck/brain 1.9.0 → 2.1.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 +33 -0
- package/brain.log +3876 -0
- package/{src/cli/commands/dashboard.ts → dashboard.html} +694 -807
- package/dist/api/server.d.ts +4 -18
- package/dist/api/server.js +4 -173
- package/dist/api/server.js.map +1 -1
- package/dist/brain.d.ts +2 -0
- package/dist/brain.js +15 -4
- package/dist/brain.js.map +1 -1
- package/dist/cli/colors.d.ts +4 -25
- package/dist/cli/colors.js +3 -89
- package/dist/cli/colors.js.map +1 -1
- package/dist/cli/commands/dashboard.js +21 -2
- package/dist/cli/commands/dashboard.js.map +1 -1
- package/dist/cli/commands/peers.d.ts +2 -0
- package/dist/cli/commands/peers.js +38 -0
- package/dist/cli/commands/peers.js.map +1 -0
- package/dist/cli/commands/status.js +0 -1
- package/dist/cli/commands/status.js.map +1 -1
- package/dist/config.js +2 -29
- package/dist/config.js.map +1 -1
- package/dist/db/connection.d.ts +1 -2
- package/dist/db/connection.js +1 -18
- package/dist/db/connection.js.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/ipc/__tests__/protocol.test.d.ts +1 -0
- package/dist/ipc/__tests__/protocol.test.js +117 -0
- package/dist/ipc/__tests__/protocol.test.js.map +1 -0
- package/dist/ipc/client.d.ts +1 -16
- package/dist/ipc/client.js +1 -100
- package/dist/ipc/client.js.map +1 -1
- package/dist/ipc/protocol.d.ts +1 -8
- package/dist/ipc/protocol.js +1 -28
- package/dist/ipc/protocol.js.map +1 -1
- package/dist/ipc/router.d.ts +2 -0
- package/dist/ipc/router.js +30 -0
- package/dist/ipc/router.js.map +1 -1
- package/dist/ipc/server.d.ts +1 -22
- package/dist/ipc/server.js +1 -163
- package/dist/ipc/server.js.map +1 -1
- package/dist/learning/confidence-scorer.d.ts +2 -5
- package/dist/learning/confidence-scorer.js +4 -19
- package/dist/learning/confidence-scorer.js.map +1 -1
- package/dist/learning/decay.js +2 -3
- package/dist/learning/decay.js.map +1 -1
- package/dist/learning/learning-engine.d.ts +2 -5
- package/dist/learning/learning-engine.js +3 -15
- package/dist/learning/learning-engine.js.map +1 -1
- package/dist/mcp/http-server.d.ts +1 -7
- package/dist/mcp/http-server.js +6 -117
- package/dist/mcp/http-server.js.map +1 -1
- package/dist/mcp/server.js +5 -61
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/tools.js +36 -0
- package/dist/mcp/tools.js.map +1 -1
- package/dist/parsing/parsers/compiler.js +1 -1
- package/dist/parsing/parsers/compiler.js.map +1 -1
- package/dist/research/research-engine.d.ts +2 -6
- package/dist/research/research-engine.js +3 -23
- package/dist/research/research-engine.js.map +1 -1
- package/dist/services/synapse.service.d.ts +3 -3
- package/dist/signals/__tests__/fingerprint.test.d.ts +1 -0
- package/dist/signals/__tests__/fingerprint.test.js +118 -0
- package/dist/signals/__tests__/fingerprint.test.js.map +1 -0
- package/dist/synapses/activation.d.ts +3 -13
- package/dist/synapses/activation.js +2 -49
- package/dist/synapses/activation.js.map +1 -1
- package/dist/synapses/decay.d.ts +2 -11
- package/dist/synapses/decay.js +2 -26
- package/dist/synapses/decay.js.map +1 -1
- package/dist/synapses/hebbian.d.ts +2 -13
- package/dist/synapses/hebbian.js +2 -35
- package/dist/synapses/hebbian.js.map +1 -1
- package/dist/synapses/pathfinder.d.ts +2 -14
- package/dist/synapses/pathfinder.js +2 -49
- package/dist/synapses/pathfinder.js.map +1 -1
- package/dist/synapses/synapse-manager.d.ts +7 -23
- package/dist/synapses/synapse-manager.js +6 -63
- package/dist/synapses/synapse-manager.js.map +1 -1
- package/dist/types/ipc.types.d.ts +1 -11
- package/dist/utils/__tests__/hash.test.d.ts +1 -0
- package/dist/utils/__tests__/hash.test.js +32 -0
- package/dist/utils/__tests__/hash.test.js.map +1 -0
- package/dist/utils/__tests__/paths.test.d.ts +1 -0
- package/dist/utils/__tests__/paths.test.js +75 -0
- package/dist/utils/__tests__/paths.test.js.map +1 -0
- package/dist/utils/events.d.ts +4 -8
- package/dist/utils/events.js +2 -14
- package/dist/utils/events.js.map +1 -1
- package/dist/utils/hash.d.ts +1 -1
- package/dist/utils/hash.js +1 -4
- package/dist/utils/hash.js.map +1 -1
- package/dist/utils/logger.d.ts +3 -2
- package/dist/utils/logger.js +8 -35
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/paths.d.ts +2 -1
- package/dist/utils/paths.js +4 -13
- package/dist/utils/paths.js.map +1 -1
- package/eslint.config.js +14 -0
- package/package.json +56 -49
- package/BRAIN_PLAN.md +0 -3324
- package/reddit_post.md +0 -45
- package/src/api/server.ts +0 -395
- package/src/brain.ts +0 -313
- package/src/cli/colors.ts +0 -116
- package/src/cli/commands/config.ts +0 -169
- package/src/cli/commands/doctor.ts +0 -124
- package/src/cli/commands/explain.ts +0 -83
- package/src/cli/commands/export.ts +0 -31
- package/src/cli/commands/import.ts +0 -199
- package/src/cli/commands/insights.ts +0 -65
- package/src/cli/commands/learn.ts +0 -24
- package/src/cli/commands/modules.ts +0 -53
- package/src/cli/commands/network.ts +0 -67
- package/src/cli/commands/projects.ts +0 -42
- package/src/cli/commands/query.ts +0 -120
- package/src/cli/commands/start.ts +0 -105
- package/src/cli/commands/status.ts +0 -75
- package/src/cli/commands/stop.ts +0 -34
- package/src/cli/ipc-helper.ts +0 -22
- package/src/cli/update-check.ts +0 -63
- package/src/code/analyzer.ts +0 -117
- package/src/code/fingerprint.ts +0 -87
- package/src/code/matcher.ts +0 -129
- package/src/code/parsers/generic.ts +0 -29
- package/src/code/parsers/python.ts +0 -54
- package/src/code/parsers/typescript.ts +0 -65
- package/src/code/registry.ts +0 -60
- package/src/code/scorer.ts +0 -120
- package/src/config.ts +0 -135
- package/src/dashboard/server.ts +0 -142
- package/src/db/connection.ts +0 -22
- package/src/db/migrations/001_core_schema.ts +0 -120
- package/src/db/migrations/002_learning_schema.ts +0 -38
- package/src/db/migrations/003_code_schema.ts +0 -53
- package/src/db/migrations/004_synapses_schema.ts +0 -57
- package/src/db/migrations/005_fts_indexes.ts +0 -78
- package/src/db/migrations/006_synapses_phase3.ts +0 -17
- package/src/db/migrations/007_feedback.ts +0 -13
- package/src/db/migrations/008_git_integration.ts +0 -38
- package/src/db/migrations/009_embeddings.ts +0 -8
- package/src/db/migrations/index.ts +0 -70
- package/src/db/repositories/antipattern.repository.ts +0 -66
- package/src/db/repositories/code-module.repository.ts +0 -142
- package/src/db/repositories/error.repository.ts +0 -189
- package/src/db/repositories/insight.repository.ts +0 -99
- package/src/db/repositories/notification.repository.ts +0 -66
- package/src/db/repositories/project.repository.ts +0 -93
- package/src/db/repositories/rule.repository.ts +0 -108
- package/src/db/repositories/solution.repository.ts +0 -154
- package/src/db/repositories/synapse.repository.ts +0 -163
- package/src/db/repositories/terminal.repository.ts +0 -101
- package/src/embeddings/engine.ts +0 -238
- package/src/hooks/post-tool-use.ts +0 -92
- package/src/hooks/post-write.ts +0 -129
- package/src/index.ts +0 -63
- package/src/ipc/client.ts +0 -118
- package/src/ipc/protocol.ts +0 -35
- package/src/ipc/router.ts +0 -133
- package/src/ipc/server.ts +0 -176
- package/src/learning/confidence-scorer.ts +0 -80
- package/src/learning/decay.ts +0 -46
- package/src/learning/learning-engine.ts +0 -170
- package/src/learning/pattern-extractor.ts +0 -90
- package/src/learning/rule-generator.ts +0 -74
- package/src/main.rs:10:5 +0 -0
- package/src/matching/error-matcher.ts +0 -166
- package/src/matching/fingerprint.ts +0 -34
- package/src/matching/similarity.ts +0 -61
- package/src/matching/tfidf.ts +0 -74
- package/src/matching/tokenizer.ts +0 -41
- package/src/mcp/auto-detect.ts +0 -93
- package/src/mcp/http-server.ts +0 -140
- package/src/mcp/server.ts +0 -73
- package/src/mcp/tools.ts +0 -328
- package/src/parsing/error-parser.ts +0 -28
- package/src/parsing/parsers/compiler.ts +0 -93
- package/src/parsing/parsers/generic.ts +0 -28
- package/src/parsing/parsers/go.ts +0 -97
- package/src/parsing/parsers/node.ts +0 -69
- package/src/parsing/parsers/python.ts +0 -62
- package/src/parsing/parsers/rust.ts +0 -50
- package/src/parsing/parsers/shell.ts +0 -42
- package/src/parsing/types.ts +0 -47
- package/src/research/gap-analyzer.ts +0 -135
- package/src/research/insight-generator.ts +0 -123
- package/src/research/research-engine.ts +0 -116
- package/src/research/synergy-detector.ts +0 -126
- package/src/research/template-extractor.ts +0 -130
- package/src/research/trend-analyzer.ts +0 -127
- package/src/services/analytics.service.ts +0 -226
- package/src/services/code.service.ts +0 -271
- package/src/services/error.service.ts +0 -266
- package/src/services/git.service.ts +0 -132
- package/src/services/notification.service.ts +0 -41
- package/src/services/prevention.service.ts +0 -159
- package/src/services/research.service.ts +0 -98
- package/src/services/solution.service.ts +0 -174
- package/src/services/synapse.service.ts +0 -59
- package/src/services/terminal.service.ts +0 -81
- package/src/synapses/activation.ts +0 -80
- package/src/synapses/decay.ts +0 -38
- package/src/synapses/hebbian.ts +0 -69
- package/src/synapses/pathfinder.ts +0 -81
- package/src/synapses/synapse-manager.ts +0 -113
- package/src/types/code.types.ts +0 -52
- package/src/types/config.types.ts +0 -103
- package/src/types/error.types.ts +0 -67
- package/src/types/ipc.types.ts +0 -8
- package/src/types/mcp.types.ts +0 -53
- package/src/types/research.types.ts +0 -28
- package/src/types/solution.types.ts +0 -30
- package/src/types/synapse.types.ts +0 -50
- package/src/utils/events.ts +0 -45
- package/src/utils/hash.ts +0 -5
- package/src/utils/logger.ts +0 -48
- package/src/utils/paths.ts +0 -19
- package/tests/e2e/test_code_intelligence.py +0 -1015
- package/tests/e2e/test_error_memory.py +0 -451
- package/tests/e2e/test_full_integration.py +0 -534
- package/tests/fixtures/code-modules/modules.ts +0 -83
- package/tests/fixtures/errors/go.ts +0 -9
- package/tests/fixtures/errors/node.ts +0 -24
- package/tests/fixtures/errors/python.ts +0 -21
- package/tests/fixtures/errors/rust.ts +0 -25
- package/tests/fixtures/errors/shell.ts +0 -15
- package/tests/fixtures/solutions/solutions.ts +0 -27
- package/tests/helpers/setup-db.ts +0 -52
- package/tests/integration/code-flow.test.ts +0 -86
- package/tests/integration/error-flow.test.ts +0 -83
- package/tests/integration/ipc-flow.test.ts +0 -166
- package/tests/integration/learning-cycle.test.ts +0 -82
- package/tests/integration/synapse-flow.test.ts +0 -117
- package/tests/unit/code/analyzer.test.ts +0 -58
- package/tests/unit/code/fingerprint.test.ts +0 -51
- package/tests/unit/code/scorer.test.ts +0 -55
- package/tests/unit/learning/confidence-scorer.test.ts +0 -60
- package/tests/unit/learning/decay.test.ts +0 -45
- package/tests/unit/learning/pattern-extractor.test.ts +0 -50
- package/tests/unit/matching/error-matcher.test.ts +0 -69
- package/tests/unit/matching/fingerprint.test.ts +0 -47
- package/tests/unit/matching/similarity.test.ts +0 -65
- package/tests/unit/matching/tfidf.test.ts +0 -71
- package/tests/unit/matching/tokenizer.test.ts +0 -83
- package/tests/unit/parsing/parsers.test.ts +0 -113
- package/tests/unit/research/gap-analyzer.test.ts +0 -45
- package/tests/unit/research/trend-analyzer.test.ts +0 -45
- package/tests/unit/synapses/activation.test.ts +0 -80
- package/tests/unit/synapses/decay.test.ts +0 -27
- package/tests/unit/synapses/hebbian.test.ts +0 -96
- package/tests/unit/synapses/pathfinder.test.ts +0 -72
- package/tsconfig.json +0 -18
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import type { LearningConfig } from '../types/config.types.js';
|
|
2
|
-
import type { RuleRepository } from '../db/repositories/rule.repository.js';
|
|
3
|
-
import type { ErrorPattern } from './pattern-extractor.js';
|
|
4
|
-
import { getLogger } from '../utils/logger.js';
|
|
5
|
-
|
|
6
|
-
export interface GeneratedRule {
|
|
7
|
-
pattern: string;
|
|
8
|
-
action: string;
|
|
9
|
-
description: string;
|
|
10
|
-
confidence: number;
|
|
11
|
-
sourceErrorIds: number[];
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Generate prevention rules from extracted patterns.
|
|
16
|
-
*/
|
|
17
|
-
export function generateRules(
|
|
18
|
-
patterns: ErrorPattern[],
|
|
19
|
-
config: LearningConfig,
|
|
20
|
-
): GeneratedRule[] {
|
|
21
|
-
return patterns
|
|
22
|
-
.filter(p =>
|
|
23
|
-
p.occurrences >= config.minOccurrences &&
|
|
24
|
-
p.confidence >= config.minConfidence,
|
|
25
|
-
)
|
|
26
|
-
.map(pattern => ({
|
|
27
|
-
pattern: pattern.messageRegex,
|
|
28
|
-
action: pattern.confidence >= 0.90
|
|
29
|
-
? `Auto-fix available for ${pattern.errorType}`
|
|
30
|
-
: `Suggestion: check ${pattern.errorType} pattern (${pattern.occurrences} occurrences)`,
|
|
31
|
-
description: `Auto-generated from ${pattern.occurrences} occurrences of ${pattern.errorType}`,
|
|
32
|
-
confidence: pattern.confidence,
|
|
33
|
-
sourceErrorIds: pattern.errorIds,
|
|
34
|
-
}));
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Persist generated rules to the database.
|
|
39
|
-
*/
|
|
40
|
-
export function persistRules(
|
|
41
|
-
rules: GeneratedRule[],
|
|
42
|
-
ruleRepo: RuleRepository,
|
|
43
|
-
projectId?: number,
|
|
44
|
-
): number {
|
|
45
|
-
const logger = getLogger();
|
|
46
|
-
let created = 0;
|
|
47
|
-
|
|
48
|
-
for (const rule of rules) {
|
|
49
|
-
// Check if similar rule already exists
|
|
50
|
-
const existing = ruleRepo.findByPattern(rule.pattern);
|
|
51
|
-
if (existing.length > 0) {
|
|
52
|
-
// Update confidence of existing rule
|
|
53
|
-
const best = existing[0]!;
|
|
54
|
-
if (rule.confidence > best.confidence) {
|
|
55
|
-
ruleRepo.update(best.id, { confidence: rule.confidence });
|
|
56
|
-
}
|
|
57
|
-
continue;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
ruleRepo.create({
|
|
61
|
-
pattern: rule.pattern,
|
|
62
|
-
action: rule.action,
|
|
63
|
-
description: rule.description,
|
|
64
|
-
confidence: rule.confidence,
|
|
65
|
-
occurrences: 0,
|
|
66
|
-
active: 1,
|
|
67
|
-
project_id: projectId ?? null,
|
|
68
|
-
});
|
|
69
|
-
created++;
|
|
70
|
-
logger.info(`New rule generated: ${rule.pattern.substring(0, 50)}...`);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
return created;
|
|
74
|
-
}
|
package/src/main.rs:10:5
DELETED
|
File without changes
|
|
@@ -1,166 +0,0 @@
|
|
|
1
|
-
import type { ErrorRecord } from '../types/error.types.js';
|
|
2
|
-
import { tokenize } from './tokenizer.js';
|
|
3
|
-
import { cosineSimilarity, jaccardSimilarity, levenshteinDistance } from './similarity.js';
|
|
4
|
-
|
|
5
|
-
export interface SignalScore {
|
|
6
|
-
signal: string;
|
|
7
|
-
score: number;
|
|
8
|
-
weighted: number;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export interface MatchResult {
|
|
12
|
-
errorId: number;
|
|
13
|
-
score: number;
|
|
14
|
-
signals: SignalScore[];
|
|
15
|
-
isStrong: boolean;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
interface MatchSignal {
|
|
19
|
-
name: string;
|
|
20
|
-
weight: number;
|
|
21
|
-
compute: (a: ErrorRecord, b: ErrorRecord) => number;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
// Base signals (used when vector search is NOT available)
|
|
25
|
-
const SIGNALS_BASE: MatchSignal[] = [
|
|
26
|
-
{ name: 'fingerprint', weight: 0.20, compute: fingerprintMatch },
|
|
27
|
-
{ name: 'message_similarity', weight: 0.25, compute: messageSimilarity },
|
|
28
|
-
{ name: 'type_match', weight: 0.15, compute: typeMatch },
|
|
29
|
-
{ name: 'stack_similarity', weight: 0.15, compute: stackSimilarity },
|
|
30
|
-
{ name: 'file_similarity', weight: 0.12, compute: fileSimilarity },
|
|
31
|
-
{ name: 'context_similarity', weight: 0.13, compute: contextSimilarity },
|
|
32
|
-
];
|
|
33
|
-
|
|
34
|
-
// Hybrid signals (used when vector search IS available — vector gets 20% weight)
|
|
35
|
-
const SIGNALS_HYBRID: MatchSignal[] = [
|
|
36
|
-
{ name: 'fingerprint', weight: 0.25, compute: fingerprintMatch },
|
|
37
|
-
{ name: 'message_similarity', weight: 0.15, compute: messageSimilarity },
|
|
38
|
-
{ name: 'type_match', weight: 0.12, compute: typeMatch },
|
|
39
|
-
{ name: 'stack_similarity', weight: 0.12, compute: stackSimilarity },
|
|
40
|
-
{ name: 'file_similarity', weight: 0.08, compute: fileSimilarity },
|
|
41
|
-
{ name: 'context_similarity', weight: 0.08, compute: contextSimilarity },
|
|
42
|
-
];
|
|
43
|
-
|
|
44
|
-
const VECTOR_WEIGHT = 0.20;
|
|
45
|
-
const MATCH_THRESHOLD = 0.55;
|
|
46
|
-
const STRONG_MATCH_THRESHOLD = 0.90;
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Hybrid error matching: TF-IDF signals + optional vector similarity + synapse boost.
|
|
50
|
-
*
|
|
51
|
-
* @param incoming - The error to match
|
|
52
|
-
* @param candidates - Candidate errors to compare against
|
|
53
|
-
* @param vectorScores - Pre-computed vector similarity scores (errorId → score)
|
|
54
|
-
* @param synapseScores - Pre-computed synapse proximity scores (errorId → score)
|
|
55
|
-
*/
|
|
56
|
-
export function matchError(
|
|
57
|
-
incoming: ErrorRecord,
|
|
58
|
-
candidates: ErrorRecord[],
|
|
59
|
-
vectorScores?: Map<number, number>,
|
|
60
|
-
synapseScores?: Map<number, number>,
|
|
61
|
-
): MatchResult[] {
|
|
62
|
-
const useHybrid = vectorScores && vectorScores.size > 0;
|
|
63
|
-
const useSynapse = synapseScores && synapseScores.size > 0;
|
|
64
|
-
const signals = useHybrid ? SIGNALS_HYBRID : SIGNALS_BASE;
|
|
65
|
-
|
|
66
|
-
return candidates
|
|
67
|
-
.map(candidate => {
|
|
68
|
-
const signalResults = signals.map(signal => {
|
|
69
|
-
const score = signal.compute(incoming, candidate);
|
|
70
|
-
return {
|
|
71
|
-
signal: signal.name,
|
|
72
|
-
score,
|
|
73
|
-
weighted: score * signal.weight,
|
|
74
|
-
};
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
// Add vector similarity signal (if available)
|
|
78
|
-
if (useHybrid) {
|
|
79
|
-
const vectorScore = vectorScores.get(candidate.id) ?? 0;
|
|
80
|
-
signalResults.push({
|
|
81
|
-
signal: 'vector_similarity',
|
|
82
|
-
score: vectorScore,
|
|
83
|
-
weighted: vectorScore * VECTOR_WEIGHT,
|
|
84
|
-
});
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
let totalScore = signalResults.reduce((sum, s) => sum + s.weighted, 0);
|
|
88
|
-
|
|
89
|
-
// Synapse boost: if errors are already connected in the synapse network,
|
|
90
|
-
// give up to 5% bonus (doesn't create false positives, only reinforces)
|
|
91
|
-
if (useSynapse) {
|
|
92
|
-
const synapseScore = synapseScores.get(candidate.id) ?? 0;
|
|
93
|
-
if (synapseScore > 0) {
|
|
94
|
-
const bonus = Math.min(synapseScore * 0.05, 0.05);
|
|
95
|
-
totalScore = Math.min(1.0, totalScore + bonus);
|
|
96
|
-
signalResults.push({
|
|
97
|
-
signal: 'synapse_boost',
|
|
98
|
-
score: synapseScore,
|
|
99
|
-
weighted: bonus,
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
return {
|
|
105
|
-
errorId: candidate.id,
|
|
106
|
-
score: totalScore,
|
|
107
|
-
signals: signalResults,
|
|
108
|
-
isStrong: totalScore >= STRONG_MATCH_THRESHOLD,
|
|
109
|
-
};
|
|
110
|
-
})
|
|
111
|
-
.filter(result => result.score >= MATCH_THRESHOLD)
|
|
112
|
-
.sort((a, b) => b.score - a.score);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
function fingerprintMatch(a: ErrorRecord, b: ErrorRecord): number {
|
|
116
|
-
return a.fingerprint === b.fingerprint ? 1.0 : 0.0;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
function messageSimilarity(a: ErrorRecord, b: ErrorRecord): number {
|
|
120
|
-
const tokensA = tokenize(a.message);
|
|
121
|
-
const tokensB = tokenize(b.message);
|
|
122
|
-
return cosineSimilarity(tokensA, tokensB);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
function typeMatch(a: ErrorRecord, b: ErrorRecord): number {
|
|
126
|
-
return a.type === b.type ? 1.0 : 0.0;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
function stackSimilarity(a: ErrorRecord, b: ErrorRecord): number {
|
|
130
|
-
const rawA = a.raw_output ?? '';
|
|
131
|
-
const rawB = b.raw_output ?? '';
|
|
132
|
-
|
|
133
|
-
const frameRe = /at (?:(.+?) )?\(/g;
|
|
134
|
-
const extractFuncs = (raw: string) => {
|
|
135
|
-
const funcs: string[] = [];
|
|
136
|
-
let m: RegExpExecArray | null;
|
|
137
|
-
const re = new RegExp(frameRe.source, 'g');
|
|
138
|
-
while ((m = re.exec(raw)) !== null) {
|
|
139
|
-
if (m[1]) funcs.push(m[1]);
|
|
140
|
-
}
|
|
141
|
-
return funcs;
|
|
142
|
-
};
|
|
143
|
-
|
|
144
|
-
const funcsA = extractFuncs(rawA);
|
|
145
|
-
const funcsB = extractFuncs(rawB);
|
|
146
|
-
|
|
147
|
-
if (funcsA.length === 0 && funcsB.length === 0) return 0.5;
|
|
148
|
-
return jaccardSimilarity(funcsA, funcsB);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
function fileSimilarity(a: ErrorRecord, b: ErrorRecord): number {
|
|
152
|
-
const pathA = a.file_path ?? '';
|
|
153
|
-
const pathB = b.file_path ?? '';
|
|
154
|
-
if (!pathA || !pathB) return 0.0;
|
|
155
|
-
if (pathA === pathB) return 1.0;
|
|
156
|
-
return levenshteinDistance(pathA, pathB);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
function contextSimilarity(a: ErrorRecord, b: ErrorRecord): number {
|
|
160
|
-
const ctxA = a.context ?? '';
|
|
161
|
-
const ctxB = b.context ?? '';
|
|
162
|
-
if (!ctxA || !ctxB) return 0.0;
|
|
163
|
-
const tokensA = tokenize(ctxA);
|
|
164
|
-
const tokensB = tokenize(ctxB);
|
|
165
|
-
return cosineSimilarity(tokensA, tokensB);
|
|
166
|
-
}
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import path from 'node:path';
|
|
2
|
-
import { sha256 } from '../utils/hash.js';
|
|
3
|
-
import type { StackFrame } from '../parsing/types.js';
|
|
4
|
-
|
|
5
|
-
export function templateMessage(msg: string): string {
|
|
6
|
-
return msg
|
|
7
|
-
.replace(/[A-Z]:\\[\w\-.\\ ]+\.\w+/g, '<PATH>')
|
|
8
|
-
.replace(/\/[\w\-./ ]+\.\w+/g, '<PATH>')
|
|
9
|
-
.replace(/:(\d+):(\d+)/g, ':<LINE>:<COL>')
|
|
10
|
-
.replace(/line \d+/gi, 'line <LINE>')
|
|
11
|
-
.replace(/0x[0-9a-fA-F]+/g, '<ADDR>')
|
|
12
|
-
.replace(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi, '<UUID>')
|
|
13
|
-
.replace(/https?:\/\/[^\s]+/g, '<URL>')
|
|
14
|
-
.replace(/\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}:\d{2}/g, '<TIMESTAMP>')
|
|
15
|
-
// Normalize JS/TS property access patterns so "reading 'map'" ≈ "reading 'forEach'"
|
|
16
|
-
.replace(/\(reading ['"][^'"]*['"]\)/g, "(reading '<PROP>')")
|
|
17
|
-
.replace(/\(writing ['"][^'"]*['"]\)/g, "(writing '<PROP>')")
|
|
18
|
-
// Normalize quoted identifiers (e.g., 'someVar', "someFunc")
|
|
19
|
-
.replace(/['"][a-zA-Z_$][\w$]*['"]/g, "'<IDENT>'");
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export function generateFingerprint(
|
|
23
|
-
errorType: string,
|
|
24
|
-
message: string,
|
|
25
|
-
frames: StackFrame[],
|
|
26
|
-
): string {
|
|
27
|
-
const template = templateMessage(message);
|
|
28
|
-
const topFrames = frames
|
|
29
|
-
.slice(0, 3)
|
|
30
|
-
.map(f => `${f.function_name || '<anon>'}@${path.basename(f.file_path || '<unknown>')}`)
|
|
31
|
-
.join('|');
|
|
32
|
-
const input = `${errorType}::${template}::${topFrames}`;
|
|
33
|
-
return sha256(input);
|
|
34
|
-
}
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
export function levenshteinDistance(a: string, b: string): number {
|
|
2
|
-
if (a === b) return 1.0;
|
|
3
|
-
if (a.length === 0 || b.length === 0) return 0.0;
|
|
4
|
-
|
|
5
|
-
const dp: number[][] = Array(b.length + 1)
|
|
6
|
-
.fill(0)
|
|
7
|
-
.map(() => Array(a.length + 1).fill(0) as number[]);
|
|
8
|
-
|
|
9
|
-
for (let i = 0; i <= a.length; i++) dp[0]![i] = i;
|
|
10
|
-
for (let j = 0; j <= b.length; j++) dp[j]![0] = j;
|
|
11
|
-
|
|
12
|
-
for (let i = 1; i <= b.length; i++) {
|
|
13
|
-
for (let j = 1; j <= a.length; j++) {
|
|
14
|
-
const cost = a[j - 1] === b[i - 1] ? 0 : 1;
|
|
15
|
-
dp[i]![j] = Math.min(
|
|
16
|
-
dp[i - 1]![j]! + 1,
|
|
17
|
-
dp[i]![j - 1]! + 1,
|
|
18
|
-
dp[i - 1]![j - 1]! + cost,
|
|
19
|
-
);
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
return 1 - dp[b.length]![a.length]! / Math.max(a.length, b.length);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export function cosineSimilarity(tokensA: string[], tokensB: string[]): number {
|
|
27
|
-
if (tokensA.length === 0 || tokensB.length === 0) return 0.0;
|
|
28
|
-
|
|
29
|
-
const vocab = new Set([...tokensA, ...tokensB]);
|
|
30
|
-
const vecA = new Map<string, number>();
|
|
31
|
-
const vecB = new Map<string, number>();
|
|
32
|
-
|
|
33
|
-
for (const t of tokensA) vecA.set(t, (vecA.get(t) ?? 0) + 1);
|
|
34
|
-
for (const t of tokensB) vecB.set(t, (vecB.get(t) ?? 0) + 1);
|
|
35
|
-
|
|
36
|
-
let dot = 0;
|
|
37
|
-
let magA = 0;
|
|
38
|
-
let magB = 0;
|
|
39
|
-
|
|
40
|
-
for (const word of vocab) {
|
|
41
|
-
const a = vecA.get(word) ?? 0;
|
|
42
|
-
const b = vecB.get(word) ?? 0;
|
|
43
|
-
dot += a * b;
|
|
44
|
-
magA += a * a;
|
|
45
|
-
magB += b * b;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const denom = Math.sqrt(magA) * Math.sqrt(magB);
|
|
49
|
-
return denom === 0 ? 0 : dot / denom;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export function jaccardSimilarity(tokensA: string[], tokensB: string[]): number {
|
|
53
|
-
if (tokensA.length === 0 && tokensB.length === 0) return 0.0;
|
|
54
|
-
|
|
55
|
-
const setA = new Set(tokensA);
|
|
56
|
-
const setB = new Set(tokensB);
|
|
57
|
-
const intersection = new Set([...setA].filter(x => setB.has(x)));
|
|
58
|
-
const union = new Set([...setA, ...setB]);
|
|
59
|
-
|
|
60
|
-
return union.size === 0 ? 0 : intersection.size / union.size;
|
|
61
|
-
}
|
package/src/matching/tfidf.ts
DELETED
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
export class TfIdfIndex {
|
|
2
|
-
private documents = new Map<number, string[]>();
|
|
3
|
-
private df = new Map<string, number>();
|
|
4
|
-
private idf = new Map<string, number>();
|
|
5
|
-
private documentCount = 0;
|
|
6
|
-
|
|
7
|
-
addDocument(id: number, tokens: string[]): void {
|
|
8
|
-
if (this.documents.has(id)) {
|
|
9
|
-
this.removeDocument(id);
|
|
10
|
-
}
|
|
11
|
-
const unique = new Set(tokens);
|
|
12
|
-
for (const token of unique) {
|
|
13
|
-
this.df.set(token, (this.df.get(token) ?? 0) + 1);
|
|
14
|
-
}
|
|
15
|
-
this.documents.set(id, tokens);
|
|
16
|
-
this.documentCount++;
|
|
17
|
-
this.recomputeIdfForTerms(unique);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
removeDocument(id: number): void {
|
|
21
|
-
const tokens = this.documents.get(id);
|
|
22
|
-
if (!tokens) return;
|
|
23
|
-
|
|
24
|
-
const unique = new Set(tokens);
|
|
25
|
-
for (const token of unique) {
|
|
26
|
-
const count = this.df.get(token) ?? 0;
|
|
27
|
-
if (count <= 1) {
|
|
28
|
-
this.df.delete(token);
|
|
29
|
-
this.idf.delete(token);
|
|
30
|
-
} else {
|
|
31
|
-
this.df.set(token, count - 1);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
this.documents.delete(id);
|
|
35
|
-
this.documentCount--;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
query(tokens: string[], topK: number = 10): Array<{ id: number; score: number }> {
|
|
39
|
-
const scores = new Map<number, number>();
|
|
40
|
-
|
|
41
|
-
for (const token of tokens) {
|
|
42
|
-
const idfVal = this.idf.get(token) ?? 0;
|
|
43
|
-
if (idfVal === 0) continue;
|
|
44
|
-
|
|
45
|
-
for (const [docId, docTokens] of this.documents) {
|
|
46
|
-
const tf = docTokens.filter(t => t === token).length / docTokens.length;
|
|
47
|
-
const score = (scores.get(docId) ?? 0) + tf * idfVal;
|
|
48
|
-
scores.set(docId, score);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
return Array.from(scores.entries())
|
|
53
|
-
.map(([id, score]) => ({ id, score }))
|
|
54
|
-
.sort((a, b) => b.score - a.score)
|
|
55
|
-
.slice(0, topK);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
getDocumentCount(): number {
|
|
59
|
-
return this.documentCount;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
getIdf(): ReadonlyMap<string, number> {
|
|
63
|
-
return this.idf;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
private recomputeIdfForTerms(terms: Set<string>): void {
|
|
67
|
-
for (const term of terms) {
|
|
68
|
-
const dfVal = this.df.get(term) ?? 0;
|
|
69
|
-
if (dfVal > 0 && this.documentCount > 0) {
|
|
70
|
-
this.idf.set(term, Math.log(this.documentCount / dfVal));
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
}
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
const STOPWORDS = new Set([
|
|
2
|
-
'the', 'is', 'are', 'a', 'an', 'and', 'or', 'not', 'in', 'at', 'by', 'for',
|
|
3
|
-
'from', 'of', 'on', 'to', 'with', 'as', 'error', 'exception', 'throw', 'catch',
|
|
4
|
-
'was', 'be', 'been', 'being', 'have', 'has', 'had', 'do', 'does', 'did',
|
|
5
|
-
'will', 'would', 'could', 'should', 'may', 'might', 'can', 'it', 'its',
|
|
6
|
-
'this', 'that', 'these', 'those', 'i', 'we', 'you', 'he', 'she', 'they',
|
|
7
|
-
]);
|
|
8
|
-
|
|
9
|
-
export function splitCamelCase(text: string): string[] {
|
|
10
|
-
return text
|
|
11
|
-
.replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2')
|
|
12
|
-
.replace(/([a-z\d])([A-Z])/g, '$1 $2')
|
|
13
|
-
.split(/\s+/)
|
|
14
|
-
.filter(t => t.length > 0);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export function splitSnakeCase(text: string): string[] {
|
|
18
|
-
return text.split(/[_\-]+/).filter(t => t.length > 0);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export function removeStopwords(tokens: string[]): string[] {
|
|
22
|
-
return tokens.filter(t => !STOPWORDS.has(t.toLowerCase()));
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export function tokenize(text: string): string[] {
|
|
26
|
-
const words = text
|
|
27
|
-
.replace(/[^\w\s]/g, ' ')
|
|
28
|
-
.split(/\s+/)
|
|
29
|
-
.filter(t => t.length > 0);
|
|
30
|
-
|
|
31
|
-
const tokens: string[] = [];
|
|
32
|
-
for (const word of words) {
|
|
33
|
-
tokens.push(...splitCamelCase(word));
|
|
34
|
-
if (word.includes('_') || word.includes('-')) {
|
|
35
|
-
tokens.push(...splitSnakeCase(word));
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const cleaned = removeStopwords(tokens);
|
|
40
|
-
return [...new Set(cleaned.map(t => t.toLowerCase()))];
|
|
41
|
-
}
|
package/src/mcp/auto-detect.ts
DELETED
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
import { IpcClient } from '../ipc/client.js';
|
|
2
|
-
import { getPipeName } from '../utils/paths.js';
|
|
3
|
-
|
|
4
|
-
interface HookInput {
|
|
5
|
-
tool_name: string;
|
|
6
|
-
tool_input: { command?: string; file_path?: string };
|
|
7
|
-
tool_output: string;
|
|
8
|
-
exit_code?: number;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
const ERROR_PATTERNS = [
|
|
12
|
-
/Error:/i,
|
|
13
|
-
/error\[E\d+\]/, // Rust errors
|
|
14
|
-
/Traceback \(most recent call last\)/, // Python
|
|
15
|
-
/FATAL|PANIC/i,
|
|
16
|
-
/npm ERR!/,
|
|
17
|
-
/SyntaxError|TypeError|ReferenceError|RangeError/,
|
|
18
|
-
/ENOENT|EACCES|ECONNREFUSED|ETIMEDOUT/,
|
|
19
|
-
/ModuleNotFoundError|ImportError/,
|
|
20
|
-
/failed to compile/i,
|
|
21
|
-
/BUILD FAILED/i,
|
|
22
|
-
/Cannot find module/,
|
|
23
|
-
/command not found/,
|
|
24
|
-
/Permission denied/,
|
|
25
|
-
];
|
|
26
|
-
|
|
27
|
-
function isError(input: HookInput): boolean {
|
|
28
|
-
if (input.exit_code !== undefined && input.exit_code !== 0) return true;
|
|
29
|
-
return ERROR_PATTERNS.some(p => p.test(input.tool_output));
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function readStdin(): Promise<string> {
|
|
33
|
-
return new Promise((resolve) => {
|
|
34
|
-
let data = '';
|
|
35
|
-
process.stdin.setEncoding('utf8');
|
|
36
|
-
process.stdin.on('data', (chunk) => { data += chunk; });
|
|
37
|
-
process.stdin.on('end', () => resolve(data));
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
async function main(): Promise<void> {
|
|
42
|
-
const raw = await readStdin();
|
|
43
|
-
if (!raw.trim()) return;
|
|
44
|
-
|
|
45
|
-
let input: HookInput;
|
|
46
|
-
try {
|
|
47
|
-
input = JSON.parse(raw);
|
|
48
|
-
} catch {
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
if (!isError(input)) return;
|
|
53
|
-
|
|
54
|
-
const client = new IpcClient(getPipeName(), 3000);
|
|
55
|
-
try {
|
|
56
|
-
await client.connect();
|
|
57
|
-
|
|
58
|
-
// Report error to Brain
|
|
59
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
60
|
-
const result: any = await client.request('error.report', {
|
|
61
|
-
project: 'auto-detected',
|
|
62
|
-
errorOutput: input.tool_output,
|
|
63
|
-
command: input.tool_input.command,
|
|
64
|
-
autoDetected: true,
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
// If Brain knows a solution, output as feedback
|
|
68
|
-
if (result.matches?.length > 0) {
|
|
69
|
-
const best = result.matches[0];
|
|
70
|
-
process.stderr.write(`Brain: Similar error found (#${best.errorId}, ${Math.round(best.score * 100)}% match)\n`);
|
|
71
|
-
if (best.solutions?.length > 0) {
|
|
72
|
-
process.stderr.write(`Brain: Solution available — use brain_query_error to see details\n`);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// Anti-pattern check
|
|
77
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
78
|
-
const antipatterns: any = await client.request('prevention.antipatterns', {
|
|
79
|
-
errorType: '',
|
|
80
|
-
message: input.tool_output,
|
|
81
|
-
});
|
|
82
|
-
if (antipatterns?.length > 0 && antipatterns[0].matched) {
|
|
83
|
-
process.stderr.write(`Brain WARNING: Known anti-pattern detected: ${antipatterns[0].description}\n`);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
} catch {
|
|
87
|
-
// Hook must never block the workflow — silent failure
|
|
88
|
-
} finally {
|
|
89
|
-
client.disconnect();
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
main();
|
package/src/mcp/http-server.ts
DELETED
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
import http from 'node:http';
|
|
2
|
-
import { randomUUID } from 'node:crypto';
|
|
3
|
-
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
4
|
-
import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
|
|
5
|
-
import { getLogger } from '../utils/logger.js';
|
|
6
|
-
import type { IpcRouter } from '../ipc/router.js';
|
|
7
|
-
import { registerToolsDirect } from './tools.js';
|
|
8
|
-
|
|
9
|
-
export class McpHttpServer {
|
|
10
|
-
private server: http.Server | null = null;
|
|
11
|
-
private transports = new Map<string, SSEServerTransport>();
|
|
12
|
-
private logger = getLogger();
|
|
13
|
-
|
|
14
|
-
constructor(
|
|
15
|
-
private port: number,
|
|
16
|
-
private router: IpcRouter,
|
|
17
|
-
) {}
|
|
18
|
-
|
|
19
|
-
start(): void {
|
|
20
|
-
this.server = http.createServer((req, res) => {
|
|
21
|
-
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
22
|
-
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, DELETE, OPTIONS');
|
|
23
|
-
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
|
|
24
|
-
|
|
25
|
-
if (req.method === 'OPTIONS') {
|
|
26
|
-
res.writeHead(204);
|
|
27
|
-
res.end();
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const url = new URL(req.url ?? '/', `http://localhost:${this.port}`);
|
|
32
|
-
|
|
33
|
-
if (url.pathname === '/sse' && req.method === 'GET') {
|
|
34
|
-
this.handleSSE(res);
|
|
35
|
-
return;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
if (url.pathname === '/messages' && req.method === 'POST') {
|
|
39
|
-
this.handleMessage(req, res, url);
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
if (url.pathname === '/' && req.method === 'GET') {
|
|
44
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
45
|
-
res.end(JSON.stringify({
|
|
46
|
-
name: 'brain',
|
|
47
|
-
version: '1.8.0',
|
|
48
|
-
protocol: 'MCP',
|
|
49
|
-
transport: 'sse',
|
|
50
|
-
endpoints: {
|
|
51
|
-
sse: '/sse',
|
|
52
|
-
messages: '/messages',
|
|
53
|
-
},
|
|
54
|
-
clients: this.transports.size,
|
|
55
|
-
}));
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
|
60
|
-
res.end('Not Found');
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
this.server.listen(this.port, () => {
|
|
64
|
-
this.logger.info(`MCP HTTP server (SSE) started on http://localhost:${this.port}`);
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
stop(): void {
|
|
69
|
-
for (const transport of this.transports.values()) {
|
|
70
|
-
try { transport.close?.(); } catch { /* ignore */ }
|
|
71
|
-
}
|
|
72
|
-
this.transports.clear();
|
|
73
|
-
this.server?.close();
|
|
74
|
-
this.server = null;
|
|
75
|
-
this.logger.info('MCP HTTP server stopped');
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
getClientCount(): number {
|
|
79
|
-
return this.transports.size;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
private async handleSSE(res: http.ServerResponse): Promise<void> {
|
|
83
|
-
try {
|
|
84
|
-
const transport = new SSEServerTransport('/messages', res);
|
|
85
|
-
const sessionId = transport.sessionId ?? randomUUID();
|
|
86
|
-
this.transports.set(sessionId, transport);
|
|
87
|
-
|
|
88
|
-
const server = new McpServer({
|
|
89
|
-
name: 'brain',
|
|
90
|
-
version: '1.8.0',
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
registerToolsDirect(server, this.router);
|
|
94
|
-
|
|
95
|
-
res.on('close', () => {
|
|
96
|
-
this.transports.delete(sessionId);
|
|
97
|
-
this.logger.debug(`MCP SSE client disconnected: ${sessionId}`);
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
await server.connect(transport);
|
|
101
|
-
this.logger.info(`MCP SSE client connected: ${sessionId}`);
|
|
102
|
-
} catch (err) {
|
|
103
|
-
this.logger.error('MCP SSE connection error:', err);
|
|
104
|
-
if (!res.headersSent) {
|
|
105
|
-
res.writeHead(500, { 'Content-Type': 'text/plain' });
|
|
106
|
-
res.end('Internal Server Error');
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
private async handleMessage(
|
|
112
|
-
req: http.IncomingMessage,
|
|
113
|
-
res: http.ServerResponse,
|
|
114
|
-
url: URL,
|
|
115
|
-
): Promise<void> {
|
|
116
|
-
try {
|
|
117
|
-
const sessionId = url.searchParams.get('sessionId');
|
|
118
|
-
if (!sessionId) {
|
|
119
|
-
res.writeHead(400, { 'Content-Type': 'text/plain' });
|
|
120
|
-
res.end('Missing sessionId parameter');
|
|
121
|
-
return;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
const transport = this.transports.get(sessionId);
|
|
125
|
-
if (!transport) {
|
|
126
|
-
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
|
127
|
-
res.end('Session not found. Connect to /sse first.');
|
|
128
|
-
return;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
await transport.handlePostMessage(req, res);
|
|
132
|
-
} catch (err) {
|
|
133
|
-
this.logger.error('MCP message error:', err);
|
|
134
|
-
if (!res.headersSent) {
|
|
135
|
-
res.writeHead(500, { 'Content-Type': 'text/plain' });
|
|
136
|
-
res.end('Internal Server Error');
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
}
|