@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
|
@@ -10,6 +10,7 @@ import { buildImportGraph } from "../resolve/importGraph.js";
|
|
|
10
10
|
import { walkBarrel } from "../resolve/barrelWalker.js";
|
|
11
11
|
import { resetResolverCaches } from "../resolve/resolver.js";
|
|
12
12
|
import { resetExportIndexCache } from "../resolve/exportIndex.js";
|
|
13
|
+
import { builtInRankExclusion } from "../score/rankEligibility.js";
|
|
13
14
|
import { baseName, extOf, GENERATED_CODE_REASON, isGeneratedCode, isNonProductPath, NON_PRODUCT_REASON, isTestSupportPath, languageOf, manifestKindOf, roleOf } from "./classify.js";
|
|
14
15
|
import { classifyTestLayer } from "./testLayer.js";
|
|
15
16
|
import { detectFromPackageJson, detectFrameworksFromManifest, detectManifestPackage, frameworkFromConfig } from "./frameworks.js";
|
|
@@ -142,6 +143,59 @@ function behaviorSurfaceReason(relPath, name, memberOf) {
|
|
|
142
143
|
return null;
|
|
143
144
|
}
|
|
144
145
|
const RUNTIME_SOURCE_EXT_RE = /\.(?:[cm]?[jt]sx?)$/i;
|
|
146
|
+
/**
|
|
147
|
+
* Return qualified `Class.run` symbols whose class extends a CLI command base
|
|
148
|
+
* imported from a known runtime package. This is deliberately import-scoped:
|
|
149
|
+
* a local class merely named Command/SfCommand never becomes an entry point.
|
|
150
|
+
*/
|
|
151
|
+
function cliEntrySymbols(content) {
|
|
152
|
+
const out = new Map();
|
|
153
|
+
const sourceFile = ts.createSourceFile("cli-entry.tsx", content, ts.ScriptTarget.Latest, false, ts.ScriptKind.TSX);
|
|
154
|
+
const importedBases = new Map();
|
|
155
|
+
for (const stmt of sourceFile.statements) {
|
|
156
|
+
if (!ts.isImportDeclaration(stmt) || !ts.isStringLiteral(stmt.moduleSpecifier) || !stmt.importClause || stmt.importClause.isTypeOnly)
|
|
157
|
+
continue;
|
|
158
|
+
const source = stmt.moduleSpecifier.text;
|
|
159
|
+
const framework = source === "@oclif/core" ? "oclif" :
|
|
160
|
+
source === "@salesforce/sf-plugins-core" ? "salesforce-cli" :
|
|
161
|
+
source === "commander" ? "commander" :
|
|
162
|
+
undefined;
|
|
163
|
+
if (!framework)
|
|
164
|
+
continue;
|
|
165
|
+
const bindings = stmt.importClause.namedBindings;
|
|
166
|
+
if (!bindings || !ts.isNamedImports(bindings))
|
|
167
|
+
continue;
|
|
168
|
+
for (const element of bindings.elements) {
|
|
169
|
+
if (element.isTypeOnly)
|
|
170
|
+
continue;
|
|
171
|
+
const imported = element.propertyName?.text ?? element.name.text;
|
|
172
|
+
if (imported === "Command" || imported === "SfCommand")
|
|
173
|
+
importedBases.set(element.name.text, framework);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
for (const stmt of sourceFile.statements) {
|
|
177
|
+
if (!ts.isClassDeclaration(stmt) || !stmt.name)
|
|
178
|
+
continue;
|
|
179
|
+
let framework;
|
|
180
|
+
for (const clause of stmt.heritageClauses ?? []) {
|
|
181
|
+
if (clause.token !== ts.SyntaxKind.ExtendsKeyword)
|
|
182
|
+
continue;
|
|
183
|
+
for (const type of clause.types) {
|
|
184
|
+
if (ts.isIdentifier(type.expression))
|
|
185
|
+
framework = importedBases.get(type.expression.text) ?? framework;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
if (!framework)
|
|
189
|
+
continue;
|
|
190
|
+
const hasRun = stmt.members.some((member) => ts.isMethodDeclaration(member) &&
|
|
191
|
+
!!member.body &&
|
|
192
|
+
ts.isIdentifier(member.name) &&
|
|
193
|
+
member.name.text === "run");
|
|
194
|
+
if (hasRun)
|
|
195
|
+
out.set(`${stmt.name.text}.run`, framework);
|
|
196
|
+
}
|
|
197
|
+
return out;
|
|
198
|
+
}
|
|
145
199
|
/**
|
|
146
200
|
* Resolve package-level public entry modules from package metadata and the two
|
|
147
201
|
* conventional source entry paths. This gives libraries a semantic behavior
|
|
@@ -487,6 +541,20 @@ export function analyzeRepo(root, opts = {}) {
|
|
|
487
541
|
if (set.size < 32)
|
|
488
542
|
set.add(name);
|
|
489
543
|
};
|
|
544
|
+
// Stronger than an unanchored receiver name: the qualifier is a runtime
|
|
545
|
+
// binding imported from a bare external module. Preserve that provenance so
|
|
546
|
+
// risk scoring can recognize replacement operations without treating every
|
|
547
|
+
// local `replace*` method as destructive.
|
|
548
|
+
const importedStaticCalleesByCaller = new Map();
|
|
549
|
+
const recordImportedStaticCallee = (callerId, name) => {
|
|
550
|
+
let set = importedStaticCalleesByCaller.get(callerId);
|
|
551
|
+
if (!set) {
|
|
552
|
+
set = new Set();
|
|
553
|
+
importedStaticCalleesByCaller.set(callerId, set);
|
|
554
|
+
}
|
|
555
|
+
if (set.size < 32)
|
|
556
|
+
set.add(name);
|
|
557
|
+
};
|
|
490
558
|
// Raw (caller, callee) call pairs per TS/JS code file, resolved after the
|
|
491
559
|
// import graph is built (cross-file calls need its bindings + targets).
|
|
492
560
|
const rawCallsByFile = new Map();
|
|
@@ -774,10 +842,18 @@ export function analyzeRepo(root, opts = {}) {
|
|
|
774
842
|
}
|
|
775
843
|
const content = readSafe(file.absPath);
|
|
776
844
|
const isGenerated = content ? isGeneratedCode(content) : false;
|
|
845
|
+
const treeStructure = content && isTreeSitterLanguage(language) && treeSitterReady(language)
|
|
846
|
+
? extractTreeSitterStructure(content, language, file.relPath)
|
|
847
|
+
: undefined;
|
|
777
848
|
if (isGenerated)
|
|
778
849
|
generatedCodeFiles.add(file.relPath);
|
|
779
|
-
if (content &&
|
|
780
|
-
|
|
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) {
|
|
781
857
|
if (behaviorSurfaceExclusionReason(file.relPath, contract.handler ?? contract.title))
|
|
782
858
|
continue;
|
|
783
859
|
behaviorContractsTotal++;
|
|
@@ -800,7 +876,9 @@ export function analyzeRepo(root, opts = {}) {
|
|
|
800
876
|
file: contract.file,
|
|
801
877
|
source: contract.source,
|
|
802
878
|
...(contract.handler ? { handler: contract.handler } : {}),
|
|
803
|
-
...(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 } : {})
|
|
804
882
|
},
|
|
805
883
|
evidence_strength: "hard",
|
|
806
884
|
review_status: "auto_detected",
|
|
@@ -830,6 +908,7 @@ export function analyzeRepo(root, opts = {}) {
|
|
|
830
908
|
const extraction = opts.parseCache
|
|
831
909
|
? opts.parseCache.symbols(file.hash, `${language}#${extractionBackend(language)}`, () => extractFileSymbols(content, language))
|
|
832
910
|
: extractFileSymbols(content, language);
|
|
911
|
+
const cliEntries = language === "typescript" || language === "javascript" ? cliEntrySymbols(content) : new Map();
|
|
833
912
|
if (extraction.truncated)
|
|
834
913
|
symbolFilesTruncated++;
|
|
835
914
|
for (const sym of extraction.symbols) {
|
|
@@ -855,20 +934,24 @@ export function analyzeRepo(root, opts = {}) {
|
|
|
855
934
|
// Trivial accessors (Java getId/toString, Python __repr__) are real
|
|
856
935
|
// symbols but carry no testable behavior — kept in the graph, dropped
|
|
857
936
|
// from the denominator with disclosure so coverage is not under-claimed.
|
|
858
|
-
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);
|
|
859
939
|
const callableBehaviorCandidate = !isDts && !isInfra && !isGeneratedSymbol && !isBoilerplate && behaviorCallableKind;
|
|
860
940
|
const surfaceExclusionReason = callableBehaviorCandidate
|
|
861
941
|
? behaviorSurfaceExclusionReason(file.relPath, sym.name, sym.member_of)
|
|
862
942
|
: null;
|
|
943
|
+
const cliFramework = cliEntries.get(sym.name);
|
|
944
|
+
const cliDenominatorOverride = Boolean(cliFramework) && callableBehaviorCandidate;
|
|
863
945
|
const surfaceReason = nonTsClassBehavior
|
|
864
946
|
? "Non-TS class/constructor behavior surface — countable for language proof."
|
|
865
947
|
: callableBehaviorCandidate && !surfaceExclusionReason
|
|
866
948
|
? behaviorSurfaceReason(file.relPath, sym.name, sym.member_of)
|
|
867
949
|
: null;
|
|
868
950
|
const publicApiEntry = callableBehaviorCandidate && !surfaceExclusionReason && publicEntryFiles.has(file.relPath);
|
|
869
|
-
const eligible = callableBehaviorCandidate && (surfaceReason !== null || publicApiEntry);
|
|
870
|
-
const behaviorSurfaceExcluded = callableBehaviorCandidate && surfaceExclusionReason !== null;
|
|
951
|
+
const eligible = cliDenominatorOverride || (callableBehaviorCandidate && (surfaceReason !== null || publicApiEntry));
|
|
952
|
+
const behaviorSurfaceExcluded = callableBehaviorCandidate && surfaceExclusionReason !== null && !cliDenominatorOverride;
|
|
871
953
|
const notEntryPointAdjacent = callableBehaviorCandidate && !eligible && !behaviorSurfaceExcluded;
|
|
954
|
+
const rankingExclusion = builtInRankExclusion(file.relPath);
|
|
872
955
|
if (isBoilerplate)
|
|
873
956
|
excludedBoilerplate++;
|
|
874
957
|
nodes.push(makeNode({
|
|
@@ -881,10 +964,27 @@ export function analyzeRepo(root, opts = {}) {
|
|
|
881
964
|
...(sym.start_line ? { start_line: sym.start_line } : {}),
|
|
882
965
|
...(sym.end_line ? { end_line: sym.end_line } : {}),
|
|
883
966
|
...(sym.member_of ? { member_of: sym.member_of } : {}),
|
|
967
|
+
...(cliFramework ? { cli_entrypoint: true, entry_lane: "cli", cli_framework: cliFramework } : {}),
|
|
884
968
|
...(sym.callable !== undefined ? { callable_const: sym.callable } : {}),
|
|
885
|
-
...(
|
|
969
|
+
...(cliDenominatorOverride
|
|
970
|
+
? {
|
|
971
|
+
behavior_surface: "cli_command_entry",
|
|
972
|
+
denominator_reason_code: "cli_command_entry",
|
|
973
|
+
...(surfaceExclusionReason ? { denominator_override_from: surfaceExclusionReason } : {})
|
|
974
|
+
}
|
|
975
|
+
: surfaceReason
|
|
976
|
+
? { behavior_surface: "entrypoint_adjacent" }
|
|
977
|
+
: publicApiEntry
|
|
978
|
+
? { behavior_surface: "public_api_entry" }
|
|
979
|
+
: {}),
|
|
886
980
|
...(behaviorSurfaceExcluded ? { denominator_reason_code: "infra_behavior_surface" } : {}),
|
|
887
|
-
...(notEntryPointAdjacent ? { denominator_reason_code: "not_entry_point_adjacent" } : {})
|
|
981
|
+
...(notEntryPointAdjacent ? { denominator_reason_code: "not_entry_point_adjacent" } : {}),
|
|
982
|
+
...(rankingExclusion
|
|
983
|
+
? {
|
|
984
|
+
ranking_exclusion_reason_code: rankingExclusion.code,
|
|
985
|
+
ranking_exclusion_reason: rankingExclusion.reason
|
|
986
|
+
}
|
|
987
|
+
: {})
|
|
888
988
|
},
|
|
889
989
|
evidence_strength: "hard",
|
|
890
990
|
review_status: "auto_detected",
|
|
@@ -900,17 +1000,19 @@ export function analyzeRepo(root, opts = {}) {
|
|
|
900
1000
|
? GENERATED_CODE_REASON
|
|
901
1001
|
: isBoilerplate
|
|
902
1002
|
? BOILERPLATE_REASON
|
|
903
|
-
:
|
|
904
|
-
?
|
|
905
|
-
:
|
|
906
|
-
?
|
|
907
|
-
:
|
|
908
|
-
?
|
|
909
|
-
:
|
|
910
|
-
?
|
|
911
|
-
:
|
|
912
|
-
? "
|
|
913
|
-
:
|
|
1003
|
+
: cliDenominatorOverride
|
|
1004
|
+
? `Verified ${cliFramework} CLI command run() entry — countable behavior surface${surfaceExclusionReason ? `; overrides: ${surfaceExclusionReason}` : "."}`
|
|
1005
|
+
: eligible && surfaceReason
|
|
1006
|
+
? surfaceReason
|
|
1007
|
+
: publicApiEntry
|
|
1008
|
+
? "Callable export from a package public entry module — countable library behavior surface."
|
|
1009
|
+
: behaviorSurfaceExcluded && surfaceExclusionReason
|
|
1010
|
+
? surfaceExclusionReason
|
|
1011
|
+
: callableBehaviorCandidate
|
|
1012
|
+
? "Callable export is not API/service/route/job-adjacent — kept for grounding, excluded from the behavior denominator."
|
|
1013
|
+
: sym.symbol_kind === "class"
|
|
1014
|
+
? "Exported class container — kept for grounding; methods/functions carry behavior in v1."
|
|
1015
|
+
: "Exported const (not provably callable) — excluded from the denominator in v1."
|
|
914
1016
|
}));
|
|
915
1017
|
edges.push(makeEdge({
|
|
916
1018
|
from_external_id: symId,
|
|
@@ -978,9 +1080,8 @@ export function analyzeRepo(root, opts = {}) {
|
|
|
978
1080
|
if (generatedServices.length > 0)
|
|
979
1081
|
medusaGeneratedServicesByFile.set(file.relPath, generatedServices);
|
|
980
1082
|
}
|
|
981
|
-
else if (
|
|
982
|
-
|
|
983
|
-
nonTsStructureByFile.set(file.relPath, { language, structure });
|
|
1083
|
+
else if (treeStructure && !isNonProductFile(file.relPath)) {
|
|
1084
|
+
nonTsStructureByFile.set(file.relPath, { language, structure: treeStructure });
|
|
984
1085
|
}
|
|
985
1086
|
}
|
|
986
1087
|
}
|
|
@@ -1075,10 +1176,18 @@ export function analyzeRepo(root, opts = {}) {
|
|
|
1075
1176
|
const importBindingsByFile = new Map();
|
|
1076
1177
|
const typeBindingsByFile = new Map();
|
|
1077
1178
|
const namespaceBindingsByFile = new Map();
|
|
1179
|
+
const externalRuntimeBindingsByFile = new Map();
|
|
1078
1180
|
for (const e of importGraph.edges) {
|
|
1181
|
+
const fromRel = relByAbs.get(path.resolve(e.from));
|
|
1182
|
+
if (fromRel && e.importKind === "runtime" && e.category === "bare-external") {
|
|
1183
|
+
let bindings = externalRuntimeBindingsByFile.get(fromRel);
|
|
1184
|
+
if (!bindings)
|
|
1185
|
+
externalRuntimeBindingsByFile.set(fromRel, (bindings = new Set()));
|
|
1186
|
+
for (const binding of e.bindings)
|
|
1187
|
+
bindings.add(binding.local);
|
|
1188
|
+
}
|
|
1079
1189
|
if (!e.resolved || e.external || !e.target)
|
|
1080
1190
|
continue;
|
|
1081
|
-
const fromRel = relByAbs.get(path.resolve(e.from));
|
|
1082
1191
|
const targetRel = relByAbs.get(path.resolve(e.target));
|
|
1083
1192
|
// Skip targets outside the scanned set (ignored/generated files): no node to link.
|
|
1084
1193
|
if (!fromRel || !targetRel || fromRel === targetRel)
|
|
@@ -1264,6 +1373,7 @@ export function analyzeRepo(root, opts = {}) {
|
|
|
1264
1373
|
const imports = importBindingsByFile.get(rel);
|
|
1265
1374
|
const typeImports = typeBindingsByFile.get(rel);
|
|
1266
1375
|
const namespaces = namespaceBindingsByFile.get(rel);
|
|
1376
|
+
const externalRuntimeBindings = externalRuntimeBindingsByFile.get(rel);
|
|
1267
1377
|
for (const c of calls) {
|
|
1268
1378
|
if (!localSyms.has(c.caller))
|
|
1269
1379
|
continue; // caller must be an emitted symbol
|
|
@@ -1323,6 +1433,9 @@ export function analyzeRepo(root, opts = {}) {
|
|
|
1323
1433
|
queueMay(callerId, cands[0], rel, 0.65, `Imported-binding member match: the qualifier is imported from the module defining the only *.method symbol.`);
|
|
1324
1434
|
}
|
|
1325
1435
|
}
|
|
1436
|
+
if (externalRuntimeBindings?.has(c.qualifier)) {
|
|
1437
|
+
recordImportedStaticCallee(callerId, `${c.qualifier}.${c.callee}`);
|
|
1438
|
+
}
|
|
1326
1439
|
// A non-imported (local/param) qualifier is NOT anchored — no edge.
|
|
1327
1440
|
if (!ns && !qImport)
|
|
1328
1441
|
recordExternalCallee(callerId, `${c.qualifier}.${c.callee}`); // round two: retain by name
|
|
@@ -1413,6 +1526,9 @@ export function analyzeRepo(root, opts = {}) {
|
|
|
1413
1526
|
let javaProofAttempted = 0;
|
|
1414
1527
|
let pythonProofConfirmedPairs = 0;
|
|
1415
1528
|
let pythonProofAttempted = 0;
|
|
1529
|
+
let pythonDependencyEdges = 0;
|
|
1530
|
+
let pythonEndpointReachablePromoted = 0;
|
|
1531
|
+
let pythonStructuralContainersExcluded = 0;
|
|
1416
1532
|
if (nonTsStructureByFile.size > 0) {
|
|
1417
1533
|
const nodeByExternalId = new Map(nodes.map((n) => [n.external_id, n]));
|
|
1418
1534
|
const codeFilesByLanguage = new Map();
|
|
@@ -1558,12 +1674,12 @@ export function analyzeRepo(root, opts = {}) {
|
|
|
1558
1674
|
}
|
|
1559
1675
|
}));
|
|
1560
1676
|
};
|
|
1561
|
-
const addImportBinding = (fromRel, local, target, imported, kind) => {
|
|
1677
|
+
const addImportBinding = (fromRel, local, target, imported, kind, module) => {
|
|
1562
1678
|
let map = importBindingsByFile.get(fromRel);
|
|
1563
1679
|
if (!map)
|
|
1564
1680
|
importBindingsByFile.set(fromRel, (map = new Map()));
|
|
1565
1681
|
if (!map.has(local))
|
|
1566
|
-
map.set(local, { ...target, imported, kind });
|
|
1682
|
+
map.set(local, { ...target, imported, kind, module });
|
|
1567
1683
|
};
|
|
1568
1684
|
const uniquePythonModule = (mod) => {
|
|
1569
1685
|
const set = pythonModuleToFiles.get(mod);
|
|
@@ -1618,6 +1734,39 @@ export function analyzeRepo(root, opts = {}) {
|
|
|
1618
1734
|
return null;
|
|
1619
1735
|
return symbolKind(targetRel, methodName) === "method" ? targetRel : null;
|
|
1620
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
|
+
};
|
|
1621
1770
|
const guardedCsharpMemberTarget = (fromRel, className, methodName) => {
|
|
1622
1771
|
const ownNamespace = nonTsStructureByFile.get(fromRel)?.structure.moduleName;
|
|
1623
1772
|
const namespaces = new Set([...(ownNamespace ? [ownNamespace] : []), ...(csharpUsingNamespacesByFile.get(fromRel) ?? [])]);
|
|
@@ -1821,7 +1970,7 @@ export function analyzeRepo(root, opts = {}) {
|
|
|
1821
1970
|
return null;
|
|
1822
1971
|
if (!classTargetRel || symbolKind(classTargetRel, rootQualifier) !== "class")
|
|
1823
1972
|
return null;
|
|
1824
|
-
return eligiblePythonSymbol(classTargetRel, callee);
|
|
1973
|
+
return eligiblePythonSymbol(classTargetRel, `${rootQualifier}.${callee}`);
|
|
1825
1974
|
};
|
|
1826
1975
|
for (const [rel, { language, structure }] of nonTsStructureByFile) {
|
|
1827
1976
|
for (const i of structure.imports) {
|
|
@@ -1850,11 +1999,19 @@ export function analyzeRepo(root, opts = {}) {
|
|
|
1850
1999
|
}
|
|
1851
2000
|
else if (language === "c" || language === "cpp")
|
|
1852
2001
|
targetRel = resolveRelativeModule(rel, i.module, [".h", ".hpp", ".hh", ".hxx", ".c", ".cc", ".cpp", ".cxx"]);
|
|
1853
|
-
if (!targetRel && !targetDir)
|
|
2002
|
+
if (!targetRel && !targetDir) {
|
|
2003
|
+
// Python external runtime imports do not produce graph edges, but the
|
|
2004
|
+
// binding still anchors `Model.delete()`/`client.replace()` as an
|
|
2005
|
+
// imported external call. TYPE_CHECKING imports were removed by the
|
|
2006
|
+
// AST extractor, so they cannot become sink provenance here.
|
|
2007
|
+
if (language === "python") {
|
|
2008
|
+
addImportBinding(rel, i.local, {}, i.imported, i.kind, i.module);
|
|
2009
|
+
}
|
|
1854
2010
|
continue;
|
|
2011
|
+
}
|
|
1855
2012
|
if (targetRel)
|
|
1856
2013
|
emitImport(rel, targetRel);
|
|
1857
|
-
addImportBinding(rel, i.local, { ...(targetRel ? { targetRel } : {}), ...(targetDir ? { targetDir } : {}) }, i.imported, i.kind);
|
|
2014
|
+
addImportBinding(rel, i.local, { ...(targetRel ? { targetRel } : {}), ...(targetDir ? { targetDir } : {}) }, i.imported, i.kind, i.module);
|
|
1858
2015
|
}
|
|
1859
2016
|
}
|
|
1860
2017
|
// Go package-level method index (dir → "Recv.M" → file) for receiver calls that
|
|
@@ -1897,8 +2054,10 @@ export function analyzeRepo(root, opts = {}) {
|
|
|
1897
2054
|
emitCall(callerId, `sym:${rel}#${c.callee}`, rel);
|
|
1898
2055
|
continue;
|
|
1899
2056
|
}
|
|
1900
|
-
if (language === "python" && localSyms.has(c.callee))
|
|
2057
|
+
if (language === "python" && localSyms.has(c.callee)) {
|
|
2058
|
+
emitCall(callerId, `sym:${rel}#${c.callee}`, rel);
|
|
1901
2059
|
continue;
|
|
2060
|
+
}
|
|
1902
2061
|
const b = imports?.get(c.callee);
|
|
1903
2062
|
if (language === "php" && b?.targetRel && b.imported && symbolKind(b.targetRel, b.imported) !== "function")
|
|
1904
2063
|
continue;
|
|
@@ -1907,11 +2066,38 @@ export function analyzeRepo(root, opts = {}) {
|
|
|
1907
2066
|
}
|
|
1908
2067
|
}
|
|
1909
2068
|
else if (c.qualifier) {
|
|
1910
|
-
const rootQualifier = c.qualifier.split(".")[0];
|
|
1911
|
-
if (shadowed.has(rootQualifier))
|
|
2069
|
+
const rootQualifier = language === "python" ? pythonQualifierRoot(c.qualifier) : c.qualifier.split(".")[0];
|
|
2070
|
+
if (shadowed.has(rootQualifier) && !(language === "python" && (rootQualifier === "self" || rootQualifier === "cls")))
|
|
1912
2071
|
continue;
|
|
1913
2072
|
const b = imports?.get(rootQualifier);
|
|
1914
|
-
if (language === "
|
|
2073
|
+
if (language === "python") {
|
|
2074
|
+
const callerNode = nodeByExternalId.get(callerId);
|
|
2075
|
+
const callerOwner = typeof callerNode?.properties.member_of === "string" ? callerNode.properties.member_of : undefined;
|
|
2076
|
+
const selfMember = (rootQualifier === "self" || rootQualifier === "cls") && callerOwner
|
|
2077
|
+
? pythonQualifiedMemberId(rel, callerOwner, c.callee)
|
|
2078
|
+
: null;
|
|
2079
|
+
const localClassMember = pythonQualifiedMemberId(rel, rootQualifier.replace(/\(.*$/, ""), c.callee);
|
|
2080
|
+
const importedClassMember = b?.kind === "named" && b.imported
|
|
2081
|
+
? pythonQualifiedMemberId(b.targetRel, b.imported, c.callee)
|
|
2082
|
+
: null;
|
|
2083
|
+
if (selfMember || localClassMember || importedClassMember) {
|
|
2084
|
+
emitCall(callerId, selfMember ?? localClassMember ?? importedClassMember, rel);
|
|
2085
|
+
}
|
|
2086
|
+
else if (b?.kind === "module" && b.targetRel && symbolsByFile.get(b.targetRel)?.has(c.callee)) {
|
|
2087
|
+
emitCall(callerId, `sym:${b.targetRel}#${c.callee}`, rel);
|
|
2088
|
+
}
|
|
2089
|
+
else if (b && !b.targetRel && shouldRetainPythonImportedSink(b, c.callee)) {
|
|
2090
|
+
const qualifier = b.kind === "named" ? c.qualifier.replace(/\([^)]*\)/g, "") : c.qualifier;
|
|
2091
|
+
recordImportedStaticCallee(callerId, `${qualifier}.${c.callee}`);
|
|
2092
|
+
}
|
|
2093
|
+
else if (!b && c.qualifier.includes(".") && shouldRetainPythonReceiverFieldSink(c.qualifier, c.callee)) {
|
|
2094
|
+
// Attribute-chain receiver (`self.repo.delete`, `store.client.replace`):
|
|
2095
|
+
// retain a name fact only. Scoring applies its own persistence + verb
|
|
2096
|
+
// policy; no graph edge or evidence claim is created here.
|
|
2097
|
+
recordExternalCallee(callerId, `${c.qualifier}.${c.callee}`);
|
|
2098
|
+
}
|
|
2099
|
+
}
|
|
2100
|
+
else if (language === "go" && c.receiverVar && c.receiverType && c.qualifier === c.receiverVar) {
|
|
1915
2101
|
// Go receiver-method call: inside `func (t *task) Run()`, `t.validate()` calls
|
|
1916
2102
|
// `task.validate`. The receiver's type is DECLARED in the method signature, so
|
|
1917
2103
|
// this is exact resolution: same file first, then the same package (methods of
|
|
@@ -1961,14 +2147,151 @@ export function analyzeRepo(root, opts = {}) {
|
|
|
1961
2147
|
}
|
|
1962
2148
|
}
|
|
1963
2149
|
}
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
2150
|
+
const resolvePythonReferenceId = (fromRel, reference) => {
|
|
2151
|
+
const parts = reference.split(".").filter(Boolean);
|
|
2152
|
+
if (parts.length === 0)
|
|
2153
|
+
return null;
|
|
2154
|
+
const local = parts[0];
|
|
2155
|
+
if (parts.length === 1 && symbolsByFile.get(fromRel)?.has(local))
|
|
2156
|
+
return `sym:${fromRel}#${local}`;
|
|
2157
|
+
const localMember = parts.length > 1 ? parts.join(".") : "";
|
|
2158
|
+
if (localMember && symbolsByFile.get(fromRel)?.has(localMember))
|
|
2159
|
+
return `sym:${fromRel}#${localMember}`;
|
|
2160
|
+
const binding = importBindingsByFile.get(fromRel)?.get(local);
|
|
2161
|
+
if (!binding?.targetRel || isNonProductFile(binding.targetRel))
|
|
2162
|
+
return null;
|
|
2163
|
+
const targetName = binding.kind === "module"
|
|
2164
|
+
? parts.slice(1).join(".")
|
|
2165
|
+
: [binding.imported, ...parts.slice(1)].filter(Boolean).join(".");
|
|
2166
|
+
return targetName && symbolsByFile.get(binding.targetRel)?.has(targetName)
|
|
2167
|
+
? `sym:${binding.targetRel}#${targetName}`
|
|
2168
|
+
: null;
|
|
2169
|
+
};
|
|
2170
|
+
const seenPythonDependencyEdges = new Set();
|
|
2171
|
+
for (const [rel, { language, structure }] of nonTsStructureByFile) {
|
|
2172
|
+
if (language !== "python")
|
|
2173
|
+
continue;
|
|
2174
|
+
for (const dependency of structure.pythonDependencies ?? []) {
|
|
2175
|
+
const handlerId = `sym:${rel}#${dependency.handler}`;
|
|
2176
|
+
const dependencyId = resolvePythonReferenceId(rel, dependency.dependency);
|
|
2177
|
+
if (!codeSymbolIds.has(handlerId) || !dependencyId || !codeSymbolIds.has(dependencyId))
|
|
2178
|
+
continue;
|
|
2179
|
+
const callKey = `${handlerId}|${dependencyId}`;
|
|
2180
|
+
if (!seenCalls.has(callKey)) {
|
|
2181
|
+
seenCalls.add(callKey);
|
|
2182
|
+
edges.push(makeEdge({
|
|
2183
|
+
from_external_id: handlerId,
|
|
2184
|
+
to_external_id: dependencyId,
|
|
2185
|
+
relationship_type: "CALLS",
|
|
2186
|
+
evidence_strength: "framework-derived",
|
|
2187
|
+
review_status: "auto_detected",
|
|
2188
|
+
provenance: prov(rel),
|
|
2189
|
+
properties: { call_via: "fastapi_dependency", resolution: "tree_sitter_exact" }
|
|
2190
|
+
}));
|
|
2191
|
+
}
|
|
2192
|
+
for (const contract of behaviorContractsByFile.get(rel) ?? []) {
|
|
2193
|
+
if (contract.framework !== "fastapi" || contract.handler !== dependency.handler)
|
|
2194
|
+
continue;
|
|
2195
|
+
const key = `${contract.id}|${dependencyId}`;
|
|
2196
|
+
if (seenPythonDependencyEdges.has(key))
|
|
2197
|
+
continue;
|
|
2198
|
+
seenPythonDependencyEdges.add(key);
|
|
2199
|
+
pythonDependencyEdges++;
|
|
2200
|
+
edges.push(makeEdge({
|
|
2201
|
+
from_external_id: contract.id,
|
|
2202
|
+
to_external_id: dependencyId,
|
|
2203
|
+
relationship_type: "DEPENDS_ON",
|
|
2204
|
+
evidence_strength: "framework-derived",
|
|
2205
|
+
review_status: "auto_detected",
|
|
2206
|
+
provenance: prov(rel),
|
|
2207
|
+
properties: { framework: "fastapi", dependency: dependency.dependency }
|
|
2208
|
+
}));
|
|
2209
|
+
}
|
|
2210
|
+
}
|
|
2211
|
+
}
|
|
2212
|
+
// Python's first-pass denominator is intentionally conservative. Promote only
|
|
2213
|
+
// concrete production methods/functions reached from a framework handler by
|
|
2214
|
+
// exact hard/framework CALLS edges. This repairs denominator visibility without
|
|
2215
|
+
// admitting every class or utility into the score population.
|
|
2216
|
+
const pythonNodeIds = new Set(nodes
|
|
2217
|
+
.filter((n) => n.kind === "CodeSymbol" && typeof n.properties.file === "string" && n.properties.file.endsWith(".py"))
|
|
2218
|
+
.map((n) => n.external_id));
|
|
2219
|
+
const pythonAdjacency = new Map();
|
|
2220
|
+
for (const edge of edges) {
|
|
2221
|
+
if (edge.relationship_type !== "CALLS" || !pythonNodeIds.has(edge.from_external_id) || !pythonNodeIds.has(edge.to_external_id))
|
|
2222
|
+
continue;
|
|
2223
|
+
const list = pythonAdjacency.get(edge.from_external_id);
|
|
2224
|
+
const step = { target: edge.to_external_id, edgeKind: edge.relationship_type };
|
|
2225
|
+
if (list)
|
|
2226
|
+
list.push(step);
|
|
2227
|
+
else
|
|
2228
|
+
pythonAdjacency.set(edge.from_external_id, [step]);
|
|
2229
|
+
}
|
|
2230
|
+
const pythonEntrypoints = [...new Set(edges
|
|
2231
|
+
.filter((e) => e.relationship_type === "IMPLEMENTED_IN" && pythonNodeIds.has(e.to_external_id))
|
|
2232
|
+
.map((e) => e.to_external_id))].sort();
|
|
2233
|
+
const reachablePython = new Set(pythonEntrypoints);
|
|
2234
|
+
const pythonWitness = new Map(pythonEntrypoints.map((seed) => [seed, { seed, edgeKinds: [], hopCount: 0 }]));
|
|
2235
|
+
let frontier = [...pythonEntrypoints];
|
|
2236
|
+
for (let depth = 0; depth < 3 && frontier.length > 0; depth++) {
|
|
2237
|
+
const next = [];
|
|
2238
|
+
for (const current of frontier) {
|
|
2239
|
+
const currentWitness = pythonWitness.get(current);
|
|
2240
|
+
if (!currentWitness)
|
|
1970
2241
|
continue;
|
|
1971
|
-
|
|
2242
|
+
for (const step of pythonAdjacency.get(current) ?? []) {
|
|
2243
|
+
if (reachablePython.has(step.target))
|
|
2244
|
+
continue;
|
|
2245
|
+
reachablePython.add(step.target);
|
|
2246
|
+
pythonWitness.set(step.target, {
|
|
2247
|
+
seed: currentWitness.seed,
|
|
2248
|
+
edgeKinds: [...currentWitness.edgeKinds, step.edgeKind],
|
|
2249
|
+
hopCount: currentWitness.hopCount + 1
|
|
2250
|
+
});
|
|
2251
|
+
next.push(step.target);
|
|
2252
|
+
}
|
|
2253
|
+
}
|
|
2254
|
+
frontier = next;
|
|
2255
|
+
}
|
|
2256
|
+
for (const id of reachablePython) {
|
|
2257
|
+
const node = nodeByExternalId.get(id);
|
|
2258
|
+
if (!node || node.properties.symbol_kind === "class")
|
|
2259
|
+
continue;
|
|
2260
|
+
const witness = pythonWitness.get(id);
|
|
2261
|
+
node.properties.endpoint_reachable = true;
|
|
2262
|
+
if (witness) {
|
|
2263
|
+
node.properties.denominator_witness = {
|
|
2264
|
+
seed: witness.seed,
|
|
2265
|
+
edge_kinds: witness.edgeKinds,
|
|
2266
|
+
hop_count: witness.hopCount
|
|
2267
|
+
};
|
|
2268
|
+
}
|
|
2269
|
+
if (node.denominator_eligible === true)
|
|
2270
|
+
continue;
|
|
2271
|
+
const callableName = (node.title ?? "").split(".").pop() ?? node.title ?? "";
|
|
2272
|
+
if (callableName.startsWith("_") || node.properties.denominator_reason_code !== "not_entry_point_adjacent")
|
|
2273
|
+
continue;
|
|
2274
|
+
node.denominator_eligible = true;
|
|
2275
|
+
node.denominator_reason = "Transitively reachable from a Python framework entrypoint through exact static calls.";
|
|
2276
|
+
node.properties.denominator_promotion = "python_entrypoint_reachable";
|
|
2277
|
+
pythonEndpointReachablePromoted++;
|
|
2278
|
+
}
|
|
2279
|
+
const pythonOwnersWithMethods = new Set(nodes
|
|
2280
|
+
.filter((n) => n.kind === "CodeSymbol" &&
|
|
2281
|
+
n.properties.symbol_kind === "method" &&
|
|
2282
|
+
n.denominator_eligible === true &&
|
|
2283
|
+
typeof n.properties.file === "string" &&
|
|
2284
|
+
n.properties.file.endsWith(".py") &&
|
|
2285
|
+
typeof n.properties.member_of === "string")
|
|
2286
|
+
.map((n) => `${n.properties.file}#${n.properties.member_of}`));
|
|
2287
|
+
for (const node of nodes) {
|
|
2288
|
+
if (node.kind === "CodeSymbol" &&
|
|
2289
|
+
node.properties.symbol_kind === "class" &&
|
|
2290
|
+
node.denominator_eligible === true &&
|
|
2291
|
+
typeof node.properties.file === "string" &&
|
|
2292
|
+
pythonOwnersWithMethods.has(`${node.properties.file}#${node.title ?? ""}`)) {
|
|
2293
|
+
node.properties.ranking_exclusion_reason = "python_structural_container";
|
|
2294
|
+
pythonStructuralContainersExcluded++;
|
|
1972
2295
|
}
|
|
1973
2296
|
}
|
|
1974
2297
|
const seenGoProof = new Set();
|
|
@@ -2075,6 +2398,25 @@ export function analyzeRepo(root, opts = {}) {
|
|
|
2075
2398
|
}
|
|
2076
2399
|
}
|
|
2077
2400
|
}
|
|
2401
|
+
// Persist retained callees for every language mix. This must live outside the
|
|
2402
|
+
// non-TS conditional: a TypeScript-only repository still needs its unresolved
|
|
2403
|
+
// external and import-anchored static calls available to risk scoring.
|
|
2404
|
+
if (externalCalleesByCaller.size > 0 || importedStaticCalleesByCaller.size > 0) {
|
|
2405
|
+
const byId = new Map(nodes.map((n) => [n.external_id, n]));
|
|
2406
|
+
const callerIds = new Set([...externalCalleesByCaller.keys(), ...importedStaticCalleesByCaller.keys()]);
|
|
2407
|
+
for (const callerId of callerIds) {
|
|
2408
|
+
const n = byId.get(callerId);
|
|
2409
|
+
if (!n)
|
|
2410
|
+
continue;
|
|
2411
|
+
const external = externalCalleesByCaller.get(callerId);
|
|
2412
|
+
const importedStatic = importedStaticCalleesByCaller.get(callerId);
|
|
2413
|
+
n.properties = {
|
|
2414
|
+
...(n.properties ?? {}),
|
|
2415
|
+
...(external?.size ? { external_callees: [...external].sort() } : {}),
|
|
2416
|
+
...(importedStatic?.size ? { imported_static_callees: [...importedStatic].sort() } : {})
|
|
2417
|
+
};
|
|
2418
|
+
}
|
|
2419
|
+
}
|
|
2078
2420
|
// ---- Static assertion candidates (Phase 4 / Gate 2): TypeChecker-derived hard edges ----
|
|
2079
2421
|
// Upgrade resolver-derived test->source candidates to HARD TESTED_BY/COVERS
|
|
2080
2422
|
// ONLY where the 5-conjunct confirmer proves a runtime, asserted use of a real
|
|
@@ -2157,7 +2499,7 @@ export function analyzeRepo(root, opts = {}) {
|
|
|
2157
2499
|
involved.add(c.implAbs);
|
|
2158
2500
|
}
|
|
2159
2501
|
if (candidates.length === 0) {
|
|
2160
|
-
confirmedCoverage = { confirmed_pairs: 0, attempted: 0, capped_downgrades: 0, skipped_files_budget: 0 };
|
|
2502
|
+
confirmedCoverage = { confirmed_pairs: 0, associated_pairs: 0, attempted: 0, capped_downgrades: 0, skipped_files_budget: 0 };
|
|
2161
2503
|
}
|
|
2162
2504
|
else
|
|
2163
2505
|
try {
|
|
@@ -2174,7 +2516,7 @@ export function analyzeRepo(root, opts = {}) {
|
|
|
2174
2516
|
})
|
|
2175
2517
|
: { candidates, riskSymbols: 0, involvedFiles: involved.size };
|
|
2176
2518
|
if (involved.size > confirmBudget && scoped.candidates.length === 0) {
|
|
2177
|
-
confirmedCoverage = { confirmed_pairs: 0, attempted: 0, capped_downgrades: 0, skipped_files_budget: involved.size };
|
|
2519
|
+
confirmedCoverage = { confirmed_pairs: 0, associated_pairs: 0, attempted: 0, capped_downgrades: 0, skipped_files_budget: involved.size };
|
|
2178
2520
|
warnings.push(`Static confirmation skipped: ${involved.size} files exceed the confirmer budget (${confirmBudget}), and no high-risk subset fit. Use \`--base <ref>\` for PR-scoped confirmation, or raise ORANGEPRO_MAX_CONFIRM_FILES.`);
|
|
2179
2521
|
}
|
|
2180
2522
|
else {
|
|
@@ -2195,8 +2537,28 @@ export function analyzeRepo(root, opts = {}) {
|
|
|
2195
2537
|
lastVerified: proofVerifiedAt
|
|
2196
2538
|
}));
|
|
2197
2539
|
}
|
|
2540
|
+
let associatedPairs = 0;
|
|
2541
|
+
for (const c of result.associations) {
|
|
2542
|
+
const edgeKey = `test:${c.testRel}|${c.symId}`;
|
|
2543
|
+
if (seenEdge.has(edgeKey))
|
|
2544
|
+
continue;
|
|
2545
|
+
seenEdge.add(edgeKey);
|
|
2546
|
+
associatedPairs++;
|
|
2547
|
+
edges.push(...makeProofEdges({
|
|
2548
|
+
testRel: c.testRel,
|
|
2549
|
+
symId: c.symId,
|
|
2550
|
+
provenance: prov(c.testRel, hashString(`${c.symId}:runtime-invocation`)),
|
|
2551
|
+
lastVerified: proofVerifiedAt,
|
|
2552
|
+
properties: {
|
|
2553
|
+
static_association: "runtime_invocation",
|
|
2554
|
+
assertion_observed: false,
|
|
2555
|
+
association_reason: c.reason
|
|
2556
|
+
}
|
|
2557
|
+
}));
|
|
2558
|
+
}
|
|
2198
2559
|
confirmedCoverage = {
|
|
2199
2560
|
confirmed_pairs: confirmedPairs,
|
|
2561
|
+
associated_pairs: associatedPairs,
|
|
2200
2562
|
attempted: result.attempted,
|
|
2201
2563
|
capped_downgrades: result.capped_downgrades,
|
|
2202
2564
|
skipped_files_budget: involved.size > confirmBudget ? involved.size : 0,
|
|
@@ -2222,7 +2584,7 @@ export function analyzeRepo(root, opts = {}) {
|
|
|
2222
2584
|
// or a TS compiler-API edge must NEVER crash analyze. Degrade to
|
|
2223
2585
|
// candidate-only coverage with a disclosure.
|
|
2224
2586
|
const msg = err instanceof Error ? err.message : String(err);
|
|
2225
|
-
confirmedCoverage = { confirmed_pairs: 0, attempted: 0, capped_downgrades: 0, skipped_files_budget: 0 };
|
|
2587
|
+
confirmedCoverage = { confirmed_pairs: 0, associated_pairs: 0, attempted: 0, capped_downgrades: 0, skipped_files_budget: 0 };
|
|
2226
2588
|
warnings.push(`Static confirmation skipped: the structural confirmer could not run on this project (${msg.slice(0, 160)}). Coverage is reported from candidate links only.`);
|
|
2227
2589
|
}
|
|
2228
2590
|
}
|
|
@@ -2230,6 +2592,7 @@ export function analyzeRepo(root, opts = {}) {
|
|
|
2230
2592
|
if (nonTsHardProofAttempted > 0) {
|
|
2231
2593
|
confirmedCoverage = {
|
|
2232
2594
|
confirmed_pairs: (confirmedCoverage?.confirmed_pairs ?? 0) + goProofConfirmedPairs + javaProofConfirmedPairs + pythonProofConfirmedPairs,
|
|
2595
|
+
associated_pairs: confirmedCoverage?.associated_pairs ?? 0,
|
|
2233
2596
|
attempted: (confirmedCoverage?.attempted ?? 0) + nonTsHardProofAttempted,
|
|
2234
2597
|
capped_downgrades: confirmedCoverage?.capped_downgrades ?? 0,
|
|
2235
2598
|
skipped_files_budget: confirmedCoverage?.skipped_files_budget ?? 0
|
|
@@ -2412,6 +2775,7 @@ export function analyzeRepo(root, opts = {}) {
|
|
|
2412
2775
|
files_scanned: filesProcessed,
|
|
2413
2776
|
files_cap_hit: filesCapHit,
|
|
2414
2777
|
max_files: maxFiles,
|
|
2778
|
+
max_symbols: maxSymbols,
|
|
2415
2779
|
...(budgetStopped
|
|
2416
2780
|
? { not_analyzed_due_to_budget: { files_not_analyzed: filesNotAnalyzed, elapsed_ms: elapsedMs, budget_ms: budgetMs } }
|
|
2417
2781
|
: {}),
|
|
@@ -2432,6 +2796,15 @@ export function analyzeRepo(root, opts = {}) {
|
|
|
2432
2796
|
}
|
|
2433
2797
|
}
|
|
2434
2798
|
: {}),
|
|
2799
|
+
...(pythonDependencyEdges || pythonEndpointReachablePromoted || pythonStructuralContainersExcluded
|
|
2800
|
+
? {
|
|
2801
|
+
python_structural_recalibration: {
|
|
2802
|
+
dependency_edges: pythonDependencyEdges,
|
|
2803
|
+
endpoint_reachable_promoted: pythonEndpointReachablePromoted,
|
|
2804
|
+
structural_containers_rank_excluded: pythonStructuralContainersExcluded
|
|
2805
|
+
}
|
|
2806
|
+
}
|
|
2807
|
+
: {}),
|
|
2435
2808
|
flows,
|
|
2436
2809
|
structural_clusters: structuralClusters,
|
|
2437
2810
|
...(tsStatus.loaded.length || tsStatus.failed.length
|