Files
wehub-resource-sync 9b395f5cc3
E2E Headed Chrome / e2e-headed (macos-15) (push) Has been cancelled
E2E Headed Chrome / e2e-headed (ubuntu-latest) (push) Has been cancelled
E2E Headed Chrome / e2e-headed (windows-latest) (push) Has been cancelled
CI / build (macos-latest) (push) Has been cancelled
CI / build (ubuntu-latest) (push) Has been cancelled
CI / build (windows-latest) (push) Has been cancelled
CI / unit-test (push) Has been cancelled
CI / bun-test (push) Has been cancelled
CI / adapter-test (push) Has been cancelled
CI / smoke-test (macos-latest) (push) Has been cancelled
CI / smoke-test (ubuntu-latest) (push) Has been cancelled
Security Audit / audit (push) Has been cancelled
Build Chrome Extension / build (push) Has been cancelled
Trigger Website Rebuild (Docs Updated) / dispatch (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:39:48 +08:00

83 lines
2.9 KiB
TypeScript

/**
* AutoResearch Configuration — type definitions and CLI parsing.
*
* Based on Karpathy's autoresearch: constraint + mechanical metric + unbounded loop.
*/
export interface AutoResearchConfig {
/** Plain-language goal, e.g. "Increase browser pass rate to 59/59" */
goal: string;
/** Glob patterns for files the agent can modify */
scope: string[];
/** What the metric measures, e.g. "pass_count" */
metric: string;
/** Whether improvement means the number goes up or down */
direction: 'higher' | 'lower';
/** Shell command that outputs a number (the metric value) */
verify: string;
/** Optional guard command — must pass for a keep decision */
guard?: string;
/** Max iterations (undefined = unbounded) */
iterations?: number;
/** Minimum delta to count as real improvement (noise filter) */
minDelta?: number;
}
export type IterationStatus =
| 'baseline'
| 'keep'
| 'keep (reworked)'
| 'discard'
| 'crash'
| 'no-op'
| 'hook-blocked';
export interface IterationResult {
iteration: number;
commit: string;
metric: number;
delta: number;
guard: 'pass' | 'fail' | '-';
status: IterationStatus;
description: string;
}
/** Parse CLI args into a partial config (missing fields filled by preset or prompts) */
export function parseArgs(argv: string[]): Partial<AutoResearchConfig> & { preset?: string; task?: string } {
const config: Partial<AutoResearchConfig> & { preset?: string; task?: string } = {};
for (let i = 0; i < argv.length; i++) {
const arg = argv[i];
const next = argv[i + 1];
switch (arg) {
case '--preset': config.preset = next; i++; break;
case '--goal': config.goal = next; i++; break;
case '--scope': config.scope = next?.split(','); i++; break;
case '--metric': config.metric = next; i++; break;
case '--direction': config.direction = next as 'higher' | 'lower'; i++; break;
case '--verify': config.verify = next; i++; break;
case '--guard': config.guard = next; i++; break;
case '--iterations': config.iterations = parseInt(next, 10); i++; break;
case '--min-delta': config.minDelta = parseFloat(next); i++; break;
case '--task': config.task = next; i++; break;
}
}
return config;
}
/** Extract a number from command output using common patterns */
export function extractMetric(output: string): number | null {
// Try: last line that looks like a number
const lines = output.trim().split('\n');
for (let i = lines.length - 1; i >= 0; i--) {
const line = lines[i].trim();
// Match standalone numbers: "56", "95.2", "SCORE=56/59" → 56
const scoreMatch = line.match(/SCORE[=:]\s*(\d+)/i);
if (scoreMatch) return parseFloat(scoreMatch[1]);
const numMatch = line.match(/^[\d.]+$/);
if (numMatch) return parseFloat(numMatch[0]);
}
// Fallback: first number in output
const fallback = output.match(/(\d+(?:\.\d+)?)/);
return fallback ? parseFloat(fallback[1]) : null;
}