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,293 @@
1
+ /**
2
+ * File Writer
3
+ * Creates .ralph directory structure and writes processed templates
4
+ */
5
+
6
+ import { mkdir, writeFile, readFile, copyFile, stat, rename, readdir } from 'node:fs/promises';
7
+ import { join, dirname, basename } from 'node:path';
8
+ import { existsSync } from 'node:fs';
9
+
10
+ /**
11
+ * Options for file writing
12
+ */
13
+ export interface WriteOptions {
14
+ /** How to handle existing files: 'backup', 'skip', 'overwrite' */
15
+ existingFiles: 'backup' | 'skip' | 'overwrite';
16
+ /** Whether to create backup files */
17
+ createBackups: boolean;
18
+ /** Verbose output */
19
+ verbose: boolean;
20
+ }
21
+
22
+ /**
23
+ * Default write options
24
+ */
25
+ export const DEFAULT_WRITE_OPTIONS: WriteOptions = {
26
+ existingFiles: 'backup',
27
+ createBackups: true,
28
+ verbose: false,
29
+ };
30
+
31
+ /**
32
+ * Result of a write operation
33
+ */
34
+ export interface WriteResult {
35
+ /** Path that was written */
36
+ path: string;
37
+ /** Whether the write was successful */
38
+ success: boolean;
39
+ /** Action taken: created, backed_up, skipped, overwritten */
40
+ action: 'created' | 'backed_up' | 'skipped' | 'overwritten' | 'error';
41
+ /** Error message if any */
42
+ error?: string;
43
+ /** Backup path if created */
44
+ backupPath?: string;
45
+ }
46
+
47
+ /**
48
+ * Summary of all write operations
49
+ */
50
+ export interface WriteSummary {
51
+ /** Total files processed */
52
+ total: number;
53
+ /** Files created (new) */
54
+ created: number;
55
+ /** Files backed up and replaced */
56
+ backedUp: number;
57
+ /** Files skipped (already existed) */
58
+ skipped: number;
59
+ /** Files overwritten */
60
+ overwritten: number;
61
+ /** Files that failed */
62
+ errors: number;
63
+ /** Individual results */
64
+ results: WriteResult[];
65
+ }
66
+
67
+ /**
68
+ * Create a directory and all parent directories
69
+ */
70
+ export async function ensureDir(dirPath: string): Promise<void> {
71
+ await mkdir(dirPath, { recursive: true });
72
+ }
73
+
74
+ /**
75
+ * Check if a file exists
76
+ */
77
+ export async function fileExists(filePath: string): Promise<boolean> {
78
+ try {
79
+ await stat(filePath);
80
+ return true;
81
+ } catch {
82
+ return false;
83
+ }
84
+ }
85
+
86
+ /**
87
+ * Create a backup of an existing file
88
+ */
89
+ export async function backupFile(filePath: string): Promise<string> {
90
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
91
+ const dir = dirname(filePath);
92
+ const name = basename(filePath);
93
+ const backupPath = join(dir, `.${name}.backup-${timestamp}`);
94
+
95
+ await copyFile(filePath, backupPath);
96
+ return backupPath;
97
+ }
98
+
99
+ /**
100
+ * Write a single file with options
101
+ */
102
+ export async function writeFileWithOptions(
103
+ filePath: string,
104
+ content: string,
105
+ options: WriteOptions = DEFAULT_WRITE_OPTIONS
106
+ ): Promise<WriteResult> {
107
+ const result: WriteResult = {
108
+ path: filePath,
109
+ success: false,
110
+ action: 'created',
111
+ };
112
+
113
+ try {
114
+ // Ensure directory exists
115
+ await ensureDir(dirname(filePath));
116
+
117
+ // Check if file exists
118
+ const exists = await fileExists(filePath);
119
+
120
+ if (exists) {
121
+ switch (options.existingFiles) {
122
+ case 'skip':
123
+ result.action = 'skipped';
124
+ result.success = true;
125
+ return result;
126
+
127
+ case 'backup':
128
+ if (options.createBackups) {
129
+ result.backupPath = await backupFile(filePath);
130
+ result.action = 'backed_up';
131
+ } else {
132
+ result.action = 'overwritten';
133
+ }
134
+ break;
135
+
136
+ case 'overwrite':
137
+ result.action = 'overwritten';
138
+ break;
139
+ }
140
+ }
141
+
142
+ // Write the file
143
+ await writeFile(filePath, content, 'utf-8');
144
+ result.success = true;
145
+
146
+ if (options.verbose) {
147
+ console.log(` ${result.action}: ${filePath}`);
148
+ }
149
+ } catch (error) {
150
+ result.action = 'error';
151
+ result.error = error instanceof Error ? error.message : String(error);
152
+ result.success = false;
153
+ }
154
+
155
+ return result;
156
+ }
157
+
158
+ /**
159
+ * Write multiple files from a Map
160
+ */
161
+ export async function writeFiles(
162
+ files: Map<string, string>,
163
+ baseDir: string,
164
+ options: WriteOptions = DEFAULT_WRITE_OPTIONS
165
+ ): Promise<WriteSummary> {
166
+ const summary: WriteSummary = {
167
+ total: files.size,
168
+ created: 0,
169
+ backedUp: 0,
170
+ skipped: 0,
171
+ overwritten: 0,
172
+ errors: 0,
173
+ results: [],
174
+ };
175
+
176
+ for (const [relativePath, content] of files) {
177
+ const fullPath = join(baseDir, relativePath);
178
+ const result = await writeFileWithOptions(fullPath, content, options);
179
+ summary.results.push(result);
180
+
181
+ switch (result.action) {
182
+ case 'created':
183
+ summary.created++;
184
+ break;
185
+ case 'backed_up':
186
+ summary.backedUp++;
187
+ break;
188
+ case 'skipped':
189
+ summary.skipped++;
190
+ break;
191
+ case 'overwritten':
192
+ summary.overwritten++;
193
+ break;
194
+ case 'error':
195
+ summary.errors++;
196
+ break;
197
+ }
198
+ }
199
+
200
+ return summary;
201
+ }
202
+
203
+ /**
204
+ * Directory structure for .ralph
205
+ */
206
+ export const RALPH_DIRECTORY_STRUCTURE = {
207
+ root: '.ralph',
208
+ directories: [
209
+ '.ralph/prompts',
210
+ '.ralph/guides',
211
+ '.ralph/specs',
212
+ '.ralph/scripts',
213
+ ],
214
+ };
215
+
216
+ /**
217
+ * Create the .ralph directory structure
218
+ */
219
+ export async function createDirectoryStructure(projectRoot: string): Promise<void> {
220
+ for (const dir of RALPH_DIRECTORY_STRUCTURE.directories) {
221
+ await ensureDir(join(projectRoot, dir));
222
+ }
223
+ }
224
+
225
+ /**
226
+ * Map template output paths to their final locations in .ralph
227
+ */
228
+ export function mapTemplateOutputPaths(templateOutputs: Map<string, string>): Map<string, string> {
229
+ const mapped = new Map<string, string>();
230
+
231
+ for (const [outputPath, content] of templateOutputs) {
232
+ // Map template categories to .ralph structure
233
+ let finalPath: string;
234
+
235
+ if (outputPath.startsWith('prompts/')) {
236
+ finalPath = `.ralph/${outputPath}`;
237
+ } else if (outputPath.startsWith('guides/')) {
238
+ finalPath = `.ralph/${outputPath}`;
239
+ } else if (outputPath.startsWith('specs/')) {
240
+ finalPath = `.ralph/${outputPath}`;
241
+ } else if (outputPath.startsWith('scripts/')) {
242
+ // Scripts go to .ralph/scripts/
243
+ finalPath = `.ralph/${outputPath}`;
244
+ } else if (outputPath.startsWith('config/')) {
245
+ // ralph.config.js goes to project root
246
+ finalPath = outputPath.replace('config/', '');
247
+ } else if (outputPath.startsWith('root/')) {
248
+ // Root files go to .ralph/
249
+ finalPath = `.ralph/${outputPath.replace('root/', '')}`;
250
+ } else {
251
+ // Default: put in .ralph/
252
+ finalPath = `.ralph/${outputPath}`;
253
+ }
254
+
255
+ mapped.set(finalPath, content);
256
+ }
257
+
258
+ return mapped;
259
+ }
260
+
261
+ /**
262
+ * Format write summary for display
263
+ */
264
+ export function formatWriteSummary(summary: WriteSummary): string {
265
+ const lines: string[] = [];
266
+
267
+ lines.push('Write Summary:');
268
+ lines.push(` Total files: ${summary.total}`);
269
+ lines.push(` Created: ${summary.created}`);
270
+
271
+ if (summary.backedUp > 0) {
272
+ lines.push(` Backed up & updated: ${summary.backedUp}`);
273
+ }
274
+
275
+ if (summary.skipped > 0) {
276
+ lines.push(` Skipped (existing): ${summary.skipped}`);
277
+ }
278
+
279
+ if (summary.overwritten > 0) {
280
+ lines.push(` Overwritten: ${summary.overwritten}`);
281
+ }
282
+
283
+ if (summary.errors > 0) {
284
+ lines.push(` Errors: ${summary.errors}`);
285
+ for (const result of summary.results) {
286
+ if (result.action === 'error') {
287
+ lines.push(` - ${result.path}: ${result.error}`);
288
+ }
289
+ }
290
+ }
291
+
292
+ return lines.join('\n');
293
+ }
package/src/index.ts ADDED
@@ -0,0 +1,41 @@
1
+ import { createCli } from './cli.js';
2
+
3
+ /**
4
+ * Main entry point for the Ralph CLI
5
+ */
6
+ export async function main(): Promise<void> {
7
+ const program = createCli();
8
+ await program.parseAsync(process.argv);
9
+ }
10
+
11
+ // Export for programmatic use
12
+ export { createCli } from './cli.js';
13
+ export { displayHeader } from './utils/header.js';
14
+ export { logger } from './utils/logger.js';
15
+
16
+ // Export scanner
17
+ export { Scanner, scanProject, formatScanResult } from './scanner/index.js';
18
+ export type {
19
+ DetectionResult,
20
+ DetectedStack,
21
+ ScanResult,
22
+ ScannerOptions,
23
+ } from './scanner/index.js';
24
+
25
+ // Export generator
26
+ export {
27
+ Generator,
28
+ generateRalph,
29
+ formatGenerationResult,
30
+ extractVariables,
31
+ generateConfig,
32
+ generateConfigFile,
33
+ } from './generator/index.js';
34
+ export type {
35
+ GeneratorOptions,
36
+ GenerationResult,
37
+ TemplateVariables,
38
+ RalphConfig,
39
+ WriteOptions,
40
+ WriteSummary,
41
+ } from './generator/index.js';
@@ -0,0 +1,332 @@
1
+ /**
2
+ * Framework Detector
3
+ * Detects web frameworks: Next.js, React, Vue, Svelte, Remix, Astro
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 DependencyMap,
14
+ } from '../utils.js';
15
+
16
+ /**
17
+ * Detect Next.js variant (app router vs pages router)
18
+ */
19
+ function detectNextJsVariant(projectRoot: string): string | undefined {
20
+ const appDir = join(projectRoot, 'app');
21
+ const srcAppDir = join(projectRoot, 'src', 'app');
22
+
23
+ if (existsSync(appDir) || existsSync(srcAppDir)) {
24
+ return 'app-router';
25
+ }
26
+
27
+ const pagesDir = join(projectRoot, 'pages');
28
+ const srcPagesDir = join(projectRoot, 'src', 'pages');
29
+
30
+ if (existsSync(pagesDir) || existsSync(srcPagesDir)) {
31
+ return 'pages-router';
32
+ }
33
+
34
+ return undefined;
35
+ }
36
+
37
+ /**
38
+ * Detect Next.js
39
+ */
40
+ function detectNextJs(projectRoot: string, deps: DependencyMap): DetectionResult | null {
41
+ const evidence: string[] = [];
42
+ let confidence = 0;
43
+
44
+ // Check for next dependency
45
+ if (deps.next) {
46
+ evidence.push(`next@${deps.next} in dependencies`);
47
+ confidence += 60;
48
+ }
49
+
50
+ // Check for next.config.* files
51
+ const configExtensions = ['.js', '.mjs', '.ts'];
52
+ const configFile = findConfigFile(projectRoot, 'next.config', configExtensions);
53
+ if (configFile) {
54
+ evidence.push(`${configFile} found`);
55
+ confidence += 30;
56
+ }
57
+
58
+ if (confidence === 0) return null;
59
+
60
+ const variant = detectNextJsVariant(projectRoot);
61
+ if (variant) {
62
+ evidence.push(`${variant} detected`);
63
+ confidence += 10;
64
+ }
65
+
66
+ return {
67
+ name: 'Next.js',
68
+ version: deps.next,
69
+ variant,
70
+ confidence: Math.min(confidence, 100),
71
+ evidence,
72
+ };
73
+ }
74
+
75
+ /**
76
+ * Detect Vue/Nuxt
77
+ */
78
+ function detectVue(projectRoot: string, deps: DependencyMap): DetectionResult | null {
79
+ const evidence: string[] = [];
80
+ let confidence = 0;
81
+
82
+ // Check for nuxt (takes precedence as it includes Vue)
83
+ if (deps.nuxt) {
84
+ evidence.push(`nuxt@${deps.nuxt} in dependencies`);
85
+ confidence += 70;
86
+
87
+ const nuxtConfig = findConfigFile(projectRoot, 'nuxt.config', ['.js', '.ts']);
88
+ if (nuxtConfig) {
89
+ evidence.push(`${nuxtConfig} found`);
90
+ confidence += 30;
91
+ }
92
+
93
+ return {
94
+ name: 'Nuxt',
95
+ version: deps.nuxt,
96
+ confidence: Math.min(confidence, 100),
97
+ evidence,
98
+ };
99
+ }
100
+
101
+ // Check for vue
102
+ if (deps.vue) {
103
+ evidence.push(`vue@${deps.vue} in dependencies`);
104
+ confidence += 50;
105
+
106
+ // Check for Vue-specific configs
107
+ const vueConfig = findConfigFile(projectRoot, 'vue.config', ['.js', '.ts']);
108
+ if (vueConfig) {
109
+ evidence.push(`${vueConfig} found`);
110
+ confidence += 20;
111
+ }
112
+
113
+ const viteConfig = findConfigFile(projectRoot, 'vite.config', ['.js', '.ts', '.mjs']);
114
+ if (viteConfig && deps['@vitejs/plugin-vue']) {
115
+ evidence.push('Vite + Vue plugin detected');
116
+ confidence += 20;
117
+ }
118
+
119
+ if (confidence === 0) return null;
120
+
121
+ return {
122
+ name: 'Vue',
123
+ version: deps.vue,
124
+ confidence: Math.min(confidence, 100),
125
+ evidence,
126
+ };
127
+ }
128
+
129
+ return null;
130
+ }
131
+
132
+ /**
133
+ * Detect Svelte/SvelteKit
134
+ */
135
+ function detectSvelte(projectRoot: string, deps: DependencyMap): DetectionResult | null {
136
+ const evidence: string[] = [];
137
+ let confidence = 0;
138
+
139
+ // Check for SvelteKit (takes precedence)
140
+ if (deps['@sveltejs/kit']) {
141
+ evidence.push(`@sveltejs/kit@${deps['@sveltejs/kit']} in dependencies`);
142
+ confidence += 70;
143
+
144
+ const svelteConfig = findConfigFile(projectRoot, 'svelte.config', ['.js', '.ts']);
145
+ if (svelteConfig) {
146
+ evidence.push(`${svelteConfig} found`);
147
+ confidence += 30;
148
+ }
149
+
150
+ return {
151
+ name: 'SvelteKit',
152
+ version: deps['@sveltejs/kit'],
153
+ confidence: Math.min(confidence, 100),
154
+ evidence,
155
+ };
156
+ }
157
+
158
+ // Check for Svelte
159
+ if (deps.svelte) {
160
+ evidence.push(`svelte@${deps.svelte} in dependencies`);
161
+ confidence += 50;
162
+
163
+ const svelteConfig = findConfigFile(projectRoot, 'svelte.config', ['.js', '.ts']);
164
+ if (svelteConfig) {
165
+ evidence.push(`${svelteConfig} found`);
166
+ confidence += 30;
167
+ }
168
+
169
+ if (confidence === 0) return null;
170
+
171
+ return {
172
+ name: 'Svelte',
173
+ version: deps.svelte,
174
+ confidence: Math.min(confidence, 100),
175
+ evidence,
176
+ };
177
+ }
178
+
179
+ return null;
180
+ }
181
+
182
+ /**
183
+ * Detect Remix
184
+ */
185
+ function detectRemix(projectRoot: string, deps: DependencyMap): DetectionResult | null {
186
+ const evidence: string[] = [];
187
+ let confidence = 0;
188
+
189
+ if (deps['@remix-run/react'] || deps['@remix-run/node']) {
190
+ const version = deps['@remix-run/react'] || deps['@remix-run/node'];
191
+ evidence.push(`@remix-run packages@${version} in dependencies`);
192
+ confidence += 70;
193
+
194
+ // Check for remix config
195
+ const remixConfig = findConfigFile(projectRoot, 'remix.config', ['.js', '.ts']);
196
+ if (remixConfig) {
197
+ evidence.push(`${remixConfig} found`);
198
+ confidence += 20;
199
+ }
200
+
201
+ // Remix v2+ uses vite
202
+ const viteConfig = findConfigFile(projectRoot, 'vite.config', ['.js', '.ts', '.mjs']);
203
+ if (viteConfig && deps['@remix-run/dev']) {
204
+ evidence.push('Vite-based Remix setup detected');
205
+ confidence += 10;
206
+ }
207
+
208
+ return {
209
+ name: 'Remix',
210
+ version,
211
+ confidence: Math.min(confidence, 100),
212
+ evidence,
213
+ };
214
+ }
215
+
216
+ return null;
217
+ }
218
+
219
+ /**
220
+ * Detect Astro
221
+ */
222
+ function detectAstro(projectRoot: string, deps: DependencyMap): DetectionResult | null {
223
+ const evidence: string[] = [];
224
+ let confidence = 0;
225
+
226
+ if (deps.astro) {
227
+ evidence.push(`astro@${deps.astro} in dependencies`);
228
+ confidence += 60;
229
+
230
+ const astroConfig = findConfigFile(projectRoot, 'astro.config', ['.mjs', '.js', '.ts']);
231
+ if (astroConfig) {
232
+ evidence.push(`${astroConfig} found`);
233
+ confidence += 30;
234
+ }
235
+
236
+ return {
237
+ name: 'Astro',
238
+ version: deps.astro,
239
+ confidence: Math.min(confidence, 100),
240
+ evidence,
241
+ };
242
+ }
243
+
244
+ return null;
245
+ }
246
+
247
+ /**
248
+ * Detect plain React (no framework)
249
+ */
250
+ function detectReact(projectRoot: string, deps: DependencyMap): DetectionResult | null {
251
+ const evidence: string[] = [];
252
+ let confidence = 0;
253
+
254
+ if (deps.react) {
255
+ evidence.push(`react@${deps.react} in dependencies`);
256
+ confidence += 40;
257
+
258
+ // Check for common React setups
259
+ if (deps['react-scripts']) {
260
+ evidence.push('Create React App detected (react-scripts)');
261
+ confidence += 40;
262
+ return {
263
+ name: 'React',
264
+ version: deps.react,
265
+ variant: 'create-react-app',
266
+ confidence: Math.min(confidence, 100),
267
+ evidence,
268
+ };
269
+ }
270
+
271
+ // Vite + React
272
+ if (deps['@vitejs/plugin-react'] || deps['@vitejs/plugin-react-swc']) {
273
+ evidence.push('Vite + React setup detected');
274
+ confidence += 30;
275
+ return {
276
+ name: 'React',
277
+ version: deps.react,
278
+ variant: 'vite',
279
+ confidence: Math.min(confidence, 100),
280
+ evidence,
281
+ };
282
+ }
283
+
284
+ // Plain React
285
+ return {
286
+ name: 'React',
287
+ version: deps.react,
288
+ confidence: Math.min(confidence, 100),
289
+ evidence,
290
+ };
291
+ }
292
+
293
+ return null;
294
+ }
295
+
296
+ /**
297
+ * Framework detector
298
+ */
299
+ export const frameworkDetector: Detector = {
300
+ category: 'framework',
301
+ name: 'Framework Detector',
302
+
303
+ async detect(projectRoot: string): Promise<DetectionResult | null> {
304
+ const pkg = readPackageJson(projectRoot);
305
+ if (!pkg) {
306
+ return null;
307
+ }
308
+
309
+ const deps = getDependencies(pkg);
310
+
311
+ // Order matters: check meta-frameworks first, then base frameworks
312
+ const detectors = [
313
+ detectNextJs,
314
+ detectRemix,
315
+ detectAstro,
316
+ detectVue, // Also handles Nuxt
317
+ detectSvelte, // Also handles SvelteKit
318
+ detectReact, // Check React last as it's often a dependency of frameworks
319
+ ];
320
+
321
+ for (const detector of detectors) {
322
+ const result = detector(projectRoot, deps);
323
+ if (result && result.confidence >= 40) {
324
+ return result;
325
+ }
326
+ }
327
+
328
+ return null;
329
+ },
330
+ };
331
+
332
+ export default frameworkDetector;