@orangepro/orangepro-mcp 0.2.42 → 0.2.44
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.
- package/dist/local/analyze/analyzer.js +283 -18
- package/dist/local/analyze/treeSitter/engine.js +604 -8
- package/dist/local/autoProve.js +222 -48
- package/dist/local/graph/ontology.js +3 -3
- package/dist/local/proofDoctor.js +8 -3
- package/dist/local/provenance.js +1 -1
- package/dist/local/score/risk.js +3 -2
- package/package.json +2 -2
package/dist/local/autoProve.js
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* No key ⇒ writes NO files, mints NO proof, returns explicit guidance.
|
|
14
14
|
*/
|
|
15
15
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
16
|
-
import { basename, dirname, posix, resolve, sep } from "node:path";
|
|
16
|
+
import { basename, dirname, join, posix, relative, resolve, sep } from "node:path";
|
|
17
17
|
import { generateTests, readDeclaredDeps, unresolvedLocalImports } from "./generate/generator.js";
|
|
18
18
|
import { GENERATED_DIR, runHintsFor } from "./generate/runHints.js";
|
|
19
19
|
import { rankRiskGaps } from "./score/risk.js";
|
|
@@ -21,12 +21,12 @@ import { resolveProviderConfig } from "./localConfig.js";
|
|
|
21
21
|
import { buildProvider, DeterministicProvider } from "./generate/providers.js";
|
|
22
22
|
import { resolveContained } from "./reprove/paths.js";
|
|
23
23
|
import { buildRtm } from "./rtm.js";
|
|
24
|
-
import { loadLedger } from "./ledger.js";
|
|
24
|
+
import { loadLedger, targetLanguage } from "./ledger.js";
|
|
25
25
|
import { reportProgress } from "./util/progress.js";
|
|
26
26
|
import { loadGraph, workspacePaths } from "./workspace.js";
|
|
27
27
|
import { systemClock } from "./util/time.js";
|
|
28
28
|
import { redactSecrets } from "./util/redact.js";
|
|
29
|
-
import { classifyBaselineFailure, EXPERIMENTAL_SQLITE_TEST_ENV,
|
|
29
|
+
import { classifyBaselineFailure, EXPERIMENTAL_SQLITE_TEST_ENV, isNeedsSetupCategory, readEnginesNode, targetNeedsExperimentalSqlite } from "./proofRunnability.js";
|
|
30
30
|
/** Real dynamic proof is profile-gated; only wired runner targets are attemptable. */
|
|
31
31
|
function isTsJsFile(file) {
|
|
32
32
|
return /\.[cm]?[jt]sx?$/i.test(file);
|
|
@@ -372,7 +372,74 @@ const GEN_WINDOW = 5;
|
|
|
372
372
|
// file (every eligible symbol × every importing test) cannot starve the shared budget before
|
|
373
373
|
// a provable hard-edge symbol is tried. Small K; promote to a flag if a repo needs a wider sweep.
|
|
374
374
|
const EXISTING_LANE_MAX_WEAK_PER_SYMBOL = 3;
|
|
375
|
+
const RUNNER_ROOT_MARKERS = {
|
|
376
|
+
typescript: ["package.json"],
|
|
377
|
+
javascript: ["package.json"],
|
|
378
|
+
python: ["pyproject.toml", "setup.cfg", "setup.py", "pytest.ini", ".pytest.ini", "tox.ini"],
|
|
379
|
+
go: ["go.mod"],
|
|
380
|
+
java: ["pom.xml", "build.gradle", "build.gradle.kts"],
|
|
381
|
+
rs: ["Cargo.toml"]
|
|
382
|
+
};
|
|
383
|
+
/**
|
|
384
|
+
* The nearest language runner root for a target, bounded by the analyzed source root.
|
|
385
|
+
* A nested root is accepted only when the selected existing test is inside it too;
|
|
386
|
+
* otherwise proof execution falls back to the analyzed root, matching the oracle's
|
|
387
|
+
* containment rule rather than inventing a cross-project plan.
|
|
388
|
+
*/
|
|
389
|
+
export function proofRunnerRoot(sourceRoot, targetRel, testRel) {
|
|
390
|
+
const stop = resolve(sourceRoot);
|
|
391
|
+
const targetAbs = resolve(stop, targetRel);
|
|
392
|
+
if (targetAbs !== stop && !targetAbs.startsWith(stop + sep))
|
|
393
|
+
return ".";
|
|
394
|
+
const language = targetLanguage(`sym:${targetRel}#target`);
|
|
395
|
+
const markers = RUNNER_ROOT_MARKERS[language] ?? [];
|
|
396
|
+
let dir = dirname(targetAbs);
|
|
397
|
+
let found = stop;
|
|
398
|
+
for (;;) {
|
|
399
|
+
if (markers.some((marker) => existsSync(join(dir, marker)))) {
|
|
400
|
+
found = dir;
|
|
401
|
+
break;
|
|
402
|
+
}
|
|
403
|
+
if (dir === stop)
|
|
404
|
+
break;
|
|
405
|
+
const parent = dirname(dir);
|
|
406
|
+
if (parent === dir || (parent !== stop && !parent.startsWith(stop + sep)))
|
|
407
|
+
break;
|
|
408
|
+
dir = parent;
|
|
409
|
+
}
|
|
410
|
+
if (testRel && found !== stop) {
|
|
411
|
+
const testFile = testRel.split("::", 1)[0] ?? testRel;
|
|
412
|
+
const testAbs = resolve(stop, testFile);
|
|
413
|
+
if ((testAbs !== stop && !testAbs.startsWith(stop + sep))
|
|
414
|
+
|| (testAbs !== found && !testAbs.startsWith(found + sep)))
|
|
415
|
+
found = stop;
|
|
416
|
+
}
|
|
417
|
+
const rel = relative(stop, found).split(sep).join("/");
|
|
418
|
+
return rel || ".";
|
|
419
|
+
}
|
|
420
|
+
/** Dominant-language order from all denominator-eligible code behaviors. */
|
|
421
|
+
export function proofLanguageOrder(graph) {
|
|
422
|
+
const counts = new Map();
|
|
423
|
+
const first = new Map();
|
|
424
|
+
for (const node of graph.nodes) {
|
|
425
|
+
if (node.kind !== "CodeSymbol" || node.denominator_eligible !== true)
|
|
426
|
+
continue;
|
|
427
|
+
const language = targetLanguage(node.external_id);
|
|
428
|
+
if (!first.has(language))
|
|
429
|
+
first.set(language, first.size);
|
|
430
|
+
counts.set(language, (counts.get(language) ?? 0) + 1);
|
|
431
|
+
}
|
|
432
|
+
return [...counts.keys()].sort((a, b) => (counts.get(b) - counts.get(a)) || (first.get(a) - first.get(b)));
|
|
433
|
+
}
|
|
375
434
|
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.";
|
|
435
|
+
/** Only failures proven to apply across a runner project may quarantine its siblings. */
|
|
436
|
+
const PROJECT_WIDE_BLOCKERS = new Set([
|
|
437
|
+
"engine_mismatch",
|
|
438
|
+
"tsconfig_missing",
|
|
439
|
+
"runner_missing",
|
|
440
|
+
"module_root_missing",
|
|
441
|
+
"go_package_build_failure"
|
|
442
|
+
]);
|
|
376
443
|
export function isRoastSurvivor(attempt) {
|
|
377
444
|
return attempt.classification === "non_killing" && attempt.mutant_status === "associated_survived";
|
|
378
445
|
}
|
|
@@ -434,7 +501,13 @@ function fileReaderFor(root) {
|
|
|
434
501
|
function classifyProof(result, ctx) {
|
|
435
502
|
if ("status" in result && result.status === "unrunnable") {
|
|
436
503
|
// Setup did not run (env non-event) — nothing was minted, target needs setup.
|
|
437
|
-
|
|
504
|
+
const reason = result.reason;
|
|
505
|
+
const category = /runner binary not found|unsupported or unknown test runner/i.test(reason)
|
|
506
|
+
? "runner_missing"
|
|
507
|
+
: /no go\.mod found|no pom\.xml|no maven or gradle|build\.gradle|no python project root/i.test(reason)
|
|
508
|
+
? "module_root_missing"
|
|
509
|
+
: undefined;
|
|
510
|
+
return { classification: "needs_setup", reason, category };
|
|
438
511
|
}
|
|
439
512
|
const dyn = result;
|
|
440
513
|
const record = dyn.record;
|
|
@@ -442,8 +515,16 @@ function classifyProof(result, ctx) {
|
|
|
442
515
|
return { classification: "proven" };
|
|
443
516
|
const cert = record.dynamic_proof;
|
|
444
517
|
if (cert && cert.baseline_green === false) {
|
|
518
|
+
const failureSummary = dyn.oracle.baseline?.failureSummary;
|
|
519
|
+
if (ctx.targetFileRel.endsWith(".go") && /^#\s+\S+/m.test(failureSummary ?? "")) {
|
|
520
|
+
return {
|
|
521
|
+
classification: "needs_setup",
|
|
522
|
+
reason: "The Go package did not compile before the selected test could run.",
|
|
523
|
+
category: "go_package_build_failure"
|
|
524
|
+
};
|
|
525
|
+
}
|
|
445
526
|
const { category, reason } = classifyBaselineFailure({
|
|
446
|
-
failureSummary
|
|
527
|
+
failureSummary,
|
|
447
528
|
enginesNode: readEnginesNode(ctx.sourceRoot, ctx.targetFileRel),
|
|
448
529
|
runnerNode: ctx.runnerNode ?? process.version
|
|
449
530
|
});
|
|
@@ -457,27 +538,30 @@ function mutantStatusOf(result) {
|
|
|
457
538
|
return "unrunnable";
|
|
458
539
|
return result.record.dynamic_proof?.mutant_status;
|
|
459
540
|
}
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
* loading the TARGET FILE with a given runner, independent of which test runs it — so
|
|
463
|
-
* same-file siblings share it. Keyed on the TARGET FILE only (every caller pins runner to
|
|
464
|
-
* undefined): the sole deduped cause is engine_mismatch, a package-level fact both lanes
|
|
465
|
-
* classify against the SAME process.version, so the runner must not be in the key. The old
|
|
466
|
-
* {runner, target file} key split the cache across lanes (lane 1 wrote "auto <file>", the
|
|
467
|
-
* generation lane read "<runner> <file>" -> never matched), silently disabling cross-lane
|
|
468
|
-
* dedup. NEVER merges across different files or a different failure class.
|
|
469
|
-
*/
|
|
470
|
-
function dedupKey(runner, targetFileRel) {
|
|
471
|
-
return `${runner ?? "auto"}\u0000${targetFileRel}`;
|
|
541
|
+
function projectBlockKey(language, projectRoot) {
|
|
542
|
+
return `${language}\u0000${projectRoot}`;
|
|
472
543
|
}
|
|
473
|
-
|
|
474
|
-
|
|
544
|
+
function projectBlockKeys(language, projectRoot, targetFileRel) {
|
|
545
|
+
return [
|
|
546
|
+
projectBlockKey(language, projectRoot),
|
|
547
|
+
`${projectBlockKey(language, projectRoot)}\u0000package:${dirname(targetFileRel).split(sep).join("/")}`
|
|
548
|
+
];
|
|
549
|
+
}
|
|
550
|
+
function classifiedProjectBlockKey(category, language, projectRoot, targetFileRel) {
|
|
551
|
+
return category === "go_package_build_failure"
|
|
552
|
+
? projectBlockKeys(language, projectRoot, targetFileRel)[1]
|
|
553
|
+
: projectBlockKey(language, projectRoot);
|
|
554
|
+
}
|
|
555
|
+
/** A same-project candidate skipped WITHOUT re-running after a classified project-wide failure. */
|
|
556
|
+
function dedupedAttempt(targetSymbol, testPath, projectRoot, blocked) {
|
|
475
557
|
return {
|
|
476
558
|
target_symbol: targetSymbol,
|
|
477
559
|
test_path: testPath,
|
|
478
560
|
classification: "needs_setup",
|
|
479
|
-
reason: `${blocked.reason} (shared
|
|
561
|
+
reason: `${blocked.reason} (shared project/toolchain cause in ${blocked.scopeLabel}; not re-run).`,
|
|
480
562
|
category: blocked.category,
|
|
563
|
+
project_root: projectRoot,
|
|
564
|
+
blocked_by: blocked.blockedBy,
|
|
481
565
|
deduped: true
|
|
482
566
|
};
|
|
483
567
|
}
|
|
@@ -616,7 +700,7 @@ export function existingAssociatedTests(graph, nodeById) {
|
|
|
616
700
|
* the budget. Order within each tier follows the Map's insertion order (graph node/edge order),
|
|
617
701
|
* so the result is deterministic.
|
|
618
702
|
*/
|
|
619
|
-
export function orderExistingAttempts(testsBySymbol, maxWeakPerSymbol = EXISTING_LANE_MAX_WEAK_PER_SYMBOL) {
|
|
703
|
+
export function orderExistingAttempts(testsBySymbol, maxWeakPerSymbol = EXISTING_LANE_MAX_WEAK_PER_SYMBOL, schedule) {
|
|
620
704
|
const hard = [];
|
|
621
705
|
const weak = [];
|
|
622
706
|
for (const [symId, tests] of testsBySymbol) {
|
|
@@ -628,7 +712,60 @@ export function orderExistingAttempts(testsBySymbol, maxWeakPerSymbol = EXISTING
|
|
|
628
712
|
weak.push({ symId, testRel: t.test, hard: false });
|
|
629
713
|
}
|
|
630
714
|
}
|
|
631
|
-
|
|
715
|
+
if (!schedule)
|
|
716
|
+
return [...hard, ...weak];
|
|
717
|
+
const languageOrder = proofLanguageOrder(schedule.graph);
|
|
718
|
+
const languageRank = new Map(languageOrder.map((language, index) => [language, index]));
|
|
719
|
+
const decorate = (attempt, index) => {
|
|
720
|
+
const node = schedule.nodeById.get(attempt.symId);
|
|
721
|
+
const targetRel = node ? symbolFileOf(node) : attempt.symId.replace(/^sym:/, "").split("#")[0];
|
|
722
|
+
return {
|
|
723
|
+
...attempt,
|
|
724
|
+
language: targetLanguage(attempt.symId),
|
|
725
|
+
projectRoot: proofRunnerRoot(schedule.sourceRoot, targetRel, attempt.testRel),
|
|
726
|
+
index
|
|
727
|
+
};
|
|
728
|
+
};
|
|
729
|
+
const stableProjectOrder = (attempts) => {
|
|
730
|
+
const decorated = attempts.map(decorate);
|
|
731
|
+
const firstProject = new Map();
|
|
732
|
+
for (const item of decorated) {
|
|
733
|
+
const key = `${item.language}\u0000${item.projectRoot}`;
|
|
734
|
+
if (!firstProject.has(key))
|
|
735
|
+
firstProject.set(key, firstProject.size);
|
|
736
|
+
}
|
|
737
|
+
return decorated
|
|
738
|
+
.sort((a, b) => ((languageRank.get(a.language) ?? Number.MAX_SAFE_INTEGER) - (languageRank.get(b.language) ?? Number.MAX_SAFE_INTEGER))
|
|
739
|
+
|| ((firstProject.get(`${a.language}\u0000${a.projectRoot}`) ?? 0) - (firstProject.get(`${b.language}\u0000${b.projectRoot}`) ?? 0))
|
|
740
|
+
|| (a.index - b.index))
|
|
741
|
+
.map(({ index: _index, ...attempt }) => attempt);
|
|
742
|
+
};
|
|
743
|
+
return [...stableProjectOrder(hard), ...stableProjectOrder(weak)];
|
|
744
|
+
}
|
|
745
|
+
/**
|
|
746
|
+
* Stable scheduling view over the existing ORS-ranked candidate list. This changes
|
|
747
|
+
* only which runner receives a scarce proof attempt first; it never changes scores,
|
|
748
|
+
* evidence tiers, or the persisted priority-gap order.
|
|
749
|
+
*/
|
|
750
|
+
export function orderRankedProofCandidates(candidates, graph, sourceRoot) {
|
|
751
|
+
const languageOrder = proofLanguageOrder(graph);
|
|
752
|
+
if (languageOrder.length <= 1)
|
|
753
|
+
return candidates;
|
|
754
|
+
const languageRank = new Map(languageOrder.map((language, index) => [language, index]));
|
|
755
|
+
const firstProject = new Map();
|
|
756
|
+
const decorated = candidates.map((candidate, index) => {
|
|
757
|
+
const language = targetLanguage(candidate.id);
|
|
758
|
+
const projectRoot = proofRunnerRoot(sourceRoot, candidate.file);
|
|
759
|
+
const projectKey = `${language}\u0000${projectRoot}`;
|
|
760
|
+
if (!firstProject.has(projectKey))
|
|
761
|
+
firstProject.set(projectKey, firstProject.size);
|
|
762
|
+
return { candidate, index, language, projectKey };
|
|
763
|
+
});
|
|
764
|
+
return decorated
|
|
765
|
+
.sort((a, b) => ((languageRank.get(a.language) ?? Number.MAX_SAFE_INTEGER) - (languageRank.get(b.language) ?? Number.MAX_SAFE_INTEGER))
|
|
766
|
+
|| ((firstProject.get(a.projectKey) ?? 0) - (firstProject.get(b.projectKey) ?? 0))
|
|
767
|
+
|| (a.index - b.index))
|
|
768
|
+
.map(({ candidate }) => candidate);
|
|
632
769
|
}
|
|
633
770
|
/**
|
|
634
771
|
* PR 1.5 lane — prove the repo's OWN existing tests, NO provider key. For each eligible
|
|
@@ -642,7 +779,7 @@ export function orderExistingAttempts(testsBySymbol, maxWeakPerSymbol = EXISTING
|
|
|
642
779
|
* — the generation lane gets whatever this lane leaves unspent, so TOTAL attempts
|
|
643
780
|
* (existing + generation) never exceed the budget.
|
|
644
781
|
*/
|
|
645
|
-
function proveExistingAssociatedTests(root, graph, sourceRoot, nodeById, opts, proveLoop, proveDeps, alreadyProven,
|
|
782
|
+
function proveExistingAssociatedTests(root, graph, sourceRoot, nodeById, opts, proveLoop, proveDeps, alreadyProven, projectBlocked, budget) {
|
|
646
783
|
const attempts = [];
|
|
647
784
|
const needsSetup = [];
|
|
648
785
|
const provenSymbols = new Set();
|
|
@@ -651,8 +788,12 @@ function proveExistingAssociatedTests(root, graph, sourceRoot, nodeById, opts, p
|
|
|
651
788
|
const changed = opts.changedFiles && opts.changedFiles.length > 0 ? new Set(opts.changedFiles) : null;
|
|
652
789
|
const reader = fileReaderFor(sourceRoot); // R-2: source scan for the node:sqlite env profile
|
|
653
790
|
// Fix 3: hard TESTED_BY/COVERS pairs first, weak MAY_* pairs after and capped per symbol.
|
|
654
|
-
const queue = orderExistingAttempts(existingAssociatedTests(graph, nodeById)
|
|
655
|
-
|
|
791
|
+
const queue = orderExistingAttempts(existingAssociatedTests(graph, nodeById), EXISTING_LANE_MAX_WEAK_PER_SYMBOL, {
|
|
792
|
+
graph,
|
|
793
|
+
nodeById,
|
|
794
|
+
sourceRoot
|
|
795
|
+
});
|
|
796
|
+
for (const { symId, testRel, hard, testName, language, projectRoot } of queue) {
|
|
656
797
|
if (attempted >= budget)
|
|
657
798
|
break;
|
|
658
799
|
const node = nodeById.get(symId);
|
|
@@ -671,10 +812,10 @@ function proveExistingAssociatedTests(root, graph, sourceRoot, nodeById, opts, p
|
|
|
671
812
|
if (provenSymbols.has(symId))
|
|
672
813
|
continue;
|
|
673
814
|
const targetFileRel = symbolFileOf(node);
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
//
|
|
677
|
-
//
|
|
815
|
+
const targetLanguageName = language ?? targetLanguage(symId);
|
|
816
|
+
const runnerRoot = projectRoot ?? proofRunnerRoot(sourceRoot, targetFileRel, testRel);
|
|
817
|
+
// A prior candidate in this exact runner project hit a classified project-wide
|
|
818
|
+
// toolchain/environment failure. Preserve the budget and explain the skip.
|
|
678
819
|
const isPython = isPythonFile(targetFileRel);
|
|
679
820
|
const candidateTestRels = isPython
|
|
680
821
|
? pytestNodeidsForTarget(sourceRoot, testRel, testName).filter((candidate) => isRunnableTestForTarget(node, candidate))
|
|
@@ -682,9 +823,10 @@ function proveExistingAssociatedTests(root, graph, sourceRoot, nodeById, opts, p
|
|
|
682
823
|
if (candidateTestRels.length === 0)
|
|
683
824
|
continue;
|
|
684
825
|
const proofTestRel = candidateTestRels[0];
|
|
685
|
-
const
|
|
826
|
+
const blockedKey = projectBlockKeys(targetLanguageName, runnerRoot, targetFileRel).find((key) => projectBlocked.has(key));
|
|
827
|
+
const blocked = blockedKey ? projectBlocked.get(blockedKey) : undefined;
|
|
686
828
|
if (blocked) {
|
|
687
|
-
const attempt = dedupedAttempt(symId, proofTestRel,
|
|
829
|
+
const attempt = dedupedAttempt(symId, proofTestRel, runnerRoot, blocked);
|
|
688
830
|
attempts.push(attempt);
|
|
689
831
|
needsSetup.push(attempt);
|
|
690
832
|
continue;
|
|
@@ -745,7 +887,8 @@ function proveExistingAssociatedTests(root, graph, sourceRoot, nodeById, opts, p
|
|
|
745
887
|
target_symbol: symId,
|
|
746
888
|
test_path: displayTest,
|
|
747
889
|
classification: "needs_setup",
|
|
748
|
-
reason: `Proof could not run: ${redactSecrets(errMsg(e))}
|
|
890
|
+
reason: `Proof could not run: ${redactSecrets(errMsg(e))}`,
|
|
891
|
+
project_root: runnerRoot
|
|
749
892
|
};
|
|
750
893
|
attempts.push(attempt);
|
|
751
894
|
needsSetup.push(attempt);
|
|
@@ -758,6 +901,7 @@ function proveExistingAssociatedTests(root, graph, sourceRoot, nodeById, opts, p
|
|
|
758
901
|
classification,
|
|
759
902
|
reason,
|
|
760
903
|
category,
|
|
904
|
+
project_root: runnerRoot,
|
|
761
905
|
mutant_status: mutantStatusOf(result)
|
|
762
906
|
};
|
|
763
907
|
attempts.push(attempt);
|
|
@@ -768,9 +912,18 @@ function proveExistingAssociatedTests(root, graph, sourceRoot, nodeById, opts, p
|
|
|
768
912
|
}
|
|
769
913
|
if (classification === "needs_setup") {
|
|
770
914
|
needsSetup.push(attempt);
|
|
771
|
-
//
|
|
772
|
-
|
|
773
|
-
|
|
915
|
+
// Quarantine only a classified project-wide root cause. Target/test-specific
|
|
916
|
+
// red baselines and surviving mutants never suppress siblings.
|
|
917
|
+
if (category && PROJECT_WIDE_BLOCKERS.has(category)) {
|
|
918
|
+
const scopeLabel = category === "go_package_build_failure"
|
|
919
|
+
? dirname(targetFileRel).split(sep).join("/") || "."
|
|
920
|
+
: runnerRoot;
|
|
921
|
+
projectBlocked.set(classifiedProjectBlockKey(category, targetLanguageName, runnerRoot, targetFileRel), {
|
|
922
|
+
category,
|
|
923
|
+
reason: reason ?? "",
|
|
924
|
+
blockedBy: symId,
|
|
925
|
+
scopeLabel
|
|
926
|
+
});
|
|
774
927
|
}
|
|
775
928
|
}
|
|
776
929
|
// non_killing → keep trying this symbol's other associated tests, if any.
|
|
@@ -810,16 +963,17 @@ export async function autoProve(root, opts, deps) {
|
|
|
810
963
|
.filter((r) => r.evidence_tier === "proven")
|
|
811
964
|
.map((r) => r.code_symbol)
|
|
812
965
|
.filter(Boolean));
|
|
813
|
-
//
|
|
814
|
-
//
|
|
815
|
-
|
|
966
|
+
// Shared, run-local quarantine for classified project-wide toolchain failures.
|
|
967
|
+
// Keyed by language + bounded runner root; never populated by a test-specific red
|
|
968
|
+
// baseline, an unknown failure, or a surviving mutant.
|
|
969
|
+
const projectBlocked = new Map();
|
|
816
970
|
// ONE unified dynamic-proof budget for the whole pass (existing-first → then generation).
|
|
817
971
|
// Default 5 ("dynamically prove top 5"); `--auto-limit N` overrides it, clamped to
|
|
818
972
|
// MAX_AUTO_LIMIT. The existing lane consumes from this budget and the generation lane gets
|
|
819
973
|
// only the remainder, so TOTAL attempts (existing + generation) are ≤ budget.
|
|
820
974
|
const autoLimit = Math.max(1, Math.min(MAX_AUTO_LIMIT, Math.floor(opts.autoLimit ?? DEFAULT_AUTO_LIMIT)));
|
|
821
975
|
// ── Lane 1: existing associated tests — NO key required, runs FIRST (PR 1.5). ──
|
|
822
|
-
const ex = proveExistingAssociatedTests(root, graph, sourceRoot, nodeById, opts, proveLoop, proveDeps, alreadyProven,
|
|
976
|
+
const ex = proveExistingAssociatedTests(root, graph, sourceRoot, nodeById, opts, proveLoop, proveDeps, alreadyProven, projectBlocked, autoLimit);
|
|
823
977
|
if (opts.existingOnly) {
|
|
824
978
|
const status = ex.proven > 0 ? "proven-run" : ex.attempted > 0 ? "ran-no-proof" : "no-targets";
|
|
825
979
|
return {
|
|
@@ -872,6 +1026,7 @@ export async function autoProve(root, opts, deps) {
|
|
|
872
1026
|
const changed = new Set(opts.changedFiles);
|
|
873
1027
|
candidates = candidates.filter((g) => changed.has(g.file));
|
|
874
1028
|
}
|
|
1029
|
+
candidates = orderRankedProofCandidates(candidates, graph, sourceRoot);
|
|
875
1030
|
const attempts = [];
|
|
876
1031
|
const needsSetup = [];
|
|
877
1032
|
const skipped = [];
|
|
@@ -917,14 +1072,24 @@ export async function autoProve(root, opts, deps) {
|
|
|
917
1072
|
}
|
|
918
1073
|
const { target_symbol, replacement, runner } = hint.prove_run.args;
|
|
919
1074
|
const targetFileRel = symbolFile(target_symbol);
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
//
|
|
923
|
-
|
|
1075
|
+
const language = targetLanguage(target_symbol);
|
|
1076
|
+
const projectRoot = proofRunnerRoot(sourceRoot, targetFileRel);
|
|
1077
|
+
// Cross-lane quarantine: a classified project-wide runner failure already
|
|
1078
|
+
// explains why this generated candidate cannot run. Skip without spending budget.
|
|
1079
|
+
const blockedKey = projectBlockKeys(language, projectRoot, targetFileRel).find((key) => projectBlocked.has(key));
|
|
1080
|
+
const blocked = blockedKey ? projectBlocked.get(blockedKey) : undefined;
|
|
924
1081
|
if (blocked) {
|
|
925
|
-
const attempt = dedupedAttempt(target_symbol, "",
|
|
1082
|
+
const attempt = dedupedAttempt(target_symbol, "", projectRoot, blocked);
|
|
926
1083
|
attempts.push(attempt);
|
|
927
1084
|
needsSetup.push(attempt);
|
|
1085
|
+
skipped.push({
|
|
1086
|
+
target_symbol,
|
|
1087
|
+
title: test.title,
|
|
1088
|
+
reason: attempt.reason ?? "Skipped after a project-wide proof failure.",
|
|
1089
|
+
language,
|
|
1090
|
+
project_root: projectRoot,
|
|
1091
|
+
blocked_by: blocked.blockedBy
|
|
1092
|
+
});
|
|
928
1093
|
continue;
|
|
929
1094
|
}
|
|
930
1095
|
const filename = basename(hint.prove_run.args.test_path);
|
|
@@ -984,7 +1149,8 @@ export async function autoProve(root, opts, deps) {
|
|
|
984
1149
|
target_symbol,
|
|
985
1150
|
test_path: writeRel,
|
|
986
1151
|
classification: "needs_setup",
|
|
987
|
-
reason: `Proof could not run: ${redactSecrets(errMsg(e))}
|
|
1152
|
+
reason: `Proof could not run: ${redactSecrets(errMsg(e))}`,
|
|
1153
|
+
project_root: projectRoot
|
|
988
1154
|
};
|
|
989
1155
|
attempts.push(attempt);
|
|
990
1156
|
needsSetup.push(attempt);
|
|
@@ -997,6 +1163,7 @@ export async function autoProve(root, opts, deps) {
|
|
|
997
1163
|
classification,
|
|
998
1164
|
reason,
|
|
999
1165
|
category,
|
|
1166
|
+
project_root: projectRoot,
|
|
1000
1167
|
mutant_status: mutantStatusOf(result)
|
|
1001
1168
|
};
|
|
1002
1169
|
attempts.push(attempt);
|
|
@@ -1004,9 +1171,16 @@ export async function autoProve(root, opts, deps) {
|
|
|
1004
1171
|
proven++;
|
|
1005
1172
|
else if (classification === "needs_setup") {
|
|
1006
1173
|
needsSetup.push(attempt);
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1174
|
+
if (category && PROJECT_WIDE_BLOCKERS.has(category)) {
|
|
1175
|
+
const scopeLabel = category === "go_package_build_failure"
|
|
1176
|
+
? dirname(targetFileRel).split(sep).join("/") || "."
|
|
1177
|
+
: projectRoot;
|
|
1178
|
+
projectBlocked.set(classifiedProjectBlockKey(category, language, projectRoot, targetFileRel), {
|
|
1179
|
+
category,
|
|
1180
|
+
reason: reason ?? "",
|
|
1181
|
+
blockedBy: target_symbol,
|
|
1182
|
+
scopeLabel
|
|
1183
|
+
});
|
|
1010
1184
|
}
|
|
1011
1185
|
}
|
|
1012
1186
|
// non_killing stays in `attempts` only — an honest skip, never Proven.
|
|
@@ -9,9 +9,9 @@
|
|
|
9
9
|
* The graph is built directly by OrangePro; it does not depend on any
|
|
10
10
|
* third-party graph product or format.
|
|
11
11
|
*/
|
|
12
|
-
//
|
|
13
|
-
// graphs hold
|
|
14
|
-
export const LOCAL_GRAPH_SCHEMA_VERSION = "orangepro.local_graph.
|
|
12
|
+
// v3: Python method symbol ids are owner-qualified (`sym:file.py#Class.method`) —
|
|
13
|
+
// old graphs hold collision-prone bare method ids and must force-rebuild.
|
|
14
|
+
export const LOCAL_GRAPH_SCHEMA_VERSION = "orangepro.local_graph.v3";
|
|
15
15
|
/** Node kinds that map to behaviors/requirements for scoring + gaps + generation. */
|
|
16
16
|
export const BEHAVIOR_KINDS = new Set([
|
|
17
17
|
"Requirement",
|
|
@@ -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.
|
|
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". */
|
|
@@ -115,12 +115,17 @@ 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
|
|
119
121
|
})),
|
|
120
122
|
skipped: auto.skipped.map((s) => ({
|
|
121
123
|
target_symbol: s.target_symbol,
|
|
122
124
|
title: s.title,
|
|
123
|
-
reason: redactSecrets(s.reason)
|
|
125
|
+
reason: redactSecrets(s.reason),
|
|
126
|
+
language: s.language,
|
|
127
|
+
project_root: s.project_root,
|
|
128
|
+
blocked_by: s.blocked_by
|
|
124
129
|
}))
|
|
125
130
|
};
|
|
126
131
|
}
|
package/dist/local/provenance.js
CHANGED
|
@@ -3,7 +3,7 @@ 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.
|
|
6
|
+
export const ANALYZER_VERSION = "orangepro.analyzer.v3";
|
|
7
7
|
export const PROOF_ORACLE_VERSION = "orangepro.targeted_mutation_oracle.v1";
|
|
8
8
|
function stable(value) {
|
|
9
9
|
if (value === null || typeof value !== "object")
|
package/dist/local/score/risk.js
CHANGED
|
@@ -446,6 +446,7 @@ export function rankRiskGaps(graph, opts = {}) {
|
|
|
446
446
|
// worklist but do not rescale an unchanged peer.
|
|
447
447
|
const normalizationSymbols = graph.nodes.filter((n) => n.kind === "CodeSymbol" && n.denominator_eligible === true && !n.stale && rankEligible(n) && !isSuppressed(n.external_id));
|
|
448
448
|
const symbols = normalizationSymbols.filter((n) => !confirmed.has(n.external_id));
|
|
449
|
+
const worklistSymbols = symbols.filter((n) => n.properties.ranking_exclusion_reason !== "python_structural_container");
|
|
449
450
|
const symbolIds = new Set(normalizationSymbols.map((s) => s.external_id));
|
|
450
451
|
const symbolsByFile = new Map();
|
|
451
452
|
for (const s of normalizationSymbols) {
|
|
@@ -603,7 +604,7 @@ export function rankRiskGaps(graph, opts = {}) {
|
|
|
603
604
|
for (const s of syms)
|
|
604
605
|
legacyIncoming.set(s.external_id, (legacyIncoming.get(s.external_id) ?? 0) + share);
|
|
605
606
|
}
|
|
606
|
-
return
|
|
607
|
+
return worklistSymbols
|
|
607
608
|
.map((s) => {
|
|
608
609
|
const file = symbolFile(s);
|
|
609
610
|
const incoming_refs = legacyIncoming.get(s.external_id) ?? 0;
|
|
@@ -649,7 +650,7 @@ export function rankRiskGaps(graph, opts = {}) {
|
|
|
649
650
|
const rawById = new Map(normalizationSymbols.map((s, idx) => [s.external_id, rawScores[idx]]));
|
|
650
651
|
const pById = new Map(normalizationSymbols.map((s, idx) => [s.external_id, pScores[idx]]));
|
|
651
652
|
const iById = new Map(normalizationSymbols.map((s, idx) => [s.external_id, iScores[idx]]));
|
|
652
|
-
const ranked =
|
|
653
|
+
const ranked = worklistSymbols
|
|
653
654
|
.map((s) => {
|
|
654
655
|
const file = symbolFile(s);
|
|
655
656
|
const incoming_refs = Math.round((incoming.get(s.external_id) ?? 0) * 10) / 10;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@orangepro/orangepro-mcp",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.44",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "OrangePro (`opro`) — a local-first, BYOK CLI + MCP server that builds an evidence graph from a local checkout, ingests runtime coverage, and generates grounded tests. Metadata-only exports; no source upload; generated tests stay local.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -101,7 +101,7 @@
|
|
|
101
101
|
"smoke:generate-prove-v5": "npm run build && node scripts/smoke-generate-prove-v5.mjs",
|
|
102
102
|
"smoke:db-sqljs": "npm run build && node scripts/smoke-db-sqljs-prove.mjs",
|
|
103
103
|
"release": "node scripts/release.mjs",
|
|
104
|
-
"prepublishOnly": "npm run build && npm test"
|
|
104
|
+
"prepublishOnly": "npm run typecheck && npm run build && npm test"
|
|
105
105
|
},
|
|
106
106
|
"engines": {
|
|
107
107
|
"node": ">=20"
|