@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,42 +1,119 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
import { withIpc } from '../ipc-helper.js';
|
|
3
|
+
import { c, icons, header, statusBadge, divider } from '../colors.js';
|
|
3
4
|
|
|
4
5
|
export function queryCommand(): Command {
|
|
5
6
|
return new Command('query')
|
|
6
|
-
.description('Search
|
|
7
|
-
.argument('<search>', '
|
|
8
|
-
.option('-l, --limit <n>', 'Maximum results', '10')
|
|
7
|
+
.description('Search errors, code modules, and insights')
|
|
8
|
+
.argument('<search>', 'Search term')
|
|
9
|
+
.option('-l, --limit <n>', 'Maximum results per category', '10')
|
|
10
|
+
.option('--errors-only', 'Only search errors')
|
|
11
|
+
.option('--modules-only', 'Only search code modules')
|
|
12
|
+
.option('--insights-only', 'Only search insights')
|
|
13
|
+
.option('--page <n>', 'Page number (starting from 1)', '1')
|
|
9
14
|
.action(async (search: string, opts) => {
|
|
10
15
|
await withIpc(async (client) => {
|
|
11
|
-
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
const limit = parseInt(opts.limit, 10);
|
|
17
|
+
const page = parseInt(opts.page, 10) || 1;
|
|
18
|
+
const offset = (page - 1) * limit;
|
|
19
|
+
const searchAll = !opts.errorsOnly && !opts.modulesOnly && !opts.insightsOnly;
|
|
20
|
+
let totalResults = 0;
|
|
21
|
+
|
|
22
|
+
// --- Errors ---
|
|
23
|
+
if (searchAll || opts.errorsOnly) {
|
|
24
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
25
|
+
const results: any = await client.request('error.query', {
|
|
26
|
+
search,
|
|
27
|
+
limit: limit + offset,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const errors = Array.isArray(results) ? results.slice(offset, offset + limit) : [];
|
|
31
|
+
if (errors.length > 0) {
|
|
32
|
+
totalResults += errors.length;
|
|
33
|
+
console.log(header(`Errors matching "${search}"`, icons.error));
|
|
34
|
+
|
|
35
|
+
for (const err of errors) {
|
|
36
|
+
const badge = statusBadge(err.resolved ? 'resolved' : 'open');
|
|
37
|
+
const typeTag = c.purple(err.errorType ?? 'unknown');
|
|
38
|
+
console.log(` ${c.dim(`#${err.id}`)} ${badge} ${typeTag}`);
|
|
39
|
+
console.log(` ${c.dim((err.message ?? '').slice(0, 120))}`);
|
|
40
|
+
|
|
41
|
+
// Get solutions for this error
|
|
42
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
43
|
+
const solutions: any = await client.request('solution.query', { error_id: err.id });
|
|
44
|
+
if (solutions?.length > 0) {
|
|
45
|
+
console.log(` ${c.green(`${icons.check} ${solutions.length} solution(s)`)}`);
|
|
46
|
+
for (const sol of solutions.slice(0, 3)) {
|
|
47
|
+
console.log(` ${c.dim(icons.corner)} ${c.dim((sol.description ?? '').slice(0, 100))}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
console.log();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
20
53
|
}
|
|
21
54
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
55
|
+
// --- Code Modules ---
|
|
56
|
+
if (searchAll || opts.modulesOnly) {
|
|
57
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
58
|
+
const modules: any = await client.request('code.find', {
|
|
59
|
+
query: search,
|
|
60
|
+
limit: limit + offset,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const mods = Array.isArray(modules) ? modules.slice(offset, offset + limit) : [];
|
|
64
|
+
if (mods.length > 0) {
|
|
65
|
+
totalResults += mods.length;
|
|
66
|
+
console.log(header(`Modules matching "${search}"`, icons.module));
|
|
28
67
|
|
|
29
|
-
|
|
68
|
+
for (const mod of mods) {
|
|
69
|
+
const score = mod.reusability_score ?? mod.reusabilityScore ?? 0;
|
|
70
|
+
const scoreColor = score >= 0.7 ? c.green : score >= 0.4 ? c.orange : c.red;
|
|
71
|
+
console.log(` ${c.dim(`#${mod.id}`)} ${c.cyan(`[${mod.language}]`)} ${c.value(mod.name)}`);
|
|
72
|
+
console.log(` ${c.label('File:')} ${c.dim(mod.file_path ?? mod.filePath)} ${c.label('Score:')} ${scoreColor(typeof score === 'number' ? score.toFixed(2) : score)}`);
|
|
73
|
+
if (mod.description) {
|
|
74
|
+
console.log(` ${c.dim(mod.description.slice(0, 120))}`);
|
|
75
|
+
}
|
|
76
|
+
console.log();
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// --- Insights ---
|
|
82
|
+
if (searchAll || opts.insightsOnly) {
|
|
30
83
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
84
|
+
const insights: any = await client.request('research.insights', {
|
|
85
|
+
activeOnly: true,
|
|
86
|
+
limit: 100,
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// Client-side search since insights API may not support text search
|
|
90
|
+
const allInsights = Array.isArray(insights) ? insights : [];
|
|
91
|
+
const searchLower = search.toLowerCase();
|
|
92
|
+
const matched = allInsights.filter((i: { title?: string; description?: string }) =>
|
|
93
|
+
(i.title ?? '').toLowerCase().includes(searchLower) ||
|
|
94
|
+
(i.description ?? '').toLowerCase().includes(searchLower)
|
|
95
|
+
).slice(offset, offset + limit);
|
|
96
|
+
|
|
97
|
+
if (matched.length > 0) {
|
|
98
|
+
totalResults += matched.length;
|
|
99
|
+
console.log(header(`Insights matching "${search}"`, icons.insight));
|
|
100
|
+
|
|
101
|
+
for (const ins of matched) {
|
|
102
|
+
const typeTag = c.cyan(`[${ins.type}]`);
|
|
103
|
+
console.log(` ${typeTag} ${c.value(ins.title)}`);
|
|
104
|
+
if (ins.description) {
|
|
105
|
+
console.log(` ${c.dim(ins.description.slice(0, 150))}`);
|
|
106
|
+
}
|
|
107
|
+
console.log();
|
|
37
108
|
}
|
|
38
109
|
}
|
|
39
|
-
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (totalResults === 0) {
|
|
113
|
+
console.log(`\n${icons.search} ${c.dim(`No results found for "${search}".`)}`);
|
|
114
|
+
} else {
|
|
115
|
+
console.log(` ${c.dim(`Page ${page} — showing ${totalResults} result(s). Use --page ${page + 1} for more.`)}`);
|
|
116
|
+
console.log(divider());
|
|
40
117
|
}
|
|
41
118
|
});
|
|
42
119
|
});
|
|
@@ -3,6 +3,7 @@ import { spawn } from 'node:child_process';
|
|
|
3
3
|
import fs from 'node:fs';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { getDataDir } from '../../utils/paths.js';
|
|
6
|
+
import { c, icons } from '../colors.js';
|
|
6
7
|
|
|
7
8
|
export function startCommand(): Command {
|
|
8
9
|
return new Command('start')
|
|
@@ -17,7 +18,7 @@ export function startCommand(): Command {
|
|
|
17
18
|
const pid = parseInt(fs.readFileSync(pidPath, 'utf8').trim(), 10);
|
|
18
19
|
try {
|
|
19
20
|
process.kill(pid, 0); // Check if process exists
|
|
20
|
-
console.log(
|
|
21
|
+
console.log(`${icons.brain} Brain daemon is ${c.green('already running')} ${c.dim(`(PID: ${pid})`)}`);
|
|
21
22
|
return;
|
|
22
23
|
} catch {
|
|
23
24
|
// PID file stale, remove it
|
|
@@ -45,14 +46,14 @@ export function startCommand(): Command {
|
|
|
45
46
|
});
|
|
46
47
|
child.unref();
|
|
47
48
|
|
|
48
|
-
console.log(
|
|
49
|
+
console.log(`${icons.brain} ${c.info('Brain daemon starting')} ${c.dim(`(PID: ${child.pid})`)}`);
|
|
49
50
|
|
|
50
51
|
// Wait briefly for PID file to appear
|
|
51
52
|
setTimeout(() => {
|
|
52
53
|
if (fs.existsSync(pidPath)) {
|
|
53
|
-
console.log('Brain daemon started successfully.');
|
|
54
|
+
console.log(`${icons.ok} ${c.success('Brain daemon started successfully.')}`);
|
|
54
55
|
} else {
|
|
55
|
-
console.log('Brain daemon may still be starting. Check: brain status');
|
|
56
|
+
console.log(`${icons.clock} ${c.warn('Brain daemon may still be starting.')} Check: ${c.cyan('brain status')}`);
|
|
56
57
|
}
|
|
57
58
|
}, 1000);
|
|
58
59
|
});
|
|
@@ -3,6 +3,7 @@ import fs from 'node:fs';
|
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { getDataDir } from '../../utils/paths.js';
|
|
5
5
|
import { withIpc } from '../ipc-helper.js';
|
|
6
|
+
import { c, icons, header, keyValue, divider } from '../colors.js';
|
|
6
7
|
|
|
7
8
|
export function statusCommand(): Command {
|
|
8
9
|
return new Command('status')
|
|
@@ -11,7 +12,7 @@ export function statusCommand(): Command {
|
|
|
11
12
|
const pidPath = path.join(getDataDir(), 'brain.pid');
|
|
12
13
|
|
|
13
14
|
if (!fs.existsSync(pidPath)) {
|
|
14
|
-
console.log(
|
|
15
|
+
console.log(`${icons.brain} Brain Daemon: ${c.red.bold('NOT RUNNING')}`);
|
|
15
16
|
return;
|
|
16
17
|
}
|
|
17
18
|
|
|
@@ -23,11 +24,12 @@ export function statusCommand(): Command {
|
|
|
23
24
|
} catch { /* not running */ }
|
|
24
25
|
|
|
25
26
|
if (!running) {
|
|
26
|
-
console.log(
|
|
27
|
+
console.log(`${icons.brain} Brain Daemon: ${c.red.bold('NOT RUNNING')} ${c.dim('(stale PID file)')}`);
|
|
27
28
|
return;
|
|
28
29
|
}
|
|
29
30
|
|
|
30
|
-
console.log(
|
|
31
|
+
console.log(header('Brain Status', icons.brain));
|
|
32
|
+
console.log(` ${c.green(`${icons.dot} RUNNING`)} ${c.dim(`(PID ${pid})`)}`);
|
|
31
33
|
|
|
32
34
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
33
35
|
await withIpc(async (client) => {
|
|
@@ -43,27 +45,28 @@ export function statusCommand(): Command {
|
|
|
43
45
|
dbSize = `${(stat.size / 1024 / 1024).toFixed(1)} MB`;
|
|
44
46
|
} catch { /* ignore */ }
|
|
45
47
|
|
|
46
|
-
console.log(
|
|
48
|
+
console.log(keyValue('Database', `${dbPath} (${dbSize})`));
|
|
47
49
|
console.log();
|
|
48
50
|
|
|
49
|
-
console.log('Error Brain
|
|
50
|
-
console.log(`
|
|
51
|
-
console.log(`
|
|
52
|
-
console.log(`
|
|
53
|
-
console.log(`
|
|
51
|
+
console.log(` ${icons.error} ${c.purple.bold('Error Brain')}`);
|
|
52
|
+
console.log(` ${c.label('Errors:')} ${c.value(summary.errors?.total ?? 0)} total, ${c.red(summary.errors?.unresolved ?? 0)} unresolved, ${c.dim(`${summary.errors?.last7d ?? 0} last 7d`)}`);
|
|
53
|
+
console.log(` ${c.label('Solutions:')} ${c.value(summary.solutions?.total ?? 0)}`);
|
|
54
|
+
console.log(` ${c.label('Rules:')} ${c.green(summary.rules?.active ?? 0)} active`);
|
|
55
|
+
console.log(` ${c.label('Anti-Pat.:')} ${c.value(summary.antipatterns?.total ?? 0)}`);
|
|
54
56
|
console.log();
|
|
55
57
|
|
|
56
|
-
console.log('Code Brain
|
|
57
|
-
console.log(`
|
|
58
|
+
console.log(` ${icons.module} ${c.blue.bold('Code Brain')}`);
|
|
59
|
+
console.log(` ${c.label('Modules:')} ${c.value(summary.modules?.total ?? 0)} registered`);
|
|
58
60
|
console.log();
|
|
59
61
|
|
|
60
|
-
console.log('Synapse Network
|
|
61
|
-
console.log(`
|
|
62
|
-
console.log(`
|
|
62
|
+
console.log(` ${icons.synapse} ${c.cyan.bold('Synapse Network')}`);
|
|
63
|
+
console.log(` ${c.label('Synapses:')} ${c.value(network.totalSynapses ?? 0)}`);
|
|
64
|
+
console.log(` ${c.label('Avg weight:')} ${c.value((network.avgWeight ?? 0).toFixed(2))}`);
|
|
63
65
|
console.log();
|
|
64
66
|
|
|
65
|
-
console.log('Research Brain
|
|
66
|
-
console.log(`
|
|
67
|
+
console.log(` ${icons.insight} ${c.orange.bold('Research Brain')}`);
|
|
68
|
+
console.log(` ${c.label('Insights:')} ${c.value(summary.insights?.active ?? 0)} active`);
|
|
69
|
+
console.log(`\n${divider()}`);
|
|
67
70
|
});
|
|
68
71
|
});
|
|
69
72
|
}
|
package/src/cli/commands/stop.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { Command } from 'commander';
|
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { getDataDir } from '../../utils/paths.js';
|
|
5
|
+
import { c, icons } from '../colors.js';
|
|
5
6
|
|
|
6
7
|
export function stopCommand(): Command {
|
|
7
8
|
return new Command('stop')
|
|
@@ -10,7 +11,7 @@ export function stopCommand(): Command {
|
|
|
10
11
|
const pidPath = path.join(getDataDir(), 'brain.pid');
|
|
11
12
|
|
|
12
13
|
if (!fs.existsSync(pidPath)) {
|
|
13
|
-
console.log('Brain daemon is not running (no PID file found).');
|
|
14
|
+
console.log(`${icons.brain} ${c.dim('Brain daemon is not running (no PID file found).')}`);
|
|
14
15
|
return;
|
|
15
16
|
}
|
|
16
17
|
|
|
@@ -18,12 +19,12 @@ export function stopCommand(): Command {
|
|
|
18
19
|
|
|
19
20
|
try {
|
|
20
21
|
process.kill(pid, 'SIGTERM');
|
|
21
|
-
console.log(
|
|
22
|
+
console.log(`${icons.brain} ${c.success('Brain daemon stopped')} ${c.dim(`(PID: ${pid})`)}`);
|
|
22
23
|
} catch (err) {
|
|
23
24
|
if ((err as NodeJS.ErrnoException).code === 'ESRCH') {
|
|
24
|
-
console.log('Brain daemon was not running (stale PID file removed).');
|
|
25
|
+
console.log(`${icons.brain} ${c.dim('Brain daemon was not running (stale PID file removed).')}`);
|
|
25
26
|
} else {
|
|
26
|
-
console.error(`Failed to stop daemon: ${err}`);
|
|
27
|
+
console.error(`${icons.error} ${c.error(`Failed to stop daemon: ${err}`)}`);
|
|
27
28
|
}
|
|
28
29
|
}
|
|
29
30
|
|
package/src/cli/ipc-helper.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { IpcClient } from '../ipc/client.js';
|
|
2
2
|
import { getPipeName } from '../utils/paths.js';
|
|
3
|
+
import { c, icons } from './colors.js';
|
|
3
4
|
|
|
4
5
|
export async function withIpc<T>(fn: (client: IpcClient) => Promise<T>): Promise<T> {
|
|
5
6
|
const client = new IpcClient(getPipeName(), 5000);
|
|
@@ -8,11 +9,11 @@ export async function withIpc<T>(fn: (client: IpcClient) => Promise<T>): Promise
|
|
|
8
9
|
return await fn(client);
|
|
9
10
|
} catch (err) {
|
|
10
11
|
if (err instanceof Error && err.message.includes('ENOENT')) {
|
|
11
|
-
console.error('Brain daemon is not running. Start it with: brain start');
|
|
12
|
+
console.error(`${icons.error} ${c.error('Brain daemon is not running.')} Start it with: ${c.cyan('brain start')}`);
|
|
12
13
|
} else if (err instanceof Error && err.message.includes('ECONNREFUSED')) {
|
|
13
|
-
console.error('Brain daemon is not responding. Try: brain stop && brain start');
|
|
14
|
+
console.error(`${icons.error} ${c.error('Brain daemon is not responding.')} Try: ${c.cyan('brain stop && brain start')}`);
|
|
14
15
|
} else {
|
|
15
|
-
console.error(
|
|
16
|
+
console.error(`${icons.error} ${c.error(err instanceof Error ? err.message : String(err))}`);
|
|
16
17
|
}
|
|
17
18
|
process.exit(1);
|
|
18
19
|
} finally {
|
package/src/code/analyzer.ts
CHANGED
|
@@ -1,77 +1,77 @@
|
|
|
1
|
-
import type { ExportInfo } from '../types/code.types.js';
|
|
2
|
-
import * as tsParser from './parsers/typescript.js';
|
|
3
|
-
import * as pyParser from './parsers/python.js';
|
|
4
|
-
import * as genericParser from './parsers/generic.js';
|
|
5
|
-
|
|
6
|
-
export interface AnalysisResult {
|
|
7
|
-
exports: ExportInfo[];
|
|
8
|
-
externalDeps: string[];
|
|
9
|
-
internalDeps: string[];
|
|
10
|
-
isPure: boolean;
|
|
11
|
-
hasTypeAnnotations: boolean;
|
|
12
|
-
linesOfCode: number;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const SIDE_EFFECT_PATTERNS = [
|
|
16
|
-
'fs.', 'process.exit', 'process.env', 'console.', 'fetch(',
|
|
17
|
-
'XMLHttpRequest', 'document.', 'window.',
|
|
18
|
-
'global.', 'require(',
|
|
19
|
-
];
|
|
20
|
-
|
|
21
|
-
function getParser(language: string) {
|
|
22
|
-
switch (language) {
|
|
23
|
-
case 'typescript':
|
|
24
|
-
case 'javascript':
|
|
25
|
-
return tsParser;
|
|
26
|
-
case 'python':
|
|
27
|
-
return pyParser;
|
|
28
|
-
default:
|
|
29
|
-
return genericParser;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export function analyzeCode(source: string, language: string): AnalysisResult {
|
|
34
|
-
const parser = getParser(language);
|
|
35
|
-
const exports = parser.extractExports(source);
|
|
36
|
-
const { external, internal } = parser.extractImports(source);
|
|
37
|
-
const isPure = checkPurity(source);
|
|
38
|
-
const typed = parser.hasTypeAnnotations(source);
|
|
39
|
-
const linesOfCode = source.split('\n').filter(l => l.trim().length > 0).length;
|
|
40
|
-
|
|
41
|
-
return {
|
|
42
|
-
exports,
|
|
43
|
-
externalDeps: external,
|
|
44
|
-
internalDeps: internal,
|
|
45
|
-
isPure,
|
|
46
|
-
hasTypeAnnotations: typed,
|
|
47
|
-
linesOfCode,
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export function checkPurity(source: string): boolean {
|
|
52
|
-
return !SIDE_EFFECT_PATTERNS.some(p => source.includes(p));
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export function measureCohesion(exports: ExportInfo[]): number {
|
|
56
|
-
if (exports.length <= 1) return 1.0;
|
|
57
|
-
|
|
58
|
-
const names = exports.map(e =>
|
|
59
|
-
e.name
|
|
60
|
-
.replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2')
|
|
61
|
-
.replace(/([a-z\d])([A-Z])/g, '$1 $2')
|
|
62
|
-
.toLowerCase()
|
|
63
|
-
.split(/\s+/)
|
|
64
|
-
);
|
|
65
|
-
|
|
66
|
-
const vocab = new Set<string>();
|
|
67
|
-
names.forEach(tokens => tokens.forEach(t => vocab.add(t)));
|
|
68
|
-
|
|
69
|
-
let sharedTokens = 0;
|
|
70
|
-
for (const token of vocab) {
|
|
71
|
-
const count = names.filter(n => n.includes(token)).length;
|
|
72
|
-
if (count > 1) sharedTokens += count;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const maxPossible = names.length * vocab.size;
|
|
76
|
-
return maxPossible === 0 ? 0 : sharedTokens / maxPossible;
|
|
77
|
-
}
|
|
1
|
+
import type { ExportInfo } from '../types/code.types.js';
|
|
2
|
+
import * as tsParser from './parsers/typescript.js';
|
|
3
|
+
import * as pyParser from './parsers/python.js';
|
|
4
|
+
import * as genericParser from './parsers/generic.js';
|
|
5
|
+
|
|
6
|
+
export interface AnalysisResult {
|
|
7
|
+
exports: ExportInfo[];
|
|
8
|
+
externalDeps: string[];
|
|
9
|
+
internalDeps: string[];
|
|
10
|
+
isPure: boolean;
|
|
11
|
+
hasTypeAnnotations: boolean;
|
|
12
|
+
linesOfCode: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const SIDE_EFFECT_PATTERNS = [
|
|
16
|
+
'fs.', 'process.exit', 'process.env', 'console.', 'fetch(',
|
|
17
|
+
'XMLHttpRequest', 'document.', 'window.',
|
|
18
|
+
'global.', 'require(',
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
function getParser(language: string) {
|
|
22
|
+
switch (language) {
|
|
23
|
+
case 'typescript':
|
|
24
|
+
case 'javascript':
|
|
25
|
+
return tsParser;
|
|
26
|
+
case 'python':
|
|
27
|
+
return pyParser;
|
|
28
|
+
default:
|
|
29
|
+
return genericParser;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function analyzeCode(source: string, language: string): AnalysisResult {
|
|
34
|
+
const parser = getParser(language);
|
|
35
|
+
const exports = parser.extractExports(source);
|
|
36
|
+
const { external, internal } = parser.extractImports(source);
|
|
37
|
+
const isPure = checkPurity(source);
|
|
38
|
+
const typed = parser.hasTypeAnnotations(source);
|
|
39
|
+
const linesOfCode = source.split('\n').filter(l => l.trim().length > 0).length;
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
exports,
|
|
43
|
+
externalDeps: external,
|
|
44
|
+
internalDeps: internal,
|
|
45
|
+
isPure,
|
|
46
|
+
hasTypeAnnotations: typed,
|
|
47
|
+
linesOfCode,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function checkPurity(source: string): boolean {
|
|
52
|
+
return !SIDE_EFFECT_PATTERNS.some(p => source.includes(p));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function measureCohesion(exports: ExportInfo[]): number {
|
|
56
|
+
if (exports.length <= 1) return 1.0;
|
|
57
|
+
|
|
58
|
+
const names = exports.map(e =>
|
|
59
|
+
e.name
|
|
60
|
+
.replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2')
|
|
61
|
+
.replace(/([a-z\d])([A-Z])/g, '$1 $2')
|
|
62
|
+
.toLowerCase()
|
|
63
|
+
.split(/\s+/)
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
const vocab = new Set<string>();
|
|
67
|
+
names.forEach(tokens => tokens.forEach(t => vocab.add(t)));
|
|
68
|
+
|
|
69
|
+
let sharedTokens = 0;
|
|
70
|
+
for (const token of vocab) {
|
|
71
|
+
const count = names.filter(n => n.includes(token)).length;
|
|
72
|
+
if (count > 1) sharedTokens += count;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const maxPossible = names.length * vocab.size;
|
|
76
|
+
return maxPossible === 0 ? 0 : sharedTokens / maxPossible;
|
|
77
|
+
}
|
package/src/code/fingerprint.ts
CHANGED
|
@@ -1,87 +1,87 @@
|
|
|
1
|
-
import { sha256 } from '../utils/hash.js';
|
|
2
|
-
|
|
3
|
-
export function fingerprintCode(source: string, language: string): string {
|
|
4
|
-
let normalized = stripComments(source, language);
|
|
5
|
-
normalized = normalized.replace(/\s+/g, ' ').trim();
|
|
6
|
-
normalized = normalizeIdentifiers(normalized, language);
|
|
7
|
-
normalized = normalized.replace(/'[^']*'/g, "'<STR>'");
|
|
8
|
-
normalized = normalized.replace(/"[^"]*"/g, '"<STR>"');
|
|
9
|
-
normalized = normalized.replace(/`[^`]*`/g, '`<STR>`');
|
|
10
|
-
normalized = normalized.replace(/\b\d+\b/g, '<NUM>');
|
|
11
|
-
return sha256(normalized);
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export function stripComments(source: string, language: string): string {
|
|
15
|
-
switch (language) {
|
|
16
|
-
case 'typescript':
|
|
17
|
-
case 'javascript':
|
|
18
|
-
case 'java':
|
|
19
|
-
case 'go':
|
|
20
|
-
case 'rust':
|
|
21
|
-
case 'c':
|
|
22
|
-
case 'cpp':
|
|
23
|
-
return source
|
|
24
|
-
.replace(/\/\/.*$/gm, '')
|
|
25
|
-
.replace(/\/\*[\s\S]*?\*\//g, '');
|
|
26
|
-
case 'python':
|
|
27
|
-
return source
|
|
28
|
-
.replace(/#.*$/gm, '')
|
|
29
|
-
.replace(/"""[\s\S]*?"""/g, '')
|
|
30
|
-
.replace(/'''[\s\S]*?'''/g, '');
|
|
31
|
-
default:
|
|
32
|
-
return source
|
|
33
|
-
.replace(/\/\/.*$/gm, '')
|
|
34
|
-
.replace(/#.*$/gm, '');
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function normalizeIdentifiers(source: string, language: string): string {
|
|
39
|
-
const importNames = extractImportNames(source, language);
|
|
40
|
-
const keywords = getLanguageKeywords(language);
|
|
41
|
-
const preserve = new Set([...importNames, ...keywords]);
|
|
42
|
-
|
|
43
|
-
return source.replace(/\b[a-zA-Z_]\w*\b/g, (match) => {
|
|
44
|
-
if (preserve.has(match)) return match;
|
|
45
|
-
if (match[0] === match[0]!.toUpperCase() && match[0] !== match[0]!.toLowerCase()) return '<CLASS>';
|
|
46
|
-
return '<VAR>';
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function extractImportNames(source: string, language: string): string[] {
|
|
51
|
-
const names: string[] = [];
|
|
52
|
-
|
|
53
|
-
if (language === 'typescript' || language === 'javascript') {
|
|
54
|
-
const re = /import\s+(?:\{([^}]+)\}|\*\s+as\s+(\w+)|(\w+))/g;
|
|
55
|
-
let m: RegExpExecArray | null;
|
|
56
|
-
while ((m = re.exec(source)) !== null) {
|
|
57
|
-
if (m[1]) names.push(...m[1].split(',').map(s => s.trim().split(/\s+as\s+/).pop()!));
|
|
58
|
-
if (m[2]) names.push(m[2]);
|
|
59
|
-
if (m[3]) names.push(m[3]);
|
|
60
|
-
}
|
|
61
|
-
} else if (language === 'python') {
|
|
62
|
-
const re = /(?:from\s+\S+\s+)?import\s+(.+)/g;
|
|
63
|
-
let m: RegExpExecArray | null;
|
|
64
|
-
while ((m = re.exec(source)) !== null) {
|
|
65
|
-
names.push(...m[1]!.split(',').map(s => {
|
|
66
|
-
const parts = s.trim().split(/\s+as\s+/);
|
|
67
|
-
return parts[parts.length - 1]!;
|
|
68
|
-
}));
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
return names.filter(Boolean);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function getLanguageKeywords(language: string): string[] {
|
|
76
|
-
const common = ['if', 'else', 'for', 'while', 'return', 'break', 'continue', 'switch', 'case', 'default', 'try', 'catch', 'throw', 'new', 'delete', 'true', 'false', 'null', 'undefined', 'void'];
|
|
77
|
-
|
|
78
|
-
const langKeywords: Record<string, string[]> = {
|
|
79
|
-
typescript: [...common, 'const', 'let', 'var', 'function', 'class', 'interface', 'type', 'enum', 'import', 'export', 'from', 'async', 'await', 'extends', 'implements', 'readonly', 'private', 'public', 'protected', 'static', 'abstract', 'as', 'is', 'in', 'of', 'typeof', 'keyof', 'infer', 'never', 'unknown', 'any', 'string', 'number', 'boolean', 'symbol', 'object'],
|
|
80
|
-
javascript: [...common, 'const', 'let', 'var', 'function', 'class', 'import', 'export', 'from', 'async', 'await', 'extends', 'typeof', 'instanceof', 'in', 'of', 'this', 'super', 'yield'],
|
|
81
|
-
python: ['def', 'class', 'import', 'from', 'if', 'elif', 'else', 'for', 'while', 'return', 'yield', 'break', 'continue', 'try', 'except', 'finally', 'raise', 'with', 'as', 'pass', 'lambda', 'True', 'False', 'None', 'and', 'or', 'not', 'in', 'is', 'global', 'nonlocal', 'assert', 'async', 'await', 'self'],
|
|
82
|
-
rust: [...common, 'fn', 'let', 'mut', 'pub', 'struct', 'enum', 'impl', 'trait', 'use', 'mod', 'crate', 'self', 'super', 'match', 'loop', 'move', 'ref', 'where', 'async', 'await', 'dyn', 'Box', 'Vec', 'String', 'Option', 'Result', 'Some', 'None', 'Ok', 'Err'],
|
|
83
|
-
go: [...common, 'func', 'package', 'import', 'type', 'struct', 'interface', 'map', 'chan', 'go', 'defer', 'select', 'range', 'var', 'const', 'nil', 'make', 'len', 'append', 'cap', 'copy', 'close'],
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
return langKeywords[language] ?? common;
|
|
87
|
-
}
|
|
1
|
+
import { sha256 } from '../utils/hash.js';
|
|
2
|
+
|
|
3
|
+
export function fingerprintCode(source: string, language: string): string {
|
|
4
|
+
let normalized = stripComments(source, language);
|
|
5
|
+
normalized = normalized.replace(/\s+/g, ' ').trim();
|
|
6
|
+
normalized = normalizeIdentifiers(normalized, language);
|
|
7
|
+
normalized = normalized.replace(/'[^']*'/g, "'<STR>'");
|
|
8
|
+
normalized = normalized.replace(/"[^"]*"/g, '"<STR>"');
|
|
9
|
+
normalized = normalized.replace(/`[^`]*`/g, '`<STR>`');
|
|
10
|
+
normalized = normalized.replace(/\b\d+\b/g, '<NUM>');
|
|
11
|
+
return sha256(normalized);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function stripComments(source: string, language: string): string {
|
|
15
|
+
switch (language) {
|
|
16
|
+
case 'typescript':
|
|
17
|
+
case 'javascript':
|
|
18
|
+
case 'java':
|
|
19
|
+
case 'go':
|
|
20
|
+
case 'rust':
|
|
21
|
+
case 'c':
|
|
22
|
+
case 'cpp':
|
|
23
|
+
return source
|
|
24
|
+
.replace(/\/\/.*$/gm, '')
|
|
25
|
+
.replace(/\/\*[\s\S]*?\*\//g, '');
|
|
26
|
+
case 'python':
|
|
27
|
+
return source
|
|
28
|
+
.replace(/#.*$/gm, '')
|
|
29
|
+
.replace(/"""[\s\S]*?"""/g, '')
|
|
30
|
+
.replace(/'''[\s\S]*?'''/g, '');
|
|
31
|
+
default:
|
|
32
|
+
return source
|
|
33
|
+
.replace(/\/\/.*$/gm, '')
|
|
34
|
+
.replace(/#.*$/gm, '');
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function normalizeIdentifiers(source: string, language: string): string {
|
|
39
|
+
const importNames = extractImportNames(source, language);
|
|
40
|
+
const keywords = getLanguageKeywords(language);
|
|
41
|
+
const preserve = new Set([...importNames, ...keywords]);
|
|
42
|
+
|
|
43
|
+
return source.replace(/\b[a-zA-Z_]\w*\b/g, (match) => {
|
|
44
|
+
if (preserve.has(match)) return match;
|
|
45
|
+
if (match[0] === match[0]!.toUpperCase() && match[0] !== match[0]!.toLowerCase()) return '<CLASS>';
|
|
46
|
+
return '<VAR>';
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function extractImportNames(source: string, language: string): string[] {
|
|
51
|
+
const names: string[] = [];
|
|
52
|
+
|
|
53
|
+
if (language === 'typescript' || language === 'javascript') {
|
|
54
|
+
const re = /import\s+(?:\{([^}]+)\}|\*\s+as\s+(\w+)|(\w+))/g;
|
|
55
|
+
let m: RegExpExecArray | null;
|
|
56
|
+
while ((m = re.exec(source)) !== null) {
|
|
57
|
+
if (m[1]) names.push(...m[1].split(',').map(s => s.trim().split(/\s+as\s+/).pop()!));
|
|
58
|
+
if (m[2]) names.push(m[2]);
|
|
59
|
+
if (m[3]) names.push(m[3]);
|
|
60
|
+
}
|
|
61
|
+
} else if (language === 'python') {
|
|
62
|
+
const re = /(?:from\s+\S+\s+)?import\s+(.+)/g;
|
|
63
|
+
let m: RegExpExecArray | null;
|
|
64
|
+
while ((m = re.exec(source)) !== null) {
|
|
65
|
+
names.push(...m[1]!.split(',').map(s => {
|
|
66
|
+
const parts = s.trim().split(/\s+as\s+/);
|
|
67
|
+
return parts[parts.length - 1]!;
|
|
68
|
+
}));
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return names.filter(Boolean);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function getLanguageKeywords(language: string): string[] {
|
|
76
|
+
const common = ['if', 'else', 'for', 'while', 'return', 'break', 'continue', 'switch', 'case', 'default', 'try', 'catch', 'throw', 'new', 'delete', 'true', 'false', 'null', 'undefined', 'void'];
|
|
77
|
+
|
|
78
|
+
const langKeywords: Record<string, string[]> = {
|
|
79
|
+
typescript: [...common, 'const', 'let', 'var', 'function', 'class', 'interface', 'type', 'enum', 'import', 'export', 'from', 'async', 'await', 'extends', 'implements', 'readonly', 'private', 'public', 'protected', 'static', 'abstract', 'as', 'is', 'in', 'of', 'typeof', 'keyof', 'infer', 'never', 'unknown', 'any', 'string', 'number', 'boolean', 'symbol', 'object'],
|
|
80
|
+
javascript: [...common, 'const', 'let', 'var', 'function', 'class', 'import', 'export', 'from', 'async', 'await', 'extends', 'typeof', 'instanceof', 'in', 'of', 'this', 'super', 'yield'],
|
|
81
|
+
python: ['def', 'class', 'import', 'from', 'if', 'elif', 'else', 'for', 'while', 'return', 'yield', 'break', 'continue', 'try', 'except', 'finally', 'raise', 'with', 'as', 'pass', 'lambda', 'True', 'False', 'None', 'and', 'or', 'not', 'in', 'is', 'global', 'nonlocal', 'assert', 'async', 'await', 'self'],
|
|
82
|
+
rust: [...common, 'fn', 'let', 'mut', 'pub', 'struct', 'enum', 'impl', 'trait', 'use', 'mod', 'crate', 'self', 'super', 'match', 'loop', 'move', 'ref', 'where', 'async', 'await', 'dyn', 'Box', 'Vec', 'String', 'Option', 'Result', 'Some', 'None', 'Ok', 'Err'],
|
|
83
|
+
go: [...common, 'func', 'package', 'import', 'type', 'struct', 'interface', 'map', 'chan', 'go', 'defer', 'select', 'range', 'var', 'const', 'nil', 'make', 'len', 'append', 'cap', 'copy', 'close'],
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
return langKeywords[language] ?? common;
|
|
87
|
+
}
|