@claudetools/cli 0.12.0 → 0.13.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.
@@ -6,18 +6,16 @@ import chalk from 'chalk';
6
6
  import ora from 'ora';
7
7
  import prompts from 'prompts';
8
8
  import { existsSync, writeFileSync, mkdirSync, readFileSync } from 'fs';
9
+ import { execSync } from 'child_process';
9
10
  import { join, basename } from 'path';
10
11
  import { detectStack } from './stack-detector.js';
11
12
  import { buildDocs } from './docs-builder.js';
12
13
  import { buildAgentsMd, writeAgentsMd } from './agents-md-builder.js';
13
14
  import { runInteractiveOnboarding } from './questions.js';
14
- import { isClaudeCliAvailable, analyzeWithClaude } from './claude-inference.js';
15
- // Progress bar helper
16
- function progressBar(current, total, width = 20) {
17
- const filled = Math.round((current / total) * width);
18
- const empty = width - filled;
19
- return chalk.cyan('█'.repeat(filled)) + chalk.dim('░'.repeat(empty));
20
- }
15
+ import { isClaudeCliAvailable, runClaudeAnalysis } from './claude-inference.js';
16
+ // =============================================================================
17
+ // Main Onboarding Flow
18
+ // =============================================================================
21
19
  /**
22
20
  * Run the complete onboarding workflow
23
21
  */
@@ -33,351 +31,494 @@ export async function runOnboard(projectPath, options = {}) {
33
31
  console.log(chalk.dim(` Project: ${chalk.white(projectName)}`));
34
32
  console.log(chalk.dim(` Path: ${projectPath}`));
35
33
  console.log('');
34
+ // ==========================================================================
36
35
  // Phase 1: Stack Detection
37
- console.log(chalk.cyan(' ▸ Phase 1/5: Stack Detection'));
36
+ // ==========================================================================
37
+ console.log(chalk.cyan(' ▸ Phase 1/4: Stack Detection'));
38
38
  const stackSpinner = ora({ text: 'Scanning project files...', indent: 4 }).start();
39
39
  const stack = detectStack(projectPath);
40
- // Handle empty/new projects
41
40
  if (stack.isEmpty) {
42
- stackSpinner.warn(chalk.yellow('No project files detected'));
43
- console.log('');
44
- console.log(chalk.dim(' ┌─ New/Empty Project ────────────────────'));
45
- console.log(chalk.dim(' │'));
46
- console.log(chalk.dim(' │ ') + chalk.yellow('No package.json, requirements.txt, go.mod,'));
47
- console.log(chalk.dim(' │ ') + chalk.yellow('or Cargo.toml found.'));
48
- console.log(chalk.dim(' │'));
49
- console.log(chalk.dim(' │ ') + chalk.white('This appears to be a new or empty project.'));
50
- console.log(chalk.dim(' │'));
51
- console.log(chalk.dim(' └──────────────────────────────────────'));
52
- console.log('');
53
- const { continueEmpty } = await prompts({
54
- type: 'confirm',
55
- name: 'continueEmpty',
56
- message: ' Continue with minimal setup for new project?',
57
- initial: true,
58
- });
59
- if (!continueEmpty) {
60
- console.log('');
61
- console.log(chalk.dim(' Tip: Initialize your project first, then run onboarding:'));
62
- console.log(chalk.dim(' • npm init / pnpm init / yarn init'));
63
- console.log(chalk.dim(' • npx create-next-app'));
64
- console.log(chalk.dim(' • pip init / poetry init'));
65
- console.log(chalk.dim(' • go mod init'));
66
- console.log(chalk.dim(' • cargo init'));
67
- console.log('');
68
- return;
69
- }
70
- console.log(chalk.dim(' Proceeding with minimal setup...'));
71
- console.log('');
41
+ stackSpinner.info(chalk.cyan('New project - let\'s set it up'));
72
42
  }
73
43
  else {
74
44
  stackSpinner.succeed(chalk.green('Stack detected'));
75
- console.log('');
76
- // Show detected stack summary
77
- console.log(chalk.dim(' ┌─ Detected Stack ─────────────────────'));
78
- console.log(chalk.dim(' │'));
79
- console.log(chalk.dim(' │ ') + chalk.white(`Language: ${stack.primaryLanguage}`));
80
- console.log(chalk.dim(' │ ') + chalk.white(`Package Manager: ${stack.packageManager}`));
81
- if (stack.frameworks.length > 0) {
82
- console.log(chalk.dim(' │ ') + chalk.white(`Frameworks: ${stack.frameworks.map(f => f.name).join(', ')}`));
83
- }
84
- if (stack.libraries.length > 0) {
85
- const libCount = stack.libraries.length;
86
- const topLibs = stack.libraries.slice(0, 5).map(l => l.name).join(', ');
87
- console.log(chalk.dim(' │ ') + chalk.white(`Libraries: ${topLibs}${libCount > 5 ? ` (+${libCount - 5} more)` : ''}`));
88
- }
89
- if (stack.devTools.length > 0) {
90
- console.log(chalk.dim(' │ ') + chalk.white(`Dev Tools: ${stack.devTools.map(t => t.name).join(', ')}`));
91
- }
92
- console.log(chalk.dim(' │'));
93
- console.log(chalk.dim(' └─────────────────────────────────────'));
94
- console.log('');
95
- if (options.verbose) {
96
- printStackDetails(stack);
97
- }
98
45
  }
99
- // Phase 2: Project Context Analysis
100
- console.log(chalk.cyan(' ▸ Phase 2/5: Project Analysis'));
46
+ console.log('');
47
+ // ==========================================================================
48
+ // Phase 2: Quick Questions
49
+ // ==========================================================================
50
+ console.log(chalk.cyan(' ▸ Phase 2/4: Quick Questions'));
101
51
  let answers = {};
102
52
  if (!options.skipQuestions && !options.refresh) {
103
- // For empty projects, use interactive questions (no codebase to analyze)
104
- if (stack.isEmpty) {
105
- console.log(chalk.dim(' New project - gathering requirements'));
106
- const { doInteractive } = await prompts({
107
- type: 'confirm',
108
- name: 'doInteractive',
109
- message: ' Define project requirements?',
110
- initial: true,
53
+ answers = await runInteractiveOnboarding(stack, {
54
+ verbose: options.verbose,
55
+ isNewProject: stack.isEmpty,
56
+ });
57
+ }
58
+ else {
59
+ console.log(chalk.dim(' Skipping questions'));
60
+ console.log('');
61
+ }
62
+ // ==========================================================================
63
+ // Phase 3: AI Analysis & Proposal
64
+ // ==========================================================================
65
+ console.log(chalk.cyan(' ▸ Phase 3/4: AI Analysis'));
66
+ const hasClaudeCli = isClaudeCliAvailable();
67
+ if (!hasClaudeCli) {
68
+ console.log(chalk.yellow(' Claude CLI not available - using detected stack'));
69
+ console.log('');
70
+ }
71
+ else {
72
+ // AI analyzes and proposes
73
+ let proposal = await generateProposal(projectPath, stack, answers);
74
+ let approved = false;
75
+ while (!approved) {
76
+ // Display proposal
77
+ displayProposal(proposal);
78
+ // Ask for approval
79
+ const { decision } = await prompts({
80
+ type: 'select',
81
+ name: 'decision',
82
+ message: ' How does this look?',
83
+ choices: [
84
+ { title: 'Looks good, proceed', value: 'approve' },
85
+ { title: 'I have feedback', value: 'feedback' },
86
+ { title: 'Skip AI analysis', value: 'skip' },
87
+ ],
111
88
  });
112
- if (doInteractive) {
113
- answers = await runInteractiveOnboarding(stack, { verbose: options.verbose });
114
- }
115
- else {
116
- console.log(chalk.dim(' Skipping - using minimal defaults'));
117
- }
118
- }
119
- else {
120
- // Existing project - offer Claude analysis
121
- const hasClaudeCli = isClaudeCliAvailable();
122
- if (hasClaudeCli) {
123
- console.log(chalk.dim(' Claude Code CLI detected'));
124
- const { useClaudeAnalysis } = await prompts({
125
- type: 'confirm',
126
- name: 'useClaudeAnalysis',
127
- message: ' Use AI to analyze codebase? (recommended)',
128
- initial: true,
129
- });
130
- if (useClaudeAnalysis) {
131
- const analysisSpinner = ora({
132
- text: 'Claude is analyzing your codebase...',
133
- indent: 4,
134
- }).start();
135
- const analysisSteps = [
136
- 'Reading project structure...',
137
- 'Analyzing architecture patterns...',
138
- 'Detecting conventions...',
139
- 'Identifying key components...',
140
- 'Generating insights...',
141
- ];
142
- let stepIndex = 0;
143
- const stepInterval = setInterval(() => {
144
- stepIndex = (stepIndex + 1) % analysisSteps.length;
145
- analysisSpinner.text = analysisSteps[stepIndex];
146
- }, 3000);
147
- try {
148
- answers = await analyzeWithClaude(projectPath, stack);
149
- clearInterval(stepInterval);
150
- analysisSpinner.succeed(chalk.green('Codebase analysis complete'));
151
- // Show what was discovered
152
- if (answers.projectDescription) {
153
- console.log('');
154
- console.log(chalk.dim(' ┌─ Analysis Results ────────────────────'));
155
- console.log(chalk.dim(' │'));
156
- console.log(chalk.dim(' │ ') + chalk.white(answers.projectDescription.slice(0, 60) + (answers.projectDescription.length > 60 ? '...' : '')));
157
- if (answers.architectureStyle) {
158
- console.log(chalk.dim(' │ ') + chalk.dim('Architecture: ') + chalk.white(answers.architectureStyle));
159
- }
160
- console.log(chalk.dim(' │'));
161
- console.log(chalk.dim(' └──────────────────────────────────────'));
162
- }
89
+ if (decision === 'approve') {
90
+ approved = true;
91
+ console.log(chalk.green(' ✓ Approved'));
92
+ // For empty projects, offer scaffolding
93
+ if (stack.isEmpty) {
94
+ console.log('');
95
+ const { scaffold } = await prompts({
96
+ type: 'confirm',
97
+ name: 'scaffold',
98
+ message: ' Create package.json and install dependencies?',
99
+ initial: true,
100
+ });
101
+ if (scaffold) {
102
+ await scaffoldProject(projectPath, proposal, answers);
163
103
  }
164
- catch (err) {
165
- clearInterval(stepInterval);
166
- analysisSpinner.fail(chalk.yellow('Analysis failed, using manual questions'));
167
- answers = await runInteractiveOnboarding(stack, { verbose: options.verbose });
168
- }
169
- }
170
- else {
171
- answers = await runInteractiveOnboarding(stack, { verbose: options.verbose });
172
104
  }
173
105
  }
174
- else {
175
- console.log(chalk.dim(' Claude Code CLI not detected - using interactive mode'));
176
- const { doInteractive } = await prompts({
177
- type: 'confirm',
178
- name: 'doInteractive',
179
- message: ' Answer questions to improve documentation?',
180
- initial: true,
106
+ else if (decision === 'feedback') {
107
+ const { feedback } = await prompts({
108
+ type: 'text',
109
+ name: 'feedback',
110
+ message: ' What should be different?',
181
111
  });
182
- if (doInteractive) {
183
- answers = await runInteractiveOnboarding(stack, { verbose: options.verbose });
184
- }
185
- else {
186
- console.log(chalk.dim(' Skipping - using defaults'));
112
+ if (feedback && typeof feedback === 'string' && feedback.trim()) {
113
+ console.log('');
114
+ const refineSpinner = ora({ text: 'Refining proposal...', indent: 4 }).start();
115
+ proposal = await refineProposal(projectPath, stack, answers, proposal, feedback.trim());
116
+ refineSpinner.succeed('Updated proposal');
117
+ console.log('');
187
118
  }
188
119
  }
120
+ else {
121
+ // Skip
122
+ console.log(chalk.dim(' Using detected stack only'));
123
+ approved = true;
124
+ }
189
125
  }
190
126
  }
191
- else {
192
- console.log(chalk.dim(' Skipping analysis (refresh mode)'));
193
- }
194
127
  console.log('');
195
- // Phase 3: Documentation Generation
196
- console.log(chalk.cyan(' ▸ Phase 3/5: Documentation Generation'));
128
+ // ==========================================================================
129
+ // Phase 4: Build Documentation
130
+ // ==========================================================================
131
+ console.log(chalk.cyan(' ▸ Phase 4/4: Building Documentation'));
197
132
  let generatedDocs = [];
198
133
  if (!options.skipDocs) {
199
- // Only use Claude for codebase analysis if there's actual code
200
- const useClaude = !stack.isEmpty && isClaudeCliAvailable();
201
- let docsSpinner = null;
202
- if (stack.isEmpty) {
203
- console.log(chalk.dim(' Generating documentation templates for new project'));
204
- console.log('');
205
- docsSpinner = ora({
206
- text: 'Creating documentation structure...',
207
- indent: 4,
208
- }).start();
209
- }
210
- else if (useClaude) {
211
- console.log(chalk.dim(' Using AI to generate comprehensive documentation'));
212
- console.log('');
213
- docsSpinner = ora({
214
- text: 'Preparing documentation structure...',
215
- indent: 4,
216
- }).start();
217
- }
218
- else {
219
- docsSpinner = ora({
220
- text: 'Generating documentation templates...',
221
- indent: 4,
222
- }).start();
223
- }
134
+ const docsSpinner = ora({ text: 'Fetching documentation...', indent: 4 }).start();
224
135
  try {
225
136
  generatedDocs = await buildDocs(projectPath, projectName, stack, answers, {
226
137
  verbose: options.verbose,
227
- useClaudeInference: useClaude,
138
+ useClaudeInference: hasClaudeCli,
228
139
  onProgress: (message, current, total) => {
229
- if (docsSpinner && current !== undefined && total !== undefined) {
230
- const bar = progressBar(current, total);
140
+ if (current !== undefined && total !== undefined) {
231
141
  const percent = Math.round((current / total) * 100);
232
- docsSpinner.text = `${bar} ${percent}% ${chalk.dim(message)}`;
142
+ docsSpinner.text = `${percent}% ${message}`;
233
143
  }
234
- else if (docsSpinner) {
144
+ else {
235
145
  docsSpinner.text = message;
236
146
  }
237
147
  },
238
148
  });
239
- docsSpinner?.succeed(chalk.green(`Generated ${generatedDocs.length} documentation files`));
240
- // Show documentation summary
241
- console.log('');
242
- console.log(chalk.dim(' ┌─ Documentation Created ──────────────'));
243
- console.log(chalk.dim(' '));
244
- const categories = {};
245
- for (const doc of generatedDocs) {
246
- const cat = doc.category || 'general';
247
- categories[cat] = (categories[cat] || 0) + 1;
248
- }
249
- for (const [category, count] of Object.entries(categories)) {
250
- const icon = getCategoryIcon(category);
251
- console.log(chalk.dim(' │ ') + icon + ' ' + chalk.white(`${category}/`) + chalk.dim(` (${count} files)`));
149
+ docsSpinner.succeed(chalk.green(`Generated ${generatedDocs.length} docs`));
150
+ // Show summary
151
+ const categories = new Set(generatedDocs.map(d => d.category).filter(Boolean));
152
+ if (categories.size > 0) {
153
+ console.log(chalk.dim(` Categories: ${[...categories].join(', ')}`));
252
154
  }
253
- console.log(chalk.dim(' │'));
254
- console.log(chalk.dim(' └──────────────────────────────────────'));
255
155
  }
256
156
  catch (err) {
257
- docsSpinner?.fail(chalk.red('Documentation generation failed'));
258
- console.error(chalk.red(` ${err instanceof Error ? err.message : String(err)}`));
157
+ docsSpinner.fail(chalk.red('Documentation failed'));
158
+ console.error(chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
259
159
  }
260
160
  }
261
161
  else {
262
- console.log(chalk.dim(' Skipping documentation generation'));
162
+ console.log(chalk.dim(' Skipping documentation'));
263
163
  }
264
164
  console.log('');
265
- // Phase 4: AGENTS.md Generation
266
- console.log(chalk.cyan(' Phase 4/5: Agent Context File'));
267
- const agentsSpinner = ora({ text: 'Building AGENTS.md...', indent: 4 }).start();
165
+ // Build AGENTS.md
166
+ const agentsSpinner = ora({ text: 'Creating AGENTS.md...', indent: 4 }).start();
268
167
  const agentsContent = buildAgentsMd(projectPath, stack, generatedDocs, answers);
269
168
  writeAgentsMd(projectPath, agentsContent);
270
169
  agentsSpinner.succeed(chalk.green('Created AGENTS.md'));
271
- console.log(chalk.dim(' Comprehensive AI agent context file'));
272
- console.log('');
273
- // Phase 5: Finalization
274
- console.log(chalk.cyan(' ▸ Phase 5/5: Finalization'));
275
- const finalSpinner = ora({ text: 'Finalizing setup...', indent: 4 }).start();
170
+ // Create CLAUDE.md pointer
276
171
  createClaudeMdPointer(projectPath, projectName);
277
- finalSpinner.succeed(chalk.green('Setup complete'));
278
172
  console.log('');
279
- // Final Summary
173
+ // ==========================================================================
174
+ // Summary
175
+ // ==========================================================================
280
176
  const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
281
177
  console.log(chalk.bold.green(' ╭─────────────────────────────────────╮'));
282
- console.log(chalk.bold.green(' │') + chalk.bold.white(' Onboarding Complete! ') + chalk.bold.green('│'));
178
+ console.log(chalk.bold.green(' │') + chalk.bold.white(' Setup Complete! ') + chalk.bold.green('│'));
283
179
  console.log(chalk.bold.green(' ╰─────────────────────────────────────╯'));
284
180
  console.log('');
285
181
  console.log(chalk.dim(` Completed in ${elapsed}s`));
286
182
  console.log('');
287
- console.log(chalk.white(' Generated files:'));
183
+ console.log(chalk.white(' Files created:'));
184
+ console.log(chalk.cyan(' AGENTS.md') + chalk.dim(' ─────── AI context'));
185
+ if (!options.skipDocs && generatedDocs.length > 0) {
186
+ console.log(chalk.cyan(' .claudetools/docs/') + chalk.dim(' ─ documentation'));
187
+ }
288
188
  console.log('');
289
- console.log(chalk.cyan(' AGENTS.md') + chalk.dim(' ────────────── AI agent entry point'));
290
- if (!options.skipDocs) {
291
- console.log(chalk.cyan(' .claudetools/'));
292
- console.log(chalk.dim(' └── docs/') + chalk.dim(' AI context documentation'));
293
- console.log(chalk.dim(' ├── architecture/') + chalk.dim(' system design, data flow'));
294
- console.log(chalk.dim(' ├── api/') + chalk.dim(' endpoints, schemas'));
295
- console.log(chalk.dim(' ├── guides/') + chalk.dim(' getting started, workflow'));
296
- console.log(chalk.dim(' ├── reference/') + chalk.dim(' config, env, deps'));
297
- console.log(chalk.dim(' ├── patterns/') + chalk.dim(' coding standards'));
298
- console.log(chalk.dim(' ├── testing/') + chalk.dim(' test strategy'));
299
- console.log(chalk.dim(' ├── deployment/') + chalk.dim(' environments, CI/CD'));
300
- console.log(chalk.dim(' └── decisions/') + chalk.dim(' ADRs'));
301
- }
302
- console.log(chalk.cyan(' .claude/CLAUDE.md') + chalk.dim(' ───── config pointer'));
189
+ console.log(chalk.dim(' Refresh: claudetools onboard --refresh'));
303
190
  console.log('');
304
- console.log(chalk.dim(' Next steps:'));
305
- console.log(chalk.dim(' • Review AGENTS.md for accuracy'));
306
- console.log(chalk.dim(' • AI context in .claudetools/ is gitignored (regeneratable)'));
307
- console.log(chalk.dim(' • Run `claudetools onboard --refresh` to update'));
191
+ }
192
+ // =============================================================================
193
+ // Proposal Generation
194
+ // =============================================================================
195
+ /**
196
+ * Format dynamic answers for inclusion in AI prompts
197
+ */
198
+ function formatDynamicAnswers(answers) {
199
+ const dynamicAnswers = Object.entries(answers)
200
+ .filter(([key, value]) => key.startsWith('dynamic_') && value)
201
+ .map(([key, value]) => {
202
+ // Convert dynamic_auth-method to "Auth method"
203
+ const label = key
204
+ .replace('dynamic_', '')
205
+ .replace(/-/g, ' ')
206
+ .replace(/\b\w/g, c => c.toUpperCase());
207
+ return `- ${label}: ${value}`;
208
+ });
209
+ if (dynamicAnswers.length === 0)
210
+ return '';
211
+ return `\nAdditional requirements:\n${dynamicAnswers.join('\n')}`;
212
+ }
213
+ async function generateProposal(projectPath, stack, answers) {
214
+ const isNewProject = stack.isEmpty;
215
+ const spinnerText = isNewProject ? 'Generating stack recommendation...' : 'Analyzing codebase...';
216
+ const spinner = ora({ text: spinnerText, indent: 4 }).start();
217
+ try {
218
+ let prompt;
219
+ if (isNewProject) {
220
+ // For new/empty projects: recommend a complete stack
221
+ const projectTypeDescriptions = {
222
+ web: 'a web application (frontend-focused)',
223
+ api: 'an API/backend service',
224
+ cli: 'a command-line tool',
225
+ library: 'a reusable library/package',
226
+ fullstack: 'a full-stack application with frontend and backend',
227
+ };
228
+ const typeDesc = answers.projectType
229
+ ? projectTypeDescriptions[answers.projectType] || answers.projectType
230
+ : 'a project';
231
+ prompt = `The user wants to build ${typeDesc}.
232
+
233
+ Project description: "${answers.projectDescription || 'Not specified'}"
234
+ ${answers.mainGoal ? `Current focus: ${answers.mainGoal}` : ''}
235
+ ${answers.additionalContext ? `Additional context: ${answers.additionalContext}` : ''}
236
+ ${formatDynamicAnswers(answers)}
237
+
238
+ Recommend a modern, production-ready tech stack for this project.
239
+ Choose popular, well-maintained tools with good documentation.
240
+
241
+ Respond in this exact JSON format:
242
+ {
243
+ "summary": "One sentence describing what this project will be",
244
+ "frameworks": ["primary framework", "supporting frameworks"],
245
+ "libraries": ["essential libraries for this type of project"],
246
+ "devTools": ["typescript", "eslint", "prettier", "testing framework"],
247
+ "architecture": "Recommended architecture approach",
248
+ "recommendations": ["key suggestion 1", "key suggestion 2", "key suggestion 3"]
249
+ }`;
250
+ }
251
+ else {
252
+ // For existing projects: analyze the codebase
253
+ prompt = `Analyze this codebase and provide a brief stack summary.
254
+
255
+ User context:
256
+ ${answers.projectDescription ? `- Project: ${answers.projectDescription}` : ''}
257
+ ${answers.mainGoal ? `- Current focus: ${answers.mainGoal}` : ''}
258
+ ${answers.additionalContext ? `- Notes: ${answers.additionalContext}` : ''}
259
+ ${formatDynamicAnswers(answers)}
260
+
261
+ Detected from package.json:
262
+ - Frameworks: ${stack.frameworks.map(f => f.name).join(', ') || 'none detected'}
263
+ - Libraries: ${stack.libraries.slice(0, 10).map(l => l.name).join(', ') || 'none detected'}
264
+ - Dev tools: ${stack.devTools.map(t => t.name).join(', ') || 'none detected'}
265
+
266
+ Respond in this exact JSON format:
267
+ {
268
+ "summary": "One sentence describing what this project is",
269
+ "frameworks": ["list", "of", "key", "frameworks"],
270
+ "libraries": ["list", "of", "important", "libraries"],
271
+ "devTools": ["list", "of", "dev", "tools"],
272
+ "architecture": "Brief architecture description",
273
+ "recommendations": ["suggestion 1", "suggestion 2"]
274
+ }`;
275
+ }
276
+ const result = await runClaudeAnalysis(projectPath, prompt, 60000);
277
+ spinner.stop();
278
+ if (result) {
279
+ // Try to parse JSON from response
280
+ const jsonMatch = result.match(/\{[\s\S]*\}/);
281
+ if (jsonMatch) {
282
+ try {
283
+ return JSON.parse(jsonMatch[0]);
284
+ }
285
+ catch {
286
+ // Fall through to default
287
+ }
288
+ }
289
+ }
290
+ }
291
+ catch {
292
+ spinner.stop();
293
+ }
294
+ // Default proposal from detected stack
295
+ return {
296
+ summary: answers.projectDescription || `${stack.primaryLanguage} project`,
297
+ frameworks: stack.frameworks.map(f => f.name),
298
+ libraries: stack.libraries.slice(0, 8).map(l => l.name),
299
+ devTools: stack.devTools.map(t => t.name),
300
+ architecture: 'Standard project structure',
301
+ recommendations: [],
302
+ };
303
+ }
304
+ async function refineProposal(projectPath, _stack, _answers, currentProposal, feedback) {
305
+ try {
306
+ const prompt = `Refine this stack proposal based on user feedback.
307
+
308
+ Current proposal:
309
+ ${JSON.stringify(currentProposal, null, 2)}
310
+
311
+ User feedback: "${feedback}"
312
+
313
+ Provide updated proposal in same JSON format:
314
+ {
315
+ "summary": "...",
316
+ "frameworks": [...],
317
+ "libraries": [...],
318
+ "devTools": [...],
319
+ "architecture": "...",
320
+ "recommendations": [...]
321
+ }`;
322
+ const result = await runClaudeAnalysis(projectPath, prompt, 45000);
323
+ if (result) {
324
+ const jsonMatch = result.match(/\{[\s\S]*\}/);
325
+ if (jsonMatch) {
326
+ try {
327
+ return JSON.parse(jsonMatch[0]);
328
+ }
329
+ catch {
330
+ // Fall through
331
+ }
332
+ }
333
+ }
334
+ }
335
+ catch {
336
+ // Fall through
337
+ }
338
+ return currentProposal;
339
+ }
340
+ function displayProposal(proposal) {
308
341
  console.log('');
342
+ console.log(chalk.dim(' ┌─ Proposed Configuration ────────────────'));
343
+ console.log(chalk.dim(' │'));
344
+ console.log(chalk.dim(' │ ') + chalk.white(proposal.summary));
345
+ console.log(chalk.dim(' │'));
346
+ if (proposal.frameworks.length > 0) {
347
+ console.log(chalk.dim(' │ ') + chalk.cyan('Frameworks: ') + proposal.frameworks.join(', '));
348
+ }
349
+ if (proposal.libraries.length > 0) {
350
+ console.log(chalk.dim(' │ ') + chalk.cyan('Libraries: ') + proposal.libraries.slice(0, 6).join(', ') +
351
+ (proposal.libraries.length > 6 ? ` (+${proposal.libraries.length - 6})` : ''));
352
+ }
353
+ if (proposal.devTools.length > 0) {
354
+ console.log(chalk.dim(' │ ') + chalk.cyan('Dev Tools: ') + proposal.devTools.join(', '));
355
+ }
356
+ if (proposal.architecture) {
357
+ console.log(chalk.dim(' │ ') + chalk.cyan('Architecture: ') + proposal.architecture);
358
+ }
359
+ if (proposal.recommendations.length > 0) {
360
+ console.log(chalk.dim(' │'));
361
+ console.log(chalk.dim(' │ ') + chalk.yellow('Recommendations:'));
362
+ for (const rec of proposal.recommendations.slice(0, 3)) {
363
+ console.log(chalk.dim(' │ ') + chalk.dim('• ') + rec);
364
+ }
365
+ }
366
+ console.log(chalk.dim(' │'));
367
+ console.log(chalk.dim(' └──────────────────────────────────────────'));
368
+ console.log('');
369
+ }
370
+ // =============================================================================
371
+ // Scaffolding for New Projects
372
+ // =============================================================================
373
+ async function scaffoldProject(projectPath, proposal, answers) {
374
+ const spinner = ora({ text: 'Scaffolding project...', indent: 4 }).start();
375
+ try {
376
+ // Detect package manager
377
+ const packageManager = detectPackageManager();
378
+ // Generate package.json
379
+ const projectName = basename(projectPath).toLowerCase().replace(/[^a-z0-9-]/g, '-');
380
+ const packageJson = generatePackageJson(projectName, proposal, answers);
381
+ const packageJsonPath = join(projectPath, 'package.json');
382
+ writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2));
383
+ spinner.text = 'Created package.json';
384
+ // Install dependencies
385
+ spinner.text = `Installing dependencies with ${packageManager}...`;
386
+ const installCmd = packageManager === 'yarn' ? 'yarn' : `${packageManager} install`;
387
+ try {
388
+ execSync(installCmd, {
389
+ cwd: projectPath,
390
+ stdio: 'pipe',
391
+ timeout: 120000, // 2 minute timeout
392
+ });
393
+ spinner.succeed(chalk.green(`Scaffolded with ${packageManager}`));
394
+ }
395
+ catch {
396
+ spinner.warn(chalk.yellow('Created package.json (install failed - run manually)'));
397
+ }
398
+ }
399
+ catch (err) {
400
+ spinner.fail(chalk.red('Scaffolding failed'));
401
+ console.error(chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
402
+ }
309
403
  }
310
- function getCategoryIcon(category) {
311
- const icons = {
312
- architecture: '🏗️ ',
313
- api: '🔌',
314
- guides: '📖',
315
- reference: '📚',
316
- patterns: '🎨',
317
- testing: '🧪',
318
- deployment: '🚀',
319
- decisions: '📝',
320
- general: '📄',
404
+ function detectPackageManager() {
405
+ // Check for lock files in current directory or common indicators
406
+ try {
407
+ execSync('pnpm --version', { stdio: 'pipe' });
408
+ return 'pnpm';
409
+ }
410
+ catch {
411
+ // pnpm not available
412
+ }
413
+ try {
414
+ execSync('bun --version', { stdio: 'pipe' });
415
+ return 'bun';
416
+ }
417
+ catch {
418
+ // bun not available
419
+ }
420
+ try {
421
+ execSync('yarn --version', { stdio: 'pipe' });
422
+ return 'yarn';
423
+ }
424
+ catch {
425
+ // yarn not available
426
+ }
427
+ return 'npm';
428
+ }
429
+ function generatePackageJson(projectName, proposal, answers) {
430
+ const dependencies = {};
431
+ const devDependencies = {};
432
+ // Add frameworks as dependencies
433
+ for (const framework of proposal.frameworks) {
434
+ const dep = frameworkToPackage(framework);
435
+ if (dep)
436
+ dependencies[dep] = 'latest';
437
+ }
438
+ // Add libraries as dependencies
439
+ for (const lib of proposal.libraries) {
440
+ const dep = libraryToPackage(lib);
441
+ if (dep)
442
+ dependencies[dep] = 'latest';
443
+ }
444
+ // Add dev tools as devDependencies
445
+ for (const tool of proposal.devTools) {
446
+ const dep = devToolToPackage(tool);
447
+ if (dep)
448
+ devDependencies[dep] = 'latest';
449
+ }
450
+ // Always include TypeScript for modern projects
451
+ if (!devDependencies['typescript']) {
452
+ devDependencies['typescript'] = 'latest';
453
+ }
454
+ return {
455
+ name: projectName,
456
+ version: '0.1.0',
457
+ description: answers.projectDescription || proposal.summary,
458
+ type: 'module',
459
+ scripts: {
460
+ dev: 'echo "Add your dev script"',
461
+ build: 'echo "Add your build script"',
462
+ test: 'echo "Add your test script"',
463
+ },
464
+ dependencies,
465
+ devDependencies,
321
466
  };
322
- return icons[category] || '📄';
323
467
  }
324
- /**
325
- * Create minimal CLAUDE.md that points to AGENTS.md
326
- */
468
+ function frameworkToPackage(framework) {
469
+ const mapping = {
470
+ 'next.js': 'next',
471
+ 'nextjs': 'next',
472
+ 'react': 'react',
473
+ 'vue': 'vue',
474
+ 'nuxt': 'nuxt',
475
+ 'express': 'express',
476
+ 'fastify': 'fastify',
477
+ 'hono': 'hono',
478
+ 'svelte': 'svelte',
479
+ 'sveltekit': '@sveltejs/kit',
480
+ 'astro': 'astro',
481
+ 'remix': '@remix-run/node',
482
+ };
483
+ return mapping[framework.toLowerCase()] || framework.toLowerCase();
484
+ }
485
+ function libraryToPackage(lib) {
486
+ // Most libraries map directly to their package name
487
+ return lib.toLowerCase().replace(/\s+/g, '-');
488
+ }
489
+ function devToolToPackage(tool) {
490
+ const mapping = {
491
+ 'typescript': 'typescript',
492
+ 'eslint': 'eslint',
493
+ 'prettier': 'prettier',
494
+ 'vitest': 'vitest',
495
+ 'jest': 'jest',
496
+ 'biome': '@biomejs/biome',
497
+ };
498
+ return mapping[tool.toLowerCase()] || tool.toLowerCase();
499
+ }
500
+ // =============================================================================
501
+ // Helper Functions
502
+ // =============================================================================
327
503
  function createClaudeMdPointer(projectPath, projectName) {
328
504
  const claudeDir = join(projectPath, '.claude');
329
505
  const claudeMdPath = join(claudeDir, 'CLAUDE.md');
330
- // Create .claude directory if needed
331
506
  if (!existsSync(claudeDir)) {
332
507
  mkdirSync(claudeDir, { recursive: true });
333
508
  }
334
- // Check if CLAUDE.md already exists with custom content
335
509
  if (existsSync(claudeMdPath)) {
336
510
  const existing = readFileSync(claudeMdPath, 'utf-8');
337
- // If it has substantial custom content, don't overwrite
338
511
  if (existing.length > 500 && !existing.includes('See [AGENTS.md]')) {
339
- console.log(chalk.dim(' Existing CLAUDE.md preserved (has custom content)'));
340
- return;
512
+ return; // Don't overwrite custom content
341
513
  }
342
514
  }
343
515
  const content = `# ${projectName}
344
516
 
345
- See [AGENTS.md](../AGENTS.md) for comprehensive project context.
346
-
347
- ## Quick Links
348
-
349
- - Project docs: \`docs/\`
350
- - Agent context: \`AGENTS.md\`
517
+ See [AGENTS.md](../AGENTS.md) for AI context.
351
518
 
352
519
  ---
353
-
354
- *Generated by ClaudeTools. Refresh: \`claudetools onboard --refresh\`*
520
+ *Generated by ClaudeTools*
355
521
  `;
356
522
  writeFileSync(claudeMdPath, content);
357
523
  }
358
- /**
359
- * Print detailed stack information
360
- */
361
- function printStackDetails(stack) {
362
- console.log(chalk.dim('\n Stack Details:'));
363
- if (stack.frameworks.length > 0) {
364
- console.log(chalk.dim(' Frameworks:'));
365
- for (const fw of stack.frameworks) {
366
- console.log(chalk.dim(` - ${fw.name} ${fw.version} (${fw.category})`));
367
- }
368
- }
369
- if (stack.libraries.length > 0) {
370
- console.log(chalk.dim(' Libraries:'));
371
- for (const lib of stack.libraries) {
372
- console.log(chalk.dim(` - ${lib.name} ${lib.version} (${lib.category})`));
373
- }
374
- }
375
- if (stack.devTools.length > 0) {
376
- console.log(chalk.dim(' Dev Tools:'));
377
- for (const tool of stack.devTools) {
378
- console.log(chalk.dim(` - ${tool.name} (${tool.category})`));
379
- }
380
- }
381
- console.log('');
382
- }
383
524
  //# sourceMappingURL=index.js.map