bmad-method 6.3.1-next.9 → 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.
Files changed (140) hide show
  1. package/package.json +3 -2
  2. package/src/bmm-skills/1-analysis/bmad-agent-analyst/SKILL.md +51 -36
  3. package/src/bmm-skills/1-analysis/bmad-agent-analyst/customize.toml +90 -0
  4. package/src/bmm-skills/1-analysis/bmad-agent-tech-writer/SKILL.md +50 -33
  5. package/src/bmm-skills/1-analysis/bmad-agent-tech-writer/customize.toml +81 -0
  6. package/src/bmm-skills/1-analysis/bmad-document-project/SKILL.md +57 -1
  7. package/src/bmm-skills/1-analysis/bmad-document-project/customize.toml +41 -0
  8. package/src/bmm-skills/1-analysis/bmad-document-project/workflows/deep-dive-instructions.md +1 -0
  9. package/src/bmm-skills/1-analysis/bmad-document-project/workflows/full-scan-instructions.md +1 -0
  10. package/src/bmm-skills/1-analysis/bmad-prfaq/SKILL.md +48 -9
  11. package/src/bmm-skills/1-analysis/bmad-prfaq/customize.toml +41 -0
  12. package/src/bmm-skills/1-analysis/bmad-prfaq/references/verdict.md +4 -0
  13. package/src/bmm-skills/1-analysis/bmad-product-brief/SKILL.md +44 -9
  14. package/src/bmm-skills/1-analysis/bmad-product-brief/customize.toml +47 -0
  15. package/src/bmm-skills/1-analysis/bmad-product-brief/prompts/contextual-discovery.md +8 -7
  16. package/src/bmm-skills/1-analysis/bmad-product-brief/prompts/draft-and-review.md +6 -5
  17. package/src/bmm-skills/1-analysis/bmad-product-brief/prompts/finalize.md +4 -1
  18. package/src/bmm-skills/1-analysis/bmad-product-brief/prompts/guided-elicitation.md +3 -2
  19. package/src/bmm-skills/1-analysis/research/bmad-domain-research/SKILL.md +91 -1
  20. package/src/bmm-skills/1-analysis/research/bmad-domain-research/customize.toml +41 -0
  21. package/src/bmm-skills/1-analysis/research/bmad-domain-research/domain-steps/step-06-research-synthesis.md +6 -0
  22. package/src/bmm-skills/1-analysis/research/bmad-market-research/SKILL.md +91 -1
  23. package/src/bmm-skills/1-analysis/research/bmad-market-research/customize.toml +41 -0
  24. package/src/bmm-skills/1-analysis/research/bmad-market-research/steps/step-06-research-completion.md +6 -0
  25. package/src/bmm-skills/1-analysis/research/bmad-technical-research/SKILL.md +91 -1
  26. package/src/bmm-skills/1-analysis/research/bmad-technical-research/customize.toml +41 -0
  27. package/src/bmm-skills/1-analysis/research/bmad-technical-research/technical-steps/step-06-research-synthesis.md +6 -0
  28. package/src/bmm-skills/2-plan-workflows/bmad-agent-pm/SKILL.md +50 -35
  29. package/src/bmm-skills/2-plan-workflows/bmad-agent-pm/customize.toml +85 -0
  30. package/src/bmm-skills/2-plan-workflows/bmad-agent-ux-designer/SKILL.md +50 -31
  31. package/src/bmm-skills/2-plan-workflows/bmad-agent-ux-designer/customize.toml +60 -0
  32. package/src/bmm-skills/2-plan-workflows/bmad-create-prd/SKILL.md +99 -1
  33. package/src/bmm-skills/2-plan-workflows/bmad-create-prd/customize.toml +41 -0
  34. package/src/bmm-skills/2-plan-workflows/bmad-create-prd/steps-c/step-12-complete.md +6 -0
  35. package/src/bmm-skills/2-plan-workflows/bmad-create-ux-design/SKILL.md +70 -1
  36. package/src/bmm-skills/2-plan-workflows/bmad-create-ux-design/customize.toml +41 -0
  37. package/src/bmm-skills/2-plan-workflows/bmad-create-ux-design/steps/step-14-complete.md +6 -0
  38. package/src/bmm-skills/2-plan-workflows/bmad-edit-prd/SKILL.md +97 -1
  39. package/src/bmm-skills/2-plan-workflows/bmad-edit-prd/customize.toml +42 -0
  40. package/src/bmm-skills/2-plan-workflows/bmad-edit-prd/steps-e/step-e-04-complete.md +2 -0
  41. package/src/bmm-skills/2-plan-workflows/bmad-validate-prd/SKILL.md +99 -1
  42. package/src/bmm-skills/2-plan-workflows/bmad-validate-prd/customize.toml +42 -0
  43. package/src/bmm-skills/2-plan-workflows/bmad-validate-prd/steps-v/step-v-13-report-complete.md +1 -0
  44. package/src/bmm-skills/3-solutioning/bmad-agent-architect/SKILL.md +50 -30
  45. package/src/bmm-skills/3-solutioning/bmad-agent-architect/customize.toml +65 -0
  46. package/src/bmm-skills/3-solutioning/bmad-check-implementation-readiness/SKILL.md +86 -1
  47. package/src/bmm-skills/3-solutioning/bmad-check-implementation-readiness/customize.toml +41 -0
  48. package/src/bmm-skills/3-solutioning/bmad-check-implementation-readiness/steps/step-06-final-assessment.md +6 -0
  49. package/src/bmm-skills/3-solutioning/bmad-create-architecture/SKILL.md +69 -1
  50. package/src/bmm-skills/3-solutioning/bmad-create-architecture/customize.toml +41 -0
  51. package/src/bmm-skills/3-solutioning/bmad-create-architecture/steps/step-08-complete.md +6 -0
  52. package/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/SKILL.md +88 -1
  53. package/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/customize.toml +41 -0
  54. package/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/steps/step-04-final-validation.md +6 -0
  55. package/src/bmm-skills/3-solutioning/bmad-generate-project-context/SKILL.md +76 -1
  56. package/src/bmm-skills/3-solutioning/bmad-generate-project-context/customize.toml +41 -0
  57. package/src/bmm-skills/3-solutioning/bmad-generate-project-context/steps/step-03-complete.md +6 -0
  58. package/src/bmm-skills/4-implementation/bmad-agent-dev/SKILL.md +48 -43
  59. package/src/bmm-skills/4-implementation/bmad-agent-dev/customize.toml +90 -0
  60. package/src/bmm-skills/4-implementation/bmad-checkpoint-preview/SKILL.md +46 -7
  61. package/src/bmm-skills/4-implementation/bmad-checkpoint-preview/customize.toml +41 -0
  62. package/src/bmm-skills/4-implementation/bmad-checkpoint-preview/step-05-wrapup.md +6 -0
  63. package/src/bmm-skills/4-implementation/bmad-code-review/SKILL.md +85 -1
  64. package/src/bmm-skills/4-implementation/bmad-code-review/customize.toml +41 -0
  65. package/src/bmm-skills/4-implementation/bmad-code-review/steps/step-04-present.md +6 -0
  66. package/src/bmm-skills/4-implementation/bmad-correct-course/SKILL.md +296 -1
  67. package/src/bmm-skills/4-implementation/bmad-correct-course/customize.toml +41 -0
  68. package/src/bmm-skills/4-implementation/bmad-create-story/SKILL.md +424 -1
  69. package/src/bmm-skills/4-implementation/bmad-create-story/customize.toml +41 -0
  70. package/src/bmm-skills/4-implementation/bmad-dev-story/SKILL.md +480 -1
  71. package/src/bmm-skills/4-implementation/bmad-dev-story/customize.toml +41 -0
  72. package/src/bmm-skills/4-implementation/bmad-qa-generate-e2e-tests/SKILL.md +171 -1
  73. package/src/bmm-skills/4-implementation/bmad-qa-generate-e2e-tests/customize.toml +41 -0
  74. package/src/bmm-skills/4-implementation/bmad-quick-dev/SKILL.md +106 -1
  75. package/src/bmm-skills/4-implementation/bmad-quick-dev/customize.toml +41 -0
  76. package/src/bmm-skills/4-implementation/bmad-quick-dev/step-05-present.md +6 -0
  77. package/src/bmm-skills/4-implementation/bmad-quick-dev/step-oneshot.md +6 -0
  78. package/src/bmm-skills/4-implementation/bmad-retrospective/SKILL.md +1507 -1
  79. package/src/bmm-skills/4-implementation/bmad-retrospective/customize.toml +41 -0
  80. package/src/bmm-skills/4-implementation/bmad-sprint-planning/SKILL.md +294 -1
  81. package/src/bmm-skills/4-implementation/bmad-sprint-planning/customize.toml +41 -0
  82. package/src/bmm-skills/4-implementation/bmad-sprint-status/SKILL.md +292 -1
  83. package/src/bmm-skills/4-implementation/bmad-sprint-status/customize.toml +41 -0
  84. package/src/bmm-skills/module.yaml +49 -0
  85. package/src/core-skills/bmad-advanced-elicitation/SKILL.md +7 -1
  86. package/src/core-skills/bmad-customize/SKILL.md +111 -0
  87. package/src/core-skills/bmad-customize/scripts/list_customizable_skills.py +231 -0
  88. package/src/core-skills/bmad-customize/scripts/tests/test_list_customizable_skills.py +249 -0
  89. package/src/core-skills/bmad-distillator/resources/distillate-format-reference.md +1 -1
  90. package/src/core-skills/bmad-party-mode/SKILL.md +13 -10
  91. package/src/core-skills/module-help.csv +1 -0
  92. package/src/core-skills/module.yaml +2 -0
  93. package/src/scripts/resolve_config.py +176 -0
  94. package/src/scripts/resolve_customization.py +230 -0
  95. package/tools/installer/commands/install.js +13 -0
  96. package/tools/installer/core/config.js +4 -1
  97. package/tools/installer/core/install-paths.js +11 -5
  98. package/tools/installer/core/installer.js +181 -94
  99. package/tools/installer/core/manifest-generator.js +339 -184
  100. package/tools/installer/core/manifest.js +86 -86
  101. package/tools/installer/ide/platform-codes.yaml +6 -0
  102. package/tools/installer/modules/channel-plan.js +203 -0
  103. package/tools/installer/modules/channel-resolver.js +241 -0
  104. package/tools/installer/modules/community-manager.js +130 -23
  105. package/tools/installer/modules/custom-module-manager.js +160 -19
  106. package/tools/installer/modules/external-manager.js +235 -32
  107. package/tools/installer/modules/official-modules.js +58 -12
  108. package/tools/installer/modules/registry-client.js +139 -7
  109. package/tools/installer/modules/registry-fallback.yaml +8 -0
  110. package/tools/installer/modules/version-resolver.js +336 -0
  111. package/tools/installer/project-root.js +54 -0
  112. package/tools/installer/ui.js +561 -50
  113. package/tools/platform-codes.yaml +6 -0
  114. package/src/bmm-skills/1-analysis/bmad-agent-analyst/bmad-skill-manifest.yaml +0 -11
  115. package/src/bmm-skills/1-analysis/bmad-agent-tech-writer/bmad-skill-manifest.yaml +0 -11
  116. package/src/bmm-skills/1-analysis/bmad-document-project/workflow.md +0 -25
  117. package/src/bmm-skills/1-analysis/research/bmad-domain-research/workflow.md +0 -51
  118. package/src/bmm-skills/1-analysis/research/bmad-market-research/workflow.md +0 -51
  119. package/src/bmm-skills/1-analysis/research/bmad-technical-research/workflow.md +0 -52
  120. package/src/bmm-skills/2-plan-workflows/bmad-agent-pm/bmad-skill-manifest.yaml +0 -11
  121. package/src/bmm-skills/2-plan-workflows/bmad-agent-ux-designer/bmad-skill-manifest.yaml +0 -11
  122. package/src/bmm-skills/2-plan-workflows/bmad-create-prd/workflow.md +0 -61
  123. package/src/bmm-skills/2-plan-workflows/bmad-create-ux-design/workflow.md +0 -35
  124. package/src/bmm-skills/2-plan-workflows/bmad-edit-prd/workflow.md +0 -62
  125. package/src/bmm-skills/2-plan-workflows/bmad-validate-prd/workflow.md +0 -61
  126. package/src/bmm-skills/3-solutioning/bmad-agent-architect/bmad-skill-manifest.yaml +0 -11
  127. package/src/bmm-skills/3-solutioning/bmad-check-implementation-readiness/workflow.md +0 -47
  128. package/src/bmm-skills/3-solutioning/bmad-create-architecture/workflow.md +0 -32
  129. package/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/workflow.md +0 -51
  130. package/src/bmm-skills/3-solutioning/bmad-generate-project-context/workflow.md +0 -39
  131. package/src/bmm-skills/4-implementation/bmad-agent-dev/bmad-skill-manifest.yaml +0 -11
  132. package/src/bmm-skills/4-implementation/bmad-code-review/workflow.md +0 -55
  133. package/src/bmm-skills/4-implementation/bmad-correct-course/workflow.md +0 -267
  134. package/src/bmm-skills/4-implementation/bmad-create-story/workflow.md +0 -380
  135. package/src/bmm-skills/4-implementation/bmad-dev-story/workflow.md +0 -450
  136. package/src/bmm-skills/4-implementation/bmad-qa-generate-e2e-tests/workflow.md +0 -136
  137. package/src/bmm-skills/4-implementation/bmad-quick-dev/workflow.md +0 -76
  138. package/src/bmm-skills/4-implementation/bmad-retrospective/workflow.md +0 -1479
  139. package/src/bmm-skills/4-implementation/bmad-sprint-planning/workflow.md +0 -263
  140. 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 { getProjectRoot } = require('../project-root');
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 version = await this._readMarketplaceVersion(moduleName, moduleSourcePath);
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
- // External module: use moduleSourcePath if provided, otherwise fall back to cache
281
- const version = await this._readMarketplaceVersion(moduleName, moduleSourcePath);
299
+ const externalResolution = extMgr.getResolution(moduleName);
300
+ const versionInfo = await resolveModuleVersion(moduleName, { moduleSourcePath });
282
301
  return {
283
- version,
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 communityVersion = await this._readMarketplaceVersion(moduleName, moduleSourcePath);
318
+ const communityResolution = communityMgr.getResolution(moduleName);
319
+ const versionInfo = await resolveModuleVersion(moduleName, {
320
+ moduleSourcePath,
321
+ fallbackVersion: communityInfo.version,
322
+ });
296
323
  return {
297
- version: communityVersion || communityInfo.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 customVersion = resolved?.version || (await this._readMarketplaceVersion(moduleName, moduleSourcePath));
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: customVersion,
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 version = await this._readMarketplaceVersion(moduleName, moduleSourcePath);
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
- try {
386
- const https = require('node:https');
387
- const { execSync } = require('node:child_process');
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 result = execSync(`npm view ${packageName} version`, {
383
+ const { stdout } = await execFileAsync('npm', ['view', packageName, 'version'], {
392
384
  encoding: 'utf8',
393
- stdio: 'pipe',
394
- timeout: 10_000,
385
+ timeout: NPM_LOOKUP_TIMEOUT_MS,
395
386
  });
396
- return result.trim();
387
+ return stdout.trim();
397
388
  } catch {
398
389
  // Fallback to npm registry API
399
- return new Promise((resolve, reject) => {
400
- https
401
- .get(`https://registry.npmjs.org/${packageName}`, (res) => {
402
- let data = '';
403
- res.on('data', (chunk) => (data += chunk));
404
- res.on('end', () => {
405
- try {
406
- const pkg = JSON.parse(data);
407
- resolve(pkg['dist-tags']?.latest || pkg.version || null);
408
- } catch {
409
- resolve(null);
410
- }
411
- });
412
- })
413
- .on('error', () => resolve(null));
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
- if (module.version !== latestVersion) {
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,
@@ -114,6 +114,12 @@ platforms:
114
114
  - .kilocode/workflows
115
115
  target_dir: .kilocode/skills
116
116
 
117
+ kimi-code:
118
+ name: "Kimi Code"
119
+ preferred: false
120
+ installer:
121
+ target_dir: .kimi/skills
122
+
117
123
  kiro:
118
124
  name: "Kiro"
119
125
  preferred: false
@@ -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
+ };