ma-agents 3.17.0-beta.1 → 3.17.0-beta.3
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/bin/cli.js +38 -2
- package/lib/bmad-extension-plugin/.claude-plugin/marketplace.json +1 -1
- package/lib/custom-marketplace.js +124 -11
- package/lib/diag.js +86 -0
- package/package.json +2 -2
package/bin/cli.js
CHANGED
|
@@ -40,6 +40,7 @@ const { uninstallProfileArtifacts } = require('../lib/uninstall');
|
|
|
40
40
|
const bmad = require('../lib/bmad');
|
|
41
41
|
const { getBmadPlatformCode } = require('../lib/agents');
|
|
42
42
|
const { enumerateCustomSource, NFR71_WARNING, CustomMarketplaceError, reconcileCustomMarketplaceSelection, registryTrustLabel } = require('../lib/custom-marketplace');
|
|
43
|
+
const { setLogSink, diag, diagError } = require('../lib/diag');
|
|
43
44
|
const { handleCreateSkill, handleValidateSkill, handleSetMandatory, handleCustomizeAgent, handleCreateAgent } = require('../lib/skill-authoring');
|
|
44
45
|
|
|
45
46
|
const PKG = require('../package.json');
|
|
@@ -55,9 +56,21 @@ const VERSION = PKG.version;
|
|
|
55
56
|
*/
|
|
56
57
|
function setupLogging(logFilePath) {
|
|
57
58
|
const logFd = fs.openSync(logFilePath, 'w');
|
|
58
|
-
|
|
59
|
+
// Rich header — the environment facts needed to make sense of a report later:
|
|
60
|
+
// exact version, how it was invoked, runtime, platform, and where it ran.
|
|
61
|
+
const header =
|
|
62
|
+
`[${new Date().toISOString()}] ${NAME} v${VERSION} — log started\n` +
|
|
63
|
+
` argv: ${process.argv.slice(1).join(' ')}\n` +
|
|
64
|
+
` node: ${process.version}\n` +
|
|
65
|
+
` platform: ${process.platform} ${process.arch}\n` +
|
|
66
|
+
` cwd: ${process.cwd()}\n`;
|
|
59
67
|
fs.writeSync(logFd, header);
|
|
60
68
|
|
|
69
|
+
// Route log-file-only diagnostics (lib/diag.js) to this fd. These breadcrumbs
|
|
70
|
+
// (custom-marketplace phases, swallowed error causes, etc.) land ONLY in the
|
|
71
|
+
// log, never on the user's console.
|
|
72
|
+
setLogSink((s) => { try { fs.writeSync(logFd, s); } catch { /* ignore */ } });
|
|
73
|
+
|
|
61
74
|
const origStdoutWrite = process.stdout.write.bind(process.stdout);
|
|
62
75
|
const origStderrWrite = process.stderr.write.bind(process.stderr);
|
|
63
76
|
|
|
@@ -81,6 +94,7 @@ function setupLogging(logFilePath) {
|
|
|
81
94
|
process.stderr.write = origStderrWrite;
|
|
82
95
|
const footer = `\n[${new Date().toISOString()}] log ended\n`;
|
|
83
96
|
try { fs.writeSync(logFd, footer); } catch { /* ignore */ }
|
|
97
|
+
setLogSink(null);
|
|
84
98
|
try { fs.closeSync(logFd); } catch { /* ignore */ }
|
|
85
99
|
delete process.env.MA_AGENTS_LOG_ACTIVE;
|
|
86
100
|
};
|
|
@@ -1025,13 +1039,16 @@ async function promptCustomMarketplace({ bmadCustomSourceFlag = '', yesFlag = fa
|
|
|
1025
1039
|
}
|
|
1026
1040
|
|
|
1027
1041
|
let enumerated;
|
|
1042
|
+
diag('custom-source: enumerate begin', { source });
|
|
1028
1043
|
try {
|
|
1029
1044
|
enumerated = await enumerateCustomSource(source);
|
|
1030
1045
|
} catch (err) {
|
|
1031
1046
|
// Fail clean (spike §3 contract) — a short, human-readable message only.
|
|
1032
1047
|
// Never print err.stack here: an unreachable/unauthorized custom source
|
|
1033
1048
|
// must not dump a raw upstream stack, and must never touch the base
|
|
1034
|
-
// install (the rest of installWizard proceeds unaffected).
|
|
1049
|
+
// install (the rest of installWizard proceeds unaffected). The full cause
|
|
1050
|
+
// (and stack) goes to the LOG FILE only, for diagnosis.
|
|
1051
|
+
diagError('custom-source: enumerate failed', err);
|
|
1035
1052
|
const message = err instanceof CustomMarketplaceError ? err.message : `Custom marketplace source error: ${err.message}`;
|
|
1036
1053
|
console.log(chalk.red(`\n ${message}`));
|
|
1037
1054
|
console.log(chalk.gray(' Skipping custom marketplace source; the rest of the install is unaffected.'));
|
|
@@ -1039,6 +1056,11 @@ async function promptCustomMarketplace({ bmadCustomSourceFlag = '', yesFlag = fa
|
|
|
1039
1056
|
}
|
|
1040
1057
|
|
|
1041
1058
|
const { plugins, mode, rootDir, sourceUrl } = enumerated;
|
|
1059
|
+
diag('custom-source: enumerated', {
|
|
1060
|
+
source, mode, rootDir, sourceUrl,
|
|
1061
|
+
pluginCount: (plugins || []).length,
|
|
1062
|
+
codes: (plugins || []).map((p) => p.code),
|
|
1063
|
+
});
|
|
1042
1064
|
if (!plugins || plugins.length === 0) {
|
|
1043
1065
|
console.log(chalk.yellow(` Custom marketplace source "${source}" declared no plugins — nothing to select.`));
|
|
1044
1066
|
return { source, mode, selectedCodes: [], plugins: [], rootDir, sourceUrl };
|
|
@@ -1117,12 +1139,16 @@ async function promptCustomMarketplace({ bmadCustomSourceFlag = '', yesFlag = fa
|
|
|
1117
1139
|
*/
|
|
1118
1140
|
async function reconcileCustomMarketplaceOnUpdate({ projectRoot }) {
|
|
1119
1141
|
const persisted = getCustomMarketplaceSelection(projectRoot);
|
|
1142
|
+
diag('custom-source: reconcile on update', persisted
|
|
1143
|
+
? { source: persisted.source, mode: persisted.mode, selectedCodes: persisted.selectedCodes }
|
|
1144
|
+
: { persisted: false });
|
|
1120
1145
|
if (!persisted) return undefined;
|
|
1121
1146
|
|
|
1122
1147
|
let enumerated;
|
|
1123
1148
|
try {
|
|
1124
1149
|
enumerated = await enumerateCustomSource(persisted.source);
|
|
1125
1150
|
} catch (err) {
|
|
1151
|
+
diagError('custom-source: reconcile re-enumerate failed', err);
|
|
1126
1152
|
const message = err instanceof CustomMarketplaceError ? err.message : `Custom marketplace source error: ${err.message}`;
|
|
1127
1153
|
console.log(chalk.red(`\n Custom marketplace update FAILED: ${message}`));
|
|
1128
1154
|
console.log(
|
|
@@ -1547,6 +1573,12 @@ async function installWizard(preselectedSkill, preselectedAgents, customPath, fo
|
|
|
1547
1573
|
}
|
|
1548
1574
|
}
|
|
1549
1575
|
if (customMarketplaceSelection && customMarketplaceSelection.selectedCodes.length > 0) {
|
|
1576
|
+
diag('custom-source: install begin', {
|
|
1577
|
+
mode: customMarketplaceSelection.mode,
|
|
1578
|
+
source: customMarketplaceSelection.source,
|
|
1579
|
+
selectedCodes: customMarketplaceSelection.selectedCodes,
|
|
1580
|
+
baseBmadOk,
|
|
1581
|
+
});
|
|
1550
1582
|
if (!baseBmadOk) {
|
|
1551
1583
|
// AC 6 (30.6b) — never layer --custom-source onto a project with no
|
|
1552
1584
|
// (or a broken) base BMAD install. The rest of the install is
|
|
@@ -1565,10 +1597,14 @@ async function installWizard(preselectedSkill, preselectedAgents, customPath, fo
|
|
|
1565
1597
|
tools: bmadTools,
|
|
1566
1598
|
selection: customMarketplaceSelection,
|
|
1567
1599
|
});
|
|
1600
|
+
diag('custom-source: install result', { success: !!success });
|
|
1568
1601
|
if (success) {
|
|
1569
1602
|
console.log(chalk.green(' Custom marketplace subset installed successfully!'));
|
|
1570
1603
|
}
|
|
1571
1604
|
} catch (err) {
|
|
1605
|
+
// User sees the one-liner; the full cause/stack (which entry, the
|
|
1606
|
+
// git/resolve error, the FR238 zero-module detail) goes to the log.
|
|
1607
|
+
diagError('custom-source: install failed', err);
|
|
1572
1608
|
console.log(chalk.red(`\n Custom marketplace install failed: ${err.message}`));
|
|
1573
1609
|
}
|
|
1574
1610
|
}
|
|
@@ -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.3",
|
|
41
41
|
"author": {
|
|
42
42
|
"name": "Alon Mayaffit"
|
|
43
43
|
},
|
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
const path = require('node:path');
|
|
41
41
|
const fs = require('fs-extra');
|
|
42
42
|
const yaml = require('js-yaml');
|
|
43
|
+
const { diag } = require('./diag');
|
|
43
44
|
|
|
44
45
|
/** Deep, pinned-internal require path (NFR17). Single call site. */
|
|
45
46
|
const CUSTOM_MODULE_MANAGER_PATH = 'bmad-method/tools/installer/modules/custom-module-manager.js';
|
|
@@ -477,6 +478,64 @@ async function scanDirectModeSkillDirs(rootDir) {
|
|
|
477
478
|
return scanSkillDirs(rootDir);
|
|
478
479
|
}
|
|
479
480
|
|
|
481
|
+
/**
|
|
482
|
+
* Recursively collect every directory that contains a `SKILL.md`, returned as
|
|
483
|
+
* POSIX paths RELATIVE to `rootDirAbs`. Unlike {@link scanSkillDirs} (DIRECT
|
|
484
|
+
* children only), this walks the whole subtree: real BMAD modules nest skills
|
|
485
|
+
* under `agents/`, `workflows/<phase>/…`, etc. A dir that IS a skill (has
|
|
486
|
+
* SKILL.md) is recorded and not descended into (skills don't nest in skills);
|
|
487
|
+
* `node_modules`/`.git` are skipped.
|
|
488
|
+
*/
|
|
489
|
+
async function findSkillDirsRecursive(baseDirAbs, rootDirAbs) {
|
|
490
|
+
const found = [];
|
|
491
|
+
async function walk(dirAbs) {
|
|
492
|
+
if (await fs.pathExists(path.join(dirAbs, 'SKILL.md'))) {
|
|
493
|
+
found.push(toPosix(path.relative(rootDirAbs, dirAbs)));
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
let entries;
|
|
497
|
+
try {
|
|
498
|
+
entries = await fs.readdir(dirAbs, { withFileTypes: true });
|
|
499
|
+
} catch {
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
for (const entry of entries) {
|
|
503
|
+
if (!entry.isDirectory()) continue;
|
|
504
|
+
if (entry.name === 'node_modules' || entry.name === '.git') continue;
|
|
505
|
+
await walk(path.join(dirAbs, entry.name));
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
await walk(baseDirAbs);
|
|
509
|
+
return found.sort();
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
/**
|
|
513
|
+
* Determine which directory to scan for a registry entry's skills, given the
|
|
514
|
+
* cloned repo root and the entry's `module_definition` (repo-relative path to
|
|
515
|
+
* its module.yaml). Skills are NOT always beside module.yaml:
|
|
516
|
+
* - module.yaml at the skills' parent (`skills/module.yaml`,
|
|
517
|
+
* `src/core-skills/module.yaml`, `src/module.yaml`) → scan THAT dir.
|
|
518
|
+
* - module.yaml inside a `<name>-setup/assets/module.yaml` (bmad-method's
|
|
519
|
+
* PluginResolver "setup skill" layout, e.g. suno-band-manager) → the
|
|
520
|
+
* module's skills are SIBLINGS of the -setup skill, so scan the setup
|
|
521
|
+
* skill's PARENT.
|
|
522
|
+
* Returns a POSIX path relative to the repo root ('.' = repo root). No
|
|
523
|
+
* module_definition → the repo root (direct-style scan).
|
|
524
|
+
*/
|
|
525
|
+
async function resolveRegistrySkillsRoot(entryRootDir, moduleDefPosix) {
|
|
526
|
+
if (!moduleDefPosix) return '.';
|
|
527
|
+
const mdDirPosix = path.posix.dirname(moduleDefPosix);
|
|
528
|
+
if (path.posix.basename(mdDirPosix) === 'assets') {
|
|
529
|
+
const setupSkillPosix = path.posix.dirname(mdDirPosix);
|
|
530
|
+
const setupSkillAbs = path.join(entryRootDir, ...setupSkillPosix.split('/'));
|
|
531
|
+
if (setupSkillPosix && (await fs.pathExists(path.join(setupSkillAbs, 'SKILL.md')))) {
|
|
532
|
+
const parentPosix = path.posix.dirname(setupSkillPosix);
|
|
533
|
+
return parentPosix === '' || parentPosix === '.' ? '.' : parentPosix;
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
return mdDirPosix === '' || mdDirPosix === '.' ? '.' : mdDirPosix;
|
|
537
|
+
}
|
|
538
|
+
|
|
480
539
|
/**
|
|
481
540
|
* Resolve every selected plugin's installable skill/module layout via
|
|
482
541
|
* bmad-method's own `CustomModuleManager#resolvePlugin` (the pinned-internal
|
|
@@ -554,6 +613,19 @@ async function resolveSelectedPlugins({ mode, rootDir, sourceUrl, plugins, selec
|
|
|
554
613
|
function assertResolutionNonEmptyAndUniqueLeaves(resolvedByPlugin, { source = '', mode = null } = {}) {
|
|
555
614
|
const allModules = resolvedByPlugin.flatMap((r) => r.resolved || []);
|
|
556
615
|
|
|
616
|
+
// Per-plugin resolution detail → log file only. When the guard below trips,
|
|
617
|
+
// this line pinpoints WHICH entry resolved to nothing (and how many
|
|
618
|
+
// skills each resolved module got) — the crux of a "zero modules" report.
|
|
619
|
+
diag('custom-source: resolution', {
|
|
620
|
+
source, mode,
|
|
621
|
+
perPlugin: resolvedByPlugin.map((r) => ({
|
|
622
|
+
plugin: (r.plugin && (r.plugin.code || r.plugin.name)) || (r.rawPlugin && r.rawPlugin.name) || '?',
|
|
623
|
+
modules: (r.resolved || []).length,
|
|
624
|
+
skills: (r.resolved || []).reduce((n, m) => n + ((m.skillPaths && m.skillPaths.length) || 0), 0),
|
|
625
|
+
})),
|
|
626
|
+
totalModules: allModules.length,
|
|
627
|
+
});
|
|
628
|
+
|
|
557
629
|
if (allModules.length === 0) {
|
|
558
630
|
// AC7 — a direct-mode source that resolves to nothing is the exact
|
|
559
631
|
// failure a mis-pointed registry URL used to produce. Name the likely
|
|
@@ -741,6 +813,13 @@ async function resolveRegistryEntry(plugin, resolveSourceFn, resolvePluginFn) {
|
|
|
741
813
|
// source string.
|
|
742
814
|
const safePinRef = pinRef && /^[\w.\-+/]+$/.test(pinRef) && !pinRef.includes(':') ? pinRef : null;
|
|
743
815
|
const cloneInput = safePinRef ? `${plugin.repository}@${safePinRef}` : plugin.repository;
|
|
816
|
+
diag('custom-source: registry entry clone', {
|
|
817
|
+
code: plugin.code,
|
|
818
|
+
repository: plugin.repository,
|
|
819
|
+
pinRef: safePinRef,
|
|
820
|
+
approvedSha: plugin.approvedSha || null,
|
|
821
|
+
moduleDefinition: plugin.moduleDefinition || '(none — repo root)',
|
|
822
|
+
});
|
|
744
823
|
|
|
745
824
|
let resolvedSource;
|
|
746
825
|
try {
|
|
@@ -763,10 +842,10 @@ async function resolveRegistryEntry(plugin, resolveSourceFn, resolvePluginFn) {
|
|
|
763
842
|
// repo-relative path to the module's module.yaml; its directory is the
|
|
764
843
|
// module root. Absent → fall back to the repo root (PluginResolver over the
|
|
765
844
|
// whole clone), matching the AC3 fallback.
|
|
766
|
-
let
|
|
845
|
+
let moduleDefPosix = null;
|
|
767
846
|
if (plugin.moduleDefinition) {
|
|
768
|
-
|
|
769
|
-
const moduleYamlAbs = path.join(entryRootDir, ...
|
|
847
|
+
moduleDefPosix = toPosix(plugin.moduleDefinition).replace(/^\.\//, '');
|
|
848
|
+
const moduleYamlAbs = path.join(entryRootDir, ...moduleDefPosix.split('/'));
|
|
770
849
|
if (!(await fs.pathExists(moduleYamlAbs))) {
|
|
771
850
|
throw new CustomMarketplaceError(
|
|
772
851
|
`Registry entry '${plugin.code}': module_definition "${plugin.moduleDefinition}" ` +
|
|
@@ -774,21 +853,33 @@ async function resolveRegistryEntry(plugin, resolveSourceFn, resolvePluginFn) {
|
|
|
774
853
|
`repository layout changed; aborting before staging (FR238).`
|
|
775
854
|
);
|
|
776
855
|
}
|
|
777
|
-
relModuleDirPosix = path.posix.dirname(defPosix);
|
|
778
856
|
}
|
|
779
857
|
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
858
|
+
// Skills are NOT necessarily beside module.yaml — resolve the module's real
|
|
859
|
+
// skills root (handles the setup-skill/assets layout and modules whose
|
|
860
|
+
// module.yaml sits at the skills' parent) and scan it RECURSIVELY. A shallow
|
|
861
|
+
// scan of module.yaml's own dir silently missed nested skills (gds/bmm) and
|
|
862
|
+
// the setup-skill layout entirely (suno), dropping those modules.
|
|
863
|
+
const skillsRootPosix = await resolveRegistrySkillsRoot(entryRootDir, moduleDefPosix);
|
|
864
|
+
const skillsRootAbs =
|
|
865
|
+
skillsRootPosix === '.' ? entryRootDir : path.join(entryRootDir, ...skillsRootPosix.split('/'));
|
|
866
|
+
const skillRelPaths = await findSkillDirsRecursive(skillsRootAbs, entryRootDir);
|
|
867
|
+
diag('custom-source: registry entry resolved', {
|
|
868
|
+
code: plugin.code,
|
|
869
|
+
moduleDef: moduleDefPosix,
|
|
870
|
+
skillsRoot: skillsRootPosix,
|
|
871
|
+
skillDirsFound: skillRelPaths.length,
|
|
872
|
+
skills: skillRelPaths,
|
|
873
|
+
});
|
|
784
874
|
|
|
785
875
|
const rawPlugin = {
|
|
786
876
|
name: plugin.code,
|
|
787
877
|
description: plugin.description || '',
|
|
788
878
|
version: plugin.version || null,
|
|
789
|
-
source:
|
|
790
|
-
//
|
|
791
|
-
// returns [] and the
|
|
879
|
+
source: skillsRootPosix,
|
|
880
|
+
// Skill dirs relative to the repo root (what PluginResolver.resolve joins
|
|
881
|
+
// against repoPath). Empty → resolvePlugin returns [] and the selected
|
|
882
|
+
// entry surfaces in the per-entry guard below (never a silent drop).
|
|
792
883
|
skills: skillRelPaths,
|
|
793
884
|
};
|
|
794
885
|
|
|
@@ -857,6 +948,26 @@ async function stageRegistrySubset({ source, plugins, selectedCodes, projectRoot
|
|
|
857
948
|
entries.push(await resolveRegistryEntry(plugin, resolveSourceFn, resolvePluginFn));
|
|
858
949
|
}
|
|
859
950
|
|
|
951
|
+
// Per-entry LOUD guard: a SELECTED entry that resolves to zero modules must
|
|
952
|
+
// NOT be silently dropped while others install (which read as an overall
|
|
953
|
+
// "success"). Fail before staging, naming exactly which entries produced
|
|
954
|
+
// nothing — the user picked them explicitly, so a partial install is a
|
|
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
|
+
});
|
|
962
|
+
throw new CustomMarketplaceError(
|
|
963
|
+
`Selected registry ${emptyEntries.length === 1 ? 'entry' : 'entries'} ${names} resolved to ` +
|
|
964
|
+
`zero installable modules — no SKILL.md directories were found for ${emptyEntries.length === 1 ? 'it' : 'them'} ` +
|
|
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.`
|
|
968
|
+
);
|
|
969
|
+
}
|
|
970
|
+
|
|
860
971
|
const allModules = assertResolutionNonEmptyAndUniqueLeaves(
|
|
861
972
|
entries.map(({ plugin, rawPlugin, resolved }) => ({ plugin, rawPlugin, resolved })),
|
|
862
973
|
{ source, mode: 'registry' }
|
|
@@ -954,6 +1065,8 @@ module.exports = {
|
|
|
954
1065
|
CUSTOM_MARKETPLACE_STAGE_DIR_NAME,
|
|
955
1066
|
scanDirectModeSkillDirs,
|
|
956
1067
|
scanSkillDirs,
|
|
1068
|
+
findSkillDirsRecursive,
|
|
1069
|
+
resolveRegistrySkillsRoot,
|
|
957
1070
|
resolveSelectedPlugins,
|
|
958
1071
|
assertResolutionNonEmptyAndUniqueLeaves,
|
|
959
1072
|
stageCustomMarketplaceSubset,
|
package/lib/diag.js
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Log-file-only diagnostics.
|
|
5
|
+
*
|
|
6
|
+
* When `--log` is active, bin/cli.js#setupLogging installs a sink that writes
|
|
7
|
+
* to the log file descriptor. `diag()`/`diagError()` then emit structured
|
|
8
|
+
* breadcrumbs to the LOG FILE ONLY — never to the user's console (the wizard's
|
|
9
|
+
* user-facing output intentionally stays clean; e.g. a failed custom-marketplace
|
|
10
|
+
* source shows a one-line message, not a raw stack — NFR71). Without a sink
|
|
11
|
+
* (no `--log`), every call is a cheap no-op.
|
|
12
|
+
*
|
|
13
|
+
* This is how the swallowed root cause of a custom-marketplace failure (the
|
|
14
|
+
* git/clone/resolve error behind a clean CustomMarketplaceError, the per-entry
|
|
15
|
+
* module_definition that was missing, the FR238 zero-module trigger, etc.)
|
|
16
|
+
* reaches the log for post-hoc diagnosis without leaking to the terminal.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
let sink = null;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @param {((text: string) => void)|null} fn - writer for log-file output, or null to disable.
|
|
23
|
+
*/
|
|
24
|
+
function setLogSink(fn) {
|
|
25
|
+
sink = typeof fn === 'function' ? fn : null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** @returns {boolean} whether diagnostics are being captured. */
|
|
29
|
+
function isActive() {
|
|
30
|
+
return sink !== null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function stamp() {
|
|
34
|
+
// new Date() is available in the CLI runtime (this is not a workflow script).
|
|
35
|
+
return new Date().toISOString();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function stringify(detail) {
|
|
39
|
+
if (typeof detail === 'string') return detail;
|
|
40
|
+
try {
|
|
41
|
+
return JSON.stringify(detail);
|
|
42
|
+
} catch {
|
|
43
|
+
return String(detail);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Emit one diagnostic breadcrumb to the log file (no-op without a sink).
|
|
49
|
+
* @param {string} label
|
|
50
|
+
* @param {*} [detail] - string or JSON-serializable value.
|
|
51
|
+
*/
|
|
52
|
+
function diag(label, detail) {
|
|
53
|
+
if (!sink) return;
|
|
54
|
+
let line = `[diag ${stamp()}] ${label}`;
|
|
55
|
+
if (detail !== undefined) line += `: ${stringify(detail)}`;
|
|
56
|
+
try {
|
|
57
|
+
sink(line + '\n');
|
|
58
|
+
} catch {
|
|
59
|
+
/* never let diagnostics break the install */
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Emit an error — including its `stack` AND any wrapped `cause` (message +
|
|
65
|
+
* stack) — to the log file. This is the crucial bit for custom-marketplace
|
|
66
|
+
* failures, whose user-facing message deliberately hides the upstream cause.
|
|
67
|
+
* @param {string} label
|
|
68
|
+
* @param {Error|*} err
|
|
69
|
+
*/
|
|
70
|
+
function diagError(label, err) {
|
|
71
|
+
if (!sink || !err) return;
|
|
72
|
+
const parts = [`[diag ${stamp()}] ${label}: ${err.message || String(err)}`];
|
|
73
|
+
if (err.stack) parts.push(` stack: ${err.stack}`);
|
|
74
|
+
if (err.cause) {
|
|
75
|
+
const c = err.cause;
|
|
76
|
+
parts.push(` cause: ${(c && c.message) || String(c)}`);
|
|
77
|
+
if (c && c.stack) parts.push(` cause.stack: ${c.stack}`);
|
|
78
|
+
}
|
|
79
|
+
try {
|
|
80
|
+
sink(parts.join('\n') + '\n');
|
|
81
|
+
} catch {
|
|
82
|
+
/* never let diagnostics break the install */
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
module.exports = { setLogSink, isActive, diag, diagError };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ma-agents",
|
|
3
|
-
"version": "3.17.0-beta.
|
|
3
|
+
"version": "3.17.0-beta.3",
|
|
4
4
|
"description": "NPX tool to install skills for AI coding agents (Claude Code, Gemini, Copilot, Kilocode, Cline, Cursor, Roo Code)",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
10
|
"start": "node bin/cli.js",
|
|
11
|
-
"test": "node test/yes-flag.test.js && node test/skill-authoring.test.js && node test/skill-validation.test.js && node test/skill-mandatory.test.js && node test/skill-customize-agent.test.js && node test/create-agent.test.js && node test/generate-project-context.test.js && node test/build-bmad-args.test.js && node test/build-bmad-cache-reader.test.js && node test/bundle-filter-set-equality.test.js && node test/bmad-version-bump.test.js && node test/staging-6-10-0-regression.test.js && node test/uv-transition-resilience.test.js && node test/passthrough-reconciliation-6-9-0.test.js && node test/extension-module-restructure.test.js && node test/convert-agents-to-skills.test.js && node test/migration.test.js && node test/migration-validation.test.js && node test/story-15-5-workflow-skills.test.js && node test/repo-layout.test.js && node test/config-storage.test.js && node test/cross-repo-validation.test.js && node test/config-lost-on-update.test.js && node test/portable-paths.test.js && node test/cicd-remote-mode.test.js && node test/config-layout.test.js && node test/roo-code-agent.test.js && node test/roo-code-injection.test.js && node test/profile.test.js && node test/instruction-block.test.js && node test/instruction-injection.test.js && node test/agents-md.test.js && node test/instruction-block-git.test.js && node test/onprem-injection.test.js && node test/onprem-layer.test.js && node test/copilot-instructions-path.test.js && node test/roomodes.test.js && node test/clinerules.test.js && node test/cline-workflow-reconciliation.test.js && node test/reconfigure.test.js && node test/experimental-warning.test.js && node test/bmad-persona-phase-prefix.test.js && node test/f1a-on-prem-prefix-post-install.test.js && node test/retired-skills-migration.test.js && node test/rename-migration.test.js && node test/uninstall.test.js && node test/offline-recompile.test.js && node test/plugin-manifests.test.js && node test/demerzel-ml-removal-drift.test.js && node test/demerzel-deprecation-notice.test.js && node test/build-plugin.test.js && node test/config-path-migration.test.js && node test/gitignore-plugin-stage.test.js && node test/customizations-translation.test.js && node test/routing-parity.test.js && node test/bmad-extension.test.js && node test/integration-verification.test.js && node test/agent-scope.test.js && node test/obsolete-tool-dirs.test.js && node test/bmad-empty-tools-guard.test.js && node test/v68-pluginresolver-enumeration-passthrough.test.js && node test/agents.test.js && node test/installer.test.js && node test/bmad.test.js && node test/cli.test.js && node test/knowledge-atlas-smoke.test.js && node test/knowledge-atlas-manifest.test.js && node test/knowledge-atlas-render.test.js && node test/knowledge-atlas-graph.test.js && node test/knowledge-atlas-packaging.test.js && node test/knowledge-atlas-offline-safety.test.js && node test/knowledge-atlas-reconcile.test.js && node test/knowledge-atlas-determinism.test.js && node test/knowledge-atlas-degradation.test.js && node test/knowledge-atlas-realproject.test.js && node test/knowledge-atlas-link-integrity.test.js && node test/knowledge-atlas-config-scan.test.js && node test/knowledge-atlas-references.test.js && node test/agent-injection-strategy.test.js && node test/bmad-module-selection.test.js && node test/custom-marketplace-surface-drift.test.js && node test/custom-marketplace-wizard.test.js && node test/custom-marketplace-live-clone.test.js && node test/custom-marketplace-stage-install.test.js && node test/custom-marketplace-persist-reconcile.test.js && node test/demerzel-nondestructive-invariant.test.js && node test/fr243-custom-source-allowlist-nonleak.test.js && node test/custom-marketplace-registry.test.js",
|
|
11
|
+
"test": "node test/yes-flag.test.js && node test/skill-authoring.test.js && node test/skill-validation.test.js && node test/skill-mandatory.test.js && node test/skill-customize-agent.test.js && node test/create-agent.test.js && node test/generate-project-context.test.js && node test/build-bmad-args.test.js && node test/build-bmad-cache-reader.test.js && node test/bundle-filter-set-equality.test.js && node test/bmad-version-bump.test.js && node test/staging-6-10-0-regression.test.js && node test/uv-transition-resilience.test.js && node test/passthrough-reconciliation-6-9-0.test.js && node test/extension-module-restructure.test.js && node test/convert-agents-to-skills.test.js && node test/migration.test.js && node test/migration-validation.test.js && node test/story-15-5-workflow-skills.test.js && node test/repo-layout.test.js && node test/config-storage.test.js && node test/cross-repo-validation.test.js && node test/config-lost-on-update.test.js && node test/portable-paths.test.js && node test/cicd-remote-mode.test.js && node test/config-layout.test.js && node test/roo-code-agent.test.js && node test/roo-code-injection.test.js && node test/profile.test.js && node test/instruction-block.test.js && node test/instruction-injection.test.js && node test/agents-md.test.js && node test/instruction-block-git.test.js && node test/onprem-injection.test.js && node test/onprem-layer.test.js && node test/copilot-instructions-path.test.js && node test/roomodes.test.js && node test/clinerules.test.js && node test/cline-workflow-reconciliation.test.js && node test/reconfigure.test.js && node test/experimental-warning.test.js && node test/bmad-persona-phase-prefix.test.js && node test/f1a-on-prem-prefix-post-install.test.js && node test/retired-skills-migration.test.js && node test/rename-migration.test.js && node test/uninstall.test.js && node test/offline-recompile.test.js && node test/plugin-manifests.test.js && node test/demerzel-ml-removal-drift.test.js && node test/demerzel-deprecation-notice.test.js && node test/build-plugin.test.js && node test/config-path-migration.test.js && node test/gitignore-plugin-stage.test.js && node test/customizations-translation.test.js && node test/routing-parity.test.js && node test/bmad-extension.test.js && node test/integration-verification.test.js && node test/agent-scope.test.js && node test/obsolete-tool-dirs.test.js && node test/bmad-empty-tools-guard.test.js && node test/v68-pluginresolver-enumeration-passthrough.test.js && node test/agents.test.js && node test/installer.test.js && node test/bmad.test.js && node test/cli.test.js && node test/knowledge-atlas-smoke.test.js && node test/knowledge-atlas-manifest.test.js && node test/knowledge-atlas-render.test.js && node test/knowledge-atlas-graph.test.js && node test/knowledge-atlas-packaging.test.js && node test/knowledge-atlas-offline-safety.test.js && node test/knowledge-atlas-reconcile.test.js && node test/knowledge-atlas-determinism.test.js && node test/knowledge-atlas-degradation.test.js && node test/knowledge-atlas-realproject.test.js && node test/knowledge-atlas-link-integrity.test.js && node test/knowledge-atlas-config-scan.test.js && node test/knowledge-atlas-references.test.js && node test/agent-injection-strategy.test.js && node test/bmad-module-selection.test.js && node test/custom-marketplace-surface-drift.test.js && node test/custom-marketplace-wizard.test.js && node test/custom-marketplace-live-clone.test.js && node test/custom-marketplace-stage-install.test.js && node test/custom-marketplace-persist-reconcile.test.js && node test/demerzel-nondestructive-invariant.test.js && node test/fr243-custom-source-allowlist-nonleak.test.js && node test/custom-marketplace-registry.test.js && node test/diag.test.js",
|
|
12
12
|
"build:bmad-cache": "node scripts/build-bmad-cache.js",
|
|
13
13
|
"build:plugin": "node scripts/build-plugin.js",
|
|
14
14
|
"build:atlas": "node scripts/build-atlas.js",
|