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,91 @@
1
+ /**
2
+ * Package Manager Detector
3
+ * Detects: pnpm, yarn, bun, npm
4
+ */
5
+
6
+ import { existsSync } from 'node:fs';
7
+ import { join } from 'node:path';
8
+ import type { Detector, DetectionResult } from '../../types.js';
9
+
10
+ interface LockFileInfo {
11
+ name: string;
12
+ file: string;
13
+ confidence: number;
14
+ }
15
+
16
+ const LOCK_FILES: LockFileInfo[] = [
17
+ { name: 'pnpm', file: 'pnpm-lock.yaml', confidence: 95 },
18
+ { name: 'yarn', file: 'yarn.lock', confidence: 95 },
19
+ { name: 'bun', file: 'bun.lockb', confidence: 95 },
20
+ { name: 'npm', file: 'package-lock.json', confidence: 95 },
21
+ ];
22
+
23
+ /**
24
+ * Package manager detector
25
+ */
26
+ export const packageManagerDetector: Detector = {
27
+ category: 'packageManager',
28
+ name: 'Package Manager Detector',
29
+
30
+ async detect(projectRoot: string): Promise<DetectionResult | null> {
31
+ const evidence: string[] = [];
32
+
33
+ // Check for lock files in order of preference
34
+ for (const lockFile of LOCK_FILES) {
35
+ const lockFilePath = join(projectRoot, lockFile.file);
36
+ if (existsSync(lockFilePath)) {
37
+ evidence.push(`${lockFile.file} found`);
38
+
39
+ return {
40
+ name: lockFile.name,
41
+ confidence: lockFile.confidence,
42
+ evidence,
43
+ };
44
+ }
45
+ }
46
+
47
+ // Check for packageManager field in package.json
48
+ const packageJsonPath = join(projectRoot, 'package.json');
49
+ if (existsSync(packageJsonPath)) {
50
+ try {
51
+ const { readFileSync } = await import('node:fs');
52
+ const content = readFileSync(packageJsonPath, 'utf-8');
53
+ const pkg = JSON.parse(content);
54
+
55
+ if (pkg.packageManager) {
56
+ const match = pkg.packageManager.match(/^(pnpm|yarn|npm|bun)@/);
57
+ if (match) {
58
+ const manager = match[1];
59
+ const versionMatch = pkg.packageManager.match(/@([^\s]+)/);
60
+ evidence.push(`packageManager field: ${pkg.packageManager}`);
61
+
62
+ return {
63
+ name: manager,
64
+ version: versionMatch ? versionMatch[1] : undefined,
65
+ confidence: 90,
66
+ evidence,
67
+ };
68
+ }
69
+ }
70
+ } catch {
71
+ // Ignore parse errors
72
+ }
73
+ }
74
+
75
+ // Check for node_modules (fallback, assume npm)
76
+ const nodeModulesPath = join(projectRoot, 'node_modules');
77
+ if (existsSync(nodeModulesPath)) {
78
+ evidence.push('node_modules exists, no lock file found');
79
+ return {
80
+ name: 'npm',
81
+ confidence: 30, // Low confidence since we're just guessing
82
+ evidence,
83
+ };
84
+ }
85
+
86
+ // No package manager detected
87
+ return null;
88
+ },
89
+ };
90
+
91
+ export default packageManagerDetector;
@@ -0,0 +1,261 @@
1
+ /**
2
+ * Styling Detector
3
+ * Detects: Tailwind, CSS Modules, styled-components, Sass/SCSS
4
+ */
5
+
6
+ import { existsSync, readFileSync, readdirSync } 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
+ * Check if a config file exists (supports multiple extensions)
37
+ */
38
+ function findConfigFile(projectRoot: string, baseName: string, extensions: string[]): string | null {
39
+ for (const ext of extensions) {
40
+ const filePath = join(projectRoot, `${baseName}${ext}`);
41
+ if (existsSync(filePath)) {
42
+ return `${baseName}${ext}`;
43
+ }
44
+ }
45
+ return null;
46
+ }
47
+
48
+ /**
49
+ * Check if CSS Module files exist in common directories
50
+ */
51
+ function hasCssModules(projectRoot: string): boolean {
52
+ const dirsToCheck = ['src', 'app', 'pages', 'components', 'styles'];
53
+
54
+ for (const dir of dirsToCheck) {
55
+ const dirPath = join(projectRoot, dir);
56
+ if (existsSync(dirPath)) {
57
+ try {
58
+ const files = readdirSync(dirPath, { recursive: true });
59
+ for (const file of files) {
60
+ const fileName = typeof file === 'string' ? file : file.toString();
61
+ if (fileName.includes('.module.css') || fileName.includes('.module.scss')) {
62
+ return true;
63
+ }
64
+ }
65
+ } catch {
66
+ // Ignore directory read errors
67
+ }
68
+ }
69
+ }
70
+
71
+ return false;
72
+ }
73
+
74
+ /**
75
+ * Detect Tailwind CSS
76
+ */
77
+ function detectTailwind(projectRoot: string, deps: Record<string, string>): DetectionResult | null {
78
+ const evidence: string[] = [];
79
+ let confidence = 0;
80
+
81
+ if (deps.tailwindcss) {
82
+ evidence.push(`tailwindcss@${deps.tailwindcss} in dependencies`);
83
+ confidence += 50;
84
+ }
85
+
86
+ // Check for tailwind config
87
+ const configExtensions = ['.config.js', '.config.ts', '.config.mjs', '.config.cjs'];
88
+ const configFile = findConfigFile(projectRoot, 'tailwind', configExtensions);
89
+ if (configFile) {
90
+ evidence.push(`${configFile} found`);
91
+ confidence += 40;
92
+ }
93
+
94
+ // Check for postcss config (often paired with Tailwind)
95
+ const postcssConfig = findConfigFile(projectRoot, 'postcss.config', ['.js', '.mjs', '.cjs']);
96
+ if (postcssConfig && deps.tailwindcss) {
97
+ evidence.push(`${postcssConfig} found`);
98
+ confidence += 10;
99
+ }
100
+
101
+ if (confidence === 0) return null;
102
+
103
+ // Detect Tailwind v4 (uses CSS-based config)
104
+ let variant: string | undefined;
105
+ if (deps.tailwindcss) {
106
+ const version = deps.tailwindcss.replace(/[\^~]/, '');
107
+ if (version.startsWith('4.')) {
108
+ variant = 'v4';
109
+ }
110
+ }
111
+
112
+ return {
113
+ name: 'Tailwind CSS',
114
+ version: deps.tailwindcss,
115
+ variant,
116
+ confidence: Math.min(confidence, 100),
117
+ evidence,
118
+ };
119
+ }
120
+
121
+ /**
122
+ * Detect styled-components
123
+ */
124
+ function detectStyledComponents(projectRoot: string, deps: Record<string, string>): DetectionResult | null {
125
+ const evidence: string[] = [];
126
+ let confidence = 0;
127
+
128
+ if (deps['styled-components']) {
129
+ evidence.push(`styled-components@${deps['styled-components']} in dependencies`);
130
+ confidence += 80;
131
+ }
132
+
133
+ // Check for babel plugin
134
+ if (deps['babel-plugin-styled-components']) {
135
+ evidence.push('babel-plugin-styled-components in dependencies');
136
+ confidence += 10;
137
+ }
138
+
139
+ if (confidence === 0) return null;
140
+
141
+ return {
142
+ name: 'styled-components',
143
+ version: deps['styled-components'],
144
+ confidence: Math.min(confidence, 100),
145
+ evidence,
146
+ };
147
+ }
148
+
149
+ /**
150
+ * Detect Emotion
151
+ */
152
+ function detectEmotion(projectRoot: string, deps: Record<string, string>): DetectionResult | null {
153
+ const evidence: string[] = [];
154
+ let confidence = 0;
155
+
156
+ if (deps['@emotion/react'] || deps['@emotion/styled']) {
157
+ const version = deps['@emotion/react'] || deps['@emotion/styled'];
158
+ evidence.push(`@emotion packages@${version} in dependencies`);
159
+ confidence += 80;
160
+ }
161
+
162
+ if (confidence === 0) return null;
163
+
164
+ return {
165
+ name: 'Emotion',
166
+ version: deps['@emotion/react'] || deps['@emotion/styled'],
167
+ confidence: Math.min(confidence, 100),
168
+ evidence,
169
+ };
170
+ }
171
+
172
+ /**
173
+ * Detect Sass/SCSS
174
+ */
175
+ function detectSass(projectRoot: string, deps: Record<string, string>): DetectionResult | null {
176
+ const evidence: string[] = [];
177
+ let confidence = 0;
178
+
179
+ if (deps.sass) {
180
+ evidence.push(`sass@${deps.sass} in dependencies`);
181
+ confidence += 70;
182
+ } else if (deps['node-sass']) {
183
+ evidence.push(`node-sass@${deps['node-sass']} in dependencies`);
184
+ confidence += 70;
185
+ }
186
+
187
+ if (confidence === 0) return null;
188
+
189
+ return {
190
+ name: 'Sass/SCSS',
191
+ version: deps.sass || deps['node-sass'],
192
+ confidence: Math.min(confidence, 100),
193
+ evidence,
194
+ };
195
+ }
196
+
197
+ /**
198
+ * Detect CSS Modules
199
+ */
200
+ function detectCssModules(projectRoot: string): DetectionResult | null {
201
+ const evidence: string[] = [];
202
+ let confidence = 0;
203
+
204
+ if (hasCssModules(projectRoot)) {
205
+ evidence.push('*.module.css or *.module.scss files found');
206
+ confidence += 80;
207
+ }
208
+
209
+ if (confidence === 0) return null;
210
+
211
+ return {
212
+ name: 'CSS Modules',
213
+ confidence: Math.min(confidence, 100),
214
+ evidence,
215
+ };
216
+ }
217
+
218
+ /**
219
+ * Styling detector
220
+ * Returns the primary styling approach detected
221
+ */
222
+ export const stylingDetector: Detector = {
223
+ category: 'styling',
224
+ name: 'Styling Detector',
225
+
226
+ async detect(projectRoot: string): Promise<DetectionResult | null> {
227
+ const pkg = readPackageJson(projectRoot);
228
+ if (!pkg) {
229
+ // Still check for CSS Modules even without package.json
230
+ return detectCssModules(projectRoot);
231
+ }
232
+
233
+ const deps = getDependencies(pkg);
234
+
235
+ // Priority order for styling detection
236
+ // Tailwind is checked first as it's often the primary styling approach
237
+ const detectors = [
238
+ () => detectTailwind(projectRoot, deps),
239
+ () => detectStyledComponents(projectRoot, deps),
240
+ () => detectEmotion(projectRoot, deps),
241
+ () => detectSass(projectRoot, deps),
242
+ () => detectCssModules(projectRoot),
243
+ ];
244
+
245
+ // Find the highest confidence result
246
+ let bestResult: DetectionResult | null = null;
247
+ let bestConfidence = 0;
248
+
249
+ for (const detector of detectors) {
250
+ const result = detector();
251
+ if (result && result.confidence > bestConfidence) {
252
+ bestResult = result;
253
+ bestConfidence = result.confidence;
254
+ }
255
+ }
256
+
257
+ return bestResult;
258
+ },
259
+ };
260
+
261
+ export default stylingDetector;
@@ -0,0 +1,221 @@
1
+ /**
2
+ * Testing Framework Detector
3
+ * Detects unit testing (Jest, Vitest) and E2E testing (Playwright, Cypress)
4
+ */
5
+
6
+ import { existsSync } from 'node:fs';
7
+ import { join } from 'node:path';
8
+ import type { Detector, DetectionResult } from '../../types.js';
9
+ import {
10
+ readPackageJson,
11
+ getDependencies,
12
+ findConfigFile,
13
+ type PackageJson,
14
+ type DependencyMap,
15
+ } from '../utils.js';
16
+
17
+ /**
18
+ * Detect Vitest
19
+ */
20
+ function detectVitest(projectRoot: string, deps: DependencyMap): DetectionResult | null {
21
+ const evidence: string[] = [];
22
+ let confidence = 0;
23
+
24
+ if (deps.vitest) {
25
+ evidence.push(`vitest@${deps.vitest} in dependencies`);
26
+ confidence += 60;
27
+ }
28
+
29
+ // Check for vitest config
30
+ const configExtensions = ['.config.ts', '.config.js', '.config.mts', '.config.mjs'];
31
+ const configFile = findConfigFile(projectRoot, 'vitest', configExtensions);
32
+ if (configFile) {
33
+ evidence.push(`${configFile} found`);
34
+ confidence += 30;
35
+ }
36
+
37
+ // Vitest can also be configured in vite.config
38
+ if (!configFile && deps.vitest) {
39
+ const viteConfig = findConfigFile(projectRoot, 'vite.config', ['.ts', '.js', '.mjs']);
40
+ if (viteConfig) {
41
+ evidence.push('Vitest likely configured in vite.config');
42
+ confidence += 10;
43
+ }
44
+ }
45
+
46
+ if (confidence === 0) return null;
47
+
48
+ return {
49
+ name: 'Vitest',
50
+ version: deps.vitest,
51
+ confidence: Math.min(confidence, 100),
52
+ evidence,
53
+ };
54
+ }
55
+
56
+ /**
57
+ * Detect Jest
58
+ */
59
+ function detectJest(projectRoot: string, deps: DependencyMap, pkg: PackageJson): DetectionResult | null {
60
+ const evidence: string[] = [];
61
+ let confidence = 0;
62
+
63
+ if (deps.jest) {
64
+ evidence.push(`jest@${deps.jest} in dependencies`);
65
+ confidence += 50;
66
+ }
67
+
68
+ // Check for jest config files
69
+ const configExtensions = ['.config.js', '.config.ts', '.config.mjs', '.config.cjs'];
70
+ const configFile = findConfigFile(projectRoot, 'jest', configExtensions);
71
+ if (configFile) {
72
+ evidence.push(`${configFile} found`);
73
+ confidence += 30;
74
+ }
75
+
76
+ // Check for jest field in package.json
77
+ if (pkg.jest) {
78
+ evidence.push('jest config in package.json');
79
+ confidence += 20;
80
+ }
81
+
82
+ // Check for related packages
83
+ if (deps['@types/jest']) {
84
+ evidence.push('@types/jest in dependencies');
85
+ confidence += 10;
86
+ }
87
+ if (deps['ts-jest']) {
88
+ evidence.push('ts-jest in dependencies');
89
+ confidence += 10;
90
+ }
91
+
92
+ if (confidence === 0) return null;
93
+
94
+ return {
95
+ name: 'Jest',
96
+ version: deps.jest,
97
+ confidence: Math.min(confidence, 100),
98
+ evidence,
99
+ };
100
+ }
101
+
102
+ /**
103
+ * Detect Playwright
104
+ */
105
+ function detectPlaywright(projectRoot: string, deps: DependencyMap): DetectionResult | null {
106
+ const evidence: string[] = [];
107
+ let confidence = 0;
108
+
109
+ if (deps['@playwright/test']) {
110
+ evidence.push(`@playwright/test@${deps['@playwright/test']} in dependencies`);
111
+ confidence += 60;
112
+ } else if (deps.playwright) {
113
+ evidence.push(`playwright@${deps.playwright} in dependencies`);
114
+ confidence += 50;
115
+ }
116
+
117
+ // Check for playwright config
118
+ const configExtensions = ['.config.ts', '.config.js', '.config.mjs'];
119
+ const configFile = findConfigFile(projectRoot, 'playwright', configExtensions);
120
+ if (configFile) {
121
+ evidence.push(`${configFile} found`);
122
+ confidence += 30;
123
+ }
124
+
125
+ if (confidence === 0) return null;
126
+
127
+ return {
128
+ name: 'Playwright',
129
+ version: deps['@playwright/test'] || deps.playwright,
130
+ confidence: Math.min(confidence, 100),
131
+ evidence,
132
+ };
133
+ }
134
+
135
+ /**
136
+ * Detect Cypress
137
+ */
138
+ function detectCypress(projectRoot: string, deps: DependencyMap): DetectionResult | null {
139
+ const evidence: string[] = [];
140
+ let confidence = 0;
141
+
142
+ if (deps.cypress) {
143
+ evidence.push(`cypress@${deps.cypress} in dependencies`);
144
+ confidence += 60;
145
+ }
146
+
147
+ // Check for cypress config
148
+ const configExtensions = ['.config.ts', '.config.js', '.config.mjs', '.config.cjs'];
149
+ const configFile = findConfigFile(projectRoot, 'cypress', configExtensions);
150
+ if (configFile) {
151
+ evidence.push(`${configFile} found`);
152
+ confidence += 30;
153
+ }
154
+
155
+ // Check for cypress folder
156
+ const cypressFolder = join(projectRoot, 'cypress');
157
+ if (existsSync(cypressFolder)) {
158
+ evidence.push('cypress/ folder found');
159
+ confidence += 10;
160
+ }
161
+
162
+ if (confidence === 0) return null;
163
+
164
+ return {
165
+ name: 'Cypress',
166
+ version: deps.cypress,
167
+ confidence: Math.min(confidence, 100),
168
+ evidence,
169
+ };
170
+ }
171
+
172
+ /**
173
+ * Testing framework detector
174
+ * Returns both unit and e2e test frameworks
175
+ */
176
+ export const testingDetector: Detector = {
177
+ category: 'testing',
178
+ name: 'Testing Framework Detector',
179
+
180
+ async detect(projectRoot: string): Promise<DetectionResult[] | null> {
181
+ const pkg = readPackageJson(projectRoot);
182
+ if (!pkg) {
183
+ return null;
184
+ }
185
+
186
+ const deps = getDependencies(pkg);
187
+ const results: DetectionResult[] = [];
188
+
189
+ // Detect unit testing frameworks
190
+ // Prefer Vitest if both are present (as Vitest is often added later)
191
+ const vitest = detectVitest(projectRoot, deps);
192
+ const jest = detectJest(projectRoot, deps, pkg);
193
+
194
+ if (vitest && vitest.confidence >= 40) {
195
+ results.push({ ...vitest, variant: 'unit' });
196
+ } else if (jest && jest.confidence >= 40) {
197
+ results.push({ ...jest, variant: 'unit' });
198
+ }
199
+
200
+ // Detect E2E testing frameworks
201
+ const playwright = detectPlaywright(projectRoot, deps);
202
+ const cypress = detectCypress(projectRoot, deps);
203
+
204
+ // If both are present, include the one with higher confidence
205
+ if (playwright && cypress) {
206
+ if (playwright.confidence >= cypress.confidence) {
207
+ results.push({ ...playwright, variant: 'e2e' });
208
+ } else {
209
+ results.push({ ...cypress, variant: 'e2e' });
210
+ }
211
+ } else if (playwright && playwright.confidence >= 40) {
212
+ results.push({ ...playwright, variant: 'e2e' });
213
+ } else if (cypress && cypress.confidence >= 40) {
214
+ results.push({ ...cypress, variant: 'e2e' });
215
+ }
216
+
217
+ return results.length > 0 ? results : null;
218
+ },
219
+ };
220
+
221
+ export default testingDetector;