// tests/integration/annotation-roundtrip.test.mjs // SC2 + SC3 + SC7 integration tests for the annotation round-trip pipeline. // // SC2 (byte-identical empty round-trip): // For each target fixture (brief/plan/review), assert that // stripAnchors(addAnchors(body, [])) === body, byte-for-byte. // // SC3 (scale: >=50 steps + >=100 anchors): // On the 51-step scale fixture, generate 100 anchors above varied lines, // run addAnchors -> stripAnchors, assert the original body is restored // byte-for-byte. // // SC7 (per-target isolation): // parseAnchors(stripAnchors(addAnchors(body, anchors))) === [] — once // anchors are stripped, no residual voyage:anchor markers remain that // parseAnchors would re-detect. import { test } from 'node:test'; import { strict as assert } from 'node:assert'; import { readFileSync } from 'node:fs'; import { join, dirname } from 'node:path'; import { fileURLToPath } from 'node:url'; import { parseDocument } from '../../lib/util/frontmatter.mjs'; import { parseAnchors, addAnchors, stripAnchors } from '../../lib/parsers/anchor-parser.mjs'; const HERE = dirname(fileURLToPath(import.meta.url)); const ROOT = join(HERE, '..', '..'); const FIX_DIR = join(ROOT, 'tests/fixtures/annotation'); function readBody(fixture) { const text = readFileSync(join(FIX_DIR, fixture), 'utf-8'); const doc = parseDocument(text); assert.ok(doc.valid, `fixture ${fixture} did not parse: ${(doc.errors || []).map(e => e.message).join(', ')}`); return doc.parsed.body; } test('annotation-brief.md byte-identical empty round-trip (SC2)', () => { const body = readBody('annotation-brief.md'); const out = stripAnchors(addAnchors(body, [])); assert.strictEqual(out, body, 'empty addAnchors+stripAnchors must be byte-identical'); }); test('annotation-plan.md byte-identical empty round-trip (SC2)', () => { const body = readBody('annotation-plan.md'); const out = stripAnchors(addAnchors(body, [])); assert.strictEqual(out, body, 'empty addAnchors+stripAnchors must be byte-identical'); }); test('annotation-review.md byte-identical empty round-trip (SC2)', () => { const body = readBody('annotation-review.md'); const out = stripAnchors(addAnchors(body, [])); assert.strictEqual(out, body, 'empty addAnchors+stripAnchors must be byte-identical'); }); test('annotation-plan-large.md scale (51 steps + 100 anchors) round-trip (SC3)', () => { const body = readBody('annotation-plan-large.md'); const lineCount = body.split('\n').length; // Generate 100 anchors targeting safe paragraph lines. Place them above // line numbers that are deliberately avoided by anchor-parser placement // rules: skip anchor insertion above headings and inside fenced blocks. // Strategy: pick 100 safe insertion points by walking blank lines outside // fenced blocks; anchor at line N inserts above line N (so line N must // be a content line, not a fence delimiter). const lines = body.split('\n'); const safe = []; let inFence = false; for (let i = 0; i < lines.length; i++) { const ln = lines[i]; if (/^```/.test(ln)) { inFence = !inFence; continue; } if (inFence) continue; // Skip headings, blank lines, list items, and structural anchors if (ln.startsWith('#') || ln.trim() === '' || /^\s*[-*+]\s/.test(ln)) continue; safe.push(i + 1); // 1-indexed line number } assert.ok(safe.length >= 100, `need >=100 safe insertion points; got ${safe.length}`); const anchors = []; for (let n = 0; n < 100; n++) { anchors.push({ id: `ANN-${String(n + 1).padStart(4, '0')}`, target: `step-${(n % 51) + 1}`, line: safe[n], intent: ['fix', 'change', 'question', 'block'][n % 4], }); } const annotated = addAnchors(body, anchors); // sanity: 100 anchors produced const parsed = parseAnchors(annotated); assert.ok(parsed.valid, `parseAnchors on annotated body failed: ${(parsed.errors || []).map(e => e.message).join('; ')}`); assert.strictEqual(parsed.parsed.length, 100, `expected 100 anchors after addAnchors, got ${parsed.parsed.length}`); // Round-trip restores body byte-for-byte. const restored = stripAnchors(annotated); assert.strictEqual(restored, body, 'addAnchors -> stripAnchors must round-trip byte-identical at scale'); }); test('parseAnchors(stripAnchors(addAnchors(brief, anchors))) === [] (SC7 brief)', () => { const body = readBody('annotation-brief.md'); const lines = body.split('\n'); // Pick a content line — first non-blank, non-heading line const target = lines.findIndex(l => l.length > 0 && !l.startsWith('#')) + 1; assert.ok(target > 0, 'brief fixture has no content lines'); const anchors = [{ id: 'ANN-0001', target: 'intent', line: target, intent: 'change' }]; const annotated = addAnchors(body, anchors); const stripped = stripAnchors(annotated); const result = parseAnchors(stripped); assert.ok(result.valid, 'parseAnchors on stripped body should be valid'); assert.deepStrictEqual(result.parsed, [], 'no anchors should remain after stripAnchors'); }); test('parseAnchors(stripAnchors(addAnchors(plan, anchors))) === [] (SC7 plan)', () => { const body = readBody('annotation-plan.md'); const lines = body.split('\n'); const target = lines.findIndex(l => l.startsWith('A minimal')) + 1; assert.ok(target > 0, 'plan fixture missing expected content line'); const anchors = [{ id: 'ANN-0001', target: 'context', line: target, intent: 'fix' }]; const annotated = addAnchors(body, anchors); const stripped = stripAnchors(annotated); const result = parseAnchors(stripped); assert.ok(result.valid); assert.deepStrictEqual(result.parsed, []); }); test('parseAnchors(stripAnchors(addAnchors(review, anchors))) === [] (SC7 review)', () => { const body = readBody('annotation-review.md'); const lines = body.split('\n'); const target = lines.findIndex(l => l.startsWith('Verdict')) + 1; assert.ok(target > 0, 'review fixture missing Verdict line'); const anchors = [{ id: 'ANN-0001', target: 'executive-summary', line: target, intent: 'question' }]; const annotated = addAnchors(body, anchors); const stripped = stripAnchors(annotated); const result = parseAnchors(stripped); assert.ok(result.valid); assert.deepStrictEqual(result.parsed, []); });