@timmeck/brain 1.8.0 → 1.8.2

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.
Files changed (177) hide show
  1. package/BRAIN_PLAN.md +3324 -3324
  2. package/LICENSE +21 -21
  3. package/dist/api/server.d.ts +4 -0
  4. package/dist/api/server.js +73 -0
  5. package/dist/api/server.js.map +1 -1
  6. package/dist/brain.js +2 -1
  7. package/dist/brain.js.map +1 -1
  8. package/dist/cli/commands/dashboard.js +606 -572
  9. package/dist/cli/commands/dashboard.js.map +1 -1
  10. package/dist/dashboard/server.js +25 -25
  11. package/dist/db/migrations/001_core_schema.js +115 -115
  12. package/dist/db/migrations/002_learning_schema.js +33 -33
  13. package/dist/db/migrations/003_code_schema.js +48 -48
  14. package/dist/db/migrations/004_synapses_schema.js +52 -52
  15. package/dist/db/migrations/005_fts_indexes.js +73 -73
  16. package/dist/db/migrations/007_feedback.js +8 -8
  17. package/dist/db/migrations/008_git_integration.js +33 -33
  18. package/dist/db/migrations/009_embeddings.js +3 -3
  19. package/dist/db/repositories/antipattern.repository.js +3 -3
  20. package/dist/db/repositories/code-module.repository.js +32 -32
  21. package/dist/db/repositories/notification.repository.js +3 -3
  22. package/dist/db/repositories/project.repository.js +21 -21
  23. package/dist/db/repositories/rule.repository.js +24 -24
  24. package/dist/db/repositories/solution.repository.js +50 -50
  25. package/dist/db/repositories/synapse.repository.js +18 -18
  26. package/dist/db/repositories/terminal.repository.js +24 -24
  27. package/dist/embeddings/engine.d.ts +2 -2
  28. package/dist/embeddings/engine.js +17 -4
  29. package/dist/embeddings/engine.js.map +1 -1
  30. package/dist/index.js +1 -1
  31. package/dist/ipc/server.d.ts +8 -0
  32. package/dist/ipc/server.js +67 -1
  33. package/dist/ipc/server.js.map +1 -1
  34. package/dist/matching/error-matcher.js +5 -5
  35. package/dist/matching/fingerprint.js +6 -1
  36. package/dist/matching/fingerprint.js.map +1 -1
  37. package/dist/mcp/http-server.js +8 -2
  38. package/dist/mcp/http-server.js.map +1 -1
  39. package/dist/services/code.service.d.ts +3 -0
  40. package/dist/services/code.service.js +33 -4
  41. package/dist/services/code.service.js.map +1 -1
  42. package/dist/services/error.service.js +4 -3
  43. package/dist/services/error.service.js.map +1 -1
  44. package/dist/services/git.service.js +14 -14
  45. package/package.json +49 -49
  46. package/src/api/server.ts +395 -321
  47. package/src/brain.ts +266 -265
  48. package/src/cli/colors.ts +116 -116
  49. package/src/cli/commands/config.ts +169 -169
  50. package/src/cli/commands/dashboard.ts +755 -720
  51. package/src/cli/commands/doctor.ts +118 -118
  52. package/src/cli/commands/explain.ts +83 -83
  53. package/src/cli/commands/export.ts +31 -31
  54. package/src/cli/commands/import.ts +199 -199
  55. package/src/cli/commands/insights.ts +65 -65
  56. package/src/cli/commands/learn.ts +24 -24
  57. package/src/cli/commands/modules.ts +53 -53
  58. package/src/cli/commands/network.ts +67 -67
  59. package/src/cli/commands/projects.ts +42 -42
  60. package/src/cli/commands/query.ts +120 -120
  61. package/src/cli/commands/start.ts +62 -62
  62. package/src/cli/commands/status.ts +75 -75
  63. package/src/cli/commands/stop.ts +34 -34
  64. package/src/cli/ipc-helper.ts +22 -22
  65. package/src/cli/update-check.ts +63 -63
  66. package/src/code/fingerprint.ts +87 -87
  67. package/src/code/parsers/generic.ts +29 -29
  68. package/src/code/parsers/python.ts +54 -54
  69. package/src/code/parsers/typescript.ts +65 -65
  70. package/src/code/registry.ts +60 -60
  71. package/src/dashboard/server.ts +142 -142
  72. package/src/db/connection.ts +22 -22
  73. package/src/db/migrations/001_core_schema.ts +120 -120
  74. package/src/db/migrations/002_learning_schema.ts +38 -38
  75. package/src/db/migrations/003_code_schema.ts +53 -53
  76. package/src/db/migrations/004_synapses_schema.ts +57 -57
  77. package/src/db/migrations/005_fts_indexes.ts +78 -78
  78. package/src/db/migrations/006_synapses_phase3.ts +17 -17
  79. package/src/db/migrations/007_feedback.ts +13 -13
  80. package/src/db/migrations/008_git_integration.ts +38 -38
  81. package/src/db/migrations/009_embeddings.ts +8 -8
  82. package/src/db/repositories/antipattern.repository.ts +66 -66
  83. package/src/db/repositories/code-module.repository.ts +142 -142
  84. package/src/db/repositories/notification.repository.ts +66 -66
  85. package/src/db/repositories/project.repository.ts +93 -93
  86. package/src/db/repositories/rule.repository.ts +108 -108
  87. package/src/db/repositories/solution.repository.ts +154 -154
  88. package/src/db/repositories/synapse.repository.ts +153 -153
  89. package/src/db/repositories/terminal.repository.ts +101 -101
  90. package/src/embeddings/engine.ts +238 -217
  91. package/src/index.ts +63 -63
  92. package/src/ipc/client.ts +118 -118
  93. package/src/ipc/protocol.ts +35 -35
  94. package/src/ipc/router.ts +133 -133
  95. package/src/ipc/server.ts +176 -110
  96. package/src/learning/decay.ts +46 -46
  97. package/src/learning/pattern-extractor.ts +90 -90
  98. package/src/learning/rule-generator.ts +74 -74
  99. package/src/matching/error-matcher.ts +5 -5
  100. package/src/matching/fingerprint.ts +34 -29
  101. package/src/matching/similarity.ts +61 -61
  102. package/src/matching/tfidf.ts +74 -74
  103. package/src/matching/tokenizer.ts +41 -41
  104. package/src/mcp/auto-detect.ts +93 -93
  105. package/src/mcp/http-server.ts +140 -137
  106. package/src/mcp/server.ts +73 -73
  107. package/src/parsing/error-parser.ts +28 -28
  108. package/src/parsing/parsers/compiler.ts +93 -93
  109. package/src/parsing/parsers/generic.ts +28 -28
  110. package/src/parsing/parsers/go.ts +97 -97
  111. package/src/parsing/parsers/node.ts +69 -69
  112. package/src/parsing/parsers/python.ts +62 -62
  113. package/src/parsing/parsers/rust.ts +50 -50
  114. package/src/parsing/parsers/shell.ts +42 -42
  115. package/src/parsing/types.ts +47 -47
  116. package/src/research/gap-analyzer.ts +135 -135
  117. package/src/research/insight-generator.ts +123 -123
  118. package/src/research/research-engine.ts +116 -116
  119. package/src/research/synergy-detector.ts +126 -126
  120. package/src/research/template-extractor.ts +130 -130
  121. package/src/research/trend-analyzer.ts +127 -127
  122. package/src/services/code.service.ts +271 -238
  123. package/src/services/error.service.ts +4 -3
  124. package/src/services/git.service.ts +132 -132
  125. package/src/services/notification.service.ts +41 -41
  126. package/src/services/synapse.service.ts +59 -59
  127. package/src/services/terminal.service.ts +81 -81
  128. package/src/synapses/activation.ts +80 -80
  129. package/src/synapses/decay.ts +38 -38
  130. package/src/synapses/hebbian.ts +69 -69
  131. package/src/synapses/pathfinder.ts +81 -81
  132. package/src/synapses/synapse-manager.ts +109 -109
  133. package/src/types/code.types.ts +52 -52
  134. package/src/types/error.types.ts +67 -67
  135. package/src/types/ipc.types.ts +8 -8
  136. package/src/types/mcp.types.ts +53 -53
  137. package/src/types/research.types.ts +28 -28
  138. package/src/types/solution.types.ts +30 -30
  139. package/src/utils/events.ts +45 -45
  140. package/src/utils/hash.ts +5 -5
  141. package/src/utils/logger.ts +48 -48
  142. package/src/utils/paths.ts +19 -19
  143. package/tests/e2e/test_code_intelligence.py +1015 -0
  144. package/tests/e2e/test_error_memory.py +451 -0
  145. package/tests/e2e/test_full_integration.py +534 -0
  146. package/tests/fixtures/code-modules/modules.ts +83 -83
  147. package/tests/fixtures/errors/go.ts +9 -9
  148. package/tests/fixtures/errors/node.ts +24 -24
  149. package/tests/fixtures/errors/python.ts +21 -21
  150. package/tests/fixtures/errors/rust.ts +25 -25
  151. package/tests/fixtures/errors/shell.ts +15 -15
  152. package/tests/fixtures/solutions/solutions.ts +27 -27
  153. package/tests/helpers/setup-db.ts +52 -52
  154. package/tests/integration/code-flow.test.ts +86 -86
  155. package/tests/integration/error-flow.test.ts +83 -83
  156. package/tests/integration/ipc-flow.test.ts +166 -166
  157. package/tests/integration/learning-cycle.test.ts +82 -82
  158. package/tests/integration/synapse-flow.test.ts +117 -117
  159. package/tests/unit/code/analyzer.test.ts +58 -58
  160. package/tests/unit/code/fingerprint.test.ts +51 -51
  161. package/tests/unit/code/scorer.test.ts +55 -55
  162. package/tests/unit/learning/confidence-scorer.test.ts +60 -60
  163. package/tests/unit/learning/decay.test.ts +45 -45
  164. package/tests/unit/learning/pattern-extractor.test.ts +50 -50
  165. package/tests/unit/matching/error-matcher.test.ts +69 -69
  166. package/tests/unit/matching/fingerprint.test.ts +47 -47
  167. package/tests/unit/matching/similarity.test.ts +65 -65
  168. package/tests/unit/matching/tfidf.test.ts +71 -71
  169. package/tests/unit/matching/tokenizer.test.ts +83 -83
  170. package/tests/unit/parsing/parsers.test.ts +113 -113
  171. package/tests/unit/research/gap-analyzer.test.ts +45 -45
  172. package/tests/unit/research/trend-analyzer.test.ts +45 -45
  173. package/tests/unit/synapses/activation.test.ts +80 -80
  174. package/tests/unit/synapses/decay.test.ts +27 -27
  175. package/tests/unit/synapses/hebbian.test.ts +96 -96
  176. package/tests/unit/synapses/pathfinder.test.ts +72 -72
  177. package/tsconfig.json +18 -18
@@ -1,87 +1,87 @@
1
- import { sha256 } from '../utils/hash.js';
2
-
3
- export function fingerprintCode(source: string, language: string): string {
4
- let normalized = stripComments(source, language);
5
- normalized = normalized.replace(/\s+/g, ' ').trim();
6
- normalized = normalizeIdentifiers(normalized, language);
7
- normalized = normalized.replace(/'[^']*'/g, "'<STR>'");
8
- normalized = normalized.replace(/"[^"]*"/g, '"<STR>"');
9
- normalized = normalized.replace(/`[^`]*`/g, '`<STR>`');
10
- normalized = normalized.replace(/\b\d+\b/g, '<NUM>');
11
- return sha256(normalized);
12
- }
13
-
14
- export function stripComments(source: string, language: string): string {
15
- switch (language) {
16
- case 'typescript':
17
- case 'javascript':
18
- case 'java':
19
- case 'go':
20
- case 'rust':
21
- case 'c':
22
- case 'cpp':
23
- return source
24
- .replace(/\/\/.*$/gm, '')
25
- .replace(/\/\*[\s\S]*?\*\//g, '');
26
- case 'python':
27
- return source
28
- .replace(/#.*$/gm, '')
29
- .replace(/"""[\s\S]*?"""/g, '')
30
- .replace(/'''[\s\S]*?'''/g, '');
31
- default:
32
- return source
33
- .replace(/\/\/.*$/gm, '')
34
- .replace(/#.*$/gm, '');
35
- }
36
- }
37
-
38
- function normalizeIdentifiers(source: string, language: string): string {
39
- const importNames = extractImportNames(source, language);
40
- const keywords = getLanguageKeywords(language);
41
- const preserve = new Set([...importNames, ...keywords]);
42
-
43
- return source.replace(/\b[a-zA-Z_]\w*\b/g, (match) => {
44
- if (preserve.has(match)) return match;
45
- if (match[0] === match[0]!.toUpperCase() && match[0] !== match[0]!.toLowerCase()) return '<CLASS>';
46
- return '<VAR>';
47
- });
48
- }
49
-
50
- function extractImportNames(source: string, language: string): string[] {
51
- const names: string[] = [];
52
-
53
- if (language === 'typescript' || language === 'javascript') {
54
- const re = /import\s+(?:\{([^}]+)\}|\*\s+as\s+(\w+)|(\w+))/g;
55
- let m: RegExpExecArray | null;
56
- while ((m = re.exec(source)) !== null) {
57
- if (m[1]) names.push(...m[1].split(',').map(s => s.trim().split(/\s+as\s+/).pop()!));
58
- if (m[2]) names.push(m[2]);
59
- if (m[3]) names.push(m[3]);
60
- }
61
- } else if (language === 'python') {
62
- const re = /(?:from\s+\S+\s+)?import\s+(.+)/g;
63
- let m: RegExpExecArray | null;
64
- while ((m = re.exec(source)) !== null) {
65
- names.push(...m[1]!.split(',').map(s => {
66
- const parts = s.trim().split(/\s+as\s+/);
67
- return parts[parts.length - 1]!;
68
- }));
69
- }
70
- }
71
-
72
- return names.filter(Boolean);
73
- }
74
-
75
- function getLanguageKeywords(language: string): string[] {
76
- const common = ['if', 'else', 'for', 'while', 'return', 'break', 'continue', 'switch', 'case', 'default', 'try', 'catch', 'throw', 'new', 'delete', 'true', 'false', 'null', 'undefined', 'void'];
77
-
78
- const langKeywords: Record<string, string[]> = {
79
- typescript: [...common, 'const', 'let', 'var', 'function', 'class', 'interface', 'type', 'enum', 'import', 'export', 'from', 'async', 'await', 'extends', 'implements', 'readonly', 'private', 'public', 'protected', 'static', 'abstract', 'as', 'is', 'in', 'of', 'typeof', 'keyof', 'infer', 'never', 'unknown', 'any', 'string', 'number', 'boolean', 'symbol', 'object'],
80
- javascript: [...common, 'const', 'let', 'var', 'function', 'class', 'import', 'export', 'from', 'async', 'await', 'extends', 'typeof', 'instanceof', 'in', 'of', 'this', 'super', 'yield'],
81
- python: ['def', 'class', 'import', 'from', 'if', 'elif', 'else', 'for', 'while', 'return', 'yield', 'break', 'continue', 'try', 'except', 'finally', 'raise', 'with', 'as', 'pass', 'lambda', 'True', 'False', 'None', 'and', 'or', 'not', 'in', 'is', 'global', 'nonlocal', 'assert', 'async', 'await', 'self'],
82
- rust: [...common, 'fn', 'let', 'mut', 'pub', 'struct', 'enum', 'impl', 'trait', 'use', 'mod', 'crate', 'self', 'super', 'match', 'loop', 'move', 'ref', 'where', 'async', 'await', 'dyn', 'Box', 'Vec', 'String', 'Option', 'Result', 'Some', 'None', 'Ok', 'Err'],
83
- go: [...common, 'func', 'package', 'import', 'type', 'struct', 'interface', 'map', 'chan', 'go', 'defer', 'select', 'range', 'var', 'const', 'nil', 'make', 'len', 'append', 'cap', 'copy', 'close'],
84
- };
85
-
86
- return langKeywords[language] ?? common;
87
- }
1
+ import { sha256 } from '../utils/hash.js';
2
+
3
+ export function fingerprintCode(source: string, language: string): string {
4
+ let normalized = stripComments(source, language);
5
+ normalized = normalized.replace(/\s+/g, ' ').trim();
6
+ normalized = normalizeIdentifiers(normalized, language);
7
+ normalized = normalized.replace(/'[^']*'/g, "'<STR>'");
8
+ normalized = normalized.replace(/"[^"]*"/g, '"<STR>"');
9
+ normalized = normalized.replace(/`[^`]*`/g, '`<STR>`');
10
+ normalized = normalized.replace(/\b\d+\b/g, '<NUM>');
11
+ return sha256(normalized);
12
+ }
13
+
14
+ export function stripComments(source: string, language: string): string {
15
+ switch (language) {
16
+ case 'typescript':
17
+ case 'javascript':
18
+ case 'java':
19
+ case 'go':
20
+ case 'rust':
21
+ case 'c':
22
+ case 'cpp':
23
+ return source
24
+ .replace(/\/\/.*$/gm, '')
25
+ .replace(/\/\*[\s\S]*?\*\//g, '');
26
+ case 'python':
27
+ return source
28
+ .replace(/#.*$/gm, '')
29
+ .replace(/"""[\s\S]*?"""/g, '')
30
+ .replace(/'''[\s\S]*?'''/g, '');
31
+ default:
32
+ return source
33
+ .replace(/\/\/.*$/gm, '')
34
+ .replace(/#.*$/gm, '');
35
+ }
36
+ }
37
+
38
+ function normalizeIdentifiers(source: string, language: string): string {
39
+ const importNames = extractImportNames(source, language);
40
+ const keywords = getLanguageKeywords(language);
41
+ const preserve = new Set([...importNames, ...keywords]);
42
+
43
+ return source.replace(/\b[a-zA-Z_]\w*\b/g, (match) => {
44
+ if (preserve.has(match)) return match;
45
+ if (match[0] === match[0]!.toUpperCase() && match[0] !== match[0]!.toLowerCase()) return '<CLASS>';
46
+ return '<VAR>';
47
+ });
48
+ }
49
+
50
+ function extractImportNames(source: string, language: string): string[] {
51
+ const names: string[] = [];
52
+
53
+ if (language === 'typescript' || language === 'javascript') {
54
+ const re = /import\s+(?:\{([^}]+)\}|\*\s+as\s+(\w+)|(\w+))/g;
55
+ let m: RegExpExecArray | null;
56
+ while ((m = re.exec(source)) !== null) {
57
+ if (m[1]) names.push(...m[1].split(',').map(s => s.trim().split(/\s+as\s+/).pop()!));
58
+ if (m[2]) names.push(m[2]);
59
+ if (m[3]) names.push(m[3]);
60
+ }
61
+ } else if (language === 'python') {
62
+ const re = /(?:from\s+\S+\s+)?import\s+(.+)/g;
63
+ let m: RegExpExecArray | null;
64
+ while ((m = re.exec(source)) !== null) {
65
+ names.push(...m[1]!.split(',').map(s => {
66
+ const parts = s.trim().split(/\s+as\s+/);
67
+ return parts[parts.length - 1]!;
68
+ }));
69
+ }
70
+ }
71
+
72
+ return names.filter(Boolean);
73
+ }
74
+
75
+ function getLanguageKeywords(language: string): string[] {
76
+ const common = ['if', 'else', 'for', 'while', 'return', 'break', 'continue', 'switch', 'case', 'default', 'try', 'catch', 'throw', 'new', 'delete', 'true', 'false', 'null', 'undefined', 'void'];
77
+
78
+ const langKeywords: Record<string, string[]> = {
79
+ typescript: [...common, 'const', 'let', 'var', 'function', 'class', 'interface', 'type', 'enum', 'import', 'export', 'from', 'async', 'await', 'extends', 'implements', 'readonly', 'private', 'public', 'protected', 'static', 'abstract', 'as', 'is', 'in', 'of', 'typeof', 'keyof', 'infer', 'never', 'unknown', 'any', 'string', 'number', 'boolean', 'symbol', 'object'],
80
+ javascript: [...common, 'const', 'let', 'var', 'function', 'class', 'import', 'export', 'from', 'async', 'await', 'extends', 'typeof', 'instanceof', 'in', 'of', 'this', 'super', 'yield'],
81
+ python: ['def', 'class', 'import', 'from', 'if', 'elif', 'else', 'for', 'while', 'return', 'yield', 'break', 'continue', 'try', 'except', 'finally', 'raise', 'with', 'as', 'pass', 'lambda', 'True', 'False', 'None', 'and', 'or', 'not', 'in', 'is', 'global', 'nonlocal', 'assert', 'async', 'await', 'self'],
82
+ rust: [...common, 'fn', 'let', 'mut', 'pub', 'struct', 'enum', 'impl', 'trait', 'use', 'mod', 'crate', 'self', 'super', 'match', 'loop', 'move', 'ref', 'where', 'async', 'await', 'dyn', 'Box', 'Vec', 'String', 'Option', 'Result', 'Some', 'None', 'Ok', 'Err'],
83
+ go: [...common, 'func', 'package', 'import', 'type', 'struct', 'interface', 'map', 'chan', 'go', 'defer', 'select', 'range', 'var', 'const', 'nil', 'make', 'len', 'append', 'cap', 'copy', 'close'],
84
+ };
85
+
86
+ return langKeywords[language] ?? common;
87
+ }
@@ -1,29 +1,29 @@
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
+ 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 +1,54 @@
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
+ 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 +1,65 @@
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
- }
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
+ }
@@ -1,60 +1,60 @@
1
- import type { ExportInfo } from '../types/code.types.js';
2
- import { analyzeCode, measureCohesion } from './analyzer.js';
3
-
4
- export type ModuleType = 'file' | 'class' | 'function';
5
-
6
- export function detectGranularity(
7
- source: string,
8
- language: string,
9
- ): ModuleType {
10
- const { exports } = analyzeCode(source, language);
11
-
12
- const hasDefault = exports.some(e => e.name === 'default');
13
- if (hasDefault && exports.length <= 3) return 'file';
14
-
15
- const classes = exports.filter(e => e.type === 'class');
16
- if (classes.length === 1 && exports.length <= 3) return 'class';
17
-
18
- if (exports.length > 3) {
19
- const cohesion = measureCohesion(exports);
20
- if (cohesion < 0.5) return 'function';
21
- }
22
-
23
- return 'file';
24
- }
25
-
26
- export interface ModuleRegistration {
27
- name: string;
28
- filePath: string;
29
- language: string;
30
- source: string;
31
- description?: string;
32
- projectId: number;
33
- }
34
-
35
- export interface RegisteredModule {
36
- name: string;
37
- moduleType: ModuleType;
38
- exports: ExportInfo[];
39
- externalDeps: string[];
40
- internalDeps: string[];
41
- isPure: boolean;
42
- hasTypeAnnotations: boolean;
43
- linesOfCode: number;
44
- }
45
-
46
- export function prepareModule(reg: ModuleRegistration): RegisteredModule {
47
- const analysis = analyzeCode(reg.source, reg.language);
48
- const moduleType = detectGranularity(reg.source, reg.language);
49
-
50
- return {
51
- name: reg.name,
52
- moduleType,
53
- exports: analysis.exports,
54
- externalDeps: analysis.externalDeps,
55
- internalDeps: analysis.internalDeps,
56
- isPure: analysis.isPure,
57
- hasTypeAnnotations: analysis.hasTypeAnnotations,
58
- linesOfCode: analysis.linesOfCode,
59
- };
60
- }
1
+ import type { ExportInfo } from '../types/code.types.js';
2
+ import { analyzeCode, measureCohesion } from './analyzer.js';
3
+
4
+ export type ModuleType = 'file' | 'class' | 'function';
5
+
6
+ export function detectGranularity(
7
+ source: string,
8
+ language: string,
9
+ ): ModuleType {
10
+ const { exports } = analyzeCode(source, language);
11
+
12
+ const hasDefault = exports.some(e => e.name === 'default');
13
+ if (hasDefault && exports.length <= 3) return 'file';
14
+
15
+ const classes = exports.filter(e => e.type === 'class');
16
+ if (classes.length === 1 && exports.length <= 3) return 'class';
17
+
18
+ if (exports.length > 3) {
19
+ const cohesion = measureCohesion(exports);
20
+ if (cohesion < 0.5) return 'function';
21
+ }
22
+
23
+ return 'file';
24
+ }
25
+
26
+ export interface ModuleRegistration {
27
+ name: string;
28
+ filePath: string;
29
+ language: string;
30
+ source: string;
31
+ description?: string;
32
+ projectId: number;
33
+ }
34
+
35
+ export interface RegisteredModule {
36
+ name: string;
37
+ moduleType: ModuleType;
38
+ exports: ExportInfo[];
39
+ externalDeps: string[];
40
+ internalDeps: string[];
41
+ isPure: boolean;
42
+ hasTypeAnnotations: boolean;
43
+ linesOfCode: number;
44
+ }
45
+
46
+ export function prepareModule(reg: ModuleRegistration): RegisteredModule {
47
+ const analysis = analyzeCode(reg.source, reg.language);
48
+ const moduleType = detectGranularity(reg.source, reg.language);
49
+
50
+ return {
51
+ name: reg.name,
52
+ moduleType,
53
+ exports: analysis.exports,
54
+ externalDeps: analysis.externalDeps,
55
+ internalDeps: analysis.internalDeps,
56
+ isPure: analysis.isPure,
57
+ hasTypeAnnotations: analysis.hasTypeAnnotations,
58
+ linesOfCode: analysis.linesOfCode,
59
+ };
60
+ }