ccsetup 1.1.1 → 1.2.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/README.md +96 -363
- package/bin/create-project.js +1616 -60
- package/bin/lib/claudeInterface.js +209 -0
- package/bin/lib/contextGenerator.js +287 -0
- package/bin/lib/scanner/index.js +28 -0
- package/bin/scan.js +367 -0
- package/lib/aiAgentSelector.js +155 -0
- package/lib/aiMergeHelper.js +112 -0
- package/lib/contextGenerator.js +574 -0
- package/lib/contextMerger.js +812 -0
- package/lib/progressReporter.js +88 -0
- package/lib/scanConfig.js +200 -0
- package/lib/scanner/fileAnalyzer.js +605 -0
- package/lib/scanner/index.js +164 -0
- package/lib/scanner/patterns.js +277 -0
- package/lib/scanner/projectDetector.js +147 -0
- package/lib/templates/README.md +176 -0
- package/lib/templates/catalog.js +230 -0
- package/lib/templates/filter.js +257 -0
- package/lib/templates/index.js +45 -0
- package/lib/templates/metadata/agents.json +413 -0
- package/lib/templates/metadata-extractor.js +329 -0
- package/lib/templates/search.js +356 -0
- package/package.json +11 -4
- package/template/{agents → .claude/agents}/checker.md +29 -0
- package/template/.claude/settings.json +15 -0
- package/template/.claude/skills/prd/SKILL.md +343 -0
- package/template/.claude/skills/ralph/SKILL.md +339 -0
- package/template/CLAUDE.md +39 -21
- package/template/CONTRIBUTING.md +37 -0
- package/template/agents/README.md +15 -171
- package/template/docs/ROADMAP.md +0 -36
- package/template/docs/agent-orchestration.md +24 -141
- package/template/hooks/workflow-selector/index.js +398 -0
- package/template/scripts/ralph/CLAUDE.md +174 -0
- package/template/scripts/ralph/ralph.sh +127 -0
- package/template/tickets/ticket-list.md +17 -68
- package/template/agents/ai-engineer.md +0 -31
- package/template/agents/api-documenter.md +0 -31
- package/template/agents/architect-review.md +0 -42
- package/template/agents/backend-architect.md +0 -29
- package/template/agents/business-analyst.md +0 -34
- package/template/agents/c-pro.md +0 -34
- package/template/agents/cloud-architect.md +0 -31
- package/template/agents/code-reviewer.md +0 -28
- package/template/agents/content-marketer.md +0 -34
- package/template/agents/context-manager.md +0 -63
- package/template/agents/cpp-pro.md +0 -37
- package/template/agents/customer-support.md +0 -34
- package/template/agents/data-engineer.md +0 -31
- package/template/agents/data-scientist.md +0 -28
- package/template/agents/database-admin.md +0 -31
- package/template/agents/database-optimizer.md +0 -31
- package/template/agents/debugger.md +0 -29
- package/template/agents/deployment-engineer.md +0 -31
- package/template/agents/devops-troubleshooter.md +0 -31
- package/template/agents/dx-optimizer.md +0 -62
- package/template/agents/error-detective.md +0 -31
- package/template/agents/frontend-developer.md +0 -30
- package/template/agents/golang-pro.md +0 -31
- package/template/agents/graphql-architect.md +0 -31
- package/template/agents/incident-responder.md +0 -73
- package/template/agents/javascript-pro.md +0 -34
- package/template/agents/legacy-modernizer.md +0 -31
- package/template/agents/ml-engineer.md +0 -31
- package/template/agents/mlops-engineer.md +0 -56
- package/template/agents/mobile-developer.md +0 -31
- package/template/agents/network-engineer.md +0 -31
- package/template/agents/payment-integration.md +0 -31
- package/template/agents/performance-engineer.md +0 -31
- package/template/agents/prompt-engineer.md +0 -58
- package/template/agents/python-pro.md +0 -31
- package/template/agents/quant-analyst.md +0 -31
- package/template/agents/risk-manager.md +0 -40
- package/template/agents/rust-pro.md +0 -34
- package/template/agents/sales-automator.md +0 -34
- package/template/agents/search-specialist.md +0 -58
- package/template/agents/security-auditor.md +0 -31
- package/template/agents/sql-pro.md +0 -34
- package/template/agents/terraform-specialist.md +0 -34
- package/template/agents/test-automator.md +0 -31
- /package/template/{agents → .claude/agents}/backend.md +0 -0
- /package/template/{agents → .claude/agents}/blockchain.md +0 -0
- /package/template/{agents → .claude/agents}/coder.md +0 -0
- /package/template/{agents → .claude/agents}/frontend.md +0 -0
- /package/template/{agents → .claude/agents}/planner.md +0 -0
- /package/template/{agents → .claude/agents}/researcher.md +0 -0
- /package/template/{agents → .claude/agents}/shadcn.md +0 -0
package/bin/create-project.js
CHANGED
|
@@ -3,9 +3,23 @@
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const readline = require('readline');
|
|
6
|
+
const RepositoryScanner = require('../lib/scanner');
|
|
7
|
+
const ContextGenerator = require('../lib/contextGenerator');
|
|
8
|
+
const ContextMerger = require('../lib/contextMerger');
|
|
9
|
+
const ProgressReporter = require('../lib/progressReporter');
|
|
10
|
+
const TemplateCatalog = require('../lib/templates/catalog');
|
|
11
|
+
const TemplateFilter = require('../lib/templates/filter');
|
|
12
|
+
const TemplateSearch = require('../lib/templates/search');
|
|
6
13
|
|
|
7
14
|
// Parse CLI arguments
|
|
8
15
|
const args = process.argv.slice(2);
|
|
16
|
+
|
|
17
|
+
// Handle scan subcommand
|
|
18
|
+
if (args[0] === 'scan') {
|
|
19
|
+
require('./scan.js');
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
9
23
|
const flags = {
|
|
10
24
|
force: false,
|
|
11
25
|
dryRun: false,
|
|
@@ -13,7 +27,11 @@ const flags = {
|
|
|
13
27
|
allAgents: false,
|
|
14
28
|
noAgents: false,
|
|
15
29
|
agents: false,
|
|
16
|
-
browseAgents: false
|
|
30
|
+
browseAgents: false,
|
|
31
|
+
browse: false,
|
|
32
|
+
scanContext: false,
|
|
33
|
+
scanOnly: false,
|
|
34
|
+
prompt: null
|
|
17
35
|
};
|
|
18
36
|
|
|
19
37
|
let projectName = '.';
|
|
@@ -35,6 +53,14 @@ for (let i = 0; i < args.length; i++) {
|
|
|
35
53
|
flags.agents = true;
|
|
36
54
|
} else if (arg === '--browse-agents') {
|
|
37
55
|
flags.browseAgents = true;
|
|
56
|
+
} else if (arg === '--browse') {
|
|
57
|
+
flags.browse = true;
|
|
58
|
+
} else if (arg === '--scan-context') {
|
|
59
|
+
flags.scanContext = true;
|
|
60
|
+
} else if (arg === '--scan-only') {
|
|
61
|
+
flags.scanOnly = true;
|
|
62
|
+
} else if (arg === '--install-hooks') {
|
|
63
|
+
flags.installHooks = true;
|
|
38
64
|
} else if (!arg.startsWith('-')) {
|
|
39
65
|
projectName = arg;
|
|
40
66
|
}
|
|
@@ -44,24 +70,48 @@ for (let i = 0; i < args.length; i++) {
|
|
|
44
70
|
if (flags.help) {
|
|
45
71
|
console.log(`
|
|
46
72
|
Usage: ccsetup [project-name] [options]
|
|
73
|
+
ccsetup scan [path] [options]
|
|
74
|
+
|
|
75
|
+
Commands:
|
|
76
|
+
ccsetup Interactive mode - choose full setup or scan-only
|
|
77
|
+
ccsetup <name> Create a new Claude Code project
|
|
78
|
+
ccsetup scan Advanced repository scanning (see 'ccsetup scan --help')
|
|
47
79
|
|
|
48
80
|
Options:
|
|
49
|
-
--
|
|
50
|
-
--
|
|
51
|
-
--
|
|
52
|
-
--
|
|
53
|
-
--
|
|
54
|
-
--
|
|
55
|
-
--
|
|
81
|
+
--scan-only Skip project setup, only scan and create/update CLAUDE.md ⭐
|
|
82
|
+
--force, -f Skip all prompts and overwrite existing files
|
|
83
|
+
--dry-run, -d Show what would be done without making changes
|
|
84
|
+
--agents Interactive agent selection mode
|
|
85
|
+
--all-agents Include all agents without prompting
|
|
86
|
+
--no-agents Skip agent selection entirely
|
|
87
|
+
--browse-agents Copy all agents to /agents folder for browsing
|
|
88
|
+
--browse Enhanced template browsing and selection interface
|
|
89
|
+
--scan-context Scan repository and add context to CLAUDE.md
|
|
90
|
+
--help, -h Show this help message
|
|
56
91
|
|
|
92
|
+
Advanced:
|
|
93
|
+
--install-hooks Install workflow selection hook to .claude/hooks (optional, power users only)
|
|
94
|
+
|
|
95
|
+
Quick Start:
|
|
96
|
+
npx ccsetup # Interactive mode - choose what to do
|
|
97
|
+
npx ccsetup --scan-only # Just scan and create CLAUDE.md ⭐
|
|
98
|
+
npx ccsetup my-project # Full project setup
|
|
99
|
+
|
|
100
|
+
Scan-Only Mode ⭐:
|
|
101
|
+
Perfect for existing projects! Analyzes your codebase and creates/updates
|
|
102
|
+
CLAUDE.md with project context without modifying your project structure.
|
|
103
|
+
|
|
57
104
|
Examples:
|
|
58
|
-
ccsetup
|
|
105
|
+
ccsetup --scan-only # Scan current directory only
|
|
106
|
+
ccsetup . --scan-only # Same as above
|
|
107
|
+
ccsetup --scan-only --force # Skip confirmation prompts
|
|
108
|
+
ccsetup --scan-only --dry-run # Preview what would happen
|
|
109
|
+
|
|
110
|
+
Full Setup Examples:
|
|
111
|
+
ccsetup # Interactive setup in current directory
|
|
59
112
|
ccsetup my-project # Create in new directory
|
|
60
|
-
ccsetup . --
|
|
61
|
-
ccsetup my-app --dry-run # Preview changes without creating files
|
|
62
|
-
ccsetup --agents # Interactive agent selection only
|
|
113
|
+
ccsetup . --scan-context # Full setup with context scanning
|
|
63
114
|
ccsetup my-app --all-agents # Include all agents automatically
|
|
64
|
-
ccsetup --browse-agents # Copy all agents for manual selection
|
|
65
115
|
`);
|
|
66
116
|
process.exit(0);
|
|
67
117
|
}
|
|
@@ -82,17 +132,221 @@ function validateProjectName(name) {
|
|
|
82
132
|
return true;
|
|
83
133
|
}
|
|
84
134
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
135
|
+
async function scanRepositoryForContext(projectPath) {
|
|
136
|
+
try {
|
|
137
|
+
const progressReporter = new ProgressReporter();
|
|
138
|
+
console.log('🔍 Scanning repository for project context...');
|
|
139
|
+
|
|
140
|
+
const scanner = new RepositoryScanner(projectPath);
|
|
141
|
+
const scanResults = await scanner.scan(progressReporter);
|
|
142
|
+
|
|
143
|
+
const contextGenerator = new ContextGenerator(scanResults);
|
|
144
|
+
const context = contextGenerator.generate();
|
|
145
|
+
const structuredSections = contextGenerator.generateStructuredSections();
|
|
146
|
+
|
|
147
|
+
console.log('\n📊 Detected project details:');
|
|
148
|
+
if (context.overview) {
|
|
149
|
+
console.log(` ${context.overview}`);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (context.techStack && context.techStack.frameworks && context.techStack.frameworks.length > 0) {
|
|
153
|
+
console.log(` Framework: ${context.techStack.frameworks.join(', ')}`);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (context.commands && Object.keys(context.commands).length > 0) {
|
|
157
|
+
const totalCommands = Object.values(context.commands).reduce((sum, cmds) => sum + cmds.length, 0);
|
|
158
|
+
console.log(` Commands: ${totalCommands} available scripts`);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (context.patterns && Object.keys(context.patterns).length > 0) {
|
|
162
|
+
const totalPatterns = Object.values(context.patterns).reduce((sum, patterns) => sum + patterns.length, 0);
|
|
163
|
+
console.log(` Patterns: ${totalPatterns} detected architectural patterns`);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
scanResults,
|
|
168
|
+
contextGenerator,
|
|
169
|
+
structuredSections,
|
|
170
|
+
formattedContext: contextGenerator.formatForClaude()
|
|
171
|
+
};
|
|
172
|
+
} catch (error) {
|
|
173
|
+
console.warn(`⚠️ Repository scanning failed: ${error.message}`);
|
|
174
|
+
console.log(' Continuing with standard setup...\n');
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
89
177
|
}
|
|
90
178
|
|
|
91
|
-
|
|
92
|
-
console.
|
|
93
|
-
|
|
179
|
+
async function previewAndConfirmContext(formattedContext) {
|
|
180
|
+
console.log('\n📝 Generated context preview:');
|
|
181
|
+
console.log('━'.repeat(60));
|
|
182
|
+
console.log(formattedContext);
|
|
183
|
+
console.log('━'.repeat(60));
|
|
184
|
+
|
|
185
|
+
// Use inquirer instead of readline prompt
|
|
186
|
+
const selectModule = await import('@inquirer/select');
|
|
187
|
+
const select = selectModule.default;
|
|
188
|
+
const response = await select({
|
|
189
|
+
message: 'Would you like to add this context to CLAUDE.md?',
|
|
190
|
+
choices: [
|
|
191
|
+
{ name: 'Yes', value: 'y' },
|
|
192
|
+
{ name: 'No', value: 'n' },
|
|
193
|
+
{ name: 'Edit', value: 'edit' }
|
|
194
|
+
],
|
|
195
|
+
default: 'y'
|
|
196
|
+
});
|
|
197
|
+
return response;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
async function shouldScanRepository() {
|
|
201
|
+
console.log('\n🔍 Existing project files detected.');
|
|
202
|
+
// Use inquirer instead of readline prompt
|
|
203
|
+
const confirmModule = await import('@inquirer/confirm');
|
|
204
|
+
const confirm = confirmModule.default;
|
|
205
|
+
const response = await confirm({
|
|
206
|
+
message: 'Would you like to scan the repository to add project context to CLAUDE.md?',
|
|
207
|
+
default: true
|
|
208
|
+
});
|
|
209
|
+
return response;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
async function mergeContextIntelligently(existingContent, repositoryContext, strategy = 'smart') {
|
|
213
|
+
try {
|
|
214
|
+
// Handle replace strategy
|
|
215
|
+
if (strategy === 'replace') {
|
|
216
|
+
console.log(' Replacing with new scan results...');
|
|
217
|
+
// Get template and apply context
|
|
218
|
+
const templatePath = path.join(__dirname, '..', 'template', 'CLAUDE.md');
|
|
219
|
+
let template = '';
|
|
220
|
+
if (fs.existsSync(templatePath)) {
|
|
221
|
+
template = fs.readFileSync(templatePath, 'utf8');
|
|
222
|
+
} else {
|
|
223
|
+
template = `# Claude Code Project Instructions
|
|
224
|
+
|
|
225
|
+
## Project Overview
|
|
226
|
+
[Project description will be added here]
|
|
227
|
+
|
|
228
|
+
## Key Objectives
|
|
229
|
+
[Project objectives will be added here]
|
|
230
|
+
|
|
231
|
+
## Additional Notes
|
|
232
|
+
[Any other important information for Claude to know about this project]
|
|
233
|
+
`;
|
|
234
|
+
}
|
|
235
|
+
return await applyContextToTemplate(template, repositoryContext, 'append');
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Use structured sections for intelligent merge, fall back to formatted context
|
|
239
|
+
let contextToMerge = repositoryContext.formattedContext;
|
|
240
|
+
|
|
241
|
+
if (repositoryContext.structuredSections &&
|
|
242
|
+
typeof repositoryContext.structuredSections === 'object' &&
|
|
243
|
+
Object.keys(repositoryContext.structuredSections).length > 0) {
|
|
244
|
+
contextToMerge = repositoryContext.structuredSections;
|
|
245
|
+
console.log(' 📊 Using intelligent merge with structured sections');
|
|
246
|
+
} else {
|
|
247
|
+
console.log(' 📝 Using fallback merge with formatted content');
|
|
248
|
+
}
|
|
249
|
+
const merger = new ContextMerger(existingContent, contextToMerge);
|
|
250
|
+
const changes = merger.getChangesSummary();
|
|
251
|
+
|
|
252
|
+
if (!changes.hasChanges) {
|
|
253
|
+
console.log(' ✅ Context is already up to date');
|
|
254
|
+
return existingContent;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
console.log('\n 📊 Merge Analysis:');
|
|
258
|
+
if (changes.added > 0) console.log(` + ${changes.added} new sections`);
|
|
259
|
+
if (changes.modified > 0) console.log(` ~ ${changes.modified} updated sections`);
|
|
260
|
+
if (changes.unchanged > 0) console.log(` ✓ ${changes.unchanged} preserved sections`);
|
|
261
|
+
|
|
262
|
+
const mergedContent = await merger.merge(strategy);
|
|
263
|
+
|
|
264
|
+
if (typeof mergedContent === 'string') {
|
|
265
|
+
const existingHeader = merger.extractHeaderContent();
|
|
266
|
+
return existingHeader + '\n\n' + mergedContent;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return mergedContent;
|
|
270
|
+
} catch (error) {
|
|
271
|
+
// Re-throw specific errors that should stop the process
|
|
272
|
+
if (error.message === 'Interactive merge cancelled' ||
|
|
273
|
+
error.message === 'Missing required dependency') {
|
|
274
|
+
throw error;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
console.warn(` ⚠️ Merge failed: ${error.message}`);
|
|
278
|
+
console.log(' 📝 Falling back to simple append...');
|
|
279
|
+
|
|
280
|
+
// Fall back to formatted context for simple append
|
|
281
|
+
const newContext = repositoryContext.formattedContext;
|
|
282
|
+
const additionalNotesMarker = '## Additional Notes';
|
|
283
|
+
const additionalNotesIdx = existingContent.indexOf(additionalNotesMarker);
|
|
284
|
+
|
|
285
|
+
if (additionalNotesIdx === -1) {
|
|
286
|
+
return existingContent.trimEnd() + '\n\n' + additionalNotesMarker + '\n\n' + newContext.trim() + '\n';
|
|
287
|
+
} else {
|
|
288
|
+
const insertIdx = existingContent.indexOf('\n', additionalNotesIdx) + 1;
|
|
289
|
+
return existingContent.substring(0, insertIdx) + '\n' + newContext.trim() + '\n' + existingContent.substring(insertIdx);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
async function applyContextToTemplate(templateContent, repositoryContext, conflictStrategy) {
|
|
295
|
+
const contextContent = repositoryContext.formattedContext;
|
|
296
|
+
const additionalNotesMarker = '## Additional Notes';
|
|
297
|
+
const placeholderContent = '[Any other important information for Claude to know about this project]';
|
|
298
|
+
|
|
299
|
+
if (templateContent.includes(additionalNotesMarker)) {
|
|
300
|
+
if (templateContent.includes(placeholderContent)) {
|
|
301
|
+
return templateContent.replace(placeholderContent, contextContent.trim());
|
|
302
|
+
} else {
|
|
303
|
+
const sections = templateContent.split(additionalNotesMarker);
|
|
304
|
+
if (sections.length >= 2) {
|
|
305
|
+
return sections[0] + additionalNotesMarker + contextContent + '\n';
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return templateContent + contextContent;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Validate conflicting flags
|
|
314
|
+
function validateFlags() {
|
|
315
|
+
// Scan-only conflicts
|
|
316
|
+
if (flags.scanOnly) {
|
|
317
|
+
const conflictingFlags = [];
|
|
318
|
+
if (flags.allAgents) conflictingFlags.push('--all-agents');
|
|
319
|
+
if (flags.noAgents) conflictingFlags.push('--no-agents');
|
|
320
|
+
if (flags.browseAgents) conflictingFlags.push('--browse-agents');
|
|
321
|
+
if (flags.browse) conflictingFlags.push('--browse');
|
|
322
|
+
if (flags.agents) conflictingFlags.push('--agents');
|
|
323
|
+
|
|
324
|
+
if (conflictingFlags.length > 0) {
|
|
325
|
+
console.error(`Error: --scan-only cannot be used with ${conflictingFlags.join(', ')}`);
|
|
326
|
+
console.log('Tip: --scan-only skips all agent-related operations');
|
|
327
|
+
process.exit(1);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Existing validations
|
|
332
|
+
if (flags.allAgents && flags.noAgents) {
|
|
333
|
+
console.error('Error: Cannot use --all-agents and --no-agents together');
|
|
334
|
+
process.exit(1);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
if (flags.browseAgents && (flags.allAgents || flags.noAgents || flags.agents || flags.browse)) {
|
|
338
|
+
console.error('Error: --browse-agents cannot be used with other agent flags');
|
|
339
|
+
process.exit(1);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
if (flags.browse && (flags.allAgents || flags.noAgents || flags.browseAgents)) {
|
|
343
|
+
console.error('Error: --browse cannot be used with other agent flags except --agents');
|
|
344
|
+
process.exit(1);
|
|
345
|
+
}
|
|
94
346
|
}
|
|
95
347
|
|
|
348
|
+
validateFlags();
|
|
349
|
+
|
|
96
350
|
// Validate the project name
|
|
97
351
|
if (projectName !== '.') {
|
|
98
352
|
try {
|
|
@@ -254,6 +508,23 @@ async function initializeClaudeDirectory(selectedAgentFiles, conflictStrategy, d
|
|
|
254
508
|
}
|
|
255
509
|
}
|
|
256
510
|
|
|
511
|
+
// Copy .claude/settings.json
|
|
512
|
+
const settingsSrc = path.join(templateClaudeDir, 'settings.json');
|
|
513
|
+
const settingsDest = path.join(claudeDir, 'settings.json');
|
|
514
|
+
if (fs.existsSync(settingsSrc)) {
|
|
515
|
+
if (!fs.existsSync(settingsDest)) {
|
|
516
|
+
if (!dryRun) {
|
|
517
|
+
fs.copyFileSync(settingsSrc, settingsDest);
|
|
518
|
+
}
|
|
519
|
+
createdItems.push('.claude/settings.json');
|
|
520
|
+
if (dryRun) {
|
|
521
|
+
console.log(' ✨ Would copy: .claude/settings.json');
|
|
522
|
+
}
|
|
523
|
+
} else {
|
|
524
|
+
skippedItems.push('.claude/settings.json');
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
|
|
257
528
|
// Copy .claude/agents/README.md
|
|
258
529
|
const agentsReadmeSrc = path.join(templateClaudeDir, 'agents', 'README.md');
|
|
259
530
|
const agentsReadmeDest = path.join(claudeAgentsDir, 'README.md');
|
|
@@ -303,8 +574,38 @@ async function initializeClaudeDirectory(selectedAgentFiles, conflictStrategy, d
|
|
|
303
574
|
}
|
|
304
575
|
}
|
|
305
576
|
|
|
577
|
+
// Copy .claude/skills/ directory (recursively)
|
|
578
|
+
const templateSkillsDir = path.join(templateClaudeDir, 'skills');
|
|
579
|
+
if (fs.existsSync(templateSkillsDir)) {
|
|
580
|
+
const claudeSkillsDir = path.join(claudeDir, 'skills');
|
|
581
|
+
const skillDirs = fs.readdirSync(templateSkillsDir).filter(d => {
|
|
582
|
+
return fs.statSync(path.join(templateSkillsDir, d)).isDirectory();
|
|
583
|
+
});
|
|
584
|
+
|
|
585
|
+
for (const skillName of skillDirs) {
|
|
586
|
+
const skillSrcDir = path.join(templateSkillsDir, skillName);
|
|
587
|
+
const skillDestDir = path.join(claudeSkillsDir, skillName);
|
|
588
|
+
const skillFile = path.join(skillSrcDir, 'SKILL.md');
|
|
589
|
+
|
|
590
|
+
if (fs.existsSync(skillFile)) {
|
|
591
|
+
if (!fs.existsSync(path.join(skillDestDir, 'SKILL.md'))) {
|
|
592
|
+
if (!dryRun) {
|
|
593
|
+
fs.mkdirSync(skillDestDir, { recursive: true });
|
|
594
|
+
fs.copyFileSync(skillFile, path.join(skillDestDir, 'SKILL.md'));
|
|
595
|
+
}
|
|
596
|
+
createdItems.push(`.claude/skills/${skillName}/SKILL.md`);
|
|
597
|
+
if (dryRun) {
|
|
598
|
+
console.log(` ✨ Would copy: .claude/skills/${skillName}/SKILL.md`);
|
|
599
|
+
}
|
|
600
|
+
} else {
|
|
601
|
+
skippedItems.push(`.claude/skills/${skillName}/SKILL.md`);
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
|
|
306
607
|
// Copy selected agents to .claude/agents
|
|
307
|
-
const templateAgentsDir = path.join(templateDir, 'agents');
|
|
608
|
+
const templateAgentsDir = path.join(templateDir, '.claude', 'agents');
|
|
308
609
|
let copiedAgents = 0;
|
|
309
610
|
let skippedAgents = 0;
|
|
310
611
|
|
|
@@ -426,7 +727,7 @@ function parseAgentFrontmatter(filePath) {
|
|
|
426
727
|
|
|
427
728
|
// Function to get available agents
|
|
428
729
|
function getAvailableAgents() {
|
|
429
|
-
const agentsDir = path.join(templateDir, 'agents');
|
|
730
|
+
const agentsDir = path.join(templateDir, '.claude', 'agents');
|
|
430
731
|
const agents = [];
|
|
431
732
|
|
|
432
733
|
try {
|
|
@@ -451,6 +752,704 @@ function getAvailableAgents() {
|
|
|
451
752
|
return agents.sort((a, b) => a.name.localeCompare(b.name));
|
|
452
753
|
}
|
|
453
754
|
|
|
755
|
+
// Install Claude Code hooks
|
|
756
|
+
async function installClaudeHooks() {
|
|
757
|
+
console.log('\n🪝 Installing Claude Code Workflow Selection Hook...\n');
|
|
758
|
+
|
|
759
|
+
const claudeDir = path.join(process.cwd(), '.claude');
|
|
760
|
+
const hooksDir = path.join(claudeDir, 'hooks');
|
|
761
|
+
const settingsFile = path.join(claudeDir, 'settings.json');
|
|
762
|
+
|
|
763
|
+
// Check if .claude directory exists
|
|
764
|
+
if (!fs.existsSync(claudeDir)) {
|
|
765
|
+
console.error('❌ Error: .claude directory not found.');
|
|
766
|
+
console.log('\n💡 Please run "claude init" first to initialize Claude Code in this directory.\n');
|
|
767
|
+
return;
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
try {
|
|
771
|
+
// Create hooks directory
|
|
772
|
+
if (!fs.existsSync(hooksDir)) {
|
|
773
|
+
fs.mkdirSync(hooksDir, { recursive: true });
|
|
774
|
+
console.log('✅ Created .claude/hooks directory');
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
// Copy workflow-selector hook
|
|
778
|
+
const hookSourceDir = path.join(templateDir, 'hooks', 'workflow-selector');
|
|
779
|
+
const hookDestDir = path.join(hooksDir, 'workflow-selector');
|
|
780
|
+
|
|
781
|
+
if (!fs.existsSync(hookSourceDir)) {
|
|
782
|
+
console.error('❌ Error: Hook source files not found in template.');
|
|
783
|
+
return;
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
// Check if hook already exists
|
|
787
|
+
const hookFile = path.join(hookSourceDir, 'index.js');
|
|
788
|
+
const destFile = path.join(hookDestDir, 'index.js');
|
|
789
|
+
let shouldCopyHook = true;
|
|
790
|
+
|
|
791
|
+
if (fs.existsSync(destFile)) {
|
|
792
|
+
console.log('⚠️ Workflow-selector hook already exists.');
|
|
793
|
+
const selectModule = await import('@inquirer/select');
|
|
794
|
+
const select = selectModule.default;
|
|
795
|
+
|
|
796
|
+
const action = await select({
|
|
797
|
+
message: 'How would you like to proceed?',
|
|
798
|
+
choices: [
|
|
799
|
+
{
|
|
800
|
+
name: '📋 Keep existing - Preserve your customizations',
|
|
801
|
+
value: 'keep',
|
|
802
|
+
description: 'Keep your existing hook file unchanged'
|
|
803
|
+
},
|
|
804
|
+
{
|
|
805
|
+
name: '🔄 Update - Replace with latest version',
|
|
806
|
+
value: 'replace',
|
|
807
|
+
description: 'Replace with the latest hook (backup will be created)'
|
|
808
|
+
},
|
|
809
|
+
{
|
|
810
|
+
name: '👀 Compare - View differences first',
|
|
811
|
+
value: 'compare',
|
|
812
|
+
description: 'Compare existing and new versions before deciding'
|
|
813
|
+
},
|
|
814
|
+
{
|
|
815
|
+
name: '❌ Skip - Cancel hook installation',
|
|
816
|
+
value: 'skip',
|
|
817
|
+
description: 'Skip installing the hook file'
|
|
818
|
+
}
|
|
819
|
+
]
|
|
820
|
+
});
|
|
821
|
+
|
|
822
|
+
if (action === 'skip') {
|
|
823
|
+
console.log('⏭️ Skipped hook file installation');
|
|
824
|
+
shouldCopyHook = false;
|
|
825
|
+
} else if (action === 'keep') {
|
|
826
|
+
console.log('✅ Keeping existing hook file');
|
|
827
|
+
shouldCopyHook = false;
|
|
828
|
+
} else if (action === 'compare') {
|
|
829
|
+
// Show comparison
|
|
830
|
+
console.log('\n📄 Existing hook file summary:');
|
|
831
|
+
const existingContent = fs.readFileSync(destFile, 'utf8');
|
|
832
|
+
const existingLines = existingContent.split('\n').length;
|
|
833
|
+
console.log(` Lines: ${existingLines}`);
|
|
834
|
+
console.log(` Size: ${fs.statSync(destFile).size} bytes`);
|
|
835
|
+
console.log(` Modified: ${fs.statSync(destFile).mtime.toLocaleString()}`);
|
|
836
|
+
|
|
837
|
+
const confirmModule = await import('@inquirer/confirm');
|
|
838
|
+
const confirm = confirmModule.default;
|
|
839
|
+
const shouldReplace = await confirm({
|
|
840
|
+
message: 'Replace with new version?',
|
|
841
|
+
default: false
|
|
842
|
+
});
|
|
843
|
+
|
|
844
|
+
if (shouldReplace) {
|
|
845
|
+
// Create backup
|
|
846
|
+
const backupFile = destFile + '.backup-' + Date.now();
|
|
847
|
+
fs.copyFileSync(destFile, backupFile);
|
|
848
|
+
console.log(`✅ Created backup: ${path.basename(backupFile)}`);
|
|
849
|
+
shouldCopyHook = true;
|
|
850
|
+
} else {
|
|
851
|
+
shouldCopyHook = false;
|
|
852
|
+
}
|
|
853
|
+
} else if (action === 'replace') {
|
|
854
|
+
// Create backup
|
|
855
|
+
const backupFile = destFile + '.backup-' + Date.now();
|
|
856
|
+
fs.copyFileSync(destFile, backupFile);
|
|
857
|
+
console.log(`✅ Created backup: ${path.basename(backupFile)}`);
|
|
858
|
+
shouldCopyHook = true;
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
// Create workflow-selector directory if needed
|
|
863
|
+
if (!fs.existsSync(hookDestDir)) {
|
|
864
|
+
fs.mkdirSync(hookDestDir, { recursive: true });
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
// Copy hook file if needed
|
|
868
|
+
if (shouldCopyHook) {
|
|
869
|
+
fs.copyFileSync(hookFile, destFile);
|
|
870
|
+
console.log('✅ Installed workflow-selector hook');
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
// Update settings.json
|
|
874
|
+
let settings = {};
|
|
875
|
+
if (fs.existsSync(settingsFile)) {
|
|
876
|
+
const content = fs.readFileSync(settingsFile, 'utf8');
|
|
877
|
+
try {
|
|
878
|
+
settings = JSON.parse(content);
|
|
879
|
+
} catch (e) {
|
|
880
|
+
console.warn('⚠️ Warning: Could not parse existing settings.json, creating new one');
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
// Add hook configuration intelligently
|
|
885
|
+
if (!settings.hooks) {
|
|
886
|
+
settings.hooks = {};
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
// Check if UserPromptSubmit hooks already exist
|
|
890
|
+
const workflowHookCommand = "node $CLAUDE_PROJECT_DIR/.claude/hooks/workflow-selector/index.js";
|
|
891
|
+
let hookExists = false;
|
|
892
|
+
|
|
893
|
+
if (settings.hooks.UserPromptSubmit && Array.isArray(settings.hooks.UserPromptSubmit)) {
|
|
894
|
+
// Check if our hook is already configured
|
|
895
|
+
hookExists = settings.hooks.UserPromptSubmit.some(hookConfig =>
|
|
896
|
+
hookConfig.hooks && hookConfig.hooks.some(hook =>
|
|
897
|
+
hook.type === 'command' && hook.command === workflowHookCommand
|
|
898
|
+
)
|
|
899
|
+
);
|
|
900
|
+
|
|
901
|
+
if (hookExists) {
|
|
902
|
+
console.log('✅ Workflow hook already configured in settings.json');
|
|
903
|
+
} else {
|
|
904
|
+
// Ask user how to proceed
|
|
905
|
+
const selectModule = await import('@inquirer/select');
|
|
906
|
+
const select = selectModule.default;
|
|
907
|
+
|
|
908
|
+
console.log('\n⚠️ Existing UserPromptSubmit hooks detected in settings.json');
|
|
909
|
+
const action = await select({
|
|
910
|
+
message: 'How would you like to add the workflow hook?',
|
|
911
|
+
choices: [
|
|
912
|
+
{
|
|
913
|
+
name: '➕ Add to existing - Preserve current hooks and add workflow hook',
|
|
914
|
+
value: 'add',
|
|
915
|
+
description: 'Keep all existing hooks and add the workflow hook'
|
|
916
|
+
},
|
|
917
|
+
{
|
|
918
|
+
name: '🔄 Replace all - Replace existing hooks with workflow hook',
|
|
919
|
+
value: 'replace',
|
|
920
|
+
description: 'Replace all existing hooks (backup will be created)'
|
|
921
|
+
},
|
|
922
|
+
{
|
|
923
|
+
name: '❌ Skip - Don\'t modify hooks configuration',
|
|
924
|
+
value: 'skip',
|
|
925
|
+
description: 'Keep settings.json unchanged'
|
|
926
|
+
}
|
|
927
|
+
]
|
|
928
|
+
});
|
|
929
|
+
|
|
930
|
+
if (action === 'add') {
|
|
931
|
+
// Add our hook to existing array
|
|
932
|
+
settings.hooks.UserPromptSubmit.push({
|
|
933
|
+
"matcher": ".*",
|
|
934
|
+
"hooks": [
|
|
935
|
+
{
|
|
936
|
+
"type": "command",
|
|
937
|
+
"command": workflowHookCommand
|
|
938
|
+
}
|
|
939
|
+
]
|
|
940
|
+
});
|
|
941
|
+
console.log('✅ Added workflow hook to existing hooks');
|
|
942
|
+
} else if (action === 'replace') {
|
|
943
|
+
// Backup existing settings
|
|
944
|
+
const backupFile = settingsFile + '.backup-' + Date.now();
|
|
945
|
+
fs.writeFileSync(backupFile, JSON.stringify(settings, null, 2));
|
|
946
|
+
console.log(`✅ Created settings backup: ${path.basename(backupFile)}`);
|
|
947
|
+
|
|
948
|
+
// Replace with our hook
|
|
949
|
+
settings.hooks.UserPromptSubmit = [
|
|
950
|
+
{
|
|
951
|
+
"matcher": ".*",
|
|
952
|
+
"hooks": [
|
|
953
|
+
{
|
|
954
|
+
"type": "command",
|
|
955
|
+
"command": workflowHookCommand
|
|
956
|
+
}
|
|
957
|
+
]
|
|
958
|
+
}
|
|
959
|
+
];
|
|
960
|
+
console.log('✅ Replaced existing hooks with workflow hook');
|
|
961
|
+
} else {
|
|
962
|
+
console.log('⏭️ Skipped settings.json modification');
|
|
963
|
+
return;
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
} else {
|
|
967
|
+
// No existing UserPromptSubmit hooks, safe to add
|
|
968
|
+
settings.hooks.UserPromptSubmit = [
|
|
969
|
+
{
|
|
970
|
+
"matcher": ".*",
|
|
971
|
+
"hooks": [
|
|
972
|
+
{
|
|
973
|
+
"type": "command",
|
|
974
|
+
"command": workflowHookCommand
|
|
975
|
+
}
|
|
976
|
+
]
|
|
977
|
+
}
|
|
978
|
+
];
|
|
979
|
+
console.log('✅ Added workflow hook configuration');
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
// Write updated settings only if we made changes
|
|
983
|
+
if (!hookExists) {
|
|
984
|
+
fs.writeFileSync(settingsFile, JSON.stringify(settings, null, 2));
|
|
985
|
+
console.log('✅ Updated .claude/settings.json');
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
// Copy agent-orchestration.md if it doesn't exist
|
|
989
|
+
const orchestrationSource = path.join(templateDir, 'docs', 'agent-orchestration.md');
|
|
990
|
+
const orchestrationDest = path.join(process.cwd(), 'docs', 'agent-orchestration.md');
|
|
991
|
+
|
|
992
|
+
if (!fs.existsSync(orchestrationDest) && fs.existsSync(orchestrationSource)) {
|
|
993
|
+
const docsDir = path.join(process.cwd(), 'docs');
|
|
994
|
+
if (!fs.existsSync(docsDir)) {
|
|
995
|
+
fs.mkdirSync(docsDir, { recursive: true });
|
|
996
|
+
}
|
|
997
|
+
fs.copyFileSync(orchestrationSource, orchestrationDest);
|
|
998
|
+
console.log('✅ Copied agent-orchestration.md to docs/');
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
console.log('\n🎉 Workflow selection hook installed successfully!\n');
|
|
1002
|
+
console.log('The hook will:');
|
|
1003
|
+
console.log(' • Analyze your prompts to determine task type');
|
|
1004
|
+
console.log(' • Suggest appropriate workflows (Feature Development, Bug Fix, etc.)');
|
|
1005
|
+
console.log(' • Generate relevant todo items');
|
|
1006
|
+
console.log(' • Guide agent selection based on the task\n');
|
|
1007
|
+
console.log('📝 Note: The hook reads workflows from docs/agent-orchestration.md');
|
|
1008
|
+
console.log('💡 You can disable the hook by editing .claude/settings.json\n');
|
|
1009
|
+
|
|
1010
|
+
} catch (error) {
|
|
1011
|
+
console.error('❌ Error installing hooks:', error.message);
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
// Setup mode selection function
|
|
1016
|
+
async function selectSetupMode() {
|
|
1017
|
+
// Direct flag handling
|
|
1018
|
+
if (flags.installHooks) return 'install-hooks';
|
|
1019
|
+
if (flags.scanOnly) return 'scan-only';
|
|
1020
|
+
if (flags.agents) return 'agents-only';
|
|
1021
|
+
if (flags.force || flags.browseAgents || flags.allAgents || flags.noAgents) return 'full';
|
|
1022
|
+
|
|
1023
|
+
// Skip selection if already in a specific mode
|
|
1024
|
+
if (projectName !== '.' || flags.scanContext) return 'full';
|
|
1025
|
+
|
|
1026
|
+
// Interactive selection for bare 'npx ccsetup'
|
|
1027
|
+
console.log('Welcome to ccsetup! 🎉\n');
|
|
1028
|
+
console.log('This tool helps you set up and maintain your Claude Code project.');
|
|
1029
|
+
console.log('Let\'s analyze your repository and create the perfect CLAUDE.md file.\n');
|
|
1030
|
+
|
|
1031
|
+
// Check if CLAUDE.md exists to provide context
|
|
1032
|
+
const claudeMdExists = fs.existsSync(path.join(process.cwd(), 'CLAUDE.md'));
|
|
1033
|
+
if (claudeMdExists) {
|
|
1034
|
+
console.log('📋 Existing CLAUDE.md detected in this directory.\n');
|
|
1035
|
+
} else {
|
|
1036
|
+
console.log('📄 No CLAUDE.md found. Let\'s create one!\n');
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
// Dynamic import for ESM module
|
|
1040
|
+
const selectModule = await import('@inquirer/select');
|
|
1041
|
+
const select = selectModule.default;
|
|
1042
|
+
|
|
1043
|
+
const mode = await select({
|
|
1044
|
+
message: claudeMdExists ?
|
|
1045
|
+
'CLAUDE.md exists. How would you like to proceed?' :
|
|
1046
|
+
'What would you like to do?',
|
|
1047
|
+
choices: claudeMdExists ? [
|
|
1048
|
+
{
|
|
1049
|
+
name: 'Smart Merge - Keep my content, add new findings',
|
|
1050
|
+
value: 'scan-smart',
|
|
1051
|
+
description: 'Preserves your customizations while adding newly detected information'
|
|
1052
|
+
},
|
|
1053
|
+
{
|
|
1054
|
+
name: 'Replace - Fresh scan, replace existing',
|
|
1055
|
+
value: 'scan-replace',
|
|
1056
|
+
description: 'Creates a brand new CLAUDE.md from your current codebase'
|
|
1057
|
+
},
|
|
1058
|
+
{
|
|
1059
|
+
name: 'Full Setup - Add agents and project structure',
|
|
1060
|
+
value: 'full',
|
|
1061
|
+
description: 'Creates the complete Claude Code boilerplate structure with agents, docs, tickets, and plans'
|
|
1062
|
+
}
|
|
1063
|
+
] : [
|
|
1064
|
+
{
|
|
1065
|
+
name: '🚀 Quick Start - Just create CLAUDE.md',
|
|
1066
|
+
value: 'scan-smart',
|
|
1067
|
+
description: 'Scans your code and creates CLAUDE.md with project context'
|
|
1068
|
+
},
|
|
1069
|
+
{
|
|
1070
|
+
name: '🏗️ Full Setup - Complete Claude Code structure',
|
|
1071
|
+
value: 'full',
|
|
1072
|
+
description: 'Creates CLAUDE.md plus agents, docs, tickets, and plans'
|
|
1073
|
+
}
|
|
1074
|
+
]
|
|
1075
|
+
});
|
|
1076
|
+
|
|
1077
|
+
return mode;
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
// Validate scan-only environment
|
|
1081
|
+
async function validateScanOnlyEnvironment() {
|
|
1082
|
+
// Check directory exists and is accessible
|
|
1083
|
+
try {
|
|
1084
|
+
await fs.promises.access(targetDir, fs.constants.R_OK | fs.constants.W_OK);
|
|
1085
|
+
} catch (error) {
|
|
1086
|
+
let message = 'Cannot access target directory';
|
|
1087
|
+
let suggestion = 'Check directory permissions';
|
|
1088
|
+
|
|
1089
|
+
if (error.code === 'ENOENT') {
|
|
1090
|
+
message = 'Target directory does not exist';
|
|
1091
|
+
suggestion = 'Create the directory first or check the path';
|
|
1092
|
+
} else if (error.code === 'EACCES') {
|
|
1093
|
+
message = 'Permission denied accessing target directory';
|
|
1094
|
+
suggestion = 'Run with appropriate permissions or use sudo';
|
|
1095
|
+
} else if (error.code === 'ENOTDIR') {
|
|
1096
|
+
message = 'Target path is not a directory';
|
|
1097
|
+
suggestion = 'Specify a valid directory path';
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
return {
|
|
1101
|
+
valid: false,
|
|
1102
|
+
message,
|
|
1103
|
+
suggestion
|
|
1104
|
+
};
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
// Check if CLAUDE.md exists and is writable
|
|
1108
|
+
const claudeMdPath = path.join(targetDir, 'CLAUDE.md');
|
|
1109
|
+
if (fs.existsSync(claudeMdPath)) {
|
|
1110
|
+
try {
|
|
1111
|
+
await fs.promises.access(claudeMdPath, fs.constants.R_OK | fs.constants.W_OK);
|
|
1112
|
+
} catch (error) {
|
|
1113
|
+
return {
|
|
1114
|
+
valid: false,
|
|
1115
|
+
message: 'Cannot access existing CLAUDE.md file',
|
|
1116
|
+
suggestion: 'Check file permissions or run with appropriate access rights'
|
|
1117
|
+
};
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
// Check if directory is empty (warning only)
|
|
1122
|
+
const files = await fs.promises.readdir(targetDir);
|
|
1123
|
+
const hasFiles = files.some(f => !f.startsWith('.') && f !== 'node_modules');
|
|
1124
|
+
|
|
1125
|
+
if (!hasFiles && projectName === '.') {
|
|
1126
|
+
console.log('⚠️ Warning: Current directory appears to be empty.');
|
|
1127
|
+
console.log(' Scan results may be limited.\n');
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
return { valid: true };
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
// Get CLAUDE.md template
|
|
1134
|
+
async function getClaudeMdTemplate() {
|
|
1135
|
+
const templatePath = path.join(__dirname, '..', 'template', 'CLAUDE.md');
|
|
1136
|
+
try {
|
|
1137
|
+
return await fs.promises.readFile(templatePath, 'utf8');
|
|
1138
|
+
} catch (error) {
|
|
1139
|
+
// Fallback to minimal template
|
|
1140
|
+
return `# Claude Code Project Instructions
|
|
1141
|
+
|
|
1142
|
+
## Project Overview
|
|
1143
|
+
[Project description will be added here]
|
|
1144
|
+
|
|
1145
|
+
## Key Objectives
|
|
1146
|
+
[Project objectives will be added here]
|
|
1147
|
+
|
|
1148
|
+
## Additional Notes
|
|
1149
|
+
[Any other important information for Claude to know about this project]
|
|
1150
|
+
`;
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
// Scan-only mode implementation
|
|
1155
|
+
async function scanOnlyMode(defaultMergeStrategy = 'smart') {
|
|
1156
|
+
console.log('\n🔍 Repository Scan Mode\n');
|
|
1157
|
+
|
|
1158
|
+
// Provide context based on merge strategy
|
|
1159
|
+
switch (defaultMergeStrategy) {
|
|
1160
|
+
case 'smart':
|
|
1161
|
+
console.log('Smart Merge Mode');
|
|
1162
|
+
console.log('Intelligently merges new findings with your existing CLAUDE.md');
|
|
1163
|
+
console.log('Preserves your customizations while adding newly detected information\n');
|
|
1164
|
+
break;
|
|
1165
|
+
case 'replace':
|
|
1166
|
+
console.log('Replace Mode');
|
|
1167
|
+
console.log('Creates a brand new CLAUDE.md from your current codebase');
|
|
1168
|
+
console.log('Perfect for: Starting fresh or major project restructuring\n');
|
|
1169
|
+
break;
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
console.log('This will analyze your repository and update CLAUDE.md with:');
|
|
1173
|
+
console.log(' • Project structure and organization');
|
|
1174
|
+
console.log(' • Technology stack and dependencies');
|
|
1175
|
+
console.log(' • Available commands and scripts');
|
|
1176
|
+
console.log(' • Architectural patterns detected');
|
|
1177
|
+
console.log(' • Important context for Claude\n');
|
|
1178
|
+
|
|
1179
|
+
// Validate environment
|
|
1180
|
+
const validation = await validateScanOnlyEnvironment();
|
|
1181
|
+
if (!validation.valid) {
|
|
1182
|
+
console.error(`❌ ${validation.message}`);
|
|
1183
|
+
if (validation.suggestion) {
|
|
1184
|
+
console.log(`💡 ${validation.suggestion}`);
|
|
1185
|
+
}
|
|
1186
|
+
return;
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
// Confirmation prompt (unless --force)
|
|
1190
|
+
if (!flags.force && !flags.dryRun) {
|
|
1191
|
+
// Create readline interface if needed
|
|
1192
|
+
if (!rl) {
|
|
1193
|
+
rl = readline.createInterface({
|
|
1194
|
+
input: process.stdin,
|
|
1195
|
+
output: process.stdout
|
|
1196
|
+
});
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
// Use inquirer instead of readline prompt
|
|
1200
|
+
const confirmModule = await import('@inquirer/confirm');
|
|
1201
|
+
const confirm = confirmModule.default;
|
|
1202
|
+
const shouldContinue = await confirm({
|
|
1203
|
+
message: 'Continue?',
|
|
1204
|
+
default: true
|
|
1205
|
+
});
|
|
1206
|
+
if (!shouldContinue) {
|
|
1207
|
+
console.log('Setup cancelled.');
|
|
1208
|
+
if (rl) rl.close();
|
|
1209
|
+
return;
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
// Check if directory has meaningful files
|
|
1214
|
+
const files = await fs.promises.readdir(targetDir);
|
|
1215
|
+
const hasFiles = files.some(f => !f.startsWith('.') && f !== 'node_modules');
|
|
1216
|
+
|
|
1217
|
+
// Perform repository scan
|
|
1218
|
+
let repositoryContext = null;
|
|
1219
|
+
try {
|
|
1220
|
+
repositoryContext = await scanRepositoryForContext(targetDir);
|
|
1221
|
+
} catch (error) {
|
|
1222
|
+
console.error('❌ Repository scanning failed:', error.message);
|
|
1223
|
+
if (rl) rl.close();
|
|
1224
|
+
return;
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
if (!repositoryContext) {
|
|
1228
|
+
// Handle empty directory case
|
|
1229
|
+
if (!hasFiles && projectName === '.') {
|
|
1230
|
+
console.log('📝 Since the directory is empty, would you like to:');
|
|
1231
|
+
console.log('1) Create a minimal CLAUDE.md with basic template');
|
|
1232
|
+
console.log('2) Cancel and run full setup instead');
|
|
1233
|
+
|
|
1234
|
+
// Use inquirer instead of readline prompt
|
|
1235
|
+
const selectModule = await import('@inquirer/select');
|
|
1236
|
+
const select = selectModule.default;
|
|
1237
|
+
const choice = await select({
|
|
1238
|
+
message: 'Choose an option:',
|
|
1239
|
+
choices: [
|
|
1240
|
+
{ name: '1) Continue with minimal context', value: '1' },
|
|
1241
|
+
{ name: '2) Cancel and run full setup instead', value: '2' }
|
|
1242
|
+
]
|
|
1243
|
+
});
|
|
1244
|
+
|
|
1245
|
+
if (choice === '2') {
|
|
1246
|
+
console.log('\n💡 Run `npx ccsetup` without --scan-only for full project setup.');
|
|
1247
|
+
if (rl) rl.close();
|
|
1248
|
+
return;
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
// Continue with minimal context
|
|
1252
|
+
repositoryContext = {
|
|
1253
|
+
formattedContext: `
|
|
1254
|
+
## Additional Notes
|
|
1255
|
+
|
|
1256
|
+
This project directory was empty when scanned.
|
|
1257
|
+
Please update this file with relevant project information as you develop.
|
|
1258
|
+
|
|
1259
|
+
### Getting Started
|
|
1260
|
+
- Add project description above
|
|
1261
|
+
- Define key objectives
|
|
1262
|
+
- Document important conventions
|
|
1263
|
+
- Update as the project evolves
|
|
1264
|
+
`
|
|
1265
|
+
};
|
|
1266
|
+
} else {
|
|
1267
|
+
console.log('❌ Unable to generate context from repository.');
|
|
1268
|
+
console.log('💡 Tip: Make sure you\'re in a valid project directory.');
|
|
1269
|
+
if (rl) rl.close();
|
|
1270
|
+
return;
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
// Preview and confirm context
|
|
1275
|
+
if (!flags.force && !flags.dryRun) {
|
|
1276
|
+
const confirmResult = await previewAndConfirmContext(repositoryContext.formattedContext);
|
|
1277
|
+
if (confirmResult === 'n' || confirmResult === 'no') {
|
|
1278
|
+
console.log('✅ Setup cancelled.');
|
|
1279
|
+
if (rl) rl.close();
|
|
1280
|
+
return;
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
// Handle CLAUDE.md creation/update
|
|
1285
|
+
const claudeMdPath = path.join(targetDir, 'CLAUDE.md');
|
|
1286
|
+
const exists = fs.existsSync(claudeMdPath);
|
|
1287
|
+
let backupPath = null;
|
|
1288
|
+
let mergeStrategy = 'smart'; // Default merge strategy
|
|
1289
|
+
|
|
1290
|
+
try {
|
|
1291
|
+
if (exists) {
|
|
1292
|
+
// Update existing CLAUDE.md
|
|
1293
|
+
if (!flags.force && !flags.dryRun) {
|
|
1294
|
+
console.log('\n📋 Existing CLAUDE.md detected!');
|
|
1295
|
+
|
|
1296
|
+
// Use the default merge strategy if provided
|
|
1297
|
+
if (defaultMergeStrategy === 'replace') {
|
|
1298
|
+
console.log('This will replace your existing CLAUDE.md with a fresh scan.');
|
|
1299
|
+
console.log(' Your current content will be backed up first.');
|
|
1300
|
+
const confirmModule = await import('@inquirer/confirm');
|
|
1301
|
+
const confirm = confirmModule.default;
|
|
1302
|
+
const shouldProceed = await confirm({
|
|
1303
|
+
message: 'Proceed with replacement?',
|
|
1304
|
+
default: false
|
|
1305
|
+
});
|
|
1306
|
+
if (!shouldProceed) {
|
|
1307
|
+
console.log('Cancelled.');
|
|
1308
|
+
if (rl) rl.close();
|
|
1309
|
+
return;
|
|
1310
|
+
}
|
|
1311
|
+
mergeStrategy = 'replace';
|
|
1312
|
+
} else {
|
|
1313
|
+
console.log('The scan will:');
|
|
1314
|
+
console.log(' • Preserve all your existing content');
|
|
1315
|
+
console.log(' • Add new findings from the scan');
|
|
1316
|
+
console.log(' • Create an automatic backup');
|
|
1317
|
+
|
|
1318
|
+
const confirmModule = await import('@inquirer/confirm');
|
|
1319
|
+
const confirm = confirmModule.default;
|
|
1320
|
+
const shouldContinue = await confirm({
|
|
1321
|
+
message: 'Continue with smart merge?',
|
|
1322
|
+
default: true
|
|
1323
|
+
});
|
|
1324
|
+
if (!shouldContinue) {
|
|
1325
|
+
console.log('Update cancelled.');
|
|
1326
|
+
if (rl) rl.close();
|
|
1327
|
+
return;
|
|
1328
|
+
}
|
|
1329
|
+
mergeStrategy = 'smart';
|
|
1330
|
+
}
|
|
1331
|
+
} else {
|
|
1332
|
+
mergeStrategy = defaultMergeStrategy;
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
console.log('\n📄 Updating existing CLAUDE.md...');
|
|
1336
|
+
|
|
1337
|
+
if (!flags.dryRun) {
|
|
1338
|
+
// Check file size before processing
|
|
1339
|
+
const stats = fs.statSync(claudeMdPath);
|
|
1340
|
+
if (stats.size > 1024 * 1024) { // 1MB
|
|
1341
|
+
console.warn('⚠️ Warning: CLAUDE.md is large (>1MB). Processing may take longer.');
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
let existingContent;
|
|
1345
|
+
try {
|
|
1346
|
+
existingContent = fs.readFileSync(claudeMdPath, 'utf8');
|
|
1347
|
+
} catch (error) {
|
|
1348
|
+
throw new Error(`Failed to read existing CLAUDE.md: ${error.message}`);
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
// Validate content
|
|
1352
|
+
if (!existingContent) {
|
|
1353
|
+
console.warn('⚠️ Warning: Existing CLAUDE.md appears to be empty.');
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
// Create backup first
|
|
1357
|
+
backupPath = `${claudeMdPath}.backup.${Date.now()}`;
|
|
1358
|
+
try {
|
|
1359
|
+
fs.writeFileSync(backupPath, existingContent);
|
|
1360
|
+
console.log(`📦 Backup created: ${path.basename(backupPath)}`);
|
|
1361
|
+
} catch (error) {
|
|
1362
|
+
throw new Error(`Failed to create backup: ${error.message}`);
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
// Use intelligent merge with backup
|
|
1366
|
+
let updatedContent;
|
|
1367
|
+
try {
|
|
1368
|
+
updatedContent = await mergeContextIntelligently(existingContent, repositoryContext, mergeStrategy);
|
|
1369
|
+
} catch (mergeError) {
|
|
1370
|
+
if (mergeError.message === 'Interactive merge cancelled') {
|
|
1371
|
+
console.log('💾 Your original CLAUDE.md is unchanged.');
|
|
1372
|
+
console.log(`📦 Scan results backup: ${path.basename(backupPath)}`);
|
|
1373
|
+
if (rl) rl.close();
|
|
1374
|
+
return;
|
|
1375
|
+
} else if (mergeError.message === 'Missing required dependency') {
|
|
1376
|
+
console.error('Please install missing dependencies and try again.');
|
|
1377
|
+
if (rl) rl.close();
|
|
1378
|
+
return;
|
|
1379
|
+
}
|
|
1380
|
+
throw mergeError;
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
try {
|
|
1384
|
+
fs.writeFileSync(claudeMdPath, updatedContent, 'utf8');
|
|
1385
|
+
} catch (error) {
|
|
1386
|
+
// Restore from backup if write fails
|
|
1387
|
+
try {
|
|
1388
|
+
fs.writeFileSync(claudeMdPath, existingContent, 'utf8');
|
|
1389
|
+
console.error('❌ Failed to update CLAUDE.md. Original content restored.');
|
|
1390
|
+
} catch (restoreError) {
|
|
1391
|
+
console.error('❌ Critical: Failed to update AND restore CLAUDE.md!');
|
|
1392
|
+
console.error(`💾 Your content is safe in: ${path.basename(backupPath)}`);
|
|
1393
|
+
}
|
|
1394
|
+
throw new Error(`Failed to write updated content: ${error.message}`);
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
console.log('✅ CLAUDE.md updated successfully!\n');
|
|
1398
|
+
|
|
1399
|
+
// Provide feedback based on merge strategy used
|
|
1400
|
+
if (mergeStrategy === 'smart') {
|
|
1401
|
+
console.log(' Smart merge completed');
|
|
1402
|
+
console.log(' - Your customizations preserved');
|
|
1403
|
+
console.log(' - New findings integrated');
|
|
1404
|
+
} else if (mergeStrategy === 'replace') {
|
|
1405
|
+
console.log(' Replace completed');
|
|
1406
|
+
console.log(' - New CLAUDE.md generated from current codebase');
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1409
|
+
console.log(` 📦 Backup saved: ${path.basename(backupPath)}`);
|
|
1410
|
+
console.log(' 💡 Review the updated content and delete backup when satisfied');
|
|
1411
|
+
} else {
|
|
1412
|
+
console.log(' Would update: CLAUDE.md (dry-run mode)');
|
|
1413
|
+
}
|
|
1414
|
+
} else {
|
|
1415
|
+
// Create new CLAUDE.md
|
|
1416
|
+
console.log('\n📄 Creating CLAUDE.md...');
|
|
1417
|
+
|
|
1418
|
+
if (!flags.dryRun) {
|
|
1419
|
+
const template = await getClaudeMdTemplate();
|
|
1420
|
+
const enhanced = await applyContextToTemplate(template, repositoryContext, 'append');
|
|
1421
|
+
fs.writeFileSync(claudeMdPath, enhanced, 'utf8');
|
|
1422
|
+
console.log('✅ CLAUDE.md created with project context!');
|
|
1423
|
+
} else {
|
|
1424
|
+
console.log(' Would create: CLAUDE.md (dry-run mode)');
|
|
1425
|
+
}
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
// Show next steps
|
|
1429
|
+
console.log('\nNext steps:');
|
|
1430
|
+
console.log('1. Review the updated Additional Notes section in CLAUDE.md');
|
|
1431
|
+
console.log('2. Move any important context to appropriate sections');
|
|
1432
|
+
if (exists && backupPath && !flags.dryRun) {
|
|
1433
|
+
console.log(`3. Delete the backup file once satisfied: ${path.basename(backupPath)}`);
|
|
1434
|
+
console.log('4. Run `ccsetup scan` anytime to refresh context');
|
|
1435
|
+
} else if (exists) {
|
|
1436
|
+
console.log('3. Run `ccsetup scan` anytime to refresh context');
|
|
1437
|
+
} else {
|
|
1438
|
+
console.log('3. Add project-specific instructions and guidelines');
|
|
1439
|
+
console.log('4. Run `npx ccsetup` for full setup if you need agents and project structure');
|
|
1440
|
+
console.log('5. Run `ccsetup scan` anytime to refresh context');
|
|
1441
|
+
}
|
|
1442
|
+
|
|
1443
|
+
} catch (error) {
|
|
1444
|
+
console.error('❌ Error handling CLAUDE.md:', error.message);
|
|
1445
|
+
if (exists && !flags.dryRun) {
|
|
1446
|
+
console.log('💡 Your original CLAUDE.md is safe. Check for backup files if needed.');
|
|
1447
|
+
}
|
|
1448
|
+
} finally {
|
|
1449
|
+
if (rl) rl.close();
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
|
|
454
1453
|
// Dynamic import for ESM module
|
|
455
1454
|
async function importCheckbox() {
|
|
456
1455
|
try {
|
|
@@ -512,6 +1511,341 @@ async function selectAgents(availableAgents) {
|
|
|
512
1511
|
return validatedFiles;
|
|
513
1512
|
}
|
|
514
1513
|
|
|
1514
|
+
async function enhancedAgentSelection() {
|
|
1515
|
+
try {
|
|
1516
|
+
console.log('\n🔍 Loading template catalog...');
|
|
1517
|
+
const catalog = TemplateCatalog.createInstance();
|
|
1518
|
+
const templates = await catalog.load();
|
|
1519
|
+
|
|
1520
|
+
if (!templates.agents || templates.agents.length === 0) {
|
|
1521
|
+
console.log('❌ No agents found in catalog. Falling back to basic selection.');
|
|
1522
|
+
return await selectAgents(getAvailableAgents());
|
|
1523
|
+
}
|
|
1524
|
+
|
|
1525
|
+
console.log(`✅ Found ${templates.agents.length} agents in catalog\n`);
|
|
1526
|
+
|
|
1527
|
+
// Import select for mode selection
|
|
1528
|
+
const selectModule = await import('@inquirer/select');
|
|
1529
|
+
const select = selectModule.default;
|
|
1530
|
+
|
|
1531
|
+
const mode = await select({
|
|
1532
|
+
message: 'How would you like to browse and select agents?',
|
|
1533
|
+
choices: [
|
|
1534
|
+
{
|
|
1535
|
+
name: '🔍 Search & Filter - Find agents by keywords and categories',
|
|
1536
|
+
value: 'search',
|
|
1537
|
+
description: 'Use search and filtering to find exactly what you need'
|
|
1538
|
+
},
|
|
1539
|
+
{
|
|
1540
|
+
name: '📂 Browse by Category - Explore agents organized by type',
|
|
1541
|
+
value: 'category',
|
|
1542
|
+
description: 'Browse agents grouped by their specialization'
|
|
1543
|
+
},
|
|
1544
|
+
{
|
|
1545
|
+
name: '🏷️ Browse by Tags - Find agents with specific capabilities',
|
|
1546
|
+
value: 'tags',
|
|
1547
|
+
description: 'Explore agents based on their tags and features'
|
|
1548
|
+
},
|
|
1549
|
+
{
|
|
1550
|
+
name: '📋 Simple List - Traditional selection from all agents',
|
|
1551
|
+
value: 'simple',
|
|
1552
|
+
description: 'Classic checkbox selection from complete agent list'
|
|
1553
|
+
}
|
|
1554
|
+
]
|
|
1555
|
+
});
|
|
1556
|
+
|
|
1557
|
+
let selectedAgents = [];
|
|
1558
|
+
|
|
1559
|
+
switch (mode) {
|
|
1560
|
+
case 'search':
|
|
1561
|
+
selectedAgents = await searchAndFilterSelection(templates.agents);
|
|
1562
|
+
break;
|
|
1563
|
+
case 'category':
|
|
1564
|
+
selectedAgents = await categoryBrowseSelection(templates.agents);
|
|
1565
|
+
break;
|
|
1566
|
+
case 'tags':
|
|
1567
|
+
selectedAgents = await tagBrowseSelection(templates.agents);
|
|
1568
|
+
break;
|
|
1569
|
+
case 'simple':
|
|
1570
|
+
default:
|
|
1571
|
+
selectedAgents = await simpleListSelection(templates.agents);
|
|
1572
|
+
break;
|
|
1573
|
+
}
|
|
1574
|
+
|
|
1575
|
+
return selectedAgents.map(agent => agent.files[0]).filter(file => file && validateAgentFile(file));
|
|
1576
|
+
|
|
1577
|
+
} catch (error) {
|
|
1578
|
+
console.warn(`⚠️ Enhanced selection failed: ${error.message}`);
|
|
1579
|
+
console.log('Falling back to basic agent selection...\n');
|
|
1580
|
+
return await selectAgents(getAvailableAgents());
|
|
1581
|
+
}
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1584
|
+
async function searchAndFilterSelection(agents) {
|
|
1585
|
+
const inputModule = await import('@inquirer/input');
|
|
1586
|
+
const input = inputModule.default;
|
|
1587
|
+
const selectModule = await import('@inquirer/select');
|
|
1588
|
+
const select = selectModule.default;
|
|
1589
|
+
const checkboxModule = await import('@inquirer/checkbox');
|
|
1590
|
+
const checkbox = checkboxModule.default;
|
|
1591
|
+
|
|
1592
|
+
console.log('\n🔍 Search & Filter Mode\n');
|
|
1593
|
+
|
|
1594
|
+
let currentResults = agents;
|
|
1595
|
+
const selectedAgents = [];
|
|
1596
|
+
|
|
1597
|
+
while (true) {
|
|
1598
|
+
console.log(`\n📊 Current results: ${currentResults.length} agents`);
|
|
1599
|
+
|
|
1600
|
+
const action = await select({
|
|
1601
|
+
message: 'What would you like to do?',
|
|
1602
|
+
choices: [
|
|
1603
|
+
{ name: '🔍 Search by keyword', value: 'search' },
|
|
1604
|
+
{ name: '📂 Filter by category', value: 'category' },
|
|
1605
|
+
{ name: '🏷️ Filter by tags', value: 'tags' },
|
|
1606
|
+
{ name: '📋 Select from current results', value: 'select' },
|
|
1607
|
+
{ name: '🔄 Reset filters', value: 'reset' },
|
|
1608
|
+
{ name: '✅ Finish selection', value: 'done' }
|
|
1609
|
+
]
|
|
1610
|
+
});
|
|
1611
|
+
|
|
1612
|
+
if (action === 'done') break;
|
|
1613
|
+
|
|
1614
|
+
if (action === 'reset') {
|
|
1615
|
+
currentResults = agents;
|
|
1616
|
+
continue;
|
|
1617
|
+
}
|
|
1618
|
+
|
|
1619
|
+
if (action === 'search') {
|
|
1620
|
+
const query = await input({
|
|
1621
|
+
message: 'Enter search terms:',
|
|
1622
|
+
validate: (input) => input.trim().length > 0 ? true : 'Please enter a search term'
|
|
1623
|
+
});
|
|
1624
|
+
|
|
1625
|
+
const search = new TemplateSearch(currentResults);
|
|
1626
|
+
currentResults = search.search(query.trim()).getResults();
|
|
1627
|
+
console.log(`Found ${currentResults.length} agents matching "${query}"`);
|
|
1628
|
+
continue;
|
|
1629
|
+
}
|
|
1630
|
+
|
|
1631
|
+
if (action === 'category') {
|
|
1632
|
+
const filter = new TemplateFilter(currentResults);
|
|
1633
|
+
const categories = filter.getSubcategories();
|
|
1634
|
+
|
|
1635
|
+
if (categories.length === 0) {
|
|
1636
|
+
console.log('No categories available in current results.');
|
|
1637
|
+
continue;
|
|
1638
|
+
}
|
|
1639
|
+
|
|
1640
|
+
const category = await select({
|
|
1641
|
+
message: 'Select category:',
|
|
1642
|
+
choices: [
|
|
1643
|
+
{ name: 'All categories', value: 'all' },
|
|
1644
|
+
...categories.map(cat => ({ name: cat, value: cat }))
|
|
1645
|
+
]
|
|
1646
|
+
});
|
|
1647
|
+
|
|
1648
|
+
currentResults = filter.byCategory(category).getResults();
|
|
1649
|
+
console.log(`Filtered to ${currentResults.length} agents in category "${category}"`);
|
|
1650
|
+
continue;
|
|
1651
|
+
}
|
|
1652
|
+
|
|
1653
|
+
if (action === 'tags') {
|
|
1654
|
+
const filter = new TemplateFilter(currentResults);
|
|
1655
|
+
const tagCloud = filter.getTagsByFrequency();
|
|
1656
|
+
|
|
1657
|
+
if (tagCloud.length === 0) {
|
|
1658
|
+
console.log('No tags available in current results.');
|
|
1659
|
+
continue;
|
|
1660
|
+
}
|
|
1661
|
+
|
|
1662
|
+
const tags = await checkbox({
|
|
1663
|
+
message: 'Select tags to filter by:',
|
|
1664
|
+
choices: tagCloud.slice(0, 15).map(({ tag, count }) => ({
|
|
1665
|
+
name: `${tag} (${count})`,
|
|
1666
|
+
value: tag
|
|
1667
|
+
})),
|
|
1668
|
+
pageSize: 10
|
|
1669
|
+
});
|
|
1670
|
+
|
|
1671
|
+
if (tags.length > 0) {
|
|
1672
|
+
currentResults = filter.byTags(tags).getResults();
|
|
1673
|
+
console.log(`Filtered to ${currentResults.length} agents with tags: ${tags.join(', ')}`);
|
|
1674
|
+
}
|
|
1675
|
+
continue;
|
|
1676
|
+
}
|
|
1677
|
+
|
|
1678
|
+
if (action === 'select') {
|
|
1679
|
+
if (currentResults.length === 0) {
|
|
1680
|
+
console.log('No agents in current results to select from.');
|
|
1681
|
+
continue;
|
|
1682
|
+
}
|
|
1683
|
+
|
|
1684
|
+
const choices = currentResults.map(agent => ({
|
|
1685
|
+
name: `${agent.name}\n ${agent.description}`,
|
|
1686
|
+
value: agent,
|
|
1687
|
+
checked: selectedAgents.some(selected => selected.id === agent.id)
|
|
1688
|
+
}));
|
|
1689
|
+
|
|
1690
|
+
const newSelections = await checkbox({
|
|
1691
|
+
message: `Select agents (${currentResults.length} available):`,
|
|
1692
|
+
choices,
|
|
1693
|
+
pageSize: 8
|
|
1694
|
+
});
|
|
1695
|
+
|
|
1696
|
+
// Update selected agents
|
|
1697
|
+
selectedAgents.length = 0;
|
|
1698
|
+
selectedAgents.push(...newSelections);
|
|
1699
|
+
|
|
1700
|
+
console.log(`Selected ${selectedAgents.length} agents`);
|
|
1701
|
+
}
|
|
1702
|
+
}
|
|
1703
|
+
|
|
1704
|
+
return selectedAgents;
|
|
1705
|
+
}
|
|
1706
|
+
|
|
1707
|
+
async function categoryBrowseSelection(agents) {
|
|
1708
|
+
const selectModule = await import('@inquirer/select');
|
|
1709
|
+
const select = selectModule.default;
|
|
1710
|
+
const checkboxModule = await import('@inquirer/checkbox');
|
|
1711
|
+
const checkbox = checkboxModule.default;
|
|
1712
|
+
|
|
1713
|
+
console.log('\n📂 Category Browse Mode\n');
|
|
1714
|
+
|
|
1715
|
+
const filter = new TemplateFilter(agents);
|
|
1716
|
+
const categoryCatalog = {};
|
|
1717
|
+
|
|
1718
|
+
// Group agents by subcategory
|
|
1719
|
+
agents.forEach(agent => {
|
|
1720
|
+
if (!categoryCatalog[agent.subcategory]) {
|
|
1721
|
+
categoryCatalog[agent.subcategory] = [];
|
|
1722
|
+
}
|
|
1723
|
+
categoryCatalog[agent.subcategory].push(agent);
|
|
1724
|
+
});
|
|
1725
|
+
|
|
1726
|
+
const categories = Object.keys(categoryCatalog).sort();
|
|
1727
|
+
|
|
1728
|
+
if (categories.length === 0) {
|
|
1729
|
+
console.log('No categories found. Using simple selection.');
|
|
1730
|
+
return await simpleListSelection(agents);
|
|
1731
|
+
}
|
|
1732
|
+
|
|
1733
|
+
console.log(`Found ${categories.length} categories:`);
|
|
1734
|
+
categories.forEach(cat => {
|
|
1735
|
+
console.log(` 📂 ${cat} (${categoryCatalog[cat].length} agents)`);
|
|
1736
|
+
});
|
|
1737
|
+
|
|
1738
|
+
const selectedAgents = [];
|
|
1739
|
+
|
|
1740
|
+
while (true) {
|
|
1741
|
+
const action = await select({
|
|
1742
|
+
message: `Select a category to browse (${selectedAgents.length} agents selected):`,
|
|
1743
|
+
choices: [
|
|
1744
|
+
...categories.map(cat => ({
|
|
1745
|
+
name: `📂 ${cat} (${categoryCatalog[cat].length} agents)`,
|
|
1746
|
+
value: cat
|
|
1747
|
+
})),
|
|
1748
|
+
{ name: '✅ Finish selection', value: 'done' }
|
|
1749
|
+
]
|
|
1750
|
+
});
|
|
1751
|
+
|
|
1752
|
+
if (action === 'done') break;
|
|
1753
|
+
|
|
1754
|
+
const categoryAgents = categoryCatalog[action];
|
|
1755
|
+
const choices = categoryAgents.map(agent => ({
|
|
1756
|
+
name: `${agent.name}\n ${agent.description}`,
|
|
1757
|
+
value: agent,
|
|
1758
|
+
checked: selectedAgents.some(selected => selected.id === agent.id)
|
|
1759
|
+
}));
|
|
1760
|
+
|
|
1761
|
+
const selections = await checkbox({
|
|
1762
|
+
message: `Select agents from ${action}:`,
|
|
1763
|
+
choices,
|
|
1764
|
+
pageSize: 8
|
|
1765
|
+
});
|
|
1766
|
+
|
|
1767
|
+
// Update selected agents for this category
|
|
1768
|
+
selectedAgents = selectedAgents.filter(agent => agent.subcategory !== action);
|
|
1769
|
+
selectedAgents.push(...selections);
|
|
1770
|
+
|
|
1771
|
+
console.log(`Updated selection: ${selectedAgents.length} total agents selected`);
|
|
1772
|
+
}
|
|
1773
|
+
|
|
1774
|
+
return selectedAgents;
|
|
1775
|
+
}
|
|
1776
|
+
|
|
1777
|
+
async function tagBrowseSelection(agents) {
|
|
1778
|
+
const selectModule = await import('@inquirer/select');
|
|
1779
|
+
const select = selectModule.default;
|
|
1780
|
+
const checkboxModule = await import('@inquirer/checkbox');
|
|
1781
|
+
const checkbox = checkboxModule.default;
|
|
1782
|
+
|
|
1783
|
+
console.log('\n🏷️ Tag Browse Mode\n');
|
|
1784
|
+
|
|
1785
|
+
const filter = new TemplateFilter(agents);
|
|
1786
|
+
const tagCloud = filter.getTagsByFrequency();
|
|
1787
|
+
|
|
1788
|
+
if (tagCloud.length === 0) {
|
|
1789
|
+
console.log('No tags found. Using simple selection.');
|
|
1790
|
+
return await simpleListSelection(agents);
|
|
1791
|
+
}
|
|
1792
|
+
|
|
1793
|
+
console.log(`Found ${tagCloud.length} tags. Most popular:`);
|
|
1794
|
+
tagCloud.slice(0, 10).forEach(({ tag, count }) => {
|
|
1795
|
+
console.log(` 🏷️ ${tag} (${count} agents)`);
|
|
1796
|
+
});
|
|
1797
|
+
|
|
1798
|
+
const selectedTags = await checkbox({
|
|
1799
|
+
message: 'Select tags to filter agents:',
|
|
1800
|
+
choices: tagCloud.map(({ tag, count }) => ({
|
|
1801
|
+
name: `${tag} (${count} agents)`,
|
|
1802
|
+
value: tag
|
|
1803
|
+
})),
|
|
1804
|
+
pageSize: 12
|
|
1805
|
+
});
|
|
1806
|
+
|
|
1807
|
+
if (selectedTags.length === 0) {
|
|
1808
|
+
console.log('No tags selected. Using all agents.');
|
|
1809
|
+
return await simpleListSelection(agents);
|
|
1810
|
+
}
|
|
1811
|
+
|
|
1812
|
+
const filteredAgents = filter.byTags(selectedTags).getResults();
|
|
1813
|
+
console.log(`\nFiltered to ${filteredAgents.length} agents with selected tags`);
|
|
1814
|
+
|
|
1815
|
+
if (filteredAgents.length === 0) {
|
|
1816
|
+
console.log('No agents match the selected tags.');
|
|
1817
|
+
return [];
|
|
1818
|
+
}
|
|
1819
|
+
|
|
1820
|
+
return await simpleListSelection(filteredAgents);
|
|
1821
|
+
}
|
|
1822
|
+
|
|
1823
|
+
async function simpleListSelection(agents) {
|
|
1824
|
+
const checkboxModule = await import('@inquirer/checkbox');
|
|
1825
|
+
const checkbox = checkboxModule.default;
|
|
1826
|
+
|
|
1827
|
+
console.log('\n📋 Simple List Selection\n');
|
|
1828
|
+
|
|
1829
|
+
if (agents.length === 0) {
|
|
1830
|
+
console.log('No agents available for selection.');
|
|
1831
|
+
return [];
|
|
1832
|
+
}
|
|
1833
|
+
|
|
1834
|
+
const choices = agents.map(agent => ({
|
|
1835
|
+
name: `${agent.name}\n ${agent.description}`,
|
|
1836
|
+
value: agent,
|
|
1837
|
+
checked: false
|
|
1838
|
+
}));
|
|
1839
|
+
|
|
1840
|
+
const selectedAgents = await checkbox({
|
|
1841
|
+
message: `Select agents (${agents.length} available):`,
|
|
1842
|
+
choices,
|
|
1843
|
+
pageSize: 10
|
|
1844
|
+
});
|
|
1845
|
+
|
|
1846
|
+
return selectedAgents;
|
|
1847
|
+
}
|
|
1848
|
+
|
|
515
1849
|
async function main() {
|
|
516
1850
|
try {
|
|
517
1851
|
// Ensure template directory exists
|
|
@@ -519,18 +1853,55 @@ async function main() {
|
|
|
519
1853
|
throw new Error(`Template directory not found: ${templateDir}`);
|
|
520
1854
|
}
|
|
521
1855
|
|
|
522
|
-
|
|
523
|
-
|
|
1856
|
+
|
|
1857
|
+
// Add mode selection early (before agent selection)
|
|
1858
|
+
const setupMode = await selectSetupMode();
|
|
1859
|
+
|
|
1860
|
+
if (setupMode === 'install-hooks') {
|
|
1861
|
+
// Install Claude Code hooks
|
|
1862
|
+
await installClaudeHooks();
|
|
1863
|
+
return;
|
|
1864
|
+
}
|
|
1865
|
+
|
|
1866
|
+
if (setupMode === 'scan-only' || setupMode === 'scan-smart' || setupMode === 'scan-replace') {
|
|
1867
|
+
// Close readline if open
|
|
1868
|
+
if (rl) {
|
|
1869
|
+
rl.close();
|
|
1870
|
+
rl = null;
|
|
1871
|
+
}
|
|
1872
|
+
|
|
1873
|
+
// Set appropriate flags based on mode
|
|
1874
|
+
flags.scanOnly = true;
|
|
1875
|
+
if (setupMode === 'scan-replace') {
|
|
1876
|
+
flags.mergeStrategy = 'replace';
|
|
1877
|
+
} else {
|
|
1878
|
+
flags.mergeStrategy = 'smart';
|
|
1879
|
+
}
|
|
1880
|
+
|
|
1881
|
+
await scanOnlyMode(flags.mergeStrategy);
|
|
1882
|
+
return;
|
|
1883
|
+
}
|
|
1884
|
+
|
|
1885
|
+
// Handle --agents and --browse flags for agent selection only
|
|
1886
|
+
if (flags.agents || flags.browse) {
|
|
524
1887
|
console.log('🤖 Interactive Agent Selection\n');
|
|
525
|
-
const availableAgents = getAvailableAgents();
|
|
526
1888
|
|
|
527
|
-
|
|
528
|
-
console.log('No agents available for selection.');
|
|
529
|
-
process.exit(0);
|
|
530
|
-
}
|
|
1889
|
+
let selectedAgentFiles = [];
|
|
531
1890
|
|
|
532
|
-
|
|
533
|
-
|
|
1891
|
+
if (flags.browse) {
|
|
1892
|
+
// Use enhanced selection interface
|
|
1893
|
+
selectedAgentFiles = await enhancedAgentSelection();
|
|
1894
|
+
} else {
|
|
1895
|
+
// Use traditional selection
|
|
1896
|
+
const availableAgents = getAvailableAgents();
|
|
1897
|
+
|
|
1898
|
+
if (availableAgents.length === 0) {
|
|
1899
|
+
console.log('No agents available for selection.');
|
|
1900
|
+
process.exit(0);
|
|
1901
|
+
}
|
|
1902
|
+
|
|
1903
|
+
selectedAgentFiles = await selectAgents(availableAgents);
|
|
1904
|
+
}
|
|
534
1905
|
|
|
535
1906
|
if (selectedAgentFiles.length === 0) {
|
|
536
1907
|
console.log('\n❌ No agents selected.');
|
|
@@ -548,13 +1919,20 @@ async function main() {
|
|
|
548
1919
|
console.log(`\n${colors.green}${colors.bold}✅ You selected ${selectedAgentFiles.length} agent${selectedAgentFiles.length === 1 ? '' : 's'}:${colors.reset}\n`);
|
|
549
1920
|
|
|
550
1921
|
// Show selected agents with descriptions
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
1922
|
+
if (flags.browse) {
|
|
1923
|
+
// For enhanced selection, we already have agent data
|
|
1924
|
+
console.log(`${colors.dim}Selected files: ${selectedAgentFiles.join(', ')}${colors.reset}\n`);
|
|
1925
|
+
} else {
|
|
1926
|
+
// For traditional selection, show agent details
|
|
1927
|
+
const availableAgents = getAvailableAgents();
|
|
1928
|
+
selectedAgentFiles.forEach(file => {
|
|
1929
|
+
const agent = availableAgents.find(a => a.file === file);
|
|
1930
|
+
if (agent) {
|
|
1931
|
+
console.log(` ${colors.cyan}${colors.bold}${agent.name}${colors.reset}`);
|
|
1932
|
+
console.log(` ${colors.dim}${agent.description}${colors.reset}\n`);
|
|
1933
|
+
}
|
|
1934
|
+
});
|
|
1935
|
+
}
|
|
558
1936
|
|
|
559
1937
|
console.log(`${colors.yellow}${colors.bold}📝 Next Steps:${colors.reset}`);
|
|
560
1938
|
console.log(`${colors.dim}1. Make sure Claude Code is initialized: ${colors.reset}${colors.cyan}claude init${colors.reset}`);
|
|
@@ -590,8 +1968,15 @@ async function main() {
|
|
|
590
1968
|
} else {
|
|
591
1969
|
console.log('⚠️ Claude Code not detected in this project.');
|
|
592
1970
|
console.log('ccsetup can create the .claude directory structure for you.\n');
|
|
593
|
-
|
|
594
|
-
|
|
1971
|
+
|
|
1972
|
+
// Use inquirer instead of readline prompt since rl might be closed
|
|
1973
|
+
const confirmModule = await import('@inquirer/confirm');
|
|
1974
|
+
const confirm = confirmModule.default;
|
|
1975
|
+
const shouldCreate = await confirm({
|
|
1976
|
+
message: 'Would you like to create .claude directory?',
|
|
1977
|
+
default: true
|
|
1978
|
+
});
|
|
1979
|
+
if (!shouldCreate) {
|
|
595
1980
|
console.log('\nTo manually initialize Claude Code:');
|
|
596
1981
|
console.log('1. Install Claude Code CLI: https://docs.anthropic.com/claude-code/quickstart');
|
|
597
1982
|
console.log('2. Run \'claude init\' in your project directory');
|
|
@@ -691,19 +2076,27 @@ async function main() {
|
|
|
691
2076
|
const select = selectModule.default;
|
|
692
2077
|
|
|
693
2078
|
const agentMode = await select({
|
|
694
|
-
message: '
|
|
2079
|
+
message: 'Would you like to include AI agents in your project?',
|
|
695
2080
|
choices: [
|
|
696
2081
|
{
|
|
697
|
-
name: '
|
|
698
|
-
value: '
|
|
2082
|
+
name: '✨ Select Agents - Choose specific agents for your needs',
|
|
2083
|
+
value: 'select',
|
|
2084
|
+
description: 'Interactive selection of agents based on your project'
|
|
2085
|
+
},
|
|
2086
|
+
{
|
|
2087
|
+
name: '🔍 Enhanced Selection - Advanced browsing with search and filters',
|
|
2088
|
+
value: 'enhanced',
|
|
2089
|
+
description: 'Use the enhanced interface with categories, tags, and search'
|
|
699
2090
|
},
|
|
700
2091
|
{
|
|
701
|
-
name: '
|
|
702
|
-
value: '
|
|
2092
|
+
name: '📚 Copy All Agents - Get all 50+ agents to explore',
|
|
2093
|
+
value: 'browse',
|
|
2094
|
+
description: 'Copies all agents to /agents folder for manual review'
|
|
703
2095
|
},
|
|
704
2096
|
{
|
|
705
|
-
name: 'Skip -
|
|
706
|
-
value: 'skip'
|
|
2097
|
+
name: '⏭️ Skip Agents - Just set up the basic structure',
|
|
2098
|
+
value: 'skip',
|
|
2099
|
+
description: 'You can always add agents later'
|
|
707
2100
|
}
|
|
708
2101
|
]
|
|
709
2102
|
});
|
|
@@ -721,6 +2114,10 @@ async function main() {
|
|
|
721
2114
|
// Interactive selection
|
|
722
2115
|
selectedAgentFiles = await selectAgents(availableAgents);
|
|
723
2116
|
console.log(`\n✅ Selected ${selectedAgentFiles.length} agent${selectedAgentFiles.length === 1 ? '' : 's'}`);
|
|
2117
|
+
} else if (agentMode === 'enhanced') {
|
|
2118
|
+
// Enhanced selection with catalog system
|
|
2119
|
+
selectedAgentFiles = await enhancedAgentSelection();
|
|
2120
|
+
console.log(`\n✅ Selected ${selectedAgentFiles.length} agent${selectedAgentFiles.length === 1 ? '' : 's'}`);
|
|
724
2121
|
}
|
|
725
2122
|
|
|
726
2123
|
// Recreate readline interface after agent selection
|
|
@@ -761,6 +2158,11 @@ async function main() {
|
|
|
761
2158
|
return; // Don't process .claude directory in regular template scan
|
|
762
2159
|
}
|
|
763
2160
|
|
|
2161
|
+
// Skip hooks directory - hooks should only be installed to .claude/hooks via --install-hooks
|
|
2162
|
+
if (path.basename(src) === 'hooks') {
|
|
2163
|
+
return; // Don't copy hooks to project root
|
|
2164
|
+
}
|
|
2165
|
+
|
|
764
2166
|
// Skip agents directory if we're handling it separately
|
|
765
2167
|
if (skipAgents && path.basename(src) === 'agents') {
|
|
766
2168
|
// Only scan selected agents
|
|
@@ -894,6 +2296,29 @@ async function main() {
|
|
|
894
2296
|
|
|
895
2297
|
scanTemplate(templateDir, targetDir, '', true);
|
|
896
2298
|
|
|
2299
|
+
// Handle repository scanning for context
|
|
2300
|
+
let repositoryContext = null;
|
|
2301
|
+
if (flags.scanContext || (!flags.dryRun && projectName === '.' && !flags.force)) {
|
|
2302
|
+
const hasExistingFiles = allItems.some(item => item.exists);
|
|
2303
|
+
|
|
2304
|
+
if (hasExistingFiles || flags.scanContext) {
|
|
2305
|
+
if (flags.scanContext || (!flags.force && await shouldScanRepository())) {
|
|
2306
|
+
repositoryContext = await scanRepositoryForContext(targetDir);
|
|
2307
|
+
|
|
2308
|
+
if (repositoryContext && !flags.dryRun) {
|
|
2309
|
+
const confirmResult = await previewAndConfirmContext(repositoryContext.formattedContext);
|
|
2310
|
+
|
|
2311
|
+
if (confirmResult === 'n' || confirmResult === 'no') {
|
|
2312
|
+
repositoryContext = null;
|
|
2313
|
+
console.log('✅ Skipping context addition to CLAUDE.md');
|
|
2314
|
+
} else if (confirmResult === 'edit') {
|
|
2315
|
+
console.log('📝 Context editing not yet implemented. Using generated context as-is.');
|
|
2316
|
+
}
|
|
2317
|
+
}
|
|
2318
|
+
}
|
|
2319
|
+
}
|
|
2320
|
+
}
|
|
2321
|
+
|
|
897
2322
|
// Handle force flag
|
|
898
2323
|
if (flags.force) {
|
|
899
2324
|
// Set all strategies to overwrite
|
|
@@ -933,18 +2358,30 @@ async function main() {
|
|
|
933
2358
|
console.log(' 2) rename (r) - Save template files with -ccsetup suffix');
|
|
934
2359
|
console.log(' 3) overwrite (o) - Replace with template versions');
|
|
935
2360
|
|
|
936
|
-
|
|
937
|
-
const
|
|
2361
|
+
// Use inquirer instead of readline prompt
|
|
2362
|
+
const selectModule = await import('@inquirer/select');
|
|
2363
|
+
const select = selectModule.default;
|
|
2364
|
+
const strategy = await select({
|
|
2365
|
+
message: `Your choice for ${category.name}:`,
|
|
2366
|
+
choices: [
|
|
2367
|
+
{ name: '(s)kip - Keep your existing files', value: 'skip' },
|
|
2368
|
+
{ name: '(r)ename - Create backups and use template versions', value: 'rename' },
|
|
2369
|
+
{ name: '(o)verwrite - Replace with template versions', value: 'overwrite' }
|
|
2370
|
+
],
|
|
2371
|
+
default: 'skip'
|
|
2372
|
+
});
|
|
938
2373
|
|
|
939
|
-
|
|
940
|
-
console.log(`\n❌ Invalid option: "${userInput}". Please use: skip/s/1, rename/r/2, or overwrite/o/3`);
|
|
941
|
-
if (rl) rl.close();
|
|
942
|
-
process.exit(1);
|
|
943
|
-
}
|
|
2374
|
+
// Strategy will always be valid when using select, no need for validation
|
|
944
2375
|
|
|
945
2376
|
if (strategy === 'overwrite' && category.key === 'CLAUDE.md') {
|
|
946
|
-
|
|
947
|
-
|
|
2377
|
+
// Use inquirer instead of readline prompt
|
|
2378
|
+
const confirmModule = await import('@inquirer/confirm');
|
|
2379
|
+
const confirm = confirmModule.default;
|
|
2380
|
+
const shouldOverwrite = await confirm({
|
|
2381
|
+
message: '⚠️ Are you sure you want to overwrite CLAUDE.md? This will lose your project instructions!',
|
|
2382
|
+
default: false
|
|
2383
|
+
});
|
|
2384
|
+
if (!shouldOverwrite) {
|
|
948
2385
|
conflictStrategies[category.key] = 'skip';
|
|
949
2386
|
console.log('Keeping existing CLAUDE.md');
|
|
950
2387
|
continue;
|
|
@@ -1014,10 +2451,28 @@ async function main() {
|
|
|
1014
2451
|
console.log(` 📄 ${flags.dryRun ? 'Would create' : 'Created'}: ${path.relative(targetDir, newDest)}`);
|
|
1015
2452
|
} else if (strategy === 'overwrite') {
|
|
1016
2453
|
if (!flags.dryRun) {
|
|
1017
|
-
|
|
2454
|
+
// Handle CLAUDE.md with context injection on overwrite
|
|
2455
|
+
if (item.relativePath === 'CLAUDE.md' && repositoryContext) {
|
|
2456
|
+
const templateContent = fs.readFileSync(item.src, 'utf8');
|
|
2457
|
+
const enhancedContent = await applyContextToTemplate(
|
|
2458
|
+
templateContent,
|
|
2459
|
+
repositoryContext,
|
|
2460
|
+
'append'
|
|
2461
|
+
);
|
|
2462
|
+
fs.writeFileSync(item.dest, enhancedContent, 'utf8');
|
|
2463
|
+
console.log(` ♻️ Replaced CLAUDE.md with project context`);
|
|
2464
|
+
} else {
|
|
2465
|
+
fs.copyFileSync(item.src, item.dest);
|
|
2466
|
+
console.log(` ♻️ Replaced: ${item.relativePath}`);
|
|
2467
|
+
}
|
|
2468
|
+
} else {
|
|
2469
|
+
if (item.relativePath === 'CLAUDE.md' && repositoryContext) {
|
|
2470
|
+
console.log(` ♻️ Would replace: ${item.relativePath} (with project context)`);
|
|
2471
|
+
} else {
|
|
2472
|
+
console.log(` ♻️ Would replace: ${item.relativePath}`);
|
|
2473
|
+
}
|
|
1018
2474
|
}
|
|
1019
2475
|
overwrittenCount++;
|
|
1020
|
-
console.log(` ♻️ ${flags.dryRun ? 'Would replace' : 'Replaced'}: ${item.relativePath}`);
|
|
1021
2476
|
}
|
|
1022
2477
|
} else {
|
|
1023
2478
|
if (!flags.dryRun) {
|
|
@@ -1026,9 +2481,26 @@ async function main() {
|
|
|
1026
2481
|
if (!fs.existsSync(destDir)) {
|
|
1027
2482
|
fs.mkdirSync(destDir, { recursive: true });
|
|
1028
2483
|
}
|
|
1029
|
-
|
|
2484
|
+
|
|
2485
|
+
// Handle CLAUDE.md with context injection
|
|
2486
|
+
if (item.relativePath === 'CLAUDE.md' && repositoryContext) {
|
|
2487
|
+
const templateContent = fs.readFileSync(item.src, 'utf8');
|
|
2488
|
+
const enhancedContent = await applyContextToTemplate(
|
|
2489
|
+
templateContent,
|
|
2490
|
+
repositoryContext,
|
|
2491
|
+
'append'
|
|
2492
|
+
);
|
|
2493
|
+
fs.writeFileSync(item.dest, enhancedContent, 'utf8');
|
|
2494
|
+
console.log(` ✨ Created CLAUDE.md with project context`);
|
|
2495
|
+
} else {
|
|
2496
|
+
fs.copyFileSync(item.src, item.dest);
|
|
2497
|
+
}
|
|
1030
2498
|
} else {
|
|
1031
|
-
|
|
2499
|
+
if (item.relativePath === 'CLAUDE.md' && repositoryContext) {
|
|
2500
|
+
console.log(` ✨ Would copy: ${item.relativePath} (with project context)`);
|
|
2501
|
+
} else {
|
|
2502
|
+
console.log(` ✨ Would copy: ${item.relativePath}`);
|
|
2503
|
+
}
|
|
1032
2504
|
}
|
|
1033
2505
|
copiedCount++;
|
|
1034
2506
|
}
|
|
@@ -1082,6 +2554,9 @@ async function main() {
|
|
|
1082
2554
|
if (claudeInitResult && claudeInitResult.createdItems.length > 0) {
|
|
1083
2555
|
console.log(` 📁 ${claudeInitResult.createdItems.length} items created in .claude directory`);
|
|
1084
2556
|
}
|
|
2557
|
+
if (repositoryContext && !flags.dryRun) {
|
|
2558
|
+
console.log(` 🔍 Project context automatically added to CLAUDE.md`);
|
|
2559
|
+
}
|
|
1085
2560
|
}
|
|
1086
2561
|
|
|
1087
2562
|
if (!flags.dryRun) {
|
|
@@ -1114,6 +2589,87 @@ async function main() {
|
|
|
1114
2589
|
console.log(' You can compare them with your existing files or copy sections you need');
|
|
1115
2590
|
}
|
|
1116
2591
|
|
|
2592
|
+
// Ask user if they want the workflow selector hook
|
|
2593
|
+
if (setupMode === 'full' && !flags.dryRun) {
|
|
2594
|
+
const claudeDir = path.join(targetDir, '.claude');
|
|
2595
|
+
if (fs.existsSync(claudeDir)) {
|
|
2596
|
+
try {
|
|
2597
|
+
let wantHook = false;
|
|
2598
|
+
|
|
2599
|
+
if (!flags.force) {
|
|
2600
|
+
const confirmModule = await import('@inquirer/confirm');
|
|
2601
|
+
const confirm = confirmModule.default;
|
|
2602
|
+
|
|
2603
|
+
wantHook = await confirm({
|
|
2604
|
+
message: 'Enable workflow selector hook? (suggests agent workflows per prompt, controlled via CCSETUP_WORKFLOW env var)',
|
|
2605
|
+
default: false
|
|
2606
|
+
});
|
|
2607
|
+
}
|
|
2608
|
+
|
|
2609
|
+
if (wantHook) {
|
|
2610
|
+
// Create hooks directory
|
|
2611
|
+
const hooksDir = path.join(claudeDir, 'hooks');
|
|
2612
|
+
if (!fs.existsSync(hooksDir)) {
|
|
2613
|
+
fs.mkdirSync(hooksDir, { recursive: true });
|
|
2614
|
+
}
|
|
2615
|
+
|
|
2616
|
+
// Copy workflow-selector hook
|
|
2617
|
+
const hookSourceDir = path.join(templateDir, 'hooks', 'workflow-selector');
|
|
2618
|
+
const hookDestDir = path.join(hooksDir, 'workflow-selector');
|
|
2619
|
+
|
|
2620
|
+
if (fs.existsSync(hookSourceDir)) {
|
|
2621
|
+
if (!fs.existsSync(hookDestDir)) {
|
|
2622
|
+
fs.mkdirSync(hookDestDir, { recursive: true });
|
|
2623
|
+
}
|
|
2624
|
+
|
|
2625
|
+
const hookFile = path.join(hookSourceDir, 'index.js');
|
|
2626
|
+
const destFile = path.join(hookDestDir, 'index.js');
|
|
2627
|
+
fs.copyFileSync(hookFile, destFile);
|
|
2628
|
+
|
|
2629
|
+
// Update settings.json
|
|
2630
|
+
const settingsFile = path.join(claudeDir, 'settings.json');
|
|
2631
|
+
let settings = {};
|
|
2632
|
+
|
|
2633
|
+
if (fs.existsSync(settingsFile)) {
|
|
2634
|
+
try {
|
|
2635
|
+
settings = JSON.parse(fs.readFileSync(settingsFile, 'utf8'));
|
|
2636
|
+
} catch (e) {
|
|
2637
|
+
// Start with empty settings
|
|
2638
|
+
}
|
|
2639
|
+
}
|
|
2640
|
+
|
|
2641
|
+
if (!settings.hooks) {
|
|
2642
|
+
settings.hooks = {};
|
|
2643
|
+
}
|
|
2644
|
+
|
|
2645
|
+
settings.hooks.UserPromptSubmit = [
|
|
2646
|
+
{
|
|
2647
|
+
"matcher": ".*",
|
|
2648
|
+
"hooks": [
|
|
2649
|
+
{
|
|
2650
|
+
"type": "command",
|
|
2651
|
+
"command": "node $CLAUDE_PROJECT_DIR/.claude/hooks/workflow-selector/index.js"
|
|
2652
|
+
}
|
|
2653
|
+
]
|
|
2654
|
+
}
|
|
2655
|
+
];
|
|
2656
|
+
|
|
2657
|
+
fs.writeFileSync(settingsFile, JSON.stringify(settings, null, 2));
|
|
2658
|
+
console.log(' ✅ Workflow selection hook installed');
|
|
2659
|
+
console.log(' 📝 To activate, set the environment variable:');
|
|
2660
|
+
console.log(' export CCSETUP_WORKFLOW=1');
|
|
2661
|
+
console.log(' 💡 The hook suggests workflows and asks before applying them');
|
|
2662
|
+
}
|
|
2663
|
+
} else {
|
|
2664
|
+
console.log(' ⏭️ Skipped workflow selector hook');
|
|
2665
|
+
console.log(' 💡 You can install it later with: npx ccsetup --install-hooks');
|
|
2666
|
+
}
|
|
2667
|
+
} catch (error) {
|
|
2668
|
+
console.warn(' ⚠️ Could not install workflow hook:', error.message);
|
|
2669
|
+
}
|
|
2670
|
+
}
|
|
2671
|
+
}
|
|
2672
|
+
|
|
1117
2673
|
console.log('\nHappy coding with Claude! 🎉');
|
|
1118
2674
|
}
|
|
1119
2675
|
|