@timmeck/brain 1.0.0 → 1.1.1
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 +8 -5
- package/dist/cli/commands/start.js.map +1 -1
- package/dist/cli/commands/status.js +21 -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/cli/update-check.d.ts +2 -0
- package/dist/cli/update-check.js +58 -0
- package/dist/cli/update-check.js.map +1 -0
- 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 +8 -5
- package/src/cli/commands/status.ts +22 -16
- package/src/cli/commands/stop.ts +5 -4
- package/src/cli/ipc-helper.ts +4 -3
- package/src/cli/update-check.ts +63 -0
- 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,5 +1,18 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
import { withIpc } from '../ipc-helper.js';
|
|
3
|
+
import { c, icons, header, priorityBadge, divider } from '../colors.js';
|
|
4
|
+
|
|
5
|
+
const TYPE_ICONS: Record<string, string> = {
|
|
6
|
+
trend: '📈',
|
|
7
|
+
pattern: '🔄',
|
|
8
|
+
gap: '⚠',
|
|
9
|
+
synergy: '⚡',
|
|
10
|
+
optimization: '🎯',
|
|
11
|
+
template_candidate: '🎨',
|
|
12
|
+
project_suggestion: '💡',
|
|
13
|
+
warning: '🚨',
|
|
14
|
+
suggestion: '💡',
|
|
15
|
+
};
|
|
3
16
|
|
|
4
17
|
export function insightsCommand(): Command {
|
|
5
18
|
return new Command('insights')
|
|
@@ -16,18 +29,37 @@ export function insightsCommand(): Command {
|
|
|
16
29
|
});
|
|
17
30
|
|
|
18
31
|
if (!insights?.length) {
|
|
19
|
-
console.log('No active insights.');
|
|
32
|
+
console.log(`${icons.insight} ${c.dim('No active insights.')}`);
|
|
20
33
|
return;
|
|
21
34
|
}
|
|
22
35
|
|
|
23
|
-
console.log(`${insights.length}
|
|
36
|
+
console.log(header(`${insights.length} Insights`, icons.insight));
|
|
37
|
+
|
|
38
|
+
// Group by type
|
|
39
|
+
const byType: Record<string, number> = {};
|
|
24
40
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
25
41
|
for (const ins of insights as any[]) {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
42
|
+
byType[ins.type] = (byType[ins.type] || 0) + 1;
|
|
43
|
+
}
|
|
44
|
+
const typeSummary = Object.entries(byType)
|
|
45
|
+
.sort((a, b) => b[1] - a[1])
|
|
46
|
+
.map(([t, count]) => `${TYPE_ICONS[t] ?? '•'} ${c.cyan(t)} ${c.dim(`(${count})`)}`)
|
|
47
|
+
.join(' ');
|
|
48
|
+
console.log(` ${typeSummary}\n`);
|
|
49
|
+
|
|
50
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
51
|
+
for (const ins of insights as any[]) {
|
|
52
|
+
const typeIcon = TYPE_ICONS[ins.type] ?? '•';
|
|
53
|
+
const pBadge = priorityBadge(ins.priority ?? 0);
|
|
54
|
+
const typeTag = c.cyan(`[${ins.type}]`);
|
|
55
|
+
|
|
56
|
+
console.log(` ${typeIcon} ${typeTag} ${pBadge} ${c.value(ins.title)}`);
|
|
57
|
+
if (ins.description) {
|
|
58
|
+
console.log(` ${c.dim(ins.description.slice(0, 150))}`);
|
|
59
|
+
}
|
|
29
60
|
console.log();
|
|
30
61
|
}
|
|
62
|
+
console.log(divider());
|
|
31
63
|
});
|
|
32
64
|
});
|
|
33
65
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { withIpc } from '../ipc-helper.js';
|
|
3
|
+
import { c, icons, header, keyValue, divider } from '../colors.js';
|
|
4
|
+
|
|
5
|
+
export function learnCommand(): Command {
|
|
6
|
+
return new Command('learn')
|
|
7
|
+
.description('Trigger a learning cycle manually (pattern extraction + rule generation)')
|
|
8
|
+
.action(async () => {
|
|
9
|
+
await withIpc(async (client) => {
|
|
10
|
+
console.log(`${icons.brain} ${c.info('Running learning cycle...')}`);
|
|
11
|
+
|
|
12
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
13
|
+
const result: any = await client.request('learning.run', {});
|
|
14
|
+
|
|
15
|
+
console.log(header('Learning Cycle Complete', icons.bolt));
|
|
16
|
+
console.log(keyValue('New patterns', result.newPatterns ?? 0));
|
|
17
|
+
console.log(keyValue('Updated rules', result.updatedRules ?? 0));
|
|
18
|
+
console.log(keyValue('Pruned rules', result.prunedRules ?? 0));
|
|
19
|
+
console.log(keyValue('New anti-patterns', result.newAntipatterns ?? 0));
|
|
20
|
+
console.log(keyValue('Duration', `${result.duration ?? 0}ms`));
|
|
21
|
+
console.log(`\n${divider()}`);
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
}
|
|
@@ -1,30 +1,53 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
import { withIpc } from '../ipc-helper.js';
|
|
3
|
+
import { c, icons, header, divider } from '../colors.js';
|
|
3
4
|
|
|
4
5
|
export function modulesCommand(): Command {
|
|
5
6
|
return new Command('modules')
|
|
6
7
|
.description('List registered code modules')
|
|
7
8
|
.option('--language <lang>', 'Filter by language')
|
|
9
|
+
.option('-l, --limit <n>', 'Maximum results')
|
|
8
10
|
.action(async (opts) => {
|
|
9
11
|
await withIpc(async (client) => {
|
|
10
12
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
11
13
|
const modules: any = await client.request('code.modules', {
|
|
12
14
|
language: opts.language,
|
|
15
|
+
limit: opts.limit ? parseInt(opts.limit, 10) : undefined,
|
|
13
16
|
});
|
|
14
17
|
|
|
15
18
|
if (!modules?.length) {
|
|
16
|
-
console.log('No code modules registered.');
|
|
19
|
+
console.log(`${icons.module} ${c.dim('No code modules registered.')}`);
|
|
17
20
|
return;
|
|
18
21
|
}
|
|
19
22
|
|
|
20
|
-
console.log(`${modules.length}
|
|
23
|
+
console.log(header(`${modules.length} Code Modules`, icons.module));
|
|
24
|
+
|
|
25
|
+
// Group by language
|
|
26
|
+
const byLang: Record<string, number> = {};
|
|
27
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
28
|
+
for (const mod of modules as any[]) {
|
|
29
|
+
byLang[mod.language] = (byLang[mod.language] || 0) + 1;
|
|
30
|
+
}
|
|
31
|
+
const langSummary = Object.entries(byLang)
|
|
32
|
+
.sort((a, b) => b[1] - a[1])
|
|
33
|
+
.map(([lang, count]) => `${c.cyan(lang)} ${c.dim(`(${count})`)}`)
|
|
34
|
+
.join(' ');
|
|
35
|
+
console.log(` ${langSummary}\n`);
|
|
36
|
+
|
|
21
37
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
22
38
|
for (const mod of modules as any[]) {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
39
|
+
const score = mod.reusabilityScore ?? mod.reusability_score ?? 0;
|
|
40
|
+
const scoreColor = score >= 0.7 ? c.green : score >= 0.4 ? c.orange : c.red;
|
|
41
|
+
const langTag = c.cyan(`[${mod.language}]`);
|
|
42
|
+
|
|
43
|
+
console.log(` ${c.dim(`#${mod.id}`)} ${langTag} ${c.value(mod.name)}`);
|
|
44
|
+
if (mod.description) {
|
|
45
|
+
console.log(` ${c.dim(mod.description.slice(0, 120))}`);
|
|
46
|
+
}
|
|
47
|
+
console.log(` ${c.label('File:')} ${c.dim(mod.filePath ?? mod.file_path)} ${c.label('Score:')} ${scoreColor(typeof score === 'number' ? score.toFixed(2) : score)}`);
|
|
26
48
|
console.log();
|
|
27
49
|
}
|
|
50
|
+
console.log(divider());
|
|
28
51
|
});
|
|
29
52
|
});
|
|
30
53
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
import { withIpc } from '../ipc-helper.js';
|
|
3
|
+
import { c, icons, header, keyValue, divider } from '../colors.js';
|
|
3
4
|
|
|
4
5
|
export function networkCommand(): Command {
|
|
5
6
|
return new Command('network')
|
|
@@ -13,7 +14,7 @@ export function networkCommand(): Command {
|
|
|
13
14
|
const nodeId = parseInt(nodeIdStr, 10);
|
|
14
15
|
|
|
15
16
|
if (!nodeType || isNaN(nodeId)) {
|
|
16
|
-
console.error('Invalid node format. Use: --node error:42');
|
|
17
|
+
console.error(c.error('Invalid node format. Use: --node error:42'));
|
|
17
18
|
return;
|
|
18
19
|
}
|
|
19
20
|
|
|
@@ -25,14 +26,16 @@ export function networkCommand(): Command {
|
|
|
25
26
|
});
|
|
26
27
|
|
|
27
28
|
if (!related?.length) {
|
|
28
|
-
console.log(
|
|
29
|
+
console.log(`${c.dim('No connections found for')} ${c.cyan(`${nodeType}:${nodeId}`)}`);
|
|
29
30
|
return;
|
|
30
31
|
}
|
|
31
32
|
|
|
32
|
-
console.log(`Connections from ${nodeType}:${nodeId}
|
|
33
|
+
console.log(header(`Connections from ${nodeType}:${nodeId}`, icons.synapse));
|
|
33
34
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
34
35
|
for (const r of related as any[]) {
|
|
35
|
-
|
|
36
|
+
const weight = (r.activation ?? r.weight ?? 0);
|
|
37
|
+
const weightColor = weight >= 0.7 ? c.green : weight >= 0.3 ? c.orange : c.dim;
|
|
38
|
+
console.log(` ${c.cyan(icons.arrow)} ${c.value(`${r.nodeType}:${r.nodeId}`)} ${c.label('weight:')} ${weightColor(weight.toFixed(3))}`);
|
|
36
39
|
}
|
|
37
40
|
} else {
|
|
38
41
|
// Show general network stats
|
|
@@ -43,19 +46,22 @@ export function networkCommand(): Command {
|
|
|
43
46
|
limit: parseInt(opts.limit, 10),
|
|
44
47
|
});
|
|
45
48
|
|
|
46
|
-
console.log('Synapse Network
|
|
47
|
-
console.log(
|
|
48
|
-
console.log(
|
|
49
|
+
console.log(header('Synapse Network', icons.synapse));
|
|
50
|
+
console.log(keyValue('Total synapses', stats.totalSynapses ?? 0));
|
|
51
|
+
console.log(keyValue('Average weight', (stats.avgWeight ?? 0).toFixed(3)));
|
|
49
52
|
console.log();
|
|
50
53
|
|
|
51
54
|
if (overview?.strongestSynapses?.length) {
|
|
52
|
-
console.log('Strongest connections:');
|
|
55
|
+
console.log(` ${c.purple.bold('Strongest connections:')}`);
|
|
53
56
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
54
57
|
for (const s of overview.strongestSynapses as any[]) {
|
|
55
|
-
|
|
58
|
+
const weight = (s.weight ?? 0);
|
|
59
|
+
const weightColor = weight >= 0.7 ? c.green : weight >= 0.3 ? c.orange : c.dim;
|
|
60
|
+
console.log(` ${c.dim(s.source)} ${c.cyan(icons.arrow)} ${c.dim(s.target)} ${c.label(`[${s.type}]`)} ${weightColor(weight.toFixed(3))}`);
|
|
56
61
|
}
|
|
57
62
|
}
|
|
58
63
|
}
|
|
64
|
+
console.log(`\n${divider()}`);
|
|
59
65
|
});
|
|
60
66
|
});
|
|
61
67
|
}
|
|
@@ -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,8 @@ 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';
|
|
7
|
+
import { checkForUpdate } from '../update-check.js';
|
|
6
8
|
|
|
7
9
|
export function startCommand(): Command {
|
|
8
10
|
return new Command('start')
|
|
@@ -17,7 +19,7 @@ export function startCommand(): Command {
|
|
|
17
19
|
const pid = parseInt(fs.readFileSync(pidPath, 'utf8').trim(), 10);
|
|
18
20
|
try {
|
|
19
21
|
process.kill(pid, 0); // Check if process exists
|
|
20
|
-
console.log(
|
|
22
|
+
console.log(`${icons.brain} Brain daemon is ${c.green('already running')} ${c.dim(`(PID: ${pid})`)}`);
|
|
21
23
|
return;
|
|
22
24
|
} catch {
|
|
23
25
|
// PID file stale, remove it
|
|
@@ -45,15 +47,16 @@ export function startCommand(): Command {
|
|
|
45
47
|
});
|
|
46
48
|
child.unref();
|
|
47
49
|
|
|
48
|
-
console.log(
|
|
50
|
+
console.log(`${icons.brain} ${c.info('Brain daemon starting')} ${c.dim(`(PID: ${child.pid})`)}`);
|
|
49
51
|
|
|
50
52
|
// Wait briefly for PID file to appear
|
|
51
|
-
setTimeout(() => {
|
|
53
|
+
setTimeout(async () => {
|
|
52
54
|
if (fs.existsSync(pidPath)) {
|
|
53
|
-
console.log('Brain daemon started successfully.');
|
|
55
|
+
console.log(`${icons.ok} ${c.success('Brain daemon started successfully.')}`);
|
|
54
56
|
} else {
|
|
55
|
-
console.log('Brain daemon may still be starting. Check: brain status');
|
|
57
|
+
console.log(`${icons.clock} ${c.warn('Brain daemon may still be starting.')} Check: ${c.cyan('brain status')}`);
|
|
56
58
|
}
|
|
59
|
+
await checkForUpdate();
|
|
57
60
|
}, 1000);
|
|
58
61
|
});
|
|
59
62
|
}
|
|
@@ -3,6 +3,8 @@ 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';
|
|
7
|
+
import { checkForUpdate, getCurrentVersion } from '../update-check.js';
|
|
6
8
|
|
|
7
9
|
export function statusCommand(): Command {
|
|
8
10
|
return new Command('status')
|
|
@@ -11,7 +13,7 @@ export function statusCommand(): Command {
|
|
|
11
13
|
const pidPath = path.join(getDataDir(), 'brain.pid');
|
|
12
14
|
|
|
13
15
|
if (!fs.existsSync(pidPath)) {
|
|
14
|
-
console.log(
|
|
16
|
+
console.log(`${icons.brain} Brain Daemon: ${c.red.bold('NOT RUNNING')}`);
|
|
15
17
|
return;
|
|
16
18
|
}
|
|
17
19
|
|
|
@@ -23,11 +25,12 @@ export function statusCommand(): Command {
|
|
|
23
25
|
} catch { /* not running */ }
|
|
24
26
|
|
|
25
27
|
if (!running) {
|
|
26
|
-
console.log(
|
|
28
|
+
console.log(`${icons.brain} Brain Daemon: ${c.red.bold('NOT RUNNING')} ${c.dim('(stale PID file)')}`);
|
|
27
29
|
return;
|
|
28
30
|
}
|
|
29
31
|
|
|
30
|
-
console.log(`Brain
|
|
32
|
+
console.log(header(`Brain Status v${getCurrentVersion()}`, icons.brain));
|
|
33
|
+
console.log(` ${c.green(`${icons.dot} RUNNING`)} ${c.dim(`(PID ${pid})`)}`);
|
|
31
34
|
|
|
32
35
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
33
36
|
await withIpc(async (client) => {
|
|
@@ -43,27 +46,30 @@ export function statusCommand(): Command {
|
|
|
43
46
|
dbSize = `${(stat.size / 1024 / 1024).toFixed(1)} MB`;
|
|
44
47
|
} catch { /* ignore */ }
|
|
45
48
|
|
|
46
|
-
console.log(
|
|
49
|
+
console.log(keyValue('Database', `${dbPath} (${dbSize})`));
|
|
47
50
|
console.log();
|
|
48
51
|
|
|
49
|
-
console.log('Error Brain
|
|
50
|
-
console.log(`
|
|
51
|
-
console.log(`
|
|
52
|
-
console.log(`
|
|
53
|
-
console.log(`
|
|
52
|
+
console.log(` ${icons.error} ${c.purple.bold('Error Brain')}`);
|
|
53
|
+
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`)}`);
|
|
54
|
+
console.log(` ${c.label('Solutions:')} ${c.value(summary.solutions?.total ?? 0)}`);
|
|
55
|
+
console.log(` ${c.label('Rules:')} ${c.green(summary.rules?.active ?? 0)} active`);
|
|
56
|
+
console.log(` ${c.label('Anti-Pat.:')} ${c.value(summary.antipatterns?.total ?? 0)}`);
|
|
54
57
|
console.log();
|
|
55
58
|
|
|
56
|
-
console.log('Code Brain
|
|
57
|
-
console.log(`
|
|
59
|
+
console.log(` ${icons.module} ${c.blue.bold('Code Brain')}`);
|
|
60
|
+
console.log(` ${c.label('Modules:')} ${c.value(summary.modules?.total ?? 0)} registered`);
|
|
58
61
|
console.log();
|
|
59
62
|
|
|
60
|
-
console.log('Synapse Network
|
|
61
|
-
console.log(`
|
|
62
|
-
console.log(`
|
|
63
|
+
console.log(` ${icons.synapse} ${c.cyan.bold('Synapse Network')}`);
|
|
64
|
+
console.log(` ${c.label('Synapses:')} ${c.value(network.totalSynapses ?? 0)}`);
|
|
65
|
+
console.log(` ${c.label('Avg weight:')} ${c.value((network.avgWeight ?? 0).toFixed(2))}`);
|
|
63
66
|
console.log();
|
|
64
67
|
|
|
65
|
-
console.log('Research Brain
|
|
66
|
-
console.log(`
|
|
68
|
+
console.log(` ${icons.insight} ${c.orange.bold('Research Brain')}`);
|
|
69
|
+
console.log(` ${c.label('Insights:')} ${c.value(summary.insights?.active ?? 0)} active`);
|
|
70
|
+
|
|
71
|
+
await checkForUpdate();
|
|
72
|
+
console.log(`\n${divider()}`);
|
|
67
73
|
});
|
|
68
74
|
});
|
|
69
75
|
}
|
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 {
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import https from 'node:https';
|
|
2
|
+
import { c, icons } from './colors.js';
|
|
3
|
+
|
|
4
|
+
// Read current version from package.json at build time
|
|
5
|
+
import { createRequire } from 'node:module';
|
|
6
|
+
const require = createRequire(import.meta.url);
|
|
7
|
+
const pkg = require('../../package.json');
|
|
8
|
+
const CURRENT_VERSION: string = pkg.version;
|
|
9
|
+
|
|
10
|
+
export function getCurrentVersion(): string {
|
|
11
|
+
return CURRENT_VERSION;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function fetchLatestVersion(): Promise<string | null> {
|
|
15
|
+
return new Promise((resolve) => {
|
|
16
|
+
const timeout = setTimeout(() => resolve(null), 3000);
|
|
17
|
+
|
|
18
|
+
const req = https.get(
|
|
19
|
+
'https://registry.npmjs.org/@timmeck/brain/latest',
|
|
20
|
+
{ headers: { Accept: 'application/json' } },
|
|
21
|
+
(res) => {
|
|
22
|
+
let data = '';
|
|
23
|
+
res.on('data', (chunk) => { data += chunk; });
|
|
24
|
+
res.on('end', () => {
|
|
25
|
+
clearTimeout(timeout);
|
|
26
|
+
try {
|
|
27
|
+
const json = JSON.parse(data);
|
|
28
|
+
resolve(json.version ?? null);
|
|
29
|
+
} catch {
|
|
30
|
+
resolve(null);
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
},
|
|
34
|
+
);
|
|
35
|
+
req.on('error', () => {
|
|
36
|
+
clearTimeout(timeout);
|
|
37
|
+
resolve(null);
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function isNewer(latest: string, current: string): boolean {
|
|
43
|
+
const l = latest.split('.').map(Number);
|
|
44
|
+
const c = current.split('.').map(Number);
|
|
45
|
+
for (let i = 0; i < 3; i++) {
|
|
46
|
+
if ((l[i] ?? 0) > (c[i] ?? 0)) return true;
|
|
47
|
+
if ((l[i] ?? 0) < (c[i] ?? 0)) return false;
|
|
48
|
+
}
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export async function checkForUpdate(): Promise<void> {
|
|
53
|
+
try {
|
|
54
|
+
const latest = await fetchLatestVersion();
|
|
55
|
+
if (latest && isNewer(latest, CURRENT_VERSION)) {
|
|
56
|
+
console.log();
|
|
57
|
+
console.log(` ${icons.star} ${c.orange.bold(`Update available: v${CURRENT_VERSION} → v${latest}`)}`);
|
|
58
|
+
console.log(` Run: ${c.cyan('npm update -g @timmeck/brain')}`);
|
|
59
|
+
}
|
|
60
|
+
} catch {
|
|
61
|
+
// silently ignore — update check is best-effort
|
|
62
|
+
}
|
|
63
|
+
}
|