@orangepro/orangepro-mcp 0.2.44 → 0.2.46

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.
@@ -152,6 +152,19 @@ function defaultDynamicProofRunner(args, opts = {}) {
152
152
  exitCode: child.status ?? 1
153
153
  };
154
154
  }
155
+ /** Metadata only. A runner process exiting zero is not a mutation proof. */
156
+ function runnerProcessDiagnostics(run, args, cwd, scriptPath, startedAt, oracle) {
157
+ const tail = (text) => redactSecrets(text).split(/\r?\n/).filter(Boolean).slice(-20).map((line) => line.slice(0, 500));
158
+ return {
159
+ command: redactSecrets([process.execPath, scriptPath, ...args].join(" ")).slice(0, 4096),
160
+ cwd: redactSecrets(cwd),
161
+ exit_code: Number.isInteger(run.exitCode) ? run.exitCode : null,
162
+ duration_ms: Math.max(0, Date.now() - startedAt),
163
+ ...((oracle.baseline?.timedOut || oracle.mutant?.timedOut) ? { failure_class: "timeout" } : {}),
164
+ stdout_tail: run.exitCode === 0 ? [] : tail(run.stdout),
165
+ stderr_tail: tail(run.stderr)
166
+ };
167
+ }
155
168
  function parseDynamicProofJson(stdout, stderr) {
156
169
  try {
157
170
  return JSON.parse(stdout);
@@ -1084,11 +1097,14 @@ export function opDynamicProof(root, opts, deps = defaultDeps()) {
1084
1097
  // match the exact receiver, never a same-named decl on another type.
1085
1098
  if (symbolTarget.memberQualifier)
1086
1099
  args.push("--recv", symbolTarget.memberQualifier);
1100
+ const scriptPath = dynamicProofSpikePathFor("go");
1101
+ const startedAt = Date.now();
1087
1102
  const run = (deps.dynamicProofRunner ?? defaultDynamicProofRunner)(args, {
1088
1103
  cwd: goRoot,
1089
- scriptPath: dynamicProofSpikePathFor("go")
1104
+ scriptPath
1090
1105
  });
1091
1106
  oracle = mapGoOracle(parseDynamicProofJson(run.stdout, run.stderr));
1107
+ oracle.diagnostics = runnerProcessDiagnostics(run, args, goRoot, scriptPath, startedAt, oracle);
1092
1108
  testRel = opts.test_run;
1093
1109
  replacementMode = GO_SENTINEL_LABEL;
1094
1110
  runner = "go";
@@ -1127,11 +1143,14 @@ export function opDynamicProof(root, opts, deps = defaultDeps()) {
1127
1143
  ];
1128
1144
  if (opts.timeout_ms !== undefined)
1129
1145
  args.push("--timeout-ms", String(opts.timeout_ms));
1146
+ const scriptPath = dynamicProofSpikePathFor("java");
1147
+ const startedAt = Date.now();
1130
1148
  const run = (deps.dynamicProofRunner ?? defaultDynamicProofRunner)(args, {
1131
1149
  cwd: javaRoot,
1132
- scriptPath: dynamicProofSpikePathFor("java")
1150
+ scriptPath
1133
1151
  });
1134
1152
  oracle = mapJavaOracle(parseDynamicProofJson(run.stdout, run.stderr));
1153
+ oracle.diagnostics = runnerProcessDiagnostics(run, args, javaRoot, scriptPath, startedAt, oracle);
1135
1154
  testRel = opts.test_run;
1136
1155
  replacementMode = JAVA_SENTINEL_LABEL;
1137
1156
  runner = "junit";
@@ -1144,6 +1163,10 @@ export function opDynamicProof(root, opts, deps = defaultDeps()) {
1144
1163
  testRel = toWorkspaceRel(sourceRoot, resolveContained(sourceRoot, opts.test_path));
1145
1164
  replacementMode = opts.replacement_mode ?? "return-json";
1146
1165
  runner = opts.runner ?? "auto";
1166
+ const configuredPythonRunner = loadRiskConfig(sourceRoot).config.proof.python_runner;
1167
+ const pythonRunner = opts.runner !== undefined
1168
+ ? (opts.runner === "pytest" ? "python -m pytest" : "auto")
1169
+ : (opts.python_runner?.trim() || configuredPythonRunner);
1147
1170
  if (replacementMode !== "return-json") {
1148
1171
  throw new Error("prove --replacement-mode promise-json is not supported for Python targets.");
1149
1172
  }
@@ -1153,26 +1176,28 @@ export function opDynamicProof(root, opts, deps = defaultDeps()) {
1153
1176
  if ((opts.test_env?.length ?? 0) > 0) {
1154
1177
  throw new Error("prove --test-env is not supported for Python targets yet.");
1155
1178
  }
1156
- // G2: narrow the copied sandbox to the owning Python project when both the
1157
- // target and the test live inside it (bounded copy — no full-repo OOM).
1158
- const pyRoot = pythonModuleRoot(sourceRoot, targetRel, testRel);
1159
- if (pyRoot !== resolve(sourceRoot)) {
1160
- proofModuleRoot = pyRoot;
1161
- reportProgress(`proof scope: Python project root ${pyRoot} (narrowed from the analyzed path)`);
1162
- }
1179
+ // R7.1: pass the bounded analyzed root to the Python spike. The spike derives
1180
+ // the nearest project from the TEST (not the target), then refuses a target
1181
+ // outside that explicit project boundary. Narrowing here from the target would
1182
+ // silently erase that boundary evidence in monorepos.
1183
+ const pyRoot = resolve(sourceRoot);
1163
1184
  const args = [
1164
1185
  "--root",
1165
1186
  pyRoot,
1166
1187
  "--test",
1167
- relative(pyRoot, resolve(sourceRoot, testRel)).split(sep).join("/"),
1188
+ testRel,
1168
1189
  "--target",
1169
- relative(pyRoot, resolve(sourceRoot, targetRel)).split(sep).join("/"),
1190
+ targetRel,
1170
1191
  "--func",
1171
1192
  method,
1172
1193
  "--mode",
1173
1194
  "sentinel",
1195
+ "--python-runner",
1196
+ pythonRunner,
1174
1197
  "--json"
1175
1198
  ];
1199
+ if (opts.skip_preflight)
1200
+ args.push("--skip-preflight");
1176
1201
  if (opts.timeout_ms !== undefined)
1177
1202
  args.push("--timeout-ms", String(opts.timeout_ms));
1178
1203
  const run = (deps.dynamicProofRunner ?? defaultDynamicProofRunner)(args, {
@@ -1180,6 +1205,9 @@ export function opDynamicProof(root, opts, deps = defaultDeps()) {
1180
1205
  scriptPath: dynamicProofSpikePathFor("python")
1181
1206
  });
1182
1207
  oracle = parseDynamicProofJson(run.stdout, run.stderr);
1208
+ if (run.exitCode !== 0 && (oracle.status === "proven" || oracle.proven === true)) {
1209
+ throw new Error(`Python proof runner exited ${run.exitCode}; no dynamic proof can be credited. ${redactSecrets(run.stderr).slice(0, 300)}`);
1210
+ }
1183
1211
  }
1184
1212
  else {
1185
1213
  if (!opts.test_path)
@@ -1225,13 +1253,17 @@ export function opDynamicProof(root, opts, deps = defaultDeps()) {
1225
1253
  args.push("--jest-config", toWorkspaceRel(sourceRoot, resolveContained(sourceRoot, opts.jest_config)));
1226
1254
  for (const entry of opts.test_env ?? [])
1227
1255
  args.push("--test-env", entry);
1256
+ const scriptPath = dynamicProofSpikePath();
1257
+ const startedAt = Date.now();
1228
1258
  const run = (deps.dynamicProofRunner ?? defaultDynamicProofRunner)(args, { cwd: sourceRoot });
1229
1259
  oracle = parseDynamicProofJson(run.stdout, run.stderr);
1260
+ oracle.diagnostics = runnerProcessDiagnostics(run, args, sourceRoot, scriptPath, startedAt, oracle);
1230
1261
  }
1231
1262
  const closed = dynamicProofSucceeded(oracle);
1232
1263
  const baselineGreen = oracle.baseline?.exitCode === 0 && oracle.baseline?.timedOut !== true;
1233
1264
  const mutantFailedAssertion = oracle.mutant?.assertionFailure === true && oracle.mutant?.timedOut !== true;
1234
1265
  const artifactIdentity = artifactIdentityFor(graph, sourceRoot);
1266
+ const proofDiagnostics = oracle.diagnostics;
1235
1267
  const dynamicProof = {
1236
1268
  proof_kind: "dynamic_targeted",
1237
1269
  baseline_green: baselineGreen,
@@ -1239,12 +1271,14 @@ export function opDynamicProof(root, opts, deps = defaultDeps()) {
1239
1271
  // Derived from the targeted mutation kill: if the credited subject is mocked/replaced,
1240
1272
  // mutating its real body cannot cause the same test to fail at an assertion.
1241
1273
  target_not_mocked: closed,
1242
- sentinel: oracle.replacementMode ?? replacementMode,
1274
+ sentinel: oracle.mutation?.sentinel ?? oracle.replacementMode ?? replacementMode,
1275
+ ...(oracle.mutation?.source ? { sentinel_source: oracle.mutation.source } : {}),
1243
1276
  runner: oracle.runner ?? runner,
1244
1277
  test_path: oracle.test ?? testRel,
1245
1278
  mutant_status: oracle.status,
1246
1279
  oracle_version: PROOF_ORACLE_VERSION,
1247
- run_fingerprint: artifactIdentity.run_fingerprint
1280
+ run_fingerprint: artifactIdentity.run_fingerprint,
1281
+ ...(proofDiagnostics ? proofDiagnostics : {})
1248
1282
  };
1249
1283
  const result = appendLedgerRecord(root, {
1250
1284
  run_id: opts.run_id,
@@ -1274,6 +1308,9 @@ export function opDynamicProof(root, opts, deps = defaultDeps()) {
1274
1308
  test: oracle.test,
1275
1309
  target: oracle.target,
1276
1310
  method: oracle.method,
1311
+ category: oracle.category,
1312
+ diagnostics: oracle.diagnostics,
1313
+ mutation: oracle.mutation,
1277
1314
  baseline: oracle.baseline
1278
1315
  ? {
1279
1316
  exitCode: oracle.baseline.exitCode,
@@ -1389,6 +1426,8 @@ export function opProveLoop(root, opts, deps = defaultDeps()) {
1389
1426
  replacement: opts.replacement,
1390
1427
  replacement_mode: opts.replacement_mode,
1391
1428
  runner: opts.runner,
1429
+ python_runner: opts.python_runner,
1430
+ skip_preflight: opts.skip_preflight,
1392
1431
  timeout_ms: opts.timeout_ms,
1393
1432
  link_node_modules: opts.link_node_modules,
1394
1433
  vitest_config: opts.vitest_config,
@@ -1583,7 +1622,8 @@ export async function opStart(root, opts = {}, deps = defaultDeps()) {
1583
1622
  reportProgress("start: deterministic graph is ready", { current: 4, total: 8 });
1584
1623
  // A per-repo/user risk config that fails to parse must announce itself here, not
1585
1624
  // silently revert to defaults (round-three review finding #1).
1586
- for (const w of loadRiskConfig(scanRoot).warnings)
1625
+ const loadedRiskConfig = loadRiskConfig(scanRoot);
1626
+ for (const w of loadedRiskConfig.warnings)
1587
1627
  warnings.push(w);
1588
1628
  const staticSnapshot = writeStartStaticSnapshot(root, opts.baseRef, warnings);
1589
1629
  const aiProviderConfigured = deps.aiProvider !== undefined || resolveProviderConfig(providerEnv, providerOpts) !== null;
@@ -1650,14 +1690,28 @@ export async function opStart(root, opts = {}, deps = defaultDeps()) {
1650
1690
  : "auto-prove: driving generate → prove on the top provable targets", { current: 6, total: 8 });
1651
1691
  let autoProveResult;
1652
1692
  try {
1653
- autoProveResult = await autoProve(root, {
1693
+ const scopedFiles = autoProveChangedScope(loadGraph(workspacePaths(root).graphPath), changed, opts.baseRef);
1694
+ const runAutoProve = (changedFiles) => autoProve(root, {
1654
1695
  autoLimit: opts.proofLimit ?? opts.autoLimit,
1696
+ proof_existing_limit: opts.proofLimit ?? opts.autoLimit ?? loadedRiskConfig.config.proof.attempt_limit,
1697
+ proof_gen_limit: loadedRiskConfig.config.proof.baseline_green_target,
1655
1698
  noAuto: opts.noAuto,
1656
1699
  provider: providerOpts.provider,
1657
1700
  model: providerOpts.model,
1658
1701
  prompt_version: opts.promptVersion,
1659
- changedFiles: autoProveChangedScope(loadGraph(workspacePaths(root).graphPath), changed, opts.baseRef)
1702
+ changedFiles
1660
1703
  }, { ...providerDeps, proveLoop: opProveLoop });
1704
+ autoProveResult = await runAutoProve(scopedFiles);
1705
+ if (scopedFiles && autoProveResult.attempted === 0 && !opts.baseRef && !opts.noAuto) {
1706
+ // An uncommitted working tree implicitly scoped the pass, but nothing in that
1707
+ // scope was provable. Fall back to the global top targets instead of reporting
1708
+ // "not attempted" for a reason the user cannot see. --base keeps strict scoping.
1709
+ warnings.push(`auto-prove: none of the ${scopedFiles.length} uncommitted changed file(s) had a provable target with a linked test; ran the global top targets instead.`);
1710
+ autoProveResult = await runAutoProve(undefined);
1711
+ }
1712
+ else if (scopedFiles) {
1713
+ autoProveResult = { ...autoProveResult, scoped_changed_files: scopedFiles.length };
1714
+ }
1661
1715
  for (const skip of autoProveResult.skipped)
1662
1716
  warnings.push(`auto-prove skipped ${skip.target_symbol ?? skip.title}: ${skip.reason}`);
1663
1717
  }
@@ -1902,7 +1956,12 @@ export async function opStart(root, opts = {}, deps = defaultDeps()) {
1902
1956
  const dynForReport = {
1903
1957
  attempted: autoProveResult.attempted,
1904
1958
  proven: autoProveResult.proven,
1905
- needsSetup: autoProveResult.needs_setup.map((a) => ({ category: a.category, reason: a.reason }))
1959
+ status: autoProveResult.status,
1960
+ reason: autoProveResult.reason,
1961
+ ...(autoProveResult.scoped_changed_files !== undefined ? { scopedChangedFiles: autoProveResult.scoped_changed_files } : {}),
1962
+ needsSetup: autoProveResult.needs_setup
1963
+ .filter((a) => !a.deduped)
1964
+ .map((a) => ({ category: a.category, reason: a.reason }))
1906
1965
  };
1907
1966
  coverageHtml = opBehaviorCoverageHtml(root, `${WORKSPACE_DIR}/behavior-coverage.html`, dynForReport).behavior_coverage_path;
1908
1967
  }
@@ -1956,6 +2015,11 @@ export async function opStart(root, opts = {}, deps = defaultDeps()) {
1956
2015
  else if (autoProveResult.ran) {
1957
2016
  nextActions.push(`Dynamic proof: attempted the top ${autoProveResult.attempted} target(s); ${autoProveResult.proven} dynamically proven. Static breadth (behaviors, flows, Associated signals) is mapped regardless.`);
1958
2017
  if (autoProveResult.proven === 0) {
2018
+ const allEnvironmentUnavailable = autoProveResult.attempts.length > 0
2019
+ && autoProveResult.attempts.every((attempt) => attempt.category === "environment_unavailable" || attempt.category === "collection_error");
2020
+ if (allEnvironmentUnavailable) {
2021
+ nextActions.push("Proof not run: test environment could not be started (see ledger)");
2022
+ }
1959
2023
  const dom = dominantBlockReason(autoProveResult.needs_setup);
1960
2024
  if (dom)
1961
2025
  nextActions.push(`Dynamic proof closed 0: blocked because ${dom.label} (${dom.count}/${dom.total}). This is a sandbox setup gap, not a static-test failure; Static Associated signals are still shown.`);
@@ -2328,7 +2392,7 @@ function sidecarDynamicProof(root, graph) {
2328
2392
  attempted: attempts.attempted,
2329
2393
  proven: attempts.proven,
2330
2394
  needsSetup: attempts.attempts
2331
- .filter((a) => a.classification === "needs_setup")
2395
+ .filter((a) => a.classification === "needs_setup" && !a.deduped)
2332
2396
  .map((a) => ({ category: a.category, reason: a.reason }))
2333
2397
  };
2334
2398
  }
@@ -107,7 +107,10 @@ export function distillProofAttempts(auto, meta) {
107
107
  artifact_identity: meta.graph.artifact_identity,
108
108
  attempted: auto.attempted,
109
109
  proven: auto.proven,
110
- attempts: auto.attempts.map((a) => ({
110
+ ...(auto.status ? { status: auto.status } : {}),
111
+ ...(auto.reason ? { reason: redactSecrets(auto.reason) } : {}),
112
+ ...(auto.scoped_changed_files !== undefined ? { scoped_changed_files: auto.scoped_changed_files } : {}),
113
+ attempts: auto.attempts.filter((a) => !a.deduped).map((a) => ({
111
114
  target_symbol: a.target_symbol,
112
115
  test_path: a.test_path || undefined,
113
116
  classification: a.classification,
@@ -117,12 +120,29 @@ export function distillProofAttempts(auto, meta) {
117
120
  deduped: a.deduped,
118
121
  language: targetLanguage(a.target_symbol),
119
122
  project_root: a.project_root,
120
- blocked_by: a.blocked_by
123
+ blocked_by: a.blocked_by,
124
+ command: a.command,
125
+ cwd: a.cwd,
126
+ exit_code: a.exit_code,
127
+ duration_ms: a.duration_ms,
128
+ failure_class: a.failure_class,
129
+ stdout_tail: a.stdout_tail?.slice(-40).map((line) => redactSecrets(line)),
130
+ stderr_tail: a.stderr_tail?.slice(-40).map((line) => redactSecrets(line)),
131
+ runner_fallback: a.runner_fallback?.map((line) => redactSecrets(line))
121
132
  })),
122
- skipped: auto.skipped.map((s) => ({
133
+ skipped: [...auto.skipped, ...auto.attempts.filter((a) => a.deduped).map((a) => ({
134
+ target_symbol: a.target_symbol,
135
+ title: a.target_symbol,
136
+ reason: a.reason ?? "Skipped after a classified project-wide proof failure.",
137
+ language: targetLanguage(a.target_symbol),
138
+ project_root: a.project_root,
139
+ blocked_by: a.blocked_by,
140
+ category: a.category
141
+ }))].map((s) => ({
123
142
  target_symbol: s.target_symbol,
124
143
  title: s.title,
125
144
  reason: redactSecrets(s.reason),
145
+ category: s.category,
126
146
  language: s.language,
127
147
  project_root: s.project_root,
128
148
  blocked_by: s.blocked_by
@@ -3,8 +3,8 @@ import { ORS_VERSION } from "./score/risk.js";
3
3
  import { ORANGEPRO_VERSION } from "./version.js";
4
4
  import { hashString } from "./util/hash.js";
5
5
  export const ARTIFACT_IDENTITY_VERSION = "orangepro.artifact_identity.v1";
6
- export const ANALYZER_VERSION = "orangepro.analyzer.v3";
7
- export const PROOF_ORACLE_VERSION = "orangepro.targeted_mutation_oracle.v1";
6
+ export const ANALYZER_VERSION = "orangepro.analyzer.v6";
7
+ export const PROOF_ORACLE_VERSION = "orangepro.targeted_mutation_oracle.v3";
8
8
  function stable(value) {
9
9
  if (value === null || typeof value !== "object")
10
10
  return JSON.stringify(value);