universal-dev-standards 3.0.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.
@@ -0,0 +1,702 @@
1
+ import inquirer from 'inquirer';
2
+ import chalk from 'chalk';
3
+
4
+ /**
5
+ * Prompt for AI tools being used
6
+ * @param {Object} detected - Detected AI tools from project
7
+ * @returns {Promise<string[]>} Selected AI tools
8
+ */
9
+ export async function promptAITools(detected = {}) {
10
+ console.log();
11
+ console.log(chalk.cyan('AI Development Tools:'));
12
+ console.log(chalk.gray(' Select the AI coding assistants you use with this project'));
13
+ console.log();
14
+
15
+ const { tools } = await inquirer.prompt([
16
+ {
17
+ type: 'checkbox',
18
+ name: 'tools',
19
+ message: 'Which AI tools are you using?',
20
+ choices: [
21
+ new inquirer.Separator(chalk.gray('── Dynamic Skills ──')),
22
+ {
23
+ name: `${chalk.green('Claude Code')} ${chalk.gray('(推薦)')} - Anthropic CLI with dynamic Skills`,
24
+ value: 'claude-code',
25
+ checked: detected.claudeCode || false
26
+ },
27
+ new inquirer.Separator(chalk.gray('── Static Rule Files ──')),
28
+ {
29
+ name: `Cursor ${chalk.gray('(.cursorrules)')}`,
30
+ value: 'cursor',
31
+ checked: detected.cursor || false
32
+ },
33
+ {
34
+ name: `Windsurf ${chalk.gray('(.windsurfrules)')}`,
35
+ value: 'windsurf',
36
+ checked: detected.windsurf || false
37
+ },
38
+ {
39
+ name: `Cline ${chalk.gray('(.clinerules)')}`,
40
+ value: 'cline',
41
+ checked: detected.cline || false
42
+ },
43
+ {
44
+ name: `GitHub Copilot ${chalk.gray('(.github/copilot-instructions.md)')}`,
45
+ value: 'copilot',
46
+ checked: detected.copilot || false
47
+ },
48
+ {
49
+ name: `Google Antigravity ${chalk.gray('(INSTRUCTIONS.md)')} - Gemini Agent`,
50
+ value: 'antigravity',
51
+ checked: detected.antigravity || false
52
+ },
53
+ new inquirer.Separator(),
54
+ {
55
+ name: chalk.gray('None / Skip'),
56
+ value: 'none'
57
+ }
58
+ ]
59
+ }
60
+ ]);
61
+
62
+ // Filter out 'none' and separators
63
+ const filtered = tools.filter(t => t !== 'none' && typeof t === 'string');
64
+ return filtered;
65
+ }
66
+
67
+ /**
68
+ * Prompt for Skills installation location
69
+ * @returns {Promise<string>} 'user', 'project', or 'none'
70
+ */
71
+ export async function promptSkillsInstallLocation() {
72
+ console.log();
73
+ console.log(chalk.cyan('Skills Installation:'));
74
+ console.log(chalk.gray(' Choose where to install Claude Code Skills'));
75
+ console.log();
76
+
77
+ const { location } = await inquirer.prompt([
78
+ {
79
+ type: 'list',
80
+ name: 'location',
81
+ message: 'Where should Skills be installed?',
82
+ choices: [
83
+ {
84
+ name: `${chalk.green('User Level')} ${chalk.gray('(推薦)')} - ~/.claude/skills/ (shared across projects)`,
85
+ value: 'user'
86
+ },
87
+ {
88
+ name: `${chalk.blue('Project Level')} - .claude/skills/ (project-specific)`,
89
+ value: 'project'
90
+ },
91
+ {
92
+ name: `${chalk.gray('Skip')} - Do not install Skills`,
93
+ value: 'none'
94
+ }
95
+ ],
96
+ default: 'user'
97
+ }
98
+ ]);
99
+
100
+ // Show explanation
101
+ console.log();
102
+ if (location === 'user') {
103
+ console.log(chalk.gray(' → Skills will be installed to ~/.claude/skills/'));
104
+ console.log(chalk.gray(' → Available across all your projects'));
105
+ } else if (location === 'project') {
106
+ console.log(chalk.gray(' → Skills will be installed to .claude/skills/'));
107
+ console.log(chalk.gray(' → Only available in this project'));
108
+ console.log(chalk.gray(' → Consider adding .claude/skills/ to .gitignore'));
109
+ } else {
110
+ console.log(chalk.gray(' → No Skills will be installed'));
111
+ console.log(chalk.gray(' → Full standards will be copied to .standards/'));
112
+ }
113
+ console.log();
114
+
115
+ return location;
116
+ }
117
+
118
+ /**
119
+ * Prompt for Skills update (dual-level check)
120
+ * @param {Object|null} projectInfo - Project-level Skills info
121
+ * @param {Object|null} userInfo - User-level Skills info
122
+ * @param {string} latestVersion - Latest available version
123
+ * @returns {Promise<Object>} Update decision { action: 'both'|'project'|'user'|'none', targets: string[] }
124
+ */
125
+ export async function promptSkillsUpdate(projectInfo, userInfo, latestVersion) {
126
+ const choices = [];
127
+ const needsUpdate = [];
128
+
129
+ // Check project-level
130
+ if (projectInfo?.installed) {
131
+ const projectVersion = projectInfo.version || 'unknown';
132
+ const projectNeedsUpdate = projectVersion !== latestVersion;
133
+ if (projectNeedsUpdate) {
134
+ needsUpdate.push('project');
135
+ }
136
+ }
137
+
138
+ // Check user-level
139
+ if (userInfo?.installed) {
140
+ const userVersion = userInfo.version || 'unknown';
141
+ const userNeedsUpdate = userVersion !== latestVersion;
142
+ if (userNeedsUpdate) {
143
+ needsUpdate.push('user');
144
+ }
145
+ }
146
+
147
+ // If nothing needs update
148
+ if (needsUpdate.length === 0) {
149
+ console.log(chalk.green('✓ All Skills installations are up to date'));
150
+ return { action: 'none', targets: [] };
151
+ }
152
+
153
+ // Build choices based on what needs updating
154
+ console.log();
155
+ console.log(chalk.cyan('Skills Update Available:'));
156
+
157
+ if (projectInfo?.installed) {
158
+ const pVer = projectInfo.version || 'unknown';
159
+ const pStatus = pVer === latestVersion
160
+ ? chalk.green('✓ up to date')
161
+ : chalk.yellow(`v${pVer} → v${latestVersion}`);
162
+ console.log(chalk.gray(` Project level (.claude/skills/): ${pStatus}`));
163
+ }
164
+
165
+ if (userInfo?.installed) {
166
+ const uVer = userInfo.version || 'unknown';
167
+ const uStatus = uVer === latestVersion
168
+ ? chalk.green('✓ up to date')
169
+ : chalk.yellow(`v${uVer} → v${latestVersion}`);
170
+ console.log(chalk.gray(` User level (~/.claude/skills/): ${uStatus}`));
171
+ }
172
+ console.log();
173
+
174
+ // Build update choices
175
+ if (needsUpdate.includes('project') && needsUpdate.includes('user')) {
176
+ choices.push({
177
+ name: `${chalk.green('Update Both')} - Update all Skills installations`,
178
+ value: 'both'
179
+ });
180
+ }
181
+
182
+ if (needsUpdate.includes('project')) {
183
+ choices.push({
184
+ name: `${chalk.blue('Update Project Level')} - Only update .claude/skills/`,
185
+ value: 'project'
186
+ });
187
+ }
188
+
189
+ if (needsUpdate.includes('user')) {
190
+ choices.push({
191
+ name: `${chalk.blue('Update User Level')} - Only update ~/.claude/skills/`,
192
+ value: 'user'
193
+ });
194
+ }
195
+
196
+ choices.push({
197
+ name: `${chalk.gray('Skip')} - Keep current versions`,
198
+ value: 'none'
199
+ });
200
+
201
+ const { action } = await inquirer.prompt([
202
+ {
203
+ type: 'list',
204
+ name: 'action',
205
+ message: 'What would you like to do?',
206
+ choices,
207
+ default: needsUpdate.length === 2 ? 'both' : needsUpdate[0]
208
+ }
209
+ ]);
210
+
211
+ // Determine targets
212
+ let targets = [];
213
+ if (action === 'both') {
214
+ targets = ['project', 'user'];
215
+ } else if (action === 'project' || action === 'user') {
216
+ targets = [action];
217
+ }
218
+
219
+ return { action, targets };
220
+ }
221
+
222
+ /**
223
+ * Prompt for standards scope when Skills are installed
224
+ * @param {boolean} hasSkills - Whether Skills are installed
225
+ * @returns {Promise<string>} 'full' or 'minimal'
226
+ */
227
+ export async function promptStandardsScope(hasSkills) {
228
+ if (!hasSkills) {
229
+ return 'full';
230
+ }
231
+
232
+ console.log();
233
+ console.log(chalk.cyan('Standards Scope:'));
234
+ console.log(chalk.gray(' Skills cover some standards dynamically. Choose what to install:'));
235
+ console.log();
236
+
237
+ const { scope } = await inquirer.prompt([
238
+ {
239
+ type: 'list',
240
+ name: 'scope',
241
+ message: 'Select standards installation scope:',
242
+ choices: [
243
+ {
244
+ name: `${chalk.green('Minimal')} ${chalk.gray('(推薦)')} - Only static standards (Skills cover the rest)`,
245
+ value: 'minimal'
246
+ },
247
+ {
248
+ name: `${chalk.blue('Full')} - Install all standards (includes Skills-covered)`,
249
+ value: 'full'
250
+ }
251
+ ],
252
+ default: 'minimal'
253
+ }
254
+ ]);
255
+
256
+ // Show explanation
257
+ console.log();
258
+ if (scope === 'minimal') {
259
+ console.log(chalk.gray(' → Only reference standards will be copied'));
260
+ console.log(chalk.gray(' → Skills provide dynamic guidance for covered standards'));
261
+ } else {
262
+ console.log(chalk.gray(' → All standards will be copied to .standards/'));
263
+ console.log(chalk.gray(' → Includes both static files and Skills-covered content'));
264
+ }
265
+ console.log();
266
+
267
+ return scope;
268
+ }
269
+
270
+ /**
271
+ * Prompt for output format (AI or Human-readable)
272
+ * @returns {Promise<string>} 'ai', 'human', or 'both'
273
+ */
274
+ export async function promptFormat() {
275
+ const { format } = await inquirer.prompt([
276
+ {
277
+ type: 'list',
278
+ name: 'format',
279
+ message: 'Select standards format:',
280
+ choices: [
281
+ {
282
+ name: `${chalk.green('AI-Optimized')} ${chalk.gray('(推薦)')} - Token-efficient YAML for AI assistants`,
283
+ value: 'ai'
284
+ },
285
+ {
286
+ name: `${chalk.blue('Human-Readable')} - Full Markdown documentation`,
287
+ value: 'human'
288
+ },
289
+ {
290
+ name: `${chalk.yellow('Both')} - Include both formats`,
291
+ value: 'both'
292
+ }
293
+ ],
294
+ default: 'ai'
295
+ }
296
+ ]);
297
+
298
+ return format;
299
+ }
300
+
301
+ /**
302
+ * Prompt for Git workflow strategy
303
+ * @returns {Promise<string>} Selected workflow ID
304
+ */
305
+ export async function promptGitWorkflow() {
306
+ const { workflow } = await inquirer.prompt([
307
+ {
308
+ type: 'list',
309
+ name: 'workflow',
310
+ message: 'Select Git branching strategy:',
311
+ choices: [
312
+ {
313
+ name: `${chalk.green('GitHub Flow')} ${chalk.gray('(推薦)')} - Simple, continuous deployment`,
314
+ value: 'github-flow'
315
+ },
316
+ {
317
+ name: `${chalk.blue('GitFlow')} - Structured releases with develop/release branches`,
318
+ value: 'gitflow'
319
+ },
320
+ {
321
+ name: `${chalk.yellow('Trunk-Based')} - Direct commits to main, feature flags`,
322
+ value: 'trunk-based'
323
+ }
324
+ ],
325
+ default: 'github-flow'
326
+ }
327
+ ]);
328
+
329
+ return workflow;
330
+ }
331
+
332
+ /**
333
+ * Prompt for merge strategy
334
+ * @returns {Promise<string>} Selected merge strategy ID
335
+ */
336
+ export async function promptMergeStrategy() {
337
+ const { strategy } = await inquirer.prompt([
338
+ {
339
+ type: 'list',
340
+ name: 'strategy',
341
+ message: 'Select merge strategy:',
342
+ choices: [
343
+ {
344
+ name: `${chalk.green('Squash Merge')} ${chalk.gray('(推薦)')} - Clean history, one commit per PR`,
345
+ value: 'squash'
346
+ },
347
+ {
348
+ name: `${chalk.blue('Merge Commit')} - Preserve full branch history`,
349
+ value: 'merge-commit'
350
+ },
351
+ {
352
+ name: `${chalk.yellow('Rebase + Fast-Forward')} - Linear history, advanced`,
353
+ value: 'rebase-ff'
354
+ }
355
+ ],
356
+ default: 'squash'
357
+ }
358
+ ]);
359
+
360
+ return strategy;
361
+ }
362
+
363
+ /**
364
+ * Prompt for commit message language
365
+ * @returns {Promise<string>} Selected language ID
366
+ */
367
+ export async function promptCommitLanguage() {
368
+ const { language } = await inquirer.prompt([
369
+ {
370
+ type: 'list',
371
+ name: 'language',
372
+ message: 'Select commit message language:',
373
+ choices: [
374
+ {
375
+ name: `${chalk.green('English')} ${chalk.gray('(推薦)')} - Standard international format`,
376
+ value: 'english'
377
+ },
378
+ {
379
+ name: `${chalk.blue('Traditional Chinese')} ${chalk.gray('(繁體中文)')} - For Chinese-speaking teams`,
380
+ value: 'traditional-chinese'
381
+ },
382
+ {
383
+ name: `${chalk.yellow('Bilingual')} ${chalk.gray('(雙語)')} - Both English and Chinese`,
384
+ value: 'bilingual'
385
+ }
386
+ ],
387
+ default: 'english'
388
+ }
389
+ ]);
390
+
391
+ return language;
392
+ }
393
+
394
+ /**
395
+ * Prompt for test levels to include
396
+ * @returns {Promise<string[]>} Selected test level IDs
397
+ */
398
+ export async function promptTestLevels() {
399
+ const { levels } = await inquirer.prompt([
400
+ {
401
+ type: 'checkbox',
402
+ name: 'levels',
403
+ message: 'Select test levels to include:',
404
+ choices: [
405
+ {
406
+ name: `Unit Testing ${chalk.gray('(70% pyramid base)')}`,
407
+ value: 'unit-testing',
408
+ checked: true
409
+ },
410
+ {
411
+ name: `Integration Testing ${chalk.gray('(20%)')}`,
412
+ value: 'integration-testing',
413
+ checked: true
414
+ },
415
+ {
416
+ name: `System Testing ${chalk.gray('(7%)')}`,
417
+ value: 'system-testing',
418
+ checked: false
419
+ },
420
+ {
421
+ name: `E2E Testing ${chalk.gray('(3% pyramid top)')}`,
422
+ value: 'e2e-testing',
423
+ checked: false
424
+ }
425
+ ]
426
+ }
427
+ ]);
428
+
429
+ return levels;
430
+ }
431
+
432
+ /**
433
+ * Prompt for all standard options
434
+ * @param {number} level - Adoption level
435
+ * @returns {Promise<Object>} Selected options
436
+ */
437
+ export async function promptStandardOptions(level) {
438
+ const options = {};
439
+
440
+ console.log();
441
+ console.log(chalk.cyan('Standard Options:'));
442
+ console.log(chalk.gray(' Configure your preferred options for each standard'));
443
+ console.log();
444
+
445
+ // Git workflow options (level 2+)
446
+ if (level >= 2) {
447
+ options.workflow = await promptGitWorkflow();
448
+ options.merge_strategy = await promptMergeStrategy();
449
+ }
450
+
451
+ // Commit message options (level 1+)
452
+ options.commit_language = await promptCommitLanguage();
453
+
454
+ // Testing options (level 2+)
455
+ if (level >= 2) {
456
+ options.test_levels = await promptTestLevels();
457
+ }
458
+
459
+ return options;
460
+ }
461
+
462
+ /**
463
+ * Prompt for installation mode
464
+ * @returns {Promise<string>} 'skills' or 'full'
465
+ */
466
+ export async function promptInstallMode() {
467
+ const { mode } = await inquirer.prompt([
468
+ {
469
+ type: 'list',
470
+ name: 'mode',
471
+ message: 'Select installation mode:',
472
+ choices: [
473
+ {
474
+ name: `${chalk.green('Skills Mode')} ${chalk.gray('(推薦)')} - Use Claude Code Skills`,
475
+ value: 'skills'
476
+ },
477
+ {
478
+ name: `${chalk.yellow('Full Mode')} - Install all standards without Skills`,
479
+ value: 'full'
480
+ }
481
+ ],
482
+ default: 'skills'
483
+ }
484
+ ]);
485
+
486
+ // Show explanation based on selection
487
+ console.log();
488
+ if (mode === 'skills') {
489
+ console.log(chalk.gray(' → Skills will be installed to ~/.claude/skills/'));
490
+ console.log(chalk.gray(' → Only static standards will be copied to .standards/'));
491
+ } else {
492
+ console.log(chalk.gray(' → All standards will be copied to .standards/'));
493
+ console.log(chalk.gray(' → No Skills will be installed'));
494
+ }
495
+ console.log();
496
+
497
+ return mode;
498
+ }
499
+
500
+ /**
501
+ * Prompt for Skills upgrade action
502
+ * @param {string} installedVersion - Currently installed version (may be null)
503
+ * @param {string} latestVersion - Latest available version
504
+ * @returns {Promise<string>} 'upgrade', 'keep', or 'reinstall'
505
+ */
506
+ export async function promptSkillsUpgrade(installedVersion, latestVersion) {
507
+ const versionDisplay = installedVersion
508
+ ? `v${installedVersion} → v${latestVersion}`
509
+ : `unknown → v${latestVersion}`;
510
+
511
+ const { action } = await inquirer.prompt([
512
+ {
513
+ type: 'list',
514
+ name: 'action',
515
+ message: `Skills detected (${versionDisplay}). What would you like to do?`,
516
+ choices: [
517
+ {
518
+ name: `${chalk.green('Upgrade')} - Update to latest version`,
519
+ value: 'upgrade'
520
+ },
521
+ {
522
+ name: `${chalk.gray('Keep')} - Keep current version`,
523
+ value: 'keep'
524
+ },
525
+ {
526
+ name: `${chalk.yellow('Reinstall')} - Fresh install (overwrites existing)`,
527
+ value: 'reinstall'
528
+ }
529
+ ],
530
+ default: 'upgrade'
531
+ }
532
+ ]);
533
+
534
+ return action;
535
+ }
536
+
537
+ /**
538
+ * Prompt for adoption level
539
+ * @returns {Promise<number>} Selected level
540
+ */
541
+ export async function promptLevel() {
542
+ const { level } = await inquirer.prompt([
543
+ {
544
+ type: 'list',
545
+ name: 'level',
546
+ message: 'Select adoption level:',
547
+ choices: [
548
+ {
549
+ name: `${chalk.green('Level 1: Essential')} ${chalk.gray('(基本)')} - Minimum viable standards`,
550
+ value: 1
551
+ },
552
+ {
553
+ name: `${chalk.yellow('Level 2: Recommended')} ${chalk.gray('(推薦)')} - Professional quality`,
554
+ value: 2
555
+ },
556
+ {
557
+ name: `${chalk.blue('Level 3: Enterprise')} ${chalk.gray('(企業)')} - Comprehensive standards`,
558
+ value: 3
559
+ }
560
+ ],
561
+ default: 1
562
+ }
563
+ ]);
564
+
565
+ return level;
566
+ }
567
+
568
+ /**
569
+ * Prompt for language extension
570
+ * @param {Object} detected - Detected languages
571
+ * @returns {Promise<string|null>} Selected language or null
572
+ */
573
+ export async function promptLanguage(detected) {
574
+ const choices = [];
575
+
576
+ if (detected.csharp) {
577
+ choices.push({ name: 'C# Style Guide', value: 'csharp', checked: true });
578
+ }
579
+ if (detected.php) {
580
+ choices.push({ name: 'PHP Style Guide (PSR-12)', value: 'php', checked: true });
581
+ }
582
+
583
+ if (choices.length === 0) {
584
+ return null;
585
+ }
586
+
587
+ const { languages } = await inquirer.prompt([
588
+ {
589
+ type: 'checkbox',
590
+ name: 'languages',
591
+ message: 'Detected language(s). Select style guides to include:',
592
+ choices
593
+ }
594
+ ]);
595
+
596
+ return languages;
597
+ }
598
+
599
+ /**
600
+ * Prompt for framework extension
601
+ * @param {Object} detected - Detected frameworks
602
+ * @returns {Promise<string|null>} Selected framework or null
603
+ */
604
+ export async function promptFramework(detected) {
605
+ const choices = [];
606
+
607
+ if (detected['fat-free']) {
608
+ choices.push({ name: 'Fat-Free Framework Patterns', value: 'fat-free', checked: true });
609
+ }
610
+
611
+ if (choices.length === 0) {
612
+ return null;
613
+ }
614
+
615
+ const { frameworks } = await inquirer.prompt([
616
+ {
617
+ type: 'checkbox',
618
+ name: 'frameworks',
619
+ message: 'Detected framework(s). Select patterns to include:',
620
+ choices
621
+ }
622
+ ]);
623
+
624
+ return frameworks;
625
+ }
626
+
627
+ /**
628
+ * Prompt for locale
629
+ * @returns {Promise<string|null>} Selected locale or null
630
+ */
631
+ export async function promptLocale() {
632
+ const { useLocale } = await inquirer.prompt([
633
+ {
634
+ type: 'confirm',
635
+ name: 'useLocale',
636
+ message: 'Use Traditional Chinese (繁體中文) locale?',
637
+ default: false
638
+ }
639
+ ]);
640
+
641
+ return useLocale ? 'zh-tw' : null;
642
+ }
643
+
644
+ /**
645
+ * Prompt for AI tool integrations
646
+ * @param {Object} detected - Detected AI tools
647
+ * @returns {Promise<string[]>} Selected integrations
648
+ */
649
+ export async function promptIntegrations(detected) {
650
+ const choices = [];
651
+
652
+ // Existing tools (checked if detected)
653
+ choices.push({
654
+ name: 'Cursor (.cursorrules)',
655
+ value: 'cursor',
656
+ checked: detected.cursor || false
657
+ });
658
+ choices.push({
659
+ name: 'Windsurf (.windsurfrules)',
660
+ value: 'windsurf',
661
+ checked: detected.windsurf || false
662
+ });
663
+ choices.push({
664
+ name: 'Cline (.clinerules)',
665
+ value: 'cline',
666
+ checked: detected.cline || false
667
+ });
668
+ choices.push({
669
+ name: 'GitHub Copilot (.github/copilot-instructions.md)',
670
+ value: 'copilot',
671
+ checked: detected.copilot || false
672
+ });
673
+
674
+ const { integrations } = await inquirer.prompt([
675
+ {
676
+ type: 'checkbox',
677
+ name: 'integrations',
678
+ message: 'Select AI tool integrations:',
679
+ choices
680
+ }
681
+ ]);
682
+
683
+ return integrations;
684
+ }
685
+
686
+ /**
687
+ * Prompt for confirmation
688
+ * @param {string} message - Confirmation message
689
+ * @returns {Promise<boolean>} True if confirmed
690
+ */
691
+ export async function promptConfirm(message) {
692
+ const { confirmed } = await inquirer.prompt([
693
+ {
694
+ type: 'confirm',
695
+ name: 'confirmed',
696
+ message,
697
+ default: true
698
+ }
699
+ ]);
700
+
701
+ return confirmed;
702
+ }