@timmeck/brain 1.8.5 → 2.0.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/.dockerignore +11 -0
- package/Dockerfile +21 -0
- package/README.md +19 -0
- package/assets/brain-avatar-256.png +0 -0
- package/assets/brain-avatar-512.png +0 -0
- package/assets/brain-avatar.png +0 -0
- package/brain.log +1164 -0
- package/{src/cli/commands/dashboard.ts → dashboard.html} +688 -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 +6 -0
- package/dist/brain.js +56 -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/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/start.js +54 -17
- package/dist/cli/commands/start.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.js +8 -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/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/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/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/gen_avatar.py +142 -0
- package/package.json +2 -1
- package/BRAIN_PLAN.md +0 -3324
- package/src/api/server.ts +0 -395
- package/src/brain.ts +0 -266
- 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 -62
- 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,226 +0,0 @@
|
|
|
1
|
-
import type { ErrorRecord } from '../types/error.types.js';
|
|
2
|
-
import type { ErrorRepository } from '../db/repositories/error.repository.js';
|
|
3
|
-
import type { SolutionRepository } from '../db/repositories/solution.repository.js';
|
|
4
|
-
import type { CodeModuleRepository } from '../db/repositories/code-module.repository.js';
|
|
5
|
-
import type { RuleRepository } from '../db/repositories/rule.repository.js';
|
|
6
|
-
import type { AntipatternRepository } from '../db/repositories/antipattern.repository.js';
|
|
7
|
-
import type { InsightRepository } from '../db/repositories/insight.repository.js';
|
|
8
|
-
import type { SynapseManager } from '../synapses/synapse-manager.js';
|
|
9
|
-
import type { NetworkStats } from '../types/synapse.types.js';
|
|
10
|
-
|
|
11
|
-
export interface ProjectSummary {
|
|
12
|
-
errors: { total: number; unresolved: number; last7d: number };
|
|
13
|
-
solutions: { total: number };
|
|
14
|
-
rules: { active: number };
|
|
15
|
-
antipatterns: { total: number };
|
|
16
|
-
modules: { total: number };
|
|
17
|
-
insights: { active: number };
|
|
18
|
-
healthScore?: number;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export interface NetworkOverview {
|
|
22
|
-
stats: NetworkStats;
|
|
23
|
-
strongestSynapses: Array<{
|
|
24
|
-
id: number;
|
|
25
|
-
source: string;
|
|
26
|
-
target: string;
|
|
27
|
-
type: string;
|
|
28
|
-
weight: number;
|
|
29
|
-
}>;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export class AnalyticsService {
|
|
33
|
-
constructor(
|
|
34
|
-
private errorRepo: ErrorRepository,
|
|
35
|
-
private solutionRepo: SolutionRepository,
|
|
36
|
-
private codeModuleRepo: CodeModuleRepository,
|
|
37
|
-
private ruleRepo: RuleRepository,
|
|
38
|
-
private antipatternRepo: AntipatternRepository,
|
|
39
|
-
private insightRepo: InsightRepository,
|
|
40
|
-
private synapseManager: SynapseManager,
|
|
41
|
-
) {}
|
|
42
|
-
|
|
43
|
-
getSummary(projectId?: number): ProjectSummary {
|
|
44
|
-
const sevenDaysAgo = new Date(Date.now() - 7 * 86400000).toISOString();
|
|
45
|
-
|
|
46
|
-
const unresolvedErrors = this.errorRepo.findUnresolved(projectId);
|
|
47
|
-
const allErrors = projectId ? this.errorRepo.findByProject(projectId) : [];
|
|
48
|
-
const last7dCount = this.errorRepo.countSince(sevenDaysAgo, projectId);
|
|
49
|
-
|
|
50
|
-
const rules = this.ruleRepo.findActive(projectId);
|
|
51
|
-
const antipatterns = projectId
|
|
52
|
-
? this.antipatternRepo.findByProject(projectId)
|
|
53
|
-
: this.antipatternRepo.findGlobal();
|
|
54
|
-
|
|
55
|
-
const moduleCount = projectId
|
|
56
|
-
? this.codeModuleRepo.findByProject(projectId).length
|
|
57
|
-
: this.codeModuleRepo.countAll();
|
|
58
|
-
const insights = this.insightRepo.findActive(projectId);
|
|
59
|
-
|
|
60
|
-
return {
|
|
61
|
-
errors: {
|
|
62
|
-
total: allErrors.length,
|
|
63
|
-
unresolved: unresolvedErrors.length,
|
|
64
|
-
last7d: last7dCount,
|
|
65
|
-
},
|
|
66
|
-
solutions: { total: 0 }, // solutions are global, not per-project
|
|
67
|
-
rules: { active: rules.length },
|
|
68
|
-
antipatterns: { total: antipatterns.length },
|
|
69
|
-
modules: { total: moduleCount },
|
|
70
|
-
insights: { active: insights.length },
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
computeHealthScore(projectId?: number): number {
|
|
75
|
-
const summary = this.getSummary(projectId);
|
|
76
|
-
const networkStats = this.synapseManager.getNetworkStats();
|
|
77
|
-
|
|
78
|
-
let score = 0;
|
|
79
|
-
let maxScore = 0;
|
|
80
|
-
|
|
81
|
-
// Data Volume (30 points)
|
|
82
|
-
maxScore += 30;
|
|
83
|
-
const dataVolume = summary.errors.total + (summary.modules.total * 2) + summary.solutions.total;
|
|
84
|
-
score += Math.min(30, dataVolume * 0.3);
|
|
85
|
-
|
|
86
|
-
// Synapse Density (20 points) - more connections = richer network
|
|
87
|
-
maxScore += 20;
|
|
88
|
-
const synapseDensity = networkStats.totalSynapses / Math.max(1, networkStats.totalNodes);
|
|
89
|
-
score += Math.min(20, synapseDensity * 5);
|
|
90
|
-
|
|
91
|
-
// Solution Coverage (20 points) - resolved errors vs total
|
|
92
|
-
maxScore += 20;
|
|
93
|
-
if (summary.errors.total > 0) {
|
|
94
|
-
const resolvedRate = 1 - (summary.errors.unresolved / summary.errors.total);
|
|
95
|
-
score += resolvedRate * 20;
|
|
96
|
-
} else {
|
|
97
|
-
score += 10; // No errors = neutral
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// Learning Activity (15 points) - active rules + insights
|
|
101
|
-
maxScore += 15;
|
|
102
|
-
const learningActivity = summary.rules.active + summary.insights.active;
|
|
103
|
-
score += Math.min(15, learningActivity * 1.5);
|
|
104
|
-
|
|
105
|
-
// Error Trend (15 points) - fewer recent errors = better
|
|
106
|
-
maxScore += 15;
|
|
107
|
-
if (summary.errors.total > 0) {
|
|
108
|
-
const recentRatio = summary.errors.last7d / Math.max(1, summary.errors.total);
|
|
109
|
-
// Low recent ratio = health is good (errors decreasing)
|
|
110
|
-
score += Math.max(0, 15 - recentRatio * 30);
|
|
111
|
-
} else {
|
|
112
|
-
score += 15;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
return Math.round((score / maxScore) * 100);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
getTimeSeries(projectId?: number, days: number = 30): Array<{ date: string; errors: number; solutions: number }> {
|
|
119
|
-
const series: Array<{ date: string; errors: number; solutions: number }> = [];
|
|
120
|
-
|
|
121
|
-
for (let i = days - 1; i >= 0; i--) {
|
|
122
|
-
const dayStart = new Date(Date.now() - (i + 1) * 86400000).toISOString();
|
|
123
|
-
const dayEnd = new Date(Date.now() - i * 86400000).toISOString();
|
|
124
|
-
|
|
125
|
-
const errorsInDay = this.errorRepo.countSince(dayStart, projectId) - this.errorRepo.countSince(dayEnd, projectId);
|
|
126
|
-
|
|
127
|
-
series.push({
|
|
128
|
-
date: dayStart.split('T')[0]!,
|
|
129
|
-
errors: Math.max(0, errorsInDay),
|
|
130
|
-
solutions: 0, // Approximation
|
|
131
|
-
});
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
return series;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
explainError(errorId: number): {
|
|
138
|
-
error: ErrorRecord | undefined;
|
|
139
|
-
solutions: Array<{ id: number; description: string; confidence: number; successRate: number }>;
|
|
140
|
-
chain: { parents: ErrorRecord[]; children: ErrorRecord[] };
|
|
141
|
-
relatedErrors: Array<{ id: number; type: string; message: string; similarity: number }>;
|
|
142
|
-
rules: Array<{ id: number; pattern: string; action: string; confidence: number }>;
|
|
143
|
-
insights: Array<{ id: number; type: string; title: string }>;
|
|
144
|
-
synapseConnections: number;
|
|
145
|
-
} {
|
|
146
|
-
const error = this.errorRepo.getById(errorId);
|
|
147
|
-
if (!error) {
|
|
148
|
-
return {
|
|
149
|
-
error: undefined, solutions: [], chain: { parents: [], children: [] },
|
|
150
|
-
relatedErrors: [], rules: [], insights: [], synapseConnections: 0,
|
|
151
|
-
};
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// Solutions
|
|
155
|
-
const solutions = this.solutionRepo.findForError(errorId).map(s => ({
|
|
156
|
-
id: s.id,
|
|
157
|
-
description: s.description,
|
|
158
|
-
confidence: s.confidence,
|
|
159
|
-
successRate: this.solutionRepo.successRate(s.id),
|
|
160
|
-
}));
|
|
161
|
-
|
|
162
|
-
// Error chain
|
|
163
|
-
const parents = this.errorRepo.findChainParents(errorId);
|
|
164
|
-
const children = this.errorRepo.findChainChildren(errorId);
|
|
165
|
-
|
|
166
|
-
// Related via synapses
|
|
167
|
-
const context = this.synapseManager.getErrorContext(errorId);
|
|
168
|
-
const relatedErrors = context.relatedErrors.map(r => {
|
|
169
|
-
const e = this.errorRepo.getById(r.node.id);
|
|
170
|
-
return {
|
|
171
|
-
id: r.node.id,
|
|
172
|
-
type: e?.type ?? 'unknown',
|
|
173
|
-
message: e?.message ?? '',
|
|
174
|
-
similarity: r.activation,
|
|
175
|
-
};
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
// Prevention rules
|
|
179
|
-
const matchedRules = this.ruleRepo.findActive(error.project_id);
|
|
180
|
-
const rules = matchedRules
|
|
181
|
-
.filter(r => {
|
|
182
|
-
try { return new RegExp(r.pattern, 'i').test(`${error.type}: ${error.message}`); }
|
|
183
|
-
catch { return false; }
|
|
184
|
-
})
|
|
185
|
-
.map(r => ({ id: r.id, pattern: r.pattern, action: r.action, confidence: r.confidence }));
|
|
186
|
-
|
|
187
|
-
// Related insights
|
|
188
|
-
const insights = context.insights.map(i => ({
|
|
189
|
-
id: i.node.id,
|
|
190
|
-
type: i.node.type,
|
|
191
|
-
title: `Insight #${i.node.id}`,
|
|
192
|
-
}));
|
|
193
|
-
|
|
194
|
-
// Total synapse connections
|
|
195
|
-
const allConnections = this.synapseManager.activate({ type: 'error', id: errorId });
|
|
196
|
-
|
|
197
|
-
return {
|
|
198
|
-
error,
|
|
199
|
-
solutions,
|
|
200
|
-
chain: { parents, children },
|
|
201
|
-
relatedErrors,
|
|
202
|
-
rules,
|
|
203
|
-
insights,
|
|
204
|
-
synapseConnections: allConnections.length,
|
|
205
|
-
};
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
getNetworkOverview(limit: number = 10): NetworkOverview {
|
|
209
|
-
const stats = this.synapseManager.getNetworkStats();
|
|
210
|
-
// Use diverse sampling for dashboard (spread across synapse types)
|
|
211
|
-
const diverse = limit >= 30
|
|
212
|
-
? this.synapseManager.getDiverseSynapses(Math.ceil(limit / 3))
|
|
213
|
-
: this.synapseManager.getStrongestSynapses(limit);
|
|
214
|
-
|
|
215
|
-
return {
|
|
216
|
-
stats,
|
|
217
|
-
strongestSynapses: diverse.map(s => ({
|
|
218
|
-
id: s.id,
|
|
219
|
-
source: `${s.source_type}:${s.source_id}`,
|
|
220
|
-
target: `${s.target_type}:${s.target_id}`,
|
|
221
|
-
type: s.synapse_type,
|
|
222
|
-
weight: s.weight,
|
|
223
|
-
})),
|
|
224
|
-
};
|
|
225
|
-
}
|
|
226
|
-
}
|
|
@@ -1,271 +0,0 @@
|
|
|
1
|
-
import type { CodeModuleRecord } from '../types/code.types.js';
|
|
2
|
-
import type { CodeModuleRepository } from '../db/repositories/code-module.repository.js';
|
|
3
|
-
import type { ProjectRepository } from '../db/repositories/project.repository.js';
|
|
4
|
-
import type { SynapseManager } from '../synapses/synapse-manager.js';
|
|
5
|
-
import { analyzeCode } from '../code/analyzer.js';
|
|
6
|
-
import { fingerprintCode } from '../code/fingerprint.js';
|
|
7
|
-
import { computeReusabilityScore } from '../code/scorer.js';
|
|
8
|
-
import { detectGranularity } from '../code/registry.js';
|
|
9
|
-
import { findExactMatches, findSemanticMatches, findStructuralMatches } from '../code/matcher.js';
|
|
10
|
-
import { sha256 } from '../utils/hash.js';
|
|
11
|
-
import { getEventBus } from '../utils/events.js';
|
|
12
|
-
import { getLogger } from '../utils/logger.js';
|
|
13
|
-
import type { EmbeddingEngine } from '../embeddings/engine.js';
|
|
14
|
-
|
|
15
|
-
export interface AnalyzeInput {
|
|
16
|
-
project: string;
|
|
17
|
-
name: string;
|
|
18
|
-
filePath: string;
|
|
19
|
-
language: string;
|
|
20
|
-
source: string;
|
|
21
|
-
description?: string;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export interface FindReusableInput {
|
|
25
|
-
query?: string;
|
|
26
|
-
language?: string;
|
|
27
|
-
projectId?: number;
|
|
28
|
-
limit?: number;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export class CodeService {
|
|
32
|
-
private logger = getLogger();
|
|
33
|
-
private eventBus = getEventBus();
|
|
34
|
-
private embeddingEngine: EmbeddingEngine | null = null;
|
|
35
|
-
|
|
36
|
-
constructor(
|
|
37
|
-
private codeModuleRepo: CodeModuleRepository,
|
|
38
|
-
private projectRepo: ProjectRepository,
|
|
39
|
-
private synapseManager: SynapseManager,
|
|
40
|
-
) {}
|
|
41
|
-
|
|
42
|
-
setEmbeddingEngine(engine: EmbeddingEngine): void {
|
|
43
|
-
this.embeddingEngine = engine;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
analyzeAndRegister(input: AnalyzeInput): { moduleId: number; isNew: boolean; reusabilityScore: number } {
|
|
47
|
-
// Ensure project exists
|
|
48
|
-
let project = this.projectRepo.findByName(input.project);
|
|
49
|
-
if (!project) {
|
|
50
|
-
const id = this.projectRepo.create({ name: input.project, path: null, language: input.language, framework: null });
|
|
51
|
-
project = this.projectRepo.getById(id)!;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// Analyze the code
|
|
55
|
-
const analysis = analyzeCode(input.source, input.language);
|
|
56
|
-
const fingerprint = fingerprintCode(input.source, input.language);
|
|
57
|
-
const sourceHash = sha256(input.source);
|
|
58
|
-
|
|
59
|
-
// Check if module already exists (by fingerprint)
|
|
60
|
-
const existing = this.codeModuleRepo.findByFingerprint(fingerprint);
|
|
61
|
-
if (existing) {
|
|
62
|
-
// Source Hash Change Detection: compare hash to decide if re-analysis needed
|
|
63
|
-
if (existing.source_hash === sourceHash) {
|
|
64
|
-
// Unchanged — skip re-analysis
|
|
65
|
-
this.logger.debug(`Module ${existing.name} unchanged (hash match), skipping`);
|
|
66
|
-
this.synapseManager.strengthen(
|
|
67
|
-
{ type: 'code_module', id: existing.id },
|
|
68
|
-
{ type: 'project', id: project.id },
|
|
69
|
-
'uses_module',
|
|
70
|
-
);
|
|
71
|
-
return { moduleId: existing.id, isNew: false, reusabilityScore: existing.reusability_score };
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// Hash changed — re-analyze
|
|
75
|
-
this.logger.info(`Module ${existing.name} changed (hash drift), re-analyzing`);
|
|
76
|
-
const reScore = computeReusabilityScore({
|
|
77
|
-
source: input.source,
|
|
78
|
-
filePath: input.filePath,
|
|
79
|
-
exports: analysis.exports,
|
|
80
|
-
internalDeps: analysis.internalDeps,
|
|
81
|
-
hasTypeAnnotations: analysis.hasTypeAnnotations,
|
|
82
|
-
complexity: analysis.complexity,
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
this.codeModuleRepo.update(existing.id, {
|
|
86
|
-
source_hash: sourceHash,
|
|
87
|
-
lines_of_code: analysis.linesOfCode,
|
|
88
|
-
complexity: analysis.complexity,
|
|
89
|
-
reusability_score: reScore,
|
|
90
|
-
updated_at: new Date().toISOString(),
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
// Re-index dependency synapses on change
|
|
94
|
-
this.indexDependencySynapses(existing.id, analysis.internalDeps, project.id);
|
|
95
|
-
|
|
96
|
-
this.synapseManager.strengthen(
|
|
97
|
-
{ type: 'code_module', id: existing.id },
|
|
98
|
-
{ type: 'project', id: project.id },
|
|
99
|
-
'uses_module',
|
|
100
|
-
);
|
|
101
|
-
|
|
102
|
-
this.eventBus.emit('module:updated', { moduleId: existing.id });
|
|
103
|
-
|
|
104
|
-
return { moduleId: existing.id, isNew: false, reusabilityScore: reScore };
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// Compute reusability score (with complexity)
|
|
108
|
-
const reusabilityScore = computeReusabilityScore({
|
|
109
|
-
source: input.source,
|
|
110
|
-
filePath: input.filePath,
|
|
111
|
-
exports: analysis.exports,
|
|
112
|
-
internalDeps: analysis.internalDeps,
|
|
113
|
-
hasTypeAnnotations: analysis.hasTypeAnnotations,
|
|
114
|
-
complexity: analysis.complexity,
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
const granularity = detectGranularity(input.source, input.language);
|
|
118
|
-
|
|
119
|
-
const moduleId = this.codeModuleRepo.create({
|
|
120
|
-
project_id: project.id,
|
|
121
|
-
name: input.name,
|
|
122
|
-
file_path: input.filePath,
|
|
123
|
-
language: input.language,
|
|
124
|
-
fingerprint,
|
|
125
|
-
description: input.description ?? null,
|
|
126
|
-
source_hash: sourceHash,
|
|
127
|
-
lines_of_code: analysis.linesOfCode,
|
|
128
|
-
complexity: analysis.complexity,
|
|
129
|
-
reusability_score: reusabilityScore,
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
// Create synapse: module ↔ project
|
|
133
|
-
this.synapseManager.strengthen(
|
|
134
|
-
{ type: 'code_module', id: moduleId },
|
|
135
|
-
{ type: 'project', id: project.id },
|
|
136
|
-
'uses_module',
|
|
137
|
-
);
|
|
138
|
-
|
|
139
|
-
// Create dependency synapses for internal imports
|
|
140
|
-
this.indexDependencySynapses(moduleId, analysis.internalDeps, project.id);
|
|
141
|
-
|
|
142
|
-
// Compute and store pairwise module similarities
|
|
143
|
-
this.computeModuleSimilarities(moduleId, input.source, input.language);
|
|
144
|
-
|
|
145
|
-
this.eventBus.emit('module:registered', { moduleId, projectId: project.id });
|
|
146
|
-
this.logger.info(`Code module registered (id=${moduleId}, name=${input.name}, granularity=${granularity}, score=${reusabilityScore.toFixed(2)})`);
|
|
147
|
-
|
|
148
|
-
return { moduleId, isNew: true, reusabilityScore };
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
findReusable(input: FindReusableInput): CodeModuleRecord[] {
|
|
152
|
-
if (input.query) {
|
|
153
|
-
return this.codeModuleRepo.search(input.query);
|
|
154
|
-
}
|
|
155
|
-
if (input.language) {
|
|
156
|
-
return this.codeModuleRepo.findByLanguage(input.language, input.limit);
|
|
157
|
-
}
|
|
158
|
-
if (input.projectId) {
|
|
159
|
-
return this.codeModuleRepo.findByProject(input.projectId);
|
|
160
|
-
}
|
|
161
|
-
return [];
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
checkSimilarity(source: string, language: string): Array<{ moduleId: number; score: number; matchType: string }> {
|
|
165
|
-
const fingerprint = fingerprintCode(source, language);
|
|
166
|
-
const allModules = this.codeModuleRepo.findByLanguage(language);
|
|
167
|
-
|
|
168
|
-
const exact = findExactMatches(fingerprint, allModules);
|
|
169
|
-
if (exact.length > 0) return exact;
|
|
170
|
-
|
|
171
|
-
return findSemanticMatches(source, allModules, 0.5);
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
listModules(projectId?: number, language?: string, limit?: number): CodeModuleRecord[] {
|
|
175
|
-
if (projectId) {
|
|
176
|
-
return this.codeModuleRepo.findByProject(projectId);
|
|
177
|
-
}
|
|
178
|
-
if (language) {
|
|
179
|
-
return this.codeModuleRepo.findByLanguage(language, limit);
|
|
180
|
-
}
|
|
181
|
-
return this.codeModuleRepo.findAll(limit);
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
getById(id: number): CodeModuleRecord | undefined {
|
|
185
|
-
return this.codeModuleRepo.getById(id);
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
private computeModuleSimilarities(moduleId: number, source: string, language: string): void {
|
|
189
|
-
const allModules = this.codeModuleRepo.findByLanguage(language, 100);
|
|
190
|
-
const candidates = allModules.filter(m => m.id !== moduleId);
|
|
191
|
-
if (candidates.length === 0) return;
|
|
192
|
-
|
|
193
|
-
const matches = findStructuralMatches(source, language, candidates, 0.3);
|
|
194
|
-
|
|
195
|
-
// Get vector similarity scores if embedding engine is ready
|
|
196
|
-
const vectorScores = this.embeddingEngine?.isReady()
|
|
197
|
-
? this.embeddingEngine.computeModuleVectorScores(moduleId, language)
|
|
198
|
-
: undefined;
|
|
199
|
-
|
|
200
|
-
// Merge structural matches with vector boost
|
|
201
|
-
const scoreMap = new Map<number, number>();
|
|
202
|
-
for (const match of matches) {
|
|
203
|
-
if (match.score >= 0.3 && match.moduleId !== moduleId) {
|
|
204
|
-
scoreMap.set(match.moduleId, match.score);
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
// Add vector-only matches that structural search missed
|
|
209
|
-
if (vectorScores) {
|
|
210
|
-
for (const [candId, vecScore] of vectorScores) {
|
|
211
|
-
if (vecScore >= 0.5 && candId !== moduleId) {
|
|
212
|
-
const existing = scoreMap.get(candId);
|
|
213
|
-
if (existing) {
|
|
214
|
-
// Boost structural score with vector similarity
|
|
215
|
-
scoreMap.set(candId, Math.min(1.0, existing + vecScore * 0.15));
|
|
216
|
-
} else {
|
|
217
|
-
// Vector-only match
|
|
218
|
-
scoreMap.set(candId, vecScore * 0.8);
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
for (const [candId, score] of scoreMap) {
|
|
225
|
-
this.codeModuleRepo.upsertSimilarity(moduleId, candId, score);
|
|
226
|
-
|
|
227
|
-
// High similarity → create synapse
|
|
228
|
-
if (score >= 0.7) {
|
|
229
|
-
this.synapseManager.strengthen(
|
|
230
|
-
{ type: 'code_module', id: moduleId },
|
|
231
|
-
{ type: 'code_module', id: candId },
|
|
232
|
-
'similar_to',
|
|
233
|
-
);
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
private indexDependencySynapses(moduleId: number, internalDeps: string[], projectId: number): void {
|
|
239
|
-
const projectModules = this.codeModuleRepo.findByProject(projectId);
|
|
240
|
-
|
|
241
|
-
for (const dep of internalDeps) {
|
|
242
|
-
// Normalize the dep path to match against registered modules
|
|
243
|
-
const depName = dep.replace(/^\.\//, '').replace(/\.\w+$/, '');
|
|
244
|
-
|
|
245
|
-
const target = projectModules.find(m => {
|
|
246
|
-
const modulePath = m.file_path.replace(/\\/g, '/').replace(/\.\w+$/, '');
|
|
247
|
-
return modulePath.endsWith(depName) || m.name === depName;
|
|
248
|
-
});
|
|
249
|
-
|
|
250
|
-
if (target && target.id !== moduleId) {
|
|
251
|
-
this.synapseManager.strengthen(
|
|
252
|
-
{ type: 'code_module', id: moduleId },
|
|
253
|
-
{ type: 'code_module', id: target.id },
|
|
254
|
-
'depends_on',
|
|
255
|
-
);
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
listProjects(): Array<{ id: number; name: string; path: string | null; language: string | null; framework: string | null; moduleCount: number }> {
|
|
261
|
-
const projects = this.projectRepo.getAll();
|
|
262
|
-
return projects.map(p => ({
|
|
263
|
-
id: p.id,
|
|
264
|
-
name: p.name,
|
|
265
|
-
path: p.path,
|
|
266
|
-
language: p.language,
|
|
267
|
-
framework: p.framework,
|
|
268
|
-
moduleCount: this.codeModuleRepo.findByProject(p.id).length,
|
|
269
|
-
}));
|
|
270
|
-
}
|
|
271
|
-
}
|