@timmeck/brain 1.0.0 → 1.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/BRAIN_PLAN.md +3324 -3324
- package/LICENSE +21 -21
- package/README.md +194 -188
- package/dist/brain.js +2 -0
- package/dist/brain.js.map +1 -1
- package/dist/cli/colors.d.ts +50 -0
- package/dist/cli/colors.js +106 -0
- package/dist/cli/colors.js.map +1 -0
- package/dist/cli/commands/config.d.ts +2 -0
- package/dist/cli/commands/config.js +165 -0
- package/dist/cli/commands/config.js.map +1 -0
- package/dist/cli/commands/dashboard.js +222 -8
- package/dist/cli/commands/dashboard.js.map +1 -1
- package/dist/cli/commands/export.js +3 -0
- package/dist/cli/commands/export.js.map +1 -1
- package/dist/cli/commands/import.js +24 -15
- package/dist/cli/commands/import.js.map +1 -1
- package/dist/cli/commands/insights.js +33 -6
- package/dist/cli/commands/insights.js.map +1 -1
- package/dist/cli/commands/learn.d.ts +2 -0
- package/dist/cli/commands/learn.js +22 -0
- package/dist/cli/commands/learn.js.map +1 -0
- package/dist/cli/commands/modules.js +25 -6
- package/dist/cli/commands/modules.js.map +1 -1
- package/dist/cli/commands/network.js +15 -9
- package/dist/cli/commands/network.js.map +1 -1
- package/dist/cli/commands/query.js +92 -25
- package/dist/cli/commands/query.js.map +1 -1
- package/dist/cli/commands/start.js +5 -4
- package/dist/cli/commands/start.js.map +1 -1
- package/dist/cli/commands/status.js +19 -16
- package/dist/cli/commands/status.js.map +1 -1
- package/dist/cli/commands/stop.js +5 -4
- package/dist/cli/commands/stop.js.map +1 -1
- package/dist/cli/ipc-helper.js +4 -3
- package/dist/cli/ipc-helper.js.map +1 -1
- 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/index.js +6 -6
- package/dist/db/repositories/antipattern.repository.js +3 -3
- package/dist/db/repositories/code-module.repository.d.ts +1 -0
- package/dist/db/repositories/code-module.repository.js +8 -0
- package/dist/db/repositories/code-module.repository.js.map +1 -1
- package/dist/db/repositories/error.repository.js +46 -46
- package/dist/db/repositories/insight.repository.js +3 -3
- 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/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/ipc/router.d.ts +2 -0
- package/dist/ipc/router.js +7 -1
- package/dist/ipc/router.js.map +1 -1
- package/dist/services/code.service.d.ts +1 -1
- package/dist/services/code.service.js +5 -2
- package/dist/services/code.service.js.map +1 -1
- package/package.json +5 -4
- package/src/brain.ts +3 -0
- package/src/cli/colors.ts +116 -0
- package/src/cli/commands/config.ts +169 -0
- package/src/cli/commands/dashboard.ts +231 -8
- package/src/cli/commands/export.ts +4 -0
- package/src/cli/commands/import.ts +24 -15
- package/src/cli/commands/insights.ts +37 -5
- package/src/cli/commands/learn.ts +24 -0
- package/src/cli/commands/modules.ts +28 -5
- package/src/cli/commands/network.ts +15 -9
- package/src/cli/commands/query.ts +103 -26
- package/src/cli/commands/start.ts +5 -4
- package/src/cli/commands/status.ts +19 -16
- package/src/cli/commands/stop.ts +5 -4
- package/src/cli/ipc-helper.ts +4 -3
- package/src/code/analyzer.ts +77 -77
- package/src/code/fingerprint.ts +87 -87
- package/src/code/matcher.ts +64 -64
- 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/code/scorer.ts +108 -108
- package/src/config.ts +111 -111
- 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/index.ts +64 -64
- package/src/db/repositories/antipattern.repository.ts +66 -66
- package/src/db/repositories/code-module.repository.ts +9 -0
- package/src/db/repositories/error.repository.ts +149 -149
- package/src/db/repositories/insight.repository.ts +78 -78
- 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/hooks/post-tool-use.ts +90 -90
- package/src/hooks/post-write.ts +117 -117
- package/src/index.ts +4 -0
- package/src/ipc/client.ts +118 -118
- package/src/ipc/protocol.ts +35 -35
- package/src/ipc/router.ts +9 -1
- package/src/ipc/server.ts +110 -110
- package/src/learning/confidence-scorer.ts +47 -47
- package/src/learning/decay.ts +46 -46
- package/src/learning/learning-engine.ts +162 -162
- package/src/learning/pattern-extractor.ts +90 -90
- package/src/learning/rule-generator.ts +74 -74
- package/src/matching/error-matcher.ts +115 -115
- package/src/matching/fingerprint.ts +29 -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/server.ts +73 -73
- package/src/mcp/tools.ts +290 -290
- 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/analytics.service.ts +87 -87
- package/src/services/code.service.ts +5 -2
- package/src/services/error.service.ts +164 -164
- package/src/services/notification.service.ts +41 -41
- package/src/services/prevention.service.ts +119 -119
- package/src/services/research.service.ts +93 -93
- package/src/services/solution.service.ts +116 -116
- 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/config.types.ts +79 -79
- 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/types/synapse.types.ts +49 -49
- 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/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,164 +1,164 @@
|
|
|
1
|
-
import type { ErrorRecord } from '../types/error.types.js';
|
|
2
|
-
import type { ErrorRepository } from '../db/repositories/error.repository.js';
|
|
3
|
-
import type { ProjectRepository } from '../db/repositories/project.repository.js';
|
|
4
|
-
import type { SynapseManager } from '../synapses/synapse-manager.js';
|
|
5
|
-
import { parseError } from '../parsing/error-parser.js';
|
|
6
|
-
import { generateFingerprint } from '../matching/fingerprint.js';
|
|
7
|
-
import { matchError, type MatchResult } from '../matching/error-matcher.js';
|
|
8
|
-
import { getEventBus } from '../utils/events.js';
|
|
9
|
-
import { getLogger } from '../utils/logger.js';
|
|
10
|
-
|
|
11
|
-
export interface ReportErrorInput {
|
|
12
|
-
project: string;
|
|
13
|
-
errorOutput: string;
|
|
14
|
-
filePath?: string;
|
|
15
|
-
terminalId?: number;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export interface ErrorQueryInput {
|
|
19
|
-
projectId?: number;
|
|
20
|
-
resolved?: boolean;
|
|
21
|
-
search?: string;
|
|
22
|
-
limit?: number;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export class ErrorService {
|
|
26
|
-
private logger = getLogger();
|
|
27
|
-
private eventBus = getEventBus();
|
|
28
|
-
|
|
29
|
-
constructor(
|
|
30
|
-
private errorRepo: ErrorRepository,
|
|
31
|
-
private projectRepo: ProjectRepository,
|
|
32
|
-
private synapseManager: SynapseManager,
|
|
33
|
-
) {}
|
|
34
|
-
|
|
35
|
-
report(input: ReportErrorInput): { errorId: number; isNew: boolean; matches: MatchResult[] } {
|
|
36
|
-
// 1. Ensure project exists
|
|
37
|
-
let project = this.projectRepo.findByName(input.project);
|
|
38
|
-
if (!project) {
|
|
39
|
-
const id = this.projectRepo.create({ name: input.project, path: null, language: null, framework: null });
|
|
40
|
-
project = this.projectRepo.getById(id)!;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// 2. Parse the error
|
|
44
|
-
const parsed = parseError(input.errorOutput);
|
|
45
|
-
if (!parsed) {
|
|
46
|
-
this.logger.warn('Could not parse error output');
|
|
47
|
-
const errorId = this.errorRepo.create({
|
|
48
|
-
project_id: project.id,
|
|
49
|
-
terminal_id: input.terminalId ?? null,
|
|
50
|
-
fingerprint: '',
|
|
51
|
-
type: 'UnknownError',
|
|
52
|
-
message: input.errorOutput.split('\n')[0] ?? input.errorOutput,
|
|
53
|
-
raw_output: input.errorOutput,
|
|
54
|
-
context: null,
|
|
55
|
-
file_path: input.filePath ?? null,
|
|
56
|
-
line_number: null,
|
|
57
|
-
column_number: null,
|
|
58
|
-
});
|
|
59
|
-
return { errorId, isNew: true, matches: [] };
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// 3. Generate fingerprint
|
|
63
|
-
const fingerprint = generateFingerprint(parsed.errorType, parsed.message, parsed.frames);
|
|
64
|
-
|
|
65
|
-
// 4. Check for existing error with same fingerprint
|
|
66
|
-
const existing = this.errorRepo.findByFingerprint(fingerprint);
|
|
67
|
-
if (existing.length > 0) {
|
|
68
|
-
const err = existing[0]!;
|
|
69
|
-
this.errorRepo.incrementOccurrence(err.id);
|
|
70
|
-
this.logger.info(`Known error (id=${err.id}), occurrence incremented`);
|
|
71
|
-
|
|
72
|
-
// Strengthen synapse
|
|
73
|
-
this.synapseManager.strengthen(
|
|
74
|
-
{ type: 'error', id: err.id },
|
|
75
|
-
{ type: 'project', id: project.id },
|
|
76
|
-
'co_occurs',
|
|
77
|
-
);
|
|
78
|
-
|
|
79
|
-
return { errorId: err.id, isNew: false, matches: [] };
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// 5. Create new error record
|
|
83
|
-
const errorId = this.errorRepo.create({
|
|
84
|
-
project_id: project.id,
|
|
85
|
-
terminal_id: input.terminalId ?? null,
|
|
86
|
-
fingerprint,
|
|
87
|
-
type: parsed.errorType,
|
|
88
|
-
message: parsed.message,
|
|
89
|
-
raw_output: input.errorOutput,
|
|
90
|
-
context: null,
|
|
91
|
-
file_path: parsed.sourceFile ?? input.filePath ?? null,
|
|
92
|
-
line_number: parsed.sourceLine ?? null,
|
|
93
|
-
column_number: null,
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
// 6. Create synapse: error ↔ project
|
|
97
|
-
this.synapseManager.strengthen(
|
|
98
|
-
{ type: 'error', id: errorId },
|
|
99
|
-
{ type: 'project', id: project.id },
|
|
100
|
-
'co_occurs',
|
|
101
|
-
);
|
|
102
|
-
|
|
103
|
-
// 7. Find similar errors
|
|
104
|
-
const candidates = this.errorRepo.findByProject(project.id)
|
|
105
|
-
.filter(e => e.id !== errorId);
|
|
106
|
-
const newError = this.errorRepo.getById(errorId)!;
|
|
107
|
-
const matches = matchError(newError, candidates);
|
|
108
|
-
|
|
109
|
-
// 8. Create similarity synapses for strong matches
|
|
110
|
-
for (const match of matches.filter(m => m.isStrong)) {
|
|
111
|
-
this.synapseManager.strengthen(
|
|
112
|
-
{ type: 'error', id: errorId },
|
|
113
|
-
{ type: 'error', id: match.errorId },
|
|
114
|
-
'similar_to',
|
|
115
|
-
);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
this.eventBus.emit('error:reported', { errorId, projectId: project.id, fingerprint });
|
|
119
|
-
this.logger.info(`New error reported (id=${errorId}, type=${parsed.errorType})`);
|
|
120
|
-
|
|
121
|
-
return { errorId, isNew: true, matches };
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
query(input: ErrorQueryInput): ErrorRecord[] {
|
|
125
|
-
if (input.search) {
|
|
126
|
-
return this.errorRepo.search(input.search);
|
|
127
|
-
}
|
|
128
|
-
if (input.resolved === false) {
|
|
129
|
-
return this.errorRepo.findUnresolved(input.projectId);
|
|
130
|
-
}
|
|
131
|
-
if (input.projectId) {
|
|
132
|
-
return this.errorRepo.findByProject(input.projectId);
|
|
133
|
-
}
|
|
134
|
-
return [];
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
matchSimilar(errorId: number): MatchResult[] {
|
|
138
|
-
const error = this.errorRepo.getById(errorId);
|
|
139
|
-
if (!error) return [];
|
|
140
|
-
|
|
141
|
-
const candidates = this.errorRepo.findByProject(error.project_id)
|
|
142
|
-
.filter(e => e.id !== errorId);
|
|
143
|
-
return matchError(error, candidates);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
resolve(errorId: number, solutionId?: number): void {
|
|
147
|
-
this.errorRepo.update(errorId, {
|
|
148
|
-
resolved: 1,
|
|
149
|
-
resolved_at: new Date().toISOString(),
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
if (solutionId) {
|
|
153
|
-
this.eventBus.emit('error:resolved', { errorId, solutionId });
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
getById(id: number): ErrorRecord | undefined {
|
|
158
|
-
return this.errorRepo.getById(id);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
countSince(since: string, projectId?: number): number {
|
|
162
|
-
return this.errorRepo.countSince(since, projectId);
|
|
163
|
-
}
|
|
164
|
-
}
|
|
1
|
+
import type { ErrorRecord } from '../types/error.types.js';
|
|
2
|
+
import type { ErrorRepository } from '../db/repositories/error.repository.js';
|
|
3
|
+
import type { ProjectRepository } from '../db/repositories/project.repository.js';
|
|
4
|
+
import type { SynapseManager } from '../synapses/synapse-manager.js';
|
|
5
|
+
import { parseError } from '../parsing/error-parser.js';
|
|
6
|
+
import { generateFingerprint } from '../matching/fingerprint.js';
|
|
7
|
+
import { matchError, type MatchResult } from '../matching/error-matcher.js';
|
|
8
|
+
import { getEventBus } from '../utils/events.js';
|
|
9
|
+
import { getLogger } from '../utils/logger.js';
|
|
10
|
+
|
|
11
|
+
export interface ReportErrorInput {
|
|
12
|
+
project: string;
|
|
13
|
+
errorOutput: string;
|
|
14
|
+
filePath?: string;
|
|
15
|
+
terminalId?: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface ErrorQueryInput {
|
|
19
|
+
projectId?: number;
|
|
20
|
+
resolved?: boolean;
|
|
21
|
+
search?: string;
|
|
22
|
+
limit?: number;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export class ErrorService {
|
|
26
|
+
private logger = getLogger();
|
|
27
|
+
private eventBus = getEventBus();
|
|
28
|
+
|
|
29
|
+
constructor(
|
|
30
|
+
private errorRepo: ErrorRepository,
|
|
31
|
+
private projectRepo: ProjectRepository,
|
|
32
|
+
private synapseManager: SynapseManager,
|
|
33
|
+
) {}
|
|
34
|
+
|
|
35
|
+
report(input: ReportErrorInput): { errorId: number; isNew: boolean; matches: MatchResult[] } {
|
|
36
|
+
// 1. Ensure project exists
|
|
37
|
+
let project = this.projectRepo.findByName(input.project);
|
|
38
|
+
if (!project) {
|
|
39
|
+
const id = this.projectRepo.create({ name: input.project, path: null, language: null, framework: null });
|
|
40
|
+
project = this.projectRepo.getById(id)!;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// 2. Parse the error
|
|
44
|
+
const parsed = parseError(input.errorOutput);
|
|
45
|
+
if (!parsed) {
|
|
46
|
+
this.logger.warn('Could not parse error output');
|
|
47
|
+
const errorId = this.errorRepo.create({
|
|
48
|
+
project_id: project.id,
|
|
49
|
+
terminal_id: input.terminalId ?? null,
|
|
50
|
+
fingerprint: '',
|
|
51
|
+
type: 'UnknownError',
|
|
52
|
+
message: input.errorOutput.split('\n')[0] ?? input.errorOutput,
|
|
53
|
+
raw_output: input.errorOutput,
|
|
54
|
+
context: null,
|
|
55
|
+
file_path: input.filePath ?? null,
|
|
56
|
+
line_number: null,
|
|
57
|
+
column_number: null,
|
|
58
|
+
});
|
|
59
|
+
return { errorId, isNew: true, matches: [] };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// 3. Generate fingerprint
|
|
63
|
+
const fingerprint = generateFingerprint(parsed.errorType, parsed.message, parsed.frames);
|
|
64
|
+
|
|
65
|
+
// 4. Check for existing error with same fingerprint
|
|
66
|
+
const existing = this.errorRepo.findByFingerprint(fingerprint);
|
|
67
|
+
if (existing.length > 0) {
|
|
68
|
+
const err = existing[0]!;
|
|
69
|
+
this.errorRepo.incrementOccurrence(err.id);
|
|
70
|
+
this.logger.info(`Known error (id=${err.id}), occurrence incremented`);
|
|
71
|
+
|
|
72
|
+
// Strengthen synapse
|
|
73
|
+
this.synapseManager.strengthen(
|
|
74
|
+
{ type: 'error', id: err.id },
|
|
75
|
+
{ type: 'project', id: project.id },
|
|
76
|
+
'co_occurs',
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
return { errorId: err.id, isNew: false, matches: [] };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// 5. Create new error record
|
|
83
|
+
const errorId = this.errorRepo.create({
|
|
84
|
+
project_id: project.id,
|
|
85
|
+
terminal_id: input.terminalId ?? null,
|
|
86
|
+
fingerprint,
|
|
87
|
+
type: parsed.errorType,
|
|
88
|
+
message: parsed.message,
|
|
89
|
+
raw_output: input.errorOutput,
|
|
90
|
+
context: null,
|
|
91
|
+
file_path: parsed.sourceFile ?? input.filePath ?? null,
|
|
92
|
+
line_number: parsed.sourceLine ?? null,
|
|
93
|
+
column_number: null,
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// 6. Create synapse: error ↔ project
|
|
97
|
+
this.synapseManager.strengthen(
|
|
98
|
+
{ type: 'error', id: errorId },
|
|
99
|
+
{ type: 'project', id: project.id },
|
|
100
|
+
'co_occurs',
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
// 7. Find similar errors
|
|
104
|
+
const candidates = this.errorRepo.findByProject(project.id)
|
|
105
|
+
.filter(e => e.id !== errorId);
|
|
106
|
+
const newError = this.errorRepo.getById(errorId)!;
|
|
107
|
+
const matches = matchError(newError, candidates);
|
|
108
|
+
|
|
109
|
+
// 8. Create similarity synapses for strong matches
|
|
110
|
+
for (const match of matches.filter(m => m.isStrong)) {
|
|
111
|
+
this.synapseManager.strengthen(
|
|
112
|
+
{ type: 'error', id: errorId },
|
|
113
|
+
{ type: 'error', id: match.errorId },
|
|
114
|
+
'similar_to',
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
this.eventBus.emit('error:reported', { errorId, projectId: project.id, fingerprint });
|
|
119
|
+
this.logger.info(`New error reported (id=${errorId}, type=${parsed.errorType})`);
|
|
120
|
+
|
|
121
|
+
return { errorId, isNew: true, matches };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
query(input: ErrorQueryInput): ErrorRecord[] {
|
|
125
|
+
if (input.search) {
|
|
126
|
+
return this.errorRepo.search(input.search);
|
|
127
|
+
}
|
|
128
|
+
if (input.resolved === false) {
|
|
129
|
+
return this.errorRepo.findUnresolved(input.projectId);
|
|
130
|
+
}
|
|
131
|
+
if (input.projectId) {
|
|
132
|
+
return this.errorRepo.findByProject(input.projectId);
|
|
133
|
+
}
|
|
134
|
+
return [];
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
matchSimilar(errorId: number): MatchResult[] {
|
|
138
|
+
const error = this.errorRepo.getById(errorId);
|
|
139
|
+
if (!error) return [];
|
|
140
|
+
|
|
141
|
+
const candidates = this.errorRepo.findByProject(error.project_id)
|
|
142
|
+
.filter(e => e.id !== errorId);
|
|
143
|
+
return matchError(error, candidates);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
resolve(errorId: number, solutionId?: number): void {
|
|
147
|
+
this.errorRepo.update(errorId, {
|
|
148
|
+
resolved: 1,
|
|
149
|
+
resolved_at: new Date().toISOString(),
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
if (solutionId) {
|
|
153
|
+
this.eventBus.emit('error:resolved', { errorId, solutionId });
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
getById(id: number): ErrorRecord | undefined {
|
|
158
|
+
return this.errorRepo.getById(id);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
countSince(since: string, projectId?: number): number {
|
|
162
|
+
return this.errorRepo.countSince(since, projectId);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
@@ -1,41 +1,41 @@
|
|
|
1
|
-
import type { NotificationRepository, NotificationRecord } from '../db/repositories/notification.repository.js';
|
|
2
|
-
import { getLogger } from '../utils/logger.js';
|
|
3
|
-
|
|
4
|
-
export interface CreateNotificationInput {
|
|
5
|
-
type: string;
|
|
6
|
-
title: string;
|
|
7
|
-
message: string;
|
|
8
|
-
priority?: number;
|
|
9
|
-
projectId?: number;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export class NotificationService {
|
|
13
|
-
private logger = getLogger();
|
|
14
|
-
|
|
15
|
-
constructor(private notificationRepo: NotificationRepository) {}
|
|
16
|
-
|
|
17
|
-
create(input: CreateNotificationInput): number {
|
|
18
|
-
const id = this.notificationRepo.create({
|
|
19
|
-
type: input.type,
|
|
20
|
-
title: input.title,
|
|
21
|
-
message: input.message,
|
|
22
|
-
priority: input.priority ?? 0,
|
|
23
|
-
project_id: input.projectId ?? null,
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
this.logger.info(`Notification created (id=${id}, type=${input.type})`);
|
|
27
|
-
return id;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
list(projectId?: number): NotificationRecord[] {
|
|
31
|
-
return this.notificationRepo.findUnacknowledged(projectId);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
acknowledge(id: number): void {
|
|
35
|
-
this.notificationRepo.acknowledge(id);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
getById(id: number): NotificationRecord | undefined {
|
|
39
|
-
return this.notificationRepo.getById(id);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
1
|
+
import type { NotificationRepository, NotificationRecord } from '../db/repositories/notification.repository.js';
|
|
2
|
+
import { getLogger } from '../utils/logger.js';
|
|
3
|
+
|
|
4
|
+
export interface CreateNotificationInput {
|
|
5
|
+
type: string;
|
|
6
|
+
title: string;
|
|
7
|
+
message: string;
|
|
8
|
+
priority?: number;
|
|
9
|
+
projectId?: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export class NotificationService {
|
|
13
|
+
private logger = getLogger();
|
|
14
|
+
|
|
15
|
+
constructor(private notificationRepo: NotificationRepository) {}
|
|
16
|
+
|
|
17
|
+
create(input: CreateNotificationInput): number {
|
|
18
|
+
const id = this.notificationRepo.create({
|
|
19
|
+
type: input.type,
|
|
20
|
+
title: input.title,
|
|
21
|
+
message: input.message,
|
|
22
|
+
priority: input.priority ?? 0,
|
|
23
|
+
project_id: input.projectId ?? null,
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
this.logger.info(`Notification created (id=${id}, type=${input.type})`);
|
|
27
|
+
return id;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
list(projectId?: number): NotificationRecord[] {
|
|
31
|
+
return this.notificationRepo.findUnacknowledged(projectId);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
acknowledge(id: number): void {
|
|
35
|
+
this.notificationRepo.acknowledge(id);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
getById(id: number): NotificationRecord | undefined {
|
|
39
|
+
return this.notificationRepo.getById(id);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -1,119 +1,119 @@
|
|
|
1
|
-
import type { RuleRepository } from '../db/repositories/rule.repository.js';
|
|
2
|
-
import type { AntipatternRepository } from '../db/repositories/antipattern.repository.js';
|
|
3
|
-
import type { SynapseManager } from '../synapses/synapse-manager.js';
|
|
4
|
-
import { getLogger } from '../utils/logger.js';
|
|
5
|
-
|
|
6
|
-
export interface RuleCheckResult {
|
|
7
|
-
matched: boolean;
|
|
8
|
-
ruleId: number;
|
|
9
|
-
action: string;
|
|
10
|
-
description: string | null;
|
|
11
|
-
confidence: number;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export interface AntipatternCheckResult {
|
|
15
|
-
matched: boolean;
|
|
16
|
-
antipatternId: number;
|
|
17
|
-
pattern: string;
|
|
18
|
-
description: string;
|
|
19
|
-
severity: string;
|
|
20
|
-
suggestion: string | null;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export class PreventionService {
|
|
24
|
-
private logger = getLogger();
|
|
25
|
-
|
|
26
|
-
constructor(
|
|
27
|
-
private ruleRepo: RuleRepository,
|
|
28
|
-
private antipatternRepo: AntipatternRepository,
|
|
29
|
-
private synapseManager: SynapseManager,
|
|
30
|
-
) {}
|
|
31
|
-
|
|
32
|
-
checkRules(errorType: string, message: string, projectId?: number): RuleCheckResult[] {
|
|
33
|
-
const rules = this.ruleRepo.findActive(projectId);
|
|
34
|
-
const results: RuleCheckResult[] = [];
|
|
35
|
-
|
|
36
|
-
for (const rule of rules) {
|
|
37
|
-
try {
|
|
38
|
-
const pattern = new RegExp(rule.pattern, 'i');
|
|
39
|
-
const input = `${errorType}: ${message}`;
|
|
40
|
-
|
|
41
|
-
if (pattern.test(input)) {
|
|
42
|
-
results.push({
|
|
43
|
-
matched: true,
|
|
44
|
-
ruleId: rule.id,
|
|
45
|
-
action: rule.action,
|
|
46
|
-
description: rule.description,
|
|
47
|
-
confidence: rule.confidence,
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
this.logger.debug(`Rule ${rule.id} matched: ${rule.pattern}`);
|
|
51
|
-
}
|
|
52
|
-
} catch {
|
|
53
|
-
// Invalid regex in rule pattern, skip
|
|
54
|
-
this.logger.warn(`Invalid regex in rule ${rule.id}: ${rule.pattern}`);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
return results.sort((a, b) => b.confidence - a.confidence);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
checkAntipatterns(errorType: string, message: string, projectId?: number): AntipatternCheckResult[] {
|
|
62
|
-
const antipatterns = projectId
|
|
63
|
-
? [...this.antipatternRepo.findByProject(projectId), ...this.antipatternRepo.findGlobal()]
|
|
64
|
-
: this.antipatternRepo.findGlobal();
|
|
65
|
-
|
|
66
|
-
const results: AntipatternCheckResult[] = [];
|
|
67
|
-
const input = `${errorType}: ${message}`;
|
|
68
|
-
|
|
69
|
-
for (const ap of antipatterns) {
|
|
70
|
-
try {
|
|
71
|
-
const pattern = new RegExp(ap.pattern, 'i');
|
|
72
|
-
if (pattern.test(input)) {
|
|
73
|
-
results.push({
|
|
74
|
-
matched: true,
|
|
75
|
-
antipatternId: ap.id,
|
|
76
|
-
pattern: ap.pattern,
|
|
77
|
-
description: ap.description,
|
|
78
|
-
severity: ap.severity,
|
|
79
|
-
suggestion: ap.suggestion,
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
} catch {
|
|
83
|
-
this.logger.warn(`Invalid regex in antipattern ${ap.id}: ${ap.pattern}`);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
return results;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
createRule(data: {
|
|
91
|
-
pattern: string;
|
|
92
|
-
action: string;
|
|
93
|
-
description?: string;
|
|
94
|
-
confidence?: number;
|
|
95
|
-
projectId?: number;
|
|
96
|
-
}): number {
|
|
97
|
-
return this.ruleRepo.create({
|
|
98
|
-
pattern: data.pattern,
|
|
99
|
-
action: data.action,
|
|
100
|
-
description: data.description ?? null,
|
|
101
|
-
confidence: data.confidence ?? 0.5,
|
|
102
|
-
occurrences: 0,
|
|
103
|
-
active: 1,
|
|
104
|
-
project_id: data.projectId ?? null,
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
reportPrevention(ruleId: number, errorId: number): void {
|
|
109
|
-
const rule = this.ruleRepo.getById(ruleId);
|
|
110
|
-
if (rule) {
|
|
111
|
-
this.ruleRepo.update(ruleId, { occurrences: rule.occurrences + 1 });
|
|
112
|
-
this.synapseManager.strengthen(
|
|
113
|
-
{ type: 'rule', id: ruleId },
|
|
114
|
-
{ type: 'error', id: errorId },
|
|
115
|
-
'prevents',
|
|
116
|
-
);
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
}
|
|
1
|
+
import type { RuleRepository } from '../db/repositories/rule.repository.js';
|
|
2
|
+
import type { AntipatternRepository } from '../db/repositories/antipattern.repository.js';
|
|
3
|
+
import type { SynapseManager } from '../synapses/synapse-manager.js';
|
|
4
|
+
import { getLogger } from '../utils/logger.js';
|
|
5
|
+
|
|
6
|
+
export interface RuleCheckResult {
|
|
7
|
+
matched: boolean;
|
|
8
|
+
ruleId: number;
|
|
9
|
+
action: string;
|
|
10
|
+
description: string | null;
|
|
11
|
+
confidence: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface AntipatternCheckResult {
|
|
15
|
+
matched: boolean;
|
|
16
|
+
antipatternId: number;
|
|
17
|
+
pattern: string;
|
|
18
|
+
description: string;
|
|
19
|
+
severity: string;
|
|
20
|
+
suggestion: string | null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export class PreventionService {
|
|
24
|
+
private logger = getLogger();
|
|
25
|
+
|
|
26
|
+
constructor(
|
|
27
|
+
private ruleRepo: RuleRepository,
|
|
28
|
+
private antipatternRepo: AntipatternRepository,
|
|
29
|
+
private synapseManager: SynapseManager,
|
|
30
|
+
) {}
|
|
31
|
+
|
|
32
|
+
checkRules(errorType: string, message: string, projectId?: number): RuleCheckResult[] {
|
|
33
|
+
const rules = this.ruleRepo.findActive(projectId);
|
|
34
|
+
const results: RuleCheckResult[] = [];
|
|
35
|
+
|
|
36
|
+
for (const rule of rules) {
|
|
37
|
+
try {
|
|
38
|
+
const pattern = new RegExp(rule.pattern, 'i');
|
|
39
|
+
const input = `${errorType}: ${message}`;
|
|
40
|
+
|
|
41
|
+
if (pattern.test(input)) {
|
|
42
|
+
results.push({
|
|
43
|
+
matched: true,
|
|
44
|
+
ruleId: rule.id,
|
|
45
|
+
action: rule.action,
|
|
46
|
+
description: rule.description,
|
|
47
|
+
confidence: rule.confidence,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
this.logger.debug(`Rule ${rule.id} matched: ${rule.pattern}`);
|
|
51
|
+
}
|
|
52
|
+
} catch {
|
|
53
|
+
// Invalid regex in rule pattern, skip
|
|
54
|
+
this.logger.warn(`Invalid regex in rule ${rule.id}: ${rule.pattern}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return results.sort((a, b) => b.confidence - a.confidence);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
checkAntipatterns(errorType: string, message: string, projectId?: number): AntipatternCheckResult[] {
|
|
62
|
+
const antipatterns = projectId
|
|
63
|
+
? [...this.antipatternRepo.findByProject(projectId), ...this.antipatternRepo.findGlobal()]
|
|
64
|
+
: this.antipatternRepo.findGlobal();
|
|
65
|
+
|
|
66
|
+
const results: AntipatternCheckResult[] = [];
|
|
67
|
+
const input = `${errorType}: ${message}`;
|
|
68
|
+
|
|
69
|
+
for (const ap of antipatterns) {
|
|
70
|
+
try {
|
|
71
|
+
const pattern = new RegExp(ap.pattern, 'i');
|
|
72
|
+
if (pattern.test(input)) {
|
|
73
|
+
results.push({
|
|
74
|
+
matched: true,
|
|
75
|
+
antipatternId: ap.id,
|
|
76
|
+
pattern: ap.pattern,
|
|
77
|
+
description: ap.description,
|
|
78
|
+
severity: ap.severity,
|
|
79
|
+
suggestion: ap.suggestion,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
} catch {
|
|
83
|
+
this.logger.warn(`Invalid regex in antipattern ${ap.id}: ${ap.pattern}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return results;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
createRule(data: {
|
|
91
|
+
pattern: string;
|
|
92
|
+
action: string;
|
|
93
|
+
description?: string;
|
|
94
|
+
confidence?: number;
|
|
95
|
+
projectId?: number;
|
|
96
|
+
}): number {
|
|
97
|
+
return this.ruleRepo.create({
|
|
98
|
+
pattern: data.pattern,
|
|
99
|
+
action: data.action,
|
|
100
|
+
description: data.description ?? null,
|
|
101
|
+
confidence: data.confidence ?? 0.5,
|
|
102
|
+
occurrences: 0,
|
|
103
|
+
active: 1,
|
|
104
|
+
project_id: data.projectId ?? null,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
reportPrevention(ruleId: number, errorId: number): void {
|
|
109
|
+
const rule = this.ruleRepo.getById(ruleId);
|
|
110
|
+
if (rule) {
|
|
111
|
+
this.ruleRepo.update(ruleId, { occurrences: rule.occurrences + 1 });
|
|
112
|
+
this.synapseManager.strengthen(
|
|
113
|
+
{ type: 'rule', id: ruleId },
|
|
114
|
+
{ type: 'error', id: errorId },
|
|
115
|
+
'prevents',
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|