diff --git a/packages/loopover-miner/docs/cross-repo-evaluation.md b/packages/loopover-miner/docs/cross-repo-evaluation.md index cc36444b74..a063e99fd1 100644 --- a/packages/loopover-miner/docs/cross-repo-evaluation.md +++ b/packages/loopover-miner/docs/cross-repo-evaluation.md @@ -60,6 +60,35 @@ fleet run-manifest). - `--repo owner/repo` — evaluate a single manifest entry - `--manifest path/to/manifest.json` — alternate benchmark set (e.g. a fixture manifest in tests) - `--require-majority` — exit `1` unless a strict majority of repos pass (for CI-style gating) + - `--full-execution` — **dry-run** execution mode (#7634): clone each repo, run the + discover→plan→code→test loop locally, and run the target repo's own tests. See below. + +## Full execution mode (dry-run) (#7634) + +`--full-execution` opts into a **dry-run** that goes one step past readiness: for every repo that already passes the +readiness gate above, it actually runs the discover→plan→code→test loop **locally** and reports pass/fail with +execution-specific categories. It is strictly **read/execute-locally-and-discard**: + +1. **Clone / checkout** the repo locally (via `ensureRepoCloned`; a read-only local clone — never a write-back) +2. **Plan** — recompose the same (leak-free) coding-task spec used by readiness +3. **Code** — run the coding agent to produce a diff (the default is a **non-spawning shadow** that produces no + diff and uses no credentials; inject `runCodingAgent` to drive a real/fake agent) +4. **Test** — run the **target repo's own** inferred test command locally against the produced change + +The execution-specific failure categories extend (never replace) the readiness taxonomy: + +| Category | Meaning | +| --- | --- | +| `exec_setup_gap` | Clone / checkout of the target repo failed before the loop could start | +| `plan_compile_gap` | Plan formed but the code phase did not produce a compiling change | +| `test_failure` | Change compiled but the target repo's own tests failed (or timed out) | +| `no_op_diff` | Tests passed but the coding agent produced an empty (no-op) diff | + +The report gains one line, `dry-run full-execution: N/total entered the code+test loop`, and the readiness format is +otherwise unchanged. **Dry-run safety is structural:** there is no PR-open / forge-write / credential path anywhere +in the execution loop — the clone, coding-agent, and test-run steps are all injectable seams (`cloneRepo`, +`runCodingAgent`, `runTests`) that unit tests replace with fakes for zero real IO. No `gh pr create`, no forge API, +no third-party write ever runs. ## Library API @@ -68,11 +97,17 @@ Pure functions live in [`lib/cross-repo-evaluation.js`](../lib/cross-repo-evalua - `parseCrossRepoEvaluationManifest(content)` - `evaluateRepoReadiness(entry, options)` — inject `existsSync`, `detectRepoStack`, etc. for unit tests - `runCrossRepoEvaluation(parsed, options)` +- `evaluateRepoExecution(entry, options)` — dry-run full-execution (#7634); inject `cloneRepo`, `runCodingAgent`, + `runTests` for unit tests +- `runCrossRepoExecution(parsed, options)` — full-execution across a parsed manifest - `summarizeCrossRepoEvaluation(results)` - `formatCrossRepoEvaluationReport(results, summary)` ## Wiring -This harness is **readiness-only**: it does not run the coding agent, open PRs, or call forge APIs. A green report -means the miner’s repo-agnostic stack-detection and coding-task-spec path is prepared for the benchmark repo; a live -attempt still needs credentials, governor policy, and queue state as documented in [`DEPLOYMENT.md`](../DEPLOYMENT.md). +By default this harness is **readiness-only**: it does not run the coding agent, open PRs, or call forge APIs. A +green report means the miner’s repo-agnostic stack-detection and coding-task-spec path is prepared for the benchmark +repo. `--full-execution` opts into a live-ish **dry-run** that additionally clones, runs the coding agent, and runs +the target repo's own tests locally — but still **never opens a PR or writes to the third-party repo**; a real +attempt that submits a PR still needs credentials, governor policy, and queue state as documented in +[`DEPLOYMENT.md`](../DEPLOYMENT.md). diff --git a/packages/loopover-miner/lib/cross-repo-evaluation.d.ts b/packages/loopover-miner/lib/cross-repo-evaluation.d.ts index af8e928558..d7aaa313d9 100644 --- a/packages/loopover-miner/lib/cross-repo-evaluation.d.ts +++ b/packages/loopover-miner/lib/cross-repo-evaluation.d.ts @@ -1,11 +1,19 @@ import type { RepoStackResult } from "./stack-detection.js"; -/** Failure taxonomy surfaced in per-repo reports (#4788). */ +/** Failure taxonomy surfaced in per-repo reports (#4788). The first five members are the offline + * readiness taxonomy; the four `EXEC_*`/plan/test/no-op members are the dry-run full-execution taxonomy + * (#7634) surfaced only when the `--full-execution` loop actually clones, runs the agent, and runs the + * target repo's own tests. New members are appended in the same frozen literal so existing members are + * never removed. */ export declare const CROSS_REPO_FAILURE_CATEGORY: Readonly<{ STACK_DETECTION: "stack_detection_gap"; EXECUTION: "execution_gap"; GITTENSOR_ASSUMPTION: "loopover_assumption"; CLONE_SETUP: "clone_setup"; OTHER: "other"; + EXEC_SETUP: "exec_setup_gap"; + PLAN_COMPILE: "plan_compile_gap"; + TEST_FAILURE: "test_failure"; + NO_OP_DIFF: "no_op_diff"; }>; /** Instruction substrings that indicate a POSITIVE loopover/LoopOver CI assumption leaked into the agent prompt. * Lines that explicitly tell the agent *not* to assume these are filtered out before scanning. */ @@ -41,6 +49,11 @@ export type CrossRepoEvaluationResult = { line: string; }>; stack?: RepoStackResult; + /** Full-execution (dry-run) fields (#7634); absent on readiness-only results. */ + executed?: boolean; + changedFiles?: string[]; + testCommand?: string | null; + testExitCode?: number | null; }; export type CrossRepoEvaluationSummary = { total: number; @@ -48,8 +61,38 @@ export type CrossRepoEvaluationSummary = { failed: number; majorityPassed: boolean; withoutLoopoverConfig: number; + /** Repos that entered the dry-run clone+agent+test loop (#7634); 0 for a readiness-only run. */ + executedCount: number; failuresByCategory: Record; }; +/** Result of the dry-run clone/checkout seam (#7634). Mirrors repo-clone's EnsureRepoClonedResult. */ +export type CrossRepoExecutionCloneResult = { + ok: boolean; + repoPath?: string; + error?: string; +}; +/** Context handed to the dry-run coding-agent seam (#7634). */ +export type CrossRepoExecutionAgentContext = { + repoFullName: string; + repoPath: string; + instructions: string; + stack: RepoStackResult; + entry: CrossRepoEvaluationManifestRepo; +}; +/** Result of the dry-run coding-agent seam (#7634): the produced (canned/real) diff surface. */ +export type CrossRepoExecutionAgentResult = { + ok: boolean; + changedFiles?: readonly string[]; + summary?: string; + error?: string; +}; +/** Result of the dry-run target-repo test seam (#7634). Mirrors the executeLocalWrite subprocess shape. */ +export type CrossRepoExecutionTestResult = { + code: number | null; + stdout?: string; + stderr?: string; + timedOut?: boolean; +}; type EvaluateRepoReadinessOptions = { repoPath?: string; resolveRepoPath?: (entry: { @@ -66,6 +109,11 @@ type EvaluateRepoReadinessOptions = { verdict?: string; instructions?: string; }; + fullExecution?: boolean; + testTimeoutMs?: number; + cloneRepo?: (entry: CrossRepoEvaluationManifestRepo, options: EvaluateRepoReadinessOptions) => CrossRepoExecutionCloneResult | Promise; + runCodingAgent?: (context: CrossRepoExecutionAgentContext) => CrossRepoExecutionAgentResult | Promise; + runTests?: (command: string, cwd: string, timeoutMs: number) => CrossRepoExecutionTestResult | Promise; }; /** Canonical `owner/repo` with exactly one slash and safe segments; anything else → null. */ export declare function normalizeCrossRepoFullName(value: unknown): string | null; @@ -86,12 +134,37 @@ export declare function scanPositiveLoopoverAssumptions(text: string): Array<{ * Evaluate one benchmark repo's miner readiness without running a live coding agent (#4788). */ export declare function evaluateRepoReadiness(entry: CrossRepoEvaluationManifestRepo, options?: EvaluateRepoReadinessOptions): CrossRepoEvaluationResult; +/** 10-minute cap on a target repo's own test suite when the caller does not override `testTimeoutMs` (#7634). */ +export declare const DEFAULT_EXECUTION_TEST_TIMEOUT_MS: number; +/** + * DRY-RUN full-execution evaluation of one benchmark repo (#7634). Reuses the readiness gate unchanged, then -- + * only for a repo that already passes readiness -- runs the discover->plan->code->test loop LOCALLY: + * + * 1. clone/checkout the repo (setup) -> EXEC_SETUP on failure + * 2. reuse the readiness-composed spec (plan) -- no second (side-effecting) buildCodingTaskSpec call + * 3. run the coding agent to produce a diff -> PLAN_COMPILE when the code phase does not converge + * 4. run the target repo's own tests locally -> TEST_FAILURE when the suite is red + * 5. no-op guard -> NO_OP_DIFF when tests pass but the diff is empty + * + * Every side-effecting step (clone, agent, tests) is behind an injectable `options.*` seam so unit tests drive + * the whole loop with fakes and zero real IO. There is deliberately NO PR-open / forge-write / credential path: + * the harness clones and executes locally, then discards. Readiness-mode behavior is untouched. + */ +export declare function evaluateRepoExecution(entry: CrossRepoEvaluationManifestRepo, options?: EvaluateRepoReadinessOptions): Promise; /** * Run the harness across every repo in a parsed manifest (#4788). */ export declare function runCrossRepoEvaluation(parsed: ParsedCrossRepoEvaluationManifest, options?: { repoFilter?: string; } & EvaluateRepoReadinessOptions): CrossRepoEvaluationResult[]; +/** + * DRY-RUN full-execution run across every repo in a parsed manifest (#7634). Sequential (one clone+agent+test + * loop at a time) so the local machine is never hammered. Same options-injection surface as the readiness run, + * plus the clone/agent/test seams. Repos are read/executed-locally-and-discarded; no PR is ever opened. + */ +export declare function runCrossRepoExecution(parsed: ParsedCrossRepoEvaluationManifest, options?: { + repoFilter?: string; +} & EvaluateRepoReadinessOptions): Promise; /** * Reduce per-repo results to pass/fail counts and whether a strict majority passed (#4788). */ diff --git a/packages/loopover-miner/lib/cross-repo-evaluation.js b/packages/loopover-miner/lib/cross-repo-evaluation.js index 995ae19eba..52d92dda62 100644 --- a/packages/loopover-miner/lib/cross-repo-evaluation.js +++ b/packages/loopover-miner/lib/cross-repo-evaluation.js @@ -3,18 +3,28 @@ // evaluated through the same stack-detection + coding-task-spec path a real attempt uses (detectRepoStack, // resolveMinerGoalSpec, buildCodingTaskSpec) and failures are categorized as stack-detection gaps, execution // readiness gaps, leaked loopover assumptions in agent instructions, clone/setup problems, or other. +import { spawn } from "node:child_process"; import { existsSync } from "node:fs"; import { buildCodingTaskSpec } from "./coding-task-spec.js"; import { resolveMinerGoalSpec } from "./miner-goal-spec.js"; -import { isValidRepoSegment, resolveRepoCloneDir } from "./repo-clone.js"; +import { ensureRepoCloned, isValidRepoSegment, resolveRepoCloneDir } from "./repo-clone.js"; import { detectRepoStack } from "./stack-detection.js"; -/** Failure taxonomy surfaced in per-repo reports (#4788). */ +/** Failure taxonomy surfaced in per-repo reports (#4788). The first five members are the offline + * readiness taxonomy; the four `EXEC_*`/plan/test/no-op members are the dry-run full-execution taxonomy + * (#7634) surfaced only when the `--full-execution` loop actually clones, runs the agent, and runs the + * target repo's own tests. New members are appended in the same frozen literal so existing members are + * never removed. */ export const CROSS_REPO_FAILURE_CATEGORY = Object.freeze({ STACK_DETECTION: "stack_detection_gap", EXECUTION: "execution_gap", GITTENSOR_ASSUMPTION: "loopover_assumption", CLONE_SETUP: "clone_setup", OTHER: "other", + // Dry-run full-execution taxonomy (#7634): + EXEC_SETUP: "exec_setup_gap", // clone / checkout of the target repo failed before the loop could start + PLAN_COMPILE: "plan_compile_gap", // plan formed but the code phase did not produce a compiling change + TEST_FAILURE: "test_failure", // change compiled but the target repo's own tests failed + NO_OP_DIFF: "no_op_diff", // tests passed but the coding agent produced an empty (no-op) diff }); /** Instruction substrings that indicate a POSITIVE loopover/LoopOver CI assumption leaked into the agent prompt. * Lines that explicitly tell the agent *not* to assume these are filtered out before scanning. */ @@ -216,13 +226,42 @@ function resolveEvaluationRepoPath(entry, options = {}) { function defaultClaimLedger(repoFullName) { return { listClaims: () => [] }; } +function describeError(error) { + return error instanceof Error ? error.message : String(error); +} /** - * Evaluate one benchmark repo's miner readiness without running a live coding agent (#4788). + * Compose the synthetic coding-task spec both the readiness gate and the full-execution loop use (#4788 / #7634). + * Shared so the "plan" (spec instructions) is derived identically in both modes -- readiness validates it is + * leak-free; full-execution reuses it as the agent's plan. */ -export function evaluateRepoReadiness(entry, options = {}) { +function composeCrossRepoEvaluationSpec(repoFullName, repoPath, buildSpecImpl, detectImpl) { + return buildSpecImpl({ + repoFullName, + issue: { + number: 1, + title: "Cross-repo evaluation harness smoke issue", + body: "Synthetic issue used only by the cross-repo evaluation harness.", + labels: ["bug"], + }, + context: { issues: [{ number: 1 }], pullRequests: [] }, + claimLedger: defaultClaimLedger(repoFullName), + workingDirectory: repoPath, + detectRepoStack: detectImpl, + }); +} +/** + * Readiness core (#4788 / #7634). Identical logic to {@link evaluateRepoReadiness} but also returns the plan + * instructions from the SINGLE `buildCodingTaskSpec` call, so the full-execution loop never re-invokes that + * (production `buildCodingTaskSpec` is side-effecting -- it writes acceptance-criteria.json -- and not idempotent). + */ +function evaluateRepoReadinessCore(entry, options = {}) { const repoFullName = entry?.repoFullName; if (typeof repoFullName !== "string" || !normalizeCrossRepoFullName(repoFullName)) { - return buildFailure(typeof repoFullName === "string" ? repoFullName : "(invalid)", CROSS_REPO_FAILURE_CATEGORY.OTHER, "Benchmark entry is missing a valid owner/repo name."); + return { + result: buildFailure(typeof repoFullName === "string" ? repoFullName : "(invalid)", CROSS_REPO_FAILURE_CATEGORY.OTHER, "Benchmark entry is missing a valid owner/repo name."), + instructions: "", + repoPath: "", + }; } const existsImpl = options.existsSync ?? existsSync; const detectImpl = options.detectRepoStack ?? detectRepoStack; @@ -231,49 +270,248 @@ export function evaluateRepoReadiness(entry, options = {}) { buildCodingTaskSpec; const repoPath = resolveEvaluationRepoPath(entry, options); if (!existsImpl(repoPath)) { - return buildFailure(repoFullName, CROSS_REPO_FAILURE_CATEGORY.CLONE_SETUP, `Repository path does not exist: ${repoPath}. Clone the repo or set LOOPOVER_MINER_REPO_CLONE_DIR.`); + return { + result: buildFailure(repoFullName, CROSS_REPO_FAILURE_CATEGORY.CLONE_SETUP, `Repository path does not exist: ${repoPath}. Clone the repo or set LOOPOVER_MINER_REPO_CLONE_DIR.`), + instructions: "", + repoPath, + }; } const goalSpec = goalSpecImpl(repoPath); const usedDefaultGoalSpec = goalSpec?.present !== true; const stack = detectImpl(repoPath); if (stack?.detected !== true) { - return buildFailure(repoFullName, CROSS_REPO_FAILURE_CATEGORY.STACK_DETECTION, stack?.reason ?? "Stack auto-detection did not recognize this repository.", { stackDetected: false, usedDefaultGoalSpec }); + return { + result: buildFailure(repoFullName, CROSS_REPO_FAILURE_CATEGORY.STACK_DETECTION, stack?.reason ?? "Stack auto-detection did not recognize this repository.", { stackDetected: false, usedDefaultGoalSpec }), + instructions: "", + repoPath, + }; } if (entry.requireTestCommand === true && !stack.testCommand) { - return buildFailure(repoFullName, CROSS_REPO_FAILURE_CATEGORY.EXECUTION, "Stack detection succeeded but no test command was inferred while requireTestCommand is set.", { stackDetected: true, usedDefaultGoalSpec, stack }); + return { + result: buildFailure(repoFullName, CROSS_REPO_FAILURE_CATEGORY.EXECUTION, "Stack detection succeeded but no test command was inferred while requireTestCommand is set.", { stackDetected: true, usedDefaultGoalSpec, stack }), + instructions: "", + repoPath, + }; } let specResult; try { - specResult = buildSpecImpl({ - repoFullName, - issue: { - number: 1, - title: "Cross-repo evaluation harness smoke issue", - body: "Synthetic issue used only by the cross-repo evaluation harness.", - labels: ["bug"], - }, - context: { issues: [{ number: 1 }], pullRequests: [] }, - claimLedger: defaultClaimLedger(repoFullName), - workingDirectory: repoPath, - detectRepoStack: detectImpl, - }); + specResult = composeCrossRepoEvaluationSpec(repoFullName, repoPath, buildSpecImpl, detectImpl); } catch (error) { - const message = error instanceof Error ? error.message : String(error); - return buildFailure(repoFullName, CROSS_REPO_FAILURE_CATEGORY.OTHER, message, { - stackDetected: true, - usedDefaultGoalSpec, - stack, - }); + return { + result: buildFailure(repoFullName, CROSS_REPO_FAILURE_CATEGORY.OTHER, describeError(error), { + stackDetected: true, + usedDefaultGoalSpec, + stack, + }), + instructions: "", + repoPath, + }; } if (specResult?.ready !== true) { - return buildFailure(repoFullName, CROSS_REPO_FAILURE_CATEGORY.EXECUTION, `Coding task spec is not ready (verdict: ${specResult?.verdict ?? "unknown"}).`, { stackDetected: true, usedDefaultGoalSpec, stack }); + return { + result: buildFailure(repoFullName, CROSS_REPO_FAILURE_CATEGORY.EXECUTION, `Coding task spec is not ready (verdict: ${specResult?.verdict ?? "unknown"}).`, { stackDetected: true, usedDefaultGoalSpec, stack }), + instructions: "", + repoPath, + }; } const assumptionFindings = scanPositiveLoopoverAssumptions(specResult.instructions ?? ""); if (assumptionFindings.length > 0) { - return buildFailure(repoFullName, CROSS_REPO_FAILURE_CATEGORY.GITTENSOR_ASSUMPTION, `Agent instructions leak loopover-specific assumptions (${assumptionFindings.map((f) => f.id).join(", ")}).`, { stackDetected: true, usedDefaultGoalSpec, stack, assumptionFindings }); + return { + result: buildFailure(repoFullName, CROSS_REPO_FAILURE_CATEGORY.GITTENSOR_ASSUMPTION, `Agent instructions leak loopover-specific assumptions (${assumptionFindings.map((f) => f.id).join(", ")}).`, { stackDetected: true, usedDefaultGoalSpec, stack, assumptionFindings }), + instructions: "", + repoPath, + }; } - return buildPass(repoFullName, { usedDefaultGoalSpec, stack }); + return { + result: buildPass(repoFullName, { usedDefaultGoalSpec, stack }), + instructions: specResult.instructions ?? "", + repoPath, + }; +} +/** + * Evaluate one benchmark repo's miner readiness without running a live coding agent (#4788). + */ +export function evaluateRepoReadiness(entry, options = {}) { + return evaluateRepoReadinessCore(entry, options).result; +} +/** 10-minute cap on a target repo's own test suite when the caller does not override `testTimeoutMs` (#7634). */ +export const DEFAULT_EXECUTION_TEST_TIMEOUT_MS = 600_000; +/** + * Default clone seam (#7634): a read-only local clone/checkout. When the working copy is already on disk (the + * normal case -- readiness resolves the same path via entry.fixturePath / options.repoPath / the clone cache and + * has already gated its existence), it is reused directly with NO network. Otherwise it falls back to repo-clone's + * ensureRepoCloned. Either way it never writes back to the third-party repo and never opens a PR -- it is the + * "local clone" the dry-run permits. + */ +async function defaultCloneRepo(entry, options) { + const existsImpl = options.existsSync ?? existsSync; + const localPath = resolveEvaluationRepoPath(entry, options); + if (localPath && existsImpl(localPath)) { + return { ok: true, repoPath: localPath }; + } + const result = await ensureRepoCloned(entry.repoFullName, { + env: (options.env ?? process.env), + }); + const out = { ok: result.ok, repoPath: result.repoPath }; + if (result.error !== undefined) + out.error = result.error; + return out; +} +/** + * Default coding-agent seam (#7634): a DRY-RUN SHADOW that never spawns a coding agent, never forwards + * credentials, and produces no diff. A real diff is produced only when a caller injects options.runCodingAgent + * (unit tests inject a fake CodingAgentDriver-shaped runner; a future live mode would inject the real driver). + */ +function defaultRunCodingAgent(_context) { + return { + ok: true, + changedFiles: [], + summary: "dry-run: coding agent not executed (shadow); inject options.runCodingAgent to produce a diff.", + }; +} +/** + * Default test seam (#7634): run the TARGET repo's own inferred test command locally via `sh -c`, mirroring the + * executeLocalWrite subprocess wrapper (resolve-not-reject; SIGKILL on timeout). Purely local; no network. + */ +function defaultRunTests(command, cwd, timeoutMs) { + return new Promise((resolve) => { + let stdout = ""; + let stderr = ""; + let timedOut = false; + let child; + try { + child = spawn("sh", ["-c", command], { cwd }); + } + catch (error) { + resolve({ code: null, stdout, stderr: describeError(error), timedOut }); + return; + } + const timer = setTimeout(() => { + timedOut = true; + child.kill("SIGKILL"); + }, timeoutMs); + child.stdout?.on("data", (chunk) => { + stdout += String(chunk); + }); + child.stderr?.on("data", (chunk) => { + stderr += String(chunk); + }); + child.on("error", (error) => { + clearTimeout(timer); + resolve({ code: null, stdout, stderr: `${stderr}${describeError(error)}`, timedOut }); + }); + child.on("close", (code) => { + clearTimeout(timer); + resolve({ code, stdout, stderr, timedOut }); + }); + }); +} +/** + * DRY-RUN full-execution evaluation of one benchmark repo (#7634). Reuses the readiness gate unchanged, then -- + * only for a repo that already passes readiness -- runs the discover->plan->code->test loop LOCALLY: + * + * 1. clone/checkout the repo (setup) -> EXEC_SETUP on failure + * 2. reuse the readiness-composed spec (plan) -- no second (side-effecting) buildCodingTaskSpec call + * 3. run the coding agent to produce a diff -> PLAN_COMPILE when the code phase does not converge + * 4. run the target repo's own tests locally -> TEST_FAILURE when the suite is red + * 5. no-op guard -> NO_OP_DIFF when tests pass but the diff is empty + * + * Every side-effecting step (clone, agent, tests) is behind an injectable `options.*` seam so unit tests drive + * the whole loop with fakes and zero real IO. There is deliberately NO PR-open / forge-write / credential path: + * the harness clones and executes locally, then discards. Readiness-mode behavior is untouched. + */ +export async function evaluateRepoExecution(entry, options = {}) { + // Step 2 (plan) is folded into the readiness gate: the plan instructions come from readiness's SINGLE + // buildCodingTaskSpec call (production buildCodingTaskSpec is side-effecting + non-idempotent, so we must + // never call it twice). + const readiness = evaluateRepoReadinessCore(entry, options); + if (!readiness.result.passed) + return readiness.result; + const repoFullName = readiness.result.repoFullName; + const usedDefaultGoalSpec = readiness.result.usedDefaultGoalSpec; + const instructions = readiness.instructions; + const stack = readiness.result.stack; + if (!stack || stack.detected !== true) { + // Defensive: a passing readiness always carries a detected stack. Guards type-narrowing + robustness. + return buildFailure(repoFullName, CROSS_REPO_FAILURE_CATEGORY.OTHER, "Readiness passed without a detected stack.", { + stackDetected: false, + usedDefaultGoalSpec, + executed: true, + }); + } + const cloneImpl = options.cloneRepo ?? defaultCloneRepo; + const agentImpl = options.runCodingAgent ?? defaultRunCodingAgent; + const testImpl = options.runTests ?? defaultRunTests; + const testTimeoutMs = typeof options.testTimeoutMs === "number" && options.testTimeoutMs > 0 + ? options.testTimeoutMs + : DEFAULT_EXECUTION_TEST_TIMEOUT_MS; + const execExtra = { + stackDetected: true, + usedDefaultGoalSpec, + stack, + executed: true, + }; + // Step 1 (setup): clone/checkout the target repo locally. Read-only -- never writes back upstream. + let clone; + try { + clone = await cloneImpl(entry, options); + } + catch (error) { + return buildFailure(repoFullName, CROSS_REPO_FAILURE_CATEGORY.EXEC_SETUP, describeError(error), execExtra); + } + if (clone?.ok !== true) { + return buildFailure(repoFullName, CROSS_REPO_FAILURE_CATEGORY.EXEC_SETUP, clone?.error ?? "Repository clone/checkout failed before the execution loop could start.", execExtra); + } + const workingDirectory = typeof clone.repoPath === "string" && clone.repoPath.trim() + ? clone.repoPath.trim() + : readiness.repoPath || resolveEvaluationRepoPath(entry, options); + // Step 3 (code): run the coding agent to produce a diff. Default is a non-spawning shadow (dry-run). + let agentResult; + try { + agentResult = await agentImpl({ repoFullName, repoPath: workingDirectory, instructions, stack, entry }); + } + catch (error) { + return buildFailure(repoFullName, CROSS_REPO_FAILURE_CATEGORY.OTHER, describeError(error), execExtra); + } + if (agentResult?.ok !== true) { + return buildFailure(repoFullName, CROSS_REPO_FAILURE_CATEGORY.PLAN_COMPILE, agentResult?.error ?? "Coding agent formed a plan but the code phase did not produce a compiling change.", execExtra); + } + const changedFiles = Array.isArray(agentResult.changedFiles) ? [...agentResult.changedFiles] : []; + // Step 4 (test): run the TARGET repo's own test suite locally against the produced change. + const testCommand = stack.testCommand; + const withDiff = { + ...execExtra, + changedFiles, + testCommand: testCommand ?? null, + }; + if (typeof testCommand === "string" && testCommand.trim()) { + let testResult; + try { + testResult = await testImpl(testCommand, workingDirectory, testTimeoutMs); + } + catch (error) { + return buildFailure(repoFullName, CROSS_REPO_FAILURE_CATEGORY.OTHER, describeError(error), withDiff); + } + const exitCode = testResult?.code ?? null; + const withExit = { ...withDiff, testExitCode: exitCode }; + if (testResult?.timedOut === true) { + return buildFailure(repoFullName, CROSS_REPO_FAILURE_CATEGORY.TEST_FAILURE, `Target repo test command timed out after ${testTimeoutMs}ms: ${testCommand}.`, withExit); + } + if (exitCode !== 0) { + return buildFailure(repoFullName, CROSS_REPO_FAILURE_CATEGORY.TEST_FAILURE, `Target repo test command exited ${exitCode ?? "null"} (${testCommand}).`, withExit); + } + // Step 5 (no-op guard): tests are green but the agent changed nothing -> a vacuous fix. + if (changedFiles.length === 0) { + return buildFailure(repoFullName, CROSS_REPO_FAILURE_CATEGORY.NO_OP_DIFF, "Target repo tests passed but the coding agent produced an empty diff (no-op change).", withExit); + } + return buildPass(repoFullName, withExit); + } + // No inferred test command (only reachable when requireTestCommand is not set): still guard the no-op diff. + if (changedFiles.length === 0) { + return buildFailure(repoFullName, CROSS_REPO_FAILURE_CATEGORY.NO_OP_DIFF, "Coding agent produced an empty diff (no-op change) and no test command was available to verify a fix.", withDiff); + } + return buildPass(repoFullName, { ...withDiff, testExitCode: null }); } /** * Run the harness across every repo in a parsed manifest (#4788). @@ -288,6 +526,21 @@ export function runCrossRepoEvaluation(parsed, options = {}) { } return results; } +/** + * DRY-RUN full-execution run across every repo in a parsed manifest (#7634). Sequential (one clone+agent+test + * loop at a time) so the local machine is never hammered. Same options-injection surface as the readiness run, + * plus the clone/agent/test seams. Repos are read/executed-locally-and-discarded; no PR is ever opened. + */ +export async function runCrossRepoExecution(parsed, options = {}) { + const repos = parsed?.manifest?.repos ?? []; + const results = []; + for (const entry of repos) { + if (options.repoFilter && entry.repoFullName !== options.repoFilter) + continue; + results.push(await evaluateRepoExecution(entry, options)); + } + return results; +} /** * Reduce per-repo results to pass/fail counts and whether a strict majority passed (#4788). */ @@ -308,12 +561,14 @@ export function summarizeCrossRepoEvaluation(results) { const total = passed + failed; const majorityPassed = total > 0 ? passed > failed : false; const withoutLoopoverConfig = list.filter((r) => r?.usedDefaultGoalSpec !== false).length; + const executedCount = list.filter((r) => r?.executed === true).length; return { total, passed, failed, majorityPassed, withoutLoopoverConfig, + executedCount, failuresByCategory, }; } @@ -334,6 +589,11 @@ export function formatCrossRepoEvaluationReport(results, summary = summarizeCros if (summary.total > 0) { lines.push(`without loopover-specific target config: ${summary.withoutLoopoverConfig}/${summary.total}`); } + // Dry-run full-execution runs surface how many repos actually entered the clone+agent+test loop (#7634). + // Readiness-only runs never set `executed`, so this line is omitted and the readiness format is unchanged. + if (summary.executedCount > 0) { + lines.push(`dry-run full-execution: ${summary.executedCount}/${summary.total} entered the code+test loop`); + } const categories = Object.entries(summary.failuresByCategory).sort(([a], [b]) => a.localeCompare(b)); if (categories.length > 0) { lines.push("", "failures by category:"); @@ -343,4 +603,4 @@ export function formatCrossRepoEvaluationReport(results, summary = summarizeCros } return lines.join("\n"); } -//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY3Jvc3MtcmVwby1ldmFsdWF0aW9uLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiY3Jvc3MtcmVwby1ldmFsdWF0aW9uLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLGlIQUFpSDtBQUNqSCw4R0FBOEc7QUFDOUcsMkdBQTJHO0FBQzNHLDZHQUE2RztBQUM3RyxxR0FBcUc7QUFFckcsT0FBTyxFQUFFLFVBQVUsRUFBRSxNQUFNLFNBQVMsQ0FBQztBQUVyQyxPQUFPLEVBQUUsbUJBQW1CLEVBQUUsTUFBTSx1QkFBdUIsQ0FBQztBQUM1RCxPQUFPLEVBQUUsb0JBQW9CLEVBQUUsTUFBTSxzQkFBc0IsQ0FBQztBQUM1RCxPQUFPLEVBQUUsa0JBQWtCLEVBQUUsbUJBQW1CLEVBQUUsTUFBTSxpQkFBaUIsQ0FBQztBQUMxRSxPQUFPLEVBQUUsZUFBZSxFQUFFLE1BQU0sc0JBQXNCLENBQUM7QUFHdkQsNkRBQTZEO0FBQzdELE1BQU0sQ0FBQyxNQUFNLDJCQUEyQixHQU1uQyxNQUFNLENBQUMsTUFBTSxDQUFDO0lBQ2pCLGVBQWUsRUFBRSxxQkFBcUI7SUFDdEMsU0FBUyxFQUFFLGVBQWU7SUFDMUIsb0JBQW9CLEVBQUUscUJBQXFCO0lBQzNDLFdBQVcsRUFBRSxhQUFhO0lBQzFCLEtBQUssRUFBRSxPQUFPO0NBQ2YsQ0FBQyxDQUFDO0FBRUg7bUdBQ21HO0FBQ25HLE1BQU0sQ0FBQyxNQUFNLG9DQUFvQyxHQUFtRCxNQUFNLENBQUMsTUFBTSxDQUFDO0lBQ2hILEVBQUUsRUFBRSxFQUFFLGdCQUFnQixFQUFFLE9BQU8sRUFBRSxrQkFBa0IsRUFBRTtJQUNyRCxFQUFFLEVBQUUsRUFBRSxlQUFlLEVBQUUsT0FBTyxFQUFFLGlCQUFpQixFQUFFO0lBQ25ELEVBQUUsRUFBRSxFQUFFLGlCQUFpQixFQUFFLE9BQU8sRUFBRSxxQ0FBcUMsRUFBRTtJQUN6RSxFQUFFLEVBQUUsRUFBRSxlQUFlLEVBQUUsT0FBTyxFQUFFLGdCQUFnQixFQUFFO0NBQ25E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 \ No newline at end of file +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY3Jvc3MtcmVwby1ldmFsdWF0aW9uLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiY3Jvc3MtcmVwby1ldmFsdWF0aW9uLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLGlIQUFpSDtBQUNqSCw4R0FBOEc7QUFDOUcsMkdBQTJHO0FBQzNHLDZHQUE2RztBQUM3RyxxR0FBcUc7QUFFckcsT0FBTyxFQUFFLEtBQUssRUFBRSxNQUFNLG9CQUFvQixDQUFDO0FBQzNDLE9BQU8sRUFBRSxVQUFVLEVBQUUsTUFBTSxTQUFTLENBQUM7QUFFckMsT0FBTyxFQUFFLG1CQUFtQixFQUFFLE1BQU0sdUJBQXVCLENBQUM7QUFDNUQsT0FBTyxFQUFFLG9CQUFvQixFQUFFLE1BQU0sc0JBQXNCLENBQUM7QUFDNUQsT0FBTyxFQUFFLGdCQUFnQixFQUFFLGtCQUFrQixFQUFFLG1CQUFtQixFQUFFLE1BQU0saUJBQWlCLENBQUM7QUFDNUYsT0FBTyxFQUFFLGVBQWUsRUFBRSxNQUFNLHNCQUFzQixDQUFDO0FBR3ZEOzs7O3FCQUlxQjtBQUNyQixNQUFNLENBQUMsTUFBTSwyQkFBMkIsR0FVbkMsTUFBTSxDQUFDLE1BQU0sQ0FBQztJQUNqQixlQUFlLEVBQUUscUJBQXFCO0lBQ3RDLFNBQVMsRUFBRSxlQUFlO0lBQzFCLG9CQUFvQixFQUFFLHFCQUFxQjtJQUMzQyxXQUFXLEVBQUUsYUFBYTtJQUMxQixLQUFLLEVBQUUsT0FBTztJQUNkLDJDQUEyQztJQUMzQyxVQUFVLEVBQUUsZ0JBQWdCLEVBQUUseUVBQXlFO0lBQ3ZHLFlBQVksRUFBRSxrQkFBa0IsRUFBRSxvRUFBb0U7SUFDdEcsWUFBWSxFQUFFLGNBQWMsRUFBRSx5REFBeUQ7SUFDdkYsVUFBVSxFQUFFLFlBQVksRUFBRSxtRUFBbUU7Q0FDOUYsQ0FBQyxDQUFDO0FBRUg7bUdBQ21HO0FBQ25HLE1BQU0sQ0FBQyxNQUFNLG9DQUFvQyxHQUFtRCxNQUFNLENBQUMsTUFBTSxDQUFDO0lBQ2hILEVBQUUsRUFB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 \ No newline at end of file diff --git a/packages/loopover-miner/lib/cross-repo-evaluation.ts b/packages/loopover-miner/lib/cross-repo-evaluation.ts index f579b18aef..f2eeb67517 100644 --- a/packages/loopover-miner/lib/cross-repo-evaluation.ts +++ b/packages/loopover-miner/lib/cross-repo-evaluation.ts @@ -4,27 +4,41 @@ // resolveMinerGoalSpec, buildCodingTaskSpec) and failures are categorized as stack-detection gaps, execution // readiness gaps, leaked loopover assumptions in agent instructions, clone/setup problems, or other. +import { spawn } from "node:child_process"; import { existsSync } from "node:fs"; import { join } from "node:path"; import { buildCodingTaskSpec } from "./coding-task-spec.js"; import { resolveMinerGoalSpec } from "./miner-goal-spec.js"; -import { isValidRepoSegment, resolveRepoCloneDir } from "./repo-clone.js"; +import { ensureRepoCloned, isValidRepoSegment, resolveRepoCloneDir } from "./repo-clone.js"; import { detectRepoStack } from "./stack-detection.js"; import type { RepoStackResult } from "./stack-detection.js"; -/** Failure taxonomy surfaced in per-repo reports (#4788). */ +/** Failure taxonomy surfaced in per-repo reports (#4788). The first five members are the offline + * readiness taxonomy; the four `EXEC_*`/plan/test/no-op members are the dry-run full-execution taxonomy + * (#7634) surfaced only when the `--full-execution` loop actually clones, runs the agent, and runs the + * target repo's own tests. New members are appended in the same frozen literal so existing members are + * never removed. */ export const CROSS_REPO_FAILURE_CATEGORY: Readonly<{ STACK_DETECTION: "stack_detection_gap"; EXECUTION: "execution_gap"; GITTENSOR_ASSUMPTION: "loopover_assumption"; CLONE_SETUP: "clone_setup"; OTHER: "other"; + EXEC_SETUP: "exec_setup_gap"; + PLAN_COMPILE: "plan_compile_gap"; + TEST_FAILURE: "test_failure"; + NO_OP_DIFF: "no_op_diff"; }> = Object.freeze({ STACK_DETECTION: "stack_detection_gap", EXECUTION: "execution_gap", GITTENSOR_ASSUMPTION: "loopover_assumption", CLONE_SETUP: "clone_setup", OTHER: "other", + // Dry-run full-execution taxonomy (#7634): + EXEC_SETUP: "exec_setup_gap", // clone / checkout of the target repo failed before the loop could start + PLAN_COMPILE: "plan_compile_gap", // plan formed but the code phase did not produce a compiling change + TEST_FAILURE: "test_failure", // change compiled but the target repo's own tests failed + NO_OP_DIFF: "no_op_diff", // tests passed but the coding agent produced an empty (no-op) diff }); /** Instruction substrings that indicate a POSITIVE loopover/LoopOver CI assumption leaked into the agent prompt. @@ -62,6 +76,11 @@ export type CrossRepoEvaluationResult = { usedDefaultGoalSpec: boolean | null; assumptionFindings: Array<{ id: string; line: string }>; stack?: RepoStackResult; + /** Full-execution (dry-run) fields (#7634); absent on readiness-only results. */ + executed?: boolean; + changedFiles?: string[]; + testCommand?: string | null; + testExitCode?: number | null; }; export type CrossRepoEvaluationSummary = { @@ -70,9 +89,43 @@ export type CrossRepoEvaluationSummary = { failed: number; majorityPassed: boolean; withoutLoopoverConfig: number; + /** Repos that entered the dry-run clone+agent+test loop (#7634); 0 for a readiness-only run. */ + executedCount: number; failuresByCategory: Record; }; +/** Result of the dry-run clone/checkout seam (#7634). Mirrors repo-clone's EnsureRepoClonedResult. */ +export type CrossRepoExecutionCloneResult = { + ok: boolean; + repoPath?: string; + error?: string; +}; + +/** Context handed to the dry-run coding-agent seam (#7634). */ +export type CrossRepoExecutionAgentContext = { + repoFullName: string; + repoPath: string; + instructions: string; + stack: RepoStackResult; + entry: CrossRepoEvaluationManifestRepo; +}; + +/** Result of the dry-run coding-agent seam (#7634): the produced (canned/real) diff surface. */ +export type CrossRepoExecutionAgentResult = { + ok: boolean; + changedFiles?: readonly string[]; + summary?: string; + error?: string; +}; + +/** Result of the dry-run target-repo test seam (#7634). Mirrors the executeLocalWrite subprocess shape. */ +export type CrossRepoExecutionTestResult = { + code: number | null; + stdout?: string; + stderr?: string; + timedOut?: boolean; +}; + type EvaluateRepoReadinessOptions = { repoPath?: string; resolveRepoPath?: (entry: { repoFullName: string }) => string; @@ -85,6 +138,23 @@ type EvaluateRepoReadinessOptions = { verdict?: string; instructions?: string; }; + // Full-execution (dry-run) seams (#7634). Every one is defaulted with `?? realImpl` exactly like the + // readiness seams above, so unit tests drive the whole loop with fakes and zero real IO. There is NO + // PR-open / forge-write / credential path anywhere in the execution loop -- the boundary is structural. + fullExecution?: boolean; + testTimeoutMs?: number; + cloneRepo?: ( + entry: CrossRepoEvaluationManifestRepo, + options: EvaluateRepoReadinessOptions, + ) => CrossRepoExecutionCloneResult | Promise; + runCodingAgent?: ( + context: CrossRepoExecutionAgentContext, + ) => CrossRepoExecutionAgentResult | Promise; + runTests?: ( + command: string, + cwd: string, + timeoutMs: number, + ) => CrossRepoExecutionTestResult | Promise; }; // True UTF-8 byte count for the size guard (#7223): JS string `.length` is UTF-16 code units, which under-counts @@ -276,20 +346,65 @@ function defaultClaimLedger(repoFullName: string): { listClaims: () => never[] } return { listClaims: () => [] }; } +function describeError(error: unknown): string { + return error instanceof Error ? error.message : String(error); +} + /** - * Evaluate one benchmark repo's miner readiness without running a live coding agent (#4788). + * Compose the synthetic coding-task spec both the readiness gate and the full-execution loop use (#4788 / #7634). + * Shared so the "plan" (spec instructions) is derived identically in both modes -- readiness validates it is + * leak-free; full-execution reuses it as the agent's plan. */ -export function evaluateRepoReadiness( +function composeCrossRepoEvaluationSpec( + repoFullName: string, + repoPath: string, + buildSpecImpl: NonNullable, + detectImpl: (repoPath: string) => RepoStackResult, +): { ready: boolean; verdict?: string; instructions?: string } { + return buildSpecImpl({ + repoFullName, + issue: { + number: 1, + title: "Cross-repo evaluation harness smoke issue", + body: "Synthetic issue used only by the cross-repo evaluation harness.", + labels: ["bug"], + }, + context: { issues: [{ number: 1 }], pullRequests: [] }, + claimLedger: defaultClaimLedger(repoFullName), + workingDirectory: repoPath, + detectRepoStack: detectImpl, + }); +} + +/** Internal readiness evaluation carrying the single composed spec so full-execution can reuse it (#7634). */ +type ReadinessEvaluation = { + result: CrossRepoEvaluationResult; + /** The composed coding-task plan/instructions when the repo passed readiness; "" otherwise. */ + instructions: string; + /** The local working-copy path readiness resolved (may be unusable on an early failure). */ + repoPath: string; +}; + +/** + * Readiness core (#4788 / #7634). Identical logic to {@link evaluateRepoReadiness} but also returns the plan + * instructions from the SINGLE `buildCodingTaskSpec` call, so the full-execution loop never re-invokes that + * (production `buildCodingTaskSpec` is side-effecting -- it writes acceptance-criteria.json -- and not idempotent). + */ +function evaluateRepoReadinessCore( entry: CrossRepoEvaluationManifestRepo, options: EvaluateRepoReadinessOptions = {}, -): CrossRepoEvaluationResult { +): ReadinessEvaluation { const repoFullName = entry?.repoFullName; if (typeof repoFullName !== "string" || !normalizeCrossRepoFullName(repoFullName)) { - return buildFailure( - typeof repoFullName === "string" ? repoFullName : "(invalid)", - CROSS_REPO_FAILURE_CATEGORY.OTHER, - "Benchmark entry is missing a valid owner/repo name.", - ); + return { + result: buildFailure( + typeof repoFullName === "string" ? repoFullName : "(invalid)", + CROSS_REPO_FAILURE_CATEGORY.OTHER, + "Benchmark entry is missing a valid owner/repo name.", + ), + instructions: "", + repoPath: "", + }; } const existsImpl = options.existsSync ?? existsSync; @@ -301,11 +416,15 @@ export function evaluateRepoReadiness( const repoPath = resolveEvaluationRepoPath(entry, options); if (!existsImpl(repoPath)) { - return buildFailure( - repoFullName, - CROSS_REPO_FAILURE_CATEGORY.CLONE_SETUP, - `Repository path does not exist: ${repoPath}. Clone the repo or set LOOPOVER_MINER_REPO_CLONE_DIR.`, - ); + return { + result: buildFailure( + repoFullName, + CROSS_REPO_FAILURE_CATEGORY.CLONE_SETUP, + `Repository path does not exist: ${repoPath}. Clone the repo or set LOOPOVER_MINER_REPO_CLONE_DIR.`, + ), + instructions: "", + repoPath, + }; } const goalSpec = goalSpecImpl(repoPath); @@ -313,67 +432,310 @@ export function evaluateRepoReadiness( const stack = detectImpl(repoPath); if (stack?.detected !== true) { - return buildFailure( - repoFullName, - CROSS_REPO_FAILURE_CATEGORY.STACK_DETECTION, - stack?.reason ?? "Stack auto-detection did not recognize this repository.", - { stackDetected: false, usedDefaultGoalSpec }, - ); + return { + result: buildFailure( + repoFullName, + CROSS_REPO_FAILURE_CATEGORY.STACK_DETECTION, + stack?.reason ?? "Stack auto-detection did not recognize this repository.", + { stackDetected: false, usedDefaultGoalSpec }, + ), + instructions: "", + repoPath, + }; } if (entry.requireTestCommand === true && !stack.testCommand) { - return buildFailure( - repoFullName, - CROSS_REPO_FAILURE_CATEGORY.EXECUTION, - "Stack detection succeeded but no test command was inferred while requireTestCommand is set.", - { stackDetected: true, usedDefaultGoalSpec, stack }, - ); + return { + result: buildFailure( + repoFullName, + CROSS_REPO_FAILURE_CATEGORY.EXECUTION, + "Stack detection succeeded but no test command was inferred while requireTestCommand is set.", + { stackDetected: true, usedDefaultGoalSpec, stack }, + ), + instructions: "", + repoPath, + }; } let specResult; try { - specResult = buildSpecImpl({ - repoFullName, - issue: { - number: 1, - title: "Cross-repo evaluation harness smoke issue", - body: "Synthetic issue used only by the cross-repo evaluation harness.", - labels: ["bug"], - }, - context: { issues: [{ number: 1 }], pullRequests: [] }, - claimLedger: defaultClaimLedger(repoFullName), - workingDirectory: repoPath, - detectRepoStack: detectImpl, - }); + specResult = composeCrossRepoEvaluationSpec(repoFullName, repoPath, buildSpecImpl, detectImpl); } catch (error) { - const message = error instanceof Error ? error.message : String(error); - return buildFailure(repoFullName, CROSS_REPO_FAILURE_CATEGORY.OTHER, message, { - stackDetected: true, + return { + result: buildFailure(repoFullName, CROSS_REPO_FAILURE_CATEGORY.OTHER, describeError(error), { + stackDetected: true, + usedDefaultGoalSpec, + stack, + }), + instructions: "", + repoPath, + }; + } + + if (specResult?.ready !== true) { + return { + result: buildFailure( + repoFullName, + CROSS_REPO_FAILURE_CATEGORY.EXECUTION, + `Coding task spec is not ready (verdict: ${specResult?.verdict ?? "unknown"}).`, + { stackDetected: true, usedDefaultGoalSpec, stack }, + ), + instructions: "", + repoPath, + }; + } + + const assumptionFindings = scanPositiveLoopoverAssumptions(specResult.instructions ?? ""); + if (assumptionFindings.length > 0) { + return { + result: buildFailure( + repoFullName, + CROSS_REPO_FAILURE_CATEGORY.GITTENSOR_ASSUMPTION, + `Agent instructions leak loopover-specific assumptions (${assumptionFindings.map((f) => f.id).join(", ")}).`, + { stackDetected: true, usedDefaultGoalSpec, stack, assumptionFindings }, + ), + instructions: "", + repoPath, + }; + } + + return { + result: buildPass(repoFullName, { usedDefaultGoalSpec, stack }), + instructions: specResult.instructions ?? "", + repoPath, + }; +} + +/** + * Evaluate one benchmark repo's miner readiness without running a live coding agent (#4788). + */ +export function evaluateRepoReadiness( + entry: CrossRepoEvaluationManifestRepo, + options: EvaluateRepoReadinessOptions = {}, +): CrossRepoEvaluationResult { + return evaluateRepoReadinessCore(entry, options).result; +} + +/** 10-minute cap on a target repo's own test suite when the caller does not override `testTimeoutMs` (#7634). */ +export const DEFAULT_EXECUTION_TEST_TIMEOUT_MS: number = 600_000; + +/** + * Default clone seam (#7634): a read-only local clone/checkout. When the working copy is already on disk (the + * normal case -- readiness resolves the same path via entry.fixturePath / options.repoPath / the clone cache and + * has already gated its existence), it is reused directly with NO network. Otherwise it falls back to repo-clone's + * ensureRepoCloned. Either way it never writes back to the third-party repo and never opens a PR -- it is the + * "local clone" the dry-run permits. + */ +async function defaultCloneRepo( + entry: CrossRepoEvaluationManifestRepo, + options: EvaluateRepoReadinessOptions, +): Promise { + const existsImpl = options.existsSync ?? existsSync; + const localPath = resolveEvaluationRepoPath(entry, options); + if (localPath && existsImpl(localPath)) { + return { ok: true, repoPath: localPath }; + } + const result = await ensureRepoCloned(entry.repoFullName, { + env: (options.env ?? process.env) as Record, + }); + const out: CrossRepoExecutionCloneResult = { ok: result.ok, repoPath: result.repoPath }; + if (result.error !== undefined) out.error = result.error; + return out; +} + +/** + * Default coding-agent seam (#7634): a DRY-RUN SHADOW that never spawns a coding agent, never forwards + * credentials, and produces no diff. A real diff is produced only when a caller injects options.runCodingAgent + * (unit tests inject a fake CodingAgentDriver-shaped runner; a future live mode would inject the real driver). + */ +function defaultRunCodingAgent(_context: CrossRepoExecutionAgentContext): CrossRepoExecutionAgentResult { + return { + ok: true, + changedFiles: [], + summary: "dry-run: coding agent not executed (shadow); inject options.runCodingAgent to produce a diff.", + }; +} + +/** + * Default test seam (#7634): run the TARGET repo's own inferred test command locally via `sh -c`, mirroring the + * executeLocalWrite subprocess wrapper (resolve-not-reject; SIGKILL on timeout). Purely local; no network. + */ +function defaultRunTests(command: string, cwd: string, timeoutMs: number): Promise { + return new Promise((resolve) => { + let stdout = ""; + let stderr = ""; + let timedOut = false; + let child; + try { + child = spawn("sh", ["-c", command], { cwd }); + } catch (error) { + resolve({ code: null, stdout, stderr: describeError(error), timedOut }); + return; + } + const timer = setTimeout(() => { + timedOut = true; + child.kill("SIGKILL"); + }, timeoutMs); + child.stdout?.on("data", (chunk) => { + stdout += String(chunk); + }); + child.stderr?.on("data", (chunk) => { + stderr += String(chunk); + }); + child.on("error", (error) => { + clearTimeout(timer); + resolve({ code: null, stdout, stderr: `${stderr}${describeError(error)}`, timedOut }); + }); + child.on("close", (code) => { + clearTimeout(timer); + resolve({ code, stdout, stderr, timedOut }); + }); + }); +} + +/** + * DRY-RUN full-execution evaluation of one benchmark repo (#7634). Reuses the readiness gate unchanged, then -- + * only for a repo that already passes readiness -- runs the discover->plan->code->test loop LOCALLY: + * + * 1. clone/checkout the repo (setup) -> EXEC_SETUP on failure + * 2. reuse the readiness-composed spec (plan) -- no second (side-effecting) buildCodingTaskSpec call + * 3. run the coding agent to produce a diff -> PLAN_COMPILE when the code phase does not converge + * 4. run the target repo's own tests locally -> TEST_FAILURE when the suite is red + * 5. no-op guard -> NO_OP_DIFF when tests pass but the diff is empty + * + * Every side-effecting step (clone, agent, tests) is behind an injectable `options.*` seam so unit tests drive + * the whole loop with fakes and zero real IO. There is deliberately NO PR-open / forge-write / credential path: + * the harness clones and executes locally, then discards. Readiness-mode behavior is untouched. + */ +export async function evaluateRepoExecution( + entry: CrossRepoEvaluationManifestRepo, + options: EvaluateRepoReadinessOptions = {}, +): Promise { + // Step 2 (plan) is folded into the readiness gate: the plan instructions come from readiness's SINGLE + // buildCodingTaskSpec call (production buildCodingTaskSpec is side-effecting + non-idempotent, so we must + // never call it twice). + const readiness = evaluateRepoReadinessCore(entry, options); + if (!readiness.result.passed) return readiness.result; + + const repoFullName = readiness.result.repoFullName; + const usedDefaultGoalSpec = readiness.result.usedDefaultGoalSpec; + const instructions = readiness.instructions; + const stack = readiness.result.stack; + if (!stack || stack.detected !== true) { + // Defensive: a passing readiness always carries a detected stack. Guards type-narrowing + robustness. + return buildFailure(repoFullName, CROSS_REPO_FAILURE_CATEGORY.OTHER, "Readiness passed without a detected stack.", { + stackDetected: false, usedDefaultGoalSpec, - stack, + executed: true, }); } - if (specResult?.ready !== true) { + const cloneImpl = options.cloneRepo ?? defaultCloneRepo; + const agentImpl = options.runCodingAgent ?? defaultRunCodingAgent; + const testImpl = options.runTests ?? defaultRunTests; + const testTimeoutMs = + typeof options.testTimeoutMs === "number" && options.testTimeoutMs > 0 + ? options.testTimeoutMs + : DEFAULT_EXECUTION_TEST_TIMEOUT_MS; + + const execExtra: Partial = { + stackDetected: true, + usedDefaultGoalSpec, + stack, + executed: true, + }; + + // Step 1 (setup): clone/checkout the target repo locally. Read-only -- never writes back upstream. + let clone: CrossRepoExecutionCloneResult; + try { + clone = await cloneImpl(entry, options); + } catch (error) { + return buildFailure(repoFullName, CROSS_REPO_FAILURE_CATEGORY.EXEC_SETUP, describeError(error), execExtra); + } + if (clone?.ok !== true) { return buildFailure( repoFullName, - CROSS_REPO_FAILURE_CATEGORY.EXECUTION, - `Coding task spec is not ready (verdict: ${specResult?.verdict ?? "unknown"}).`, - { stackDetected: true, usedDefaultGoalSpec, stack }, + CROSS_REPO_FAILURE_CATEGORY.EXEC_SETUP, + clone?.error ?? "Repository clone/checkout failed before the execution loop could start.", + execExtra, ); } + const workingDirectory = + typeof clone.repoPath === "string" && clone.repoPath.trim() + ? clone.repoPath.trim() + : readiness.repoPath || resolveEvaluationRepoPath(entry, options); - const assumptionFindings = scanPositiveLoopoverAssumptions(specResult.instructions ?? ""); - if (assumptionFindings.length > 0) { + // Step 3 (code): run the coding agent to produce a diff. Default is a non-spawning shadow (dry-run). + let agentResult: CrossRepoExecutionAgentResult; + try { + agentResult = await agentImpl({ repoFullName, repoPath: workingDirectory, instructions, stack, entry }); + } catch (error) { + return buildFailure(repoFullName, CROSS_REPO_FAILURE_CATEGORY.OTHER, describeError(error), execExtra); + } + if (agentResult?.ok !== true) { return buildFailure( repoFullName, - CROSS_REPO_FAILURE_CATEGORY.GITTENSOR_ASSUMPTION, - `Agent instructions leak loopover-specific assumptions (${assumptionFindings.map((f) => f.id).join(", ")}).`, - { stackDetected: true, usedDefaultGoalSpec, stack, assumptionFindings }, + CROSS_REPO_FAILURE_CATEGORY.PLAN_COMPILE, + agentResult?.error ?? "Coding agent formed a plan but the code phase did not produce a compiling change.", + execExtra, ); } + const changedFiles = Array.isArray(agentResult.changedFiles) ? [...agentResult.changedFiles] : []; + + // Step 4 (test): run the TARGET repo's own test suite locally against the produced change. + const testCommand = stack.testCommand; + const withDiff: Partial = { + ...execExtra, + changedFiles, + testCommand: testCommand ?? null, + }; + if (typeof testCommand === "string" && testCommand.trim()) { + let testResult: CrossRepoExecutionTestResult; + try { + testResult = await testImpl(testCommand, workingDirectory, testTimeoutMs); + } catch (error) { + return buildFailure(repoFullName, CROSS_REPO_FAILURE_CATEGORY.OTHER, describeError(error), withDiff); + } + const exitCode = testResult?.code ?? null; + const withExit: Partial = { ...withDiff, testExitCode: exitCode }; + if (testResult?.timedOut === true) { + return buildFailure( + repoFullName, + CROSS_REPO_FAILURE_CATEGORY.TEST_FAILURE, + `Target repo test command timed out after ${testTimeoutMs}ms: ${testCommand}.`, + withExit, + ); + } + if (exitCode !== 0) { + return buildFailure( + repoFullName, + CROSS_REPO_FAILURE_CATEGORY.TEST_FAILURE, + `Target repo test command exited ${exitCode ?? "null"} (${testCommand}).`, + withExit, + ); + } + // Step 5 (no-op guard): tests are green but the agent changed nothing -> a vacuous fix. + if (changedFiles.length === 0) { + return buildFailure( + repoFullName, + CROSS_REPO_FAILURE_CATEGORY.NO_OP_DIFF, + "Target repo tests passed but the coding agent produced an empty diff (no-op change).", + withExit, + ); + } + return buildPass(repoFullName, withExit); + } - return buildPass(repoFullName, { usedDefaultGoalSpec, stack }); + // No inferred test command (only reachable when requireTestCommand is not set): still guard the no-op diff. + if (changedFiles.length === 0) { + return buildFailure( + repoFullName, + CROSS_REPO_FAILURE_CATEGORY.NO_OP_DIFF, + "Coding agent produced an empty diff (no-op change) and no test command was available to verify a fix.", + withDiff, + ); + } + return buildPass(repoFullName, { ...withDiff, testExitCode: null }); } /** @@ -392,6 +754,24 @@ export function runCrossRepoEvaluation( return results; } +/** + * DRY-RUN full-execution run across every repo in a parsed manifest (#7634). Sequential (one clone+agent+test + * loop at a time) so the local machine is never hammered. Same options-injection surface as the readiness run, + * plus the clone/agent/test seams. Repos are read/executed-locally-and-discarded; no PR is ever opened. + */ +export async function runCrossRepoExecution( + parsed: ParsedCrossRepoEvaluationManifest, + options: { repoFilter?: string } & EvaluateRepoReadinessOptions = {}, +): Promise { + const repos = parsed?.manifest?.repos ?? []; + const results: CrossRepoEvaluationResult[] = []; + for (const entry of repos) { + if (options.repoFilter && entry.repoFullName !== options.repoFilter) continue; + results.push(await evaluateRepoExecution(entry, options)); + } + return results; +} + /** * Reduce per-repo results to pass/fail counts and whether a strict majority passed (#4788). */ @@ -412,12 +792,14 @@ export function summarizeCrossRepoEvaluation(results: CrossRepoEvaluationResult[ const total = passed + failed; const majorityPassed = total > 0 ? passed > failed : false; const withoutLoopoverConfig = list.filter((r) => r?.usedDefaultGoalSpec !== false).length; + const executedCount = list.filter((r) => r?.executed === true).length; return { total, passed, failed, majorityPassed, withoutLoopoverConfig, + executedCount, failuresByCategory, }; } @@ -445,6 +827,11 @@ export function formatCrossRepoEvaluationReport( if (summary.total > 0) { lines.push(`without loopover-specific target config: ${summary.withoutLoopoverConfig}/${summary.total}`); } + // Dry-run full-execution runs surface how many repos actually entered the clone+agent+test loop (#7634). + // Readiness-only runs never set `executed`, so this line is omitted and the readiness format is unchanged. + if (summary.executedCount > 0) { + lines.push(`dry-run full-execution: ${summary.executedCount}/${summary.total} entered the code+test loop`); + } const categories = Object.entries(summary.failuresByCategory).sort(([a], [b]) => a.localeCompare(b)); if (categories.length > 0) { lines.push("", "failures by category:"); diff --git a/packages/loopover-miner/scripts/cross-repo-evaluation.d.mts b/packages/loopover-miner/scripts/cross-repo-evaluation.d.mts index c263aa2347..b08339b3c0 100644 --- a/packages/loopover-miner/scripts/cross-repo-evaluation.d.mts +++ b/packages/loopover-miner/scripts/cross-repo-evaluation.d.mts @@ -1,11 +1,22 @@ import type { + CrossRepoEvaluationManifestRepo, CrossRepoEvaluationResult, CrossRepoEvaluationSummary, + CrossRepoExecutionAgentContext, + CrossRepoExecutionAgentResult, + CrossRepoExecutionCloneResult, + CrossRepoExecutionTestResult, ParsedCrossRepoEvaluationManifest, } from "../lib/cross-repo-evaluation.js"; export type CrossRepoEvaluationCliArgs = - | { manifestPath: string; json: boolean; repoFilter: string | null; requireMajority: boolean } + | { + manifestPath: string; + json: boolean; + repoFilter: string | null; + requireMajority: boolean; + fullExecution: boolean; + } | { error: string } | { help: true }; @@ -15,6 +26,32 @@ export type CrossRepoEvaluationCliOptions = { repoFilter?: string | null; }; +/** Full-execution (dry-run) CLI options (#7634): the readiness options plus the injectable clone/agent/test seams + * that unit tests replace with fakes for zero real IO. */ +export type CrossRepoExecutionCliOptions = CrossRepoEvaluationCliOptions & { + fullExecution?: boolean; + testTimeoutMs?: number; + env?: Record; + existsSync?: (path: string) => boolean; + buildCodingTaskSpec?: (input: Record) => { + ready: boolean; + verdict?: string; + instructions?: string; + }; + cloneRepo?: ( + entry: CrossRepoEvaluationManifestRepo, + options: CrossRepoExecutionCliOptions, + ) => CrossRepoExecutionCloneResult | Promise; + runCodingAgent?: ( + context: CrossRepoExecutionAgentContext, + ) => CrossRepoExecutionAgentResult | Promise; + runTests?: ( + command: string, + cwd: string, + timeoutMs: number, + ) => CrossRepoExecutionTestResult | Promise; +}; + export declare function resolveDefaultManifestPath(): string; export declare function parseCrossRepoEvaluationArgs(argv?: readonly string[]): CrossRepoEvaluationCliArgs; @@ -26,3 +63,11 @@ export declare function runCrossRepoEvaluationCli(options?: CrossRepoEvaluationC results: CrossRepoEvaluationResult[]; summary: CrossRepoEvaluationSummary; }; + +/** DRY-RUN full-execution driver (#7634): clones each benchmark repo locally, runs the discover->plan->code->test + * loop, and runs the target repo's own tests. Async; never opens a PR. */ +export declare function runCrossRepoExecutionCli(options?: CrossRepoExecutionCliOptions): Promise<{ + parsed: ParsedCrossRepoEvaluationManifest; + results: CrossRepoEvaluationResult[]; + summary: CrossRepoEvaluationSummary; +}>; diff --git a/packages/loopover-miner/scripts/cross-repo-evaluation.mjs b/packages/loopover-miner/scripts/cross-repo-evaluation.mjs index 04de1d9fa4..1e65c2dee2 100644 --- a/packages/loopover-miner/scripts/cross-repo-evaluation.mjs +++ b/packages/loopover-miner/scripts/cross-repo-evaluation.mjs @@ -7,6 +7,7 @@ import { formatCrossRepoEvaluationReport, parseCrossRepoEvaluationManifest, runCrossRepoEvaluation, + runCrossRepoExecution, summarizeCrossRepoEvaluation, } from "../lib/cross-repo-evaluation.js"; @@ -22,6 +23,7 @@ export function parseCrossRepoEvaluationArgs(argv) { let json = false; let repoFilter = null; let requireMajority = false; + let fullExecution = false; for (let i = 0; i < args.length; i += 1) { const token = args[i]; if (token === "--json") { @@ -32,6 +34,10 @@ export function parseCrossRepoEvaluationArgs(argv) { requireMajority = true; continue; } + if (token === "--full-execution") { + fullExecution = true; + continue; + } if (token === "--manifest") { const value = args[i + 1]; if (!value) return { error: "Missing value for --manifest." }; @@ -51,7 +57,7 @@ export function parseCrossRepoEvaluationArgs(argv) { } return { error: `Unknown argument: ${token}` }; } - return { manifestPath, json, repoFilter, requireMajority }; + return { manifestPath, json, repoFilter, requireMajority, fullExecution }; } export function loadCrossRepoEvaluationManifest(manifestPath) { @@ -66,6 +72,21 @@ export function runCrossRepoEvaluationCli(options = {}) { return { parsed, results, summary }; } +// DRY-RUN full-execution driver (#7634): clones each benchmark repo locally, runs the discover->plan->code->test +// loop, runs the target repo's own tests, and reports execution-specific categories. Async because the loop +// clones and spawns local subprocesses. Injectable seams (cloneRepo/runCodingAgent/runTests/...) are forwarded +// straight through, so unit tests drive it with fakes and zero real IO. No PR is ever opened. +export async function runCrossRepoExecutionCli(options = {}) { + const parsed = options.parsed ?? loadCrossRepoEvaluationManifest(options.manifestPath ?? resolveDefaultManifestPath()); + const results = await runCrossRepoExecution(parsed, { + ...options, + repoFilter: options.repoFilter ?? null, + fullExecution: true, + }); + const summary = summarizeCrossRepoEvaluation(results); + return { parsed, results, summary }; +} + function printHelp() { console.log( [ @@ -79,6 +100,9 @@ function printHelp() { " --repo Evaluate a single benchmark entry", " --json Emit machine-readable JSON on stdout", " --require-majority Exit 1 unless a strict majority of repos pass", + " --full-execution DRY-RUN: clone each repo, run the discover->plan->code->test loop locally, and", + " run the target repo's own tests (no PR is ever opened). Reports execution", + " categories: exec_setup_gap, plan_compile_gap, test_failure, no_op_diff.", " -h, --help Show this help", "", "Prerequisite: clone benchmark repos into LOOPOVER_MINER_REPO_CLONE_DIR (see docs/cross-repo-evaluation.md).", @@ -86,7 +110,7 @@ function printHelp() { ); } -function main() { +async function main() { const parsedArgs = parseCrossRepoEvaluationArgs(); if (parsedArgs.help) { printHelp(); @@ -97,7 +121,9 @@ function main() { return 2; } - const { parsed, results, summary } = runCrossRepoEvaluationCli(parsedArgs); + const { parsed, results, summary } = parsedArgs.fullExecution + ? await runCrossRepoExecutionCli(parsedArgs) + : runCrossRepoEvaluationCli(parsedArgs); if (parsedArgs.json) { console.log(JSON.stringify({ warnings: parsed.warnings, results, summary }, null, 2)); } else { @@ -112,5 +138,7 @@ function main() { } if (import.meta.url === pathToFileURL(process.argv[1] ?? "").href) { - process.exitCode = main(); + main().then((code) => { + process.exitCode = code; + }); } diff --git a/test/unit/miner-cross-repo-evaluation.test.ts b/test/unit/miner-cross-repo-evaluation.test.ts index 1f1de6a21d..e2e60f035a 100644 --- a/test/unit/miner-cross-repo-evaluation.test.ts +++ b/test/unit/miner-cross-repo-evaluation.test.ts @@ -12,10 +12,12 @@ import { DEFAULT_CROSS_REPO_MANIFEST_RELATIVE_PATH, MAX_CROSS_REPO_MANIFEST_BYTES, formatCrossRepoEvaluationReport, + evaluateRepoExecution, evaluateRepoReadiness, normalizeCrossRepoFullName, parseCrossRepoEvaluationManifest, runCrossRepoEvaluation, + runCrossRepoExecution, scanPositiveLoopoverAssumptions, summarizeCrossRepoEvaluation, } from "../../packages/loopover-miner/lib/cross-repo-evaluation.js"; @@ -25,6 +27,7 @@ import { parseCrossRepoEvaluationArgs, resolveDefaultManifestPath, runCrossRepoEvaluationCli, + runCrossRepoExecutionCli, } from "../../packages/loopover-miner/scripts/cross-repo-evaluation.mjs"; const roots: string[] = []; @@ -371,6 +374,204 @@ describe("cross-repo evaluation harness (#4788)", () => { }); }); + describe("evaluateRepoExecution --full-execution (dry-run) (#7634)", () => { + // A node fixture whose real stack detection yields a non-null test command, so readiness passes and the + // execution loop reaches the clone/agent/test steps. + const nodeRepo = () => tempRepo({ "package.json": pkg({ scripts: { test: "node --test" } }) }); + const readySpec = () => ({ ready: true, instructions: "Fix the failing test." }); + + it("returns the readiness failure unchanged when the repo is not even ready (reuses the gate)", async () => { + const result = await evaluateRepoExecution( + { repoFullName: "acme/missing", requireTestCommand: false }, + { repoPath: "/tmp/definitely-missing-repo-path", existsSync: () => false }, + ); + expect(result.passed).toBe(false); + expect(result.failureCategory).toBe(CROSS_REPO_FAILURE_CATEGORY.CLONE_SETUP); + // Readiness-only failure never entered the execution loop. + expect(result.executed).toBeUndefined(); + }); + + it("fails exec_setup_gap when the clone step reports not-ok", async () => { + const repoPath = nodeRepo(); + const result = await evaluateRepoExecution( + { repoFullName: "acme/clone-fail", requireTestCommand: false }, + { + repoPath, + existsSync: () => true, + buildCodingTaskSpec: readySpec, + cloneRepo: () => ({ ok: false, error: "git clone exited 128" }), + }, + ); + expect(result.failureCategory).toBe(CROSS_REPO_FAILURE_CATEGORY.EXEC_SETUP); + expect(result.reason).toContain("128"); + expect(result.executed).toBe(true); + }); + + it("fails exec_setup_gap when the clone step throws", async () => { + const repoPath = nodeRepo(); + const result = await evaluateRepoExecution( + { repoFullName: "acme/clone-throws", requireTestCommand: false }, + { + repoPath, + existsSync: () => true, + buildCodingTaskSpec: readySpec, + cloneRepo: () => { + throw new Error("network down"); + }, + }, + ); + expect(result.failureCategory).toBe(CROSS_REPO_FAILURE_CATEGORY.EXEC_SETUP); + expect(result.reason).toBe("network down"); + }); + + it("fails plan_compile_gap when the coding agent does not converge into a compiling change", async () => { + const repoPath = nodeRepo(); + const result = await evaluateRepoExecution( + { repoFullName: "acme/agent-fail", requireTestCommand: false }, + { + repoPath, + existsSync: () => true, + buildCodingTaskSpec: readySpec, + cloneRepo: () => ({ ok: true, repoPath }), + runCodingAgent: () => ({ ok: false, error: "compile error: TS2322" }), + }, + ); + expect(result.failureCategory).toBe(CROSS_REPO_FAILURE_CATEGORY.PLAN_COMPILE); + expect(result.reason).toContain("TS2322"); + }); + + it("fails other when the coding agent throws", async () => { + const repoPath = nodeRepo(); + const result = await evaluateRepoExecution( + { repoFullName: "acme/agent-throws", requireTestCommand: false }, + { + repoPath, + existsSync: () => true, + buildCodingTaskSpec: readySpec, + cloneRepo: () => ({ ok: true, repoPath }), + runCodingAgent: () => { + throw new Error("agent-boom"); + }, + }, + ); + expect(result.failureCategory).toBe(CROSS_REPO_FAILURE_CATEGORY.OTHER); + expect(result.reason).toBe("agent-boom"); + }); + + it("fails test_failure when the target repo's own tests are red", async () => { + const repoPath = nodeRepo(); + let ranCommand: string | null = null; + const result = await evaluateRepoExecution( + { repoFullName: "acme/tests-red", requireTestCommand: true }, + { + repoPath, + existsSync: () => true, + buildCodingTaskSpec: readySpec, + cloneRepo: () => ({ ok: true, repoPath }), + runCodingAgent: () => ({ ok: true, changedFiles: ["src/foo.ts", "src/foo.test.ts"], summary: "guard + test" }), + runTests: (command) => { + ranCommand = command; + return { code: 1, stdout: "", stderr: "1 failing", timedOut: false }; + }, + }, + ); + expect(result.failureCategory).toBe(CROSS_REPO_FAILURE_CATEGORY.TEST_FAILURE); + expect(result.testExitCode).toBe(1); + expect(ranCommand).toBeTruthy(); // the detected test command was actually handed to the runner + expect(result.changedFiles).toEqual(["src/foo.ts", "src/foo.test.ts"]); + }); + + it("fails test_failure when the target repo's own tests time out", async () => { + const repoPath = nodeRepo(); + const result = await evaluateRepoExecution( + { repoFullName: "acme/tests-timeout", requireTestCommand: true }, + { + repoPath, + existsSync: () => true, + buildCodingTaskSpec: readySpec, + cloneRepo: () => ({ ok: true, repoPath }), + runCodingAgent: () => ({ ok: true, changedFiles: ["src/foo.ts"] }), + runTests: () => ({ code: null, stdout: "", stderr: "", timedOut: true }), + }, + ); + expect(result.failureCategory).toBe(CROSS_REPO_FAILURE_CATEGORY.TEST_FAILURE); + expect(result.reason).toContain("timed out"); + }); + + it("fails no_op_diff when tests pass but the agent produced an empty diff", async () => { + const repoPath = nodeRepo(); + const result = await evaluateRepoExecution( + { repoFullName: "acme/no-op", requireTestCommand: true }, + { + repoPath, + existsSync: () => true, + buildCodingTaskSpec: readySpec, + cloneRepo: () => ({ ok: true, repoPath }), + runCodingAgent: () => ({ ok: true, changedFiles: [] }), + runTests: () => ({ code: 0, stdout: "ok", stderr: "", timedOut: false }), + }, + ); + expect(result.failureCategory).toBe(CROSS_REPO_FAILURE_CATEGORY.NO_OP_DIFF); + expect(result.testExitCode).toBe(0); + expect(result.changedFiles).toEqual([]); + }); + + it("passes end-to-end when clone, agent, and target tests all succeed with a real diff", async () => { + const repoPath = nodeRepo(); + const result = await evaluateRepoExecution( + { repoFullName: "acme/full-pass", requireTestCommand: true }, + { + repoPath, + existsSync: () => true, + buildCodingTaskSpec: readySpec, + cloneRepo: () => ({ ok: true, repoPath }), + runCodingAgent: () => ({ ok: true, changedFiles: ["src/fix.ts"], summary: "applied fix" }), + runTests: () => ({ code: 0, stdout: "all green", stderr: "", timedOut: false }), + }, + ); + expect(result.passed).toBe(true); + expect(result.executed).toBe(true); + expect(result.changedFiles).toEqual(["src/fix.ts"]); + expect(result.testExitCode).toBe(0); + expect(result.failureCategory).toBeNull(); + }); + + it("runCrossRepoExecution drives every manifest repo through the dry-run loop", async () => { + const repoA = nodeRepo(); + const repoB = nodeRepo(); + const parsed = parseCrossRepoEvaluationManifest( + JSON.stringify({ + repos: [ + { repoFullName: "acme/exec-a", fixturePath: repoA, requireTestCommand: true }, + { repoFullName: "acme/exec-b", fixturePath: repoB, requireTestCommand: true }, + ], + }), + ); + const results = await runCrossRepoExecution(parsed, { + existsSync: () => true, + buildCodingTaskSpec: readySpec, + cloneRepo: (entry) => ({ ok: true, repoPath: entry.fixturePath ?? "" }), + runCodingAgent: (context) => + context.repoFullName.includes("exec-b") + ? { ok: true, changedFiles: [] } // exec-b makes no change -> no_op_diff + : { ok: true, changedFiles: ["src/a.ts"] }, + runTests: () => ({ code: 0, stdout: "", stderr: "", timedOut: false }), + }); + expect(results).toHaveLength(2); + expect(results[0]?.passed).toBe(true); + expect(results[1]?.failureCategory).toBe(CROSS_REPO_FAILURE_CATEGORY.NO_OP_DIFF); + + const summary = summarizeCrossRepoEvaluation(results); + expect(summary.executedCount).toBe(2); + expect(summary.passed).toBe(1); + expect(summary.failuresByCategory[CROSS_REPO_FAILURE_CATEGORY.NO_OP_DIFF]).toBe(1); + + const report = formatCrossRepoEvaluationReport(results, summary); + expect(report).toContain("dry-run full-execution: 2/2 entered the code+test loop"); + expect(report).toContain("- no_op_diff: 1"); + }); + }); + describe("runCrossRepoEvaluation + summarizeCrossRepoEvaluation", () => { it("filters to a single repo and computes majority + category counts", () => { const parsed = parseCrossRepoEvaluationManifest( @@ -495,12 +696,23 @@ describe("cross-repo evaluation harness (#4788)", () => { json: true, repoFilter: "acme/widgets", requireMajority: true, + fullExecution: false, }); expect(parseCrossRepoEvaluationArgs(["--manifest"])).toEqual({ error: "Missing value for --manifest." }); expect(parseCrossRepoEvaluationArgs(["--nope"])).toEqual({ error: "Unknown argument: --nope" }); expect(parseCrossRepoEvaluationArgs(["--help"])).toEqual({ help: true }); }); + it("parses the --full-execution dry-run flag (#7634)", () => { + expect(parseCrossRepoEvaluationArgs(["--full-execution"])).toEqual({ + manifestPath: resolveDefaultManifestPath(), + json: false, + repoFilter: null, + requireMajority: false, + fullExecution: true, + }); + }); + it("runs the harness driver against a fixture manifest", () => { const repoPath = tempRepo({ "package.json": pkg({ scripts: { test: "node --test" } }), @@ -523,6 +735,50 @@ describe("cross-repo evaluation harness (#4788)", () => { expect(formatCrossRepoEvaluationReport(results, summary)).toContain("PASS acme/fixture"); }); + it("runCrossRepoExecutionCli drives a dry-run full-execution loop over >=2 repos with injected seams (#7634)", async () => { + const repoA = tempRepo({ "package.json": pkg({ scripts: { test: "node --test" } }) }); + const repoB = tempRepo({ "package.json": pkg({ scripts: { test: "node --test" } }) }); + const manifestDir = tempRepo(); + writeFileSync( + join(manifestDir, "manifest.json"), + JSON.stringify({ + repos: [ + { repoFullName: "acme/exec-pass", fixturePath: repoA, requireTestCommand: true }, + { repoFullName: "acme/exec-red", fixturePath: repoB, requireTestCommand: true }, + ], + }), + "utf8", + ); + + const { results, summary } = await runCrossRepoExecutionCli({ + manifestPath: join(manifestDir, "manifest.json"), + // Every side-effecting seam is a fake -> zero real clone / agent / test-run / network. + buildCodingTaskSpec: () => ({ ready: true, instructions: "Apply the smallest correct fix." }), + cloneRepo: (entry: { fixturePath?: string }) => ({ ok: true, repoPath: entry.fixturePath ?? "" }), + runCodingAgent: (context: { repoFullName: string }) => ({ + ok: true, + changedFiles: ["src/change.ts"], + summary: `edited ${context.repoFullName}`, + }), + runTests: (command: string, cwd: string) => ({ + code: cwd === repoB ? 1 : 0, // exec-red's suite is red + stdout: "", + stderr: "", + timedOut: false, + }), + }); + + expect(results).toHaveLength(2); + expect(results[0]?.passed).toBe(true); + expect(results[0]?.executed).toBe(true); + expect(results[1]?.failureCategory).toBe(CROSS_REPO_FAILURE_CATEGORY.TEST_FAILURE); + expect(summary.passed).toBe(1); + expect(summary.executedCount).toBe(2); + const report = formatCrossRepoEvaluationReport(results, summary); + expect(report).toContain("dry-run full-execution: 2/2"); + expect(report).toContain("- test_failure: 1"); + }); + it("parseCrossRepoEvaluationArgs treats a missing --repo value as an error", () => { expect(parseCrossRepoEvaluationArgs(["--repo"])).toEqual({ error: "Missing value for --repo." }); }); @@ -534,5 +790,8 @@ describe("cross-repo evaluation harness (#4788)", () => { expect(doc).toContain("stack_detection_gap"); expect(doc).toContain("cross-repo-evaluation.mjs"); expect(doc).toContain("benchmarks/cross-repo/manifest.json"); + // The dry-run full-execution mode (#7634) and its execution-specific categories are documented in-style. + expect(doc).toContain("--full-execution"); + expect(doc).toContain("no_op_diff"); }); });