Mosaic concepts pages now own the adapted content; source/license metadata under docs/reference/concepts. Adds ACT-1 agent-context planning capture, pinned concept test package + preparation utility, foundation observation notes (durability, evidence, federation, onboarding, workflow), and the #1495 consolidation assessment. TOOLS.md updated for the host-dev launcher.
89 lines
5.3 KiB
JavaScript
89 lines
5.3 KiB
JavaScript
#!/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;
|
|
}
|