bmad-method 6.3.1-next.2 → 6.3.1-next.21

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 (160) hide show
  1. package/package.json +3 -3
  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-08-scoping.md +70 -23
  35. package/src/bmm-skills/2-plan-workflows/bmad-create-prd/steps-c/step-11-polish.md +1 -1
  36. package/src/bmm-skills/2-plan-workflows/bmad-create-prd/steps-c/step-12-complete.md +6 -0
  37. package/src/bmm-skills/2-plan-workflows/bmad-create-ux-design/SKILL.md +70 -1
  38. package/src/bmm-skills/2-plan-workflows/bmad-create-ux-design/customize.toml +41 -0
  39. package/src/bmm-skills/2-plan-workflows/bmad-create-ux-design/steps/step-14-complete.md +6 -0
  40. package/src/bmm-skills/2-plan-workflows/bmad-edit-prd/SKILL.md +97 -1
  41. package/src/bmm-skills/2-plan-workflows/bmad-edit-prd/customize.toml +42 -0
  42. package/src/bmm-skills/2-plan-workflows/bmad-edit-prd/steps-e/step-e-04-complete.md +2 -0
  43. package/src/bmm-skills/2-plan-workflows/bmad-validate-prd/SKILL.md +99 -1
  44. package/src/bmm-skills/2-plan-workflows/bmad-validate-prd/customize.toml +42 -0
  45. package/src/bmm-skills/2-plan-workflows/bmad-validate-prd/steps-v/step-v-13-report-complete.md +1 -0
  46. package/src/bmm-skills/3-solutioning/bmad-agent-architect/SKILL.md +50 -30
  47. package/src/bmm-skills/3-solutioning/bmad-agent-architect/customize.toml +65 -0
  48. package/src/bmm-skills/3-solutioning/bmad-check-implementation-readiness/SKILL.md +86 -1
  49. package/src/bmm-skills/3-solutioning/bmad-check-implementation-readiness/customize.toml +41 -0
  50. package/src/bmm-skills/3-solutioning/bmad-check-implementation-readiness/steps/step-06-final-assessment.md +6 -0
  51. package/src/bmm-skills/3-solutioning/bmad-create-architecture/SKILL.md +69 -1
  52. package/src/bmm-skills/3-solutioning/bmad-create-architecture/customize.toml +41 -0
  53. package/src/bmm-skills/3-solutioning/bmad-create-architecture/steps/step-08-complete.md +6 -0
  54. package/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/SKILL.md +88 -1
  55. package/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/customize.toml +41 -0
  56. package/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/steps/step-04-final-validation.md +6 -0
  57. package/src/bmm-skills/3-solutioning/bmad-generate-project-context/SKILL.md +76 -1
  58. package/src/bmm-skills/3-solutioning/bmad-generate-project-context/customize.toml +41 -0
  59. package/src/bmm-skills/3-solutioning/bmad-generate-project-context/steps/step-03-complete.md +6 -0
  60. package/src/bmm-skills/4-implementation/bmad-agent-dev/SKILL.md +48 -43
  61. package/src/bmm-skills/4-implementation/bmad-agent-dev/customize.toml +90 -0
  62. package/src/bmm-skills/4-implementation/bmad-correct-course/SKILL.md +296 -1
  63. package/src/bmm-skills/4-implementation/bmad-correct-course/customize.toml +41 -0
  64. package/src/bmm-skills/4-implementation/bmad-create-story/SKILL.md +412 -1
  65. package/src/bmm-skills/4-implementation/bmad-create-story/customize.toml +41 -0
  66. package/src/bmm-skills/4-implementation/bmad-qa-generate-e2e-tests/SKILL.md +171 -1
  67. package/src/bmm-skills/4-implementation/bmad-qa-generate-e2e-tests/customize.toml +41 -0
  68. package/src/bmm-skills/4-implementation/bmad-retrospective/SKILL.md +1507 -1
  69. package/src/bmm-skills/4-implementation/bmad-retrospective/customize.toml +41 -0
  70. package/src/bmm-skills/module.yaml +49 -0
  71. package/src/core-skills/bmad-advanced-elicitation/SKILL.md +7 -1
  72. package/src/core-skills/bmad-customize/SKILL.md +111 -0
  73. package/src/core-skills/bmad-customize/scripts/list_customizable_skills.py +231 -0
  74. package/src/core-skills/bmad-customize/scripts/tests/test_list_customizable_skills.py +249 -0
  75. package/src/core-skills/bmad-distillator/resources/distillate-format-reference.md +1 -1
  76. package/src/core-skills/bmad-party-mode/SKILL.md +13 -10
  77. package/src/core-skills/module-help.csv +1 -0
  78. package/src/core-skills/module.yaml +3 -0
  79. package/src/scripts/resolve_config.py +176 -0
  80. package/src/scripts/resolve_customization.py +230 -0
  81. package/tools/installer/cli-utils.js +0 -137
  82. package/tools/installer/commands/install.js +13 -0
  83. package/tools/installer/commands/status.js +1 -1
  84. package/tools/installer/commands/uninstall.js +1 -1
  85. package/tools/installer/core/config.js +4 -1
  86. package/tools/installer/core/existing-install.js +1 -1
  87. package/tools/installer/core/install-paths.js +12 -6
  88. package/tools/installer/core/installer.js +182 -95
  89. package/tools/installer/core/manifest-generator.js +347 -190
  90. package/tools/installer/core/manifest.js +49 -642
  91. package/tools/installer/file-ops.js +1 -1
  92. package/tools/installer/fs-native.js +116 -0
  93. package/tools/installer/ide/_config-driven.js +1 -1
  94. package/tools/installer/ide/platform-codes.js +1 -1
  95. package/tools/installer/ide/shared/path-utils.js +0 -145
  96. package/tools/installer/ide/shared/skill-manifest.js +1 -1
  97. package/tools/installer/message-loader.js +1 -1
  98. package/tools/installer/modules/channel-plan.js +203 -0
  99. package/tools/installer/modules/channel-resolver.js +241 -0
  100. package/tools/installer/modules/community-manager.js +131 -24
  101. package/tools/installer/modules/custom-module-manager.js +161 -47
  102. package/tools/installer/modules/external-manager.js +236 -73
  103. package/tools/installer/modules/official-modules.js +61 -63
  104. package/tools/installer/modules/plugin-resolver.js +1 -1
  105. package/tools/installer/modules/registry-client.js +133 -12
  106. package/tools/installer/modules/registry-fallback.yaml +8 -0
  107. package/tools/installer/modules/version-resolver.js +336 -0
  108. package/tools/installer/project-root.js +55 -1
  109. package/tools/installer/prompts.js +0 -106
  110. package/tools/installer/ui.js +457 -51
  111. package/tools/migrate-custom-module-paths.js +1 -1
  112. package/src/bmm-skills/1-analysis/bmad-agent-analyst/bmad-skill-manifest.yaml +0 -11
  113. package/src/bmm-skills/1-analysis/bmad-agent-tech-writer/bmad-skill-manifest.yaml +0 -11
  114. package/src/bmm-skills/1-analysis/bmad-document-project/workflow.md +0 -25
  115. package/src/bmm-skills/1-analysis/research/bmad-domain-research/workflow.md +0 -51
  116. package/src/bmm-skills/1-analysis/research/bmad-market-research/workflow.md +0 -51
  117. package/src/bmm-skills/1-analysis/research/bmad-technical-research/workflow.md +0 -52
  118. package/src/bmm-skills/2-plan-workflows/bmad-agent-pm/bmad-skill-manifest.yaml +0 -11
  119. package/src/bmm-skills/2-plan-workflows/bmad-agent-ux-designer/bmad-skill-manifest.yaml +0 -11
  120. package/src/bmm-skills/2-plan-workflows/bmad-create-prd/workflow.md +0 -61
  121. package/src/bmm-skills/2-plan-workflows/bmad-create-ux-design/workflow.md +0 -35
  122. package/src/bmm-skills/2-plan-workflows/bmad-edit-prd/workflow.md +0 -62
  123. package/src/bmm-skills/2-plan-workflows/bmad-validate-prd/workflow.md +0 -61
  124. package/src/bmm-skills/3-solutioning/bmad-agent-architect/bmad-skill-manifest.yaml +0 -11
  125. package/src/bmm-skills/3-solutioning/bmad-check-implementation-readiness/workflow.md +0 -47
  126. package/src/bmm-skills/3-solutioning/bmad-create-architecture/workflow.md +0 -32
  127. package/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/workflow.md +0 -51
  128. package/src/bmm-skills/3-solutioning/bmad-generate-project-context/workflow.md +0 -39
  129. package/src/bmm-skills/4-implementation/bmad-agent-dev/bmad-skill-manifest.yaml +0 -11
  130. package/src/bmm-skills/4-implementation/bmad-correct-course/workflow.md +0 -267
  131. package/src/bmm-skills/4-implementation/bmad-create-story/workflow.md +0 -380
  132. package/src/bmm-skills/4-implementation/bmad-qa-generate-e2e-tests/workflow.md +0 -136
  133. package/src/bmm-skills/4-implementation/bmad-retrospective/workflow.md +0 -1479
  134. package/tools/installer/ide/shared/agent-command-generator.js +0 -180
  135. package/tools/installer/ide/shared/bmad-artifacts.js +0 -208
  136. package/tools/installer/ide/shared/module-injections.js +0 -136
  137. package/tools/installer/ide/templates/agent-command-template.md +0 -14
  138. package/tools/installer/ide/templates/combined/antigravity.md +0 -8
  139. package/tools/installer/ide/templates/combined/default-agent.md +0 -15
  140. package/tools/installer/ide/templates/combined/default-task.md +0 -10
  141. package/tools/installer/ide/templates/combined/default-tool.md +0 -10
  142. package/tools/installer/ide/templates/combined/default-workflow.md +0 -6
  143. package/tools/installer/ide/templates/combined/gemini-agent.toml +0 -14
  144. package/tools/installer/ide/templates/combined/gemini-task.toml +0 -11
  145. package/tools/installer/ide/templates/combined/gemini-tool.toml +0 -11
  146. package/tools/installer/ide/templates/combined/gemini-workflow-yaml.toml +0 -16
  147. package/tools/installer/ide/templates/combined/gemini-workflow.toml +0 -14
  148. package/tools/installer/ide/templates/combined/kiro-agent.md +0 -16
  149. package/tools/installer/ide/templates/combined/kiro-task.md +0 -9
  150. package/tools/installer/ide/templates/combined/kiro-tool.md +0 -9
  151. package/tools/installer/ide/templates/combined/kiro-workflow.md +0 -7
  152. package/tools/installer/ide/templates/combined/opencode-agent.md +0 -15
  153. package/tools/installer/ide/templates/combined/opencode-task.md +0 -13
  154. package/tools/installer/ide/templates/combined/opencode-tool.md +0 -13
  155. package/tools/installer/ide/templates/combined/opencode-workflow-yaml.md +0 -16
  156. package/tools/installer/ide/templates/combined/opencode-workflow.md +0 -16
  157. package/tools/installer/ide/templates/combined/rovodev.md +0 -9
  158. package/tools/installer/ide/templates/combined/trae.md +0 -9
  159. package/tools/installer/ide/templates/combined/windsurf-workflow.md +0 -10
  160. package/tools/installer/ide/templates/split/.gitkeep +0 -0
@@ -1,7 +1,7 @@
1
1
  const path = require('node:path');
2
- const fs = require('fs-extra');
2
+ const fs = require('../fs-native');
3
3
  const crypto = require('node:crypto');
4
- const { getProjectRoot } = require('../project-root');
4
+ const { resolveModuleVersion } = require('../modules/version-resolver');
5
5
  const prompts = require('../prompts');
6
6
 
7
7
  class Manifest {
@@ -107,117 +107,6 @@ class Manifest {
107
107
  return null;
108
108
  }
109
109
 
110
- /**
111
- * Update existing manifest
112
- * @param {string} bmadDir - Path to bmad directory
113
- * @param {Object} updates - Fields to update
114
- * @param {Array} installedFiles - Updated list of installed files
115
- */
116
- async update(bmadDir, updates, installedFiles = null) {
117
- const yaml = require('yaml');
118
- const manifest = (await this._readRaw(bmadDir)) || {
119
- installation: {},
120
- modules: [],
121
- ides: [],
122
- };
123
-
124
- // Handle module updates
125
- if (updates.modules) {
126
- // If modules is being updated, we need to preserve detailed module info
127
- const existingDetailed = manifest.modules || [];
128
- const incomingNames = updates.modules;
129
-
130
- // Build updated modules array
131
- const updatedModules = [];
132
- for (const name of incomingNames) {
133
- const existing = existingDetailed.find((m) => m.name === name);
134
- if (existing) {
135
- // Preserve existing details, update lastUpdated if this module is being updated
136
- updatedModules.push({
137
- ...existing,
138
- lastUpdated: new Date().toISOString(),
139
- });
140
- } else {
141
- // New module - add with minimal details
142
- updatedModules.push({
143
- name,
144
- version: null,
145
- installDate: new Date().toISOString(),
146
- lastUpdated: new Date().toISOString(),
147
- source: 'unknown',
148
- });
149
- }
150
- }
151
-
152
- manifest.modules = updatedModules;
153
- }
154
-
155
- // Merge other updates
156
- if (updates.version) {
157
- manifest.installation.version = updates.version;
158
- }
159
- if (updates.installDate) {
160
- manifest.installation.installDate = updates.installDate;
161
- }
162
- manifest.installation.lastUpdated = new Date().toISOString();
163
-
164
- if (updates.ides) {
165
- manifest.ides = updates.ides;
166
- }
167
-
168
- // Handle per-module version updates
169
- if (updates.moduleVersions) {
170
- for (const [moduleName, versionInfo] of Object.entries(updates.moduleVersions)) {
171
- const moduleIndex = manifest.modules.findIndex((m) => m.name === moduleName);
172
- if (moduleIndex !== -1) {
173
- manifest.modules[moduleIndex] = {
174
- ...manifest.modules[moduleIndex],
175
- ...versionInfo,
176
- lastUpdated: new Date().toISOString(),
177
- };
178
- }
179
- }
180
- }
181
-
182
- // Handle adding a new module with version info
183
- if (updates.addModule) {
184
- const { name, version, source, npmPackage, repoUrl, localPath } = updates.addModule;
185
- const existing = manifest.modules.find((m) => m.name === name);
186
- if (!existing) {
187
- const entry = {
188
- name,
189
- version: version || null,
190
- installDate: new Date().toISOString(),
191
- lastUpdated: new Date().toISOString(),
192
- source: source || 'external',
193
- npmPackage: npmPackage || null,
194
- repoUrl: repoUrl || null,
195
- };
196
- if (localPath) entry.localPath = localPath;
197
- manifest.modules.push(entry);
198
- }
199
- }
200
-
201
- const manifestPath = path.join(bmadDir, '_config', 'manifest.yaml');
202
- await fs.ensureDir(path.dirname(manifestPath));
203
-
204
- // Clean the manifest data to remove any non-serializable values
205
- const cleanManifestData = structuredClone(manifest);
206
-
207
- const yamlContent = yaml.stringify(cleanManifestData, {
208
- indent: 2,
209
- lineWidth: 0,
210
- sortKeys: false,
211
- });
212
-
213
- // Ensure POSIX-compliant final newline
214
- const content = yamlContent.endsWith('\n') ? yamlContent : yamlContent + '\n';
215
- await fs.writeFile(manifestPath, content, 'utf8');
216
-
217
- // Return the flattened format for compatibility
218
- return this._flattenManifest(manifest);
219
- }
220
-
221
110
  /**
222
111
  * Read raw manifest data without flattening
223
112
  * @param {string} bmadDir - Path to bmad directory
@@ -291,7 +180,12 @@ class Manifest {
291
180
  npmPackage: options.npmPackage || null,
292
181
  repoUrl: options.repoUrl || null,
293
182
  };
183
+ if (options.channel) entry.channel = options.channel;
184
+ if (options.sha) entry.sha = options.sha;
294
185
  if (options.localPath) entry.localPath = options.localPath;
186
+ if (options.rawSource) entry.rawSource = options.rawSource;
187
+ if (options.registryApprovedTag) entry.registryApprovedTag = options.registryApprovedTag;
188
+ if (options.registryApprovedSha) entry.registryApprovedSha = options.registryApprovedSha;
295
189
  manifest.modules.push(entry);
296
190
  } else {
297
191
  // Module exists, update its version info
@@ -303,6 +197,11 @@ class Manifest {
303
197
  npmPackage: options.npmPackage === undefined ? existing.npmPackage : options.npmPackage,
304
198
  repoUrl: options.repoUrl === undefined ? existing.repoUrl : options.repoUrl,
305
199
  localPath: options.localPath === undefined ? existing.localPath : options.localPath,
200
+ channel: options.channel === undefined ? existing.channel : options.channel,
201
+ sha: options.sha === undefined ? existing.sha : options.sha,
202
+ rawSource: options.rawSource === undefined ? existing.rawSource : options.rawSource,
203
+ registryApprovedTag: options.registryApprovedTag === undefined ? existing.registryApprovedTag : options.registryApprovedTag,
204
+ registryApprovedSha: options.registryApprovedSha === undefined ? existing.registryApprovedSha : options.registryApprovedSha,
306
205
  lastUpdated: new Date().toISOString(),
307
206
  };
308
207
  }
@@ -310,62 +209,6 @@ class Manifest {
310
209
  await this._writeRaw(bmadDir, manifest);
311
210
  }
312
211
 
313
- /**
314
- * Remove a module from the manifest
315
- * @param {string} bmadDir - Path to bmad directory
316
- * @param {string} moduleName - Module name to remove
317
- */
318
- async removeModule(bmadDir, moduleName) {
319
- const manifest = await this._readRaw(bmadDir);
320
- if (!manifest || !manifest.modules) {
321
- return;
322
- }
323
-
324
- const index = manifest.modules.findIndex((m) => m.name === moduleName);
325
- if (index !== -1) {
326
- manifest.modules.splice(index, 1);
327
- await this._writeRaw(bmadDir, manifest);
328
- }
329
- }
330
-
331
- /**
332
- * Update a single module's version info
333
- * @param {string} bmadDir - Path to bmad directory
334
- * @param {string} moduleName - Module name
335
- * @param {Object} versionInfo - Version info to update
336
- */
337
- async updateModuleVersion(bmadDir, moduleName, versionInfo) {
338
- const manifest = await this._readRaw(bmadDir);
339
- if (!manifest || !manifest.modules) {
340
- return;
341
- }
342
-
343
- const index = manifest.modules.findIndex((m) => m.name === moduleName);
344
- if (index !== -1) {
345
- manifest.modules[index] = {
346
- ...manifest.modules[index],
347
- ...versionInfo,
348
- lastUpdated: new Date().toISOString(),
349
- };
350
- await this._writeRaw(bmadDir, manifest);
351
- }
352
- }
353
-
354
- /**
355
- * Get version info for a specific module
356
- * @param {string} bmadDir - Path to bmad directory
357
- * @param {string} moduleName - Module name
358
- * @returns {Object|null} Module version info or null
359
- */
360
- async getModuleVersion(bmadDir, moduleName) {
361
- const manifest = await this._readRaw(bmadDir);
362
- if (!manifest || !manifest.modules) {
363
- return null;
364
- }
365
-
366
- return manifest.modules.find((m) => m.name === moduleName) || null;
367
- }
368
-
369
212
  /**
370
213
  * Get all modules with their version info
371
214
  * @param {string} bmadDir - Path to bmad directory
@@ -403,27 +246,6 @@ class Manifest {
403
246
  await fs.writeFile(manifestPath, content, 'utf8');
404
247
  }
405
248
 
406
- /**
407
- * Add an IDE configuration to the manifest
408
- * @param {string} bmadDir - Path to bmad directory
409
- * @param {string} ideName - IDE name to add
410
- */
411
- async addIde(bmadDir, ideName) {
412
- const manifest = await this.read(bmadDir);
413
- if (!manifest) {
414
- throw new Error('No manifest found');
415
- }
416
-
417
- if (!manifest.ides) {
418
- manifest.ides = [];
419
- }
420
-
421
- if (!manifest.ides.includes(ideName)) {
422
- manifest.ides.push(ideName);
423
- await this.update(bmadDir, { ides: manifest.ides });
424
- }
425
- }
426
-
427
249
  /**
428
250
  * Calculate SHA256 hash of a file
429
251
  * @param {string} filePath - Path to file
@@ -438,354 +260,6 @@ class Manifest {
438
260
  }
439
261
  }
440
262
 
441
- /**
442
- * Parse installed files to extract metadata
443
- * @param {Array} installedFiles - List of installed file paths
444
- * @param {string} bmadDir - Path to bmad directory for relative paths
445
- * @returns {Array} Array of file metadata objects
446
- */
447
- async parseInstalledFiles(installedFiles, bmadDir) {
448
- const fileMetadata = [];
449
-
450
- for (const filePath of installedFiles) {
451
- const fileExt = path.extname(filePath).toLowerCase();
452
- // Make path relative to parent of bmad directory, starting with 'bmad/'
453
- const relativePath = 'bmad' + filePath.replace(bmadDir, '').replaceAll('\\', '/');
454
-
455
- // Calculate file hash
456
- const hash = await this.calculateFileHash(filePath);
457
-
458
- // Handle markdown files - extract XML metadata if present
459
- if (fileExt === '.md') {
460
- try {
461
- if (await fs.pathExists(filePath)) {
462
- const content = await fs.readFile(filePath, 'utf8');
463
- const metadata = this.extractXmlNodeAttributes(content, filePath, relativePath);
464
-
465
- if (metadata) {
466
- // Has XML metadata
467
- metadata.hash = hash;
468
- fileMetadata.push(metadata);
469
- } else {
470
- // No XML metadata - still track the file
471
- fileMetadata.push({
472
- file: relativePath,
473
- type: 'md',
474
- name: path.basename(filePath, fileExt),
475
- title: null,
476
- hash: hash,
477
- });
478
- }
479
- }
480
- } catch (error) {
481
- await prompts.log.warn(`Could not parse ${filePath}: ${error.message}`);
482
- }
483
- }
484
- // Handle other file types (CSV, JSON, YAML, etc.)
485
- else {
486
- fileMetadata.push({
487
- file: relativePath,
488
- type: fileExt.slice(1), // Remove the dot
489
- name: path.basename(filePath, fileExt),
490
- title: null,
491
- hash: hash,
492
- });
493
- }
494
- }
495
-
496
- return fileMetadata;
497
- }
498
-
499
- /**
500
- * Extract XML node attributes from MD file content
501
- * @param {string} content - File content
502
- * @param {string} filePath - File path for context
503
- * @param {string} relativePath - Relative path starting with 'bmad/'
504
- * @returns {Object|null} Extracted metadata or null
505
- */
506
- extractXmlNodeAttributes(content, filePath, relativePath) {
507
- // Look for XML blocks in code fences
508
- const xmlBlockMatch = content.match(/```xml\s*([\s\S]*?)```/);
509
- if (!xmlBlockMatch) {
510
- return null;
511
- }
512
-
513
- const xmlContent = xmlBlockMatch[1];
514
-
515
- // Extract root XML node (agent, task, template, etc.)
516
- const rootNodeMatch = xmlContent.match(/<(\w+)([^>]*)>/);
517
- if (!rootNodeMatch) {
518
- return null;
519
- }
520
-
521
- const nodeType = rootNodeMatch[1];
522
- const attributes = rootNodeMatch[2];
523
-
524
- // Extract name and title attributes (id not needed since we have path)
525
- const nameMatch = attributes.match(/name="([^"]*)"/);
526
- const titleMatch = attributes.match(/title="([^"]*)"/);
527
-
528
- return {
529
- file: relativePath,
530
- type: nodeType,
531
- name: nameMatch ? nameMatch[1] : null,
532
- title: titleMatch ? titleMatch[1] : null,
533
- };
534
- }
535
-
536
- /**
537
- * Generate CSV manifest content
538
- * @param {Object} data - Manifest data
539
- * @param {Array} fileMetadata - File metadata array
540
- * @param {Object} moduleConfigs - Module configuration data
541
- * @returns {string} CSV content
542
- */
543
- generateManifestCsv(data, fileMetadata, moduleConfigs = {}) {
544
- const timestamp = new Date().toISOString();
545
- let csv = [];
546
-
547
- // Header section
548
- csv.push(
549
- '# BMAD Manifest',
550
- `# Generated: ${timestamp}`,
551
- '',
552
- '## Installation Info',
553
- 'Property,Value',
554
- `Version,${data.version}`,
555
- `InstallDate,${data.installDate || timestamp}`,
556
- `LastUpdated,${data.lastUpdated || timestamp}`,
557
- );
558
- if (data.language) {
559
- csv.push(`Language,${data.language}`);
560
- }
561
- csv.push('');
562
-
563
- // Modules section
564
- if (data.modules && data.modules.length > 0) {
565
- csv.push('## Modules', 'Name,Version,ShortTitle');
566
- for (const moduleName of data.modules) {
567
- const config = moduleConfigs[moduleName] || {};
568
- csv.push([moduleName, config.version || '', config['short-title'] || ''].map((v) => this.escapeCsv(v)).join(','));
569
- }
570
- csv.push('');
571
- }
572
-
573
- // IDEs section
574
- if (data.ides && data.ides.length > 0) {
575
- csv.push('## IDEs', 'IDE');
576
- for (const ide of data.ides) {
577
- csv.push(this.escapeCsv(ide));
578
- }
579
- csv.push('');
580
- }
581
-
582
- // Files section - NO LONGER USED
583
- // Files are now tracked in files-manifest.csv by ManifestGenerator
584
-
585
- return csv.join('\n');
586
- }
587
-
588
- /**
589
- * Parse CSV manifest content back to object
590
- * @param {string} csvContent - CSV content to parse
591
- * @returns {Object} Parsed manifest data
592
- */
593
- parseManifestCsv(csvContent) {
594
- const result = {
595
- modules: [],
596
- ides: [],
597
- files: [],
598
- };
599
-
600
- const lines = csvContent.split('\n');
601
- let section = '';
602
-
603
- for (const line_ of lines) {
604
- const line = line_.trim();
605
-
606
- // Skip empty lines and comments
607
- if (!line || line.startsWith('#')) {
608
- // Check for section headers
609
- if (line.startsWith('## ')) {
610
- section = line.slice(3).toLowerCase();
611
- }
612
- continue;
613
- }
614
-
615
- // Parse based on current section
616
- switch (section) {
617
- case 'installation info': {
618
- // Skip header row
619
- if (line === 'Property,Value') continue;
620
-
621
- const [property, ...valueParts] = line.split(',');
622
- const value = this.unescapeCsv(valueParts.join(','));
623
-
624
- switch (property) {
625
- // Path no longer stored in manifest
626
- case 'Version': {
627
- result.version = value;
628
- break;
629
- }
630
- case 'InstallDate': {
631
- result.installDate = value;
632
- break;
633
- }
634
- case 'LastUpdated': {
635
- result.lastUpdated = value;
636
- break;
637
- }
638
- case 'Language': {
639
- result.language = value;
640
- break;
641
- }
642
- }
643
-
644
- break;
645
- }
646
- case 'modules': {
647
- // Skip header row
648
- if (line === 'Name,Version,ShortTitle') continue;
649
-
650
- const parts = this.parseCsvLine(line);
651
- if (parts[0]) {
652
- result.modules.push(parts[0]);
653
- }
654
-
655
- break;
656
- }
657
- case 'ides': {
658
- // Skip header row
659
- if (line === 'IDE') continue;
660
-
661
- result.ides.push(this.unescapeCsv(line));
662
-
663
- break;
664
- }
665
- case 'files': {
666
- // Skip header rows (support both old and new format)
667
- if (line === 'Type,Path,Name,Title' || line === 'Type,Path,Name,Title,Hash') continue;
668
-
669
- const parts = this.parseCsvLine(line);
670
- if (parts.length >= 2) {
671
- result.files.push({
672
- type: parts[0] || '',
673
- file: parts[1] || '',
674
- name: parts[2] || null,
675
- title: parts[3] || null,
676
- hash: parts[4] || null, // Hash column (may not exist in old manifests)
677
- });
678
- }
679
-
680
- break;
681
- }
682
- // No default
683
- }
684
- }
685
-
686
- return result;
687
- }
688
-
689
- /**
690
- * Parse a CSV line handling quotes and commas
691
- * @param {string} line - CSV line to parse
692
- * @returns {Array} Array of values
693
- */
694
- parseCsvLine(line) {
695
- const result = [];
696
- let current = '';
697
- let inQuotes = false;
698
-
699
- for (let i = 0; i < line.length; i++) {
700
- const char = line[i];
701
-
702
- if (char === '"') {
703
- if (inQuotes && line[i + 1] === '"') {
704
- // Escaped quote
705
- current += '"';
706
- i++;
707
- } else {
708
- // Toggle quote state
709
- inQuotes = !inQuotes;
710
- }
711
- } else if (char === ',' && !inQuotes) {
712
- // Field separator
713
- result.push(this.unescapeCsv(current));
714
- current = '';
715
- } else {
716
- current += char;
717
- }
718
- }
719
-
720
- // Add the last field
721
- result.push(this.unescapeCsv(current));
722
-
723
- return result;
724
- }
725
-
726
- /**
727
- * Escape CSV special characters
728
- * @param {string} text - Text to escape
729
- * @returns {string} Escaped text
730
- */
731
- escapeCsv(text) {
732
- if (!text) return '';
733
- const str = String(text);
734
-
735
- // If contains comma, newline, or quote, wrap in quotes and escape quotes
736
- if (str.includes(',') || str.includes('\n') || str.includes('"')) {
737
- return '"' + str.replaceAll('"', '""') + '"';
738
- }
739
-
740
- return str;
741
- }
742
-
743
- /**
744
- * Unescape CSV field
745
- * @param {string} text - Text to unescape
746
- * @returns {string} Unescaped text
747
- */
748
- unescapeCsv(text) {
749
- if (!text) return '';
750
-
751
- // Remove surrounding quotes if present
752
- if (text.startsWith('"') && text.endsWith('"')) {
753
- text = text.slice(1, -1);
754
- // Unescape doubled quotes
755
- text = text.replaceAll('""', '"');
756
- }
757
-
758
- return text;
759
- }
760
-
761
- /**
762
- * Load module configuration files
763
- * @param {Array} modules - List of module names
764
- * @returns {Object} Module configurations indexed by name
765
- */
766
- async loadModuleConfigs(modules) {
767
- const configs = {};
768
-
769
- for (const moduleName of modules) {
770
- // Handle core module differently - it's in src/core-skills not src/modules/core
771
- const configPath =
772
- moduleName === 'core'
773
- ? path.join(process.cwd(), 'src', 'core-skills', 'config.yaml')
774
- : path.join(process.cwd(), 'src', 'modules', moduleName, 'config.yaml');
775
-
776
- try {
777
- if (await fs.pathExists(configPath)) {
778
- const yaml = require('yaml');
779
- const content = await fs.readFile(configPath, 'utf8');
780
- configs[moduleName] = yaml.parse(content);
781
- }
782
- } catch (error) {
783
- await prompts.log.warn(`Could not load config for module ${moduleName}: ${error.message}`);
784
- }
785
- }
786
-
787
- return configs;
788
- }
789
263
  /**
790
264
  * Get module version info from source
791
265
  * @param {string} moduleName - Module name/code
@@ -794,13 +268,11 @@ class Manifest {
794
268
  * @returns {Object} Version info object with version, source, npmPackage, repoUrl
795
269
  */
796
270
  async getModuleVersionInfo(moduleName, bmadDir, moduleSourcePath = null) {
797
- const yaml = require('yaml');
798
-
799
271
  // Resolve source type first, then read version with the correct path context
800
272
  if (['core', 'bmm'].includes(moduleName)) {
801
- const version = await this._readMarketplaceVersion(moduleName, moduleSourcePath);
273
+ const versionInfo = await resolveModuleVersion(moduleName, { moduleSourcePath });
802
274
  return {
803
- version,
275
+ version: versionInfo.version,
804
276
  source: 'built-in',
805
277
  npmPackage: null,
806
278
  repoUrl: null,
@@ -813,13 +285,17 @@ class Manifest {
813
285
  const moduleInfo = await extMgr.getModuleByCode(moduleName);
814
286
 
815
287
  if (moduleInfo) {
816
- // External module: use moduleSourcePath if provided, otherwise fall back to cache
817
- const version = await this._readMarketplaceVersion(moduleName, moduleSourcePath);
288
+ const externalResolution = extMgr.getResolution(moduleName);
289
+ const versionInfo = await resolveModuleVersion(moduleName, { moduleSourcePath });
818
290
  return {
819
- version,
291
+ // Git tag recorded during install trumps the on-disk package.json
292
+ // version, so the manifest carries "v1.7.0" instead of "1.7.0".
293
+ version: externalResolution?.version || versionInfo.version,
820
294
  source: 'external',
821
295
  npmPackage: moduleInfo.npmPackage || null,
822
296
  repoUrl: moduleInfo.url || null,
297
+ channel: externalResolution?.channel || null,
298
+ sha: externalResolution?.sha || null,
823
299
  };
824
300
  }
825
301
 
@@ -828,12 +304,20 @@ class Manifest {
828
304
  const communityMgr = new CommunityModuleManager();
829
305
  const communityInfo = await communityMgr.getModuleByCode(moduleName);
830
306
  if (communityInfo) {
831
- const communityVersion = await this._readMarketplaceVersion(moduleName, moduleSourcePath);
307
+ const communityResolution = communityMgr.getResolution(moduleName);
308
+ const versionInfo = await resolveModuleVersion(moduleName, {
309
+ moduleSourcePath,
310
+ fallbackVersion: communityInfo.version,
311
+ });
832
312
  return {
833
- version: communityVersion || communityInfo.version,
313
+ version: communityResolution?.version || versionInfo.version || communityInfo.version,
834
314
  source: 'community',
835
315
  npmPackage: communityInfo.npmPackage || null,
836
316
  repoUrl: communityInfo.url || null,
317
+ channel: communityResolution?.channel || null,
318
+ sha: communityResolution?.sha || null,
319
+ registryApprovedTag: communityResolution?.registryApprovedTag || null,
320
+ registryApprovedSha: communityResolution?.registryApprovedSha || null,
837
321
  };
838
322
  }
839
323
 
@@ -843,75 +327,35 @@ class Manifest {
843
327
  const resolved = customMgr.getResolution(moduleName);
844
328
  const customSource = await customMgr.findModuleSourceByCode(moduleName, { bmadDir });
845
329
  if (customSource || resolved) {
846
- const customVersion = resolved?.version || (await this._readMarketplaceVersion(moduleName, moduleSourcePath));
330
+ const versionInfo = await resolveModuleVersion(moduleName, {
331
+ moduleSourcePath: moduleSourcePath || customSource,
332
+ fallbackVersion: resolved?.version,
333
+ marketplacePluginNames: resolved?.pluginName ? [resolved.pluginName] : [],
334
+ });
335
+ const hasGitClone = !!resolved?.repoUrl;
847
336
  return {
848
- version: customVersion,
337
+ // Prefer the git ref we actually cloned over the package.json version.
338
+ version: resolved?.cloneRef || (hasGitClone ? 'main' : versionInfo.version),
849
339
  source: 'custom',
850
340
  npmPackage: null,
851
341
  repoUrl: resolved?.repoUrl || null,
852
342
  localPath: resolved?.localPath || null,
343
+ channel: hasGitClone ? (resolved?.cloneRef ? 'pinned' : 'next') : null,
344
+ sha: resolved?.cloneSha || null,
345
+ rawSource: resolved?.rawInput || null,
853
346
  };
854
347
  }
855
348
 
856
349
  // Unknown module
857
- const version = await this._readMarketplaceVersion(moduleName, moduleSourcePath);
350
+ const versionInfo = await resolveModuleVersion(moduleName, { moduleSourcePath });
858
351
  return {
859
- version,
352
+ version: versionInfo.version,
860
353
  source: 'unknown',
861
354
  npmPackage: null,
862
355
  repoUrl: null,
863
356
  };
864
357
  }
865
358
 
866
- /**
867
- * Read version from .claude-plugin/marketplace.json for a module
868
- * @param {string} moduleName - Module code
869
- * @returns {string|null} Version or null
870
- */
871
- async _readMarketplaceVersion(moduleName, moduleSourcePath = null) {
872
- const os = require('node:os');
873
- let marketplacePath;
874
-
875
- if (['core', 'bmm'].includes(moduleName)) {
876
- marketplacePath = path.join(getProjectRoot(), '.claude-plugin', 'marketplace.json');
877
- } else if (moduleSourcePath) {
878
- // Walk up from source path to find marketplace.json
879
- let dir = moduleSourcePath;
880
- for (let i = 0; i < 5; i++) {
881
- const candidate = path.join(dir, '.claude-plugin', 'marketplace.json');
882
- if (await fs.pathExists(candidate)) {
883
- marketplacePath = candidate;
884
- break;
885
- }
886
- const parent = path.dirname(dir);
887
- if (parent === dir) break;
888
- dir = parent;
889
- }
890
- }
891
-
892
- // Fallback to external module cache
893
- if (!marketplacePath) {
894
- const cacheDir = path.join(os.homedir(), '.bmad', 'cache', 'external-modules', moduleName);
895
- marketplacePath = path.join(cacheDir, '.claude-plugin', 'marketplace.json');
896
- }
897
-
898
- try {
899
- if (await fs.pathExists(marketplacePath)) {
900
- const data = JSON.parse(await fs.readFile(marketplacePath, 'utf8'));
901
- const plugins = data?.plugins;
902
- if (!Array.isArray(plugins) || plugins.length === 0) return null;
903
- let best = null;
904
- for (const p of plugins) {
905
- if (p.version && (!best || p.version > best)) best = p.version;
906
- }
907
- return best;
908
- }
909
- } catch {
910
- // ignore
911
- }
912
- return null;
913
- }
914
-
915
359
  /**
916
360
  * Fetch latest version from npm for a package
917
361
  * @param {string} packageName - npm package name
@@ -960,6 +404,7 @@ class Manifest {
960
404
  * @returns {Array} Array of update info objects
961
405
  */
962
406
  async checkForUpdates(bmadDir) {
407
+ const semver = require('semver');
963
408
  const modules = await this.getAllModuleVersions(bmadDir);
964
409
  const updates = [];
965
410
 
@@ -973,7 +418,10 @@ class Manifest {
973
418
  continue;
974
419
  }
975
420
 
976
- if (module.version !== latestVersion) {
421
+ const installedVersion = semver.valid(module.version) || semver.valid(semver.coerce(module.version || ''));
422
+ const availableVersion = semver.valid(latestVersion) || semver.valid(semver.coerce(latestVersion));
423
+
424
+ if (installedVersion && availableVersion && semver.gt(availableVersion, installedVersion)) {
977
425
  updates.push({
978
426
  name: module.name,
979
427
  installedVersion: module.version,
@@ -986,47 +434,6 @@ class Manifest {
986
434
 
987
435
  return updates;
988
436
  }
989
-
990
- /**
991
- * Compare two semantic versions
992
- * @param {string} v1 - First version
993
- * @param {string} v2 - Second version
994
- * @returns {number} -1 if v1 < v2, 0 if v1 == v2, 1 if v1 > v2
995
- */
996
- compareVersions(v1, v2) {
997
- if (!v1 || !v2) return 0;
998
-
999
- const normalize = (v) => {
1000
- // Remove leading 'v' if present
1001
- v = v.replace(/^v/, '');
1002
- // Handle prerelease tags
1003
- const parts = v.split('-');
1004
- const main = parts[0].split('.');
1005
- const prerelease = parts[1];
1006
- return { main, prerelease };
1007
- };
1008
-
1009
- const n1 = normalize(v1);
1010
- const n2 = normalize(v2);
1011
-
1012
- // Compare main version parts
1013
- for (let i = 0; i < 3; i++) {
1014
- const num1 = parseInt(n1.main[i] || '0', 10);
1015
- const num2 = parseInt(n2.main[i] || '0', 10);
1016
- if (num1 !== num2) {
1017
- return num1 < num2 ? -1 : 1;
1018
- }
1019
- }
1020
-
1021
- // If main versions are equal, compare prerelease
1022
- if (n1.prerelease && n2.prerelease) {
1023
- return n1.prerelease < n2.prerelease ? -1 : n1.prerelease > n2.prerelease ? 1 : 0;
1024
- }
1025
- if (n1.prerelease) return -1; // Prerelease is older than stable
1026
- if (n2.prerelease) return 1; // Stable is newer than prerelease
1027
-
1028
- return 0;
1029
- }
1030
437
  }
1031
438
 
1032
439
  module.exports = { Manifest };