@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
|
@@ -2,6 +2,7 @@ import { Command } from 'commander';
|
|
|
2
2
|
import { withIpc } from '../ipc-helper.js';
|
|
3
3
|
import { writeFileSync } from 'fs';
|
|
4
4
|
import { resolve } from 'path';
|
|
5
|
+
import { c, icons } from '../colors.js';
|
|
5
6
|
|
|
6
7
|
export function dashboardCommand(): Command {
|
|
7
8
|
return new Command('dashboard')
|
|
@@ -10,13 +11,15 @@ export function dashboardCommand(): Command {
|
|
|
10
11
|
.option('--no-open', 'Generate without opening in browser')
|
|
11
12
|
.action(async (opts) => {
|
|
12
13
|
await withIpc(async (client) => {
|
|
13
|
-
console.log('Fetching data from Brain...');
|
|
14
|
+
console.log(`${icons.chart} ${c.info('Fetching data from Brain...')}`);
|
|
14
15
|
|
|
15
16
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
16
17
|
const summary: any = await client.request('analytics.summary', {});
|
|
17
18
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
18
19
|
const network: any = await client.request('synapse.stats', {});
|
|
19
20
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
21
|
+
const networkOverview: any = await client.request('analytics.network', { limit: 50 });
|
|
22
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
20
23
|
const insights: any = await client.request('research.insights', {
|
|
21
24
|
activeOnly: true,
|
|
22
25
|
limit: 500,
|
|
@@ -44,6 +47,9 @@ export function dashboardCommand(): Command {
|
|
|
44
47
|
const warnings = insightList.filter((i: InsightItem) => i.type === 'warning');
|
|
45
48
|
const synergies = insightList.filter((i: InsightItem) => i.type === 'synergy' || i.type === 'optimization');
|
|
46
49
|
|
|
50
|
+
// Build synapse graph data
|
|
51
|
+
const synapseEdges = Array.isArray(networkOverview?.strongestSynapses) ? networkOverview.strongestSynapses : [];
|
|
52
|
+
|
|
47
53
|
const data = {
|
|
48
54
|
stats: {
|
|
49
55
|
modules: summary.modules?.total ?? 0,
|
|
@@ -55,6 +61,7 @@ export function dashboardCommand(): Command {
|
|
|
55
61
|
},
|
|
56
62
|
langStats,
|
|
57
63
|
insights: { templates, suggestions, trends, gaps, warnings, synergies },
|
|
64
|
+
synapseEdges,
|
|
58
65
|
};
|
|
59
66
|
|
|
60
67
|
const html = generateHtml(data);
|
|
@@ -63,10 +70,8 @@ export function dashboardCommand(): Command {
|
|
|
63
70
|
: resolve(import.meta.dirname, '../../../dashboard.html');
|
|
64
71
|
|
|
65
72
|
writeFileSync(outPath, html, 'utf-8');
|
|
66
|
-
console.log(
|
|
67
|
-
console.log(` Modules: ${data.stats.modules}`);
|
|
68
|
-
console.log(` Synapses: ${data.stats.synapses}`);
|
|
69
|
-
console.log(` Insights: ${data.stats.insights}`);
|
|
73
|
+
console.log(`${icons.ok} ${c.success('Dashboard written to')} ${c.dim(outPath)}`);
|
|
74
|
+
console.log(` ${c.label('Modules:')} ${c.value(data.stats.modules)} ${c.label('Synapses:')} ${c.value(data.stats.synapses)} ${c.label('Insights:')} ${c.value(data.stats.insights)}`);
|
|
70
75
|
|
|
71
76
|
if (opts.open !== false) {
|
|
72
77
|
const { exec } = await import('child_process');
|
|
@@ -83,6 +88,13 @@ interface InsightItem {
|
|
|
83
88
|
priority?: string;
|
|
84
89
|
}
|
|
85
90
|
|
|
91
|
+
interface SynapseEdge {
|
|
92
|
+
source: string;
|
|
93
|
+
target: string;
|
|
94
|
+
type: string;
|
|
95
|
+
weight: number;
|
|
96
|
+
}
|
|
97
|
+
|
|
86
98
|
interface DashboardData {
|
|
87
99
|
stats: {
|
|
88
100
|
modules: number;
|
|
@@ -101,6 +113,7 @@ interface DashboardData {
|
|
|
101
113
|
warnings: InsightItem[];
|
|
102
114
|
synergies: InsightItem[];
|
|
103
115
|
};
|
|
116
|
+
synapseEdges: SynapseEdge[];
|
|
104
117
|
}
|
|
105
118
|
|
|
106
119
|
function esc(s: string): string {
|
|
@@ -108,7 +121,7 @@ function esc(s: string): string {
|
|
|
108
121
|
}
|
|
109
122
|
|
|
110
123
|
function generateHtml(data: DashboardData): string {
|
|
111
|
-
const { stats, langStats, insights } = data;
|
|
124
|
+
const { stats, langStats, insights, synapseEdges } = data;
|
|
112
125
|
|
|
113
126
|
// Build language chart bars
|
|
114
127
|
const sortedLangs = Object.entries(langStats).sort((a, b) => b[1] - a[1]);
|
|
@@ -276,6 +289,14 @@ function generateHtml(data: DashboardData): string {
|
|
|
276
289
|
.prio-low{background:rgba(139,143,176,.1);color:var(--text2);border:1px solid rgba(139,143,176,.2)}
|
|
277
290
|
.empty{color:var(--text3);font-style:italic;padding:24px}
|
|
278
291
|
|
|
292
|
+
/* Graph */
|
|
293
|
+
.graph-container{position:relative;background:var(--glass);border:1px solid var(--glass-border);border-radius:var(--radius);overflow:hidden;backdrop-filter:blur(20px)}
|
|
294
|
+
#synapse-graph{width:100%;height:500px;display:block;cursor:grab}
|
|
295
|
+
#synapse-graph:active{cursor:grabbing}
|
|
296
|
+
.graph-legend{display:flex;gap:16px;flex-wrap:wrap;padding:12px 20px;border-top:1px solid var(--glass-border);font-size:.8rem;color:var(--text2)}
|
|
297
|
+
.legend-dot{display:inline-block;width:10px;height:10px;border-radius:50%;margin-right:6px;vertical-align:middle}
|
|
298
|
+
.graph-tooltip{position:absolute;display:none;background:var(--bg2);border:1px solid var(--glass-border);border-radius:8px;padding:8px 14px;font-size:.8rem;color:var(--text);pointer-events:none;z-index:10;backdrop-filter:blur(20px);box-shadow:0 8px 30px rgba(0,0,0,.3)}
|
|
299
|
+
|
|
279
300
|
/* Footer */
|
|
280
301
|
footer{text-align:center;padding:40px 0;border-top:1px solid var(--glass-border)}
|
|
281
302
|
footer p{color:var(--text3);font-size:.8rem}
|
|
@@ -310,7 +331,8 @@ function generateHtml(data: DashboardData): string {
|
|
|
310
331
|
<nav class="reveal reveal-delay-1">
|
|
311
332
|
<a href="#stats">Stats</a>
|
|
312
333
|
<a href="#languages">Languages</a>
|
|
313
|
-
<a href="#
|
|
334
|
+
<a href="#network">🔬 Network</a>
|
|
335
|
+
<a href="#research" class="research">💡 Research</a>
|
|
314
336
|
</nav>
|
|
315
337
|
|
|
316
338
|
<section id="stats" class="reveal reveal-delay-2">
|
|
@@ -330,7 +352,22 @@ function generateHtml(data: DashboardData): string {
|
|
|
330
352
|
<div class="lang-chart">${langBars}</div>
|
|
331
353
|
</section>
|
|
332
354
|
|
|
333
|
-
<section id="
|
|
355
|
+
<section id="network" class="reveal reveal-delay-4">
|
|
356
|
+
<div class="section-title"><div class="icon" style="background:rgba(71,229,255,.1)">🔬</div> Synapse Network</div>
|
|
357
|
+
<div class="graph-container">
|
|
358
|
+
<canvas id="synapse-graph"></canvas>
|
|
359
|
+
<div class="graph-legend">
|
|
360
|
+
<span><span class="legend-dot" style="background:var(--blue)"></span> error</span>
|
|
361
|
+
<span><span class="legend-dot" style="background:var(--green)"></span> solution</span>
|
|
362
|
+
<span><span class="legend-dot" style="background:var(--purple)"></span> code_module</span>
|
|
363
|
+
<span><span class="legend-dot" style="background:var(--orange)"></span> project</span>
|
|
364
|
+
<span><span class="legend-dot" style="background:var(--cyan)"></span> other</span>
|
|
365
|
+
</div>
|
|
366
|
+
<div id="graph-tooltip" class="graph-tooltip"></div>
|
|
367
|
+
</div>
|
|
368
|
+
</section>
|
|
369
|
+
|
|
370
|
+
<section id="research" class="reveal reveal-delay-5">
|
|
334
371
|
<div class="section-title"><div class="icon" style="background:rgba(71,229,255,.1)">🔬</div> Research Insights</div>
|
|
335
372
|
<div class="tab-bar">
|
|
336
373
|
<button class="tab-btn active" data-tab="templates">🎨 Templates <span class="count">${insights.templates.length}</span></button>
|
|
@@ -489,6 +526,192 @@ setTimeout(() => {
|
|
|
489
526
|
el.style.width = el.dataset.target + '%';
|
|
490
527
|
});
|
|
491
528
|
}, 500);
|
|
529
|
+
|
|
530
|
+
// --- Synapse Force-Directed Graph ---
|
|
531
|
+
(function(){
|
|
532
|
+
const edges = ${JSON.stringify(synapseEdges.map((e: SynapseEdge) => ({ s: e.source, t: e.target, type: e.type, w: e.weight })))};
|
|
533
|
+
const canvas = document.getElementById('synapse-graph');
|
|
534
|
+
if (!canvas || !edges.length) return;
|
|
535
|
+
const ctx = canvas.getContext('2d');
|
|
536
|
+
const container = canvas.parentElement;
|
|
537
|
+
let W, H, dpr;
|
|
538
|
+
|
|
539
|
+
const NODE_COLORS = {
|
|
540
|
+
error: '#ff5577', solution: '#3dffa0', code_module: '#b47aff',
|
|
541
|
+
project: '#ffb347', rule: '#5b9cff', antipattern: '#ff5577'
|
|
542
|
+
};
|
|
543
|
+
const DEFAULT_COLOR = '#47e5ff';
|
|
544
|
+
|
|
545
|
+
// Build graph nodes & edges
|
|
546
|
+
const nodeMap = new Map();
|
|
547
|
+
const graphEdges = [];
|
|
548
|
+
for (const e of edges) {
|
|
549
|
+
if (!nodeMap.has(e.s)) nodeMap.set(e.s, { id: e.s, type: e.s.split(':')[0], x: 0, y: 0, vx: 0, vy: 0, connections: 0 });
|
|
550
|
+
if (!nodeMap.has(e.t)) nodeMap.set(e.t, { id: e.t, type: e.t.split(':')[0], x: 0, y: 0, vx: 0, vy: 0, connections: 0 });
|
|
551
|
+
nodeMap.get(e.s).connections++;
|
|
552
|
+
nodeMap.get(e.t).connections++;
|
|
553
|
+
graphEdges.push({ source: nodeMap.get(e.s), target: nodeMap.get(e.t), type: e.type, weight: e.w });
|
|
554
|
+
}
|
|
555
|
+
const nodes = [...nodeMap.values()];
|
|
556
|
+
|
|
557
|
+
function resize() {
|
|
558
|
+
dpr = window.devicePixelRatio || 1;
|
|
559
|
+
W = container.clientWidth;
|
|
560
|
+
H = 500;
|
|
561
|
+
canvas.width = W * dpr;
|
|
562
|
+
canvas.height = H * dpr;
|
|
563
|
+
canvas.style.width = W + 'px';
|
|
564
|
+
canvas.style.height = H + 'px';
|
|
565
|
+
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
566
|
+
}
|
|
567
|
+
resize();
|
|
568
|
+
window.addEventListener('resize', resize);
|
|
569
|
+
|
|
570
|
+
// Random initial positions
|
|
571
|
+
for (const n of nodes) {
|
|
572
|
+
n.x = W * 0.2 + Math.random() * W * 0.6;
|
|
573
|
+
n.y = H * 0.2 + Math.random() * H * 0.6;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
// Force simulation
|
|
577
|
+
const REPULSION = 3000;
|
|
578
|
+
const ATTRACTION = 0.008;
|
|
579
|
+
const DAMPING = 0.85;
|
|
580
|
+
const CENTER_GRAVITY = 0.002;
|
|
581
|
+
let hovered = null;
|
|
582
|
+
let dragging = null;
|
|
583
|
+
let dragOff = {x:0,y:0};
|
|
584
|
+
|
|
585
|
+
function simulate() {
|
|
586
|
+
// Repulsion
|
|
587
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
588
|
+
for (let j = i + 1; j < nodes.length; j++) {
|
|
589
|
+
let dx = nodes[i].x - nodes[j].x;
|
|
590
|
+
let dy = nodes[i].y - nodes[j].y;
|
|
591
|
+
let dist = Math.sqrt(dx*dx + dy*dy) || 1;
|
|
592
|
+
let force = REPULSION / (dist * dist);
|
|
593
|
+
let fx = (dx / dist) * force;
|
|
594
|
+
let fy = (dy / dist) * force;
|
|
595
|
+
nodes[i].vx += fx; nodes[i].vy += fy;
|
|
596
|
+
nodes[j].vx -= fx; nodes[j].vy -= fy;
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
// Attraction along edges
|
|
600
|
+
for (const e of graphEdges) {
|
|
601
|
+
let dx = e.target.x - e.source.x;
|
|
602
|
+
let dy = e.target.y - e.source.y;
|
|
603
|
+
let dist = Math.sqrt(dx*dx + dy*dy) || 1;
|
|
604
|
+
let force = (dist - 100) * ATTRACTION * e.weight;
|
|
605
|
+
let fx = (dx / dist) * force;
|
|
606
|
+
let fy = (dy / dist) * force;
|
|
607
|
+
e.source.vx += fx; e.source.vy += fy;
|
|
608
|
+
e.target.vx -= fx; e.target.vy -= fy;
|
|
609
|
+
}
|
|
610
|
+
// Center gravity
|
|
611
|
+
for (const n of nodes) {
|
|
612
|
+
n.vx += (W/2 - n.x) * CENTER_GRAVITY;
|
|
613
|
+
n.vy += (H/2 - n.y) * CENTER_GRAVITY;
|
|
614
|
+
}
|
|
615
|
+
// Apply & damp
|
|
616
|
+
for (const n of nodes) {
|
|
617
|
+
if (n === dragging) continue;
|
|
618
|
+
n.vx *= DAMPING; n.vy *= DAMPING;
|
|
619
|
+
n.x += n.vx; n.y += n.vy;
|
|
620
|
+
n.x = Math.max(20, Math.min(W - 20, n.x));
|
|
621
|
+
n.y = Math.max(20, Math.min(H - 20, n.y));
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
function getNodeRadius(n) { return Math.min(16, 5 + n.connections * 1.5); }
|
|
626
|
+
|
|
627
|
+
function draw() {
|
|
628
|
+
ctx.clearRect(0, 0, W, H);
|
|
629
|
+
// Edges
|
|
630
|
+
for (const e of graphEdges) {
|
|
631
|
+
const alpha = 0.15 + e.weight * 0.5;
|
|
632
|
+
ctx.strokeStyle = 'rgba(91,156,255,' + Math.min(0.8, alpha) + ')';
|
|
633
|
+
ctx.lineWidth = 0.5 + e.weight * 2;
|
|
634
|
+
ctx.beginPath();
|
|
635
|
+
ctx.moveTo(e.source.x, e.source.y);
|
|
636
|
+
ctx.lineTo(e.target.x, e.target.y);
|
|
637
|
+
ctx.stroke();
|
|
638
|
+
}
|
|
639
|
+
// Nodes
|
|
640
|
+
for (const n of nodes) {
|
|
641
|
+
const r = getNodeRadius(n);
|
|
642
|
+
const color = NODE_COLORS[n.type] || DEFAULT_COLOR;
|
|
643
|
+
const isHover = n === hovered || n === dragging;
|
|
644
|
+
// Glow
|
|
645
|
+
if (isHover) {
|
|
646
|
+
ctx.shadowColor = color;
|
|
647
|
+
ctx.shadowBlur = 20;
|
|
648
|
+
}
|
|
649
|
+
ctx.fillStyle = color;
|
|
650
|
+
ctx.globalAlpha = isHover ? 1 : 0.8;
|
|
651
|
+
ctx.beginPath();
|
|
652
|
+
ctx.arc(n.x, n.y, r, 0, Math.PI * 2);
|
|
653
|
+
ctx.fill();
|
|
654
|
+
ctx.globalAlpha = 1;
|
|
655
|
+
ctx.shadowBlur = 0;
|
|
656
|
+
// Label for hovered or large nodes
|
|
657
|
+
if (isHover || n.connections >= 4) {
|
|
658
|
+
ctx.fillStyle = '#e8eaf6';
|
|
659
|
+
ctx.font = (isHover ? 'bold ' : '') + '11px Inter, system-ui, sans-serif';
|
|
660
|
+
ctx.textAlign = 'center';
|
|
661
|
+
ctx.fillText(n.id, n.x, n.y - r - 6);
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
simulate();
|
|
665
|
+
requestAnimationFrame(draw);
|
|
666
|
+
}
|
|
667
|
+
draw();
|
|
668
|
+
|
|
669
|
+
// Interaction
|
|
670
|
+
const tooltip = document.getElementById('graph-tooltip');
|
|
671
|
+
function getNodeAt(mx, my) {
|
|
672
|
+
for (let i = nodes.length - 1; i >= 0; i--) {
|
|
673
|
+
const n = nodes[i], r = getNodeRadius(n);
|
|
674
|
+
if (Math.hypot(mx - n.x, my - n.y) <= r + 4) return n;
|
|
675
|
+
}
|
|
676
|
+
return null;
|
|
677
|
+
}
|
|
678
|
+
function getPos(e) {
|
|
679
|
+
const rect = canvas.getBoundingClientRect();
|
|
680
|
+
return { x: e.clientX - rect.left, y: e.clientY - rect.top };
|
|
681
|
+
}
|
|
682
|
+
canvas.addEventListener('mousemove', function(e) {
|
|
683
|
+
const p = getPos(e);
|
|
684
|
+
if (dragging) {
|
|
685
|
+
dragging.x = p.x + dragOff.x;
|
|
686
|
+
dragging.y = p.y + dragOff.y;
|
|
687
|
+
dragging.vx = 0; dragging.vy = 0;
|
|
688
|
+
return;
|
|
689
|
+
}
|
|
690
|
+
const n = getNodeAt(p.x, p.y);
|
|
691
|
+
hovered = n;
|
|
692
|
+
canvas.style.cursor = n ? 'pointer' : 'grab';
|
|
693
|
+
if (n) {
|
|
694
|
+
const conns = graphEdges.filter(e => e.source === n || e.target === n);
|
|
695
|
+
tooltip.innerHTML = '<strong>' + n.id + '</strong><br>' + conns.length + ' connections';
|
|
696
|
+
tooltip.style.display = 'block';
|
|
697
|
+
tooltip.style.left = (p.x + 15) + 'px';
|
|
698
|
+
tooltip.style.top = (p.y - 10) + 'px';
|
|
699
|
+
} else {
|
|
700
|
+
tooltip.style.display = 'none';
|
|
701
|
+
}
|
|
702
|
+
});
|
|
703
|
+
canvas.addEventListener('mousedown', function(e) {
|
|
704
|
+
const p = getPos(e);
|
|
705
|
+
const n = getNodeAt(p.x, p.y);
|
|
706
|
+
if (n) {
|
|
707
|
+
dragging = n;
|
|
708
|
+
dragOff = { x: n.x - p.x, y: n.y - p.y };
|
|
709
|
+
canvas.style.cursor = 'grabbing';
|
|
710
|
+
}
|
|
711
|
+
});
|
|
712
|
+
canvas.addEventListener('mouseup', function() { dragging = null; });
|
|
713
|
+
canvas.addEventListener('mouseleave', function() { dragging = null; hovered = null; tooltip.style.display = 'none'; });
|
|
714
|
+
})();
|
|
492
715
|
</script>
|
|
493
716
|
</body>
|
|
494
717
|
</html>`;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
import { withIpc } from '../ipc-helper.js';
|
|
3
|
+
import { c, icons } from '../colors.js';
|
|
3
4
|
|
|
4
5
|
export function exportCommand(): Command {
|
|
5
6
|
return new Command('export')
|
|
@@ -7,6 +8,8 @@ export function exportCommand(): Command {
|
|
|
7
8
|
.option('--format <fmt>', 'Output format: json (default)', 'json')
|
|
8
9
|
.action(async () => {
|
|
9
10
|
await withIpc(async (client) => {
|
|
11
|
+
process.stderr.write(`${icons.gear} ${c.info('Exporting Brain data...')}\n`);
|
|
12
|
+
|
|
10
13
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
11
14
|
const summary: any = await client.request('analytics.summary', {});
|
|
12
15
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -22,6 +25,7 @@ export function exportCommand(): Command {
|
|
|
22
25
|
};
|
|
23
26
|
|
|
24
27
|
console.log(JSON.stringify(data, null, 2));
|
|
28
|
+
process.stderr.write(`${icons.ok} ${c.success('Export complete.')}\n`);
|
|
25
29
|
});
|
|
26
30
|
});
|
|
27
31
|
}
|
|
@@ -2,10 +2,13 @@ import { Command } from 'commander';
|
|
|
2
2
|
import { withIpc } from '../ipc-helper.js';
|
|
3
3
|
import { readdirSync, readFileSync, statSync } from 'fs';
|
|
4
4
|
import { resolve, basename, relative, extname } from 'path';
|
|
5
|
+
import { c, icons, header, divider, progressBar } from '../colors.js';
|
|
5
6
|
|
|
6
7
|
const DEFAULT_EXTENSIONS = new Set([
|
|
7
8
|
'.ts', '.tsx', '.js', '.jsx', '.py', '.rs', '.go',
|
|
8
9
|
'.java', '.c', '.cpp', '.h', '.hpp', '.rb', '.sh',
|
|
10
|
+
'.html', '.css', '.scss', '.json', '.yaml', '.yml', '.toml',
|
|
11
|
+
'.md', '.sql', '.php', '.svelte', '.vue', '.astro',
|
|
9
12
|
]);
|
|
10
13
|
|
|
11
14
|
const EXCLUDE_DIRS = new Set([
|
|
@@ -22,6 +25,10 @@ const LANG_MAP: Record<string, string> = {
|
|
|
22
25
|
py: 'python', rs: 'rust', go: 'go', java: 'java',
|
|
23
26
|
c: 'c', cpp: 'cpp', h: 'c', hpp: 'cpp',
|
|
24
27
|
rb: 'ruby', sh: 'shell', bash: 'shell',
|
|
28
|
+
html: 'html', css: 'css', scss: 'scss',
|
|
29
|
+
json: 'json', yaml: 'yaml', yml: 'yaml', toml: 'toml',
|
|
30
|
+
md: 'markdown', sql: 'sql', php: 'php',
|
|
31
|
+
svelte: 'svelte', vue: 'vue', astro: 'astro',
|
|
25
32
|
};
|
|
26
33
|
|
|
27
34
|
function detectLanguage(filePath: string): string {
|
|
@@ -75,7 +82,7 @@ export function importCommand(): Command {
|
|
|
75
82
|
.description('Import source files from a project directory into Brain')
|
|
76
83
|
.argument('<directory>', 'Project directory to scan')
|
|
77
84
|
.option('-p, --project <name>', 'Project name (default: directory basename)')
|
|
78
|
-
.option('-e, --extensions <list>', 'Comma-separated extensions (default: ts,tsx,js,jsx,py,rs,go,java,c,cpp,h,hpp,rb,sh)')
|
|
85
|
+
.option('-e, --extensions <list>', 'Comma-separated extensions (default: ts,tsx,js,jsx,py,rs,go,java,c,cpp,h,hpp,rb,sh,html,css,scss,json,yaml,yml,toml,md,sql,php,svelte,vue,astro)')
|
|
79
86
|
.option('--dry-run', 'List files that would be imported without importing')
|
|
80
87
|
.option('--max-size <kb>', 'Skip files larger than N KB', '100')
|
|
81
88
|
.action(async (directory: string, opts) => {
|
|
@@ -107,23 +114,23 @@ export function importCommand(): Command {
|
|
|
107
114
|
process.exit(1);
|
|
108
115
|
}
|
|
109
116
|
|
|
110
|
-
console.log(
|
|
117
|
+
console.log(`${icons.search} ${c.info('Scanning')} ${c.value(dir)} ...`);
|
|
111
118
|
const files = findSourceFiles(dir, extensions, maxSizeBytes);
|
|
112
119
|
|
|
113
120
|
if (files.length === 0) {
|
|
114
|
-
console.log('No source files found.');
|
|
121
|
+
console.log(`${c.dim('No source files found.')}`);
|
|
115
122
|
return;
|
|
116
123
|
}
|
|
117
124
|
|
|
118
|
-
console.log(
|
|
125
|
+
console.log(`${icons.ok} Found ${c.value(files.length)} source files.\n`);
|
|
119
126
|
|
|
120
127
|
if (opts.dryRun) {
|
|
121
128
|
for (const f of files) {
|
|
122
129
|
const rel = relative(dir, f);
|
|
123
130
|
const lang = detectLanguage(f);
|
|
124
|
-
console.log(` [${lang}] ${rel}`);
|
|
131
|
+
console.log(` ${c.cyan(`[${lang}]`)} ${c.dim(rel)}`);
|
|
125
132
|
}
|
|
126
|
-
console.log(`\n${files.length} files would be imported as project "${projectName}".`);
|
|
133
|
+
console.log(`\n${c.value(files.length)} files would be imported as project ${c.cyan(`"${projectName}"`)}.`);
|
|
127
134
|
return;
|
|
128
135
|
}
|
|
129
136
|
|
|
@@ -146,7 +153,7 @@ export function importCommand(): Command {
|
|
|
146
153
|
source = readFileSync(filePath, 'utf-8');
|
|
147
154
|
} catch {
|
|
148
155
|
failedCount++;
|
|
149
|
-
process.stdout.write(` [${i + 1}/${files.length}] ${rel} — read error\n`);
|
|
156
|
+
process.stdout.write(` ${c.dim(`[${i + 1}/${files.length}]`)} ${c.dim(rel)} ${c.red('— read error')}\n`);
|
|
150
157
|
continue;
|
|
151
158
|
}
|
|
152
159
|
|
|
@@ -164,27 +171,29 @@ export function importCommand(): Command {
|
|
|
164
171
|
});
|
|
165
172
|
|
|
166
173
|
const score = result.reusabilityScore ?? 0;
|
|
167
|
-
const
|
|
174
|
+
const scoreColor = score >= 0.7 ? c.green : score >= 0.4 ? c.orange : c.red;
|
|
175
|
+
const statusTag = result.isNew ? c.green('new') : c.dim('existing');
|
|
168
176
|
totalScore += score;
|
|
169
177
|
imported++;
|
|
170
178
|
|
|
171
179
|
if (result.isNew) newCount++;
|
|
172
180
|
else existingCount++;
|
|
173
181
|
|
|
174
|
-
process.stdout.write(` [${i + 1}/${files.length}] ${rel}
|
|
182
|
+
process.stdout.write(` ${c.dim(`[${i + 1}/${files.length}]`)} ${c.dim(rel)} ${c.dim(icons.arrow)} ${scoreColor(score.toFixed(2))} (${statusTag})\n`);
|
|
175
183
|
} catch (err) {
|
|
176
184
|
failedCount++;
|
|
177
185
|
const msg = err instanceof Error ? err.message : String(err);
|
|
178
|
-
process.stdout.write(` [${i + 1}/${files.length}] ${rel}
|
|
186
|
+
process.stdout.write(` ${c.dim(`[${i + 1}/${files.length}]`)} ${c.dim(rel)} ${c.red(`— ${msg.slice(0, 80)}`)}\n`);
|
|
179
187
|
}
|
|
180
188
|
}
|
|
181
189
|
|
|
182
190
|
const avgScore = imported > 0 ? (totalScore / imported).toFixed(2) : '0';
|
|
183
|
-
console.log(
|
|
184
|
-
console.log(` Project: ${projectName}`);
|
|
185
|
-
console.log(` Imported: ${imported} (${newCount} new, ${existingCount} existing)`);
|
|
186
|
-
if (failedCount > 0) console.log(` Failed: ${failedCount}`);
|
|
187
|
-
console.log(` Avg
|
|
191
|
+
console.log(header('Import Summary', icons.module));
|
|
192
|
+
console.log(` ${c.label('Project:')} ${c.cyan(projectName)}`);
|
|
193
|
+
console.log(` ${c.label('Imported:')} ${c.value(imported)} (${c.green(`${newCount} new`)}, ${c.dim(`${existingCount} existing`)})`);
|
|
194
|
+
if (failedCount > 0) console.log(` ${c.label('Failed:')} ${c.red(failedCount)}`);
|
|
195
|
+
console.log(` ${c.label('Avg score:')} ${c.value(avgScore)} ${progressBar(parseFloat(avgScore), 1)}`);
|
|
196
|
+
console.log(divider());
|
|
188
197
|
});
|
|
189
198
|
});
|
|
190
199
|
}
|
|
@@ -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
|
}
|