diff --git a/apps/loopover-ui/src/lib/ams-env-reference.ts b/apps/loopover-ui/src/lib/ams-env-reference.ts index bcca7cfd6d..f6c695d819 100644 --- a/apps/loopover-ui/src/lib/ams-env-reference.ts +++ b/apps/loopover-ui/src/lib/ams-env-reference.ts @@ -91,6 +91,11 @@ export const AMS_ENV_REFERENCE_ROWS: MinerEnvReferenceRow[] = [ firstReference: "lib/event-ledger.js", defaultValue: null, }, + { + name: "LOOPOVER_MINER_FULL_EXECUTION_STUB", + firstReference: "lib/cross-repo-evaluation.js", + defaultValue: "", + }, { name: "LOOPOVER_MINER_GOVERNOR_LEDGER_DB", firstReference: "lib/governor-ledger.js", @@ -232,6 +237,7 @@ export const AMS_ENV_REFERENCE_MARKDOWN = [ '| `LOOPOVER_MINER_DISCOVERY_SHARED_SECRET` | `lib/discovery-index-client.js` | `""` |', '| `LOOPOVER_MINER_DISCOVERY_TELEMETRY` | `lib/discovery-index-client.js` | `""` |', "| `LOOPOVER_MINER_EVENT_LEDGER_DB` | `lib/event-ledger.js` | (none) |", + '| `LOOPOVER_MINER_FULL_EXECUTION_STUB` | `lib/cross-repo-evaluation.js` | `""` |', "| `LOOPOVER_MINER_GOVERNOR_LEDGER_DB` | `lib/governor-ledger.js` | (none) |", "| `LOOPOVER_MINER_GOVERNOR_STATE_DB` | `lib/governor-state.js` | (none) |", '| `LOOPOVER_MINER_LOG_LEVEL` | `lib/logger.js` | `""` |', diff --git a/packages/loopover-miner/DEPLOYMENT.md b/packages/loopover-miner/DEPLOYMENT.md index 4651bb0d2b..60b6bb7757 100644 --- a/packages/loopover-miner/DEPLOYMENT.md +++ b/packages/loopover-miner/DEPLOYMENT.md @@ -197,6 +197,7 @@ and `LOOPOVER_MINER_CONFIG_DIR` are covered above under the fleet/state notes; t | `LOOPOVER_MINER_AMS_POLICY_PATH` | `lib/ams-policy.js` | Path to the operator's `.loopover-ams.yml` policy spec. | | `LOOPOVER_MINER_AMS_COLLECTOR_URL`, `LOOPOVER_MINER_AMS_COLLECTOR_TOKEN` | `lib/orb-export.js` | Endpoint + bearer for pushing fleet state to an AMS/Orb collector. | | `LOOPOVER_MINER_CHAT_ACTIONS` | `lib/chat-action-dispatch.js` | Truthy string enables the chat-action-dispatch flag (off by default). | +| `LOOPOVER_MINER_FULL_EXECUTION_STUB` | `lib/cross-repo-evaluation.js` | Truthy string enables a synthetic one-file handoff for local full-execution demos (off by default; never invents a live coding-agent run or forge PR). | | `LOOPOVER_MINER_LEDGER_RETENTION_DAYS`, `LOOPOVER_MINER_LEDGER_RETENTION_MAX_ROWS` | `lib/store-maintenance.js` | Opt-in ledger retention window / row cap. | | `LOOPOVER_MINER_LOG_LEVEL` | `lib/logger.js` | Log verbosity override. | | `LOOPOVER_MINER_NO_UPDATE_CHECK` | `lib/update-check.js` | Truthy string disables the background update check. | diff --git a/packages/loopover-miner/benchmarks/cross-repo/manifest.json b/packages/loopover-miner/benchmarks/cross-repo/manifest.json index a6d1bc1b35..4d62bd0b2d 100644 --- a/packages/loopover-miner/benchmarks/cross-repo/manifest.json +++ b/packages/loopover-miner/benchmarks/cross-repo/manifest.json @@ -1,5 +1,5 @@ { - "description": "Cross-repo evaluation benchmark set (#4788). Diverse public repos exercised by `node packages/loopover-miner/scripts/cross-repo-evaluation.mjs` after cloning into LOOPOVER_MINER_REPO_CLONE_DIR.", + "description": "Cross-repo evaluation benchmark set (#4788 readiness / #7634 full-execution). Diverse public repos exercised by `node packages/loopover-miner/scripts/cross-repo-evaluation.mjs` after cloning into LOOPOVER_MINER_REPO_CLONE_DIR. Entries with fullExecution=true are the default --full-execution subset (dry-run: no forge PR writes).", "repos": [ { "repoFullName": "sindresorhus/is", @@ -8,7 +8,8 @@ { "repoFullName": "expressjs/express", "stackHint": "nodejs", - "requireTestCommand": true + "requireTestCommand": true, + "fullExecution": true }, { "repoFullName": "pallets/flask", @@ -28,7 +29,8 @@ { "repoFullName": "lodash/lodash", "stackHint": "nodejs", - "requireTestCommand": true + "requireTestCommand": true, + "fullExecution": true } ] } diff --git a/packages/loopover-miner/docs/cross-repo-evaluation.md b/packages/loopover-miner/docs/cross-repo-evaluation.md index cc36444b74..1b46dd2195 100644 --- a/packages/loopover-miner/docs/cross-repo-evaluation.md +++ b/packages/loopover-miner/docs/cross-repo-evaluation.md @@ -20,6 +20,10 @@ Each benchmark repo receives a **pass/fail** line. Failures are categorized: | `loopover_assumption` | Agent instructions leak loopover-specific CI assumptions | | `clone_setup` | The repo has not been cloned to the expected cache path | | `other` | Unexpected errors | +| `plan_formed_compile_failed` | Full-execution: local build/compile failed after a coding handoff (#7634) | +| `compiled_tests_failed` | Full-execution: build ok but the repo's own test command failed (#7634) | +| `tests_passed_noop_diff` | Full-execution: build+tests ok but the coding attempt changed no files (#7634) | +| `execution_abandon` | Full-execution: coding attempt abandoned before handoff (#7634) | The run also reports whether a **strict majority** of repos passed and how many succeeded **without** a per-target `.loopover-miner.yml` (the default goal spec is acceptable). @@ -33,6 +37,8 @@ bare `"owner/repo"` string or an object: - **`stackHint`** — documentation only (not used by the evaluator) - **`requireTestCommand`** — when `true`, stack detection must infer a test command or the repo fails with `execution_gap` +- **`fullExecution`** — when `true`, include this repo in the default `--full-execution` subset (#7634). At least two + shipped entries are tagged; if fewer than two are tagged, the harness falls back to all `requireTestCommand` entries. Malformed manifest fields degrade to documented defaults with warnings (same tolerant-parser convention as the fleet run-manifest). @@ -60,6 +66,22 @@ 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` — after readiness, run local discover→plan→code→build→test (**no forge PR writes**, #7634) + +## Full-execution mode (#7634) + +`--full-execution` extends readiness with a **local-only** code→build→test loop against the tagged benchmark subset +(or `--repo` for a single entry). It never opens a GitHub/GitLab PR and never calls forge write APIs against the +benchmark repos. + +1. Run the same readiness checks as the default mode +2. Invoke an injectable `runCodingAttempt` seam (discover→plan→code) that must return `handoff` + `changedFiles` +3. Run the detected `buildCommand` (when present) and `testCommand` locally via `runShellCommand` +4. Fail with execution-specific categories when compile fails, tests fail, the diff is empty, or the attempt abandons + +**Default coding seam:** without an injected `runCodingAttempt`, the harness abandons with `execution_abandon` +unless `LOOPOVER_MINER_FULL_EXECUTION_STUB=1` is set (synthetic one-file handoff for local demos). Real agent wiring +stays opt-in so CI and unit tests stay offline and forge-write-free. ## Library API @@ -67,12 +89,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)` +- `evaluateRepoFullExecution(entry, options)` — readiness + local code→build→test; inject `runCodingAttempt` / + `runShellCommand` (#7634) +- `runCrossRepoEvaluation(parsed, options)` — async; pass `fullExecution: true` for the execution path - `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 +**Readiness mode** 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 mode** still does not open PRs or call forge write APIs. A green report means a local coding +handoff produced a non-empty diff that survived the target repo’s own build and test commands. A live production attempt still needs credentials, governor policy, and queue state as documented in [`DEPLOYMENT.md`](../DEPLOYMENT.md). diff --git a/packages/loopover-miner/docs/env-reference.md b/packages/loopover-miner/docs/env-reference.md index b95e770147..7456f95a4c 100644 --- a/packages/loopover-miner/docs/env-reference.md +++ b/packages/loopover-miner/docs/env-reference.md @@ -21,6 +21,7 @@ Generated by `npm run miner:env-reference`. Do not edit manually. | `LOOPOVER_MINER_DISCOVERY_SHARED_SECRET` | `lib/discovery-index-client.js` | `""` | | `LOOPOVER_MINER_DISCOVERY_TELEMETRY` | `lib/discovery-index-client.js` | `""` | | `LOOPOVER_MINER_EVENT_LEDGER_DB` | `lib/event-ledger.js` | (none) | +| `LOOPOVER_MINER_FULL_EXECUTION_STUB` | `lib/cross-repo-evaluation.js` | `""` | | `LOOPOVER_MINER_GOVERNOR_LEDGER_DB` | `lib/governor-ledger.js` | (none) | | `LOOPOVER_MINER_GOVERNOR_STATE_DB` | `lib/governor-state.js` | (none) | | `LOOPOVER_MINER_LOG_LEVEL` | `lib/logger.js` | `""` | diff --git a/packages/loopover-miner/lib/cross-repo-evaluation.d.ts b/packages/loopover-miner/lib/cross-repo-evaluation.d.ts index af8e928558..c863def20b 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 readiness + #7634 full-execution). */ 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"; + /** Plan/spec formed but local build/compile command failed (#7634). */ + COMPILE_FAILED: "plan_formed_compile_failed"; + /** Build succeeded but the repo's own test suite failed (#7634). */ + TESTS_FAILED: "compiled_tests_failed"; + /** Tests passed but the coding attempt produced no file changes (#7634). */ + NOOP_DIFF: "tests_passed_noop_diff"; + /** Coding attempt abandoned before a handoff (agent unconfigured / refused) (#7634). */ + EXECUTION_ABANDON: "execution_abandon"; }>; /** 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. */ @@ -20,6 +28,8 @@ export type CrossRepoEvaluationManifestRepo = { repoFullName: string; stackHint?: string; requireTestCommand?: boolean; + /** When true, include this repo in `--full-execution` runs (#7634). */ + fullExecution?: boolean; fixturePath?: string; }; export type ParsedCrossRepoEvaluationManifest = { @@ -67,6 +77,36 @@ type EvaluateRepoReadinessOptions = { instructions?: string; }; }; +/** Local coding-attempt outcome for `--full-execution` (#7634). Never opens a forge PR. */ +export type CrossRepoCodingAttemptResult = { + outcome: "handoff" | "abandon"; + changedFiles: string[]; + reason?: string; +}; +export type CrossRepoShellCommandResult = { + ok: boolean; + exitCode: number; + stdout?: string; + stderr?: string; +}; +export type EvaluateRepoFullExecutionOptions = EvaluateRepoReadinessOptions & { + /** + * Run the local discover→plan→code step and return changed files. MUST NOT open PRs or call forge + * write APIs (#7634). Injected in unit tests; default is a no-network stub that abandons unless + * `LOOPOVER_MINER_FULL_EXECUTION_STUB=1` (synthetic handoff for local dry demos). + */ + runCodingAttempt?: (input: { + repoFullName: string; + repoPath: string; + stack: RepoStackResult; + instructions: string; + }) => CrossRepoCodingAttemptResult | Promise; + /** Run a local shell command (build/test). Injected in unit tests. */ + runShellCommand?: (input: { + command: string; + cwd: string; + }) => CrossRepoShellCommandResult | Promise; +}; /** Canonical `owner/repo` with exactly one slash and safe segments; anything else → null. */ export declare function normalizeCrossRepoFullName(value: unknown): string | null; /** @@ -86,12 +126,39 @@ 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; +/** + * Default coding-attempt seam for `--full-execution` (#7634). Never opens a PR. + * - `LOOPOVER_MINER_FULL_EXECUTION_STUB=1` → synthetic handoff with one changed path (local demo only). + * - Otherwise abandons with a clear reason (real agent wiring stays opt-in via `runCodingAttempt`). + */ +export declare function defaultRunCodingAttempt(input: { + repoFullName: string; + repoPath: string; + stack: RepoStackResult; + instructions: string; + env?: NodeJS.ProcessEnv; +}): CrossRepoCodingAttemptResult; +/** + * Default local shell runner for build/test commands (#7634). Sync spawn; no network. + */ +export declare function defaultRunShellCommand(input: { + command: string; + cwd: string; +}): CrossRepoShellCommandResult; +/** + * Full-execution evaluation (#7634): readiness first, then local code→build→test with no forge writes. + */ +export declare function evaluateRepoFullExecution(entry: CrossRepoEvaluationManifestRepo, options?: EvaluateRepoFullExecutionOptions): Promise; /** * Run the harness across every repo in a parsed manifest (#4788). + * Pass `fullExecution: true` (#7634) to run local code→build→test (still no forge writes). */ export declare function runCrossRepoEvaluation(parsed: ParsedCrossRepoEvaluationManifest, options?: { repoFilter?: string; -} & EvaluateRepoReadinessOptions): CrossRepoEvaluationResult[]; + fullExecution?: boolean; + /** Minimum repos to include in full-execution when filtering by `fullExecution` / requireTestCommand. */ + fullExecutionMinRepos?: number; +} & EvaluateRepoFullExecutionOptions): 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..f56d568617 100644 --- a/packages/loopover-miner/lib/cross-repo-evaluation.js +++ b/packages/loopover-miner/lib/cross-repo-evaluation.js @@ -3,18 +3,23 @@ // 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 { spawnSync } 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 { detectRepoStack } from "./stack-detection.js"; -/** Failure taxonomy surfaced in per-repo reports (#4788). */ +/** Failure taxonomy surfaced in per-repo reports (#4788 readiness + #7634 full-execution). */ 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", + COMPILE_FAILED: "plan_formed_compile_failed", + TESTS_FAILED: "compiled_tests_failed", + NOOP_DIFF: "tests_passed_noop_diff", + EXECUTION_ABANDON: "execution_abandon", }); /** 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. */ @@ -96,6 +101,7 @@ function normalizeRepoList(value, warnings) { let repoFullName = null; let stackHint = null; let requireTestCommand = false; + let fullExecution = false; let fixturePath = null; if (typeof entry === "string") { repoFullName = normalizeCrossRepoFullName(entry); @@ -105,6 +111,7 @@ function normalizeRepoList(value, warnings) { repoFullName = normalizeCrossRepoFullName(record.repoFullName); stackHint = normalizeOptionalString(record.stackHint, "stackHint", warnings); requireTestCommand = normalizeBoolean(record.requireTestCommand, "requireTestCommand", false, warnings); + fullExecution = normalizeBoolean(record.fullExecution, "fullExecution", false, warnings); fixturePath = normalizeOptionalString(record.fixturePath, "fixturePath", warnings); } else { @@ -125,6 +132,8 @@ function normalizeRepoList(value, warnings) { normalized.stackHint = stackHint; if (fixturePath) normalized.fixturePath = fixturePath; + if (fullExecution) + normalized.fullExecution = true; result.push(normalized); } return result; @@ -275,16 +284,106 @@ export function evaluateRepoReadiness(entry, options = {}) { } return buildPass(repoFullName, { usedDefaultGoalSpec, stack }); } +/** + * Default coding-attempt seam for `--full-execution` (#7634). Never opens a PR. + * - `LOOPOVER_MINER_FULL_EXECUTION_STUB=1` → synthetic handoff with one changed path (local demo only). + * - Otherwise abandons with a clear reason (real agent wiring stays opt-in via `runCodingAttempt`). + */ +export function defaultRunCodingAttempt(input) { + const env = input.env ?? process.env; + if (/^(1|true|yes|on)$/i.test(env.LOOPOVER_MINER_FULL_EXECUTION_STUB ?? "")) { + return { outcome: "handoff", changedFiles: ["CROSS_REPO_EVALUATION_STUB.diff"] }; + } + return { + outcome: "abandon", + changedFiles: [], + reason: "No runCodingAttempt injection and LOOPOVER_MINER_FULL_EXECUTION_STUB is unset — full-execution will not invent a live coding-agent run or open a forge PR.", + }; +} +/** + * Default local shell runner for build/test commands (#7634). Sync spawn; no network. + */ +export function defaultRunShellCommand(input) { + const result = spawnSync(input.command, { + cwd: input.cwd, + shell: true, + encoding: "utf8", + env: process.env, + }); + const exitCode = typeof result.status === "number" ? result.status : 1; + const out = { ok: exitCode === 0, exitCode }; + if (typeof result.stdout === "string") + out.stdout = result.stdout; + if (typeof result.stderr === "string") + out.stderr = result.stderr; + return out; +} +/** + * Full-execution evaluation (#7634): readiness first, then local code→build→test with no forge writes. + */ +export async function evaluateRepoFullExecution(entry, options = {}) { + const readiness = evaluateRepoReadiness(entry, options); + if (readiness.passed !== true) + return readiness; + const repoFullName = readiness.repoFullName; + const stack = readiness.stack; + if (!stack || stack.detected !== true) { + return buildFailure(repoFullName, CROSS_REPO_FAILURE_CATEGORY.STACK_DETECTION, "Full-execution requires a detected stack after readiness passed.", { stackDetected: false, usedDefaultGoalSpec: readiness.usedDefaultGoalSpec }); + } + const repoPath = resolveEvaluationRepoPath(entry, options); + const instructions = "Cross-repo full-execution local attempt (readiness already validated stack detection and coding-task composition)."; + const runAttempt = options.runCodingAttempt ?? + ((input) => defaultRunCodingAttempt(options.env ? { ...input, env: options.env } : input)); + const runCmd = options.runShellCommand ?? defaultRunShellCommand; + const attempt = await runAttempt({ repoFullName, repoPath, stack, instructions }); + if (attempt?.outcome !== "handoff") { + return buildFailure(repoFullName, CROSS_REPO_FAILURE_CATEGORY.EXECUTION_ABANDON, attempt?.reason ?? "Coding attempt abandoned before handoff.", { stackDetected: true, usedDefaultGoalSpec: readiness.usedDefaultGoalSpec, stack }); + } + const changedFiles = Array.isArray(attempt.changedFiles) + ? attempt.changedFiles.filter((p) => typeof p === "string" && p.trim()) + : []; + if (stack.buildCommand) { + const build = await runCmd({ command: stack.buildCommand, cwd: repoPath }); + if (!build?.ok) { + return buildFailure(repoFullName, CROSS_REPO_FAILURE_CATEGORY.COMPILE_FAILED, `Local build failed (exit ${build?.exitCode ?? "unknown"}): ${stack.buildCommand}`, { stackDetected: true, usedDefaultGoalSpec: readiness.usedDefaultGoalSpec, stack }); + } + } + if (stack.testCommand) { + const test = await runCmd({ command: stack.testCommand, cwd: repoPath }); + if (!test?.ok) { + return buildFailure(repoFullName, CROSS_REPO_FAILURE_CATEGORY.TESTS_FAILED, `Local tests failed (exit ${test?.exitCode ?? "unknown"}): ${stack.testCommand}`, { stackDetected: true, usedDefaultGoalSpec: readiness.usedDefaultGoalSpec, stack }); + } + } + else if (entry.requireTestCommand === true) { + return buildFailure(repoFullName, CROSS_REPO_FAILURE_CATEGORY.EXECUTION, "Full-execution requires an inferred test command when requireTestCommand is set.", { stackDetected: true, usedDefaultGoalSpec: readiness.usedDefaultGoalSpec, stack }); + } + if (changedFiles.length === 0) { + return buildFailure(repoFullName, CROSS_REPO_FAILURE_CATEGORY.NOOP_DIFF, "Build and tests succeeded but the coding attempt produced no changed files.", { stackDetected: true, usedDefaultGoalSpec: readiness.usedDefaultGoalSpec, stack }); + } + return buildPass(repoFullName, { usedDefaultGoalSpec: readiness.usedDefaultGoalSpec, stack }); +} /** * Run the harness across every repo in a parsed manifest (#4788). + * Pass `fullExecution: true` (#7634) to run local code→build→test (still no forge writes). */ -export function runCrossRepoEvaluation(parsed, options = {}) { +export async function runCrossRepoEvaluation(parsed, options = {}) { const repos = parsed?.manifest?.repos ?? []; + let selected = repos; + if (options.fullExecution === true && !options.repoFilter) { + const tagged = repos.filter((r) => r.fullExecution === true); + const withTests = repos.filter((r) => r.requireTestCommand === true); + selected = tagged.length >= (options.fullExecutionMinRepos ?? 2) ? tagged : withTests; + } const results = []; - for (const entry of repos) { + for (const entry of selected) { if (options.repoFilter && entry.repoFullName !== options.repoFilter) continue; - results.push(evaluateRepoReadiness(entry, options)); + if (options.fullExecution === true) { + results.push(await evaluateRepoFullExecution(entry, options)); + } + else { + results.push(evaluateRepoReadiness(entry, options)); + } } return results; } @@ -343,4 +442,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,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY3Jvc3MtcmVwby1ldmFsdWF0aW9uLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiY3Jvc3MtcmVwby1ldmFsdWF0aW9uLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLGlIQUFpSDtBQUNqSCw4R0FBOEc7QUFDOUcsMkdBQTJHO0FBQzNHLDZHQUE2RztBQUM3RyxxR0FBcUc7QUFFckcsT0FBTyxFQUFFLFNBQVMsRUFBRSxNQUFNLG9CQUFvQixDQUFDO0FBQy9DLE9BQU8sRUFBRSxVQUFVLEVBQUUsTUFBTSxTQUFTLENBQUM7QUFFckMsT0FBTyxFQUFFLG1CQUFtQixFQUFFLE1BQU0sdUJBQXVCLENBQUM7QUFDNUQsT0FBTyxFQUFFLG9CQUFvQixFQUFFLE1BQU0sc0JBQXNCLENBQUM7QUFDNUQsT0FBTyxFQUFFLGtCQUFrQixFQUFFLG1CQUFtQixFQUFFLE1BQU0saUJBQWlCLENBQUM7QUFDMUUsT0FBTyxFQUFFLGVBQWUsRUFBRSxNQUFNLHNCQUFzQixDQUFDO0FBR3ZELDhGQUE4RjtBQUM5RixNQUFNLENBQUMsTUFBTSwyQkFBMkIsR0FjbkMsTUFBTSxDQUFDLE1BQU0sQ0FBQztJQUNqQixlQUFlLEVBQUUscUJBQXFCO0lBQ3RDLFNBQVMsRUFBRSxlQUFlO0lBQzFCLG9CQUFvQixFQUFFLHFCQUFxQjtJQUMzQyxXQUFXLEVBQUUsYUFBYTtJQUMxQixLQUFLLEVBQUUsT0FBTztJQUNkLGNBQWMsRUFBRSw0QkFBNEI7SUFDNUMsWUFBWSxFQUFFLHVCQUF1QjtJQUNyQyxTQUFTLEVBQUUsd0JBQXdCO0lBQ25DLGlCQUFpQixFQUFFLG1CQUFtQjtDQUN2QyxDQUFDLENBQUM7QUFFSDttR0FDbUc7QUFDbkcsTUFBTSxDQUFDLE1BQU0sb0NBQW9DLEdBQW1ELE1BQU0sQ0FBQyxNQUFNLENBQUM7SUFDaEgsRUFBRSxFQUFFLEVBQUUsZ0JBQWdCLEVBQUUsT0FBTyxFQUFFLGtCQUFrQixFQUFFO0lBQ3JELEVBQUUsRUFBRSxFQUFFLGVBQWUsRUFB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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..46eaba6210 100644 --- a/packages/loopover-miner/lib/cross-repo-evaluation.ts +++ b/packages/loopover-miner/lib/cross-repo-evaluation.ts @@ -4,6 +4,7 @@ // 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 { spawnSync } from "node:child_process"; import { existsSync } from "node:fs"; import { join } from "node:path"; import { buildCodingTaskSpec } from "./coding-task-spec.js"; @@ -12,19 +13,31 @@ import { 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 readiness + #7634 full-execution). */ 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"; + /** Plan/spec formed but local build/compile command failed (#7634). */ + COMPILE_FAILED: "plan_formed_compile_failed"; + /** Build succeeded but the repo's own test suite failed (#7634). */ + TESTS_FAILED: "compiled_tests_failed"; + /** Tests passed but the coding attempt produced no file changes (#7634). */ + NOOP_DIFF: "tests_passed_noop_diff"; + /** Coding attempt abandoned before a handoff (agent unconfigured / refused) (#7634). */ + EXECUTION_ABANDON: "execution_abandon"; }> = Object.freeze({ STACK_DETECTION: "stack_detection_gap", EXECUTION: "execution_gap", GITTENSOR_ASSUMPTION: "loopover_assumption", CLONE_SETUP: "clone_setup", OTHER: "other", + COMPILE_FAILED: "plan_formed_compile_failed", + TESTS_FAILED: "compiled_tests_failed", + NOOP_DIFF: "tests_passed_noop_diff", + EXECUTION_ABANDON: "execution_abandon", }); /** Instruction substrings that indicate a POSITIVE loopover/LoopOver CI assumption leaked into the agent prompt. @@ -44,6 +57,8 @@ export type CrossRepoEvaluationManifestRepo = { repoFullName: string; stackHint?: string; requireTestCommand?: boolean; + /** When true, include this repo in `--full-execution` runs (#7634). */ + fullExecution?: boolean; fixturePath?: string; }; @@ -87,6 +102,39 @@ type EvaluateRepoReadinessOptions = { }; }; +/** Local coding-attempt outcome for `--full-execution` (#7634). Never opens a forge PR. */ +export type CrossRepoCodingAttemptResult = { + outcome: "handoff" | "abandon"; + changedFiles: string[]; + reason?: string; +}; + +export type CrossRepoShellCommandResult = { + ok: boolean; + exitCode: number; + stdout?: string; + stderr?: string; +}; + +export type EvaluateRepoFullExecutionOptions = EvaluateRepoReadinessOptions & { + /** + * Run the local discover→plan→code step and return changed files. MUST NOT open PRs or call forge + * write APIs (#7634). Injected in unit tests; default is a no-network stub that abandons unless + * `LOOPOVER_MINER_FULL_EXECUTION_STUB=1` (synthetic handoff for local dry demos). + */ + runCodingAttempt?: (input: { + repoFullName: string; + repoPath: string; + stack: RepoStackResult; + instructions: string; + }) => CrossRepoCodingAttemptResult | Promise; + /** Run a local shell command (build/test). Injected in unit tests. */ + runShellCommand?: (input: { + command: string; + cwd: string; + }) => CrossRepoShellCommandResult | Promise; +}; + // True UTF-8 byte count for the size guard (#7223): JS string `.length` is UTF-16 code units, which under-counts // any multi-byte character (up to 4x for astral-plane code points), so `MAX_CROSS_REPO_MANIFEST_BYTES` -- named // and warned about in BYTES -- was actually being compared against a code-unit count. Mirrors the identical helper @@ -152,6 +200,7 @@ function normalizeRepoList(value: unknown, warnings: string[]): CrossRepoEvaluat let repoFullName: string | null = null; let stackHint: string | null = null; let requireTestCommand = false; + let fullExecution = false; let fixturePath: string | null = null; if (typeof entry === "string") { repoFullName = normalizeCrossRepoFullName(entry); @@ -160,6 +209,7 @@ function normalizeRepoList(value: unknown, warnings: string[]): CrossRepoEvaluat repoFullName = normalizeCrossRepoFullName(record.repoFullName); stackHint = normalizeOptionalString(record.stackHint, "stackHint", warnings); requireTestCommand = normalizeBoolean(record.requireTestCommand, "requireTestCommand", false, warnings); + fullExecution = normalizeBoolean(record.fullExecution, "fullExecution", false, warnings); fixturePath = normalizeOptionalString(record.fixturePath, "fixturePath", warnings); } else { warnings.push(`CrossRepoEvaluationManifest "repos" skipped a non-string, non-mapping entry.`); @@ -177,6 +227,7 @@ function normalizeRepoList(value: unknown, warnings: string[]): CrossRepoEvaluat const normalized: CrossRepoEvaluationManifestRepo = { repoFullName, requireTestCommand }; if (stackHint) normalized.stackHint = stackHint; if (fixturePath) normalized.fixturePath = fixturePath; + if (fullExecution) normalized.fullExecution = true; result.push(normalized); } return result; @@ -376,18 +427,162 @@ export function evaluateRepoReadiness( return buildPass(repoFullName, { usedDefaultGoalSpec, stack }); } +/** + * Default coding-attempt seam for `--full-execution` (#7634). Never opens a PR. + * - `LOOPOVER_MINER_FULL_EXECUTION_STUB=1` → synthetic handoff with one changed path (local demo only). + * - Otherwise abandons with a clear reason (real agent wiring stays opt-in via `runCodingAttempt`). + */ +export function defaultRunCodingAttempt(input: { + repoFullName: string; + repoPath: string; + stack: RepoStackResult; + instructions: string; + env?: NodeJS.ProcessEnv; +}): CrossRepoCodingAttemptResult { + const env = input.env ?? process.env; + if (/^(1|true|yes|on)$/i.test(env.LOOPOVER_MINER_FULL_EXECUTION_STUB ?? "")) { + return { outcome: "handoff", changedFiles: ["CROSS_REPO_EVALUATION_STUB.diff"] }; + } + return { + outcome: "abandon", + changedFiles: [], + reason: + "No runCodingAttempt injection and LOOPOVER_MINER_FULL_EXECUTION_STUB is unset — full-execution will not invent a live coding-agent run or open a forge PR.", + }; +} + +/** + * Default local shell runner for build/test commands (#7634). Sync spawn; no network. + */ +export function defaultRunShellCommand(input: { command: string; cwd: string }): CrossRepoShellCommandResult { + const result = spawnSync(input.command, { + cwd: input.cwd, + shell: true, + encoding: "utf8", + env: process.env, + }); + const exitCode = typeof result.status === "number" ? result.status : 1; + const out: CrossRepoShellCommandResult = { ok: exitCode === 0, exitCode }; + if (typeof result.stdout === "string") out.stdout = result.stdout; + if (typeof result.stderr === "string") out.stderr = result.stderr; + return out; +} + +/** + * Full-execution evaluation (#7634): readiness first, then local code→build→test with no forge writes. + */ +export async function evaluateRepoFullExecution( + entry: CrossRepoEvaluationManifestRepo, + options: EvaluateRepoFullExecutionOptions = {}, +): Promise { + const readiness = evaluateRepoReadiness(entry, options); + if (readiness.passed !== true) return readiness; + + const repoFullName = readiness.repoFullName; + const stack = readiness.stack; + if (!stack || stack.detected !== true) { + return buildFailure( + repoFullName, + CROSS_REPO_FAILURE_CATEGORY.STACK_DETECTION, + "Full-execution requires a detected stack after readiness passed.", + { stackDetected: false, usedDefaultGoalSpec: readiness.usedDefaultGoalSpec }, + ); + } + + const repoPath = resolveEvaluationRepoPath(entry, options); + const instructions = + "Cross-repo full-execution local attempt (readiness already validated stack detection and coding-task composition)."; + + const runAttempt = + options.runCodingAttempt ?? + ((input) => defaultRunCodingAttempt(options.env ? { ...input, env: options.env } : input)); + const runCmd = options.runShellCommand ?? defaultRunShellCommand; + + const attempt = await runAttempt({ repoFullName, repoPath, stack, instructions }); + if (attempt?.outcome !== "handoff") { + return buildFailure( + repoFullName, + CROSS_REPO_FAILURE_CATEGORY.EXECUTION_ABANDON, + attempt?.reason ?? "Coding attempt abandoned before handoff.", + { stackDetected: true, usedDefaultGoalSpec: readiness.usedDefaultGoalSpec, stack }, + ); + } + + const changedFiles = Array.isArray(attempt.changedFiles) + ? attempt.changedFiles.filter((p) => typeof p === "string" && p.trim()) + : []; + + if (stack.buildCommand) { + const build = await runCmd({ command: stack.buildCommand, cwd: repoPath }); + if (!build?.ok) { + return buildFailure( + repoFullName, + CROSS_REPO_FAILURE_CATEGORY.COMPILE_FAILED, + `Local build failed (exit ${build?.exitCode ?? "unknown"}): ${stack.buildCommand}`, + { stackDetected: true, usedDefaultGoalSpec: readiness.usedDefaultGoalSpec, stack }, + ); + } + } + + if (stack.testCommand) { + const test = await runCmd({ command: stack.testCommand, cwd: repoPath }); + if (!test?.ok) { + return buildFailure( + repoFullName, + CROSS_REPO_FAILURE_CATEGORY.TESTS_FAILED, + `Local tests failed (exit ${test?.exitCode ?? "unknown"}): ${stack.testCommand}`, + { stackDetected: true, usedDefaultGoalSpec: readiness.usedDefaultGoalSpec, stack }, + ); + } + } else if (entry.requireTestCommand === true) { + return buildFailure( + repoFullName, + CROSS_REPO_FAILURE_CATEGORY.EXECUTION, + "Full-execution requires an inferred test command when requireTestCommand is set.", + { stackDetected: true, usedDefaultGoalSpec: readiness.usedDefaultGoalSpec, stack }, + ); + } + + if (changedFiles.length === 0) { + return buildFailure( + repoFullName, + CROSS_REPO_FAILURE_CATEGORY.NOOP_DIFF, + "Build and tests succeeded but the coding attempt produced no changed files.", + { stackDetected: true, usedDefaultGoalSpec: readiness.usedDefaultGoalSpec, stack }, + ); + } + + return buildPass(repoFullName, { usedDefaultGoalSpec: readiness.usedDefaultGoalSpec, stack }); +} + /** * Run the harness across every repo in a parsed manifest (#4788). + * Pass `fullExecution: true` (#7634) to run local code→build→test (still no forge writes). */ -export function runCrossRepoEvaluation( +export async function runCrossRepoEvaluation( parsed: ParsedCrossRepoEvaluationManifest, - options: { repoFilter?: string } & EvaluateRepoReadinessOptions = {}, -): CrossRepoEvaluationResult[] { + options: { + repoFilter?: string; + fullExecution?: boolean; + /** Minimum repos to include in full-execution when filtering by `fullExecution` / requireTestCommand. */ + fullExecutionMinRepos?: number; + } & EvaluateRepoFullExecutionOptions = {}, +): Promise { const repos = parsed?.manifest?.repos ?? []; + let selected = repos; + if (options.fullExecution === true && !options.repoFilter) { + const tagged = repos.filter((r) => r.fullExecution === true); + const withTests = repos.filter((r) => r.requireTestCommand === true); + selected = tagged.length >= (options.fullExecutionMinRepos ?? 2) ? tagged : withTests; + } const results: CrossRepoEvaluationResult[] = []; - for (const entry of repos) { + for (const entry of selected) { if (options.repoFilter && entry.repoFullName !== options.repoFilter) continue; - results.push(evaluateRepoReadiness(entry, options)); + if (options.fullExecution === true) { + results.push(await evaluateRepoFullExecution(entry, options)); + } else { + results.push(evaluateRepoReadiness(entry, options)); + } } return results; } diff --git a/packages/loopover-miner/scripts/cross-repo-evaluation.d.mts b/packages/loopover-miner/scripts/cross-repo-evaluation.d.mts index c263aa2347..a0760ceda4 100644 --- a/packages/loopover-miner/scripts/cross-repo-evaluation.d.mts +++ b/packages/loopover-miner/scripts/cross-repo-evaluation.d.mts @@ -5,7 +5,13 @@ import type { } 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 }; @@ -13,6 +19,7 @@ export type CrossRepoEvaluationCliOptions = { parsed?: ParsedCrossRepoEvaluationManifest; manifestPath?: string; repoFilter?: string | null; + fullExecution?: boolean; }; export declare function resolveDefaultManifestPath(): string; @@ -21,8 +28,8 @@ export declare function parseCrossRepoEvaluationArgs(argv?: readonly string[]): export declare function loadCrossRepoEvaluationManifest(manifestPath: string): ParsedCrossRepoEvaluationManifest; -export declare function runCrossRepoEvaluationCli(options?: CrossRepoEvaluationCliOptions): { +export declare function runCrossRepoEvaluationCli(options?: CrossRepoEvaluationCliOptions): 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..1eb5011529 100644 --- a/packages/loopover-miner/scripts/cross-repo-evaluation.mjs +++ b/packages/loopover-miner/scripts/cross-repo-evaluation.mjs @@ -22,6 +22,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 +33,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 +56,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) { @@ -59,9 +64,12 @@ export function loadCrossRepoEvaluationManifest(manifestPath) { return parseCrossRepoEvaluationManifest(content); } -export function runCrossRepoEvaluationCli(options = {}) { +export async function runCrossRepoEvaluationCli(options = {}) { const parsed = options.parsed ?? loadCrossRepoEvaluationManifest(options.manifestPath ?? resolveDefaultManifestPath()); - const results = runCrossRepoEvaluation(parsed, { repoFilter: options.repoFilter ?? null }); + const results = await runCrossRepoEvaluation(parsed, { + repoFilter: options.repoFilter ?? null, + fullExecution: options.fullExecution === true, + }); const summary = summarizeCrossRepoEvaluation(results); return { parsed, results, summary }; } @@ -69,7 +77,7 @@ export function runCrossRepoEvaluationCli(options = {}) { function printHelp() { console.log( [ - "loopover-miner cross-repo evaluation (#4788)", + "loopover-miner cross-repo evaluation (#4788 readiness / #7634 full-execution)", "", "Usage:", " node packages/loopover-miner/scripts/cross-repo-evaluation.mjs [options]", @@ -77,16 +85,19 @@ function printHelp() { "Options:", " --manifest Benchmark manifest (default: benchmarks/cross-repo/manifest.json)", " --repo Evaluate a single benchmark entry", + " --full-execution Local discover->plan->code->build->test (no forge PR writes) (#7634)", " --json Emit machine-readable JSON on stdout", " --require-majority Exit 1 unless a strict majority of repos pass", " -h, --help Show this help", "", "Prerequisite: clone benchmark repos into LOOPOVER_MINER_REPO_CLONE_DIR (see docs/cross-repo-evaluation.md).", + "Full-execution never opens PRs against benchmark repos; inject runCodingAttempt or set", + "LOOPOVER_MINER_FULL_EXECUTION_STUB=1 for a synthetic local handoff.", ].join("\n"), ); } -function main() { +async function main() { const parsedArgs = parseCrossRepoEvaluationArgs(); if (parsedArgs.help) { printHelp(); @@ -97,7 +108,7 @@ function main() { return 2; } - const { parsed, results, summary } = runCrossRepoEvaluationCli(parsedArgs); + const { parsed, results, summary } = await runCrossRepoEvaluationCli(parsedArgs); if (parsedArgs.json) { console.log(JSON.stringify({ warnings: parsed.warnings, results, summary }, null, 2)); } else { @@ -112,5 +123,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..c28b4e77e2 100644 --- a/test/unit/miner-cross-repo-evaluation.test.ts +++ b/test/unit/miner-cross-repo-evaluation.test.ts @@ -12,6 +12,7 @@ import { DEFAULT_CROSS_REPO_MANIFEST_RELATIVE_PATH, MAX_CROSS_REPO_MANIFEST_BYTES, formatCrossRepoEvaluationReport, + evaluateRepoFullExecution, evaluateRepoReadiness, normalizeCrossRepoFullName, parseCrossRepoEvaluationManifest, @@ -107,7 +108,7 @@ describe("cross-repo evaluation harness (#4788)", () => { JSON.stringify({ repos: [ "acme/alpha", - { repoFullName: "acme/beta", stackHint: "nodejs", requireTestCommand: true }, + { repoFullName: "acme/beta", stackHint: "nodejs", requireTestCommand: true, fullExecution: true }, "acme/alpha", { repoFullName: "bad", requireTestCommand: "yes" }, 7, @@ -117,7 +118,7 @@ describe("cross-repo evaluation harness (#4788)", () => { expect(parsed.present).toBe(true); expect(parsed.manifest.repos).toEqual([ { repoFullName: "acme/alpha", requireTestCommand: false }, - { repoFullName: "acme/beta", stackHint: "nodejs", requireTestCommand: true }, + { repoFullName: "acme/beta", stackHint: "nodejs", requireTestCommand: true, fullExecution: true }, ]); expect(parsed.warnings.some((w) => w.includes("duplicate"))).toBe(true); expect(parsed.warnings.some((w) => w.includes("invalid"))).toBe(true); @@ -372,11 +373,11 @@ describe("cross-repo evaluation harness (#4788)", () => { }); describe("runCrossRepoEvaluation + summarizeCrossRepoEvaluation", () => { - it("filters to a single repo and computes majority + category counts", () => { + it("filters to a single repo and computes majority + category counts", async () => { const parsed = parseCrossRepoEvaluationManifest( JSON.stringify({ repos: ["acme/a", "acme/b", "acme/c"] }), ); - const results = runCrossRepoEvaluation(parsed, { + const results = await runCrossRepoEvaluation(parsed, { repoFilter: "acme/b", existsSync: () => false, }); @@ -446,9 +447,9 @@ describe("cross-repo evaluation harness (#4788)", () => { expect(summary.failuresByCategory.other).toBe(1); }); - it("runCrossRepoEvaluation treats a parsed manifest without a repos list as no repos", () => { - expect(runCrossRepoEvaluation({} as never)).toEqual([]); - expect(runCrossRepoEvaluation(undefined as never)).toEqual([]); + it("runCrossRepoEvaluation treats a parsed manifest without a repos list as no repos", async () => { + expect(await runCrossRepoEvaluation({} as never)).toEqual([]); + expect(await runCrossRepoEvaluation(undefined as never)).toEqual([]); }); it("summarizeCrossRepoEvaluation treats a non-array input as an empty run", () => { @@ -495,13 +496,21 @@ describe("cross-repo evaluation harness (#4788)", () => { json: true, repoFilter: "acme/widgets", requireMajority: true, + fullExecution: false, + }); + expect(parseCrossRepoEvaluationArgs(["--full-execution"])).toEqual({ + manifestPath: resolveDefaultManifestPath(), + json: false, + repoFilter: null, + requireMajority: false, + fullExecution: true, }); expect(parseCrossRepoEvaluationArgs(["--manifest"])).toEqual({ error: "Missing value for --manifest." }); expect(parseCrossRepoEvaluationArgs(["--nope"])).toEqual({ error: "Unknown argument: --nope" }); expect(parseCrossRepoEvaluationArgs(["--help"])).toEqual({ help: true }); }); - it("runs the harness driver against a fixture manifest", () => { + it("runs the harness driver against a fixture manifest", async () => { const repoPath = tempRepo({ "package.json": pkg({ scripts: { test: "node --test" } }), }); @@ -514,7 +523,7 @@ describe("cross-repo evaluation harness (#4788)", () => { "utf8", ); - const { parsed, results, summary } = runCrossRepoEvaluationCli({ + const { parsed, results, summary } = await runCrossRepoEvaluationCli({ manifestPath: join(manifestPath, "manifest.json"), }); expect(parsed.warnings).toEqual([]); @@ -528,10 +537,173 @@ describe("cross-repo evaluation harness (#4788)", () => { }); }); + describe("evaluateRepoFullExecution (#7634)", () => { + const readyStack = (overrides: Partial = {}): RepoStackResult => + ({ + detected: true, + language: "javascript", + packageManager: "npm", + testCommand: "npm test", + buildCommand: "npm run build", + ...overrides, + }) as RepoStackResult; + + const readyOptions = (repoPath: string, extra: Record = {}) => ({ + repoPath, + existsSync: () => true, + detectRepoStack: () => readyStack(), + resolveMinerGoalSpec: () => ({ present: false }), + buildCodingTaskSpec: () => ({ ready: true, instructions: "Fix the bug without assuming LoopOver CI." }), + ...extra, + }); + + it("returns readiness failures unchanged", async () => { + const result = await evaluateRepoFullExecution( + { repoFullName: "acme/missing", requireTestCommand: false }, + { repoPath: "/tmp/missing-full-exec", existsSync: () => false }, + ); + expect(result.failureCategory).toBe(CROSS_REPO_FAILURE_CATEGORY.CLONE_SETUP); + }); + + it("fails execution_abandon when the coding attempt abandons", async () => { + const repoPath = tempRepo({ "package.json": pkg({ scripts: { test: "node --test", build: "echo ok" } }) }); + const result = await evaluateRepoFullExecution( + { repoFullName: "acme/abandon", requireTestCommand: true }, + readyOptions(repoPath, { + runCodingAttempt: () => ({ outcome: "abandon", changedFiles: [], reason: "agent offline" }), + }), + ); + expect(result.failureCategory).toBe(CROSS_REPO_FAILURE_CATEGORY.EXECUTION_ABANDON); + expect(result.reason).toContain("agent offline"); + }); + + it("fails plan_formed_compile_failed when the local build fails", async () => { + const repoPath = tempRepo({ "package.json": pkg({ scripts: { test: "node --test", build: "echo ok" } }) }); + const result = await evaluateRepoFullExecution( + { repoFullName: "acme/compile", requireTestCommand: true }, + readyOptions(repoPath, { + runCodingAttempt: () => ({ outcome: "handoff", changedFiles: ["src/fix.js"] }), + runShellCommand: ({ command }: { command: string }) => + command.includes("build") + ? { ok: false, exitCode: 2, stderr: "tsc failed" } + : { ok: true, exitCode: 0 }, + }), + ); + expect(result.failureCategory).toBe(CROSS_REPO_FAILURE_CATEGORY.COMPILE_FAILED); + }); + + it("fails compiled_tests_failed when tests fail after a successful build", async () => { + const repoPath = tempRepo({ "package.json": pkg({ scripts: { test: "node --test", build: "echo ok" } }) }); + const result = await evaluateRepoFullExecution( + { repoFullName: "acme/tests", requireTestCommand: true }, + readyOptions(repoPath, { + runCodingAttempt: () => ({ outcome: "handoff", changedFiles: ["src/fix.js"] }), + runShellCommand: ({ command }: { command: string }) => + command.includes("test") + ? { ok: false, exitCode: 1, stderr: "1 failing" } + : { ok: true, exitCode: 0 }, + }), + ); + expect(result.failureCategory).toBe(CROSS_REPO_FAILURE_CATEGORY.TESTS_FAILED); + }); + + it("fails tests_passed_noop_diff when the handoff changed no files", async () => { + const repoPath = tempRepo({ "package.json": pkg({ scripts: { test: "node --test", build: "echo ok" } }) }); + const result = await evaluateRepoFullExecution( + { repoFullName: "acme/noop", requireTestCommand: true }, + readyOptions(repoPath, { + runCodingAttempt: () => ({ outcome: "handoff", changedFiles: [] }), + runShellCommand: () => ({ ok: true, exitCode: 0 }), + }), + ); + expect(result.failureCategory).toBe(CROSS_REPO_FAILURE_CATEGORY.NOOP_DIFF); + }); + + it("passes when handoff + build + test succeed with a non-empty diff", async () => { + const repoPath = tempRepo({ "package.json": pkg({ scripts: { test: "node --test", build: "echo ok" } }) }); + const result = await evaluateRepoFullExecution( + { repoFullName: "acme/ok", requireTestCommand: true }, + readyOptions(repoPath, { + runCodingAttempt: () => ({ outcome: "handoff", changedFiles: ["src/fix.js"] }), + runShellCommand: () => ({ ok: true, exitCode: 0 }), + }), + ); + expect(result.passed).toBe(true); + expect(result.failureCategory).toBeNull(); + }); + + it("selects fullExecution-tagged repos for --full-execution runs", async () => { + const repoPath = tempRepo({ "package.json": pkg({ scripts: { test: "node --test", build: "echo ok" } }) }); + const parsed = parseCrossRepoEvaluationManifest( + JSON.stringify({ + repos: [ + { repoFullName: "acme/skip", requireTestCommand: true }, + { repoFullName: "acme/one", fixturePath: repoPath, requireTestCommand: true, fullExecution: true }, + { repoFullName: "acme/two", fixturePath: repoPath, requireTestCommand: true, fullExecution: true }, + ], + }), + ); + const results = await runCrossRepoEvaluation(parsed, { + fullExecution: true, + existsSync: () => true, + detectRepoStack: () => readyStack(), + resolveMinerGoalSpec: () => ({ present: false }), + buildCodingTaskSpec: () => ({ ready: true, instructions: "ok" }), + runCodingAttempt: () => ({ outcome: "handoff", changedFiles: ["a.js"] }), + runShellCommand: () => ({ ok: true, exitCode: 0 }), + }); + expect(results.map((r) => r.repoFullName)).toEqual(["acme/one", "acme/two"]); + expect(results.every((r) => r.passed)).toBe(true); + }); + + it("runs the CLI full-execution driver against a fixture without forge writes", async () => { + const repoPathA = tempRepo({ + "package.json": pkg({ scripts: { test: "node --test", build: "echo ok" } }), + }); + const repoPathB = tempRepo({ + "package.json": pkg({ scripts: { test: "node --test", build: "echo ok" } }), + }); + const manifestDir = tempRepo(); + writeFileSync( + join(manifestDir, "manifest.json"), + JSON.stringify({ + repos: [ + { + repoFullName: "acme/full-a", + fixturePath: repoPathA, + requireTestCommand: true, + fullExecution: true, + }, + { + repoFullName: "acme/full-b", + fixturePath: repoPathB, + requireTestCommand: true, + fullExecution: true, + }, + ], + }), + "utf8", + ); + + const { results, summary } = await runCrossRepoEvaluationCli({ + manifestPath: join(manifestDir, "manifest.json"), + fullExecution: true, + }); + // Default coding seam abandons without STUB/injection -- still a valid dry-run path (no PR). + expect(results).toHaveLength(2); + expect(results.every((r) => r.failureCategory === CROSS_REPO_FAILURE_CATEGORY.EXECUTION_ABANDON)).toBe(true); + expect(summary.failed).toBe(2); + expect(formatCrossRepoEvaluationReport(results, summary)).toContain("execution_abandon"); + }); + }); + it("documents the harness in packages/loopover-miner/docs/cross-repo-evaluation.md", () => { const doc = readFileSync(join(process.cwd(), "packages/loopover-miner/docs/cross-repo-evaluation.md"), "utf8"); expect(doc).toContain("#4788"); + expect(doc).toContain("#7634"); expect(doc).toContain("stack_detection_gap"); + expect(doc).toContain("plan_formed_compile_failed"); + expect(doc).toContain("--full-execution"); expect(doc).toContain("cross-repo-evaluation.mjs"); expect(doc).toContain("benchmarks/cross-repo/manifest.json"); });