sourcecode 5.5.0__py3-none-any.whl → 5.5.2__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.
Potentially problematic release.
This version of sourcecode might be problematic. Click here for more details.
- sourcecode/__init__.py +1 -1
- sourcecode/_docs/USER_GUIDE.md +1 -1
- sourcecode/cli.py +73 -6
- sourcecode/dependency_analyzer.py +15 -1
- sourcecode/manifest_cache.py +62 -0
- sourcecode/migrate_check.py +20 -0
- sourcecode/reference_facts.py +13 -1
- sourcecode/release_info.py +1 -1
- sourcecode/repository_ir.py +98 -11
- sourcecode/risk.py +25 -0
- sourcecode/servlet_surface.py +10 -3
- sourcecode/validation_surface.py +7 -11
- {sourcecode-5.5.0.dist-info → sourcecode-5.5.2.dist-info}/METADATA +3 -3
- {sourcecode-5.5.0.dist-info → sourcecode-5.5.2.dist-info}/RECORD +17 -16
- {sourcecode-5.5.0.dist-info → sourcecode-5.5.2.dist-info}/WHEEL +0 -0
- {sourcecode-5.5.0.dist-info → sourcecode-5.5.2.dist-info}/entry_points.txt +0 -0
- {sourcecode-5.5.0.dist-info → sourcecode-5.5.2.dist-info}/licenses/LICENSE +0 -0
sourcecode/__init__.py
CHANGED
sourcecode/_docs/USER_GUIDE.md
CHANGED
sourcecode/cli.py
CHANGED
|
@@ -3125,6 +3125,7 @@ def main(
|
|
|
3125
3125
|
raise typer.Exit()
|
|
3126
3126
|
|
|
3127
3127
|
_t0 = time.monotonic()
|
|
3128
|
+
_root_timing: dict[str, float] = {}
|
|
3128
3129
|
no_tree: bool = False # set True by --agent; --no-tree flag removed
|
|
3129
3130
|
|
|
3130
3131
|
# Validate new flag choices
|
|
@@ -3457,6 +3458,9 @@ def main(
|
|
|
3457
3458
|
_core_hash = ""
|
|
3458
3459
|
_core_flags_str = ""
|
|
3459
3460
|
_view_flags_str = ""
|
|
3461
|
+
# Reuse the freshness decision for cache metadata; otherwise the root
|
|
3462
|
+
# command repeats a full Git status walk after the analysis completes.
|
|
3463
|
+
_tree_is_dirty: Optional[bool] = None
|
|
3460
3464
|
|
|
3461
3465
|
if not no_cache:
|
|
3462
3466
|
try:
|
|
@@ -3506,6 +3510,8 @@ def main(
|
|
|
3506
3510
|
Path(_git_root_str) if _git_root_str else target,
|
|
3507
3511
|
scope=target,
|
|
3508
3512
|
)
|
|
3513
|
+
if _tree_sig and _git_sha:
|
|
3514
|
+
_tree_is_dirty = _tree_sig != _git_sha
|
|
3509
3515
|
if _tree_sig:
|
|
3510
3516
|
_core_key = f"{_tree_sig}-{_core_h}"
|
|
3511
3517
|
else:
|
|
@@ -3821,7 +3827,11 @@ def main(
|
|
|
3821
3827
|
if format == "json":
|
|
3822
3828
|
try:
|
|
3823
3829
|
from sourcecode.ris import _has_uncommitted_changes as _huc
|
|
3824
|
-
_uncommitted =
|
|
3830
|
+
_uncommitted = (
|
|
3831
|
+
_tree_is_dirty
|
|
3832
|
+
if _tree_is_dirty is not None
|
|
3833
|
+
else _huc(target)
|
|
3834
|
+
)
|
|
3825
3835
|
except Exception:
|
|
3826
3836
|
_uncommitted = False
|
|
3827
3837
|
_data_scope = "COMPACT" if compact else ("AGENT" if agent else "FULL")
|
|
@@ -3938,6 +3948,7 @@ def main(
|
|
|
3938
3948
|
if _advisory:
|
|
3939
3949
|
typer.echo(_advisory, err=True)
|
|
3940
3950
|
|
|
3951
|
+
_manifest_t0 = time.perf_counter()
|
|
3941
3952
|
_perf_detection = perf.start()
|
|
3942
3953
|
detector = ProjectDetector(build_default_detectors())
|
|
3943
3954
|
workspace_analysis = WorkspaceAnalyzer().analyze(target, manifests)
|
|
@@ -3998,6 +4009,7 @@ def main(
|
|
|
3998
4009
|
root_dependencies, root_summary = dependency_analyzer.analyze(target)
|
|
3999
4010
|
dependency_records.extend(root_dependencies)
|
|
4000
4011
|
dependency_summaries.append(root_summary)
|
|
4012
|
+
_root_timing["manifests_ms"] = round((time.perf_counter() - _manifest_t0) * 1000, 2)
|
|
4001
4013
|
module_graphs = []
|
|
4002
4014
|
if graph_analyzer is not None:
|
|
4003
4015
|
root_graph_tree = (
|
|
@@ -4643,6 +4655,13 @@ def main(
|
|
|
4643
4655
|
)
|
|
4644
4656
|
|
|
4645
4657
|
perf.stop("analysis", _perf_analysis)
|
|
4658
|
+
# Root timings are deliberately a small diagnostic envelope. They make cold
|
|
4659
|
+
# manifest cost visible even when the optional perf recorder is disabled.
|
|
4660
|
+
_root_timing.setdefault("context_ms", round((time.monotonic() - _t0) * 1000, 2))
|
|
4661
|
+
_root_timing.setdefault("parse_ms", 0.0)
|
|
4662
|
+
_root_timing.setdefault("cir_ms", 0.0)
|
|
4663
|
+
_root_timing.setdefault("ris_ms", 0.0)
|
|
4664
|
+
_root_timing.setdefault("snapshot_ms", 0.0)
|
|
4646
4665
|
# 4. Serialize
|
|
4647
4666
|
_progress.update("serializing output")
|
|
4648
4667
|
_perf_serialize = perf.start()
|
|
@@ -4688,8 +4707,16 @@ def main(
|
|
|
4688
4707
|
raw_dict = standard_view(sm, include_tree=tree and not no_tree)
|
|
4689
4708
|
if not no_redact:
|
|
4690
4709
|
raw_dict = redact_dict(raw_dict)
|
|
4710
|
+
data = raw_dict
|
|
4691
4711
|
content = _serialize_dict(raw_dict, format)
|
|
4692
4712
|
|
|
4713
|
+
if isinstance(data, dict):
|
|
4714
|
+
data.setdefault("_meta", {})["timing_ms"] = {
|
|
4715
|
+
**_root_timing,
|
|
4716
|
+
"total_ms": round((time.monotonic() - _t0) * 1000, 2),
|
|
4717
|
+
}
|
|
4718
|
+
content = _serialize_dict(data, format)
|
|
4719
|
+
|
|
4693
4720
|
perf.stop("serialize", _perf_serialize)
|
|
4694
4721
|
perf.flush_recorder()
|
|
4695
4722
|
|
|
@@ -4717,7 +4744,11 @@ def main(
|
|
|
4717
4744
|
if format == "json":
|
|
4718
4745
|
try:
|
|
4719
4746
|
from sourcecode.ris import _has_uncommitted_changes as _huc_fresh
|
|
4720
|
-
_uncommitted_fresh =
|
|
4747
|
+
_uncommitted_fresh = (
|
|
4748
|
+
_tree_is_dirty
|
|
4749
|
+
if _tree_is_dirty is not None
|
|
4750
|
+
else _huc_fresh(target)
|
|
4751
|
+
)
|
|
4721
4752
|
# _huc_fresh uses --untracked-files=no — it misses repos where all
|
|
4722
4753
|
# files are untracked (no staged/unstaged changes to tracked files).
|
|
4723
4754
|
# Promote to True when _allowed_changed_files contains untracked files.
|
|
@@ -6503,6 +6534,10 @@ def endpoints_cmd(
|
|
|
6503
6534
|
"the endpoint count, and the endpoint total is carried across as one "
|
|
6504
6535
|
"number rather than 3 500 rows.",
|
|
6505
6536
|
),
|
|
6537
|
+
include_tests: bool = typer.Option(
|
|
6538
|
+
False, "--include-tests",
|
|
6539
|
+
help="Include endpoints from test source and test-fixture modules; excluded by default.",
|
|
6540
|
+
),
|
|
6506
6541
|
client_usage: bool = typer.Option(
|
|
6507
6542
|
False, "--client-usage",
|
|
6508
6543
|
help="Join the routes against the HTTP calls written in the TypeScript/"
|
|
@@ -6563,7 +6598,7 @@ def endpoints_cmd(
|
|
|
6563
6598
|
_prog = Progress()
|
|
6564
6599
|
_prog.start("scanning endpoints")
|
|
6565
6600
|
try:
|
|
6566
|
-
data = _extract_java_endpoints(target)
|
|
6601
|
+
data = _extract_java_endpoints(target, include_tests=include_tests)
|
|
6567
6602
|
finally:
|
|
6568
6603
|
_prog.finish()
|
|
6569
6604
|
|
|
@@ -8989,9 +9024,26 @@ def spring_audit_cmd(
|
|
|
8989
9024
|
# 9,53 s on a 1 303-file repository) and it is the same parse the other
|
|
8990
9025
|
# knowledge commands share, so it is fetched rather than rebuilt — which
|
|
8991
9026
|
# is what makes a warm worth anything here (cold == warm before C3-94).
|
|
8992
|
-
|
|
8993
|
-
|
|
9027
|
+
_repo_root = _ctxcache.repo_root_of(target)
|
|
9028
|
+
cir, _cir_lookup = _ctxcache.get_or_build_cir(
|
|
9029
|
+
_repo_root, target, file_list, progress=_work_sink(_prog)
|
|
8994
9030
|
)
|
|
9031
|
+
_cache_state = {
|
|
9032
|
+
"context": "warm" if _cir_lookup.hit else "cold",
|
|
9033
|
+
"parse": "warm" if _cir_lookup.hit else "cold",
|
|
9034
|
+
}
|
|
9035
|
+
try:
|
|
9036
|
+
from sourcecode import cache as _cache_state_mod
|
|
9037
|
+
_cache_status = _cache_state_mod.status(target)
|
|
9038
|
+
_cache_state["snapshot"] = (
|
|
9039
|
+
"warm" if _cache_status.get("cached_cores") else "cold"
|
|
9040
|
+
)
|
|
9041
|
+
_cache_state["ris"] = (
|
|
9042
|
+
"warm" if _cache_status.get("ris_exists") else "cold"
|
|
9043
|
+
)
|
|
9044
|
+
except Exception:
|
|
9045
|
+
_cache_state["snapshot"] = "unknown"
|
|
9046
|
+
_cache_state["ris"] = "unknown"
|
|
8995
9047
|
# C3-85: the stretch between the last `linking` tick and the first rule
|
|
8996
9048
|
# tick reports nothing, and what stays on the line is `n/n` — a counter
|
|
8997
9049
|
# that finished, reading as a stage still running. Measured on keycloak
|
|
@@ -9142,7 +9194,18 @@ def spring_audit_cmd(
|
|
|
9142
9194
|
"phase that never ran is not evidence there is none."
|
|
9143
9195
|
)
|
|
9144
9196
|
|
|
9145
|
-
|
|
9197
|
+
_bounded_default = (
|
|
9198
|
+
not compact and output_path is None and format == "json" and not table
|
|
9199
|
+
)
|
|
9200
|
+
data = (
|
|
9201
|
+
combined.to_compact_dict()
|
|
9202
|
+
if compact or _bounded_default
|
|
9203
|
+
else combined.to_dict()
|
|
9204
|
+
)
|
|
9205
|
+
if _bounded_default:
|
|
9206
|
+
data.pop("mode", None)
|
|
9207
|
+
data["default_output"] = "bounded"
|
|
9208
|
+
data.setdefault("metadata", {})["cache_layers"] = _cache_state
|
|
9146
9209
|
if _stopped_early:
|
|
9147
9210
|
# C3-87: the block is composed by `PhasedRun.status`, which is the
|
|
9148
9211
|
# authority `risk` is now bound to as well — one vocabulary for
|
|
@@ -9176,6 +9239,7 @@ def spring_audit_cmd(
|
|
|
9176
9239
|
elif format == "github-comment":
|
|
9177
9240
|
output = _render_spring_audit_github_comment(combined, min_severity)
|
|
9178
9241
|
else:
|
|
9242
|
+
data = combined.to_compact_dict() if compact else combined.to_dict()
|
|
9179
9243
|
output = _serialize_dict(data, format)
|
|
9180
9244
|
|
|
9181
9245
|
# F-AV: our own readers ask for the count under either spelling — the break
|
|
@@ -9763,6 +9827,9 @@ def risk_cmd(
|
|
|
9763
9827
|
data, rule_filter=rule_filter, band_filter=band_filter, top_n=top_n
|
|
9764
9828
|
)
|
|
9765
9829
|
else:
|
|
9830
|
+
if output_path is None:
|
|
9831
|
+
from sourcecode.risk import compact_risk_output
|
|
9832
|
+
data = compact_risk_output(data)
|
|
9766
9833
|
output = _serialize_dict(data, format)
|
|
9767
9834
|
_emit_command_output(
|
|
9768
9835
|
output,
|
|
@@ -162,6 +162,15 @@ class DependencyAnalyzer:
|
|
|
162
162
|
*,
|
|
163
163
|
workspace: str | None = None,
|
|
164
164
|
) -> tuple[list[DependencyRecord], DependencySummary]:
|
|
165
|
+
from sourcecode import manifest_cache
|
|
166
|
+
|
|
167
|
+
cached = manifest_cache.load(root)
|
|
168
|
+
if cached is not None:
|
|
169
|
+
records, summary = cached
|
|
170
|
+
if workspace is not None:
|
|
171
|
+
records = [replace(record, workspace=workspace) for record in records]
|
|
172
|
+
summary = replace(summary, dependencies=records)
|
|
173
|
+
return records, summary
|
|
165
174
|
records: list[DependencyRecord] = []
|
|
166
175
|
limitations: list[str] = []
|
|
167
176
|
|
|
@@ -185,7 +194,12 @@ class DependencyAnalyzer:
|
|
|
185
194
|
replace(r, role=_infer_role(r.name, r.ecosystem, r.scope))
|
|
186
195
|
for r in deduped
|
|
187
196
|
]
|
|
188
|
-
|
|
197
|
+
summary = self._build_summary(deduped, limitations)
|
|
198
|
+
manifest_cache.save(root, deduped, summary)
|
|
199
|
+
if workspace is not None:
|
|
200
|
+
deduped = [replace(record, workspace=workspace) for record in deduped]
|
|
201
|
+
summary = replace(summary, dependencies=deduped)
|
|
202
|
+
return deduped, summary
|
|
189
203
|
|
|
190
204
|
def merge_summaries(self, summaries: Iterable[DependencySummary]) -> DependencySummary:
|
|
191
205
|
result = DependencySummary(requested=True)
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""Content-addressed cache for manifest dependency analysis."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import hashlib
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
from dataclasses import asdict
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
from sourcecode.schema import DependencyRecord, DependencySummary
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _base() -> Path:
|
|
14
|
+
configured = os.environ.get("SOURCECODE_CACHE_DIR", "")
|
|
15
|
+
return Path(configured) if configured else Path.home() / ".sourcecode" / "cache"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _key(root: Path) -> str:
|
|
19
|
+
digest = hashlib.sha256()
|
|
20
|
+
analyzer = Path(__file__).with_name("dependency_analyzer.py")
|
|
21
|
+
digest.update(analyzer.read_bytes())
|
|
22
|
+
names = {
|
|
23
|
+
"pom.xml", "build.gradle", "build.gradle.kts", "package.json",
|
|
24
|
+
"package-lock.json", "pyproject.toml", "Cargo.toml", "go.mod",
|
|
25
|
+
"composer.json", "Gemfile",
|
|
26
|
+
}
|
|
27
|
+
paths: set[Path] = set()
|
|
28
|
+
for name in names:
|
|
29
|
+
paths.update(root.rglob(name))
|
|
30
|
+
for path in sorted(paths):
|
|
31
|
+
if not path.is_file():
|
|
32
|
+
continue
|
|
33
|
+
try:
|
|
34
|
+
digest.update(path.relative_to(root).as_posix().encode())
|
|
35
|
+
digest.update(path.read_bytes())
|
|
36
|
+
except OSError:
|
|
37
|
+
continue
|
|
38
|
+
return digest.hexdigest()
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def load(root: Path) -> tuple[list[DependencyRecord], DependencySummary] | None:
|
|
42
|
+
try:
|
|
43
|
+
path = _base() / "manifest-cache-v1" / f"{_key(root)}.json"
|
|
44
|
+
data = json.loads(path.read_text(encoding="utf-8"))
|
|
45
|
+
records = [DependencyRecord(**row) for row in data["records"]]
|
|
46
|
+
summary = DependencySummary(**data["summary"])
|
|
47
|
+
summary.dependencies = records
|
|
48
|
+
return records, summary
|
|
49
|
+
except (OSError, ValueError, KeyError, TypeError):
|
|
50
|
+
return None
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def save(root: Path, records: list[DependencyRecord], summary: DependencySummary) -> None:
|
|
54
|
+
try:
|
|
55
|
+
path = _base() / "manifest-cache-v1" / f"{_key(root)}.json"
|
|
56
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
57
|
+
payload = {"records": [asdict(row) for row in records], "summary": asdict(summary)}
|
|
58
|
+
tmp = path.with_suffix(".tmp")
|
|
59
|
+
tmp.write_text(json.dumps(payload, sort_keys=True), encoding="utf-8")
|
|
60
|
+
tmp.replace(path)
|
|
61
|
+
except OSError:
|
|
62
|
+
return
|
sourcecode/migrate_check.py
CHANGED
|
@@ -2375,6 +2375,26 @@ class MigrationReport:
|
|
|
2375
2375
|
compact, "findings", top_n,
|
|
2376
2376
|
key=lambda f: SEVERITY_ORDER.get(f.get("severity"), 3),
|
|
2377
2377
|
)
|
|
2378
|
+
# Keep invariant rule prose once; each finding carries only location and
|
|
2379
|
+
# rule identity. Full prose remains available in the non-compact report.
|
|
2380
|
+
_rule_catalog: dict[str, dict] = {}
|
|
2381
|
+
_lean_findings: list[dict] = []
|
|
2382
|
+
for _finding in compact.get("findings", []):
|
|
2383
|
+
_rule_id = _finding.get("rule_id", "unknown")
|
|
2384
|
+
_rule_catalog.setdefault(_rule_id, {
|
|
2385
|
+
"title": _finding.get("title", ""),
|
|
2386
|
+
"explanation": _finding.get("explanation", ""),
|
|
2387
|
+
"fix_hint": _finding.get("fix_hint", ""),
|
|
2388
|
+
})
|
|
2389
|
+
_lean_findings.append({
|
|
2390
|
+
key: _finding[key]
|
|
2391
|
+
for key in (
|
|
2392
|
+
"id", "rule_id", "severity", "source_file", "first_line",
|
|
2393
|
+
"migration_target", "imports_found", "code_context",
|
|
2394
|
+
) if key in _finding
|
|
2395
|
+
})
|
|
2396
|
+
compact["rule_catalog"] = _rule_catalog
|
|
2397
|
+
compact["findings"] = _lean_findings
|
|
2378
2398
|
hib = full.get("hibernate")
|
|
2379
2399
|
if isinstance(hib, dict):
|
|
2380
2400
|
h = dict(hib)
|
sourcecode/reference_facts.py
CHANGED
|
@@ -223,6 +223,18 @@ def _scan_repository(
|
|
|
223
223
|
for fqn in by_qualified[hit]:
|
|
224
224
|
if fqn not in nested_hits and nested[fqn][1] != rel:
|
|
225
225
|
nested_hits[fqn] = rel
|
|
226
|
+
# Nested types are often used unqualified by their enclosing
|
|
227
|
+
# class (`private Operation operation` or `Map<String, DATATYPE>`).
|
|
228
|
+
# The declaration is not a reference; a second occurrence is.
|
|
229
|
+
for fqn, (qualified, owner_file) in nested.items():
|
|
230
|
+
if owner_file != rel or fqn in nested_hits:
|
|
231
|
+
continue
|
|
232
|
+
nested_name = qualified.rsplit(".", 1)[-1]
|
|
233
|
+
unqualified = re.findall(
|
|
234
|
+
rf"(?<!\.)\b{re.escape(nested_name)}\b", text
|
|
235
|
+
)
|
|
236
|
+
if len(unqualified) > 1:
|
|
237
|
+
nested_hits[fqn] = rel
|
|
226
238
|
scan_java = len(nested_hits) < len(nested)
|
|
227
239
|
if not scan_config and not scan_java:
|
|
228
240
|
return config_hits, nested_hits, literal_hits
|
|
@@ -311,7 +323,7 @@ def analyze_type_references(nodes: "list[dict]", root: Path) -> ReferenceFacts:
|
|
|
311
323
|
entries.append(TypeReference(
|
|
312
324
|
status=REFERENCED,
|
|
313
325
|
basis=(
|
|
314
|
-
f"
|
|
326
|
+
f"referenced by its enclosing source in {nested_hits[fqn]}"
|
|
315
327
|
if fqn in nested_hits
|
|
316
328
|
else f"referenced by a class literal in {literal_refs[fqn]}"
|
|
317
329
|
),
|
sourcecode/release_info.py
CHANGED
|
@@ -27,7 +27,7 @@ from typing import Any, Optional
|
|
|
27
27
|
#: The date `__version__` was released, from that version's CHANGELOG entry.
|
|
28
28
|
#: `tests/test_release_info.py` fails the build if the two disagree, so this is
|
|
29
29
|
#: a copy of a fact rather than a second authority for it.
|
|
30
|
-
RELEASE_DATE = "2026-08-
|
|
30
|
+
RELEASE_DATE = "2026-08-13"
|
|
31
31
|
|
|
32
32
|
#: Releases per day, measured over the 30 releases before this one (the same
|
|
33
33
|
#: computation the test re-runs against the CHANGELOG). Stable across window
|
sourcecode/repository_ir.py
CHANGED
|
@@ -6362,10 +6362,13 @@ def is_test_source_file(rel_path: str) -> bool:
|
|
|
6362
6362
|
# Convenience: find Java files in a repo
|
|
6363
6363
|
# ---------------------------------------------------------------------------
|
|
6364
6364
|
|
|
6365
|
-
def extract_java_endpoints(
|
|
6365
|
+
def extract_java_endpoints(
|
|
6366
|
+
root: Path, *, include_tests: bool = False
|
|
6367
|
+
) -> "dict[str, Any]":
|
|
6366
6368
|
"""Extract REST endpoint surface from Java source files.
|
|
6367
6369
|
|
|
6368
6370
|
Canonical endpoint extractor — uses IR symbol extraction + _build_route_surface().
|
|
6371
|
+
Test-fixture modules remain auditable by default and can be included explicitly.
|
|
6369
6372
|
Security extraction delegated to _route_security_from_sym (single source of truth);
|
|
6370
6373
|
results stored in route["security_annotations"] by _build_route_surface.
|
|
6371
6374
|
|
|
@@ -6408,7 +6411,7 @@ def extract_java_endpoints(root: Path) -> "dict[str, Any]":
|
|
|
6408
6411
|
|
|
6409
6412
|
java_files = sorted(
|
|
6410
6413
|
p for p in root.rglob("*.java")
|
|
6411
|
-
if not is_test_source_file(_rel(p))
|
|
6414
|
+
if (include_tests or not is_test_source_file(_rel(p)))
|
|
6412
6415
|
and "target/" not in str(p).replace("\\", "/")
|
|
6413
6416
|
and not any(f in str(p).replace("\\", "/") for f in _CLIENT_PATH_FRAGMENTS)
|
|
6414
6417
|
)
|
|
@@ -6674,7 +6677,8 @@ def extract_java_endpoints(root: Path) -> "dict[str, Any]":
|
|
|
6674
6677
|
# counted with, the real API surface — but they are preserved in their own bucket
|
|
6675
6678
|
# so nothing is silently dropped and the exclusion is auditable.
|
|
6676
6679
|
_test_util_endpoints = [e for e in endpoints if e.get("scope") == "test_util"]
|
|
6677
|
-
|
|
6680
|
+
if not include_tests:
|
|
6681
|
+
endpoints = [e for e in endpoints if e.get("scope") != "test_util"]
|
|
6678
6682
|
|
|
6679
6683
|
# "no_security_signal" = no recognized security annotation at method OR class level.
|
|
6680
6684
|
# Note: repos may use framework-level security (e.g. Keycloak itself) with no
|
|
@@ -6898,7 +6902,7 @@ def extract_java_endpoints(root: Path) -> "dict[str, Any]":
|
|
|
6898
6902
|
pass
|
|
6899
6903
|
# Surface test-fixture endpoints excluded from the production surface above.
|
|
6900
6904
|
# Reported separately (not hidden) so the exclusion is transparent and auditable.
|
|
6901
|
-
if _test_util_endpoints:
|
|
6905
|
+
if _test_util_endpoints and not include_tests:
|
|
6902
6906
|
result["test_util_endpoints"] = _test_util_endpoints
|
|
6903
6907
|
result["test_util_excluded"] = len(_test_util_endpoints)
|
|
6904
6908
|
result.setdefault("notes", []).append(
|
|
@@ -6906,6 +6910,8 @@ def extract_java_endpoints(root: Path) -> "dict[str, Any]":
|
|
|
6906
6910
|
f"module(s) under src/main were excluded from the production surface "
|
|
6907
6911
|
f"(see test_util_endpoints)."
|
|
6908
6912
|
)
|
|
6913
|
+
elif _test_util_endpoints:
|
|
6914
|
+
result["test_util_included"] = len(_test_util_endpoints)
|
|
6909
6915
|
# Surface incomplete-endpoint warnings (interface-defined controllers) only when
|
|
6910
6916
|
# present, to keep output backward-compatible for the common case.
|
|
6911
6917
|
if endpoint_warnings:
|
|
@@ -6964,6 +6970,17 @@ def extract_java_endpoints(root: Path) -> "dict[str, Any]":
|
|
|
6964
6970
|
if any(r.get("framework") == "jax_rs" for r in routes):
|
|
6965
6971
|
_nonspring.pop("jax_rs", None)
|
|
6966
6972
|
|
|
6973
|
+
# Servlet mappings are a URL declaration population, not a count of
|
|
6974
|
+
# HttpServlet classes. Reuse the servlet extractor so `endpoints` and
|
|
6975
|
+
# `endpoints --servlets` publish the same authoritative census.
|
|
6976
|
+
try:
|
|
6977
|
+
from sourcecode.servlet_surface import build_servlet_surface
|
|
6978
|
+
|
|
6979
|
+
_nonspring["servlets"] = int(
|
|
6980
|
+
build_servlet_surface(root).get("declarations", 0)
|
|
6981
|
+
)
|
|
6982
|
+
except Exception:
|
|
6983
|
+
pass
|
|
6967
6984
|
_nonspring_total = sum(_nonspring.values())
|
|
6968
6985
|
if _nonspring_total:
|
|
6969
6986
|
_frameworks = [k for k, v in _nonspring.items() if v]
|
|
@@ -7264,6 +7281,23 @@ def compute_blast_radius(
|
|
|
7264
7281
|
# class-level node traversable at all (C1-16), and rebuilding it per node
|
|
7265
7282
|
# would make the walk quadratic in the graph.
|
|
7266
7283
|
_rg_index = _class_method_index(reverse_graph)
|
|
7284
|
+
|
|
7285
|
+
def _walk_callers(_node: str) -> list[str]:
|
|
7286
|
+
"""Expand method frontiers through their owning class as well.
|
|
7287
|
+
|
|
7288
|
+
Service-locator accessors enter the frontier as ``Class#accessor`` while
|
|
7289
|
+
callers of the class are indexed under its method keys. Keeping the
|
|
7290
|
+
exact lookup preserves method-level reach; adding the enclosing class
|
|
7291
|
+
lookup prevents that frontier from becoming a false terminal node.
|
|
7292
|
+
"""
|
|
7293
|
+
candidates = list(_all_callers_from_rg(
|
|
7294
|
+
_node, reverse_graph, _rg_index, _impl_graph
|
|
7295
|
+
))
|
|
7296
|
+
if "#" in _node:
|
|
7297
|
+
candidates.extend(_all_callers_from_rg(
|
|
7298
|
+
_enclosing_class(_node), reverse_graph, _rg_index, _impl_graph
|
|
7299
|
+
))
|
|
7300
|
+
return list(dict.fromkeys(candidates))
|
|
7267
7301
|
# Callers inject the interface, not the implementation, so a walk that does not
|
|
7268
7302
|
# know the implements relation stops one hop short of every DI consumer. Without
|
|
7269
7303
|
# it this traversal reached 200 classes and **no controller at all** for
|
|
@@ -7298,14 +7332,14 @@ def compute_blast_radius(
|
|
|
7298
7332
|
_expanded.add(_sym)
|
|
7299
7333
|
matched_fqns = _expanded
|
|
7300
7334
|
for seed in matched_fqns:
|
|
7301
|
-
_seed_callers =
|
|
7335
|
+
_seed_callers = _walk_callers(seed)
|
|
7302
7336
|
if len(_seed_callers) > _HUB_CALLER_THRESHOLD and max_depth > 1:
|
|
7303
7337
|
_effective_depth = 1
|
|
7304
7338
|
_hub_class_guard = True
|
|
7305
7339
|
break
|
|
7306
7340
|
|
|
7307
7341
|
for seed in matched_fqns:
|
|
7308
|
-
callers =
|
|
7342
|
+
callers = _walk_callers(seed)
|
|
7309
7343
|
for c in callers:
|
|
7310
7344
|
if c not in all_affected:
|
|
7311
7345
|
all_affected[c] = 1
|
|
@@ -7385,16 +7419,22 @@ def compute_blast_radius(
|
|
|
7385
7419
|
|
|
7386
7420
|
# BFS for indirect callers
|
|
7387
7421
|
indirect_callers: list[str] = []
|
|
7422
|
+
_indirect_parent: dict[str, str] = {}
|
|
7423
|
+
_hub_frontier_nodes: set[str] = set()
|
|
7388
7424
|
visited: set[str] = set(matched_fqns) | set(direct_callers)
|
|
7389
7425
|
|
|
7390
7426
|
while queue:
|
|
7391
7427
|
node, depth = queue.pop(0)
|
|
7392
7428
|
if depth >= _effective_depth:
|
|
7393
7429
|
continue
|
|
7394
|
-
|
|
7430
|
+
_node_callers = _walk_callers(node)
|
|
7431
|
+
if len(_node_callers) > _HUB_CALLER_THRESHOLD:
|
|
7432
|
+
_hub_frontier_nodes.add(node)
|
|
7433
|
+
for caller in _node_callers:
|
|
7395
7434
|
if caller not in visited:
|
|
7396
7435
|
visited.add(caller)
|
|
7397
7436
|
all_affected[caller] = depth + 1
|
|
7437
|
+
_indirect_parent[caller] = node
|
|
7398
7438
|
indirect_callers.append(caller)
|
|
7399
7439
|
queue.append((caller, depth + 1))
|
|
7400
7440
|
|
|
@@ -7414,7 +7454,7 @@ def compute_blast_radius(
|
|
|
7414
7454
|
_snode, _sdepth = _sample_queue.pop(0)
|
|
7415
7455
|
if _sdepth >= _HUB_SAMPLE_DEPTH:
|
|
7416
7456
|
continue
|
|
7417
|
-
for _scaller in
|
|
7457
|
+
for _scaller in _walk_callers(_snode):
|
|
7418
7458
|
if _scaller not in _sample_visited:
|
|
7419
7459
|
_sample_visited.add(_scaller)
|
|
7420
7460
|
all_affected[_scaller] = _sdepth + 1
|
|
@@ -7615,10 +7655,38 @@ def compute_blast_radius(
|
|
|
7615
7655
|
_direct_caller_classes = sorted(
|
|
7616
7656
|
caller_classes(direct_callers, exclude=matched_fqns)
|
|
7617
7657
|
)
|
|
7618
|
-
|
|
7658
|
+
_direct_class_set = set(_direct_caller_classes)
|
|
7659
|
+
_direct_caller_symbols = sorted(
|
|
7660
|
+
symbol for symbol in direct_callers
|
|
7661
|
+
if _enclosing_class(symbol) in _direct_class_set
|
|
7662
|
+
)
|
|
7663
|
+
n_direct_symbols = len(_direct_caller_symbols)
|
|
7619
7664
|
n_direct_classes = len(_direct_caller_classes)
|
|
7620
7665
|
n_direct = n_direct_symbols
|
|
7621
7666
|
n_indirect = len(indirect_callers)
|
|
7667
|
+
|
|
7668
|
+
# The complete reverse walk is useful as a measured upper reach, but a
|
|
7669
|
+
# service-locator hub can make it collapse to "the whole repository". Publish
|
|
7670
|
+
# a second, conservative axis that stops at hub frontiers instead of forcing
|
|
7671
|
+
# consumers to guess from a flat list of symbols.
|
|
7672
|
+
_without_hubs: list[str] = []
|
|
7673
|
+
_without_hubs_seen = set(matched_fqns) | set(direct_callers)
|
|
7674
|
+
_without_hubs_queue: list[tuple[str, int]] = [
|
|
7675
|
+
(c, 1) for c in direct_callers
|
|
7676
|
+
]
|
|
7677
|
+
while _without_hubs_queue:
|
|
7678
|
+
_node, _depth = _without_hubs_queue.pop(0)
|
|
7679
|
+
if _depth >= _effective_depth:
|
|
7680
|
+
continue
|
|
7681
|
+
_next = _walk_callers(_node)
|
|
7682
|
+
if len(_next) > _HUB_CALLER_THRESHOLD:
|
|
7683
|
+
continue
|
|
7684
|
+
for _caller in _next:
|
|
7685
|
+
if _caller not in _without_hubs_seen:
|
|
7686
|
+
_without_hubs_seen.add(_caller)
|
|
7687
|
+
_without_hubs.append(_caller)
|
|
7688
|
+
_without_hubs_queue.append((_caller, _depth + 1))
|
|
7689
|
+
|
|
7622
7690
|
n_ep = len(endpoints_affected)
|
|
7623
7691
|
n_txn = len(txn_nodes)
|
|
7624
7692
|
n_mappers = len(mappers_affected)
|
|
@@ -7785,6 +7853,20 @@ def compute_blast_radius(
|
|
|
7785
7853
|
_indirect_summary = (
|
|
7786
7854
|
_indirect_ordered if _indirect_cap is None else _indirect_ordered[:_indirect_cap]
|
|
7787
7855
|
)
|
|
7856
|
+
def _via_hub(_node: str) -> bool:
|
|
7857
|
+
_seen_path: set[str] = set()
|
|
7858
|
+
_cursor = _node
|
|
7859
|
+
while _cursor in _indirect_parent and _cursor not in _seen_path:
|
|
7860
|
+
_seen_path.add(_cursor)
|
|
7861
|
+
_cursor = _indirect_parent[_cursor]
|
|
7862
|
+
if _cursor in _hub_frontier_nodes:
|
|
7863
|
+
return True
|
|
7864
|
+
return False
|
|
7865
|
+
|
|
7866
|
+
_indirect_details = [
|
|
7867
|
+
{"fqn": _node, "depth": all_affected.get(_node), "via_hub": _via_hub(_node)}
|
|
7868
|
+
for _node in _indirect_summary
|
|
7869
|
+
]
|
|
7788
7870
|
|
|
7789
7871
|
out: dict = {
|
|
7790
7872
|
"target": target,
|
|
@@ -7827,10 +7909,11 @@ def compute_blast_radius(
|
|
|
7827
7909
|
else _direct_caller_classes[:_direct_cap]
|
|
7828
7910
|
),
|
|
7829
7911
|
"direct_caller_symbols": (
|
|
7830
|
-
|
|
7831
|
-
else
|
|
7912
|
+
_direct_caller_symbols if _direct_cap is None
|
|
7913
|
+
else _direct_caller_symbols[:_direct_cap]
|
|
7832
7914
|
),
|
|
7833
7915
|
"indirect_callers": _indirect_summary,
|
|
7916
|
+
"indirect_caller_details": _indirect_details,
|
|
7834
7917
|
"container_wired": [w.to_dict() for w in _container_wired],
|
|
7835
7918
|
"endpoints_affected": endpoints_affected,
|
|
7836
7919
|
"mappers_affected": mappers_affected,
|
|
@@ -7850,6 +7933,10 @@ def compute_blast_radius(
|
|
|
7850
7933
|
"indirect_caller_count": n_indirect,
|
|
7851
7934
|
"indirect_callers_computed": not _bfs_truncated or _indirect_sampled,
|
|
7852
7935
|
"indirect_callers_sampled": _indirect_sampled,
|
|
7936
|
+
"indirect_caller_count_unit": "reference symbols (methods or classes)",
|
|
7937
|
+
"indirect_callers_without_hubs_count": len(_without_hubs),
|
|
7938
|
+
"indirect_callers_without_hubs_unit": "reference symbols (hub frontiers excluded)",
|
|
7939
|
+
"hub_frontier_count": len(_hub_frontier_nodes),
|
|
7853
7940
|
"endpoints_affected_count": n_ep,
|
|
7854
7941
|
# C1-37: four figures called "endpoints" at one commit (3 364 /
|
|
7855
7942
|
# 3 382 / 3 382 / 3 574). Each one now says which population it
|
sourcecode/risk.py
CHANGED
|
@@ -47,6 +47,31 @@ if TYPE_CHECKING:
|
|
|
47
47
|
|
|
48
48
|
RISK_SCHEMA = "risk-v1"
|
|
49
49
|
|
|
50
|
+
|
|
51
|
+
def compact_risk_output(data: dict) -> dict:
|
|
52
|
+
"""Project stdout to stable rule prose plus small per-defect evidence."""
|
|
53
|
+
compact = dict(data)
|
|
54
|
+
rows = data.get("risks") or []
|
|
55
|
+
rules: dict[str, dict] = {}
|
|
56
|
+
lean: list[dict] = []
|
|
57
|
+
for row in rows:
|
|
58
|
+
rule_ids = row.get("rule_ids") or [row.get("rule_id", "unknown")]
|
|
59
|
+
for rule_id in rule_ids:
|
|
60
|
+
rules.setdefault(rule_id, {
|
|
61
|
+
"title": row.get("title", ""),
|
|
62
|
+
"statement": row.get("statement", ""),
|
|
63
|
+
})
|
|
64
|
+
lean.append({
|
|
65
|
+
key: row[key] for key in (
|
|
66
|
+
"defect_id", "symbol", "source_file", "first_line", "band",
|
|
67
|
+
"score", "rule_ids", "factors",
|
|
68
|
+
) if key in row
|
|
69
|
+
})
|
|
70
|
+
compact["rules"] = rules
|
|
71
|
+
compact["risks"] = lean
|
|
72
|
+
compact["default_output"] = "bounded"
|
|
73
|
+
return compact
|
|
74
|
+
|
|
50
75
|
#: The composition, in one place. The payload publishes it, `--help` renders it and
|
|
51
76
|
#: the battery holds both to it: a hand-copied model line in the help screen went on
|
|
52
77
|
#: advertising four factors for as long as it took someone to read it, which is the
|
sourcecode/servlet_surface.py
CHANGED
|
@@ -168,9 +168,16 @@ def build_servlet_surface(root: Path, *, limit: Optional[int] = None) -> dict:
|
|
|
168
168
|
elif path.suffix == ".java":
|
|
169
169
|
mappings.extend(_from_registration_beans(path, relative))
|
|
170
170
|
|
|
171
|
-
#
|
|
172
|
-
#
|
|
173
|
-
|
|
171
|
+
# A URL pattern is the observable HTTP surface. Multiple descriptors can
|
|
172
|
+
# repeat it, but publishing every duplicate made `declarations` disagree with
|
|
173
|
+
# the pattern census and with endpoint-side reconciliation. Keep the first
|
|
174
|
+
# deterministic declaration as provenance and expose one row per reachable
|
|
175
|
+
# pattern.
|
|
176
|
+
_unique: dict[str, ServletMapping] = {}
|
|
177
|
+
for mapping in sorted(mappings, key=lambda m: (m.url_pattern, m.source_file, m.source)):
|
|
178
|
+
_unique.setdefault(mapping.url_pattern, mapping)
|
|
179
|
+
mappings = list(_unique.values())
|
|
180
|
+
patterns = sorted(_unique)
|
|
174
181
|
by_source: "dict[str, int]" = {}
|
|
175
182
|
for mapping in mappings:
|
|
176
183
|
by_source[mapping.source] = by_source.get(mapping.source, 0) + 1
|
sourcecode/validation_surface.py
CHANGED
|
@@ -484,18 +484,15 @@ def _programmatic_validation_signals(graph: "ContextGraph") -> "list[str]":
|
|
|
484
484
|
if _is_excluded(t.source_file):
|
|
485
485
|
continue
|
|
486
486
|
sig = (t.signature or "")
|
|
487
|
-
if
|
|
487
|
+
if (
|
|
488
|
+
"org.springframework.validation.Validator" not in sig
|
|
489
|
+
and not re.search(r"\bimplements\s+Validator\b", sig)
|
|
490
|
+
):
|
|
488
491
|
continue
|
|
489
492
|
if "ConstraintValidator" in sig:
|
|
490
493
|
continue
|
|
491
494
|
evidence.append(f"{t.fqn} implements org.springframework.validation.Validator")
|
|
492
495
|
|
|
493
|
-
for m in graph.symbols(kind="method"):
|
|
494
|
-
if _is_excluded(m.source_file):
|
|
495
|
-
continue
|
|
496
|
-
if m.has_annotation("InitBinder"):
|
|
497
|
-
evidence.append(f"{m.fqn} is annotated with @InitBinder")
|
|
498
|
-
|
|
499
496
|
if evidence:
|
|
500
497
|
return sorted(dict.fromkeys(evidence))
|
|
501
498
|
|
|
@@ -627,6 +624,7 @@ def build_validation_surface(
|
|
|
627
624
|
"detected": True,
|
|
628
625
|
"evidence": programmatic_evidence,
|
|
629
626
|
}
|
|
627
|
+
result["programmatic_validation_gaps"] = []
|
|
630
628
|
spec_path = endpoints_data.get("openapi_spec")
|
|
631
629
|
if spec_path:
|
|
632
630
|
result["openapi_spec"] = spec_path
|
|
@@ -717,7 +715,7 @@ def build_validation_surface(
|
|
|
717
715
|
"outside the declared-constraint surface this command models."
|
|
718
716
|
).strip()
|
|
719
717
|
if not result.get("gaps"):
|
|
720
|
-
result["
|
|
718
|
+
result["programmatic_validation_gaps"] = [
|
|
721
719
|
{
|
|
722
720
|
"method": "",
|
|
723
721
|
"path": "",
|
|
@@ -727,7 +725,6 @@ def build_validation_surface(
|
|
|
727
725
|
"confidence": "Likely",
|
|
728
726
|
}
|
|
729
727
|
]
|
|
730
|
-
result["summary"]["gaps"] = 1
|
|
731
728
|
|
|
732
729
|
_reconcile_with_code_axis(result, graph, root)
|
|
733
730
|
if programmatic_evidence:
|
|
@@ -740,7 +737,7 @@ def build_validation_surface(
|
|
|
740
737
|
if programmatic_note.strip() not in note:
|
|
741
738
|
result["note"] = (note + programmatic_note).strip()
|
|
742
739
|
if not result.get("gaps"):
|
|
743
|
-
result["
|
|
740
|
+
result["programmatic_validation_gaps"] = [
|
|
744
741
|
{
|
|
745
742
|
"method": "",
|
|
746
743
|
"path": "",
|
|
@@ -750,7 +747,6 @@ def build_validation_surface(
|
|
|
750
747
|
"confidence": "Likely",
|
|
751
748
|
}
|
|
752
749
|
]
|
|
753
|
-
result["summary"]["gaps"] = 1
|
|
754
750
|
return result
|
|
755
751
|
|
|
756
752
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sourcecode
|
|
3
|
-
Version: 5.5.
|
|
3
|
+
Version: 5.5.2
|
|
4
4
|
Summary: Persistent structural context and ultra-fast repeated analysis for AI coding agents
|
|
5
5
|
License-File: LICENSE
|
|
6
6
|
Keywords: agents,ai,codebase,context,developer-tools,llm
|
|
@@ -42,7 +42,7 @@ Description-Content-Type: text/markdown
|
|
|
42
42
|
|
|
43
43
|
**Context · Impact · Migration · Architecture · Review — everything from one structural model.**
|
|
44
44
|
|
|
45
|
-

|
|
46
46
|

|
|
47
47
|
|
|
48
48
|
> **ASK Engine** is the product. The CLI command is **`ask`**. The legacy **`sourcecode`**
|
|
@@ -126,7 +126,7 @@ brew tap haroundominique/sourcecode && brew install sourcecode
|
|
|
126
126
|
# pip / pipx
|
|
127
127
|
pipx install sourcecode # or: pip install sourcecode
|
|
128
128
|
|
|
129
|
-
ask version # ask 5.5.
|
|
129
|
+
ask version # ask 5.5.2 — and, on a build that has aged,
|
|
130
130
|
# how many releases have probably shipped since
|
|
131
131
|
```
|
|
132
132
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
sourcecode/__init__.py,sha256=
|
|
1
|
+
sourcecode/__init__.py,sha256=N8caEJv8V-c5M8fb_R3zr9u-Fd7axm-yB4yw-d_rbf0,308
|
|
2
2
|
sourcecode/adaptive_scanner.py,sha256=yJBKjNpkY6bpueYJ2YnRezen3sYZDecEt7WaaNWdqug,9466
|
|
3
3
|
sourcecode/archetype.py,sha256=CZvRLpkHot_D8D3JFQVorr-EHDJyh0BbS7RnSTqBigM,40499
|
|
4
4
|
sourcecode/architectural_baseline.py,sha256=4GiMVBLJVHvRKSWQGf2ZVurI1-R0qk5pmaevgvGSgVA,26325
|
|
@@ -19,7 +19,7 @@ sourcecode/chain_rules.py,sha256=Bi6UHfgd-GxWswmnHRcPz5jdbAuqka3Zkz_P-MTvqhw,127
|
|
|
19
19
|
sourcecode/change_plan.py,sha256=aX1mp2XnlDN-8R2BcTiu8HlkDwQ_4fN1ZLckj0VMXHM,9945
|
|
20
20
|
sourcecode/cir_graphs.py,sha256=9G0HHj1kw2325IDyzo2OpX73BNswEckecf4MZUXB4JM,12078
|
|
21
21
|
sourcecode/classifier.py,sha256=JBzPwSSrDG-tUHAbcKB678HRbjLpD-ohzbzzO62mgpo,20114
|
|
22
|
-
sourcecode/cli.py,sha256=
|
|
22
|
+
sourcecode/cli.py,sha256=hJukwL8ZTc1ZHVkkZWau9J0LFYtAfyHvVdp4HKIRWIY,648972
|
|
23
23
|
sourcecode/client_calls.py,sha256=5_sBfFVXi6RgGiyWrlJKH4x4EF21do6gKlAOuFCKAoA,17709
|
|
24
24
|
sourcecode/code_notes_analyzer.py,sha256=EJemNCNc9Dn-1RZYu-aNbK0ELzmsyC4s6FdHi3XyNEI,9392
|
|
25
25
|
sourcecode/compare.py,sha256=2GdDy0qkDSgmkkpgvoCHitoLj3mt30EeFgJ8PGEXJng,14712
|
|
@@ -41,7 +41,7 @@ sourcecode/data_labels.py,sha256=c_hmgudTE68cVRdywfbfonr7nUWIL-JH2HOXB8Pplv0,845
|
|
|
41
41
|
sourcecode/declarations.py,sha256=Jr7oJDg004sFw0TvJySNVggXFT2jB6hQgbeQK2qreVo,7000
|
|
42
42
|
sourcecode/defect_identity.py,sha256=XS2VFDImg9NLaRImybKTeyk-CoDSsXmchldg0dwvM7c,7028
|
|
43
43
|
sourcecode/degradation.py,sha256=BtuGUKFgkzI3Z6fwmUdxEUEaLFAcPQ_bHhU2w9ItDxw,18376
|
|
44
|
-
sourcecode/dependency_analyzer.py,sha256=
|
|
44
|
+
sourcecode/dependency_analyzer.py,sha256=MRv6eh1TG5E9Y5Db-bNdZHDIJ3gWvvL3bBU1-9_QSZI,64593
|
|
45
45
|
sourcecode/deployment_prefix.py,sha256=g0_4bE_m9svjhnapRpOp8BJkj6Ul94Ij_iVezygMZ0I,11956
|
|
46
46
|
sourcecode/detach.py,sha256=PvkCcbIuTaDKKt5TGqtQLahmsmCkflui33jLnMEWkvg,8006
|
|
47
47
|
sourcecode/doc_analyzer.py,sha256=05bjTUbDbmnbajD_cgRnACzS8T7xxBKVX4CjkJlhZg8,24411
|
|
@@ -71,9 +71,10 @@ sourcecode/identity_fallback.py,sha256=uRC_j_80uaI4Ue4tf-OhKX4h472dmufYcjEFGSxY2
|
|
|
71
71
|
sourcecode/integration_coordinates.py,sha256=7vGFxN4tCn_KTisYFJuixa8XkyHBoasEFB3fqM62OBI,7407
|
|
72
72
|
sourcecode/jdk_exports.py,sha256=fCrlwNAXUT9gge_joq6kMnY3zJxYB2pxqy-0w3o3MJI,874
|
|
73
73
|
sourcecode/license.py,sha256=vvgE1vBUWh8ozWI7USYuteh6noxntnjuzDQIPF-l4rA,27795
|
|
74
|
+
sourcecode/manifest_cache.py,sha256=NkVtg0T5YfHdIiM63jStvcLcsznM6_bnTHib4t8_FW8,2158
|
|
74
75
|
sourcecode/mcp_nudge.py,sha256=lKemOqK_wny2u7Ymcr2Idi5Kx8pXY02jCi-_nJYLGMg,2992
|
|
75
76
|
sourcecode/metrics_analyzer.py,sha256=gLoRWKygF18jLgwsqmGXSWopw-f5iO1VbM7Jjdif6ag,22809
|
|
76
|
-
sourcecode/migrate_check.py,sha256=
|
|
77
|
+
sourcecode/migrate_check.py,sha256=vdJ_2hOR9XOIQsz4czzamQMKKzHnNErg8vPdzzYqfMY,150019
|
|
77
78
|
sourcecode/migration_blast.py,sha256=OPryWkM6-PbvcG3eJFdcWUnlpXBje41nu2BkrZnE2hg,9359
|
|
78
79
|
sourcecode/non_coverage.py,sha256=sKp0J4_hKzKb_HMoXtdP91D0lMfEAPrOx44sqdQc18Y,23520
|
|
79
80
|
sourcecode/openapi_surface.py,sha256=BTt0K-woZbkbWTN77IkqeBm_Okag9owR0848fmot8sk,16207
|
|
@@ -99,15 +100,15 @@ sourcecode/readiness_timeline.py,sha256=KjghC1MWH7Tzto97TBR3N0F8FkR87w5z7lZ1QsWv
|
|
|
99
100
|
sourcecode/readonly.py,sha256=dqGiNVvpfi4Ww8VzwUs6E3SdbqcHL1LFHItIK7IjrM0,5830
|
|
100
101
|
sourcecode/reconciliation.py,sha256=GU-1PTcVr8zcbtC7BASfpHcZndP9AdboXPNQiBc0fzo,34251
|
|
101
102
|
sourcecode/redactor.py,sha256=SB4hwIvg8h-hvcqKcDWaZvA-aSyn-at-BIRwa0tUv5E,3227
|
|
102
|
-
sourcecode/reference_facts.py,sha256=
|
|
103
|
-
sourcecode/release_info.py,sha256=
|
|
103
|
+
sourcecode/reference_facts.py,sha256=mx7H-Ax5y8sKAQHrnJCXlgjkUzqZtQTByv9Zy2lxKQQ,15500
|
|
104
|
+
sourcecode/release_info.py,sha256=lCzjRkPWDmOZQxPujpY5FwPU8a87G358ectkhNU8cX8,5895
|
|
104
105
|
sourcecode/relevance_scorer.py,sha256=0AgEt4KrV73nioMqBgjhGjtY7L2C7L7cSyKtj3IKcrw,9408
|
|
105
106
|
sourcecode/remedies.py,sha256=V2_cD4pLq8bKRkzF-3RGFoiu1xWYZS384WDVp1MrlZw,10225
|
|
106
107
|
sourcecode/rename_refactor.py,sha256=h6dNFlB9aZ_3q6heeHBkgXQeXaT03nvPSsYH6P8qxFg,12965
|
|
107
108
|
sourcecode/repo_classifier.py,sha256=FG1vaWKdWXsWdl-S8hjVMiTqcwgaRXkDyvK4rPcOGtQ,22681
|
|
108
|
-
sourcecode/repository_ir.py,sha256=
|
|
109
|
+
sourcecode/repository_ir.py,sha256=tV0Uu7qWw_gDUG9WldYxmbNLZnbQljxHYZznWP4cdlA,384071
|
|
109
110
|
sourcecode/ris.py,sha256=Xbx34dkJI_uXil9xsgw-fA_Apcm4nbnTjQgFm66dP1g,27489
|
|
110
|
-
sourcecode/risk.py,sha256=
|
|
111
|
+
sourcecode/risk.py,sha256=u2V7nG5yGW8VV4sFDSgFkZqdI6ltmilWhlQwlIH7d2o,82660
|
|
111
112
|
sourcecode/rule_catalog.py,sha256=pTkgkQZ1u7atB3fkQJ9BPWo8lNiamvEuRN7MtRgle0c,5263
|
|
112
113
|
sourcecode/rule_pass.py,sha256=DTNxD-nAecMJhIzI1LCd9FyRumInQSZjitacNh4Zoc8,14671
|
|
113
114
|
sourcecode/runs.py,sha256=o4oXkB3WFJGScbK_C10OAWzsE18B9Zc55u_IE8_yl4M,6291
|
|
@@ -124,7 +125,7 @@ sourcecode/semantic_impact_engine.py,sha256=t09IirGC3JjQDy33JZd1_WKzQVKXkoNl3-XE
|
|
|
124
125
|
sourcecode/semantic_integration_engine.py,sha256=7a0WqAInOv39f0Yr_94TYo_JP_8QpeI9KGaknpzAyQU,18899
|
|
125
126
|
sourcecode/semantic_services.py,sha256=nbUuPv-F01USTt_9CHT8iy_ucCIw3fz4W3Aquea_pd4,10782
|
|
126
127
|
sourcecode/serializer.py,sha256=cK4I3WzX0H-oBPbqzBsUBRux5GcYmH_yPDQwxvkf8yQ,141598
|
|
127
|
-
sourcecode/servlet_surface.py,sha256=
|
|
128
|
+
sourcecode/servlet_surface.py,sha256=QY3QPw6pZJ4CQK4yS8q7uDd3f2PDyUUsQ8TS_BkbGs8,9506
|
|
128
129
|
sourcecode/source_text.py,sha256=R52gmSljz7L6YVPbixv5W-LOZ5eKUVvSUP3S-QH_IWc,9047
|
|
129
130
|
sourcecode/spring_event_topology.py,sha256=5_ON_21Le5zbG-1GRc5GLIi5HJfy_QjcXLVPC5WeUGQ,18055
|
|
130
131
|
sourcecode/spring_findings.py,sha256=cW6NiQAFUM9mDYPitJr6MrQg--YBI41LxrSEMI9aeIQ,25272
|
|
@@ -146,7 +147,7 @@ sourcecode/token_estimate.py,sha256=G-ivzIJfVq5TLQN04dC0WK4LZJjWVYL54Ebcxe4fJ7M,
|
|
|
146
147
|
sourcecode/tree_utils.py,sha256=8GAkIfQAsvtEudIeW1l4ooH_oRtrWR8cpJQJsEa_Pfw,2093
|
|
147
148
|
sourcecode/type_usage_surface.py,sha256=51IrKRQoIoRnlsiDjHnqpJBn2rc6E59aRhgS0HTzAF0,4428
|
|
148
149
|
sourcecode/validation_inference.py,sha256=-oWJqE6PqkqcZbCFDJeWCQHgI9Dbuv5ggJWD3LE_2IU,20817
|
|
149
|
-
sourcecode/validation_surface.py,sha256=
|
|
150
|
+
sourcecode/validation_surface.py,sha256=06muCMxymQEQSqmNgTMdsguC7XQ22tqcZmYJOmvWFHY,34745
|
|
150
151
|
sourcecode/verify_edit.py,sha256=SUaJeH5Icc54mutQx3-gh7i8U6d50cb_oxYc9SDUnHs,42757
|
|
151
152
|
sourcecode/verify_repo.py,sha256=aHlNIUfoVVUxTXTNtSFryP4dOFBOvCfdyC31fFRXU6s,15346
|
|
152
153
|
sourcecode/verify_rules.py,sha256=R3l6nLT6WQzUNBzxtmnxyGZcKj9qMDW38Y0c0gNdCGk,19973
|
|
@@ -213,9 +214,9 @@ sourcecode/telemetry/events.py,sha256=4_yeO58U-Cwc1Qb27VB0_EjhmroY0k91n3_VGxeALB
|
|
|
213
214
|
sourcecode/telemetry/filters.py,sha256=RzxauTz8HliO4BllQnXEXc7zTeqdCZi5MgqGEDuW7OQ,6570
|
|
214
215
|
sourcecode/telemetry/transport.py,sha256=4gGHsq0WeY9VywEZXA3vUxykfiYnw9uuqfjAAec7F8o,1681
|
|
215
216
|
sourcecode/_docs/DEFECT-LEDGER.md,sha256=sTFPEV2xx-L9n4mrG8UO4LxeETMJpGKHXyKQBZlqNog,497112
|
|
216
|
-
sourcecode/_docs/USER_GUIDE.md,sha256=
|
|
217
|
-
sourcecode-5.5.
|
|
218
|
-
sourcecode-5.5.
|
|
219
|
-
sourcecode-5.5.
|
|
220
|
-
sourcecode-5.5.
|
|
221
|
-
sourcecode-5.5.
|
|
217
|
+
sourcecode/_docs/USER_GUIDE.md,sha256=IOYL9rI9tLViNMICXma_uRSiztjMaW92V5sDAYlPkfo,79546
|
|
218
|
+
sourcecode-5.5.2.dist-info/METADATA,sha256=VjdCWsZA1Bm_mhWsXA-yr6JT1zTSKcLVJA8lejltoug,47104
|
|
219
|
+
sourcecode-5.5.2.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
220
|
+
sourcecode-5.5.2.dist-info/entry_points.txt,sha256=-JEAdChrK5We51kZcb7OaDcyil-dHBjBPL-NhuO-QY8,89
|
|
221
|
+
sourcecode-5.5.2.dist-info/licenses/LICENSE,sha256=7DdHrU9Z_3e7dSvq4ISijZNjnuHo5NIHNiHDouMQ9JU,10491
|
|
222
|
+
sourcecode-5.5.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|