@timmeck/brain 1.8.1 → 1.8.2
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/BRAIN_PLAN.md +3324 -3324
- package/LICENSE +21 -21
- package/dist/cli/commands/dashboard.js +595 -595
- package/dist/dashboard/server.js +25 -25
- package/dist/db/migrations/001_core_schema.js +115 -115
- package/dist/db/migrations/002_learning_schema.js +33 -33
- package/dist/db/migrations/003_code_schema.js +48 -48
- package/dist/db/migrations/004_synapses_schema.js +52 -52
- package/dist/db/migrations/005_fts_indexes.js +73 -73
- package/dist/db/migrations/007_feedback.js +8 -8
- package/dist/db/migrations/008_git_integration.js +33 -33
- package/dist/db/migrations/009_embeddings.js +3 -3
- package/dist/db/repositories/antipattern.repository.js +3 -3
- package/dist/db/repositories/code-module.repository.js +32 -32
- package/dist/db/repositories/notification.repository.js +3 -3
- package/dist/db/repositories/project.repository.js +21 -21
- package/dist/db/repositories/rule.repository.js +24 -24
- package/dist/db/repositories/solution.repository.js +50 -50
- package/dist/db/repositories/synapse.repository.js +18 -18
- package/dist/db/repositories/terminal.repository.js +24 -24
- package/dist/ipc/server.d.ts +8 -0
- package/dist/ipc/server.js +67 -1
- package/dist/ipc/server.js.map +1 -1
- package/dist/matching/error-matcher.js +5 -5
- package/dist/matching/fingerprint.js +6 -1
- package/dist/matching/fingerprint.js.map +1 -1
- package/dist/services/error.service.js +4 -3
- package/dist/services/error.service.js.map +1 -1
- package/dist/services/git.service.js +14 -14
- package/package.json +49 -49
- package/src/api/server.ts +395 -395
- package/src/brain.ts +266 -266
- package/src/cli/colors.ts +116 -116
- package/src/cli/commands/config.ts +169 -169
- package/src/cli/commands/dashboard.ts +755 -755
- package/src/cli/commands/doctor.ts +118 -118
- package/src/cli/commands/explain.ts +83 -83
- package/src/cli/commands/export.ts +31 -31
- package/src/cli/commands/import.ts +199 -199
- package/src/cli/commands/insights.ts +65 -65
- package/src/cli/commands/learn.ts +24 -24
- package/src/cli/commands/modules.ts +53 -53
- package/src/cli/commands/network.ts +67 -67
- package/src/cli/commands/projects.ts +42 -42
- package/src/cli/commands/query.ts +120 -120
- package/src/cli/commands/start.ts +62 -62
- package/src/cli/commands/status.ts +75 -75
- package/src/cli/commands/stop.ts +34 -34
- package/src/cli/ipc-helper.ts +22 -22
- package/src/cli/update-check.ts +63 -63
- package/src/code/fingerprint.ts +87 -87
- package/src/code/parsers/generic.ts +29 -29
- package/src/code/parsers/python.ts +54 -54
- package/src/code/parsers/typescript.ts +65 -65
- package/src/code/registry.ts +60 -60
- package/src/dashboard/server.ts +142 -142
- package/src/db/connection.ts +22 -22
- package/src/db/migrations/001_core_schema.ts +120 -120
- package/src/db/migrations/002_learning_schema.ts +38 -38
- package/src/db/migrations/003_code_schema.ts +53 -53
- package/src/db/migrations/004_synapses_schema.ts +57 -57
- package/src/db/migrations/005_fts_indexes.ts +78 -78
- package/src/db/migrations/006_synapses_phase3.ts +17 -17
- package/src/db/migrations/007_feedback.ts +13 -13
- package/src/db/migrations/008_git_integration.ts +38 -38
- package/src/db/migrations/009_embeddings.ts +8 -8
- package/src/db/repositories/antipattern.repository.ts +66 -66
- package/src/db/repositories/code-module.repository.ts +142 -142
- package/src/db/repositories/notification.repository.ts +66 -66
- package/src/db/repositories/project.repository.ts +93 -93
- package/src/db/repositories/rule.repository.ts +108 -108
- package/src/db/repositories/solution.repository.ts +154 -154
- package/src/db/repositories/synapse.repository.ts +153 -153
- package/src/db/repositories/terminal.repository.ts +101 -101
- package/src/embeddings/engine.ts +238 -238
- package/src/index.ts +63 -63
- package/src/ipc/client.ts +118 -118
- package/src/ipc/protocol.ts +35 -35
- package/src/ipc/router.ts +133 -133
- package/src/ipc/server.ts +176 -110
- package/src/learning/decay.ts +46 -46
- package/src/learning/pattern-extractor.ts +90 -90
- package/src/learning/rule-generator.ts +74 -74
- package/src/matching/error-matcher.ts +5 -5
- package/src/matching/fingerprint.ts +34 -29
- package/src/matching/similarity.ts +61 -61
- package/src/matching/tfidf.ts +74 -74
- package/src/matching/tokenizer.ts +41 -41
- package/src/mcp/auto-detect.ts +93 -93
- package/src/mcp/http-server.ts +140 -140
- package/src/mcp/server.ts +73 -73
- package/src/parsing/error-parser.ts +28 -28
- package/src/parsing/parsers/compiler.ts +93 -93
- package/src/parsing/parsers/generic.ts +28 -28
- package/src/parsing/parsers/go.ts +97 -97
- package/src/parsing/parsers/node.ts +69 -69
- package/src/parsing/parsers/python.ts +62 -62
- package/src/parsing/parsers/rust.ts +50 -50
- package/src/parsing/parsers/shell.ts +42 -42
- package/src/parsing/types.ts +47 -47
- package/src/research/gap-analyzer.ts +135 -135
- package/src/research/insight-generator.ts +123 -123
- package/src/research/research-engine.ts +116 -116
- package/src/research/synergy-detector.ts +126 -126
- package/src/research/template-extractor.ts +130 -130
- package/src/research/trend-analyzer.ts +127 -127
- package/src/services/code.service.ts +271 -271
- package/src/services/error.service.ts +4 -3
- package/src/services/git.service.ts +132 -132
- package/src/services/notification.service.ts +41 -41
- package/src/services/synapse.service.ts +59 -59
- package/src/services/terminal.service.ts +81 -81
- package/src/synapses/activation.ts +80 -80
- package/src/synapses/decay.ts +38 -38
- package/src/synapses/hebbian.ts +69 -69
- package/src/synapses/pathfinder.ts +81 -81
- package/src/synapses/synapse-manager.ts +109 -109
- package/src/types/code.types.ts +52 -52
- package/src/types/error.types.ts +67 -67
- package/src/types/ipc.types.ts +8 -8
- package/src/types/mcp.types.ts +53 -53
- package/src/types/research.types.ts +28 -28
- package/src/types/solution.types.ts +30 -30
- package/src/utils/events.ts +45 -45
- package/src/utils/hash.ts +5 -5
- package/src/utils/logger.ts +48 -48
- package/src/utils/paths.ts +19 -19
- package/tests/e2e/test_code_intelligence.py +1015 -0
- package/tests/e2e/test_error_memory.py +451 -0
- package/tests/e2e/test_full_integration.py +534 -0
- package/tests/fixtures/code-modules/modules.ts +83 -83
- package/tests/fixtures/errors/go.ts +9 -9
- package/tests/fixtures/errors/node.ts +24 -24
- package/tests/fixtures/errors/python.ts +21 -21
- package/tests/fixtures/errors/rust.ts +25 -25
- package/tests/fixtures/errors/shell.ts +15 -15
- package/tests/fixtures/solutions/solutions.ts +27 -27
- package/tests/helpers/setup-db.ts +52 -52
- package/tests/integration/code-flow.test.ts +86 -86
- package/tests/integration/error-flow.test.ts +83 -83
- package/tests/integration/ipc-flow.test.ts +166 -166
- package/tests/integration/learning-cycle.test.ts +82 -82
- package/tests/integration/synapse-flow.test.ts +117 -117
- package/tests/unit/code/analyzer.test.ts +58 -58
- package/tests/unit/code/fingerprint.test.ts +51 -51
- package/tests/unit/code/scorer.test.ts +55 -55
- package/tests/unit/learning/confidence-scorer.test.ts +60 -60
- package/tests/unit/learning/decay.test.ts +45 -45
- package/tests/unit/learning/pattern-extractor.test.ts +50 -50
- package/tests/unit/matching/error-matcher.test.ts +69 -69
- package/tests/unit/matching/fingerprint.test.ts +47 -47
- package/tests/unit/matching/similarity.test.ts +65 -65
- package/tests/unit/matching/tfidf.test.ts +71 -71
- package/tests/unit/matching/tokenizer.test.ts +83 -83
- package/tests/unit/parsing/parsers.test.ts +113 -113
- package/tests/unit/research/gap-analyzer.test.ts +45 -45
- package/tests/unit/research/trend-analyzer.test.ts +45 -45
- package/tests/unit/synapses/activation.test.ts +80 -80
- package/tests/unit/synapses/decay.test.ts +27 -27
- package/tests/unit/synapses/hebbian.test.ts +96 -96
- package/tests/unit/synapses/pathfinder.test.ts +72 -72
- package/tsconfig.json +18 -18
|
@@ -1,271 +1,271 @@
|
|
|
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
|
-
}
|
|
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
|
+
}
|
|
@@ -159,8 +159,8 @@ export class ErrorService {
|
|
|
159
159
|
}
|
|
160
160
|
|
|
161
161
|
query(input: ErrorQueryInput): ErrorRecord[] {
|
|
162
|
-
if (input.search) {
|
|
163
|
-
return this.errorRepo.search(input.search);
|
|
162
|
+
if (input.search && input.search.trim()) {
|
|
163
|
+
return this.errorRepo.search(input.search.trim());
|
|
164
164
|
}
|
|
165
165
|
if (input.resolved === false) {
|
|
166
166
|
return this.errorRepo.findUnresolved(input.projectId);
|
|
@@ -168,7 +168,8 @@ export class ErrorService {
|
|
|
168
168
|
if (input.projectId) {
|
|
169
169
|
return this.errorRepo.findByProject(input.projectId);
|
|
170
170
|
}
|
|
171
|
-
return
|
|
171
|
+
// Default: return recent errors (most recent first)
|
|
172
|
+
return this.errorRepo.findAll(input.limit ?? 100);
|
|
172
173
|
}
|
|
173
174
|
|
|
174
175
|
matchSimilar(errorId: number): MatchResult[] {
|