// Step 1 test — validates plugin-map.json shape (rename-awareness, HTTPS URLs, blob/vendor flags). // Pattern: plugins/voyage/tests/validators/*.test.mjs (node:test style, zero deps). import test from 'node:test'; import assert from 'node:assert/strict'; import { readFileSync } from 'node:fs'; import { fileURLToPath } from 'node:url'; import { dirname, join } from 'node:path'; const here = dirname(fileURLToPath(import.meta.url)); const map = JSON.parse(readFileSync(join(here, 'plugin-map.json'), 'utf8')); const targets = map.targets; const keys = Object.keys(targets); // voyage ← ultraplan-local and linkedin-studio ← linkedin-thought-leadership are TRUE renames, so each // carries >=2 --path entries (F1). llm-security is NOT a rename: llm-security-copilot was a SEPARATE, // COEXISTING plugin, so llm-security is SINGLE-path (corrected 2026-06-17, commit 836b8e9) — a dual --path // there collided at the coexistence commits and dropped 87 files. The map is authoritative; this suite // tracks it. const RENAMED = ['voyage', 'linkedin-studio']; test('plugin-map.json parses and has exactly 11 targets (10 plugins + DS)', () => { assert.equal(keys.length, 11, `expected 11 targets, found ${keys.length}: ${keys.join(', ')}`); }); test('the renamed plugins (voyage, linkedin-studio) each carry >=2 --path entries (F1)', () => { for (const k of RENAMED) { assert.ok(targets[k], `missing renamed target ${k}`); assert.ok( Array.isArray(targets[k].paths) && targets[k].paths.length >= 2, `${k} must carry >=2 paths (both names), has ${targets[k].paths?.length}` ); } }); test('llm-security is SINGLE-path — NOT a rename (copilot was a coexisting plugin, 836b8e9)', () => { // Locks in the 87-file-drop correction: re-introducing a 2nd --path here would re-collide at the // coexistence commits and silently drop files again. assert.equal( targets['llm-security'].paths.length, 1, `llm-security must stay single-path (dual-path dropped 87 files), has ${targets['llm-security'].paths.length}` ); }); test('every target repo_url is https:// (not ssh://) — F2 / #9740', () => { for (const k of keys) { assert.match(targets[k].repo_url, /^https:\/\/git\.fromaitochitta\.com\/open\//, `${k} repo_url not the expected HTTPS form`); assert.doesNotMatch(targets[k].repo_url, /^ssh:\/\//, `${k} repo_url must not be ssh://`); } }); test('ms-ai-architect has blob_strip: true (F3)', () => { assert.equal(targets['ms-ai-architect'].blob_strip, true); }); test('only llm-security + ms-ai-architect have has_vendor_ds: true', () => { const vendor = keys.filter((k) => targets[k].has_vendor_ds === true).sort(); assert.deepEqual(vendor, ['llm-security', 'ms-ai-architect']); }); test('every target tag is v (F5 single clean re-tag per repo)', () => { for (const k of keys) { assert.match(targets[k].tag, /^v\d+\.\d+\.\d+$/, `${k} tag '${targets[k].tag}' is not v`); } }); test('the removed plugin is dropped, not extracted', () => { assert.ok(Array.isArray(map.drop) && map.drop.includes('plugins/ultra-cc-architect/')); assert.ok(!keys.includes('ultra-cc-architect')); });