@timmeck/brain 1.8.1 → 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 (162) hide show
  1. package/BRAIN_PLAN.md +3324 -3324
  2. package/LICENSE +21 -21
  3. package/dist/cli/commands/dashboard.js +595 -595
  4. package/dist/dashboard/server.js +25 -25
  5. package/dist/db/migrations/001_core_schema.js +115 -115
  6. package/dist/db/migrations/002_learning_schema.js +33 -33
  7. package/dist/db/migrations/003_code_schema.js +48 -48
  8. package/dist/db/migrations/004_synapses_schema.js +52 -52
  9. package/dist/db/migrations/005_fts_indexes.js +73 -73
  10. package/dist/db/migrations/007_feedback.js +8 -8
  11. package/dist/db/migrations/008_git_integration.js +33 -33
  12. package/dist/db/migrations/009_embeddings.js +3 -3
  13. package/dist/db/repositories/antipattern.repository.js +3 -3
  14. package/dist/db/repositories/code-module.repository.js +32 -32
  15. package/dist/db/repositories/notification.repository.js +3 -3
  16. package/dist/db/repositories/project.repository.js +21 -21
  17. package/dist/db/repositories/rule.repository.js +24 -24
  18. package/dist/db/repositories/solution.repository.js +50 -50
  19. package/dist/db/repositories/synapse.repository.js +18 -18
  20. package/dist/db/repositories/terminal.repository.js +24 -24
  21. package/dist/ipc/server.d.ts +8 -0
  22. package/dist/ipc/server.js +67 -1
  23. package/dist/ipc/server.js.map +1 -1
  24. package/dist/matching/error-matcher.js +5 -5
  25. package/dist/matching/fingerprint.js +6 -1
  26. package/dist/matching/fingerprint.js.map +1 -1
  27. package/dist/services/error.service.js +4 -3
  28. package/dist/services/error.service.js.map +1 -1
  29. package/dist/services/git.service.js +14 -14
  30. package/package.json +49 -49
  31. package/src/api/server.ts +395 -395
  32. package/src/brain.ts +266 -266
  33. package/src/cli/colors.ts +116 -116
  34. package/src/cli/commands/config.ts +169 -169
  35. package/src/cli/commands/dashboard.ts +755 -755
  36. package/src/cli/commands/doctor.ts +118 -118
  37. package/src/cli/commands/explain.ts +83 -83
  38. package/src/cli/commands/export.ts +31 -31
  39. package/src/cli/commands/import.ts +199 -199
  40. package/src/cli/commands/insights.ts +65 -65
  41. package/src/cli/commands/learn.ts +24 -24
  42. package/src/cli/commands/modules.ts +53 -53
  43. package/src/cli/commands/network.ts +67 -67
  44. package/src/cli/commands/projects.ts +42 -42
  45. package/src/cli/commands/query.ts +120 -120
  46. package/src/cli/commands/start.ts +62 -62
  47. package/src/cli/commands/status.ts +75 -75
  48. package/src/cli/commands/stop.ts +34 -34
  49. package/src/cli/ipc-helper.ts +22 -22
  50. package/src/cli/update-check.ts +63 -63
  51. package/src/code/fingerprint.ts +87 -87
  52. package/src/code/parsers/generic.ts +29 -29
  53. package/src/code/parsers/python.ts +54 -54
  54. package/src/code/parsers/typescript.ts +65 -65
  55. package/src/code/registry.ts +60 -60
  56. package/src/dashboard/server.ts +142 -142
  57. package/src/db/connection.ts +22 -22
  58. package/src/db/migrations/001_core_schema.ts +120 -120
  59. package/src/db/migrations/002_learning_schema.ts +38 -38
  60. package/src/db/migrations/003_code_schema.ts +53 -53
  61. package/src/db/migrations/004_synapses_schema.ts +57 -57
  62. package/src/db/migrations/005_fts_indexes.ts +78 -78
  63. package/src/db/migrations/006_synapses_phase3.ts +17 -17
  64. package/src/db/migrations/007_feedback.ts +13 -13
  65. package/src/db/migrations/008_git_integration.ts +38 -38
  66. package/src/db/migrations/009_embeddings.ts +8 -8
  67. package/src/db/repositories/antipattern.repository.ts +66 -66
  68. package/src/db/repositories/code-module.repository.ts +142 -142
  69. package/src/db/repositories/notification.repository.ts +66 -66
  70. package/src/db/repositories/project.repository.ts +93 -93
  71. package/src/db/repositories/rule.repository.ts +108 -108
  72. package/src/db/repositories/solution.repository.ts +154 -154
  73. package/src/db/repositories/synapse.repository.ts +153 -153
  74. package/src/db/repositories/terminal.repository.ts +101 -101
  75. package/src/embeddings/engine.ts +238 -238
  76. package/src/index.ts +63 -63
  77. package/src/ipc/client.ts +118 -118
  78. package/src/ipc/protocol.ts +35 -35
  79. package/src/ipc/router.ts +133 -133
  80. package/src/ipc/server.ts +176 -110
  81. package/src/learning/decay.ts +46 -46
  82. package/src/learning/pattern-extractor.ts +90 -90
  83. package/src/learning/rule-generator.ts +74 -74
  84. package/src/matching/error-matcher.ts +5 -5
  85. package/src/matching/fingerprint.ts +34 -29
  86. package/src/matching/similarity.ts +61 -61
  87. package/src/matching/tfidf.ts +74 -74
  88. package/src/matching/tokenizer.ts +41 -41
  89. package/src/mcp/auto-detect.ts +93 -93
  90. package/src/mcp/http-server.ts +140 -140
  91. package/src/mcp/server.ts +73 -73
  92. package/src/parsing/error-parser.ts +28 -28
  93. package/src/parsing/parsers/compiler.ts +93 -93
  94. package/src/parsing/parsers/generic.ts +28 -28
  95. package/src/parsing/parsers/go.ts +97 -97
  96. package/src/parsing/parsers/node.ts +69 -69
  97. package/src/parsing/parsers/python.ts +62 -62
  98. package/src/parsing/parsers/rust.ts +50 -50
  99. package/src/parsing/parsers/shell.ts +42 -42
  100. package/src/parsing/types.ts +47 -47
  101. package/src/research/gap-analyzer.ts +135 -135
  102. package/src/research/insight-generator.ts +123 -123
  103. package/src/research/research-engine.ts +116 -116
  104. package/src/research/synergy-detector.ts +126 -126
  105. package/src/research/template-extractor.ts +130 -130
  106. package/src/research/trend-analyzer.ts +127 -127
  107. package/src/services/code.service.ts +271 -271
  108. package/src/services/error.service.ts +4 -3
  109. package/src/services/git.service.ts +132 -132
  110. package/src/services/notification.service.ts +41 -41
  111. package/src/services/synapse.service.ts +59 -59
  112. package/src/services/terminal.service.ts +81 -81
  113. package/src/synapses/activation.ts +80 -80
  114. package/src/synapses/decay.ts +38 -38
  115. package/src/synapses/hebbian.ts +69 -69
  116. package/src/synapses/pathfinder.ts +81 -81
  117. package/src/synapses/synapse-manager.ts +109 -109
  118. package/src/types/code.types.ts +52 -52
  119. package/src/types/error.types.ts +67 -67
  120. package/src/types/ipc.types.ts +8 -8
  121. package/src/types/mcp.types.ts +53 -53
  122. package/src/types/research.types.ts +28 -28
  123. package/src/types/solution.types.ts +30 -30
  124. package/src/utils/events.ts +45 -45
  125. package/src/utils/hash.ts +5 -5
  126. package/src/utils/logger.ts +48 -48
  127. package/src/utils/paths.ts +19 -19
  128. package/tests/e2e/test_code_intelligence.py +1015 -0
  129. package/tests/e2e/test_error_memory.py +451 -0
  130. package/tests/e2e/test_full_integration.py +534 -0
  131. package/tests/fixtures/code-modules/modules.ts +83 -83
  132. package/tests/fixtures/errors/go.ts +9 -9
  133. package/tests/fixtures/errors/node.ts +24 -24
  134. package/tests/fixtures/errors/python.ts +21 -21
  135. package/tests/fixtures/errors/rust.ts +25 -25
  136. package/tests/fixtures/errors/shell.ts +15 -15
  137. package/tests/fixtures/solutions/solutions.ts +27 -27
  138. package/tests/helpers/setup-db.ts +52 -52
  139. package/tests/integration/code-flow.test.ts +86 -86
  140. package/tests/integration/error-flow.test.ts +83 -83
  141. package/tests/integration/ipc-flow.test.ts +166 -166
  142. package/tests/integration/learning-cycle.test.ts +82 -82
  143. package/tests/integration/synapse-flow.test.ts +117 -117
  144. package/tests/unit/code/analyzer.test.ts +58 -58
  145. package/tests/unit/code/fingerprint.test.ts +51 -51
  146. package/tests/unit/code/scorer.test.ts +55 -55
  147. package/tests/unit/learning/confidence-scorer.test.ts +60 -60
  148. package/tests/unit/learning/decay.test.ts +45 -45
  149. package/tests/unit/learning/pattern-extractor.test.ts +50 -50
  150. package/tests/unit/matching/error-matcher.test.ts +69 -69
  151. package/tests/unit/matching/fingerprint.test.ts +47 -47
  152. package/tests/unit/matching/similarity.test.ts +65 -65
  153. package/tests/unit/matching/tfidf.test.ts +71 -71
  154. package/tests/unit/matching/tokenizer.test.ts +83 -83
  155. package/tests/unit/parsing/parsers.test.ts +113 -113
  156. package/tests/unit/research/gap-analyzer.test.ts +45 -45
  157. package/tests/unit/research/trend-analyzer.test.ts +45 -45
  158. package/tests/unit/synapses/activation.test.ts +80 -80
  159. package/tests/unit/synapses/decay.test.ts +27 -27
  160. package/tests/unit/synapses/hebbian.test.ts +96 -96
  161. package/tests/unit/synapses/pathfinder.test.ts +72 -72
  162. package/tsconfig.json +18 -18
@@ -1,53 +1,53 @@
1
- import { Command } from 'commander';
2
- import { withIpc } from '../ipc-helper.js';
3
- import { c, icons, header, divider } from '../colors.js';
4
-
5
- export function modulesCommand(): Command {
6
- return new Command('modules')
7
- .description('List registered code modules')
8
- .option('--language <lang>', 'Filter by language')
9
- .option('-l, --limit <n>', 'Maximum results')
10
- .action(async (opts) => {
11
- await withIpc(async (client) => {
12
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
13
- const modules: any = await client.request('code.modules', {
14
- language: opts.language,
15
- limit: opts.limit ? parseInt(opts.limit, 10) : undefined,
16
- });
17
-
18
- if (!modules?.length) {
19
- console.log(`${icons.module} ${c.dim('No code modules registered.')}`);
20
- return;
21
- }
22
-
23
- console.log(header(`${modules.length} Code Modules`, icons.module));
24
-
25
- // Group by language
26
- const byLang: Record<string, number> = {};
27
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
28
- for (const mod of modules as any[]) {
29
- byLang[mod.language] = (byLang[mod.language] || 0) + 1;
30
- }
31
- const langSummary = Object.entries(byLang)
32
- .sort((a, b) => b[1] - a[1])
33
- .map(([lang, count]) => `${c.cyan(lang)} ${c.dim(`(${count})`)}`)
34
- .join(' ');
35
- console.log(` ${langSummary}\n`);
36
-
37
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
38
- for (const mod of modules as any[]) {
39
- const score = mod.reusabilityScore ?? mod.reusability_score ?? 0;
40
- const scoreColor = score >= 0.7 ? c.green : score >= 0.4 ? c.orange : c.red;
41
- const langTag = c.cyan(`[${mod.language}]`);
42
-
43
- console.log(` ${c.dim(`#${mod.id}`)} ${langTag} ${c.value(mod.name)}`);
44
- if (mod.description) {
45
- console.log(` ${c.dim(mod.description.slice(0, 120))}`);
46
- }
47
- console.log(` ${c.label('File:')} ${c.dim(mod.filePath ?? mod.file_path)} ${c.label('Score:')} ${scoreColor(typeof score === 'number' ? score.toFixed(2) : score)}`);
48
- console.log();
49
- }
50
- console.log(divider());
51
- });
52
- });
53
- }
1
+ import { Command } from 'commander';
2
+ import { withIpc } from '../ipc-helper.js';
3
+ import { c, icons, header, divider } from '../colors.js';
4
+
5
+ export function modulesCommand(): Command {
6
+ return new Command('modules')
7
+ .description('List registered code modules')
8
+ .option('--language <lang>', 'Filter by language')
9
+ .option('-l, --limit <n>', 'Maximum results')
10
+ .action(async (opts) => {
11
+ await withIpc(async (client) => {
12
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
13
+ const modules: any = await client.request('code.modules', {
14
+ language: opts.language,
15
+ limit: opts.limit ? parseInt(opts.limit, 10) : undefined,
16
+ });
17
+
18
+ if (!modules?.length) {
19
+ console.log(`${icons.module} ${c.dim('No code modules registered.')}`);
20
+ return;
21
+ }
22
+
23
+ console.log(header(`${modules.length} Code Modules`, icons.module));
24
+
25
+ // Group by language
26
+ const byLang: Record<string, number> = {};
27
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
28
+ for (const mod of modules as any[]) {
29
+ byLang[mod.language] = (byLang[mod.language] || 0) + 1;
30
+ }
31
+ const langSummary = Object.entries(byLang)
32
+ .sort((a, b) => b[1] - a[1])
33
+ .map(([lang, count]) => `${c.cyan(lang)} ${c.dim(`(${count})`)}`)
34
+ .join(' ');
35
+ console.log(` ${langSummary}\n`);
36
+
37
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
38
+ for (const mod of modules as any[]) {
39
+ const score = mod.reusabilityScore ?? mod.reusability_score ?? 0;
40
+ const scoreColor = score >= 0.7 ? c.green : score >= 0.4 ? c.orange : c.red;
41
+ const langTag = c.cyan(`[${mod.language}]`);
42
+
43
+ console.log(` ${c.dim(`#${mod.id}`)} ${langTag} ${c.value(mod.name)}`);
44
+ if (mod.description) {
45
+ console.log(` ${c.dim(mod.description.slice(0, 120))}`);
46
+ }
47
+ console.log(` ${c.label('File:')} ${c.dim(mod.filePath ?? mod.file_path)} ${c.label('Score:')} ${scoreColor(typeof score === 'number' ? score.toFixed(2) : score)}`);
48
+ console.log();
49
+ }
50
+ console.log(divider());
51
+ });
52
+ });
53
+ }
@@ -1,67 +1,67 @@
1
- import { Command } from 'commander';
2
- import { withIpc } from '../ipc-helper.js';
3
- import { c, icons, header, keyValue, divider } from '../colors.js';
4
-
5
- export function networkCommand(): Command {
6
- return new Command('network')
7
- .description('Explore the synapse network')
8
- .option('--node <type:id>', 'Node to explore (e.g., error:42)')
9
- .option('-l, --limit <n>', 'Max synapses to show', '20')
10
- .action(async (opts) => {
11
- await withIpc(async (client) => {
12
- if (opts.node) {
13
- const [nodeType, nodeIdStr] = opts.node.split(':');
14
- const nodeId = parseInt(nodeIdStr, 10);
15
-
16
- if (!nodeType || isNaN(nodeId)) {
17
- console.error(c.error('Invalid node format. Use: --node error:42'));
18
- return;
19
- }
20
-
21
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
22
- const related: any = await client.request('synapse.related', {
23
- nodeType,
24
- nodeId,
25
- maxDepth: 2,
26
- });
27
-
28
- if (!related?.length) {
29
- console.log(`${c.dim('No connections found for')} ${c.cyan(`${nodeType}:${nodeId}`)}`);
30
- return;
31
- }
32
-
33
- console.log(header(`Connections from ${nodeType}:${nodeId}`, icons.synapse));
34
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
35
- for (const r of related as any[]) {
36
- const weight = (r.activation ?? r.weight ?? 0);
37
- const weightColor = weight >= 0.7 ? c.green : weight >= 0.3 ? c.orange : c.dim;
38
- console.log(` ${c.cyan(icons.arrow)} ${c.value(`${r.nodeType}:${r.nodeId}`)} ${c.label('weight:')} ${weightColor(weight.toFixed(3))}`);
39
- }
40
- } else {
41
- // Show general network stats
42
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
43
- const stats: any = await client.request('synapse.stats', {});
44
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
45
- const overview: any = await client.request('analytics.network', {
46
- limit: parseInt(opts.limit, 10),
47
- });
48
-
49
- console.log(header('Synapse Network', icons.synapse));
50
- console.log(keyValue('Total synapses', stats.totalSynapses ?? 0));
51
- console.log(keyValue('Average weight', (stats.avgWeight ?? 0).toFixed(3)));
52
- console.log();
53
-
54
- if (overview?.strongestSynapses?.length) {
55
- console.log(` ${c.purple.bold('Strongest connections:')}`);
56
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
57
- for (const s of overview.strongestSynapses as any[]) {
58
- const weight = (s.weight ?? 0);
59
- const weightColor = weight >= 0.7 ? c.green : weight >= 0.3 ? c.orange : c.dim;
60
- console.log(` ${c.dim(s.source)} ${c.cyan(icons.arrow)} ${c.dim(s.target)} ${c.label(`[${s.type}]`)} ${weightColor(weight.toFixed(3))}`);
61
- }
62
- }
63
- }
64
- console.log(`\n${divider()}`);
65
- });
66
- });
67
- }
1
+ import { Command } from 'commander';
2
+ import { withIpc } from '../ipc-helper.js';
3
+ import { c, icons, header, keyValue, divider } from '../colors.js';
4
+
5
+ export function networkCommand(): Command {
6
+ return new Command('network')
7
+ .description('Explore the synapse network')
8
+ .option('--node <type:id>', 'Node to explore (e.g., error:42)')
9
+ .option('-l, --limit <n>', 'Max synapses to show', '20')
10
+ .action(async (opts) => {
11
+ await withIpc(async (client) => {
12
+ if (opts.node) {
13
+ const [nodeType, nodeIdStr] = opts.node.split(':');
14
+ const nodeId = parseInt(nodeIdStr, 10);
15
+
16
+ if (!nodeType || isNaN(nodeId)) {
17
+ console.error(c.error('Invalid node format. Use: --node error:42'));
18
+ return;
19
+ }
20
+
21
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
22
+ const related: any = await client.request('synapse.related', {
23
+ nodeType,
24
+ nodeId,
25
+ maxDepth: 2,
26
+ });
27
+
28
+ if (!related?.length) {
29
+ console.log(`${c.dim('No connections found for')} ${c.cyan(`${nodeType}:${nodeId}`)}`);
30
+ return;
31
+ }
32
+
33
+ console.log(header(`Connections from ${nodeType}:${nodeId}`, icons.synapse));
34
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
35
+ for (const r of related as any[]) {
36
+ const weight = (r.activation ?? r.weight ?? 0);
37
+ const weightColor = weight >= 0.7 ? c.green : weight >= 0.3 ? c.orange : c.dim;
38
+ console.log(` ${c.cyan(icons.arrow)} ${c.value(`${r.nodeType}:${r.nodeId}`)} ${c.label('weight:')} ${weightColor(weight.toFixed(3))}`);
39
+ }
40
+ } else {
41
+ // Show general network stats
42
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
43
+ const stats: any = await client.request('synapse.stats', {});
44
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
45
+ const overview: any = await client.request('analytics.network', {
46
+ limit: parseInt(opts.limit, 10),
47
+ });
48
+
49
+ console.log(header('Synapse Network', icons.synapse));
50
+ console.log(keyValue('Total synapses', stats.totalSynapses ?? 0));
51
+ console.log(keyValue('Average weight', (stats.avgWeight ?? 0).toFixed(3)));
52
+ console.log();
53
+
54
+ if (overview?.strongestSynapses?.length) {
55
+ console.log(` ${c.purple.bold('Strongest connections:')}`);
56
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
57
+ for (const s of overview.strongestSynapses as any[]) {
58
+ const weight = (s.weight ?? 0);
59
+ const weightColor = weight >= 0.7 ? c.green : weight >= 0.3 ? c.orange : c.dim;
60
+ console.log(` ${c.dim(s.source)} ${c.cyan(icons.arrow)} ${c.dim(s.target)} ${c.label(`[${s.type}]`)} ${weightColor(weight.toFixed(3))}`);
61
+ }
62
+ }
63
+ }
64
+ console.log(`\n${divider()}`);
65
+ });
66
+ });
67
+ }
@@ -1,42 +1,42 @@
1
- import { Command } from 'commander';
2
- import { withIpc } from '../ipc-helper.js';
3
- import { c, icons, header, divider, table } from '../colors.js';
4
-
5
- export function projectsCommand(): Command {
6
- return new Command('projects')
7
- .description('List all imported projects with stats')
8
- .action(async () => {
9
- await withIpc(async (client) => {
10
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
11
- const projects = await client.request('project.list', {}) as any[];
12
-
13
- console.log(header('Projects', icons.module));
14
-
15
- if (projects.length === 0) {
16
- console.log(`\n ${c.dim('No projects imported yet.')} Use ${c.cyan('brain import <dir>')} to get started.`);
17
- console.log(`\n${divider()}`);
18
- return;
19
- }
20
-
21
- console.log();
22
-
23
- const rows: string[][] = [
24
- [c.dim(' #'), c.dim('Name'), c.dim('Language'), c.dim('Modules'), c.dim('Path')],
25
- ];
26
-
27
- for (const p of projects) {
28
- rows.push([
29
- c.dimmer(` ${p.id}`),
30
- c.value(p.name),
31
- p.language ? c.cyan(p.language) : c.dim('—'),
32
- c.green(String(p.moduleCount)),
33
- p.path ? c.dim(p.path) : c.dim('—'),
34
- ]);
35
- }
36
-
37
- console.log(table(rows, [5, 24, 14, 9, 40]));
38
- console.log(`\n ${c.label('Total:')} ${c.value(String(projects.length))} projects, ${c.green(String(projects.reduce((s: number, p: any) => s + p.moduleCount, 0)))} modules`);
39
- console.log(`\n${divider()}`);
40
- });
41
- });
42
- }
1
+ import { Command } from 'commander';
2
+ import { withIpc } from '../ipc-helper.js';
3
+ import { c, icons, header, divider, table } from '../colors.js';
4
+
5
+ export function projectsCommand(): Command {
6
+ return new Command('projects')
7
+ .description('List all imported projects with stats')
8
+ .action(async () => {
9
+ await withIpc(async (client) => {
10
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
11
+ const projects = await client.request('project.list', {}) as any[];
12
+
13
+ console.log(header('Projects', icons.module));
14
+
15
+ if (projects.length === 0) {
16
+ console.log(`\n ${c.dim('No projects imported yet.')} Use ${c.cyan('brain import <dir>')} to get started.`);
17
+ console.log(`\n${divider()}`);
18
+ return;
19
+ }
20
+
21
+ console.log();
22
+
23
+ const rows: string[][] = [
24
+ [c.dim(' #'), c.dim('Name'), c.dim('Language'), c.dim('Modules'), c.dim('Path')],
25
+ ];
26
+
27
+ for (const p of projects) {
28
+ rows.push([
29
+ c.dimmer(` ${p.id}`),
30
+ c.value(p.name),
31
+ p.language ? c.cyan(p.language) : c.dim('—'),
32
+ c.green(String(p.moduleCount)),
33
+ p.path ? c.dim(p.path) : c.dim('—'),
34
+ ]);
35
+ }
36
+
37
+ console.log(table(rows, [5, 24, 14, 9, 40]));
38
+ console.log(`\n ${c.label('Total:')} ${c.value(String(projects.length))} projects, ${c.green(String(projects.reduce((s: number, p: any) => s + p.moduleCount, 0)))} modules`);
39
+ console.log(`\n${divider()}`);
40
+ });
41
+ });
42
+ }
@@ -1,120 +1,120 @@
1
- import { Command } from 'commander';
2
- import { withIpc } from '../ipc-helper.js';
3
- import { c, icons, header, statusBadge, divider } from '../colors.js';
4
-
5
- export function queryCommand(): Command {
6
- return new Command('query')
7
- .description('Search errors, code modules, and insights')
8
- .argument('<search>', 'Search term')
9
- .option('-l, --limit <n>', 'Maximum results per category', '10')
10
- .option('--errors-only', 'Only search errors')
11
- .option('--modules-only', 'Only search code modules')
12
- .option('--insights-only', 'Only search insights')
13
- .option('--page <n>', 'Page number (starting from 1)', '1')
14
- .action(async (search: string, opts) => {
15
- await withIpc(async (client) => {
16
- const limit = parseInt(opts.limit, 10);
17
- const page = parseInt(opts.page, 10) || 1;
18
- const offset = (page - 1) * limit;
19
- const searchAll = !opts.errorsOnly && !opts.modulesOnly && !opts.insightsOnly;
20
- let totalResults = 0;
21
-
22
- // --- Errors ---
23
- if (searchAll || opts.errorsOnly) {
24
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
25
- const results: any = await client.request('error.query', {
26
- search,
27
- limit: limit + offset,
28
- });
29
-
30
- const errors = Array.isArray(results) ? results.slice(offset, offset + limit) : [];
31
- if (errors.length > 0) {
32
- totalResults += errors.length;
33
- console.log(header(`Errors matching "${search}"`, icons.error));
34
-
35
- for (const err of errors) {
36
- const badge = statusBadge(err.resolved ? 'resolved' : 'open');
37
- const typeTag = c.purple(err.errorType ?? 'unknown');
38
- console.log(` ${c.dim(`#${err.id}`)} ${badge} ${typeTag}`);
39
- console.log(` ${c.dim((err.message ?? '').slice(0, 120))}`);
40
-
41
- // Get solutions for this error
42
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
43
- const solutions: any = await client.request('solution.query', { error_id: err.id });
44
- if (solutions?.length > 0) {
45
- console.log(` ${c.green(`${icons.check} ${solutions.length} solution(s)`)}`);
46
- for (const sol of solutions.slice(0, 3)) {
47
- console.log(` ${c.dim(icons.corner)} ${c.dim((sol.description ?? '').slice(0, 100))}`);
48
- }
49
- }
50
- console.log();
51
- }
52
- }
53
- }
54
-
55
- // --- Code Modules ---
56
- if (searchAll || opts.modulesOnly) {
57
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
58
- const modules: any = await client.request('code.find', {
59
- query: search,
60
- limit: limit + offset,
61
- });
62
-
63
- const mods = Array.isArray(modules) ? modules.slice(offset, offset + limit) : [];
64
- if (mods.length > 0) {
65
- totalResults += mods.length;
66
- console.log(header(`Modules matching "${search}"`, icons.module));
67
-
68
- for (const mod of mods) {
69
- const score = mod.reusability_score ?? mod.reusabilityScore ?? 0;
70
- const scoreColor = score >= 0.7 ? c.green : score >= 0.4 ? c.orange : c.red;
71
- console.log(` ${c.dim(`#${mod.id}`)} ${c.cyan(`[${mod.language}]`)} ${c.value(mod.name)}`);
72
- console.log(` ${c.label('File:')} ${c.dim(mod.file_path ?? mod.filePath)} ${c.label('Score:')} ${scoreColor(typeof score === 'number' ? score.toFixed(2) : score)}`);
73
- if (mod.description) {
74
- console.log(` ${c.dim(mod.description.slice(0, 120))}`);
75
- }
76
- console.log();
77
- }
78
- }
79
- }
80
-
81
- // --- Insights ---
82
- if (searchAll || opts.insightsOnly) {
83
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
84
- const insights: any = await client.request('research.insights', {
85
- activeOnly: true,
86
- limit: 100,
87
- });
88
-
89
- // Client-side search since insights API may not support text search
90
- const allInsights = Array.isArray(insights) ? insights : [];
91
- const searchLower = search.toLowerCase();
92
- const matched = allInsights.filter((i: { title?: string; description?: string }) =>
93
- (i.title ?? '').toLowerCase().includes(searchLower) ||
94
- (i.description ?? '').toLowerCase().includes(searchLower)
95
- ).slice(offset, offset + limit);
96
-
97
- if (matched.length > 0) {
98
- totalResults += matched.length;
99
- console.log(header(`Insights matching "${search}"`, icons.insight));
100
-
101
- for (const ins of matched) {
102
- const typeTag = c.cyan(`[${ins.type}]`);
103
- console.log(` ${typeTag} ${c.value(ins.title)}`);
104
- if (ins.description) {
105
- console.log(` ${c.dim(ins.description.slice(0, 150))}`);
106
- }
107
- console.log();
108
- }
109
- }
110
- }
111
-
112
- if (totalResults === 0) {
113
- console.log(`\n${icons.search} ${c.dim(`No results found for "${search}".`)}`);
114
- } else {
115
- console.log(` ${c.dim(`Page ${page} — showing ${totalResults} result(s). Use --page ${page + 1} for more.`)}`);
116
- console.log(divider());
117
- }
118
- });
119
- });
120
- }
1
+ import { Command } from 'commander';
2
+ import { withIpc } from '../ipc-helper.js';
3
+ import { c, icons, header, statusBadge, divider } from '../colors.js';
4
+
5
+ export function queryCommand(): Command {
6
+ return new Command('query')
7
+ .description('Search errors, code modules, and insights')
8
+ .argument('<search>', 'Search term')
9
+ .option('-l, --limit <n>', 'Maximum results per category', '10')
10
+ .option('--errors-only', 'Only search errors')
11
+ .option('--modules-only', 'Only search code modules')
12
+ .option('--insights-only', 'Only search insights')
13
+ .option('--page <n>', 'Page number (starting from 1)', '1')
14
+ .action(async (search: string, opts) => {
15
+ await withIpc(async (client) => {
16
+ const limit = parseInt(opts.limit, 10);
17
+ const page = parseInt(opts.page, 10) || 1;
18
+ const offset = (page - 1) * limit;
19
+ const searchAll = !opts.errorsOnly && !opts.modulesOnly && !opts.insightsOnly;
20
+ let totalResults = 0;
21
+
22
+ // --- Errors ---
23
+ if (searchAll || opts.errorsOnly) {
24
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
25
+ const results: any = await client.request('error.query', {
26
+ search,
27
+ limit: limit + offset,
28
+ });
29
+
30
+ const errors = Array.isArray(results) ? results.slice(offset, offset + limit) : [];
31
+ if (errors.length > 0) {
32
+ totalResults += errors.length;
33
+ console.log(header(`Errors matching "${search}"`, icons.error));
34
+
35
+ for (const err of errors) {
36
+ const badge = statusBadge(err.resolved ? 'resolved' : 'open');
37
+ const typeTag = c.purple(err.errorType ?? 'unknown');
38
+ console.log(` ${c.dim(`#${err.id}`)} ${badge} ${typeTag}`);
39
+ console.log(` ${c.dim((err.message ?? '').slice(0, 120))}`);
40
+
41
+ // Get solutions for this error
42
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
43
+ const solutions: any = await client.request('solution.query', { error_id: err.id });
44
+ if (solutions?.length > 0) {
45
+ console.log(` ${c.green(`${icons.check} ${solutions.length} solution(s)`)}`);
46
+ for (const sol of solutions.slice(0, 3)) {
47
+ console.log(` ${c.dim(icons.corner)} ${c.dim((sol.description ?? '').slice(0, 100))}`);
48
+ }
49
+ }
50
+ console.log();
51
+ }
52
+ }
53
+ }
54
+
55
+ // --- Code Modules ---
56
+ if (searchAll || opts.modulesOnly) {
57
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
58
+ const modules: any = await client.request('code.find', {
59
+ query: search,
60
+ limit: limit + offset,
61
+ });
62
+
63
+ const mods = Array.isArray(modules) ? modules.slice(offset, offset + limit) : [];
64
+ if (mods.length > 0) {
65
+ totalResults += mods.length;
66
+ console.log(header(`Modules matching "${search}"`, icons.module));
67
+
68
+ for (const mod of mods) {
69
+ const score = mod.reusability_score ?? mod.reusabilityScore ?? 0;
70
+ const scoreColor = score >= 0.7 ? c.green : score >= 0.4 ? c.orange : c.red;
71
+ console.log(` ${c.dim(`#${mod.id}`)} ${c.cyan(`[${mod.language}]`)} ${c.value(mod.name)}`);
72
+ console.log(` ${c.label('File:')} ${c.dim(mod.file_path ?? mod.filePath)} ${c.label('Score:')} ${scoreColor(typeof score === 'number' ? score.toFixed(2) : score)}`);
73
+ if (mod.description) {
74
+ console.log(` ${c.dim(mod.description.slice(0, 120))}`);
75
+ }
76
+ console.log();
77
+ }
78
+ }
79
+ }
80
+
81
+ // --- Insights ---
82
+ if (searchAll || opts.insightsOnly) {
83
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
84
+ const insights: any = await client.request('research.insights', {
85
+ activeOnly: true,
86
+ limit: 100,
87
+ });
88
+
89
+ // Client-side search since insights API may not support text search
90
+ const allInsights = Array.isArray(insights) ? insights : [];
91
+ const searchLower = search.toLowerCase();
92
+ const matched = allInsights.filter((i: { title?: string; description?: string }) =>
93
+ (i.title ?? '').toLowerCase().includes(searchLower) ||
94
+ (i.description ?? '').toLowerCase().includes(searchLower)
95
+ ).slice(offset, offset + limit);
96
+
97
+ if (matched.length > 0) {
98
+ totalResults += matched.length;
99
+ console.log(header(`Insights matching "${search}"`, icons.insight));
100
+
101
+ for (const ins of matched) {
102
+ const typeTag = c.cyan(`[${ins.type}]`);
103
+ console.log(` ${typeTag} ${c.value(ins.title)}`);
104
+ if (ins.description) {
105
+ console.log(` ${c.dim(ins.description.slice(0, 150))}`);
106
+ }
107
+ console.log();
108
+ }
109
+ }
110
+ }
111
+
112
+ if (totalResults === 0) {
113
+ console.log(`\n${icons.search} ${c.dim(`No results found for "${search}".`)}`);
114
+ } else {
115
+ console.log(` ${c.dim(`Page ${page} — showing ${totalResults} result(s). Use --page ${page + 1} for more.`)}`);
116
+ console.log(divider());
117
+ }
118
+ });
119
+ });
120
+ }