clauderc 1.0.0 → 1.0.1

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/README.md CHANGED
@@ -12,14 +12,17 @@
12
12
  </p>
13
13
 
14
14
  <p align="center">
15
- <img src="https://img.shields.io/npm/v/clauderc?color=blue" alt="npm version">
15
+ <a href="https://www.npmjs.com/package/clauderc"><img src="https://img.shields.io/npm/v/clauderc?color=blue" alt="npm version"></a>
16
16
  <img src="https://img.shields.io/badge/platform-macOS%20%7C%20Linux%20%7C%20Windows-lightgrey" alt="platform">
17
17
  <img src="https://img.shields.io/badge/node-%3E%3D16.7-green" alt="node version">
18
+ <a href="https://github.com/matheuskindrazki/clauderc/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="license"></a>
19
+ <a href="https://github.com/matheuskindrazki/clauderc/blob/main/CONTRIBUTING.md"><img src="https://img.shields.io/badge/PRs-welcome-brightgreen.svg" alt="PRs Welcome"></a>
20
+ <a href="https://github.com/matheuskindrazki/clauderc/blob/main/CODE_OF_CONDUCT.md"><img src="https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg" alt="Contributor Covenant"></a>
18
21
  </p>
19
22
 
20
23
  ---
21
24
 
22
- Based on tips from [Boris Cherny](https://twitter.com/bcherny) (Claude Code creator) and official documentation.
25
+ Based on my experiences and tips from [ Boris Cherny ](https://twitter.com/bcherny) (creator of the Claude Code) and official documentation.
23
26
 
24
27
  ## Quick Start
25
28
 
@@ -198,10 +201,18 @@ After installation:
198
201
 
199
202
  ## Contributing
200
203
 
201
- Contributions are welcome! Feel free to:
204
+ Contributions are welcome! Please read our guidelines before contributing:
205
+
206
+ - **[Contributing Guide](CONTRIBUTING.md)** - How to contribute to the project
207
+ - **[Code of Conduct](CODE_OF_CONDUCT.md)** - Community guidelines
208
+ - **[Security Policy](SECURITY.md)** - How to report vulnerabilities
209
+
210
+ Ways to contribute:
202
211
  - Add new commands, skills, or agents
212
+ - Add support for new stacks/languages
203
213
  - Improve existing templates
204
- - Report issues or suggest features
214
+ - Report bugs or suggest features
215
+ - Improve documentation
205
216
 
206
217
  ## Author
207
218
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clauderc",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Setup Claude Code with best practices - agents, skills, commands, and templates",
5
5
  "type": "module",
6
6
  "bin": {
package/src/detector.js CHANGED
@@ -4,8 +4,293 @@
4
4
 
5
5
  import { existsSync, readFileSync, readdirSync } from 'fs';
6
6
  import { join } from 'path';
7
+ import { execSync } from 'child_process';
7
8
  import { STACKS, MONOREPO_TOOLS, CI_PLATFORMS } from './stacks.js';
8
9
 
10
+ /**
11
+ * Files to read for LLM analysis
12
+ */
13
+ const ANALYSIS_FILES = [
14
+ 'package.json',
15
+ 'pyproject.toml',
16
+ 'requirements.txt',
17
+ 'go.mod',
18
+ 'Cargo.toml',
19
+ 'composer.json',
20
+ 'Gemfile',
21
+ 'pom.xml',
22
+ 'build.gradle',
23
+ 'build.gradle.kts',
24
+ 'mix.exs',
25
+ 'pubspec.yaml',
26
+ 'Package.swift',
27
+ '*.csproj',
28
+ 'turbo.json',
29
+ 'nx.json',
30
+ 'pnpm-workspace.yaml',
31
+ 'lerna.json',
32
+ '.github/workflows/*.yml',
33
+ '.gitlab-ci.yml',
34
+ 'Jenkinsfile',
35
+ 'biome.json',
36
+ 'biome.jsonc',
37
+ '.eslintrc*',
38
+ '.prettierrc*',
39
+ 'tsconfig.json',
40
+ 'vite.config.*',
41
+ 'next.config.*',
42
+ 'nuxt.config.*',
43
+ ];
44
+
45
+ /**
46
+ * Read relevant project files for analysis
47
+ */
48
+ function readProjectFiles(projectPath) {
49
+ const files = {};
50
+
51
+ for (const pattern of ANALYSIS_FILES) {
52
+ if (pattern.includes('*')) {
53
+ // Handle glob patterns
54
+ const parts = pattern.split('/');
55
+ let searchPath = projectPath;
56
+ let filePattern = pattern;
57
+
58
+ if (parts.length > 1) {
59
+ searchPath = join(projectPath, ...parts.slice(0, -1));
60
+ filePattern = parts[parts.length - 1];
61
+ }
62
+
63
+ if (existsSync(searchPath)) {
64
+ const dirFiles = safeReadDir(searchPath);
65
+ const regex = new RegExp('^' + filePattern.replace(/\./g, '\\.').replace(/\*/g, '.*') + '$');
66
+ for (const file of dirFiles) {
67
+ if (regex.test(file)) {
68
+ const fullPath = join(searchPath, file);
69
+ const relativePath = fullPath.replace(projectPath + '/', '');
70
+ const content = safeReadFile(fullPath);
71
+ if (content && content.length < 10000) { // Limit file size
72
+ files[relativePath] = content;
73
+ }
74
+ }
75
+ }
76
+ }
77
+ } else {
78
+ const fullPath = join(projectPath, pattern);
79
+ if (existsSync(fullPath)) {
80
+ const content = safeReadFile(fullPath);
81
+ if (content && content.length < 10000) {
82
+ files[pattern] = content;
83
+ }
84
+ }
85
+ }
86
+ }
87
+
88
+ return files;
89
+ }
90
+
91
+ /**
92
+ * Analyze project using Claude LLM
93
+ * @param {string} projectPath - Path to the project
94
+ * @returns {Object} - Detection result compatible with detectStack output
95
+ */
96
+ export function analyzeWithClaude(projectPath = process.cwd()) {
97
+ const files = readProjectFiles(projectPath);
98
+
99
+ if (Object.keys(files).length === 0) {
100
+ console.warn(' ⚠ No project files found for analysis');
101
+ return null;
102
+ }
103
+
104
+ const filesContent = Object.entries(files)
105
+ .map(([name, content]) => `=== ${name} ===\n${content}`)
106
+ .join('\n\n');
107
+
108
+ const prompt = `Analyze this project and return a JSON configuration for Claude Code setup.
109
+
110
+ PROJECT FILES:
111
+ ${filesContent}
112
+
113
+ Return ONLY valid JSON (no markdown, no explanation) with this exact structure:
114
+ {
115
+ "stack": {
116
+ "language": "Node.js|Python|Go|Rust|Java|PHP|Ruby|.NET|Elixir|Dart|Swift",
117
+ "framework": "Next.js|Nuxt|React|Vue|FastAPI|Django|Flask|Express|Gin|Rails|Laravel|Phoenix|etc or null",
118
+ "packageManager": "npm|pnpm|bun|yarn|pip|poetry|uv|cargo|go|maven|gradle|composer|bundler|mix|etc",
119
+ "monorepo": "turborepo|nx|lerna|pnpm-workspaces|yarn-workspaces or null",
120
+ "testFramework": "vitest|jest|pytest|go test|cargo test|phpunit|rspec|etc or null",
121
+ "linter": "eslint|biome|ruff|golangci-lint|clippy|rubocop|etc or null",
122
+ "formatter": "prettier|biome|black|ruff|gofmt|rustfmt|etc or null",
123
+ "ci": "github-actions|gitlab-ci|jenkins|circleci or null"
124
+ },
125
+ "commands": {
126
+ "setup": "command to install dependencies",
127
+ "dev": "command to run dev server",
128
+ "test": "command to run tests",
129
+ "lint": "command to lint code",
130
+ "format": "command to format code",
131
+ "typecheck": "command to check types or null",
132
+ "build": "command to build or null",
133
+ "verify": "combined lint + test + build command"
134
+ },
135
+ "preferences": {
136
+ "notes": "any special preferences detected (e.g., prefers bun over npm, uses specific conventions)"
137
+ }
138
+ }
139
+
140
+ IMPORTANT:
141
+ - Detect ACTUAL tools being used, not defaults
142
+ - If bun.lockb exists, use bun commands
143
+ - If pnpm-lock.yaml exists, use pnpm commands
144
+ - If uv.lock exists, use uv commands
145
+ - Look at scripts in package.json for exact command names
146
+ - Return commands that will actually work for this project`;
147
+
148
+ try {
149
+ // Escape the prompt for shell
150
+ const escapedPrompt = prompt
151
+ .replace(/\\/g, '\\\\')
152
+ .replace(/"/g, '\\"')
153
+ .replace(/\$/g, '\\$')
154
+ .replace(/`/g, '\\`');
155
+
156
+ const result = execSync(
157
+ `claude -p --model haiku "${escapedPrompt}"`,
158
+ {
159
+ encoding: 'utf-8',
160
+ maxBuffer: 1024 * 1024 * 10,
161
+ timeout: 120000, // 2 minute timeout
162
+ cwd: projectPath,
163
+ }
164
+ );
165
+
166
+ // Extract JSON from response
167
+ const jsonMatch = result.match(/\{[\s\S]*\}/);
168
+ if (!jsonMatch) {
169
+ console.warn(' ⚠ Could not parse Claude response');
170
+ return null;
171
+ }
172
+
173
+ const analysis = JSON.parse(jsonMatch[0]);
174
+
175
+ // Convert to detectStack compatible format
176
+ return {
177
+ analysis,
178
+ stack: convertToStackFormat(analysis.stack),
179
+ commands: analysis.commands,
180
+ preferences: analysis.preferences,
181
+ };
182
+ } catch (error) {
183
+ console.warn(' ⚠ Claude analysis failed:', error.message);
184
+ return null;
185
+ }
186
+ }
187
+
188
+ /**
189
+ * Convert LLM analysis to detectStack format
190
+ */
191
+ function convertToStackFormat(llmStack) {
192
+ const result = {
193
+ stacks: [],
194
+ packageManager: null,
195
+ framework: null,
196
+ monorepo: null,
197
+ ci: null,
198
+ testFramework: null,
199
+ linter: null,
200
+ formatter: null,
201
+ typechecker: null,
202
+ };
203
+
204
+ // Map language to stack
205
+ const languageMap = {
206
+ 'Node.js': 'node',
207
+ 'Python': 'python',
208
+ 'Go': 'go',
209
+ 'Rust': 'rust',
210
+ 'Java': 'java',
211
+ 'PHP': 'php',
212
+ 'Ruby': 'ruby',
213
+ '.NET': 'dotnet',
214
+ 'Elixir': 'elixir',
215
+ 'Dart': 'dart',
216
+ 'Swift': 'swift',
217
+ };
218
+
219
+ const stackId = languageMap[llmStack.language];
220
+ if (stackId && STACKS[stackId]) {
221
+ result.stacks.push({ id: stackId, name: llmStack.language, ...STACKS[stackId] });
222
+ }
223
+
224
+ // Package manager
225
+ if (llmStack.packageManager) {
226
+ result.packageManager = {
227
+ name: llmStack.packageManager,
228
+ install: getInstallCommand(llmStack.packageManager),
229
+ run: getRunCommand(llmStack.packageManager),
230
+ };
231
+ }
232
+
233
+ // Framework
234
+ if (llmStack.framework) {
235
+ result.framework = { name: llmStack.framework };
236
+ }
237
+
238
+ // Monorepo
239
+ if (llmStack.monorepo) {
240
+ result.monorepo = { name: llmStack.monorepo };
241
+ }
242
+
243
+ // CI
244
+ if (llmStack.ci) {
245
+ result.ci = { name: llmStack.ci };
246
+ }
247
+
248
+ // Tools
249
+ result.testFramework = llmStack.testFramework;
250
+ result.linter = llmStack.linter;
251
+ result.formatter = llmStack.formatter;
252
+
253
+ return result;
254
+ }
255
+
256
+ /**
257
+ * Get install command for package manager
258
+ */
259
+ function getInstallCommand(pm) {
260
+ const commands = {
261
+ npm: 'npm install',
262
+ pnpm: 'pnpm install',
263
+ bun: 'bun install',
264
+ yarn: 'yarn install',
265
+ pip: 'pip install -r requirements.txt',
266
+ poetry: 'poetry install',
267
+ uv: 'uv sync',
268
+ cargo: 'cargo build',
269
+ go: 'go mod download',
270
+ maven: 'mvn install -DskipTests',
271
+ gradle: 'gradle build -x test',
272
+ composer: 'composer install',
273
+ bundler: 'bundle install',
274
+ mix: 'mix deps.get',
275
+ };
276
+ return commands[pm] || `${pm} install`;
277
+ }
278
+
279
+ /**
280
+ * Get run command prefix for package manager
281
+ */
282
+ function getRunCommand(pm) {
283
+ const commands = {
284
+ npm: 'npm run',
285
+ pnpm: 'pnpm',
286
+ bun: 'bun run',
287
+ yarn: 'yarn',
288
+ poetry: 'poetry run',
289
+ uv: 'uv run',
290
+ };
291
+ return commands[pm] || '';
292
+ }
293
+
9
294
  /**
10
295
  * Detect project stack from current directory
11
296
  */
@@ -384,4 +669,4 @@ export function generateCommands(detection) {
384
669
  return commands;
385
670
  }
386
671
 
387
- export default { detectStack, generateCommands };
672
+ export default { detectStack, generateCommands, analyzeWithClaude };
package/src/project.js CHANGED
@@ -5,7 +5,74 @@
5
5
  import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'fs';
6
6
  import { join, basename } from 'path';
7
7
  import { createInterface } from 'readline';
8
- import { detectStack, generateCommands } from './detector.js';
8
+ import { execSync } from 'child_process';
9
+ import { detectStack, generateCommands, analyzeWithClaude } from './detector.js';
10
+
11
+ /**
12
+ * Use Claude CLI to intelligently merge existing and new content
13
+ * @param {string} existingContent - The current file content
14
+ * @param {string} newContent - The proposed new content
15
+ * @param {string} fileType - Type of file for context (e.g., 'CLAUDE.md', 'settings.json')
16
+ * @returns {string} - The merged content
17
+ */
18
+ function mergeWithClaude(existingContent, newContent, fileType) {
19
+ const prompt = `You are merging two versions of a ${fileType} configuration file.
20
+
21
+ EXISTING content (user's current configuration - PRESERVE user preferences and customizations):
22
+ ---
23
+ ${existingContent}
24
+ ---
25
+
26
+ NEW content (auto-generated with updated stack detection):
27
+ ---
28
+ ${newContent}
29
+ ---
30
+
31
+ IMPORTANT RULES:
32
+ 1. PRESERVE any custom rules, preferences, or configurations the user added in EXISTING
33
+ 2. UPDATE commands and stack info from NEW if they are more accurate
34
+ 3. MERGE sections intelligently - don't duplicate, combine them
35
+ 4. Keep user's custom sections that don't exist in NEW
36
+ 5. If EXISTING has custom hooks, permissions, or rules - KEEP THEM
37
+ 6. Output ONLY the merged content, no explanations
38
+
39
+ Output the merged ${fileType}:`;
40
+
41
+ try {
42
+ // Use Claude CLI in print mode for non-interactive merge
43
+ const result = execSync(
44
+ `claude -p --model haiku "${prompt.replace(/"/g, '\\"').replace(/\n/g, '\\n')}"`,
45
+ {
46
+ encoding: 'utf-8',
47
+ maxBuffer: 1024 * 1024 * 10, // 10MB buffer
48
+ timeout: 60000, // 60 second timeout
49
+ }
50
+ );
51
+ return result.trim();
52
+ } catch (error) {
53
+ // If Claude CLI fails, return new content with a warning
54
+ console.warn(' ⚠ Could not use Claude for merge, using new content');
55
+ return newContent;
56
+ }
57
+ }
58
+
59
+ /**
60
+ * Smart write that merges with existing content if file exists
61
+ * @param {string} filePath - Path to the file
62
+ * @param {string} newContent - New content to write
63
+ * @param {string} fileType - Type of file for merge context
64
+ * @param {boolean} useMerge - Whether to use Claude merge
65
+ */
66
+ function smartWrite(filePath, newContent, fileType, useMerge = true) {
67
+ if (useMerge && existsSync(filePath)) {
68
+ const existingContent = readFileSync(filePath, 'utf-8');
69
+ const mergedContent = mergeWithClaude(existingContent, newContent, fileType);
70
+ writeFileSync(filePath, mergedContent);
71
+ return { merged: true, path: filePath };
72
+ }
73
+ writeFileSync(filePath, newContent);
74
+ return { merged: false, path: filePath };
75
+ }
9
76
 
10
77
  /**
11
78
  * Interactive prompt helper
@@ -307,11 +374,37 @@ export async function runProjectWizard(options = {}) {
307
374
  const prompt = createPrompt();
308
375
 
309
376
  try {
310
- log('\n Analyzing project...\n');
311
-
312
- // Detect stack
313
- const stack = detectStack(projectPath);
314
- const commands = generateCommands(stack);
377
+ log('\n Claude Code Project Setup\n');
378
+ log(' This wizard will configure Claude Code for your project.\n');
379
+
380
+ // Ask about AI analysis
381
+ const useAI = await prompt.confirm(' Use Claude AI for smarter detection? (recommended)');
382
+
383
+ let stack;
384
+ let commands;
385
+ let aiAnalysis = null;
386
+
387
+ if (useAI) {
388
+ log('\n Analyzing project with Claude AI...\n');
389
+ aiAnalysis = analyzeWithClaude(projectPath);
390
+
391
+ if (aiAnalysis) {
392
+ stack = aiAnalysis.stack;
393
+ commands = aiAnalysis.commands;
394
+
395
+ if (aiAnalysis.preferences?.notes) {
396
+ log(` Notes: ${aiAnalysis.preferences.notes}\n`);
397
+ }
398
+ } else {
399
+ log(' AI analysis failed, falling back to deterministic detection...\n');
400
+ stack = detectStack(projectPath);
401
+ commands = generateCommands(stack);
402
+ }
403
+ } else {
404
+ log('\n Analyzing project...\n');
405
+ stack = detectStack(projectPath);
406
+ commands = generateCommands(stack);
407
+ }
315
408
 
316
409
  // Show detection results
317
410
  log(' Detected configuration:\n');
@@ -375,20 +468,35 @@ export async function runProjectWizard(options = {}) {
375
468
  // Check existing .claude directory
376
469
  const claudeDir = join(projectPath, '.claude');
377
470
  const hasExisting = existsSync(claudeDir);
378
-
379
- if (hasExisting) {
380
- const overwrite = await prompt.confirm('\n .claude/ already exists. Overwrite?');
381
- if (!overwrite) {
471
+ const hasExistingClaudeMd = existsSync(join(projectPath, 'CLAUDE.md'));
472
+ let useMerge = false;
473
+
474
+ if (hasExisting || hasExistingClaudeMd) {
475
+ log('\n Existing configuration detected.\n');
476
+ const mergeChoice = await prompt.select(' How would you like to proceed?', [
477
+ { label: 'Merge', description: 'Use Claude to intelligently merge with existing config (recommended)' },
478
+ { label: 'Overwrite', description: 'Replace all existing configuration' },
479
+ { label: 'Cancel', description: 'Keep existing configuration unchanged' },
480
+ ]);
481
+
482
+ if (mergeChoice.label === 'Cancel') {
382
483
  log('\n Setup cancelled.\n');
383
484
  prompt.close();
384
485
  return null;
385
486
  }
487
+
488
+ useMerge = mergeChoice.label === 'Merge';
489
+
490
+ if (useMerge) {
491
+ log('\n Will use Claude to merge configurations intelligently.\n');
492
+ }
386
493
  }
387
494
 
388
495
  prompt.close();
389
496
 
390
497
  if (dryRun) {
391
- log('\n DRY RUN - Would create:\n');
498
+ const action = useMerge ? 'merge with' : 'create';
499
+ log(`\n DRY RUN - Would ${action}:\n`);
392
500
  log(' .claude/');
393
501
  log(' ├── commands/test.md');
394
502
  log(' ├── commands/lint.md');
@@ -396,30 +504,39 @@ export async function runProjectWizard(options = {}) {
396
504
  log(' ├── commands/setup.md');
397
505
  log(' ├── commands/pr.md');
398
506
  log(' ├── settings.json');
399
- log(' CLAUDE.md\n');
507
+ log(' CLAUDE.md');
508
+ if (useMerge) {
509
+ log('\n Note: Claude will intelligently merge with existing files.\n');
510
+ }
400
511
  return config;
401
512
  }
402
513
 
403
514
  // Create directories
404
515
  mkdirSync(join(claudeDir, 'commands'), { recursive: true });
405
516
 
406
- // Write files
517
+ // Write files with smart merge
407
518
  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) },
519
+ { path: join(projectPath, 'CLAUDE.md'), content: generateClaudeMd(config), type: 'CLAUDE.md' },
520
+ { path: join(claudeDir, 'settings.json'), content: JSON.stringify(generateSettings(config), null, 2), type: 'settings.json' },
521
+ { path: join(claudeDir, 'commands', 'test.md'), content: generateCommandFile('test', config), type: 'command file' },
522
+ { path: join(claudeDir, 'commands', 'lint.md'), content: generateCommandFile('lint', config), type: 'command file' },
523
+ { path: join(claudeDir, 'commands', 'verify.md'), content: generateCommandFile('verify', config), type: 'command file' },
524
+ { path: join(claudeDir, 'commands', 'setup.md'), content: generateCommandFile('setup', config), type: 'command file' },
525
+ { path: join(claudeDir, 'commands', 'pr.md'), content: generateCommandFile('pr', config), type: 'command file' },
415
526
  ];
416
527
 
417
528
  for (const file of files) {
418
- writeFileSync(file.path, file.content);
419
- log(` + ${file.path.replace(projectPath, '.')}`);
529
+ const result = smartWrite(file.path, file.content, file.type, useMerge);
530
+ const symbol = result.merged ? '~' : '+';
531
+ const action = result.merged ? 'merged' : 'created';
532
+ log(` ${symbol} ${file.path.replace(projectPath, '.')} (${action})`);
420
533
  }
421
534
 
422
535
  log('\n Project setup complete!\n');
536
+ if (useMerge) {
537
+ log(' Files were merged with existing configuration.');
538
+ log(' Your custom settings and preferences were preserved.\n');
539
+ }
423
540
  log(' Next steps:');
424
541
  log(' 1. Review CLAUDE.md and adjust as needed');
425
542
  log(' 2. Commit .claude/ to your repository');