bmad-method 6.0.0-Beta.5 → 6.0.0-Beta.7

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.
Files changed (119) hide show
  1. package/.claude/skills/changelog-social/SKILL.md +178 -0
  2. package/.claude/skills/changelog-social/examples/discord-example.md +53 -0
  3. package/.claude/skills/changelog-social/examples/linkedin-example.md +49 -0
  4. package/.claude/skills/changelog-social/examples/twitter-example.md +55 -0
  5. package/.claude/skills/changelog-social.skill +0 -0
  6. package/.claude/skills/draft-changelog/SKILL.md +2 -2
  7. package/.claude/skills/draft-changelog/prompts/instructions.md +25 -1
  8. package/.claude/skills/gh-triage/SKILL.md +1 -1
  9. package/.claude/skills/release-module/SKILL.md +1 -1
  10. package/.claude/skills/release-module/prompts/instructions.md +0 -4
  11. package/.github/workflows/quality.yaml +3 -0
  12. package/.vscode/settings.json +1 -2
  13. package/CHANGELOG.md +65 -1
  14. package/docs/bmgd/game-types.md +1 -0
  15. package/docs/bmgd/index.md +1 -1
  16. package/docs/bmgd/quick-flow-workflows.md +1 -0
  17. package/docs/reference/agents.md +22 -0
  18. package/docs/reference/commands.md +34 -0
  19. package/docs/reference/testing.md +21 -0
  20. package/docs/reference/workflow-map.md +1 -1
  21. package/package.json +5 -2
  22. package/src/bmm/agents/analyst.agent.yaml +11 -5
  23. package/src/bmm/agents/dev.agent.yaml +0 -1
  24. package/src/bmm/agents/pm.agent.yaml +3 -6
  25. package/src/bmm/agents/{quinn.agent.yaml → qa.agent.yaml} +3 -3
  26. package/src/bmm/module-help.csv +13 -20
  27. package/src/bmm/workflows/1-analysis/create-product-brief/workflow.md +0 -1
  28. package/src/bmm/workflows/1-analysis/research/workflow-domain-research.md +54 -0
  29. package/src/bmm/workflows/1-analysis/research/workflow-market-research.md +54 -0
  30. package/src/bmm/workflows/1-analysis/research/workflow-technical-research.md +54 -0
  31. package/src/bmm/workflows/2-plan-workflows/create-prd/data/domain-complexity.csv +2 -0
  32. package/src/bmm/workflows/2-plan-workflows/create-prd/workflow-create-prd.md +63 -0
  33. package/src/bmm/workflows/2-plan-workflows/create-prd/workflow-edit-prd.md +65 -0
  34. package/src/bmm/workflows/2-plan-workflows/create-prd/workflow-validate-prd.md +65 -0
  35. package/src/bmm/workflows/2-plan-workflows/create-ux-design/workflow.md +0 -1
  36. package/src/bmm/workflows/3-solutioning/check-implementation-readiness/workflow.md +0 -1
  37. package/src/bmm/workflows/3-solutioning/create-architecture/data/domain-complexity.csv +2 -0
  38. package/src/bmm/workflows/3-solutioning/create-architecture/workflow.md +0 -1
  39. package/src/bmm/workflows/3-solutioning/create-epics-and-stories/workflow.md +0 -1
  40. package/src/bmm/workflows/4-implementation/code-review/workflow.yaml +0 -3
  41. package/src/bmm/workflows/4-implementation/correct-course/workflow.yaml +0 -4
  42. package/src/bmm/workflows/4-implementation/create-story/workflow.yaml +0 -4
  43. package/src/bmm/workflows/4-implementation/dev-story/workflow.yaml +0 -4
  44. package/src/bmm/workflows/4-implementation/retrospective/workflow.yaml +0 -3
  45. package/src/bmm/workflows/4-implementation/sprint-planning/workflow.yaml +0 -4
  46. package/src/bmm/workflows/4-implementation/sprint-status/workflow.yaml +0 -6
  47. package/src/bmm/workflows/bmad-quick-flow/quick-spec/workflow.md +0 -1
  48. package/src/bmm/workflows/document-project/workflow.yaml +0 -8
  49. package/src/bmm/workflows/qa/automate/workflow.yaml +0 -2
  50. package/src/core/tasks/editorial-review-prose.xml +14 -12
  51. package/src/core/tasks/editorial-review-structure.xml +28 -28
  52. package/src/core/tasks/help.md +36 -16
  53. package/src/core/tasks/index-docs.xml +1 -1
  54. package/src/core/tasks/review-adversarial-general.xml +2 -2
  55. package/src/core/tasks/shard-doc.xml +1 -2
  56. package/src/core/tasks/workflow.xml +1 -1
  57. package/src/core/workflows/advanced-elicitation/workflow.xml +1 -1
  58. package/test/test-installation-components.js +1 -1
  59. package/tools/build-docs.js +21 -1
  60. package/tools/cli/bmad-cli.js +42 -3
  61. package/tools/cli/installers/lib/core/config-collector.js +5 -1
  62. package/tools/cli/installers/lib/core/dependency-resolver.js +1 -1
  63. package/tools/cli/installers/lib/core/installer.js +1 -6
  64. package/tools/cli/installers/lib/core/manifest-generator.js +149 -125
  65. package/tools/cli/installers/lib/custom/handler.js +1 -1
  66. package/tools/cli/installers/lib/ide/_base-ide.js +25 -15
  67. package/tools/cli/installers/lib/ide/_config-driven.js +79 -9
  68. package/tools/cli/installers/lib/ide/codex.js +7 -1
  69. package/tools/cli/installers/lib/ide/manager.js +8 -2
  70. package/tools/cli/installers/lib/ide/platform-codes.yaml +0 -3
  71. package/tools/cli/installers/lib/ide/shared/agent-command-generator.js +2 -2
  72. package/tools/cli/installers/lib/ide/shared/bmad-artifacts.js +14 -3
  73. package/tools/cli/installers/lib/ide/shared/path-utils.js +9 -2
  74. package/tools/cli/installers/lib/ide/shared/task-tool-command-generator.js +126 -29
  75. package/tools/cli/installers/lib/ide/shared/workflow-command-generator.js +2 -2
  76. package/tools/cli/installers/lib/ide/templates/combined/default-task.md +10 -0
  77. package/tools/cli/installers/lib/ide/templates/combined/default-tool.md +10 -0
  78. package/tools/cli/installers/lib/ide/templates/combined/gemini-task.toml +11 -0
  79. package/tools/cli/installers/lib/ide/templates/combined/gemini-tool.toml +11 -0
  80. package/tools/cli/installers/lib/modules/manager.js +3 -6
  81. package/tools/cli/lib/agent/installer.js +1 -1
  82. package/tools/cli/lib/prompts.js +192 -6
  83. package/tools/cli/lib/ui.js +124 -72
  84. package/tools/schema/agent.js +0 -1
  85. package/tools/validate-file-refs.js +480 -0
  86. package/website/astro.config.mjs +1 -35
  87. package/src/bmm/workflows/1-analysis/research/workflow.md +0 -173
  88. package/src/bmm/workflows/2-plan-workflows/create-prd/validation-report-prd-workflow.md +0 -433
  89. package/src/bmm/workflows/2-plan-workflows/create-prd/workflow.md +0 -150
  90. package/src/bmm/workflows/excalidraw-diagrams/_shared/excalidraw-library.json +0 -90
  91. package/src/bmm/workflows/excalidraw-diagrams/_shared/excalidraw-templates.yaml +0 -127
  92. package/src/bmm/workflows/excalidraw-diagrams/create-dataflow/checklist.md +0 -39
  93. package/src/bmm/workflows/excalidraw-diagrams/create-dataflow/instructions.md +0 -130
  94. package/src/bmm/workflows/excalidraw-diagrams/create-dataflow/workflow.yaml +0 -27
  95. package/src/bmm/workflows/excalidraw-diagrams/create-diagram/checklist.md +0 -43
  96. package/src/bmm/workflows/excalidraw-diagrams/create-diagram/instructions.md +0 -141
  97. package/src/bmm/workflows/excalidraw-diagrams/create-diagram/workflow.yaml +0 -27
  98. package/src/bmm/workflows/excalidraw-diagrams/create-flowchart/checklist.md +0 -49
  99. package/src/bmm/workflows/excalidraw-diagrams/create-flowchart/instructions.md +0 -241
  100. package/src/bmm/workflows/excalidraw-diagrams/create-flowchart/workflow.yaml +0 -27
  101. package/src/bmm/workflows/excalidraw-diagrams/create-wireframe/checklist.md +0 -38
  102. package/src/bmm/workflows/excalidraw-diagrams/create-wireframe/instructions.md +0 -133
  103. package/src/bmm/workflows/excalidraw-diagrams/create-wireframe/workflow.yaml +0 -27
  104. package/src/core/resources/excalidraw/README.md +0 -160
  105. package/src/core/resources/excalidraw/excalidraw-helpers.md +0 -127
  106. package/src/core/resources/excalidraw/library-loader.md +0 -50
  107. package/src/core/resources/excalidraw/validate-json-instructions.md +0 -79
  108. package/tools/flattener/aggregate.js +0 -76
  109. package/tools/flattener/binary.js +0 -80
  110. package/tools/flattener/discovery.js +0 -71
  111. package/tools/flattener/files.js +0 -35
  112. package/tools/flattener/ignoreRules.js +0 -172
  113. package/tools/flattener/main.js +0 -483
  114. package/tools/flattener/projectRoot.js +0 -201
  115. package/tools/flattener/prompts.js +0 -44
  116. package/tools/flattener/stats.helpers.js +0 -368
  117. package/tools/flattener/stats.js +0 -75
  118. package/tools/flattener/test-matrix.js +0 -409
  119. package/tools/flattener/xml.js +0 -82
@@ -8,6 +8,8 @@
8
8
  */
9
9
 
10
10
  let _clack = null;
11
+ let _clackCore = null;
12
+ let _picocolors = null;
11
13
 
12
14
  /**
13
15
  * Lazy-load @clack/prompts (ESM module)
@@ -20,6 +22,28 @@ async function getClack() {
20
22
  return _clack;
21
23
  }
22
24
 
25
+ /**
26
+ * Lazy-load @clack/core (ESM module)
27
+ * @returns {Promise<Object>} The clack core module
28
+ */
29
+ async function getClackCore() {
30
+ if (!_clackCore) {
31
+ _clackCore = await import('@clack/core');
32
+ }
33
+ return _clackCore;
34
+ }
35
+
36
+ /**
37
+ * Lazy-load picocolors
38
+ * @returns {Promise<Object>} The picocolors module
39
+ */
40
+ async function getPicocolors() {
41
+ if (!_picocolors) {
42
+ _picocolors = (await import('picocolors')).default;
43
+ }
44
+ return _picocolors;
45
+ }
46
+
23
47
  /**
24
48
  * Handle user cancellation gracefully
25
49
  * @param {any} value - The value to check
@@ -191,6 +215,118 @@ async function groupMultiselect(options) {
191
215
  return result;
192
216
  }
193
217
 
218
+ /**
219
+ * Default filter function for autocomplete - case-insensitive label matching
220
+ * @param {string} search - Search string
221
+ * @param {Object} option - Option object with label
222
+ * @returns {boolean} Whether the option matches
223
+ */
224
+ function defaultAutocompleteFilter(search, option) {
225
+ const label = option.label ?? String(option.value ?? '');
226
+ return label.toLowerCase().includes(search.toLowerCase());
227
+ }
228
+
229
+ /**
230
+ * Autocomplete multi-select prompt with type-ahead filtering
231
+ * Custom implementation that always shows "Space/Tab:" in the hint
232
+ * @param {Object} options - Prompt options
233
+ * @param {string} options.message - The question to ask
234
+ * @param {Array} options.options - Array of choices [{label, value, hint?}]
235
+ * @param {string} [options.placeholder] - Placeholder text for search input
236
+ * @param {Array} [options.initialValues] - Array of initially selected values
237
+ * @param {boolean} [options.required=false] - Whether at least one must be selected
238
+ * @param {number} [options.maxItems=5] - Maximum visible items in scrollable list
239
+ * @param {Function} [options.filter] - Custom filter function (search, option) => boolean
240
+ * @returns {Promise<Array>} Array of selected values
241
+ */
242
+ async function autocompleteMultiselect(options) {
243
+ const core = await getClackCore();
244
+ const clack = await getClack();
245
+ const color = await getPicocolors();
246
+
247
+ const filterFn = options.filter ?? defaultAutocompleteFilter;
248
+
249
+ const prompt = new core.AutocompletePrompt({
250
+ options: options.options,
251
+ multiple: true,
252
+ filter: filterFn,
253
+ validate: () => {
254
+ if (options.required && prompt.selectedValues.length === 0) {
255
+ return 'Please select at least one item';
256
+ }
257
+ },
258
+ initialValue: options.initialValues,
259
+ render() {
260
+ const barColor = this.state === 'error' ? color.yellow : color.cyan;
261
+ const bar = barColor(clack.S_BAR);
262
+ const barEnd = barColor(clack.S_BAR_END);
263
+
264
+ const title = `${color.gray(clack.S_BAR)}\n${clack.symbol(this.state)} ${options.message}\n`;
265
+
266
+ const userInput = this.userInput;
267
+ const placeholder = options.placeholder || 'Type to search...';
268
+ const hasPlaceholder = userInput === '' && placeholder !== undefined;
269
+
270
+ // Show placeholder or user input with cursor
271
+ const searchDisplay =
272
+ this.isNavigating || hasPlaceholder ? color.dim(hasPlaceholder ? placeholder : userInput) : this.userInputWithCursor;
273
+
274
+ const allOptions = this.options;
275
+ const matchCount =
276
+ this.filteredOptions.length === allOptions.length
277
+ ? ''
278
+ : color.dim(` (${this.filteredOptions.length} match${this.filteredOptions.length === 1 ? '' : 'es'})`);
279
+
280
+ // Render option with checkbox
281
+ const renderOption = (opt, isHighlighted) => {
282
+ const isSelected = this.selectedValues.includes(opt.value);
283
+ const label = opt.label ?? String(opt.value ?? '');
284
+ const hintText = opt.hint && opt.value === this.focusedValue ? color.dim(` (${opt.hint})`) : '';
285
+ const checkbox = isSelected ? color.green(clack.S_CHECKBOX_SELECTED) : color.dim(clack.S_CHECKBOX_INACTIVE);
286
+ return isHighlighted ? `${checkbox} ${label}${hintText}` : `${checkbox} ${color.dim(label)}`;
287
+ };
288
+
289
+ switch (this.state) {
290
+ case 'submit': {
291
+ return `${title}${color.gray(clack.S_BAR)} ${color.dim(`${this.selectedValues.length} items selected`)}`;
292
+ }
293
+
294
+ case 'cancel': {
295
+ return `${title}${color.gray(clack.S_BAR)} ${color.strikethrough(color.dim(userInput))}`;
296
+ }
297
+
298
+ default: {
299
+ // Always show "SPACE:" regardless of isNavigating state
300
+ const hints = [`${color.dim('↑/↓')} to navigate`, `${color.dim('TAB/SPACE:')} select`, `${color.dim('ENTER:')} confirm`];
301
+
302
+ const noMatchesLine = this.filteredOptions.length === 0 && userInput ? [`${bar} ${color.yellow('No matches found')}`] : [];
303
+
304
+ const errorLine = this.state === 'error' ? [`${bar} ${color.yellow(this.error)}`] : [];
305
+
306
+ const headerLines = [...`${title}${bar}`.split('\n'), `${bar} ${searchDisplay}${matchCount}`, ...noMatchesLine, ...errorLine];
307
+
308
+ const footerLines = [`${bar} ${color.dim(hints.join(' • '))}`, `${barEnd}`];
309
+
310
+ const optionLines = clack.limitOptions({
311
+ cursor: this.cursor,
312
+ options: this.filteredOptions,
313
+ style: renderOption,
314
+ maxItems: options.maxItems || 5,
315
+ output: options.output,
316
+ rowPadding: headerLines.length + footerLines.length,
317
+ });
318
+
319
+ return [...headerLines, ...optionLines.map((line) => `${bar} ${line}`), ...footerLines].join('\n');
320
+ }
321
+ }
322
+ },
323
+ });
324
+
325
+ const result = await prompt.prompt();
326
+ await handleCancel(result);
327
+ return result;
328
+ }
329
+
194
330
  /**
195
331
  * Confirm prompt (replaces Inquirer 'confirm' type)
196
332
  * @param {Object} options - Prompt options
@@ -211,7 +347,12 @@ async function confirm(options) {
211
347
  }
212
348
 
213
349
  /**
214
- * Text input prompt (replaces Inquirer 'input' type)
350
+ * Text input prompt with Tab-to-fill-placeholder support (replaces Inquirer 'input' type)
351
+ *
352
+ * This custom implementation restores the Tab-to-fill-placeholder behavior that was
353
+ * intentionally removed in @clack/prompts v1.0.0 (placeholder became purely visual).
354
+ * Uses @clack/core's TextPrompt primitive with custom key handling.
355
+ *
215
356
  * @param {Object} options - Prompt options
216
357
  * @param {string} options.message - The question to ask
217
358
  * @param {string} [options.default] - Default value
@@ -220,20 +361,64 @@ async function confirm(options) {
220
361
  * @returns {Promise<string>} User's input
221
362
  */
222
363
  async function text(options) {
223
- const clack = await getClack();
364
+ const core = await getClackCore();
365
+ const color = await getPicocolors();
224
366
 
225
367
  // Use default as placeholder if placeholder not explicitly provided
226
368
  // This shows the default value as grayed-out hint text
227
369
  const placeholder = options.placeholder === undefined ? options.default : options.placeholder;
370
+ const defaultValue = options.default;
228
371
 
229
- const result = await clack.text({
230
- message: options.message,
231
- defaultValue: options.default,
232
- placeholder: typeof placeholder === 'string' ? placeholder : undefined,
372
+ const prompt = new core.TextPrompt({
373
+ defaultValue,
233
374
  validate: options.validate,
375
+ render() {
376
+ const title = `${color.gray('◆')} ${options.message}`;
377
+
378
+ // Show placeholder as dim text when input is empty
379
+ let valueDisplay;
380
+ if (this.state === 'error') {
381
+ valueDisplay = color.yellow(this.userInputWithCursor);
382
+ } else if (this.userInput) {
383
+ valueDisplay = this.userInputWithCursor;
384
+ } else if (placeholder) {
385
+ // Show placeholder with cursor indicator when empty
386
+ valueDisplay = `${color.inverse(color.hidden('_'))}${color.dim(placeholder)}`;
387
+ } else {
388
+ valueDisplay = color.inverse(color.hidden('_'));
389
+ }
390
+
391
+ const bar = color.gray('│');
392
+
393
+ // Handle different states
394
+ if (this.state === 'submit') {
395
+ return `${color.gray('◇')} ${options.message}\n${bar} ${color.dim(this.value || defaultValue || '')}`;
396
+ }
397
+
398
+ if (this.state === 'cancel') {
399
+ return `${color.gray('◇')} ${options.message}\n${bar} ${color.strikethrough(color.dim(this.userInput || ''))}`;
400
+ }
401
+
402
+ if (this.state === 'error') {
403
+ return `${color.yellow('▲')} ${options.message}\n${bar} ${valueDisplay}\n${color.yellow('│')} ${color.yellow(this.error)}`;
404
+ }
405
+
406
+ return `${title}\n${bar} ${valueDisplay}\n${bar}`;
407
+ },
234
408
  });
235
409
 
410
+ // Add Tab key handler to fill placeholder into input
411
+ prompt.on('key', (char) => {
412
+ if (char === '\t' && placeholder && !prompt.userInput) {
413
+ // Use _setUserInput with write=true to populate the readline and update internal state
414
+ prompt._setUserInput(placeholder, true);
415
+ }
416
+ });
417
+
418
+ const result = await prompt.prompt();
236
419
  await handleCancel(result);
420
+
421
+ // TextPrompt's finalize handler already applies defaultValue for empty input
237
422
  return result;
238
423
  }
239
424
 
@@ -423,6 +608,7 @@ module.exports = {
423
608
  select,
424
609
  multiselect,
425
610
  groupMultiselect,
611
+ autocompleteMultiselect,
426
612
  confirm,
427
613
  text,
428
614
  password,
@@ -344,6 +344,9 @@ class UI {
344
344
 
345
345
  /**
346
346
  * Prompt for tool/IDE selection (called after module configuration)
347
+ * Uses a split prompt approach:
348
+ * 1. Recommended tools - standard multiselect for 3 preferred tools
349
+ * 2. Additional tools - autocompleteMultiselect with search capability
347
350
  * @param {string} projectDir - Project directory to check for existing IDEs
348
351
  * @returns {Object} Tool configuration
349
352
  */
@@ -366,95 +369,123 @@ class UI {
366
369
  const preferredIdes = ideManager.getPreferredIdes();
367
370
  const otherIdes = ideManager.getOtherIdes();
368
371
 
369
- // Build grouped options object for groupMultiselect
370
- const groupedOptions = {};
371
- const processedIdes = new Set();
372
- const initialValues = [];
372
+ // Determine which configured IDEs are in "preferred" vs "other" categories
373
+ const configuredPreferred = configuredIdes.filter((id) => preferredIdes.some((ide) => ide.value === id));
374
+ const configuredOther = configuredIdes.filter((id) => otherIdes.some((ide) => ide.value === id));
375
+
376
+ // Warn about previously configured tools that are no longer available
377
+ const allKnownValues = new Set([...preferredIdes, ...otherIdes].map((ide) => ide.value));
378
+ const unknownTools = configuredIdes.filter((id) => id && typeof id === 'string' && !allKnownValues.has(id));
379
+ if (unknownTools.length > 0) {
380
+ console.log(chalk.yellow(`⚠️ Previously configured tools are no longer available: ${unknownTools.join(', ')}`));
381
+ }
373
382
 
374
- // First, add previously configured IDEs, marked with ✅
383
+ // ─────────────────────────────────────────────────────────────────────────────
384
+ // UPGRADE PATH: If tools already configured, show all tools with configured at top
385
+ // ─────────────────────────────────────────────────────────────────────────────
375
386
  if (configuredIdes.length > 0) {
376
- const configuredGroup = [];
377
- for (const ideValue of configuredIdes) {
378
- // Skip empty or invalid IDE values
379
- if (!ideValue || typeof ideValue !== 'string') {
380
- continue;
381
- }
387
+ const allTools = [...preferredIdes, ...otherIdes];
388
+
389
+ // Sort: configured tools first, then preferred, then others
390
+ const sortedTools = [
391
+ ...allTools.filter((ide) => configuredIdes.includes(ide.value)),
392
+ ...allTools.filter((ide) => !configuredIdes.includes(ide.value)),
393
+ ];
394
+
395
+ const upgradeOptions = sortedTools.map((ide) => {
396
+ const isConfigured = configuredIdes.includes(ide.value);
397
+ const isPreferred = preferredIdes.some((p) => p.value === ide.value);
398
+ let label = ide.name;
399
+ if (isPreferred) label += ' ⭐';
400
+ if (isConfigured) label += ' ✅';
401
+ return { label, value: ide.value };
402
+ });
382
403
 
383
- // Find the IDE in either preferred or other lists
384
- const preferredIde = preferredIdes.find((ide) => ide.value === ideValue);
385
- const otherIde = otherIdes.find((ide) => ide.value === ideValue);
386
- const ide = preferredIde || otherIde;
404
+ // Sort initialValues to match display order
405
+ const sortedInitialValues = sortedTools.filter((ide) => configuredIdes.includes(ide.value)).map((ide) => ide.value);
387
406
 
388
- if (ide) {
389
- configuredGroup.push({
390
- label: `${ide.name} ✅`,
391
- value: ide.value,
392
- });
393
- processedIdes.add(ide.value);
394
- initialValues.push(ide.value); // Pre-select configured IDEs
395
- } else {
396
- // Warn about unrecognized IDE (but don't fail)
397
- console.log(chalk.yellow(`⚠️ Previously configured IDE '${ideValue}' is no longer available`));
407
+ const upgradeSelected = await prompts.autocompleteMultiselect({
408
+ message: 'Integrate with',
409
+ options: upgradeOptions,
410
+ initialValues: sortedInitialValues,
411
+ required: false,
412
+ maxItems: 8,
413
+ });
414
+
415
+ const selectedIdes = upgradeSelected || [];
416
+
417
+ if (selectedIdes.length === 0) {
418
+ console.log('');
419
+ const confirmNoTools = await prompts.confirm({
420
+ message: 'No tools selected. Continue without installing any tools?',
421
+ default: false,
422
+ });
423
+
424
+ if (!confirmNoTools) {
425
+ return this.promptToolSelection(projectDir);
398
426
  }
427
+
428
+ return { ides: [], skipIde: true };
399
429
  }
400
- if (configuredGroup.length > 0) {
401
- groupedOptions['Previously Configured'] = configuredGroup;
402
- }
403
- }
404
430
 
405
- // Add preferred tools (excluding already processed)
406
- const remainingPreferred = preferredIdes.filter((ide) => !processedIdes.has(ide.value));
407
- if (remainingPreferred.length > 0) {
408
- groupedOptions['Recommended Tools'] = remainingPreferred.map((ide) => {
409
- processedIdes.add(ide.value);
410
- return {
411
- label: `${ide.name} ⭐`,
412
- value: ide.value,
413
- };
414
- });
415
- }
431
+ // Display selected tools
432
+ this.displaySelectedTools(selectedIdes, preferredIdes, allTools);
416
433
 
417
- // Add other tools (excluding already processed)
418
- const remainingOther = otherIdes.filter((ide) => !processedIdes.has(ide.value));
419
- if (remainingOther.length > 0) {
420
- groupedOptions['Additional Tools'] = remainingOther.map((ide) => ({
421
- label: ide.name,
422
- value: ide.value,
423
- }));
434
+ return { ides: selectedIdes, skipIde: false };
424
435
  }
425
436
 
426
- // Add standalone "None" option at the end
427
- groupedOptions[' '] = [
428
- {
429
- label: '⚠ None - I am not installing any tools',
430
- value: '__NONE__',
431
- },
432
- ];
437
+ // ─────────────────────────────────────────────────────────────────────────────
438
+ // NEW INSTALL: Show all tools with search
439
+ // ─────────────────────────────────────────────────────────────────────────────
440
+ const allTools = [...preferredIdes, ...otherIdes];
433
441
 
434
- let selectedIdes = [];
442
+ const allToolOptions = allTools.map((ide) => {
443
+ const isPreferred = preferredIdes.some((p) => p.value === ide.value);
444
+ let label = ide.name;
445
+ if (isPreferred) label += ' ⭐';
446
+ return {
447
+ label,
448
+ value: ide.value,
449
+ };
450
+ });
435
451
 
436
- selectedIdes = await prompts.groupMultiselect({
437
- message: `Select tools to configure ${chalk.dim('(↑/↓ navigates, SPACE toggles, ENTER to confirm)')}:`,
438
- options: groupedOptions,
439
- initialValues: initialValues.length > 0 ? initialValues : undefined,
440
- required: true,
441
- selectableGroups: false,
452
+ const selectedIdes = await prompts.autocompleteMultiselect({
453
+ message: 'Select tools:',
454
+ options: allToolOptions,
455
+ initialValues: configuredIdes.length > 0 ? configuredIdes : undefined,
456
+ required: false,
457
+ maxItems: 8,
442
458
  });
443
459
 
444
- // If user selected both "__NONE__" and other tools, honor the "None" choice
445
- if (selectedIdes && selectedIdes.includes('__NONE__') && selectedIdes.length > 1) {
446
- console.log();
447
- console.log(chalk.yellow('⚠️ "None - I am not installing any tools" was selected, so no tools will be configured.'));
448
- console.log();
449
- selectedIdes = [];
450
- } else if (selectedIdes && selectedIdes.includes('__NONE__')) {
451
- // Only "__NONE__" was selected
452
- selectedIdes = [];
460
+ const allSelectedIdes = selectedIdes || [];
461
+
462
+ // ─────────────────────────────────────────────────────────────────────────────
463
+ // STEP 3: Confirm if no tools selected
464
+ // ─────────────────────────────────────────────────────────────────────────────
465
+ if (allSelectedIdes.length === 0) {
466
+ console.log('');
467
+ const confirmNoTools = await prompts.confirm({
468
+ message: 'No tools selected. Continue without installing any tools?',
469
+ default: false,
470
+ });
471
+
472
+ if (!confirmNoTools) {
473
+ // User wants to select tools - recurse
474
+ return this.promptToolSelection(projectDir);
475
+ }
476
+
477
+ return {
478
+ ides: [],
479
+ skipIde: true,
480
+ };
453
481
  }
454
482
 
483
+ // Display selected tools
484
+ this.displaySelectedTools(allSelectedIdes, preferredIdes, allTools);
485
+
455
486
  return {
456
- ides: selectedIdes || [],
457
- skipIde: !selectedIdes || selectedIdes.length === 0,
487
+ ides: allSelectedIdes,
488
+ skipIde: allSelectedIdes.length === 0,
458
489
  };
459
490
  }
460
491
 
@@ -1655,6 +1686,27 @@ class UI {
1655
1686
 
1656
1687
  console.log('');
1657
1688
  }
1689
+
1690
+ /**
1691
+ * Display list of selected tools after IDE selection
1692
+ * @param {Array} selectedIdes - Array of selected IDE values
1693
+ * @param {Array} preferredIdes - Array of preferred IDE objects
1694
+ * @param {Array} allTools - Array of all tool objects
1695
+ */
1696
+ displaySelectedTools(selectedIdes, preferredIdes, allTools) {
1697
+ if (selectedIdes.length === 0) return;
1698
+
1699
+ const preferredValues = new Set(preferredIdes.map((ide) => ide.value));
1700
+
1701
+ console.log('');
1702
+ console.log(chalk.dim(' Selected tools:'));
1703
+ for (const ideValue of selectedIdes) {
1704
+ const tool = allTools.find((t) => t.value === ideValue);
1705
+ const name = tool?.name || ideValue;
1706
+ const marker = preferredValues.has(ideValue) ? ' ⭐' : '';
1707
+ console.log(chalk.dim(` • ${name}${marker}`));
1708
+ }
1709
+ }
1658
1710
  }
1659
1711
 
1660
1712
  module.exports = { UI };
@@ -210,7 +210,6 @@ function buildAgentSchema(expectedModule) {
210
210
  critical_actions: z.array(createNonEmptyString('agent.critical_actions[]')).optional(),
211
211
  menu: z.array(buildMenuItemSchema()).min(1, { message: 'agent.menu must include at least one entry' }),
212
212
  prompts: z.array(buildPromptSchema()).optional(),
213
- webskip: z.boolean().optional(),
214
213
  discussion: z.boolean().optional(),
215
214
  conversational_knowledge: z.array(z.object({}).passthrough()).min(1).optional(),
216
215
  })