@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
|
@@ -842,10 +842,18 @@ export function analyzeRepo(root, opts = {}) {
|
|
|
842
842
|
}
|
|
843
843
|
const content = readSafe(file.absPath);
|
|
844
844
|
const isGenerated = content ? isGeneratedCode(content) : false;
|
|
845
|
+
const treeStructure = content && isTreeSitterLanguage(language) && treeSitterReady(language)
|
|
846
|
+
? extractTreeSitterStructure(content, language, file.relPath)
|
|
847
|
+
: undefined;
|
|
845
848
|
if (isGenerated)
|
|
846
849
|
generatedCodeFiles.add(file.relPath);
|
|
847
|
-
if (content &&
|
|
848
|
-
|
|
850
|
+
if (content && !isGenerated && !isNonProductPath(file.relPath)) {
|
|
851
|
+
const behaviorContracts = language === "typescript" || language === "javascript"
|
|
852
|
+
? extractBehaviorContracts(content, file.relPath)
|
|
853
|
+
: language === "python"
|
|
854
|
+
? (treeStructure?.pythonBehaviorContracts ?? [])
|
|
855
|
+
: [];
|
|
856
|
+
for (const contract of behaviorContracts) {
|
|
849
857
|
if (behaviorSurfaceExclusionReason(file.relPath, contract.handler ?? contract.title))
|
|
850
858
|
continue;
|
|
851
859
|
behaviorContractsTotal++;
|
|
@@ -868,7 +876,9 @@ export function analyzeRepo(root, opts = {}) {
|
|
|
868
876
|
file: contract.file,
|
|
869
877
|
source: contract.source,
|
|
870
878
|
...(contract.handler ? { handler: contract.handler } : {}),
|
|
871
|
-
...(contract.controller ? { controller: contract.controller } : {})
|
|
879
|
+
...(contract.controller ? { controller: contract.controller } : {}),
|
|
880
|
+
...(contract.route_prefix_status ? { route_prefix_status: contract.route_prefix_status } : {}),
|
|
881
|
+
...(contract.route_prefix ? { route_prefix: contract.route_prefix } : {})
|
|
872
882
|
},
|
|
873
883
|
evidence_strength: "hard",
|
|
874
884
|
review_status: "auto_detected",
|
|
@@ -924,7 +934,8 @@ export function analyzeRepo(root, opts = {}) {
|
|
|
924
934
|
// Trivial accessors (Java getId/toString, Python __repr__) are real
|
|
925
935
|
// symbols but carry no testable behavior — kept in the graph, dropped
|
|
926
936
|
// from the denominator with disclosure so coverage is not under-claimed.
|
|
927
|
-
const
|
|
937
|
+
const boilerplateName = language === "python" ? (sym.name.split(".").pop() ?? sym.name) : sym.name;
|
|
938
|
+
const isBoilerplate = !isDts && !isInfra && !isGeneratedSymbol && callableKind && isBoilerplateSymbol(boilerplateName, language, sym.symbol_kind, sym.trivial_accessor);
|
|
928
939
|
const callableBehaviorCandidate = !isDts && !isInfra && !isGeneratedSymbol && !isBoilerplate && behaviorCallableKind;
|
|
929
940
|
const surfaceExclusionReason = callableBehaviorCandidate
|
|
930
941
|
? behaviorSurfaceExclusionReason(file.relPath, sym.name, sym.member_of)
|
|
@@ -1069,9 +1080,8 @@ export function analyzeRepo(root, opts = {}) {
|
|
|
1069
1080
|
if (generatedServices.length > 0)
|
|
1070
1081
|
medusaGeneratedServicesByFile.set(file.relPath, generatedServices);
|
|
1071
1082
|
}
|
|
1072
|
-
else if (
|
|
1073
|
-
|
|
1074
|
-
nonTsStructureByFile.set(file.relPath, { language, structure });
|
|
1083
|
+
else if (treeStructure && !isNonProductFile(file.relPath)) {
|
|
1084
|
+
nonTsStructureByFile.set(file.relPath, { language, structure: treeStructure });
|
|
1075
1085
|
}
|
|
1076
1086
|
}
|
|
1077
1087
|
}
|
|
@@ -1516,6 +1526,9 @@ export function analyzeRepo(root, opts = {}) {
|
|
|
1516
1526
|
let javaProofAttempted = 0;
|
|
1517
1527
|
let pythonProofConfirmedPairs = 0;
|
|
1518
1528
|
let pythonProofAttempted = 0;
|
|
1529
|
+
let pythonDependencyEdges = 0;
|
|
1530
|
+
let pythonEndpointReachablePromoted = 0;
|
|
1531
|
+
let pythonStructuralContainersExcluded = 0;
|
|
1519
1532
|
if (nonTsStructureByFile.size > 0) {
|
|
1520
1533
|
const nodeByExternalId = new Map(nodes.map((n) => [n.external_id, n]));
|
|
1521
1534
|
const codeFilesByLanguage = new Map();
|
|
@@ -1661,12 +1674,12 @@ export function analyzeRepo(root, opts = {}) {
|
|
|
1661
1674
|
}
|
|
1662
1675
|
}));
|
|
1663
1676
|
};
|
|
1664
|
-
const addImportBinding = (fromRel, local, target, imported, kind) => {
|
|
1677
|
+
const addImportBinding = (fromRel, local, target, imported, kind, module) => {
|
|
1665
1678
|
let map = importBindingsByFile.get(fromRel);
|
|
1666
1679
|
if (!map)
|
|
1667
1680
|
importBindingsByFile.set(fromRel, (map = new Map()));
|
|
1668
1681
|
if (!map.has(local))
|
|
1669
|
-
map.set(local, { ...target, imported, kind });
|
|
1682
|
+
map.set(local, { ...target, imported, kind, module });
|
|
1670
1683
|
};
|
|
1671
1684
|
const uniquePythonModule = (mod) => {
|
|
1672
1685
|
const set = pythonModuleToFiles.get(mod);
|
|
@@ -1721,6 +1734,39 @@ export function analyzeRepo(root, opts = {}) {
|
|
|
1721
1734
|
return null;
|
|
1722
1735
|
return symbolKind(targetRel, methodName) === "method" ? targetRel : null;
|
|
1723
1736
|
};
|
|
1737
|
+
const pythonQualifiedMemberId = (targetRel, className, methodName) => {
|
|
1738
|
+
if (!targetRel)
|
|
1739
|
+
return null;
|
|
1740
|
+
const member = `${className}.${methodName}`;
|
|
1741
|
+
return symbolsByFile.get(targetRel)?.has(member) && symbolKind(targetRel, member) === "method"
|
|
1742
|
+
? `sym:${targetRel}#${member}`
|
|
1743
|
+
: null;
|
|
1744
|
+
};
|
|
1745
|
+
const shouldRetainPythonImportedSink = (binding, callee) => {
|
|
1746
|
+
if (!/^(?:delete|destroy|purge|drop|erase|wipe|truncate|terminate|revoke|remove|replace|overwrite)/i.test(callee))
|
|
1747
|
+
return false;
|
|
1748
|
+
const topModule = (binding.module ?? "").replace(/^\.+/, "").split(".")[0]?.toLowerCase();
|
|
1749
|
+
if (new Set(["os", "pathlib", "shutil", "tempfile"]).has(topModule ?? ""))
|
|
1750
|
+
return false;
|
|
1751
|
+
const moduleName = (binding.module ?? "").toLowerCase();
|
|
1752
|
+
const owner = binding.imported ?? "";
|
|
1753
|
+
return /(?:^|[._])(db|database|model|orm|prisma|repo|repository|record|redis|mongo|sql|store|storage|table)(?:$|[._])/.test(moduleName)
|
|
1754
|
+
|| /(?:Model|Repository|Repo|Store|Client|Table|Database|DB)$/.test(owner);
|
|
1755
|
+
};
|
|
1756
|
+
const shouldRetainPythonReceiverFieldSink = (qualifier, callee) => {
|
|
1757
|
+
if (!/^(?:delete|destroy|purge|drop|erase|wipe|truncate|terminate|revoke|remove|replace|overwrite)/i.test(callee))
|
|
1758
|
+
return false;
|
|
1759
|
+
const tokens = qualifier
|
|
1760
|
+
.replace(/\([^)]*\)/g, "")
|
|
1761
|
+
.replace(/([a-z0-9])([A-Z])/g, "$1_$2")
|
|
1762
|
+
.toLowerCase()
|
|
1763
|
+
.split(/[^a-z0-9]+/)
|
|
1764
|
+
.filter(Boolean);
|
|
1765
|
+
return tokens.some((token) => new Set([
|
|
1766
|
+
"db", "database", "model", "orm", "prisma", "repo", "repository", "record", "redis",
|
|
1767
|
+
"mongo", "sql", "session", "store", "storage", "table"
|
|
1768
|
+
]).has(token));
|
|
1769
|
+
};
|
|
1724
1770
|
const guardedCsharpMemberTarget = (fromRel, className, methodName) => {
|
|
1725
1771
|
const ownNamespace = nonTsStructureByFile.get(fromRel)?.structure.moduleName;
|
|
1726
1772
|
const namespaces = new Set([...(ownNamespace ? [ownNamespace] : []), ...(csharpUsingNamespacesByFile.get(fromRel) ?? [])]);
|
|
@@ -1889,10 +1935,36 @@ export function analyzeRepo(root, opts = {}) {
|
|
|
1889
1935
|
const targetRel = resolvePythonImport(testRel, binding.module) ?? undefined;
|
|
1890
1936
|
return { targetRel, imported: binding.imported, kind: binding.kind };
|
|
1891
1937
|
};
|
|
1892
|
-
const
|
|
1938
|
+
const resolvePythonClassTarget = (testRel, structure, classLocal) => {
|
|
1939
|
+
const binding = pythonProofImportBinding(testRel, structure, classLocal);
|
|
1940
|
+
if (!binding)
|
|
1941
|
+
return null;
|
|
1942
|
+
if (binding.kind !== "named" || !binding.targetRel || !binding.imported || isNonProductFile(binding.targetRel))
|
|
1943
|
+
return null;
|
|
1944
|
+
if (symbolKind(binding.targetRel, binding.imported) === "class") {
|
|
1945
|
+
return { targetRel: binding.targetRel, className: binding.imported };
|
|
1946
|
+
}
|
|
1947
|
+
// One explicit, unique package re-export hop (`pkg.__init__: from .impl import Cls`).
|
|
1948
|
+
// Ambiguity, wildcard exports, cycles, or a second hop fail closed.
|
|
1949
|
+
const exportStructure = nonTsStructureByFile.get(binding.targetRel)?.structure;
|
|
1950
|
+
const exports = (exportStructure?.imports ?? []).filter((candidate) => candidate.kind === "named" && candidate.local === binding.imported && candidate.imported === binding.imported);
|
|
1951
|
+
if (exports.length !== 1)
|
|
1952
|
+
return null;
|
|
1953
|
+
const targetRel = resolvePythonImport(binding.targetRel, exports[0].module);
|
|
1954
|
+
return targetRel && targetRel !== binding.targetRel && !isNonProductFile(targetRel) && symbolKind(targetRel, binding.imported) === "class"
|
|
1955
|
+
? { targetRel, className: binding.imported }
|
|
1956
|
+
: null;
|
|
1957
|
+
};
|
|
1958
|
+
const resolvePythonProofTarget = (testRel, structure, qualifier, callee, shadowed, instanceClass) => {
|
|
1893
1959
|
const conv = conventionSibling(testRel, "python", codeFileSet);
|
|
1894
1960
|
const conventionTargetRel = conv?.relPath && !isNonProductFile(conv.relPath) ? conv.relPath : null;
|
|
1895
1961
|
const hasWildcardImport = structure.imports.some((i) => i.imported === "*");
|
|
1962
|
+
if (instanceClass) {
|
|
1963
|
+
if (!qualifier || shadowed.has(instanceClass) || hasWildcardImport)
|
|
1964
|
+
return null;
|
|
1965
|
+
const resolved = resolvePythonClassTarget(testRel, structure, instanceClass);
|
|
1966
|
+
return resolved ? pythonQualifiedMemberId(resolved.targetRel, resolved.className, callee) : null;
|
|
1967
|
+
}
|
|
1896
1968
|
if (!qualifier) {
|
|
1897
1969
|
if (shadowed.has(callee))
|
|
1898
1970
|
return null;
|
|
@@ -1924,7 +1996,7 @@ export function analyzeRepo(root, opts = {}) {
|
|
|
1924
1996
|
return null;
|
|
1925
1997
|
if (!classTargetRel || symbolKind(classTargetRel, rootQualifier) !== "class")
|
|
1926
1998
|
return null;
|
|
1927
|
-
return eligiblePythonSymbol(classTargetRel, callee);
|
|
1999
|
+
return eligiblePythonSymbol(classTargetRel, `${rootQualifier}.${callee}`);
|
|
1928
2000
|
};
|
|
1929
2001
|
for (const [rel, { language, structure }] of nonTsStructureByFile) {
|
|
1930
2002
|
for (const i of structure.imports) {
|
|
@@ -1953,11 +2025,19 @@ export function analyzeRepo(root, opts = {}) {
|
|
|
1953
2025
|
}
|
|
1954
2026
|
else if (language === "c" || language === "cpp")
|
|
1955
2027
|
targetRel = resolveRelativeModule(rel, i.module, [".h", ".hpp", ".hh", ".hxx", ".c", ".cc", ".cpp", ".cxx"]);
|
|
1956
|
-
if (!targetRel && !targetDir)
|
|
2028
|
+
if (!targetRel && !targetDir) {
|
|
2029
|
+
// Python external runtime imports do not produce graph edges, but the
|
|
2030
|
+
// binding still anchors `Model.delete()`/`client.replace()` as an
|
|
2031
|
+
// imported external call. TYPE_CHECKING imports were removed by the
|
|
2032
|
+
// AST extractor, so they cannot become sink provenance here.
|
|
2033
|
+
if (language === "python") {
|
|
2034
|
+
addImportBinding(rel, i.local, {}, i.imported, i.kind, i.module);
|
|
2035
|
+
}
|
|
1957
2036
|
continue;
|
|
2037
|
+
}
|
|
1958
2038
|
if (targetRel)
|
|
1959
2039
|
emitImport(rel, targetRel);
|
|
1960
|
-
addImportBinding(rel, i.local, { ...(targetRel ? { targetRel } : {}), ...(targetDir ? { targetDir } : {}) }, i.imported, i.kind);
|
|
2040
|
+
addImportBinding(rel, i.local, { ...(targetRel ? { targetRel } : {}), ...(targetDir ? { targetDir } : {}) }, i.imported, i.kind, i.module);
|
|
1961
2041
|
}
|
|
1962
2042
|
}
|
|
1963
2043
|
// Go package-level method index (dir → "Recv.M" → file) for receiver calls that
|
|
@@ -2000,8 +2080,10 @@ export function analyzeRepo(root, opts = {}) {
|
|
|
2000
2080
|
emitCall(callerId, `sym:${rel}#${c.callee}`, rel);
|
|
2001
2081
|
continue;
|
|
2002
2082
|
}
|
|
2003
|
-
if (language === "python" && localSyms.has(c.callee))
|
|
2083
|
+
if (language === "python" && localSyms.has(c.callee)) {
|
|
2084
|
+
emitCall(callerId, `sym:${rel}#${c.callee}`, rel);
|
|
2004
2085
|
continue;
|
|
2086
|
+
}
|
|
2005
2087
|
const b = imports?.get(c.callee);
|
|
2006
2088
|
if (language === "php" && b?.targetRel && b.imported && symbolKind(b.targetRel, b.imported) !== "function")
|
|
2007
2089
|
continue;
|
|
@@ -2010,11 +2092,38 @@ export function analyzeRepo(root, opts = {}) {
|
|
|
2010
2092
|
}
|
|
2011
2093
|
}
|
|
2012
2094
|
else if (c.qualifier) {
|
|
2013
|
-
const rootQualifier = c.qualifier.split(".")[0];
|
|
2014
|
-
if (shadowed.has(rootQualifier))
|
|
2095
|
+
const rootQualifier = language === "python" ? pythonQualifierRoot(c.qualifier) : c.qualifier.split(".")[0];
|
|
2096
|
+
if (shadowed.has(rootQualifier) && !(language === "python" && (rootQualifier === "self" || rootQualifier === "cls")))
|
|
2015
2097
|
continue;
|
|
2016
2098
|
const b = imports?.get(rootQualifier);
|
|
2017
|
-
if (language === "
|
|
2099
|
+
if (language === "python") {
|
|
2100
|
+
const callerNode = nodeByExternalId.get(callerId);
|
|
2101
|
+
const callerOwner = typeof callerNode?.properties.member_of === "string" ? callerNode.properties.member_of : undefined;
|
|
2102
|
+
const selfMember = (rootQualifier === "self" || rootQualifier === "cls") && callerOwner
|
|
2103
|
+
? pythonQualifiedMemberId(rel, callerOwner, c.callee)
|
|
2104
|
+
: null;
|
|
2105
|
+
const localClassMember = pythonQualifiedMemberId(rel, rootQualifier.replace(/\(.*$/, ""), c.callee);
|
|
2106
|
+
const importedClassMember = b?.kind === "named" && b.imported
|
|
2107
|
+
? pythonQualifiedMemberId(b.targetRel, b.imported, c.callee)
|
|
2108
|
+
: null;
|
|
2109
|
+
if (selfMember || localClassMember || importedClassMember) {
|
|
2110
|
+
emitCall(callerId, selfMember ?? localClassMember ?? importedClassMember, rel);
|
|
2111
|
+
}
|
|
2112
|
+
else if (b?.kind === "module" && b.targetRel && symbolsByFile.get(b.targetRel)?.has(c.callee)) {
|
|
2113
|
+
emitCall(callerId, `sym:${b.targetRel}#${c.callee}`, rel);
|
|
2114
|
+
}
|
|
2115
|
+
else if (b && !b.targetRel && shouldRetainPythonImportedSink(b, c.callee)) {
|
|
2116
|
+
const qualifier = b.kind === "named" ? c.qualifier.replace(/\([^)]*\)/g, "") : c.qualifier;
|
|
2117
|
+
recordImportedStaticCallee(callerId, `${qualifier}.${c.callee}`);
|
|
2118
|
+
}
|
|
2119
|
+
else if (!b && c.qualifier.includes(".") && shouldRetainPythonReceiverFieldSink(c.qualifier, c.callee)) {
|
|
2120
|
+
// Attribute-chain receiver (`self.repo.delete`, `store.client.replace`):
|
|
2121
|
+
// retain a name fact only. Scoring applies its own persistence + verb
|
|
2122
|
+
// policy; no graph edge or evidence claim is created here.
|
|
2123
|
+
recordExternalCallee(callerId, `${c.qualifier}.${c.callee}`);
|
|
2124
|
+
}
|
|
2125
|
+
}
|
|
2126
|
+
else if (language === "go" && c.receiverVar && c.receiverType && c.qualifier === c.receiverVar) {
|
|
2018
2127
|
// Go receiver-method call: inside `func (t *task) Run()`, `t.validate()` calls
|
|
2019
2128
|
// `task.validate`. The receiver's type is DECLARED in the method signature, so
|
|
2020
2129
|
// this is exact resolution: same file first, then the same package (methods of
|
|
@@ -2064,6 +2173,153 @@ export function analyzeRepo(root, opts = {}) {
|
|
|
2064
2173
|
}
|
|
2065
2174
|
}
|
|
2066
2175
|
}
|
|
2176
|
+
const resolvePythonReferenceId = (fromRel, reference) => {
|
|
2177
|
+
const parts = reference.split(".").filter(Boolean);
|
|
2178
|
+
if (parts.length === 0)
|
|
2179
|
+
return null;
|
|
2180
|
+
const local = parts[0];
|
|
2181
|
+
if (parts.length === 1 && symbolsByFile.get(fromRel)?.has(local))
|
|
2182
|
+
return `sym:${fromRel}#${local}`;
|
|
2183
|
+
const localMember = parts.length > 1 ? parts.join(".") : "";
|
|
2184
|
+
if (localMember && symbolsByFile.get(fromRel)?.has(localMember))
|
|
2185
|
+
return `sym:${fromRel}#${localMember}`;
|
|
2186
|
+
const binding = importBindingsByFile.get(fromRel)?.get(local);
|
|
2187
|
+
if (!binding?.targetRel || isNonProductFile(binding.targetRel))
|
|
2188
|
+
return null;
|
|
2189
|
+
const targetName = binding.kind === "module"
|
|
2190
|
+
? parts.slice(1).join(".")
|
|
2191
|
+
: [binding.imported, ...parts.slice(1)].filter(Boolean).join(".");
|
|
2192
|
+
return targetName && symbolsByFile.get(binding.targetRel)?.has(targetName)
|
|
2193
|
+
? `sym:${binding.targetRel}#${targetName}`
|
|
2194
|
+
: null;
|
|
2195
|
+
};
|
|
2196
|
+
const seenPythonDependencyEdges = new Set();
|
|
2197
|
+
for (const [rel, { language, structure }] of nonTsStructureByFile) {
|
|
2198
|
+
if (language !== "python")
|
|
2199
|
+
continue;
|
|
2200
|
+
for (const dependency of structure.pythonDependencies ?? []) {
|
|
2201
|
+
const handlerId = `sym:${rel}#${dependency.handler}`;
|
|
2202
|
+
const dependencyId = resolvePythonReferenceId(rel, dependency.dependency);
|
|
2203
|
+
if (!codeSymbolIds.has(handlerId) || !dependencyId || !codeSymbolIds.has(dependencyId))
|
|
2204
|
+
continue;
|
|
2205
|
+
const callKey = `${handlerId}|${dependencyId}`;
|
|
2206
|
+
if (!seenCalls.has(callKey)) {
|
|
2207
|
+
seenCalls.add(callKey);
|
|
2208
|
+
edges.push(makeEdge({
|
|
2209
|
+
from_external_id: handlerId,
|
|
2210
|
+
to_external_id: dependencyId,
|
|
2211
|
+
relationship_type: "CALLS",
|
|
2212
|
+
evidence_strength: "framework-derived",
|
|
2213
|
+
review_status: "auto_detected",
|
|
2214
|
+
provenance: prov(rel),
|
|
2215
|
+
properties: { call_via: "fastapi_dependency", resolution: "tree_sitter_exact" }
|
|
2216
|
+
}));
|
|
2217
|
+
}
|
|
2218
|
+
for (const contract of behaviorContractsByFile.get(rel) ?? []) {
|
|
2219
|
+
if (contract.framework !== "fastapi" || contract.handler !== dependency.handler)
|
|
2220
|
+
continue;
|
|
2221
|
+
const key = `${contract.id}|${dependencyId}`;
|
|
2222
|
+
if (seenPythonDependencyEdges.has(key))
|
|
2223
|
+
continue;
|
|
2224
|
+
seenPythonDependencyEdges.add(key);
|
|
2225
|
+
pythonDependencyEdges++;
|
|
2226
|
+
edges.push(makeEdge({
|
|
2227
|
+
from_external_id: contract.id,
|
|
2228
|
+
to_external_id: dependencyId,
|
|
2229
|
+
relationship_type: "DEPENDS_ON",
|
|
2230
|
+
evidence_strength: "framework-derived",
|
|
2231
|
+
review_status: "auto_detected",
|
|
2232
|
+
provenance: prov(rel),
|
|
2233
|
+
properties: { framework: "fastapi", dependency: dependency.dependency }
|
|
2234
|
+
}));
|
|
2235
|
+
}
|
|
2236
|
+
}
|
|
2237
|
+
}
|
|
2238
|
+
// Python's first-pass denominator is intentionally conservative. Promote only
|
|
2239
|
+
// concrete production methods/functions reached from a framework handler by
|
|
2240
|
+
// exact hard/framework CALLS edges. This repairs denominator visibility without
|
|
2241
|
+
// admitting every class or utility into the score population.
|
|
2242
|
+
const pythonNodeIds = new Set(nodes
|
|
2243
|
+
.filter((n) => n.kind === "CodeSymbol" && typeof n.properties.file === "string" && n.properties.file.endsWith(".py"))
|
|
2244
|
+
.map((n) => n.external_id));
|
|
2245
|
+
const pythonAdjacency = new Map();
|
|
2246
|
+
for (const edge of edges) {
|
|
2247
|
+
if (edge.relationship_type !== "CALLS" || !pythonNodeIds.has(edge.from_external_id) || !pythonNodeIds.has(edge.to_external_id))
|
|
2248
|
+
continue;
|
|
2249
|
+
const list = pythonAdjacency.get(edge.from_external_id);
|
|
2250
|
+
const step = { target: edge.to_external_id, edgeKind: edge.relationship_type };
|
|
2251
|
+
if (list)
|
|
2252
|
+
list.push(step);
|
|
2253
|
+
else
|
|
2254
|
+
pythonAdjacency.set(edge.from_external_id, [step]);
|
|
2255
|
+
}
|
|
2256
|
+
const pythonEntrypoints = [...new Set(edges
|
|
2257
|
+
.filter((e) => e.relationship_type === "IMPLEMENTED_IN" && pythonNodeIds.has(e.to_external_id))
|
|
2258
|
+
.map((e) => e.to_external_id))].sort();
|
|
2259
|
+
const reachablePython = new Set(pythonEntrypoints);
|
|
2260
|
+
const pythonWitness = new Map(pythonEntrypoints.map((seed) => [seed, { seed, edgeKinds: [], hopCount: 0 }]));
|
|
2261
|
+
let frontier = [...pythonEntrypoints];
|
|
2262
|
+
for (let depth = 0; depth < 3 && frontier.length > 0; depth++) {
|
|
2263
|
+
const next = [];
|
|
2264
|
+
for (const current of frontier) {
|
|
2265
|
+
const currentWitness = pythonWitness.get(current);
|
|
2266
|
+
if (!currentWitness)
|
|
2267
|
+
continue;
|
|
2268
|
+
for (const step of pythonAdjacency.get(current) ?? []) {
|
|
2269
|
+
if (reachablePython.has(step.target))
|
|
2270
|
+
continue;
|
|
2271
|
+
reachablePython.add(step.target);
|
|
2272
|
+
pythonWitness.set(step.target, {
|
|
2273
|
+
seed: currentWitness.seed,
|
|
2274
|
+
edgeKinds: [...currentWitness.edgeKinds, step.edgeKind],
|
|
2275
|
+
hopCount: currentWitness.hopCount + 1
|
|
2276
|
+
});
|
|
2277
|
+
next.push(step.target);
|
|
2278
|
+
}
|
|
2279
|
+
}
|
|
2280
|
+
frontier = next;
|
|
2281
|
+
}
|
|
2282
|
+
for (const id of reachablePython) {
|
|
2283
|
+
const node = nodeByExternalId.get(id);
|
|
2284
|
+
if (!node || node.properties.symbol_kind === "class")
|
|
2285
|
+
continue;
|
|
2286
|
+
const witness = pythonWitness.get(id);
|
|
2287
|
+
node.properties.endpoint_reachable = true;
|
|
2288
|
+
if (witness) {
|
|
2289
|
+
node.properties.denominator_witness = {
|
|
2290
|
+
seed: witness.seed,
|
|
2291
|
+
edge_kinds: witness.edgeKinds,
|
|
2292
|
+
hop_count: witness.hopCount
|
|
2293
|
+
};
|
|
2294
|
+
}
|
|
2295
|
+
if (node.denominator_eligible === true)
|
|
2296
|
+
continue;
|
|
2297
|
+
const callableName = (node.title ?? "").split(".").pop() ?? node.title ?? "";
|
|
2298
|
+
if (callableName.startsWith("_") || node.properties.denominator_reason_code !== "not_entry_point_adjacent")
|
|
2299
|
+
continue;
|
|
2300
|
+
node.denominator_eligible = true;
|
|
2301
|
+
node.denominator_reason = "Transitively reachable from a Python framework entrypoint through exact static calls.";
|
|
2302
|
+
node.properties.denominator_promotion = "python_entrypoint_reachable";
|
|
2303
|
+
pythonEndpointReachablePromoted++;
|
|
2304
|
+
}
|
|
2305
|
+
const pythonOwnersWithMethods = new Set(nodes
|
|
2306
|
+
.filter((n) => n.kind === "CodeSymbol" &&
|
|
2307
|
+
n.properties.symbol_kind === "method" &&
|
|
2308
|
+
n.denominator_eligible === true &&
|
|
2309
|
+
typeof n.properties.file === "string" &&
|
|
2310
|
+
n.properties.file.endsWith(".py") &&
|
|
2311
|
+
typeof n.properties.member_of === "string")
|
|
2312
|
+
.map((n) => `${n.properties.file}#${n.properties.member_of}`));
|
|
2313
|
+
for (const node of nodes) {
|
|
2314
|
+
if (node.kind === "CodeSymbol" &&
|
|
2315
|
+
node.properties.symbol_kind === "class" &&
|
|
2316
|
+
node.denominator_eligible === true &&
|
|
2317
|
+
typeof node.properties.file === "string" &&
|
|
2318
|
+
pythonOwnersWithMethods.has(`${node.properties.file}#${node.title ?? ""}`)) {
|
|
2319
|
+
node.properties.ranking_exclusion_reason = "python_structural_container";
|
|
2320
|
+
pythonStructuralContainersExcluded++;
|
|
2321
|
+
}
|
|
2322
|
+
}
|
|
2067
2323
|
const seenGoProof = new Set();
|
|
2068
2324
|
const proofVerifiedAt = scanStartMs;
|
|
2069
2325
|
for (const [testRel, structure] of goTestStructureByFile) {
|
|
@@ -2147,7 +2403,7 @@ export function analyzeRepo(root, opts = {}) {
|
|
|
2147
2403
|
const testExternalId = `test:${testRel}`;
|
|
2148
2404
|
for (const proof of structure.pythonProofCalls ?? []) {
|
|
2149
2405
|
pythonProofAttempted++;
|
|
2150
|
-
const symId = resolvePythonProofTarget(testRel, structure, proof.qualifier, proof.callee, new Set(proof.shadowed));
|
|
2406
|
+
const symId = resolvePythonProofTarget(testRel, structure, proof.qualifier, proof.callee, new Set(proof.shadowed), proof.instanceClass);
|
|
2151
2407
|
if (!symId)
|
|
2152
2408
|
continue;
|
|
2153
2409
|
const edgeKey = `${testExternalId}|${symId}`;
|
|
@@ -2566,6 +2822,15 @@ export function analyzeRepo(root, opts = {}) {
|
|
|
2566
2822
|
}
|
|
2567
2823
|
}
|
|
2568
2824
|
: {}),
|
|
2825
|
+
...(pythonDependencyEdges || pythonEndpointReachablePromoted || pythonStructuralContainersExcluded
|
|
2826
|
+
? {
|
|
2827
|
+
python_structural_recalibration: {
|
|
2828
|
+
dependency_edges: pythonDependencyEdges,
|
|
2829
|
+
endpoint_reachable_promoted: pythonEndpointReachablePromoted,
|
|
2830
|
+
structural_containers_rank_excluded: pythonStructuralContainersExcluded
|
|
2831
|
+
}
|
|
2832
|
+
}
|
|
2833
|
+
: {}),
|
|
2569
2834
|
flows,
|
|
2570
2835
|
structural_clusters: structuralClusters,
|
|
2571
2836
|
...(tsStatus.loaded.length || tsStatus.failed.length
|