wiggum-cli 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (236) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +341 -0
  3. package/bin/ralph.js +8 -0
  4. package/dist/ai/enhancer.d.ts +100 -0
  5. package/dist/ai/enhancer.d.ts.map +1 -0
  6. package/dist/ai/enhancer.js +233 -0
  7. package/dist/ai/enhancer.js.map +1 -0
  8. package/dist/ai/index.d.ts +8 -0
  9. package/dist/ai/index.d.ts.map +1 -0
  10. package/dist/ai/index.js +11 -0
  11. package/dist/ai/index.js.map +1 -0
  12. package/dist/ai/prompts.d.ts +26 -0
  13. package/dist/ai/prompts.d.ts.map +1 -0
  14. package/dist/ai/prompts.js +201 -0
  15. package/dist/ai/prompts.js.map +1 -0
  16. package/dist/ai/providers.d.ts +35 -0
  17. package/dist/ai/providers.d.ts.map +1 -0
  18. package/dist/ai/providers.js +104 -0
  19. package/dist/ai/providers.js.map +1 -0
  20. package/dist/cli.d.ts +6 -0
  21. package/dist/cli.d.ts.map +1 -0
  22. package/dist/cli.js +196 -0
  23. package/dist/cli.js.map +1 -0
  24. package/dist/commands/init.d.ts +16 -0
  25. package/dist/commands/init.d.ts.map +1 -0
  26. package/dist/commands/init.js +124 -0
  27. package/dist/commands/init.js.map +1 -0
  28. package/dist/commands/monitor.d.ts +17 -0
  29. package/dist/commands/monitor.d.ts.map +1 -0
  30. package/dist/commands/monitor.js +342 -0
  31. package/dist/commands/monitor.js.map +1 -0
  32. package/dist/commands/new.d.ts +19 -0
  33. package/dist/commands/new.d.ts.map +1 -0
  34. package/dist/commands/new.js +272 -0
  35. package/dist/commands/new.js.map +1 -0
  36. package/dist/commands/run.d.ts +16 -0
  37. package/dist/commands/run.d.ts.map +1 -0
  38. package/dist/commands/run.js +175 -0
  39. package/dist/commands/run.js.map +1 -0
  40. package/dist/generator/config.d.ts +59 -0
  41. package/dist/generator/config.d.ts.map +1 -0
  42. package/dist/generator/config.js +68 -0
  43. package/dist/generator/config.js.map +1 -0
  44. package/dist/generator/index.d.ts +64 -0
  45. package/dist/generator/index.d.ts.map +1 -0
  46. package/dist/generator/index.js +147 -0
  47. package/dist/generator/index.js.map +1 -0
  48. package/dist/generator/templates.d.ts +70 -0
  49. package/dist/generator/templates.d.ts.map +1 -0
  50. package/dist/generator/templates.js +296 -0
  51. package/dist/generator/templates.js.map +1 -0
  52. package/dist/generator/writer.d.ts +93 -0
  53. package/dist/generator/writer.d.ts.map +1 -0
  54. package/dist/generator/writer.js +213 -0
  55. package/dist/generator/writer.js.map +1 -0
  56. package/dist/index.d.ts +12 -0
  57. package/dist/index.d.ts.map +1 -0
  58. package/dist/index.js +17 -0
  59. package/dist/index.js.map +1 -0
  60. package/dist/scanner/detectors/core/framework.d.ts +11 -0
  61. package/dist/scanner/detectors/core/framework.d.ts.map +1 -0
  62. package/dist/scanner/detectors/core/framework.js +275 -0
  63. package/dist/scanner/detectors/core/framework.js.map +1 -0
  64. package/dist/scanner/detectors/core/packageManager.d.ts +11 -0
  65. package/dist/scanner/detectors/core/packageManager.d.ts.map +1 -0
  66. package/dist/scanner/detectors/core/packageManager.js +74 -0
  67. package/dist/scanner/detectors/core/packageManager.js.map +1 -0
  68. package/dist/scanner/detectors/core/styling.d.ts +12 -0
  69. package/dist/scanner/detectors/core/styling.d.ts.map +1 -0
  70. package/dist/scanner/detectors/core/styling.js +230 -0
  71. package/dist/scanner/detectors/core/styling.js.map +1 -0
  72. package/dist/scanner/detectors/core/testing.d.ts +12 -0
  73. package/dist/scanner/detectors/core/testing.d.ts.map +1 -0
  74. package/dist/scanner/detectors/core/testing.js +190 -0
  75. package/dist/scanner/detectors/core/testing.js.map +1 -0
  76. package/dist/scanner/detectors/data/api.d.ts +12 -0
  77. package/dist/scanner/detectors/data/api.d.ts.map +1 -0
  78. package/dist/scanner/detectors/data/api.js +261 -0
  79. package/dist/scanner/detectors/data/api.js.map +1 -0
  80. package/dist/scanner/detectors/data/database.d.ts +12 -0
  81. package/dist/scanner/detectors/data/database.d.ts.map +1 -0
  82. package/dist/scanner/detectors/data/database.js +213 -0
  83. package/dist/scanner/detectors/data/database.js.map +1 -0
  84. package/dist/scanner/detectors/data/orm.d.ts +12 -0
  85. package/dist/scanner/detectors/data/orm.d.ts.map +1 -0
  86. package/dist/scanner/detectors/data/orm.js +160 -0
  87. package/dist/scanner/detectors/data/orm.js.map +1 -0
  88. package/dist/scanner/detectors/frontend/formHandling.d.ts +12 -0
  89. package/dist/scanner/detectors/frontend/formHandling.d.ts.map +1 -0
  90. package/dist/scanner/detectors/frontend/formHandling.js +211 -0
  91. package/dist/scanner/detectors/frontend/formHandling.js.map +1 -0
  92. package/dist/scanner/detectors/frontend/stateManagement.d.ts +12 -0
  93. package/dist/scanner/detectors/frontend/stateManagement.d.ts.map +1 -0
  94. package/dist/scanner/detectors/frontend/stateManagement.js +221 -0
  95. package/dist/scanner/detectors/frontend/stateManagement.js.map +1 -0
  96. package/dist/scanner/detectors/frontend/uiComponents.d.ts +12 -0
  97. package/dist/scanner/detectors/frontend/uiComponents.d.ts.map +1 -0
  98. package/dist/scanner/detectors/frontend/uiComponents.js +285 -0
  99. package/dist/scanner/detectors/frontend/uiComponents.js.map +1 -0
  100. package/dist/scanner/detectors/infra/deployment.d.ts +12 -0
  101. package/dist/scanner/detectors/infra/deployment.d.ts.map +1 -0
  102. package/dist/scanner/detectors/infra/deployment.js +301 -0
  103. package/dist/scanner/detectors/infra/deployment.js.map +1 -0
  104. package/dist/scanner/detectors/infra/monorepo.d.ts +12 -0
  105. package/dist/scanner/detectors/infra/monorepo.d.ts.map +1 -0
  106. package/dist/scanner/detectors/infra/monorepo.js +219 -0
  107. package/dist/scanner/detectors/infra/monorepo.js.map +1 -0
  108. package/dist/scanner/detectors/mcp/mcpProject.d.ts +12 -0
  109. package/dist/scanner/detectors/mcp/mcpProject.d.ts.map +1 -0
  110. package/dist/scanner/detectors/mcp/mcpProject.js +154 -0
  111. package/dist/scanner/detectors/mcp/mcpProject.js.map +1 -0
  112. package/dist/scanner/detectors/mcp/mcpServers.d.ts +17 -0
  113. package/dist/scanner/detectors/mcp/mcpServers.d.ts.map +1 -0
  114. package/dist/scanner/detectors/mcp/mcpServers.js +193 -0
  115. package/dist/scanner/detectors/mcp/mcpServers.js.map +1 -0
  116. package/dist/scanner/detectors/services/analytics.d.ts +12 -0
  117. package/dist/scanner/detectors/services/analytics.d.ts.map +1 -0
  118. package/dist/scanner/detectors/services/analytics.js +236 -0
  119. package/dist/scanner/detectors/services/analytics.js.map +1 -0
  120. package/dist/scanner/detectors/services/auth.d.ts +12 -0
  121. package/dist/scanner/detectors/services/auth.d.ts.map +1 -0
  122. package/dist/scanner/detectors/services/auth.js +217 -0
  123. package/dist/scanner/detectors/services/auth.js.map +1 -0
  124. package/dist/scanner/detectors/services/email.d.ts +12 -0
  125. package/dist/scanner/detectors/services/email.d.ts.map +1 -0
  126. package/dist/scanner/detectors/services/email.js +211 -0
  127. package/dist/scanner/detectors/services/email.js.map +1 -0
  128. package/dist/scanner/detectors/services/payments.d.ts +12 -0
  129. package/dist/scanner/detectors/services/payments.d.ts.map +1 -0
  130. package/dist/scanner/detectors/services/payments.js +185 -0
  131. package/dist/scanner/detectors/services/payments.js.map +1 -0
  132. package/dist/scanner/detectors/utils.d.ts +160 -0
  133. package/dist/scanner/detectors/utils.d.ts.map +1 -0
  134. package/dist/scanner/detectors/utils.js +222 -0
  135. package/dist/scanner/detectors/utils.js.map +1 -0
  136. package/dist/scanner/index.d.ts +42 -0
  137. package/dist/scanner/index.d.ts.map +1 -0
  138. package/dist/scanner/index.js +282 -0
  139. package/dist/scanner/index.js.map +1 -0
  140. package/dist/scanner/registry.d.ts +43 -0
  141. package/dist/scanner/registry.d.ts.map +1 -0
  142. package/dist/scanner/registry.js +243 -0
  143. package/dist/scanner/registry.js.map +1 -0
  144. package/dist/scanner/types.d.ts +112 -0
  145. package/dist/scanner/types.d.ts.map +1 -0
  146. package/dist/scanner/types.js +6 -0
  147. package/dist/scanner/types.js.map +1 -0
  148. package/dist/templates/config/ralph.config.js.tmpl +38 -0
  149. package/dist/templates/guides/AGENTS.md.tmpl +100 -0
  150. package/dist/templates/guides/FRONTEND.md.tmpl +523 -0
  151. package/dist/templates/guides/PERFORMANCE.md.tmpl +264 -0
  152. package/dist/templates/guides/SECURITY.md.tmpl +100 -0
  153. package/dist/templates/prompts/PROMPT.md.tmpl +77 -0
  154. package/dist/templates/prompts/PROMPT_e2e.md.tmpl +234 -0
  155. package/dist/templates/prompts/PROMPT_feature.md.tmpl +83 -0
  156. package/dist/templates/prompts/PROMPT_review.md.tmpl +167 -0
  157. package/dist/templates/prompts/PROMPT_verify.md.tmpl +72 -0
  158. package/dist/templates/root/.gitignore.tmpl +5 -0
  159. package/dist/templates/root/LEARNINGS.md.tmpl +24 -0
  160. package/dist/templates/root/README.md.tmpl +61 -0
  161. package/dist/templates/scripts/feature-loop.sh.tmpl +267 -0
  162. package/dist/templates/scripts/loop.sh.tmpl +59 -0
  163. package/dist/templates/scripts/ralph-monitor.sh.tmpl +244 -0
  164. package/dist/templates/specs/README.md.tmpl +57 -0
  165. package/dist/templates/specs/_example.md.tmpl +71 -0
  166. package/dist/utils/config.d.ts +95 -0
  167. package/dist/utils/config.d.ts.map +1 -0
  168. package/dist/utils/config.js +148 -0
  169. package/dist/utils/config.js.map +1 -0
  170. package/dist/utils/header.d.ts +5 -0
  171. package/dist/utils/header.d.ts.map +1 -0
  172. package/dist/utils/header.js +15 -0
  173. package/dist/utils/header.js.map +1 -0
  174. package/dist/utils/logger.d.ts +11 -0
  175. package/dist/utils/logger.d.ts.map +1 -0
  176. package/dist/utils/logger.js +24 -0
  177. package/dist/utils/logger.js.map +1 -0
  178. package/package.json +44 -0
  179. package/src/ai/enhancer.ts +350 -0
  180. package/src/ai/index.ts +38 -0
  181. package/src/ai/prompts.ts +217 -0
  182. package/src/ai/providers.ts +136 -0
  183. package/src/cli.ts +255 -0
  184. package/src/commands/init.ts +149 -0
  185. package/src/commands/monitor.ts +412 -0
  186. package/src/commands/new.ts +312 -0
  187. package/src/commands/run.ts +214 -0
  188. package/src/generator/config.ts +116 -0
  189. package/src/generator/index.ts +227 -0
  190. package/src/generator/templates.ts +412 -0
  191. package/src/generator/writer.ts +293 -0
  192. package/src/index.ts +41 -0
  193. package/src/scanner/detectors/core/framework.ts +332 -0
  194. package/src/scanner/detectors/core/packageManager.ts +91 -0
  195. package/src/scanner/detectors/core/styling.ts +261 -0
  196. package/src/scanner/detectors/core/testing.ts +221 -0
  197. package/src/scanner/detectors/data/api.ts +303 -0
  198. package/src/scanner/detectors/data/database.ts +245 -0
  199. package/src/scanner/detectors/data/orm.ts +180 -0
  200. package/src/scanner/detectors/frontend/formHandling.ts +244 -0
  201. package/src/scanner/detectors/frontend/stateManagement.ts +261 -0
  202. package/src/scanner/detectors/frontend/uiComponents.ts +328 -0
  203. package/src/scanner/detectors/infra/deployment.ts +343 -0
  204. package/src/scanner/detectors/infra/monorepo.ts +251 -0
  205. package/src/scanner/detectors/mcp/mcpProject.ts +176 -0
  206. package/src/scanner/detectors/mcp/mcpServers.ts +237 -0
  207. package/src/scanner/detectors/services/analytics.ts +273 -0
  208. package/src/scanner/detectors/services/auth.ts +254 -0
  209. package/src/scanner/detectors/services/email.ts +244 -0
  210. package/src/scanner/detectors/services/payments.ts +213 -0
  211. package/src/scanner/detectors/utils.ts +251 -0
  212. package/src/scanner/index.ts +354 -0
  213. package/src/scanner/registry.ts +301 -0
  214. package/src/scanner/types.ts +152 -0
  215. package/src/templates/config/ralph.config.js.tmpl +38 -0
  216. package/src/templates/guides/AGENTS.md.tmpl +100 -0
  217. package/src/templates/guides/FRONTEND.md.tmpl +523 -0
  218. package/src/templates/guides/PERFORMANCE.md.tmpl +264 -0
  219. package/src/templates/guides/SECURITY.md.tmpl +100 -0
  220. package/src/templates/prompts/PROMPT.md.tmpl +77 -0
  221. package/src/templates/prompts/PROMPT_e2e.md.tmpl +234 -0
  222. package/src/templates/prompts/PROMPT_feature.md.tmpl +83 -0
  223. package/src/templates/prompts/PROMPT_review.md.tmpl +167 -0
  224. package/src/templates/prompts/PROMPT_verify.md.tmpl +72 -0
  225. package/src/templates/root/.gitignore.tmpl +5 -0
  226. package/src/templates/root/LEARNINGS.md.tmpl +24 -0
  227. package/src/templates/root/README.md.tmpl +61 -0
  228. package/src/templates/scripts/feature-loop.sh.tmpl +267 -0
  229. package/src/templates/scripts/loop.sh.tmpl +59 -0
  230. package/src/templates/scripts/ralph-monitor.sh.tmpl +244 -0
  231. package/src/templates/specs/README.md.tmpl +57 -0
  232. package/src/templates/specs/_example.md.tmpl +71 -0
  233. package/src/utils/config.ts +221 -0
  234. package/src/utils/header.ts +15 -0
  235. package/src/utils/logger.ts +28 -0
  236. package/tsconfig.json +19 -0
@@ -0,0 +1,354 @@
1
+ /**
2
+ * Scanner Orchestrator
3
+ * Main entry point for project scanning
4
+ */
5
+
6
+ import { existsSync } from 'node:fs';
7
+ import { resolve } from 'node:path';
8
+ import { createDefaultRegistry, DetectorRegistry } from './registry.js';
9
+ import type { ScanResult, ScannerOptions, DetectedStack, DetectionResult } from './types.js';
10
+
11
+ // Re-export types
12
+ export type {
13
+ DetectionResult,
14
+ DetectedStack,
15
+ MCPStack,
16
+ DetectorCategory,
17
+ Detector,
18
+ ScannerOptions,
19
+ ScanResult,
20
+ } from './types.js';
21
+
22
+ // Re-export registry
23
+ export { DetectorRegistry, createDefaultRegistry } from './registry.js';
24
+
25
+ /**
26
+ * Scanner class
27
+ * Orchestrates detection of project tech stack
28
+ */
29
+ export class Scanner {
30
+ private registry: DetectorRegistry;
31
+ private options: ScannerOptions;
32
+
33
+ constructor(options: ScannerOptions = {}) {
34
+ this.registry = createDefaultRegistry();
35
+ this.options = {
36
+ includeLowConfidence: false,
37
+ minConfidence: 40,
38
+ ...options,
39
+ };
40
+ }
41
+
42
+ /**
43
+ * Scan a project directory and detect its tech stack
44
+ */
45
+ async scan(projectRoot: string): Promise<ScanResult> {
46
+ const startTime = Date.now();
47
+ const errors: string[] = [];
48
+
49
+ // Resolve to absolute path
50
+ const absolutePath = resolve(projectRoot);
51
+
52
+ // Validate project root exists
53
+ if (!existsSync(absolutePath)) {
54
+ throw new Error(`Project root does not exist: ${absolutePath}`);
55
+ }
56
+
57
+ // Run all detectors
58
+ let stack: DetectedStack;
59
+ try {
60
+ stack = await this.registry.runAllDetectors(absolutePath);
61
+ } catch (error) {
62
+ errors.push(`Detection error: ${error instanceof Error ? error.message : String(error)}`);
63
+ stack = {};
64
+ }
65
+
66
+ // Filter by confidence threshold if configured
67
+ if (!this.options.includeLowConfidence && this.options.minConfidence) {
68
+ stack = this.filterByConfidence(stack, this.options.minConfidence);
69
+ }
70
+
71
+ const scanTime = Date.now() - startTime;
72
+
73
+ return {
74
+ projectRoot: absolutePath,
75
+ stack,
76
+ scanTime,
77
+ errors: errors.length > 0 ? errors : undefined,
78
+ };
79
+ }
80
+
81
+ /**
82
+ * Filter array of detection results by confidence
83
+ */
84
+ private filterArrayByConfidence(
85
+ results: DetectionResult[] | undefined,
86
+ minConfidence: number
87
+ ): DetectionResult[] | undefined {
88
+ if (!results) return undefined;
89
+ const filtered = results.filter(r => r.confidence >= minConfidence);
90
+ return filtered.length > 0 ? filtered : undefined;
91
+ }
92
+
93
+ /**
94
+ * Filter stack results by minimum confidence
95
+ */
96
+ private filterByConfidence(stack: DetectedStack, minConfidence: number): DetectedStack {
97
+ const filtered: DetectedStack = {};
98
+
99
+ // Core
100
+ if (stack.framework && stack.framework.confidence >= minConfidence) {
101
+ filtered.framework = stack.framework;
102
+ }
103
+
104
+ if (stack.packageManager && stack.packageManager.confidence >= minConfidence) {
105
+ filtered.packageManager = stack.packageManager;
106
+ }
107
+
108
+ if (stack.testing) {
109
+ const testing: DetectedStack['testing'] = {};
110
+ if (stack.testing.unit && stack.testing.unit.confidence >= minConfidence) {
111
+ testing.unit = stack.testing.unit;
112
+ }
113
+ if (stack.testing.e2e && stack.testing.e2e.confidence >= minConfidence) {
114
+ testing.e2e = stack.testing.e2e;
115
+ }
116
+ if (testing.unit || testing.e2e) {
117
+ filtered.testing = testing;
118
+ }
119
+ }
120
+
121
+ if (stack.styling && stack.styling.confidence >= minConfidence) {
122
+ filtered.styling = stack.styling;
123
+ }
124
+
125
+ // Data Layer
126
+ if (stack.database && stack.database.confidence >= minConfidence) {
127
+ filtered.database = stack.database;
128
+ }
129
+
130
+ if (stack.orm && stack.orm.confidence >= minConfidence) {
131
+ filtered.orm = stack.orm;
132
+ }
133
+
134
+ filtered.api = this.filterArrayByConfidence(stack.api, minConfidence);
135
+
136
+ // Frontend
137
+ if (stack.stateManagement && stack.stateManagement.confidence >= minConfidence) {
138
+ filtered.stateManagement = stack.stateManagement;
139
+ }
140
+
141
+ filtered.uiComponents = this.filterArrayByConfidence(stack.uiComponents, minConfidence);
142
+ filtered.formHandling = this.filterArrayByConfidence(stack.formHandling, minConfidence);
143
+
144
+ // Services
145
+ if (stack.auth && stack.auth.confidence >= minConfidence) {
146
+ filtered.auth = stack.auth;
147
+ }
148
+
149
+ filtered.analytics = this.filterArrayByConfidence(stack.analytics, minConfidence);
150
+
151
+ if (stack.payments && stack.payments.confidence >= minConfidence) {
152
+ filtered.payments = stack.payments;
153
+ }
154
+
155
+ if (stack.email && stack.email.confidence >= minConfidence) {
156
+ filtered.email = stack.email;
157
+ }
158
+
159
+ // Infrastructure
160
+ filtered.deployment = this.filterArrayByConfidence(stack.deployment, minConfidence);
161
+
162
+ if (stack.monorepo && stack.monorepo.confidence >= minConfidence) {
163
+ filtered.monorepo = stack.monorepo;
164
+ }
165
+
166
+ // MCP (keep as-is since it has its own structure)
167
+ if (stack.mcp) {
168
+ filtered.mcp = stack.mcp;
169
+ }
170
+
171
+ return filtered;
172
+ }
173
+
174
+ /**
175
+ * Get the detector registry for custom detector registration
176
+ */
177
+ getRegistry(): DetectorRegistry {
178
+ return this.registry;
179
+ }
180
+ }
181
+
182
+ /**
183
+ * Convenience function to scan a project
184
+ */
185
+ export async function scanProject(projectRoot: string, options?: ScannerOptions): Promise<ScanResult> {
186
+ const scanner = new Scanner(options);
187
+ return scanner.scan(projectRoot);
188
+ }
189
+
190
+ /**
191
+ * Helper to format a single detection result
192
+ */
193
+ function formatDetection(label: string, result: DetectionResult | undefined, indent = ''): string[] {
194
+ const lines: string[] = [];
195
+ if (result) {
196
+ const version = result.version ? `@${result.version}` : '';
197
+ const variant = result.variant ? ` (${result.variant})` : '';
198
+ lines.push(`${indent}${label}: ${result.name}${version}${variant} [${result.confidence}%]`);
199
+ lines.push(`${indent} Evidence: ${result.evidence.join(', ')}`);
200
+ }
201
+ return lines;
202
+ }
203
+
204
+ /**
205
+ * Helper to format an array of detection results
206
+ */
207
+ function formatDetectionArray(label: string, results: DetectionResult[] | undefined, indent = ''): string[] {
208
+ const lines: string[] = [];
209
+ if (results && results.length > 0) {
210
+ lines.push(`${indent}${label}:`);
211
+ for (const result of results) {
212
+ const version = result.version ? `@${result.version}` : '';
213
+ const variant = result.variant ? ` (${result.variant})` : '';
214
+ lines.push(`${indent} - ${result.name}${version}${variant} [${result.confidence}%]`);
215
+ }
216
+ }
217
+ return lines;
218
+ }
219
+
220
+ /**
221
+ * Format a scan result for display
222
+ */
223
+ export function formatScanResult(result: ScanResult): string {
224
+ const lines: string[] = [];
225
+ const { stack } = result;
226
+
227
+ lines.push(`Project: ${result.projectRoot}`);
228
+ lines.push(`Scan time: ${result.scanTime}ms`);
229
+ lines.push('');
230
+
231
+ // ============ Core ============
232
+ lines.push('=== Core ===');
233
+
234
+ if (stack.framework) {
235
+ lines.push(...formatDetection('Framework', stack.framework));
236
+ } else {
237
+ lines.push('Framework: Not detected');
238
+ }
239
+
240
+ if (stack.packageManager) {
241
+ lines.push(...formatDetection('Package Manager', stack.packageManager));
242
+ } else {
243
+ lines.push('Package Manager: Not detected');
244
+ }
245
+
246
+ if (stack.testing) {
247
+ if (stack.testing.unit) {
248
+ lines.push(...formatDetection('Unit Testing', stack.testing.unit));
249
+ }
250
+ if (stack.testing.e2e) {
251
+ lines.push(...formatDetection('E2E Testing', stack.testing.e2e));
252
+ }
253
+ }
254
+
255
+ if (stack.styling) {
256
+ lines.push(...formatDetection('Styling', stack.styling));
257
+ }
258
+
259
+ // ============ Data Layer ============
260
+ if (stack.database || stack.orm || stack.api) {
261
+ lines.push('');
262
+ lines.push('=== Data Layer ===');
263
+
264
+ if (stack.database) {
265
+ lines.push(...formatDetection('Database', stack.database));
266
+ }
267
+
268
+ if (stack.orm) {
269
+ lines.push(...formatDetection('ORM', stack.orm));
270
+ }
271
+
272
+ lines.push(...formatDetectionArray('API Patterns', stack.api));
273
+ }
274
+
275
+ // ============ Frontend ============
276
+ if (stack.stateManagement || stack.uiComponents || stack.formHandling) {
277
+ lines.push('');
278
+ lines.push('=== Frontend ===');
279
+
280
+ if (stack.stateManagement) {
281
+ lines.push(...formatDetection('State Management', stack.stateManagement));
282
+ }
283
+
284
+ lines.push(...formatDetectionArray('UI Components', stack.uiComponents));
285
+ lines.push(...formatDetectionArray('Form Handling', stack.formHandling));
286
+ }
287
+
288
+ // ============ Services ============
289
+ if (stack.auth || stack.analytics || stack.payments || stack.email) {
290
+ lines.push('');
291
+ lines.push('=== Services ===');
292
+
293
+ if (stack.auth) {
294
+ lines.push(...formatDetection('Auth', stack.auth));
295
+ }
296
+
297
+ lines.push(...formatDetectionArray('Analytics', stack.analytics));
298
+
299
+ if (stack.payments) {
300
+ lines.push(...formatDetection('Payments', stack.payments));
301
+ }
302
+
303
+ if (stack.email) {
304
+ lines.push(...formatDetection('Email', stack.email));
305
+ }
306
+ }
307
+
308
+ // ============ Infrastructure ============
309
+ if (stack.deployment || stack.monorepo) {
310
+ lines.push('');
311
+ lines.push('=== Infrastructure ===');
312
+
313
+ lines.push(...formatDetectionArray('Deployment', stack.deployment));
314
+
315
+ if (stack.monorepo) {
316
+ lines.push(...formatDetection('Monorepo', stack.monorepo));
317
+ }
318
+ }
319
+
320
+ // ============ MCP ============
321
+ if (stack.mcp) {
322
+ lines.push('');
323
+ lines.push('=== MCP ===');
324
+
325
+ if (stack.mcp.isProject) {
326
+ lines.push('Type: MCP Server Project');
327
+ if (stack.mcp.projectInfo) {
328
+ lines.push(...formatDetection('Project Info', stack.mcp.projectInfo, ' '));
329
+ }
330
+ }
331
+
332
+ if (stack.mcp.detected && stack.mcp.detected.length > 0) {
333
+ lines.push('Configured MCP Servers:');
334
+ for (const server of stack.mcp.detected) {
335
+ lines.push(` - ${server.name}`);
336
+ }
337
+ }
338
+
339
+ if (stack.mcp.recommended && stack.mcp.recommended.length > 0) {
340
+ lines.push(`Recommended MCP Servers: ${stack.mcp.recommended.join(', ')}`);
341
+ }
342
+ }
343
+
344
+ // ============ Errors ============
345
+ if (result.errors && result.errors.length > 0) {
346
+ lines.push('');
347
+ lines.push('Errors:');
348
+ for (const error of result.errors) {
349
+ lines.push(` - ${error}`);
350
+ }
351
+ }
352
+
353
+ return lines.join('\n');
354
+ }
@@ -0,0 +1,301 @@
1
+ /**
2
+ * Detector Registry
3
+ * Registers and manages all detectors for the scanner
4
+ */
5
+
6
+ import type { Detector, DetectorCategory, DetectionResult, DetectedStack, MCPStack } from './types.js';
7
+
8
+ // Import core detectors
9
+ import { frameworkDetector } from './detectors/core/framework.js';
10
+ import { packageManagerDetector } from './detectors/core/packageManager.js';
11
+ import { testingDetector } from './detectors/core/testing.js';
12
+ import { stylingDetector } from './detectors/core/styling.js';
13
+
14
+ // Import data layer detectors
15
+ import { databaseDetector } from './detectors/data/database.js';
16
+ import { ormDetector } from './detectors/data/orm.js';
17
+ import { apiDetector } from './detectors/data/api.js';
18
+
19
+ // Import frontend detectors
20
+ import { stateManagementDetector } from './detectors/frontend/stateManagement.js';
21
+ import { uiComponentsDetector } from './detectors/frontend/uiComponents.js';
22
+ import { formHandlingDetector } from './detectors/frontend/formHandling.js';
23
+
24
+ // Import services detectors
25
+ import { authDetector } from './detectors/services/auth.js';
26
+ import { analyticsDetector } from './detectors/services/analytics.js';
27
+ import { paymentsDetector } from './detectors/services/payments.js';
28
+ import { emailDetector } from './detectors/services/email.js';
29
+
30
+ // Import infrastructure detectors
31
+ import { deploymentDetector } from './detectors/infra/deployment.js';
32
+ import { monorepoDetector } from './detectors/infra/monorepo.js';
33
+
34
+ // Import MCP detectors
35
+ import { mcpServersDetector, getRecommendedMCPServers } from './detectors/mcp/mcpServers.js';
36
+ import { mcpProjectDetector } from './detectors/mcp/mcpProject.js';
37
+
38
+ /**
39
+ * All detector categories
40
+ */
41
+ const ALL_CATEGORIES: DetectorCategory[] = [
42
+ // Core
43
+ 'framework',
44
+ 'packageManager',
45
+ 'testing',
46
+ 'styling',
47
+ // Data Layer
48
+ 'database',
49
+ 'orm',
50
+ 'api',
51
+ // Frontend
52
+ 'stateManagement',
53
+ 'uiComponents',
54
+ 'formHandling',
55
+ // Services
56
+ 'auth',
57
+ 'analytics',
58
+ 'payments',
59
+ 'email',
60
+ // Infrastructure
61
+ 'deployment',
62
+ 'monorepo',
63
+ // MCP
64
+ 'mcp',
65
+ ];
66
+
67
+ /**
68
+ * Detector Registry class
69
+ * Manages registration and execution of detectors
70
+ */
71
+ export class DetectorRegistry {
72
+ private detectors: Map<DetectorCategory, Detector[]> = new Map();
73
+
74
+ constructor() {
75
+ // Initialize all categories
76
+ for (const category of ALL_CATEGORIES) {
77
+ this.detectors.set(category, []);
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Register a detector
83
+ */
84
+ register(detector: Detector): void {
85
+ const category = this.detectors.get(detector.category);
86
+ if (category) {
87
+ category.push(detector);
88
+ }
89
+ }
90
+
91
+ /**
92
+ * Get all detectors for a category
93
+ */
94
+ getDetectors(category: DetectorCategory): Detector[] {
95
+ return this.detectors.get(category) || [];
96
+ }
97
+
98
+ /**
99
+ * Get all registered detectors
100
+ */
101
+ getAllDetectors(): Detector[] {
102
+ const all: Detector[] = [];
103
+ for (const detectors of this.detectors.values()) {
104
+ all.push(...detectors);
105
+ }
106
+ return all;
107
+ }
108
+
109
+ /**
110
+ * Run a single-result detector category
111
+ */
112
+ private async runSingleDetector(
113
+ projectRoot: string,
114
+ category: DetectorCategory
115
+ ): Promise<DetectionResult | null> {
116
+ for (const detector of this.getDetectors(category)) {
117
+ const result = await detector.detect(projectRoot);
118
+ if (result && !Array.isArray(result)) {
119
+ return result;
120
+ }
121
+ }
122
+ return null;
123
+ }
124
+
125
+ /**
126
+ * Run a multi-result detector category
127
+ */
128
+ private async runMultiDetector(
129
+ projectRoot: string,
130
+ category: DetectorCategory
131
+ ): Promise<DetectionResult[] | null> {
132
+ for (const detector of this.getDetectors(category)) {
133
+ const result = await detector.detect(projectRoot);
134
+ if (result) {
135
+ return Array.isArray(result) ? result : [result];
136
+ }
137
+ }
138
+ return null;
139
+ }
140
+
141
+ /**
142
+ * Run all detectors for a project
143
+ */
144
+ async runAllDetectors(projectRoot: string): Promise<DetectedStack> {
145
+ const stack: DetectedStack = {};
146
+
147
+ // ============ Core ============
148
+
149
+ // Run framework detector
150
+ stack.framework = await this.runSingleDetector(projectRoot, 'framework') || undefined;
151
+
152
+ // Run package manager detector
153
+ stack.packageManager = await this.runSingleDetector(projectRoot, 'packageManager') || undefined;
154
+
155
+ // Run testing detectors
156
+ for (const detector of this.getDetectors('testing')) {
157
+ const result = await detector.detect(projectRoot);
158
+ if (result) {
159
+ stack.testing = {};
160
+ if (Array.isArray(result)) {
161
+ for (const r of result) {
162
+ if (r.variant === 'unit') {
163
+ stack.testing.unit = r;
164
+ } else if (r.variant === 'e2e') {
165
+ stack.testing.e2e = r;
166
+ }
167
+ }
168
+ } else {
169
+ // Single result, determine type from name
170
+ if (result.name === 'Jest' || result.name === 'Vitest') {
171
+ stack.testing.unit = result;
172
+ } else {
173
+ stack.testing.e2e = result;
174
+ }
175
+ }
176
+ break;
177
+ }
178
+ }
179
+
180
+ // Run styling detector
181
+ stack.styling = await this.runSingleDetector(projectRoot, 'styling') || undefined;
182
+
183
+ // ============ Data Layer ============
184
+
185
+ // Run database detector
186
+ stack.database = await this.runSingleDetector(projectRoot, 'database') || undefined;
187
+
188
+ // Run ORM detector
189
+ stack.orm = await this.runSingleDetector(projectRoot, 'orm') || undefined;
190
+
191
+ // Run API detector (multi-result)
192
+ stack.api = await this.runMultiDetector(projectRoot, 'api') || undefined;
193
+
194
+ // ============ Frontend ============
195
+
196
+ // Run state management detector
197
+ stack.stateManagement = await this.runSingleDetector(projectRoot, 'stateManagement') || undefined;
198
+
199
+ // Run UI components detector (multi-result)
200
+ stack.uiComponents = await this.runMultiDetector(projectRoot, 'uiComponents') || undefined;
201
+
202
+ // Run form handling detector (multi-result)
203
+ stack.formHandling = await this.runMultiDetector(projectRoot, 'formHandling') || undefined;
204
+
205
+ // ============ Services ============
206
+
207
+ // Run auth detector
208
+ stack.auth = await this.runSingleDetector(projectRoot, 'auth') || undefined;
209
+
210
+ // Run analytics detector (multi-result)
211
+ stack.analytics = await this.runMultiDetector(projectRoot, 'analytics') || undefined;
212
+
213
+ // Run payments detector
214
+ stack.payments = await this.runSingleDetector(projectRoot, 'payments') || undefined;
215
+
216
+ // Run email detector
217
+ stack.email = await this.runSingleDetector(projectRoot, 'email') || undefined;
218
+
219
+ // ============ Infrastructure ============
220
+
221
+ // Run deployment detector (multi-result)
222
+ stack.deployment = await this.runMultiDetector(projectRoot, 'deployment') || undefined;
223
+
224
+ // Run monorepo detector
225
+ stack.monorepo = await this.runSingleDetector(projectRoot, 'monorepo') || undefined;
226
+
227
+ // ============ MCP ============
228
+
229
+ // Run MCP detectors
230
+ const mcpStack: MCPStack = {};
231
+
232
+ // Detect configured MCP servers
233
+ const mcpServers = await this.runMultiDetector(projectRoot, 'mcp');
234
+
235
+ // Separate MCP server configs from MCP project detection
236
+ if (mcpServers) {
237
+ const serverConfigs = mcpServers.filter(r => r.name.startsWith('MCP:') || r.name === 'MCP Config Directory');
238
+ const projectInfo = mcpServers.find(r => r.name === 'MCP Server Project' || r.name === 'MCP Client');
239
+
240
+ if (serverConfigs.length > 0) {
241
+ mcpStack.detected = serverConfigs;
242
+ }
243
+
244
+ if (projectInfo) {
245
+ mcpStack.isProject = projectInfo.name === 'MCP Server Project';
246
+ mcpStack.projectInfo = projectInfo;
247
+ }
248
+ }
249
+
250
+ // Get recommended MCP servers based on stack
251
+ mcpStack.recommended = getRecommendedMCPServers(projectRoot);
252
+
253
+ // Only add MCP stack if there's meaningful data
254
+ if (mcpStack.detected || mcpStack.isProject || (mcpStack.recommended && mcpStack.recommended.length > 0)) {
255
+ stack.mcp = mcpStack;
256
+ }
257
+
258
+ return stack;
259
+ }
260
+ }
261
+
262
+ /**
263
+ * Create a registry with all core detectors pre-registered
264
+ */
265
+ export function createDefaultRegistry(): DetectorRegistry {
266
+ const registry = new DetectorRegistry();
267
+
268
+ // Register core detectors
269
+ registry.register(frameworkDetector);
270
+ registry.register(packageManagerDetector);
271
+ registry.register(testingDetector);
272
+ registry.register(stylingDetector);
273
+
274
+ // Register data layer detectors
275
+ registry.register(databaseDetector);
276
+ registry.register(ormDetector);
277
+ registry.register(apiDetector);
278
+
279
+ // Register frontend detectors
280
+ registry.register(stateManagementDetector);
281
+ registry.register(uiComponentsDetector);
282
+ registry.register(formHandlingDetector);
283
+
284
+ // Register services detectors
285
+ registry.register(authDetector);
286
+ registry.register(analyticsDetector);
287
+ registry.register(paymentsDetector);
288
+ registry.register(emailDetector);
289
+
290
+ // Register infrastructure detectors
291
+ registry.register(deploymentDetector);
292
+ registry.register(monorepoDetector);
293
+
294
+ // Register MCP detectors
295
+ registry.register(mcpServersDetector);
296
+ registry.register(mcpProjectDetector);
297
+
298
+ return registry;
299
+ }
300
+
301
+ export { DetectorRegistry as Registry };