ccsetup 1.2.0 → 1.2.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/bin/scan.js DELETED
@@ -1,367 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- const fs = require('fs');
4
- const path = require('path');
5
- const RepositoryScanner = require('../lib/scanner');
6
- const ContextGenerator = require('../lib/contextGenerator');
7
- const ProgressReporter = require('../lib/progressReporter');
8
- const ContextMerger = require('../lib/contextMerger');
9
- const ScanConfigurator = require('../lib/scanConfig');
10
-
11
- const args = process.argv.slice(2);
12
- const flags = {
13
- help: false,
14
- update: false,
15
- dryRun: false,
16
- interactive: false,
17
- depth: 5,
18
- ignore: null,
19
- format: 'md',
20
- mergeStrategy: 'smart'
21
- };
22
-
23
- let scanPath = '.';
24
-
25
- for (let i = 0; i < args.length; i++) {
26
- const arg = args[i];
27
- if (arg === '--help' || arg === '-h') {
28
- flags.help = true;
29
- } else if (arg === '--update') {
30
- flags.update = true;
31
- } else if (arg === '--dry-run') {
32
- flags.dryRun = true;
33
- } else if (arg === '--interactive') {
34
- flags.interactive = true;
35
- } else if (arg === '--depth') {
36
- flags.depth = parseInt(args[i + 1], 10);
37
- i++;
38
- } else if (arg === '--ignore') {
39
- flags.ignore = args[i + 1];
40
- i++;
41
- } else if (arg === '--format') {
42
- flags.format = args[i + 1];
43
- i++;
44
- } else if (arg === '--merge-strategy') {
45
- const strategy = args[i + 1];
46
- const validStrategies = ['smart', 'replace'];
47
- if (validStrategies.includes(strategy)) {
48
- flags.mergeStrategy = strategy;
49
- } else {
50
- console.error(`Error: Invalid merge strategy '${strategy}'. Valid options: ${validStrategies.join(', ')}`);
51
- process.exit(1);
52
- }
53
- i++;
54
- } else if (!arg.startsWith('-')) {
55
- scanPath = arg;
56
- }
57
- }
58
-
59
- if (flags.help) {
60
- console.log(`
61
- Usage: ccsetup scan [path] [options]
62
-
63
- Scan repository and generate Claude Code context
64
-
65
- Arguments:
66
- path Path to scan (default: current directory)
67
-
68
- Options:
69
- --update Update existing CLAUDE.md with new context
70
- --depth <number> Maximum directory depth to scan (default: 5)
71
- --ignore <patterns> Comma-separated ignore patterns
72
- --dry-run Preview changes without applying them
73
- --interactive Interactive scan configuration
74
- --format <type> Output format: md, json, clipboard (default: md)
75
- --merge-strategy <type> Merge strategy: smart, replace (default: smart)
76
- --help, -h Show this help message
77
-
78
- Examples:
79
- ccsetup scan # Scan current directory
80
- ccsetup scan ./my-project # Scan specific directory
81
- ccsetup scan --update # Update existing CLAUDE.md
82
- ccsetup scan --depth 3 # Limit scan depth
83
- ccsetup scan --ignore "test/**,*.log" # Exclude patterns
84
- ccsetup scan --dry-run # Preview what would be scanned
85
- ccsetup scan --interactive # Configure scan interactively
86
- ccsetup scan --format clipboard # Copy results to clipboard
87
- ccsetup scan --merge-strategy replace # Replace existing CLAUDE.md entirely
88
-
89
- The scan command analyzes your project to generate contextual information
90
- for Claude Code, including tech stack, project structure, available commands,
91
- and architectural patterns.
92
- `);
93
- process.exit(0);
94
- }
95
-
96
- async function validateScanPath(scanPath) {
97
- const resolvedPath = path.resolve(scanPath);
98
-
99
- if (!fs.existsSync(resolvedPath)) {
100
- throw new Error(`Path does not exist: ${scanPath}`);
101
- }
102
-
103
- const stats = fs.statSync(resolvedPath);
104
- if (!stats.isDirectory()) {
105
- throw new Error(`Path is not a directory: ${scanPath}`);
106
- }
107
-
108
- return resolvedPath;
109
- }
110
-
111
- async function scanRepository(projectPath, options = {}) {
112
- try {
113
- const progressReporter = new ProgressReporter();
114
- console.log(`\nšŸ” Scanning repository: ${path.relative(process.cwd(), projectPath) || '.'}`);
115
-
116
- const scannerOptions = {
117
- maxDepth: options.depth || 5,
118
- respectGitignore: true,
119
- maxFiles: 1000,
120
- timeout: 30000
121
- };
122
-
123
- if (options.ignore) {
124
- scannerOptions.ignorePatterns = options.ignore.split(',').map(p => p.trim());
125
- }
126
-
127
- const scanner = new RepositoryScanner(projectPath, scannerOptions);
128
- const scanResults = await scanner.scan(progressReporter);
129
-
130
- const contextGenerator = new ContextGenerator(scanResults);
131
- const context = contextGenerator.generate();
132
- const structuredSections = contextGenerator.generateStructuredSections();
133
-
134
- console.log('\nšŸ“Š Detected project details:');
135
- if (context.overview) {
136
- console.log(` ${context.overview}`);
137
- }
138
-
139
- if (context.techStack && context.techStack.frameworks && context.techStack.frameworks.length > 0) {
140
- console.log(` Framework: ${context.techStack.frameworks.join(', ')}`);
141
- }
142
-
143
- if (context.commands && Object.keys(context.commands).length > 0) {
144
- const totalCommands = Object.values(context.commands).reduce((sum, cmds) => sum + cmds.length, 0);
145
- console.log(` Commands: ${totalCommands} available scripts`);
146
- }
147
-
148
- if (context.patterns && Object.keys(context.patterns).length > 0) {
149
- const totalPatterns = Object.values(context.patterns).reduce((sum, patterns) => sum + patterns.length, 0);
150
- console.log(` Patterns: ${totalPatterns} detected architectural patterns`);
151
- }
152
-
153
- return {
154
- scanResults,
155
- contextGenerator,
156
- structuredSections,
157
- formattedContext: contextGenerator.formatForClaude()
158
- };
159
- } catch (error) {
160
- throw new Error(`Repository scanning failed: ${error.message}`);
161
- }
162
- }
163
-
164
- async function handleOutput(context, format, dryRun = false) {
165
- const formattedContext = context.formattedContext;
166
-
167
- switch (format) {
168
- case 'json':
169
- const jsonOutput = JSON.stringify(context.contextGenerator.generate(), null, 2);
170
- if (dryRun) {
171
- console.log('\nšŸ“‹ JSON Output Preview:');
172
- console.log(jsonOutput);
173
- } else {
174
- console.log(jsonOutput);
175
- }
176
- break;
177
-
178
- case 'clipboard':
179
- if (dryRun) {
180
- console.log('\nšŸ“‹ Would copy to clipboard:');
181
- console.log('━'.repeat(60));
182
- console.log(formattedContext);
183
- console.log('━'.repeat(60));
184
- } else {
185
- try {
186
- const clipboardy = require('clipboardy');
187
- await clipboardy.write(formattedContext);
188
- console.log('\nšŸ“‹ Context copied to clipboard!');
189
- } catch (error) {
190
- console.warn('āš ļø Failed to copy to clipboard. Install clipboardy package for clipboard support.');
191
- console.log('\nšŸ“‹ Context output:');
192
- console.log('━'.repeat(60));
193
- console.log(formattedContext);
194
- console.log('━'.repeat(60));
195
- }
196
- }
197
- break;
198
-
199
- case 'md':
200
- default:
201
- console.log('\nšŸ“ Generated context:');
202
- console.log('━'.repeat(60));
203
- console.log(formattedContext);
204
- console.log('━'.repeat(60));
205
- break;
206
- }
207
- }
208
-
209
- async function updateClaudeMd(projectPath, repositoryContext, dryRun = false) {
210
- const formattedContext = repositoryContext.formattedContext;
211
- const claudeFilePath = path.join(projectPath, 'CLAUDE.md');
212
-
213
- if (!fs.existsSync(claudeFilePath)) {
214
- console.log('\nāš ļø CLAUDE.md not found. Creating new file...');
215
-
216
- const templatePath = path.join(__dirname, '..', 'template', 'CLAUDE.md');
217
- let templateContent = '';
218
-
219
- if (fs.existsSync(templatePath)) {
220
- templateContent = fs.readFileSync(templatePath, 'utf8');
221
- } else {
222
- templateContent = `# Claude Code Project Instructions
223
-
224
- ## Project Overview
225
- [Add your project description here]
226
-
227
- ## Development Guidelines
228
- [Add your development guidelines here]
229
-
230
- ## Additional Notes
231
- [Any other important information for Claude to know about this project]
232
- `;
233
- }
234
-
235
- const additionalNotesMarker = '## Additional Notes';
236
- const placeholderContent = '[Any other important information for Claude to know about this project]';
237
-
238
- let enhancedContent;
239
- if (templateContent.includes(additionalNotesMarker)) {
240
- if (templateContent.includes(placeholderContent)) {
241
- enhancedContent = templateContent.replace(placeholderContent, formattedContext.trim());
242
- } else {
243
- const sections = templateContent.split(additionalNotesMarker);
244
- if (sections.length >= 2) {
245
- enhancedContent = sections[0] + additionalNotesMarker + formattedContext + '\n';
246
- } else {
247
- enhancedContent = templateContent + formattedContext;
248
- }
249
- }
250
- } else {
251
- enhancedContent = templateContent + formattedContext;
252
- }
253
-
254
- if (dryRun) {
255
- console.log(`\nšŸ“„ Would create CLAUDE.md with context`);
256
- } else {
257
- fs.writeFileSync(claudeFilePath, enhancedContent, 'utf8');
258
- console.log(`\nāœ… Created CLAUDE.md with project context`);
259
- }
260
- } else {
261
- console.log('\nšŸ“„ Found existing CLAUDE.md');
262
-
263
- const existingContent = fs.readFileSync(claudeFilePath, 'utf8');
264
-
265
- // Try structured sections first, fall back to formatted context
266
- let contextToMerge = repositoryContext.formattedContext;
267
- let usingStructured = false;
268
-
269
- if (repositoryContext.structuredSections &&
270
- typeof repositoryContext.structuredSections === 'object' &&
271
- Object.keys(repositoryContext.structuredSections).length > 0) {
272
- contextToMerge = repositoryContext.structuredSections;
273
- usingStructured = true;
274
- console.log(' šŸ“Š Using intelligent merge with structured sections');
275
- } else {
276
- console.log(' šŸ“ Using fallback merge with formatted content');
277
- }
278
-
279
- const merger = new ContextMerger(existingContent, contextToMerge);
280
- const changes = merger.getChangesSummary();
281
-
282
- if (!changes.hasChanges) {
283
- console.log('āœ… Context is already up to date - no changes needed');
284
- return;
285
- }
286
-
287
- console.log('\nšŸ“Š Analyzing changes...');
288
- console.log(`+ ${changes.added} new sections`);
289
- console.log(`~ ${changes.modified} updated sections`);
290
- console.log(`- ${changes.removed} removed sections`);
291
- console.log(`āœ“ ${changes.unchanged} unchanged sections`);
292
-
293
- const strategyDescriptions = {
294
- 'smart': 'Preserves user content while adding new findings intelligently',
295
- 'replace': 'Replaces CLAUDE.md entirely with new scan results'
296
- };
297
-
298
- console.log(`\nšŸ”„ Using merge strategy: ${flags.mergeStrategy}`);
299
- console.log(` ${strategyDescriptions[flags.mergeStrategy]}`);
300
-
301
- if (dryRun) {
302
- console.log('\nšŸ“„ Would update CLAUDE.md with merged context:');
303
- console.log(merger.generateDiff());
304
- await merger.updateFile(claudeFilePath, flags.mergeStrategy, true);
305
- } else {
306
- console.log('\nšŸ“ Preview changes:');
307
- console.log(merger.generateDiff());
308
-
309
- const backupPath = `${claudeFilePath}.backup.${Date.now()}`;
310
- fs.writeFileSync(backupPath, existingContent, 'utf8');
311
- console.log(`šŸ“¦ Backup created: ${path.basename(backupPath)}`);
312
-
313
- const mergedContent = await merger.updateFile(claudeFilePath, flags.mergeStrategy, false);
314
-
315
- console.log('āœ… CLAUDE.md updated successfully with intelligent merge!');
316
- console.log('\nšŸ“‹ Merge Results:');
317
- if (changes.added > 0) console.log(` + Added ${changes.added} new sections`);
318
- if (changes.modified > 0) console.log(` ~ Updated ${changes.modified} sections using ${flags.mergeStrategy} strategy`);
319
- if (changes.unchanged > 0) console.log(` āœ“ Preserved ${changes.unchanged} existing sections`);
320
- console.log(` šŸ“¦ Backup available: ${path.basename(backupPath)}`);
321
- }
322
- }
323
- }
324
-
325
- async function main() {
326
- try {
327
- const resolvedPath = await validateScanPath(scanPath);
328
- let scanOptions = { ...flags };
329
-
330
- if (flags.interactive) {
331
- const configurator = new ScanConfigurator();
332
- const config = await configurator.configure();
333
-
334
- scanOptions = {
335
- ...flags,
336
- depth: config.depth,
337
- ignore: config.ignore,
338
- maxFiles: config.maxFiles
339
- };
340
-
341
- configurator.showConfigSummary(config);
342
- }
343
-
344
- if (flags.dryRun) {
345
- console.log('šŸ” DRY RUN MODE - No files will be created or modified\n');
346
- }
347
-
348
- const context = await scanRepository(resolvedPath, scanOptions);
349
-
350
- await handleOutput(context, flags.format, flags.dryRun);
351
-
352
- if (flags.update) {
353
- await updateClaudeMd(resolvedPath, context, flags.dryRun);
354
- }
355
-
356
- if (!flags.update && !flags.dryRun && flags.format === 'md') {
357
- console.log('\nšŸ’” To save this context to CLAUDE.md, run:');
358
- console.log(' ccsetup scan --update');
359
- }
360
-
361
- } catch (error) {
362
- console.error(`Error: ${error.message}`);
363
- process.exit(1);
364
- }
365
- }
366
-
367
- main();
@@ -1,112 +0,0 @@
1
- const { execFileSync } = require('child_process');
2
-
3
- class AIMergeHelper {
4
- constructor() {
5
- this.hasClaudeCode = this.checkClaudeCode();
6
- }
7
-
8
- checkClaudeCode() {
9
- try {
10
- execFileSync('which', ['claude'], { stdio: 'ignore' });
11
- return true;
12
- } catch {
13
- return false;
14
- }
15
- }
16
-
17
- async analyzeConflict(conflict) {
18
- if (!this.hasClaudeCode) {
19
- return null;
20
- }
21
-
22
- const prompt = `You are helping merge changes in a CLAUDE.md file. Analyze these two versions and suggest the best approach:
23
-
24
- Section: ${conflict.sectionName}
25
-
26
- EXISTING CONTENT:
27
- ${this.formatContent(conflict.existing)}
28
-
29
- NEW CONTENT FROM SCAN:
30
- ${this.formatContent(conflict.new)}
31
-
32
- Provide a brief analysis (max 3 sentences) explaining:
33
- 1. What's different between the versions
34
- 2. Which version appears more complete/accurate
35
- 3. Whether merging both would be beneficial
36
-
37
- End with one of these recommendations: KEEP_EXISTING, USE_NEW, MERGE_BOTH, or SKIP`;
38
-
39
- try {
40
- const analysis = execFileSync(
41
- 'claude', ['--print', prompt],
42
- { encoding: 'utf8', stdio: ['pipe', 'pipe', 'ignore'], maxBuffer: 1024 * 1024, timeout: 30000 }
43
- ).trim();
44
-
45
- return this.parseAnalysis(analysis);
46
- } catch (error) {
47
- console.warn(' āš ļø AI analysis unavailable, falling back to manual selection');
48
- return null;
49
- }
50
- }
51
-
52
- async generateMergedContent(conflict) {
53
- if (!this.hasClaudeCode) {
54
- return null;
55
- }
56
-
57
- const prompt = `Merge these two versions of content intelligently, preserving important information from both:
58
-
59
- Section: ${conflict.sectionName}
60
-
61
- EXISTING:
62
- ${this.formatContent(conflict.existing)}
63
-
64
- NEW:
65
- ${this.formatContent(conflict.new)}
66
-
67
- Create a merged version that:
68
- - Preserves user customizations from EXISTING
69
- - Adds new information from NEW
70
- - Removes duplicates
71
- - Maintains proper formatting
72
-
73
- Output only the merged content, no explanations.`;
74
-
75
- try {
76
- const merged = execFileSync(
77
- 'claude', ['--print', prompt],
78
- { encoding: 'utf8', stdio: ['pipe', 'pipe', 'ignore'], maxBuffer: 1024 * 1024, timeout: 30000 }
79
- ).trim();
80
-
81
- return merged;
82
- } catch (error) {
83
- return null;
84
- }
85
- }
86
-
87
- formatContent(section) {
88
- if (typeof section === 'string') {
89
- return section;
90
- } else if (section.content) {
91
- if (typeof section.content === 'string') {
92
- return section.content;
93
- } else if (Array.isArray(section.content)) {
94
- return section.content.join('\n');
95
- } else if (typeof section.content === 'object') {
96
- return JSON.stringify(section.content, null, 2);
97
- }
98
- }
99
- return String(section);
100
- }
101
-
102
- parseAnalysis(analysis) {
103
- const recommendation = analysis.match(/\b(KEEP_EXISTING|USE_NEW|MERGE_BOTH|SKIP)\b/);
104
-
105
- return {
106
- analysis: analysis.replace(/\b(KEEP_EXISTING|USE_NEW|MERGE_BOTH|SKIP)\b/g, '').trim(),
107
- recommendation: recommendation ? recommendation[1].toLowerCase().replace('_', '-') : null
108
- };
109
- }
110
- }
111
-
112
- module.exports = AIMergeHelper;