@timmeck/brain 1.8.1 → 1.8.3

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 (164) 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/cli/commands/doctor.js +6 -1
  5. package/dist/cli/commands/doctor.js.map +1 -1
  6. package/dist/dashboard/server.js +25 -25
  7. package/dist/db/migrations/001_core_schema.js +115 -115
  8. package/dist/db/migrations/002_learning_schema.js +33 -33
  9. package/dist/db/migrations/003_code_schema.js +48 -48
  10. package/dist/db/migrations/004_synapses_schema.js +52 -52
  11. package/dist/db/migrations/005_fts_indexes.js +73 -73
  12. package/dist/db/migrations/007_feedback.js +8 -8
  13. package/dist/db/migrations/008_git_integration.js +33 -33
  14. package/dist/db/migrations/009_embeddings.js +3 -3
  15. package/dist/db/repositories/antipattern.repository.js +3 -3
  16. package/dist/db/repositories/code-module.repository.js +32 -32
  17. package/dist/db/repositories/notification.repository.js +3 -3
  18. package/dist/db/repositories/project.repository.js +21 -21
  19. package/dist/db/repositories/rule.repository.js +24 -24
  20. package/dist/db/repositories/solution.repository.js +50 -50
  21. package/dist/db/repositories/synapse.repository.js +18 -18
  22. package/dist/db/repositories/terminal.repository.js +24 -24
  23. package/dist/ipc/server.d.ts +8 -0
  24. package/dist/ipc/server.js +67 -1
  25. package/dist/ipc/server.js.map +1 -1
  26. package/dist/matching/error-matcher.js +5 -5
  27. package/dist/matching/fingerprint.js +6 -1
  28. package/dist/matching/fingerprint.js.map +1 -1
  29. package/dist/services/error.service.js +4 -3
  30. package/dist/services/error.service.js.map +1 -1
  31. package/dist/services/git.service.js +14 -14
  32. package/package.json +49 -49
  33. package/src/api/server.ts +395 -395
  34. package/src/brain.ts +266 -266
  35. package/src/cli/colors.ts +116 -116
  36. package/src/cli/commands/config.ts +169 -169
  37. package/src/cli/commands/dashboard.ts +755 -755
  38. package/src/cli/commands/doctor.ts +124 -118
  39. package/src/cli/commands/explain.ts +83 -83
  40. package/src/cli/commands/export.ts +31 -31
  41. package/src/cli/commands/import.ts +199 -199
  42. package/src/cli/commands/insights.ts +65 -65
  43. package/src/cli/commands/learn.ts +24 -24
  44. package/src/cli/commands/modules.ts +53 -53
  45. package/src/cli/commands/network.ts +67 -67
  46. package/src/cli/commands/projects.ts +42 -42
  47. package/src/cli/commands/query.ts +120 -120
  48. package/src/cli/commands/start.ts +62 -62
  49. package/src/cli/commands/status.ts +75 -75
  50. package/src/cli/commands/stop.ts +34 -34
  51. package/src/cli/ipc-helper.ts +22 -22
  52. package/src/cli/update-check.ts +63 -63
  53. package/src/code/fingerprint.ts +87 -87
  54. package/src/code/parsers/generic.ts +29 -29
  55. package/src/code/parsers/python.ts +54 -54
  56. package/src/code/parsers/typescript.ts +65 -65
  57. package/src/code/registry.ts +60 -60
  58. package/src/dashboard/server.ts +142 -142
  59. package/src/db/connection.ts +22 -22
  60. package/src/db/migrations/001_core_schema.ts +120 -120
  61. package/src/db/migrations/002_learning_schema.ts +38 -38
  62. package/src/db/migrations/003_code_schema.ts +53 -53
  63. package/src/db/migrations/004_synapses_schema.ts +57 -57
  64. package/src/db/migrations/005_fts_indexes.ts +78 -78
  65. package/src/db/migrations/006_synapses_phase3.ts +17 -17
  66. package/src/db/migrations/007_feedback.ts +13 -13
  67. package/src/db/migrations/008_git_integration.ts +38 -38
  68. package/src/db/migrations/009_embeddings.ts +8 -8
  69. package/src/db/repositories/antipattern.repository.ts +66 -66
  70. package/src/db/repositories/code-module.repository.ts +142 -142
  71. package/src/db/repositories/notification.repository.ts +66 -66
  72. package/src/db/repositories/project.repository.ts +93 -93
  73. package/src/db/repositories/rule.repository.ts +108 -108
  74. package/src/db/repositories/solution.repository.ts +154 -154
  75. package/src/db/repositories/synapse.repository.ts +153 -153
  76. package/src/db/repositories/terminal.repository.ts +101 -101
  77. package/src/embeddings/engine.ts +238 -238
  78. package/src/index.ts +63 -63
  79. package/src/ipc/client.ts +118 -118
  80. package/src/ipc/protocol.ts +35 -35
  81. package/src/ipc/router.ts +133 -133
  82. package/src/ipc/server.ts +176 -110
  83. package/src/learning/decay.ts +46 -46
  84. package/src/learning/pattern-extractor.ts +90 -90
  85. package/src/learning/rule-generator.ts +74 -74
  86. package/src/matching/error-matcher.ts +5 -5
  87. package/src/matching/fingerprint.ts +34 -29
  88. package/src/matching/similarity.ts +61 -61
  89. package/src/matching/tfidf.ts +74 -74
  90. package/src/matching/tokenizer.ts +41 -41
  91. package/src/mcp/auto-detect.ts +93 -93
  92. package/src/mcp/http-server.ts +140 -140
  93. package/src/mcp/server.ts +73 -73
  94. package/src/parsing/error-parser.ts +28 -28
  95. package/src/parsing/parsers/compiler.ts +93 -93
  96. package/src/parsing/parsers/generic.ts +28 -28
  97. package/src/parsing/parsers/go.ts +97 -97
  98. package/src/parsing/parsers/node.ts +69 -69
  99. package/src/parsing/parsers/python.ts +62 -62
  100. package/src/parsing/parsers/rust.ts +50 -50
  101. package/src/parsing/parsers/shell.ts +42 -42
  102. package/src/parsing/types.ts +47 -47
  103. package/src/research/gap-analyzer.ts +135 -135
  104. package/src/research/insight-generator.ts +123 -123
  105. package/src/research/research-engine.ts +116 -116
  106. package/src/research/synergy-detector.ts +126 -126
  107. package/src/research/template-extractor.ts +130 -130
  108. package/src/research/trend-analyzer.ts +127 -127
  109. package/src/services/code.service.ts +271 -271
  110. package/src/services/error.service.ts +4 -3
  111. package/src/services/git.service.ts +132 -132
  112. package/src/services/notification.service.ts +41 -41
  113. package/src/services/synapse.service.ts +59 -59
  114. package/src/services/terminal.service.ts +81 -81
  115. package/src/synapses/activation.ts +80 -80
  116. package/src/synapses/decay.ts +38 -38
  117. package/src/synapses/hebbian.ts +69 -69
  118. package/src/synapses/pathfinder.ts +81 -81
  119. package/src/synapses/synapse-manager.ts +109 -109
  120. package/src/types/code.types.ts +52 -52
  121. package/src/types/error.types.ts +67 -67
  122. package/src/types/ipc.types.ts +8 -8
  123. package/src/types/mcp.types.ts +53 -53
  124. package/src/types/research.types.ts +28 -28
  125. package/src/types/solution.types.ts +30 -30
  126. package/src/utils/events.ts +45 -45
  127. package/src/utils/hash.ts +5 -5
  128. package/src/utils/logger.ts +48 -48
  129. package/src/utils/paths.ts +19 -19
  130. package/tests/e2e/test_code_intelligence.py +1015 -0
  131. package/tests/e2e/test_error_memory.py +451 -0
  132. package/tests/e2e/test_full_integration.py +534 -0
  133. package/tests/fixtures/code-modules/modules.ts +83 -83
  134. package/tests/fixtures/errors/go.ts +9 -9
  135. package/tests/fixtures/errors/node.ts +24 -24
  136. package/tests/fixtures/errors/python.ts +21 -21
  137. package/tests/fixtures/errors/rust.ts +25 -25
  138. package/tests/fixtures/errors/shell.ts +15 -15
  139. package/tests/fixtures/solutions/solutions.ts +27 -27
  140. package/tests/helpers/setup-db.ts +52 -52
  141. package/tests/integration/code-flow.test.ts +86 -86
  142. package/tests/integration/error-flow.test.ts +83 -83
  143. package/tests/integration/ipc-flow.test.ts +166 -166
  144. package/tests/integration/learning-cycle.test.ts +82 -82
  145. package/tests/integration/synapse-flow.test.ts +117 -117
  146. package/tests/unit/code/analyzer.test.ts +58 -58
  147. package/tests/unit/code/fingerprint.test.ts +51 -51
  148. package/tests/unit/code/scorer.test.ts +55 -55
  149. package/tests/unit/learning/confidence-scorer.test.ts +60 -60
  150. package/tests/unit/learning/decay.test.ts +45 -45
  151. package/tests/unit/learning/pattern-extractor.test.ts +50 -50
  152. package/tests/unit/matching/error-matcher.test.ts +69 -69
  153. package/tests/unit/matching/fingerprint.test.ts +47 -47
  154. package/tests/unit/matching/similarity.test.ts +65 -65
  155. package/tests/unit/matching/tfidf.test.ts +71 -71
  156. package/tests/unit/matching/tokenizer.test.ts +83 -83
  157. package/tests/unit/parsing/parsers.test.ts +113 -113
  158. package/tests/unit/research/gap-analyzer.test.ts +45 -45
  159. package/tests/unit/research/trend-analyzer.test.ts +45 -45
  160. package/tests/unit/synapses/activation.test.ts +80 -80
  161. package/tests/unit/synapses/decay.test.ts +27 -27
  162. package/tests/unit/synapses/hebbian.test.ts +96 -96
  163. package/tests/unit/synapses/pathfinder.test.ts +72 -72
  164. 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
+ }