@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.
@@ -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 && (language === "typescript" || language === "javascript") && !isGenerated && !isNonProductPath(file.relPath)) {
848
- for (const contract of extractBehaviorContracts(content, file.relPath)) {
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 isBoilerplate = !isDts && !isInfra && !isGeneratedSymbol && callableKind && isBoilerplateSymbol(sym.name, language, sym.symbol_kind, sym.trivial_accessor);
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 (isTreeSitterLanguage(language) && treeSitterReady(language) && !isNonProductFile(file.relPath)) {
1073
- const structure = extractTreeSitterStructure(content, language);
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 resolvePythonProofTarget = (testRel, structure, qualifier, callee, shadowed) => {
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 === "go" && c.receiverVar && c.receiverType && c.qualifier === c.receiverVar) {
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