// tests/lib/removed-surfaces.test.mjs // Cleanup R-b removed three surfaces: Voyage's own measurement, statistics and // export (R1, decision 189; Claude Code's own OpenTelemetry took over), the // STORM loop and the research engine choice (R2, decision 190), and the // profiles, phase signals and effort dialog (R3, decision 191). No tracked // file may name them again, so a removed surface cannot come back unseen. // // Scope: every tracked text file except CHANGELOG.md (history), *.local.* // files (never tracked) and this test. A term is a plain substring, except a // flag, which must stand as its own token (`--engine`, `--profile`). // // Every group always runs. When this test fails, remove the mention; add an // EXEMPT entry only for a line that must keep the term, with the reason. import { test } from 'node:test'; import { strict as assert } from 'node:assert'; import { execFileSync } from 'node:child_process'; import { mkdtempSync, mkdirSync, writeFileSync, readFileSync, rmSync } from 'node:fs'; import { join, dirname } from 'node:path'; import { tmpdir } from 'node:os'; import { fileURLToPath } from 'node:url'; const HERE = dirname(fileURLToPath(import.meta.url)); const ROOT = join(HERE, '..', '..'); const SELF = 'tests/lib/removed-surfaces.test.mjs'; const flag = name => new RegExp(`(? reason. Each must still hit (a stale // exemption fails below). const EXEMPT = new Map([ ['docs/HANDOVER-CONTRACTS.md:81', 'public contract: an older brief\'s key is ignored (decisions 179/191)'], ['docs/proevesett.md:139', 'dated v1/v2 measurement record of the fixed test set, kept unedited'], ['docs/proevesett.md:143', 'dated v1/v2 measurement record of the fixed test set, kept unedited'], ['docs/proevesett.md:146', 'dated v1/v2 measurement record of the fixed test set, kept unedited'], ['commands/trekbrief.md:34', 'a command strips the retired flag where it parses its arguments'], ['commands/trekexecute.md:36', 'a command strips the retired flag where it parses its arguments'], ['commands/trekplan.md:31', 'a command strips the retired flag where it parses its arguments'], ['commands/trekresearch.md:29', 'a command strips the retired flag where it parses its arguments'], ['lib/parsers/arg-parser.mjs:19', 'retired flag swallows its value'], ['lib/parsers/arg-parser.mjs:23', 'retired flag swallows its value'], ['tests/lib/arg-parser.test.mjs:285', 'retired flag swallows its value'], ['tests/lib/arg-parser.test.mjs:286', 'retired flag swallows its value'], ['tests/lib/arg-parser.test.mjs:289', 'retired flag swallows its value'], ['tests/lib/arg-parser.test.mjs:291', 'retired flag swallows its value'], ['tests/lib/arg-parser.test.mjs:292', 'retired flag swallows its value'], ['tests/lib/arg-parser.test.mjs:293', 'retired flag swallows its value'], ['tests/lib/arg-parser.test.mjs:294', 'retired flag swallows its value'], ['tests/lib/arg-parser.test.mjs:295', 'retired flag swallows its value'], ['tests/validators/brief-validator.test.mjs:200', 'older brief stays valid (decision 191)'], ['tests/validators/brief-validator.test.mjs:203', 'older brief stays valid (decision 191)'], ['tests/validators/brief-validator.test.mjs:205', 'older brief stays valid (decision 191)'], ]); const matches = (term, line) => (typeof term === 'string' ? line.includes(term) : term.test(line)); // Each file is read and split once for all groups; a binary file has no lines. const lineCache = new Map(); function linesOf(root, rel) { const path = join(root, rel); if (!lineCache.has(path)) { const buf = readFileSync(path); lineCache.set(path, buf.includes(0) ? [] : buf.toString('utf-8').split('\n')); } return lineCache.get(path); } // "path:line" for every line of the given files that names a term of the group. function scan(root, files, terms) { const hits = []; for (const rel of files) { linesOf(root, rel).forEach((line, i) => { if (terms.some(t => matches(t, line))) hits.push(`${rel}:${i + 1}`); }); } return hits; } function inScope(rel) { return rel !== 'CHANGELOG.md' && rel !== SELF && !/\.local\./.test(rel); } let files = null; function tracked() { if (!files) { files = execFileSync('git', ['ls-files', '-z'], { cwd: ROOT, encoding: 'utf-8' }) .split('\0').filter(Boolean).filter(inScope); } return files; } test('scan finds a term, and a flag only as its own token (known-positive)', () => { const dir = mkdtempSync(join(tmpdir(), 'removed-surfaces-')); try { mkdirSync(join(dir, 'docs'), { recursive: true }); writeFileSync(join(dir, 'docs', 'a.md'), [ 'Set VOYAGE_STORM_ENABLED=1 first.', 'Run with --engine deep-research.', 'The engine-findings compare stays; so does --engines-list.', 'Pass `--profile economy`.', ].join('\n')); assert.deepEqual(scan(dir, ['docs/a.md'], GROUPS.R2), ['docs/a.md:1', 'docs/a.md:2']); assert.deepEqual(scan(dir, ['docs/a.md'], GROUPS.R3), ['docs/a.md:4']); } finally { rmSync(dir, { recursive: true, force: true }); } }); test('scope leaves out history, local files and this test', () => { assert.ok(inScope('commands/trekresearch.md')); assert.ok(inScope('lib/validators/brief-validator.mjs')); assert.ok(!inScope('CHANGELOG.md')); assert.ok(!inScope('docs/notes.local.md')); assert.ok(!inScope(SELF)); }); for (const group of Object.keys(GROUPS)) { test(`${group}: no tracked file names a removed surface`, () => { const all = tracked(); assert.ok(all.length > 100, `git ls-files returned ${all.length} files; refusing to pass on an empty scan`); const hits = scan(ROOT, all, GROUPS[group]).filter(h => !EXEMPT.has(h)); assert.deepEqual(hits, [], `${hits.length} line(s) still name a ${group} surface:\n${hits.join('\n')}`); }); } test('every EXEMPT entry still names a removed surface', () => { const terms = Object.values(GROUPS).flat(); const all = new Set(tracked()); for (const key of EXEMPT.keys()) { const rel = key.replace(/:\d+$/, ''); assert.ok(all.has(rel), `EXEMPT names untracked ${rel}`); assert.ok(scan(ROOT, [rel], terms).includes(key), `EXEMPT entry "${key}" no longer hits; remove it`); } });