bmad-method 6.6.1-next.8 → 6.6.1-next.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/package.json +4 -4
- package/tools/installer/prompts.js +149 -0
- package/tools/installer/ui.js +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/bmad-method)
|
|
4
4
|
[](LICENSE)
|
|
5
|
-
[](https://nodejs.org)
|
|
6
6
|
[](https://www.python.org)
|
|
7
7
|
[](https://docs.astral.sh/uv/)
|
|
8
8
|
[](https://discord.gg/gk8jAdXWmj)
|
|
@@ -36,7 +36,7 @@ Traditional AI tools do the thinking for you, producing average results. BMad ag
|
|
|
36
36
|
|
|
37
37
|
## Quick Start
|
|
38
38
|
|
|
39
|
-
**Prerequisites**: [Node.js](https://nodejs.org) v20+ · [Python](https://www.python.org) 3.10+ · [uv](https://docs.astral.sh/uv/)
|
|
39
|
+
**Prerequisites**: [Node.js](https://nodejs.org) v20.12+ · [Python](https://www.python.org) 3.10+ · [uv](https://docs.astral.sh/uv/)
|
|
40
40
|
|
|
41
41
|
```bash
|
|
42
42
|
npx bmad-method install
|
|
@@ -82,11 +82,11 @@ BMad Method extends with official modules for specialized domains. Available dur
|
|
|
82
82
|
[BMad Method Docs Site](https://docs.bmad-method.org) — Tutorials, guides, concepts, and reference
|
|
83
83
|
|
|
84
84
|
**Quick links:**
|
|
85
|
+
|
|
85
86
|
- [Getting Started Tutorial](https://docs.bmad-method.org/tutorials/getting-started/)
|
|
86
87
|
- [Upgrading from Previous Versions](https://docs.bmad-method.org/how-to/upgrade-to-v6/)
|
|
87
88
|
- [Test Architect Documentation](https://bmad-code-org.github.io/bmad-method-test-architecture-enterprise/)
|
|
88
89
|
|
|
89
|
-
|
|
90
90
|
## Community
|
|
91
91
|
|
|
92
92
|
- [Discord](https://discord.gg/gk8jAdXWmj) — Get help, share ideas, collaborate
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package.json",
|
|
3
3
|
"name": "bmad-method",
|
|
4
|
-
"version": "6.6.1-next.
|
|
4
|
+
"version": "6.6.1-next.9",
|
|
5
5
|
"description": "Breakthrough Method of Agile AI-driven Development",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"agile",
|
|
@@ -66,8 +66,8 @@
|
|
|
66
66
|
]
|
|
67
67
|
},
|
|
68
68
|
"dependencies": {
|
|
69
|
-
"@clack/core": "^1.
|
|
70
|
-
"@clack/prompts": "^1.
|
|
69
|
+
"@clack/core": "^1.3.1",
|
|
70
|
+
"@clack/prompts": "^1.4.0",
|
|
71
71
|
"@kayvan/markdown-tree-parser": "^1.6.1",
|
|
72
72
|
"chalk": "^4.1.2",
|
|
73
73
|
"commander": "^14.0.0",
|
|
@@ -103,7 +103,7 @@
|
|
|
103
103
|
"yaml-lint": "^1.7.0"
|
|
104
104
|
},
|
|
105
105
|
"engines": {
|
|
106
|
-
"node": ">=20.
|
|
106
|
+
"node": ">=20.12.0"
|
|
107
107
|
},
|
|
108
108
|
"publishConfig": {
|
|
109
109
|
"access": "public"
|
|
@@ -10,6 +10,9 @@
|
|
|
10
10
|
let _clack = null;
|
|
11
11
|
let _clackCore = null;
|
|
12
12
|
let _picocolors = null;
|
|
13
|
+
const fs = require('node:fs');
|
|
14
|
+
const os = require('node:os');
|
|
15
|
+
const path = require('node:path');
|
|
13
16
|
|
|
14
17
|
/**
|
|
15
18
|
* Lazy-load @clack/prompts (ESM module)
|
|
@@ -575,6 +578,151 @@ async function autocomplete(options) {
|
|
|
575
578
|
return result;
|
|
576
579
|
}
|
|
577
580
|
|
|
581
|
+
function hasPathSeparator(value) {
|
|
582
|
+
return value.endsWith('/') || value.endsWith('\\');
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
function expandHome(input) {
|
|
586
|
+
if (!input) return input;
|
|
587
|
+
if (input === '~') return os.homedir();
|
|
588
|
+
if (input.startsWith('~/') || input.startsWith('~\\')) {
|
|
589
|
+
return path.join(os.homedir(), input.slice(2));
|
|
590
|
+
}
|
|
591
|
+
return input;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
function toDirectoryOption(value, label = value, synthetic = false) {
|
|
595
|
+
return { value, label, synthetic };
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
function isExistingDirectory(value) {
|
|
599
|
+
try {
|
|
600
|
+
return fs.existsSync(value) && fs.statSync(value).isDirectory();
|
|
601
|
+
} catch {
|
|
602
|
+
return false;
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
function listDirectoryOptions(input, options) {
|
|
607
|
+
const cwd = options.cwd || process.cwd();
|
|
608
|
+
const rawInput = input.trim();
|
|
609
|
+
const expandedInput = expandHome(rawInput);
|
|
610
|
+
const trailingSep = hasPathSeparator(rawInput) || hasPathSeparator(expandedInput);
|
|
611
|
+
const resolvedInput = expandedInput ? path.resolve(cwd, expandedInput) : cwd;
|
|
612
|
+
const browseDir = expandedInput && !trailingSep && !isExistingDirectory(resolvedInput) ? path.dirname(resolvedInput) : resolvedInput;
|
|
613
|
+
const prefix = expandedInput && browseDir !== resolvedInput ? path.basename(resolvedInput).toLowerCase() : '';
|
|
614
|
+
const results = [];
|
|
615
|
+
|
|
616
|
+
if (!trailingSep && isExistingDirectory(resolvedInput)) {
|
|
617
|
+
results.push(toDirectoryOption(resolvedInput, `. (use this directory)`));
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
if (isExistingDirectory(browseDir)) {
|
|
621
|
+
try {
|
|
622
|
+
for (const entry of fs.readdirSync(browseDir, { withFileTypes: true })) {
|
|
623
|
+
if (!entry.isDirectory()) continue;
|
|
624
|
+
if (prefix && !entry.name.toLowerCase().startsWith(prefix)) continue;
|
|
625
|
+
const fullPath = path.join(browseDir, entry.name);
|
|
626
|
+
if (!results.some((option) => option.value === fullPath)) {
|
|
627
|
+
results.push(toDirectoryOption(fullPath));
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
} catch {
|
|
631
|
+
// Skip unreadable directories; validation still reports path issues.
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
const validation = options.validate?.(rawInput);
|
|
636
|
+
const hasMatchingOption = results.some((option) => option.value === resolvedInput);
|
|
637
|
+
if (expandedInput && !validation && !hasMatchingOption) {
|
|
638
|
+
results.unshift(toDirectoryOption(resolvedInput, `Create/use: ${resolvedInput}`, true));
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
return results;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
/**
|
|
645
|
+
* Directory prompt with autocomplete candidates and create-directory support.
|
|
646
|
+
* Uses @clack/core directly so typed paths that do not exist yet can still be
|
|
647
|
+
* submitted when validation allows creating them.
|
|
648
|
+
* @param {Object} options - Prompt options
|
|
649
|
+
* @param {string} options.message - Prompt message
|
|
650
|
+
* @param {string} [options.default] - Default directory
|
|
651
|
+
* @param {string} [options.placeholder] - Placeholder text
|
|
652
|
+
* @param {Function} [options.validate] - Sync validation function
|
|
653
|
+
* @returns {Promise<string>} Selected or typed directory path
|
|
654
|
+
*/
|
|
655
|
+
async function directory(options) {
|
|
656
|
+
const core = await getClackCore();
|
|
657
|
+
const color = await getPicocolors();
|
|
658
|
+
const tabCompletion = {
|
|
659
|
+
prefix: '',
|
|
660
|
+
index: -1,
|
|
661
|
+
options: [],
|
|
662
|
+
lastValue: '',
|
|
663
|
+
};
|
|
664
|
+
|
|
665
|
+
let prompt;
|
|
666
|
+
prompt = new core.AutocompletePrompt({
|
|
667
|
+
initialValue: options.default,
|
|
668
|
+
options: () => listDirectoryOptions(prompt?.userInput || '', options),
|
|
669
|
+
filter: () => true,
|
|
670
|
+
validate: (value) => options.validate?.(value ?? prompt.userInput),
|
|
671
|
+
render() {
|
|
672
|
+
const title = `${color.gray('◆')} ${options.message}`;
|
|
673
|
+
const bar = color.gray('│');
|
|
674
|
+
const barEnd = color.gray('└');
|
|
675
|
+
const userInput = this.userInput;
|
|
676
|
+
const placeholder = options.placeholder || options.default;
|
|
677
|
+
const inputDisplay = userInput ? this.userInputWithCursor : `${color.inverse(color.hidden('_'))}${color.dim(placeholder || '')}`;
|
|
678
|
+
const errorLine = this.state === 'error' ? [`${color.yellow('│')} ${color.yellow(this.error)}`] : [];
|
|
679
|
+
|
|
680
|
+
switch (this.state) {
|
|
681
|
+
case 'submit': {
|
|
682
|
+
return `${color.gray('◇')} ${options.message}\n${bar} ${color.dim(this.value || '')}`;
|
|
683
|
+
}
|
|
684
|
+
case 'cancel': {
|
|
685
|
+
return `${color.gray('◇')} ${options.message}\n${bar} ${color.strikethrough(color.dim(userInput || ''))}`;
|
|
686
|
+
}
|
|
687
|
+
default: {
|
|
688
|
+
return [title, `${bar} ${inputDisplay}`, ...errorLine, barEnd].join('\n');
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
},
|
|
692
|
+
});
|
|
693
|
+
|
|
694
|
+
const hasSetUserInput = typeof prompt._setUserInput === 'function';
|
|
695
|
+
const hasClearUserInput = typeof prompt._clearUserInput === 'function';
|
|
696
|
+
|
|
697
|
+
prompt.on('key', (_, key) => {
|
|
698
|
+
if (key?.name !== 'tab') return;
|
|
699
|
+
if (!hasSetUserInput) return; // @clack/core API surface changed — skip Tab silently.
|
|
700
|
+
const currentInput = prompt.userInput;
|
|
701
|
+
const isContinuingCycle = tabCompletion.lastValue && currentInput === tabCompletion.lastValue;
|
|
702
|
+
const completionOptions = isContinuingCycle ? tabCompletion.options : prompt.filteredOptions.filter((option) => !option.synthetic);
|
|
703
|
+
if (completionOptions.length === 0) return;
|
|
704
|
+
|
|
705
|
+
if (isContinuingCycle) {
|
|
706
|
+
tabCompletion.index = (tabCompletion.index + 1) % completionOptions.length;
|
|
707
|
+
} else {
|
|
708
|
+
tabCompletion.prefix = currentInput;
|
|
709
|
+
tabCompletion.options = completionOptions;
|
|
710
|
+
tabCompletion.index = 0;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
const focusedOption = completionOptions[tabCompletion.index];
|
|
714
|
+
if (!focusedOption) return;
|
|
715
|
+
const completedValue = focusedOption.value;
|
|
716
|
+
tabCompletion.lastValue = completedValue;
|
|
717
|
+
if (hasClearUserInput) prompt._clearUserInput();
|
|
718
|
+
prompt._setUserInput(completedValue, true);
|
|
719
|
+
});
|
|
720
|
+
|
|
721
|
+
const result = await prompt.prompt();
|
|
722
|
+
await handleCancel(result);
|
|
723
|
+
return result;
|
|
724
|
+
}
|
|
725
|
+
|
|
578
726
|
/**
|
|
579
727
|
* Get the color utility (picocolors instance from @clack/prompts)
|
|
580
728
|
* @returns {Promise<Object>} The color utility (picocolors)
|
|
@@ -694,6 +842,7 @@ module.exports = {
|
|
|
694
842
|
multiselect,
|
|
695
843
|
autocompleteMultiselect,
|
|
696
844
|
autocomplete,
|
|
845
|
+
directory,
|
|
697
846
|
confirm,
|
|
698
847
|
text,
|
|
699
848
|
password,
|
package/tools/installer/ui.js
CHANGED
|
@@ -1436,7 +1436,7 @@ class UI {
|
|
|
1436
1436
|
*/
|
|
1437
1437
|
async promptForDirectory() {
|
|
1438
1438
|
// Use sync validation because @clack/prompts doesn't support async validate
|
|
1439
|
-
const directory = await prompts.
|
|
1439
|
+
const directory = await prompts.directory({
|
|
1440
1440
|
message: 'Installation directory:',
|
|
1441
1441
|
default: process.cwd(),
|
|
1442
1442
|
placeholder: process.cwd(),
|