Files
Nick Nisi dc9dfb093c fix(workos): tighten auth guidance and risky eval reruns (#25)
* fix(workos): include required --org flag in portal generate-link examples

The "Not in the CLI" table in workos-management.md listed
`workos portal generate-link --intent=sso` and
`workos portal generate-link --intent=dsync` as the correct way to
reach the Admin Portal for connection creation. Both omit the
required --org flag and fail before opening the Portal. This is the
same hallucination shape the PR is trying to prevent agents from
producing — caught in review.

Add --org=<org_id> to both rows, matching the Quick Reference
entry at the top of the file.

* test(workos): tighten no-CLI assertion in connection-create evals

The first assertion in evals 8 and 9 was titled "States connection
creation is NOT in the CLI" but its needles included Admin Portal
and WorkOS Dashboard. An answer that only said "Use the Admin
Portal" without ever stating CLI support is absent passed the
assertion. Since the second assertion in each eval already checks
for the Admin Portal/Dashboard destination, the first was both
redundant and weakened.

Remove destination terms from the first assertion so it genuinely
checks for the explicit no-CLI signal.

* test(workos): improve risky eval coverage

Add targeted recipes for SSO, AuthKit, and RBAC cases that showed negative or noisy eval deltas. Tighten brittle flow-step expectations where the previous wording rewarded incidental prose ordering instead of the intended behavior.

Add multi-case eval filtering and an eval:risk helper so the latest triage cases can be rerun with more samples and saved outputs.

* Fix explicit triage handling in risk reruns

* fix: format:check script incorrectly using prettier

* chore: format files for oxfmt check
2026-04-26 09:06:35 -05:00

114 lines
3.7 KiB
TypeScript

import { runEval } from './eval/runner.ts';
import {
printTable,
printSummary,
writeJsonReport,
writeTranscripts,
printLanguageBreakdown,
printErrorReductions,
checkGates,
} from './eval/reporter.ts';
import { rankByRisk, printTriage, writeTriageReport } from './eval/triage.ts';
import { readLabels } from './eval/labels.ts';
import { computeCalibration, printCalibration } from './eval/calibrate.ts';
import type { EvalOptions } from './eval/types.ts';
function parseArgs(): EvalOptions {
const args = process.argv.slice(2);
const rawSamples = args.find((a) => a.startsWith('--samples='))?.split('=')[1];
const parsedSamples = rawSamples ? parseInt(rawSamples) : 1;
const samples = Math.max(1, parsedSamples || 1);
const rawCaseFilter = args.find((a) => a.startsWith('--cases=')) ?? args.find((a) => a.startsWith('--case='));
const caseIds = rawCaseFilter
?.split('=')[1]
?.split(',')
.map((id) => id.trim())
.filter(Boolean);
if (rawSamples && samples !== parsedSamples) {
console.warn(`⚠ Invalid --samples=${rawSamples}, using --samples=${samples}`);
}
return {
product: args.find((a) => a.startsWith('--product='))?.split('=')[1],
caseId: caseIds?.length === 1 ? caseIds[0] : undefined,
caseIds,
model: args.find((a) => a.startsWith('--model='))?.split('=')[1] ?? 'claude-sonnet-4-5-20250929',
noCache: args.includes('--no-cache'),
dryRun: args.includes('--dry-run'),
concurrency: parseInt(args.find((a) => a.startsWith('--concurrency='))?.split('=')[1] ?? '3'),
apiKey: process.env.ANTHROPIC_API_KEY ?? '',
lang: args.find((a) => a.startsWith('--lang='))?.split('=')[1],
reportFormat: args.find((a) => a.startsWith('--report='))?.split('=')[1] ?? 'both',
failOnRegression: args.includes('--fail-on-regression'),
samples,
saveAllSamples: args.includes('--save-all-samples'),
};
}
async function main() {
const options = parseArgs();
if (!options.apiKey && !options.dryRun) {
console.error('Error: ANTHROPIC_API_KEY environment variable is required.');
console.error('Use --dry-run to preview cases without API calls.');
process.exit(1);
}
const report = await runEval(options);
const fmt = options.reportFormat ?? 'both';
if (fmt === 'table' || fmt === 'both') {
printTable(report);
printSummary(report);
printLanguageBreakdown(report);
printErrorReductions(report);
}
// Triage report
if (report.results.length > 0) {
const triageCases = rankByRisk(report.results);
if (fmt === 'table' || fmt === 'both') {
printTriage(triageCases);
}
if (fmt === 'json' || fmt === 'both') {
await writeTriageReport(triageCases, report.runId);
}
}
if (report.results.length > 0 && (fmt === 'json' || fmt === 'both')) {
await writeJsonReport(report);
await writeTranscripts(report);
}
if (options.failOnRegression && report.results.length > 0) {
const gateResult = checkGates(report);
console.log('\n Regression Gates:');
if (gateResult.passed) {
console.log(' ✓ All gates passed');
} else {
for (const f of gateResult.failures) {
console.log(`${f}`);
}
process.exit(1);
}
// Calibration gate (when labels exist)
const labels = await readLabels();
if (labels.length > 0) {
const cal = computeCalibration(labels, report.results);
printCalibration(cal);
if (!cal.passed) {
console.log(
`\n ✗ Calibration gate failed: ${Math.round(cal.agreement * 100)}% < ${Math.round(cal.threshold * 100)}% threshold`,
);
process.exit(1);
}
}
}
}
main().catch((err) => {
console.error('Eval failed:', err);
process.exit(1);
});