bmad-method 6.3.1-next.21 → 6.3.1-next.23
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/package.json +1 -1
- package/src/bmm-skills/4-implementation/bmad-checkpoint-preview/SKILL.md +46 -7
- package/src/bmm-skills/4-implementation/bmad-checkpoint-preview/customize.toml +41 -0
- package/src/bmm-skills/4-implementation/bmad-checkpoint-preview/step-05-wrapup.md +6 -0
- package/src/bmm-skills/4-implementation/bmad-code-review/SKILL.md +85 -1
- package/src/bmm-skills/4-implementation/bmad-code-review/customize.toml +41 -0
- package/src/bmm-skills/4-implementation/bmad-code-review/steps/step-04-present.md +6 -0
- package/src/bmm-skills/4-implementation/bmad-dev-story/SKILL.md +480 -1
- package/src/bmm-skills/4-implementation/bmad-dev-story/customize.toml +41 -0
- package/src/bmm-skills/4-implementation/bmad-quick-dev/SKILL.md +106 -1
- package/src/bmm-skills/4-implementation/bmad-quick-dev/customize.toml +41 -0
- package/src/bmm-skills/4-implementation/bmad-quick-dev/step-05-present.md +6 -0
- package/src/bmm-skills/4-implementation/bmad-quick-dev/step-oneshot.md +6 -0
- package/src/bmm-skills/4-implementation/bmad-sprint-planning/SKILL.md +294 -1
- package/src/bmm-skills/4-implementation/bmad-sprint-planning/customize.toml +41 -0
- package/src/bmm-skills/4-implementation/bmad-sprint-status/SKILL.md +292 -1
- package/src/bmm-skills/4-implementation/bmad-sprint-status/customize.toml +41 -0
- package/tools/installer/core/manifest.js +38 -22
- package/tools/installer/ui.js +159 -21
- package/src/bmm-skills/4-implementation/bmad-code-review/workflow.md +0 -55
- package/src/bmm-skills/4-implementation/bmad-dev-story/workflow.md +0 -450
- package/src/bmm-skills/4-implementation/bmad-quick-dev/workflow.md +0 -76
- package/src/bmm-skills/4-implementation/bmad-sprint-planning/workflow.md +0 -263
- package/src/bmm-skills/4-implementation/bmad-sprint-status/workflow.md +0 -261
package/tools/installer/ui.js
CHANGED
|
@@ -1,20 +1,107 @@
|
|
|
1
1
|
const path = require('node:path');
|
|
2
2
|
const os = require('node:os');
|
|
3
|
+
const semver = require('semver');
|
|
3
4
|
const fs = require('./fs-native');
|
|
4
5
|
const { CLIUtils } = require('./cli-utils');
|
|
5
6
|
const { ExternalModuleManager } = require('./modules/external-manager');
|
|
6
7
|
const { resolveModuleVersion } = require('./modules/version-resolver');
|
|
7
|
-
const {
|
|
8
|
+
const { Manifest } = require('./core/manifest');
|
|
9
|
+
const {
|
|
10
|
+
parseChannelOptions,
|
|
11
|
+
buildPlan,
|
|
12
|
+
decideChannelForModule,
|
|
13
|
+
orphanPinWarnings,
|
|
14
|
+
bundledTargetWarnings,
|
|
15
|
+
} = require('./modules/channel-plan');
|
|
16
|
+
const channelResolver = require('./modules/channel-resolver');
|
|
8
17
|
const prompts = require('./prompts');
|
|
9
18
|
|
|
19
|
+
const manifest = new Manifest();
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Format a resolved version for display in installer labels.
|
|
23
|
+
* Semver-like values are normalized to a single leading "v".
|
|
24
|
+
* @param {string|null|undefined} version
|
|
25
|
+
* @returns {string}
|
|
26
|
+
*/
|
|
27
|
+
function formatDisplayVersion(version) {
|
|
28
|
+
const trimmed = typeof version === 'string' ? version.trim() : '';
|
|
29
|
+
if (!trimmed) return '';
|
|
30
|
+
|
|
31
|
+
const normalized = semver.valid(semver.coerce(trimmed));
|
|
32
|
+
if (normalized) {
|
|
33
|
+
return `v${normalized}`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return trimmed;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Build the display label for a module, showing an upgrade arrow when an
|
|
41
|
+
* installed semver differs from the latest resolvable semver.
|
|
42
|
+
* @param {string} name
|
|
43
|
+
* @param {string} latestVersion
|
|
44
|
+
* @param {string} installedVersion
|
|
45
|
+
* @returns {string}
|
|
46
|
+
*/
|
|
47
|
+
function buildModuleLabel(name, latestVersion, installedVersion = '') {
|
|
48
|
+
const latestDisplay = formatDisplayVersion(latestVersion);
|
|
49
|
+
if (!latestDisplay) return name;
|
|
50
|
+
|
|
51
|
+
const installedDisplay = formatDisplayVersion(installedVersion);
|
|
52
|
+
const latestSemver = semver.valid(semver.coerce(latestVersion || ''));
|
|
53
|
+
const installedSemver = semver.valid(semver.coerce(installedVersion || ''));
|
|
54
|
+
|
|
55
|
+
if (installedDisplay && latestSemver && installedSemver && semver.neq(installedSemver, latestSemver)) {
|
|
56
|
+
return `${name} (${installedDisplay} → ${latestDisplay})`;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return `${name} (${latestDisplay})`;
|
|
60
|
+
}
|
|
61
|
+
|
|
10
62
|
/**
|
|
11
|
-
*
|
|
63
|
+
* Resolve the version to show for a module picker entry. External modules use
|
|
64
|
+
* the same channel/tag resolver as installs; bundled modules fall back to local
|
|
65
|
+
* source metadata.
|
|
12
66
|
* @param {string} moduleCode - Module code (e.g., 'core', 'bmm', 'cis')
|
|
13
|
-
* @
|
|
67
|
+
* @param {Object} options
|
|
68
|
+
* @param {string|null} [options.repoUrl] - Module repository URL for tag resolution
|
|
69
|
+
* @param {string|null} [options.registryDefault] - Registry default channel
|
|
70
|
+
* @param {Object|null} [options.channelOptions] - Parsed installer channel options
|
|
71
|
+
* @returns {Promise<{version: string, lookupAttempted: boolean, lookupSucceeded: boolean}>}
|
|
14
72
|
*/
|
|
15
|
-
async function getModuleVersion(moduleCode) {
|
|
73
|
+
async function getModuleVersion(moduleCode, { repoUrl = null, registryDefault = null, channelOptions = null } = {}) {
|
|
74
|
+
if (repoUrl) {
|
|
75
|
+
const plan = decideChannelForModule({
|
|
76
|
+
code: moduleCode,
|
|
77
|
+
channelOptions,
|
|
78
|
+
registryDefault,
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
const resolved = await channelResolver.resolveChannel({
|
|
83
|
+
channel: plan.channel,
|
|
84
|
+
pin: plan.pin,
|
|
85
|
+
repoUrl,
|
|
86
|
+
});
|
|
87
|
+
if (resolved?.version) {
|
|
88
|
+
return {
|
|
89
|
+
version: resolved.version,
|
|
90
|
+
lookupAttempted: plan.channel === 'stable',
|
|
91
|
+
lookupSucceeded: true,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
} catch {
|
|
95
|
+
// Fall back to local metadata when tag resolution is unavailable.
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
16
99
|
const versionInfo = await resolveModuleVersion(moduleCode);
|
|
17
|
-
return
|
|
100
|
+
return {
|
|
101
|
+
version: versionInfo.version || '',
|
|
102
|
+
lookupAttempted: !!repoUrl,
|
|
103
|
+
lookupSucceeded: false,
|
|
104
|
+
};
|
|
18
105
|
}
|
|
19
106
|
|
|
20
107
|
/**
|
|
@@ -122,7 +209,7 @@ class UI {
|
|
|
122
209
|
// Return early with modify configuration
|
|
123
210
|
if (actionType === 'update') {
|
|
124
211
|
// Get existing installation info
|
|
125
|
-
const { installedModuleIds } = await this.getExistingInstallation(confirmedDirectory);
|
|
212
|
+
const { installedModuleIds, installedModuleVersions } = await this.getExistingInstallation(confirmedDirectory);
|
|
126
213
|
|
|
127
214
|
await prompts.log.message(`Found existing modules: ${[...installedModuleIds].join(', ')}`);
|
|
128
215
|
|
|
@@ -144,7 +231,7 @@ class UI {
|
|
|
144
231
|
`Non-interactive mode (--yes): using default modules (installed + defaults): ${selectedModules.join(', ')}`,
|
|
145
232
|
);
|
|
146
233
|
} else {
|
|
147
|
-
selectedModules = await this.selectAllModules(installedModuleIds);
|
|
234
|
+
selectedModules = await this.selectAllModules(installedModuleIds, installedModuleVersions, channelOptions);
|
|
148
235
|
}
|
|
149
236
|
|
|
150
237
|
// Resolve custom sources from --custom-source flag
|
|
@@ -208,7 +295,7 @@ class UI {
|
|
|
208
295
|
}
|
|
209
296
|
|
|
210
297
|
// This section is only for new installations (update returns early above)
|
|
211
|
-
const { installedModuleIds } = await this.getExistingInstallation(confirmedDirectory);
|
|
298
|
+
const { installedModuleIds, installedModuleVersions } = await this.getExistingInstallation(confirmedDirectory);
|
|
212
299
|
|
|
213
300
|
// Unified module selection - all modules in one grouped multiselect
|
|
214
301
|
let selectedModules;
|
|
@@ -227,7 +314,7 @@ class UI {
|
|
|
227
314
|
selectedModules = await this.getDefaultModules(installedModuleIds);
|
|
228
315
|
await prompts.log.info(`Using default modules (--yes flag): ${selectedModules.join(', ')}`);
|
|
229
316
|
} else {
|
|
230
|
-
selectedModules = await this.selectAllModules(installedModuleIds);
|
|
317
|
+
selectedModules = await this.selectAllModules(installedModuleIds, installedModuleVersions, channelOptions);
|
|
231
318
|
}
|
|
232
319
|
|
|
233
320
|
// Resolve custom sources from --custom-source flag
|
|
@@ -526,7 +613,7 @@ class UI {
|
|
|
526
613
|
/**
|
|
527
614
|
* Get existing installation info and installed modules
|
|
528
615
|
* @param {string} directory - Installation directory
|
|
529
|
-
* @returns {Object} Object with existingInstall, installedModuleIds, and bmadDir
|
|
616
|
+
* @returns {Object} Object with existingInstall, installedModuleIds, installedModuleVersions, and bmadDir
|
|
530
617
|
*/
|
|
531
618
|
async getExistingInstallation(directory) {
|
|
532
619
|
const { ExistingInstall } = require('./core/existing-install');
|
|
@@ -535,8 +622,26 @@ class UI {
|
|
|
535
622
|
const { bmadDir } = await installer.findBmadDir(directory);
|
|
536
623
|
const existingInstall = await ExistingInstall.detect(bmadDir);
|
|
537
624
|
const installedModuleIds = new Set(existingInstall.moduleIds);
|
|
625
|
+
const installedModuleVersions = new Map();
|
|
626
|
+
const manifestModules = await manifest.getAllModuleVersions(bmadDir);
|
|
538
627
|
|
|
539
|
-
|
|
628
|
+
for (const module of manifestModules) {
|
|
629
|
+
if (module?.name && module.version) {
|
|
630
|
+
installedModuleVersions.set(module.name, module.version);
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
for (const module of existingInstall.modules) {
|
|
635
|
+
if (module?.id && module.version && module.version !== 'unknown' && !installedModuleVersions.has(module.id)) {
|
|
636
|
+
installedModuleVersions.set(module.id, module.version);
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
if (existingInstall.hasCore && existingInstall.version && !installedModuleVersions.has('core')) {
|
|
641
|
+
installedModuleVersions.set('core', existingInstall.version);
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
return { existingInstall, installedModuleIds, installedModuleVersions, bmadDir };
|
|
540
645
|
}
|
|
541
646
|
|
|
542
647
|
/**
|
|
@@ -617,11 +722,13 @@ class UI {
|
|
|
617
722
|
/**
|
|
618
723
|
* Select all modules across three tiers: official, community, and custom URL.
|
|
619
724
|
* @param {Set} installedModuleIds - Currently installed module IDs
|
|
725
|
+
* @param {Map<string, string>} installedModuleVersions - Installed module versions from the local manifest
|
|
726
|
+
* @param {Object|null} channelOptions - Parsed installer channel options
|
|
620
727
|
* @returns {Array} Selected module codes (excluding core)
|
|
621
728
|
*/
|
|
622
|
-
async selectAllModules(installedModuleIds = new Set()) {
|
|
729
|
+
async selectAllModules(installedModuleIds = new Set(), installedModuleVersions = new Map(), channelOptions = null) {
|
|
623
730
|
// Phase 1: Official modules
|
|
624
|
-
const officialSelected = await this._selectOfficialModules(installedModuleIds);
|
|
731
|
+
const officialSelected = await this._selectOfficialModules(installedModuleIds, installedModuleVersions, channelOptions);
|
|
625
732
|
|
|
626
733
|
// Determine which installed modules are NOT official (community or custom).
|
|
627
734
|
// These must be preserved even if the user declines to browse community/custom.
|
|
@@ -657,9 +764,11 @@ class UI {
|
|
|
657
764
|
* Select official modules using autocompleteMultiselect.
|
|
658
765
|
* Extracted from the original selectAllModules - unchanged behavior.
|
|
659
766
|
* @param {Set} installedModuleIds - Currently installed module IDs
|
|
767
|
+
* @param {Map<string, string>} installedModuleVersions - Installed module versions from the local manifest
|
|
768
|
+
* @param {Object|null} channelOptions - Parsed installer channel options
|
|
660
769
|
* @returns {Array} Selected official module codes
|
|
661
770
|
*/
|
|
662
|
-
async _selectOfficialModules(installedModuleIds = new Set()) {
|
|
771
|
+
async _selectOfficialModules(installedModuleIds = new Set(), installedModuleVersions = new Map(), channelOptions = null) {
|
|
663
772
|
// Built-in modules (core, bmm) come from local source, not the registry
|
|
664
773
|
const { OfficialModules } = require('./modules/official-modules');
|
|
665
774
|
const builtInModules = (await new OfficialModules().listAvailable()).modules || [];
|
|
@@ -672,15 +781,18 @@ class UI {
|
|
|
672
781
|
const initialValues = [];
|
|
673
782
|
const lockedValues = ['core'];
|
|
674
783
|
|
|
675
|
-
const buildModuleEntry = async (code, name, description, isDefault) => {
|
|
784
|
+
const buildModuleEntry = async (code, name, description, isDefault, repoUrl = null, registryDefault = null) => {
|
|
676
785
|
const isInstalled = installedModuleIds.has(code);
|
|
677
|
-
const
|
|
678
|
-
const
|
|
786
|
+
const installedVersion = installedModuleVersions.get(code) || '';
|
|
787
|
+
const versionState = await getModuleVersion(code, { repoUrl, registryDefault, channelOptions });
|
|
788
|
+
const label = buildModuleLabel(name, versionState.version, installedVersion);
|
|
679
789
|
return {
|
|
680
790
|
label,
|
|
681
791
|
value: code,
|
|
682
792
|
hint: description,
|
|
683
793
|
selected: isInstalled || isDefault,
|
|
794
|
+
lookupAttempted: versionState.lookupAttempted,
|
|
795
|
+
lookupSucceeded: versionState.lookupSucceeded,
|
|
684
796
|
};
|
|
685
797
|
};
|
|
686
798
|
|
|
@@ -697,12 +809,38 @@ class UI {
|
|
|
697
809
|
}
|
|
698
810
|
|
|
699
811
|
// Add external registry modules (skip built-in duplicates)
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
812
|
+
const externalRegistryModules = registryModules.filter((mod) => !mod.builtIn && !builtInCodes.has(mod.code));
|
|
813
|
+
let externalRegistryEntries = [];
|
|
814
|
+
if (externalRegistryModules.length > 0) {
|
|
815
|
+
const spinner = await prompts.spinner();
|
|
816
|
+
spinner.start('Checking latest module versions...');
|
|
817
|
+
|
|
818
|
+
externalRegistryEntries = await Promise.all(
|
|
819
|
+
externalRegistryModules.map(async (mod) => ({
|
|
820
|
+
code: mod.code,
|
|
821
|
+
entry: await buildModuleEntry(
|
|
822
|
+
mod.code,
|
|
823
|
+
mod.name,
|
|
824
|
+
mod.description,
|
|
825
|
+
mod.defaultSelected,
|
|
826
|
+
mod.url || null,
|
|
827
|
+
mod.defaultChannel || null,
|
|
828
|
+
),
|
|
829
|
+
})),
|
|
830
|
+
);
|
|
831
|
+
|
|
832
|
+
spinner.stop('Checked latest module versions.');
|
|
833
|
+
|
|
834
|
+
const attemptedLookups = externalRegistryEntries.filter(({ entry }) => entry.lookupAttempted).length;
|
|
835
|
+
const successfulLookups = externalRegistryEntries.filter(({ entry }) => entry.lookupSucceeded).length;
|
|
836
|
+
if (attemptedLookups > 0 && successfulLookups === 0) {
|
|
837
|
+
await prompts.log.warn('Could not check latest module versions; showing cached/local versions.');
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
for (const { code, entry } of externalRegistryEntries) {
|
|
703
841
|
allOptions.push({ label: entry.label, value: entry.value, hint: entry.hint });
|
|
704
842
|
if (entry.selected) {
|
|
705
|
-
initialValues.push(
|
|
843
|
+
initialValues.push(code);
|
|
706
844
|
}
|
|
707
845
|
}
|
|
708
846
|
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
main_config: '{project-root}/_bmad/bmm/config.yaml'
|
|
3
|
-
---
|
|
4
|
-
|
|
5
|
-
# Code Review Workflow
|
|
6
|
-
|
|
7
|
-
**Goal:** Review code changes adversarially using parallel review layers and structured triage.
|
|
8
|
-
|
|
9
|
-
**Your Role:** You are an elite code reviewer. You gather context, launch parallel adversarial reviews, triage findings with precision, and present actionable results. No noise, no filler.
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
## WORKFLOW ARCHITECTURE
|
|
13
|
-
|
|
14
|
-
This uses **step-file architecture** for disciplined execution:
|
|
15
|
-
|
|
16
|
-
- **Micro-file Design**: Each step is self-contained and followed exactly
|
|
17
|
-
- **Just-In-Time Loading**: Only load the current step file
|
|
18
|
-
- **Sequential Enforcement**: Complete steps in order, no skipping
|
|
19
|
-
- **State Tracking**: Persist progress via in-memory variables
|
|
20
|
-
- **Append-Only Building**: Build artifacts incrementally
|
|
21
|
-
|
|
22
|
-
### Step Processing Rules
|
|
23
|
-
|
|
24
|
-
1. **READ COMPLETELY**: Read the entire step file before acting
|
|
25
|
-
2. **FOLLOW SEQUENCE**: Execute sections in order
|
|
26
|
-
3. **WAIT FOR INPUT**: Halt at checkpoints and wait for human
|
|
27
|
-
4. **LOAD NEXT**: When directed, read fully and follow the next step file
|
|
28
|
-
|
|
29
|
-
### Critical Rules (NO EXCEPTIONS)
|
|
30
|
-
|
|
31
|
-
- **NEVER** load multiple step files simultaneously
|
|
32
|
-
- **ALWAYS** read entire step file before execution
|
|
33
|
-
- **NEVER** skip steps or optimize the sequence
|
|
34
|
-
- **ALWAYS** follow the exact instructions in the step file
|
|
35
|
-
- **ALWAYS** halt at checkpoints and wait for human input
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
## INITIALIZATION SEQUENCE
|
|
39
|
-
|
|
40
|
-
### 1. Configuration Loading
|
|
41
|
-
|
|
42
|
-
Load and read full config from `{main_config}` and resolve:
|
|
43
|
-
|
|
44
|
-
- `project_name`, `planning_artifacts`, `implementation_artifacts`, `user_name`
|
|
45
|
-
- `communication_language`, `document_output_language`, `user_skill_level`
|
|
46
|
-
- `date` as system-generated current datetime
|
|
47
|
-
- `sprint_status` = `{implementation_artifacts}/sprint-status.yaml`
|
|
48
|
-
- `project_context` = `**/project-context.md` (load if exists)
|
|
49
|
-
- CLAUDE.md / memory files (load if exist)
|
|
50
|
-
|
|
51
|
-
YOU MUST ALWAYS SPEAK OUTPUT in your Agent communication style with the config `{communication_language}`.
|
|
52
|
-
|
|
53
|
-
### 2. First Step Execution
|
|
54
|
-
|
|
55
|
-
Read fully and follow: `./steps/step-01-gather-context.md` to begin the workflow.
|