@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
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { getDataDir } from '../../utils/paths.js';
|
|
5
|
+
import { c, icons, header, divider } from '../colors.js';
|
|
6
|
+
function getConfigPath() {
|
|
7
|
+
return path.join(getDataDir(), 'config.json');
|
|
8
|
+
}
|
|
9
|
+
function readConfig() {
|
|
10
|
+
const configPath = getConfigPath();
|
|
11
|
+
if (fs.existsSync(configPath)) {
|
|
12
|
+
return JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
13
|
+
}
|
|
14
|
+
return {};
|
|
15
|
+
}
|
|
16
|
+
function writeConfig(config) {
|
|
17
|
+
const configPath = getConfigPath();
|
|
18
|
+
fs.mkdirSync(path.dirname(configPath), { recursive: true });
|
|
19
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
|
|
20
|
+
}
|
|
21
|
+
function getNestedValue(obj, keyPath) {
|
|
22
|
+
const parts = keyPath.split('.');
|
|
23
|
+
let current = obj;
|
|
24
|
+
for (const part of parts) {
|
|
25
|
+
if (current === null || current === undefined || typeof current !== 'object')
|
|
26
|
+
return undefined;
|
|
27
|
+
current = current[part];
|
|
28
|
+
}
|
|
29
|
+
return current;
|
|
30
|
+
}
|
|
31
|
+
function setNestedValue(obj, keyPath, value) {
|
|
32
|
+
const parts = keyPath.split('.');
|
|
33
|
+
let current = obj;
|
|
34
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
35
|
+
const part = parts[i];
|
|
36
|
+
if (!current[part] || typeof current[part] !== 'object') {
|
|
37
|
+
current[part] = {};
|
|
38
|
+
}
|
|
39
|
+
current = current[part];
|
|
40
|
+
}
|
|
41
|
+
current[parts[parts.length - 1]] = value;
|
|
42
|
+
}
|
|
43
|
+
function deleteNestedValue(obj, keyPath) {
|
|
44
|
+
const parts = keyPath.split('.');
|
|
45
|
+
let current = obj;
|
|
46
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
47
|
+
const part = parts[i];
|
|
48
|
+
if (!current[part] || typeof current[part] !== 'object')
|
|
49
|
+
return false;
|
|
50
|
+
current = current[part];
|
|
51
|
+
}
|
|
52
|
+
const last = parts[parts.length - 1];
|
|
53
|
+
if (last in current) {
|
|
54
|
+
delete current[last];
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
function parseValue(value) {
|
|
60
|
+
if (value === 'true')
|
|
61
|
+
return true;
|
|
62
|
+
if (value === 'false')
|
|
63
|
+
return false;
|
|
64
|
+
if (value === 'null')
|
|
65
|
+
return null;
|
|
66
|
+
const num = Number(value);
|
|
67
|
+
if (!isNaN(num) && value.trim() !== '')
|
|
68
|
+
return num;
|
|
69
|
+
// Try JSON for arrays/objects
|
|
70
|
+
if ((value.startsWith('[') && value.endsWith(']')) || (value.startsWith('{') && value.endsWith('}'))) {
|
|
71
|
+
try {
|
|
72
|
+
return JSON.parse(value);
|
|
73
|
+
}
|
|
74
|
+
catch { /* fall through */ }
|
|
75
|
+
}
|
|
76
|
+
return value;
|
|
77
|
+
}
|
|
78
|
+
function printObject(obj, indent = 0) {
|
|
79
|
+
const pad = ' '.repeat(indent);
|
|
80
|
+
if (obj === null || obj === undefined) {
|
|
81
|
+
console.log(`${pad}${c.dim('(not set)')}`);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
if (typeof obj !== 'object') {
|
|
85
|
+
console.log(`${pad}${c.value(String(obj))}`);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
for (const [key, val] of Object.entries(obj)) {
|
|
89
|
+
if (val && typeof val === 'object' && !Array.isArray(val)) {
|
|
90
|
+
console.log(`${pad}${c.cyan(key + ':')}`);
|
|
91
|
+
printObject(val, indent + 2);
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
const display = Array.isArray(val) ? JSON.stringify(val) : String(val);
|
|
95
|
+
console.log(`${pad}${c.label(key + ':')} ${c.value(display)}`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
export function configCommand() {
|
|
100
|
+
const cmd = new Command('config')
|
|
101
|
+
.description('View and modify Brain configuration');
|
|
102
|
+
cmd
|
|
103
|
+
.command('show')
|
|
104
|
+
.description('Show current configuration')
|
|
105
|
+
.argument('[key]', 'Specific config key (e.g., learning.intervalMs)')
|
|
106
|
+
.action((key) => {
|
|
107
|
+
const config = readConfig();
|
|
108
|
+
if (key) {
|
|
109
|
+
const value = getNestedValue(config, key);
|
|
110
|
+
if (value === undefined) {
|
|
111
|
+
console.log(`${c.dim(`Key "${key}" is not set in config overrides.`)}`);
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
console.log(`${c.label(key + ':')} ${c.value(typeof value === 'object' ? JSON.stringify(value, null, 2) : String(value))}`);
|
|
115
|
+
}
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
console.log(header('Brain Configuration', icons.gear));
|
|
119
|
+
console.log(` ${c.label('Config file:')} ${c.dim(getConfigPath())}\n`);
|
|
120
|
+
if (Object.keys(config).length === 0) {
|
|
121
|
+
console.log(` ${c.dim('No custom overrides. Using defaults.')}`);
|
|
122
|
+
console.log(` ${c.dim('Set values with:')} ${c.cyan('brain config set <key> <value>')}`);
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
printObject(config, 2);
|
|
126
|
+
}
|
|
127
|
+
console.log(`\n${divider()}`);
|
|
128
|
+
});
|
|
129
|
+
cmd
|
|
130
|
+
.command('set')
|
|
131
|
+
.description('Set a configuration value')
|
|
132
|
+
.argument('<key>', 'Config key path (e.g., learning.intervalMs)')
|
|
133
|
+
.argument('<value>', 'Value to set')
|
|
134
|
+
.action((key, value) => {
|
|
135
|
+
const config = readConfig();
|
|
136
|
+
const parsed = parseValue(value);
|
|
137
|
+
setNestedValue(config, key, parsed);
|
|
138
|
+
writeConfig(config);
|
|
139
|
+
console.log(`${icons.ok} ${c.label(key)} ${c.dim(icons.arrow)} ${c.value(String(parsed))}`);
|
|
140
|
+
console.log(` ${c.dim('Restart the daemon for changes to take effect:')} ${c.cyan('brain stop && brain start')}`);
|
|
141
|
+
});
|
|
142
|
+
cmd
|
|
143
|
+
.command('delete')
|
|
144
|
+
.description('Remove a configuration override (revert to default)')
|
|
145
|
+
.argument('<key>', 'Config key path to remove')
|
|
146
|
+
.action((key) => {
|
|
147
|
+
const config = readConfig();
|
|
148
|
+
if (deleteNestedValue(config, key)) {
|
|
149
|
+
writeConfig(config);
|
|
150
|
+
console.log(`${icons.ok} ${c.dim(`Removed "${key}" — will use default value.`)}`);
|
|
151
|
+
console.log(` ${c.dim('Restart the daemon for changes to take effect:')} ${c.cyan('brain stop && brain start')}`);
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
console.log(`${c.dim(`Key "${key}" not found in config overrides.`)}`);
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
cmd
|
|
158
|
+
.command('path')
|
|
159
|
+
.description('Show the config file path')
|
|
160
|
+
.action(() => {
|
|
161
|
+
console.log(getConfigPath());
|
|
162
|
+
});
|
|
163
|
+
return cmd;
|
|
164
|
+
}
|
|
165
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../../src/cli/commands/config.ts"],"names":[],"mappings":"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"}
|
|
@@ -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
|
export function dashboardCommand() {
|
|
6
7
|
return new Command('dashboard')
|
|
7
8
|
.description('Generate and open the Brain dashboard with live data')
|
|
@@ -9,12 +10,14 @@ export function dashboardCommand() {
|
|
|
9
10
|
.option('--no-open', 'Generate without opening in browser')
|
|
10
11
|
.action(async (opts) => {
|
|
11
12
|
await withIpc(async (client) => {
|
|
12
|
-
console.log('Fetching data from Brain...');
|
|
13
|
+
console.log(`${icons.chart} ${c.info('Fetching data from Brain...')}`);
|
|
13
14
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
14
15
|
const summary = await client.request('analytics.summary', {});
|
|
15
16
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
16
17
|
const network = await client.request('synapse.stats', {});
|
|
17
18
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
19
|
+
const networkOverview = await client.request('analytics.network', { limit: 50 });
|
|
20
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
18
21
|
const insights = await client.request('research.insights', {
|
|
19
22
|
activeOnly: true,
|
|
20
23
|
limit: 500,
|
|
@@ -40,6 +43,8 @@ export function dashboardCommand() {
|
|
|
40
43
|
const gaps = insightList.filter((i) => i.type === 'gap');
|
|
41
44
|
const warnings = insightList.filter((i) => i.type === 'warning');
|
|
42
45
|
const synergies = insightList.filter((i) => i.type === 'synergy' || i.type === 'optimization');
|
|
46
|
+
// Build synapse graph data
|
|
47
|
+
const synapseEdges = Array.isArray(networkOverview?.strongestSynapses) ? networkOverview.strongestSynapses : [];
|
|
43
48
|
const data = {
|
|
44
49
|
stats: {
|
|
45
50
|
modules: summary.modules?.total ?? 0,
|
|
@@ -51,16 +56,15 @@ export function dashboardCommand() {
|
|
|
51
56
|
},
|
|
52
57
|
langStats,
|
|
53
58
|
insights: { templates, suggestions, trends, gaps, warnings, synergies },
|
|
59
|
+
synapseEdges,
|
|
54
60
|
};
|
|
55
61
|
const html = generateHtml(data);
|
|
56
62
|
const outPath = opts.output
|
|
57
63
|
? resolve(opts.output)
|
|
58
64
|
: resolve(import.meta.dirname, '../../../dashboard.html');
|
|
59
65
|
writeFileSync(outPath, html, 'utf-8');
|
|
60
|
-
console.log(
|
|
61
|
-
console.log(` Modules: ${data.stats.modules}`);
|
|
62
|
-
console.log(` Synapses: ${data.stats.synapses}`);
|
|
63
|
-
console.log(` Insights: ${data.stats.insights}`);
|
|
66
|
+
console.log(`${icons.ok} ${c.success('Dashboard written to')} ${c.dim(outPath)}`);
|
|
67
|
+
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)}`);
|
|
64
68
|
if (opts.open !== false) {
|
|
65
69
|
const { exec } = await import('child_process');
|
|
66
70
|
exec(`start "" "${outPath}"`);
|
|
@@ -72,7 +76,7 @@ function esc(s) {
|
|
|
72
76
|
return s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
73
77
|
}
|
|
74
78
|
function generateHtml(data) {
|
|
75
|
-
const { stats, langStats, insights } = data;
|
|
79
|
+
const { stats, langStats, insights, synapseEdges } = data;
|
|
76
80
|
// Build language chart bars
|
|
77
81
|
const sortedLangs = Object.entries(langStats).sort((a, b) => b[1] - a[1]);
|
|
78
82
|
const maxLang = sortedLangs[0]?.[1] || 1;
|
|
@@ -237,6 +241,14 @@ function generateHtml(data) {
|
|
|
237
241
|
.prio-low{background:rgba(139,143,176,.1);color:var(--text2);border:1px solid rgba(139,143,176,.2)}
|
|
238
242
|
.empty{color:var(--text3);font-style:italic;padding:24px}
|
|
239
243
|
|
|
244
|
+
/* Graph */
|
|
245
|
+
.graph-container{position:relative;background:var(--glass);border:1px solid var(--glass-border);border-radius:var(--radius);overflow:hidden;backdrop-filter:blur(20px)}
|
|
246
|
+
#synapse-graph{width:100%;height:500px;display:block;cursor:grab}
|
|
247
|
+
#synapse-graph:active{cursor:grabbing}
|
|
248
|
+
.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)}
|
|
249
|
+
.legend-dot{display:inline-block;width:10px;height:10px;border-radius:50%;margin-right:6px;vertical-align:middle}
|
|
250
|
+
.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)}
|
|
251
|
+
|
|
240
252
|
/* Footer */
|
|
241
253
|
footer{text-align:center;padding:40px 0;border-top:1px solid var(--glass-border)}
|
|
242
254
|
footer p{color:var(--text3);font-size:.8rem}
|
|
@@ -271,7 +283,8 @@ function generateHtml(data) {
|
|
|
271
283
|
<nav class="reveal reveal-delay-1">
|
|
272
284
|
<a href="#stats">Stats</a>
|
|
273
285
|
<a href="#languages">Languages</a>
|
|
274
|
-
<a href="#
|
|
286
|
+
<a href="#network">🔬 Network</a>
|
|
287
|
+
<a href="#research" class="research">💡 Research</a>
|
|
275
288
|
</nav>
|
|
276
289
|
|
|
277
290
|
<section id="stats" class="reveal reveal-delay-2">
|
|
@@ -291,7 +304,22 @@ function generateHtml(data) {
|
|
|
291
304
|
<div class="lang-chart">${langBars}</div>
|
|
292
305
|
</section>
|
|
293
306
|
|
|
294
|
-
<section id="
|
|
307
|
+
<section id="network" class="reveal reveal-delay-4">
|
|
308
|
+
<div class="section-title"><div class="icon" style="background:rgba(71,229,255,.1)">🔬</div> Synapse Network</div>
|
|
309
|
+
<div class="graph-container">
|
|
310
|
+
<canvas id="synapse-graph"></canvas>
|
|
311
|
+
<div class="graph-legend">
|
|
312
|
+
<span><span class="legend-dot" style="background:var(--blue)"></span> error</span>
|
|
313
|
+
<span><span class="legend-dot" style="background:var(--green)"></span> solution</span>
|
|
314
|
+
<span><span class="legend-dot" style="background:var(--purple)"></span> code_module</span>
|
|
315
|
+
<span><span class="legend-dot" style="background:var(--orange)"></span> project</span>
|
|
316
|
+
<span><span class="legend-dot" style="background:var(--cyan)"></span> other</span>
|
|
317
|
+
</div>
|
|
318
|
+
<div id="graph-tooltip" class="graph-tooltip"></div>
|
|
319
|
+
</div>
|
|
320
|
+
</section>
|
|
321
|
+
|
|
322
|
+
<section id="research" class="reveal reveal-delay-5">
|
|
295
323
|
<div class="section-title"><div class="icon" style="background:rgba(71,229,255,.1)">🔬</div> Research Insights</div>
|
|
296
324
|
<div class="tab-bar">
|
|
297
325
|
<button class="tab-btn active" data-tab="templates">🎨 Templates <span class="count">${insights.templates.length}</span></button>
|
|
@@ -450,6 +478,192 @@ setTimeout(() => {
|
|
|
450
478
|
el.style.width = el.dataset.target + '%';
|
|
451
479
|
});
|
|
452
480
|
}, 500);
|
|
481
|
+
|
|
482
|
+
// --- Synapse Force-Directed Graph ---
|
|
483
|
+
(function(){
|
|
484
|
+
const edges = ${JSON.stringify(synapseEdges.map((e) => ({ s: e.source, t: e.target, type: e.type, w: e.weight })))};
|
|
485
|
+
const canvas = document.getElementById('synapse-graph');
|
|
486
|
+
if (!canvas || !edges.length) return;
|
|
487
|
+
const ctx = canvas.getContext('2d');
|
|
488
|
+
const container = canvas.parentElement;
|
|
489
|
+
let W, H, dpr;
|
|
490
|
+
|
|
491
|
+
const NODE_COLORS = {
|
|
492
|
+
error: '#ff5577', solution: '#3dffa0', code_module: '#b47aff',
|
|
493
|
+
project: '#ffb347', rule: '#5b9cff', antipattern: '#ff5577'
|
|
494
|
+
};
|
|
495
|
+
const DEFAULT_COLOR = '#47e5ff';
|
|
496
|
+
|
|
497
|
+
// Build graph nodes & edges
|
|
498
|
+
const nodeMap = new Map();
|
|
499
|
+
const graphEdges = [];
|
|
500
|
+
for (const e of edges) {
|
|
501
|
+
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 });
|
|
502
|
+
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 });
|
|
503
|
+
nodeMap.get(e.s).connections++;
|
|
504
|
+
nodeMap.get(e.t).connections++;
|
|
505
|
+
graphEdges.push({ source: nodeMap.get(e.s), target: nodeMap.get(e.t), type: e.type, weight: e.w });
|
|
506
|
+
}
|
|
507
|
+
const nodes = [...nodeMap.values()];
|
|
508
|
+
|
|
509
|
+
function resize() {
|
|
510
|
+
dpr = window.devicePixelRatio || 1;
|
|
511
|
+
W = container.clientWidth;
|
|
512
|
+
H = 500;
|
|
513
|
+
canvas.width = W * dpr;
|
|
514
|
+
canvas.height = H * dpr;
|
|
515
|
+
canvas.style.width = W + 'px';
|
|
516
|
+
canvas.style.height = H + 'px';
|
|
517
|
+
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
518
|
+
}
|
|
519
|
+
resize();
|
|
520
|
+
window.addEventListener('resize', resize);
|
|
521
|
+
|
|
522
|
+
// Random initial positions
|
|
523
|
+
for (const n of nodes) {
|
|
524
|
+
n.x = W * 0.2 + Math.random() * W * 0.6;
|
|
525
|
+
n.y = H * 0.2 + Math.random() * H * 0.6;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// Force simulation
|
|
529
|
+
const REPULSION = 3000;
|
|
530
|
+
const ATTRACTION = 0.008;
|
|
531
|
+
const DAMPING = 0.85;
|
|
532
|
+
const CENTER_GRAVITY = 0.002;
|
|
533
|
+
let hovered = null;
|
|
534
|
+
let dragging = null;
|
|
535
|
+
let dragOff = {x:0,y:0};
|
|
536
|
+
|
|
537
|
+
function simulate() {
|
|
538
|
+
// Repulsion
|
|
539
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
540
|
+
for (let j = i + 1; j < nodes.length; j++) {
|
|
541
|
+
let dx = nodes[i].x - nodes[j].x;
|
|
542
|
+
let dy = nodes[i].y - nodes[j].y;
|
|
543
|
+
let dist = Math.sqrt(dx*dx + dy*dy) || 1;
|
|
544
|
+
let force = REPULSION / (dist * dist);
|
|
545
|
+
let fx = (dx / dist) * force;
|
|
546
|
+
let fy = (dy / dist) * force;
|
|
547
|
+
nodes[i].vx += fx; nodes[i].vy += fy;
|
|
548
|
+
nodes[j].vx -= fx; nodes[j].vy -= fy;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
// Attraction along edges
|
|
552
|
+
for (const e of graphEdges) {
|
|
553
|
+
let dx = e.target.x - e.source.x;
|
|
554
|
+
let dy = e.target.y - e.source.y;
|
|
555
|
+
let dist = Math.sqrt(dx*dx + dy*dy) || 1;
|
|
556
|
+
let force = (dist - 100) * ATTRACTION * e.weight;
|
|
557
|
+
let fx = (dx / dist) * force;
|
|
558
|
+
let fy = (dy / dist) * force;
|
|
559
|
+
e.source.vx += fx; e.source.vy += fy;
|
|
560
|
+
e.target.vx -= fx; e.target.vy -= fy;
|
|
561
|
+
}
|
|
562
|
+
// Center gravity
|
|
563
|
+
for (const n of nodes) {
|
|
564
|
+
n.vx += (W/2 - n.x) * CENTER_GRAVITY;
|
|
565
|
+
n.vy += (H/2 - n.y) * CENTER_GRAVITY;
|
|
566
|
+
}
|
|
567
|
+
// Apply & damp
|
|
568
|
+
for (const n of nodes) {
|
|
569
|
+
if (n === dragging) continue;
|
|
570
|
+
n.vx *= DAMPING; n.vy *= DAMPING;
|
|
571
|
+
n.x += n.vx; n.y += n.vy;
|
|
572
|
+
n.x = Math.max(20, Math.min(W - 20, n.x));
|
|
573
|
+
n.y = Math.max(20, Math.min(H - 20, n.y));
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
function getNodeRadius(n) { return Math.min(16, 5 + n.connections * 1.5); }
|
|
578
|
+
|
|
579
|
+
function draw() {
|
|
580
|
+
ctx.clearRect(0, 0, W, H);
|
|
581
|
+
// Edges
|
|
582
|
+
for (const e of graphEdges) {
|
|
583
|
+
const alpha = 0.15 + e.weight * 0.5;
|
|
584
|
+
ctx.strokeStyle = 'rgba(91,156,255,' + Math.min(0.8, alpha) + ')';
|
|
585
|
+
ctx.lineWidth = 0.5 + e.weight * 2;
|
|
586
|
+
ctx.beginPath();
|
|
587
|
+
ctx.moveTo(e.source.x, e.source.y);
|
|
588
|
+
ctx.lineTo(e.target.x, e.target.y);
|
|
589
|
+
ctx.stroke();
|
|
590
|
+
}
|
|
591
|
+
// Nodes
|
|
592
|
+
for (const n of nodes) {
|
|
593
|
+
const r = getNodeRadius(n);
|
|
594
|
+
const color = NODE_COLORS[n.type] || DEFAULT_COLOR;
|
|
595
|
+
const isHover = n === hovered || n === dragging;
|
|
596
|
+
// Glow
|
|
597
|
+
if (isHover) {
|
|
598
|
+
ctx.shadowColor = color;
|
|
599
|
+
ctx.shadowBlur = 20;
|
|
600
|
+
}
|
|
601
|
+
ctx.fillStyle = color;
|
|
602
|
+
ctx.globalAlpha = isHover ? 1 : 0.8;
|
|
603
|
+
ctx.beginPath();
|
|
604
|
+
ctx.arc(n.x, n.y, r, 0, Math.PI * 2);
|
|
605
|
+
ctx.fill();
|
|
606
|
+
ctx.globalAlpha = 1;
|
|
607
|
+
ctx.shadowBlur = 0;
|
|
608
|
+
// Label for hovered or large nodes
|
|
609
|
+
if (isHover || n.connections >= 4) {
|
|
610
|
+
ctx.fillStyle = '#e8eaf6';
|
|
611
|
+
ctx.font = (isHover ? 'bold ' : '') + '11px Inter, system-ui, sans-serif';
|
|
612
|
+
ctx.textAlign = 'center';
|
|
613
|
+
ctx.fillText(n.id, n.x, n.y - r - 6);
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
simulate();
|
|
617
|
+
requestAnimationFrame(draw);
|
|
618
|
+
}
|
|
619
|
+
draw();
|
|
620
|
+
|
|
621
|
+
// Interaction
|
|
622
|
+
const tooltip = document.getElementById('graph-tooltip');
|
|
623
|
+
function getNodeAt(mx, my) {
|
|
624
|
+
for (let i = nodes.length - 1; i >= 0; i--) {
|
|
625
|
+
const n = nodes[i], r = getNodeRadius(n);
|
|
626
|
+
if (Math.hypot(mx - n.x, my - n.y) <= r + 4) return n;
|
|
627
|
+
}
|
|
628
|
+
return null;
|
|
629
|
+
}
|
|
630
|
+
function getPos(e) {
|
|
631
|
+
const rect = canvas.getBoundingClientRect();
|
|
632
|
+
return { x: e.clientX - rect.left, y: e.clientY - rect.top };
|
|
633
|
+
}
|
|
634
|
+
canvas.addEventListener('mousemove', function(e) {
|
|
635
|
+
const p = getPos(e);
|
|
636
|
+
if (dragging) {
|
|
637
|
+
dragging.x = p.x + dragOff.x;
|
|
638
|
+
dragging.y = p.y + dragOff.y;
|
|
639
|
+
dragging.vx = 0; dragging.vy = 0;
|
|
640
|
+
return;
|
|
641
|
+
}
|
|
642
|
+
const n = getNodeAt(p.x, p.y);
|
|
643
|
+
hovered = n;
|
|
644
|
+
canvas.style.cursor = n ? 'pointer' : 'grab';
|
|
645
|
+
if (n) {
|
|
646
|
+
const conns = graphEdges.filter(e => e.source === n || e.target === n);
|
|
647
|
+
tooltip.innerHTML = '<strong>' + n.id + '</strong><br>' + conns.length + ' connections';
|
|
648
|
+
tooltip.style.display = 'block';
|
|
649
|
+
tooltip.style.left = (p.x + 15) + 'px';
|
|
650
|
+
tooltip.style.top = (p.y - 10) + 'px';
|
|
651
|
+
} else {
|
|
652
|
+
tooltip.style.display = 'none';
|
|
653
|
+
}
|
|
654
|
+
});
|
|
655
|
+
canvas.addEventListener('mousedown', function(e) {
|
|
656
|
+
const p = getPos(e);
|
|
657
|
+
const n = getNodeAt(p.x, p.y);
|
|
658
|
+
if (n) {
|
|
659
|
+
dragging = n;
|
|
660
|
+
dragOff = { x: n.x - p.x, y: n.y - p.y };
|
|
661
|
+
canvas.style.cursor = 'grabbing';
|
|
662
|
+
}
|
|
663
|
+
});
|
|
664
|
+
canvas.addEventListener('mouseup', function() { dragging = null; });
|
|
665
|
+
canvas.addEventListener('mouseleave', function() { dragging = null; hovered = null; tooltip.style.display = 'none'; });
|
|
666
|
+
})();
|
|
453
667
|
</script>
|
|
454
668
|
</body>
|
|
455
669
|
</html>`;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dashboard.js","sourceRoot":"","sources":["../../../src/cli/commands/dashboard.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAC3C,OAAO,EAAE,aAAa,EAAE,MAAM,IAAI,CAAC;AACnC,OAAO,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"dashboard.js","sourceRoot":"","sources":["../../../src/cli/commands/dashboard.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
import { withIpc } from '../ipc-helper.js';
|
|
3
|
+
import { c, icons } from '../colors.js';
|
|
3
4
|
export function exportCommand() {
|
|
4
5
|
return new Command('export')
|
|
5
6
|
.description('Export Brain data')
|
|
6
7
|
.option('--format <fmt>', 'Output format: json (default)', 'json')
|
|
7
8
|
.action(async () => {
|
|
8
9
|
await withIpc(async (client) => {
|
|
10
|
+
process.stderr.write(`${icons.gear} ${c.info('Exporting Brain data...')}\n`);
|
|
9
11
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
10
12
|
const summary = await client.request('analytics.summary', {});
|
|
11
13
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -19,6 +21,7 @@ export function exportCommand() {
|
|
|
19
21
|
insights,
|
|
20
22
|
};
|
|
21
23
|
console.log(JSON.stringify(data, null, 2));
|
|
24
|
+
process.stderr.write(`${icons.ok} ${c.success('Export complete.')}\n`);
|
|
22
25
|
});
|
|
23
26
|
});
|
|
24
27
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"export.js","sourceRoot":"","sources":["../../../src/cli/commands/export.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"export.js","sourceRoot":"","sources":["../../../src/cli/commands/export.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAC3C,OAAO,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AAExC,MAAM,UAAU,aAAa;IAC3B,OAAO,IAAI,OAAO,CAAC,QAAQ,CAAC;SACzB,WAAW,CAAC,mBAAmB,CAAC;SAChC,MAAM,CAAC,gBAAgB,EAAE,+BAA+B,EAAE,MAAM,CAAC;SACjE,MAAM,CAAC,KAAK,IAAI,EAAE;QACjB,MAAM,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE;YAC7B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,KAAK,CAAC,IAAI,KAAK,CAAC,CAAC,IAAI,CAAC,yBAAyB,CAAC,IAAI,CAAC,CAAC;YAE9E,8DAA8D;YAC9D,MAAM,OAAO,GAAQ,MAAM,MAAM,CAAC,OAAO,CAAC,mBAAmB,EAAE,EAAE,CAAC,CAAC;YACnE,8DAA8D;YAC9D,MAAM,OAAO,GAAQ,MAAM,MAAM,CAAC,OAAO,CAAC,mBAAmB,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC;YAC/E,8DAA8D;YAC9D,MAAM,QAAQ,GAAQ,MAAM,MAAM,CAAC,OAAO,CAAC,mBAAmB,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC;YAElG,MAAM,IAAI,GAAG;gBACX,UAAU,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;gBACpC,OAAO;gBACP,OAAO;gBACP,QAAQ;aACT,CAAC;YAEF,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;YAC3C,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,KAAK,CAAC,EAAE,KAAK,CAAC,CAAC,OAAO,CAAC,kBAAkB,CAAC,IAAI,CAAC,CAAC;QAC1E,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACP,CAAC"}
|
|
@@ -2,9 +2,12 @@ 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
|
const DEFAULT_EXTENSIONS = new Set([
|
|
6
7
|
'.ts', '.tsx', '.js', '.jsx', '.py', '.rs', '.go',
|
|
7
8
|
'.java', '.c', '.cpp', '.h', '.hpp', '.rb', '.sh',
|
|
9
|
+
'.html', '.css', '.scss', '.json', '.yaml', '.yml', '.toml',
|
|
10
|
+
'.md', '.sql', '.php', '.svelte', '.vue', '.astro',
|
|
8
11
|
]);
|
|
9
12
|
const EXCLUDE_DIRS = new Set([
|
|
10
13
|
'node_modules', 'dist', 'build', '.git', '.next',
|
|
@@ -18,6 +21,10 @@ const LANG_MAP = {
|
|
|
18
21
|
py: 'python', rs: 'rust', go: 'go', java: 'java',
|
|
19
22
|
c: 'c', cpp: 'cpp', h: 'c', hpp: 'cpp',
|
|
20
23
|
rb: 'ruby', sh: 'shell', bash: 'shell',
|
|
24
|
+
html: 'html', css: 'css', scss: 'scss',
|
|
25
|
+
json: 'json', yaml: 'yaml', yml: 'yaml', toml: 'toml',
|
|
26
|
+
md: 'markdown', sql: 'sql', php: 'php',
|
|
27
|
+
svelte: 'svelte', vue: 'vue', astro: 'astro',
|
|
21
28
|
};
|
|
22
29
|
function detectLanguage(filePath) {
|
|
23
30
|
const ext = extname(filePath).slice(1).toLowerCase();
|
|
@@ -67,7 +74,7 @@ export function importCommand() {
|
|
|
67
74
|
.description('Import source files from a project directory into Brain')
|
|
68
75
|
.argument('<directory>', 'Project directory to scan')
|
|
69
76
|
.option('-p, --project <name>', 'Project name (default: directory basename)')
|
|
70
|
-
.option('-e, --extensions <list>', 'Comma-separated extensions (default: ts,tsx,js,jsx,py,rs,go,java,c,cpp,h,hpp,rb,sh)')
|
|
77
|
+
.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)')
|
|
71
78
|
.option('--dry-run', 'List files that would be imported without importing')
|
|
72
79
|
.option('--max-size <kb>', 'Skip files larger than N KB', '100')
|
|
73
80
|
.action(async (directory, opts) => {
|
|
@@ -95,20 +102,20 @@ export function importCommand() {
|
|
|
95
102
|
console.error(`Directory not found: ${dir}`);
|
|
96
103
|
process.exit(1);
|
|
97
104
|
}
|
|
98
|
-
console.log(
|
|
105
|
+
console.log(`${icons.search} ${c.info('Scanning')} ${c.value(dir)} ...`);
|
|
99
106
|
const files = findSourceFiles(dir, extensions, maxSizeBytes);
|
|
100
107
|
if (files.length === 0) {
|
|
101
|
-
console.log('No source files found.');
|
|
108
|
+
console.log(`${c.dim('No source files found.')}`);
|
|
102
109
|
return;
|
|
103
110
|
}
|
|
104
|
-
console.log(
|
|
111
|
+
console.log(`${icons.ok} Found ${c.value(files.length)} source files.\n`);
|
|
105
112
|
if (opts.dryRun) {
|
|
106
113
|
for (const f of files) {
|
|
107
114
|
const rel = relative(dir, f);
|
|
108
115
|
const lang = detectLanguage(f);
|
|
109
|
-
console.log(` [${lang}] ${rel}`);
|
|
116
|
+
console.log(` ${c.cyan(`[${lang}]`)} ${c.dim(rel)}`);
|
|
110
117
|
}
|
|
111
|
-
console.log(`\n${files.length} files would be imported as project "${projectName}".`);
|
|
118
|
+
console.log(`\n${c.value(files.length)} files would be imported as project ${c.cyan(`"${projectName}"`)}.`);
|
|
112
119
|
return;
|
|
113
120
|
}
|
|
114
121
|
// Import via IPC
|
|
@@ -129,7 +136,7 @@ export function importCommand() {
|
|
|
129
136
|
}
|
|
130
137
|
catch {
|
|
131
138
|
failedCount++;
|
|
132
|
-
process.stdout.write(` [${i + 1}/${files.length}] ${rel} — read error\n`);
|
|
139
|
+
process.stdout.write(` ${c.dim(`[${i + 1}/${files.length}]`)} ${c.dim(rel)} ${c.red('— read error')}\n`);
|
|
133
140
|
continue;
|
|
134
141
|
}
|
|
135
142
|
// Skip empty files
|
|
@@ -145,28 +152,30 @@ export function importCommand() {
|
|
|
145
152
|
source,
|
|
146
153
|
});
|
|
147
154
|
const score = result.reusabilityScore ?? 0;
|
|
148
|
-
const
|
|
155
|
+
const scoreColor = score >= 0.7 ? c.green : score >= 0.4 ? c.orange : c.red;
|
|
156
|
+
const statusTag = result.isNew ? c.green('new') : c.dim('existing');
|
|
149
157
|
totalScore += score;
|
|
150
158
|
imported++;
|
|
151
159
|
if (result.isNew)
|
|
152
160
|
newCount++;
|
|
153
161
|
else
|
|
154
162
|
existingCount++;
|
|
155
|
-
process.stdout.write(` [${i + 1}/${files.length}] ${rel}
|
|
163
|
+
process.stdout.write(` ${c.dim(`[${i + 1}/${files.length}]`)} ${c.dim(rel)} ${c.dim(icons.arrow)} ${scoreColor(score.toFixed(2))} (${statusTag})\n`);
|
|
156
164
|
}
|
|
157
165
|
catch (err) {
|
|
158
166
|
failedCount++;
|
|
159
167
|
const msg = err instanceof Error ? err.message : String(err);
|
|
160
|
-
process.stdout.write(` [${i + 1}/${files.length}] ${rel}
|
|
168
|
+
process.stdout.write(` ${c.dim(`[${i + 1}/${files.length}]`)} ${c.dim(rel)} ${c.red(`— ${msg.slice(0, 80)}`)}\n`);
|
|
161
169
|
}
|
|
162
170
|
}
|
|
163
171
|
const avgScore = imported > 0 ? (totalScore / imported).toFixed(2) : '0';
|
|
164
|
-
console.log(
|
|
165
|
-
console.log(` Project: ${projectName}`);
|
|
166
|
-
console.log(` Imported: ${imported} (${newCount} new, ${existingCount} existing)`);
|
|
172
|
+
console.log(header('Import Summary', icons.module));
|
|
173
|
+
console.log(` ${c.label('Project:')} ${c.cyan(projectName)}`);
|
|
174
|
+
console.log(` ${c.label('Imported:')} ${c.value(imported)} (${c.green(`${newCount} new`)}, ${c.dim(`${existingCount} existing`)})`);
|
|
167
175
|
if (failedCount > 0)
|
|
168
|
-
console.log(` Failed: ${failedCount}`);
|
|
169
|
-
console.log(` Avg
|
|
176
|
+
console.log(` ${c.label('Failed:')} ${c.red(failedCount)}`);
|
|
177
|
+
console.log(` ${c.label('Avg score:')} ${c.value(avgScore)} ${progressBar(parseFloat(avgScore), 1)}`);
|
|
178
|
+
console.log(divider());
|
|
170
179
|
});
|
|
171
180
|
});
|
|
172
181
|
}
|