feat(installer): warn on non-canonical module-help.csv headers
mergeModuleHelpCatalogs now compares each per-module file's header against the canonical schema and emits a one-shot prompts.log.warn per module on drift, naming both the expected and actual header. Data continues to load positionally so external modules built against the old after/before schema still install cleanly — the warning is the maintainer signal to rename their columns. Centralize the canonical header in modules/module-help-schema.js so the merger and the synthesizer (PluginResolver._buildSynthesizedHelpCsv) read the same source of truth; future column renames are one edit. Verified by installing all four bmad-org external modules (bmb, cis, gds, tea) — every one ships the legacy after/before header today and now fires an advisory warning while still merging cleanly into _bmad/_config/bmad-help.csv with the canonical column names.
This commit is contained in:
parent
1e339afe51
commit
95bed595a8
|
|
@ -12,6 +12,7 @@ const { BMAD_FOLDER_NAME } = require('../ide/shared/path-utils');
|
||||||
const { InstallPaths } = require('./install-paths');
|
const { InstallPaths } = require('./install-paths');
|
||||||
const { ExternalModuleManager } = require('../modules/external-manager');
|
const { ExternalModuleManager } = require('../modules/external-manager');
|
||||||
const { resolveModuleVersion } = require('../modules/version-resolver');
|
const { resolveModuleVersion } = require('../modules/version-resolver');
|
||||||
|
const { MODULE_HELP_CSV_HEADER } = require('../modules/module-help-schema');
|
||||||
|
|
||||||
const { ExistingInstall } = require('./existing-install');
|
const { ExistingInstall } = require('./existing-install');
|
||||||
const { warnPreNativeSkillsLegacy } = require('./legacy-warnings');
|
const { warnPreNativeSkillsLegacy } = require('./legacy-warnings');
|
||||||
|
|
@ -942,8 +943,7 @@ class Installer {
|
||||||
*/
|
*/
|
||||||
async mergeModuleHelpCatalogs(bmadDir, _agentEntries = []) {
|
async mergeModuleHelpCatalogs(bmadDir, _agentEntries = []) {
|
||||||
const allRows = [];
|
const allRows = [];
|
||||||
const headerRow =
|
const headerRow = MODULE_HELP_CSV_HEADER;
|
||||||
'module,skill,display-name,menu-code,description,action,args,phase,preceded-by,followed-by,required,output-location,outputs';
|
|
||||||
const COLUMN_COUNT = 13;
|
const COLUMN_COUNT = 13;
|
||||||
const PHASE_INDEX = 7;
|
const PHASE_INDEX = 7;
|
||||||
|
|
||||||
|
|
@ -976,9 +976,19 @@ class Installer {
|
||||||
const content = await fs.readFile(helpFilePath, 'utf8');
|
const content = await fs.readFile(helpFilePath, 'utf8');
|
||||||
const lines = content.split('\n').filter((line) => line.trim() && !line.startsWith('#'));
|
const lines = content.split('\n').filter((line) => line.trim() && !line.startsWith('#'));
|
||||||
|
|
||||||
|
let headerWarned = false;
|
||||||
for (const line of lines) {
|
for (const line of lines) {
|
||||||
// Skip header row
|
// Header row: warn on drift from canonical schema, then skip.
|
||||||
|
// Data rows are loaded positionally regardless, so the warning
|
||||||
|
// is advisory — the maintainer should rename their columns.
|
||||||
if (line.startsWith('module,')) {
|
if (line.startsWith('module,')) {
|
||||||
|
if (!headerWarned && line.trim() !== headerRow) {
|
||||||
|
await prompts.log.warn(
|
||||||
|
` ${moduleName}/module-help.csv header does not match canonical schema. ` +
|
||||||
|
`Expected: ${headerRow} | Found: ${line.trim()} | Data loaded positionally.`,
|
||||||
|
);
|
||||||
|
headerWarned = true;
|
||||||
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
/**
|
||||||
|
* Canonical schema for per-module `module-help.csv` files.
|
||||||
|
*
|
||||||
|
* Both the merger (`Installer.mergeModuleHelpCatalogs`) and the synthesizer
|
||||||
|
* (`PluginResolver._buildSynthesizedHelpCsv`) emit this exact header. The
|
||||||
|
* merger compares each per-module file's header against this string and
|
||||||
|
* warns on drift, so any rename here must be matched in external module
|
||||||
|
* authors' CSVs (or accepted as a positional fall-through with a warning).
|
||||||
|
*/
|
||||||
|
const MODULE_HELP_CSV_HEADER =
|
||||||
|
'module,skill,display-name,menu-code,description,action,args,phase,preceded-by,followed-by,required,output-location,outputs';
|
||||||
|
|
||||||
|
module.exports = { MODULE_HELP_CSV_HEADER };
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
const fs = require('../fs-native');
|
const fs = require('../fs-native');
|
||||||
const path = require('node:path');
|
const path = require('node:path');
|
||||||
const yaml = require('yaml');
|
const yaml = require('yaml');
|
||||||
|
const { MODULE_HELP_CSV_HEADER } = require('./module-help-schema');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolves how to install a plugin from marketplace.json by analyzing
|
* Resolves how to install a plugin from marketplace.json by analyzing
|
||||||
|
|
@ -338,9 +339,7 @@ class PluginResolver {
|
||||||
* @returns {string} CSV content
|
* @returns {string} CSV content
|
||||||
*/
|
*/
|
||||||
_buildSynthesizedHelpCsv(moduleName, skillInfos) {
|
_buildSynthesizedHelpCsv(moduleName, skillInfos) {
|
||||||
const header =
|
const rows = [MODULE_HELP_CSV_HEADER];
|
||||||
'module,skill,display-name,menu-code,description,action,args,phase,preceded-by,followed-by,required,output-location,outputs';
|
|
||||||
const rows = [header];
|
|
||||||
|
|
||||||
for (const info of skillInfos) {
|
for (const info of skillInfos) {
|
||||||
const displayName = this._formatDisplayName(info.name || info.dirName);
|
const displayName = this._formatDisplayName(info.name || info.dirName);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue