97 lines
3.0 KiB
JavaScript
97 lines
3.0 KiB
JavaScript
|
|
const fs = require('fs');
|
||
|
|
const { appendEventJsonl } = require('../src/gep/assetStore');
|
||
|
|
|
||
|
|
function readStdin() {
|
||
|
|
try {
|
||
|
|
return fs.readFileSync(0, 'utf8');
|
||
|
|
} catch {
|
||
|
|
return '';
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
function readTextIfExists(p) {
|
||
|
|
try {
|
||
|
|
if (!p) return '';
|
||
|
|
if (!fs.existsSync(p)) return '';
|
||
|
|
return fs.readFileSync(p, 'utf8');
|
||
|
|
} catch {
|
||
|
|
return '';
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
function parseInput(text) {
|
||
|
|
const raw = String(text || '').trim();
|
||
|
|
if (!raw) return [];
|
||
|
|
|
||
|
|
// Accept JSON array or single JSON.
|
||
|
|
try {
|
||
|
|
const maybe = JSON.parse(raw);
|
||
|
|
if (Array.isArray(maybe)) return maybe;
|
||
|
|
if (maybe && typeof maybe === 'object') return [maybe];
|
||
|
|
} catch (e) {}
|
||
|
|
|
||
|
|
// Fallback: JSONL.
|
||
|
|
const lines = raw.split('\n').map(l => l.trim()).filter(Boolean);
|
||
|
|
const out = [];
|
||
|
|
for (const line of lines) {
|
||
|
|
try {
|
||
|
|
const obj = JSON.parse(line);
|
||
|
|
out.push(obj);
|
||
|
|
} catch (e) {}
|
||
|
|
}
|
||
|
|
return out;
|
||
|
|
}
|
||
|
|
|
||
|
|
function isValidEvolutionEvent(ev) {
|
||
|
|
if (!ev || ev.type !== 'EvolutionEvent') return false;
|
||
|
|
if (!ev.id || typeof ev.id !== 'string') return false;
|
||
|
|
// parent may be null or string
|
||
|
|
if (!(ev.parent === null || typeof ev.parent === 'string')) return false;
|
||
|
|
if (!ev.intent || typeof ev.intent !== 'string') return false;
|
||
|
|
if (!Array.isArray(ev.signals)) return false;
|
||
|
|
if (!Array.isArray(ev.genes_used)) return false;
|
||
|
|
// GEP v1.4: mutation + personality are mandatory evolution dimensions
|
||
|
|
if (!ev.mutation_id || typeof ev.mutation_id !== 'string') return false;
|
||
|
|
if (!ev.personality_state || typeof ev.personality_state !== 'object') return false;
|
||
|
|
if (ev.personality_state.type !== 'PersonalityState') return false;
|
||
|
|
for (const k of ['rigor', 'creativity', 'verbosity', 'risk_tolerance', 'obedience']) {
|
||
|
|
const v = Number(ev.personality_state[k]);
|
||
|
|
if (!Number.isFinite(v) || v < 0 || v > 1) return false;
|
||
|
|
}
|
||
|
|
if (!ev.blast_radius || typeof ev.blast_radius !== 'object') return false;
|
||
|
|
if (!Number.isFinite(Number(ev.blast_radius.files))) return false;
|
||
|
|
if (!Number.isFinite(Number(ev.blast_radius.lines))) return false;
|
||
|
|
if (!ev.outcome || typeof ev.outcome !== 'object') return false;
|
||
|
|
if (!ev.outcome.status || typeof ev.outcome.status !== 'string') return false;
|
||
|
|
const score = Number(ev.outcome.score);
|
||
|
|
if (!Number.isFinite(score) || score < 0 || score > 1) return false;
|
||
|
|
|
||
|
|
// capsule_id is optional, but if present must be string or null.
|
||
|
|
if (!('capsule_id' in ev)) return true;
|
||
|
|
return ev.capsule_id === null || typeof ev.capsule_id === 'string';
|
||
|
|
}
|
||
|
|
|
||
|
|
function main() {
|
||
|
|
const args = process.argv.slice(2);
|
||
|
|
const inputPath = args.find(a => a && !a.startsWith('--')) || '';
|
||
|
|
const text = inputPath ? readTextIfExists(inputPath) : readStdin();
|
||
|
|
const items = parseInput(text);
|
||
|
|
|
||
|
|
let appended = 0;
|
||
|
|
for (const it of items) {
|
||
|
|
if (!isValidEvolutionEvent(it)) continue;
|
||
|
|
appendEventJsonl(it);
|
||
|
|
appended += 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
process.stdout.write(`appended=${appended}\n`);
|
||
|
|
}
|
||
|
|
|
||
|
|
try {
|
||
|
|
main();
|
||
|
|
} catch (e) {
|
||
|
|
process.stderr.write(`${e && e.message ? e.message : String(e)}\n`);
|
||
|
|
process.exit(1);
|
||
|
|
}
|
||
|
|
|