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,251 @@
1
+ /**
2
+ * Monorepo Tool Detector
3
+ * Detects: Turborepo, Nx, pnpm workspaces, Lerna
4
+ */
5
+
6
+ import { existsSync, readFileSync } from 'node:fs';
7
+ import { join } from 'node:path';
8
+ import type { Detector, DetectionResult } from '../../types.js';
9
+
10
+ /**
11
+ * Read and parse package.json from a directory
12
+ */
13
+ function readPackageJson(projectRoot: string): Record<string, unknown> | null {
14
+ const packageJsonPath = join(projectRoot, 'package.json');
15
+ if (!existsSync(packageJsonPath)) {
16
+ return null;
17
+ }
18
+ try {
19
+ const content = readFileSync(packageJsonPath, 'utf-8');
20
+ return JSON.parse(content);
21
+ } catch {
22
+ return null;
23
+ }
24
+ }
25
+
26
+ /**
27
+ * Get all dependencies from package.json (deps + devDeps)
28
+ */
29
+ function getDependencies(pkg: Record<string, unknown>): Record<string, string> {
30
+ const deps = (pkg.dependencies as Record<string, string>) || {};
31
+ const devDeps = (pkg.devDependencies as Record<string, string>) || {};
32
+ return { ...deps, ...devDeps };
33
+ }
34
+
35
+ /**
36
+ * Detect Turborepo
37
+ */
38
+ function detectTurborepo(projectRoot: string, deps: Record<string, string>): DetectionResult | null {
39
+ const evidence: string[] = [];
40
+ let confidence = 0;
41
+
42
+ // Check for turbo.json
43
+ const turboJson = join(projectRoot, 'turbo.json');
44
+ if (existsSync(turboJson)) {
45
+ evidence.push('turbo.json found');
46
+ confidence += 60;
47
+ }
48
+
49
+ // Check for turbo in dependencies
50
+ if (deps.turbo) {
51
+ evidence.push(`turbo@${deps.turbo} in devDependencies`);
52
+ confidence += 40;
53
+ }
54
+
55
+ if (confidence === 0) return null;
56
+
57
+ return {
58
+ name: 'Turborepo',
59
+ version: deps.turbo,
60
+ confidence: Math.min(confidence, 100),
61
+ evidence,
62
+ };
63
+ }
64
+
65
+ /**
66
+ * Detect Nx
67
+ */
68
+ function detectNx(projectRoot: string, deps: Record<string, string>): DetectionResult | null {
69
+ const evidence: string[] = [];
70
+ let confidence = 0;
71
+
72
+ // Check for nx.json
73
+ const nxJson = join(projectRoot, 'nx.json');
74
+ if (existsSync(nxJson)) {
75
+ evidence.push('nx.json found');
76
+ confidence += 60;
77
+ }
78
+
79
+ // Check for nx in dependencies
80
+ if (deps.nx) {
81
+ evidence.push(`nx@${deps.nx} in devDependencies`);
82
+ confidence += 40;
83
+ }
84
+
85
+ // Check for @nrwl/* packages (older Nx packages)
86
+ if (deps['@nrwl/workspace'] || deps['@nx/workspace']) {
87
+ evidence.push('Nx workspace package found');
88
+ confidence += 20;
89
+ }
90
+
91
+ if (confidence === 0) return null;
92
+
93
+ return {
94
+ name: 'Nx',
95
+ version: deps.nx,
96
+ confidence: Math.min(confidence, 100),
97
+ evidence,
98
+ };
99
+ }
100
+
101
+ /**
102
+ * Detect pnpm workspaces
103
+ */
104
+ function detectPnpmWorkspaces(projectRoot: string): DetectionResult | null {
105
+ const evidence: string[] = [];
106
+ let confidence = 0;
107
+
108
+ // Check for pnpm-workspace.yaml
109
+ const pnpmWorkspace = join(projectRoot, 'pnpm-workspace.yaml');
110
+ if (existsSync(pnpmWorkspace)) {
111
+ evidence.push('pnpm-workspace.yaml found');
112
+ confidence += 80;
113
+ }
114
+
115
+ if (confidence === 0) return null;
116
+
117
+ return {
118
+ name: 'pnpm Workspaces',
119
+ confidence: Math.min(confidence, 100),
120
+ evidence,
121
+ };
122
+ }
123
+
124
+ /**
125
+ * Detect npm/yarn workspaces
126
+ */
127
+ function detectNpmYarnWorkspaces(projectRoot: string, pkg: Record<string, unknown>): DetectionResult | null {
128
+ const evidence: string[] = [];
129
+ let confidence = 0;
130
+ let variant: string | undefined;
131
+
132
+ // Check for workspaces field in package.json
133
+ if (pkg.workspaces) {
134
+ evidence.push('workspaces field in package.json');
135
+ confidence += 70;
136
+
137
+ // Determine if it's yarn or npm based on lock files
138
+ const yarnLock = join(projectRoot, 'yarn.lock');
139
+ const npmLock = join(projectRoot, 'package-lock.json');
140
+
141
+ if (existsSync(yarnLock)) {
142
+ variant = 'yarn';
143
+ evidence.push('yarn.lock found');
144
+ } else if (existsSync(npmLock)) {
145
+ variant = 'npm';
146
+ evidence.push('package-lock.json found');
147
+ }
148
+ }
149
+
150
+ if (confidence === 0) return null;
151
+
152
+ return {
153
+ name: variant === 'yarn' ? 'Yarn Workspaces' : 'npm Workspaces',
154
+ variant,
155
+ confidence: Math.min(confidence, 100),
156
+ evidence,
157
+ };
158
+ }
159
+
160
+ /**
161
+ * Detect Lerna
162
+ */
163
+ function detectLerna(projectRoot: string, deps: Record<string, string>): DetectionResult | null {
164
+ const evidence: string[] = [];
165
+ let confidence = 0;
166
+
167
+ // Check for lerna.json
168
+ const lernaJson = join(projectRoot, 'lerna.json');
169
+ if (existsSync(lernaJson)) {
170
+ evidence.push('lerna.json found');
171
+ confidence += 70;
172
+ }
173
+
174
+ // Check for lerna in dependencies
175
+ if (deps.lerna) {
176
+ evidence.push(`lerna@${deps.lerna} in devDependencies`);
177
+ confidence += 30;
178
+ }
179
+
180
+ if (confidence === 0) return null;
181
+
182
+ return {
183
+ name: 'Lerna',
184
+ version: deps.lerna,
185
+ confidence: Math.min(confidence, 100),
186
+ evidence,
187
+ };
188
+ }
189
+
190
+ /**
191
+ * Detect Rush
192
+ */
193
+ function detectRush(projectRoot: string): DetectionResult | null {
194
+ const evidence: string[] = [];
195
+ let confidence = 0;
196
+
197
+ // Check for rush.json
198
+ const rushJson = join(projectRoot, 'rush.json');
199
+ if (existsSync(rushJson)) {
200
+ evidence.push('rush.json found');
201
+ confidence += 80;
202
+ }
203
+
204
+ if (confidence === 0) return null;
205
+
206
+ return {
207
+ name: 'Rush',
208
+ confidence: Math.min(confidence, 100),
209
+ evidence,
210
+ };
211
+ }
212
+
213
+ /**
214
+ * Monorepo tool detector
215
+ * Returns the primary monorepo tool detected
216
+ */
217
+ export const monorepoDetector: Detector = {
218
+ category: 'monorepo',
219
+ name: 'Monorepo Tool Detector',
220
+
221
+ async detect(projectRoot: string): Promise<DetectionResult | null> {
222
+ const pkg = readPackageJson(projectRoot);
223
+ const deps = pkg ? getDependencies(pkg) : {};
224
+
225
+ // Priority: dedicated monorepo tools first, then workspace configs
226
+ const detectors = [
227
+ () => detectTurborepo(projectRoot, deps),
228
+ () => detectNx(projectRoot, deps),
229
+ () => detectLerna(projectRoot, deps),
230
+ () => detectRush(projectRoot),
231
+ () => detectPnpmWorkspaces(projectRoot),
232
+ () => pkg ? detectNpmYarnWorkspaces(projectRoot, pkg) : null,
233
+ ];
234
+
235
+ // Find the highest confidence result
236
+ let bestResult: DetectionResult | null = null;
237
+ let bestConfidence = 0;
238
+
239
+ for (const detector of detectors) {
240
+ const result = detector();
241
+ if (result && result.confidence > bestConfidence) {
242
+ bestResult = result;
243
+ bestConfidence = result.confidence;
244
+ }
245
+ }
246
+
247
+ return bestResult && bestResult.confidence >= 40 ? bestResult : null;
248
+ },
249
+ };
250
+
251
+ export default monorepoDetector;
@@ -0,0 +1,176 @@
1
+ /**
2
+ * MCP Project Detector
3
+ * Detects if the project is building an MCP server
4
+ */
5
+
6
+ import { existsSync, readFileSync } from 'node:fs';
7
+ import { join } from 'node:path';
8
+ import type { Detector, DetectionResult } from '../../types.js';
9
+
10
+ /**
11
+ * Read and parse package.json from a directory
12
+ */
13
+ function readPackageJson(projectRoot: string): Record<string, unknown> | null {
14
+ const packageJsonPath = join(projectRoot, 'package.json');
15
+ if (!existsSync(packageJsonPath)) {
16
+ return null;
17
+ }
18
+ try {
19
+ const content = readFileSync(packageJsonPath, 'utf-8');
20
+ return JSON.parse(content);
21
+ } catch {
22
+ return null;
23
+ }
24
+ }
25
+
26
+ /**
27
+ * Get all dependencies from package.json (deps + devDeps)
28
+ */
29
+ function getDependencies(pkg: Record<string, unknown>): Record<string, string> {
30
+ const deps = (pkg.dependencies as Record<string, string>) || {};
31
+ const devDeps = (pkg.devDependencies as Record<string, string>) || {};
32
+ return { ...deps, ...devDeps };
33
+ }
34
+
35
+ /**
36
+ * Detect if project is an MCP server
37
+ */
38
+ function detectMCPServer(projectRoot: string, pkg: Record<string, unknown>, deps: Record<string, string>): DetectionResult | null {
39
+ const evidence: string[] = [];
40
+ let confidence = 0;
41
+
42
+ // Check for @modelcontextprotocol/sdk
43
+ if (deps['@modelcontextprotocol/sdk']) {
44
+ evidence.push(`@modelcontextprotocol/sdk@${deps['@modelcontextprotocol/sdk']} in dependencies`);
45
+ confidence += 70;
46
+ }
47
+
48
+ // Check for mcp-* or *-mcp in package name
49
+ const packageName = pkg.name as string | undefined;
50
+ if (packageName) {
51
+ if (packageName.startsWith('mcp-') || packageName.endsWith('-mcp')) {
52
+ evidence.push(`Package name "${packageName}" follows MCP naming convention`);
53
+ confidence += 20;
54
+ }
55
+ if (packageName.includes('mcp-server') || packageName.includes('server-mcp')) {
56
+ evidence.push(`Package name "${packageName}" indicates MCP server`);
57
+ confidence += 10;
58
+ }
59
+ }
60
+
61
+ // Check for MCP-related keywords in package.json
62
+ const keywords = pkg.keywords as string[] | undefined;
63
+ if (keywords && Array.isArray(keywords)) {
64
+ const mcpKeywords = keywords.filter(k =>
65
+ k.toLowerCase().includes('mcp') ||
66
+ k.toLowerCase().includes('model-context-protocol') ||
67
+ k.toLowerCase().includes('claude')
68
+ );
69
+ if (mcpKeywords.length > 0) {
70
+ evidence.push(`MCP-related keywords: ${mcpKeywords.join(', ')}`);
71
+ confidence += 10;
72
+ }
73
+ }
74
+
75
+ // Check for MCP server files
76
+ const serverFiles = [
77
+ 'src/index.ts',
78
+ 'src/server.ts',
79
+ 'src/mcp.ts',
80
+ 'index.ts',
81
+ 'server.ts',
82
+ ];
83
+
84
+ for (const file of serverFiles) {
85
+ const filePath = join(projectRoot, file);
86
+ if (existsSync(filePath)) {
87
+ try {
88
+ const content = readFileSync(filePath, 'utf-8');
89
+ if (content.includes('@modelcontextprotocol/sdk') || content.includes('McpServer')) {
90
+ evidence.push(`MCP server code found in ${file}`);
91
+ confidence += 20;
92
+ break;
93
+ }
94
+ } catch {
95
+ // Ignore read errors
96
+ }
97
+ }
98
+ }
99
+
100
+ // Check for package.json bin field (common for MCP servers)
101
+ if (pkg.bin && deps['@modelcontextprotocol/sdk']) {
102
+ evidence.push('Package has bin field (likely a CLI MCP server)');
103
+ confidence += 10;
104
+ }
105
+
106
+ if (confidence === 0) return null;
107
+
108
+ return {
109
+ name: 'MCP Server Project',
110
+ version: deps['@modelcontextprotocol/sdk'],
111
+ confidence: Math.min(confidence, 100),
112
+ evidence,
113
+ };
114
+ }
115
+
116
+ /**
117
+ * Detect if project uses MCP client
118
+ */
119
+ function detectMCPClient(deps: Record<string, string>): DetectionResult | null {
120
+ const evidence: string[] = [];
121
+ let confidence = 0;
122
+
123
+ // Check for MCP client packages
124
+ if (deps['@anthropic-ai/sdk']) {
125
+ evidence.push(`@anthropic-ai/sdk@${deps['@anthropic-ai/sdk']} in dependencies`);
126
+ confidence += 50;
127
+ }
128
+
129
+ if (deps['@modelcontextprotocol/sdk']) {
130
+ // Could be either client or server, lower confidence
131
+ evidence.push('MCP SDK found (could be client usage)');
132
+ confidence += 30;
133
+ }
134
+
135
+ if (confidence === 0) return null;
136
+
137
+ return {
138
+ name: 'MCP Client',
139
+ confidence: Math.min(confidence, 100),
140
+ evidence,
141
+ };
142
+ }
143
+
144
+ /**
145
+ * MCP project detector
146
+ * Detects if the project is an MCP server or uses MCP
147
+ */
148
+ export const mcpProjectDetector: Detector = {
149
+ category: 'mcp',
150
+ name: 'MCP Project Detector',
151
+
152
+ async detect(projectRoot: string): Promise<DetectionResult | null> {
153
+ const pkg = readPackageJson(projectRoot);
154
+ if (!pkg) {
155
+ return null;
156
+ }
157
+
158
+ const deps = getDependencies(pkg);
159
+
160
+ // First check if it's an MCP server project
161
+ const serverResult = detectMCPServer(projectRoot, pkg, deps);
162
+ if (serverResult && serverResult.confidence >= 40) {
163
+ return serverResult;
164
+ }
165
+
166
+ // Then check if it uses MCP client
167
+ const clientResult = detectMCPClient(deps);
168
+ if (clientResult && clientResult.confidence >= 40) {
169
+ return clientResult;
170
+ }
171
+
172
+ return null;
173
+ },
174
+ };
175
+
176
+ export default mcpProjectDetector;
@@ -0,0 +1,237 @@
1
+ /**
2
+ * MCP Servers Detector
3
+ * Detects usable MCP tools from config files and infers from dependencies
4
+ */
5
+
6
+ import { existsSync, readFileSync } from 'node:fs';
7
+ import { join } from 'node:path';
8
+ import type { Detector, DetectionResult } from '../../types.js';
9
+
10
+ /**
11
+ * Read and parse package.json from a directory
12
+ */
13
+ function readPackageJson(projectRoot: string): Record<string, unknown> | null {
14
+ const packageJsonPath = join(projectRoot, 'package.json');
15
+ if (!existsSync(packageJsonPath)) {
16
+ return null;
17
+ }
18
+ try {
19
+ const content = readFileSync(packageJsonPath, 'utf-8');
20
+ return JSON.parse(content);
21
+ } catch {
22
+ return null;
23
+ }
24
+ }
25
+
26
+ /**
27
+ * Get all dependencies from package.json (deps + devDeps)
28
+ */
29
+ function getDependencies(pkg: Record<string, unknown>): Record<string, string> {
30
+ const deps = (pkg.dependencies as Record<string, string>) || {};
31
+ const devDeps = (pkg.devDependencies as Record<string, string>) || {};
32
+ return { ...deps, ...devDeps };
33
+ }
34
+
35
+ /**
36
+ * Read JSON file safely
37
+ */
38
+ function readJsonFile(filePath: string): Record<string, unknown> | null {
39
+ if (!existsSync(filePath)) {
40
+ return null;
41
+ }
42
+ try {
43
+ const content = readFileSync(filePath, 'utf-8');
44
+ return JSON.parse(content);
45
+ } catch {
46
+ return null;
47
+ }
48
+ }
49
+
50
+ /**
51
+ * MCP configuration structure
52
+ */
53
+ interface MCPConfig {
54
+ mcpServers?: Record<string, {
55
+ command?: string;
56
+ args?: string[];
57
+ url?: string;
58
+ }>;
59
+ }
60
+
61
+ /**
62
+ * Detect MCP configuration from .claude directory
63
+ */
64
+ function detectClaudeMCP(projectRoot: string): DetectionResult[] {
65
+ const results: DetectionResult[] = [];
66
+ const claudeDir = join(projectRoot, '.claude');
67
+
68
+ if (!existsSync(claudeDir)) {
69
+ return results;
70
+ }
71
+
72
+ // Check for mcp.json in .claude directory
73
+ const mcpConfigPath = join(claudeDir, 'mcp.json');
74
+ const config = readJsonFile(mcpConfigPath) as MCPConfig | null;
75
+
76
+ if (config?.mcpServers) {
77
+ for (const [name, serverConfig] of Object.entries(config.mcpServers)) {
78
+ results.push({
79
+ name: `MCP: ${name}`,
80
+ confidence: 90,
81
+ evidence: [`Configured in .claude/mcp.json`],
82
+ variant: serverConfig.command || serverConfig.url || 'configured',
83
+ });
84
+ }
85
+ }
86
+
87
+ return results;
88
+ }
89
+
90
+ /**
91
+ * Detect MCP configuration from .cursor directory
92
+ */
93
+ function detectCursorMCP(projectRoot: string): DetectionResult[] {
94
+ const results: DetectionResult[] = [];
95
+ const cursorDir = join(projectRoot, '.cursor');
96
+
97
+ if (!existsSync(cursorDir)) {
98
+ return results;
99
+ }
100
+
101
+ // Check for mcp.json in .cursor directory
102
+ const mcpConfigPath = join(cursorDir, 'mcp.json');
103
+ const config = readJsonFile(mcpConfigPath) as MCPConfig | null;
104
+
105
+ if (config?.mcpServers) {
106
+ for (const [name, serverConfig] of Object.entries(config.mcpServers)) {
107
+ results.push({
108
+ name: `MCP: ${name}`,
109
+ confidence: 90,
110
+ evidence: [`Configured in .cursor/mcp.json`],
111
+ variant: serverConfig.command || serverConfig.url || 'configured',
112
+ });
113
+ }
114
+ }
115
+
116
+ return results;
117
+ }
118
+
119
+ /**
120
+ * Detect MCP configuration from root mcp.json
121
+ */
122
+ function detectRootMCP(projectRoot: string): DetectionResult[] {
123
+ const results: DetectionResult[] = [];
124
+ const mcpConfigPath = join(projectRoot, 'mcp.json');
125
+ const config = readJsonFile(mcpConfigPath) as MCPConfig | null;
126
+
127
+ if (config?.mcpServers) {
128
+ for (const [name, serverConfig] of Object.entries(config.mcpServers)) {
129
+ results.push({
130
+ name: `MCP: ${name}`,
131
+ confidence: 90,
132
+ evidence: [`Configured in mcp.json`],
133
+ variant: serverConfig.command || serverConfig.url || 'configured',
134
+ });
135
+ }
136
+ }
137
+
138
+ return results;
139
+ }
140
+
141
+ /**
142
+ * Infer recommended MCP servers from dependencies
143
+ */
144
+ function inferMCPFromDeps(deps: Record<string, string>): string[] {
145
+ const recommendations: string[] = [];
146
+
147
+ // Supabase → Supabase MCP
148
+ if (deps['@supabase/supabase-js'] || deps['@supabase/ssr']) {
149
+ recommendations.push('supabase');
150
+ }
151
+
152
+ // GitHub (if repo uses git) → GitHub MCP
153
+ // This is almost always useful
154
+ recommendations.push('github');
155
+
156
+ // Stripe → Stripe MCP (if available)
157
+ if (deps.stripe) {
158
+ recommendations.push('stripe');
159
+ }
160
+
161
+ // PostgreSQL → Postgres MCP
162
+ if (deps.pg || deps.postgres || deps['@prisma/client']) {
163
+ recommendations.push('postgres');
164
+ }
165
+
166
+ // Filesystem is almost always useful
167
+ recommendations.push('filesystem');
168
+
169
+ // Memory/knowledge base
170
+ recommendations.push('memory');
171
+
172
+ return [...new Set(recommendations)]; // Remove duplicates
173
+ }
174
+
175
+ /**
176
+ * MCP servers detector
177
+ * Returns detected MCP configurations and recommendations
178
+ */
179
+ export const mcpServersDetector: Detector = {
180
+ category: 'mcp',
181
+ name: 'MCP Servers Detector',
182
+
183
+ async detect(projectRoot: string): Promise<DetectionResult[] | null> {
184
+ const results: DetectionResult[] = [];
185
+
186
+ // Detect configured MCP servers
187
+ results.push(...detectClaudeMCP(projectRoot));
188
+ results.push(...detectCursorMCP(projectRoot));
189
+ results.push(...detectRootMCP(projectRoot));
190
+
191
+ // Deduplicate by name
192
+ const seen = new Set<string>();
193
+ const uniqueResults = results.filter(r => {
194
+ if (seen.has(r.name)) return false;
195
+ seen.add(r.name);
196
+ return true;
197
+ });
198
+
199
+ // If we found configured servers, return them
200
+ if (uniqueResults.length > 0) {
201
+ return uniqueResults;
202
+ }
203
+
204
+ // Otherwise, check for MCP config directories existence
205
+ const claudeDir = join(projectRoot, '.claude');
206
+ const cursorDir = join(projectRoot, '.cursor');
207
+
208
+ if (existsSync(claudeDir) || existsSync(cursorDir)) {
209
+ return [{
210
+ name: 'MCP Config Directory',
211
+ confidence: 50,
212
+ evidence: [
213
+ existsSync(claudeDir) ? '.claude/ directory found' : '',
214
+ existsSync(cursorDir) ? '.cursor/ directory found' : '',
215
+ ].filter(Boolean),
216
+ }];
217
+ }
218
+
219
+ return null;
220
+ },
221
+ };
222
+
223
+ /**
224
+ * Get recommended MCP servers based on project stack
225
+ * This is a utility function, not a detector
226
+ */
227
+ export function getRecommendedMCPServers(projectRoot: string): string[] {
228
+ const pkg = readPackageJson(projectRoot);
229
+ if (!pkg) {
230
+ return ['filesystem', 'github', 'memory'];
231
+ }
232
+
233
+ const deps = getDependencies(pkg);
234
+ return inferMCPFromDeps(deps);
235
+ }
236
+
237
+ export default mcpServersDetector;