@timmeck/brain 1.8.5 → 2.0.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/.dockerignore +11 -0
- package/Dockerfile +21 -0
- package/README.md +19 -0
- package/assets/brain-avatar-256.png +0 -0
- package/assets/brain-avatar-512.png +0 -0
- package/assets/brain-avatar.png +0 -0
- package/brain.log +1164 -0
- package/{src/cli/commands/dashboard.ts → dashboard.html} +688 -807
- package/dist/api/server.d.ts +4 -18
- package/dist/api/server.js +4 -173
- package/dist/api/server.js.map +1 -1
- package/dist/brain.d.ts +6 -0
- package/dist/brain.js +56 -4
- package/dist/brain.js.map +1 -1
- package/dist/cli/colors.d.ts +4 -25
- package/dist/cli/colors.js +3 -89
- package/dist/cli/colors.js.map +1 -1
- package/dist/cli/commands/peers.d.ts +2 -0
- package/dist/cli/commands/peers.js +38 -0
- package/dist/cli/commands/peers.js.map +1 -0
- package/dist/cli/commands/start.js +54 -17
- package/dist/cli/commands/start.js.map +1 -1
- package/dist/db/connection.d.ts +1 -2
- package/dist/db/connection.js +1 -18
- package/dist/db/connection.js.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/ipc/__tests__/protocol.test.d.ts +1 -0
- package/dist/ipc/__tests__/protocol.test.js +117 -0
- package/dist/ipc/__tests__/protocol.test.js.map +1 -0
- package/dist/ipc/client.d.ts +1 -16
- package/dist/ipc/client.js +1 -100
- package/dist/ipc/client.js.map +1 -1
- package/dist/ipc/protocol.d.ts +1 -8
- package/dist/ipc/protocol.js +1 -28
- package/dist/ipc/protocol.js.map +1 -1
- package/dist/ipc/router.js +8 -0
- package/dist/ipc/router.js.map +1 -1
- package/dist/ipc/server.d.ts +1 -22
- package/dist/ipc/server.js +1 -163
- package/dist/ipc/server.js.map +1 -1
- package/dist/mcp/http-server.d.ts +1 -7
- package/dist/mcp/http-server.js +6 -117
- package/dist/mcp/http-server.js.map +1 -1
- package/dist/mcp/server.js +5 -61
- package/dist/mcp/server.js.map +1 -1
- package/dist/signals/__tests__/fingerprint.test.d.ts +1 -0
- package/dist/signals/__tests__/fingerprint.test.js +118 -0
- package/dist/signals/__tests__/fingerprint.test.js.map +1 -0
- package/dist/types/ipc.types.d.ts +1 -11
- package/dist/utils/__tests__/hash.test.d.ts +1 -0
- package/dist/utils/__tests__/hash.test.js +32 -0
- package/dist/utils/__tests__/hash.test.js.map +1 -0
- package/dist/utils/__tests__/paths.test.d.ts +1 -0
- package/dist/utils/__tests__/paths.test.js +75 -0
- package/dist/utils/__tests__/paths.test.js.map +1 -0
- package/dist/utils/events.d.ts +4 -8
- package/dist/utils/events.js +2 -14
- package/dist/utils/events.js.map +1 -1
- package/dist/utils/hash.d.ts +1 -1
- package/dist/utils/hash.js +1 -4
- package/dist/utils/hash.js.map +1 -1
- package/dist/utils/logger.d.ts +3 -2
- package/dist/utils/logger.js +8 -35
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/paths.d.ts +2 -1
- package/dist/utils/paths.js +4 -13
- package/dist/utils/paths.js.map +1 -1
- package/gen_avatar.py +142 -0
- package/package.json +2 -1
- package/BRAIN_PLAN.md +0 -3324
- package/src/api/server.ts +0 -395
- package/src/brain.ts +0 -266
- package/src/cli/colors.ts +0 -116
- package/src/cli/commands/config.ts +0 -169
- package/src/cli/commands/doctor.ts +0 -124
- package/src/cli/commands/explain.ts +0 -83
- package/src/cli/commands/export.ts +0 -31
- package/src/cli/commands/import.ts +0 -199
- package/src/cli/commands/insights.ts +0 -65
- package/src/cli/commands/learn.ts +0 -24
- package/src/cli/commands/modules.ts +0 -53
- package/src/cli/commands/network.ts +0 -67
- package/src/cli/commands/projects.ts +0 -42
- package/src/cli/commands/query.ts +0 -120
- package/src/cli/commands/start.ts +0 -62
- package/src/cli/commands/status.ts +0 -75
- package/src/cli/commands/stop.ts +0 -34
- package/src/cli/ipc-helper.ts +0 -22
- package/src/cli/update-check.ts +0 -63
- package/src/code/analyzer.ts +0 -117
- package/src/code/fingerprint.ts +0 -87
- package/src/code/matcher.ts +0 -129
- package/src/code/parsers/generic.ts +0 -29
- package/src/code/parsers/python.ts +0 -54
- package/src/code/parsers/typescript.ts +0 -65
- package/src/code/registry.ts +0 -60
- package/src/code/scorer.ts +0 -120
- package/src/config.ts +0 -135
- package/src/dashboard/server.ts +0 -142
- package/src/db/connection.ts +0 -22
- package/src/db/migrations/001_core_schema.ts +0 -120
- package/src/db/migrations/002_learning_schema.ts +0 -38
- package/src/db/migrations/003_code_schema.ts +0 -53
- package/src/db/migrations/004_synapses_schema.ts +0 -57
- package/src/db/migrations/005_fts_indexes.ts +0 -78
- package/src/db/migrations/006_synapses_phase3.ts +0 -17
- package/src/db/migrations/007_feedback.ts +0 -13
- package/src/db/migrations/008_git_integration.ts +0 -38
- package/src/db/migrations/009_embeddings.ts +0 -8
- package/src/db/migrations/index.ts +0 -70
- package/src/db/repositories/antipattern.repository.ts +0 -66
- package/src/db/repositories/code-module.repository.ts +0 -142
- package/src/db/repositories/error.repository.ts +0 -189
- package/src/db/repositories/insight.repository.ts +0 -99
- package/src/db/repositories/notification.repository.ts +0 -66
- package/src/db/repositories/project.repository.ts +0 -93
- package/src/db/repositories/rule.repository.ts +0 -108
- package/src/db/repositories/solution.repository.ts +0 -154
- package/src/db/repositories/synapse.repository.ts +0 -163
- package/src/db/repositories/terminal.repository.ts +0 -101
- package/src/embeddings/engine.ts +0 -238
- package/src/hooks/post-tool-use.ts +0 -92
- package/src/hooks/post-write.ts +0 -129
- package/src/index.ts +0 -63
- package/src/ipc/client.ts +0 -118
- package/src/ipc/protocol.ts +0 -35
- package/src/ipc/router.ts +0 -133
- package/src/ipc/server.ts +0 -176
- package/src/learning/confidence-scorer.ts +0 -80
- package/src/learning/decay.ts +0 -46
- package/src/learning/learning-engine.ts +0 -170
- package/src/learning/pattern-extractor.ts +0 -90
- package/src/learning/rule-generator.ts +0 -74
- package/src/main.rs:10:5 +0 -0
- package/src/matching/error-matcher.ts +0 -166
- package/src/matching/fingerprint.ts +0 -34
- package/src/matching/similarity.ts +0 -61
- package/src/matching/tfidf.ts +0 -74
- package/src/matching/tokenizer.ts +0 -41
- package/src/mcp/auto-detect.ts +0 -93
- package/src/mcp/http-server.ts +0 -140
- package/src/mcp/server.ts +0 -73
- package/src/mcp/tools.ts +0 -328
- package/src/parsing/error-parser.ts +0 -28
- package/src/parsing/parsers/compiler.ts +0 -93
- package/src/parsing/parsers/generic.ts +0 -28
- package/src/parsing/parsers/go.ts +0 -97
- package/src/parsing/parsers/node.ts +0 -69
- package/src/parsing/parsers/python.ts +0 -62
- package/src/parsing/parsers/rust.ts +0 -50
- package/src/parsing/parsers/shell.ts +0 -42
- package/src/parsing/types.ts +0 -47
- package/src/research/gap-analyzer.ts +0 -135
- package/src/research/insight-generator.ts +0 -123
- package/src/research/research-engine.ts +0 -116
- package/src/research/synergy-detector.ts +0 -126
- package/src/research/template-extractor.ts +0 -130
- package/src/research/trend-analyzer.ts +0 -127
- package/src/services/analytics.service.ts +0 -226
- package/src/services/code.service.ts +0 -271
- package/src/services/error.service.ts +0 -266
- package/src/services/git.service.ts +0 -132
- package/src/services/notification.service.ts +0 -41
- package/src/services/prevention.service.ts +0 -159
- package/src/services/research.service.ts +0 -98
- package/src/services/solution.service.ts +0 -174
- package/src/services/synapse.service.ts +0 -59
- package/src/services/terminal.service.ts +0 -81
- package/src/synapses/activation.ts +0 -80
- package/src/synapses/decay.ts +0 -38
- package/src/synapses/hebbian.ts +0 -69
- package/src/synapses/pathfinder.ts +0 -81
- package/src/synapses/synapse-manager.ts +0 -113
- package/src/types/code.types.ts +0 -52
- package/src/types/config.types.ts +0 -103
- package/src/types/error.types.ts +0 -67
- package/src/types/ipc.types.ts +0 -8
- package/src/types/mcp.types.ts +0 -53
- package/src/types/research.types.ts +0 -28
- package/src/types/solution.types.ts +0 -30
- package/src/types/synapse.types.ts +0 -50
- package/src/utils/events.ts +0 -45
- package/src/utils/hash.ts +0 -5
- package/src/utils/logger.ts +0 -48
- package/src/utils/paths.ts +0 -19
- package/tests/e2e/test_code_intelligence.py +0 -1015
- package/tests/e2e/test_error_memory.py +0 -451
- package/tests/e2e/test_full_integration.py +0 -534
- package/tests/fixtures/code-modules/modules.ts +0 -83
- package/tests/fixtures/errors/go.ts +0 -9
- package/tests/fixtures/errors/node.ts +0 -24
- package/tests/fixtures/errors/python.ts +0 -21
- package/tests/fixtures/errors/rust.ts +0 -25
- package/tests/fixtures/errors/shell.ts +0 -15
- package/tests/fixtures/solutions/solutions.ts +0 -27
- package/tests/helpers/setup-db.ts +0 -52
- package/tests/integration/code-flow.test.ts +0 -86
- package/tests/integration/error-flow.test.ts +0 -83
- package/tests/integration/ipc-flow.test.ts +0 -166
- package/tests/integration/learning-cycle.test.ts +0 -82
- package/tests/integration/synapse-flow.test.ts +0 -117
- package/tests/unit/code/analyzer.test.ts +0 -58
- package/tests/unit/code/fingerprint.test.ts +0 -51
- package/tests/unit/code/scorer.test.ts +0 -55
- package/tests/unit/learning/confidence-scorer.test.ts +0 -60
- package/tests/unit/learning/decay.test.ts +0 -45
- package/tests/unit/learning/pattern-extractor.test.ts +0 -50
- package/tests/unit/matching/error-matcher.test.ts +0 -69
- package/tests/unit/matching/fingerprint.test.ts +0 -47
- package/tests/unit/matching/similarity.test.ts +0 -65
- package/tests/unit/matching/tfidf.test.ts +0 -71
- package/tests/unit/matching/tokenizer.test.ts +0 -83
- package/tests/unit/parsing/parsers.test.ts +0 -113
- package/tests/unit/research/gap-analyzer.test.ts +0 -45
- package/tests/unit/research/trend-analyzer.test.ts +0 -45
- package/tests/unit/synapses/activation.test.ts +0 -80
- package/tests/unit/synapses/decay.test.ts +0 -27
- package/tests/unit/synapses/hebbian.test.ts +0 -96
- package/tests/unit/synapses/pathfinder.test.ts +0 -72
- package/tsconfig.json +0 -18
|
@@ -1,75 +0,0 @@
|
|
|
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 { withIpc } from '../ipc-helper.js';
|
|
6
|
-
import { c, icons, header, keyValue, divider } from '../colors.js';
|
|
7
|
-
import { checkForUpdate, getCurrentVersion } from '../update-check.js';
|
|
8
|
-
|
|
9
|
-
export function statusCommand(): Command {
|
|
10
|
-
return new Command('status')
|
|
11
|
-
.description('Show Brain daemon status')
|
|
12
|
-
.action(async () => {
|
|
13
|
-
const pidPath = path.join(getDataDir(), 'brain.pid');
|
|
14
|
-
|
|
15
|
-
if (!fs.existsSync(pidPath)) {
|
|
16
|
-
console.log(`${icons.brain} Brain Daemon: ${c.red.bold('NOT RUNNING')}`);
|
|
17
|
-
return;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const pid = parseInt(fs.readFileSync(pidPath, 'utf8').trim(), 10);
|
|
21
|
-
let running = false;
|
|
22
|
-
try {
|
|
23
|
-
process.kill(pid, 0);
|
|
24
|
-
running = true;
|
|
25
|
-
} catch { /* not running */ }
|
|
26
|
-
|
|
27
|
-
if (!running) {
|
|
28
|
-
console.log(`${icons.brain} Brain Daemon: ${c.red.bold('NOT RUNNING')} ${c.dim('(stale PID file)')}`);
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
console.log(header(`Brain Status v${getCurrentVersion()}`, icons.brain));
|
|
33
|
-
console.log(` ${c.green(`${icons.dot} RUNNING`)} ${c.dim(`(PID ${pid})`)}`);
|
|
34
|
-
|
|
35
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
36
|
-
await withIpc(async (client) => {
|
|
37
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
38
|
-
const summary: any = await client.request('analytics.summary', {});
|
|
39
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
40
|
-
const network: any = await client.request('synapse.stats', {});
|
|
41
|
-
|
|
42
|
-
const dbPath = path.join(getDataDir(), 'brain.db');
|
|
43
|
-
let dbSize = '?';
|
|
44
|
-
try {
|
|
45
|
-
const stat = fs.statSync(dbPath);
|
|
46
|
-
dbSize = `${(stat.size / 1024 / 1024).toFixed(1)} MB`;
|
|
47
|
-
} catch { /* ignore */ }
|
|
48
|
-
|
|
49
|
-
console.log(keyValue('Database', `${dbPath} (${dbSize})`));
|
|
50
|
-
console.log();
|
|
51
|
-
|
|
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)}`);
|
|
57
|
-
console.log();
|
|
58
|
-
|
|
59
|
-
console.log(` ${icons.module} ${c.blue.bold('Code Brain')}`);
|
|
60
|
-
console.log(` ${c.label('Modules:')} ${c.value(summary.modules?.total ?? 0)} registered`);
|
|
61
|
-
console.log();
|
|
62
|
-
|
|
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))}`);
|
|
66
|
-
console.log();
|
|
67
|
-
|
|
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()}`);
|
|
73
|
-
});
|
|
74
|
-
});
|
|
75
|
-
}
|
package/src/cli/commands/stop.ts
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
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 } from '../colors.js';
|
|
6
|
-
|
|
7
|
-
export function stopCommand(): Command {
|
|
8
|
-
return new Command('stop')
|
|
9
|
-
.description('Stop the Brain daemon')
|
|
10
|
-
.action(() => {
|
|
11
|
-
const pidPath = path.join(getDataDir(), 'brain.pid');
|
|
12
|
-
|
|
13
|
-
if (!fs.existsSync(pidPath)) {
|
|
14
|
-
console.log(`${icons.brain} ${c.dim('Brain daemon is not running (no PID file found).')}`);
|
|
15
|
-
return;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const pid = parseInt(fs.readFileSync(pidPath, 'utf8').trim(), 10);
|
|
19
|
-
|
|
20
|
-
try {
|
|
21
|
-
process.kill(pid, 'SIGTERM');
|
|
22
|
-
console.log(`${icons.brain} ${c.success('Brain daemon stopped')} ${c.dim(`(PID: ${pid})`)}`);
|
|
23
|
-
} catch (err) {
|
|
24
|
-
if ((err as NodeJS.ErrnoException).code === 'ESRCH') {
|
|
25
|
-
console.log(`${icons.brain} ${c.dim('Brain daemon was not running (stale PID file removed).')}`);
|
|
26
|
-
} else {
|
|
27
|
-
console.error(`${icons.error} ${c.error(`Failed to stop daemon: ${err}`)}`);
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// Clean up PID file
|
|
32
|
-
try { fs.unlinkSync(pidPath); } catch { /* ignore */ }
|
|
33
|
-
});
|
|
34
|
-
}
|
package/src/cli/ipc-helper.ts
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { IpcClient } from '../ipc/client.js';
|
|
2
|
-
import { getPipeName } from '../utils/paths.js';
|
|
3
|
-
import { c, icons } from './colors.js';
|
|
4
|
-
|
|
5
|
-
export async function withIpc<T>(fn: (client: IpcClient) => Promise<T>): Promise<T> {
|
|
6
|
-
const client = new IpcClient(getPipeName(), 5000);
|
|
7
|
-
try {
|
|
8
|
-
await client.connect();
|
|
9
|
-
return await fn(client);
|
|
10
|
-
} catch (err) {
|
|
11
|
-
if (err instanceof Error && err.message.includes('ENOENT')) {
|
|
12
|
-
console.error(`${icons.error} ${c.error('Brain daemon is not running.')} Start it with: ${c.cyan('brain start')}`);
|
|
13
|
-
} else if (err instanceof Error && err.message.includes('ECONNREFUSED')) {
|
|
14
|
-
console.error(`${icons.error} ${c.error('Brain daemon is not responding.')} Try: ${c.cyan('brain stop && brain start')}`);
|
|
15
|
-
} else {
|
|
16
|
-
console.error(`${icons.error} ${c.error(err instanceof Error ? err.message : String(err))}`);
|
|
17
|
-
}
|
|
18
|
-
process.exit(1);
|
|
19
|
-
} finally {
|
|
20
|
-
client.disconnect();
|
|
21
|
-
}
|
|
22
|
-
}
|
package/src/cli/update-check.ts
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
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
|
-
}
|
package/src/code/analyzer.ts
DELETED
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
import type { ExportInfo } from '../types/code.types.js';
|
|
2
|
-
import * as tsParser from './parsers/typescript.js';
|
|
3
|
-
import * as pyParser from './parsers/python.js';
|
|
4
|
-
import * as genericParser from './parsers/generic.js';
|
|
5
|
-
|
|
6
|
-
export interface AnalysisResult {
|
|
7
|
-
exports: ExportInfo[];
|
|
8
|
-
externalDeps: string[];
|
|
9
|
-
internalDeps: string[];
|
|
10
|
-
isPure: boolean;
|
|
11
|
-
hasTypeAnnotations: boolean;
|
|
12
|
-
linesOfCode: number;
|
|
13
|
-
complexity: number;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const SIDE_EFFECT_PATTERNS = [
|
|
17
|
-
'fs.', 'process.exit', 'process.env', 'console.', 'fetch(',
|
|
18
|
-
'XMLHttpRequest', 'document.', 'window.',
|
|
19
|
-
'global.', 'require(',
|
|
20
|
-
];
|
|
21
|
-
|
|
22
|
-
function getParser(language: string) {
|
|
23
|
-
switch (language) {
|
|
24
|
-
case 'typescript':
|
|
25
|
-
case 'javascript':
|
|
26
|
-
return tsParser;
|
|
27
|
-
case 'python':
|
|
28
|
-
return pyParser;
|
|
29
|
-
default:
|
|
30
|
-
return genericParser;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export function analyzeCode(source: string, language: string): AnalysisResult {
|
|
35
|
-
const parser = getParser(language);
|
|
36
|
-
const exports = parser.extractExports(source);
|
|
37
|
-
const { external, internal } = parser.extractImports(source);
|
|
38
|
-
const isPure = checkPurity(source);
|
|
39
|
-
const typed = parser.hasTypeAnnotations(source);
|
|
40
|
-
const linesOfCode = source.split('\n').filter(l => l.trim().length > 0).length;
|
|
41
|
-
const complexity = computeCyclomaticComplexity(source, language);
|
|
42
|
-
|
|
43
|
-
return {
|
|
44
|
-
exports,
|
|
45
|
-
externalDeps: external,
|
|
46
|
-
internalDeps: internal,
|
|
47
|
-
isPure,
|
|
48
|
-
hasTypeAnnotations: typed,
|
|
49
|
-
linesOfCode,
|
|
50
|
-
complexity,
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Computes cyclomatic complexity: counts decision points in the code.
|
|
56
|
-
* CC = 1 + number of decision points (if, else if, for, while, case, catch, &&, ||, ?:)
|
|
57
|
-
*/
|
|
58
|
-
export function computeCyclomaticComplexity(source: string, language: string): number {
|
|
59
|
-
// Remove comments and strings to avoid false positives
|
|
60
|
-
const cleaned = source
|
|
61
|
-
.replace(/\/\/.*$/gm, '') // single-line comments
|
|
62
|
-
.replace(/\/\*[\s\S]*?\*\//g, '') // multi-line comments
|
|
63
|
-
.replace(/#.*$/gm, '') // Python comments
|
|
64
|
-
.replace(/(["'`])(?:(?!\1|\\).|\\.)*\1/g, '""'); // strings
|
|
65
|
-
|
|
66
|
-
let complexity = 1; // Base complexity
|
|
67
|
-
|
|
68
|
-
// Language-agnostic decision point patterns
|
|
69
|
-
const patterns = [
|
|
70
|
-
/\bif\b/g,
|
|
71
|
-
/\belse\s+if\b/g,
|
|
72
|
-
/\belif\b/g,
|
|
73
|
-
/\bfor\b/g,
|
|
74
|
-
/\bwhile\b/g,
|
|
75
|
-
/\bcase\b/g,
|
|
76
|
-
/\bcatch\b/g,
|
|
77
|
-
/\bexcept\b/g,
|
|
78
|
-
/&&/g,
|
|
79
|
-
/\|\|/g,
|
|
80
|
-
/\?\s*[^:]/g, // ternary operator (not type annotations)
|
|
81
|
-
];
|
|
82
|
-
|
|
83
|
-
for (const pattern of patterns) {
|
|
84
|
-
const matches = cleaned.match(pattern);
|
|
85
|
-
if (matches) complexity += matches.length;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
return complexity;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
export function checkPurity(source: string): boolean {
|
|
92
|
-
return !SIDE_EFFECT_PATTERNS.some(p => source.includes(p));
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
export function measureCohesion(exports: ExportInfo[]): number {
|
|
96
|
-
if (exports.length <= 1) return 1.0;
|
|
97
|
-
|
|
98
|
-
const names = exports.map(e =>
|
|
99
|
-
e.name
|
|
100
|
-
.replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2')
|
|
101
|
-
.replace(/([a-z\d])([A-Z])/g, '$1 $2')
|
|
102
|
-
.toLowerCase()
|
|
103
|
-
.split(/\s+/)
|
|
104
|
-
);
|
|
105
|
-
|
|
106
|
-
const vocab = new Set<string>();
|
|
107
|
-
names.forEach(tokens => tokens.forEach(t => vocab.add(t)));
|
|
108
|
-
|
|
109
|
-
let sharedTokens = 0;
|
|
110
|
-
for (const token of vocab) {
|
|
111
|
-
const count = names.filter(n => n.includes(token)).length;
|
|
112
|
-
if (count > 1) sharedTokens += count;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
const maxPossible = names.length * vocab.size;
|
|
116
|
-
return maxPossible === 0 ? 0 : sharedTokens / maxPossible;
|
|
117
|
-
}
|
package/src/code/fingerprint.ts
DELETED
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
import { sha256 } from '../utils/hash.js';
|
|
2
|
-
|
|
3
|
-
export function fingerprintCode(source: string, language: string): string {
|
|
4
|
-
let normalized = stripComments(source, language);
|
|
5
|
-
normalized = normalized.replace(/\s+/g, ' ').trim();
|
|
6
|
-
normalized = normalizeIdentifiers(normalized, language);
|
|
7
|
-
normalized = normalized.replace(/'[^']*'/g, "'<STR>'");
|
|
8
|
-
normalized = normalized.replace(/"[^"]*"/g, '"<STR>"');
|
|
9
|
-
normalized = normalized.replace(/`[^`]*`/g, '`<STR>`');
|
|
10
|
-
normalized = normalized.replace(/\b\d+\b/g, '<NUM>');
|
|
11
|
-
return sha256(normalized);
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export function stripComments(source: string, language: string): string {
|
|
15
|
-
switch (language) {
|
|
16
|
-
case 'typescript':
|
|
17
|
-
case 'javascript':
|
|
18
|
-
case 'java':
|
|
19
|
-
case 'go':
|
|
20
|
-
case 'rust':
|
|
21
|
-
case 'c':
|
|
22
|
-
case 'cpp':
|
|
23
|
-
return source
|
|
24
|
-
.replace(/\/\/.*$/gm, '')
|
|
25
|
-
.replace(/\/\*[\s\S]*?\*\//g, '');
|
|
26
|
-
case 'python':
|
|
27
|
-
return source
|
|
28
|
-
.replace(/#.*$/gm, '')
|
|
29
|
-
.replace(/"""[\s\S]*?"""/g, '')
|
|
30
|
-
.replace(/'''[\s\S]*?'''/g, '');
|
|
31
|
-
default:
|
|
32
|
-
return source
|
|
33
|
-
.replace(/\/\/.*$/gm, '')
|
|
34
|
-
.replace(/#.*$/gm, '');
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function normalizeIdentifiers(source: string, language: string): string {
|
|
39
|
-
const importNames = extractImportNames(source, language);
|
|
40
|
-
const keywords = getLanguageKeywords(language);
|
|
41
|
-
const preserve = new Set([...importNames, ...keywords]);
|
|
42
|
-
|
|
43
|
-
return source.replace(/\b[a-zA-Z_]\w*\b/g, (match) => {
|
|
44
|
-
if (preserve.has(match)) return match;
|
|
45
|
-
if (match[0] === match[0]!.toUpperCase() && match[0] !== match[0]!.toLowerCase()) return '<CLASS>';
|
|
46
|
-
return '<VAR>';
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function extractImportNames(source: string, language: string): string[] {
|
|
51
|
-
const names: string[] = [];
|
|
52
|
-
|
|
53
|
-
if (language === 'typescript' || language === 'javascript') {
|
|
54
|
-
const re = /import\s+(?:\{([^}]+)\}|\*\s+as\s+(\w+)|(\w+))/g;
|
|
55
|
-
let m: RegExpExecArray | null;
|
|
56
|
-
while ((m = re.exec(source)) !== null) {
|
|
57
|
-
if (m[1]) names.push(...m[1].split(',').map(s => s.trim().split(/\s+as\s+/).pop()!));
|
|
58
|
-
if (m[2]) names.push(m[2]);
|
|
59
|
-
if (m[3]) names.push(m[3]);
|
|
60
|
-
}
|
|
61
|
-
} else if (language === 'python') {
|
|
62
|
-
const re = /(?:from\s+\S+\s+)?import\s+(.+)/g;
|
|
63
|
-
let m: RegExpExecArray | null;
|
|
64
|
-
while ((m = re.exec(source)) !== null) {
|
|
65
|
-
names.push(...m[1]!.split(',').map(s => {
|
|
66
|
-
const parts = s.trim().split(/\s+as\s+/);
|
|
67
|
-
return parts[parts.length - 1]!;
|
|
68
|
-
}));
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
return names.filter(Boolean);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function getLanguageKeywords(language: string): string[] {
|
|
76
|
-
const common = ['if', 'else', 'for', 'while', 'return', 'break', 'continue', 'switch', 'case', 'default', 'try', 'catch', 'throw', 'new', 'delete', 'true', 'false', 'null', 'undefined', 'void'];
|
|
77
|
-
|
|
78
|
-
const langKeywords: Record<string, string[]> = {
|
|
79
|
-
typescript: [...common, 'const', 'let', 'var', 'function', 'class', 'interface', 'type', 'enum', 'import', 'export', 'from', 'async', 'await', 'extends', 'implements', 'readonly', 'private', 'public', 'protected', 'static', 'abstract', 'as', 'is', 'in', 'of', 'typeof', 'keyof', 'infer', 'never', 'unknown', 'any', 'string', 'number', 'boolean', 'symbol', 'object'],
|
|
80
|
-
javascript: [...common, 'const', 'let', 'var', 'function', 'class', 'import', 'export', 'from', 'async', 'await', 'extends', 'typeof', 'instanceof', 'in', 'of', 'this', 'super', 'yield'],
|
|
81
|
-
python: ['def', 'class', 'import', 'from', 'if', 'elif', 'else', 'for', 'while', 'return', 'yield', 'break', 'continue', 'try', 'except', 'finally', 'raise', 'with', 'as', 'pass', 'lambda', 'True', 'False', 'None', 'and', 'or', 'not', 'in', 'is', 'global', 'nonlocal', 'assert', 'async', 'await', 'self'],
|
|
82
|
-
rust: [...common, 'fn', 'let', 'mut', 'pub', 'struct', 'enum', 'impl', 'trait', 'use', 'mod', 'crate', 'self', 'super', 'match', 'loop', 'move', 'ref', 'where', 'async', 'await', 'dyn', 'Box', 'Vec', 'String', 'Option', 'Result', 'Some', 'None', 'Ok', 'Err'],
|
|
83
|
-
go: [...common, 'func', 'package', 'import', 'type', 'struct', 'interface', 'map', 'chan', 'go', 'defer', 'select', 'range', 'var', 'const', 'nil', 'make', 'len', 'append', 'cap', 'copy', 'close'],
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
return langKeywords[language] ?? common;
|
|
87
|
-
}
|
package/src/code/matcher.ts
DELETED
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
import type { CodeModuleRecord } from '../types/code.types.js';
|
|
2
|
-
import { fingerprintCode } from './fingerprint.js';
|
|
3
|
-
import { tokenize } from '../matching/tokenizer.js';
|
|
4
|
-
import { cosineSimilarity } from '../matching/similarity.js';
|
|
5
|
-
|
|
6
|
-
export interface CodeMatchResult {
|
|
7
|
-
moduleId: number;
|
|
8
|
-
score: number;
|
|
9
|
-
matchType: 'exact' | 'structural' | 'semantic' | 'vector';
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export function findExactMatches(
|
|
13
|
-
fingerprint: string,
|
|
14
|
-
candidates: CodeModuleRecord[],
|
|
15
|
-
): CodeMatchResult[] {
|
|
16
|
-
return candidates
|
|
17
|
-
.filter(c => c.fingerprint === fingerprint)
|
|
18
|
-
.map(c => ({ moduleId: c.id, score: 1.0, matchType: 'exact' as const }));
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export function findStructuralMatches(
|
|
22
|
-
source: string,
|
|
23
|
-
language: string,
|
|
24
|
-
candidates: CodeModuleRecord[],
|
|
25
|
-
threshold: number = 0.75,
|
|
26
|
-
): CodeMatchResult[] {
|
|
27
|
-
const fp = fingerprintCode(source, language);
|
|
28
|
-
const results: CodeMatchResult[] = [];
|
|
29
|
-
|
|
30
|
-
for (const candidate of candidates) {
|
|
31
|
-
if (candidate.fingerprint === fp) {
|
|
32
|
-
results.push({ moduleId: candidate.id, score: 1.0, matchType: 'structural' });
|
|
33
|
-
continue;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const tokensA = tokenize(source);
|
|
37
|
-
const tokensB = tokenize(candidate.name + ' ' + (candidate.description ?? ''));
|
|
38
|
-
const sim = cosineSimilarity(tokensA, tokensB);
|
|
39
|
-
if (sim >= threshold) {
|
|
40
|
-
results.push({ moduleId: candidate.id, score: sim, matchType: 'structural' });
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
return results.sort((a, b) => b.score - a.score);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export function findSemanticMatches(
|
|
48
|
-
description: string,
|
|
49
|
-
candidates: CodeModuleRecord[],
|
|
50
|
-
threshold: number = 0.5,
|
|
51
|
-
): CodeMatchResult[] {
|
|
52
|
-
const queryTokens = tokenize(description);
|
|
53
|
-
|
|
54
|
-
return candidates
|
|
55
|
-
.map(c => {
|
|
56
|
-
const candidateTokens = tokenize(
|
|
57
|
-
[c.name, c.description ?? '', c.file_path].join(' ')
|
|
58
|
-
);
|
|
59
|
-
const score = cosineSimilarity(queryTokens, candidateTokens);
|
|
60
|
-
return { moduleId: c.id, score, matchType: 'semantic' as const };
|
|
61
|
-
})
|
|
62
|
-
.filter(r => r.score >= threshold)
|
|
63
|
-
.sort((a, b) => b.score - a.score);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Find matches using pre-computed vector embeddings.
|
|
68
|
-
* Vector scores are computed externally (by the EmbeddingEngine) and passed in.
|
|
69
|
-
*/
|
|
70
|
-
export function findVectorMatches(
|
|
71
|
-
vectorScores: Map<number, number>,
|
|
72
|
-
threshold: number = 0.5,
|
|
73
|
-
): CodeMatchResult[] {
|
|
74
|
-
const results: CodeMatchResult[] = [];
|
|
75
|
-
|
|
76
|
-
for (const [moduleId, score] of vectorScores) {
|
|
77
|
-
if (score >= threshold) {
|
|
78
|
-
results.push({ moduleId, score, matchType: 'vector' });
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
return results.sort((a, b) => b.score - a.score);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Hybrid search: combine structural + semantic + vector matches,
|
|
87
|
-
* deduplicating and taking the highest score per module.
|
|
88
|
-
*/
|
|
89
|
-
export function findHybridMatches(
|
|
90
|
-
source: string,
|
|
91
|
-
language: string,
|
|
92
|
-
description: string,
|
|
93
|
-
candidates: CodeModuleRecord[],
|
|
94
|
-
vectorScores?: Map<number, number>,
|
|
95
|
-
): CodeMatchResult[] {
|
|
96
|
-
const scoreMap = new Map<number, CodeMatchResult>();
|
|
97
|
-
|
|
98
|
-
// Structural matches (highest priority)
|
|
99
|
-
for (const match of findStructuralMatches(source, language, candidates, 0.5)) {
|
|
100
|
-
const existing = scoreMap.get(match.moduleId);
|
|
101
|
-
if (!existing || match.score > existing.score) {
|
|
102
|
-
scoreMap.set(match.moduleId, match);
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// Semantic matches
|
|
107
|
-
for (const match of findSemanticMatches(description, candidates, 0.3)) {
|
|
108
|
-
const existing = scoreMap.get(match.moduleId);
|
|
109
|
-
if (!existing || match.score > existing.score) {
|
|
110
|
-
scoreMap.set(match.moduleId, match);
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// Vector matches (if available)
|
|
115
|
-
if (vectorScores && vectorScores.size > 0) {
|
|
116
|
-
for (const match of findVectorMatches(vectorScores, 0.4)) {
|
|
117
|
-
const existing = scoreMap.get(match.moduleId);
|
|
118
|
-
if (!existing) {
|
|
119
|
-
scoreMap.set(match.moduleId, match);
|
|
120
|
-
} else {
|
|
121
|
-
// Boost existing matches that also have high vector similarity
|
|
122
|
-
const vectorBoost = match.score * 0.15;
|
|
123
|
-
existing.score = Math.min(1.0, existing.score + vectorBoost);
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
return [...scoreMap.values()].sort((a, b) => b.score - a.score);
|
|
129
|
-
}
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import type { ExportInfo } from '../../types/code.types.js';
|
|
2
|
-
|
|
3
|
-
const FUNC_RE = /(?:function|func|fn|def|sub)\s+(\w+)/g;
|
|
4
|
-
const CLASS_RE = /(?:class|struct|type)\s+(\w+)/g;
|
|
5
|
-
|
|
6
|
-
export function extractExports(source: string): ExportInfo[] {
|
|
7
|
-
const exports: ExportInfo[] = [];
|
|
8
|
-
let match: RegExpExecArray | null;
|
|
9
|
-
|
|
10
|
-
const funcRe = new RegExp(FUNC_RE.source, 'g');
|
|
11
|
-
while ((match = funcRe.exec(source)) !== null) {
|
|
12
|
-
exports.push({ name: match[1]!, type: 'function' });
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const classRe = new RegExp(CLASS_RE.source, 'g');
|
|
16
|
-
while ((match = classRe.exec(source)) !== null) {
|
|
17
|
-
exports.push({ name: match[1]!, type: 'class' });
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
return exports;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export function extractImports(_source: string): { external: string[]; internal: string[] } {
|
|
24
|
-
return { external: [], internal: [] };
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export function hasTypeAnnotations(_source: string): boolean {
|
|
28
|
-
return false;
|
|
29
|
-
}
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import type { ExportInfo } from '../../types/code.types.js';
|
|
2
|
-
|
|
3
|
-
const FUNC_DEF_RE = /^def\s+(\w+)\s*\(/gm;
|
|
4
|
-
const CLASS_DEF_RE = /^class\s+(\w+)/gm;
|
|
5
|
-
const IMPORT_RE = /^(?:from\s+(\S+)\s+)?import\s+(.+)/gm;
|
|
6
|
-
const TOP_LEVEL_ASSIGN_RE = /^([A-Z_][A-Z_\d]*)\s*=/gm;
|
|
7
|
-
|
|
8
|
-
export function extractExports(source: string): ExportInfo[] {
|
|
9
|
-
const exports: ExportInfo[] = [];
|
|
10
|
-
let match: RegExpExecArray | null;
|
|
11
|
-
|
|
12
|
-
const funcRe = new RegExp(FUNC_DEF_RE.source, 'gm');
|
|
13
|
-
while ((match = funcRe.exec(source)) !== null) {
|
|
14
|
-
if (!match[1]!.startsWith('_')) {
|
|
15
|
-
exports.push({ name: match[1]!, type: 'function' });
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const classRe = new RegExp(CLASS_DEF_RE.source, 'gm');
|
|
20
|
-
while ((match = classRe.exec(source)) !== null) {
|
|
21
|
-
if (!match[1]!.startsWith('_')) {
|
|
22
|
-
exports.push({ name: match[1]!, type: 'class' });
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const constRe = new RegExp(TOP_LEVEL_ASSIGN_RE.source, 'gm');
|
|
27
|
-
while ((match = constRe.exec(source)) !== null) {
|
|
28
|
-
exports.push({ name: match[1]!, type: 'constant' });
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
return exports;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export function extractImports(source: string): { external: string[]; internal: string[] } {
|
|
35
|
-
const external: string[] = [];
|
|
36
|
-
const internal: string[] = [];
|
|
37
|
-
let match: RegExpExecArray | null;
|
|
38
|
-
|
|
39
|
-
const importRe = new RegExp(IMPORT_RE.source, 'gm');
|
|
40
|
-
while ((match = importRe.exec(source)) !== null) {
|
|
41
|
-
const module = match[1] ?? match[2]!.trim().split(/\s*,\s*/)[0]!;
|
|
42
|
-
if (module.startsWith('.')) {
|
|
43
|
-
internal.push(module);
|
|
44
|
-
} else {
|
|
45
|
-
external.push(module);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
return { external, internal };
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export function hasTypeAnnotations(source: string): boolean {
|
|
53
|
-
return /:\s*\w+/.test(source) && /->/.test(source);
|
|
54
|
-
}
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import type { ExportInfo } from '../../types/code.types.js';
|
|
2
|
-
|
|
3
|
-
const NAMED_EXPORT_RE = /export\s+(?:async\s+)?(?:function|const|let|var|class|interface|type|enum)\s+(\w+)/g;
|
|
4
|
-
const DEFAULT_EXPORT_RE = /export\s+default\s+(?:(?:async\s+)?(?:function|class)\s+)?(\w+)?/g;
|
|
5
|
-
const IMPORT_RE = /import\s+(?:(?:type\s+)?(?:\{[^}]*\}|\*\s+as\s+\w+|\w+)(?:\s*,\s*(?:\{[^}]*\}|\*\s+as\s+\w+|\w+))*\s+from\s+)?['"]([^'"]+)['"]/g;
|
|
6
|
-
|
|
7
|
-
export function extractExports(source: string): ExportInfo[] {
|
|
8
|
-
const exports: ExportInfo[] = [];
|
|
9
|
-
let match: RegExpExecArray | null;
|
|
10
|
-
|
|
11
|
-
const namedRe = new RegExp(NAMED_EXPORT_RE.source, 'g');
|
|
12
|
-
while ((match = namedRe.exec(source)) !== null) {
|
|
13
|
-
const line = source.substring(
|
|
14
|
-
source.lastIndexOf('\n', match.index) + 1,
|
|
15
|
-
source.indexOf('\n', match.index),
|
|
16
|
-
);
|
|
17
|
-
exports.push({
|
|
18
|
-
name: match[1]!,
|
|
19
|
-
type: detectExportType(line),
|
|
20
|
-
});
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const defaultRe = new RegExp(DEFAULT_EXPORT_RE.source, 'g');
|
|
24
|
-
while ((match = defaultRe.exec(source)) !== null) {
|
|
25
|
-
exports.push({
|
|
26
|
-
name: match[1] ?? 'default',
|
|
27
|
-
type: 'function',
|
|
28
|
-
});
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
return exports;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export function extractImports(source: string): { external: string[]; internal: string[] } {
|
|
35
|
-
const external: string[] = [];
|
|
36
|
-
const internal: string[] = [];
|
|
37
|
-
let match: RegExpExecArray | null;
|
|
38
|
-
|
|
39
|
-
const importRe = new RegExp(IMPORT_RE.source, 'g');
|
|
40
|
-
while ((match = importRe.exec(source)) !== null) {
|
|
41
|
-
const specifier = match[1]!;
|
|
42
|
-
if (specifier.startsWith('.') || specifier.startsWith('/')) {
|
|
43
|
-
internal.push(specifier);
|
|
44
|
-
} else {
|
|
45
|
-
external.push(specifier);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
return { external, internal };
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export function hasTypeAnnotations(source: string): boolean {
|
|
53
|
-
return /:\s*\w+[\[\]<>|&]*\s*[=;,)\n{]/.test(source) ||
|
|
54
|
-
/interface\s+\w+/.test(source) ||
|
|
55
|
-
/type\s+\w+\s*=/.test(source);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function detectExportType(line: string): ExportInfo['type'] {
|
|
59
|
-
if (/\bfunction\b/.test(line)) return 'function';
|
|
60
|
-
if (/\bclass\b/.test(line)) return 'class';
|
|
61
|
-
if (/\binterface\b/.test(line)) return 'interface';
|
|
62
|
-
if (/\btype\b/.test(line)) return 'type';
|
|
63
|
-
if (/\bconst\b/.test(line)) return 'constant';
|
|
64
|
-
return 'variable';
|
|
65
|
-
}
|