bmad-method 6.3.1-next.8 → 6.4.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.
- package/package.json +3 -2
- package/src/bmm-skills/1-analysis/bmad-agent-analyst/SKILL.md +51 -36
- package/src/bmm-skills/1-analysis/bmad-agent-analyst/customize.toml +90 -0
- package/src/bmm-skills/1-analysis/bmad-agent-tech-writer/SKILL.md +50 -33
- package/src/bmm-skills/1-analysis/bmad-agent-tech-writer/customize.toml +81 -0
- package/src/bmm-skills/1-analysis/bmad-document-project/SKILL.md +57 -1
- package/src/bmm-skills/1-analysis/bmad-document-project/customize.toml +41 -0
- package/src/bmm-skills/1-analysis/bmad-document-project/workflows/deep-dive-instructions.md +1 -0
- package/src/bmm-skills/1-analysis/bmad-document-project/workflows/full-scan-instructions.md +1 -0
- package/src/bmm-skills/1-analysis/bmad-prfaq/SKILL.md +48 -9
- package/src/bmm-skills/1-analysis/bmad-prfaq/customize.toml +41 -0
- package/src/bmm-skills/1-analysis/bmad-prfaq/references/verdict.md +4 -0
- package/src/bmm-skills/1-analysis/bmad-product-brief/SKILL.md +44 -9
- package/src/bmm-skills/1-analysis/bmad-product-brief/customize.toml +47 -0
- package/src/bmm-skills/1-analysis/bmad-product-brief/prompts/contextual-discovery.md +8 -7
- package/src/bmm-skills/1-analysis/bmad-product-brief/prompts/draft-and-review.md +6 -5
- package/src/bmm-skills/1-analysis/bmad-product-brief/prompts/finalize.md +4 -1
- package/src/bmm-skills/1-analysis/bmad-product-brief/prompts/guided-elicitation.md +3 -2
- package/src/bmm-skills/1-analysis/research/bmad-domain-research/SKILL.md +91 -1
- package/src/bmm-skills/1-analysis/research/bmad-domain-research/customize.toml +41 -0
- package/src/bmm-skills/1-analysis/research/bmad-domain-research/domain-steps/step-06-research-synthesis.md +6 -0
- package/src/bmm-skills/1-analysis/research/bmad-market-research/SKILL.md +91 -1
- package/src/bmm-skills/1-analysis/research/bmad-market-research/customize.toml +41 -0
- package/src/bmm-skills/1-analysis/research/bmad-market-research/steps/step-06-research-completion.md +6 -0
- package/src/bmm-skills/1-analysis/research/bmad-technical-research/SKILL.md +91 -1
- package/src/bmm-skills/1-analysis/research/bmad-technical-research/customize.toml +41 -0
- package/src/bmm-skills/1-analysis/research/bmad-technical-research/technical-steps/step-06-research-synthesis.md +6 -0
- package/src/bmm-skills/2-plan-workflows/bmad-agent-pm/SKILL.md +50 -35
- package/src/bmm-skills/2-plan-workflows/bmad-agent-pm/customize.toml +85 -0
- package/src/bmm-skills/2-plan-workflows/bmad-agent-ux-designer/SKILL.md +50 -31
- package/src/bmm-skills/2-plan-workflows/bmad-agent-ux-designer/customize.toml +60 -0
- package/src/bmm-skills/2-plan-workflows/bmad-create-prd/SKILL.md +99 -1
- package/src/bmm-skills/2-plan-workflows/bmad-create-prd/customize.toml +41 -0
- package/src/bmm-skills/2-plan-workflows/bmad-create-prd/steps-c/step-12-complete.md +6 -0
- package/src/bmm-skills/2-plan-workflows/bmad-create-ux-design/SKILL.md +70 -1
- package/src/bmm-skills/2-plan-workflows/bmad-create-ux-design/customize.toml +41 -0
- package/src/bmm-skills/2-plan-workflows/bmad-create-ux-design/steps/step-14-complete.md +6 -0
- package/src/bmm-skills/2-plan-workflows/bmad-edit-prd/SKILL.md +97 -1
- package/src/bmm-skills/2-plan-workflows/bmad-edit-prd/customize.toml +42 -0
- package/src/bmm-skills/2-plan-workflows/bmad-edit-prd/steps-e/step-e-04-complete.md +2 -0
- package/src/bmm-skills/2-plan-workflows/bmad-validate-prd/SKILL.md +99 -1
- package/src/bmm-skills/2-plan-workflows/bmad-validate-prd/customize.toml +42 -0
- package/src/bmm-skills/2-plan-workflows/bmad-validate-prd/steps-v/step-v-13-report-complete.md +1 -0
- package/src/bmm-skills/3-solutioning/bmad-agent-architect/SKILL.md +50 -30
- package/src/bmm-skills/3-solutioning/bmad-agent-architect/customize.toml +65 -0
- package/src/bmm-skills/3-solutioning/bmad-check-implementation-readiness/SKILL.md +86 -1
- package/src/bmm-skills/3-solutioning/bmad-check-implementation-readiness/customize.toml +41 -0
- package/src/bmm-skills/3-solutioning/bmad-check-implementation-readiness/steps/step-06-final-assessment.md +6 -0
- package/src/bmm-skills/3-solutioning/bmad-create-architecture/SKILL.md +69 -1
- package/src/bmm-skills/3-solutioning/bmad-create-architecture/customize.toml +41 -0
- package/src/bmm-skills/3-solutioning/bmad-create-architecture/steps/step-08-complete.md +6 -0
- package/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/SKILL.md +88 -1
- package/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/customize.toml +41 -0
- package/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/steps/step-04-final-validation.md +6 -0
- package/src/bmm-skills/3-solutioning/bmad-generate-project-context/SKILL.md +76 -1
- package/src/bmm-skills/3-solutioning/bmad-generate-project-context/customize.toml +41 -0
- package/src/bmm-skills/3-solutioning/bmad-generate-project-context/steps/step-03-complete.md +6 -0
- package/src/bmm-skills/4-implementation/bmad-agent-dev/SKILL.md +48 -43
- package/src/bmm-skills/4-implementation/bmad-agent-dev/customize.toml +90 -0
- 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-correct-course/SKILL.md +296 -1
- package/src/bmm-skills/4-implementation/bmad-correct-course/customize.toml +41 -0
- package/src/bmm-skills/4-implementation/bmad-create-story/SKILL.md +424 -1
- package/src/bmm-skills/4-implementation/bmad-create-story/customize.toml +41 -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-qa-generate-e2e-tests/SKILL.md +171 -1
- package/src/bmm-skills/4-implementation/bmad-qa-generate-e2e-tests/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-retrospective/SKILL.md +1507 -1
- package/src/bmm-skills/4-implementation/bmad-retrospective/customize.toml +41 -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/src/bmm-skills/module.yaml +49 -0
- package/src/core-skills/bmad-advanced-elicitation/SKILL.md +7 -1
- package/src/core-skills/bmad-customize/SKILL.md +111 -0
- package/src/core-skills/bmad-customize/scripts/list_customizable_skills.py +231 -0
- package/src/core-skills/bmad-customize/scripts/tests/test_list_customizable_skills.py +249 -0
- package/src/core-skills/bmad-distillator/resources/distillate-format-reference.md +1 -1
- package/src/core-skills/bmad-party-mode/SKILL.md +13 -10
- package/src/core-skills/module-help.csv +1 -0
- package/src/core-skills/module.yaml +2 -0
- package/src/scripts/resolve_config.py +176 -0
- package/src/scripts/resolve_customization.py +230 -0
- package/tools/installer/commands/install.js +13 -0
- package/tools/installer/core/config.js +4 -1
- package/tools/installer/core/install-paths.js +11 -5
- package/tools/installer/core/installer.js +181 -94
- package/tools/installer/core/manifest-generator.js +339 -184
- package/tools/installer/core/manifest.js +86 -86
- package/tools/installer/fs-native.js +5 -0
- package/tools/installer/ide/platform-codes.yaml +6 -0
- package/tools/installer/modules/channel-plan.js +203 -0
- package/tools/installer/modules/channel-resolver.js +241 -0
- package/tools/installer/modules/community-manager.js +130 -23
- package/tools/installer/modules/custom-module-manager.js +160 -19
- package/tools/installer/modules/external-manager.js +235 -32
- package/tools/installer/modules/official-modules.js +58 -12
- package/tools/installer/modules/registry-client.js +139 -7
- package/tools/installer/modules/registry-fallback.yaml +8 -0
- package/tools/installer/modules/version-resolver.js +336 -0
- package/tools/installer/project-root.js +54 -0
- package/tools/installer/ui.js +561 -50
- package/tools/platform-codes.yaml +6 -0
- package/src/bmm-skills/1-analysis/bmad-agent-analyst/bmad-skill-manifest.yaml +0 -11
- package/src/bmm-skills/1-analysis/bmad-agent-tech-writer/bmad-skill-manifest.yaml +0 -11
- package/src/bmm-skills/1-analysis/bmad-document-project/workflow.md +0 -25
- package/src/bmm-skills/1-analysis/research/bmad-domain-research/workflow.md +0 -51
- package/src/bmm-skills/1-analysis/research/bmad-market-research/workflow.md +0 -51
- package/src/bmm-skills/1-analysis/research/bmad-technical-research/workflow.md +0 -52
- package/src/bmm-skills/2-plan-workflows/bmad-agent-pm/bmad-skill-manifest.yaml +0 -11
- package/src/bmm-skills/2-plan-workflows/bmad-agent-ux-designer/bmad-skill-manifest.yaml +0 -11
- package/src/bmm-skills/2-plan-workflows/bmad-create-prd/workflow.md +0 -61
- package/src/bmm-skills/2-plan-workflows/bmad-create-ux-design/workflow.md +0 -35
- package/src/bmm-skills/2-plan-workflows/bmad-edit-prd/workflow.md +0 -62
- package/src/bmm-skills/2-plan-workflows/bmad-validate-prd/workflow.md +0 -61
- package/src/bmm-skills/3-solutioning/bmad-agent-architect/bmad-skill-manifest.yaml +0 -11
- package/src/bmm-skills/3-solutioning/bmad-check-implementation-readiness/workflow.md +0 -47
- package/src/bmm-skills/3-solutioning/bmad-create-architecture/workflow.md +0 -32
- package/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/workflow.md +0 -51
- package/src/bmm-skills/3-solutioning/bmad-generate-project-context/workflow.md +0 -39
- package/src/bmm-skills/4-implementation/bmad-agent-dev/bmad-skill-manifest.yaml +0 -11
- package/src/bmm-skills/4-implementation/bmad-code-review/workflow.md +0 -55
- package/src/bmm-skills/4-implementation/bmad-correct-course/workflow.md +0 -267
- package/src/bmm-skills/4-implementation/bmad-create-story/workflow.md +0 -380
- package/src/bmm-skills/4-implementation/bmad-dev-story/workflow.md +0 -450
- package/src/bmm-skills/4-implementation/bmad-qa-generate-e2e-tests/workflow.md +0 -136
- package/src/bmm-skills/4-implementation/bmad-quick-dev/workflow.md +0 -76
- package/src/bmm-skills/4-implementation/bmad-retrospective/workflow.md +0 -1479
- 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
|
@@ -1,9 +1,20 @@
|
|
|
1
1
|
const path = require('node:path');
|
|
2
|
+
const https = require('node:https');
|
|
3
|
+
const { execFile } = require('node:child_process');
|
|
4
|
+
const { promisify } = require('node:util');
|
|
2
5
|
const fs = require('../fs-native');
|
|
3
6
|
const crypto = require('node:crypto');
|
|
4
|
-
const {
|
|
7
|
+
const { resolveModuleVersion } = require('../modules/version-resolver');
|
|
5
8
|
const prompts = require('../prompts');
|
|
6
9
|
|
|
10
|
+
const execFileAsync = promisify(execFile);
|
|
11
|
+
const NPM_LOOKUP_TIMEOUT_MS = 10_000;
|
|
12
|
+
const NPM_PACKAGE_NAME_PATTERN = /^(?:@[a-z0-9][a-z0-9._~-]*\/)?[a-z0-9][a-z0-9._~-]*$/;
|
|
13
|
+
|
|
14
|
+
function isValidNpmPackageName(packageName) {
|
|
15
|
+
return typeof packageName === 'string' && NPM_PACKAGE_NAME_PATTERN.test(packageName);
|
|
16
|
+
}
|
|
17
|
+
|
|
7
18
|
class Manifest {
|
|
8
19
|
/**
|
|
9
20
|
* Create a new manifest
|
|
@@ -180,7 +191,12 @@ class Manifest {
|
|
|
180
191
|
npmPackage: options.npmPackage || null,
|
|
181
192
|
repoUrl: options.repoUrl || null,
|
|
182
193
|
};
|
|
194
|
+
if (options.channel) entry.channel = options.channel;
|
|
195
|
+
if (options.sha) entry.sha = options.sha;
|
|
183
196
|
if (options.localPath) entry.localPath = options.localPath;
|
|
197
|
+
if (options.rawSource) entry.rawSource = options.rawSource;
|
|
198
|
+
if (options.registryApprovedTag) entry.registryApprovedTag = options.registryApprovedTag;
|
|
199
|
+
if (options.registryApprovedSha) entry.registryApprovedSha = options.registryApprovedSha;
|
|
184
200
|
manifest.modules.push(entry);
|
|
185
201
|
} else {
|
|
186
202
|
// Module exists, update its version info
|
|
@@ -192,6 +208,11 @@ class Manifest {
|
|
|
192
208
|
npmPackage: options.npmPackage === undefined ? existing.npmPackage : options.npmPackage,
|
|
193
209
|
repoUrl: options.repoUrl === undefined ? existing.repoUrl : options.repoUrl,
|
|
194
210
|
localPath: options.localPath === undefined ? existing.localPath : options.localPath,
|
|
211
|
+
channel: options.channel === undefined ? existing.channel : options.channel,
|
|
212
|
+
sha: options.sha === undefined ? existing.sha : options.sha,
|
|
213
|
+
rawSource: options.rawSource === undefined ? existing.rawSource : options.rawSource,
|
|
214
|
+
registryApprovedTag: options.registryApprovedTag === undefined ? existing.registryApprovedTag : options.registryApprovedTag,
|
|
215
|
+
registryApprovedSha: options.registryApprovedSha === undefined ? existing.registryApprovedSha : options.registryApprovedSha,
|
|
195
216
|
lastUpdated: new Date().toISOString(),
|
|
196
217
|
};
|
|
197
218
|
}
|
|
@@ -258,13 +279,11 @@ class Manifest {
|
|
|
258
279
|
* @returns {Object} Version info object with version, source, npmPackage, repoUrl
|
|
259
280
|
*/
|
|
260
281
|
async getModuleVersionInfo(moduleName, bmadDir, moduleSourcePath = null) {
|
|
261
|
-
const yaml = require('yaml');
|
|
262
|
-
|
|
263
282
|
// Resolve source type first, then read version with the correct path context
|
|
264
283
|
if (['core', 'bmm'].includes(moduleName)) {
|
|
265
|
-
const
|
|
284
|
+
const versionInfo = await resolveModuleVersion(moduleName, { moduleSourcePath });
|
|
266
285
|
return {
|
|
267
|
-
version,
|
|
286
|
+
version: versionInfo.version,
|
|
268
287
|
source: 'built-in',
|
|
269
288
|
npmPackage: null,
|
|
270
289
|
repoUrl: null,
|
|
@@ -277,13 +296,17 @@ class Manifest {
|
|
|
277
296
|
const moduleInfo = await extMgr.getModuleByCode(moduleName);
|
|
278
297
|
|
|
279
298
|
if (moduleInfo) {
|
|
280
|
-
|
|
281
|
-
const
|
|
299
|
+
const externalResolution = extMgr.getResolution(moduleName);
|
|
300
|
+
const versionInfo = await resolveModuleVersion(moduleName, { moduleSourcePath });
|
|
282
301
|
return {
|
|
283
|
-
|
|
302
|
+
// Git tag recorded during install trumps the on-disk package.json
|
|
303
|
+
// version, so the manifest carries "v1.7.0" instead of "1.7.0".
|
|
304
|
+
version: externalResolution?.version || versionInfo.version,
|
|
284
305
|
source: 'external',
|
|
285
306
|
npmPackage: moduleInfo.npmPackage || null,
|
|
286
307
|
repoUrl: moduleInfo.url || null,
|
|
308
|
+
channel: externalResolution?.channel || null,
|
|
309
|
+
sha: externalResolution?.sha || null,
|
|
287
310
|
};
|
|
288
311
|
}
|
|
289
312
|
|
|
@@ -292,12 +315,20 @@ class Manifest {
|
|
|
292
315
|
const communityMgr = new CommunityModuleManager();
|
|
293
316
|
const communityInfo = await communityMgr.getModuleByCode(moduleName);
|
|
294
317
|
if (communityInfo) {
|
|
295
|
-
const
|
|
318
|
+
const communityResolution = communityMgr.getResolution(moduleName);
|
|
319
|
+
const versionInfo = await resolveModuleVersion(moduleName, {
|
|
320
|
+
moduleSourcePath,
|
|
321
|
+
fallbackVersion: communityInfo.version,
|
|
322
|
+
});
|
|
296
323
|
return {
|
|
297
|
-
version:
|
|
324
|
+
version: communityResolution?.version || versionInfo.version || communityInfo.version,
|
|
298
325
|
source: 'community',
|
|
299
326
|
npmPackage: communityInfo.npmPackage || null,
|
|
300
327
|
repoUrl: communityInfo.url || null,
|
|
328
|
+
channel: communityResolution?.channel || null,
|
|
329
|
+
sha: communityResolution?.sha || null,
|
|
330
|
+
registryApprovedTag: communityResolution?.registryApprovedTag || null,
|
|
331
|
+
registryApprovedSha: communityResolution?.registryApprovedSha || null,
|
|
301
332
|
};
|
|
302
333
|
}
|
|
303
334
|
|
|
@@ -307,110 +338,75 @@ class Manifest {
|
|
|
307
338
|
const resolved = customMgr.getResolution(moduleName);
|
|
308
339
|
const customSource = await customMgr.findModuleSourceByCode(moduleName, { bmadDir });
|
|
309
340
|
if (customSource || resolved) {
|
|
310
|
-
const
|
|
341
|
+
const versionInfo = await resolveModuleVersion(moduleName, {
|
|
342
|
+
moduleSourcePath: moduleSourcePath || customSource,
|
|
343
|
+
fallbackVersion: resolved?.version,
|
|
344
|
+
marketplacePluginNames: resolved?.pluginName ? [resolved.pluginName] : [],
|
|
345
|
+
});
|
|
346
|
+
const hasGitClone = !!resolved?.repoUrl;
|
|
311
347
|
return {
|
|
312
|
-
version
|
|
348
|
+
// Prefer the git ref we actually cloned over the package.json version.
|
|
349
|
+
version: resolved?.cloneRef || (hasGitClone ? 'main' : versionInfo.version),
|
|
313
350
|
source: 'custom',
|
|
314
351
|
npmPackage: null,
|
|
315
352
|
repoUrl: resolved?.repoUrl || null,
|
|
316
353
|
localPath: resolved?.localPath || null,
|
|
354
|
+
channel: hasGitClone ? (resolved?.cloneRef ? 'pinned' : 'next') : null,
|
|
355
|
+
sha: resolved?.cloneSha || null,
|
|
356
|
+
rawSource: resolved?.rawInput || null,
|
|
317
357
|
};
|
|
318
358
|
}
|
|
319
359
|
|
|
320
360
|
// Unknown module
|
|
321
|
-
const
|
|
361
|
+
const versionInfo = await resolveModuleVersion(moduleName, { moduleSourcePath });
|
|
322
362
|
return {
|
|
323
|
-
version,
|
|
363
|
+
version: versionInfo.version,
|
|
324
364
|
source: 'unknown',
|
|
325
365
|
npmPackage: null,
|
|
326
366
|
repoUrl: null,
|
|
327
367
|
};
|
|
328
368
|
}
|
|
329
369
|
|
|
330
|
-
/**
|
|
331
|
-
* Read version from .claude-plugin/marketplace.json for a module
|
|
332
|
-
* @param {string} moduleName - Module code
|
|
333
|
-
* @returns {string|null} Version or null
|
|
334
|
-
*/
|
|
335
|
-
async _readMarketplaceVersion(moduleName, moduleSourcePath = null) {
|
|
336
|
-
const os = require('node:os');
|
|
337
|
-
let marketplacePath;
|
|
338
|
-
|
|
339
|
-
if (['core', 'bmm'].includes(moduleName)) {
|
|
340
|
-
marketplacePath = path.join(getProjectRoot(), '.claude-plugin', 'marketplace.json');
|
|
341
|
-
} else if (moduleSourcePath) {
|
|
342
|
-
// Walk up from source path to find marketplace.json
|
|
343
|
-
let dir = moduleSourcePath;
|
|
344
|
-
for (let i = 0; i < 5; i++) {
|
|
345
|
-
const candidate = path.join(dir, '.claude-plugin', 'marketplace.json');
|
|
346
|
-
if (await fs.pathExists(candidate)) {
|
|
347
|
-
marketplacePath = candidate;
|
|
348
|
-
break;
|
|
349
|
-
}
|
|
350
|
-
const parent = path.dirname(dir);
|
|
351
|
-
if (parent === dir) break;
|
|
352
|
-
dir = parent;
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
// Fallback to external module cache
|
|
357
|
-
if (!marketplacePath) {
|
|
358
|
-
const cacheDir = path.join(os.homedir(), '.bmad', 'cache', 'external-modules', moduleName);
|
|
359
|
-
marketplacePath = path.join(cacheDir, '.claude-plugin', 'marketplace.json');
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
try {
|
|
363
|
-
if (await fs.pathExists(marketplacePath)) {
|
|
364
|
-
const data = JSON.parse(await fs.readFile(marketplacePath, 'utf8'));
|
|
365
|
-
const plugins = data?.plugins;
|
|
366
|
-
if (!Array.isArray(plugins) || plugins.length === 0) return null;
|
|
367
|
-
let best = null;
|
|
368
|
-
for (const p of plugins) {
|
|
369
|
-
if (p.version && (!best || p.version > best)) best = p.version;
|
|
370
|
-
}
|
|
371
|
-
return best;
|
|
372
|
-
}
|
|
373
|
-
} catch {
|
|
374
|
-
// ignore
|
|
375
|
-
}
|
|
376
|
-
return null;
|
|
377
|
-
}
|
|
378
|
-
|
|
379
370
|
/**
|
|
380
371
|
* Fetch latest version from npm for a package
|
|
381
372
|
* @param {string} packageName - npm package name
|
|
382
373
|
* @returns {string|null} Latest version or null
|
|
383
374
|
*/
|
|
384
375
|
async fetchNpmVersion(packageName) {
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
376
|
+
if (!isValidNpmPackageName(packageName)) {
|
|
377
|
+
return null;
|
|
378
|
+
}
|
|
388
379
|
|
|
380
|
+
try {
|
|
389
381
|
// Try using npm view first (more reliable)
|
|
390
382
|
try {
|
|
391
|
-
const
|
|
383
|
+
const { stdout } = await execFileAsync('npm', ['view', packageName, 'version'], {
|
|
392
384
|
encoding: 'utf8',
|
|
393
|
-
|
|
394
|
-
timeout: 10_000,
|
|
385
|
+
timeout: NPM_LOOKUP_TIMEOUT_MS,
|
|
395
386
|
});
|
|
396
|
-
return
|
|
387
|
+
return stdout.trim();
|
|
397
388
|
} catch {
|
|
398
389
|
// Fallback to npm registry API
|
|
399
|
-
return new Promise((resolve
|
|
400
|
-
https
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
390
|
+
return new Promise((resolve) => {
|
|
391
|
+
const request = https.get(`https://registry.npmjs.org/${encodeURIComponent(packageName)}`, (res) => {
|
|
392
|
+
let data = '';
|
|
393
|
+
res.on('data', (chunk) => (data += chunk));
|
|
394
|
+
res.on('end', () => {
|
|
395
|
+
try {
|
|
396
|
+
const pkg = JSON.parse(data);
|
|
397
|
+
resolve(pkg['dist-tags']?.latest || pkg.version || null);
|
|
398
|
+
} catch {
|
|
399
|
+
resolve(null);
|
|
400
|
+
}
|
|
401
|
+
});
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
request.setTimeout(NPM_LOOKUP_TIMEOUT_MS, () => {
|
|
405
|
+
request.destroy();
|
|
406
|
+
resolve(null);
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
request.on('error', () => resolve(null));
|
|
414
410
|
});
|
|
415
411
|
}
|
|
416
412
|
} catch {
|
|
@@ -424,6 +420,7 @@ class Manifest {
|
|
|
424
420
|
* @returns {Array} Array of update info objects
|
|
425
421
|
*/
|
|
426
422
|
async checkForUpdates(bmadDir) {
|
|
423
|
+
const semver = require('semver');
|
|
427
424
|
const modules = await this.getAllModuleVersions(bmadDir);
|
|
428
425
|
const updates = [];
|
|
429
426
|
|
|
@@ -437,7 +434,10 @@ class Manifest {
|
|
|
437
434
|
continue;
|
|
438
435
|
}
|
|
439
436
|
|
|
440
|
-
|
|
437
|
+
const installedVersion = semver.valid(module.version) || semver.valid(semver.coerce(module.version || ''));
|
|
438
|
+
const availableVersion = semver.valid(latestVersion) || semver.valid(semver.coerce(latestVersion));
|
|
439
|
+
|
|
440
|
+
if (installedVersion && availableVersion && semver.gt(availableVersion, installedVersion)) {
|
|
441
441
|
updates.push({
|
|
442
442
|
name: module.name,
|
|
443
443
|
installedVersion: module.version,
|
|
@@ -82,7 +82,9 @@ module.exports = {
|
|
|
82
82
|
stat: fsp.stat,
|
|
83
83
|
readdir: fsp.readdir,
|
|
84
84
|
access: fsp.access,
|
|
85
|
+
realpath: fsp.realpath,
|
|
85
86
|
rename: fsp.rename,
|
|
87
|
+
rmdir: fsp.rmdir,
|
|
86
88
|
unlink: fsp.unlink,
|
|
87
89
|
chmod: fsp.chmod,
|
|
88
90
|
mkdir: fsp.mkdir,
|
|
@@ -103,6 +105,9 @@ module.exports = {
|
|
|
103
105
|
existsSync: fs.existsSync.bind(fs),
|
|
104
106
|
readFileSync: fs.readFileSync.bind(fs),
|
|
105
107
|
writeFileSync: fs.writeFileSync.bind(fs),
|
|
108
|
+
statSync: fs.statSync.bind(fs),
|
|
109
|
+
accessSync: fs.accessSync.bind(fs),
|
|
110
|
+
readdirSync: fs.readdirSync.bind(fs),
|
|
106
111
|
createReadStream: fs.createReadStream.bind(fs),
|
|
107
112
|
pathExistsSync: fs.existsSync.bind(fs),
|
|
108
113
|
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Channel plan: the per-module resolution decision applied at install time.
|
|
3
|
+
*
|
|
4
|
+
* A "plan entry" for a module is:
|
|
5
|
+
* { channel: 'stable'|'next'|'pinned', pin?: string }
|
|
6
|
+
*
|
|
7
|
+
* We build the plan from:
|
|
8
|
+
* 1. CLI flags (--channel / --all-* / --next=CODE / --pin CODE=TAG)
|
|
9
|
+
* 2. Interactive answers (the "all stable?" gate + per-module picker)
|
|
10
|
+
* 3. Registry defaults (default_channel from registry-fallback.yaml / official.yaml)
|
|
11
|
+
* 4. Hardcoded fallback 'stable'
|
|
12
|
+
*
|
|
13
|
+
* Precedence: --pin > --next=CODE > --channel (global) > registry default > 'stable'.
|
|
14
|
+
*
|
|
15
|
+
* This module is pure. No prompts, no git, no filesystem.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const VALID_CHANNELS = new Set(['stable', 'next']);
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Parse raw commander options into a structured channel options object.
|
|
22
|
+
*
|
|
23
|
+
* @param {Object} options - raw command-line options
|
|
24
|
+
* @returns {{
|
|
25
|
+
* global: 'stable'|'next'|null,
|
|
26
|
+
* nextSet: Set<string>,
|
|
27
|
+
* pins: Map<string, string>,
|
|
28
|
+
* warnings: string[]
|
|
29
|
+
* }}
|
|
30
|
+
*/
|
|
31
|
+
function parseChannelOptions(options = {}) {
|
|
32
|
+
const warnings = [];
|
|
33
|
+
|
|
34
|
+
// Global channel from --channel / --all-stable / --all-next.
|
|
35
|
+
let global = null;
|
|
36
|
+
const aliases = [];
|
|
37
|
+
if (options.channel) aliases.push({ flag: '--channel', value: normalizeChannel(options.channel, warnings, '--channel') });
|
|
38
|
+
if (options.allStable) aliases.push({ flag: '--all-stable', value: 'stable' });
|
|
39
|
+
if (options.allNext) aliases.push({ flag: '--all-next', value: 'next' });
|
|
40
|
+
|
|
41
|
+
const distinct = new Set(aliases.map((a) => a.value).filter(Boolean));
|
|
42
|
+
if (distinct.size > 1) {
|
|
43
|
+
warnings.push(
|
|
44
|
+
`Conflicting channel flags: ${aliases
|
|
45
|
+
.filter((a) => a.value)
|
|
46
|
+
.map((a) => a.flag + '=' + a.value)
|
|
47
|
+
.join(', ')}. Using first: ${aliases.find((a) => a.value).flag}.`,
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
const firstValid = aliases.find((a) => a.value);
|
|
51
|
+
if (firstValid) global = firstValid.value;
|
|
52
|
+
|
|
53
|
+
// --next=CODE (repeatable)
|
|
54
|
+
const nextSet = new Set();
|
|
55
|
+
for (const code of options.next || []) {
|
|
56
|
+
const trimmed = String(code).trim();
|
|
57
|
+
if (!trimmed) continue;
|
|
58
|
+
nextSet.add(trimmed);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// --pin CODE=TAG (repeatable)
|
|
62
|
+
const pins = new Map();
|
|
63
|
+
for (const spec of options.pin || []) {
|
|
64
|
+
const parsed = parsePinSpec(spec);
|
|
65
|
+
if (!parsed) {
|
|
66
|
+
warnings.push(`Ignoring malformed --pin value '${spec}'. Expected CODE=TAG.`);
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
if (pins.has(parsed.code)) {
|
|
70
|
+
warnings.push(`--pin specified multiple times for '${parsed.code}'. Using last: ${parsed.tag}.`);
|
|
71
|
+
}
|
|
72
|
+
pins.set(parsed.code, parsed.tag);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// --yes auto-confirms the community-module curator-bypass prompt so
|
|
76
|
+
// headless installs with --next=/--pin for a community module don't hang.
|
|
77
|
+
const acceptBypass = options.yes === true || options.acceptBypass === true;
|
|
78
|
+
|
|
79
|
+
return { global, nextSet, pins, warnings, acceptBypass };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function normalizeChannel(raw, warnings, flagName) {
|
|
83
|
+
if (typeof raw !== 'string') return null;
|
|
84
|
+
const lower = raw.trim().toLowerCase();
|
|
85
|
+
if (VALID_CHANNELS.has(lower)) return lower;
|
|
86
|
+
warnings.push(`Ignoring invalid ${flagName} value '${raw}'. Expected one of: stable, next.`);
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function parsePinSpec(spec) {
|
|
91
|
+
if (typeof spec !== 'string') return null;
|
|
92
|
+
const idx = spec.indexOf('=');
|
|
93
|
+
if (idx <= 0 || idx === spec.length - 1) return null;
|
|
94
|
+
const code = spec.slice(0, idx).trim();
|
|
95
|
+
const tag = spec.slice(idx + 1).trim();
|
|
96
|
+
if (!code || !tag) return null;
|
|
97
|
+
return { code, tag };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Build a per-module plan entry, applying precedence.
|
|
102
|
+
*
|
|
103
|
+
* @param {Object} args
|
|
104
|
+
* @param {string} args.code
|
|
105
|
+
* @param {Object} args.channelOptions - from parseChannelOptions
|
|
106
|
+
* @param {string} [args.registryDefault] - module's default_channel, if any
|
|
107
|
+
* @returns {{channel: 'stable'|'next'|'pinned', pin?: string, source: string}}
|
|
108
|
+
* source describes where the decision came from, for logging / debugging.
|
|
109
|
+
*/
|
|
110
|
+
function decideChannelForModule({ code, channelOptions, registryDefault }) {
|
|
111
|
+
const { global, nextSet, pins } = channelOptions || { nextSet: new Set(), pins: new Map() };
|
|
112
|
+
|
|
113
|
+
if (pins && pins.has(code)) {
|
|
114
|
+
return { channel: 'pinned', pin: pins.get(code), source: 'flag:--pin' };
|
|
115
|
+
}
|
|
116
|
+
if (nextSet && nextSet.has(code)) {
|
|
117
|
+
return { channel: 'next', source: 'flag:--next' };
|
|
118
|
+
}
|
|
119
|
+
if (global) {
|
|
120
|
+
return { channel: global, source: 'flag:--channel' };
|
|
121
|
+
}
|
|
122
|
+
if (registryDefault && VALID_CHANNELS.has(registryDefault)) {
|
|
123
|
+
return { channel: registryDefault, source: 'registry' };
|
|
124
|
+
}
|
|
125
|
+
return { channel: 'stable', source: 'default' };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Build a full channel plan map for a set of modules.
|
|
130
|
+
*
|
|
131
|
+
* @param {Object} args
|
|
132
|
+
* @param {Array<{code: string, defaultChannel?: string, builtIn?: boolean}>} args.modules
|
|
133
|
+
* Only the modules that need a channel entry; callers should filter out
|
|
134
|
+
* bundled modules (core/bmm) before calling.
|
|
135
|
+
* @param {Object} args.channelOptions - from parseChannelOptions
|
|
136
|
+
* @returns {Map<string, {channel: string, pin?: string, source: string}>}
|
|
137
|
+
*/
|
|
138
|
+
function buildPlan({ modules, channelOptions }) {
|
|
139
|
+
const plan = new Map();
|
|
140
|
+
for (const mod of modules || []) {
|
|
141
|
+
plan.set(
|
|
142
|
+
mod.code,
|
|
143
|
+
decideChannelForModule({
|
|
144
|
+
code: mod.code,
|
|
145
|
+
channelOptions,
|
|
146
|
+
registryDefault: mod.defaultChannel,
|
|
147
|
+
}),
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
return plan;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Report any --pin CODE=TAG entries that don't correspond to a selected module.
|
|
155
|
+
* These get warned about but don't abort the install.
|
|
156
|
+
*/
|
|
157
|
+
function orphanPinWarnings(channelOptions, selectedCodes) {
|
|
158
|
+
const warnings = [];
|
|
159
|
+
const selected = new Set(selectedCodes || []);
|
|
160
|
+
for (const code of channelOptions?.pins?.keys() || []) {
|
|
161
|
+
if (!selected.has(code)) {
|
|
162
|
+
warnings.push(`--pin for '${code}' has no effect (module not selected).`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
for (const code of channelOptions?.nextSet || []) {
|
|
166
|
+
if (!selected.has(code)) {
|
|
167
|
+
warnings.push(`--next for '${code}' has no effect (module not selected).`);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return warnings;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Warn when --pin / --next targets a bundled module (core, bmm). Those are
|
|
175
|
+
* shipped inside the installer binary — there's no git clone to override, so
|
|
176
|
+
* the flag has no effect. Users who actually want a prerelease core/bmm
|
|
177
|
+
* should use `npx bmad-method@next install`.
|
|
178
|
+
*/
|
|
179
|
+
function bundledTargetWarnings(channelOptions, bundledCodes) {
|
|
180
|
+
const warnings = [];
|
|
181
|
+
const bundled = new Set(bundledCodes || []);
|
|
182
|
+
const hint = '(bundled module; use `npx bmad-method@next install` for a prerelease)';
|
|
183
|
+
for (const code of channelOptions?.pins?.keys() || []) {
|
|
184
|
+
if (bundled.has(code)) {
|
|
185
|
+
warnings.push(`--pin for '${code}' has no effect ${hint}.`);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
for (const code of channelOptions?.nextSet || []) {
|
|
189
|
+
if (bundled.has(code)) {
|
|
190
|
+
warnings.push(`--next for '${code}' has no effect ${hint}.`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return warnings;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
module.exports = {
|
|
197
|
+
parseChannelOptions,
|
|
198
|
+
decideChannelForModule,
|
|
199
|
+
buildPlan,
|
|
200
|
+
orphanPinWarnings,
|
|
201
|
+
bundledTargetWarnings,
|
|
202
|
+
parsePinSpec,
|
|
203
|
+
};
|