Files
ruflo/scripts/e2e/host-matrix.mjs
T
randRuFlo 7718a142b4 feat(tui): the AI page: ask Codex (read-only, pinned) or OpenRouter, each question after a card (ADR-489 phase 3)
Page 6. Reuses harness.send and the conversation bridge's sendTo, not a second harness. Host.spawn runs only the pinned Codex 0.162.0
release (never current or PATH), read-only, from a fixed argument allow-list; Host.httpSend sends only https POST to openrouter.ai or a
convoTargets host, never follows a redirect, and a key is read by variable name and never drawn. Enter shows a card of exactly what would be
sent; a lone y answers it only while it was drawn in full (#3983); spend cap from the provider-reported cost. e2e host matrix gets a tui leg
(keyless in CI, live gated). Not in this PR: the workspace-write toggle.

Co-Authored-By: RuFlo <ruv@ruv.net>
2026-10-11 16:54:16 -04:00

35 lines
2.5 KiB
JavaScript

#!/usr/bin/env node
// Three-host e2e loop (ADR-490): Claude Code, Codex and OpenRouter against the real ruflo CLI/MCP/mods, host x assertion matrix.
// node scripts/e2e/host-matrix.mjs keyless + stubbed assertions only (what CI runs): $0, no model call
// RUFLO_E2E_LIVE=1 node scripts/e2e/host-matrix.mjs --live also run the live legs (spend-incurring, capped at --cap USD, default 6)
// Flags: --legs claude,codex,openrouter,tui --cli-version latest|X.Y.Z --cli-path <repo cli.js> --json out.json --cap 6
import { writeFileSync } from 'node:fs'
import { spend, renderTable, redact } from './lib.mjs'
import { claudeLeg } from './leg-claude.mjs'
import { codexLeg } from './leg-codex.mjs'
import { openrouterLeg } from './leg-openrouter.mjs'
import { tuiLeg } from './leg-tui.mjs'
const arg = (k, d) => { const i = process.argv.indexOf(`--${k}`); return i > 0 ? process.argv[i + 1] : d }
const live = process.argv.includes('--live') && process.env.RUFLO_E2E_LIVE === '1'
if (process.argv.includes('--live') && !live) console.error('--live ignored: set RUFLO_E2E_LIVE=1 as well (spend guard)')
spend.cap = Number(arg('cap', '6'))
const cliPath = arg('cli-path', process.env.RUFLO_E2E_CLI_PATH)
const cli = cliPath ? { cmd: process.execPath, args: [cliPath], label: `path:${cliPath}` } : { cmd: 'npx', args: ['-y', `ruflo@${arg('cli-version', process.env.RUFLO_E2E_CLI_VERSION ?? 'latest')}`], label: `ruflo@${arg('cli-version', process.env.RUFLO_E2E_CLI_VERSION ?? 'latest')}` }
const legs = (arg('legs', 'claude,codex,openrouter,tui')).split(',')
const ctx = { cli, live, spend }
const fns = { claude: claudeLeg, codex: codexLeg, openrouter: openrouterLeg, tui: tuiLeg }
const rows = []
for (const leg of legs) {
if (!fns[leg]) { console.error(`unknown leg ${leg}`); process.exit(2) }
const t0 = Date.now()
try { rows.push(...await fns[leg](ctx)) } catch (e) { rows.push({ leg, name: 'leg ran to completion', status: 'fail', evidence: redact(String(e?.stack ?? e)).slice(0, 400) }) }
console.error(`# ${leg} done in ${Math.round((Date.now() - t0) / 1000)}s`)
}
console.log(renderTable(rows))
const n = s => rows.filter(r => r.status === s).length
console.log(`\ncli=${cli.label} live=${live} pass=${n('pass')} fail=${n('fail')} blocked=${n('blocked')} skipped=${n('skipped')} advisory=${n('advisory')} flaky=${n('flaky')} spend_usd=${spend.usd.toFixed(4)}/${spend.cap}`)
if (arg('json')) writeFileSync(arg('json'), `${redact(JSON.stringify({ cli: cli.label, live, spend: spend.usd, rows }, null, 2))}\n`)
process.exit(n('fail') ? 1 : 0)