sourcecode 1.31.20__py3-none-any.whl → 1.31.22__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 CHANGED
@@ -1,3 +1,3 @@
1
1
  """sourcecode — Deterministic codebase context maps for AI coding agents."""
2
2
 
3
- __version__ = "1.31.20"
3
+ __version__ = "1.31.22"
@@ -1131,6 +1131,23 @@ def _detect_role(path: str, contract: FileContract) -> str:
1131
1131
  if any(n.startswith("use") and len(n) > 3 and n[3:4].isupper() for n in export_names):
1132
1132
  return "hook"
1133
1133
 
1134
+ # Angular-specific roles (.ts files): detect by stem suffix before generic checks.
1135
+ # Must run before the "service" path-keyword check to prevent misclassification.
1136
+ if ext == ".ts":
1137
+ _ts_last = Path(path).stem.lower().rsplit(".", 1)[-1]
1138
+ _NG_ROLE_MAP = {
1139
+ "component": "component",
1140
+ "pipe": "pipe",
1141
+ "directive": "directive",
1142
+ "guard": "guard",
1143
+ "interceptor": "interceptor",
1144
+ "resolver": "resolver",
1145
+ "module": "module",
1146
+ "service": "service",
1147
+ }
1148
+ if _ts_last in _NG_ROLE_MAP:
1149
+ return _NG_ROLE_MAP[_ts_last]
1150
+
1134
1151
  # Route / page
1135
1152
  if any(x in path_lower for x in ["/routes/", "/route.", "/pages/", "/api/", "/handlers/"]):
1136
1153
  return "route"
sourcecode/cli.py CHANGED
@@ -238,6 +238,21 @@ def _preprocess_argv() -> None:
238
238
  _sys.argv = _sys.argv[:1] + modified
239
239
 
240
240
 
241
+ def _emit_error_json(error: str, message: str, **context: object) -> None:
242
+ """Write a structured JSON error envelope to stderr.
243
+
244
+ Format: {"error": "<code>", "message": "<human text>", ...<context>}
245
+ All CLI validation and runtime errors must go through this helper so that
246
+ agents and tools can parse stderr reliably regardless of error type.
247
+ """
248
+ import json as _json
249
+ import sys as _sys
250
+ payload: dict[str, object] = {"error": error, "message": message}
251
+ payload.update(context)
252
+ _sys.stderr.write(_json.dumps(payload, ensure_ascii=False) + "\n")
253
+ _sys.stderr.flush()
254
+
255
+
241
256
  def _copy_to_clipboard(content: str) -> bool:
242
257
  """Copy text to system clipboard. Returns True on success, False otherwise (never raises)."""
243
258
  import subprocess
@@ -751,21 +766,30 @@ def main(
751
766
 
752
767
  # Validate format choices
753
768
  if format not in FORMAT_CHOICES:
754
- typer.echo(
755
- f"Error: invalid value '{format}' for --format. Valid options: {', '.join(FORMAT_CHOICES)}",
756
- err=True,
769
+ _emit_error_json(
770
+ "invalid_flag_value",
771
+ f"Invalid value '{format}' for --format. Valid values: {', '.join(FORMAT_CHOICES)}.",
772
+ flag="--format",
773
+ value=format,
774
+ valid_values=list(FORMAT_CHOICES),
757
775
  )
758
776
  raise typer.Exit(code=2) # FIX-P2-7: arg validation → exit 2
759
777
  if graph_detail not in GRAPH_DETAIL_CHOICES:
760
- typer.echo(
761
- f"Error: invalid value '{graph_detail}' for --graph-detail. Valid options: {', '.join(GRAPH_DETAIL_CHOICES)}",
762
- err=True,
778
+ _emit_error_json(
779
+ "invalid_flag_value",
780
+ f"Invalid value '{graph_detail}' for --graph-detail. Valid values: {', '.join(GRAPH_DETAIL_CHOICES)}.",
781
+ flag="--graph-detail",
782
+ value=graph_detail,
783
+ valid_values=list(GRAPH_DETAIL_CHOICES),
763
784
  )
764
785
  raise typer.Exit(code=2) # FIX-P2-7: arg validation → exit 2
765
786
  if docs_depth not in DOCS_DEPTH_CHOICES:
766
- typer.echo(
767
- f"Error: invalid value '{docs_depth}' for --docs-depth. Valid options: {', '.join(DOCS_DEPTH_CHOICES)}",
768
- err=True,
787
+ _emit_error_json(
788
+ "invalid_flag_value",
789
+ f"Invalid value '{docs_depth}' for --docs-depth. Valid values: {', '.join(DOCS_DEPTH_CHOICES)}.",
790
+ flag="--docs-depth",
791
+ value=docs_depth,
792
+ valid_values=list(DOCS_DEPTH_CHOICES),
769
793
  )
770
794
  raise typer.Exit(code=2) # FIX-P2-7: arg validation → exit 2
771
795
 
@@ -775,10 +799,18 @@ def main(
775
799
  _raw_path_input = _detected_path[0]
776
800
  target = Path(_raw_path_input).resolve()
777
801
  if not target.exists():
778
- typer.echo(f"Error: directory '{_raw_path_input}' does not exist.", err=True)
802
+ _emit_error_json(
803
+ "directory_not_found",
804
+ f"Directory '{_raw_path_input}' does not exist.",
805
+ path=_raw_path_input,
806
+ )
779
807
  raise typer.Exit(code=1)
780
808
  if not target.is_dir():
781
- typer.echo(f"Error: '{_raw_path_input}' is not a directory.", err=True)
809
+ _emit_error_json(
810
+ "not_a_directory",
811
+ f"Path '{_raw_path_input}' is not a directory.",
812
+ path=_raw_path_input,
813
+ )
782
814
  raise typer.Exit(code=1)
783
815
 
784
816
  # Normalize mode aliases
@@ -1201,6 +1233,15 @@ def main(
1201
1233
  workspace_root = target / workspace.path
1202
1234
  if not workspace_root.exists() or not workspace_root.is_dir():
1203
1235
  continue
1236
+ # BUG-2: skip workspaces explicitly excluded via --exclude.
1237
+ # Without this guard, excluded frontend/backend modules still contribute
1238
+ # their stacks and entry_points, causing architecture_summary to describe
1239
+ # stacks that were intentionally filtered out.
1240
+ if _extra_excludes:
1241
+ _ws_norm = workspace.path.replace("\\", "/").strip("/")
1242
+ _ws_parts = frozenset(_ws_norm.split("/"))
1243
+ if _ws_parts & _extra_excludes:
1244
+ continue
1204
1245
  _ws_topology = RepoClassifier().classify(workspace_root)
1205
1246
  workspace_scanner = AdaptiveScanner(workspace_root, topology=_ws_topology, base_depth=depth)
1206
1247
  workspace_tree = filter_sensitive_files(workspace_scanner.scan_tree())
@@ -1776,8 +1817,21 @@ def main(
1776
1817
  if changed_only and _allowed_changed_files:
1777
1818
  # GAP-5: preserve full entry_points for architecture context even in
1778
1819
  # --changed-only mode. Only filter file_paths and code_notes.
1820
+ # ALWAYS-INCLUDE: security-const files must stay in file_paths even when
1821
+ # not in the git diff — they resolve Java constant references used in
1822
+ # @M3FiltroSeguridad annotations (read-only anchors, not diff output).
1823
+ def _is_always_include_ref(p: str) -> bool:
1824
+ name = p.rsplit("/", 1)[-1].rsplit("\\", 1)[-1]
1825
+ if name.endswith("Const.java") or name.endswith("Constants.java"):
1826
+ return True
1827
+ parts = p.replace("\\", "/").lower().split("/")
1828
+ return any(seg in ("security", "seguridad", "constantes") for seg in parts)
1829
+
1779
1830
  sm = _replace(sm,
1780
- file_paths=[p for p in sm.file_paths if p in _allowed_changed_files],
1831
+ file_paths=[
1832
+ p for p in sm.file_paths
1833
+ if p in _allowed_changed_files or _is_always_include_ref(p)
1834
+ ],
1781
1835
  code_notes=[n for n in sm.code_notes if n.path in _allowed_changed_files],
1782
1836
  )
1783
1837
  data = compact_view(sm, no_tree=no_tree, full=full)
@@ -242,7 +242,27 @@ class ContractPipeline:
242
242
  ]
243
243
 
244
244
  if changed_only:
245
- src_paths = [p for p in src_paths if p in changed_files]
245
+ def _is_always_include_ref(p: str) -> bool:
246
+ """Always include security-const files regardless of diff status.
247
+
248
+ These are read-only reference anchors: they resolve Java constant
249
+ references (e.g. SeguridadRecursosConst.REC_X) used in security
250
+ annotations. They do NOT appear in diff output because
251
+ contract.is_changed is set from ``changed_files`` only (line below).
252
+ Criteria per bug-spec:
253
+ - name ends with Const.java or Constants.java
254
+ - any path segment is: security | seguridad | constantes
255
+ """
256
+ name = p.rsplit("/", 1)[-1].rsplit("\\", 1)[-1]
257
+ if name.endswith("Const.java") or name.endswith("Constants.java"):
258
+ return True
259
+ parts = p.replace("\\", "/").lower().split("/")
260
+ return any(seg in ("security", "seguridad", "constantes") for seg in parts)
261
+
262
+ src_paths = [
263
+ p for p in src_paths
264
+ if p in changed_files or _is_always_include_ref(p)
265
+ ]
246
266
 
247
267
  # Apply max_files cap — bypass when symbol search to ensure defining files are found.
248
268
  # A symbol query over a large repo needs all files; result set is small after filtering.
sourcecode/mcp/server.py CHANGED
@@ -10,10 +10,12 @@ data is the parsed JSON object from the CLI output, not a shell string.
10
10
  """
11
11
  from __future__ import annotations
12
12
 
13
+ import json
13
14
  import os
14
15
  from typing import Any
15
16
 
16
17
  from mcp.server.fastmcp import FastMCP
18
+ from mcp.types import CallToolResult, TextContent
17
19
 
18
20
  from sourcecode import __version__ as _sourcecode_version
19
21
  from sourcecode.mcp.runner import run_command
@@ -30,15 +32,35 @@ def _ok(data: Any) -> dict:
30
32
  return {"success": True, "data": data, "error": None}
31
33
 
32
34
 
33
- def _err(message: str, code: str = "EXECUTION_FAILED") -> dict:
34
- return {"success": False, "data": None, "error": {"code": code, "message": message}}
35
+ def _err(message: str, code: str = "EXECUTION_FAILED") -> CallToolResult:
36
+ """Return an MCP tool-error result with isError=True per MCP spec §tool-result."""
37
+ payload = {"success": False, "data": None, "error": {"code": code, "message": message}}
38
+ return CallToolResult(
39
+ content=[TextContent(type="text", text=json.dumps(payload))],
40
+ isError=True,
41
+ )
35
42
 
36
43
 
37
- def _execute(args: list[str]) -> dict:
44
+ def _execute(args: list[str]) -> dict | CallToolResult:
38
45
  try:
39
- return _ok(run_command(args))
46
+ result = run_command(args)
40
47
  except RuntimeError as exc:
41
48
  return _err(str(exc))
49
+ # If CLI output itself signals failure via success:false, propagate as isError=True
50
+ if isinstance(result, dict) and result.get("success") is False:
51
+ payload = {
52
+ "success": False,
53
+ "data": None,
54
+ "error": result.get("error") or {
55
+ "code": "EXECUTION_FAILED",
56
+ "message": "Command returned success=false",
57
+ },
58
+ }
59
+ return CallToolResult(
60
+ content=[TextContent(type="text", text=json.dumps(payload))],
61
+ isError=True,
62
+ )
63
+ return _ok(result)
42
64
 
43
65
 
44
66
  @mcp.tool()
@@ -589,6 +589,15 @@ _ARTIFACT_CHANGE_EFFECT: dict[str, str] = {
589
589
  "documentation": "documentation file (no runtime impact)",
590
590
  "ide_noise": "IDE/tooling artifact (no application impact)",
591
591
  "source": "application source file (role could not be confirmed from code signals)",
592
+ # Angular-specific artifact types (detected before Java/Spring heuristics)
593
+ "ng_component": "UI presentation layer (Angular @Component)",
594
+ "ng_pipe": "data transformation layer (Angular @Pipe)",
595
+ "ng_directive": "DOM behavior layer (Angular @Directive)",
596
+ "ng_guard": "navigation guard (Angular CanActivate / CanActivateFn)",
597
+ "ng_interceptor": "HTTP middleware layer (Angular HttpInterceptor)",
598
+ "ng_resolver": "data pre-fetch layer (Angular Resolve)",
599
+ "ng_service": "Angular injectable service (@Injectable)",
600
+ "ng_module": "Angular feature module (@NgModule)",
592
601
  }
593
602
 
594
603
  # Maps frontend symptom keywords → backend terms likely to contain the root cause.
@@ -2710,6 +2719,29 @@ class TaskContextBuilder:
2710
2719
  ):
2711
2720
  return {"artifact_type": "entrypoint", "risk_areas": ["api", "config"], "impact_level": "critical", "is_noise": False, "module": module, "confidence": "high"}
2712
2721
 
2722
+ # Angular-specific artifact detection (.ts files only).
2723
+ # Must run BEFORE the Java/Spring heuristics so that *.component.ts,
2724
+ # *.pipe.ts, etc. are never misclassified as "service" or "security".
2725
+ # Detection is pure-path/stem — no file reads, fully deterministic.
2726
+ if suffix == ".ts":
2727
+ # Stem may be multi-part: "causa-denegacion-form.component" → last part is the Angular type
2728
+ _ts_last = stem_lower.rsplit(".", 1)[-1] # "component", "pipe", etc.
2729
+ _NG_SUFFIX_MAP = {
2730
+ "component": ("ng_component", ["ui"], "medium"),
2731
+ "pipe": ("ng_pipe", ["ui"], "low"),
2732
+ "directive": ("ng_directive", ["ui"], "medium"),
2733
+ "guard": ("ng_guard", ["security", "auth"], "high"),
2734
+ "interceptor": ("ng_interceptor", ["api"], "medium"),
2735
+ "resolver": ("ng_resolver", ["api"], "low"),
2736
+ "module": ("ng_module", ["config"], "medium"),
2737
+ }
2738
+ if _ts_last in _NG_SUFFIX_MAP:
2739
+ _ng_atype, _ng_risks, _ng_impact = _NG_SUFFIX_MAP[_ts_last]
2740
+ return {"artifact_type": _ng_atype, "risk_areas": _ng_risks, "impact_level": _ng_impact, "is_noise": False, "module": module, "confidence": "high"}
2741
+ # Angular service: stem ends with ".service" or equals "service"
2742
+ if _ts_last == "service":
2743
+ return {"artifact_type": "ng_service", "risk_areas": ["business_logic"], "impact_level": "medium", "is_noise": False, "module": module, "confidence": "high"}
2744
+
2713
2745
  # Security surface (extended: interceptor, filter, cors, acl)
2714
2746
  _SECURITY_KW = ("security", "auth", "jwt", "token", "permission", "role",
2715
2747
  "credential", "encrypt", "decrypt", "oauth", "saml", "ldap",
@@ -2724,9 +2756,11 @@ class TaskContextBuilder:
2724
2756
  if suffix in _CODE_EXTS and any(kw in stem_lower for kw in _API_KW):
2725
2757
  return {"artifact_type": "controller", "risk_areas": ["api"], "impact_level": "high", "is_noise": False, "module": module, "confidence": "high"}
2726
2758
 
2727
- # Business logic / services (extended: facade, usecase, aspect, listener, component)
2759
+ # Business logic / services (extended: facade, usecase, aspect, listener)
2760
+ # NOTE: "component" intentionally removed — Angular *.component.ts files
2761
+ # are caught above by the Angular-specific block before reaching here.
2728
2762
  _SERVICE_KW = ("service", "serviceimpl", "servicefacade", "facade", "usecase",
2729
- "interactor", "aspect", "listener", "subscriber", "eventhandler", "component")
2763
+ "interactor", "aspect", "listener", "subscriber", "eventhandler")
2730
2764
  if suffix in _CODE_EXTS and any(kw in stem_lower for kw in _SERVICE_KW):
2731
2765
  return {"artifact_type": "service", "risk_areas": ["transactions", "business_logic"], "impact_level": "high", "is_noise": False, "module": module, "confidence": "high"}
2732
2766
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sourcecode
3
- Version: 1.31.20
3
+ Version: 1.31.22
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
- ![Version](https://img.shields.io/badge/version-1.31.20-blue)
228
+ ![Version](https://img.shields.io/badge/version-1.31.22-blue)
229
229
  ![Python](https://img.shields.io/badge/python-3.10%2B-green)
230
230
 
231
231
  ---
@@ -263,7 +263,7 @@ pipx install sourcecode
263
263
 
264
264
  ```bash
265
265
  sourcecode version
266
- # sourcecode 1.31.20
266
+ # sourcecode 1.31.22
267
267
  ```
268
268
 
269
269
  ---
@@ -1,18 +1,18 @@
1
- sourcecode/__init__.py,sha256=SO-Vu2UFaZzh6UldfBANt4sXszun2WaihkKiyt-cYX4,104
1
+ sourcecode/__init__.py,sha256=Wsav7BZkVmw8XZqjz_WUnhLQyGjtZVwjYnyc_N4sraE,104
2
2
  sourcecode/adaptive_scanner.py,sha256=XffluXKzJUXrMtjEiAOnSNPZnztdIcts17T9ouHeID0,10521
3
3
  sourcecode/architecture_analyzer.py,sha256=4R13Yb02OrPeB4IH3z6V_g7HWhmGcRHbI8CobCVnRrc,39111
4
4
  sourcecode/architecture_summary.py,sha256=z34_6v7cSwy98cof2UVciGho7SCrZ93tiqMmq5WNzRQ,20405
5
- sourcecode/ast_extractor.py,sha256=XgrZg2DcWcUm9r87cRG3KGO7IK2TIL_N-CvhSbUmmh4,49901
5
+ sourcecode/ast_extractor.py,sha256=_btmeOJIe3t-NicF94D5ZAesa2YIJ0_QNExGnbHxGFE,50578
6
6
  sourcecode/cache.py,sha256=TiYa3ECjBKtvlfCk7GvQ9v6gZkAITpH3ow9PubA7sUo,22946
7
7
  sourcecode/canonical_ir.py,sha256=NZu0XICv__hkQGKzW2LNQLRqb1L28K2p_WQCQKS5Zlk,23141
8
8
  sourcecode/classifier.py,sha256=yWeq6agTjkFa3zuNa-gdVIHtjoBoPoVlJnX-b7tdVJs,7851
9
- sourcecode/cli.py,sha256=Ng9hOoMbpCXvB186etvSqV1q3I4mnndPxr3vMzcZQjc,145199
9
+ sourcecode/cli.py,sha256=qMn-4zD8v03dmkn-AZsf2TSplyhjbq9ZPMAcWl_Lrxg,147576
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
13
  sourcecode/context_summarizer.py,sha256=CiQrfBEzun949bWvmLabWoj2HhPn6Lw62ofqnsy0FlQ,6503
14
14
  sourcecode/contract_model.py,sha256=nRxJKPMs1VHwFTa8AVXhGmaLjti3Lr2sjHDpWgv1bfE,3917
15
- sourcecode/contract_pipeline.py,sha256=w18t_MdbrkIeLcCW-VMQYeb9hlWenAOB-NMiME_Fo-Y,27652
15
+ sourcecode/contract_pipeline.py,sha256=gvTdDniedm_mjq4vaHqnBY2UkQ0s00gtXqzTLILNXHc,28719
16
16
  sourcecode/coverage_parser.py,sha256=q0LeZJaX1bnntLu-ImksdBsMlpsVmk_iUfSaB4eaJGo,19702
17
17
  sourcecode/dependency_analyzer.py,sha256=Po7GKJnClCkXty0np1B4F1zo_bPeKAtgbehazhXuaBM,56493
18
18
  sourcecode/doc_analyzer.py,sha256=05bjTUbDbmnbajD_cgRnACzS8T7xxBKVX4CjkJlhZg8,24411
@@ -26,7 +26,7 @@ sourcecode/metrics_analyzer.py,sha256=m0ENgtqKeBL17kUIK3fmGkgo7UfXBNHxCMj0H_Y5K7
26
26
  sourcecode/output_budget.py,sha256=43307mJEyUPU3MI-QEQoVxrcAvNyUzdzF_SAPgisBQE,6603
27
27
  sourcecode/path_filters.py,sha256=ROFRQ8eSLBEMiixK9f45-RO7um4VEEcjoD5AA4I427I,3739
28
28
  sourcecode/pr_comment_renderer.py,sha256=smHslxiG14lrytCkq5nFrFu-qTHgA-t-LFYfdrfjz2o,14423
29
- sourcecode/prepare_context.py,sha256=j82rwu3M4qcRzrnBJfXSdhmLuWeAwSHQbHHLXnwz1dc,193567
29
+ sourcecode/prepare_context.py,sha256=_WZqHLqdRD5YftN7ebm2jXd1c-iJpNE8TzVKjK6LUbE,196037
30
30
  sourcecode/progress.py,sha256=qn30sWaHOkjTgXsSBmiPkz7Rsbwc5oSlIe6JNEMYp_k,3149
31
31
  sourcecode/ranking_engine.py,sha256=ZAucq_YX2KkWUuAZf4P0lhtQ_38vEFnUhuGtSZd1S0E,12970
32
32
  sourcecode/redactor.py,sha256=xuGcadGEHaPw4qZXlMDvzMCsr4VOkdp3oBQptHyJk8c,2884
@@ -64,7 +64,7 @@ sourcecode/detectors/terraform.py,sha256=cxORPR_zVLOJpHlh4e9JnFpkQsn_UnqMMom5yG6
64
64
  sourcecode/detectors/tooling.py,sha256=8CKbtxwQoABP-WyBRNmdAmHDOvAH57AR1cF4UKuWEdQ,2074
65
65
  sourcecode/mcp/__init__.py,sha256=XU4HfRGbdid8wdUA0x_4f7uKZD1z3mv_XUY_WU_T9Mw,179
66
66
  sourcecode/mcp/runner.py,sha256=7PnFjKYbgxFeDnqVeSntXHxZX7ZtK3-krDkEuVjI24M,1386
67
- sourcecode/mcp/server.py,sha256=T7775VLZFGtEh9CxfdKFP8z_7OK9fai9O5DHbyuegEQ,13437
67
+ sourcecode/mcp/server.py,sha256=QL-evpZsqF44xLVWo-b3dKv70Li9ElXk_SvCRB_w7WE,14311
68
68
  sourcecode/mcp/onboarding/__init__.py,sha256=sj2PWqEBmMc4zBNkomg89WtL0M6S7A9yb7_wAuSWNP4,66
69
69
  sourcecode/mcp/onboarding/applier.py,sha256=yfSMT0NKdZsjavtLkC8yQ7OtkfepOl5IXGByqg6bdEY,1894
70
70
  sourcecode/mcp/onboarding/backup.py,sha256=ihqGOR8QTX8HASRSEDyfFyXr5bkXrygPHamv4p9KTmk,1452
@@ -76,8 +76,8 @@ sourcecode/telemetry/consent.py,sha256=wLMvGNJeSSyZoNkQXpoUioY6mMv4Qdvuw7S9jAEWn
76
76
  sourcecode/telemetry/events.py,sha256=oEvvulfsv5GIDWG2174gSS6tNB95w38AIYiYeifGKlE,2294
77
77
  sourcecode/telemetry/filters.py,sha256=Asa71oRl7q3Wt_FMwuufIZJFzSYdgRNKS8LHCIyFeYE,4805
78
78
  sourcecode/telemetry/transport.py,sha256=KJeIPCPWMdmbCP3ySGs2iUlia34U6vWne2dZsUezesw,1560
79
- sourcecode-1.31.20.dist-info/METADATA,sha256=jAgvJ3ggn8x9HGWRoANJO5pu8_bQ74T1ftvcjAvDosE,31103
80
- sourcecode-1.31.20.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
81
- sourcecode-1.31.20.dist-info/entry_points.txt,sha256=ex3F9rmbXeyDIoFQHtkEqTsKSaJow8F0LrVu8XfIktQ,57
82
- sourcecode-1.31.20.dist-info/licenses/LICENSE,sha256=7DdHrU9Z_3e7dSvq4ISijZNjnuHo5NIHNiHDouMQ9JU,10491
83
- sourcecode-1.31.20.dist-info/RECORD,,
79
+ sourcecode-1.31.22.dist-info/METADATA,sha256=zjEDrWUQ-08LOjvIfXTDUgQ4UTPkneyr4CFGZc5yaOo,31103
80
+ sourcecode-1.31.22.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
81
+ sourcecode-1.31.22.dist-info/entry_points.txt,sha256=ex3F9rmbXeyDIoFQHtkEqTsKSaJow8F0LrVu8XfIktQ,57
82
+ sourcecode-1.31.22.dist-info/licenses/LICENSE,sha256=7DdHrU9Z_3e7dSvq4ISijZNjnuHo5NIHNiHDouMQ9JU,10491
83
+ sourcecode-1.31.22.dist-info/RECORD,,