@orangepro/orangepro-mcp 0.2.41 → 0.2.43
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 +415 -42
- package/dist/local/analyze/classify.js +5 -1
- package/dist/local/analyze/confirm.js +90 -6
- package/dist/local/analyze/frameworks.js +3 -0
- package/dist/local/analyze/treeSitter/engine.js +425 -5
- package/dist/local/flows/flowWalker.js +1 -1
- package/dist/local/graph/ontology.js +3 -3
- package/dist/local/operations.js +30 -5
- package/dist/local/proofDoctor.js +5 -4
- package/dist/local/provenance.js +84 -0
- package/dist/local/resolve/importGraph.js +15 -1
- package/dist/local/score/rankEligibility.js +15 -0
- package/dist/local/score/risk.js +99 -26
- package/dist/local/viz/behaviorReportData.js +91 -12
- package/dist/local/viz/behaviorReportHtml.js +19 -2
- package/dist/local/viz/html.js +3 -3
- package/package.json +2 -2
|
@@ -75,10 +75,14 @@ export function languageOf(relPath) {
|
|
|
75
75
|
export function isTestFile(relPath) {
|
|
76
76
|
const p = relPath.toLowerCase();
|
|
77
77
|
const file = relPath.split("/").pop() || relPath;
|
|
78
|
+
// CLI frameworks commonly use `src/commands/<topic>/test/` as a PRODUCT
|
|
79
|
+
// command namespace (for example `agent test run`), not as a test suite.
|
|
80
|
+
// Explicit test filenames and real test roots remain authoritative.
|
|
81
|
+
const cliTestCommandNamespace = /(^|\/)src\/commands\/(?:[^/]+\/)*test\//.test(p);
|
|
78
82
|
return (/\.(test|spec)\.[a-z]+$/.test(p) ||
|
|
79
83
|
/(^|\/)test\.[cm]?[jt]sx?$/.test(p) ||
|
|
80
84
|
/(^|\/)__tests__\//.test(p) ||
|
|
81
|
-
/(^|\/)(tests?|e2e|cypress|spec)\//.test(p) ||
|
|
85
|
+
(!cliTestCommandNamespace && /(^|\/)(tests?|e2e|cypress|spec)\//.test(p)) ||
|
|
82
86
|
/(^|\/)(unit|integration|functional|acceptance)tests?\//.test(p) ||
|
|
83
87
|
/_test\.(py|go)$/.test(p) ||
|
|
84
88
|
/test_.*\.py$/.test(p) ||
|
|
@@ -572,6 +572,21 @@ export function confirmBehaviors(ctx, testAbsPath, implAbsPath, behaviorNames) {
|
|
|
572
572
|
return out;
|
|
573
573
|
}
|
|
574
574
|
const implNorm = norm(implAbsPath);
|
|
575
|
+
const implSf = program.getSourceFile(implNorm);
|
|
576
|
+
const hasModifier = (node, kind) => ts.canHaveModifiers(node) && (ts.getModifiers(node)?.some((m) => m.kind === kind) ?? false);
|
|
577
|
+
const cliCommandClasses = new Set();
|
|
578
|
+
let defaultCliCommandClass = null;
|
|
579
|
+
for (const statement of implSf?.statements ?? []) {
|
|
580
|
+
if (!ts.isClassDeclaration(statement) || !statement.name)
|
|
581
|
+
continue;
|
|
582
|
+
const extendsCliCommand = statement.heritageClauses?.some((clause) => clause.token === ts.SyntaxKind.ExtendsKeyword &&
|
|
583
|
+
clause.types.some((t) => /(?:^|\.)(?:Sf)?Command$/.test(t.expression.getText(implSf)))) ?? false;
|
|
584
|
+
if (!extendsCliCommand || !memberTargetsByClass.has(statement.name.text))
|
|
585
|
+
continue;
|
|
586
|
+
cliCommandClasses.add(statement.name.text);
|
|
587
|
+
if (hasModifier(statement, ts.SyntaxKind.DefaultKeyword))
|
|
588
|
+
defaultCliCommandClass = statement.name.text;
|
|
589
|
+
}
|
|
575
590
|
// Resolve an identifier's binding to a terminal export name in implAbsPath, or
|
|
576
591
|
// null. Follows aliases (renames, re-export barrels) to the defining file.
|
|
577
592
|
const resolvedTargetName = (id) => {
|
|
@@ -918,6 +933,7 @@ export function confirmBehaviors(ctx, testAbsPath, implAbsPath, behaviorNames) {
|
|
|
918
933
|
const constructorBindings = new Map();
|
|
919
934
|
const prototypeBindings = new Map();
|
|
920
935
|
const mutatorAliases = new Map();
|
|
936
|
+
const loadedModuleBindings = new Map();
|
|
921
937
|
const unwrapExpression = (expr) => {
|
|
922
938
|
let current = expr;
|
|
923
939
|
while (ts.isParenthesizedExpression(current) ||
|
|
@@ -930,6 +946,38 @@ export function confirmBehaviors(ctx, testAbsPath, implAbsPath, behaviorNames) {
|
|
|
930
946
|
}
|
|
931
947
|
return current;
|
|
932
948
|
};
|
|
949
|
+
const loadedModuleFromEsmock = (expr) => {
|
|
950
|
+
const value = unwrapExpression(expr);
|
|
951
|
+
if (!ts.isCallExpression(value) ||
|
|
952
|
+
!ts.isIdentifier(value.expression) ||
|
|
953
|
+
!importedFromPkg(value.expression, ["esmock"]) ||
|
|
954
|
+
value.arguments.length === 0) {
|
|
955
|
+
return null;
|
|
956
|
+
}
|
|
957
|
+
const spec = unwrapExpression(value.arguments[0]);
|
|
958
|
+
if (!ts.isStringLiteralLike(spec))
|
|
959
|
+
return null;
|
|
960
|
+
const resolved = resolveImport(spec.text, testAbsPath);
|
|
961
|
+
if (!resolved.resolvedFileName || norm(resolved.resolvedFileName) !== implNorm)
|
|
962
|
+
return null;
|
|
963
|
+
return {
|
|
964
|
+
src: { file: implNorm, importedName: "default", typeOnly: false },
|
|
965
|
+
defaultClassName: defaultCliCommandClass
|
|
966
|
+
};
|
|
967
|
+
};
|
|
968
|
+
const loadedDefaultClassBinding = (expr) => {
|
|
969
|
+
const value = unwrapExpression(expr);
|
|
970
|
+
if (!ts.isPropertyAccessExpression(value) || value.name.text !== "default")
|
|
971
|
+
return null;
|
|
972
|
+
const receiver = unwrapExpression(value.expression);
|
|
973
|
+
if (!ts.isIdentifier(receiver))
|
|
974
|
+
return null;
|
|
975
|
+
const sym = checker.getSymbolAtLocation(receiver);
|
|
976
|
+
const loaded = sym ? loadedModuleBindings.get(sym) : undefined;
|
|
977
|
+
if (!loaded?.defaultClassName)
|
|
978
|
+
return null;
|
|
979
|
+
return { className: loaded.defaultClassName, src: loaded.src, fromNest: false };
|
|
980
|
+
};
|
|
933
981
|
const numericIndex = (expr) => {
|
|
934
982
|
const index = unwrapExpression(expr);
|
|
935
983
|
if (ts.isNumericLiteral(index))
|
|
@@ -1159,7 +1207,13 @@ export function confirmBehaviors(ctx, testAbsPath, implAbsPath, behaviorNames) {
|
|
|
1159
1207
|
if (ts.isIdentifier(receiver)) {
|
|
1160
1208
|
const sym = checker.getSymbolAtLocation(receiver);
|
|
1161
1209
|
const binding = sym ? instanceBindings.get(sym) : undefined;
|
|
1162
|
-
|
|
1210
|
+
if (binding)
|
|
1211
|
+
return binding;
|
|
1212
|
+
const directClass = classNameFromToken(receiver);
|
|
1213
|
+
if (directClass && cliCommandClasses.has(directClass)) {
|
|
1214
|
+
return { className: directClass, src: importSourceOf(receiver), fromNest: false };
|
|
1215
|
+
}
|
|
1216
|
+
return null;
|
|
1163
1217
|
}
|
|
1164
1218
|
if (ts.isConditionalExpression(receiver)) {
|
|
1165
1219
|
return sameClassBinding([bindingFromNestInstanceExpr(receiver.whenTrue), bindingFromNestInstanceExpr(receiver.whenFalse)]);
|
|
@@ -1824,6 +1878,9 @@ export function confirmBehaviors(ctx, testAbsPath, implAbsPath, behaviorNames) {
|
|
|
1824
1878
|
};
|
|
1825
1879
|
const classBindingFromExpr = (expr, requireNest) => {
|
|
1826
1880
|
const value = unwrapExpression(expr);
|
|
1881
|
+
const loadedDefault = loadedDefaultClassBinding(value);
|
|
1882
|
+
if (loadedDefault)
|
|
1883
|
+
return loadedDefault;
|
|
1827
1884
|
if (ts.isIdentifier(value)) {
|
|
1828
1885
|
const sym = checker.getSymbolAtLocation(value);
|
|
1829
1886
|
const binding = sym ? instanceBindings.get(sym) : undefined;
|
|
@@ -1856,6 +1913,22 @@ export function confirmBehaviors(ctx, testAbsPath, implAbsPath, behaviorNames) {
|
|
|
1856
1913
|
if (sym)
|
|
1857
1914
|
instanceBindings.set(sym, binding);
|
|
1858
1915
|
};
|
|
1916
|
+
const maybeBindLoadedModule = (name, expr) => {
|
|
1917
|
+
const loaded = loadedModuleFromEsmock(expr);
|
|
1918
|
+
if (!loaded)
|
|
1919
|
+
return;
|
|
1920
|
+
const sym = checker.getSymbolAtLocation(name);
|
|
1921
|
+
if (sym)
|
|
1922
|
+
loadedModuleBindings.set(sym, loaded);
|
|
1923
|
+
if (!loaded.defaultClassName)
|
|
1924
|
+
return;
|
|
1925
|
+
const targetName = `${loaded.defaultClassName}.run`;
|
|
1926
|
+
if (!nameSet.has(targetName))
|
|
1927
|
+
return;
|
|
1928
|
+
const st = stateFor(targetName);
|
|
1929
|
+
st.importedRuntime = true;
|
|
1930
|
+
st.importSources.push(loaded.src);
|
|
1931
|
+
};
|
|
1859
1932
|
const maybeBindDerivedIdentity = (name, expr) => {
|
|
1860
1933
|
bindMutatorAlias(name, expr);
|
|
1861
1934
|
const sym = checker.getSymbolAtLocation(name);
|
|
@@ -2065,6 +2138,7 @@ export function confirmBehaviors(ctx, testAbsPath, implAbsPath, behaviorNames) {
|
|
|
2065
2138
|
markTupleMutationSeed(node.initializer);
|
|
2066
2139
|
if (ts.isIdentifier(node.name)) {
|
|
2067
2140
|
recordContainerBindings(node.name, node.initializer);
|
|
2141
|
+
maybeBindLoadedModule(node.name, node.initializer);
|
|
2068
2142
|
maybeBindInstance(node.name, node.initializer);
|
|
2069
2143
|
maybeBindDerivedIdentity(node.name, node.initializer);
|
|
2070
2144
|
}
|
|
@@ -2395,7 +2469,10 @@ export function confirmBehaviors(ctx, testAbsPath, implAbsPath, behaviorNames) {
|
|
|
2395
2469
|
out.set(name, {
|
|
2396
2470
|
verdict: "inferred",
|
|
2397
2471
|
reason: "the behavior is exercised but no assertion consumes its result or observes its rendered effect via the real binding",
|
|
2398
|
-
rejected_conjunct: 5
|
|
2472
|
+
rejected_conjunct: 5,
|
|
2473
|
+
...(st.runtimeUses.some((u) => u.src != null && cleanBindingSource(u.src) && !srcMocked(u.src))
|
|
2474
|
+
? { association_signal: "runtime_invocation" }
|
|
2475
|
+
: {})
|
|
2399
2476
|
});
|
|
2400
2477
|
}
|
|
2401
2478
|
return out;
|
|
@@ -2546,10 +2623,11 @@ export function resolveSpecifier(spec, fromFile, repoRoot, wsPkgs) {
|
|
|
2546
2623
|
export function runConfirmer(input) {
|
|
2547
2624
|
const { candidates, symbolsByImpl, existingSymIds, anchorFile } = input;
|
|
2548
2625
|
const confirmations = [];
|
|
2626
|
+
const associations = [];
|
|
2549
2627
|
let attempted = 0;
|
|
2550
2628
|
let capped_downgrades = 0;
|
|
2551
2629
|
if (candidates.length === 0)
|
|
2552
|
-
return { confirmations, attempted, capped_downgrades };
|
|
2630
|
+
return { confirmations, associations, attempted, capped_downgrades };
|
|
2553
2631
|
const absFiles = new Set();
|
|
2554
2632
|
for (const c of candidates) {
|
|
2555
2633
|
absFiles.add(c.testAbs);
|
|
@@ -2564,7 +2642,9 @@ export function runConfirmer(input) {
|
|
|
2564
2642
|
const verdicts = confirmBehaviors(ctx, c.testAbs, c.implAbs, names);
|
|
2565
2643
|
for (const [name, v] of verdicts) {
|
|
2566
2644
|
attempted++;
|
|
2567
|
-
|
|
2645
|
+
const isConfirmed = v.verdict === "confirmed";
|
|
2646
|
+
const isAssociated = v.verdict === "inferred" && v.association_signal === "runtime_invocation";
|
|
2647
|
+
if (!isConfirmed && !isAssociated)
|
|
2568
2648
|
continue;
|
|
2569
2649
|
const symId = `sym:${c.implRel}#${name}`;
|
|
2570
2650
|
if (!existingSymIds.has(symId)) {
|
|
@@ -2575,8 +2655,12 @@ export function runConfirmer(input) {
|
|
|
2575
2655
|
if (seen.has(key))
|
|
2576
2656
|
continue;
|
|
2577
2657
|
seen.add(key);
|
|
2578
|
-
|
|
2658
|
+
const edge = { testRel: c.testRel, implRel: c.implRel, behaviorName: name, symId, reason: v.reason };
|
|
2659
|
+
if (isConfirmed)
|
|
2660
|
+
confirmations.push(edge);
|
|
2661
|
+
else
|
|
2662
|
+
associations.push(edge);
|
|
2579
2663
|
}
|
|
2580
2664
|
}
|
|
2581
|
-
return { confirmations, attempted, capped_downgrades };
|
|
2665
|
+
return { confirmations, associations, attempted, capped_downgrades };
|
|
2582
2666
|
}
|
|
@@ -30,6 +30,9 @@ const RUNTIME_HINTS = {
|
|
|
30
30
|
"django": "Django",
|
|
31
31
|
"flask": "Flask",
|
|
32
32
|
"spring-boot": "Spring Boot",
|
|
33
|
+
"@oclif/core": "oclif",
|
|
34
|
+
"@salesforce/sf-plugins-core": "Salesforce CLI",
|
|
35
|
+
"commander": "Commander",
|
|
33
36
|
};
|
|
34
37
|
const CONFIG_FRAMEWORK_HINTS = [
|
|
35
38
|
{ match: /^vitest\.config\.[tj]s$/, name: "vitest", layer: "unit" },
|