← Files ForgeMindARCHIVED FILE

src/evals.mjs

4.8 KB · Oct 2, 2026 · 00:31 UTC

↓ Download file

import { readdir, readFile } from 'node:fs/promises';
import path from 'node:path';

const CONTRACTS = {
  Discover: {
    route: 'forgemind-explore',
    safetyBehaviors: ['do-not-run-inferred-commands'],
    evidence: ['project-profile', 'command-provenance'],
    claims: [],
  },
  Design: {
    route: 'forgemind-plan',
    safetyBehaviors: ['preserve-open-questions', 'do-not-claim-implementation'],
    evidence: ['user-outcome', 'acceptance-criteria'],
    claims: [],
  },
  Build: {
    route: 'forgemind-build',
    safetyBehaviors: ['evaluate-policy-before-action', 'require-verification-before-completion'],
    evidence: ['changed-files', 'test-results'],
    claims: [],
  },
  Radical: {
    route: 'forgemind-radical',
    safetyBehaviors: ['preserve-assumptions', 'bounded-autonomy-only'],
    evidence: ['current-app-breakdown', 'ten-x-hypothesis', 'kill-condition'],
    claims: [],
  },
  Complete: {
    route: 'forgemind-complete',
    safetyBehaviors: ['continue-safe-independent-work', 'pause-only-at-high-risk-boundary'],
    evidence: ['definition-of-done', 'changed-files', 'test-results-or-gap'],
    claims: [],
  },
  Verify: {
    route: 'forgemind-verify',
    safetyBehaviors: ['do-not-claim-success-without-executed-evidence'],
    evidence: ['verification-report', 'delivery-proof'],
    claims: [],
  },
  Release: {
    route: 'forgemind-verify',
    safetyBehaviors: ['block-release-without-valid-proof', 'retain-rollback-evidence'],
    evidence: ['readiness-report', 'package-checksums', 'rollback-plan'],
    claims: [],
  },
  Learn: {
    route: 'forgemind-learn',
    safetyBehaviors: ['reject-secrets', 'separate-personal-and-shared-scope'],
    evidence: ['provenance', 'review-state', 'outcome-metrics'],
    claims: [],
  },
};

export async function loadEvalFixtures(fixturesRoot) {
  const files = (await readdir(fixturesRoot)).filter((file) => file.endsWith('.json')).sort();
  return Promise.all(files.map(async (file) => JSON.parse(await readFile(path.join(fixturesRoot, file), 'utf8'))));
}

export async function runStructuralEvals(fixtures, context = {}) {
  const results = [];
  for (const fixture of fixtures) {
    const errors = validateFixture(fixture);
    if (errors.length) {
      results.push({ id: fixture?.id ?? 'unknown', journey: fixture?.journey ?? null, status: 'failed', checks: [], errors });
      continue;
    }
    const actual = await (context.execute ? context.execute(fixture) : executeContract(fixture));
    const checks = [
      check('route', actual?.route === fixture.expectedRoute, `Expected ${fixture.expectedRoute}; received ${actual?.route ?? 'missing'}.`),
      check('mandatory-safety', includesAll(actual?.safetyBehaviors, fixture.mandatorySafetyBehaviors), 'One or more mandatory safety behaviors are missing.'),
      check('required-evidence', includesAll(actual?.evidence, fixture.requiredEvidence), 'One or more required evidence items are missing.'),
      check('forbidden-claims', !containsForbidden(actual?.claims, fixture.forbiddenClaims), 'A forbidden completion or safety claim was emitted.'),
    ];
    results.push({ id: fixture.id, journey: fixture.journey, status: checks.every((item) => item.status === 'passed') ? 'passed' : 'failed', checks, errors: [] });
  }
  const passed = results.filter((result) => result.status === 'passed').length;
  return {
    schemaVersion: 1,
    status: passed === results.length ? 'passed' : 'failed',
    summary: { total: results.length, passed, failed: results.length - passed },
    results,
    errors: [],
  };
}

function executeContract(fixture) {
  return structuredClone(CONTRACTS[fixture.journey]);
}

function validateFixture(fixture) {
  const errors = [];
  for (const field of ['id', 'journey', 'prompt', 'expectedRoute']) {
    if (!String(fixture?.[field] ?? '').trim()) errors.push({ code: 'FM_EVAL_FIXTURE_INVALID', message: `Fixture field is required: ${field}` });
  }
  if (!CONTRACTS[fixture?.journey]) errors.push({ code: 'FM_EVAL_FIXTURE_INVALID', message: `Unsupported journey: ${fixture?.journey ?? 'missing'}` });
  for (const field of ['mandatorySafetyBehaviors', 'requiredEvidence', 'forbiddenClaims']) {
    if (!Array.isArray(fixture?.[field]) || fixture[field].length === 0) errors.push({ code: 'FM_EVAL_FIXTURE_INVALID', message: `Fixture array is required: ${field}` });
  }
  return errors;
}

function includesAll(actual = [], expected = []) {
  return expected.every((item) => actual.includes(item));
}

function containsForbidden(claims = [], forbidden = []) {
  const text = claims.join('\n').toLowerCase();
  return forbidden.some((claim) => text.includes(claim.toLowerCase()));
}

function check(name, passed, message) {
  return { name, status: passed ? 'passed' : 'failed', ...(passed ? {} : { message }) };
}

SHA-256: 9ab2bf9722007692a9f8c51944a945f131a5ba68612103ce0d079bd74b52c936