sourcecode 2.5.3__py3-none-any.whl → 2.5.4__py3-none-any.whl
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.
- sourcecode/__init__.py +1 -1
- sourcecode/architecture_analyzer.py +101 -21
- sourcecode/classifier.py +4 -2
- sourcecode/detectors/java.py +27 -10
- sourcecode/reconciliation.py +201 -0
- sourcecode/repository_ir.py +255 -39
- sourcecode/spring_impact.py +157 -17
- sourcecode/summarizer.py +35 -14
- {sourcecode-2.5.3.dist-info → sourcecode-2.5.4.dist-info}/METADATA +1 -1
- {sourcecode-2.5.3.dist-info → sourcecode-2.5.4.dist-info}/RECORD +13 -12
- {sourcecode-2.5.3.dist-info → sourcecode-2.5.4.dist-info}/WHEEL +0 -0
- {sourcecode-2.5.3.dist-info → sourcecode-2.5.4.dist-info}/entry_points.txt +0 -0
- {sourcecode-2.5.3.dist-info → sourcecode-2.5.4.dist-info}/licenses/LICENSE +0 -0
sourcecode/spring_impact.py
CHANGED
|
@@ -26,6 +26,7 @@ from pathlib import Path
|
|
|
26
26
|
from typing import TYPE_CHECKING, Optional
|
|
27
27
|
|
|
28
28
|
from sourcecode.fqn_utils import normalize_owner_fqn
|
|
29
|
+
from sourcecode.reconciliation import http_handler_classes, reconcile_impact_evidence
|
|
29
30
|
from sourcecode.spring_findings import SEVERITY_ORDER, SpringFinding
|
|
30
31
|
from sourcecode.spring_model import SpringSemanticModel
|
|
31
32
|
|
|
@@ -340,6 +341,22 @@ def _recover_external_iface_callers(
|
|
|
340
341
|
# Symbol resolution
|
|
341
342
|
# ---------------------------------------------------------------------------
|
|
342
343
|
|
|
344
|
+
def _parse_coverage_gap(cir) -> Optional[dict]:
|
|
345
|
+
"""The parse_coverage analysis gap from CIR metadata, if any (P1-E).
|
|
346
|
+
|
|
347
|
+
Written by the extraction-time reconciliation rule (reconciliation.py):
|
|
348
|
+
files that declare a type but produced no symbols. Impact answers built on
|
|
349
|
+
a graph with invisible files must say so instead of asserting completeness.
|
|
350
|
+
"""
|
|
351
|
+
try:
|
|
352
|
+
for g in (getattr(cir, "metadata", None) or {}).get("analysis_gaps") or []:
|
|
353
|
+
if isinstance(g, dict) and g.get("area") == "parse_coverage":
|
|
354
|
+
return g
|
|
355
|
+
except Exception:
|
|
356
|
+
pass
|
|
357
|
+
return None
|
|
358
|
+
|
|
359
|
+
|
|
343
360
|
def _class_of(fqn: str) -> str:
|
|
344
361
|
"""Extract class FQN from a method FQN. pkg.Class#method → pkg.Class."""
|
|
345
362
|
if "#" in fqn:
|
|
@@ -581,6 +598,32 @@ def _bfs_callers(
|
|
|
581
598
|
return direct, indirect, was_truncated, self_excluded
|
|
582
599
|
|
|
583
600
|
|
|
601
|
+
def _dedupe_caller_granularity(callers: list[str]) -> list[str]:
|
|
602
|
+
"""Drop a class-level caller entry when a method entry of the same class is
|
|
603
|
+
present in the same list (P1-B3). Order preserved.
|
|
604
|
+
|
|
605
|
+
A static call binds TWO edges: the coarse class-level one from the per-file
|
|
606
|
+
scan (`X --calls--> T`, which cannot name the callee) and, since P1-B2, the
|
|
607
|
+
precise method-level one (`X#m --calls--> T#callee`). They are additive on
|
|
608
|
+
purpose — a static call in a field initializer or static block produces no
|
|
609
|
+
body fact, so only the class edge carries it and dropping it erases real
|
|
610
|
+
coupling (measured: BroadleafCurrencyUtils lost 1325→561 transitive reach and
|
|
611
|
+
all 20 affected endpoints). The cost of keeping both is that one caller is
|
|
612
|
+
reported at two granularities: `X` and `X#m` read as two callers, and
|
|
613
|
+
`len(direct_callers)` double-counts them into `_compute_risk`.
|
|
614
|
+
|
|
615
|
+
Collapsing is safe HERE and only here, because it is a view: the method entry
|
|
616
|
+
already names the class, so the set of caller *classes* is identical and no
|
|
617
|
+
edge is deleted. A class entry with no method sibling (the field-initializer
|
|
618
|
+
case) has no precise entry to defer to and is kept. Callers of both
|
|
619
|
+
granularities remain the input to endpoint and finding mapping — a class node
|
|
620
|
+
reaches every endpoint of a controller, a method node only its own — so this
|
|
621
|
+
is applied to the reported/scored lists, never to the reach computation.
|
|
622
|
+
"""
|
|
623
|
+
classes_with_methods = {_class_of(c) for c in callers if "#" in c}
|
|
624
|
+
return [c for c in callers if "#" in c or c not in classes_with_methods]
|
|
625
|
+
|
|
626
|
+
|
|
584
627
|
# ---------------------------------------------------------------------------
|
|
585
628
|
# Endpoint mapping
|
|
586
629
|
# ---------------------------------------------------------------------------
|
|
@@ -851,10 +894,22 @@ class ImpactOrchestrator:
|
|
|
851
894
|
warnings.extend(sym_warnings)
|
|
852
895
|
|
|
853
896
|
if resolution == "not_found" or not seed_fqns:
|
|
897
|
+
if not warnings:
|
|
898
|
+
warnings = [f"Symbol '{symbol}' not found in CIR."]
|
|
899
|
+
# P1-E: a not-found verdict is weaker when files failed extraction —
|
|
900
|
+
# the queried symbol may be defined in one of them.
|
|
901
|
+
_nf_gap = _parse_coverage_gap(cir)
|
|
902
|
+
if _nf_gap:
|
|
903
|
+
_nf_n = len(_nf_gap.get("files") or [])
|
|
904
|
+
warnings.append(
|
|
905
|
+
f"Parse-coverage gap (P1-E): {_nf_n} Java file(s) declare a type "
|
|
906
|
+
"but produced no symbols — the queried symbol may be defined in "
|
|
907
|
+
"one of them (see analysis_gaps)."
|
|
908
|
+
)
|
|
854
909
|
return ImpactChainResult(
|
|
855
910
|
symbol=symbol,
|
|
856
911
|
resolution="not_found",
|
|
857
|
-
analysis_warnings=warnings
|
|
912
|
+
analysis_warnings=warnings,
|
|
858
913
|
risk_level="unknown",
|
|
859
914
|
confidence="low",
|
|
860
915
|
explanation=f"Symbol {symbol!r} not found in the IR.",
|
|
@@ -953,10 +1008,17 @@ class ImpactOrchestrator:
|
|
|
953
1008
|
)
|
|
954
1009
|
|
|
955
1010
|
# ── 2. BFS through reverse graph ─────────────────────────────────
|
|
956
|
-
|
|
1011
|
+
direct_callers_raw, indirect_callers_raw, truncated, self_excluded = _bfs_callers(
|
|
957
1012
|
seed_fqns, cir.reverse_graph, depth, impl_graph=impl_graph,
|
|
958
1013
|
method_scoped=_method_scoped,
|
|
959
1014
|
)
|
|
1015
|
+
# P1-B3: the reported/scored caller lists collapse the class-vs-method
|
|
1016
|
+
# granularity duplicate that additive static edges (P1-B2) produce. The RAW
|
|
1017
|
+
# lists stay the input to endpoint/finding mapping below: a class-level node
|
|
1018
|
+
# carries reach a method node does not (all handlers of a controller), so
|
|
1019
|
+
# deduping there would erase endpoints. See _dedupe_caller_granularity.
|
|
1020
|
+
direct_callers = _dedupe_caller_granularity(direct_callers_raw)
|
|
1021
|
+
indirect_callers = _dedupe_caller_granularity(indirect_callers_raw)
|
|
960
1022
|
if self_excluded:
|
|
961
1023
|
if _method_scoped:
|
|
962
1024
|
warnings.append(
|
|
@@ -978,7 +1040,7 @@ class ImpactOrchestrator:
|
|
|
978
1040
|
confidence_reducing = True # capped traversal → result is incomplete
|
|
979
1041
|
|
|
980
1042
|
# ── 3. Endpoints affected ─────────────────────────────────────────
|
|
981
|
-
all_callers =
|
|
1043
|
+
all_callers = direct_callers_raw + indirect_callers_raw
|
|
982
1044
|
endpoints_affected = _collect_endpoints(all_callers, seed_fqns, model)
|
|
983
1045
|
|
|
984
1046
|
# ── 4. TX boundary for the target symbol ─────────────────────────
|
|
@@ -1005,7 +1067,8 @@ class ImpactOrchestrator:
|
|
|
1005
1067
|
all_findings = _run_audit_for_chain(cir, model, root)
|
|
1006
1068
|
|
|
1007
1069
|
impact_findings_raw = _filter_findings(
|
|
1008
|
-
all_findings, seed_fqns,
|
|
1070
|
+
all_findings, seed_fqns, direct_callers_raw, indirect_callers_raw,
|
|
1071
|
+
endpoints_affected,
|
|
1009
1072
|
)
|
|
1010
1073
|
# Sort by severity, then symbol
|
|
1011
1074
|
impact_findings_raw.sort(
|
|
@@ -1052,19 +1115,21 @@ class ImpactOrchestrator:
|
|
|
1052
1115
|
)
|
|
1053
1116
|
di_binding_ambiguous = _max_impl > 1
|
|
1054
1117
|
# Keep only genuinely new caller classes (not seeds / already found).
|
|
1055
|
-
_known = set(seed_fqns) | set(
|
|
1118
|
+
_known = set(seed_fqns) | set(direct_callers_raw) | set(indirect_callers_raw)
|
|
1056
1119
|
di_recovered_callers = [c for c in di_recovered_callers if c not in _known]
|
|
1057
1120
|
|
|
1058
1121
|
di_recovered = bool(di_recovered_callers)
|
|
1059
1122
|
if di_recovered:
|
|
1060
1123
|
# Merge recovered wiring sites into the chain and recompute the views
|
|
1061
1124
|
# that depend on the caller set (endpoints, findings, surfaces, risk).
|
|
1125
|
+
direct_callers_raw = direct_callers_raw + di_recovered_callers
|
|
1062
1126
|
direct_callers = direct_callers + di_recovered_callers
|
|
1063
1127
|
empty_blast = False
|
|
1064
|
-
all_callers =
|
|
1128
|
+
all_callers = direct_callers_raw + indirect_callers_raw
|
|
1065
1129
|
endpoints_affected = _collect_endpoints(all_callers, seed_fqns, model)
|
|
1066
1130
|
impact_findings_raw = _filter_findings(
|
|
1067
|
-
all_findings, seed_fqns,
|
|
1131
|
+
all_findings, seed_fqns, direct_callers_raw, indirect_callers_raw,
|
|
1132
|
+
endpoints_affected,
|
|
1068
1133
|
)
|
|
1069
1134
|
impact_findings_raw.sort(
|
|
1070
1135
|
key=lambda f: (SEVERITY_ORDER.get(f.severity, 9), f.symbol)
|
|
@@ -1170,12 +1235,76 @@ class ImpactOrchestrator:
|
|
|
1170
1235
|
"the '#method') to see the declaring class's callers."
|
|
1171
1236
|
)
|
|
1172
1237
|
|
|
1238
|
+
# P1-E: an empty blast radius computed over a graph with provably
|
|
1239
|
+
# invisible files is unproven — a caller could live in a file that
|
|
1240
|
+
# declared a type but produced no symbols (parse_coverage gap written by
|
|
1241
|
+
# the extraction-time reconciliation rule). Fires at any seed
|
|
1242
|
+
# granularity: unlike CH-003/CH-005/G-2 (class-level) and P1-B
|
|
1243
|
+
# (method-level), file invisibility undermines both equally.
|
|
1244
|
+
parse_coverage_blind_spot = False
|
|
1245
|
+
if empty_blast:
|
|
1246
|
+
_pc_gap = _parse_coverage_gap(cir)
|
|
1247
|
+
if _pc_gap:
|
|
1248
|
+
parse_coverage_blind_spot = True
|
|
1249
|
+
_pc_n = len(_pc_gap.get("files") or [])
|
|
1250
|
+
warnings.append(
|
|
1251
|
+
f"Parse-coverage gap (P1-E): {_pc_n} Java file(s) declare a type "
|
|
1252
|
+
"but produced no symbols — a caller could live in an unmodeled "
|
|
1253
|
+
"file, so an empty blast radius is NOT proof this symbol is "
|
|
1254
|
+
"unused (see analysis_gaps for the file list)."
|
|
1255
|
+
)
|
|
1256
|
+
|
|
1257
|
+
# F-1 (Evidence Reconciliation Layer) / SIM-2: the call graph and the
|
|
1258
|
+
# endpoint index are independent evidence views and can contradict each
|
|
1259
|
+
# other. keycloak simulacro: the verified caller chain contained
|
|
1260
|
+
# AuthorizationEndpoint#process while the endpoint index had NO route for
|
|
1261
|
+
# that class (a JAX-RS sub-resource locator chain the route extractor
|
|
1262
|
+
# cannot compose) — "2 endpoints exposed" was narrated at confidence=high
|
|
1263
|
+
# while the real OAuth surface was invisible. Unlike the guards above,
|
|
1264
|
+
# this fires on PARTIAL results, not just empty ones: a chain class that
|
|
1265
|
+
# provably declares HTTP handlers but has no modeled route is a
|
|
1266
|
+
# positively-detected under-report of endpoints_affected.
|
|
1267
|
+
endpoint_coverage_blind_spot = False
|
|
1268
|
+
reconciliation_findings: list[dict] = []
|
|
1269
|
+
try:
|
|
1270
|
+
_chain_classes = frozenset(
|
|
1271
|
+
_class_of(f)
|
|
1272
|
+
for f in set(seed_fqns) | set(direct_callers_raw) | set(indirect_callers_raw)
|
|
1273
|
+
)
|
|
1274
|
+
_rec = reconcile_impact_evidence(
|
|
1275
|
+
chain_classes=_chain_classes,
|
|
1276
|
+
handler_classes=http_handler_classes(cir),
|
|
1277
|
+
modeled_classes=frozenset(model.endpoint_index.controller_fqns),
|
|
1278
|
+
)
|
|
1279
|
+
reconciliation_findings = [f.to_dict() for f in _rec]
|
|
1280
|
+
_unmapped: list[str] = []
|
|
1281
|
+
for _f in _rec:
|
|
1282
|
+
if _f.rule == "endpoint_coverage":
|
|
1283
|
+
endpoint_coverage_blind_spot = True
|
|
1284
|
+
_unmapped = list(_f.symbols)
|
|
1285
|
+
if endpoint_coverage_blind_spot:
|
|
1286
|
+
warnings.append(
|
|
1287
|
+
f"Endpoint-coverage contradiction (evidence reconciliation): {len(_unmapped)} "
|
|
1288
|
+
"class(es) in the call chain declare HTTP handler methods but the "
|
|
1289
|
+
"endpoint index has no modeled route for them ["
|
|
1290
|
+
+ ", ".join(_unmapped[:5])
|
|
1291
|
+
+ (", …" if len(_unmapped) > 5 else "")
|
|
1292
|
+
+ "]. endpoints_affected UNDER-REPORTS real HTTP exposure (routes "
|
|
1293
|
+
"the extractor could not compose, e.g. JAX-RS sub-resource "
|
|
1294
|
+
"locator chains). Callers remain reliable; verify these classes' "
|
|
1295
|
+
"exposure before treating the endpoint list as complete."
|
|
1296
|
+
)
|
|
1297
|
+
except Exception:
|
|
1298
|
+
pass
|
|
1299
|
+
|
|
1173
1300
|
blind_spots = (
|
|
1174
1301
|
# framework_di stops being a blind spot once CH-007 recovers callers
|
|
1175
1302
|
(["framework_di"] if framework_di_blind_spot and not di_recovered else [])
|
|
1176
1303
|
+ (["value_type"] if value_type_blind_spot else [])
|
|
1177
1304
|
+ (["unresolved_refs"] if unresolved_ref_blind_spot else [])
|
|
1178
1305
|
+ (["method_scope_unproven"] if method_scope_blind_spot else [])
|
|
1306
|
+
+ (["parse_coverage"] if parse_coverage_blind_spot else [])
|
|
1307
|
+
+ (["endpoint_coverage_partial"] if endpoint_coverage_blind_spot else [])
|
|
1179
1308
|
)
|
|
1180
1309
|
|
|
1181
1310
|
confidence: str
|
|
@@ -1190,15 +1319,33 @@ class ImpactOrchestrator:
|
|
|
1190
1319
|
or value_type_blind_spot
|
|
1191
1320
|
or unresolved_ref_blind_spot
|
|
1192
1321
|
or method_scope_blind_spot
|
|
1322
|
+
or parse_coverage_blind_spot
|
|
1193
1323
|
):
|
|
1194
1324
|
confidence = "low"
|
|
1195
|
-
elif resolution == "partial" or confidence_reducing:
|
|
1325
|
+
elif resolution == "partial" or confidence_reducing or endpoint_coverage_blind_spot:
|
|
1326
|
+
# endpoint_coverage: the callers are still trustworthy — only the
|
|
1327
|
+
# endpoint axis is provably incomplete, so cap at medium, not low.
|
|
1196
1328
|
confidence = "medium"
|
|
1197
1329
|
else:
|
|
1198
1330
|
confidence = "high"
|
|
1199
1331
|
|
|
1200
1332
|
elapsed_ms = round((time.monotonic() - t0) * 1000, 2)
|
|
1201
1333
|
|
|
1334
|
+
explanation = _build_chain_explanation(
|
|
1335
|
+
risk_level,
|
|
1336
|
+
len(direct_callers),
|
|
1337
|
+
len(indirect_callers),
|
|
1338
|
+
len(endpoints_affected),
|
|
1339
|
+
len(impact_findings),
|
|
1340
|
+
confidence,
|
|
1341
|
+
blind_spot=bool(blind_spots),
|
|
1342
|
+
)
|
|
1343
|
+
if endpoint_coverage_blind_spot:
|
|
1344
|
+
explanation += (
|
|
1345
|
+
" HTTP exposure is under-reported: handler class(es) in the chain "
|
|
1346
|
+
"have no modeled routes (blind_spots=endpoint_coverage_partial)."
|
|
1347
|
+
)
|
|
1348
|
+
|
|
1202
1349
|
return ImpactChainResult(
|
|
1203
1350
|
symbol=resolved_symbol,
|
|
1204
1351
|
resolution=resolution,
|
|
@@ -1214,15 +1361,7 @@ class ImpactOrchestrator:
|
|
|
1214
1361
|
risk_level=risk_level,
|
|
1215
1362
|
risk_score=risk_score,
|
|
1216
1363
|
confidence=confidence,
|
|
1217
|
-
explanation=
|
|
1218
|
-
risk_level,
|
|
1219
|
-
len(direct_callers),
|
|
1220
|
-
len(indirect_callers),
|
|
1221
|
-
len(endpoints_affected),
|
|
1222
|
-
len(impact_findings),
|
|
1223
|
-
confidence,
|
|
1224
|
-
blind_spot=bool(blind_spots),
|
|
1225
|
-
),
|
|
1364
|
+
explanation=explanation,
|
|
1226
1365
|
metadata={
|
|
1227
1366
|
"analysis_depth": depth,
|
|
1228
1367
|
"callers_total": len(direct_callers) + len(indirect_callers),
|
|
@@ -1232,6 +1371,7 @@ class ImpactOrchestrator:
|
|
|
1232
1371
|
"model_build_time_ms": model.build_time_ms,
|
|
1233
1372
|
"query_time_ms": elapsed_ms,
|
|
1234
1373
|
"blind_spots": blind_spots,
|
|
1374
|
+
"reconciliation": reconciliation_findings,
|
|
1235
1375
|
"external_supertypes": external_supertypes,
|
|
1236
1376
|
"external_iface_callers_recovered": len(di_recovered_callers),
|
|
1237
1377
|
"external_iface_binding_ambiguous": di_binding_ambiguous,
|
sourcecode/summarizer.py
CHANGED
|
@@ -8,6 +8,7 @@ No API calls — templates applied directly over SourceMap.
|
|
|
8
8
|
from pathlib import Path
|
|
9
9
|
from typing import Any
|
|
10
10
|
|
|
11
|
+
from sourcecode.architecture_analyzer import _LAYER_MIN_SHARE
|
|
11
12
|
from sourcecode.detectors.parsers import load_json_file, load_toml_file
|
|
12
13
|
from sourcecode.tree_utils import safe_read_text
|
|
13
14
|
from sourcecode.entrypoint_classifier import is_production_entry_point
|
|
@@ -48,11 +49,15 @@ _ARCH_LAYER_PATTERNS: dict[str, dict[str, list[str]]] = {
|
|
|
48
49
|
"model": ["models", "entities", "domain"],
|
|
49
50
|
"view": ["views", "templates", "pages", "components"],
|
|
50
51
|
},
|
|
52
|
+
# P1-D: keep in step with architecture_analyzer.LAYER_PATTERNS — a keyword may
|
|
53
|
+
# only name a layer it attests. "api" (a public API package, not an HTTP
|
|
54
|
+
# controller layer), "store" and "db"/"database" are naming conventions that
|
|
55
|
+
# fabricated the "layered" claim in this very headline for neo4j and openmrs.
|
|
51
56
|
"layered": {
|
|
52
|
-
"controller": ["controllers", "
|
|
57
|
+
"controller": ["controllers", "routes", "handlers", "endpoints"],
|
|
53
58
|
"service": ["services", "usecases", "application"],
|
|
54
|
-
"repository": ["repositories", "repos", "
|
|
55
|
-
"infrastructure": ["infra", "infrastructure", "persistence"
|
|
59
|
+
"repository": ["repositories", "repos", "dao"],
|
|
60
|
+
"infrastructure": ["infra", "infrastructure", "persistence"],
|
|
56
61
|
},
|
|
57
62
|
"hexagonal": {
|
|
58
63
|
"port": ["ports", "interfaces"],
|
|
@@ -61,7 +66,11 @@ _ARCH_LAYER_PATTERNS: dict[str, dict[str, list[str]]] = {
|
|
|
61
66
|
},
|
|
62
67
|
"fullstack": {
|
|
63
68
|
"frontend": ["frontend", "client", "web", "ui", "pages", "components"],
|
|
64
|
-
|
|
69
|
+
# P1-D: "api" dropped — a `foo/api/` package is a public API surface, not
|
|
70
|
+
# evidence of a backend TIER. With it, openmrs (94.8% `org/openmrs/api/`)
|
|
71
|
+
# and jobrunr fell straight from a fabricated "layered" into a fabricated
|
|
72
|
+
# "fullstack" instead. Same conflation, same fix.
|
|
73
|
+
"backend": ["backend", "server", "services"],
|
|
65
74
|
},
|
|
66
75
|
}
|
|
67
76
|
|
|
@@ -390,20 +399,32 @@ class ProjectSummarizer:
|
|
|
390
399
|
return ". ".join(parts) + "."
|
|
391
400
|
|
|
392
401
|
def _detect_architecture_pattern(self, file_paths: list[str]) -> str | None:
|
|
393
|
-
"""Infer architecture pattern from directory names in file paths.
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
402
|
+
"""Infer architecture pattern from directory names in file paths.
|
|
403
|
+
|
|
404
|
+
P1-D: a layer must hold a real share of the code. Scoring on the mere
|
|
405
|
+
PRESENCE of a directory name meant one incidental dir asserted a whole
|
|
406
|
+
layer, and the claim landed in the headline: neo4j — a graph engine with no
|
|
407
|
+
controller, repository or infrastructure directory anywhere — read "Library
|
|
408
|
+
in Java (JAX-RS), layered". Presence does not scale, so the largest repos
|
|
409
|
+
were the most confidently mislabelled. Same gate and threshold as
|
|
410
|
+
ArchitectureAnalyzer, which answers this question for the architecture prose.
|
|
411
|
+
"""
|
|
412
|
+
dir_files: dict[str, set[int]] = {}
|
|
413
|
+
for i, p in enumerate(file_paths):
|
|
414
|
+
for seg in p.replace("\\", "/").split("/")[:-1]:
|
|
415
|
+
dir_files.setdefault(seg.lower(), set()).add(i)
|
|
416
|
+
total = len(file_paths)
|
|
417
|
+
|
|
418
|
+
def _has_mass(keywords: list[str]) -> bool:
|
|
419
|
+
files: set[int] = set()
|
|
420
|
+
for d in keywords:
|
|
421
|
+
files |= dir_files.get(d, set())
|
|
422
|
+
return bool(files) and (not total or len(files) / total >= _LAYER_MIN_SHARE)
|
|
399
423
|
|
|
400
424
|
best_pattern: str | None = None
|
|
401
425
|
best_score = 0
|
|
402
426
|
for pattern_name, layer_keys in _ARCH_LAYER_PATTERNS.items():
|
|
403
|
-
score = sum(
|
|
404
|
-
1 for keywords in layer_keys.values()
|
|
405
|
-
if any(d in keywords for d in dir_names)
|
|
406
|
-
)
|
|
427
|
+
score = sum(1 for keywords in layer_keys.values() if _has_mass(keywords))
|
|
407
428
|
if score > best_score:
|
|
408
429
|
best_score = score
|
|
409
430
|
best_pattern = pattern_name
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
sourcecode/__init__.py,sha256=
|
|
1
|
+
sourcecode/__init__.py,sha256=XumauaLNdzXEwNGsJupjZthcLY2Qo8ud3CgsBguv8-o,308
|
|
2
2
|
sourcecode/adaptive_scanner.py,sha256=XffluXKzJUXrMtjEiAOnSNPZnztdIcts17T9ouHeID0,10521
|
|
3
3
|
sourcecode/archetype.py,sha256=d7yoN6Tj4OBj-VgSMPEKg4WF9H8FypnZ5A6AkUh5oIE,34484
|
|
4
|
-
sourcecode/architecture_analyzer.py,sha256=
|
|
4
|
+
sourcecode/architecture_analyzer.py,sha256=CoxlEDWn1axufjIT4fpNHd_2HNUKgqBClq9z9k6jp4M,51684
|
|
5
5
|
sourcecode/architecture_summary.py,sha256=pkl6lIOnSy-poa9EzW43U0MkXhx2FDfAFh8pDXLpcTo,26630
|
|
6
6
|
sourcecode/ast_extractor.py,sha256=FEsYTsMCXbJfSYQZRk1k-I-PjUNGnKqO1IxMftsgA5U,51351
|
|
7
7
|
sourcecode/cache.py,sha256=1V3vsaODAa2UBJAC0xpvxpmRdriCezQx5Q8JCcfgziE,31892
|
|
@@ -9,7 +9,7 @@ sourcecode/call_surface.py,sha256=fiqYfHooxN1fX9oQoysq1LS3LoZcobhyUNjAGEZKpwk,41
|
|
|
9
9
|
sourcecode/caller_metrics.py,sha256=HG8RCOkpzzsEGdnMRob6byrwjoj5__t0TEsBj4yR7ks,2574
|
|
10
10
|
sourcecode/canonical_ir.py,sha256=5hLAhhSpXNOdZAvPt-J-rXccJE6M3A6gVhmhU0Tym6Y,29269
|
|
11
11
|
sourcecode/cir_graphs.py,sha256=9G0HHj1kw2325IDyzo2OpX73BNswEckecf4MZUXB4JM,12078
|
|
12
|
-
sourcecode/classifier.py,sha256=
|
|
12
|
+
sourcecode/classifier.py,sha256=9olDN5joeKHcwG9vf73X-t_son16hLVNib0rBbqk1vc,18865
|
|
13
13
|
sourcecode/cli.py,sha256=46Chp0JsEWD_j0HUWOx4tOhrsXSWgWV8JhL5G8SyW4I,322078
|
|
14
14
|
sourcecode/code_notes_analyzer.py,sha256=EJemNCNc9Dn-1RZYu-aNbK0ELzmsyC4s6FdHi3XyNEI,9392
|
|
15
15
|
sourcecode/confidence_analyzer.py,sha256=_jckZSxksV-OU38vbkxfVNBnWCtlCq8Vwfg23x1uspA,19054
|
|
@@ -51,11 +51,12 @@ sourcecode/pr_impact.py,sha256=dCDVw83EDbyVf6F9ZmEQmsFz8ruVH7d4mpeKQCIZHM0,16805
|
|
|
51
51
|
sourcecode/prepare_context.py,sha256=PYygAKL_jwz8gGElgM9mroUSih18UKSSjv08ndfl4aA,222877
|
|
52
52
|
sourcecode/progress.py,sha256=qn30sWaHOkjTgXsSBmiPkz7Rsbwc5oSlIe6JNEMYp_k,3149
|
|
53
53
|
sourcecode/ranking_engine.py,sha256=ZAucq_YX2KkWUuAZf4P0lhtQ_38vEFnUhuGtSZd1S0E,12970
|
|
54
|
+
sourcecode/reconciliation.py,sha256=kW8PyB_R7J2Ig0N_QP9Jdm1fQXM1Ebf3PZbaTc80ULA,7941
|
|
54
55
|
sourcecode/redactor.py,sha256=SB4hwIvg8h-hvcqKcDWaZvA-aSyn-at-BIRwa0tUv5E,3227
|
|
55
56
|
sourcecode/relevance_scorer.py,sha256=0AgEt4KrV73nioMqBgjhGjtY7L2C7L7cSyKtj3IKcrw,9408
|
|
56
57
|
sourcecode/rename_refactor.py,sha256=h6dNFlB9aZ_3q6heeHBkgXQeXaT03nvPSsYH6P8qxFg,12965
|
|
57
58
|
sourcecode/repo_classifier.py,sha256=FG1vaWKdWXsWdl-S8hjVMiTqcwgaRXkDyvK4rPcOGtQ,22681
|
|
58
|
-
sourcecode/repository_ir.py,sha256=
|
|
59
|
+
sourcecode/repository_ir.py,sha256=F6lRgPoP8oTtnGpRgjoLw-dlxZRXH7XOcehtcNwufEY,309550
|
|
59
60
|
sourcecode/ris.py,sha256=aG2D4159gtpg968yD3PylYbIXhGFOwIlFBI0DSr84yY,20412
|
|
60
61
|
sourcecode/runtime_classifier.py,sha256=uTAD6BDCiBLUZEDRfqk718kM4RTT_vAbfkcOI2_Xx58,18432
|
|
61
62
|
sourcecode/scanner.py,sha256=WdOQ78mMzjR1NjmKTlbxdgwinnCTfAhxCVLBEFQiFHU,8899
|
|
@@ -69,12 +70,12 @@ sourcecode/semantic_services.py,sha256=nbUuPv-F01USTt_9CHT8iy_ucCIw3fz4W3Aquea_p
|
|
|
69
70
|
sourcecode/serializer.py,sha256=FmQo7eojA_z3ptLW-Vgze5mrEJ6FabcZqiwbQWWTgFI,130056
|
|
70
71
|
sourcecode/spring_event_topology.py,sha256=5_ON_21Le5zbG-1GRc5GLIi5HJfy_QjcXLVPC5WeUGQ,18055
|
|
71
72
|
sourcecode/spring_findings.py,sha256=EX7kLZLN74CFyR9iZPm3CI115BfFKNd4WRPuErNljZM,5729
|
|
72
|
-
sourcecode/spring_impact.py,sha256=
|
|
73
|
+
sourcecode/spring_impact.py,sha256=ayZtHZ2YCRSlZpKBACoM9M7tW0K1W_C0r8SGj3R-4vU,70603
|
|
73
74
|
sourcecode/spring_model.py,sha256=zOAgFmrRbG4a6KLm1TJl55aWMyPNsz3OS3FSczqPG6A,16594
|
|
74
75
|
sourcecode/spring_security_audit.py,sha256=Rk-aSohezdc7YDYbSoJquVnwpkDB8ty1BCD-4Hc4R5A,22832
|
|
75
76
|
sourcecode/spring_semantic.py,sha256=jteQ1PkY9ArFJv0embg_jBIdbOxqrk9mQ2Xz8OF_FKA,14214
|
|
76
77
|
sourcecode/spring_tx_analyzer.py,sha256=QMXkxa_hrqDVN3D8WJsd0nT7vfdZCT1E8ZWNoFFbvgU,34692
|
|
77
|
-
sourcecode/summarizer.py,sha256=
|
|
78
|
+
sourcecode/summarizer.py,sha256=BaBO0WKQi4fX2b2Xjip-GLEZwI3PYiV_U9jmBQlrfFI,25060
|
|
78
79
|
sourcecode/tree_utils.py,sha256=8GAkIfQAsvtEudIeW1l4ooH_oRtrWR8cpJQJsEa_Pfw,2093
|
|
79
80
|
sourcecode/type_usage_surface.py,sha256=51IrKRQoIoRnlsiDjHnqpJBn2rc6E59aRhgS0HTzAF0,4428
|
|
80
81
|
sourcecode/validation_inference.py,sha256=UtmYv-d4TEzT1ihGEipAhtXDrwq0XtVtjMpb3i7NsNc,10815
|
|
@@ -90,7 +91,7 @@ sourcecode/detectors/elixir.py,sha256=jCpvt5Yi6jvplc80ovRtWh17q-11ZGo9qX7o8b57TJ
|
|
|
90
91
|
sourcecode/detectors/go.py,sha256=2r66uRQfeTWsqxr4HDhT6vExZErby0t46QXLHVBRv9w,2782
|
|
91
92
|
sourcecode/detectors/heuristic.py,sha256=7cRxrip4yIaggYzZJB6ef8yHKh-gHgiH_pXMFcjlyFU,3723
|
|
92
93
|
sourcecode/detectors/hybrid.py,sha256=IGFRUVsAZ1ooRlFdznCeJAV6vy1yVDx-VyghvLtddXc,9101
|
|
93
|
-
sourcecode/detectors/java.py,sha256=
|
|
94
|
+
sourcecode/detectors/java.py,sha256=ut8czxGzhFUxRAS5LeG78wev8kJolC7LG8IESDJZTNk,42349
|
|
94
95
|
sourcecode/detectors/jvm_ext.py,sha256=EgHJ5W8EE-ZTN9V607mVzohyKgZE8Mc2jCi-DF8RAZU,2616
|
|
95
96
|
sourcecode/detectors/nodejs.py,sha256=Hg3Gmr7yIMJFiLoDwOTk2wtu00wxIs6kZf-oQujTFUA,13187
|
|
96
97
|
sourcecode/detectors/parsers.py,sha256=ug9K31tyHqinmv0HkIVQVjdTZpBv67FYKAEf52YXOSM,3178
|
|
@@ -136,8 +137,8 @@ sourcecode/telemetry/consent.py,sha256=LIAO9ohJZF8OuZwM4u1VWtALlYfTCCKq4wV3Vwc7i
|
|
|
136
137
|
sourcecode/telemetry/events.py,sha256=LtzYfaX9Ilckj5PTvAcTpDa9mLqDsYPDUiDkRa58piY,2580
|
|
137
138
|
sourcecode/telemetry/filters.py,sha256=NHa5T-6DaZduQPFuC34jOqHWQgSizM-Ygq8aZ4j19ng,5834
|
|
138
139
|
sourcecode/telemetry/transport.py,sha256=4gGHsq0WeY9VywEZXA3vUxykfiYnw9uuqfjAAec7F8o,1681
|
|
139
|
-
sourcecode-2.5.
|
|
140
|
-
sourcecode-2.5.
|
|
141
|
-
sourcecode-2.5.
|
|
142
|
-
sourcecode-2.5.
|
|
143
|
-
sourcecode-2.5.
|
|
140
|
+
sourcecode-2.5.4.dist-info/METADATA,sha256=vFyQq0a39iemrb04OqC6TwsnM_xA7j6PTBz_lbEccTM,10851
|
|
141
|
+
sourcecode-2.5.4.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
142
|
+
sourcecode-2.5.4.dist-info/entry_points.txt,sha256=-JEAdChrK5We51kZcb7OaDcyil-dHBjBPL-NhuO-QY8,89
|
|
143
|
+
sourcecode-2.5.4.dist-info/licenses/LICENSE,sha256=7DdHrU9Z_3e7dSvq4ISijZNjnuHo5NIHNiHDouMQ9JU,10491
|
|
144
|
+
sourcecode-2.5.4.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|