clauderc 1.0.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.
package/src/project.js ADDED
@@ -0,0 +1,435 @@
1
+ /**
2
+ * Project setup wizard
3
+ */
4
+
5
+ import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'fs';
6
+ import { join, basename } from 'path';
7
+ import { createInterface } from 'readline';
8
+ import { detectStack, generateCommands } from './detector.js';
9
+
10
+ /**
11
+ * Interactive prompt helper
12
+ */
13
+ function createPrompt() {
14
+ const rl = createInterface({
15
+ input: process.stdin,
16
+ output: process.stdout,
17
+ });
18
+
19
+ return {
20
+ ask: (question) => new Promise((resolve) => {
21
+ rl.question(question, (answer) => resolve(answer.trim()));
22
+ }),
23
+ select: async (question, options) => {
24
+ console.log(`\n${question}\n`);
25
+ options.forEach((opt, i) => {
26
+ console.log(` ${i + 1}) ${opt.label}${opt.description ? ` - ${opt.description}` : ''}`);
27
+ });
28
+ const answer = await new Promise((resolve) => {
29
+ rl.question('\n Enter number: ', resolve);
30
+ });
31
+ const index = parseInt(answer) - 1;
32
+ return options[index] || options[0];
33
+ },
34
+ confirm: async (question) => {
35
+ const answer = await new Promise((resolve) => {
36
+ rl.question(`${question} (Y/n): `, resolve);
37
+ });
38
+ return answer.toLowerCase() !== 'n';
39
+ },
40
+ close: () => rl.close(),
41
+ };
42
+ }
43
+
44
+ /**
45
+ * Generate CLAUDE.md content
46
+ */
47
+ function generateClaudeMd(config) {
48
+ const { projectName, stack, commands, customRules } = config;
49
+
50
+ let content = `# ${projectName}
51
+
52
+ ## Stack
53
+ `;
54
+
55
+ if (stack.stacks.length > 0) {
56
+ content += `- **Language**: ${stack.stacks.map(s => s.name).join(', ')}\n`;
57
+ }
58
+ if (stack.framework) {
59
+ content += `- **Framework**: ${stack.framework.name}\n`;
60
+ }
61
+ if (stack.packageManager) {
62
+ content += `- **Package Manager**: ${stack.packageManager.name}\n`;
63
+ }
64
+ if (stack.monorepo) {
65
+ content += `- **Monorepo**: ${stack.monorepo.name}\n`;
66
+ }
67
+
68
+ content += `
69
+ ## Commands
70
+
71
+ \`\`\`bash
72
+ # Setup
73
+ ${commands.setup || '# No setup command detected'}
74
+
75
+ # Development
76
+ ${commands.dev || '# No dev command detected'}
77
+
78
+ # Test
79
+ ${commands.test || '# No test command detected'}
80
+
81
+ # Lint
82
+ ${commands.lint || '# No lint command detected'}
83
+
84
+ # Build
85
+ ${commands.build || '# No build command detected'}
86
+
87
+ # Full verification
88
+ ${commands.verify || '# No verify command detected'}
89
+ \`\`\`
90
+
91
+ ## Workflow
92
+
93
+ ### Plan Mode
94
+ Use Plan Mode (Shift+Tab 2x) for:
95
+ - Refactoring > 3 files
96
+ - New feature implementation
97
+ - Architecture changes
98
+ - Database migrations
99
+
100
+ ### Verification
101
+ - ALWAYS run \`${commands.verify || 'tests'}\` before committing
102
+ - NEVER skip tests without explicit approval
103
+ `;
104
+
105
+ if (customRules && customRules.length > 0) {
106
+ content += `
107
+ ## Project Rules
108
+
109
+ ${customRules.map(r => `- ${r}`).join('\n')}
110
+ `;
111
+ }
112
+
113
+ content += `
114
+ ## Documentation
115
+ @README.md
116
+ `;
117
+
118
+ return content;
119
+ }
120
+
121
+ /**
122
+ * Generate settings.json
123
+ */
124
+ function generateSettings(config) {
125
+ const { stack, commands } = config;
126
+ const pm = stack.packageManager?.name || 'npm';
127
+
128
+ const allowedCommands = [];
129
+
130
+ // Add package manager commands
131
+ switch (pm) {
132
+ case 'bun':
133
+ allowedCommands.push('Bash(bun:*)');
134
+ break;
135
+ case 'pnpm':
136
+ allowedCommands.push('Bash(pnpm:*)');
137
+ break;
138
+ case 'yarn':
139
+ allowedCommands.push('Bash(yarn:*)');
140
+ break;
141
+ case 'npm':
142
+ allowedCommands.push('Bash(npm:*)');
143
+ break;
144
+ case 'poetry':
145
+ allowedCommands.push('Bash(poetry:*)');
146
+ break;
147
+ case 'cargo':
148
+ allowedCommands.push('Bash(cargo:*)');
149
+ break;
150
+ }
151
+
152
+ // Add common commands
153
+ allowedCommands.push('Bash(git:*)');
154
+ allowedCommands.push('Bash(gh:*)');
155
+
156
+ // Add stack-specific commands
157
+ const stackId = stack.stacks[0]?.id;
158
+ switch (stackId) {
159
+ case 'go':
160
+ allowedCommands.push('Bash(go:*)');
161
+ allowedCommands.push('Bash(golangci-lint:*)');
162
+ break;
163
+ case 'rust':
164
+ allowedCommands.push('Bash(cargo:*)');
165
+ break;
166
+ case 'python':
167
+ allowedCommands.push('Bash(pytest:*)');
168
+ allowedCommands.push('Bash(ruff:*)');
169
+ allowedCommands.push('Bash(mypy:*)');
170
+ break;
171
+ case 'dotnet':
172
+ allowedCommands.push('Bash(dotnet:*)');
173
+ break;
174
+ case 'elixir':
175
+ allowedCommands.push('Bash(mix:*)');
176
+ break;
177
+ case 'ruby':
178
+ allowedCommands.push('Bash(bundle:*)');
179
+ allowedCommands.push('Bash(rails:*)');
180
+ break;
181
+ case 'php':
182
+ allowedCommands.push('Bash(composer:*)');
183
+ allowedCommands.push('Bash(php:*)');
184
+ break;
185
+ case 'java':
186
+ allowedCommands.push('Bash(mvn:*)');
187
+ allowedCommands.push('Bash(gradle:*)');
188
+ break;
189
+ }
190
+
191
+ const settings = {
192
+ permissions: {
193
+ allow: allowedCommands,
194
+ },
195
+ };
196
+
197
+ // Add hooks for formatting
198
+ if (commands.format || commands.lint) {
199
+ const hookCommand = commands.format || `${commands.lint} --fix`;
200
+ settings.hooks = {
201
+ PostToolUse: [
202
+ {
203
+ matcher: 'Edit|Write',
204
+ hooks: [
205
+ {
206
+ type: 'command',
207
+ command: `${hookCommand} || true`,
208
+ },
209
+ ],
210
+ },
211
+ ],
212
+ };
213
+ }
214
+
215
+ return settings;
216
+ }
217
+
218
+ /**
219
+ * Generate universal command files
220
+ */
221
+ function generateCommandFile(name, config) {
222
+ const { commands, stack } = config;
223
+ const stackName = stack.stacks[0]?.name || 'Unknown';
224
+
225
+ const templates = {
226
+ test: `# Run ${stackName} tests
227
+
228
+ Runs the project test suite.
229
+
230
+ ## Command
231
+ \`\`\`bash
232
+ ${commands.test || '# No test command detected - configure in CLAUDE.md'}
233
+ \`\`\`
234
+
235
+ ## Usage
236
+ Run this command to execute all project tests before committing changes.
237
+ `,
238
+
239
+ lint: `# Lint ${stackName} code
240
+
241
+ Runs linter and optionally fixes issues.
242
+
243
+ ## Command
244
+ \`\`\`bash
245
+ ${commands.lint || '# No lint command detected - configure in CLAUDE.md'}
246
+ \`\`\`
247
+
248
+ ## Auto-fix
249
+ \`\`\`bash
250
+ ${commands.format || commands.lint + ' --fix' || '# No format command detected'}
251
+ \`\`\`
252
+ `,
253
+
254
+ verify: `# Full verification
255
+
256
+ Runs all checks: lint, test, and build.
257
+
258
+ ## Command
259
+ \`\`\`bash
260
+ ${commands.verify || [commands.lint, commands.test, commands.build].filter(Boolean).join(' && ') || '# Configure verify steps in CLAUDE.md'}
261
+ \`\`\`
262
+
263
+ ## When to use
264
+ - Before committing changes
265
+ - Before creating a PR
266
+ - After major refactoring
267
+ `,
268
+
269
+ setup: `# Project setup
270
+
271
+ Install dependencies and prepare the development environment.
272
+
273
+ ## Command
274
+ \`\`\`bash
275
+ ${commands.setup || '# No setup command detected - configure in CLAUDE.md'}
276
+ \`\`\`
277
+ `,
278
+
279
+ pr: `# Create Pull Request
280
+
281
+ Commit, push, and create a PR.
282
+
283
+ ## Steps
284
+ 1. Stage all changes
285
+ 2. Commit with descriptive message
286
+ 3. Push to remote
287
+ 4. Create PR with \`gh pr create\`
288
+
289
+ ## Prerequisites
290
+ - \`gh\` CLI installed and authenticated
291
+ - All tests passing (\`/verify\`)
292
+ `,
293
+ };
294
+
295
+ return templates[name] || '';
296
+ }
297
+
298
+ /**
299
+ * Run project setup wizard
300
+ */
301
+ export async function runProjectWizard(options = {}) {
302
+ const { dryRun = false, silent = false } = options;
303
+ const projectPath = process.cwd();
304
+ const projectName = basename(projectPath);
305
+
306
+ const log = silent ? () => {} : console.log;
307
+ const prompt = createPrompt();
308
+
309
+ try {
310
+ log('\n Analyzing project...\n');
311
+
312
+ // Detect stack
313
+ const stack = detectStack(projectPath);
314
+ const commands = generateCommands(stack);
315
+
316
+ // Show detection results
317
+ log(' Detected configuration:\n');
318
+
319
+ if (stack.stacks.length > 0) {
320
+ log(` Language: ${stack.stacks.map(s => s.name).join(', ')}`);
321
+ } else {
322
+ log(' Language: Not detected');
323
+ }
324
+
325
+ if (stack.framework) {
326
+ log(` Framework: ${stack.framework.name}`);
327
+ }
328
+
329
+ if (stack.packageManager) {
330
+ log(` Package Manager: ${stack.packageManager.name}`);
331
+ }
332
+
333
+ if (stack.monorepo) {
334
+ log(` Monorepo: ${stack.monorepo.name}`);
335
+ }
336
+
337
+ if (stack.ci) {
338
+ log(` CI/CD: ${stack.ci.name}`);
339
+ }
340
+
341
+ log('\n Generated commands:\n');
342
+ if (commands.setup) log(` Setup: ${commands.setup}`);
343
+ if (commands.dev) log(` Dev: ${commands.dev}`);
344
+ if (commands.test) log(` Test: ${commands.test}`);
345
+ if (commands.lint) log(` Lint: ${commands.lint}`);
346
+ if (commands.build) log(` Build: ${commands.build}`);
347
+
348
+ // Confirm or customize
349
+ log('');
350
+ const confirmed = await prompt.confirm(' Proceed with this configuration?');
351
+
352
+ if (!confirmed) {
353
+ log('\n Setup cancelled.\n');
354
+ prompt.close();
355
+ return null;
356
+ }
357
+
358
+ // Ask for custom rules
359
+ log('\n Any project-specific rules? (one per line, empty line to finish)\n');
360
+ const customRules = [];
361
+ let rule = await prompt.ask(' Rule: ');
362
+ while (rule) {
363
+ customRules.push(rule);
364
+ rule = await prompt.ask(' Rule: ');
365
+ }
366
+
367
+ // Generate config
368
+ const config = {
369
+ projectName,
370
+ stack,
371
+ commands,
372
+ customRules,
373
+ };
374
+
375
+ // Check existing .claude directory
376
+ const claudeDir = join(projectPath, '.claude');
377
+ const hasExisting = existsSync(claudeDir);
378
+
379
+ if (hasExisting) {
380
+ const overwrite = await prompt.confirm('\n .claude/ already exists. Overwrite?');
381
+ if (!overwrite) {
382
+ log('\n Setup cancelled.\n');
383
+ prompt.close();
384
+ return null;
385
+ }
386
+ }
387
+
388
+ prompt.close();
389
+
390
+ if (dryRun) {
391
+ log('\n DRY RUN - Would create:\n');
392
+ log(' .claude/');
393
+ log(' ├── commands/test.md');
394
+ log(' ├── commands/lint.md');
395
+ log(' ├── commands/verify.md');
396
+ log(' ├── commands/setup.md');
397
+ log(' ├── commands/pr.md');
398
+ log(' ├── settings.json');
399
+ log(' CLAUDE.md\n');
400
+ return config;
401
+ }
402
+
403
+ // Create directories
404
+ mkdirSync(join(claudeDir, 'commands'), { recursive: true });
405
+
406
+ // Write files
407
+ const files = [
408
+ { path: join(projectPath, 'CLAUDE.md'), content: generateClaudeMd(config) },
409
+ { path: join(claudeDir, 'settings.json'), content: JSON.stringify(generateSettings(config), null, 2) },
410
+ { path: join(claudeDir, 'commands', 'test.md'), content: generateCommandFile('test', config) },
411
+ { path: join(claudeDir, 'commands', 'lint.md'), content: generateCommandFile('lint', config) },
412
+ { path: join(claudeDir, 'commands', 'verify.md'), content: generateCommandFile('verify', config) },
413
+ { path: join(claudeDir, 'commands', 'setup.md'), content: generateCommandFile('setup', config) },
414
+ { path: join(claudeDir, 'commands', 'pr.md'), content: generateCommandFile('pr', config) },
415
+ ];
416
+
417
+ for (const file of files) {
418
+ writeFileSync(file.path, file.content);
419
+ log(` + ${file.path.replace(projectPath, '.')}`);
420
+ }
421
+
422
+ log('\n Project setup complete!\n');
423
+ log(' Next steps:');
424
+ log(' 1. Review CLAUDE.md and adjust as needed');
425
+ log(' 2. Commit .claude/ to your repository');
426
+ log(' 3. Run /test, /lint, /verify in Claude Code\n');
427
+
428
+ return config;
429
+ } catch (error) {
430
+ prompt.close();
431
+ throw error;
432
+ }
433
+ }
434
+
435
+ export default { runProjectWizard, detectStack, generateCommands };