feat(installer): harden skill metadata authority and validation
This commit is contained in:
parent
b298ff456d
commit
239bc9d206
|
|
@ -36,6 +36,8 @@ const {
|
|||
SHARD_DOC_SIDECAR_ERROR_CODES,
|
||||
INDEX_DOCS_SIDECAR_REQUIRED_FIELDS,
|
||||
INDEX_DOCS_SIDECAR_ERROR_CODES,
|
||||
SKILL_METADATA_RESOLUTION_ERROR_CODES,
|
||||
resolveSkillMetadataAuthority,
|
||||
validateHelpSidecarContractFile,
|
||||
validateShardDocSidecarContractFile,
|
||||
validateIndexDocsSidecarContractFile,
|
||||
|
|
@ -255,7 +257,7 @@ async function runTests() {
|
|||
|
||||
const tempSidecarRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-help-sidecar-'));
|
||||
const tempSidecarPath = path.join(tempSidecarRoot, 'help.artifact.yaml');
|
||||
const deterministicSourcePath = 'bmad-fork/src/core/tasks/help.artifact.yaml';
|
||||
const deterministicSourcePath = 'bmad-fork/src/core/tasks/help/skill-manifest.yaml';
|
||||
const expectedUnsupportedMajorDetail = 'sidecar schema major version is unsupported';
|
||||
const expectedBasenameMismatchDetail = 'sidecar basename does not match sourcePath basename';
|
||||
|
||||
|
|
@ -434,7 +436,7 @@ async function runTests() {
|
|||
|
||||
const tempShardDocRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-shard-doc-sidecar-'));
|
||||
const tempShardDocSidecarPath = path.join(tempShardDocRoot, 'shard-doc.artifact.yaml');
|
||||
const deterministicShardDocSourcePath = 'bmad-fork/src/core/tasks/shard-doc.artifact.yaml';
|
||||
const deterministicShardDocSourcePath = 'bmad-fork/src/core/tasks/shard-doc/skill-manifest.yaml';
|
||||
|
||||
const writeTempShardDocSidecar = async (data) => {
|
||||
await fs.writeFile(tempShardDocSidecarPath, yaml.stringify(data), 'utf8');
|
||||
|
|
@ -631,7 +633,7 @@ async function runTests() {
|
|||
|
||||
const tempIndexDocsRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-index-docs-sidecar-'));
|
||||
const tempIndexDocsSidecarPath = path.join(tempIndexDocsRoot, 'index-docs.artifact.yaml');
|
||||
const deterministicIndexDocsSourcePath = 'bmad-fork/src/core/tasks/index-docs.artifact.yaml';
|
||||
const deterministicIndexDocsSourcePath = 'bmad-fork/src/core/tasks/index-docs/skill-manifest.yaml';
|
||||
|
||||
const writeTempIndexDocsSidecar = async (data) => {
|
||||
await fs.writeFile(tempIndexDocsSidecarPath, yaml.stringify(data), 'utf8');
|
||||
|
|
@ -803,6 +805,140 @@ async function runTests() {
|
|||
|
||||
console.log('');
|
||||
|
||||
// ============================================================
|
||||
// Test 4d: Skill Metadata Filename Authority Resolution
|
||||
// ============================================================
|
||||
console.log(`${colors.yellow}Test Suite 4d: Skill Metadata Filename Authority Resolution${colors.reset}\n`);
|
||||
try {
|
||||
const convertedCapabilitySources = [
|
||||
{ label: 'help', sourceFilename: 'help.md', artifactFilename: 'help.artifact.yaml' },
|
||||
{ label: 'shard-doc', sourceFilename: 'shard-doc.xml', artifactFilename: 'shard-doc.artifact.yaml' },
|
||||
{ label: 'index-docs', sourceFilename: 'index-docs.xml', artifactFilename: 'index-docs.artifact.yaml' },
|
||||
];
|
||||
|
||||
const withResolverWorkspace = async (sourceFilename, callback) => {
|
||||
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), `bmad-metadata-authority-${sourceFilename.replaceAll(/\W+/g, '-')}-`));
|
||||
try {
|
||||
const tasksDir = path.join(tempRoot, 'src', 'core', 'tasks');
|
||||
await fs.ensureDir(tasksDir);
|
||||
|
||||
const sourcePath = path.join(tasksDir, sourceFilename);
|
||||
await fs.writeFile(sourcePath, '# source\n', 'utf8');
|
||||
|
||||
const sourceStem = path.basename(sourceFilename, path.extname(sourceFilename));
|
||||
const skillDir = path.join(tasksDir, sourceStem);
|
||||
await fs.ensureDir(skillDir);
|
||||
|
||||
await callback({
|
||||
tempRoot,
|
||||
tasksDir,
|
||||
sourcePath,
|
||||
skillDir,
|
||||
});
|
||||
} finally {
|
||||
await fs.remove(tempRoot);
|
||||
}
|
||||
};
|
||||
|
||||
for (const sourceConfig of convertedCapabilitySources) {
|
||||
const { label, sourceFilename, artifactFilename } = sourceConfig;
|
||||
|
||||
await withResolverWorkspace(sourceFilename, async ({ tempRoot, tasksDir, sourcePath, skillDir }) => {
|
||||
await fs.writeFile(path.join(skillDir, 'skill-manifest.yaml'), 'canonicalId: canonical\n', 'utf8');
|
||||
await fs.writeFile(path.join(skillDir, 'bmad-config.yaml'), 'canonicalId: bmad-config\n', 'utf8');
|
||||
await fs.writeFile(path.join(skillDir, 'manifest.yaml'), 'canonicalId: manifest\n', 'utf8');
|
||||
await fs.writeFile(path.join(tasksDir, artifactFilename), 'canonicalId: artifact\n', 'utf8');
|
||||
|
||||
const resolution = await resolveSkillMetadataAuthority({
|
||||
sourceFilePath: sourcePath,
|
||||
projectRoot: tempRoot,
|
||||
});
|
||||
assert(
|
||||
resolution.resolvedFilename === 'skill-manifest.yaml' && resolution.derivationMode === 'canonical',
|
||||
`${label} resolver prioritizes per-skill canonical skill-manifest.yaml over legacy metadata files`,
|
||||
);
|
||||
});
|
||||
|
||||
await withResolverWorkspace(sourceFilename, async ({ tempRoot, tasksDir, sourcePath, skillDir }) => {
|
||||
await fs.writeFile(path.join(skillDir, 'bmad-config.yaml'), 'canonicalId: bmad-config\n', 'utf8');
|
||||
await fs.writeFile(path.join(skillDir, 'manifest.yaml'), 'canonicalId: manifest\n', 'utf8');
|
||||
await fs.writeFile(path.join(tasksDir, artifactFilename), 'canonicalId: artifact\n', 'utf8');
|
||||
|
||||
const resolution = await resolveSkillMetadataAuthority({
|
||||
sourceFilePath: sourcePath,
|
||||
projectRoot: tempRoot,
|
||||
});
|
||||
assert(
|
||||
resolution.resolvedFilename === 'bmad-config.yaml' && resolution.derivationMode === 'legacy-fallback',
|
||||
`${label} resolver falls back to bmad-config.yaml before manifest.yaml and *.artifact.yaml`,
|
||||
);
|
||||
});
|
||||
|
||||
await withResolverWorkspace(sourceFilename, async ({ tempRoot, tasksDir, sourcePath, skillDir }) => {
|
||||
await fs.writeFile(path.join(skillDir, 'manifest.yaml'), 'canonicalId: manifest\n', 'utf8');
|
||||
await fs.writeFile(path.join(tasksDir, artifactFilename), 'canonicalId: artifact\n', 'utf8');
|
||||
|
||||
const resolution = await resolveSkillMetadataAuthority({
|
||||
sourceFilePath: sourcePath,
|
||||
projectRoot: tempRoot,
|
||||
});
|
||||
assert(
|
||||
resolution.resolvedFilename === 'manifest.yaml' && resolution.derivationMode === 'legacy-fallback',
|
||||
`${label} resolver falls back to manifest.yaml before *.artifact.yaml`,
|
||||
);
|
||||
});
|
||||
|
||||
await withResolverWorkspace(sourceFilename, async ({ tempRoot, tasksDir, sourcePath }) => {
|
||||
await fs.writeFile(path.join(tasksDir, artifactFilename), 'canonicalId: artifact\n', 'utf8');
|
||||
|
||||
const resolution = await resolveSkillMetadataAuthority({
|
||||
sourceFilePath: sourcePath,
|
||||
projectRoot: tempRoot,
|
||||
});
|
||||
assert(
|
||||
resolution.resolvedFilename === artifactFilename && resolution.derivationMode === 'legacy-fallback',
|
||||
`${label} resolver supports capability-scoped *.artifact.yaml fallback`,
|
||||
);
|
||||
});
|
||||
|
||||
await withResolverWorkspace(sourceFilename, async ({ tempRoot, tasksDir, sourcePath }) => {
|
||||
await fs.writeFile(path.join(tasksDir, 'skill-manifest.yaml'), 'canonicalId: root-canonical\n', 'utf8');
|
||||
await fs.writeFile(path.join(tasksDir, artifactFilename), 'canonicalId: artifact\n', 'utf8');
|
||||
|
||||
const resolution = await resolveSkillMetadataAuthority({
|
||||
sourceFilePath: sourcePath,
|
||||
projectRoot: tempRoot,
|
||||
});
|
||||
assert(
|
||||
resolution.resolvedFilename === artifactFilename,
|
||||
`${label} resolver does not treat root task-folder skill-manifest.yaml as per-skill canonical authority`,
|
||||
);
|
||||
});
|
||||
|
||||
await withResolverWorkspace(sourceFilename, async ({ tempRoot, tasksDir, sourcePath, skillDir }) => {
|
||||
await fs.writeFile(path.join(tasksDir, 'bmad-config.yaml'), 'canonicalId: root-bmad-config\n', 'utf8');
|
||||
await fs.writeFile(path.join(skillDir, 'bmad-config.yaml'), 'canonicalId: skill-bmad-config\n', 'utf8');
|
||||
|
||||
try {
|
||||
await resolveSkillMetadataAuthority({
|
||||
sourceFilePath: sourcePath,
|
||||
projectRoot: tempRoot,
|
||||
});
|
||||
assert(false, `${label} resolver rejects ambiguous bmad-config.yaml coexistence across legacy locations`);
|
||||
} catch (error) {
|
||||
assert(
|
||||
error.code === SKILL_METADATA_RESOLUTION_ERROR_CODES.AMBIGUOUS_MATCH,
|
||||
`${label} resolver emits deterministic ambiguity code for bmad-config.yaml coexistence`,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
assert(false, 'Skill metadata filename authority resolver suite setup', error.message);
|
||||
}
|
||||
|
||||
console.log('');
|
||||
|
||||
// ============================================================
|
||||
// Test 5: Authority Split and Frontmatter Precedence
|
||||
// ============================================================
|
||||
|
|
@ -814,7 +950,7 @@ async function runTests() {
|
|||
const tempAuthorityRuntimePath = path.join(tempAuthorityRoot, 'help-runtime.md');
|
||||
|
||||
const deterministicAuthorityPaths = {
|
||||
sidecar: 'bmad-fork/src/core/tasks/help.artifact.yaml',
|
||||
sidecar: 'bmad-fork/src/core/tasks/help/skill-manifest.yaml',
|
||||
source: 'bmad-fork/src/core/tasks/help.md',
|
||||
runtime: '_bmad/core/tasks/help.md',
|
||||
};
|
||||
|
|
@ -1000,7 +1136,7 @@ async function runTests() {
|
|||
const tempShardDocModuleHelpPath = path.join(tempAuthorityRoot, 'module-help.csv');
|
||||
|
||||
const deterministicShardDocAuthorityPaths = {
|
||||
sidecar: 'bmad-fork/src/core/tasks/shard-doc.artifact.yaml',
|
||||
sidecar: 'bmad-fork/src/core/tasks/shard-doc/skill-manifest.yaml',
|
||||
source: 'bmad-fork/src/core/tasks/shard-doc.xml',
|
||||
compatibility: 'bmad-fork/src/core/module-help.csv',
|
||||
workflowFile: '_bmad/core/tasks/shard-doc.xml',
|
||||
|
|
@ -1211,7 +1347,7 @@ async function runTests() {
|
|||
const tempIndexDocsModuleHelpPath = path.join(tempAuthorityRoot, 'index-docs-module-help.csv');
|
||||
|
||||
const deterministicIndexDocsAuthorityPaths = {
|
||||
sidecar: 'bmad-fork/src/core/tasks/index-docs.artifact.yaml',
|
||||
sidecar: 'bmad-fork/src/core/tasks/index-docs/skill-manifest.yaml',
|
||||
source: 'bmad-fork/src/core/tasks/index-docs.xml',
|
||||
compatibility: 'bmad-fork/src/core/module-help.csv',
|
||||
workflowFile: '_bmad/core/tasks/index-docs.xml',
|
||||
|
|
@ -1546,7 +1682,7 @@ async function runTests() {
|
|||
|
||||
// 6b: Shard-doc fail-fast covers Shard-doc negative matrix classes.
|
||||
{
|
||||
const deterministicShardDocFailFastSourcePath = 'bmad-fork/src/core/tasks/shard-doc.artifact.yaml';
|
||||
const deterministicShardDocFailFastSourcePath = 'bmad-fork/src/core/tasks/shard-doc/skill-manifest.yaml';
|
||||
const shardDocFailureScenarios = [
|
||||
{
|
||||
label: 'missing shard-doc sidecar file',
|
||||
|
|
@ -1793,7 +1929,7 @@ async function runTests() {
|
|||
const error = new Error('Converted shard-doc sidecar canonicalId must remain locked to bmad-shard-doc');
|
||||
error.code = SHARD_DOC_AUTHORITY_VALIDATION_ERROR_CODES.SIDECAR_CANONICAL_ID_MISMATCH;
|
||||
error.fieldPath = 'canonicalId';
|
||||
error.sourcePath = 'bmad-fork/src/core/tasks/shard-doc.artifact.yaml';
|
||||
error.sourcePath = 'bmad-fork/src/core/tasks/shard-doc/skill-manifest.yaml';
|
||||
throw error;
|
||||
};
|
||||
installer.validateIndexDocsAuthoritySplitAndPrecedence = async () => {
|
||||
|
|
@ -1847,7 +1983,7 @@ async function runTests() {
|
|||
);
|
||||
assert(error.fieldPath === 'canonicalId', 'Installer shard-doc canonical drift returns deterministic field path');
|
||||
assert(
|
||||
error.sourcePath === 'bmad-fork/src/core/tasks/shard-doc.artifact.yaml',
|
||||
error.sourcePath === 'bmad-fork/src/core/tasks/shard-doc/skill-manifest.yaml',
|
||||
'Installer shard-doc canonical drift returns deterministic source path',
|
||||
);
|
||||
assert(
|
||||
|
|
@ -2064,7 +2200,7 @@ async function runTests() {
|
|||
const tempAliasConfigDir = path.join(tempAliasAuthorityRoot, '_config');
|
||||
const tempAuthorityAliasTablePath = path.join(tempAliasConfigDir, 'canonical-aliases.csv');
|
||||
const aliasAuthorityPaths = {
|
||||
sidecar: 'bmad-fork/src/core/tasks/help.artifact.yaml',
|
||||
sidecar: 'bmad-fork/src/core/tasks/help/skill-manifest.yaml',
|
||||
source: 'bmad-fork/src/core/tasks/help.md',
|
||||
runtime: '_bmad/core/tasks/help.md',
|
||||
};
|
||||
|
|
@ -2443,7 +2579,7 @@ async function runTests() {
|
|||
canonicalId: 'bmad-help',
|
||||
authoritativePresenceKey: 'capability:bmad-help',
|
||||
authoritySourceType: 'sidecar',
|
||||
authoritySourcePath: 'bmad-fork/src/core/tasks/help.artifact.yaml',
|
||||
authoritySourcePath: 'bmad-fork/src/core/tasks/help/skill-manifest.yaml',
|
||||
sourcePath: 'bmad-fork/src/core/tasks/help.md',
|
||||
},
|
||||
];
|
||||
|
|
@ -2454,7 +2590,7 @@ async function runTests() {
|
|||
canonicalId: 'bmad-shard-doc',
|
||||
authoritativePresenceKey: 'capability:bmad-shard-doc',
|
||||
authoritySourceType: 'sidecar',
|
||||
authoritySourcePath: 'bmad-fork/src/core/tasks/shard-doc.artifact.yaml',
|
||||
authoritySourcePath: 'bmad-fork/src/core/tasks/shard-doc/skill-manifest.yaml',
|
||||
sourcePath: 'bmad-fork/src/core/tasks/shard-doc.xml',
|
||||
},
|
||||
{
|
||||
|
|
@ -2462,7 +2598,7 @@ async function runTests() {
|
|||
canonicalId: 'bmad-index-docs',
|
||||
authoritativePresenceKey: 'capability:bmad-index-docs',
|
||||
authoritySourceType: 'sidecar',
|
||||
authoritySourcePath: 'bmad-fork/src/core/tasks/index-docs.artifact.yaml',
|
||||
authoritySourcePath: 'bmad-fork/src/core/tasks/index-docs/skill-manifest.yaml',
|
||||
sourcePath: 'bmad-fork/src/core/tasks/index-docs.xml',
|
||||
},
|
||||
];
|
||||
|
|
@ -2495,7 +2631,7 @@ async function runTests() {
|
|||
assert(helpTaskRow && helpTaskRow.canonicalId === 'bmad-help', 'Task manifest help row sets canonicalId=bmad-help');
|
||||
assert(helpTaskRow && helpTaskRow.authoritySourceType === 'sidecar', 'Task manifest help row sets authoritySourceType=sidecar');
|
||||
assert(
|
||||
helpTaskRow && helpTaskRow.authoritySourcePath === 'bmad-fork/src/core/tasks/help.artifact.yaml',
|
||||
helpTaskRow && helpTaskRow.authoritySourcePath === 'bmad-fork/src/core/tasks/help/skill-manifest.yaml',
|
||||
'Task manifest help row sets authoritySourcePath to sidecar source path',
|
||||
);
|
||||
|
||||
|
|
@ -2515,7 +2651,7 @@ async function runTests() {
|
|||
'Task manifest shard-doc row sets authoritySourceType=sidecar',
|
||||
);
|
||||
assert(
|
||||
shardDocTaskRow && shardDocTaskRow.authoritySourcePath === 'bmad-fork/src/core/tasks/shard-doc.artifact.yaml',
|
||||
shardDocTaskRow && shardDocTaskRow.authoritySourcePath === 'bmad-fork/src/core/tasks/shard-doc/skill-manifest.yaml',
|
||||
'Task manifest shard-doc row sets authoritySourcePath to shard-doc sidecar source path',
|
||||
);
|
||||
assert(!!indexDocsTaskRow, 'Task manifest includes converted index-docs row');
|
||||
|
|
@ -2529,7 +2665,7 @@ async function runTests() {
|
|||
'Task manifest index-docs row sets authoritySourceType=sidecar',
|
||||
);
|
||||
assert(
|
||||
indexDocsTaskRow && indexDocsTaskRow.authoritySourcePath === 'bmad-fork/src/core/tasks/index-docs.artifact.yaml',
|
||||
indexDocsTaskRow && indexDocsTaskRow.authoritySourcePath === 'bmad-fork/src/core/tasks/index-docs/skill-manifest.yaml',
|
||||
'Task manifest index-docs row sets authoritySourcePath to index-docs sidecar source path',
|
||||
);
|
||||
|
||||
|
|
@ -2642,7 +2778,7 @@ async function runTests() {
|
|||
|
||||
assert(
|
||||
capturedAuthorityValidationOptions &&
|
||||
capturedAuthorityValidationOptions.sidecarSourcePath === 'bmad-fork/src/core/tasks/help.artifact.yaml',
|
||||
capturedAuthorityValidationOptions.sidecarSourcePath === 'bmad-fork/src/core/tasks/help/skill-manifest.yaml',
|
||||
'Installer passes locked sidecar source path to authority validation',
|
||||
);
|
||||
assert(
|
||||
|
|
@ -2656,7 +2792,7 @@ async function runTests() {
|
|||
);
|
||||
assert(
|
||||
capturedShardDocAuthorityValidationOptions &&
|
||||
capturedShardDocAuthorityValidationOptions.sidecarSourcePath === 'bmad-fork/src/core/tasks/shard-doc.artifact.yaml',
|
||||
capturedShardDocAuthorityValidationOptions.sidecarSourcePath === 'bmad-fork/src/core/tasks/shard-doc/skill-manifest.yaml',
|
||||
'Installer passes locked shard-doc sidecar source path to shard-doc authority validation',
|
||||
);
|
||||
assert(
|
||||
|
|
@ -2671,7 +2807,7 @@ async function runTests() {
|
|||
);
|
||||
assert(
|
||||
capturedIndexDocsAuthorityValidationOptions &&
|
||||
capturedIndexDocsAuthorityValidationOptions.sidecarSourcePath === 'bmad-fork/src/core/tasks/index-docs.artifact.yaml',
|
||||
capturedIndexDocsAuthorityValidationOptions.sidecarSourcePath === 'bmad-fork/src/core/tasks/index-docs/skill-manifest.yaml',
|
||||
'Installer passes locked index-docs sidecar source path to index-docs authority validation',
|
||||
);
|
||||
assert(
|
||||
|
|
@ -2687,7 +2823,7 @@ async function runTests() {
|
|||
assert(
|
||||
Array.isArray(capturedManifestHelpAuthorityRecords) &&
|
||||
capturedManifestHelpAuthorityRecords[0] &&
|
||||
capturedManifestHelpAuthorityRecords[0].authoritySourcePath === 'bmad-fork/src/core/tasks/help.artifact.yaml',
|
||||
capturedManifestHelpAuthorityRecords[0].authoritySourcePath === 'bmad-fork/src/core/tasks/help/skill-manifest.yaml',
|
||||
'Installer passes sidecar authority path into manifest generation options',
|
||||
);
|
||||
assert(
|
||||
|
|
@ -2697,7 +2833,7 @@ async function runTests() {
|
|||
record &&
|
||||
record.canonicalId === 'bmad-shard-doc' &&
|
||||
record.authoritySourceType === 'sidecar' &&
|
||||
record.authoritySourcePath === 'bmad-fork/src/core/tasks/shard-doc.artifact.yaml',
|
||||
record.authoritySourcePath === 'bmad-fork/src/core/tasks/shard-doc/skill-manifest.yaml',
|
||||
),
|
||||
'Installer passes shard-doc sidecar authority records into task-manifest projection options',
|
||||
);
|
||||
|
|
@ -2708,7 +2844,7 @@ async function runTests() {
|
|||
record &&
|
||||
record.canonicalId === 'bmad-index-docs' &&
|
||||
record.authoritySourceType === 'sidecar' &&
|
||||
record.authoritySourcePath === 'bmad-fork/src/core/tasks/index-docs.artifact.yaml',
|
||||
record.authoritySourcePath === 'bmad-fork/src/core/tasks/index-docs/skill-manifest.yaml',
|
||||
),
|
||||
'Installer passes index-docs sidecar authority records into task-manifest projection options',
|
||||
);
|
||||
|
|
@ -2741,7 +2877,7 @@ async function runTests() {
|
|||
canonicalId: 'bmad-help',
|
||||
authoritativePresenceKey: 'capability:bmad-help',
|
||||
authoritySourceType: 'sidecar',
|
||||
authoritySourcePath: 'bmad-fork/src/core/tasks/help.artifact.yaml',
|
||||
authoritySourcePath: 'bmad-fork/src/core/tasks/help/skill-manifest.yaml',
|
||||
sourcePath: 'bmad-fork/src/core/tasks/help.md',
|
||||
},
|
||||
];
|
||||
|
|
@ -2752,7 +2888,7 @@ async function runTests() {
|
|||
canonicalId: 'bmad-shard-doc',
|
||||
authoritativePresenceKey: 'capability:bmad-shard-doc',
|
||||
authoritySourceType: 'sidecar',
|
||||
authoritySourcePath: 'bmad-fork/src/core/tasks/shard-doc.artifact.yaml',
|
||||
authoritySourcePath: 'bmad-fork/src/core/tasks/shard-doc/skill-manifest.yaml',
|
||||
sourcePath: 'bmad-fork/src/core/tasks/shard-doc.xml',
|
||||
},
|
||||
{
|
||||
|
|
@ -2760,7 +2896,7 @@ async function runTests() {
|
|||
canonicalId: 'bmad-index-docs',
|
||||
authoritativePresenceKey: 'capability:bmad-index-docs',
|
||||
authoritySourceType: 'sidecar',
|
||||
authoritySourcePath: 'bmad-fork/src/core/tasks/index-docs.artifact.yaml',
|
||||
authoritySourcePath: 'bmad-fork/src/core/tasks/index-docs/skill-manifest.yaml',
|
||||
sourcePath: 'bmad-fork/src/core/tasks/index-docs.xml',
|
||||
},
|
||||
];
|
||||
|
|
@ -2797,7 +2933,7 @@ async function runTests() {
|
|||
canonicalId: 'bmad-help',
|
||||
alias: 'bmad-help',
|
||||
aliasType: 'canonical-id',
|
||||
authoritySourcePath: 'bmad-fork/src/core/tasks/help.artifact.yaml',
|
||||
authoritySourcePath: 'bmad-fork/src/core/tasks/help/skill-manifest.yaml',
|
||||
normalizedAliasValue: 'bmad-help',
|
||||
rawIdentityHasLeadingSlash: 'false',
|
||||
resolutionEligibility: 'canonical-id-only',
|
||||
|
|
@ -2809,7 +2945,7 @@ async function runTests() {
|
|||
canonicalId: 'bmad-help',
|
||||
alias: 'help',
|
||||
aliasType: 'legacy-name',
|
||||
authoritySourcePath: 'bmad-fork/src/core/tasks/help.artifact.yaml',
|
||||
authoritySourcePath: 'bmad-fork/src/core/tasks/help/skill-manifest.yaml',
|
||||
normalizedAliasValue: 'help',
|
||||
rawIdentityHasLeadingSlash: 'false',
|
||||
resolutionEligibility: 'legacy-name-only',
|
||||
|
|
@ -2821,7 +2957,7 @@ async function runTests() {
|
|||
canonicalId: 'bmad-help',
|
||||
alias: '/bmad-help',
|
||||
aliasType: 'slash-command',
|
||||
authoritySourcePath: 'bmad-fork/src/core/tasks/help.artifact.yaml',
|
||||
authoritySourcePath: 'bmad-fork/src/core/tasks/help/skill-manifest.yaml',
|
||||
normalizedAliasValue: 'bmad-help',
|
||||
rawIdentityHasLeadingSlash: 'true',
|
||||
resolutionEligibility: 'slash-command-only',
|
||||
|
|
@ -2833,7 +2969,7 @@ async function runTests() {
|
|||
canonicalId: 'bmad-shard-doc',
|
||||
alias: 'bmad-shard-doc',
|
||||
aliasType: 'canonical-id',
|
||||
authoritySourcePath: 'bmad-fork/src/core/tasks/shard-doc.artifact.yaml',
|
||||
authoritySourcePath: 'bmad-fork/src/core/tasks/shard-doc/skill-manifest.yaml',
|
||||
normalizedAliasValue: 'bmad-shard-doc',
|
||||
rawIdentityHasLeadingSlash: 'false',
|
||||
resolutionEligibility: 'canonical-id-only',
|
||||
|
|
@ -2845,7 +2981,7 @@ async function runTests() {
|
|||
canonicalId: 'bmad-shard-doc',
|
||||
alias: 'shard-doc',
|
||||
aliasType: 'legacy-name',
|
||||
authoritySourcePath: 'bmad-fork/src/core/tasks/shard-doc.artifact.yaml',
|
||||
authoritySourcePath: 'bmad-fork/src/core/tasks/shard-doc/skill-manifest.yaml',
|
||||
normalizedAliasValue: 'shard-doc',
|
||||
rawIdentityHasLeadingSlash: 'false',
|
||||
resolutionEligibility: 'legacy-name-only',
|
||||
|
|
@ -2857,7 +2993,7 @@ async function runTests() {
|
|||
canonicalId: 'bmad-shard-doc',
|
||||
alias: '/bmad-shard-doc',
|
||||
aliasType: 'slash-command',
|
||||
authoritySourcePath: 'bmad-fork/src/core/tasks/shard-doc.artifact.yaml',
|
||||
authoritySourcePath: 'bmad-fork/src/core/tasks/shard-doc/skill-manifest.yaml',
|
||||
normalizedAliasValue: 'bmad-shard-doc',
|
||||
rawIdentityHasLeadingSlash: 'true',
|
||||
resolutionEligibility: 'slash-command-only',
|
||||
|
|
@ -2869,7 +3005,7 @@ async function runTests() {
|
|||
canonicalId: 'bmad-index-docs',
|
||||
alias: 'bmad-index-docs',
|
||||
aliasType: 'canonical-id',
|
||||
authoritySourcePath: 'bmad-fork/src/core/tasks/index-docs.artifact.yaml',
|
||||
authoritySourcePath: 'bmad-fork/src/core/tasks/index-docs/skill-manifest.yaml',
|
||||
normalizedAliasValue: 'bmad-index-docs',
|
||||
rawIdentityHasLeadingSlash: 'false',
|
||||
resolutionEligibility: 'canonical-id-only',
|
||||
|
|
@ -2881,7 +3017,7 @@ async function runTests() {
|
|||
canonicalId: 'bmad-index-docs',
|
||||
alias: 'index-docs',
|
||||
aliasType: 'legacy-name',
|
||||
authoritySourcePath: 'bmad-fork/src/core/tasks/index-docs.artifact.yaml',
|
||||
authoritySourcePath: 'bmad-fork/src/core/tasks/index-docs/skill-manifest.yaml',
|
||||
normalizedAliasValue: 'index-docs',
|
||||
rawIdentityHasLeadingSlash: 'false',
|
||||
resolutionEligibility: 'legacy-name-only',
|
||||
|
|
@ -2893,7 +3029,7 @@ async function runTests() {
|
|||
canonicalId: 'bmad-index-docs',
|
||||
alias: '/bmad-index-docs',
|
||||
aliasType: 'slash-command',
|
||||
authoritySourcePath: 'bmad-fork/src/core/tasks/index-docs.artifact.yaml',
|
||||
authoritySourcePath: 'bmad-fork/src/core/tasks/index-docs/skill-manifest.yaml',
|
||||
normalizedAliasValue: 'bmad-index-docs',
|
||||
rawIdentityHasLeadingSlash: 'true',
|
||||
resolutionEligibility: 'slash-command-only',
|
||||
|
|
@ -3006,10 +3142,10 @@ async function runTests() {
|
|||
return false;
|
||||
}
|
||||
if (row.canonicalId === 'bmad-help') {
|
||||
return row.authoritySourcePath === 'bmad-fork/src/core/tasks/help.artifact.yaml';
|
||||
return row.authoritySourcePath === 'bmad-fork/src/core/tasks/help/skill-manifest.yaml';
|
||||
}
|
||||
if (row.canonicalId === 'bmad-shard-doc') {
|
||||
return row.authoritySourcePath === 'bmad-fork/src/core/tasks/shard-doc.artifact.yaml';
|
||||
return row.authoritySourcePath === 'bmad-fork/src/core/tasks/shard-doc/skill-manifest.yaml';
|
||||
}
|
||||
return false;
|
||||
}),
|
||||
|
|
@ -3063,7 +3199,7 @@ async function runTests() {
|
|||
canonicalId: 'bmad-help',
|
||||
authoritativePresenceKey: 'capability:bmad-help',
|
||||
authoritySourceType: 'sidecar',
|
||||
authoritySourcePath: 'bmad-fork/src/core/tasks/help.artifact.yaml',
|
||||
authoritySourcePath: 'bmad-fork/src/core/tasks/help/skill-manifest.yaml',
|
||||
sourcePath: 'bmad-fork/src/core/tasks/help.md',
|
||||
},
|
||||
];
|
||||
|
|
@ -3165,7 +3301,7 @@ async function runTests() {
|
|||
assert(
|
||||
helpCommandLabelRow &&
|
||||
helpCommandLabelRow.authoritySourceType === 'sidecar' &&
|
||||
helpCommandLabelRow.authoritySourcePath === 'bmad-fork/src/core/tasks/help.artifact.yaml',
|
||||
helpCommandLabelRow.authoritySourcePath === 'bmad-fork/src/core/tasks/help/skill-manifest.yaml',
|
||||
'Command-label report includes sidecar provenance linkage',
|
||||
);
|
||||
assert(
|
||||
|
|
@ -3177,7 +3313,7 @@ async function runTests() {
|
|||
assert(
|
||||
shardDocCommandLabelRow &&
|
||||
shardDocCommandLabelRow.authoritySourceType === 'sidecar' &&
|
||||
shardDocCommandLabelRow.authoritySourcePath === 'bmad-fork/src/core/tasks/shard-doc.artifact.yaml',
|
||||
shardDocCommandLabelRow.authoritySourcePath === 'bmad-fork/src/core/tasks/shard-doc/skill-manifest.yaml',
|
||||
'Command-label report includes shard-doc sidecar provenance linkage',
|
||||
);
|
||||
assert(
|
||||
|
|
@ -3189,7 +3325,7 @@ async function runTests() {
|
|||
assert(
|
||||
indexDocsCommandLabelRow &&
|
||||
indexDocsCommandLabelRow.authoritySourceType === 'sidecar' &&
|
||||
indexDocsCommandLabelRow.authoritySourcePath === 'bmad-fork/src/core/tasks/index-docs.artifact.yaml',
|
||||
indexDocsCommandLabelRow.authoritySourcePath === 'bmad-fork/src/core/tasks/index-docs/skill-manifest.yaml',
|
||||
'Command-label report includes index-docs sidecar provenance linkage',
|
||||
);
|
||||
const generatedCommandLabelReportRaw = await fs.readFile(generatedCommandLabelReportPath, 'utf8');
|
||||
|
|
@ -3224,7 +3360,7 @@ async function runTests() {
|
|||
const baselineShardDocLabelContract = evaluateExemplarCommandLabelReportRows(commandLabelRows, {
|
||||
canonicalId: 'bmad-shard-doc',
|
||||
displayedCommandLabel: '/bmad-shard-doc',
|
||||
authoritySourcePath: 'bmad-fork/src/core/tasks/shard-doc.artifact.yaml',
|
||||
authoritySourcePath: 'bmad-fork/src/core/tasks/shard-doc/skill-manifest.yaml',
|
||||
});
|
||||
assert(
|
||||
baselineShardDocLabelContract.valid,
|
||||
|
|
@ -3234,7 +3370,7 @@ async function runTests() {
|
|||
const baselineIndexDocsLabelContract = evaluateExemplarCommandLabelReportRows(commandLabelRows, {
|
||||
canonicalId: 'bmad-index-docs',
|
||||
displayedCommandLabel: '/bmad-index-docs',
|
||||
authoritySourcePath: 'bmad-fork/src/core/tasks/index-docs.artifact.yaml',
|
||||
authoritySourcePath: 'bmad-fork/src/core/tasks/index-docs/skill-manifest.yaml',
|
||||
});
|
||||
assert(
|
||||
baselineIndexDocsLabelContract.valid,
|
||||
|
|
@ -3355,7 +3491,7 @@ async function runTests() {
|
|||
{
|
||||
canonicalId: 'bmad-shard-doc',
|
||||
displayedCommandLabel: '/bmad-shard-doc',
|
||||
authoritySourcePath: 'bmad-fork/src/core/tasks/shard-doc.artifact.yaml',
|
||||
authoritySourcePath: 'bmad-fork/src/core/tasks/shard-doc/skill-manifest.yaml',
|
||||
},
|
||||
);
|
||||
assert(
|
||||
|
|
@ -3372,14 +3508,14 @@ async function runTests() {
|
|||
installedStageRow &&
|
||||
installedStageRow.issuingComponent === EXEMPLAR_HELP_CATALOG_ISSUING_COMPONENT &&
|
||||
installedStageRow.commandAuthoritySourceType === 'sidecar' &&
|
||||
installedStageRow.commandAuthoritySourcePath === 'bmad-fork/src/core/tasks/help.artifact.yaml',
|
||||
installedStageRow.commandAuthoritySourcePath === 'bmad-fork/src/core/tasks/help/skill-manifest.yaml',
|
||||
'Installed compatibility stage row preserves sidecar command provenance and issuing component linkage',
|
||||
);
|
||||
assert(
|
||||
mergedStageRow &&
|
||||
mergedStageRow.issuingComponent === INSTALLER_HELP_CATALOG_MERGE_COMPONENT &&
|
||||
mergedStageRow.commandAuthoritySourceType === 'sidecar' &&
|
||||
mergedStageRow.commandAuthoritySourcePath === 'bmad-fork/src/core/tasks/help.artifact.yaml',
|
||||
mergedStageRow.commandAuthoritySourcePath === 'bmad-fork/src/core/tasks/help/skill-manifest.yaml',
|
||||
'Merged config stage row preserves sidecar command provenance and merge issuing component linkage',
|
||||
);
|
||||
assert(
|
||||
|
|
@ -3397,7 +3533,7 @@ async function runTests() {
|
|||
generatedPipelineReportRows.every(
|
||||
(row) =>
|
||||
row.commandAuthoritySourceType === 'sidecar' &&
|
||||
row.commandAuthoritySourcePath === 'bmad-fork/src/core/tasks/help.artifact.yaml',
|
||||
row.commandAuthoritySourcePath === 'bmad-fork/src/core/tasks/help/skill-manifest.yaml',
|
||||
),
|
||||
'Installer persists pipeline stage artifact with sidecar command provenance linkage for both stages',
|
||||
);
|
||||
|
|
@ -3546,7 +3682,7 @@ async function runTests() {
|
|||
assert(
|
||||
exportDerivationRecord &&
|
||||
exportDerivationRecord.exportIdDerivationSourceType === EXEMPLAR_HELP_EXPORT_DERIVATION_SOURCE_TYPE &&
|
||||
exportDerivationRecord.exportIdDerivationSourcePath === 'bmad-fork/src/core/tasks/help.artifact.yaml',
|
||||
exportDerivationRecord.exportIdDerivationSourcePath === 'bmad-fork/src/core/tasks/help/skill-manifest.yaml',
|
||||
'Codex export records exemplar derivation source metadata from sidecar canonical-id',
|
||||
);
|
||||
|
||||
|
|
@ -3572,7 +3708,7 @@ async function runTests() {
|
|||
assert(
|
||||
shardDocExportDerivationRecord &&
|
||||
shardDocExportDerivationRecord.exportIdDerivationSourceType === EXEMPLAR_HELP_EXPORT_DERIVATION_SOURCE_TYPE &&
|
||||
shardDocExportDerivationRecord.exportIdDerivationSourcePath === 'bmad-fork/src/core/tasks/shard-doc.artifact.yaml' &&
|
||||
shardDocExportDerivationRecord.exportIdDerivationSourcePath === 'bmad-fork/src/core/tasks/shard-doc/skill-manifest.yaml' &&
|
||||
shardDocExportDerivationRecord.sourcePath === 'bmad-fork/src/core/tasks/shard-doc.xml',
|
||||
'Codex export records shard-doc sidecar-canonical derivation metadata and source path',
|
||||
);
|
||||
|
|
@ -3599,7 +3735,7 @@ async function runTests() {
|
|||
assert(
|
||||
indexDocsExportDerivationRecord &&
|
||||
indexDocsExportDerivationRecord.exportIdDerivationSourceType === EXEMPLAR_HELP_EXPORT_DERIVATION_SOURCE_TYPE &&
|
||||
indexDocsExportDerivationRecord.exportIdDerivationSourcePath === 'bmad-fork/src/core/tasks/index-docs.artifact.yaml' &&
|
||||
indexDocsExportDerivationRecord.exportIdDerivationSourcePath === 'bmad-fork/src/core/tasks/index-docs/skill-manifest.yaml' &&
|
||||
indexDocsExportDerivationRecord.sourcePath === 'bmad-fork/src/core/tasks/index-docs.xml',
|
||||
'Codex export records index-docs sidecar-canonical derivation metadata and source path',
|
||||
);
|
||||
|
|
@ -3665,7 +3801,7 @@ async function runTests() {
|
|||
);
|
||||
assert(
|
||||
submoduleExportDerivationRecord &&
|
||||
submoduleExportDerivationRecord.exportIdDerivationSourcePath === 'bmad-fork/src/core/tasks/help.artifact.yaml',
|
||||
submoduleExportDerivationRecord.exportIdDerivationSourcePath === 'bmad-fork/src/core/tasks/help/skill-manifest.yaml',
|
||||
'Codex export locks exemplar derivation source-path contract when running from submodule root',
|
||||
);
|
||||
} finally {
|
||||
|
|
@ -3907,7 +4043,7 @@ async function runTests() {
|
|||
legacyName: 'help',
|
||||
canonicalId: 'bmad-help',
|
||||
authoritySourceType: 'sidecar',
|
||||
authoritySourcePath: 'bmad-fork/src/core/tasks/help.artifact.yaml',
|
||||
authoritySourcePath: 'bmad-fork/src/core/tasks/help/skill-manifest.yaml',
|
||||
futureAdditiveField: 'canonical-additive',
|
||||
},
|
||||
{
|
||||
|
|
@ -4304,7 +4440,7 @@ async function runTests() {
|
|||
legacyName: 'help',
|
||||
canonicalId: 'bmad-help',
|
||||
authoritySourceType: 'sidecar',
|
||||
authoritySourcePath: 'bmad-fork/src/core/tasks/help.artifact.yaml',
|
||||
authoritySourcePath: 'bmad-fork/src/core/tasks/help/skill-manifest.yaml',
|
||||
},
|
||||
],
|
||||
);
|
||||
|
|
@ -4327,7 +4463,7 @@ async function runTests() {
|
|||
alias: 'bmad-help',
|
||||
aliasType: 'canonical-id',
|
||||
authoritySourceType: 'sidecar',
|
||||
authoritySourcePath: 'bmad-fork/src/core/tasks/help.artifact.yaml',
|
||||
authoritySourcePath: 'bmad-fork/src/core/tasks/help/skill-manifest.yaml',
|
||||
rowIdentity: 'alias-row:bmad-help:canonical-id',
|
||||
normalizedAliasValue: 'bmad-help',
|
||||
rawIdentityHasLeadingSlash: 'false',
|
||||
|
|
@ -4338,7 +4474,7 @@ async function runTests() {
|
|||
alias: 'help',
|
||||
aliasType: 'legacy-name',
|
||||
authoritySourceType: 'sidecar',
|
||||
authoritySourcePath: 'bmad-fork/src/core/tasks/help.artifact.yaml',
|
||||
authoritySourcePath: 'bmad-fork/src/core/tasks/help/skill-manifest.yaml',
|
||||
rowIdentity: 'alias-row:bmad-help:legacy-name',
|
||||
normalizedAliasValue: 'help',
|
||||
rawIdentityHasLeadingSlash: 'false',
|
||||
|
|
@ -4349,7 +4485,7 @@ async function runTests() {
|
|||
alias: '/bmad-help',
|
||||
aliasType: 'slash-command',
|
||||
authoritySourceType: 'sidecar',
|
||||
authoritySourcePath: 'bmad-fork/src/core/tasks/help.artifact.yaml',
|
||||
authoritySourcePath: 'bmad-fork/src/core/tasks/help/skill-manifest.yaml',
|
||||
rowIdentity: 'alias-row:bmad-help:slash-command',
|
||||
normalizedAliasValue: 'bmad-help',
|
||||
rawIdentityHasLeadingSlash: 'true',
|
||||
|
|
@ -4520,9 +4656,9 @@ async function runTests() {
|
|||
descriptionValue: 'Help command',
|
||||
expectedDescriptionValue: 'Help command',
|
||||
descriptionAuthoritySourceType: 'sidecar',
|
||||
descriptionAuthoritySourcePath: 'bmad-fork/src/core/tasks/help.artifact.yaml',
|
||||
descriptionAuthoritySourcePath: 'bmad-fork/src/core/tasks/help/skill-manifest.yaml',
|
||||
commandAuthoritySourceType: 'sidecar',
|
||||
commandAuthoritySourcePath: 'bmad-fork/src/core/tasks/help.artifact.yaml',
|
||||
commandAuthoritySourcePath: 'bmad-fork/src/core/tasks/help/skill-manifest.yaml',
|
||||
issuerOwnerClass: 'installer',
|
||||
issuingComponent: 'bmad-fork/tools/cli/installers/lib/core/help-catalog-generator.js::buildSidecarAwareExemplarHelpRow()',
|
||||
issuingComponentBindingEvidence: 'deterministic',
|
||||
|
|
@ -4541,9 +4677,9 @@ async function runTests() {
|
|||
descriptionValue: 'Help command',
|
||||
expectedDescriptionValue: 'Help command',
|
||||
descriptionAuthoritySourceType: 'sidecar',
|
||||
descriptionAuthoritySourcePath: 'bmad-fork/src/core/tasks/help.artifact.yaml',
|
||||
descriptionAuthoritySourcePath: 'bmad-fork/src/core/tasks/help/skill-manifest.yaml',
|
||||
commandAuthoritySourceType: 'sidecar',
|
||||
commandAuthoritySourcePath: 'bmad-fork/src/core/tasks/help.artifact.yaml',
|
||||
commandAuthoritySourcePath: 'bmad-fork/src/core/tasks/help/skill-manifest.yaml',
|
||||
issuerOwnerClass: 'installer',
|
||||
issuingComponent: 'bmad-fork/tools/cli/installers/lib/core/installer.js::mergeModuleHelpCatalogs()',
|
||||
issuingComponentBindingEvidence: 'deterministic',
|
||||
|
|
@ -4575,7 +4711,7 @@ async function runTests() {
|
|||
normalizedDisplayedLabel: '/bmad-help',
|
||||
rowCountForCanonicalId: '1',
|
||||
authoritySourceType: 'sidecar',
|
||||
authoritySourcePath: 'bmad-fork/src/core/tasks/help.artifact.yaml',
|
||||
authoritySourcePath: 'bmad-fork/src/core/tasks/help/skill-manifest.yaml',
|
||||
status: 'PASS',
|
||||
failureReason: '',
|
||||
},
|
||||
|
|
@ -4734,7 +4870,7 @@ async function runTests() {
|
|||
legacyName: 'help',
|
||||
canonicalId: 'bmad-help',
|
||||
authoritySourceType: 'sidecar',
|
||||
authoritySourcePath: 'bmad-fork/src/core/tasks/help.artifact.yaml',
|
||||
authoritySourcePath: 'bmad-fork/src/core/tasks/help/skill-manifest.yaml',
|
||||
},
|
||||
],
|
||||
);
|
||||
|
|
@ -4976,6 +5112,24 @@ async function runTests() {
|
|||
'Help validation harness emits deterministic replay-evidence validation error code',
|
||||
);
|
||||
}
|
||||
|
||||
await fs.writeFile(path.join(tempSourceTasksDir, 'bmad-config.yaml'), 'canonicalId: root-bmad-config\n', 'utf8');
|
||||
await fs.ensureDir(path.join(tempSourceTasksDir, 'help'));
|
||||
await fs.writeFile(path.join(tempSourceTasksDir, 'help', 'bmad-config.yaml'), 'canonicalId: help-bmad-config\n', 'utf8');
|
||||
try {
|
||||
await harness.generateValidationArtifacts({
|
||||
projectDir: tempProjectRoot,
|
||||
bmadDir: tempBmadDir,
|
||||
bmadFolderName: '_bmad',
|
||||
sourceMarkdownPath: path.join(tempSourceTasksDir, 'help.md'),
|
||||
});
|
||||
assert(false, 'Help validation harness normalizes metadata-resolution ambiguity into harness-native deterministic error');
|
||||
} catch (error) {
|
||||
assert(
|
||||
error.code === HELP_VALIDATION_ERROR_CODES.METADATA_RESOLUTION_FAILED,
|
||||
'Help validation harness emits deterministic metadata-resolution error code',
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
assert(false, 'Deterministic validation artifact suite setup', error.message);
|
||||
} finally {
|
||||
|
|
@ -5033,7 +5187,7 @@ async function runTests() {
|
|||
normalizedDisplayedLabel: '/bmad-shard-doc',
|
||||
rowCountForCanonicalId: '1',
|
||||
authoritySourceType: 'sidecar',
|
||||
authoritySourcePath: 'bmad-fork/src/core/tasks/shard-doc.artifact.yaml',
|
||||
authoritySourcePath: 'bmad-fork/src/core/tasks/shard-doc/skill-manifest.yaml',
|
||||
status: 'PASS',
|
||||
failureReason: '',
|
||||
},
|
||||
|
|
@ -5075,7 +5229,7 @@ async function runTests() {
|
|||
legacyName: 'shard-doc',
|
||||
canonicalId: 'bmad-shard-doc',
|
||||
authoritySourceType: 'sidecar',
|
||||
authoritySourcePath: 'bmad-fork/src/core/tasks/shard-doc.artifact.yaml',
|
||||
authoritySourcePath: 'bmad-fork/src/core/tasks/shard-doc/skill-manifest.yaml',
|
||||
},
|
||||
],
|
||||
);
|
||||
|
|
@ -5159,7 +5313,7 @@ async function runTests() {
|
|||
alias: 'bmad-shard-doc',
|
||||
aliasType: 'canonical-id',
|
||||
authoritySourceType: 'sidecar',
|
||||
authoritySourcePath: 'bmad-fork/src/core/tasks/shard-doc.artifact.yaml',
|
||||
authoritySourcePath: 'bmad-fork/src/core/tasks/shard-doc/skill-manifest.yaml',
|
||||
rowIdentity: 'alias-row:bmad-shard-doc:canonical-id',
|
||||
normalizedAliasValue: 'bmad-shard-doc',
|
||||
rawIdentityHasLeadingSlash: 'false',
|
||||
|
|
@ -5170,7 +5324,7 @@ async function runTests() {
|
|||
alias: 'shard-doc',
|
||||
aliasType: 'legacy-name',
|
||||
authoritySourceType: 'sidecar',
|
||||
authoritySourcePath: 'bmad-fork/src/core/tasks/shard-doc.artifact.yaml',
|
||||
authoritySourcePath: 'bmad-fork/src/core/tasks/shard-doc/skill-manifest.yaml',
|
||||
rowIdentity: 'alias-row:bmad-shard-doc:legacy-name',
|
||||
normalizedAliasValue: 'shard-doc',
|
||||
rawIdentityHasLeadingSlash: 'false',
|
||||
|
|
@ -5181,7 +5335,7 @@ async function runTests() {
|
|||
alias: '/bmad-shard-doc',
|
||||
aliasType: 'slash-command',
|
||||
authoritySourceType: 'sidecar',
|
||||
authoritySourcePath: 'bmad-fork/src/core/tasks/shard-doc.artifact.yaml',
|
||||
authoritySourcePath: 'bmad-fork/src/core/tasks/shard-doc/skill-manifest.yaml',
|
||||
rowIdentity: 'alias-row:bmad-shard-doc:slash-command',
|
||||
normalizedAliasValue: 'bmad-shard-doc',
|
||||
rawIdentityHasLeadingSlash: 'true',
|
||||
|
|
@ -5197,7 +5351,7 @@ async function runTests() {
|
|||
canonicalId: 'bmad-shard-doc',
|
||||
authoritativePresenceKey: 'capability:bmad-shard-doc',
|
||||
authoritySourceType: 'sidecar',
|
||||
authoritySourcePath: 'bmad-fork/src/core/tasks/shard-doc.artifact.yaml',
|
||||
authoritySourcePath: 'bmad-fork/src/core/tasks/shard-doc/skill-manifest.yaml',
|
||||
},
|
||||
{
|
||||
recordType: 'source-body-authority',
|
||||
|
|
@ -5412,6 +5566,24 @@ async function runTests() {
|
|||
'Shard-doc validation harness emits deterministic missing-row error code',
|
||||
);
|
||||
}
|
||||
|
||||
await fs.writeFile(path.join(tempSourceTasksDir, 'bmad-config.yaml'), 'canonicalId: root-bmad-config\n', 'utf8');
|
||||
await fs.ensureDir(path.join(tempSourceTasksDir, 'shard-doc'));
|
||||
await fs.writeFile(path.join(tempSourceTasksDir, 'shard-doc', 'bmad-config.yaml'), 'canonicalId: shard-doc-bmad-config\n', 'utf8');
|
||||
try {
|
||||
await harness.generateValidationArtifacts({
|
||||
projectDir: tempProjectRoot,
|
||||
bmadDir: tempBmadDir,
|
||||
bmadFolderName: '_bmad',
|
||||
sourceXmlPath: path.join(tempSourceTasksDir, 'shard-doc.xml'),
|
||||
});
|
||||
assert(false, 'Shard-doc validation harness normalizes metadata-resolution ambiguity into harness-native deterministic error');
|
||||
} catch (error) {
|
||||
assert(
|
||||
error.code === SHARD_DOC_VALIDATION_ERROR_CODES.METADATA_RESOLUTION_FAILED,
|
||||
'Shard-doc validation harness emits deterministic metadata-resolution error code',
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
assert(false, 'Shard-doc validation artifact suite setup', error.message);
|
||||
} finally {
|
||||
|
|
@ -5468,7 +5640,7 @@ async function runTests() {
|
|||
normalizedDisplayedLabel: '/bmad-index-docs',
|
||||
rowCountForCanonicalId: '1',
|
||||
authoritySourceType: 'sidecar',
|
||||
authoritySourcePath: 'bmad-fork/src/core/tasks/index-docs.artifact.yaml',
|
||||
authoritySourcePath: 'bmad-fork/src/core/tasks/index-docs/skill-manifest.yaml',
|
||||
status: 'PASS',
|
||||
failureReason: '',
|
||||
},
|
||||
|
|
@ -5512,7 +5684,7 @@ async function runTests() {
|
|||
legacyName: 'index-docs',
|
||||
canonicalId: 'bmad-index-docs',
|
||||
authoritySourceType: 'sidecar',
|
||||
authoritySourcePath: 'bmad-fork/src/core/tasks/index-docs.artifact.yaml',
|
||||
authoritySourcePath: 'bmad-fork/src/core/tasks/index-docs/skill-manifest.yaml',
|
||||
},
|
||||
],
|
||||
);
|
||||
|
|
@ -5596,7 +5768,7 @@ async function runTests() {
|
|||
alias: 'bmad-index-docs',
|
||||
aliasType: 'canonical-id',
|
||||
authoritySourceType: 'sidecar',
|
||||
authoritySourcePath: 'bmad-fork/src/core/tasks/index-docs.artifact.yaml',
|
||||
authoritySourcePath: 'bmad-fork/src/core/tasks/index-docs/skill-manifest.yaml',
|
||||
rowIdentity: 'alias-row:bmad-index-docs:canonical-id',
|
||||
normalizedAliasValue: 'bmad-index-docs',
|
||||
rawIdentityHasLeadingSlash: 'false',
|
||||
|
|
@ -5607,7 +5779,7 @@ async function runTests() {
|
|||
alias: 'index-docs',
|
||||
aliasType: 'legacy-name',
|
||||
authoritySourceType: 'sidecar',
|
||||
authoritySourcePath: 'bmad-fork/src/core/tasks/index-docs.artifact.yaml',
|
||||
authoritySourcePath: 'bmad-fork/src/core/tasks/index-docs/skill-manifest.yaml',
|
||||
rowIdentity: 'alias-row:bmad-index-docs:legacy-name',
|
||||
normalizedAliasValue: 'index-docs',
|
||||
rawIdentityHasLeadingSlash: 'false',
|
||||
|
|
@ -5618,7 +5790,7 @@ async function runTests() {
|
|||
alias: '/bmad-index-docs',
|
||||
aliasType: 'slash-command',
|
||||
authoritySourceType: 'sidecar',
|
||||
authoritySourcePath: 'bmad-fork/src/core/tasks/index-docs.artifact.yaml',
|
||||
authoritySourcePath: 'bmad-fork/src/core/tasks/index-docs/skill-manifest.yaml',
|
||||
rowIdentity: 'alias-row:bmad-index-docs:slash-command',
|
||||
normalizedAliasValue: 'bmad-index-docs',
|
||||
rawIdentityHasLeadingSlash: 'true',
|
||||
|
|
@ -5634,7 +5806,7 @@ async function runTests() {
|
|||
canonicalId: 'bmad-index-docs',
|
||||
authoritativePresenceKey: 'capability:bmad-index-docs',
|
||||
authoritySourceType: 'sidecar',
|
||||
authoritySourcePath: 'bmad-fork/src/core/tasks/index-docs.artifact.yaml',
|
||||
authoritySourcePath: 'bmad-fork/src/core/tasks/index-docs/skill-manifest.yaml',
|
||||
},
|
||||
{
|
||||
recordType: 'source-body-authority',
|
||||
|
|
@ -5849,6 +6021,24 @@ async function runTests() {
|
|||
'Index-docs validation harness emits deterministic missing-row error code',
|
||||
);
|
||||
}
|
||||
|
||||
await fs.writeFile(path.join(tempSourceTasksDir, 'bmad-config.yaml'), 'canonicalId: root-bmad-config\n', 'utf8');
|
||||
await fs.ensureDir(path.join(tempSourceTasksDir, 'index-docs'));
|
||||
await fs.writeFile(path.join(tempSourceTasksDir, 'index-docs', 'bmad-config.yaml'), 'canonicalId: index-docs-bmad-config\n', 'utf8');
|
||||
try {
|
||||
await harness.generateValidationArtifacts({
|
||||
projectDir: tempProjectRoot,
|
||||
bmadDir: tempBmadDir,
|
||||
bmadFolderName: '_bmad',
|
||||
sourceXmlPath: path.join(tempSourceTasksDir, 'index-docs.xml'),
|
||||
});
|
||||
assert(false, 'Index-docs validation harness normalizes metadata-resolution ambiguity into harness-native deterministic error');
|
||||
} catch (error) {
|
||||
assert(
|
||||
error.code === INDEX_DOCS_VALIDATION_ERROR_CODES.METADATA_RESOLUTION_FAILED,
|
||||
'Index-docs validation harness emits deterministic metadata-resolution error code',
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
assert(false, 'Index-docs validation artifact suite setup', error.message);
|
||||
} finally {
|
||||
|
|
|
|||
|
|
@ -3,9 +3,11 @@ const fs = require('fs-extra');
|
|||
const yaml = require('yaml');
|
||||
const { getProjectRoot, getSourcePath } = require('../../../lib/project-root');
|
||||
const { normalizeAndResolveExemplarAlias } = require('./help-alias-normalizer');
|
||||
const { resolveSkillMetadataAuthority } = require('./sidecar-contract-validator');
|
||||
|
||||
const HELP_AUTHORITY_VALIDATION_ERROR_CODES = Object.freeze({
|
||||
SIDECAR_FILE_NOT_FOUND: 'ERR_HELP_AUTHORITY_SIDECAR_FILE_NOT_FOUND',
|
||||
SIDECAR_FILENAME_AMBIGUOUS: 'ERR_HELP_AUTHORITY_SIDECAR_FILENAME_AMBIGUOUS',
|
||||
SIDECAR_PARSE_FAILED: 'ERR_HELP_AUTHORITY_SIDECAR_PARSE_FAILED',
|
||||
SIDECAR_INVALID_METADATA: 'ERR_HELP_AUTHORITY_SIDECAR_INVALID_METADATA',
|
||||
MARKDOWN_FILE_NOT_FOUND: 'ERR_HELP_AUTHORITY_MARKDOWN_FILE_NOT_FOUND',
|
||||
|
|
@ -277,17 +279,37 @@ function buildHelpAuthorityRecords({ canonicalId, sidecarSourcePath, sourceMarkd
|
|||
}
|
||||
|
||||
async function validateHelpAuthoritySplitAndPrecedence(options = {}) {
|
||||
const sidecarPath = options.sidecarPath || getSourcePath('core', 'tasks', 'help.artifact.yaml');
|
||||
const sourceMarkdownPath = options.sourceMarkdownPath || getSourcePath('core', 'tasks', 'help.md');
|
||||
const runtimeMarkdownPath = options.runtimeMarkdownPath || '';
|
||||
|
||||
const sidecarSourcePath = normalizeSourcePath(options.sidecarSourcePath || toProjectRelativePath(sidecarPath));
|
||||
let resolvedMetadataAuthority;
|
||||
try {
|
||||
resolvedMetadataAuthority = await resolveSkillMetadataAuthority({
|
||||
sourceFilePath: sourceMarkdownPath,
|
||||
metadataPath: options.sidecarPath || '',
|
||||
metadataSourcePath: options.sidecarSourcePath || '',
|
||||
ambiguousErrorCode: HELP_AUTHORITY_VALIDATION_ERROR_CODES.SIDECAR_FILENAME_AMBIGUOUS,
|
||||
});
|
||||
} catch (error) {
|
||||
throw new HelpAuthorityValidationError({
|
||||
code: error.code || HELP_AUTHORITY_VALIDATION_ERROR_CODES.SIDECAR_FILENAME_AMBIGUOUS,
|
||||
detail: error.detail || error.message,
|
||||
fieldPath: error.fieldPath || '<file>',
|
||||
sourcePath: normalizeSourcePath(error.sourcePath || toProjectRelativePath(sourceMarkdownPath)),
|
||||
});
|
||||
}
|
||||
|
||||
const sidecarPath = resolvedMetadataAuthority.resolvedAbsolutePath;
|
||||
|
||||
const sidecarSourcePath = normalizeSourcePath(
|
||||
options.sidecarSourcePath || resolvedMetadataAuthority.canonicalTargetSourcePath || resolvedMetadataAuthority.resolvedSourcePath,
|
||||
);
|
||||
const sourceMarkdownSourcePath = normalizeSourcePath(options.sourceMarkdownSourcePath || toProjectRelativePath(sourceMarkdownPath));
|
||||
const runtimeMarkdownSourcePath = normalizeSourcePath(
|
||||
options.runtimeMarkdownSourcePath || (runtimeMarkdownPath ? toProjectRelativePath(runtimeMarkdownPath) : ''),
|
||||
);
|
||||
|
||||
if (!(await fs.pathExists(sidecarPath))) {
|
||||
if (!sidecarPath || !(await fs.pathExists(sidecarPath))) {
|
||||
throw new HelpAuthorityValidationError({
|
||||
code: HELP_AUTHORITY_VALIDATION_ERROR_CODES.SIDECAR_FILE_NOT_FOUND,
|
||||
detail: 'Expected sidecar metadata file was not found',
|
||||
|
|
@ -359,6 +381,13 @@ async function validateHelpAuthoritySplitAndPrecedence(options = {}) {
|
|||
authoritativePresenceKey: `capability:${canonicalId}`,
|
||||
authoritativeRecords,
|
||||
checkedSurfaces,
|
||||
metadataAuthority: {
|
||||
resolvedPath: normalizeSourcePath(resolvedMetadataAuthority.resolvedSourcePath || sidecarSourcePath),
|
||||
resolvedFilename: normalizeSourcePath(resolvedMetadataAuthority.resolvedFilename || ''),
|
||||
canonicalTargetFilename: normalizeSourcePath(resolvedMetadataAuthority.canonicalTargetFilename || 'skill-manifest.yaml'),
|
||||
canonicalTargetPath: normalizeSourcePath(resolvedMetadataAuthority.canonicalTargetSourcePath || sidecarSourcePath),
|
||||
derivationMode: normalizeSourcePath(resolvedMetadataAuthority.derivationMode || ''),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,9 +3,10 @@ const path = require('node:path');
|
|||
const yaml = require('yaml');
|
||||
const { getSourcePath, getProjectRoot } = require('../../../lib/project-root');
|
||||
const { normalizeAndResolveExemplarAlias } = require('./help-alias-normalizer');
|
||||
const { resolveSkillMetadataAuthority } = require('./sidecar-contract-validator');
|
||||
|
||||
const EXEMPLAR_HELP_CATALOG_CANONICAL_ID = 'bmad-help';
|
||||
const EXEMPLAR_HELP_CATALOG_AUTHORITY_SOURCE_PATH = 'bmad-fork/src/core/tasks/help.artifact.yaml';
|
||||
const EXEMPLAR_HELP_CATALOG_AUTHORITY_SOURCE_PATH = 'bmad-fork/src/core/tasks/help/skill-manifest.yaml';
|
||||
const EXEMPLAR_HELP_CATALOG_SOURCE_MARKDOWN_SOURCE_PATH = 'bmad-fork/src/core/tasks/help.md';
|
||||
const EXEMPLAR_HELP_CATALOG_ISSUING_COMPONENT =
|
||||
'bmad-fork/tools/cli/installers/lib/core/help-catalog-generator.js::buildSidecarAwareExemplarHelpRow()';
|
||||
|
|
@ -13,6 +14,7 @@ const INSTALLER_HELP_CATALOG_MERGE_COMPONENT = 'bmad-fork/tools/cli/installers/l
|
|||
|
||||
const HELP_CATALOG_GENERATION_ERROR_CODES = Object.freeze({
|
||||
SIDECAR_FILE_NOT_FOUND: 'ERR_HELP_CATALOG_SIDECAR_FILE_NOT_FOUND',
|
||||
SIDECAR_FILENAME_AMBIGUOUS: 'ERR_HELP_CATALOG_SIDECAR_FILENAME_AMBIGUOUS',
|
||||
SIDECAR_PARSE_FAILED: 'ERR_HELP_CATALOG_SIDECAR_PARSE_FAILED',
|
||||
SIDECAR_INVALID_METADATA: 'ERR_HELP_CATALOG_SIDECAR_INVALID_METADATA',
|
||||
CANONICAL_ID_MISMATCH: 'ERR_HELP_CATALOG_CANONICAL_ID_MISMATCH',
|
||||
|
|
@ -71,9 +73,29 @@ function createGenerationError(code, fieldPath, sourcePath, detail, observedValu
|
|||
});
|
||||
}
|
||||
|
||||
async function loadExemplarHelpSidecar(sidecarPath = getSourcePath('core', 'tasks', 'help.artifact.yaml')) {
|
||||
const sourcePath = normalizeSourcePath(toProjectRelativePath(sidecarPath));
|
||||
if (!(await fs.pathExists(sidecarPath))) {
|
||||
async function loadExemplarHelpSidecar(sidecarPath = '') {
|
||||
const sourceMarkdownPath = getSourcePath('core', 'tasks', 'help.md');
|
||||
let resolvedMetadataAuthority;
|
||||
try {
|
||||
resolvedMetadataAuthority = await resolveSkillMetadataAuthority({
|
||||
sourceFilePath: sourceMarkdownPath,
|
||||
metadataPath: sidecarPath,
|
||||
ambiguousErrorCode: HELP_CATALOG_GENERATION_ERROR_CODES.SIDECAR_FILENAME_AMBIGUOUS,
|
||||
});
|
||||
} catch (error) {
|
||||
createGenerationError(
|
||||
error.code || HELP_CATALOG_GENERATION_ERROR_CODES.SIDECAR_FILENAME_AMBIGUOUS,
|
||||
error.fieldPath || '<file>',
|
||||
normalizeSourcePath(error.sourcePath || toProjectRelativePath(sourceMarkdownPath)),
|
||||
error.detail || error.message,
|
||||
);
|
||||
}
|
||||
|
||||
const resolvedMetadataPath = resolvedMetadataAuthority.resolvedAbsolutePath;
|
||||
const sourcePath = normalizeSourcePath(
|
||||
resolvedMetadataAuthority.canonicalTargetSourcePath || resolvedMetadataAuthority.resolvedSourcePath,
|
||||
);
|
||||
if (!resolvedMetadataPath || !(await fs.pathExists(resolvedMetadataPath))) {
|
||||
createGenerationError(
|
||||
HELP_CATALOG_GENERATION_ERROR_CODES.SIDECAR_FILE_NOT_FOUND,
|
||||
'<file>',
|
||||
|
|
@ -84,7 +106,7 @@ async function loadExemplarHelpSidecar(sidecarPath = getSourcePath('core', 'task
|
|||
|
||||
let sidecarData;
|
||||
try {
|
||||
sidecarData = yaml.parse(await fs.readFile(sidecarPath, 'utf8'));
|
||||
sidecarData = yaml.parse(await fs.readFile(resolvedMetadataPath, 'utf8'));
|
||||
} catch (error) {
|
||||
createGenerationError(
|
||||
HELP_CATALOG_GENERATION_ERROR_CODES.SIDECAR_PARSE_FAILED,
|
||||
|
|
@ -128,6 +150,9 @@ async function loadExemplarHelpSidecar(sidecarPath = getSourcePath('core', 'task
|
|||
displayName,
|
||||
description,
|
||||
sourcePath,
|
||||
resolvedFilename: normalizeSourcePath(resolvedMetadataAuthority.resolvedFilename || ''),
|
||||
canonicalTargetFilename: normalizeSourcePath(resolvedMetadataAuthority.canonicalTargetFilename || 'skill-manifest.yaml'),
|
||||
derivationMode: normalizeSourcePath(resolvedMetadataAuthority.derivationMode || ''),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,11 @@ const fs = require('fs-extra');
|
|||
const yaml = require('yaml');
|
||||
const csv = require('csv-parse/sync');
|
||||
const { getSourcePath } = require('../../../lib/project-root');
|
||||
const { validateHelpSidecarContractFile, HELP_SIDECAR_ERROR_CODES } = require('./sidecar-contract-validator');
|
||||
const {
|
||||
validateHelpSidecarContractFile,
|
||||
HELP_SIDECAR_ERROR_CODES,
|
||||
resolveSkillMetadataAuthority,
|
||||
} = require('./sidecar-contract-validator');
|
||||
const { validateHelpAuthoritySplitAndPrecedence, HELP_FRONTMATTER_MISMATCH_ERROR_CODES } = require('./help-authority-validator');
|
||||
const { ManifestGenerator } = require('./manifest-generator');
|
||||
const { buildSidecarAwareExemplarHelpRow } = require('./help-catalog-generator');
|
||||
|
|
@ -13,6 +17,7 @@ const { CodexSetup } = require('../ide/codex');
|
|||
|
||||
const HELP_VALIDATION_ERROR_CODES = Object.freeze({
|
||||
REQUIRED_ARTIFACT_MISSING: 'ERR_HELP_VALIDATION_REQUIRED_ARTIFACT_MISSING',
|
||||
METADATA_RESOLUTION_FAILED: 'ERR_HELP_VALIDATION_METADATA_RESOLUTION_FAILED',
|
||||
CSV_SCHEMA_MISMATCH: 'ERR_HELP_VALIDATION_CSV_SCHEMA_MISMATCH',
|
||||
REQUIRED_ROW_IDENTITY_MISSING: 'ERR_HELP_VALIDATION_REQUIRED_ROW_IDENTITY_MISSING',
|
||||
REQUIRED_EVIDENCE_LINK_MISSING: 'ERR_HELP_VALIDATION_REQUIRED_EVIDENCE_LINK_MISSING',
|
||||
|
|
@ -25,7 +30,7 @@ const HELP_VALIDATION_ERROR_CODES = Object.freeze({
|
|||
DECISION_RECORD_PARSE_FAILED: 'ERR_HELP_VALIDATION_DECISION_RECORD_PARSE_FAILED',
|
||||
});
|
||||
|
||||
const SIDEcar_AUTHORITY_SOURCE_PATH = 'bmad-fork/src/core/tasks/help.artifact.yaml';
|
||||
const SIDEcar_AUTHORITY_SOURCE_PATH = 'bmad-fork/src/core/tasks/help/skill-manifest.yaml';
|
||||
const SOURCE_MARKDOWN_SOURCE_PATH = 'bmad-fork/src/core/tasks/help.md';
|
||||
const EVIDENCE_ISSUER_COMPONENT = 'bmad-fork/tools/cli/installers/lib/core/help-validation-harness.js';
|
||||
|
||||
|
|
@ -536,16 +541,9 @@ class HelpValidationHarness {
|
|||
};
|
||||
}
|
||||
|
||||
resolveSourceArtifactPaths(options = {}) {
|
||||
async resolveSourceArtifactPaths(options = {}) {
|
||||
const projectDir = path.resolve(options.projectDir || process.cwd());
|
||||
|
||||
const sidecarCandidates = [
|
||||
options.sidecarPath,
|
||||
path.join(projectDir, 'bmad-fork', 'src', 'core', 'tasks', 'help.artifact.yaml'),
|
||||
path.join(projectDir, 'src', 'core', 'tasks', 'help.artifact.yaml'),
|
||||
getSourcePath('core', 'tasks', 'help.artifact.yaml'),
|
||||
].filter(Boolean);
|
||||
|
||||
const sourceMarkdownCandidates = [
|
||||
options.sourceMarkdownPath,
|
||||
path.join(projectDir, 'bmad-fork', 'src', 'core', 'tasks', 'help.md'),
|
||||
|
|
@ -562,12 +560,33 @@ class HelpValidationHarness {
|
|||
return candidates[0];
|
||||
};
|
||||
|
||||
return Promise.all([resolveExistingPath(sidecarCandidates), resolveExistingPath(sourceMarkdownCandidates)]).then(
|
||||
([sidecarPath, sourceMarkdownPath]) => ({
|
||||
sidecarPath,
|
||||
sourceMarkdownPath,
|
||||
}),
|
||||
);
|
||||
const sourceMarkdownPath = await resolveExistingPath(sourceMarkdownCandidates);
|
||||
|
||||
let resolvedMetadataAuthority;
|
||||
try {
|
||||
resolvedMetadataAuthority = await resolveSkillMetadataAuthority({
|
||||
sourceFilePath: sourceMarkdownPath,
|
||||
metadataPath: options.sidecarPath || '',
|
||||
projectRoot: projectDir,
|
||||
ambiguousErrorCode: HELP_VALIDATION_ERROR_CODES.METADATA_RESOLUTION_FAILED,
|
||||
});
|
||||
} catch (error) {
|
||||
throw new HelpValidationHarnessError({
|
||||
code: HELP_VALIDATION_ERROR_CODES.METADATA_RESOLUTION_FAILED,
|
||||
detail: error.detail || error.message || 'metadata authority resolution failed',
|
||||
artifactId: 1,
|
||||
fieldPath: normalizeValue(error.fieldPath || '<file>'),
|
||||
sourcePath: normalizePath(error.sourcePath || SIDEcar_AUTHORITY_SOURCE_PATH),
|
||||
observedValue: normalizeValue(error.code || '<resolution-error>'),
|
||||
expectedValue: 'unambiguous metadata authority candidate',
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
sidecarPath: resolvedMetadataAuthority.resolvedAbsolutePath || options.sidecarPath || '',
|
||||
sourceMarkdownPath,
|
||||
metadataAuthority: resolvedMetadataAuthority,
|
||||
};
|
||||
}
|
||||
|
||||
async readSidecarMetadata(sidecarPath) {
|
||||
|
|
|
|||
|
|
@ -3,9 +3,11 @@ const fs = require('fs-extra');
|
|||
const yaml = require('yaml');
|
||||
const csv = require('csv-parse/sync');
|
||||
const { getProjectRoot, getSourcePath } = require('../../../lib/project-root');
|
||||
const { resolveSkillMetadataAuthority } = require('./sidecar-contract-validator');
|
||||
|
||||
const INDEX_DOCS_AUTHORITY_VALIDATION_ERROR_CODES = Object.freeze({
|
||||
SIDECAR_FILE_NOT_FOUND: 'ERR_INDEX_DOCS_AUTHORITY_SIDECAR_FILE_NOT_FOUND',
|
||||
SIDECAR_FILENAME_AMBIGUOUS: 'ERR_INDEX_DOCS_AUTHORITY_SIDECAR_FILENAME_AMBIGUOUS',
|
||||
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',
|
||||
|
|
@ -249,18 +251,38 @@ function buildIndexDocsAuthorityRecords({ canonicalId, sidecarSourcePath, source
|
|||
}
|
||||
|
||||
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));
|
||||
let resolvedMetadataAuthority;
|
||||
try {
|
||||
resolvedMetadataAuthority = await resolveSkillMetadataAuthority({
|
||||
sourceFilePath: sourceXmlPath,
|
||||
metadataPath: options.sidecarPath || '',
|
||||
metadataSourcePath: options.sidecarSourcePath || '',
|
||||
ambiguousErrorCode: INDEX_DOCS_AUTHORITY_VALIDATION_ERROR_CODES.SIDECAR_FILENAME_AMBIGUOUS,
|
||||
});
|
||||
} catch (error) {
|
||||
createValidationError(
|
||||
error.code || INDEX_DOCS_AUTHORITY_VALIDATION_ERROR_CODES.SIDECAR_FILENAME_AMBIGUOUS,
|
||||
error.detail || error.message,
|
||||
error.fieldPath || '<file>',
|
||||
normalizeSourcePath(error.sourcePath || toProjectRelativePath(sourceXmlPath)),
|
||||
);
|
||||
}
|
||||
|
||||
const sidecarPath = resolvedMetadataAuthority.resolvedAbsolutePath;
|
||||
|
||||
const sidecarSourcePath = normalizeSourcePath(
|
||||
options.sidecarSourcePath || resolvedMetadataAuthority.canonicalTargetSourcePath || resolvedMetadataAuthority.resolvedSourcePath,
|
||||
);
|
||||
const sourceXmlSourcePath = normalizeSourcePath(options.sourceXmlSourcePath || toProjectRelativePath(sourceXmlPath));
|
||||
const compatibilityCatalogSourcePath = normalizeSourcePath(
|
||||
options.compatibilityCatalogSourcePath || toProjectRelativePath(compatibilityCatalogPath),
|
||||
);
|
||||
|
||||
if (!(await fs.pathExists(sidecarPath))) {
|
||||
if (!sidecarPath || !(await fs.pathExists(sidecarPath))) {
|
||||
createValidationError(
|
||||
INDEX_DOCS_AUTHORITY_VALIDATION_ERROR_CODES.SIDECAR_FILE_NOT_FOUND,
|
||||
'Expected index-docs sidecar metadata file was not found',
|
||||
|
|
@ -320,6 +342,13 @@ async function validateIndexDocsAuthoritySplitAndPrecedence(options = {}) {
|
|||
canonicalId,
|
||||
authoritativePresenceKey: INDEX_DOCS_LOCKED_AUTHORITATIVE_PRESENCE_KEY,
|
||||
authoritativeRecords,
|
||||
metadataAuthority: {
|
||||
resolvedPath: normalizeSourcePath(resolvedMetadataAuthority.resolvedSourcePath || sidecarSourcePath),
|
||||
resolvedFilename: normalizeSourcePath(resolvedMetadataAuthority.resolvedFilename || ''),
|
||||
canonicalTargetFilename: normalizeSourcePath(resolvedMetadataAuthority.canonicalTargetFilename || 'skill-manifest.yaml'),
|
||||
canonicalTargetPath: normalizeSourcePath(resolvedMetadataAuthority.canonicalTargetSourcePath || sidecarSourcePath),
|
||||
derivationMode: normalizeSourcePath(resolvedMetadataAuthority.derivationMode || ''),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ const fs = require('fs-extra');
|
|||
const yaml = require('yaml');
|
||||
const csv = require('csv-parse/sync');
|
||||
const { getSourcePath } = require('../../../lib/project-root');
|
||||
const { resolveSkillMetadataAuthority } = require('./sidecar-contract-validator');
|
||||
const { normalizeDisplayedCommandLabel } = require('./help-catalog-generator');
|
||||
const { ManifestGenerator } = require('./manifest-generator');
|
||||
const {
|
||||
|
|
@ -14,12 +15,13 @@ const {
|
|||
validateGithubCopilotHelpLoaderEntries,
|
||||
} = require('./projection-compatibility-validator');
|
||||
|
||||
const INDEX_DOCS_SIDECAR_SOURCE_PATH = 'bmad-fork/src/core/tasks/index-docs.artifact.yaml';
|
||||
const INDEX_DOCS_SIDECAR_SOURCE_PATH = 'bmad-fork/src/core/tasks/index-docs/skill-manifest.yaml';
|
||||
const INDEX_DOCS_SOURCE_XML_SOURCE_PATH = 'bmad-fork/src/core/tasks/index-docs.xml';
|
||||
const INDEX_DOCS_EVIDENCE_ISSUER_COMPONENT = 'bmad-fork/tools/cli/installers/lib/core/index-docs-validation-harness.js';
|
||||
|
||||
const INDEX_DOCS_VALIDATION_ERROR_CODES = Object.freeze({
|
||||
REQUIRED_ARTIFACT_MISSING: 'ERR_INDEX_DOCS_VALIDATION_REQUIRED_ARTIFACT_MISSING',
|
||||
METADATA_RESOLUTION_FAILED: 'ERR_INDEX_DOCS_VALIDATION_METADATA_RESOLUTION_FAILED',
|
||||
CSV_SCHEMA_MISMATCH: 'ERR_INDEX_DOCS_VALIDATION_CSV_SCHEMA_MISMATCH',
|
||||
REQUIRED_ROW_MISSING: 'ERR_INDEX_DOCS_VALIDATION_REQUIRED_ROW_MISSING',
|
||||
YAML_SCHEMA_MISMATCH: 'ERR_INDEX_DOCS_VALIDATION_YAML_SCHEMA_MISMATCH',
|
||||
|
|
@ -889,16 +891,34 @@ class IndexDocsValidationHarness {
|
|||
const runtimeFolder = normalizeValue(options.bmadFolderName || '_bmad');
|
||||
const bmadDir = path.resolve(options.bmadDir || path.join(outputPaths.projectDir, runtimeFolder));
|
||||
const artifactPaths = this.buildArtifactPathsMap(outputPaths);
|
||||
const sidecarPath =
|
||||
options.sidecarPath ||
|
||||
((await fs.pathExists(path.join(outputPaths.projectDir, INDEX_DOCS_SIDECAR_SOURCE_PATH)))
|
||||
? path.join(outputPaths.projectDir, INDEX_DOCS_SIDECAR_SOURCE_PATH)
|
||||
: getSourcePath('core', 'tasks', 'index-docs.artifact.yaml'));
|
||||
const sourceXmlPath =
|
||||
options.sourceXmlPath ||
|
||||
((await fs.pathExists(path.join(outputPaths.projectDir, INDEX_DOCS_SOURCE_XML_SOURCE_PATH)))
|
||||
? path.join(outputPaths.projectDir, INDEX_DOCS_SOURCE_XML_SOURCE_PATH)
|
||||
: getSourcePath('core', 'tasks', 'index-docs.xml'));
|
||||
let resolvedMetadataAuthority;
|
||||
try {
|
||||
resolvedMetadataAuthority = await resolveSkillMetadataAuthority({
|
||||
sourceFilePath: sourceXmlPath,
|
||||
metadataPath: options.sidecarPath || '',
|
||||
projectRoot: outputPaths.projectDir,
|
||||
ambiguousErrorCode: INDEX_DOCS_VALIDATION_ERROR_CODES.METADATA_RESOLUTION_FAILED,
|
||||
});
|
||||
} catch (error) {
|
||||
throw new IndexDocsValidationHarnessError({
|
||||
code: INDEX_DOCS_VALIDATION_ERROR_CODES.METADATA_RESOLUTION_FAILED,
|
||||
detail: error.detail || error.message || 'metadata authority resolution failed',
|
||||
artifactId: 1,
|
||||
fieldPath: normalizeValue(error.fieldPath || '<file>'),
|
||||
sourcePath: normalizePath(error.sourcePath || INDEX_DOCS_SIDECAR_SOURCE_PATH),
|
||||
observedValue: normalizeValue(error.code || '<resolution-error>'),
|
||||
expectedValue: 'unambiguous metadata authority candidate',
|
||||
});
|
||||
}
|
||||
const sidecarPath =
|
||||
resolvedMetadataAuthority.resolvedAbsolutePath ||
|
||||
options.sidecarPath ||
|
||||
path.join(path.dirname(sourceXmlPath), path.basename(sourceXmlPath, path.extname(sourceXmlPath)), 'skill-manifest.yaml');
|
||||
|
||||
await fs.ensureDir(outputPaths.validationRoot);
|
||||
|
||||
|
|
|
|||
|
|
@ -36,13 +36,13 @@ const { CustomHandler } = require('../custom/handler');
|
|||
const prompts = require('../../../lib/prompts');
|
||||
const { BMAD_FOLDER_NAME } = require('../ide/shared/path-utils');
|
||||
|
||||
const EXEMPLAR_HELP_SIDECAR_SOURCE_PATH = 'bmad-fork/src/core/tasks/help.artifact.yaml';
|
||||
const EXEMPLAR_HELP_SIDECAR_SOURCE_PATH = 'bmad-fork/src/core/tasks/help/skill-manifest.yaml';
|
||||
const EXEMPLAR_HELP_SOURCE_MARKDOWN_SOURCE_PATH = 'bmad-fork/src/core/tasks/help.md';
|
||||
const EXEMPLAR_SHARD_DOC_SIDECAR_SOURCE_PATH = 'bmad-fork/src/core/tasks/shard-doc.artifact.yaml';
|
||||
const EXEMPLAR_SHARD_DOC_SIDECAR_SOURCE_PATH = 'bmad-fork/src/core/tasks/shard-doc/skill-manifest.yaml';
|
||||
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_SIDECAR_SOURCE_PATH = 'bmad-fork/src/core/tasks/index-docs/skill-manifest.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';
|
||||
|
|
|
|||
|
|
@ -14,9 +14,9 @@ const { validateTaskManifestCompatibilitySurface } = require('./projection-compa
|
|||
|
||||
// Load package.json for version info
|
||||
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 DEFAULT_EXEMPLAR_HELP_SIDECAR_SOURCE_PATH = 'bmad-fork/src/core/tasks/help/skill-manifest.yaml';
|
||||
const DEFAULT_EXEMPLAR_SHARD_DOC_SIDECAR_SOURCE_PATH = 'bmad-fork/src/core/tasks/shard-doc/skill-manifest.yaml';
|
||||
const DEFAULT_EXEMPLAR_INDEX_DOCS_SIDECAR_SOURCE_PATH = 'bmad-fork/src/core/tasks/index-docs/skill-manifest.yaml';
|
||||
const CANONICAL_ALIAS_TABLE_COLUMNS = Object.freeze([
|
||||
'canonicalId',
|
||||
'alias',
|
||||
|
|
|
|||
|
|
@ -3,9 +3,11 @@ const fs = require('fs-extra');
|
|||
const yaml = require('yaml');
|
||||
const csv = require('csv-parse/sync');
|
||||
const { getProjectRoot, getSourcePath } = require('../../../lib/project-root');
|
||||
const { resolveSkillMetadataAuthority } = require('./sidecar-contract-validator');
|
||||
|
||||
const SHARD_DOC_AUTHORITY_VALIDATION_ERROR_CODES = Object.freeze({
|
||||
SIDECAR_FILE_NOT_FOUND: 'ERR_SHARD_DOC_AUTHORITY_SIDECAR_FILE_NOT_FOUND',
|
||||
SIDECAR_FILENAME_AMBIGUOUS: 'ERR_SHARD_DOC_AUTHORITY_SIDECAR_FILENAME_AMBIGUOUS',
|
||||
SIDECAR_PARSE_FAILED: 'ERR_SHARD_DOC_AUTHORITY_SIDECAR_PARSE_FAILED',
|
||||
SIDECAR_INVALID_METADATA: 'ERR_SHARD_DOC_AUTHORITY_SIDECAR_INVALID_METADATA',
|
||||
SIDECAR_CANONICAL_ID_MISMATCH: 'ERR_SHARD_DOC_AUTHORITY_SIDECAR_CANONICAL_ID_MISMATCH',
|
||||
|
|
@ -249,18 +251,38 @@ function buildShardDocAuthorityRecords({ canonicalId, sidecarSourcePath, sourceX
|
|||
}
|
||||
|
||||
async function validateShardDocAuthoritySplitAndPrecedence(options = {}) {
|
||||
const sidecarPath = options.sidecarPath || getSourcePath('core', 'tasks', 'shard-doc.artifact.yaml');
|
||||
const sourceXmlPath = options.sourceXmlPath || getSourcePath('core', 'tasks', 'shard-doc.xml');
|
||||
const compatibilityCatalogPath = options.compatibilityCatalogPath || getSourcePath('core', 'module-help.csv');
|
||||
const compatibilityWorkflowFilePath = options.compatibilityWorkflowFilePath || '_bmad/core/tasks/shard-doc.xml';
|
||||
|
||||
const sidecarSourcePath = normalizeSourcePath(options.sidecarSourcePath || toProjectRelativePath(sidecarPath));
|
||||
let resolvedMetadataAuthority;
|
||||
try {
|
||||
resolvedMetadataAuthority = await resolveSkillMetadataAuthority({
|
||||
sourceFilePath: sourceXmlPath,
|
||||
metadataPath: options.sidecarPath || '',
|
||||
metadataSourcePath: options.sidecarSourcePath || '',
|
||||
ambiguousErrorCode: SHARD_DOC_AUTHORITY_VALIDATION_ERROR_CODES.SIDECAR_FILENAME_AMBIGUOUS,
|
||||
});
|
||||
} catch (error) {
|
||||
createValidationError(
|
||||
error.code || SHARD_DOC_AUTHORITY_VALIDATION_ERROR_CODES.SIDECAR_FILENAME_AMBIGUOUS,
|
||||
error.detail || error.message,
|
||||
error.fieldPath || '<file>',
|
||||
normalizeSourcePath(error.sourcePath || toProjectRelativePath(sourceXmlPath)),
|
||||
);
|
||||
}
|
||||
|
||||
const sidecarPath = resolvedMetadataAuthority.resolvedAbsolutePath;
|
||||
|
||||
const sidecarSourcePath = normalizeSourcePath(
|
||||
options.sidecarSourcePath || resolvedMetadataAuthority.canonicalTargetSourcePath || resolvedMetadataAuthority.resolvedSourcePath,
|
||||
);
|
||||
const sourceXmlSourcePath = normalizeSourcePath(options.sourceXmlSourcePath || toProjectRelativePath(sourceXmlPath));
|
||||
const compatibilityCatalogSourcePath = normalizeSourcePath(
|
||||
options.compatibilityCatalogSourcePath || toProjectRelativePath(compatibilityCatalogPath),
|
||||
);
|
||||
|
||||
if (!(await fs.pathExists(sidecarPath))) {
|
||||
if (!sidecarPath || !(await fs.pathExists(sidecarPath))) {
|
||||
createValidationError(
|
||||
SHARD_DOC_AUTHORITY_VALIDATION_ERROR_CODES.SIDECAR_FILE_NOT_FOUND,
|
||||
'Expected shard-doc sidecar metadata file was not found',
|
||||
|
|
@ -322,6 +344,13 @@ async function validateShardDocAuthoritySplitAndPrecedence(options = {}) {
|
|||
authoritativePresenceKey: SHARD_DOC_LOCKED_AUTHORITATIVE_PRESENCE_KEY,
|
||||
authoritativeRecords,
|
||||
checkedSurfaces: [sourceXmlSourcePath, compatibilityCatalogSourcePath],
|
||||
metadataAuthority: {
|
||||
resolvedPath: normalizeSourcePath(resolvedMetadataAuthority.resolvedSourcePath || sidecarSourcePath),
|
||||
resolvedFilename: normalizeSourcePath(resolvedMetadataAuthority.resolvedFilename || ''),
|
||||
canonicalTargetFilename: normalizeSourcePath(resolvedMetadataAuthority.canonicalTargetFilename || 'skill-manifest.yaml'),
|
||||
canonicalTargetPath: normalizeSourcePath(resolvedMetadataAuthority.canonicalTargetSourcePath || sidecarSourcePath),
|
||||
derivationMode: normalizeSourcePath(resolvedMetadataAuthority.derivationMode || ''),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ const fs = require('fs-extra');
|
|||
const yaml = require('yaml');
|
||||
const csv = require('csv-parse/sync');
|
||||
const { getSourcePath } = require('../../../lib/project-root');
|
||||
const { resolveSkillMetadataAuthority } = require('./sidecar-contract-validator');
|
||||
const { normalizeDisplayedCommandLabel } = require('./help-catalog-generator');
|
||||
const { ManifestGenerator } = require('./manifest-generator');
|
||||
const {
|
||||
|
|
@ -14,12 +15,13 @@ const {
|
|||
validateGithubCopilotHelpLoaderEntries,
|
||||
} = require('./projection-compatibility-validator');
|
||||
|
||||
const SHARD_DOC_SIDECAR_SOURCE_PATH = 'bmad-fork/src/core/tasks/shard-doc.artifact.yaml';
|
||||
const SHARD_DOC_SIDECAR_SOURCE_PATH = 'bmad-fork/src/core/tasks/shard-doc/skill-manifest.yaml';
|
||||
const SHARD_DOC_SOURCE_XML_SOURCE_PATH = 'bmad-fork/src/core/tasks/shard-doc.xml';
|
||||
const SHARD_DOC_EVIDENCE_ISSUER_COMPONENT = 'bmad-fork/tools/cli/installers/lib/core/shard-doc-validation-harness.js';
|
||||
|
||||
const SHARD_DOC_VALIDATION_ERROR_CODES = Object.freeze({
|
||||
REQUIRED_ARTIFACT_MISSING: 'ERR_SHARD_DOC_VALIDATION_REQUIRED_ARTIFACT_MISSING',
|
||||
METADATA_RESOLUTION_FAILED: 'ERR_SHARD_DOC_VALIDATION_METADATA_RESOLUTION_FAILED',
|
||||
CSV_SCHEMA_MISMATCH: 'ERR_SHARD_DOC_VALIDATION_CSV_SCHEMA_MISMATCH',
|
||||
REQUIRED_ROW_MISSING: 'ERR_SHARD_DOC_VALIDATION_REQUIRED_ROW_MISSING',
|
||||
YAML_SCHEMA_MISMATCH: 'ERR_SHARD_DOC_VALIDATION_YAML_SCHEMA_MISMATCH',
|
||||
|
|
@ -888,16 +890,34 @@ class ShardDocValidationHarness {
|
|||
const runtimeFolder = normalizeValue(options.bmadFolderName || '_bmad');
|
||||
const bmadDir = path.resolve(options.bmadDir || path.join(outputPaths.projectDir, runtimeFolder));
|
||||
const artifactPaths = this.buildArtifactPathsMap(outputPaths);
|
||||
const sidecarPath =
|
||||
options.sidecarPath ||
|
||||
((await fs.pathExists(path.join(outputPaths.projectDir, SHARD_DOC_SIDECAR_SOURCE_PATH)))
|
||||
? path.join(outputPaths.projectDir, SHARD_DOC_SIDECAR_SOURCE_PATH)
|
||||
: getSourcePath('core', 'tasks', 'shard-doc.artifact.yaml'));
|
||||
const sourceXmlPath =
|
||||
options.sourceXmlPath ||
|
||||
((await fs.pathExists(path.join(outputPaths.projectDir, SHARD_DOC_SOURCE_XML_SOURCE_PATH)))
|
||||
? path.join(outputPaths.projectDir, SHARD_DOC_SOURCE_XML_SOURCE_PATH)
|
||||
: getSourcePath('core', 'tasks', 'shard-doc.xml'));
|
||||
let resolvedMetadataAuthority;
|
||||
try {
|
||||
resolvedMetadataAuthority = await resolveSkillMetadataAuthority({
|
||||
sourceFilePath: sourceXmlPath,
|
||||
metadataPath: options.sidecarPath || '',
|
||||
projectRoot: outputPaths.projectDir,
|
||||
ambiguousErrorCode: SHARD_DOC_VALIDATION_ERROR_CODES.METADATA_RESOLUTION_FAILED,
|
||||
});
|
||||
} catch (error) {
|
||||
throw new ShardDocValidationHarnessError({
|
||||
code: SHARD_DOC_VALIDATION_ERROR_CODES.METADATA_RESOLUTION_FAILED,
|
||||
detail: error.detail || error.message || 'metadata authority resolution failed',
|
||||
artifactId: 1,
|
||||
fieldPath: normalizeValue(error.fieldPath || '<file>'),
|
||||
sourcePath: normalizePath(error.sourcePath || SHARD_DOC_SIDECAR_SOURCE_PATH),
|
||||
observedValue: normalizeValue(error.code || '<resolution-error>'),
|
||||
expectedValue: 'unambiguous metadata authority candidate',
|
||||
});
|
||||
}
|
||||
const sidecarPath =
|
||||
resolvedMetadataAuthority.resolvedAbsolutePath ||
|
||||
options.sidecarPath ||
|
||||
path.join(path.dirname(sourceXmlPath), path.basename(sourceXmlPath, path.extname(sourceXmlPath)), 'skill-manifest.yaml');
|
||||
|
||||
await fs.ensureDir(outputPaths.validationRoot);
|
||||
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ const HELP_SIDECAR_ERROR_CODES = Object.freeze({
|
|||
DEPENDENCIES_REQUIRES_NOT_EMPTY: 'ERR_HELP_SIDECAR_DEPENDENCIES_REQUIRES_NOT_EMPTY',
|
||||
MAJOR_VERSION_UNSUPPORTED: 'ERR_SIDECAR_MAJOR_VERSION_UNSUPPORTED',
|
||||
SOURCEPATH_BASENAME_MISMATCH: 'ERR_SIDECAR_SOURCEPATH_BASENAME_MISMATCH',
|
||||
METADATA_FILENAME_AMBIGUOUS: 'ERR_HELP_SIDECAR_METADATA_FILENAME_AMBIGUOUS',
|
||||
});
|
||||
|
||||
const SHARD_DOC_SIDECAR_ERROR_CODES = Object.freeze({
|
||||
|
|
@ -45,6 +46,7 @@ const SHARD_DOC_SIDECAR_ERROR_CODES = Object.freeze({
|
|||
DEPENDENCIES_REQUIRES_NOT_EMPTY: 'ERR_SHARD_DOC_SIDECAR_DEPENDENCIES_REQUIRES_NOT_EMPTY',
|
||||
MAJOR_VERSION_UNSUPPORTED: 'ERR_SHARD_DOC_SIDECAR_MAJOR_VERSION_UNSUPPORTED',
|
||||
SOURCEPATH_BASENAME_MISMATCH: 'ERR_SHARD_DOC_SIDECAR_SOURCEPATH_BASENAME_MISMATCH',
|
||||
METADATA_FILENAME_AMBIGUOUS: 'ERR_SHARD_DOC_SIDECAR_METADATA_FILENAME_AMBIGUOUS',
|
||||
});
|
||||
|
||||
const INDEX_DOCS_SIDECAR_ERROR_CODES = Object.freeze({
|
||||
|
|
@ -60,11 +62,21 @@ const INDEX_DOCS_SIDECAR_ERROR_CODES = Object.freeze({
|
|||
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',
|
||||
METADATA_FILENAME_AMBIGUOUS: 'ERR_INDEX_DOCS_SIDECAR_METADATA_FILENAME_AMBIGUOUS',
|
||||
});
|
||||
|
||||
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 SKILL_METADATA_CANONICAL_FILENAME = 'skill-manifest.yaml';
|
||||
const SKILL_METADATA_LEGACY_FILENAMES = Object.freeze(['bmad-config.yaml', 'manifest.yaml']);
|
||||
const SKILL_METADATA_DERIVATION_MODES = Object.freeze({
|
||||
CANONICAL: 'canonical',
|
||||
LEGACY_FALLBACK: 'legacy-fallback',
|
||||
});
|
||||
const SKILL_METADATA_RESOLUTION_ERROR_CODES = Object.freeze({
|
||||
AMBIGUOUS_MATCH: 'ERR_SKILL_METADATA_FILENAME_AMBIGUOUS',
|
||||
});
|
||||
const SIDECAR_SUPPORTED_SCHEMA_MAJOR = 1;
|
||||
|
||||
class SidecarContractError extends Error {
|
||||
|
|
@ -85,8 +97,7 @@ function normalizeSourcePath(value) {
|
|||
return String(value).replaceAll('\\', '/');
|
||||
}
|
||||
|
||||
function toProjectRelativePath(filePath) {
|
||||
const projectRoot = getProjectRoot();
|
||||
function toProjectRelativePath(filePath, projectRoot = getProjectRoot()) {
|
||||
const relative = path.relative(projectRoot, filePath);
|
||||
|
||||
if (!relative || relative.startsWith('..')) {
|
||||
|
|
@ -96,6 +107,17 @@ function toProjectRelativePath(filePath) {
|
|||
return normalizeSourcePath(relative);
|
||||
}
|
||||
|
||||
function dedupeAndSort(values) {
|
||||
const normalized = new Set();
|
||||
for (const value of values || []) {
|
||||
const text = normalizeSourcePath(value).trim();
|
||||
if (text.length > 0) {
|
||||
normalized.add(text);
|
||||
}
|
||||
}
|
||||
return [...normalized].sort((left, right) => left.localeCompare(right));
|
||||
}
|
||||
|
||||
function hasOwn(obj, key) {
|
||||
return Object.prototype.hasOwnProperty.call(obj, key);
|
||||
}
|
||||
|
|
@ -120,7 +142,169 @@ function parseSchemaMajorVersion(value) {
|
|||
return null;
|
||||
}
|
||||
|
||||
function getExpectedSidecarBasenameFromSourcePath(sourcePathValue) {
|
||||
function classifyMetadataFilename(filename) {
|
||||
const normalizedFilename = String(filename || '')
|
||||
.trim()
|
||||
.toLowerCase();
|
||||
if (normalizedFilename === SKILL_METADATA_CANONICAL_FILENAME) {
|
||||
return SKILL_METADATA_DERIVATION_MODES.CANONICAL;
|
||||
}
|
||||
if (SKILL_METADATA_LEGACY_FILENAMES.includes(normalizedFilename) || normalizedFilename.endsWith('.artifact.yaml')) {
|
||||
return SKILL_METADATA_DERIVATION_MODES.LEGACY_FALLBACK;
|
||||
}
|
||||
return SKILL_METADATA_DERIVATION_MODES.LEGACY_FALLBACK;
|
||||
}
|
||||
|
||||
function getMetadataStemFromSourcePath(sourcePathValue) {
|
||||
const normalizedSourcePath = normalizeSourcePath(sourcePathValue).trim();
|
||||
if (!normalizedSourcePath) return '';
|
||||
|
||||
const sourceBasename = path.posix.basename(normalizedSourcePath);
|
||||
if (!sourceBasename) return '';
|
||||
|
||||
const sourceExt = path.posix.extname(sourceBasename);
|
||||
const baseWithoutExt = sourceExt ? sourceBasename.slice(0, -sourceExt.length) : sourceBasename;
|
||||
return baseWithoutExt.trim();
|
||||
}
|
||||
|
||||
function buildSkillMetadataResolutionPlan({ sourceFilePath, projectRoot = getProjectRoot() }) {
|
||||
const absoluteSourceFilePath = path.resolve(sourceFilePath);
|
||||
const sourceDirAbsolutePath = path.dirname(absoluteSourceFilePath);
|
||||
const metadataStem = getMetadataStemFromSourcePath(absoluteSourceFilePath);
|
||||
const skillFolderAbsolutePath = path.join(sourceDirAbsolutePath, metadataStem);
|
||||
const canonicalTargetAbsolutePath = path.join(skillFolderAbsolutePath, SKILL_METADATA_CANONICAL_FILENAME);
|
||||
|
||||
const candidateGroups = [
|
||||
{
|
||||
precedenceToken: SKILL_METADATA_CANONICAL_FILENAME,
|
||||
derivationMode: SKILL_METADATA_DERIVATION_MODES.CANONICAL,
|
||||
// Canonical authority is per-skill only; root task-folder canonical files are not eligible.
|
||||
explicitCandidates: [canonicalTargetAbsolutePath],
|
||||
wildcardDirectories: [],
|
||||
},
|
||||
{
|
||||
precedenceToken: 'bmad-config.yaml',
|
||||
derivationMode: SKILL_METADATA_DERIVATION_MODES.LEGACY_FALLBACK,
|
||||
explicitCandidates: [path.join(skillFolderAbsolutePath, 'bmad-config.yaml'), path.join(sourceDirAbsolutePath, 'bmad-config.yaml')],
|
||||
wildcardDirectories: [],
|
||||
},
|
||||
{
|
||||
precedenceToken: 'manifest.yaml',
|
||||
derivationMode: SKILL_METADATA_DERIVATION_MODES.LEGACY_FALLBACK,
|
||||
explicitCandidates: [path.join(skillFolderAbsolutePath, 'manifest.yaml'), path.join(sourceDirAbsolutePath, 'manifest.yaml')],
|
||||
wildcardDirectories: [],
|
||||
},
|
||||
{
|
||||
precedenceToken: `${metadataStem}.artifact.yaml`,
|
||||
derivationMode: SKILL_METADATA_DERIVATION_MODES.LEGACY_FALLBACK,
|
||||
explicitCandidates: [
|
||||
path.join(sourceDirAbsolutePath, `${metadataStem}.artifact.yaml`),
|
||||
path.join(skillFolderAbsolutePath, `${metadataStem}.artifact.yaml`),
|
||||
],
|
||||
wildcardDirectories: [],
|
||||
},
|
||||
];
|
||||
|
||||
return {
|
||||
metadataStem,
|
||||
canonicalTargetAbsolutePath,
|
||||
canonicalTargetSourcePath: toProjectRelativePath(canonicalTargetAbsolutePath, projectRoot),
|
||||
candidateGroups,
|
||||
};
|
||||
}
|
||||
|
||||
async function resolveCandidateGroupMatches(group = {}) {
|
||||
const explicitMatches = [];
|
||||
for (const candidatePath of group.explicitCandidates || []) {
|
||||
if (await fs.pathExists(candidatePath)) {
|
||||
explicitMatches.push(path.resolve(candidatePath));
|
||||
}
|
||||
}
|
||||
|
||||
const wildcardMatches = [];
|
||||
for (const wildcardDirectory of group.wildcardDirectories || []) {
|
||||
if (!(await fs.pathExists(wildcardDirectory))) {
|
||||
continue;
|
||||
}
|
||||
const directoryEntries = await fs.readdir(wildcardDirectory, { withFileTypes: true });
|
||||
for (const entry of directoryEntries) {
|
||||
if (!entry.isFile()) continue;
|
||||
const filename = String(entry.name || '').trim();
|
||||
if (!filename.toLowerCase().endsWith('.artifact.yaml')) continue;
|
||||
wildcardMatches.push(path.join(wildcardDirectory, filename));
|
||||
}
|
||||
}
|
||||
|
||||
return dedupeAndSort([...explicitMatches, ...wildcardMatches]);
|
||||
}
|
||||
|
||||
async function resolveSkillMetadataAuthority({
|
||||
sourceFilePath,
|
||||
metadataPath = '',
|
||||
metadataSourcePath = '',
|
||||
projectRoot = getProjectRoot(),
|
||||
ambiguousErrorCode = SKILL_METADATA_RESOLUTION_ERROR_CODES.AMBIGUOUS_MATCH,
|
||||
}) {
|
||||
const resolutionPlan = buildSkillMetadataResolutionPlan({
|
||||
sourceFilePath,
|
||||
projectRoot,
|
||||
});
|
||||
|
||||
const resolvedMetadataPath = String(metadataPath || '').trim();
|
||||
if (resolvedMetadataPath.length > 0) {
|
||||
const resolvedAbsolutePath = path.resolve(resolvedMetadataPath);
|
||||
const resolvedFilename = path.posix.basename(normalizeSourcePath(resolvedAbsolutePath));
|
||||
return {
|
||||
resolvedAbsolutePath,
|
||||
resolvedSourcePath: normalizeSourcePath(metadataSourcePath || toProjectRelativePath(resolvedAbsolutePath, projectRoot)),
|
||||
resolvedFilename,
|
||||
canonicalTargetFilename: SKILL_METADATA_CANONICAL_FILENAME,
|
||||
canonicalTargetSourcePath: resolutionPlan.canonicalTargetSourcePath,
|
||||
derivationMode: classifyMetadataFilename(resolvedFilename),
|
||||
precedenceToken: resolvedFilename,
|
||||
};
|
||||
}
|
||||
|
||||
for (const group of resolutionPlan.candidateGroups) {
|
||||
const matches = await resolveCandidateGroupMatches(group);
|
||||
if (matches.length === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (matches.length > 1) {
|
||||
throw new SidecarContractError({
|
||||
code: ambiguousErrorCode,
|
||||
detail: `metadata filename resolution is ambiguous for precedence "${group.precedenceToken}": ${matches.join('|')}`,
|
||||
fieldPath: '<file>',
|
||||
sourcePath: resolutionPlan.canonicalTargetSourcePath,
|
||||
});
|
||||
}
|
||||
|
||||
const resolvedAbsolutePath = matches[0];
|
||||
const resolvedFilename = path.posix.basename(normalizeSourcePath(resolvedAbsolutePath));
|
||||
return {
|
||||
resolvedAbsolutePath,
|
||||
resolvedSourcePath: normalizeSourcePath(toProjectRelativePath(resolvedAbsolutePath, projectRoot)),
|
||||
resolvedFilename,
|
||||
canonicalTargetFilename: SKILL_METADATA_CANONICAL_FILENAME,
|
||||
canonicalTargetSourcePath: resolutionPlan.canonicalTargetSourcePath,
|
||||
derivationMode: group.derivationMode,
|
||||
precedenceToken: group.precedenceToken,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
resolvedAbsolutePath: '',
|
||||
resolvedSourcePath: '',
|
||||
resolvedFilename: '',
|
||||
canonicalTargetFilename: SKILL_METADATA_CANONICAL_FILENAME,
|
||||
canonicalTargetSourcePath: resolutionPlan.canonicalTargetSourcePath,
|
||||
derivationMode: '',
|
||||
precedenceToken: '',
|
||||
};
|
||||
}
|
||||
|
||||
function getExpectedLegacyArtifactBasenameFromSourcePath(sourcePathValue) {
|
||||
const normalized = normalizeSourcePath(sourcePathValue).trim();
|
||||
if (!normalized) return '';
|
||||
|
||||
|
|
@ -218,11 +402,15 @@ function validateSidecarContractData(sidecarData, options) {
|
|||
}
|
||||
|
||||
const normalizedDeclaredSourcePath = normalizeSourcePath(sidecarData.sourcePath);
|
||||
const sidecarBasename = path.posix.basename(sourcePath);
|
||||
const expectedSidecarBasename = getExpectedSidecarBasenameFromSourcePath(normalizedDeclaredSourcePath);
|
||||
const sidecarBasename = path.posix.basename(normalizeSourcePath(sourcePath)).toLowerCase();
|
||||
const expectedLegacyArtifactBasename = getExpectedLegacyArtifactBasenameFromSourcePath(normalizedDeclaredSourcePath).toLowerCase();
|
||||
const allowedMetadataBasenames = new Set([SKILL_METADATA_CANONICAL_FILENAME, ...SKILL_METADATA_LEGACY_FILENAMES]);
|
||||
if (expectedLegacyArtifactBasename.length > 0) {
|
||||
allowedMetadataBasenames.add(expectedLegacyArtifactBasename);
|
||||
}
|
||||
|
||||
const sourcePathMismatch = normalizedDeclaredSourcePath !== expectedCanonicalSourcePath;
|
||||
const basenameMismatch = !expectedSidecarBasename || sidecarBasename !== expectedSidecarBasename;
|
||||
const basenameMismatch = !allowedMetadataBasenames.has(sidecarBasename);
|
||||
|
||||
if (sourcePathMismatch || basenameMismatch) {
|
||||
createValidationError(
|
||||
|
|
@ -235,7 +423,7 @@ function validateSidecarContractData(sidecarData, options) {
|
|||
}
|
||||
|
||||
function validateHelpSidecarContractData(sidecarData, options = {}) {
|
||||
const sourcePath = normalizeSourcePath(options.errorSourcePath || 'src/core/tasks/help.artifact.yaml');
|
||||
const sourcePath = normalizeSourcePath(options.errorSourcePath || 'src/core/tasks/help/skill-manifest.yaml');
|
||||
validateSidecarContractData(sidecarData, {
|
||||
sourcePath,
|
||||
requiredFields: HELP_SIDECAR_REQUIRED_FIELDS,
|
||||
|
|
@ -255,7 +443,7 @@ function validateHelpSidecarContractData(sidecarData, options = {}) {
|
|||
}
|
||||
|
||||
function validateShardDocSidecarContractData(sidecarData, options = {}) {
|
||||
const sourcePath = normalizeSourcePath(options.errorSourcePath || 'src/core/tasks/shard-doc.artifact.yaml');
|
||||
const sourcePath = normalizeSourcePath(options.errorSourcePath || 'src/core/tasks/shard-doc/skill-manifest.yaml');
|
||||
validateSidecarContractData(sidecarData, {
|
||||
sourcePath,
|
||||
requiredFields: SHARD_DOC_SIDECAR_REQUIRED_FIELDS,
|
||||
|
|
@ -275,7 +463,7 @@ function validateShardDocSidecarContractData(sidecarData, options = {}) {
|
|||
}
|
||||
|
||||
function validateIndexDocsSidecarContractData(sidecarData, options = {}) {
|
||||
const sourcePath = normalizeSourcePath(options.errorSourcePath || 'src/core/tasks/index-docs.artifact.yaml');
|
||||
const sourcePath = normalizeSourcePath(options.errorSourcePath || 'src/core/tasks/index-docs/skill-manifest.yaml');
|
||||
validateSidecarContractData(sidecarData, {
|
||||
sourcePath,
|
||||
requiredFields: INDEX_DOCS_SIDECAR_REQUIRED_FIELDS,
|
||||
|
|
@ -294,10 +482,20 @@ function validateIndexDocsSidecarContractData(sidecarData, options = {}) {
|
|||
});
|
||||
}
|
||||
|
||||
async function validateHelpSidecarContractFile(sidecarPath = getSourcePath('core', 'tasks', 'help.artifact.yaml'), options = {}) {
|
||||
const normalizedSourcePath = normalizeSourcePath(options.errorSourcePath || toProjectRelativePath(sidecarPath));
|
||||
async function validateHelpSidecarContractFile(sidecarPath = '', options = {}) {
|
||||
const sourceFilePath = options.sourceFilePath || getSourcePath('core', 'tasks', 'help.md');
|
||||
const resolvedMetadataAuthority = await resolveSkillMetadataAuthority({
|
||||
sourceFilePath,
|
||||
metadataPath: sidecarPath,
|
||||
metadataSourcePath: options.errorSourcePath,
|
||||
ambiguousErrorCode: HELP_SIDECAR_ERROR_CODES.METADATA_FILENAME_AMBIGUOUS,
|
||||
});
|
||||
const resolvedSidecarPath = resolvedMetadataAuthority.resolvedAbsolutePath;
|
||||
const normalizedSourcePath = normalizeSourcePath(
|
||||
options.errorSourcePath || resolvedMetadataAuthority.resolvedSourcePath || resolvedMetadataAuthority.canonicalTargetSourcePath,
|
||||
);
|
||||
|
||||
if (!(await fs.pathExists(sidecarPath))) {
|
||||
if (!resolvedSidecarPath || !(await fs.pathExists(resolvedSidecarPath))) {
|
||||
createValidationError(
|
||||
HELP_SIDECAR_ERROR_CODES.FILE_NOT_FOUND,
|
||||
'<file>',
|
||||
|
|
@ -308,7 +506,7 @@ async function validateHelpSidecarContractFile(sidecarPath = getSourcePath('core
|
|||
|
||||
let parsedSidecar;
|
||||
try {
|
||||
const sidecarRaw = await fs.readFile(sidecarPath, 'utf8');
|
||||
const sidecarRaw = await fs.readFile(resolvedSidecarPath, 'utf8');
|
||||
parsedSidecar = yaml.parse(sidecarRaw);
|
||||
} catch (error) {
|
||||
createValidationError(
|
||||
|
|
@ -320,12 +518,23 @@ async function validateHelpSidecarContractFile(sidecarPath = getSourcePath('core
|
|||
}
|
||||
|
||||
validateHelpSidecarContractData(parsedSidecar, { errorSourcePath: normalizedSourcePath });
|
||||
return resolvedMetadataAuthority;
|
||||
}
|
||||
|
||||
async function validateShardDocSidecarContractFile(sidecarPath = getSourcePath('core', 'tasks', 'shard-doc.artifact.yaml'), options = {}) {
|
||||
const normalizedSourcePath = normalizeSourcePath(options.errorSourcePath || toProjectRelativePath(sidecarPath));
|
||||
async function validateShardDocSidecarContractFile(sidecarPath = '', options = {}) {
|
||||
const sourceFilePath = options.sourceFilePath || getSourcePath('core', 'tasks', 'shard-doc.xml');
|
||||
const resolvedMetadataAuthority = await resolveSkillMetadataAuthority({
|
||||
sourceFilePath,
|
||||
metadataPath: sidecarPath,
|
||||
metadataSourcePath: options.errorSourcePath,
|
||||
ambiguousErrorCode: SHARD_DOC_SIDECAR_ERROR_CODES.METADATA_FILENAME_AMBIGUOUS,
|
||||
});
|
||||
const resolvedSidecarPath = resolvedMetadataAuthority.resolvedAbsolutePath;
|
||||
const normalizedSourcePath = normalizeSourcePath(
|
||||
options.errorSourcePath || resolvedMetadataAuthority.resolvedSourcePath || resolvedMetadataAuthority.canonicalTargetSourcePath,
|
||||
);
|
||||
|
||||
if (!(await fs.pathExists(sidecarPath))) {
|
||||
if (!resolvedSidecarPath || !(await fs.pathExists(resolvedSidecarPath))) {
|
||||
createValidationError(
|
||||
SHARD_DOC_SIDECAR_ERROR_CODES.FILE_NOT_FOUND,
|
||||
'<file>',
|
||||
|
|
@ -336,7 +545,7 @@ async function validateShardDocSidecarContractFile(sidecarPath = getSourcePath('
|
|||
|
||||
let parsedSidecar;
|
||||
try {
|
||||
const sidecarRaw = await fs.readFile(sidecarPath, 'utf8');
|
||||
const sidecarRaw = await fs.readFile(resolvedSidecarPath, 'utf8');
|
||||
parsedSidecar = yaml.parse(sidecarRaw);
|
||||
} catch (error) {
|
||||
createValidationError(
|
||||
|
|
@ -348,15 +557,23 @@ async function validateShardDocSidecarContractFile(sidecarPath = getSourcePath('
|
|||
}
|
||||
|
||||
validateShardDocSidecarContractData(parsedSidecar, { errorSourcePath: normalizedSourcePath });
|
||||
return resolvedMetadataAuthority;
|
||||
}
|
||||
|
||||
async function validateIndexDocsSidecarContractFile(
|
||||
sidecarPath = getSourcePath('core', 'tasks', 'index-docs.artifact.yaml'),
|
||||
options = {},
|
||||
) {
|
||||
const normalizedSourcePath = normalizeSourcePath(options.errorSourcePath || toProjectRelativePath(sidecarPath));
|
||||
async function validateIndexDocsSidecarContractFile(sidecarPath = '', options = {}) {
|
||||
const sourceFilePath = options.sourceFilePath || getSourcePath('core', 'tasks', 'index-docs.xml');
|
||||
const resolvedMetadataAuthority = await resolveSkillMetadataAuthority({
|
||||
sourceFilePath,
|
||||
metadataPath: sidecarPath,
|
||||
metadataSourcePath: options.errorSourcePath,
|
||||
ambiguousErrorCode: INDEX_DOCS_SIDECAR_ERROR_CODES.METADATA_FILENAME_AMBIGUOUS,
|
||||
});
|
||||
const resolvedSidecarPath = resolvedMetadataAuthority.resolvedAbsolutePath;
|
||||
const normalizedSourcePath = normalizeSourcePath(
|
||||
options.errorSourcePath || resolvedMetadataAuthority.resolvedSourcePath || resolvedMetadataAuthority.canonicalTargetSourcePath,
|
||||
);
|
||||
|
||||
if (!(await fs.pathExists(sidecarPath))) {
|
||||
if (!resolvedSidecarPath || !(await fs.pathExists(resolvedSidecarPath))) {
|
||||
createValidationError(
|
||||
INDEX_DOCS_SIDECAR_ERROR_CODES.FILE_NOT_FOUND,
|
||||
'<file>',
|
||||
|
|
@ -367,7 +584,7 @@ async function validateIndexDocsSidecarContractFile(
|
|||
|
||||
let parsedSidecar;
|
||||
try {
|
||||
const sidecarRaw = await fs.readFile(sidecarPath, 'utf8');
|
||||
const sidecarRaw = await fs.readFile(resolvedSidecarPath, 'utf8');
|
||||
parsedSidecar = yaml.parse(sidecarRaw);
|
||||
} catch (error) {
|
||||
createValidationError(
|
||||
|
|
@ -379,6 +596,7 @@ async function validateIndexDocsSidecarContractFile(
|
|||
}
|
||||
|
||||
validateIndexDocsSidecarContractData(parsedSidecar, { errorSourcePath: normalizedSourcePath });
|
||||
return resolvedMetadataAuthority;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
|
@ -388,7 +606,12 @@ module.exports = {
|
|||
HELP_SIDECAR_ERROR_CODES,
|
||||
SHARD_DOC_SIDECAR_ERROR_CODES,
|
||||
INDEX_DOCS_SIDECAR_ERROR_CODES,
|
||||
SKILL_METADATA_CANONICAL_FILENAME,
|
||||
SKILL_METADATA_DERIVATION_MODES,
|
||||
SKILL_METADATA_LEGACY_FILENAMES,
|
||||
SKILL_METADATA_RESOLUTION_ERROR_CODES,
|
||||
SidecarContractError,
|
||||
resolveSkillMetadataAuthority,
|
||||
validateHelpSidecarContractData,
|
||||
validateHelpSidecarContractFile,
|
||||
validateShardDocSidecarContractData,
|
||||
|
|
|
|||
|
|
@ -9,10 +9,12 @@ const { TaskToolCommandGenerator } = require('./shared/task-tool-command-generat
|
|||
const { getTasksFromBmad } = require('./shared/bmad-artifacts');
|
||||
const { toDashPath, customAgentDashName } = require('./shared/path-utils');
|
||||
const { normalizeAndResolveExemplarAlias } = require('../core/help-alias-normalizer');
|
||||
const { resolveSkillMetadataAuthority } = require('../core/sidecar-contract-validator');
|
||||
const prompts = require('../../../lib/prompts');
|
||||
|
||||
const CODEX_EXPORT_DERIVATION_ERROR_CODES = Object.freeze({
|
||||
SIDECAR_FILE_NOT_FOUND: 'ERR_CODEX_EXPORT_SIDECAR_FILE_NOT_FOUND',
|
||||
SIDECAR_FILENAME_AMBIGUOUS: 'ERR_CODEX_EXPORT_SIDECAR_FILENAME_AMBIGUOUS',
|
||||
SIDECAR_PARSE_FAILED: 'ERR_CODEX_EXPORT_SIDECAR_PARSE_FAILED',
|
||||
CANONICAL_ID_MISSING: 'ERR_CODEX_EXPORT_CANONICAL_ID_MISSING',
|
||||
CANONICAL_ID_DERIVATION_FAILED: 'ERR_CODEX_EXPORT_CANONICAL_ID_DERIVATION_FAILED',
|
||||
|
|
@ -22,9 +24,9 @@ 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_SIDECAR_CONTRACT_SOURCE_PATH = 'bmad-fork/src/core/tasks/help/skill-manifest.yaml';
|
||||
const EXEMPLAR_SHARD_DOC_SIDECAR_CONTRACT_SOURCE_PATH = 'bmad-fork/src/core/tasks/shard-doc/skill-manifest.yaml';
|
||||
const EXEMPLAR_INDEX_DOCS_SIDECAR_CONTRACT_SOURCE_PATH = 'bmad-fork/src/core/tasks/index-docs/skill-manifest.yaml';
|
||||
const EXEMPLAR_HELP_EXPORT_DERIVATION_SOURCE_TYPE = 'sidecar-canonical-id';
|
||||
const SHARD_DOC_EXPORT_ALIAS_ROWS = Object.freeze([
|
||||
Object.freeze({
|
||||
|
|
@ -71,12 +73,12 @@ const EXEMPLAR_CONVERTED_TASK_EXPORT_TARGETS = Object.freeze({
|
|||
taskSourcePath: EXEMPLAR_HELP_TASK_MARKDOWN_SOURCE_PATH,
|
||||
sourcePathSuffix: '/core/tasks/help.md',
|
||||
sidecarSourcePath: EXEMPLAR_HELP_SIDECAR_CONTRACT_SOURCE_PATH,
|
||||
sidecarSourceCandidates: Object.freeze([
|
||||
sourceFileCandidates: Object.freeze([
|
||||
Object.freeze({
|
||||
segments: ['bmad-fork', 'src', 'core', 'tasks', 'help.artifact.yaml'],
|
||||
segments: ['bmad-fork', 'src', 'core', 'tasks', 'help.md'],
|
||||
}),
|
||||
Object.freeze({
|
||||
segments: ['src', 'core', 'tasks', 'help.artifact.yaml'],
|
||||
segments: ['src', 'core', 'tasks', 'help.md'],
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
|
|
@ -85,12 +87,12 @@ const EXEMPLAR_CONVERTED_TASK_EXPORT_TARGETS = Object.freeze({
|
|||
sourcePathSuffix: '/core/tasks/shard-doc.xml',
|
||||
sidecarSourcePath: EXEMPLAR_SHARD_DOC_SIDECAR_CONTRACT_SOURCE_PATH,
|
||||
aliasRows: SHARD_DOC_EXPORT_ALIAS_ROWS,
|
||||
sidecarSourceCandidates: Object.freeze([
|
||||
sourceFileCandidates: Object.freeze([
|
||||
Object.freeze({
|
||||
segments: ['bmad-fork', 'src', 'core', 'tasks', 'shard-doc.artifact.yaml'],
|
||||
segments: ['bmad-fork', 'src', 'core', 'tasks', 'shard-doc.xml'],
|
||||
}),
|
||||
Object.freeze({
|
||||
segments: ['src', 'core', 'tasks', 'shard-doc.artifact.yaml'],
|
||||
segments: ['src', 'core', 'tasks', 'shard-doc.xml'],
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
|
|
@ -99,12 +101,12 @@ const EXEMPLAR_CONVERTED_TASK_EXPORT_TARGETS = Object.freeze({
|
|||
sourcePathSuffix: '/core/tasks/index-docs.xml',
|
||||
sidecarSourcePath: EXEMPLAR_INDEX_DOCS_SIDECAR_CONTRACT_SOURCE_PATH,
|
||||
aliasRows: INDEX_DOCS_EXPORT_ALIAS_ROWS,
|
||||
sidecarSourceCandidates: Object.freeze([
|
||||
sourceFileCandidates: Object.freeze([
|
||||
Object.freeze({
|
||||
segments: ['bmad-fork', 'src', 'core', 'tasks', 'index-docs.artifact.yaml'],
|
||||
segments: ['bmad-fork', 'src', 'core', 'tasks', 'index-docs.xml'],
|
||||
}),
|
||||
Object.freeze({
|
||||
segments: ['src', 'core', 'tasks', 'index-docs.artifact.yaml'],
|
||||
segments: ['src', 'core', 'tasks', 'index-docs.xml'],
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
|
|
@ -375,58 +377,96 @@ class CodexSetup extends BaseIdeSetup {
|
|||
}
|
||||
|
||||
async loadConvertedTaskSidecar(projectDir, exportTarget) {
|
||||
for (const candidate of exportTarget.sidecarSourceCandidates) {
|
||||
const sidecarPath = path.join(projectDir, ...candidate.segments);
|
||||
if (await fs.pathExists(sidecarPath)) {
|
||||
let sidecarData;
|
||||
try {
|
||||
sidecarData = yaml.parse(await fs.readFile(sidecarPath, 'utf8'));
|
||||
} catch (error) {
|
||||
this.throwExportDerivationError({
|
||||
code: CODEX_EXPORT_DERIVATION_ERROR_CODES.SIDECAR_PARSE_FAILED,
|
||||
detail: `YAML parse failure: ${error.message}`,
|
||||
fieldPath: '<document>',
|
||||
sourcePath: exportTarget.sidecarSourcePath,
|
||||
observedValue: '<parse-error>',
|
||||
cause: error,
|
||||
});
|
||||
}
|
||||
const sourceCandidates = (exportTarget.sourceFileCandidates || []).map((candidate) => path.join(projectDir, ...candidate.segments));
|
||||
if (sourceCandidates.length === 0) {
|
||||
this.throwExportDerivationError({
|
||||
code: CODEX_EXPORT_DERIVATION_ERROR_CODES.SIDECAR_FILE_NOT_FOUND,
|
||||
detail: 'expected exemplar metadata source candidates are missing',
|
||||
fieldPath: '<file>',
|
||||
sourcePath: exportTarget.sidecarSourcePath,
|
||||
observedValue: projectDir,
|
||||
});
|
||||
}
|
||||
|
||||
if (!sidecarData || typeof sidecarData !== 'object' || Array.isArray(sidecarData)) {
|
||||
this.throwExportDerivationError({
|
||||
code: CODEX_EXPORT_DERIVATION_ERROR_CODES.SIDECAR_PARSE_FAILED,
|
||||
detail: 'sidecar root must be a YAML mapping object',
|
||||
fieldPath: '<document>',
|
||||
sourcePath: exportTarget.sidecarSourcePath,
|
||||
observedValue: typeof sidecarData,
|
||||
});
|
||||
let resolvedMetadataAuthority = null;
|
||||
for (const sourceCandidate of sourceCandidates) {
|
||||
try {
|
||||
const resolution = await resolveSkillMetadataAuthority({
|
||||
sourceFilePath: sourceCandidate,
|
||||
projectRoot: projectDir,
|
||||
ambiguousErrorCode: CODEX_EXPORT_DERIVATION_ERROR_CODES.SIDECAR_FILENAME_AMBIGUOUS,
|
||||
});
|
||||
if (!resolvedMetadataAuthority) {
|
||||
resolvedMetadataAuthority = resolution;
|
||||
}
|
||||
|
||||
const canonicalId = String(sidecarData.canonicalId || '').trim();
|
||||
if (canonicalId.length === 0) {
|
||||
this.throwExportDerivationError({
|
||||
code: CODEX_EXPORT_DERIVATION_ERROR_CODES.CANONICAL_ID_MISSING,
|
||||
detail: 'sidecar canonicalId is required for exemplar export derivation',
|
||||
fieldPath: 'canonicalId',
|
||||
sourcePath: exportTarget.sidecarSourcePath,
|
||||
observedValue: canonicalId,
|
||||
});
|
||||
if (resolution.resolvedAbsolutePath && (await fs.pathExists(resolution.resolvedAbsolutePath))) {
|
||||
resolvedMetadataAuthority = resolution;
|
||||
break;
|
||||
}
|
||||
|
||||
return {
|
||||
canonicalId,
|
||||
} catch (error) {
|
||||
this.throwExportDerivationError({
|
||||
code: error.code || CODEX_EXPORT_DERIVATION_ERROR_CODES.SIDECAR_FILENAME_AMBIGUOUS,
|
||||
detail: error.detail || error.message,
|
||||
fieldPath: error.fieldPath || '<file>',
|
||||
sourcePath: exportTarget.sidecarSourcePath,
|
||||
};
|
||||
observedValue: error.sourcePath || projectDir,
|
||||
cause: error,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
this.throwExportDerivationError({
|
||||
code: CODEX_EXPORT_DERIVATION_ERROR_CODES.SIDECAR_FILE_NOT_FOUND,
|
||||
detail: 'expected exemplar sidecar metadata file was not found',
|
||||
fieldPath: '<file>',
|
||||
const sidecarPath = resolvedMetadataAuthority.resolvedAbsolutePath;
|
||||
if (!sidecarPath || !(await fs.pathExists(sidecarPath))) {
|
||||
this.throwExportDerivationError({
|
||||
code: CODEX_EXPORT_DERIVATION_ERROR_CODES.SIDECAR_FILE_NOT_FOUND,
|
||||
detail: 'expected exemplar sidecar metadata file was not found',
|
||||
fieldPath: '<file>',
|
||||
sourcePath: exportTarget.sidecarSourcePath,
|
||||
observedValue: projectDir,
|
||||
});
|
||||
}
|
||||
|
||||
let sidecarData;
|
||||
try {
|
||||
sidecarData = yaml.parse(await fs.readFile(sidecarPath, 'utf8'));
|
||||
} catch (error) {
|
||||
this.throwExportDerivationError({
|
||||
code: CODEX_EXPORT_DERIVATION_ERROR_CODES.SIDECAR_PARSE_FAILED,
|
||||
detail: `YAML parse failure: ${error.message}`,
|
||||
fieldPath: '<document>',
|
||||
sourcePath: exportTarget.sidecarSourcePath,
|
||||
observedValue: '<parse-error>',
|
||||
cause: error,
|
||||
});
|
||||
}
|
||||
|
||||
if (!sidecarData || typeof sidecarData !== 'object' || Array.isArray(sidecarData)) {
|
||||
this.throwExportDerivationError({
|
||||
code: CODEX_EXPORT_DERIVATION_ERROR_CODES.SIDECAR_PARSE_FAILED,
|
||||
detail: 'sidecar root must be a YAML mapping object',
|
||||
fieldPath: '<document>',
|
||||
sourcePath: exportTarget.sidecarSourcePath,
|
||||
observedValue: typeof sidecarData,
|
||||
});
|
||||
}
|
||||
|
||||
const canonicalId = String(sidecarData.canonicalId || '').trim();
|
||||
if (canonicalId.length === 0) {
|
||||
this.throwExportDerivationError({
|
||||
code: CODEX_EXPORT_DERIVATION_ERROR_CODES.CANONICAL_ID_MISSING,
|
||||
detail: 'sidecar canonicalId is required for exemplar export derivation',
|
||||
fieldPath: 'canonicalId',
|
||||
sourcePath: exportTarget.sidecarSourcePath,
|
||||
observedValue: canonicalId,
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
canonicalId,
|
||||
sourcePath: exportTarget.sidecarSourcePath,
|
||||
observedValue: projectDir,
|
||||
});
|
||||
resolvedFilename: String(resolvedMetadataAuthority.resolvedFilename || ''),
|
||||
derivationMode: String(resolvedMetadataAuthority.derivationMode || ''),
|
||||
};
|
||||
}
|
||||
|
||||
async resolveSkillIdentityFromArtifact(artifact, projectDir) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue