@orangepro/orangepro-mcp 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -26,10 +26,71 @@ import { loadGraph, workspacePaths } from "./workspace.js";
26
26
  import { systemClock } from "./util/time.js";
27
27
  import { redactSecrets } from "./util/redact.js";
28
28
  import { classifyBaselineFailure, EXPERIMENTAL_SQLITE_TEST_ENV, IMPORT_TIME_CATEGORIES, isNeedsSetupCategory, readEnginesNode, targetNeedsExperimentalSqlite } from "./proofRunnability.js";
29
- /** Real dynamic proof is TS/JS-only; only these targets are attemptable. */
29
+ /** Real dynamic proof is profile-gated; only wired runner targets are attemptable. */
30
30
  function isTsJsFile(file) {
31
31
  return /\.[cm]?[jt]sx?$/i.test(file);
32
32
  }
33
+ function isGoFile(file) {
34
+ return /\.go$/i.test(file);
35
+ }
36
+ function isJavaFile(file) {
37
+ return /\.java$/i.test(file);
38
+ }
39
+ function isPythonFile(file) {
40
+ return /\.py$/i.test(file);
41
+ }
42
+ function replacementForTarget(file) {
43
+ return isPythonFile(file) ? "return 0" : "return null;";
44
+ }
45
+ function codeSymbolFile(node) {
46
+ return typeof node.properties.file === "string" ? node.properties.file : node.external_id.replace(/^sym:/, "").split("#")[0];
47
+ }
48
+ function isRunnablePythonTestPath(testRel) {
49
+ const file = testRel.split("::", 1)[0] ?? testRel;
50
+ return /(^|\/)(test_[^/]+|[^/]+_test)\.py$/i.test(file);
51
+ }
52
+ function isRunnableTestForTarget(node, testRel) {
53
+ const file = codeSymbolFile(node);
54
+ if (isPythonFile(file))
55
+ return isRunnablePythonTestPath(testRel);
56
+ return true;
57
+ }
58
+ function pytestNodeidsForFile(sourceRoot, testRel) {
59
+ if (testRel.includes("::"))
60
+ return [testRel];
61
+ if (!isRunnablePythonTestPath(testRel))
62
+ return [testRel];
63
+ let text = "";
64
+ try {
65
+ text = readFileSync(resolve(sourceRoot, testRel), "utf8");
66
+ }
67
+ catch {
68
+ return [testRel];
69
+ }
70
+ const out = [];
71
+ let currentClass = null;
72
+ let classIndent = 0;
73
+ for (const line of text.split(/\r?\n/)) {
74
+ const indent = line.match(/^\s*/)?.[0].length ?? 0;
75
+ const classMatch = /^(\s*)class\s+(Test[A-Za-z0-9_]*)\b/.exec(line);
76
+ if (classMatch) {
77
+ currentClass = classMatch[2];
78
+ classIndent = classMatch[1].length;
79
+ continue;
80
+ }
81
+ if (currentClass && indent <= classIndent && line.trim() !== "" && !line.startsWith(" "))
82
+ currentClass = null;
83
+ const fnMatch = /^(\s*)def\s+(test_[A-Za-z0-9_]*)\s*\(/.exec(line);
84
+ if (!fnMatch)
85
+ continue;
86
+ const fnIndent = fnMatch[1].length;
87
+ if (currentClass && fnIndent > classIndent)
88
+ out.push(`${testRel}::${currentClass}::${fnMatch[2]}`);
89
+ else
90
+ out.push(`${testRel}::${fnMatch[2]}`);
91
+ }
92
+ return out.length ? out.slice(0, 25) : [testRel];
93
+ }
33
94
  /**
34
95
  * SOLE trust barrier for auto-prove target selection. `opDynamicProof` has NO
35
96
  * eligibility guard — `resolveTargetSymbol` resolves ANY CodeSymbol and the prove
@@ -37,7 +98,17 @@ function isTsJsFile(file) {
37
98
  * Proven against plumbing. Auto-prove must therefore refuse anything that is not an
38
99
  * entry-point-adjacent behavior surface: eligible (top-level `denominator_eligible`),
39
100
  * `behavior_surface === "entrypoint_adjacent"`, and carrying NO `denominator_reason_code`
40
- * (infra_behavior_surface / not_entry_point_adjacent). TS/JS file, always.
101
+ * (infra_behavior_surface / not_entry_point_adjacent).
102
+ *
103
+ * Language: TS/JS (unchanged), Go (G-INT-2), OR Java (J-INT-2). Go dynamic proof
104
+ * (G-1) proves FREE FUNCTIONS ONLY — a Go method classifies `unrunnable` and never
105
+ * mints, so admitting one is safe (never a false Proven) but wastes an attempt; when
106
+ * the clean `symbol_kind === "method"` signal is present we exclude it up front. Java
107
+ * dynamic proof (J-1) is the INVERSE — it proves single-top-level-return METHODS, so
108
+ * we admit Java methods; a non-J-1-shape method (void/constructor/nested/generic) just
109
+ * classifies `unrunnable` and never mints, safe by construction. Everything downstream
110
+ * of `closed`/the cert is language-agnostic and mints Go/Java only through the
111
+ * unchanged G-INT-1/J-INT-1 gates.
41
112
  */
42
113
  export function isEligibleProvableTarget(node) {
43
114
  if (!node || node.kind !== "CodeSymbol")
@@ -48,8 +119,218 @@ export function isEligibleProvableTarget(node) {
48
119
  return false;
49
120
  if (node.properties.denominator_reason_code != null)
50
121
  return false;
51
- const file = typeof node.properties.file === "string" ? node.properties.file : node.external_id.replace(/^sym:/, "").split("#")[0];
52
- return isTsJsFile(file);
122
+ return matchesProvableLanguageShape(node);
123
+ }
124
+ /**
125
+ * The LANGUAGE + oracle-SHAPE half of eligibility, factored out so both the strict
126
+ * `isEligibleProvableTarget` (which layers the entry-point-adjacent SCOPE guards on top)
127
+ * and the relaxed hard-edge existing-tests path share ONE definition of "a shape the
128
+ * oracle can prove". CodeSymbol + a TS/JS file, a Go free function, a Java method, or a
129
+ * Python function/method.
130
+ */
131
+ function matchesProvableLanguageShape(node) {
132
+ if (node.kind !== "CodeSymbol")
133
+ return false;
134
+ const file = codeSymbolFile(node);
135
+ if (isTsJsFile(file))
136
+ return true;
137
+ // Go: free functions only. A method is out of scope for the Go oracle (G-1 refuses it).
138
+ if (isGoFile(file))
139
+ return node.properties.symbol_kind !== "method";
140
+ // Java: METHODS only (J-1 proves single-top-level-return methods). A non-J-1-shape
141
+ // method classifies `unrunnable` and never mints, so admitting all Java methods is
142
+ // safe (never a false Proven); a non-method Java symbol (a class container) is out
143
+ // of scope. No graph return-shape signal exists to pre-filter, so we admit broadly
144
+ // and let the Java oracle refuse non-J-1 shapes at run time.
145
+ if (isJavaFile(file))
146
+ return node.properties.symbol_kind === "method";
147
+ if (isPythonFile(file)) {
148
+ const symbolKind = typeof node.properties.symbol_kind === "string" ? node.properties.symbol_kind : "";
149
+ if (symbolKind !== "function" && symbolKind !== "method")
150
+ return false;
151
+ const member = node.external_id.split("#")[1] ?? "";
152
+ if (/^__.*__$/.test(member))
153
+ return false;
154
+ return true;
155
+ }
156
+ return false;
157
+ }
158
+ /**
159
+ * Relaxed eligibility for the existing-tests HARD-edge path ONLY. A symbol carrying a
160
+ * HARD `TESTED_BY`/`COVERS` proof edge (a real, analyzer-derived test) is admitted even
161
+ * when it is `not_entry_point_adjacent` — a Formatter/Converter SPI method like
162
+ * `PetTypeFormatter#print` is a genuine behavior the repo's own test exercises, but it
163
+ * sits below the entry-point-adjacent denominator bar. Only the `not_entry_point_adjacent`
164
+ * SCOPE guard is dropped, NOT a trust guard: a relaxed pick is only ever proven-or-refused
165
+ * by the frozen oracle, never false-Proven. Guards deliberately KEPT:
166
+ * - `infra_behavior_surface` still excludes plumbing (getters/registry accessors) — a
167
+ * hard edge does not buy an infra symbol an attempt (waste, and preserves the #4 bar);
168
+ * - the language + oracle-shape filter (never hand a class container / Go method down).
169
+ * Used solely on the hard lane; weak MAY_* fan-out stays strict.
170
+ */
171
+ function isEligibleHardExistingTarget(node) {
172
+ if (!node)
173
+ return false;
174
+ if (node.properties.denominator_reason_code === "infra_behavior_surface")
175
+ return false;
176
+ return matchesProvableLanguageShape(node);
177
+ }
178
+ /** Go `_test.go` top-level test-name regex (matches `extractTestNames`'s Go pattern). */
179
+ const GO_TEST_NAME_RE = /^Test[A-Za-z0-9_]+$/;
180
+ /**
181
+ * Edge `test_name` may also be a literal-named subtest path (`TestX/sub`, `TestX/a/b`).
182
+ * The analyzer only records `[A-Za-z0-9_]` segments (a runtime `tc.Name` or a literal
183
+ * needing Go's `-run` sanitization is dropped to the bare parent), so each segment is
184
+ * `-run`-safe verbatim and this stays a strict superset of `GO_TEST_NAME_RE`.
185
+ */
186
+ const GO_TEST_PATH_RE = /^Test[A-Za-z0-9_]+(\/[A-Za-z0-9_]+)*$/;
187
+ /**
188
+ * Anchor a Go test-name path into a fully-anchored `-run` pattern the oracle binds to
189
+ * EXACTLY one test: `TestX` → `^TestX$`; `TestX/sub` → `^TestX$/^sub$` (every segment
190
+ * anchored so no segment can prefix-match a sibling). Mirrors the spike's `targetTestName`.
191
+ */
192
+ function anchorGoTestRun(name) {
193
+ return name.split("/").map((seg) => `^${seg}$`).join("/");
194
+ }
195
+ /**
196
+ * G-INT-2: resolve a Go CodeSymbol target to the single anchored `^TestName$` that
197
+ * exercises it, or null when it cannot be resolved uniquely. The Go oracle selects
198
+ * its target test BY NAME (`go test -run ^TestX$`), so auto-drive must derive that
199
+ * name — G-INT-1 took it as an explicit input.
200
+ *
201
+ * The link is the analyzer's HARD `TESTED_BY`/`COVERS` proof edge (Go free-fn sym ↔
202
+ * `test:<file>_test.go`), emitted only for an eligible Go symbol whose test genuinely
203
+ * asserts on it. PRIMARY: the edge carries `properties.test_name` — the EXACT enclosing
204
+ * `func TestXxx` where the assertion witnessed THIS target (structural metadata, never
205
+ * proof) — which disambiguates even a `_test.go` file with many tests. FALLBACK (old
206
+ * graphs / no edge metadata): the linked TestCase node's file-level `test_names[]`,
207
+ * usable only when it lists exactly ONE Go test. Either way we return a name only when
208
+ * it resolves UNIQUELY across all associated tests; zero/ambiguous ⇒ null (SKIP). A
209
+ * wrong name never mints a false Proven — the mutant survives an unrelated test and
210
+ * classifies unproven — but it wastes a run and the spike refuses a broad pattern anyway.
211
+ */
212
+ export function goTestRunForTarget(nodeById, graph, symId) {
213
+ const names = new Set();
214
+ const testIds = new Set();
215
+ for (const e of graph.edges) {
216
+ const isTestedBy = e.from_external_id === symId && e.relationship_type === "TESTED_BY";
217
+ const isCovers = e.to_external_id === symId && e.relationship_type === "COVERS";
218
+ if (!isTestedBy && !isCovers)
219
+ continue;
220
+ testIds.add(isTestedBy ? e.to_external_id : e.from_external_id);
221
+ const edgeName = e.properties?.test_name;
222
+ if (typeof edgeName === "string" && GO_TEST_PATH_RE.test(edgeName))
223
+ names.add(edgeName);
224
+ }
225
+ if (names.size >= 1)
226
+ return names.size === 1 ? anchorGoTestRun([...names][0]) : null;
227
+ // Fallback: no per-edge test_name (older graph) — a file that holds exactly one
228
+ // (bare, top-level) test; file-level `test_names[]` never carries subtest paths.
229
+ for (const testId of testIds) {
230
+ const tc = nodeById.get(testId);
231
+ if (!tc || tc.kind !== "TestCase")
232
+ continue;
233
+ const testNames = Array.isArray(tc.properties.test_names) ? tc.properties.test_names : [];
234
+ for (const n of testNames)
235
+ if (typeof n === "string" && GO_TEST_NAME_RE.test(n))
236
+ names.add(n);
237
+ }
238
+ return names.size === 1 ? anchorGoTestRun([...names][0]) : null;
239
+ }
240
+ /**
241
+ * Slice 2: the assertion source line for the edge whose `test_name` anchors to `testRun`.
242
+ * Returned only when EXACTLY ONE such edge carries a numeric `assertion_line` (unique-else-
243
+ * undefined, mirroring `goTestRunForTarget`'s discipline). Undefined ⇒ the spike keeps its
244
+ * exact-name behavior. Never widens trust — a wrong line makes the oracle refuse, not prove.
245
+ */
246
+ export function goAssertionLineForTarget(graph, symId, testRun) {
247
+ const lines = new Set();
248
+ for (const e of graph.edges) {
249
+ const isTestedBy = e.from_external_id === symId && e.relationship_type === "TESTED_BY";
250
+ const isCovers = e.to_external_id === symId && e.relationship_type === "COVERS";
251
+ if (!isTestedBy && !isCovers)
252
+ continue;
253
+ const edgeName = e.properties?.test_name;
254
+ const line = e.properties?.assertion_line;
255
+ if (typeof edgeName === "string" && GO_TEST_PATH_RE.test(edgeName) && anchorGoTestRun(edgeName) === testRun && typeof line === "number") {
256
+ lines.add(line);
257
+ }
258
+ }
259
+ return lines.size === 1 ? [...lines][0] : undefined;
260
+ }
261
+ /** A JUnit test method identifier (any Java identifier — JUnit does not require a Test* prefix). */
262
+ const JAVA_TEST_METHOD_RE = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
263
+ /** The simple test-class name for a Java `test:<relPath>` id — the file basename without `.java`. */
264
+ function javaTestClassOf(testRelId) {
265
+ if (!testRelId.startsWith("test:"))
266
+ return null;
267
+ const rel = testRelId.slice("test:".length);
268
+ if (!/\.java$/i.test(rel))
269
+ return null;
270
+ const base = rel.split("/").pop() ?? rel;
271
+ return base.replace(/\.java$/i, "") || null;
272
+ }
273
+ /**
274
+ * J-INT-2: resolve a Java CodeSymbol target to the single `Class#method` JUnit test
275
+ * that exercises it, or null when it cannot be resolved uniquely. The Java oracle
276
+ * selects its target test with `mvn test -Dtest=Class#method`, so auto-drive must
277
+ * derive that selector — J-INT-1 took it as an explicit input.
278
+ *
279
+ * The link is the analyzer's HARD `TESTED_BY`/`COVERS` proof edge (Java method sym ↔
280
+ * `test:<...>Test.java`), emitted only for a Java symbol a JUnit test genuinely
281
+ * asserts on. The test CLASS is the test file's simple class name (its basename, per
282
+ * Java's one-public-class-per-file convention; the spike accepts a simple class).
283
+ * The test METHOD is, PRIMARY: the edge's `properties.test_name` — the exact enclosing
284
+ * `@Test` method where the assertion witnessed THIS target (structural metadata, never
285
+ * proof) — which disambiguates a test class with many @Test methods. FALLBACK (older
286
+ * graphs / no edge metadata): the linked TestCase node's file-level `test_names[]`,
287
+ * usable only when it lists exactly ONE test. We return a `Class#method` only when the
288
+ * PAIR resolves UNIQUELY across all associated tests; zero/ambiguous ⇒ null (SKIP). A
289
+ * wrong selector never mints a false Proven — the mutant survives an unrelated test and
290
+ * classifies unproven — but it wastes a run, so we refuse to guess.
291
+ */
292
+ export function javaTestForTarget(nodeById, graph, symId) {
293
+ const selectors = new Set();
294
+ // Per test id, the enclosing @Test method names named by proof edges (primary source).
295
+ const edgeMethodsByTest = new Map();
296
+ const testIds = new Set();
297
+ for (const e of graph.edges) {
298
+ const isTestedBy = e.from_external_id === symId && e.relationship_type === "TESTED_BY";
299
+ const isCovers = e.to_external_id === symId && e.relationship_type === "COVERS";
300
+ if (!isTestedBy && !isCovers)
301
+ continue;
302
+ const testId = isTestedBy ? e.to_external_id : e.from_external_id;
303
+ testIds.add(testId);
304
+ const edgeName = e.properties?.test_name;
305
+ if (typeof edgeName === "string" && JAVA_TEST_METHOD_RE.test(edgeName)) {
306
+ const set = edgeMethodsByTest.get(testId) ?? new Set();
307
+ set.add(edgeName);
308
+ edgeMethodsByTest.set(testId, set);
309
+ }
310
+ }
311
+ for (const testId of testIds) {
312
+ const cls = javaTestClassOf(testId);
313
+ if (!cls)
314
+ continue;
315
+ for (const m of edgeMethodsByTest.get(testId) ?? [])
316
+ selectors.add(`${cls}#${m}`);
317
+ }
318
+ if (selectors.size >= 1)
319
+ return selectors.size === 1 ? [...selectors][0] : null;
320
+ // Fallback: no per-edge test_name (older graph) — a test class holding exactly one test.
321
+ for (const testId of testIds) {
322
+ const cls = javaTestClassOf(testId);
323
+ if (!cls)
324
+ continue;
325
+ const tc = nodeById.get(testId);
326
+ if (!tc || tc.kind !== "TestCase")
327
+ continue;
328
+ const testNames = Array.isArray(tc.properties.test_names) ? tc.properties.test_names : [];
329
+ const valid = testNames.filter((n) => typeof n === "string" && JAVA_TEST_METHOD_RE.test(n));
330
+ if (valid.length === 1)
331
+ selectors.add(`${cls}#${valid[0]}`);
332
+ }
333
+ return selectors.size === 1 ? [...selectors][0] : null;
53
334
  }
54
335
  const GENERATED_HEADER = "// Generated by OrangePro — do not edit";
55
336
  // "Static map first, dynamically prove top 5": ONE unified dynamic-proof budget spans
@@ -67,6 +348,9 @@ const GEN_WINDOW = 5;
67
348
  // a provable hard-edge symbol is tried. Small K; promote to a flag if a repo needs a wider sweep.
68
349
  const EXISTING_LANE_MAX_WEAK_PER_SYMBOL = 3;
69
350
  export const NO_KEY_MESSAGE = "No provider key; auto-prove skipped — add OPENAI_API_KEY / ANTHROPIC_API_KEY, or use the OrangePro MCP in your coding agent.";
351
+ export function isRoastSurvivor(attempt) {
352
+ return attempt.classification === "non_killing" && attempt.mutant_status === "associated_survived";
353
+ }
70
354
  /**
71
355
  * Resolve the contained absolute path for a generated file, REJECTING any name that
72
356
  * escapes `<sourceRoot>/orangepro_generated/`. Exported so the guardrail is unit-tested.
@@ -89,7 +373,8 @@ const REL_IMPORT_RE = /(\bfrom\s*|\bimport\s*|\brequire\s*\(\s*|\bimport\s*\(\s*
89
373
  * against the target source file's directory, then re-express it relative to
90
374
  * `orangepro_generated/` (POSIX, leading `./`) — e.g. `./order.service` →
91
375
  * `../src/order.service`. Only import/require positions are touched, never plain
92
- * string literals. TS/JS only (dynamic proof is TS/JS-only).
376
+ * string literals. TS/JS only this rewrite serves the TS/JS generation lane;
377
+ * Go/Java/Python proofs route through their own language spikes.
93
378
  */
94
379
  function rewriteRelativeImports(body, targetFileRel, generatedDir) {
95
380
  const baseDir = posix.dirname(targetFileRel.split(sep).join("/"));
@@ -142,6 +427,11 @@ function classifyProof(result, ctx) {
142
427
  }
143
428
  return { classification: "non_killing", reason: record.reason ?? "Mutant survived; the test does not assert on the target's real behavior." };
144
429
  }
430
+ function mutantStatusOf(result) {
431
+ if ("status" in result && result.status === "unrunnable")
432
+ return "unrunnable";
433
+ return result.record.dynamic_proof?.mutant_status;
434
+ }
145
435
  /**
146
436
  * R-1 sibling-dedup key: a baseline-red import-time failure is a deterministic property of
147
437
  * loading the TARGET FILE with a given runner, independent of which test runs it — so
@@ -223,7 +513,10 @@ export function existingAssociatedTests(graph, nodeById) {
223
513
  // edges. Hard is recorded before weak (graph.edges scanned first), so a test already
224
514
  // linked hard is never downgraded; a later weak dup only upgrades an existing weak to hard.
225
515
  const add = (symId, testRel, hard) => {
226
- if (!isEligibleProvableTarget(nodeById.get(symId)))
516
+ // A HARD TESTED_BY/COVERS edge is a real derivable test; admit it even when the symbol
517
+ // is not_entry_point_adjacent (relaxed shape-only guard). Weak MAY_* fan-out stays strict.
518
+ const node = nodeById.get(symId);
519
+ if (!(hard ? isEligibleHardExistingTarget(node) : isEligibleProvableTarget(node)))
227
520
  return;
228
521
  const list = out.get(symId);
229
522
  if (!list) {
@@ -313,11 +606,13 @@ export function orderExistingAttempts(testsBySymbol, maxWeakPerSymbol = EXISTING
313
606
  * PR 1.5 lane — prove the repo's OWN existing tests, NO provider key. For each eligible
314
607
  * target with an existing associated test, run the UNCHANGED `opProveLoop` with the test
315
608
  * IN ITS ORIGINAL LOCATION (never copied/relocated — that avoids the generation lane's
316
- * import-grounding pitfall) and a null-sentinel mutant. Closes Proven; survives /
317
- * crashes-pre-assert / setup-fails honest skip (never Proven, and #162 best-ever
318
- * selection means it can never clobber a prior Proven). Consumes from the SHARED unified
319
- * budget (`budget`) the generation lane gets whatever this lane leaves unspent, so
320
- * TOTAL attempts (existing + generation) never exceed the budget.
609
+ * import-grounding pitfall). TS/JS uses a null-sentinel mutant; Go (G-INT-2) selects the
610
+ * test by its derived `^TestName$` and lets the Go oracle compute its own zero-value
611
+ * sentinel. Closes Proven; survives / crashes-pre-assert / setup-fails / (Go)
612
+ * unresolvable-test-name honest skip (never Proven, and #162 best-ever selection means
613
+ * it can never clobber a prior Proven). Consumes from the SHARED unified budget (`budget`)
614
+ * — the generation lane gets whatever this lane leaves unspent, so TOTAL attempts
615
+ * (existing + generation) never exceed the budget.
321
616
  */
322
617
  function proveExistingAssociatedTests(root, graph, sourceRoot, nodeById, opts, proveLoop, proveDeps, alreadyProven, importTimeBlocked, budget) {
323
618
  const attempts = [];
@@ -329,13 +624,14 @@ function proveExistingAssociatedTests(root, graph, sourceRoot, nodeById, opts, p
329
624
  const reader = fileReaderFor(sourceRoot); // R-2: source scan for the node:sqlite env profile
330
625
  // Fix 3: hard TESTED_BY/COVERS pairs first, weak MAY_* pairs after and capped per symbol.
331
626
  const queue = orderExistingAttempts(existingAssociatedTests(graph, nodeById));
332
- for (const { symId, testRel } of queue) {
627
+ for (const { symId, testRel, hard } of queue) {
333
628
  if (attempted >= budget)
334
629
  break;
335
630
  const node = nodeById.get(symId);
336
631
  // Redundant with existingAssociatedTests' own filter, but the eligibility barrier is
337
632
  // the sole guard against handing plumbing to the guard-less prove path — assert it here too.
338
- if (!isEligibleProvableTarget(node))
633
+ // A hard-edge pick uses the relaxed shape-only guard (mirrors the add() decision above).
634
+ if (!(hard ? isEligibleHardExistingTarget(node) : isEligibleProvableTarget(node)))
339
635
  continue;
340
636
  if (changed && !changed.has(symbolFileOf(node)))
341
637
  continue;
@@ -351,29 +647,66 @@ function proveExistingAssociatedTests(root, graph, sourceRoot, nodeById, opts, p
351
647
  // (engine_mismatch: runner Node outside the declared engines range). Every sibling in this
352
648
  // file fails baseline identically → mark it
353
649
  // needs_setup WITHOUT re-running (and WITHOUT consuming the attempt budget).
650
+ const isPython = isPythonFile(targetFileRel);
651
+ const candidateTestRels = isPython
652
+ ? pytestNodeidsForFile(sourceRoot, testRel).filter((candidate) => isRunnableTestForTarget(node, candidate))
653
+ : [testRel];
654
+ if (candidateTestRels.length === 0)
655
+ continue;
656
+ const proofTestRel = candidateTestRels[0];
354
657
  const blocked = importTimeBlocked.get(dedupKey(undefined, targetFileRel));
355
658
  if (blocked) {
356
- const attempt = dedupedAttempt(symId, testRel, targetFileRel, blocked);
659
+ const attempt = dedupedAttempt(symId, proofTestRel, targetFileRel, blocked);
357
660
  attempts.push(attempt);
358
661
  needsSetup.push(attempt);
359
662
  continue;
360
663
  }
664
+ // Go and Java targets select the test BY NAME and let their oracle derive its own
665
+ // typed sentinel — no test_path/replacement/link_node_modules/test_env apply. Go
666
+ // uses `go test -run ^TestX$`; Java uses `mvn test -Dtest=Class#method`. Resolve the
667
+ // exact selector; if it can't be resolved uniquely, SKIP without spending the attempt
668
+ // budget (a broad/wrong selector is refused/unmatched by the oracle and wastes a run).
669
+ const isGo = isGoFile(targetFileRel);
670
+ const isJava = isJavaFile(targetFileRel);
671
+ let nativeTestRun = null;
672
+ let goAssertionLine;
673
+ if (isGo) {
674
+ nativeTestRun = goTestRunForTarget(nodeById, graph, symId);
675
+ if (!nativeTestRun)
676
+ continue;
677
+ goAssertionLine = goAssertionLineForTarget(graph, symId, nativeTestRun);
678
+ }
679
+ else if (isJava) {
680
+ nativeTestRun = javaTestForTarget(nodeById, graph, symId);
681
+ if (!nativeTestRun)
682
+ continue;
683
+ }
361
684
  attempted++;
362
685
  // R-2: inject NODE_OPTIONS=--experimental-sqlite via the existing test_env path when the
363
686
  // target references node:sqlite. Makes the baseline runnable only; never mints Proven.
364
- const testEnv = experimentalSqliteTestEnv(reader, targetFileRel);
687
+ const testEnv = isGo || isJava || isPython ? undefined : experimentalSqliteTestEnv(reader, targetFileRel);
688
+ const displayTest = nativeTestRun ?? proofTestRel;
365
689
  let result;
366
690
  try {
367
- result = proveLoop(root,
368
- // link_node_modules: the isolated proof copy excludes node_modules; without linking,
369
- // any target/test importing a repo dependency fails baseline → needs_setup. Linking only
370
- // makes real tests runnable Proven still requires the dynamic oracle's sentinel kill.
371
- { target_symbol: symId, source: sourceRoot, test_path: testRel, replacement: "return null;", link_node_modules: true, ...(testEnv ? { test_env: testEnv } : {}), run_id: `auto-prove-existing-${attempted}` }, proveDeps);
691
+ result = proveLoop(root, nativeTestRun
692
+ ? { target_symbol: symId, source: sourceRoot, test_run: nativeTestRun, ...(goAssertionLine !== undefined ? { go_assertion_line: goAssertionLine } : {}), run_id: `auto-prove-existing-${attempted}` }
693
+ // link_node_modules: the isolated proof copy excludes node_modules; without linking,
694
+ // any target/test importing a repo dependency fails baseline needs_setup. Linking only
695
+ // makes real tests runnable Proven still requires the dynamic oracle's sentinel kill.
696
+ : {
697
+ target_symbol: symId,
698
+ source: sourceRoot,
699
+ test_path: proofTestRel,
700
+ replacement: replacementForTarget(targetFileRel),
701
+ link_node_modules: true,
702
+ ...(testEnv ? { test_env: testEnv } : {}),
703
+ run_id: `auto-prove-existing-${attempted}`
704
+ }, proveDeps);
372
705
  }
373
706
  catch (e) {
374
707
  const attempt = {
375
708
  target_symbol: symId,
376
- test_path: testRel,
709
+ test_path: displayTest,
377
710
  classification: "needs_setup",
378
711
  reason: `Proof could not run: ${redactSecrets(errMsg(e))}`
379
712
  };
@@ -382,7 +715,14 @@ function proveExistingAssociatedTests(root, graph, sourceRoot, nodeById, opts, p
382
715
  continue;
383
716
  }
384
717
  const { classification, reason, category } = classifyProof(result, { sourceRoot, targetFileRel });
385
- const attempt = { target_symbol: symId, test_path: testRel, classification, reason, category };
718
+ const attempt = {
719
+ target_symbol: symId,
720
+ test_path: displayTest,
721
+ classification,
722
+ reason,
723
+ category,
724
+ mutant_status: mutantStatusOf(result)
725
+ };
386
726
  attempts.push(attempt);
387
727
  if (classification === "proven") {
388
728
  proven++;
@@ -442,6 +782,20 @@ export async function autoProve(root, opts, deps) {
442
782
  const autoLimit = Math.max(1, Math.min(MAX_AUTO_LIMIT, Math.floor(opts.autoLimit ?? DEFAULT_AUTO_LIMIT)));
443
783
  // ── Lane 1: existing associated tests — NO key required, runs FIRST (PR 1.5). ──
444
784
  const ex = proveExistingAssociatedTests(root, graph, sourceRoot, nodeById, opts, proveLoop, proveDeps, alreadyProven, importTimeBlocked, autoLimit);
785
+ if (opts.existingOnly) {
786
+ const status = ex.proven > 0 ? "proven-run" : ex.attempted > 0 ? "ran-no-proof" : "no-targets";
787
+ return {
788
+ ran: ex.attempted > 0,
789
+ status,
790
+ reason: "existing-tests-only: generation disabled.",
791
+ attempted: ex.attempted,
792
+ proven: ex.proven,
793
+ needs_setup: ex.needsSetup,
794
+ skipped: [],
795
+ generated_files: [],
796
+ attempts: ex.attempts
797
+ };
798
+ }
445
799
  // Key gate applies ONLY to the generation lane. No provider key ⇒ generation is skipped
446
800
  // (no files, no fake proof) with explicit guidance; the existing-tests lane still counts.
447
801
  const providerConfig = resolveProviderConfig(deps.env, { provider: opts.provider, model: opts.model });
@@ -489,7 +843,7 @@ export async function autoProve(root, opts, deps) {
489
843
  const window = candidates.slice(start, start + GEN_WINDOW);
490
844
  const need = genBudget - attempted;
491
845
  const windowIds = window.map((g) => g.id);
492
- const gen = await generate(graph, { target_ids: windowIds, limit: Math.min(windowIds.length, need) }, provider, reader, clock);
846
+ const gen = await generate(graph, { target_ids: windowIds, limit: Math.min(windowIds.length, need), ...(opts.prompt_version ? { prompt_version: opts.prompt_version } : {}) }, provider, reader, clock);
493
847
  const tests = gen.generated_tests;
494
848
  // Global start offset so filenames stay unique across windows — runHintsFor
495
849
  // otherwise resets its index to 0 per window and same-slug targets collide.
@@ -498,13 +852,26 @@ export async function autoProve(root, opts, deps) {
498
852
  const test = tests[i];
499
853
  const hint = hints[i];
500
854
  if (!hint.prove_run) {
501
- // Non-TS/JS target or no resolvable code symbol not attemptable.
855
+ // No JS prove_run. Go and Java are dynamically provable only through their OWN
856
+ // test in the target's package/module (the existing-tests lane): the Go oracle
857
+ // runs `go test -run ^TestX$ ./<pkgdir>` and the Java oracle runs
858
+ // `mvn test -Dtest=Class#method` in the target's Maven module, so a freshly
859
+ // generated test written to orangepro_generated/ is in the wrong package/module
860
+ // and can never be reached. Skip generated Go/Java here with an honest reason
861
+ // (the existing-tests lane covers both).
862
+ const genFile = hint.target_symbol_external_id ? symbolFile(hint.target_symbol_external_id) : "";
863
+ const goGen = genFile ? isGoFile(genFile) : false;
864
+ const javaGen = genFile ? isJavaFile(genFile) : false;
502
865
  skipped.push({
503
866
  target_symbol: hint.target_symbol_external_id,
504
867
  title: test.title,
505
- reason: hint.target_symbol_external_id
506
- ? "Target is not TS/JS; dynamic proof supports TS/JS CodeSymbol targets only."
507
- : "No resolvable TS/JS code-symbol target to prove."
868
+ reason: goGen
869
+ ? "Go dynamic proof runs the target package's own test; a generated Go test is out of that package — proven via the existing-tests lane instead."
870
+ : javaGen
871
+ ? "Java dynamic proof runs the target module's own test; a generated Java test is out of that module — proven via the existing-tests lane instead."
872
+ : hint.target_symbol_external_id
873
+ ? "Target is not TS/JS; dynamic proof supports TS/JS CodeSymbol targets only."
874
+ : "No resolvable TS/JS code-symbol target to prove."
508
875
  });
509
876
  continue;
510
877
  }
@@ -582,7 +949,14 @@ export async function autoProve(root, opts, deps) {
582
949
  continue;
583
950
  }
584
951
  const { classification, reason, category } = classifyProof(result, { sourceRoot, targetFileRel });
585
- const attempt = { target_symbol, test_path: writeRel, classification, reason, category };
952
+ const attempt = {
953
+ target_symbol,
954
+ test_path: writeRel,
955
+ classification,
956
+ reason,
957
+ category,
958
+ mutant_status: mutantStatusOf(result)
959
+ };
586
960
  attempts.push(attempt);
587
961
  if (classification === "proven")
588
962
  proven++;