sourcecode 1.31.32__py3-none-any.whl → 1.32.1__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 +64 -15
- sourcecode/classifier.py +4 -0
- sourcecode/cli.py +100 -53
- sourcecode/context_summarizer.py +1 -0
- sourcecode/dependency_analyzer.py +3 -3
- sourcecode/detectors/java.py +6 -1
- sourcecode/license.py +107 -16
- sourcecode/prepare_context.py +7 -1
- sourcecode/repository_ir.py +6 -1
- sourcecode/summarizer.py +1 -0
- {sourcecode-1.31.32.dist-info → sourcecode-1.32.1.dist-info}/METADATA +3 -3
- {sourcecode-1.31.32.dist-info → sourcecode-1.32.1.dist-info}/RECORD +16 -16
- {sourcecode-1.31.32.dist-info → sourcecode-1.32.1.dist-info}/WHEEL +0 -0
- {sourcecode-1.31.32.dist-info → sourcecode-1.32.1.dist-info}/entry_points.txt +0 -0
- {sourcecode-1.31.32.dist-info → sourcecode-1.32.1.dist-info}/licenses/LICENSE +0 -0
sourcecode/__init__.py
CHANGED
|
@@ -152,14 +152,15 @@ LAYER_PATTERNS: dict[str, dict[str, list[str]]] = {
|
|
|
152
152
|
|
|
153
153
|
# Higher value = wins when score ties
|
|
154
154
|
_PATTERN_PRIORITY: dict[str, int] = {
|
|
155
|
-
"cqrs":
|
|
156
|
-
"clean":
|
|
157
|
-
"onion":
|
|
158
|
-
"hexagonal":
|
|
159
|
-
"monorepo":
|
|
160
|
-
"
|
|
161
|
-
"
|
|
162
|
-
"
|
|
155
|
+
"cqrs": 8,
|
|
156
|
+
"clean": 7,
|
|
157
|
+
"onion": 6,
|
|
158
|
+
"hexagonal": 5,
|
|
159
|
+
"monorepo": 4,
|
|
160
|
+
"spring_mvc_layered": 3,
|
|
161
|
+
"mvc": 3,
|
|
162
|
+
"layered": 2,
|
|
163
|
+
"fullstack": 1,
|
|
163
164
|
}
|
|
164
165
|
|
|
165
166
|
|
|
@@ -248,9 +249,9 @@ class ArchitectureAnalyzer:
|
|
|
248
249
|
pattern, layers = self._detect_layers(filtered)
|
|
249
250
|
if pattern in (None, "flat", "unknown"):
|
|
250
251
|
if pattern == "flat":
|
|
251
|
-
limitations.append("
|
|
252
|
+
limitations.append("Layer pattern not detected: project has a flat directory structure")
|
|
252
253
|
elif pattern == "unknown":
|
|
253
|
-
limitations.append("
|
|
254
|
+
limitations.append("Unrecognized layer pattern: directory structure has no clear architectural signals")
|
|
254
255
|
|
|
255
256
|
# Step 3b: monorepo override — workspace config is hard evidence.
|
|
256
257
|
# Overrides all weak inferred patterns; only truly specialised patterns
|
|
@@ -269,7 +270,7 @@ class ArchitectureAnalyzer:
|
|
|
269
270
|
pattern = "monorepo"
|
|
270
271
|
layers = mono_layers
|
|
271
272
|
limitations.append(
|
|
272
|
-
"Workspace config
|
|
273
|
+
"Workspace config detected — architecture reflects package topology"
|
|
273
274
|
)
|
|
274
275
|
ws_files = [p for p in sm.file_paths if p.split("/")[-1] in _WORKSPACE_CONFIG_FILES]
|
|
275
276
|
evidence.append({
|
|
@@ -570,28 +571,76 @@ class ArchitectureAnalyzer:
|
|
|
570
571
|
))
|
|
571
572
|
return best_pattern, layers
|
|
572
573
|
|
|
573
|
-
# 2.
|
|
574
|
+
# 2. Spring domain-module detection (petclinic-style: deep common prefix + feature dirs)
|
|
575
|
+
spring_result = self._detect_spring_domain_modules(source_paths)
|
|
576
|
+
if spring_result is not None:
|
|
577
|
+
return spring_result
|
|
578
|
+
|
|
579
|
+
# 3. Microservices structural detection (before file-naming heuristics)
|
|
574
580
|
microservices_result = self._detect_microservices(source_paths)
|
|
575
581
|
if microservices_result is not None:
|
|
576
582
|
return microservices_result
|
|
577
583
|
|
|
578
|
-
#
|
|
584
|
+
# 5. Functional file-naming heuristic: *_analyzer.py, cli.py, schema.py, …
|
|
579
585
|
func_result = self._detect_layered_functional(source_paths)
|
|
580
586
|
if func_result is not None:
|
|
581
587
|
return func_result
|
|
582
588
|
|
|
583
|
-
#
|
|
589
|
+
# 6. Modular sub-package heuristic: ≥2 distinct named sub-packages
|
|
584
590
|
modular_result = self._detect_modular(source_paths)
|
|
585
591
|
if modular_result is not None:
|
|
586
592
|
return modular_result
|
|
587
593
|
|
|
588
|
-
#
|
|
594
|
+
# 7. Fallback: flat (shallow) vs truly unknown (deep but unrecognised)
|
|
589
595
|
max_depth = max(
|
|
590
596
|
(len(p.replace("\\", "/").split("/")) - 1 for p in source_paths),
|
|
591
597
|
default=0,
|
|
592
598
|
)
|
|
593
599
|
return ("flat" if max_depth <= 2 else "unknown"), []
|
|
594
600
|
|
|
601
|
+
def _detect_spring_domain_modules(
|
|
602
|
+
self, paths: list[str]
|
|
603
|
+
) -> Optional[tuple[str, list[ArchitectureLayer]]]:
|
|
604
|
+
"""Detect Spring Boot domain-organized packages (petclinic-style).
|
|
605
|
+
|
|
606
|
+
When all source paths share a deep common package prefix
|
|
607
|
+
(e.g. src/main/java/org/springframework/samples/petclinic/),
|
|
608
|
+
strips that prefix and detects feature/domain modules in the remainder.
|
|
609
|
+
Requires ≥3 distinct domain directories to avoid false positives.
|
|
610
|
+
"""
|
|
611
|
+
if len(paths) < 6:
|
|
612
|
+
return None
|
|
613
|
+
|
|
614
|
+
parts_list = [p.replace("\\", "/").split("/") for p in paths]
|
|
615
|
+
min_depth = min(len(p) for p in parts_list)
|
|
616
|
+
common_depth = 0
|
|
617
|
+
for i in range(min_depth - 1):
|
|
618
|
+
seg = parts_list[0][i]
|
|
619
|
+
if all(pl[i] == seg for pl in parts_list):
|
|
620
|
+
common_depth = i + 1
|
|
621
|
+
else:
|
|
622
|
+
break
|
|
623
|
+
|
|
624
|
+
if common_depth < 3:
|
|
625
|
+
return None
|
|
626
|
+
|
|
627
|
+
module_files: dict[str, list[str]] = {}
|
|
628
|
+
for orig, pl in zip(paths, parts_list):
|
|
629
|
+
remaining = pl[common_depth:]
|
|
630
|
+
if remaining and remaining[0] not in _GENERIC_NAMES:
|
|
631
|
+
module_files.setdefault(remaining[0], []).append(orig)
|
|
632
|
+
|
|
633
|
+
meaningful = {k: v for k, v in module_files.items() if len(v) >= 2}
|
|
634
|
+
if len(meaningful) < 3:
|
|
635
|
+
return None
|
|
636
|
+
|
|
637
|
+
return "spring_mvc_layered", [
|
|
638
|
+
ArchitectureLayer(
|
|
639
|
+
name=k, pattern="spring_mvc_layered", files=v, confidence="medium"
|
|
640
|
+
)
|
|
641
|
+
for k, v in meaningful.items()
|
|
642
|
+
]
|
|
643
|
+
|
|
595
644
|
def _detect_microservices(
|
|
596
645
|
self, paths: list[str]
|
|
597
646
|
) -> Optional[tuple[str, list[ArchitectureLayer]]]:
|
sourcecode/classifier.py
CHANGED
|
@@ -118,6 +118,10 @@ class TypeClassifier:
|
|
|
118
118
|
):
|
|
119
119
|
return "webapp"
|
|
120
120
|
|
|
121
|
+
_SERVERSIDE_TEMPLATE_FRAMEWORKS = frozenset({"Thymeleaf", "FreeMarker"})
|
|
122
|
+
if framework_names & _SERVERSIDE_TEMPLATE_FRAMEWORKS:
|
|
123
|
+
return "web_mvc"
|
|
124
|
+
|
|
121
125
|
if framework_names & _API_FRAMEWORKS:
|
|
122
126
|
return "api"
|
|
123
127
|
|
sourcecode/cli.py
CHANGED
|
@@ -781,6 +781,11 @@ def main(
|
|
|
781
781
|
err=True,
|
|
782
782
|
)
|
|
783
783
|
|
|
784
|
+
# Pro gate for --full: removing truncation limits is enterprise-scale functionality.
|
|
785
|
+
if full:
|
|
786
|
+
from sourcecode.license import require_feature as _req_full
|
|
787
|
+
_req_full("--full")
|
|
788
|
+
|
|
784
789
|
# P0-2 FIX: --compact and --full are mutually exclusive.
|
|
785
790
|
# compact is designed to be a bounded summary; --full removes truncation limits,
|
|
786
791
|
# which contradicts compact's purpose. Use --agent --full for expanded output.
|
|
@@ -2672,6 +2677,32 @@ def prepare_context_cmd(
|
|
|
2672
2677
|
_pc_budget = _pc_budgets.get(task, BUDGET_EXPLAIN)
|
|
2673
2678
|
out = _pc_trim(out, _pc_budget, label=task)
|
|
2674
2679
|
|
|
2680
|
+
# Free-tier limits: fix-bug (top-5 files) and review-pr (lightweight).
|
|
2681
|
+
# Pro users get the full analysis; free users get enough to see the value.
|
|
2682
|
+
if task in ("fix-bug", "review-pr"):
|
|
2683
|
+
from sourcecode.license import can_use as _tier_can_use
|
|
2684
|
+
if not _tier_can_use(task):
|
|
2685
|
+
_FREE_FILE_LIMIT = 5
|
|
2686
|
+
if task == "fix-bug":
|
|
2687
|
+
_rf = out.get("relevant_files")
|
|
2688
|
+
if isinstance(_rf, list) and len(_rf) > _FREE_FILE_LIMIT:
|
|
2689
|
+
out["relevant_files"] = _rf[:_FREE_FILE_LIMIT]
|
|
2690
|
+
out["tier"] = "free"
|
|
2691
|
+
out["tier_note"] = (
|
|
2692
|
+
f"Showing top {_FREE_FILE_LIMIT} files. "
|
|
2693
|
+
"Upgrade to Pro for complete risk-ranked analysis across all files."
|
|
2694
|
+
)
|
|
2695
|
+
else: # review-pr
|
|
2696
|
+
for _cap_field in ("runtime_changes", "execution_paths", "review_hotspots", "suggested_review_order"):
|
|
2697
|
+
_fval = out.get(_cap_field)
|
|
2698
|
+
if isinstance(_fval, list) and len(_fval) > _FREE_FILE_LIMIT:
|
|
2699
|
+
out[_cap_field] = _fval[:_FREE_FILE_LIMIT]
|
|
2700
|
+
out["tier"] = "free"
|
|
2701
|
+
out["tier_note"] = (
|
|
2702
|
+
"Lightweight review. Upgrade to Pro for full blast-radius analysis, "
|
|
2703
|
+
"complete execution paths, and CI-grade risk scoring."
|
|
2704
|
+
)
|
|
2705
|
+
|
|
2675
2706
|
if format == "github-comment" and task == "review-pr":
|
|
2676
2707
|
from sourcecode.pr_comment_renderer import render_github_comment
|
|
2677
2708
|
_pc_content = render_github_comment(out)
|
|
@@ -3373,13 +3404,11 @@ def modernize_cmd(
|
|
|
3373
3404
|
sourcecode onboard . — Architecture overview first
|
|
3374
3405
|
sourcecode impact <target> — Verify impact before touching a hotspot
|
|
3375
3406
|
"""
|
|
3376
|
-
from sourcecode.license import require_pro as _require_pro
|
|
3377
|
-
_require_pro("modernize")
|
|
3378
|
-
|
|
3379
3407
|
import json as _json
|
|
3380
3408
|
import sys as _sys
|
|
3381
3409
|
from sourcecode.repository_ir import build_repo_ir, find_java_files, apply_ir_size_limits
|
|
3382
3410
|
from sourcecode.output_budget import trim_to_budget, BUDGET_ONBOARD
|
|
3411
|
+
from sourcecode.license import can_use as _mod_can_use
|
|
3383
3412
|
|
|
3384
3413
|
root = path.resolve()
|
|
3385
3414
|
if not root.is_dir():
|
|
@@ -3481,52 +3510,73 @@ def modernize_cmd(
|
|
|
3481
3510
|
key=lambda s: -len(s.get("members") or []),
|
|
3482
3511
|
)[:10]
|
|
3483
3512
|
|
|
3484
|
-
|
|
3485
|
-
"
|
|
3486
|
-
"
|
|
3487
|
-
"
|
|
3488
|
-
|
|
3489
|
-
"total_subsystems": len(subsystems),
|
|
3490
|
-
"high_coupling_nodes": len(coupling_nodes),
|
|
3491
|
-
"dead_zone_candidates": len(dead_zones),
|
|
3492
|
-
},
|
|
3493
|
-
"hotspot_candidates": hotspots,
|
|
3494
|
-
"high_coupling_nodes": [
|
|
3495
|
-
{"fqn": n["fqn"], "in_degree": n.get("in_degree", 0), "role": n.get("role", "other")}
|
|
3496
|
-
for n in coupling_nodes
|
|
3497
|
-
],
|
|
3498
|
-
"dead_zone_candidates": [
|
|
3499
|
-
{"fqn": n["fqn"], "type": n.get("type", ""), "role": n.get("role", "other")}
|
|
3500
|
-
for n in dead_zones
|
|
3501
|
-
],
|
|
3502
|
-
"subsystem_summary": [
|
|
3503
|
-
{
|
|
3504
|
-
"label": s.get("label") or s.get("name") or "",
|
|
3505
|
-
"package_prefix": s.get("package_prefix") or s.get("pkg") or "",
|
|
3506
|
-
"member_count": len(s.get("members") or []),
|
|
3507
|
-
}
|
|
3508
|
-
for s in subsystems[:15]
|
|
3509
|
-
],
|
|
3510
|
-
"cross_module_tangles": [
|
|
3511
|
-
{
|
|
3512
|
-
"label": s.get("label") or s.get("name") or "",
|
|
3513
|
-
"member_count": len(s.get("members") or []),
|
|
3514
|
-
}
|
|
3515
|
-
for s in tangle_modules
|
|
3516
|
-
],
|
|
3517
|
-
# BUG-05 fix: don't recommend "Start with hotspot_candidates" when the list is empty.
|
|
3518
|
-
# hotspots filters by role=service/repository/controller; annotation types and
|
|
3519
|
-
# value objects end up in high_coupling_nodes instead.
|
|
3520
|
-
"recommendation": (
|
|
3521
|
-
(
|
|
3522
|
-
"Start with hotspot_candidates (high fan-in = highest blast radius). "
|
|
3523
|
-
if hotspots else
|
|
3524
|
-
"high_coupling_nodes shows the most-referenced classes — start there. "
|
|
3525
|
-
)
|
|
3526
|
-
+ "Dead zones are safe to remove or refactor. "
|
|
3527
|
-
+ "Cross-module tangles indicate coupling worth decomposing."
|
|
3528
|
-
),
|
|
3513
|
+
_summary = {
|
|
3514
|
+
"total_classes": len([n for n in graph_nodes if n.get("type") in ("class", "interface")]),
|
|
3515
|
+
"total_subsystems": len(subsystems),
|
|
3516
|
+
"high_coupling_nodes": len(coupling_nodes),
|
|
3517
|
+
"dead_zone_candidates": len(dead_zones),
|
|
3529
3518
|
}
|
|
3519
|
+
_subsystem_summary = [
|
|
3520
|
+
{
|
|
3521
|
+
"label": s.get("label") or s.get("name") or "",
|
|
3522
|
+
"package_prefix": s.get("package_prefix") or s.get("pkg") or "",
|
|
3523
|
+
"member_count": len(s.get("members") or []),
|
|
3524
|
+
}
|
|
3525
|
+
for s in subsystems[:15]
|
|
3526
|
+
]
|
|
3527
|
+
|
|
3528
|
+
if not _mod_can_use("modernize"):
|
|
3529
|
+
# Free tier: structural discovery only — no dead zones, tangles, or full refactor list.
|
|
3530
|
+
result = {
|
|
3531
|
+
"workflow": "modernize",
|
|
3532
|
+
"path": str(root),
|
|
3533
|
+
"tier": "free",
|
|
3534
|
+
"tier_note": (
|
|
3535
|
+
"Upgrade to Pro for full analysis: dead zones, dependency tangles, "
|
|
3536
|
+
"refactor candidates ranked by git churn, and complete coupling graphs."
|
|
3537
|
+
),
|
|
3538
|
+
"summary": _summary,
|
|
3539
|
+
"subsystem_summary": _subsystem_summary,
|
|
3540
|
+
"hotspot_candidates": hotspots[:3],
|
|
3541
|
+
"high_coupling_nodes": [
|
|
3542
|
+
{"fqn": n["fqn"], "in_degree": n.get("in_degree", 0), "role": n.get("role", "other")}
|
|
3543
|
+
for n in coupling_nodes[:3]
|
|
3544
|
+
],
|
|
3545
|
+
}
|
|
3546
|
+
else:
|
|
3547
|
+
# Pro tier: full analysis.
|
|
3548
|
+
result = {
|
|
3549
|
+
"workflow": "modernize",
|
|
3550
|
+
"path": str(root),
|
|
3551
|
+
"summary": _summary,
|
|
3552
|
+
"hotspot_candidates": hotspots,
|
|
3553
|
+
"high_coupling_nodes": [
|
|
3554
|
+
{"fqn": n["fqn"], "in_degree": n.get("in_degree", 0), "role": n.get("role", "other")}
|
|
3555
|
+
for n in coupling_nodes
|
|
3556
|
+
],
|
|
3557
|
+
"dead_zone_candidates": [
|
|
3558
|
+
{"fqn": n["fqn"], "type": n.get("type", ""), "role": n.get("role", "other")}
|
|
3559
|
+
for n in dead_zones
|
|
3560
|
+
],
|
|
3561
|
+
"subsystem_summary": _subsystem_summary,
|
|
3562
|
+
"cross_module_tangles": [
|
|
3563
|
+
{
|
|
3564
|
+
"label": s.get("label") or s.get("name") or "",
|
|
3565
|
+
"member_count": len(s.get("members") or []),
|
|
3566
|
+
}
|
|
3567
|
+
for s in tangle_modules
|
|
3568
|
+
],
|
|
3569
|
+
# BUG-05 fix: don't recommend "Start with hotspot_candidates" when the list is empty.
|
|
3570
|
+
"recommendation": (
|
|
3571
|
+
(
|
|
3572
|
+
"Start with hotspot_candidates (high fan-in = highest blast radius). "
|
|
3573
|
+
if hotspots else
|
|
3574
|
+
"high_coupling_nodes shows the most-referenced classes — start there. "
|
|
3575
|
+
)
|
|
3576
|
+
+ "Dead zones are safe to remove or refactor. "
|
|
3577
|
+
+ "Cross-module tangles indicate coupling worth decomposing."
|
|
3578
|
+
),
|
|
3579
|
+
}
|
|
3530
3580
|
|
|
3531
3581
|
result = trim_to_budget(result, BUDGET_ONBOARD, label="modernize")
|
|
3532
3582
|
output = _json.dumps(result, indent=2, ensure_ascii=False)
|
|
@@ -3619,9 +3669,6 @@ def mcp_serve() -> None:
|
|
|
3619
3669
|
}
|
|
3620
3670
|
}
|
|
3621
3671
|
"""
|
|
3622
|
-
from sourcecode.license import require_pro as _require_pro
|
|
3623
|
-
_require_pro("mcp serve")
|
|
3624
|
-
|
|
3625
3672
|
import logging
|
|
3626
3673
|
import sys as _sys
|
|
3627
3674
|
|
|
@@ -3777,7 +3824,7 @@ def mcp_init(
|
|
|
3777
3824
|
raise typer.Exit(code=1)
|
|
3778
3825
|
|
|
3779
3826
|
typer.echo("MCP integration active.")
|
|
3780
|
-
typer.echo(" Note: repo_path
|
|
3827
|
+
typer.echo(" Note: repo_path must use forward slashes: C:/Users/... or /unix/path")
|
|
3781
3828
|
typer.echo("")
|
|
3782
3829
|
|
|
3783
3830
|
# Post-write: validate config and warn if client not running
|
|
@@ -3929,7 +3976,7 @@ def mcp_status() -> None:
|
|
|
3929
3976
|
typer.echo(sep)
|
|
3930
3977
|
typer.echo(" Note: 'configured' and 'running' are checked independently.")
|
|
3931
3978
|
typer.echo(" A running app still needs restart after first-time config.")
|
|
3932
|
-
typer.echo(" Path: repo_path
|
|
3979
|
+
typer.echo(" Path: repo_path must use forward slashes: C:/Users/... or /unix/path")
|
|
3933
3980
|
typer.echo(" Setup: sourcecode mcp init")
|
|
3934
3981
|
typer.echo(" Remove: sourcecode mcp remove")
|
|
3935
3982
|
|
sourcecode/context_summarizer.py
CHANGED
|
@@ -1118,7 +1118,7 @@ class DependencyAnalyzer:
|
|
|
1118
1118
|
try:
|
|
1119
1119
|
tree = ET.parse(pom)
|
|
1120
1120
|
except (ET.ParseError, OSError):
|
|
1121
|
-
return [], ["java: error
|
|
1121
|
+
return [], ["java: error parsing pom.xml"]
|
|
1122
1122
|
|
|
1123
1123
|
root_elem = tree.getroot()
|
|
1124
1124
|
ns_match = re.match(r"\{[^}]+\}", root_elem.tag)
|
|
@@ -1217,10 +1217,10 @@ class DependencyAnalyzer:
|
|
|
1217
1217
|
try:
|
|
1218
1218
|
content = gradle_file.read_text(encoding="utf-8", errors="replace")
|
|
1219
1219
|
except OSError:
|
|
1220
|
-
return [], [f"gradle: error
|
|
1220
|
+
return [], [f"gradle: error reading {filename}"]
|
|
1221
1221
|
props = self._parse_gradle_properties(root, content)
|
|
1222
1222
|
records = self._parse_gradle_dependencies(content, props, filename)
|
|
1223
|
-
return records, ["gradle:
|
|
1223
|
+
return records, ["gradle: no compatible lockfile found; transitive dependencies unavailable"]
|
|
1224
1224
|
return [], []
|
|
1225
1225
|
|
|
1226
1226
|
def _parse_gradle_properties(self, root: Path, content: str) -> dict[str, str]:
|
sourcecode/detectors/java.py
CHANGED
|
@@ -23,6 +23,7 @@ _MAX_ANNOTATION_ENTRY_POINTS = 1000
|
|
|
23
23
|
_REST_CONTROLLER_RE = re.compile(r'@RestController\b')
|
|
24
24
|
_MVC_CONTROLLER_RE = re.compile(r'@Controller\b')
|
|
25
25
|
_REQUEST_MAPPING_RE = re.compile(r'@RequestMapping\b')
|
|
26
|
+
_ANY_MAPPING_RE = re.compile(r'@(?:Request|Get|Post|Put|Delete|Patch)Mapping\b')
|
|
26
27
|
_CONTROLLER_ADVICE_RE = re.compile(r'@ControllerAdvice\b')
|
|
27
28
|
_WEB_FILTER_RE = re.compile(r'@WebFilter\b')
|
|
28
29
|
_FILTER_BEAN_RE = re.compile(r'FilterRegistrationBean\b')
|
|
@@ -298,6 +299,10 @@ class JavaDetector(AbstractDetector):
|
|
|
298
299
|
frameworks.append(FrameworkDetection(name="Jakarta EE", source=source))
|
|
299
300
|
if "mybatis" in text:
|
|
300
301
|
frameworks.append(FrameworkDetection(name="MyBatis", source=source))
|
|
302
|
+
if "thymeleaf" in text:
|
|
303
|
+
frameworks.append(FrameworkDetection(name="Thymeleaf", source=source))
|
|
304
|
+
if "freemarker" in text:
|
|
305
|
+
frameworks.append(FrameworkDetection(name="FreeMarker", source=source))
|
|
301
306
|
if "spring-boot-starter-security" in text or "spring-security-core" in text:
|
|
302
307
|
frameworks.append(FrameworkDetection(name="Spring Security", source=source))
|
|
303
308
|
if "spring-boot-starter-data-jpa" in text or "spring-data-jpa" in text:
|
|
@@ -470,7 +475,7 @@ class JavaDetector(AbstractDetector):
|
|
|
470
475
|
path=rel_path, stack="java", kind="exception_handler",
|
|
471
476
|
source="annotation", confidence="medium",
|
|
472
477
|
)]
|
|
473
|
-
if _MVC_CONTROLLER_RE.search(content) and
|
|
478
|
+
if _MVC_CONTROLLER_RE.search(content) and _ANY_MAPPING_RE.search(content):
|
|
474
479
|
http_path_match = _HTTP_PATH_RE.search(content)
|
|
475
480
|
http_path = http_path_match.group(1) if http_path_match else None
|
|
476
481
|
verb_match = _REQUEST_METHOD_VERB_RE.search(content)
|
sourcecode/license.py
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Flow:
|
|
4
4
|
1. Module imported → _init() loads ~/.sourcecode/license.json (if present)
|
|
5
5
|
2. is_pro set globally (True when plan == "pro")
|
|
6
|
-
3. Pro commands call
|
|
6
|
+
3. Pro commands call require_feature(feature_name) at entry — exits 1 if not Pro
|
|
7
7
|
4. `sourcecode activate <key>` calls activate_license(key) — validates via
|
|
8
8
|
Edge Function, writes ~/.sourcecode/license.json, exits 0 on success
|
|
9
9
|
5. Cached license is re-validated every 24 h (online); network errors keep
|
|
@@ -38,6 +38,53 @@ _LICENSE_DIR: Path = Path.home() / ".sourcecode"
|
|
|
38
38
|
_LICENSE_FILE: Path = _LICENSE_DIR / "license.json"
|
|
39
39
|
_CACHE_TTL_SECONDS: int = 86400 # 24 hours
|
|
40
40
|
|
|
41
|
+
# ---------------------------------------------------------------------------
|
|
42
|
+
# Per-feature descriptions for upgrade UX
|
|
43
|
+
# ---------------------------------------------------------------------------
|
|
44
|
+
_FEATURE_INFO: dict[str, dict[str, str]] = {
|
|
45
|
+
"impact": {
|
|
46
|
+
"display": "impact",
|
|
47
|
+
"description": (
|
|
48
|
+
"Shows blast radius, callers, affected endpoints, and persistence paths in one call."
|
|
49
|
+
),
|
|
50
|
+
"value": "Answers: what breaks if I touch this? The core risk signal before any change.",
|
|
51
|
+
},
|
|
52
|
+
"modernize": {
|
|
53
|
+
"display": "modernize (full)",
|
|
54
|
+
"description": (
|
|
55
|
+
"Full analysis: dead zones, refactor candidates, dependency tangles, and coupling ranked by git churn."
|
|
56
|
+
),
|
|
57
|
+
"value": "Prioritizes where to refactor and what is safe to touch.",
|
|
58
|
+
},
|
|
59
|
+
"fix-bug": {
|
|
60
|
+
"display": "fix-bug (full)",
|
|
61
|
+
"description": "Complete risk-ranked file list with all annotation and structural signals.",
|
|
62
|
+
"value": "More results means less time scanning the codebase manually.",
|
|
63
|
+
},
|
|
64
|
+
"review-pr": {
|
|
65
|
+
"display": "review-pr (expanded)",
|
|
66
|
+
"description": "Full PR review: blast radius, all execution paths, security and transaction impact.",
|
|
67
|
+
"value": "CI-grade review — the complete picture before merging.",
|
|
68
|
+
},
|
|
69
|
+
"delta": {
|
|
70
|
+
"display": "prepare-context delta",
|
|
71
|
+
"description": "Incremental context: git-changed files with impact propagation.",
|
|
72
|
+
"value": "Designed for CI/CD pipelines — runs on every PR, flags risk automatically.",
|
|
73
|
+
},
|
|
74
|
+
"generate-tests": {
|
|
75
|
+
"display": "prepare-context generate-tests",
|
|
76
|
+
"description": "Test gap analysis: finds untested files with coverage recommendations.",
|
|
77
|
+
"value": "Reduces test debt systematically across the entire codebase.",
|
|
78
|
+
},
|
|
79
|
+
"--full": {
|
|
80
|
+
"display": "--full flag",
|
|
81
|
+
"description": (
|
|
82
|
+
"Removes truncation limits on transactional boundaries, DTO mappers, and large result sets."
|
|
83
|
+
),
|
|
84
|
+
"value": "Essential for complete analysis of enterprise-scale codebases.",
|
|
85
|
+
},
|
|
86
|
+
}
|
|
87
|
+
|
|
41
88
|
# ---------------------------------------------------------------------------
|
|
42
89
|
# Global license state — loaded once at import time
|
|
43
90
|
# ---------------------------------------------------------------------------
|
|
@@ -147,30 +194,74 @@ _init()
|
|
|
147
194
|
|
|
148
195
|
|
|
149
196
|
# ---------------------------------------------------------------------------
|
|
150
|
-
#
|
|
197
|
+
# Entitlement helpers
|
|
151
198
|
# ---------------------------------------------------------------------------
|
|
152
199
|
|
|
153
|
-
def
|
|
154
|
-
"""
|
|
200
|
+
def can_use(feature_name: str) -> bool:
|
|
201
|
+
"""Return True if the current plan has access to feature_name.
|
|
202
|
+
|
|
203
|
+
Does not trigger revalidation — use require_feature() at command entry
|
|
204
|
+
points where you want revalidation + gating in one call.
|
|
205
|
+
"""
|
|
206
|
+
return is_pro
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def require_feature(feature_name: str) -> None:
|
|
210
|
+
"""Exit with a clean upgrade prompt when feature_name requires Pro.
|
|
155
211
|
|
|
156
212
|
Re-validates stale cached license before gating (once per 24 h, online).
|
|
157
213
|
|
|
214
|
+
Writes human-readable context to stderr (terminal UX) and a JSON error
|
|
215
|
+
to stdout (backward-compatible machine-readable format).
|
|
216
|
+
|
|
217
|
+
Example:
|
|
218
|
+
from sourcecode.license import require_feature
|
|
219
|
+
require_feature("impact")
|
|
220
|
+
"""
|
|
221
|
+
_maybe_revalidate()
|
|
222
|
+
|
|
223
|
+
if is_pro:
|
|
224
|
+
return
|
|
225
|
+
|
|
226
|
+
info = _FEATURE_INFO.get(feature_name, {})
|
|
227
|
+
display = info.get("display", feature_name)
|
|
228
|
+
description = info.get("description", "")
|
|
229
|
+
value = info.get("value", "")
|
|
230
|
+
|
|
231
|
+
# Human-readable upgrade prompt on stderr
|
|
232
|
+
lines = [f"\n '{display}' is a Pro feature."]
|
|
233
|
+
if description:
|
|
234
|
+
lines.append(f" {description}")
|
|
235
|
+
if value:
|
|
236
|
+
lines.append(f" {value}")
|
|
237
|
+
lines.append("")
|
|
238
|
+
lines.append(" Upgrade: sourcecode activate <license_key>")
|
|
239
|
+
lines.append("")
|
|
240
|
+
sys.stderr.write("\n".join(lines) + "\n")
|
|
241
|
+
sys.stderr.flush()
|
|
242
|
+
|
|
243
|
+
# JSON on stdout — backward-compatible for CI / MCP consumers
|
|
244
|
+
payload = {
|
|
245
|
+
"error": "pro_required",
|
|
246
|
+
"feature": feature_name,
|
|
247
|
+
"message": (
|
|
248
|
+
f"'{display}' requires a Pro license. "
|
|
249
|
+
"Run: sourcecode activate <license_key>"
|
|
250
|
+
),
|
|
251
|
+
}
|
|
252
|
+
sys.stdout.write(json.dumps(payload, ensure_ascii=False) + "\n")
|
|
253
|
+
sys.stdout.flush()
|
|
254
|
+
sys.exit(1)
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def require_pro(feature_name: str) -> None:
|
|
258
|
+
"""Backward-compatible alias for require_feature.
|
|
259
|
+
|
|
158
260
|
Example:
|
|
159
261
|
from sourcecode.license import require_pro
|
|
160
262
|
require_pro("impact")
|
|
161
263
|
"""
|
|
162
|
-
|
|
163
|
-
_maybe_revalidate()
|
|
164
|
-
|
|
165
|
-
if not is_pro:
|
|
166
|
-
payload = {
|
|
167
|
-
"error": "pro_required",
|
|
168
|
-
"feature": feature_name,
|
|
169
|
-
"message": "Run: sourcecode activate <license_key>",
|
|
170
|
-
}
|
|
171
|
-
sys.stdout.write(json.dumps(payload, ensure_ascii=False) + "\n")
|
|
172
|
-
sys.stdout.flush()
|
|
173
|
-
sys.exit(1)
|
|
264
|
+
require_feature(feature_name)
|
|
174
265
|
|
|
175
266
|
|
|
176
267
|
# ---------------------------------------------------------------------------
|
sourcecode/prepare_context.py
CHANGED
|
@@ -981,12 +981,18 @@ class TaskContextBuilder:
|
|
|
981
981
|
|
|
982
982
|
dep_records, dep_summary = DependencyAnalyzer().analyze(self.root)
|
|
983
983
|
primary_eco = stacks[0].stack if stacks else ""
|
|
984
|
-
|
|
984
|
+
_direct_raw = [
|
|
985
985
|
d for d in dep_records
|
|
986
986
|
if d.scope != "transitive" and d.source in {"manifest", "lockfile"}
|
|
987
987
|
and (d.role or "unknown") in {"runtime", "parsing", "serialization", "observability", "infra"}
|
|
988
988
|
and d.scope not in {"dev"}
|
|
989
989
|
]
|
|
990
|
+
_seen_dep: set[str] = set()
|
|
991
|
+
direct = []
|
|
992
|
+
for _d in _direct_raw:
|
|
993
|
+
if _d.name not in _seen_dep:
|
|
994
|
+
_seen_dep.add(_d.name)
|
|
995
|
+
direct.append(_d)
|
|
990
996
|
# Rank by framework centrality: core infra (ORM, Spring) > serialization > other.
|
|
991
997
|
# Penalise vendored tooling (closure-compiler, shaded utilities) so that
|
|
992
998
|
# Hibernate/JPA/Solr appear before minor build-time dependencies.
|
sourcecode/repository_ir.py
CHANGED
|
@@ -1697,6 +1697,9 @@ def _canonical_subsystem_pkg(fqn: str) -> str:
|
|
|
1697
1697
|
(even if uppercase) to force at least 2-segment grouping.
|
|
1698
1698
|
"""
|
|
1699
1699
|
_TOP_LEVEL = {"com", "org", "net", "io", "java", "javax"}
|
|
1700
|
+
# Well-known framework namespaces that are not application boundaries;
|
|
1701
|
+
# go one level deeper (depth 5) so callers get the actual app module.
|
|
1702
|
+
_FRAMEWORK_NS = {"springframework", "apache", "eclipse", "google", "jetbrains"}
|
|
1700
1703
|
parts: list[str] = []
|
|
1701
1704
|
for segment in fqn.split("."):
|
|
1702
1705
|
if "#" in segment or (segment and segment[0].isupper()):
|
|
@@ -1705,6 +1708,8 @@ def _canonical_subsystem_pkg(fqn: str) -> str:
|
|
|
1705
1708
|
if not parts:
|
|
1706
1709
|
return fqn.rsplit(".", 1)[0] if "." in fqn else fqn
|
|
1707
1710
|
if parts[0] in _TOP_LEVEL and len(parts) >= 3:
|
|
1711
|
+
if len(parts) >= 5 and len(parts) > 3 and parts[1] in _FRAMEWORK_NS:
|
|
1712
|
+
return ".".join(parts[:5])
|
|
1708
1713
|
return ".".join(parts[:3])
|
|
1709
1714
|
# Prevent bare TLD collapse: "org" or "com" alone as subsystem key is meaningless
|
|
1710
1715
|
# and groups ALL classes under that TLD into a single giant component.
|
|
@@ -3338,7 +3343,7 @@ def compute_blast_radius(
|
|
|
3338
3343
|
_seen_mapper_fqns.add(fqn)
|
|
3339
3344
|
_mapper_entry: dict = {
|
|
3340
3345
|
"fqn": fqn,
|
|
3341
|
-
"role": role or "mapper",
|
|
3346
|
+
"role": role or ("mapper" if symbol_kind == "mapper_interface" else "repository"),
|
|
3342
3347
|
"source_file": node_dict.get("source_file") or "",
|
|
3343
3348
|
}
|
|
3344
3349
|
if canonical != fqn:
|
sourcecode/summarizer.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sourcecode
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.32.1
|
|
4
4
|
Summary: Deterministic codebase context for AI coding agents
|
|
5
5
|
License: Apache License
|
|
6
6
|
Version 2.0, January 2004
|
|
@@ -225,7 +225,7 @@ Description-Content-Type: text/markdown
|
|
|
225
225
|
|
|
226
226
|
**AI-ready change intelligence for Java/Spring enterprise monoliths.**
|
|
227
227
|
|
|
228
|
-

|
|
229
229
|

|
|
230
230
|
|
|
231
231
|
---
|
|
@@ -263,7 +263,7 @@ pipx install sourcecode
|
|
|
263
263
|
|
|
264
264
|
```bash
|
|
265
265
|
sourcecode version
|
|
266
|
-
# sourcecode 1.
|
|
266
|
+
# sourcecode 1.32.0
|
|
267
267
|
```
|
|
268
268
|
|
|
269
269
|
---
|
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
sourcecode/__init__.py,sha256=
|
|
1
|
+
sourcecode/__init__.py,sha256=fI8Cp1YcT6ueuKNKvHveO2gFDtDkSHrDE6DpgADO1i8,103
|
|
2
2
|
sourcecode/adaptive_scanner.py,sha256=XffluXKzJUXrMtjEiAOnSNPZnztdIcts17T9ouHeID0,10521
|
|
3
|
-
sourcecode/architecture_analyzer.py,sha256=
|
|
3
|
+
sourcecode/architecture_analyzer.py,sha256=qh749a7ykPtGmQI1MR9y6j8TtL_jBdVYFx9YRsLqOMw,44121
|
|
4
4
|
sourcecode/architecture_summary.py,sha256=z34_6v7cSwy98cof2UVciGho7SCrZ93tiqMmq5WNzRQ,20405
|
|
5
5
|
sourcecode/ast_extractor.py,sha256=_btmeOJIe3t-NicF94D5ZAesa2YIJ0_QNExGnbHxGFE,50578
|
|
6
6
|
sourcecode/cache.py,sha256=pBrPdpPrOgpXHHQO670U3aUfVf5N3A3obsTKgiZtN4I,23030
|
|
7
7
|
sourcecode/canonical_ir.py,sha256=_HM3AUmKSdna9u4dCoU6rpgSA6HdF8gzOKZykIUCNGY,23277
|
|
8
|
-
sourcecode/classifier.py,sha256=
|
|
9
|
-
sourcecode/cli.py,sha256=
|
|
8
|
+
sourcecode/classifier.py,sha256=2lYoSH3vOTkXZYPU7Go2WIet1-IuNzTWVhc-ULnXtgw,8024
|
|
9
|
+
sourcecode/cli.py,sha256=xV3CKxTAzc1tcEJ6-lfGCuYlBWdLQgxv1-thMzQ3cpk,163863
|
|
10
10
|
sourcecode/code_notes_analyzer.py,sha256=EJemNCNc9Dn-1RZYu-aNbK0ELzmsyC4s6FdHi3XyNEI,9392
|
|
11
11
|
sourcecode/confidence_analyzer.py,sha256=_jckZSxksV-OU38vbkxfVNBnWCtlCq8Vwfg23x1uspA,19054
|
|
12
12
|
sourcecode/context_scorer.py,sha256=QpChSpsmaAYz91rXA4Ue5xzQmNz_ZboZN09YOHScq1U,14679
|
|
13
|
-
sourcecode/context_summarizer.py,sha256=
|
|
13
|
+
sourcecode/context_summarizer.py,sha256=zlbm8ytdvJToohU108-dwBmEl52xl0gXpf6PZBOW_2A,6540
|
|
14
14
|
sourcecode/contract_model.py,sha256=nRxJKPMs1VHwFTa8AVXhGmaLjti3Lr2sjHDpWgv1bfE,3917
|
|
15
15
|
sourcecode/contract_pipeline.py,sha256=gvTdDniedm_mjq4vaHqnBY2UkQ0s00gtXqzTLILNXHc,28719
|
|
16
16
|
sourcecode/coverage_parser.py,sha256=q0LeZJaX1bnntLu-ImksdBsMlpsVmk_iUfSaB4eaJGo,19702
|
|
17
|
-
sourcecode/dependency_analyzer.py,sha256=
|
|
17
|
+
sourcecode/dependency_analyzer.py,sha256=wE7IgXOepfagGdRJX4-u5mhps_kfR2njz2RFgPikFFY,56491
|
|
18
18
|
sourcecode/doc_analyzer.py,sha256=05bjTUbDbmnbajD_cgRnACzS8T7xxBKVX4CjkJlhZg8,24411
|
|
19
19
|
sourcecode/entrypoint_classifier.py,sha256=MTa7yqbeuJ9XPbGCPuvtR9IqY-SN3hoXXyVtb3iXDhs,4316
|
|
20
20
|
sourcecode/env_analyzer.py,sha256=9_q_U_Q1tjiY5FSaeEImSDgToP2uHGrjBAFF7ihJn2I,22204
|
|
@@ -22,25 +22,25 @@ sourcecode/file_classifier.py,sha256=QrYm7MlG29HQdAR1WOfpnIIBysAz62c5coz9eQ76meo
|
|
|
22
22
|
sourcecode/flow_analyzer.py,sha256=dSiuY4w49k29jW_EPXUOND9B5uVbuCA7kjnuHi-pIWA,28781
|
|
23
23
|
sourcecode/git_analyzer.py,sha256=JStxTQXNjBWi_wLdwhsZs9mT-v50cSJIz4Agzn6Kh9I,13362
|
|
24
24
|
sourcecode/graph_analyzer.py,sha256=iUK-7pSV-cvGqqD2hENdYmhnm0wcXFEyK-xnu5ul8OU,62515
|
|
25
|
-
sourcecode/license.py,sha256=
|
|
25
|
+
sourcecode/license.py,sha256=m5n4PKZ7ZZZ17bpLjIsKwd94PXaIg5iS_2kFNjCV2Og,11378
|
|
26
26
|
sourcecode/mcp_nudge.py,sha256=5ELU_ixzh6uA83NXLOZT8h00OhL53okfQdji3jyKOjg,2917
|
|
27
27
|
sourcecode/metrics_analyzer.py,sha256=m0ENgtqKeBL17kUIK3fmGkgo7UfXBNHxCMj0H_Y5K7c,22750
|
|
28
28
|
sourcecode/output_budget.py,sha256=43307mJEyUPU3MI-QEQoVxrcAvNyUzdzF_SAPgisBQE,6603
|
|
29
29
|
sourcecode/path_filters.py,sha256=ROFRQ8eSLBEMiixK9f45-RO7um4VEEcjoD5AA4I427I,3739
|
|
30
30
|
sourcecode/pr_comment_renderer.py,sha256=smHslxiG14lrytCkq5nFrFu-qTHgA-t-LFYfdrfjz2o,14423
|
|
31
|
-
sourcecode/prepare_context.py,sha256=
|
|
31
|
+
sourcecode/prepare_context.py,sha256=TFg8GDJ8kotJtR4rOTAvwQGVNSj0x-Usamy-mtjxeqs,201681
|
|
32
32
|
sourcecode/progress.py,sha256=qn30sWaHOkjTgXsSBmiPkz7Rsbwc5oSlIe6JNEMYp_k,3149
|
|
33
33
|
sourcecode/ranking_engine.py,sha256=ZAucq_YX2KkWUuAZf4P0lhtQ_38vEFnUhuGtSZd1S0E,12970
|
|
34
34
|
sourcecode/redactor.py,sha256=SB4hwIvg8h-hvcqKcDWaZvA-aSyn-at-BIRwa0tUv5E,3227
|
|
35
35
|
sourcecode/relevance_scorer.py,sha256=MYF4FFkveAQps9SmTeTlh6ODiBz2F--_hWNeHMLtUHQ,8405
|
|
36
36
|
sourcecode/repo_classifier.py,sha256=FG1vaWKdWXsWdl-S8hjVMiTqcwgaRXkDyvK4rPcOGtQ,22681
|
|
37
|
-
sourcecode/repository_ir.py,sha256
|
|
37
|
+
sourcecode/repository_ir.py,sha256=-NjBQUT7zyya4ng8Hq0-ChoiHZkUif9lr-Q878gmj8M,153163
|
|
38
38
|
sourcecode/runtime_classifier.py,sha256=uTAD6BDCiBLUZEDRfqk718kM4RTT_vAbfkcOI2_Xx58,18432
|
|
39
39
|
sourcecode/scanner.py,sha256=WdOQ78mMzjR1NjmKTlbxdgwinnCTfAhxCVLBEFQiFHU,8899
|
|
40
40
|
sourcecode/schema.py,sha256=aHNXDf8LGyUC8ZDE_VS9kiskC2-Oswhi_WnpdGy6HDw,24897
|
|
41
41
|
sourcecode/semantic_analyzer.py,sha256=TDuC3wzZR2DPm1mgrAg1YSLk2QzJoueS3TZAmyGGpCU,89417
|
|
42
42
|
sourcecode/serializer.py,sha256=3JBvsMDj5pt1RXpi1zJpk5DGWUKbeb2Jl622-kmYWD4,123312
|
|
43
|
-
sourcecode/summarizer.py,sha256=
|
|
43
|
+
sourcecode/summarizer.py,sha256=YspHEVeYJVmltq0FMtGZF8kIP3qiR2KLcanGL6Y7uTI,20747
|
|
44
44
|
sourcecode/tree_utils.py,sha256=8GAkIfQAsvtEudIeW1l4ooH_oRtrWR8cpJQJsEa_Pfw,2093
|
|
45
45
|
sourcecode/workspace.py,sha256=X_6NmNnitvT3_38V-JDChydo_sR68s249hLFlrQskU0,8271
|
|
46
46
|
sourcecode/detectors/__init__.py,sha256=A0AACJFF6HWf_RgatNtWu3PUzstcKtIGM9f1PoFcJug,1987
|
|
@@ -52,7 +52,7 @@ sourcecode/detectors/elixir.py,sha256=jCpvt5Yi6jvplc80ovRtWh17q-11ZGo9qX7o8b57TJ
|
|
|
52
52
|
sourcecode/detectors/go.py,sha256=2r66uRQfeTWsqxr4HDhT6vExZErby0t46QXLHVBRv9w,2782
|
|
53
53
|
sourcecode/detectors/heuristic.py,sha256=7cRxrip4yIaggYzZJB6ef8yHKh-gHgiH_pXMFcjlyFU,3723
|
|
54
54
|
sourcecode/detectors/hybrid.py,sha256=IGFRUVsAZ1ooRlFdznCeJAV6vy1yVDx-VyghvLtddXc,9101
|
|
55
|
-
sourcecode/detectors/java.py,sha256=
|
|
55
|
+
sourcecode/detectors/java.py,sha256=BMdDLBe6vzMa8dqNc8cZ6An0OpQmW5oWcJ02ULzHC_Q,29288
|
|
56
56
|
sourcecode/detectors/jvm_ext.py,sha256=EgHJ5W8EE-ZTN9V607mVzohyKgZE8Mc2jCi-DF8RAZU,2616
|
|
57
57
|
sourcecode/detectors/nodejs.py,sha256=Hg3Gmr7yIMJFiLoDwOTk2wtu00wxIs6kZf-oQujTFUA,13187
|
|
58
58
|
sourcecode/detectors/parsers.py,sha256=ugPg8yNUf0Ai1gA7Fnn6wAkYGFjTxRodSP3IeViYJJ4,2290
|
|
@@ -78,8 +78,8 @@ sourcecode/telemetry/consent.py,sha256=wLMvGNJeSSyZoNkQXpoUioY6mMv4Qdvuw7S9jAEWn
|
|
|
78
78
|
sourcecode/telemetry/events.py,sha256=oEvvulfsv5GIDWG2174gSS6tNB95w38AIYiYeifGKlE,2294
|
|
79
79
|
sourcecode/telemetry/filters.py,sha256=Asa71oRl7q3Wt_FMwuufIZJFzSYdgRNKS8LHCIyFeYE,4805
|
|
80
80
|
sourcecode/telemetry/transport.py,sha256=KJeIPCPWMdmbCP3ySGs2iUlia34U6vWne2dZsUezesw,1560
|
|
81
|
-
sourcecode-1.
|
|
82
|
-
sourcecode-1.
|
|
83
|
-
sourcecode-1.
|
|
84
|
-
sourcecode-1.
|
|
85
|
-
sourcecode-1.
|
|
81
|
+
sourcecode-1.32.1.dist-info/METADATA,sha256=llWp2sPfqGuDQXERl94vF19Sxpktl_zPUyDNDwT4ORM,31100
|
|
82
|
+
sourcecode-1.32.1.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
83
|
+
sourcecode-1.32.1.dist-info/entry_points.txt,sha256=ex3F9rmbXeyDIoFQHtkEqTsKSaJow8F0LrVu8XfIktQ,57
|
|
84
|
+
sourcecode-1.32.1.dist-info/licenses/LICENSE,sha256=7DdHrU9Z_3e7dSvq4ISijZNjnuHo5NIHNiHDouMQ9JU,10491
|
|
85
|
+
sourcecode-1.32.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|