3 new test files, 24 cases (8 per validator): - baseline (no annotation fields) still valid - revision: 0 / revision: 5 accepted - source_annotations list-of-dict accepted - annotation_digest string accepted - revision_reason accepted - all 4 fields together accepted - unrecognized future field tolerated (forward-compat policy) Pin against future strict-key refactors. No production code change — pure regression pin.
79 lines
3.2 KiB
JavaScript
79 lines
3.2 KiB
JavaScript
// tests/validators/plan-validator-annotation-fields.test.mjs
|
|
// Pin forward-compat for v4.2 annotation frontmatter fields on plan.md.
|
|
// Adding revision/source_annotations/annotation_digest/revision_reason must NOT
|
|
// trigger PLAN_UNKNOWN_FIELD or similar — validator is purely additive-tolerant
|
|
// per source_findings precedent. No code change required; this test pins the policy.
|
|
|
|
import { test } from 'node:test';
|
|
import { strict as assert } from 'node:assert';
|
|
import { validatePlanContent } from '../../lib/validators/plan-validator.mjs';
|
|
|
|
const STEP_BLOCK = `### Step 1: Do thing
|
|
|
|
- Files: a.ts
|
|
- Manifest:
|
|
\`\`\`yaml
|
|
manifest:
|
|
expected_paths:
|
|
- a.ts
|
|
min_file_count: 1
|
|
commit_message_pattern: "^feat:"
|
|
bash_syntax_check: []
|
|
forbidden_paths: []
|
|
must_contain: []
|
|
\`\`\`
|
|
`;
|
|
|
|
const baseFm = (extra = '') => `---
|
|
plan_version: "1.7"
|
|
profile: balanced${extra}
|
|
---
|
|
|
|
# Plan
|
|
|
|
## Implementation Plan
|
|
|
|
${STEP_BLOCK}
|
|
`;
|
|
|
|
test('plan-validator forward-compat — baseline (no annotation fields) still valid', () => {
|
|
const r = validatePlanContent(baseFm(), { strict: true });
|
|
assert.equal(r.valid, true, JSON.stringify(r.errors));
|
|
});
|
|
|
|
test('plan-validator forward-compat — accepts revision: 0', () => {
|
|
const r = validatePlanContent(baseFm('\nrevision: 0'), { strict: true });
|
|
assert.equal(r.valid, true, JSON.stringify(r.errors));
|
|
});
|
|
|
|
test('plan-validator forward-compat — accepts revision: 5', () => {
|
|
const r = validatePlanContent(baseFm('\nrevision: 5'), { strict: true });
|
|
assert.equal(r.valid, true, JSON.stringify(r.errors));
|
|
});
|
|
|
|
test('plan-validator forward-compat — accepts source_annotations list-of-dict', () => {
|
|
const inject = `\nrevision: 1\nsource_annotations:\n - id: ANN-0001\n target_artifact: plan.md\n target_anchor: step-3\n intent: change\n comment: "reorder ahead of step 4"\n timestamp: "2026-05-09T10:00:00Z"`;
|
|
const r = validatePlanContent(baseFm(inject), { strict: true });
|
|
assert.equal(r.valid, true, JSON.stringify(r.errors));
|
|
});
|
|
|
|
test('plan-validator forward-compat — accepts annotation_digest string', () => {
|
|
const r = validatePlanContent(baseFm('\nrevision: 1\nannotation_digest: 0123456789abcdef'), { strict: true });
|
|
assert.equal(r.valid, true, JSON.stringify(r.errors));
|
|
});
|
|
|
|
test('plan-validator forward-compat — accepts revision_reason', () => {
|
|
const r = validatePlanContent(baseFm('\nrevision: 2\nrevision_reason: "structural step reorder"'), { strict: true });
|
|
assert.equal(r.valid, true, JSON.stringify(r.errors));
|
|
});
|
|
|
|
test('plan-validator forward-compat — all 4 fields together with source_findings', () => {
|
|
const inject = `\nrevision: 3\nrevision_reason: "applied 5 annotations"\nannotation_digest: abc1234567890def\nsource_annotations:\n - id: ANN-0001\n target_artifact: plan.md\n target_anchor: step-3\n intent: change\nsource_findings:\n - 0123456789abcdef0123456789abcdef01234567`;
|
|
const r = validatePlanContent(baseFm(inject), { strict: true });
|
|
assert.equal(r.valid, true, JSON.stringify(r.errors));
|
|
});
|
|
|
|
test('plan-validator forward-compat — unrecognized future field tolerated', () => {
|
|
const r = validatePlanContent(baseFm('\nfuture_v4_3_key: "any"'), { strict: true });
|
|
assert.equal(r.valid, true, JSON.stringify(r.errors));
|
|
});
|