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 { ExternalModuleManager } = require('../modules/external-manager');
|
||||
const { resolveModuleVersion } = require('../modules/version-resolver');
|
||||
const { MODULE_HELP_CSV_HEADER } = require('../modules/module-help-schema');
|
||||
|
||||
const { ExistingInstall } = require('./existing-install');
|
||||
const { warnPreNativeSkillsLegacy } = require('./legacy-warnings');
|
||||
|
|
@ -942,8 +943,7 @@ class Installer {
|
|||
*/
|
||||
async mergeModuleHelpCatalogs(bmadDir, _agentEntries = []) {
|
||||
const allRows = [];
|
||||
const headerRow =
|
||||
'module,skill,display-name,menu-code,description,action,args,phase,preceded-by,followed-by,required,output-location,outputs';
|
||||
const headerRow = MODULE_HELP_CSV_HEADER;
|
||||
const COLUMN_COUNT = 13;
|
||||
const PHASE_INDEX = 7;
|
||||
|
||||
|
|
@ -976,9 +976,19 @@ class Installer {
|
|||
const content = await fs.readFile(helpFilePath, 'utf8');
|
||||
const lines = content.split('\n').filter((line) => line.trim() && !line.startsWith('#'));
|
||||
|
||||
let headerWarned = false;
|
||||
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 (!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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 path = require('node:path');
|
||||
const yaml = require('yaml');
|
||||
const { MODULE_HELP_CSV_HEADER } = require('./module-help-schema');
|
||||
|
||||
/**
|
||||
* Resolves how to install a plugin from marketplace.json by analyzing
|
||||
|
|
@ -338,9 +339,7 @@ class PluginResolver {
|
|||
* @returns {string} CSV content
|
||||
*/
|
||||
_buildSynthesizedHelpCsv(moduleName, skillInfos) {
|
||||
const header =
|
||||
'module,skill,display-name,menu-code,description,action,args,phase,preceded-by,followed-by,required,output-location,outputs';
|
||||
const rows = [header];
|
||||
const rows = [MODULE_HELP_CSV_HEADER];
|
||||
|
||||
for (const info of skillInfos) {
|
||||
const displayName = this._formatDisplayName(info.name || info.dirName);
|
||||
|
|
|
|||
Loading…
Reference in New Issue