ma-agents 3.18.0 → 3.18.1-beta.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 (60) hide show
  1. package/bin/cli.js +4 -2
  2. package/lib/bmad-cache/cache-manifest.json +3 -0
  3. package/lib/bmad-extension/.claude-plugin/marketplace.json.template +1 -0
  4. package/lib/bmad-extension/skills/add-sprint/SKILL.md +12 -0
  5. package/lib/bmad-extension/skills/add-to-sprint/SKILL.md +12 -0
  6. package/lib/bmad-extension/skills/bmad-dev-story/workflow.md +12 -0
  7. package/lib/bmad-extension/skills/bmad-sprint-planning/workflow.md +12 -0
  8. package/lib/bmad-extension/skills/bmad-sprint-status/workflow.md +12 -0
  9. package/lib/bmad-extension/skills/cleanup-done/SKILL.md +12 -0
  10. package/lib/bmad-extension/skills/close-sprint/SKILL.md +12 -0
  11. package/lib/bmad-extension/skills/generate-backlog/SKILL.md +12 -0
  12. package/lib/bmad-extension/skills/modify-sprint/SKILL.md +12 -0
  13. package/lib/bmad-extension/skills/module-help.csv +1 -0
  14. package/lib/bmad-extension/skills/module.yaml +1 -1
  15. package/lib/bmad-extension/skills/my-next-issue/.gitkeep +0 -0
  16. package/lib/bmad-extension/skills/my-next-issue/SKILL.md +195 -0
  17. package/lib/bmad-extension/skills/my-next-issue/bmad-skill-manifest.yaml +3 -0
  18. package/lib/bmad-extension/skills/prioritize-backlog/SKILL.md +12 -0
  19. package/lib/bmad-extension/skills/remove-from-sprint/SKILL.md +12 -0
  20. package/lib/bmad-extension/skills/sprint-status-view/SKILL.md +12 -0
  21. package/lib/bmad-extension/workflows/add-sprint/workflow.md +12 -0
  22. package/lib/bmad-extension/workflows/add-to-sprint/workflow.md +12 -0
  23. package/lib/bmad-extension/workflows/modify-sprint/workflow.md +12 -0
  24. package/lib/bmad-extension/workflows/sprint-status-view/workflow.md +12 -0
  25. package/lib/bmad-extension-plugin/.claude-plugin/marketplace.json +2 -1
  26. package/lib/bmad-extension-plugin/skills/add-sprint/SKILL.md +12 -0
  27. package/lib/bmad-extension-plugin/skills/add-to-sprint/SKILL.md +12 -0
  28. package/lib/bmad-extension-plugin/skills/bmad-dev-story/workflow.md +12 -0
  29. package/lib/bmad-extension-plugin/skills/bmad-sprint-planning/workflow.md +12 -0
  30. package/lib/bmad-extension-plugin/skills/bmad-sprint-status/workflow.md +12 -0
  31. package/lib/bmad-extension-plugin/skills/cleanup-done/SKILL.md +12 -0
  32. package/lib/bmad-extension-plugin/skills/close-sprint/SKILL.md +12 -0
  33. package/lib/bmad-extension-plugin/skills/generate-backlog/SKILL.md +12 -0
  34. package/lib/bmad-extension-plugin/skills/modify-sprint/SKILL.md +12 -0
  35. package/lib/bmad-extension-plugin/skills/module-help.csv +1 -0
  36. package/lib/bmad-extension-plugin/skills/module.yaml +1 -1
  37. package/lib/bmad-extension-plugin/skills/my-next-issue/.gitkeep +0 -0
  38. package/lib/bmad-extension-plugin/skills/my-next-issue/SKILL.md +195 -0
  39. package/lib/bmad-extension-plugin/skills/my-next-issue/bmad-skill-manifest.yaml +3 -0
  40. package/lib/bmad-extension-plugin/skills/prioritize-backlog/SKILL.md +12 -0
  41. package/lib/bmad-extension-plugin/skills/remove-from-sprint/SKILL.md +12 -0
  42. package/lib/bmad-extension-plugin/skills/sprint-status-view/SKILL.md +12 -0
  43. package/lib/bmad.js +125 -6
  44. package/lib/knowledge-atlas/dist/atlas.cjs +14651 -14651
  45. package/package.json +2 -2
  46. package/scripts/build-bmad-cache.js +17 -0
  47. package/skills/add-sprint/SKILL.md +12 -0
  48. package/skills/add-to-sprint/SKILL.md +12 -0
  49. package/skills/bmad-sprint-planning/SKILL.md +12 -0
  50. package/skills/bmad-sprint-status/SKILL.md +12 -0
  51. package/skills/cleanup-done/SKILL.md +12 -0
  52. package/skills/close-sprint/SKILL.md +12 -0
  53. package/skills/generate-backlog/SKILL.md +12 -0
  54. package/skills/modify-sprint/SKILL.md +12 -0
  55. package/skills/my-next-issue/SKILL.md +184 -0
  56. package/skills/my-next-issue/skill.json +7 -0
  57. package/skills/prioritize-backlog/SKILL.md +12 -0
  58. package/skills/remove-from-sprint/SKILL.md +12 -0
  59. package/skills/sprint-status-view/SKILL.md +12 -0
  60. package/skills/story-status-lookup/SKILL.md +12 -0
package/lib/bmad.js CHANGED
@@ -626,6 +626,46 @@ function getInstallableBmadModules() {
626
626
  return [bmmEntry, ...cacheEntries];
627
627
  }
628
628
 
629
+ /**
630
+ * Return the upstream release tag each bundled external module is pinned to,
631
+ * read from the `pinTag` field of `lib/bmad-cache/cache-manifest.json`.
632
+ *
633
+ * Why this exists: `bmad-method` itself is pinned exactly (package.json), but
634
+ * left to its defaults it resolves every external module to that module's
635
+ * LATEST stable tag at install time. An upstream release cut after ma-agents
636
+ * shipped then lands on users' machines untested — TEA v2.0.x dropped
637
+ * `src/module.yaml`, which bmad-method 6.10.0 requires, and broke every
638
+ * install that selected tea. Pinning to the tag the bundled cache was built
639
+ * from makes an online install resolve to the same release an offline one
640
+ * does.
641
+ *
642
+ * Entries without a `pinTag` are omitted (and so float, as before);
643
+ * test/bundled-module-pins.test.js fails the build when a bundled module
644
+ * ships without one.
645
+ *
646
+ * @param {string} [manifestPath] - Override the manifest path (tests only)
647
+ * @returns {Object<string, string>} module id → release tag (e.g. `{ tea: 'v1.19.0' }`);
648
+ * empty when the manifest is missing or unreadable
649
+ */
650
+ function getBundledModulePins(manifestPath = path.join(__dirname, 'bmad-cache', 'cache-manifest.json')) {
651
+ let manifest;
652
+ try {
653
+ manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
654
+ } catch {
655
+ return {};
656
+ }
657
+ const pins = {};
658
+ for (const [id, meta] of Object.entries(manifest.modules || {})) {
659
+ const tag = meta && typeof meta.pinTag === 'string' ? meta.pinTag.trim() : '';
660
+ // The tag is interpolated into a shell command (buildBmadArgs), so
661
+ // accept only plain git-tag characters.
662
+ if (/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(tag)) {
663
+ pins[id] = tag;
664
+ }
665
+ }
666
+ return pins;
667
+ }
668
+
629
669
  /**
630
670
  * Detect which BMad modules are already installed under a given project
631
671
  * root by checking for `_bmad/<id>/` directories. Used by the install
@@ -830,6 +870,10 @@ function resolveCustomContentFlag() {
830
870
  * `--custom-source <path>`; when omitted no extension flag is added.
831
871
  * The legacy fallback to `lib/bmad-extension/` was removed when the
832
872
  * stage-and-invoke flow replaced applyCustomizations().
873
+ * @param {Object<string, string>} [ctx.pins] - module id → release tag to pass
874
+ * as `--pin <id>=<tag>`. Defaults to getBundledModulePins(). Only modules in
875
+ * `ctx.modules` are pinned (bmad-method warns on a pin for an unselected
876
+ * module).
833
877
  * @returns {string} Complete command string ready for execSync
834
878
  */
835
879
  function buildBmadArgs(ctx) {
@@ -868,6 +912,16 @@ function buildBmadArgs(ctx) {
868
912
  parts.push(flag, `"${ctx.customSource}"`);
869
913
  }
870
914
 
915
+ // Pin each selected external module to the release the bundled cache was
916
+ // built from, so upstream releases cut after this ma-agents version can't
917
+ // reach the install (see getBundledModulePins).
918
+ const pins = ctx.pins || getBundledModulePins();
919
+ for (const mod of ctx.modules) {
920
+ if (pins[mod]) {
921
+ parts.push('--pin', `${mod}=${pins[mod]}`);
922
+ }
923
+ }
924
+
871
925
  if (ctx.action === 'update') {
872
926
  parts.push('--action', 'update');
873
927
  }
@@ -2118,12 +2172,20 @@ async function installCustomMarketplaceSubset({ projectRoot, modules, tools, sel
2118
2172
  * `stable` because that's bmad-method's own default when a registry
2119
2173
  * entry has no `default_channel` field.
2120
2174
  *
2175
+ * When the manifest entry carries a `pinTag`, the marker is written on the
2176
+ * `pinned` channel at that tag instead, matching the `--pin` buildBmadArgs
2177
+ * passes. bmad-method compares the marker's channel to the requested one and
2178
+ * wipes + re-clones the cache on a mismatch, which an air-gapped host cannot
2179
+ * survive. A local tag ref is also created so bmad-method's
2180
+ * `git fetch origin tag <pinTag>` (origin = the cache itself, see
2181
+ * restoreGitDir) resolves without the network.
2182
+ *
2121
2183
  * Best-effort: failures are swallowed. The next install attempt (online or
2122
2184
  * offline-with-marker) will write a fresh one.
2123
2185
  *
2124
2186
  * @param {string} moduleDir - Cache dir for the module
2125
2187
  * (e.g. `~/.bmad/cache/external-modules/tea`)
2126
- * @param {{ branch?: string, commitSha?: string }} [manifestEntry] - Entry
2188
+ * @param {{ branch?: string, commitSha?: string, pinTag?: string }} [manifestEntry] - Entry
2127
2189
  * from `lib/bmad-cache/cache-manifest.json` for this module
2128
2190
  * @returns {Promise<boolean>} `true` if the marker was written, `false` if
2129
2191
  * it already existed or the manifest entry is missing required fields
@@ -2132,13 +2194,15 @@ async function ensureCacheMarker(moduleDir, manifestEntry) {
2132
2194
  const markerPath = path.join(moduleDir, '.bmad-channel.json');
2133
2195
  if (await fs.pathExists(markerPath)) return false;
2134
2196
  if (!manifestEntry || !manifestEntry.commitSha) return false;
2197
+ const pinTag = manifestEntry.pinTag;
2135
2198
  try {
2199
+ if (pinTag) ensureLocalTag(moduleDir, pinTag);
2136
2200
  await fs.writeFile(
2137
2201
  markerPath,
2138
2202
  JSON.stringify(
2139
2203
  {
2140
- channel: 'stable',
2141
- version: manifestEntry.branch || 'main',
2204
+ channel: pinTag ? 'pinned' : 'stable',
2205
+ version: pinTag || manifestEntry.branch || 'main',
2142
2206
  sha: manifestEntry.commitSha,
2143
2207
  writtenAt: new Date().toISOString(),
2144
2208
  },
@@ -2154,6 +2218,53 @@ async function ensureCacheMarker(moduleDir, manifestEntry) {
2154
2218
  }
2155
2219
  }
2156
2220
 
2221
+ /**
2222
+ * Create tag `tag` at HEAD in a cached module's git repo unless it already
2223
+ * exists. The bundled cache is a shallow clone of the commit the pin tag
2224
+ * names (or a metadata-only commit past it), and the clone only carries the
2225
+ * tag when it pointed at HEAD exactly.
2226
+ *
2227
+ * Best-effort: no `.git/` or no `git` binary is a silent no-op — bmad-method
2228
+ * then falls back to cloning the tag from upstream.
2229
+ *
2230
+ * @param {string} moduleDir - Cached module directory
2231
+ * @param {string} tag - Release tag (e.g. `v2.1.0`)
2232
+ */
2233
+ function ensureLocalTag(moduleDir, tag) {
2234
+ if (!fs.existsSync(path.join(moduleDir, '.git'))) return;
2235
+ try {
2236
+ execFileSync('git', ['rev-parse', '--quiet', '--verify', `refs/tags/${tag}`], { cwd: moduleDir, stdio: 'pipe' });
2237
+ } catch {
2238
+ try {
2239
+ execFileSync('git', ['tag', tag, 'HEAD'], { cwd: moduleDir, stdio: 'pipe' });
2240
+ } catch {
2241
+ // Non-fatal — see above.
2242
+ }
2243
+ }
2244
+ }
2245
+
2246
+ /**
2247
+ * True when a cached module's `.bmad-channel.json` records a resolution other
2248
+ * than the pin the manifest asks for — e.g. a cache bmad-method cloned at a
2249
+ * newer upstream release before pinning existed. Such a cache must be
2250
+ * replaced from the bundle: left alone, bmad-method would wipe it to switch
2251
+ * channels and need the network to re-clone.
2252
+ *
2253
+ * @param {string} moduleDir - Cached module directory
2254
+ * @param {{ pinTag?: string }} [manifestEntry] - Manifest entry for the module
2255
+ * @returns {Promise<boolean>} `false` when the entry has no pinTag
2256
+ */
2257
+ async function cacheDiffersFromPin(moduleDir, manifestEntry) {
2258
+ if (!manifestEntry || !manifestEntry.pinTag) return false;
2259
+ try {
2260
+ const marker = JSON.parse(await fs.readFile(path.join(moduleDir, '.bmad-channel.json'), 'utf8'));
2261
+ return marker.channel !== 'pinned' || marker.version !== manifestEntry.pinTag;
2262
+ } catch {
2263
+ // No marker or unreadable — we can't tell what this cache holds.
2264
+ return true;
2265
+ }
2266
+ }
2267
+
2157
2268
  async function prePopulateBmadCache(force = false) {
2158
2269
  const cacheSource = path.join(__dirname, 'bmad-cache');
2159
2270
  if (!(await fs.pathExists(cacheSource))) {
@@ -2195,12 +2306,17 @@ async function prePopulateBmadCache(force = false) {
2195
2306
  && (await fs.pathExists(path.join(src, 'skills')))
2196
2307
  && !(await fs.pathExists(path.join(dst, 'skills')));
2197
2308
 
2309
+ const manifestEntry = manifest && manifest.modules ? manifest.modules[mod] : null;
2310
+ const offPin = (await fs.pathExists(dst)) && (await cacheDiffersFromPin(dst, manifestEntry));
2311
+
2198
2312
  if (await fs.pathExists(dst)) {
2199
- if (force || structurallyBroken) {
2313
+ if (force || structurallyBroken || offPin) {
2200
2314
  await fs.remove(dst);
2201
2315
  await fs.copy(src, dst);
2202
2316
  restoreGitDir(dst);
2203
- const reason = structurallyBroken ? '(structural repair)' : '';
2317
+ const reason = structurallyBroken
2318
+ ? '(structural repair)'
2319
+ : offPin ? `(not at pinned ${manifestEntry.pinTag})` : '';
2204
2320
  console.log(chalk.cyan(` + Replaced cached module: ${mod} ${reason}`));
2205
2321
  replaced++;
2206
2322
  } else {
@@ -2218,7 +2334,7 @@ async function prePopulateBmadCache(force = false) {
2218
2334
  // skip path, since prior ma-agents versions copied the module
2219
2335
  // without the marker and left users wedged on offline installs.
2220
2336
  // ensureCacheMarker no-ops when the marker already exists.
2221
- const wrote = await ensureCacheMarker(dst, manifest && manifest.modules ? manifest.modules[mod] : null);
2337
+ const wrote = await ensureCacheMarker(dst, manifestEntry);
2222
2338
  if (wrote) markersWritten++;
2223
2339
  }
2224
2340
 
@@ -3747,4 +3863,7 @@ module.exports = {
3747
3863
  getDefaultBmadModules,
3748
3864
  // 3.12.2 — offline-fallback marker for bmad-method's cloneExternalModule
3749
3865
  ensureCacheMarker,
3866
+ // Bug: external modules pinned to the bundled release (--pin)
3867
+ getBundledModulePins,
3868
+ cacheDiffersFromPin,
3750
3869
  };