feat(installer): add index-docs native skill authority projection
This commit is contained in:
parent
c7680ab1a8
commit
99537b20ab
|
|
@ -0,0 +1,9 @@
|
|||
schemaVersion: 1
|
||||
canonicalId: bmad-index-docs
|
||||
artifactType: task
|
||||
module: core
|
||||
sourcePath: bmad-fork/src/core/tasks/index-docs.xml
|
||||
displayName: Index Docs
|
||||
description: "Create lightweight index for quick LLM scanning. Use when LLM needs to understand available docs without loading everything."
|
||||
dependencies:
|
||||
requires: []
|
||||
9
test/fixtures/index-docs/sidecar-negative/basename-path-mismatch/index-docs.artifact.yaml
vendored
Normal file
9
test/fixtures/index-docs/sidecar-negative/basename-path-mismatch/index-docs.artifact.yaml
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
schemaVersion: 1
|
||||
canonicalId: bmad-index-docs
|
||||
artifactType: task
|
||||
module: core
|
||||
sourcePath: bmad-fork/src/core/tasks/not-index-docs.xml
|
||||
displayName: Index Docs
|
||||
description: "Create lightweight index for quick LLM scanning. Use when LLM needs to understand available docs without loading everything."
|
||||
dependencies:
|
||||
requires: []
|
||||
9
test/fixtures/index-docs/sidecar-negative/unknown-major-version/index-docs.artifact.yaml
vendored
Normal file
9
test/fixtures/index-docs/sidecar-negative/unknown-major-version/index-docs.artifact.yaml
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
schemaVersion: 2
|
||||
canonicalId: bmad-index-docs
|
||||
artifactType: task
|
||||
module: core
|
||||
sourcePath: bmad-fork/src/core/tasks/index-docs.xml
|
||||
displayName: Index Docs
|
||||
description: "Create lightweight index for quick LLM scanning. Use when LLM needs to understand available docs without loading everything."
|
||||
dependencies:
|
||||
requires: []
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -949,6 +949,22 @@ class HelpValidationHarness {
|
|||
'output-location': '',
|
||||
outputs: '',
|
||||
},
|
||||
{
|
||||
module: 'core',
|
||||
phase: 'anytime',
|
||||
name: 'Index Docs',
|
||||
code: 'ID',
|
||||
sequence: '',
|
||||
'workflow-file': `${runtimeFolder}/core/tasks/index-docs.xml`,
|
||||
command: 'bmad-index-docs',
|
||||
required: 'false',
|
||||
agent: '',
|
||||
options: '',
|
||||
description:
|
||||
'Create lightweight index for quick LLM scanning. Use when LLM needs to understand available docs without loading everything.',
|
||||
'output-location': '',
|
||||
outputs: '',
|
||||
},
|
||||
];
|
||||
await fs.writeFile(path.join(coreDir, 'module-help.csv'), serializeCsv(MODULE_HELP_COMPAT_COLUMNS, moduleHelpFixtureRows), 'utf8');
|
||||
await fs.writeFile(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,330 @@
|
|||
const path = require('node:path');
|
||||
const fs = require('fs-extra');
|
||||
const yaml = require('yaml');
|
||||
const csv = require('csv-parse/sync');
|
||||
const { getProjectRoot, getSourcePath } = require('../../../lib/project-root');
|
||||
|
||||
const INDEX_DOCS_AUTHORITY_VALIDATION_ERROR_CODES = Object.freeze({
|
||||
SIDECAR_FILE_NOT_FOUND: 'ERR_INDEX_DOCS_AUTHORITY_SIDECAR_FILE_NOT_FOUND',
|
||||
SIDECAR_PARSE_FAILED: 'ERR_INDEX_DOCS_AUTHORITY_SIDECAR_PARSE_FAILED',
|
||||
SIDECAR_INVALID_METADATA: 'ERR_INDEX_DOCS_AUTHORITY_SIDECAR_INVALID_METADATA',
|
||||
SIDECAR_CANONICAL_ID_MISMATCH: 'ERR_INDEX_DOCS_AUTHORITY_SIDECAR_CANONICAL_ID_MISMATCH',
|
||||
SOURCE_XML_FILE_NOT_FOUND: 'ERR_INDEX_DOCS_AUTHORITY_SOURCE_XML_FILE_NOT_FOUND',
|
||||
COMPATIBILITY_FILE_NOT_FOUND: 'ERR_INDEX_DOCS_AUTHORITY_COMPATIBILITY_FILE_NOT_FOUND',
|
||||
COMPATIBILITY_PARSE_FAILED: 'ERR_INDEX_DOCS_AUTHORITY_COMPATIBILITY_PARSE_FAILED',
|
||||
COMPATIBILITY_ROW_MISSING: 'ERR_INDEX_DOCS_AUTHORITY_COMPATIBILITY_ROW_MISSING',
|
||||
COMPATIBILITY_ROW_DUPLICATE: 'ERR_INDEX_DOCS_AUTHORITY_COMPATIBILITY_ROW_DUPLICATE',
|
||||
COMMAND_MISMATCH: 'ERR_INDEX_DOCS_AUTHORITY_COMMAND_MISMATCH',
|
||||
DISPLAY_NAME_MISMATCH: 'ERR_INDEX_DOCS_AUTHORITY_DISPLAY_NAME_MISMATCH',
|
||||
DUPLICATE_CANONICAL_COMMAND: 'ERR_INDEX_DOCS_AUTHORITY_DUPLICATE_CANONICAL_COMMAND',
|
||||
});
|
||||
|
||||
const INDEX_DOCS_LOCKED_CANONICAL_ID = 'bmad-index-docs';
|
||||
const INDEX_DOCS_LOCKED_AUTHORITATIVE_PRESENCE_KEY = `capability:${INDEX_DOCS_LOCKED_CANONICAL_ID}`;
|
||||
|
||||
class IndexDocsAuthorityValidationError extends Error {
|
||||
constructor({ code, detail, fieldPath, sourcePath, observedValue, expectedValue }) {
|
||||
const message = `${code}: ${detail} (fieldPath=${fieldPath}, sourcePath=${sourcePath})`;
|
||||
super(message);
|
||||
this.name = 'IndexDocsAuthorityValidationError';
|
||||
this.code = code;
|
||||
this.detail = detail;
|
||||
this.fieldPath = fieldPath;
|
||||
this.sourcePath = sourcePath;
|
||||
this.observedValue = observedValue;
|
||||
this.expectedValue = expectedValue;
|
||||
this.fullMessage = message;
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeSourcePath(value) {
|
||||
if (!value) return '';
|
||||
return String(value).replaceAll('\\', '/');
|
||||
}
|
||||
|
||||
function toProjectRelativePath(filePath) {
|
||||
const projectRoot = getProjectRoot();
|
||||
const relative = path.relative(projectRoot, filePath);
|
||||
|
||||
if (!relative || relative.startsWith('..')) {
|
||||
return normalizeSourcePath(path.resolve(filePath));
|
||||
}
|
||||
|
||||
return normalizeSourcePath(relative);
|
||||
}
|
||||
|
||||
function hasOwn(obj, key) {
|
||||
return Object.prototype.hasOwnProperty.call(obj, key);
|
||||
}
|
||||
|
||||
function isBlankString(value) {
|
||||
return typeof value !== 'string' || value.trim().length === 0;
|
||||
}
|
||||
|
||||
function csvMatchValue(value) {
|
||||
return String(value ?? '').trim();
|
||||
}
|
||||
|
||||
function createValidationError(code, detail, fieldPath, sourcePath, observedValue, expectedValue) {
|
||||
throw new IndexDocsAuthorityValidationError({
|
||||
code,
|
||||
detail,
|
||||
fieldPath,
|
||||
sourcePath,
|
||||
observedValue,
|
||||
expectedValue,
|
||||
});
|
||||
}
|
||||
|
||||
function ensureSidecarMetadata(sidecarData, sidecarSourcePath, sourceXmlSourcePath) {
|
||||
const requiredFields = ['canonicalId', 'displayName', 'description', 'sourcePath'];
|
||||
for (const requiredField of requiredFields) {
|
||||
if (!hasOwn(sidecarData, requiredField)) {
|
||||
createValidationError(
|
||||
INDEX_DOCS_AUTHORITY_VALIDATION_ERROR_CODES.SIDECAR_INVALID_METADATA,
|
||||
`Missing required sidecar metadata field "${requiredField}"`,
|
||||
requiredField,
|
||||
sidecarSourcePath,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
for (const requiredField of requiredFields) {
|
||||
if (isBlankString(sidecarData[requiredField])) {
|
||||
createValidationError(
|
||||
INDEX_DOCS_AUTHORITY_VALIDATION_ERROR_CODES.SIDECAR_INVALID_METADATA,
|
||||
`Required sidecar metadata field "${requiredField}" must be a non-empty string`,
|
||||
requiredField,
|
||||
sidecarSourcePath,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const normalizedCanonicalId = String(sidecarData.canonicalId).trim();
|
||||
if (normalizedCanonicalId !== INDEX_DOCS_LOCKED_CANONICAL_ID) {
|
||||
createValidationError(
|
||||
INDEX_DOCS_AUTHORITY_VALIDATION_ERROR_CODES.SIDECAR_CANONICAL_ID_MISMATCH,
|
||||
'Converted index-docs sidecar canonicalId must remain locked to bmad-index-docs',
|
||||
'canonicalId',
|
||||
sidecarSourcePath,
|
||||
normalizedCanonicalId,
|
||||
INDEX_DOCS_LOCKED_CANONICAL_ID,
|
||||
);
|
||||
}
|
||||
|
||||
const normalizedDeclaredSourcePath = normalizeSourcePath(sidecarData.sourcePath);
|
||||
if (normalizedDeclaredSourcePath !== sourceXmlSourcePath) {
|
||||
createValidationError(
|
||||
INDEX_DOCS_AUTHORITY_VALIDATION_ERROR_CODES.SIDECAR_INVALID_METADATA,
|
||||
'Sidecar sourcePath must match index-docs XML source path',
|
||||
'sourcePath',
|
||||
sidecarSourcePath,
|
||||
normalizedDeclaredSourcePath,
|
||||
sourceXmlSourcePath,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async function parseCompatibilityRows(compatibilityCatalogPath, compatibilityCatalogSourcePath) {
|
||||
if (!(await fs.pathExists(compatibilityCatalogPath))) {
|
||||
createValidationError(
|
||||
INDEX_DOCS_AUTHORITY_VALIDATION_ERROR_CODES.COMPATIBILITY_FILE_NOT_FOUND,
|
||||
'Expected module-help compatibility catalog file was not found',
|
||||
'<file>',
|
||||
compatibilityCatalogSourcePath,
|
||||
);
|
||||
}
|
||||
|
||||
let csvRaw;
|
||||
try {
|
||||
csvRaw = await fs.readFile(compatibilityCatalogPath, 'utf8');
|
||||
} catch (error) {
|
||||
createValidationError(
|
||||
INDEX_DOCS_AUTHORITY_VALIDATION_ERROR_CODES.COMPATIBILITY_PARSE_FAILED,
|
||||
`Unable to read compatibility catalog file: ${error.message}`,
|
||||
'<document>',
|
||||
compatibilityCatalogSourcePath,
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
return csv.parse(csvRaw, {
|
||||
columns: true,
|
||||
skip_empty_lines: true,
|
||||
relax_column_count: true,
|
||||
trim: true,
|
||||
});
|
||||
} catch (error) {
|
||||
createValidationError(
|
||||
INDEX_DOCS_AUTHORITY_VALIDATION_ERROR_CODES.COMPATIBILITY_PARSE_FAILED,
|
||||
`CSV parse failure: ${error.message}`,
|
||||
'<document>',
|
||||
compatibilityCatalogSourcePath,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function validateCompatibilityPrecedence({ rows, displayName, workflowFilePath, compatibilityCatalogSourcePath }) {
|
||||
const workflowMatches = rows.filter((row) => csvMatchValue(row['workflow-file']) === workflowFilePath);
|
||||
|
||||
if (workflowMatches.length === 0) {
|
||||
createValidationError(
|
||||
INDEX_DOCS_AUTHORITY_VALIDATION_ERROR_CODES.COMPATIBILITY_ROW_MISSING,
|
||||
'Converted index-docs compatibility row is missing from module-help catalog',
|
||||
'workflow-file',
|
||||
compatibilityCatalogSourcePath,
|
||||
'<missing>',
|
||||
workflowFilePath,
|
||||
);
|
||||
}
|
||||
|
||||
if (workflowMatches.length > 1) {
|
||||
createValidationError(
|
||||
INDEX_DOCS_AUTHORITY_VALIDATION_ERROR_CODES.COMPATIBILITY_ROW_DUPLICATE,
|
||||
'Converted index-docs compatibility row appears more than once in module-help catalog',
|
||||
'workflow-file',
|
||||
compatibilityCatalogSourcePath,
|
||||
workflowMatches.length,
|
||||
1,
|
||||
);
|
||||
}
|
||||
|
||||
const canonicalCommandMatches = rows.filter((row) => csvMatchValue(row.command) === INDEX_DOCS_LOCKED_CANONICAL_ID);
|
||||
if (canonicalCommandMatches.length > 1) {
|
||||
createValidationError(
|
||||
INDEX_DOCS_AUTHORITY_VALIDATION_ERROR_CODES.DUPLICATE_CANONICAL_COMMAND,
|
||||
'Converted index-docs canonical command appears in more than one compatibility row',
|
||||
'command',
|
||||
compatibilityCatalogSourcePath,
|
||||
canonicalCommandMatches.length,
|
||||
1,
|
||||
);
|
||||
}
|
||||
|
||||
const indexDocsRow = workflowMatches[0];
|
||||
const observedCommand = csvMatchValue(indexDocsRow.command);
|
||||
if (!observedCommand || observedCommand !== INDEX_DOCS_LOCKED_CANONICAL_ID) {
|
||||
createValidationError(
|
||||
INDEX_DOCS_AUTHORITY_VALIDATION_ERROR_CODES.COMMAND_MISMATCH,
|
||||
'Converted index-docs compatibility command must match locked canonical command bmad-index-docs',
|
||||
'command',
|
||||
compatibilityCatalogSourcePath,
|
||||
observedCommand || '<empty>',
|
||||
INDEX_DOCS_LOCKED_CANONICAL_ID,
|
||||
);
|
||||
}
|
||||
|
||||
const observedDisplayName = csvMatchValue(indexDocsRow.name);
|
||||
if (observedDisplayName && observedDisplayName !== displayName) {
|
||||
createValidationError(
|
||||
INDEX_DOCS_AUTHORITY_VALIDATION_ERROR_CODES.DISPLAY_NAME_MISMATCH,
|
||||
'Converted index-docs compatibility name must match sidecar displayName when provided',
|
||||
'name',
|
||||
compatibilityCatalogSourcePath,
|
||||
observedDisplayName,
|
||||
displayName,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function buildIndexDocsAuthorityRecords({ canonicalId, sidecarSourcePath, sourceXmlSourcePath }) {
|
||||
return [
|
||||
{
|
||||
recordType: 'metadata-authority',
|
||||
canonicalId,
|
||||
authoritativePresenceKey: INDEX_DOCS_LOCKED_AUTHORITATIVE_PRESENCE_KEY,
|
||||
authoritySourceType: 'sidecar',
|
||||
authoritySourcePath: sidecarSourcePath,
|
||||
sourcePath: sourceXmlSourcePath,
|
||||
},
|
||||
{
|
||||
recordType: 'source-body-authority',
|
||||
canonicalId,
|
||||
authoritativePresenceKey: INDEX_DOCS_LOCKED_AUTHORITATIVE_PRESENCE_KEY,
|
||||
authoritySourceType: 'source-xml',
|
||||
authoritySourcePath: sourceXmlSourcePath,
|
||||
sourcePath: sourceXmlSourcePath,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
async function validateIndexDocsAuthoritySplitAndPrecedence(options = {}) {
|
||||
const sidecarPath = options.sidecarPath || getSourcePath('core', 'tasks', 'index-docs.artifact.yaml');
|
||||
const sourceXmlPath = options.sourceXmlPath || getSourcePath('core', 'tasks', 'index-docs.xml');
|
||||
const compatibilityCatalogPath = options.compatibilityCatalogPath || getSourcePath('core', 'module-help.csv');
|
||||
const compatibilityWorkflowFilePath = options.compatibilityWorkflowFilePath || '_bmad/core/tasks/index-docs.xml';
|
||||
|
||||
const sidecarSourcePath = normalizeSourcePath(options.sidecarSourcePath || toProjectRelativePath(sidecarPath));
|
||||
const sourceXmlSourcePath = normalizeSourcePath(options.sourceXmlSourcePath || toProjectRelativePath(sourceXmlPath));
|
||||
const compatibilityCatalogSourcePath = normalizeSourcePath(
|
||||
options.compatibilityCatalogSourcePath || toProjectRelativePath(compatibilityCatalogPath),
|
||||
);
|
||||
|
||||
if (!(await fs.pathExists(sidecarPath))) {
|
||||
createValidationError(
|
||||
INDEX_DOCS_AUTHORITY_VALIDATION_ERROR_CODES.SIDECAR_FILE_NOT_FOUND,
|
||||
'Expected index-docs sidecar metadata file was not found',
|
||||
'<file>',
|
||||
sidecarSourcePath,
|
||||
);
|
||||
}
|
||||
|
||||
let sidecarData;
|
||||
try {
|
||||
sidecarData = yaml.parse(await fs.readFile(sidecarPath, 'utf8'));
|
||||
} catch (error) {
|
||||
createValidationError(
|
||||
INDEX_DOCS_AUTHORITY_VALIDATION_ERROR_CODES.SIDECAR_PARSE_FAILED,
|
||||
`YAML parse failure: ${error.message}`,
|
||||
'<document>',
|
||||
sidecarSourcePath,
|
||||
);
|
||||
}
|
||||
|
||||
if (!sidecarData || typeof sidecarData !== 'object' || Array.isArray(sidecarData)) {
|
||||
createValidationError(
|
||||
INDEX_DOCS_AUTHORITY_VALIDATION_ERROR_CODES.SIDECAR_INVALID_METADATA,
|
||||
'Sidecar root must be a YAML mapping object',
|
||||
'<document>',
|
||||
sidecarSourcePath,
|
||||
);
|
||||
}
|
||||
|
||||
ensureSidecarMetadata(sidecarData, sidecarSourcePath, sourceXmlSourcePath);
|
||||
|
||||
if (!(await fs.pathExists(sourceXmlPath))) {
|
||||
createValidationError(
|
||||
INDEX_DOCS_AUTHORITY_VALIDATION_ERROR_CODES.SOURCE_XML_FILE_NOT_FOUND,
|
||||
'Expected index-docs XML source file was not found',
|
||||
'<file>',
|
||||
sourceXmlSourcePath,
|
||||
);
|
||||
}
|
||||
|
||||
const compatibilityRows = await parseCompatibilityRows(compatibilityCatalogPath, compatibilityCatalogSourcePath);
|
||||
validateCompatibilityPrecedence({
|
||||
rows: compatibilityRows,
|
||||
displayName: String(sidecarData.displayName || '').trim(),
|
||||
workflowFilePath: compatibilityWorkflowFilePath,
|
||||
compatibilityCatalogSourcePath,
|
||||
});
|
||||
|
||||
const canonicalId = INDEX_DOCS_LOCKED_CANONICAL_ID;
|
||||
const authoritativeRecords = buildIndexDocsAuthorityRecords({
|
||||
canonicalId,
|
||||
sidecarSourcePath,
|
||||
sourceXmlSourcePath,
|
||||
});
|
||||
|
||||
return {
|
||||
canonicalId,
|
||||
authoritativePresenceKey: INDEX_DOCS_LOCKED_AUTHORITATIVE_PRESENCE_KEY,
|
||||
authoritativeRecords,
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
INDEX_DOCS_AUTHORITY_VALIDATION_ERROR_CODES,
|
||||
IndexDocsAuthorityValidationError,
|
||||
validateIndexDocsAuthoritySplitAndPrecedence,
|
||||
};
|
||||
|
|
@ -9,9 +9,14 @@ const { Config } = require('../../../lib/config');
|
|||
const { XmlHandler } = require('../../../lib/xml-handler');
|
||||
const { DependencyResolver } = require('./dependency-resolver');
|
||||
const { ConfigCollector } = require('./config-collector');
|
||||
const { validateHelpSidecarContractFile, validateShardDocSidecarContractFile } = require('./sidecar-contract-validator');
|
||||
const {
|
||||
validateHelpSidecarContractFile,
|
||||
validateShardDocSidecarContractFile,
|
||||
validateIndexDocsSidecarContractFile,
|
||||
} = require('./sidecar-contract-validator');
|
||||
const { validateHelpAuthoritySplitAndPrecedence } = require('./help-authority-validator');
|
||||
const { validateShardDocAuthoritySplitAndPrecedence } = require('./shard-doc-authority-validator');
|
||||
const { validateIndexDocsAuthoritySplitAndPrecedence } = require('./index-docs-authority-validator');
|
||||
const {
|
||||
HELP_CATALOG_GENERATION_ERROR_CODES,
|
||||
buildSidecarAwareExemplarHelpRow,
|
||||
|
|
@ -36,6 +41,10 @@ const EXEMPLAR_SHARD_DOC_SIDECAR_SOURCE_PATH = 'bmad-fork/src/core/tasks/shard-d
|
|||
const EXEMPLAR_SHARD_DOC_SOURCE_XML_SOURCE_PATH = 'bmad-fork/src/core/tasks/shard-doc.xml';
|
||||
const EXEMPLAR_SHARD_DOC_COMPATIBILITY_CATALOG_SOURCE_PATH = 'bmad-fork/src/core/module-help.csv';
|
||||
const EXEMPLAR_SHARD_DOC_WORKFLOW_FILE_PATH = '_bmad/core/tasks/shard-doc.xml';
|
||||
const EXEMPLAR_INDEX_DOCS_SIDECAR_SOURCE_PATH = 'bmad-fork/src/core/tasks/index-docs.artifact.yaml';
|
||||
const EXEMPLAR_INDEX_DOCS_SOURCE_XML_SOURCE_PATH = 'bmad-fork/src/core/tasks/index-docs.xml';
|
||||
const EXEMPLAR_INDEX_DOCS_COMPATIBILITY_CATALOG_SOURCE_PATH = 'bmad-fork/src/core/module-help.csv';
|
||||
const EXEMPLAR_INDEX_DOCS_WORKFLOW_FILE_PATH = '_bmad/core/tasks/index-docs.xml';
|
||||
|
||||
class Installer {
|
||||
constructor() {
|
||||
|
|
@ -51,14 +60,19 @@ class Installer {
|
|||
this.ideConfigManager = new IdeConfigManager();
|
||||
this.validateHelpSidecarContractFile = validateHelpSidecarContractFile;
|
||||
this.validateShardDocSidecarContractFile = validateShardDocSidecarContractFile;
|
||||
this.validateIndexDocsSidecarContractFile = validateIndexDocsSidecarContractFile;
|
||||
this.validateHelpAuthoritySplitAndPrecedence = validateHelpAuthoritySplitAndPrecedence;
|
||||
this.validateShardDocAuthoritySplitAndPrecedence = validateShardDocAuthoritySplitAndPrecedence;
|
||||
this.validateIndexDocsAuthoritySplitAndPrecedence = validateIndexDocsAuthoritySplitAndPrecedence;
|
||||
this.ManifestGenerator = ManifestGenerator;
|
||||
this.installedFiles = new Set(); // Track all installed files
|
||||
this.bmadFolderName = BMAD_FOLDER_NAME;
|
||||
this.helpCatalogPipelineRows = [];
|
||||
this.helpCatalogCommandLabelReportRows = [];
|
||||
this.codexExportDerivationRecords = [];
|
||||
this.helpAuthorityRecords = [];
|
||||
this.shardDocAuthorityRecords = [];
|
||||
this.indexDocsAuthorityRecords = [];
|
||||
this.latestHelpValidationRun = null;
|
||||
this.latestShardDocValidationRun = null;
|
||||
this.helpValidationHarness = new HelpValidationHarness();
|
||||
|
|
@ -71,10 +85,14 @@ class Installer {
|
|||
message('Validating shard-doc sidecar contract...');
|
||||
await this.validateShardDocSidecarContractFile();
|
||||
|
||||
message('Validating index-docs sidecar contract...');
|
||||
await this.validateIndexDocsSidecarContractFile();
|
||||
|
||||
message('Validating exemplar sidecar contract...');
|
||||
await this.validateHelpSidecarContractFile();
|
||||
|
||||
addResult('Shard-doc sidecar contract', 'ok', 'validated');
|
||||
addResult('Index-docs sidecar contract', 'ok', 'validated');
|
||||
addResult('Sidecar contract', 'ok', 'validated');
|
||||
|
||||
message('Validating shard-doc authority split and XML precedence...');
|
||||
|
|
@ -87,6 +105,16 @@ class Installer {
|
|||
this.shardDocAuthorityRecords = shardDocAuthorityValidation.authoritativeRecords;
|
||||
addResult('Shard-doc authority split', 'ok', shardDocAuthorityValidation.authoritativePresenceKey);
|
||||
|
||||
message('Validating index-docs authority split and XML precedence...');
|
||||
const indexDocsAuthorityValidation = await this.validateIndexDocsAuthoritySplitAndPrecedence({
|
||||
sidecarSourcePath: EXEMPLAR_INDEX_DOCS_SIDECAR_SOURCE_PATH,
|
||||
sourceXmlSourcePath: EXEMPLAR_INDEX_DOCS_SOURCE_XML_SOURCE_PATH,
|
||||
compatibilityCatalogSourcePath: EXEMPLAR_INDEX_DOCS_COMPATIBILITY_CATALOG_SOURCE_PATH,
|
||||
compatibilityWorkflowFilePath: EXEMPLAR_INDEX_DOCS_WORKFLOW_FILE_PATH,
|
||||
});
|
||||
this.indexDocsAuthorityRecords = indexDocsAuthorityValidation.authoritativeRecords;
|
||||
addResult('Index-docs authority split', 'ok', indexDocsAuthorityValidation.authoritativePresenceKey);
|
||||
|
||||
message('Validating authority split and frontmatter precedence...');
|
||||
const helpAuthorityValidation = await this.validateHelpAuthoritySplitAndPrecedence({
|
||||
bmadDir,
|
||||
|
|
@ -134,7 +162,11 @@ class Installer {
|
|||
ides: config.ides || [],
|
||||
preservedModules: modulesForCsvPreserve,
|
||||
helpAuthorityRecords: this.helpAuthorityRecords || [],
|
||||
taskAuthorityRecords: [...(this.helpAuthorityRecords || []), ...(this.shardDocAuthorityRecords || [])],
|
||||
taskAuthorityRecords: [
|
||||
...(this.helpAuthorityRecords || []),
|
||||
...(this.shardDocAuthorityRecords || []),
|
||||
...(this.indexDocsAuthorityRecords || []),
|
||||
],
|
||||
});
|
||||
|
||||
addResult(
|
||||
|
|
@ -1983,6 +2015,11 @@ class Installer {
|
|||
authoritySourcePath: EXEMPLAR_SHARD_DOC_SIDECAR_SOURCE_PATH,
|
||||
fallbackCanonicalId: 'bmad-shard-doc',
|
||||
});
|
||||
const indexDocsCanonicalId = this.resolveCanonicalIdFromAuthorityRecords({
|
||||
authorityRecords: this.indexDocsAuthorityRecords || [],
|
||||
authoritySourcePath: EXEMPLAR_INDEX_DOCS_SIDECAR_SOURCE_PATH,
|
||||
fallbackCanonicalId: 'bmad-index-docs',
|
||||
});
|
||||
const commandLabelContracts = [
|
||||
{
|
||||
canonicalId: sidecarAwareExemplar.canonicalId,
|
||||
|
|
@ -2002,6 +2039,15 @@ class Installer {
|
|||
workflowFilePath: EXEMPLAR_SHARD_DOC_WORKFLOW_FILE_PATH,
|
||||
nameCandidates: ['shard document', 'shard-doc'],
|
||||
},
|
||||
{
|
||||
canonicalId: indexDocsCanonicalId,
|
||||
legacyName: 'index-docs',
|
||||
displayedCommandLabel: renderDisplayedCommandLabel(indexDocsCanonicalId),
|
||||
authoritySourceType: 'sidecar',
|
||||
authoritySourcePath: EXEMPLAR_INDEX_DOCS_SIDECAR_SOURCE_PATH,
|
||||
workflowFilePath: EXEMPLAR_INDEX_DOCS_WORKFLOW_FILE_PATH,
|
||||
nameCandidates: ['index docs', 'index-docs'],
|
||||
},
|
||||
];
|
||||
let exemplarRowWritten = false;
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ const { validateTaskManifestCompatibilitySurface } = require('./projection-compa
|
|||
const packageJson = require('../../../../../package.json');
|
||||
const DEFAULT_EXEMPLAR_HELP_SIDECAR_SOURCE_PATH = 'bmad-fork/src/core/tasks/help.artifact.yaml';
|
||||
const DEFAULT_EXEMPLAR_SHARD_DOC_SIDECAR_SOURCE_PATH = 'bmad-fork/src/core/tasks/shard-doc.artifact.yaml';
|
||||
const DEFAULT_EXEMPLAR_INDEX_DOCS_SIDECAR_SOURCE_PATH = 'bmad-fork/src/core/tasks/index-docs.artifact.yaml';
|
||||
const CANONICAL_ALIAS_TABLE_COLUMNS = Object.freeze([
|
||||
'canonicalId',
|
||||
'alias',
|
||||
|
|
@ -85,6 +86,35 @@ const LOCKED_CANONICAL_ALIAS_TABLE_SHARD_DOC_ROWS = Object.freeze([
|
|||
resolutionEligibility: 'slash-command-only',
|
||||
}),
|
||||
]);
|
||||
const LOCKED_CANONICAL_ALIAS_TABLE_INDEX_DOCS_ROWS = Object.freeze([
|
||||
Object.freeze({
|
||||
canonicalId: 'bmad-index-docs',
|
||||
alias: 'bmad-index-docs',
|
||||
aliasType: 'canonical-id',
|
||||
rowIdentity: 'alias-row:bmad-index-docs:canonical-id',
|
||||
normalizedAliasValue: 'bmad-index-docs',
|
||||
rawIdentityHasLeadingSlash: false,
|
||||
resolutionEligibility: 'canonical-id-only',
|
||||
}),
|
||||
Object.freeze({
|
||||
canonicalId: 'bmad-index-docs',
|
||||
alias: 'index-docs',
|
||||
aliasType: 'legacy-name',
|
||||
rowIdentity: 'alias-row:bmad-index-docs:legacy-name',
|
||||
normalizedAliasValue: 'index-docs',
|
||||
rawIdentityHasLeadingSlash: false,
|
||||
resolutionEligibility: 'legacy-name-only',
|
||||
}),
|
||||
Object.freeze({
|
||||
canonicalId: 'bmad-index-docs',
|
||||
alias: '/bmad-index-docs',
|
||||
aliasType: 'slash-command',
|
||||
rowIdentity: 'alias-row:bmad-index-docs:slash-command',
|
||||
normalizedAliasValue: 'bmad-index-docs',
|
||||
rawIdentityHasLeadingSlash: true,
|
||||
resolutionEligibility: 'slash-command-only',
|
||||
}),
|
||||
]);
|
||||
|
||||
/**
|
||||
* Generates manifest files for installed workflows, agents, and tasks
|
||||
|
|
@ -99,6 +129,7 @@ class ManifestGenerator {
|
|||
this.files = [];
|
||||
this.selectedIdes = [];
|
||||
this.includeConvertedShardDocAliasRows = null;
|
||||
this.includeConvertedIndexDocsAliasRows = null;
|
||||
}
|
||||
|
||||
normalizeTaskAuthorityRecords(records) {
|
||||
|
|
@ -286,6 +317,9 @@ class ManifestGenerator {
|
|||
this.includeConvertedShardDocAliasRows = Object.prototype.hasOwnProperty.call(options, 'includeConvertedShardDocAliasRows')
|
||||
? options.includeConvertedShardDocAliasRows === true
|
||||
: null;
|
||||
this.includeConvertedIndexDocsAliasRows = Object.prototype.hasOwnProperty.call(options, 'includeConvertedIndexDocsAliasRows')
|
||||
? options.includeConvertedIndexDocsAliasRows === true
|
||||
: null;
|
||||
|
||||
// Filter out any undefined/null values from IDE list
|
||||
this.selectedIdes = resolvedIdes.filter((ide) => ide && typeof ide === 'string');
|
||||
|
|
@ -1183,6 +1217,20 @@ class ManifestGenerator {
|
|||
};
|
||||
}
|
||||
|
||||
resolveIndexDocsAliasAuthorityRecord() {
|
||||
const sidecarAuthorityRecord = Array.isArray(this.taskAuthorityRecords)
|
||||
? this.taskAuthorityRecords.find(
|
||||
(record) => record?.canonicalId === 'bmad-index-docs' && record?.authoritySourceType === 'sidecar' && record?.authoritySourcePath,
|
||||
)
|
||||
: null;
|
||||
return {
|
||||
authoritySourceType: sidecarAuthorityRecord ? sidecarAuthorityRecord.authoritySourceType : 'sidecar',
|
||||
authoritySourcePath: sidecarAuthorityRecord
|
||||
? sidecarAuthorityRecord.authoritySourcePath
|
||||
: DEFAULT_EXEMPLAR_INDEX_DOCS_SIDECAR_SOURCE_PATH,
|
||||
};
|
||||
}
|
||||
|
||||
hasShardDocTaskAuthorityProjection() {
|
||||
if (!Array.isArray(this.taskAuthorityRecords)) {
|
||||
return false;
|
||||
|
|
@ -1208,6 +1256,31 @@ class ManifestGenerator {
|
|||
return this.hasShardDocTaskAuthorityProjection();
|
||||
}
|
||||
|
||||
hasIndexDocsTaskAuthorityProjection() {
|
||||
if (!Array.isArray(this.taskAuthorityRecords)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.taskAuthorityRecords.some(
|
||||
(record) =>
|
||||
record?.recordType === 'metadata-authority' &&
|
||||
record?.canonicalId === 'bmad-index-docs' &&
|
||||
record?.authoritySourceType === 'sidecar' &&
|
||||
String(record?.authoritySourcePath || '').trim().length > 0,
|
||||
);
|
||||
}
|
||||
|
||||
shouldProjectIndexDocsAliasRows() {
|
||||
if (this.includeConvertedIndexDocsAliasRows === true) {
|
||||
return true;
|
||||
}
|
||||
if (this.includeConvertedIndexDocsAliasRows === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.hasIndexDocsTaskAuthorityProjection();
|
||||
}
|
||||
|
||||
buildCanonicalAliasProjectionRows() {
|
||||
const buildRows = (lockedRows, authorityRecord) =>
|
||||
lockedRows.map((row) => ({
|
||||
|
|
@ -1226,6 +1299,9 @@ class ManifestGenerator {
|
|||
if (this.shouldProjectShardDocAliasRows()) {
|
||||
rows.push(...buildRows(LOCKED_CANONICAL_ALIAS_TABLE_SHARD_DOC_ROWS, this.resolveShardDocAliasAuthorityRecord()));
|
||||
}
|
||||
if (this.shouldProjectIndexDocsAliasRows()) {
|
||||
rows.push(...buildRows(LOCKED_CANONICAL_ALIAS_TABLE_INDEX_DOCS_ROWS, this.resolveIndexDocsAliasAuthorityRecord()));
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ const PROJECTION_COMPATIBILITY_ERROR_CODES = Object.freeze({
|
|||
HELP_CATALOG_REQUIRED_COLUMN_MISSING: 'ERR_HELP_CATALOG_COMPAT_REQUIRED_COLUMN_MISSING',
|
||||
HELP_CATALOG_EXEMPLAR_ROW_CONTRACT_FAILED: 'ERR_HELP_CATALOG_COMPAT_EXEMPLAR_ROW_CONTRACT_FAILED',
|
||||
HELP_CATALOG_SHARD_DOC_ROW_CONTRACT_FAILED: 'ERR_HELP_CATALOG_COMPAT_SHARD_DOC_ROW_CONTRACT_FAILED',
|
||||
HELP_CATALOG_INDEX_DOCS_ROW_CONTRACT_FAILED: 'ERR_HELP_CATALOG_COMPAT_INDEX_DOCS_ROW_CONTRACT_FAILED',
|
||||
GITHUB_COPILOT_WORKFLOW_FILE_MISSING: 'ERR_GITHUB_COPILOT_HELP_WORKFLOW_FILE_MISSING',
|
||||
COMMAND_DOC_PARSE_FAILED: 'ERR_COMMAND_DOC_CONSISTENCY_PARSE_FAILED',
|
||||
COMMAND_DOC_CANONICAL_COMMAND_MISSING: 'ERR_COMMAND_DOC_CONSISTENCY_CANONICAL_COMMAND_MISSING',
|
||||
|
|
@ -315,6 +316,23 @@ function validateHelpCatalogLoaderEntries(rows, options = {}) {
|
|||
});
|
||||
}
|
||||
|
||||
const indexDocsRows = parsedRows.filter(
|
||||
(row) =>
|
||||
normalizeCommandValue(row.command) === 'bmad-index-docs' &&
|
||||
normalizeWorkflowPath(row['workflow-file']).endsWith('/core/tasks/index-docs.xml'),
|
||||
);
|
||||
if (indexDocsRows.length !== 1) {
|
||||
throwCompatibilityError({
|
||||
code: PROJECTION_COMPATIBILITY_ERROR_CODES.HELP_CATALOG_INDEX_DOCS_ROW_CONTRACT_FAILED,
|
||||
detail: 'Exactly one index-docs compatibility row is required for help catalog consumers',
|
||||
surface,
|
||||
fieldPath: 'rows[*].command',
|
||||
sourcePath,
|
||||
observedValue: String(indexDocsRows.length),
|
||||
expectedValue: '1',
|
||||
});
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -547,6 +547,22 @@ class ShardDocValidationHarness {
|
|||
'output-location': '',
|
||||
outputs: '',
|
||||
},
|
||||
{
|
||||
module: 'core',
|
||||
phase: 'anytime',
|
||||
name: 'Index Docs',
|
||||
code: 'ID',
|
||||
sequence: '',
|
||||
'workflow-file': `${runtimeFolder}/core/tasks/index-docs.xml`,
|
||||
command: 'bmad-index-docs',
|
||||
required: 'false',
|
||||
agent: '',
|
||||
options: '',
|
||||
description:
|
||||
'Create lightweight index for quick LLM scanning. Use when LLM needs to understand available docs without loading everything.',
|
||||
'output-location': '',
|
||||
outputs: '',
|
||||
},
|
||||
],
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ const HELP_SIDECAR_REQUIRED_FIELDS = Object.freeze([
|
|||
]);
|
||||
|
||||
const SHARD_DOC_SIDECAR_REQUIRED_FIELDS = Object.freeze([...HELP_SIDECAR_REQUIRED_FIELDS]);
|
||||
const INDEX_DOCS_SIDECAR_REQUIRED_FIELDS = Object.freeze([...HELP_SIDECAR_REQUIRED_FIELDS]);
|
||||
|
||||
const HELP_SIDECAR_ERROR_CODES = Object.freeze({
|
||||
FILE_NOT_FOUND: 'ERR_HELP_SIDECAR_FILE_NOT_FOUND',
|
||||
|
|
@ -46,8 +47,24 @@ const SHARD_DOC_SIDECAR_ERROR_CODES = Object.freeze({
|
|||
SOURCEPATH_BASENAME_MISMATCH: 'ERR_SHARD_DOC_SIDECAR_SOURCEPATH_BASENAME_MISMATCH',
|
||||
});
|
||||
|
||||
const INDEX_DOCS_SIDECAR_ERROR_CODES = Object.freeze({
|
||||
FILE_NOT_FOUND: 'ERR_INDEX_DOCS_SIDECAR_FILE_NOT_FOUND',
|
||||
PARSE_FAILED: 'ERR_INDEX_DOCS_SIDECAR_PARSE_FAILED',
|
||||
INVALID_ROOT_OBJECT: 'ERR_INDEX_DOCS_SIDECAR_INVALID_ROOT_OBJECT',
|
||||
REQUIRED_FIELD_MISSING: 'ERR_INDEX_DOCS_SIDECAR_REQUIRED_FIELD_MISSING',
|
||||
REQUIRED_FIELD_EMPTY: 'ERR_INDEX_DOCS_SIDECAR_REQUIRED_FIELD_EMPTY',
|
||||
ARTIFACT_TYPE_INVALID: 'ERR_INDEX_DOCS_SIDECAR_ARTIFACT_TYPE_INVALID',
|
||||
MODULE_INVALID: 'ERR_INDEX_DOCS_SIDECAR_MODULE_INVALID',
|
||||
DEPENDENCIES_MISSING: 'ERR_INDEX_DOCS_SIDECAR_DEPENDENCIES_MISSING',
|
||||
DEPENDENCIES_REQUIRES_INVALID: 'ERR_INDEX_DOCS_SIDECAR_DEPENDENCIES_REQUIRES_INVALID',
|
||||
DEPENDENCIES_REQUIRES_NOT_EMPTY: 'ERR_INDEX_DOCS_SIDECAR_DEPENDENCIES_REQUIRES_NOT_EMPTY',
|
||||
MAJOR_VERSION_UNSUPPORTED: 'ERR_INDEX_DOCS_SIDECAR_MAJOR_VERSION_UNSUPPORTED',
|
||||
SOURCEPATH_BASENAME_MISMATCH: 'ERR_INDEX_DOCS_SIDECAR_SOURCEPATH_BASENAME_MISMATCH',
|
||||
});
|
||||
|
||||
const HELP_EXEMPLAR_CANONICAL_SOURCE_PATH = 'bmad-fork/src/core/tasks/help.md';
|
||||
const SHARD_DOC_CANONICAL_SOURCE_PATH = 'bmad-fork/src/core/tasks/shard-doc.xml';
|
||||
const INDEX_DOCS_CANONICAL_SOURCE_PATH = 'bmad-fork/src/core/tasks/index-docs.xml';
|
||||
const SIDECAR_SUPPORTED_SCHEMA_MAJOR = 1;
|
||||
|
||||
class SidecarContractError extends Error {
|
||||
|
|
@ -257,6 +274,26 @@ function validateShardDocSidecarContractData(sidecarData, options = {}) {
|
|||
});
|
||||
}
|
||||
|
||||
function validateIndexDocsSidecarContractData(sidecarData, options = {}) {
|
||||
const sourcePath = normalizeSourcePath(options.errorSourcePath || 'src/core/tasks/index-docs.artifact.yaml');
|
||||
validateSidecarContractData(sidecarData, {
|
||||
sourcePath,
|
||||
requiredFields: INDEX_DOCS_SIDECAR_REQUIRED_FIELDS,
|
||||
requiredNonEmptyStringFields: ['canonicalId', 'sourcePath', 'displayName', 'description'],
|
||||
errorCodes: INDEX_DOCS_SIDECAR_ERROR_CODES,
|
||||
expectedArtifactType: 'task',
|
||||
expectedModule: 'core',
|
||||
expectedCanonicalSourcePath: INDEX_DOCS_CANONICAL_SOURCE_PATH,
|
||||
missingDependenciesDetail: 'Index-docs sidecar requires an explicit dependencies block.',
|
||||
dependenciesObjectDetail: 'Index-docs sidecar requires an explicit dependencies object.',
|
||||
dependenciesRequiresArrayDetail: 'Index-docs dependencies.requires must be an array.',
|
||||
dependenciesRequiresNotEmptyDetail: 'Index-docs contract requires explicit zero dependencies: dependencies.requires must be [].',
|
||||
artifactTypeDetail: 'Index-docs contract requires artifactType to equal "task".',
|
||||
moduleDetail: 'Index-docs contract requires module to equal "core".',
|
||||
requiresMustBeEmpty: true,
|
||||
});
|
||||
}
|
||||
|
||||
async function validateHelpSidecarContractFile(sidecarPath = getSourcePath('core', 'tasks', 'help.artifact.yaml'), options = {}) {
|
||||
const normalizedSourcePath = normalizeSourcePath(options.errorSourcePath || toProjectRelativePath(sidecarPath));
|
||||
|
||||
|
|
@ -313,14 +350,49 @@ async function validateShardDocSidecarContractFile(sidecarPath = getSourcePath('
|
|||
validateShardDocSidecarContractData(parsedSidecar, { errorSourcePath: normalizedSourcePath });
|
||||
}
|
||||
|
||||
async function validateIndexDocsSidecarContractFile(
|
||||
sidecarPath = getSourcePath('core', 'tasks', 'index-docs.artifact.yaml'),
|
||||
options = {},
|
||||
) {
|
||||
const normalizedSourcePath = normalizeSourcePath(options.errorSourcePath || toProjectRelativePath(sidecarPath));
|
||||
|
||||
if (!(await fs.pathExists(sidecarPath))) {
|
||||
createValidationError(
|
||||
INDEX_DOCS_SIDECAR_ERROR_CODES.FILE_NOT_FOUND,
|
||||
'<file>',
|
||||
normalizedSourcePath,
|
||||
'Expected index-docs sidecar file was not found.',
|
||||
);
|
||||
}
|
||||
|
||||
let parsedSidecar;
|
||||
try {
|
||||
const sidecarRaw = await fs.readFile(sidecarPath, 'utf8');
|
||||
parsedSidecar = yaml.parse(sidecarRaw);
|
||||
} catch (error) {
|
||||
createValidationError(
|
||||
INDEX_DOCS_SIDECAR_ERROR_CODES.PARSE_FAILED,
|
||||
'<document>',
|
||||
normalizedSourcePath,
|
||||
`YAML parse failure: ${error.message}`,
|
||||
);
|
||||
}
|
||||
|
||||
validateIndexDocsSidecarContractData(parsedSidecar, { errorSourcePath: normalizedSourcePath });
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
HELP_SIDECAR_REQUIRED_FIELDS,
|
||||
SHARD_DOC_SIDECAR_REQUIRED_FIELDS,
|
||||
INDEX_DOCS_SIDECAR_REQUIRED_FIELDS,
|
||||
HELP_SIDECAR_ERROR_CODES,
|
||||
SHARD_DOC_SIDECAR_ERROR_CODES,
|
||||
INDEX_DOCS_SIDECAR_ERROR_CODES,
|
||||
SidecarContractError,
|
||||
validateHelpSidecarContractData,
|
||||
validateHelpSidecarContractFile,
|
||||
validateShardDocSidecarContractData,
|
||||
validateShardDocSidecarContractFile,
|
||||
validateIndexDocsSidecarContractData,
|
||||
validateIndexDocsSidecarContractFile,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -21,8 +21,10 @@ const CODEX_EXPORT_DERIVATION_ERROR_CODES = Object.freeze({
|
|||
|
||||
const EXEMPLAR_HELP_TASK_MARKDOWN_SOURCE_PATH = 'bmad-fork/src/core/tasks/help.md';
|
||||
const EXEMPLAR_SHARD_DOC_TASK_XML_SOURCE_PATH = 'bmad-fork/src/core/tasks/shard-doc.xml';
|
||||
const EXEMPLAR_INDEX_DOCS_TASK_XML_SOURCE_PATH = 'bmad-fork/src/core/tasks/index-docs.xml';
|
||||
const EXEMPLAR_HELP_SIDECAR_CONTRACT_SOURCE_PATH = 'bmad-fork/src/core/tasks/help.artifact.yaml';
|
||||
const EXEMPLAR_SHARD_DOC_SIDECAR_CONTRACT_SOURCE_PATH = 'bmad-fork/src/core/tasks/shard-doc.artifact.yaml';
|
||||
const EXEMPLAR_INDEX_DOCS_SIDECAR_CONTRACT_SOURCE_PATH = 'bmad-fork/src/core/tasks/index-docs.artifact.yaml';
|
||||
const EXEMPLAR_HELP_EXPORT_DERIVATION_SOURCE_TYPE = 'sidecar-canonical-id';
|
||||
const SHARD_DOC_EXPORT_ALIAS_ROWS = Object.freeze([
|
||||
Object.freeze({
|
||||
|
|
@ -44,6 +46,26 @@ const SHARD_DOC_EXPORT_ALIAS_ROWS = Object.freeze([
|
|||
rawIdentityHasLeadingSlash: true,
|
||||
}),
|
||||
]);
|
||||
const INDEX_DOCS_EXPORT_ALIAS_ROWS = Object.freeze([
|
||||
Object.freeze({
|
||||
rowIdentity: 'alias-row:bmad-index-docs:canonical-id',
|
||||
canonicalId: 'bmad-index-docs',
|
||||
normalizedAliasValue: 'bmad-index-docs',
|
||||
rawIdentityHasLeadingSlash: false,
|
||||
}),
|
||||
Object.freeze({
|
||||
rowIdentity: 'alias-row:bmad-index-docs:legacy-name',
|
||||
canonicalId: 'bmad-index-docs',
|
||||
normalizedAliasValue: 'index-docs',
|
||||
rawIdentityHasLeadingSlash: false,
|
||||
}),
|
||||
Object.freeze({
|
||||
rowIdentity: 'alias-row:bmad-index-docs:slash-command',
|
||||
canonicalId: 'bmad-index-docs',
|
||||
normalizedAliasValue: 'bmad-index-docs',
|
||||
rawIdentityHasLeadingSlash: true,
|
||||
}),
|
||||
]);
|
||||
const EXEMPLAR_CONVERTED_TASK_EXPORT_TARGETS = Object.freeze({
|
||||
help: Object.freeze({
|
||||
taskSourcePath: EXEMPLAR_HELP_TASK_MARKDOWN_SOURCE_PATH,
|
||||
|
|
@ -62,6 +84,7 @@ const EXEMPLAR_CONVERTED_TASK_EXPORT_TARGETS = Object.freeze({
|
|||
taskSourcePath: EXEMPLAR_SHARD_DOC_TASK_XML_SOURCE_PATH,
|
||||
sourcePathSuffix: '/core/tasks/shard-doc.xml',
|
||||
sidecarSourcePath: EXEMPLAR_SHARD_DOC_SIDECAR_CONTRACT_SOURCE_PATH,
|
||||
aliasRows: SHARD_DOC_EXPORT_ALIAS_ROWS,
|
||||
sidecarSourceCandidates: Object.freeze([
|
||||
Object.freeze({
|
||||
segments: ['bmad-fork', 'src', 'core', 'tasks', 'shard-doc.artifact.yaml'],
|
||||
|
|
@ -71,6 +94,20 @@ const EXEMPLAR_CONVERTED_TASK_EXPORT_TARGETS = Object.freeze({
|
|||
}),
|
||||
]),
|
||||
}),
|
||||
'index-docs': Object.freeze({
|
||||
taskSourcePath: EXEMPLAR_INDEX_DOCS_TASK_XML_SOURCE_PATH,
|
||||
sourcePathSuffix: '/core/tasks/index-docs.xml',
|
||||
sidecarSourcePath: EXEMPLAR_INDEX_DOCS_SIDECAR_CONTRACT_SOURCE_PATH,
|
||||
aliasRows: INDEX_DOCS_EXPORT_ALIAS_ROWS,
|
||||
sidecarSourceCandidates: Object.freeze([
|
||||
Object.freeze({
|
||||
segments: ['bmad-fork', 'src', 'core', 'tasks', 'index-docs.artifact.yaml'],
|
||||
}),
|
||||
Object.freeze({
|
||||
segments: ['src', 'core', 'tasks', 'index-docs.artifact.yaml'],
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
});
|
||||
|
||||
class CodexExportDerivationError extends Error {
|
||||
|
|
@ -412,8 +449,8 @@ class CodexSetup extends BaseIdeSetup {
|
|||
fieldPath: 'canonicalId',
|
||||
sourcePath: sidecarData.sourcePath,
|
||||
};
|
||||
if (exportTarget.taskSourcePath === EXEMPLAR_SHARD_DOC_TASK_XML_SOURCE_PATH) {
|
||||
aliasResolutionOptions.aliasRows = SHARD_DOC_EXPORT_ALIAS_ROWS;
|
||||
if (Array.isArray(exportTarget.aliasRows)) {
|
||||
aliasResolutionOptions.aliasRows = exportTarget.aliasRows;
|
||||
aliasResolutionOptions.aliasTableSourcePath = '_bmad/_config/canonical-aliases.csv';
|
||||
}
|
||||
canonicalResolution = await normalizeAndResolveExemplarAlias(sidecarData.canonicalId, aliasResolutionOptions);
|
||||
|
|
|
|||
Loading…
Reference in New Issue