mirror of
https://github.com/jackwener/OpenCLI.git
synced 2026-09-14 18:25:42 +08:00
37f1b46a77
* feat: AutoResearch framework + V2EX test suite (40 tasks) AutoResearch framework (Karpathy-style autonomous iteration): - engine.ts: 8-phase loop (review → modify → commit → verify → guard → decide → log) - config.ts: typed config + CLI parser + metric extraction - logger.ts: TSV append-only results log - commands/run.ts: main loop spawning Claude Code per iteration - commands/plan.ts: interactive config wizard - commands/fix.ts: auto-detect broken state, iteratively fix - commands/debug.ts: hypothesis-driven debugging for failing tasks V2EX test suite (5 layers, 40 tasks): - L1 Atomic (10): open, state, click, scroll, eval, back, wait - L2 Single Page (10): hot topics, node list, topic meta, pagination - L3 Multi-Step (10): click-read, navigate-node, tab-then-topic, pagination - L4 Write Ops (5): reply typing, favorite detection, form detection - L5 Complex Chain (5): cross-page collect, multi-node compare, full workflow Presets: operate-reliability, skill-quality, v2ex-reliability * test: V2EX test suite 60/60 — fix selectors, add harder tasks - Fix v2ex-collect-hot-authors selector (pathname-based member link detection) - Fix v2ex-wait-text judge (accept "appeared") - Fix trailing commas in eval step strings - Add 20 harder tasks: state+click interaction + long chain workflows - Baseline: 60/60 across all layers * docs: optimize SKILL.md for efficiency — aggressive chaining, minimize turns - Add Rule #7: minimize total tool calls (3-5 per task, not 15-20) - Strengthen Rule #5: chain aggressively with && - Add explicit good/bad chaining examples - Add click+wait+state chaining pattern - Add type+verify chaining pattern Before: 21 turns for complex V2EX reply task After: 12 turns for same task (-43% turns, -28% cost)
164 lines
6.0 KiB
TypeScript
164 lines
6.0 KiB
TypeScript
#!/usr/bin/env npx tsx
|
|
/**
|
|
* /autoresearch:debug — Hypothesis-driven debugging for specific failing tasks.
|
|
*
|
|
* Scientific method: Gather → Hypothesize → Test → Classify → Log → Repeat
|
|
*
|
|
* Usage:
|
|
* npx tsx autoresearch/commands/debug.ts --task extract-npm-description
|
|
* npx tsx autoresearch/commands/debug.ts --task bench-imdb-matrix --iterations 5
|
|
*/
|
|
|
|
import { execSync } from 'node:child_process';
|
|
import { readFileSync, appendFileSync, writeFileSync, existsSync } from 'node:fs';
|
|
import { join, dirname } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
import { parseArgs } from '../config.js';
|
|
|
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
const ROOT = join(__dirname, '..', '..');
|
|
const TASKS_FILE = join(__dirname, '..', 'browse-tasks.json');
|
|
const DEBUG_LOG = join(ROOT, 'debug-results.tsv');
|
|
|
|
interface BrowseTask {
|
|
name: string;
|
|
steps: string[];
|
|
judge: { type: string; value?: string; minLength?: number; pattern?: string };
|
|
}
|
|
|
|
function exec(cmd: string): string {
|
|
try {
|
|
return execSync(cmd, {
|
|
cwd: ROOT, timeout: 30_000, encoding: 'utf-8',
|
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
}).trim();
|
|
} catch (err: any) {
|
|
return err.stdout?.trim() ?? err.message ?? '';
|
|
}
|
|
}
|
|
|
|
function initLog(): void {
|
|
if (!existsSync(DEBUG_LOG)) {
|
|
writeFileSync(DEBUG_LOG, '# AutoResearch Debug Log\niteration\ttask\thypothesis\tresult\tverdict\tdescription\n', 'utf-8');
|
|
}
|
|
}
|
|
|
|
function appendLog(iteration: number, task: string, hypothesis: string, result: string, verdict: string, description: string): void {
|
|
appendFileSync(DEBUG_LOG, `${iteration}\t${task}\t${hypothesis}\t${result}\t${verdict}\t${description}\n`, 'utf-8');
|
|
}
|
|
|
|
async function main() {
|
|
const args = parseArgs(process.argv.slice(2));
|
|
const taskName = args.task;
|
|
const maxIterations = args.iterations ?? 10;
|
|
|
|
if (!taskName) {
|
|
console.error('Usage: npx tsx autoresearch/commands/debug.ts --task <task-name> [--iterations N]');
|
|
console.error('\nAvailable tasks:');
|
|
const tasks: BrowseTask[] = JSON.parse(readFileSync(TASKS_FILE, 'utf-8'));
|
|
// Show only failing tasks
|
|
for (const task of tasks) {
|
|
try { exec('opencli operate close'); } catch {}
|
|
let lastOutput = '';
|
|
for (const step of task.steps) lastOutput = exec(step);
|
|
const passed = lastOutput.trim().length > 0; // simplified check
|
|
if (!passed) console.error(` ✗ ${task.name}`);
|
|
}
|
|
process.exit(1);
|
|
}
|
|
|
|
const tasks: BrowseTask[] = JSON.parse(readFileSync(TASKS_FILE, 'utf-8'));
|
|
const task = tasks.find(t => t.name === taskName);
|
|
if (!task) {
|
|
console.error(`Task not found: ${taskName}`);
|
|
process.exit(1);
|
|
}
|
|
|
|
console.log(`\n🔍 AutoResearch Debug: ${taskName}`);
|
|
console.log(` Steps: ${task.steps.length}`);
|
|
console.log(` Judge: ${task.judge.type}${task.judge.value ? ` "${task.judge.value}"` : ''}`);
|
|
console.log(` Max iterations: ${maxIterations}\n`);
|
|
|
|
initLog();
|
|
|
|
// Phase 1: Gather — run the task and capture output
|
|
console.log('Phase 1: Gathering symptoms...');
|
|
try { exec('opencli operate close'); } catch {}
|
|
|
|
let lastOutput = '';
|
|
for (let i = 0; i < task.steps.length; i++) {
|
|
const step = task.steps[i];
|
|
console.log(` Step ${i + 1}: ${step.slice(0, 80)}`);
|
|
lastOutput = exec(step);
|
|
if (i < task.steps.length - 1) {
|
|
console.log(` → ${lastOutput.slice(0, 100)}`);
|
|
}
|
|
}
|
|
console.log(`\n Final output: ${lastOutput.slice(0, 200)}`);
|
|
console.log(` Judge expects: ${JSON.stringify(task.judge)}`);
|
|
|
|
// Phase 2: Hypothesize + investigate via Claude Code
|
|
for (let iter = 1; iter <= maxIterations; iter++) {
|
|
console.log(`\n━━━ Debug Iteration ${iter}/${maxIterations} ━━━`);
|
|
|
|
const prompt = `You are debugging a failing browser automation task.
|
|
|
|
## Task: ${taskName}
|
|
Steps:
|
|
${task.steps.map((s, i) => ` ${i + 1}. ${s}`).join('\n')}
|
|
|
|
## Judge criteria
|
|
${JSON.stringify(task.judge)}
|
|
|
|
## Last output
|
|
${lastOutput.slice(0, 500)}
|
|
|
|
## Instructions
|
|
1. Form a SPECIFIC, FALSIFIABLE hypothesis about why this task fails
|
|
2. Run the MINIMUM experiment to test your hypothesis (e.g. run one step, check output)
|
|
3. Classify: CONFIRMED (bug found), DISPROVEN (try different hypothesis), INCONCLUSIVE
|
|
4. If CONFIRMED: describe the root cause and suggest a fix
|
|
5. Output format: one line "HYPOTHESIS: ...", one line "RESULT: CONFIRMED|DISPROVEN|INCONCLUSIVE — ..."
|
|
|
|
Do NOT fix the code — just diagnose. Use opencli operate commands to investigate.`;
|
|
|
|
try {
|
|
const result = execSync(
|
|
`claude -p --dangerously-skip-permissions --allowedTools "Bash(opencli:*),Bash(npm:*),Read,Grep,Glob" --output-format text --no-session-persistence "${prompt.replace(/"/g, '\\"')}"`,
|
|
{ cwd: ROOT, timeout: 120_000, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }
|
|
).trim();
|
|
|
|
// Extract hypothesis and result
|
|
const hypMatch = result.match(/HYPOTHESIS:\s*(.+)/i);
|
|
const resMatch = result.match(/RESULT:\s*(CONFIRMED|DISPROVEN|INCONCLUSIVE)\s*[-—]\s*(.+)/i);
|
|
|
|
const hypothesis = hypMatch?.[1]?.trim() ?? 'unknown';
|
|
const verdict = resMatch?.[1]?.trim() ?? 'INCONCLUSIVE';
|
|
const description = resMatch?.[2]?.trim() ?? result.split('\n').pop()?.trim() ?? '';
|
|
|
|
console.log(` Hypothesis: ${hypothesis.slice(0, 100)}`);
|
|
console.log(` Verdict: ${verdict} — ${description.slice(0, 100)}`);
|
|
|
|
appendLog(iter, taskName, hypothesis, lastOutput.slice(0, 50), verdict, description);
|
|
|
|
if (verdict === 'CONFIRMED') {
|
|
console.log(`\n✅ Root cause found at iteration ${iter}!`);
|
|
console.log(` ${description}`);
|
|
break;
|
|
}
|
|
} catch (err: any) {
|
|
console.error(` Error: ${err.message?.slice(0, 100)}`);
|
|
appendLog(iter, taskName, 'error', '', 'CRASH', err.message?.slice(0, 80) ?? '');
|
|
}
|
|
|
|
// Re-run task for fresh output
|
|
try { exec('opencli operate close'); } catch {}
|
|
for (const step of task.steps) lastOutput = exec(step);
|
|
}
|
|
|
|
try { exec('opencli operate close'); } catch {}
|
|
console.log(`\nDebug log saved to: ${DEBUG_LOG}\n`);
|
|
}
|
|
|
|
main();
|