@orangepro/orangepro-mcp 0.2.43 → 0.2.45

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;
@@ -1652,6 +1692,8 @@ export async function opStart(root, opts = {}, deps = defaultDeps()) {
1652
1692
  try {
1653
1693
  autoProveResult = await autoProve(root, {
1654
1694
  autoLimit: opts.proofLimit ?? opts.autoLimit,
1695
+ proof_existing_limit: opts.proofLimit ?? opts.autoLimit ?? loadedRiskConfig.config.proof.attempt_limit,
1696
+ proof_gen_limit: loadedRiskConfig.config.proof.baseline_green_target,
1655
1697
  noAuto: opts.noAuto,
1656
1698
  provider: providerOpts.provider,
1657
1699
  model: providerOpts.model,
@@ -1902,7 +1944,9 @@ export async function opStart(root, opts = {}, deps = defaultDeps()) {
1902
1944
  const dynForReport = {
1903
1945
  attempted: autoProveResult.attempted,
1904
1946
  proven: autoProveResult.proven,
1905
- needsSetup: autoProveResult.needs_setup.map((a) => ({ category: a.category, reason: a.reason }))
1947
+ needsSetup: autoProveResult.needs_setup
1948
+ .filter((a) => !a.deduped)
1949
+ .map((a) => ({ category: a.category, reason: a.reason }))
1906
1950
  };
1907
1951
  coverageHtml = opBehaviorCoverageHtml(root, `${WORKSPACE_DIR}/behavior-coverage.html`, dynForReport).behavior_coverage_path;
1908
1952
  }
@@ -1956,6 +2000,11 @@ export async function opStart(root, opts = {}, deps = defaultDeps()) {
1956
2000
  else if (autoProveResult.ran) {
1957
2001
  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
2002
  if (autoProveResult.proven === 0) {
2003
+ const allEnvironmentUnavailable = autoProveResult.attempts.length > 0
2004
+ && autoProveResult.attempts.every((attempt) => attempt.category === "environment_unavailable" || attempt.category === "collection_error");
2005
+ if (allEnvironmentUnavailable) {
2006
+ nextActions.push("Proof not run: test environment could not be started (see ledger)");
2007
+ }
1959
2008
  const dom = dominantBlockReason(autoProveResult.needs_setup);
1960
2009
  if (dom)
1961
2010
  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 +2377,7 @@ function sidecarDynamicProof(root, graph) {
2328
2377
  attempted: attempts.attempted,
2329
2378
  proven: attempts.proven,
2330
2379
  needsSetup: attempts.attempts
2331
- .filter((a) => a.classification === "needs_setup")
2380
+ .filter((a) => a.classification === "needs_setup" && !a.deduped)
2332
2381
  .map((a) => ({ category: a.category, reason: a.reason }))
2333
2382
  };
2334
2383
  }
@@ -23,7 +23,7 @@ import { workspacePaths } from "./workspace.js";
23
23
  import { redactSecrets } from "./util/redact.js";
24
24
  import { targetLanguage } from "./ledger.js";
25
25
  import { readEnginesNode, satisfiesNodeRange } from "./proofRunnability.js";
26
- export const PROOF_ATTEMPTS_SCHEMA_VERSION = "orangepro.proof_attempts.v1";
26
+ export const PROOF_ATTEMPTS_SCHEMA_VERSION = "orangepro.proof_attempts.v2";
27
27
  export const PROOF_ATTEMPTS_FILE = "proof-attempts.json";
28
28
  export const PROOF_DOCTOR_SCHEMA_VERSION = "orangepro.proof_doctor.v1";
29
29
  /** Languages with a shipped dynamic-proof profile. Everything else is honest "not yet". */
@@ -107,7 +107,7 @@ 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
+ attempts: auto.attempts.filter((a) => !a.deduped).map((a) => ({
111
111
  target_symbol: a.target_symbol,
112
112
  test_path: a.test_path || undefined,
113
113
  classification: a.classification,
@@ -115,12 +115,34 @@ export function distillProofAttempts(auto, meta) {
115
115
  category: a.category,
116
116
  reason: a.reason ? redactSecrets(a.reason) : undefined,
117
117
  deduped: a.deduped,
118
- language: targetLanguage(a.target_symbol)
118
+ language: targetLanguage(a.target_symbol),
119
+ project_root: a.project_root,
120
+ blocked_by: a.blocked_by,
121
+ command: a.command,
122
+ cwd: a.cwd,
123
+ exit_code: a.exit_code,
124
+ duration_ms: a.duration_ms,
125
+ failure_class: a.failure_class,
126
+ stdout_tail: a.stdout_tail?.slice(-40).map((line) => redactSecrets(line)),
127
+ stderr_tail: a.stderr_tail?.slice(-40).map((line) => redactSecrets(line)),
128
+ runner_fallback: a.runner_fallback?.map((line) => redactSecrets(line))
119
129
  })),
120
- skipped: auto.skipped.map((s) => ({
130
+ skipped: [...auto.skipped, ...auto.attempts.filter((a) => a.deduped).map((a) => ({
131
+ target_symbol: a.target_symbol,
132
+ title: a.target_symbol,
133
+ reason: a.reason ?? "Skipped after a classified project-wide proof failure.",
134
+ language: targetLanguage(a.target_symbol),
135
+ project_root: a.project_root,
136
+ blocked_by: a.blocked_by,
137
+ category: a.category
138
+ }))].map((s) => ({
121
139
  target_symbol: s.target_symbol,
122
140
  title: s.title,
123
- reason: redactSecrets(s.reason)
141
+ reason: redactSecrets(s.reason),
142
+ category: s.category,
143
+ language: s.language,
144
+ project_root: s.project_root,
145
+ blocked_by: s.blocked_by
124
146
  }))
125
147
  };
126
148
  }
@@ -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.v2";
7
- export const PROOF_ORACLE_VERSION = "orangepro.targeted_mutation_oracle.v1";
6
+ export const ANALYZER_VERSION = "orangepro.analyzer.v5";
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);