Files
irl-dan 66fe7a85e6 ship: OpenProse learning example library (13 examples, offline gate)
Ships the 13 wired tier-1/tier-2 examples under skills/open-prose/examples
with a hermetic offline gate at zero model spend:

- skills/open-prose/examples/{agent-observatory, basic-unit-suite,
  forme-fixpoint, github-star-enricher, implementation-pipeline,
  inbox-triage, masked-relay, monorepo-ci, oblique-weave, renewal-risk,
  research-tree, surprise-cost, tamper-forge}: source .prose.md contracts,
  committed replay/ state-dirs, and co-located deterministic tier-2
  ledger-replay tests driving the real @openprose/reactor reconciler.
- tests/open-prose/examples/intelligent-react-examples-corpus.test.ts:
  tier-1 doc-conformance over the 13 OWNED examples (agent-observatory
  and forme-fixpoint normalized to lead each ### Continuity body with the
  canonical input-driven/self-driven token; gateways stay external-driven).
- tools/eval-harness/: deterministic offline eval checker (10/10).
- vitest.config.ts: aliases @openprose/reactor public subpaths to the
  prebuilt workspace dist; collects example *.test.ts; excludes key-gated
  *.live.test.ts so the gate stays hermetic.
- package.json: adds test:examples (REACTOR_OFFLINE=1 vitest + eval
  harness) and folds it into test.
- .github/workflows/ci-examples-gate.yml: runs the offline gate on
  example/test path changes (no judge key).
- prune: removes the superseded packages/std/examples/{composed-reviewer,
  confident-writer} .prose.md set.

Gates green: pnpm build clean; REACTOR_OFFLINE=1 pnpm test:examples =
17 vitest files / 214 tests + eval-harness 10/10; REACTOR_OFFLINE=1
pnpm test:skill = 14 files / 252 tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 14:16:32 -07:00

102 lines
3.5 KiB
JavaScript

#!/usr/bin/env node
// cli.mjs — the integrator-wireable entry point.
//
// Usage:
// node tools/eval-harness/cli.mjs \
// --example masked-relay=packages/reactor-devtools/fixtures/masked-relay \
// [--example NAME=PATH ...] \
// [--scenarios cold_start,no_change_replay,...] \
// [--out reports/eval-report.md] \
// [--env /Users/sl/code/openprose/.env]
//
// Defaults to the shipped devtools fixtures when no --example is given, so a
// keyless `node tools/eval-harness/cli.mjs` is a self-contained offline smoke.
// The LLM judge path is OFF unless an OpenRouter key is resolvable AND
// REACTOR_OFFLINE is unset. The key value is NEVER printed.
import { join } from "node:path";
import { runEval, judgesEnabled } from "./index.mjs";
import { renderMarkdown, writeReport } from "./report.mjs";
import { REPO_ROOT, DEFAULT_ENV_PATH } from "./resolve.mjs";
function parseArgs(argv) {
const out = { examples: [], scenarios: undefined, out: undefined, env: DEFAULT_ENV_PATH };
for (let i = 0; i < argv.length; i += 1) {
const a = argv[i];
if (a === "--example") {
const v = argv[++i] ?? "";
const eq = v.indexOf("=");
if (eq > 0) {
out.examples.push({ exampleId: v.slice(0, eq), stateDir: v.slice(eq + 1) });
}
} else if (a === "--scenarios") {
out.scenarios = (argv[++i] ?? "").split(",").map((s) => s.trim()).filter(Boolean);
} else if (a === "--out") {
out.out = argv[++i];
} else if (a === "--env") {
out.env = argv[++i];
} else if (a === "--help" || a === "-h") {
out.help = true;
}
}
return out;
}
function defaultExamples() {
const fx = (n) => join(REPO_ROOT, "packages", "reactor-devtools", "fixtures", n);
// The shipped fixtures with topology + receipts. monorepo-ci / inbox-triage
// carry a `failed` receipt → exercise blocked_or_gated.
return [
{ exampleId: "masked-relay", stateDir: fx("masked-relay") },
{ exampleId: "contract-redline", stateDir: fx("contract-redline") },
{ exampleId: "monorepo-ci", stateDir: fx("monorepo-ci") },
];
}
async function main() {
const args = parseArgs(process.argv.slice(2));
if (args.help) {
process.stdout.write(
"reactor-eval-harness — deterministic-first eval over committed replay state-dirs\n" +
" --example NAME=PATH add an example (repeatable)\n" +
" --scenarios a,b,c restrict scenario kinds\n" +
" --out PATH write the markdown report\n" +
" --env PATH .env to read the OpenRouter key from (judges, key-gated)\n",
);
return 0;
}
const examples = (args.examples.length ? args.examples : defaultExamples()).map(
(e) => ({ ...e, scenarios: args.scenarios }),
);
const on = judgesEnabled(args.env);
process.stderr.write(
`[eval-harness] LLM judges: ${on ? "ON (key resolved)" : "OFF (no key / REACTOR_OFFLINE) — deterministic-only"}\n`,
);
const { report } = await runEval({
harnessBuildId: process.env["EVAL_HARNESS_BUILD_ID"] ?? "local",
examples,
envPath: args.env,
});
if (args.out) {
const written = writeReport(report, args.out);
process.stderr.write(`[eval-harness] wrote ${written.path} (${written.contentHash})\n`);
} else {
process.stdout.write(renderMarkdown(report));
}
// Exit non-zero if any scenario failed (CI-safe signal).
return report.summary.failed > 0 ? 1 : 0;
}
main()
.then((code) => process.exit(code))
.catch((err) => {
process.stderr.write(`[eval-harness] fatal: ${err?.stack ?? err}\n`);
process.exit(2);
});