refactor(installer): replace stage labels with capability-based shard-doc/help naming
This commit is contained in:
parent
96528d9bd3
commit
a770fa5808
|
|
@ -61,9 +61,9 @@ const {
|
|||
const {
|
||||
PROJECTION_COMPATIBILITY_ERROR_CODES,
|
||||
TASK_MANIFEST_COMPATIBILITY_PREFIX_COLUMNS,
|
||||
TASK_MANIFEST_WAVE1_ADDITIVE_COLUMNS,
|
||||
TASK_MANIFEST_CANONICAL_ADDITIVE_COLUMNS,
|
||||
HELP_CATALOG_COMPATIBILITY_PREFIX_COLUMNS,
|
||||
HELP_CATALOG_WAVE1_ADDITIVE_COLUMNS,
|
||||
HELP_CATALOG_CANONICAL_ADDITIVE_COLUMNS,
|
||||
validateTaskManifestCompatibilitySurface,
|
||||
validateTaskManifestLoaderEntries,
|
||||
validateHelpCatalogCompatibilitySurface,
|
||||
|
|
@ -72,15 +72,15 @@ const {
|
|||
validateCommandDocSurfaceConsistency,
|
||||
} = require('../tools/cli/installers/lib/core/projection-compatibility-validator');
|
||||
const {
|
||||
WAVE1_VALIDATION_ERROR_CODES,
|
||||
WAVE1_VALIDATION_ARTIFACT_REGISTRY,
|
||||
Wave1ValidationHarness,
|
||||
} = require('../tools/cli/installers/lib/core/wave-1-validation-harness');
|
||||
HELP_VALIDATION_ERROR_CODES,
|
||||
HELP_VALIDATION_ARTIFACT_REGISTRY,
|
||||
HelpValidationHarness,
|
||||
} = require('../tools/cli/installers/lib/core/help-validation-harness');
|
||||
const {
|
||||
WAVE2_VALIDATION_ERROR_CODES,
|
||||
WAVE2_VALIDATION_ARTIFACT_REGISTRY,
|
||||
Wave2ValidationHarness,
|
||||
} = require('../tools/cli/installers/lib/core/wave-2-validation-harness');
|
||||
SHARD_DOC_VALIDATION_ERROR_CODES,
|
||||
SHARD_DOC_VALIDATION_ARTIFACT_REGISTRY,
|
||||
ShardDocValidationHarness,
|
||||
} = require('../tools/cli/installers/lib/core/shard-doc-validation-harness');
|
||||
|
||||
// ANSI colors
|
||||
const colors = {
|
||||
|
|
@ -399,9 +399,9 @@ async function runTests() {
|
|||
console.log('');
|
||||
|
||||
// ============================================================
|
||||
// Test 4b: Wave-2 shard-doc Sidecar Contract Validation
|
||||
// Test 4b: Shard-doc Sidecar Contract Validation
|
||||
// ============================================================
|
||||
console.log(`${colors.yellow}Test Suite 4b: Wave-2 shard-doc Sidecar Contract Validation${colors.reset}\n`);
|
||||
console.log(`${colors.yellow}Test Suite 4b: Shard-doc Sidecar Contract Validation${colors.reset}\n`);
|
||||
|
||||
const validShardDocSidecar = {
|
||||
schemaVersion: 1,
|
||||
|
|
@ -416,7 +416,7 @@ async function runTests() {
|
|||
},
|
||||
};
|
||||
|
||||
const shardDocFixtureRoot = path.join(projectRoot, 'test', 'fixtures', 'wave-2', 'sidecar-negative');
|
||||
const shardDocFixtureRoot = path.join(projectRoot, 'test', 'fixtures', 'shard-doc', 'sidecar-negative');
|
||||
const unknownMajorFixturePath = path.join(shardDocFixtureRoot, 'unknown-major-version', 'shard-doc.artifact.yaml');
|
||||
const basenameMismatchFixturePath = path.join(shardDocFixtureRoot, 'basename-path-mismatch', 'shard-doc.artifact.yaml');
|
||||
|
||||
|
|
@ -587,7 +587,7 @@ async function runTests() {
|
|||
'Shard-doc non-empty dependencies.requires',
|
||||
);
|
||||
} catch (error) {
|
||||
assert(false, 'Wave-2 shard-doc sidecar validation suite setup', error.message);
|
||||
assert(false, 'Shard-doc sidecar validation suite setup', error.message);
|
||||
} finally {
|
||||
await fs.remove(tempShardDocRoot);
|
||||
}
|
||||
|
|
@ -1109,7 +1109,7 @@ async function runTests() {
|
|||
}
|
||||
}
|
||||
|
||||
// 6b: Shard-doc fail-fast covers Wave-2 negative matrix classes.
|
||||
// 6b: Shard-doc fail-fast covers Shard-doc negative matrix classes.
|
||||
{
|
||||
const deterministicShardDocFailFastSourcePath = 'bmad-fork/src/core/tasks/shard-doc.artifact.yaml';
|
||||
const shardDocFailureScenarios = [
|
||||
|
|
@ -1967,7 +1967,7 @@ async function runTests() {
|
|||
|
||||
assert(
|
||||
writtenTaskManifestLines[0] === expectedHeader,
|
||||
'Task manifest writes compatibility-prefix columns with locked wave-1 appended column order',
|
||||
'Task manifest writes compatibility-prefix columns with locked canonical appended column order',
|
||||
);
|
||||
|
||||
const writtenTaskManifestRecords = csv.parse(writtenTaskManifestRaw, {
|
||||
|
|
@ -3147,7 +3147,7 @@ async function runTests() {
|
|||
|
||||
const taskManifestColumns = [
|
||||
...TASK_MANIFEST_COMPATIBILITY_PREFIX_COLUMNS,
|
||||
...TASK_MANIFEST_WAVE1_ADDITIVE_COLUMNS,
|
||||
...TASK_MANIFEST_CANONICAL_ADDITIVE_COLUMNS,
|
||||
'futureAdditiveField',
|
||||
];
|
||||
const validTaskRows = [
|
||||
|
|
@ -3162,7 +3162,7 @@ async function runTests() {
|
|||
canonicalId: 'bmad-help',
|
||||
authoritySourceType: 'sidecar',
|
||||
authoritySourcePath: 'bmad-fork/src/core/tasks/help.artifact.yaml',
|
||||
futureAdditiveField: 'wave-1',
|
||||
futureAdditiveField: 'canonical-additive',
|
||||
},
|
||||
{
|
||||
name: 'create-story',
|
||||
|
|
@ -3175,7 +3175,7 @@ async function runTests() {
|
|||
canonicalId: '',
|
||||
authoritySourceType: '',
|
||||
authoritySourcePath: '',
|
||||
futureAdditiveField: 'wave-1',
|
||||
futureAdditiveField: 'canonical-additive',
|
||||
},
|
||||
];
|
||||
const validTaskManifestCsv =
|
||||
|
|
@ -3188,11 +3188,11 @@ async function runTests() {
|
|||
assert(
|
||||
validatedTaskSurface.headerColumns[0] === 'name' &&
|
||||
validatedTaskSurface.headerColumns[TASK_MANIFEST_COMPATIBILITY_PREFIX_COLUMNS.length] === 'legacyName',
|
||||
'Task-manifest compatibility validator enforces locked prefix plus additive wave-1 ordering',
|
||||
'Task-manifest compatibility validator enforces locked prefix plus additive canonical ordering',
|
||||
);
|
||||
assert(
|
||||
validatedTaskSurface.headerColumns.at(-1) === 'futureAdditiveField',
|
||||
'Task-manifest compatibility validator allows additive columns appended after locked wave-1 columns',
|
||||
'Task-manifest compatibility validator allows additive columns appended after locked canonical columns',
|
||||
);
|
||||
|
||||
validateTaskManifestLoaderEntries(validatedTaskSurface.rows, {
|
||||
|
|
@ -3229,8 +3229,8 @@ async function runTests() {
|
|||
assert(false, 'Task-manifest strict validator rejects legacy prefix-only header without migration mode');
|
||||
} catch (error) {
|
||||
assert(
|
||||
error.code === PROJECTION_COMPATIBILITY_ERROR_CODES.TASK_MANIFEST_HEADER_WAVE1_MISMATCH,
|
||||
'Task-manifest strict validator emits deterministic wave-1 mismatch error for legacy prefix-only headers',
|
||||
error.code === PROJECTION_COMPATIBILITY_ERROR_CODES.TASK_MANIFEST_HEADER_CANONICAL_MISMATCH,
|
||||
'Task-manifest strict validator emits deterministic canonical mismatch error for legacy prefix-only headers',
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -3252,7 +3252,7 @@ async function runTests() {
|
|||
|
||||
const helpCatalogColumns = [
|
||||
...HELP_CATALOG_COMPATIBILITY_PREFIX_COLUMNS,
|
||||
...HELP_CATALOG_WAVE1_ADDITIVE_COLUMNS,
|
||||
...HELP_CATALOG_CANONICAL_ADDITIVE_COLUMNS,
|
||||
'futureAdditiveField',
|
||||
];
|
||||
const validHelpRows = [
|
||||
|
|
@ -3273,7 +3273,7 @@ async function runTests() {
|
|||
description: 'Help command',
|
||||
'output-location': '',
|
||||
outputs: '',
|
||||
futureAdditiveField: 'wave-1',
|
||||
futureAdditiveField: 'canonical-additive',
|
||||
},
|
||||
{
|
||||
module: 'core',
|
||||
|
|
@ -3292,7 +3292,7 @@ async function runTests() {
|
|||
description: 'Shard document command',
|
||||
'output-location': '',
|
||||
outputs: '',
|
||||
futureAdditiveField: 'wave-1',
|
||||
futureAdditiveField: 'canonical-additive',
|
||||
},
|
||||
{
|
||||
module: 'bmm',
|
||||
|
|
@ -3311,7 +3311,7 @@ async function runTests() {
|
|||
description: 'Create next story',
|
||||
'output-location': '',
|
||||
outputs: '',
|
||||
futureAdditiveField: 'wave-1',
|
||||
futureAdditiveField: 'canonical-additive',
|
||||
},
|
||||
];
|
||||
const validHelpCatalogCsv =
|
||||
|
|
@ -3327,7 +3327,7 @@ async function runTests() {
|
|||
);
|
||||
assert(
|
||||
validatedHelpSurface.headerColumns.at(-1) === 'futureAdditiveField',
|
||||
'Help-catalog compatibility validator allows additive columns appended after locked wave-1 columns',
|
||||
'Help-catalog compatibility validator allows additive columns appended after locked canonical columns',
|
||||
);
|
||||
|
||||
validateHelpCatalogLoaderEntries(validatedHelpSurface.rows, {
|
||||
|
|
@ -3438,7 +3438,7 @@ async function runTests() {
|
|||
// ============================================================
|
||||
console.log(`${colors.yellow}Test Suite 14: Deterministic Validation Artifact Suite${colors.reset}\n`);
|
||||
|
||||
const tempValidationHarnessRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-wave1-validation-suite-'));
|
||||
const tempValidationHarnessRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-help-validation-suite-'));
|
||||
try {
|
||||
const tempProjectRoot = tempValidationHarnessRoot;
|
||||
const tempBmadDir = path.join(tempProjectRoot, '_bmad');
|
||||
|
|
@ -3509,7 +3509,7 @@ async function runTests() {
|
|||
|
||||
await writeCsv(
|
||||
path.join(tempConfigDir, 'task-manifest.csv'),
|
||||
[...TASK_MANIFEST_COMPATIBILITY_PREFIX_COLUMNS, ...TASK_MANIFEST_WAVE1_ADDITIVE_COLUMNS],
|
||||
[...TASK_MANIFEST_COMPATIBILITY_PREFIX_COLUMNS, ...TASK_MANIFEST_CANONICAL_ADDITIVE_COLUMNS],
|
||||
[
|
||||
{
|
||||
name: 'help',
|
||||
|
|
@ -3576,7 +3576,7 @@ async function runTests() {
|
|||
);
|
||||
await writeCsv(
|
||||
path.join(tempConfigDir, 'bmad-help.csv'),
|
||||
[...HELP_CATALOG_COMPATIBILITY_PREFIX_COLUMNS, ...HELP_CATALOG_WAVE1_ADDITIVE_COLUMNS],
|
||||
[...HELP_CATALOG_COMPATIBILITY_PREFIX_COLUMNS, ...HELP_CATALOG_CANONICAL_ADDITIVE_COLUMNS],
|
||||
[
|
||||
{
|
||||
module: 'core',
|
||||
|
|
@ -3764,7 +3764,7 @@ async function runTests() {
|
|||
],
|
||||
);
|
||||
|
||||
const harness = new Wave1ValidationHarness();
|
||||
const harness = new HelpValidationHarness();
|
||||
const firstRun = await harness.generateAndValidate({
|
||||
projectDir: tempProjectRoot,
|
||||
bmadDir: tempBmadDir,
|
||||
|
|
@ -3773,18 +3773,18 @@ async function runTests() {
|
|||
sourceMarkdownPath: path.join(tempSourceTasksDir, 'help.md'),
|
||||
});
|
||||
assert(
|
||||
firstRun.terminalStatus === 'PASS' && firstRun.generatedArtifactCount === WAVE1_VALIDATION_ARTIFACT_REGISTRY.length,
|
||||
'Wave-1 validation harness generates and validates all required artifacts',
|
||||
firstRun.terminalStatus === 'PASS' && firstRun.generatedArtifactCount === HELP_VALIDATION_ARTIFACT_REGISTRY.length,
|
||||
'Help validation harness generates and validates all required artifacts',
|
||||
);
|
||||
|
||||
const artifactPathsById = new Map(
|
||||
WAVE1_VALIDATION_ARTIFACT_REGISTRY.map((artifact) => [
|
||||
HELP_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), `Wave-1 validation harness outputs artifact ${artifactId}`);
|
||||
assert(await fs.pathExists(artifactPath), `Help validation harness outputs artifact ${artifactId}`);
|
||||
}
|
||||
|
||||
const artifactThreeBaselineRows = csv.parse(await fs.readFile(artifactPathsById.get(3), 'utf8'), {
|
||||
|
|
@ -3810,7 +3810,7 @@ async function runTests() {
|
|||
manifestReplayEvidence.perturbationApplied === true &&
|
||||
Number(manifestReplayEvidence.baselineTargetRowCount) > Number(manifestReplayEvidence.mutatedTargetRowCount) &&
|
||||
manifestReplayEvidence.targetedRowLocator === manifestProvenanceRow.rowIdentity,
|
||||
'Wave-1 validation harness emits validator-observed replay evidence with baseline/perturbation impact',
|
||||
'Help validation harness emits validator-observed replay evidence with baseline/perturbation impact',
|
||||
);
|
||||
|
||||
const firstArtifactContents = new Map();
|
||||
|
|
@ -3834,18 +3834,18 @@ async function runTests() {
|
|||
break;
|
||||
}
|
||||
}
|
||||
assert(deterministicOutputs, 'Wave-1 validation harness outputs are byte-stable across unchanged repeated runs');
|
||||
assert(deterministicOutputs, 'Help validation harness outputs are byte-stable across unchanged repeated runs');
|
||||
|
||||
await fs.remove(path.join(tempSkillDir, 'SKILL.md'));
|
||||
const noIdeInstaller = new Installer();
|
||||
noIdeInstaller.codexExportDerivationRecords = [];
|
||||
const noIdeValidationOptions = await noIdeInstaller.buildWave1ValidationOptions({
|
||||
const noIdeValidationOptions = await noIdeInstaller.buildHelpValidationOptions({
|
||||
projectDir: tempProjectRoot,
|
||||
bmadDir: tempBmadDir,
|
||||
});
|
||||
assert(
|
||||
noIdeValidationOptions.requireExportSkillProjection === false,
|
||||
'Installer wave-1 validation options disable export-surface requirement for no-IDE/non-Codex flow',
|
||||
'Installer help validation options disable export-surface requirement for no-IDE/non-Codex flow',
|
||||
);
|
||||
const noIdeRun = await harness.generateAndValidate({
|
||||
...noIdeValidationOptions,
|
||||
|
|
@ -3854,7 +3854,7 @@ async function runTests() {
|
|||
});
|
||||
assert(
|
||||
noIdeRun.terminalStatus === 'PASS',
|
||||
'Wave-1 validation harness remains terminal-PASS for no-IDE/non-Codex flow when core projection surfaces are present',
|
||||
'Help validation harness remains terminal-PASS for no-IDE/non-Codex flow when core projection surfaces are present',
|
||||
);
|
||||
const noIdeStandaloneValidation = await harness.validateGeneratedArtifacts({
|
||||
projectDir: tempProjectRoot,
|
||||
|
|
@ -3862,7 +3862,7 @@ async function runTests() {
|
|||
});
|
||||
assert(
|
||||
noIdeStandaloneValidation.status === 'PASS',
|
||||
'Wave-1 validation harness infers no-IDE export prerequisite context during standalone validation when options are omitted',
|
||||
'Help validation harness infers no-IDE export prerequisite context during standalone validation when options are omitted',
|
||||
);
|
||||
try {
|
||||
await harness.buildObservedBindingEvidence({
|
||||
|
|
@ -3873,11 +3873,11 @@ async function runTests() {
|
|||
optionalSurface: false,
|
||||
runtimeFolder: '_bmad',
|
||||
});
|
||||
assert(false, 'Wave-1 replay evidence generation rejects unmapped claimed rowIdentity');
|
||||
assert(false, 'Help replay evidence generation rejects unmapped claimed rowIdentity');
|
||||
} catch (error) {
|
||||
assert(
|
||||
error.code === WAVE1_VALIDATION_ERROR_CODES.REQUIRED_ROW_IDENTITY_MISSING,
|
||||
'Wave-1 replay evidence generation emits deterministic missing-claimed-rowIdentity error code',
|
||||
error.code === HELP_VALIDATION_ERROR_CODES.REQUIRED_ROW_IDENTITY_MISSING,
|
||||
'Help replay evidence generation emits deterministic missing-claimed-rowIdentity error code',
|
||||
);
|
||||
}
|
||||
await fs.writeFile(
|
||||
|
|
@ -3895,16 +3895,16 @@ async function runTests() {
|
|||
sidecarPath: path.join(tempSourceTasksDir, 'help.artifact.yaml'),
|
||||
sourceMarkdownPath: path.join(tempSourceTasksDir, 'help.md'),
|
||||
});
|
||||
assert(false, 'Wave-1 validation harness fails when required projection input surfaces are missing');
|
||||
assert(false, 'Help validation harness fails when required projection input surfaces are missing');
|
||||
} catch (error) {
|
||||
assert(
|
||||
error.code === WAVE1_VALIDATION_ERROR_CODES.REQUIRED_ARTIFACT_MISSING,
|
||||
'Wave-1 validation harness emits deterministic missing-input-surface error code',
|
||||
error.code === HELP_VALIDATION_ERROR_CODES.REQUIRED_ARTIFACT_MISSING,
|
||||
'Help validation harness emits deterministic missing-input-surface error code',
|
||||
);
|
||||
}
|
||||
await writeCsv(
|
||||
path.join(tempConfigDir, 'task-manifest.csv'),
|
||||
[...TASK_MANIFEST_COMPATIBILITY_PREFIX_COLUMNS, ...TASK_MANIFEST_WAVE1_ADDITIVE_COLUMNS],
|
||||
[...TASK_MANIFEST_COMPATIBILITY_PREFIX_COLUMNS, ...TASK_MANIFEST_CANONICAL_ADDITIVE_COLUMNS],
|
||||
[
|
||||
{
|
||||
name: 'help',
|
||||
|
|
@ -3931,11 +3931,11 @@ async function runTests() {
|
|||
await fs.remove(artifactPathsById.get(14));
|
||||
try {
|
||||
await harness.validateGeneratedArtifacts({ projectDir: tempProjectRoot });
|
||||
assert(false, 'Wave-1 validation harness fails when a required artifact is missing');
|
||||
assert(false, 'Help validation harness fails when a required artifact is missing');
|
||||
} catch (error) {
|
||||
assert(
|
||||
error.code === WAVE1_VALIDATION_ERROR_CODES.REQUIRED_ARTIFACT_MISSING,
|
||||
'Wave-1 validation harness emits deterministic missing-artifact error code',
|
||||
error.code === HELP_VALIDATION_ERROR_CODES.REQUIRED_ARTIFACT_MISSING,
|
||||
'Help validation harness emits deterministic missing-artifact error code',
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -3954,11 +3954,11 @@ async function runTests() {
|
|||
await fs.writeFile(artifactTwoPath, artifactTwoLines.join('\n'), 'utf8');
|
||||
try {
|
||||
await harness.validateGeneratedArtifacts({ projectDir: tempProjectRoot });
|
||||
assert(false, 'Wave-1 validation harness rejects schema/header drift');
|
||||
assert(false, 'Help validation harness rejects schema/header drift');
|
||||
} catch (error) {
|
||||
assert(
|
||||
error.code === WAVE1_VALIDATION_ERROR_CODES.CSV_SCHEMA_MISMATCH,
|
||||
'Wave-1 validation harness emits deterministic schema-mismatch error code',
|
||||
error.code === HELP_VALIDATION_ERROR_CODES.CSV_SCHEMA_MISMATCH,
|
||||
'Help validation harness emits deterministic schema-mismatch error code',
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -3975,11 +3975,11 @@ async function runTests() {
|
|||
await fs.writeFile(artifactNinePath, `${artifactNineHeader}\n`, 'utf8');
|
||||
try {
|
||||
await harness.validateGeneratedArtifacts({ projectDir: tempProjectRoot });
|
||||
assert(false, 'Wave-1 validation harness rejects header-only required-identity artifacts');
|
||||
assert(false, 'Help validation harness rejects header-only required-identity artifacts');
|
||||
} catch (error) {
|
||||
assert(
|
||||
error.code === WAVE1_VALIDATION_ERROR_CODES.REQUIRED_ROW_IDENTITY_MISSING,
|
||||
'Wave-1 validation harness emits deterministic missing-row error code for header-only artifacts',
|
||||
error.code === HELP_VALIDATION_ERROR_CODES.REQUIRED_ROW_IDENTITY_MISSING,
|
||||
'Help validation harness emits deterministic missing-row error code for header-only artifacts',
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -4017,11 +4017,11 @@ async function runTests() {
|
|||
);
|
||||
try {
|
||||
await harness.validateGeneratedArtifacts({ projectDir: tempProjectRoot });
|
||||
assert(false, 'Wave-1 validation harness rejects missing required row identity values');
|
||||
assert(false, 'Help validation harness rejects missing required row identity values');
|
||||
} catch (error) {
|
||||
assert(
|
||||
error.code === WAVE1_VALIDATION_ERROR_CODES.REQUIRED_ROW_IDENTITY_MISSING,
|
||||
'Wave-1 validation harness emits deterministic row-identity error code',
|
||||
error.code === HELP_VALIDATION_ERROR_CODES.REQUIRED_ROW_IDENTITY_MISSING,
|
||||
'Help validation harness emits deterministic row-identity error code',
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -4061,11 +4061,11 @@ async function runTests() {
|
|||
);
|
||||
try {
|
||||
await harness.validateGeneratedArtifacts({ projectDir: tempProjectRoot });
|
||||
assert(false, 'Wave-1 validation harness rejects PASS rows missing required evidence-link fields');
|
||||
assert(false, 'Help validation harness rejects PASS rows missing required evidence-link fields');
|
||||
} catch (error) {
|
||||
assert(
|
||||
error.code === WAVE1_VALIDATION_ERROR_CODES.REQUIRED_EVIDENCE_LINK_MISSING,
|
||||
'Wave-1 validation harness emits deterministic evidence-link error code for missing row identity link',
|
||||
error.code === HELP_VALIDATION_ERROR_CODES.REQUIRED_EVIDENCE_LINK_MISSING,
|
||||
'Help validation harness emits deterministic evidence-link error code for missing row identity link',
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -4111,11 +4111,11 @@ async function runTests() {
|
|||
);
|
||||
try {
|
||||
await harness.validateGeneratedArtifacts({ projectDir: tempProjectRoot });
|
||||
assert(false, 'Wave-1 validation harness rejects self-attested issuer claims that diverge from validator evidence');
|
||||
assert(false, 'Help validation harness rejects self-attested issuer claims that diverge from validator evidence');
|
||||
} catch (error) {
|
||||
assert(
|
||||
error.code === WAVE1_VALIDATION_ERROR_CODES.SELF_ATTESTED_ISSUER_CLAIM,
|
||||
'Wave-1 validation harness emits deterministic self-attested issuer-claim rejection code',
|
||||
error.code === HELP_VALIDATION_ERROR_CODES.SELF_ATTESTED_ISSUER_CLAIM,
|
||||
'Help validation harness emits deterministic self-attested issuer-claim rejection code',
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -4151,11 +4151,11 @@ async function runTests() {
|
|||
);
|
||||
try {
|
||||
await harness.validateGeneratedArtifacts({ projectDir: tempProjectRoot });
|
||||
assert(false, 'Wave-1 validation harness rejects malformed replay-evidence payloads');
|
||||
assert(false, 'Help validation harness rejects malformed replay-evidence payloads');
|
||||
} catch (error) {
|
||||
assert(
|
||||
error.code === WAVE1_VALIDATION_ERROR_CODES.BINDING_EVIDENCE_INVALID,
|
||||
'Wave-1 validation harness emits deterministic replay-evidence validation error code',
|
||||
error.code === HELP_VALIDATION_ERROR_CODES.BINDING_EVIDENCE_INVALID,
|
||||
'Help validation harness emits deterministic replay-evidence validation error code',
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
|
|
@ -4167,13 +4167,13 @@ async function runTests() {
|
|||
console.log('');
|
||||
|
||||
// ============================================================
|
||||
// Test 15: Wave-2 shard-doc Validation Artifact Suite
|
||||
// Test 15: Shard-doc Validation Artifact Suite
|
||||
// ============================================================
|
||||
console.log(`${colors.yellow}Test Suite 15: Wave-2 shard-doc Validation Artifact Suite${colors.reset}\n`);
|
||||
console.log(`${colors.yellow}Test Suite 15: Shard-doc Validation Artifact Suite${colors.reset}\n`);
|
||||
|
||||
const tempWave2ValidationHarnessRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-wave2-validation-suite-'));
|
||||
const tempShardDocValidationHarnessRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-shard-doc-validation-suite-'));
|
||||
try {
|
||||
const tempProjectRoot = tempWave2ValidationHarnessRoot;
|
||||
const tempProjectRoot = tempShardDocValidationHarnessRoot;
|
||||
const tempBmadDir = path.join(tempProjectRoot, '_bmad');
|
||||
const tempConfigDir = path.join(tempBmadDir, '_config');
|
||||
const tempSourceTasksDir = path.join(tempProjectRoot, 'bmad-fork', 'src', 'core', 'tasks');
|
||||
|
|
@ -4245,7 +4245,7 @@ async function runTests() {
|
|||
|
||||
await writeCsv(
|
||||
path.join(tempConfigDir, 'task-manifest.csv'),
|
||||
[...TASK_MANIFEST_COMPATIBILITY_PREFIX_COLUMNS, ...TASK_MANIFEST_WAVE1_ADDITIVE_COLUMNS],
|
||||
[...TASK_MANIFEST_COMPATIBILITY_PREFIX_COLUMNS, ...TASK_MANIFEST_CANONICAL_ADDITIVE_COLUMNS],
|
||||
[
|
||||
{
|
||||
name: 'shard-doc',
|
||||
|
|
@ -4263,7 +4263,7 @@ async function runTests() {
|
|||
);
|
||||
await writeCsv(
|
||||
path.join(tempConfigDir, 'bmad-help.csv'),
|
||||
[...HELP_CATALOG_COMPATIBILITY_PREFIX_COLUMNS, ...HELP_CATALOG_WAVE1_ADDITIVE_COLUMNS],
|
||||
[...HELP_CATALOG_COMPATIBILITY_PREFIX_COLUMNS, ...HELP_CATALOG_CANONICAL_ADDITIVE_COLUMNS],
|
||||
[
|
||||
{
|
||||
module: 'core',
|
||||
|
|
@ -4353,7 +4353,7 @@ async function runTests() {
|
|||
},
|
||||
];
|
||||
|
||||
const harness = new Wave2ValidationHarness();
|
||||
const harness = new ShardDocValidationHarness();
|
||||
const firstRun = await harness.generateAndValidate({
|
||||
projectDir: tempProjectRoot,
|
||||
bmadDir: tempBmadDir,
|
||||
|
|
@ -4361,18 +4361,18 @@ async function runTests() {
|
|||
shardDocAuthorityRecords: authorityRecords,
|
||||
});
|
||||
assert(
|
||||
firstRun.terminalStatus === 'PASS' && firstRun.generatedArtifactCount === WAVE2_VALIDATION_ARTIFACT_REGISTRY.length,
|
||||
'Wave-2 validation harness generates and validates all required artifacts',
|
||||
firstRun.terminalStatus === 'PASS' && firstRun.generatedArtifactCount === SHARD_DOC_VALIDATION_ARTIFACT_REGISTRY.length,
|
||||
'Shard-doc validation harness generates and validates all required artifacts',
|
||||
);
|
||||
|
||||
const artifactPathsById = new Map(
|
||||
WAVE2_VALIDATION_ARTIFACT_REGISTRY.map((artifact) => [
|
||||
SHARD_DOC_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), `Wave-2 validation harness outputs artifact ${artifactId}`);
|
||||
assert(await fs.pathExists(artifactPath), `Shard-doc validation harness outputs artifact ${artifactId}`);
|
||||
}
|
||||
|
||||
const firstArtifactContents = new Map();
|
||||
|
|
@ -4394,16 +4394,16 @@ async function runTests() {
|
|||
break;
|
||||
}
|
||||
}
|
||||
assert(deterministicOutputs, 'Wave-2 validation harness outputs are byte-stable across unchanged repeated runs');
|
||||
assert(deterministicOutputs, 'Shard-doc validation harness outputs are byte-stable across unchanged repeated runs');
|
||||
|
||||
await fs.remove(artifactPathsById.get(8));
|
||||
try {
|
||||
await harness.validateGeneratedArtifacts({ projectDir: tempProjectRoot });
|
||||
assert(false, 'Wave-2 validation harness fails when a required artifact is missing');
|
||||
assert(false, 'Shard-doc validation harness fails when a required artifact is missing');
|
||||
} catch (error) {
|
||||
assert(
|
||||
error.code === WAVE2_VALIDATION_ERROR_CODES.REQUIRED_ARTIFACT_MISSING,
|
||||
'Wave-2 validation harness emits deterministic missing-artifact error code',
|
||||
error.code === SHARD_DOC_VALIDATION_ERROR_CODES.REQUIRED_ARTIFACT_MISSING,
|
||||
'Shard-doc validation harness emits deterministic missing-artifact error code',
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -4422,11 +4422,11 @@ async function runTests() {
|
|||
bmadFolderName: '_bmad',
|
||||
shardDocAuthorityRecords: authorityRecords,
|
||||
});
|
||||
assert(false, 'Wave-2 validation harness rejects missing command-label report input surface');
|
||||
assert(false, 'Shard-doc validation harness rejects missing command-label report input surface');
|
||||
} catch (error) {
|
||||
assert(
|
||||
error.code === WAVE2_VALIDATION_ERROR_CODES.REQUIRED_ARTIFACT_MISSING,
|
||||
'Wave-2 validation harness emits deterministic missing-input-surface error code',
|
||||
error.code === SHARD_DOC_VALIDATION_ERROR_CODES.REQUIRED_ARTIFACT_MISSING,
|
||||
'Shard-doc validation harness emits deterministic missing-input-surface error code',
|
||||
);
|
||||
}
|
||||
await writeCsv(commandLabelReportPath, commandLabelReportColumns, commandLabelReportRows);
|
||||
|
|
@ -4437,11 +4437,11 @@ async function runTests() {
|
|||
await fs.writeFile(artifactSixPath, artifactSixLines.join('\n'), 'utf8');
|
||||
try {
|
||||
await harness.validateGeneratedArtifacts({ projectDir: tempProjectRoot });
|
||||
assert(false, 'Wave-2 validation harness rejects schema/header drift');
|
||||
assert(false, 'Shard-doc validation harness rejects schema/header drift');
|
||||
} catch (error) {
|
||||
assert(
|
||||
error.code === WAVE2_VALIDATION_ERROR_CODES.CSV_SCHEMA_MISMATCH,
|
||||
'Wave-2 validation harness emits deterministic schema-mismatch error code',
|
||||
error.code === SHARD_DOC_VALIDATION_ERROR_CODES.CSV_SCHEMA_MISMATCH,
|
||||
'Shard-doc validation harness emits deterministic schema-mismatch error code',
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -4459,7 +4459,7 @@ async function runTests() {
|
|||
});
|
||||
const artifactSixInventoryRow = artifactEightRows.find((row) => row.artifactId === '6');
|
||||
if (artifactSixInventoryRow) {
|
||||
artifactSixInventoryRow.artifactPath = 'validation/wave-2/drifted-command-label-report.csv';
|
||||
artifactSixInventoryRow.artifactPath = 'validation/shard-doc/drifted-command-label-report.csv';
|
||||
}
|
||||
await writeCsv(
|
||||
artifactEightPath,
|
||||
|
|
@ -4468,11 +4468,11 @@ async function runTests() {
|
|||
);
|
||||
try {
|
||||
await harness.validateGeneratedArtifacts({ projectDir: tempProjectRoot });
|
||||
assert(false, 'Wave-2 validation harness rejects inventory deterministic-identifier drift');
|
||||
assert(false, 'Shard-doc validation harness rejects inventory deterministic-identifier drift');
|
||||
} catch (error) {
|
||||
assert(
|
||||
error.code === WAVE2_VALIDATION_ERROR_CODES.REQUIRED_ROW_MISSING,
|
||||
'Wave-2 validation harness emits deterministic inventory-row validation error code',
|
||||
error.code === SHARD_DOC_VALIDATION_ERROR_CODES.REQUIRED_ROW_MISSING,
|
||||
'Shard-doc validation harness emits deterministic inventory-row validation error code',
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -4496,17 +4496,17 @@ async function runTests() {
|
|||
);
|
||||
try {
|
||||
await harness.validateGeneratedArtifacts({ projectDir: tempProjectRoot });
|
||||
assert(false, 'Wave-2 validation harness rejects missing source-body authority records');
|
||||
assert(false, 'Shard-doc validation harness rejects missing source-body authority records');
|
||||
} catch (error) {
|
||||
assert(
|
||||
error.code === WAVE2_VALIDATION_ERROR_CODES.REQUIRED_ROW_MISSING,
|
||||
'Wave-2 validation harness emits deterministic missing-row error code',
|
||||
error.code === SHARD_DOC_VALIDATION_ERROR_CODES.REQUIRED_ROW_MISSING,
|
||||
'Shard-doc validation harness emits deterministic missing-row error code',
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
assert(false, 'Wave-2 shard-doc validation artifact suite setup', error.message);
|
||||
assert(false, 'Shard-doc validation artifact suite setup', error.message);
|
||||
} finally {
|
||||
await fs.remove(tempWave2ValidationHarnessRoot);
|
||||
await fs.remove(tempShardDocValidationHarnessRoot);
|
||||
}
|
||||
|
||||
console.log('');
|
||||
|
|
|
|||
|
|
@ -11,23 +11,23 @@ const { ManifestGenerator } = require('./manifest-generator');
|
|||
const { buildSidecarAwareExemplarHelpRow } = require('./help-catalog-generator');
|
||||
const { CodexSetup } = require('../ide/codex');
|
||||
|
||||
const WAVE1_VALIDATION_ERROR_CODES = Object.freeze({
|
||||
REQUIRED_ARTIFACT_MISSING: 'ERR_WAVE1_VALIDATION_REQUIRED_ARTIFACT_MISSING',
|
||||
CSV_SCHEMA_MISMATCH: 'ERR_WAVE1_VALIDATION_CSV_SCHEMA_MISMATCH',
|
||||
REQUIRED_ROW_IDENTITY_MISSING: 'ERR_WAVE1_VALIDATION_REQUIRED_ROW_IDENTITY_MISSING',
|
||||
REQUIRED_EVIDENCE_LINK_MISSING: 'ERR_WAVE1_VALIDATION_REQUIRED_EVIDENCE_LINK_MISSING',
|
||||
EVIDENCE_LINK_REFERENCE_INVALID: 'ERR_WAVE1_VALIDATION_EVIDENCE_LINK_REFERENCE_INVALID',
|
||||
BINDING_EVIDENCE_INVALID: 'ERR_WAVE1_VALIDATION_BINDING_EVIDENCE_INVALID',
|
||||
ISSUER_PREREQUISITE_MISSING: 'ERR_WAVE1_VALIDATION_ISSUER_PREREQUISITE_MISSING',
|
||||
SELF_ATTESTED_ISSUER_CLAIM: 'ERR_WAVE1_VALIDATION_SELF_ATTESTED_ISSUER_CLAIM',
|
||||
YAML_SCHEMA_MISMATCH: 'ERR_WAVE1_VALIDATION_YAML_SCHEMA_MISMATCH',
|
||||
DECISION_RECORD_SCHEMA_MISMATCH: 'ERR_WAVE1_VALIDATION_DECISION_RECORD_SCHEMA_MISMATCH',
|
||||
DECISION_RECORD_PARSE_FAILED: 'ERR_WAVE1_VALIDATION_DECISION_RECORD_PARSE_FAILED',
|
||||
const HELP_VALIDATION_ERROR_CODES = Object.freeze({
|
||||
REQUIRED_ARTIFACT_MISSING: 'ERR_HELP_VALIDATION_REQUIRED_ARTIFACT_MISSING',
|
||||
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',
|
||||
EVIDENCE_LINK_REFERENCE_INVALID: 'ERR_HELP_VALIDATION_EVIDENCE_LINK_REFERENCE_INVALID',
|
||||
BINDING_EVIDENCE_INVALID: 'ERR_HELP_VALIDATION_BINDING_EVIDENCE_INVALID',
|
||||
ISSUER_PREREQUISITE_MISSING: 'ERR_HELP_VALIDATION_ISSUER_PREREQUISITE_MISSING',
|
||||
SELF_ATTESTED_ISSUER_CLAIM: 'ERR_HELP_VALIDATION_SELF_ATTESTED_ISSUER_CLAIM',
|
||||
YAML_SCHEMA_MISMATCH: 'ERR_HELP_VALIDATION_YAML_SCHEMA_MISMATCH',
|
||||
DECISION_RECORD_SCHEMA_MISMATCH: 'ERR_HELP_VALIDATION_DECISION_RECORD_SCHEMA_MISMATCH',
|
||||
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 SOURCE_MARKDOWN_SOURCE_PATH = 'bmad-fork/src/core/tasks/help.md';
|
||||
const EVIDENCE_ISSUER_COMPONENT = 'bmad-fork/tools/cli/installers/lib/core/wave-1-validation-harness.js';
|
||||
const EVIDENCE_ISSUER_COMPONENT = 'bmad-fork/tools/cli/installers/lib/core/help-validation-harness.js';
|
||||
|
||||
const FRONTMATTER_MISMATCH_DETAILS = Object.freeze({
|
||||
[HELP_FRONTMATTER_MISMATCH_ERROR_CODES.CANONICAL_ID_MISMATCH]: 'frontmatter canonicalId must match sidecar canonicalId',
|
||||
|
|
@ -37,16 +37,16 @@ const FRONTMATTER_MISMATCH_DETAILS = Object.freeze({
|
|||
'frontmatter dependencies.requires must match sidecar dependencies.requires',
|
||||
});
|
||||
|
||||
const WAVE1_VALIDATION_ARTIFACT_REGISTRY = Object.freeze([
|
||||
const HELP_VALIDATION_ARTIFACT_REGISTRY = Object.freeze([
|
||||
Object.freeze({
|
||||
artifactId: 1,
|
||||
relativePath: path.join('validation', 'wave-1', 'bmad-help-sidecar-snapshot.yaml'),
|
||||
relativePath: path.join('validation', 'help', 'bmad-help-sidecar-snapshot.yaml'),
|
||||
type: 'yaml',
|
||||
requiredTopLevelKeys: ['schemaVersion', 'canonicalId', 'artifactType', 'module', 'sourcePath', 'displayName', 'description', 'status'],
|
||||
}),
|
||||
Object.freeze({
|
||||
artifactId: 2,
|
||||
relativePath: path.join('validation', 'wave-1', 'bmad-help-runtime-comparison.csv'),
|
||||
relativePath: path.join('validation', 'help', 'bmad-help-runtime-comparison.csv'),
|
||||
type: 'csv',
|
||||
columns: [
|
||||
'surface',
|
||||
|
|
@ -65,7 +65,7 @@ const WAVE1_VALIDATION_ARTIFACT_REGISTRY = Object.freeze([
|
|||
}),
|
||||
Object.freeze({
|
||||
artifactId: 3,
|
||||
relativePath: path.join('validation', 'wave-1', 'bmad-help-issued-artifact-provenance.csv'),
|
||||
relativePath: path.join('validation', 'help', 'bmad-help-issued-artifact-provenance.csv'),
|
||||
type: 'csv',
|
||||
columns: [
|
||||
'rowIdentity',
|
||||
|
|
@ -84,7 +84,7 @@ const WAVE1_VALIDATION_ARTIFACT_REGISTRY = Object.freeze([
|
|||
}),
|
||||
Object.freeze({
|
||||
artifactId: 4,
|
||||
relativePath: path.join('validation', 'wave-1', 'bmad-help-manifest-comparison.csv'),
|
||||
relativePath: path.join('validation', 'help', 'bmad-help-manifest-comparison.csv'),
|
||||
type: 'csv',
|
||||
columns: [
|
||||
'surface',
|
||||
|
|
@ -106,7 +106,7 @@ const WAVE1_VALIDATION_ARTIFACT_REGISTRY = Object.freeze([
|
|||
}),
|
||||
Object.freeze({
|
||||
artifactId: 5,
|
||||
relativePath: path.join('validation', 'wave-1', 'bmad-help-alias-table.csv'),
|
||||
relativePath: path.join('validation', 'help', 'bmad-help-alias-table.csv'),
|
||||
type: 'csv',
|
||||
columns: [
|
||||
'rowIdentity',
|
||||
|
|
@ -124,7 +124,7 @@ const WAVE1_VALIDATION_ARTIFACT_REGISTRY = Object.freeze([
|
|||
}),
|
||||
Object.freeze({
|
||||
artifactId: 6,
|
||||
relativePath: path.join('validation', 'wave-1', 'bmad-help-description-provenance.csv'),
|
||||
relativePath: path.join('validation', 'help', 'bmad-help-description-provenance.csv'),
|
||||
type: 'csv',
|
||||
columns: [
|
||||
'surface',
|
||||
|
|
@ -142,7 +142,7 @@ const WAVE1_VALIDATION_ARTIFACT_REGISTRY = Object.freeze([
|
|||
}),
|
||||
Object.freeze({
|
||||
artifactId: 7,
|
||||
relativePath: path.join('validation', 'wave-1', 'bmad-help-export-comparison.csv'),
|
||||
relativePath: path.join('validation', 'help', 'bmad-help-export-comparison.csv'),
|
||||
type: 'csv',
|
||||
columns: [
|
||||
'exportPath',
|
||||
|
|
@ -166,7 +166,7 @@ const WAVE1_VALIDATION_ARTIFACT_REGISTRY = Object.freeze([
|
|||
}),
|
||||
Object.freeze({
|
||||
artifactId: 8,
|
||||
relativePath: path.join('validation', 'wave-1', 'bmad-help-command-label-report.csv'),
|
||||
relativePath: path.join('validation', 'help', 'bmad-help-command-label-report.csv'),
|
||||
type: 'csv',
|
||||
columns: [
|
||||
'surface',
|
||||
|
|
@ -186,7 +186,7 @@ const WAVE1_VALIDATION_ARTIFACT_REGISTRY = Object.freeze([
|
|||
}),
|
||||
Object.freeze({
|
||||
artifactId: 9,
|
||||
relativePath: path.join('validation', 'wave-1', 'bmad-help-catalog-pipeline.csv'),
|
||||
relativePath: path.join('validation', 'help', 'bmad-help-catalog-pipeline.csv'),
|
||||
type: 'csv',
|
||||
columns: [
|
||||
'stage',
|
||||
|
|
@ -215,7 +215,7 @@ const WAVE1_VALIDATION_ARTIFACT_REGISTRY = Object.freeze([
|
|||
}),
|
||||
Object.freeze({
|
||||
artifactId: 10,
|
||||
relativePath: path.join('validation', 'wave-1', 'bmad-help-duplicate-report.csv'),
|
||||
relativePath: path.join('validation', 'help', 'bmad-help-duplicate-report.csv'),
|
||||
type: 'csv',
|
||||
columns: [
|
||||
'surface',
|
||||
|
|
@ -247,7 +247,7 @@ const WAVE1_VALIDATION_ARTIFACT_REGISTRY = Object.freeze([
|
|||
}),
|
||||
Object.freeze({
|
||||
artifactId: 11,
|
||||
relativePath: path.join('validation', 'wave-1', 'bmad-help-dependency-report.csv'),
|
||||
relativePath: path.join('validation', 'help', 'bmad-help-dependency-report.csv'),
|
||||
type: 'csv',
|
||||
columns: [
|
||||
'declaredIn',
|
||||
|
|
@ -268,13 +268,13 @@ const WAVE1_VALIDATION_ARTIFACT_REGISTRY = Object.freeze([
|
|||
}),
|
||||
Object.freeze({
|
||||
artifactId: 12,
|
||||
relativePath: path.join('decision-records', 'wave-1-native-skills-exit.md'),
|
||||
relativePath: path.join('decision-records', 'help-native-skills-exit.md'),
|
||||
type: 'markdown',
|
||||
requiredFrontmatterKeys: ['wave', 'goNoGo', 'status'],
|
||||
requiredFrontmatterKeys: ['capability', 'goNoGo', 'status'],
|
||||
}),
|
||||
Object.freeze({
|
||||
artifactId: 13,
|
||||
relativePath: path.join('validation', 'wave-1', 'bmad-help-sidecar-negative-validation.csv'),
|
||||
relativePath: path.join('validation', 'help', 'bmad-help-sidecar-negative-validation.csv'),
|
||||
type: 'csv',
|
||||
columns: [
|
||||
'scenario',
|
||||
|
|
@ -291,7 +291,7 @@ const WAVE1_VALIDATION_ARTIFACT_REGISTRY = Object.freeze([
|
|||
}),
|
||||
Object.freeze({
|
||||
artifactId: 14,
|
||||
relativePath: path.join('validation', 'wave-1', 'bmad-help-frontmatter-mismatch-validation.csv'),
|
||||
relativePath: path.join('validation', 'help', 'bmad-help-frontmatter-mismatch-validation.csv'),
|
||||
type: 'csv',
|
||||
columns: [
|
||||
'scenario',
|
||||
|
|
@ -314,11 +314,11 @@ const WAVE1_VALIDATION_ARTIFACT_REGISTRY = Object.freeze([
|
|||
}),
|
||||
]);
|
||||
|
||||
class Wave1ValidationHarnessError extends Error {
|
||||
class HelpValidationHarnessError extends Error {
|
||||
constructor({ code, detail, artifactId, fieldPath, sourcePath, observedValue, expectedValue }) {
|
||||
const message = `${code}: ${detail} (artifact=${artifactId}, fieldPath=${fieldPath}, sourcePath=${sourcePath})`;
|
||||
super(message);
|
||||
this.name = 'Wave1ValidationHarnessError';
|
||||
this.name = 'HelpValidationHarnessError';
|
||||
this.code = code;
|
||||
this.detail = detail;
|
||||
this.artifactId = artifactId;
|
||||
|
|
@ -505,7 +505,7 @@ function buildReplaySidecarFixture({ canonicalId = 'bmad-help', description = 'H
|
|||
|
||||
function replayFailurePayload(error) {
|
||||
return canonicalJsonStringify({
|
||||
replayFailureCode: normalizeValue(error?.code || 'ERR_WAVE1_REPLAY_COMPONENT_FAILED'),
|
||||
replayFailureCode: normalizeValue(error?.code || 'ERR_HELP_VALIDATION_REPLAY_COMPONENT_FAILED'),
|
||||
replayFailureDetail: normalizeValue(error?.detail || error?.message || 'component replay failed'),
|
||||
});
|
||||
}
|
||||
|
|
@ -514,9 +514,9 @@ function isSha256(value) {
|
|||
return /^[a-f0-9]{64}$/.test(String(value || ''));
|
||||
}
|
||||
|
||||
class Wave1ValidationHarness {
|
||||
class HelpValidationHarness {
|
||||
constructor() {
|
||||
this.registry = WAVE1_VALIDATION_ARTIFACT_REGISTRY;
|
||||
this.registry = HELP_VALIDATION_ARTIFACT_REGISTRY;
|
||||
}
|
||||
|
||||
getArtifactRegistry() {
|
||||
|
|
@ -526,7 +526,7 @@ class Wave1ValidationHarness {
|
|||
resolveOutputPaths(options = {}) {
|
||||
const projectDir = path.resolve(options.projectDir || process.cwd());
|
||||
const planningArtifactsRoot = path.join(projectDir, '_bmad-output', 'planning-artifacts');
|
||||
const validationRoot = path.join(planningArtifactsRoot, 'validation', 'wave-1');
|
||||
const validationRoot = path.join(planningArtifactsRoot, 'validation', 'help');
|
||||
const decisionRecordsRoot = path.join(planningArtifactsRoot, 'decision-records');
|
||||
return {
|
||||
projectDir,
|
||||
|
|
@ -608,8 +608,8 @@ class Wave1ValidationHarness {
|
|||
if (await fs.pathExists(absolutePath)) {
|
||||
return;
|
||||
}
|
||||
throw new Wave1ValidationHarnessError({
|
||||
code: WAVE1_VALIDATION_ERROR_CODES.REQUIRED_ARTIFACT_MISSING,
|
||||
throw new HelpValidationHarnessError({
|
||||
code: HELP_VALIDATION_ERROR_CODES.REQUIRED_ARTIFACT_MISSING,
|
||||
detail: `Required input surface is missing (${description})`,
|
||||
artifactId,
|
||||
fieldPath: '<file>',
|
||||
|
|
@ -624,8 +624,8 @@ class Wave1ValidationHarness {
|
|||
if (match) {
|
||||
return match;
|
||||
}
|
||||
throw new Wave1ValidationHarnessError({
|
||||
code: WAVE1_VALIDATION_ERROR_CODES.REQUIRED_ROW_IDENTITY_MISSING,
|
||||
throw new HelpValidationHarnessError({
|
||||
code: HELP_VALIDATION_ERROR_CODES.REQUIRED_ROW_IDENTITY_MISSING,
|
||||
detail,
|
||||
artifactId,
|
||||
fieldPath,
|
||||
|
|
@ -734,8 +734,8 @@ class Wave1ValidationHarness {
|
|||
resolveReplayContract({ artifactPath, componentPath, rowIdentity, runtimeFolder }) {
|
||||
const claimedRowIdentity = normalizeValue(rowIdentity);
|
||||
if (!claimedRowIdentity) {
|
||||
throw new Wave1ValidationHarnessError({
|
||||
code: WAVE1_VALIDATION_ERROR_CODES.REQUIRED_ROW_IDENTITY_MISSING,
|
||||
throw new HelpValidationHarnessError({
|
||||
code: HELP_VALIDATION_ERROR_CODES.REQUIRED_ROW_IDENTITY_MISSING,
|
||||
detail: 'Claimed replay rowIdentity is required',
|
||||
artifactId: 3,
|
||||
fieldPath: 'rowIdentity',
|
||||
|
|
@ -747,8 +747,8 @@ class Wave1ValidationHarness {
|
|||
|
||||
const expectedRowIdentity = buildIssuedArtifactRowIdentity(artifactPath);
|
||||
if (claimedRowIdentity !== expectedRowIdentity) {
|
||||
throw new Wave1ValidationHarnessError({
|
||||
code: WAVE1_VALIDATION_ERROR_CODES.REQUIRED_ROW_IDENTITY_MISSING,
|
||||
throw new HelpValidationHarnessError({
|
||||
code: HELP_VALIDATION_ERROR_CODES.REQUIRED_ROW_IDENTITY_MISSING,
|
||||
detail: 'Claimed replay rowIdentity does not match artifact claim rowIdentity contract',
|
||||
artifactId: 3,
|
||||
fieldPath: 'rowIdentity',
|
||||
|
|
@ -809,8 +809,8 @@ class Wave1ValidationHarness {
|
|||
|
||||
const contract = contractsByClaimRowIdentity.get(claimedRowIdentity);
|
||||
if (!contract) {
|
||||
throw new Wave1ValidationHarnessError({
|
||||
code: WAVE1_VALIDATION_ERROR_CODES.REQUIRED_ROW_IDENTITY_MISSING,
|
||||
throw new HelpValidationHarnessError({
|
||||
code: HELP_VALIDATION_ERROR_CODES.REQUIRED_ROW_IDENTITY_MISSING,
|
||||
detail: 'Claimed rowIdentity is not mapped to a replay contract',
|
||||
artifactId: 3,
|
||||
fieldPath: 'rowIdentity',
|
||||
|
|
@ -825,8 +825,8 @@ class Wave1ValidationHarness {
|
|||
normalizeValue(artifactPath) !== normalizeValue(contract.artifactPath) ||
|
||||
!normalizedComponentPath.includes(String(contract.componentPathIncludes || '').toLowerCase())
|
||||
) {
|
||||
throw new Wave1ValidationHarnessError({
|
||||
code: WAVE1_VALIDATION_ERROR_CODES.BINDING_EVIDENCE_INVALID,
|
||||
throw new HelpValidationHarnessError({
|
||||
code: HELP_VALIDATION_ERROR_CODES.BINDING_EVIDENCE_INVALID,
|
||||
detail: 'Claimed replay rowIdentity/component pair does not match replay contract mapping',
|
||||
artifactId: 3,
|
||||
fieldPath: 'issuingComponent',
|
||||
|
|
@ -1024,14 +1024,14 @@ class Wave1ValidationHarness {
|
|||
rowIdentity,
|
||||
runtimeFolder,
|
||||
});
|
||||
const baselineWorkspaceRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'wave1-replay-baseline-'));
|
||||
const perturbedWorkspaceRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'wave1-replay-perturbed-'));
|
||||
const baselineWorkspaceRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'help-replay-baseline-'));
|
||||
const perturbedWorkspaceRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'help-replay-perturbed-'));
|
||||
|
||||
try {
|
||||
const baseline = await contract.run({ workspaceRoot: baselineWorkspaceRoot, perturbed: false });
|
||||
if (Number(baseline.targetRowCount) <= 0) {
|
||||
throw new Wave1ValidationHarnessError({
|
||||
code: WAVE1_VALIDATION_ERROR_CODES.REQUIRED_ROW_IDENTITY_MISSING,
|
||||
throw new HelpValidationHarnessError({
|
||||
code: HELP_VALIDATION_ERROR_CODES.REQUIRED_ROW_IDENTITY_MISSING,
|
||||
detail: 'Claimed rowIdentity target is absent in baseline component replay output',
|
||||
artifactId: 3,
|
||||
fieldPath: 'rowIdentity',
|
||||
|
|
@ -1236,7 +1236,7 @@ class Wave1ValidationHarness {
|
|||
const runtimeHelpCatalogPath = `${runtimeFolder}/_config/bmad-help.csv`;
|
||||
const runtimePipelinePath = `${runtimeFolder}/_config/bmad-help-catalog-pipeline.csv`;
|
||||
const runtimeCommandLabelPath = `${runtimeFolder}/_config/bmad-help-command-label-report.csv`;
|
||||
const evidenceArtifactPath = '_bmad-output/planning-artifacts/validation/wave-1/bmad-help-issued-artifact-provenance.csv';
|
||||
const evidenceArtifactPath = '_bmad-output/planning-artifacts/validation/help/bmad-help-issued-artifact-provenance.csv';
|
||||
const exportSkillPath = '.agents/skills/bmad-help/SKILL.md';
|
||||
const exportSkillAbsolutePath = path.join(outputPaths.projectDir, '.agents', 'skills', 'bmad-help', 'SKILL.md');
|
||||
const codexExportRows =
|
||||
|
|
@ -1445,8 +1445,8 @@ class Wave1ValidationHarness {
|
|||
status: 'PASS',
|
||||
}));
|
||||
if (aliasRowsForExemplar.length === 0) {
|
||||
throw new Wave1ValidationHarnessError({
|
||||
code: WAVE1_VALIDATION_ERROR_CODES.REQUIRED_ROW_IDENTITY_MISSING,
|
||||
throw new HelpValidationHarnessError({
|
||||
code: HELP_VALIDATION_ERROR_CODES.REQUIRED_ROW_IDENTITY_MISSING,
|
||||
detail: 'Required canonical alias rows for exemplar are missing',
|
||||
artifactId: 5,
|
||||
fieldPath: 'rows[canonicalId=bmad-help]',
|
||||
|
|
@ -1626,8 +1626,8 @@ class Wave1ValidationHarness {
|
|||
};
|
||||
});
|
||||
if (pipelineWithEvidence.length === 0) {
|
||||
throw new Wave1ValidationHarnessError({
|
||||
code: WAVE1_VALIDATION_ERROR_CODES.REQUIRED_ROW_IDENTITY_MISSING,
|
||||
throw new HelpValidationHarnessError({
|
||||
code: HELP_VALIDATION_ERROR_CODES.REQUIRED_ROW_IDENTITY_MISSING,
|
||||
detail: 'Required help-catalog pipeline exemplar rows are missing',
|
||||
artifactId: 9,
|
||||
fieldPath: 'rows[canonicalId=bmad-help]',
|
||||
|
|
@ -1883,11 +1883,11 @@ class Wave1ValidationHarness {
|
|||
|
||||
// Artifact 12: decision record
|
||||
const decisionRecord = {
|
||||
wave: 1,
|
||||
capability: 'bmad-help',
|
||||
goNoGo: 'GO',
|
||||
status: 'PASS',
|
||||
};
|
||||
const decisionRecordContent = `---\n${yaml.stringify(decisionRecord).trimEnd()}\n---\n\n# Wave 1 Native Skills Exit\n\nStatus: PASS\n`;
|
||||
const decisionRecordContent = `---\n${yaml.stringify(decisionRecord).trimEnd()}\n---\n\n# Help Native Skills Exit\n\nStatus: PASS\n`;
|
||||
await fs.writeFile(artifactPaths.get(12), decisionRecordContent, 'utf8');
|
||||
|
||||
// Fixtures for artifacts 13 and 14
|
||||
|
|
@ -1898,15 +1898,14 @@ class Wave1ValidationHarness {
|
|||
const sidecarNegativeScenarios = [
|
||||
{
|
||||
scenario: 'unknown-major-version',
|
||||
fixturePath: '_bmad-output/planning-artifacts/validation/wave-1/fixtures/sidecar-negative/unknown-major-version/help.artifact.yaml',
|
||||
fixturePath: '_bmad-output/planning-artifacts/validation/help/fixtures/sidecar-negative/unknown-major-version/help.artifact.yaml',
|
||||
absolutePath: fixtures.unknownMajorFixturePath,
|
||||
expectedFailureCode: HELP_SIDECAR_ERROR_CODES.MAJOR_VERSION_UNSUPPORTED,
|
||||
expectedFailureDetail: 'sidecar schema major version is unsupported',
|
||||
},
|
||||
{
|
||||
scenario: 'basename-path-mismatch',
|
||||
fixturePath:
|
||||
'_bmad-output/planning-artifacts/validation/wave-1/fixtures/sidecar-negative/basename-path-mismatch/help.artifact.yaml',
|
||||
fixturePath: '_bmad-output/planning-artifacts/validation/help/fixtures/sidecar-negative/basename-path-mismatch/help.artifact.yaml',
|
||||
absolutePath: fixtures.basenameMismatchFixturePath,
|
||||
expectedFailureCode: HELP_SIDECAR_ERROR_CODES.SOURCEPATH_BASENAME_MISMATCH,
|
||||
expectedFailureDetail: 'sidecar basename does not match sourcePath basename',
|
||||
|
|
@ -1991,7 +1990,7 @@ class Wave1ValidationHarness {
|
|||
for (const scope of ['source', 'runtime']) {
|
||||
for (const scenario of mismatchScenarios) {
|
||||
const fixturePath = path.join(outputPaths.validationRoot, 'fixtures', 'frontmatter-mismatch', scope, `${scenario.scenario}.md`);
|
||||
const fixtureRelativePath = `_bmad-output/planning-artifacts/validation/wave-1/fixtures/frontmatter-mismatch/${scope}/${scenario.scenario}.md`;
|
||||
const fixtureRelativePath = `_bmad-output/planning-artifacts/validation/help/fixtures/frontmatter-mismatch/${scope}/${scenario.scenario}.md`;
|
||||
let observedFailureCode = '';
|
||||
let observedFailureDetail = '';
|
||||
let observedFrontmatterValue = '';
|
||||
|
|
@ -2071,8 +2070,8 @@ class Wave1ValidationHarness {
|
|||
try {
|
||||
parsed = JSON.parse(String(payloadRaw || ''));
|
||||
} catch (error) {
|
||||
throw new Wave1ValidationHarnessError({
|
||||
code: WAVE1_VALIDATION_ERROR_CODES.BINDING_EVIDENCE_INVALID,
|
||||
throw new HelpValidationHarnessError({
|
||||
code: HELP_VALIDATION_ERROR_CODES.BINDING_EVIDENCE_INVALID,
|
||||
detail: `Binding evidence payload is not valid JSON (${error.message})`,
|
||||
artifactId,
|
||||
fieldPath,
|
||||
|
|
@ -2083,8 +2082,8 @@ class Wave1ValidationHarness {
|
|||
}
|
||||
|
||||
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
||||
throw new Wave1ValidationHarnessError({
|
||||
code: WAVE1_VALIDATION_ERROR_CODES.BINDING_EVIDENCE_INVALID,
|
||||
throw new HelpValidationHarnessError({
|
||||
code: HELP_VALIDATION_ERROR_CODES.BINDING_EVIDENCE_INVALID,
|
||||
detail: 'Binding evidence payload must be a JSON object',
|
||||
artifactId,
|
||||
fieldPath,
|
||||
|
|
@ -2108,8 +2107,8 @@ class Wave1ValidationHarness {
|
|||
});
|
||||
|
||||
if (normalizeValue(payload.evidenceVersion) !== '1') {
|
||||
throw new Wave1ValidationHarnessError({
|
||||
code: WAVE1_VALIDATION_ERROR_CODES.BINDING_EVIDENCE_INVALID,
|
||||
throw new HelpValidationHarnessError({
|
||||
code: HELP_VALIDATION_ERROR_CODES.BINDING_EVIDENCE_INVALID,
|
||||
detail: 'Binding evidence payload must use evidenceVersion=1',
|
||||
artifactId,
|
||||
fieldPath: 'issuingComponentBindingEvidence.evidenceVersion',
|
||||
|
|
@ -2121,8 +2120,8 @@ class Wave1ValidationHarness {
|
|||
|
||||
if (rowStatus === 'SKIP') {
|
||||
if (normalizeValue(payload.observationMethod) !== 'validator-observed-optional-surface-omitted') {
|
||||
throw new Wave1ValidationHarnessError({
|
||||
code: WAVE1_VALIDATION_ERROR_CODES.BINDING_EVIDENCE_INVALID,
|
||||
throw new HelpValidationHarnessError({
|
||||
code: HELP_VALIDATION_ERROR_CODES.BINDING_EVIDENCE_INVALID,
|
||||
detail: 'Optional-surface provenance rows must use optional-surface evidence method',
|
||||
artifactId,
|
||||
fieldPath: 'issuingComponentBindingEvidence.observationMethod',
|
||||
|
|
@ -2150,8 +2149,8 @@ class Wave1ValidationHarness {
|
|||
];
|
||||
for (const key of requiredPayloadFields) {
|
||||
if (normalizeValue(payload[key]).length === 0 && payload[key] !== false) {
|
||||
throw new Wave1ValidationHarnessError({
|
||||
code: WAVE1_VALIDATION_ERROR_CODES.BINDING_EVIDENCE_INVALID,
|
||||
throw new HelpValidationHarnessError({
|
||||
code: HELP_VALIDATION_ERROR_CODES.BINDING_EVIDENCE_INVALID,
|
||||
detail: 'Required binding evidence field is missing',
|
||||
artifactId,
|
||||
fieldPath: `issuingComponentBindingEvidence.${key}`,
|
||||
|
|
@ -2167,8 +2166,8 @@ class Wave1ValidationHarness {
|
|||
normalizeValue(row.evidenceMethod) !== 'validator-observed-baseline-plus-isolated-single-component-perturbation' ||
|
||||
normalizeValue(row.issuingComponentBindingBasis) !== 'validator-observed-baseline-plus-isolated-single-component-perturbation'
|
||||
) {
|
||||
throw new Wave1ValidationHarnessError({
|
||||
code: WAVE1_VALIDATION_ERROR_CODES.BINDING_EVIDENCE_INVALID,
|
||||
throw new HelpValidationHarnessError({
|
||||
code: HELP_VALIDATION_ERROR_CODES.BINDING_EVIDENCE_INVALID,
|
||||
detail: 'Replay evidence must use the baseline-plus-isolated-perturbation method',
|
||||
artifactId,
|
||||
fieldPath: 'evidenceMethod',
|
||||
|
|
@ -2185,8 +2184,8 @@ class Wave1ValidationHarness {
|
|||
normalizeValue(payload.mutatedRowIdentity) !== normalizeValue(row.rowIdentity) ||
|
||||
normalizeValue(payload.targetedRowLocator) !== normalizeValue(row.rowIdentity)
|
||||
) {
|
||||
throw new Wave1ValidationHarnessError({
|
||||
code: WAVE1_VALIDATION_ERROR_CODES.BINDING_EVIDENCE_INVALID,
|
||||
throw new HelpValidationHarnessError({
|
||||
code: HELP_VALIDATION_ERROR_CODES.BINDING_EVIDENCE_INVALID,
|
||||
detail: 'Binding evidence payload does not match provenance row contract fields',
|
||||
artifactId,
|
||||
fieldPath: 'issuingComponentBindingEvidence',
|
||||
|
|
@ -2197,8 +2196,8 @@ class Wave1ValidationHarness {
|
|||
}
|
||||
|
||||
if (!isSha256(payload.baselineArtifactSha256) || !isSha256(payload.mutatedArtifactSha256) || !isSha256(payload.rowLevelDiffSha256)) {
|
||||
throw new Wave1ValidationHarnessError({
|
||||
code: WAVE1_VALIDATION_ERROR_CODES.BINDING_EVIDENCE_INVALID,
|
||||
throw new HelpValidationHarnessError({
|
||||
code: HELP_VALIDATION_ERROR_CODES.BINDING_EVIDENCE_INVALID,
|
||||
detail: 'Replay evidence hashes must be sha256 hex values',
|
||||
artifactId,
|
||||
fieldPath: 'issuingComponentBindingEvidence.*Sha256',
|
||||
|
|
@ -2213,8 +2212,8 @@ class Wave1ValidationHarness {
|
|||
}
|
||||
|
||||
if (payload.baselineArtifactSha256 === payload.mutatedArtifactSha256 || payload.perturbationApplied !== true) {
|
||||
throw new Wave1ValidationHarnessError({
|
||||
code: WAVE1_VALIDATION_ERROR_CODES.BINDING_EVIDENCE_INVALID,
|
||||
throw new HelpValidationHarnessError({
|
||||
code: HELP_VALIDATION_ERROR_CODES.BINDING_EVIDENCE_INVALID,
|
||||
detail: 'Replay evidence must show isolated perturbation impact',
|
||||
artifactId,
|
||||
fieldPath: 'issuingComponentBindingEvidence.perturbationApplied',
|
||||
|
|
@ -2229,8 +2228,8 @@ class Wave1ValidationHarness {
|
|||
}
|
||||
|
||||
if (Number(payload.baselineTargetRowCount) <= Number(payload.mutatedTargetRowCount)) {
|
||||
throw new Wave1ValidationHarnessError({
|
||||
code: WAVE1_VALIDATION_ERROR_CODES.BINDING_EVIDENCE_INVALID,
|
||||
throw new HelpValidationHarnessError({
|
||||
code: HELP_VALIDATION_ERROR_CODES.BINDING_EVIDENCE_INVALID,
|
||||
detail: 'Replay evidence must show reduced target-row impact after perturbation',
|
||||
artifactId,
|
||||
fieldPath: 'issuingComponentBindingEvidence.baselineTargetRowCount',
|
||||
|
|
@ -2250,8 +2249,8 @@ class Wave1ValidationHarness {
|
|||
if (normalizeValue(value).length > 0) {
|
||||
return;
|
||||
}
|
||||
throw new Wave1ValidationHarnessError({
|
||||
code: WAVE1_VALIDATION_ERROR_CODES.REQUIRED_EVIDENCE_LINK_MISSING,
|
||||
throw new HelpValidationHarnessError({
|
||||
code: HELP_VALIDATION_ERROR_CODES.REQUIRED_EVIDENCE_LINK_MISSING,
|
||||
detail: 'Required evidence-link field is missing or empty',
|
||||
artifactId,
|
||||
fieldPath,
|
||||
|
|
@ -2276,8 +2275,8 @@ class Wave1ValidationHarness {
|
|||
}
|
||||
|
||||
if (normalizeValue(row.issuedArtifactEvidencePath) !== evidencePath) {
|
||||
throw new Wave1ValidationHarnessError({
|
||||
code: WAVE1_VALIDATION_ERROR_CODES.EVIDENCE_LINK_REFERENCE_INVALID,
|
||||
throw new HelpValidationHarnessError({
|
||||
code: HELP_VALIDATION_ERROR_CODES.EVIDENCE_LINK_REFERENCE_INVALID,
|
||||
detail: 'Evidence-link path does not point to required provenance artifact',
|
||||
artifactId,
|
||||
fieldPath: `rows[${index}].issuedArtifactEvidencePath`,
|
||||
|
|
@ -2290,8 +2289,8 @@ class Wave1ValidationHarness {
|
|||
const linkedEvidenceRowIdentity = normalizeValue(row.issuedArtifactEvidenceRowIdentity);
|
||||
const provenanceRow = provenanceByIdentity.get(linkedEvidenceRowIdentity);
|
||||
if (!provenanceRow) {
|
||||
throw new Wave1ValidationHarnessError({
|
||||
code: WAVE1_VALIDATION_ERROR_CODES.EVIDENCE_LINK_REFERENCE_INVALID,
|
||||
throw new HelpValidationHarnessError({
|
||||
code: HELP_VALIDATION_ERROR_CODES.EVIDENCE_LINK_REFERENCE_INVALID,
|
||||
detail: 'Evidence-link row identity does not resolve to provenance artifact row',
|
||||
artifactId,
|
||||
fieldPath: `rows[${index}].issuedArtifactEvidenceRowIdentity`,
|
||||
|
|
@ -2302,8 +2301,8 @@ class Wave1ValidationHarness {
|
|||
}
|
||||
|
||||
if (normalizeValue(provenanceRow.status) !== 'PASS') {
|
||||
throw new Wave1ValidationHarnessError({
|
||||
code: WAVE1_VALIDATION_ERROR_CODES.ISSUER_PREREQUISITE_MISSING,
|
||||
throw new HelpValidationHarnessError({
|
||||
code: HELP_VALIDATION_ERROR_CODES.ISSUER_PREREQUISITE_MISSING,
|
||||
detail: 'Terminal PASS requires linked provenance rows to be PASS',
|
||||
artifactId,
|
||||
fieldPath: `rows[${index}].issuedArtifactEvidenceRowIdentity`,
|
||||
|
|
@ -2314,8 +2313,8 @@ class Wave1ValidationHarness {
|
|||
}
|
||||
|
||||
if (rowArtifactPathField && normalizeValue(row[rowArtifactPathField]) !== normalizeValue(provenanceRow.artifactPath)) {
|
||||
throw new Wave1ValidationHarnessError({
|
||||
code: WAVE1_VALIDATION_ERROR_CODES.EVIDENCE_LINK_REFERENCE_INVALID,
|
||||
throw new HelpValidationHarnessError({
|
||||
code: HELP_VALIDATION_ERROR_CODES.EVIDENCE_LINK_REFERENCE_INVALID,
|
||||
detail: 'Evidence-linked provenance row does not match claimed artifact path',
|
||||
artifactId,
|
||||
fieldPath: `rows[${index}].${rowArtifactPathField}`,
|
||||
|
|
@ -2330,8 +2329,8 @@ class Wave1ValidationHarness {
|
|||
normalizeValue(row.issuingComponent).length > 0 &&
|
||||
normalizeValue(row.issuingComponent) !== normalizeValue(provenanceRow.issuingComponent)
|
||||
) {
|
||||
throw new Wave1ValidationHarnessError({
|
||||
code: WAVE1_VALIDATION_ERROR_CODES.SELF_ATTESTED_ISSUER_CLAIM,
|
||||
throw new HelpValidationHarnessError({
|
||||
code: HELP_VALIDATION_ERROR_CODES.SELF_ATTESTED_ISSUER_CLAIM,
|
||||
detail: 'Issuer component claim diverges from validator-linked provenance evidence',
|
||||
artifactId,
|
||||
fieldPath: `rows[${index}].issuingComponent`,
|
||||
|
|
@ -2346,8 +2345,8 @@ class Wave1ValidationHarness {
|
|||
normalizeValue(row.issuingComponentBindingEvidence).length > 0 &&
|
||||
normalizeValue(row.issuingComponentBindingEvidence) !== normalizeValue(provenanceRow.issuingComponentBindingEvidence)
|
||||
) {
|
||||
throw new Wave1ValidationHarnessError({
|
||||
code: WAVE1_VALIDATION_ERROR_CODES.SELF_ATTESTED_ISSUER_CLAIM,
|
||||
throw new HelpValidationHarnessError({
|
||||
code: HELP_VALIDATION_ERROR_CODES.SELF_ATTESTED_ISSUER_CLAIM,
|
||||
detail: 'Issuer binding evidence claim diverges from validator-linked provenance evidence',
|
||||
artifactId,
|
||||
fieldPath: `rows[${index}].issuingComponentBindingEvidence`,
|
||||
|
|
@ -2360,7 +2359,7 @@ class Wave1ValidationHarness {
|
|||
}
|
||||
|
||||
validateIssuerPrerequisites({ artifactDataById, runtimeFolder, requireExportSkillProjection }) {
|
||||
const evidencePath = '_bmad-output/planning-artifacts/validation/wave-1/bmad-help-issued-artifact-provenance.csv';
|
||||
const evidencePath = '_bmad-output/planning-artifacts/validation/help/bmad-help-issued-artifact-provenance.csv';
|
||||
const provenanceArtifact = artifactDataById.get(3) || { rows: [] };
|
||||
const provenanceRows = Array.isArray(provenanceArtifact.rows) ? provenanceArtifact.rows : [];
|
||||
const provenanceByIdentity = new Map();
|
||||
|
|
@ -2392,8 +2391,8 @@ class Wave1ValidationHarness {
|
|||
for (const artifactPath of requiredProvenanceArtifactPaths) {
|
||||
const row = provenanceByArtifactPath.get(artifactPath);
|
||||
if (!row || normalizeValue(row.status) !== 'PASS') {
|
||||
throw new Wave1ValidationHarnessError({
|
||||
code: WAVE1_VALIDATION_ERROR_CODES.ISSUER_PREREQUISITE_MISSING,
|
||||
throw new HelpValidationHarnessError({
|
||||
code: HELP_VALIDATION_ERROR_CODES.ISSUER_PREREQUISITE_MISSING,
|
||||
detail: 'Terminal PASS requires provenance prerequisite rows for all required issuing-component claims',
|
||||
artifactId: 3,
|
||||
fieldPath: `rows[artifactPath=${artifactPath}]`,
|
||||
|
|
@ -2494,9 +2493,9 @@ class Wave1ValidationHarness {
|
|||
for (const artifact of this.registry) {
|
||||
const artifactPath = path.join(planningArtifactsRoot, artifact.relativePath);
|
||||
if (!(await fs.pathExists(artifactPath))) {
|
||||
throw new Wave1ValidationHarnessError({
|
||||
code: WAVE1_VALIDATION_ERROR_CODES.REQUIRED_ARTIFACT_MISSING,
|
||||
detail: 'Required wave-1 validation artifact is missing',
|
||||
throw new HelpValidationHarnessError({
|
||||
code: HELP_VALIDATION_ERROR_CODES.REQUIRED_ARTIFACT_MISSING,
|
||||
detail: 'Required help validation artifact is missing',
|
||||
artifactId: artifact.artifactId,
|
||||
fieldPath: '<file>',
|
||||
sourcePath: normalizePath(artifact.relativePath),
|
||||
|
|
@ -2519,8 +2518,8 @@ class Wave1ValidationHarness {
|
|||
});
|
||||
|
||||
if (observedHeader.length !== expectedHeader.length) {
|
||||
throw new Wave1ValidationHarnessError({
|
||||
code: WAVE1_VALIDATION_ERROR_CODES.CSV_SCHEMA_MISMATCH,
|
||||
throw new HelpValidationHarnessError({
|
||||
code: HELP_VALIDATION_ERROR_CODES.CSV_SCHEMA_MISMATCH,
|
||||
detail: 'CSV header length does not match required schema',
|
||||
artifactId: artifact.artifactId,
|
||||
fieldPath: '<header>',
|
||||
|
|
@ -2534,8 +2533,8 @@ class Wave1ValidationHarness {
|
|||
const observed = normalizeValue(observedHeader[index]);
|
||||
const expected = normalizeValue(expectedValue);
|
||||
if (observed !== expected) {
|
||||
throw new Wave1ValidationHarnessError({
|
||||
code: WAVE1_VALIDATION_ERROR_CODES.CSV_SCHEMA_MISMATCH,
|
||||
throw new HelpValidationHarnessError({
|
||||
code: HELP_VALIDATION_ERROR_CODES.CSV_SCHEMA_MISMATCH,
|
||||
detail: 'CSV header ordering does not match required schema',
|
||||
artifactId: artifact.artifactId,
|
||||
fieldPath: `header[${index}]`,
|
||||
|
|
@ -2548,8 +2547,8 @@ class Wave1ValidationHarness {
|
|||
|
||||
if (Array.isArray(artifact.requiredRowIdentityFields) && artifact.requiredRowIdentityFields.length > 0) {
|
||||
if (rows.length === 0) {
|
||||
throw new Wave1ValidationHarnessError({
|
||||
code: WAVE1_VALIDATION_ERROR_CODES.REQUIRED_ROW_IDENTITY_MISSING,
|
||||
throw new HelpValidationHarnessError({
|
||||
code: HELP_VALIDATION_ERROR_CODES.REQUIRED_ROW_IDENTITY_MISSING,
|
||||
detail: 'Required row identity rows are missing',
|
||||
artifactId: artifact.artifactId,
|
||||
fieldPath: 'rows',
|
||||
|
|
@ -2560,8 +2559,8 @@ class Wave1ValidationHarness {
|
|||
}
|
||||
for (const field of artifact.requiredRowIdentityFields) {
|
||||
if (!expectedHeader.includes(field)) {
|
||||
throw new Wave1ValidationHarnessError({
|
||||
code: WAVE1_VALIDATION_ERROR_CODES.CSV_SCHEMA_MISMATCH,
|
||||
throw new HelpValidationHarnessError({
|
||||
code: HELP_VALIDATION_ERROR_CODES.CSV_SCHEMA_MISMATCH,
|
||||
detail: 'Required row identity field is missing from artifact schema',
|
||||
artifactId: artifact.artifactId,
|
||||
fieldPath: `header.${field}`,
|
||||
|
|
@ -2574,10 +2573,10 @@ class Wave1ValidationHarness {
|
|||
for (const [rowIndex, row] of rows.entries()) {
|
||||
if (normalizeValue(row[field]).length === 0) {
|
||||
const isEvidenceLinkField = field === 'issuedArtifactEvidenceRowIdentity';
|
||||
throw new Wave1ValidationHarnessError({
|
||||
throw new HelpValidationHarnessError({
|
||||
code: isEvidenceLinkField
|
||||
? WAVE1_VALIDATION_ERROR_CODES.REQUIRED_EVIDENCE_LINK_MISSING
|
||||
: WAVE1_VALIDATION_ERROR_CODES.REQUIRED_ROW_IDENTITY_MISSING,
|
||||
? HELP_VALIDATION_ERROR_CODES.REQUIRED_EVIDENCE_LINK_MISSING
|
||||
: HELP_VALIDATION_ERROR_CODES.REQUIRED_ROW_IDENTITY_MISSING,
|
||||
detail: isEvidenceLinkField
|
||||
? 'Required evidence-link row identity is missing or empty'
|
||||
: 'Required row identity value is missing or empty',
|
||||
|
|
@ -2601,8 +2600,8 @@ class Wave1ValidationHarness {
|
|||
parsed,
|
||||
});
|
||||
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
||||
throw new Wave1ValidationHarnessError({
|
||||
code: WAVE1_VALIDATION_ERROR_CODES.YAML_SCHEMA_MISMATCH,
|
||||
throw new HelpValidationHarnessError({
|
||||
code: HELP_VALIDATION_ERROR_CODES.YAML_SCHEMA_MISMATCH,
|
||||
detail: 'YAML artifact root must be a mapping object',
|
||||
artifactId: artifact.artifactId,
|
||||
fieldPath: '<document>',
|
||||
|
|
@ -2613,8 +2612,8 @@ class Wave1ValidationHarness {
|
|||
}
|
||||
for (const requiredKey of artifact.requiredTopLevelKeys || []) {
|
||||
if (!Object.prototype.hasOwnProperty.call(parsed, requiredKey)) {
|
||||
throw new Wave1ValidationHarnessError({
|
||||
code: WAVE1_VALIDATION_ERROR_CODES.YAML_SCHEMA_MISMATCH,
|
||||
throw new HelpValidationHarnessError({
|
||||
code: HELP_VALIDATION_ERROR_CODES.YAML_SCHEMA_MISMATCH,
|
||||
detail: 'Required YAML key is missing',
|
||||
artifactId: artifact.artifactId,
|
||||
fieldPath: requiredKey,
|
||||
|
|
@ -2637,8 +2636,8 @@ class Wave1ValidationHarness {
|
|||
try {
|
||||
frontmatter = parseFrontmatter(content);
|
||||
} catch (error) {
|
||||
throw new Wave1ValidationHarnessError({
|
||||
code: WAVE1_VALIDATION_ERROR_CODES.DECISION_RECORD_PARSE_FAILED,
|
||||
throw new HelpValidationHarnessError({
|
||||
code: HELP_VALIDATION_ERROR_CODES.DECISION_RECORD_PARSE_FAILED,
|
||||
detail: `Unable to parse decision record frontmatter (${error.message})`,
|
||||
artifactId: artifact.artifactId,
|
||||
fieldPath: '<frontmatter>',
|
||||
|
|
@ -2647,8 +2646,8 @@ class Wave1ValidationHarness {
|
|||
}
|
||||
for (const requiredKey of artifact.requiredFrontmatterKeys || []) {
|
||||
if (!Object.prototype.hasOwnProperty.call(frontmatter, requiredKey)) {
|
||||
throw new Wave1ValidationHarnessError({
|
||||
code: WAVE1_VALIDATION_ERROR_CODES.DECISION_RECORD_SCHEMA_MISMATCH,
|
||||
throw new HelpValidationHarnessError({
|
||||
code: HELP_VALIDATION_ERROR_CODES.DECISION_RECORD_SCHEMA_MISMATCH,
|
||||
detail: 'Required decision-record key is missing',
|
||||
artifactId: artifact.artifactId,
|
||||
fieldPath: requiredKey,
|
||||
|
|
@ -2695,8 +2694,8 @@ class Wave1ValidationHarness {
|
|||
}
|
||||
|
||||
module.exports = {
|
||||
WAVE1_VALIDATION_ERROR_CODES,
|
||||
WAVE1_VALIDATION_ARTIFACT_REGISTRY,
|
||||
Wave1ValidationHarnessError,
|
||||
Wave1ValidationHarness,
|
||||
HELP_VALIDATION_ERROR_CODES,
|
||||
HELP_VALIDATION_ARTIFACT_REGISTRY,
|
||||
HelpValidationHarnessError,
|
||||
HelpValidationHarness,
|
||||
};
|
||||
|
|
@ -20,8 +20,8 @@ const {
|
|||
renderDisplayedCommandLabel,
|
||||
} = require('./help-catalog-generator');
|
||||
const { validateHelpCatalogCompatibilitySurface } = require('./projection-compatibility-validator');
|
||||
const { Wave1ValidationHarness } = require('./wave-1-validation-harness');
|
||||
const { Wave2ValidationHarness } = require('./wave-2-validation-harness');
|
||||
const { HelpValidationHarness } = require('./help-validation-harness');
|
||||
const { ShardDocValidationHarness } = require('./shard-doc-validation-harness');
|
||||
const { getProjectRoot, getSourcePath, getModulePath } = require('../../../lib/project-root');
|
||||
const { CLIUtils } = require('../../../lib/cli-utils');
|
||||
const { ManifestGenerator } = require('./manifest-generator');
|
||||
|
|
@ -59,10 +59,10 @@ class Installer {
|
|||
this.helpCatalogPipelineRows = [];
|
||||
this.helpCatalogCommandLabelReportRows = [];
|
||||
this.codexExportDerivationRecords = [];
|
||||
this.latestWave1ValidationRun = null;
|
||||
this.latestWave2ValidationRun = null;
|
||||
this.wave1ValidationHarness = new Wave1ValidationHarness();
|
||||
this.wave2ValidationHarness = new Wave2ValidationHarness();
|
||||
this.latestHelpValidationRun = null;
|
||||
this.latestShardDocValidationRun = null;
|
||||
this.helpValidationHarness = new HelpValidationHarness();
|
||||
this.shardDocValidationHarness = new ShardDocValidationHarness();
|
||||
}
|
||||
|
||||
async runConfigurationGenerationTask({ message, bmadDir, moduleConfigs, config, allModules, addResult }) {
|
||||
|
|
@ -151,7 +151,7 @@ class Installer {
|
|||
return 'Configurations generated';
|
||||
}
|
||||
|
||||
async buildWave1ValidationOptions({ projectDir, bmadDir }) {
|
||||
async buildHelpValidationOptions({ projectDir, bmadDir }) {
|
||||
const exportSkillProjectionPath = path.join(projectDir, '.agents', 'skills', 'bmad-help', 'SKILL.md');
|
||||
const hasCodexExportDerivationRecords =
|
||||
Array.isArray(this.codexExportDerivationRecords) && this.codexExportDerivationRecords.length > 0;
|
||||
|
|
@ -169,7 +169,7 @@ class Installer {
|
|||
};
|
||||
}
|
||||
|
||||
async buildWave2ValidationOptions({ projectDir, bmadDir }) {
|
||||
async buildShardDocValidationOptions({ projectDir, bmadDir }) {
|
||||
return {
|
||||
projectDir,
|
||||
bmadDir,
|
||||
|
|
@ -1356,25 +1356,25 @@ class Installer {
|
|||
postIdeTasks.push({
|
||||
title: 'Generating validation artifacts',
|
||||
task: async (message) => {
|
||||
message('Generating deterministic wave-1 validation artifact suite...');
|
||||
const validationOptions = await this.buildWave1ValidationOptions({
|
||||
message('Generating deterministic help validation artifact suite...');
|
||||
const validationOptions = await this.buildHelpValidationOptions({
|
||||
projectDir,
|
||||
bmadDir,
|
||||
});
|
||||
const validationRun = await this.wave1ValidationHarness.generateAndValidate(validationOptions);
|
||||
this.latestWave1ValidationRun = validationRun;
|
||||
addResult('Wave-1 validation artifacts', 'ok', `${validationRun.generatedArtifactCount} artifacts`);
|
||||
const validationRun = await this.helpValidationHarness.generateAndValidate(validationOptions);
|
||||
this.latestHelpValidationRun = validationRun;
|
||||
addResult('Help validation artifacts', 'ok', `${validationRun.generatedArtifactCount} artifacts`);
|
||||
|
||||
message('Generating deterministic wave-2 shard-doc validation artifact suite...');
|
||||
const wave2ValidationOptions = await this.buildWave2ValidationOptions({
|
||||
message('Generating deterministic shard-doc validation artifact suite...');
|
||||
const shardDocValidationOptions = await this.buildShardDocValidationOptions({
|
||||
projectDir,
|
||||
bmadDir,
|
||||
});
|
||||
const wave2ValidationRun = await this.wave2ValidationHarness.generateAndValidate(wave2ValidationOptions);
|
||||
this.latestWave2ValidationRun = wave2ValidationRun;
|
||||
addResult('Wave-2 validation artifacts', 'ok', `${wave2ValidationRun.generatedArtifactCount} artifacts`);
|
||||
const shardDocValidationRun = await this.shardDocValidationHarness.generateAndValidate(shardDocValidationOptions);
|
||||
this.latestShardDocValidationRun = shardDocValidationRun;
|
||||
addResult('Shard-doc validation artifacts', 'ok', `${shardDocValidationRun.generatedArtifactCount} artifacts`);
|
||||
|
||||
return `${validationRun.generatedArtifactCount + wave2ValidationRun.generatedArtifactCount} validation artifacts generated`;
|
||||
return `${validationRun.generatedArtifactCount + shardDocValidationRun.generatedArtifactCount} validation artifacts generated`;
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1099,7 +1099,7 @@ class ManifestGenerator {
|
|||
}
|
||||
}
|
||||
|
||||
// Create CSV header with compatibility-prefix columns followed by additive wave-1 columns.
|
||||
// Create CSV header with compatibility-prefix columns followed by additive canonical-identity columns.
|
||||
let csvContent = 'name,displayName,description,module,path,standalone,legacyName,canonicalId,authoritySourceType,authoritySourcePath\n';
|
||||
|
||||
// Combine existing and new tasks
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ const csv = require('csv-parse/sync');
|
|||
|
||||
const TASK_MANIFEST_COMPATIBILITY_PREFIX_COLUMNS = Object.freeze(['name', 'displayName', 'description', 'module', 'path', 'standalone']);
|
||||
|
||||
const TASK_MANIFEST_WAVE1_ADDITIVE_COLUMNS = Object.freeze(['legacyName', 'canonicalId', 'authoritySourceType', 'authoritySourcePath']);
|
||||
const TASK_MANIFEST_CANONICAL_ADDITIVE_COLUMNS = Object.freeze(['legacyName', 'canonicalId', 'authoritySourceType', 'authoritySourcePath']);
|
||||
|
||||
const HELP_CATALOG_COMPATIBILITY_PREFIX_COLUMNS = Object.freeze([
|
||||
'module',
|
||||
|
|
@ -15,7 +15,7 @@ const HELP_CATALOG_COMPATIBILITY_PREFIX_COLUMNS = Object.freeze([
|
|||
'required',
|
||||
]);
|
||||
|
||||
const HELP_CATALOG_WAVE1_ADDITIVE_COLUMNS = Object.freeze([
|
||||
const HELP_CATALOG_CANONICAL_ADDITIVE_COLUMNS = Object.freeze([
|
||||
'agent-name',
|
||||
'agent-command',
|
||||
'agent-display-name',
|
||||
|
|
@ -29,12 +29,12 @@ const HELP_CATALOG_WAVE1_ADDITIVE_COLUMNS = Object.freeze([
|
|||
const PROJECTION_COMPATIBILITY_ERROR_CODES = Object.freeze({
|
||||
TASK_MANIFEST_CSV_PARSE_FAILED: 'ERR_TASK_MANIFEST_COMPAT_PARSE_FAILED',
|
||||
TASK_MANIFEST_HEADER_PREFIX_MISMATCH: 'ERR_TASK_MANIFEST_COMPAT_HEADER_PREFIX_MISMATCH',
|
||||
TASK_MANIFEST_HEADER_WAVE1_MISMATCH: 'ERR_TASK_MANIFEST_COMPAT_HEADER_WAVE1_MISMATCH',
|
||||
TASK_MANIFEST_HEADER_CANONICAL_MISMATCH: 'ERR_TASK_MANIFEST_COMPAT_HEADER_CANONICAL_MISMATCH',
|
||||
TASK_MANIFEST_REQUIRED_COLUMN_MISSING: 'ERR_TASK_MANIFEST_COMPAT_REQUIRED_COLUMN_MISSING',
|
||||
TASK_MANIFEST_ROW_FIELD_EMPTY: 'ERR_TASK_MANIFEST_COMPAT_ROW_FIELD_EMPTY',
|
||||
HELP_CATALOG_CSV_PARSE_FAILED: 'ERR_HELP_CATALOG_COMPAT_PARSE_FAILED',
|
||||
HELP_CATALOG_HEADER_PREFIX_MISMATCH: 'ERR_HELP_CATALOG_COMPAT_HEADER_PREFIX_MISMATCH',
|
||||
HELP_CATALOG_HEADER_WAVE1_MISMATCH: 'ERR_HELP_CATALOG_COMPAT_HEADER_WAVE1_MISMATCH',
|
||||
HELP_CATALOG_HEADER_CANONICAL_MISMATCH: 'ERR_HELP_CATALOG_COMPAT_HEADER_CANONICAL_MISMATCH',
|
||||
HELP_CATALOG_REQUIRED_COLUMN_MISSING: 'ERR_HELP_CATALOG_COMPAT_REQUIRED_COLUMN_MISSING',
|
||||
HELP_CATALOG_EXEMPLAR_ROW_CONTRACT_FAILED: 'ERR_HELP_CATALOG_COMPAT_EXEMPLAR_ROW_CONTRACT_FAILED',
|
||||
HELP_CATALOG_SHARD_DOC_ROW_CONTRACT_FAILED: 'ERR_HELP_CATALOG_COMPAT_SHARD_DOC_ROW_CONTRACT_FAILED',
|
||||
|
|
@ -375,10 +375,10 @@ function validateTaskManifestCompatibilitySurface(csvContent, options = {}) {
|
|||
});
|
||||
assertLockedColumns({
|
||||
headerColumns,
|
||||
expectedColumns: TASK_MANIFEST_WAVE1_ADDITIVE_COLUMNS,
|
||||
expectedColumns: TASK_MANIFEST_CANONICAL_ADDITIVE_COLUMNS,
|
||||
offset: TASK_MANIFEST_COMPATIBILITY_PREFIX_COLUMNS.length,
|
||||
code: PROJECTION_COMPATIBILITY_ERROR_CODES.TASK_MANIFEST_HEADER_WAVE1_MISMATCH,
|
||||
detail: 'Task-manifest wave-1 additive columns must remain appended after compatibility-prefix columns',
|
||||
code: PROJECTION_COMPATIBILITY_ERROR_CODES.TASK_MANIFEST_HEADER_CANONICAL_MISMATCH,
|
||||
detail: 'Task-manifest canonical additive columns must remain appended after compatibility-prefix columns',
|
||||
surface,
|
||||
sourcePath,
|
||||
});
|
||||
|
|
@ -419,10 +419,10 @@ function validateHelpCatalogCompatibilitySurface(csvContent, options = {}) {
|
|||
});
|
||||
assertLockedColumns({
|
||||
headerColumns,
|
||||
expectedColumns: HELP_CATALOG_WAVE1_ADDITIVE_COLUMNS,
|
||||
expectedColumns: HELP_CATALOG_CANONICAL_ADDITIVE_COLUMNS,
|
||||
offset: HELP_CATALOG_COMPATIBILITY_PREFIX_COLUMNS.length,
|
||||
code: PROJECTION_COMPATIBILITY_ERROR_CODES.HELP_CATALOG_HEADER_WAVE1_MISMATCH,
|
||||
detail: 'Help-catalog wave-1 additive columns must remain appended after compatibility-prefix columns',
|
||||
code: PROJECTION_COMPATIBILITY_ERROR_CODES.HELP_CATALOG_HEADER_CANONICAL_MISMATCH,
|
||||
detail: 'Help-catalog canonical additive columns must remain appended after compatibility-prefix columns',
|
||||
surface,
|
||||
sourcePath,
|
||||
});
|
||||
|
|
@ -528,9 +528,9 @@ module.exports = {
|
|||
PROJECTION_COMPATIBILITY_ERROR_CODES,
|
||||
ProjectionCompatibilityError,
|
||||
TASK_MANIFEST_COMPATIBILITY_PREFIX_COLUMNS,
|
||||
TASK_MANIFEST_WAVE1_ADDITIVE_COLUMNS,
|
||||
TASK_MANIFEST_CANONICAL_ADDITIVE_COLUMNS,
|
||||
HELP_CATALOG_COMPATIBILITY_PREFIX_COLUMNS,
|
||||
HELP_CATALOG_WAVE1_ADDITIVE_COLUMNS,
|
||||
HELP_CATALOG_CANONICAL_ADDITIVE_COLUMNS,
|
||||
validateTaskManifestCompatibilitySurface,
|
||||
validateTaskManifestLoaderEntries,
|
||||
validateHelpCatalogCompatibilitySurface,
|
||||
|
|
|
|||
|
|
@ -8,23 +8,23 @@ const { normalizeDisplayedCommandLabel } = require('./help-catalog-generator');
|
|||
const SHARD_DOC_SIDECAR_SOURCE_PATH = 'bmad-fork/src/core/tasks/shard-doc.artifact.yaml';
|
||||
const SHARD_DOC_SOURCE_XML_SOURCE_PATH = 'bmad-fork/src/core/tasks/shard-doc.xml';
|
||||
|
||||
const WAVE2_VALIDATION_ERROR_CODES = Object.freeze({
|
||||
REQUIRED_ARTIFACT_MISSING: 'ERR_WAVE2_VALIDATION_REQUIRED_ARTIFACT_MISSING',
|
||||
CSV_SCHEMA_MISMATCH: 'ERR_WAVE2_VALIDATION_CSV_SCHEMA_MISMATCH',
|
||||
REQUIRED_ROW_MISSING: 'ERR_WAVE2_VALIDATION_REQUIRED_ROW_MISSING',
|
||||
YAML_SCHEMA_MISMATCH: 'ERR_WAVE2_VALIDATION_YAML_SCHEMA_MISMATCH',
|
||||
const SHARD_DOC_VALIDATION_ERROR_CODES = Object.freeze({
|
||||
REQUIRED_ARTIFACT_MISSING: 'ERR_SHARD_DOC_VALIDATION_REQUIRED_ARTIFACT_MISSING',
|
||||
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',
|
||||
});
|
||||
|
||||
const WAVE2_VALIDATION_ARTIFACT_REGISTRY = Object.freeze([
|
||||
const SHARD_DOC_VALIDATION_ARTIFACT_REGISTRY = Object.freeze([
|
||||
Object.freeze({
|
||||
artifactId: 1,
|
||||
relativePath: path.join('validation', 'wave-2', 'shard-doc-sidecar-snapshot.yaml'),
|
||||
relativePath: path.join('validation', 'shard-doc', 'shard-doc-sidecar-snapshot.yaml'),
|
||||
type: 'yaml',
|
||||
requiredTopLevelKeys: ['schemaVersion', 'canonicalId', 'artifactType', 'module', 'sourcePath', 'displayName', 'description', 'status'],
|
||||
}),
|
||||
Object.freeze({
|
||||
artifactId: 2,
|
||||
relativePath: path.join('validation', 'wave-2', 'shard-doc-authority-records.csv'),
|
||||
relativePath: path.join('validation', 'shard-doc', 'shard-doc-authority-records.csv'),
|
||||
type: 'csv',
|
||||
columns: [
|
||||
'rowIdentity',
|
||||
|
|
@ -39,7 +39,7 @@ const WAVE2_VALIDATION_ARTIFACT_REGISTRY = Object.freeze([
|
|||
}),
|
||||
Object.freeze({
|
||||
artifactId: 3,
|
||||
relativePath: path.join('validation', 'wave-2', 'shard-doc-task-manifest-comparison.csv'),
|
||||
relativePath: path.join('validation', 'shard-doc', 'shard-doc-task-manifest-comparison.csv'),
|
||||
type: 'csv',
|
||||
columns: [
|
||||
'surface',
|
||||
|
|
@ -56,13 +56,13 @@ const WAVE2_VALIDATION_ARTIFACT_REGISTRY = Object.freeze([
|
|||
}),
|
||||
Object.freeze({
|
||||
artifactId: 4,
|
||||
relativePath: path.join('validation', 'wave-2', 'shard-doc-help-catalog-comparison.csv'),
|
||||
relativePath: path.join('validation', 'shard-doc', 'shard-doc-help-catalog-comparison.csv'),
|
||||
type: 'csv',
|
||||
columns: ['surface', 'sourcePath', 'name', 'workflowFile', 'command', 'rowCountForCanonicalCommand', 'status'],
|
||||
}),
|
||||
Object.freeze({
|
||||
artifactId: 5,
|
||||
relativePath: path.join('validation', 'wave-2', 'shard-doc-alias-table.csv'),
|
||||
relativePath: path.join('validation', 'shard-doc', 'shard-doc-alias-table.csv'),
|
||||
type: 'csv',
|
||||
columns: [
|
||||
'rowIdentity',
|
||||
|
|
@ -80,7 +80,7 @@ const WAVE2_VALIDATION_ARTIFACT_REGISTRY = Object.freeze([
|
|||
}),
|
||||
Object.freeze({
|
||||
artifactId: 6,
|
||||
relativePath: path.join('validation', 'wave-2', 'shard-doc-command-label-report.csv'),
|
||||
relativePath: path.join('validation', 'shard-doc', 'shard-doc-command-label-report.csv'),
|
||||
type: 'csv',
|
||||
columns: [
|
||||
'surface',
|
||||
|
|
@ -96,24 +96,24 @@ const WAVE2_VALIDATION_ARTIFACT_REGISTRY = Object.freeze([
|
|||
}),
|
||||
Object.freeze({
|
||||
artifactId: 7,
|
||||
relativePath: path.join('validation', 'wave-2', 'shard-doc-duplicate-report.csv'),
|
||||
relativePath: path.join('validation', 'shard-doc', 'shard-doc-duplicate-report.csv'),
|
||||
type: 'csv',
|
||||
columns: ['surface', 'canonicalId', 'normalizedVisibleKey', 'matchingRowCount', 'status'],
|
||||
}),
|
||||
Object.freeze({
|
||||
artifactId: 8,
|
||||
relativePath: path.join('validation', 'wave-2', 'shard-doc-artifact-inventory.csv'),
|
||||
relativePath: path.join('validation', 'shard-doc', 'shard-doc-artifact-inventory.csv'),
|
||||
type: 'csv',
|
||||
columns: ['rowIdentity', 'artifactId', 'artifactPath', 'artifactType', 'required', 'rowCount', 'exists', 'schemaVersion', 'status'],
|
||||
requiredRowIdentityFields: ['rowIdentity'],
|
||||
}),
|
||||
]);
|
||||
|
||||
class Wave2ValidationHarnessError extends Error {
|
||||
class ShardDocValidationHarnessError extends Error {
|
||||
constructor({ code, detail, artifactId, fieldPath, sourcePath, observedValue, expectedValue }) {
|
||||
const message = `${code}: ${detail} (artifact=${artifactId}, fieldPath=${fieldPath}, sourcePath=${sourcePath})`;
|
||||
super(message);
|
||||
this.name = 'Wave2ValidationHarnessError';
|
||||
this.name = 'ShardDocValidationHarnessError';
|
||||
this.code = code;
|
||||
this.detail = detail;
|
||||
this.artifactId = artifactId;
|
||||
|
|
@ -170,9 +170,9 @@ function sortRowsDeterministically(rows, columns) {
|
|||
});
|
||||
}
|
||||
|
||||
class Wave2ValidationHarness {
|
||||
class ShardDocValidationHarness {
|
||||
constructor() {
|
||||
this.registry = WAVE2_VALIDATION_ARTIFACT_REGISTRY;
|
||||
this.registry = SHARD_DOC_VALIDATION_ARTIFACT_REGISTRY;
|
||||
}
|
||||
|
||||
getArtifactRegistry() {
|
||||
|
|
@ -182,7 +182,7 @@ class Wave2ValidationHarness {
|
|||
resolveOutputPaths(options = {}) {
|
||||
const projectDir = path.resolve(options.projectDir || process.cwd());
|
||||
const planningArtifactsRoot = path.join(projectDir, '_bmad-output', 'planning-artifacts');
|
||||
const validationRoot = path.join(planningArtifactsRoot, 'validation', 'wave-2');
|
||||
const validationRoot = path.join(planningArtifactsRoot, 'validation', 'shard-doc');
|
||||
return {
|
||||
projectDir,
|
||||
planningArtifactsRoot,
|
||||
|
|
@ -207,8 +207,8 @@ class Wave2ValidationHarness {
|
|||
if (await fs.pathExists(absolutePath)) {
|
||||
return;
|
||||
}
|
||||
throw new Wave2ValidationHarnessError({
|
||||
code: WAVE2_VALIDATION_ERROR_CODES.REQUIRED_ARTIFACT_MISSING,
|
||||
throw new ShardDocValidationHarnessError({
|
||||
code: SHARD_DOC_VALIDATION_ERROR_CODES.REQUIRED_ARTIFACT_MISSING,
|
||||
detail: `Required input surface is missing (${description})`,
|
||||
artifactId,
|
||||
fieldPath: '<file>',
|
||||
|
|
@ -223,8 +223,8 @@ class Wave2ValidationHarness {
|
|||
if (match) {
|
||||
return match;
|
||||
}
|
||||
throw new Wave2ValidationHarnessError({
|
||||
code: WAVE2_VALIDATION_ERROR_CODES.REQUIRED_ROW_MISSING,
|
||||
throw new ShardDocValidationHarnessError({
|
||||
code: SHARD_DOC_VALIDATION_ERROR_CODES.REQUIRED_ROW_MISSING,
|
||||
detail,
|
||||
artifactId,
|
||||
fieldPath,
|
||||
|
|
@ -318,8 +318,8 @@ class Wave2ValidationHarness {
|
|||
normalizePath(normalizeValue(row['workflow-file'])).toLowerCase().endsWith('/core/tasks/shard-doc.xml'),
|
||||
);
|
||||
if (shardDocHelpRows.length !== 1) {
|
||||
throw new Wave2ValidationHarnessError({
|
||||
code: WAVE2_VALIDATION_ERROR_CODES.REQUIRED_ROW_MISSING,
|
||||
throw new ShardDocValidationHarnessError({
|
||||
code: SHARD_DOC_VALIDATION_ERROR_CODES.REQUIRED_ROW_MISSING,
|
||||
detail: 'Expected exactly one shard-doc help-catalog command row',
|
||||
artifactId: 4,
|
||||
fieldPath: 'rows[*].command',
|
||||
|
|
@ -334,8 +334,8 @@ class Wave2ValidationHarness {
|
|||
const observedAliasTypes = new Set(shardDocAliasRows.map((row) => normalizeValue(row.aliasType)));
|
||||
for (const aliasType of requiredAliasTypes) {
|
||||
if (!observedAliasTypes.has(aliasType)) {
|
||||
throw new Wave2ValidationHarnessError({
|
||||
code: WAVE2_VALIDATION_ERROR_CODES.REQUIRED_ROW_MISSING,
|
||||
throw new ShardDocValidationHarnessError({
|
||||
code: SHARD_DOC_VALIDATION_ERROR_CODES.REQUIRED_ROW_MISSING,
|
||||
detail: 'Required shard-doc alias type row is missing',
|
||||
artifactId: 5,
|
||||
fieldPath: 'rows[*].aliasType',
|
||||
|
|
@ -348,8 +348,8 @@ class Wave2ValidationHarness {
|
|||
|
||||
const shardDocCommandLabelRows = commandLabelRows.filter((row) => normalizeValue(row.canonicalId) === 'bmad-shard-doc');
|
||||
if (shardDocCommandLabelRows.length !== 1) {
|
||||
throw new Wave2ValidationHarnessError({
|
||||
code: WAVE2_VALIDATION_ERROR_CODES.REQUIRED_ROW_MISSING,
|
||||
throw new ShardDocValidationHarnessError({
|
||||
code: SHARD_DOC_VALIDATION_ERROR_CODES.REQUIRED_ROW_MISSING,
|
||||
detail: 'Expected exactly one shard-doc command-label row',
|
||||
artifactId: 6,
|
||||
fieldPath: 'rows[*].canonicalId',
|
||||
|
|
@ -536,9 +536,9 @@ class Wave2ValidationHarness {
|
|||
for (const artifact of this.registry) {
|
||||
const artifactPath = path.join(outputPaths.planningArtifactsRoot, artifact.relativePath);
|
||||
if (!(await fs.pathExists(artifactPath))) {
|
||||
throw new Wave2ValidationHarnessError({
|
||||
code: WAVE2_VALIDATION_ERROR_CODES.REQUIRED_ARTIFACT_MISSING,
|
||||
detail: 'Required wave-2 validation artifact is missing',
|
||||
throw new ShardDocValidationHarnessError({
|
||||
code: SHARD_DOC_VALIDATION_ERROR_CODES.REQUIRED_ARTIFACT_MISSING,
|
||||
detail: 'Required shard-doc validation artifact is missing',
|
||||
artifactId: artifact.artifactId,
|
||||
fieldPath: '<file>',
|
||||
sourcePath: normalizePath(artifact.relativePath),
|
||||
|
|
@ -552,8 +552,8 @@ class Wave2ValidationHarness {
|
|||
const observedHeader = parseCsvHeader(content);
|
||||
const expectedHeader = artifact.columns || [];
|
||||
if (observedHeader.length !== expectedHeader.length) {
|
||||
throw new Wave2ValidationHarnessError({
|
||||
code: WAVE2_VALIDATION_ERROR_CODES.CSV_SCHEMA_MISMATCH,
|
||||
throw new ShardDocValidationHarnessError({
|
||||
code: SHARD_DOC_VALIDATION_ERROR_CODES.CSV_SCHEMA_MISMATCH,
|
||||
detail: 'CSV header length does not match required schema',
|
||||
artifactId: artifact.artifactId,
|
||||
fieldPath: '<header>',
|
||||
|
|
@ -567,8 +567,8 @@ class Wave2ValidationHarness {
|
|||
const observed = normalizeValue(observedHeader[index]);
|
||||
const expected = normalizeValue(expectedValue);
|
||||
if (observed !== expected) {
|
||||
throw new Wave2ValidationHarnessError({
|
||||
code: WAVE2_VALIDATION_ERROR_CODES.CSV_SCHEMA_MISMATCH,
|
||||
throw new ShardDocValidationHarnessError({
|
||||
code: SHARD_DOC_VALIDATION_ERROR_CODES.CSV_SCHEMA_MISMATCH,
|
||||
detail: 'CSV header ordering does not match required schema',
|
||||
artifactId: artifact.artifactId,
|
||||
fieldPath: `header[${index}]`,
|
||||
|
|
@ -581,8 +581,8 @@ class Wave2ValidationHarness {
|
|||
|
||||
const rows = parseCsvRows(content);
|
||||
if (rows.length === 0) {
|
||||
throw new Wave2ValidationHarnessError({
|
||||
code: WAVE2_VALIDATION_ERROR_CODES.REQUIRED_ROW_MISSING,
|
||||
throw new ShardDocValidationHarnessError({
|
||||
code: SHARD_DOC_VALIDATION_ERROR_CODES.REQUIRED_ROW_MISSING,
|
||||
detail: 'Required CSV artifact rows are missing',
|
||||
artifactId: artifact.artifactId,
|
||||
fieldPath: 'rows',
|
||||
|
|
@ -594,8 +594,8 @@ class Wave2ValidationHarness {
|
|||
for (const requiredField of artifact.requiredRowIdentityFields || []) {
|
||||
for (const [rowIndex, row] of rows.entries()) {
|
||||
if (!normalizeValue(row[requiredField])) {
|
||||
throw new Wave2ValidationHarnessError({
|
||||
code: WAVE2_VALIDATION_ERROR_CODES.REQUIRED_ROW_MISSING,
|
||||
throw new ShardDocValidationHarnessError({
|
||||
code: SHARD_DOC_VALIDATION_ERROR_CODES.REQUIRED_ROW_MISSING,
|
||||
detail: 'Required row identity field is empty',
|
||||
artifactId: artifact.artifactId,
|
||||
fieldPath: `rows[${rowIndex}].${requiredField}`,
|
||||
|
|
@ -611,8 +611,8 @@ class Wave2ValidationHarness {
|
|||
} else if (artifact.type === 'yaml') {
|
||||
const parsed = yaml.parse(await fs.readFile(artifactPath, 'utf8'));
|
||||
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
||||
throw new Wave2ValidationHarnessError({
|
||||
code: WAVE2_VALIDATION_ERROR_CODES.YAML_SCHEMA_MISMATCH,
|
||||
throw new ShardDocValidationHarnessError({
|
||||
code: SHARD_DOC_VALIDATION_ERROR_CODES.YAML_SCHEMA_MISMATCH,
|
||||
detail: 'YAML artifact root must be a mapping object',
|
||||
artifactId: artifact.artifactId,
|
||||
fieldPath: '<document>',
|
||||
|
|
@ -623,8 +623,8 @@ class Wave2ValidationHarness {
|
|||
}
|
||||
for (const key of artifact.requiredTopLevelKeys || []) {
|
||||
if (!Object.prototype.hasOwnProperty.call(parsed, key)) {
|
||||
throw new Wave2ValidationHarnessError({
|
||||
code: WAVE2_VALIDATION_ERROR_CODES.YAML_SCHEMA_MISMATCH,
|
||||
throw new ShardDocValidationHarnessError({
|
||||
code: SHARD_DOC_VALIDATION_ERROR_CODES.YAML_SCHEMA_MISMATCH,
|
||||
detail: 'Required YAML key is missing',
|
||||
artifactId: artifact.artifactId,
|
||||
fieldPath: key,
|
||||
|
|
@ -664,8 +664,8 @@ class Wave2ValidationHarness {
|
|||
|
||||
const inventoryRows = artifactDataById.get(8)?.rows || [];
|
||||
if (inventoryRows.length !== this.registry.length) {
|
||||
throw new Wave2ValidationHarnessError({
|
||||
code: WAVE2_VALIDATION_ERROR_CODES.REQUIRED_ROW_MISSING,
|
||||
throw new ShardDocValidationHarnessError({
|
||||
code: SHARD_DOC_VALIDATION_ERROR_CODES.REQUIRED_ROW_MISSING,
|
||||
detail: 'Artifact inventory must include one row per required artifact',
|
||||
artifactId: 8,
|
||||
fieldPath: 'rows',
|
||||
|
|
@ -699,8 +699,8 @@ class Wave2ValidationHarness {
|
|||
Number.isFinite(observedRowCount) &&
|
||||
(expectedInventoryRowCount === null ? observedRowCount >= 1 : observedRowCount === expectedInventoryRowCount);
|
||||
if (!rowCountIsValid) {
|
||||
throw new Wave2ValidationHarnessError({
|
||||
code: WAVE2_VALIDATION_ERROR_CODES.REQUIRED_ROW_MISSING,
|
||||
throw new ShardDocValidationHarnessError({
|
||||
code: SHARD_DOC_VALIDATION_ERROR_CODES.REQUIRED_ROW_MISSING,
|
||||
detail: 'Artifact inventory rowCount does not satisfy deterministic contract',
|
||||
artifactId: 8,
|
||||
fieldPath: `rows[artifactId=${artifact.artifactId}].rowCount`,
|
||||
|
|
@ -729,8 +729,8 @@ class Wave2ValidationHarness {
|
|||
}
|
||||
|
||||
module.exports = {
|
||||
WAVE2_VALIDATION_ERROR_CODES,
|
||||
WAVE2_VALIDATION_ARTIFACT_REGISTRY,
|
||||
Wave2ValidationHarnessError,
|
||||
Wave2ValidationHarness,
|
||||
SHARD_DOC_VALIDATION_ERROR_CODES,
|
||||
SHARD_DOC_VALIDATION_ARTIFACT_REGISTRY,
|
||||
ShardDocValidationHarnessError,
|
||||
ShardDocValidationHarness,
|
||||
};
|
||||
|
|
@ -230,9 +230,9 @@ function validateHelpSidecarContractData(sidecarData, options = {}) {
|
|||
missingDependenciesDetail: 'Exemplar sidecar requires an explicit dependencies block.',
|
||||
dependenciesObjectDetail: 'Exemplar sidecar requires an explicit dependencies object.',
|
||||
dependenciesRequiresArrayDetail: 'Exemplar dependencies.requires must be an array.',
|
||||
dependenciesRequiresNotEmptyDetail: 'Wave-1 exemplar requires explicit zero dependencies: dependencies.requires must be [].',
|
||||
artifactTypeDetail: 'Wave-1 exemplar requires artifactType to equal "task".',
|
||||
moduleDetail: 'Wave-1 exemplar requires module to equal "core".',
|
||||
dependenciesRequiresNotEmptyDetail: 'help exemplar requires explicit zero dependencies: dependencies.requires must be [].',
|
||||
artifactTypeDetail: 'help exemplar requires artifactType to equal "task".',
|
||||
moduleDetail: 'help exemplar requires module to equal "core".',
|
||||
requiresMustBeEmpty: true,
|
||||
});
|
||||
}
|
||||
|
|
@ -250,9 +250,9 @@ function validateShardDocSidecarContractData(sidecarData, options = {}) {
|
|||
missingDependenciesDetail: 'Shard-doc sidecar requires an explicit dependencies block.',
|
||||
dependenciesObjectDetail: 'Shard-doc sidecar requires an explicit dependencies object.',
|
||||
dependenciesRequiresArrayDetail: 'Shard-doc dependencies.requires must be an array.',
|
||||
dependenciesRequiresNotEmptyDetail: 'Wave-2 shard-doc contract requires explicit zero dependencies: dependencies.requires must be [].',
|
||||
artifactTypeDetail: 'Wave-2 shard-doc contract requires artifactType to equal "task".',
|
||||
moduleDetail: 'Wave-2 shard-doc contract requires module to equal "core".',
|
||||
dependenciesRequiresNotEmptyDetail: 'Shard-doc contract requires explicit zero dependencies: dependencies.requires must be [].',
|
||||
artifactTypeDetail: 'Shard-doc contract requires artifactType to equal "task".',
|
||||
moduleDetail: 'Shard-doc contract requires module to equal "core".',
|
||||
requiresMustBeEmpty: true,
|
||||
});
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue