@claudetools/cli 0.10.0 → 0.11.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.
@@ -1,274 +1,427 @@
1
1
  // =============================================================================
2
- // Interactive Onboarding Questions
2
+ // Interactive Onboarding Questions - Progressive Q&A System
3
3
  // =============================================================================
4
- // Dynamic Q&A flow to understand project context
4
+ // Comprehensive questioning that adapts based on project type and user answers
5
5
  import prompts from 'prompts';
6
6
  import chalk from 'chalk';
7
- // Dynamic questions based on detected stack
8
- const QUESTIONS = [
9
- {
10
- id: 'projectDescription',
11
- question: 'Briefly describe what this project does:',
7
+ /**
8
+ * Run comprehensive interactive onboarding with progressive questions
9
+ */
10
+ export async function runInteractiveOnboarding(stack, options = {}) {
11
+ const answers = {};
12
+ const isNew = options.isNewProject || stack.isEmpty;
13
+ console.log('');
14
+ console.log(chalk.dim(' ┌─ Project Understanding ─────────────────'));
15
+ console.log(chalk.dim(' │'));
16
+ console.log(chalk.dim(' │ Answer questions to help generate better docs.'));
17
+ console.log(chalk.dim(' │ Press Enter to skip, Ctrl+C to finish early.'));
18
+ console.log(chalk.dim(' │ The more you share, the better the results.'));
19
+ console.log(chalk.dim(' │'));
20
+ console.log(chalk.dim(' └──────────────────────────────────────────'));
21
+ console.log('');
22
+ // =========================================================================
23
+ // PHASE 1: Essential Questions (Always ask)
24
+ // =========================================================================
25
+ console.log(chalk.cyan.bold(' 📋 Project Basics\n'));
26
+ // Q1: What is this project?
27
+ const descResponse = await safePrompt({
12
28
  type: 'text',
13
- category: 'Overview',
14
- },
15
- {
16
- id: 'primaryPurpose',
17
- question: 'What is the primary purpose of this project?',
29
+ name: 'projectDescription',
30
+ message: isNew
31
+ ? 'What do you want to build? (describe your project)'
32
+ : 'What is this project? (brief description)',
33
+ });
34
+ if (descResponse?.projectDescription && typeof descResponse.projectDescription === 'string') {
35
+ answers.projectDescription = descResponse.projectDescription;
36
+ }
37
+ // Q2: Project type
38
+ const typeResponse = await safePrompt({
18
39
  type: 'select',
19
- options: [
20
- 'Web application (user-facing)',
21
- 'API/Backend service',
22
- 'CLI tool',
23
- 'Library/Package',
24
- 'Mobile app',
25
- 'Desktop app',
26
- 'Documentation site',
27
- 'Internal tool',
28
- 'Other',
40
+ name: 'projectType',
41
+ message: 'What type of project is this?',
42
+ choices: [
43
+ { title: 'Web Application', value: 'web-app', description: 'Full-stack or frontend web app' },
44
+ { title: 'API / Backend', value: 'api', description: 'REST API, GraphQL, or backend service' },
45
+ { title: 'CLI Tool', value: 'cli', description: 'Command-line application' },
46
+ { title: 'Library / Package', value: 'library', description: 'Reusable code library' },
47
+ { title: 'Mobile App', value: 'mobile', description: 'iOS, Android, or cross-platform' },
48
+ { title: 'Desktop App', value: 'desktop', description: 'Electron, Tauri, etc.' },
49
+ { title: 'Monorepo', value: 'monorepo', description: 'Multiple packages in one repo' },
50
+ { title: 'Other', value: 'other', description: 'Something else' },
29
51
  ],
30
- category: 'Overview',
31
- },
32
- {
33
- id: 'targetAudience',
34
- question: 'Who is the target audience?',
52
+ initial: guessProjectType(stack),
53
+ });
54
+ if (typeResponse?.projectType && typeof typeResponse.projectType === 'string') {
55
+ answers.projectType = typeResponse.projectType;
56
+ }
57
+ // Q3: Primary purpose - more specific based on type
58
+ const purposeResponse = await safePrompt({
59
+ type: 'text',
60
+ name: 'primaryPurpose',
61
+ message: 'What problem does this solve? (be specific)',
62
+ });
63
+ if (purposeResponse?.primaryPurpose && typeof purposeResponse.primaryPurpose === 'string') {
64
+ answers.primaryPurpose = purposeResponse.primaryPurpose;
65
+ }
66
+ // Q4: Key features (if they described the project)
67
+ if (answers.projectDescription || answers.primaryPurpose) {
68
+ const featuresResponse = await safePrompt({
69
+ type: 'text',
70
+ name: 'keyFeatures',
71
+ message: 'What are the main features? (comma-separated)',
72
+ });
73
+ if (featuresResponse?.keyFeatures && typeof featuresResponse.keyFeatures === 'string') {
74
+ answers.keyFeatures = featuresResponse.keyFeatures.split(',').map((f) => f.trim()).filter(Boolean);
75
+ }
76
+ }
77
+ // =========================================================================
78
+ // PHASE 2: Context Questions
79
+ // =========================================================================
80
+ console.log('');
81
+ console.log(chalk.cyan.bold(' 👥 Context\n'));
82
+ // Q5: Target audience
83
+ const audienceResponse = await safePrompt({
35
84
  type: 'select',
36
- options: [
37
- 'End users (consumers)',
38
- 'Business users (B2B)',
39
- 'Developers',
40
- 'Internal team',
41
- 'Mixed audience',
85
+ name: 'targetAudience',
86
+ message: 'Who will use this?',
87
+ choices: [
88
+ { title: 'End users (consumers)', value: 'consumers' },
89
+ { title: 'Business users (B2B)', value: 'business' },
90
+ { title: 'Developers', value: 'developers' },
91
+ { title: 'Internal team only', value: 'internal' },
92
+ { title: 'Mixed audience', value: 'mixed' },
42
93
  ],
43
- category: 'Overview',
44
- },
45
- {
46
- id: 'architectureStyle',
47
- question: 'What architectural style does this project follow?',
94
+ });
95
+ if (audienceResponse?.targetAudience && typeof audienceResponse.targetAudience === 'string') {
96
+ answers.targetAudience = audienceResponse.targetAudience;
97
+ }
98
+ // Q6: Experience level - affects documentation style
99
+ const expResponse = await safePrompt({
48
100
  type: 'select',
49
- options: [
50
- 'Monolithic',
51
- 'Microservices',
52
- 'Serverless',
53
- 'JAMstack',
54
- 'Modular monolith',
55
- 'Event-driven',
56
- 'Not sure / Mixed',
101
+ name: 'experienceLevel',
102
+ message: 'Your experience with this stack?',
103
+ choices: [
104
+ { title: 'Beginner', value: 'beginner', description: 'Learning as I go - need detailed explanations' },
105
+ { title: 'Intermediate', value: 'intermediate', description: 'Comfortable but still learning' },
106
+ { title: 'Expert', value: 'expert', description: 'Very experienced - just need references' },
57
107
  ],
58
- category: 'Architecture',
59
- condition: (stack) => stack.frameworks.length > 0,
60
- },
61
- {
62
- id: 'stateManagement',
63
- question: 'How is state managed in this project?',
108
+ initial: 1,
109
+ });
110
+ if (expResponse?.experienceLevel && typeof expResponse.experienceLevel === 'string') {
111
+ const level = expResponse.experienceLevel;
112
+ if (level === 'beginner' || level === 'intermediate' || level === 'expert') {
113
+ answers.experienceLevel = level;
114
+ }
115
+ }
116
+ // Q7: Team size
117
+ const teamResponse = await safePrompt({
64
118
  type: 'select',
65
- options: [
66
- 'React Context',
67
- 'Redux / Redux Toolkit',
68
- 'Zustand',
69
- 'Jotai / Recoil',
70
- 'MobX',
71
- 'Server state only (TanStack Query/SWR)',
72
- 'URL state',
73
- 'No client state management',
74
- 'Other',
119
+ name: 'teamSize',
120
+ message: 'Team size?',
121
+ choices: [
122
+ { title: 'Solo developer', value: 'solo' },
123
+ { title: 'Small team (2-5)', value: 'small' },
124
+ { title: 'Medium team (6-15)', value: 'medium' },
125
+ { title: 'Large team (15+)', value: 'large' },
75
126
  ],
76
- category: 'Architecture',
77
- condition: (stack) => stack.frameworks.some(f => ['react', 'next', 'vue', 'svelte'].includes(f.name.toLowerCase())),
78
- },
79
- {
80
- id: 'dataFetching',
81
- question: 'How is data fetched in this project?',
82
- type: 'select',
83
- options: [
84
- 'TanStack Query (React Query)',
85
- 'SWR',
86
- 'tRPC',
87
- 'GraphQL (Apollo/urql)',
88
- 'REST with fetch/axios',
89
- 'Server Components (Next.js)',
90
- 'Server Actions',
91
- 'Mixed approach',
127
+ });
128
+ if (teamResponse?.teamSize && typeof teamResponse.teamSize === 'string') {
129
+ answers.teamSize = teamResponse.teamSize;
130
+ }
131
+ // =========================================================================
132
+ // PHASE 3: Technical Questions (based on project type)
133
+ // =========================================================================
134
+ const isWebOrMobile = ['web-app', 'mobile'].includes(answers.projectType || '');
135
+ const isApi = answers.projectType === 'api';
136
+ const hasFrontendFramework = stack.frameworks.some(f => ['react', 'next', 'vue', 'nuxt', 'svelte', 'sveltekit', 'angular'].includes(f.name.toLowerCase()));
137
+ if (isWebOrMobile || hasFrontendFramework) {
138
+ console.log('');
139
+ console.log(chalk.cyan.bold(' ⚙️ Technical Choices\n'));
140
+ // Architecture
141
+ const archResponse = await safePrompt({
142
+ type: 'select',
143
+ name: 'architectureStyle',
144
+ message: 'How is the code organized?',
145
+ choices: [
146
+ { title: 'Feature-based', value: 'feature-based', description: 'Folders by feature/domain' },
147
+ { title: 'Component-based', value: 'component-based', description: 'UI components hierarchy' },
148
+ { title: 'Layered', value: 'layered', description: 'UI → Business → Data layers' },
149
+ { title: 'Atomic Design', value: 'atomic', description: 'Atoms → Molecules → Organisms' },
150
+ { title: 'Not organized yet', value: 'none', description: 'Still figuring it out' },
151
+ { title: 'Not sure', value: 'unsure', description: 'Let AI analyze and document' },
152
+ ],
153
+ });
154
+ if (archResponse?.architectureStyle && typeof archResponse.architectureStyle === 'string') {
155
+ answers.architectureStyle = archResponse.architectureStyle;
156
+ }
157
+ // State management (frontend only)
158
+ if (hasFrontendFramework) {
159
+ const stateResponse = await safePrompt({
160
+ type: 'select',
161
+ name: 'stateManagement',
162
+ message: 'State management approach?',
163
+ choices: getStateManagementChoices(stack),
164
+ });
165
+ if (stateResponse?.stateManagement && typeof stateResponse.stateManagement === 'string') {
166
+ answers.stateManagement = stateResponse.stateManagement;
167
+ }
168
+ // Data fetching
169
+ const dataResponse = await safePrompt({
170
+ type: 'select',
171
+ name: 'dataFetching',
172
+ message: 'How do you fetch data?',
173
+ choices: [
174
+ { title: 'TanStack Query', value: 'tanstack-query', description: 'Powerful async state' },
175
+ { title: 'SWR', value: 'swr', description: 'Stale-while-revalidate' },
176
+ { title: 'tRPC', value: 'trpc', description: 'End-to-end typesafe' },
177
+ { title: 'Plain fetch/axios', value: 'fetch', description: 'Simple HTTP' },
178
+ { title: 'GraphQL', value: 'graphql', description: 'Apollo, urql, etc.' },
179
+ { title: 'Server Components', value: 'rsc', description: 'Next.js RSC' },
180
+ { title: 'Not sure', value: 'unsure' },
181
+ ],
182
+ initial: detectDataFetchingChoice(stack),
183
+ });
184
+ if (dataResponse?.dataFetching && typeof dataResponse.dataFetching === 'string') {
185
+ answers.dataFetching = dataResponse.dataFetching;
186
+ }
187
+ }
188
+ }
189
+ // Authentication (API or web app)
190
+ if (isApi || isWebOrMobile) {
191
+ const authResponse = await safePrompt({
192
+ type: 'select',
193
+ name: 'authenticationMethod',
194
+ message: 'Authentication method?',
195
+ choices: [
196
+ { title: 'None yet', value: 'none' },
197
+ { title: 'JWT tokens', value: 'jwt' },
198
+ { title: 'Session cookies', value: 'session' },
199
+ { title: 'OAuth (Google, GitHub)', value: 'oauth' },
200
+ { title: 'Auth provider (Auth0, Clerk)', value: 'provider' },
201
+ { title: 'API keys', value: 'api-keys' },
202
+ { title: 'Not sure', value: 'unsure' },
203
+ ],
204
+ });
205
+ if (authResponse?.authenticationMethod && typeof authResponse.authenticationMethod === 'string') {
206
+ answers.authenticationMethod = authResponse.authenticationMethod;
207
+ }
208
+ }
209
+ // =========================================================================
210
+ // PHASE 4: Development Practices
211
+ // =========================================================================
212
+ console.log('');
213
+ console.log(chalk.cyan.bold(' 🛠️ Development Practices\n'));
214
+ // Testing
215
+ const testResponse = await safePrompt({
216
+ type: 'multiselect',
217
+ name: 'testingApproach',
218
+ message: 'Testing approach? (Space to select)',
219
+ choices: [
220
+ { title: 'Unit tests', value: 'unit' },
221
+ { title: 'Integration tests', value: 'integration' },
222
+ { title: 'E2E tests (Playwright/Cypress)', value: 'e2e' },
223
+ { title: 'Component tests', value: 'component' },
224
+ { title: 'API tests', value: 'api' },
225
+ { title: 'No tests yet', value: 'none' },
92
226
  ],
93
- category: 'Architecture',
94
- condition: (stack) => stack.frameworks.some(f => ['react', 'next', 'vue', 'svelte'].includes(f.name.toLowerCase())),
95
- },
96
- {
97
- id: 'testingApproach',
98
- question: 'What testing approaches are used?',
227
+ });
228
+ if (testResponse?.testingApproach && Array.isArray(testResponse.testingApproach) && testResponse.testingApproach.length > 0) {
229
+ answers.testingApproach = testResponse.testingApproach;
230
+ }
231
+ // Coding conventions
232
+ const convResponse = await safePrompt({
99
233
  type: 'multiselect',
100
- options: [
101
- 'Unit tests',
102
- 'Integration tests',
103
- 'End-to-end tests (Playwright/Cypress)',
104
- 'Component tests',
105
- 'API tests',
106
- 'Snapshot tests',
107
- 'No tests yet',
234
+ name: 'codingConventions',
235
+ message: 'Coding conventions? (Space to select)',
236
+ choices: [
237
+ { title: 'TypeScript strict mode', value: 'typescript-strict', selected: hasTypeScript(stack) },
238
+ { title: 'ESLint + Prettier', value: 'eslint-prettier', selected: hasEslint(stack) },
239
+ { title: 'Conventional commits', value: 'conventional-commits' },
240
+ { title: 'Feature folders', value: 'feature-folders' },
241
+ { title: 'Barrel exports (index.ts)', value: 'barrel-exports' },
242
+ { title: 'No strict conventions', value: 'none' },
108
243
  ],
109
- category: 'Quality',
110
- },
111
- {
112
- id: 'codingConventions',
113
- question: 'Which coding conventions does this project follow?',
244
+ });
245
+ if (convResponse?.codingConventions && Array.isArray(convResponse.codingConventions) && convResponse.codingConventions.length > 0) {
246
+ answers.codingConventions = convResponse.codingConventions;
247
+ }
248
+ // Important patterns
249
+ const patternResponse = await safePrompt({
114
250
  type: 'multiselect',
115
- options: [
116
- 'Functional components only (no classes)',
117
- 'Strict TypeScript (no any)',
118
- 'Named exports over default exports',
119
- 'Barrel files (index.ts re-exports)',
120
- 'Co-located tests (__tests__ folders)',
121
- 'Feature-based folder structure',
122
- 'Atomic design (atoms/molecules/organisms)',
123
- 'Domain-driven design',
251
+ name: 'importantPatterns',
252
+ message: 'Key patterns used? (Space to select)',
253
+ choices: [
254
+ { title: 'Custom hooks', value: 'custom-hooks' },
255
+ { title: 'Compound components', value: 'compound-components' },
256
+ { title: 'Repository pattern', value: 'repository' },
257
+ { title: 'Factory pattern', value: 'factory' },
258
+ { title: 'Dependency injection', value: 'di' },
259
+ { title: 'None specifically', value: 'none' },
124
260
  ],
125
- category: 'Conventions',
126
- },
127
- {
128
- id: 'importantPatterns',
129
- question: 'Which patterns are important in this codebase?',
261
+ });
262
+ if (patternResponse?.importantPatterns && Array.isArray(patternResponse.importantPatterns) && patternResponse.importantPatterns.length > 0) {
263
+ answers.importantPatterns = patternResponse.importantPatterns;
264
+ }
265
+ // =========================================================================
266
+ // PHASE 5: Priorities and Challenges
267
+ // =========================================================================
268
+ console.log('');
269
+ console.log(chalk.cyan.bold(' 🎯 Current Focus\n'));
270
+ // Priorities
271
+ const priorityResponse = await safePrompt({
130
272
  type: 'multiselect',
131
- options: [
132
- 'Repository pattern',
133
- 'Factory pattern',
134
- 'Dependency injection',
135
- 'Composition over inheritance',
136
- 'Custom hooks for logic reuse',
137
- 'Higher-order components',
138
- 'Render props',
139
- 'Compound components',
140
- 'None specifically',
273
+ name: 'currentPriorities',
274
+ message: 'Current priorities? (pick top 3)',
275
+ choices: [
276
+ { title: 'Get MVP working', value: 'mvp' },
277
+ { title: 'Ship new features', value: 'features' },
278
+ { title: 'Fix bugs', value: 'bugs' },
279
+ { title: 'Improve performance', value: 'performance' },
280
+ { title: 'Add tests', value: 'testing' },
281
+ { title: 'Refactor/cleanup', value: 'refactor' },
282
+ { title: 'Better documentation', value: 'docs' },
141
283
  ],
142
- category: 'Patterns',
143
- },
144
- {
145
- id: 'knownChallenges',
146
- question: 'Any known challenges or areas needing attention?',
284
+ max: 3,
285
+ });
286
+ if (priorityResponse?.currentPriorities && Array.isArray(priorityResponse.currentPriorities) && priorityResponse.currentPriorities.length > 0) {
287
+ answers.currentPriorities = priorityResponse.currentPriorities;
288
+ }
289
+ // Challenges - free text for rich context
290
+ const challengeResponse = await safePrompt({
147
291
  type: 'text',
148
- category: 'Context',
149
- },
150
- {
151
- id: 'teamSize',
152
- question: 'Team size working on this project?',
153
- type: 'select',
154
- options: [
155
- 'Solo developer',
156
- 'Small team (2-5)',
157
- 'Medium team (6-15)',
158
- 'Large team (15+)',
159
- ],
160
- category: 'Context',
161
- },
162
- ];
163
- export async function runInteractiveOnboarding(stack, options = {}) {
164
- const answers = {};
165
- let questionCount = 0;
166
- let currentCategory = '';
167
- console.log(chalk.dim('\n Answer a few questions to help generate better documentation.'));
168
- console.log(chalk.dim(' Press Enter to skip any question.\n'));
169
- for (const q of QUESTIONS) {
170
- // Check condition
171
- if (q.condition && !q.condition(stack, answers)) {
172
- continue;
173
- }
174
- // Show category header
175
- if (q.category !== currentCategory) {
176
- currentCategory = q.category;
177
- console.log(chalk.cyan.bold(`\n ${currentCategory}`));
292
+ name: 'knownChallenges',
293
+ message: 'Any pain points or challenges?',
294
+ });
295
+ if (challengeResponse?.knownChallenges && typeof challengeResponse.knownChallenges === 'string') {
296
+ answers.knownChallenges = challengeResponse.knownChallenges;
297
+ }
298
+ // =========================================================================
299
+ // PHASE 6: New Project Stack (if empty project)
300
+ // =========================================================================
301
+ if (isNew && stack.isEmpty) {
302
+ console.log('');
303
+ console.log(chalk.cyan.bold(' 🚀 Stack Setup\n'));
304
+ const recResponse = await safePrompt({
305
+ type: 'confirm',
306
+ name: 'needsRecommendation',
307
+ message: 'Want AI to recommend a tech stack?',
308
+ initial: true,
309
+ });
310
+ if (recResponse?.needsRecommendation !== undefined && typeof recResponse.needsRecommendation === 'boolean') {
311
+ answers.needsRecommendation = recResponse.needsRecommendation;
178
312
  }
179
- questionCount++;
180
- try {
181
- if (q.type === 'text') {
182
- const { response } = await prompts({
183
- type: 'text',
184
- name: 'response',
185
- message: q.question,
186
- }, { onCancel: () => { throw new Error('cancelled'); } });
187
- if (response && response.trim()) {
188
- answers[q.id] = response.trim();
189
- }
190
- }
191
- else if (q.type === 'select') {
192
- const { selected } = await prompts({
193
- type: 'select',
194
- name: 'selected',
195
- message: q.question,
196
- choices: [
197
- ...(q.options || []).map(opt => ({ title: opt, value: opt })),
198
- { title: chalk.dim('Skip'), value: '__skip__' },
199
- ],
200
- }, { onCancel: () => { throw new Error('cancelled'); } });
201
- if (selected && selected !== '__skip__') {
202
- answers[q.id] = selected;
203
- }
313
+ if (!answers.needsRecommendation) {
314
+ const stackResponse = await safePrompt({
315
+ type: 'text',
316
+ name: 'preferredStack',
317
+ message: 'What stack do you want?',
318
+ });
319
+ if (stackResponse?.preferredStack && typeof stackResponse.preferredStack === 'string') {
320
+ answers.preferredStack = stackResponse.preferredStack;
204
321
  }
205
- else if (q.type === 'multiselect') {
206
- const { selected } = await prompts({
207
- type: 'multiselect',
208
- name: 'selected',
209
- message: q.question,
210
- choices: (q.options || []).map(opt => ({ title: opt, value: opt })),
211
- hint: '- Space to select, Enter to confirm',
212
- }, { onCancel: () => { throw new Error('cancelled'); } });
213
- if (selected && selected.length > 0) {
214
- answers[q.id] = selected;
215
- }
216
- }
217
- }
218
- catch {
219
- // User cancelled
220
- console.log(chalk.dim('\n Onboarding cancelled. Using auto-detected information only.\n'));
221
- break;
222
322
  }
223
323
  }
224
- if (options.verbose) {
225
- console.log(chalk.dim(`\n Collected ${Object.keys(answers).length} answers from ${questionCount} questions.`));
226
- }
324
+ console.log('');
325
+ const answeredCount = Object.keys(answers).filter(k => answers[k] !== undefined).length;
326
+ console.log(chalk.dim(` Collected ${answeredCount} responses. Generating documentation...\n`));
227
327
  return answers;
228
328
  }
329
+ // =========================================================================
330
+ // Safe prompt wrapper - handles cancellation gracefully
331
+ // =========================================================================
332
+ async function safePrompt(config) {
333
+ try {
334
+ const response = await prompts(config, {
335
+ onCancel: () => {
336
+ throw new Error('cancelled');
337
+ },
338
+ });
339
+ return response;
340
+ }
341
+ catch {
342
+ return null;
343
+ }
344
+ }
345
+ // =========================================================================
346
+ // Helper Functions
347
+ // =========================================================================
348
+ function guessProjectType(stack) {
349
+ if (stack.frameworks.some(f => ['next', 'nuxt', 'sveltekit', 'remix', 'astro'].includes(f.name.toLowerCase()))) {
350
+ return 0; // web-app
351
+ }
352
+ if (stack.frameworks.some(f => ['express', 'fastify', 'hono', 'nestjs', 'koa'].includes(f.name.toLowerCase()))) {
353
+ return 1; // api
354
+ }
355
+ if (stack.frameworks.some(f => ['react-native', 'expo'].includes(f.name.toLowerCase()))) {
356
+ return 4; // mobile
357
+ }
358
+ if (stack.frameworks.some(f => ['electron', 'tauri'].includes(f.name.toLowerCase()))) {
359
+ return 5; // desktop
360
+ }
361
+ return 0;
362
+ }
363
+ function getStateManagementChoices(stack) {
364
+ const choices = [
365
+ { title: 'React Context / useState', value: 'context' },
366
+ { title: 'Zustand', value: 'zustand' },
367
+ { title: 'Jotai', value: 'jotai' },
368
+ { title: 'Redux Toolkit', value: 'redux' },
369
+ { title: 'Server state only', value: 'server-state' },
370
+ { title: 'Not sure', value: 'unsure' },
371
+ ];
372
+ // Detect from stack
373
+ const detected = stack.libraries.find(l => ['zustand', 'jotai', 'redux', '@reduxjs/toolkit', 'recoil', 'mobx'].includes(l.name));
374
+ if (detected) {
375
+ const name = detected.name.replace('@reduxjs/toolkit', 'redux');
376
+ const idx = choices.findIndex(c => c.value === name);
377
+ if (idx > 0) {
378
+ const [item] = choices.splice(idx, 1);
379
+ if (item)
380
+ choices.unshift(item);
381
+ }
382
+ }
383
+ return choices;
384
+ }
385
+ function detectDataFetchingChoice(stack) {
386
+ if (stack.libraries.some(l => l.name.includes('tanstack') || l.name.includes('react-query')))
387
+ return 0;
388
+ if (stack.libraries.some(l => l.name === 'swr'))
389
+ return 1;
390
+ if (stack.libraries.some(l => l.name.includes('trpc')))
391
+ return 2;
392
+ if (stack.frameworks.some(f => f.name === 'next'))
393
+ return 5;
394
+ return 3;
395
+ }
396
+ function hasTypeScript(stack) {
397
+ return stack.primaryLanguage === 'typescript';
398
+ }
399
+ function hasEslint(stack) {
400
+ return stack.devTools.some(t => t.name === 'eslint' || t.name === 'prettier');
401
+ }
402
+ /**
403
+ * Format answers for documentation
404
+ */
229
405
  export function formatAnswersForDocs(answers) {
230
406
  const sections = [];
231
407
  if (answers.projectDescription) {
232
- sections.push(`## Project Description\n\n${answers.projectDescription}`);
408
+ sections.push(`## Project Overview\n\n${answers.projectDescription}`);
233
409
  }
234
- if (answers.primaryPurpose || answers.targetAudience) {
235
- let overview = '## Overview\n\n';
236
- if (answers.primaryPurpose) {
237
- overview += `**Purpose:** ${answers.primaryPurpose}\n`;
238
- }
239
- if (answers.targetAudience) {
240
- overview += `**Target Audience:** ${answers.targetAudience}\n`;
241
- }
242
- sections.push(overview);
410
+ if (answers.primaryPurpose) {
411
+ sections.push(`## Purpose\n\n${answers.primaryPurpose}`);
243
412
  }
244
- if (answers.architectureStyle || answers.stateManagement || answers.dataFetching) {
245
- let arch = '## Architecture\n\n';
246
- if (answers.architectureStyle) {
247
- arch += `**Style:** ${answers.architectureStyle}\n`;
248
- }
249
- if (answers.stateManagement) {
250
- arch += `**State Management:** ${answers.stateManagement}\n`;
251
- }
252
- if (answers.dataFetching) {
253
- arch += `**Data Fetching:** ${answers.dataFetching}\n`;
254
- }
255
- sections.push(arch);
256
- }
257
- if (answers.codingConventions && Array.isArray(answers.codingConventions) && answers.codingConventions.length > 0) {
258
- sections.push(`## Coding Conventions\n\n${answers.codingConventions.map((c) => `- ${c}`).join('\n')}`);
413
+ if (answers.keyFeatures?.length) {
414
+ sections.push(`## Key Features\n\n${answers.keyFeatures.map(f => `- ${f}`).join('\n')}`);
259
415
  }
260
- if (answers.importantPatterns && Array.isArray(answers.importantPatterns) && answers.importantPatterns.length > 0) {
261
- sections.push(`## Important Patterns\n\n${answers.importantPatterns.map((p) => `- ${p}`).join('\n')}`);
416
+ if (answers.targetAudience) {
417
+ sections.push(`## Target Audience\n\n${answers.targetAudience}`);
262
418
  }
263
- if (answers.testingApproach && Array.isArray(answers.testingApproach) && answers.testingApproach.length > 0) {
264
- sections.push(`## Testing\n\n${answers.testingApproach.map((t) => `- ${t}`).join('\n')}`);
419
+ if (answers.architectureStyle && answers.architectureStyle !== 'unsure') {
420
+ sections.push(`## Architecture\n\n**Style:** ${answers.architectureStyle}`);
265
421
  }
266
422
  if (answers.knownChallenges) {
267
423
  sections.push(`## Known Challenges\n\n${answers.knownChallenges}`);
268
424
  }
269
- if (answers.teamSize) {
270
- sections.push(`## Team\n\n**Size:** ${answers.teamSize}`);
271
- }
272
425
  return sections.join('\n\n');
273
426
  }
274
427
  //# sourceMappingURL=questions.js.map