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.
- package/package.json +3 -3
- 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-08-scoping.md +70 -23
- package/src/bmm-skills/2-plan-workflows/bmad-create-prd/steps-c/step-11-polish.md +1 -1
- 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-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 +412 -1
- package/src/bmm-skills/4-implementation/bmad-create-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-retrospective/SKILL.md +1507 -1
- package/src/bmm-skills/4-implementation/bmad-retrospective/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 +3 -0
- package/src/scripts/resolve_config.py +176 -0
- package/src/scripts/resolve_customization.py +230 -0
- package/tools/installer/cli-utils.js +0 -137
- package/tools/installer/commands/install.js +13 -0
- package/tools/installer/commands/status.js +1 -1
- package/tools/installer/commands/uninstall.js +1 -1
- package/tools/installer/core/config.js +4 -1
- package/tools/installer/core/existing-install.js +1 -1
- package/tools/installer/core/install-paths.js +12 -6
- package/tools/installer/core/installer.js +182 -95
- package/tools/installer/core/manifest-generator.js +347 -190
- package/tools/installer/core/manifest.js +49 -642
- package/tools/installer/file-ops.js +1 -1
- package/tools/installer/fs-native.js +116 -0
- package/tools/installer/ide/_config-driven.js +1 -1
- package/tools/installer/ide/platform-codes.js +1 -1
- package/tools/installer/ide/shared/path-utils.js +0 -145
- package/tools/installer/ide/shared/skill-manifest.js +1 -1
- package/tools/installer/message-loader.js +1 -1
- 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 +131 -24
- package/tools/installer/modules/custom-module-manager.js +161 -47
- package/tools/installer/modules/external-manager.js +236 -73
- package/tools/installer/modules/official-modules.js +61 -63
- package/tools/installer/modules/plugin-resolver.js +1 -1
- package/tools/installer/modules/registry-client.js +133 -12
- package/tools/installer/modules/registry-fallback.yaml +8 -0
- package/tools/installer/modules/version-resolver.js +336 -0
- package/tools/installer/project-root.js +55 -1
- package/tools/installer/prompts.js +0 -106
- package/tools/installer/ui.js +457 -51
- package/tools/migrate-custom-module-paths.js +1 -1
- 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-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-qa-generate-e2e-tests/workflow.md +0 -136
- package/src/bmm-skills/4-implementation/bmad-retrospective/workflow.md +0 -1479
- package/tools/installer/ide/shared/agent-command-generator.js +0 -180
- package/tools/installer/ide/shared/bmad-artifacts.js +0 -208
- package/tools/installer/ide/shared/module-injections.js +0 -136
- package/tools/installer/ide/templates/agent-command-template.md +0 -14
- package/tools/installer/ide/templates/combined/antigravity.md +0 -8
- package/tools/installer/ide/templates/combined/default-agent.md +0 -15
- package/tools/installer/ide/templates/combined/default-task.md +0 -10
- package/tools/installer/ide/templates/combined/default-tool.md +0 -10
- package/tools/installer/ide/templates/combined/default-workflow.md +0 -6
- package/tools/installer/ide/templates/combined/gemini-agent.toml +0 -14
- package/tools/installer/ide/templates/combined/gemini-task.toml +0 -11
- package/tools/installer/ide/templates/combined/gemini-tool.toml +0 -11
- package/tools/installer/ide/templates/combined/gemini-workflow-yaml.toml +0 -16
- package/tools/installer/ide/templates/combined/gemini-workflow.toml +0 -14
- package/tools/installer/ide/templates/combined/kiro-agent.md +0 -16
- package/tools/installer/ide/templates/combined/kiro-task.md +0 -9
- package/tools/installer/ide/templates/combined/kiro-tool.md +0 -9
- package/tools/installer/ide/templates/combined/kiro-workflow.md +0 -7
- package/tools/installer/ide/templates/combined/opencode-agent.md +0 -15
- package/tools/installer/ide/templates/combined/opencode-task.md +0 -13
- package/tools/installer/ide/templates/combined/opencode-tool.md +0 -13
- package/tools/installer/ide/templates/combined/opencode-workflow-yaml.md +0 -16
- package/tools/installer/ide/templates/combined/opencode-workflow.md +0 -16
- package/tools/installer/ide/templates/combined/rovodev.md +0 -9
- package/tools/installer/ide/templates/combined/trae.md +0 -9
- package/tools/installer/ide/templates/combined/windsurf-workflow.md +0 -10
- 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-
|
|
2
|
+
const fs = require('../fs-native');
|
|
3
3
|
const crypto = require('node:crypto');
|
|
4
|
-
const {
|
|
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
|
|
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
|
-
|
|
817
|
-
const
|
|
288
|
+
const externalResolution = extMgr.getResolution(moduleName);
|
|
289
|
+
const versionInfo = await resolveModuleVersion(moduleName, { moduleSourcePath });
|
|
818
290
|
return {
|
|
819
|
-
|
|
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
|
|
307
|
+
const communityResolution = communityMgr.getResolution(moduleName);
|
|
308
|
+
const versionInfo = await resolveModuleVersion(moduleName, {
|
|
309
|
+
moduleSourcePath,
|
|
310
|
+
fallbackVersion: communityInfo.version,
|
|
311
|
+
});
|
|
832
312
|
return {
|
|
833
|
-
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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 };
|