#!/usr/bin/env node // Validate or stage synthetic ACT-1 review inputs. Never launches an agent. import { readFileSync, readdirSync, lstatSync, mkdtempSync, mkdirSync, cpSync, writeFileSync, chmodSync } from 'node:fs'; import { createHash } from 'node:crypto'; import { tmpdir } from 'node:os'; import { resolve, join } from 'node:path'; const root = resolve(import.meta.dirname, '..'); const pack = join(root, 'docs/plans/act-1-tests'); const refs = join(root, 'docs/reference/concepts'); const concepts = join(root, 'docs/concepts'); const hash = bytes => createHash('sha256').update(bytes).digest('hex'); const readJson = file => JSON.parse(readFileSync(file, 'utf8')); function requireTrue(value, message) { if (!value) throw new Error(message); } function regularTree(dir) { for (const entry of readdirSync(dir)) { const file = join(dir, entry), stat = lstatSync(file); requireTrue(!stat.isSymbolicLink(), `symlink not allowed in test inputs: ${file}`); if (stat.isDirectory()) regularTree(file); else requireTrue(stat.isFile(), `not a regular input: ${file}`); } } try { requireTrue(process.argv.length <= 3 && (!process.argv[2] || process.argv[2] === '--check'), 'usage: node scripts/prepare-concept-tests.mjs [--check]'); regularTree(pack); regularTree(refs); regularTree(concepts); const source = readJson(join(refs, 'SOURCE.json')); requireTrue(source.schemaVersion === 2, 'unsupported concept manifest'); for (const entry of source.files) { requireTrue(/^[a-z0-9-]+\.md$/.test(entry.file) && entry.path === `docs/concepts/${entry.file}`, 'invalid concept path'); const bytes = readFileSync(join(concepts, entry.file)); requireTrue(bytes.length === entry.bytes && hash(bytes) === entry.sha256, `concept drift: ${entry.file}; update current content identity after reviewing the change`); } requireTrue(source.license.path === 'docs/reference/concepts/LICENSE', 'invalid license path'); const license = readFileSync(join(refs, 'LICENSE')); requireTrue(license.length === source.license.bytes && hash(license) === source.license.sha256, 'license drift'); const cases = readJson(join(pack, 'cases.json')); requireTrue(cases.schemaVersion === 1 && cases.automatedModelCalls === 0, 'invalid pack metadata'); requireTrue(cases.cases.length > 0, 'empty scenario catalog'); const ids = new Set(); for (const item of cases.cases) { requireTrue(/^ACT-C\d{2}$/.test(item.id) && !ids.has(item.id), `invalid/duplicate case: ${item.id}`); ids.add(item.id); requireTrue(item.kind === 'manual-agent' && typeof item.prompt === 'string' && item.prompt.trim(), `invalid case: ${item.id}`); requireTrue(Array.isArray(item.expected) && item.expected.length > 0 && item.expected.every(x => typeof x === 'string' && x.trim()), `missing rubric: ${item.id}`); requireTrue(source.files.some(entry => entry.file === item.concept), `missing reference: ${item.id}`); if (item.fixture !== null) { requireTrue(/^[a-z0-9-]+\.json$/.test(item.fixture), `invalid fixture: ${item.id}`); requireTrue(readJson(join(pack, 'fixtures', item.fixture)).synthetic === true, `non-synthetic fixture: ${item.id}`); } } const baseline = readFileSync(join(root, 'agents/darkwing/SOUL.md')); const candidate = readFileSync(join(pack, 'candidate/SOUL.md')); requireTrue(baseline.length > 0 && candidate.length > 0, 'empty personality input'); console.log(`Verified ${source.files.length} Mosaic concepts, license attribution and ${ids.size} synthetic scenarios; no model calls.`); if (process.argv[2] !== '--check') { const out = mkdtempSync(join(tmpdir(), 'mosaic-act1-')); chmodSync(out, 0o700); for (const dir of ['baseline', 'candidate']) mkdirSync(join(out, dir)); writeFileSync(join(out, 'baseline/SOUL.md'), baseline); writeFileSync(join(out, 'candidate/SOUL.md'), candidate); cpSync(join(pack, 'fixtures'), join(out, 'fixtures'), { recursive: true, errorOnExist: true, force: false }); cpSync(concepts, join(out, 'concepts'), { recursive: true, errorOnExist: true, force: false }); cpSync(refs, join(out, 'provenance'), { recursive: true, errorOnExist: true, force: false }); for (const file of ['cases.json', 'README.md']) cpSync(join(pack, file), join(out, file)); const results = { packId: cases.packId, preparedAt: new Date().toISOString(), upstreamRevision: source.sourceRevision, concepts: source.files.map(({ file, sha256 }) => ({ file, sha256 })), sourceManifestSha256: hash(readFileSync(join(refs, 'SOURCE.json'))), casesSha256: hash(readFileSync(join(pack, 'cases.json'))), variants: { baseline: hash(baseline), candidate: hash(candidate) }, operator: null, reviewer: null, modelBudget: null, environment: null, cases: cases.cases.flatMap(item => ['baseline', 'candidate'].map(variant => ({ id: item.id, variant, status: 'NOT_RUN', actualOutputFile: null, evidence: null, runtimeGate: item.runtimeGate ? 'DEFERRED' : 'NOT_APPLICABLE' }))) }; writeFileSync(join(out, 'RESULTS.json'), JSON.stringify(results, null, 2) + '\n', { mode: 0o600 }); console.log(`Prepared review workspace: ${out}\nResults: ${out}/RESULTS.json\nNo TUI, model, deployment, or session mutation performed.`); } } catch (error) { console.error(`concept-test preparation: ${error.message}`); process.exitCode = 1; }