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,180 @@
1
+ /**
2
+ * ORM Detector
3
+ * Detects ORMs: Prisma, Drizzle
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
+ * 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
+ * Detect Prisma
50
+ */
51
+ function detectPrisma(projectRoot: string, deps: Record<string, string>): DetectionResult | null {
52
+ const evidence: string[] = [];
53
+ let confidence = 0;
54
+
55
+ // Check for @prisma/client
56
+ if (deps['@prisma/client']) {
57
+ evidence.push(`@prisma/client@${deps['@prisma/client']} in dependencies`);
58
+ confidence += 50;
59
+ }
60
+
61
+ // Check for prisma CLI
62
+ if (deps.prisma) {
63
+ evidence.push(`prisma@${deps.prisma} in devDependencies`);
64
+ confidence += 30;
65
+ }
66
+
67
+ // Check for prisma schema file
68
+ const schemaPath = join(projectRoot, 'prisma', 'schema.prisma');
69
+ if (existsSync(schemaPath)) {
70
+ evidence.push('prisma/schema.prisma found');
71
+ confidence += 30;
72
+ }
73
+
74
+ // Also check for schema in root (less common but valid)
75
+ const rootSchema = join(projectRoot, 'schema.prisma');
76
+ if (existsSync(rootSchema)) {
77
+ evidence.push('schema.prisma found in root');
78
+ confidence += 20;
79
+ }
80
+
81
+ if (confidence === 0) return null;
82
+
83
+ return {
84
+ name: 'Prisma',
85
+ version: deps['@prisma/client'] || deps.prisma,
86
+ confidence: Math.min(confidence, 100),
87
+ evidence,
88
+ };
89
+ }
90
+
91
+ /**
92
+ * Detect Drizzle ORM
93
+ */
94
+ function detectDrizzle(projectRoot: string, deps: Record<string, string>): DetectionResult | null {
95
+ const evidence: string[] = [];
96
+ let confidence = 0;
97
+
98
+ // Check for drizzle-orm
99
+ if (deps['drizzle-orm']) {
100
+ evidence.push(`drizzle-orm@${deps['drizzle-orm']} in dependencies`);
101
+ confidence += 60;
102
+ }
103
+
104
+ // Check for drizzle-kit (migration tool)
105
+ if (deps['drizzle-kit']) {
106
+ evidence.push(`drizzle-kit@${deps['drizzle-kit']} in dependencies`);
107
+ confidence += 20;
108
+ }
109
+
110
+ // Check for drizzle config file
111
+ const configExtensions = ['.config.ts', '.config.js', '.config.mjs'];
112
+ const configFile = findConfigFile(projectRoot, 'drizzle', configExtensions);
113
+ if (configFile) {
114
+ evidence.push(`${configFile} found`);
115
+ confidence += 20;
116
+ }
117
+
118
+ // Detect database adapter
119
+ let variant: string | undefined;
120
+ if (deps['@planetscale/database'] || deps['drizzle-orm/planetscale-serverless']) {
121
+ variant = 'planetscale';
122
+ } else if (deps['@neondatabase/serverless']) {
123
+ variant = 'neon';
124
+ } else if (deps['@libsql/client'] || deps['better-sqlite3']) {
125
+ variant = 'sqlite';
126
+ } else if (deps.pg || deps.postgres) {
127
+ variant = 'postgres';
128
+ } else if (deps.mysql2) {
129
+ variant = 'mysql';
130
+ }
131
+
132
+ if (confidence === 0) return null;
133
+
134
+ return {
135
+ name: 'Drizzle',
136
+ version: deps['drizzle-orm'],
137
+ variant,
138
+ confidence: Math.min(confidence, 100),
139
+ evidence,
140
+ };
141
+ }
142
+
143
+ /**
144
+ * ORM detector
145
+ * Returns the primary ORM detected
146
+ */
147
+ export const ormDetector: Detector = {
148
+ category: 'orm',
149
+ name: 'ORM Detector',
150
+
151
+ async detect(projectRoot: string): Promise<DetectionResult | null> {
152
+ const pkg = readPackageJson(projectRoot);
153
+ if (!pkg) {
154
+ return null;
155
+ }
156
+
157
+ const deps = getDependencies(pkg);
158
+
159
+ // Check both ORMs and return the one with higher confidence
160
+ const prisma = detectPrisma(projectRoot, deps);
161
+ const drizzle = detectDrizzle(projectRoot, deps);
162
+
163
+ // Return the one with higher confidence, or first one if equal
164
+ if (prisma && drizzle) {
165
+ return prisma.confidence >= drizzle.confidence ? prisma : drizzle;
166
+ }
167
+
168
+ if (prisma && prisma.confidence >= 40) {
169
+ return prisma;
170
+ }
171
+
172
+ if (drizzle && drizzle.confidence >= 40) {
173
+ return drizzle;
174
+ }
175
+
176
+ return null;
177
+ },
178
+ };
179
+
180
+ export default ormDetector;
@@ -0,0 +1,244 @@
1
+ /**
2
+ * Form Handling Detector
3
+ * Detects: React Hook Form, Formik, Zod, Yup
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 React Hook Form
37
+ */
38
+ function detectReactHookForm(deps: Record<string, string>): DetectionResult | null {
39
+ const evidence: string[] = [];
40
+ let confidence = 0;
41
+
42
+ if (deps['react-hook-form']) {
43
+ evidence.push(`react-hook-form@${deps['react-hook-form']} in dependencies`);
44
+ confidence += 80;
45
+ }
46
+
47
+ // Check for @hookform/resolvers (validation integrations)
48
+ if (deps['@hookform/resolvers']) {
49
+ evidence.push('@hookform/resolvers found');
50
+ confidence += 10;
51
+ }
52
+
53
+ // Check for @hookform/devtools
54
+ if (deps['@hookform/devtools']) {
55
+ evidence.push('@hookform/devtools found');
56
+ confidence += 5;
57
+ }
58
+
59
+ if (confidence === 0) return null;
60
+
61
+ return {
62
+ name: 'React Hook Form',
63
+ version: deps['react-hook-form'],
64
+ confidence: Math.min(confidence, 100),
65
+ evidence,
66
+ };
67
+ }
68
+
69
+ /**
70
+ * Detect Formik
71
+ */
72
+ function detectFormik(deps: Record<string, string>): DetectionResult | null {
73
+ const evidence: string[] = [];
74
+ let confidence = 0;
75
+
76
+ if (deps.formik) {
77
+ evidence.push(`formik@${deps.formik} in dependencies`);
78
+ confidence += 80;
79
+ }
80
+
81
+ if (confidence === 0) return null;
82
+
83
+ return {
84
+ name: 'Formik',
85
+ version: deps.formik,
86
+ confidence: Math.min(confidence, 100),
87
+ evidence,
88
+ };
89
+ }
90
+
91
+ /**
92
+ * Detect Zod
93
+ */
94
+ function detectZod(deps: Record<string, string>): DetectionResult | null {
95
+ const evidence: string[] = [];
96
+ let confidence = 0;
97
+
98
+ if (deps.zod) {
99
+ evidence.push(`zod@${deps.zod} in dependencies`);
100
+ confidence += 80;
101
+ }
102
+
103
+ // Check for zod-to-json-schema
104
+ if (deps['zod-to-json-schema']) {
105
+ evidence.push('zod-to-json-schema found');
106
+ confidence += 10;
107
+ }
108
+
109
+ if (confidence === 0) return null;
110
+
111
+ return {
112
+ name: 'Zod',
113
+ version: deps.zod,
114
+ variant: 'validation',
115
+ confidence: Math.min(confidence, 100),
116
+ evidence,
117
+ };
118
+ }
119
+
120
+ /**
121
+ * Detect Yup
122
+ */
123
+ function detectYup(deps: Record<string, string>): DetectionResult | null {
124
+ const evidence: string[] = [];
125
+ let confidence = 0;
126
+
127
+ if (deps.yup) {
128
+ evidence.push(`yup@${deps.yup} in dependencies`);
129
+ confidence += 80;
130
+ }
131
+
132
+ if (confidence === 0) return null;
133
+
134
+ return {
135
+ name: 'Yup',
136
+ version: deps.yup,
137
+ variant: 'validation',
138
+ confidence: Math.min(confidence, 100),
139
+ evidence,
140
+ };
141
+ }
142
+
143
+ /**
144
+ * Detect Valibot (lightweight alternative to Zod)
145
+ */
146
+ function detectValibot(deps: Record<string, string>): DetectionResult | null {
147
+ const evidence: string[] = [];
148
+ let confidence = 0;
149
+
150
+ if (deps.valibot) {
151
+ evidence.push(`valibot@${deps.valibot} in dependencies`);
152
+ confidence += 80;
153
+ }
154
+
155
+ if (confidence === 0) return null;
156
+
157
+ return {
158
+ name: 'Valibot',
159
+ version: deps.valibot,
160
+ variant: 'validation',
161
+ confidence: Math.min(confidence, 100),
162
+ evidence,
163
+ };
164
+ }
165
+
166
+ /**
167
+ * Detect TanStack Form
168
+ */
169
+ function detectTanStackForm(deps: Record<string, string>): DetectionResult | null {
170
+ const evidence: string[] = [];
171
+ let confidence = 0;
172
+
173
+ if (deps['@tanstack/react-form']) {
174
+ evidence.push(`@tanstack/react-form@${deps['@tanstack/react-form']} in dependencies`);
175
+ confidence += 80;
176
+ }
177
+
178
+ if (deps['@tanstack/vue-form']) {
179
+ evidence.push(`@tanstack/vue-form@${deps['@tanstack/vue-form']} in dependencies`);
180
+ confidence += 80;
181
+ }
182
+
183
+ if (confidence === 0) return null;
184
+
185
+ return {
186
+ name: 'TanStack Form',
187
+ version: deps['@tanstack/react-form'] || deps['@tanstack/vue-form'],
188
+ confidence: Math.min(confidence, 100),
189
+ evidence,
190
+ };
191
+ }
192
+
193
+ /**
194
+ * Form handling detector
195
+ * Returns all detected form/validation libraries (projects often use multiple)
196
+ */
197
+ export const formHandlingDetector: Detector = {
198
+ category: 'formHandling',
199
+ name: 'Form Handling Detector',
200
+
201
+ async detect(projectRoot: string): Promise<DetectionResult[] | null> {
202
+ const pkg = readPackageJson(projectRoot);
203
+ if (!pkg) {
204
+ return null;
205
+ }
206
+
207
+ const deps = getDependencies(pkg);
208
+ const results: DetectionResult[] = [];
209
+
210
+ // Check form libraries
211
+ const formDetectors = [
212
+ () => detectReactHookForm(deps),
213
+ () => detectFormik(deps),
214
+ () => detectTanStackForm(deps),
215
+ ];
216
+
217
+ // Check validation libraries
218
+ const validationDetectors = [
219
+ () => detectZod(deps),
220
+ () => detectYup(deps),
221
+ () => detectValibot(deps),
222
+ ];
223
+
224
+ // Add form libraries
225
+ for (const detector of formDetectors) {
226
+ const result = detector();
227
+ if (result && result.confidence >= 40) {
228
+ results.push(result);
229
+ }
230
+ }
231
+
232
+ // Add validation libraries
233
+ for (const detector of validationDetectors) {
234
+ const result = detector();
235
+ if (result && result.confidence >= 40) {
236
+ results.push(result);
237
+ }
238
+ }
239
+
240
+ return results.length > 0 ? results : null;
241
+ },
242
+ };
243
+
244
+ export default formHandlingDetector;
@@ -0,0 +1,261 @@
1
+ /**
2
+ * State Management Detector
3
+ * Detects: Redux, Zustand, Jotai, Pinia, Recoil, MobX
4
+ */
5
+
6
+ import type { Detector, DetectionResult } from '../../types.js';
7
+ import {
8
+ readPackageJson,
9
+ getDependencies,
10
+ type DependencyMap,
11
+ } from '../utils.js';
12
+
13
+ /**
14
+ * Detect Redux / Redux Toolkit
15
+ */
16
+ function detectRedux(deps: DependencyMap): DetectionResult | null {
17
+ const evidence: string[] = [];
18
+ let confidence = 0;
19
+ let variant: string | undefined;
20
+
21
+ // Check for Redux Toolkit (modern approach)
22
+ if (deps['@reduxjs/toolkit']) {
23
+ evidence.push(`@reduxjs/toolkit@${deps['@reduxjs/toolkit']} in dependencies`);
24
+ confidence += 70;
25
+ variant = 'toolkit';
26
+ }
27
+
28
+ // Check for classic redux
29
+ if (deps.redux) {
30
+ evidence.push(`redux@${deps.redux} in dependencies`);
31
+ confidence += 50;
32
+ if (!variant) variant = 'classic';
33
+ }
34
+
35
+ // Check for react-redux bindings
36
+ if (deps['react-redux']) {
37
+ evidence.push(`react-redux@${deps['react-redux']} in dependencies`);
38
+ confidence += 20;
39
+ }
40
+
41
+ // Check for redux-saga or redux-thunk
42
+ if (deps['redux-saga']) {
43
+ evidence.push('redux-saga detected');
44
+ confidence += 10;
45
+ }
46
+ if (deps['redux-thunk']) {
47
+ evidence.push('redux-thunk detected');
48
+ confidence += 10;
49
+ }
50
+
51
+ if (confidence === 0) return null;
52
+
53
+ return {
54
+ name: 'Redux',
55
+ version: deps['@reduxjs/toolkit'] || deps.redux,
56
+ variant,
57
+ confidence: Math.min(confidence, 100),
58
+ evidence,
59
+ };
60
+ }
61
+
62
+ /**
63
+ * Detect Zustand
64
+ */
65
+ function detectZustand(deps: DependencyMap): DetectionResult | null {
66
+ const evidence: string[] = [];
67
+ let confidence = 0;
68
+
69
+ if (deps.zustand) {
70
+ evidence.push(`zustand@${deps.zustand} in dependencies`);
71
+ confidence += 90;
72
+ }
73
+
74
+ if (confidence === 0) return null;
75
+
76
+ return {
77
+ name: 'Zustand',
78
+ version: deps.zustand,
79
+ confidence: Math.min(confidence, 100),
80
+ evidence,
81
+ };
82
+ }
83
+
84
+ /**
85
+ * Detect Jotai
86
+ */
87
+ function detectJotai(deps: DependencyMap): DetectionResult | null {
88
+ const evidence: string[] = [];
89
+ let confidence = 0;
90
+
91
+ if (deps.jotai) {
92
+ evidence.push(`jotai@${deps.jotai} in dependencies`);
93
+ confidence += 90;
94
+ }
95
+
96
+ // Check for jotai utils
97
+ if (deps['jotai-devtools']) {
98
+ evidence.push('jotai-devtools detected');
99
+ confidence += 10;
100
+ }
101
+
102
+ if (confidence === 0) return null;
103
+
104
+ return {
105
+ name: 'Jotai',
106
+ version: deps.jotai,
107
+ confidence: Math.min(confidence, 100),
108
+ evidence,
109
+ };
110
+ }
111
+
112
+ /**
113
+ * Detect Pinia (Vue state management)
114
+ */
115
+ function detectPinia(deps: DependencyMap): DetectionResult | null {
116
+ const evidence: string[] = [];
117
+ let confidence = 0;
118
+
119
+ if (deps.pinia) {
120
+ evidence.push(`pinia@${deps.pinia} in dependencies`);
121
+ confidence += 90;
122
+ }
123
+
124
+ // Check for pinia plugins
125
+ if (deps['pinia-plugin-persistedstate']) {
126
+ evidence.push('pinia-plugin-persistedstate detected');
127
+ confidence += 10;
128
+ }
129
+
130
+ if (confidence === 0) return null;
131
+
132
+ return {
133
+ name: 'Pinia',
134
+ version: deps.pinia,
135
+ confidence: Math.min(confidence, 100),
136
+ evidence,
137
+ };
138
+ }
139
+
140
+ /**
141
+ * Detect Recoil
142
+ */
143
+ function detectRecoil(deps: DependencyMap): DetectionResult | null {
144
+ const evidence: string[] = [];
145
+ let confidence = 0;
146
+
147
+ if (deps.recoil) {
148
+ evidence.push(`recoil@${deps.recoil} in dependencies`);
149
+ confidence += 90;
150
+ }
151
+
152
+ if (confidence === 0) return null;
153
+
154
+ return {
155
+ name: 'Recoil',
156
+ version: deps.recoil,
157
+ confidence: Math.min(confidence, 100),
158
+ evidence,
159
+ };
160
+ }
161
+
162
+ /**
163
+ * Detect MobX
164
+ */
165
+ function detectMobX(deps: DependencyMap): DetectionResult | null {
166
+ const evidence: string[] = [];
167
+ let confidence = 0;
168
+
169
+ if (deps.mobx) {
170
+ evidence.push(`mobx@${deps.mobx} in dependencies`);
171
+ confidence += 70;
172
+ }
173
+
174
+ // Check for mobx-react bindings
175
+ if (deps['mobx-react'] || deps['mobx-react-lite']) {
176
+ evidence.push('mobx-react bindings detected');
177
+ confidence += 20;
178
+ }
179
+
180
+ // Check for mobx-state-tree
181
+ if (deps['mobx-state-tree']) {
182
+ evidence.push('mobx-state-tree detected');
183
+ confidence += 10;
184
+ }
185
+
186
+ if (confidence === 0) return null;
187
+
188
+ return {
189
+ name: 'MobX',
190
+ version: deps.mobx,
191
+ confidence: Math.min(confidence, 100),
192
+ evidence,
193
+ };
194
+ }
195
+
196
+ /**
197
+ * Detect Valtio
198
+ */
199
+ function detectValtio(deps: DependencyMap): DetectionResult | null {
200
+ const evidence: string[] = [];
201
+ let confidence = 0;
202
+
203
+ if (deps.valtio) {
204
+ evidence.push(`valtio@${deps.valtio} in dependencies`);
205
+ confidence += 90;
206
+ }
207
+
208
+ if (confidence === 0) return null;
209
+
210
+ return {
211
+ name: 'Valtio',
212
+ version: deps.valtio,
213
+ confidence: Math.min(confidence, 100),
214
+ evidence,
215
+ };
216
+ }
217
+
218
+ /**
219
+ * State management detector
220
+ * Returns the primary state management solution detected
221
+ */
222
+ export const stateManagementDetector: Detector = {
223
+ category: 'stateManagement',
224
+ name: 'State Management Detector',
225
+
226
+ async detect(projectRoot: string): Promise<DetectionResult | null> {
227
+ const pkg = readPackageJson(projectRoot);
228
+ if (!pkg) {
229
+ return null;
230
+ }
231
+
232
+ const deps = getDependencies(pkg);
233
+
234
+ // Modern/lightweight solutions preferred in detection order
235
+ const detectors = [
236
+ () => detectZustand(deps),
237
+ () => detectJotai(deps),
238
+ () => detectValtio(deps),
239
+ () => detectPinia(deps),
240
+ () => detectRecoil(deps),
241
+ () => detectRedux(deps),
242
+ () => detectMobX(deps),
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 && bestResult.confidence >= 40 ? bestResult : null;
258
+ },
259
+ };
260
+
261
+ export default stateManagementDetector;