@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,132 +1,132 @@
|
|
|
1
|
-
import { execSync } from 'node:child_process';
|
|
2
|
-
import type Database from 'better-sqlite3';
|
|
3
|
-
import type { SynapseManager } from '../synapses/synapse-manager.js';
|
|
4
|
-
import { getLogger } from '../utils/logger.js';
|
|
5
|
-
|
|
6
|
-
export interface GitCommitInfo {
|
|
7
|
-
hash: string;
|
|
8
|
-
message: string;
|
|
9
|
-
author: string;
|
|
10
|
-
timestamp: string;
|
|
11
|
-
filesChanged: number;
|
|
12
|
-
insertions: number;
|
|
13
|
-
deletions: number;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export class GitService {
|
|
17
|
-
private logger = getLogger();
|
|
18
|
-
|
|
19
|
-
constructor(
|
|
20
|
-
private db: Database.Database,
|
|
21
|
-
private synapseManager: SynapseManager,
|
|
22
|
-
) {}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Get current git info for context enrichment
|
|
26
|
-
*/
|
|
27
|
-
getGitContext(cwd?: string): { branch: string | null; diff: string | null; lastCommit: string | null } {
|
|
28
|
-
try {
|
|
29
|
-
const opts = cwd ? { cwd, timeout: 5000 } : { timeout: 5000 };
|
|
30
|
-
const branch = execSync('git rev-parse --abbrev-ref HEAD', { ...opts, encoding: 'utf8' }).trim();
|
|
31
|
-
const diff = execSync('git diff --stat HEAD', { ...opts, encoding: 'utf8' }).trim();
|
|
32
|
-
const lastCommit = execSync('git log -1 --pretty=format:"%H %s"', { ...opts, encoding: 'utf8' }).trim();
|
|
33
|
-
return { branch, diff: diff || null, lastCommit };
|
|
34
|
-
} catch {
|
|
35
|
-
return { branch: null, diff: null, lastCommit: null };
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Store a git commit and link it to an error
|
|
41
|
-
*/
|
|
42
|
-
linkErrorToCommit(errorId: number, projectId: number, commitHash: string, relationship: string = 'introduced_by'): void {
|
|
43
|
-
try {
|
|
44
|
-
// Store commit info
|
|
45
|
-
const commitInfo = this.getCommitInfo(commitHash);
|
|
46
|
-
if (commitInfo) {
|
|
47
|
-
this.db.prepare(`
|
|
48
|
-
INSERT OR IGNORE INTO git_commits (project_id, commit_hash, message, author, timestamp, files_changed, insertions, deletions)
|
|
49
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
50
|
-
`).run(projectId, commitInfo.hash, commitInfo.message, commitInfo.author, commitInfo.timestamp,
|
|
51
|
-
commitInfo.filesChanged, commitInfo.insertions, commitInfo.deletions);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// Link error to commit
|
|
55
|
-
this.db.prepare(`
|
|
56
|
-
INSERT OR IGNORE INTO error_commits (error_id, commit_hash, relationship)
|
|
57
|
-
VALUES (?, ?, ?)
|
|
58
|
-
`).run(errorId, commitHash, relationship);
|
|
59
|
-
|
|
60
|
-
this.logger.info(`Linked error #${errorId} to commit ${commitHash.slice(0, 8)} (${relationship})`);
|
|
61
|
-
} catch (err) {
|
|
62
|
-
this.logger.warn(`Failed to link error to commit: ${err}`);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Find which commit introduced an error
|
|
68
|
-
*/
|
|
69
|
-
findIntroducingCommit(errorId: number): Array<{ commitHash: string; message: string; relationship: string }> {
|
|
70
|
-
const rows = this.db.prepare(`
|
|
71
|
-
SELECT ec.commit_hash, ec.relationship, gc.message
|
|
72
|
-
FROM error_commits ec
|
|
73
|
-
LEFT JOIN git_commits gc ON ec.commit_hash = gc.commit_hash
|
|
74
|
-
WHERE ec.error_id = ?
|
|
75
|
-
ORDER BY ec.created_at DESC
|
|
76
|
-
`).all(errorId) as Array<{ commit_hash: string; message: string | null; relationship: string }>;
|
|
77
|
-
|
|
78
|
-
return rows.map(r => ({
|
|
79
|
-
commitHash: r.commit_hash,
|
|
80
|
-
message: r.message ?? 'unknown',
|
|
81
|
-
relationship: r.relationship,
|
|
82
|
-
}));
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Find errors introduced by a specific commit
|
|
87
|
-
*/
|
|
88
|
-
findErrorsByCommit(commitHash: string): Array<{ errorId: number; relationship: string }> {
|
|
89
|
-
const rows = this.db.prepare(`
|
|
90
|
-
SELECT error_id, relationship FROM error_commits WHERE commit_hash = ?
|
|
91
|
-
`).all(commitHash) as Array<{ error_id: number; relationship: string }>;
|
|
92
|
-
|
|
93
|
-
return rows.map(r => ({ errorId: r.error_id, relationship: r.relationship }));
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Capture current git diff for error context
|
|
98
|
-
*/
|
|
99
|
-
captureDiff(cwd?: string): string | null {
|
|
100
|
-
try {
|
|
101
|
-
const opts = cwd ? { cwd, timeout: 5000, encoding: 'utf8' as const } : { timeout: 5000, encoding: 'utf8' as const };
|
|
102
|
-
const diff = execSync('git diff HEAD --no-color', opts).trim();
|
|
103
|
-
// Truncate to avoid huge diffs
|
|
104
|
-
return diff.length > 5000 ? diff.slice(0, 5000) + '\n... (truncated)' : diff || null;
|
|
105
|
-
} catch {
|
|
106
|
-
return null;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
private getCommitInfo(hash: string, cwd?: string): GitCommitInfo | null {
|
|
111
|
-
try {
|
|
112
|
-
const opts = cwd ? { cwd, timeout: 5000, encoding: 'utf8' as const } : { timeout: 5000, encoding: 'utf8' as const };
|
|
113
|
-
const info = execSync(`git log -1 --pretty=format:"%H|||%s|||%an|||%aI" ${hash}`, opts).trim();
|
|
114
|
-
const [commitHash, message, author, timestamp] = info.split('|||');
|
|
115
|
-
|
|
116
|
-
const stat = execSync(`git diff --stat ${hash}~1..${hash}`, opts).trim();
|
|
117
|
-
const statMatch = stat.match(/(\d+) files? changed(?:, (\d+) insertions?\(\+\))?(?:, (\d+) deletions?\(-\))?/);
|
|
118
|
-
|
|
119
|
-
return {
|
|
120
|
-
hash: commitHash ?? hash,
|
|
121
|
-
message: message ?? '',
|
|
122
|
-
author: author ?? '',
|
|
123
|
-
timestamp: timestamp ?? new Date().toISOString(),
|
|
124
|
-
filesChanged: parseInt(statMatch?.[1] ?? '0', 10),
|
|
125
|
-
insertions: parseInt(statMatch?.[2] ?? '0', 10),
|
|
126
|
-
deletions: parseInt(statMatch?.[3] ?? '0', 10),
|
|
127
|
-
};
|
|
128
|
-
} catch {
|
|
129
|
-
return null;
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
}
|
|
1
|
+
import { execSync } from 'node:child_process';
|
|
2
|
+
import type Database from 'better-sqlite3';
|
|
3
|
+
import type { SynapseManager } from '../synapses/synapse-manager.js';
|
|
4
|
+
import { getLogger } from '../utils/logger.js';
|
|
5
|
+
|
|
6
|
+
export interface GitCommitInfo {
|
|
7
|
+
hash: string;
|
|
8
|
+
message: string;
|
|
9
|
+
author: string;
|
|
10
|
+
timestamp: string;
|
|
11
|
+
filesChanged: number;
|
|
12
|
+
insertions: number;
|
|
13
|
+
deletions: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export class GitService {
|
|
17
|
+
private logger = getLogger();
|
|
18
|
+
|
|
19
|
+
constructor(
|
|
20
|
+
private db: Database.Database,
|
|
21
|
+
private synapseManager: SynapseManager,
|
|
22
|
+
) {}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Get current git info for context enrichment
|
|
26
|
+
*/
|
|
27
|
+
getGitContext(cwd?: string): { branch: string | null; diff: string | null; lastCommit: string | null } {
|
|
28
|
+
try {
|
|
29
|
+
const opts = cwd ? { cwd, timeout: 5000 } : { timeout: 5000 };
|
|
30
|
+
const branch = execSync('git rev-parse --abbrev-ref HEAD', { ...opts, encoding: 'utf8' }).trim();
|
|
31
|
+
const diff = execSync('git diff --stat HEAD', { ...opts, encoding: 'utf8' }).trim();
|
|
32
|
+
const lastCommit = execSync('git log -1 --pretty=format:"%H %s"', { ...opts, encoding: 'utf8' }).trim();
|
|
33
|
+
return { branch, diff: diff || null, lastCommit };
|
|
34
|
+
} catch {
|
|
35
|
+
return { branch: null, diff: null, lastCommit: null };
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Store a git commit and link it to an error
|
|
41
|
+
*/
|
|
42
|
+
linkErrorToCommit(errorId: number, projectId: number, commitHash: string, relationship: string = 'introduced_by'): void {
|
|
43
|
+
try {
|
|
44
|
+
// Store commit info
|
|
45
|
+
const commitInfo = this.getCommitInfo(commitHash);
|
|
46
|
+
if (commitInfo) {
|
|
47
|
+
this.db.prepare(`
|
|
48
|
+
INSERT OR IGNORE INTO git_commits (project_id, commit_hash, message, author, timestamp, files_changed, insertions, deletions)
|
|
49
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
50
|
+
`).run(projectId, commitInfo.hash, commitInfo.message, commitInfo.author, commitInfo.timestamp,
|
|
51
|
+
commitInfo.filesChanged, commitInfo.insertions, commitInfo.deletions);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Link error to commit
|
|
55
|
+
this.db.prepare(`
|
|
56
|
+
INSERT OR IGNORE INTO error_commits (error_id, commit_hash, relationship)
|
|
57
|
+
VALUES (?, ?, ?)
|
|
58
|
+
`).run(errorId, commitHash, relationship);
|
|
59
|
+
|
|
60
|
+
this.logger.info(`Linked error #${errorId} to commit ${commitHash.slice(0, 8)} (${relationship})`);
|
|
61
|
+
} catch (err) {
|
|
62
|
+
this.logger.warn(`Failed to link error to commit: ${err}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Find which commit introduced an error
|
|
68
|
+
*/
|
|
69
|
+
findIntroducingCommit(errorId: number): Array<{ commitHash: string; message: string; relationship: string }> {
|
|
70
|
+
const rows = this.db.prepare(`
|
|
71
|
+
SELECT ec.commit_hash, ec.relationship, gc.message
|
|
72
|
+
FROM error_commits ec
|
|
73
|
+
LEFT JOIN git_commits gc ON ec.commit_hash = gc.commit_hash
|
|
74
|
+
WHERE ec.error_id = ?
|
|
75
|
+
ORDER BY ec.created_at DESC
|
|
76
|
+
`).all(errorId) as Array<{ commit_hash: string; message: string | null; relationship: string }>;
|
|
77
|
+
|
|
78
|
+
return rows.map(r => ({
|
|
79
|
+
commitHash: r.commit_hash,
|
|
80
|
+
message: r.message ?? 'unknown',
|
|
81
|
+
relationship: r.relationship,
|
|
82
|
+
}));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Find errors introduced by a specific commit
|
|
87
|
+
*/
|
|
88
|
+
findErrorsByCommit(commitHash: string): Array<{ errorId: number; relationship: string }> {
|
|
89
|
+
const rows = this.db.prepare(`
|
|
90
|
+
SELECT error_id, relationship FROM error_commits WHERE commit_hash = ?
|
|
91
|
+
`).all(commitHash) as Array<{ error_id: number; relationship: string }>;
|
|
92
|
+
|
|
93
|
+
return rows.map(r => ({ errorId: r.error_id, relationship: r.relationship }));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Capture current git diff for error context
|
|
98
|
+
*/
|
|
99
|
+
captureDiff(cwd?: string): string | null {
|
|
100
|
+
try {
|
|
101
|
+
const opts = cwd ? { cwd, timeout: 5000, encoding: 'utf8' as const } : { timeout: 5000, encoding: 'utf8' as const };
|
|
102
|
+
const diff = execSync('git diff HEAD --no-color', opts).trim();
|
|
103
|
+
// Truncate to avoid huge diffs
|
|
104
|
+
return diff.length > 5000 ? diff.slice(0, 5000) + '\n... (truncated)' : diff || null;
|
|
105
|
+
} catch {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
private getCommitInfo(hash: string, cwd?: string): GitCommitInfo | null {
|
|
111
|
+
try {
|
|
112
|
+
const opts = cwd ? { cwd, timeout: 5000, encoding: 'utf8' as const } : { timeout: 5000, encoding: 'utf8' as const };
|
|
113
|
+
const info = execSync(`git log -1 --pretty=format:"%H|||%s|||%an|||%aI" ${hash}`, opts).trim();
|
|
114
|
+
const [commitHash, message, author, timestamp] = info.split('|||');
|
|
115
|
+
|
|
116
|
+
const stat = execSync(`git diff --stat ${hash}~1..${hash}`, opts).trim();
|
|
117
|
+
const statMatch = stat.match(/(\d+) files? changed(?:, (\d+) insertions?\(\+\))?(?:, (\d+) deletions?\(-\))?/);
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
hash: commitHash ?? hash,
|
|
121
|
+
message: message ?? '',
|
|
122
|
+
author: author ?? '',
|
|
123
|
+
timestamp: timestamp ?? new Date().toISOString(),
|
|
124
|
+
filesChanged: parseInt(statMatch?.[1] ?? '0', 10),
|
|
125
|
+
insertions: parseInt(statMatch?.[2] ?? '0', 10),
|
|
126
|
+
deletions: parseInt(statMatch?.[3] ?? '0', 10),
|
|
127
|
+
};
|
|
128
|
+
} catch {
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
@@ -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,59 +1,59 @@
|
|
|
1
|
-
import type { NodeType, SynapseRecord, NetworkStats } from '../types/synapse.types.js';
|
|
2
|
-
import type { SynapseManager } from '../synapses/synapse-manager.js';
|
|
3
|
-
import type { ActivationResult } from '../synapses/activation.js';
|
|
4
|
-
import type { SynapsePath } from '../synapses/pathfinder.js';
|
|
5
|
-
|
|
6
|
-
export interface ErrorContext {
|
|
7
|
-
solutions: ActivationResult[];
|
|
8
|
-
relatedErrors: ActivationResult[];
|
|
9
|
-
relevantModules: ActivationResult[];
|
|
10
|
-
preventionRules: ActivationResult[];
|
|
11
|
-
insights: ActivationResult[];
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export interface RelatedQuery {
|
|
15
|
-
nodeType: NodeType;
|
|
16
|
-
nodeId: number;
|
|
17
|
-
maxDepth?: number;
|
|
18
|
-
minWeight?: number;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export class SynapseService {
|
|
22
|
-
constructor(private manager: SynapseManager) {}
|
|
23
|
-
|
|
24
|
-
getErrorContext(errorId: number): ErrorContext {
|
|
25
|
-
return this.manager.getErrorContext(errorId);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
findPath(
|
|
29
|
-
fromType: NodeType,
|
|
30
|
-
fromId: number,
|
|
31
|
-
toType: NodeType,
|
|
32
|
-
toId: number,
|
|
33
|
-
): SynapsePath | null {
|
|
34
|
-
return this.manager.findPath(
|
|
35
|
-
{ type: fromType, id: fromId },
|
|
36
|
-
{ type: toType, id: toId },
|
|
37
|
-
);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
getRelated(query: RelatedQuery): ActivationResult[] {
|
|
41
|
-
return this.manager.activate(
|
|
42
|
-
{ type: query.nodeType, id: query.nodeId },
|
|
43
|
-
query.maxDepth,
|
|
44
|
-
query.minWeight,
|
|
45
|
-
);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
getNetworkStats(): NetworkStats {
|
|
49
|
-
return this.manager.getNetworkStats();
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
getStrongestSynapses(limit?: number): SynapseRecord[] {
|
|
53
|
-
return this.manager.getStrongestSynapses(limit);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
runDecay(): { decayed: number; pruned: number } {
|
|
57
|
-
return this.manager.runDecay();
|
|
58
|
-
}
|
|
59
|
-
}
|
|
1
|
+
import type { NodeType, SynapseRecord, NetworkStats } from '../types/synapse.types.js';
|
|
2
|
+
import type { SynapseManager } from '../synapses/synapse-manager.js';
|
|
3
|
+
import type { ActivationResult } from '../synapses/activation.js';
|
|
4
|
+
import type { SynapsePath } from '../synapses/pathfinder.js';
|
|
5
|
+
|
|
6
|
+
export interface ErrorContext {
|
|
7
|
+
solutions: ActivationResult[];
|
|
8
|
+
relatedErrors: ActivationResult[];
|
|
9
|
+
relevantModules: ActivationResult[];
|
|
10
|
+
preventionRules: ActivationResult[];
|
|
11
|
+
insights: ActivationResult[];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface RelatedQuery {
|
|
15
|
+
nodeType: NodeType;
|
|
16
|
+
nodeId: number;
|
|
17
|
+
maxDepth?: number;
|
|
18
|
+
minWeight?: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export class SynapseService {
|
|
22
|
+
constructor(private manager: SynapseManager) {}
|
|
23
|
+
|
|
24
|
+
getErrorContext(errorId: number): ErrorContext {
|
|
25
|
+
return this.manager.getErrorContext(errorId);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
findPath(
|
|
29
|
+
fromType: NodeType,
|
|
30
|
+
fromId: number,
|
|
31
|
+
toType: NodeType,
|
|
32
|
+
toId: number,
|
|
33
|
+
): SynapsePath | null {
|
|
34
|
+
return this.manager.findPath(
|
|
35
|
+
{ type: fromType, id: fromId },
|
|
36
|
+
{ type: toType, id: toId },
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
getRelated(query: RelatedQuery): ActivationResult[] {
|
|
41
|
+
return this.manager.activate(
|
|
42
|
+
{ type: query.nodeType, id: query.nodeId },
|
|
43
|
+
query.maxDepth,
|
|
44
|
+
query.minWeight,
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
getNetworkStats(): NetworkStats {
|
|
49
|
+
return this.manager.getNetworkStats();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
getStrongestSynapses(limit?: number): SynapseRecord[] {
|
|
53
|
+
return this.manager.getStrongestSynapses(limit);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
runDecay(): { decayed: number; pruned: number } {
|
|
57
|
+
return this.manager.runDecay();
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -1,81 +1,81 @@
|
|
|
1
|
-
import type { TerminalRepository } from '../db/repositories/terminal.repository.js';
|
|
2
|
-
import { getEventBus } from '../utils/events.js';
|
|
3
|
-
import { getLogger } from '../utils/logger.js';
|
|
4
|
-
|
|
5
|
-
export interface RegisterTerminalInput {
|
|
6
|
-
uuid: string;
|
|
7
|
-
projectId?: number;
|
|
8
|
-
pid?: number;
|
|
9
|
-
shell?: string;
|
|
10
|
-
cwd?: string;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export class TerminalService {
|
|
14
|
-
private logger = getLogger();
|
|
15
|
-
private eventBus = getEventBus();
|
|
16
|
-
|
|
17
|
-
constructor(
|
|
18
|
-
private terminalRepo: TerminalRepository,
|
|
19
|
-
private staleTimeout: number,
|
|
20
|
-
) {}
|
|
21
|
-
|
|
22
|
-
register(input: RegisterTerminalInput): number {
|
|
23
|
-
const existing = this.terminalRepo.findByUuid(input.uuid);
|
|
24
|
-
if (existing) {
|
|
25
|
-
this.terminalRepo.update(existing.id, {
|
|
26
|
-
last_seen: new Date().toISOString(),
|
|
27
|
-
disconnected_at: null,
|
|
28
|
-
project_id: input.projectId ?? existing.project_id,
|
|
29
|
-
cwd: input.cwd ?? existing.cwd,
|
|
30
|
-
});
|
|
31
|
-
this.logger.info(`Terminal reconnected (id=${existing.id}, uuid=${input.uuid})`);
|
|
32
|
-
this.eventBus.emit('terminal:connected', { terminalId: existing.id, uuid: input.uuid });
|
|
33
|
-
return existing.id;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const id = this.terminalRepo.create({
|
|
37
|
-
uuid: input.uuid,
|
|
38
|
-
project_id: input.projectId ?? null,
|
|
39
|
-
pid: input.pid ?? null,
|
|
40
|
-
shell: input.shell ?? null,
|
|
41
|
-
cwd: input.cwd ?? null,
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
this.logger.info(`Terminal registered (id=${id}, uuid=${input.uuid})`);
|
|
45
|
-
this.eventBus.emit('terminal:connected', { terminalId: id, uuid: input.uuid });
|
|
46
|
-
return id;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
heartbeat(uuid: string): void {
|
|
50
|
-
const terminal = this.terminalRepo.findByUuid(uuid);
|
|
51
|
-
if (terminal) {
|
|
52
|
-
this.terminalRepo.update(terminal.id, {
|
|
53
|
-
last_seen: new Date().toISOString(),
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
disconnect(uuid: string): void {
|
|
59
|
-
const terminal = this.terminalRepo.findByUuid(uuid);
|
|
60
|
-
if (terminal) {
|
|
61
|
-
this.terminalRepo.update(terminal.id, {
|
|
62
|
-
disconnected_at: new Date().toISOString(),
|
|
63
|
-
});
|
|
64
|
-
this.eventBus.emit('terminal:disconnected', { terminalId: terminal.id });
|
|
65
|
-
this.logger.info(`Terminal disconnected (id=${terminal.id}, uuid=${uuid})`);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
cleanup(): number {
|
|
70
|
-
const cutoff = new Date(Date.now() - this.staleTimeout).toISOString();
|
|
71
|
-
const count = this.terminalRepo.cleanupStale(cutoff);
|
|
72
|
-
if (count > 0) {
|
|
73
|
-
this.logger.info(`Cleaned up ${count} stale terminal(s)`);
|
|
74
|
-
}
|
|
75
|
-
return count;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
getConnected() {
|
|
79
|
-
return this.terminalRepo.findConnected();
|
|
80
|
-
}
|
|
81
|
-
}
|
|
1
|
+
import type { TerminalRepository } from '../db/repositories/terminal.repository.js';
|
|
2
|
+
import { getEventBus } from '../utils/events.js';
|
|
3
|
+
import { getLogger } from '../utils/logger.js';
|
|
4
|
+
|
|
5
|
+
export interface RegisterTerminalInput {
|
|
6
|
+
uuid: string;
|
|
7
|
+
projectId?: number;
|
|
8
|
+
pid?: number;
|
|
9
|
+
shell?: string;
|
|
10
|
+
cwd?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class TerminalService {
|
|
14
|
+
private logger = getLogger();
|
|
15
|
+
private eventBus = getEventBus();
|
|
16
|
+
|
|
17
|
+
constructor(
|
|
18
|
+
private terminalRepo: TerminalRepository,
|
|
19
|
+
private staleTimeout: number,
|
|
20
|
+
) {}
|
|
21
|
+
|
|
22
|
+
register(input: RegisterTerminalInput): number {
|
|
23
|
+
const existing = this.terminalRepo.findByUuid(input.uuid);
|
|
24
|
+
if (existing) {
|
|
25
|
+
this.terminalRepo.update(existing.id, {
|
|
26
|
+
last_seen: new Date().toISOString(),
|
|
27
|
+
disconnected_at: null,
|
|
28
|
+
project_id: input.projectId ?? existing.project_id,
|
|
29
|
+
cwd: input.cwd ?? existing.cwd,
|
|
30
|
+
});
|
|
31
|
+
this.logger.info(`Terminal reconnected (id=${existing.id}, uuid=${input.uuid})`);
|
|
32
|
+
this.eventBus.emit('terminal:connected', { terminalId: existing.id, uuid: input.uuid });
|
|
33
|
+
return existing.id;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const id = this.terminalRepo.create({
|
|
37
|
+
uuid: input.uuid,
|
|
38
|
+
project_id: input.projectId ?? null,
|
|
39
|
+
pid: input.pid ?? null,
|
|
40
|
+
shell: input.shell ?? null,
|
|
41
|
+
cwd: input.cwd ?? null,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
this.logger.info(`Terminal registered (id=${id}, uuid=${input.uuid})`);
|
|
45
|
+
this.eventBus.emit('terminal:connected', { terminalId: id, uuid: input.uuid });
|
|
46
|
+
return id;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
heartbeat(uuid: string): void {
|
|
50
|
+
const terminal = this.terminalRepo.findByUuid(uuid);
|
|
51
|
+
if (terminal) {
|
|
52
|
+
this.terminalRepo.update(terminal.id, {
|
|
53
|
+
last_seen: new Date().toISOString(),
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
disconnect(uuid: string): void {
|
|
59
|
+
const terminal = this.terminalRepo.findByUuid(uuid);
|
|
60
|
+
if (terminal) {
|
|
61
|
+
this.terminalRepo.update(terminal.id, {
|
|
62
|
+
disconnected_at: new Date().toISOString(),
|
|
63
|
+
});
|
|
64
|
+
this.eventBus.emit('terminal:disconnected', { terminalId: terminal.id });
|
|
65
|
+
this.logger.info(`Terminal disconnected (id=${terminal.id}, uuid=${uuid})`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
cleanup(): number {
|
|
70
|
+
const cutoff = new Date(Date.now() - this.staleTimeout).toISOString();
|
|
71
|
+
const count = this.terminalRepo.cleanupStale(cutoff);
|
|
72
|
+
if (count > 0) {
|
|
73
|
+
this.logger.info(`Cleaned up ${count} stale terminal(s)`);
|
|
74
|
+
}
|
|
75
|
+
return count;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
getConnected() {
|
|
79
|
+
return this.terminalRepo.findConnected();
|
|
80
|
+
}
|
|
81
|
+
}
|