feat(installer): add index-docs deterministic validation harness
This commit is contained in:
parent
99537b20ab
commit
3f82476e20
|
|
@ -88,6 +88,11 @@ const {
|
||||||
SHARD_DOC_VALIDATION_ARTIFACT_REGISTRY,
|
SHARD_DOC_VALIDATION_ARTIFACT_REGISTRY,
|
||||||
ShardDocValidationHarness,
|
ShardDocValidationHarness,
|
||||||
} = require('../tools/cli/installers/lib/core/shard-doc-validation-harness');
|
} = require('../tools/cli/installers/lib/core/shard-doc-validation-harness');
|
||||||
|
const {
|
||||||
|
INDEX_DOCS_VALIDATION_ERROR_CODES,
|
||||||
|
INDEX_DOCS_VALIDATION_ARTIFACT_REGISTRY,
|
||||||
|
IndexDocsValidationHarness,
|
||||||
|
} = require('../tools/cli/installers/lib/core/index-docs-validation-harness');
|
||||||
|
|
||||||
// ANSI colors
|
// ANSI colors
|
||||||
const colors = {
|
const colors = {
|
||||||
|
|
@ -5415,6 +5420,443 @@ async function runTests() {
|
||||||
|
|
||||||
console.log('');
|
console.log('');
|
||||||
|
|
||||||
|
// Test 16: Index-docs Validation Artifact Suite
|
||||||
|
// ============================================================
|
||||||
|
console.log(`${colors.yellow}Test Suite 16: Index-docs Validation Artifact Suite${colors.reset}\n`);
|
||||||
|
|
||||||
|
const tempIndexDocsValidationHarnessRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-index-docs-validation-suite-'));
|
||||||
|
try {
|
||||||
|
const tempProjectRoot = tempIndexDocsValidationHarnessRoot;
|
||||||
|
const tempBmadDir = path.join(tempProjectRoot, '_bmad');
|
||||||
|
const tempConfigDir = path.join(tempBmadDir, '_config');
|
||||||
|
const tempSourceTasksDir = path.join(tempProjectRoot, 'bmad-fork', 'src', 'core', 'tasks');
|
||||||
|
const commandLabelReportPath = path.join(tempConfigDir, 'bmad-help-command-label-report.csv');
|
||||||
|
|
||||||
|
await fs.ensureDir(tempConfigDir);
|
||||||
|
await fs.ensureDir(tempSourceTasksDir);
|
||||||
|
|
||||||
|
const writeCsv = async (filePath, columns, rows) => {
|
||||||
|
const buildCsvLine = (values) =>
|
||||||
|
values
|
||||||
|
.map((value) => {
|
||||||
|
const text = String(value ?? '');
|
||||||
|
return text.includes(',') || text.includes('"') ? `"${text.replaceAll('"', '""')}"` : text;
|
||||||
|
})
|
||||||
|
.join(',');
|
||||||
|
const lines = [columns.join(','), ...rows.map((row) => buildCsvLine(columns.map((column) => row[column] ?? '')))];
|
||||||
|
await fs.writeFile(filePath, `${lines.join('\n')}\n`, 'utf8');
|
||||||
|
};
|
||||||
|
|
||||||
|
const commandLabelReportColumns = [
|
||||||
|
'surface',
|
||||||
|
'canonicalId',
|
||||||
|
'rawCommandValue',
|
||||||
|
'displayedCommandLabel',
|
||||||
|
'normalizedDisplayedLabel',
|
||||||
|
'rowCountForCanonicalId',
|
||||||
|
'authoritySourceType',
|
||||||
|
'authoritySourcePath',
|
||||||
|
'status',
|
||||||
|
'failureReason',
|
||||||
|
];
|
||||||
|
const commandLabelReportRows = [
|
||||||
|
{
|
||||||
|
surface: '_bmad/_config/bmad-help.csv',
|
||||||
|
canonicalId: 'bmad-index-docs',
|
||||||
|
rawCommandValue: 'bmad-index-docs',
|
||||||
|
displayedCommandLabel: '/bmad-index-docs',
|
||||||
|
normalizedDisplayedLabel: '/bmad-index-docs',
|
||||||
|
rowCountForCanonicalId: '1',
|
||||||
|
authoritySourceType: 'sidecar',
|
||||||
|
authoritySourcePath: 'bmad-fork/src/core/tasks/index-docs.artifact.yaml',
|
||||||
|
status: 'PASS',
|
||||||
|
failureReason: '',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
await fs.writeFile(
|
||||||
|
path.join(tempSourceTasksDir, 'index-docs.artifact.yaml'),
|
||||||
|
yaml.stringify({
|
||||||
|
schemaVersion: 1,
|
||||||
|
canonicalId: 'bmad-index-docs',
|
||||||
|
artifactType: 'task',
|
||||||
|
module: 'core',
|
||||||
|
sourcePath: 'bmad-fork/src/core/tasks/index-docs.xml',
|
||||||
|
displayName: 'Index Docs',
|
||||||
|
description:
|
||||||
|
'Create lightweight index for quick LLM scanning. Use when LLM needs to understand available docs without loading everything.',
|
||||||
|
dependencies: {
|
||||||
|
requires: [],
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
'utf8',
|
||||||
|
);
|
||||||
|
await fs.writeFile(
|
||||||
|
path.join(tempSourceTasksDir, 'index-docs.xml'),
|
||||||
|
'<task id="index-docs"><description>Create lightweight index for quick LLM scanning</description></task>\n',
|
||||||
|
'utf8',
|
||||||
|
);
|
||||||
|
|
||||||
|
await writeCsv(
|
||||||
|
path.join(tempConfigDir, 'task-manifest.csv'),
|
||||||
|
[...TASK_MANIFEST_COMPATIBILITY_PREFIX_COLUMNS, ...TASK_MANIFEST_CANONICAL_ADDITIVE_COLUMNS],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
name: 'index-docs',
|
||||||
|
displayName: 'Index Docs',
|
||||||
|
description:
|
||||||
|
'Create lightweight index for quick LLM scanning. Use when LLM needs to understand available docs without loading everything.',
|
||||||
|
module: 'core',
|
||||||
|
path: '_bmad/core/tasks/index-docs.xml',
|
||||||
|
standalone: 'true',
|
||||||
|
legacyName: 'index-docs',
|
||||||
|
canonicalId: 'bmad-index-docs',
|
||||||
|
authoritySourceType: 'sidecar',
|
||||||
|
authoritySourcePath: 'bmad-fork/src/core/tasks/index-docs.artifact.yaml',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
);
|
||||||
|
await writeCsv(
|
||||||
|
path.join(tempConfigDir, 'bmad-help.csv'),
|
||||||
|
[...HELP_CATALOG_COMPATIBILITY_PREFIX_COLUMNS, ...HELP_CATALOG_CANONICAL_ADDITIVE_COLUMNS],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
module: 'core',
|
||||||
|
phase: 'anytime',
|
||||||
|
name: 'Help',
|
||||||
|
code: 'BH',
|
||||||
|
sequence: '',
|
||||||
|
'workflow-file': '_bmad/core/tasks/help.md',
|
||||||
|
command: 'bmad-help',
|
||||||
|
required: 'false',
|
||||||
|
'agent-name': '',
|
||||||
|
'agent-command': '',
|
||||||
|
'agent-display-name': '',
|
||||||
|
'agent-title': '',
|
||||||
|
options: '',
|
||||||
|
description: 'Show BMAD help and available resources.',
|
||||||
|
'output-location': '',
|
||||||
|
outputs: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
module: 'core',
|
||||||
|
phase: 'anytime',
|
||||||
|
name: 'Shard Document',
|
||||||
|
code: 'SD',
|
||||||
|
sequence: '',
|
||||||
|
'workflow-file': '_bmad/core/tasks/shard-doc.xml',
|
||||||
|
command: 'bmad-shard-doc',
|
||||||
|
required: 'false',
|
||||||
|
'agent-name': '',
|
||||||
|
'agent-command': '',
|
||||||
|
'agent-display-name': '',
|
||||||
|
'agent-title': '',
|
||||||
|
options: '',
|
||||||
|
description: 'Split large markdown documents into smaller files by section with an index.',
|
||||||
|
'output-location': '',
|
||||||
|
outputs: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
module: 'core',
|
||||||
|
phase: 'anytime',
|
||||||
|
name: 'Index Docs',
|
||||||
|
code: 'ID',
|
||||||
|
sequence: '',
|
||||||
|
'workflow-file': '_bmad/core/tasks/index-docs.xml',
|
||||||
|
command: 'bmad-index-docs',
|
||||||
|
required: 'false',
|
||||||
|
'agent-name': '',
|
||||||
|
'agent-command': '',
|
||||||
|
'agent-display-name': '',
|
||||||
|
'agent-title': '',
|
||||||
|
options: '',
|
||||||
|
description:
|
||||||
|
'Create lightweight index for quick LLM scanning. Use when LLM needs to understand available docs without loading everything.',
|
||||||
|
'output-location': '',
|
||||||
|
outputs: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
);
|
||||||
|
await writeCsv(
|
||||||
|
path.join(tempConfigDir, 'canonical-aliases.csv'),
|
||||||
|
[
|
||||||
|
'canonicalId',
|
||||||
|
'alias',
|
||||||
|
'aliasType',
|
||||||
|
'authoritySourceType',
|
||||||
|
'authoritySourcePath',
|
||||||
|
'rowIdentity',
|
||||||
|
'normalizedAliasValue',
|
||||||
|
'rawIdentityHasLeadingSlash',
|
||||||
|
'resolutionEligibility',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
canonicalId: 'bmad-index-docs',
|
||||||
|
alias: 'bmad-index-docs',
|
||||||
|
aliasType: 'canonical-id',
|
||||||
|
authoritySourceType: 'sidecar',
|
||||||
|
authoritySourcePath: 'bmad-fork/src/core/tasks/index-docs.artifact.yaml',
|
||||||
|
rowIdentity: 'alias-row:bmad-index-docs:canonical-id',
|
||||||
|
normalizedAliasValue: 'bmad-index-docs',
|
||||||
|
rawIdentityHasLeadingSlash: 'false',
|
||||||
|
resolutionEligibility: 'canonical-id-only',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
canonicalId: 'bmad-index-docs',
|
||||||
|
alias: 'index-docs',
|
||||||
|
aliasType: 'legacy-name',
|
||||||
|
authoritySourceType: 'sidecar',
|
||||||
|
authoritySourcePath: 'bmad-fork/src/core/tasks/index-docs.artifact.yaml',
|
||||||
|
rowIdentity: 'alias-row:bmad-index-docs:legacy-name',
|
||||||
|
normalizedAliasValue: 'index-docs',
|
||||||
|
rawIdentityHasLeadingSlash: 'false',
|
||||||
|
resolutionEligibility: 'legacy-name-only',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
canonicalId: 'bmad-index-docs',
|
||||||
|
alias: '/bmad-index-docs',
|
||||||
|
aliasType: 'slash-command',
|
||||||
|
authoritySourceType: 'sidecar',
|
||||||
|
authoritySourcePath: 'bmad-fork/src/core/tasks/index-docs.artifact.yaml',
|
||||||
|
rowIdentity: 'alias-row:bmad-index-docs:slash-command',
|
||||||
|
normalizedAliasValue: 'bmad-index-docs',
|
||||||
|
rawIdentityHasLeadingSlash: 'true',
|
||||||
|
resolutionEligibility: 'slash-command-only',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
);
|
||||||
|
await writeCsv(commandLabelReportPath, commandLabelReportColumns, commandLabelReportRows);
|
||||||
|
|
||||||
|
const authorityRecords = [
|
||||||
|
{
|
||||||
|
recordType: 'metadata-authority',
|
||||||
|
canonicalId: 'bmad-index-docs',
|
||||||
|
authoritativePresenceKey: 'capability:bmad-index-docs',
|
||||||
|
authoritySourceType: 'sidecar',
|
||||||
|
authoritySourcePath: 'bmad-fork/src/core/tasks/index-docs.artifact.yaml',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
recordType: 'source-body-authority',
|
||||||
|
canonicalId: 'bmad-index-docs',
|
||||||
|
authoritativePresenceKey: 'capability:bmad-index-docs',
|
||||||
|
authoritySourceType: 'source-xml',
|
||||||
|
authoritySourcePath: 'bmad-fork/src/core/tasks/index-docs.xml',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const harness = new IndexDocsValidationHarness();
|
||||||
|
const firstRun = await harness.generateAndValidate({
|
||||||
|
projectDir: tempProjectRoot,
|
||||||
|
bmadDir: tempBmadDir,
|
||||||
|
bmadFolderName: '_bmad',
|
||||||
|
indexDocsAuthorityRecords: authorityRecords,
|
||||||
|
});
|
||||||
|
assert(
|
||||||
|
firstRun.terminalStatus === 'PASS' && firstRun.generatedArtifactCount === INDEX_DOCS_VALIDATION_ARTIFACT_REGISTRY.length,
|
||||||
|
'Index-docs validation harness generates and validates all required artifacts',
|
||||||
|
);
|
||||||
|
|
||||||
|
const artifactPathsById = new Map(
|
||||||
|
INDEX_DOCS_VALIDATION_ARTIFACT_REGISTRY.map((artifact) => [
|
||||||
|
artifact.artifactId,
|
||||||
|
path.join(tempProjectRoot, '_bmad-output', 'planning-artifacts', artifact.relativePath),
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
for (const [artifactId, artifactPath] of artifactPathsById.entries()) {
|
||||||
|
assert(await fs.pathExists(artifactPath), `Index-docs validation harness outputs artifact ${artifactId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const firstArtifactContents = new Map();
|
||||||
|
for (const [artifactId, artifactPath] of artifactPathsById.entries()) {
|
||||||
|
firstArtifactContents.set(artifactId, await fs.readFile(artifactPath, 'utf8'));
|
||||||
|
}
|
||||||
|
|
||||||
|
await harness.generateAndValidate({
|
||||||
|
projectDir: tempProjectRoot,
|
||||||
|
bmadDir: tempBmadDir,
|
||||||
|
bmadFolderName: '_bmad',
|
||||||
|
indexDocsAuthorityRecords: authorityRecords,
|
||||||
|
});
|
||||||
|
let deterministicOutputs = true;
|
||||||
|
for (const [artifactId, artifactPath] of artifactPathsById.entries()) {
|
||||||
|
const rerunContent = await fs.readFile(artifactPath, 'utf8');
|
||||||
|
if (rerunContent !== firstArtifactContents.get(artifactId)) {
|
||||||
|
deterministicOutputs = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert(deterministicOutputs, 'Index-docs validation harness outputs are byte-stable across unchanged repeated runs');
|
||||||
|
|
||||||
|
try {
|
||||||
|
await harness.executeIsolatedReplay({
|
||||||
|
artifactPath: '_bmad/_config/task-manifest.csv',
|
||||||
|
componentPath: 'bmad-fork/tools/cli/installers/lib/core/manifest-generator.js',
|
||||||
|
rowIdentity: '',
|
||||||
|
runtimeFolder: '_bmad',
|
||||||
|
});
|
||||||
|
assert(false, 'Index-docs replay evidence generation rejects missing claimed rowIdentity');
|
||||||
|
} catch (error) {
|
||||||
|
assert(
|
||||||
|
error.code === INDEX_DOCS_VALIDATION_ERROR_CODES.REQUIRED_ROW_MISSING,
|
||||||
|
'Index-docs replay evidence generation emits deterministic missing-claimed-rowIdentity error code',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await harness.executeIsolatedReplay({
|
||||||
|
artifactPath: '_bmad/_config/task-manifest.csv',
|
||||||
|
componentPath: 'bmad-fork/tools/cli/installers/lib/core/installer.js::mergeModuleHelpCatalogs()',
|
||||||
|
rowIdentity: 'issued-artifact:_bmad-_config-task-manifest.csv',
|
||||||
|
runtimeFolder: '_bmad',
|
||||||
|
});
|
||||||
|
assert(false, 'Index-docs replay evidence generation rejects issuing-component contract mismatch');
|
||||||
|
} catch (error) {
|
||||||
|
assert(
|
||||||
|
error.code === INDEX_DOCS_VALIDATION_ERROR_CODES.BINDING_EVIDENCE_INVALID,
|
||||||
|
'Index-docs replay evidence generation emits deterministic issuing-component contract mismatch code',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const artifactElevenPath = artifactPathsById.get(11);
|
||||||
|
const artifactElevenRows = csv.parse(await fs.readFile(artifactElevenPath, 'utf8'), {
|
||||||
|
columns: true,
|
||||||
|
skip_empty_lines: true,
|
||||||
|
});
|
||||||
|
artifactElevenRows[0].baselineArtifactSha256 = 'not-a-sha';
|
||||||
|
await writeCsv(artifactElevenPath, INDEX_DOCS_VALIDATION_ARTIFACT_REGISTRY[10].columns, artifactElevenRows);
|
||||||
|
try {
|
||||||
|
await harness.validateGeneratedArtifacts({ projectDir: tempProjectRoot });
|
||||||
|
assert(false, 'Index-docs validation harness rejects malformed replay-evidence payloads');
|
||||||
|
} catch (error) {
|
||||||
|
assert(
|
||||||
|
error.code === INDEX_DOCS_VALIDATION_ERROR_CODES.REPLAY_EVIDENCE_INVALID,
|
||||||
|
'Index-docs validation harness emits deterministic replay-evidence validation error code',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await harness.generateAndValidate({
|
||||||
|
projectDir: tempProjectRoot,
|
||||||
|
bmadDir: tempBmadDir,
|
||||||
|
bmadFolderName: '_bmad',
|
||||||
|
indexDocsAuthorityRecords: authorityRecords,
|
||||||
|
});
|
||||||
|
|
||||||
|
await fs.remove(artifactPathsById.get(8));
|
||||||
|
try {
|
||||||
|
await harness.validateGeneratedArtifacts({ projectDir: tempProjectRoot });
|
||||||
|
assert(false, 'Index-docs validation harness fails when a required artifact is missing');
|
||||||
|
} catch (error) {
|
||||||
|
assert(
|
||||||
|
error.code === INDEX_DOCS_VALIDATION_ERROR_CODES.REQUIRED_ARTIFACT_MISSING,
|
||||||
|
'Index-docs validation harness emits deterministic missing-artifact error code',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await harness.generateAndValidate({
|
||||||
|
projectDir: tempProjectRoot,
|
||||||
|
bmadDir: tempBmadDir,
|
||||||
|
bmadFolderName: '_bmad',
|
||||||
|
indexDocsAuthorityRecords: authorityRecords,
|
||||||
|
});
|
||||||
|
|
||||||
|
await fs.remove(commandLabelReportPath);
|
||||||
|
try {
|
||||||
|
await harness.generateValidationArtifacts({
|
||||||
|
projectDir: tempProjectRoot,
|
||||||
|
bmadDir: tempBmadDir,
|
||||||
|
bmadFolderName: '_bmad',
|
||||||
|
indexDocsAuthorityRecords: authorityRecords,
|
||||||
|
});
|
||||||
|
assert(false, 'Index-docs validation harness rejects missing command-label report input surface');
|
||||||
|
} catch (error) {
|
||||||
|
assert(
|
||||||
|
error.code === INDEX_DOCS_VALIDATION_ERROR_CODES.REQUIRED_ARTIFACT_MISSING,
|
||||||
|
'Index-docs validation harness emits deterministic missing-input-surface error code',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
await writeCsv(commandLabelReportPath, commandLabelReportColumns, commandLabelReportRows);
|
||||||
|
|
||||||
|
const artifactSixPath = artifactPathsById.get(6);
|
||||||
|
const artifactSixLines = (await fs.readFile(artifactSixPath, 'utf8')).split('\n');
|
||||||
|
artifactSixLines[0] = artifactSixLines[0].replace('canonicalId', 'brokenCanonicalId');
|
||||||
|
await fs.writeFile(artifactSixPath, artifactSixLines.join('\n'), 'utf8');
|
||||||
|
try {
|
||||||
|
await harness.validateGeneratedArtifacts({ projectDir: tempProjectRoot });
|
||||||
|
assert(false, 'Index-docs validation harness rejects schema/header drift');
|
||||||
|
} catch (error) {
|
||||||
|
assert(
|
||||||
|
error.code === INDEX_DOCS_VALIDATION_ERROR_CODES.CSV_SCHEMA_MISMATCH,
|
||||||
|
'Index-docs validation harness emits deterministic schema-mismatch error code',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await harness.generateAndValidate({
|
||||||
|
projectDir: tempProjectRoot,
|
||||||
|
bmadDir: tempBmadDir,
|
||||||
|
bmadFolderName: '_bmad',
|
||||||
|
indexDocsAuthorityRecords: authorityRecords,
|
||||||
|
});
|
||||||
|
|
||||||
|
const artifactEightPath = artifactPathsById.get(8);
|
||||||
|
const artifactEightRows = csv.parse(await fs.readFile(artifactEightPath, 'utf8'), {
|
||||||
|
columns: true,
|
||||||
|
skip_empty_lines: true,
|
||||||
|
});
|
||||||
|
const artifactSixInventoryRow = artifactEightRows.find((row) => row.artifactId === '6');
|
||||||
|
if (artifactSixInventoryRow) {
|
||||||
|
artifactSixInventoryRow.artifactPath = 'validation/index-docs/drifted-command-label-report.csv';
|
||||||
|
}
|
||||||
|
await writeCsv(
|
||||||
|
artifactEightPath,
|
||||||
|
['rowIdentity', 'artifactId', 'artifactPath', 'artifactType', 'required', 'rowCount', 'exists', 'schemaVersion', 'status'],
|
||||||
|
artifactEightRows,
|
||||||
|
);
|
||||||
|
try {
|
||||||
|
await harness.validateGeneratedArtifacts({ projectDir: tempProjectRoot });
|
||||||
|
assert(false, 'Index-docs validation harness rejects inventory deterministic-identifier drift');
|
||||||
|
} catch (error) {
|
||||||
|
assert(
|
||||||
|
error.code === INDEX_DOCS_VALIDATION_ERROR_CODES.REQUIRED_ROW_MISSING,
|
||||||
|
'Index-docs validation harness emits deterministic inventory-row validation error code',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await harness.generateAndValidate({
|
||||||
|
projectDir: tempProjectRoot,
|
||||||
|
bmadDir: tempBmadDir,
|
||||||
|
bmadFolderName: '_bmad',
|
||||||
|
indexDocsAuthorityRecords: authorityRecords,
|
||||||
|
});
|
||||||
|
|
||||||
|
const artifactTwoPath = artifactPathsById.get(2);
|
||||||
|
const artifactTwoRows = csv.parse(await fs.readFile(artifactTwoPath, 'utf8'), {
|
||||||
|
columns: true,
|
||||||
|
skip_empty_lines: true,
|
||||||
|
});
|
||||||
|
const filteredAuthorityRows = artifactTwoRows.filter((row) => row.recordType !== 'source-body-authority');
|
||||||
|
await writeCsv(
|
||||||
|
artifactTwoPath,
|
||||||
|
['rowIdentity', 'recordType', 'canonicalId', 'authoritativePresenceKey', 'authoritySourceType', 'authoritySourcePath', 'status'],
|
||||||
|
filteredAuthorityRows,
|
||||||
|
);
|
||||||
|
try {
|
||||||
|
await harness.validateGeneratedArtifacts({ projectDir: tempProjectRoot });
|
||||||
|
assert(false, 'Index-docs validation harness rejects missing source-body authority records');
|
||||||
|
} catch (error) {
|
||||||
|
assert(
|
||||||
|
error.code === INDEX_DOCS_VALIDATION_ERROR_CODES.REQUIRED_ROW_MISSING,
|
||||||
|
'Index-docs validation harness emits deterministic missing-row error code',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
assert(false, 'Index-docs validation artifact suite setup', error.message);
|
||||||
|
} finally {
|
||||||
|
await fs.remove(tempIndexDocsValidationHarnessRoot);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('');
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Summary
|
// Summary
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -27,6 +27,7 @@ const {
|
||||||
const { validateHelpCatalogCompatibilitySurface } = require('./projection-compatibility-validator');
|
const { validateHelpCatalogCompatibilitySurface } = require('./projection-compatibility-validator');
|
||||||
const { HelpValidationHarness } = require('./help-validation-harness');
|
const { HelpValidationHarness } = require('./help-validation-harness');
|
||||||
const { ShardDocValidationHarness } = require('./shard-doc-validation-harness');
|
const { ShardDocValidationHarness } = require('./shard-doc-validation-harness');
|
||||||
|
const { IndexDocsValidationHarness } = require('./index-docs-validation-harness');
|
||||||
const { getProjectRoot, getSourcePath, getModulePath } = require('../../../lib/project-root');
|
const { getProjectRoot, getSourcePath, getModulePath } = require('../../../lib/project-root');
|
||||||
const { CLIUtils } = require('../../../lib/cli-utils');
|
const { CLIUtils } = require('../../../lib/cli-utils');
|
||||||
const { ManifestGenerator } = require('./manifest-generator');
|
const { ManifestGenerator } = require('./manifest-generator');
|
||||||
|
|
@ -75,8 +76,10 @@ class Installer {
|
||||||
this.indexDocsAuthorityRecords = [];
|
this.indexDocsAuthorityRecords = [];
|
||||||
this.latestHelpValidationRun = null;
|
this.latestHelpValidationRun = null;
|
||||||
this.latestShardDocValidationRun = null;
|
this.latestShardDocValidationRun = null;
|
||||||
|
this.latestIndexDocsValidationRun = null;
|
||||||
this.helpValidationHarness = new HelpValidationHarness();
|
this.helpValidationHarness = new HelpValidationHarness();
|
||||||
this.shardDocValidationHarness = new ShardDocValidationHarness();
|
this.shardDocValidationHarness = new ShardDocValidationHarness();
|
||||||
|
this.indexDocsValidationHarness = new IndexDocsValidationHarness();
|
||||||
}
|
}
|
||||||
|
|
||||||
async runConfigurationGenerationTask({ message, bmadDir, moduleConfigs, config, allModules, addResult }) {
|
async runConfigurationGenerationTask({ message, bmadDir, moduleConfigs, config, allModules, addResult }) {
|
||||||
|
|
@ -211,6 +214,16 @@ class Installer {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async buildIndexDocsValidationOptions({ projectDir, bmadDir }) {
|
||||||
|
return {
|
||||||
|
projectDir,
|
||||||
|
bmadDir,
|
||||||
|
bmadFolderName: this.bmadFolderName || BMAD_FOLDER_NAME,
|
||||||
|
indexDocsAuthorityRecords: this.indexDocsAuthorityRecords || [],
|
||||||
|
helpCatalogCommandLabelReportRows: this.helpCatalogCommandLabelReportRows || [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find the bmad installation directory in a project
|
* Find the bmad installation directory in a project
|
||||||
* Always uses the standard _bmad folder name
|
* Always uses the standard _bmad folder name
|
||||||
|
|
@ -1406,7 +1419,20 @@ class Installer {
|
||||||
this.latestShardDocValidationRun = shardDocValidationRun;
|
this.latestShardDocValidationRun = shardDocValidationRun;
|
||||||
addResult('Shard-doc validation artifacts', 'ok', `${shardDocValidationRun.generatedArtifactCount} artifacts`);
|
addResult('Shard-doc validation artifacts', 'ok', `${shardDocValidationRun.generatedArtifactCount} artifacts`);
|
||||||
|
|
||||||
return `${validationRun.generatedArtifactCount + shardDocValidationRun.generatedArtifactCount} validation artifacts generated`;
|
message('Generating deterministic index-docs validation artifact suite...');
|
||||||
|
const indexDocsValidationOptions = await this.buildIndexDocsValidationOptions({
|
||||||
|
projectDir,
|
||||||
|
bmadDir,
|
||||||
|
});
|
||||||
|
const indexDocsValidationRun = await this.indexDocsValidationHarness.generateAndValidate(indexDocsValidationOptions);
|
||||||
|
this.latestIndexDocsValidationRun = indexDocsValidationRun;
|
||||||
|
addResult('Index-docs validation artifacts', 'ok', `${indexDocsValidationRun.generatedArtifactCount} artifacts`);
|
||||||
|
|
||||||
|
return `${
|
||||||
|
validationRun.generatedArtifactCount +
|
||||||
|
shardDocValidationRun.generatedArtifactCount +
|
||||||
|
indexDocsValidationRun.generatedArtifactCount
|
||||||
|
} validation artifacts generated`;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue