ma-agents 3.17.0-beta.3 → 3.17.0-beta.4
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.
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"name": "ma-skills",
|
|
38
38
|
"source": "./",
|
|
39
39
|
"description": "ma-agents extension module providing enterprise SDLC personas and operational workflow skills.",
|
|
40
|
-
"version": "3.17.0-beta.
|
|
40
|
+
"version": "3.17.0-beta.4",
|
|
41
41
|
"author": {
|
|
42
42
|
"name": "Alon Mayaffit"
|
|
43
43
|
},
|
package/lib/bmad.js
CHANGED
|
@@ -1719,12 +1719,19 @@ async function installCustomMarketplaceSubset({ projectRoot, modules, tools, sel
|
|
|
1719
1719
|
let stagePath;
|
|
1720
1720
|
let customSourceArg;
|
|
1721
1721
|
let resolvedModules;
|
|
1722
|
+
// Registry mode is best-effort across entries: `installedCodes` is the subset
|
|
1723
|
+
// that actually resolved, `skipped` names the ones that did not (with reasons).
|
|
1724
|
+
// discovery/direct are atomic → installedCodes = the selection, no skips.
|
|
1725
|
+
let installedCodes = selection.selectedCodes;
|
|
1726
|
+
let skipped = [];
|
|
1722
1727
|
try {
|
|
1723
1728
|
if (selection.mode === 'registry') {
|
|
1724
1729
|
// Registry-index mode (this story): two-level clone+resolve+stage of
|
|
1725
1730
|
// each selected entry's own repository. Shares the same stage dir +
|
|
1726
1731
|
// install path as discovery/direct; only the staging step differs.
|
|
1727
|
-
|
|
1732
|
+
// Best-effort: a stale/broken entry is skipped (loudly), not fatal —
|
|
1733
|
+
// it does not block the entries that do resolve.
|
|
1734
|
+
({ stagePath, customSourceArg, resolvedModules, installedCodes, skipped } = await stageRegistryFn({
|
|
1728
1735
|
source: selection.source,
|
|
1729
1736
|
plugins: selection.plugins,
|
|
1730
1737
|
selectedCodes: selection.selectedCodes,
|
|
@@ -1747,6 +1754,11 @@ async function installCustomMarketplaceSubset({ projectRoot, modules, tools, sel
|
|
|
1747
1754
|
return false;
|
|
1748
1755
|
}
|
|
1749
1756
|
|
|
1757
|
+
// Defensive: a stager that predates the best-effort contract (or a partial
|
|
1758
|
+
// mock) may omit these — fall back to treating the whole selection as installed.
|
|
1759
|
+
if (!Array.isArray(installedCodes)) installedCodes = selection.selectedCodes;
|
|
1760
|
+
if (!Array.isArray(skipped)) skipped = [];
|
|
1761
|
+
|
|
1750
1762
|
const command = buildBmadArgs({
|
|
1751
1763
|
projectRoot,
|
|
1752
1764
|
modules,
|
|
@@ -1755,7 +1767,7 @@ async function installCustomMarketplaceSubset({ projectRoot, modules, tools, sel
|
|
|
1755
1767
|
customSource: customSourceArg,
|
|
1756
1768
|
});
|
|
1757
1769
|
|
|
1758
|
-
console.log(chalk.cyan(` Installing custom marketplace subset (${
|
|
1770
|
+
console.log(chalk.cyan(` Installing custom marketplace subset (${(installedCodes || []).join(', ')})...`));
|
|
1759
1771
|
console.log(chalk.gray(` Running: ${command}`));
|
|
1760
1772
|
|
|
1761
1773
|
try {
|
|
@@ -1768,6 +1780,27 @@ async function installCustomMarketplaceSubset({ projectRoot, modules, tools, sel
|
|
|
1768
1780
|
// for diagnostics on any post-invoke failure" policy (AC 8, 30.6b).
|
|
1769
1781
|
cleanupStageFn(stagePath);
|
|
1770
1782
|
|
|
1783
|
+
// LOUD best-effort report: any selected registry entry that could not be
|
|
1784
|
+
// installed (unreachable repo, stale approved_tag, no skills) is named
|
|
1785
|
+
// here with its reason — never silently omitted (FR238).
|
|
1786
|
+
if (skipped && skipped.length > 0) {
|
|
1787
|
+
console.log(
|
|
1788
|
+
chalk.yellow(
|
|
1789
|
+
`\n ${skipped.length} selected extension${skipped.length === 1 ? ' was' : 's were'} NOT installed ` +
|
|
1790
|
+
`(the rest installed fine):`
|
|
1791
|
+
)
|
|
1792
|
+
);
|
|
1793
|
+
for (const s of skipped) {
|
|
1794
|
+
console.log(chalk.yellow(` - ${s.code}: ${s.reason}`));
|
|
1795
|
+
}
|
|
1796
|
+
console.log(
|
|
1797
|
+
chalk.gray(
|
|
1798
|
+
' These are typically stale/broken registry entries (e.g. an approved_tag that no longer ' +
|
|
1799
|
+
'exists upstream). Report them to the marketplace maintainer, or re-run without them.'
|
|
1800
|
+
)
|
|
1801
|
+
);
|
|
1802
|
+
}
|
|
1803
|
+
|
|
1771
1804
|
// Story 30.7 (FR242) — persist source + selection (+ SHA pin) now that
|
|
1772
1805
|
// the subset is actually installed. `resolvedModules[].cloneSha` is
|
|
1773
1806
|
// only stamped for Git-URL sources (see custom-module-manager.js
|
|
@@ -1776,19 +1809,22 @@ async function installCustomMarketplaceSubset({ projectRoot, modules, tools, sel
|
|
|
1776
1809
|
// failure is logged as a warning only — it must never turn an
|
|
1777
1810
|
// otherwise-successful install into a reported failure.
|
|
1778
1811
|
try {
|
|
1812
|
+
// Persist only what ACTUALLY installed (`installedCodes`), not the
|
|
1813
|
+
// raw selection — otherwise a skipped/broken entry would be recorded
|
|
1814
|
+
// as installed and re-attempted (and re-reported) on every update.
|
|
1779
1815
|
const persistEntry = {
|
|
1780
1816
|
source: selection.source,
|
|
1781
1817
|
sourceUrl: selection.sourceUrl ?? null,
|
|
1782
1818
|
mode: selection.mode,
|
|
1783
|
-
selectedCodes:
|
|
1819
|
+
selectedCodes: installedCodes,
|
|
1784
1820
|
};
|
|
1785
1821
|
if (selection.mode === 'registry') {
|
|
1786
|
-
// AC5 — record each
|
|
1822
|
+
// AC5 — record each INSTALLED entry's registry-provided
|
|
1787
1823
|
// `approved_sha` (the immutable pin the registry declares), plus
|
|
1788
1824
|
// its `approved_tag`. `sha` (the top-level field) mirrors the
|
|
1789
1825
|
// first available approved_sha for back-compat with readers that
|
|
1790
1826
|
// only look at `sha`.
|
|
1791
|
-
const selectedSet = new Set(
|
|
1827
|
+
const selectedSet = new Set(installedCodes);
|
|
1792
1828
|
const registryEntries = (selection.plugins || [])
|
|
1793
1829
|
.filter((p) => selectedSet.has(p.code))
|
|
1794
1830
|
.map((p) => ({ code: p.code, approvedSha: p.approvedSha ?? null, approvedTag: p.approvedTag ?? null }));
|
|
@@ -941,30 +941,44 @@ async function stageRegistrySubset({ source, plugins, selectedCodes, projectRoot
|
|
|
941
941
|
seenCodes.add(p.code);
|
|
942
942
|
}
|
|
943
943
|
|
|
944
|
-
// Resolve
|
|
945
|
-
// stage dir
|
|
944
|
+
// Resolve each selected entry INDEPENDENTLY (each from its own clone) BEFORE
|
|
945
|
+
// touching the stage dir. A community registry legitimately contains
|
|
946
|
+
// individually-broken/stale entries — an unreachable repo, a nonexistent
|
|
947
|
+
// approved_tag (e.g. the marketplace pinning wds@v0.1.0 when that tag does
|
|
948
|
+
// not exist), or a module with no skills. One bad entry must NOT nuke the
|
|
949
|
+
// whole multi-select. Best-effort: keep the resolvable entries, collect the
|
|
950
|
+
// skipped ones (with reasons), install the good ones, and let the caller
|
|
951
|
+
// report the skips LOUDLY. FR238 = no SILENT partial install — NOT
|
|
952
|
+
// all-or-nothing.
|
|
946
953
|
const entries = [];
|
|
954
|
+
const skipped = [];
|
|
947
955
|
for (const plugin of selected) {
|
|
948
|
-
|
|
956
|
+
let entry;
|
|
957
|
+
try {
|
|
958
|
+
entry = await resolveRegistryEntry(plugin, resolveSourceFn, resolvePluginFn);
|
|
959
|
+
} catch (err) {
|
|
960
|
+
diag('custom-source: registry entry skipped (resolve failed)', { code: plugin.code, error: err.message });
|
|
961
|
+
skipped.push({ code: plugin.code, reason: err.message });
|
|
962
|
+
continue;
|
|
963
|
+
}
|
|
964
|
+
if (!entry.resolved || entry.resolved.length === 0) {
|
|
965
|
+
const reason =
|
|
966
|
+
'resolved to zero installable modules — no SKILL.md directories found ' +
|
|
967
|
+
'(stale registry pointer, changed repo layout, or a module with no skills)';
|
|
968
|
+
diag('custom-source: registry entry skipped (zero modules)', { code: plugin.code, repository: plugin.repository });
|
|
969
|
+
skipped.push({ code: plugin.code, reason });
|
|
970
|
+
continue;
|
|
971
|
+
}
|
|
972
|
+
entries.push(entry);
|
|
949
973
|
}
|
|
950
974
|
|
|
951
|
-
//
|
|
952
|
-
//
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
// fail-clean violation (FR238), not an acceptable default.
|
|
956
|
-
const emptyEntries = entries.filter(({ resolved }) => !resolved || resolved.length === 0);
|
|
957
|
-
if (emptyEntries.length > 0) {
|
|
958
|
-
const names = emptyEntries.map(({ plugin }) => plugin.code).join(', ');
|
|
959
|
-
diag('custom-source: registry entries resolved to zero modules', {
|
|
960
|
-
entries: emptyEntries.map(({ plugin }) => ({ code: plugin.code, repository: plugin.repository, moduleDefinition: plugin.moduleDefinition || null })),
|
|
961
|
-
});
|
|
975
|
+
// Hard-fail ONLY when nothing at all resolved — otherwise proceed with the
|
|
976
|
+
// resolvable subset and surface `skipped` to the caller for a loud report.
|
|
977
|
+
if (entries.length === 0) {
|
|
978
|
+
const detail = skipped.map((s) => `${s.code} (${s.reason})`).join('; ');
|
|
962
979
|
throw new CustomMarketplaceError(
|
|
963
|
-
`
|
|
964
|
-
`
|
|
965
|
-
`(a stale registry pointer, a changed repo layout, or a module with no skills). ` +
|
|
966
|
-
`Aborting before staging so the selection is not silently partially installed (FR238). ` +
|
|
967
|
-
`Re-run and deselect ${emptyEntries.length === 1 ? 'it' : 'those'}, or report the registry entry as broken.`
|
|
980
|
+
`None of the ${selected.length} selected registry ${selected.length === 1 ? 'entry' : 'entries'} could be installed — ${detail}. ` +
|
|
981
|
+
`Nothing was staged (FR238).`
|
|
968
982
|
);
|
|
969
983
|
}
|
|
970
984
|
|
|
@@ -1021,7 +1035,13 @@ async function stageRegistrySubset({ source, plugins, selectedCodes, projectRoot
|
|
|
1021
1035
|
);
|
|
1022
1036
|
|
|
1023
1037
|
const customSourceArg = `./${CUSTOM_MARKETPLACE_STAGE_DIR_NAME}`;
|
|
1024
|
-
return {
|
|
1038
|
+
return {
|
|
1039
|
+
stagePath,
|
|
1040
|
+
customSourceArg,
|
|
1041
|
+
resolvedModules: allModules,
|
|
1042
|
+
installedCodes: entries.map((e) => e.plugin.code),
|
|
1043
|
+
skipped,
|
|
1044
|
+
};
|
|
1025
1045
|
}
|
|
1026
1046
|
|
|
1027
1047
|
// ─── Story 30.7 — persist + re-enumerate/reconcile on update ───────────────
|
package/package.json
CHANGED